Coverage for manila/share/drivers/dell_emc/plugins/powermax/object_manager.py: 98%
1062 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) 2016 Dell Inc. or its subsidiaries.
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 copy
17import re
19from lxml import builder
20from lxml import etree as ET
21from oslo_concurrency import processutils
22from oslo_log import log
24from manila.common import constants as const
25from manila import exception
26from manila.i18n import _
27from manila.share.drivers.dell_emc.common.enas import connector
28from manila.share.drivers.dell_emc.common.enas import constants
29from manila.share.drivers.dell_emc.common.enas import utils as powermax_utils
30from manila.share.drivers.dell_emc.common.enas import xml_api_parser as parser
31from manila import utils
33LOG = log.getLogger(__name__)
36@powermax_utils.decorate_all_methods(powermax_utils.log_enter_exit,
37 debug_only=True)
38class StorageObjectManager(object):
39 def __init__(self, configuration):
40 self.context = {}
42 self.connectors = {}
43 self.connectors['XML'] = connector.XMLAPIConnector(configuration)
44 self.connectors['SSH'] = connector.SSHConnector(configuration)
46 elt_maker = builder.ElementMaker(nsmap={None: constants.XML_NAMESPACE})
47 xml_parser = parser.XMLAPIParser()
49 obj_types = StorageObject.__subclasses__() # pylint: disable=no-member
50 for item in obj_types:
51 key = item.__name__
52 self.context[key] = eval(key)(self.connectors,
53 elt_maker,
54 xml_parser,
55 self)
57 def getStorageContext(self, type):
58 if type in self.context:
59 return self.context[type]
60 else:
61 message = (_("Invalid storage object type %s.") % type)
62 LOG.error(message)
63 raise exception.EMCPowerMaxXMLAPIError(err=message)
66class StorageObject(object):
67 def __init__(self, conn, elt_maker, xml_parser, manager):
68 self.conn = conn
69 self.elt_maker = elt_maker
70 self.xml_parser = xml_parser
71 self.manager = manager
72 self.xml_retry = False
73 self.ssh_retry_patterns = [
74 (
75 constants.SSH_DEFAULT_RETRY_PATTERN,
76 exception.EMCPowerMaxLockRequiredException()
77 ),
78 ]
80 def _translate_response(self, response):
81 """Translate different status to ok/error status."""
82 if (constants.STATUS_OK == response['maxSeverity'] or
83 constants.STATUS_ERROR == response['maxSeverity']):
84 return
86 old_Severity = response['maxSeverity']
87 if response['maxSeverity'] in (constants.STATUS_DEBUG,
88 constants.STATUS_INFO):
89 response['maxSeverity'] = constants.STATUS_OK
91 LOG.warning("Translated status from %(old)s to %(new)s. "
92 "Message: %(info)s.",
93 {'old': old_Severity,
94 'new': response['maxSeverity'],
95 'info': response})
97 def _response_validation(self, response, error_code):
98 """Validates whether a response includes a certain error code."""
99 msg_codes = self._get_problem_message_codes(response['problems'])
101 for code in msg_codes:
102 if code == error_code:
103 return True
105 return False
107 def _get_problem_message_codes(self, problems):
108 message_codes = []
109 for problem in problems:
110 if 'messageCode' in problem: 110 ↛ 109line 110 didn't jump to line 109 because the condition on line 110 was always true
111 message_codes.append(problem['messageCode'])
113 return message_codes
115 def _get_problem_messages(self, problems):
116 messages = []
117 for problem in problems:
118 if 'message' in problem: 118 ↛ 117line 118 didn't jump to line 117 because the condition on line 118 was always true
119 messages.append(problem['message'])
121 return messages
123 def _get_problem_diags(self, problems):
124 diags = []
126 for problem in problems:
127 if 'Diagnostics' in problem: 127 ↛ 126line 127 didn't jump to line 126 because the condition on line 127 was always true
128 diags.append(problem['Diagnostics'])
130 return diags
132 def _build_query_package(self, body):
133 return self.elt_maker.RequestPacket(
134 self.elt_maker.Request(
135 self.elt_maker.Query(body)
136 )
137 )
139 def _build_task_package(self, body):
140 return self.elt_maker.RequestPacket(
141 self.elt_maker.Request(
142 self.elt_maker.StartTask(body, timeout='300')
143 )
144 )
146 @utils.retry(retry_param=exception.EMCPowerMaxLockRequiredException)
147 def _send_request(self, req):
148 req_xml = constants.XML_HEADER + ET.tostring(req).decode('utf-8')
150 rsp_xml = self.conn['XML'].request(str(req_xml))
152 response = self.xml_parser.parse(rsp_xml)
154 self._translate_response(response)
156 if (response['maxSeverity'] != constants.STATUS_OK and
157 self._response_validation(response,
158 constants.MSG_CODE_RETRY)):
159 raise exception.EMCPowerMaxLockRequiredException
161 return response
163 @utils.retry(retry_param=exception.EMCPowerMaxLockRequiredException)
164 def _execute_cmd(self, cmd, retry_patterns=None, check_exit_code=False):
165 """Execute NAS command via SSH.
167 :param retry_patterns: list of tuples,where each tuple contains a reg
168 expression and an exception.
169 :param check_exit_code: Boolean. Raise
170 processutils.ProcessExecutionError if the command failed to
171 execute and this parameter is set to True.
172 """
173 if retry_patterns is None: 173 ↛ 176line 173 didn't jump to line 176 because the condition on line 173 was always true
174 retry_patterns = self.ssh_retry_patterns
176 try:
177 out, err = self.conn['SSH'].run_ssh(cmd, check_exit_code)
178 except processutils.ProcessExecutionError as e:
179 for pattern in retry_patterns:
180 if re.search(pattern[0], e.stdout):
181 raise pattern[1]
183 raise
185 return out, err
187 def _copy_properties(self, source, target, property_map, deep_copy=True):
188 for prop in property_map:
189 if isinstance(prop, tuple):
190 target_key, src_key = prop
191 else:
192 target_key = src_key = prop
194 if src_key in source:
195 if deep_copy and isinstance(source[src_key], list):
196 target[target_key] = copy.deepcopy(source[src_key])
197 else:
198 target[target_key] = source[src_key]
199 else:
200 target[target_key] = None
202 def _get_mover_id(self, mover_name, is_vdm):
203 if is_vdm:
204 return self.get_context('VDM').get_id(mover_name)
205 else:
206 return self.get_context('Mover').get_id(mover_name,
207 self.xml_retry)
209 def get_context(self, type):
210 return self.manager.getStorageContext(type)
213@powermax_utils.decorate_all_methods(powermax_utils.log_enter_exit,
214 debug_only=True)
215class FileSystem(StorageObject):
216 def __init__(self, conn, elt_maker, xml_parser, manager):
217 super(FileSystem, self).__init__(conn, elt_maker, xml_parser, manager)
218 self.filesystem_map = {}
220 @utils.retry(retry_param=exception.EMCPowerMaxInvalidMoverID)
221 def create(self, name, size, pool_name, mover_name, is_vdm=True):
222 pool_id = self.get_context('StoragePool').get_id(pool_name)
224 mover_id = self._get_mover_id(mover_name, is_vdm)
225 if is_vdm:
226 mover = self.elt_maker.Vdm(vdm=mover_id)
227 else:
228 mover = self.elt_maker.Mover(mover=mover_id)
230 if self.xml_retry:
231 self.xml_retry = False
233 request = self._build_task_package(
234 self.elt_maker.NewFileSystem(
235 mover,
236 self.elt_maker.StoragePool(
237 pool=pool_id,
238 size=str(size),
239 mayContainSlices='true'
240 ),
241 name=name
242 )
243 )
245 response = self._send_request(request)
247 if (self._response_validation(response,
248 constants.MSG_INVALID_MOVER_ID) and
249 not self.xml_retry):
250 self.xml_retry = True
251 raise exception.EMCPowerMaxInvalidMoverID(id=mover_id)
252 elif self._response_validation(
253 response, constants.MSG_FILESYSTEM_EXIST):
254 LOG.warning("File system %s already exists. "
255 "Skip the creation.", name)
256 return
257 elif constants.STATUS_OK != response['maxSeverity']:
258 message = (_("Failed to create file system %(name)s. "
259 "Reason: %(err)s.") %
260 {'name': name, 'err': response['problems']})
261 LOG.error(message)
262 raise exception.EMCPowerMaxXMLAPIError(err=message)
264 def get(self, name):
265 if name not in self.filesystem_map:
266 request = self._build_query_package(
267 self.elt_maker.FileSystemQueryParams(
268 self.elt_maker.AspectSelection(
269 fileSystems='true',
270 fileSystemCapacityInfos='true'
271 ),
272 self.elt_maker.Alias(name=name)
273 )
274 )
276 response = self._send_request(request)
278 if constants.STATUS_OK != response['maxSeverity']:
279 if self._is_filesystem_nonexistent(response):
280 return constants.STATUS_NOT_FOUND, response['problems']
281 else:
282 return response['maxSeverity'], response['problems']
284 if not response['objects']:
285 return constants.STATUS_NOT_FOUND, response['problems']
287 src = response['objects'][0]
288 filesystem = {}
289 property_map = (
290 'name',
291 ('pools_id', 'storagePools'),
292 ('volume_id', 'volume'),
293 ('size', 'volumeSize'),
294 ('id', 'fileSystem'),
295 'type',
296 'dataServicePolicies',
297 )
299 self._copy_properties(src, filesystem, property_map)
301 self.filesystem_map[name] = filesystem
303 return constants.STATUS_OK, self.filesystem_map[name]
305 def delete(self, name):
306 status, out = self.get(name)
307 if constants.STATUS_NOT_FOUND == status:
308 LOG.warning("File system %s not found. Skip the deletion.",
309 name)
310 return
311 elif constants.STATUS_OK != status:
312 message = (_("Failed to get file system by name %(name)s. "
313 "Reason: %(err)s.") %
314 {'name': name, 'err': out})
315 LOG.error(message)
316 raise exception.EMCPowerMaxXMLAPIError(err=message)
318 enas_id = self.filesystem_map[name]['id']
320 request = self._build_task_package(
321 self.elt_maker.DeleteFileSystem(fileSystem=enas_id)
322 )
324 response = self._send_request(request)
326 if constants.STATUS_OK != response['maxSeverity']:
327 message = (_("Failed to delete file system %(name)s. "
328 "Reason: %(err)s.") %
329 {'name': name, 'err': response['problems']})
330 LOG.error(message)
331 raise exception.EMCPowerMaxXMLAPIError(err=message)
333 self.filesystem_map.pop(name)
335 def extend(self, name, pool_name, new_size):
336 status, out = self.get(name)
337 if constants.STATUS_OK != status:
338 message = (_("Failed to get file system by name %(name)s. "
339 "Reason: %(err)s.") %
340 {'name': name, 'err': out})
341 LOG.error(message)
342 raise exception.EMCPowerMaxXMLAPIError(err=message)
344 enas_id = out['id']
345 size = int(out['size'])
346 if new_size < size:
347 message = (_("Failed to extend file system %(name)s because new "
348 "size %(new_size)d is smaller than old size "
349 "%(size)d.") %
350 {'name': name, 'new_size': new_size, 'size': size})
351 LOG.error(message)
352 raise exception.EMCPowerMaxXMLAPIError(err=message)
353 elif new_size == size:
354 return
356 pool_id = self.get_context('StoragePool').get_id(pool_name)
358 request = self._build_task_package(
359 self.elt_maker.ExtendFileSystem(
360 self.elt_maker.StoragePool(
361 pool=pool_id,
362 size=str(new_size - size)
363 ),
364 fileSystem=enas_id,
365 )
366 )
368 response = self._send_request(request)
370 if constants.STATUS_OK != response['maxSeverity']:
371 message = (_("Failed to extend file system %(name)s to new size "
372 "%(new_size)d. Reason: %(err)s.") %
373 {'name': name,
374 'new_size': new_size,
375 'err': response['problems']})
376 LOG.error(message)
377 raise exception.EMCPowerMaxXMLAPIError(err=message)
379 def get_id(self, name):
380 status, out = self.get(name)
381 if constants.STATUS_OK != status:
382 message = (_("Failed to get file system by name %(name)s. "
383 "Reason: %(err)s.") %
384 {'name': name, 'err': out})
385 LOG.error(message)
386 raise exception.EMCPowerMaxXMLAPIError(err=message)
388 return self.filesystem_map[name]['id']
390 def _is_filesystem_nonexistent(self, response):
391 """Translate different status to ok/error status."""
392 msg_codes = self._get_problem_message_codes(response['problems'])
393 diags = self._get_problem_diags(response['problems'])
395 for code, diagnose in zip(msg_codes, diags):
396 if (code == constants.MSG_FILESYSTEM_NOT_FOUND and
397 diagnose.find('File system not found.') != -1):
398 return True
400 return False
402 def create_from_snapshot(self, name, snap_name, source_fs_name, pool_name,
403 mover_name, connect_id):
404 create_fs_cmd = [
405 'env', 'NAS_DB=/nas', '/nas/bin/nas_fs',
406 '-name', name,
407 '-type', 'uxfs',
408 '-create',
409 'samesize=' + source_fs_name,
410 'pool=%s' % pool_name,
411 'storage=SINGLE',
412 'worm=off',
413 '-thin', 'no',
414 '-option', 'slice=y',
415 ]
417 self._execute_cmd(create_fs_cmd)
419 ro_mount_cmd = [
420 'env', 'NAS_DB=/nas', '/nas/bin/server_mount', mover_name,
421 '-option', 'ro',
422 name,
423 '/%s' % name,
424 ]
425 self._execute_cmd(ro_mount_cmd)
427 session_name = name + ':' + snap_name
428 copy_ckpt_cmd = [
429 'env', 'NAS_DB=/nas', '/nas/bin/nas_copy',
430 '-name', session_name[0:63],
431 '-source', '-ckpt', snap_name,
432 '-destination', '-fs', name,
433 '-interconnect',
434 'id=%s' % connect_id,
435 '-overwrite_destination',
436 '-full_copy',
437 ]
439 try:
440 self._execute_cmd(copy_ckpt_cmd, check_exit_code=True)
441 except processutils.ProcessExecutionError as expt:
442 LOG.error("Failed to copy content from snapshot %(snap)s to "
443 "file system %(filesystem)s. Reason: %(err)s.",
444 {'snap': snap_name,
445 'filesystem': name,
446 'err': expt})
448 # When an error happens during nas_copy, we need to continue
449 # deleting the checkpoint of the target file system if it exists.
450 query_fs_cmd = [
451 'env', 'NAS_DB=/nas', '/nas/bin/nas_fs',
452 '-info', name,
453 ]
454 out, err = self._execute_cmd(query_fs_cmd)
455 re_ckpts = r'ckpts\s*=\s*(.*)\s*'
456 m = re.search(re_ckpts, out)
457 if m is not None: 457 ↛ 473line 457 didn't jump to line 473 because the condition on line 457 was always true
458 ckpts = m.group(1)
459 for ckpt in re.split(',', ckpts):
460 umount_ckpt_cmd = [
461 'env', 'NAS_DB=/nas',
462 '/nas/bin/server_umount', mover_name,
463 '-perm', ckpt,
464 ]
465 self._execute_cmd(umount_ckpt_cmd)
466 delete_ckpt_cmd = [
467 'env', 'NAS_DB=/nas', '/nas/bin/nas_fs',
468 '-delete', ckpt,
469 '-Force',
470 ]
471 self._execute_cmd(delete_ckpt_cmd)
473 rw_mount_cmd = [
474 'env', 'NAS_DB=/nas', '/nas/bin/server_mount', mover_name,
475 '-option', 'rw',
476 name,
477 '/%s' % name,
478 ]
479 self._execute_cmd(rw_mount_cmd)
482@powermax_utils.decorate_all_methods(powermax_utils.log_enter_exit,
483 debug_only=True)
484class StoragePool(StorageObject):
485 def __init__(self, conn, elt_maker, xml_parser, manager):
486 super(StoragePool, self).__init__(conn, elt_maker, xml_parser, manager)
487 self.pool_map = {}
489 def get(self, name, force=False):
490 if name not in self.pool_map or force:
491 status, out = self.get_all()
492 if constants.STATUS_OK != status:
493 return status, out
495 if name not in self.pool_map:
496 return constants.STATUS_NOT_FOUND, None
498 return constants.STATUS_OK, self.pool_map[name]
500 def get_all(self):
501 self.pool_map.clear()
503 request = self._build_query_package(
504 self.elt_maker.StoragePoolQueryParams()
505 )
507 response = self._send_request(request)
509 if constants.STATUS_OK != response['maxSeverity']:
510 return response['maxSeverity'], response['problems']
512 if not response['objects']:
513 return constants.STATUS_NOT_FOUND, response['problems']
515 for item in response['objects']:
516 pool = {}
517 property_map = (
518 'name',
519 ('movers_id', 'movers'),
520 ('total_size', 'autoSize'),
521 ('used_size', 'usedSize'),
522 'diskType',
523 'dataServicePolicies',
524 ('id', 'pool'),
525 )
526 self._copy_properties(item, pool, property_map)
527 self.pool_map[item['name']] = pool
529 return constants.STATUS_OK, self.pool_map
531 def get_id(self, name):
532 status, out = self.get(name)
534 if constants.STATUS_OK != status:
535 message = (_("Failed to get storage pool by name %(name)s. "
536 "Reason: %(err)s.") %
537 {'name': name, 'err': out})
538 LOG.error(message)
539 raise exception.EMCPowerMaxXMLAPIError(err=message)
541 return out['id']
544@powermax_utils.decorate_all_methods(powermax_utils.log_enter_exit,
545 debug_only=True)
546class MountPoint(StorageObject):
547 def __init__(self, conn, elt_maker, xml_parser, manager):
548 super(MountPoint, self).__init__(conn, elt_maker, xml_parser, manager)
550 @utils.retry(retry_param=exception.EMCPowerMaxInvalidMoverID)
551 def create(self, mount_path, fs_name, mover_name, is_vdm=True):
552 fs_id = self.get_context('FileSystem').get_id(fs_name)
554 mover_id = self._get_mover_id(mover_name, is_vdm)
556 if self.xml_retry:
557 self.xml_retry = False
559 request = self._build_task_package(
560 self.elt_maker.NewMount(
561 self.elt_maker.MoverOrVdm(
562 mover=mover_id,
563 moverIdIsVdm='true' if is_vdm else 'false',
564 ),
565 fileSystem=fs_id,
566 path=mount_path
567 )
568 )
570 response = self._send_request(request)
572 if (self._response_validation(response,
573 constants.MSG_INVALID_MOVER_ID) and
574 not self.xml_retry):
575 self.xml_retry = True
576 raise exception.EMCPowerMaxInvalidMoverID(id=mover_id)
577 elif self._is_mount_point_already_existent(response):
578 LOG.warning("Mount Point %(mount)s already exists. "
579 "Skip the creation.", {'mount': mount_path})
580 return
581 elif constants.STATUS_OK != response['maxSeverity']:
582 message = (_('Failed to create Mount Point %(mount)s for '
583 'file system %(fs_name)s. Reason: %(err)s.') %
584 {'mount': mount_path,
585 'fs_name': fs_name,
586 'err': response['problems']})
587 LOG.error(message)
588 raise exception.EMCPowerMaxXMLAPIError(err=message)
590 @utils.retry(retry_param=exception.EMCPowerMaxInvalidMoverID)
591 def get(self, mover_name, is_vdm=True):
592 mover_id = self._get_mover_id(mover_name, is_vdm)
594 if self.xml_retry:
595 self.xml_retry = False
597 request = self._build_query_package(
598 self.elt_maker.MountQueryParams(
599 self.elt_maker.MoverOrVdm(
600 mover=mover_id,
601 moverIdIsVdm='true' if is_vdm else 'false'
602 )
603 )
604 )
606 response = self._send_request(request)
608 if (self._response_validation(response,
609 constants.MSG_INVALID_MOVER_ID) and
610 not self.xml_retry):
611 self.xml_retry = True
612 raise exception.EMCPowerMaxInvalidMoverID(id=mover_id)
613 elif constants.STATUS_OK != response['maxSeverity']:
614 return response['maxSeverity'], response['objects']
616 if not response['objects']:
617 return constants.STATUS_NOT_FOUND, None
618 else:
619 return constants.STATUS_OK, response['objects']
621 @utils.retry(retry_param=exception.EMCPowerMaxInvalidMoverID)
622 def delete(self, mount_path, mover_name, is_vdm=True):
623 mover_id = self._get_mover_id(mover_name, is_vdm)
625 if self.xml_retry:
626 self.xml_retry = False
628 request = self._build_task_package(
629 self.elt_maker.DeleteMount(
630 mover=mover_id,
631 moverIdIsVdm='true' if is_vdm else 'false',
632 path=mount_path
633 )
634 )
636 response = self._send_request(request)
638 if (self._response_validation(response,
639 constants.MSG_INVALID_MOVER_ID) and
640 not self.xml_retry):
641 self.xml_retry = True
642 raise exception.EMCPowerMaxInvalidMoverID(id=mover_id)
643 elif self._is_mount_point_nonexistent(response):
644 LOG.warning('Mount point %(mount)s on mover %(mover_name)s '
645 'not found.',
646 {'mount': mount_path, 'mover_name': mover_name})
648 return
649 elif constants.STATUS_OK != response['maxSeverity']:
650 message = (_('Failed to delete mount point %(mount)s on mover '
651 '%(mover_name)s. Reason: %(err)s.') %
652 {'mount': mount_path,
653 'mover_name': mover_name,
654 'err': response})
655 LOG.error(message)
656 raise exception.EMCPowerMaxXMLAPIError(err=message)
658 def _is_mount_point_nonexistent(self, response):
659 """Translate different status to ok/error status."""
660 msg_codes = self._get_problem_message_codes(response['problems'])
661 message = self._get_problem_messages(response['problems'])
663 for code, msg in zip(msg_codes, message):
664 if ((code == constants.MSG_GENERAL_ERROR and msg.find( 664 ↛ 663line 664 didn't jump to line 663 because the condition on line 664 was always true
665 'No such path or invalid operation') != -1) or
666 code == constants.MSG_INVALID_VDM_ID or
667 code == constants.MSG_INVALID_MOVER_ID):
668 return True
670 return False
672 def _is_mount_point_already_existent(self, response):
673 """Translate different status to ok/error status."""
674 msg_codes = self._get_problem_message_codes(response['problems'])
675 message = self._get_problem_messages(response['problems'])
677 for code, msg in zip(msg_codes, message):
678 if ((code == constants.MSG_GENERAL_ERROR and msg.find( 678 ↛ 677line 678 didn't jump to line 677 because the condition on line 678 was always true
679 'Mount already exists') != -1)):
680 return True
682 return False
685@powermax_utils.decorate_all_methods(powermax_utils.log_enter_exit,
686 debug_only=True)
687class Mover(StorageObject):
688 def __init__(self, conn, elt_maker, xml_parser, manager):
689 super(Mover, self).__init__(conn, elt_maker, xml_parser, manager)
690 self.mover_map = {}
691 self.mover_ref_map = {}
693 def get_ref(self, name, force=False):
694 if name not in self.mover_ref_map or force:
695 self.mover_ref_map.clear()
697 request = self._build_query_package(
698 self.elt_maker.MoverQueryParams(
699 self.elt_maker.AspectSelection(movers='true')
700 )
701 )
703 response = self._send_request(request)
705 if constants.STATUS_ERROR == response['maxSeverity']:
706 return response['maxSeverity'], response['problems']
708 for item in response['objects']:
709 mover = {}
710 property_map = ('name', ('id', 'mover'))
711 self._copy_properties(item, mover, property_map)
712 if mover: 712 ↛ 708line 712 didn't jump to line 708 because the condition on line 712 was always true
713 self.mover_ref_map[mover['name']] = mover
715 if (name not in self.mover_ref_map or
716 self.mover_ref_map[name]['id'] == ''):
717 return constants.STATUS_NOT_FOUND, None
719 return constants.STATUS_OK, self.mover_ref_map[name]
721 def get(self, name, force=False):
722 if name not in self.mover_map or force:
723 if name in self.mover_ref_map and not force:
724 mover_id = self.mover_ref_map[name]['id']
725 else:
726 mover_id = self.get_id(name, force)
728 if name in self.mover_map:
729 self.mover_map.pop(name)
731 request = self._build_query_package(
732 self.elt_maker.MoverQueryParams(
733 self.elt_maker.AspectSelection(
734 moverDeduplicationSettings='true',
735 moverDnsDomains='true',
736 moverInterfaces='true',
737 moverNetworkDevices='true',
738 moverNisDomains='true',
739 moverRoutes='true',
740 movers='true',
741 moverStatuses='true'
742 ),
743 mover=mover_id
744 )
745 )
747 response = self._send_request(request)
748 if constants.STATUS_ERROR == response['maxSeverity']:
749 return response['maxSeverity'], response['problems']
751 if not response['objects']:
752 return constants.STATUS_NOT_FOUND, response['problems']
754 mover = {}
755 src = response['objects'][0]
756 property_map = (
757 'name',
758 ('id', 'mover'),
759 ('Status', 'maxSeverity'),
760 'version',
761 'uptime',
762 'role',
763 ('interfaces', 'MoverInterface'),
764 ('devices', 'LogicalNetworkDevice'),
765 ('dns_domain', 'MoverDnsDomain'),
766 )
768 self._copy_properties(src, mover, property_map)
770 internal_devices = []
771 if mover['interfaces']: 771 ↛ 779line 771 didn't jump to line 779 because the condition on line 771 was always true
772 for interface in mover['interfaces']:
773 if self._is_internal_device(interface['device']):
774 internal_devices.append(interface)
776 mover['interfaces'] = [var for var in mover['interfaces'] if
777 var not in internal_devices]
779 self.mover_map[name] = mover
781 return constants.STATUS_OK, self.mover_map[name]
783 def get_id(self, name, force=False):
784 status, mover_ref = self.get_ref(name, force)
785 if constants.STATUS_OK != status:
786 message = (_("Failed to get mover by name %(name)s.") %
787 {'name': name})
788 LOG.error(message)
789 raise exception.EMCPowerMaxXMLAPIError(err=message)
791 return mover_ref['id']
793 def _is_internal_device(self, device):
794 for device_type in ('mge', 'fxg', 'tks', 'fsn'):
795 if device.find(device_type) == 0:
796 return True
797 return False
799 def get_interconnect_id(self, source, destination):
800 header = [
801 'id',
802 'name',
803 'source_server',
804 'destination_system',
805 'destination_server',
806 ]
808 conn_id = None
810 command_nas_cel = [
811 'env', 'NAS_DB=/nas', '/nas/bin/nas_cel',
812 '-interconnect', '-l',
813 ]
814 out, err = self._execute_cmd(command_nas_cel)
816 lines = out.strip().split('\n')
817 for line in lines:
818 if line.strip().split() == header:
819 LOG.info('Found the header of the command '
820 '/nas/bin/nas_cel -interconnect -l.')
821 else:
822 interconn = line.strip().split()
823 if interconn[2] == source and interconn[4] == destination: 823 ↛ 817line 823 didn't jump to line 817 because the condition on line 823 was always true
824 conn_id = interconn[0]
826 return conn_id
828 def get_physical_devices(self, mover_name):
830 physical_network_devices = []
832 cmd_sysconfig = [
833 'env', 'NAS_DB=/nas', '/nas/bin/server_sysconfig', mover_name,
834 '-pci'
835 ]
837 out, err = self._execute_cmd(cmd_sysconfig)
839 re_pattern = (r'0:\s*(?P<name>\S+)\s*IRQ:\s*(?P<irq>\d+)\n'
840 r'.*\n'
841 r'\s*Link:\s*(?P<link>[A-Za-z]+)')
843 for device in re.finditer(re_pattern, out):
844 if 'Up' in device.group('link'):
845 physical_network_devices.append(device.group('name'))
847 return physical_network_devices
850@powermax_utils.decorate_all_methods(powermax_utils.log_enter_exit,
851 debug_only=True)
852class VDM(StorageObject):
853 def __init__(self, conn, elt_maker, xml_parser, manager):
854 super(VDM, self).__init__(conn, elt_maker, xml_parser, manager)
855 self.vdm_map = {}
857 @utils.retry(retry_param=exception.EMCPowerMaxInvalidMoverID)
858 def create(self, name, mover_name):
859 mover_id = self._get_mover_id(mover_name, False)
861 if self.xml_retry:
862 self.xml_retry = False
864 request = self._build_task_package(
865 self.elt_maker.NewVdm(mover=mover_id, name=name)
866 )
868 response = self._send_request(request)
870 if (self._response_validation(response,
871 constants.MSG_INVALID_MOVER_ID) and
872 not self.xml_retry):
873 self.xml_retry = True
874 raise exception.EMCPowerMaxInvalidMoverID(id=mover_id)
875 elif self._response_validation(response, constants.MSG_VDM_EXIST):
876 LOG.warning("VDM %(name)s already exists. Skip the creation.",
877 {'name': name})
878 elif constants.STATUS_OK != response['maxSeverity']:
879 message = (_("Failed to create VDM %(name)s on mover "
880 "%(mover_name)s. Reason: %(err)s.") %
881 {'name': name,
882 'mover_name': mover_name,
883 'err': response['problems']})
884 LOG.error(message)
885 raise exception.EMCPowerMaxXMLAPIError(err=message)
887 def get(self, name):
888 if name not in self.vdm_map:
889 request = self._build_query_package(
890 self.elt_maker.VdmQueryParams()
891 )
893 response = self._send_request(request)
895 if constants.STATUS_OK != response['maxSeverity']:
896 return response['maxSeverity'], response['problems']
897 elif not response['objects']:
898 return constants.STATUS_NOT_FOUND, response['problems']
900 for item in response['objects']:
901 vdm = {}
902 property_map = (
903 'name',
904 ('id', 'vdm'),
905 'state',
906 ('host_mover_id', 'mover'),
907 ('interfaces', 'Interfaces'),
908 )
909 self._copy_properties(item, vdm, property_map)
910 self.vdm_map[item['name']] = vdm
912 if name not in self.vdm_map:
913 return constants.STATUS_NOT_FOUND, None
915 return constants.STATUS_OK, self.vdm_map[name]
917 def delete(self, name):
918 status, out = self.get(name)
919 if constants.STATUS_NOT_FOUND == status:
920 LOG.warning("VDM %s not found. Skip the deletion.",
921 name)
922 return
923 elif constants.STATUS_OK != status:
924 message = (_("Failed to get VDM by name %(name)s. "
925 "Reason: %(err)s.") %
926 {'name': name, 'err': out})
927 LOG.error(message)
928 raise exception.EMCPowerMaxXMLAPIError(err=message)
930 vdm_id = self.vdm_map[name]['id']
932 request = self._build_task_package(
933 self.elt_maker.DeleteVdm(vdm=vdm_id)
934 )
936 response = self._send_request(request)
938 if constants.STATUS_OK != response['maxSeverity']:
939 message = (_("Failed to delete VDM %(name)s. "
940 "Reason: %(err)s.") %
941 {'name': name, 'err': response['problems']})
942 LOG.error(message)
943 raise exception.EMCPowerMaxXMLAPIError(err=message)
945 self.vdm_map.pop(name)
947 def get_id(self, name):
948 status, vdm = self.get(name)
949 if constants.STATUS_OK != status:
950 message = (_("Failed to get VDM by name %(name)s.") %
951 {'name': name})
952 LOG.error(message)
953 raise exception.EMCPowerMaxXMLAPIError(err=message)
955 return vdm['id']
957 def attach_nfs_interface(self, vdm_name, if_name):
959 command_attach_nfs_interface = [
960 'env', 'NAS_DB=/nas', '/nas/bin/nas_server',
961 '-vdm', vdm_name,
962 '-attach', if_name,
963 ]
965 self._execute_cmd(command_attach_nfs_interface)
967 def detach_nfs_interface(self, vdm_name, if_name):
969 command_detach_nfs_interface = [
970 'env', 'NAS_DB=/nas', '/nas/bin/nas_server',
971 '-vdm', vdm_name,
972 '-detach', if_name,
973 ]
975 try:
976 self._execute_cmd(command_detach_nfs_interface,
977 check_exit_code=True)
978 except processutils.ProcessExecutionError:
979 interfaces = self.get_interfaces(vdm_name)
980 if if_name not in interfaces['nfs']:
981 LOG.debug("Failed to detach interface %(interface)s "
982 "from mover %(mover_name)s.",
983 {'interface': if_name, 'mover_name': vdm_name})
984 else:
985 message = (_("Failed to detach interface %(interface)s "
986 "from mover %(mover_name)s.") %
987 {'interface': if_name, 'mover_name': vdm_name})
988 LOG.exception(message)
989 raise exception.EMCPowerMaxXMLAPIError(err=message)
991 def get_interfaces(self, vdm_name):
992 interfaces = {
993 'cifs': [],
994 'nfs': [],
995 }
997 re_pattern = (r'Interfaces to services mapping:'
998 r'\s*(?P<interfaces>(\s*interface=.*)*)')
1000 command_get_interfaces = [
1001 'env', 'NAS_DB=/nas', '/nas/bin/nas_server',
1002 '-i',
1003 '-vdm', vdm_name,
1004 ]
1006 out, err = self._execute_cmd(command_get_interfaces)
1008 m = re.search(re_pattern, out)
1009 if m: 1009 ↛ 1022line 1009 didn't jump to line 1022 because the condition on line 1009 was always true
1010 if_list = m.group('interfaces').split('\n')
1011 for i in if_list:
1012 m_if = re.search(r'\s*interface=(?P<if>.*)\s*:'
1013 r'\s*(?P<type>.*)\s*', i)
1014 if m_if: 1014 ↛ 1011line 1014 didn't jump to line 1011 because the condition on line 1014 was always true
1015 if_name = m_if.group('if').strip()
1016 if 'cifs' == m_if.group('type') and if_name != '':
1017 interfaces['cifs'].append(if_name)
1018 elif (m_if.group('type') in ('vdm', 'nfs') 1018 ↛ 1011line 1018 didn't jump to line 1011 because the condition on line 1018 was always true
1019 and if_name != ''):
1020 interfaces['nfs'].append(if_name)
1022 return interfaces
1025@powermax_utils.decorate_all_methods(powermax_utils.log_enter_exit,
1026 debug_only=True)
1027class Snapshot(StorageObject):
1028 def __init__(self, conn, elt_maker, xml_parser, manager):
1029 super(Snapshot, self).__init__(conn, elt_maker, xml_parser, manager)
1030 self.snap_map = {}
1032 def create(self, name, fs_name, pool_id, ckpt_size=None):
1033 fs_id = self.get_context('FileSystem').get_id(fs_name)
1035 if ckpt_size:
1036 elt_pool = self.elt_maker.StoragePool(
1037 pool=pool_id,
1038 size=str(ckpt_size)
1039 )
1040 else:
1041 elt_pool = self.elt_maker.StoragePool(pool=pool_id)
1043 new_ckpt = self.elt_maker.NewCheckpoint(
1044 self.elt_maker.SpaceAllocationMethod(
1045 elt_pool
1046 ),
1047 checkpointOf=fs_id,
1048 name=name
1049 )
1051 request = self._build_task_package(new_ckpt)
1053 response = self._send_request(request)
1055 if self._response_validation(response, constants.MSG_SNAP_EXIST):
1056 LOG.warning("Snapshot %(name)s already exists. "
1057 "Skip the creation.",
1058 {'name': name})
1059 elif constants.STATUS_OK != response['maxSeverity']:
1060 message = (_("Failed to create snapshot %(name)s on "
1061 "filesystem %(fs_name)s. Reason: %(err)s.") %
1062 {'name': name,
1063 'fs_name': fs_name,
1064 'err': response['problems']})
1065 LOG.error(message)
1066 raise exception.EMCPowerMaxXMLAPIError(err=message)
1068 def get(self, name):
1069 if name not in self.snap_map: 1069 ↛ 1096line 1069 didn't jump to line 1096 because the condition on line 1069 was always true
1070 request = self._build_query_package(
1071 self.elt_maker.CheckpointQueryParams(
1072 self.elt_maker.Alias(name=name)
1073 )
1074 )
1076 response = self._send_request(request)
1078 if constants.STATUS_OK != response['maxSeverity']:
1079 return response['maxSeverity'], response['problems']
1081 if not response['objects']:
1082 return constants.STATUS_NOT_FOUND, response['problems']
1084 src = response['objects'][0]
1085 snap = {}
1086 property_map = (
1087 'name',
1088 ('id', 'checkpoint'),
1089 'checkpointOf',
1090 'state',
1091 )
1092 self._copy_properties(src, snap, property_map)
1094 self.snap_map[name] = snap
1096 return constants.STATUS_OK, self.snap_map[name]
1098 def delete(self, name):
1099 status, out = self.get(name)
1100 if constants.STATUS_NOT_FOUND == status:
1101 LOG.warning("Snapshot %s not found. Skip the deletion.",
1102 name)
1103 return
1104 elif constants.STATUS_OK != status:
1105 message = (_("Failed to get snapshot by name %(name)s. "
1106 "Reason: %(err)s.") %
1107 {'name': name, 'err': out})
1108 LOG.error(message)
1109 raise exception.EMCPowerMaxXMLAPIError(err=message)
1111 chpt_id = self.snap_map[name]['id']
1113 request = self._build_task_package(
1114 self.elt_maker.DeleteCheckpoint(checkpoint=chpt_id)
1115 )
1117 response = self._send_request(request)
1118 if constants.STATUS_OK != response['maxSeverity']:
1119 message = (_("Failed to delete snapshot %(name)s. "
1120 "Reason: %(err)s.") %
1121 {'name': name, 'err': response['problems']})
1122 LOG.error(message)
1123 raise exception.EMCPowerMaxXMLAPIError(err=message)
1125 self.snap_map.pop(name)
1127 def get_id(self, name):
1128 status, out = self.get(name)
1130 if constants.STATUS_OK != status:
1131 message = (_("Failed to get snapshot by %(name)s. "
1132 "Reason: %(err)s.") %
1133 {'name': name, 'err': out})
1134 LOG.error(message)
1135 raise exception.EMCPowerMaxXMLAPIError(err=message)
1137 return self.snap_map[name]['id']
1140@powermax_utils.decorate_all_methods(powermax_utils.log_enter_exit,
1141 debug_only=True)
1142class MoverInterface(StorageObject):
1143 def __init__(self, conn, elt_maker, xml_parser, manager):
1144 super(MoverInterface, self).__init__(conn, elt_maker, xml_parser,
1145 manager)
1147 @utils.retry(retry_param=exception.EMCPowerMaxInvalidMoverID)
1148 def create(self, interface):
1149 # Maximum of 32 characters for mover interface name
1150 name = interface['name']
1151 if len(name) > 32:
1152 name = name[0:31]
1154 device_name = interface['device_name']
1155 ip_addr = interface['ip']
1156 mover_name = interface['mover_name']
1157 net_mask = interface['net_mask']
1158 vlan_id = interface['vlan_id'] if interface['vlan_id'] else -1
1160 mover_id = self._get_mover_id(mover_name, False)
1162 params = dict(device=device_name,
1163 ipAddress=str(ip_addr),
1164 mover=mover_id,
1165 name=name,
1166 netMask=net_mask,
1167 vlanid=str(vlan_id))
1169 if interface.get('ip_version') == 6: 1169 ↛ 1170line 1169 didn't jump to line 1170 because the condition on line 1169 was never true
1170 params['ipVersion'] = 'IPv6'
1172 if self.xml_retry:
1173 self.xml_retry = False
1175 request = self._build_task_package(
1176 self.elt_maker.NewMoverInterface(**params))
1178 response = self._send_request(request)
1180 if (self._response_validation(response,
1181 constants.MSG_INVALID_MOVER_ID) and
1182 not self.xml_retry):
1183 self.xml_retry = True
1184 raise exception.EMCPowerMaxInvalidMoverID(id=mover_id)
1185 elif self._response_validation(
1186 response, constants.MSG_INTERFACE_NAME_EXIST):
1187 LOG.warning("Mover interface name %s already exists. "
1188 "Skip the creation.", name)
1189 elif self._response_validation(
1190 response, constants.MSG_INTERFACE_EXIST):
1191 LOG.warning("Mover interface IP %s already exists. "
1192 "Skip the creation.", ip_addr)
1193 elif self._response_validation(
1194 response, constants.MSG_INTERFACE_INVALID_VLAN_ID):
1195 # When fail to create a mover interface with the specified
1196 # vlan id, PowerMax will leave an interface with vlan id 0 in the
1197 # backend. So we should explicitly remove the interface.
1198 try:
1199 self.delete(str(ip_addr), mover_name)
1200 except exception.EMCPowerMaxXMLAPIError:
1201 pass
1202 message = (_("Invalid vlan id %s. Other interfaces on this "
1203 "subnet are in a different vlan.") % vlan_id)
1204 LOG.error(message)
1205 raise exception.EMCPowerMaxXMLAPIError(err=message)
1206 elif constants.STATUS_OK != response['maxSeverity']:
1207 message = (_("Failed to create mover interface %(interface)s. "
1208 "Reason: %(err)s.") %
1209 {'interface': interface,
1210 'err': response['problems']})
1211 LOG.error(message)
1212 raise exception.EMCPowerMaxXMLAPIError(err=message)
1214 def get(self, name, mover_name):
1215 # Maximum of 32 characters for mover interface name
1216 if len(name) > 32:
1217 name = name[0:31]
1219 status, mover = self.manager.getStorageContext('Mover').get(
1220 mover_name, True)
1221 if constants.STATUS_OK == status:
1222 for interface in mover['interfaces']:
1223 if name == interface['name']:
1224 return constants.STATUS_OK, interface
1226 return constants.STATUS_NOT_FOUND, None
1228 @utils.retry(retry_param=exception.EMCPowerMaxInvalidMoverID)
1229 def delete(self, ip_addr, mover_name):
1230 mover_id = self._get_mover_id(mover_name, False)
1232 if self.xml_retry:
1233 self.xml_retry = False
1235 request = self._build_task_package(
1236 self.elt_maker.DeleteMoverInterface(
1237 ipAddress=str(ip_addr),
1238 mover=mover_id
1239 )
1240 )
1242 response = self._send_request(request)
1244 if (self._response_validation(response,
1245 constants.MSG_INVALID_MOVER_ID) and
1246 not self.xml_retry):
1247 self.xml_retry = True
1248 raise exception.EMCPowerMaxInvalidMoverID(id=mover_id)
1249 elif self._response_validation(
1250 response, constants.MSG_INTERFACE_NON_EXISTENT):
1251 LOG.warning("Mover interface %s not found. "
1252 "Skip the deletion.", ip_addr)
1253 return
1254 elif constants.STATUS_OK != response['maxSeverity']:
1255 message = (_("Failed to delete mover interface %(ip)s on mover "
1256 "%(mover)s. Reason: %(err)s.") %
1257 {'ip': ip_addr,
1258 'mover': mover_name,
1259 'err': response['problems']})
1260 LOG.error(message)
1261 raise exception.EMCPowerMaxXMLAPIError(err=message)
1264@powermax_utils.decorate_all_methods(powermax_utils.log_enter_exit,
1265 debug_only=True)
1266class DNSDomain(StorageObject):
1267 def __init__(self, conn, elt_maker, xml_parser, manager):
1268 super(DNSDomain, self).__init__(conn, elt_maker, xml_parser, manager)
1270 @utils.retry(retry_param=exception.EMCPowerMaxInvalidMoverID)
1271 def create(self, mover_name, name, servers, protocol='udp'):
1272 mover_id = self._get_mover_id(mover_name, False)
1274 if self.xml_retry:
1275 self.xml_retry = False
1277 request = self._build_task_package(
1278 self.elt_maker.NewMoverDnsDomain(
1279 mover=mover_id,
1280 name=name,
1281 servers=servers,
1282 protocol=protocol
1283 )
1284 )
1286 response = self._send_request(request)
1288 if (self._response_validation(response,
1289 constants.MSG_INVALID_MOVER_ID) and
1290 not self.xml_retry):
1291 self.xml_retry = True
1292 raise exception.EMCPowerMaxInvalidMoverID(id=mover_id)
1293 elif constants.STATUS_OK != response['maxSeverity']:
1294 message = (_("Failed to create DNS domain %(name)s. "
1295 "Reason: %(err)s.") %
1296 {'name': name, 'err': response['problems']})
1297 LOG.error(message)
1298 raise exception.EMCPowerMaxXMLAPIError(err=message)
1300 @utils.retry(retry_param=exception.EMCPowerMaxInvalidMoverID)
1301 def delete(self, mover_name, name):
1302 mover_id = self._get_mover_id(mover_name, False)
1304 if self.xml_retry:
1305 self.xml_retry = False
1307 request = self._build_task_package(
1308 self.elt_maker.DeleteMoverDnsDomain(
1309 mover=mover_id,
1310 name=name
1311 )
1312 )
1314 response = self._send_request(request)
1315 if (self._response_validation(response,
1316 constants.MSG_INVALID_MOVER_ID) and
1317 not self.xml_retry):
1318 self.xml_retry = True
1319 raise exception.EMCPowerMaxInvalidMoverID(id=mover_id)
1320 elif constants.STATUS_OK != response['maxSeverity']:
1321 LOG.warning("Failed to delete DNS domain %(name)s. "
1322 "Reason: %(err)s.",
1323 {'name': name, 'err': response['problems']})
1326@powermax_utils.decorate_all_methods(powermax_utils.log_enter_exit,
1327 debug_only=True)
1328class CIFSServer(StorageObject):
1329 def __init__(self, conn, elt_maker, xml_parser, manager):
1330 super(CIFSServer, self).__init__(conn, elt_maker, xml_parser, manager)
1331 self.cifs_server_map = {}
1333 @utils.retry(retry_param=exception.EMCPowerMaxInvalidMoverID)
1334 def create(self, server_args):
1335 compName = server_args['name']
1336 # Maximum of 14 characters for netBIOS name
1337 name = server_args['name'][-14:]
1338 # Maximum of 12 characters for alias name
1339 alias_name = server_args['name'][-12:]
1340 interfaces = server_args['interface_ip']
1341 domain_name = server_args['domain_name']
1342 user_name = server_args['user_name']
1343 password = server_args['password']
1344 mover_name = server_args['mover_name']
1345 is_vdm = server_args['is_vdm']
1347 mover_id = self._get_mover_id(mover_name, is_vdm)
1349 if self.xml_retry:
1350 self.xml_retry = False
1352 alias_name_list = [self.elt_maker.li(alias_name)]
1354 request = self._build_task_package(
1355 self.elt_maker.NewW2KCifsServer(
1356 self.elt_maker.MoverOrVdm(
1357 mover=mover_id,
1358 moverIdIsVdm='true' if server_args['is_vdm'] else 'false'
1359 ),
1360 self.elt_maker.Aliases(*alias_name_list),
1361 self.elt_maker.JoinDomain(userName=user_name,
1362 password=password),
1363 compName=compName,
1364 domain=domain_name,
1365 interfaces=interfaces,
1366 name=name
1367 )
1368 )
1370 response = self._send_request(request)
1372 if (self._response_validation(response,
1373 constants.MSG_INVALID_MOVER_ID) and
1374 not self.xml_retry):
1375 self.xml_retry = True
1376 raise exception.EMCPowerMaxInvalidMoverID(id=mover_id)
1377 if constants.STATUS_OK != response['maxSeverity']:
1378 status, out = self.get(compName, mover_name, is_vdm)
1379 if constants.STATUS_OK == status and out['domainJoined'] == 'true':
1380 return
1381 else:
1382 message = (_("Failed to create CIFS server %(name)s. "
1383 "Reason: %(err)s.") %
1384 {'name': name,
1385 'err': response['problems']})
1386 LOG.error(message)
1387 raise exception.EMCPowerMaxXMLAPIError(err=message)
1389 @utils.retry(retry_param=exception.EMCPowerMaxInvalidMoverID)
1390 def get_all(self, mover_name, is_vdm=True):
1391 mover_id = self._get_mover_id(mover_name, is_vdm)
1393 if self.xml_retry:
1394 self.xml_retry = False
1396 request = self._build_query_package(
1397 self.elt_maker.CifsServerQueryParams(
1398 self.elt_maker.MoverOrVdm(
1399 mover=mover_id,
1400 moverIdIsVdm='true' if is_vdm else 'false'
1401 )
1402 )
1403 )
1405 response = self._send_request(request)
1406 if (self._response_validation(response,
1407 constants.MSG_INVALID_MOVER_ID) and
1408 not self.xml_retry):
1409 self.xml_retry = True
1410 raise exception.EMCPowerMaxInvalidMoverID(id=mover_id)
1411 elif constants.STATUS_OK != response['maxSeverity']:
1412 return response['maxSeverity'], response['objects']
1414 if mover_name in self.cifs_server_map:
1415 self.cifs_server_map.pop(mover_name)
1417 self.cifs_server_map[mover_name] = {}
1419 for item in response['objects']:
1420 self.cifs_server_map[mover_name][item['compName'].lower()] = item
1422 return constants.STATUS_OK, self.cifs_server_map[mover_name]
1424 def get(self, name, mover_name, is_vdm=True, force=False):
1425 # name is compName
1426 name = name.lower()
1428 if (mover_name in self.cifs_server_map and
1429 name in self.cifs_server_map[mover_name]) and not force:
1430 return constants.STATUS_OK, self.cifs_server_map[mover_name][name]
1432 self.get_all(mover_name, is_vdm)
1434 if mover_name in self.cifs_server_map:
1435 for compName, server in self.cifs_server_map[mover_name].items():
1436 if name == compName: 1436 ↛ 1435line 1436 didn't jump to line 1435 because the condition on line 1436 was always true
1437 return constants.STATUS_OK, server
1439 return constants.STATUS_NOT_FOUND, None
1441 @utils.retry(retry_param=exception.EMCPowerMaxInvalidMoverID)
1442 def modify(self, server_args):
1443 """Make CIFS server join or un-join the domain.
1445 :param server_args: Dictionary for CIFS server modification
1446 name: CIFS server name instead of compName
1447 join_domain: True for joining the domain, false for un-joining
1448 user_name: User name under which the domain is joined
1449 password: Password associated with the user name
1450 mover_name: mover or VDM name
1451 is_vdm: Boolean to indicate mover or VDM
1452 :raises exception.EMCPowerMaxXMLAPIError: if modification fails.
1453 """
1454 name = server_args['name']
1455 join_domain = server_args['join_domain']
1456 user_name = server_args['user_name']
1457 password = server_args['password']
1458 mover_name = server_args['mover_name']
1460 if 'is_vdm' in server_args.keys():
1461 is_vdm = server_args['is_vdm']
1462 else:
1463 is_vdm = True
1465 mover_id = self._get_mover_id(mover_name, is_vdm)
1467 if self.xml_retry:
1468 self.xml_retry = False
1470 request = self._build_task_package(
1471 self.elt_maker.ModifyW2KCifsServer(
1472 self.elt_maker.DomainSetting(
1473 joinDomain='true' if join_domain else 'false',
1474 password=password,
1475 userName=user_name,
1476 ),
1477 mover=mover_id,
1478 moverIdIsVdm='true' if is_vdm else 'false',
1479 name=name
1480 )
1481 )
1483 response = self._send_request(request)
1485 if (self._response_validation(response,
1486 constants.MSG_INVALID_MOVER_ID) and
1487 not self.xml_retry):
1488 self.xml_retry = True
1489 raise exception.EMCPowerMaxInvalidMoverID(id=mover_id)
1490 elif self._ignore_modification_error(response, join_domain):
1491 return
1492 elif constants.STATUS_OK != response['maxSeverity']:
1493 message = (_("Failed to modify CIFS server %(name)s. "
1494 "Reason: %(err)s.") %
1495 {'name': name,
1496 'err': response['problems']})
1497 LOG.error(message)
1498 raise exception.EMCPowerMaxXMLAPIError(err=message)
1500 def _ignore_modification_error(self, response, join_domain):
1501 if self._response_validation(response, constants.MSG_JOIN_DOMAIN):
1502 return join_domain
1503 elif self._response_validation(response, constants.MSG_UNJOIN_DOMAIN):
1504 return not join_domain
1506 return False
1508 def delete(self, computer_name, mover_name, is_vdm=True):
1509 try:
1510 status, out = self.get(
1511 computer_name.lower(), mover_name, is_vdm, self.xml_retry)
1512 if constants.STATUS_NOT_FOUND == status:
1513 LOG.warning("CIFS server %(name)s on mover %(mover_name)s "
1514 "not found. Skip the deletion.",
1515 {'name': computer_name, 'mover_name': mover_name})
1516 return
1517 except exception.EMCPowerMaxXMLAPIError:
1518 LOG.warning("CIFS server %(name)s on mover %(mover_name)s "
1519 "not found. Skip the deletion.",
1520 {'name': computer_name, 'mover_name': mover_name})
1521 return
1523 server_name = out['name']
1525 mover_id = self._get_mover_id(mover_name, is_vdm)
1527 request = self._build_task_package(
1528 self.elt_maker.DeleteCifsServer(
1529 mover=mover_id,
1530 moverIdIsVdm='true' if is_vdm else 'false',
1531 name=server_name
1532 )
1533 )
1535 response = self._send_request(request)
1537 if constants.STATUS_OK != response['maxSeverity']:
1538 message = (_("Failed to delete CIFS server %(name)s. "
1539 "Reason: %(err)s.") %
1540 {'name': computer_name, 'err': response['problems']})
1541 LOG.error(message)
1542 raise exception.EMCPowerMaxXMLAPIError(err=message)
1544 self.cifs_server_map[mover_name].pop(computer_name)
1547@powermax_utils.decorate_all_methods(powermax_utils.log_enter_exit,
1548 debug_only=True)
1549class CIFSShare(StorageObject):
1550 def __init__(self, conn, elt_maker, xml_parser, manager):
1551 super(CIFSShare, self).__init__(conn, elt_maker, xml_parser, manager)
1552 self.cifs_share_map = {}
1554 @utils.retry(retry_param=exception.EMCPowerMaxInvalidMoverID)
1555 def create(self, name, server_name, mover_name, is_vdm=True):
1556 mover_id = self._get_mover_id(mover_name, is_vdm)
1558 if self.xml_retry:
1559 self.xml_retry = False
1561 share_path = '/' + name
1563 request = self._build_task_package(
1564 self.elt_maker.NewCifsShare(
1565 self.elt_maker.MoverOrVdm(
1566 mover=mover_id,
1567 moverIdIsVdm='true' if is_vdm else 'false'
1568 ),
1569 self.elt_maker.CifsServers(self.elt_maker.li(server_name)),
1570 name=name,
1571 path=share_path
1572 )
1573 )
1575 response = self._send_request(request)
1577 if (self._response_validation(response,
1578 constants.MSG_INVALID_MOVER_ID) and
1579 not self.xml_retry):
1580 self.xml_retry = True
1581 raise exception.EMCPowerMaxInvalidMoverID(id=mover_id)
1582 elif constants.STATUS_OK != response['maxSeverity']:
1583 message = (_("Failed to create file share %(name)s. "
1584 "Reason: %(err)s.") %
1585 {'name': name, 'err': response['problems']})
1586 LOG.error(message)
1587 raise exception.EMCPowerMaxXMLAPIError(err=message)
1589 def get(self, name):
1590 if name not in self.cifs_share_map:
1591 request = self._build_query_package(
1592 self.elt_maker.CifsShareQueryParams(name=name)
1593 )
1595 response = self._send_request(request)
1597 if constants.STATUS_OK != response['maxSeverity']:
1598 return response['maxSeverity'], response['problems']
1600 if not response['objects']:
1601 return constants.STATUS_NOT_FOUND, None
1603 self.cifs_share_map[name] = response['objects'][0]
1605 return constants.STATUS_OK, self.cifs_share_map[name]
1607 @utils.retry(retry_param=exception.EMCPowerMaxInvalidMoverID)
1608 def delete(self, name, mover_name, is_vdm=True):
1609 status, out = self.get(name)
1610 if constants.STATUS_NOT_FOUND == status:
1611 LOG.warning("CIFS share %s not found. Skip the deletion.",
1612 name)
1613 return
1614 elif constants.STATUS_OK != status:
1615 message = (_("Failed to get CIFS share by name %(name)s. "
1616 "Reason: %(err)s.") %
1617 {'name': name, 'err': out})
1618 LOG.error(message)
1619 raise exception.EMCPowerMaxXMLAPIError(err=message)
1621 mover_id = self._get_mover_id(mover_name, is_vdm)
1623 if self.xml_retry:
1624 self.xml_retry = False
1626 netbios_names = self.cifs_share_map[name]['CifsServers']
1628 request = self._build_task_package(
1629 self.elt_maker.DeleteCifsShare(
1630 self.elt_maker.CifsServers(*map(lambda a: self.elt_maker.li(a),
1631 netbios_names)),
1632 mover=mover_id,
1633 moverIdIsVdm='true' if is_vdm else 'false',
1634 name=name
1635 )
1636 )
1638 response = self._send_request(request)
1640 if (self._response_validation(response,
1641 constants.MSG_INVALID_MOVER_ID) and
1642 not self.xml_retry):
1643 self.xml_retry = True
1644 raise exception.EMCPowerMaxInvalidMoverID(id=mover_id)
1645 elif constants.STATUS_OK != response['maxSeverity']:
1646 message = (_("Failed to delete file system %(name)s. "
1647 "Reason: %(err)s.") %
1648 {'name': name, 'err': response['problems']})
1649 LOG.error(message)
1650 raise exception.EMCPowerMaxXMLAPIError(err=message)
1652 self.cifs_share_map.pop(name)
1654 def disable_share_access(self, share_name, mover_name):
1655 cmd_str = 'sharesd %s set noaccess' % share_name
1656 disable_access = [
1657 'env', 'NAS_DB=/nas', '/nas/bin/.server_config', mover_name,
1658 '-v', "%s" % cmd_str,
1659 ]
1661 try:
1662 self._execute_cmd(disable_access, check_exit_code=True)
1663 except processutils.ProcessExecutionError:
1664 message = (_('Failed to disable the access to CIFS share '
1665 '%(name)s.') %
1666 {'name': share_name})
1667 LOG.exception(message)
1668 raise exception.EMCPowerMaxXMLAPIError(err=message)
1670 def allow_share_access(self, mover_name, share_name, user_name, domain,
1671 access=constants.CIFS_ACL_FULLCONTROL):
1672 account = user_name + "@" + domain
1673 allow_str = ('sharesd %(share_name)s grant %(account)s=%(access)s'
1674 % {'share_name': share_name,
1675 'account': account,
1676 'access': access})
1678 allow_access = [
1679 'env', 'NAS_DB=/nas', '/nas/bin/.server_config', mover_name,
1680 '-v', "%s" % allow_str,
1681 ]
1683 try:
1684 self._execute_cmd(allow_access, check_exit_code=True)
1685 except processutils.ProcessExecutionError as expt:
1686 dup_msg = re.compile(r'ACE for %(domain)s\\%(user)s unchanged' %
1687 {'domain': domain, 'user': user_name}, re.I)
1688 if re.search(dup_msg, expt.stdout):
1689 LOG.warning("Duplicate access control entry, "
1690 "skipping allow...")
1691 else:
1692 message = (_('Failed to allow the access %(access)s to '
1693 'CIFS share %(name)s. Reason: %(err)s.') %
1694 {'access': access, 'name': share_name, 'err': expt})
1695 LOG.error(message)
1696 raise exception.EMCPowerMaxXMLAPIError(err=message)
1698 def deny_share_access(self, mover_name, share_name, user_name, domain,
1699 access=constants.CIFS_ACL_FULLCONTROL):
1700 account = user_name + "@" + domain
1701 revoke_str = ('sharesd %(share_name)s revoke %(account)s=%(access)s'
1702 % {'share_name': share_name,
1703 'account': account,
1704 'access': access})
1706 allow_access = [
1707 'env', 'NAS_DB=/nas', '/nas/bin/.server_config', mover_name,
1708 '-v', "%s" % revoke_str,
1709 ]
1710 try:
1711 self._execute_cmd(allow_access, check_exit_code=True)
1712 except processutils.ProcessExecutionError as expt:
1713 not_found_msg = re.compile(
1714 r'No ACE found for %(domain)s\\%(user)s'
1715 % {'domain': domain, 'user': user_name}, re.I)
1716 user_err_msg = re.compile(
1717 r'Cannot get mapping for %(domain)s\\%(user)s'
1718 % {'domain': domain, 'user': user_name}, re.I)
1720 if re.search(not_found_msg, expt.stdout):
1721 LOG.warning("No access control entry found, "
1722 "skipping deny...")
1723 elif re.search(user_err_msg, expt.stdout):
1724 LOG.warning("User not found on domain, skipping deny...")
1725 else:
1726 message = (_('Failed to deny the access %(access)s to '
1727 'CIFS share %(name)s. Reason: %(err)s.') %
1728 {'access': access, 'name': share_name, 'err': expt})
1729 LOG.exception(message)
1730 raise exception.EMCPowerMaxXMLAPIError(err=message)
1732 def get_share_access(self, mover_name, share_name):
1733 get_str = 'sharesd %s dump' % share_name
1734 get_access = [
1735 'env', 'NAS_DB=/nas', '/nas/bin/.server_config', mover_name,
1736 '-v', "%s" % get_str,
1737 ]
1739 try:
1740 out, err = self._execute_cmd(get_access, check_exit_code=True)
1741 except processutils.ProcessExecutionError:
1742 msg = _('Failed to get access list of CIFS share %s.') % share_name
1743 LOG.exception(msg)
1744 raise exception.EMCPowerMaxXMLAPIError(err=msg)
1746 ret = {}
1747 name_pattern = re.compile(r"Unix user '(.+?)'")
1748 access_pattern = re.compile(r"ALLOWED:(.+?):")
1750 name = None
1751 for line in out.splitlines():
1752 if name is None:
1753 names = name_pattern.findall(line)
1754 if names:
1755 name = names[0].lower()
1756 else:
1757 accesses = access_pattern.findall(line)
1758 if accesses:
1759 ret[name] = accesses[0].lower()
1760 name = None
1761 return ret
1763 def clear_share_access(self, mover_name, share_name, domain,
1764 white_list_users):
1765 existing_users = self.get_share_access(mover_name, share_name)
1766 white_list_users_set = set(user.lower() for user in white_list_users)
1767 users_to_remove = set(existing_users.keys()) - white_list_users_set
1768 for user in users_to_remove:
1769 self.deny_share_access(mover_name, share_name, user, domain,
1770 existing_users[user])
1771 return users_to_remove
1774@powermax_utils.decorate_all_methods(powermax_utils.log_enter_exit,
1775 debug_only=True)
1776class NFSShare(StorageObject):
1777 def __init__(self, conn, elt_maker, xml_parser, manager):
1778 super(NFSShare, self).__init__(conn, elt_maker, xml_parser, manager)
1779 self.nfs_share_map = {}
1781 def create(self, name, mover_name):
1782 share_path = '/' + name
1783 create_nfs_share_cmd = [
1784 'env', 'NAS_DB=/nas', '/nas/bin/server_export', mover_name,
1785 '-option', 'access=-0.0.0.0/0.0.0.0',
1786 share_path,
1787 ]
1789 try:
1790 self._execute_cmd(create_nfs_share_cmd, check_exit_code=True)
1791 except processutils.ProcessExecutionError as expt:
1792 message = (_('Failed to create NFS share %(name)s on mover '
1793 '%(mover_name)s. Reason: %(err)s.') %
1794 {'name': name, 'mover_name': mover_name, 'err': expt})
1795 LOG.exception(message)
1796 raise exception.EMCPowerMaxXMLAPIError(err=message)
1798 def delete(self, name, mover_name):
1799 path = '/' + name
1801 status, out = self.get(name, mover_name)
1802 if constants.STATUS_NOT_FOUND == status:
1803 LOG.warning("NFS share %s not found. Skip the deletion.",
1804 path)
1805 return
1807 delete_nfs_share_cmd = [
1808 'env', 'NAS_DB=/nas', '/nas/bin/server_export', mover_name,
1809 '-unexport',
1810 '-perm',
1811 path,
1812 ]
1814 try:
1815 self._execute_cmd(delete_nfs_share_cmd, check_exit_code=True)
1816 except processutils.ProcessExecutionError as expt:
1817 message = (_('Failed to delete NFS share %(name)s on '
1818 '%(mover_name)s. Reason: %(err)s.') %
1819 {'name': name, 'mover_name': mover_name, 'err': expt})
1820 LOG.exception(message)
1821 raise exception.EMCPowerMaxXMLAPIError(err=message)
1823 self.nfs_share_map.pop(name)
1825 def get(self, name, mover_name, force=False, check_exit_code=False):
1826 if name in self.nfs_share_map and not force:
1827 return constants.STATUS_OK, self.nfs_share_map[name]
1829 path = '/' + name
1831 nfs_share = {
1832 "mover_name": '',
1833 "path": '',
1834 'AccessHosts': [],
1835 'RwHosts': [],
1836 'RoHosts': [],
1837 'RootHosts': [],
1838 'readOnly': '',
1839 }
1841 nfs_query_cmd = [
1842 'env', 'NAS_DB=/nas', '/nas/bin/server_export', mover_name,
1843 '-P', 'nfs',
1844 '-list', path,
1845 ]
1847 try:
1848 out, err = self._execute_cmd(nfs_query_cmd,
1849 check_exit_code=check_exit_code)
1850 except processutils.ProcessExecutionError as expt:
1851 dup_msg = (r'%(mover_name)s : No such file or directory' %
1852 {'mover_name': mover_name})
1853 if re.search(dup_msg, expt.stdout):
1854 LOG.warning("NFS share %s not found.", name)
1855 return constants.STATUS_NOT_FOUND, None
1856 else:
1857 message = (_('Failed to list NFS share %(name)s on '
1858 '%(mover_name)s. Reason: %(err)s.') %
1859 {'name': name,
1860 'mover_name': mover_name,
1861 'err': expt})
1862 LOG.exception(message)
1863 raise exception.EMCPowerMaxXMLAPIError(err=message)
1865 re_exports = r'%s\s*:\s*\nexport\s*(.*)\n' % mover_name
1866 m = re.search(re_exports, out)
1867 if m is not None:
1868 nfs_share['path'] = path
1869 nfs_share['mover_name'] = mover_name
1870 export = m.group(1)
1871 fields = export.split(" ")
1872 for field in fields:
1873 field = field.strip()
1874 if field.startswith('rw='):
1875 nfs_share['RwHosts'] = powermax_utils.parse_ipaddr(
1876 field[3:])
1877 elif field.startswith('access='):
1878 nfs_share['AccessHosts'] = powermax_utils.parse_ipaddr(
1879 field[7:])
1880 elif field.startswith('root='):
1881 nfs_share['RootHosts'] = powermax_utils.parse_ipaddr(
1882 field[5:])
1883 elif field.startswith('ro='):
1884 nfs_share['RoHosts'] = powermax_utils.parse_ipaddr(
1885 field[3:])
1887 self.nfs_share_map[name] = nfs_share
1888 else:
1889 return constants.STATUS_NOT_FOUND, None
1891 return constants.STATUS_OK, self.nfs_share_map[name]
1893 def allow_share_access(self, share_name, host_ip, mover_name,
1894 access_level=const.ACCESS_LEVEL_RW):
1895 @utils.synchronized('emc-shareaccess-' + share_name)
1896 def do_allow_access(share_name, host_ip, mover_name, access_level):
1897 status, share = self.get(share_name, mover_name)
1898 if constants.STATUS_NOT_FOUND == status:
1899 message = (_('NFS share %s not found.') % share_name)
1900 LOG.error(message)
1901 raise exception.EMCPowerMaxXMLAPIError(err=message)
1903 changed = False
1904 rwhosts = share['RwHosts']
1905 rohosts = share['RoHosts']
1907 host_ip = powermax_utils.convert_ipv6_format_if_needed(host_ip)
1909 if access_level == const.ACCESS_LEVEL_RW:
1910 if host_ip not in rwhosts:
1911 rwhosts.append(host_ip)
1912 changed = True
1913 if host_ip in rohosts:
1914 rohosts.remove(host_ip)
1915 changed = True
1916 if access_level == const.ACCESS_LEVEL_RO:
1917 if host_ip not in rohosts: 1917 ↛ 1920line 1917 didn't jump to line 1920 because the condition on line 1917 was always true
1918 rohosts.append(host_ip)
1919 changed = True
1920 if host_ip in rwhosts: 1920 ↛ 1924line 1920 didn't jump to line 1924 because the condition on line 1920 was always true
1921 rwhosts.remove(host_ip)
1922 changed = True
1924 roothosts = share['RootHosts']
1925 if host_ip not in roothosts:
1926 roothosts.append(host_ip)
1927 changed = True
1928 accesshosts = share['AccessHosts']
1929 if host_ip not in accesshosts:
1930 accesshosts.append(host_ip)
1931 changed = True
1933 if not changed:
1934 LOG.debug("%(host)s is already in access list of share "
1935 "%(name)s.", {'host': host_ip, 'name': share_name})
1936 else:
1937 path = '/' + share_name
1938 self._set_share_access(path,
1939 mover_name,
1940 rwhosts,
1941 rohosts,
1942 roothosts,
1943 accesshosts)
1945 # Update self.nfs_share_map
1946 self.get(share_name, mover_name, force=True,
1947 check_exit_code=True)
1949 do_allow_access(share_name, host_ip, mover_name, access_level)
1951 def deny_share_access(self, share_name, host_ip, mover_name):
1953 @utils.synchronized('emc-shareaccess-' + share_name)
1954 def do_deny_access(share_name, host_ip, mover_name):
1955 status, share = self.get(share_name, mover_name)
1956 if constants.STATUS_OK != status:
1957 message = (_('Query nfs share %(path)s failed. '
1958 'Reason %(err)s.') %
1959 {'path': share_name, 'err': share})
1960 LOG.error(message)
1961 raise exception.EMCPowerMaxXMLAPIError(err=message)
1963 changed = False
1964 rwhosts = set(share['RwHosts'])
1965 if host_ip in rwhosts:
1966 rwhosts.remove(host_ip)
1967 changed = True
1968 roothosts = set(share['RootHosts'])
1969 if host_ip in roothosts:
1970 roothosts.remove(host_ip)
1971 changed = True
1972 accesshosts = set(share['AccessHosts'])
1973 if host_ip in accesshosts:
1974 accesshosts.remove(host_ip)
1975 changed = True
1976 rohosts = set(share['RoHosts'])
1977 if host_ip in rohosts:
1978 rohosts.remove(host_ip)
1979 changed = True
1980 if not changed:
1981 LOG.debug("%(host)s is already in access list of share "
1982 "%(name)s.", {'host': host_ip, 'name': share_name})
1983 else:
1984 path = '/' + share_name
1985 self._set_share_access(path,
1986 mover_name,
1987 rwhosts,
1988 rohosts,
1989 roothosts,
1990 accesshosts)
1992 # Update self.nfs_share_map
1993 self.get(share_name, mover_name, force=True,
1994 check_exit_code=True)
1996 do_deny_access(share_name, host_ip, mover_name)
1998 def clear_share_access(self, share_name, mover_name, white_list_hosts):
1999 @utils.synchronized('emc-shareaccess-' + share_name)
2000 def do_clear_access(share_name, mover_name, white_list_hosts):
2001 def hosts_to_remove(orig_list):
2002 if white_list_hosts is None: 2002 ↛ 2003line 2002 didn't jump to line 2003 because the condition on line 2002 was never true
2003 ret = set()
2004 else:
2005 ret = set(white_list_hosts).intersection(set(orig_list))
2006 return ret
2008 status, share = self.get(share_name, mover_name)
2009 if constants.STATUS_OK != status:
2010 message = (_('Query nfs share %(path)s failed. '
2011 'Reason %(err)s.') %
2012 {'path': share_name, 'err': status})
2013 raise exception.EMCPowerMaxXMLAPIError(err=message)
2015 self._set_share_access('/' + share_name,
2016 mover_name,
2017 hosts_to_remove(share['RwHosts']),
2018 hosts_to_remove(share['RoHosts']),
2019 hosts_to_remove(share['RootHosts']),
2020 hosts_to_remove(share['AccessHosts']))
2022 # Update self.nfs_share_map
2023 self.get(share_name, mover_name, force=True,
2024 check_exit_code=True)
2026 do_clear_access(share_name, mover_name, white_list_hosts)
2028 def _set_share_access(self, path, mover_name, rw_hosts, ro_hosts,
2029 root_hosts, access_hosts):
2031 if access_hosts is None: 2031 ↛ 2032line 2031 didn't jump to line 2032 because the condition on line 2031 was never true
2032 access_hosts = set()
2033 try:
2034 access_hosts.remove('-0.0.0.0/0.0.0.0')
2035 except (ValueError, KeyError):
2036 pass
2038 access_str = ('access=%(access)s' % {'access': ':'.join(
2039 list(access_hosts) + ['-0.0.0.0/0.0.0.0'])})
2041 if root_hosts: 2041 ↛ 2043line 2041 didn't jump to line 2043 because the condition on line 2041 was always true
2042 access_str += (',root=%(root)s' % {'root': ':'.join(root_hosts)})
2043 if rw_hosts: 2043 ↛ 2045line 2043 didn't jump to line 2045 because the condition on line 2043 was always true
2044 access_str += ',rw=%(rw)s' % {'rw': ':'.join(rw_hosts)}
2045 if ro_hosts: 2045 ↛ 2047line 2045 didn't jump to line 2047 because the condition on line 2045 was always true
2046 access_str += ',ro=%(ro)s' % {'ro': ':'.join(ro_hosts)}
2047 set_nfs_share_access_cmd = [
2048 'env', 'NAS_DB=/nas', '/nas/bin/server_export', mover_name,
2049 '-ignore',
2050 '-option', access_str,
2051 path,
2052 ]
2054 try:
2055 self._execute_cmd(set_nfs_share_access_cmd, check_exit_code=True)
2056 except processutils.ProcessExecutionError as expt:
2057 message = (_('Failed to set NFS share %(name)s access on '
2058 '%(mover_name)s. Reason: %(err)s.') %
2059 {'name': path[1:],
2060 'mover_name': mover_name,
2061 'err': expt})
2062 LOG.exception(message)
2063 raise exception.EMCPowerMaxXMLAPIError(err=message)