├── .gitignore ├── Dockerfile ├── README.md ├── Storage Pystormon 4.4.xml ├── Storage Pystormon 5.2.json ├── Storage Pystormon.json ├── build.sh ├── conf.d ├── devices.conf ├── monitored_properties.json └── pystormon.conf ├── configread.py ├── dockerrun.sh ├── functions.py ├── pynetdevices.py ├── requirements.txt ├── storage_cim_print.py ├── storage_cim_print_search.py ├── storage_objects_discovery.py ├── storage_objects_status.py └── storage_perfomance.py /.gitignore: -------------------------------------------------------------------------------- 1 | _pycache__/ 2 | .vscode 3 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos:latest 2 | LABEL maintainer="Denis O. Pavlov pavlovdo@gmail.com" 3 | 4 | ARG project 5 | 6 | RUN yum update -y && yum install -y \ 7 | cronie \ 8 | epel-release \ 9 | python36 10 | 11 | COPY *.py requirements.txt /etc/zabbix/externalscripts/${project}/ 12 | WORKDIR /etc/zabbix/externalscripts/${project} 13 | 14 | RUN pip3.6 install -r requirements.txt 15 | 16 | ENV TZ=Europe/Moscow 17 | RUN ln -fs /usr/share/zoneinfo/$TZ /etc/localtime 18 | 19 | RUN echo "00 */1 * * * /etc/zabbix/externalscripts/pystormon/storage_objects_discovery.py 1> /proc/1/fd/1 2> /proc/1/fd/2" > /tmp/crontab && \ 20 | echo "*/5 * * * * /etc/zabbix/externalscripts/pystormon/storage_objects_status.py 1> /proc/1/fd/1 2> /proc/1/fd/2" >> /tmp/crontab && \ 21 | echo "*/1 * * * * /etc/zabbix/externalscripts/pystormon/storage_perfomance.py 1> /proc/1/fd/1 2> /proc/1/fd/2" >> /tmp/crontab && \ 22 | crontab /tmp/crontab && rm /tmp/crontab 23 | 24 | CMD ["crond","-n"] 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Description 2 | =========== 3 | Zabbix Storage Monitoring via CIM/WBEM 4 | 5 | 6 | Requirements 7 | ============ 8 | 9 | 1) python >= 3.6 10 | 11 | 2) python module pywbem: connect and get information from storage via CIM/WBEM 12 | 13 | 3) zabbix-server (tested with versions 4.4-5.2) 14 | 15 | 4) python module py-zabbix: sending traps to zabbix 16 | 17 | 18 | Installation 19 | ============ 20 | 1) Give access to storage statistic and parameters of your storages for monitoring user. 21 | For collect statistic under user zabbix from IBM Storwize: 22 | ``` 23 | chuser -usergrp RestrictedAdmin zabbix 24 | ``` 25 | 26 | 2) Tune statistic collection interval. For collect statistic every minute from IBM Storwize: 27 | 28 | ``` 29 | startstats -interval 1 30 | ``` 31 | 32 | 3) Clone pystormon repo to directory /etc/zabbix/externalscripts of monitoring server: 33 | ``` 34 | sudo mkdir -p /etc/zabbix/externalscripts 35 | sudo git clone https://github.com/pavlovdo/pystormon /etc/zabbix/externalscripts/pystormon 36 | cd /etc/zabbix/externalscripts/pystormon 37 | ``` 38 | 39 | 4) A) Check execute permissions for scripts: 40 | ``` 41 | ls -l *.py *.sh 42 | ``` 43 | B) If not: 44 | ``` 45 | sudo chmod +x *.py *.sh 46 | ``` 47 | 48 | 5) Change example configuration files pystormon.conf: storages login/password, address of zabbix_server; 49 | 50 | 6) Change example configuration files devices.conf: IP and hostnames of storages; 51 | 52 | 7) Give and check network access from monitoring server to storage management network CIM/WBEM port (TCP/5989); 53 | 54 | 8) Check configuration and running zabbix trappers on your zabbix server or proxy: 55 | ``` 56 | ### Option: StartTrappers 57 | # Number of pre-forked instances of trappers. 58 | # Trappers accept incoming connections from Zabbix sender, active agents and active proxies. 59 | # At least one trapper process must be running to display server availability and view queue 60 | # in the frontend. 61 | # 62 | # Mandatory: no 63 | # Range: 0-1000 64 | # Default: 65 | # StartTrappers=5 66 | ``` 67 | ``` 68 | # ps aux | grep trapper 69 | zabbix 776389 0.2 0.4 2049416 111772 ? S дек07 63:41 /usr/sbin/zabbix_server: trapper #1 [processed data in 0.000166 sec, waiting for connection] 70 | zabbix 776390 0.2 0.4 2049512 112016 ? S дек07 63:43 /usr/sbin/zabbix_server: trapper #2 [processed data in 0.000342 sec, waiting for connection] 71 | zabbix 776391 0.2 0.4 2049452 112092 ? S дек07 63:12 /usr/sbin/zabbix_server: trapper #3 [processed data in 0.000301 sec, waiting for connection] 72 | zabbix 776392 0.2 0.4 2049600 112064 ? S дек07 63:57 /usr/sbin/zabbix_server: trapper #4 [processed data in 0.000187 sec, waiting for connection] 73 | zabbix 776393 0.2 0.4 2049412 111836 ? S дек07 63:31 /usr/sbin/zabbix_server: trapper #5 [processed data in 0.000176 sec, waiting for connection] 74 | ``` 75 | 76 | 9) Import template Storage Pystormon.json to Zabbix, if use Zabbix 5.2, 77 | and Storage Pystormon 4.4.xml (no more support) for Zabbix 4.4; 78 | 79 | 10) Create your storage hosts in Zabbix and link template Storage Pystormon to it. 80 | In host configuration set parameters "Host name" and "IP address" for Agent Interface. 81 | Use the same hostname as in the file devices.conf, storwize.example.com for example. 82 | 83 | 11) Further you have options: run scripts from host or run scripts from docker container. 84 | 85 | If you want to run scripts from host: 86 | 87 | A) Install Python 3 and pip3 if it is not installed; 88 | 89 | B) Install required python modules: 90 | ``` 91 | pip3 install -r requirements.txt 92 | ``` 93 | 94 | C) Create cron jobs for zabbix trappers: 95 | ``` 96 | echo "00 */1 * * * /etc/zabbix/externalscripts/pystormon/storage_objects_discovery.py 1> /dev/null" > /tmp/crontab && \ 97 | echo "*/5 * * * * /etc/zabbix/externalscripts/pystormon/storage_objects_status.py 1> /dev/null" >> /tmp/crontab && \ 98 | echo "*/1 * * * * /etc/zabbix/externalscripts/pystormon/storage_perfomance.py 1> /dev/null" >> /tmp/crontab && \ 99 | crontab /tmp/crontab && rm /tmp/crontab 100 | ``` 101 | 102 | 103 | If you want to run scripts from docker container: 104 | 105 | A) Run build.sh: 106 | ``` 107 | cd /etc/zabbix/externalscripts/pystormon 108 | ./build.sh 109 | ``` 110 | 111 | B) Run dockerrun.sh; 112 | ``` 113 | ./dockerrun.sh 114 | ``` 115 | 116 | 117 | Notes 118 | ====== 119 | 1) You can add or remove monitoring of your storage cim classes and properties in file monitored_properties.json 120 | and in template Storage Pystormon. Storage CIM classes maps to Zabbix discoveries, and CIM class properties maps 121 | to Zabbix discoveries items. 122 | 123 | 124 | 2) You can change perfomance macros values in template Storage Pystormon (for Zabbix 5.2 templates); 125 | 126 | 127 | 3) For send exception alarms via slack hook to your slack channel, set parameter slack_hook in conf.d/pystormon.conf. 128 | More details in https://api.slack.com/messaging/webhooks 129 | 130 | 131 | 4) You can print all names/values from those storage CIM classes that exist in monitored_properties.json, via script storage_cim_print.py. 132 | It get CIM classes from monitored_properties.json and print all names/values for its. 133 | 134 | 135 | 5) If you start storage_cim_print.py and get empty values for some classes, for example classes IBMTSSVC_StorageVolume, IBMTSSVC_StorageVolumeStatistics, 136 | check that you create corresponding objects (VDisks in our example) on your storage; 137 | 138 | 139 | 6) You can print any storage CIM class and it property's names/values via script storage_cim_print_search.py. For that you have to create directory with your username permissions for detected_properties_file: 140 | ``` 141 | sudo mkdir /var/tmp/pystormon && sudo chown username /var/tmp/pystormon 142 | ``` 143 | 144 | And now you set search substrings via script arguments, for example: 145 | ``` 146 | /etc/zabbix/externalscripts/pystormon/storage_cim_print_search.py FC iSCSI 147 | ``` 148 | 149 | In result you get output of property's names and values of all storage CIM classes that contain word 'FC' and 'iSCSI' (case sensitive) to console and to file set by config parameter detected_properties_file (see pystormon.conf): 150 | ``` 151 | $ cat /var/tmp/pystormon/detected_properties.txt 152 | Device: storwize.example.com CIM class: IBMTSSVC_iSCSICapabilities 153 | Property: Caption, Value: None 154 | 155 | Device: storwize1.example.com, CIM class: IBMTSSVC_iSCSICapabilities 156 | Property: Description, Value: None 157 | ... 158 | ``` 159 | 160 | 161 | Tested 162 | ====== 163 | IBM Storwize v3700/v5010/v5030/v7000, software versions 7.8.x - 8.3.x 164 | 165 | Zabbix 5.2 166 | 167 | 168 | Related Links 169 | ============= 170 | http://pywbem.github.io/pywbem/ 171 | 172 | https://www.snia.org/forums/smi/knowledge/smis-getting-started/smi_architecture 173 | 174 | https://www.ibm.com/support/knowledgecenter/STHGUJ_8.3.1/com.ibm.storwize.tb5.831.doc/svc_cim_main.html 175 | 176 | https://github.com/adubkov/py-zabbix 177 | 178 | https://api.slack.com/messaging/webhooks 179 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | readonly PROJECT=pystormon 4 | readonly CONTAINER_OLD=$(docker ps -q --all --filter name="$PROJECT") 5 | 6 | docker stop "$CONTAINER_OLD" 7 | docker rm "$CONTAINER_OLD" 8 | docker build --tag "$PROJECT" --build-arg project="$PROJECT" . 9 | -------------------------------------------------------------------------------- /conf.d/devices.conf: -------------------------------------------------------------------------------- 1 | storwize:storwize.example.com:192.168.0.1 2 | -------------------------------------------------------------------------------- /conf.d/monitored_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "Array": { 3 | "cim_class": "IBMTSSVC_Array", 4 | "cim_property_name": "ElementName", 5 | "cim_properties_mon": [ 6 | "Access", 7 | "AdditionalAvailability", 8 | "AllocatedCapacity", 9 | "BlockSize", 10 | "Capacity", 11 | "ConsumableBlocks", 12 | "DataOrganization", 13 | "DataRedundancy", 14 | "Dedupe", 15 | "DeltaReservation", 16 | "Distributed", 17 | "ElementName", 18 | "EnabledDefault", 19 | "EnabledState", 20 | "Encrypt", 21 | "ExtentStatus", 22 | "ExtentStripeLength", 23 | "IsBasedOnUnderlyingRedundancy", 24 | "IsConcatenated", 25 | "Mode", 26 | "NativeStatus", 27 | "NoSinglePointOfFailure", 28 | "NumberOfBlocks", 29 | "OperationalStatus", 30 | "OverProvisioned", 31 | "PackageRedundancy", 32 | "PhysicalCapacity", 33 | "PhysicalFreeCapacity", 34 | "PoolID", 35 | "Poolname", 36 | "Primordial", 37 | "RaidLevel", 38 | "RaidStatus", 39 | "Redundancy", 40 | "RequestedState", 41 | "SequentialAccess", 42 | "StripSize", 43 | "SupportsUnmap", 44 | "WriteProtected" 45 | ], 46 | "zabbix_discovery_key": "arrays.discovery" 47 | }, 48 | "DiskDrive": { 49 | "cim_class": "IBMTSSVC_DiskDrive", 50 | "cim_perfomance_class": "IBMTSSVC_DiskDriveStatistics", 51 | "cim_property_name": "Name", 52 | "cim_properties_mon": [ 53 | "BlockSize", 54 | "Capacity", 55 | "Compressed", 56 | "DeviceID", 57 | "EffectiveUsedCapacity", 58 | "EnabledDefault", 59 | "EnabledState", 60 | "EnclosureID", 61 | "ErrorSequenceNumber", 62 | "FirmwareLevel", 63 | "FRUIdentity", 64 | "FRUPartNum", 65 | "HealthState", 66 | "MdiskID", 67 | "MdiskName", 68 | "MemberID", 69 | "Name", 70 | "OperationalStatus", 71 | "PhysicalCapacity", 72 | "PhysicalUsedCapacity", 73 | "ProductID", 74 | "QuorumId", 75 | "RequestedState", 76 | "RPM", 77 | "SlotID", 78 | "TechType", 79 | "TransitioningToState", 80 | "UID", 81 | "Use", 82 | "VendorID" 83 | ], 84 | "cim_properties_perfomance": [ 85 | "IOTimeCounter", 86 | "KBytesRead", 87 | "KBytesTransferred", 88 | "KBytesWritten", 89 | "ReadIOs", 90 | "ReadIOTimeCounter", 91 | "TotalIOs", 92 | "WriteIOs", 93 | "WriteIOTimeCounter" 94 | ], 95 | "zabbix_discovery_key": "diskdrives.discovery" 96 | }, 97 | "Enclosure": { 98 | "cim_class": "IBMTSSVC_Enclosure", 99 | "cim_property_name": "ElementName", 100 | "cim_properties_mon": [ 101 | "ChassisPackageType", 102 | "DriveSlots", 103 | "ElementName", 104 | "EnclosureStatus", 105 | "HealthState", 106 | "IOGroupID", 107 | "IOGroupName", 108 | "Managed", 109 | "Manufacturer", 110 | "Model", 111 | "OnlineCanisters", 112 | "OnlineFanModules", 113 | "OnlinePSUs", 114 | "OperationalStatus", 115 | "PackageType", 116 | "ProductMTM", 117 | "RemovalConditions", 118 | "SerialNumber", 119 | "Tag", 120 | "TotalCanisters", 121 | "TotalFanModules", 122 | "TotalPSUs", 123 | "Type" 124 | ], 125 | "zabbix_discovery_key": "enclosures.discovery" 126 | }, 127 | "mdisk": { 128 | "cim_class": "IBMTSSVC_BackendVolume", 129 | "cim_property_name": "ElementName", 130 | "cim_properties_mon": [ 131 | "Access", 132 | "AdditionalAvailability", 133 | "BlockSize", 134 | "Capacity", 135 | "ConsumableBlocks", 136 | "Counts", 137 | "DataOrganization", 138 | "DataRedundancy", 139 | "DeltaReservation", 140 | "DeviceID", 141 | "ElementName", 142 | "EnabledDefault", 143 | "EnabledState", 144 | "ExtentStatus", 145 | "IsBasedOnUnderlyingRedundancy", 146 | "Mode", 147 | "NativeStatus", 148 | "NoSinglePointOfFailure", 149 | "NumberOfBlocks", 150 | "OperationalStatus", 151 | "PackageRedundancy", 152 | "PoolID", 153 | "Poolname", 154 | "Primordial", 155 | "RequestedState", 156 | "SlotLocation", 157 | "TransitioningToState" 158 | ], 159 | "zabbix_discovery_key": "mdisks.discovery" 160 | }, 161 | "mdiskgrp": { 162 | "cim_class": "IBMTSSVC_ConcreteStoragePool", 163 | "cim_property_name": "ElementName", 164 | "cim_properties_mon": [ 165 | "ChildCapacity", 166 | "ElementName", 167 | "ElementsShareSpace", 168 | "ExtentSize", 169 | "InstanceID", 170 | "Name", 171 | "NativeStatus", 172 | "NumberOfBackendVolumes", 173 | "NumberOfStorageVolumes", 174 | "OperationalStatus", 175 | "Overallocation", 176 | "ParentPoolID", 177 | "PoolID", 178 | "Primordial", 179 | "RemainingManagedSpace", 180 | "SpaceLimit", 181 | "SpaceLimitDetermination", 182 | "TotalManagedSpace", 183 | "Usage", 184 | "UsedCapacity", 185 | "VirtualCapacity", 186 | "Warning" 187 | ], 188 | "zabbix_discovery_key": "mdiskgrps.discovery" 189 | }, 190 | "System": { 191 | "cim_class": "IBMTSSVC_Cluster", 192 | "cim_property_name": "ElementName", 193 | "cim_properties_mon": [ 194 | "AllocatedCapacity", 195 | "AvailableCapacity", 196 | "BackendStorageCapacity", 197 | "ClusterState", 198 | "CodeLevel", 199 | "ConfiguredAddress", 200 | "ConnectionType", 201 | "ConsoleIP", 202 | "ConsolePort", 203 | "DiscoveryStatus", 204 | "ElementName", 205 | "EMailContact", 206 | "EMailContactAlternate", 207 | "EMailContactLocation", 208 | "EMailContactPrimary", 209 | "EMailInterval", 210 | "EMailReply", 211 | "EMailServer", 212 | "EmailSetting", 213 | "EMailState", 214 | "EMailUserCount", 215 | "EnabledDefault", 216 | "EnabledState", 217 | "FcPortSpeed", 218 | "GMInterClusterDelaySimulation", 219 | "GMIntraClusterDelaySimulation", 220 | "GMLinkTolerance", 221 | "ID", 222 | "iSCSIAuthMethod", 223 | "Locale", 224 | "MaxNumberOfNodes", 225 | "Name", 226 | "OperationalStatus", 227 | "PoolCapacity", 228 | "RequestedState", 229 | "RequiredMemory", 230 | "ResetCapability", 231 | "SNMPCommunity", 232 | "SNMPServerIP", 233 | "SNMPSetting", 234 | "StatisticsFrequency", 235 | "StatisticsStatus", 236 | "TimeZone", 237 | "Topology", 238 | "TotalOverallocation", 239 | "TotalUsedCapacity", 240 | "TotalVdiskCapacity", 241 | "TotalVdiskCopyCapacity", 242 | "TransitioningToState", 243 | "VolumeProtection", 244 | "VolumeProtectionTime" 245 | ], 246 | "zabbix_discovery_key": "systems.discovery" 247 | }, 248 | "VDisk": { 249 | "cim_class": "IBMTSSVC_StorageVolume", 250 | "cim_perfomance_class": "IBMTSSVC_StorageVolumeStatistics", 251 | "cim_property_name": "ElementName", 252 | "cim_properties_mon": [ 253 | "Access", 254 | "AccessGranted", 255 | "BlockSize", 256 | "CacheMode", 257 | "CacheState", 258 | "Cluster", 259 | "ConsumableBlocks", 260 | "Controlled", 261 | "CopyCount", 262 | "DataOrganization", 263 | "DataRedundancy", 264 | "DeltaReservation", 265 | "DeviceID", 266 | "ElementName", 267 | "EnabledDefault", 268 | "EnabledState", 269 | "ExtentStatus", 270 | "FlashCopyMapCount", 271 | "GroupID", 272 | "GroupName", 273 | "IsBasedOnUnderlyingRedundancy", 274 | "IsFormatted", 275 | "IsSpaceEfficient", 276 | "LastAccessTime", 277 | "Name", 278 | "NameFormat", 279 | "NameNamespace", 280 | "NativeStatus", 281 | "NoSinglePointOfFailure", 282 | "NumberOfBlocks", 283 | "OperationalStatus", 284 | "PackageRedundancy", 285 | "PoolID", 286 | "PoolName", 287 | "PreferredNode", 288 | "Primordial", 289 | "RCChange", 290 | "RequestedState", 291 | "SequentialAccess", 292 | "SyncRate", 293 | "SystemName", 294 | "ThinlyProvisioned", 295 | "TransitioningToState", 296 | "Type", 297 | "UncompressedUsedCapacity", 298 | "UniqueID", 299 | "VolumeId" 300 | ], 301 | "cim_properties_perfomance": [ 302 | "IOTimeCounter", 303 | "KBytesRead", 304 | "KBytesTransferred", 305 | "KBytesWritten", 306 | "ReadHitIOs", 307 | "ReadIOs", 308 | "ReadIOTimeCounter", 309 | "TotalIOs", 310 | "WriteHitIOs", 311 | "WriteIOs", 312 | "WriteIOTimeCounter" 313 | ], 314 | "zabbix_discovery_key": "vdisks.discovery" 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /conf.d/pystormon.conf: -------------------------------------------------------------------------------- 1 | [NetworkDevice] 2 | device_file = /etc/zabbix/externalscripts/pystormon/conf.d/devices.conf 3 | login = zabbix 4 | name_space = root/ibm 5 | password = zabbix 6 | zabbix_server = zabbix.example.com 7 | slack_hook = 8 | printing = False 9 | 10 | [StorageDevice] 11 | detected_properties_file = /var/tmp/pystormon/detected_properties.txt 12 | monitored_properties_file = /etc/zabbix/externalscripts/pystormon/conf.d/monitored_properties.json 13 | -------------------------------------------------------------------------------- /configread.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Python module for load parameters from main config file 4 | 5 | import configparser 6 | 7 | 8 | def configread(conffile, section, *parameters): 9 | 10 | # read configuration file 11 | config = configparser.RawConfigParser() 12 | config.read(conffile) 13 | params = {} 14 | 15 | # check presence parameters in config file 16 | for parameter in parameters: 17 | try: 18 | params[parameter] = config.get(section, parameter) 19 | except configparser.NoOptionError as error: 20 | print(f'{error}. Please set {parameter} value ' 21 | f'in the configuration file {conffile}') 22 | 23 | return params 24 | -------------------------------------------------------------------------------- /dockerrun.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | readonly PROJECT=pystormon 4 | readonly CONFIG_DIR=/etc/zabbix/externalscripts/$PROJECT/conf.d 5 | readonly DATA_DIR=/var/tmp/$PROJECT 6 | 7 | message="\nRunning of container from image $PROJECT with name $PROJECT and mounting $CONFIG_DIR':'$CONFIG_DIR':ro 8 | and $DATA_DIR:$DATA_DIR" 9 | 10 | docker run --detach --tty --name "$PROJECT" --restart=always --volume "$CONFIG_DIR":"$CONFIG_DIR":ro \ 11 | --volume "$DATA_DIR":"$DATA_DIR" "$PROJECT" 12 | echo -e "${message}" 13 | -------------------------------------------------------------------------------- /functions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import requests 5 | import socket 6 | 7 | from configread import configread 8 | from pyzabbix import ZabbixSender 9 | from socket import timeout 10 | from sys import stderr 11 | 12 | # set project name as current directory name 13 | project = os.path.abspath(__file__).split('/')[-2] 14 | 15 | # get config file name 16 | conf_file = ( 17 | f'/etc/zabbix/externalscripts/{project}/conf.d/{project}.conf') 18 | 19 | # read network device parameters from config and save it to dict 20 | nd_parameters = configread(conf_file, 'NetworkDevice', 21 | 'slack_hook', 'zabbix_server') 22 | 23 | # get variables for error notifications 24 | slack_hook = nd_parameters['slack_hook'] 25 | zabbix_server = nd_parameters['zabbix_server'] 26 | 27 | hostname = socket.gethostname() 28 | 29 | 30 | def slack_post(software, message, icon_emoji=':snake:'): 31 | """ post alarms to slack channel """ 32 | 33 | if slack_hook: 34 | requests.post(slack_hook, json={'username': hostname, 35 | 'icon_emoji': icon_emoji, 36 | 'text': f'{project}_error: exception in {software}: {message}'}) 37 | 38 | 39 | def zabbix_send(data, printing, software): 40 | """ send packet of data to zabbix """ 41 | 42 | try: 43 | zabbix_send_status = ZabbixSender(zabbix_server).send(data) 44 | if printing: 45 | print( 46 | f'Status of sending data to zabbix:\n{zabbix_send_status}') 47 | except timeout as error: 48 | print( 49 | (f'{project}_error: exception in {software}: Zabbix trapper socket timeout on {zabbix_server}: {error}. ' 50 | f'Check health status of zabbix server.'), 51 | file=stderr) 52 | slack_post( 53 | software, 54 | (f'Zabbix trapper socket timeout on {zabbix_server}: {error}. ' 55 | f'Check health status of zabbix server.')) 56 | exit(1) 57 | except ConnectionRefusedError as error: 58 | print( 59 | (f'{project}_error: exception in {software}: Zabbix trapper refused connection on {zabbix_server}: {error}. ' 60 | f'Check running of zabbix trappers and firewall trafic permissions.'), 61 | file=stderr) 62 | slack_post( 63 | software, 64 | (f'Zabbix trapper refused connection on {zabbix_server}: {error}. ' 65 | f'Check running of zabbix trappers and firewall trafic permissions.')) 66 | exit(1) 67 | except ConnectionResetError as error: 68 | print( 69 | (f'{project}_error: exception in {software}: Zabbix trapper reset connection on {zabbix_server}: {error}. ' 70 | f'Check running of zabbix trappers.'), 71 | file=stderr) 72 | slack_post( 73 | software, 74 | (f'Zabbix trapper reset connection on {zabbix_server}: {error}. ' 75 | f'Check running of zabbix trappers.')) 76 | exit(1) 77 | -------------------------------------------------------------------------------- /pynetdevices.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | 4 | class NetworkDevice: 5 | """ base class for network devices """ 6 | 7 | def __init__(self, hostname, ip, login=None, password=None): 8 | 9 | self.hostname = hostname 10 | self.ip = ip 11 | self.login = login 12 | self.password = password 13 | 14 | 15 | class WBEMDevice(NetworkDevice): 16 | """ class for WBEM devices """ 17 | 18 | def Connect(self, namespace='root/ibm', printing=False): 19 | 20 | from pywbem import WBEMConnection 21 | 22 | server_uri = f'https://{self.ip.rstrip()}' 23 | 24 | conn = WBEMConnection(server_uri, (self.login, self.password), 25 | namespace, no_verification=True) 26 | 27 | return conn 28 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | py-zabbix>=1.1.3 2 | pywbem>=0.12.3 3 | -------------------------------------------------------------------------------- /storage_cim_print.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # 4 | # IBM Storwize CIM print 5 | # 6 | # 2020 Denis Pavlov 7 | # 8 | # Print names and values of properties of storage CIM classes from monitored_properies.json 9 | # 10 | 11 | import os 12 | import sys 13 | 14 | from configread import configread 15 | from json import load 16 | from pynetdevices import WBEMDevice 17 | from pywbem import _exceptions 18 | 19 | 20 | def main(): 21 | 22 | # get script name 23 | software = sys.argv[0] 24 | 25 | # set project name as current directory name 26 | project = os.path.abspath(__file__).split('/')[-2] 27 | 28 | # get config file name 29 | conf_file = ( 30 | f'/etc/zabbix/externalscripts/{project}/conf.d/{project}.conf') 31 | 32 | # read network device parameters from config and save it to dict 33 | nd_parameters = configread(conf_file, 'NetworkDevice', 'device_file', 34 | 'login', 'password', 'name_space') 35 | 36 | # read storage device parameters from config and save it to another dict 37 | sd_parameters = configread(conf_file, 'StorageDevice', 38 | 'monitored_properties_file') 39 | 40 | # form dictionary of matching storage concepts and cim properties 41 | # more details in https://www.ibm.com/support/knowledgecenter/STHGUJ_8.3.1/com.ibm.storwize.v5000.831.doc/svc_conceptsmaptocimconcepts_3skacv.html 42 | with open(sd_parameters['monitored_properties_file'], "r") as monitored_properties_file: 43 | monitored_properties = load(monitored_properties_file) 44 | 45 | # get variables 46 | login = nd_parameters['login'] 47 | password = nd_parameters['password'] 48 | 49 | # open config file with list of monitored storages 50 | device_list_file = open(nd_parameters['device_file']) 51 | 52 | # unpack storage list to variables 53 | for device_line in device_list_file: 54 | device_type, device_name, device_ip = device_line.split(':') 55 | device_ip = device_ip.rstrip('\n') 56 | 57 | # connect to each storage via WBEM, get conn object 58 | if device_type == 'storwize': 59 | device = WBEMDevice(device_name, device_ip, login, password) 60 | # get namespace from config, root/ibm by default 61 | namespace = nd_parameters.get('name_space', 'root/ibm') 62 | conn = device.Connect(namespace) 63 | 64 | # print all properties for all instances (objects) for cim classes from dict sc_maps 65 | for storage_concept in monitored_properties: 66 | storage_cim_class = monitored_properties[storage_concept]['cim_class'] 67 | 68 | # try to request storage via WBEM 69 | try: 70 | instances = conn.EnumerateInstances(storage_cim_class, 71 | namespace=nd_parameters['name_space']) 72 | except _exceptions.AuthError as error: 73 | print((f'{project}_error: exception in {software}: can\'t exec query on {device_name}: {error} ' 74 | f'Check your username/password and permissions of user.'), 75 | file=sys.stderr) 76 | exit(1) 77 | except _exceptions.ConnectionError as error: 78 | print((f'{project}_error: exception in {software}: can\'t exec query on {device_name}: {error}. ' 79 | f'Check the connection to storage or try later.'), 80 | file=sys.stderr) 81 | exit(1) 82 | except _exceptions.HTTPError as error: 83 | print((f'{project}_error: exception in {software}: WBEM server return code 400 (Bad Request) on {device_name}: {error}. ' 84 | f'Check the your request.'), 85 | file=sys.stderr) 86 | exit(1) 87 | except: 88 | print(f'{project}_error: exception in {software}: {sys.exc_info()}', 89 | file=sys.stderr) 90 | exit(1) 91 | 92 | for instance in instances: 93 | for prop_name, prop_value in instance.items(): 94 | print( 95 | (f'Device: {device_name}, Concept: {storage_concept}, ' 96 | f'CIM class: {storage_cim_class}\nProperty: {prop_name}, ' 97 | f'Value: {prop_value}\n')) 98 | 99 | device_list_file.close() 100 | 101 | 102 | if __name__ == "__main__": 103 | main() 104 | -------------------------------------------------------------------------------- /storage_cim_print_search.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # 4 | # IBM Storwize CIM print 5 | # 6 | # 2020 - 2021 Denis Pavlov 7 | # 8 | # Print CIM properties of Storwize classes with names set by search string to stdout and detected_properties_file 9 | # 10 | 11 | import os 12 | import sys 13 | 14 | from configread import configread 15 | from json import load 16 | from pynetdevices import WBEMDevice 17 | from pywbem import _exceptions 18 | 19 | 20 | def main(): 21 | 22 | # set project name as current directory name 23 | project = os.path.abspath(__file__).split('/')[-2] 24 | 25 | software = sys.argv[0] 26 | 27 | if len(sys.argv) == 1: 28 | print((f'Please add argument(s) to call of program for search Storage CIM classes: \n' 29 | f'Example of syntax: {software} Array Volume')) 30 | exit(1) 31 | 32 | search_strings = sys.argv[1:] 33 | 34 | # get config file name 35 | conf_file = ( 36 | f'/etc/zabbix/externalscripts/{project}/conf.d/{project}.conf') 37 | 38 | # read network device parameters from config and save it to dict 39 | nd_parameters = configread(conf_file, 'NetworkDevice', 'device_file', 40 | 'login', 'name_space', 'password') 41 | 42 | sd_parameters = configread( 43 | conf_file, 'StorageDevice', 'detected_properties_file') 44 | 45 | # get variables 46 | login = nd_parameters['login'] 47 | password = nd_parameters['password'] 48 | 49 | # open config file with list of monitored storages 50 | device_list_file = open(nd_parameters['device_file']) 51 | 52 | # open properties file for writing 53 | detected_properties_file = open( 54 | sd_parameters['detected_properties_file'], 'w') 55 | 56 | # unpack storage list to variables 57 | for device_line in device_list_file: 58 | device_type, device_name, device_ip = device_line.split(':') 59 | device_ip = device_ip.rstrip('\n') 60 | 61 | # connect to each storage via WBEM, get conn object 62 | if device_type == 'storwize': 63 | device = WBEMDevice(device_name, device_ip, login, password) 64 | # get namespace from config, root/ibm by default 65 | namespace = nd_parameters.get('name_space', 'root/ibm') 66 | 67 | print(f'Connecting to {device_name} ...') 68 | conn = device.Connect(namespace) 69 | 70 | # try to get all cim classes from storage via WBEM 71 | try: 72 | sc_cim_classes = conn.EnumerateClassNames( 73 | namespace=nd_parameters['name_space'], DeepInheritance=True) 74 | except _exceptions.AuthError as error: 75 | print((f'{project}_error: exception in {software}: can\'t exec query on {device_name}: {error} ' 76 | f'Check your username/password and permissions of user.'), 77 | file=sys.stderr) 78 | exit(1) 79 | except _exceptions.ConnectionError as error: 80 | print((f'{project}_error: exception in {software}: can\'t exec query on {device_name}: {error}. ' 81 | f'Check the connection to storage or try later.'), 82 | file=sys.stderr) 83 | exit(1) 84 | except _exceptions.HTTPError as error: 85 | print((f'{project}_error: exception in {software}: WBEM server return code 400 (Bad Request) on {device_name}: {error}. ' 86 | f'Check the your request.'), 87 | file=sys.stderr) 88 | exit(1) 89 | except: 90 | print(f'{project}_error: exception in {software}: {sys.exc_info()}', 91 | file=sys.stderr) 92 | exit(1) 93 | 94 | # try to get all instances for each cim class from storage via WBEM 95 | for sc_cim_class in sc_cim_classes: 96 | for search_string in search_strings: 97 | if sc_cim_class.find(search_string) > 0: 98 | try: 99 | instances = conn.EnumerateInstances(sc_cim_class, 100 | namespace=nd_parameters['name_space']) 101 | except _exceptions.AuthError as error: 102 | print((f'{project}_error: exception in {software}: can\'t exec query on {device_name}: {error} ' 103 | f'Check your username/password and permissions of user.'), 104 | file=sys.stderr) 105 | exit(1) 106 | except _exceptions.ConnectionError as error: 107 | print((f'{project}_error: exception in {software}: can\'t exec query on {device_name}: {error}. ' 108 | f'Check the connection to storage or try later.'), 109 | file=sys.stderr) 110 | exit(1) 111 | except _exceptions.HTTPError as error: 112 | print((f'{project}_error: exception in {software}: WBEM server return code 400 (Bad Request) on {device_name}: {error}. ' 113 | f'Check the your request.'), 114 | file=sys.stderr) 115 | exit(1) 116 | except: 117 | print(f'{project}_error: exception in {software}: {sys.exc_info()}', 118 | file=sys.stderr) 119 | exit(1) 120 | 121 | for instance in instances: 122 | for prop_name, prop_value in instance.items(): 123 | output_string = (f'Device: {device_name}, CIM class: {sc_cim_class}\n' 124 | f'Property: {prop_name}, Value: {prop_value}\n') 125 | print(output_string) 126 | detected_properties_file.write( 127 | f'{output_string} \n') 128 | 129 | device_list_file.close() 130 | detected_properties_file.close() 131 | 132 | 133 | if __name__ == "__main__": 134 | main() 135 | -------------------------------------------------------------------------------- /storage_objects_discovery.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # 4 | # IBM Storwize objects discovery for Zabbix 5 | # 6 | # 2016-2020 Denis Pavlov 7 | # 8 | # Discover storage objects from Storwize including physical and logical disks, via CIM/WBEM and sends it to Zabbix Server via Zabbix Sender API 9 | # 10 | # Use with template Template Storage Pystormon 11 | # 12 | 13 | import os 14 | import sys 15 | 16 | from configread import configread 17 | from functions import slack_post, zabbix_send 18 | from json import load 19 | from pynetdevices import WBEMDevice 20 | from pywbem import _exceptions 21 | from pyzabbix import ZabbixMetric 22 | 23 | 24 | # set project as current directory name, software as name of current script 25 | project = os.path.abspath(__file__).split('/')[-2] 26 | software = sys.argv[0] 27 | 28 | 29 | def storage_objects_discovery(wbem_connection, storage_name, cim_class, cim_property_name): 30 | """ get list of storage objects """ 31 | 32 | # create empty list for storage object names 33 | result = [] 34 | 35 | # form "SELECT" request string 36 | request = f'SELECT {cim_property_name} FROM {cim_class}' 37 | 38 | # try to request storage via WBEM 39 | try: 40 | storage_response = wbem_connection.ExecQuery('DMTF:CQL', request) 41 | except _exceptions.AuthError as error: 42 | print((f'{project}_error: exception in {software}: can\'t exec query on {storage_name}: {error} ' 43 | f'Check your username/password and permissions of user.'), 44 | file=sys.stderr) 45 | slack_post(software, (f'can\'t exec query on {storage_name}: {error} .' 46 | f'Check your username/password and permissions of user.')) 47 | exit(1) 48 | except _exceptions.ConnectionError as error: 49 | print((f'{project}_error: exception in {software}: can\'t exec query on {storage_name}: {error}. ' 50 | f'Check the connection to storage or try later.'), 51 | file=sys.stderr) 52 | slack_post(software, (f'can\'t exec query on {storage_name}: {error}. ' 53 | f'Check the connection to storage or try later.')) 54 | exit(1) 55 | except _exceptions.HTTPError as error: 56 | print((f'{project}_error: exception in {software}: WBEM server return code 400 (Bad Request) on {storage_name}: {error}. ' 57 | f'Check the your request.'), 58 | file=sys.stderr) 59 | slack_post(software, (f'WBEM server return code 400 (Bad Request) on {storage_name}: {error}. ' 60 | f'Check the your request {request}.')) 61 | exit(1) 62 | except: 63 | print(f'{project}_error: exception in {software}: {sys.exc_info()}', 64 | file=sys.stderr) 65 | slack_post(software, sys.exc_info()) 66 | exit(1) 67 | 68 | # parse reply and form a list of storage objects 69 | for cim_object in storage_response: 70 | object_name = cim_object.properties[cim_property_name].value 71 | result.append(object_name) 72 | 73 | return result 74 | 75 | 76 | def main(): 77 | 78 | # get config file name 79 | conf_file = ( 80 | f'/etc/zabbix/externalscripts/{project}/conf.d/{project}.conf') 81 | 82 | # read network device parameters from config and save it to dict 83 | nd_parameters = configread(conf_file, 'NetworkDevice', 'device_file', 84 | 'login', 'password', 'name_space', 'printing') 85 | 86 | # read storage device parameters from config and save it to another dict 87 | sd_parameters = configread(conf_file, 'StorageDevice', 88 | 'monitored_properties_file') 89 | 90 | # get printing boolean variable from config for debugging enable/disable 91 | printing = eval(nd_parameters['printing']) 92 | 93 | # get variables 94 | login = nd_parameters['login'] 95 | password = nd_parameters['password'] 96 | 97 | # form dictionary of matching storage concepts and cim properties 98 | # more details in https://www.ibm.com/support/knowledgecenter/STHGUJ_8.3.1/com.ibm.storwize.v5000.831.doc/svc_conceptsmaptocimconcepts_3skacv.html 99 | with open(sd_parameters['monitored_properties_file'], "r") as monitored_properties_file: 100 | monitored_properties = load(monitored_properties_file) 101 | 102 | # open config file with list of monitored storages 103 | device_list_file = open(nd_parameters['device_file']) 104 | 105 | # unpack storage list to variables 106 | for device_line in device_list_file: 107 | device_type, device_name, device_ip = device_line.split(':') 108 | device_ip = device_ip.rstrip('\n') 109 | 110 | # connect to each storage via WBEM, get conn object 111 | if device_type == 'storwize': 112 | device = WBEMDevice(device_name, device_ip, login, password) 113 | # get namespace from config, root/ibm by default 114 | namespace = nd_parameters.get('name_space', 'root/ibm') 115 | conn = device.Connect(namespace) 116 | 117 | # initialize packet for sending to zabbix 118 | packet = [] 119 | 120 | # iterate through dictionary of monitored storage concepts 121 | for storage_concept in monitored_properties: 122 | # get list of storage objects 123 | so_names = storage_objects_discovery(conn, device_name, 124 | monitored_properties[storage_concept]['cim_class'], 125 | monitored_properties[storage_concept]['cim_property_name']) 126 | 127 | so_names_dict = {} 128 | so_names_list = [] 129 | 130 | # create list of disk types and names in JSON 131 | for so_name in so_names: 132 | so_name_json = {"{#SO_TYPE}": storage_concept, 133 | "{#SO_NAME}": so_name} 134 | so_names_list.append(so_name_json) 135 | 136 | # form data for send to zabbix 137 | so_names_dict['data'] = so_names_list 138 | 139 | trapper_key = monitored_properties[storage_concept]['zabbix_discovery_key'] 140 | trapper_value = str(so_names_dict).replace("\'", "\"") 141 | 142 | # form packet for sending to zabbix 143 | packet.append(ZabbixMetric(device_name, trapper_key, 144 | trapper_value)) 145 | 146 | # print data for visual check 147 | if printing: 148 | print(device_name) 149 | print(trapper_key) 150 | print(trapper_value) 151 | 152 | # trying send data to zabbix 153 | zabbix_send(packet, printing, software) 154 | 155 | device_list_file.close() 156 | 157 | 158 | if __name__ == "__main__": 159 | main() 160 | else: 161 | print("Please execute this program as main\n") 162 | -------------------------------------------------------------------------------- /storage_objects_status.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # 4 | # IBM Storwize storage objects status monitoring for Zabbix 5 | # 6 | # 2020 Denis Pavlov 7 | # 8 | # Get status of storage objects parameters from Storwize including physical and logical disks, via CIM/WBEM and sends it to Zabbix via Zabbix Sender API 9 | # 10 | # Use with template Template Storage Pystormon 11 | # 12 | 13 | import os 14 | import sys 15 | 16 | from configread import configread 17 | from functions import slack_post, zabbix_send 18 | from json import load 19 | from pynetdevices import WBEMDevice 20 | from pywbem import _exceptions 21 | from pyzabbix import ZabbixMetric 22 | 23 | 24 | # set project as current directory name, software as name of current script 25 | project = os.path.abspath(__file__).split('/')[-2] 26 | software = sys.argv[0] 27 | 28 | 29 | def storage_objects_get_params(wbem_connection, storage_name, cim_class, cim_property_name, cim_properties_mon): 30 | """ get status of disk parameters """ 31 | 32 | # create empty dictionary for save storage objects parameters values 33 | storage_objects = {} 34 | 35 | # form "SELECT" request string 36 | str_cim_properties_mon = ','.join(cim_properties_mon) 37 | request = f'SELECT {str_cim_properties_mon} FROM {cim_class}' 38 | 39 | # try to request storage via WBEM 40 | try: 41 | storage_response = wbem_connection.ExecQuery('DMTF:CQL', request) 42 | except _exceptions.AuthError as error: 43 | print((f'{project}_error: exception in {software}: can\'t exec query on {storage_name}: {error} ' 44 | f'Check your username/password and permissions of user.'), 45 | file=sys.stderr) 46 | slack_post(software, (f'can\'t exec query on {storage_name}: {error} .' 47 | f'Check your username/password and permissions of user.')) 48 | exit(1) 49 | except _exceptions.ConnectionError as error: 50 | print((f'{project}_error: exception in {software}: can\'t exec query on {storage_name}: {error}. ' 51 | f'Check the connection to storage or try later.'), 52 | file=sys.stderr) 53 | slack_post(software, (f'can\'t exec query on {storage_name}: {error}. ' 54 | f'Check the connection to storage or try later.')) 55 | exit(1) 56 | except _exceptions.HTTPError as error: 57 | print((f'{project}_error: exception in {software}: WBEM server return code 400 (Bad Request) on {storage_name}: {error}. ' 58 | f'Check the your request.'), 59 | file=sys.stderr) 60 | slack_post(software, (f'WBEM server return code 400 (Bad Request) on {storage_name}: {error}. ' 61 | f'Check the your request {request}.')) 62 | exit(1) 63 | except: 64 | print(f'{project}_error: exception in {software}: {sys.exc_info()}', 65 | file=sys.stderr) 66 | slack_post(software, sys.exc_info()) 67 | exit(1) 68 | 69 | # form dictionary of dictionaries of disk parameters 70 | for cim_object in storage_response: 71 | storage_object = {} 72 | so_name = cim_object.properties[cim_property_name].value 73 | for so_property in cim_object.properties: 74 | if cim_object.properties[so_property].value: 75 | if type(cim_object.properties[so_property].value) == list: 76 | for value in cim_object.properties[so_property].value: 77 | index = cim_object.properties[so_property].value.index( 78 | value) 79 | storage_object[so_property + '.' + str(index)] = value 80 | else: 81 | storage_object[so_property] = cim_object.properties[so_property].value 82 | storage_objects[so_name] = storage_object 83 | 84 | return storage_objects 85 | 86 | 87 | def main(): 88 | 89 | # get config file name 90 | conf_file = ( 91 | f'/etc/zabbix/externalscripts/{project}/conf.d/{project}.conf') 92 | 93 | # read network device parameters from config and save it to dict 94 | nd_parameters = configread(conf_file, 'NetworkDevice', 'device_file', 95 | 'login', 'password', 'name_space', 'printing') 96 | 97 | # read storage device parameters from config and save it to another dict 98 | sd_parameters = configread(conf_file, 'StorageDevice', 99 | 'monitored_properties_file') 100 | 101 | # get printing boolean variable from config for debugging enable/disable 102 | printing = eval(nd_parameters['printing']) 103 | 104 | # get variables 105 | login = nd_parameters['login'] 106 | password = nd_parameters['password'] 107 | 108 | # form dictionary of matching storage concepts and cim properties 109 | # more details in https://www.ibm.com/support/knowledgecenter/STHGUJ_8.3.1/com.ibm.storwize.v5000.831.doc/svc_conceptsmaptocimconcepts_3skacv.html 110 | with open(sd_parameters['monitored_properties_file'], "r") as monitored_properties_file: 111 | monitored_properties = load(monitored_properties_file) 112 | 113 | # open config file with list of monitored storages 114 | device_list_file = open(nd_parameters['device_file']) 115 | 116 | # unpack storage list to variables 117 | for device_line in device_list_file: 118 | device_type, device_name, device_ip = device_line.split(':') 119 | device_ip = device_ip.rstrip('\n') 120 | 121 | # connect to each storage via WBEM, get conn object 122 | if device_type == 'storwize': 123 | device = WBEMDevice(device_name, device_ip, login, password) 124 | # get namespace from config, root/ibm by default 125 | namespace = nd_parameters.get('name_space', 'root/ibm') 126 | conn = device.Connect(namespace) 127 | 128 | # initialize packet for sending to zabbix 129 | packet = [] 130 | 131 | # iterate through dictionary of monitored storage concepts 132 | for storage_concept in monitored_properties: 133 | # get values of object parameters for all object of each type 134 | storage_objects = storage_objects_get_params(conn, device_name, 135 | monitored_properties[storage_concept]['cim_class'], 136 | monitored_properties[storage_concept]['cim_property_name'], 137 | monitored_properties[storage_concept]['cim_properties_mon']) 138 | 139 | # get status for each parameter for each storage object 140 | for storage_object in storage_objects: 141 | for so_parameter in storage_objects[storage_object]: 142 | trapper_key = f'{so_parameter}[{storage_concept}.{storage_object}]' 143 | trapper_value = storage_objects[storage_object][so_parameter] 144 | 145 | # form list of data for sending to zabbix 146 | packet.append(ZabbixMetric( 147 | device_name, 148 | trapper_key, 149 | trapper_value)) 150 | 151 | # print data for visual check 152 | if printing: 153 | # print("zabbix_sender -z wcmon.forum.lo -p 10051 -s", device_name, "-k",trapper_key, "-o", trapper_value) 154 | print(device_name) 155 | print(trapper_key) 156 | print(trapper_value) 157 | 158 | # trying send data to zabbix 159 | zabbix_send(packet, printing, software) 160 | 161 | device_list_file.close() 162 | 163 | 164 | if __name__ == "__main__": 165 | main() 166 | else: 167 | print("Please execute this program as main\n") 168 | -------------------------------------------------------------------------------- /storage_perfomance.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # 4 | # IBM Storwize performance monitoring script for Zabbix 5 | # 6 | # 2020 Denis Pavlov 7 | # 8 | # Get perfomance statistic from Storwize via CIM/WBEM and sends it to Zabbix via Zabbix Sender API 9 | # 10 | # Use with template Template Storage Pystormon 11 | # 12 | 13 | import os 14 | import sys 15 | 16 | from configread import configread 17 | from functions import slack_post, zabbix_send 18 | from json import load 19 | from pynetdevices import WBEMDevice 20 | from pywbem import _exceptions 21 | from pyzabbix import ZabbixMetric 22 | 23 | # set project as current directory name, software as name of current script 24 | project = os.path.abspath(__file__).split('/')[-2] 25 | software = sys.argv[0] 26 | 27 | 28 | def storage_objects_get_perf(wbem_connection, storage_name, cim_class, cim_property_name, cim_perf_class, cim_perf_properties): 29 | """ get performance statistics for storage objects """ 30 | 31 | objects_names_list = [] 32 | objects_perfs_list = [] 33 | objects_perfs_dict = {} 34 | 35 | # form "SELECT" request strings 36 | objects_request = f'SELECT {cim_property_name} FROM {cim_class}' 37 | str_cim_perf_properties = ','.join(cim_perf_properties) 38 | perf_request = f'SELECT {str_cim_perf_properties} FROM {cim_perf_class}' 39 | 40 | # try to request storage via WBEM 41 | try: 42 | objects_names_cim = wbem_connection.ExecQuery( 43 | 'DMTF:CQL', objects_request) 44 | except _exceptions.AuthError as error: 45 | print((f'{project}_error: exception in {software}: can\'t exec query on {storage_name}: {error} ' 46 | f'Check your username/password and permissions of user.'), 47 | file=sys.stderr) 48 | slack_post(software, (f'can\'t exec query on {storage_name}: {error} .' 49 | f'Check your username/password and permissions of user.')) 50 | exit(1) 51 | except _exceptions.ConnectionError as error: 52 | print((f'{project}_error: exception in {software}: can\'t exec query on {storage_name}: {error}. ' 53 | f'Check the connection to storage or try later.'), 54 | file=sys.stderr) 55 | slack_post(software, (f'can\'t exec query on {storage_name}: {error}. ' 56 | f'Check the connection to storage or try later.')) 57 | exit(1) 58 | except _exceptions.HTTPError as error: 59 | print((f'{project}_error: exception in {software}: WBEM server return code 400 (Bad Request) on {storage_name}: {error}. ' 60 | f'Check the your request.'), 61 | file=sys.stderr) 62 | slack_post(software, (f'WBEM server return code 400 (Bad Request) on {storage_name}: {error}. ' 63 | f'Check the your request {objects_request}.')) 64 | exit(1) 65 | except: 66 | print(f'{project}_error: exception in {software}: {sys.exc_info()}', 67 | file=sys.stderr) 68 | slack_post(software, sys.exc_info()) 69 | exit(1) 70 | 71 | try: 72 | objects_perfs_cim = wbem_connection.ExecQuery('DMTF:CQL', perf_request) 73 | except _exceptions.AuthError as error: 74 | print((f'{project}_error: exception in {software}: can\'t exec query on {storage_name}: {error} ' 75 | f'Check your username/password and permissions of user.'), 76 | file=sys.stderr) 77 | slack_post(software, (f'can\'t exec query on {storage_name}: {error} .' 78 | f'Check your username/password and permissions of user.')) 79 | exit(1) 80 | except _exceptions.ConnectionError as error: 81 | print((f'{project}_error: exception in {software}: can\'t exec query on {storage_name}: {error}. ' 82 | f'Check the connection to storage or try later.'), 83 | file=sys.stderr) 84 | slack_post(software, (f'can\'t exec query on {storage_name}: {error}. ' 85 | f'Check the connection to storage or try later.')) 86 | exit(1) 87 | except _exceptions.HTTPError as error: 88 | print((f'{project}_error: exception in {software}: WBEM server return code 400 (Bad Request) on {storage_name}: {error}. ' 89 | f'Check the your request.'), 90 | file=sys.stderr) 91 | slack_post(software, (f'WBEM server return code 400 (Bad Request) on {storage_name}: {error}. ' 92 | f'Check the your request {perf_request}.')) 93 | exit(1) 94 | except: 95 | print(f'{project}_error: exception in {software}: {sys.exc_info()}', 96 | file=sys.stderr) 97 | slack_post(software, sys.exc_info()) 98 | exit(1) 99 | 100 | # form list of storage objects 101 | for object_name_cim in objects_names_cim: 102 | objects_names_list.append( 103 | object_name_cim.properties[cim_property_name].value) 104 | 105 | # form list of lists of objects perf counters 106 | for object_perf_cim in objects_perfs_cim: 107 | object_perf_list = [] 108 | for cim_perf_property in cim_perf_properties: 109 | object_perf_list.append( 110 | int(object_perf_cim.properties[cim_perf_property].value)) 111 | objects_perfs_list.append(object_perf_list) 112 | 113 | # form dict of objects perf counters 114 | for object_name, object_perf in zip(objects_names_list, objects_perfs_list): 115 | objects_perfs_dict[object_name] = object_perf 116 | 117 | return objects_perfs_dict 118 | 119 | 120 | def main(): 121 | 122 | # get config file name 123 | conf_file = ( 124 | f'/etc/zabbix/externalscripts/{project}/conf.d/{project}.conf') 125 | 126 | # read network device parameters from config and save it to dict 127 | nd_parameters = configread(conf_file, 'NetworkDevice', 'device_file', 128 | 'login', 'password', 'name_space', 'printing') 129 | 130 | # read storage device parameters from config and save it to another dict 131 | sd_parameters = configread(conf_file, 'StorageDevice', 132 | 'monitored_properties_file') 133 | 134 | # get printing boolean variable from config for debugging enable/disable 135 | printing = eval(nd_parameters['printing']) 136 | 137 | # get variables 138 | login = nd_parameters['login'] 139 | password = nd_parameters['password'] 140 | 141 | # form dictionary of matching storage concepts and cim properties 142 | # more details in https://www.ibm.com/support/knowledgecenter/STHGUJ_8.3.1/com.ibm.storwize.v5000.831.doc/svc_conceptsmaptocimconcepts_3skacv.html 143 | with open(sd_parameters['monitored_properties_file'], "r") as monitored_properties_file: 144 | monitored_properties = load(monitored_properties_file) 145 | 146 | # open config file with list of monitored storages 147 | device_list_file = open(nd_parameters['device_file']) 148 | 149 | # unpack storage list to variables 150 | for device_line in device_list_file: 151 | device_type, device_name, device_ip = device_line.split(':') 152 | device_ip = device_ip.rstrip('\n') 153 | 154 | # connect to each storage via WBEM, get conn object 155 | if device_type == 'storwize': 156 | device = WBEMDevice(device_name, device_ip, login, password) 157 | # get namespace from config, root/ibm by default 158 | namespace = nd_parameters.get('name_space', 'root/ibm') 159 | conn = device.Connect(namespace) 160 | 161 | # initialize packet for sending to zabbix 162 | packet = [] 163 | 164 | # iterate through dictionary of monitored storage concepts 165 | for storage_concept in monitored_properties: 166 | # get values of object perfomance statistic for all object of each type where perfomance class is given 167 | if 'cim_perfomance_class' in monitored_properties[storage_concept]: 168 | storage_objects_perf = storage_objects_get_perf(conn, device_name, 169 | monitored_properties[storage_concept]['cim_class'], 170 | monitored_properties[storage_concept]['cim_property_name'], 171 | monitored_properties[storage_concept]['cim_perfomance_class'], 172 | monitored_properties[storage_concept]['cim_properties_perfomance']) 173 | 174 | # get statistic for each storage object, form data for sending to zabbix 175 | for so_name in storage_objects_perf: 176 | for perf_counter_name, perf_counter_value in zip(monitored_properties[storage_concept]['cim_properties_perfomance'], 177 | storage_objects_perf[so_name]): 178 | trapper_key = f'{perf_counter_name}[{storage_concept}.{so_name}]' 179 | trapper_value = perf_counter_value 180 | 181 | # form list of data for sending to zabbix 182 | packet.append(ZabbixMetric( 183 | device_name, trapper_key, trapper_value)) 184 | 185 | # print data for visual check 186 | if printing: 187 | print(device_name) 188 | print(trapper_key) 189 | print(trapper_value) 190 | 191 | # trying send data to zabbix 192 | zabbix_send(packet, printing, software) 193 | 194 | device_list_file.close() 195 | 196 | 197 | if __name__ == "__main__": 198 | main() 199 | else: 200 | print("Please execute this program as main\n") 201 | --------------------------------------------------------------------------------