├── 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 |
--------------------------------------------------------------------------------