Skip to content

Commit d58a908

Browse files
committed
Adding of VMAX module
With that module you can collect data on VMAX 2 and 3 arrays.
1 parent 4fe80be commit d58a908

File tree

9 files changed

+807
-18
lines changed

9 files changed

+807
-18
lines changed

README.md

100644100755
Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# Array-XRay
22

3-
A tool to provide an inventory of your EMC VPLEX and IBM SVC arrays
3+
A tool to provide an inventory of your EMC VPLEX, VMAX and IBM SVC arrays
44

55
## Purpose
66

7-
This tool can manage EMC VPLEX and IBM SVC/FlashSystem arrays.
7+
This tool can manage EMC VPLEX, VMAX and IBM SVC/FlashSystem arrays.
88

99
On EMC VPLEX systems you can inventory:
1010
- All Cluster's information
@@ -13,6 +13,19 @@ On EMC VPLEX systems you can inventory:
1313
- List of all Storage Views
1414
- List of all backend Storage arrays
1515

16+
On EMC VMAX systems you can inventory:
17+
- All System's information
18+
- List of Thin Pools (for VMAX-2 family)
19+
- List of TDEVs
20+
- List of SRPs (for VMAX-3 family)
21+
- List of Initiators
22+
- List of Masking Views
23+
- List of Hosts
24+
- List of Host Groups
25+
- List of Port Groups
26+
- List of Storage Groups
27+
- List of FAST Policies (for VMAX-2 family)
28+
1629
On IBM SVC systems you can inventory:
1730
- List of all SVC systems
1831
- List of all Vdisks/Volumes
@@ -52,8 +65,6 @@ optional arguments:
5265

5366
### Example
5467

55-
56-
5768
```
5869
[jbrt@locahost]$ ./vplex-xray.py --config conf.txt --path . --file VPLEX.xlsx
5970
Initializing a Excel workbook (VPLEX.xlsx)
@@ -81,6 +92,61 @@ Here is the syntax of the configuration file needed by this tool :
8192

8293
You can add all the VPLEXs you need.
8394

95+
## EMC VMAX
96+
97+
For collecting data on VMAXs this tool use the REST API of UNIPSHERE for VMAX.
98+
**You must use version 8.x at least.**
99+
100+
### Usage
101+
102+
```
103+
[jbrt@localhost]$ ./vmax-xray.py --help
104+
usage: vmax-xray.py [-h] -c CONFIG -p PATH -f FILE [-d]
105+
106+
VMAX-XRay - Tool for Inventory a VMAX array
107+
108+
optional arguments:
109+
-h, --help show this help message and exit
110+
-c CONFIG, --config CONFIG
111+
config file
112+
-p PATH, --path PATH path to store file
113+
-f FILE, --file FILE name of the file
114+
-d, --debug enable debug mode
115+
```
116+
117+
### Example
118+
119+
```
120+
[jbrt@locahost]$ ./vmax-xray.py --config conf.txt --path . --file VMAX.xlsx
121+
Initializing a Excel workbook (VMAX.xlsx)
122+
123+
Beginning of data extraction 000298700609
124+
- Extraction of System's information
125+
- Extraction of Thin Pools
126+
- Extraction of TDEVs
127+
- Extraction of Initiators
128+
- Extraction of Masking Views
129+
- Extraction of Hosts
130+
- Extraction of Host Groups
131+
- Extraction of Port Groups
132+
- Extraction of Storage Groups
133+
- Extraction of FAST Policies
134+
End of data extraction 000298700609
135+
```
136+
137+
### Configuration file
138+
139+
Here is the syntax of the configuration file needed by this tool :
140+
141+
```
142+
[YOUR_SYMMETRIX_ID]
143+
address = IP_ADDRESS_OF_UNISPHERE
144+
user = username
145+
password = password
146+
```
147+
148+
You can add all the VMAXs you need.
149+
84150
## IBM SVC / FlashSystem
85151

86152
### Usage
@@ -145,7 +211,7 @@ You can add all the SVCs you need.
145211
There is a lot of work ahead ! This is a first release of that tool. Many
146212
new features will come depending on the needs. Here is some of ideas :
147213

148-
- Adding support of the VMAX arrays (merge from my project Vmax-XRay)
214+
- ~~Adding support of the VMAX arrays (merge from my project Vmax-XRay)~~ done
149215
- Adding new inventory format (load data into ElasticSearch, for example)
150216

151217
Feel free to contribute if you want.

arrays/errors.py

100644100755
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def __init__(self, message):
1717
Exception.__init__(self, message)
1818

1919

20-
class VmaxIteratorError(Exception):
20+
class VMAXConnectionError(Exception):
2121
def __init__(self, message):
2222
Exception.__init__(self, message)
2323

arrays/vmax/README.md

Lines changed: 0 additions & 12 deletions
This file was deleted.

arrays/vmax/__init__.py

Whitespace-only changes.

arrays/vmax/vmax_connector.py

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
#!/usr/bin/env python3
2+
# coding: utf-8
3+
4+
"""
5+
Sending REST calls to a VMAX equipment.
6+
Returning, in the most of cases, a list of dictionary for each method.
7+
"""
8+
9+
import abc
10+
import json
11+
import logging
12+
import requests
13+
from requests.packages.urllib3.exceptions import InsecureRequestWarning, MaxRetryError
14+
from arrays.errors import VMAXConnectionError, VmaxInventoryFactoryError
15+
16+
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
17+
18+
19+
def next_range(start, end, pagesize):
20+
"""
21+
Generate a start and a stop number used in VMAX's iterator concept
22+
:param start: (int) start at this point
23+
:param end: (int) last number
24+
:param pagesize: (int) maximum of values by page
25+
:return: (int, int)
26+
"""
27+
if (end-start) < pagesize:
28+
yield start, end
29+
else:
30+
values = [i for i in range(start, end, pagesize-1)]
31+
for index, value in enumerate(values):
32+
if value != values[-1]:
33+
yield value, values[index+1]-1
34+
else:
35+
yield value, end
36+
37+
38+
class BaseVMAXArray(object, metaclass=abc.ABCMeta):
39+
"""
40+
Abstract class of VMAX objects
41+
"""
42+
43+
def __init__(self, sym_id: str, address: str, user: str, password: str):
44+
"""
45+
Constructor
46+
:param sym_id: Symmetrix ID of the VMAX
47+
:param address: IP address
48+
:param user: Unipshere user
49+
:param password: Unisphere password
50+
"""
51+
self._sym_id = sym_id
52+
self._address = address
53+
self._user = user
54+
self._password = password
55+
self._logger = logging.getLogger('arrayxray')
56+
57+
# Sample of UNISPHERE REST API
58+
# https://126.199.128.46:8443/univmax/restapi/provisioning/symmetrix/000295700220/volume/00A00
59+
self._url = 'https://%s:8443/univmax/restapi' % self._address
60+
61+
def __str__(self):
62+
return self._sym_id
63+
64+
@property
65+
def node(self):
66+
raise NotImplementedError
67+
68+
def _get_request(self, request: str):
69+
"""
70+
Send a GET request to the VMAX
71+
:param request: URI of the request
72+
:return: JSON payload
73+
"""
74+
url = '/'.join([self._url, request])
75+
try:
76+
self._logger.debug('---> GET %s' % request)
77+
data = requests.get(url,
78+
auth=(self._user, self._password),
79+
timeout=600,
80+
verify=False)
81+
82+
if isinstance(data.text, str) and 'Unauthorized' in data.text:
83+
raise VMAXConnectionError('Authentication failure')
84+
85+
data = json.loads(data.text)
86+
except requests.exceptions.ConnectionError as error:
87+
# If we experience a MaxRetryError, try again ! ;-)
88+
# UNISPHERE may be slow to respond
89+
if isinstance(error.args[0], MaxRetryError):
90+
self._logger.error('/!\ RETRY FOR %s' % request)
91+
return self._get_request(request)
92+
93+
self._logger.error('%s' % error)
94+
raise VMAXConnectionError('Problem while connecting to VMAX')
95+
96+
except requests.exceptions.ReadTimeout:
97+
raise VMAXConnectionError('Timeout reached')
98+
99+
else:
100+
return data
101+
102+
def _get_request_recursive(self, request: str, re_type: str, re_name: str):
103+
"""
104+
Execute a recursive request on each items on a list
105+
:param request: URI request
106+
:param re_type: resource type to find
107+
:param re_name: resource name
108+
:return: (list)
109+
"""
110+
response = []
111+
data = self._get_request(request)
112+
# if 'message' in data : no data to collect
113+
if 'message' not in data:
114+
for item in data[re_type]:
115+
data = self._get_request('/'.join([request, item]))
116+
response.append(data[re_name][0])
117+
return response
118+
119+
def _get_volumes_iterator(self, iterator: str, count: int, page_size: int):
120+
"""
121+
Deal with UNISPHERE iterators to make a list all volumes on an array
122+
:param iterator: ID of the iterator
123+
:param count: number of devices on array
124+
:param page_size: maximum size of a respond page
125+
:return: list of volumes string
126+
"""
127+
# Sample of iterator request
128+
# common/Iterator/4f477856-5209-46af-a040-9abeed6d31f3_0/page?from=1999&to=1999
129+
request = 'common/Iterator/%s/page' % iterator
130+
volumes = []
131+
132+
for from_, to_ in next_range(1, count, page_size):
133+
req_range = request + '?from=%s&to=%s' % (from_, to_)
134+
135+
for vol in self._get_request(req_range)['result']:
136+
volumes.append(vol['volumeId'])
137+
138+
return volumes
139+
140+
def get_hosts(self):
141+
request = '%s/symmetrix/%s/host' % (self.node, self._sym_id)
142+
return self._get_request_recursive(request, 'hostId', 'host')
143+
144+
def get_host_groups(self):
145+
request = '%s/symmetrix/%s/hostgroup' % (self.node, self._sym_id)
146+
return self._get_request_recursive(request, 'hostGroupId', 'hostGroup')
147+
148+
def get_initiators(self):
149+
request = '%s/symmetrix/%s/initiator' % (self.node, self._sym_id)
150+
return self._get_request_recursive(request, 'initiatorId', 'initiator')
151+
152+
def get_masking_view(self):
153+
request = '%s/symmetrix/%s/maskingview' % (self.node, self._sym_id)
154+
return self._get_request_recursive(request, 'maskingViewId', 'maskingView')
155+
156+
def get_port_groups(self):
157+
request = '%s/symmetrix/%s/portgroup' % (self.node, self._sym_id)
158+
return self._get_request_recursive(request, 'portGroupId', 'portGroup')
159+
160+
def get_storage_group(self):
161+
request = '%s/symmetrix/%s/storagegroup' % (self.node, self._sym_id)
162+
return self._get_request_recursive(request, 'storageGroupId', 'storageGroup')
163+
164+
def get_system(self):
165+
request = '%s/symmetrix/%s' % (self.node, self._sym_id)
166+
return self._get_request(request)['symmetrix'][0]
167+
168+
def get_thin_volumes(self):
169+
base_request = '%s/symmetrix/%s/volume' % (self.node, self._sym_id)
170+
request = base_request + '?meta_member=false&tdev=true'
171+
data = self._get_request(request)
172+
all_devices = []
173+
# First of all : get the list of all volumes on the array
174+
# If there is more than one page : use an iterator
175+
if data['count'] > data['maxPageSize']:
176+
all_devices = self._get_volumes_iterator(data['id'],
177+
data['count'],
178+
data['maxPageSize'])
179+
else:
180+
for volume in data['resultList']['result']:
181+
all_devices.append(volume['volumeId'])
182+
183+
# Then for each of volume in the list : run a GET request
184+
# I know it's slow and not very clean but it's the only way to get all
185+
# the information i needs
186+
response = []
187+
for device in all_devices:
188+
data = self._get_request('/'.join([base_request, device]))
189+
response.append(data['volume'][0])
190+
return response
191+
192+
def get_version(self):
193+
request = 'system/version'
194+
return tuple(self._get_request(request)['version'].split('.'))
195+
196+
197+
class VMAX2Array(BaseVMAXArray):
198+
"""Concrete VMAX-2 array"""
199+
200+
def __init__(self, sym_id: str, address: str, user: str, password: str):
201+
super(VMAX2Array, self).__init__(sym_id, address, user, password)
202+
203+
@property
204+
def node(self):
205+
return 'provisioning'
206+
207+
def get_fast_policy(self):
208+
request = '%s/symmetrix/%s/fastpolicy' % (self.node, self._sym_id)
209+
return self._get_request_recursive(request, 'fastPolicyId', 'fastPolicy')
210+
211+
def get_thin_pool(self):
212+
request = '%s/symmetrix/%s/thinpool' % (self.node, self._sym_id)
213+
return self._get_request_recursive(request, 'poolId', 'thinPool')
214+
215+
216+
class VMAX3Array(BaseVMAXArray):
217+
"""Concrete VMAX-3 array"""
218+
219+
def __init__(self, sym_id: str, address: str, user: str, password: str):
220+
super(VMAX3Array, self).__init__(sym_id, address, user, password)
221+
222+
@property
223+
def node(self):
224+
return 'sloprovisioning'
225+
226+
def get_srp(self):
227+
request = '%s/symmetrix/%s/srp' % (self.node, self._sym_id)
228+
return self._get_request_recursive(request, 'srpId', 'srp')
229+
230+
231+
class VMAXArrayFactory(object):
232+
"""VMAX Factory"""
233+
234+
classes = {'26': VMAX2Array,
235+
'49': VMAX2Array,
236+
'57': VMAX2Array,
237+
'59': VMAX2Array,
238+
'87': VMAX2Array,
239+
'67': VMAX3Array,
240+
'68': VMAX3Array,
241+
'72': VMAX3Array,
242+
'70': VMAX3Array,
243+
'75': VMAX3Array,
244+
'77': VMAX3Array,
245+
'78': VMAX3Array}
246+
247+
def __new__(cls, sym_id: str, address: str, user: str, password: str):
248+
cls._logger = logging.getLogger('arrayxray')
249+
250+
model_type = sym_id[5:7] # Model type is in the middle of the SID
251+
252+
if model_type not in cls.classes:
253+
msg = "The SID %s doesn't match with any VMAX Array model" % sym_id
254+
cls._logger.error(msg)
255+
raise VmaxInventoryFactoryError
256+
257+
return cls.classes[model_type](sym_id, address, user, password)

0 commit comments

Comments
 (0)