01-gRPC配置
本章節下載: 01-gRPC配置 (600.41 KB)
gRPC(Google Remote Procedure Call,Google遠程過程調用)是Google發布的基於HTTP 2.0協議承載的高性能開源軟件框架,提供了支持多種編程語言的、對網絡設備進行配置和管理的方法。通信雙方可以基於該軟件框架進行二次開發。
gRPC協議棧分層如表1-1所示。
表1-1 gRPC協議棧分層模型
分層 |
說明 |
內容層 |
業務模塊的數據 通信雙方需要了解彼此的數據模型,才能正確交互信息 |
Protocol Buffers編碼層 |
gRPC通過Protocol Buffers編碼格式承載數據 |
gRPC層 |
遠程過程調用,定義了遠程過程調用的協議交互格式 |
HTTP 2.0層 |
gRPC承載在HTTP 2.0協議上 |
TCP層 |
TCP連接提供麵向連接的、可靠的數據鏈路 |
如圖1-1所示,gRPC網絡采用客戶端/服務器模型,使用HTTP 2.0協議傳輸報文。
圖1-1 gRPC網絡架構
gRPC網絡的工作機製如下:
(1) 服務器通過監聽指定服務端口來等待客戶端的連接請求。
(2) 用戶通過執行客戶端程序登錄到服務器。
(3) 客戶端調用.proto文件提供的gRPC方法發送請求消息。
(4) 服務器回複應答消息。
H3C設備支持作為gRPC服務器或者gRPC客戶端。
.proto文件使用protocol buffers語言編寫。protocol buffers是Google開發的數據描述語言,用於自定義數據結構並生成基於各種語言的代碼,在序列化和結構化數據方麵比XML語言更簡單、解析更快。
Telemetry是一項監控設備性能和故障的遠程數據采集技術。H3C的Telemetry技術采用gRPC協議將數據從設備推送給網管的采集器。如圖1-2所示,網絡設備和網管係統建立gRPC連接後,網管可以訂閱設備上指定業務模塊的數據信息。
圖1-2 基於gRPC的Telemetry技術
圖1-2中,設備支持以下兩種gRPC對接模式:
· Dial-in模式:設備作為gRPC服務器,采集器作為gRPC客戶端。由采集器主動向設備發起gRPC連接並訂閱需要采集的數據信息。
Dial-in模式支持以下操作:
¡ Get操作:獲取設備運行狀態和運行配置,以及向設備訂閱事件。
¡ gNMI(gRPC Network Management Interface,gRPC網絡管理接口)類操作,具體包括:
- gNMI Capabilities操作:獲取設備的能力集。
- gNMI Get操作:獲取設備運行狀態和運行配置。
- gNMI Set操作:向設備下發配置。
- gNMI Subscribe操作:向設備訂閱數據推送服務,包括事件觸發類數據和周期采樣類數據。
¡ CLI操作:向設備下發命令行。
· Dial-out模式:設備作為gRPC客戶端,采集器作為gRPC服務器。設備主動和采集器建立gRPC連接,將設備上配置的訂閱數據推送給采集器。
根據對編碼格式支持能力的不同,Telemetry數據模型分為以下兩種類型:
· 三層Telemetry數據模型。該模型下,數據經過以下三個層次的處理。
¡ RPC層:定義在公共proto文件grpc_dialout.proto和grpc_dialout_v3.proto中,提供消息格式等公共RPC方法。
¡ Telemetry層:定義在公共proto文件telemetry.proto中,提供數據采樣相關服務。
¡ 內容層:可承載GPB或JSON編碼格式的業務數據。
RPC層和Telemetry層在gRPC協議棧中屬於gRPC層。
· 二層Telemetry數據模型。該模型下,數據僅經過RPC層和內容層兩個層次處理,其中內容層的業務數據隻支持JSON編碼。
gNMI(gRPC Network Management Interface,gRPC網絡管理接口)是基於gRPC框架開發的一種操作協議,定義了一係列用於設備狀態獲取和配置操作的RPC方法。
由於gNMI協議的通信報文與原gRPC有差異,因此,當采集器使用gNMI協議時,設備需要配合使用gNMI模式的訂閱來上送對應格式的報文。
與gRPC相關的協議規範有:
· RFC 7540 - Hypertext Transfer Protocol version 2 (HTTP/2)
僅非FIPS模式支持配置gRPC特性。有關FIPS的介紹,請參見“安全配置指導”中的“FIPS”。
如果執行undo grpc enable命令關閉gRPC功能,所有gRPC相關配置都會被刪除。
如果沒有配置設備發送訂閱報文的源地址(source-address命令),則Dial-out模式下推送給采集器的信息中,deviceIpAddr字段將顯示為not-config。
請根據實際組網情況選擇配置Dial-in模式或Dial-out模式。
(1) 配置gRPC Dial-in模式
設備作為gRPC服務器、采集器作為gRPC客戶端的組網中,在設備上進行如下配置:
a. 配置gRPC服務
b. 配置gRPC用戶
c. (可選)開啟gRPC Dial-in模式的日誌功能
(2) 配置gRPC Dial-out模式
設備作為gRPC客戶端、采集器作為gRPC服務器的組網中,在設備上進行如下配置:
a. 開啟gRPC功能
b. 配置傳感器
c. 配置采集器
d. 配置訂閱
e. (可選)開啟gRPC Dial-out模式的日誌功能
(3) (可選)配置設備與采集器之間的安全通信
(4) (可選)配置gRPC的CPU最大占用率
(1) 進入係統視圖。
system-view
(2) 開啟gRPC功能。
grpc enable
缺省情況下,gRPC功能處於關閉狀態。
(3) (可選)配置gRPC服務的端口號。
grpc port port-number
缺省情況下,gRPC服務的端口號為50051。
grpc idle-timeout minutes
缺省情況下,gRPC會話超時時間為5分鍾。
設備上需要為gRPC客戶端創建本地用戶,gRPC客戶端才能與設備建立gRPC會話。
(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-user、password、authorization-attribute和service-type命令的詳細介紹,請參見“安全命令參考”中的“AAA”。
為了管理員定位gRPC問題的需要,可以開啟gRPC日誌功能,以便記錄設備對gRPC報文的處理信息。
設備生成的gRPC日誌信息會交給信息中心模塊處理,信息中心模塊的配置將決定日誌信息的發送規則和發送方向。關於信息中心的詳細描述請參見“網絡管理和監控配置指導”中的“信息中心”。
如果gRPC操作頻繁,設備會輸出大量gRPC日誌,這可能會影響設備性能,建議僅開啟需關注的gRPC操作的日誌功能。
(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) 進入係統視圖。
system-view
(2) 開啟gRPC功能。
grpc enable
缺省情況下,gRPC功能處於關閉狀態。
(3) (可選)配置gRPC使用的Telemetry數據模型。
grpc data-model { 2-layer | 3-layer }
缺省情況下,設備使用二層Telemetry數據模型上送數據。
設備使用二層Telemetry數據模型上送數據時,不支持對上送數據使用GPB編碼格式。
設備通過傳感器完成數據的采集,具體實現方式為配置采樣的數據源,即采樣路徑。
傳感器組是一到多個采樣路徑的集合。傳感器組有兩種類型:普通傳感器組和gNMI模式的傳感器組。
采樣路徑包括以下類型:
· 事件觸發采樣:傳感器組的數據采樣沒有固定周期,僅由事件觸發。關於事件觸發采樣類型的采樣路徑,請參見對應模塊的《NETCONF XML API Event Reference》手冊。
· 周期采樣:傳感器組以固定的時間間隔來進行數據采樣。關於周期采樣類型的采樣路徑,請參見對應模塊的《NETCONF XML API Configuration Reference》和《NETCONF XML API Data Reference》手冊。
· 條件觸發采樣:傳感器組根據一定頻率檢測采樣路徑,如果滿足推送條件,則采集數據並上送給采集器。關於條件觸發采樣類型的采樣路徑以及相關的檢測頻率、推送條件,請聯係H3C技術支持。
條件觸發采樣類型的采樣路徑僅在gNMI模式的傳感器組中配置才能生效。
建議同一傳感器組內隻配置一種類型的采樣路徑。
(1) 進入係統視圖。
system-view
(2) 進入Telemetry視圖。
telemetry
(3) 創建普通傳感器組,並進入傳感器組視圖。
sensor-group group-name
(4) 配置采樣路徑。
sensor path path [ selection-nodes node-list ]
多次執行本命令可配置多個采樣路徑。多次執行本命令且指定的采樣路徑相同時,最後一次執行的命令生效。
(1) 進入係統視圖。
system-view
(2) 進入Telemetry視圖。
telemetry
(3) 創建gNMI模式傳感器組,並進入傳感器組視圖。
sensor-group group-name gnmi
通過本命令進入已經創建的gNMI模式的傳感器組視圖時,不需要攜帶gnmi關鍵字。
(4) 配置采樣路徑。
sensor path path
多次執行本命令可配置多個采樣路徑。
采集器用於接收網絡設備推送的采樣數據。設備上需要建立目標組並在目標組中配置正確的采集器地址信息,才能和采集器通信。
建議係統中創建的目標組數量不超過5個,否則會影響係統性能。
往目標組添加采集器有兩種方式:
· 指定IP地址方式
· 指定域名方式。使用該方式時需要注意:
¡ 通過域名指定采集器時,需要配置DNS(Domain Name System,域名係統),將采集器的域名解析為IPv4地址,訂閱報文才能上送給采集器。關於域名解析的配置,請參見“三層技術-IP業務配置指導”中的“域名解析”。
¡ 可在設備上通過display dns host命令查看域名解析信息。如果一個域名對應多個IP地址,則DNS解析該域名得到的第一個路由可達的IP地址將作為訂閱報文的上送地址。
用戶可以在係統視圖下通過grpc pki domain命令全局配置TLS加密(該加密方式使用PKI域中的證書),也可以在目標組視圖下配置與指定采集器之間的TLS加密(該加密方式使用設備出廠自帶的證書),且係統視圖下的配置優先級較高。
如果采集器位於VPN中,請先配置VPN實例,再配置采集器。關於VPN實例的配置,請參見“MPLS配置指導”中的“MPLS L3VPN”。
(1) 進入係統視圖。
system-view
(2) 進入Telemetry視圖。
telemetry
(3) 創建目標組,並進入目標組視圖。
destination-group group-name
(4) 配置采集器的IP地址和相關參數。
(IPv4網絡)
ipv4-address ipv4-address [ port port-number ] [ vpn-instance vpn-instance-name ] [ tls ]
(IPv6網絡)
ipv6-address ipv6-address [ port port-number ] [ vpn-instance vpn-instance-name ] [ tls ]
采集器的IPv6地址不能指定為IPv6鏈路本地地址。有關IPv6鏈路本地地址的介紹,請參見“三層技術-IP業務配置指導”中的“IPv6基礎”。
多次執行本命令可配置多個采集器。配置本命令時,隻要IP地址、端口號、VPN實例中任意一個屬性不同,則被設備視為不同的采集器。
(5) 配置采集器的域名和相關參數。
(IPv4網絡)
domain-name domain-name [ port port-number ] [ vpn-instance vpn-instance-name ] [ tls ]
(IPv6網絡)
ipv6 domain-name domain-name [ port port-number ] [ vpn-instance vpn-instance-name ] [ tls ]
多次執行本命令可配置多個采集器。配置本命令時,隻要域名、端口號、VPN實例中任意一個屬性不同,則被設備視為不同的采集器。
完成傳感器組和目標組的配置後,需要創建訂閱並將二者關聯,設備才能和目標組中的采集器建立gRPC連接,從而將訂閱報文發送給采集器。
訂閱分為普通訂閱和gNMI模式的訂閱。
普通訂閱僅支持關聯普通傳感器組,gNMI模式的訂閱僅支持關聯gNMI模式的傳感器組。
根據傳感器組中的采樣路徑的類型,關聯該傳感器組的訂閱的配置需要符合表1-2所示的條件,指定類型的采樣路徑才能生效。
傳感器組的采樣路徑類型 |
訂閱視圖下是否配置push-mode condition-triggered |
訂閱視圖下sensor-group命令是否指定sample-interval參數 |
訂閱視圖下sensor-group命令是否允許指定suppress-time參數 |
周期采樣 |
× |
√ |
× |
事件觸發采樣 |
× |
× |
× |
條件觸發采樣 |
√ |
× |
√ |
同一目標組不支持同時在普通訂閱和gNMI模式訂閱中使用。
(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) (可選)配置訂閱報文的JSON格式業務數據按行打時間戳。
json row-timestamp enable
缺省情況下,訂閱報文的JSON格式業務數據按訂閱報文打時間戳。
設備使用三層Telemetry數據模型時,不支持本命令。
(8) 配置關聯傳感器組。
sensor-group group-name [ sample-interval [ msec ] interval | suppress-time suppress-time ]
缺省情況下,未配置關聯傳感器組。
(9) 配置關聯目標組。
destination-group group-name
缺省情況下,未配置關聯目標組。
(1) 進入係統視圖。
system-view
(2) 進入Telemetry視圖。
telemetry
(3) 創建gNMI訂閱,並進入訂閱視圖。
subscription subscription-name gnmi
通過本命令進入已經創建的gNMI模式的訂閱視圖時,不需要攜帶gnmi關鍵字。
(4) (可選)配置訂閱的采樣數據推送模式為條件觸發推送模式。
push-mode condition-triggered
僅當訂閱的傳感器組中的采樣路徑類型為條件觸發采樣時,需要配置本命令。
缺省情況下,訂閱僅支持推送來自周期采樣和事件觸發采樣類型的采樣路徑的數據。
(5) (可選)配置設備發送的訂閱報文的DSCP優先級。
dscp dscp-value
缺省情況下,設備發送的訂閱報文的DSCP優先級為0。
DSCP優先級的取值越大,報文的優先級越高。
(6) (可選)配置設備發送訂閱報文的源地址。
source-address { ipv4-address | interface interface-type interface-number | ipv6 ipv6-address }
缺省情況下,設備使用路由出接口的主IP地址作為發送訂閱報文的源IP地址。
當設備發送訂閱報文的源地址發生變化時,設備將會重新連接gRPC服務器。
(7) 配置關聯傳感器組。
sensor-group group-name [ sample-interval [ msec ] interval | suppress-time suppress-time ]
缺省情況下,未配置關聯傳感器組。
(8) 配置關聯目標組。
destination-group group-name
缺省情況下,未配置關聯目標組。
為了管理員定位gRPC問題的需要,可以開啟gRPC日誌功能,以便記錄設備對gRPC報文的處理信息。
設備生成的gRPC日誌信息會交給信息中心模塊處理,信息中心模塊的配置將決定日誌信息的發送規則和發送方向。關於信息中心的詳細描述請參見“網絡管理和監控配置指導”中的“信息中心”。
gNMI模式的訂閱不支持gRPC Dial-out模式的日誌功能。
(1) 進入係統視圖。
system-view
(2) 開啟gRPC Dial-out模式的日誌功能。
grpc log dial-out { all | { event | sample }* }
缺省情況下,gRPC Dial-out模式的日誌功能處於關閉狀態。
缺省情況下,設備和采集器建立的gRPC連接是非加密的。配置本功能引用PKI域後,設備和采集器會基於TLS協議進行通道加密和雙向證書認證,從而提高gRPC通信的安全性。
指定的PKI域必須存在,並且PKI域中包含完整的證書和密鑰。關於PKI的配置,請參見“安全配置指導”中的“PKI”。
指定PKI域後,gRPC功能將重啟,與采集器的連接將短暫斷開。Dial-in模式下,采集器需要重新發送連接請求才能繼續訪問;Dial-out模式下,設備會自動連接采集器。
(1) 進入係統視圖。
system-view
(2) 配置設備和采集器建立gRPC連接時引用的PKI域。
grpc pki domain domain-name
缺省情況下,設備和采集器建立gRPC連接時不會引用PKI域。
gRPC功能在采樣數據時可能會占用大量CPU資源,為避免影響其他業務運行,可以配置gRPC的CPU最大占用率,使gRPC的CPU占用率超過配置值時,設備中止采樣,等CPU占用率降低後再繼續采樣。
(1) 進入係統視圖。
system-view
(2) 配置gRPC的CPU最大占用率。
grpc cpu-usage max-percent percentage
缺省情況下,gRPC沒有CPU占用率的限製。
在完成上述配置後,在任意視圖下執行display命令可以顯示配置後gRPC Dial-in模式的運行情況,通過查看顯示信息驗證配置的效果。
表1-3 gRPC顯示和維護
操作 |
命令 |
顯示gRPC的相關信息 |
display grpc [ verbose ] |
顯示有最小采樣周期的采樣路徑信息 |
display telemetry sensor-path |
本章節主要描述設備上的命令行配置。采集器上的gRPC對接軟件需要另外開發,請參見“2.3 gRPC對接軟件二次開發舉例”。
如圖1-3所示,設備作為gRPC服務器與采集器相連。采集器為gRPC客戶端。
通過在設備上配置gRPC Dial-in模式,使gRPC客戶端可以訂閱設備上的LLDP事件。
圖1-3 gRPC Dial-in模式配置組網圖
在開始下麵的配置之前,假設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服務器。
當設備發生LLDP事件時,gRPC客戶端成功收到設備上的訂閱信息。
訂閱事件觸發類型(一般帶有“event”字符)的采樣路徑的傳感器組時,請不要在sensor-group (subscription view)命令中指定周期采樣參數sample-interval。
如圖1-4所示,設備作為gRPC客戶端與采集器相連。采集器為gRPC服務器,接收數據的端口號為50050。
通過在設備上配置gRPC Dial-out模式,使設備向采集器推送接口模塊的事件數據。
圖1-4 gRPC Dial-out模式配置組網圖
在開始下麵的配置之前,假設設備與采集器的IP地址都已配置完畢,並且它們之間路由可達。
# 開啟gRPC功能。
<Device> system-view
[Device] grpc enable
# 創建傳感器組Test,並添加采樣路徑為ifmgr/interfaceevent。
[Device] telemetry
[Device-telemetry] sensor-group Test
[Device-telemetry-sensor-group-Test] sensor path ifmgr/interfaceevent
[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
當設備的接口模塊上報事件後,采集器收到設備推送的事件數據。
如圖1-5所示,設備作為gRPC客戶端與采集器相連。采集器為gRPC服務器,接收數據的端口號為50050。
通過在設備上配置gRPC Dial-out模式,使設備以10秒的周期向采集器推送接口模塊的設備能力信息。
圖1-5 gRPC Dial-out模式配置組網圖
在開始下麵的配置之前,假設設備與采集器的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
采集器每10秒收到一次設備推送的數據信息。
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" } } } |
Protocol Buffers編碼通過proto文件描述數據結構,用戶可以利用Protoc等工具軟件根據proto文件自動生成其他編程語言(例如Java、C++)代碼,然後基於這些生成的代碼進行二次開發,以實現gRPC設備對接。
H3C為Dial-in模式和Dial-out模式分別提供了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命令,並返回查詢結果
}
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) {} //獲取設備實體信息
}
Dial-out模式的公共proto文件包括:
· grpc_dialout.proto:定義了Dial-out模式下普通訂閱的公共RPC方法。
· grpc_dialout_v3.proto文件:定義了Dial-out模式下三層Telemetry數據模型的公共RPC方法。
· telemetry.proto文件定義了Dial-out模式下三層Telemetry數據模型的數據采樣參數。
· dial_out.proto:定義了Dial-out模式下gNMI模式訂閱的報文格式。
· gnmi.proto:定義了gNMI模式訂閱的公共RPC方法。
· gnmi_ext.proto:定義了gnmi.proto文件所需的擴展消息結構。
其中dial_out.proto文件由SONIC發布,gnmi.proto和gnmi_ext.proto文件由Google發布,下載地址請參見“2.2.3 獲取proto文件的方法”。
grpc_dialout.proto文件由H3C提供,其內容和含義如下:
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;//承載的采樣實例數據
}
業務數據使用GPB格式編碼時,需要配合對應的業務模塊proto文件才能解碼。
Dial-out模式支持Device、Ifmgr等業務模塊proto文件。
可聯係H3C技術支持提供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
dial-out.proto文件可從以下網站下載:
· https://github.com/Azure/sonic-telemetry/blob/master/proto/dial_out.proto
本舉例開發的軟件用於實現:
· 采集器獲取設備數據(Dial-in模式下的Get操作、gNMI Capabilities操作、gNMI Get操作、gNMI Subscribe操作或Dial-out模式)
· 采集器向設備下發配置(Dial-in模式下的gNMI Set操作或CLI操作)
開發環境為Linux,編程語言以C++為例。
(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文件。
¡ Dial-out模式下的gNMI模式訂閱:需要dial_out.proto、gnmi.proto和gnmi_ext.proto文件。
(2) 獲取處理proto文件的工具軟件protoc。
下載地址:https://github.com/google/protobuf/releases
(3) 獲取對應開發語言的protobuf插件,例如C++插件protobuf-cpp。
下載地址:https://github.com/google/protobuf/releases
生成proto文件對應的C++代碼。
將需要的proto文件收集到當前目錄下,例如grpc_service.proto和BufferMonitor.proto。
$protoc --plugin=./grpc_cpp_plugin --grpc_out=. --cpp_out=. *.proto
將grpc_dialout.proto文件收集到當前目錄下。
$ protoc --plugin=./grpc_cpp_plugin --grpc_out=. --cpp_out=. *.proto
對於Dial-in模式,主要是實現gRPC客戶端代碼(采集器上使用)。
由於生成的proto代碼已經封裝好了對應的服務類,隻要在編寫的客戶端代碼中調用其RPC方法,客戶端就能夠向設備(gRPC服務器)發起對應的RPC請求。
客戶端代碼主要包括以下3部分:
· 進行登錄操作,獲取token_id。
· 為要發起的RPC方法準備參數,用proto生成的服務類發起RPC調用並解析返回結果。
· 退出登錄。
以調用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;
}
編碼步驟如下:
(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操作相同。
編碼步驟如下:
(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操作相同。
(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操作相同。
編碼步驟如下:
(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操作相同。
(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操作相同。
對於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對象後,可以調用對應的方法獲取相應的字段值。
gNMI類操作中添加列的方法如表2-2所示。
表2-2 gNMI類操作中添加列的方法
請求描述 |
請求示例 |
客戶端代碼示例 |
對key中的列賦值 |
path: |
auto pathelem01 = path->add_elem(); |
對key中的列屬性賦值 |
path: |
auto pathelem01 = path->add_elem(); |
對key中的分組的列賦值 |
path: |
auto pathelem01 = path->add_elem(); |
對key中的分組的列的屬性賦值 |
path: |
auto pathelem01 = path->add_elem(); |
通過key對列屬性賦值 |
path: |
auto pathelem01 = path->add_elem(); |
對於JSON中的列和屬性賦值 |
{ |
{ |
不同YANG文件中存在同名的頂層節點時,可以在頂層的同名節點名之前加上YANG模型名來精確指定該節點;如果不加YANG模型名,則自動匹配第一個找到的同名節點 例如: 在節點interfaces前加上YANG模型名oc-if,表示指定OpenConfig組織發布的YANG文件中的interfaces節點 |
path: |
auto pathelem01 = path->add_elem(); |
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所示。
操作 |
命令 |
說明 |
大於 |
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";
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";
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"] = "Ten-GigabitEthernet3/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保留在沒有任何通知或提示的情況下對資料內容進行修改的權利!