Coverage for manila/share/drivers/dell_emc/plugins/powermax/connection.py: 0%

471 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2026-02-18 22:19 +0000

1# Copyright (c) 2019 Dell Inc. or its subsidiaries. 

2# All Rights Reserved. 

3# 

4# Licensed under the Apache License, Version 2.0 (the "License"); you may 

5# not use this file except in compliance with the License. You may obtain 

6# a copy of the License at 

7# 

8# http://www.apache.org/licenses/LICENSE-2.0 

9# 

10# Unless required by applicable law or agreed to in writing, software 

11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 

12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 

13# License for the specific language governing permissions and limitations 

14# under the License. 

15"""PowerMax backend for the Dell EMC Manila driver.""" 

16 

17import copy 

18import random 

19 

20from oslo_config import cfg 

21from oslo_log import log 

22from oslo_utils import excutils 

23from oslo_utils import units 

24 

25from manila.common import constants as const 

26from manila import exception 

27from manila.i18n import _ 

28from manila.share.drivers.dell_emc.common.enas import constants 

29from manila.share.drivers.dell_emc.common.enas import utils as enas_utils 

30from manila.share.drivers.dell_emc.plugins import base as driver 

31from manila.share.drivers.dell_emc.plugins.powermax import ( 

32 object_manager as manager) 

33from manila.share import utils as share_utils 

34from manila import utils 

35 

36"""Version history: 

37 1.0.0 - Initial version 

38 2.0.0 - Implement IPv6 support 

39 3.0.0 - Rebranding to PowerMax 

40 3.1.0 - Access Host details prevents a read-only share mounts 

41 (bug #1845147) 

42 3.2.0 - Wrong format of export locations (bug #1871999) 

43 3.3.0 - Victoria release 

44 3.4.0 - Wallaby release 

45 3.5.0 - Xena release 

46""" 

47VERSION = "3.5.0" 

48 

49LOG = log.getLogger(__name__) 

50 

51POWERMAX_OPTS = [ 

52 cfg.StrOpt('powermax_server_container', 

53 help='Data mover to host the NAS server.'), 

54 cfg.ListOpt('powermax_share_data_pools', 

55 help='Comma separated list of pools that can be used to ' 

56 'persist share data.'), 

57 cfg.ListOpt('powermax_ethernet_ports', 

58 help='Comma separated list of ports that can be used for ' 

59 'share server interfaces. Members of the list ' 

60 'can be Unix-style glob expressions.') 

61] 

62 

63CONF = cfg.CONF 

64CONF.register_opts(POWERMAX_OPTS) 

65 

66 

67@enas_utils.decorate_all_methods(enas_utils.log_enter_exit, 

68 debug_only=True) 

69class PowerMaxStorageConnection(driver.StorageConnection): 

70 """Implements powermax specific functionality for Dell EMC Manila driver. 

71 

72 """ 

73 @enas_utils.log_enter_exit 

74 def __init__(self, *args, **kwargs): 

75 super(PowerMaxStorageConnection, self).__init__(*args, **kwargs) 

76 if 'configuration' in kwargs: 

77 kwargs['configuration'].append_config_values(POWERMAX_OPTS) 

78 

79 self.mover_name = None 

80 self.pools = None 

81 self.manager = None 

82 self.pool_conf = None 

83 self.reserved_percentage = None 

84 self.reserved_snapshot_percentage = None 

85 self.reserved_share_extend_percentage = None 

86 self.driver_handles_share_servers = True 

87 self.port_conf = None 

88 self.ipv6_implemented = True 

89 self.dhss_mandatory_security_service_association = { 

90 'nfs': None, 

91 'cifs': ['active_directory', ] 

92 } 

93 

94 def create_share(self, context, share, share_server=None): 

95 """Create a share and export it based on protocol used.""" 

96 share_name = share['id'] 

97 size = share['size'] * units.Ki 

98 

99 share_proto = share['share_proto'].upper() 

100 

101 # Validate the share protocol 

102 if share_proto not in ('NFS', 'CIFS'): 

103 raise exception.InvalidShare( 

104 reason=(_('Invalid NAS protocol supplied: %s.') 

105 % share_proto)) 

106 

107 # Get the pool name from share host field 

108 pool_name = share_utils.extract_host(share['host'], level='pool') 

109 if not pool_name: 

110 message = (_("Pool is not available in the share host %s.") % 

111 share['host']) 

112 raise exception.InvalidHost(reason=message) 

113 

114 # Validate share server 

115 self._share_server_validation(share_server) 

116 

117 if share_proto == 'CIFS': 

118 vdm_name = self._get_share_server_name(share_server) 

119 server_name = vdm_name 

120 

121 # Check if CIFS server exists. 

122 status, server = self._get_context('CIFSServer').get(server_name, 

123 vdm_name) 

124 if status != constants.STATUS_OK: 

125 message = (_("CIFS server %s not found.") % server_name) 

126 LOG.error(message) 

127 raise exception.EMCPowerMaxXMLAPIError(err=message) 

128 

129 self._allocate_container(share_name, size, share_server, pool_name) 

130 

131 if share_proto == 'NFS': 

132 location = self._create_nfs_share(share_name, share_server) 

133 elif share_proto == 'CIFS': 

134 location = self._create_cifs_share(share_name, share_server) 

135 

136 return [ 

137 {'path': location} 

138 ] 

139 

140 def _share_server_validation(self, share_server): 

141 """Validate the share server.""" 

142 if not share_server: 

143 msg = _('Share server not provided') 

144 raise exception.InvalidInput(reason=msg) 

145 

146 backend_details = share_server.get('backend_details') 

147 vdm = backend_details.get( 

148 'share_server_name') if backend_details else None 

149 

150 if vdm is None: 

151 message = _("No share server found.") 

152 LOG.error(message) 

153 raise exception.EMCPowerMaxXMLAPIError(err=message) 

154 

155 def _allocate_container(self, share_name, size, share_server, pool_name): 

156 """Allocate file system for share.""" 

157 vdm_name = self._get_share_server_name(share_server) 

158 

159 self._get_context('FileSystem').create( 

160 share_name, size, pool_name, vdm_name) 

161 

162 def _allocate_container_from_snapshot(self, share, snapshot, share_server, 

163 pool_name): 

164 """Allocate file system from snapshot.""" 

165 vdm_name = self._get_share_server_name(share_server) 

166 

167 interconn_id = self._get_context('Mover').get_interconnect_id( 

168 self.mover_name, self.mover_name) 

169 

170 self._get_context('FileSystem').create_from_snapshot( 

171 share['id'], snapshot['id'], snapshot['share_id'], 

172 pool_name, vdm_name, interconn_id) 

173 

174 nwe_size = share['size'] * units.Ki 

175 self._get_context('FileSystem').extend(share['id'], pool_name, 

176 nwe_size) 

177 

178 @enas_utils.log_enter_exit 

179 def _create_cifs_share(self, share_name, share_server): 

180 """Create CIFS share.""" 

181 vdm_name = self._get_share_server_name(share_server) 

182 server_name = vdm_name 

183 

184 # Get available CIFS Server and interface (one CIFS server per VDM) 

185 status, server = self._get_context('CIFSServer').get(server_name, 

186 vdm_name) 

187 

188 if 'interfaces' not in server or len(server['interfaces']) == 0: 

189 message = (_("CIFS server %s doesn't have interface, " 

190 "so the share is inaccessible.") 

191 % server['compName']) 

192 LOG.error(message) 

193 raise exception.EMCPowerMaxXMLAPIError(err=message) 

194 

195 interface = enas_utils.export_unc_path(server['interfaces'][0]) 

196 

197 self._get_context('CIFSShare').create(share_name, server['name'], 

198 vdm_name) 

199 

200 self._get_context('CIFSShare').disable_share_access(share_name, 

201 vdm_name) 

202 location = (r'\\%(interface)s\%(name)s' % 

203 {'interface': interface, 'name': share_name}) 

204 return location 

205 

206 @enas_utils.log_enter_exit 

207 def _create_nfs_share(self, share_name, share_server): 

208 """Create NFS share.""" 

209 vdm_name = self._get_share_server_name(share_server) 

210 

211 self._get_context('NFSShare').create(share_name, vdm_name) 

212 

213 nfs_if = enas_utils.convert_ipv6_format_if_needed( 

214 share_server['backend_details']['nfs_if']) 

215 

216 return ('%(nfs_if)s:/%(share_name)s' 

217 % {'nfs_if': nfs_if, 

218 'share_name': share_name}) 

219 

220 def create_share_from_snapshot(self, context, share, snapshot, 

221 share_server=None, parent_share=None): 

222 """Create a share from a snapshot - clone a snapshot.""" 

223 share_name = share['id'] 

224 

225 share_proto = share['share_proto'].upper() 

226 

227 # Validate the share protocol 

228 if share_proto not in ('NFS', 'CIFS'): 

229 raise exception.InvalidShare( 

230 reason=(_('Invalid NAS protocol supplied: %s.') 

231 % share_proto)) 

232 

233 # Get the pool name from share host field 

234 pool_name = share_utils.extract_host(share['host'], level='pool') 

235 if not pool_name: 

236 message = (_("Pool is not available in the share host %s.") % 

237 share['host']) 

238 raise exception.InvalidHost(reason=message) 

239 

240 self._share_server_validation(share_server) 

241 

242 self._allocate_container_from_snapshot( 

243 share, snapshot, share_server, pool_name) 

244 

245 nfs_if = enas_utils.convert_ipv6_format_if_needed( 

246 share_server['backend_details']['nfs_if']) 

247 

248 if share_proto == 'NFS': 

249 self._create_nfs_share(share_name, share_server) 

250 location = ('%(nfs_if)s:/%(share_name)s' 

251 % {'nfs_if': nfs_if, 

252 'share_name': share_name}) 

253 elif share_proto == 'CIFS': 

254 location = self._create_cifs_share(share_name, share_server) 

255 

256 return [ 

257 {'path': location} 

258 ] 

259 

260 def create_snapshot(self, context, snapshot, share_server=None): 

261 """Create snapshot from share.""" 

262 share_name = snapshot['share_id'] 

263 status, filesystem = self._get_context('FileSystem').get(share_name) 

264 if status != constants.STATUS_OK: 

265 message = (_("File System %s not found.") % share_name) 

266 LOG.error(message) 

267 raise exception.EMCPowerMaxXMLAPIError(err=message) 

268 

269 pool_id = filesystem['pools_id'][0] 

270 

271 self._get_context('Snapshot').create(snapshot['id'], 

272 snapshot['share_id'], 

273 pool_id) 

274 

275 def delete_share(self, context, share, share_server=None): 

276 """Delete a share.""" 

277 if share_server is None: 

278 LOG.warning("Share network should be specified for " 

279 "share deletion.") 

280 return 

281 

282 share_proto = share['share_proto'].upper() 

283 

284 if share_proto == 'NFS': 

285 self._delete_nfs_share(share, share_server) 

286 elif share_proto == 'CIFS': 

287 self._delete_cifs_share(share, share_server) 

288 else: 

289 raise exception.InvalidShare( 

290 reason=_('Unsupported share protocol')) 

291 

292 @enas_utils.log_enter_exit 

293 def _delete_cifs_share(self, share, share_server): 

294 """Delete CIFS share.""" 

295 vdm_name = self._get_share_server_name(share_server) 

296 

297 name = share['id'] 

298 

299 self._get_context('CIFSShare').delete(name, vdm_name) 

300 

301 self._deallocate_container(name, vdm_name) 

302 

303 @enas_utils.log_enter_exit 

304 def _delete_nfs_share(self, share, share_server): 

305 """Delete NFS share.""" 

306 vdm_name = self._get_share_server_name(share_server) 

307 

308 name = share['id'] 

309 

310 self._get_context('NFSShare').delete(name, vdm_name) 

311 

312 self._deallocate_container(name, vdm_name) 

313 

314 @enas_utils.log_enter_exit 

315 def _deallocate_container(self, share_name, vdm_name): 

316 """Delete underneath objects of the share.""" 

317 path = '/' + share_name 

318 

319 try: 

320 # Delete mount point 

321 self._get_context('MountPoint').delete(path, vdm_name) 

322 except exception.EMCPowerMaxXMLAPIError as e: 

323 LOG.exception("CIFS server %(name)s on mover %(mover_name)s " 

324 "not found due to error %(err)s. Skip the " 

325 "deletion.", 

326 {'name': path, 'mover_name': vdm_name, 

327 'err': e.message}) 

328 

329 try: 

330 # Delete file system 

331 self._get_context('FileSystem').delete(share_name) 

332 except exception.EMCPowerMaxXMLAPIError as e: 

333 LOG.exception("File system %(share_name)s not found due to " 

334 "error %(err)s. Skip the deletion.", 

335 {'share_name': share_name, 

336 'err': e.message}) 

337 

338 def delete_snapshot(self, context, snapshot, share_server=None): 

339 """Delete a snapshot.""" 

340 self._get_context('Snapshot').delete(snapshot['id']) 

341 

342 def ensure_share(self, context, share, share_server=None): 

343 """Ensure that the share is exported.""" 

344 

345 def extend_share(self, share, new_size, share_server=None): 

346 # Get the pool name from share host field 

347 pool_name = share_utils.extract_host(share['host'], level='pool') 

348 if not pool_name: 

349 message = (_("Pool is not available in the share host %s.") % 

350 share['host']) 

351 raise exception.InvalidHost(reason=message) 

352 

353 share_name = share['id'] 

354 

355 self._get_context('FileSystem').extend( 

356 share_name, pool_name, new_size * units.Ki) 

357 

358 def allow_access(self, context, share, access, share_server=None): 

359 """Allow access to a share.""" 

360 access_level = access['access_level'] 

361 if access_level not in const.ACCESS_LEVELS: 

362 raise exception.InvalidShareAccessLevel(level=access_level) 

363 

364 share_proto = share['share_proto'] 

365 

366 if share_proto == 'NFS': 

367 self._nfs_allow_access(context, share, access, share_server) 

368 elif share_proto == 'CIFS': 

369 self._cifs_allow_access(context, share, access, share_server) 

370 else: 

371 raise exception.InvalidShare( 

372 reason=(_('Invalid NAS protocol supplied: %s.') 

373 % share_proto)) 

374 

375 @enas_utils.log_enter_exit 

376 def _cifs_allow_access(self, context, share, access, share_server): 

377 """Allow access to CIFS share.""" 

378 vdm_name = self._get_share_server_name(share_server) 

379 share_name = share['id'] 

380 

381 if access['access_type'] != 'user': 

382 reason = _('Only user access type allowed for CIFS share') 

383 raise exception.InvalidShareAccess(reason=reason) 

384 

385 user_name = access['access_to'] 

386 

387 access_level = access['access_level'] 

388 if access_level == const.ACCESS_LEVEL_RW: 

389 cifs_access = constants.CIFS_ACL_FULLCONTROL 

390 else: 

391 cifs_access = constants.CIFS_ACL_READ 

392 

393 # Check if CIFS server exists. 

394 server_name = vdm_name 

395 status, server = self._get_context('CIFSServer').get(server_name, 

396 vdm_name) 

397 if status != constants.STATUS_OK: 

398 message = (_("CIFS server %s not found.") % server_name) 

399 LOG.error(message) 

400 raise exception.EMCPowerMaxXMLAPIError(err=message) 

401 

402 self._get_context('CIFSShare').allow_share_access( 

403 vdm_name, 

404 share_name, 

405 user_name, 

406 server['domain'], 

407 access=cifs_access) 

408 

409 @enas_utils.log_enter_exit 

410 def _nfs_allow_access(self, context, share, access, share_server): 

411 """Allow access to NFS share.""" 

412 vdm_name = self._get_share_server_name(share_server) 

413 

414 access_type = access['access_type'] 

415 if access_type != 'ip': 

416 reason = _('Only ip access type allowed.') 

417 raise exception.InvalidShareAccess(reason=reason) 

418 

419 host_ip = access['access_to'] 

420 access_level = access['access_level'] 

421 

422 self._get_context('NFSShare').allow_share_access( 

423 share['id'], host_ip, vdm_name, access_level) 

424 

425 def update_access(self, context, share, access_rules, add_rules, 

426 delete_rules, share_server=None): 

427 # deleting rules 

428 for rule in delete_rules: 

429 self.deny_access(context, share, rule, share_server) 

430 

431 # adding rules 

432 for rule in add_rules: 

433 self.allow_access(context, share, rule, share_server) 

434 

435 # recovery mode 

436 if not (add_rules or delete_rules): 

437 white_list = [] 

438 for rule in access_rules: 

439 self.allow_access(context, share, rule, share_server) 

440 white_list.append( 

441 enas_utils.convert_ipv6_format_if_needed( 

442 rule['access_to'])) 

443 self.clear_access(share, share_server, white_list) 

444 

445 def clear_access(self, share, share_server, white_list): 

446 share_proto = share['share_proto'].upper() 

447 share_name = share['id'] 

448 if share_proto == 'CIFS': 

449 self._cifs_clear_access(share_name, share_server, white_list) 

450 elif share_proto == 'NFS': 

451 self._nfs_clear_access(share_name, share_server, white_list) 

452 

453 @enas_utils.log_enter_exit 

454 def _cifs_clear_access(self, share_name, share_server, white_list): 

455 """Clear access for CIFS share except hosts in the white list.""" 

456 vdm_name = self._get_share_server_name(share_server) 

457 

458 # Check if CIFS server exists. 

459 server_name = vdm_name 

460 status, server = self._get_context('CIFSServer').get(server_name, 

461 vdm_name) 

462 if status != constants.STATUS_OK: 

463 message = (_("CIFS server %(server_name)s has issue. " 

464 "Detail: %(status)s") % 

465 {'server_name': server_name, 'status': status}) 

466 raise exception.EMCPowerMaxXMLAPIError(err=message) 

467 

468 self._get_context('CIFSShare').clear_share_access( 

469 share_name=share_name, 

470 mover_name=vdm_name, 

471 domain=server['domain'], 

472 white_list_users=white_list) 

473 

474 @enas_utils.log_enter_exit 

475 def _nfs_clear_access(self, share_name, share_server, white_list): 

476 """Clear access for NFS share except hosts in the white list.""" 

477 self._get_context('NFSShare').clear_share_access( 

478 share_name=share_name, 

479 mover_name=self._get_share_server_name(share_server), 

480 white_list_hosts=white_list) 

481 

482 def deny_access(self, context, share, access, share_server=None): 

483 """Deny access to a share.""" 

484 share_proto = share['share_proto'] 

485 

486 if share_proto == 'NFS': 

487 self._nfs_deny_access(share, access, share_server) 

488 elif share_proto == 'CIFS': 

489 self._cifs_deny_access(share, access, share_server) 

490 else: 

491 raise exception.InvalidShare( 

492 reason=_('Unsupported share protocol')) 

493 

494 @enas_utils.log_enter_exit 

495 def _cifs_deny_access(self, share, access, share_server): 

496 """Deny access to CIFS share.""" 

497 vdm_name = self._get_share_server_name(share_server) 

498 share_name = share['id'] 

499 

500 if access['access_type'] != 'user': 

501 LOG.warning("Only user access type allowed for CIFS share.") 

502 return 

503 

504 user_name = access['access_to'] 

505 

506 access_level = access['access_level'] 

507 if access_level == const.ACCESS_LEVEL_RW: 

508 cifs_access = constants.CIFS_ACL_FULLCONTROL 

509 else: 

510 cifs_access = constants.CIFS_ACL_READ 

511 

512 # Check if CIFS server exists. 

513 server_name = vdm_name 

514 status, server = self._get_context('CIFSServer').get(server_name, 

515 vdm_name) 

516 if status != constants.STATUS_OK: 

517 message = (_("CIFS server %s not found.") % server_name) 

518 LOG.error(message) 

519 raise exception.EMCPowerMaxXMLAPIError(err=message) 

520 

521 self._get_context('CIFSShare').deny_share_access( 

522 vdm_name, 

523 share_name, 

524 user_name, 

525 server['domain'], 

526 access=cifs_access) 

527 

528 @enas_utils.log_enter_exit 

529 def _nfs_deny_access(self, share, access, share_server): 

530 """Deny access to NFS share.""" 

531 vdm_name = self._get_share_server_name(share_server) 

532 

533 access_type = access['access_type'] 

534 if access_type != 'ip': 

535 LOG.warning("Only ip access type allowed.") 

536 return 

537 

538 host_ip = enas_utils.convert_ipv6_format_if_needed(access['access_to']) 

539 

540 self._get_context('NFSShare').deny_share_access(share['id'], host_ip, 

541 vdm_name) 

542 

543 def check_for_setup_error(self): 

544 """Check for setup error.""" 

545 # To verify the input from Manila configuration 

546 status, out = self._get_context('Mover').get_ref(self.mover_name, 

547 True) 

548 if constants.STATUS_ERROR == status: 

549 message = (_("Could not find Data Mover by name: %s.") % 

550 self.mover_name) 

551 LOG.error(message) 

552 raise exception.InvalidParameterValue(err=message) 

553 

554 self.pools = self._get_managed_storage_pools(self.pool_conf) 

555 

556 def _get_managed_storage_pools(self, pools): 

557 matched_pools = set() 

558 if pools: 

559 # Get the real pools from the backend storage 

560 status, backend_pools = self._get_context('StoragePool').get_all() 

561 if status != constants.STATUS_OK: 

562 message = (_("Failed to get storage pool information. " 

563 "Reason: %s") % backend_pools) 

564 LOG.error(message) 

565 raise exception.EMCPowerMaxXMLAPIError(err=message) 

566 

567 real_pools = set([item for item in backend_pools]) 

568 conf_pools = set([item.strip() for item in pools]) 

569 matched_pools, unmatched_pools = enas_utils.do_match_any( 

570 real_pools, conf_pools) 

571 

572 if not matched_pools: 

573 msg = (_("None of the specified storage pools to be managed " 

574 "exist. Please check your configuration " 

575 "emc_nas_pool_names in manila.conf. " 

576 "The available pools in the backend are %s.") % 

577 ",".join(real_pools)) 

578 raise exception.InvalidParameterValue(err=msg) 

579 

580 LOG.info("Storage pools: %s will be managed.", 

581 ",".join(matched_pools)) 

582 else: 

583 LOG.debug("No storage pool is specified, so all pools " 

584 "in storage system will be managed.") 

585 return matched_pools 

586 

587 def connect(self, emc_share_driver, context): 

588 """Connect to PowerMax NAS server.""" 

589 config = emc_share_driver.configuration 

590 config.append_config_values(POWERMAX_OPTS) 

591 self.mover_name = config.safe_get('powermax_server_container') 

592 

593 self.pool_conf = config.safe_get('powermax_share_data_pools') 

594 

595 self.reserved_percentage = config.safe_get('reserved_share_percentage') 

596 if self.reserved_percentage is None: 

597 self.reserved_percentage = 0 

598 

599 self.reserved_snapshot_percentage = config.safe_get( 

600 'reserved_share_from_snapshot_percentage') 

601 if self.reserved_snapshot_percentage is None: 

602 self.reserved_snapshot_percentage = self.reserved_percentage 

603 

604 self.reserved_share_extend_percentage = config.safe_get( 

605 'reserved_share_extend_percentage') 

606 if self.reserved_share_extend_percentage is None: 

607 self.reserved_share_extend_percentage = self.reserved_percentage 

608 

609 self.manager = manager.StorageObjectManager(config) 

610 self.port_conf = config.safe_get('powermax_ethernet_ports') 

611 

612 def get_managed_ports(self): 

613 # Get the real ports(devices) list from the backend storage 

614 real_ports = self._get_physical_devices(self.mover_name) 

615 

616 if not self.port_conf: 

617 LOG.debug("No ports are specified, so any of the ports on the " 

618 "Data Mover can be used.") 

619 return real_ports 

620 

621 matched_ports, unmanaged_ports = enas_utils.do_match_any( 

622 real_ports, self.port_conf) 

623 

624 if not matched_ports: 

625 msg = (_("None of the specified network ports exist. " 

626 "Please check your configuration powermax_ethernet_ports " 

627 "in manila.conf. The available ports on the Data Mover " 

628 "are %s.") % 

629 ",".join(real_ports)) 

630 raise exception.BadConfigurationException(reason=msg) 

631 

632 LOG.debug("Ports: %s can be used.", ",".join(matched_ports)) 

633 

634 return list(matched_ports) 

635 

636 def update_share_stats(self, stats_dict): 

637 """Communicate with EMCNASClient to get the stats.""" 

638 stats_dict['driver_version'] = VERSION 

639 

640 self._get_context('Mover').get_ref(self.mover_name, True) 

641 

642 stats_dict['pools'] = [] 

643 

644 status, pools = self._get_context('StoragePool').get_all() 

645 for name, pool in pools.items(): 

646 if not self.pools or pool['name'] in self.pools: 

647 total_size = float(pool['total_size']) 

648 used_size = float(pool['used_size']) 

649 

650 pool_stat = { 

651 'pool_name': pool['name'], 

652 'total_capacity_gb': enas_utils.mb_to_gb(total_size), 

653 'free_capacity_gb': 

654 enas_utils.mb_to_gb(total_size - used_size), 

655 'qos': False, 

656 'reserved_percentage': self.reserved_percentage, 

657 'reserved_snapshot_percentage': 

658 self.reserved_snapshot_percentage, 

659 'reserved_share_extend_percentage': 

660 self.reserved_share_extend_percentage, 

661 'snapshot_support': True, 

662 'create_share_from_snapshot_support': True, 

663 'revert_to_snapshot_support': False, 

664 'ipv6_support': True 

665 } 

666 stats_dict['pools'].append(pool_stat) 

667 

668 if not stats_dict['pools']: 

669 message = _("Failed to update storage pool.") 

670 LOG.error(message) 

671 raise exception.EMCPowerMaxXMLAPIError(err=message) 

672 

673 def get_pool(self, share): 

674 """Get the pool name of the share.""" 

675 share_name = share['id'] 

676 status, filesystem = self._get_context('FileSystem').get(share_name) 

677 if status != constants.STATUS_OK: 

678 message = (_("File System %(name)s not found. " 

679 "Reason: %(err)s") % 

680 {'name': share_name, 'err': filesystem}) 

681 LOG.error(message) 

682 raise exception.EMCPowerMaxXMLAPIError(err=message) 

683 

684 pool_id = filesystem['pools_id'][0] 

685 

686 # Get the real pools from the backend storage 

687 status, backend_pools = self._get_context('StoragePool').get_all() 

688 if status != constants.STATUS_OK: 

689 message = (_("Failed to get storage pool information. " 

690 "Reason: %s") % backend_pools) 

691 LOG.error(message) 

692 raise exception.EMCPowerMaxXMLAPIError(err=message) 

693 

694 for name, pool_info in backend_pools.items(): 

695 if pool_info['id'] == pool_id: 

696 return name 

697 

698 available_pools = [item for item in backend_pools] 

699 message = (_("No matched pool name for share: %(share)s. " 

700 "Available pools: %(pools)s") % 

701 {'share': share_name, 'pools': available_pools}) 

702 raise exception.EMCPowerMaxXMLAPIError(err=message) 

703 

704 def get_network_allocations_number(self): 

705 """Returns number of network allocations for creating VIFs.""" 

706 return constants.IP_ALLOCATIONS 

707 

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

709 """Set up and configure share server. 

710 

711 Sets up and configures share server with given network parameters. 

712 """ 

713 # Only support single security service with type 'active_directory' 

714 vdm_name = network_info['server_id'] 

715 vlan_id = network_info['segmentation_id'] 

716 active_directory = None 

717 allocated_interfaces = [] 

718 

719 if network_info.get('security_services'): 

720 is_valid, active_directory = self._get_valid_security_service( 

721 network_info['security_services']) 

722 

723 if not is_valid: 

724 raise exception.EMCPowerMaxXMLAPIError(err=active_directory) 

725 

726 try: 

727 if not self._vdm_exist(vdm_name): 

728 LOG.debug('Share server %s not found, creating ' 

729 'share server...', vdm_name) 

730 self._get_context('VDM').create(vdm_name, self.mover_name) 

731 

732 devices = self.get_managed_ports() 

733 

734 for net_info in network_info['network_allocations']: 

735 random.shuffle(devices) 

736 ip_version = net_info['ip_version'] 

737 interface = { 

738 'name': net_info['id'][-12:], 

739 'device_name': devices[0], 

740 'ip': net_info['ip_address'], 

741 'mover_name': self.mover_name, 

742 'vlan_id': vlan_id if vlan_id else -1, 

743 } 

744 if ip_version == 6: 

745 interface['ip_version'] = ip_version 

746 interface['net_mask'] = str( 

747 utils.cidr_to_prefixlen( 

748 network_info['cidr'])) 

749 else: 

750 interface['net_mask'] = utils.cidr_to_netmask( 

751 network_info['cidr']) 

752 

753 self._get_context('MoverInterface').create(interface) 

754 

755 allocated_interfaces.append(interface) 

756 

757 cifs_interface = allocated_interfaces[0] 

758 nfs_interface = allocated_interfaces[1] 

759 if active_directory: 

760 self._configure_active_directory( 

761 active_directory, vdm_name, cifs_interface) 

762 

763 self._get_context('VDM').attach_nfs_interface( 

764 vdm_name, nfs_interface['name']) 

765 

766 return { 

767 'share_server_name': vdm_name, 

768 'cifs_if': cifs_interface['ip'], 

769 'nfs_if': nfs_interface['ip'], 

770 } 

771 

772 except Exception: 

773 with excutils.save_and_reraise_exception(): 

774 LOG.exception('Could not setup server') 

775 server_details = self._construct_backend_details( 

776 vdm_name, allocated_interfaces) 

777 self.teardown_server( 

778 server_details, network_info['security_services']) 

779 

780 def _construct_backend_details(self, vdm_name, interfaces): 

781 if_number = len(interfaces) 

782 cifs_if = interfaces[0]['ip'] if if_number > 0 else None 

783 nfs_if = interfaces[1]['ip'] if if_number > 1 else None 

784 

785 return { 

786 'share_server_name': vdm_name, 

787 'cifs_if': cifs_if, 

788 'nfs_if': nfs_if, 

789 } 

790 

791 @enas_utils.log_enter_exit 

792 def _vdm_exist(self, name): 

793 status, out = self._get_context('VDM').get(name) 

794 if constants.STATUS_OK != status: 

795 return False 

796 

797 return True 

798 

799 def _get_physical_devices(self, mover_name): 

800 """Get a proper network device to create interface.""" 

801 devices = self._get_context('Mover').get_physical_devices(mover_name) 

802 if not devices: 

803 message = (_("Could not get physical device port on mover %s.") % 

804 self.mover_name) 

805 LOG.error(message) 

806 raise exception.EMCPowerMaxXMLAPIError(err=message) 

807 

808 return devices 

809 

810 def _configure_active_directory( 

811 self, security_service, vdm_name, interface): 

812 

813 domain = security_service['domain'] 

814 server = security_service['dns_ip'] 

815 

816 self._get_context('DNSDomain').create(self.mover_name, domain, server) 

817 

818 cifs_server_args = { 

819 'name': vdm_name, 

820 'interface_ip': interface['ip'], 

821 'domain_name': security_service['domain'], 

822 'user_name': security_service['user'], 

823 'password': security_service['password'], 

824 'mover_name': vdm_name, 

825 'is_vdm': True, 

826 } 

827 

828 self._get_context('CIFSServer').create(cifs_server_args) 

829 

830 def teardown_server(self, server_details, security_services=None): 

831 """Teardown share server.""" 

832 if not server_details: 

833 LOG.debug('Server details are empty.') 

834 return 

835 

836 vdm_name = server_details.get('share_server_name') 

837 if not vdm_name: 

838 LOG.debug('No share server found in server details.') 

839 return 

840 

841 cifs_if = server_details.get('cifs_if') 

842 nfs_if = server_details.get('nfs_if') 

843 

844 status, vdm = self._get_context('VDM').get(vdm_name) 

845 if constants.STATUS_OK != status: 

846 LOG.debug('Share server %s not found.', vdm_name) 

847 return 

848 

849 interfaces = self._get_context('VDM').get_interfaces(vdm_name) 

850 

851 for if_name in interfaces['nfs']: 

852 self._get_context('VDM').detach_nfs_interface(vdm_name, if_name) 

853 

854 if security_services: 

855 # Only support single security service with type 'active_directory' 

856 is_valid, active_directory = self._get_valid_security_service( 

857 security_services) 

858 if is_valid: 

859 status, servers = self._get_context('CIFSServer').get_all( 

860 vdm_name) 

861 if constants.STATUS_OK != status: 

862 LOG.error('Could not find CIFS server by name: %s.', 

863 vdm_name) 

864 else: 

865 cifs_servers = copy.deepcopy(servers) 

866 for name, server in cifs_servers.items(): 

867 # Unjoin CIFS Server from domain 

868 cifs_server_args = { 

869 'name': server['name'], 

870 'join_domain': False, 

871 'user_name': active_directory['user'], 

872 'password': active_directory['password'], 

873 'mover_name': vdm_name, 

874 'is_vdm': True, 

875 } 

876 

877 try: 

878 self._get_context('CIFSServer').modify( 

879 cifs_server_args) 

880 except exception.EMCPowerMaxXMLAPIError as expt: 

881 LOG.debug("Failed to modify CIFS server " 

882 "%(server)s. Reason: %(err)s.", 

883 {'server': server, 'err': expt}) 

884 

885 self._get_context('CIFSServer').delete(name, vdm_name) 

886 

887 # Delete interface from Data Mover 

888 if cifs_if: 

889 self._get_context('MoverInterface').delete(cifs_if, 

890 self.mover_name) 

891 

892 if nfs_if: 

893 self._get_context('MoverInterface').delete(nfs_if, 

894 self.mover_name) 

895 

896 # Delete Virtual Data Mover 

897 self._get_context('VDM').delete(vdm_name) 

898 

899 def _get_valid_security_service(self, security_services): 

900 """Validate security services and return a supported security service. 

901 

902 :param security_services: 

903 :returns: (<is_valid>, <data>) -- <is_valid> is true to indicate 

904 security_services includes zero or single security service for 

905 active directory. Otherwise, it would return false. <data> return 

906 error message when <is_valid> is false. Otherwise, it will 

907 return zero or single security service for active directory. 

908 """ 

909 

910 # Only support single security service with type 'active_directory' 

911 if (len(security_services) > 1 or 

912 (security_services and 

913 security_services[0]['type'] != 'active_directory')): 

914 return False, _("Unsupported security services. " 

915 "Only support single security service and " 

916 "only support type 'active_directory'") 

917 

918 return True, security_services[0] 

919 

920 def _get_share_server_name(self, share_server): 

921 try: 

922 return share_server['backend_details']['share_server_name'] 

923 except Exception: 

924 LOG.debug("Didn't get share server name from share_server %s.", 

925 share_server) 

926 return share_server['id'] 

927 

928 def _get_context(self, context_type): 

929 return self.manager.getStorageContext(context_type)