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

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. 

16 

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 

23 

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 

30 

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 

44 

45 

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' 

54 

55 

56class NetAppCmodeMultiSVMFileStorageLibrary( 

57 lib_base.NetAppCmodeFileStorageLibrary): 

58 

59 @na_utils.trace 

60 def check_for_setup_error(self): 

61 

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) 

67 

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) 

72 

73 # Ensure FlexGroup support 

74 aggr_list = self._client.list_non_root_aggregates() 

75 self._initialize_flexgroup_pools(set(aggr_list)) 

76 

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) 

84 

85 (super(NetAppCmodeMultiSVMFileStorageLibrary, self). 

86 check_for_setup_error()) 

87 

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 

95 

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) 

105 

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) 

112 

113 if not vserver_client.vserver_exists(vserver): 

114 raise exception.VserverNotFound(vserver=vserver) 

115 

116 return vserver, vserver_client 

117 

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 } 

126 

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() 

134 

135 (super(NetAppCmodeMultiSVMFileStorageLibrary, self). 

136 _handle_housekeeping_tasks()) 

137 

138 @na_utils.trace 

139 def _find_matching_aggregates(self, aggregate_names=None): 

140 """Find all aggregates match pattern.""" 

141 

142 if not self.is_flexvol_pool_configured(): 

143 return [] 

144 

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() 

147 

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)] 

151 

152 @na_utils.trace 

153 def _set_network_with_metadata(self, network_info): 

154 """Set the subnet metadata information for network_info object.""" 

155 

156 for network in network_info: 

157 metadata = network.get('subnet_metadata') 

158 if not metadata: 

159 continue 

160 

161 metadata_vlan = metadata.get(METADATA_VLAN) 

162 if not metadata_vlan: 

163 continue 

164 

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) 

172 

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) 

179 

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']) 

187 

188 @na_utils.trace 

189 def setup_server(self, network_info, metadata=None): 

190 """Creates and configures new Vserver.""" 

191 

192 # only changes network_info if one of networks has metadata set. 

193 self._set_network_with_metadata(network_info) 

194 

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']) 

202 

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) 

210 

211 vlan = network_info[0]['segmentation_id'] 

212 

213 @utils.synchronized('netapp-VLAN-%s' % vlan, external=True) 

214 def setup_server_with_lock(): 

215 self._validate_network_type(network_info) 

216 

217 # Before proceeding, make sure subnet configuration is valid 

218 self._validate_share_network_subnets(network_info) 

219 

220 vserver_name = self._get_vserver_name(server_id) 

221 server_details = { 

222 'vserver_name': vserver_name, 

223 'ports': jsonutils.dumps(ports), 

224 } 

225 

226 if self.is_nfs_config_supported: 

227 server_details['nfs_config'] = jsonutils.dumps(nfs_config) 

228 

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 

239 

240 if metadata.get('encryption_key_ref'): 

241 self._create_barbican_kms_config_for_specified_vserver( 

242 vserver_name, metadata) 

243 

244 return server_details 

245 return setup_server_with_lock() 

246 

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) 

256 

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) 

265 

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) 

271 

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] 

276 

277 return nfs_config 

278 

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] 

285 

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']) 

291 

292 @na_utils.trace 

293 def _get_vserver_name(self, server_id): 

294 return self.configuration.netapp_vserver_name_template % server_id 

295 

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) 

307 

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.""" 

312 

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) 

323 

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) 

335 

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]) 

341 

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) 

369 

370 vserver_client = self._get_api_client(vserver=vserver_name) 

371 

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) 

385 

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.""" 

390 

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 

409 

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) 

416 

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) 

420 

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) 

424 

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 

438 

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 

446 

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) 

453 

454 self._create_vserver_routes(vserver_client, network) 

455 

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) 

465 

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) 

472 

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('-', '_') 

476 

477 @na_utils.trace 

478 def _create_ipspace(self, network_info, client=None): 

479 """If supported, create an IPspace for a new Vserver.""" 

480 

481 desired_client = client if client else self._client 

482 

483 if not desired_client.features.IPSPACES: 

484 return None 

485 

486 if (network_info['network_allocations'][0]['network_type'] 

487 not in SEGMENTED_NETWORK_TYPES): 

488 return client_cmode.DEFAULT_IPSPACE 

489 

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 

496 

497 ipspace_name = self._get_valid_ipspace_name(ipspace_id) 

498 desired_client.create_ipspace(ipspace_name) 

499 

500 return ipspace_name 

501 

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 

511 

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) 

518 

519 self._create_lif(vserver_client, vserver_name, ipspace_name, 

520 node_name, lif_name, network_allocation, 

521 lif_home_port=lif_home_port) 

522 

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) 

532 

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] 

539 

540 network_allocation = network_allocations[0] 

541 lif_name = self._get_lif_name(node_name, network_allocation) 

542 

543 self._create_lif(vserver_client, vserver_name, ipspace_name, 

544 node_name, lif_name, network_allocation, 

545 lif_home_port=home_port) 

546 

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']) 

560 

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] 

572 

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 

580 

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']) 

590 

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 

606 

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)) 

612 

613 self._client.create_network_interface( 

614 ip_address, netmask, node_name, lif_home_port, 

615 vserver_name, lif_name) 

616 

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']) 

621 

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 

627 

628 self._client.create_port_and_broadcast_domain( 

629 node_name, port, vlan, mtu, ipspace_name) 

630 

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()) 

635 

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 

640 

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 

646 

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 

652 

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 

658 

659 self._delete_vserver(vserver, security_services=security_services) 

660 

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.""" 

665 

666 ipspace_name = None 

667 

668 ipspaces = self._client.get_ipspaces(vserver_name=vserver) 

669 if ipspaces: 

670 ipspace = ipspaces[0] 

671 ipspace_name = ipspace['ipspace'] 

672 

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) 

676 

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']) 

683 

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 

689 

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']) 

697 

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) 

702 

703 # NOTE(dviroel): Attempt to delete all vserver peering 

704 # created by replication 

705 self._delete_vserver_peers(vserver) 

706 

707 self._client.delete_vserver(vserver, 

708 vserver_client, 

709 security_services=security_services) 

710 

711 if ipspace_name is None: 

712 return 

713 

714 ipspace_deleted = self._client.delete_ipspace(ipspace_name) 

715 

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) 

733 

734 self._delete_port_vlans(self._client, ports) 

735 

736 @utils.synchronized('netapp-VLAN-%s' % vlan_id, external=True) 

737 def _delete_vserver_with_lock(): 

738 _delete_vserver_without_lock() 

739 

740 if needs_lock: 

741 return _delete_vserver_with_lock() 

742 else: 

743 return _delete_vserver_without_lock() 

744 

745 def _delete_port_vlans(_self, client, ports): 

746 """Delete Port's VLAN configuration 

747 

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.") 

757 

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')) 

764 

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 

771 

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. 

776 

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) 

784 

785 # 2. Retrieve the active replica host's client and cluster name 

786 src_replica = self.find_active_replica(replica_list) 

787 

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() 

794 

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() 

801 

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) 

809 

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) 

815 

816 return (super(NetAppCmodeMultiSVMFileStorageLibrary, self). 

817 create_replica(context, replica_list, new_replica, 

818 access_rules, share_snapshots)) 

819 

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. 

823 

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) 

831 

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) 

841 

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) 

851 

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) 

858 

859 backend_details = { 

860 'vserver_name': new_vserver_name, 

861 } 

862 

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) 

868 

869 return new_vserver_name, backend_details 

870 

871 def unmanage_server(self, server_details, security_services=None): 

872 pass 

873 

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) 

878 

879 vserver, vserver_client = self._get_vserver(vserver_name=vserver_name) 

880 

881 interfaces = vserver_client.get_network_interfaces() 

882 allocations = [] 

883 for lif in interfaces: 

884 allocations.append(lif['address']) 

885 return allocations 

886 

887 def _get_correct_vserver_old_name(self, identifier): 

888 

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 

893 

894 def _get_snapmirrors(self, vserver, peer_vserver): 

895 return self._client.get_snapmirrors( 

896 source_vserver=vserver, dest_vserver=peer_vserver) 

897 

898 def _get_snapmirrors_destinations(self, vserver, peer_vserver): 

899 return self._client.get_snapmirror_destinations( 

900 source_vserver=vserver, dest_vserver=peer_vserver) 

901 

902 def _get_vservers_from_replicas(self, context, replica_list, new_replica): 

903 active_replica = self.find_active_replica(replica_list) 

904 

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) 

908 

909 return vserver, peer_vserver 

910 

911 def _get_vserver_peers(self, vserver=None, peer_vserver=None): 

912 return self._client.get_vserver_peers(vserver, peer_vserver) 

913 

914 def _create_vserver_peer(self, context, vserver, peer_vserver): 

915 self._client.create_vserver_peer(vserver, peer_vserver) 

916 

917 def _delete_vserver_peer(self, vserver, peer_vserver): 

918 self._client.delete_vserver_peer(vserver, peer_vserver) 

919 

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) 

933 

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() 

941 

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) 

957 

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) 

962 

963 return (super(NetAppCmodeMultiSVMFileStorageLibrary, self) 

964 .create_share_from_snapshot( 

965 context, share, snapshot, share_server=share_server, 

966 parent_share=parent_share)) 

967 

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 

971 

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 

977 

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 

986 

987 return False 

988 

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. 

994 

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 

1007 

1008 nfs_config = None 

1009 extra_specs = share_types.get_extra_specs_from_share(share) 

1010 

1011 if self.is_nfs_config_supported: 

1012 nfs_config = self._get_nfs_config_provisioning_options(extra_specs) 

1013 

1014 provisioning_options = self._get_provisioning_options(extra_specs) 

1015 

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') 

1023 

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 

1034 

1035 # There is no compatible share server to be reused 

1036 return None 

1037 

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.""" 

1045 

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 

1055 

1056 if (share_group and share_group.get('share_server_id') != 

1057 share_server['id']): 

1058 return False 

1059 

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 

1077 

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 

1089 

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) 

1095 

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 

1109 

1110 return True 

1111 

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. 

1117 

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 

1124 

1125 nfs_config = None 

1126 if self.is_nfs_config_supported: 

1127 nfs_config = self._get_nfs_config_share_group(share_group_ref) 

1128 

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 

1136 

1137 return None 

1138 

1139 @na_utils.trace 

1140 def _get_nfs_config_share_group(self, share_group_ref): 

1141 """Get the NFS config of the share group. 

1142 

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']) 

1151 

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 

1158 

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) 

1166 

1167 return nfs_config 

1168 

1169 @na_utils.trace 

1170 def manage_existing(self, share, driver_options, share_server=None): 

1171 

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) 

1183 

1184 return (super(NetAppCmodeMultiSVMFileStorageLibrary, self). 

1185 manage_existing(share, driver_options, 

1186 share_server=share_server)) 

1187 

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. 

1192 

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 

1207 

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 

1220 

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 

1236 

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") 

1242 

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. 

1248 

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. 

1255 

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 

1263 

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") 

1270 

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) 

1280 

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')}) 

1288 

1289 # Didn't fail, so we can retry. 

1290 raise exception.ShareBackendException(msg) 

1291 

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 

1298 

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) 

1308 

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. 

1314 

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 """ 

1318 

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 } 

1328 

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) 

1334 

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) 

1344 

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.') 

1351 

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) 

1357 

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 

1367 

1368 job_id = self._get_job_uuid(job) 

1369 

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 

1381 

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 

1387 

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 """ 

1395 

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"]) 

1401 

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 

1413 

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) 

1418 

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): 

1423 

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 } 

1433 

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 

1440 

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 

1448 

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 

1462 

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 

1473 

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 

1482 

1483 pools = self._get_pools() 

1484 

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) 

1489 

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) 

1493 

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 

1496 

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 

1511 

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 

1525 

1526 nondisruptive = (migration_method == SERVER_MIGRATE_SVM_MIGRATE) 

1527 

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 } 

1537 

1538 return compatibility 

1539 

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. 

1544 

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() 

1553 

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() 

1559 

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) 

1568 

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) 

1573 

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 

1592 

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. 

1598 

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 """ 

1605 

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 

1615 

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 } 

1625 

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) 

1635 

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"]) 

1640 

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) 

1647 

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) 

1657 

1658 msg = _("Unable to start the migration for share server %s." 

1659 % source_share_server['id']) 

1660 raise exception.NetAppException(msg) 

1661 

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 

1669 

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. 

1675 

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) 

1687 

1688 use_svm_migrate = ( 

1689 src_client.is_svm_migrate_supported() 

1690 and dest_client.is_svm_migrate_supported()) 

1691 

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) 

1699 

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) 

1708 

1709 return result 

1710 

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) 

1725 

1726 return snapmirrors 

1727 

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) 

1743 

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 

1754 

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 

1763 

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. 

1768 

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) 

1785 

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) 

1795 

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) 

1800 

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) 

1804 

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) 

1808 

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'] 

1812 

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) 

1820 

1821 setup_network_for_destination_vserver() 

1822 

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. 

1828 

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) 

1840 

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 

1850 

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)) 

1863 

1864 # 3. Stop source vserver 

1865 src_client.stop_vserver(src_vserver) 

1866 

1867 # 4. Setup network configuration 

1868 self._setup_networking_for_destination_vserver( 

1869 dest_client, dest_vserver, new_net_allocations) 

1870 

1871 # 5. Start the destination. 

1872 dest_client.start_vserver(dest_vserver) 

1873 

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) 

1889 

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) 

1897 

1898 # 6. Delete/release snapmirror 

1899 dm_session.delete_snapmirror_svm(source_share_server, 

1900 dest_share_server) 

1901 

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. 

1906 

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']) 

1913 

1914 try: 

1915 # Triggers the migration completion. 

1916 job = dest_client.svm_migrate_complete(migration_id) 

1917 job_id = self._get_job_uuid(job) 

1918 

1919 # Wait until the job is successful. 

1920 self._wait_for_operation_status( 

1921 job_id, dest_client.get_job) 

1922 

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) 

1932 

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. 

1938 

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') 

1950 

1951 migration_id = self._get_share_server_migration_id(dest_share_server) 

1952 

1953 share_server_to_get_vserver_name_from = ( 

1954 source_share_server if migration_id else dest_share_server) 

1955 

1956 dest_vserver, dest_client = self._get_vserver( 

1957 share_server=share_server_to_get_vserver_name_from, 

1958 backend_name=dest_backend_name) 

1959 

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) 

1966 

1967 server_backend_details = source_share_server['backend_details'] 

1968 

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()) 

1975 

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) 

1985 

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) 

1991 

1992 # Delete ipspace on source cluster when possible 

1993 src_cluster_client = data_motion.get_client_for_host( 

1994 source_share_server['host']) 

1995 

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) 

2007 

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) 

2018 

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') 

2030 

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) 

2035 

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) 

2045 

2046 new_share_data = { 

2047 'pool_name': volume.get('aggregate') 

2048 } 

2049 

2050 share_host = instance['host'] 

2051 

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) 

2058 

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}) 

2065 

2066 share_updates.update({instance['id']: new_share_data}) 

2067 

2068 # NOTE(dviroel): Nothing to update in snapshot instances since the 

2069 # provider location didn't change. 

2070 

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) 

2080 

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 } 

2088 

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) 

2093 

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) 

2103 

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) 

2114 

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) 

2123 

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. 

2128 

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 """ 

2133 

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) 

2138 

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"]) 

2143 

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) 

2151 

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) 

2159 

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) 

2169 

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) 

2181 

2182 return 

2183 

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.""" 

2188 

2189 migration_id = self._get_share_server_migration_id(dest_share_server) 

2190 

2191 if migration_id: 

2192 return self._migration_cancel_using_svm_migrate( 

2193 migration_id, dest_share_server) 

2194 

2195 self._migration_cancel_using_svm_dr( 

2196 source_share_server, dest_share_server, shares) 

2197 

2198 LOG.info('Share server migration was cancelled.') 

2199 

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. 

2205 

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 """ 

2211 

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)) 

2217 

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']) 

2223 

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) 

2227 

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) 

2232 

2233 return {'total_progress': round(total_progress)} 

2234 

2235 return {'total_progress': 0} 

2236 

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) 

2259 

2260 # Modify volume to match extra specs 

2261 dest_client.modify_volume(dest_aggregate, volume_name, 

2262 **provisioning_options) 

2263 

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) 

2272 

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) 

2279 

2280 (super(NetAppCmodeMultiSVMFileStorageLibrary, self) 

2281 .validate_provisioning_options_for_share(provisioning_options, 

2282 extra_specs=extra_specs, 

2283 qos_specs=qos_specs)) 

2284 

2285 def _get_different_keys_for_equal_ss_type(self, current_sec_service, 

2286 new_sec_service): 

2287 different_keys = [] 

2288 

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) 

2295 

2296 return different_keys 

2297 

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 

2315 

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 

2324 

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 

2332 

2333 return True 

2334 

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() 

2343 

2344 vserver_name, vserver_client = self._get_vserver( 

2345 share_server=share_server) 

2346 

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) 

2355 

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 

2366 

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 

2374 

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']) 

2394 

2395 # Update vserver DNS configuration 

2396 vserver_client.update_dns_configuration(dns_ips, domains) 

2397 

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) 

2405 

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) 

2413 

2414 LOG.info("Security service configuration was updated for share server " 

2415 "'%(share_server_id)s'", 

2416 {'share_server_id': share_server['id']}) 

2417 

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 '') 

2424 

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 

2435 

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 

2450 

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 

2473 

2474 return is_valid_configuration 

2475 

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'] 

2483 

2484 for alloc in new_network_allocations['network_allocations']: 

2485 ports[alloc['id']] = alloc['ip_address'] 

2486 

2487 model_update = {'server_details': {'ports': jsonutils.dumps(ports)}} 

2488 if export_locations: 

2489 model_update.update({'share_updates': export_locations}) 

2490 

2491 return model_update 

2492 

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] 

2501 

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 

2515 

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}) 

2528 

2529 updates = self._build_model_update( 

2530 current_network_allocations, new_network_allocations, 

2531 updated_export_locations) 

2532 return updates 

2533 

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) 

2539 

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 

2566 

2567 def _delete_backup_vserver(self, backup, des_vserver): 

2568 """Delete the vserver """ 

2569 

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 

2582 

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} 

2594 

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)