From a7476491942a55a2c6a76e0ec39b4db848832a9e Mon Sep 17 00:00:00 2001 From: "th.l" <thl-cmk@outlook.com> Date: Thu, 18 Apr 2024 19:21:24 +0200 Subject: [PATCH] update project --- README.md | 1 + agent_based/cisco_asyncos_dns.py | 90 ------------- agent_based/cisco_asyncos_queue.py | 105 --------------- cisco_asyncos.mkp | Bin 10670 -> 0 bytes {doc => img}/.gitkeep | 0 {doc => img}/sample_fan.png | Bin {doc => img}/sample_license.png | Bin {doc => img}/sample_power.png | Bin {doc => img}/sample_raid.png | Bin {doc => img}/sample_temp.png | Bin {doc => img}/sample_updates.png | Bin {doc => img}/sample_workqueue.png | Bin mkp/cisco_asyncos-0.4.1-20240418.mkp | Bin 0 -> 11759 bytes .../agent_based}/cisco_asyncos_bandwidth.py | 73 +++++------ .../agent_based}/cisco_asyncos_cache.py | 72 +++++++---- .../agent_based}/cisco_asyncos_conn.py | 72 ++++++----- .../agent_based}/cisco_asyncos_cpu.py | 28 +++- source/agent_based/cisco_asyncos_dns.py | 112 ++++++++++++++++ .../agent_based}/cisco_asyncos_fan.py | 43 ++++--- .../cisco_asyncos_feature_keys.py | 68 ++++++---- .../agent_based}/cisco_asyncos_mem.py | 33 +++-- .../agent_based}/cisco_asyncos_messageage.py | 44 ++++--- .../agent_based}/cisco_asyncos_power.py | 30 +++-- source/agent_based/cisco_asyncos_queue.py | 121 ++++++++++++++++++ .../agent_based}/cisco_asyncos_raid.py | 43 ++++--- .../agent_based}/cisco_asyncos_resources.py | 46 ++++--- .../agent_based}/cisco_asyncos_temp.py | 41 +++--- .../agent_based}/cisco_asyncos_updates.py | 96 ++++++++------ source/gui/metrics/cisco_asyncos.py | 100 +++++++++++++++ source/gui/wato/cisco_asyncos_dns.py | 51 ++++++++ .../gui}/wato/cisco_asyncos_feature_keys.py | 8 +- .../gui}/wato/cisco_asyncos_license.py | 8 +- .../gui}/wato/cisco_asyncos_queue.py | 7 +- .../gui}/wato/cisco_asyncos_updates.py | 8 +- {packages => source/packages}/cisco_asyncos | 12 +- 35 files changed, 826 insertions(+), 486 deletions(-) delete mode 100644 agent_based/cisco_asyncos_dns.py delete mode 100644 agent_based/cisco_asyncos_queue.py delete mode 100644 cisco_asyncos.mkp rename {doc => img}/.gitkeep (100%) rename {doc => img}/sample_fan.png (100%) rename {doc => img}/sample_license.png (100%) rename {doc => img}/sample_power.png (100%) rename {doc => img}/sample_raid.png (100%) rename {doc => img}/sample_temp.png (100%) rename {doc => img}/sample_updates.png (100%) rename {doc => img}/sample_workqueue.png (100%) create mode 100644 mkp/cisco_asyncos-0.4.1-20240418.mkp rename {agent_based => source/agent_based}/cisco_asyncos_bandwidth.py (55%) rename {agent_based => source/agent_based}/cisco_asyncos_cache.py (53%) rename {agent_based => source/agent_based}/cisco_asyncos_conn.py (50%) rename {agent_based => source/agent_based}/cisco_asyncos_cpu.py (73%) create mode 100644 source/agent_based/cisco_asyncos_dns.py rename {agent_based => source/agent_based}/cisco_asyncos_fan.py (82%) rename {agent_based => source/agent_based}/cisco_asyncos_feature_keys.py (84%) rename {agent_based => source/agent_based}/cisco_asyncos_mem.py (75%) rename {agent_based => source/agent_based}/cisco_asyncos_messageage.py (72%) rename {agent_based => source/agent_based}/cisco_asyncos_power.py (88%) create mode 100644 source/agent_based/cisco_asyncos_queue.py rename {agent_based => source/agent_based}/cisco_asyncos_raid.py (81%) rename {agent_based => source/agent_based}/cisco_asyncos_resources.py (70%) rename {agent_based => source/agent_based}/cisco_asyncos_temp.py (75%) rename {agent_based => source/agent_based}/cisco_asyncos_updates.py (82%) create mode 100644 source/gui/metrics/cisco_asyncos.py create mode 100644 source/gui/wato/cisco_asyncos_dns.py rename {gui => source/gui}/wato/cisco_asyncos_feature_keys.py (87%) rename {gui => source/gui}/wato/cisco_asyncos_license.py (87%) rename {gui => source/gui}/wato/cisco_asyncos_queue.py (82%) rename {gui => source/gui}/wato/cisco_asyncos_updates.py (86%) rename {packages => source/packages}/cisco_asyncos (89%) diff --git a/README.md b/README.md index a34402a..4fa2cab 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[PACKAGE]: ../../raw/master/mkp/cisco_asyncos-0.4.1-20240418.mkp "cisco_asyncos-0.4.1-20240418.mkp" # Cisco AsyncOS (IronPort) **Note: this package is for CheckMK version 2.x. For other versions see the corresponding branch.** diff --git a/agent_based/cisco_asyncos_dns.py b/agent_based/cisco_asyncos_dns.py deleted file mode 100644 index 534f79a..0000000 --- a/agent_based/cisco_asyncos_dns.py +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# License: GNU General Public License v2 -# -# Author: thl-cmk[at]outlook[dot]com -# URL : https://thl-cmk.hopto.org -# Date : 2020-03-08 -# -# monitors status Cisco IronPort Appliances (ESA) DNS requests -# Tested with: C380, C370 -# -# 2021-03-24: rewrite for CMK 2.0 -# -# .1.3.6.1.4.1.15497.1.1.1.15.0 = Gauge32: 0 -# .1.3.6.1.4.1.15497.1.1.1.16.0 = Gauge32: 0 -# -# ASYNCOS-MAIL-MIB::outstandingDNSRequests.0 = Gauge32: 0 -# ASYNCOS-MAIL-MIB::pendingDNSRequests.0 = Gauge32: 0 -# -# -from typing import Mapping, List, NamedTuple, Optional - -from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( - DiscoveryResult, - CheckResult, - StringTable, -) - -from cmk.base.plugins.agent_based.agent_based_api.v1 import ( - register, - Service, - check_levels, - Result, - Metric, - SNMPTree, - contains, - State, -) - - -class CiscoAsyncosDns(NamedTuple): - outstandingDNSRequests: int - pendingDNSRequests: int - - -# [['0', '0']] -def parse_cisco_asyncos_dns(string_table: StringTable) -> Optional[CiscoAsyncosDns]: - try: - return CiscoAsyncosDns( - outstandingDNSRequests=int(string_table[0][0]), - pendingDNSRequests=int(string_table[0][1]), - ) - except IndexError: - return - - -def discovery_cisco_asyncos_dns(section: CiscoAsyncosDns) -> DiscoveryResult: - yield Service() - - -def check_cisco_asyncos_dns(params, section: CiscoAsyncosDns) -> CheckResult: - yield Result(state=State.OK, - summary='%s outstanding, %s pending' % ( - section.outstandingDNSRequests, section.pendingDNSRequests)) - - yield Metric(name='outstandingDNSRequests', value=section.outstandingDNSRequests) - yield Metric(name='pendingDNSRequests', value=section.pendingDNSRequests) - - -register.snmp_section( - name='cisco_asyncos_dns', - parse_function=parse_cisco_asyncos_dns, - fetch= SNMPTree( - base='.1.3.6.1.4.1.15497.1.1.1', # ASYNCOS-MAIL-MIB - oids=[ - '15', # outstandingDNSRequests - '16', # pendingDNSRequests - ] - ), - detect=contains('.1.3.6.1.2.1.1.1.0', 'AsyncOS'), -) - -register.check_plugin( - name='cisco_asyncos_dns', - service_name='DNS requests', - discovery_function=discovery_cisco_asyncos_dns, - check_function=check_cisco_asyncos_dns, - check_default_parameters={}, -) diff --git a/agent_based/cisco_asyncos_queue.py b/agent_based/cisco_asyncos_queue.py deleted file mode 100644 index b4a7988..0000000 --- a/agent_based/cisco_asyncos_queue.py +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# License: GNU General Public License v2 -# -# Author: thl-cmk[at]outlook[dot]com -# URL : https://thl-cmk.hopto.org -# Date : 2020-02-10 -# -# added wato, fixed inventory function, changed snmp scan function, changed item to 'work queue' -# -# 2021-03-24: rewrite for CMK 2.0 -# - -from typing import Mapping, List, NamedTuple, Optional - -from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( - DiscoveryResult, - CheckResult, - StringTable, -) - -from cmk.base.plugins.agent_based.agent_based_api.v1 import ( - register, - Service, - check_levels, - Result, - Metric, - SNMPTree, - contains, - State, -) - - -class CiscoAsyncosQueue(NamedTuple): - state: State - status_readable: str - workQueueMessages: int - - -def get_status_readable(st: str) -> str: - states = { - '1': 'enough', - '2': 'near full', - '3': 'full', - } - return states.get(st, st) - - -def get_cmk_state(st: str) -> State: - states = { - '1': State.OK, - '2': State.CRIT, - '3': State.WARN - } - return states.get(st, State.CRIT) - - -# [['1', '1']] -def parse_cisco_asyncos_messageage(string_table: StringTable) -> Optional[CiscoAsyncosQueue]: - try: - return CiscoAsyncosQueue( - state=get_cmk_state(string_table[0][0]), - status_readable=get_status_readable(string_table[0][0]), - workQueueMessages=int(string_table[0][1]) - ) - except IndexError: - return - - -def discovery_cisco_asyncos_queue(section: CiscoAsyncosQueue) -> DiscoveryResult: - yield Service() - - -def check_cisco_asyncos_queue(params, section: CiscoAsyncosQueue) -> CheckResult: - yield from check_levels( - value=section.workQueueMessages, - label='Messages in work queue', - levels_upper=params.get('upper', None), - metric_name='work_queue_size', - ) - yield Result(state=section.state, notice='Queue space: %s' % section.status_readable) - - -register.snmp_section( - name='cisco_asyncos_queue', - parse_function=parse_cisco_asyncos_messageage, - fetch= SNMPTree( - base='.1.3.6.1.4.1.15497.1.1.1', # ASYNCOS-MAIL-MIB - oids=[ - '5', # queueAvailabilityStatus - '11' # workQueueMessages - ] - ), - detect=contains('.1.3.6.1.2.1.1.1.0', 'AsyncOS'), -) - -register.check_plugin( - name='cisco_asyncos_queue', - service_name='Queue', - discovery_function=discovery_cisco_asyncos_queue, - check_function=check_cisco_asyncos_queue, - check_default_parameters={'levels': (50, 100)}, - check_ruleset_name='cisco_asyncos_queue', -) diff --git a/cisco_asyncos.mkp b/cisco_asyncos.mkp deleted file mode 100644 index 60bcf605212b7552bb0db41df2d304ff042462d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10670 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 VS_l5$Xexpr>bJR_L8U-L{XZKJ^2GoE diff --git a/doc/.gitkeep b/img/.gitkeep similarity index 100% rename from doc/.gitkeep rename to img/.gitkeep diff --git a/doc/sample_fan.png b/img/sample_fan.png similarity index 100% rename from doc/sample_fan.png rename to img/sample_fan.png diff --git a/doc/sample_license.png b/img/sample_license.png similarity index 100% rename from doc/sample_license.png rename to img/sample_license.png diff --git a/doc/sample_power.png b/img/sample_power.png similarity index 100% rename from doc/sample_power.png rename to img/sample_power.png diff --git a/doc/sample_raid.png b/img/sample_raid.png similarity index 100% rename from doc/sample_raid.png rename to img/sample_raid.png diff --git a/doc/sample_temp.png b/img/sample_temp.png similarity index 100% rename from doc/sample_temp.png rename to img/sample_temp.png diff --git a/doc/sample_updates.png b/img/sample_updates.png similarity index 100% rename from doc/sample_updates.png rename to img/sample_updates.png diff --git a/doc/sample_workqueue.png b/img/sample_workqueue.png similarity index 100% rename from doc/sample_workqueue.png rename to img/sample_workqueue.png diff --git a/mkp/cisco_asyncos-0.4.1-20240418.mkp b/mkp/cisco_asyncos-0.4.1-20240418.mkp new file mode 100644 index 0000000000000000000000000000000000000000..a794d4f34a5506ada4d5d0ed69d3d03c83785c39 GIT binary patch literal 11759 zcmb7|LvSSww5?;?M#o0SNrxSFoQ`eVJh5$C9oxx?ZFg+@bc}QVJ9&dQdG)H+s`>_d zysGv`9)kdptU+l31-|yTcirS`y77Ce-;;HaTh!K0m+9rJd(`kwp0=|&pr-Tim`z_R zE2ddLkSgY)HXqBmeBX5Bg-C{gOfF>J%pz>EFv+=sBN8oHP&%9ev1Nz|-7SS#uHP;d z+?fH;d=$YE27vX;4d}5pdoK|Zv}^L38}x*`nxS!2iYfB1+`0Df?s$u-LT4jxMaw)- z(rUt)lkSv}jPJYxhIJ*cF0aRSU9q!tQ<BvSJB>tYSc*`NxZc0WI}N<|&8$ho+P{v5 zqE`HfnLiVzC~UM-2($bU!N<HFHPcJ;eOs-pz4c}`-e27JCFV}H`gNa(7AX?KS)E_F z?4j6eN>-a1EiKAcmo9`_a~*nf56PnYW2{&#L-&rhO|<aof_dUqybdij-+FSF%aD`D z>oul`vCItN>UiySvti7eI(aDrWSX`vSC-=<kKdLasJ}I7X+w4*_C&s{j?|81Z|2R_ znsZS>I<kEf%U)K3d}J+Lv9_w^YTRYSkps@XzRddav8bdN`AtU*W%r~m1951RQtGx_ zySw!*+q=ozn{t<y(NT%0DE3`Q<z+JM<6#jco}>E#VXx>OzWypxH8j`ql^q)=Vb9si zJxl;XjLYQt4Fo0)3H0iL8`5E-qzUgPo$4^%7SvW0-<|41L+0)!mXyK*x*+4R4v1~w zefa`2)6FofKaY%2)4zp2<)OBo`_o^Z0tWD|Eq2ZHI$`j>zFQO?7W0Zzvz#5p%NaYl zp`vhj|B$Y5Ww4SNKKxgetD2g-B-1}CihjGR<JCM@39X|uH7OHoq#@=sF)A(AsS{dv zYg$0jR8B&-75j9LJ{2+2k!CTj`Px-5GQl-79I_p?ak4dQr(WwF*y`2(*!VGjbZwtl z6rYq3ljaqDsX9iPDm&Lqp|YnSo>i-gr<XH(JKyqh6I=+HEK|7<)nlcI(!(m}rx2Vd zXuYinT-t=K-n@PCeLq;69R!J~1?c)havP#{FI~E^`C!dV=J`R^9`6p+)L-@eY(U$N z;U}#B9G6&n4LDEf&Lh7#?FI<-1M&b7zrg2r1*QqPC||^=kXWlPw;Xr7-)&U$NwwzW z<R_ck9#K%SljXt@MG!LQDgFS0#?(WfVhgu{CU(#DmZ>Eu6MufLu<NLcwsE{+6{c%g zphi|jXC@9IAlpOu-2}Pd$PB4%V$`HkV!INkqPm_zz<FQ^Yni|&jhXJ7axQ;U7g~<q z9of%bnBUi-#)LPexL*VD`<XkFnK2}Q=2=+^DUT*4C1m6WFqHj$X#*)=-mkLIP$(nN z?kjKkYg9zouZsH;Afw;HN-!Qn5V)7?Ir8WIj0?p+`~CR!DPf-tKS|!ph!$X_4b*U( zKs8vUH<!(>F9-pA_E|0>Bq@w;U9Jej$v9n0GKr^xr|h7~AHeSFyml5LanI{Qs()u+ z<XWO1`VU~AZ55#AJ0RiyY#IdJ25?*^Z!8E=se$(=-noBjfA@#q0Pcp6C#-*^KMsv- z<R~GZP4<Bm3r@kU?Pwvr$aA|?U#+cOuyg~S9{IZ6U{u9pmy1_T_*H?ft?e;h!MXh@ zL5nZ+nu1+GTL7)FSBC55;Tu$V*h`MPz1kwxg)52HYaJYh|JT3SrW$CbwE>9iwKGa6 zg$tCMe%*fs9^IeY1_y()>BeR=+sK^l6j47e=$U(7uOY{f1O+n&T#O~#ndmc*Su_IW zM9<g{$k`3K!t+-A{Er8esWzndpBuX$1lD@dsDF;Cxe$(8b6fYH;tGHYWIFPtnW6p> zbrZf=wjWFZLmF<CH8i3ig4f@%2{1mZ)!Ys?1Z!ugQ;ua_4hVZhzPyMq2EpG3xVebm zLk1J?bM;cZ$9I`O$HP$m8bWpkZ$QO{249O#Sf?fY7aG$=6hujTc!+z5*Xu6@wH@f} zlV;1gulzRM*rzi(IWjV86|PQ|vb~M-X=HD#yI(Tje&S4gkUxH4+H+V%N2adMr5gPm z(n5O!tx|!k8SSxEZuh@4*g7fJZNCy3j1d~S7fwdpm~hi+$ZR^17*h>yHa9m*0oV9M zMBu;Gw5Ab<n$>9SmXCXia)acRBV2V7lnYkDWgI*j&?*$aB}Mx~KuJ<^mo>h#U$!c8 z5ANa`Nsq{#{@5_if_u#_Ly5dsT&vo7nSa}6=wE`Alk|Yujf3pCYD=Mv&iD{4X)R$p z!;2%op$?Q}+2QPv@#t(8R|}VwZOV9v*aq?0h*ptTtptOeY8(;ecICY~Nvshv7}tys zSH|XyFnc?n$uppI(AlqJ`!$t~MJ+EQ5sVGf={tHT&M29TPosa%z?IKiEos=59ZeIG zsl~;})|{qG<I?WK5_joHo8IvA>fo7)S*h9=9km2B@RCltk61NuAIxqceva5oPfalf z$`*2Au@NrqbX1U?(0?z%y^<JAwERvmMiWKbJk(rD-`Hs*_eom2Z45nMzeG3)46G3B z6$2k506ctZcH$MkUcXi%Qnn9E+HXA)cE7xw_L^>du6#h~_~3A?2-V_WjzmcObeO)~ zK@(!IeZfdDurIl7z@L8kA0xzRb3yX5Fy5r2Z7o@o_TRk)^%FY=;$%xCXKWXR<VCXf zd9CvkANlH}D(i!LG|*d9)yn;ataE}L*w8Q${S`g&tc!qM^WI~>BZ_E)@9sA9WH4$@ zVVHUF2|C8a;cZ3rmqo8tJ^*;(-gaQ1qbDwSx9$_X0cQILF0h1v$Wb&JqJDfDE?Tq6 zpZ&ro757x^>*6C)g*;)sukYuF>O9d@!TKt-K8<6xs;-r|ynbKe6#>4w8M((;NprHK z=#Zwp^t;I8P3>tktk@_r-Uf4$>JLSp7(iIM`C@f02&8(`RPkFs=@hp|HwP~N(&u#| z(czs}hYcoTS+P17TfiETe2){?f~k7<4V3&n%`)^&eu)InKGrI88&fO}G1!<qmOU0P zZF+lTWVy9&z>T+0f4A4l4n@8&dpZbF5mlIhodb%5r2hyNWf%2KrrH3Gw3AUC(wUmY zJdlsG1s4;Zm=qi<Dj~T2*6{^4ns;sb0xl7+egUq*mmlC8hqaw+ck8#9vfCh27&H`j zZ&***3}S8R>Xj{5|2wUklbn>2-ju_VUPGmLor7Y62HgMzSy7t`7?j=RuKVJ@L64Jv zZ`*23-)@;KLxJe`yVx&3M#(0-aE!g9a8SZku150!4B|*^f9Fti=*!#9eKuK5o#=-q z&MVq)0m2ROc=%)}to)fjD@)qhtpMugYTo5XNZ~_wxG%6|6d3AVgLXS1EVG!(@IMWF zQ-~rlNUtEC?c_;soDmi=QSlfuP^E7gs623FThw0eFyYtKD&?GwaQSSOQ(}CG+T!Zg zl*q>_9OFgem3DeWUhn=8z!{HNI3*T3li17i;p|DFzAd&3o^**XtpCcbPfV7zk%5wb zQNG{A(`th)9bTt_`ho?xK{aJ}2U-%DGH0z=$)@TRqUpXbeS|Z&th`Ro=*!KDX@SUJ zf({zmBv~*%dud{1g0dj)Ol%EeSG4p;yCPQ76IL5e`Vw_@>|;wprb@DdDL4y<ib<hl zF&j#tD2#KYYtT4<FwXu;_*a^)YE&=}o0BC~Xq|67`f!563OnZcP|<q(*>)1}X--wo zMY>VyN$5qTRWuZ<ZiAG#fa0(DGmJispZVv-`@T3XDjt;>NfN01PbjV5p@i)%&fZA) zjA|0RVb-uQb=H7~v{Yj4SV)}s=6J{bZvrQS&=n5Tjaaxsj$eA1+(@mj-C^-v>a7Vv z;O`gD@Q)u#abU&+pf9B8mehS71kj-U+ruGB8%!Mv0ZGyThtXLK%BLz^xD>!+O>?-B zGQzmsw%^pT|In^~DZ}gS{aA=o>+AfiSFD+@Md(|CegC1E7YTHJKzKy~^r6^N+`g}P z^_d1_ZLtPyOSF`=wTQnET5DITlD%fJ2cya*m-MgNVK$SziHcS0U&5`5N)LbBiIVZ9 zt=E>i9$RiaD%>MJSw|3Cu(@V8@p@bqHF*Fu%KR7AeQ!4XhSwGK;eN*U4p%T;p$j+< zQjn?5H?O@W^cl<n!2wn{#psKrVK!#pNIUQG<WRWp^`-T!kcEu<VL|wZEb36qEqzvp zi2ErAk+9XoLt&#e*c_4*iZcZ@2w7Fl_pVzf^mC(!#O|EJfKQG-hOC|lIomDI>qf3A z^zCx*?XG4cy=}iX@-j}e%RRDNy4>rE9UX&2c(arc!4alJYsM`G6`<gRCqolx`4W0@ zH2F|S1QKGCx-X@rLd=U!LYJfKd-nj$pvX%x#OKZVSS<LTPj3<@Smf;K@<oXh{6_$+ z^tgt!M({*g)Y`*^itxi7!wP7E@?R0G44ZWdbNh7O6eV9t`9D?P`a)|(@-v;*IV1Ag zzla`BU{+36`Lv4f2w)Uo$ogb-CuLqfJP&3|{@O1&5Yn3M3Kl7ubv;$g^E<ef$;k?T zh-V}cS6DgHNzx1tt`z3VmLQG{U^vAEIr?;k3x8A~#YT$bz{o1!%6Pk(al-eEPv2pJ zIE787l!^T}<*PGGx+xYSpwlHzuK6f#QpLO=u%{b;awcW>jbp4DXXmn`>PG})lm+QW zjcAm5dQ8-rZ!E+XGc8ef1zYdC1gi6u$H88;WWhu#?V7%r(>1fc_1i3#-+cJ|JO2@R z#ztkuO;Ioh$<V&>xAQ*qYluzshv|uOk*jK#X2DMWqFZf=CX&8kQAwe=+#PH8Wng;P z;3>P!wV5r2x`P6DDLL|GaQgKSo8-vp0TSY@m@U$=qP6*c+1`3@v1yCNhPaeEm3d*| zXb@+qxie=TO4AC&No?>S&?Qg-N5`~q5hoN*5io;P6t{YurBK#m-4zk61#1?S8IkSD zNX7DHoSE%+Y?@v8Q6c{Yp(4#?wz~&dC4aDyuPg+P>Ywyqe8si<K9$uKHvr66j+D9R z=e#3)1qek$yZO-a@x}I^-VjY2<)QH6xDy6lK*^{>7Dq-`3U&3VW!Ro~k0$$Y?GYKM zC!FM3yFY_Vg>OSeeFBP2Zk5!&s5{tpapV|cBD5abOvH?q_UHw#9~g#8-d`6~27>BI zXw>&q9D05uOa8v-6aUng)E@E3IILOz(vm-lgQ^RTCkWMpy*M2)UVD>|xMlyEnj8Pv zcRmDq?9Pq?u8m2-wTD3SdqV{n0`7H}kp5o(+&f1Rk&pNNmq)_X65bD=fIIM)5*A!r z&9<$t$+e=@q+35UHIMV@zpJk8FisquZgSR0a{qF3!DIO7V<kQfRGR0i(PZCjBbT|r zfx1x&HlQ!5*IMaIP25gj6GVb@+9^#$`Ys#GMO+x1p9k75&!(Y`*NVzu{iw=i>N<s9 z+;A7)BTh9p8uKZPJ*<H*_D*}f>xM{%-tL{xgQo+*LffSM3uj+$_}WNn!Zz-gc0?LE zSIq}vwsagFF>J(NA7Ti>&|r2-qgYIm%24@KtN}&^m2s-n16MA*q|g(OQm<T$_|2q) z1Zyo<I@GZ!%HhO_{S9#<7BSUMyX20EKat*pv|^#LEpflrat_flVjJQDl`|rNY*^`| zsFyFkv|kQ+dB(%z0;WkG4tsZN!?}B?a<RjTvh<Iq&%hq=-Q|0#sLar;tOSMgr0m-< zbqETgYHq=)B2M8aS2o?=(*ugtrX2q1g4xZ1?9_LT5DpOZ<`sHRchFbz)6L)u*Dr3w z!3Qq1--0D@iRk8Pl;g=!6PCfA*#S9oIe3TcYBhOwp=q9y8sDlMnPrGBv&xk>=&4gA zVnqXjEXHE9NP?m6Fsy3I&G`!-q2?qqq)l9hV@>ihni|P_C}u-W=K*&laOla<aXk2= z+1eQi_P)+QRzbEH0cr(?*;sIk)mjDUhg@P8J%WN{ipjhbRJPs7$-YTkT%!0I>5N-6 zhODs9dv-e<Dh*StXN@-VO|zMd>J){;L_nhds#eMghow)^Y)aiYOC*1ZQ(DLj!{S67 z=l9TLVvY2~5l`&jcpK{R3~DP|)akixy<+rPm}|xd|E*%QCgqQfuUlhHez51Bo3#5! zdp!8006f*!&HDMuV&Ll8ncsZJ1ZxM(C((AmI*&VSMeDtC7)M7E#z-eBJQR(n!cn@2 z$5Rx54{@b58I93}=<+RE`EY53pA{gGGZ~O6*4KHVi+i^-FEH<_rf2&AnQjT#pwL@) zGy;2yD8r{O|GJQ#?I>;QK6S+#SQX>{tF(Gb*e2&jN(88u(41;oZ#MsvH|)hT(-Rki zMth8aXs-dc`LL5{`>qN<PWI_}VLsVo#Cn2qxm5gHqMnX^x~_{&wQPBywI2;vXvlB- zX(7yn@<g?2>LzXSjq=U>;-BUdnY$Px{{AD5vT*t2f`;`H;z~A^CnPXAKj$X<r)t*+ z&a*c&1wW@cHp6C>c4}k`u^OXt$ys?uyi#$F`5jGfr5l}kZd^6yirydTP!_y^KeY2a zM?nE4`0&DZ)C%TDIqGE4FFDr9()_m5;hrUmhETo0zSsMsnd-U!CiM$+b~xtgn6Lx% z8CuhaovNRoyDz7>N(AP!mLyPjFbf66LfiOgUL>@5^&ijfvN>j2hJI!daaySAZVXK{ zZS`t9$?FxoYj93cM8IYnjDgNhbIiFV;O0dv`ZNOP2|qdd7j)u%R5U)V?f`y&?@RK* zJZ`zh9-sIIh`$fPL|{jF@S2s7*x}f6iC-=*Vf}n|rBg0Z);)?4Bw<4HGcp@W)DyDP zp2Au6xNf4*tavbjerRa1)D!Kv!AAwe{t{AM;LRVK-{Md5S*07vxD4p4?c73tl8^VF z+Jxz3fe*^Q2Jy$!@%h>sdMuXUp4MvwNJlkN)rz>rG+#q&vM`k(uKL_aQID=Ph1RN= z`o&K5a(@|29c(g$$WtZ3CXH=vK*VlMIH#xQU#lu+Rpeb3ab&Q;L0}{s<mKh!1a->A zNuIkrfOk?~g`L|MIWaH_rm4$V;9p#>4i(&twM0W>*~ER~@3=S*xY4Ns-H|_~>d7gJ za`TL7(p9B?oz$Gruky+2XL5^a#NX&tvaC{U{W-Fb@ZZ0^no6@(jj_s^;)TQ$wDuc( z1$a}XpwM8g65<xC@Q^l0SF2eeg}E5YvS$1%`zdEiEU+P+U>n($+-c6&`iFRnCHEqA z;(Ac~LLj@5<pcQda|{TcBDA^zSKR~Rz;X8?5Ov<hFokFkLb4DL)3Rh=1=II7^^LrF z*ft)6%sQtq)4dGES{;TXx7@{ZlK<QWzMQNQkSs47S*u08DeA+IIQ&s3)wzn1iZxry zkecRfRQa2Bv4+dlYbAT{%$XpFUFk>HoE3FL)(H9j@XX}7!@#T!5B3n+TU}?1s!2-U z)U@CE(iWLMSGQ}imA3ObXi<jz#J-7jx?!3OOEyY3MpIP_btI&g^9O|d4!hu%#j6^p zSSTgp2}<kNgj9!mqzaMiMyfPx^*B(FWoRHl9A_yZAQY;^D38kv3PCd83Q$6TU3!|K z7${w{M}fySXBP`0JRm}GhzFPN=?cGn8iwpQ)Ng@T>gT|f_l7&K<g*T*fRIa|&g$he z-CpKr8?AAw>+KrTA+~TS?m<e{rJ<PW-d0S^`dR+pf6f(Zg+;aqwNOw2?NIrc=VGrI z?#f9I?Odn=`uEAj2d13iNR3Y&&{p)7>g3!IRoB?{+}u<%6Z)m8)!|3z&e%!O{f0w( z^$<=)<_iZ2hh#|ywmZSu_1s;onQUwjE6ORj?A%<sG%|5B%#PaA)+XO&YHhz<_<BiY z<eB6m)QVy^A%-&pD9PeMowskG9$Mv9W`GQ0%D|r_SE_^^dR~;eoz6a6f-R(B5rn^Y zL+rdd)wN4hWiRAW+6yZx=9eQU+SHPDvECH{R%qT8jhKKM<ci1c{1|)>P}r(Z@6<fn zD`ggS#?6A5%?1Uh6fWt&#S51;KeShNQLPtZl1?@Yv^Iape=gU_U=L_N*pKzGM*goE z1c3I^JOmPf?TNuGr)p>Sc$%)rUqCh_=ASzLgwN|C{_~eY9g>oQ7RjVy@Y4-_B<>N9 zswZ^oT7J3fKXGN9C98^5gt?$*S`G1LdCNl-VM9~f<Y)@1xypa*>V>;_wU8!K;b}*R zsdBVKdWUWyB`MLay+cvt*G79%Q{`UXf^JP;fQ_&I<=NW>9rB!dBc2itd&d=z);Cub zLc74ZInQVd>Cy_&K^js<-s_<oU;WLLCGlu~*;@05@M+#rG9-nL$k!Jz08qIDK41Rd zuR3EJJV7V#(C=*nQu7f46ZMRnJLPyOUYmKq&2-}`>KTIK@+lBlRiTyO8my$P?AS4f zLpvY=7tu6W<Q<zrd7o_Mx3d$P_+ykh(5@LTntK+aBh&T89jZE6)|Bb=#BWwR%;sHZ zm<jQsNtozcbP1*LXT7$Wh~@GXg^TTc{|+1TG7&}MzYB(h>3-ZFeNK=(@7HT`n{=*W zu0Q93U&()``sPN*WsgXOc`ciQM7Pd&R{t&y8`jlZOfD=5LoUVY)R73Y^l%A!zwM(v zl&@i2pgoMnn|LMi-u`;z`0IlrJD2P67Gi)ITdJc2DYjUbgie;V*u7Q<kGnV1d&a}B zqqD?tr^89elYKBuQaCAaqqu;%)QN?3SMkwco${Ltwp>OV)ECyyWwYbi<8-0#t<r2B z^n_{g!{kF9Ce6F~;*PPl#2&&dU7C_d5!A)Ty!tyHDk;IV^}+Agoph{a({B=HF<Y_F z`FEvU2V1)5Q9)XMpfvgiROS586x^j4L>V3R4}g|ymVtR7OZqB=uG+&bqy{-j$Fv)% zgyWmjy(7J)Ru6nqnFO*EwVT+C^I)7oeNfA~N6UdJskeX@D^E)7I9U2c6*2}H)f!?5 z*M<}-L2x)|g~V;s3qgI#tBZwom)!f@>SWp+i**eWWhcS=>$ab)>J*H2O$1(h0c?T* z`}UFSA}X#+e}O?zpTFq9>x{qdd00QoRP|Cdy&A-wu8UjWe{NyGsYdM@1}rKsBDY+$ zrlNaHEo=1GqUUEq+2fl9aa3V+NmI``u?N2EUg%m!y)i5(0il_5U+35KLd*lU(@t*c zJ0u(lE-H;Z;XE8c)TgD+j5EvS3OYV(s^>ndE;u!^CR1vC6PYDrO|s7IL6%I%>OI%Z z1dyW)MVJb3Wwqi1rS!YcnB6%uEf26MufqLH#@%+kmJWdvezh*}^3yf}nDUb<jt9P4 zAD*)PacJWN^>Z6!FwCQXr+LCB&KJk^>opLN`->?LtZ=uRdphO3XS(%~mw+gNpYWi> z>L<+Jjp{dIGk?YCbXMrtSJx95k9+JHys3T*(_X!UIQxG*f<JV4&^SB05kWMH1ax@P zI6J4&IzHhODuF>X3Y!9vA{vdGg4WR}tE0Vc7vlREP3-va8$4pqFUNxi!K6vmM29^O zsx_Z>n8GIxiVo@_W<+V3gBi^`O+R+litl%nRF`z(<wasd{*-YALT6FZiR1QXS$Akn zJq6=7q*)sM%6kFGR2NXtoP2~vpQO2q{2%6t<1+HpjfToW-E>Tz#)<uXtU<iUIEQf} zhz^?JW!?;>u0s4>)Wbhxx}eq~+}$7*o?)x84)19{iiz@>HI0z8_rd$Hmk%(xRT{H@ zY>r9(PG+(HtL8+h;zX&&^ZCsk@mYn!R?RuCF&kN;8CRl|Hsq8eWZF>8$@eY96#ea~ z(s6K2^W~}AAG&WK+;6A~me^ja;j@ac7l#l6pO0#@(z=@%Y5nq?u>GXvyqDXZRrFg) z5ba7Pp)&*COyn=;o!AZ>^x6O5@INs8UpV^@>i-w6r**U+Z>L{=(<x0oXNRWrmkjj< zWoTxH2IXjGhes7?X3Hd%Xin-~ou@Ssclwn5NqzGvW5^WnEkm^K?R9Zybbg-1rUyN@ zF!Z2`BpXQ<w+d$1o8i<T=HarOrqmpj38Kp`@tC;rm|r<g@~x*jcNlNS^2|5^)J8LM zI44J&u`KJ6P7$-1KdoN~-CPw>IaGRO)uepYMulz{Q$^;cxQ@xUQUD#+1JcjTFJI+^ zr(PwajElV$d!0MyXohe>n}qJjqVfNIa(~-)y!pZm>f!$$_+X&VMekdbGJIjO3GCTc zt{h`m3}l<7D}IEAOE<#=SBT+@MS8aoMg-8np!Zi_QeZtnUz&90f@FVGIUI6dY-TEl z{X&P#>1y{bSl>Nl(AQC6D;)pZF4``Y$IWHHqd52Ul4WeKSPm*Z-`*e;sBO>17=`;* zmO)~gg=Xg&&iWXHO#gUOS0`WfOP9i`?zyTO_N0i`u)|mpXsN#t?1x*SCJ|E#>#H>E zy)->Z(9&laoF!QCHWvpXoNsgge9mRyDp)P{2qlDH*8gg=pU@Cn%W;Mlv#|fQp?<I| zc!~RTbkgoE)LhX_pM`zk)fO>w!rqJ~O+_i_A_k%ys@5v;ld^T3a|!pdf6F(2J~o4y za*w34gts}x0$2<Ca<m%gX|a7Fl4b`KiIKiVeaQlBwE>MTAbUjH@Ee_CmIT{uqHxDb zd7bLQ`j?iv^F7S9Z!_7NlI;qYbfD-@shEuh_(E66yxw70O8TJHz0@eti2Un`LSBm; zWXxwRbv#S1)UtE5+7jHf(fG0?h2tM(5Gmg13@{QOzg><pU&qu{XAmBKZzPEb|8S|; zH{Ctyd{|JO>CX5A3qjt4mpq*o^!O)Rg0zj?5DVj|&g*Z*72&Xh)A^mDIqsS3rD@at zpV!)f&=FLnPgWcRIPo6O<SESfUM7I7$f;NEDZ9&YEmIm&iCqR4Q)<%|7ZqWLz(K%h z&6fRTEZa=4Gqg}2<y|ofay^99$oeQYfqhb4j(c%s$Z$sWqDiYNc)UASB+vN*q=-~P z-7em2!J|!Iz?u<9S|b=rBP*u?8a;&Pcaknn62LM;Q$=X9xF``tI<8CUiP1ffrlQ4- z^-Xs0k+#tx4Hk5-=roY*t>rRTC+QO0c-0|wg0dp?-c$hpQf#OBq-ni1M61o$bCs*p zp?k%^8EW`(9ssMaKC{K%hK*N=&&AF|4;D((;hN^kQB*ARr~#_#i_G&fjm{rSz(-e$ zWe9mxHXS*WOVhsES^q9a2VVWwSYk&V_lpq7UK!6*C8pSl=c1d)Q`$Oexq0e~iuS|z zgcQ0qzgBwoQd&2I?6qp1S(}g1d)AdIVD;9-U(R5ws&M`1ol{#zxWF5Lvo&d1Sj}|f zJsnq0Hz)DOOi`S6ny}LBGgKaRM{fC)fzRyG^~<aJm0OO2?JPl|PE^@g0a(QXH}wX} zZ0YV$s~CkWurTY35LA2cffn0;1K=TSJduiyT!Kk``AE#30iM^!y<b2BZ~^cOY~Dja z{zY>2^aeku;<TEa?S}H^DjyWl9VQj?ymI^=hCIM|*&m&LRbe}<#byevvGBlEaZ%;} z|Log;H~)3^8@K0NX|Jzp$g{jo4;{DnT<MIjYRU7YuKrGtviW^)$(3+rhr^D_6B$8` zr)TO6+)8@2;AA*7yAT}7!Q}UUM9#t_=&djkcKs}Md{hAqxpVlL5Z!l6frfgH9+K%O z4KQRJ4AmXOc5OXT>9hirtn9jVMsA3fXERw1(Qh~yd%c|dSda9%a3|_Z*tqmC9r$tQ zHn7+lDsh94NOYx#Os?|lk}QK!RbXcS5Io}?8UthG<iI*d4x`5J%=*w|6+S$l7tA3S z%tIH<B^S)^E7}H=%(Q9pzH;jMJ8(iza6+?iLd2D#^-XC11k<VucN?pl`Bt>wR<z+( zw8K}lrB<|`s+)UiW>tHU|M8wdeWG@W0zz2lHM;^a6RMz}q1nCrB~iy4Z$||+f4YRQ zFn#MBFu+aPGD?Y-acB^fq~={u|CUUAC5<_O%2OP91ShCmYQg!}=Nm`n6@L=J??BbT z!od_QINaP+_R4wcgdnAs1D8oGFO%e8egE!=>`2o#6$$ez%^mBa@}fap>^hTilVov1 zI}X_rgs#lH%e|PSog)bXDi4-ej)UniPN}U|r_sn>f<*$FBzW70ObOD$3A$CWEI*qH zxy4V$;Sk2*A;#en#^HDMNbVhNv1&Iam1jnk&9urqx#hI%M$IBpfJ-mG;++R$D))XP z@18Ty-nnD{_w_)_4#+tx==@JqF9U~uHOXzYas{rU_9(`GJPTnvw3CHH-?D4v>>^Z~ zjL((2)Xy`?7Qhgj>1MLdtg)m3hF=o+4|KZ+l28wVpZO(ocJF~r$B~;OZkrgs9DPR@ zDuy}QZSFG7be1x_<yRTkjpqxf%xvz&7FI}4g=WwwWfyU(jCW7&x_+EDJ@G&9*W7<5 zOVm*nlJWY|0WgGYwk=OMF8fN!8lP-0?rlV-$Z8c2hD~ur2HS(<e)ddlIih1;CY(Mz z=y)Sz0yg8{tpqoDk$KTZtZ9iOUSgp0v=h7V=i+a+_kosAuVzOW^eLX5n|3Chq4qo@ zISd_<jlYLFh?2MR{FnR@vPym0l2@mSkKfCZ*P(~pZ_5}SrG#j%7DmF9y}EO`H!peR zrCL#lX{mJ+P_d2i$`d<trkHh#P*e8OZ6q89vhA~jjqZ6AzM10k+C?q9Xeb6cOwey6 zC1tKJ`Xg_}I~@MJ;Zp-CV4Iwv11+JL66JUI_4&<-=~xd}USEmnv~0ZShzylyyk`Xo zf8{&X^j9z+AS~e?c^R|`w$h@go25XIbN=);cWnDR&olNS$!B4A8(3}J8;QI&iBslM z)g$~%)tySl7B_4~P52;J_2OVOtz3(AZ({MX-^o;5jXCK?;@j_!`#=LYRG4v>!9a-* zS)#6j5XC?X29}a<QB~IqC$*a!%nJ6lE#6F9ObQBuEwv5{774q*B*(WAd)fEn<Qbna zsd0C#WMN0}#}C_U^I~;iJ9-3HnU)<|h6gt0Zi@FkPV0+*X3sF7E9=^DHvpTkTm+vr z8+~pmjbTYuHd8-aQ>YGhqa|aB!}in(WS2TJUf7g^Camu@dL6%~KDK<!$W+am?|#s% z=IS?MlK(W3TxrfSilyx%z>>5d-n+Qze;c)Va=0b_b)vXE6WU8bT8Qk4tg+QikK}<5 z-T7l@{64VmZ_nDIMK;i}zJsGIKfi5n7tPgUOnqmb4nIZxg>0%WMGeox*>apMEl6tf z+WP!VLn!X(EzYwnc}Xyh?AJq2+qzZ|PfC5eTHzpb;KsB<l+>PA#u>zVcK2Q@FEh$? zEOD&|D|yU9nah!y!Jfkpe2z2YB?sN^wmz-h$8fQo=9y=tQOZ)fZQ8RVM{FET7WH-V zN_lB2EWxI44`~h?qgAH+Mj~YAk;!2lNr{@+<t_qL=|BU$eyz@)(DZ!yLQTE8Qw<Z` z;w)nA@6Xv<Qxj&Sd*uf?g!}ya(^15ZdsEv<^hzT}ZF5I{ubG~^LIo~{B{kI#>7#Rt z?MU5(H2ys6<CR5gH^w*|JO<J+?F-qCX7VvQx5hOEZ-4&pOwO%rT>f;pEA&P<1@5K1 z@)rU@AVZSPw*Aj>GIw&&2mbhj(#DsU!Q>9KA(Woqm(tofTGKN)zH2+c|7*@vXk`v8 zn}J<&edqHY&=c>)3@A1RIZ4t!2pq1Fp_X+Hups;QK2o_zzI?OQJAc_vW^q5lY*l;U z7C@rQ9|I4=jy^P5Ofwq*;@-BjrFZn&p9J~o9<kvzM4JqIt)6(kA@`kS13&q->NGhe zUh;qPPVFJrDOob1@n2_3GB>i{zZGy@@G4dno>8dxMF-a#%U4&(p1@>`Mv2+X9OQEm zFsU0@5K%uA^YfF?ef%qJcXPIK`JK#(-h|>LOQ<j8z1~49cBHW7O(k(OdpecF5uW7u z%FzArOqf;A4ziM>u=O!Hgl+tw$$Nvces+35!yfJIMaVFsOYZFNpIccj*k1PnrID@U zjCNCQzCIhZwmzq|vS29)s7{1=6YsmbwAF~pK62DISvT;erYnf`Hb2ZWj>zf=si^+( zP}rnHzvY;r&N0+x(Q$w|*K)qgQqbM^_O{)PNknrEjt1(Dx=*eSx4F3*%F)&z&@LWx zR^?B4f1Knhs+zy6(d8qtp98Hwn?>JnpY|H}Wh31{(3~V191w2bvRUCE!iPG$J2JcA z|1@G<#5v&ZEs+!x8I{Crv^tec*v0y4q7kb#Km~gd#kgoKOqWJ+OqfJXQJ31)`poXn z9fI$d9NRq+%(B4n@rF+yV@cmDKY<(I_mVe2Pt1>|OMLR#S1p5GS0>V06Hk-<*J;zU zR^$Hn$pFA>le%E6c0|&}c<A!%!4iK=PmXX{0fFV`JgbRcM&=*S%t>{@aDX!Sww+r$ z#hwh(-~jx0@7??p7yuLozt5e4KQZ%uY!ond`M6%d+a2({*!K0L)VGuib~bY1r5*13 zMFi~$3gN@9hfmF4W><W1^+8o{)7{}5Somv+1f7^%(a2P3ydwbX?*W$?fvIue5(lLy zFv{gCP~Zy`;f{E42tmgC1<JZz*;IWw&F3KMAxl`1@XnPI*eS^)gVm<Qpt|W>H+xgu zVFm>{{2rZC;oJ%TuFjoM;r!=l`$xjX?-6a07G0WaQckBD)y}pf9hS5F31tsRvd6Fb zy6+eKS@%E@7pcb=uzx&w^cnDD{Mzty+X2uXF;=*g(z|D_;aW?(c8({bdsK5qiZELP zqFk?NJT+w1e(pxvT9JS8l?<Muxx9P9`T<qL&YI)C|F%TzNHn9&PTANj^<aL*=16qv zr9rIFAU5-#kA>PwK9{y;Y{axGWP#5f9bvf{Pq;QWZOUc4H>(R@+H~DN-kw!tyvwq) z6_mn~DYr^yLa!I^G1VamJSN%DfX;<zPr4z5z2|&fbJk=!P|9hfHu7xb10AO!4{h4* zD-fy1x1b&At)FHI0?lYUf+~bUoX?-1u7$U0H{V~SD|w`(`|A3u3o8u&QYe?dmIqXF zk(_;BM<?%|_)h{CtpCw8R{ZpriDD^i3(geUOk7@>aFmjTs-Y~|5=oA8Z9B{bJ;F1N z$=J4ZUgFYLIDF3@vf^%qaO;kaKDN?ohU8c13#8jP`R4Zvi*3F;_U0HdchLf;k;yOF z9o_K0b|6QlxH?{;Y#s7c=?3BcoSJL0#0}96sMTRb;sd7S*SWXFN@}4`%lh_&!b}+R zBkQb~2zO>-*e=O!)E2qz*MvnyVdrnt*4h_zx8rMpz?C%sx|6K(HFyOi0thui|5p)b z0T><hG9hVtL9w7~II-R{(LQja-_L!XfTaA^=_D`?OX>BGsw?0!^2GT&i1vHz$6k>@ zyC^PaaiDypr$WN>UxSXH(R!t2f2o;GGMBv#<fGcL$?bU^OK>{@?^_*YKT2tIOmNV0 zECpNpY8ios{vtM8i2i@+BKgH!g}r*LFLK7RGld>;3_h2Y{{J)Q`T7B9-NuK3=zxUy EA2lh0Gynhq literal 0 HcmV?d00001 diff --git a/agent_based/cisco_asyncos_bandwidth.py b/source/agent_based/cisco_asyncos_bandwidth.py similarity index 55% rename from agent_based/cisco_asyncos_bandwidth.py rename to source/agent_based/cisco_asyncos_bandwidth.py index 4d73994..eb6a701 100644 --- a/agent_based/cisco_asyncos_bandwidth.py +++ b/source/agent_based/cisco_asyncos_bandwidth.py @@ -7,27 +7,27 @@ # URL : https://thl-cmk.hopto.org # Date : 2021-03-24 # -# only use full for Cisco WSA appliances +# only useful for Cisco WSA appliances # # 2023-03-06: fixed: IndexError in parse function -# +# 2024-04-18: fixed bandwidth values to bytes/s (kb/s), see MIB -from typing import Mapping, List, NamedTuple, Optional +from typing import NamedTuple from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( - DiscoveryResult, CheckResult, + DiscoveryResult, + HostLabelGenerator, StringTable, ) from cmk.base.plugins.agent_based.agent_based_api.v1 import ( - register, + HostLabel, + SNMPTree, Service, check_levels, - Result, - SNMPTree, contains, - State, + register, render, ) @@ -38,13 +38,22 @@ class CiscoAsyncosBandwidth(NamedTuple): bandwidth_day: int +def host_label_cisco_asyncos(section: int) -> HostLabelGenerator: + """Host label function + Labels: + cmk/os_family: + This label is set to "AsyncOS" for all Cisco Ironport devices + """ + yield HostLabel(name="cmk/os_family", value="AsyncOS") + + # [['0', '0', '247']] -def parse_cisco_asyncos_bandwidth(string_table: StringTable) -> Optional[CiscoAsyncosBandwidth]: +def parse_cisco_asyncos_bandwidth(string_table: StringTable) -> CiscoAsyncosBandwidth | None: try: return CiscoAsyncosBandwidth( - bandwidth_now=int(string_table[0][0]), - bandwidth_hour=int(string_table[0][1]), - bandwidth_day = int(string_table[0][2]), + bandwidth_now=int(string_table[0][0]) * 1000 / 8, # kbits/s -> bytes/s + bandwidth_hour=int(string_table[0][1]) * 1000 / 8, + bandwidth_day=int(string_table[0][2]) * 1000 / 8, ) except IndexError: return @@ -56,44 +65,36 @@ def discovery_cisco_asyncos_bandwidth(section: CiscoAsyncosBandwidth) -> Discove def check_cisco_asyncos_bandwidth(params, section: CiscoAsyncosBandwidth) -> CheckResult: yield from check_levels( - section.bandwidth_now * 8, + value=section.bandwidth_now, label='Current bandwidth', - levels_upper=params.get('upper', None), - levels_lower=params.get('lower', None), - metric_name='bandwith_now', + # levels_upper=params.get('upper', None), # not implemented + # levels_lower=params.get('lower', None), # not implemented + metric_name='bandwidth_now', render_func=render.networkbandwidth, - # unit='bits/s' ) yield from check_levels( - section.bandwidth_hour * 8, - label='last hour', - # levels_upper=params.get('upper', None), - # levels_lower=params.get('lower', None), - # metric_name='bandwith_now', + value=section.bandwidth_hour, + label='Last hour', render_func=render.networkbandwidth ) yield from check_levels( - section.bandwidth_day * 8, - label='last day', - #levels_upper=params.get('upper', None), - #levels_lower=params.get('lower', None), - #metric_name='bandwith_now', + value=section.bandwidth_day, + label='Last day', render_func=render.networkbandwidth ) - # yield Result(state=State.OK, summary='last hour: %s, last day: %s' % (section.bandwidth_hour, section.bandwidth_day)) - register.snmp_section( name='cisco_asyncos_bandwidth', parse_function=parse_cisco_asyncos_bandwidth, - fetch= SNMPTree( - base='.1.3.6.1.4.1.15497.1.2.3.7.4', # ASYNCOSWEBSECURITYAPPLIANCE-MIB::proxyRecentBandWTotPerf - oids=[ - '1', # cacheBwidthTotalNow - '3', # cacheBwidthTotal1hrMean - '5', # cacheBwidthTotal1dayMean - ] + host_label_function=host_label_cisco_asyncos, + fetch=SNMPTree( + base='.1.3.6.1.4.1.15497.1.2.3.7.4', # ASYNCOSWEBSECURITYAPPLIANCE-MIB::proxyRecentBandWTotPerf + oids=[ + '1', # cacheBwidthTotalNow (kb/s -> 1000 bits/s) + '3', # cacheBwidthTotal1hrMean (kb/s) + '5', # cacheBwidthTotal1dayMean (kb/s) + ] ), detect=contains('.1.3.6.1.2.1.1.1.0', 'AsyncOS'), ) diff --git a/agent_based/cisco_asyncos_cache.py b/source/agent_based/cisco_asyncos_cache.py similarity index 53% rename from agent_based/cisco_asyncos_cache.py rename to source/agent_based/cisco_asyncos_cache.py index bf53571..54dac16 100644 --- a/agent_based/cisco_asyncos_cache.py +++ b/source/agent_based/cisco_asyncos_cache.py @@ -7,26 +7,27 @@ # URL : https://thl-cmk.hopto.org # Date : 2021-03-24 # -# only use full for Cisco WSA appliances +# only useful for Cisco WSA appliances # -from typing import Mapping, List, NamedTuple, Optional +from typing import NamedTuple from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( - DiscoveryResult, CheckResult, + DiscoveryResult, + HostLabelGenerator, StringTable, ) from cmk.base.plugins.agent_based.agent_based_api.v1 import ( - register, - Service, - check_levels, - Result, + HostLabel, Metric, + Result, SNMPTree, - contains, + Service, State, + contains, + register, ) @@ -38,15 +39,24 @@ class CiscoAsyncosCache(NamedTuple): cache_total_resptime: int +def host_label_cisco_asyncos(section: int) -> HostLabelGenerator: + """Host label function + Labels: + cmk/os_family: + This label is set to "AsyncOS" for all Cisco Ironport devices + """ + yield HostLabel(name="cmk/os_family", value="AsyncOS") + + # [['0', '0', '0', '0', '0']] -def parse_cisco_asyncos_cache(string_table: StringTable) -> Optional[CiscoAsyncosCache]: +def parse_cisco_asyncos_cache(string_table: StringTable) -> CiscoAsyncosCache | None: try: return CiscoAsyncosCache( - cache_hits_now=int(string_table[0][0]), - cache_misses_now=int(string_table[0][1]), - cache_hit_resptime_now=int(string_table[0][2]), - cache_miss_resptime_now=int(string_table[0][3]), - cache_total_resptime=int(string_table[0][4]), + cache_hits_now=int(string_table[0][0]), + cache_misses_now=int(string_table[0][1]), + cache_hit_resptime_now=int(string_table[0][2]), + cache_miss_resptime_now=int(string_table[0][3]), + cache_total_resptime=int(string_table[0][4]), ) except IndexError: return @@ -57,10 +67,14 @@ def discovery_cisco_asyncos_cache(section: CiscoAsyncosCache) -> DiscoveryResult def check_cisco_asyncos_cache(params, section: CiscoAsyncosCache) -> CheckResult: - yield Result(state=State.OK, - summary='Cache Stats current %s Hits (%sms response time), %s Misses (%sms response time) and %sms total response time' % ( - section.cache_hits_now, section.cache_hit_resptime_now, section.cache_misses_now, - section.cache_miss_resptime_now, section.cache_total_resptime)) + yield Result( + state=State.OK, + summary=f'Cache Stats current {section.cache_hits_now} Hits ' + f'({section.cache_hit_resptime_now}ms response time), ' + f'{section.cache_misses_now} Misses ' + f'({section.cache_miss_resptime_now}ms response time) and ' + f'{section.cache_total_resptime}ms total response time' + ) yield Metric(name='cache_hits_now', value=section.cache_hits_now) yield Metric(name='cache_misses_now', value=section.cache_misses_now) @@ -68,19 +82,21 @@ def check_cisco_asyncos_cache(params, section: CiscoAsyncosCache) -> CheckResult yield Metric(name='cache_miss_resptime_now', value=section.cache_miss_resptime_now) yield Metric(name='cache_total_resptime', value=section.cache_total_resptime) + register.snmp_section( name='cisco_asyncos_cache', parse_function=parse_cisco_asyncos_cache, - fetch= SNMPTree( - base='.1.3.6.1.4.1.15497.1.2.3.7', # ASYNCOSWEBSECURITYAPPLIANCE-MIB::proxyRecentPerf - oids=[ - '5.1', # cacheHitsNow - '6.1', # cacheMissesNow - '7.1', # cacheHitRespTimeNow - '8.1', # cacheMissRespTimeNow - '9.1', # cacheTotalRespTimeNow - ] - ), + host_label_function=host_label_cisco_asyncos, + fetch=SNMPTree( + base='.1.3.6.1.4.1.15497.1.2.3.7', # ASYNCOSWEBSECURITYAPPLIANCE-MIB::proxyRecentPerf + oids=[ + '5.1', # cacheHitsNow + '6.1', # cacheMissesNow + '7.1', # cacheHitRespTimeNow + '8.1', # cacheMissRespTimeNow + '9.1', # cacheTotalRespTimeNow + ] + ), detect=contains('.1.3.6.1.2.1.1.1.0', 'AsyncOS'), ) diff --git a/agent_based/cisco_asyncos_conn.py b/source/agent_based/cisco_asyncos_conn.py similarity index 50% rename from agent_based/cisco_asyncos_conn.py rename to source/agent_based/cisco_asyncos_conn.py index f5436cd..c7e5986 100644 --- a/agent_based/cisco_asyncos_conn.py +++ b/source/agent_based/cisco_asyncos_conn.py @@ -9,42 +9,52 @@ # # only use full for Cisco WSA appliances # -from typing import Mapping, List, NamedTuple, Optional +from typing import NamedTuple from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( - DiscoveryResult, CheckResult, + DiscoveryResult, + HostLabelGenerator, StringTable, ) from cmk.base.plugins.agent_based.agent_based_api.v1 import ( - register, - Service, - check_levels, - Result, + HostLabel, Metric, + Result, SNMPTree, - contains, + Service, State, + contains, + register, ) class CiscoAsyncosConn(NamedTuple): - cacheClientIdleConns: int - cacheClientTotalConns: int - cacheClientMaxConns: int + cache_client_idle_conns: int + cache_client_total_conns: int + cache_client_max_conns: int + + +def host_label_cisco_asyncos(section: int) -> HostLabelGenerator: + """Host label function + Labels: + cmk/os_family: + This label is set to "AsyncOS" for all Cisco Ironport devices + """ + yield HostLabel(name="cmk/os_family", value="AsyncOS") # [['0', '5', '66277']] -def parse_cisco_asyncos_conn(string_table: StringTable) -> Optional[CiscoAsyncosConn]: +def parse_cisco_asyncos_conn(string_table: StringTable) -> CiscoAsyncosConn | None: try: return CiscoAsyncosConn( - cacheClientIdleConns=int(string_table[0][0]), - cacheClientTotalConns=int(string_table[0][1]), - cacheClientMaxConns=int(string_table[0][2]), + cache_client_idle_conns=int(string_table[0][0]), + cache_client_total_conns=int(string_table[0][1]), + cache_client_max_conns=int(string_table[0][2]), ) except IndexError: - return + return def discovery_cisco_asyncos_conn(section: CiscoAsyncosConn) -> DiscoveryResult: @@ -52,26 +62,30 @@ def discovery_cisco_asyncos_conn(section: CiscoAsyncosConn) -> DiscoveryResult: def check_cisco_asyncos_conn(params, section: CiscoAsyncosConn) -> CheckResult: - yield Result(state=State.OK, - summary='%s Active Connections - %s Idle Connections - Maximum Connections was %s' % ( - section.cacheClientTotalConns, section.cacheClientIdleConns, section.cacheClientMaxConns)) + yield Result( + state=State.OK, + summary=f'{section.cache_client_total_conns} Active Connections - ' + f'{section.cache_client_idle_conns} Idle Connections - Maximum Connections was ' + f'{section.cache_client_max_conns}' + ) - yield Metric(name='cacheClientIdleConns', value=section.cacheClientIdleConns) - yield Metric(name='cacheClientTotalConns', value=section.cacheClientTotalConns) - yield Metric(name='cacheClientMaxConns', value=section.cacheClientMaxConns) + yield Metric(name='cacheClientIdleConns', value=section.cache_client_idle_conns) + yield Metric(name='cacheClientTotalConns', value=section.cache_client_total_conns) + yield Metric(name='cacheClientMaxConns', value=section.cache_client_max_conns) register.snmp_section( name='cisco_asyncos_conn', parse_function=parse_cisco_asyncos_conn, - fetch= SNMPTree( - base='.1.3.6.1.4.1.15497.1.2.3.2', # ASYNCOSWEBSECURITYAPPLIANCE-MIB::proxyClientSidePerf - oids=[ - '7', # cacheClientIdleConns - '8', # cacheClientTotalConns - '9', # cacheClientMaxConns - ] - ), + host_label_function=host_label_cisco_asyncos, + fetch=SNMPTree( + base='.1.3.6.1.4.1.15497.1.2.3.2', # ASYNCOSWEBSECURITYAPPLIANCE-MIB::proxyClientSidePerf + oids=[ + '7', # cacheClientIdleConns + '8', # cacheClientTotalConns + '9', # cacheClientMaxConns + ] + ), detect=contains('.1.3.6.1.2.1.1.1.0', 'AsyncOS'), ) diff --git a/agent_based/cisco_asyncos_cpu.py b/source/agent_based/cisco_asyncos_cpu.py similarity index 73% rename from agent_based/cisco_asyncos_cpu.py rename to source/agent_based/cisco_asyncos_cpu.py index 5123500..bb00f79 100644 --- a/agent_based/cisco_asyncos_cpu.py +++ b/source/agent_based/cisco_asyncos_cpu.py @@ -7,28 +7,41 @@ # URL : https://thl-cmk.hopto.org # Date : 2021-03-21 -from typing import Mapping, Dict, List, Tuple, NamedTuple +# 2024-04-18: added host label cmk/os_family:AsyncOS + +from typing import List from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( - DiscoveryResult, CheckResult, + DiscoveryResult, + HostLabelGenerator, StringTable, ) from cmk.base.plugins.agent_based.agent_based_api.v1 import ( - register, + HostLabel, + SNMPTree, Service, check_levels, - SNMPTree, contains, + register, ) +def host_label_cisco_asyncos(section: int) -> HostLabelGenerator: + """Host label function + Labels: + cmk/os_family: + This label is set to "AsyncOS" for all Cisco Ironport devices + """ + yield HostLabel(name="cmk/os_family", value="AsyncOS") + + # # sample string_table # [[['41']]] # -def parse_cisco_asyncos_cpu(string_table: List[StringTable]) -> int: +def parse_cisco_asyncos_cpu(string_table: List[StringTable]) -> int | None: try: return int(string_table[0][0][0]) except KeyError: @@ -55,11 +68,12 @@ def check_cisco_asyncos_cpu(params, section: int) -> CheckResult: register.snmp_section( name='cisco_asyncos_cpu', parse_function=parse_cisco_asyncos_cpu, + host_label_function=host_label_cisco_asyncos, fetch=[ SNMPTree( - base='.1.3.6.1.4.1.15497.1.1.1', + base='.1.3.6.1.4.1.15497.1.1.1', # ASYNCOS-MAIL-MIB::asyncOSMailObjects oids=[ - '2' # ASYNCOS-MAIL-MIB::perCentCPUUtilization + '2' # perCentCPUUtilization ] ), ], diff --git a/source/agent_based/cisco_asyncos_dns.py b/source/agent_based/cisco_asyncos_dns.py new file mode 100644 index 0000000..dc25ff9 --- /dev/null +++ b/source/agent_based/cisco_asyncos_dns.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# License: GNU General Public License v2 +# +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : 2020-03-08 +# +# monitors status Cisco IronPort Appliances (ESA) DNS requests +# Tested with: C380, C370 +# +# 2021-03-24: rewrite for CMK 2.0 +# 2024-04-18: changed to check_levels +# added upper levels +# added metrics definition + +# +# .1.3.6.1.4.1.15497.1.1.1.15.0 = Gauge32: 0 +# .1.3.6.1.4.1.15497.1.1.1.16.0 = Gauge32: 0 +# +# ASYNCOS-MAIL-MIB::outstandingDNSRequests.0 = Gauge32: 0 +# ASYNCOS-MAIL-MIB::pendingDNSRequests.0 = Gauge32: 0 +# +# +from typing import NamedTuple + +from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( + CheckResult, + DiscoveryResult, + HostLabelGenerator, + StringTable, +) + +from cmk.base.plugins.agent_based.agent_based_api.v1 import ( + HostLabel, + SNMPTree, + Service, + check_levels, + contains, + register, +) + + +class CiscoAsyncosDns(NamedTuple): + outstanding_dns_requests: int + pending_dns_requests: int + + +def host_label_cisco_asyncos(section: int) -> HostLabelGenerator: + """Host label function + Labels: + cmk/os_family: + This label is set to "AsyncOS" for all Cisco Ironport devices + """ + yield HostLabel(name="cmk/os_family", value="AsyncOS") + + +# [['0', '0']] +def parse_cisco_asyncos_dns(string_table: StringTable) -> CiscoAsyncosDns | None: + try: + return CiscoAsyncosDns( + outstanding_dns_requests=int(string_table[0][0]), + pending_dns_requests=int(string_table[0][1]), + ) + except IndexError: + return + + +def discovery_cisco_asyncos_dns(section: CiscoAsyncosDns) -> DiscoveryResult: + yield Service() + + +def check_cisco_asyncos_dns(params, section: CiscoAsyncosDns) -> CheckResult: + yield from check_levels( + value=section.outstanding_dns_requests, + label='Outstanding requests', + metric_name='outstandingDNSRequests', + render_func=lambda v: f'{v}', + levels_upper=params.get('levels_outstanding') + ) + yield from check_levels( + value=section.pending_dns_requests, + label='Pending requests', + metric_name='pendingDNSRequests', + render_func=lambda v: f'{v}', + levels_upper=params.get('levels_pending') + ) + + +register.snmp_section( + name='cisco_asyncos_dns', + parse_function=parse_cisco_asyncos_dns, + host_label_function=host_label_cisco_asyncos, + fetch=SNMPTree( + base='.1.3.6.1.4.1.15497.1.1.1', # ASYNCOS-MAIL-MIB + oids=[ + '15', # outstandingDNSRequests + '16', # pendingDNSRequests + ] + ), + detect=contains('.1.3.6.1.2.1.1.1.0', 'AsyncOS'), +) + +register.check_plugin( + name='cisco_asyncos_dns', + service_name='DNS requests', + discovery_function=discovery_cisco_asyncos_dns, + check_function=check_cisco_asyncos_dns, + check_default_parameters={}, + check_ruleset_name='cisco_asyncos_dns' +) diff --git a/agent_based/cisco_asyncos_fan.py b/source/agent_based/cisco_asyncos_fan.py similarity index 82% rename from agent_based/cisco_asyncos_fan.py rename to source/agent_based/cisco_asyncos_fan.py index 13e6618..5e69950 100644 --- a/agent_based/cisco_asyncos_fan.py +++ b/source/agent_based/cisco_asyncos_fan.py @@ -44,25 +44,36 @@ from typing import Dict, List from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( - DiscoveryResult, CheckResult, + DiscoveryResult, + HostLabelGenerator, StringTable, ) from cmk.base.plugins.agent_based.agent_based_api.v1 import ( - register, + HostLabel, + SNMPTree, Service, check_levels, - State, - SNMPTree, contains, + register, ) + +def host_label_cisco_asyncos(section: int) -> HostLabelGenerator: + """Host label function + Labels: + cmk/os_family: + This label is set to "AsyncOS" for all Cisco Ironport devices + """ + yield HostLabel(name="cmk/os_family", value="AsyncOS") + + # # [[['6400', 'FAN 1'], ['6300', 'FAN 2'], ['6400', 'FAN 3'], ['6400', 'FAN 4'], ['6400', 'FAN 5'], ['6800', 'FAN 6']]] # -def parse_cisco_asyncos_fan(string_table: List[StringTable]) -> dict: +def parse_cisco_asyncos_fan(string_table: List[StringTable]) -> Dict: sensors = {} for sensor in string_table[0]: try: @@ -83,23 +94,23 @@ def discovery_cisco_asyncos_fan(section: Dict) -> DiscoveryResult: def check_cisco_asyncos_fan(item, params, section) -> CheckResult: try: value = section[item] - - yield from check_levels( - value, - label='Speed', - levels_lower=params.get('lower', None), - levels_upper=params.get('upper', None), - metric_name='fan' if params.get('output_metrics') else None, - render_func=lambda v: '%s RPM' % str(v), - ) - except KeyError: - pass + return + + yield from check_levels( + value=value, + label='Speed', + levels_lower=params.get('lower', None), + levels_upper=params.get('upper', None), + metric_name='fan' if params.get('output_metrics') else None, + render_func=lambda v: f'{str(v)} RPM', + ) register.snmp_section( name='cisco_asyncos_fan', parse_function=parse_cisco_asyncos_fan, + host_label_function=host_label_cisco_asyncos, fetch=[ SNMPTree( base='.1.3.6.1.4.1.15497.1.1.1.10.1', # ASYNCOS-MAIL-MIB::fanEntry diff --git a/agent_based/cisco_asyncos_feature_keys.py b/source/agent_based/cisco_asyncos_feature_keys.py similarity index 84% rename from agent_based/cisco_asyncos_feature_keys.py rename to source/agent_based/cisco_asyncos_feature_keys.py index 4d75a73..37b078b 100644 --- a/agent_based/cisco_asyncos_feature_keys.py +++ b/source/agent_based/cisco_asyncos_feature_keys.py @@ -111,36 +111,45 @@ import time from typing import Mapping, List, NamedTuple from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( - DiscoveryResult, CheckResult, + DiscoveryResult, + HostLabelGenerator, StringTable, ) from cmk.base.plugins.agent_based.agent_based_api.v1 import ( - register, - Service, - check_levels, + HostLabel, Result, SNMPTree, - contains, + Service, State, - render, + contains, + register, ) -def get_isperpetual(isperpetual): +def get_is_perpetual(is_perpetual): s_status = { '1': True, '2': False } - return s_status.get(isperpetual, False) + return s_status.get(is_perpetual, False) class CiscoAsyncosLicense(NamedTuple): perpetual: bool - secondsuntilexpire: int - daysuntilexpire: int - expiredate: str + seconds_until_expire: int + days_until_expire: int + expire_date: str + + +def host_label_cisco_asyncos(section: int) -> HostLabelGenerator: + """Host label function + Labels: + cmk/os_family: + This label is set to "AsyncOS" for all Cisco Ironport devices + """ + yield HostLabel(name="cmk/os_family", value="AsyncOS") # [[ @@ -158,12 +167,12 @@ class CiscoAsyncosLicense(NamedTuple): # ]] def parse_cisco_asyncos_feature_keys(string_table: List[StringTable]) -> Mapping[str, CiscoAsyncosLicense]: licenses = {} - for license, isperpetual, secondsuntilexpire in string_table[0]: + for license, is_perpetual, seconds_until_expire in string_table[0]: licenses.update({license: CiscoAsyncosLicense( - perpetual=get_isperpetual(isperpetual), - secondsuntilexpire=int(secondsuntilexpire), - daysuntilexpire=int(secondsuntilexpire) // 3600 // 24, - expiredate=time.strftime("%d %b %Y", time.localtime(time.time() + int(secondsuntilexpire))) + perpetual=get_is_perpetual(is_perpetual), + seconds_until_expire=int(seconds_until_expire), + days_until_expire=int(seconds_until_expire) // 3600 // 24, + expire_date=time.strftime("%d %b %Y", time.localtime(time.time() + int(seconds_until_expire))) )}) return licenses @@ -193,30 +202,37 @@ def check_cisco_asyncos_feature_keys(params, section: Mapping[str, CiscoAsyncosL ignore_count = 0 for license in section.keys(): if section[license].perpetual: - yield Result(state=State.OK, notice='%s is perpetual (will not expire)' % license) + yield Result(state=State.OK, notice=f'{license} is perpetual (will not expire)') elif license in features_ignore: - yield Result(state=State.OK, notice='%s will expire at %s (%s days). Feature ignored!' % ( - license, section[license].expiredate, section[license].daysuntilexpire)) + yield Result( + state=State.OK, + notice=f'{license} will expire at {section[license].expire_date} ' + f'({section[license].days_until_expire} days). Feature ignored!' + ) ignore_count += 1 - elif section[license].secondsuntilexpire == 0: - yield Result(state=State.CRIT, notice='%s has expired or is not licensed' % license) + elif section[license].seconds_until_expire == 0: + yield Result(state=State.CRIT, notice=f'{license} has expired or is not licensed') else: - if section[license].daysuntilexpire < crit: + if section[license].days_until_expire < crit: state = State.CRIT - elif section[license].daysuntilexpire < warn: + elif section[license].days_until_expire < warn: state = State.WARN else: state = State.OK - yield Result(state=state, notice='%s will expire at %s (%s days)' % ( - license, section[license].expiredate, section[license].daysuntilexpire)) + yield Result( + state=state, + notice=f'{license} will expire at {section[license].expire_date} ' + f'({section[license].days_until_expire} days)' + ) - yield Result(state=State.OK, summary='%d/%d Features found/ignored' % (len(section.keys()), ignore_count)) + yield Result(state=State.OK, summary=f'{len(section.keys())}/{ignore_count} Features found/ignored') register.snmp_section( name='cisco_asyncos_feature_keys', parse_function=parse_cisco_asyncos_feature_keys, + host_label_function=host_label_cisco_asyncos, fetch=[ SNMPTree( base='.1.3.6.1.4.1.15497.1.1.1.12.1', # ASYNCOS-MAIL-MIB::keyExpirationEntry diff --git a/agent_based/cisco_asyncos_mem.py b/source/agent_based/cisco_asyncos_mem.py similarity index 75% rename from agent_based/cisco_asyncos_mem.py rename to source/agent_based/cisco_asyncos_mem.py index f50d747..237e57f 100644 --- a/agent_based/cisco_asyncos_mem.py +++ b/source/agent_based/cisco_asyncos_mem.py @@ -7,6 +7,8 @@ # URL : https://thl-cmk.hopto.org # Date : 2021-03-21 # +# 2024-04-18: fixed params for levels_upper (ThX to alexander[dot]laengle[at]lgt[dot]com) +# # .1.3.6.1.4.1.15497.1.1.1.1.0 = INTEGER: 0 # .1.3.6.1.4.1.15497.1.1.1.7.0 = INTEGER: 1 # @@ -17,31 +19,38 @@ # [[u'0', u'1']] # -from typing import Mapping, Dict, List, Tuple, NamedTuple +from typing import List from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( - DiscoveryResult, CheckResult, + DiscoveryResult, + HostLabelGenerator, StringTable, ) from cmk.base.plugins.agent_based.agent_based_api.v1 import ( - register, + HostLabel, + SNMPTree, Service, check_levels, - SNMPTree, contains, + register, ) -# factory_settings['cisco_asyncos_mem_default_levels'] = { -# 'levels':(80.0, 90.0), -# } +def host_label_cisco_asyncos(section: int) -> HostLabelGenerator: + """Host label function + Labels: + cmk/os_family: + This label is set to "AsyncOS" for all Cisco Ironport devices + """ + yield HostLabel(name="cmk/os_family", value="AsyncOS") + # # [[['48']] # -def parse_cisco_asyncos_mem(string_table: List[StringTable]) -> int: +def parse_cisco_asyncos_mem(string_table: List[StringTable]) -> int | None: try: return int(string_table[0][0][0]) except KeyError: @@ -53,21 +62,19 @@ def discovery_cisco_asyncos_mem(section: int) -> DiscoveryResult: def check_cisco_asyncos_mem(params, section: int) -> CheckResult: - def render_percent(value) -> str: - return '%s%%' % value - yield from check_levels( section, label='Memory utilization', - levels_upper=params.get('util', None), + levels_upper=params.get('levels', None), metric_name='util', - render_func=render_percent, + render_func=lambda v: f'{v}%', ) register.snmp_section( name='cisco_asyncos_mem', parse_function=parse_cisco_asyncos_mem, + host_label_function=host_label_cisco_asyncos, fetch=[ SNMPTree( base='.1.3.6.1.4.1.15497.1.1.1', diff --git a/agent_based/cisco_asyncos_messageage.py b/source/agent_based/cisco_asyncos_messageage.py similarity index 72% rename from agent_based/cisco_asyncos_messageage.py rename to source/agent_based/cisco_asyncos_messageage.py index 91383d7..9b6b7a8 100644 --- a/agent_based/cisco_asyncos_messageage.py +++ b/source/agent_based/cisco_asyncos_messageage.py @@ -17,35 +17,44 @@ # ASYNCOS-MAIL-MIB::oldestMessageAge.0 = Gauge32: 259706 # -from typing import Mapping, List, NamedTuple, Optional +from typing import NamedTuple from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( - DiscoveryResult, CheckResult, + DiscoveryResult, + HostLabelGenerator, StringTable, ) from cmk.base.plugins.agent_based.agent_based_api.v1 import ( - register, + HostLabel, + SNMPTree, Service, check_levels, - Result, - Metric, - SNMPTree, contains, - State, + register, render, ) class CiscoAsyncosMessageage(NamedTuple): - messageage: int + message: int + + +def host_label_cisco_asyncos(section: int) -> HostLabelGenerator: + """Host label function + Labels: + cmk/os_family: + This label is set to "AsyncOS" for all Cisco Ironport devices + """ + yield HostLabel(name="cmk/os_family", value="AsyncOS") + # [['259297']] -def parse_cisco_asyncos_messageage(string_table: StringTable) -> Optional[CiscoAsyncosMessageage]: +def parse_cisco_asyncos_messageage(string_table: StringTable) -> CiscoAsyncosMessageage | None: try: return CiscoAsyncosMessageage( - messageage=int(string_table[0][0]), + message=int(string_table[0][0]), ) except IndexError: return @@ -57,7 +66,7 @@ def discovery_cisco_asyncos_messageage(section: CiscoAsyncosMessageage) -> Disco def check_cisco_asyncos_messageage(params, section: CiscoAsyncosMessageage) -> CheckResult: yield from check_levels( - value=section.messageage, + value=section.message, label='Oldest messages', metric_name='message_age', levels_upper=params.get('upper', None), @@ -68,12 +77,13 @@ def check_cisco_asyncos_messageage(params, section: CiscoAsyncosMessageage) -> C register.snmp_section( name='cisco_asyncos_messageage', parse_function=parse_cisco_asyncos_messageage, - fetch= SNMPTree( - base='.1.3.6.1.4.1.15497.1.1.1', # ASYNCOS-MAIL-MIB - oids=[ - '14', # oldestMessageAge - ] - ), + host_label_function=host_label_cisco_asyncos, + fetch=SNMPTree( + base='.1.3.6.1.4.1.15497.1.1.1', # ASYNCOS-MAIL-MIB + oids=[ + '14', # oldestMessageAge + ] + ), detect=contains('.1.3.6.1.2.1.1.1.0', 'AsyncOS'), ) diff --git a/agent_based/cisco_asyncos_power.py b/source/agent_based/cisco_asyncos_power.py similarity index 88% rename from agent_based/cisco_asyncos_power.py rename to source/agent_based/cisco_asyncos_power.py index 2ea635e..ee043d1 100644 --- a/agent_based/cisco_asyncos_power.py +++ b/source/agent_based/cisco_asyncos_power.py @@ -35,28 +35,39 @@ from typing import Dict, List, NamedTuple from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( - DiscoveryResult, CheckResult, + DiscoveryResult, + HostLabelGenerator, StringTable, ) from cmk.base.plugins.agent_based.agent_based_api.v1 import ( - register, - Service, + HostLabel, Result, - State, SNMPTree, + Service, + State, contains, + register, ) class CiscoAsyncosPowersupply(NamedTuple): redundancy: State - state: State redundancy_readable: str + state: State state_readable: str +def host_label_cisco_asyncos(section: int) -> HostLabelGenerator: + """Host label function + Labels: + cmk/os_family: + This label is set to "AsyncOS" for all Cisco Ironport devices + """ + yield HostLabel(name="cmk/os_family", value="AsyncOS") + + # # [[['2', '1', 'PS 1'], ['2', '1', 'PS 2']]] # @@ -89,10 +100,10 @@ def parse_cisco_asyncos_power(string_table: List[StringTable]) -> dict: redundancy, redundancy_readable = get_ps_redundancy(redundancy) sensors.update({item: CiscoAsyncosPowersupply( - state=state, - state_readable=state_readable, redundancy=redundancy, redundancy_readable=redundancy_readable, + state=state, + state_readable=state_readable, )}) except (ValueError, IndexError): pass @@ -115,8 +126,8 @@ def check_cisco_asyncos_power(item, section) -> CheckResult: try: sensor = section[item] - yield Result(state=sensor.state, summary='Status: %s' % sensor.state_readable) - yield Result(state=sensor.redundancy, summary='Redundancy: %s' % sensor.redundancy_readable) + yield Result(state=sensor.state, summary=f'Status: {sensor.state_readable}') + yield Result(state=sensor.redundancy, summary=f'Redundancy: {sensor.redundancy_readable}') except KeyError: pass @@ -125,6 +136,7 @@ def check_cisco_asyncos_power(item, section) -> CheckResult: register.snmp_section( name='cisco_asyncos_power', parse_function=parse_cisco_asyncos_power, + host_label_function=host_label_cisco_asyncos, fetch=[ SNMPTree( base='.1.3.6.1.4.1.15497.1.1.1.8.1', # ASYNCOS-MAIL-MIB::powerSupplyEntry diff --git a/source/agent_based/cisco_asyncos_queue.py b/source/agent_based/cisco_asyncos_queue.py new file mode 100644 index 0000000..9792927 --- /dev/null +++ b/source/agent_based/cisco_asyncos_queue.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# License: GNU General Public License v2 +# +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : 2020-02-10 +# +# added wato, fixed inventory function, changed snmp scan function, changed item to 'work queue' +# +# 2021-03-24: rewrite for CMK 2.0 +# 2024-04-18: fixed wrong queue states (ThX to alexander[dot]laengle[at]lgt[dot]com) +# fixed levels for queue full not working (ThX to alexander[dot]laengle[at]lgt[dot]com) +# changed state for not defined state from CRIT to UNKNOWN +# a bit of clean up + +from typing import NamedTuple + +from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( + CheckResult, + DiscoveryResult, + HostLabelGenerator, + StringTable, +) + +from cmk.base.plugins.agent_based.agent_based_api.v1 import ( + HostLabel, + Result, + SNMPTree, + Service, + State, + check_levels, + contains, + register, +) + + +class CiscoAsyncosQueue(NamedTuple): + state: State + state_readable: str + work_queue_messages: int | None + + +def get_state_readable(st: str) -> str: + states = { + '1': 'enough', + '2': 'near full', + '3': 'full', + } + return states.get(st, st) + + +def get_cmk_state(st: str) -> State: + states = { + '1': State.OK, + '2': State.WARN, + '3': State.CRIT + } + return states.get(st, State.UNKNOWN) + + +def host_label_cisco_asyncos(section: int) -> HostLabelGenerator: + """Host label function + Labels: + cmk/os_family: + This label is set to "AsyncOS" for all Cisco Ironport devices + """ + yield HostLabel(name="cmk/os_family", value="AsyncOS") + + +# [['1', '3451']] +def parse_cisco_asyncos_queue(string_table: StringTable) -> CiscoAsyncosQueue | None: + try: + queue_availability_status, work_queue_messages = string_table[0] + except IndexError: + return + + return CiscoAsyncosQueue( + state=get_cmk_state(queue_availability_status), + state_readable=get_state_readable(queue_availability_status), + work_queue_messages=int(work_queue_messages) if work_queue_messages.isdigit() else None + ) + + +def discovery_cisco_asyncos_queue(section: CiscoAsyncosQueue) -> DiscoveryResult: + yield Service() + + +def check_cisco_asyncos_queue(params, section: CiscoAsyncosQueue) -> CheckResult: + yield from check_levels( + value=section.work_queue_messages, + label='Messages in work queue', + levels_upper=params.get('levels', None), + metric_name='work_queue_size', + ) + yield Result(state=section.state, notice=f'Queue space: {section.state_readable}') + + +register.snmp_section( + name='cisco_asyncos_queue', + parse_function=parse_cisco_asyncos_queue, + host_label_function=host_label_cisco_asyncos, + fetch=SNMPTree( + base='.1.3.6.1.4.1.15497.1.1.1', # ASYNCOS-MAIL-MIB::asyncOSMailObjects + oids=[ + '5', # queueAvailabilityStatus + '11' # workQueueMessages + ] + ), + detect=contains('.1.3.6.1.2.1.1.1.0', 'AsyncOS'), +) + +register.check_plugin( + name='cisco_asyncos_queue', + service_name='Queue', + discovery_function=discovery_cisco_asyncos_queue, + check_function=check_cisco_asyncos_queue, + check_default_parameters={'levels': (50, 100)}, + check_ruleset_name='cisco_asyncos_queue', +) diff --git a/agent_based/cisco_asyncos_raid.py b/source/agent_based/cisco_asyncos_raid.py similarity index 81% rename from agent_based/cisco_asyncos_raid.py rename to source/agent_based/cisco_asyncos_raid.py index be917e5..70d48a0 100644 --- a/agent_based/cisco_asyncos_raid.py +++ b/source/agent_based/cisco_asyncos_raid.py @@ -36,25 +36,27 @@ from typing import Mapping, List, NamedTuple from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( - DiscoveryResult, CheckResult, + DiscoveryResult, + HostLabelGenerator, StringTable, ) from cmk.base.plugins.agent_based.agent_based_api.v1 import ( - register, - Service, + HostLabel, Result, SNMPTree, - contains, + Service, State, + contains, + register, ) class CiscoAsyncosRaid(NamedTuple): state: State - status_readable: str - lasterror: str + state_readable: str + last_error: str def get_cmk_state(st: str) -> State: @@ -66,7 +68,7 @@ def get_cmk_state(st: str) -> State: return states.get(st, State.CRIT) -def get_status_readable(st: str) -> str: +def get_state_readable(st: str) -> str: states = { '1': 'healthy', '2': 'failure', @@ -75,6 +77,15 @@ def get_status_readable(st: str) -> str: return states.get(st, st) +def host_label_cisco_asyncos(section: int) -> HostLabelGenerator: + """Host label function + Labels: + cmk/os_family: + This label is set to "AsyncOS" for all Cisco Ironport devices + """ + yield HostLabel(name="cmk/os_family", value="AsyncOS") + + # [[ # ['1', 'Drive 0', 'No Errors'], # ['2', 'Drive 1', 'UNKNOWN'], @@ -85,11 +96,11 @@ def get_status_readable(st: str) -> str: # ]] def parse_cisco_asyncos_raid(string_table: List[StringTable]) -> Mapping[str, CiscoAsyncosRaid]: arrays = {} - for raidStatus, raidID, raidLastError in string_table[0]: - arrays.update({raidID: CiscoAsyncosRaid( - state=get_cmk_state(raidStatus), - status_readable=get_status_readable(raidStatus), - lasterror=raidLastError + for raid_status, raid_id, raid_last_error in string_table[0]: + arrays.update({raid_id: CiscoAsyncosRaid( + state=get_cmk_state(raid_status), + state_readable=get_state_readable(raid_status), + last_error=raid_last_error )}) return arrays @@ -109,9 +120,10 @@ def discovery_cisco_asyncos_raid(section: Mapping[str, CiscoAsyncosRaid]) -> Dis def check_cisco_asyncos_raid(item, section: Mapping[str, CiscoAsyncosRaid]) -> CheckResult: try: - yield Result(state=section[item].state, - summary='RAID status: %s, last error: %s' % (section[item].state, section[item].lasterror), - ) + yield Result( + state=section[item].state, + summary=f'RAID status: {section[item].state}, last error: {section[item].last_error}', + ) except KeyError: pass @@ -119,6 +131,7 @@ def check_cisco_asyncos_raid(item, section: Mapping[str, CiscoAsyncosRaid]) -> C register.snmp_section( name='cisco_asyncos_raid', parse_function=parse_cisco_asyncos_raid, + host_label_function=host_label_cisco_asyncos, fetch=[ SNMPTree( base='.1.3.6.1.4.1.15497.1.1.1.18.1', # ASYNCOS-MAIL-MIB::raidEntry diff --git a/agent_based/cisco_asyncos_resources.py b/source/agent_based/cisco_asyncos_resources.py similarity index 70% rename from agent_based/cisco_asyncos_resources.py rename to source/agent_based/cisco_asyncos_resources.py index c6041a2..a771e9b 100644 --- a/agent_based/cisco_asyncos_resources.py +++ b/source/agent_based/cisco_asyncos_resources.py @@ -17,27 +17,27 @@ # ASYNCOS-MAIL-MIB::resourceConservationReason.0 = INTEGER: noResourceConservation(1) # # -from typing import Mapping, List, NamedTuple, Optional +from typing import NamedTuple from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( - DiscoveryResult, CheckResult, + DiscoveryResult, + HostLabelGenerator, StringTable, ) from cmk.base.plugins.agent_based.agent_based_api.v1 import ( - register, - Service, - check_levels, + HostLabel, Result, SNMPTree, - contains, + Service, State, - render, + contains, + register, ) -def get_status_readable(st: str) -> str: +def get_state_readable(st: str) -> str: states = { '1': 'none', '2': 'memory shortage', @@ -59,14 +59,23 @@ def get_cmk_state(st: str) -> State: class CiscoAsyncosResources(NamedTuple): state: State - status_readable: str + state_readable: str + + +def host_label_cisco_asyncos(section: int) -> HostLabelGenerator: + """Host label function + Labels: + cmk/os_family: + This label is set to "AsyncOS" for all Cisco Ironport devices + """ + yield HostLabel(name="cmk/os_family", value="AsyncOS") -def parse_cisco_asyncos_bandwidth(string_table: StringTable) -> Optional[CiscoAsyncosResources]: +def parse_cisco_asyncos_bandwidth(string_table: StringTable) -> CiscoAsyncosResources | None: try: return CiscoAsyncosResources( state=get_cmk_state(string_table[0][0]), - status_readable=get_status_readable(string_table[0][0]) + state_readable=get_state_readable(string_table[0][0]) ) except IndexError: return @@ -77,18 +86,19 @@ def discovery_cisco_asyncos_resources(section: CiscoAsyncosResources) -> Discove def check_cisco_asyncos_resources(section: CiscoAsyncosResources) -> CheckResult: - yield Result(state=section.state, notice='Resource conservation %s' % section.status_readable) + yield Result(state=section.state, notice=f'Resource conservation {section.state_readable}') register.snmp_section( name='cisco_asyncos_resources', parse_function=parse_cisco_asyncos_bandwidth, - fetch= SNMPTree( - base='.1.3.6.1.4.1.15497.1.1.1', # ASYNCOS-MAIL-MIB - oids=[ - '6', # resourceConservationReason - ] - ), + host_label_function=host_label_cisco_asyncos, + fetch=SNMPTree( + base='.1.3.6.1.4.1.15497.1.1.1', # ASYNCOS-MAIL-MIB + oids=[ + '6', # resourceConservationReason + ] + ), detect=contains('.1.3.6.1.2.1.1.1.0', 'AsyncOS'), ) diff --git a/agent_based/cisco_asyncos_temp.py b/source/agent_based/cisco_asyncos_temp.py similarity index 75% rename from agent_based/cisco_asyncos_temp.py rename to source/agent_based/cisco_asyncos_temp.py index bb38d10..4782bb6 100644 --- a/agent_based/cisco_asyncos_temp.py +++ b/source/agent_based/cisco_asyncos_temp.py @@ -22,33 +22,43 @@ # ASYNCOS-MAIL-MIB::temperatureName.1 = STRING: Ambient # -from typing import Mapping, Dict, List, Tuple, NamedTuple +from typing import Dict from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( - DiscoveryResult, CheckResult, + DiscoveryResult, + HostLabelGenerator, StringTable, ) from cmk.base.plugins.agent_based.agent_based_api.v1 import ( - register, - Service, - check_levels, + HostLabel, SNMPTree, + Service, contains, get_value_store, + register, ) from cmk.base.plugins.agent_based.utils.temperature import ( - check_temperature, TempParamType, + check_temperature, ) +def host_label_cisco_asyncos(section: int) -> HostLabelGenerator: + """Host label function + Labels: + cmk/os_family: + This label is set to "AsyncOS" for all Cisco Ironport devices + """ + yield HostLabel(name="cmk/os_family", value="AsyncOS") + + # # [['22', 'FP_TEMP_SENSOR']] # -def parse_cisco_asyncos_temp(string_table: StringTable) -> dict: +def parse_cisco_asyncos_temp(string_table: StringTable) -> Dict: sensors = {} for sensor in string_table: try: @@ -70,7 +80,7 @@ def check_cisco_asyncos_temp(item, params: TempParamType, section) -> CheckResul yield from check_temperature( section[item], params=params, - unique_name='check_cisco_asyncos_temp.%s' % item, + unique_name=f'check_cisco_asyncos_temp.{item}', value_store=get_value_store(), ) except KeyError: @@ -80,13 +90,14 @@ def check_cisco_asyncos_temp(item, params: TempParamType, section) -> CheckResul register.snmp_section( name='cisco_asyncos_temp', parse_function=parse_cisco_asyncos_temp, - fetch= SNMPTree( - base='.1.3.6.1.4.1.15497.1.1.1.9.1', # ASYNCOS-MAIL-MIB::temperatureEntry - oids=[ - '2', # degreesCelsius - '3', # temperatureName - ] - ), + host_label_function=host_label_cisco_asyncos, + fetch=SNMPTree( + base='.1.3.6.1.4.1.15497.1.1.1.9.1', # ASYNCOS-MAIL-MIB::temperatureEntry + oids=[ + '2', # degreesCelsius + '3', # temperatureName + ] + ), detect=contains('.1.3.6.1.2.1.1.1.0', 'AsyncOS'), ) diff --git a/agent_based/cisco_asyncos_updates.py b/source/agent_based/cisco_asyncos_updates.py similarity index 82% rename from agent_based/cisco_asyncos_updates.py rename to source/agent_based/cisco_asyncos_updates.py index db567ce..f6c8bf5 100644 --- a/agent_based/cisco_asyncos_updates.py +++ b/source/agent_based/cisco_asyncos_updates.py @@ -137,22 +137,24 @@ # ASYNCOS-MAIL-MIB::updateFailures.14 = Counter32: 0 # ASYNCOS-MAIL-MIB::updateFailures.15 = Counter32: 0 # -from typing import Mapping, List, NamedTuple +from typing import List, Mapping, NamedTuple from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( - DiscoveryResult, CheckResult, + DiscoveryResult, + HostLabelGenerator, StringTable, ) from cmk.base.plugins.agent_based.agent_based_api.v1 import ( - register, - Service, + HostLabel, + Metric, Result, SNMPTree, - contains, + Service, State, - Metric, + contains, + register, ) from cmk.base.item_state import ( @@ -160,9 +162,20 @@ from cmk.base.item_state import ( set_item_state, ) + class CiscoAsyncosUpdate(NamedTuple): updates: int - updatefailures: int + update_failures: int + + +def host_label_cisco_asyncos(section: int) -> HostLabelGenerator: + """Host label function + Labels: + cmk/os_family: + This label is set to "AsyncOS" for all Cisco Ironport devices + """ + yield HostLabel(name="cmk/os_family", value="AsyncOS") + # [[ # ['File Reputation', '6', '38'], @@ -186,10 +199,11 @@ def parse_cisco_asyncos_updates(string_table: List[StringTable]) -> Mapping[str, for feature, updates, update_failures in string_table[0]: features.update({feature: CiscoAsyncosUpdate( updates=int(updates), - updatefailures=int(update_failures) - ) }) + update_failures=int(update_failures) + )}) return features + # { # 'File Reputation': CiscoAsyncosUpdate(updates=6, updatefailures=38), # 'IronPort Anti-Spam': CiscoAsyncosUpdate(updates=12, updatefailures=9), @@ -207,25 +221,24 @@ def parse_cisco_asyncos_updates(string_table: List[StringTable]) -> Mapping[str, # 'support_request': CiscoAsyncosUpdate(updates=1, updatefailures=0), # 'timezones': CiscoAsyncosUpdate(updates=1, updatefailures=0) # } -def discovery_cisco_asyncos_updates(section:Mapping[str, CiscoAsyncosUpdate])-> DiscoveryResult: +def discovery_cisco_asyncos_updates(section: Mapping[str, CiscoAsyncosUpdate]) -> DiscoveryResult: yield Service() -def check_cisco_asyncos_updates(params, section:Mapping[str, CiscoAsyncosUpdate]) -> CheckResult: + +def check_cisco_asyncos_updates(params, section: Mapping[str, CiscoAsyncosUpdate]) -> CheckResult: features_ignore = params.get('features_ignore', []) warn, crit = params.get('failedLevel') ignore_count = 0 for feature in section.keys(): - failed = section[feature].updatefailures + failed = section[feature].update_failures passed = section[feature].updates # read counters - passedLast = get_item_state('cisco_asyncos_updates_%s_passedLast' % feature) - failedLast = get_item_state('cisco_asyncos_updates_%s_failedLast' % feature) - failedAttempts = get_item_state('cisco_asyncos_updates_%s_failedAttempts' % feature) + passed_last = get_item_state('cisco_asyncos_updates_%s_passedLast' % feature) + failed_last = get_item_state('cisco_asyncos_updates_%s_failedLast' % feature) + failed_attempts = get_item_state('cisco_asyncos_updates_%s_failedAttempts' % feature) - # print('Feature %s, passed: %s/%s failed: %s/%s, Attempts: %s' % (feature, passed,passedLast, failed,failedLast, failedAttempts)) - - if (passedLast == None) or (failedLast == None) or (failedAttempts == None): # or (lastState == None): + if (passed_last is None) or (failed_last is None) or (failed_attempts is None): # or (lastState is None): # init counters set_item_state('cisco_asyncos_updates_%s_passedLast' % feature, passed) set_item_state('cisco_asyncos_updates_%s_failedLast' % feature, failed) @@ -233,50 +246,52 @@ def check_cisco_asyncos_updates(params, section:Mapping[str, CiscoAsyncosUpdate] else: set_item_state('cisco_asyncos_updates_%s_passedLast' % feature, passed) set_item_state('cisco_asyncos_updates_%s_failedLast' % feature, failed) - passedLast = int(passedLast) - failedLast = int(failedLast) - failedAttempts = int(failedAttempts) + passed_last = int(passed_last) + failed_last = int(failed_last) + failed_attempts = int(failed_attempts) # reset counter if overrun - if failed < failedLast: + if failed < failed_last: set_item_state('cisco_asyncos_updates_%s_failedLast' % feature, failed) - failedLast = failed - if passed < passedLast: + failed_last = failed + if passed < passed_last: set_item_state('cisco_asyncos_updates_%s_passedLast' % feature, passed) - passedLast = passed + passed_last = passed - if passed > passedLast: + if passed > passed_last: # reset error counter after passed update attempt set_item_state('cisco_asyncos_updates_%s_failedAttempts' % feature, 0) else: - failedAttempts = failedAttempts + failed - failedLast - set_item_state('cisco_asyncos_updates_%s_failedAttempts' % feature, failedAttempts) + failed_attempts = failed_attempts + failed - failed_last + set_item_state('cisco_asyncos_updates_%s_failedAttempts' % feature, failed_attempts) + base_message = f'{feature}: {passed}/{failed}' if feature in features_ignore: - yield Result(state=State.OK, notice='%s: %d/%d updates/update failures. Feature ignored!' % (feature, passed, failed)) + yield Result(state=State.OK, notice=f'{base_message} updates/update failures. Feature ignored!') ignore_count += 1 else: - if failedAttempts >= crit: - yield Result(state=State.CRIT, notice='%s: %d/%d passed/failed.' % (feature, passed, failed)) - lastState=-1 - elif failedAttempts >= warn: - yield Result(state=State.WARN, notice='%s: %d/%d passed/failed.' % (feature, passed, failed)) - lastState=-1 + if failed_attempts >= crit: + yield Result(state=State.CRIT, notice=f'{base_message} passed/failed.') + last_state = -1 + elif failed_attempts >= warn: + yield Result(state=State.WARN, notice=f'{base_message} passed/failed.') + last_state = -1 else: - yield Result(state=State.OK, notice='%s: %d/%d passed/failed.' % (feature, passed, failed)) - lastState=1 + yield Result(state=State.OK, notice=f'{base_message} passed/failed.') + last_state = 1 yield Metric( name=feature.replace(' ', '_'), - value=lastState, - boundaries=(2,-2) + value=last_state, + boundaries=(2, -2) ) - yield Result(state=State.OK, summary='%d/%d Features found/ignored' % (len(section.keys()), ignore_count)) + yield Result(state=State.OK, summary=f'{len(section.keys())}/{ignore_count} Features found/ignored') register.snmp_section( name='cisco_asyncos_updates', parse_function=parse_cisco_asyncos_updates, + host_label_function=host_label_cisco_asyncos, fetch=[ SNMPTree( base='.1.3.6.1.4.1.15497.1.1.1.13.1', # ASYNCOS-MAIL-MIB::updateEntry @@ -298,4 +313,3 @@ register.check_plugin( check_default_parameters={'failedLevel': (5, 10)}, check_ruleset_name='cisco_asyncos_updates', ) - diff --git a/source/gui/metrics/cisco_asyncos.py b/source/gui/metrics/cisco_asyncos.py new file mode 100644 index 0000000..68350c8 --- /dev/null +++ b/source/gui/metrics/cisco_asyncos.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# License: GNU General Public License v2 +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : 2024-04-18 +# File : cisco_asyncos.py (metrics) +# + +from cmk.gui.i18n import _ + +from cmk.gui.plugins.metrics.utils import ( + metric_info, + graph_info, + perfometer_info +) + +# +# asyncos bandwidth +# + +metric_info['bandwidth_now'] = { + 'title': _('Current bandwidth'), + 'unit': 'bytes/s', + 'color': '31/a', +} + +graph_info['cisco_asyncos.bandwidth_now'] = { + 'title': _('Cisco AsyncOS current bandwidth'), + 'metrics': [ + ('bandwidth_now', 'area'), + ], +} + +perfometer_info.append({ + 'type': 'logarithmic', + 'metric': 'bandwidth_now', + 'half_value': 1073741824, # 1GByte + 'exponent': 2, +}) + +# +# asyncos dns +# + +metric_info['outstandingDNSRequests'] = { + 'title': _('Outstanding DNS requests'), + 'unit': 'count', + 'color': '31/a', +} +metric_info['pendingDNSRequests'] = { + 'title': _('Pending DNS requests'), + 'unit': 'count', + 'color': '21/a', +} +graph_info['cisco_asyncos.dns'] = { + 'title': _('Cisco AsyncOS DNS requests'), + 'metrics': [ + ('outstandingDNSRequests', 'area'), + ('pendingDNSRequests', '-area'), + ], +} +perfometer_info.append(('stacked', [ + { + 'type': 'logarithmic', + 'metric': 'outstandingDNSRequests', + 'half_value': 100, + 'exponent': 2, + }, + { + 'type': 'logarithmic', + 'metric': 'pendingDNSRequests', + 'half_value': 100, + 'exponent': 2, + } +])) + +# +# asyncos message queue +# + +metric_info['work_queue_size'] = { + 'title': _('Work queue size'), + 'unit': 'count', + 'color': '11/a', +} + +graph_info['cisco_asyncos.queue'] = { + 'title': _('Cisco AsyncOS work queue'), + 'metrics': [ + ('work_queue_size', 'area'), + ], +} +perfometer_info.append({ + 'type': 'logarithmic', + 'metric': 'work_queue_size', + 'half_value': 100, + 'exponent': 2, +}) diff --git a/source/gui/wato/cisco_asyncos_dns.py b/source/gui/wato/cisco_asyncos_dns.py new file mode 100644 index 0000000..02b399b --- /dev/null +++ b/source/gui/wato/cisco_asyncos_dns.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# +from cmk.gui.i18n import _ +from cmk.gui.valuespec import ( + Dictionary, + Integer, + TextAscii, + Tuple, +) + +from cmk.gui.plugins.wato.utils import ( + CheckParameterRulespecWithItem, + RulespecGroupCheckParametersNetworking, + rulespec_registry, +) + + +def _parameter_valuespec_cisco_asyncos_dns(): + return Dictionary(elements=[ + ('levels_outstanding', + Tuple( + title=_('Upper levels for outstanding DNS requests'), + help=_('Number of DNS requests that have been sent but for which no reply has been received.'), + elements=[ + Integer(title=_('Warning at'), unit=_('# of requests')), + Integer(title=_('Critical at'), unit=_('# of requests')), + ]) + ), + ('levels_pending', + Tuple( + title=_('Upper levels for pending DNS requests'), + help=_('Number of DNS requests waiting to be sent.'), + elements=[ + Integer(title=_('Warning'), unit=_('# of requests')), + Integer(title=_('Critical'), unit=_('# of requests')), + ]) + ), + ]) + + +rulespec_registry.register( + CheckParameterRulespecWithItem( + check_group_name='cisco_asyncos_dns', + group=RulespecGroupCheckParametersNetworking, + item_spec=lambda: TextAscii(title=_('Cisco AsyncOS DNS'), ), + match_type='dict', + parameter_valuespec=_parameter_valuespec_cisco_asyncos_dns, + title=lambda: _('Cisco AsyncOS DNS'), + )) diff --git a/gui/wato/cisco_asyncos_feature_keys.py b/source/gui/wato/cisco_asyncos_feature_keys.py similarity index 87% rename from gui/wato/cisco_asyncos_feature_keys.py rename to source/gui/wato/cisco_asyncos_feature_keys.py index 8b38a99..a429360 100644 --- a/gui/wato/cisco_asyncos_feature_keys.py +++ b/source/gui/wato/cisco_asyncos_feature_keys.py @@ -6,15 +6,15 @@ from cmk.gui.i18n import _ from cmk.gui.valuespec import ( Dictionary, Integer, - TextAscii, ListOfStrings, + TextAscii, Tuple, ) from cmk.gui.plugins.wato.utils import ( CheckParameterRulespecWithItem, - rulespec_registry, RulespecGroupCheckParametersNetworking, + rulespec_registry, ) @@ -33,8 +33,8 @@ def _parameter_valuespec_cisco_asyncos_feature_keys(): Tuple( title=_('Levels for feature key expiration in days'), elements=[ - Integer(title=_('Warning'), default_value=30, unit=_('days before expiration')), - Integer(title=_('Critical'), default_value=7, unit=_('days before expiration')), + Integer(title=_('Warning at'), default_value=30, unit=_('days before expiration')), + Integer(title=_('Critical at'), default_value=7, unit=_('days before expiration')), ])), ]) diff --git a/gui/wato/cisco_asyncos_license.py b/source/gui/wato/cisco_asyncos_license.py similarity index 87% rename from gui/wato/cisco_asyncos_license.py rename to source/gui/wato/cisco_asyncos_license.py index fb7cbca..31c4a61 100644 --- a/gui/wato/cisco_asyncos_license.py +++ b/source/gui/wato/cisco_asyncos_license.py @@ -6,15 +6,15 @@ from cmk.gui.i18n import _ from cmk.gui.valuespec import ( Dictionary, Integer, - TextAscii, ListOfStrings, + TextAscii, Tuple, ) from cmk.gui.plugins.wato.utils import ( CheckParameterRulespecWithItem, - rulespec_registry, RulespecGroupCheckParametersNetworking, + rulespec_registry, ) @@ -34,8 +34,8 @@ def _parameter_valuespec_cisco_asyncos_license(): Tuple( title=_('Levels for licence expiration in days'), elements=[ - Integer(title=_('Warning'), default_value=30, unit=_('days before expiration')), - Integer(title=_('Critical'), default_value=7, unit=_('days before expiration')), + Integer(title=_('Warning at'), default_value=30, unit=_('days before expiration')), + Integer(title=_('Critical at'), default_value=7, unit=_('days before expiration')), ])), ]) diff --git a/gui/wato/cisco_asyncos_queue.py b/source/gui/wato/cisco_asyncos_queue.py similarity index 82% rename from gui/wato/cisco_asyncos_queue.py rename to source/gui/wato/cisco_asyncos_queue.py index 66d0258..403a5e5 100644 --- a/gui/wato/cisco_asyncos_queue.py +++ b/source/gui/wato/cisco_asyncos_queue.py @@ -7,14 +7,13 @@ from cmk.gui.valuespec import ( Dictionary, Integer, TextAscii, - ListOfStrings, Tuple, ) from cmk.gui.plugins.wato.utils import ( CheckParameterRulespecWithItem, - rulespec_registry, RulespecGroupCheckParametersNetworking, + rulespec_registry, ) @@ -24,8 +23,8 @@ def _parameter_valuespec_cisco_asyncos_queue(): Tuple( title=_('Levels for nuber of messages in work queue'), elements=[ - Integer(title=_('Warning'), default_value=50, unit=_('# of messages')), - Integer(title=_('Critical'), default_value=100, unit=_('# of messages')), + Integer(title=_('Warning at'), default_value=50, unit=_('# of messages')), + Integer(title=_('Critical at'), default_value=100, unit=_('# of messages')), ])), ]) diff --git a/gui/wato/cisco_asyncos_updates.py b/source/gui/wato/cisco_asyncos_updates.py similarity index 86% rename from gui/wato/cisco_asyncos_updates.py rename to source/gui/wato/cisco_asyncos_updates.py index 4f25b4d..5f1061e 100644 --- a/gui/wato/cisco_asyncos_updates.py +++ b/source/gui/wato/cisco_asyncos_updates.py @@ -6,15 +6,15 @@ from cmk.gui.i18n import _ from cmk.gui.valuespec import ( Dictionary, Integer, - TextAscii, ListOfStrings, + TextAscii, Tuple, ) from cmk.gui.plugins.wato.utils import ( CheckParameterRulespecWithItem, - rulespec_registry, RulespecGroupCheckParametersNetworking, + rulespec_registry, ) @@ -32,8 +32,8 @@ def _parameter_valuespec_cisco_asyncos_updates(): Tuple( title=_('Levels for failed attempts'), elements=[ - Integer(title=_('Warning'), default_value=5, unit=_('# of failed attempts')), - Integer(title=_('Critical'), default_value=10, unit=_('# of failed attempts')), + Integer(title=_('Warning at'), default_value=5, unit=_('# of failed attempts')), + Integer(title=_('Critical at'), default_value=10, unit=_('# of failed attempts')), ])), ]) diff --git a/packages/cisco_asyncos b/source/packages/cisco_asyncos similarity index 89% rename from packages/cisco_asyncos rename to source/packages/cisco_asyncos index 40f7910..bd98f08 100644 --- a/packages/cisco_asyncos +++ b/source/packages/cisco_asyncos @@ -42,10 +42,12 @@ 'gui': ['wato/cisco_asyncos_feature_keys.py', 'wato/cisco_asyncos_license.py', 'wato/cisco_asyncos_queue.py', - 'wato/cisco_asyncos_updates.py']}, + 'wato/cisco_asyncos_updates.py', + 'metrics/cisco_asyncos.py', + 'wato/cisco_asyncos_dns.py']}, 'name': 'cisco_asyncos', 'title': 'Cisco AsyncOS (IronPort) checks', - 'version': '20230306.v0.3.0', - 'version.min_required': '2.1.0', - 'version.packaged': '2.1.0p22', - 'version.usable_until': None} \ No newline at end of file + 'version': '0.4.1-20240418', + 'version.min_required': '2.1.0b1', + 'version.packaged': '2.2.0p24', + 'version.usable_until': None} -- GitLab