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

243 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 Driver for OpenStack Manila.""" 

16 

17import datetime 

18import hashlib 

19import inspect 

20import os 

21import re 

22 

23from oslo_config import cfg 

24from oslo_config import types 

25from oslo_log import log 

26 

27from manila.common import config 

28from manila import exception 

29from manila.i18n import _ 

30from manila.share import driver 

31from manila.share.drivers.hpe import hpe_3par_mediator 

32from manila.share import share_types 

33from manila.share import utils as share_utils 

34from manila import utils 

35 

36LOG = log.getLogger(__name__) 

37 

38 

39class FPG(types.String, types.IPAddress): 

40 """FPG type. 

41 

42 Used to represent multiple pools per backend values. 

43 Converts configuration value to an FPGs value. 

44 FPGs value format:: 

45 

46 FPG name, IP address 1, IP address 2, ..., IP address 4 

47 

48 where FPG name is a string value, 

49 IP address is of type types.IPAddress 

50 

51 Optionally doing range checking. 

52 If value is whitespace or empty string will raise error 

53 

54 :param min_ip: Optional check that number of min IP address of VFS. 

55 :param max_ip: Optional check that number of max IP address of VFS. 

56 :param type_name: Type name to be used in the sample config file. 

57 

58 """ 

59 

60 MAX_SUPPORTED_IP_PER_VFS = 4 

61 

62 def __init__(self, min_ip=0, max_ip=MAX_SUPPORTED_IP_PER_VFS, 

63 type_name='FPG'): 

64 types.String.__init__(self, type_name=type_name) 

65 types.IPAddress.__init__(self, type_name=type_name) 

66 

67 if max_ip < min_ip: 

68 msg = _("Pool's max acceptable IP cannot be less than min.") 

69 raise exception.HPE3ParInvalid(err=msg) 

70 

71 if min_ip < 0: 

72 msg = _("Pools must be configured with zero or more IPs.") 

73 raise exception.HPE3ParInvalid(err=msg) 

74 

75 if max_ip > FPG.MAX_SUPPORTED_IP_PER_VFS: 

76 msg = (_("Pool's max acceptable IP cannot be greater than " 

77 "supported value=%s.") % FPG.MAX_SUPPORTED_IP_PER_VFS) 

78 raise exception.HPE3ParInvalid(err=msg) 

79 

80 self.min_ip = min_ip 

81 self.max_ip = max_ip 

82 

83 def __call__(self, value): 

84 if value is None or value.strip(' ') == '': 

85 message = _("Invalid configuration. hpe3par_fpg must be set.") 

86 LOG.error(message) 

87 raise exception.HPE3ParInvalid(err=message) 

88 

89 ips = [] 

90 values = value.split(",") 

91 # Extract pool name 

92 pool_name = values.pop(0).strip() 

93 

94 # values will now be ['ip1', ...] 

95 if len(values) < self.min_ip: 

96 msg = (_("Require at least %s IPs configured per " 

97 "pool") % self.min_ip) 

98 raise exception.HPE3ParInvalid(err=msg) 

99 if len(values) > self.max_ip: 

100 msg = (_("Cannot configure IPs more than max supported " 

101 "%s IPs per pool") % self.max_ip) 

102 raise exception.HPE3ParInvalid(err=msg) 

103 

104 for ip_addr in values: 

105 ip_addr = types.String.__call__(self, ip_addr.strip()) 

106 try: 

107 ips.append(types.IPAddress.__call__(self, ip_addr)) 

108 except ValueError as verror: 

109 raise exception.HPE3ParInvalid(err=verror) 

110 fpg = {pool_name: ips} 

111 return fpg 

112 

113 def __repr__(self): 

114 return 'FPG' 

115 

116 def _formatter(self, value): 

117 return str(value) 

118 

119 

120HPE3PAR_OPTS = [ 

121 cfg.StrOpt('hpe3par_api_url', 

122 default='', 

123 help="3PAR WSAPI Server Url like " 

124 "https://<3par ip>:8080/api/v1"), 

125 cfg.StrOpt('hpe3par_username', 

126 default='', 

127 help="3PAR username with the 'edit' role"), 

128 cfg.StrOpt('hpe3par_password', 

129 default='', 

130 help="3PAR password for the user specified in hpe3par_username", 

131 secret=True), 

132 cfg.HostAddressOpt('hpe3par_san_ip', 

133 help="IP address of SAN controller"), 

134 cfg.StrOpt('hpe3par_san_login', 

135 default='', 

136 help="Username for SAN controller"), 

137 cfg.StrOpt('hpe3par_san_password', 

138 default='', 

139 help="Password for SAN controller", 

140 secret=True), 

141 cfg.PortOpt('hpe3par_san_ssh_port', 

142 default=22, 

143 help='SSH port to use with SAN'), 

144 cfg.MultiOpt('hpe3par_fpg', 

145 item_type=FPG(min_ip=0, max_ip=FPG.MAX_SUPPORTED_IP_PER_VFS), 

146 help="The File Provisioning Group (FPG) to use"), 

147 cfg.BoolOpt('hpe3par_fstore_per_share', 

148 default=False, 

149 help="Use one filestore per share"), 

150 cfg.BoolOpt('hpe3par_require_cifs_ip', 

151 default=False, 

152 help="Require IP access rules for CIFS (in addition to user)"), 

153 cfg.BoolOpt('hpe3par_debug', 

154 default=False, 

155 help="Enable HTTP debugging to 3PAR"), 

156 cfg.StrOpt('hpe3par_cifs_admin_access_username', 

157 default='', 

158 help="File system admin user name for CIFS."), 

159 cfg.StrOpt('hpe3par_cifs_admin_access_password', 

160 default='', 

161 help="File system admin password for CIFS.", 

162 secret=True), 

163 cfg.StrOpt('hpe3par_cifs_admin_access_domain', 

164 default='LOCAL_CLUSTER', 

165 help="File system domain for the CIFS admin user."), 

166 cfg.StrOpt('hpe3par_share_mount_path', 

167 default='/mnt/', 

168 help="The path where shares will be mounted when deleting " 

169 "nested file trees."), 

170] 

171 

172CONF = cfg.CONF 

173CONF.register_opts(HPE3PAR_OPTS) 

174 

175 

176def to_list(var): 

177 """Convert var to list type if not""" 

178 if isinstance(var, str): 178 ↛ 181line 178 didn't jump to line 181 because the condition on line 178 was always true

179 return [var] 

180 else: 

181 return var 

182 

183 

184class HPE3ParShareDriver(driver.ShareDriver): 

185 """HPE 3PAR driver for Manila. 

186 

187 Supports NFS and CIFS protocols on arrays with File Persona. 

188 

189 Version history:: 

190 

191 1.0.0 - Begin Liberty development (post-Kilo) 

192 1.0.1 - Report thin/dedup/hp_flash_cache capabilities 

193 1.0.2 - Add share server/share network support 

194 2.0.0 - Rebranded HP to HPE 

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

196 2.0.2 - Add extend/shrink 

197 2.0.3 - Remove file tree on delete when using nested shares #1538800 

198 2.0.4 - Reduce the fsquota by share size 

199 when a share is deleted #1582931 

200 2.0.5 - Add update_access support 

201 2.0.6 - Multi pool support per backend 

202 2.0.7 - Fix get_vfs() to correctly validate conf IP addresses at 

203 boot up #1621016 

204 2.0.8 - Replace ConsistencyGroup with ShareGroup 

205 

206 """ 

207 

208 VERSION = "2.0.8" 

209 

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

211 super(HPE3ParShareDriver, self).__init__((True, False), 

212 *args, 

213 **kwargs) 

214 

215 self.configuration = kwargs.get('configuration', None) 

216 self.configuration.append_config_values(HPE3PAR_OPTS) 

217 self.configuration.append_config_values(driver.ssh_opts) 

218 self.configuration.append_config_values(config.global_opts) 

219 self.fpgs = {} 

220 self._hpe3par = None # mediator between driver and client 

221 

222 def do_setup(self, context): 

223 """Any initialization the share driver does while starting.""" 

224 

225 LOG.info("Starting share driver %(driver_name)s (%(version)s)", 

226 {'driver_name': self.__class__.__name__, 

227 'version': self.VERSION}) 

228 

229 mediator = hpe_3par_mediator.HPE3ParMediator( 

230 hpe3par_username=self.configuration.hpe3par_username, 

231 hpe3par_password=self.configuration.hpe3par_password, 

232 hpe3par_api_url=self.configuration.hpe3par_api_url, 

233 hpe3par_debug=self.configuration.hpe3par_debug, 

234 hpe3par_san_ip=self.configuration.hpe3par_san_ip, 

235 hpe3par_san_login=self.configuration.hpe3par_san_login, 

236 hpe3par_san_password=self.configuration.hpe3par_san_password, 

237 hpe3par_san_ssh_port=self.configuration.hpe3par_san_ssh_port, 

238 hpe3par_fstore_per_share=(self.configuration 

239 .hpe3par_fstore_per_share), 

240 hpe3par_require_cifs_ip=self.configuration.hpe3par_require_cifs_ip, 

241 hpe3par_cifs_admin_access_username=( 

242 self.configuration.hpe3par_cifs_admin_access_username), 

243 hpe3par_cifs_admin_access_password=( 

244 self.configuration.hpe3par_cifs_admin_access_password), 

245 hpe3par_cifs_admin_access_domain=( 

246 self.configuration.hpe3par_cifs_admin_access_domain), 

247 hpe3par_share_mount_path=( 

248 self.configuration.hpe3par_share_mount_path), 

249 my_ip=self.configuration.my_ip, 

250 ssh_conn_timeout=self.configuration.ssh_conn_timeout, 

251 ) 

252 

253 mediator.do_setup() 

254 

255 def _validate_pool_ips(addresses, conf_pool_ips): 

256 # Pool configured IP addresses should be subset of IP addresses 

257 # retured from vfs 

258 if not set(conf_pool_ips) <= set(addresses): 

259 msg = _("Incorrect configuration. " 

260 "Configuration pool IP address did not match with " 

261 "IP addresses at 3par array") 

262 raise exception.HPE3ParInvalid(err=msg) 

263 

264 def _construct_fpg(): 

265 # FPG must be configured and must exist. 

266 # self.configuration.safe_get('hpe3par_fpg') will have value in 

267 # following format: 

268 # [ {'pool_name':['ip_addr', 'ip_addr', ...]}, ... ] 

269 for fpg in self.configuration.safe_get('hpe3par_fpg'): 

270 pool_name = list(fpg)[0] 

271 conf_pool_ips = fpg[pool_name] 

272 

273 # Validate the FPG and discover the VFS 

274 # This also validates the client, connection, firmware, WSAPI, 

275 # FPG... 

276 vfs_info = mediator.get_vfs(pool_name) 

277 if self.driver_handles_share_servers: 

278 # Use discovered IP(s) from array 

279 self.fpgs[pool_name] = { 

280 vfs_info['vfsname']: vfs_info['vfsip']['address']} 

281 elif conf_pool_ips == []: 

282 # not DHSS and IPs not configured in manila.conf. 

283 if not vfs_info['vfsip']['address']: 

284 msg = _("Unsupported configuration. " 

285 "hpe3par_fpg must have IP address " 

286 "or be discoverable at 3PAR") 

287 LOG.error(msg) 

288 raise exception.HPE3ParInvalid(err=msg) 

289 else: 

290 # Use discovered pool ips 

291 self.fpgs[pool_name] = { 

292 vfs_info['vfsname']: vfs_info['vfsip']['address']} 

293 else: 

294 # not DHSS and IPs configured in manila.conf 

295 _validate_pool_ips(vfs_info['vfsip']['address'], 

296 conf_pool_ips) 

297 self.fpgs[pool_name] = { 

298 vfs_info['vfsname']: conf_pool_ips} 

299 

300 _construct_fpg() 

301 

302 # Don't set _hpe3par until it is ready. Otherwise _update_stats fails. 

303 self._hpe3par = mediator 

304 

305 def _get_pool_location_from_share_host(self, share_instance_host): 

306 # Return pool name, vfs, IPs for a pool from share instance host 

307 pool_name = share_utils.extract_host(share_instance_host, level='pool') 

308 if not pool_name: 

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

310 share_instance_host) 

311 raise exception.InvalidHost(reason=message) 

312 

313 if pool_name not in self.fpgs: 

314 message = (_("Pool location lookup failed. " 

315 "Could not find pool %s") % 

316 pool_name) 

317 raise exception.InvalidHost(reason=message) 

318 

319 vfs = list(self.fpgs[pool_name])[0] 

320 ips = self.fpgs[pool_name][vfs] 

321 

322 return (pool_name, vfs, ips) 

323 

324 def _get_pool_location(self, share, share_server=None): 

325 # Return pool name, vfs, IPs for a pool from share host field 

326 # Use share_server if provided, instead of self.fpgs 

327 if share_server is not None: 

328 # When DHSS 

329 ips = share_server['backend_details'].get('ip') 

330 ips = to_list(ips) 

331 vfs = share_server['backend_details'].get('vfs') 

332 pool_name = share_server['backend_details'].get('fpg') 

333 return (pool_name, vfs, ips) 

334 else: 

335 # When DHSS = false 

336 return self._get_pool_location_from_share_host(share['host']) 

337 

338 def check_for_setup_error(self): 

339 

340 try: 

341 # Log the source SHA for support. Only do this with DEBUG. 

342 if LOG.isEnabledFor(log.DEBUG): 342 ↛ exitline 342 didn't return from function 'check_for_setup_error' because the condition on line 342 was always true

343 LOG.debug('HPE3ParShareDriver SHA1: %s', 

344 self.sha1_hash(HPE3ParShareDriver)) 

345 LOG.debug('HPE3ParMediator SHA1: %s', 

346 self.sha1_hash(hpe_3par_mediator.HPE3ParMediator)) 

347 except Exception as e: 

348 # Don't let any exceptions during the SHA1 logging interfere 

349 # with startup. This is just debug info to identify the source 

350 # code. If it doesn't work, just log a debug message. 

351 LOG.debug('Source code SHA1 not logged due to: %s', 

352 str(e)) 

353 

354 @staticmethod 

355 def sha1_hash(clazz): 

356 """Get the SHA1 hash for the source of a class.""" 

357 source_file = inspect.getsourcefile(clazz) 

358 file_size = os.path.getsize(source_file) 

359 

360 sha1 = hashlib.sha1(usedforsecurity=False) 

361 sha1.update(("blob %u\0" % file_size).encode('utf-8')) 

362 

363 with open(source_file, 'rb') as f: 

364 sha1.update(f.read()) 

365 

366 return sha1.hexdigest() 

367 

368 def get_network_allocations_number(self): 

369 return 1 

370 

371 def choose_share_server_compatible_with_share(self, context, share_servers, 

372 share, snapshot=None, 

373 share_group=None, 

374 encryption_key_ref=None): 

375 """Method that allows driver to choose share server for provided share. 

376 

377 If compatible share-server is not found, method should return None. 

378 

379 :param context: Current context 

380 :param share_servers: list with share-server models 

381 :param share: share model 

382 :param snapshot: snapshot model 

383 :param share_group: ShareGroup model with shares 

384 :returns: share-server or None 

385 """ 

386 # If creating in a share group, raise exception 

387 if share_group: 

388 msg = _("HPE 3PAR driver does not support share group") 

389 raise exception.InvalidRequest(message=msg) 

390 

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

392 for share_server in share_servers: 

393 if share_server['backend_details'].get('fpg') == pool_name: 393 ↛ 392line 393 didn't jump to line 392 because the condition on line 393 was always true

394 return share_server 

395 return None 

396 

397 @staticmethod 

398 def _validate_network_type(network_type): 

399 if network_type not in ('flat', 'vlan', None): 

400 reason = _('Invalid network type. %s is not supported by the ' 

401 '3PAR driver.') 

402 raise exception.NetworkBadConfigurationException( 

403 reason=reason % network_type) 

404 

405 def _create_share_server(self, network_info, request_host=None): 

406 """Is called to create/setup share server""" 

407 # Return pool name, vfs, IPs for a pool 

408 pool_name, vfs, ips = self._get_pool_location_from_share_host( 

409 request_host) 

410 

411 ip = network_info['network_allocations'][0]['ip_address'] 

412 if ip not in ips: 412 ↛ 427line 412 didn't jump to line 427 because the condition on line 412 was always true

413 # Besides DHSS, admin could have setup IP to VFS directly on array 

414 if len(ips) > (FPG.MAX_SUPPORTED_IP_PER_VFS - 1): 

415 message = (_("Pool %s has exceeded 3PAR's " 

416 "max supported VFS IP address") % pool_name) 

417 LOG.error(message) 

418 raise exception.Invalid(message) 

419 

420 subnet = utils.cidr_to_netmask(network_info['cidr']) 

421 vlantag = network_info['segmentation_id'] 

422 

423 self._hpe3par.create_fsip(ip, subnet, vlantag, pool_name, vfs) 

424 # Update in global saved config, self.fpgs[pool_name] 

425 ips.append(ip) 

426 

427 return {'share_server_name': network_info['server_id'], 

428 'share_server_id': network_info['server_id'], 

429 'ip': ip, 

430 'subnet': subnet, 

431 'vlantag': vlantag if vlantag else 0, 

432 'fpg': pool_name, 

433 'vfs': vfs} 

434 

435 def _setup_server(self, network_info, metadata=None): 

436 # NOTE(felipe_rodrigues): keep legacy network_info support as a dict. 

437 network_info = network_info[0] 

438 

439 LOG.debug("begin _setup_server with %s", network_info) 

440 

441 self._validate_network_type(network_info['network_type']) 

442 if metadata is not None and metadata['request_host'] is not None: 442 ↛ exitline 442 didn't return from function '_setup_server' because the condition on line 442 was always true

443 return self._create_share_server(network_info, 

444 metadata['request_host']) 

445 

446 def _teardown_server(self, server_details, security_services=None): 

447 LOG.debug("begin _teardown_server with %s", server_details) 

448 fpg = server_details.get('fpg') 

449 vfs = server_details.get('vfs') 

450 ip = server_details.get('ip') 

451 self._hpe3par.remove_fsip(ip, fpg, vfs) 

452 if ip in self.fpgs[fpg][vfs]: 452 ↛ exitline 452 didn't return from function '_teardown_server' because the condition on line 452 was always true

453 self.fpgs[fpg][vfs].remove(ip) 

454 

455 @staticmethod 

456 def build_share_comment(share): 

457 """Create an informational only comment to help admins and testers.""" 

458 

459 info = { 

460 'name': share['display_name'], 

461 'host': share['host'], 

462 'now': datetime.datetime.now().strftime('%H%M%S'), 

463 } 

464 

465 acceptable = re.compile(r'[^a-zA-Z0-9_=:@# \-]+', re.UNICODE) 

466 comment = ("OpenStack Manila - host=%(host)s orig_name=%(name)s " 

467 "created=%(now)s" % info) 

468 

469 return acceptable.sub('_', comment)[:254] # clean and truncate 

470 

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

472 """Is called to create share.""" 

473 

474 fpg, vfs, ips = self._get_pool_location(share, share_server) 

475 

476 protocol = share['share_proto'] 

477 extra_specs = share_types.get_extra_specs_from_share(share) 

478 

479 path = self._hpe3par.create_share( 

480 share['project_id'], 

481 share['id'], 

482 protocol, 

483 extra_specs, 

484 fpg, vfs, 

485 size=share['size'], 

486 comment=self.build_share_comment(share) 

487 ) 

488 

489 return self._hpe3par.build_export_locations(protocol, ips, path) 

490 

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

492 share_server=None, parent_share=None): 

493 """Is called to create share from snapshot.""" 

494 

495 fpg, vfs, ips = self._get_pool_location(share, share_server) 

496 

497 protocol = share['share_proto'] 

498 extra_specs = share_types.get_extra_specs_from_share(share) 

499 

500 path = self._hpe3par.create_share_from_snapshot( 

501 share['id'], 

502 protocol, 

503 extra_specs, 

504 share['project_id'], 

505 snapshot['share_id'], 

506 snapshot['id'], 

507 fpg, 

508 vfs, 

509 ips, 

510 size=share['size'], 

511 comment=self.build_share_comment(share) 

512 ) 

513 

514 return self._hpe3par.build_export_locations(protocol, ips, path) 

515 

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

517 """Deletes share and its fstore.""" 

518 

519 fpg, vfs, ips = self._get_pool_location(share, share_server) 

520 self._hpe3par.delete_share(share['project_id'], 

521 share['id'], 

522 share['size'], 

523 share['share_proto'], 

524 fpg, 

525 vfs, 

526 ips[0]) 

527 

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

529 """Creates a snapshot of a share.""" 

530 

531 fpg, vfs, ips = self._get_pool_location(snapshot['share'], 

532 share_server) 

533 self._hpe3par.create_snapshot(snapshot['share']['project_id'], 

534 snapshot['share']['id'], 

535 snapshot['share']['share_proto'], 

536 snapshot['id'], 

537 fpg, 

538 vfs) 

539 

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

541 """Deletes a snapshot of a share.""" 

542 

543 fpg, vfs, ips = self._get_pool_location(snapshot['share'], 

544 share_server) 

545 self._hpe3par.delete_snapshot(snapshot['share']['project_id'], 

546 snapshot['share']['id'], 

547 snapshot['share']['share_proto'], 

548 snapshot['id'], 

549 fpg, 

550 vfs) 

551 

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

553 pass 

554 

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

556 delete_rules, update_rules, share_server=None): 

557 """Update access to the share.""" 

558 extra_specs = None 

559 if 'NFS' == share['share_proto']: # Avoiding DB call otherwise 559 ↛ 562line 559 didn't jump to line 562 because the condition on line 559 was always true

560 extra_specs = share_types.get_extra_specs_from_share(share) 

561 

562 fpg, vfs, ips = self._get_pool_location(share, share_server) 

563 self._hpe3par.update_access(share['project_id'], 

564 share['id'], 

565 share['share_proto'], 

566 extra_specs, 

567 access_rules, 

568 add_rules, 

569 delete_rules, 

570 fpg, 

571 vfs) 

572 

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

574 """Extends size of existing share.""" 

575 

576 fpg, vfs, ips = self._get_pool_location(share, share_server) 

577 self._hpe3par.resize_share(share['project_id'], 

578 share['id'], 

579 share['share_proto'], 

580 new_size, 

581 share['size'], 

582 fpg, 

583 vfs) 

584 

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

586 """Shrinks size of existing share.""" 

587 

588 fpg, vfs, ips = self._get_pool_location(share, share_server) 

589 self._hpe3par.resize_share(share['project_id'], 

590 share['id'], 

591 share['share_proto'], 

592 new_size, 

593 share['size'], 

594 fpg, 

595 vfs) 

596 

597 def _update_share_stats(self): 

598 """Retrieve stats info from share group.""" 

599 

600 backend_name = self.configuration.safe_get( 

601 'share_backend_name') or "HPE_3PAR" 

602 

603 max_over_subscription_ratio = self.configuration.safe_get( 

604 'max_over_subscription_ratio') 

605 

606 reserved_share_percentage = self.configuration.safe_get( 

607 'reserved_share_percentage') 

608 if reserved_share_percentage is None: 608 ↛ 611line 608 didn't jump to line 611 because the condition on line 608 was always true

609 reserved_share_percentage = 0 

610 

611 reserved_share_from_snapshot_percentage = self.configuration.safe_get( 

612 'reserved_share_from_snapshot_percentage') 

613 if reserved_share_from_snapshot_percentage is None: 613 ↛ 616line 613 didn't jump to line 616 because the condition on line 613 was always true

614 reserved_share_from_snapshot_percentage = reserved_share_percentage 

615 

616 reserved_share_extend_percentage = self.configuration.safe_get( 

617 'reserved_share_extend_percentage') 

618 if reserved_share_extend_percentage is None: 618 ↛ 621line 618 didn't jump to line 621 because the condition on line 618 was always true

619 reserved_share_extend_percentage = reserved_share_percentage 

620 

621 stats = { 

622 'share_backend_name': backend_name, 

623 'driver_handles_share_servers': self.driver_handles_share_servers, 

624 'vendor_name': 'HPE', 

625 'driver_version': self.VERSION, 

626 'storage_protocol': 'NFS_CIFS', 

627 'total_capacity_gb': 0, 

628 'free_capacity_gb': 0, 

629 'provisioned_capacity_gb': 0, 

630 'reserved_percentage': reserved_share_percentage, 

631 'reserved_snapshot_percentage': 

632 reserved_share_from_snapshot_percentage, 

633 'reserved_share_extend_percentage': 

634 reserved_share_extend_percentage, 

635 'max_over_subscription_ratio': max_over_subscription_ratio, 

636 'qos': False, 

637 'thin_provisioning': True, # 3PAR default is thin 

638 } 

639 

640 if not self._hpe3par: 

641 LOG.info( 

642 "Skipping capacity and capabilities update. Setup has not " 

643 "completed.") 

644 else: 

645 for fpg in self.fpgs: 

646 fpg_status = self._hpe3par.get_fpg_status(fpg) 

647 fpg_status['reserved_percentage'] = reserved_share_percentage 

648 fpg_status['reserved_snapshot_percentage'] = ( 

649 reserved_share_from_snapshot_percentage) 

650 fpg_status['reserved_share_extend_percentage'] = ( 

651 reserved_share_extend_percentage) 

652 LOG.debug("FPG status = %s.", fpg_status) 

653 stats.setdefault('pools', []).append(fpg_status) 

654 

655 super(HPE3ParShareDriver, self)._update_share_stats(stats)