From cd28ae90c790a00990acb90884a9df73967fd3a9 Mon Sep 17 00:00:00 2001 From: "th.l" <thl-cmk@outlook.com> Date: Sun, 1 May 2022 18:14:06 +0200 Subject: [PATCH] update project --- agent_based/huawei_bgp_peer.py | 75 ++++++++++++++++++--------------- huawei_bgp_peer.mkp | Bin 3255 -> 3323 bytes 2 files changed, 41 insertions(+), 34 deletions(-) diff --git a/agent_based/huawei_bgp_peer.py b/agent_based/huawei_bgp_peer.py index 7c5fff3..7746ade 100644 --- a/agent_based/huawei_bgp_peer.py +++ b/agent_based/huawei_bgp_peer.py @@ -12,7 +12,9 @@ # Monitor status of Huawei BGP Peers (IPv4 and IPv6) # # 2020-07-20: added BGP prefix counter +# 2022-04-30: code cleanup/streamlining # + # snmpwalk sample # .1.3.6.1.4.1.2011.5.25.177.1.1.2.1.1.0.2.1.2.16.32.32.9.200.0.2.0.1.0.0.0.0.31.100.0.7 = Gauge32: 4 # .1.3.6.1.4.1.2011.5.25.177.1.1.2.1.2.0.2.1.2.16.32.32.9.200.0.2.0.1.0.0.0.0.31.100.0.7 = Gauge32: 16374 @@ -55,7 +57,7 @@ # HUAWEI-BGP-VPN-MIB::hwBgpPeerEntry.11.0.ipv4.unicast.ipv4."79.200.120.2" = INTEGER: 2 -from typing import List, Dict, Optional +from typing import List, Dict from cmk.base.plugins.agent_based.agent_based_api.v1 import ( register, @@ -68,13 +70,13 @@ from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( ) from cmk.base.plugins.agent_based.utils.bgp_peer import ( - bgp_get_peer, + bgp_get_ip_address_from_oid, BgpPeer, bgp_get_peer_entry, ) -def _huawei_get_adress_family(OID_END): +def _huawei_get_adress_family(afi, safi): # HWBgpAfi ::= TEXTUAL-CONVENTION # STATUS current # DESCRIPTION @@ -101,15 +103,13 @@ def _huawei_get_adress_family(OID_END): 132: 'route-target', } - OID_END = OID_END.split('.') - - adress_family = HWBgpAfi.get(int(OID_END[1]), OID_END[1]) - sub_family = HWBgpSafi.get(int(OID_END[2]), OID_END[2]) + address_family = HWBgpAfi.get(int(afi), afi) + sub_family = HWBgpSafi.get(int(safi), safi) - return '%s %s' % (adress_family, sub_family) + return f'{address_family} {sub_family}' -def _huawei_bgp_PeerUnAvaiReason(st): +def _huawei_bgp_peer_unavail_reason(st): reason = { '1': 'Configuration lead peer down', '2': 'Receive notification', @@ -127,43 +127,39 @@ def parse_huawei_bgp_peer(string_table: List[StringTable]) -> Dict[str, BgpPeer] peer_table = {} for entry in hwbgpPeer2Entry: - oid_end, hwBgpPeerRemoteAddr, hwBgpPeerState, hwBgpPeerFsmEstablishedCounter, hwBgpPeerFsmEstablishedTime, \ - hwBgpPeerUnAvaiReason = entry + oid_end, remote_address, state, fsm_established_counter, fsm_established_time, unavail_reason = entry bgp_peer = bgp_get_peer_entry([ - hwBgpPeerRemoteAddr, - hwBgpPeerState, + remote_address, + state, '2', # admin_state not admin_down None, # in_updates None, # out_updates None, # in_messages None, # out_messages - hwBgpPeerFsmEstablishedCounter, - hwBgpPeerFsmEstablishedTime, + fsm_established_counter, + fsm_established_time, None, # in_update_elapsed_time ] ) if bgp_peer: - adressfamily = _huawei_get_adress_family(oid_end) - remote_address = list(bgp_peer.keys())[0] - index = f'{remote_address} {adressfamily}' - bgp_peer[remote_address].peer_unavail_reason = int(hwBgpPeerUnAvaiReason) - bgp_peer[remote_address].peer_unavail_reason_str = _huawei_bgp_PeerUnAvaiReason(int(hwBgpPeerUnAvaiReason)) - bgp_peer = {index: bgp_peer[remote_address]} + afi, safi = oid_end.split('.')[1:3] + adress_family = _huawei_get_adress_family(afi, safi) + item = f'{remote_address} {adress_family}' + bgp_peer[remote_address].peer_unavail_reason = int(unavail_reason) + bgp_peer[remote_address].peer_unavail_reason_str = _huawei_bgp_peer_unavail_reason(int(unavail_reason)) + bgp_peer = {item: bgp_peer[remote_address]} peer_table.update(bgp_peer) - # peer = { - # 'hwpeerunavireason': _huawei_bgp_PeerUnAvaiReason(hwBgpPeerUnAvaiReason), - # } - # # add BGP route counter for entry in hwBgpPeerRouteEntry: - oid_end, hwBgpPeerPrefixRcvCounter, hwBgpPeerPrefixActiveCounter, hwBgpPeerPrefixAdvCounter = entry + oid_end, prefix_rcv_counter, prefix_active_counter, prefix_adv_counter = entry + afi, safi = oid_end.split('.')[1:3] counter = [] for key, value in [ - ('prefixrcvcounter', hwBgpPeerPrefixRcvCounter), - ('prefixactivecounter', hwBgpPeerPrefixActiveCounter), - ('prefixadvcounter', hwBgpPeerPrefixAdvCounter), + ('prefixrcvcounter', prefix_rcv_counter), + ('prefixactivecounter', prefix_active_counter), + ('prefixadvcounter', prefix_adv_counter), ]: try: counter.append((key, int(value))) @@ -171,9 +167,9 @@ def parse_huawei_bgp_peer(string_table: List[StringTable]) -> Dict[str, BgpPeer] pass # adjust oid_end to match cisco oid_end - service = '%s %s' % (bgp_get_peer(f'{oid_end[6:]}.1.1'), _huawei_get_adress_family(oid_end)) - if service in peer_table.keys(): - peer_table[service].metric_count += counter + item = f'{bgp_get_ip_address_from_oid(f"{oid_end[6:]}.1.1")} {_huawei_get_adress_family(afi, safi)}' + if item in peer_table.keys(): + peer_table[item].metric_count += counter return peer_table @@ -187,7 +183,7 @@ register.snmp_section( SNMPTree( base='.1.3.6.1.4.1.2011.5.25.177.1.1.2.1', # HUAWEI-BGP-VPN-MIB::hwBgpPeerEntry oids=[ - OIDEnd(), # 0.ipv4/ipv6.adressFamily.ipv4/ipv6.RemotePeerIP + OIDEnd(), # 0.afi.safi.adresstype.length.RemotePeerIP # '1', # hwBgpPeerNegotiatedVersion # '2', # hwBgpPeerRemoteAs '4', # hwBgpPeerRemoteAddr @@ -202,12 +198,23 @@ register.snmp_section( SNMPTree( base='.1.3.6.1.4.1.2011.5.25.177.1.1.3.1', # HUAWEI-BGP-VPN-MIB::hwBgpPeerRouteEntry oids=[ - OIDEnd(), # 0.ipv4/ipv6.adressFamily.ipv4/ipv6.RemotePeerIP + OIDEnd(), # 0.afi.safi.adresstype.length.RemotePeerIP '1', # hwBgpPeerPrefixRcvCounter '2', # hwBgpPeerPrefixActiveCounter '3', # hwBgpPeerPrefixAdvCounter ] ), + # ToDo: sample need to add counters :-( + # SNMPTree( + # base='.1.3.6.1.4.1.2011.5.25.177.1.1.4.1', # HUAWEI-BGP-VPN-MIB::hwBgpPeerMessageEntry + # oids=[ + # OIDEnd(), # 0.afi.safi.adresstype.length.RemotePeerIP(?) + # '1', # hwBgpPeerInTotalMsgCounter + # '2', # hwBgpPeerOutTotalMsgCounter + # '4', # hwBgpPeerInUpdateMsgCounter + # '9', # hwBgpPeerOutUpdateMsgCounter + # ] + # ), ], detect=startswith('.1.3.6.1.2.1.1.2.0', '.1.3.6.1.4.1.2011') ) diff --git a/huawei_bgp_peer.mkp b/huawei_bgp_peer.mkp index 0daaee78511b9f9e8a49389f2c78a3bbb26fb746..f675408e006c0873df195f239d39afc3ee38a059 100644 GIT binary patch literal 3323 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 PYEMZ*{fFc&3CVu|0)=W_ literal 3255 zcmbWkhd&gK!vJ8VfkGL{=Iop;BO@0sgsiOMGR_F$>+HRSb5{1=$vVfy8HK|cnRSjj zBkRb>>SWyg{)PARJ`aBi9o=l2o#R!io4-RK)E(mJ>I3nCLjRMJmz9^7Rg_ba4v?0W zmY4SO@S)kY3-$h?!m__aVe4kQH6Mruj-ZR^{#=<C-MyhVE8vT{*^yAL*Y*_-bU*R2 zW&5|MT*ZeULmzhkP@@z)x~IC9t}u|FAufR}QQNoYWYO9s&wFbfd}??w5cnU&G(p3i zU>>qBY?B$<6Q~zLzCm}XMSd+VBR8_MMgg2kkQ`cI>LBg@Ac>NcI!<6?8WK}T>ib4d zJLbD5TEl9{==z;yfl)Y*psfDu1{Uf5aWc*5>hRIDcX<SY{_JE{1cSZLQ1F?^LyZW* zM`upVjipR%q988;XD+uGEuu_p2%v<-6Pj}zQtlA>N%RKTZ&=$}IR|%L)Q+gAv2*U$ z)SPreTfQ;BjzPeF=&<uXDPE|bTpA)Ao3>y{0V@;YYQFxP6&uL}XZEHkE>Zd?TFF)7 zW)MxbxmFd=7jgmqtHzMmY$~M1gFQY$T7IX-`eEpuj$Zuk&?rl88n;TtsuboHi}jh; zoVDZXTmLybrysCxCZy3$zm?}rqO%gwcy4yTOOnXEETA?#T0Ui}aH&Q{uah=_$G|r< zFBALgHnIJh>Cxp<QU7)<$w>NPR)>)tXwIR3B;9O4PNNUPo3q*@{+8o8k)281@swDf z!<?Pp*euI!RuaXCWwJs!%Q8sOX_=&AUU#HE!b>$S)>&oih`f}H5gAv%aMe>+5(8af zka&96V=+}NQ+G?x;!wD}dC4~D-olmg!)_X<$RO0o^Doqt$kVh-{$r}f2go0)eAo(w zYIH#btx=g3VJ%Bk{<dk*(B&)`5lCIy*;M^YNQp$1l1j%WP7A;O-0?WKSjJw<J1Dh? zc{y+9xPgpfIBxST^-)(9SL=(FP^2jxsVAS0)BedPMPUq;lEXCCzw!t-O4jAz2V1M# zgQ<BR#yg*a^@&b70tacn%E-WRGv{I<llgM!G)SzMCpkMYkmosgs9tBAZfTLqOdxkt zZ^oK+QX_9=6Z=1IL-_tCPVe^#+<OoBsI5BlxZ3OkC6w0xH0SIoiRh&V^t(yyJ^L)a zNNwakyHl4$9Q@1G`B;FnB@|AsI|@viNz>nnPw0QWs9GfgC!6>&^>JOTNzk<WS@xVT zRPwfb><=CQHcP}a2x+)<#wVa_*|cDO{!Am3v_H2L#nPorxae5So|>r{e2Wy6MYgI4 zg39Iu;%+<HXXLN>(c^*J!30xbM$lfg5qLi&$hbFFK!9fO^8D{;&s?M0+Hc9ZeiOi` z<8zA@6f>0_XFnKScUvb|CC*9Qp_JvEk@^BzJH^knC2R>qZj&I)ZZ-#Z;8S86ua-+H z$|E$SM~^yiZ@jIu%&GcCO~Y2M9HkZ3GVV|LnIHbs%X(<E7irPH0@A?8iXk<-?w3Y9 z>X}KmMWmb=n&MaG!}xOTBg>eo212^iO0d6`FM6`-j53#NYkHnd7TVY;9Ui3$9s(ZD zHIGwH1|6^<%cx;ij;-D%flJQ%6RTf2do$}fQjypPKW28#b7p$!$KG6CM_C1pnm#u} zY&R!M@opGGbZ3_D)_nF(MIiUX-y~@}Y&pN2%^`o5e#Lbt{imOv4r_Ac5oRy)4r}+( z(T$bX_(dhH&luN*-qVE(lPuxwq80PZdDlZBa(m}!FEev8{<Gh0tDyC3r-aC0y#CMn zfknk04(=}$AGDv&Tzc6u2e;uHru!`XxWNk%r9#C|^BPGA$~J6vSvz~PJ0_V=IZe)H zC71siV{T$%Pq?<~&9}8Bo83kC>tje0f<Fnv9>uR5h>`GWOz!0e-k`^T&7=OIe=x}R z$+QZY4W|VK9@E{3th@_x8B{a5Q4rI#Nf=&UCqD6t9@#eM2oEo=DyPUvhCs;GUl4qs z?LD5aWTYU`z3;)5a^xa*hKLRy76fk8znjK*>aq-OoOGoWLeLE(Cev@JD)-zGAIeP| z+-dI{C+F|6_)3XsD9z^-CvwEuAEqh&X_C=|q-ms9R?4`F)uK7WYpq^hrIuV93#dF1 z?gbdqO&Y3`kAymy)blp;?4@h$=16L{lh#v4s++^S+hEbOmaK4f%cory8Uq&EsjjpM z@h%4sAKEsfj8bjd-c4TvjT_4t+j9M;J*KF^Le$0dX2EV@XUzv+4}v!ba#NaXIaX0J z9CGZ%KTd<f{NSlmM^W=N7i-671K;PNC3(M=RTee>-S;Xjnl@sP7bAG^MXGxsd+r5< z$J!sMz4n^zOP!j8aaiIBO~DX?=lBEDi~=bxt@*jJqu+-4VO397I<MZ~&DB(Fo$bX# zpo?=WjR@-PXf7{~z7u24mJIfIAJ#@p43B1_r3r9<_HgXTkpBHdvY9`=saV3w2?<T9 zo6;;zMa5V!an9?aQ$v{g{Q7AEUxT8n*P%SzT;l+W=vk9)OpO3TV-mnM@7w0!0!MkX zC^8mnDR$WQ9h@KQ#-8{ozQ)3>8f}1<;!$+8x*JylkAs9Ar^u8+Oca#!0&2*=GAqWK zNZy4JcVxs7yqgwcIN0iCtkdKO;25^OpC0d@CBZvg+>vT=J)Q;PK%4atjMi=(eC^e$ z(*wL)V;a9J{;PqJE<6Ad?UXMbRktB{K@{PeFuPsx)(vxq@y6*i=ly5feXZFKs@0=6 zz)`n{(JO(8Q$gx;jIRm?(oddA(b_ocDkWsx4Hp)#k>+C5ib59I5swZUTEu7+zo91F z%7(a71hP_5#b}ewQecH)R7>&ch<jDm`lT1Fp+yA`F0^=;mcbq~)CvShI+VQUHID}m zxT;|rt|L=4JU-CzIL16vLN&}Z0L^ikb>qI!QB-@0sHE4i6x24K;R|lwhg-si%M;8l zSfx5bgqc6QcoXCHpz~47y3$DsMqk-stKG+C`ates0gr0WR@}5UlvDuo?#1wBqYG&s zS@$_6sB~2(8YF8J)M<~kwYoYgcy37>iIGOfr;TSTEzjWN=Ed%+zi_VA#y+7_h_A@^ z9=meyCo&6pz%7=SW%+yk#1;Ms)b|3t*;kSw1AT!4CAS_Q@v4`V&L60+?JpW&ir2V} zbX`Kq8)N%yQQtz%nC;ys8pX&AH%QPpi>;wgsK$+F#H*}HXn%3$&IE1*0GyLP+Vvgh zd{=+zANDH!Sg5V`rf!9PbR|)>e0!u#;S#(W%AfnC&PKDpKLoRGu|hUrg5~aaIjdP) z4PkbsE9#wuqV&-1ctft53E&E}YCQ{{KT{raf}Xbh^u%zuFz}6$;DXsvs{c=Wf*irJ zs}(ts(A^&X7W@Mv`{`(DTy-|6076myz#$CSmhO?Nn|F>W3-}s0{ouYl<?U-_Ehm*? z2eI09$}1vS85wJf<1gnIHmFgSZ9M*B4TwEW$YQHK9tnR@=~@3C{>bQ$kZ<L?dLw?4 zzsnVQ^kpw_lb=<y?DsfJ%Bwsy^t$mcu(=%OvHOrtB1O(%SF^C$a{*M6S>&D!6AIYS zR=P%S3!4G&L%CiMFGig8%?pFw6wXYcHCg3~StYKfwegD=Q-j$I=anNh(!JsSX&{M8 zI8n@X;A=O3tlv~dOC+m6^6I(F-L*$Ib^lfBkCas?9Jr+TbJ{&8wWfq*6jUly8~SDk zo7{8Ft)5t%-6=R}yIin)Ayhpj-pOaxyV?OUqF?MWR1=?SEvhrDxrck;2mcSXGU2l7 zD0+ADxNhn}57P@5foInjJg4f|!cG4|g>Y*MyScJM_EJti<EoWE;W9Put?GBio6Q3_ zK%?-G@9=R0B@rpCeLwa}P~P-LjJ2Ooq=@~+H}O&1Xung-*d+|E@Bii?@?B`y`J4T4 z!<O?)gu&+v%2W5LtkZM;rK{%<*7J(9<{+xmu|@>7vf(c%?iijfA4y$(xg5EFiSUQ4 zbp(U30AWA|^X?ks9Td1%Iyg|L8+<5}oKy;_*f&M&{e}c#y2g3B2!(1|@fwymd)5s1 zw%{-~%Mkgu1)7O@y$um~wu)ND7DP8m5N4jl54`iF@Y?Z#H(oQsjGn21G^vLB&nmG$ zvH0f^0>8_trSmE)yxA(T$0qPl>s?ROX?yL`^HXImZua*ShCUz^GT7s(FL6XrAuUO| xdGaa!Z2F<Z0(7kH|0pb@xoKS)-wE$ybD5asW%~a~qlR2?Bh=Y;Y07A5{s(e<f|mdQ -- GitLab