Coverage for manila/share/drivers/dell_emc/plugins/unity/connection.py: 94%
477 statements
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
1# Copyright (c) 2016 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"""Unity backend for the EMC Manila driver."""
16import random
18from oslo_config import cfg
19from oslo_log import log
20from oslo_utils import excutils
21from oslo_utils import importutils
22from oslo_utils import netutils
24storops = importutils.try_import('storops')
25if storops: 25 ↛ 30line 25 didn't jump to line 30 because the condition on line 25 was always true
26 # pylint: disable=import-error
27 from storops import exception as storops_ex
28 from storops.unity import enums
30from manila.common import constants as const
31from manila import exception
32from manila.i18n import _
33from manila.share.drivers.dell_emc.common.enas import utils as enas_utils
34from manila.share.drivers.dell_emc.plugins import base as driver
35from manila.share.drivers.dell_emc.plugins.unity import client
36from manila.share.drivers.dell_emc.plugins.unity import utils as unity_utils
37from manila.share import utils as share_utils
38from manila import utils
40"""Version history:
41 7.0.0 - Supports DHSS=False mode
42 7.0.1 - Fix parsing management IPv6 address
43 7.0.2 - Bugfix: failed to delete CIFS share if wrong access was set
44 8.0.0 - Supports manage/unmanage share server/share/snapshot
45 9.0.0 - Implements default filter function
46 9.0.1 - Bugfix: remove enable ace process when creating cifs share
47 9.0.2 - Bugfix: fix the driver startup issue with LACP ports configured
48"""
50VERSION = "9.0.2"
52LOG = log.getLogger(__name__)
53SUPPORTED_NETWORK_TYPES = (None, 'flat', 'vlan')
55UNITY_OPTS = [
56 cfg.StrOpt('unity_server_meta_pool',
57 required=True,
58 help='Pool to persist the meta-data of NAS server.'),
59 cfg.ListOpt('unity_share_data_pools',
60 help='Comma separated list of pools that can be used to '
61 'persist share data.'),
62 cfg.ListOpt('unity_ethernet_ports',
63 help='Comma separated list of ports that can be used for '
64 'share server interfaces. Members of the list '
65 'can be Unix-style glob expressions.'),
66 cfg.StrOpt('unity_share_server',
67 help='NAS server used for creating share when driver '
68 'is in DHSS=False mode. It is required when '
69 'driver_handles_share_servers=False in manila.conf.'),
70 cfg.StrOpt('report_default_filter_function',
71 default=False,
72 help='Whether or not report default filter function.'),
73]
75CONF = cfg.CONF
76CONF.register_opts(UNITY_OPTS)
79@enas_utils.decorate_all_methods(enas_utils.log_enter_exit,
80 debug_only=True)
81class UnityStorageConnection(driver.StorageConnection):
82 """Implements Unity specific functionality for EMC Manila driver."""
84 IP_ALLOCATIONS = 1
86 @enas_utils.log_enter_exit
87 def __init__(self, *args, **kwargs):
88 super(UnityStorageConnection, self).__init__(*args, **kwargs)
89 if 'configuration' in kwargs: 89 ↛ 90line 89 didn't jump to line 90 because the condition on line 89 was never true
90 kwargs['configuration'].append_config_values(UNITY_OPTS)
92 self.client = None
93 self.pool_set = None
94 self.nas_server_pool = None
95 self.reserved_percentage = None
96 self.reserved_snapshot_percentage = None
97 self.reserved_share_extend_percentage = None
98 self.max_over_subscription_ratio = None
99 self.port_ids_conf = None
100 self.unity_share_server = None
101 self.ipv6_implemented = True
102 self.revert_to_snap_support = True
103 self.shrink_share_support = True
104 self.manage_existing_support = True
105 self.manage_existing_with_server_support = True
106 self.manage_existing_snapshot_support = True
107 self.manage_snapshot_with_server_support = True
108 self.manage_server_support = True
109 self.get_share_server_network_info_support = True
111 # props from super class.
112 self.driver_handles_share_servers = (True, False)
113 self.dhss_mandatory_security_service_association = {
114 'nfs': None,
115 'cifs': ['active_directory', ]
116 }
118 def connect(self, emc_share_driver, context):
119 """Connect to Unity storage."""
120 config = emc_share_driver.configuration
121 storage_ip = enas_utils.convert_ipv6_format_if_needed(
122 config.emc_nas_server)
123 username = config.emc_nas_login
124 password = config.emc_nas_password
125 self.client = client.UnityClient(storage_ip, username, password)
127 pool_conf = config.safe_get('unity_share_data_pools')
128 self.pool_set = self._get_managed_pools(pool_conf)
130 self.reserved_percentage = config.safe_get(
131 'reserved_share_percentage')
132 if self.reserved_percentage is None: 132 ↛ 133line 132 didn't jump to line 133 because the condition on line 132 was never true
133 self.reserved_percentage = 0
135 self.reserved_snapshot_percentage = config.safe_get(
136 'reserved_share_from_snapshot_percentage')
137 if self.reserved_snapshot_percentage is None: 137 ↛ 138line 137 didn't jump to line 138 because the condition on line 137 was never true
138 self.reserved_snapshot_percentage = self.reserved_percentage
140 self.reserved_share_extend_percentage = config.safe_get(
141 'reserved_share_extend_percentage')
142 if self.reserved_share_extend_percentage is None: 142 ↛ 143line 142 didn't jump to line 143 because the condition on line 142 was never true
143 self.reserved_share_extend_percentage = self.reserved_percentage
145 self.max_over_subscription_ratio = config.safe_get(
146 'max_over_subscription_ratio')
147 self.port_ids_conf = config.safe_get('unity_ethernet_ports')
148 self.unity_share_server = config.safe_get('unity_share_server')
149 self.driver_handles_share_servers = config.safe_get(
150 'driver_handles_share_servers')
151 if (not self.driver_handles_share_servers) and (
152 not self.unity_share_server):
153 msg = ("Make sure there is NAS server name "
154 "configured for share creation when driver "
155 "is in DHSS=False mode.")
156 raise exception.BadConfigurationException(reason=msg)
157 self.validate_port_configuration(self.port_ids_conf)
158 pool_name = config.unity_server_meta_pool
159 self._config_pool(pool_name)
160 self.report_default_filter_function = config.safe_get(
161 'report_default_filter_function')
163 def get_server_name(self, share_server=None):
164 if not self.driver_handles_share_servers:
165 return self.unity_share_server
166 else:
167 return self._get_server_name(share_server)
169 def validate_port_configuration(self, port_ids_conf):
170 """Initializes the SP and ports based on the port option."""
172 ports = self.client.get_file_ports()
174 sp_ports_map, unmanaged_port_ids = unity_utils.match_ports(
175 ports, port_ids_conf)
177 if not sp_ports_map:
178 msg = (_("All the specified storage ports to be managed "
179 "do not exist. Please check your configuration "
180 "unity_ethernet_ports in manila.conf. "
181 "The available ports in the backend are %s.") %
182 ",".join([port.get_id() for port in ports]))
183 raise exception.BadConfigurationException(reason=msg)
185 if unmanaged_port_ids: 185 ↛ 186line 185 didn't jump to line 186 because the condition on line 185 was never true
186 LOG.info("The following specified ports are not managed by "
187 "the backend: %(unmanaged)s. This host will only "
188 "manage the storage ports: %(exist)s",
189 {'unmanaged': ",".join(unmanaged_port_ids),
190 'exist': ",".join(map(",".join,
191 sp_ports_map.values()))})
192 else:
193 LOG.debug("Ports: %s will be managed.",
194 ",".join(map(",".join, sp_ports_map.values())))
196 if len(sp_ports_map) == 1:
197 LOG.info("Only ports of %s are configured. Configure ports "
198 "of both SPA and SPB to use both of the SPs.",
199 list(sp_ports_map)[0])
201 return sp_ports_map
203 def check_for_setup_error(self):
204 """Check for setup error."""
206 def manage_existing(self, share, driver_options, share_server=None):
207 """Manages a share that exists on backend.
209 :param share: Share that will be managed.
210 :param driver_options: Driver-specific options provided by admin.
211 :param share_server: Share server name provided by admin in DHSS=True.
212 :returns: Returns a dict with share size and export location.
213 """
214 export_locations = share['export_locations']
215 if not export_locations:
216 message = ("Failed to manage existing share: %s, missing "
217 "export locations." % share['id'])
218 raise exception.ManageInvalidShare(reason=message)
220 try:
221 share_size = int(driver_options.get("size", 0))
222 except (ValueError, TypeError):
223 msg = _("The driver options' size to manage the share "
224 "%(share_id)s, should be an integer, in format "
225 "driver-options size=<SIZE>. Value specified: "
226 "%(size)s.") % {'share_id': share['id'],
227 'size': driver_options.get("size")}
228 raise exception.ManageInvalidShare(reason=msg)
230 if not share_size:
231 msg = _("Share %(share_id)s has no specified size. "
232 "Using default value 1, set size in driver options if you "
233 "want.") % {'share_id': share['id']}
234 LOG.warning(msg)
235 share_size = 1
237 share_id = unity_utils.get_share_backend_id(share)
238 backend_share = self.client.get_share(share_id,
239 share['share_proto'])
240 if not backend_share: 240 ↛ 241line 240 didn't jump to line 241 because the condition on line 240 was never true
241 message = ("Could not find the share in backend, please make sure "
242 "the export location is right.")
243 raise exception.ManageInvalidShare(reason=message)
245 # Check the share server when in DHSS=true mode
246 if share_server:
247 backend_share_server = self._get_server_name(share_server)
248 if not backend_share_server: 248 ↛ 249line 248 didn't jump to line 249 because the condition on line 248 was never true
249 message = ("Could not find the backend share server: %s, "
250 "please make sure that share server with the "
251 "specified name exists in the backend.",
252 share_server)
253 raise exception.BadConfigurationException(message)
254 LOG.info("Share %(shr_path)s is being managed with ID "
255 "%(shr_id)s.",
256 {'shr_path': share['export_locations'][0]['path'],
257 'shr_id': share['id']})
258 # export_locations was not changed, return original value
259 return {"size": share_size, 'export_locations': {
260 'path': share['export_locations'][0]['path']}}
262 def manage_existing_with_server(self, share, driver_options, share_server):
263 return self.manage_existing(share, driver_options, share_server)
265 def manage_existing_snapshot(self, snapshot, driver_options,
266 share_server=None):
267 """Brings an existing snapshot under Manila management."""
268 try:
269 snapshot_size = int(driver_options.get("size", 0))
270 except (ValueError, TypeError):
271 msg = _("The size in driver options to manage snapshot "
272 "%(snap_id)s should be an integer, in format "
273 "driver-options size=<SIZE>. Value passed: "
274 "%(size)s.") % {'snap_id': snapshot['id'],
275 'size': driver_options.get("size")}
276 raise exception.ManageInvalidShareSnapshot(reason=msg)
278 if not snapshot_size:
279 msg = _("Snapshot %(snap_id)s has no specified size. "
280 "Use default value 1, set size in driver options if you "
281 "want.") % {'snap_id': snapshot['id']}
282 LOG.info(msg)
283 snapshot_size = 1
284 provider_location = snapshot.get('provider_location')
285 snap = self.client.get_snapshot(provider_location)
286 if not snap: 286 ↛ 287line 286 didn't jump to line 287 because the condition on line 286 was never true
287 message = ("Could not find a snapshot in the backend with "
288 "provider_location: %s, please make sure "
289 "the snapshot exists in the backend."
290 % provider_location)
291 raise exception.ManageInvalidShareSnapshot(reason=message)
293 LOG.info("Snapshot %(provider_location)s in Unity will be managed "
294 "with ID %(snapshot_id)s.",
295 {'provider_location': snapshot.get('provider_location'),
296 'snapshot_id': snapshot['id']})
297 return {"size": snapshot_size, "provider_location": provider_location}
299 def manage_existing_snapshot_with_server(self, snapshot, driver_options,
300 share_server):
301 return self.manage_existing_snapshot(snapshot, driver_options,
302 share_server)
304 def manage_server(self, context, share_server, identifier, driver_options):
305 """Manage the share server and return compiled back end details.
307 :param context: Current context.
308 :param share_server: Share server model.
309 :param identifier: A driver-specific share server identifier
310 :param driver_options: Dictionary of driver options to assist managing
311 the share server
312 :return: Identifier and dictionary with back end details to be saved
313 in the database.
315 Example::
317 'my_new_server_identifier',{'server_name': 'my_old_server'}
319 """
320 nas_server = self.client.get_nas_server(identifier)
321 if not nas_server: 321 ↛ 322line 321 didn't jump to line 322 because the condition on line 321 was never true
322 message = ("Could not find the backend share server by server "
323 "name: %s, please make sure the share server is "
324 "existing in the backend." % identifier)
325 raise exception.ManageInvalidShare(reason=message)
326 return identifier, driver_options
328 def get_share_server_network_info(
329 self, context, share_server, identifier, driver_options):
330 """Obtain network allocations used by share server.
332 :param context: Current context.
333 :param share_server: Share server model.
334 :param identifier: A driver-specific share server identifier
335 :param driver_options: Dictionary of driver options to assist managing
336 the share server
337 :return: The containing IP address allocated in the backend, Unity
338 only supports single IP address
339 Example::
341 ['10.10.10.10'] or ['fd11::2000']
343 """
344 containing_ips = []
345 nas_server = self.client.get_nas_server(identifier)
346 if nas_server: 346 ↛ 349line 346 didn't jump to line 349 because the condition on line 346 was always true
347 for file_interface in nas_server.file_interface:
348 containing_ips.append(file_interface.ip_address)
349 return containing_ips
351 def create_share(self, context, share, share_server=None):
352 """Create a share and export it based on protocol used."""
353 share_name = share['id']
354 size = share['size']
356 # Check share's protocol.
357 # Throw an exception immediately if it is an invalid protocol.
358 share_proto = share['share_proto'].upper()
359 proto_enum = self._get_proto_enum(share_proto)
361 # Get pool name from share host field
362 pool_name = self._get_pool_name_from_host(share['host'])
363 # Get share server name from share server or manila.conf.
364 server_name = self.get_server_name(share_server)
365 pool = self.client.get_pool(pool_name)
366 try:
367 nas_server = self.client.get_nas_server(server_name)
368 except storops_ex.UnityResourceNotFoundError:
369 message = (_("Failed to get NAS server %(server)s when "
370 "creating the share %(share)s.") %
371 {'server': server_name, 'share': share_name})
372 LOG.exception(message)
373 raise exception.EMCUnityError(err=message)
375 locations = None
376 if share_proto == 'CIFS':
377 filesystem = self.client.create_filesystem(
378 pool, nas_server, share_name,
379 size, proto=proto_enum)
380 self.client.create_cifs_share(filesystem, share_name)
382 locations = self._get_cifs_location(
383 nas_server.file_interface, share_name)
384 elif share_proto == 'NFS': 384 ↛ 391line 384 didn't jump to line 391 because the condition on line 384 was always true
385 self.client.create_nfs_filesystem_and_share(
386 pool, nas_server, share_name, size)
388 locations = self._get_nfs_location(
389 nas_server.file_interface, share_name)
391 return locations
393 def create_share_from_snapshot(self, context, share, snapshot,
394 share_server=None, parent_share=None):
395 """Create a share from a snapshot - clone a snapshot."""
396 share_name = share['id']
398 # Check share's protocol.
399 # Throw an exception immediately if it is an invalid protocol.
400 share_proto = share['share_proto'].upper()
401 self._validate_share_protocol(share_proto)
403 # Get share server name from share server
404 server_name = self.get_server_name(share_server)
406 try:
407 nas_server = self.client.get_nas_server(server_name)
408 except storops_ex.UnityResourceNotFoundError:
409 message = (_("Failed to get NAS server %(server)s when "
410 "creating the share %(share)s.") %
411 {'server': server_name, 'share': share_name})
412 LOG.exception(message)
413 raise exception.EMCUnityError(err=message)
414 snapshot_id = unity_utils.get_snapshot_id(snapshot)
415 backend_snap = self.client.create_snap_of_snap(snapshot_id,
416 share_name)
418 locations = None
419 if share_proto == 'CIFS':
420 self.client.create_cifs_share(backend_snap, share_name)
422 locations = self._get_cifs_location(
423 nas_server.file_interface, share_name)
424 elif share_proto == 'NFS': 424 ↛ 430line 424 didn't jump to line 430 because the condition on line 424 was always true
425 self.client.create_nfs_share(backend_snap, share_name)
427 locations = self._get_nfs_location(
428 nas_server.file_interface, share_name)
430 return locations
432 def delete_share(self, context, share, share_server=None):
433 """Delete a share."""
434 share_name = unity_utils.get_share_backend_id(share)
435 try:
436 backend_share = self.client.get_share(share_name,
437 share['share_proto'])
438 except storops_ex.UnityResourceNotFoundError:
439 LOG.warning("Share %s is not found when deleting the share",
440 share_name)
441 return
443 # Share created by the API create_share_from_snapshot()
444 if self._is_share_from_snapshot(backend_share):
445 filesystem = backend_share.snap.filesystem
446 self.client.delete_snapshot(backend_share.snap)
447 else:
448 filesystem = backend_share.filesystem
449 self.client.delete_share(backend_share)
451 if self._is_isolated_filesystem(filesystem):
452 self.client.delete_filesystem(filesystem)
454 def extend_share(self, share, new_size, share_server=None):
455 share_id = unity_utils.get_share_backend_id(share)
456 backend_share = self.client.get_share(share_id,
457 share['share_proto'])
459 if not self._is_share_from_snapshot(backend_share):
460 self.client.extend_filesystem(backend_share.filesystem,
461 new_size)
462 else:
463 share_id = share['id']
464 reason = ("Driver does not support extending a "
465 "snapshot based share.")
466 raise exception.ShareExtendingError(share_id=share_id,
467 reason=reason)
469 def shrink_share(self, share, new_size, share_server=None):
470 """Shrinks a share to new size.
472 :param share: Share that will be shrunk.
473 :param new_size: New size of share.
474 :param share_server: Data structure with share server information.
475 Not used by this driver.
476 """
477 share_id = unity_utils.get_share_backend_id(share)
478 backend_share = self.client.get_share(share_id,
479 share['share_proto'])
480 if self._is_share_from_snapshot(backend_share):
481 reason = ("Driver does not support shrinking a "
482 "snapshot based share.")
483 raise exception.ShareShrinkingError(share_id=share_id,
484 reason=reason)
485 self.client.shrink_filesystem(share_id, backend_share.filesystem,
486 new_size)
487 LOG.info("Share %(shr_id)s successfully shrunk to "
488 "%(shr_size)sG.",
489 {'shr_id': share_id,
490 'shr_size': new_size})
492 def create_snapshot(self, context, snapshot, share_server=None):
493 """Create snapshot from share."""
494 share = snapshot['share']
495 share_name = unity_utils.get_share_backend_id(
496 share) if share else snapshot['share_id']
497 share_proto = snapshot['share']['share_proto']
498 backend_share = self.client.get_share(share_name, share_proto)
500 snapshot_name = snapshot['id']
501 if self._is_share_from_snapshot(backend_share):
502 self.client.create_snap_of_snap(backend_share.snap, snapshot_name)
503 else:
504 self.client.create_snapshot(backend_share.filesystem,
505 snapshot_name)
506 return {'provider_location': snapshot_name}
508 def delete_snapshot(self, context, snapshot, share_server=None):
509 """Delete a snapshot."""
510 snapshot_id = unity_utils.get_snapshot_id(snapshot)
511 snap = self.client.get_snapshot(snapshot_id)
512 self.client.delete_snapshot(snap)
514 def update_access(self, context, share, access_rules, add_rules,
515 delete_rules, share_server=None):
516 # adding rules
517 if add_rules:
518 for rule in add_rules:
519 self.allow_access(context, share, rule, share_server)
521 # deleting rules
522 if delete_rules:
523 for rule in delete_rules:
524 self.deny_access(context, share, rule, share_server)
526 # recovery mode
527 if not (add_rules or delete_rules):
528 white_list = []
529 for rule in access_rules:
530 self.allow_access(context, share, rule, share_server)
531 white_list.append(rule['access_to'])
532 self.clear_access(share, white_list)
534 def clear_access(self, share, white_list=None):
535 share_proto = share['share_proto'].upper()
536 share_name = unity_utils.get_share_backend_id(share)
537 if share_proto == 'CIFS':
538 self.client.cifs_clear_access(share_name, white_list)
539 elif share_proto == 'NFS': 539 ↛ exitline 539 didn't return from function 'clear_access' because the condition on line 539 was always true
540 self.client.nfs_clear_access(share_name, white_list)
542 def allow_access(self, context, share, access, share_server=None):
543 """Allow access to a share."""
544 access_level = access['access_level']
545 if access_level not in const.ACCESS_LEVELS:
546 raise exception.InvalidShareAccessLevel(level=access_level)
548 share_proto = share['share_proto'].upper()
550 self._validate_share_protocol(share_proto)
551 self._validate_share_access_type(share, access)
553 if share_proto == 'CIFS':
554 self._cifs_allow_access(share, access)
555 elif share_proto == 'NFS': 555 ↛ exitline 555 didn't return from function 'allow_access' because the condition on line 555 was always true
556 self._nfs_allow_access(share, access)
558 def deny_access(self, context, share, access, share_server):
559 """Deny access to a share."""
560 share_proto = share['share_proto'].upper()
562 self._validate_share_protocol(share_proto)
563 self._validate_share_access_type(share, access)
565 if share_proto == 'CIFS':
566 self._cifs_deny_access(share, access)
567 elif share_proto == 'NFS': 567 ↛ exitline 567 didn't return from function 'deny_access' because the condition on line 567 was always true
568 self._nfs_deny_access(share, access)
570 def ensure_share(self, context, share, share_server):
571 """Ensure that the share is exported."""
572 share_name = unity_utils.get_share_backend_id(share)
573 share_proto = share['share_proto']
575 backend_share = self.client.get_share(share_name, share_proto)
576 if not backend_share.existed:
577 raise exception.ShareNotFound(share_id=share_name)
579 def update_share_stats(self, stats_dict):
580 """Communicate with EMCNASClient to get the stats."""
581 stats_dict['driver_version'] = VERSION
582 stats_dict['pools'] = []
584 for pool in self.client.get_pool():
585 if pool.name in self.pool_set: 585 ↛ 584line 585 didn't jump to line 584 because the condition on line 585 was always true
586 # the unit of following numbers are GB
587 total_size = float(pool.size_total)
588 used_size = float(pool.size_used)
590 pool_stat = {
591 'pool_name': pool.name,
592 'thin_provisioning': True,
593 'total_capacity_gb': enas_utils.bytes_to_gb(total_size),
594 'free_capacity_gb':
595 enas_utils.bytes_to_gb(total_size - used_size),
596 'allocated_capacity_gb': enas_utils.bytes_to_gb(used_size),
597 'provisioned_capacity_gb':
598 enas_utils.bytes_to_gb(pool.size_subscribed),
599 'qos': False,
600 'reserved_percentage': self.reserved_percentage,
601 'reserved_snapshot_percentage':
602 self.reserved_snapshot_percentage,
603 'reserved_share_extend_percentage':
604 self.reserved_share_extend_percentage,
605 'max_over_subscription_ratio':
606 self.max_over_subscription_ratio,
607 }
608 stats_dict['pools'].append(pool_stat)
610 if not stats_dict.get('pools'):
611 message = _("Failed to update storage pool.")
612 LOG.error(message)
613 raise exception.EMCUnityError(err=message)
615 def get_pool(self, share):
616 """Get the pool name of the share."""
617 backend_share = self.client.get_share(
618 share['id'], share['share_proto'])
620 return backend_share.filesystem.pool.name
622 def get_network_allocations_number(self):
623 """Returns number of network allocations for creating VIFs."""
624 return self.IP_ALLOCATIONS
626 def setup_server(self, network_info, metadata=None):
627 """Set up and configures share server with given network parameters."""
628 server_name = network_info['server_id']
629 segmentation_id = network_info['segmentation_id']
630 network = self.validate_network(network_info)
631 mtu = network['mtu']
632 tenant = self.client.get_tenant(network_info['server_id'],
633 segmentation_id)
635 sp_ports_map = unity_utils.find_ports_by_mtu(
636 self.client.get_file_ports(),
637 self.port_ids_conf, mtu)
639 sp = self._choose_sp(sp_ports_map)
640 nas_server = self.client.create_nas_server(server_name,
641 sp,
642 self.nas_server_pool,
643 tenant=tenant)
644 sp = nas_server.home_sp
645 port_id = self._choose_port(sp_ports_map, sp)
646 try:
647 self._create_network_interface(nas_server, network, port_id)
649 self._handle_security_services(
650 nas_server, network_info['security_services'])
652 return {'share_server_name': server_name}
654 except Exception:
655 with excutils.save_and_reraise_exception():
656 LOG.exception('Could not setup server.')
657 server_details = {'share_server_name': server_name}
658 self.teardown_server(
659 server_details, network_info['security_services'])
661 def teardown_server(self, server_details, security_services=None):
662 """Teardown share server."""
663 if not server_details:
664 LOG.debug('Server details are empty.')
665 return
667 server_name = server_details.get('share_server_name')
668 if not server_name:
669 LOG.debug('No share server found for server %s.',
670 server_details.get('instance_id'))
671 return
673 username = None
674 password = None
675 for security_service in security_services:
676 if security_service['type'] == 'active_directory': 676 ↛ 675line 676 didn't jump to line 675 because the condition on line 676 was always true
677 username = security_service['user']
678 password = security_service['password']
679 break
681 self.client.delete_nas_server(server_name, username, password)
683 def _cifs_allow_access(self, share, access):
684 """Allow access to CIFS share."""
685 self.client.cifs_allow_access(
686 share['id'], access['access_to'], access['access_level'])
688 def _cifs_deny_access(self, share, access):
689 """Deny access to CIFS share."""
690 self.client.cifs_deny_access(share['id'], access['access_to'])
692 def _config_pool(self, pool_name):
693 try:
694 self.nas_server_pool = self.client.get_pool(pool_name)
695 except storops_ex.UnityResourceNotFoundError:
696 message = (_("The storage pools %s to store NAS server "
697 "configuration do not exist.") % pool_name)
698 LOG.exception(message)
699 raise exception.BadConfigurationException(reason=message)
701 @staticmethod
702 def validate_network(network_info):
703 network = network_info['network_allocations'][0]
704 if network['network_type'] not in SUPPORTED_NETWORK_TYPES:
705 msg = _('The specified network type %s is unsupported by '
706 'the EMC Unity driver')
707 raise exception.NetworkBadConfigurationException(
708 reason=msg % network['network_type'])
709 return network
711 def _create_network_interface(self, nas_server, network, port_id):
712 kargs = {'ip_addr': network['ip_address'],
713 'gateway': network['gateway'],
714 'vlan_id': network['segmentation_id'],
715 'port_id': port_id}
717 if netutils.is_valid_ipv6_cidr(kargs['ip_addr']):
718 kargs['netmask'] = None
719 kargs['prefix_length'] = str(utils.cidr_to_prefixlen(
720 network['cidr']))
721 else:
722 kargs['netmask'] = utils.cidr_to_netmask(network['cidr'])
724 # Create the interfaces on NAS server
725 self.client.create_interface(nas_server, **kargs)
727 def _choose_sp(self, sp_ports_map):
728 sp = None
729 if len(sp_ports_map.keys()) == 1: 729 ↛ 737line 729 didn't jump to line 737 because the condition on line 729 was always true
730 # Only one storage processor has usable ports,
731 # create NAS server on that SP.
732 sp = self.client.get_storage_processor(
733 sp_id=list(sp_ports_map.keys())[0])
734 LOG.debug('All the usable ports belong to %s. '
735 'Creating NAS server on this SP without '
736 'load balance.', sp.get_id())
737 return sp
739 @staticmethod
740 def _choose_port(sp_ports_map, sp):
741 ports = sp_ports_map[sp.get_id()]
742 return random.choice(list(ports))
744 @staticmethod
745 def _get_cifs_location(file_interfaces, share_name):
746 return [
747 {'path': r'\\%(interface)s\%(share_name)s' % {
748 'interface': enas_utils.export_unc_path(interface.ip_address),
749 'share_name': share_name}
750 }
751 for interface in file_interfaces
752 ]
754 def _get_managed_pools(self, pool_conf):
755 # Get the real pools from the backend storage
756 real_pools = set(pool.name for pool in self.client.get_pool())
758 if not pool_conf:
759 LOG.debug("No storage pool is specified, so all pools in storage "
760 "system will be managed.")
761 return real_pools
763 matched_pools, unmanaged_pools = unity_utils.do_match(real_pools,
764 pool_conf)
766 if not matched_pools:
767 msg = (_("All the specified storage pools to be managed "
768 "do not exist. Please check your configuration "
769 "emc_nas_pool_names in manila.conf. "
770 "The available pools in the backend are %s") %
771 ",".join(real_pools))
772 raise exception.BadConfigurationException(reason=msg)
774 if unmanaged_pools:
775 LOG.info("The following specified storage pools "
776 "are not managed by the backend: "
777 "%(un_managed)s. This host will only manage "
778 "the storage pools: %(exist)s",
779 {'un_managed': ",".join(unmanaged_pools),
780 'exist': ",".join(matched_pools)})
781 else:
782 LOG.debug("Storage pools: %s will be managed.",
783 ",".join(matched_pools))
785 return matched_pools
787 @staticmethod
788 def _get_nfs_location(file_interfaces, share_name):
789 return [
790 {'path': '%(interface)s:/%(share_name)s' % {
791 'interface': enas_utils.convert_ipv6_format_if_needed(
792 interface.ip_address),
793 'share_name': share_name}
794 }
795 for interface in file_interfaces
796 ]
798 @staticmethod
799 def _get_pool_name_from_host(host):
800 pool_name = share_utils.extract_host(host, level='pool')
801 if not pool_name:
802 message = (_("Pool is not available in the share host %s.") %
803 host)
804 raise exception.InvalidHost(reason=message)
806 return pool_name
808 @staticmethod
809 def _get_proto_enum(share_proto):
810 share_proto = share_proto.upper()
811 UnityStorageConnection._validate_share_protocol(share_proto)
813 if share_proto == 'CIFS':
814 return enums.FSSupportedProtocolEnum.CIFS
815 elif share_proto == 'NFS': 815 ↛ exitline 815 didn't return from function '_get_proto_enum' because the condition on line 815 was always true
816 return enums.FSSupportedProtocolEnum.NFS
818 @staticmethod
819 def _get_server_name(share_server):
820 if not share_server:
821 msg = _('Share server not provided.')
822 raise exception.InvalidInput(reason=msg)
823 # Try to get share server name from property 'identifier' first in
824 # case this is managed share server.
825 server_name = share_server.get('identifier') or share_server.get(
826 'backend_details', {}).get('share_server_name')
828 if server_name is None:
829 msg = (_("Name of the share server %s not found.")
830 % share_server['id'])
831 LOG.error(msg)
832 raise exception.InvalidInput(reason=msg)
834 return server_name
836 def _handle_security_services(self, nas_server, security_services):
837 kerberos_enabled = False
838 # Support 'active_directory' and 'kerberos'
839 for security_service in security_services:
840 service_type = security_service['type']
841 if service_type == 'active_directory':
842 # Create DNS server for NAS server
843 domain = security_service['domain']
844 dns_ip = security_service['dns_ip']
845 self.client.create_dns_server(nas_server,
846 domain,
847 dns_ip)
849 # Enable CIFS service
850 username = security_service['user']
851 password = security_service['password']
852 self.client.enable_cifs_service(nas_server,
853 domain=domain,
854 username=username,
855 password=password)
856 elif service_type == 'kerberos': 856 ↛ 862line 856 didn't jump to line 862 because the condition on line 856 was always true
857 # Enable NFS service with kerberos
858 kerberos_enabled = True
859 # TODO(jay.xu): enable nfs service with kerberos
860 LOG.warning('Kerberos is not supported by '
861 'EMC Unity manila driver plugin.')
862 elif service_type == 'ldap':
863 LOG.warning('LDAP is not supported by '
864 'EMC Unity manila driver plugin.')
865 else:
866 LOG.warning('Unknown security service type: %s.',
867 service_type)
869 if not kerberos_enabled:
870 # Enable NFS service without kerberos
871 self.client.enable_nfs_service(nas_server)
873 def _nfs_allow_access(self, share, access):
874 """Allow access to NFS share."""
875 self.client.nfs_allow_access(
876 share['id'], access['access_to'], access['access_level'])
878 def _nfs_deny_access(self, share, access):
879 """Deny access to NFS share."""
880 self.client.nfs_deny_access(share['id'], access['access_to'])
882 @staticmethod
883 def _is_isolated_filesystem(filesystem):
884 filesystem.update()
885 return (
886 not filesystem.has_snap() and
887 not (filesystem.cifs_share or filesystem.nfs_share)
888 )
890 @staticmethod
891 def _is_share_from_snapshot(share):
892 return True if share.snap else False
894 @staticmethod
895 def _validate_share_access_type(share, access):
896 reason = None
897 share_proto = share['share_proto'].upper()
899 if share_proto == 'CIFS' and access['access_type'] != 'user':
900 reason = _('Only user access type allowed for CIFS share.')
901 elif share_proto == 'NFS' and access['access_type'] != 'ip':
902 reason = _('Only IP access type allowed for NFS share.')
904 if reason:
905 raise exception.InvalidShareAccess(reason=reason)
907 @staticmethod
908 def _validate_share_protocol(share_proto):
909 if share_proto not in ('NFS', 'CIFS'):
910 raise exception.InvalidShare(
911 reason=(_('Invalid NAS protocol supplied: %s.') %
912 share_proto))
914 def revert_to_snapshot(self, context, snapshot, share_access_rules,
915 snapshot_access_rules, share_server=None):
916 """Reverts a share (in place) to the specified snapshot."""
917 snapshot_id = unity_utils.get_snapshot_id(snapshot)
918 return self.client.restore_snapshot(snapshot_id)
920 def get_default_filter_function(self):
921 if self.report_default_filter_function:
922 return "share.size >= 3"
923 return None