├── README.md ├── Template EMC Unity REST-API.xml ├── Unity_Health_Status_valuemaps.xml ├── Unity_Running_Status_valuemaps.xml └── unity_get_state.py /README.md: -------------------------------------------------------------------------------- 1 | # zabbix-emc-unity 2 | Python script for monitoring EMC Unity storages 3 | 4 | 5 | For succesfull work of script need installed python3, zabbix-sender. 6 | 7 | In template "Template EMC Unity REST-API" in section "Macros" need set these macros: 8 | - {$API_USER} 9 | - {$API_PASSWORD} 10 | - {$API_PORT} 11 | - {$SUBSCRIBED_PERCENT} 12 | - {$USED_PERCENT} 13 | 14 | In agent configuration file, **/etc/zabbix/zabbix_agentd.conf** must be set parameter **ServerActive=xxx.xxx.xxx.xxx** 15 | 16 | Scirpt must be copied to zabbix-server or zabbix-proxy, depending on what the torage is being monitored. 17 | 18 | 19 | - In Linux-console on zabbix-server or zabbix-proxy need run this command to make discovery. Script must return value 0 in case of success. 20 | ```bash 21 | ./unity_get_state.py --api_ip=xxx.xxx.xxx.xxx --api_port=443 --api_user=username_on_storagedevice --api_password='password' --storage_name="storage-name_in_zabbix" --discovery 22 | ``` 23 | - On zabbix proxy or on zabbix servers need run **zabbix_proxy -R config_cache_reload** (zabbix_server -R config_cache_reload) 24 | 25 | - In Linux-console on zabbix-server or zabbix-proxy need run this command to get value of metrics. Scripts must return value 0 in case of success. 26 | ```bash 27 | ./unity_get_stateNEW.py --api_ip=xxx.xxx.xxx.xxx --api_port=443 --api_user=username_on_storagedevice --api_password='password' --storage_name="storage-name_in_zabbix" --status 28 | ``` 29 | If you have executed this script from console from user root or from another user, please check access permission on file **/tmp/unity_state.log**. It must be allow read, write to user zabbix. 30 | 31 | - Return code 1 or 2 is zabbix_sender return code. Read here - https://www.zabbix.com/documentation/4.4/manpages/zabbix_sender 32 | -------------------------------------------------------------------------------- /Unity_Health_Status_valuemaps.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0 4 | 2019-09-23T15:55:12Z 5 | 6 | 7 | Unity_Health_Status 8 | 9 | 10 | 0 11 | UNKNOWN 12 | 13 | 14 | 5 15 | OK 16 | 17 | 18 | 7 19 | OK_BUT 20 | 21 | 22 | 10 23 | DEGRADED 24 | 25 | 26 | 15 27 | MINOR 28 | 29 | 30 | 20 31 | MAJOR 32 | 33 | 34 | 25 35 | CRITICAL 36 | 37 | 38 | 30 39 | NON_RECOVERABLE 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Unity_Running_Status_valuemaps.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0 4 | 2019-09-23T15:55:33Z 5 | 6 | 7 | Unity_Running_Status 8 | 9 | 10 | 5 11 | NOT_OK 12 | 13 | 14 | 6 15 | DISK_SLOT_EMPTY 16 | 17 | 18 | 8 19 | COMPONENT_OK 20 | 21 | 22 | 10 23 | Link_up 24 | 25 | 26 | 11 27 | Link_down 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /unity_get_state.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import time 6 | import argparse 7 | import sys 8 | import json 9 | import subprocess 10 | import logging 11 | import logging.handlers 12 | import requests 13 | import urllib3 14 | #urllib3.disable_warnings() 15 | #urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 16 | 17 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 18 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 19 | 20 | 21 | # Create log-object 22 | LOG_FILENAME = "/tmp/unity_state.log" 23 | unity_logger = logging.getLogger("unity_logger") 24 | unity_logger.setLevel(logging.INFO) 25 | 26 | # Set handler 27 | unity_handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=1024*1024, backupCount=5) 28 | unity_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 29 | 30 | # Set formatter for handler 31 | unity_handler.setFormatter(unity_formatter) 32 | 33 | # Add handler to log-object 34 | unity_logger.addHandler(unity_handler) 35 | 36 | 37 | def api_connect(api_user, api_password, api_ip, api_port): 38 | api_login_url = "https://{0}:{1}/api/types/loginSessionInfo".format(api_ip, api_port) 39 | session_unity = requests.Session() 40 | session_unity.auth = (api_user, api_password) 41 | session_unity.headers = {'X-EMC-REST-CLIENT': 'true', 'Content-type': 'application/json', 'Accept': 'application/json'} 42 | 43 | try: 44 | login = session_unity.get(api_login_url, verify=False) 45 | except Exception as oops: 46 | unity_logger.error("Connection Error Occurs: {0}".format(oops)) 47 | sys.exit("50") 48 | 49 | if login.status_code != 200: 50 | unity_logger.error("Connection Return Code = {0}".format(login.status_code)) 51 | sys.exit("60") 52 | elif login.text.find("isPasswordChangeRequired") >= 0: # If i can find string "isPasswordChangeRequired" therefore login is successful 53 | unity_logger.info("Connection established") 54 | return session_unity 55 | else: 56 | unity_logger.error("Login Something went wrong") 57 | sys.exit("70") 58 | 59 | 60 | 61 | def api_logout(api_ip, session_unity): 62 | api_logout_url = "https://{0}/api/types/loginSessionInfo/action/logout".format(api_ip) 63 | session_unity.headers = {'Content-type': 'application/json', 'Accept': 'application/json'} 64 | 65 | try: 66 | logout = session_unity.post(api_logout_url, verify=False) 67 | except Exception as oops: 68 | unity_logger.error("Logout Error Occurs: {0}".format(oops)) 69 | sys.exit("150") 70 | 71 | if logout.status_code != 200: 72 | unity_logger.error("Logout status = {0}".format(logout.status_code)) 73 | sys.exit("160") 74 | elif logout.text.find("Logout successful") >= 0: 75 | unity_logger.info("Logout successful") 76 | else: 77 | unity_logger.error("Logout Something went wrong") 78 | sys.exit("170") 79 | 80 | 81 | def convert_to_zabbix_json(data): 82 | output = json.dumps({"data": data}, indent = None, separators = (',',': ')) 83 | return output 84 | 85 | 86 | 87 | def send_data_to_zabbix(zabbix_data, storage_name): 88 | sender_command = "/usr/bin/zabbix_sender" 89 | config_path = "/etc/zabbix/zabbix_agentd.conf" 90 | time_of_create_file = int(time.time()) 91 | temp_file = "/tmp/{0}_{1}.tmp".format(storage_name, time_of_create_file) 92 | 93 | with open(temp_file, "w") as f: 94 | f.write("") 95 | f.write("\n".join(zabbix_data)) 96 | 97 | send_code = subprocess.call([sender_command, "-vv", "-c", config_path, "-s", storage_name, "-T", "-i", temp_file], stdout = subprocess.PIPE, stderr = subprocess.PIPE) 98 | os.remove(temp_file) 99 | return send_code 100 | 101 | 102 | 103 | def discovering_resources(api_user, api_password, api_ip, api_port, storage_name, list_resources): 104 | api_session = api_connect(api_user, api_password, api_ip, api_port) 105 | 106 | xer = [] 107 | try: 108 | for resource in list_resources: 109 | resource_url = "https://{0}:{1}/api/types/{2}/instances?fields=name".format(api_ip, api_port, resource) 110 | resource_info = api_session.get(resource_url, verify=False) 111 | resource_info = json.loads(resource_info.content.decode('utf8')) 112 | 113 | discovered_resource = [] 114 | for one_object in resource_info['entries']: 115 | if ['lun', 'pool'].count(resource) == 1: 116 | one_object_list = {} 117 | one_object_list["{#ID}"] = one_object['content']['id'] 118 | one_object_list["{#NAME}"] = one_object['content']['name'].replace(' ', '_') 119 | discovered_resource.append(one_object_list) 120 | else: 121 | one_object_list = {} 122 | one_object_list["{#ID}"] = one_object['content']['id'] 123 | discovered_resource.append(one_object_list) 124 | converted_resource = convert_to_zabbix_json(discovered_resource) 125 | timestampnow = int(time.time()) 126 | xer.append("%s %s %s %s" % (storage_name, resource, timestampnow, converted_resource)) 127 | except Exception as oops: 128 | unity_logger.error("Error occurs in discovering") 129 | sys.exit("1000") 130 | 131 | api_session_logout = api_logout(api_ip, api_session) 132 | return send_data_to_zabbix(xer, storage_name) 133 | 134 | 135 | 136 | def get_status_resources(api_user, api_password, api_ip, api_port, storage_name, list_resources): 137 | api_session = api_connect(api_user, api_password, api_ip, api_port) 138 | 139 | state_resources = [] # This list will persist state of resources (pool, lun, fcPort, battery, diks, ...) on zabbix format 140 | try: 141 | for resource in list_resources: 142 | # Create different URI for different resources 143 | if ['pool'].count(resource) == 1: 144 | resource_url = "https://{0}:{1}/api/types/{2}/instances?fields=name,health,sizeTotal,sizeUsed,sizeSubscribed".format(api_ip, api_port, resource) 145 | elif ['lun'].count(resource) == 1: 146 | resource_url = "https://{0}:{1}/api/types/{2}/instances?fields=name,health,sizeTotal,sizeAllocated".format(api_ip, api_port, resource) 147 | else: 148 | resource_url = "https://{0}:{1}/api/types/{2}/instances?fields=name,health,needsReplacement".format(api_ip, api_port, resource) 149 | 150 | # Get info about one resource 151 | resource_info = api_session.get(resource_url, verify=False) 152 | resource_info = json.loads(resource_info.content.decode('utf8')) 153 | timestampnow = int(time.time()) 154 | 155 | if ['ethernetPort', 'fcPort', 'sasPort'].count(resource) == 1: 156 | for one_object in resource_info['entries']: 157 | key_health = "health.{0}.[{1}]".format(resource, one_object['content']['id'].replace(' ', '_')) 158 | key_status = "link.{0}.[{1}]".format(resource, one_object['content']['id'].replace(' ', '_')) 159 | state_resources.append("%s %s %s %s" % (storage_name, key_health, timestampnow, one_object['content']['health']['value'])) 160 | 161 | # Get state of interfaces from description 162 | descriptionIds = str(one_object['content']['health']['descriptionIds'][0]) # Convert description to string 163 | if descriptionIds.find("LINK_UP") >= 0: # From description i can known, link is up or link is down 164 | link_status = 10 165 | elif descriptionIds.find("LINK_DOWN") >=0: 166 | link_status = 11 167 | 168 | state_resources.append("%s %s %s %s" % (storage_name, key_status, timestampnow, link_status)) 169 | 170 | elif ['lun'].count(resource) == 1: 171 | for one_object in resource_info['entries']: 172 | key_health = "health.{0}.[{1}]".format(resource, one_object['content']['name'].replace(' ', '_')) # Use lun name instead lun id on zabbix key 173 | key_sizeTotal = "sizeTotal.{0}.[{1}]".format(resource, one_object['content']['name'].replace(' ', '_')) 174 | key_sizeAllocated = "sizeAllocated.{0}.[{1}]".format(resource, one_object['content']['name'].replace(' ', '_')) 175 | 176 | state_resources.append("%s %s %s %s" % (storage_name, key_health, timestampnow, one_object['content']['health']['value'])) 177 | state_resources.append("%s %s %s %s" % (storage_name, key_sizeTotal, timestampnow, one_object['content']['sizeTotal'])) 178 | state_resources.append("%s %s %s %s" % (storage_name, key_sizeAllocated, timestampnow, one_object['content']['sizeAllocated'])) 179 | elif ['pool'].count(resource) == 1: 180 | for one_object in resource_info['entries']: 181 | key_health = "health.{0}.[{1}]".format(resource, one_object['content']['name'].replace(' ', '_')) # Use pull name instead lun id on zabbix key 182 | key_sizeUsedBytes = "sizeUsedBytes.{0}.[{1}]".format(resource, one_object['content']['name'].replace(' ', '_')) 183 | key_sizeTotalBytes = "sizeTotalBytes.{0}.[{1}]".format(resource, one_object['content']['name'].replace(' ', '_')) 184 | key_sizeSubscribedBytes = "sizeSubscribedBytes.{0}.[{1}]".format(resource, one_object['content']['name'].replace(' ', '_')) 185 | 186 | state_resources.append("%s %s %s %s" % (storage_name, key_health, timestampnow, one_object['content']['health']['value'])) 187 | state_resources.append("%s %s %s %s" % (storage_name, key_sizeUsedBytes, timestampnow, one_object['content']['sizeUsed'])) 188 | state_resources.append("%s %s %s %s" % (storage_name, key_sizeTotalBytes, timestampnow, one_object['content']['sizeTotal'])) 189 | state_resources.append("%s %s %s %s" % (storage_name, key_sizeSubscribedBytes, timestampnow, one_object['content']['sizeSubscribed'])) 190 | else: 191 | for one_object in resource_info['entries']: 192 | # Get state of resources from description 193 | descriptionIds = str(one_object['content']['health']['descriptionIds'][0]) # Convert description to string 194 | if descriptionIds.find("ALRT_COMPONENT_OK") >= 0: 195 | running_status = 8 196 | elif descriptionIds.find("ALRT_DISK_SLOT_EMPTY") >= 0: 197 | running_status = 6 198 | else: 199 | running_status = 5 200 | 201 | key_health = "health.{0}.[{1}]".format(resource, one_object['content']['id'].replace(' ', '_')) 202 | key_status = "running.{0}.[{1}]".format(resource, one_object['content']['id'].replace(' ', '_')) 203 | state_resources.append("%s %s %s %s" % (storage_name, key_health, timestampnow, one_object['content']['health']['value'])) 204 | state_resources.append("%s %s %s %s" % (storage_name, key_status, timestampnow, running_status)) 205 | except Exception as oops: 206 | unity_logger.error("Error occured in get state") 207 | sys.exit("1000") 208 | 209 | api_session_logout = api_logout(api_ip, api_session) 210 | return send_data_to_zabbix(state_resources, storage_name) 211 | 212 | 213 | 214 | def main(): 215 | # Parsing arguments 216 | unity_parser = argparse.ArgumentParser() 217 | unity_parser.add_argument('--api_ip', action="store", help="Where to connect", required=True) 218 | unity_parser.add_argument('--api_port', action="store", required=True) 219 | unity_parser.add_argument('--api_user', action="store", required=True) 220 | unity_parser.add_argument('--api_password', action="store", required=True) 221 | unity_parser.add_argument('--storage_name', action="store", required=True) 222 | 223 | group = unity_parser.add_mutually_exclusive_group(required=True) 224 | group.add_argument('--discovery', action ='store_true') 225 | group.add_argument('--status', action='store_true') 226 | arguments = unity_parser.parse_args() 227 | 228 | list_resources = ['battery','ssd','ethernetPort','fcPort','sasPort','fan','powerSupply','storageProcessor','lun','pool','dae','dpe','ioModule','lcc','memoryModule','ssc','uncommittedPort','disk'] 229 | if arguments.discovery: 230 | result_discovery = discovering_resources(arguments.api_user, arguments.api_password, arguments.api_ip, arguments.api_port, arguments.storage_name, list_resources) 231 | print (result_discovery) 232 | elif arguments.status: 233 | result_status = get_status_resources(arguments.api_user, arguments.api_password, arguments.api_ip, arguments.api_port, arguments.storage_name, list_resources) 234 | print (result_status) 235 | 236 | if __name__ == "__main__": 237 | main() 238 | 239 | --------------------------------------------------------------------------------