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 | 指定比對正規表達式的時間。 | 布爾型,取值如下:
|
sync | 指定指令行觸發執行動作後,是否等待腳本執行結束。 |
|
async_skip | 在sync取值為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)
network | 指定路由字首。 | 點分十進制形式。 |
maskLen | 指定掩碼長度。 | 整數形式,取值範圍是0~32。 |
minLen | 指定掩碼長度比對範圍的下限。 | 整數形式,必須大于等于maskLen的值。預設值是None,表示掩碼長度比對範圍的下限是0。 |
maxLen | 指定掩碼長度比對範圍的上限。 | 整數形式,必須大于等于minLen的值。預設值是None,表示掩碼長度比對範圍的上限是0。 |
neLen | 指定不比對的掩碼長度。 | 整數形式,必須大于等于minLen的值,小于等于maxLen的值。預設值是None,表示不比對的掩碼長度是0。 |
optype | 指定路由事件變更類型。 | 枚舉類型,取值如下:
|
protocol | 指定路由協定屬性。 | 字元串形式,預設值為all,表示所有路由協定。
|
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配置偵聽事件,編寫事件所觸發的執行腳本,指令行能實作的功能都可以寫入腳本,也可以讀取系統變量來動态執行指令
以驅魔為理想,為生計而奔波