• 产品与解决方案
  • 行业解决方案
  • 服务
  • 支持
  • 合作伙伴
  • 关于我们

16-Telemetry配置指导

目录

01-gRPC配置

本章节下载 01-gRPC配置  (497.00 KB)

01-gRPC配置


1 gRPC

1.1  gRPC简介

gRPC(Google Remote Procedure Call,Google远程过程调用)是Google发布的基于HTTP 2.0协议承载的高性能开源软件框架,提供了支持多种编程语言的、对网络设备进行配置和管理的方法。通信双方可以基于该软件框架进行二次开发。

1.1.1  gRPC协议栈

图1-1 gRPC协议栈分层

 

gRPC协议栈分层如图1-1所示。自下而上各层的含义为:

·     TCP传输层:TCP提供面向连接的、可靠的数据链路。

·     TLS(Transport Layer Security,传输层安全)传输层:该层是可选的,设备和采集器可以基于TLS协议进行通道加密和双向证书认证,实现安全通信。

·     HTTP 2.0应用层:gRPC承载在HTTP 2.0协议上,利用了该协议的首部数据压缩、单TCP连接支持多路请求、流量控制等性能增强特性。

·     gRPC层:定义了RPC(Remote Procedure Call,远程过程调用)的协议交互格式。公共RPC方法定义在公共proto文件中,例如grpc_dialout.proto。

·     内容层:用于承载编码后的业务数据。业务数据的编码格式包括:

¡     GPB(Google Protocol Buffer):高效的二进制编码格式,通过proto文件描述编码使用的数据结构。在设备和采集器之间传输数据时,该编码格式的数据比其他格式(如JSON)的数据具有更高的信息负载能力。

业务数据使用GPB格式编码时,需要配合对应的业务模块proto文件才能解码。

¡     JSON(JavaScript Object Notation):轻量级的数据交换格式,采用独立于编程语言的文本格式来存储和表示数据,易于阅读和编写。

业务数据使用JSON格式编码时,通过公共proto文件即可解码,无需对应的业务模块proto文件。

设备和采集器通信时,双方的proto文件必须保持一致才能解码。

1.1.2  gRPC网络架构

图1-2所示,gRPC网络采用客户端/服务器模型,使用HTTP 2.0协议传输报文。

图1-2 gRPC网络架构

 

gRPC网络的工作机制如下:

(1)     服务器通过监听指定服务端口来等待客户端的连接请求。

(2)     用户通过执行客户端程序登录到服务器。

(3)     客户端调用.proto文件提供的gRPC方法发送请求消息。

(4)     服务器回复应答消息。

H3C设备支持作为gRPC服务器或者gRPC客户端。

说明

.proto文件使用protocol buffers语言编写。protocol buffers是Google开发的数据描述语言,用于自定义数据结构并生成基于各种语言的代码,在序列化和结构化数据方面比XML语言更简单、解析更快。

 

1.1.3  基于gRPC的Telemetry技术

Telemetry是一项监控设备性能和故障的远程数据采集技术。H3C的Telemetry技术采用gRPC协议将数据从设备推送给网管的采集器。如图1-3所示,网络设备和网管系统建立gRPC连接后,网管可以订阅设备上指定业务模块的数据信息。

图1-3 基于gRPC的Telemetry技术

 

1.1.4  Dial-in模式和Dial-out模式

图1-3中,设备支持以下两种gRPC对接模式:

·     Dial-in模式:设备作为gRPC服务器,采集器作为gRPC客户端。由采集器主动向设备发起gRPC连接并订阅需要采集的数据信息。

Dial-in模式支持以下操作:

¡     Get操作:获取设备运行状态和运行配置。

¡     gNMI(gRPC Network Management Interface,gRPC网络管理接口)类操作,具体包括:

-     gNMI Capabilities操作:获取设备的能力集。

-     gNMI Get操作:获取设备运行状态和运行配置。

-     gNMI Set操作:向设备下发配置。

-     gNMI Subscribe操作:向设备订阅数据推送服务,包括事件触发类数据和周期采样类数据。该操作实现的功能与gRPC Dial-out模式类似。

¡     CLI操作:向设备下发命令行。

·     Dial-out模式:设备作为gRPC客户端,采集器作为gRPC服务器。设备主动和采集器建立gRPC连接,将设备上配置的订阅数据推送给采集器。

1.1.5  Telemetry数据模型

根据对编码格式支持能力的不同,Telemetry数据模型分为以下两种类型:

·     三层Telemetry数据模型。该模型下,数据经过以下三个层次的处理。

¡     RPC层:定义在公共proto文件grpc_dialout.proto和grpc_dialout_v3.proto中,提供消息格式等公共RPC方法。

¡     Telemetry层:定义在公共proto文件telemetry.proto中,提供数据采样相关服务。

¡     内容层:可承载GPB或JSON编码格式的业务数据。

RPC层和Telemetry层在图1-1所示的gRPC协议栈中属于gRPC层。

关于以上proto文件的介绍,请参见“2.2.2  Dial-out模式的proto文件”。

·     二层Telemetry数据模型。该模型下,数据仅经过RPC层和内容层两个层次处理,其中内容层的业务数据只支持JSON编码。

1.1.6  协议规范

与gRPC相关的协议规范有:

·     RFC 7540 - Hypertext Transfer Protocol version 2 (HTTP/2)

·     RFC 2818 - HTTP Over TLS

1.2  gRPC配置限制和指导

如果执行undo grpc enable命令关闭gRPC功能,所有gRPC相关配置都会被删除。

1.3  配置gRPC Dial-in模式

1.3.1  gRPC Dial-in模式配置任务简介

gRPC Dial-in模式配置任务如下:

(1)     配置gRPC服务

(2)     (可选)配置设备与采集器之间的安全通信

(3)     配置gRPC用户

(4)     (可选)开启gRPC Dial-in模式的日志功能

1.3.2  配置gRPC服务

(1)     进入系统视图。

system-view

(2)     开启gRPC功能。

grpc enable

缺省情况下,gRPC功能处于关闭状态。

(3)     (可选)配置gRPC服务的端口号。

grpc port port-number

缺省情况下,gRPC服务的端口号为50051。

修改端口号后,gRPC功能将重启,正在访问的客户端将被断开,客户端需要重新发送连接请求才能继续访问。

(4)     (可选)配置gRPC会话超时时间。

grpc idle-timeout minutes

缺省情况下,gRPC会话超时时间为5分钟。

1.3.3  配置设备与采集器之间的安全通信

1. 功能简介

缺省情况下,设备和采集器建立的gRPC连接是非加密的。配置本功能引用PKI域后,设备和采集器会基于TLS(Transport Layer Security,传输层安全)协议进行通道加密和双向证书认证,从而提高gRPC通信的安全性。

2. 配置限制和指导

指定的PKI域必须存在,并且PKI域中包含完整的证书和密钥,否则,设备和采集器仍将采用非加密方式建立gRPC连接。关于PKI的详细介绍,请参见“安全配置指导”中的“PKI”。

3. 操作步骤

(1)     进入系统视图。

system-view

(2)     配置设备和采集器建立gRPC连接时引用的PKI域。

grpc pki domain domain-name

缺省情况下,设备和采集器建立gRPC连接时不会引用PKI域。

1.3.4  配置gRPC用户

1. 功能简介

设备上需要为gRPC客户端创建本地用户,gRPC客户端才能与设备建立gRPC会话。

2. 配置步骤

(1)     进入系统视图。

system-view

(2)     添加设备管理类本地用户。

local-user user-name [ class manage ]

(3)     设置本地用户的密码。

password [ { hash | simple } password ]

缺省情况下,不存在本地用户密码,即本地用户认证时无需输入密码,只要用户名有效且其他属性验证通过即可认证成功。

(4)     配置本地用户的授权用户角色为network-admin。

authorization-attribute user-role user-role

缺省情况下,本地用户的授权用户角色为network-operator。

(5)     配置本地用户可以使用的服务类型为HTTPS服务。

service-type https

缺省情况下,未配置用户的服务类型。

有关local-userpasswordauthorization-attributeservice-type命令的详细介绍,请参见“BRAS业务命令参考”中的“AAA”。

1.3.5  开启gRPC Dial-in模式的日志功能

1. 功能简介

为了管理员定位gRPC问题的需要,可以开启gRPC日志功能,以便记录设备对gRPC报文的处理信息。

设备生成的gRPC日志信息会交给信息中心模块处理,信息中心模块的配置将决定日志信息的发送规则和发送方向。关于信息中心的详细描述请参见“网络管理和监控配置指导”中的“信息中心”。

如果gRPC操作频繁,设备会输出大量gRPC日志,这可能会影响设备性能,建议仅开启需关注的gRPC操作的日志功能。

2. 配置步骤

(1)     进入系统视图。

system-view

(2)     开启gRPC Dial-in模式的日志功能。请至少选择其中一项进行配置。

¡     开启gRPC Dial-in模式的RPC类操作日志功能。

grpc log dial-in rpc { all | { cli | get }* }

缺省情况下,gRPC Dial-in模式的RPC类操作日志功能处于关闭状态。

¡     开启gRPC Dial-in模式的gNMI类操作日志功能。

grpc log dial-in gnmi { all | { capabilities | get | set | subscribe }* }

缺省情况下,gRPC Dial-in模式的gNMI Set操作日志功能处于开启状态,其他gNMI类操作日志功能处于关闭状态。

1.4  配置gRPC Dial-out模式

1.4.1  gRPC Dial-out模式配置任务简介

gRPC Dial-out模式配置任务如下:

(1)     开启gRPC功能

(2)     (可选)配置设备与采集器之间的安全通信

(3)     配置传感器

(4)     配置采集器

(5)     配置订阅

(6)     (可选)开启gRPC Dial-out模式的日志功能

1.4.2  开启gRPC功能

(1)     进入系统视图。

system-view

(2)     开启gRPC功能。

grpc enable

缺省情况下,gRPC功能处于关闭状态。

(3)     (可选)配置gRPC使用的Telemetry数据模型。

grpc data-model { 2-layer | 3-layer }

缺省情况下,设备使用二层Telemetry数据模型上送数据。

设备使用二层Telemetry数据模型上送数据时,不支持对上送数据使用GPB编码格式。

1.4.3  配置设备与采集器之间的安全通信

1. 功能简介

缺省情况下,设备和采集器建立的gRPC连接是非加密的。配置本功能引用PKI域后,设备和采集器会基于TLS协议进行通道加密和双向证书认证,从而提高gRPC通信的安全性。

2. 配置限制和指导

指定的PKI域必须存在,并且PKI域中包含完整的证书和密钥,否则,设备和采集器仍将采用非加密方式建立gRPC连接。关于PKI的详细介绍,请参见“安全配置指导”中的“PKI”。

3. 操作步骤

(1)     进入系统视图。

system-view

(2)     配置设备和采集器建立gRPC连接时引用的PKI域。

grpc pki domain domain-name

缺省情况下,设备和采集器建立gRPC连接时不会引用PKI域。

1.4.4  配置传感器

1. 功能简介

设备通过传感器完成数据的采集。

采样路径用于指定需要采样的数据源,具体包括以下2种类型:

·     事件触发:传感器组的数据采样没有固定周期,仅由事件触发。关于事件触发类型的采样路径,请参见对应模块的《NETCONF XML API Event Reference》手册。

·     周期采样:传感器组以固定的时间间隔来进行数据采样。关于周期采样类型的采样路径,请参见对应模块的除《NETCONF XML API Event Reference》之外的其他NETCONF XML API手册。

2. 配置限制和指导

多次执行sensor path命令可配置多个采样路径和推送条件(condition)。同一采样路径最多支持5个推送条件,只有这些条件都满足时才推送数据。

采样路径为ifmgr/statistics时,支持在路径后直接附加过滤条件[ifindex=”index”]index表示接口名称或接口索引,为1~99个字符的字符串,不区分大小写。需要注意的是:

·     采样路径附加过滤条件时,不支持再配置推送条件(condition参数);反之亦然。

·     同一采样路径最多支持64个过滤条件,只要满足其中一个过滤条件就会推送数据。

·     index的最后一个字符可以为通配符“*”,例如sensor path ifmgr/statistics[ifindex="GigabitEthernet3/1/*"]

3. 配置步骤

(1)     进入系统视图。

system-view

(2)     进入Telemetry视图。

telemetry

(3)     创建传感器组,并进入传感器组视图。

sensor-group group-name

(4)     配置采样路径。

sensor path path [ condition node node operator operator value value | depth depth ]

1.4.5  配置采集器

1. 功能简介

采集器用于接收网络设备推送的采样数据。设备上需要建立目标组并在目标组中配置正确的采集器地址信息,才能和采集器通信。

2. 配置限制和指导

建议系统中创建的目标组数量不超过5个,否则会影响系统性能。

3. 配置步骤

(1)     进入系统视图。

system-view

(2)     进入Telemetry视图。

telemetry

(3)     创建目标组,并进入目标组视图。

destination-group group-name

(4)     配置采集器的地址和相关参数。

(IPv4网络)

ipv4-address ipv4-address [ port port-number ] [ vpn-instance vpn-instance-name ]

(IPv6网络)

ipv6-address ipv6-address [ port port-number ] [ vpn-instance vpn-instance-name ]

采集器的IPv6地址不能指定为IPv6链路本地地址。有关IPv6链路本地地址的介绍,请参见“三层技术-IP业务配置指导”中的“IPv6基础”。

多次执行本命令可配置多个采集器。配置本命令时,只要任意一个参数不同,就算不同的采集器。

1.4.6  配置订阅

1. 功能简介

完成传感器组和目标组的配置后,需要创建订阅并将二者关联,设备才能和目标组中的采集器建立gRPC连接,从而将订阅报文发送给采集器。

2. 配置步骤

(1)     进入系统视图。

system-view

(2)     进入Telemetry视图。

telemetry

(3)     创建订阅,并进入订阅视图。

subscription subscription-name

(4)     (可选)配置设备发送的订阅报文的DSCP优先级。

dscp dscp-value

缺省情况下,设备发送的订阅报文的DSCP优先级为0。

DSCP优先级的取值越大,报文的优先级越高。

(5)     (可选)配置设备发送订阅报文的源地址。

source-address { ipv4-address | interface interface-type interface-number | ipv6 ipv6-address }

缺省情况下,设备使用路由出接口的主IP地址作为发送订阅报文的源IP地址。

当设备发送订阅报文的源地址发生变化时,设备将会重新连接gRPC服务器。

(6)     (可选)配置上送数据的编码格式。

encoding { gpb | json }

缺省情况下,上送数据的编码格式为JSON。

仅当设备使用三层Telemetry数据模型时,可以对上送数据使用GPB编码格式。

(7)     配置关联传感器组。

sensor-group group-name [ sample-interval [ msec ] interval ]

当传感器组中的采样路径为事件触发类型时,请不要配置sample-interval参数,否则该采样路径不生效;当采样路径为周期采样类型时,必须配置sample-interval参数才会采样和推送数据。

(8)     配置关联目标组。

destination-group group-name

1.4.7  开启gRPC Dial-out模式的日志功能

1. 功能简介

为了管理员定位gRPC问题的需要,可以开启gRPC日志功能,以便记录设备对gRPC报文的处理信息。

设备生成的gRPC日志信息会交给信息中心模块处理,信息中心模块的配置将决定日志信息的发送规则和发送方向。关于信息中心的详细描述请参见“网络管理和监控配置指导”中的“信息中心”。

2. 配置限制和指导

gNMI模式的订阅不支持gRPC Dial-out模式的日志功能。

3. 配置步骤

(1)     进入系统视图。

system-view

(2)     开启gRPC Dial-out模式的日志功能。

grpc log dial-out { all | { event | sample }* }

缺省情况下,gRPC Dial-out模式的日志功能处于关闭状态。

1.5  gRPC显示和维护

在完成上述配置后,在任意视图下执行display命令可以显示配置后gRPC的运行情况,通过查看显示信息验证配置的效果。

表1-1 gRPC显示和维护

操作

命令

显示gRPC的相关信息

display grpc

 

1.6  gRPC典型配置举例

本章节主要描述设备上的命令行配置。采集器上的gRPC对接软件需要另外开发,请参见“gRPC配置指导”中的“Protocol Buffers编码介绍/gRPC对接软件二次开发举例”。

1.6.1  gRPC Dial-in模式的配置举例

1. 组网需求

图1-4所示,设备作为gRPC服务器与采集器相连。采集器为gRPC客户端。

通过在设备上配置gRPC Dial-in模式,使gRPC客户端可以订阅设备上的LLDP事件。

2. 组网图

图1-4 gRPC Dial-in模式配置组网图

 

3. 配置步骤

在开始下面的配置之前,假设gRPC服务器与gRPC客户端的IP地址都已配置完毕,并且它们之间路由可达。

(1)     配置Device(gRPC服务器)

# 开启gRPC功能。

<Device> system-view

[Device] grpc enable

# 创建本地用户test,配置该用户的密码,授权用户角色为network-admin,可以使用的服务类型为HTTPS服务。

[Device] local-user test

[Device-luser-manage-test] password simple 123456TESTplat&!

[Device-luser-manage-test] authorization-attribute user-role network-admin

[Device-luser-manage-test] service-type https

[Device-luser-manage-test] quit

(2)     配置gRPC客户端

a.     在gRPC客户端安装gRPC环境,具体安装方式请参考相关文档。

b.     获取H3C提供的.proto文件(该文件中已写入订阅LLDP事件的配置),并通过protocol buffers编译器生成特定语言(例如Java、Python、C/C++、Go)的执行代码。

c.     编写客户端程序,调用上一步生成的代码。

d.     执行客户端程序,登录到gRPC服务器。

4. 验证配置

当设备发生LLDP事件时,gRPC客户端成功收到设备上的订阅信息。

1.6.2  gRPC Dial-out模式的周期采样配置举例

1. 组网需求

图1-5所示,设备作为gRPC客户端与采集器相连。采集器为gRPC服务器,接收数据的端口号为50050。

通过在设备上配置gRPC Dial-out模式,使设备以10秒的周期向采集器推送接口模块的设备能力信息。

2. 组网图

图1-5 gRPC Dial-out模式配置组网图

 

3. 配置步骤

在开始下面的配置之前,假设设备与采集器的IP地址都已配置完毕,并且它们之间路由可达。

# 开启gRPC功能。

<Device> system-view

[Device] grpc enable

# 创建传感器组test,并添加采样路径为ifmgr/devicecapabilities。

[Device] telemetry

[Device-telemetry] sensor-group test

[Device-telemetry-sensor-group-test] sensor path ifmgr/devicecapabilities

[Device-telemetry-sensor-group-test] quit

# 创建目标组collector1,并配置IP地址为192.168.2.1、端口号为50050的采集器。

[Device-telemetry] destination-group collector1

[Device-telemetry-destination-group-collector1] ipv4-address 192.168.2.1 port 50050

[Device-telemetry-destination-group-collector1] quit

# 创建订阅A,配置关联传感器组为test,数据采样和推送周期为10秒,关联目标组为collector1。

[Device-telemetry] subscription A

[Device-telemetry-subscription-A] sensor-group test sample-interval 10

[Device-telemetry-subscription-A] destination-group collector1

[Device-telemetry-subscription-A] quit

4. 验证配置

采集器每10秒收到一次设备推送的数据信息。

1.6.3  gRPC Dial-out模式的事件触发采样配置举例

说明

订阅事件触发类型(一般带有“event”字符)的采样路径的传感器组时,请不要在sensor-group (subscription view)命令中指定周期采样参数sample-interval

 

1. 组网需求

图1-6所示,设备作为gRPC客户端与采集器相连。采集器为gRPC服务器,接收数据的端口号为50050。

通过在设备上配置gRPC Dial-out模式,使设备向采集器推送NetStream4模块的事件数据。

2. 组网图

图1-6 gRPC Dial-out模式配置组网图

 

3. 配置步骤

在开始下面的配置之前,假设设备与采集器的IP地址都已配置完毕,并且它们之间路由可达。

# 开启gRPC功能。

<Device> system-view

[Device] grpc enable

# 创建传感器组Test,并添加采样路径为netstream4/netstream4event。

[Device] telemetry

[Device-telemetry] sensor-group Test

[Device-telemetry-sensor-group-Test] sensor path netstream4/netstream4event

[Device-telemetry-sensor-group-Test] quit

# 创建目标组collector1,并配置IP地址为192.168.2.1、端口号为50050的采集器。

[Device-telemetry] destination-group collector1

[Device-telemetry-destination-group-collector1] ipv4-address 192.168.2.1 port 50050

[Device-telemetry-destination-group-collector1] quit

# 创建订阅B,配置关联传感器组为Test,关联目标组为collector1。

[Device-telemetry] subscription B

[Device-telemetry-subscription-B] sensor-group Test

[Device-telemetry-subscription-B] destination-group collector1

[Device-telemetry-subscription-B] quit

4. 验证配置

当设备的NetStream4模块上报事件后,采集器收到设备推送的事件数据。

 

 


2 Protocol Buffers编码介绍

2.1  Protocol Buffers编码格式

Protocol Buffers编码提供了一种灵活、高效、自动序列化结构数据的机制。Protocol Buffers与XML、JSON编码类似,不同之处在于Protocol Buffers是一种二进制编码,性能更高。

表2-1对比了Protocol Buffers和对应的JSON编码格式。

表2-1 Protocol Buffers和对应的JSON编码格式示例

Protocol Buffers编码

对应的JSON编码

{

1:“H3C”

2:“H3C”

3:“H3C device_test”

4:“Syslog/LogBuffer”

5:"notification": {

"Syslog": {

"LogBuffer": {

"BufferSize": 512,

"BufferSizeLimit": 1024,

"DroppedLogsCount": 0,

"LogsCount": 100,

"LogsCountPerSeverity": {

"Alert": 0,

"Critical": 1,

"Debug": 0,

"Emergency": 0,

"Error": 3,

"Informational": 80,

"Notice": 15,

"Warning": 1

},

"OverwrittenLogsCount": 0,

"State": "enable"

}

},

"Timestamp": "1527206160022"

}

}

{

"producerName": "H3C",

"deviceName": "H3C",

"deviceModel": "H3C device_test",

"sensorPath": "Syslog/LogBuffer",

"jsonData": {

"notification": {

"Syslog": {

"LogBuffer": {

"BufferSize": 512,

"BufferSizeLimit": 1024,

"DroppedLogsCount": 0,

"LogsCount": 100,

"LogsCountPerSeverity": {

"Alert": 0,

"Critical": 1,

"Debug": 0,

"Emergency": 0,

"Error": 3,

"Informational": 80,

"Notice": 15,

"Warning": 1

},

"OverwrittenLogsCount": 0,

"State": "enable"

}

},

"Timestamp": "1527206160022"

}

}

}

 

2.2  proto文件介绍

Protocol Buffers编码通过proto文件描述数据结构,用户可以利用Protoc等工具软件根据proto文件自动生成其他编程语言(例如Java、C++)代码,然后基于这些生成的代码进行二次开发,以实现gRPC设备对接。

H3C为Dial-in模式和Dial-out模式分别提供了proto文件。

2.2.1  Dial-in模式的proto文件

1. 公共proto文件

Dial-in模式支持以下公共proto文件:

·     grpc_service.proto文件:定义了Dial-in模式下的公共RPC方法。

·     gnmi.proto文件:定义了gNMI类操作的公共RPC方法。

·     gnmi_ext.proto文件:定义了gnmi.proto文件所需的扩展消息结构。

其中gnmi.proto和gnmi_ext.proto文件由Google提供,下载地址请参见“2.2.3  获取proto文件的方法

grpc_service.proto文件由H3C提供,其内容和含义如下:

syntax = "proto2";

package grpc_service;

message GetJsonReply { //Get方法应答结果

    required string result = 1;

}

message SubscribeReply { //订阅结果

    required string result = 1;

}

message ConfigReply { //配置结果

    required string result = 1;

}

message ReportEvent { //订阅事件结果定义

    required string token_id = 1; //登录token_id

    required string stream_name = 2; //订阅的事件流名称

    required string event_name = 3; //订阅的事件名

    required string json_text = 4; //订阅结果json字符串

}

message GetReportRequest{ //获取事件订阅结果请求

    required string token_id = 1; //登录成功后的token_id

}

message LoginRequest {  //登录请求参数定义

    required string user_name = 1; //登录请求用户名

    required string password = 2; //登录请求密码

}

message LoginReply {  //登录请求应答定义

    required string token_id = 1; //登录成功后返回的token_id

}

message LogoutRequest { //退出登录请求参数定义

    required string token_id = 1; //token_id

}

message LogoutReply { //退出登录返回结果定义

    required string result = 1; //退出登录结果

}

message SubscribeRequest { //定义事件流名称

    required string stream_name = 1;

}

message CliConfigArgs { //向设备下发配置命令,并指定命令行参数

    required int64 ReqId = 1; //配置命令请求ID

    required string cli = 2;  //配置命令

}

message CliConfigReply { //设备返回配置命令行执行的结果

    required int64 ResReqId = 1; //返回配置命令请求ID,与CliConfigArgs相对应

  required string output = 2;    //返回配置命令执行输出

    required string errors = 3;  //标记配置命令执行结果

}

message DisplayCmdArgs { //向设备下发display命令,并指定命令行参数

    required int64 ReqId = 1; //display命令请求ID

    required string cli = 2;  //display命令

}

message DisplayCmdReply { //设备返回display命令行执行的结果

    required int64 ResReqId =1;  //display命令请求ID,与DisplayCmdArgs相对应

    required string output = 2;  //返回display命令执行输出

    required string errors = 3;  //标记display命令执行结果

}

service GrpcService { //定义gRPC方法

    rpc Login (LoginRequest) returns (LoginReply) {}  //登录方法

    rpc Logout (LogoutRequest) returns (LogoutReply) {} //退出登录方法

    rpc SubscribeByStreamName (SubscribeRequest) returns (SubscribeReply) {} //订阅事件流

    rpc GetEventReport (GetReportRequest) returns (stream ReportEvent) {} //获取事件结果

    rpc CliConfig (CliConfigArgs)  returns (stream CliConfigReply) {} //gRPC支持通过命令行下发配置命令,并返回执行结果

    rpc DisplayCmdTextOutput(DisplayCmdArgs)  returns(stream DisplayCmdReply) {} //gRPC支持通过命令行下发display命令,并返回查询结果

}

2. 业务模块proto文件

Dial-in模式支持Device、Ifmgr、IPFW、LLDP、Syslog等业务模块proto文件。

以Device.proto文件为例,该文件定义了Device模块数据的RPC方法,其内容和含义如下:

syntax = "proto2";

import "grpc_service.proto";

package  device;

message DeviceBase { //获取设备基本信息结构定义

    optional string HostName = 1; //设备的名称

    optional string HostOid = 2; //sysoid

    optional uint32 MaxChassisNum = 3; //最大框数

    optional uint32 MaxSlotNum = 4; //最大slot数

    optional string HostDescription = 5; //设备描述信息

}

message DevicePhysicalEntities { //设备物理实体信息

    message Entity {

        optional uint32 PhysicalIndex = 1; //实体索引

        optional string VendorType = 2; //vendor类型

        optional uint32 EntityClass = 3;//实体类型

        optional string SoftwareRev = 4; //软件版本

        optional string SerialNumber = 5; //序列号

        optional string Model = 6; //模式

    }

    repeated Entity entity = 1;

}

service DeviceService { //定义的RPC方法

    rpc GetJsonDeviceBase(DeviceBase) returns (grpc_service.GetJsonReply) {} //获取设备基本信息

    rpc GetJsonDevicePhysicalEntities(DevicePhysicalEntities) returns (grpc_service.GetJsonReply) {} //获取设备实体信息

}

2.2.2  Dial-out模式的proto文件

1. 公共proto文件

Dial-out模式支持以下公共proto文件:

·     grpc_dialout.proto文件:定义了Dial-out模式下的公共RPC方法。

·     grpc_dialout_v3.proto文件:定义了Dial-out模式下三层Telemetry数据模型的公共RPC方法。

·     telemetry.proto文件定义了Dial-out模式下三层Telemetry数据模型的数据采样参数。

# grpc_dialout.proto文件的内容和含义如下:

syntax = "proto2";

package grpc_dialout;

message DeviceInfo{ //推送的设备信息

    required string producerName = 1; //厂商名

    required string deviceName = 2; //设备的名称

    required string deviceModel = 3; //设备型号

}

message DialoutMsg{   //推送的消息格式描述

    required DeviceInfo deviceMsg = 1; //DeviceInfo所描述的设备信息

    required string sensorPath = 2; //采样路径,对应netconf表的xpath路径

    required string jsonData = 3; //采样结果数据(JSON格式字符串)

}

message DialoutResponse{  //采集器(gRPC服务器)返回信息,预留(暂不处理返回值,可填充任意值)

    required string response = 1;

}

service GRPCDialout {  //推送方法

    rpc Dialout(stream DialoutMsg) returns (DialoutResponse);

}

# grpc_dialout_v3.proto文件的内容和含义如下:

syntax = "proto3";

package grpc_dialout_v3;

 

message DialoutV3Args{

    int64 ReqId = 1;//请求ID

    bytes data = 2;//承载的数据

    string errors = 3;//产生错误时的描述信息

    int32 totalSize = 4;//分片时信息的总大小,未分片时为0

}

service gRPCDialoutV3{

    rpc DialoutV3(stream DialoutV3Args) returns (stream DialoutV3Args) {};

}

# telemetry.proto文件的内容和含义如下:

syntax = "proto3";

package telemetry;

message Telemetry {

    string producer_name = 1;//厂商名

    string node_id_str = 2;//设备名

    string product_name = 3;//产品名

    string subscription_id_str = 15;//订阅名

    string sensor_path = 16;//采样路径

    uint64 collection_id = 17;//标识采样轮次

    uint64 collection_start_time = 18;//采样开始时间

    uint64 msg_timestamp = 19;//生成本消息的时间戳

    uint64 collection_end_time = 20;//采样结束时间

    uint32 current_period = 21;//采样精度

    string except_desc = 22;//异常描述信息

    enum Encoding {

        Encoding_JSON = 0;//表示GPB数据编码格式

        Encoding_GPB = 1;//表示JSON数据编码格式

        };

    Encoding encoding = 23;//数据编码格式

    string data_str = 24;// 数据编码非GPB时有效,否则为空

    TelemetryGPBTable data_gpb = 25;//承载的数据由TelemetryGPBTable定义

}

message TelemetryGPBTable {

    repeated TelemetryRowGPB row = 1;//数组定义,标识数据是TelemetryRowGPB结构的重复

}

message TelemetryRowGPB {

    uint64 timestamp = 1;//采样当前实例的时间戳

    bytes keys = 10;//保留字段

    bytes content = 11;//承载的采样实例数据

}

2. 业务模块proto文件

业务数据使用GPB格式编码时,需要配合对应的业务模块proto文件才能解码。

Dial-out模式支持Device、Ifmgr、Vlan、Syslog等业务模块proto文件。

以oc_vlan_v3.proto文件为例,该文件定义了VLAN模块数据的RPC方法,其内容如下:

syntax = "proto3";

package  vlans_v3;

message vlans {

    message Vlan {

        uint32 vlan_id = 1;

        message Config {

            uint32 vlan_id = 1;

            string name = 2;

            uint32 tpid = 3;

        }

        Config config = 2;

    }

    repeated Vlan vlan = 1;

}

2.2.3  获取proto文件的方法

gnmi.proto和gnmi_ext.proto文件的下载地址:

·     https://github.com/openconfig/gnmi/tree/master/proto/gnmi/gnmi.proto

·     https://github.com/openconfig/gnmi/tree/master/proto/gnmi_ext/gnmi_ext.proto

其他proto文件,请联系H3C技术支持提供。

2.3  gRPC对接软件二次开发举例

本举例开发的软件用于实现:

·     采集器获取设备数据(Dial-in模式下的Get操作、gNMI Capabilities操作、gNMI Get操作、gNMI Subscribe操作或Dial-out模式)

·     采集器向设备下发配置(Dial-in模式下的gNMI Set操作或CLI操作)

开发环境为Linux,编程语言以C++为例。

2.3.1  准备工作

(1)     获取proto文件:

¡     Dial-in模式下的Get操作:需要grpc_service.proto文件和具体业务模块对应的proto文件。

¡     Dial-in模式下的gNMI类操作:需要grpc_service.proto、gnmi.proto和gnmi_ext.proto文件。

¡     Dial-in模式下的CLI操作:需要grpc_service.proto文件。

¡     Dial-out模式:需要grpc_dialout.proto文件。

(2)     获取处理proto文件的工具软件protoc。

下载地址:https://github.com/google/protobuf/releases

(3)     获取对应开发语言的protobuf插件,例如C++插件protobuf-cpp。

下载地址:https://github.com/google/protobuf/releases

2.3.2  生成代码

生成proto文件对应的C++代码。

1. Dial-in模式

将需要的proto文件收集到当前目录下,例如grpc_service.proto和BufferMonitor.proto。

$protoc --plugin=./grpc_cpp_plugin  --grpc_out=. --cpp_out=. *.proto

2. Dial-out模式

将grpc_dialout.proto文件收集到当前目录下。

$ protoc --plugin=./grpc_cpp_plugin  --grpc_out=.  --cpp_out=. *.proto

2.3.3  开发代码(Dial-in模式)

对于Dial-in模式,主要是实现gRPC客户端代码(采集器上使用)。

由于生成的proto代码已经封装好了对应的服务类,只要在编写的客户端代码中调用其RPC方法,客户端就能够向设备(gRPC服务器)发起对应的RPC请求。

客户端代码主要包括以下3部分:

·     进行登录操作,获取token_id。

·     为要发起的RPC方法准备参数,用proto生成的服务类发起RPC调用并解析返回结果。

·     退出登录。

1. Get操作

以调用GrpcService和BufferMonitorService服务类为例,编码步骤如下:

(1)     编写一个GrpcServiceTest类。

在这个类中使用由grpc_service.proto生成的GrpcService::Stub类,通过grpc_service.proto自动生成的Login和Logout方法分别完成登录和退出。

class GrpcServiceTest

{

public:

    /* 构造函数 */

GrpcServiceTest(std::shared_ptr<Channel> channel): GrpcServiceStub(GrpcService::NewStub(channel))  {}

 

    /* 成员函数 */

    int Login(const std::string& username, const std::string& password);

void Logout();

void listen();

Status listen(const std::string& command);

 

    /* 成员变量 */

std::string token;

 

private:

std::unique_ptr<GrpcService::Stub> GrpcServiceStub;  //使用grpc_service.proto生成的GrpcService::Stub类

};

(2)     实现自定义的Login方法。

通过用户输入的用户名,密码调用GrpcService::Stub类的Login方法完成登录。

int GrpcServiceTest::Login(const std::string& username, const std::string& password)

{

    LoginRequest request;   //设置用户名密码

    request.set_user_name(username);

    request.set_password(password);

 

LoginReply reply;

ClientContext context;

 

//调用登录方法

Status status = GrpcServiceStub->Login(&context, request, &reply);

if (status.ok())

    {

     std::cout << "login ok!" << std::endl;

        std::cout <<"token id is :" << reply.token_id() << std::endl;

     token = reply.token_id();  //登录成功,获取到token.

        return 0;

    }

    else{

     std::cout << status.error_code() << ": " << status.error_message()

                 << ". Login failed!" << std::endl;

        return -1;

    }

}

(3)     发起对设备的RPC方法请求。

这里以订阅接口丢包事件举例:

rpc SubscribePortQueDropEvent(PortQueDropEvent) returns (grpc_service.SubscribeReply) {}

(4)     编写一个BufMon_GrpcClient类来封装发起的RPC方法。

使用BufferMonitor.proto自动生成的BufferMonitorService::Stub类完成RPC方法的调用。

class BufMon_GrpcClient

{

public:

    BufMon_GrpcClient(std::shared_ptr<Channel> channel): mStub(BufferMonitorService::NewStub(channel))

    {}

 

    std::string BufMon_Sub_AllEvent(std::string token);

    std::string BufMon_Sub_BoardEvent(std::string token);

    std::string BufMon_Sub_PortOverrunEvent(std::string token);

    std::string BufMon_Sub_PortDropEvent(std::string token);

 

    /* get 表项 */

    std::string BufMon_Sub_GetStatistics(std::string token);

    std::string BufMon_Sub_GetGlobalCfg(std::string token);

    std::string BufMon_Sub_GetBoardCfg(std::string token);

    std::string BufMon_Sub_GetNodeQueCfg(std::string token);

    std::string BufMon_Sub_GetPortQueCfg(std::string token);

 

private:

std::unique_ptr<BufferMonitorService::Stub> mStub; //使用BufferMonitor.proto自动生成的类

};

(5)     实现自定义的std::string BufMon_Sub_PortDropEvent(std::string token)方法完成接口丢包事件订阅。

std::string BufMon_GrpcClient::BufMon_Sub_PortDropEvent(std::string token)

{

    std::cout << "-------BufMon_Sub_PortDropEvent-------- " << std::endl;

 

PortQueDropEvent stNodeEvent;

PortQueDropEvent_PortQueDrop* pstParam = stNodeEvent.add_portquedrop();

 

    UINT uiIfIndex = 0;

    UINT uiQueIdx = 0;

    UINT uiAlarmType = 0;

 

    std::cout<<"Please input interface queue info : ifIndex queIdx alarmtype " << std::endl;

    cout<<"alarmtype : 1 for ingress; 2 for egress; 3 for port headroom"<<endl;

 

    std::cin>>uiIfIndex>>uiQueIdx>>uiAlarmType; //设置订阅参数,接口索引等。

    pstParam->set_ifindex(uiIfIndex);

    pstParam->set_queindex(uiQueIdx);

    pstParam->set_alarmtype(uiAlarmType);

 

    ClientContext context;

 

/* token need add to context */ //设置登录成功后返回的token_id

std::string key = "token_id";

std::string value = token;

context.AddMetadata(key, value);

SubscribeReply reply;

Status status = mStub->SubscribePortQueDropEvent(&context,stNodeEvent,&reply); //调用RPC方法

 

return reply.result();

}

(6)     循环等待事件上报。

在之前的GrpcServiceTest类中实现此方法,代码如下:

void GrpcServiceTest::listen()

{

    GetReportRequest reportRequest;

    ClientContext context;

    ReportEvent reportedEvent;

/* add token to request */

reportRequest.set_token_id(token);

 

    std::unique_ptr< ClientReader< ReportEvent>> reader(GrpcServiceStub->GetEventReport(&context, reportRequest)); //通过grpc_service.proto自动生成的类的GetEventReport来获取事件信息

 

    std::string streamName;     

    std::string eventName;

    std::string jsonText;

std::string token;

 

    JsonFormatTool jsonTool;

 

std::cout << "Listen to server for Event" << std::endl;

    while(reader->Read(&reportedEvent) ) //读取收到的上报事件

    {

        streamName = reportedEvent.stream_name();

        eventName = reportedEvent.event_name();

     jsonText = reportedEvent.json_text();

     token = reportedEvent.token_id();

 

      std::cout << "/***********EVENT COME**************/" << std::endl;

     std::cout << "TOKEN: " << token << std::endl;

     std::cout << "StreamName: "<< streamName << std::endl;

     std::cout << "EventName: " << eventName << std::endl;

     std::cout << "JsonText without format: " << std::endl << jsonText << std::endl;

     std::cout << std::endl;

     std::cout << "JsonText Formated: " << jsonTool.formatJson(jsonText) << std::endl;

        std::cout << std::endl;

    }

 

    Status status = reader->Finish();

    std::cout << "Status Message:" << status.error_message() << "ERROR code :" << status.error_code();

}

(7)     这样就完成了Dial-in模式的登录和RPC请求调用,最后调用Logout退出即可。

void GrpcServiceTest:: Logout ()

{

    LogoutRequest request;

request.set_token_id(token);

LogoutReply reply;

ClientContext context;

Status status = mStub->Logout(&context, request, &reply);

std::cout << "Logout! :" << reply.result() << std::endl;

}

2. gNMI Capabilities操作

编码步骤如下:

(1)     编写一个GrpcServiceTest类。

与Get操作相同。

(2)     实现自定义的Login方法。

与Get操作相同。

(3)     发起对设备的RPC方法请求。

rpc Capabilities(CapabilityRequest) returns (CapabilityResponse);

(4)     编写gnmi_client类来封装发起的RPC方法。

class gnmi_client

{

public:

explicit gnmi_client(const std::string &address,const std::string &tokenId);

 

bool TestCapabilities();

bool TestGet();

bool TestSet();

bool TestSubscribePool();

bool TestSubscribeOnce();

bool TestSubscribeStream();

bool TestSubscribeStreamWithAlias();

 

 

private:

void PrintCapabilityResponse(const gnmi::CapabilityResponse &response);

void PrintGetResponse(const gnmi::GetResponse &response);

void PrintSubscribeResponse(const gnmi::SubscribeResponse &response);

void PrintSubscribeRequest(const gnmi::SubscribeRequest &request);

void PrintGetRequest(const gnmi::GetRequest &request);

void PrintSetRequest(const gnmi::SetRequest &request);

 

void FillGetRequest(gnmi::GetRequest &request);

void FillSetRequest(gnmi::SetRequest &request);

void FillSubscribeRequestByOnce(gnmi::SubscribeRequest &request);

void FillSubscribeRequestByPool(gnmi::SubscribeRequest &request);

void FillSubscribeRequestByStream(gnmi::SubscribeRequest &request);

void FillSubscribePool(gnmi::SubscribeRequest &request);

void FillSubscribeAlias(gnmi::SubscribeRequest &request);

 

private:

std::unique_ptr<gnmi::gNMI::Stub> mStubGnmiService;

std::string mTokenID;  

};

(5)     实现自定义的TestCapabilities ()方法,以获取Capabilities内容。

bool gnmi_client::TestCapabilities()

{

CapabilityRequest request;

CapabilityResponse response;

ClientContext context;

 

  context.AddMetadata("token_id", mTokenID);

 

std::cout << std::endl << "CapabilitiesRequest => " << std::endl;

Status ret = mStubGnmiService->Capabilities(&context,request,&response);

 

if( StatusCode::OK != ret.error_code() )

{

     cout << "TestCapabilities ErrorMessage:" << ret.error_message() << endl;

     return false;

}

else

{

     PrintCapabilityResponse(response);

     return true;

}

}

(6)     调用Logout退出登录。

与Get操作相同。

3. gNMI Get操作

编码步骤如下:

(1)     编写一个GrpcServiceTest类。

与Get操作相同。

(2)     实现自定义的Login方法。

与Get操作相同。

(3)     发起对设备的RPC方法请求。

rpc Get(GetRequest) returns (GetResponse);

(4)     编写gnmi_client类来封装发起的RPC方法。

与gNMI Capabilities操作相同。

(5)     以获取Device/Base/HostName内容为例,实现自定义的TestGet方法。

bool gnmi_client::TestGet()

{

  GetRequest request;

  FillGetRequest(request);

  PrintGetRequest(request);

  GetResponse response;

  ClientContext context;

 

  context.AddMetadata("token_id", mTokenID);

 

  Status ret = mStubGnmiService->Get(&context,request,&response);

 

  if( StatusCode::OK != ret.error_code() )

  {

    cout << "TestGet ErrorMessage:" << ret.error_message() << endl;

    return false;

  }

  else

  {

    PrintGetResponse(response);

    return true;

  }

}

 

void gnmi_client::FillGetRequest(gnmi::GetRequest &request)

{

auto prefix = request.mutable_prefix();

auto pathelem01 = prefix->add_elem();

pathelem01->set_name("Device");

 

auto path1 = request.add_path();

auto pathelem02 = path1->add_elem();

pathelem02->set_name("Base");

auto pathelem03 = path1->add_elem();

pathelem03->set_name("HostName");

}

(6)     调用Logout退出登录。

与Get操作相同。

4. gNMI Set操作

(1)     编写一个GrpcServiceTest类。

与Get操作相同。

(2)     实现自定义的Login方法。

与Get操作相同。

(3)     发起对设备的RPC方法请求。

这里以Device模块为例,实现Set方法:

rpc Set(SetRequest) returns (SetResponse)

(4)     编写gNMITest类来封装发起的RPC方法。

使用gnmi.proto自动生成的gNMI::Stub类完成对RPC方法的调用。

class gNMITest

{

public:

gNMITest(std::shared_ptr<Channel> channel, const std::string tokenId ):

                                             mStubGrpcService(GrpcService::NewStub(channel)),

                                             mStubgNMIService(gNMI::NewStub(channel)),

                                             mTokenID(tokenId ){}

  SetResponse TestSetResponseInformation(SetRequest &request, const std::string tokenId);

 

/*---delete: Device/Base/HostName,将HostName恢复默认值-----------*/

SetResponse DeleteDeviceBaseHostName();      /* delete: Device Base/HostName*/

 

/* update: Device/Base/HostName, string_val("string_hostname") */

SetResponse UpdateDeviceBaseHostNameStringVal();

 

/* replace: Device/Base/HostName, string_val("string_hostname") */

REPL_001 SetResponse ReplaceDeviceBaseHostNameStringVal();  

 

private:

std::unique_ptr<GrpcService::Stub> mStubGrpcService;

std::unique_ptr<gNMI::Stub> mStubgNMIService;

std::string mTokenID;

};

(5)     实现自定义的方法以完成对业务模块(以Device为例)数据的Set操作。

// 调用Set方法,实现client与server通信,并返回response。

SetResponse gNMITest::TestSetResponseInformation(SetRequest &request, const std::string tokenId)

{

SetResponse reply;

ClientContext context;

context.AddMetadata("token_id", tokenId);

   

/* 调用Set方法 */

Status ret = mStubgNMIService->Set(&context,request,&reply);

if( StatusCode::OK != ret.error_code())

{

std::cout<<"error: "<<ret.error_message()<<std::endl;

}

return reply; 

}

// Delete操作

SetResponse gNMITest:: DeleteDeviceBaseHostName ()       /* prefix == Device/Base */

{

SetRequest   request;

/* SetRequest->prefix */

Path     *path01 = request.mutable_prefix();

PathElem *pathelem01 = path01->add_elem();  

pathelem01->set_name("Device"); 

PathElem *pathelem02 = path01->add_elem();

pathelem02->set_name("Base");

      

/* SetRequest->delete */

Path     *path02 = request.add_delete_();

PathElem *pathelem03 = path02->add_elem();

pathelem03->set_name("HostName");

/* gather response info. */ 

 return TestSetResponseInformation(request, mTokenID); 

}

// Update操作

SetResponse gNMITest::UpdateDeviceBaseHostNameStringVal()

{

SetRequest   request;

/* SetRequest->prefix */

Path     *path01 = request.mutable_prefix();

PathElem *pathelem01 = path01->add_elem();  

pathelem01->set_name("Device"); 

PathElem *pathelem02 = path01->add_elem();

pathelem02->set_name("Base");

/* SetRequest->update */

Update       *update01 = request.add_update();

Path     *path02 = update01->mutable_path();

PathElem *pathelem03 = path02->add_elem();

pathelem03->set_name("HostName");

TypedValue   *typevalue01 = update01->mutable_val();

typevalue01->set_string_val("string_hostname");

/* gather response info. */ 

return TestSetResponseInformation(request, mTokenID);

}

// Replace操作

SetResponse gNMITest::ReplaceDeviceBaseHostNameStringVal()

{

SetRequest   request;

/* SetRequest->prefix */

Path     *path01 = request.mutable_prefix();

PathElem *pathelem01 = path01->add_elem();  

pathelem01->set_name("Device"); 

PathElem *pathelem02 = path01->add_elem();

pathelem02->set_name("Base");

/* SetRequest->replace */

Update       *replace01 = request.add_replace();

Path     *path02 = replace01->mutable_path();

PathElem *pathelem03 = path02->add_elem();

pathelem03->set_name("HostName");

TypedValue   *typevalue01 = replace01->mutable_val();

typevalue01->set_string_val("string_hostname");

 

/* gather response info. */ 

return TestSetResponseInformation(request, mTokenID);

}

(6)     调用Logout退出登录。

与Get操作相同。

5. gNMI Subscribe操作

编码步骤如下:

(1)     编写一个GrpcServiceTest类。

与Get操作相同。

(2)     实现自定义的Login方法。

与Get操作相同。

(3)     发起对设备的RPC方法请求。

rpc Subscribe(stream SubscribeRequest) returns (stream SubscribeResponse);

(4)     编写gnmi_client类来封装发起的RPC方法。

与gNMI Capabilities操作相同。

(5)     实现自定义的订阅方法。

void gnmi_client::FillSubscribeRequestByOnce(gnmi::SubscribeRequest &request)

{

  auto subscribeList = request.mutable_subscribe();

 

  auto prefix = subscribeList->mutable_prefix();

  auto pathelem01 = prefix->add_elem();

  pathelem01->set_name("LLDP");

 

  auto subscribe = subscribeList->add_subscription();

 

  auto path = subscribe->mutable_path();

  auto pathelem02 = path->add_elem();

  pathelem02->set_name("NeighborEvent");

 

  auto pathelem03 = path->add_elem();

  pathelem03->set_name("Neighbor");

  (*pathelem03->mutable_key())["IfName"] = "xxx";

 

subscribeList->set_mode(::gnmi::SubscriptionList_Mode_ONCE);

subscribeList->set_encoding(::gnmi::JSON);

}

 

void gnmi_client::FillSubscribeRequestByPool(gnmi::SubscribeRequest &request)

{

auto subscribeList =  request.mutable_subscribe();

 

auto prefix = subscribeList->mutable_prefix();

auto pathelem01 = prefix->add_elem();

pathelem01->set_name("Device");

 

auto subscribe = subscribeList->add_subscription();

auto path = subscribe->mutable_path();

auto pathelem02 = path->add_elem();

pathelem02->set_name("CPUs");

auto pathelem03 = path->add_elem();

pathelem03->set_name("CPU");

auto pathelem04 = path->add_elem();

pathelem04->set_name("CPUUsage");

 

subscribeList->set_mode(::gnmi::SubscriptionList_Mode_POLL);

subscribeList->set_encoding(::gnmi::JSON);

}

 

void gnmi_client::FillSubscribeRequestByStream(gnmi::SubscribeRequest &request)

{

 auto subscribeList =  request.mutable_subscribe();

 

 auto prefix = subscribeList->mutable_prefix();

 auto pathelem01 = prefix->add_elem();

 pathelem01->set_name("Diagnostic");

 

 auto subscribe = subscribeList->add_subscription();

 

 auto path = subscribe->mutable_path();

 auto pathelem02 = path->add_elem();

 pathelem02->set_name("CPUEvent");

 auto pathelem03 = path->add_elem();

 pathelem03->set_name("CPU");

 (*pathelem03->mutable_key())["Chassis#condition"] = "equal:1";

 subscribe->set_mode(::gnmi::ON_CHANGE);

 subscribe->set_sample_interval(1000);

 subscribe->set_suppress_redundant(false);

 subscribe->set_heartbeat_interval(1000);

 

 subscribeList->set_mode(::gnmi::SubscriptionList_Mode_STREAM);

 subscribeList->set_encoding(::gnmi::JSON);

}

 

void gnmi_client::FillSubscribeAlias(gnmi::SubscribeRequest &request)

{

auto aliases = request.mutable_aliases();

auto alias = aliases->add_alias();

 

auto path = alias->mutable_path();

auto pathelem01 = path->add_elem();

pathelem01->set_name("Device");

auto pathelem02 = path->add_elem();

pathelem02->set_name("CPUs");

auto pathelem03 = path->add_elem();

pathelem03->set_name("CPU");

auto pathelem04 = path->add_elem();

pathelem04->set_name("CPUUsage");

 

alias->set_alias("#cpu_usage");

}

(6)     调用Logout退出登录。

与Get操作相同。

6. CLI操作

(1)     编写一个GrpcServiceTest类。

与Get操作相同。

(2)     实现自定义的Login方法。

与Get操作相同。

(3)     发起对设备的RPC方法请求。

这里使用grpc_service.proto文件中的CliConfig方法:

rpc CliConfig (CliConfigArgs)  returns (stream CliConfigReply) {}

(4)     使用GrpcServiceTest类封装发起的RPC请求。

见Get操作中的GrpcServiceTest类。

(5)     实现自定义的方法以支持CliConfig操作。

//  make a thread to listen the sever and get message

Status GrpcServiceTest::listen(const std::string& command)

{

CliConfigArgs reportRequest;

ClientContext context;

CliConfigReply reportedEvent;

std::string key = "token_id";

std::string value = token;

context.AddMetadata(key, value);

/* add token to request */

reportRequest.set_reqid(12345678);

reportRequest.set_cli(command);

 

std::unique_ptr< ClientReader< CliConfigReply>> reader(mStub->CliConfig(&context, reportRequest));

std::string streamName; 

std::string output; 

int64 resreqid;

 

std::cout << "Command result" << std::endl;

while( reader->Read(&reportedEvent) )

{

streamName = reportedEvent.errors();

output = reportedEvent.output();

resreqid = reportedEvent.resreqid();

std::cout << "resreqid: "<< resreqid << std::endl;

std::cout << "errors: "<< streamName << std::endl;

std::cout << "output: \n"<< output << "\n"<< std::endl;

}

Status status = reader->Finish();

return status;

}

(6)     循环等待下发命令。

在main函数中实现,并在下发命令之后返回命令执行结果(在GrpcServiceTest类中实现),main函数代码如下:

int main(int argc, char *argv[])

{

const char *cmd;

unsigned int i = 0;

unsigned int cycle = 0;

if (4 == argc)

{

g_server_address = argv[1];

g_username = argv[2];

g_password = argv[3];

std::cout << "server_address: " << g_server_address <<std::endl;

std::cout << "username: " << g_username << " " << "password: " << g_password << std::endl;

auto channel = grpc::CreateChannel(g_server_address,grpc::InsecureChannelCredentials());

 // 1. login

GrpcServiceTest reporter(channel);

if(0 != reporter.Login(g_username, g_password))

    {

         return 0;

    }

     while(1)

     {// 2.循环读取命令并执行

             std::cout<<"\n\nPlease Input Command:\n";

             getline(std::cin,g_command);            // 获取下发的命令

             Status status = reporter.listen(g_command);

             if (!status.ok())

            {

                 std::cout << status.error_code() << ": " << status.error_message() << std::endl;

                 break;

            }

    }

    }   

std::cout<<"Complete exec Command."<<std::endl;

return 0;

}

(7)     调用Logout退出登录。

与Get操作相同。

2.3.4  开发代码(Dial-out模式)

对于Dial-out模式,主要是实现服务端代码,使采集器(gRPC服务器)接收从设备上获取到的数据并进行解析。

服务端代码主要包括以下2部分:

·     继承自动生成的GRPCDialout::Service类,重载自动生成的RPC服务Dialout,并完成解析,获得相应字段内容。

·     将RPC服务注册到指定监听端口上。

编码步骤如下:

(1)     继承并重载RPC服务Dialout。

新建一个类DialoutTest并继承GRPCDialout::Service。

class DialoutTest final : public GRPCDialout::Service { //重载自动生成的抽象类

    Status Dialout(ServerContext* context, ServerReader< DialoutMsg>* reader, DialoutResponse* response) override; //实现Dialout RPC方法

};

(2)     将DialoutTest服务注册为gRPC服务,并指定监听端口。

using grpc::Server;

using grpc::ServerBuilder;

std::string server_address("0.0.0.0:60057"); //指定要监听的地址和端口

DialoutTest dialout_test; //定义(1)中声明的对象

ServerBuilder builder;

builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());//添加监听

builder.RegisterService(&dialout_test); //注册服务

std::unique_ptr<Server> server(builder.BuildAndStart()); //启动服务

server->Wait();

(3)     实现Dialout方法,实现数据解析。

Status DialoutTest::Dialout(ServerContext* context, ServerReader< DialoutMsg>* reader, DialoutResponse* response)

{

        DialoutMsg msg;

 

        while( reader->Read(&msg))

        {

            const DeviceInfo &device_msg = msg.devicemsg();

            std::cout<< "Producer-Name: " << device_msg.producername() << std::endl;

            std::cout<< "Device-Name: " << device_msg.devicename() << std::endl;

            std::cout<< "Device-Model: " << device_msg.devicemodel() << std::endl;

            std::cout<<"Sensor-Path: " << msg.sensorpath()<<std::endl;

            std::cout<<"Json-Data: " << msg.jsondata()<<std::endl;

            std::cout<<std::endl;

        }

        response->set_response("test");

 

        return Status::OK;

}

(4)     通过Read方法获取到proto文件生成的DialoutMsg对象后,可以调用对应的方法获取相应的字段值。

2.3.5  附录:gNMI类操作的方法和属性

1. gNMI类操作中添加列的方法

gNMI类操作中添加列的方法如表2-2所示。

表2-2 gNMI类操作中添加列的方法

请求描述

请求示例

客户端代码示例

对key中的列赋值
例如:
ifTypeExt=colvalue

path:
<
    elem:
    <
     name:"Interface"
     key:
     <
        name:"ifTypeExt"
        value:"colvalue"
     >
    >
>

  auto pathelem01 = path->add_elem();
  pathelem01->set_name("Interface");
  (*pathelem01->mutable_key())["ifTypeExt"] = "colvalue";

对key中的列属性赋值
例如:
ifTypeExt的属性attr=attrvalue

path:
<
    elem:
    <
     name:"Interface"
     key:
     <
        name:"ifTypeExt#attr"
        value:"attrvalue"
     >
    >
>

  auto pathelem01 = path->add_elem();
  pathelem01->set_name("Interface");
  (*pathelem01->mutable_key())["ifTypeExt#attr"] = "attrvalue";

对key中的分组的列赋值
例如:
Upper属于Broadcast组
Upper=colvalue

path:
<
    elem:
    <
     name:"Interface"
     key:
     <
        name:"Broadcast:Upper"
        value:"colvalue"
     >
    >
>

  auto pathelem01 = path->add_elem();
  pathelem01->set_name("Interface");
  (*pathelem01->mutable_key())["Broadcast:Upper"] = "colvalue";

对key中的分组的列的属性赋值
例如:
Upper属于Broadcast组,其属性为attr
attr=attrvalue

path:
<
    elem:
    <
     name:"Interface"
     key:
     <
        name:"Broadcast:Upper#attr"
        value:"attrvalue"
     >
    >
>

  auto pathelem01 = path->add_elem();
  pathelem01->set_name("Interface");
  (*pathelem01->mutable_key())["Broadcast:Upper#attr"] = "attrvalue";

通过key对列属性赋值
例如:
Interface有属性attr
attr=attrvalue

path:
<
    elem:
    <
     name:"Interface"
     key:
     <
        name:"#attr"
        value:"attrvalue"
     >
    >
>

  auto pathelem01 = path->add_elem();
  pathelem01->set_name("Interface");
  (*pathelem01->mutable_key())["#attr"] = "attrvalue";

对于JSON中的列和属性赋值
例如:
Upper的值为colvalue
Upper的属性attr的值为attrvalue

{
 "Upper": {
  "#text": "colvalue",
  "@#attr": "attrvalue"
 }
}

{
 "Upper": {
  "#text": "colvalue",
  "@#attr": "attrvalue"
 }
}

不同YANG文件中存在同名的顶层节点时,可以在顶层的同名节点名之前加上YANG模型名来精确指定该节点;如果不加YANG模型名,则自动匹配第一个找到的同名节点

例如:在节点interfaces前加上YANG模型名oc-if,表示指定OpenConfig组织发布的YANG文件中的interfaces节点

path:
<
    elem:
    <
     name:"oc-if:interfaces"
    >
>

  auto pathelem01 = path->add_elem();
  pathelem01->set_name("oc-if:interfaces");

 

2. gNMI Get操作支持的属性

gNMI Get操作支持的属性如下:

·     regExp(正则表达式)

当过滤条件比较复杂时,可以在指定列上设置regExp属性为一个正则表达式,以完成过滤的目的。如下示例用于获取接口的描述信息,并要求这些描述信息全部为大写字母,不能有其他字符。

auto prefix = request.mutable_prefix();

 auto pathelem01 = prefix->add_elem();

 pathelem01->set_name("Ifmgr");

 

 auto path1 = request.add_path();

 auto pathelem02 = path1->add_elem();

 pathelem02->set_name("Interfaces");

 auto pathelem03 = path1->add_elem();

 pathelem03->set_name("Interface");

 auto pathelem04 = path1->add_elem();

 pathelem04->set_name("Description");

 (*pathelem04->mutable_key())["#regExp"] = "^[A-Z]*$";

·     match(条件匹配)

条件匹配命令如表2-3所示。

表2-3 条件匹配命令

操作

命令

说明

大于

match="more:value"

值大于value,支持的数据类型为:日期、数字、字符串

小于

match="less:value"

值小于value,支持的数据类型为:日期、数字、字符串

不小于

match="notLess:value"

值不小于value,支持的数据类型为:日期、数字、字符串

不大于

match="notMore:value"

值不大于value,支持的数据类型为:日期、数字、字符串

等于

match="equal:value"

值等于value,支持的数据类型为:日期、数字、字符串、OID、BOOL

不等于

match="notEqual:value"

值不等于value,支持的数据类型为:日期、数字、字符串、OID、BOOL

包含

match="include:string"

包含字符串string,支持的数据类型为:字符串

不包含

match="exclude:string"

不能包含字符串string,支持的数据类型为:字符串

开始于

match="startWith:string"

以字符串string开头,支持的数据类型为:字符串、OID

结束于

match="endWith:string"

以字符串string结束,支持的数据类型为:字符串

 

如下示例用于获取实体扩展信息中CPU利用率大于50%的实体。

auto prefix = request.mutable_prefix();

 auto pathelem01 = prefix->add_elem();

 pathelem01->set_name("Device");

 

 auto path1 = request.add_path();

 auto pathelem02 = path1->add_elem();

 pathelem02->set_name("ExtPhysicalEntities");

 auto pathelem03 = path1->add_elem();

 pathelem03->set_name("Entity");

 auto pathelem04 = path1->add_elem();

 pathelem04->set_name("CpuUsage");

 (*pathelem04->mutable_key())["#match"] = "more:50";

·     valuetype(属性值类型)

获取接口数据时,如果IfIndex、vrfindex列的值为数字,设备无法识别该值为名称类型还是索引类型。此时,用户可以使用valuetype指定该值的类型。valuetype取值为:

属性

说明

name

值为名称类型

index

值为索引类型

auto

设备先按名称类型进行匹配,如果没有匹配到任何信息,再按照索引类型进行匹配

如果不指定valuetype的属性,缺省使用auto

 

如下示例中,IfIndex属性值为1,属性值类型为index:

auto prefix = request.mutable_prefix();

 auto pathelem01 = prefix->add_elem();

 pathelem01->set_name("VLAN");

 

 auto path1 = request.add_path();

 auto pathelem02 = path1->add_elem();

 pathelem02->set_name("TrunkInterfaces");

 auto pathelem03 = path1->add_elem();

 pathelem03->set_name("Interface");

(*pathelem03->mutable_key())["IfIndex"] = "1";

(*pathelem03->mutable_key())["IfIndex#valuetype"] = "index";

·     count

该属性用于获取指定数量的数据项。

如下为一个携带了count属性的示例:

auto prefix = request.mutable_prefix();

 auto pathelem01 = prefix->add_elem();

 pathelem01->set_name("Syslog");

 

 auto path1 = request.add_path();

 auto pathelem02 = path1->add_elem();

 pathelem02->set_name("Logs");

(*pathelem03->mutable_key())["count"] = "5";

 auto pathelem03 = path1->add_elem();

 pathelem03->set_name("Log");

(*pathelem03->mutable_key())["Index"] = "10";

·     值过滤

在Get操作中,对表中的列直接指定值,设备将对这些值进行严格匹配。如果指定了多个列的值,则返回同时符合这几个条件的数据。

如下为匹配IfIndex=1的示例:

auto prefix = request.mutable_prefix();

 auto pathelem01 = prefix->add_elem();

 pathelem01->set_name("VLAN");

 

 auto path1 = request.add_path();

 auto pathelem02 = path1->add_elem();

 pathelem02->set_name("TrunkInterfaces");

 auto pathelem03 = path1->add_elem();

 pathelem03->set_name("Interface");

(*pathelem03->mutable_key())["IfIndex"] = "1";

3. gNMI Set操作支持的属性

gNMI Set支持的属性如下:

·     incremental

该属性作用于集合性质的列,例如vlan permitlist列表。XML请求中有增量下发选项时,最终执行结果不影响本列原有的数据。

例如,下发一个接口的VLAN配置,使用增量下发,262接口原有Untagged VLAN列表为12~15,下发后为1~10,12~15。请求如下:

Path     *path01 = request.mutable_prefix();

PathElem *pathelem01 = path01->add_elem();  

pathelem01->set_name("VLAN");   

PathElem *pathelem02 = path01->add_elem();

pathelem02->set_name("HybridInterfaces");

Update   *Update01 = request.add_update();

Path     *path02 = Update01->mutable_path();

PathElem *pathelem03 = path02->add_elem();

pathelem03->set_name("Interface");

(*pathelem03->mutable_key())["IfName"] = "262";

(*pathelem03->mutable_key())["UntaggedVlanList#incremental"] = "true";

(*pathelem03->mutable_key())["UntaggedVlanList"] = "1-10";

·     valuetype

配置接口数据时,如果IfIndex和vrfindex列的值为数字,设备无法识别该值为名称类型还是索引类型。此时,用户可以使用valuetype指定该值的类型。valuetype取值为:

属性

说明

name

值为名称类型

index

值为索引类型

auto

设备先按名称类型进行匹配,如果没有匹配到任何信息,再按照索引类型进行匹配

如果不指定valuetype的属性,缺省使用auto

 

下面以IfIndex列值为index类型,值为1为例:

Path        *path01 = request.mutable_prefix();

PathElem    *pathelem01 = path01->add_elem();  

pathelem01->set_name("VLAN");  

PathElem    *pathelem02 = path01->add_elem();

pathelem02->set_name("TrunkInterfaces");

 

/* SetRequest->delete */

Path        *path02 = request.add_delete_();   

PathElem    *pathelem04 = path02->add_elem();

pathelem04->set_name("Interface ");

(*pathelem04->mutable_key())["IfIndex"] = "1";

(*pathelem04->mutable_key())["IfIndex#valuetype"] = "index";

4. gNMI Subscribe操作支持的属性

gNMI Subscribe支持的属性如下:

·     gNMI Get操作支持的所有属性,请参见“gNMI Get操作支持的属性

这些属性可用于周期式上报的数据表的订阅。

·     condition(数据推送条件)

该属性用于事件表的订阅,可选择的属性值包括:

¡     more:大于

¡     less:小于

¡     notLess:不小于

¡     notMore:不大于

¡     equal:等于

¡     notEqual:不等于

¡     include:包含

¡     exclude:不包含

¡     startWith:开始于

¡     endWith:结束于

如下示例用于监控LLDP事件:

  auto prefix = subscribeList->mutable_prefix();

  auto pathelem01 = prefix->add_elem();

  pathelem01->set_name("LLDP");

 

  auto subscribe = subscribeList->add_subscription();

  auto path = subscribe->mutable_path();

  auto pathelem02 = path->add_elem();

  pathelem02->set_name("NeighborEvent");

  auto pathelem03 = path->add_elem();

  pathelem03->set_name("Neighbor");

  (*pathelem03->mutable_key())["IfName"] = "GigabitEthernet1/0/1";

  (*pathelem03->mutable_key())["IfName#condition"] = "equal";

·     match(条件匹配)

该属性用于数据表的订阅,有以下两种使用方法:

¡     与固定值进行条件匹配:支持的匹配命令如表2-3所示。

¡     与其他列数据进行条件匹配(仅触发式上报的数据表支持)。可选择的属性值包括:

-     refMore:大于

-     refLess:小于

-     refNotLess:不小于

-     refNotMore:不大于

-     refEqual:等于

-     refNotEqual:不等于

下面以current-usage列值大于alarm-threshold列值触发条件为例:

  auto subscribe = subscribeList->add_subscription();

  auto path = subscribe->mutable_path();

  auto pathelem02 = path->add_elem();

  pathelem02->set_name("queue");

  auto pathelem03 = path->add_elem();

  pathelem03->set_name("state");

  (*pathelem03->mutable_key())["current-usage#match"] = "refMore:alarm-threshold";

·     select

该属性用于在触发式上报的数据表的订阅中选择列,表示仅订阅指定的列数据。

下面以订阅current-usage和available-usage为例:

  auto subscribe = subscribeList->add_subscription();

  auto path = subscribe->mutable_path();

  auto pathelem02 = path->add_elem();

  pathelem02->set_name("queue");

  auto pathelem03 = path->add_elem();

  pathelem03->set_name("state");

  (*pathelem03->mutable_key())["current-usage#select"] = "";

  (*pathelem03->mutable_key())["available-usage#select"] = "";

说明

gNMI类操作对象包括数据表和事件表:

·     数据表有两种类型:周期式上报和触发式上报。

·     事件表均为触发式上报。

 

 

 

不同款型规格的资料略有差异, 详细信息请向具体销售和400咨询。H3C保留在没有任何通知或提示的情况下对资料内容进行修改的权利!

新华三官网
联系我们