Coverage for manila/share/drivers/hitachi/hnas/driver.py: 99%

456 statements  

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

1# Copyright (c) 2015 Hitachi Data Systems, Inc. 

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 

16import os 

17 

18from oslo_config import cfg 

19from oslo_log import log 

20from oslo_utils import excutils 

21from oslo_utils import importutils 

22 

23from manila.common import constants 

24from manila import exception 

25from manila.i18n import _ 

26from manila.share import driver 

27from manila.share import utils 

28 

29LOG = log.getLogger(__name__) 

30 

31hitachi_hnas_opts = [ 

32 cfg.HostAddressOpt('hitachi_hnas_ip', 

33 help="HNAS management interface IP for communication " 

34 "between Manila controller and HNAS."), 

35 cfg.StrOpt('hitachi_hnas_user', 

36 help="HNAS username Base64 String in order to perform tasks " 

37 "such as create file-systems and network interfaces."), 

38 cfg.StrOpt('hitachi_hnas_password', 

39 secret=True, 

40 help="HNAS user password. Required only if private key is not " 

41 "provided."), 

42 cfg.IntOpt('hitachi_hnas_evs_id', 

43 help="Specify which EVS this backend is assigned to."), 

44 cfg.HostAddressOpt('hitachi_hnas_evs_ip', 

45 help="Specify IP for mounting shares."), 

46 cfg.HostAddressOpt('hitachi_hnas_admin_network_ip', 

47 help="Specify IP for mounting shares in the Admin " 

48 "network."), 

49 cfg.StrOpt('hitachi_hnas_file_system_name', 

50 help="Specify file-system name for creating shares."), 

51 cfg.StrOpt('hitachi_hnas_ssh_private_key', 

52 secret=True, 

53 help="RSA/DSA private key value used to connect into HNAS. " 

54 "Required only if password is not provided."), 

55 cfg.HostAddressOpt('hitachi_hnas_cluster_admin_ip0', 

56 help="The IP of the clusters admin node. Only set in " 

57 "HNAS multinode clusters."), 

58 cfg.IntOpt('hitachi_hnas_stalled_job_timeout', 

59 default=30, 

60 help="The time (in seconds) to wait for stalled HNAS jobs " 

61 "before aborting."), 

62 cfg.StrOpt('hitachi_hnas_driver_helper', 

63 default='manila.share.drivers.hitachi.hnas.ssh.HNASSSHBackend', 

64 help="Python class to be used for driver helper."), 

65 cfg.BoolOpt('hitachi_hnas_allow_cifs_snapshot_while_mounted', 

66 default=False, 

67 help="By default, CIFS snapshots are not allowed to be taken " 

68 "when the share has clients connected because consistent " 

69 "point-in-time replica cannot be guaranteed for all " 

70 "files. Enabling this might cause inconsistent snapshots " 

71 "on CIFS shares."), 

72] 

73 

74CONF = cfg.CONF 

75CONF.register_opts(hitachi_hnas_opts) 

76 

77 

78class HitachiHNASDriver(driver.ShareDriver): 

79 """Manila HNAS Driver implementation. 

80 

81 Driver versions:: 

82 

83 1.0.0 - Initial Version. 

84 2.0.0 - Refactoring, bugfixes, implemented Share Shrink and 

85 Update Access. 

86 3.0.0 - New driver location, implemented support for CIFS protocol. 

87 3.1.0 - Added admin network export location support. 

88 4.0.0 - Added mountable snapshots, revert-to-snapshot and 

89 manage snapshots features support. 

90 """ 

91 

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

93 """Do initialization.""" 

94 

95 LOG.debug("Invoking base constructor for Manila Hitachi HNAS Driver.") 

96 super(HitachiHNASDriver, self).__init__(False, *args, **kwargs) 

97 

98 LOG.debug("Setting up attributes for Manila Hitachi HNAS Driver.") 

99 self.configuration.append_config_values(hitachi_hnas_opts) 

100 

101 LOG.debug("Reading config parameters for Manila Hitachi HNAS Driver.") 

102 self.backend_name = self.configuration.safe_get('share_backend_name') 

103 hnas_helper = self.configuration.safe_get('hitachi_hnas_driver_helper') 

104 hnas_ip = self.configuration.safe_get('hitachi_hnas_ip') 

105 hnas_username = self.configuration.safe_get('hitachi_hnas_user') 

106 hnas_password = self.configuration.safe_get('hitachi_hnas_password') 

107 hnas_evs_id = self.configuration.safe_get('hitachi_hnas_evs_id') 

108 self.hnas_evs_ip = self.configuration.safe_get('hitachi_hnas_evs_ip') 

109 self.hnas_admin_network_ip = self.configuration.safe_get( 

110 'hitachi_hnas_admin_network_ip') 

111 self.fs_name = self.configuration.safe_get( 

112 'hitachi_hnas_file_system_name') 

113 self.cifs_snapshot = self.configuration.safe_get( 

114 'hitachi_hnas_allow_cifs_snapshot_while_mounted') 

115 ssh_private_key = self.configuration.safe_get( 

116 'hitachi_hnas_ssh_private_key') 

117 cluster_admin_ip0 = self.configuration.safe_get( 

118 'hitachi_hnas_cluster_admin_ip0') 

119 self.private_storage = kwargs.get('private_storage') 

120 job_timeout = self.configuration.safe_get( 

121 'hitachi_hnas_stalled_job_timeout') 

122 

123 if hnas_helper is None: 

124 msg = _("The config parameter hitachi_hnas_driver_helper is not " 

125 "set.") 

126 raise exception.InvalidParameterValue(err=msg) 

127 

128 if hnas_evs_id is None: 

129 msg = _("The config parameter hitachi_hnas_evs_id is not set.") 

130 raise exception.InvalidParameterValue(err=msg) 

131 

132 if self.hnas_evs_ip is None: 

133 msg = _("The config parameter hitachi_hnas_evs_ip is not set.") 

134 raise exception.InvalidParameterValue(err=msg) 

135 

136 if hnas_ip is None: 

137 msg = _("The config parameter hitachi_hnas_ip is not set.") 

138 raise exception.InvalidParameterValue(err=msg) 

139 

140 if hnas_username is None: 

141 msg = _("The config parameter hitachi_hnas_user is not set.") 

142 raise exception.InvalidParameterValue(err=msg) 

143 

144 if hnas_password is None and ssh_private_key is None: 

145 msg = _("Credentials configuration parameters missing: " 

146 "you need to set hitachi_hnas_password or " 

147 "hitachi_hnas_ssh_private_key.") 

148 raise exception.InvalidParameterValue(err=msg) 

149 

150 LOG.debug("Initializing HNAS Layer.") 

151 

152 helper = importutils.import_class(hnas_helper) 

153 

154 self.hnas = helper(hnas_ip, hnas_username, hnas_password, 

155 ssh_private_key, cluster_admin_ip0, 

156 hnas_evs_id, self.hnas_evs_ip, self.fs_name, 

157 job_timeout) 

158 

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

160 delete_rules, update_rules, share_server=None): 

161 """Update access rules for given share. 

162 

163 :param context: The `context.RequestContext` object for the request 

164 :param share: Share that will have its access rules updated. 

165 :param access_rules: All access rules for given share. 

166 :param add_rules: Empty List or List of access rules which should be 

167 added. access_rules already contains these rules. 

168 :param delete_rules: Empty List or List of access rules which should be 

169 removed. access_rules doesn't contain these rules. 

170 :param update_rules: Empty List or List of access rules which should be 

171 updated. access_rules already contains these rules. 

172 :param share_server: Data structure with share server information. 

173 Not used by this driver. 

174 """ 

175 

176 hnas_share_id = self._get_hnas_share_id(share['id']) 

177 

178 try: 

179 self._ensure_share(share, hnas_share_id) 

180 except exception.HNASItemNotFoundException: 

181 raise exception.ShareResourceNotFound(share_id=share['id']) 

182 

183 self._check_protocol(share['id'], share['share_proto']) 

184 

185 if share['share_proto'].lower() == 'nfs': 

186 self._nfs_update_access(share, hnas_share_id, access_rules) 

187 else: 

188 if not (add_rules or delete_rules): 

189 # recovery mode 

190 self._clean_cifs_access_list(hnas_share_id) 

191 self._cifs_allow_access(share, hnas_share_id, access_rules) 

192 else: 

193 self._cifs_deny_access(share, hnas_share_id, delete_rules) 

194 self._cifs_allow_access(share, hnas_share_id, add_rules) 

195 

196 def _nfs_update_access(self, share, hnas_share_id, access_rules): 

197 host_list = [] 

198 

199 for rule in access_rules: 

200 if rule['access_type'].lower() != 'ip': 

201 msg = _("Only IP access type currently supported for NFS. " 

202 "Share provided %(share)s with rule type " 

203 "%(type)s.") % {'share': share['id'], 

204 'type': rule['access_type']} 

205 raise exception.InvalidShareAccess(reason=msg) 

206 

207 if rule['access_level'] == constants.ACCESS_LEVEL_RW: 

208 host_list.append(rule['access_to'] + '(' + 

209 rule['access_level'] + 

210 ',norootsquash)') 

211 else: 

212 host_list.append(rule['access_to'] + '(' + 

213 rule['access_level'] + ')') 

214 

215 self.hnas.update_nfs_access_rule(host_list, share_id=hnas_share_id) 

216 

217 if host_list: 

218 LOG.debug("Share %(share)s has the rules: %(rules)s", 

219 {'share': share['id'], 'rules': ', '.join(host_list)}) 

220 else: 

221 LOG.debug("Share %(share)s has no rules.", {'share': share['id']}) 

222 

223 def _cifs_allow_access(self, share_or_snapshot, hnas_id, add_rules, 

224 is_snapshot=False): 

225 

226 entity_type = "share" 

227 if is_snapshot: 

228 entity_type = "snapshot" 

229 

230 for rule in add_rules: 

231 if rule['access_type'].lower() != 'user': 

232 msg = _("Only USER access type currently supported for CIFS. " 

233 "%(entity_type)s provided %(share)s with " 

234 "rule %(r_id)s type %(type)s allowing permission " 

235 "to %(to)s.") % { 

236 'entity_type': entity_type.capitalize(), 

237 'share': share_or_snapshot['id'], 

238 'type': rule['access_type'], 

239 'r_id': rule['id'], 

240 'to': rule['access_to'], 

241 } 

242 raise exception.InvalidShareAccess(reason=msg) 

243 

244 if rule['access_level'] == constants.ACCESS_LEVEL_RW: 

245 # Adding permission acr = Allow Change&Read 

246 permission = 'acr' 

247 else: 

248 # Adding permission ar = Allow Read 

249 permission = 'ar' 

250 

251 formatted_user = rule['access_to'].replace('\\', '\\\\') 

252 

253 self.hnas.cifs_allow_access(hnas_id, formatted_user, 

254 permission, is_snapshot=is_snapshot) 

255 

256 LOG.debug("Added %(rule)s rule for user/group %(user)s " 

257 "to %(entity_type)s %(share)s.", 

258 {'rule': rule['access_level'], 

259 'user': rule['access_to'], 

260 'entity_type': entity_type, 

261 'share': share_or_snapshot['id']}) 

262 

263 def _cifs_deny_access(self, share_or_snapshot, hnas_id, delete_rules, 

264 is_snapshot=False): 

265 if is_snapshot: 

266 entity_type = "snapshot" 

267 share_proto = share_or_snapshot['share']['share_proto'] 

268 else: 

269 entity_type = "share" 

270 share_proto = share_or_snapshot['share_proto'] 

271 

272 for rule in delete_rules: 

273 if rule['access_type'].lower() != 'user': 

274 LOG.warning('Only USER access type is allowed for ' 

275 'CIFS. %(entity_type)s ' 

276 'provided %(share)s with ' 

277 'protocol %(proto)s.', 

278 {'entity_type': entity_type.capitalize(), 

279 'share': share_or_snapshot['id'], 

280 'proto': share_proto}) 

281 continue 

282 

283 formatted_user = rule['access_to'].replace('\\', '\\\\') 

284 

285 self.hnas.cifs_deny_access(hnas_id, formatted_user, 

286 is_snapshot=is_snapshot) 

287 

288 LOG.debug("Access denied for user/group %(user)s " 

289 "to %(entity_type)s %(share)s.", 

290 {'user': rule['access_to'], 

291 'entity_type': entity_type, 

292 'share': share_or_snapshot['id']}) 

293 

294 def _clean_cifs_access_list(self, hnas_id, is_snapshot=False): 

295 permission_list = self.hnas.list_cifs_permissions(hnas_id) 

296 

297 for permission in permission_list: 

298 formatted_user = r'"\{1}{0}\{1}"'.format(permission[0], '"') 

299 self.hnas.cifs_deny_access(hnas_id, formatted_user, 

300 is_snapshot=is_snapshot) 

301 

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

303 r"""Creates share. 

304 

305 :param context: The `context.RequestContext` object for the request 

306 :param share: Share that will be created. 

307 :param share_server: Data structure with share server information. 

308 Not used by this driver. 

309 :returns: Returns a list of dicts containing the EVS IP concatenated 

310 with the path of share in the filesystem. 

311 

312 Example for NFS:: 

313 

314 [ 

315 

316 { 

317 

318 'path': '172.24.44.10:/shares/id', 

319 'metadata': {}, 

320 'is_admin_only': False 

321 

322 }, 

323 

324 { 

325 

326 'path': '192.168.0.10:/shares/id', 

327 'metadata': {}, 

328 'is_admin_only': True 

329 

330 } 

331 

332 ] 

333 

334 Example for CIFS:: 

335 

336 [ 

337 

338 { 

339 

340 'path': '\\172.24.44.10\id', 

341 'metadata': {}, 

342 'is_admin_only': False 

343 

344 }, 

345 

346 { 

347 

348 'path': '\\192.168.0.10\id', 

349 'metadata': {}, 

350 'is_admin_only': True 

351 

352 } 

353 

354 ] 

355 

356 """ 

357 LOG.debug("Creating share in HNAS: %(shr)s.", {'shr': share['id']}) 

358 

359 self._check_protocol(share['id'], share['share_proto']) 

360 

361 export_list = self._create_share(share['id'], share['size'], 

362 share['share_proto']) 

363 

364 LOG.debug("Share %(share)s created successfully on path(s): " 

365 "%(paths)s.", 

366 {'paths': ', '.join([x['path'] for x in export_list]), 

367 'share': share['id']}) 

368 

369 return export_list 

370 

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

372 """Deletes share. 

373 

374 :param context: The `context.RequestContext` object for the request 

375 :param share: Share that will be deleted. 

376 :param share_server: Data structure with share server information. 

377 Not used by this driver. 

378 """ 

379 hnas_share_id = self._get_hnas_share_id(share['id']) 

380 

381 LOG.debug("Deleting share in HNAS: %(shr)s.", 

382 {'shr': share['id']}) 

383 

384 self._delete_share(hnas_share_id, share['share_proto']) 

385 

386 LOG.debug("Export and share successfully deleted: %(shr)s.", 

387 {'shr': share['id']}) 

388 

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

390 """Creates snapshot. 

391 

392 :param context: The `context.RequestContext` object for the request 

393 :param snapshot: Snapshot that will be created. 

394 :param share_server: Data structure with share server information. 

395 Not used by this driver. 

396 """ 

397 hnas_share_id = self._get_hnas_share_id(snapshot['share_id']) 

398 

399 LOG.debug("The snapshot of share %(snap_share_id)s will be created " 

400 "with id %(snap_id)s.", 

401 {'snap_share_id': snapshot['share_id'], 

402 'snap_id': snapshot['id']}) 

403 

404 export_locations = self._create_snapshot(hnas_share_id, snapshot) 

405 LOG.info("Snapshot %(id)s successfully created.", 

406 {'id': snapshot['id']}) 

407 

408 output = { 

409 'provider_location': os.path.join( 

410 '/snapshots', hnas_share_id, snapshot['id']) 

411 } 

412 

413 if export_locations: 

414 output['export_locations'] = export_locations 

415 

416 return output 

417 

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

419 """Deletes snapshot. 

420 

421 :param context: The `context.RequestContext` object for the request 

422 :param snapshot: Snapshot that will be deleted. 

423 :param share_server: Data structure with share server information. 

424 Not used by this driver. 

425 """ 

426 hnas_share_id = self._get_hnas_share_id(snapshot['share_id']) 

427 hnas_snapshot_id = self._get_hnas_snapshot_id(snapshot) 

428 

429 LOG.debug("The snapshot %(snap_id)s will be deleted. The related " 

430 "share ID is %(snap_share_id)s.", 

431 {'snap_id': snapshot['id'], 

432 'snap_share_id': snapshot['share_id']}) 

433 

434 self._delete_snapshot(snapshot['share'], 

435 hnas_share_id, hnas_snapshot_id) 

436 

437 LOG.info("Snapshot %(id)s successfully deleted.", 

438 {'id': snapshot['id']}) 

439 

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

441 share_server=None, parent_share=None): 

442 r"""Creates a new share from snapshot. 

443 

444 :param context: The `context.RequestContext` object for the request 

445 :param share: Information about the new share. 

446 :param snapshot: Information about the snapshot that will be copied 

447 to new share. 

448 :param share_server: Data structure with share server information. 

449 Not used by this driver. 

450 :returns: Returns a list of dicts containing the EVS IP concatenated 

451 with the path of share in the filesystem. 

452 

453 Example for NFS:: 

454 

455 [ 

456 

457 { 

458 

459 'path': '172.24.44.10:/shares/id', 

460 'metadata': {}, 

461 'is_admin_only': False 

462 

463 }, 

464 

465 { 

466 

467 'path': '192.168.0.10:/shares/id', 

468 'metadata': {}, 

469 'is_admin_only': True 

470 

471 } 

472 

473 ] 

474 

475 Example for CIFS:: 

476 

477 [ 

478 

479 { 

480 

481 'path': '\\172.24.44.10\id', 

482 'metadata': {}, 

483 'is_admin_only': False 

484 

485 }, 

486 

487 { 

488 

489 'path': '\\192.168.0.10\id', 

490 'metadata': {}, 

491 'is_admin_only': True 

492 

493 } 

494 

495 ] 

496 

497 """ 

498 LOG.debug("Creating a new share from snapshot: %(ss_id)s.", 

499 {'ss_id': snapshot['id']}) 

500 

501 hnas_src_share_id = self._get_hnas_share_id(snapshot['share_id']) 

502 hnas_src_snap_id = self._get_hnas_snapshot_id(snapshot) 

503 

504 export_list = self._create_share_from_snapshot( 

505 share, hnas_src_share_id, hnas_src_snap_id) 

506 

507 LOG.debug("Share %(share)s created successfully on path(s): " 

508 "%(paths)s.", 

509 {'paths': ', '.join([x['path'] for x in export_list]), 

510 'share': share['id']}) 

511 return export_list 

512 

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

514 r"""Ensure that share is exported. 

515 

516 :param context: The `context.RequestContext` object for the request 

517 :param share: Share that will be checked. 

518 :param share_server: Data structure with share server information. 

519 Not used by this driver. 

520 :returns: Returns a list of dicts containing the EVS IP concatenated 

521 with the path of share in the filesystem. 

522 

523 Example for NFS:: 

524 

525 [ 

526 

527 { 

528 

529 'path': '172.24.44.10:/shares/id', 

530 'metadata': {}, 

531 'is_admin_only': False 

532 

533 }, 

534 

535 { 

536 

537 'path': '192.168.0.10:/shares/id', 

538 'metadata': {}, 

539 'is_admin_only': True 

540 

541 } 

542 

543 ] 

544 

545 Example for CIFS:: 

546 

547 [ 

548 

549 { 

550 

551 'path': '\\172.24.44.10\id', 

552 'metadata': {}, 

553 'is_admin_only': False 

554 

555 }, 

556 

557 { 

558 

559 'path': '\\192.168.0.10\id', 

560 'metadata': {}, 

561 'is_admin_only': True 

562 

563 } 

564 

565 ] 

566 

567 """ 

568 LOG.debug("Ensuring share in HNAS: %(shr)s.", {'shr': share['id']}) 

569 

570 hnas_share_id = self._get_hnas_share_id(share['id']) 

571 

572 export_list = self._ensure_share(share, hnas_share_id) 

573 

574 LOG.debug("Share ensured in HNAS: %(shr)s, protocol %(proto)s.", 

575 {'shr': share['id'], 'proto': share['share_proto']}) 

576 return export_list 

577 

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

579 """Extends a share to new size. 

580 

581 :param share: Share that will be extended. 

582 :param new_size: New size of share. 

583 :param share_server: Data structure with share server information. 

584 Not used by this driver. 

585 """ 

586 hnas_share_id = self._get_hnas_share_id(share['id']) 

587 

588 LOG.debug("Expanding share in HNAS: %(shr_id)s.", 

589 {'shr_id': share['id']}) 

590 

591 self._extend_share(hnas_share_id, share, new_size) 

592 LOG.info("Share %(shr_id)s successfully extended to " 

593 "%(shr_size)s.", 

594 {'shr_id': share['id'], 

595 'shr_size': str(new_size)}) 

596 

597 # TODO(alyson): Implement in DHSS = true mode 

598 def get_network_allocations_number(self): 

599 """Track allocations_number in DHSS = true. 

600 

601 When using the setting driver_handles_share_server = false 

602 does not require to track allocations_number because we do not handle 

603 network stuff. 

604 """ 

605 return 0 

606 

607 def _update_share_stats(self, data=None): 

608 """Updates the Capability of Backend.""" 

609 LOG.debug("Updating Backend Capability Information - Hitachi HNAS.") 

610 

611 self._check_fs_mounted() 

612 

613 total_space, free_space, dedupe = self.hnas.get_stats() 

614 

615 reserved = self.configuration.safe_get('reserved_share_percentage') 

616 reserved_snapshot = self.configuration.safe_get( 

617 'reserved_share_from_snapshot_percentage') or reserved 

618 reserved_share_extend = self.configuration.safe_get( 

619 'reserved_share_extend_percentage') or reserved 

620 

621 data = { 

622 'share_backend_name': self.backend_name, 

623 'driver_handles_share_servers': self.driver_handles_share_servers, 

624 'vendor_name': 'Hitachi', 

625 'driver_version': '4.0.0', 

626 'storage_protocol': 'NFS_CIFS', 

627 'total_capacity_gb': total_space, 

628 'free_capacity_gb': free_space, 

629 'reserved_percentage': reserved, 

630 'reserved_snapshot_percentage': reserved_snapshot, 

631 'reserved_share_extend_percentage': reserved_share_extend, 

632 'qos': False, 

633 'thin_provisioning': True, 

634 'dedupe': dedupe, 

635 'revert_to_snapshot_support': True, 

636 'mount_snapshot_support': True, 

637 } 

638 

639 LOG.info("HNAS Capabilities: %(data)s.", 

640 {'data': str(data)}) 

641 

642 super(HitachiHNASDriver, self)._update_share_stats(data) 

643 

644 def manage_existing(self, share, driver_options): 

645 r"""Manages a share that exists on backend. 

646 

647 :param share: Share that will be managed. 

648 :param driver_options: Empty dict or dict with 'volume_id' option. 

649 :returns: Returns a dict with size of the share managed and a list of 

650 dicts containing its export locations. 

651 

652 Example for NFS:: 

653 

654 { 

655 

656 'size': 10, 

657 'export_locations': [ 

658 

659 { 

660 

661 'path': '172.24.44.10:/shares/id', 

662 'metadata': {}, 

663 'is_admin_only': False 

664 

665 }, 

666 

667 { 

668 

669 'path': '192.168.0.10:/shares/id', 

670 'metadata': {}, 

671 'is_admin_only': True 

672 

673 } 

674 

675 ] 

676 

677 } 

678 

679 Example for CIFS:: 

680 

681 { 

682 

683 'size': 10, 

684 'export_locations': [ 

685 

686 { 

687 

688 'path': '\\172.24.44.10\id', 

689 'metadata': {}, 

690 'is_admin_only': False 

691 

692 }, 

693 

694 { 

695 

696 'path': '\\192.168.0.10\id', 

697 'metadata': {}, 

698 'is_admin_only': True 

699 

700 } 

701 

702 ] 

703 

704 } 

705 

706 """ 

707 hnas_share_id = self._get_hnas_share_id(share['id']) 

708 

709 # Make sure returned value is the same as provided, 

710 # confirming it does not exist. 

711 if hnas_share_id != share['id']: 

712 msg = _("Share ID %s already exists, cannot manage.") % share['id'] 

713 raise exception.HNASBackendException(msg=msg) 

714 

715 self._check_protocol(share['id'], share['share_proto']) 

716 

717 if share['share_proto'].lower() == 'nfs': 

718 # 10.0.0.1:/shares/example 

719 LOG.info("Share %(shr_path)s will be managed with ID " 

720 "%(shr_id)s.", 

721 {'shr_path': share['export_locations'][0]['path'], 

722 'shr_id': share['id']}) 

723 

724 old_path_info = share['export_locations'][0]['path'].split( 

725 ':/shares/') 

726 

727 if len(old_path_info) == 2: 

728 evs_ip = old_path_info[0] 

729 hnas_share_id = old_path_info[1] 

730 else: 

731 msg = _("Incorrect path. It should have the following format: " 

732 "IP:/shares/share_id.") 

733 raise exception.ShareBackendException(msg=msg) 

734 else: # then its CIFS 

735 # \\10.0.0.1\example 

736 old_path = share['export_locations'][0]['path'].split('\\') 

737 

738 if len(old_path) == 4: 

739 evs_ip = old_path[2] 

740 hnas_share_id = old_path[3] 

741 else: 

742 msg = _("Incorrect path. It should have the following format: " 

743 "\\\\IP\\share_id.") 

744 raise exception.ShareBackendException(msg=msg) 

745 

746 if evs_ip != self.hnas_evs_ip: 

747 msg = _("The EVS IP %(evs)s is not " 

748 "configured.") % {'evs': evs_ip} 

749 raise exception.ShareBackendException(msg=msg) 

750 

751 if self.backend_name not in share['host']: 

752 msg = _("The backend passed in the host parameter (%(shr)s) is " 

753 "not configured.") % {'shr': share['host']} 

754 raise exception.ShareBackendException(msg=msg) 

755 

756 output = self._manage_existing(share, hnas_share_id) 

757 self.private_storage.update( 

758 share['id'], {'hnas_id': hnas_share_id}) 

759 

760 LOG.debug("HNAS ID %(hnas_id)s has been saved to private storage for " 

761 "Share ID %(share_id)s", {'hnas_id': hnas_share_id, 

762 'share_id': share['id']}) 

763 

764 LOG.info("Share %(shr_path)s was successfully managed with ID " 

765 "%(shr_id)s.", 

766 {'shr_path': share['export_locations'][0]['path'], 

767 'shr_id': share['id']}) 

768 

769 return output 

770 

771 def unmanage(self, share): 

772 """Unmanages a share. 

773 

774 :param share: Share that will be unmanaged. 

775 """ 

776 self.private_storage.delete(share['id']) 

777 

778 if len(share['export_locations']) == 0: 

779 LOG.info("The share with ID %(shr_id)s is no longer being " 

780 "managed.", {'shr_id': share['id']}) 

781 else: 

782 LOG.info("The share with current path %(shr_path)s and ID " 

783 "%(shr_id)s is no longer being managed.", 

784 {'shr_path': share['export_locations'][0]['path'], 

785 'shr_id': share['id']}) 

786 

787 def shrink_share(self, share, new_size, share_server=None): 

788 """Shrinks a share to new size. 

789 

790 :param share: Share that will be shrunk. 

791 :param new_size: New size of share. 

792 :param share_server: Data structure with share server information. 

793 Not used by this driver. 

794 """ 

795 hnas_share_id = self._get_hnas_share_id(share['id']) 

796 

797 LOG.debug("Shrinking share in HNAS: %(shr_id)s.", 

798 {'shr_id': share['id']}) 

799 

800 self._shrink_share(hnas_share_id, share, new_size) 

801 LOG.info("Share %(shr_id)s successfully shrunk to " 

802 "%(shr_size)sG.", 

803 {'shr_id': share['id'], 

804 'shr_size': str(new_size)}) 

805 

806 def revert_to_snapshot(self, context, snapshot, share_access_rules, 

807 snapshot_access_rules, share_server=None): 

808 """Reverts a share to a given snapshot. 

809 

810 :param context: The `context.RequestContext` object for the request 

811 :param snapshot: The snapshot to which the share is to be reverted to. 

812 :param share_access_rules: List of all access rules for the affected 

813 share. Not used by this driver. 

814 :param snapshot_access_rules: List of all access rules for the affected 

815 snapshot. Not used by this driver. 

816 :param share_server: Data structure with share server information. 

817 Not used by this driver. 

818 """ 

819 

820 hnas_share_id = self._get_hnas_share_id(snapshot['share_id']) 

821 

822 hnas_snapshot_id = self._get_hnas_snapshot_id(snapshot) 

823 

824 self._ensure_snapshot(snapshot, hnas_snapshot_id) 

825 

826 dest_path = os.path.join('/shares', hnas_share_id) 

827 src_path = os.path.join('/snapshots', hnas_share_id, hnas_snapshot_id) 

828 

829 self.hnas.tree_delete(dest_path) 

830 

831 self.hnas.vvol_create(hnas_share_id) 

832 

833 self.hnas.quota_add(hnas_share_id, snapshot['size']) 

834 

835 try: 

836 self.hnas.tree_clone(src_path, dest_path) 

837 except exception.HNASNothingToCloneException: 

838 LOG.warning("Source directory is empty, creating an empty " 

839 "directory.") 

840 

841 LOG.info("Share %(share)s successfully reverted to snapshot " 

842 "%(snapshot)s.", {'share': snapshot['share_id'], 

843 'snapshot': snapshot['id']}) 

844 

845 def _get_hnas_share_id(self, share_id): 

846 hnas_id = self.private_storage.get(share_id, 'hnas_id') 

847 

848 if hnas_id is None: 

849 hnas_id = share_id 

850 

851 LOG.debug("Share ID is %(shr_id)s and respective HNAS ID " 

852 "is %(hnas_id)s.", {'shr_id': share_id, 

853 'hnas_id': hnas_id}) 

854 

855 return hnas_id 

856 

857 def _get_hnas_snapshot_id(self, snapshot): 

858 hnas_snapshot_id = snapshot['id'] 

859 

860 if snapshot['provider_location']: 860 ↛ 867line 860 didn't jump to line 867 because the condition on line 860 was always true

861 LOG.debug("Snapshot %(snap_id)s with provider_location: " 

862 "%(p_loc)s.", 

863 {'snap_id': hnas_snapshot_id, 

864 'p_loc': snapshot['provider_location']}) 

865 hnas_snapshot_id = snapshot['provider_location'].split('/')[-1] 

866 

867 return hnas_snapshot_id 

868 

869 def _create_share(self, share_id, share_size, share_proto): 

870 """Creates share. 

871 

872 Creates a virtual-volume, adds a quota limit and exports it. 

873 :param share_id: manila's database ID of share that will be created. 

874 :param share_size: Size limit of share. 

875 :param share_proto: Protocol of share that will be created 

876 (NFS or CIFS) 

877 :returns: Returns a list of dicts containing the new share's export 

878 locations. 

879 

880 """ 

881 self._check_fs_mounted() 

882 

883 self.hnas.vvol_create(share_id) 

884 

885 self.hnas.quota_add(share_id, share_size) 

886 

887 LOG.debug("Share created with id %(shr)s, size %(size)sG.", 

888 {'shr': share_id, 'size': share_size}) 

889 

890 self._create_export(share_id, share_proto) 

891 

892 export_list = self._get_export_locations(share_proto, share_id) 

893 return export_list 

894 

895 def _create_export(self, share_id, share_proto, snapshot_id=None): 

896 try: 

897 if share_proto.lower() == 'nfs': 

898 # Create NFS export 

899 self.hnas.nfs_export_add(share_id, snapshot_id=snapshot_id) 

900 LOG.debug("NFS Export created to %(shr)s.", 

901 {'shr': share_id}) 

902 else: 

903 # Create CIFS share with vvol path 

904 self.hnas.cifs_share_add(share_id, snapshot_id=snapshot_id) 

905 LOG.debug("CIFS share created to %(shr)s.", 

906 {'shr': share_id}) 

907 except exception.HNASBackendException: 

908 with excutils.save_and_reraise_exception(): 

909 if snapshot_id is None: 909 ↛ exitline 909 didn't jump to the function exit

910 self.hnas.vvol_delete(share_id) 

911 

912 def _check_fs_mounted(self): 

913 mounted = self.hnas.check_fs_mounted() 

914 if not mounted: 

915 msg = _("Filesystem %s is not mounted.") % self.fs_name 

916 raise exception.HNASBackendException(msg=msg) 

917 

918 def _ensure_share(self, share, hnas_share_id): 

919 """Ensure that share is exported. 

920 

921 :param share: Share that will be checked. 

922 :param hnas_share_id: HNAS ID of share that will be checked. 

923 :returns: Returns a list of dicts containing the share's export 

924 locations. 

925 

926 """ 

927 self._check_protocol(share['id'], share['share_proto']) 

928 self._check_fs_mounted() 

929 

930 self.hnas.check_vvol(hnas_share_id) 

931 self.hnas.check_quota(hnas_share_id) 

932 

933 if share['share_proto'].lower() == 'nfs': 

934 self.hnas.check_export(hnas_share_id) 

935 else: 

936 self.hnas.check_cifs(hnas_share_id) 

937 

938 export_list = self._get_export_locations( 

939 share['share_proto'], hnas_share_id) 

940 

941 return export_list 

942 

943 def _shrink_share(self, hnas_share_id, share, new_size): 

944 """Shrinks a share to new size. 

945 

946 :param hnas_share_id: HNAS ID of share that will be shrunk. 

947 :param share: model of share that will be shrunk. 

948 :param new_size: New size of share after shrink operation. 

949 """ 

950 self._ensure_share(share, hnas_share_id) 

951 

952 usage = self.hnas.get_share_usage(hnas_share_id) 

953 

954 LOG.debug("Usage space in share %(share)s: %(usage)sG", 

955 {'share': share['id'], 'usage': usage}) 

956 

957 if new_size > usage: 

958 self.hnas.modify_quota(hnas_share_id, new_size) 

959 else: 

960 raise exception.ShareShrinkingPossibleDataLoss( 

961 share_id=share['id']) 

962 

963 def _extend_share(self, hnas_share_id, share, new_size): 

964 """Extends a share to new size. 

965 

966 :param hnas_share_id: HNAS ID of share that will be extended. 

967 :param share: model of share that will be extended. 

968 :param new_size: New size of share after extend operation. 

969 """ 

970 self._ensure_share(share, hnas_share_id) 

971 

972 old_size = share['size'] 

973 available_space = self.hnas.get_stats()[1] 

974 

975 LOG.debug("Available space in filesystem: %(space)sG.", 

976 {'space': available_space}) 

977 

978 if (new_size - old_size) < available_space: 

979 self.hnas.modify_quota(hnas_share_id, new_size) 

980 else: 

981 msg = (_("Share %s cannot be extended due to insufficient space.") 

982 % share['id']) 

983 raise exception.HNASBackendException(msg=msg) 

984 

985 def _delete_share(self, hnas_share_id, share_proto): 

986 """Deletes share. 

987 

988 It uses tree-delete-job-submit to format and delete virtual-volumes. 

989 Quota is deleted with virtual-volume. 

990 :param hnas_share_id: HNAS ID of share that will be deleted. 

991 :param share_proto: Protocol of share that will be deleted. 

992 """ 

993 self._check_fs_mounted() 

994 

995 if share_proto.lower() == 'nfs': 

996 self.hnas.nfs_export_del(hnas_share_id) 

997 elif share_proto.lower() == 'cifs': 997 ↛ 999line 997 didn't jump to line 999 because the condition on line 997 was always true

998 self.hnas.cifs_share_del(hnas_share_id) 

999 self.hnas.vvol_delete(hnas_share_id) 

1000 

1001 def _manage_existing(self, share, hnas_share_id): 

1002 """Manages a share that exists on backend. 

1003 

1004 :param share: share that will be managed. 

1005 :param hnas_share_id: HNAS ID of share that will be managed. 

1006 :returns: Returns a dict with size of the share managed and a list of 

1007 dicts containing its export locations. 

1008 """ 

1009 self._ensure_share(share, hnas_share_id) 

1010 

1011 share_size = self.hnas.get_share_quota(hnas_share_id) 

1012 if share_size is None: 

1013 msg = (_("The share %s trying to be managed does not have a " 

1014 "quota limit, please set it before manage.") 

1015 % share['id']) 

1016 raise exception.ManageInvalidShare(reason=msg) 

1017 

1018 export_list = self._get_export_locations( 

1019 share['share_proto'], hnas_share_id) 

1020 

1021 return {'size': share_size, 'export_locations': export_list} 

1022 

1023 def _create_snapshot(self, hnas_share_id, snapshot): 

1024 """Creates a snapshot of share. 

1025 

1026 It copies the directory and all files to a new directory inside 

1027 /snapshots/share_id/. 

1028 :param hnas_share_id: HNAS ID of share for snapshot. 

1029 :param snapshot: Snapshot that will be created. 

1030 """ 

1031 self._ensure_share(snapshot['share'], hnas_share_id) 

1032 saved_list = [] 

1033 

1034 share_proto = snapshot['share']['share_proto'] 

1035 self._check_protocol(snapshot['share_id'], share_proto) 

1036 

1037 if share_proto.lower() == 'nfs': 

1038 saved_list = self.hnas.get_nfs_host_list(hnas_share_id) 

1039 new_list = [] 

1040 for access in saved_list: 

1041 for rw in ('read_write', 'readwrite', 'rw'): 

1042 access = access.replace(rw, 'ro') 

1043 new_list.append(access) 

1044 self.hnas.update_nfs_access_rule(new_list, share_id=hnas_share_id) 

1045 else: # CIFS 

1046 if (self.hnas.is_cifs_in_use(hnas_share_id) and 

1047 not self.cifs_snapshot): 

1048 msg = _("CIFS snapshot when share is mounted is disabled. " 

1049 "Set hitachi_hnas_allow_cifs_snapshot_while_mounted to" 

1050 " True or unmount the share to take a snapshot.") 

1051 raise exception.ShareBackendException(msg=msg) 

1052 

1053 src_path = os.path.join('/shares', hnas_share_id) 

1054 dest_path = os.path.join('/snapshots', hnas_share_id, snapshot['id']) 

1055 try: 

1056 self.hnas.tree_clone(src_path, dest_path) 

1057 except exception.HNASNothingToCloneException: 

1058 LOG.warning("Source directory is empty, creating an empty " 

1059 "directory.") 

1060 self.hnas.create_directory(dest_path) 

1061 finally: 

1062 if share_proto.lower() == 'nfs': 

1063 self.hnas.update_nfs_access_rule(saved_list, 

1064 share_id=hnas_share_id) 

1065 

1066 export_locations = [] 

1067 

1068 if snapshot['share'].get('mount_snapshot_support'): 

1069 self._create_export(hnas_share_id, share_proto, 

1070 snapshot_id=snapshot['id']) 

1071 export_locations = self._get_export_locations( 

1072 share_proto, snapshot['id'], is_snapshot=True) 

1073 

1074 return export_locations 

1075 

1076 def _delete_snapshot(self, share, hnas_share_id, snapshot_id): 

1077 """Deletes snapshot. 

1078 

1079 It receives the hnas_share_id only to join the path for snapshot. 

1080 :param hnas_share_id: HNAS ID of share from which snapshot was taken. 

1081 :param snapshot_id: ID of snapshot. 

1082 """ 

1083 self._check_fs_mounted() 

1084 share_proto = share['share_proto'] 

1085 

1086 if share.get('mount_snapshot_support'): 

1087 if share_proto.lower() == 'nfs': 

1088 self.hnas.nfs_export_del(snapshot_id=snapshot_id) 

1089 elif share_proto.lower() == 'cifs': 1089 ↛ 1092line 1089 didn't jump to line 1092 because the condition on line 1089 was always true

1090 self.hnas.cifs_share_del(snapshot_id) 

1091 

1092 path = os.path.join('/snapshots', hnas_share_id, snapshot_id) 

1093 self.hnas.tree_delete(path) 

1094 path = os.path.join('/snapshots', hnas_share_id) 

1095 self.hnas.delete_directory(path) 

1096 

1097 def _create_share_from_snapshot(self, share, src_hnas_share_id, 

1098 hnas_snapshot_id): 

1099 """Creates a new share from snapshot. 

1100 

1101 It copies everything from snapshot directory to a new vvol, 

1102 set a quota limit for it and export. 

1103 :param share: a dict from new share. 

1104 :param src_hnas_share_id: HNAS ID of share from which snapshot was 

1105 taken. 

1106 :param hnas_snapshot_id: HNAS ID from snapshot that will be copied to 

1107 new share. 

1108 :returns: Returns a list of dicts containing the new share's export 

1109 locations. 

1110 """ 

1111 dest_path = os.path.join('/shares', share['id']) 

1112 src_path = os.path.join('/snapshots', src_hnas_share_id, 

1113 hnas_snapshot_id) 

1114 

1115 # Before copying everything to new vvol, we need to create it, 

1116 # because we only can transform an empty directory into a vvol. 

1117 

1118 self._check_fs_mounted() 

1119 

1120 self.hnas.vvol_create(share['id']) 

1121 

1122 self.hnas.quota_add(share['id'], share['size']) 

1123 

1124 try: 

1125 self.hnas.tree_clone(src_path, dest_path) 

1126 except exception.HNASNothingToCloneException: 

1127 LOG.warning("Source directory is empty, exporting " 

1128 "directory.") 

1129 

1130 self._check_protocol(share['id'], share['share_proto']) 

1131 

1132 try: 

1133 if share['share_proto'].lower() == 'nfs': 

1134 self.hnas.nfs_export_add(share['id']) 

1135 

1136 else: 

1137 self.hnas.cifs_share_add(share['id']) 

1138 except exception.HNASBackendException: 

1139 with excutils.save_and_reraise_exception(): 

1140 self.hnas.vvol_delete(share['id']) 

1141 

1142 return self._get_export_locations( 

1143 share['share_proto'], share['id']) 

1144 

1145 def _check_protocol(self, share_id, protocol): 

1146 if protocol.lower() not in ('nfs', 'cifs'): 

1147 msg = _("Only NFS or CIFS protocol are currently supported. " 

1148 "Share provided %(share)s with protocol " 

1149 "%(proto)s.") % {'share': share_id, 

1150 'proto': protocol} 

1151 raise exception.ShareBackendException(msg=msg) 

1152 

1153 def _get_export_locations(self, share_proto, hnas_id, is_snapshot=False): 

1154 export_list = [] 

1155 for ip in (self.hnas_evs_ip, self.hnas_admin_network_ip): 

1156 if ip: 1156 ↛ 1155line 1156 didn't jump to line 1155 because the condition on line 1156 was always true

1157 path = self._get_export_path(ip, share_proto, hnas_id, 

1158 is_snapshot) 

1159 export_list.append({ 

1160 "path": path, 

1161 "is_admin_only": ip == self.hnas_admin_network_ip, 

1162 "metadata": {}, 

1163 }) 

1164 return export_list 

1165 

1166 def _get_export_path(self, ip, share_proto, hnas_id, is_snapshot): 

1167 r"""Gets and returns export path. 

1168 

1169 :param ip: IP from HNAS EVS configured. 

1170 :param share_proto: Share or snapshot protocol (NFS or CIFS). 

1171 :param hnas_id: Entity ID in HNAS, it can be the ID from a share or 

1172 a snapshot. 

1173 :param is_snapshot: Boolean to determine if export is related to a 

1174 share or a snapshot. 

1175 :return: Complete export path, for example: 

1176 - In NFS: 

1177 SHARE: 172.24.44.10:/shares/id 

1178 SNAPSHOT: 172.24.44.10:/snapshots/id 

1179 - In CIFS: 

1180 SHARE and SNAPSHOT: \\172.24.44.10\id 

1181 """ 

1182 if share_proto.lower() == 'nfs': 

1183 if is_snapshot: 

1184 path = os.path.join('/snapshots', hnas_id) 

1185 else: 

1186 path = os.path.join('/shares', hnas_id) 

1187 export = ':'.join((ip, path)) 

1188 else: 

1189 export = r'\\%s\%s' % (ip, hnas_id) 

1190 return export 

1191 

1192 def _ensure_snapshot(self, snapshot, hnas_snapshot_id): 

1193 """Ensure that snapshot is exported. 

1194 

1195 :param snapshot: Snapshot that will be checked. 

1196 :param hnas_snapshot_id: HNAS ID of snapshot that will be checked. 

1197 

1198 :returns: Returns a list of dicts containing the snapshot's export 

1199 locations or None if mount_snapshot_support is False. 

1200 """ 

1201 self._check_protocol(snapshot['share_id'], 

1202 snapshot['share']['share_proto']) 

1203 self._check_fs_mounted() 

1204 

1205 self.hnas.check_directory(snapshot['provider_location']) 

1206 

1207 export_list = None 

1208 if snapshot['share'].get('mount_snapshot_support'): 

1209 if snapshot['share']['share_proto'].lower() == 'nfs': 

1210 self.hnas.check_export(hnas_snapshot_id, is_snapshot=True) 

1211 else: 

1212 self.hnas.check_cifs(hnas_snapshot_id) 

1213 

1214 export_list = self._get_export_locations( 

1215 snapshot['share']['share_proto'], 

1216 hnas_snapshot_id, 

1217 is_snapshot=True) 

1218 

1219 return export_list 

1220 

1221 def ensure_snapshot(self, context, snapshot, share_server=None): 

1222 r"""Ensure that snapshot is exported. 

1223 

1224 :param context: The `context.RequestContext` object for the request. 

1225 :param snapshot: Snapshot that will be checked. 

1226 :param share_server: Data structure with share server information. 

1227 Not used by this driver. 

1228 

1229 :returns: Returns a list of dicts containing the EVS IP concatenated 

1230 with the path of snapshot in the filesystem or None if 

1231 mount_snapshot_support is False. 

1232 

1233 Example for NFS:: 

1234 

1235 [ 

1236 

1237 { 

1238 

1239 'path': '172.24.44.10:/snapshots/id', 

1240 'metadata': {}, 

1241 'is_admin_only': False 

1242 

1243 }, 

1244 

1245 { 

1246 

1247 'path': '192.168.0.10:/snapshots/id', 

1248 'metadata': {}, 

1249 'is_admin_only': True 

1250 

1251 } 

1252 

1253 ] 

1254 

1255 Example for CIFS:: 

1256 

1257 [ 

1258 

1259 { 

1260 

1261 'path': '\\172.24.44.10\id', 

1262 'metadata': {}, 

1263 'is_admin_only': False 

1264 

1265 }, 

1266 

1267 { 

1268 

1269 'path': '\\192.168.0.10\id', 

1270 'metadata': {}, 

1271 'is_admin_only': True 

1272 

1273 } 

1274 

1275 ] 

1276 

1277 """ 

1278 LOG.debug("Ensuring snapshot in HNAS: %(snap)s.", 

1279 {'snap': snapshot['id']}) 

1280 

1281 hnas_snapshot_id = self._get_hnas_snapshot_id(snapshot) 

1282 

1283 export_list = self._ensure_snapshot(snapshot, hnas_snapshot_id) 

1284 

1285 LOG.debug("Snapshot ensured in HNAS: %(snap)s, protocol %(proto)s.", 

1286 {'snap': snapshot['id'], 

1287 'proto': snapshot['share']['share_proto']}) 

1288 return export_list 

1289 

1290 def manage_existing_snapshot(self, snapshot, driver_options): 

1291 """Manages a snapshot that exists only in HNAS. 

1292 

1293 The snapshot to be managed should be in the path 

1294 /snapshots/SHARE_ID/SNAPSHOT_ID. Also, the size of snapshot should be 

1295 provided as --driver_options size=<size>. 

1296 :param snapshot: snapshot that will be managed. 

1297 :param driver_options: expects only one key 'size'. It must be 

1298 provided in order to manage a snapshot. 

1299 

1300 :returns: Returns a dict with size of snapshot managed 

1301 """ 

1302 try: 

1303 snapshot_size = int(driver_options.get("size", 0)) 

1304 except (ValueError, TypeError): 

1305 msg = _("The size in driver options to manage snapshot " 

1306 "%(snap_id)s should be an integer, in format " 

1307 "driver-options size=<SIZE>. Value passed: " 

1308 "%(size)s.") % {'snap_id': snapshot['id'], 

1309 'size': driver_options.get("size")} 

1310 raise exception.ManageInvalidShareSnapshot(reason=msg) 

1311 

1312 if snapshot_size == 0: 

1313 msg = _("Snapshot %(snap_id)s has no size specified for manage. " 

1314 "Please, provide the size with parameter driver-options " 

1315 "size=<SIZE>.") % {'snap_id': snapshot['id']} 

1316 raise exception.ManageInvalidShareSnapshot(reason=msg) 

1317 

1318 hnas_share_id = self._get_hnas_share_id(snapshot['share_id']) 

1319 

1320 LOG.debug("Path provided to manage snapshot: %(path)s.", 

1321 {'path': snapshot['provider_location']}) 

1322 

1323 path_info = snapshot['provider_location'].split('/') 

1324 

1325 if len(path_info) == 4 and path_info[1] == 'snapshots': 

1326 path_share_id = path_info[2] 

1327 hnas_snapshot_id = path_info[3] 

1328 else: 

1329 msg = (_("Incorrect path %(path)s for manage snapshot " 

1330 "%(snap_id)s. It should have the following format: " 

1331 "/snapshots/SHARE_ID/SNAPSHOT_ID.") % 

1332 {'path': snapshot['provider_location'], 

1333 'snap_id': snapshot['id']}) 

1334 raise exception.ManageInvalidShareSnapshot(reason=msg) 

1335 

1336 if hnas_share_id != path_share_id: 

1337 msg = _("The snapshot %(snap_id)s does not belong to share " 

1338 "%(share_id)s.") % {'snap_id': snapshot['id'], 

1339 'share_id': snapshot['share_id']} 

1340 raise exception.ManageInvalidShareSnapshot(reason=msg) 

1341 

1342 if not self.hnas.check_directory(snapshot['provider_location']): 

1343 msg = _("Snapshot %(snap_id)s does not exist in " 

1344 "HNAS.") % {'snap_id': hnas_snapshot_id} 

1345 raise exception.ManageInvalidShareSnapshot(reason=msg) 

1346 

1347 try: 

1348 self._ensure_snapshot(snapshot, hnas_snapshot_id) 

1349 except exception.HNASItemNotFoundException: 

1350 LOG.warning("Export does not exist for snapshot %s, " 

1351 "creating a new one.", snapshot['id']) 

1352 self._create_export(hnas_share_id, 

1353 snapshot['share']['share_proto'], 

1354 snapshot_id=hnas_snapshot_id) 

1355 

1356 output = {'size': snapshot_size} 

1357 if snapshot['share'].get('mount_snapshot_support'): 

1358 export_locations = self._get_export_locations( 

1359 snapshot['share']['share_proto'], 

1360 hnas_snapshot_id, 

1361 is_snapshot=True) 

1362 output['export_locations'] = export_locations 

1363 

1364 LOG.info("Snapshot %(snap_path)s for share %(shr_id)s was " 

1365 "successfully managed with ID %(snap_id)s.", 

1366 {'snap_path': snapshot['provider_location'], 

1367 'shr_id': snapshot['share_id'], 

1368 'snap_id': snapshot['id']}) 

1369 

1370 return output 

1371 

1372 def unmanage_snapshot(self, snapshot): 

1373 """Unmanage a share snapshot 

1374 

1375 :param snapshot: Snapshot that will be unmanaged. 

1376 """ 

1377 LOG.info("The snapshot with ID %(snap_id)s from share " 

1378 "%(share_id)s is no longer being managed by Manila. " 

1379 "However, it is not deleted and can be found in HNAS.", 

1380 {'snap_id': snapshot['id'], 

1381 'share_id': snapshot['share_id']}) 

1382 

1383 def snapshot_update_access(self, context, snapshot, access_rules, 

1384 add_rules, delete_rules, share_server=None): 

1385 """Update access rules for given snapshot. 

1386 

1387 Drivers should support 2 different cases in this method: 

1388 1. Recovery after error - 'access_rules' contains all access rules, 

1389 'add_rules' and 'delete_rules' shall be empty. Driver should clear any 

1390 existent access rules and apply all access rules for given snapshot. 

1391 This recovery is made at driver start up. 

1392 

1393 2. Adding/Deleting of several access rules - 'access_rules' contains 

1394 all access rules, 'add_rules' and 'delete_rules' contain rules which 

1395 should be added/deleted. Driver can ignore rules in 'access_rules' and 

1396 apply only rules from 'add_rules' and 'delete_rules'. All snapshots 

1397 rules should be read only. 

1398 

1399 :param context: Current context 

1400 :param snapshot: Snapshot model with snapshot data. 

1401 :param access_rules: All access rules for given snapshot 

1402 :param add_rules: Empty List or List of access rules which should be 

1403 added. access_rules already contains these rules. 

1404 :param delete_rules: Empty List or List of access rules which should be 

1405 removed. access_rules doesn't contain these rules. 

1406 :param share_server: None or Share server model 

1407 """ 

1408 hnas_snapshot_id = self._get_hnas_snapshot_id(snapshot) 

1409 

1410 self._ensure_snapshot(snapshot, hnas_snapshot_id) 

1411 

1412 access_rules, add_rules, delete_rules = utils.change_rules_to_readonly( 

1413 access_rules, add_rules, delete_rules) 

1414 

1415 if snapshot['share']['share_proto'].lower() == 'nfs': 

1416 host_list = [] 

1417 

1418 for rule in access_rules: 

1419 if rule['access_type'].lower() != 'ip': 

1420 msg = _("Only IP access type currently supported for NFS. " 

1421 "Snapshot provided %(snapshot)s with rule type " 

1422 "%(type)s.") % {'snapshot': snapshot['id'], 

1423 'type': rule['access_type']} 

1424 raise exception.InvalidSnapshotAccess(reason=msg) 

1425 

1426 host_list.append(rule['access_to'] + '(ro)') 

1427 

1428 self.hnas.update_nfs_access_rule(host_list, 

1429 snapshot_id=hnas_snapshot_id) 

1430 

1431 if host_list: 

1432 LOG.debug("Snapshot %(snapshot)s has the rules: %(rules)s", 

1433 {'snapshot': snapshot['id'], 

1434 'rules': ', '.join(host_list)}) 

1435 else: 

1436 LOG.debug("Snapshot %(snapshot)s has no rules.", 

1437 {'snapshot': snapshot['id']}) 

1438 else: 

1439 if not (add_rules or delete_rules): 

1440 # cifs recovery mode 

1441 self._clean_cifs_access_list(hnas_snapshot_id, 

1442 is_snapshot=True) 

1443 self._cifs_allow_access(snapshot, hnas_snapshot_id, 

1444 access_rules, is_snapshot=True) 

1445 else: 

1446 self._cifs_deny_access(snapshot, hnas_snapshot_id, 

1447 delete_rules, is_snapshot=True) 

1448 self._cifs_allow_access(snapshot, hnas_snapshot_id, 

1449 add_rules, is_snapshot=True)