本页内容
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,请设置为message
StartBERTRequest
并设置为input_file
input_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_file
message
GetBERTResultRequest
设置为 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 成功完成,并显示链路质量良好。