華為OPS,自定義命令,動態執行命令


 OPS

    開放可編程系統OPS(Open Programmability System)是指設備通過提供統一的應用程序接口API(Application Programming Interface)來開放系統,使得系統具備可編程能力。
華為設備OPS的使用分為訂閱和執行兩個階段,兩者關系類似於觸發器和active,在ops環境中兩個階段函數名固定為ops_condition,ops_execute兩個函數下可以自定義訂閱內容和執行語句,其中交換機內置一些環境變量,如用戶輸入,lldp鄰居狀態等,也支持用戶自定義變量
應用場景
  使用OPS功能可以自定義系統命令,例如可以自定義health命令來執行查看cpu,內存,溫度等一系列命令,獲取設備狀態
在割接時也可以自定義命令來執行一系列py腳本里預定義的配置
 
以S9300為例,列舉設備支持的OPS API

適用階段

OPS API

訂閱階段

命令行事件訂閱

定時器事件訂閱

路由變更事件訂閱

日志事件訂閱

告警事件訂閱

LLDP鄰居變化事件訂閱

單板狀態變化事件訂閱

端口統計事件訂閱

多條件關系組合

組合事件觸發器

執行階段

打開命令行通道

執行命令行命令

關閉命令行通道

向終端打印提示信息

從終端讀取用戶輸入

支持常駐腳本

返回事件執行結果

訂閱階段和執行階段

獲取環境變量

通過SNMP獲取設備信息(get)

通過SNMP獲取設備信息(getnext)

記錄日志

保存腳本變量

恢復腳本變量

OPS API示例
 
命令行事件訂閱
result1_value, result2_description = ops.cli.subscribe(tag, pattern, enter=False, sync=True, async_skip=False, sync_wait=30)
參數說明

參數

參數說明

取值

tag

用於標識條件。

字符串形式,不區分大小寫,長度范圍是1~12,由字母、數字和下划線組成,以字母開頭。tag不能為""、None、and、or以及andnot,不能包含\0。

pattern

指定匹配命令的正則表達式。

字符串形式,取值范圍是1~128個字符,不能包含\0。

enter

指定匹配正則表達式的時間。

布爾型,取值如下:

  • True:表示按回車鍵后立刻匹配正則表達式。

  • False:表示命令中縮寫的關鍵字以完整的形式進行匹配。

缺省值是False。

sync

指定命令行觸發執行動作后,是否等待腳本執行結束。

布爾型,取值如下:

  • True:表示等待。

  • False:表示不等待。

缺省值是True。

async_skip

在sync取值為False時,指定是否跳過原有命令執行。

布爾型,取值如下:

  • True:表示跳過。

  • False:表示不跳過。

缺省值是False。

sync_wait

在sync取值為True時,指定命令行同步等待腳本執行的時間。

整數形式,取值范圍是1~2147483647,單位是秒。缺省值是30秒。

 
路由變更事件訂閱
result1_value, result2_description = ops.route.subscribe(tag, network, maskLen, minLen=None, maxLen=None, neLen=None, optype=all, protocol=all)
參數說明

參數

參數說明

取值

tag

用於標識條件。

字符串形式,不區分大小寫,長度范圍是1~12,由字母、數字和下划線組成,以字母開頭。tag不能為""、None、and、or以及andnot,不能包含\0。

network

指定路由前綴。

點分十進制形式。

maskLen

指定掩碼長度。

整數形式,取值范圍是0~32。

minLen

指定掩碼長度匹配范圍的下限。

整數形式,必須大於等於maskLen的值。缺省值是None,表示掩碼長度匹配范圍的下限是0。

maxLen

指定掩碼長度匹配范圍的上限。

整數形式,必須大於等於minLen的值。缺省值是None,表示掩碼長度匹配范圍的上限是0。

neLen

指定不匹配的掩碼長度。

整數形式,必須大於等於minLen的值,小於等於maxLen的值。缺省值是None,表示不匹配的掩碼長度是0。

optype

指定路由事件變更類型。

枚舉類型,取值如下:

  • add:新增路由。

  • delete:刪除路由。

  • modify:修改路由。

  • all:全部變化。

缺省值是all。

protocol

指定路由協議屬性。

字符串形式,缺省值為all,表示所有路由協議。

  • direct:直連路由

  • static:靜態路由

  • ospf:OSPF路由

  • isis:IS-IS路由

  • bgp:BGP路由

  • rip:RIP路由

  • unr:用戶網絡路由

 
打開命令行通道
result1_handle, result2_description = ops.cli.open()
第一個返回值:命令行句柄。None表示錯誤,其他值為命令行句柄。第二個返回值:失敗原因(僅當第一個返回值為None時返回)。
使用說明
腳本中打開的命令行通道,用戶級別為15。
腳本中打開命令行通道后,才能向設備下發執行命令。
一個腳本中只能創建一個命令行通道,再創建第二個命令行通道時,將返回失敗。
每打開一個命令行通道,消耗一個VTY資源。通過display users命令可以看到該VTY資源被Assistant: Name占用。當設備上剩余的VTY資源少於等於3個時,打開命令行通道失敗。因此,腳本中,創建命令行通道並執行完命令后,需要通過關閉命令行通道接口(ops.cli.close(fd))及時關閉命令行通道,節省VTY資源。
執行命令行命令和關閉命令行通道接口使用打開命令行通道接口的第一個返回值作為輸入參數。因此使用打開命令行通道接口時,必須指定返回值

 

OPS 腳本模板

 1 # -*- coding: utf-8 -*-     # 聲明使用utf-8編碼格式,可以在Python腳本中添加中文注釋。
 2 
 3 # 固定語句,導入ops模塊。導入ops模塊后,才能在腳本中使用設備支持的OPS API。詳見OPS API列表。
 4 import ops
 5 # 固定語句,導入sys模塊。sys模塊負責程序與設備內置Python解釋器的交互,提供了一系列的函數和變量。
 6 # 導入sys模塊后,可以使用這些函數和變量。函數和變量的相關信息,請參考官方Python文檔。
 7 import sys
 8 # 固定語句,導入os模塊。os模塊負責程序與操作系統的交互,提供了訪問操作系統底層的接口。
 9 # 導入os模塊后,可以使用這些接口。接口的相關信息,請參考官方Python文檔。
10 import os
11 
12 # 固定語句,定義訂閱處理函數ops_condition。該函數在配置腳本助手的時候調用,用於訂閱事件,由設備內置的框架腳本_frame.py調度執行。
13 # 函數ops_condition的輸入參數是_frame.py中創建的ops對象,用戶可以在該對象下進行數據訪問。
14 def ops_condition(ops):
15     # 當前是訂閱階段,需要使用適用於訂閱階段的OPS API,詳見設備支持的OPS API中的表6-1。以下以定時器事件訂閱為例。
16     # 使用ops.timer.cron接口訂閱一個定時器,以t1標識。其含義為在每周一06:00觸發執行階段指定的動作。可以根據實際需求修改該定時器。
17     # 當輸入的參數值為字符串時,需要在字符串兩端使用雙引號。
18     # status和err_str為用戶定義的腳本變量,分別表示ops.timer.cron接口的第一個返回值和第二個返回值。
19     # 通常在調試階段,可以通過print語句將返回值打印出來,便於查看調試信息和定位問題。
20     # 對於OPS API,用戶可以根據實際需求決定是否需要返回值。如需要返回值,則必須根據各OPS API接口原型指定返回值的個數。
21     # 返回值的含義因OPS API而異。詳細描述請參見各OPS API。
22     # 對於ops.timer.cron接口,第一個返回值是數字時,0表示該API執行成功,1表示該API執行失敗。
23     # 第二個返回值僅當第一個返回值為1時返回,為字符串形式,描述執行失敗的原因。
24     status, err_str = ops.timer.cron("t1", "0 6 * * 1")
25     # 指定函數的返回值。函數的返回值可以作為處理結果,也可以通過result函數明確返回處理結果(詳見返回事件執行結果)。
26     # 函數的返回值作為函數的輸出,可以賦值給其他變量,作為其他函數的輸入。
27     # 這里指定函數ops_condition的返回值為0,表示返回值為0時,ops_condition函數執行成功。
28     return 0
29 
30 # 固定語句,定義執行處理函數ops_execute。該函數在腳本事件執行的時候調用,用於執行動作,由設備內置的框架腳本_frame.py調度執行。
31 # 函數ops_execute的輸入參數是_frame.py中創建的ops對象,用戶可以在該對象下進行數據訪問。
32 def ops_execute(ops):
33     # 當前是執行階段,需要使用適用於執行階段的OPS API,詳見設備支持的OPS API中的表6-1。
34     # 以下以打開命令行通道、執行命令行命令和關閉命令行通道為例。
35     # 打開命令行通道。只有打開命令行通道之后,才能執行命令行。執行完命令行之后,需要關閉命令行通道。
36     # handle和err_desp為用戶定義的腳本變量,分別表示ops.cli.open接口的第一個返回值和第二個返回值。
37     # 通常在調試階段,可以通過print語句將返回值打印出來,便於查看調試信息和定位問題。
38     # 執行命令行命令和關閉命令行通道接口使用打開命令行通道接口的第一個返回值作為輸入參數。因此使用打開命令行通道接口時,必須指定返回值。
39     handle, err_desp = ops.cli.open()
40     # 執行命令display interface gigabitethernet 1/0/1。
41     # 返回值result為None時,表示命令行未能發送給CLI或者命令行執行超時,其他值為顯示輸出,即CLI中執行的命令行。
42     # 返回值n11為Next:0表示后續沒有輸出了,Next:1表示后續還有輸出。返回值n12僅在返回值result為None時顯示,表示命令行執行失敗的原因。
43     result1, n11, n12 = ops.cli.execute(handle, "display interface gigabitethernet 1/0/1")
44     # 執行命令display current-configuration interface gigabitethernet 1/0/1。
45     result2, n21, n22 = ops.cli.execute(handle, "display current-configuration interface gigabitethernet 1/0/1")
46     # 命令行執行結束,關閉命令行通道。
47     result = ops.cli.close(handle)
48     # 將display interface gigabitethernet 1/0/1命令的顯示結果記錄到日志中,可以在日志文件中查看相應信息。
49     log1, descri_str1 = ops.syslog(result1, "informational", "syslog")
50     # 將display current-configuration interface gigabitethernet 1/0/1命令的顯示結果記錄到日志中,可以在日志文件中查看相應信息。
51     log2, descri_str2 = ops.syslog(result2, "informational", "syslog")
52     # 指定函數的返回值。函數的返回值可以作為處理結果,也可以通過result函數明確返回處理結果(詳見返回事件執行結果)。
53     # 函數的返回值作為函數的輸出,可以賦值給其他變量,作為其他函數的輸入。
54     # 這里指定函數ops_execute的返回值為0,表示返回值為0時,ops_execute函數執行成功。
55     return 0

 

OPS 功能示例
  偵聽LLDP鄰居狀態自動添加接口描述
  python腳本
 1 # -*- coding: utf-8 -*-
 2 import ops         # 導入ops模塊
 3 import sys         # 導入sys模塊
 4 import os          # 導入os模塊
 5 import re          # 導入re模塊,正則表達式
 6 # 訂閱處理函數
 7 def ops_condition (ops):
 8     # 檢測有新增鄰居事件,這里僅訂閱鄰居是交換機和路由器類型的事件,如果需要訂閱其他類型的事件,請按照下面格式補充
 9     value1, err_str1 = ops.lldp.subscribe("add1", ops.lldp.LLDP_NEIGHBOR_EVENT_ADD, "INTERFACE_ALL", ops.lldp.LLDP_NEIGHBOR_TYPE_SWITCH)
10     value11, err_str11 = ops.lldp.subscribe("add2", ops.lldp.LLDP_NEIGHBOR_EVENT_ADD, "INTERFACE_ALL", ops.lldp.LLDP_NEIGHBOR_TYPE_ROUTER)
11     
12     # 檢測有刪除鄰居事件
13     value2, err_str2 = ops.lldp.subscribe("delete1", ops.lldp.LLDP_NEIGHBOR_EVENT_DEL, "INTERFACE_ALL", ops.lldp.LLDP_NEIGHBOR_TYPE_SWITCH)
14     value22, err_str21 = ops.lldp.subscribe("delete2", ops.lldp.LLDP_NEIGHBOR_EVENT_DEL, "INTERFACE_ALL", ops.lldp.LLDP_NEIGHBOR_TYPE_ROUTER)
15     
16     # 組合事件,新增鄰居或刪除鄰居,最多支持8個事件組合
17     value10, err_str10 = ops.correlate("add1 or add2 or delete1 or delete2")
18     return 0
19 
20 # 工作處理函數
21 def ops_execute (ops):
22     # 獲取系統環境變量_lldp_event,表示事件觸發類型
23     key, value = ops.environment.get("_lldp_event")
24     inter, value = ops.environment.get("_lldp_interface")
25     
26     if key == "OPR_TYPE_ADD":
27     
28         # 打開命令行通道
29         handle, err_desp = ops.cli.open() 
30         neighbor, n11, n21 = ops.cli.execute(handle,"display lldp neighbor interface " + inter)
31         
32         resultsys = re.search(r'System[\s]+name[\s:]*\S*', neighbor).group()
33         sysname = re.split(':', resultsys)        
34 
35         resultport = re.search(r'Port\s+ID\s{2,}:*\S*', neighbor).group()
36         port = re.split(':', resultport)
37 
38         # 進入系統視圖
39         result, n11, n21 = ops.cli.execute(handle,"system-view")
40         
41         # 進入接口視圖
42         result, n11, n21 = ops.cli.execute(handle,"interface " + inter)
43         
44         # 設置接口描述信息
45         result, n11, n21 = ops.cli.execute(handle,"description " + "To-" + sysname[1] + "-" + port[1])
46 
47 
48         # 關閉命令行通道
49         result = ops.cli.close(handle)
50 
51         
52     elif key == "OPR_TYPE_DEL":
53         handle, err_desp = ops.cli.open() 
54         
55         # 進入系統視圖
56         result, n11, n21 = ops.cli.execute(handle,"system-view")
57         
58         # 進入接口視圖
59         result, n11, n21 = ops.cli.execute(handle,"interface " + inter)
60         
61         # 設置接口描述信息
62         result, n11, n21 = ops.cli.execute(handle,"undo description")
63         
64         # 關閉命令行通道
65         result = ops.cli.close(handle)
66 
67     else:
68         return 1
69     return 0
交換機配置
#下載lldp.py文件
tftp 10.0.64.74 get ops/lldp.py
#ops 安裝 lldp.py
ops install file lldp.py
#查看是否被安裝
<test>dir $_user/ 
Directory of flash:/$_user/

  Idx  Attr     Size(Byte)  Date        Time       FileName 
    0  drw-              -  Oct 15 2021 15:54:30   __pycache__
    1  -rw-          2,603  Oct 14 2021 15:29:20   lldp.py
    2  drw-              -  May 27 2020 11:35:19   huawei_pys
    3  -rw-            612  Oct 14 2021 16:43:13   dangerouscli.py
    4  -rw-            912  Oct 15 2021 11:47:06   ospfroute.py
    5  -rw-          2,145  Oct 15 2021 15:54:20   20211015.py
    6  -rw-            471  Oct 14 2021 15:25:17   portshutdown.py

#ops注冊
[test]ops
[test-ops] script-assistant python lldp.py

#設備開啟lldp
[test]lldp enable
#查看接口是否自動配置描述信息
[test]dis cu int MEth 0/0/1
#
interface MEth0/0/1
 description To-CN-ZhZ01-SW-B-eth-0-10
 ip address 10.0.3.105 255.255.255.0
#
#禁用LLDP
[test]undo lldp enable 
#查看接口描述信息是否被刪除
Info: Global LLDP is disabled successfully.
[test]dis cu int me
[test]dis cu int MEth 0/0/1
#
interface MEth0/0/1
 ip address 10.0.3.105 255.255.255.0
 
 
阻止危險命令並發出告警
  python腳本,切記腳本名不要和ban掉的命令重名
import ops
def ops_condition (ops):
    value1, descri_str1 = ops.cli.subscribe("cli1", "reboot", enter=False, sync=False,async_skip=True, sync_wait=60)
    value2, descri_str2 = ops.cli.subscribe("cli2", "stp disable", enter=False, sync=False,async_skip=True, sync_wait=60)
    value3, descri_str3 = ops.cli.subscribe("cli3", "stp enable", enter=False, sync=False,async_skip=True, sync_wait=60)
    value10, err_str10 = ops.correlate("cli1 or cli2 or cli3")
    return 0
def ops_execute (ops):
    value, descri_str = ops.terminal.write("Dangerous order, please contact the administrator to execute", vty="all")

驗證

#加載ops略
#執行關閉與開啟stp和reboot命令
[test]stp enable 
[test]
Dangerous order, please contact the administrator to execute

[test]undo stp enable 
[test]
Dangerous order, please contact the administrator to execute

[test]q  
<test>reboot
<test>
Dangerous order, please contact the administrator to execute
 
路由狀態聯動接口狀態
  腳本
import ops,sys
def ops_condition (ops):
    #result1_value, result2_description = ops.route.subscribe(tag, network, maskLen, minLen=None, maxLen=None, neLen=None, optype=all, protocol=all)
    #network可以自定義環境變量,在ops視圖下使用environment ospf_routes 10.2.1.0設定值
    #獲取自定義環境變量值
    slotid, errstr = ops.environment.get("ospf_routes")
    value,descri_str=ops.route.subscribe("route1", slotid, 24, minLen=None, maxLen=None, neLen=None, optype="all", protocol="ospf")
    return 0 
def ops_execute (ops):
    key,values = ops.environment.get("_routing_type")
    if key == "Delete":
        handle, err_desp = ops.cli.open()
        cli, n11, n21 = ops.cli.execute(handle,"sys")
        cli, n11, n21 = ops.cli.execute(handle,"interface vlan 1588")
        cli1, n12, n22 = ops.cli.execute(handle,"shutdown")
        result = ops.cli.close(handle)

  測試

#定義環境變量
[test-ops] environment ospf_routes 122.114.1.0
#加載ops腳本略
#查看ospf路由
[test]dis ospf routing 122.114.1.0

         OSPF Process 1 with Router ID 192.168.35.60

 Destination : 122.114.1.0/24     
 AdverRouter : 5.5.5.5                  Area      : 0.0.0.0  
 Cost        : 2                        Type      : Stub 
 NextHop     : 10.35.0.133              Interface : Vlanif1588  
 Priority    : Low                      Age       : 22h42m42s  
[test]
#刪除接口ospf后查看vlanif接口是否自動down
interface Vlanif1588
 ip address 10.35.0.134 255.255.255.252
 ospf enable 1 area 0.0.0.0
#
return
[test-Vlanif1588]undo ospf enable ar 0
[test-Vlanif1588]dis this
#
interface Vlanif1588
 shutdown
 ip address 10.35.0.134 255.255.255.252
#
return
[test-Vlanif1588]
 
 
割接,自定義三條命令,start開始做割接配置,rollback回滾配置,end刪除ops腳本
  腳本
import ops,sys,os,re
def ops_condition (ops):
    value, descri_str = ops.cli.subscribe("cli1", "start", enter=True, sync=False,async_skip=True, sync_wait=60)
    value1, descri_str1 = ops.cli.subscribe("cli2", "rollback", enter=True, sync=False,async_skip=True, sync_wait=60)
    value2, descri_str2 = ops.cli.subscribe("cli3", "end", enter=True, sync=False,async_skip=True, sync_wait=60)
    value10, err_str10 = ops.correlate("cli1 or cli2 or cli3")
    return 0
def ops_execute (ops):
    key,value = ops.environment.get('_cli_command')
    if key =="start":
        #value, descri_str = ops.terminal.write(key, vty="all")
        handle, err_desp = ops.cli.open()
        ops.cli.execute(handle,"system-view")
        ops.cli.execute(handle,"ospf")
        ops.cli.execute(handle,"ar 0")
        ops.cli.execute(handle,"vlan 1587")
        ops.cli.execute(handle,"interface vlan 1587")
        ops.cli.execute(handle,"ip address 10.35.0.33 30")
        ops.cli.execute(handle,"ospf enable area 0")
        ops.cli.execute(handle,"interface XGigabitEthernet 0/0/5")
        ops.cli.execute(handle,"port link-type trunk")
        ops.cli.execute(handle,"undo port trunk allow-pass vlan 1")
        ops.cli.execute(handle,"port trunk allow-pass vlan 1587")
        ops.cli.execute(handle,"quit")
        result = ops.cli.close(handle)
    elif key =="rollback":
        handle, err_desp = ops.cli.open()
        ops.cli.execute(handle,"system-view")
        ops.cli.execute(handle,"undo interface vlan 1587")
        ops.cli.execute(handle,"undo vlan 1587")
        ops.cli.execute(handle,"interface XGigabitEthernet 0/0/5")
        ops.cli.execute(handle,"undo port trunk allow-pass vlan 1587")
        ops.cli.execute(handle,"undo port link-type")
        ops.cli.execute(handle,"quit")
        result = ops.cli.close(handle)   
    elif key =='end':
        handle, err_desp = ops.cli.open()
        ops.cli.execute(handle,"system-view") 
        ops.cli.execute(handle,"ops")
        ops.cli.execute(handle,"undo script-assistant python 20211015.py")
        ops.cli.execute(handle,"quit")
        ops.cli.execute(handle,"quit")
        ops.cli.execute(handle,"ops uninstall file 20211015.py")  
        ops.cli.execute(handle,"delete 20211015.py")
        ops.cli.execute(handle,"y")  
        result = ops.cli.close(handle)         

測試

[test]dis vlan 1587
Error: The VLAN does not exist.
[test]start
[test]dis vlan 1587
--------------------------------------------------------------------------------
U: Up;         D: Down;         TG: Tagged;         UT: Untagged;
MP: Vlan-mapping;               ST: Vlan-stacking;
#: ProtocolTransparent-vlan;    *: Management-vlan;
--------------------------------------------------------------------------------

VID  Type    Ports                                                          
--------------------------------------------------------------------------------
1587 common  TG:XGE0/0/5(D)                                                     

VID  Status  Property      MAC-LRN Statistics Description      
--------------------------------------------------------------------------------
1587 enable  default       enable  disable    VLAN 1587                         
[test]dis cu int vlan 1587
#
interface Vlanif1587
 ip address 10.35.0.33 255.255.255.252
 ospf enable 1 area 0.0.0.0
#
return
[test]rollback
[test]dis vlan 1587
Error: The VLAN does not exist.
[test]end
[test]dis ops assistant  current 
------------------------------------------------------------------
Assistant                                State           Condition
------------------------------------------------------------------
lldp.py                                  ready           multi
dangerouscli.py                          ready           multi
ospfroute.py                             ready           URM
------------------------------------------------------------------

 華為ops核心思想

  調用OPS API配置偵聽事件,編寫事件所觸發的執行腳本,命令行能實現的功能都可以寫入腳本,也可以讀取系統變量來動態執行命令


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM