本页内容
gNOI 诊断 (Diag) 服务
总结 使用 gNOI 诊断 (Diag) 服务测试两台设备之间链路的可靠性。
概述
使用服务 RPC 对 Diag 一对连接的端口执行误码率测试 (BERT)。 Diag 服务 proto 定义文件位于 https://github.com/openconfig/gnoi/blob/master/diag/diag.proto。
BERT 也称为伪随机二进制序列 (PRBS) 测试,可测试链路的可靠性。gNOI RPC 在 StartBERT() 一对连接的物理接口上启动双向 BERT。设备在链路上交换一组 1 和 0 的模式。设备会将收到的消息与已发送的消息进行比较,并计算错误数。错误数越少,链路质量就越高。
您必须在链路的两侧运行 gNOI BERT,以便设备可以比较结果。您测试的链路在 BERT 期间会关闭,BERT 结束后会重新连接。但是,如果运行 BERT 的一个设备重新启动,则链路将继续关闭,除非您在其他设备上停止 BERT。
您可以从几个预先确定的类型中选择测试模式。BERT 或 PRBS 测试模式以 PRBSx 的形式命名,其中 x 是一个数字。Junos 设备支持以下 GNOIBERT 测试模式:
- PRBS7
- PRBS9
- PRBS15
- PRBS23
- PRBS31
您必须为每个 gNOI BERT 提供唯一的操作 ID。启动 BERT、停止 BERT 和获取 BERT 结果的 RPC 通过 BERT 操作 ID 进行链接。运行新的 BERT 时,必须将操作 ID 更改为新字符串。由于 RPC 通过其操作 ID 来识别每个 BERT,因此您可以在具有相同 ID 的不同接口上运行多个BERT。
设备会保留最后 5 个 BERT 操作的结果。但是,保存的 BERT 结果不是永久的。如果系统重新启动,它们将丢失。
要查看特定保存的 BERT 操作的结果,请 GetBERTResultRequest 发送所需 BERT 操作 ID 的消息, result_from_all_ports 并将字段设置为 False。要查看不同 ID 的所有请求结果,请将 result_from_all_ports 消息中的 GetBERTResultRequest 字段设置为 True。
在设备上运行 GetBERTResult() RPC 时,RPC 将显示特定设备在 BERT 期间检测到的不匹配位数。由于没有配置 RPC 的通过或未通过标准,因此由用户来评估结果。您可能会看到大量错误,原因有很多,包括:
- 链路质量很差。
- 其中一台设备在 BERT 期间脱机。
- BERT 仅在一台设备上运行。
- BERT 不会同时在两台设备上启动和停止。
为了避免出现上一个错误,我们建议同时将 StartBERT() RPC 发送至两台设备。如果在一台设备上先启动 BERT,则第一个设备直到另一台设备上的 BERT 启动才会收到响应。第一个设备将缺乏响应记录为不匹配的比特。第一个设备会继续报告错误,直到第二个设备启动 BERT。如果无法同时启动 BERT,我们建议在 GetBERTResult() 上次启动 BERT 的设备上运行 RPC。由于第一个设备已经在运行 BERT,因此第二个设备不应报告任何误报位。
支持的 RPC
| 版本中引入的 | RPC | 说明 |
|---|---|---|
StartBERT() |
在一组端口上启动 BERT。Junos 设备支持以下 PRBS 模式用于 GNOIBERT:
|
Junos OS Evolved 22.2R1 |
StopBert() |
在一组端口上阻止已经正在进行的 BERT。 |
Junos OS Evolved 22.2R1 |
GetBERTResult() |
在 BERT 期间或完成之后获取 BERT 结果。 |
Junos OS Evolved 22.2R1 |
网络设备配置
- 按照配置 gRPC 服务中的说明,在网络设备上 配置 gRPC 服务。
- 按照配置 gNOI 服务中的说明,配置网络管理系统以支持 gNOI 操作。
- 对于要运行 BERT 的链路,请配置要匹配的服务器和对等接口速度。仅当接口速度匹配时,BERT 才会运行。
示例:运行 BERT
当某个接口上的 BERT 正在进行时,该接口上的物理链路会关闭。
配置 gNOI 客户端和服务器后,即可编写和执行应用程序来运行 BERT。在此示例中,客户端执行 gnoi_bert_client.py Python 应用程序以测试服务器与对等设备之间的链路。应用程序 gnoi_bert_client.py 可以根据参数启动 BERT、停止 BERT 或获取 BERT 结果。
首先,客户端使用 gnoi_bert_client.py 发送 StartBERT() RPC 在服务器和对等方上启动 BERT。BERT 运行时,服务器和对等方通过 et-1/0/2 和 et-1/0/3 接口之间的链路交换 BERT 测试数据包。
期间的网络拓扑
BERT 在设置的时间到期后结束。然后,客户端使用 GetBERTResult() RPC 第二次执行应用程序,以便从服务器获取 BERT 结果。
消息参数 StartBERTRequest 存储在 input_bert_start.json JSON 文件中。此文件指定 BERT 应使用 PRBS 模式 31 运行 60 秒。消息参数 GetBERTResultRequest 存储在 input_bert_get.json JSON 文件中。字段 result_from_all_ports 设置为 False,因此 GetBERTResult() RPC 仅从此端口检索此特定 BERT 的结果。BERT 操作 ID 同时位于 BERT-operation id 1 JSON 文件中。
应用程序会导入模块grpc_channel以建立通道。配置 gNOI 服务中介绍了该grpc_channel模块。此处显示应用程序文件和 JSON 文件。
gnoi_bert_client.py
"""gRPC gNOI BERT utility."""
from __future__ import print_function
import argparse
import json
import sys
import logging
from getpass import getpass
import diag_pb2
import diag_pb2_grpc
from grpc_channel import grpc_authenticate_channel_mutual
from google.protobuf.json_format import MessageToJson
from google.protobuf.json_format import ParseDict
def get_args(parser):
# Main arguments
parser.add_argument('--server',
dest='server',
type=str,
default='localhost',
help='Server IP or name. Default is localhost')
parser.add_argument('--port',
dest='port',
nargs='?',
type=int,
default=50051,
help='The server port. Default is 50051')
parser.add_argument('--client_key',
dest='client_key',
type=str,
default='',
help='Full path of the client private key. Default ""')
parser.add_argument('--client_cert',
dest='client_cert',
type=str,
default='',
help='Full path of the client certificate. Default ""')
parser.add_argument('--root_ca_cert',
dest='root_ca_cert',
required=True,
type=str,
help='Full path of the Root CA certificate.')
parser.add_argument('--user_id',
dest='user_id',
required=True,
type=str,
help='User ID for RPC call credentials.')
# BERT arguments
parser.add_argument('--input_file',
dest='input_file',
type=str,
default=None,
help='Input JSON file to convert to a Message Object. Default NULL string')
parser.add_argument('--output_file',
dest='output_file',
type=str,
default=None,
help='Output file. Default NULL string')
parser.add_argument('--message',
dest='message',
type=str,
default=None,
help='The type of Message Object. Must correspond to input file JSON. Default NULL string')
args = parser.parse_args()
return args
def check_inputs(args):
# Check each of the default=None arguments
if args.server is None:
print('\nFAIL: --server is not passed in\n')
return False
if args.port is None:
print('\nFAIL: server port (--port) is not passed in\n')
return False
if args.input_file is None:
print('\nFAIL: --input_file is not passed in\n')
return False
if args.output_file is None:
print('\nFAIL: --output_file is not passed in\n')
return False
if args.message is None:
print('\nFAIL: --message is not passed in\n')
return False
return True
# Create a dictionary where top-level keys match what is passed in via args.message
# The values are pointers to the relevant classes and method names needed to build/send message objects
MESSAGE_RELATED_OBJECTS = {
'StartBERTRequest': {
'msg_type': diag_pb2.StartBERTRequest,
'grpc': diag_pb2_grpc.DiagStub,
'method': 'StartBERT'
},
'StopBERTRequest': {
'msg_type': diag_pb2.StopBERTRequest,
'grpc': diag_pb2_grpc.DiagStub,
'method': 'StopBERT'
},
'GetBERTResultRequest': {
'msg_type': diag_pb2.GetBERTResultRequest,
'grpc': diag_pb2_grpc.DiagStub,
'method': 'GetBERTResult'
}
}
def send_rpc(channel, metadata, args):
if not check_inputs(args):
print('\nFAIL: One of the inputs was not as expected.\n')
return False
print('\nMessage Type is {}'.format(args.message))
# Message objects to send
msg_object_list = []
with open(args.input_file) as json_file:
user_input = json.load(json_file)
# Choose the Request Message Object type based on the --message type passed
request_message = MESSAGE_RELATED_OBJECTS[args.message]['msg_type']()
# Convert the dictionary to the type of message object specified by request_message
try:
msg_object_list.append(ParseDict(user_input, request_message))
except Exception as error:
print('\n\nError:\n{}'.format(error), file=sys.stderr)
raise
# Assemble callable object to use for sending, e.g. diag_pb2_grpc.DiagStub(channel).StartBERT
method = MESSAGE_RELATED_OBJECTS[args.message]['method']
send_message = getattr(
MESSAGE_RELATED_OBJECTS[args.message]['grpc'](channel), method)
# send the Request Object(s)
for msg_object in msg_object_list:
resp = send_message(msg_object, metadata=metadata)
print('\n\nResponse:\n{}'.format(resp))
print('=================================')
resp_json = MessageToJson(resp)
print('\n\nResponse JSON:\n{}'.format(resp_json))
with open(args.output_file, 'w') as data:
data.write(str(resp_json))
return True
def main():
parser = argparse.ArgumentParser()
args = get_args(parser)
grpc_server_password = getpass("gRPC server password for executing RPCs: ")
metadata = [('username', args.user_id),
('password', grpc_server_password)]
try:
# Establish grpc channel to network device
channel = grpc_authenticate_channel_mutual(
args.server, args.port, args.root_ca_cert, args.client_key, args.client_cert)
response = send_rpc(channel, metadata, args)
except Exception as e:
logging.error('Received error: %s', e)
print(e)
if __name__ == '__main__':
logging.basicConfig(filename='gnoi-testing.log',
format='%(asctime)s %(levelname)-8s %(message)s',
level=logging.INFO,
datefmt='%Y-%m-%d %H:%M:%S')
main()
input_bert_start.json
{
"bert_operation_id": "BERT-operation id 1",
"per_port_requests": [
{
"interface": {
"origin": "origin",
"elem": [
{"name": "interfaces"},
{"name": "interface", "key": {"name": "et-1/0/2"}}
]
},
"prbs_polynomial": "PRBS_POLYNOMIAL_PRBS31",
"test_duration_in_secs": "60"
}
]
}
input_bert_get.json
{
"bert_operation_id": "BERT-operation id 1",
"result_from_all_ports": false,
"per_port_requests": [
{
"interface": {
"origin": "origin",
"elem": [
{"name": "interfaces"},
{"name": "interface", "key": {"name": "et-1/0/2"}}
]
}
}
]
}
执行应用程序
-
从客户端运行
gnoi_bert_client.py应用程序,在对等方上启动 BERT(未显示)。然后运行gnoi_bert_client.py应用程序以在服务器上启动 BERT(如下所示)。要启动 BERT,请设置为messageStartBERTRequest并设置为input_fileinput_bert_start.json 文件路径。对于每台设备,输入文件应指定在该设备上测试的接口。状态BERT_STATUS_OK表示 BERT 启动成功。lab@gnoi-client:~/src/gnoi/proto$ python3 gnoi_bert_client.py --server 10.0.2.1 --port 50051 --root_ca_cert /etc/pki/certs/serverRootCA.crt --client_key /home/lab/certs/client.key --client_cert /home/lab/certs/client.crt --user_id gnoi-user --message StartBERTRequest --input_file diag/input_bert_start.json --output_file diag/output/bert-start-resp1.json gRPC server password for executing RPCs: Message Type is StartBERTRequest Response: bert_operation_id: "BERT-operation id 1" per_port_responses { interface { origin: "origin" elem { name: "interfaces" } elem { name: "interface" key { key: "name" value: "et-1/0/2" } } } status: BERT_STATUS_OK } ================================= Response JSON: { "bertOperationId": "BERT-operation id 1", "perPortResponses": [ { "interface": { "origin": "origin", "elem": [ { "name": "interfaces" }, { "name": "interface", "key": { "name": "et-1/0/2" } } ] }, "status": "BERT_STATUS_OK" } ] } -
(可选)运行 BERT 时,使用
show interfaces服务器或对等设备上的命令查看正在进行的 BERT 结果。当 BERT 运行时,PRBS 模式为Enabled。为了清晰起见,此示例中的输出被截断。user@server> show interfaces et-1/0/2 Physical interface: et-1/0/2, Enabled, Physical link is Down Interface index: 1018, SNMP ifIndex: 534 [...] PRBS Mode : Enabled PRBS Pattern : 31 PRBS Statistics Lane 0 : Error Bits : 0 Total Bits : 200000000000 Monitored Seconds : 8 Lane 1 : Error Bits : 0 Total Bits : 200000000000 Monitored Seconds : 8 Lane 2 : Error Bits : 0 Total Bits : 200000000000 Monitored Seconds : 8 Lane 3 : Error Bits : 0 Total Bits : 200000000000 Monitored Seconds : 8 Interface transmit statistics: Disabled Link Degrade : Link Monitoring : Disable [...] -
BERT 完成后,再次运行应用程序,
gnoi_bert_client.py并input_filemessageGetBERTResultRequest设置为 input_bert_get.json 文件路径,以获取测试结果。在此示例中,BERT 在一分钟测试中发现零错误。lab@gnoi-client:~/src/gnoi/proto$ python3 gnoi_bert_client.py --server 10.0.2.1 --port 50051 --root_ca_cert /etc/pki/certs/serverRootCA.crt --client_key /home/lab/certs/client.key --client_cert /home/lab/certs/client.crt --user_id gnoi-user --message GetBERTResultRequest --input_file diag/input_bert_get.json --output_file diag/output/bert-get-resp1.json gRPC server password for executing RPCs: Message Type is GetBERTResultRequest Response: per_port_responses { interface { origin: "origin" elem { name: "interfaces" } elem { name: "interface" key { key: "name" value: "et-1/0/2" } } } status: BERT_STATUS_OK bert_operation_id: "BERT-operation id 1" prbs_polynomial: PRBS_POLYNOMIAL_PRBS31 last_bert_start_timestamp: 1652379568178 last_bert_get_result_timestamp: 1652379688037 peer_lock_established: true error_count_per_minute: 0 total_errors: 0 } ================================= Response JSON: { "perPortResponses": [ { "interface": { "origin": "origin", "elem": [ { "name": "interfaces" }, { "name": "interface", "key": { "name": "et-1/0/2" } } ] }, "status": "BERT_STATUS_OK", "bertOperationId": "BERT-operation id 1", "prbsPolynomial": "PRBS_POLYNOMIAL_PRBS31", "lastBertStartTimestamp": "1652379568178", "lastBertGetResultTimestamp": "1652379688037", "peerLockEstablished": true, "errorCountPerMinute": [ 0 ], "totalErrors": "0" } ] }BERT 成功完成,并显示链路质量良好。