Coverage for manila/share/drivers/inspur/as13000/as13000_nas.py: 96%

497 statements  

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

1# Copyright 2018 Inspur Corp. 

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 

16""" 

17Share driver for Inspur AS13000 

18""" 

19 

20import functools 

21import json 

22import re 

23import requests 

24import time 

25 

26from oslo_config import cfg 

27from oslo_log import log as logging 

28from oslo_utils import units 

29 

30from manila import exception 

31from manila.i18n import _ 

32from manila.share import driver 

33from manila.share import utils as share_utils 

34 

35 

36inspur_as13000_opts = [ 

37 cfg.HostAddressOpt( 

38 'as13000_nas_ip', 

39 required=True, 

40 help='IP address for the AS13000 storage.'), 

41 cfg.PortOpt( 

42 'as13000_nas_port', 

43 default=8088, 

44 help='Port number for the AS13000 storage.'), 

45 cfg.StrOpt( 

46 'as13000_nas_login', 

47 required=True, 

48 help='Username for the AS13000 storage'), 

49 cfg.StrOpt( 

50 'as13000_nas_password', 

51 required=True, 

52 secret=True, 

53 help='Password for the AS13000 storage'), 

54 cfg.ListOpt( 

55 'as13000_share_pools', 

56 required=True, 

57 help='The Storage Pools Manila should use, a comma separated list'), 

58 cfg.IntOpt( 

59 'as13000_token_available_time', 

60 default=3600, 

61 help='The effective time of token validity in seconds.') 

62] 

63 

64CONF = cfg.CONF 

65CONF.register_opts(inspur_as13000_opts) 

66LOG = logging.getLogger(__name__) 

67 

68 

69def inspur_driver_debug_trace(f): 

70 """Log the method entrance and exit including active backend name. 

71 

72 This should only be used on Share_Driver class methods. It depends on 

73 having a 'self' argument that is a AS13000_Driver. 

74 """ 

75 @functools.wraps(f) 

76 def wrapper(*args, **kwargs): 

77 driver = args[0] 

78 cls_name = driver.__class__.__name__ 

79 method_name = "%(cls_name)s.%(method)s" % {"cls_name": cls_name, 

80 "method": f.__name__} 

81 backend_name = driver.configuration.share_backend_name 

82 LOG.debug("[%(backend_name)s] Enter %(method_name)s", 

83 {"method_name": method_name, "backend_name": backend_name}) 

84 result = f(*args, **kwargs) 

85 LOG.debug("[%(backend_name)s] Leave %(method_name)s", 

86 {"method_name": method_name, "backend_name": backend_name}) 

87 return result 

88 

89 return wrapper 

90 

91 

92class RestAPIExecutor(object): 

93 def __init__(self, hostname, port, username, password): 

94 self._hostname = hostname 

95 self._port = port 

96 self._username = username 

97 self._password = password 

98 self._token_pool = [] 

99 self._token_size = 1 

100 

101 def logins(self): 

102 """login the AS13000 and store the token in token_pool""" 

103 times = self._token_size 

104 while times > 0: 

105 token = self.login() 

106 self._token_pool.append(token) 

107 times = times - 1 

108 LOG.debug('Logged into the AS13000.') 

109 

110 def login(self): 

111 """login in the AS13000 and return the token""" 

112 method = 'security/token' 

113 params = {'name': self._username, 'password': self._password} 

114 token = self.send_rest_api(method=method, params=params, 

115 request_type='post').get('token') 

116 return token 

117 

118 def logout(self): 

119 method = 'security/token' 

120 self.send_rest_api(method=method, request_type='delete') 

121 

122 def refresh_token(self, force=False): 

123 if force is True: 

124 for i in range(self._token_size): 

125 self._token_pool = [] 

126 token = self.login() 

127 self._token_pool.append(token) 

128 else: 

129 for i in range(self._token_size): 

130 self.logout() 

131 token = self.login() 

132 self._token_pool.append(token) 

133 LOG.debug('Tokens have been refreshed.') 

134 

135 def send_rest_api(self, method, params=None, request_type='post'): 

136 attempts = 3 

137 msge = '' 

138 while attempts > 0: 

139 attempts -= 1 

140 try: 

141 return self.send_api(method, params, request_type) 

142 except exception.NetworkException as e: 

143 msge = str(e) 

144 LOG.error(msge) 

145 

146 self.refresh_token(force=True) 

147 time.sleep(1) 

148 except exception.ShareBackendException as e: 

149 msge = str(e) 

150 break 

151 

152 msg = (_('Access RestAPI /rest/%(method)s by %(type)s failed,' 

153 ' error: %(msge)s') % {'method': method, 

154 'msge': msge, 

155 'type': request_type}) 

156 LOG.error(msg) 

157 raise exception.ShareBackendException(msg) 

158 

159 @staticmethod 

160 def do_request(cmd, url, header, data): 

161 LOG.debug('CMD: %(cmd)s, URL: %(url)s, DATA: %(data)s', 

162 {'cmd': cmd, 'url': url, 'data': data}) 

163 if cmd == 'post': 

164 req = requests.post(url, 

165 data=data, 

166 headers=header) 

167 elif cmd == 'get': 

168 req = requests.get(url, 

169 data=data, 

170 headers=header) 

171 elif cmd == 'put': 

172 req = requests.put(url, 

173 data=data, 

174 headers=header) 

175 elif cmd == 'delete': 175 ↛ 180line 175 didn't jump to line 180 because the condition on line 175 was always true

176 req = requests.delete(url, 

177 data=data, 

178 headers=header) 

179 else: 

180 msg = (_('Unsupported cmd: %s') % cmd) 

181 raise exception.ShareBackendException(msg) 

182 

183 response = req.json() 

184 code = req.status_code 

185 LOG.debug('CODE: %(code)s, RESPONSE: %(response)s', 

186 {'code': code, 'response': response}) 

187 

188 if code != 200: 188 ↛ 189line 188 didn't jump to line 189 because the condition on line 188 was never true

189 msg = (_('Code: %(code)s, URL: %(url)s, Message: %(msg)s') 

190 % {'code': req.status_code, 

191 'url': req.url, 

192 'msg': req.text}) 

193 LOG.error(msg) 

194 raise exception.NetworkException(msg) 

195 

196 return response 

197 

198 def send_api(self, method, params=None, request_type='post'): 

199 if params: 

200 params = json.dumps(params) 

201 

202 url = ('http://%(hostname)s:%(port)s/%(rest)s/%(method)s' 

203 % {'hostname': self._hostname, 

204 'port': self._port, 

205 'rest': 'rest', 

206 'method': method}) 

207 

208 # header is not needed when the driver login the backend 

209 if method == 'security/token': 

210 # token won't be return to the token_pool 

211 if request_type == 'delete': 

212 header = {'X-Auth-Token': self._token_pool.pop(0)} 

213 else: 

214 header = None 

215 else: 

216 if len(self._token_pool) == 0: 216 ↛ 217line 216 didn't jump to line 217 because the condition on line 216 was never true

217 self.logins() 

218 token = self._token_pool.pop(0) 

219 header = {'X-Auth-Token': token} 

220 self._token_pool.append(token) 

221 

222 response = self.do_request(request_type, url, header, params) 

223 

224 try: 

225 code = response.get('code') 

226 if code == 0: 

227 if request_type == 'get': 

228 data = response.get('data') 

229 else: 

230 if method == 'security/token': 

231 data = response.get('data') 

232 else: 

233 data = response.get('message') 

234 data = str(data).lower() 

235 if hasattr(data, 'success'): 235 ↛ 236line 235 didn't jump to line 236 because the condition on line 235 was never true

236 return 

237 elif code == 301: 

238 msg = _('Token is expired') 

239 LOG.error(msg) 

240 raise exception.NetworkException(msg) 

241 else: 

242 message = response.get('message') 

243 msg = (_('Unexpected RestAPI response: %(code)d %(msg)s') % { 

244 'code': code, 'msg': message}) 

245 LOG.error(msg) 

246 raise exception.ShareBackendException(msg) 

247 except ValueError: 

248 msg = _("Deal with response failed") 

249 raise exception.ShareBackendException(msg) 

250 

251 return data 

252 

253 

254class AS13000ShareDriver(driver.ShareDriver): 

255 

256 """AS13000 Share Driver 

257 

258 Version history: 

259 V1.0.0: Initial version 

260 Driver support: 

261 share create/delete, 

262 snapshot create/delete, 

263 extend size, 

264 create_share_from_snapshot, 

265 update_access. 

266 protocol: NFS/CIFS 

267 

268 """ 

269 

270 VENDOR = 'INSPUR' 

271 VERSION = '1.0.0' 

272 PROTOCOL = 'NFS_CIFS' 

273 

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

275 super(AS13000ShareDriver, self).__init__(False, *args, **kwargs) 

276 self.configuration.append_config_values(inspur_as13000_opts) 

277 self.hostname = self.configuration.as13000_nas_ip 

278 self.port = self.configuration.as13000_nas_port 

279 self.username = self.configuration.as13000_nas_login 

280 self.password = self.configuration.as13000_nas_password 

281 self.token_available_time = (self.configuration. 

282 as13000_token_available_time) 

283 self.pools = self.configuration.as13000_share_pools 

284 # base dir detail contain the information which we will use 

285 # when we create subdirectorys 

286 self.base_dir_detail = None 

287 self._token_time = 0 

288 self.ips = [] 

289 self._rest = RestAPIExecutor(self.hostname, self.port, 

290 self.username, self.password) 

291 

292 @inspur_driver_debug_trace 

293 def do_setup(self, context): 

294 # get access tokens 

295 self._rest.logins() 

296 self._token_time = time.time() 

297 

298 # Check the pool in conf exist in the backend 

299 self._validate_pools_exist() 

300 

301 # get the base directory detail 

302 self.base_dir_detail = self._get_directory_detail(self.pools[0]) 

303 

304 # get all backend node ip 

305 self.ips = self._get_nodes_ips() 

306 

307 @inspur_driver_debug_trace 

308 def check_for_setup_error(self): 

309 if self.base_dir_detail is None: 

310 msg = _('The pool status is not right') 

311 raise exception.ShareBackendException(msg) 

312 

313 if len(self.ips) == 0: 313 ↛ exitline 313 didn't return from function 'check_for_setup_error' because the condition on line 313 was always true

314 msg = _('All backend nodes are down') 

315 raise exception.ShareBackendException(msg) 

316 

317 @inspur_driver_debug_trace 

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

319 """Create a share.""" 

320 pool, name, size, proto = self._get_share_instance_pnsp(share) 

321 

322 # create directory first 

323 share_path = self._create_directory(share_name=name, 

324 pool_name=pool) 

325 

326 # then create nfs or cifs share 

327 if proto == 'nfs': 

328 self._create_nfs_share(share_path=share_path) 

329 else: 

330 self._create_cifs_share(share_name=name, 

331 share_path=share_path) 

332 

333 # finally we set the quota of directory 

334 self._set_directory_quota(share_path, size) 

335 

336 locations = self._get_location_path(name, share_path, proto) 

337 LOG.debug('Create share: name:%(name)s' 

338 ' protocol:%(proto)s,location: %(loc)s', 

339 {'name': name, 'proto': proto, 'loc': locations}) 

340 return locations 

341 

342 @inspur_driver_debug_trace 

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

344 share_server=None, parent_share=None): 

345 """Create a share from snapshot.""" 

346 pool, name, size, proto = self._get_share_instance_pnsp(share) 

347 

348 # create directory first 

349 share_path = self._create_directory(share_name=name, 

350 pool_name=pool) 

351 

352 # as quota must be set when directory is empty 

353 # then we set the quota of directory 

354 self._set_directory_quota(share_path, size) 

355 

356 # and next clone snapshot to dest_path 

357 self._clone_directory_to_dest(snapshot=snapshot, dest_path=share_path) 

358 

359 # finally create share 

360 if proto == 'nfs': 

361 self._create_nfs_share(share_path=share_path) 

362 else: 

363 self._create_cifs_share(share_name=name, 

364 share_path=share_path) 

365 

366 locations = self._get_location_path(name, share_path, proto) 

367 LOG.debug('Create share from snapshot:' 

368 ' name:%(name)s protocol:%(proto)s,location: %(loc)s', 

369 {'name': name, 'proto': proto, 'loc': locations}) 

370 return locations 

371 

372 @inspur_driver_debug_trace 

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

374 """Delete share.""" 

375 pool, name, _, proto = self._get_share_instance_pnsp(share) 

376 share_path = self._generate_share_path(pool, name) 

377 if proto == 'nfs': 

378 share_backend = self._get_nfs_share(share_path) 

379 if len(share_backend) == 0: 

380 return 

381 else: 

382 self._delete_nfs_share(share_path) 

383 else: 

384 share_backend = self._get_cifs_share(name) 

385 if len(share_backend) == 0: 

386 return 

387 else: 

388 self._delete_cifs_share(name) 

389 self._delete_directory(share_path) 

390 LOG.debug('Delete share: %s', name) 

391 

392 @inspur_driver_debug_trace 

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

394 """extend share to new size""" 

395 pool, name, size, proto = self._get_share_instance_pnsp(share) 

396 share_path = self._generate_share_path(pool, name) 

397 self._set_directory_quota(share_path, new_size) 

398 LOG.debug('extend share %(name)s to new size %(size)s GB', 

399 {'name': name, 'size': new_size}) 

400 

401 @inspur_driver_debug_trace 

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

403 """Ensure that share is exported.""" 

404 pool, name, size, proto = self._get_share_instance_pnsp(share) 

405 share_path = self._generate_share_path(pool, name) 

406 

407 if proto == 'nfs': 

408 share_backend = self._get_nfs_share(share_path) 

409 elif proto == 'cifs': 

410 share_backend = self._get_cifs_share(name) 

411 else: 

412 msg = (_('Invalid NAS protocol supplied: %s.') % proto) 

413 LOG.error(msg) 

414 raise exception.InvalidInput(msg) 

415 

416 if len(share_backend) == 0: 

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

418 

419 return self._get_location_path(name, share_path, proto) 

420 

421 @inspur_driver_debug_trace 

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

423 """create snapshot of share""" 

424 # !!! Attention the share property is a ShareInstance 

425 share = snapshot['share'] 

426 pool, share_name, _, _ = self._get_share_instance_pnsp(share) 

427 share_path = self._generate_share_path(pool, share_name) 

428 

429 snap_name = self._generate_snapshot_name(snapshot) 

430 

431 method = 'snapshot/directory' 

432 request_type = 'post' 

433 params = {'path': share_path, 'snapName': snap_name} 

434 self._rest.send_rest_api(method=method, 

435 params=params, 

436 request_type=request_type) 

437 LOG.debug('Create snapshot %(snap)s for share %(share)s', 

438 {'snap': snap_name, 'share': share_name}) 

439 

440 @inspur_driver_debug_trace 

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

442 """delete snapshot of share""" 

443 # !!! Attention the share property is a ShareInstance 

444 share = snapshot['share'] 

445 pool, share_name, _, _ = self._get_share_instance_pnsp(share) 

446 share_path = self._generate_share_path(pool, share_name) 

447 

448 # if there are no snapshot exist, driver will return directly 

449 snaps_backend = self._get_snapshots_from_share(share_path) 

450 if len(snaps_backend) == 0: 

451 return 

452 

453 snap_name = self._generate_snapshot_name(snapshot) 

454 

455 method = ('snapshot/directory?path=%s&snapName=%s' 

456 % (share_path, snap_name)) 

457 request_type = 'delete' 

458 self._rest.send_rest_api(method=method, request_type=request_type) 

459 LOG.debug('Delete snapshot %(snap)s of share %(share)s', 

460 {'snap': snap_name, 'share': share_name}) 

461 

462 @staticmethod 

463 def transfer_rule_to_client(proto, rule): 

464 """transfer manila access rule to backend client""" 

465 access_level = rule['access_level'] 

466 if proto == 'cifs' and access_level == 'rw': 

467 access_level = 'rwx' 

468 return dict(name=rule['access_to'], 

469 type=(0 if proto == 'nfs' else 1), 

470 authority=access_level) 

471 

472 @inspur_driver_debug_trace 

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

474 delete_rules, update_rules, share_server=None): 

475 """update access of share""" 

476 pool, share_name, _, proto = self._get_share_instance_pnsp(share) 

477 share_path = self._generate_share_path(pool, share_name) 

478 

479 method = 'file/share/%s' % proto 

480 request_type = 'put' 

481 params = { 

482 'path': share_path, 

483 'addedClientList': [], 

484 'deletedClientList': [], 

485 'editedClientList': [] 

486 } 

487 

488 if proto == 'nfs': 

489 share_backend = self._get_nfs_share(share_path) 

490 params['pathAuthority'] = share_backend['pathAuthority'] 

491 else: 

492 params['name'] = share_name 

493 

494 if add_rules or delete_rules: 

495 to_add_clients = [self.transfer_rule_to_client(proto, rule) 

496 for rule in add_rules] 

497 params['addedClientList'] = to_add_clients 

498 to_del_clients = [self.transfer_rule_to_client(proto, rule) 

499 for rule in delete_rules] 

500 params['deletedClientList'] = to_del_clients 

501 else: 

502 access_clients = [self.transfer_rule_to_client(proto, rule) 

503 for rule in access_rules] 

504 params['addedClientList'] = access_clients 

505 self._clear_access(share) 

506 

507 self._rest.send_rest_api(method=method, 

508 params=params, 

509 request_type=request_type) 

510 LOG.debug('complete the update access work for share %s', share_name) 

511 

512 @inspur_driver_debug_trace 

513 def _update_share_stats(self, data=None): 

514 """update the backend stats including driver info and pools info""" 

515 # Do a check of the token validity each time we update share stats, 

516 # do a refresh if token already expires 

517 time_difference = time.time() - self._token_time 

518 if time_difference > self.token_available_time: 

519 self._rest.refresh_token() 

520 self._token_time = time.time() 

521 LOG.debug('Token of Driver has been refreshed') 

522 

523 data = { 

524 'vendor_name': self.VENDOR, 

525 'driver_version': self.VERSION, 

526 'storage_protocol': self.PROTOCOL, 

527 'share_backend_name': 

528 self.configuration.safe_get('share_backend_name'), 

529 'snapshot_support': True, 

530 'create_share_from_snapshot_support': True, 

531 'pools': [self._get_pool_stats(pool) for pool in self.pools] 

532 } 

533 

534 super(AS13000ShareDriver, self)._update_share_stats(data) 

535 

536 @inspur_driver_debug_trace 

537 def _clear_access(self, share): 

538 """clear all access of share""" 

539 pool, share_name, size, proto = self._get_share_instance_pnsp(share) 

540 share_path = self._generate_share_path(pool, share_name) 

541 

542 method = 'file/share/%s' % proto 

543 request_type = 'put' 

544 params = { 

545 'path': share_path, 

546 'addedClientList': [], 

547 'deletedClientList': [], 

548 'editedClientList': [] 

549 } 

550 

551 if proto == 'nfs': 

552 share_backend = self._get_nfs_share(share_path) 

553 params['deletedClientList'] = share_backend['clientList'] 

554 params['pathAuthority'] = share_backend['pathAuthority'] 

555 else: 

556 share_backend = self._get_cifs_share(share_name) 

557 params['deletedClientList'] = share_backend['userList'] 

558 params['name'] = share_name 

559 

560 self._rest.send_rest_api(method=method, 

561 params=params, 

562 request_type=request_type) 

563 LOG.debug('Clear all the access of share %s', share_name) 

564 

565 @inspur_driver_debug_trace 

566 def _validate_pools_exist(self): 

567 """Check the pool in conf exist in the backend""" 

568 available_pools = self._get_directory_list('/') 

569 for pool in self.pools: 

570 if pool not in available_pools: 

571 msg = (_('Pool %s is not exist in backend storage.') % pool) 

572 LOG.error(msg) 

573 raise exception.InvalidInput(reason=msg) 

574 

575 @inspur_driver_debug_trace 

576 def _get_directory_quota(self, path): 

577 """get the quota of directory""" 

578 method = 'file/quota/directory?path=/%s' % path 

579 request_type = 'get' 

580 data = self._rest.send_rest_api(method=method, 

581 request_type=request_type) 

582 quota = data.get('hardthreshold') 

583 if quota is None: 

584 # the method of '_update_share_stats' will check quota of pools. 

585 # To avoid return NONE for pool info, so raise this exception 

586 msg = (_(r'Quota of pool: /%s is not set, ' 

587 r'please set it in GUI of AS13000') % path) 

588 LOG.error(msg) 

589 raise exception.ShareBackendException(msg=msg) 

590 

591 hardunit = data.get('hardunit') 

592 used_capacity = data.get('capacity') 

593 used_capacity = (str(used_capacity)).upper() 

594 used_capacity = self._unit_convert(used_capacity) 

595 

596 if hardunit == 1: 

597 quota = quota * 1024 

598 total_capacity = int(quota) 

599 used_capacity = int(used_capacity) 

600 return total_capacity, used_capacity 

601 

602 def _get_pool_stats(self, path): 

603 """Get the stats of pools, such as capacity and other information.""" 

604 

605 total_capacity, used_capacity = self._get_directory_quota(path) 

606 free_capacity = total_capacity - used_capacity 

607 

608 pool = { 

609 'pool_name': path, 

610 'reserved_percentage': 

611 self.configuration.reserved_share_percentage, 

612 'reserved_snapshot_percentage': 

613 self.configuration.reserved_share_from_snapshot_percentage 

614 or self.configuration.reserved_share_percentage, 

615 'reserved_share_extend_percentage': 

616 self.configuration.reserved_share_extend_percentage 

617 or self.configuration.reserved_share_percentage, 

618 'max_over_subscription_ratio': 

619 self.configuration.max_over_subscription_ratio, 

620 'dedupe': False, 

621 'compression': False, 

622 'qos': False, 

623 'thin_provisioning': True, 

624 'total_capacity_gb': total_capacity, 

625 'free_capacity_gb': free_capacity, 

626 'allocated_capacity_gb': used_capacity, 

627 'snapshot_support': True, 

628 'create_share_from_snapshot_support': True 

629 } 

630 

631 return pool 

632 

633 @inspur_driver_debug_trace 

634 def _get_directory_list(self, path): 

635 """Get all the directory list of target path""" 

636 method = 'file/directory?path=%s' % path 

637 request_type = 'get' 

638 directory_list = self._rest.send_rest_api(method=method, 

639 request_type=request_type) 

640 dir_list = [] 

641 for directory in directory_list: 

642 dir_list.append(directory['name']) 

643 return dir_list 

644 

645 @inspur_driver_debug_trace 

646 def _create_directory(self, share_name, pool_name): 

647 """create a directory for share""" 

648 

649 method = 'file/directory' 

650 request_type = 'post' 

651 params = {'name': share_name, 

652 'parentPath': self.base_dir_detail['path'], 

653 'authorityInfo': self.base_dir_detail['authorityInfo'], 

654 'dataProtection': self.base_dir_detail['dataProtection'], 

655 'poolName': self.base_dir_detail['poolName']} 

656 self._rest.send_rest_api(method=method, 

657 params=params, 

658 request_type=request_type) 

659 

660 return self._generate_share_path(pool_name, share_name) 

661 

662 @inspur_driver_debug_trace 

663 def _delete_directory(self, share_path): 

664 """delete the directory when delete share""" 

665 method = 'file/directory?path=%s' % share_path 

666 request_type = 'delete' 

667 self._rest.send_rest_api(method=method, request_type=request_type) 

668 

669 @inspur_driver_debug_trace 

670 def _set_directory_quota(self, share_path, quota): 

671 """set directory quota for share""" 

672 method = 'file/quota/directory' 

673 request_type = 'put' 

674 params = {'path': share_path, 'hardthreshold': quota, 'hardunit': 2} 

675 self._rest.send_rest_api(method=method, 

676 params=params, 

677 request_type=request_type) 

678 

679 @inspur_driver_debug_trace 

680 def _create_nfs_share(self, share_path): 

681 """create a NFS share""" 

682 method = 'file/share/nfs' 

683 request_type = 'post' 

684 params = {'path': share_path, 'pathAuthority': 'rw', 'client': []} 

685 self._rest.send_rest_api(method=method, 

686 params=params, 

687 request_type=request_type) 

688 

689 @inspur_driver_debug_trace 

690 def _delete_nfs_share(self, share_path): 

691 """Delete the NFS share""" 

692 method = 'file/share/nfs?path=%s' % share_path 

693 request_type = 'delete' 

694 self._rest.send_rest_api(method=method, request_type=request_type) 

695 

696 @inspur_driver_debug_trace 

697 def _get_nfs_share(self, share_path): 

698 """Get the nfs share in backend""" 

699 method = 'file/share/nfs?path=%s' % share_path 

700 request_type = 'get' 

701 share_backend = self._rest.send_rest_api(method=method, 

702 request_type=request_type) 

703 return share_backend 

704 

705 @inspur_driver_debug_trace 

706 def _create_cifs_share(self, share_name, share_path): 

707 """Create a CIFS share.""" 

708 method = 'file/share/cifs' 

709 request_type = 'post' 

710 params = {'path': share_path, 

711 'name': share_name, 

712 'userlist': []} 

713 self._rest.send_rest_api(method=method, 

714 params=params, 

715 request_type=request_type) 

716 

717 @inspur_driver_debug_trace 

718 def _delete_cifs_share(self, share_name): 

719 """Delete the CIFS share.""" 

720 method = 'file/share/cifs?name=%s' % share_name 

721 request_type = 'delete' 

722 self._rest.send_rest_api(method=method, request_type=request_type) 

723 

724 @inspur_driver_debug_trace 

725 def _get_cifs_share(self, share_name): 

726 """Get the CIFS share in backend""" 

727 method = 'file/share/cifs?name=%s' % share_name 

728 request_type = 'get' 

729 share_backend = self._rest.send_rest_api(method=method, 

730 request_type=request_type) 

731 return share_backend 

732 

733 @inspur_driver_debug_trace 

734 def _clone_directory_to_dest(self, snapshot, dest_path): 

735 """Clone the directory to the new directory""" 

736 # get the origin share name of the snapshot 

737 share_instance = snapshot['share_instance'] 

738 pool, name, _, _ = self._get_share_instance_pnsp(share_instance) 

739 share_path = self._generate_share_path(pool, name) 

740 

741 # get the snapshot instance name 

742 snap_name = self._generate_snapshot_name(snapshot) 

743 

744 method = 'snapshot/directory/clone' 

745 request_type = 'post' 

746 params = {'path': share_path, 

747 'snapName': snap_name, 

748 'destPath': dest_path} 

749 self._rest.send_rest_api(method=method, 

750 params=params, 

751 request_type=request_type) 

752 LOG.debug('Clone Path: %(path)s Snapshot: %(snap)s to Path %(dest)s', 

753 {'path': share_path, 'snap': snap_name, 'dest': dest_path}) 

754 

755 @inspur_driver_debug_trace 

756 def _get_snapshots_from_share(self, path): 

757 """get all the snapshot of share""" 

758 method = 'snapshot/directory?path=%s' % path 

759 request_type = 'get' 

760 snaps = self._rest.send_rest_api(method=method, 

761 request_type=request_type) 

762 return snaps 

763 

764 @inspur_driver_debug_trace 

765 def _get_location_path(self, share_name, share_path, share_proto): 

766 """return all the location of all nodes""" 

767 if share_proto == 'nfs': 

768 location = [ 

769 {'path': r'%(ip)s:%(share_path)s' 

770 % {'ip': ip, 'share_path': share_path}} 

771 for ip in self.ips] 

772 else: 

773 location = [ 

774 {'path': r'\\%(ip)s\%(share_name)s' 

775 % {'ip': ip, 'share_name': share_name}} 

776 for ip in self.ips] 

777 

778 return location 

779 

780 def _get_nodes_virtual_ips(self): 

781 """Get the virtual ip list of the node""" 

782 method = 'ctdb/set' 

783 request_type = 'get' 

784 ctdb_set = self._rest.send_rest_api(method=method, 

785 request_type=request_type) 

786 virtual_ips = [] 

787 for vip in ctdb_set['virtualIpList']: 

788 ip = vip['ip'].split('/')[0] 

789 virtual_ips.append(ip) 

790 return virtual_ips 

791 

792 def _get_nodes_physical_ips(self): 

793 """Get the physical ip of all the backend nodes""" 

794 method = 'cluster/node/cache' 

795 request_type = 'get' 

796 cached_nodes = self._rest.send_rest_api(method=method, 

797 request_type=request_type) 

798 node_ips = [] 

799 for node in cached_nodes: 

800 if node['runningStatus'] == 1 and node['healthStatus'] == 1: 

801 node_ips.append(node['nodeIp']) 

802 

803 return node_ips 

804 

805 def _get_nodes_ips(self): 

806 """Return both the physical ip and virtual ip""" 

807 virtual_ips = self._get_nodes_virtual_ips() 

808 physical_ips = self._get_nodes_physical_ips() 

809 

810 return virtual_ips + physical_ips 

811 

812 def _get_share_instance_pnsp(self, share_instance): 

813 """Get pool, name, size, proto information of a share instance. 

814 

815 AS13000 require all the names can only consist of letters,numbers, 

816 and undercores,and must begin with a letter. 

817 Also the length of name must less than 32 character. 

818 The driver will use the ID as the name in backend, 

819 add 'share_' to the beginning,and convert '-' to '_' 

820 """ 

821 pool = share_utils.extract_host(share_instance['host'], level='pool') 

822 name = self._generate_share_name(share_instance) 

823 # a share instance may not contain size attr. 

824 try: 

825 size = share_instance['size'] 

826 except AttributeError: 

827 size = None 

828 

829 # a share instance may not contain proto attr. 

830 try: 

831 proto = share_instance['share_proto'].lower() 

832 except AttributeError: 

833 proto = None 

834 

835 LOG.debug("Pool %s, Name: %s, Size: %s, Protocol: %s", 

836 pool, name, size, proto) 

837 

838 return pool, name, size, proto 

839 

840 def _unit_convert(self, capacity): 

841 """Convert all units to GB""" 

842 capacity = str(capacity) 

843 capacity = capacity.upper() 

844 try: 

845 unit_of_used = re.findall(r'[A-Z]', capacity) 

846 unit_of_used = ''.join(unit_of_used) 

847 except BaseException: 

848 unit_of_used = '' 

849 capacity = capacity.replace(unit_of_used, '') 

850 capacity = float(capacity.replace(unit_of_used, '')) 

851 if unit_of_used in ['B', '']: 

852 capacity = capacity / units.Gi 

853 elif unit_of_used in ['K', 'KB']: 

854 capacity = capacity / units.Mi 

855 elif unit_of_used in ['M', 'MB']: 

856 capacity = capacity / units.Ki 

857 elif unit_of_used in ['G', 'GB']: 

858 capacity = capacity 

859 elif unit_of_used in ['T', 'TB']: 859 ↛ 861line 859 didn't jump to line 861 because the condition on line 859 was always true

860 capacity = capacity * units.Ki 

861 elif unit_of_used in ['E', 'EB']: 

862 capacity = capacity * units.Mi 

863 

864 capacity = '%.0f' % capacity 

865 return float(capacity) 

866 

867 def _format_name(self, name): 

868 """format name to meet the backend requirements""" 

869 name = name[0:32] 

870 name = name.replace('-', '_') 

871 return name 

872 

873 def _generate_share_name(self, share_instance): 

874 share_name = 'share_%s' % share_instance['id'] 

875 return self._format_name(share_name) 

876 

877 def _generate_snapshot_name(self, snapshot_instance): 

878 snap_name = 'snap_%s' % snapshot_instance['id'] 

879 return self._format_name(snap_name) 

880 

881 @staticmethod 

882 def _generate_share_path(pool, share_name): 

883 return r'/%s/%s' % (pool, share_name) 

884 

885 def _get_directory_detail(self, directory): 

886 method = 'file/directory/detail?path=/%s' % directory 

887 request_type = 'get' 

888 details = self._rest.send_rest_api(method=method, 

889 request_type=request_type) 

890 return details[0]