Coverage for manila/share/drivers/hpe/hpe_3par_mediator.py: 95%

825 statements  

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

1# Copyright 2015 Hewlett Packard Enterprise Development LP 

2# 

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

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

5# a copy of the License at 

6# 

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

8# 

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

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

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

12# License for the specific language governing permissions and limitations 

13# under the License. 

14 

15"""HPE 3PAR Mediator for OpenStack Manila. 

16 

17This 'mediator' de-couples the 3PAR focused client from the OpenStack focused 

18driver. 

19""" 

20 

21from oslo_log import log 

22from oslo_utils import importutils 

23from oslo_utils import units 

24 

25from manila.data import utils as data_utils 

26from manila import exception 

27from manila.i18n import _ 

28from manila import utils 

29 

30hpe3parclient = importutils.try_import("hpe3parclient") 

31if hpe3parclient: 31 ↛ 35line 31 didn't jump to line 35 because the condition on line 31 was always true

32 from hpe3parclient import file_client # pylint: disable=import-error 

33 

34 

35LOG = log.getLogger(__name__) 

36MIN_CLIENT_VERSION = (4, 0, 0) 

37DENY = '-' 

38ALLOW = '+' 

39OPEN_STACK_MANILA = 'OpenStack Manila' 

40FULL = 1 

41THIN = 2 

42DEDUPE = 6 

43ENABLED = 1 

44DISABLED = 2 

45CACHE = 'cache' 

46CONTINUOUS_AVAIL = 'continuous_avail' 

47ACCESS_BASED_ENUM = 'access_based_enum' 

48SMB_EXTRA_SPECS_MAP = { 

49 CACHE: CACHE, 

50 CONTINUOUS_AVAIL: 'ca', 

51 ACCESS_BASED_ENUM: 'abe', 

52} 

53IP_ALREADY_EXISTS = 'IP address %s already exists' 

54USER_ALREADY_EXISTS = '"allow" permission already exists for "%s"' 

55DOES_NOT_EXIST = 'does not exist, cannot' 

56LOCAL_IP = '127.0.0.1' 

57LOCAL_IP_RO = '127.0.0.2' 

58SUPER_SHARE = 'OPENSTACK_SUPER_SHARE' 

59TMP_RO_SNAP_EXPORT = "Temp RO snapshot export as source for creating RW share." 

60 

61 

62class HPE3ParMediator(object): 

63 """3PAR client-facing code for the 3PAR driver. 

64 

65 Version history: 

66 1.0.0 - Begin Liberty development (post-Kilo) 

67 1.0.1 - Report thin/dedup/hp_flash_cache capabilities 

68 1.0.2 - Add share server/share network support 

69 1.0.3 - Use hp3par prefix for share types and capabilities 

70 2.0.0 - Rebranded HP to HPE 

71 2.0.1 - Add access_level (e.g. read-only support) 

72 2.0.2 - Add extend/shrink 

73 2.0.3 - Fix SMB read-only access (added in 2.0.1) 

74 2.0.4 - Remove file tree on delete when using nested shares #1538800 

75 2.0.5 - Reduce the fsquota by share size 

76 when a share is deleted #1582931 

77 2.0.6 - Read-write share from snapshot (using driver mount and copy) 

78 2.0.7 - Add update_access support 

79 2.0.8 - Multi pools support per backend 

80 2.0.9 - Fix get_vfs() to correctly validate conf IP addresses at 

81 boot up #1621016 

82 

83 """ 

84 

85 VERSION = "2.0.9" 

86 

87 def __init__(self, **kwargs): 

88 

89 self.hpe3par_username = kwargs.get('hpe3par_username') 

90 self.hpe3par_password = kwargs.get('hpe3par_password') 

91 self.hpe3par_api_url = kwargs.get('hpe3par_api_url') 

92 self.hpe3par_debug = kwargs.get('hpe3par_debug') 

93 self.hpe3par_san_ip = kwargs.get('hpe3par_san_ip') 

94 self.hpe3par_san_login = kwargs.get('hpe3par_san_login') 

95 self.hpe3par_san_password = kwargs.get('hpe3par_san_password') 

96 self.hpe3par_san_ssh_port = kwargs.get('hpe3par_san_ssh_port') 

97 self.hpe3par_san_private_key = kwargs.get('hpe3par_san_private_key') 

98 self.hpe3par_fstore_per_share = kwargs.get('hpe3par_fstore_per_share') 

99 self.hpe3par_require_cifs_ip = kwargs.get('hpe3par_require_cifs_ip') 

100 self.hpe3par_cifs_admin_access_username = ( 

101 kwargs.get('hpe3par_cifs_admin_access_username')) 

102 self.hpe3par_cifs_admin_access_password = ( 

103 kwargs.get('hpe3par_cifs_admin_access_password')) 

104 self.hpe3par_cifs_admin_access_domain = ( 

105 kwargs.get('hpe3par_cifs_admin_access_domain')) 

106 self.hpe3par_share_mount_path = kwargs.get('hpe3par_share_mount_path') 

107 self.my_ip = kwargs.get('my_ip') 

108 

109 self.ssh_conn_timeout = kwargs.get('ssh_conn_timeout') 

110 self._client = None 

111 self.client_version = None 

112 

113 @staticmethod 

114 def no_client(): 

115 return hpe3parclient is None 

116 

117 def do_setup(self): 

118 

119 if self.no_client(): 

120 msg = _('You must install hpe3parclient before using the 3PAR ' 

121 'driver. Run "pip install --upgrade python-3parclient" ' 

122 'to upgrade the hpe3parclient.') 

123 LOG.error(msg) 

124 raise exception.HPE3ParInvalidClient(message=msg) 

125 

126 self.client_version = hpe3parclient.version_tuple 

127 if self.client_version < MIN_CLIENT_VERSION: 

128 msg = (_('Invalid hpe3parclient version found (%(found)s). ' 

129 'Version %(minimum)s or greater required. Run "pip' 

130 ' install --upgrade python-3parclient" to upgrade' 

131 ' the hpe3parclient.') % 

132 {'found': '.'.join(map(str, self.client_version)), 

133 'minimum': '.'.join(map(str, 

134 MIN_CLIENT_VERSION))}) 

135 LOG.error(msg) 

136 raise exception.HPE3ParInvalidClient(message=msg) 

137 

138 try: 

139 self._client = file_client.HPE3ParFilePersonaClient( 

140 self.hpe3par_api_url) 

141 except Exception as e: 

142 msg = (_('Failed to connect to HPE 3PAR File Persona Client: %s') % 

143 str(e)) 

144 LOG.exception(msg) 

145 raise exception.ShareBackendException(message=msg) 

146 

147 try: 

148 ssh_kwargs = {} 

149 if self.hpe3par_san_ssh_port: 149 ↛ 151line 149 didn't jump to line 151 because the condition on line 149 was always true

150 ssh_kwargs['port'] = self.hpe3par_san_ssh_port 

151 if self.ssh_conn_timeout: 151 ↛ 153line 151 didn't jump to line 153 because the condition on line 151 was always true

152 ssh_kwargs['conn_timeout'] = self.ssh_conn_timeout 

153 if self.hpe3par_san_private_key: 153 ↛ 154line 153 didn't jump to line 154 because the condition on line 153 was never true

154 ssh_kwargs['privatekey'] = self.hpe3par_san_private_key 

155 

156 self._client.setSSHOptions( 

157 self.hpe3par_san_ip, 

158 self.hpe3par_san_login, 

159 self.hpe3par_san_password, 

160 **ssh_kwargs 

161 ) 

162 

163 except Exception as e: 

164 msg = (_('Failed to set SSH options for HPE 3PAR File Persona ' 

165 'Client: %s') % str(e)) 

166 LOG.exception(msg) 

167 raise exception.ShareBackendException(message=msg) 

168 

169 LOG.info("HPE3ParMediator %(version)s, " 

170 "hpe3parclient %(client_version)s", 

171 {"version": self.VERSION, 

172 "client_version": hpe3parclient.get_version_string()}) 

173 

174 try: 

175 wsapi_version = self._client.getWsApiVersion()['build'] 

176 LOG.info("3PAR WSAPI %s", wsapi_version) 

177 except Exception as e: 

178 msg = (_('Failed to get 3PAR WSAPI version: %s') % 

179 str(e)) 

180 LOG.exception(msg) 

181 raise exception.ShareBackendException(message=msg) 

182 

183 if self.hpe3par_debug: 183 ↛ exitline 183 didn't return from function 'do_setup' because the condition on line 183 was always true

184 self._client.debug_rest(True) # Includes SSH debug (setSSH above) 

185 

186 def _wsapi_login(self): 

187 try: 

188 self._client.login(self.hpe3par_username, self.hpe3par_password) 

189 except Exception as e: 

190 msg = (_("Failed to Login to 3PAR (%(url)s) as %(user)s " 

191 "because: %(err)s") % 

192 {'url': self.hpe3par_api_url, 

193 'user': self.hpe3par_username, 

194 'err': str(e)}) 

195 LOG.error(msg) 

196 raise exception.ShareBackendException(msg=msg) 

197 

198 def _wsapi_logout(self): 

199 try: 

200 self._client.http.unauthenticate() 

201 except Exception as e: 

202 msg = ("Failed to Logout from 3PAR (%(url)s) because %(err)s") 

203 LOG.warning(msg, {'url': self.hpe3par_api_url, 

204 'err': str(e)}) 

205 # don't raise exception on logout() 

206 

207 @staticmethod 

208 def build_export_locations(protocol, ips, path): 

209 

210 if not ips: 

211 message = _('Failed to build export location due to missing IP.') 

212 raise exception.InvalidInput(reason=message) 

213 

214 if not path: 

215 message = _('Failed to build export location due to missing path.') 

216 raise exception.InvalidInput(reason=message) 

217 

218 share_proto = HPE3ParMediator.ensure_supported_protocol(protocol) 

219 if share_proto == 'nfs': 

220 return ['%s:%s' % (ip, path) for ip in ips] 

221 else: 

222 return [r'\\%s\%s' % (ip, path) for ip in ips] 

223 

224 def get_provisioned_gb(self, fpg): 

225 total_mb = 0 

226 try: 

227 result = self._client.getfsquota(fpg=fpg) 

228 except Exception as e: 

229 result = {'message': str(e)} 

230 

231 error_msg = result.get('message') 

232 if error_msg: 

233 message = (_('Error while getting fsquotas for FPG ' 

234 '%(fpg)s: %(msg)s') % 

235 {'fpg': fpg, 'msg': error_msg}) 

236 LOG.error(message) 

237 raise exception.ShareBackendException(msg=message) 

238 

239 for fsquota in result['members']: 

240 total_mb += float(fsquota['hardBlock']) 

241 return total_mb / units.Ki 

242 

243 def get_fpg_status(self, fpg): 

244 """Get capacity and capabilities for FPG.""" 

245 

246 try: 

247 result = self._client.getfpg(fpg) 

248 except Exception as e: 

249 msg = (_('Failed to get capacity for fpg %(fpg)s: %(e)s') % 

250 {'fpg': fpg, 'e': str(e)}) 

251 LOG.error(msg) 

252 raise exception.ShareBackendException(msg=msg) 

253 

254 if result['total'] != 1: 

255 msg = (_('Failed to get capacity for fpg %s.') % fpg) 

256 LOG.error(msg) 

257 raise exception.ShareBackendException(msg=msg) 

258 

259 member = result['members'][0] 

260 total_capacity_gb = float(member['capacityKiB']) / units.Mi 

261 free_capacity_gb = float(member['availCapacityKiB']) / units.Mi 

262 

263 volumes = member['vvs'] 

264 if isinstance(volumes, list): 

265 volume = volumes[0] # Use first name from list 

266 else: 

267 volume = volumes # There is just a name 

268 

269 self._wsapi_login() 

270 try: 

271 volume_info = self._client.getVolume(volume) 

272 volume_set = self._client.getVolumeSet(fpg) 

273 finally: 

274 self._wsapi_logout() 

275 

276 provisioning_type = volume_info['provisioningType'] 

277 if provisioning_type not in (THIN, FULL, DEDUPE): 

278 msg = (_('Unexpected provisioning type for FPG %(fpg)s: ' 

279 '%(ptype)s.') % {'fpg': fpg, 'ptype': provisioning_type}) 

280 LOG.error(msg) 

281 raise exception.ShareBackendException(msg=msg) 

282 

283 dedupe = provisioning_type == DEDUPE 

284 thin_provisioning = provisioning_type in (THIN, DEDUPE) 

285 

286 flash_cache_policy = volume_set.get('flashCachePolicy', DISABLED) 

287 hpe3par_flash_cache = flash_cache_policy == ENABLED 

288 

289 status = { 

290 'pool_name': fpg, 

291 'total_capacity_gb': total_capacity_gb, 

292 'free_capacity_gb': free_capacity_gb, 

293 'thin_provisioning': thin_provisioning, 

294 'dedupe': dedupe, 

295 'hpe3par_flash_cache': hpe3par_flash_cache, 

296 'hp3par_flash_cache': hpe3par_flash_cache, 

297 } 

298 

299 if thin_provisioning: 299 ↛ 302line 299 didn't jump to line 302 because the condition on line 299 was always true

300 status['provisioned_capacity_gb'] = self.get_provisioned_gb(fpg) 

301 

302 return status 

303 

304 @staticmethod 

305 def ensure_supported_protocol(share_proto): 

306 protocol = share_proto.lower() 

307 if protocol == 'cifs': 

308 protocol = 'smb' 

309 if protocol not in ['smb', 'nfs']: 

310 message = (_('Invalid protocol. Expected nfs or smb. Got %s.') % 

311 protocol) 

312 LOG.error(message) 

313 raise exception.InvalidShareAccess(reason=message) 

314 return protocol 

315 

316 @staticmethod 

317 def other_protocol(share_proto): 

318 """Given 'nfs' or 'smb' (or equivalent) return the other one.""" 

319 protocol = HPE3ParMediator.ensure_supported_protocol(share_proto) 

320 return 'nfs' if protocol == 'smb' else 'smb' 

321 

322 @staticmethod 

323 def ensure_prefix(uid, protocol=None, readonly=False): 

324 if uid.startswith('osf-'): 

325 return uid 

326 

327 if protocol: 

328 proto = '-%s' % HPE3ParMediator.ensure_supported_protocol(protocol) 

329 else: 

330 proto = '' 

331 

332 if readonly: 

333 ro = '-ro' 

334 else: 

335 ro = '' 

336 

337 # Format is osf[-ro]-{nfs|smb}-uid 

338 return 'osf%s%s-%s' % (proto, ro, uid) 

339 

340 @staticmethod 

341 def _get_nfs_options(extra_specs, readonly): 

342 """Validate the NFS extra_specs and return the options to use.""" 

343 

344 nfs_options = extra_specs.get('hpe3par:nfs_options') 

345 if nfs_options is None: 

346 nfs_options = extra_specs.get('hp3par:nfs_options') 

347 if nfs_options: 347 ↛ 348line 347 didn't jump to line 348 because the condition on line 347 was never true

348 msg = ("hp3par:nfs_options is deprecated. Use " 

349 "hpe3par:nfs_options instead.") 

350 LOG.warning(msg) 

351 

352 if nfs_options: 

353 options = nfs_options.split(',') 

354 else: 

355 options = [] 

356 

357 # rw, ro, and (no)root_squash (in)secure options are not allowed in 

358 # extra_specs because they will be forcibly set below. 

359 # no_subtree_check and fsid are not allowed per 3PAR support. 

360 # Other strings will be allowed to be sent to the 3PAR which will do 

361 # further validation. 

362 options_not_allowed = ['ro', 'rw', 

363 'no_root_squash', 'root_squash', 

364 'secure', 'insecure', 

365 'no_subtree_check', 'fsid'] 

366 

367 invalid_options = [ 

368 option for option in options if option in options_not_allowed 

369 ] 

370 

371 if invalid_options: 

372 raise exception.InvalidInput(_('Invalid hp3par:nfs_options or ' 

373 'hpe3par:nfs_options in ' 

374 'extra-specs. The following ' 

375 'options are not allowed: %s') % 

376 invalid_options) 

377 

378 options.append('ro' if readonly else 'rw') 

379 options.append('no_root_squash') 

380 options.append('insecure') 

381 

382 return ','.join(options) 

383 

384 def _build_createfshare_kwargs(self, protocol, fpg, fstore, readonly, 

385 sharedir, extra_specs, comment, 

386 client_ip=None): 

387 createfshare_kwargs = dict(fpg=fpg, 

388 fstore=fstore, 

389 sharedir=sharedir, 

390 comment=comment) 

391 

392 if 'hp3par_flash_cache' in extra_specs: 392 ↛ 393line 392 didn't jump to line 393 because the condition on line 392 was never true

393 msg = ("hp3par_flash_cache is deprecated. Use " 

394 "hpe3par_flash_cache instead.") 

395 LOG.warning(msg) 

396 

397 if protocol == 'nfs': 

398 if client_ip: 

399 createfshare_kwargs['clientip'] = client_ip 

400 else: 

401 # New NFS shares needs seed IP to prevent "all" access. 

402 # Readonly and readwrite NFS shares client IPs cannot overlap. 

403 if readonly: 

404 createfshare_kwargs['clientip'] = LOCAL_IP_RO 

405 else: 

406 createfshare_kwargs['clientip'] = LOCAL_IP 

407 options = self._get_nfs_options(extra_specs, readonly) 

408 createfshare_kwargs['options'] = options 

409 else: 

410 

411 # To keep the original (Kilo, Liberty) behavior where CIFS IP 

412 # access rules were required in addition to user rules enable 

413 # this to use a seed IP instead of the default (all allowed). 

414 if self.hpe3par_require_cifs_ip: 

415 if client_ip: 415 ↛ 418line 415 didn't jump to line 418 because the condition on line 415 was always true

416 createfshare_kwargs['allowip'] = client_ip 

417 else: 

418 createfshare_kwargs['allowip'] = LOCAL_IP 

419 

420 smb_opts = (ACCESS_BASED_ENUM, CONTINUOUS_AVAIL, CACHE) 

421 

422 for smb_opt in smb_opts: 

423 opt_value = extra_specs.get('hpe3par:smb_%s' % smb_opt) 

424 if opt_value is None: 

425 opt_value = extra_specs.get('hp3par:smb_%s' % smb_opt) 

426 if opt_value: 426 ↛ 427line 426 didn't jump to line 427 because the condition on line 426 was never true

427 msg = ("hp3par:smb_* is deprecated. Use " 

428 "hpe3par:smb_* instead.") 

429 LOG.warning(msg) 

430 

431 if opt_value: 

432 opt_key = SMB_EXTRA_SPECS_MAP[smb_opt] 

433 createfshare_kwargs[opt_key] = opt_value 

434 return createfshare_kwargs 

435 

436 def _update_capacity_quotas(self, fstore, new_size, old_size, fpg, vfs): 

437 

438 @utils.synchronized('hpe3par-update-quota-' + fstore) 

439 def _sync_update_capacity_quotas(fstore, new_size, old_size, fpg, vfs): 

440 """Update 3PAR quotas and return setfsquota output.""" 

441 

442 if self.hpe3par_fstore_per_share: 

443 hcapacity = str(new_size * units.Ki) 

444 scapacity = hcapacity 

445 else: 

446 hard_size_mb = (new_size - old_size) * units.Ki 

447 soft_size_mb = hard_size_mb 

448 result = self._client.getfsquota( 

449 fpg=fpg, vfs=vfs, fstore=fstore) 

450 LOG.debug("getfsquota result=%s", result) 

451 quotas = result['members'] 

452 if len(quotas) == 1: 452 ↛ 455line 452 didn't jump to line 455 because the condition on line 452 was always true

453 hard_size_mb += int(quotas[0].get('hardBlock', '0')) 

454 soft_size_mb += int(quotas[0].get('softBlock', '0')) 

455 hcapacity = str(hard_size_mb) 

456 scapacity = str(soft_size_mb) 

457 

458 return self._client.setfsquota(vfs, 

459 fpg=fpg, 

460 fstore=fstore, 

461 scapacity=scapacity, 

462 hcapacity=hcapacity) 

463 

464 try: 

465 result = _sync_update_capacity_quotas( 

466 fstore, new_size, old_size, fpg, vfs) 

467 LOG.debug("setfsquota result=%s", result) 

468 except Exception as e: 

469 msg = (_('Failed to update capacity quota ' 

470 '%(size)s on %(fstore)s with exception: %(e)s') % 

471 {'size': new_size - old_size, 

472 'fstore': fstore, 

473 'e': str(e)}) 

474 LOG.error(msg) 

475 raise exception.ShareBackendException(msg=msg) 

476 

477 # Non-empty result is an error message returned from the 3PAR 

478 if result: 

479 msg = (_('Failed to update capacity quota ' 

480 '%(size)s on %(fstore)s with error: %(error)s') % 

481 {'size': new_size - old_size, 

482 'fstore': fstore, 

483 'error': result}) 

484 LOG.error(msg) 

485 raise exception.ShareBackendException(msg=msg) 

486 

487 def _create_share(self, project_id, share_id, protocol, extra_specs, 

488 fpg, vfs, fstore, sharedir, readonly, size, comment, 

489 client_ip=None): 

490 share_name = self.ensure_prefix(share_id, readonly=readonly) 

491 

492 if not (sharedir or self.hpe3par_fstore_per_share): 

493 sharedir = share_name 

494 

495 if fstore: 

496 use_existing_fstore = True 

497 else: 

498 use_existing_fstore = False 

499 if self.hpe3par_fstore_per_share: 499 ↛ 501line 499 didn't jump to line 501 because the condition on line 499 was never true

500 # Do not use -ro in the fstore name. 

501 fstore = self.ensure_prefix(share_id, readonly=False) 

502 else: 

503 fstore = self.ensure_prefix(project_id, protocol) 

504 

505 createfshare_kwargs = self._build_createfshare_kwargs( 

506 protocol, 

507 fpg, 

508 fstore, 

509 readonly, 

510 sharedir, 

511 extra_specs, 

512 comment, 

513 client_ip=client_ip) 

514 

515 if not use_existing_fstore: 

516 

517 try: 

518 result = self._client.createfstore( 

519 vfs, fstore, fpg=fpg, 

520 comment=comment) 

521 LOG.debug("createfstore result=%s", result) 

522 except Exception as e: 

523 msg = (_('Failed to create fstore %(fstore)s: %(e)s') % 

524 {'fstore': fstore, 'e': str(e)}) 

525 LOG.exception(msg) 

526 raise exception.ShareBackendException(msg=msg) 

527 

528 if size: 528 ↛ 531line 528 didn't jump to line 531 because the condition on line 528 was always true

529 self._update_capacity_quotas(fstore, size, 0, fpg, vfs) 

530 

531 try: 

532 

533 if readonly and protocol == 'nfs': 

534 # For NFS, RO is a 2nd 3PAR share pointing to same sharedir 

535 share_name = self.ensure_prefix(share_id, readonly=readonly) 

536 

537 result = self._client.createfshare(protocol, 

538 vfs, 

539 share_name, 

540 **createfshare_kwargs) 

541 

542 LOG.debug("createfshare result=%s", result) 

543 

544 except Exception as e: 

545 msg = (_('Failed to create share %(share_name)s: %(e)s') % 

546 {'share_name': share_name, 'e': str(e)}) 

547 LOG.exception(msg) 

548 raise exception.ShareBackendException(msg=msg) 

549 

550 try: 

551 result = self._client.getfshare( 

552 protocol, share_name, 

553 fpg=fpg, vfs=vfs, fstore=fstore) 

554 LOG.debug("getfshare result=%s", result) 

555 

556 except Exception as e: 

557 msg = (_('Failed to get fshare %(share_name)s after creating it: ' 

558 '%(e)s') % {'share_name': share_name, 

559 'e': str(e)}) 

560 LOG.exception(msg) 

561 raise exception.ShareBackendException(msg=msg) 

562 

563 if result['total'] != 1: 

564 msg = (_('Failed to get fshare %(share_name)s after creating it. ' 

565 'Expected to get 1 fshare. Got %(total)s.') % 

566 {'share_name': share_name, 'total': result['total']}) 

567 LOG.error(msg) 

568 raise exception.ShareBackendException(msg=msg) 

569 return result['members'][0] 

570 

571 def create_share(self, project_id, share_id, share_proto, extra_specs, 

572 fpg, vfs, 

573 fstore=None, sharedir=None, readonly=False, size=None, 

574 comment=OPEN_STACK_MANILA, 

575 client_ip=None): 

576 """Create the share and return its path. 

577 

578 This method can create a share when called by the driver or when 

579 called locally from create_share_from_snapshot(). The optional 

580 parameters allow re-use. 

581 

582 :param project_id: The tenant ID. 

583 :param share_id: The share-id with or without osf- prefix. 

584 :param share_proto: The protocol (to map to smb or nfs) 

585 :param extra_specs: The share type extra-specs 

586 :param fpg: The file provisioning group 

587 :param vfs: The virtual file system 

588 :param fstore: (optional) The file store. When provided, an existing 

589 file store is used. Otherwise one is created. 

590 :param sharedir: (optional) Share directory. 

591 :param readonly: (optional) Create share as read-only. 

592 :param size: (optional) Size limit for file store if creating one. 

593 :param comment: (optional) Comment to set on the share. 

594 :param client_ip: (optional) IP address to give access to. 

595 :return: share path string 

596 """ 

597 

598 protocol = self.ensure_supported_protocol(share_proto) 

599 share = self._create_share(project_id, 

600 share_id, 

601 protocol, 

602 extra_specs, 

603 fpg, 

604 vfs, 

605 fstore, 

606 sharedir, 

607 readonly, 

608 size, 

609 comment, 

610 client_ip=client_ip) 

611 

612 if protocol == 'nfs': 

613 return share['sharePath'] 

614 else: 

615 return share['shareName'] 

616 

617 def create_share_from_snapshot(self, share_id, share_proto, extra_specs, 

618 orig_project_id, orig_share_id, 

619 snapshot_id, fpg, vfs, ips, 

620 size=None, 

621 comment=OPEN_STACK_MANILA): 

622 

623 protocol = self.ensure_supported_protocol(share_proto) 

624 snapshot_tag = self.ensure_prefix(snapshot_id) 

625 orig_share_name = self.ensure_prefix(orig_share_id) 

626 

627 snapshot = self._find_fsnap(orig_project_id, 

628 orig_share_name, 

629 protocol, 

630 snapshot_tag, 

631 fpg, 

632 vfs) 

633 

634 if not snapshot: 

635 msg = (_('Failed to create share from snapshot for ' 

636 'FPG/VFS/tag %(fpg)s/%(vfs)s/%(tag)s. ' 

637 'Snapshot not found.') % 

638 { 

639 'fpg': fpg, 

640 'vfs': vfs, 

641 'tag': snapshot_tag}) 

642 LOG.error(msg) 

643 raise exception.ShareBackendException(msg=msg) 

644 

645 fstore = snapshot['fstoreName'] 

646 if fstore == orig_share_name: 646 ↛ 648line 646 didn't jump to line 648 because the condition on line 646 was never true

647 # No subdir for original share created with fstore_per_share 

648 sharedir = '.snapshot/%s' % snapshot['snapName'] 

649 else: 

650 sharedir = '.snapshot/%s/%s' % (snapshot['snapName'], 

651 orig_share_name) 

652 

653 if protocol == "smb" and (not self.hpe3par_cifs_admin_access_username 

654 or not self.hpe3par_cifs_admin_access_password): 

655 LOG.warning("hpe3par_cifs_admin_access_username and " 

656 "hpe3par_cifs_admin_access_password must be " 

657 "provided in order for CIFS shares created from " 

658 "snapshots to be writable.") 

659 return self.create_share( 

660 orig_project_id, 

661 share_id, 

662 protocol, 

663 extra_specs, 

664 fpg, 

665 vfs, 

666 fstore=fstore, 

667 sharedir=sharedir, 

668 readonly=True, 

669 comment=comment, 

670 ) 

671 

672 # Export the snapshot as read-only to copy from. 

673 temp = ' '.join((comment, TMP_RO_SNAP_EXPORT)) 

674 source_path = self.create_share( 

675 orig_project_id, 

676 share_id, 

677 protocol, 

678 extra_specs, 

679 fpg, 

680 vfs, 

681 fstore=fstore, 

682 sharedir=sharedir, 

683 readonly=True, 

684 comment=temp, 

685 client_ip=self.my_ip 

686 ) 

687 

688 try: 

689 share_name = self.ensure_prefix(share_id) 

690 dest_path = self.create_share( 

691 orig_project_id, 

692 share_id, 

693 protocol, 

694 extra_specs, 

695 fpg, 

696 vfs, 

697 fstore=fstore, 

698 readonly=False, 

699 size=size, 

700 comment=comment, 

701 client_ip=','.join((self.my_ip, LOCAL_IP)) 

702 ) 

703 

704 try: 

705 if protocol == 'smb': 

706 self._grant_admin_smb_access( 

707 protocol, fpg, vfs, fstore, comment, share=share_name) 

708 

709 ro_share_name = self.ensure_prefix(share_id, readonly=True) 

710 self._grant_admin_smb_access( 

711 protocol, fpg, vfs, fstore, temp, share=ro_share_name) 

712 

713 source_locations = self.build_export_locations( 

714 protocol, ips, source_path) 

715 dest_locations = self.build_export_locations( 

716 protocol, ips, dest_path) 

717 

718 self._copy_share_data( 

719 share_id, source_locations[0], dest_locations[0], protocol) 

720 

721 # Revoke the admin access that was needed to copy to the dest. 

722 if protocol == 'nfs': 

723 self._change_access(DENY, 

724 orig_project_id, 

725 share_id, 

726 protocol, 

727 'ip', 

728 self.my_ip, 

729 'rw', 

730 fpg, 

731 vfs) 

732 else: 

733 self._revoke_admin_smb_access( 

734 protocol, fpg, vfs, fstore, comment) 

735 

736 except Exception as e: 

737 msg = ('Exception during mount and copy from RO snapshot ' 

738 'to RW share: %s') 

739 LOG.error(msg, e) 

740 self._delete_share(share_name, protocol, fpg, vfs, fstore) 

741 raise 

742 

743 finally: 

744 self._delete_ro_share( 

745 orig_project_id, share_id, protocol, fpg, vfs, fstore) 

746 

747 return dest_path 

748 

749 def _copy_share_data(self, dest_id, source_location, dest_location, 

750 protocol): 

751 

752 mount_location = "%s%s" % (self.hpe3par_share_mount_path, dest_id) 

753 source_share_dir = '/'.join((mount_location, "source_snap")) 

754 dest_share_dir = '/'.join((mount_location, "dest_share")) 

755 

756 dirs_to_remove = [] 

757 dirs_to_unmount = [] 

758 try: 

759 utils.execute('mkdir', '-p', source_share_dir, run_as_root=True) 

760 dirs_to_remove.append(source_share_dir) 

761 self._mount_share(protocol, source_location, source_share_dir) 

762 dirs_to_unmount.append(source_share_dir) 

763 

764 utils.execute('mkdir', dest_share_dir, run_as_root=True) 

765 dirs_to_remove.append(dest_share_dir) 

766 self._mount_share(protocol, dest_location, dest_share_dir) 

767 dirs_to_unmount.append(dest_share_dir) 

768 

769 self._copy_data(source_share_dir, dest_share_dir) 

770 finally: 

771 for d in dirs_to_unmount: 

772 self._unmount_share(d) 

773 

774 if dirs_to_remove: 774 ↛ exitline 774 didn't return from function '_copy_share_data' because the condition on line 774 was always true

775 dirs_to_remove.append(mount_location) 

776 utils.execute('rmdir', *dirs_to_remove, run_as_root=True) 

777 

778 def _copy_data(self, source_share_dir, dest_share_dir): 

779 

780 err_msg = None 

781 err_data = None 

782 try: 

783 copy = data_utils.Copy(source_share_dir, dest_share_dir, '') 

784 copy.run() 

785 progress = copy.get_progress()['total_progress'] 

786 if progress != 100: 

787 err_msg = _("Failed to copy data, reason: " 

788 "Total progress %d != 100.") 

789 err_data = progress 

790 except Exception as err: 

791 err_msg = _("Failed to copy data, reason: %s.") 

792 err_data = str(err) 

793 

794 if err_msg: 

795 raise exception.ShareBackendException(msg=err_msg % err_data) 

796 

797 def _delete_share(self, share_name, protocol, fpg, vfs, fstore): 

798 try: 

799 self._client.removefshare( 

800 protocol, vfs, share_name, fpg=fpg, fstore=fstore) 

801 

802 except Exception as e: 

803 msg = (_('Failed to remove share %(share_name)s: %(e)s') % 

804 {'share_name': share_name, 'e': str(e)}) 

805 LOG.exception(msg) 

806 raise exception.ShareBackendException(msg=msg) 

807 

808 def _delete_ro_share(self, project_id, share_id, protocol, 

809 fpg, vfs, fstore): 

810 share_name_ro = self.ensure_prefix(share_id, readonly=True) 

811 if not fstore: 

812 fstore = self._find_fstore(project_id, 

813 share_name_ro, 

814 protocol, 

815 fpg, 

816 vfs, 

817 allow_cross_protocol=True) 

818 if fstore: 

819 self._delete_share(share_name_ro, protocol, fpg, vfs, fstore) 

820 return fstore 

821 

822 def delete_share(self, project_id, share_id, share_size, share_proto, 

823 fpg, vfs, share_ip): 

824 

825 protocol = self.ensure_supported_protocol(share_proto) 

826 share_name = self.ensure_prefix(share_id) 

827 fstore = self._find_fstore(project_id, 

828 share_name, 

829 protocol, 

830 fpg, 

831 vfs, 

832 allow_cross_protocol=True) 

833 

834 removed_writable = False 

835 if fstore: 

836 self._delete_share(share_name, protocol, fpg, vfs, fstore) 

837 removed_writable = True 

838 

839 # Try to delete the read-only twin share, too. 

840 fstore = self._delete_ro_share( 

841 project_id, share_id, protocol, fpg, vfs, fstore) 

842 

843 if fstore == share_name: 

844 try: 

845 self._client.removefstore(vfs, fstore, fpg=fpg) 

846 except Exception as e: 

847 msg = (_('Failed to remove fstore %(fstore)s: %(e)s') % 

848 {'fstore': fstore, 'e': str(e)}) 

849 LOG.exception(msg) 

850 raise exception.ShareBackendException(msg=msg) 

851 

852 elif removed_writable: 

853 try: 

854 # Attempt to remove file tree on delete when using nested 

855 # shares. If the file tree cannot be removed for whatever 

856 # reason, we will not treat this as an error_deleting 

857 # issue. We will allow the delete to continue as requested. 

858 self._delete_file_tree( 

859 share_name, protocol, fpg, vfs, fstore, share_ip) 

860 # reduce the fsquota by share size when a tree is deleted. 

861 self._update_capacity_quotas( 

862 fstore, 0, share_size, fpg, vfs) 

863 except Exception as e: 

864 msg = ('Exception during cleanup of deleted ' 

865 'share %(share)s in filestore %(fstore)s: %(e)s') 

866 data = { 

867 'fstore': fstore, 

868 'share': share_name, 

869 'e': str(e), 

870 } 

871 LOG.warning(msg, data) 

872 

873 def _delete_file_tree(self, share_name, protocol, fpg, vfs, fstore, 

874 share_ip): 

875 # If the share protocol is CIFS, we need to make sure the admin 

876 # provided the proper config values. If they have not, we can simply 

877 # return out and log a warning. 

878 if protocol == "smb" and (not self.hpe3par_cifs_admin_access_username 

879 or not self.hpe3par_cifs_admin_access_password): 

880 LOG.warning("hpe3par_cifs_admin_access_username and " 

881 "hpe3par_cifs_admin_access_password must be " 

882 "provided in order for the file tree to be " 

883 "properly deleted.") 

884 return 

885 

886 mount_location = "%s%s" % (self.hpe3par_share_mount_path, share_name) 

887 share_dir = mount_location + "/%s" % share_name 

888 

889 # Create the super share. 

890 self._create_super_share(protocol, fpg, vfs, fstore) 

891 

892 # Create the mount directory. 

893 self._create_mount_directory(mount_location) 

894 

895 # Mount the super share. 

896 self._mount_super_share(protocol, mount_location, fpg, vfs, fstore, 

897 share_ip) 

898 

899 # Delete the share from the super share. 

900 self._delete_share_directory(share_dir) 

901 

902 # Unmount the super share. 

903 self._unmount_share(mount_location) 

904 

905 # Delete the mount directory. 

906 self._delete_share_directory(mount_location) 

907 

908 def _grant_admin_smb_access(self, protocol, fpg, vfs, fstore, comment, 

909 share=SUPER_SHARE): 

910 user = '+%s:fullcontrol' % self.hpe3par_cifs_admin_access_username 

911 setfshare_kwargs = { 

912 'fpg': fpg, 

913 'fstore': fstore, 

914 'comment': comment, 

915 'allowperm': user, 

916 } 

917 try: 

918 self._client.setfshare( 

919 protocol, vfs, share, **setfshare_kwargs) 

920 except Exception as err: 

921 raise exception.ShareBackendException( 

922 msg=_("There was an error adding permissions: %s") % err) 

923 

924 def _revoke_admin_smb_access(self, protocol, fpg, vfs, fstore, comment, 

925 share=SUPER_SHARE): 

926 user = '-%s:fullcontrol' % self.hpe3par_cifs_admin_access_username 

927 setfshare_kwargs = { 

928 'fpg': fpg, 

929 'fstore': fstore, 

930 'comment': comment, 

931 'allowperm': user, 

932 } 

933 try: 

934 self._client.setfshare( 

935 protocol, vfs, share, **setfshare_kwargs) 

936 except Exception as err: 

937 raise exception.ShareBackendException( 

938 msg=_("There was an error revoking permissions: %s") % err) 

939 

940 def _create_super_share(self, protocol, fpg, vfs, fstore, readonly=False): 

941 sharedir = '' 

942 extra_specs = {} 

943 comment = 'OpenStack super share used to delete nested shares.' 

944 createfshare_kwargs = self._build_createfshare_kwargs(protocol, 

945 fpg, 

946 fstore, 

947 readonly, 

948 sharedir, 

949 extra_specs, 

950 comment) 

951 

952 # If the share is NFS, we need to give the host access to the share in 

953 # order to properly mount it. 

954 if protocol == 'nfs': 

955 createfshare_kwargs['clientip'] = self.my_ip 

956 else: 

957 createfshare_kwargs['allowip'] = self.my_ip 

958 

959 try: 

960 result = self._client.createfshare(protocol, 

961 vfs, 

962 SUPER_SHARE, 

963 **createfshare_kwargs) 

964 LOG.debug("createfshare for %(name)s, result=%(result)s", 

965 {'name': SUPER_SHARE, 'result': result}) 

966 except Exception as e: 

967 msg = (_('Failed to create share %(share_name)s: %(e)s'), 

968 {'share_name': SUPER_SHARE, 'e': str(e)}) 

969 LOG.exception(msg) 

970 raise exception.ShareBackendException(msg=msg) 

971 

972 # If the share is CIFS, we need to grant access to the specified admin. 

973 if protocol == 'smb': 973 ↛ exitline 973 didn't return from function '_create_super_share' because the condition on line 973 was always true

974 self._grant_admin_smb_access(protocol, fpg, vfs, fstore, comment) 

975 

976 def _create_mount_directory(self, mount_location): 

977 try: 

978 utils.execute('mkdir', mount_location, run_as_root=True) 

979 except Exception as err: 

980 message = ("There was an error creating mount directory: " 

981 "%s. The nested file tree will not be deleted.", 

982 str(err)) 

983 LOG.warning(message) 

984 

985 def _mount_share(self, protocol, export_location, mount_dir): 

986 if protocol == 'nfs': 

987 cmd = ('mount', '-t', 'nfs', export_location, mount_dir) 

988 utils.execute(*cmd, run_as_root=True) 

989 else: 

990 export_location = export_location.replace('\\', '/') 

991 cred = ('username=' + self.hpe3par_cifs_admin_access_username + 

992 ',password=' + 

993 self.hpe3par_cifs_admin_access_password + 

994 ',domain=' + self.hpe3par_cifs_admin_access_domain) 

995 cmd = ('mount', '-t', 'cifs', export_location, mount_dir, 

996 '-o', cred) 

997 utils.execute(*cmd, run_as_root=True) 

998 

999 def _mount_super_share(self, protocol, mount_dir, fpg, vfs, fstore, 

1000 share_ip): 

1001 try: 

1002 mount_location = self._generate_mount_path( 

1003 protocol, fpg, vfs, fstore, share_ip) 

1004 self._mount_share(protocol, mount_location, mount_dir) 

1005 except Exception as err: 

1006 message = ("There was an error mounting the super share: " 

1007 "%s. The nested file tree will not be deleted.", 

1008 str(err)) 

1009 LOG.warning(message) 

1010 

1011 def _unmount_share(self, mount_location): 

1012 try: 

1013 utils.execute('umount', mount_location, run_as_root=True) 

1014 except Exception as err: 

1015 message = ("There was an error unmounting the share at " 

1016 "%(mount_location)s: %(error)s") 

1017 msg_data = { 

1018 'mount_location': mount_location, 

1019 'error': str(err), 

1020 } 

1021 LOG.warning(message, msg_data) 

1022 

1023 def _delete_share_directory(self, directory): 

1024 try: 

1025 utils.execute('rm', '-rf', directory, run_as_root=True) 

1026 except Exception as err: 

1027 message = ("There was an error removing the share: " 

1028 "%s. The nested file tree will not be deleted.", 

1029 str(err)) 

1030 LOG.warning(message) 

1031 

1032 def _generate_mount_path(self, protocol, fpg, vfs, fstore, share_ip): 

1033 path = None 

1034 if protocol == 'nfs': 

1035 path = (("%(share_ip)s:/%(fpg)s/%(vfs)s/%(fstore)s/") % 

1036 {'share_ip': share_ip, 

1037 'fpg': fpg, 

1038 'vfs': vfs, 

1039 'fstore': fstore}) 

1040 else: 

1041 path = (("//%(share_ip)s/%(share_name)s/") % 

1042 {'share_ip': share_ip, 

1043 'share_name': SUPER_SHARE}) 

1044 return path 

1045 

1046 def get_vfs(self, fpg, vfs=None): 

1047 """Get the VFS or raise an exception.""" 

1048 

1049 try: 

1050 result = self._client.getvfs(fpg=fpg, vfs=vfs) 

1051 except Exception as e: 

1052 msg = (_('Exception during getvfs %(vfs)s: %(e)s') % 

1053 {'vfs': vfs, 'e': str(e)}) 

1054 LOG.exception(msg) 

1055 raise exception.ShareBackendException(msg=msg) 

1056 

1057 if result['total'] != 1: 

1058 error_msg = result.get('message') 

1059 if error_msg: 1059 ↛ 1060line 1059 didn't jump to line 1060 because the condition on line 1059 was never true

1060 message = (_('Error while validating FPG/VFS ' 

1061 '(%(fpg)s/%(vfs)s): %(msg)s') % 

1062 {'fpg': fpg, 'vfs': vfs, 'msg': error_msg}) 

1063 LOG.error(message) 

1064 raise exception.ShareBackendException(msg=message) 

1065 else: 

1066 message = (_('Error while validating FPG/VFS ' 

1067 '(%(fpg)s/%(vfs)s): Expected 1, ' 

1068 'got %(total)s.') % 

1069 {'fpg': fpg, 'vfs': vfs, 

1070 'total': result['total']}) 

1071 

1072 LOG.error(message) 

1073 raise exception.ShareBackendException(msg=message) 

1074 

1075 value = result['members'][0] 

1076 if isinstance(value['vfsip'], dict): 

1077 # This is for 3parclient returning only one VFS entry 

1078 LOG.debug("3parclient version up to 4.2.1 is in use. Client " 

1079 "upgrade may be needed if using a VFS with multiple " 

1080 "IP addresses.") 

1081 value['vfsip']['address'] = [value['vfsip']['address']] 

1082 else: 

1083 # This is for 3parclient returning list of VFS entries 

1084 # Format get_vfs ret value to combine all IP addresses 

1085 discovered_vfs_ips = [] 

1086 for vfs_entry in value['vfsip']: 

1087 if vfs_entry['address']: 1087 ↛ 1086line 1087 didn't jump to line 1086 because the condition on line 1087 was always true

1088 discovered_vfs_ips.append(vfs_entry['address']) 

1089 value['vfsip'] = value['vfsip'][0] 

1090 value['vfsip']['address'] = discovered_vfs_ips 

1091 return value 

1092 

1093 @staticmethod 

1094 def _is_share_from_snapshot(fshare): 

1095 

1096 path = fshare.get('shareDir') 

1097 if path: 

1098 return '.snapshot' in path.split('/') 

1099 

1100 path = fshare.get('sharePath') 

1101 return path and '.snapshot' in path.split('/') 

1102 

1103 def create_snapshot(self, orig_project_id, orig_share_id, orig_share_proto, 

1104 snapshot_id, fpg, vfs): 

1105 """Creates a snapshot of a share.""" 

1106 

1107 fshare = self._find_fshare(orig_project_id, 

1108 orig_share_id, 

1109 orig_share_proto, 

1110 fpg, 

1111 vfs) 

1112 

1113 if not fshare: 

1114 msg = (_('Failed to create snapshot for FPG/VFS/fshare ' 

1115 '%(fpg)s/%(vfs)s/%(fshare)s: Failed to find fshare.') % 

1116 {'fpg': fpg, 'vfs': vfs, 'fshare': orig_share_id}) 

1117 LOG.error(msg) 

1118 raise exception.ShareBackendException(msg=msg) 

1119 

1120 if self._is_share_from_snapshot(fshare): 

1121 msg = (_('Failed to create snapshot for FPG/VFS/fshare ' 

1122 '%(fpg)s/%(vfs)s/%(fshare)s: Share is a read-only ' 

1123 'share of an existing snapshot.') % 

1124 {'fpg': fpg, 'vfs': vfs, 'fshare': orig_share_id}) 

1125 LOG.error(msg) 

1126 raise exception.ShareBackendException(msg=msg) 

1127 

1128 fstore = fshare.get('fstoreName') 

1129 snapshot_tag = self.ensure_prefix(snapshot_id) 

1130 try: 

1131 result = self._client.createfsnap( 

1132 vfs, fstore, snapshot_tag, fpg=fpg) 

1133 

1134 LOG.debug("createfsnap result=%s", result) 

1135 

1136 except Exception as e: 

1137 msg = (_('Failed to create snapshot for FPG/VFS/fstore ' 

1138 '%(fpg)s/%(vfs)s/%(fstore)s: %(e)s') % 

1139 {'fpg': fpg, 'vfs': vfs, 'fstore': fstore, 

1140 'e': str(e)}) 

1141 LOG.exception(msg) 

1142 raise exception.ShareBackendException(msg=msg) 

1143 

1144 def delete_snapshot(self, orig_project_id, orig_share_id, orig_proto, 

1145 snapshot_id, fpg, vfs): 

1146 """Deletes a snapshot of a share.""" 

1147 

1148 snapshot_tag = self.ensure_prefix(snapshot_id) 

1149 

1150 snapshot = self._find_fsnap(orig_project_id, orig_share_id, orig_proto, 

1151 snapshot_tag, fpg, vfs) 

1152 

1153 if not snapshot: 

1154 return 

1155 

1156 fstore = snapshot.get('fstoreName') 

1157 

1158 for protocol in ('nfs', 'smb'): 

1159 try: 

1160 shares = self._client.getfshare(protocol, 

1161 fpg=fpg, 

1162 vfs=vfs, 

1163 fstore=fstore) 

1164 except Exception as e: 

1165 msg = (_('Unexpected exception while getting share list. ' 

1166 'Cannot delete snapshot without checking for ' 

1167 'dependent shares first: %s') % str(e)) 

1168 LOG.exception(msg) 

1169 raise exception.ShareBackendException(msg=msg) 

1170 

1171 for share in shares['members']: 

1172 if protocol == 'nfs': 

1173 path = share['sharePath'][1:].split('/') 

1174 dot_snapshot_index = 3 

1175 else: 

1176 if share['shareDir']: 

1177 path = share['shareDir'].split('/') 

1178 else: 

1179 path = None 

1180 dot_snapshot_index = 0 

1181 

1182 snapshot_index = dot_snapshot_index + 1 

1183 if path and len(path) > snapshot_index: 

1184 if (path[dot_snapshot_index] == '.snapshot' and 1184 ↛ 1171line 1184 didn't jump to line 1171 because the condition on line 1184 was always true

1185 path[snapshot_index].endswith(snapshot_tag)): 

1186 msg = (_('Cannot delete snapshot because it has a ' 

1187 'dependent share.')) 

1188 raise exception.Invalid(msg) 

1189 

1190 snapname = snapshot['snapName'] 

1191 try: 

1192 result = self._client.removefsnap( 

1193 vfs, fstore, snapname=snapname, fpg=fpg) 

1194 

1195 LOG.debug("removefsnap result=%s", result) 

1196 

1197 except Exception as e: 

1198 msg = (_('Failed to delete snapshot for FPG/VFS/fstore/snapshot ' 

1199 '%(fpg)s/%(vfs)s/%(fstore)s/%(snapname)s: %(e)s') % 

1200 { 

1201 'fpg': fpg, 

1202 'vfs': vfs, 

1203 'fstore': fstore, 

1204 'snapname': snapname, 

1205 'e': str(e)}) 

1206 LOG.exception(msg) 

1207 raise exception.ShareBackendException(msg=msg) 

1208 

1209 # Try to reclaim the space 

1210 try: 

1211 self._client.startfsnapclean(fpg, reclaimStrategy='maxspeed') 

1212 except Exception: 

1213 # Remove already happened so only log this. 

1214 LOG.exception('Unexpected exception calling startfsnapclean ' 

1215 'for FPG %(fpg)s.', {'fpg': fpg}) 

1216 

1217 @staticmethod 

1218 def _validate_access_type(protocol, access_type): 

1219 

1220 if access_type not in ('ip', 'user'): 

1221 msg = (_("Invalid access type. Expected 'ip' or 'user'. " 

1222 "Actual '%s'.") % access_type) 

1223 LOG.error(msg) 

1224 raise exception.InvalidInput(reason=msg) 

1225 

1226 if protocol == 'nfs' and access_type != 'ip': 

1227 msg = (_("Invalid NFS access type. HPE 3PAR NFS supports 'ip'. " 

1228 "Actual '%s'.") % access_type) 

1229 LOG.error(msg) 

1230 raise exception.HPE3ParInvalid(err=msg) 

1231 

1232 return protocol 

1233 

1234 @staticmethod 

1235 def _validate_access_level(protocol, access_type, access_level, fshare): 

1236 

1237 readonly = access_level == 'ro' 

1238 snapshot = HPE3ParMediator._is_share_from_snapshot(fshare) 

1239 

1240 if snapshot and not readonly: 

1241 reason = _('3PAR shares from snapshots require read-only access') 

1242 LOG.error(reason) 

1243 raise exception.InvalidShareAccess(reason=reason) 

1244 

1245 if protocol == 'smb' and access_type == 'ip' and snapshot != readonly: 

1246 msg = (_("Invalid CIFS access rule. HPE 3PAR optionally supports " 

1247 "IP access rules for CIFS shares, but they must be " 

1248 "read-only for shares from snapshots and read-write for " 

1249 "other shares. Use the required CIFS 'user' access rules " 

1250 "to refine access.")) 

1251 LOG.error(msg) 

1252 raise exception.InvalidShareAccess(reason=msg) 

1253 

1254 @staticmethod 

1255 def ignore_benign_access_results(plus_or_minus, access_type, access_to, 

1256 result): 

1257 

1258 # TODO(markstur): Remove the next line when hpe3parclient is fixed. 

1259 result = [x for x in result if x != '\r'] 

1260 

1261 if result: 

1262 if plus_or_minus == DENY: 

1263 if DOES_NOT_EXIST in result[0]: 

1264 return None 

1265 else: 

1266 if access_type == 'user': 

1267 if USER_ALREADY_EXISTS % access_to in result[0]: 1267 ↛ 1271line 1267 didn't jump to line 1271 because the condition on line 1267 was always true

1268 return None 

1269 elif IP_ALREADY_EXISTS % access_to in result[0]: 

1270 return None 

1271 return result 

1272 

1273 def _change_access(self, plus_or_minus, project_id, share_id, share_proto, 

1274 access_type, access_to, access_level, 

1275 fpg, vfs, extra_specs=None): 

1276 """Allow or deny access to a share. 

1277 

1278 Plus_or_minus character indicates add to allow list (+) or remove from 

1279 allow list (-). 

1280 """ 

1281 

1282 readonly = access_level == 'ro' 

1283 protocol = self.ensure_supported_protocol(share_proto) 

1284 

1285 try: 

1286 self._validate_access_type(protocol, access_type) 

1287 except Exception: 

1288 if plus_or_minus == DENY: 1288 ↛ 1290line 1288 didn't jump to line 1290 because the condition on line 1288 was never true

1289 # Catch invalid rules for deny. Allow them to be deleted. 

1290 return 

1291 else: 

1292 raise 

1293 

1294 fshare = self._find_fshare(project_id, 

1295 share_id, 

1296 protocol, 

1297 fpg, 

1298 vfs, 

1299 readonly=readonly) 

1300 if not fshare: 

1301 # Change access might apply to the share with the name that 

1302 # does not match the access_level prefix. 

1303 other_fshare = self._find_fshare(project_id, 

1304 share_id, 

1305 protocol, 

1306 fpg, 

1307 vfs, 

1308 readonly=not readonly) 

1309 if other_fshare: 

1310 

1311 if plus_or_minus == DENY: 

1312 # Try to deny rule from 'other' share for SMB or legacy. 

1313 fshare = other_fshare 

1314 

1315 elif self._is_share_from_snapshot(other_fshare): 1315 ↛ 1318line 1315 didn't jump to line 1318 because the condition on line 1315 was never true

1316 # Found a share-from-snapshot from before 

1317 # "-ro" was added to the name. Use it. 

1318 fshare = other_fshare 

1319 

1320 elif protocol == 'nfs': 

1321 # We don't have the RO|RW share we need, but the 

1322 # opposite one already exists. It is OK to create 

1323 # the one we need for ALLOW with NFS (not from snapshot). 

1324 fstore = other_fshare.get('fstoreName') 

1325 sharedir = other_fshare.get('shareDir') 

1326 comment = other_fshare.get('comment') 

1327 

1328 fshare = self._create_share(project_id, 

1329 share_id, 

1330 protocol, 

1331 extra_specs, 

1332 fpg, 

1333 vfs, 

1334 fstore=fstore, 

1335 sharedir=sharedir, 

1336 readonly=readonly, 

1337 size=None, 

1338 comment=comment) 

1339 else: 

1340 # SMB only has one share for RO and RW. Try to use it. 

1341 fshare = other_fshare 

1342 

1343 if not fshare: 

1344 msg = _('Failed to change (%(change)s) access ' 

1345 'to FPG/share %(fpg)s/%(share)s ' 

1346 'for %(type)s %(to)s %(level)s): ' 

1347 'Share does not exist on 3PAR.') 

1348 msg_data = { 

1349 'change': plus_or_minus, 

1350 'fpg': fpg, 

1351 'share': share_id, 

1352 'type': access_type, 

1353 'to': access_to, 

1354 'level': access_level, 

1355 } 

1356 

1357 if plus_or_minus == DENY: 

1358 LOG.warning(msg, msg_data) 

1359 return 

1360 else: 

1361 raise exception.HPE3ParInvalid(err=msg % msg_data) 

1362 

1363 try: 

1364 self._validate_access_level( 

1365 protocol, access_type, access_level, fshare) 

1366 except exception.InvalidShareAccess as e: 

1367 if plus_or_minus == DENY: 

1368 # Allow invalid access rules to be deleted. 

1369 msg = _('Ignoring deny invalid access rule ' 

1370 'for FPG/share %(fpg)s/%(share)s ' 

1371 'for %(type)s %(to)s %(level)s): %(e)s') 

1372 msg_data = { 

1373 'change': plus_or_minus, 

1374 'fpg': fpg, 

1375 'share': share_id, 

1376 'type': access_type, 

1377 'to': access_to, 

1378 'level': access_level, 

1379 'e': str(e), 

1380 } 

1381 LOG.info(msg, msg_data) 

1382 return 

1383 else: 

1384 raise 

1385 

1386 share_name = fshare.get('shareName') 

1387 setfshare_kwargs = { 

1388 'fpg': fpg, 

1389 'fstore': fshare.get('fstoreName'), 

1390 'comment': fshare.get('comment'), 

1391 } 

1392 

1393 if protocol == 'nfs': 

1394 access_change = '%s%s' % (plus_or_minus, access_to) 

1395 setfshare_kwargs['clientip'] = access_change 

1396 

1397 elif protocol == 'smb': 1397 ↛ 1408line 1397 didn't jump to line 1408 because the condition on line 1397 was always true

1398 

1399 if access_type == 'ip': 

1400 access_change = '%s%s' % (plus_or_minus, access_to) 

1401 setfshare_kwargs['allowip'] = access_change 

1402 

1403 else: 

1404 access_str = 'read' if readonly else 'fullcontrol' 

1405 perm = '%s%s:%s' % (plus_or_minus, access_to, access_str) 

1406 setfshare_kwargs['allowperm'] = perm 

1407 

1408 try: 

1409 result = self._client.setfshare( 

1410 protocol, vfs, share_name, **setfshare_kwargs) 

1411 

1412 result = self.ignore_benign_access_results( 

1413 plus_or_minus, access_type, access_to, result) 

1414 

1415 except Exception as e: 

1416 result = str(e) 

1417 

1418 LOG.debug("setfshare result=%s", result) 

1419 if result: 1419 ↛ 1420line 1419 didn't jump to line 1420 because the condition on line 1419 was never true

1420 msg = (_('Failed to change (%(change)s) access to FPG/share ' 

1421 '%(fpg)s/%(share)s for %(type)s %(to)s %(level)s: ' 

1422 '%(error)s') % 

1423 {'change': plus_or_minus, 

1424 'fpg': fpg, 

1425 'share': share_id, 

1426 'type': access_type, 

1427 'to': access_to, 

1428 'level': access_level, 

1429 'error': result}) 

1430 raise exception.ShareBackendException(msg=msg) 

1431 

1432 def _find_fstore(self, project_id, share_id, share_proto, fpg, vfs, 

1433 allow_cross_protocol=False): 

1434 

1435 share = self._find_fshare(project_id, 

1436 share_id, 

1437 share_proto, 

1438 fpg, 

1439 vfs, 

1440 allow_cross_protocol=allow_cross_protocol) 

1441 

1442 return share.get('fstoreName') if share else None 

1443 

1444 def _find_fshare(self, project_id, share_id, share_proto, fpg, vfs, 

1445 allow_cross_protocol=False, readonly=False): 

1446 

1447 share = self._find_fshare_with_proto(project_id, 

1448 share_id, 

1449 share_proto, 

1450 fpg, 

1451 vfs, 

1452 readonly=readonly) 

1453 

1454 if not share and allow_cross_protocol: 

1455 other_proto = self.other_protocol(share_proto) 

1456 share = self._find_fshare_with_proto(project_id, 

1457 share_id, 

1458 other_proto, 

1459 fpg, 

1460 vfs, 

1461 readonly=readonly) 

1462 return share 

1463 

1464 def _find_fshare_with_proto(self, project_id, share_id, share_proto, 

1465 fpg, vfs, readonly=False): 

1466 

1467 protocol = self.ensure_supported_protocol(share_proto) 

1468 share_name = self.ensure_prefix(share_id, readonly=readonly) 

1469 

1470 project_fstore = self.ensure_prefix(project_id, share_proto) 

1471 search_order = [ 

1472 {'fpg': fpg, 'vfs': vfs, 'fstore': project_fstore}, 

1473 {'fpg': fpg, 'vfs': vfs, 'fstore': share_name}, 

1474 {'fpg': fpg}, 

1475 {} 

1476 ] 

1477 

1478 try: 

1479 for search_params in search_order: 

1480 result = self._client.getfshare(protocol, share_name, 

1481 **search_params) 

1482 shares = result.get('members', []) 

1483 if len(shares) == 1: 

1484 return shares[0] 

1485 except Exception as e: 

1486 msg = (_('Unexpected exception while getting share list: %s') % 

1487 str(e)) 

1488 raise exception.ShareBackendException(msg=msg) 

1489 

1490 def _find_fsnap(self, project_id, share_id, orig_proto, snapshot_tag, 

1491 fpg, vfs): 

1492 

1493 share_name = self.ensure_prefix(share_id) 

1494 osf_project_id = self.ensure_prefix(project_id, orig_proto) 

1495 pattern = '*_%s' % self.ensure_prefix(snapshot_tag) 

1496 

1497 search_order = [ 

1498 {'pat': True, 'fpg': fpg, 'vfs': vfs, 'fstore': osf_project_id}, 

1499 {'pat': True, 'fpg': fpg, 'vfs': vfs, 'fstore': share_name}, 

1500 {'pat': True, 'fpg': fpg}, 

1501 {'pat': True}, 

1502 ] 

1503 

1504 try: 

1505 for search_params in search_order: 

1506 result = self._client.getfsnap(pattern, **search_params) 

1507 snapshots = result.get('members', []) 

1508 if len(snapshots) == 1: 

1509 return snapshots[0] 

1510 except Exception as e: 

1511 msg = (_('Unexpected exception while getting snapshots: %s') % 

1512 str(e)) 

1513 raise exception.ShareBackendException(msg=msg) 

1514 

1515 def update_access(self, project_id, share_id, share_proto, extra_specs, 

1516 access_rules, add_rules, delete_rules, fpg, vfs): 

1517 """Update access to a share.""" 

1518 protocol = self.ensure_supported_protocol(share_proto) 

1519 

1520 if not (delete_rules or add_rules): 

1521 # We need to re add all the rules. Check with 3PAR on it's current 

1522 # list and only add the deltas. 

1523 share = self._find_fshare(project_id, 

1524 share_id, 

1525 share_proto, 

1526 fpg, 

1527 vfs) 

1528 

1529 ref_users = [] 

1530 ro_ref_rules = [] 

1531 if protocol == 'nfs': 

1532 ref_rules = share['clients'] 

1533 

1534 # Check for RO rules. 

1535 ro_share = self._find_fshare(project_id, 

1536 share_id, 

1537 share_proto, 

1538 fpg, 

1539 vfs, 

1540 readonly=True) 

1541 if ro_share: 1541 ↛ 1555line 1541 didn't jump to line 1555 because the condition on line 1541 was always true

1542 ro_ref_rules = ro_share['clients'] 

1543 else: 

1544 ref_rules = [x[0] for x in share['allowPerm']] 

1545 ref_users = ref_rules[:] 

1546 # Get IP access as well 

1547 ips = share['allowIP'] 

1548 if not isinstance(ips, list): 1548 ↛ 1552line 1548 didn't jump to line 1552 because the condition on line 1548 was always true

1549 # If there is only one IP, the API returns a string 

1550 # rather than a list. We need to account for that. 

1551 ips = [ips] 

1552 ref_rules += ips 

1553 

1554 # Retrieve base rules. 

1555 base_rules = [] 

1556 for rule in access_rules: 

1557 base_rules.append(rule['access_to']) 

1558 

1559 # Check if we need to remove any rules from 3PAR. 

1560 for rule in ref_rules: 

1561 if rule in ref_users: 

1562 rule_type = 'user' 

1563 else: 

1564 rule_type = 'ip' 

1565 

1566 if rule not in base_rules + [LOCAL_IP, LOCAL_IP_RO]: 

1567 self._change_access(DENY, 

1568 project_id, 

1569 share_id, 

1570 share_proto, 

1571 rule_type, 

1572 rule, 

1573 None, 

1574 fpg, 

1575 vfs) 

1576 

1577 # Check to see if there are any RO rules to remove. 

1578 for rule in ro_ref_rules: 

1579 if rule not in base_rules + [LOCAL_IP, LOCAL_IP_RO]: 1579 ↛ 1580line 1579 didn't jump to line 1580 because the condition on line 1579 was never true

1580 self._change_access(DENY, 

1581 project_id, 

1582 share_id, 

1583 share_proto, 

1584 rule_type, 

1585 rule, 

1586 'ro', 

1587 fpg, 

1588 vfs) 

1589 

1590 # Check the rules we need to add. 

1591 for rule in access_rules: 

1592 if rule['access_to'] not in ref_rules and ( 1592 ↛ 1591line 1592 didn't jump to line 1591 because the condition on line 1592 was always true

1593 rule['access_to'] not in ro_ref_rules): 

1594 # Rule does not exist, we need to add it 

1595 self._change_access(ALLOW, 

1596 project_id, 

1597 share_id, 

1598 share_proto, 

1599 rule['access_type'], 

1600 rule['access_to'], 

1601 rule['access_level'], 

1602 fpg, 

1603 vfs, 

1604 extra_specs=extra_specs) 

1605 else: 

1606 # We have deltas of the rules that need to be added and deleted. 

1607 for rule in delete_rules: 

1608 self._change_access(DENY, 

1609 project_id, 

1610 share_id, 

1611 share_proto, 

1612 rule['access_type'], 

1613 rule['access_to'], 

1614 rule['access_level'], 

1615 fpg, 

1616 vfs) 

1617 for rule in add_rules: 

1618 self._change_access(ALLOW, 

1619 project_id, 

1620 share_id, 

1621 share_proto, 

1622 rule['access_type'], 

1623 rule['access_to'], 

1624 rule['access_level'], 

1625 fpg, 

1626 vfs, 

1627 extra_specs=extra_specs) 

1628 

1629 def resize_share(self, project_id, share_id, share_proto, 

1630 new_size, old_size, fpg, vfs): 

1631 """Extends or shrinks size of existing share.""" 

1632 

1633 share_name = self.ensure_prefix(share_id) 

1634 fstore = self._find_fstore(project_id, 

1635 share_name, 

1636 share_proto, 

1637 fpg, 

1638 vfs, 

1639 allow_cross_protocol=False) 

1640 

1641 if not fstore: 

1642 msg = (_('Cannot resize share because it was not found.')) 

1643 raise exception.InvalidShare(reason=msg) 

1644 

1645 self._update_capacity_quotas(fstore, new_size, old_size, fpg, vfs) 

1646 

1647 def fsip_exists(self, fsip): 

1648 """Try to get FSIP. Return True if it exists.""" 

1649 

1650 vfs = fsip['vfs'] 

1651 fpg = fsip['fspool'] 

1652 

1653 try: 

1654 result = self._client.getfsip(vfs, fpg=fpg) 

1655 LOG.debug("getfsip result: %s", result) 

1656 except Exception: 

1657 msg = (_('Failed to get FSIPs for FPG/VFS %(fspool)s/%(vfs)s.') % 

1658 fsip) 

1659 LOG.exception(msg) 

1660 raise exception.ShareBackendException(msg=msg) 

1661 

1662 for member in result['members']: 

1663 if all(item in member.items() for item in fsip.items()): 

1664 return True 

1665 

1666 return False 

1667 

1668 def create_fsip(self, ip, subnet, vlantag, fpg, vfs): 

1669 

1670 vlantag_str = str(vlantag) if vlantag else '0' 

1671 

1672 # Try to create it. It's OK if it already exists. 

1673 try: 

1674 result = self._client.createfsip(ip, 

1675 subnet, 

1676 vfs, 

1677 fpg=fpg, 

1678 vlantag=vlantag_str) 

1679 LOG.debug("createfsip result: %s", result) 

1680 

1681 except Exception: 

1682 msg = (_('Failed to create FSIP for %s') % ip) 

1683 LOG.exception(msg) 

1684 raise exception.ShareBackendException(msg=msg) 

1685 

1686 # Verify that it really exists. 

1687 fsip = { 

1688 'fspool': fpg, 

1689 'vfs': vfs, 

1690 'address': ip, 

1691 'prefixLen': subnet, 

1692 'vlanTag': vlantag_str, 

1693 } 

1694 if not self.fsip_exists(fsip): 

1695 msg = (_('Failed to get FSIP after creating it for ' 

1696 'FPG/VFS/IP/subnet/VLAN ' 

1697 '%(fspool)s/%(vfs)s/' 

1698 '%(address)s/%(prefixLen)s/%(vlanTag)s.') % fsip) 

1699 LOG.error(msg) 

1700 raise exception.ShareBackendException(msg=msg) 

1701 

1702 def remove_fsip(self, ip, fpg, vfs): 

1703 

1704 if not (vfs and ip): 

1705 # If there is no VFS and/or IP, then there is no FSIP to remove. 

1706 return 

1707 

1708 try: 

1709 result = self._client.removefsip(vfs, ip, fpg=fpg) 

1710 LOG.debug("removefsip result: %s", result) 

1711 

1712 except Exception: 

1713 msg = (_('Failed to remove FSIP %s') % ip) 

1714 LOG.exception(msg) 

1715 raise exception.ShareBackendException(msg=msg) 

1716 

1717 # Verify that it really no longer exists. 

1718 fsip = { 

1719 'fspool': fpg, 

1720 'vfs': vfs, 

1721 'address': ip, 

1722 } 

1723 if self.fsip_exists(fsip): 

1724 msg = (_('Failed to remove FSIP for FPG/VFS/IP ' 

1725 '%(fspool)s/%(vfs)s/%(address)s.') % fsip) 

1726 LOG.error(msg) 

1727 raise exception.ShareBackendException(msg=msg)