Coverage for manila/share/drivers/netapp/dataontap/cluster_mode/lib_multi_svm.py: 93%
1212 statements
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
1# Copyright (c) 2015 Clinton Knight. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14"""
15NetApp Data ONTAP cDOT multi-SVM storage driver library.
17This library extends the abstract base library and completes the multi-SVM
18functionality needed by the cDOT multi-SVM Manila driver. This library
19variant creates Data ONTAP storage virtual machines (i.e. 'vservers')
20as needed to provision shares.
21"""
22import re
24from manila.share.drivers.netapp.dataontap.cluster_mode.lib_base import Backup
25from oslo_log import log
26from oslo_serialization import jsonutils
27from oslo_utils import excutils
28from oslo_utils import units
29from oslo_utils import uuidutils
31from manila.common import constants
32from manila import exception
33from manila.i18n import _
34from manila.message import message_field
35from manila.share.drivers.netapp.dataontap.client import api as netapp_api
36from manila.share.drivers.netapp.dataontap.client import client_cmode
37from manila.share.drivers.netapp.dataontap.client import client_cmode_rest
38from manila.share.drivers.netapp.dataontap.cluster_mode import data_motion
39from manila.share.drivers.netapp.dataontap.cluster_mode import lib_base
40from manila.share.drivers.netapp import utils as na_utils
41from manila.share import share_types
42from manila.share import utils as share_utils
43from manila import utils
46LOG = log.getLogger(__name__)
47SUPPORTED_NETWORK_TYPES = (None, 'flat', 'vlan')
48SEGMENTED_NETWORK_TYPES = ('vlan',)
49DEFAULT_MTU = 1500
50SERVER_MIGRATE_SVM_DR = 'svm_dr'
51SERVER_MIGRATE_SVM_MIGRATE = 'svm_migrate'
52METADATA_VLAN = 'set_vlan'
53METADATA_MTU = 'set_mtu'
56class NetAppCmodeMultiSVMFileStorageLibrary(
57 lib_base.NetAppCmodeFileStorageLibrary):
59 @na_utils.trace
60 def check_for_setup_error(self):
62 if self._have_cluster_creds:
63 if self.configuration.netapp_vserver: 63 ↛ 74line 63 didn't jump to line 74 because the condition on line 63 was always true
64 msg = ('Vserver is specified in the configuration. This is '
65 'ignored when the driver is managing share servers.')
66 LOG.warning(msg)
68 else: # only have vserver creds, which is an error in multi_svm mode
69 msg = _('Cluster credentials must be specified in the '
70 'configuration when the driver is managing share servers.')
71 raise exception.InvalidInput(reason=msg)
73 # Ensure FlexGroup support
74 aggr_list = self._client.list_non_root_aggregates()
75 self._initialize_flexgroup_pools(set(aggr_list))
77 # Ensure one or more aggregates are available.
78 if (self.is_flexvol_pool_configured() and
79 not self._find_matching_aggregates(aggregate_names=aggr_list)):
80 msg = _('No aggregates are available for provisioning shares. '
81 'Ensure that the configuration option '
82 'netapp_aggregate_name_search_pattern is set correctly.')
83 raise exception.NetAppException(msg)
85 (super(NetAppCmodeMultiSVMFileStorageLibrary, self).
86 check_for_setup_error())
88 @na_utils.trace
89 def _get_vserver(self, share_server=None, vserver_name=None,
90 backend_name=None):
91 if share_server:
92 backend_details = share_server.get('backend_details')
93 vserver = backend_details.get(
94 'vserver_name') if backend_details else None
96 if not vserver:
97 msg = _('Vserver name is absent in backend details. Please '
98 'check whether Vserver was created properly.')
99 raise exception.VserverNotSpecified(msg)
100 elif vserver_name:
101 vserver = vserver_name
102 else:
103 msg = _('Share server or vserver name not provided')
104 raise exception.InvalidInput(reason=msg)
106 if backend_name: 106 ↛ 107line 106 didn't jump to line 107 because the condition on line 106 was never true
107 vserver_client = data_motion.get_client_for_backend(
108 backend_name, vserver
109 )
110 else:
111 vserver_client = self._get_api_client(vserver)
113 if not vserver_client.vserver_exists(vserver):
114 raise exception.VserverNotFound(vserver=vserver)
116 return vserver, vserver_client
118 def _get_ems_pool_info(self):
119 return {
120 'pools': {
121 'vserver': None,
122 'aggregates': self._find_matching_aggregates(),
123 'flexgroup_aggregates': self._flexgroup_pools,
124 },
125 }
127 @na_utils.trace
128 def _handle_housekeeping_tasks(self):
129 """Handle various cleanup activities."""
130 self._client.prune_deleted_nfs_export_policies()
131 self._client.prune_deleted_snapshots()
132 self._client.remove_unused_qos_policy_groups()
133 self._client.prune_deleted_volumes()
135 (super(NetAppCmodeMultiSVMFileStorageLibrary, self).
136 _handle_housekeeping_tasks())
138 @na_utils.trace
139 def _find_matching_aggregates(self, aggregate_names=None):
140 """Find all aggregates match pattern."""
142 if not self.is_flexvol_pool_configured():
143 return []
145 if not aggregate_names: 145 ↛ 148line 145 didn't jump to line 148 because the condition on line 145 was always true
146 aggregate_names = self._client.list_non_root_aggregates()
148 pattern = self.configuration.netapp_aggregate_name_search_pattern
149 return [aggr_name for aggr_name in aggregate_names
150 if re.match(pattern, aggr_name)]
152 @na_utils.trace
153 def _set_network_with_metadata(self, network_info):
154 """Set the subnet metadata information for network_info object."""
156 for network in network_info:
157 metadata = network.get('subnet_metadata')
158 if not metadata:
159 continue
161 metadata_vlan = metadata.get(METADATA_VLAN)
162 if not metadata_vlan:
163 continue
165 if int(metadata_vlan) > 4094 or int(metadata_vlan) < 1:
166 msg = _(
167 'A segmentation ID %s was specified but is not valid for '
168 'a VLAN network type; the segmentation ID must be an '
169 'integer value in the range of [1,4094]')
170 raise exception.NetworkBadConfigurationException(
171 reason=msg % metadata_vlan)
173 if metadata.get(METADATA_MTU) is not None:
174 try:
175 int(metadata.get(METADATA_MTU))
176 except ValueError:
177 msg = _('Metadata network MTU must be an integer value.')
178 raise exception.NetworkBadConfigurationException(msg)
180 network['network_type'] = 'vlan'
181 network['segmentation_id'] = metadata_vlan
182 for allocation in network['network_allocations']:
183 allocation['network_type'] = 'vlan'
184 allocation['segmentation_id'] = metadata_vlan
185 allocation['mtu'] = int(metadata.get(METADATA_MTU) or
186 allocation['mtu'])
188 @na_utils.trace
189 def setup_server(self, network_info, metadata=None):
190 """Creates and configures new Vserver."""
192 # only changes network_info if one of networks has metadata set.
193 self._set_network_with_metadata(network_info)
195 ports = {}
196 server_id = network_info[0]['server_id']
197 LOG.debug("Setting up server %s.", server_id)
198 for network in network_info:
199 for network_allocation in network['network_allocations']:
200 ports[network_allocation['id']] = (
201 network_allocation['ip_address'])
203 nfs_config = self._default_nfs_config
204 if (self.is_nfs_config_supported and metadata and
205 'share_type_id' in metadata):
206 extra_specs = share_types.get_share_type_extra_specs(
207 metadata['share_type_id'])
208 self._check_nfs_config_extra_specs_validity(extra_specs)
209 nfs_config = self._get_nfs_config_provisioning_options(extra_specs)
211 vlan = network_info[0]['segmentation_id']
213 @utils.synchronized('netapp-VLAN-%s' % vlan, external=True)
214 def setup_server_with_lock():
215 self._validate_network_type(network_info)
217 # Before proceeding, make sure subnet configuration is valid
218 self._validate_share_network_subnets(network_info)
220 vserver_name = self._get_vserver_name(server_id)
221 server_details = {
222 'vserver_name': vserver_name,
223 'ports': jsonutils.dumps(ports),
224 }
226 if self.is_nfs_config_supported:
227 server_details['nfs_config'] = jsonutils.dumps(nfs_config)
229 if self.configuration.netapp_restrict_lif_creation_per_ha_pair:
230 self._check_data_lif_count_limit_reached_for_ha_pair(
231 self._client
232 )
233 try:
234 self._create_vserver(vserver_name, network_info, metadata,
235 nfs_config=nfs_config)
236 except Exception as e:
237 e.detail_data = {'server_details': server_details}
238 raise
240 if metadata.get('encryption_key_ref'):
241 self._create_barbican_kms_config_for_specified_vserver(
242 vserver_name, metadata)
244 return server_details
245 return setup_server_with_lock()
247 @na_utils.trace
248 def _check_nfs_config_extra_specs_validity(self, extra_specs):
249 """Check if the nfs config extra_spec has valid values."""
250 int_extra_specs = ['netapp:tcp_max_xfer_size',
251 'netapp:udp_max_xfer_size']
252 for key in int_extra_specs:
253 if key in extra_specs:
254 self._check_if_extra_spec_is_positive(
255 extra_specs[key], key)
257 @na_utils.trace
258 def _check_if_extra_spec_is_positive(self, value, key):
259 """Check if extra_spec has a valid positive int value."""
260 if int(value) < 0:
261 args = {'value': value, 'key': key}
262 msg = _('Invalid value "%(value)s" for extra_spec "%(key)s" '
263 'used by share server setup.')
264 raise exception.NetAppException(msg % args)
266 @na_utils.trace
267 def _get_nfs_config_provisioning_options(self, specs):
268 """Return the nfs config provisioning option."""
269 nfs_config = self.get_string_provisioning_options(
270 specs, self.NFS_CONFIG_EXTRA_SPECS_MAP)
272 # Changes the no set config to the default value
273 for k, v in nfs_config.items():
274 if v is None:
275 nfs_config[k] = self._default_nfs_config[k]
277 return nfs_config
279 @na_utils.trace
280 def _validate_network_type(self, network_info):
281 """Raises exception if the segmentation type is incorrect."""
282 unsupported_nets = [network for network in network_info
283 if network['network_type']
284 not in SUPPORTED_NETWORK_TYPES]
286 if unsupported_nets:
287 msg = _('The specified network type %s is unsupported by the '
288 'NetApp clustered Data ONTAP driver')
289 raise exception.NetworkBadConfigurationException(
290 reason=msg % unsupported_nets[0]['network_type'])
292 @na_utils.trace
293 def _get_vserver_name(self, server_id):
294 return self.configuration.netapp_vserver_name_template % server_id
296 @na_utils.trace
297 def _validate_share_network_subnets(self, network_info):
298 """Raises exception if subnet configuration isn't valid."""
299 # Driver supports multiple subnets only if in the same network segment
300 ref_vlan = network_info[0]['segmentation_id']
301 if not all([network['segmentation_id'] == ref_vlan
302 for network in network_info]):
303 msg = _("The specified network configuration isn't supported by "
304 "the NetApp clustered Data ONTAP driver. All subnets must "
305 "reside in the same network segment.")
306 raise exception.NetworkBadConfigurationException(reason=msg)
308 @na_utils.trace
309 def _create_vserver(self, vserver_name, network_info, metadata=None,
310 nfs_config=None):
311 """Creates Vserver with given parameters if it doesn't exist."""
313 if self._client.vserver_exists(vserver_name):
314 msg = _('Vserver %s already exists.')
315 raise exception.NetAppException(msg % vserver_name)
316 # NOTE(dviroel): check if this vserver will be a data protection server
317 is_dp_destination = False
318 if metadata and metadata.get('migration_destination') is True:
319 is_dp_destination = True
320 msg = _("Starting creation of a vserver with 'dp_destination' "
321 "subtype.")
322 LOG.debug(msg)
324 # NOTE(lseki): If there's already an ipspace created for the same VLAN
325 # port, reuse it. It will be named after the previously created share
326 # server's neutron subnet id.
327 node_name = self._client.list_cluster_nodes()[0]
328 port = self._get_node_data_port(node_name)
329 # NOTE(sfernand): ONTAP driver currently supports multiple subnets
330 # only in a same network segment. A validation is performed in a
331 # earlier step to make sure all subnets have the same segmentation_id.
332 vlan = network_info[0]['segmentation_id']
333 ipspace_name = self._client.get_ipspace_name_for_vlan_port(
334 node_name, port, vlan)
336 if (
337 ipspace_name is None
338 or ipspace_name in client_cmode.CLUSTER_IPSPACES
339 ):
340 ipspace_name = self._create_ipspace(network_info[0])
342 aggregate_names = self._find_matching_aggregates()
343 if is_dp_destination:
344 # Get Data ONTAP aggregate name as pool name.
345 LOG.debug('Creating a new Vserver (%s) for data protection.',
346 vserver_name)
347 self._client.create_vserver_dp_destination(
348 vserver_name,
349 aggregate_names,
350 ipspace_name,
351 self.configuration.netapp_delete_retention_hours,
352 self.configuration.netapp_enable_logical_space_reporting)
353 # Set up port and broadcast domain for the current ipspace
354 self._create_port_and_broadcast_domain(
355 ipspace_name, network_info[0])
356 else:
357 LOG.debug('Vserver %s does not exist, creating.', vserver_name)
358 aggr_set = set(aggregate_names).union(
359 self._get_flexgroup_aggr_set())
360 self._client.create_vserver(
361 vserver_name,
362 self.configuration.netapp_root_volume_aggregate,
363 self.configuration.netapp_root_volume,
364 aggr_set,
365 ipspace_name,
366 self.configuration.netapp_security_cert_expire_days,
367 self.configuration.netapp_delete_retention_hours,
368 self.configuration.netapp_enable_logical_space_reporting)
370 vserver_client = self._get_api_client(vserver=vserver_name)
372 security_services = network_info[0].get('security_services')
373 try:
374 self._setup_network_for_vserver(
375 vserver_name, vserver_client, network_info, ipspace_name,
376 security_services=security_services, nfs_config=nfs_config)
377 except Exception:
378 with excutils.save_and_reraise_exception():
379 LOG.error("Failed to configure Vserver.")
380 # NOTE(dviroel): At this point, the lock was already
381 # acquired by the caller of _create_vserver.
382 self._delete_vserver(vserver_name,
383 security_services=security_services,
384 needs_lock=False)
386 @na_utils.trace
387 def _create_barbican_kms_config_for_specified_vserver(self, vserver_name,
388 metadata):
389 """Creates a Barbican KMS configuration for the specified vserver."""
391 key_href = metadata.get('encryption_key_ref')
392 config_name = "barbican_config_" + uuidutils.generate_uuid()
393 keystone_auth_url = metadata.get('keystone_url')
394 keystone_auth_token_path = self.configuration.safe_get(
395 'netapp_identity_auth_token_path')
396 keystone_url = keystone_auth_url + keystone_auth_token_path
397 app_cred_id = metadata.get('application_credential_id')
398 app_cred_secret = metadata.get('application_credential_secret')
399 backend_name = share_utils.extract_host(metadata.get('request_host'),
400 level='backend_name')
401 try:
402 rest_client = data_motion.get_client_for_backend(
403 backend_name, vserver_name=None, force_rest_client=True)
404 except exception.NetAppException:
405 LOG.error("Failed to get REST client for backend %s. "
406 "Please ensure that REST is enabled in the cluster.",
407 backend_name)
408 raise
410 LOG.debug('Creating a Barbican KMS configuration for the vserver '
411 '%(vserver)s', {'vserver': vserver_name})
412 rest_client.create_barbican_kms_config_for_specified_vserver(
413 vserver_name, config_name,
414 key_href, keystone_url,
415 app_cred_id, app_cred_secret)
417 LOG.debug('Getting the key store configuration uuid for the config '
418 '%(config)s', {'config': config_name})
419 config_uuid = rest_client.get_key_store_config_uuid(config_name)
421 LOG.debug('Enabling the key store configuration the config %(config)s',
422 {'config': config_name})
423 rest_client.enable_key_store_config(config_uuid)
425 def _setup_network_for_vserver(self, vserver_name, vserver_client,
426 network_info, ipspace_name,
427 enable_nfs=True, security_services=None,
428 nfs_config=None):
429 """Setup Vserver network configuration"""
430 # segmentation_id and mtu are the same for all allocations and can be
431 # extracted from the first index, as subnets were previously checked
432 # at this point to ensure they are all in the same network segment and
433 # consequently belongs to the same Neutron network (which holds L2
434 # information).
435 ref_subnet_allocation = network_info[0]['network_allocations'][0]
436 vlan = ref_subnet_allocation['segmentation_id']
437 mtu = ref_subnet_allocation['mtu'] or DEFAULT_MTU
439 home_ports = {}
440 nodes = self._client.list_cluster_nodes()
441 for node in nodes:
442 port = self._get_node_data_port(node)
443 vlan_port_name = self._client.create_port_and_broadcast_domain(
444 node, port, vlan, mtu, ipspace_name)
445 home_ports[node] = vlan_port_name
447 for network in network_info:
448 self._create_vserver_lifs(vserver_name,
449 vserver_client,
450 network,
451 ipspace_name,
452 lif_home_ports=home_ports)
454 self._create_vserver_routes(vserver_client, network)
456 self._create_vserver_admin_lif(vserver_name,
457 vserver_client,
458 network_info[0],
459 ipspace_name,
460 lif_home_ports=home_ports)
461 if enable_nfs: 461 ↛ 466line 461 didn't jump to line 466 because the condition on line 461 was always true
462 vserver_client.enable_nfs(
463 self.configuration.netapp_enabled_share_protocols,
464 nfs_config=nfs_config)
466 if security_services: 466 ↛ exitline 466 didn't return from function '_setup_network_for_vserver' because the condition on line 466 was always true
467 self._client.setup_security_services(
468 security_services,
469 vserver_client,
470 vserver_name,
471 self.configuration.netapp_cifs_aes_encryption)
473 def _get_valid_ipspace_name(self, network_id):
474 """Get IPspace name according to network id."""
475 return client_cmode.IPSPACE_PREFIX + network_id.replace('-', '_')
477 @na_utils.trace
478 def _create_ipspace(self, network_info, client=None):
479 """If supported, create an IPspace for a new Vserver."""
481 desired_client = client if client else self._client
483 if not desired_client.features.IPSPACES:
484 return None
486 if (network_info['network_allocations'][0]['network_type']
487 not in SEGMENTED_NETWORK_TYPES):
488 return client_cmode.DEFAULT_IPSPACE
490 # NOTE(cknight): Neutron needs cDOT IP spaces because it can provide
491 # overlapping IP address ranges for different subnets. That is not
492 # believed to be an issue for any of Manila's other network plugins.
493 ipspace_id = network_info.get('neutron_net_id')
494 if not ipspace_id: 494 ↛ 495line 494 didn't jump to line 495 because the condition on line 494 was never true
495 return client_cmode.DEFAULT_IPSPACE
497 ipspace_name = self._get_valid_ipspace_name(ipspace_id)
498 desired_client.create_ipspace(ipspace_name)
500 return ipspace_name
502 @na_utils.trace
503 def _create_vserver_lifs(self, vserver_name, vserver_client, network_info,
504 ipspace_name, lif_home_ports=None):
505 """Create Vserver data logical interfaces (LIFs)."""
506 # We can get node names directly from lif_home_ports in case
507 # it was passed as parameter, otherwise a request to the cluster is
508 nodes = (list(lif_home_ports.keys()) if lif_home_ports
509 else self._client.list_cluster_nodes())
510 # required
512 node_network_info = zip(nodes, network_info['network_allocations'])
513 # Creating LIF per node
514 for node_name, network_allocation in node_network_info:
515 lif_home_port = (lif_home_ports[node_name] if
516 lif_home_ports else None)
517 lif_name = self._get_lif_name(node_name, network_allocation)
519 self._create_lif(vserver_client, vserver_name, ipspace_name,
520 node_name, lif_name, network_allocation,
521 lif_home_port=lif_home_port)
523 @na_utils.trace
524 def _create_vserver_admin_lif(self, vserver_name, vserver_client,
525 network_info, ipspace_name,
526 lif_home_ports=None):
527 """Create Vserver admin LIF, if defined."""
528 network_allocations = network_info.get('admin_network_allocations')
529 if not network_allocations:
530 return
531 LOG.info('Admin network defined for Vserver %s.', vserver_name)
533 home_port = None
534 if lif_home_ports: 534 ↛ 535line 534 didn't jump to line 535 because the condition on line 534 was never true
535 node_name, home_port = list(lif_home_ports.items())[0]
536 else:
537 nodes = self._client.list_cluster_nodes()
538 node_name = nodes[0]
540 network_allocation = network_allocations[0]
541 lif_name = self._get_lif_name(node_name, network_allocation)
543 self._create_lif(vserver_client, vserver_name, ipspace_name,
544 node_name, lif_name, network_allocation,
545 lif_home_port=home_port)
547 @na_utils.trace
548 def _create_vserver_routes(self, vserver_client, network_info):
549 """Create Vserver route and set gateways."""
550 route_gateways = []
551 # NOTE(gouthamr): Use the gateway from the tenant subnet/s
552 # for the static routes. Do not configure a route for the admin
553 # subnet because fast path routing will work for incoming
554 # connections and there are no requirements for outgoing
555 # connections on the admin network yet.
556 for net_allocation in (network_info['network_allocations']):
557 if net_allocation['gateway'] not in route_gateways:
558 vserver_client.create_route(net_allocation['gateway'])
559 route_gateways.append(net_allocation['gateway'])
561 @na_utils.trace
562 def _get_node_data_port(self, node):
563 port_names = self._client.list_node_data_ports(node)
564 pattern = self.configuration.netapp_port_name_search_pattern
565 matched_port_names = [port_name for port_name in port_names
566 if re.match(pattern, port_name)]
567 if not matched_port_names:
568 raise exception.NetAppException(
569 _('Could not find eligible network ports on node %s on which '
570 'to create Vserver LIFs.') % node)
571 return matched_port_names[0]
573 def _get_lif_name(self, node_name, network_allocation):
574 """Get LIF name based on template from manila.conf file."""
575 lif_name_args = {
576 'node': node_name,
577 'net_allocation_id': network_allocation['id'],
578 }
579 return self.configuration.netapp_lif_name_template % lif_name_args
581 @na_utils.trace
582 def _create_lif(self, vserver_client, vserver_name, ipspace_name,
583 node_name, lif_name, network_allocation,
584 lif_home_port=None):
585 """Creates LIF for Vserver."""
586 port = lif_home_port or self._get_node_data_port(node_name)
587 vlan = network_allocation['segmentation_id']
588 ip_address = network_allocation['ip_address']
589 netmask = utils.cidr_to_netmask(network_allocation['cidr'])
591 # We can skip the operation if an lif already exists with the same
592 # configuration
593 if vserver_client.network_interface_exists(
594 vserver_name, node_name, port, ip_address, netmask, vlan,
595 home_port=lif_home_port):
596 msg = ('LIF %(ip)s netmask %(mask)s already exists for '
597 'node %(node)s port %(port)s in vserver %(vserver)s.' % {
598 'ip': ip_address,
599 'mask': netmask,
600 'node': node_name,
601 'vserver': vserver_name,
602 'port': '%(port)s-%(vlan)s' % {'port': port,
603 'vlan': vlan}})
604 LOG.debug(msg)
605 return
607 if not lif_home_port:
608 mtu = network_allocation.get('mtu') or DEFAULT_MTU
609 lif_home_port = (
610 self._client.create_port_and_broadcast_domain(
611 node_name, port, vlan, mtu, ipspace_name))
613 self._client.create_network_interface(
614 ip_address, netmask, node_name, lif_home_port,
615 vserver_name, lif_name)
617 @na_utils.trace
618 def _create_port_and_broadcast_domain(self, ipspace_name, network_info):
619 nodes = self._client.list_cluster_nodes()
620 node_network_info = zip(nodes, network_info['network_allocations'])
622 for node_name, network_allocation in node_network_info:
623 port = self._get_node_data_port(node_name)
624 vlan = network_allocation['segmentation_id']
625 network_mtu = network_allocation.get('mtu')
626 mtu = network_mtu or DEFAULT_MTU
628 self._client.create_port_and_broadcast_domain(
629 node_name, port, vlan, mtu, ipspace_name)
631 @na_utils.trace
632 def get_network_allocations_number(self):
633 """Get number of network interfaces to be created."""
634 return len(self._client.list_cluster_nodes())
636 @na_utils.trace
637 def get_admin_network_allocations_number(self, admin_network_api):
638 """Get number of network allocations for creating admin LIFs."""
639 return 1 if admin_network_api else 0
641 @na_utils.trace
642 def teardown_server(self, server_details, security_services=None):
643 """Teardown share server."""
644 vserver = server_details.get(
645 'vserver_name') if server_details else None
647 if not vserver:
648 LOG.warning("Vserver not specified for share server being "
649 "deleted. Deletion of share server record will "
650 "proceed anyway.")
651 return
653 elif not self._client.vserver_exists(vserver):
654 LOG.warning("Could not find Vserver for share server being "
655 "deleted: %s. Deletion of share server "
656 "record will proceed anyway.", vserver)
657 return
659 self._delete_vserver(vserver, security_services=security_services)
661 @na_utils.trace
662 def _delete_vserver(self, vserver, security_services=None,
663 needs_lock=True):
664 """Delete a Vserver plus IPspace and security services as needed."""
666 ipspace_name = None
668 ipspaces = self._client.get_ipspaces(vserver_name=vserver)
669 if ipspaces:
670 ipspace = ipspaces[0]
671 ipspace_name = ipspace['ipspace']
673 vserver_client = self._get_api_client(vserver=vserver)
674 network_interfaces = vserver_client.get_network_interfaces()
675 snapmirror_policies = self._client.get_snapmirror_policies(vserver)
677 interfaces_on_vlans = []
678 vlans = []
679 for interface in network_interfaces:
680 if '-' in interface['home-port']: 680 ↛ 679line 680 didn't jump to line 679 because the condition on line 680 was always true
681 interfaces_on_vlans.append(interface)
682 vlans.append(interface['home-port'])
684 if vlans:
685 vlans = '-'.join(sorted(set(vlans))) if vlans else None
686 vlan_id = vlans.split('-')[-1]
687 else:
688 vlan_id = None
690 def _delete_vserver_without_lock():
691 # we already deleted the neutron network allocations,
692 # make sure those are no longer used
693 for interface in network_interfaces:
694 if interface['administrative-status'] != 'down': 694 ↛ 693line 694 didn't jump to line 693 because the condition on line 694 was always true
695 self._client.disable_network_interface(
696 vserver, interface['interface-name'])
698 # NOTE(dviroel): always delete all policies before deleting the
699 # vserver
700 for policy in snapmirror_policies:
701 vserver_client.delete_snapmirror_policy(policy)
703 # NOTE(dviroel): Attempt to delete all vserver peering
704 # created by replication
705 self._delete_vserver_peers(vserver)
707 self._client.delete_vserver(vserver,
708 vserver_client,
709 security_services=security_services)
711 if ipspace_name is None:
712 return
714 ipspace_deleted = self._client.delete_ipspace(ipspace_name)
716 ports = set()
717 if ipspace_deleted:
718 # we don't want to leave any ports behind, clean them all up
719 ports.update(ipspace['ports'])
720 # NOTE(dviroel): only delete vlans if they are not being used
721 # by any ipspaces and data vservers.
722 else:
723 broadcast_domains = ipspace['broadcast-domains']
724 # NOTE(carthaca): filter for degraded ports, those where
725 # reachability is down (e.g. because neutron port is gone)
726 ports.update(self._client.get_degraded_ports(broadcast_domains,
727 ipspace_name))
728 # make sure to delete ports of the vserver we are currently
729 # deleting (may not be marked degraded, yet)
730 for interface in interfaces_on_vlans:
731 port = f"{interface['home-node']}:{interface['home-port']}"
732 ports.add(port)
734 self._delete_port_vlans(self._client, ports)
736 @utils.synchronized('netapp-VLAN-%s' % vlan_id, external=True)
737 def _delete_vserver_with_lock():
738 _delete_vserver_without_lock()
740 if needs_lock:
741 return _delete_vserver_with_lock()
742 else:
743 return _delete_vserver_without_lock()
745 def _delete_port_vlans(_self, client, ports):
746 """Delete Port's VLAN configuration
748 must be called with a cluster client
749 """
750 for port_name in ports:
751 try:
752 node, port = port_name.split(':')
753 port, vlan = port.split('-')
754 client.delete_vlan(node, port, vlan)
755 except exception.NetAppException:
756 LOG.exception("Deleting Vserver VLAN failed.")
758 @na_utils.trace
759 def _delete_vserver_peers(self, vserver):
760 vserver_peers = self._get_vserver_peers(vserver=vserver)
761 for peer in vserver_peers:
762 self._delete_vserver_peer(peer.get('vserver'),
763 peer.get('peer-vserver'))
765 def get_configured_ip_versions(self):
766 versions = [4]
767 options = self._client.get_net_options()
768 if options['ipv6-enabled']:
769 versions.append(6)
770 return versions
772 @na_utils.trace
773 def create_replica(self, context, replica_list, new_replica,
774 access_rules, share_snapshots, share_server=None):
775 """Creates the new replica on this backend and sets up SnapMirror.
777 It creates the peering between the associated vservers before creating
778 the share replica and setting up the SnapMirror.
779 """
780 # 1. Retrieve source and destination vservers from both replicas,
781 # active and and new_replica
782 src_vserver, dst_vserver = self._get_vservers_from_replicas(
783 context, replica_list, new_replica)
785 # 2. Retrieve the active replica host's client and cluster name
786 src_replica = self.find_active_replica(replica_list)
788 src_replica_host = share_utils.extract_host(
789 src_replica['host'], level='backend_name')
790 src_replica_client = data_motion.get_client_for_backend(
791 src_replica_host, vserver_name=src_vserver)
792 # Cluster name is needed for setting up the vserver peering
793 src_replica_cluster_name = src_replica_client.get_cluster_name()
795 # 3. Retrieve new replica host's client
796 new_replica_host = share_utils.extract_host(
797 new_replica['host'], level='backend_name')
798 new_replica_client = data_motion.get_client_for_backend(
799 new_replica_host, vserver_name=dst_vserver)
800 new_replica_cluster_name = new_replica_client.get_cluster_name()
802 if (dst_vserver != src_vserver
803 and not self._get_vserver_peers(dst_vserver, src_vserver)):
804 # 3.1. Request vserver peer creation from new_replica's host
805 # to active replica's host
806 new_replica_client.create_vserver_peer(
807 dst_vserver, src_vserver,
808 peer_cluster_name=src_replica_cluster_name)
810 # 3.2. Accepts the vserver peering using active replica host's
811 # client (inter-cluster only)
812 if new_replica_cluster_name != src_replica_cluster_name: 812 ↛ 816line 812 didn't jump to line 816 because the condition on line 812 was always true
813 src_replica_client.accept_vserver_peer(src_vserver,
814 dst_vserver)
816 return (super(NetAppCmodeMultiSVMFileStorageLibrary, self).
817 create_replica(context, replica_list, new_replica,
818 access_rules, share_snapshots))
820 def delete_replica(self, context, replica_list, replica, share_snapshots,
821 share_server=None):
822 """Removes the replica on this backend and destroys SnapMirror.
824 Removes the replica, destroys the SnapMirror and delete the vserver
825 peering if needed.
826 """
827 vserver, peer_vserver = self._get_vservers_from_replicas(
828 context, replica_list, replica)
829 super(NetAppCmodeMultiSVMFileStorageLibrary, self).delete_replica(
830 context, replica_list, replica, share_snapshots)
832 # Fix for bug 1996907- If snapmirror relationship still exist,
833 # deletes those again.
834 snapmirrors_des_list = self._get_snapmirrors_destinations(
835 vserver, peer_vserver)
836 snapmirrors_des_list_from_peer = self._get_snapmirrors_destinations(
837 peer_vserver, vserver)
838 if snapmirrors_des_list or snapmirrors_des_list_from_peer: 838 ↛ 839line 838 didn't jump to line 839 because the condition on line 838 was never true
839 super(NetAppCmodeMultiSVMFileStorageLibrary, self).delete_replica(
840 context, replica_list, replica, share_snapshots)
842 # Check if there are no remaining SnapMirror connections and if a
843 # vserver peering exists and delete it.
844 snapmirrors_from_local = self._get_snapmirrors(vserver, peer_vserver)
845 snapmirrors_from_peer = self._get_snapmirrors(peer_vserver, vserver)
846 peers = self._get_vserver_peers(peer_vserver, vserver)
847 if (not (snapmirrors_from_local or snapmirrors_from_peer 847 ↛ exitline 847 didn't return from function 'delete_replica' because the condition on line 847 was always true
848 or snapmirrors_des_list or snapmirrors_des_list_from_peer)
849 and peers):
850 self._delete_vserver_peer(peer_vserver, vserver)
852 def manage_server(self, context, share_server, identifier, driver_options):
853 """Manages a vserver by renaming it and returning backend_details."""
854 new_vserver_name = self._get_vserver_name(share_server['id'])
855 old_vserver_name = self._get_correct_vserver_old_name(identifier)
856 if new_vserver_name != old_vserver_name:
857 self._client.rename_vserver(old_vserver_name, new_vserver_name)
859 backend_details = {
860 'vserver_name': new_vserver_name,
861 }
863 if self.is_nfs_config_supported:
864 nfs_config = self._client.get_nfs_config(
865 list(self.NFS_CONFIG_EXTRA_SPECS_MAP.values()),
866 new_vserver_name)
867 backend_details['nfs_config'] = jsonutils.dumps(nfs_config)
869 return new_vserver_name, backend_details
871 def unmanage_server(self, server_details, security_services=None):
872 pass
874 def get_share_server_network_info(
875 self, context, share_server, identifier, driver_options):
876 """Returns a list of IPs for each vserver network interface."""
877 vserver_name = self._get_correct_vserver_old_name(identifier)
879 vserver, vserver_client = self._get_vserver(vserver_name=vserver_name)
881 interfaces = vserver_client.get_network_interfaces()
882 allocations = []
883 for lif in interfaces:
884 allocations.append(lif['address'])
885 return allocations
887 def _get_correct_vserver_old_name(self, identifier):
889 # In case vserver_name includes the template, we check and add it here
890 if not self._client.vserver_exists(identifier):
891 return self._get_vserver_name(identifier)
892 return identifier
894 def _get_snapmirrors(self, vserver, peer_vserver):
895 return self._client.get_snapmirrors(
896 source_vserver=vserver, dest_vserver=peer_vserver)
898 def _get_snapmirrors_destinations(self, vserver, peer_vserver):
899 return self._client.get_snapmirror_destinations(
900 source_vserver=vserver, dest_vserver=peer_vserver)
902 def _get_vservers_from_replicas(self, context, replica_list, new_replica):
903 active_replica = self.find_active_replica(replica_list)
905 dm_session = data_motion.DataMotionSession()
906 vserver = dm_session.get_vserver_from_share(active_replica)
907 peer_vserver = dm_session.get_vserver_from_share(new_replica)
909 return vserver, peer_vserver
911 def _get_vserver_peers(self, vserver=None, peer_vserver=None):
912 return self._client.get_vserver_peers(vserver, peer_vserver)
914 def _create_vserver_peer(self, context, vserver, peer_vserver):
915 self._client.create_vserver_peer(vserver, peer_vserver)
917 def _delete_vserver_peer(self, vserver, peer_vserver):
918 self._client.delete_vserver_peer(vserver, peer_vserver)
920 def create_share_from_snapshot(self, context, share, snapshot,
921 share_server=None, parent_share=None):
922 # NOTE(dviroel): If both parent and child shares are in the same host,
923 # they belong to the same cluster, and we can skip all the processing
924 # below. Group snapshot is always to the same host too, so we can skip.
925 is_group_snapshot = share.get('source_share_group_snapshot_member_id')
926 if not is_group_snapshot and parent_share['host'] != share['host']:
927 # 1. Retrieve source and destination vservers from source and
928 # destination shares
929 dm_session = data_motion.DataMotionSession()
930 src_vserver = dm_session.get_vserver_from_share(parent_share)
931 dest_vserver = dm_session.get_vserver_from_share_server(
932 share_server)
934 # 2. Retrieve the source share host's client and cluster name
935 src_share_host = share_utils.extract_host(
936 parent_share['host'], level='backend_name')
937 src_share_client = data_motion.get_client_for_backend(
938 src_share_host, vserver_name=src_vserver)
939 # Cluster name is needed for setting up the vserver peering
940 src_share_cluster_name = src_share_client.get_cluster_name()
942 # 3. Retrieve new share host's client
943 dest_share_host = share_utils.extract_host(
944 share['host'], level='backend_name')
945 dest_share_client = data_motion.get_client_for_backend(
946 dest_share_host, vserver_name=dest_vserver)
947 dest_share_cluster_name = dest_share_client.get_cluster_name()
948 # If source and destination shares are placed in a different
949 # clusters, we'll need the both vserver peered.
950 if src_share_cluster_name != dest_share_cluster_name:
951 if not self._get_vserver_peers(dest_vserver, src_vserver):
952 # 3.1. Request vserver peer creation from new_replica's
953 # host to active replica's host
954 dest_share_client.create_vserver_peer(
955 dest_vserver, src_vserver,
956 peer_cluster_name=src_share_cluster_name)
958 # 3.2. Accepts the vserver peering using active replica
959 # host's client
960 src_share_client.accept_vserver_peer(src_vserver,
961 dest_vserver)
963 return (super(NetAppCmodeMultiSVMFileStorageLibrary, self)
964 .create_share_from_snapshot(
965 context, share, snapshot, share_server=share_server,
966 parent_share=parent_share))
968 @na_utils.trace
969 def _is_share_server_compatible(self, share_server, expected_nfs_config):
970 """Check if the share server has the given nfs config
972 The None and the default_nfs_config should be considered
973 as the same configuration.
974 """
975 nfs_config = share_server.get('backend_details', {}).get('nfs_config')
976 share_server_nfs = jsonutils.loads(nfs_config) if nfs_config else None
978 if share_server_nfs == expected_nfs_config:
979 return True
980 elif (share_server_nfs is None and
981 expected_nfs_config == self._default_nfs_config):
982 return True
983 elif (expected_nfs_config is None and
984 share_server_nfs == self._default_nfs_config):
985 return True
987 return False
989 def choose_share_server_compatible_with_share(self, context, share_servers,
990 share, snapshot=None,
991 share_group=None,
992 encryption_key_ref=None):
993 """Method that allows driver to choose share server for provided share.
995 If compatible share-server is not found, method should return None.
996 :param context: Current context
997 :param share_servers: list with share-server models
998 :param share: share model
999 :param snapshot: snapshot model
1000 :param share_group: ShareGroup model with shares
1001 :param encryption_key_ref: Encryption key reference
1002 :returns: share-server or None
1003 """
1004 if not share_servers: 1004 ↛ 1006line 1004 didn't jump to line 1006 because the condition on line 1004 was never true
1005 # No share server to reuse
1006 return None
1008 nfs_config = None
1009 extra_specs = share_types.get_extra_specs_from_share(share)
1011 if self.is_nfs_config_supported:
1012 nfs_config = self._get_nfs_config_provisioning_options(extra_specs)
1014 provisioning_options = self._get_provisioning_options(extra_specs)
1016 # Get FPolicy extra specs to avoid incompatible share servers
1017 fpolicy_ext_to_include = provisioning_options.get(
1018 'fpolicy_extensions_to_include')
1019 fpolicy_ext_to_exclude = provisioning_options.get(
1020 'fpolicy_extensions_to_exclude')
1021 fpolicy_file_operations = provisioning_options.get(
1022 'fpolicy_file_operations')
1024 # Avoid the reuse of 'dp_protection' vservers:
1025 for share_server in share_servers:
1026 if self._check_reuse_share_server(
1027 share_server, nfs_config, share=share,
1028 share_group=share_group,
1029 fpolicy_ext_include=fpolicy_ext_to_include,
1030 fpolicy_ext_exclude=fpolicy_ext_to_exclude,
1031 fpolicy_file_operations=fpolicy_file_operations,
1032 encryption_key_ref=encryption_key_ref):
1033 return share_server
1035 # There is no compatible share server to be reused
1036 return None
1038 @na_utils.trace
1039 def _check_reuse_share_server(self, share_server, nfs_config, share=None,
1040 share_group=None, fpolicy_ext_include=None,
1041 fpolicy_ext_exclude=None,
1042 fpolicy_file_operations=None,
1043 encryption_key_ref=None):
1044 """Check whether the share_server can be reused or not."""
1046 LOG.debug('Checking if the encryption key ref passed is already '
1047 'configured for the existing share_servers')
1048 if (encryption_key_ref and encryption_key_ref != 1048 ↛ 1050line 1048 didn't jump to line 1050 because the condition on line 1048 was never true
1049 share_server['encryption_key_ref']):
1050 msg = _('The available share server %(server_id)s is already'
1051 'configured with a different encryption-key-ref',
1052 {'server_id': share_server['id']})
1053 LOG.warning(msg)
1054 return False
1056 if (share_group and share_group.get('share_server_id') !=
1057 share_server['id']):
1058 return False
1060 backend_name = share_utils.extract_host(share_server['host'],
1061 level='backend_name')
1062 try:
1063 vserver_name, client = self._get_vserver(share_server,
1064 backend_name=backend_name)
1065 except (exception.InvalidInput,
1066 exception.VserverNotSpecified,
1067 exception.VserverNotFound) as error:
1068 LOG.warning("Could not determine vserver for reuse of "
1069 "share server. Share server: %(ss)s - Error: %(err)s",
1070 {'ss': share_server, 'err': error})
1071 return False
1072 vserver_info = client.get_vserver_info(vserver_name)
1073 if (vserver_info.get('operational_state') != 'running'
1074 or vserver_info.get('state') != 'running'
1075 or vserver_info.get('subtype') != 'default'):
1076 return False
1078 if share:
1079 share_pool = share_utils.extract_host(
1080 share['host'], level='pool')
1081 if self._is_flexgroup_pool(share_pool):
1082 share_pool_list = self._get_flexgroup_aggregate_list(
1083 share_pool)
1084 else:
1085 share_pool_list = [share_pool]
1086 aggr_list = client.list_vserver_aggregates()
1087 if not set(share_pool_list).issubset(set(aggr_list)):
1088 return False
1090 if self.is_nfs_config_supported:
1091 # NOTE(felipe_rodrigues): Do not check that the share nfs_config
1092 # matches with the group nfs_config, because the API guarantees
1093 # that the share type is an element of the group types.
1094 return self._is_share_server_compatible(share_server, nfs_config)
1096 if fpolicy_ext_include or fpolicy_ext_exclude:
1097 fpolicies = client.get_fpolicy_policies_status()
1098 if len(fpolicies) >= self.FPOLICY_MAX_VSERVER_POLICIES:
1099 # This share server already reached it maximum number of
1100 # policies, we need to check if we can reuse one, otherwise,
1101 # it is not suitable for this share.
1102 reusable_scope = self._find_reusable_fpolicy_scope(
1103 share, client,
1104 fpolicy_extensions_to_include=fpolicy_ext_include,
1105 fpolicy_extensions_to_exclude=fpolicy_ext_exclude,
1106 fpolicy_file_operations=fpolicy_file_operations)
1107 if not reusable_scope:
1108 return False
1110 return True
1112 @na_utils.trace
1113 def choose_share_server_compatible_with_share_group(
1114 self, context, share_servers, share_group_ref,
1115 share_group_snapshot=None):
1116 """Choose the server compatible with group.
1118 If the NFS configuration is supported, it will check that the group
1119 types agree for the NFS extra-specs values.
1120 """
1121 if not share_servers:
1122 # No share server to reuse
1123 return None
1125 nfs_config = None
1126 if self.is_nfs_config_supported:
1127 nfs_config = self._get_nfs_config_share_group(share_group_ref)
1129 # NOTE(dviroel): FPolicy extra-specs won't be conflicting, since
1130 # multiple policies can be created. The maximum number of policies or
1131 # the reusability of existing ones, can only be analyzed at share
1132 # instance creation.
1133 for share_server in share_servers:
1134 if self._check_reuse_share_server(share_server, nfs_config):
1135 return share_server
1137 return None
1139 @na_utils.trace
1140 def _get_nfs_config_share_group(self, share_group_ref):
1141 """Get the NFS config of the share group.
1143 In case the group types do not agree for the NFS config, it throws an
1144 exception.
1145 """
1146 nfs_config = None
1147 first = True
1148 for st in share_group_ref.get('share_types', []):
1149 extra_specs = share_types.get_share_type_extra_specs(
1150 st['share_type_id'])
1152 if first:
1153 self._check_nfs_config_extra_specs_validity(extra_specs)
1154 nfs_config = self._get_nfs_config_provisioning_options(
1155 extra_specs)
1156 first = False
1157 continue
1159 type_nfs_config = self._get_nfs_config_provisioning_options(
1160 extra_specs)
1161 if nfs_config != type_nfs_config:
1162 msg = _("The specified share_types cannot have "
1163 "conflicting values for the NFS configuration "
1164 "extra-specs.")
1165 raise exception.InvalidInput(reason=msg)
1167 return nfs_config
1169 @na_utils.trace
1170 def manage_existing(self, share, driver_options, share_server=None):
1172 # In case NFS config is supported, the share's nfs_config must be the
1173 # same as the server
1174 if share_server and self.is_nfs_config_supported: 1174 ↛ 1184line 1174 didn't jump to line 1184 because the condition on line 1174 was always true
1175 extra_specs = share_types.get_extra_specs_from_share(share)
1176 nfs_config = self._get_nfs_config_provisioning_options(extra_specs)
1177 if not self._is_share_server_compatible(share_server, nfs_config): 1177 ↛ 1184line 1177 didn't jump to line 1184 because the condition on line 1177 was always true
1178 args = {'server_id': share_server['id']}
1179 msg = _('Invalid NFS configuration for the server '
1180 '%(server_id)s . The extra-specs must match the '
1181 'values of NFS of the server.')
1182 raise exception.NetAppException(msg % args)
1184 return (super(NetAppCmodeMultiSVMFileStorageLibrary, self).
1185 manage_existing(share, driver_options,
1186 share_server=share_server))
1188 @na_utils.trace
1189 def _check_compatibility_using_svm_dr(
1190 self, src_client, dest_client, shares_request_spec, pools):
1191 """Send a request to pause a migration.
1193 :param src_client: source cluster client.
1194 :param dest_client: destination cluster client.
1195 :param shares_request_spec: shares specifications.
1196 :param pools: pools to be used during the migration.
1197 :returns server migration mechanism name and compatibility result
1198 example: (svm_dr, True).
1199 """
1200 method = SERVER_MIGRATE_SVM_DR
1201 if (not src_client.is_svm_dr_supported()
1202 or not dest_client.is_svm_dr_supported()):
1203 msg = _("Cannot perform server migration because at leat one of "
1204 "the backends doesn't support SVM DR.")
1205 LOG.error(msg)
1206 return method, False
1208 # Check that server does not have any FlexGroup volume.
1209 if src_client.is_flexgroup_supported():
1210 dm_session = data_motion.DataMotionSession()
1211 for req_spec in shares_request_spec.get('shares_req_spec', []): 1211 ↛ 1222line 1211 didn't jump to line 1222 because the loop on line 1211 didn't complete
1212 share_instance = req_spec.get('share_instance_properties', {})
1213 host = share_instance.get('host')
1214 if self.is_flexgroup_destination_host(host, dm_session): 1214 ↛ 1211line 1214 didn't jump to line 1211 because the condition on line 1214 was always true
1215 msg = _("Cannot perform server migration since a "
1216 "FlexGroup was encountered in share server to be "
1217 "migrated.")
1218 LOG.error(msg)
1219 return method, False
1221 # Check capacity.
1222 server_total_size = (shares_request_spec.get('shares_size', 0) +
1223 shares_request_spec.get('snapshots_size', 0))
1224 # NOTE(dviroel): If the backend has a 'max_over_subscription_ratio'
1225 # configured and greater than 1, we'll consider thin provisioning
1226 # enable for all shares.
1227 thin_provisioning = self.configuration.max_over_subscription_ratio > 1
1228 if self.configuration.netapp_server_migration_check_capacity is True: 1228 ↛ 1235line 1228 didn't jump to line 1235 because the condition on line 1228 was always true
1229 if not self._check_capacity_compatibility(pools, thin_provisioning, 1229 ↛ 1235line 1229 didn't jump to line 1235 because the condition on line 1229 was always true
1230 server_total_size):
1231 msg = _("Cannot perform server migration because destination "
1232 "host doesn't have enough free space.")
1233 LOG.error(msg)
1234 return method, False
1235 return method, True
1237 @na_utils.trace
1238 def _get_job_uuid(self, job):
1239 """Get the uuid of a job."""
1240 job = job.get("job", {})
1241 return job.get("uuid")
1243 @na_utils.trace
1244 def _wait_for_operation_status(
1245 self, operation_id, func_get_operation, desired_status='success',
1246 timeout=None):
1247 """Waits until a given operation reachs the desired status.
1249 :param operation_id: ID of the operation to be searched.
1250 :param func_get_operation: Function to be used to get the operation
1251 details.
1252 :param desired_status: Operation expected status.
1253 :param timeout: How long (in seconds) should the driver wait for the
1254 status to be reached.
1256 """
1257 if not timeout: 1257 ↛ 1261line 1257 didn't jump to line 1261 because the condition on line 1257 was always true
1258 timeout = (
1259 self.configuration.netapp_server_migration_state_change_timeout
1260 )
1261 interval = 10
1262 retries = int(timeout / interval) or 1
1264 @utils.retry(exception.ShareBackendException, interval=interval,
1265 retries=retries, backoff_rate=1)
1266 def wait_for_status():
1267 # Get the job based on its id.
1268 operation = func_get_operation(operation_id)
1269 status = operation.get("status") or operation.get("state")
1271 if status != desired_status:
1272 msg = _(
1273 "Operation %(operation_id)s didn't reach status "
1274 "%(desired_status)s. Current status is %(status)s.") % {
1275 'operation_id': operation_id,
1276 'desired_status': desired_status,
1277 'status': status
1278 }
1279 LOG.debug(msg)
1281 # Failed, no need to retry.
1282 if status == 'error':
1283 msg = _('Operation %(operation_id)s is in error status.'
1284 'Reason: %(message)s')
1285 raise exception.NetAppException(
1286 msg % {'operation_id': operation_id,
1287 'message': operation.get('message')})
1289 # Didn't fail, so we can retry.
1290 raise exception.ShareBackendException(msg)
1292 elif status == desired_status: 1292 ↛ exitline 1292 didn't return from function 'wait_for_status' because the condition on line 1292 was always true
1293 msg = _(
1294 'Operation %(operation_id)s reached status %(status)s.')
1295 LOG.debug(
1296 msg, {'operation_id': operation_id, 'status': status})
1297 return
1299 try:
1300 wait_for_status()
1301 except exception.NetAppException:
1302 raise
1303 except exception.ShareBackendException:
1304 msg_args = {'operation_id': operation_id, 'status': desired_status}
1305 msg = _('Timed out while waiting for operation %(operation_id)s '
1306 'to reach status %(status)s') % msg_args
1307 raise exception.NetAppException(msg)
1309 @na_utils.trace
1310 def _check_compatibility_for_svm_migrate(
1311 self, source_cluster_name, source_share_server_name,
1312 source_share_server, dest_aggregates, dest_client):
1313 """Checks if the migration can be performed using SVM Migrate.
1315 1. Send the request to the backed to check if the migration is possible
1316 2. Wait until the job finishes checking the migration status
1317 """
1319 # Reuse network information from the source share server in the SVM
1320 # Migrate if the there was no share network changes.
1321 network_info = {
1322 'network_allocations':
1323 source_share_server['network_allocations'],
1324 'neutron_subnet_id':
1325 source_share_server['share_network_subnets'][0].get(
1326 'neutron_subnet_id')
1327 }
1329 # Check the LIF creation on destination cluster when
1330 # 'netapp_restrict_lif_creation_per_ha_pair' option is set
1331 # to true.
1332 if self.configuration.netapp_restrict_lif_creation_per_ha_pair:
1333 self._check_data_lif_count_limit_reached_for_ha_pair(dest_client)
1335 # 2. Create new ipspace, port and broadcast domain.
1336 node_name = self._client.list_cluster_nodes()[0]
1337 port = self._get_node_data_port(node_name)
1338 vlan = network_info['network_allocations'][0]['segmentation_id']
1339 destination_ipspace = self._client.get_ipspace_name_for_vlan_port(
1340 node_name, port, vlan) or self._create_ipspace(
1341 network_info, client=dest_client)
1342 self._create_port_and_broadcast_domain(
1343 destination_ipspace, network_info)
1345 def _cleanup_ipspace(ipspace):
1346 if not dest_client.delete_ipspace(ipspace): 1346 ↛ 1347line 1346 didn't jump to line 1347 because the condition on line 1346 was never true
1347 LOG.info(
1348 'Did not delete ipspace used to check the compatibility '
1349 'for SVM Migrate. It is possible that it was reused and '
1350 'there are other entities consuming it.')
1352 if vlan: 1352 ↛ exitline 1352 didn't return from function '_cleanup_ipspace' because the condition on line 1352 was always true
1353 port = None
1354 for node in dest_client.list_cluster_nodes():
1355 port = port or self._get_node_data_port(node)
1356 dest_client.delete_vlan(node, port, vlan)
1358 # 1. Sends the request to the backend.
1359 try:
1360 job = dest_client.svm_migration_start(
1361 source_cluster_name, source_share_server_name, dest_aggregates,
1362 dest_ipspace=destination_ipspace, check_only=True)
1363 except Exception:
1364 LOG.error('Failed to check compatibility for migration.')
1365 _cleanup_ipspace(destination_ipspace)
1366 raise
1368 job_id = self._get_job_uuid(job)
1370 try:
1371 # 2. Wait until the job to check the migration status concludes.
1372 self._wait_for_operation_status(
1373 job_id, dest_client.get_migration_check_job_state)
1374 _cleanup_ipspace(destination_ipspace)
1375 return True
1376 except exception.NetAppException:
1377 # Performed the check with the given parameters and the backend
1378 # returned an error, so the migration is not compatible
1379 _cleanup_ipspace(destination_ipspace)
1380 return False
1382 @na_utils.trace
1383 def _check_for_migration_support(
1384 self, src_client, dest_client, source_share_server,
1385 shares_request_spec, src_cluster_name, pools):
1386 """Checks if the migration is supported and chooses the way to do it
1388 In terms of performance, SVM Migrate is more adequate and it should
1389 be prioritised over a SVM DR migration. If both source and destination
1390 clusters do not support SVM Migrate, then SVM DR is the option to be
1391 used.
1392 1. Checks if both source and destination clients support SVM Migrate.
1393 2. Requests the migration.
1394 """
1396 # 1. Checks if both source and destination clients support SVM Migrate.
1397 if (dest_client.is_svm_migrate_supported()
1398 and src_client.is_svm_migrate_supported()):
1399 source_share_server_name = (
1400 source_share_server["backend_details"]["vserver_name"])
1402 # Check if the migration is supported.
1403 try:
1404 result = self._check_compatibility_for_svm_migrate(
1405 src_cluster_name, source_share_server_name,
1406 source_share_server, self._find_matching_aggregates(),
1407 dest_client)
1408 return SERVER_MIGRATE_SVM_MIGRATE, result
1409 except Exception:
1410 LOG.error('Failed to check the compatibility for the share '
1411 'server migration using SVM Migrate.')
1412 return SERVER_MIGRATE_SVM_MIGRATE, False
1414 # SVM Migrate is not supported, try to check the compatibility using
1415 # SVM DR.
1416 return self._check_compatibility_using_svm_dr(
1417 src_client, dest_client, shares_request_spec, pools)
1419 @na_utils.trace
1420 def share_server_migration_check_compatibility(
1421 self, context, source_share_server, dest_host, old_share_network,
1422 new_share_network, shares_request_spec):
1424 not_compatible = {
1425 'compatible': False,
1426 'writable': None,
1427 'nondisruptive': None,
1428 'preserve_snapshots': None,
1429 'migration_cancel': None,
1430 'migration_get_progress': None,
1431 'share_network_id': None,
1432 }
1434 # We need cluster creds, of course
1435 if not self._have_cluster_creds: 1435 ↛ 1436line 1435 didn't jump to line 1436 because the condition on line 1435 was never true
1436 msg = _("Cluster credentials have not been configured with this "
1437 "share driver. Cannot perform server migration operation.")
1438 LOG.error(msg)
1439 return not_compatible
1441 # Vserver will spread across aggregates in this implementation
1442 if share_utils.extract_host(dest_host, level='pool') is not None:
1443 msg = _("Cannot perform server migration to a specific pool. "
1444 "Please choose a destination host 'host@backend' as "
1445 "destination.")
1446 LOG.error(msg)
1447 return not_compatible
1449 src_backend_name = share_utils.extract_host(
1450 source_share_server['host'], level='backend_name')
1451 src_vserver, src_client = self._get_vserver(
1452 source_share_server, backend_name=src_backend_name)
1453 dest_backend_name = share_utils.extract_host(dest_host,
1454 level='backend_name')
1455 # Block migration within the same backend.
1456 if src_backend_name == dest_backend_name:
1457 msg = _("Cannot perform server migration within the same backend. "
1458 "Please choose a destination host different from the "
1459 "source.")
1460 LOG.error(msg)
1461 return not_compatible
1463 src_cluster_name = src_client.get_cluster_name()
1464 # NOTE(dviroel): This call is supposed to made in the destination host
1465 dest_cluster_name = self._client.get_cluster_name()
1466 # Must be in different clusters too, SVM-DR restriction
1467 if src_cluster_name == dest_cluster_name:
1468 msg = _("Cannot perform server migration within the same cluster. "
1469 "Please choose a destination host that's in a different "
1470 "cluster.")
1471 LOG.error(msg)
1472 return not_compatible
1474 # Blocking multiple subnets
1475 new_subnets = new_share_network.get('share_network_subnets', [])
1476 old_subnets = old_share_network.get('share_network_subnets', [])
1477 if (len(new_subnets) != 1) or (len(old_subnets) != 1): 1477 ↛ 1478line 1477 didn't jump to line 1478 because the condition on line 1477 was never true
1478 msg = _("Cannot perform server migration for share network"
1479 "with multiple subnets.")
1480 LOG.error(msg)
1481 return not_compatible
1483 pools = self._get_pools()
1485 # NOTE(dviroel): These clients can only be used for non-tunneling
1486 # requests.
1487 dst_client = data_motion.get_client_for_backend(dest_backend_name,
1488 vserver_name=None)
1490 migration_method, compatibility = self._check_for_migration_support(
1491 src_client, dst_client, source_share_server, shares_request_spec,
1492 src_cluster_name, pools)
1494 if not compatibility: 1494 ↛ 1495line 1494 didn't jump to line 1495 because the condition on line 1494 was never true
1495 return not_compatible
1497 # Blocking different security services for now
1498 if old_share_network['id'] != new_share_network['id']:
1499 new_sec_services = new_share_network.get('security_services', [])
1500 old_sec_services = old_share_network.get('security_services', [])
1501 if new_sec_services or old_sec_services: 1501 ↛ 1513line 1501 didn't jump to line 1513 because the condition on line 1501 was always true
1502 new_sec_serv_ids = [ss['id'] for ss in new_sec_services]
1503 old_sec_serv_ids = [ss['id'] for ss in old_sec_services]
1504 if not set(new_sec_serv_ids) == set(old_sec_serv_ids): 1504 ↛ 1513line 1504 didn't jump to line 1513 because the condition on line 1504 was always true
1505 msg = _("Cannot perform server migration for different "
1506 "security services. Please choose a suitable "
1507 "share network that matches the source security "
1508 "service.")
1509 LOG.error(msg)
1510 return not_compatible
1512 # Check 'netapp_flexvol_encryption' and 'revert_to_snapshot_support'
1513 specs_to_validate = ('netapp_flexvol_encryption',
1514 'revert_to_snapshot_support')
1515 for req_spec in shares_request_spec.get('shares_req_spec', []):
1516 extra_specs = req_spec.get('share_type', {}).get('extra_specs', {})
1517 for spec in specs_to_validate:
1518 if extra_specs.get(spec) and not pools[0][spec]:
1519 msg = _("Cannot perform server migration since the "
1520 "destination host doesn't support the required "
1521 "extra-spec %s.") % spec
1522 LOG.error(msg)
1523 return not_compatible
1524 # TODO(dviroel): disk_type extra-spec
1526 nondisruptive = (migration_method == SERVER_MIGRATE_SVM_MIGRATE)
1528 compatibility = {
1529 'compatible': True,
1530 'writable': True,
1531 'nondisruptive': nondisruptive,
1532 'preserve_snapshots': True,
1533 'share_network_id': new_share_network['id'],
1534 'migration_cancel': True,
1535 'migration_get_progress': False,
1536 }
1538 return compatibility
1540 @na_utils.trace
1541 def _migration_start_using_svm_dr(
1542 self, source_share_server, dest_share_server):
1543 """Start share server migration using SVM DR.
1545 1. Create vserver peering between source and destination
1546 2. Create SnapMirror
1547 """
1548 src_backend_name = share_utils.extract_host(
1549 source_share_server['host'], level='backend_name')
1550 src_vserver, src_client = self._get_vserver(
1551 share_server=source_share_server, backend_name=src_backend_name)
1552 src_cluster = src_client.get_cluster_name()
1554 dest_backend_name = share_utils.extract_host(
1555 dest_share_server['host'], level='backend_name')
1556 dest_vserver, dest_client = self._get_vserver(
1557 share_server=dest_share_server, backend_name=dest_backend_name)
1558 dest_cluster = dest_client.get_cluster_name()
1560 # 1. Check and create vserver peer if needed
1561 if not self._get_vserver_peers(dest_vserver, src_vserver):
1562 # Request vserver peer creation from destination to source
1563 # NOTE(dviroel): vserver peering rollback is handled by
1564 # '_delete_vserver' function.
1565 dest_client.create_vserver_peer(
1566 dest_vserver, src_vserver,
1567 peer_cluster_name=src_cluster)
1569 # Accepts the vserver peering using active replica host's
1570 # client (inter-cluster only)
1571 if dest_cluster != src_cluster:
1572 src_client.accept_vserver_peer(src_vserver, dest_vserver)
1574 # 2. Create SnapMirror
1575 dm_session = data_motion.DataMotionSession()
1576 try:
1577 dm_session.create_snapmirror_svm(source_share_server,
1578 dest_share_server)
1579 except Exception:
1580 # NOTE(dviroel): vserver peer delete will be handled on vserver
1581 # teardown
1582 dm_session.cancel_snapmirror_svm(source_share_server,
1583 dest_share_server)
1584 msg_args = {
1585 'src': source_share_server['id'],
1586 'dest': dest_share_server['id'],
1587 }
1588 msg = _('Could not initialize SnapMirror between %(src)s and '
1589 '%(dest)s vservers.') % msg_args
1590 raise exception.NetAppException(message=msg)
1591 return None
1593 @na_utils.trace
1594 def _migration_start_using_svm_migrate(
1595 self, context, source_share_server, dest_share_server, src_client,
1596 dest_client):
1597 """Start share server migration using SVM Migrate.
1599 1. Check if share network reusage is supported
1600 2. Create a new ipspace, port and broadcast domain to the dest server
1601 3. Send the request start the share server migration
1602 4. Read the job id and get the id of the migration
1603 5. Set the migration uuid in the backend details
1604 """
1606 # 1. Check if share network reusage is supported
1607 # NOTE(carloss): if share network was not changed, SVM migrate can
1608 # reuse the network allocation from the source share server, so as
1609 # Manila haven't made new allocations, we can just get allocation data
1610 # from the source share server.
1611 if not dest_share_server['network_allocations']:
1612 share_server_network_info = source_share_server
1613 else:
1614 share_server_network_info = dest_share_server
1616 # Reuse network information from the source share server in the SVM
1617 # Migrate if the there was no share network changes.
1618 network_info = {
1619 'network_allocations':
1620 share_server_network_info['network_allocations'],
1621 'neutron_subnet_id':
1622 share_server_network_info['share_network_subnets'][0].get(
1623 'neutron_subnet_id')
1624 }
1626 # 2. Create new ipspace, port and broadcast domain.
1627 node_name = self._client.list_cluster_nodes()[0]
1628 port = self._get_node_data_port(node_name)
1629 vlan = network_info['network_allocations'][0]['segmentation_id']
1630 destination_ipspace = self._client.get_ipspace_name_for_vlan_port(
1631 node_name, port, vlan) or self._create_ipspace(
1632 network_info, client=dest_client)
1633 self._create_port_and_broadcast_domain(
1634 destination_ipspace, network_info)
1636 # Prepare the migration request.
1637 src_cluster_name = src_client.get_cluster_name()
1638 source_share_server_name = (
1639 source_share_server["backend_details"]["vserver_name"])
1641 # 3. Send the migration request to ONTAP.
1642 try:
1643 result = dest_client.svm_migration_start(
1644 src_cluster_name, source_share_server_name,
1645 self._find_matching_aggregates(),
1646 dest_ipspace=destination_ipspace)
1648 # 4. Read the job id and get the id of the migration.
1649 result_job = result.get("job", {})
1650 job_details = dest_client.get_job(result_job.get("uuid"))
1651 job_description = job_details.get('description')
1652 migration_uuid = job_description.split('/')[-1]
1653 except Exception:
1654 # As it failed, we must remove the ipspace, ports and broadcast
1655 # domain.
1656 dest_client.delete_ipspace(destination_ipspace)
1658 msg = _("Unable to start the migration for share server %s."
1659 % source_share_server['id'])
1660 raise exception.NetAppException(msg)
1662 # 5. Returns migration data to be saved as backend details.
1663 server_info = {
1664 "backend_details": {
1665 na_utils.MIGRATION_OPERATION_ID_KEY: migration_uuid
1666 }
1667 }
1668 return server_info
1670 @na_utils.trace
1671 def share_server_migration_start(
1672 self, context, source_share_server, dest_share_server,
1673 share_intances, snapshot_instances):
1674 """Start share server migration.
1676 This method will choose the best migration strategy to perform the
1677 migration, based on the storage functionalities support.
1678 """
1679 src_backend_name = share_utils.extract_host(
1680 source_share_server['host'], level='backend_name')
1681 dest_backend_name = share_utils.extract_host(
1682 dest_share_server['host'], level='backend_name')
1683 dest_client = data_motion.get_client_for_backend(
1684 dest_backend_name, vserver_name=None)
1685 __, src_client = self._get_vserver(
1686 share_server=source_share_server, backend_name=src_backend_name)
1688 use_svm_migrate = (
1689 src_client.is_svm_migrate_supported()
1690 and dest_client.is_svm_migrate_supported())
1692 if use_svm_migrate:
1693 result = self._migration_start_using_svm_migrate(
1694 context, source_share_server, dest_share_server, src_client,
1695 dest_client)
1696 else:
1697 result = self._migration_start_using_svm_dr(
1698 source_share_server, dest_share_server)
1700 msg_args = {
1701 'src': source_share_server['id'],
1702 'dest': dest_share_server['id'],
1703 'migration_method': 'SVM Migrate' if use_svm_migrate else 'SVM DR'
1704 }
1705 msg = _('Starting share server migration from %(src)s to %(dest)s '
1706 'using %(migration_method)s as migration method.')
1707 LOG.info(msg, msg_args)
1709 return result
1711 def _get_snapmirror_svm(self, source_share_server, dest_share_server):
1712 dm_session = data_motion.DataMotionSession()
1713 try:
1714 snapmirrors = dm_session.get_snapmirrors_svm(
1715 source_share_server, dest_share_server)
1716 except netapp_api.NaApiError:
1717 msg_args = {
1718 'src': source_share_server['id'],
1719 'dest': dest_share_server['id']
1720 }
1721 msg = _("Could not retrieve snapmirrors between source "
1722 "%(src)s and destination %(dest)s vServers.") % msg_args
1723 LOG.exception(msg)
1724 raise exception.NetAppException(message=msg)
1726 return snapmirrors
1728 @na_utils.trace
1729 def _share_server_migration_continue_svm_dr(
1730 self, source_share_server, dest_share_server):
1731 """Continues a share server migration using SVM DR."""
1732 snapmirrors = self._get_snapmirror_svm(source_share_server,
1733 dest_share_server)
1734 if not snapmirrors:
1735 msg_args = {
1736 'src': source_share_server['id'],
1737 'dest': dest_share_server['id']
1738 }
1739 msg = _("No snapmirror relationship was found between source "
1740 "%(src)s and destination %(dest)s vServers.") % msg_args
1741 LOG.exception(msg)
1742 raise exception.NetAppException(message=msg)
1744 snapmirror = snapmirrors[0]
1745 in_progress_status = ['preparing', 'transferring', 'finalizing']
1746 mirror_state = snapmirror.get('mirror-state')
1747 status = snapmirror.get('relationship-status')
1748 if mirror_state != 'snapmirrored' and status in in_progress_status:
1749 LOG.debug("Data transfer still in progress.")
1750 return False
1751 elif mirror_state == 'snapmirrored' and status == 'idle':
1752 LOG.info("Source and destination vServers are now snapmirrored.")
1753 return True
1755 msg = _("Snapmirror is not ready yet. The current mirror state is "
1756 "'%(mirror_state)s' and relationship status is '%(status)s'.")
1757 msg_args = {
1758 'mirror_state': mirror_state,
1759 'status': status,
1760 }
1761 LOG.debug(msg, msg_args)
1762 return False
1764 @na_utils.trace
1765 def _share_server_migration_continue_svm_migrate(self, dest_share_server,
1766 migration_id):
1767 """Continues the migration for a share server.
1769 :param dest_share_server: reference for the destination share server.
1770 :param migration_id: ID of the migration.
1771 """
1772 dest_client = data_motion.get_client_for_host(
1773 dest_share_server['host'])
1774 try:
1775 result = dest_client.svm_migration_get(migration_id)
1776 except netapp_api.NaApiError as e:
1777 msg = (_('Failed to continue the migration for share server '
1778 '%(server_id)s. Reason: %(reason)s'
1779 ) % {'server_id': dest_share_server['id'],
1780 'reason': e.message}
1781 )
1782 raise exception.NetAppException(message=msg)
1783 return (
1784 result.get("state") == na_utils.MIGRATION_STATE_READY_FOR_CUTOVER)
1786 @na_utils.trace
1787 def share_server_migration_continue(self, context, source_share_server,
1788 dest_share_server, share_instances,
1789 snapshot_instances):
1790 """Continues the migration of a share server."""
1791 # If the migration operation was started using SVM migrate, it
1792 # returned a migration ID to get information about the job afterwards.
1793 migration_id = self._get_share_server_migration_id(
1794 dest_share_server)
1796 # Checks the progress for a SVM migrate migration.
1797 if migration_id:
1798 return self._share_server_migration_continue_svm_migrate(
1799 dest_share_server, migration_id)
1801 # Checks the progress of a SVM DR Migration.
1802 return self._share_server_migration_continue_svm_dr(
1803 source_share_server, dest_share_server)
1805 def _setup_networking_for_destination_vserver(
1806 self, vserver_client, vserver_name, new_net_allocations):
1807 ipspace_name = vserver_client.get_vserver_ipspace(vserver_name)
1809 # NOTE(dviroel): Security service and NFS configuration should be
1810 # handled by SVM DR, so no changes will be made here.
1811 vlan = new_net_allocations[0]['segmentation_id']
1813 @utils.synchronized('netapp-VLAN-%s' % vlan, external=True)
1814 def setup_network_for_destination_vserver():
1815 self._setup_network_for_vserver(
1816 vserver_name, vserver_client, new_net_allocations,
1817 ipspace_name,
1818 enable_nfs=False,
1819 security_services=None)
1821 setup_network_for_destination_vserver()
1823 @na_utils.trace
1824 def _share_server_migration_complete_svm_dr(
1825 self, source_share_server, dest_share_server, src_vserver,
1826 src_client, share_instances, new_net_allocations):
1827 """Perform steps to complete the SVM DR migration.
1829 1. Do a last SnapMirror update.
1830 2. Quiesce, abort and then break the relationship.
1831 3. Stop the source vserver
1832 4. Configure network interfaces in the destination vserver
1833 5. Start the destinarion vserver
1834 6. Delete and release the snapmirror
1835 """
1836 dest_backend_name = share_utils.extract_host(
1837 dest_share_server['host'], level='backend_name')
1838 dest_vserver, dest_client = self._get_vserver(
1839 share_server=dest_share_server, backend_name=dest_backend_name)
1841 dm_session = data_motion.DataMotionSession()
1842 try:
1843 # 1. Start an update to try to get a last minute transfer before we
1844 # quiesce and break
1845 dm_session.update_snapmirror_svm(source_share_server,
1846 dest_share_server)
1847 except exception.StorageCommunicationException:
1848 # Ignore any errors since the current source may be unreachable
1849 pass
1851 try:
1852 # 2. Attempt to quiesce, abort and then break SnapMirror
1853 dm_session.quiesce_and_break_snapmirror_svm(source_share_server,
1854 dest_share_server)
1855 # NOTE(dviroel): Lets wait until the destination vserver be
1856 # promoted to 'default' and state 'running', before starting
1857 # shutting down the source
1858 dm_session.wait_for_vserver_state(
1859 dest_vserver, dest_client, subtype='default',
1860 state='running', operational_state='stopped',
1861 timeout=(self.configuration.
1862 netapp_server_migration_state_change_timeout))
1864 # 3. Stop source vserver
1865 src_client.stop_vserver(src_vserver)
1867 # 4. Setup network configuration
1868 self._setup_networking_for_destination_vserver(
1869 dest_client, dest_vserver, new_net_allocations)
1871 # 5. Start the destination.
1872 dest_client.start_vserver(dest_vserver)
1874 except Exception:
1875 # Try to recover source vserver
1876 try:
1877 src_client.start_vserver(src_vserver)
1878 except Exception:
1879 LOG.warning("Unable to recover source share server after a "
1880 "migration failure.")
1881 # Destroy any snapmirror and make destination vserver to have its
1882 # subtype set to 'default'
1883 dm_session.cancel_snapmirror_svm(source_share_server,
1884 dest_share_server)
1885 # Rollback resources transferred to the destination
1886 for instance in share_instances:
1887 self._delete_share(instance, dest_vserver, dest_client,
1888 remove_export=False)
1890 msg_args = {
1891 'src': source_share_server['id'],
1892 'dest': dest_share_server['id'],
1893 }
1894 msg = _('Could not complete the migration between %(src)s and '
1895 '%(dest)s vservers.') % msg_args
1896 raise exception.NetAppException(message=msg)
1898 # 6. Delete/release snapmirror
1899 dm_session.delete_snapmirror_svm(source_share_server,
1900 dest_share_server)
1902 @na_utils.trace
1903 def _share_server_migration_complete_svm_migrate(
1904 self, migration_id, dest_share_server):
1905 """Completes share server migration using SVM Migrate.
1907 1. Call functions to conclude the migration for SVM Migrate
1908 2. Waits until the job gets a success status
1909 3. Wait until the migration cancellation reach the desired status
1910 """
1911 dest_client = data_motion.get_client_for_host(
1912 dest_share_server['host'])
1914 try:
1915 # Triggers the migration completion.
1916 job = dest_client.svm_migrate_complete(migration_id)
1917 job_id = self._get_job_uuid(job)
1919 # Wait until the job is successful.
1920 self._wait_for_operation_status(
1921 job_id, dest_client.get_job)
1923 # Wait until the migration is entirely finished.
1924 self._wait_for_operation_status(
1925 migration_id, dest_client.svm_migration_get,
1926 desired_status=na_utils.MIGRATION_STATE_MIGRATE_COMPLETE)
1927 except exception.NetAppException:
1928 msg = _(
1929 "Failed to complete the migration for "
1930 "share server %s.") % dest_share_server['id']
1931 raise exception.NetAppException(msg)
1933 @na_utils.trace
1934 def share_server_migration_complete(self, context, source_share_server,
1935 dest_share_server, share_instances,
1936 snapshot_instances, new_network_alloc):
1937 """Completes share server migration.
1939 1. Call functions to conclude the migration for SVM DR or SVM Migrate
1940 2. Build the list of export_locations for each share
1941 3. Release all resources from the source share server
1942 """
1943 src_backend_name = share_utils.extract_host(
1944 source_share_server['host'], level='backend_name')
1945 src_vserver, src_client = self._get_vserver(
1946 share_server=source_share_server, backend_name=src_backend_name)
1947 src_ipspace_name = src_client.get_vserver_ipspace(src_vserver)
1948 dest_backend_name = share_utils.extract_host(
1949 dest_share_server['host'], level='backend_name')
1951 migration_id = self._get_share_server_migration_id(dest_share_server)
1953 share_server_to_get_vserver_name_from = (
1954 source_share_server if migration_id else dest_share_server)
1956 dest_vserver, dest_client = self._get_vserver(
1957 share_server=share_server_to_get_vserver_name_from,
1958 backend_name=dest_backend_name)
1960 server_backend_details = {}
1961 # 1. Call functions to conclude the migration for SVM DR or SVM
1962 # Migrate.
1963 if migration_id:
1964 self._share_server_migration_complete_svm_migrate(
1965 migration_id, dest_share_server)
1967 server_backend_details = source_share_server['backend_details']
1969 # If there are new network allocations to be added, do so, and add
1970 # them to the share server's backend details.
1971 if dest_share_server['network_allocations']: 1971 ↛ 1993line 1971 didn't jump to line 1993 because the condition on line 1971 was always true
1972 # Teardown the current network allocations
1973 current_network_interfaces = (
1974 dest_client.list_network_interfaces())
1976 # Need a cluster client to be able to remove the current
1977 # network interfaces
1978 dest_cluster_client = data_motion.get_client_for_host(
1979 dest_share_server['host'])
1980 for interface_name in current_network_interfaces:
1981 dest_cluster_client.delete_network_interface(
1982 src_vserver, interface_name)
1983 self._setup_networking_for_destination_vserver(
1984 dest_client, src_vserver, new_network_alloc)
1986 server_backend_details.pop('ports')
1987 ports = {}
1988 for allocation in dest_share_server['network_allocations']:
1989 ports[allocation['id']] = allocation['ip_address']
1990 server_backend_details['ports'] = jsonutils.dumps(ports)
1992 # Delete ipspace on source cluster when possible
1993 src_cluster_client = data_motion.get_client_for_host(
1994 source_share_server['host'])
1996 def _delete_ipspace_and_vlan():
1997 src_ipspace = src_cluster_client.get_ipspaces(
1998 src_ipspace_name)[0]
1999 ports = src_ipspace['ports']
2000 broadcast_domains = src_ipspace['broadcast-domains']
2001 ipspace_deleted = src_cluster_client.delete_ipspace(
2002 src_ipspace_name)
2003 if not ipspace_deleted:
2004 ports = src_cluster_client.get_degraded_ports(
2005 broadcast_domains, src_ipspace_name)
2006 self._delete_port_vlans(src_cluster_client, ports)
2008 try:
2009 _delete_ipspace_and_vlan()
2010 except Exception as e:
2011 msg = _('Could not delete ipspace %s on SVM migration '
2012 'source. Reason: %s') % (src_ipspace_name, e)
2013 LOG.warning(msg)
2014 else:
2015 self._share_server_migration_complete_svm_dr(
2016 source_share_server, dest_share_server, src_vserver,
2017 src_client, share_instances, new_network_alloc)
2019 # 2. Build a dict with shares/snapshot location updates.
2020 # NOTE(dviroel): For SVM DR, the share names aren't modified, only the
2021 # export_locations are updated due to network changes.
2022 share_updates = {}
2023 for instance in share_instances:
2024 # Get the volume to find out the associated aggregate
2025 # Update post-migration info that can't be replicated
2026 try:
2027 share_name = self._get_backend_share_name(instance['id'])
2028 volume = dest_client.get_volume(share_name)
2029 dest_aggregate = volume.get('aggregate')
2031 if not migration_id:
2032 # Update share attributes according with share extra specs.
2033 self._update_share_attributes_after_server_migration(
2034 instance, src_client, dest_aggregate, dest_client)
2036 except Exception:
2037 msg_args = {
2038 'src': source_share_server['id'],
2039 'dest': dest_share_server['id'],
2040 }
2041 msg = _('Could not complete the migration between %(src)s and '
2042 '%(dest)s vservers. One of the shares was not found '
2043 'in the destination vserver.') % msg_args
2044 raise exception.NetAppException(message=msg)
2046 new_share_data = {
2047 'pool_name': volume.get('aggregate')
2048 }
2050 share_host = instance['host']
2052 # If using SVM migrate, must already ensure the export policies
2053 # using the new host information.
2054 if migration_id:
2055 old_aggregate = share_host.split('#')[1]
2056 share_host = share_host.replace(
2057 old_aggregate, dest_aggregate)
2059 export_locations = self._create_export(
2060 instance, dest_share_server, dest_vserver, dest_client,
2061 clear_current_export_policy=False,
2062 ensure_share_already_exists=True,
2063 share_host=share_host)
2064 new_share_data.update({'export_locations': export_locations})
2066 share_updates.update({instance['id']: new_share_data})
2068 # NOTE(dviroel): Nothing to update in snapshot instances since the
2069 # provider location didn't change.
2071 # NOTE(carloss): as SVM DR works like a replica, we must delete the
2072 # source shares after the migration. In case of SVM Migrate, the shares
2073 # were moved to the destination, so there's no need to remove them.
2074 # Then, we need to delete the source server
2075 if not migration_id:
2076 # 3. Release source share resources.
2077 for instance in share_instances:
2078 self._delete_share(instance, src_vserver, src_client,
2079 remove_export=True)
2081 # NOTE(dviroel): source share server deletion must be triggered by
2082 # the manager after finishing the migration
2083 LOG.info('Share server migration completed.')
2084 return {
2085 'share_updates': share_updates,
2086 'server_backend_details': server_backend_details
2087 }
2089 @na_utils.trace
2090 def _get_share_server_migration_id(self, dest_share_server):
2091 return dest_share_server['backend_details'].get(
2092 na_utils.MIGRATION_OPERATION_ID_KEY)
2094 @na_utils.trace
2095 def _migration_cancel_using_svm_dr(
2096 self, source_share_server, dest_share_server, shares):
2097 """Cancel a share server migration that is using SVM DR."""
2098 dm_session = data_motion.DataMotionSession()
2099 dest_backend_name = share_utils.extract_host(dest_share_server['host'],
2100 level='backend_name')
2101 dest_vserver, dest_client = self._get_vserver(
2102 share_server=dest_share_server, backend_name=dest_backend_name)
2104 try:
2105 snapmirrors = self._get_snapmirror_svm(source_share_server,
2106 dest_share_server)
2107 if snapmirrors:
2108 dm_session.cancel_snapmirror_svm(source_share_server,
2109 dest_share_server)
2110 # Do a simple volume cleanup in the destination vserver
2111 for instance in shares:
2112 self._delete_share(instance, dest_vserver, dest_client,
2113 remove_export=False)
2115 except Exception:
2116 msg_args = {
2117 'src': source_share_server['id'],
2118 'dest': dest_share_server['id'],
2119 }
2120 msg = _('Unable to cancel SnapMirror relationship between %(src)s '
2121 'and %(dest)s vservers.') % msg_args
2122 raise exception.NetAppException(message=msg)
2124 @na_utils.trace
2125 def _migration_cancel_using_svm_migrate(self, migration_id,
2126 dest_share_server):
2127 """Cancel a share server migration that is using SVM migrate.
2129 1. Gets information about the migration
2130 2. Pauses the migration, as it can't be cancelled without pausing
2131 3. Ask to ONTAP to actually cancel the migration
2132 """
2134 # 1. Gets information about the migration.
2135 dest_client = data_motion.get_client_for_host(
2136 dest_share_server['host'])
2137 migration_information = dest_client.svm_migration_get(migration_id)
2139 # Gets the ipspace that was created so we can delete it if it's not
2140 # being used anymore.
2141 dest_ipspace_name = (
2142 migration_information["destination"]["ipspace"]["name"])
2144 # 2. Pauses the migration.
2145 try:
2146 # Request the migration to be paused and wait until the job is
2147 # successful.
2148 job = dest_client.svm_migrate_pause(migration_id)
2149 job_id = self._get_job_uuid(job)
2150 self._wait_for_operation_status(job_id, dest_client.get_job)
2152 # Wait until the migration get actually paused.
2153 self._wait_for_operation_status(
2154 migration_id, dest_client.svm_migration_get,
2155 desired_status=na_utils.MIGRATION_STATE_MIGRATE_PAUSED)
2156 except exception.NetAppException:
2157 msg = _("Failed to pause the share server migration.")
2158 raise exception.NetAppException(message=msg)
2160 try:
2161 # 3. Ask to ONTAP to actually cancel the migration.
2162 job = dest_client.svm_migrate_cancel(migration_id)
2163 job_id = self._get_job_uuid(job)
2164 self._wait_for_operation_status(
2165 job_id, dest_client.get_job)
2166 except exception.NetAppException:
2167 msg = _("Failed to cancel the share server migration.")
2168 raise exception.NetAppException(message=msg)
2170 # TODO(chuan) Wait until the ipspace is not being used by vserver
2171 # anymore, which is not deleted immediately after migration
2172 # cancelled.
2173 dest_client.delete_ipspace(dest_ipspace_name)
2174 network_info = dest_share_server.get('network_allocations')
2175 vlan = network_info[0]['segmentation_id'] if network_info else None
2176 if vlan: 2176 ↛ 2182line 2176 didn't jump to line 2182 because the condition on line 2176 was always true
2177 port = None
2178 for node in dest_client.list_cluster_nodes():
2179 port = port or self._get_node_data_port(node)
2180 dest_client.delete_vlan(node, port, vlan)
2182 return
2184 @na_utils.trace
2185 def share_server_migration_cancel(self, context, source_share_server,
2186 dest_share_server, shares, snapshots):
2187 """Send the request to cancel the SVM migration."""
2189 migration_id = self._get_share_server_migration_id(dest_share_server)
2191 if migration_id:
2192 return self._migration_cancel_using_svm_migrate(
2193 migration_id, dest_share_server)
2195 self._migration_cancel_using_svm_dr(
2196 source_share_server, dest_share_server, shares)
2198 LOG.info('Share server migration was cancelled.')
2200 @na_utils.trace
2201 def share_server_migration_get_progress(self, context, src_share_server,
2202 dest_share_server, shares,
2203 snapshots):
2204 """Compare source SVM total shares size with the destination SVM.
2206 1. Gets the total size of the source SVM shares
2207 2. Gets the total size of the destination SVM shares
2208 3. Return the progress up to 99%, because 100% migration will be
2209 returned when SVM migration phase 1 is finished.
2210 """
2212 # Get the total size of the source share server shares.
2213 src_shares_total_size = 0
2214 for instance in shares:
2215 src_shares_total_size = (
2216 src_shares_total_size + instance.get('size', 0))
2218 if src_shares_total_size > 0: 2218 ↛ 2235line 2218 didn't jump to line 2235 because the condition on line 2218 was always true
2219 # Destination share server has the same name as the source share
2220 # server.
2221 dest_share_server_name = self._get_vserver_name(
2222 dest_share_server['source_share_server_id'])
2224 # Get current volume total size in the destination SVM.
2225 dest_shares_total_size = self._client.get_svm_volumes_total_size(
2226 dest_share_server_name)
2228 # The 100% progress will be return only when the SVM migration
2229 # phase 1 is completed. 99% is an arbitrary number.
2230 total_progress = (
2231 (99 * dest_shares_total_size) / src_shares_total_size)
2233 return {'total_progress': round(total_progress)}
2235 return {'total_progress': 0}
2237 def _update_share_attributes_after_server_migration(
2238 self, src_share_instance, src_client, dest_aggregate, dest_client):
2239 """Updates destination share instance with share type extra specs."""
2240 extra_specs = share_types.get_extra_specs_from_share(
2241 src_share_instance)
2242 provisioning_options = self._get_provisioning_options(extra_specs)
2243 volume_name = self._get_backend_share_name(src_share_instance['id'])
2244 # NOTE(dviroel): Need to retrieve current autosize attributes since
2245 # they aren't being updated by SVM DR.
2246 autosize_attrs = src_client.get_volume_autosize_attributes(volume_name)
2247 # NOTE(dviroel): In order to modify maximum and minimum size, we must
2248 # convert from Kbytes to bytes.
2249 for key in ('minimum-size', 'maximum-size'):
2250 autosize_attrs[key] = int(autosize_attrs[key]) * units.Ki
2251 provisioning_options['autosize_attributes'] = autosize_attrs
2252 # NOTE(dviroel): SVM DR already creates a copy of the snapshot policies
2253 # at the destination, using a different name. If we update the snapshot
2254 # policy in these volumes, might end up with an error if the policy
2255 # still does not exist in the destination cluster. Administrators will
2256 # have the opportunity to add the snapshot policy after a successful
2257 # migration.
2258 provisioning_options.pop('snapshot_policy', None)
2260 # Modify volume to match extra specs
2261 dest_client.modify_volume(dest_aggregate, volume_name,
2262 **provisioning_options)
2264 def validate_provisioning_options_for_share(self, provisioning_options,
2265 extra_specs=None,
2266 qos_specs=None):
2267 if provisioning_options.get('adaptive_qos_policy_group') is not None:
2268 msg = _("The extra spec 'adaptive_qos_policy_group' is not "
2269 "supported by backends configured with "
2270 "'driver_handles_share_server' == True mode.")
2271 raise exception.NetAppException(msg)
2273 if (self.configuration.netapp_enable_logical_space_reporting and 2273 ↛ 2275line 2273 didn't jump to line 2275 because the condition on line 2273 was never true
2274 not provisioning_options.get('thin_provisioned')):
2275 msg = _("Logical space reporting is only available if thin "
2276 "provisioning is enabled. Set 'thin_provisioning=True' "
2277 "in your provisioning options")
2278 raise exception.NetAppException(msg)
2280 (super(NetAppCmodeMultiSVMFileStorageLibrary, self)
2281 .validate_provisioning_options_for_share(provisioning_options,
2282 extra_specs=extra_specs,
2283 qos_specs=qos_specs))
2285 def _get_different_keys_for_equal_ss_type(self, current_sec_service,
2286 new_sec_service):
2287 different_keys = []
2289 valid_keys = ['dns_ip', 'server', 'domain', 'user', 'password',
2290 'ou', 'default_ad_site']
2291 for key, value in current_sec_service.items():
2292 if (current_sec_service[key] != new_sec_service[key]
2293 and key in valid_keys):
2294 different_keys.append(key)
2296 return different_keys
2298 def _is_security_service_valid(self, security_service):
2299 mandatory_params = {
2300 'ldap': ['user', 'password'],
2301 'active_directory': ['dns_ip', 'domain', 'user', 'password'],
2302 'kerberos': ['dns_ip', 'domain', 'user', 'password', 'server'],
2303 }
2304 ss_type = security_service['type']
2305 if ss_type == 'ldap':
2306 ad_domain = security_service.get('domain')
2307 ldap_servers = security_service.get('server')
2308 if not bool(ad_domain) ^ bool(ldap_servers): 2308 ↛ 2309line 2308 didn't jump to line 2309 because the condition on line 2308 was never true
2309 msg = _("LDAP security service must have either 'server' or "
2310 "'domain' parameters. Use 'server' for Linux/Unix "
2311 "LDAP servers or 'domain' for Active Directory LDAP "
2312 "server.")
2313 LOG.error(msg)
2314 return False
2316 if ss_type == 'active_directory':
2317 server = security_service.get('server')
2318 default_ad_site = security_service.get('default_ad_site')
2319 if server and default_ad_site: 2319 ↛ 2320line 2319 didn't jump to line 2320 because the condition on line 2319 was never true
2320 msg = _("Active directory security service must not have "
2321 "both 'server' and 'default_ad_site' parameters.")
2322 LOG.error(msg)
2323 return False
2325 if not all([security_service[key] is not None 2325 ↛ 2327line 2325 didn't jump to line 2327 because the condition on line 2325 was never true
2326 for key in mandatory_params[ss_type]]):
2327 msg = _("The security service %s does not have all the "
2328 "parameters needed to used by the share driver."
2329 ) % security_service['id']
2330 LOG.error(msg)
2331 return False
2333 return True
2335 def update_share_server_security_service(self, context, share_server,
2336 network_info,
2337 new_security_service,
2338 current_security_service=None):
2339 current_type = (
2340 current_security_service['type'].lower()
2341 if current_security_service else '')
2342 new_type = new_security_service['type'].lower()
2344 vserver_name, vserver_client = self._get_vserver(
2345 share_server=share_server)
2347 # Check if this update is supported by our driver
2348 if not self.check_update_share_server_security_service(
2349 context, share_server, network_info, new_security_service,
2350 current_security_service=current_security_service):
2351 msg = _("The requested security service update is not supported "
2352 "by the NetApp driver.")
2353 LOG.error(msg)
2354 raise exception.NetAppException(msg)
2356 if current_security_service is None:
2357 self._client.setup_security_services(
2358 [new_security_service],
2359 vserver_client,
2360 vserver_name,
2361 self.configuration.netapp_cifs_aes_encryption)
2362 LOG.info("A new security service configuration was added to share "
2363 "server '%(share_server_id)s'",
2364 {'share_server_id': share_server['id']})
2365 return
2367 different_keys = self._get_different_keys_for_equal_ss_type(
2368 current_security_service, new_security_service)
2369 if not different_keys:
2370 msg = _("The former and the latter security services are "
2371 "equal. Nothing to do.")
2372 LOG.debug(msg)
2373 return
2375 if 'dns_ip' in different_keys: 2375 ↛ 2398line 2375 didn't jump to line 2398 because the condition on line 2375 was always true
2376 dns_ips = set()
2377 domains = set()
2378 # Read all dns-ips and domains from other security services
2379 for sec_svc in network_info[0]['security_services']:
2380 if sec_svc['type'] == current_type:
2381 # skip the one that we are replacing
2382 continue
2383 if sec_svc.get('dns_ip') is not None: 2383 ↛ 2386line 2383 didn't jump to line 2386 because the condition on line 2383 was always true
2384 for dns_ip in sec_svc['dns_ip'].split(','):
2385 dns_ips.add(dns_ip.strip())
2386 if sec_svc.get('domain') is not None: 2386 ↛ 2379line 2386 didn't jump to line 2379 because the condition on line 2386 was always true
2387 domains.add(sec_svc['domain'])
2388 # Merge with the new dns configuration
2389 if new_security_service.get('dns_ip') is not None: 2389 ↛ 2392line 2389 didn't jump to line 2392 because the condition on line 2389 was always true
2390 for dns_ip in new_security_service['dns_ip'].split(','):
2391 dns_ips.add(dns_ip.strip())
2392 if new_security_service.get('domain') is not None: 2392 ↛ 2396line 2392 didn't jump to line 2396 because the condition on line 2392 was always true
2393 domains.add(new_security_service['domain'])
2395 # Update vserver DNS configuration
2396 vserver_client.update_dns_configuration(dns_ips, domains)
2398 if new_type == 'kerberos':
2399 if 'server' in different_keys: 2399 ↛ 2414line 2399 didn't jump to line 2414 because the condition on line 2399 was always true
2400 # NOTE(dviroel): Only IPs will be updated here, new principals
2401 # won't be configured here. It is expected that only the IP was
2402 # changed, but the KDC remains the same.
2403 LOG.debug('Updating kerberos realm on NetApp backend.')
2404 vserver_client.update_kerberos_realm(new_security_service)
2406 elif new_type == 'active_directory': 2406 ↛ 2411line 2406 didn't jump to line 2411 because the condition on line 2406 was always true
2407 vserver_client.modify_active_directory_security_service(
2408 vserver_name, different_keys, new_security_service,
2409 current_security_service)
2410 else:
2411 vserver_client.modify_ldap(new_security_service,
2412 current_security_service)
2414 LOG.info("Security service configuration was updated for share server "
2415 "'%(share_server_id)s'",
2416 {'share_server_id': share_server['id']})
2418 def check_update_share_server_security_service(
2419 self, context, share_server, network_info,
2420 new_security_service, current_security_service=None):
2421 current_type = (
2422 current_security_service['type'].lower()
2423 if current_security_service else '')
2425 if not self._is_security_service_valid(new_security_service): 2425 ↛ 2426line 2425 didn't jump to line 2426 because the condition on line 2425 was never true
2426 self.message_api.create(
2427 context,
2428 message_field.Action.ADD_UPDATE_SECURITY_SERVICE,
2429 new_security_service['project_id'],
2430 resource_type=message_field.Resource.SECURITY_SERVICE,
2431 resource_id=new_security_service['id'],
2432 detail=(message_field.Detail
2433 .UNSUPPORTED_ADD_UDPATE_SECURITY_SERVICE))
2434 return False
2436 if current_security_service:
2437 if current_type != 'ldap':
2438 # NOTE(dviroel): We don't support domain/realm updates for
2439 # Kerberos security service, because it might require a new SPN
2440 # to be created and to destroy the old one, thus disrupting all
2441 # shares hosted by this share server. Same issue can happen
2442 # with AD domain modifications.
2443 if (current_security_service['domain'].lower() !=
2444 new_security_service['domain'].lower()):
2445 msg = _("Currently the driver does not support updates "
2446 "in the security service 'domain'.")
2447 LOG.info(msg)
2448 return False
2449 return True
2451 def check_update_share_server_network_allocations(
2452 self, context, share_server, current_network_allocations,
2453 new_share_network_subnet, security_services, share_instances,
2454 share_instances_rules):
2455 """Check if new network configuration is valid."""
2456 LOG.debug('Checking if network configuration is valid to update share'
2457 'server %s.', share_server['id'])
2458 # Get segmentation_id from current allocations to check if added
2459 # subnet is in the same network segment as the others.
2460 ref_subnet = current_network_allocations['subnets'][0]
2461 ref_subnet_allocation = ref_subnet['network_allocations'][0]
2462 seg_id = ref_subnet_allocation['segmentation_id']
2463 new_subnet_seg_id = new_share_network_subnet['segmentation_id']
2464 network_info = [dict(segmentation_id=seg_id),
2465 dict(segmentation_id=new_subnet_seg_id)]
2466 is_valid_configuration = True
2467 try:
2468 self._validate_network_type([new_share_network_subnet])
2469 self._validate_share_network_subnets(network_info)
2470 except exception.NetworkBadConfigurationException as e:
2471 LOG.error('Invalid share server network allocation. %s', e)
2472 is_valid_configuration = False
2474 return is_valid_configuration
2476 def _build_model_update(self, current_network_allocations,
2477 new_network_allocations, export_locations=None):
2478 """Updates server details for a new set of network allocations"""
2479 ports = {}
2480 for subnet in current_network_allocations['subnets']:
2481 for alloc in subnet['network_allocations']:
2482 ports[alloc['id']] = alloc['ip_address']
2484 for alloc in new_network_allocations['network_allocations']:
2485 ports[alloc['id']] = alloc['ip_address']
2487 model_update = {'server_details': {'ports': jsonutils.dumps(ports)}}
2488 if export_locations:
2489 model_update.update({'share_updates': export_locations})
2491 return model_update
2493 def update_share_server_network_allocations(
2494 self, context, share_server, current_network_allocations,
2495 new_network_allocations, security_services, shares, snapshots):
2496 """Update network allocations for the share server."""
2497 vserver_name = self._get_vserver_name(share_server['id'])
2498 vserver_client = self._get_api_client(vserver=vserver_name)
2499 ipspace_name = self._client.get_vserver_ipspace(vserver_name)
2500 network_info = [new_network_allocations]
2502 LOG.debug('Adding new subnet allocations to share server %s',
2503 share_server['id'])
2504 try:
2505 self._setup_network_for_vserver(
2506 vserver_name, vserver_client, network_info, ipspace_name,
2507 enable_nfs=False, security_services=None, nfs_config=None)
2508 except Exception as e:
2509 with excutils.save_and_reraise_exception():
2510 LOG.error("Failed to update vserver network configuration.")
2511 updates = self._build_model_update(
2512 current_network_allocations, new_network_allocations,
2513 export_locations=None)
2514 e.detail_data = updates
2516 updated_export_locations = {}
2517 for share in shares:
2518 if share["replica_state"] in (None,
2519 constants.REPLICA_STATE_ACTIVE):
2520 host = share['host']
2521 export_locations = self._create_export(
2522 share, share_server, vserver_name, vserver_client,
2523 clear_current_export_policy=False,
2524 ensure_share_already_exists=True,
2525 share_host=host)
2526 updated_export_locations.update(
2527 {share['id']: export_locations})
2529 updates = self._build_model_update(
2530 current_network_allocations, new_network_allocations,
2531 updated_export_locations)
2532 return updates
2534 def _get_backup_vserver(self, backup, share_server=None):
2535 backend_name = self._get_backend(backup)
2536 backend_config = data_motion.get_backend_configuration(backend_name)
2537 des_cluster_api_client = self._get_api_client_for_backend(
2538 backend_name)
2540 aggr_list = des_cluster_api_client.list_non_root_aggregates()
2541 aggr_pattern = (backend_config.
2542 netapp_aggregate_name_search_pattern)
2543 if aggr_pattern: 2543 ↛ 2549line 2543 didn't jump to line 2549 because the condition on line 2543 was always true
2544 aggr_matching_list = [
2545 element for element in aggr_list if re.search(aggr_pattern,
2546 element)
2547 ]
2548 aggr_list = aggr_matching_list
2549 share_server_id = share_server['id']
2550 des_vserver = f"{Backup.DES_VSERVER_PREFIX.value}_{share_server_id}"
2551 LOG.debug("Creating vserver %s:", des_vserver)
2552 try:
2553 des_cluster_api_client.create_vserver(
2554 des_vserver,
2555 None,
2556 None,
2557 aggr_list,
2558 'Default',
2559 client_cmode_rest.DEFAULT_SECURITY_CERT_EXPIRE_DAYS,
2560 )
2561 except netapp_api.NaApiError as e:
2562 with excutils.save_and_reraise_exception() as exc_context:
2563 if 'already used' in e.message: 2563 ↛ 2565line 2563 didn't jump to line 2565
2564 exc_context.reraise = False
2565 return des_vserver
2567 def _delete_backup_vserver(self, backup, des_vserver):
2568 """Delete the vserver """
2570 backend_name = self._get_backend(backup)
2571 des_vserver_client = self._get_api_client_for_backend(
2572 backend_name, vserver=des_vserver)
2573 try:
2574 des_cluster_api_client = self._get_api_client_for_backend(
2575 backend_name)
2576 des_cluster_api_client.delete_vserver(des_vserver,
2577 des_vserver_client)
2578 except exception.NetAppException as e:
2579 with excutils.save_and_reraise_exception() as exc_context:
2580 if 'has shares' in e.msg: 2580 ↛ exitline 2580 didn't jump to the function exit
2581 exc_context.reraise = False
2583 def _check_data_lif_count_limit_reached_for_ha_pair(self, client):
2584 ha_pair = {node: client.get_storage_failover_partner(node)
2585 for node in client.list_cluster_nodes()}
2586 # TODO(agireesh): Get the data LIFs details for node using REST call
2587 # The 'get_data_lif_details_for_nodes' method is missing for REST
2588 # workflow because there is no REST available to retrieve the data
2589 # LIF's capacity and details for the nodes. Filed the RFE on ONTAP
2590 # to implement the corresponding REST, and once it is available, the
2591 # REST workflow will be added as part of the fix (bug #2100673).
2592 lif_info_for_node = client.get_data_lif_details_for_nodes()
2593 lif_info_dict = {info['node']: info for info in lif_info_for_node}
2595 for node, ha_partner in ha_pair.items():
2596 if node in lif_info_dict: 2596 ↛ 2595line 2596 didn't jump to line 2595 because the condition on line 2596 was always true
2597 data_lif_count = int(lif_info_dict[node].get(
2598 'count-for-node', 0)
2599 )
2600 lif_limit_for_node = int(lif_info_dict[node].get(
2601 'limit-for-node')
2602 )
2603 migratable_data_lifs = (
2604 client.get_migratable_data_lif_for_node(ha_partner)
2605 )
2606 expected_lif_count_after_failover = (
2607 data_lif_count + len(migratable_data_lifs)
2608 )
2609 if expected_lif_count_after_failover > lif_limit_for_node:
2610 msg_args = {
2611 'data_lif': expected_lif_count_after_failover,
2612 'lif_limit': lif_limit_for_node,
2613 }
2614 msg = _("If a partner node fails, the number of data LIFs"
2615 " {%(data_lif)s} will exceed the node's maximum "
2616 "data LIF limit {%(lif_limit)s}") % msg_args
2617 LOG.error(msg)
2618 raise exception.NetAppException(msg)