Coverage for manila/share/drivers/ibm/gpfs.py: 94%

719 statements  

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

1# Copyright 2014 IBM Corp. 

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""" 

15GPFS Driver for shares. 

16 

17Config Requirements: 

18 GPFS file system must have quotas enabled (`mmchfs -Q yes`). 

19Notes: 

20 GPFS independent fileset is used for each share. 

21 

22TODO(nileshb): add support for share server creation/deletion/handling. 

23 

24Limitation: 

25 While using remote GPFS node, with Ganesha NFS, 'gpfs_ssh_private_key' 

26 for remote login to the GPFS node must be specified and there must be 

27 a passwordless authentication already setup between the Manila share 

28 service and the remote GPFS node. 

29 

30""" 

31import abc 

32import math 

33import os 

34import re 

35import shlex 

36import socket 

37 

38from oslo_config import cfg 

39from oslo_log import log 

40from oslo_utils import excutils 

41from oslo_utils import importutils 

42from oslo_utils import strutils 

43from oslo_utils import units 

44 

45from manila.common import constants 

46from manila import exception 

47from manila.i18n import _ 

48from manila.share import driver 

49from manila.share.drivers.helpers import NFSHelper 

50from manila.share import share_types 

51from manila import ssh_utils 

52from manila import utils 

53 

54LOG = log.getLogger(__name__) 

55 

56# matches multiple comma separated avpairs on a line. values with an embedded 

57# comma must be wrapped in quotation marks 

58AVPATTERN = re.compile(r'\s*(?P<attr>\w+)\s*=\s*(?P<val>' 

59 r'(["][a-zA-Z0-9_, ]+["])|(\w+))\s*[,]?') 

60 

61ERR_FILE_NOT_FOUND = 2 

62 

63gpfs_share_opts = [ 

64 cfg.HostAddressOpt('gpfs_share_export_ip', 

65 help='IP to be added to GPFS export string.'), 

66 cfg.StrOpt('gpfs_mount_point_base', 

67 default='$state_path/mnt', 

68 help='Base folder where exported shares are located.'), 

69 cfg.StrOpt('gpfs_nfs_server_type', 

70 default='CES', 

71 help=('NFS Server type. Valid choices are "CES" (Ganesha NFS) ' 

72 'or "KNFS" (Kernel NFS).')), 

73 cfg.ListOpt('gpfs_nfs_server_list', 

74 help=('A list of the fully qualified NFS server names that ' 

75 'make up the OpenStack Manila configuration.')), 

76 cfg.BoolOpt('is_gpfs_node', 

77 default=False, 

78 help=('True:when Manila services are running on one of the ' 

79 'Spectrum Scale node. ' 

80 'False:when Manila services are not running on any of ' 

81 'the Spectrum Scale node.')), 

82 cfg.PortOpt('gpfs_ssh_port', 

83 default=22, 

84 help='GPFS server SSH port.'), 

85 cfg.StrOpt('gpfs_ssh_login', 

86 help='GPFS server SSH login name.'), 

87 cfg.StrOpt('gpfs_ssh_password', 

88 secret=True, 

89 help='GPFS server SSH login password. ' 

90 'The password is not needed, if \'gpfs_ssh_private_key\' ' 

91 'is configured.'), 

92 cfg.StrOpt('gpfs_ssh_private_key', 

93 help='Path to GPFS server SSH private key for login.'), 

94 cfg.ListOpt('gpfs_share_helpers', 

95 default=[ 

96 'KNFS=manila.share.drivers.ibm.gpfs.KNFSHelper', 

97 'CES=manila.share.drivers.ibm.gpfs.CESHelper', 

98 ], 

99 help='Specify list of share export helpers.'), 

100] 

101 

102 

103CONF = cfg.CONF 

104CONF.register_opts(gpfs_share_opts) 

105 

106 

107class GPFSShareDriver(driver.ExecuteMixin, driver.GaneshaMixin, 

108 driver.ShareDriver): 

109 

110 """GPFS Share Driver. 

111 

112 Executes commands relating to Shares. 

113 Supports creation of shares on a GPFS cluster. 

114 

115 API version history: 

116 

117 1.0 - Initial version. 

118 1.1 - Added extend_share functionality 

119 2.0 - Added CES support for NFS Ganesha 

120 """ 

121 

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

123 """Do initialization.""" 

124 super(GPFSShareDriver, self).__init__(False, *args, **kwargs) 

125 self._helpers = {} 

126 self.configuration.append_config_values(gpfs_share_opts) 

127 self.backend_name = self.configuration.safe_get( 

128 'share_backend_name') or "IBM Storage System" 

129 self.sshpool = None 

130 self.ssh_connections = {} 

131 self._gpfs_execute = None 

132 if self.configuration.is_gpfs_node: 132 ↛ 133line 132 didn't jump to line 133 because the condition on line 132 was never true

133 self.GPFS_PATH = '' 

134 else: 

135 self.GPFS_PATH = '/usr/lpp/mmfs/bin/' 

136 

137 def do_setup(self, context): 

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

139 super(GPFSShareDriver, self).do_setup(context) 

140 if self.configuration.is_gpfs_node: 

141 self._gpfs_execute = self._gpfs_local_execute 

142 else: 

143 self._gpfs_execute = self._gpfs_remote_execute 

144 self._setup_helpers() 

145 

146 def _gpfs_local_execute(self, *cmd, **kwargs): 

147 if 'run_as_root' not in kwargs: 147 ↛ 149line 147 didn't jump to line 149 because the condition on line 147 was always true

148 kwargs.update({'run_as_root': True}) 

149 if 'ignore_exit_code' in kwargs: 149 ↛ 154line 149 didn't jump to line 154 because the condition on line 149 was always true

150 check_exit_code = kwargs.pop('ignore_exit_code') 

151 check_exit_code.append(0) 

152 kwargs.update({'check_exit_code': check_exit_code}) 

153 

154 return utils.execute(*cmd, **kwargs) 

155 

156 def _gpfs_remote_execute(self, *cmd, **kwargs): 

157 host = self.configuration.gpfs_share_export_ip 

158 check_exit_code = kwargs.pop('check_exit_code', True) 

159 ignore_exit_code = kwargs.pop('ignore_exit_code', None) 

160 

161 return self._run_ssh(host, cmd, ignore_exit_code, check_exit_code) 

162 

163 def _sanitize_command(self, cmd_list): 

164 # pylint: disable=too-many-function-args 

165 return ' '.join(shlex.quote(cmd_arg) for cmd_arg in cmd_list) 

166 

167 def _run_ssh(self, host, cmd_list, ignore_exit_code=None, 

168 check_exit_code=True): 

169 command = self._sanitize_command(cmd_list) 

170 if not self.sshpool: 170 ↛ 187line 170 didn't jump to line 187 because the condition on line 170 was always true

171 gpfs_ssh_login = self.configuration.gpfs_ssh_login 

172 password = self.configuration.gpfs_ssh_password 

173 privatekey = self.configuration.gpfs_ssh_private_key 

174 gpfs_ssh_port = self.configuration.gpfs_ssh_port 

175 ssh_conn_timeout = self.configuration.ssh_conn_timeout 

176 min_size = self.configuration.ssh_min_pool_conn 

177 max_size = self.configuration.ssh_max_pool_conn 

178 

179 self.sshpool = ssh_utils.SSHPool(host, 

180 gpfs_ssh_port, 

181 ssh_conn_timeout, 

182 gpfs_ssh_login, 

183 password=password, 

184 privatekey=privatekey, 

185 min_size=min_size, 

186 max_size=max_size) 

187 try: 

188 with self.sshpool.item() as ssh: 

189 return self._gpfs_ssh_execute( 

190 ssh, 

191 command, 

192 ignore_exit_code=ignore_exit_code, 

193 check_exit_code=check_exit_code) 

194 

195 except Exception as e: 

196 with excutils.save_and_reraise_exception(): 

197 msg = (_('Error running SSH command: %(cmd)s. ' 

198 'Error: %(excmsg)s.') % 

199 {'cmd': command, 'excmsg': e}) 

200 LOG.error(msg) 

201 raise exception.GPFSException(msg) 

202 

203 def _gpfs_ssh_execute(self, ssh, cmd, ignore_exit_code=None, 

204 check_exit_code=True): 

205 sanitized_cmd = strutils.mask_password(cmd) 

206 LOG.debug('Running cmd (SSH): %s', sanitized_cmd) 

207 

208 stdin_stream, stdout_stream, stderr_stream = ssh.exec_command(cmd) 

209 channel = stdout_stream.channel 

210 

211 stdout = stdout_stream.read() 

212 sanitized_stdout = strutils.mask_password(stdout) 

213 stderr = stderr_stream.read() 

214 sanitized_stderr = strutils.mask_password(stderr) 

215 

216 stdin_stream.close() 

217 

218 exit_status = channel.recv_exit_status() 

219 

220 # exit_status == -1 if no exit code was returned 

221 if exit_status != -1: 

222 LOG.debug('Result was %s', exit_status) 

223 if ((check_exit_code and exit_status != 0) 223 ↛ 232line 223 didn't jump to line 232 because the condition on line 223 was always true

224 and 

225 (ignore_exit_code is None or 

226 exit_status not in ignore_exit_code)): 

227 raise exception.ProcessExecutionError(exit_code=exit_status, 

228 stdout=sanitized_stdout, 

229 stderr=sanitized_stderr, 

230 cmd=sanitized_cmd) 

231 

232 return (sanitized_stdout, sanitized_stderr) 

233 

234 def _check_gpfs_state(self): 

235 try: 

236 out, __ = self._gpfs_execute(self.GPFS_PATH + 'mmgetstate', '-Y') 

237 except exception.ProcessExecutionError as e: 

238 msg = (_('Failed to check GPFS state. Error: %(excmsg)s.') % 

239 {'excmsg': e}) 

240 LOG.error(msg) 

241 raise exception.GPFSException(msg) 

242 lines = out.splitlines() 

243 try: 

244 state_token = lines[0].split(':').index('state') 

245 gpfs_state = lines[1].split(':')[state_token] 

246 except (IndexError, ValueError) as e: 

247 msg = (_('Failed to check GPFS state. Error: %(excmsg)s.') % 

248 {'excmsg': e}) 

249 LOG.error(msg) 

250 raise exception.GPFSException(msg) 

251 if gpfs_state != 'active': 

252 return False 

253 return True 

254 

255 def _is_dir(self, path): 

256 try: 

257 output, __ = self._gpfs_execute('stat', '--format=%F', path, 

258 run_as_root=False) 

259 except exception.ProcessExecutionError as e: 

260 msg = (_('%(path)s is not a directory. Error: %(excmsg)s') % 

261 {'path': path, 'excmsg': e}) 

262 LOG.error(msg) 

263 raise exception.GPFSException(msg) 

264 

265 return output.strip() == 'directory' 

266 

267 def _is_gpfs_path(self, directory): 

268 try: 

269 self._gpfs_execute(self.GPFS_PATH + 'mmlsattr', directory) 

270 except exception.ProcessExecutionError as e: 

271 msg = (_('%(dir)s is not on GPFS filesystem. Error: %(excmsg)s.') % 

272 {'dir': directory, 'excmsg': e}) 

273 LOG.error(msg) 

274 raise exception.GPFSException(msg) 

275 

276 return True 

277 

278 def _setup_helpers(self): 

279 """Initializes protocol-specific NAS drivers.""" 

280 self._helpers = {} 

281 for helper_str in self.configuration.gpfs_share_helpers: 

282 share_proto, _, import_str = helper_str.partition('=') 

283 helper = importutils.import_class(import_str) 

284 self._helpers[share_proto.upper()] = helper(self._gpfs_execute, 

285 self.configuration) 

286 

287 def _local_path(self, sharename): 

288 """Get local path for a share or share snapshot by name.""" 

289 return os.path.join(self.configuration.gpfs_mount_point_base, 

290 sharename) 

291 

292 def _get_gpfs_device(self): 

293 fspath = self.configuration.gpfs_mount_point_base 

294 try: 

295 (out, __) = self._gpfs_execute('df', fspath) 

296 except exception.ProcessExecutionError as e: 

297 msg = (_('Failed to get GPFS device for %(fspath)s.' 

298 'Error: %(excmsg)s') % 

299 {'fspath': fspath, 'excmsg': e}) 

300 LOG.error(msg) 

301 raise exception.GPFSException(msg) 

302 

303 lines = out.splitlines() 

304 fs = lines[1].split()[0] 

305 return fs 

306 

307 def _create_share(self, shareobj): 

308 """Create a linked fileset file in GPFS. 

309 

310 Note: GPFS file system must have quotas enabled 

311 (mmchfs -Q yes). 

312 """ 

313 sharename = shareobj['name'] 

314 sizestr = '%sG' % shareobj['size'] 

315 sharepath = self._local_path(sharename) 

316 fsdev = self._get_gpfs_device() 

317 

318 # create fileset for the share, link it to root path and set max size 

319 try: 

320 self._gpfs_execute(self.GPFS_PATH + 'mmcrfileset', fsdev, 

321 sharename, '--inode-space', 'new') 

322 except exception.ProcessExecutionError as e: 

323 msg = (_('Failed to create fileset on %(fsdev)s for ' 

324 'the share %(sharename)s. Error: %(excmsg)s.') % 

325 {'fsdev': fsdev, 'sharename': sharename, 

326 'excmsg': e}) 

327 LOG.error(msg) 

328 raise exception.GPFSException(msg) 

329 

330 try: 

331 self._gpfs_execute(self.GPFS_PATH + 'mmlinkfileset', fsdev, 

332 sharename, '-J', sharepath) 

333 except exception.ProcessExecutionError as e: 

334 msg = (_('Failed to link fileset for the share %(sharename)s. ' 

335 'Error: %(excmsg)s.') % 

336 {'sharename': sharename, 'excmsg': e}) 

337 LOG.error(msg) 

338 raise exception.GPFSException(msg) 

339 

340 try: 

341 self._gpfs_execute(self.GPFS_PATH + 'mmsetquota', fsdev + ':' + 

342 sharename, '--block', '0:' + sizestr) 

343 except exception.ProcessExecutionError as e: 

344 msg = (_('Failed to set quota for the share %(sharename)s. ' 

345 'Error: %(excmsg)s.') % 

346 {'sharename': sharename, 'excmsg': e}) 

347 LOG.error(msg) 

348 raise exception.GPFSException(msg) 

349 

350 try: 

351 self._gpfs_execute('chmod', '777', sharepath) 

352 except exception.ProcessExecutionError as e: 

353 msg = (_('Failed to set permissions for share %(sharename)s. ' 

354 'Error: %(excmsg)s.') % 

355 {'sharename': sharename, 'excmsg': e}) 

356 LOG.error(msg) 

357 raise exception.GPFSException(msg) 

358 

359 def _delete_share(self, shareobj): 

360 """Remove container by removing GPFS fileset.""" 

361 sharename = shareobj['name'] 

362 fsdev = self._get_gpfs_device() 

363 # ignore error, when the fileset does not exist 

364 # it may happen, when the share creation failed, the share is in 

365 # 'error' state, and the fileset was never created 

366 # we want to ignore that error condition while deleting the fileset, 

367 # i.e. 'Fileset name share-xyz not found', with error code '2' 

368 # and mark the deletion successful 

369 ignore_exit_code = [ERR_FILE_NOT_FOUND] 

370 

371 # unlink and delete the share's fileset 

372 try: 

373 self._gpfs_execute(self.GPFS_PATH + 'mmunlinkfileset', fsdev, 

374 sharename, '-f', 

375 ignore_exit_code=ignore_exit_code) 

376 except exception.ProcessExecutionError as e: 

377 msg = (_('Failed unlink fileset for share %(sharename)s. ' 

378 'Error: %(excmsg)s.') % 

379 {'sharename': sharename, 'excmsg': e}) 

380 LOG.error(msg) 

381 raise exception.GPFSException(msg) 

382 

383 try: 

384 self._gpfs_execute(self.GPFS_PATH + 'mmdelfileset', fsdev, 

385 sharename, '-f', 

386 ignore_exit_code=ignore_exit_code) 

387 except exception.ProcessExecutionError as e: 

388 msg = (_('Failed delete fileset for share %(sharename)s. ' 

389 'Error: %(excmsg)s.') % 

390 {'sharename': sharename, 'excmsg': e}) 

391 LOG.error(msg) 

392 raise exception.GPFSException(msg) 

393 

394 def _get_available_capacity(self, path): 

395 """Calculate available space on path.""" 

396 try: 

397 out, __ = self._gpfs_execute('df', '-P', '-B', '1', path) 

398 except exception.ProcessExecutionError as e: 

399 msg = (_('Failed to check available capacity for %(path)s.' 

400 'Error: %(excmsg)s.') % 

401 {'path': path, 'excmsg': e}) 

402 LOG.error(msg) 

403 raise exception.GPFSException(msg) 

404 

405 out = out.splitlines()[1] 

406 size = int(out.split()[1]) 

407 available = int(out.split()[3]) 

408 return available, size 

409 

410 def _create_share_snapshot(self, snapshot): 

411 """Create a snapshot of the share.""" 

412 sharename = snapshot['share_name'] 

413 snapshotname = snapshot['name'] 

414 fsdev = self._get_gpfs_device() 

415 LOG.debug( 

416 'Attempting to create a snapshot %(snap)s from share %(share)s ' 

417 'on device %(dev)s.', 

418 {'share': sharename, 'snap': snapshotname, 'dev': fsdev} 

419 ) 

420 

421 try: 

422 self._gpfs_execute(self.GPFS_PATH + 'mmcrsnapshot', fsdev, 

423 snapshot['name'], '-j', sharename) 

424 except exception.ProcessExecutionError as e: 

425 msg = (_('Failed to create snapshot %(snapshot)s. ' 

426 'Error: %(excmsg)s.') % 

427 {'snapshot': snapshot['name'], 'excmsg': e}) 

428 LOG.error(msg) 

429 raise exception.GPFSException(msg) 

430 

431 def _delete_share_snapshot(self, snapshot): 

432 """Delete a snapshot of the share.""" 

433 sharename = snapshot['share_name'] 

434 fsdev = self._get_gpfs_device() 

435 

436 try: 

437 self._gpfs_execute(self.GPFS_PATH + 'mmdelsnapshot', fsdev, 

438 snapshot['name'], '-j', sharename) 

439 except exception.ProcessExecutionError as e: 

440 msg = (_('Failed to delete snapshot %(snapshot)s. ' 

441 'Error: %(excmsg)s.') % 

442 {'snapshot': snapshot['name'], 'excmsg': e}) 

443 LOG.error(msg) 

444 raise exception.GPFSException(msg) 

445 

446 def _create_share_from_snapshot(self, share, snapshot, share_path): 

447 """Create share from a share snapshot.""" 

448 self._create_share(share) 

449 snapshot_path = self._get_snapshot_path(snapshot) 

450 snapshot_path = snapshot_path + "/" 

451 try: 

452 self._gpfs_execute('rsync', '-rp', snapshot_path, share_path) 

453 except exception.ProcessExecutionError as e: 

454 msg = (_('Failed to create share %(share)s from ' 

455 'snapshot %(snapshot)s. Error: %(excmsg)s.') % 

456 {'share': share['name'], 'snapshot': snapshot['name'], 

457 'excmsg': e}) 

458 LOG.error(msg) 

459 raise exception.GPFSException(msg) 

460 

461 def _extend_share(self, shareobj, new_size): 

462 sharename = shareobj['name'] 

463 sizestr = '%sG' % new_size 

464 fsdev = self._get_gpfs_device() 

465 try: 

466 self._gpfs_execute(self.GPFS_PATH + 'mmsetquota', fsdev + ':' + 

467 sharename, '--block', '0:' + sizestr) 

468 except exception.ProcessExecutionError as e: 

469 msg = (_('Failed to set quota for the share %(sharename)s. ' 

470 'Error: %(excmsg)s.') % 

471 {'sharename': sharename, 'excmsg': e}) 

472 LOG.error(msg) 

473 raise exception.GPFSException(msg) 

474 

475 def get_network_allocations_number(self): 

476 return 0 

477 

478 def create_share(self, ctx, share, share_server=None): 

479 """Create GPFS directory that will be represented as share.""" 

480 self._create_share(share) 

481 share_path = self._get_share_path(share) 

482 location = self._get_helper(share).create_export(share_path) 

483 return location 

484 

485 def create_share_from_snapshot(self, ctx, share, snapshot, 

486 share_server=None, parent_share=None): 

487 """Is called to create share from a snapshot.""" 

488 share_path = self._get_share_path(share) 

489 self._create_share_from_snapshot(share, snapshot, share_path) 

490 location = self._get_helper(share).create_export(share_path) 

491 return location 

492 

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

494 """Creates a snapshot.""" 

495 self._create_share_snapshot(snapshot) 

496 

497 def delete_share(self, ctx, share, share_server=None): 

498 """Remove and cleanup share storage.""" 

499 location = self._get_share_path(share) 

500 self._get_helper(share).remove_export(location, share) 

501 self._delete_share(share) 

502 

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

504 """Deletes a snapshot.""" 

505 self._delete_share_snapshot(snapshot) 

506 

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

508 """Extends the quota on the share fileset.""" 

509 self._extend_share(share, new_size) 

510 

511 def ensure_share(self, ctx, share, share_server=None): 

512 """Ensure that storage are mounted and exported.""" 

513 

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

515 delete_rules, update_rules, share_server=None): 

516 """Update access rules for given share.""" 

517 helper = self._get_helper(share) 

518 location = self._get_share_path(share) 

519 

520 for access in delete_rules: 

521 helper.deny_access(location, share, access) 

522 

523 for access in add_rules: 

524 helper.allow_access(location, share, access) 

525 

526 if not (add_rules or delete_rules): 

527 helper.resync_access(location, share, access_rules) 

528 

529 def check_for_setup_error(self): 

530 """Returns an error if prerequisites aren't met.""" 

531 if not self._check_gpfs_state(): 

532 msg = (_('GPFS is not active.')) 

533 LOG.error(msg) 

534 raise exception.GPFSException(msg) 

535 

536 if not self.configuration.gpfs_share_export_ip: 

537 msg = (_('gpfs_share_export_ip must be specified.')) 

538 LOG.error(msg) 

539 raise exception.InvalidParameterValue(err=msg) 

540 

541 gpfs_base_dir = self.configuration.gpfs_mount_point_base 

542 if not gpfs_base_dir.startswith('/'): 

543 msg = (_('%s must be an absolute path.') % gpfs_base_dir) 

544 LOG.error(msg) 

545 raise exception.GPFSException(msg) 

546 

547 if not self._is_dir(gpfs_base_dir): 

548 msg = (_('%s is not a directory.') % gpfs_base_dir) 

549 LOG.error(msg) 

550 raise exception.GPFSException(msg) 

551 

552 if not self._is_gpfs_path(gpfs_base_dir): 

553 msg = (_('%s is not on GPFS. Perhaps GPFS not mounted.') 

554 % gpfs_base_dir) 

555 LOG.error(msg) 

556 raise exception.GPFSException(msg) 

557 

558 if self.configuration.gpfs_nfs_server_type not in ("KNFS", "CES"): 

559 msg = (_('Invalid gpfs_nfs_server_type value: %s. ' 

560 'Valid values are: "KNFS", "CES".') 

561 % self.configuration.gpfs_nfs_server_type) 

562 LOG.error(msg) 

563 raise exception.InvalidParameterValue(err=msg) 

564 

565 if ((not self.configuration.gpfs_nfs_server_list) and 565 ↛ exitline 565 didn't return from function 'check_for_setup_error' because the condition on line 565 was always true

566 (self.configuration.gpfs_nfs_server_type != 'CES')): 

567 msg = (_('Missing value for gpfs_nfs_server_list.')) 

568 LOG.error(msg) 

569 raise exception.InvalidParameterValue(err=msg) 

570 

571 def _is_share_valid(self, fsdev, location): 

572 try: 

573 out, __ = self._gpfs_execute(self.GPFS_PATH + 'mmlsfileset', fsdev, 

574 '-J', location, '-L', '-Y') 

575 except exception.ProcessExecutionError: 

576 msg = (_('Given share path %(share_path)s does not exist at ' 

577 'mount point %(mount_point)s.') 

578 % {'share_path': location, 'mount_point': fsdev}) 

579 LOG.exception(msg) 

580 raise exception.ManageInvalidShare(reason=msg) 

581 

582 lines = out.splitlines() 

583 try: 

584 validation_token = lines[0].split(':').index('allocInodes') 

585 alloc_inodes = lines[1].split(':')[validation_token] 

586 except (IndexError, ValueError): 

587 msg = (_('Failed to check share at %s.') % location) 

588 LOG.exception(msg) 

589 raise exception.GPFSException(msg) 

590 

591 return alloc_inodes != '0' 

592 

593 def _get_share_name(self, fsdev, location): 

594 try: 

595 out, __ = self._gpfs_execute(self.GPFS_PATH + 'mmlsfileset', fsdev, 

596 '-J', location, '-L', '-Y') 

597 except exception.ProcessExecutionError: 

598 msg = (_('Given share path %(share_path)s does not exist at ' 

599 'mount point %(mount_point)s.') 

600 % {'share_path': location, 'mount_point': fsdev}) 

601 LOG.exception(msg) 

602 raise exception.ManageInvalidShare(reason=msg) 

603 

604 lines = out.splitlines() 

605 try: 

606 validation_token = lines[0].split(':').index('filesetName') 

607 share_name = lines[1].split(':')[validation_token] 

608 except (IndexError, ValueError): 

609 msg = (_('Failed to check share at %s.') % location) 

610 LOG.exception(msg) 

611 raise exception.GPFSException(msg) 

612 

613 return share_name 

614 

615 def _manage_existing(self, fsdev, share, old_share_name): 

616 new_share_name = share['name'] 

617 new_export_location = self._local_path(new_share_name) 

618 try: 

619 self._gpfs_execute(self.GPFS_PATH + 'mmunlinkfileset', fsdev, 

620 old_share_name, '-f') 

621 except exception.ProcessExecutionError: 

622 msg = _('Failed to unlink fileset for share %s.') % new_share_name 

623 LOG.exception(msg) 

624 raise exception.GPFSException(msg) 

625 LOG.debug('Unlinked the fileset of share %s.', old_share_name) 

626 

627 try: 

628 self._gpfs_execute(self.GPFS_PATH + 'mmchfileset', fsdev, 

629 old_share_name, '-j', new_share_name) 

630 except exception.ProcessExecutionError: 

631 msg = _('Failed to rename fileset for share %s.') % new_share_name 

632 LOG.exception(msg) 

633 raise exception.GPFSException(msg) 

634 LOG.debug('Renamed the fileset from %(old_share)s to %(new_share)s.', 

635 {'old_share': old_share_name, 'new_share': new_share_name}) 

636 

637 try: 

638 self._gpfs_execute(self.GPFS_PATH + 'mmlinkfileset', fsdev, 

639 new_share_name, '-J', new_export_location) 

640 except exception.ProcessExecutionError: 

641 msg = _('Failed to link fileset for the share %s.' 

642 ) % new_share_name 

643 LOG.exception(msg) 

644 raise exception.GPFSException(msg) 

645 LOG.debug('Linked the fileset of share %(share_name)s at location ' 

646 '%(export_location)s.', 

647 {'share_name': new_share_name, 

648 'export_location': new_export_location}) 

649 

650 try: 

651 self._gpfs_execute('chmod', '777', new_export_location) 

652 except exception.ProcessExecutionError: 

653 msg = _('Failed to set permissions for share %s.') % new_share_name 

654 LOG.exception(msg) 

655 raise exception.GPFSException(msg) 

656 LOG.debug('Changed the permission of share %s.', new_share_name) 

657 

658 try: 

659 out, __ = self._gpfs_execute(self.GPFS_PATH + 'mmlsquota', '-j', 

660 new_share_name, '-Y', fsdev) 

661 except exception.ProcessExecutionError: 

662 msg = _('Failed to check size for share %s.') % new_share_name 

663 LOG.exception(msg) 

664 raise exception.GPFSException(msg) 

665 

666 lines = out.splitlines() 

667 try: 

668 quota_limit = lines[0].split(':').index('blockLimit') 

669 quota_status = lines[1].split(':')[quota_limit] 

670 except (IndexError, ValueError): 

671 msg = _('Failed to check quota for share %s.') % new_share_name 

672 LOG.exception(msg) 

673 raise exception.GPFSException(msg) 

674 

675 share_size = int(quota_status) 

676 # Note: since share_size returns integer value in KB, 

677 # we are checking whether share is less than 1GiB. 

678 # (units.Mi * KB = 1GB) 

679 if share_size < units.Mi: 

680 try: 

681 self._gpfs_execute(self.GPFS_PATH + 'mmsetquota', fsdev + ':' + 

682 new_share_name, '--block', '0:1G') 

683 except exception.ProcessExecutionError: 

684 msg = _('Failed to set quota for share %s.') % new_share_name 

685 LOG.exception(msg) 

686 raise exception.GPFSException(msg) 

687 LOG.info('Existing share %(shr)s has size %(size)s KB ' 

688 'which is below 1GiB, so extended it to 1GiB.', 

689 {'shr': new_share_name, 'size': share_size}) 

690 share_size = 1 

691 else: 

692 orig_share_size = share_size 

693 share_size = int(math.ceil(float(share_size) / units.Mi)) 

694 if orig_share_size != share_size * units.Mi: 

695 try: 

696 self._gpfs_execute(self.GPFS_PATH + 'mmsetquota', fsdev + 

697 ':' + new_share_name, '--block', '0:' + 

698 str(share_size) + 'G') 

699 except exception.ProcessExecutionError: 

700 msg = _('Failed to set quota for share %s.' 

701 ) % new_share_name 

702 LOG.exception(msg) 

703 raise exception.GPFSException(msg) 

704 

705 new_export_location = self._get_helper(share).create_export( 

706 new_export_location) 

707 return share_size, new_export_location 

708 

709 def manage_existing(self, share, driver_options): 

710 

711 old_export = share['export_location'].split(':') 

712 try: 

713 ces_ip = old_export[0] 

714 old_export_location = old_export[1] 

715 except IndexError: 

716 msg = _('Incorrect export path. Expected format: ' 

717 'IP:/gpfs_mount_point_base/share_id.') 

718 LOG.exception(msg) 

719 raise exception.ShareBackendException(msg=msg) 

720 

721 if ces_ip not in self.configuration.gpfs_nfs_server_list: 

722 msg = _('The CES IP %s is not present in the ' 

723 'configuration option "gpfs_nfs_server_list".') % ces_ip 

724 raise exception.ShareBackendException(msg=msg) 

725 

726 fsdev = self._get_gpfs_device() 

727 if not self._is_share_valid(fsdev, old_export_location): 

728 err_msg = _('Given share path %s does not have a valid ' 

729 'share.') % old_export_location 

730 raise exception.ManageInvalidShare(reason=err_msg) 

731 

732 share_name = self._get_share_name(fsdev, old_export_location) 

733 

734 out = self._get_helper(share)._has_client_access(old_export_location) 

735 if out: 

736 err_msg = _('Clients have access to %s share currently. Evict any ' 

737 'clients before trying again.') % share_name 

738 raise exception.ManageInvalidShare(reason=err_msg) 

739 

740 share_size, new_export_location = self._manage_existing( 

741 fsdev, share, share_name) 

742 return {"size": share_size, "export_locations": new_export_location} 

743 

744 def _update_share_stats(self): 

745 """Retrieve stats info from share volume group.""" 

746 

747 data = dict( 

748 share_backend_name=self.backend_name, 

749 vendor_name='IBM', 

750 storage_protocol='NFS', 

751 reserved_percentage=self.configuration.reserved_share_percentage, 

752 reserved_snapshot_percentage=( 

753 self.configuration.reserved_share_from_snapshot_percentage 

754 or self.configuration.reserved_share_percentage), 

755 reserved_share_extend_percentage=( 

756 self.configuration.reserved_share_extend_percentage 

757 or self.configuration.reserved_share_percentage)) 

758 

759 free, capacity = self._get_available_capacity( 

760 self.configuration.gpfs_mount_point_base) 

761 

762 data['total_capacity_gb'] = math.ceil(capacity / units.Gi) 

763 data['free_capacity_gb'] = math.ceil(free / units.Gi) 

764 

765 super(GPFSShareDriver, self)._update_share_stats(data) 

766 

767 def _get_helper(self, share): 

768 if share['share_proto'] == 'NFS': 

769 return self._helpers[self.configuration.gpfs_nfs_server_type] 

770 else: 

771 msg = (_('Share protocol %s not supported by GPFS driver.') 

772 % share['share_proto']) 

773 LOG.error(msg) 

774 raise exception.InvalidShare(reason=msg) 

775 

776 def _get_share_path(self, share): 

777 """Returns share path on storage provider.""" 

778 return os.path.join(self.configuration.gpfs_mount_point_base, 

779 share['name']) 

780 

781 def _get_snapshot_path(self, snapshot): 

782 """Returns share path on storage provider.""" 

783 snapshot_dir = ".snapshots" 

784 return os.path.join(self.configuration.gpfs_mount_point_base, 

785 snapshot["share_name"], snapshot_dir, 

786 snapshot["name"]) 

787 

788 

789class NASHelperBase(metaclass=abc.ABCMeta): 

790 """Interface to work with share.""" 

791 

792 def __init__(self, execute, config_object): 

793 self.configuration = config_object 

794 self._execute = execute 

795 

796 def create_export(self, local_path): 

797 """Construct location of new export.""" 

798 return ':'.join([self.configuration.gpfs_share_export_ip, local_path]) 

799 

800 def get_export_options(self, share, access, helper): 

801 """Get the export options.""" 

802 extra_specs = share_types.get_extra_specs_from_share(share) 

803 if helper == 'KNFS': 

804 export_options = extra_specs.get('knfs:export_options') 

805 elif helper == 'CES': 805 ↛ 808line 805 didn't jump to line 808 because the condition on line 805 was always true

806 export_options = extra_specs.get('ces:export_options') 

807 else: 

808 export_options = None 

809 

810 options = self._get_validated_opt_list(export_options) 

811 options.append(self.get_access_option(access)) 

812 return ','.join(options) 

813 

814 def _validate_export_options(self, options): 

815 """Validate the export options.""" 

816 options_not_allowed = self._get_options_not_allowed() 

817 invalid_options = [ 

818 option for option in options if option in options_not_allowed 

819 ] 

820 

821 if invalid_options: 

822 raise exception.InvalidInput(reason='Invalid export_option %s as ' 

823 'it is set by access_type.' 

824 % invalid_options) 

825 

826 def _get_validated_opt_list(self, export_options): 

827 """Validate the export options and return an option list.""" 

828 if export_options: 

829 options = export_options.lower().split(',') 

830 self._validate_export_options(options) 

831 else: 

832 options = [] 

833 return options 

834 

835 @abc.abstractmethod 

836 def get_access_option(self, access): 

837 """Get access option string based on access level.""" 

838 

839 @abc.abstractmethod 

840 def _get_options_not_allowed(self): 

841 """Get access options that are not allowed in extra-specs.""" 

842 

843 @abc.abstractmethod 

844 def remove_export(self, local_path, share): 

845 """Remove export.""" 

846 

847 @abc.abstractmethod 

848 def allow_access(self, local_path, share, access): 

849 """Allow access to the host.""" 

850 

851 @abc.abstractmethod 

852 def deny_access(self, local_path, share, access): 

853 """Deny access to the host.""" 

854 

855 @abc.abstractmethod 

856 def resync_access(self, local_path, share, access_rules): 

857 """Re-sync all access rules for given share.""" 

858 

859 

860class KNFSHelper(NASHelperBase): 

861 """Wrapper for Kernel NFS Commands.""" 

862 

863 def __init__(self, execute, config_object): 

864 super(KNFSHelper, self).__init__(execute, config_object) 

865 self._execute = execute 

866 try: 

867 self._execute('exportfs', check_exit_code=True, run_as_root=True) 

868 except exception.ProcessExecutionError as e: 

869 msg = (_('NFS server not found. Error: %s.') % e) 

870 LOG.error(msg) 

871 raise exception.GPFSException(msg) 

872 

873 def _has_client_access(self, local_path, access_to=None): 

874 try: 

875 out, __ = self._execute('exportfs', run_as_root=True) 

876 except exception.ProcessExecutionError: 

877 msg = _('Failed to check exports on the systems.') 

878 LOG.exception(msg) 

879 raise exception.GPFSException(msg) 

880 

881 if access_to: 

882 if (re.search(re.escape(local_path) + r'[\s\n]*' 

883 + re.escape(access_to), out)): 

884 return True 

885 else: 

886 if re.findall(local_path + '\\b', ''.join(out)): 

887 return True 

888 return False 

889 

890 def _publish_access(self, *cmd, **kwargs): 

891 check_exit_code = kwargs.get('check_exit_code', True) 

892 

893 outs = [] 

894 localserver_iplist = socket.gethostbyname_ex(socket.gethostname())[2] 

895 for server in self.configuration.gpfs_nfs_server_list: 

896 if server in localserver_iplist: 

897 run_command = cmd 

898 run_local = True 

899 else: 

900 sshlogin = self.configuration.gpfs_ssh_login 

901 remote_login = sshlogin + '@' + server 

902 run_command = ['ssh', remote_login] + list(cmd) 

903 run_local = False 

904 try: 

905 out = utils.execute(*run_command, 

906 run_as_root=run_local, 

907 check_exit_code=check_exit_code) 

908 except exception.ProcessExecutionError: 

909 raise 

910 outs.append(out) 

911 return outs 

912 

913 def _verify_denied_access(self, local_path, share, ip): 

914 try: 

915 cmd = ['exportfs'] 

916 outs = self._publish_access(*cmd) 

917 except exception.ProcessExecutionError: 

918 msg = _('Failed to verify denied access for ' 

919 'share %s.') % share['name'] 

920 LOG.exception(msg) 

921 raise exception.GPFSException(msg) 

922 

923 for stdout, stderr in outs: 

924 if stderr and stderr.strip(): 

925 msg = ('Log/ignore stderr during _validate_denied_access for ' 

926 'share %(sharename)s. Return code OK. ' 

927 'Stderr: %(stderr)s' % {'sharename': share['name'], 

928 'stderr': stderr}) 

929 LOG.debug(msg) 

930 

931 gpfs_ips = NFSHelper.get_host_list(stdout, local_path) 

932 if ip in gpfs_ips: 

933 msg = (_('Failed to deny access for share %(sharename)s. ' 

934 'IP %(ip)s still has access.') % 

935 {'sharename': share['name'], 

936 'ip': ip}) 

937 LOG.error(msg) 

938 raise exception.GPFSException(msg) 

939 

940 def remove_export(self, local_path, share): 

941 """Remove export.""" 

942 

943 def get_access_option(self, access): 

944 """Get access option string based on access level.""" 

945 return access['access_level'] 

946 

947 def _get_options_not_allowed(self): 

948 """Get access options that are not allowed in extra-specs.""" 

949 return list(constants.ACCESS_LEVELS) 

950 

951 def _get_exports(self): 

952 """Get exportfs output.""" 

953 try: 

954 out, __ = self._execute('exportfs', run_as_root=True) 

955 except exception.ProcessExecutionError as e: 

956 msg = (_('Failed to check exports on the systems. ' 

957 ' Error: %s.') % e) 

958 LOG.error(msg) 

959 raise exception.GPFSException(msg) 

960 return out 

961 

962 def allow_access(self, local_path, share, access, error_on_exists=True): 

963 """Allow access to one or more vm instances.""" 

964 

965 if access['access_type'] != 'ip': 

966 raise exception.InvalidShareAccess(reason='Only ip access type ' 

967 'supported.') 

968 

969 if error_on_exists: 

970 # check if present in export 

971 out = re.search( 

972 re.escape(local_path) + r'[\s\n]*' 

973 + re.escape(access['access_to']), self._get_exports()) 

974 

975 if out is not None: 

976 access_type = access['access_type'] 

977 access_to = access['access_to'] 

978 raise exception.ShareAccessExists(access_type=access_type, 

979 access=access_to) 

980 

981 export_opts = self.get_export_options(share, access, 'KNFS') 

982 cmd = ['exportfs', '-o', export_opts, 

983 ':'.join([access['access_to'], local_path])] 

984 try: 

985 self._publish_access(*cmd) 

986 except exception.ProcessExecutionError: 

987 msg = _('Failed to allow access for share %s.') % share['name'] 

988 LOG.exception(msg) 

989 raise exception.GPFSException(msg) 

990 

991 def _deny_ip(self, local_path, share, ip): 

992 """Remove access for one or more vm instances.""" 

993 cmd = ['exportfs', '-u', ':'.join([ip, local_path])] 

994 try: 

995 # Can get exit code 0 for success or 1 for already gone (also 

996 # potentially get 1 due to exportfs bug). So allow 

997 # _publish_access to continue with [0, 1] and then verify after 

998 # it is done. 

999 self._publish_access(*cmd, check_exit_code=[0, 1]) 

1000 except exception.ProcessExecutionError: 

1001 msg = _('Failed to deny access for share %s.') % share['name'] 

1002 LOG.exception(msg) 

1003 raise exception.GPFSException(msg) 

1004 

1005 # Error code (0 or 1) makes deny IP success indeterminate. 

1006 # So, verify that the IP access was completely removed. 

1007 self._verify_denied_access(local_path, share, ip) 

1008 

1009 def deny_access(self, local_path, share, access): 

1010 """Remove access for one or more vm instances.""" 

1011 self._deny_ip(local_path, share, access['access_to']) 

1012 

1013 def _remove_other_access(self, local_path, share, access_rules): 

1014 """Remove any client access that is not in access_rules.""" 

1015 exports = self._get_exports() 

1016 gpfs_ips = set(NFSHelper.get_host_list(exports, local_path)) 

1017 manila_ips = set([x['access_to'] for x in access_rules]) 

1018 remove_ips = gpfs_ips - manila_ips 

1019 for ip in remove_ips: 

1020 self._deny_ip(local_path, share, ip) 

1021 

1022 def resync_access(self, local_path, share, access_rules): 

1023 """Re-sync all access rules for given share.""" 

1024 for access in access_rules: 

1025 self.allow_access(local_path, share, access, error_on_exists=False) 

1026 self._remove_other_access(local_path, share, access_rules) 

1027 

1028 

1029class CESHelper(NASHelperBase): 

1030 """Wrapper for NFS by Spectrum Scale CES""" 

1031 

1032 def __init__(self, execute, config_object): 

1033 super(CESHelper, self).__init__(execute, config_object) 

1034 self._execute = execute 

1035 if self.configuration.is_gpfs_node: 1035 ↛ 1036line 1035 didn't jump to line 1036 because the condition on line 1035 was never true

1036 self.GPFS_PATH = '' 

1037 else: 

1038 self.GPFS_PATH = '/usr/lpp/mmfs/bin/' 

1039 

1040 def _execute_mmnfs_command(self, cmd, err_msg): 

1041 try: 

1042 out, __ = self._execute(self.GPFS_PATH + 'mmnfs', 'export', *cmd) 

1043 except exception.ProcessExecutionError as e: 

1044 msg = (_('%(err_msg)s Error: %(e)s.') 

1045 % {'err_msg': err_msg, 'e': e}) 

1046 LOG.error(msg) 

1047 raise exception.GPFSException(msg) 

1048 return out 

1049 

1050 @staticmethod 

1051 def _fix_export_data(data, headers): 

1052 """Export data split by ':' may need fixing if client had colons.""" 

1053 

1054 # If an IPv6 client shows up then ':' delimiters don't work. 

1055 # So use header positions to get data before/after Clients. 

1056 # Then what is left in between can be joined back into a client IP. 

1057 client_index = headers.index('Clients') 

1058 # reverse_client_index is distance from end. 

1059 reverse_client_index = len(headers) - (client_index + 1) 

1060 after_client_index = len(data) - reverse_client_index 

1061 

1062 before_client = data[:client_index] 

1063 client = data[client_index: after_client_index] 

1064 after_client = data[after_client_index:] 

1065 

1066 result_data = before_client 

1067 result_data.append(':'.join(client)) # Fixes colons in client IP 

1068 result_data.extend(after_client) 

1069 return result_data 

1070 

1071 def _get_nfs_client_exports(self, local_path): 

1072 """Get the current NFS client export details from GPFS.""" 

1073 

1074 out = self._execute_mmnfs_command( 

1075 ('list', '-n', local_path, '-Y'), 

1076 'Failed to get exports from the system.') 

1077 

1078 # Remove the header line and use the headers to describe the data 

1079 lines = out.splitlines() 

1080 for line in lines: 

1081 data = line.split(':') 

1082 if "HEADER" in data: 

1083 headers = data 

1084 lines.remove(line) 

1085 break 

1086 else: 

1087 msg = _('Failed to parse exports for path %s. ' 

1088 'No HEADER found.') % local_path 

1089 LOG.error(msg) 

1090 raise exception.GPFSException(msg) 

1091 

1092 exports = [] 

1093 for line in lines: 

1094 data = line.split(':') 

1095 if len(data) < 3: 

1096 continue # Skip empty lines (and anything less than minimal). 

1097 

1098 result_data = self._fix_export_data(data, headers) 

1099 exports.append(dict(zip(headers, result_data))) 

1100 

1101 return exports 

1102 

1103 def _has_client_access(self, local_path, access_to=None): 

1104 """Check path for any export or for one with a specific IP address.""" 

1105 gpfs_clients = self._get_nfs_client_exports(local_path) 

1106 return gpfs_clients and (access_to is None or access_to in [ 

1107 x['Clients'] for x in gpfs_clients]) 

1108 

1109 def remove_export(self, local_path, share): 

1110 """Remove export.""" 

1111 if self._has_client_access(local_path): 

1112 err_msg = ('Failed to remove export for share %s.' 

1113 % share['name']) 

1114 self._execute_mmnfs_command(('remove', local_path), err_msg) 

1115 

1116 def _get_options_not_allowed(self): 

1117 """Get access options that are not allowed in extra-specs.""" 

1118 return ['access_type=ro', 'access_type=rw'] 

1119 

1120 def get_access_option(self, access): 

1121 """Get access option string based on access level.""" 

1122 if access['access_level'] == constants.ACCESS_LEVEL_RO: 

1123 return 'access_type=ro' 

1124 else: 

1125 return 'access_type=rw' 

1126 

1127 def allow_access(self, local_path, share, access): 

1128 """Allow access to the host.""" 

1129 

1130 if access['access_type'] != 'ip': 

1131 raise exception.InvalidShareAccess(reason='Only ip access type ' 

1132 'supported.') 

1133 has_exports = self._has_client_access(local_path) 

1134 

1135 export_opts = self.get_export_options(share, access, 'CES') 

1136 

1137 if not has_exports: 

1138 cmd = ['add', local_path, '-c', 

1139 access['access_to'] + 

1140 '(' + export_opts + ')'] 

1141 else: 

1142 cmd = ['change', local_path, '--nfsadd', 

1143 access['access_to'] + 

1144 '(' + export_opts + ')'] 

1145 

1146 err_msg = ('Failed to allow access for share %s.' 

1147 % share['name']) 

1148 self._execute_mmnfs_command(cmd, err_msg) 

1149 

1150 def deny_access(self, local_path, share, access, force=False): 

1151 """Deny access to the host.""" 

1152 has_export = self._has_client_access(local_path, access['access_to']) 

1153 

1154 if has_export: 1154 ↛ exitline 1154 didn't return from function 'deny_access' because the condition on line 1154 was always true

1155 err_msg = ('Failed to remove access for share %s.' 

1156 % share['name']) 

1157 self._execute_mmnfs_command(('change', local_path, 

1158 '--nfsremove', access['access_to']), 

1159 err_msg) 

1160 

1161 def _get_client_opts(self, access, opts_list): 

1162 """Get client options string for access rule and NFS options.""" 

1163 nfs_opts = ','.join([self.get_access_option(access)] + opts_list) 

1164 

1165 return '%(ip)s(%(nfs_opts)s)' % {'ip': access['access_to'], 

1166 'nfs_opts': nfs_opts} 

1167 

1168 def _get_share_opts(self, share): 

1169 """Get a list of NFS options from the share's share type.""" 

1170 extra_specs = share_types.get_extra_specs_from_share(share) 

1171 opts_list = self._get_validated_opt_list( 

1172 extra_specs.get('ces:export_options')) 

1173 return opts_list 

1174 

1175 def _nfs_change(self, local_path, share, access_rules, gpfs_clients): 

1176 """Bulk add/update/remove of access rules for share.""" 

1177 opts_list = self._get_share_opts(share) 

1178 

1179 # Create a map of existing client access rules from GPFS. 

1180 # Key from 'Clients' is an IP address or 

1181 # Value from 'Access_Type' is RW|RO (case varies) 

1182 gpfs_map = { 

1183 x['Clients']: x['Access_Type'].lower() for x in gpfs_clients} 

1184 gpfs_ips = set(gpfs_map.keys()) 

1185 

1186 manila_ips = set([x['access_to'] for x in access_rules]) 

1187 add_ips = manila_ips - gpfs_ips 

1188 update_ips = gpfs_ips.intersection(manila_ips) 

1189 remove_ips = gpfs_ips - manila_ips 

1190 

1191 adds = [] 

1192 updates = [] 

1193 if add_ips or update_ips: 1193 ↛ 1202line 1193 didn't jump to line 1202 because the condition on line 1193 was always true

1194 for access in access_rules: 

1195 ip = access['access_to'] 

1196 if ip in add_ips: 

1197 adds.append(self._get_client_opts(access, opts_list)) 

1198 elif (ip in update_ips 1198 ↛ 1194line 1198 didn't jump to line 1194 because the condition on line 1198 was always true

1199 and access['access_level'] != gpfs_map[ip]): 

1200 updates.append(self._get_client_opts(access, opts_list)) 

1201 

1202 if remove_ips or adds or updates: 1202 ↛ exitline 1202 didn't return from function '_nfs_change' because the condition on line 1202 was always true

1203 cmd = ['change', local_path] 

1204 if remove_ips: 1204 ↛ 1207line 1204 didn't jump to line 1207 because the condition on line 1204 was always true

1205 cmd.append('--nfsremove') 

1206 cmd.append(','.join(remove_ips)) 

1207 if adds: 1207 ↛ 1210line 1207 didn't jump to line 1210 because the condition on line 1207 was always true

1208 cmd.append('--nfsadd') 

1209 cmd.append(';'.join(adds)) 

1210 if updates: 1210 ↛ 1213line 1210 didn't jump to line 1213 because the condition on line 1210 was always true

1211 cmd.append('--nfschange') 

1212 cmd.append(';'.join(updates)) 

1213 err_msg = ('Failed to resync access for share %s.' % share['name']) 

1214 self._execute_mmnfs_command(cmd, err_msg) 

1215 

1216 def _nfs_add(self, access_rules, local_path, share): 

1217 """Bulk add of access rules to share.""" 

1218 if not access_rules: 

1219 return 

1220 

1221 opts_list = self._get_share_opts(share) 

1222 client_options = [] 

1223 for access in access_rules: 

1224 client_options.append(self._get_client_opts(access, opts_list)) 

1225 

1226 cmd = ['add', local_path, '-c', ';'.join(client_options)] 

1227 err_msg = ('Failed to resync access for share %s.' % share['name']) 

1228 self._execute_mmnfs_command(cmd, err_msg) 

1229 

1230 def resync_access(self, local_path, share, access_rules): 

1231 """Re-sync all access rules for given share.""" 

1232 gpfs_clients = self._get_nfs_client_exports(local_path) 

1233 if not gpfs_clients: 

1234 self._nfs_add(access_rules, local_path, share) 

1235 else: 

1236 self._nfs_change(local_path, share, access_rules, gpfs_clients)