Coverage for manila/share/drivers/huawei/v3/helper.py: 93%
891 statements
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
1# Copyright (c) 2014 Huawei Technologies Co., Ltd.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
16import base64
17import copy
18import requests
19import time
21from defusedxml import ElementTree as ET
22from oslo_log import log
23from oslo_serialization import jsonutils
25from manila import exception
26from manila.i18n import _
27from manila.share.drivers.huawei import constants
28from manila import utils
30LOG = log.getLogger(__name__)
33class RestHelper(object):
34 """Helper class for Huawei OceanStor V3 storage system."""
36 def __init__(self, configuration):
37 self.configuration = configuration
38 self.url = None
39 self.session = None
41 # pylint: disable=no-member
42 requests.packages.urllib3.disable_warnings(
43 requests.packages.urllib3.exceptions.InsecureRequestWarning)
44 requests.packages.urllib3.disable_warnings(
45 requests.packages.urllib3.exceptions.InsecurePlatformWarning)
46 # pylint: enable=no-member
48 def init_http_head(self):
49 self.url = None
50 self.session = requests.Session()
51 self.session.headers.update({
52 "Connection": "keep-alive",
53 "Content-Type": "application/json"})
54 self.session.verify = False
56 def do_call(self, url, data, method, calltimeout=constants.SOCKET_TIMEOUT):
57 """Send requests to server.
59 Send HTTPS call, get response in JSON.
60 Convert response into Python Object and return it.
61 """
62 if self.url: 62 ↛ 63line 62 didn't jump to line 63 because the condition on line 62 was never true
63 url = self.url + url
65 LOG.debug('Request URL: %(url)s\n'
66 'Call Method: %(method)s\n'
67 'Request Data: %(data)s\n',
68 {'url': url,
69 'method': method,
70 'data': data})
72 kwargs = {'timeout': calltimeout}
73 if data:
74 kwargs['data'] = data
76 if method in ('POST', 'PUT', 'GET', 'DELETE'):
77 func = getattr(self.session, method.lower())
78 else:
79 msg = _("Request method %s is invalid.") % method
80 LOG.error(msg)
81 raise exception.ShareBackendException(msg=msg)
83 try:
84 res = func(url, **kwargs)
85 except Exception as err:
86 LOG.error('\nBad response from server: %(url)s.'
87 ' Error: %(err)s', {'url': url, 'err': err})
88 return {"error": {"code": constants.ERROR_CONNECT_TO_SERVER,
89 "description": "Connect server error"}}
91 try:
92 res.raise_for_status()
93 except requests.HTTPError as exc:
94 return {"error": {"code": exc.response.status_code,
95 "description": str(exc)}}
97 result = res.json()
98 LOG.debug('Response Data: %s', result)
99 return result
101 def login(self):
102 """Login huawei array."""
103 login_info = self._get_login_info()
104 urlstr = login_info['RestURL']
105 url_list = urlstr.split(";")
106 deviceid = None
107 for item_url in url_list:
108 url = item_url.strip('').strip('\n') + "xx/sessions"
109 data = jsonutils.dumps({"username": login_info['UserName'],
110 "password": login_info['UserPassword'],
111 "scope": "0"})
112 self.init_http_head()
113 result = self.do_call(url, data, 'POST',
114 calltimeout=constants.LOGIN_SOCKET_TIMEOUT)
116 if ((result['error']['code'] != 0)
117 or ("data" not in result)
118 or (result['data']['deviceid'] is None)):
119 LOG.error("Login to %s failed, try another.", item_url)
120 continue
122 LOG.debug('Login success: %(url)s\n', {'url': item_url})
123 deviceid = result['data']['deviceid']
124 self.url = item_url + deviceid
125 self.session.headers['iBaseToken'] = result['data']['iBaseToken']
126 break
128 if deviceid is None:
129 err_msg = _("All url login fail.")
130 LOG.error(err_msg)
131 raise exception.InvalidShare(reason=err_msg)
133 return deviceid
135 @utils.synchronized('huawei_manila')
136 def call(self, url, data, method):
137 """Send requests to server.
139 If fail, try another RestURL.
140 """
141 deviceid = None
142 old_url = self.url
143 result = self.do_call(url, data, method)
144 error_code = result['error']['code']
145 if (error_code == constants.ERROR_CONNECT_TO_SERVER
146 or error_code == constants.ERROR_UNAUTHORIZED_TO_SERVER):
147 LOG.error("Can't open the recent url, re-login.")
148 deviceid = self.login()
150 if deviceid is not None:
151 LOG.debug('Replace URL: \n'
152 'Old URL: %(old_url)s\n'
153 'New URL: %(new_url)s\n',
154 {'old_url': old_url,
155 'new_url': self.url})
156 result = self.do_call(url, data, method)
157 return result
159 def _create_filesystem(self, fs_param):
160 """Create file system."""
161 url = "/filesystem"
162 data = jsonutils.dumps(fs_param)
163 result = self.call(url, data, 'POST')
165 msg = 'Create filesystem error.'
166 self._assert_rest_result(result, msg)
167 self._assert_data_in_result(result, msg)
169 return result['data']['ID']
171 def _assert_rest_result(self, result, err_str):
172 if result['error']['code'] != 0:
173 err_msg = (_('%(err)s\nresult: %(res)s.') % {'err': err_str,
174 'res': result})
175 LOG.error(err_msg)
176 raise exception.InvalidShare(reason=err_msg)
178 def _assert_data_in_result(self, result, msg):
179 if "data" not in result: 179 ↛ 180line 179 didn't jump to line 180 because the condition on line 179 was never true
180 err_msg = (_('%s "data" was not in result.') % msg)
181 LOG.error(err_msg)
182 raise exception.InvalidShare(reason=err_msg)
184 def _get_login_info(self):
185 """Get login IP, username and password from config file."""
186 logininfo = {}
187 filename = self.configuration.manila_huawei_conf_file
188 tree = ET.parse(filename)
189 root = tree.getroot()
190 RestURL = root.findtext('Storage/RestURL')
191 logininfo['RestURL'] = RestURL.strip()
193 # Prefix !$$$ means encoded already.
194 prefix_name = '!$$$'
195 need_encode = False
196 for key in ['UserName', 'UserPassword']:
197 node = root.find('Storage/%s' % key)
198 if node.text.startswith(prefix_name):
199 logininfo[key] = base64.b64decode(
200 (node.text[4:]).encode("latin-1")).decode()
201 else:
202 logininfo[key] = node.text
203 node.text = prefix_name + base64.b64encode(
204 node.text.encode("latin-1")).decode()
205 need_encode = True
206 if need_encode:
207 self._change_file_mode(filename)
208 try:
209 tree.write(filename, 'UTF-8')
210 except Exception as err:
211 err_msg = (_('File write error %s.') % err)
212 LOG.error(err_msg)
213 raise exception.InvalidShare(reason=err_msg)
215 return logininfo
217 def _change_file_mode(self, filepath):
218 try:
219 utils.execute('chmod', '666', filepath, run_as_root=True)
221 except Exception as err:
222 LOG.error('Bad response from change file: %s.', err)
223 raise
225 def create_share(self, share_name, fs_id, share_proto):
226 """Create a share."""
227 share_url_type = self._get_share_url_type(share_proto)
228 share_path = self._get_share_path(share_name)
230 filepath = {}
231 if share_proto == 'NFS':
232 filepath = {
233 "DESCRIPTION": "",
234 "FSID": fs_id,
235 "SHAREPATH": share_path,
236 }
237 elif share_proto == 'CIFS': 237 ↛ 249line 237 didn't jump to line 249 because the condition on line 237 was always true
238 filepath = {
239 "SHAREPATH": share_path,
240 "DESCRIPTION": "",
241 "ABEENABLE": "false",
242 "ENABLENOTIFY": "true",
243 "ENABLEOPLOCK": "true",
244 "NAME": share_name.replace("-", "_"),
245 "FSID": fs_id,
246 "TENANCYID": "0",
247 }
248 else:
249 raise exception.InvalidShare(
250 reason=(_('Invalid NAS protocol supplied: %s.')
251 % share_proto))
253 url = "/" + share_url_type
254 data = jsonutils.dumps(filepath)
256 result = self.call(url, data, "POST")
258 msg = 'Create share error.'
259 self._assert_rest_result(result, msg)
260 self._assert_data_in_result(result, msg)
262 return result['data']['ID']
264 def _delete_share_by_id(self, share_id, share_url_type):
265 """Delete share by share id."""
266 url = "/" + share_url_type + "/" + share_id
268 result = self.call(url, None, "DELETE")
269 self._assert_rest_result(result, 'Delete share error.')
271 def _delete_fs(self, fs_id):
272 """Delete file system."""
273 # Get available file system
274 url = "/filesystem/" + fs_id
276 result = self.call(url, None, "DELETE")
277 self._assert_rest_result(result, 'Delete file system error.')
279 def _get_cifs_service_status(self):
280 url = "/CIFSSERVICE"
281 result = self.call(url, None, "GET")
283 msg = 'Get CIFS service status error.'
284 self._assert_rest_result(result, msg)
285 self._assert_data_in_result(result, msg)
287 return result['data']['RUNNINGSTATUS']
289 def _get_nfs_service_status(self):
290 url = "/NFSSERVICE"
291 result = self.call(url, None, "GET")
293 msg = 'Get NFS service status error.'
294 self._assert_rest_result(result, msg)
295 self._assert_data_in_result(result, msg)
297 service = {}
299 service['RUNNINGSTATUS'] = result['data']['RUNNINGSTATUS']
300 service['SUPPORTV3'] = result['data']['SUPPORTV3']
301 service['SUPPORTV4'] = result['data']['SUPPORTV4']
302 return service
304 def _start_nfs_service_status(self):
305 url = "/NFSSERVICE"
306 nfsserviceinfo = {
307 "NFSV4DOMAIN": "localdomain",
308 "RUNNINGSTATUS": "2",
309 "SUPPORTV3": 'true',
310 "SUPPORTV4": 'true',
311 "TYPE": "16452",
312 }
314 data = jsonutils.dumps(nfsserviceinfo)
315 result = self.call(url, data, "PUT")
317 self._assert_rest_result(result, 'Start NFS service error.')
319 def _start_cifs_service_status(self):
320 url = "/CIFSSERVICE"
321 cifsserviceinfo = {
322 "ENABLENOTIFY": "true",
323 "ENABLEOPLOCK": "true",
324 "ENABLEOPLOCKLEASE": "false",
325 "GUESTENABLE": "false",
326 "OPLOCKTIMEOUT": "35",
327 "RUNNINGSTATUS": "2",
328 "SECURITYMODEL": "3",
329 "SIGNINGENABLE": "false",
330 "SIGNINGREQUIRED": "false",
331 "TYPE": "16453",
332 }
334 data = jsonutils.dumps(cifsserviceinfo)
335 result = self.call(url, data, "PUT")
337 self._assert_rest_result(result, 'Start CIFS service error.')
339 def _find_pool_info(self, pool_name, result):
340 if pool_name is None: 340 ↛ 341line 340 didn't jump to line 341 because the condition on line 340 was never true
341 return
343 poolinfo = {}
344 pool_name = pool_name.strip()
345 for item in result.get('data', []):
346 if pool_name == item['NAME'] and '2' == item['USAGETYPE']:
347 poolinfo['name'] = pool_name
348 poolinfo['ID'] = item['ID']
349 poolinfo['CAPACITY'] = item['USERFREECAPACITY']
350 poolinfo['TOTALCAPACITY'] = item['USERTOTALCAPACITY']
351 poolinfo['CONSUMEDCAPACITY'] = item['USERCONSUMEDCAPACITY']
352 poolinfo['TIER0CAPACITY'] = item['TIER0CAPACITY']
353 poolinfo['TIER1CAPACITY'] = item['TIER1CAPACITY']
354 poolinfo['TIER2CAPACITY'] = item['TIER2CAPACITY']
355 break
357 return poolinfo
359 def _find_all_pool_info(self):
360 url = "/storagepool"
361 result = self.call(url, None, "GET")
363 msg = "Query resource pool error."
364 self._assert_rest_result(result, msg)
365 self._assert_data_in_result(result, msg)
367 return result
369 def _read_xml(self):
370 """Open xml file and parse the content."""
371 filename = self.configuration.manila_huawei_conf_file
372 try:
373 tree = ET.parse(filename)
374 root = tree.getroot()
375 except Exception as err:
376 message = (_('Read Huawei config file(%(filename)s)'
377 ' for Manila error: %(err)s')
378 % {'filename': filename,
379 'err': err})
380 LOG.error(message)
381 raise exception.InvalidInput(reason=message)
382 return root
384 def _remove_access_from_share(self, access_id, share_proto):
385 access_type = self._get_share_client_type(share_proto)
386 url = "/" + access_type + "/" + access_id
387 result = self.call(url, None, "DELETE")
388 self._assert_rest_result(result, 'delete access from share error!')
390 def _get_access_count(self, share_id, share_client_type):
391 url_subfix = ("/" + share_client_type + "/count?"
392 + "filter=PARENTID::" + share_id)
393 url = url_subfix
394 result = self.call(url, None, "GET")
396 msg = "Get access count by share error!"
397 self._assert_rest_result(result, msg)
398 self._assert_data_in_result(result, msg)
400 return int(result['data']['COUNT'])
402 def _get_all_access_from_share(self, share_id, share_proto):
403 """Return a list of all the access IDs of the share"""
404 share_client_type = self._get_share_client_type(share_proto)
405 count = self._get_access_count(share_id, share_client_type)
407 access_ids = []
408 range_begin = 0
409 while count > 0:
410 access_range = self._get_access_from_share_range(share_id,
411 range_begin,
412 share_client_type)
413 for item in access_range:
414 access_ids.append(item['ID'])
415 range_begin += 100
416 count -= 100
418 return access_ids
420 def _get_access_from_share(self, share_id, access_to, share_proto):
421 """Segments to find access for a period of 100."""
422 share_client_type = self._get_share_client_type(share_proto)
423 count = self._get_access_count(share_id, share_client_type)
425 access_id = None
426 range_begin = 0
427 while count > 0:
428 if access_id: 428 ↛ 429line 428 didn't jump to line 429 because the condition on line 428 was never true
429 break
430 access_range = self._get_access_from_share_range(share_id,
431 range_begin,
432 share_client_type)
433 for item in access_range:
434 if item['NAME'] in (access_to, '@' + access_to):
435 access_id = item['ID']
437 range_begin += 100
438 count -= 100
440 return access_id
442 def _get_access_from_share_range(self, share_id,
443 range_begin,
444 share_client_type):
445 range_end = range_begin + 100
446 url = ("/" + share_client_type + "?filter=PARENTID::"
447 + share_id + "&range=[" + str(range_begin)
448 + "-" + str(range_end) + "]")
449 result = self.call(url, None, "GET")
450 self._assert_rest_result(result, 'Get access id by share error!')
451 return result.get('data', [])
453 def _get_level_by_access_id(self, access_id, share_proto):
454 share_client_type = self._get_share_client_type(share_proto)
455 url = "/" + share_client_type + "/" + access_id
456 result = self.call(url, None, "GET")
457 self._assert_rest_result(result, 'Get access information error!')
458 access_info = result.get('data', [])
459 access_level = access_info.get('ACCESSVAL')
460 if not access_level:
461 access_level = access_info.get('PERMISSION')
462 return access_level
464 def _change_access_rest(self, access_id,
465 share_proto, access_level):
466 """Change access level of the share."""
467 if share_proto == 'NFS':
468 self._change_nfs_access_rest(access_id, access_level)
469 elif share_proto == 'CIFS': 469 ↛ 472line 469 didn't jump to line 472 because the condition on line 469 was always true
470 self._change_cifs_access_rest(access_id, access_level)
471 else:
472 raise exception.InvalidInput(
473 reason=(_('Invalid NAS protocol supplied: %s.')
474 % share_proto))
476 def _change_nfs_access_rest(self, access_id, access_level):
477 url = "/NFS_SHARE_AUTH_CLIENT/" + access_id
478 access = {
479 "ACCESSVAL": access_level,
480 "SYNC": "0",
481 "ALLSQUASH": "1",
482 "ROOTSQUASH": "0",
483 }
484 data = jsonutils.dumps(access)
485 result = self.call(url, data, "PUT")
487 msg = 'Change access error.'
488 self._assert_rest_result(result, msg)
490 def _change_cifs_access_rest(self, access_id, access_level):
491 url = "/CIFS_SHARE_AUTH_CLIENT/" + access_id
492 access = {
493 "PERMISSION": access_level,
494 }
495 data = jsonutils.dumps(access)
496 result = self.call(url, data, "PUT")
498 msg = 'Change access error.'
499 self._assert_rest_result(result, msg)
501 def _allow_access_rest(self, share_id, access_to,
502 share_proto, access_level):
503 """Allow access to the share."""
504 if share_proto == 'NFS':
505 self._allow_nfs_access_rest(share_id, access_to, access_level)
506 elif share_proto == 'CIFS': 506 ↛ 509line 506 didn't jump to line 509 because the condition on line 506 was always true
507 self._allow_cifs_access_rest(share_id, access_to, access_level)
508 else:
509 raise exception.InvalidInput(
510 reason=(_('Invalid NAS protocol supplied: %s.')
511 % share_proto))
513 def _allow_nfs_access_rest(self, share_id, access_to, access_level):
514 url = "/NFS_SHARE_AUTH_CLIENT"
515 access = {
516 "TYPE": "16409",
517 "NAME": access_to,
518 "PARENTID": share_id,
519 "ACCESSVAL": access_level,
520 "SYNC": "0",
521 "ALLSQUASH": "1",
522 "ROOTSQUASH": "0",
523 }
524 data = jsonutils.dumps(access)
525 result = self.call(url, data, "POST")
527 msg = 'Allow access error.'
528 self._assert_rest_result(result, msg)
530 def _allow_cifs_access_rest(self, share_id, access_to, access_level):
531 url = "/CIFS_SHARE_AUTH_CLIENT"
532 domain_type = {
533 'local': '2',
534 'ad': '0'
535 }
536 error_msg = 'Allow access error.'
537 access_info = ('Access info (access_to: %(access_to)s, '
538 'access_level: %(access_level)s, share_id: %(id)s)'
539 % {'access_to': access_to,
540 'access_level': access_level,
541 'id': share_id})
543 def send_rest(access_to, domain_type):
544 access = {
545 "NAME": access_to,
546 "PARENTID": share_id,
547 "PERMISSION": access_level,
548 "DOMAINTYPE": domain_type,
549 }
550 data = jsonutils.dumps(access)
551 result = self.call(url, data, "POST")
552 error_code = result['error']['code']
553 if error_code == 0:
554 return True
555 elif error_code != constants.ERROR_USER_OR_GROUP_NOT_EXIST: 555 ↛ 556line 555 didn't jump to line 556 because the condition on line 555 was never true
556 self._assert_rest_result(result, error_msg)
557 return False
559 if '\\' not in access_to:
560 # First, try to add user access.
561 LOG.debug('Try to add user access. %s.', access_info)
562 if send_rest(access_to, domain_type['local']):
563 return
564 # Second, if add user access failed,
565 # try to add group access.
566 LOG.debug('Failed with add user access, '
567 'try to add group access. %s.', access_info)
568 # Group name starts with @.
569 if send_rest('@' + access_to, domain_type['local']): 569 ↛ 583line 569 didn't jump to line 583 because the condition on line 569 was always true
570 return
571 else:
572 LOG.debug('Try to add domain user access. %s.', access_info)
573 if send_rest(access_to, domain_type['ad']):
574 return
575 # If add domain user access failed,
576 # try to add domain group access.
577 LOG.debug('Failed with add domain user access, '
578 'try to add domain group access. %s.', access_info)
579 # Group name starts with @.
580 if send_rest('@' + access_to, domain_type['ad']): 580 ↛ 583line 580 didn't jump to line 583 because the condition on line 580 was always true
581 return
583 raise exception.InvalidShare(reason=error_msg)
585 def _get_share_client_type(self, share_proto):
586 share_client_type = None
587 if share_proto == 'NFS':
588 share_client_type = "NFS_SHARE_AUTH_CLIENT"
589 elif share_proto == 'CIFS':
590 share_client_type = "CIFS_SHARE_AUTH_CLIENT"
591 else:
592 raise exception.InvalidInput(
593 reason=(_('Invalid NAS protocol supplied: %s.')
594 % share_proto))
596 return share_client_type
598 def _check_snapshot_id_exist(self, snapshot_info):
599 """Check the snapshot id exists."""
601 if snapshot_info['error']['code'] == constants.MSG_SNAPSHOT_NOT_FOUND:
602 return False
603 elif snapshot_info['error']['code'] == 0:
604 return True
605 else:
606 err_str = "Check the snapshot id exists error!"
607 err_msg = (_('%(err)s\nresult: %(res)s.') % {'err': err_str,
608 'res': snapshot_info})
609 raise exception.InvalidShareSnapshot(reason=err_msg)
611 def _get_snapshot_by_id(self, snap_id):
612 """Get snapshot by id"""
613 url = "/FSSNAPSHOT/" + snap_id
615 result = self.call(url, None, "GET")
616 return result
618 def _delete_snapshot(self, snap_id):
619 """Deletes snapshot."""
620 url = "/FSSNAPSHOT/%s" % snap_id
621 data = jsonutils.dumps({"TYPE": "48", "ID": snap_id})
622 result = self.call(url, data, "DELETE")
623 self._assert_rest_result(result, 'Delete snapshot error.')
625 def _create_snapshot(self, sharefsid, snapshot_name):
626 """Create a snapshot."""
627 filepath = {
628 "PARENTTYPE": "40",
629 "TYPE": "48",
630 "PARENTID": sharefsid,
631 "NAME": snapshot_name.replace("-", "_"),
632 "DESCRIPTION": "",
633 }
635 url = "/FSSNAPSHOT"
636 data = jsonutils.dumps(filepath)
638 result = self.call(url, data, "POST")
640 msg = 'Create a snapshot error.'
641 self._assert_rest_result(result, msg)
642 self._assert_data_in_result(result, msg)
644 return result['data']['ID']
646 def _get_share_by_name(self, share_name, share_url_type):
647 """Segments to find share for a period of 100."""
648 count = self._get_share_count(share_url_type)
650 share = {}
651 range_begin = 0
652 while True:
653 if count < 0 or share:
654 break
655 share = self._get_share_by_name_range(share_name,
656 range_begin,
657 share_url_type)
658 range_begin += 100
659 count -= 100
661 return share
663 def _get_share_count(self, share_url_type):
664 """Get share count."""
665 url = "/" + share_url_type + "/count"
666 result = self.call(url, None, "GET")
667 self._assert_rest_result(result, 'Get share count error!')
669 return int(result['data']['COUNT'])
671 def _get_share_by_name_range(self, share_name,
672 range_begin, share_url_type):
673 """Get share by share name."""
674 range_end = range_begin + 100
675 url = ("/" + share_url_type + "?range=["
676 + str(range_begin) + "-"
677 + str(range_end) + "]")
678 result = self.call(url, None, "GET")
679 self._assert_rest_result(result, 'Get share by name error!')
681 share_path = self._get_share_path(share_name)
683 share = {}
684 for item in result.get('data', []):
685 if share_path == item['SHAREPATH']:
686 share['ID'] = item['ID']
687 share['FSID'] = item['FSID']
688 break
690 return share
692 def _get_share_url_type(self, share_proto):
693 share_url_type = None
694 if share_proto == 'NFS':
695 share_url_type = "NFSHARE"
696 elif share_proto == 'CIFS':
697 share_url_type = "CIFSHARE"
698 else:
699 raise exception.InvalidInput(
700 reason=(_('Invalid NAS protocol supplied: %s.')
701 % share_proto))
703 return share_url_type
705 def get_fsid_by_name(self, share_name):
706 share_name = share_name.replace("-", "_")
707 url = "/FILESYSTEM?filter=NAME::%s&range=[0-8191]" % share_name
708 result = self.call(url, None, "GET")
709 self._assert_rest_result(result, 'Get filesystem by name error!')
711 for item in result.get('data', []):
712 if share_name == item['NAME']:
713 return item['ID']
715 def _get_fs_info_by_id(self, fsid):
716 url = "/filesystem/%s" % fsid
717 result = self.call(url, None, "GET")
719 msg = "Get filesystem info by id error!"
720 self._assert_rest_result(result, msg)
721 self._assert_data_in_result(result, msg)
723 fs = {}
724 fs['HEALTHSTATUS'] = result['data']['HEALTHSTATUS']
725 fs['RUNNINGSTATUS'] = result['data']['RUNNINGSTATUS']
726 fs['CAPACITY'] = result['data']['CAPACITY']
727 fs['ALLOCTYPE'] = result['data']['ALLOCTYPE']
728 fs['POOLNAME'] = result['data']['PARENTNAME']
729 fs['COMPRESSION'] = result['data']['ENABLECOMPRESSION']
730 fs['DEDUP'] = result['data']['ENABLEDEDUP']
731 fs['SMARTPARTITIONID'] = result['data']['CACHEPARTITIONID']
732 fs['SMARTCACHEID'] = result['data']['SMARTCACHEPARTITIONID']
733 return fs
735 def _get_share_path(self, share_name):
736 share_path = "/" + share_name.replace("-", "_") + "/"
737 return share_path
739 def get_share_name_by_id(self, share_id):
740 share_name = "share_" + share_id
741 return share_name
743 def _get_share_name_by_export_location(self, export_location, share_proto):
744 export_location_split = None
745 share_name = None
746 share_ip = None
747 if export_location:
748 if share_proto == 'NFS':
749 export_location_split = export_location.split(':/')
750 if len(export_location_split) == 2:
751 share_name = export_location_split[1]
752 share_ip = export_location_split[0]
753 elif share_proto == 'CIFS': 753 ↛ 761line 753 didn't jump to line 761 because the condition on line 753 was always true
754 export_location_split = export_location.split('\\')
755 if (len(export_location_split) == 4 and
756 export_location_split[0] == "" and
757 export_location_split[1] == ""):
758 share_ip = export_location_split[2]
759 share_name = export_location_split[3]
761 if share_name is None:
762 raise exception.InvalidInput(
763 reason=(_('No share with export location %s could be found.')
764 % export_location))
766 root = self._read_xml()
767 target_ip = root.findtext('Storage/LogicalPortIP')
769 if target_ip:
770 if share_ip != target_ip.strip():
771 raise exception.InvalidInput(
772 reason=(_('The share IP %s is not configured.')
773 % share_ip))
774 else:
775 raise exception.InvalidInput(
776 reason=(_('The config parameter LogicalPortIP is not set.')))
778 return share_name
780 def _get_snapshot_id(self, fs_id, snap_name):
781 snapshot_id = (fs_id + "@" + "share_snapshot_"
782 + snap_name.replace("-", "_"))
783 return snapshot_id
785 def _change_share_size(self, fsid, new_size):
786 url = "/filesystem/%s" % fsid
788 capacityinfo = {
789 "CAPACITY": new_size,
790 }
792 data = jsonutils.dumps(capacityinfo)
793 result = self.call(url, data, "PUT")
795 msg = "Change a share size error!"
796 self._assert_rest_result(result, msg)
797 self._assert_data_in_result(result, msg)
799 def _change_fs_name(self, fsid, name):
800 url = "/filesystem/%s" % fsid
801 fs_param = {
802 "NAME": name.replace("-", "_"),
803 }
804 data = jsonutils.dumps(fs_param)
805 result = self.call(url, data, "PUT")
807 msg = _("Change filesystem name error.")
808 self._assert_rest_result(result, msg)
810 def _change_extra_specs(self, fsid, extra_specs):
811 url = "/filesystem/%s" % fsid
812 fs_param = {
813 "ENABLEDEDUP": extra_specs['dedupe'],
814 "ENABLECOMPRESSION": extra_specs['compression']
815 }
816 data = jsonutils.dumps(fs_param)
817 result = self.call(url, data, "PUT")
819 msg = _("Change extra_specs error.")
820 self._assert_rest_result(result, msg)
822 def _get_partition_id_by_name(self, name):
823 url = "/cachepartition"
824 result = self.call(url, None, "GET")
825 self._assert_rest_result(result, _('Get partition by name error.'))
827 if "data" in result: 827 ↛ 831line 827 didn't jump to line 831 because the condition on line 827 was always true
828 for item in result['data']:
829 if name == item['NAME']:
830 return item['ID']
831 return None
833 def get_partition_info_by_id(self, partitionid):
834 url = '/cachepartition/' + partitionid
835 result = self.call(url, None, "GET")
836 self._assert_rest_result(result,
837 _('Get partition by partition id error.'))
839 return result['data']
841 def _add_fs_to_partition(self, fs_id, partition_id):
842 url = "/filesystem/associate/cachepartition"
843 data = jsonutils.dumps({"ID": partition_id,
844 "ASSOCIATEOBJTYPE": 40,
845 "ASSOCIATEOBJID": fs_id,
846 "TYPE": 268})
847 result = self.call(url, data, "POST")
849 self._assert_rest_result(result,
850 _('Add filesystem to partition error.'))
852 def _remove_fs_from_partition(self, fs_id, partition_id):
853 url = "/smartPartition/removeFs"
854 data = jsonutils.dumps({"ID": partition_id,
855 "ASSOCIATEOBJTYPE": 40,
856 "ASSOCIATEOBJID": fs_id,
857 "TYPE": 268})
858 result = self.call(url, data, "PUT")
860 self._assert_rest_result(result,
861 _('Remove filesystem from partition error.'))
863 def _rename_share_snapshot(self, snapshot_id, new_name):
864 url = "/FSSNAPSHOT/" + snapshot_id
865 data = jsonutils.dumps({"NAME": new_name})
866 result = self.call(url, data, "PUT")
867 msg = _('Rename share snapshot on array error.')
868 self._assert_rest_result(result, msg)
869 self._assert_data_in_result(result, msg)
871 def _get_cache_id_by_name(self, name):
872 url = "/SMARTCACHEPARTITION"
873 result = self.call(url, None, "GET")
874 self._assert_rest_result(result, _('Get cache by name error.'))
876 if "data" in result: 876 ↛ 880line 876 didn't jump to line 880 because the condition on line 876 was always true
877 for item in result['data']:
878 if name == item['NAME']:
879 return item['ID']
880 return None
882 def get_cache_info_by_id(self, cacheid):
883 url = "/SMARTCACHEPARTITION/" + cacheid
884 data = jsonutils.dumps({"TYPE": "273",
885 "ID": cacheid})
887 result = self.call(url, data, "GET")
888 self._assert_rest_result(
889 result, _('Get smartcache by cache id error.'))
891 return result['data']
893 def _add_fs_to_cache(self, fs_id, cache_id):
894 url = "/SMARTCACHEPARTITION/CREATE_ASSOCIATE"
895 data = jsonutils.dumps({"ID": cache_id,
896 "ASSOCIATEOBJTYPE": 40,
897 "ASSOCIATEOBJID": fs_id,
898 "TYPE": 273})
899 result = self.call(url, data, "PUT")
901 self._assert_rest_result(result, _('Add filesystem to cache error.'))
903 def get_qos(self):
904 url = "/ioclass"
905 result = self.call(url, None, "GET")
906 self._assert_rest_result(result, _('Get QoS information error.'))
907 return result
909 def find_available_qos(self, qos):
910 """"Find available QoS on the array."""
911 qos_id = None
912 fs_list = []
913 temp_qos = copy.deepcopy(qos)
914 result = self.get_qos()
916 if 'data' in result: 916 ↛ 937line 916 didn't jump to line 937 because the condition on line 916 was always true
917 if 'LATENCY' not in temp_qos:
918 temp_qos['LATENCY'] = '0'
919 for item in result['data']:
920 for key in constants.OPTS_QOS_VALUE:
921 if temp_qos.get(key.upper()) != item.get(key.upper()): 921 ↛ 922line 921 didn't jump to line 922 because the condition on line 921 was never true
922 break
923 else:
924 fs_num = len(item['FSLIST'].split(","))
925 # We use this QoS only if the filesystems in it is less
926 # than 64, else we cannot add filesystem to this QoS
927 # any more.
928 if (item['RUNNINGSTATUS'] == constants.STATUS_QOS_ACTIVE
929 and fs_num < constants.MAX_FS_NUM_IN_QOS
930 and item['NAME'].startswith(
931 constants.QOS_NAME_PREFIX)
932 and item['LUNLIST'] == '[""]'):
933 qos_id = item['ID']
934 fs_list = item['FSLIST']
935 break
937 return (qos_id, fs_list)
939 def add_share_to_qos(self, qos_id, fs_id, fs_list):
940 """Add filesystem to QoS."""
941 url = "/ioclass/" + qos_id
942 new_fs_list = []
943 fs_list_string = fs_list[1:-1]
944 for fs_string in fs_list_string.split(","):
945 tmp_fs_id = fs_string[1:-1]
946 if '' != tmp_fs_id and tmp_fs_id != fs_id: 946 ↛ 944line 946 didn't jump to line 944 because the condition on line 946 was always true
947 new_fs_list.append(tmp_fs_id)
949 new_fs_list.append(fs_id)
951 data = jsonutils.dumps({"FSLIST": new_fs_list,
952 "TYPE": 230,
953 "ID": qos_id})
954 result = self.call(url, data, "PUT")
955 msg = _('Associate filesystem to Qos error.')
956 self._assert_rest_result(result, msg)
958 def create_qos_policy(self, qos, fs_id):
959 # Get local time.
960 localtime = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time()))
961 # Package QoS name.
962 qos_name = constants.QOS_NAME_PREFIX + fs_id + '_' + localtime
964 mergedata = {
965 "TYPE": "230",
966 "NAME": qos_name,
967 "FSLIST": ["%s" % fs_id],
968 "CLASSTYPE": "1",
969 "SCHEDULEPOLICY": "2",
970 "SCHEDULESTARTTIME": "1410969600",
971 "STARTTIME": "08:00",
972 "DURATION": "86400",
973 "CYCLESET": "[1,2,3,4,5,6,0]",
974 }
975 mergedata.update(qos)
976 data = jsonutils.dumps(mergedata)
977 url = "/ioclass"
979 result = self.call(url, data, 'POST')
980 self._assert_rest_result(result, _('Create QoS policy error.'))
982 return result['data']['ID']
984 def activate_deactivate_qos(self, qos_id, enablestatus):
985 """Activate or deactivate QoS.
987 enablestatus: true (activate)
988 enablestatus: false (deactivate)
989 """
990 url = "/ioclass/active/" + qos_id
991 data = jsonutils.dumps({
992 "TYPE": 230,
993 "ID": qos_id,
994 "ENABLESTATUS": enablestatus})
995 result = self.call(url, data, "PUT")
996 self._assert_rest_result(
997 result, _('Activate or deactivate QoS error.'))
999 def change_fs_priority_high(self, fs_id):
1000 """Change fs priority to high."""
1001 url = "/filesystem/" + fs_id
1002 data = jsonutils.dumps({"IOPRIORITY": "3"})
1004 result = self.call(url, data, "PUT")
1005 self._assert_rest_result(
1006 result, _('Change filesystem priority error.'))
1008 def delete_qos_policy(self, qos_id):
1009 """Delete a QoS policy."""
1010 url = "/ioclass/" + qos_id
1011 data = jsonutils.dumps({"TYPE": "230",
1012 "ID": qos_id})
1014 result = self.call(url, data, 'DELETE')
1015 self._assert_rest_result(result, _('Delete QoS policy error.'))
1017 def get_qosid_by_fsid(self, fs_id):
1018 """Get QoS id by fs id."""
1019 url = "/filesystem/" + fs_id
1020 result = self.call(url, None, "GET")
1021 self._assert_rest_result(
1022 result, _('Get QoS id by filesystem id error.'))
1024 return result['data'].get('IOCLASSID')
1026 def get_fs_list_in_qos(self, qos_id):
1027 """Get the filesystem list in QoS."""
1028 qos_info = self.get_qos_info(qos_id)
1030 fs_list = []
1031 fs_string = qos_info['FSLIST'][1:-1]
1033 for fs in fs_string.split(","):
1034 fs_id = fs[1:-1]
1035 fs_list.append(fs_id)
1037 return fs_list
1039 def get_qos_info(self, qos_id):
1040 """Get QoS information."""
1041 url = "/ioclass/" + qos_id
1042 result = self.call(url, None, "GET")
1043 self._assert_rest_result(result, _('Get QoS information error.'))
1045 return result['data']
1047 def remove_fs_from_qos(self, fs_id, fs_list, qos_id):
1048 """Remove filesystem from QoS."""
1049 fs_list = [i for i in fs_list if i != fs_id]
1050 url = "/ioclass/" + qos_id
1051 data = jsonutils.dumps({"FSLIST": fs_list,
1052 "TYPE": 230,
1053 "ID": qos_id})
1054 result = self.call(url, data, "PUT")
1056 msg = _('Remove filesystem from QoS error.')
1057 self._assert_rest_result(result, msg)
1059 def _remove_fs_from_cache(self, fs_id, cache_id):
1060 url = "/SMARTCACHEPARTITION/REMOVE_ASSOCIATE"
1061 data = jsonutils.dumps({"ID": cache_id,
1062 "ASSOCIATEOBJTYPE": 40,
1063 "ASSOCIATEOBJID": fs_id,
1064 "TYPE": 273})
1065 result = self.call(url, data, "PUT")
1067 self._assert_rest_result(result,
1068 _('Remove filesystem from cache error.'))
1070 def get_all_eth_port(self):
1071 url = "/ETH_PORT"
1072 result = self.call(url, None, 'GET')
1073 self._assert_rest_result(result, _('Get all eth port error.'))
1075 all_eth = {}
1076 if "data" in result: 1076 ↛ 1079line 1076 didn't jump to line 1079 because the condition on line 1076 was always true
1077 all_eth = result['data']
1079 return all_eth
1081 def get_eth_port_by_id(self, port_id):
1082 url = "/ETH_PORT/" + port_id
1083 result = self.call(url, None, 'GET')
1084 self._assert_rest_result(result, _('Get eth port by id error.'))
1086 if "data" in result: 1086 ↛ 1089line 1086 didn't jump to line 1089 because the condition on line 1086 was always true
1087 return result['data']
1089 return None
1091 def get_all_bond_port(self):
1092 url = "/BOND_PORT"
1093 result = self.call(url, None, 'GET')
1094 self._assert_rest_result(result, _('Get all bond port error.'))
1096 all_bond = {}
1097 if "data" in result: 1097 ↛ 1100line 1097 didn't jump to line 1100 because the condition on line 1097 was always true
1098 all_bond = result['data']
1100 return all_bond
1102 def get_port_id(self, port_name, port_type):
1103 if port_type == constants.PORT_TYPE_ETH:
1104 all_eth = self.get_all_eth_port()
1105 for item in all_eth: 1105 ↛ 1114line 1105 didn't jump to line 1114 because the loop on line 1105 didn't complete
1106 if port_name == item['LOCATION']: 1106 ↛ 1105line 1106 didn't jump to line 1105 because the condition on line 1106 was always true
1107 return item['ID']
1108 elif port_type == constants.PORT_TYPE_BOND:
1109 all_bond = self.get_all_bond_port()
1110 for item in all_bond: 1110 ↛ 1114line 1110 didn't jump to line 1114 because the loop on line 1110 didn't complete
1111 if port_name == item['NAME']: 1111 ↛ 1110line 1111 didn't jump to line 1110 because the condition on line 1111 was always true
1112 return item['ID']
1114 return None
1116 def get_all_vlan(self):
1117 url = "/vlan"
1118 result = self.call(url, None, 'GET')
1119 self._assert_rest_result(result, _('Get all vlan error.'))
1121 all_vlan = {}
1122 if "data" in result:
1123 all_vlan = result['data']
1125 return all_vlan
1127 def get_vlan(self, port_id, vlan_tag):
1128 url = "/vlan"
1129 result = self.call(url, None, 'GET')
1130 self._assert_rest_result(result, _('Get vlan error.'))
1132 vlan_tag = str(vlan_tag)
1133 if "data" in result:
1134 for item in result['data']: 1134 ↛ 1138line 1134 didn't jump to line 1138 because the loop on line 1134 didn't complete
1135 if port_id == item['PORTID'] and vlan_tag == item['TAG']: 1135 ↛ 1134line 1135 didn't jump to line 1134 because the condition on line 1135 was always true
1136 return True, item['ID']
1138 return False, None
1140 def create_vlan(self, port_id, port_type, vlan_tag):
1141 url = "/vlan"
1142 data = jsonutils.dumps({"PORTID": port_id,
1143 "PORTTYPE": port_type,
1144 "TAG": str(vlan_tag),
1145 "TYPE": "280"})
1146 result = self.call(url, data, "POST")
1147 self._assert_rest_result(result, _('Create vlan error.'))
1149 return result['data']['ID']
1151 def check_vlan_exists_by_id(self, vlan_id):
1152 all_vlan = self.get_all_vlan()
1153 return any(vlan['ID'] == vlan_id for vlan in all_vlan)
1155 def delete_vlan(self, vlan_id):
1156 url = "/vlan/" + vlan_id
1157 result = self.call(url, None, 'DELETE')
1158 if result['error']['code'] == constants.ERROR_LOGICAL_PORT_EXIST: 1158 ↛ 1163line 1158 didn't jump to line 1163 because the condition on line 1158 was always true
1159 LOG.warning('Cannot delete vlan because there is '
1160 'a logical port on vlan.')
1161 return
1163 self._assert_rest_result(result, _('Delete vlan error.'))
1165 def get_logical_port(self, home_port_id, ip, subnet):
1166 url = "/LIF"
1167 result = self.call(url, None, 'GET')
1168 self._assert_rest_result(result, _('Get logical port error.'))
1170 if "data" not in result:
1171 return False, None
1173 for item in result['data']: 1173 ↛ 1181line 1173 didn't jump to line 1181 because the loop on line 1173 didn't complete
1174 if (home_port_id == item['HOMEPORTID'] 1174 ↛ 1173line 1174 didn't jump to line 1173 because the condition on line 1174 was always true
1175 and ip == item['IPV4ADDR']
1176 and subnet == item['IPV4MASK']):
1177 if item['OPERATIONALSTATUS'] != 'true': 1177 ↛ 1179line 1177 didn't jump to line 1179 because the condition on line 1177 was always true
1178 self._activate_logical_port(item['ID'])
1179 return True, item['ID']
1181 return False, None
1183 def _activate_logical_port(self, logical_port_id):
1184 url = "/LIF/" + logical_port_id
1185 data = jsonutils.dumps({"OPERATIONALSTATUS": "true"})
1186 result = self.call(url, data, 'PUT')
1187 self._assert_rest_result(result, _('Activate logical port error.'))
1189 def create_logical_port(self, home_port_id, home_port_type, ip, subnet):
1190 url = "/LIF"
1191 info = {
1192 "ADDRESSFAMILY": 0,
1193 "CANFAILOVER": "true",
1194 "HOMEPORTID": home_port_id,
1195 "HOMEPORTTYPE": home_port_type,
1196 "IPV4ADDR": ip,
1197 "IPV4GATEWAY": "",
1198 "IPV4MASK": subnet,
1199 "NAME": ip,
1200 "OPERATIONALSTATUS": "true",
1201 "ROLE": 2,
1202 "SUPPORTPROTOCOL": 3,
1203 "TYPE": "279",
1204 }
1206 data = jsonutils.dumps(info)
1207 result = self.call(url, data, 'POST')
1208 self._assert_rest_result(result, _('Create logical port error.'))
1210 return result['data']['ID']
1212 def check_logical_port_exists_by_id(self, logical_port_id):
1213 all_logical_port = self.get_all_logical_port()
1214 return any(port['ID'] == logical_port_id for port in all_logical_port)
1216 def get_all_logical_port(self):
1217 url = "/LIF"
1218 result = self.call(url, None, 'GET')
1219 self._assert_rest_result(result, _('Get all logical port error.'))
1221 all_logical_port = {}
1222 if "data" in result: 1222 ↛ 1225line 1222 didn't jump to line 1225 because the condition on line 1222 was always true
1223 all_logical_port = result['data']
1225 return all_logical_port
1227 def delete_logical_port(self, logical_port_id):
1228 url = "/LIF/" + logical_port_id
1229 result = self.call(url, None, 'DELETE')
1230 self._assert_rest_result(result, _('Delete logical port error.'))
1232 def set_DNS_ip_address(self, dns_ip_list):
1233 if len(dns_ip_list) > 3:
1234 message = _('Most three ips can be set to DNS.')
1235 LOG.error(message)
1236 raise exception.InvalidInput(reason=message)
1238 url = "/DNS_Server"
1239 dns_info = {
1240 "ADDRESS": jsonutils.dumps(dns_ip_list),
1241 "TYPE": "260",
1242 }
1243 data = jsonutils.dumps(dns_info)
1244 result = self.call(url, data, 'PUT')
1245 self._assert_rest_result(result, _('Set DNS ip address error.'))
1247 if "data" in result: 1247 ↛ 1248line 1247 didn't jump to line 1248 because the condition on line 1247 was never true
1248 return result['data']
1250 return None
1252 def get_DNS_ip_address(self):
1253 url = "/DNS_Server"
1254 result = self.call(url, None, 'GET')
1255 self._assert_rest_result(result, _('Get DNS ip address error.'))
1257 ip_address = {}
1258 if "data" in result: 1258 ↛ 1261line 1258 didn't jump to line 1261 because the condition on line 1258 was always true
1259 ip_address = jsonutils.loads(result['data']['ADDRESS'])
1261 return ip_address
1263 def add_AD_config(self, user, password, domain, system_name):
1264 url = "/AD_CONFIG"
1265 info = {
1266 "ADMINNAME": user,
1267 "ADMINPWD": password,
1268 "DOMAINSTATUS": 1,
1269 "FULLDOMAINNAME": domain,
1270 "OU": "",
1271 "SYSTEMNAME": system_name,
1272 "TYPE": "16414",
1273 }
1274 data = jsonutils.dumps(info)
1275 result = self.call(url, data, 'PUT')
1276 self._assert_rest_result(result, _('Add AD config error.'))
1278 def delete_AD_config(self, user, password):
1279 url = "/AD_CONFIG"
1280 info = {
1281 "ADMINNAME": user,
1282 "ADMINPWD": password,
1283 "DOMAINSTATUS": 0,
1284 "TYPE": "16414",
1285 }
1286 data = jsonutils.dumps(info)
1287 result = self.call(url, data, 'PUT')
1288 self._assert_rest_result(result, _('Delete AD config error.'))
1290 def get_AD_config(self):
1291 url = "/AD_CONFIG"
1292 result = self.call(url, None, 'GET')
1293 self._assert_rest_result(result, _('Get AD config error.'))
1295 if "data" in result: 1295 ↛ 1298line 1295 didn't jump to line 1298 because the condition on line 1295 was always true
1296 return result['data']
1298 return None
1300 def get_AD_domain_name(self):
1301 result = self.get_AD_config()
1302 if result and result['DOMAINSTATUS'] == '1':
1303 return True, result['FULLDOMAINNAME']
1305 return False, None
1307 def add_LDAP_config(self, server, domain):
1308 url = "/LDAP_CONFIG"
1309 info = {
1310 "BASEDN": domain,
1311 "LDAPSERVER": server,
1312 "PORTNUM": 389,
1313 "TRANSFERTYPE": "1",
1314 "TYPE": "16413",
1315 "USERNAME": "",
1316 }
1317 data = jsonutils.dumps(info)
1318 result = self.call(url, data, 'PUT')
1319 self._assert_rest_result(result, _('Add LDAP config error.'))
1321 def delete_LDAP_config(self):
1322 url = "/LDAP_CONFIG"
1323 result = self.call(url, None, 'DELETE')
1324 self._assert_rest_result(result, _('Delete LDAP config error.'))
1326 def get_LDAP_config(self):
1327 url = "/LDAP_CONFIG"
1328 result = self.call(url, None, 'GET')
1329 self._assert_rest_result(result, _('Get LDAP config error.'))
1331 if "data" in result: 1331 ↛ 1334line 1331 didn't jump to line 1334 because the condition on line 1331 was always true
1332 return result['data']
1334 return None
1336 def get_LDAP_domain_server(self):
1337 result = self.get_LDAP_config()
1338 if result and result['LDAPSERVER']:
1339 return True, result['LDAPSERVER']
1341 return False, None
1343 def _get_array_info(self):
1344 url = "/system/"
1345 result = self.call(url, None, "GET")
1346 msg = _('Get array info error.')
1347 self._assert_rest_result(result, msg)
1348 self._assert_data_in_result(result, msg)
1349 return result.get('data')
1351 def find_array_version(self):
1352 info = self._get_array_info()
1353 return info.get('PRODUCTVERSION')
1355 def get_array_wwn(self):
1356 info = self._get_array_info()
1357 return info.get('wwn')
1359 def _get_all_remote_devices(self):
1360 url = "/remote_device"
1361 result = self.call(url, None, "GET")
1362 self._assert_rest_result(result, _('Get all remote devices error.'))
1363 return result.get('data', [])
1365 def get_remote_device_by_wwn(self, wwn):
1366 devices = self._get_all_remote_devices()
1367 for device in devices: 1367 ↛ 1370line 1367 didn't jump to line 1370 because the loop on line 1367 didn't complete
1368 if device.get('WWN') == wwn: 1368 ↛ 1367line 1368 didn't jump to line 1367 because the condition on line 1368 was always true
1369 return device
1370 return {}
1372 def create_replication_pair(self, pair_params):
1373 url = "/REPLICATIONPAIR"
1374 data = jsonutils.dumps(pair_params)
1375 result = self.call(url, data, "POST")
1377 msg = _('Failed to create replication pair for '
1378 '(LOCALRESID: %(lres)s, REMOTEDEVICEID: %(rdev)s, '
1379 'REMOTERESID: %(rres)s).') % {
1380 'lres': pair_params['LOCALRESID'],
1381 'rdev': pair_params['REMOTEDEVICEID'],
1382 'rres': pair_params['REMOTERESID']}
1383 self._assert_rest_result(result, msg)
1384 self._assert_data_in_result(result, msg)
1385 return result['data']
1387 def split_replication_pair(self, pair_id):
1388 url = '/REPLICATIONPAIR/split'
1389 data = jsonutils.dumps({"ID": pair_id, "TYPE": "263"})
1390 result = self.call(url, data, "PUT")
1392 msg = _('Failed to split replication pair %s.') % pair_id
1393 self._assert_rest_result(result, msg)
1395 def switch_replication_pair(self, pair_id):
1396 url = '/REPLICATIONPAIR/switch'
1397 data = jsonutils.dumps({"ID": pair_id, "TYPE": "263"})
1398 result = self.call(url, data, "PUT")
1400 msg = _('Failed to switch replication pair %s.') % pair_id
1401 self._assert_rest_result(result, msg)
1403 def delete_replication_pair(self, pair_id):
1404 url = "/REPLICATIONPAIR/" + pair_id
1405 data = None
1406 result = self.call(url, data, "DELETE")
1408 if (result['error']['code'] ==
1409 constants.ERROR_REPLICATION_PAIR_NOT_EXIST):
1410 LOG.warning('Replication pair %s was not found.',
1411 pair_id)
1412 return
1414 msg = _('Failed to delete replication pair %s.') % pair_id
1415 self._assert_rest_result(result, msg)
1417 def sync_replication_pair(self, pair_id):
1418 url = "/REPLICATIONPAIR/sync"
1419 data = jsonutils.dumps({"ID": pair_id, "TYPE": "263"})
1420 result = self.call(url, data, "PUT")
1422 msg = _('Failed to sync replication pair %s.') % pair_id
1423 self._assert_rest_result(result, msg)
1425 def cancel_pair_secondary_write_lock(self, pair_id):
1426 url = "/REPLICATIONPAIR/CANCEL_SECODARY_WRITE_LOCK"
1427 data = jsonutils.dumps({"ID": pair_id, "TYPE": "263"})
1428 result = self.call(url, data, "PUT")
1430 msg = _('Failed to cancel replication pair %s '
1431 'secondary write lock.') % pair_id
1432 self._assert_rest_result(result, msg)
1434 def set_pair_secondary_write_lock(self, pair_id):
1435 url = "/REPLICATIONPAIR/SET_SECODARY_WRITE_LOCK"
1436 data = jsonutils.dumps({"ID": pair_id, "TYPE": "263"})
1437 result = self.call(url, data, "PUT")
1439 msg = _('Failed to set replication pair %s '
1440 'secondary write lock.') % pair_id
1441 self._assert_rest_result(result, msg)
1443 def get_replication_pair_by_id(self, pair_id):
1444 url = "/REPLICATIONPAIR/" + pair_id
1445 result = self.call(url, None, "GET")
1447 msg = _('Failed to get replication pair %s.') % pair_id
1448 self._assert_rest_result(result, msg)
1449 self._assert_data_in_result(result, msg)
1450 return result.get('data')
1452 def rollback_snapshot(self, snap_id):
1453 url = "/FSSNAPSHOT/ROLLBACK_FSSNAPSHOT"
1454 data = jsonutils.dumps({"ID": snap_id})
1455 result = self.call(url, data, "PUT")
1457 msg = _('Failed to rollback snapshot %s.') % snap_id
1458 self._assert_rest_result(result, msg)