├── LICENSE ├── README.md ├── emc_vnx_discovery.py ├── emc_vnx_stats.py ├── emc_vnx_template.xml └── tools └── ecom_vnx_manage.py /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 EMC Corporation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | EMC-Zabbix-Integration 2 | ======================= 3 | 4 | This template and supporting scripts have been developed to integrate EMC VNX storage into the Open-Source Monitoring Tool Zabbix (http://www.zabbix.com) 5 | 6 | ## Description 7 | This project aims to provide a simple to implement integration for the collection of performance and health data from EMC VNX and CLARiiON based systems into the Zabbix Open-Source monitoring framework. 8 | 9 | This integration is expected to flex many of Zabbix's features including Low Level Discovery, application based separation, and visualization. 10 | 11 | 12 | ## Installation 13 | 14 | *Prerequisites* 15 | 16 | 1. Confirm that block statistics collection is enabled on your array: https://community.emc.com/docs/DOC-24564 17 | 1. EMC ECOM Server is installed with Storage arrays registered (make sure it's been running for a few hours to build out the object model and collect some stats) 18 | * Be sure you read the Installation documentation for the ECOM to confirm you have all of the necessary 32 and 64 bit libraries installed for your OS. 19 | 2. Python module: pywbem, argparse(If Python < 2.7) 20 | 21 | 22 | A script in the tools subdir can be used to easily add the array to the ECOM server if you are unfamiliar with the ECOM tools 23 | 24 | *Installation* 25 | 26 | 1. Place the two python scripts included here in the external scripts directory for your zabbix server, be sure they are owned by, and executable by the zabbix user. 27 | 2. Edit the emc_vnx_stats.py script, confirming that the path to the zabbix_sender command is correct along with the path to the agentd configuration file. 28 | 2. Confirm that the script Timeout value is set to 30 seconds in the zabbix_server.conf file. 29 | 4. Create a new host in Zabbix, with a hostname of the ARRAY SERIAL, the visible hostname may be whatever you like. 30 | 5. Create a host macro {$ECOMIP} with a value of the IP address of the ECOM server. 31 | 6. Create host macros: {$ECOMUSER}, {$ECOMPASS} with the ECOM username and password. 32 | 5. Update the Host inventory, setting it to manual to include the array serial number 33 | 6. Import the template and link to the newly added host 34 | 7. Patiently wait for the discovery and first sync to run 35 | 36 | *Troubleshooting* 37 | 38 | * Discovery Issues 39 | * Check the /tmp/emc_vnx_discovery.log file for any exceptions. 40 | * Check that you can run the scripts from the command line AS THE ZABBIX USER successfully, if you can run them from the command line but not from within Zabbix, you may want to confirm the host macros and host name have been properly configured. 41 | 42 | * Stats Collection Issues 43 | * Each group of statisics have a "Statistics Collection" key that runs the external emc_vnx_stats.py collection script, check the output for exceptions or problems 44 | * If you see the error "ERROR_FAMILY_OPERATION_NOT_AVAILABLE Statistics Service is not enabled for array" Be sure that you have Block Statistics data collection enabled (See https://community.emc.com/docs/DOC-24564) 45 | 46 | 47 | ## Currently Supported Objects 48 | * Harware Monitoring 49 | * Storage Processors 50 | * Enclosures 51 | * Fans 52 | * Batteries (SPS) 53 | * Disks 54 | * LCC Cards 55 | * Storage Processors 56 | * Discovery 57 | * Up/Down validation 58 | * Performance Metrics 59 | * SP % Utilization 60 | * IO % Read 61 | * IO % Write 62 | * Cache Flush (Idle, High, Low Watermark) 63 | * Cache % Dirty 64 | * Queue Length & Arrivals 65 | * Physical Disks 66 | * Discovery 67 | * Performance Metrics 68 | * Physical Disk Utilization 69 | * IO % Read 70 | * Total Read/Write IOs 71 | * KB Read/Written/Transferred 72 | * Queue Length & Arrivals 73 | * Volumes 74 | * Discovery 75 | * Performance Metrics 76 | * Volume Utilization 77 | * Total Read/Write IOs 78 | * KB Read/Written/Transferred 79 | * Queue Length & Arrivals 80 | * Prefetched KB 81 | * FAST Cache Hits & Misses 82 | * Disk Crossings 83 | * Forced Flushes 84 | * Response Time 85 | * Pools & RAID Groups 86 | * Discovery 87 | * Capacity/Subscribed 88 | * Performance Metrics 89 | 90 | ## Future 91 | 92 | * Volumes 93 | * Capacity/Subscribed 94 | * Tresspassed or not 95 | * Pools 96 | * RG Type 97 | * Volume Offset 98 | 99 | Licensing 100 | --------- 101 | This software is provided under the MIT License 102 | 103 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 104 | 105 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 106 | 107 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 108 | Status API Training Shop Blog About Pricing 109 | 110 | 111 | Support 112 | ------- 113 | Please file bugs and issues at the Github issues page. 114 | -------------------------------------------------------------------------------- /emc_vnx_discovery.py: -------------------------------------------------------------------------------- 1 | #!/bin/env python 2 | 3 | import sys 4 | import json 5 | import pywbem 6 | import argparse 7 | import logging 8 | import logging.handlers 9 | 10 | log_level = logging.INFO 11 | 12 | 13 | def ecom_connect(ecom_ip, ecom_user, ecom_pass, default_namespace="/root/emc"): 14 | """ returns a connection to the ecom server """ 15 | ecom_url = "https://%s:5989" % ecom_ip 16 | 17 | logger = logging.getLogger('discovery') 18 | logger.info("Building WBEM Connection to %s" % ecom_url) 19 | 20 | return pywbem.WBEMConnection(ecom_url, (ecom_user, ecom_pass), 21 | default_namespace="/root/emc") 22 | 23 | 24 | def get_array_instancename(array_serial, ecom_conn): 25 | """ Returns the InstanceName of the array serial provided """ 26 | 27 | logger = logging.getLogger('discovery') 28 | logger.debug("Collecting Array InstanceName for %s" % array_serial) 29 | 30 | registered_arrays = ecom_conn.EnumerateInstanceNames("Clar_StorageSystem") 31 | for array in registered_arrays: 32 | if array_serial in array['Name']: 33 | logger.debug("Array InstanceName located for %s" % array_serial) 34 | return array 35 | 36 | # No array found 37 | logging.warning("Array InstanceName for %s not found" % array_serial) 38 | return None 39 | 40 | 41 | def discover_array_volumes(ecom_conn, array_serial): 42 | """Discover the Volumes in the VNX array 43 | 44 | Arguments- 45 | ecom_conn: (pyWBEM) pyWBEM connection 46 | 47 | Returns- 48 | Zabbix compatible List of Hashes to be JSONified 49 | or appended to additional data 50 | 51 | """ 52 | 53 | array = get_array_instancename(array_serial, ecom_conn) 54 | 55 | # Locate all volumes associated with the array 56 | logger = logging.getLogger('discovery') 57 | logger.debug("Started volume info collection from ECOM") 58 | volumes = ecom_conn.Associators(array, ResultClass="CIM_StorageVolume") 59 | logger.debug("Completed volume info collection ECOM") 60 | 61 | logger.debug("Generating discovery objects") 62 | discovered_volumes = [] 63 | for volume in volumes: 64 | 65 | diskitem = dict() 66 | diskitem["{#VOLDEVICEID}"] = volume["DeviceID"] 67 | diskitem["{#VOLALIAS}"] = volume["ElementName"] 68 | diskitem["{#VOLPERFDEVICEID}"] = volume["EMCBSPInstanceID"] 69 | diskitem["{#ARRAYSERIAL}"] = array_serial 70 | 71 | discovered_volumes.append(diskitem) 72 | logger.debug(str(diskitem)) 73 | 74 | return discovered_volumes 75 | 76 | 77 | def discover_array_disks(ecom_conn, array_serial): 78 | """Discover the disks in the VNX array 79 | 80 | Arguments- 81 | ecom_conn: (pyWBEM) pyWBEM connection 82 | 83 | Returns- 84 | Zabbix compatible List of Hashes to be JSONified or appended 85 | to additional data 86 | 87 | """ 88 | 89 | array = get_array_instancename(array_serial, ecom_conn) 90 | 91 | logger = logging.getLogger('discovery') 92 | logger.debug("Started disk info collection from ECOM") 93 | physical_disks = ecom_conn.Associators(array, ResultClass="CIM_DiskDrive") 94 | logger.debug("Completed disk info collection from ECOM") 95 | 96 | logger.debug("Generating discovery objects") 97 | discovered_disks = [] 98 | for disk in physical_disks: 99 | 100 | dev_id = "CLAR+%s+%s" % (array_serial, disk["Name"]) 101 | perf_dev_id = "CLAR+%s+Disk+%s" % (array_serial, disk["Name"]) 102 | bus_enc = disk["Name"].split('_') 103 | dev_name = "Bus %s Enclosure %s Slot %s" % ( 104 | bus_enc[0], bus_enc[1], bus_enc[2]) 105 | 106 | diskitem = dict() 107 | diskitem["{#DISKDEVICEID}"] = dev_id 108 | diskitem["{#DISKPERFDEVICEID}"] = perf_dev_id 109 | diskitem["{#ARRAYSERIAL}"] = array_serial 110 | diskitem["{#DISKNAME}"] = dev_name 111 | 112 | discovered_disks.append(diskitem) 113 | logger.debug(str(diskitem)) 114 | 115 | return discovered_disks 116 | 117 | 118 | def discover_array_SPs(ecom_conn, array_serial): 119 | """Discover the SPs in the VNX array 120 | 121 | Arguments- 122 | ecom_conn: (pyWBEM) pyWBEM connection 123 | 124 | Returns- 125 | Zabbix compatible List of Hashes to be JSONified 126 | or appended to additional data 127 | 128 | """ 129 | array = get_array_instancename(array_serial, ecom_conn) 130 | 131 | logger = logging.getLogger('discovery') 132 | logger.debug("Gathering list of SPs from ECOM") 133 | sps = ecom_conn.AssociatorNames(array, 134 | ResultClass="EMC_StorageProcessorSystem") 135 | logger.debug("Completed SP list") 136 | 137 | logger.debug("Locating Access Points for SPs from ECOM") 138 | 139 | storage_procs = [] 140 | for sp in sps: 141 | i = ecom_conn.Associators(sp, 142 | ResultClass="CIM_RemoteServiceAccessPoint") 143 | storage_procs.append(i[0]) 144 | 145 | logger.debug("Completed Locating Access Points for SPs from ECOM") 146 | 147 | discovered_procs = [] 148 | for proc in storage_procs: 149 | dev_id = proc['SystemName'] 150 | sp_name = dev_id[-4:].replace("_", "") 151 | perf_dev_id = "CLAR+%s+FEAdapt+SP-%s" % (array_serial, sp_name[-1]) 152 | sp_ip = proc['AccessInfo'] 153 | 154 | spitem = dict() 155 | spitem["{#SPDEVICEID}"] = dev_id 156 | spitem["{#SPPERFDEVICEID}"] = perf_dev_id 157 | spitem["{#SPNAME}"] = sp_name 158 | spitem["{#SPIP}"] = sp_ip 159 | 160 | discovered_procs.append(spitem) 161 | logger.debug(str(spitem)) 162 | 163 | return discovered_procs 164 | 165 | 166 | def discover_array_pools(ecom_conn, array_serial): 167 | """Discover the Pools in the VNX array 168 | 169 | Arguments- 170 | ecom_conn: (pyWBEM) pyWBEM connection 171 | 172 | Returns- 173 | Zabbix compatible List of Hashes to be JSONified 174 | or appended to additional data 175 | 176 | """ 177 | 178 | # Lets locate our array 179 | array = get_array_instancename(array_serial, ecom_conn) 180 | 181 | pool_classes = ["EMC_DeviceStoragePool", "EMC_UnifiedStoragePool", 182 | "EMC_VirtualProvisioningPool"] 183 | 184 | logger = logging.getLogger('discovery') 185 | 186 | discovered_pools = [] 187 | for c in pool_classes: 188 | logger.debug("Starting discovery of pools of class: %s" % c) 189 | for pool in ecom_conn.Associators(array, ResultClass=c): 190 | pool_name = None 191 | pool_item = dict() 192 | pool_type = pool["EMCPoolID"][0] 193 | if pool_type == "C": # RAID Group 194 | pool_name = "RAID Group %s" % pool["PoolID"] 195 | else: 196 | pool_name = pool["PoolID"] 197 | 198 | pool_item["{#POOLNAME}"] = pool_name 199 | pool_item["{#POOLDEVICEID}"] = pool["InstanceID"].replace(" ", "_") 200 | pool_item["{#ARRAYSERIAL}"] = array_serial 201 | 202 | discovered_pools.append(pool_item) 203 | logger.debug(str(pool_item)) 204 | 205 | return discovered_pools 206 | 207 | 208 | def discover_array_devices(ecom_conn, array_serial): 209 | """Discover the enclosures, batteries, etc. in the array 210 | 211 | Arguments- 212 | ecom_conn: (pyWBEM) pyWBEM connection 213 | 214 | Returns- 215 | Zabbix compatible List of Hashes to be JSONified 216 | or appended to additional data 217 | 218 | """ 219 | 220 | array_hardware = [] 221 | # Lets locate our array 222 | array = get_array_instancename(array_serial, ecom_conn) 223 | 224 | # Enclosures are associated with the ArrayChassis of the Storage System 225 | logger = logging.getLogger('discovery') 226 | logger.debug("Collecting Array Chassis info from ECOM") 227 | array_chassis = ecom_conn.AssociatorNames( 228 | array, ResultClass="EMC_ArrayChassis")[0] 229 | 230 | logger.debug("Completed Array Chassis") 231 | 232 | enclosures = ecom_conn.Associators(array_chassis, 233 | ResultClass="EMC_EnclosureChassis") 234 | 235 | logger.debug("Completed EnclosureChassis") 236 | 237 | for i in enclosures: 238 | if "SPE" in i["ElementName"]: 239 | enclosure_name = "Storage Processor Enclosure" 240 | else: 241 | enc_addr = tuple(i["ElementName"].split('_')) 242 | enclosure_name = "Bus %s Enclosure %s" % enc_addr 243 | 244 | hardware = {"{#ARRAYSERIAL}": array_serial, 245 | "{#DEVICEID}": i["Tag"], 246 | "{#DEVICENAME}": enclosure_name, 247 | "{#DEVICETYPE}": "Enclosure" 248 | } 249 | 250 | array_hardware.append(hardware) 251 | logger.debug(str(hardware)) 252 | 253 | # Power Supplies 254 | logger.debug("Collecting power supplies from ECOM") 255 | pow_supplies = ecom_conn.Associators(array, ResultClass="EMC_PowerDevice") 256 | logger.debug("Completed collecting power supplies from ECOM") 257 | 258 | for i in pow_supplies: 259 | location = i["DeviceID"].split('+') 260 | device = location[2] 261 | supply_side = location[-1] 262 | 263 | if 'SPE' in device: 264 | device = "SPE Power Supply " # Strip out the N/As 265 | else: 266 | enc_addr = tuple(device.split('_')) 267 | device = "Bus %s Enclosure %s Power Supply " % enc_addr 268 | 269 | device = device + supply_side 270 | 271 | hardware = {"{#ARRAYSERIAL}": array_serial, 272 | "{#DEVICEID}": i["DeviceID"], 273 | "{#DEVICENAME}": device, 274 | "{#DEVICETYPE}": "Supply" 275 | } 276 | 277 | array_hardware.append(hardware) 278 | logger.debug(str(hardware)) 279 | 280 | # Batteries 281 | logger.debug("Collecting batteries from ECOM") 282 | batteries = ecom_conn.Associators(array, ResultClass="EMC_BatteryDevice") 283 | logger.debug("Completed collecting batteries from ECOM") 284 | 285 | for i in batteries: 286 | location = i["DeviceID"].split('+') 287 | device = location[2] 288 | battery_side = location[-1] 289 | 290 | if "SPE" in device: 291 | device = "Storage Processor Enclosure Battery " 292 | else: 293 | device = "Bus %s Enclosure %s Battery " % tuple(device.split('_')) 294 | 295 | device = device + battery_side 296 | 297 | hardware = {"{ARRAYSERIAL}": array_serial, 298 | "{#DEVICEID}": i["DeviceID"], 299 | "{#DEVICENAME}": device, 300 | "{#DEVICETYPE}": "Battery" 301 | } 302 | 303 | array_hardware.append(hardware) 304 | logger.debug(str(hardware)) 305 | 306 | # LCC Cards 307 | logger.debug("Collecting LCC cards from ECOM") 308 | lcc_cards = ecom_conn.Associators(array, 309 | ResultClass="EMC_LinkControlDevice") 310 | logger.debug("Completed collecting LCC cards from ECOM") 311 | 312 | for i in lcc_cards: 313 | location = i["DeviceID"].split('+') 314 | device = location[2] 315 | side = location[-1] 316 | 317 | device = "Bus %s Enclosure %s LCC Card " % tuple(device.split('_')) 318 | 319 | device = device + side 320 | 321 | hardware = {"{ARRAYSERIAL}": array_serial, 322 | "{#DEVICEID}": i["DeviceID"], 323 | "{#DEVICENAME}": device, 324 | "{#DEVICETYPE}": "LCC" 325 | } 326 | 327 | array_hardware.append(hardware) 328 | logger.debug(str(hardware)) 329 | 330 | # Fans (Fun fact, NOT all arrays have monitored fans in them!) 331 | # If no FAN data is reported, physically check your array... 332 | logger.debug("Collecting Fans from ECOM") 333 | fans = ecom_conn.Associators(array, ResultClass="EMC_FanDevice") 334 | logger.debug("Completed collecting Fans from ECOM") 335 | 336 | for i in fans: 337 | location = i["DeviceID"].split('+') 338 | device = location[2] 339 | side = location[-1] 340 | 341 | device = "Bus %s Enclosure %s Fan " % tuple(device.split('_')) 342 | 343 | device = device + side 344 | 345 | hardware = {"{ARRAYSERIAL}": array_serial, 346 | "{#DEVICEID}": i["DeviceID"]+"+Fan", 347 | "{#DEVICENAME}": device, 348 | "{#DEVICETYPE}": "Fan" 349 | } 350 | 351 | array_hardware.append(hardware) 352 | logger.debug(str(hardware)) 353 | 354 | # Storage Processors 355 | logger.debug("Collecting SP hardware from ECOM") 356 | sps = ecom_conn.Associators(array, 357 | ResultClass="EMC_StorageProcessorSystem") 358 | logger.debug("Completed collecting SP hardware from ECOM") 359 | for i in sps: 360 | device = "Storage Processor %s" % (i["Name"].split('_')[-1]) 361 | hardware = {"{#ARRAYSERIAL}": array_serial, 362 | "{#DEVICEID}": i["Name"], 363 | "{#DEVICENAME}": device, 364 | "{#DEVICETYPE}": "SP" 365 | } 366 | 367 | array_hardware.append(hardware) 368 | logger.debug(str(hardware)) 369 | 370 | # Disks 371 | logger.debug("Collecting Disk hardware from ECOM") 372 | disks = ecom_conn.Associators(array, ResultClass="CIM_DiskDrive") 373 | logger.debug("Completed collecting Disk hardware from ECOM") 374 | 375 | for i in disks: 376 | dev_id = "CLARiiON+%s+%s" % (array_serial, i["Name"]) 377 | bus_enc = i["Name"].split('_') 378 | dev_name = "Disk at Bus %s Enclosure %s Slot %s" % ( 379 | bus_enc[0], bus_enc[1], bus_enc[2]) 380 | 381 | hardware = {"{#ARRAYSERIAL}": array_serial, 382 | "{#DEVICEID}": dev_id, 383 | "{#DEVICENAME}": dev_name, 384 | "{#DEVICETYPE}": "Disk" 385 | } 386 | 387 | array_hardware.append(hardware) 388 | logger.debug(str(hardware)) 389 | 390 | return array_hardware 391 | 392 | 393 | def zabbix_safe_output(data): 394 | """ Generate JSON output for zabbix from a passed in list of dicts """ 395 | logger = logging.getLogger('discovery') 396 | logger.info("Generating output") 397 | output = json.dumps({"data": data}, indent=4, separators=(',', ': ')) 398 | 399 | logger.debug(json.dumps({"data": data})) 400 | 401 | return output 402 | 403 | def log_exception_handler(type, value, tb): 404 | logger = logging.getLogger('discovery') 405 | logger.exception("Uncaught exception: {0}".format(str(value))) 406 | 407 | def setup_logging(log_file): 408 | """ Sets up our file logging with rotation """ 409 | my_logger = logging.getLogger('discovery') 410 | my_logger.setLevel(log_level) 411 | 412 | handler = logging.handlers.RotatingFileHandler( 413 | log_file, maxBytes=5120000, backupCount=5) 414 | 415 | formatter = logging.Formatter( 416 | '%(asctime)s %(levelname)s %(process)d %(message)s') 417 | handler.setFormatter(formatter) 418 | 419 | my_logger.addHandler(handler) 420 | 421 | sys.excepthook = log_exception_handler 422 | 423 | return 424 | 425 | 426 | def main(): 427 | 428 | log_file = '/tmp/emc_vnx_discovery.log' 429 | setup_logging(log_file) 430 | 431 | logger = logging.getLogger('discovery') 432 | logger.debug("Discovery script started") 433 | 434 | parser = argparse.ArgumentParser() 435 | 436 | parser.add_argument('--serial', '-s', action="store", 437 | help="Array Serial Number", required=True) 438 | parser.add_argument('--ecom_ip', '-i', action="store", 439 | help="IP Address of ECOM server", required=True) 440 | 441 | parser.add_argument('--ecom_user', action="store", 442 | help="ECOM Username", default="admin") 443 | parser.add_argument('--ecom_pass', action="store", 444 | help="ECOM Password", default="#1Password") 445 | 446 | group = parser.add_mutually_exclusive_group(required=True) 447 | group.add_argument('--disks', '-d', action="store_true", 448 | help="Discover Physical Disks") 449 | group.add_argument('--volumes', '-v', action="store_true", 450 | help="Discover Volumes/LUNs") 451 | group.add_argument('--procs', '-p', action="store_true", 452 | help="Discover Storage Processors") 453 | group.add_argument('--pools', '-o', action="store_true", 454 | help="Discover Physical Disks") 455 | group.add_argument('--array', '-a', action="store_true", 456 | help="Discover Array devices and enclosures") 457 | 458 | args = parser.parse_args() 459 | 460 | logger.debug("Arguments parsed: %s" % str(args)) 461 | 462 | ecom_conn = ecom_connect(args.ecom_ip, args.ecom_user, args.ecom_pass) 463 | 464 | result = None 465 | if args.disks: 466 | logger.info("Disk discovery started") 467 | result = discover_array_disks(ecom_conn, args.serial) 468 | elif args.volumes: 469 | logger.info("Volume discovery started") 470 | result = discover_array_volumes(ecom_conn, args.serial) 471 | elif args.procs: 472 | logger.info("Storage Processor discovery started") 473 | result = discover_array_SPs(ecom_conn, args.serial) 474 | elif args.pools: 475 | logger.info("Pool discovery started") 476 | result = discover_array_pools(ecom_conn, args.serial) 477 | elif args.array: 478 | logger.info("Array hardware discovery started") 479 | result = discover_array_devices(ecom_conn, args.serial) 480 | 481 | print zabbix_safe_output(result) 482 | 483 | logger.info("Discovery Complete") 484 | 485 | if __name__ == "__main__": 486 | main() 487 | -------------------------------------------------------------------------------- /emc_vnx_stats.py: -------------------------------------------------------------------------------- 1 | #!/bin/env python 2 | 3 | import os 4 | import csv 5 | import sys 6 | import argparse 7 | import pywbem 8 | import StringIO 9 | import subprocess 10 | import logging 11 | import logging.handlers 12 | from collections import defaultdict 13 | from datetime import datetime, timedelta 14 | 15 | log_level = logging.INFO 16 | 17 | # User Configurable Parameters 18 | # -------------------------------- 19 | sender_command = "/usr/local/bin/zabbix_sender" 20 | config_path = "/etc/zabbix_agentd.conf" 21 | sample_interval = 5 # in minutes, must be >= 5 22 | 23 | # Globals 24 | # -------------------------------- 25 | stat_manifest_info = dict() 26 | stat_manifest_info["SP"] = {"InstanceID": "FEAdapt", "ManifestID": 2} 27 | stat_manifest_info["Volumes"] = {"InstanceID": "Volume", "ManifestID": 5} 28 | stat_manifest_info["Disks"] = {"InstanceID": "Disk", "ManifestID": 1} 29 | 30 | # These align with the proper entries in Clar_Blockmanifest 31 | # 0 = Array 32 | # 1 = Disks 33 | # 2 = SPs 34 | # 3 = SP Ports 35 | # 4 = Snap 36 | # 5 = Volumes 37 | 38 | 39 | def convert_to_local(timestamp): 40 | """ Convert the CIM timestamp to a local one, 41 | correcting for an invalid TZ setting and DST """ 42 | 43 | # Convert timestamp to datetime object, stripping UTC offset 44 | time_stamp = datetime.strptime(timestamp[:-4], "%Y%m%d%H%M%S.%f") 45 | zone_offset = int(timestamp[-4:]) 46 | 47 | # Calculte the time in UTC based on the array's assumed offset 48 | utc_time = time_stamp - timedelta(minutes=zone_offset) 49 | 50 | # Recalculate time based on current timezone 51 | offset = datetime.now() - datetime.utcnow() 52 | local_time = utc_time + offset 53 | 54 | return local_time 55 | 56 | 57 | def total_seconds(timedelta): 58 | """ Hack for python 2.6, provides total seconds in a timedelta object """ 59 | return ( 60 | timedelta.microseconds + 0.0 + 61 | (timedelta.seconds + timedelta.days * 24 * 3600) * 10 ** 6) / 10 ** 6 62 | 63 | 64 | def get_array_instancename(ecom_conn, array_serial): 65 | """ Returns the InstanceName of the array serial provided """ 66 | 67 | registered_arrays = ecom_conn.EnumerateInstanceNames("Clar_StorageSystem") 68 | for array in registered_arrays: 69 | if array_serial in array['Name']: 70 | return array 71 | 72 | # No array found 73 | return None 74 | 75 | 76 | def ecom_connect(ecom_ip, ecom_user, ecom_pass, default_namespace="/root/emc"): 77 | """ returns a connection to the ecom server """ 78 | ecom_url = "https://%s:5989" % ecom_ip 79 | 80 | return pywbem.WBEMConnection(ecom_url, (ecom_user, ecom_pass), 81 | default_namespace="/root/emc") 82 | 83 | 84 | def get_sample_interval(ecom_conn, array_serial): 85 | """ Returns the current sample interval in minutes """ 86 | 87 | array = get_array_instancename(ecom_conn, array_serial) 88 | 89 | SampleInterval = ecom_conn.Associators( 90 | array, ResultClass="CIM_StatisticsCollection", 91 | PropertyList=["SampleInterval"]) 92 | 93 | interval = total_seconds(SampleInterval[0]["SampleInterval"].timedelta) 94 | 95 | return interval/60 96 | 97 | 98 | def set_sample_interval(ecom_conn, array_serial, sample_interval): 99 | 100 | array = get_array_instancename(ecom_conn, array_serial) 101 | 102 | SampleInterval = ecom_conn.Associators( 103 | array, ResultClass="CIM_StatisticsCollection", 104 | PropertyList=["SampleInterval"]) 105 | 106 | new_interval = timedelta(minutes=sample_interval) 107 | 108 | SampleInterval[0]["SampleInterval"] = pywbem.CIMDateTime(new_interval) 109 | 110 | ecom_conn.ModifyInstance(SampleInterval[0], 111 | PropertyList=["SampleInterval"]) 112 | 113 | return 114 | 115 | 116 | def get_stats(array_serial, ecom_ip, instance_id, ecom_user="admin", 117 | ecom_pass="#1Password"): 118 | """ Collect performance statistics """ 119 | 120 | ecom_conn = ecom_connect(ecom_ip, ecom_user, ecom_pass) 121 | 122 | # Check and set the sample interval 123 | interval = get_sample_interval(ecom_conn, array_serial) 124 | if interval != sample_interval: 125 | set_sample_interval(ecom_conn, array_serial, sample_interval) 126 | 127 | # Determine the sequence our Stats are coming in from the Manifest 128 | array = get_array_instancename(ecom_conn, array_serial) 129 | man_coll = ecom_conn.AssociatorNames( 130 | array, ResultClass="CIM_BlockStatisticsManifestCollection")[0] 131 | 132 | manifests = ecom_conn.Associators( 133 | man_coll, ResultClass="CIM_BlockStatisticsManifest") 134 | 135 | for i in manifests: 136 | if instance_id in i["InstanceID"]: 137 | header_row = i["CSVSequence"] 138 | 139 | # Grab our stats 140 | stats_service = ecom_conn.AssociatorNames( 141 | array, ResultClass="CIM_BlockStatisticsService")[0] 142 | 143 | stat_output = ecom_conn.InvokeMethod("GetStatisticsCollection", 144 | stats_service, 145 | StatisticsFormat=pywbem.Uint16(2)) 146 | 147 | return (header_row, stat_output[1]["Statistics"]) 148 | 149 | 150 | def process_stats(header_row, stat_output, array_serial, manifest_info, 151 | ignore_fields=[]): 152 | """ Pushes statistics out to Zabbix """ 153 | 154 | sp_data = stat_output[stat_manifest_info[manifest_info]["ManifestID"]] 155 | f = StringIO.StringIO(sp_data) 156 | reader = csv.reader(f, delimiter=';') 157 | 158 | timestamp_index = header_row.index("StatisticTime") 159 | perf_dev_id_index = header_row.index("InstanceID") 160 | 161 | ignore_fields = ignore_fields + ["ElementType", 162 | "StatisticTime", 163 | "InstanceID"] 164 | 165 | skip_fields = [] 166 | 167 | for i in ignore_fields: 168 | skip_fields.append(header_row.index(i)) 169 | 170 | zabbix_data = [] 171 | 172 | timestamp = None 173 | for row in reader: 174 | 175 | if not timestamp: 176 | timestamp = convert_to_local(row[timestamp_index]).strftime("%s") 177 | 178 | perf_dev_id = row[perf_dev_id_index] 179 | 180 | for i in range(0, len(header_row)): 181 | if i in skip_fields: 182 | continue 183 | elif row[i] == "18446744073709551615": # If the data is N/A 184 | continue 185 | zabbix_key = "emc.vnx.perf.%s[%s]" % (header_row[i], perf_dev_id) 186 | zabbix_data.append("%s %s %s %s" % (array_serial, zabbix_key, 187 | timestamp, row[i])) 188 | 189 | print "------------------------------------------------------" 190 | current_time = datetime.now().strftime("%c") 191 | stat_time = datetime.fromtimestamp(int(timestamp)).strftime("%c") 192 | print "Current Time: %s Stat Time: %s" % (current_time, stat_time) 193 | 194 | # Check if we've already collected and sent this dataset 195 | last_stat = None 196 | 197 | last_file = "/tmp/%s_last.tmp" % manifest_info 198 | stat_file = "/tmp/%s_data.tmp" % manifest_info 199 | 200 | if os.path.isfile(last_file): 201 | with open(last_file) as f: 202 | last_stat = f.readline() 203 | 204 | if timestamp != last_stat: 205 | with open(stat_file, "w") as f: 206 | f.write("\n".join(zabbix_data)) 207 | 208 | subprocess.call([sender_command, "-v", "-c", config_path, 209 | "-s", array_serial, "-T", "-i", stat_file]) 210 | 211 | print "\n".join(zabbix_data) 212 | print "\n" 213 | 214 | with open(last_file, "w") as f: 215 | f.write(timestamp) 216 | 217 | else: 218 | print "Already posted stats to Zabbix, skipping" 219 | 220 | print "------------------------------------------------------\n" 221 | 222 | 223 | def sp_stats_query(array_serial, ecom_ip, ecom_user="admin", 224 | ecom_pass="#1Password"): 225 | 226 | InstanceID = stat_manifest_info["SP"]["InstanceID"] 227 | 228 | header_row, stat_output = get_stats(array_serial, ecom_ip, InstanceID, 229 | ecom_user, ecom_pass) 230 | 231 | process_stats(header_row, stat_output, array_serial, "SP") 232 | 233 | 234 | def volume_stats_query(array_serial, ecom_ip, ecom_user="admin", 235 | ecom_pass="#1Password"): 236 | 237 | InstanceID = stat_manifest_info["Volumes"]["InstanceID"] 238 | 239 | header_row, stat_output = get_stats(array_serial, ecom_ip, InstanceID, 240 | ecom_user, ecom_pass) 241 | 242 | skip_fields = ["EMCRaid3Writes", "EMCSnapCacheReads", 243 | "EMCSnapCacheWrites", "EMCSnapLogicalUnitReads", 244 | "EMCSnapTLUReads", "EMCSnapTLUWrites", 245 | "EMCSnapLargeWrites", "EMCSPAIOTimeCounter", 246 | "EMCSPBIOTimeCounter", "EMCSPAIdleTimeCounter", 247 | "EMCSPBIdleTimeCounter", "EMCSPAReadIOs", 248 | "EMCSPBReadIOs", "EMCSPAWriteIOs", 249 | "EMCSPBWriteIOs", "EMCKBytesSPARead", 250 | "EMCKBytesSPBRead", "EMCKBytesSPAWritten", 251 | "EMCKBytesSPBWritten", "EMCNonZeroQueueArrivals", 252 | "EMCQueueLengthsOnArrival", "EMCNonZeroRequestArrivals", 253 | "EMCSPANonZeroRequestArrivals", 254 | "EMCSPBNonZeroRequestArrivals", 255 | "EMCOutstandingRequests", "EMCSPAOutstandingRequests", 256 | "EMCSPBOutstandingRequests", "EMCImplicitTresspasses", 257 | "EMCSPAImplicitTresspasses", "EMCSPBImplicitTresspasses", 258 | "EMCExplicitTresspasses", "EMCSPAExplicitTresspasses", 259 | "EMCSPBExplicitTresspasses", "EMCLoggingTime", 260 | "EMCReadHistogram", "EMCReadHistogramOverflows", 261 | "EMCWriteHistogram", "EMCWriteHistogramOverflows"] 262 | 263 | process_stats(header_row, stat_output, array_serial, "Volumes", 264 | skip_fields) 265 | 266 | # Calculate our response times 267 | 268 | def disk_stats_query(array_serial, ecom_ip, ecom_user="admin", 269 | ecom_pass="#1Password"): 270 | 271 | InstanceID = stat_manifest_info["Disks"]["InstanceID"] 272 | 273 | header_row, stat_output = get_stats(array_serial, ecom_ip, InstanceID, 274 | ecom_user, ecom_pass) 275 | 276 | skip_fields = ["EMCSpinUPS", "EMCCurrentPWRSavingLogTimeStamp", 277 | "EMCSpinningCounter", "EMCStandbyCounter"] 278 | 279 | process_stats(header_row, stat_output, array_serial, "Disks", skip_fields) 280 | 281 | 282 | def pool_stats_query(array_serial, ecom_ip, ecom_user="admin", 283 | ecom_pass="#1Password"): 284 | 285 | ecom_conn = ecom_connect(ecom_ip, ecom_user, ecom_pass) 286 | 287 | # Lets locate our array 288 | array_list = ecom_conn.EnumerateInstanceNames("Clar_StorageSystem") 289 | array = None 290 | 291 | for i in array_list: 292 | if i["Name"] == "CLARiiON+%s" % array_serial: 293 | array = i 294 | 295 | # Walk our pools for stats 296 | pool_classes = ["EMC_UnifiedStoragePool", "EMC_DeviceStoragePool", 297 | "EMC_VirtualProvisioningPool"] 298 | 299 | processed_stats = ["TotalManagedSpace", "RemainingManagedSpace", 300 | "EMCPercentSubscribed", "EMCSubscribedCapacity", 301 | "EMCEFDCacheEnabled"] 302 | 303 | zabbix_data = [] 304 | timestamp = datetime.now().strftime("%s") 305 | 306 | for pool_class in pool_classes: 307 | for i in ecom_conn.Associators(array, ResultClass=pool_class): 308 | for stat in processed_stats: 309 | try: 310 | zabbix_key = "emc.vnx.perf.%s[%s]" % ( 311 | stat, i["InstanceID"].replace(" ", "_")) 312 | zabbix_data.append("%s %s %s %s" % (array_serial, 313 | zabbix_key, 314 | timestamp, 315 | i[stat])) 316 | except KeyError: 317 | pass 318 | 319 | stat_file = "/tmp/pool_data.tmp" 320 | 321 | with open(stat_file, "w") as f: 322 | f.write("\n".join(zabbix_data)) 323 | 324 | subprocess.call([sender_command, "-v", "-c", config_path, 325 | "-s", array_serial, "-T", "-i", stat_file]) 326 | 327 | print "\n".join(zabbix_data) 328 | print "\n" 329 | 330 | 331 | def hardware_healthcheck(array_serial, ecom_ip, ecom_user="admin", 332 | ecom_pass="#1Password"): 333 | 334 | ecom_conn = ecom_connect(ecom_ip, ecom_user, ecom_pass) 335 | 336 | # Generate our timestamp 337 | timestamp = datetime.now().strftime("%s") 338 | zabbix_data = [] 339 | 340 | # Lets locate our array 341 | array_list = ecom_conn.EnumerateInstanceNames("Clar_StorageSystem") 342 | array = None 343 | 344 | for i in array_list: 345 | if i["Name"] == "CLARiiON+%s" % array_serial: 346 | array = i 347 | 348 | # Devices we're just locating status on 349 | health_classes = ["EMC_LinkControlDevice", "EMC_PowerDevice", 350 | "EMC_BatteryDevice", "EMC_StorageProcessorSystem", 351 | "EMC_DiskDrive"] 352 | 353 | for device in health_classes: 354 | dev_instance = ecom_conn.Associators(array, ResultClass=device) 355 | for inst in dev_instance: 356 | status = " ".join(inst["StatusDescriptions"]) 357 | if "DiskDrive" in device: 358 | device_id = inst["SystemName"] + "+" + inst["Name"] 359 | elif "StorageProcessor" in device: 360 | device_id = inst["EMCBSPInstanceID"] 361 | else: 362 | device_id = inst["DeviceID"] 363 | 364 | zabbix_key = "emc.vnx.health.Status[%s]" % device_id 365 | zabbix_data.append("%s %s %s %s" % (array_serial, zabbix_key, 366 | timestamp, status)) 367 | 368 | # For enclosures we need to locate the ArrayChassis 369 | chassis_list = ecom_conn.EnumerateInstanceNames("EMC_ArrayChassis") 370 | array_chassis = None 371 | 372 | for i in chassis_list: 373 | if array_serial in i["Tag"]: 374 | array_chassis = i 375 | 376 | # Now we can locate enclosures 377 | enclosures = ecom_conn.Associators(array_chassis, 378 | ResultClass="EMC_EnclosureChassis") 379 | 380 | for inst in enclosures: 381 | status = " ".join(inst["StatusDescriptions"]) 382 | device_id = inst["Tag"] 383 | zabbix_key = "emc.vnx.health.Status[%s]" % device_id 384 | zabbix_data.append("%s %s %s %s" % (array_serial, zabbix_key, 385 | timestamp, status)) 386 | 387 | stat_file = "/tmp/health_data.tmp" 388 | 389 | with open(stat_file, "w") as f: 390 | f.write("\n".join(zabbix_data)) 391 | 392 | subprocess.call([sender_command, "-v", "-c", config_path, 393 | "-s", array_serial, "-T", "-i", stat_file]) 394 | 395 | print "\n".join(zabbix_data) 396 | print "\n" 397 | 398 | 399 | def get_pool_io_stats(ecom_conn, array, disk_id_list, vol_id_list): 400 | # We are using these as a cache for block stats 401 | cim_stats = None 402 | disk_sequence = None 403 | vol_sequence = None 404 | 405 | # Determine the order that the stats are provided, this is the CSVSequence 406 | # from the block manifest 407 | 408 | if not disk_sequence: 409 | manifest = ecom_conn.EnumerateInstanceNames("Clar_BlockManifest") 410 | 411 | for i in manifest: 412 | if "Disk" in i["InstanceID"]: 413 | inst = ecom_conn.GetInstance(i) 414 | disk_sequence = inst["CSVSequence"] 415 | if "Volume" in i["InstanceID"]: 416 | inst = ecom_conn.GetInstance(i) 417 | vol_sequence = inst["CSVSequence"] 418 | 419 | if not cim_stats: 420 | # Grab our block stats service for the array 421 | block_stats = ecom_conn.AssociatorNames( 422 | array, ResultClass="Clar_BlockStatisticsService")[0] 423 | 424 | # Pull statistics, Elementtypes 8 and 10 (Volumes and Disks) 425 | cim_stats = ecom_conn.InvokeMethod( 426 | "GetStatisticsCollection", block_stats, 427 | StatisticsFormat=pywbem.Uint16(2), 428 | ElementTypes=[pywbem.Uint16(8), pywbem.Uint16(10)]) 429 | 430 | disk_stat = StringIO.StringIO(cim_stats[1]["Statistics"][0]) # Disk stats 431 | vol_stat = StringIO.StringIO(cim_stats[1]["Statistics"][1]) # Vol Stats 432 | 433 | # The parameters we care about 434 | pool_stats = ["TotalIOs", "KBytesTransferred", "ReadIOs", "KBytesRead", 435 | "WriteIOs", "KBytesWritten"] 436 | 437 | disk_index_info = {} 438 | vol_index_info = {} 439 | totals_disk = {} 440 | totals_vol = {} 441 | 442 | timestamp = None 443 | 444 | for i in pool_stats: 445 | disk_index_info[disk_sequence.index(i)] = i 446 | vol_index_info[vol_sequence.index(i)] = i 447 | totals_disk[disk_sequence.index(i)] = 0 448 | totals_vol[vol_sequence.index(i)] = 0 449 | 450 | # Disk Stats 451 | reader = csv.reader(disk_stat, delimiter=';') 452 | for row in reader: 453 | if row[0] in disk_id_list: 454 | for j in disk_index_info.keys(): 455 | totals_disk[j] = totals_disk[j] + int(row[j]) 456 | timestamp = row[2] 457 | 458 | # Volume Stats 459 | reader = csv.reader(vol_stat, delimiter=';') 460 | for row in reader: 461 | if row[0] in vol_id_list: 462 | for j in vol_index_info: 463 | totals_vol[j] = totals_vol[j] + int(row[j]) 464 | timestamp = row[2] 465 | 466 | # Build the resultset 467 | results = defaultdict(dict) 468 | for i in disk_index_info.keys(): 469 | results["disks"][disk_index_info[i]] = totals_disk[i] 470 | for i in vol_index_info.keys(): 471 | results["volumes"][vol_index_info[i]] = totals_vol[i] 472 | results["timestamp"] = convert_to_local(timestamp).strftime("%s") 473 | 474 | return results 475 | 476 | 477 | def pool_performance(req_pool, array_serial, ecom_ip, 478 | ecom_user="admin", ecom_pass="#1Password"): 479 | 480 | array_pool = req_pool.replace("_", " ") 481 | 482 | ecom_conn = ecom_connect(ecom_ip, ecom_user, ecom_pass) 483 | 484 | # Lets locate our array 485 | array_list = ecom_conn.EnumerateInstanceNames("Clar_StorageSystem") 486 | array = None 487 | 488 | for i in array_list: 489 | if i["Name"] == "CLARiiON+%s" % array_serial: 490 | array = i 491 | 492 | pools = ecom_conn.AssociatorNames(array, ResultClass="EMC_StoragePool") 493 | 494 | zabbix_data = [] 495 | for pool in pools: 496 | if array_pool in pool["InstanceID"]: 497 | 498 | pool_disks = ecom_conn.Associators( 499 | pool, AssocClass="CIM_ConcreteDependency", 500 | ResultClass="CIM_DiskDrive") 501 | 502 | pool_volumes = ecom_conn.Associators( 503 | pool, ResultClass="CIM_StorageVolume") 504 | 505 | disk_list = [] 506 | for i in pool_disks: 507 | perf_dev_id = "CLAR+%s+Disk+%s" % (array_serial, i["Name"]) 508 | disk_list.append(perf_dev_id) 509 | 510 | vol_list = [] 511 | for i in pool_volumes: 512 | vol_list.append(i["EMCBSPInstanceID"]) 513 | 514 | stats = get_pool_io_stats(ecom_conn, array, disk_list, vol_list) 515 | 516 | timestamp = stats["timestamp"] 517 | for i in stats["disks"].keys(): 518 | zabbix_key = "emc.vnx.perf.PoolDisk%s[%s]" % (i, req_pool) 519 | zabbix_data.append("%s %s %s %s" % (array_serial, zabbix_key, 520 | timestamp, 521 | str(stats["disks"][i]))) 522 | 523 | for i in stats["volumes"].keys(): 524 | zabbix_key = "emc.vnx.perf.PoolVol%s[%s]" % (i, req_pool) 525 | zabbix_data.append("%s %s %s %s" % (array_serial, zabbix_key, 526 | timestamp, 527 | str(stats["volumes"][i]))) 528 | 529 | print "------------------------------------------------------" 530 | current_time = datetime.now().strftime("%c") 531 | stat_time = datetime.fromtimestamp(int(timestamp)).strftime("%c") 532 | print "Current Time: %s Stat Time: %s" % (current_time, stat_time) 533 | 534 | # Check if we've already collected and sent this dataset 535 | last_stat = None 536 | 537 | last_file = "/tmp/poolperf_%s_last.tmp" % req_pool 538 | stat_file = "/tmp/poolperf_%s_data.tmp" % req_pool 539 | 540 | if os.path.isfile(last_file): 541 | with open(last_file) as f: 542 | last_stat = f.readline() 543 | 544 | if timestamp != last_stat: 545 | with open(stat_file, "w") as f: 546 | f.write("\n".join(zabbix_data)) 547 | 548 | subprocess.call([sender_command, "-v", "-c", config_path, 549 | "-s", array_serial, "-T", "-i", stat_file]) 550 | 551 | print "\n".join(zabbix_data) 552 | print "\n" 553 | 554 | with open(last_file, "w") as f: 555 | f.write(timestamp) 556 | 557 | else: 558 | print "Already posted stats to Zabbix, skipping" 559 | 560 | print "------------------------------------------------------\n" 561 | 562 | def log_exception_handler(type, value, tb): 563 | logger = logging.getLogger('discovery') 564 | logger.exception("Uncaught exception: {0}".format(str(value))) 565 | 566 | def setup_logging(log_file): 567 | """ Sets up our file logging with rotation """ 568 | my_logger = logging.getLogger('discovery') 569 | my_logger.setLevel(log_level) 570 | 571 | handler = logging.handlers.RotatingFileHandler( 572 | log_file, maxBytes=5120000, backupCount=5) 573 | 574 | formatter = logging.Formatter( 575 | '%(asctime)s %(levelname)s %(process)d %(message)s') 576 | handler.setFormatter(formatter) 577 | 578 | my_logger.addHandler(handler) 579 | 580 | sys.excepthook = log_exception_handler 581 | 582 | return 583 | 584 | def main(): 585 | 586 | log_file = '/tmp/emc_vnx_stats.log' 587 | setup_logging(log_file) 588 | 589 | logger = logging.getLogger('discovery') 590 | 591 | parser = argparse.ArgumentParser() 592 | 593 | parser.add_argument('--serial', '-s', action="store", 594 | help="Array Serial Number", required=True) 595 | parser.add_argument('--ecom_ip', '-i', action="store", 596 | help="IP Address of ECOM server", required=True) 597 | 598 | parser.add_argument('--ecom_user', action="store", 599 | help="ECOM Username", default="admin") 600 | parser.add_argument('--ecom_pass', action="store", 601 | help="ECOM Password", default="#1Password") 602 | 603 | group = parser.add_mutually_exclusive_group(required=True) 604 | group.add_argument('--disks', '-d', action="store_true", 605 | help="Collect Stats on Physical Disks") 606 | group.add_argument('--volumes', '-v', action="store_true", 607 | help="Collect Stats on Volumes/LUNs") 608 | group.add_argument('--procs', '-p', action="store_true", 609 | help="Collect Stats on Storage Processors") 610 | group.add_argument('--pools', '-o', action="store_true", 611 | help="Collect Stats on Physical Disks") 612 | group.add_argument('--array', '-a', action="store_true", 613 | help="Collect Stats on Array devices and enclosures") 614 | group.add_argument('--poolperf', '-r', action="store", 615 | help="Collect individual perf stats on a pool") 616 | 617 | args = parser.parse_args() 618 | logger.debug("Arguments parsed: %s" % str(args)) 619 | 620 | # Check for zabbix_sender and agentd files 621 | if not os.path.isfile(sender_command): 622 | logging.info("Unable to find sender command at: %s" % sender_command) 623 | print "" 624 | print "Unable to locate zabbix_sender command at: %s" % sender_command 625 | print "Please update the script with the appropriate path" 626 | sys.exit() 627 | 628 | if not os.path.isfile(config_path): 629 | logging.info("Unable to find zabbix_agentd.conf at: %s" % config_path) 630 | print "" 631 | print "Unable to locate zabbix_agentd.conf file at: %s" % config_path 632 | print "Please update the script with the appropriate path" 633 | sys.exit() 634 | 635 | if args.disks: 636 | disk_stats_query(args.serial, args.ecom_ip, 637 | args.ecom_user, args.ecom_pass) 638 | sys.exit() 639 | elif args.volumes: 640 | volume_stats_query(args.serial, args.ecom_ip, 641 | args.ecom_user, args.ecom_pass) 642 | sys.exit() 643 | elif args.procs: 644 | sp_stats_query(args.serial, args.ecom_ip, 645 | args.ecom_user, args.ecom_pass) 646 | sys.exit() 647 | elif args.pools: 648 | pool_stats_query(args.serial, args.ecom_ip, 649 | args.ecom_user, args.ecom_pass) 650 | sys.exit() 651 | elif args.array: 652 | hardware_healthcheck(args.serial, args.ecom_ip, 653 | args.ecom_user, args.ecom_pass) 654 | elif args.poolperf: 655 | pool_performance(args.poolperf, args.serial, args.ecom_ip, 656 | args.ecom_user, args.ecom_pass) 657 | sys.exit() 658 | 659 | 660 | if __name__ == "__main__": 661 | main() 662 | -------------------------------------------------------------------------------- /tools/ecom_vnx_manage.py: -------------------------------------------------------------------------------- 1 | #!/bin/env python 2 | 3 | import sys 4 | import json 5 | import pywbem 6 | import argparse 7 | 8 | 9 | def add_vnx(spa_ip, spb_ip, array_user, array_pass, 10 | ecom_ip, ecom_user="admin", ecom_pass="#1Password"): 11 | 12 | ecom_url = "https://%s:5989" % ecom_ip 13 | ecom_conn = pywbem.WBEMConnection(ecom_url,(ecom_user,ecom_pass), 14 | default_namespace="/root/emc") 15 | ers = ecom_conn.EnumerateInstanceNames("EMC_SystemRegistrationService") 16 | o = ecom_conn.InvokeMethod("EMCAddSystem",ers[0], 17 | ArrayType = pywbem.Uint16(1), 18 | Addresses = [spa_ip,spb_ip], 19 | Types = [pywbem.Uint16(2),pywbem.Uint16(2)], 20 | User = array_user, Password = array_pass 21 | ) 22 | 23 | results = ["Success","Not Supported","Unknown","Timeout","Failed", 24 | "Inavlid Parameter","In Use","Existing"] 25 | print "Execution Ouput:" 26 | print o 27 | print "Result: %s" % results[o[0]] 28 | 29 | def main(): 30 | 31 | parser = argparse.ArgumentParser() 32 | parser.add_argument("spa_ip", help="IP Address of SPA") 33 | parser.add_argument("spb_ip", help="IP Address of SPB") 34 | parser.add_argument("array_user", help="Username for Array") 35 | parser.add_argument("array_pass", help="Password for Array") 36 | parser.add_argument("ecom_ip", help="IP Address of ECOM Server") 37 | parser.add_argument("--ecom_user", help="Username for ECOM Server", 38 | default="admin") 39 | parser.add_argument("--ecom_pass", help="Password for ECOM Server", 40 | default="#1Password") 41 | 42 | 43 | args = parser.parse_args() 44 | 45 | add_vnx(args.spa_ip, args.spb_ip, args.array_user, args.array_pass, 46 | args.ecom_ip, args.ecom_user, args.ecom_pass) 47 | 48 | if __name__ == "__main__": 49 | main() 50 | 51 | 52 | 53 | --------------------------------------------------------------------------------