Coverage for manila/share/drivers/dell_emc/plugins/vnx/connection.py: 98%

472 statements  

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

1# Copyright (c) 2014 EMC Corporation. 

2# All Rights Reserved. 

3# 

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

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

6# a copy of the License at 

7# 

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

9# 

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

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

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

13# License for the specific language governing permissions and limitations 

14# under the License. 

15"""VNX backend for the 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.vnx import object_manager as manager 

32from manila.share import utils as share_utils 

33from manila import utils 

34 

35"""Version history: 

36 1.0.0 - Initial version (Liberty) 

37 2.0.0 - Bumped the version for Mitaka 

38 3.0.0 - Bumped the version for Ocata 

39 4.0.0 - Bumped the version for Pike 

40 5.0.0 - Bumped the version for Queens 

41 9.0.0 - Bumped the version for Ussuri 

42 9.0.1 - Fixes bug 1871999: wrong format of export locations 

43""" 

44VERSION = "9.0.1" 

45 

46LOG = log.getLogger(__name__) 

47 

48VNX_OPTS = [ 

49 cfg.StrOpt('vnx_server_container', 

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

51 cfg.ListOpt('vnx_share_data_pools', 

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

53 'persist share data.'), 

54 cfg.ListOpt('vnx_ethernet_ports', 

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

56 'share server interfaces. Members of the list ' 

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

58] 

59 

60CONF = cfg.CONF 

61CONF.register_opts(VNX_OPTS) 

62 

63 

64@enas_utils.decorate_all_methods(enas_utils.log_enter_exit, 

65 debug_only=True) 

66class VNXStorageConnection(driver.StorageConnection): 

67 """Implements VNX specific functionality for EMC Manila driver.""" 

68 

69 @enas_utils.log_enter_exit 

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

71 super(VNXStorageConnection, self).__init__(*args, **kwargs) 

72 if 'configuration' in kwargs: 72 ↛ 73line 72 didn't jump to line 73 because the condition on line 72 was never true

73 kwargs['configuration'].append_config_values(VNX_OPTS) 

74 

75 self.mover_name = None 

76 self.pools = None 

77 self.manager = None 

78 self.pool_conf = None 

79 self.reserved_percentage = None 

80 self.reserved_snapshot_percentage = None 

81 self.reserved_share_extend_percentage = None 

82 self.driver_handles_share_servers = True 

83 self.port_conf = None 

84 self.ipv6_implemented = True 

85 self.dhss_mandatory_security_service_association = { 

86 'nfs': None, 

87 'cifs': ['active_directory', ] 

88 } 

89 

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

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

92 share_name = share['id'] 

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

94 

95 share_proto = share['share_proto'] 

96 

97 # Validate the share protocol 

98 if share_proto.upper() not in ('NFS', 'CIFS'): 

99 raise exception.InvalidShare( 

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

101 % share_proto)) 

102 

103 # Get the pool name from share host field 

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

105 if not pool_name: 

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

107 share['host']) 

108 raise exception.InvalidHost(reason=message) 

109 

110 # Validate share server 

111 self._share_server_validation(share_server) 

112 

113 if share_proto == 'CIFS': 

114 vdm_name = self._get_share_server_name(share_server) 

115 server_name = vdm_name 

116 

117 # Check if CIFS server exists. 

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

119 vdm_name) 

120 if status != constants.STATUS_OK: 

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

122 LOG.error(message) 

123 raise exception.EMCVnxXMLAPIError(err=message) 

124 

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

126 

127 if share_proto == 'NFS': 

128 location = self._create_nfs_share(share_name, share_server) 

129 elif share_proto == 'CIFS': 129 ↛ 132line 129 didn't jump to line 132 because the condition on line 129 was always true

130 location = self._create_cifs_share(share_name, share_server) 

131 

132 return [ 

133 {'path': location} 

134 ] 

135 

136 def _share_server_validation(self, share_server): 

137 """Validate the share server.""" 

138 if not share_server: 

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

140 raise exception.InvalidInput(reason=msg) 

141 

142 backend_details = share_server.get('backend_details') 

143 vdm = backend_details.get( 

144 'share_server_name') if backend_details else None 

145 

146 if vdm is None: 

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

148 LOG.error(message) 

149 raise exception.EMCVnxXMLAPIError(err=message) 

150 

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

152 """Allocate file system for share.""" 

153 vdm_name = self._get_share_server_name(share_server) 

154 

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

156 share_name, size, pool_name, vdm_name) 

157 

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

159 pool_name): 

160 """Allocate file system from snapshot.""" 

161 vdm_name = self._get_share_server_name(share_server) 

162 

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

164 self.mover_name, self.mover_name) 

165 

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

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

168 pool_name, vdm_name, interconn_id) 

169 

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

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

172 nwe_size) 

173 

174 @enas_utils.log_enter_exit 

175 def _create_cifs_share(self, share_name, share_server): 

176 """Create CIFS share.""" 

177 vdm_name = self._get_share_server_name(share_server) 

178 server_name = vdm_name 

179 

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

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

182 vdm_name) 

183 

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

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

186 "so the share is inaccessible.") 

187 % server['compName']) 

188 LOG.error(message) 

189 raise exception.EMCVnxXMLAPIError(err=message) 

190 

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

192 

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

194 vdm_name) 

195 

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

197 vdm_name) 

198 

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

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

201 

202 return location 

203 

204 @enas_utils.log_enter_exit 

205 def _create_nfs_share(self, share_name, share_server): 

206 """Create NFS share.""" 

207 vdm_name = self._get_share_server_name(share_server) 

208 

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

210 

211 nfs_if = enas_utils.convert_ipv6_format_if_needed( 

212 share_server['backend_details']['nfs_if']) 

213 

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

215 % {'nfs_if': nfs_if, 

216 'share_name': share_name}) 

217 

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

219 share_server=None, parent_share=None): 

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

221 share_name = share['id'] 

222 

223 share_proto = share['share_proto'] 

224 

225 # Validate the share protocol 

226 if share_proto.upper() not in ('NFS', 'CIFS'): 

227 raise exception.InvalidShare( 

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

229 % share_proto)) 

230 

231 # Get the pool name from share host field 

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

233 if not pool_name: 

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

235 share['host']) 

236 raise exception.InvalidHost(reason=message) 

237 

238 self._share_server_validation(share_server) 

239 

240 self._allocate_container_from_snapshot( 

241 share, snapshot, share_server, pool_name) 

242 

243 nfs_if = enas_utils.convert_ipv6_format_if_needed( 

244 share_server['backend_details']['nfs_if']) 

245 

246 if share_proto == 'NFS': 

247 self._create_nfs_share(share_name, share_server) 

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

249 % {'nfs_if': nfs_if, 

250 'share_name': share_name}) 

251 elif share_proto == 'CIFS': 251 ↛ 254line 251 didn't jump to line 254 because the condition on line 251 was always true

252 location = self._create_cifs_share(share_name, share_server) 

253 

254 return [ 

255 {'path': location} 

256 ] 

257 

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

259 """Create snapshot from share.""" 

260 share_name = snapshot['share_id'] 

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

262 if status != constants.STATUS_OK: 

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

264 LOG.error(message) 

265 raise exception.EMCVnxXMLAPIError(err=message) 

266 

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

268 

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

270 snapshot['share_id'], 

271 pool_id) 

272 

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

274 """Delete a share.""" 

275 if share_server is None: 

276 LOG.warning("Driver does not support share deletion without " 

277 "share network specified. Return directly because " 

278 "there is nothing to clean.") 

279 return 

280 

281 share_proto = share['share_proto'] 

282 

283 if share_proto == 'NFS': 

284 self._delete_nfs_share(share, share_server) 

285 elif share_proto == 'CIFS': 

286 self._delete_cifs_share(share, share_server) 

287 else: 

288 raise exception.InvalidShare( 

289 reason='Unsupported share type') 

290 

291 @enas_utils.log_enter_exit 

292 def _delete_cifs_share(self, share, share_server): 

293 """Delete CIFS share.""" 

294 vdm_name = self._get_share_server_name(share_server) 

295 

296 name = share['id'] 

297 

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

299 

300 self._deallocate_container(name, vdm_name) 

301 

302 @enas_utils.log_enter_exit 

303 def _delete_nfs_share(self, share, share_server): 

304 """Delete NFS share.""" 

305 vdm_name = self._get_share_server_name(share_server) 

306 

307 name = share['id'] 

308 

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

310 

311 self._deallocate_container(name, vdm_name) 

312 

313 @enas_utils.log_enter_exit 

314 def _deallocate_container(self, share_name, vdm_name): 

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

316 path = '/' + share_name 

317 

318 try: 

319 # Delete mount point 

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

321 except Exception: 

322 LOG.debug("Skip the failure of mount point %s deletion.", path) 

323 

324 try: 

325 # Delete file system 

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

327 except Exception: 

328 LOG.debug("Skip the failure of file system %s deletion.", 

329 share_name) 

330 

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

332 """Delete a snapshot.""" 

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

334 

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

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

337 

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

339 # Get the pool name from share host field 

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

341 if not pool_name: 

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

343 share['host']) 

344 raise exception.InvalidHost(reason=message) 

345 

346 share_name = share['id'] 

347 

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

349 share_name, pool_name, new_size * units.Ki) 

350 

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

352 """Allow access to a share.""" 

353 access_level = access['access_level'] 

354 if access_level not in const.ACCESS_LEVELS: 

355 raise exception.InvalidShareAccessLevel(level=access_level) 

356 

357 share_proto = share['share_proto'] 

358 

359 if share_proto == 'NFS': 

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

361 elif share_proto == 'CIFS': 

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

363 else: 

364 raise exception.InvalidShare( 

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

366 % share_proto)) 

367 

368 @enas_utils.log_enter_exit 

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

370 """Allow access to CIFS share.""" 

371 vdm_name = self._get_share_server_name(share_server) 

372 share_name = share['id'] 

373 

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

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

376 raise exception.InvalidShareAccess(reason=reason) 

377 

378 user_name = access['access_to'] 

379 

380 access_level = access['access_level'] 

381 if access_level == const.ACCESS_LEVEL_RW: 

382 cifs_access = constants.CIFS_ACL_FULLCONTROL 

383 else: 

384 cifs_access = constants.CIFS_ACL_READ 

385 

386 # Check if CIFS server exists. 

387 server_name = vdm_name 

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

389 vdm_name) 

390 if status != constants.STATUS_OK: 

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

392 LOG.error(message) 

393 raise exception.EMCVnxXMLAPIError(err=message) 

394 

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

396 vdm_name, 

397 share_name, 

398 user_name, 

399 server['domain'], 

400 access=cifs_access) 

401 

402 @enas_utils.log_enter_exit 

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

404 """Allow access to NFS share.""" 

405 vdm_name = self._get_share_server_name(share_server) 

406 

407 access_type = access['access_type'] 

408 if access_type != 'ip': 

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

410 raise exception.InvalidShareAccess(reason=reason) 

411 

412 host_ip = access['access_to'] 

413 access_level = access['access_level'] 

414 

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

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

417 

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

419 delete_rules, share_server=None): 

420 # deleting rules 

421 for rule in delete_rules: 

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

423 

424 # adding rules 

425 for rule in add_rules: 

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

427 

428 # recovery mode 

429 if not (add_rules or delete_rules): 

430 white_list = [] 

431 for rule in access_rules: 

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

433 white_list.append( 

434 enas_utils.convert_ipv6_format_if_needed( 

435 rule['access_to'])) 

436 self.clear_access(share, share_server, white_list) 

437 

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

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

440 share_name = share['id'] 

441 if share_proto == 'CIFS': 

442 self._cifs_clear_access(share_name, share_server, white_list) 

443 elif share_proto == 'NFS': 443 ↛ exitline 443 didn't return from function 'clear_access' because the condition on line 443 was always true

444 self._nfs_clear_access(share_name, share_server, white_list) 

445 

446 @enas_utils.log_enter_exit 

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

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

449 vdm_name = self._get_share_server_name(share_server) 

450 

451 # Check if CIFS server exists. 

452 server_name = vdm_name 

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

454 vdm_name) 

455 if status != constants.STATUS_OK: 

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

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

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

459 raise exception.EMCVnxXMLAPIError(err=message) 

460 

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

462 share_name=share_name, 

463 mover_name=vdm_name, 

464 domain=server['domain'], 

465 white_list_users=white_list) 

466 

467 @enas_utils.log_enter_exit 

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

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

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

471 share_name=share_name, 

472 mover_name=self._get_share_server_name(share_server), 

473 white_list_hosts=white_list) 

474 

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

476 """Deny access to a share.""" 

477 share_proto = share['share_proto'] 

478 

479 if share_proto == 'NFS': 

480 self._nfs_deny_access(share, access, share_server) 

481 elif share_proto == 'CIFS': 

482 self._cifs_deny_access(share, access, share_server) 

483 else: 

484 raise exception.InvalidShare( 

485 reason=_('Unsupported share type')) 

486 

487 @enas_utils.log_enter_exit 

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

489 """Deny access to CIFS share.""" 

490 vdm_name = self._get_share_server_name(share_server) 

491 share_name = share['id'] 

492 

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

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

495 raise exception.InvalidShareAccess(reason=reason) 

496 

497 user_name = access['access_to'] 

498 

499 access_level = access['access_level'] 

500 if access_level == const.ACCESS_LEVEL_RW: 

501 cifs_access = constants.CIFS_ACL_FULLCONTROL 

502 else: 

503 cifs_access = constants.CIFS_ACL_READ 

504 

505 # Check if CIFS server exists. 

506 server_name = vdm_name 

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

508 vdm_name) 

509 if status != constants.STATUS_OK: 

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

511 LOG.error(message) 

512 raise exception.EMCVnxXMLAPIError(err=message) 

513 

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

515 vdm_name, 

516 share_name, 

517 user_name, 

518 server['domain'], 

519 access=cifs_access) 

520 

521 @enas_utils.log_enter_exit 

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

523 """Deny access to NFS share.""" 

524 vdm_name = self._get_share_server_name(share_server) 

525 

526 access_type = access['access_type'] 

527 if access_type != 'ip': 

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

529 raise exception.InvalidShareAccess(reason=reason) 

530 

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

532 

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

534 vdm_name) 

535 

536 def check_for_setup_error(self): 

537 """Check for setup error.""" 

538 # To verify the input from Manila configuration 

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

540 True) 

541 if constants.STATUS_ERROR == status: 

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

543 self.mover_name) 

544 LOG.error(message) 

545 raise exception.InvalidParameterValue(err=message) 

546 

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

548 

549 def _get_managed_storage_pools(self, pools): 

550 matched_pools = set() 

551 if pools: 

552 # Get the real pools from the backend storage 

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

554 if status != constants.STATUS_OK: 

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

556 "Reason: %s") % backend_pools) 

557 LOG.error(message) 

558 raise exception.EMCVnxXMLAPIError(err=message) 

559 

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

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

562 matched_pools, unmatched_pools = enas_utils.do_match_any( 

563 real_pools, conf_pools) 

564 

565 if not matched_pools: 

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

567 "exist. Please check your configuration " 

568 "vnx_share_data_pools in manila.conf. " 

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

570 ",".join(real_pools)) 

571 raise exception.InvalidParameterValue(err=msg) 

572 

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

574 ",".join(matched_pools)) 

575 else: 

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

577 "in storage system will be managed.") 

578 return matched_pools 

579 

580 def connect(self, emc_share_driver, context): 

581 """Connect to VNX NAS server.""" 

582 config = emc_share_driver.configuration 

583 config.append_config_values(VNX_OPTS) 

584 self.mover_name = config.vnx_server_container 

585 

586 self.pool_conf = config.safe_get('vnx_share_data_pools') 

587 

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

589 if self.reserved_percentage is None: 589 ↛ 590line 589 didn't jump to line 590 because the condition on line 589 was never true

590 self.reserved_percentage = 0 

591 

592 self.reserved_snapshot_percentage = config.safe_get( 

593 'reserved_share_from_snapshot_percentage') 

594 if self.reserved_snapshot_percentage is None: 594 ↛ 595line 594 didn't jump to line 595 because the condition on line 594 was never true

595 self.reserved_snapshot_percentage = self.reserved_percentage 

596 

597 self.reserved_share_extend_percentage = config.safe_get( 

598 'reserved_share_extend_percentage') 

599 if self.reserved_share_extend_percentage is None: 599 ↛ 600line 599 didn't jump to line 600 because the condition on line 599 was never true

600 self.reserved_share_extend_percentage = self.reserved_percentage 

601 

602 self.manager = manager.StorageObjectManager(config) 

603 self.port_conf = config.safe_get('vnx_ethernet_ports') 

604 

605 def get_managed_ports(self): 

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

607 real_ports = self._get_physical_devices(self.mover_name) 

608 

609 if not self.port_conf: 

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

611 "Data Mover can be used.") 

612 return real_ports 

613 

614 matched_ports, unmanaged_ports = enas_utils.do_match_any( 

615 real_ports, self.port_conf) 

616 

617 if not matched_ports: 

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

619 "Please check your configuration vnx_ethernet_ports " 

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

621 "are %s.") % 

622 ",".join(real_ports)) 

623 raise exception.BadConfigurationException(reason=msg) 

624 

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

626 

627 return list(matched_ports) 

628 

629 def update_share_stats(self, stats_dict): 

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

631 stats_dict['driver_version'] = VERSION 

632 

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

634 

635 stats_dict['pools'] = [] 

636 

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

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

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

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

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

642 

643 pool_stat = dict( 

644 pool_name=pool['name'], 

645 total_capacity_gb=enas_utils.mb_to_gb(total_size), 

646 free_capacity_gb=enas_utils.mb_to_gb( 

647 total_size - used_size), 

648 qos=False, 

649 reserved_percentage=self.reserved_percentage, 

650 reserved_snapshot_percentage=( 

651 self.reserved_snapshot_percentage), 

652 reserved_share_extend_percentage=( 

653 self.reserved_share_extend_percentage), 

654 ) 

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

656 

657 if not stats_dict['pools']: 

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

659 LOG.error(message) 

660 raise exception.EMCVnxXMLAPIError(err=message) 

661 

662 def get_pool(self, share): 

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

664 share_name = share['id'] 

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

666 if status != constants.STATUS_OK: 

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

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

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

670 LOG.error(message) 

671 raise exception.EMCVnxXMLAPIError(err=message) 

672 

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

674 

675 # Get the real pools from the backend storage 

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

677 if status != constants.STATUS_OK: 

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

679 "Reason: %s") % backend_pools) 

680 LOG.error(message) 

681 raise exception.EMCVnxXMLAPIError(err=message) 

682 

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

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

685 return name 

686 

687 available_pools = [item for item in backend_pools] 

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

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

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

691 raise exception.EMCVnxXMLAPIError(err=message) 

692 

693 def get_network_allocations_number(self): 

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

695 return constants.IP_ALLOCATIONS 

696 

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

698 """Set up and configures share server with given network parameters.""" 

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

700 vdm_name = network_info['server_id'] 

701 vlan_id = network_info['segmentation_id'] 

702 active_directory = None 

703 allocated_interfaces = [] 

704 

705 if network_info.get('security_services'): 705 ↛ 712line 705 didn't jump to line 712 because the condition on line 705 was always true

706 is_valid, active_directory = self._get_valid_security_service( 

707 network_info['security_services']) 

708 

709 if not is_valid: 

710 raise exception.EMCVnxXMLAPIError(err=active_directory) 

711 

712 try: 

713 if not self._vdm_exist(vdm_name): 

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

715 'share server...', vdm_name) 

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

717 

718 devices = self.get_managed_ports() 

719 

720 for net_info in network_info['network_allocations']: 

721 random.shuffle(devices) 

722 

723 ip_version = net_info['ip_version'] 

724 

725 interface = { 

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

727 'device_name': devices[0], 

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

729 'mover_name': self.mover_name, 

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

731 } 

732 

733 if ip_version == 6: 

734 interface['ip_version'] = ip_version 

735 interface['net_mask'] = str( 

736 utils.cidr_to_prefixlen(network_info['cidr'])) 

737 else: 

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

739 network_info['cidr']) 

740 

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

742 

743 allocated_interfaces.append(interface) 

744 

745 cifs_interface = allocated_interfaces[0] 

746 nfs_interface = allocated_interfaces[1] 

747 if active_directory: 747 ↛ 751line 747 didn't jump to line 751 because the condition on line 747 was always true

748 self._configure_active_directory( 

749 active_directory, vdm_name, cifs_interface) 

750 

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

752 vdm_name, nfs_interface['name']) 

753 

754 return { 

755 'share_server_name': vdm_name, 

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

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

758 } 

759 

760 except Exception: 

761 with excutils.save_and_reraise_exception(): 

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

763 server_details = self._construct_backend_details( 

764 vdm_name, allocated_interfaces) 

765 self.teardown_server( 

766 server_details, network_info['security_services']) 

767 

768 def _construct_backend_details(self, vdm_name, interfaces): 

769 if_number = len(interfaces) 

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

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

772 

773 return { 

774 'share_server_name': vdm_name, 

775 'cifs_if': cifs_if, 

776 'nfs_if': nfs_if, 

777 } 

778 

779 @enas_utils.log_enter_exit 

780 def _vdm_exist(self, name): 

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

782 if constants.STATUS_OK != status: 

783 return False 

784 

785 return True 

786 

787 def _get_physical_devices(self, mover_name): 

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

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

790 if not devices: 

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

792 self.mover_name) 

793 LOG.error(message) 

794 raise exception.EMCVnxXMLAPIError(err=message) 

795 

796 return devices 

797 

798 def _configure_active_directory( 

799 self, security_service, vdm_name, interface): 

800 

801 domain = security_service['domain'] 

802 server = security_service['dns_ip'] 

803 

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

805 

806 cifs_server_args = { 

807 'name': vdm_name, 

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

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

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

811 'password': security_service['password'], 

812 'mover_name': vdm_name, 

813 'is_vdm': True, 

814 } 

815 

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

817 

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

819 """Teardown share server.""" 

820 if not server_details: 

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

822 return 

823 

824 vdm_name = server_details.get('share_server_name') 

825 if not vdm_name: 

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

827 return 

828 

829 cifs_if = server_details.get('cifs_if') 

830 nfs_if = server_details.get('nfs_if') 

831 

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

833 if constants.STATUS_OK != status: 

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

835 return 

836 

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

838 

839 for if_name in interfaces['nfs']: 

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

841 

842 if security_services: 

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

844 is_valid, active_directory = self._get_valid_security_service( 

845 security_services) 

846 if is_valid: 846 ↛ 876line 846 didn't jump to line 876 because the condition on line 846 was always true

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

848 vdm_name) 

849 if constants.STATUS_OK != status: 

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

851 vdm_name) 

852 else: 

853 cifs_servers = copy.deepcopy(servers) 

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

855 # Unjoin CIFS Server from domain 

856 cifs_server_args = { 

857 'name': server['name'], 

858 'join_domain': False, 

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

860 'password': active_directory['password'], 

861 'mover_name': vdm_name, 

862 'is_vdm': True, 

863 } 

864 

865 try: 

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

867 cifs_server_args) 

868 except exception.EMCVnxXMLAPIError as expt: 

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

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

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

872 

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

874 

875 # Delete interface from Data Mover 

876 if cifs_if: 

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

878 self.mover_name) 

879 

880 if nfs_if: 

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

882 self.mover_name) 

883 

884 # Delete Virtual Data Mover 

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

886 

887 def _get_valid_security_service(self, security_services): 

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

889 

890 :param security_services: 

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

892 security_services includes zero or single security service for 

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

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

895 return zero or single security service for active directory. 

896 """ 

897 

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

899 service_number = len(security_services) 

900 

901 if (service_number > 1 or 

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

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

904 "Only support single security service and " 

905 "only support type 'active_directory'") 

906 

907 return True, security_services[0] 

908 

909 def _get_share_server_name(self, share_server): 

910 try: 

911 return share_server['backend_details']['share_server_name'] 

912 except Exception: 

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

914 share_server) 

915 return share_server['id'] 

916 

917 def _get_context(self, type): 

918 return self.manager.getStorageContext(type)