Coverage for manila/share/drivers/dell_emc/plugins/vnx/object_manager.py: 99%
1064 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) 2015 EMC Corporation.
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 enas_utils
30from manila.share.drivers.dell_emc.common.enas import xml_api_parser as parser
31from manila import utils
33LOG = log.getLogger(__name__)
36@enas_utils.decorate_all_methods(enas_utils.log_enter_exit,
37 debug_only=True)
38class StorageObjectManager(object):
39 def __init__(self, configuration):
40 self.context = dict()
42 self.connectors = dict()
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.EMCVnxXMLAPIError(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.EMCVnxLockRequiredException()
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.EMCVnxLockRequiredException)
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.EMCVnxLockRequiredException
161 return response
163 @utils.retry(retry_param=exception.EMCVnxLockRequiredException)
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 property in property_map:
189 if isinstance(property, tuple):
190 target_key, src_key = property
191 else:
192 target_key = src_key = property
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@enas_utils.decorate_all_methods(enas_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 = dict()
220 @utils.retry(retry_param=exception.EMCVnxInvalidMoverID)
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.EMCVnxInvalidMoverID(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.EMCVnxXMLAPIError(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.EMCVnxXMLAPIError(err=message)
318 id = self.filesystem_map[name]['id']
320 request = self._build_task_package(
321 self.elt_maker.DeleteFileSystem(fileSystem=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.EMCVnxXMLAPIError(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.EMCVnxXMLAPIError(err=message)
344 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.EMCVnxXMLAPIError(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=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.EMCVnxXMLAPIError(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.EMCVnxXMLAPIError(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:
442 LOG.exception("Failed to copy content from snapshot %(snap)s "
443 "to file system %(filesystem)s.",
444 {'snap': snap_name,
445 'filesystem': name})
447 # When an error happens during nas_copy, we need to continue
448 # deleting the checkpoint of the target file system if it exists.
449 query_fs_cmd = [
450 'env', 'NAS_DB=/nas', '/nas/bin/nas_fs',
451 '-info', name,
452 ]
453 out, err = self._execute_cmd(query_fs_cmd)
454 re_ckpts = r'ckpts\s*=\s*(.*)\s*'
455 m = re.search(re_ckpts, out)
456 if m is not None: 456 ↛ 472line 456 didn't jump to line 472 because the condition on line 456 was always true
457 ckpts = m.group(1)
458 for ckpt in re.split(',', ckpts):
459 umount_ckpt_cmd = [
460 'env', 'NAS_DB=/nas',
461 '/nas/bin/server_umount', mover_name,
462 '-perm', ckpt,
463 ]
464 self._execute_cmd(umount_ckpt_cmd)
465 delete_ckpt_cmd = [
466 'env', 'NAS_DB=/nas', '/nas/bin/nas_fs',
467 '-delete', ckpt,
468 '-Force',
469 ]
470 self._execute_cmd(delete_ckpt_cmd)
472 rw_mount_cmd = [
473 'env', 'NAS_DB=/nas', '/nas/bin/server_mount', mover_name,
474 '-option', 'rw',
475 name,
476 '/%s' % name,
477 ]
478 self._execute_cmd(rw_mount_cmd)
481@enas_utils.decorate_all_methods(enas_utils.log_enter_exit,
482 debug_only=True)
483class StoragePool(StorageObject):
484 def __init__(self, conn, elt_maker, xml_parser, manager):
485 super(StoragePool, self).__init__(conn, elt_maker, xml_parser, manager)
486 self.pool_map = dict()
488 def get(self, name, force=False):
489 if name not in self.pool_map or force:
490 status, out = self.get_all()
491 if constants.STATUS_OK != status:
492 return status, out
494 if name not in self.pool_map:
495 return constants.STATUS_NOT_FOUND, None
497 return constants.STATUS_OK, self.pool_map[name]
499 def get_all(self):
500 self.pool_map.clear()
502 request = self._build_query_package(
503 self.elt_maker.StoragePoolQueryParams()
504 )
506 response = self._send_request(request)
508 if constants.STATUS_OK != response['maxSeverity']:
509 return response['maxSeverity'], response['problems']
511 if not response['objects']:
512 return constants.STATUS_NOT_FOUND, response['problems']
514 for item in response['objects']:
515 pool = {}
516 property_map = (
517 'name',
518 ('movers_id', 'movers'),
519 ('total_size', 'autoSize'),
520 ('used_size', 'usedSize'),
521 'diskType',
522 'dataServicePolicies',
523 ('id', 'pool'),
524 )
525 self._copy_properties(item, pool, property_map)
526 self.pool_map[item['name']] = pool
528 return constants.STATUS_OK, self.pool_map
530 def get_id(self, name):
531 status, out = self.get(name)
533 if constants.STATUS_OK != status:
534 message = (_("Failed to get storage pool by name %(name)s. "
535 "Reason: %(err)s.") %
536 {'name': name, 'err': out})
537 LOG.error(message)
538 raise exception.EMCVnxXMLAPIError(err=message)
540 return out['id']
543@enas_utils.decorate_all_methods(enas_utils.log_enter_exit,
544 debug_only=True)
545class MountPoint(StorageObject):
546 def __init__(self, conn, elt_maker, xml_parser, manager):
547 super(MountPoint, self).__init__(conn, elt_maker, xml_parser, manager)
549 @utils.retry(retry_param=exception.EMCVnxInvalidMoverID)
550 def create(self, mount_path, fs_name, mover_name, is_vdm=True):
551 fs_id = self.get_context('FileSystem').get_id(fs_name)
553 mover_id = self._get_mover_id(mover_name, is_vdm)
555 if self.xml_retry:
556 self.xml_retry = False
558 request = self._build_task_package(
559 self.elt_maker.NewMount(
560 self.elt_maker.MoverOrVdm(
561 mover=mover_id,
562 moverIdIsVdm='true' if is_vdm else 'false',
563 ),
564 fileSystem=fs_id,
565 path=mount_path
566 )
567 )
569 response = self._send_request(request)
571 if (self._response_validation(response,
572 constants.MSG_INVALID_MOVER_ID) and
573 not self.xml_retry):
574 self.xml_retry = True
575 raise exception.EMCVnxInvalidMoverID(id=mover_id)
576 elif self._is_mount_point_already_existent(response):
577 LOG.warning("Mount Point %(mount)s already exists. "
578 "Skip the creation.", {'mount': mount_path})
579 return
580 elif constants.STATUS_OK != response['maxSeverity']:
581 message = (_('Failed to create Mount Point %(mount)s for '
582 'file system %(fs_name)s. Reason: %(err)s.') %
583 {'mount': mount_path,
584 'fs_name': fs_name,
585 'err': response['problems']})
586 LOG.error(message)
587 raise exception.EMCVnxXMLAPIError(err=message)
589 @utils.retry(retry_param=exception.EMCVnxInvalidMoverID)
590 def get(self, mover_name, is_vdm=True):
591 mover_id = self._get_mover_id(mover_name, is_vdm)
593 if self.xml_retry:
594 self.xml_retry = False
596 request = self._build_query_package(
597 self.elt_maker.MountQueryParams(
598 self.elt_maker.MoverOrVdm(
599 mover=mover_id,
600 moverIdIsVdm='true' if is_vdm else 'false'
601 )
602 )
603 )
605 response = self._send_request(request)
607 if (self._response_validation(response,
608 constants.MSG_INVALID_MOVER_ID) and
609 not self.xml_retry):
610 self.xml_retry = True
611 raise exception.EMCVnxInvalidMoverID(id=mover_id)
612 elif constants.STATUS_OK != response['maxSeverity']:
613 return response['maxSeverity'], response['objects']
615 if not response['objects']:
616 return constants.STATUS_NOT_FOUND, None
617 else:
618 return constants.STATUS_OK, response['objects']
620 @utils.retry(retry_param=exception.EMCVnxInvalidMoverID)
621 def delete(self, mount_path, mover_name, is_vdm=True):
622 mover_id = self._get_mover_id(mover_name, is_vdm)
624 if self.xml_retry:
625 self.xml_retry = False
627 request = self._build_task_package(
628 self.elt_maker.DeleteMount(
629 mover=mover_id,
630 moverIdIsVdm='true' if is_vdm else 'false',
631 path=mount_path
632 )
633 )
635 response = self._send_request(request)
637 if (self._response_validation(response,
638 constants.MSG_INVALID_MOVER_ID) and
639 not self.xml_retry):
640 self.xml_retry = True
641 raise exception.EMCVnxInvalidMoverID(id=mover_id)
642 elif self._is_mount_point_nonexistent(response):
643 LOG.warning('Mount point %(mount)s on mover %(mover_name)s '
644 'not found.',
645 {'mount': mount_path, 'mover_name': mover_name})
647 return
648 elif constants.STATUS_OK != response['maxSeverity']:
649 message = (_('Failed to delete mount point %(mount)s on mover '
650 '%(mover_name)s. Reason: %(err)s.') %
651 {'mount': mount_path,
652 'mover_name': mover_name,
653 'err': response})
654 LOG.error(message)
655 raise exception.EMCVnxXMLAPIError(err=message)
657 def _is_mount_point_nonexistent(self, response):
658 """Translate different status to ok/error status."""
659 msg_codes = self._get_problem_message_codes(response['problems'])
660 message = self._get_problem_messages(response['problems'])
662 for code, msg in zip(msg_codes, message):
663 if ((code == constants.MSG_GENERAL_ERROR and msg.find( 663 ↛ 662line 663 didn't jump to line 662 because the condition on line 663 was always true
664 'No such path or invalid operation') != -1) or
665 code == constants.MSG_INVALID_VDM_ID or
666 code == constants.MSG_INVALID_MOVER_ID):
667 return True
669 return False
671 def _is_mount_point_already_existent(self, response):
672 """Translate different status to ok/error status."""
673 msg_codes = self._get_problem_message_codes(response['problems'])
674 message = self._get_problem_messages(response['problems'])
676 for code, msg in zip(msg_codes, message):
677 if ((code == constants.MSG_GENERAL_ERROR and msg.find( 677 ↛ 676line 677 didn't jump to line 676 because the condition on line 677 was always true
678 'Mount already exists') != -1)):
679 return True
681 return False
684@enas_utils.decorate_all_methods(enas_utils.log_enter_exit,
685 debug_only=True)
686class Mover(StorageObject):
687 def __init__(self, conn, elt_maker, xml_parser, manager):
688 super(Mover, self).__init__(conn, elt_maker, xml_parser, manager)
689 self.mover_map = dict()
690 self.mover_ref_map = dict()
692 def get_ref(self, name, force=False):
693 if name not in self.mover_ref_map or force:
694 self.mover_ref_map.clear()
696 request = self._build_query_package(
697 self.elt_maker.MoverQueryParams(
698 self.elt_maker.AspectSelection(movers='true')
699 )
700 )
702 response = self._send_request(request)
704 if constants.STATUS_ERROR == response['maxSeverity']:
705 return response['maxSeverity'], response['problems']
707 for item in response['objects']:
708 mover = {}
709 property_map = ('name', ('id', 'mover'))
710 self._copy_properties(item, mover, property_map)
711 if mover: 711 ↛ 707line 711 didn't jump to line 707 because the condition on line 711 was always true
712 self.mover_ref_map[mover['name']] = mover
714 if (name not in self.mover_ref_map or
715 self.mover_ref_map[name]['id'] == ''):
716 return constants.STATUS_NOT_FOUND, None
718 return constants.STATUS_OK, self.mover_ref_map[name]
720 def get(self, name, force=False):
721 if name not in self.mover_map or force:
722 if name in self.mover_ref_map and not force:
723 mover_id = self.mover_ref_map[name]['id']
724 else:
725 mover_id = self.get_id(name, force)
727 if name in self.mover_map:
728 self.mover_map.pop(name)
730 request = self._build_query_package(
731 self.elt_maker.MoverQueryParams(
732 self.elt_maker.AspectSelection(
733 moverDeduplicationSettings='true',
734 moverDnsDomains='true',
735 moverInterfaces='true',
736 moverNetworkDevices='true',
737 moverNisDomains='true',
738 moverRoutes='true',
739 movers='true',
740 moverStatuses='true'
741 ),
742 mover=mover_id
743 )
744 )
746 response = self._send_request(request)
747 if constants.STATUS_ERROR == response['maxSeverity']:
748 return response['maxSeverity'], response['problems']
750 if not response['objects']:
751 return constants.STATUS_NOT_FOUND, response['problems']
753 mover = {}
754 src = response['objects'][0]
755 property_map = (
756 'name',
757 ('id', 'mover'),
758 ('Status', 'maxSeverity'),
759 'version',
760 'uptime',
761 'role',
762 ('interfaces', 'MoverInterface'),
763 ('devices', 'LogicalNetworkDevice'),
764 ('dns_domain', 'MoverDnsDomain'),
765 )
767 self._copy_properties(src, mover, property_map)
769 internal_devices = []
770 if mover['interfaces']: 770 ↛ 778line 770 didn't jump to line 778 because the condition on line 770 was always true
771 for interface in mover['interfaces']:
772 if self._is_internal_device(interface['device']):
773 internal_devices.append(interface)
775 mover['interfaces'] = [var for var in mover['interfaces'] if
776 var not in internal_devices]
778 self.mover_map[name] = mover
780 return constants.STATUS_OK, self.mover_map[name]
782 def get_id(self, name, force=False):
783 status, mover_ref = self.get_ref(name, force)
784 if constants.STATUS_OK != status:
785 message = (_("Failed to get mover by name %(name)s.") %
786 {'name': name})
787 LOG.error(message)
788 raise exception.EMCVnxXMLAPIError(err=message)
790 return mover_ref['id']
792 def _is_internal_device(self, device):
793 for device_type in ('mge', 'fxg', 'tks', 'fsn'):
794 if device.find(device_type) == 0:
795 return True
796 return False
798 def get_interconnect_id(self, source, destination):
799 header = [
800 'id',
801 'name',
802 'source_server',
803 'destination_system',
804 'destination_server',
805 ]
807 conn_id = None
809 command_nas_cel = [
810 'env', 'NAS_DB=/nas', '/nas/bin/nas_cel',
811 '-interconnect', '-l',
812 ]
813 out, err = self._execute_cmd(command_nas_cel)
815 lines = out.strip().split('\n')
816 for line in lines:
817 if line.strip().split() == header:
818 LOG.info('Found the header of the command '
819 '/nas/bin/nas_cel -interconnect -l.')
820 else:
821 interconn = line.strip().split()
822 if interconn[2] == source and interconn[4] == destination: 822 ↛ 816line 822 didn't jump to line 816 because the condition on line 822 was always true
823 conn_id = interconn[0]
825 return conn_id
827 def get_physical_devices(self, mover_name):
829 physical_network_devices = []
831 cmd_sysconfig = [
832 'env', 'NAS_DB=/nas', '/nas/bin/server_sysconfig', mover_name,
833 '-pci'
834 ]
836 out, err = self._execute_cmd(cmd_sysconfig)
838 re_pattern = (r'0:\s*(?P<name>\S+)\s*IRQ:\s*(?P<irq>\d+)\n'
839 r'.*\n'
840 r'\s*Link:\s*(?P<link>[A-Za-z]+)')
842 for device in re.finditer(re_pattern, out):
843 if 'Up' in device.group('link'):
844 physical_network_devices.append(device.group('name'))
846 return physical_network_devices
849@enas_utils.decorate_all_methods(enas_utils.log_enter_exit,
850 debug_only=True)
851class VDM(StorageObject):
852 def __init__(self, conn, elt_maker, xml_parser, manager):
853 super(VDM, self).__init__(conn, elt_maker, xml_parser, manager)
854 self.vdm_map = dict()
856 @utils.retry(retry_param=exception.EMCVnxInvalidMoverID)
857 def create(self, name, mover_name):
858 mover_id = self._get_mover_id(mover_name, False)
860 if self.xml_retry:
861 self.xml_retry = False
863 request = self._build_task_package(
864 self.elt_maker.NewVdm(mover=mover_id, name=name)
865 )
867 response = self._send_request(request)
869 if (self._response_validation(response,
870 constants.MSG_INVALID_MOVER_ID) and
871 not self.xml_retry):
872 self.xml_retry = True
873 raise exception.EMCVnxInvalidMoverID(id=mover_id)
874 elif self._response_validation(response, constants.MSG_VDM_EXIST):
875 LOG.warning("VDM %(name)s already exists. Skip the creation.",
876 {'name': name})
877 elif constants.STATUS_OK != response['maxSeverity']:
878 message = (_("Failed to create VDM %(name)s on mover "
879 "%(mover_name)s. Reason: %(err)s.") %
880 {'name': name,
881 'mover_name': mover_name,
882 'err': response['problems']})
883 LOG.error(message)
884 raise exception.EMCVnxXMLAPIError(err=message)
886 def get(self, name):
887 if name not in self.vdm_map:
888 request = self._build_query_package(
889 self.elt_maker.VdmQueryParams()
890 )
892 response = self._send_request(request)
894 if constants.STATUS_OK != response['maxSeverity']:
895 return response['maxSeverity'], response['problems']
896 elif not response['objects']:
897 return constants.STATUS_NOT_FOUND, response['problems']
899 for item in response['objects']:
900 vdm = {}
901 property_map = (
902 'name',
903 ('id', 'vdm'),
904 'state',
905 ('host_mover_id', 'mover'),
906 ('interfaces', 'Interfaces'),
907 )
908 self._copy_properties(item, vdm, property_map)
909 self.vdm_map[item['name']] = vdm
911 if name not in self.vdm_map:
912 return constants.STATUS_NOT_FOUND, None
914 return constants.STATUS_OK, self.vdm_map[name]
916 def delete(self, name):
917 status, out = self.get(name)
918 if constants.STATUS_NOT_FOUND == status:
919 LOG.warning("VDM %s not found. Skip the deletion.",
920 name)
921 return
922 elif constants.STATUS_OK != status:
923 message = (_("Failed to get VDM by name %(name)s. "
924 "Reason: %(err)s.") %
925 {'name': name, 'err': out})
926 LOG.error(message)
927 raise exception.EMCVnxXMLAPIError(err=message)
929 vdm_id = self.vdm_map[name]['id']
931 request = self._build_task_package(
932 self.elt_maker.DeleteVdm(vdm=vdm_id)
933 )
935 response = self._send_request(request)
937 if constants.STATUS_OK != response['maxSeverity']:
938 message = (_("Failed to delete VDM %(name)s. "
939 "Reason: %(err)s.") %
940 {'name': name, 'err': response['problems']})
941 LOG.error(message)
942 raise exception.EMCVnxXMLAPIError(err=message)
944 self.vdm_map.pop(name)
946 def get_id(self, name):
947 status, vdm = self.get(name)
948 if constants.STATUS_OK != status:
949 message = (_("Failed to get VDM by name %(name)s.") %
950 {'name': name})
951 LOG.error(message)
952 raise exception.EMCVnxXMLAPIError(err=message)
954 return vdm['id']
956 def attach_nfs_interface(self, vdm_name, if_name):
958 command_attach_nfs_interface = [
959 'env', 'NAS_DB=/nas', '/nas/bin/nas_server',
960 '-vdm', vdm_name,
961 '-attach', if_name,
962 ]
964 self._execute_cmd(command_attach_nfs_interface)
966 def detach_nfs_interface(self, vdm_name, if_name):
968 command_detach_nfs_interface = [
969 'env', 'NAS_DB=/nas', '/nas/bin/nas_server',
970 '-vdm', vdm_name,
971 '-detach', if_name,
972 ]
974 try:
975 self._execute_cmd(command_detach_nfs_interface,
976 check_exit_code=True)
977 except processutils.ProcessExecutionError:
978 interfaces = self.get_interfaces(vdm_name)
979 if if_name not in interfaces['nfs']:
980 LOG.debug("Failed to detach interface %(interface)s "
981 "from mover %(mover_name)s.",
982 {'interface': if_name, 'mover_name': vdm_name})
983 else:
984 message = (_("Failed to detach interface %(interface)s "
985 "from mover %(mover_name)s.") %
986 {'interface': if_name, 'mover_name': vdm_name})
987 LOG.error(message)
988 raise exception.EMCVnxXMLAPIError(err=message)
990 def get_interfaces(self, vdm_name):
991 interfaces = {
992 'cifs': [],
993 'nfs': [],
994 }
996 re_pattern = (r'Interfaces to services mapping:'
997 r'\s*(?P<interfaces>(\s*interface=.*)*)')
999 command_get_interfaces = [
1000 'env', 'NAS_DB=/nas', '/nas/bin/nas_server',
1001 '-i',
1002 '-vdm', vdm_name,
1003 ]
1005 out, err = self._execute_cmd(command_get_interfaces)
1007 m = re.search(re_pattern, out)
1008 if m: 1008 ↛ 1021line 1008 didn't jump to line 1021 because the condition on line 1008 was always true
1009 if_list = m.group('interfaces').split('\n')
1010 for i in if_list:
1011 m_if = re.search(r'\s*interface=(?P<if>.*)\s*:'
1012 r'\s*(?P<type>.*)\s*', i)
1013 if m_if: 1013 ↛ 1010line 1013 didn't jump to line 1010 because the condition on line 1013 was always true
1014 if_name = m_if.group('if').strip()
1015 if 'cifs' == m_if.group('type') and if_name != '':
1016 interfaces['cifs'].append(if_name)
1017 elif (m_if.group('type') in ('vdm', 'nfs')
1018 and if_name != ''):
1019 interfaces['nfs'].append(if_name)
1021 return interfaces
1024@enas_utils.decorate_all_methods(enas_utils.log_enter_exit,
1025 debug_only=True)
1026class Snapshot(StorageObject):
1027 def __init__(self, conn, elt_maker, xml_parser, manager):
1028 super(Snapshot, self).__init__(conn, elt_maker, xml_parser, manager)
1029 self.snap_map = dict()
1031 def create(self, name, fs_name, pool_id, ckpt_size=None):
1032 fs_id = self.get_context('FileSystem').get_id(fs_name)
1034 if ckpt_size:
1035 elt_pool = self.elt_maker.StoragePool(
1036 pool=pool_id,
1037 size=str(ckpt_size)
1038 )
1039 else:
1040 elt_pool = self.elt_maker.StoragePool(pool=pool_id)
1042 new_ckpt = self.elt_maker.NewCheckpoint(
1043 self.elt_maker.SpaceAllocationMethod(
1044 elt_pool
1045 ),
1046 checkpointOf=fs_id,
1047 name=name
1048 )
1050 request = self._build_task_package(new_ckpt)
1052 response = self._send_request(request)
1054 if self._response_validation(response, constants.MSG_SNAP_EXIST):
1055 LOG.warning("Snapshot %(name)s already exists. "
1056 "Skip the creation.",
1057 {'name': name})
1058 elif constants.STATUS_OK != response['maxSeverity']:
1059 message = (_("Failed to create snapshot %(name)s on "
1060 "filesystem %(fs_name)s. Reason: %(err)s.") %
1061 {'name': name,
1062 'fs_name': fs_name,
1063 'err': response['problems']})
1064 LOG.error(message)
1065 raise exception.EMCVnxXMLAPIError(err=message)
1067 def get(self, name):
1068 if name not in self.snap_map: 1068 ↛ 1095line 1068 didn't jump to line 1095 because the condition on line 1068 was always true
1069 request = self._build_query_package(
1070 self.elt_maker.CheckpointQueryParams(
1071 self.elt_maker.Alias(name=name)
1072 )
1073 )
1075 response = self._send_request(request)
1077 if constants.STATUS_OK != response['maxSeverity']:
1078 return response['maxSeverity'], response['problems']
1080 if not response['objects']:
1081 return constants.STATUS_NOT_FOUND, response['problems']
1083 src = response['objects'][0]
1084 snap = {}
1085 property_map = (
1086 'name',
1087 ('id', 'checkpoint'),
1088 'checkpointOf',
1089 'state',
1090 )
1091 self._copy_properties(src, snap, property_map)
1093 self.snap_map[name] = snap
1095 return constants.STATUS_OK, self.snap_map[name]
1097 def delete(self, name):
1098 status, out = self.get(name)
1099 if constants.STATUS_NOT_FOUND == status:
1100 LOG.warning("Snapshot %s not found. Skip the deletion.",
1101 name)
1102 return
1103 elif constants.STATUS_OK != status:
1104 message = (_("Failed to get snapshot by name %(name)s. "
1105 "Reason: %(err)s.") %
1106 {'name': name, 'err': out})
1107 LOG.error(message)
1108 raise exception.EMCVnxXMLAPIError(err=message)
1110 chpt_id = self.snap_map[name]['id']
1112 request = self._build_task_package(
1113 self.elt_maker.DeleteCheckpoint(checkpoint=chpt_id)
1114 )
1116 response = self._send_request(request)
1117 if constants.STATUS_OK != response['maxSeverity']:
1118 message = (_("Failed to delete snapshot %(name)s. "
1119 "Reason: %(err)s.") %
1120 {'name': name, 'err': response['problems']})
1121 LOG.error(message)
1122 raise exception.EMCVnxXMLAPIError(err=message)
1124 self.snap_map.pop(name)
1126 def get_id(self, name):
1127 status, out = self.get(name)
1129 if constants.STATUS_OK != status:
1130 message = (_("Failed to get snapshot by %(name)s. "
1131 "Reason: %(err)s.") %
1132 {'name': name, 'err': out})
1133 LOG.error(message)
1134 raise exception.EMCVnxXMLAPIError(err=message)
1136 return self.snap_map[name]['id']
1139@enas_utils.decorate_all_methods(enas_utils.log_enter_exit,
1140 debug_only=True)
1141class MoverInterface(StorageObject):
1142 def __init__(self, conn, elt_maker, xml_parser, manager):
1143 super(MoverInterface, self).__init__(conn, elt_maker, xml_parser,
1144 manager)
1146 @utils.retry(retry_param=exception.EMCVnxInvalidMoverID)
1147 def create(self, interface):
1148 # Maximum of 32 characters for mover interface name
1149 name = interface['name']
1150 if len(name) > 32:
1151 name = name[0:31]
1153 device_name = interface['device_name']
1154 ip_addr = interface['ip']
1155 mover_name = interface['mover_name']
1156 net_mask = interface['net_mask']
1157 vlan_id = interface['vlan_id'] if interface['vlan_id'] else -1
1159 mover_id = self._get_mover_id(mover_name, False)
1161 params = dict(device=device_name,
1162 ipAddress=str(ip_addr),
1163 mover=mover_id,
1164 name=name,
1165 netMask=net_mask,
1166 vlanid=str(vlan_id))
1168 if interface.get('ip_version') == 6:
1169 params['ipVersion'] = 'IPv6'
1171 if self.xml_retry:
1172 self.xml_retry = False
1174 request = self._build_task_package(
1175 self.elt_maker.NewMoverInterface(**params)
1176 )
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.EMCVnxInvalidMoverID(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 return
1190 elif self._response_validation(
1191 response, constants.MSG_INTERFACE_EXIST):
1192 LOG.warning("Mover interface IP %s already exists. "
1193 "Skip the creation.", ip_addr)
1194 return
1195 elif self._response_validation(
1196 response, constants.MSG_INTERFACE_INVALID_VLAN_ID):
1197 # When fail to create a mover interface with the specified
1198 # vlan id, VNX will leave an interface with vlan id 0 in the
1199 # backend. So we should explicitly remove the interface.
1200 try:
1201 self.delete(str(ip_addr), mover_name)
1202 except exception.EMCVnxXMLAPIError:
1203 pass
1204 message = (_("Invalid vlan id %s. Other interfaces on this "
1205 "subnet are in a different vlan.") % vlan_id)
1206 LOG.error(message)
1207 raise exception.EMCVnxXMLAPIError(err=message)
1208 elif constants.STATUS_OK != response['maxSeverity']:
1209 message = (_("Failed to create mover interface %(interface)s. "
1210 "Reason: %(err)s.") %
1211 {'interface': interface,
1212 'err': response['problems']})
1213 LOG.error(message)
1214 raise exception.EMCVnxXMLAPIError(err=message)
1216 def get(self, name, mover_name):
1217 # Maximum of 32 characters for mover interface name
1218 if len(name) > 32:
1219 name = name[0:31]
1221 status, mover = self.manager.getStorageContext('Mover').get(
1222 mover_name, True)
1223 if constants.STATUS_OK == status:
1224 for interface in mover['interfaces']:
1225 if name == interface['name']:
1226 return constants.STATUS_OK, interface
1228 return constants.STATUS_NOT_FOUND, None
1230 @utils.retry(retry_param=exception.EMCVnxInvalidMoverID)
1231 def delete(self, ip_addr, mover_name):
1232 mover_id = self._get_mover_id(mover_name, False)
1234 if self.xml_retry:
1235 self.xml_retry = False
1237 request = self._build_task_package(
1238 self.elt_maker.DeleteMoverInterface(
1239 ipAddress=str(ip_addr),
1240 mover=mover_id
1241 )
1242 )
1244 response = self._send_request(request)
1246 if (self._response_validation(response,
1247 constants.MSG_INVALID_MOVER_ID) and
1248 not self.xml_retry):
1249 self.xml_retry = True
1250 raise exception.EMCVnxInvalidMoverID(id=mover_id)
1251 elif self._response_validation(
1252 response, constants.MSG_INTERFACE_NON_EXISTENT):
1253 LOG.warning("Mover interface %s not found. "
1254 "Skip the deletion.", ip_addr)
1255 return
1256 elif constants.STATUS_OK != response['maxSeverity']:
1257 message = (_("Failed to delete mover interface %(ip)s on mover "
1258 "%(mover)s. Reason: %(err)s.") %
1259 {'ip': ip_addr,
1260 'mover': mover_name,
1261 'err': response['problems']})
1262 LOG.error(message)
1263 raise exception.EMCVnxXMLAPIError(err=message)
1266@enas_utils.decorate_all_methods(enas_utils.log_enter_exit,
1267 debug_only=True)
1268class DNSDomain(StorageObject):
1269 def __init__(self, conn, elt_maker, xml_parser, manager):
1270 super(DNSDomain, self).__init__(conn, elt_maker, xml_parser, manager)
1272 @utils.retry(retry_param=exception.EMCVnxInvalidMoverID)
1273 def create(self, mover_name, name, servers, protocol='udp'):
1274 mover_id = self._get_mover_id(mover_name, False)
1276 if self.xml_retry:
1277 self.xml_retry = False
1279 request = self._build_task_package(
1280 self.elt_maker.NewMoverDnsDomain(
1281 mover=mover_id,
1282 name=name,
1283 servers=servers,
1284 protocol=protocol
1285 )
1286 )
1288 response = self._send_request(request)
1290 if (self._response_validation(response,
1291 constants.MSG_INVALID_MOVER_ID) and
1292 not self.xml_retry):
1293 self.xml_retry = True
1294 raise exception.EMCVnxInvalidMoverID(id=mover_id)
1295 elif constants.STATUS_OK != response['maxSeverity']:
1296 message = (_("Failed to create DNS domain %(name)s. "
1297 "Reason: %(err)s.") %
1298 {'name': name, 'err': response['problems']})
1299 LOG.error(message)
1300 raise exception.EMCVnxXMLAPIError(err=message)
1302 @utils.retry(retry_param=exception.EMCVnxInvalidMoverID)
1303 def delete(self, mover_name, name):
1304 mover_id = self._get_mover_id(mover_name, False)
1306 if self.xml_retry:
1307 self.xml_retry = False
1309 request = self._build_task_package(
1310 self.elt_maker.DeleteMoverDnsDomain(
1311 mover=mover_id,
1312 name=name
1313 )
1314 )
1316 response = self._send_request(request)
1317 if (self._response_validation(response,
1318 constants.MSG_INVALID_MOVER_ID) and
1319 not self.xml_retry):
1320 self.xml_retry = True
1321 raise exception.EMCVnxInvalidMoverID(id=mover_id)
1322 elif constants.STATUS_OK != response['maxSeverity']:
1323 LOG.warning("Failed to delete DNS domain %(name)s. "
1324 "Reason: %(err)s.",
1325 {'name': name, 'err': response['problems']})
1328@enas_utils.decorate_all_methods(enas_utils.log_enter_exit,
1329 debug_only=True)
1330class CIFSServer(StorageObject):
1331 def __init__(self, conn, elt_maker, xml_parser, manager):
1332 super(CIFSServer, self).__init__(conn, elt_maker, xml_parser, manager)
1333 self.cifs_server_map = dict()
1335 @utils.retry(retry_param=exception.EMCVnxInvalidMoverID)
1336 def create(self, server_args):
1337 compName = server_args['name']
1338 # Maximum of 14 characters for netBIOS name
1339 name = server_args['name'][-14:]
1340 # Maximum of 12 characters for alias name
1341 alias_name = server_args['name'][-12:]
1342 interfaces = server_args['interface_ip']
1343 domain_name = server_args['domain_name']
1344 user_name = server_args['user_name']
1345 password = server_args['password']
1346 mover_name = server_args['mover_name']
1347 is_vdm = server_args['is_vdm']
1349 mover_id = self._get_mover_id(mover_name, is_vdm)
1351 if self.xml_retry:
1352 self.xml_retry = False
1354 alias_name_list = [self.elt_maker.li(alias_name)]
1356 request = self._build_task_package(
1357 self.elt_maker.NewW2KCifsServer(
1358 self.elt_maker.MoverOrVdm(
1359 mover=mover_id,
1360 moverIdIsVdm='true' if server_args['is_vdm'] else 'false'
1361 ),
1362 self.elt_maker.Aliases(*alias_name_list),
1363 self.elt_maker.JoinDomain(userName=user_name,
1364 password=password),
1365 compName=compName,
1366 domain=domain_name,
1367 interfaces=interfaces,
1368 name=name
1369 )
1370 )
1372 response = self._send_request(request)
1374 if (self._response_validation(response,
1375 constants.MSG_INVALID_MOVER_ID) and
1376 not self.xml_retry):
1377 self.xml_retry = True
1378 raise exception.EMCVnxInvalidMoverID(id=mover_id)
1379 if constants.STATUS_OK != response['maxSeverity']:
1380 status, out = self.get(compName, mover_name, is_vdm)
1381 if constants.STATUS_OK == status and out['domainJoined'] == 'true':
1382 return
1383 else:
1384 message = (_("Failed to create CIFS server %(name)s. "
1385 "Reason: %(err)s.") %
1386 {'name': name,
1387 'err': response['problems']})
1388 LOG.error(message)
1389 raise exception.EMCVnxXMLAPIError(err=message)
1391 @utils.retry(retry_param=exception.EMCVnxInvalidMoverID)
1392 def get_all(self, mover_name, is_vdm=True):
1393 mover_id = self._get_mover_id(mover_name, is_vdm)
1395 if self.xml_retry:
1396 self.xml_retry = False
1398 request = self._build_query_package(
1399 self.elt_maker.CifsServerQueryParams(
1400 self.elt_maker.MoverOrVdm(
1401 mover=mover_id,
1402 moverIdIsVdm='true' if is_vdm else 'false'
1403 )
1404 )
1405 )
1407 response = self._send_request(request)
1408 if (self._response_validation(response,
1409 constants.MSG_INVALID_MOVER_ID) and
1410 not self.xml_retry):
1411 self.xml_retry = True
1412 raise exception.EMCVnxInvalidMoverID(id=mover_id)
1413 elif constants.STATUS_OK != response['maxSeverity']:
1414 return response['maxSeverity'], response['objects']
1416 if mover_name in self.cifs_server_map:
1417 self.cifs_server_map.pop(mover_name)
1419 self.cifs_server_map[mover_name] = dict()
1421 for item in response['objects']:
1422 self.cifs_server_map[mover_name][item['compName'].lower()] = item
1424 return constants.STATUS_OK, self.cifs_server_map[mover_name]
1426 def get(self, name, mover_name, is_vdm=True, force=False):
1427 # name is compName
1428 name = name.lower()
1430 if (mover_name in self.cifs_server_map and
1431 name in self.cifs_server_map[mover_name]) and not force:
1432 return constants.STATUS_OK, self.cifs_server_map[mover_name][name]
1434 self.get_all(mover_name, is_vdm)
1436 if mover_name in self.cifs_server_map:
1437 for compName, server in self.cifs_server_map[mover_name].items():
1438 if name == compName:
1439 return constants.STATUS_OK, server
1441 return constants.STATUS_NOT_FOUND, None
1443 @utils.retry(retry_param=exception.EMCVnxInvalidMoverID)
1444 def modify(self, server_args):
1445 """Make CIFS server join or un-join the domain.
1447 :param server_args: Dictionary for CIFS server modification
1448 name: CIFS server name instead of compName
1449 join_domain: True for joining the domain, false for un-joining
1450 user_name: User name under which the domain is joined
1451 password: Password associated with the user name
1452 mover_name: mover or VDM name
1453 is_vdm: Boolean to indicate mover or VDM
1454 :raises exception.EMCVnxXMLAPIError: if modification fails.
1455 """
1456 name = server_args['name']
1457 join_domain = server_args['join_domain']
1458 user_name = server_args['user_name']
1459 password = server_args['password']
1460 mover_name = server_args['mover_name']
1462 if 'is_vdm' in server_args.keys():
1463 is_vdm = server_args['is_vdm']
1464 else:
1465 is_vdm = True
1467 mover_id = self._get_mover_id(mover_name, is_vdm)
1469 if self.xml_retry:
1470 self.xml_retry = False
1472 request = self._build_task_package(
1473 self.elt_maker.ModifyW2KCifsServer(
1474 self.elt_maker.DomainSetting(
1475 joinDomain='true' if join_domain else 'false',
1476 password=password,
1477 userName=user_name,
1478 ),
1479 mover=mover_id,
1480 moverIdIsVdm='true' if is_vdm else 'false',
1481 name=name
1482 )
1483 )
1485 response = self._send_request(request)
1487 if (self._response_validation(response,
1488 constants.MSG_INVALID_MOVER_ID) and
1489 not self.xml_retry):
1490 self.xml_retry = True
1491 raise exception.EMCVnxInvalidMoverID(id=mover_id)
1492 elif self._ignore_modification_error(response, join_domain):
1493 return
1494 elif constants.STATUS_OK != response['maxSeverity']:
1495 message = (_("Failed to modify CIFS server %(name)s. "
1496 "Reason: %(err)s.") %
1497 {'name': name,
1498 'err': response['problems']})
1499 LOG.error(message)
1500 raise exception.EMCVnxXMLAPIError(err=message)
1502 def _ignore_modification_error(self, response, join_domain):
1503 if self._response_validation(response, constants.MSG_JOIN_DOMAIN):
1504 return join_domain
1505 elif self._response_validation(response, constants.MSG_UNJOIN_DOMAIN):
1506 return not join_domain
1508 return False
1510 def delete(self, computer_name, mover_name, is_vdm=True):
1511 try:
1512 status, out = self.get(
1513 computer_name.lower(), mover_name, is_vdm, self.xml_retry)
1514 if constants.STATUS_NOT_FOUND == status:
1515 LOG.warning("CIFS server %(name)s on mover %(mover_name)s "
1516 "not found. Skip the deletion.",
1517 {'name': computer_name, 'mover_name': mover_name})
1518 return
1519 except exception.EMCVnxXMLAPIError:
1520 LOG.warning("CIFS server %(name)s on mover %(mover_name)s "
1521 "not found. Skip the deletion.",
1522 {'name': computer_name, 'mover_name': mover_name})
1523 return
1525 server_name = out['name']
1527 mover_id = self._get_mover_id(mover_name, is_vdm)
1529 request = self._build_task_package(
1530 self.elt_maker.DeleteCifsServer(
1531 mover=mover_id,
1532 moverIdIsVdm='true' if is_vdm else 'false',
1533 name=server_name
1534 )
1535 )
1537 response = self._send_request(request)
1539 if constants.STATUS_OK != response['maxSeverity']:
1540 message = (_("Failed to delete CIFS server %(name)s. "
1541 "Reason: %(err)s.") %
1542 {'name': computer_name, 'err': response['problems']})
1543 LOG.error(message)
1544 raise exception.EMCVnxXMLAPIError(err=message)
1546 self.cifs_server_map[mover_name].pop(computer_name)
1549@enas_utils.decorate_all_methods(enas_utils.log_enter_exit,
1550 debug_only=True)
1551class CIFSShare(StorageObject):
1552 def __init__(self, conn, elt_maker, xml_parser, manager):
1553 super(CIFSShare, self).__init__(conn, elt_maker, xml_parser, manager)
1554 self.cifs_share_map = dict()
1556 @utils.retry(retry_param=exception.EMCVnxInvalidMoverID)
1557 def create(self, name, server_name, mover_name, is_vdm=True):
1558 mover_id = self._get_mover_id(mover_name, is_vdm)
1560 if self.xml_retry:
1561 self.xml_retry = False
1563 share_path = '/' + name
1565 request = self._build_task_package(
1566 self.elt_maker.NewCifsShare(
1567 self.elt_maker.MoverOrVdm(
1568 mover=mover_id,
1569 moverIdIsVdm='true' if is_vdm else 'false'
1570 ),
1571 self.elt_maker.CifsServers(self.elt_maker.li(server_name)),
1572 name=name,
1573 path=share_path
1574 )
1575 )
1577 response = self._send_request(request)
1579 if (self._response_validation(response,
1580 constants.MSG_INVALID_MOVER_ID) and
1581 not self.xml_retry):
1582 self.xml_retry = True
1583 raise exception.EMCVnxInvalidMoverID(id=mover_id)
1584 elif constants.STATUS_OK != response['maxSeverity']:
1585 message = (_("Failed to create file share %(name)s. "
1586 "Reason: %(err)s.") %
1587 {'name': name, 'err': response['problems']})
1588 LOG.error(message)
1589 raise exception.EMCVnxXMLAPIError(err=message)
1591 def get(self, name):
1592 if name not in self.cifs_share_map:
1593 request = self._build_query_package(
1594 self.elt_maker.CifsShareQueryParams(name=name)
1595 )
1597 response = self._send_request(request)
1599 if constants.STATUS_OK != response['maxSeverity']:
1600 return response['maxSeverity'], response['problems']
1602 if not response['objects']:
1603 return constants.STATUS_NOT_FOUND, None
1605 self.cifs_share_map[name] = response['objects'][0]
1607 return constants.STATUS_OK, self.cifs_share_map[name]
1609 @utils.retry(retry_param=exception.EMCVnxInvalidMoverID)
1610 def delete(self, name, mover_name, is_vdm=True):
1611 status, out = self.get(name)
1612 if constants.STATUS_NOT_FOUND == status:
1613 LOG.warning("CIFS share %s not found. Skip the deletion.",
1614 name)
1615 return
1616 elif constants.STATUS_OK != status:
1617 message = (_("Failed to get CIFS share by name %(name)s. "
1618 "Reason: %(err)s.") %
1619 {'name': name, 'err': out})
1620 LOG.error(message)
1621 raise exception.EMCVnxXMLAPIError(err=message)
1623 mover_id = self._get_mover_id(mover_name, is_vdm)
1625 if self.xml_retry:
1626 self.xml_retry = False
1628 netbios_names = self.cifs_share_map[name]['CifsServers']
1630 request = self._build_task_package(
1631 self.elt_maker.DeleteCifsShare(
1632 self.elt_maker.CifsServers(*map(lambda a: self.elt_maker.li(a),
1633 netbios_names)),
1634 mover=mover_id,
1635 moverIdIsVdm='true' if is_vdm else 'false',
1636 name=name
1637 )
1638 )
1640 response = self._send_request(request)
1642 if (self._response_validation(response,
1643 constants.MSG_INVALID_MOVER_ID) and
1644 not self.xml_retry):
1645 self.xml_retry = True
1646 raise exception.EMCVnxInvalidMoverID(id=mover_id)
1647 elif constants.STATUS_OK != response['maxSeverity']:
1648 message = (_("Failed to delete file system %(name)s. "
1649 "Reason: %(err)s.") %
1650 {'name': name, 'err': response['problems']})
1651 LOG.error(message)
1652 raise exception.EMCVnxXMLAPIError(err=message)
1654 self.cifs_share_map.pop(name)
1656 def disable_share_access(self, share_name, mover_name):
1657 cmd_str = 'sharesd %s set noaccess' % share_name
1658 disable_access = [
1659 'env', 'NAS_DB=/nas', '/nas/bin/.server_config', mover_name,
1660 '-v', "%s" % cmd_str,
1661 ]
1663 try:
1664 self._execute_cmd(disable_access, check_exit_code=True)
1665 except processutils.ProcessExecutionError as expt:
1666 message = (_('Failed to disable the access to CIFS share '
1667 '%(name)s. Reason: %(err)s.') %
1668 {'name': share_name, 'err': expt})
1669 LOG.error(message)
1670 raise exception.EMCVnxXMLAPIError(err=message)
1672 def allow_share_access(self, mover_name, share_name, user_name, domain,
1673 access=constants.CIFS_ACL_FULLCONTROL):
1674 account = user_name + "@" + domain
1675 allow_str = ('sharesd %(share_name)s grant %(account)s=%(access)s'
1676 % {'share_name': share_name,
1677 'account': account,
1678 'access': access})
1680 allow_access = [
1681 'env', 'NAS_DB=/nas', '/nas/bin/.server_config', mover_name,
1682 '-v', "%s" % allow_str,
1683 ]
1685 try:
1686 self._execute_cmd(allow_access, check_exit_code=True)
1687 except processutils.ProcessExecutionError as expt:
1688 dup_msg = re.compile(r'ACE for %(domain)s\\%(user)s unchanged' %
1689 {'domain': domain, 'user': user_name}, re.I)
1690 if re.search(dup_msg, expt.stdout):
1691 LOG.warning("Duplicate access control entry, "
1692 "skipping allow...")
1693 else:
1694 message = (_('Failed to allow the access %(access)s to '
1695 'CIFS share %(name)s. Reason: %(err)s.') %
1696 {'access': access, 'name': share_name, 'err': expt})
1697 LOG.error(message)
1698 raise exception.EMCVnxXMLAPIError(err=message)
1700 def deny_share_access(self, mover_name, share_name, user_name, domain,
1701 access=constants.CIFS_ACL_FULLCONTROL):
1702 account = user_name + "@" + domain
1703 revoke_str = ('sharesd %(share_name)s revoke %(account)s=%(access)s'
1704 % {'share_name': share_name,
1705 'account': account,
1706 'access': access})
1708 allow_access = [
1709 'env', 'NAS_DB=/nas', '/nas/bin/.server_config', mover_name,
1710 '-v', "%s" % revoke_str,
1711 ]
1712 try:
1713 self._execute_cmd(allow_access, check_exit_code=True)
1714 except processutils.ProcessExecutionError as expt:
1715 not_found_msg = re.compile(
1716 r'No ACE found for %(domain)s\\%(user)s'
1717 % {'domain': domain, 'user': user_name}, re.I)
1718 user_err_msg = re.compile(
1719 r'Cannot get mapping for %(domain)s\\%(user)s'
1720 % {'domain': domain, 'user': user_name}, re.I)
1722 if re.search(not_found_msg, expt.stdout):
1723 LOG.warning("No access control entry found, "
1724 "skipping deny...")
1725 elif re.search(user_err_msg, expt.stdout):
1726 LOG.warning("User not found on domain, skipping deny...")
1727 else:
1728 message = (_('Failed to deny the access %(access)s to '
1729 'CIFS share %(name)s. Reason: %(err)s.') %
1730 {'access': access, 'name': share_name, 'err': expt})
1731 LOG.error(message)
1732 raise exception.EMCVnxXMLAPIError(err=message)
1734 def get_share_access(self, mover_name, share_name):
1735 get_str = 'sharesd %s dump' % share_name
1736 get_access = [
1737 'env', 'NAS_DB=/nas', '/nas/bin/.server_config', mover_name,
1738 '-v', "%s" % get_str,
1739 ]
1741 try:
1742 out, err = self._execute_cmd(get_access, check_exit_code=True)
1743 except processutils.ProcessExecutionError:
1744 msg = _('Failed to get access list of CIFS share %s.') % share_name
1745 LOG.exception(msg)
1746 raise exception.EMCVnxXMLAPIError(err=msg)
1748 ret = {}
1749 name_pattern = re.compile(r"Unix user '(.+?)'")
1750 access_pattern = re.compile(r"ALLOWED:(.+?):")
1752 name = None
1753 for line in out.splitlines():
1754 if name is None:
1755 names = name_pattern.findall(line)
1756 if names:
1757 name = names[0].lower()
1758 else:
1759 accesses = access_pattern.findall(line)
1760 if accesses:
1761 ret[name] = accesses[0].lower()
1762 name = None
1763 return ret
1765 def clear_share_access(self, mover_name, share_name, domain,
1766 white_list_users):
1767 existing_users = self.get_share_access(mover_name, share_name)
1768 white_list_users_set = set(user.lower() for user in white_list_users)
1769 users_to_remove = set(existing_users.keys()) - white_list_users_set
1770 for user in users_to_remove:
1771 self.deny_share_access(mover_name, share_name, user, domain,
1772 existing_users[user])
1773 return users_to_remove
1776@enas_utils.decorate_all_methods(enas_utils.log_enter_exit,
1777 debug_only=True)
1778class NFSShare(StorageObject):
1779 def __init__(self, conn, elt_maker, xml_parser, manager):
1780 super(NFSShare, self).__init__(conn, elt_maker, xml_parser, manager)
1781 self.nfs_share_map = {}
1783 def create(self, name, mover_name):
1784 share_path = '/' + name
1785 create_nfs_share_cmd = [
1786 'env', 'NAS_DB=/nas', '/nas/bin/server_export', mover_name,
1787 '-option', 'access=-0.0.0.0/0.0.0.0',
1788 share_path,
1789 ]
1791 try:
1792 self._execute_cmd(create_nfs_share_cmd, check_exit_code=True)
1793 except processutils.ProcessExecutionError as expt:
1794 message = (_('Failed to create NFS share %(name)s on mover '
1795 '%(mover_name)s. Reason: %(err)s.') %
1796 {'name': name, 'mover_name': mover_name, 'err': expt})
1797 LOG.error(message)
1798 raise exception.EMCVnxXMLAPIError(err=message)
1800 def delete(self, name, mover_name):
1801 path = '/' + name
1803 status, out = self.get(name, mover_name)
1804 if constants.STATUS_NOT_FOUND == status:
1805 LOG.warning("NFS share %s not found. Skip the deletion.",
1806 path)
1807 return
1809 delete_nfs_share_cmd = [
1810 'env', 'NAS_DB=/nas', '/nas/bin/server_export', mover_name,
1811 '-unexport',
1812 '-perm',
1813 path,
1814 ]
1816 try:
1817 self._execute_cmd(delete_nfs_share_cmd, check_exit_code=True)
1818 except processutils.ProcessExecutionError as expt:
1819 message = (_('Failed to delete NFS share %(name)s on '
1820 '%(mover_name)s. Reason: %(err)s.') %
1821 {'name': name, 'mover_name': mover_name, 'err': expt})
1822 LOG.error(message)
1823 raise exception.EMCVnxXMLAPIError(err=message)
1825 self.nfs_share_map.pop(name)
1827 def get(self, name, mover_name, force=False, check_exit_code=False):
1828 if name in self.nfs_share_map and not force:
1829 return constants.STATUS_OK, self.nfs_share_map[name]
1831 path = '/' + name
1833 nfs_share = {
1834 "mover_name": '',
1835 "path": '',
1836 'AccessHosts': [],
1837 'RwHosts': [],
1838 'RoHosts': [],
1839 'RootHosts': [],
1840 'readOnly': '',
1841 }
1843 nfs_query_cmd = [
1844 'env', 'NAS_DB=/nas', '/nas/bin/server_export', mover_name,
1845 '-P', 'nfs',
1846 '-list', path,
1847 ]
1849 try:
1850 out, err = self._execute_cmd(nfs_query_cmd,
1851 check_exit_code=check_exit_code)
1852 except processutils.ProcessExecutionError as expt:
1853 dup_msg = (r'%(mover_name)s : No such file or directory' %
1854 {'mover_name': mover_name})
1855 if re.search(dup_msg, expt.stdout):
1856 LOG.warning("NFS share %s not found.", name)
1857 return constants.STATUS_NOT_FOUND, None
1858 else:
1859 message = (_('Failed to list NFS share %(name)s on '
1860 '%(mover_name)s. Reason: %(err)s.') %
1861 {'name': name,
1862 'mover_name': mover_name,
1863 'err': expt})
1864 LOG.error(message)
1865 raise exception.EMCVnxXMLAPIError(err=message)
1867 re_exports = r'%s\s*:\s*\nexport\s*(.*)\n' % mover_name
1868 m = re.search(re_exports, out)
1869 if m is not None:
1870 nfs_share['path'] = path
1871 nfs_share['mover_name'] = mover_name
1872 export = m.group(1)
1873 fields = export.split(" ")
1874 for field in fields:
1875 field = field.strip()
1876 if field.startswith('rw='):
1877 nfs_share['RwHosts'] = enas_utils.parse_ipaddr(field[3:])
1878 elif field.startswith('access='):
1879 nfs_share['AccessHosts'] = enas_utils.parse_ipaddr(
1880 field[7:])
1881 elif field.startswith('root='):
1882 nfs_share['RootHosts'] = enas_utils.parse_ipaddr(field[5:])
1883 elif field.startswith('ro='):
1884 nfs_share['RoHosts'] = enas_utils.parse_ipaddr(field[3:])
1886 self.nfs_share_map[name] = nfs_share
1887 else:
1888 return constants.STATUS_NOT_FOUND, None
1890 return constants.STATUS_OK, self.nfs_share_map[name]
1892 def allow_share_access(self, share_name, host_ip, mover_name,
1893 access_level=const.ACCESS_LEVEL_RW):
1894 @utils.synchronized('emc-shareaccess-' + share_name)
1895 def do_allow_access(share_name, host_ip, mover_name, access_level):
1896 status, share = self.get(share_name, mover_name)
1897 if constants.STATUS_NOT_FOUND == status:
1898 message = (_('NFS share %s not found.') % share_name)
1899 LOG.error(message)
1900 raise exception.EMCVnxXMLAPIError(err=message)
1902 changed = False
1903 rwhosts = share['RwHosts']
1904 rohosts = share['RoHosts']
1906 host_ip = enas_utils.convert_ipv6_format_if_needed(host_ip)
1908 if access_level == const.ACCESS_LEVEL_RW:
1909 if host_ip not in rwhosts:
1910 rwhosts.append(host_ip)
1911 changed = True
1912 if host_ip in rohosts:
1913 rohosts.remove(host_ip)
1914 changed = True
1915 if access_level == const.ACCESS_LEVEL_RO:
1916 if host_ip not in rohosts: 1916 ↛ 1919line 1916 didn't jump to line 1919 because the condition on line 1916 was always true
1917 rohosts.append(host_ip)
1918 changed = True
1919 if host_ip in rwhosts: 1919 ↛ 1923line 1919 didn't jump to line 1923 because the condition on line 1919 was always true
1920 rwhosts.remove(host_ip)
1921 changed = True
1923 roothosts = share['RootHosts']
1924 if host_ip not in roothosts:
1925 roothosts.append(host_ip)
1926 changed = True
1927 accesshosts = share['AccessHosts']
1928 if host_ip not in accesshosts:
1929 accesshosts.append(host_ip)
1930 changed = True
1932 if not changed:
1933 LOG.debug("%(host)s is already in access list of share "
1934 "%(name)s.", {'host': host_ip, 'name': share_name})
1935 else:
1936 path = '/' + share_name
1937 self._set_share_access(path,
1938 mover_name,
1939 rwhosts,
1940 rohosts,
1941 roothosts,
1942 accesshosts)
1944 # Update self.nfs_share_map
1945 self.get(share_name, mover_name, force=True,
1946 check_exit_code=True)
1948 do_allow_access(share_name, host_ip, mover_name, access_level)
1950 def deny_share_access(self, share_name, host_ip, mover_name):
1951 @utils.synchronized('emc-shareaccess-' + share_name)
1952 def do_deny_access(share_name, host_ip, mover_name):
1953 status, share = self.get(share_name, mover_name)
1954 if constants.STATUS_OK != status:
1955 message = (_('Query nfs share %(path)s failed. '
1956 'Reason %(err)s.') %
1957 {'path': share_name, 'err': share})
1958 LOG.error(message)
1959 raise exception.EMCVnxXMLAPIError(err=message)
1961 changed = False
1962 rwhosts = set(share['RwHosts'])
1963 if host_ip in rwhosts:
1964 rwhosts.remove(host_ip)
1965 changed = True
1966 roothosts = set(share['RootHosts'])
1967 if host_ip in roothosts:
1968 roothosts.remove(host_ip)
1969 changed = True
1970 accesshosts = set(share['AccessHosts'])
1971 if host_ip in accesshosts:
1972 accesshosts.remove(host_ip)
1973 changed = True
1974 rohosts = set(share['RoHosts'])
1975 if host_ip in rohosts:
1976 rohosts.remove(host_ip)
1977 changed = True
1978 if not changed:
1979 LOG.debug("%(host)s is already in access list of share "
1980 "%(name)s.", {'host': host_ip, 'name': share_name})
1981 else:
1982 path = '/' + share_name
1983 self._set_share_access(path,
1984 mover_name,
1985 rwhosts,
1986 rohosts,
1987 roothosts,
1988 accesshosts)
1990 # Update self.nfs_share_map
1991 self.get(share_name, mover_name, force=True,
1992 check_exit_code=True)
1994 do_deny_access(share_name, host_ip, mover_name)
1996 def clear_share_access(self, share_name, mover_name, white_list_hosts):
1997 @utils.synchronized('emc-shareaccess-' + share_name)
1998 def do_clear_access(share_name, mover_name, white_list_hosts):
1999 def hosts_to_remove(orig_list):
2000 if white_list_hosts is None: 2000 ↛ 2001line 2000 didn't jump to line 2001 because the condition on line 2000 was never true
2001 ret = set()
2002 else:
2003 ret = set(white_list_hosts).intersection(set(orig_list))
2004 return ret
2006 status, share = self.get(share_name, mover_name)
2007 if constants.STATUS_OK != status:
2008 message = (_('Query nfs share %(path)s failed. '
2009 'Reason %(err)s.') %
2010 {'path': share_name, 'err': status})
2011 raise exception.EMCVnxXMLAPIError(err=message)
2013 self._set_share_access('/' + share_name,
2014 mover_name,
2015 hosts_to_remove(share['RwHosts']),
2016 hosts_to_remove(share['RoHosts']),
2017 hosts_to_remove(share['RootHosts']),
2018 hosts_to_remove(share['AccessHosts']))
2020 # Update self.nfs_share_map
2021 self.get(share_name, mover_name, force=True,
2022 check_exit_code=True)
2024 do_clear_access(share_name, mover_name, white_list_hosts)
2026 def _set_share_access(self, path, mover_name, rw_hosts, ro_hosts,
2027 root_hosts, access_hosts):
2029 if access_hosts is None: 2029 ↛ 2030line 2029 didn't jump to line 2030 because the condition on line 2029 was never true
2030 access_hosts = set()
2031 try:
2032 access_hosts.remove('-0.0.0.0/0.0.0.0')
2033 except (ValueError, KeyError):
2034 pass
2036 access_str = ('access=%(access)s' % {'access': ':'.join(
2037 list(access_hosts) + ['-0.0.0.0/0.0.0.0'])})
2039 if root_hosts: 2039 ↛ 2041line 2039 didn't jump to line 2041 because the condition on line 2039 was always true
2040 access_str += (',root=%(root)s' % {'root': ':'.join(root_hosts)})
2041 if rw_hosts: 2041 ↛ 2043line 2041 didn't jump to line 2043 because the condition on line 2041 was always true
2042 access_str += ',rw=%(rw)s' % {'rw': ':'.join(rw_hosts)}
2043 if ro_hosts:
2044 access_str += ',ro=%(ro)s' % {'ro': ':'.join(ro_hosts)}
2046 set_nfs_share_access_cmd = [
2047 'env', 'NAS_DB=/nas', '/nas/bin/server_export', mover_name,
2048 '-ignore',
2049 '-option', access_str,
2050 path,
2051 ]
2053 try:
2054 self._execute_cmd(set_nfs_share_access_cmd, check_exit_code=True)
2055 except processutils.ProcessExecutionError as expt:
2056 message = (_('Failed to set NFS share %(name)s access on '
2057 '%(mover_name)s. Reason: %(err)s.') %
2058 {'name': path[1:],
2059 'mover_name': mover_name,
2060 'err': expt})
2061 LOG.error(message)
2062 raise exception.EMCVnxXMLAPIError(err=message)