Coverage for manila/share/drivers/dell_emc/plugins/vnx/connection.py: 98%
472 statements
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
1# Copyright (c) 2014 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.
15"""VNX backend for the EMC Manila driver."""
17import copy
18import random
20from oslo_config import cfg
21from oslo_log import log
22from oslo_utils import excutils
23from oslo_utils import units
25from manila.common import constants as const
26from manila import exception
27from manila.i18n import _
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.plugins import base as driver
31from manila.share.drivers.dell_emc.plugins.vnx import object_manager as manager
32from manila.share import utils as share_utils
33from manila import utils
35"""Version history:
36 1.0.0 - Initial version (Liberty)
37 2.0.0 - Bumped the version for Mitaka
38 3.0.0 - Bumped the version for Ocata
39 4.0.0 - Bumped the version for Pike
40 5.0.0 - Bumped the version for Queens
41 9.0.0 - Bumped the version for Ussuri
42 9.0.1 - Fixes bug 1871999: wrong format of export locations
43"""
44VERSION = "9.0.1"
46LOG = log.getLogger(__name__)
48VNX_OPTS = [
49 cfg.StrOpt('vnx_server_container',
50 help='Data mover to host the NAS server.'),
51 cfg.ListOpt('vnx_share_data_pools',
52 help='Comma separated list of pools that can be used to '
53 'persist share data.'),
54 cfg.ListOpt('vnx_ethernet_ports',
55 help='Comma separated list of ports that can be used for '
56 'share server interfaces. Members of the list '
57 'can be Unix-style glob expressions.')
58]
60CONF = cfg.CONF
61CONF.register_opts(VNX_OPTS)
64@enas_utils.decorate_all_methods(enas_utils.log_enter_exit,
65 debug_only=True)
66class VNXStorageConnection(driver.StorageConnection):
67 """Implements VNX specific functionality for EMC Manila driver."""
69 @enas_utils.log_enter_exit
70 def __init__(self, *args, **kwargs):
71 super(VNXStorageConnection, self).__init__(*args, **kwargs)
72 if 'configuration' in kwargs: 72 ↛ 73line 72 didn't jump to line 73 because the condition on line 72 was never true
73 kwargs['configuration'].append_config_values(VNX_OPTS)
75 self.mover_name = None
76 self.pools = None
77 self.manager = None
78 self.pool_conf = None
79 self.reserved_percentage = None
80 self.reserved_snapshot_percentage = None
81 self.reserved_share_extend_percentage = None
82 self.driver_handles_share_servers = True
83 self.port_conf = None
84 self.ipv6_implemented = True
85 self.dhss_mandatory_security_service_association = {
86 'nfs': None,
87 'cifs': ['active_directory', ]
88 }
90 def create_share(self, context, share, share_server=None):
91 """Create a share and export it based on protocol used."""
92 share_name = share['id']
93 size = share['size'] * units.Ki
95 share_proto = share['share_proto']
97 # Validate the share protocol
98 if share_proto.upper() not in ('NFS', 'CIFS'):
99 raise exception.InvalidShare(
100 reason=(_('Invalid NAS protocol supplied: %s.')
101 % share_proto))
103 # Get the pool name from share host field
104 pool_name = share_utils.extract_host(share['host'], level='pool')
105 if not pool_name:
106 message = (_("Pool is not available in the share host %s.") %
107 share['host'])
108 raise exception.InvalidHost(reason=message)
110 # Validate share server
111 self._share_server_validation(share_server)
113 if share_proto == 'CIFS':
114 vdm_name = self._get_share_server_name(share_server)
115 server_name = vdm_name
117 # Check if CIFS server exists.
118 status, server = self._get_context('CIFSServer').get(server_name,
119 vdm_name)
120 if status != constants.STATUS_OK:
121 message = (_("CIFS server %s not found.") % server_name)
122 LOG.error(message)
123 raise exception.EMCVnxXMLAPIError(err=message)
125 self._allocate_container(share_name, size, share_server, pool_name)
127 if share_proto == 'NFS':
128 location = self._create_nfs_share(share_name, share_server)
129 elif share_proto == 'CIFS': 129 ↛ 132line 129 didn't jump to line 132 because the condition on line 129 was always true
130 location = self._create_cifs_share(share_name, share_server)
132 return [
133 {'path': location}
134 ]
136 def _share_server_validation(self, share_server):
137 """Validate the share server."""
138 if not share_server:
139 msg = _('Share server not provided')
140 raise exception.InvalidInput(reason=msg)
142 backend_details = share_server.get('backend_details')
143 vdm = backend_details.get(
144 'share_server_name') if backend_details else None
146 if vdm is None:
147 message = _("No share server found.")
148 LOG.error(message)
149 raise exception.EMCVnxXMLAPIError(err=message)
151 def _allocate_container(self, share_name, size, share_server, pool_name):
152 """Allocate file system for share."""
153 vdm_name = self._get_share_server_name(share_server)
155 self._get_context('FileSystem').create(
156 share_name, size, pool_name, vdm_name)
158 def _allocate_container_from_snapshot(self, share, snapshot, share_server,
159 pool_name):
160 """Allocate file system from snapshot."""
161 vdm_name = self._get_share_server_name(share_server)
163 interconn_id = self._get_context('Mover').get_interconnect_id(
164 self.mover_name, self.mover_name)
166 self._get_context('FileSystem').create_from_snapshot(
167 share['id'], snapshot['id'], snapshot['share_id'],
168 pool_name, vdm_name, interconn_id)
170 nwe_size = share['size'] * units.Ki
171 self._get_context('FileSystem').extend(share['id'], pool_name,
172 nwe_size)
174 @enas_utils.log_enter_exit
175 def _create_cifs_share(self, share_name, share_server):
176 """Create CIFS share."""
177 vdm_name = self._get_share_server_name(share_server)
178 server_name = vdm_name
180 # Get available CIFS Server and interface (one CIFS server per VDM)
181 status, server = self._get_context('CIFSServer').get(server_name,
182 vdm_name)
184 if 'interfaces' not in server or len(server['interfaces']) == 0:
185 message = (_("CIFS server %s doesn't have interface, "
186 "so the share is inaccessible.")
187 % server['compName'])
188 LOG.error(message)
189 raise exception.EMCVnxXMLAPIError(err=message)
191 interface = enas_utils.export_unc_path(server['interfaces'][0])
193 self._get_context('CIFSShare').create(share_name, server['name'],
194 vdm_name)
196 self._get_context('CIFSShare').disable_share_access(share_name,
197 vdm_name)
199 location = (r'\\%(interface)s\%(name)s' %
200 {'interface': interface, 'name': share_name})
202 return location
204 @enas_utils.log_enter_exit
205 def _create_nfs_share(self, share_name, share_server):
206 """Create NFS share."""
207 vdm_name = self._get_share_server_name(share_server)
209 self._get_context('NFSShare').create(share_name, vdm_name)
211 nfs_if = enas_utils.convert_ipv6_format_if_needed(
212 share_server['backend_details']['nfs_if'])
214 return ('%(nfs_if)s:/%(share_name)s'
215 % {'nfs_if': nfs_if,
216 'share_name': share_name})
218 def create_share_from_snapshot(self, context, share, snapshot,
219 share_server=None, parent_share=None):
220 """Create a share from a snapshot - clone a snapshot."""
221 share_name = share['id']
223 share_proto = share['share_proto']
225 # Validate the share protocol
226 if share_proto.upper() not in ('NFS', 'CIFS'):
227 raise exception.InvalidShare(
228 reason=(_('Invalid NAS protocol supplied: %s.')
229 % share_proto))
231 # Get the pool name from share host field
232 pool_name = share_utils.extract_host(share['host'], level='pool')
233 if not pool_name:
234 message = (_("Pool is not available in the share host %s.") %
235 share['host'])
236 raise exception.InvalidHost(reason=message)
238 self._share_server_validation(share_server)
240 self._allocate_container_from_snapshot(
241 share, snapshot, share_server, pool_name)
243 nfs_if = enas_utils.convert_ipv6_format_if_needed(
244 share_server['backend_details']['nfs_if'])
246 if share_proto == 'NFS':
247 self._create_nfs_share(share_name, share_server)
248 location = ('%(nfs_if)s:/%(share_name)s'
249 % {'nfs_if': nfs_if,
250 'share_name': share_name})
251 elif share_proto == 'CIFS': 251 ↛ 254line 251 didn't jump to line 254 because the condition on line 251 was always true
252 location = self._create_cifs_share(share_name, share_server)
254 return [
255 {'path': location}
256 ]
258 def create_snapshot(self, context, snapshot, share_server=None):
259 """Create snapshot from share."""
260 share_name = snapshot['share_id']
261 status, filesystem = self._get_context('FileSystem').get(share_name)
262 if status != constants.STATUS_OK:
263 message = (_("File System %s not found.") % share_name)
264 LOG.error(message)
265 raise exception.EMCVnxXMLAPIError(err=message)
267 pool_id = filesystem['pools_id'][0]
269 self._get_context('Snapshot').create(snapshot['id'],
270 snapshot['share_id'],
271 pool_id)
273 def delete_share(self, context, share, share_server=None):
274 """Delete a share."""
275 if share_server is None:
276 LOG.warning("Driver does not support share deletion without "
277 "share network specified. Return directly because "
278 "there is nothing to clean.")
279 return
281 share_proto = share['share_proto']
283 if share_proto == 'NFS':
284 self._delete_nfs_share(share, share_server)
285 elif share_proto == 'CIFS':
286 self._delete_cifs_share(share, share_server)
287 else:
288 raise exception.InvalidShare(
289 reason='Unsupported share type')
291 @enas_utils.log_enter_exit
292 def _delete_cifs_share(self, share, share_server):
293 """Delete CIFS share."""
294 vdm_name = self._get_share_server_name(share_server)
296 name = share['id']
298 self._get_context('CIFSShare').delete(name, vdm_name)
300 self._deallocate_container(name, vdm_name)
302 @enas_utils.log_enter_exit
303 def _delete_nfs_share(self, share, share_server):
304 """Delete NFS share."""
305 vdm_name = self._get_share_server_name(share_server)
307 name = share['id']
309 self._get_context('NFSShare').delete(name, vdm_name)
311 self._deallocate_container(name, vdm_name)
313 @enas_utils.log_enter_exit
314 def _deallocate_container(self, share_name, vdm_name):
315 """Delete underneath objects of the share."""
316 path = '/' + share_name
318 try:
319 # Delete mount point
320 self._get_context('MountPoint').delete(path, vdm_name)
321 except Exception:
322 LOG.debug("Skip the failure of mount point %s deletion.", path)
324 try:
325 # Delete file system
326 self._get_context('FileSystem').delete(share_name)
327 except Exception:
328 LOG.debug("Skip the failure of file system %s deletion.",
329 share_name)
331 def delete_snapshot(self, context, snapshot, share_server=None):
332 """Delete a snapshot."""
333 self._get_context('Snapshot').delete(snapshot['id'])
335 def ensure_share(self, context, share, share_server=None):
336 """Ensure that the share is exported."""
338 def extend_share(self, share, new_size, share_server=None):
339 # Get the pool name from share host field
340 pool_name = share_utils.extract_host(share['host'], level='pool')
341 if not pool_name:
342 message = (_("Pool is not available in the share host %s.") %
343 share['host'])
344 raise exception.InvalidHost(reason=message)
346 share_name = share['id']
348 self._get_context('FileSystem').extend(
349 share_name, pool_name, new_size * units.Ki)
351 def allow_access(self, context, share, access, share_server=None):
352 """Allow access to a share."""
353 access_level = access['access_level']
354 if access_level not in const.ACCESS_LEVELS:
355 raise exception.InvalidShareAccessLevel(level=access_level)
357 share_proto = share['share_proto']
359 if share_proto == 'NFS':
360 self._nfs_allow_access(context, share, access, share_server)
361 elif share_proto == 'CIFS':
362 self._cifs_allow_access(context, share, access, share_server)
363 else:
364 raise exception.InvalidShare(
365 reason=(_('Invalid NAS protocol supplied: %s.')
366 % share_proto))
368 @enas_utils.log_enter_exit
369 def _cifs_allow_access(self, context, share, access, share_server):
370 """Allow access to CIFS share."""
371 vdm_name = self._get_share_server_name(share_server)
372 share_name = share['id']
374 if access['access_type'] != 'user':
375 reason = _('Only user access type allowed for CIFS share')
376 raise exception.InvalidShareAccess(reason=reason)
378 user_name = access['access_to']
380 access_level = access['access_level']
381 if access_level == const.ACCESS_LEVEL_RW:
382 cifs_access = constants.CIFS_ACL_FULLCONTROL
383 else:
384 cifs_access = constants.CIFS_ACL_READ
386 # Check if CIFS server exists.
387 server_name = vdm_name
388 status, server = self._get_context('CIFSServer').get(server_name,
389 vdm_name)
390 if status != constants.STATUS_OK:
391 message = (_("CIFS server %s not found.") % server_name)
392 LOG.error(message)
393 raise exception.EMCVnxXMLAPIError(err=message)
395 self._get_context('CIFSShare').allow_share_access(
396 vdm_name,
397 share_name,
398 user_name,
399 server['domain'],
400 access=cifs_access)
402 @enas_utils.log_enter_exit
403 def _nfs_allow_access(self, context, share, access, share_server):
404 """Allow access to NFS share."""
405 vdm_name = self._get_share_server_name(share_server)
407 access_type = access['access_type']
408 if access_type != 'ip':
409 reason = _('Only ip access type allowed.')
410 raise exception.InvalidShareAccess(reason=reason)
412 host_ip = access['access_to']
413 access_level = access['access_level']
415 self._get_context('NFSShare').allow_share_access(
416 share['id'], host_ip, vdm_name, access_level)
418 def update_access(self, context, share, access_rules, add_rules,
419 delete_rules, share_server=None):
420 # deleting rules
421 for rule in delete_rules:
422 self.deny_access(context, share, rule, share_server)
424 # adding rules
425 for rule in add_rules:
426 self.allow_access(context, share, rule, share_server)
428 # recovery mode
429 if not (add_rules or delete_rules):
430 white_list = []
431 for rule in access_rules:
432 self.allow_access(context, share, rule, share_server)
433 white_list.append(
434 enas_utils.convert_ipv6_format_if_needed(
435 rule['access_to']))
436 self.clear_access(share, share_server, white_list)
438 def clear_access(self, share, share_server, white_list):
439 share_proto = share['share_proto'].upper()
440 share_name = share['id']
441 if share_proto == 'CIFS':
442 self._cifs_clear_access(share_name, share_server, white_list)
443 elif share_proto == 'NFS': 443 ↛ exitline 443 didn't return from function 'clear_access' because the condition on line 443 was always true
444 self._nfs_clear_access(share_name, share_server, white_list)
446 @enas_utils.log_enter_exit
447 def _cifs_clear_access(self, share_name, share_server, white_list):
448 """Clear access for CIFS share except hosts in the white list."""
449 vdm_name = self._get_share_server_name(share_server)
451 # Check if CIFS server exists.
452 server_name = vdm_name
453 status, server = self._get_context('CIFSServer').get(server_name,
454 vdm_name)
455 if status != constants.STATUS_OK:
456 message = (_("CIFS server %(server_name)s has issue. "
457 "Detail: %(status)s") %
458 {'server_name': server_name, 'status': status})
459 raise exception.EMCVnxXMLAPIError(err=message)
461 self._get_context('CIFSShare').clear_share_access(
462 share_name=share_name,
463 mover_name=vdm_name,
464 domain=server['domain'],
465 white_list_users=white_list)
467 @enas_utils.log_enter_exit
468 def _nfs_clear_access(self, share_name, share_server, white_list):
469 """Clear access for NFS share except hosts in the white list."""
470 self._get_context('NFSShare').clear_share_access(
471 share_name=share_name,
472 mover_name=self._get_share_server_name(share_server),
473 white_list_hosts=white_list)
475 def deny_access(self, context, share, access, share_server=None):
476 """Deny access to a share."""
477 share_proto = share['share_proto']
479 if share_proto == 'NFS':
480 self._nfs_deny_access(share, access, share_server)
481 elif share_proto == 'CIFS':
482 self._cifs_deny_access(share, access, share_server)
483 else:
484 raise exception.InvalidShare(
485 reason=_('Unsupported share type'))
487 @enas_utils.log_enter_exit
488 def _cifs_deny_access(self, share, access, share_server):
489 """Deny access to CIFS share."""
490 vdm_name = self._get_share_server_name(share_server)
491 share_name = share['id']
493 if access['access_type'] != 'user':
494 reason = _('Only user access type allowed for CIFS share')
495 raise exception.InvalidShareAccess(reason=reason)
497 user_name = access['access_to']
499 access_level = access['access_level']
500 if access_level == const.ACCESS_LEVEL_RW:
501 cifs_access = constants.CIFS_ACL_FULLCONTROL
502 else:
503 cifs_access = constants.CIFS_ACL_READ
505 # Check if CIFS server exists.
506 server_name = vdm_name
507 status, server = self._get_context('CIFSServer').get(server_name,
508 vdm_name)
509 if status != constants.STATUS_OK:
510 message = (_("CIFS server %s not found.") % server_name)
511 LOG.error(message)
512 raise exception.EMCVnxXMLAPIError(err=message)
514 self._get_context('CIFSShare').deny_share_access(
515 vdm_name,
516 share_name,
517 user_name,
518 server['domain'],
519 access=cifs_access)
521 @enas_utils.log_enter_exit
522 def _nfs_deny_access(self, share, access, share_server):
523 """Deny access to NFS share."""
524 vdm_name = self._get_share_server_name(share_server)
526 access_type = access['access_type']
527 if access_type != 'ip':
528 reason = _('Only ip access type allowed.')
529 raise exception.InvalidShareAccess(reason=reason)
531 host_ip = enas_utils.convert_ipv6_format_if_needed(access['access_to'])
533 self._get_context('NFSShare').deny_share_access(share['id'], host_ip,
534 vdm_name)
536 def check_for_setup_error(self):
537 """Check for setup error."""
538 # To verify the input from Manila configuration
539 status, out = self._get_context('Mover').get_ref(self.mover_name,
540 True)
541 if constants.STATUS_ERROR == status:
542 message = (_("Could not find Data Mover by name: %s.") %
543 self.mover_name)
544 LOG.error(message)
545 raise exception.InvalidParameterValue(err=message)
547 self.pools = self._get_managed_storage_pools(self.pool_conf)
549 def _get_managed_storage_pools(self, pools):
550 matched_pools = set()
551 if pools:
552 # Get the real pools from the backend storage
553 status, backend_pools = self._get_context('StoragePool').get_all()
554 if status != constants.STATUS_OK:
555 message = (_("Failed to get storage pool information. "
556 "Reason: %s") % backend_pools)
557 LOG.error(message)
558 raise exception.EMCVnxXMLAPIError(err=message)
560 real_pools = set([item for item in backend_pools])
561 conf_pools = set([item.strip() for item in pools])
562 matched_pools, unmatched_pools = enas_utils.do_match_any(
563 real_pools, conf_pools)
565 if not matched_pools:
566 msg = (_("None of the specified storage pools to be managed "
567 "exist. Please check your configuration "
568 "vnx_share_data_pools in manila.conf. "
569 "The available pools in the backend are %s.") %
570 ",".join(real_pools))
571 raise exception.InvalidParameterValue(err=msg)
573 LOG.info("Storage pools: %s will be managed.",
574 ",".join(matched_pools))
575 else:
576 LOG.debug("No storage pool is specified, so all pools "
577 "in storage system will be managed.")
578 return matched_pools
580 def connect(self, emc_share_driver, context):
581 """Connect to VNX NAS server."""
582 config = emc_share_driver.configuration
583 config.append_config_values(VNX_OPTS)
584 self.mover_name = config.vnx_server_container
586 self.pool_conf = config.safe_get('vnx_share_data_pools')
588 self.reserved_percentage = config.safe_get('reserved_share_percentage')
589 if self.reserved_percentage is None: 589 ↛ 590line 589 didn't jump to line 590 because the condition on line 589 was never true
590 self.reserved_percentage = 0
592 self.reserved_snapshot_percentage = config.safe_get(
593 'reserved_share_from_snapshot_percentage')
594 if self.reserved_snapshot_percentage is None: 594 ↛ 595line 594 didn't jump to line 595 because the condition on line 594 was never true
595 self.reserved_snapshot_percentage = self.reserved_percentage
597 self.reserved_share_extend_percentage = config.safe_get(
598 'reserved_share_extend_percentage')
599 if self.reserved_share_extend_percentage is None: 599 ↛ 600line 599 didn't jump to line 600 because the condition on line 599 was never true
600 self.reserved_share_extend_percentage = self.reserved_percentage
602 self.manager = manager.StorageObjectManager(config)
603 self.port_conf = config.safe_get('vnx_ethernet_ports')
605 def get_managed_ports(self):
606 # Get the real ports(devices) list from the backend storage
607 real_ports = self._get_physical_devices(self.mover_name)
609 if not self.port_conf:
610 LOG.debug("No ports are specified, so any of the ports on the "
611 "Data Mover can be used.")
612 return real_ports
614 matched_ports, unmanaged_ports = enas_utils.do_match_any(
615 real_ports, self.port_conf)
617 if not matched_ports:
618 msg = (_("None of the specified network ports exist. "
619 "Please check your configuration vnx_ethernet_ports "
620 "in manila.conf. The available ports on the Data Mover "
621 "are %s.") %
622 ",".join(real_ports))
623 raise exception.BadConfigurationException(reason=msg)
625 LOG.debug("Ports: %s can be used.", ",".join(matched_ports))
627 return list(matched_ports)
629 def update_share_stats(self, stats_dict):
630 """Communicate with EMCNASClient to get the stats."""
631 stats_dict['driver_version'] = VERSION
633 self._get_context('Mover').get_ref(self.mover_name, True)
635 stats_dict['pools'] = []
637 status, pools = self._get_context('StoragePool').get_all()
638 for name, pool in pools.items():
639 if not self.pools or pool['name'] in self.pools:
640 total_size = float(pool['total_size'])
641 used_size = float(pool['used_size'])
643 pool_stat = dict(
644 pool_name=pool['name'],
645 total_capacity_gb=enas_utils.mb_to_gb(total_size),
646 free_capacity_gb=enas_utils.mb_to_gb(
647 total_size - used_size),
648 qos=False,
649 reserved_percentage=self.reserved_percentage,
650 reserved_snapshot_percentage=(
651 self.reserved_snapshot_percentage),
652 reserved_share_extend_percentage=(
653 self.reserved_share_extend_percentage),
654 )
655 stats_dict['pools'].append(pool_stat)
657 if not stats_dict['pools']:
658 message = _("Failed to update storage pool.")
659 LOG.error(message)
660 raise exception.EMCVnxXMLAPIError(err=message)
662 def get_pool(self, share):
663 """Get the pool name of the share."""
664 share_name = share['id']
665 status, filesystem = self._get_context('FileSystem').get(share_name)
666 if status != constants.STATUS_OK:
667 message = (_("File System %(name)s not found. "
668 "Reason: %(err)s") %
669 {'name': share_name, 'err': filesystem})
670 LOG.error(message)
671 raise exception.EMCVnxXMLAPIError(err=message)
673 pool_id = filesystem['pools_id'][0]
675 # Get the real pools from the backend storage
676 status, backend_pools = self._get_context('StoragePool').get_all()
677 if status != constants.STATUS_OK:
678 message = (_("Failed to get storage pool information. "
679 "Reason: %s") % backend_pools)
680 LOG.error(message)
681 raise exception.EMCVnxXMLAPIError(err=message)
683 for name, pool_info in backend_pools.items():
684 if pool_info['id'] == pool_id:
685 return name
687 available_pools = [item for item in backend_pools]
688 message = (_("No matched pool name for share: %(share)s. "
689 "Available pools: %(pools)s") %
690 {'share': share_name, 'pools': available_pools})
691 raise exception.EMCVnxXMLAPIError(err=message)
693 def get_network_allocations_number(self):
694 """Returns number of network allocations for creating VIFs."""
695 return constants.IP_ALLOCATIONS
697 def setup_server(self, network_info, metadata=None):
698 """Set up and configures share server with given network parameters."""
699 # Only support single security service with type 'active_directory'
700 vdm_name = network_info['server_id']
701 vlan_id = network_info['segmentation_id']
702 active_directory = None
703 allocated_interfaces = []
705 if network_info.get('security_services'): 705 ↛ 712line 705 didn't jump to line 712 because the condition on line 705 was always true
706 is_valid, active_directory = self._get_valid_security_service(
707 network_info['security_services'])
709 if not is_valid:
710 raise exception.EMCVnxXMLAPIError(err=active_directory)
712 try:
713 if not self._vdm_exist(vdm_name):
714 LOG.debug('Share server %s not found, creating '
715 'share server...', vdm_name)
716 self._get_context('VDM').create(vdm_name, self.mover_name)
718 devices = self.get_managed_ports()
720 for net_info in network_info['network_allocations']:
721 random.shuffle(devices)
723 ip_version = net_info['ip_version']
725 interface = {
726 'name': net_info['id'][-12:],
727 'device_name': devices[0],
728 'ip': net_info['ip_address'],
729 'mover_name': self.mover_name,
730 'vlan_id': vlan_id if vlan_id else -1,
731 }
733 if ip_version == 6:
734 interface['ip_version'] = ip_version
735 interface['net_mask'] = str(
736 utils.cidr_to_prefixlen(network_info['cidr']))
737 else:
738 interface['net_mask'] = utils.cidr_to_netmask(
739 network_info['cidr'])
741 self._get_context('MoverInterface').create(interface)
743 allocated_interfaces.append(interface)
745 cifs_interface = allocated_interfaces[0]
746 nfs_interface = allocated_interfaces[1]
747 if active_directory: 747 ↛ 751line 747 didn't jump to line 751 because the condition on line 747 was always true
748 self._configure_active_directory(
749 active_directory, vdm_name, cifs_interface)
751 self._get_context('VDM').attach_nfs_interface(
752 vdm_name, nfs_interface['name'])
754 return {
755 'share_server_name': vdm_name,
756 'cifs_if': cifs_interface['ip'],
757 'nfs_if': nfs_interface['ip'],
758 }
760 except Exception:
761 with excutils.save_and_reraise_exception():
762 LOG.exception('Could not setup server.')
763 server_details = self._construct_backend_details(
764 vdm_name, allocated_interfaces)
765 self.teardown_server(
766 server_details, network_info['security_services'])
768 def _construct_backend_details(self, vdm_name, interfaces):
769 if_number = len(interfaces)
770 cifs_if = interfaces[0]['ip'] if if_number > 0 else None
771 nfs_if = interfaces[1]['ip'] if if_number > 1 else None
773 return {
774 'share_server_name': vdm_name,
775 'cifs_if': cifs_if,
776 'nfs_if': nfs_if,
777 }
779 @enas_utils.log_enter_exit
780 def _vdm_exist(self, name):
781 status, out = self._get_context('VDM').get(name)
782 if constants.STATUS_OK != status:
783 return False
785 return True
787 def _get_physical_devices(self, mover_name):
788 """Get a proper network device to create interface."""
789 devices = self._get_context('Mover').get_physical_devices(mover_name)
790 if not devices:
791 message = (_("Could not get physical device port on mover %s.") %
792 self.mover_name)
793 LOG.error(message)
794 raise exception.EMCVnxXMLAPIError(err=message)
796 return devices
798 def _configure_active_directory(
799 self, security_service, vdm_name, interface):
801 domain = security_service['domain']
802 server = security_service['dns_ip']
804 self._get_context('DNSDomain').create(self.mover_name, domain, server)
806 cifs_server_args = {
807 'name': vdm_name,
808 'interface_ip': interface['ip'],
809 'domain_name': security_service['domain'],
810 'user_name': security_service['user'],
811 'password': security_service['password'],
812 'mover_name': vdm_name,
813 'is_vdm': True,
814 }
816 self._get_context('CIFSServer').create(cifs_server_args)
818 def teardown_server(self, server_details, security_services=None):
819 """Teardown share server."""
820 if not server_details:
821 LOG.debug('Server details are empty.')
822 return
824 vdm_name = server_details.get('share_server_name')
825 if not vdm_name:
826 LOG.debug('No share server found in server details.')
827 return
829 cifs_if = server_details.get('cifs_if')
830 nfs_if = server_details.get('nfs_if')
832 status, vdm = self._get_context('VDM').get(vdm_name)
833 if constants.STATUS_OK != status:
834 LOG.debug('Share server %s not found.', vdm_name)
835 return
837 interfaces = self._get_context('VDM').get_interfaces(vdm_name)
839 for if_name in interfaces['nfs']:
840 self._get_context('VDM').detach_nfs_interface(vdm_name, if_name)
842 if security_services:
843 # Only support single security service with type 'active_directory'
844 is_valid, active_directory = self._get_valid_security_service(
845 security_services)
846 if is_valid: 846 ↛ 876line 846 didn't jump to line 876 because the condition on line 846 was always true
847 status, servers = self._get_context('CIFSServer').get_all(
848 vdm_name)
849 if constants.STATUS_OK != status:
850 LOG.error('Could not find CIFS server by name: %s.',
851 vdm_name)
852 else:
853 cifs_servers = copy.deepcopy(servers)
854 for name, server in cifs_servers.items():
855 # Unjoin CIFS Server from domain
856 cifs_server_args = {
857 'name': server['name'],
858 'join_domain': False,
859 'user_name': active_directory['user'],
860 'password': active_directory['password'],
861 'mover_name': vdm_name,
862 'is_vdm': True,
863 }
865 try:
866 self._get_context('CIFSServer').modify(
867 cifs_server_args)
868 except exception.EMCVnxXMLAPIError as expt:
869 LOG.debug("Failed to modify CIFS server "
870 "%(server)s. Reason: %(err)s.",
871 {'server': server, 'err': expt})
873 self._get_context('CIFSServer').delete(name, vdm_name)
875 # Delete interface from Data Mover
876 if cifs_if:
877 self._get_context('MoverInterface').delete(cifs_if,
878 self.mover_name)
880 if nfs_if:
881 self._get_context('MoverInterface').delete(nfs_if,
882 self.mover_name)
884 # Delete Virtual Data Mover
885 self._get_context('VDM').delete(vdm_name)
887 def _get_valid_security_service(self, security_services):
888 """Validate security services and return a supported security service.
890 :param security_services:
891 :returns: (<is_valid>, <data>) -- <is_valid> is true to indicate
892 security_services includes zero or single security service for
893 active directory. Otherwise, it would return false. <data> return
894 error message when <is_valid> is false. Otherwise, it will
895 return zero or single security service for active directory.
896 """
898 # Only support single security service with type 'active_directory'
899 service_number = len(security_services)
901 if (service_number > 1 or
902 security_services[0]['type'] != 'active_directory'):
903 return False, _("Unsupported security services. "
904 "Only support single security service and "
905 "only support type 'active_directory'")
907 return True, security_services[0]
909 def _get_share_server_name(self, share_server):
910 try:
911 return share_server['backend_details']['share_server_name']
912 except Exception:
913 LOG.debug("Didn't get share server name from share_server %s.",
914 share_server)
915 return share_server['id']
917 def _get_context(self, type):
918 return self.manager.getStorageContext(type)