Coverage for manila/share/drivers/dell_emc/plugins/powermax/connection.py: 0%
471 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) 2019 Dell Inc. or its subsidiaries.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15"""PowerMax backend for the Dell 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.powermax import (
32 object_manager as manager)
33from manila.share import utils as share_utils
34from manila import utils
36"""Version history:
37 1.0.0 - Initial version
38 2.0.0 - Implement IPv6 support
39 3.0.0 - Rebranding to PowerMax
40 3.1.0 - Access Host details prevents a read-only share mounts
41 (bug #1845147)
42 3.2.0 - Wrong format of export locations (bug #1871999)
43 3.3.0 - Victoria release
44 3.4.0 - Wallaby release
45 3.5.0 - Xena release
46"""
47VERSION = "3.5.0"
49LOG = log.getLogger(__name__)
51POWERMAX_OPTS = [
52 cfg.StrOpt('powermax_server_container',
53 help='Data mover to host the NAS server.'),
54 cfg.ListOpt('powermax_share_data_pools',
55 help='Comma separated list of pools that can be used to '
56 'persist share data.'),
57 cfg.ListOpt('powermax_ethernet_ports',
58 help='Comma separated list of ports that can be used for '
59 'share server interfaces. Members of the list '
60 'can be Unix-style glob expressions.')
61]
63CONF = cfg.CONF
64CONF.register_opts(POWERMAX_OPTS)
67@enas_utils.decorate_all_methods(enas_utils.log_enter_exit,
68 debug_only=True)
69class PowerMaxStorageConnection(driver.StorageConnection):
70 """Implements powermax specific functionality for Dell EMC Manila driver.
72 """
73 @enas_utils.log_enter_exit
74 def __init__(self, *args, **kwargs):
75 super(PowerMaxStorageConnection, self).__init__(*args, **kwargs)
76 if 'configuration' in kwargs:
77 kwargs['configuration'].append_config_values(POWERMAX_OPTS)
79 self.mover_name = None
80 self.pools = None
81 self.manager = None
82 self.pool_conf = None
83 self.reserved_percentage = None
84 self.reserved_snapshot_percentage = None
85 self.reserved_share_extend_percentage = None
86 self.driver_handles_share_servers = True
87 self.port_conf = None
88 self.ipv6_implemented = True
89 self.dhss_mandatory_security_service_association = {
90 'nfs': None,
91 'cifs': ['active_directory', ]
92 }
94 def create_share(self, context, share, share_server=None):
95 """Create a share and export it based on protocol used."""
96 share_name = share['id']
97 size = share['size'] * units.Ki
99 share_proto = share['share_proto'].upper()
101 # Validate the share protocol
102 if share_proto not in ('NFS', 'CIFS'):
103 raise exception.InvalidShare(
104 reason=(_('Invalid NAS protocol supplied: %s.')
105 % share_proto))
107 # Get the pool name from share host field
108 pool_name = share_utils.extract_host(share['host'], level='pool')
109 if not pool_name:
110 message = (_("Pool is not available in the share host %s.") %
111 share['host'])
112 raise exception.InvalidHost(reason=message)
114 # Validate share server
115 self._share_server_validation(share_server)
117 if share_proto == 'CIFS':
118 vdm_name = self._get_share_server_name(share_server)
119 server_name = vdm_name
121 # Check if CIFS server exists.
122 status, server = self._get_context('CIFSServer').get(server_name,
123 vdm_name)
124 if status != constants.STATUS_OK:
125 message = (_("CIFS server %s not found.") % server_name)
126 LOG.error(message)
127 raise exception.EMCPowerMaxXMLAPIError(err=message)
129 self._allocate_container(share_name, size, share_server, pool_name)
131 if share_proto == 'NFS':
132 location = self._create_nfs_share(share_name, share_server)
133 elif share_proto == 'CIFS':
134 location = self._create_cifs_share(share_name, share_server)
136 return [
137 {'path': location}
138 ]
140 def _share_server_validation(self, share_server):
141 """Validate the share server."""
142 if not share_server:
143 msg = _('Share server not provided')
144 raise exception.InvalidInput(reason=msg)
146 backend_details = share_server.get('backend_details')
147 vdm = backend_details.get(
148 'share_server_name') if backend_details else None
150 if vdm is None:
151 message = _("No share server found.")
152 LOG.error(message)
153 raise exception.EMCPowerMaxXMLAPIError(err=message)
155 def _allocate_container(self, share_name, size, share_server, pool_name):
156 """Allocate file system for share."""
157 vdm_name = self._get_share_server_name(share_server)
159 self._get_context('FileSystem').create(
160 share_name, size, pool_name, vdm_name)
162 def _allocate_container_from_snapshot(self, share, snapshot, share_server,
163 pool_name):
164 """Allocate file system from snapshot."""
165 vdm_name = self._get_share_server_name(share_server)
167 interconn_id = self._get_context('Mover').get_interconnect_id(
168 self.mover_name, self.mover_name)
170 self._get_context('FileSystem').create_from_snapshot(
171 share['id'], snapshot['id'], snapshot['share_id'],
172 pool_name, vdm_name, interconn_id)
174 nwe_size = share['size'] * units.Ki
175 self._get_context('FileSystem').extend(share['id'], pool_name,
176 nwe_size)
178 @enas_utils.log_enter_exit
179 def _create_cifs_share(self, share_name, share_server):
180 """Create CIFS share."""
181 vdm_name = self._get_share_server_name(share_server)
182 server_name = vdm_name
184 # Get available CIFS Server and interface (one CIFS server per VDM)
185 status, server = self._get_context('CIFSServer').get(server_name,
186 vdm_name)
188 if 'interfaces' not in server or len(server['interfaces']) == 0:
189 message = (_("CIFS server %s doesn't have interface, "
190 "so the share is inaccessible.")
191 % server['compName'])
192 LOG.error(message)
193 raise exception.EMCPowerMaxXMLAPIError(err=message)
195 interface = enas_utils.export_unc_path(server['interfaces'][0])
197 self._get_context('CIFSShare').create(share_name, server['name'],
198 vdm_name)
200 self._get_context('CIFSShare').disable_share_access(share_name,
201 vdm_name)
202 location = (r'\\%(interface)s\%(name)s' %
203 {'interface': interface, 'name': share_name})
204 return location
206 @enas_utils.log_enter_exit
207 def _create_nfs_share(self, share_name, share_server):
208 """Create NFS share."""
209 vdm_name = self._get_share_server_name(share_server)
211 self._get_context('NFSShare').create(share_name, vdm_name)
213 nfs_if = enas_utils.convert_ipv6_format_if_needed(
214 share_server['backend_details']['nfs_if'])
216 return ('%(nfs_if)s:/%(share_name)s'
217 % {'nfs_if': nfs_if,
218 'share_name': share_name})
220 def create_share_from_snapshot(self, context, share, snapshot,
221 share_server=None, parent_share=None):
222 """Create a share from a snapshot - clone a snapshot."""
223 share_name = share['id']
225 share_proto = share['share_proto'].upper()
227 # Validate the share protocol
228 if share_proto not in ('NFS', 'CIFS'):
229 raise exception.InvalidShare(
230 reason=(_('Invalid NAS protocol supplied: %s.')
231 % share_proto))
233 # Get the pool name from share host field
234 pool_name = share_utils.extract_host(share['host'], level='pool')
235 if not pool_name:
236 message = (_("Pool is not available in the share host %s.") %
237 share['host'])
238 raise exception.InvalidHost(reason=message)
240 self._share_server_validation(share_server)
242 self._allocate_container_from_snapshot(
243 share, snapshot, share_server, pool_name)
245 nfs_if = enas_utils.convert_ipv6_format_if_needed(
246 share_server['backend_details']['nfs_if'])
248 if share_proto == 'NFS':
249 self._create_nfs_share(share_name, share_server)
250 location = ('%(nfs_if)s:/%(share_name)s'
251 % {'nfs_if': nfs_if,
252 'share_name': share_name})
253 elif share_proto == 'CIFS':
254 location = self._create_cifs_share(share_name, share_server)
256 return [
257 {'path': location}
258 ]
260 def create_snapshot(self, context, snapshot, share_server=None):
261 """Create snapshot from share."""
262 share_name = snapshot['share_id']
263 status, filesystem = self._get_context('FileSystem').get(share_name)
264 if status != constants.STATUS_OK:
265 message = (_("File System %s not found.") % share_name)
266 LOG.error(message)
267 raise exception.EMCPowerMaxXMLAPIError(err=message)
269 pool_id = filesystem['pools_id'][0]
271 self._get_context('Snapshot').create(snapshot['id'],
272 snapshot['share_id'],
273 pool_id)
275 def delete_share(self, context, share, share_server=None):
276 """Delete a share."""
277 if share_server is None:
278 LOG.warning("Share network should be specified for "
279 "share deletion.")
280 return
282 share_proto = share['share_proto'].upper()
284 if share_proto == 'NFS':
285 self._delete_nfs_share(share, share_server)
286 elif share_proto == 'CIFS':
287 self._delete_cifs_share(share, share_server)
288 else:
289 raise exception.InvalidShare(
290 reason=_('Unsupported share protocol'))
292 @enas_utils.log_enter_exit
293 def _delete_cifs_share(self, share, share_server):
294 """Delete CIFS share."""
295 vdm_name = self._get_share_server_name(share_server)
297 name = share['id']
299 self._get_context('CIFSShare').delete(name, vdm_name)
301 self._deallocate_container(name, vdm_name)
303 @enas_utils.log_enter_exit
304 def _delete_nfs_share(self, share, share_server):
305 """Delete NFS share."""
306 vdm_name = self._get_share_server_name(share_server)
308 name = share['id']
310 self._get_context('NFSShare').delete(name, vdm_name)
312 self._deallocate_container(name, vdm_name)
314 @enas_utils.log_enter_exit
315 def _deallocate_container(self, share_name, vdm_name):
316 """Delete underneath objects of the share."""
317 path = '/' + share_name
319 try:
320 # Delete mount point
321 self._get_context('MountPoint').delete(path, vdm_name)
322 except exception.EMCPowerMaxXMLAPIError as e:
323 LOG.exception("CIFS server %(name)s on mover %(mover_name)s "
324 "not found due to error %(err)s. Skip the "
325 "deletion.",
326 {'name': path, 'mover_name': vdm_name,
327 'err': e.message})
329 try:
330 # Delete file system
331 self._get_context('FileSystem').delete(share_name)
332 except exception.EMCPowerMaxXMLAPIError as e:
333 LOG.exception("File system %(share_name)s not found due to "
334 "error %(err)s. Skip the deletion.",
335 {'share_name': share_name,
336 'err': e.message})
338 def delete_snapshot(self, context, snapshot, share_server=None):
339 """Delete a snapshot."""
340 self._get_context('Snapshot').delete(snapshot['id'])
342 def ensure_share(self, context, share, share_server=None):
343 """Ensure that the share is exported."""
345 def extend_share(self, share, new_size, share_server=None):
346 # Get the pool name from share host field
347 pool_name = share_utils.extract_host(share['host'], level='pool')
348 if not pool_name:
349 message = (_("Pool is not available in the share host %s.") %
350 share['host'])
351 raise exception.InvalidHost(reason=message)
353 share_name = share['id']
355 self._get_context('FileSystem').extend(
356 share_name, pool_name, new_size * units.Ki)
358 def allow_access(self, context, share, access, share_server=None):
359 """Allow access to a share."""
360 access_level = access['access_level']
361 if access_level not in const.ACCESS_LEVELS:
362 raise exception.InvalidShareAccessLevel(level=access_level)
364 share_proto = share['share_proto']
366 if share_proto == 'NFS':
367 self._nfs_allow_access(context, share, access, share_server)
368 elif share_proto == 'CIFS':
369 self._cifs_allow_access(context, share, access, share_server)
370 else:
371 raise exception.InvalidShare(
372 reason=(_('Invalid NAS protocol supplied: %s.')
373 % share_proto))
375 @enas_utils.log_enter_exit
376 def _cifs_allow_access(self, context, share, access, share_server):
377 """Allow access to CIFS share."""
378 vdm_name = self._get_share_server_name(share_server)
379 share_name = share['id']
381 if access['access_type'] != 'user':
382 reason = _('Only user access type allowed for CIFS share')
383 raise exception.InvalidShareAccess(reason=reason)
385 user_name = access['access_to']
387 access_level = access['access_level']
388 if access_level == const.ACCESS_LEVEL_RW:
389 cifs_access = constants.CIFS_ACL_FULLCONTROL
390 else:
391 cifs_access = constants.CIFS_ACL_READ
393 # Check if CIFS server exists.
394 server_name = vdm_name
395 status, server = self._get_context('CIFSServer').get(server_name,
396 vdm_name)
397 if status != constants.STATUS_OK:
398 message = (_("CIFS server %s not found.") % server_name)
399 LOG.error(message)
400 raise exception.EMCPowerMaxXMLAPIError(err=message)
402 self._get_context('CIFSShare').allow_share_access(
403 vdm_name,
404 share_name,
405 user_name,
406 server['domain'],
407 access=cifs_access)
409 @enas_utils.log_enter_exit
410 def _nfs_allow_access(self, context, share, access, share_server):
411 """Allow access to NFS share."""
412 vdm_name = self._get_share_server_name(share_server)
414 access_type = access['access_type']
415 if access_type != 'ip':
416 reason = _('Only ip access type allowed.')
417 raise exception.InvalidShareAccess(reason=reason)
419 host_ip = access['access_to']
420 access_level = access['access_level']
422 self._get_context('NFSShare').allow_share_access(
423 share['id'], host_ip, vdm_name, access_level)
425 def update_access(self, context, share, access_rules, add_rules,
426 delete_rules, share_server=None):
427 # deleting rules
428 for rule in delete_rules:
429 self.deny_access(context, share, rule, share_server)
431 # adding rules
432 for rule in add_rules:
433 self.allow_access(context, share, rule, share_server)
435 # recovery mode
436 if not (add_rules or delete_rules):
437 white_list = []
438 for rule in access_rules:
439 self.allow_access(context, share, rule, share_server)
440 white_list.append(
441 enas_utils.convert_ipv6_format_if_needed(
442 rule['access_to']))
443 self.clear_access(share, share_server, white_list)
445 def clear_access(self, share, share_server, white_list):
446 share_proto = share['share_proto'].upper()
447 share_name = share['id']
448 if share_proto == 'CIFS':
449 self._cifs_clear_access(share_name, share_server, white_list)
450 elif share_proto == 'NFS':
451 self._nfs_clear_access(share_name, share_server, white_list)
453 @enas_utils.log_enter_exit
454 def _cifs_clear_access(self, share_name, share_server, white_list):
455 """Clear access for CIFS share except hosts in the white list."""
456 vdm_name = self._get_share_server_name(share_server)
458 # Check if CIFS server exists.
459 server_name = vdm_name
460 status, server = self._get_context('CIFSServer').get(server_name,
461 vdm_name)
462 if status != constants.STATUS_OK:
463 message = (_("CIFS server %(server_name)s has issue. "
464 "Detail: %(status)s") %
465 {'server_name': server_name, 'status': status})
466 raise exception.EMCPowerMaxXMLAPIError(err=message)
468 self._get_context('CIFSShare').clear_share_access(
469 share_name=share_name,
470 mover_name=vdm_name,
471 domain=server['domain'],
472 white_list_users=white_list)
474 @enas_utils.log_enter_exit
475 def _nfs_clear_access(self, share_name, share_server, white_list):
476 """Clear access for NFS share except hosts in the white list."""
477 self._get_context('NFSShare').clear_share_access(
478 share_name=share_name,
479 mover_name=self._get_share_server_name(share_server),
480 white_list_hosts=white_list)
482 def deny_access(self, context, share, access, share_server=None):
483 """Deny access to a share."""
484 share_proto = share['share_proto']
486 if share_proto == 'NFS':
487 self._nfs_deny_access(share, access, share_server)
488 elif share_proto == 'CIFS':
489 self._cifs_deny_access(share, access, share_server)
490 else:
491 raise exception.InvalidShare(
492 reason=_('Unsupported share protocol'))
494 @enas_utils.log_enter_exit
495 def _cifs_deny_access(self, share, access, share_server):
496 """Deny access to CIFS share."""
497 vdm_name = self._get_share_server_name(share_server)
498 share_name = share['id']
500 if access['access_type'] != 'user':
501 LOG.warning("Only user access type allowed for CIFS share.")
502 return
504 user_name = access['access_to']
506 access_level = access['access_level']
507 if access_level == const.ACCESS_LEVEL_RW:
508 cifs_access = constants.CIFS_ACL_FULLCONTROL
509 else:
510 cifs_access = constants.CIFS_ACL_READ
512 # Check if CIFS server exists.
513 server_name = vdm_name
514 status, server = self._get_context('CIFSServer').get(server_name,
515 vdm_name)
516 if status != constants.STATUS_OK:
517 message = (_("CIFS server %s not found.") % server_name)
518 LOG.error(message)
519 raise exception.EMCPowerMaxXMLAPIError(err=message)
521 self._get_context('CIFSShare').deny_share_access(
522 vdm_name,
523 share_name,
524 user_name,
525 server['domain'],
526 access=cifs_access)
528 @enas_utils.log_enter_exit
529 def _nfs_deny_access(self, share, access, share_server):
530 """Deny access to NFS share."""
531 vdm_name = self._get_share_server_name(share_server)
533 access_type = access['access_type']
534 if access_type != 'ip':
535 LOG.warning("Only ip access type allowed.")
536 return
538 host_ip = enas_utils.convert_ipv6_format_if_needed(access['access_to'])
540 self._get_context('NFSShare').deny_share_access(share['id'], host_ip,
541 vdm_name)
543 def check_for_setup_error(self):
544 """Check for setup error."""
545 # To verify the input from Manila configuration
546 status, out = self._get_context('Mover').get_ref(self.mover_name,
547 True)
548 if constants.STATUS_ERROR == status:
549 message = (_("Could not find Data Mover by name: %s.") %
550 self.mover_name)
551 LOG.error(message)
552 raise exception.InvalidParameterValue(err=message)
554 self.pools = self._get_managed_storage_pools(self.pool_conf)
556 def _get_managed_storage_pools(self, pools):
557 matched_pools = set()
558 if pools:
559 # Get the real pools from the backend storage
560 status, backend_pools = self._get_context('StoragePool').get_all()
561 if status != constants.STATUS_OK:
562 message = (_("Failed to get storage pool information. "
563 "Reason: %s") % backend_pools)
564 LOG.error(message)
565 raise exception.EMCPowerMaxXMLAPIError(err=message)
567 real_pools = set([item for item in backend_pools])
568 conf_pools = set([item.strip() for item in pools])
569 matched_pools, unmatched_pools = enas_utils.do_match_any(
570 real_pools, conf_pools)
572 if not matched_pools:
573 msg = (_("None of the specified storage pools to be managed "
574 "exist. Please check your configuration "
575 "emc_nas_pool_names in manila.conf. "
576 "The available pools in the backend are %s.") %
577 ",".join(real_pools))
578 raise exception.InvalidParameterValue(err=msg)
580 LOG.info("Storage pools: %s will be managed.",
581 ",".join(matched_pools))
582 else:
583 LOG.debug("No storage pool is specified, so all pools "
584 "in storage system will be managed.")
585 return matched_pools
587 def connect(self, emc_share_driver, context):
588 """Connect to PowerMax NAS server."""
589 config = emc_share_driver.configuration
590 config.append_config_values(POWERMAX_OPTS)
591 self.mover_name = config.safe_get('powermax_server_container')
593 self.pool_conf = config.safe_get('powermax_share_data_pools')
595 self.reserved_percentage = config.safe_get('reserved_share_percentage')
596 if self.reserved_percentage is None:
597 self.reserved_percentage = 0
599 self.reserved_snapshot_percentage = config.safe_get(
600 'reserved_share_from_snapshot_percentage')
601 if self.reserved_snapshot_percentage is None:
602 self.reserved_snapshot_percentage = self.reserved_percentage
604 self.reserved_share_extend_percentage = config.safe_get(
605 'reserved_share_extend_percentage')
606 if self.reserved_share_extend_percentage is None:
607 self.reserved_share_extend_percentage = self.reserved_percentage
609 self.manager = manager.StorageObjectManager(config)
610 self.port_conf = config.safe_get('powermax_ethernet_ports')
612 def get_managed_ports(self):
613 # Get the real ports(devices) list from the backend storage
614 real_ports = self._get_physical_devices(self.mover_name)
616 if not self.port_conf:
617 LOG.debug("No ports are specified, so any of the ports on the "
618 "Data Mover can be used.")
619 return real_ports
621 matched_ports, unmanaged_ports = enas_utils.do_match_any(
622 real_ports, self.port_conf)
624 if not matched_ports:
625 msg = (_("None of the specified network ports exist. "
626 "Please check your configuration powermax_ethernet_ports "
627 "in manila.conf. The available ports on the Data Mover "
628 "are %s.") %
629 ",".join(real_ports))
630 raise exception.BadConfigurationException(reason=msg)
632 LOG.debug("Ports: %s can be used.", ",".join(matched_ports))
634 return list(matched_ports)
636 def update_share_stats(self, stats_dict):
637 """Communicate with EMCNASClient to get the stats."""
638 stats_dict['driver_version'] = VERSION
640 self._get_context('Mover').get_ref(self.mover_name, True)
642 stats_dict['pools'] = []
644 status, pools = self._get_context('StoragePool').get_all()
645 for name, pool in pools.items():
646 if not self.pools or pool['name'] in self.pools:
647 total_size = float(pool['total_size'])
648 used_size = float(pool['used_size'])
650 pool_stat = {
651 'pool_name': pool['name'],
652 'total_capacity_gb': enas_utils.mb_to_gb(total_size),
653 'free_capacity_gb':
654 enas_utils.mb_to_gb(total_size - used_size),
655 'qos': False,
656 'reserved_percentage': self.reserved_percentage,
657 'reserved_snapshot_percentage':
658 self.reserved_snapshot_percentage,
659 'reserved_share_extend_percentage':
660 self.reserved_share_extend_percentage,
661 'snapshot_support': True,
662 'create_share_from_snapshot_support': True,
663 'revert_to_snapshot_support': False,
664 'ipv6_support': True
665 }
666 stats_dict['pools'].append(pool_stat)
668 if not stats_dict['pools']:
669 message = _("Failed to update storage pool.")
670 LOG.error(message)
671 raise exception.EMCPowerMaxXMLAPIError(err=message)
673 def get_pool(self, share):
674 """Get the pool name of the share."""
675 share_name = share['id']
676 status, filesystem = self._get_context('FileSystem').get(share_name)
677 if status != constants.STATUS_OK:
678 message = (_("File System %(name)s not found. "
679 "Reason: %(err)s") %
680 {'name': share_name, 'err': filesystem})
681 LOG.error(message)
682 raise exception.EMCPowerMaxXMLAPIError(err=message)
684 pool_id = filesystem['pools_id'][0]
686 # Get the real pools from the backend storage
687 status, backend_pools = self._get_context('StoragePool').get_all()
688 if status != constants.STATUS_OK:
689 message = (_("Failed to get storage pool information. "
690 "Reason: %s") % backend_pools)
691 LOG.error(message)
692 raise exception.EMCPowerMaxXMLAPIError(err=message)
694 for name, pool_info in backend_pools.items():
695 if pool_info['id'] == pool_id:
696 return name
698 available_pools = [item for item in backend_pools]
699 message = (_("No matched pool name for share: %(share)s. "
700 "Available pools: %(pools)s") %
701 {'share': share_name, 'pools': available_pools})
702 raise exception.EMCPowerMaxXMLAPIError(err=message)
704 def get_network_allocations_number(self):
705 """Returns number of network allocations for creating VIFs."""
706 return constants.IP_ALLOCATIONS
708 def setup_server(self, network_info, metadata=None):
709 """Set up and configure share server.
711 Sets up and configures share server with given network parameters.
712 """
713 # Only support single security service with type 'active_directory'
714 vdm_name = network_info['server_id']
715 vlan_id = network_info['segmentation_id']
716 active_directory = None
717 allocated_interfaces = []
719 if network_info.get('security_services'):
720 is_valid, active_directory = self._get_valid_security_service(
721 network_info['security_services'])
723 if not is_valid:
724 raise exception.EMCPowerMaxXMLAPIError(err=active_directory)
726 try:
727 if not self._vdm_exist(vdm_name):
728 LOG.debug('Share server %s not found, creating '
729 'share server...', vdm_name)
730 self._get_context('VDM').create(vdm_name, self.mover_name)
732 devices = self.get_managed_ports()
734 for net_info in network_info['network_allocations']:
735 random.shuffle(devices)
736 ip_version = net_info['ip_version']
737 interface = {
738 'name': net_info['id'][-12:],
739 'device_name': devices[0],
740 'ip': net_info['ip_address'],
741 'mover_name': self.mover_name,
742 'vlan_id': vlan_id if vlan_id else -1,
743 }
744 if ip_version == 6:
745 interface['ip_version'] = ip_version
746 interface['net_mask'] = str(
747 utils.cidr_to_prefixlen(
748 network_info['cidr']))
749 else:
750 interface['net_mask'] = utils.cidr_to_netmask(
751 network_info['cidr'])
753 self._get_context('MoverInterface').create(interface)
755 allocated_interfaces.append(interface)
757 cifs_interface = allocated_interfaces[0]
758 nfs_interface = allocated_interfaces[1]
759 if active_directory:
760 self._configure_active_directory(
761 active_directory, vdm_name, cifs_interface)
763 self._get_context('VDM').attach_nfs_interface(
764 vdm_name, nfs_interface['name'])
766 return {
767 'share_server_name': vdm_name,
768 'cifs_if': cifs_interface['ip'],
769 'nfs_if': nfs_interface['ip'],
770 }
772 except Exception:
773 with excutils.save_and_reraise_exception():
774 LOG.exception('Could not setup server')
775 server_details = self._construct_backend_details(
776 vdm_name, allocated_interfaces)
777 self.teardown_server(
778 server_details, network_info['security_services'])
780 def _construct_backend_details(self, vdm_name, interfaces):
781 if_number = len(interfaces)
782 cifs_if = interfaces[0]['ip'] if if_number > 0 else None
783 nfs_if = interfaces[1]['ip'] if if_number > 1 else None
785 return {
786 'share_server_name': vdm_name,
787 'cifs_if': cifs_if,
788 'nfs_if': nfs_if,
789 }
791 @enas_utils.log_enter_exit
792 def _vdm_exist(self, name):
793 status, out = self._get_context('VDM').get(name)
794 if constants.STATUS_OK != status:
795 return False
797 return True
799 def _get_physical_devices(self, mover_name):
800 """Get a proper network device to create interface."""
801 devices = self._get_context('Mover').get_physical_devices(mover_name)
802 if not devices:
803 message = (_("Could not get physical device port on mover %s.") %
804 self.mover_name)
805 LOG.error(message)
806 raise exception.EMCPowerMaxXMLAPIError(err=message)
808 return devices
810 def _configure_active_directory(
811 self, security_service, vdm_name, interface):
813 domain = security_service['domain']
814 server = security_service['dns_ip']
816 self._get_context('DNSDomain').create(self.mover_name, domain, server)
818 cifs_server_args = {
819 'name': vdm_name,
820 'interface_ip': interface['ip'],
821 'domain_name': security_service['domain'],
822 'user_name': security_service['user'],
823 'password': security_service['password'],
824 'mover_name': vdm_name,
825 'is_vdm': True,
826 }
828 self._get_context('CIFSServer').create(cifs_server_args)
830 def teardown_server(self, server_details, security_services=None):
831 """Teardown share server."""
832 if not server_details:
833 LOG.debug('Server details are empty.')
834 return
836 vdm_name = server_details.get('share_server_name')
837 if not vdm_name:
838 LOG.debug('No share server found in server details.')
839 return
841 cifs_if = server_details.get('cifs_if')
842 nfs_if = server_details.get('nfs_if')
844 status, vdm = self._get_context('VDM').get(vdm_name)
845 if constants.STATUS_OK != status:
846 LOG.debug('Share server %s not found.', vdm_name)
847 return
849 interfaces = self._get_context('VDM').get_interfaces(vdm_name)
851 for if_name in interfaces['nfs']:
852 self._get_context('VDM').detach_nfs_interface(vdm_name, if_name)
854 if security_services:
855 # Only support single security service with type 'active_directory'
856 is_valid, active_directory = self._get_valid_security_service(
857 security_services)
858 if is_valid:
859 status, servers = self._get_context('CIFSServer').get_all(
860 vdm_name)
861 if constants.STATUS_OK != status:
862 LOG.error('Could not find CIFS server by name: %s.',
863 vdm_name)
864 else:
865 cifs_servers = copy.deepcopy(servers)
866 for name, server in cifs_servers.items():
867 # Unjoin CIFS Server from domain
868 cifs_server_args = {
869 'name': server['name'],
870 'join_domain': False,
871 'user_name': active_directory['user'],
872 'password': active_directory['password'],
873 'mover_name': vdm_name,
874 'is_vdm': True,
875 }
877 try:
878 self._get_context('CIFSServer').modify(
879 cifs_server_args)
880 except exception.EMCPowerMaxXMLAPIError as expt:
881 LOG.debug("Failed to modify CIFS server "
882 "%(server)s. Reason: %(err)s.",
883 {'server': server, 'err': expt})
885 self._get_context('CIFSServer').delete(name, vdm_name)
887 # Delete interface from Data Mover
888 if cifs_if:
889 self._get_context('MoverInterface').delete(cifs_if,
890 self.mover_name)
892 if nfs_if:
893 self._get_context('MoverInterface').delete(nfs_if,
894 self.mover_name)
896 # Delete Virtual Data Mover
897 self._get_context('VDM').delete(vdm_name)
899 def _get_valid_security_service(self, security_services):
900 """Validate security services and return a supported security service.
902 :param security_services:
903 :returns: (<is_valid>, <data>) -- <is_valid> is true to indicate
904 security_services includes zero or single security service for
905 active directory. Otherwise, it would return false. <data> return
906 error message when <is_valid> is false. Otherwise, it will
907 return zero or single security service for active directory.
908 """
910 # Only support single security service with type 'active_directory'
911 if (len(security_services) > 1 or
912 (security_services and
913 security_services[0]['type'] != 'active_directory')):
914 return False, _("Unsupported security services. "
915 "Only support single security service and "
916 "only support type 'active_directory'")
918 return True, security_services[0]
920 def _get_share_server_name(self, share_server):
921 try:
922 return share_server['backend_details']['share_server_name']
923 except Exception:
924 LOG.debug("Didn't get share server name from share_server %s.",
925 share_server)
926 return share_server['id']
928 def _get_context(self, context_type):
929 return self.manager.getStorageContext(context_type)