Coverage for manila/share/drivers/hitachi/hnas/ssh.py: 98%

586 statements  

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

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

2# All Rights Reserved. 

3# 

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

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

6# a copy of the License at 

7# 

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

9# 

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

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

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

13# License for the specific language governing permissions and limitations 

14# under the License. 

15 

16from oslo_concurrency import processutils 

17from oslo_log import log 

18from oslo_utils import strutils 

19from oslo_utils import units 

20import paramiko 

21 

22import os 

23import time 

24 

25from manila import exception 

26from manila.i18n import _ 

27from manila import ssh_utils 

28from manila import utils as mutils 

29 

30LOG = log.getLogger(__name__) 

31 

32 

33class HNASSSHBackend(object): 

34 def __init__(self, hnas_ip, hnas_username, hnas_password, ssh_private_key, 

35 cluster_admin_ip0, evs_id, evs_ip, fs_name, job_timeout): 

36 self.ip = hnas_ip 

37 self.port = 22 

38 self.user = hnas_username 

39 self.password = hnas_password 

40 self.priv_key = ssh_private_key 

41 self.admin_ip0 = cluster_admin_ip0 

42 self.evs_id = str(evs_id) 

43 self.fs_name = fs_name 

44 self.evs_ip = evs_ip 

45 self.sshpool = None 

46 self.job_timeout = job_timeout 

47 LOG.debug("Hitachi HNAS Driver using SSH backend.") 

48 

49 def get_stats(self): 

50 """Get the stats from file-system. 

51 

52 :returns: 

53 fs_capacity.size = Total size from filesystem. 

54 available_space = Free space currently on filesystem. 

55 dedupe = True if dedupe is enabled on filesystem. 

56 """ 

57 command = ['df', '-a', '-f', self.fs_name] 

58 try: 

59 output, err = self._execute(command) 

60 

61 except processutils.ProcessExecutionError: 

62 msg = _("Could not get HNAS backend stats.") 

63 LOG.exception(msg) 

64 raise exception.HNASBackendException(msg=msg) 

65 

66 line = output.split('\n') 

67 fs = Filesystem(line[3]) 

68 available_space = fs.size - fs.used 

69 return fs.size, available_space, fs.dedupe 

70 

71 def nfs_export_add(self, share_id, snapshot_id=None): 

72 if snapshot_id is not None: 

73 path = os.path.join('/snapshots', share_id, snapshot_id) 

74 name = os.path.join('/snapshots', snapshot_id) 

75 else: 

76 path = name = os.path.join('/shares', share_id) 

77 

78 command = ['nfs-export', 'add', '-S', 'disable', '-c', '127.0.0.1', 

79 name, self.fs_name, path] 

80 try: 

81 self._execute(command) 

82 except processutils.ProcessExecutionError: 

83 msg = _("Could not create NFS export %s.") % name 

84 LOG.exception(msg) 

85 raise exception.HNASBackendException(msg=msg) 

86 

87 def nfs_export_del(self, share_id=None, snapshot_id=None): 

88 if share_id is not None: 

89 name = os.path.join('/shares', share_id) 

90 elif snapshot_id is not None: 

91 name = os.path.join('/snapshots', snapshot_id) 

92 else: 

93 msg = _("NFS export not specified to delete.") 

94 raise exception.HNASBackendException(msg=msg) 

95 

96 command = ['nfs-export', 'del', name] 

97 try: 

98 self._execute(command) 

99 except processutils.ProcessExecutionError as e: 

100 if 'does not exist' in e.stderr: 

101 LOG.warning("Export %s does not exist on " 

102 "backend anymore.", name) 

103 else: 

104 msg = _("Could not delete NFS export %s.") % name 

105 LOG.exception(msg) 

106 raise exception.HNASBackendException(msg=msg) 

107 

108 def cifs_share_add(self, share_id, snapshot_id=None): 

109 if snapshot_id is not None: 

110 path = r'\\snapshots\\' + share_id + r'\\' + snapshot_id 

111 name = snapshot_id 

112 else: 

113 path = r'\\shares\\' + share_id 

114 name = share_id 

115 command = ['cifs-share', 'add', '-S', 'disable', '--enable-abe', 

116 '--nodefaultsaa', name, self.fs_name, path] 

117 try: 

118 self._execute(command) 

119 except processutils.ProcessExecutionError: 

120 msg = _("Could not create CIFS share %s.") % name 

121 LOG.exception(msg) 

122 raise exception.HNASBackendException(msg=msg) 

123 

124 def cifs_share_del(self, name): 

125 command = ['cifs-share', 'del', '--target-label', self.fs_name, 

126 name] 

127 try: 

128 self._execute(command) 

129 except processutils.ProcessExecutionError as e: 

130 if e.exit_code == 1: 

131 LOG.warning("CIFS share %s does not exist on " 

132 "backend anymore.", name) 

133 else: 

134 msg = _("Could not delete CIFS share %s.") % name 

135 LOG.exception(msg) 

136 raise exception.HNASBackendException(msg=msg) 

137 

138 def get_nfs_host_list(self, share_id): 

139 export = self._get_export(share_id) 

140 return export[0].export_configuration 

141 

142 def update_nfs_access_rule(self, host_list, share_id=None, 

143 snapshot_id=None): 

144 if share_id is not None: 

145 name = os.path.join('/shares', share_id) 

146 elif snapshot_id is not None: 

147 name = os.path.join('/snapshots', snapshot_id) 

148 else: 

149 msg = _("No share/snapshot provided to update NFS rules.") 

150 raise exception.HNASBackendException(msg=msg) 

151 

152 command = ['nfs-export', 'mod', '-c'] 

153 

154 if len(host_list) == 0: 

155 command.append('127.0.0.1') 

156 else: 

157 string_command = '"' + str(host_list[0]) 

158 

159 for i in range(1, len(host_list)): 

160 string_command += ',' + (str(host_list[i])) 

161 string_command += '"' 

162 command.append(string_command) 

163 

164 command.append(name) 

165 try: 

166 self._execute(command) 

167 except processutils.ProcessExecutionError: 

168 msg = _("Could not update access rules for NFS export %s.") % name 

169 LOG.exception(msg) 

170 raise exception.HNASBackendException(msg=msg) 

171 

172 def cifs_allow_access(self, name, user, permission, is_snapshot=False): 

173 command = ['cifs-saa', 'add', '--target-label', self.fs_name, 

174 name, user, permission] 

175 

176 try: 

177 self._execute(command) 

178 except processutils.ProcessExecutionError as e: 

179 if 'already listed as a user' in e.stderr: 

180 if is_snapshot: 

181 LOG.debug('User %(user)s already allowed to access ' 

182 'snapshot %(snapshot)s.', { 

183 'user': user, 

184 'snapshot': name, 

185 }) 

186 else: 

187 self._update_cifs_rule(name, user, permission) 

188 else: 

189 entity_type = "share" 

190 if is_snapshot: 

191 entity_type = "snapshot" 

192 

193 msg = _("Could not add access of user %(user)s to " 

194 "%(entity_type)s %(name)s.") % { 

195 'user': user, 

196 'name': name, 

197 'entity_type': entity_type, 

198 } 

199 LOG.exception(msg) 

200 raise exception.HNASBackendException(msg=msg) 

201 

202 def _update_cifs_rule(self, name, user, permission): 

203 LOG.debug('User %(user)s already allowed to access ' 

204 'share %(share)s. Updating access level...', { 

205 'user': user, 

206 'share': name, 

207 }) 

208 

209 command = ['cifs-saa', 'change', '--target-label', self.fs_name, 

210 name, user, permission] 

211 try: 

212 self._execute(command) 

213 except processutils.ProcessExecutionError: 

214 msg = _("Could not update access of user %(user)s to " 

215 "share %(share)s.") % { 

216 'user': user, 

217 'share': name, 

218 } 

219 LOG.exception(msg) 

220 raise exception.HNASBackendException(msg=msg) 

221 

222 def cifs_deny_access(self, name, user, is_snapshot=False): 

223 command = ['cifs-saa', 'delete', '--target-label', self.fs_name, 

224 name, user] 

225 

226 entity_type = "share" 

227 if is_snapshot: 

228 entity_type = "snapshot" 

229 

230 try: 

231 self._execute(command) 

232 except processutils.ProcessExecutionError as e: 

233 if ('not listed as a user' in e.stderr or 

234 'Could not delete user/group' in e.stderr): 

235 LOG.warning('User %(user)s already not allowed to access ' 

236 '%(entity_type)s %(name)s.', { 

237 'entity_type': entity_type, 

238 'user': user, 

239 'name': name 

240 }) 

241 else: 

242 msg = _("Could not delete access of user %(user)s to " 

243 "%(entity_type)s %(name)s.") % { 

244 'user': user, 

245 'name': name, 

246 'entity_type': entity_type, 

247 } 

248 LOG.exception(msg) 

249 raise exception.HNASBackendException(msg=msg) 

250 

251 def list_cifs_permissions(self, hnas_share_id): 

252 command = ['cifs-saa', 'list', '--target-label', self.fs_name, 

253 hnas_share_id] 

254 try: 

255 output, err = self._execute(command) 

256 except processutils.ProcessExecutionError as e: 

257 if 'No entries for this share' in e.stderr: 

258 LOG.debug('Share %(share)s does not have any permission ' 

259 'added.', {'share': hnas_share_id}) 

260 return [] 

261 else: 

262 msg = _("Could not list access of share %s.") % hnas_share_id 

263 LOG.exception(msg) 

264 raise exception.HNASBackendException(msg=msg) 

265 

266 permissions = CIFSPermissions(output) 

267 

268 return permissions.permission_list 

269 

270 def tree_clone(self, src_path, dest_path): 

271 command = ['tree-clone-job-submit', '-e', '-f', self.fs_name, 

272 src_path, dest_path] 

273 try: 

274 output, err = self._execute(command) 

275 except processutils.ProcessExecutionError as e: 

276 if ('Cannot find any clonable files in the source directory' in 

277 e.stderr): 

278 msg = _("Source path %s is empty.") % src_path 

279 LOG.debug(msg) 

280 raise exception.HNASNothingToCloneException(msg=msg) 

281 else: 

282 msg = _("Could not submit tree clone job to clone from %(src)s" 

283 " to %(dest)s.") % {'src': src_path, 'dest': dest_path} 

284 LOG.exception(msg) 

285 raise exception.HNASBackendException(msg=msg) 

286 

287 job_submit = JobSubmit(output) 

288 if job_submit.request_status == 'Request submitted successfully': 288 ↛ exitline 288 didn't return from function 'tree_clone' because the condition on line 288 was always true

289 job_id = job_submit.job_id 

290 

291 job_status = None 

292 progress = '' 

293 job_rechecks = 0 

294 starttime = time.time() 

295 deadline = starttime + self.job_timeout 

296 while (not job_status or 

297 job_status.job_state != "Job was completed"): 

298 

299 command = ['tree-clone-job-status', job_id] 

300 output, err = self._execute(command) 

301 job_status = JobStatus(output) 

302 

303 if job_status.job_state == 'Job failed': 

304 break 

305 

306 old_progress = progress 

307 progress = job_status.data_bytes_processed 

308 

309 if old_progress == progress: 

310 job_rechecks += 1 

311 now = time.time() 

312 if now > deadline: 

313 command = ['tree-clone-job-abort', job_id] 

314 self._execute(command) 

315 LOG.error("Timeout in snapshot creation from " 

316 "source path %s.", src_path) 

317 msg = _("Share snapshot of source path %s " 

318 "was not created.") % src_path 

319 raise exception.HNASBackendException(msg=msg) 

320 else: 

321 time.sleep(job_rechecks ** 2) 

322 else: 

323 job_rechecks = 0 

324 

325 if (job_status.job_state, job_status.job_status, 

326 job_status.directories_missing, 

327 job_status.files_missing) == ("Job was completed", 

328 "Success", '0', '0'): 

329 

330 LOG.debug("Snapshot of source path %(src)s to destination " 

331 "path %(dest)s created successfully.", 

332 {'src': src_path, 

333 'dest': dest_path}) 

334 else: 

335 LOG.error('Error creating snapshot of source path %s.', 

336 src_path) 

337 msg = _('Snapshot of source path %s was not ' 

338 'created.') % src_path 

339 raise exception.HNASBackendException(msg=msg) 

340 

341 def tree_delete(self, path): 

342 command = ['tree-delete-job-submit', '--confirm', '-f', self.fs_name, 

343 path] 

344 try: 

345 self._execute(command) 

346 except processutils.ProcessExecutionError as e: 

347 if 'Source path: Cannot access' in e.stderr: 

348 LOG.warning("Attempted to delete path %s " 

349 "but it does not exist.", path) 

350 else: 

351 msg = _("Could not submit tree delete job to delete path " 

352 "%s.") % path 

353 LOG.exception(msg) 

354 raise exception.HNASBackendException(msg=msg) 

355 

356 @mutils.retry(retry_param=exception.HNASSSCContextChange, wait_random=True, 

357 retries=5) 

358 def create_directory(self, dest_path): 

359 self._locked_selectfs('create', dest_path) 

360 if not self.check_directory(dest_path): 

361 msg = _("Command to create directory %(path)s was run in another " 

362 "filesystem instead of %(fs)s.") % { 

363 'path': dest_path, 

364 'fs': self.fs_name, 

365 } 

366 LOG.warning(msg) 

367 raise exception.HNASSSCContextChange(msg=msg) 

368 

369 @mutils.retry(retry_param=exception.HNASSSCContextChange, wait_random=True, 

370 retries=5) 

371 def delete_directory(self, path): 

372 try: 

373 self._locked_selectfs('delete', path) 

374 except exception.HNASDirectoryNotEmpty: 

375 pass 

376 else: 

377 if self.check_directory(path): 

378 msg = _("Command to delete empty directory %(path)s was run in" 

379 " another filesystem instead of %(fs)s.") % { 

380 'path': path, 

381 'fs': self.fs_name, 

382 } 

383 LOG.debug(msg) 

384 raise exception.HNASSSCContextChange(msg=msg) 

385 

386 @mutils.retry(retry_param=exception.HNASSSCIsBusy, wait_random=True, 

387 retries=5) 

388 def check_directory(self, path): 

389 command = ['path-to-object-number', '-f', self.fs_name, path] 

390 

391 try: 

392 self._execute(command) 

393 except processutils.ProcessExecutionError as e: 

394 if 'path-to-object-number is currently running' in e.stdout: 

395 msg = (_("SSC command path-to-object-number for path %s " 

396 "is currently busy.") % path) 

397 raise exception.HNASSSCIsBusy(msg=msg) 

398 if 'Unable to locate component:' in e.stdout: 

399 LOG.debug("Cannot find %(path)s: %(out)s", 

400 {'path': path, 'out': e.stdout}) 

401 return False 

402 else: 

403 msg = _("Could not check if path %s exists.") % path 

404 LOG.exception(msg) 

405 raise exception.HNASBackendException(msg=msg) 

406 return True 

407 

408 def check_fs_mounted(self): 

409 command = ['df', '-a', '-f', self.fs_name] 

410 output, err = self._execute(command) 

411 if "not found" in output: 

412 msg = _("Filesystem %s does not exist or it is not available " 

413 "in the current EVS context.") % self.fs_name 

414 LOG.error(msg) 

415 raise exception.HNASItemNotFoundException(msg=msg) 

416 else: 

417 line = output.split('\n') 

418 fs = Filesystem(line[3]) 

419 return fs.mounted 

420 

421 def mount(self): 

422 command = ['mount', self.fs_name] 

423 try: 

424 self._execute(command) 

425 except processutils.ProcessExecutionError as e: 

426 if 'file system is already mounted' not in e.stderr: 426 ↛ exitline 426 didn't return from function 'mount' because the condition on line 426 was always true

427 msg = _("Failed to mount filesystem %s.") % self.fs_name 

428 LOG.exception(msg) 

429 raise exception.HNASBackendException(msg=msg) 

430 

431 def vvol_create(self, vvol_name): 

432 # create a virtual-volume inside directory 

433 path = '/shares/' + vvol_name 

434 command = ['virtual-volume', 'add', '--ensure', self.fs_name, 

435 vvol_name, path] 

436 try: 

437 self._execute(command) 

438 except processutils.ProcessExecutionError: 

439 msg = _("Failed to create vvol %s.") % vvol_name 

440 LOG.exception(msg) 

441 raise exception.HNASBackendException(msg=msg) 

442 

443 def vvol_delete(self, vvol_name): 

444 path = '/shares/' + vvol_name 

445 # Virtual-volume and quota are deleted together 

446 command = ['tree-delete-job-submit', '--confirm', '-f', 

447 self.fs_name, path] 

448 try: 

449 self._execute(command) 

450 except processutils.ProcessExecutionError as e: 

451 if 'Source path: Cannot access' in e.stderr: 

452 LOG.warning("Share %s does not exist.", vvol_name) 

453 else: 

454 msg = _("Failed to delete vvol %s.") % vvol_name 

455 LOG.exception(msg) 

456 raise exception.HNASBackendException(msg=msg) 

457 

458 def quota_add(self, vvol_name, vvol_quota): 

459 str_quota = str(vvol_quota) + 'G' 

460 command = ['quota', 'add', '--usage-limit', 

461 str_quota, '--usage-hard-limit', 

462 'yes', self.fs_name, vvol_name] 

463 try: 

464 self._execute(command) 

465 except processutils.ProcessExecutionError: 

466 msg = _("Failed to add %(quota)s quota to vvol " 

467 "%(vvol)s.") % {'quota': str_quota, 'vvol': vvol_name} 

468 LOG.exception(msg) 

469 raise exception.HNASBackendException(msg=msg) 

470 

471 def modify_quota(self, vvol_name, new_size): 

472 str_quota = str(new_size) + 'G' 

473 command = ['quota', 'mod', '--usage-limit', str_quota, 

474 self.fs_name, vvol_name] 

475 try: 

476 self._execute(command) 

477 except processutils.ProcessExecutionError: 

478 msg = _("Failed to update quota of vvol %(vvol)s to " 

479 "%(quota)s.") % {'quota': str_quota, 'vvol': vvol_name} 

480 LOG.exception(msg) 

481 raise exception.HNASBackendException(msg=msg) 

482 

483 def check_vvol(self, vvol_name): 

484 command = ['virtual-volume', 'list', '--verbose', self.fs_name, 

485 vvol_name] 

486 try: 

487 self._execute(command) 

488 except processutils.ProcessExecutionError: 

489 msg = _("Virtual volume %s does not exist.") % vvol_name 

490 LOG.exception(msg) 

491 raise exception.HNASItemNotFoundException(msg=msg) 

492 

493 def check_quota(self, vvol_name): 

494 command = ['quota', 'list', '--verbose', self.fs_name, vvol_name] 

495 try: 

496 output, err = self._execute(command) 

497 

498 except processutils.ProcessExecutionError: 

499 msg = _("Could not check quota of vvol %s.") % vvol_name 

500 LOG.exception(msg) 

501 raise exception.HNASBackendException(msg=msg) 

502 

503 if 'No quotas matching specified filter criteria' in output: 503 ↛ exitline 503 didn't return from function 'check_quota' because the condition on line 503 was always true

504 msg = _("Virtual volume %s does not have any" 

505 " quota.") % vvol_name 

506 LOG.error(msg) 

507 raise exception.HNASItemNotFoundException(msg=msg) 

508 

509 def check_export(self, vvol_name, is_snapshot=False): 

510 export = self._get_export(vvol_name, is_snapshot=is_snapshot) 

511 if (vvol_name in export[0].export_name and 

512 self.fs_name in export[0].file_system_label): 

513 return 

514 else: 

515 msg = _("Export %s does not exist.") % export[0].export_name 

516 LOG.error(msg) 

517 raise exception.HNASItemNotFoundException(msg=msg) 

518 

519 def check_cifs(self, vvol_name): 

520 output = self._cifs_list(vvol_name) 

521 

522 cifs_share = CIFSShare(output) 

523 

524 if self.fs_name != cifs_share.fs: 

525 msg = _("CIFS share %(share)s is not located in " 

526 "configured filesystem " 

527 "%(fs)s.") % {'share': vvol_name, 

528 'fs': self.fs_name} 

529 LOG.error(msg) 

530 raise exception.HNASItemNotFoundException(msg=msg) 

531 

532 def is_cifs_in_use(self, vvol_name): 

533 output = self._cifs_list(vvol_name) 

534 

535 cifs_share = CIFSShare(output) 

536 

537 return cifs_share.is_mounted 

538 

539 def _cifs_list(self, vvol_name): 

540 command = ['cifs-share', 'list', vvol_name] 

541 try: 

542 output, err = self._execute(command) 

543 except processutils.ProcessExecutionError as e: 

544 if 'does not exist' in e.stderr: 

545 msg = _("CIFS share %(share)s was not found in EVS " 

546 "%(evs_id)s") % {'share': vvol_name, 

547 'evs_id': self.evs_id} 

548 LOG.exception(msg) 

549 raise exception.HNASItemNotFoundException(msg=msg) 

550 else: 

551 msg = _("Could not list CIFS shares by vvol name " 

552 "%s.") % vvol_name 

553 LOG.exception(msg) 

554 raise exception.HNASBackendException(msg=msg) 

555 

556 return output 

557 

558 def get_share_quota(self, share_id): 

559 command = ['quota', 'list', self.fs_name, share_id] 

560 output, err = self._execute(command) 

561 

562 quota = Quota(output) 

563 

564 if quota.limit is None: 

565 return None 

566 

567 if quota.limit_unit == 'TB': 

568 return quota.limit * units.Ki 

569 elif quota.limit_unit == 'GB': 

570 return quota.limit 

571 else: 

572 msg = _("Share %s does not support quota values " 

573 "below 1G.") % share_id 

574 LOG.error(msg) 

575 raise exception.HNASBackendException(msg=msg) 

576 

577 def get_share_usage(self, share_id): 

578 command = ['quota', 'list', self.fs_name, share_id] 

579 output, err = self._execute(command) 

580 

581 quota = Quota(output) 

582 

583 if quota.usage is None: 

584 msg = _("Virtual volume %s does not have any quota.") % share_id 

585 LOG.error(msg) 

586 raise exception.HNASItemNotFoundException(msg=msg) 

587 else: 

588 bytes_usage = strutils.string_to_bytes(str(quota.usage) + 

589 quota.usage_unit) 

590 return bytes_usage / units.Gi 

591 

592 def _get_export(self, name, is_snapshot=False): 

593 if is_snapshot: 

594 name = '/snapshots/' + name 

595 else: 

596 name = '/shares/' + name 

597 

598 command = ['nfs-export', 'list ', name] 

599 export_list = [] 

600 try: 

601 output, err = self._execute(command) 

602 except processutils.ProcessExecutionError as e: 

603 if 'does not exist' in e.stderr: 

604 msg = _("Export %(name)s was not found in EVS " 

605 "%(evs_id)s.") % { 

606 'name': name, 

607 'evs_id': self.evs_id, 

608 } 

609 LOG.exception(msg) 

610 raise exception.HNASItemNotFoundException(msg=msg) 

611 else: 

612 msg = _("Could not list NFS exports by name %s.") % name 

613 LOG.exception(msg) 

614 raise exception.HNASBackendException(msg=msg) 

615 items = output.split('Export name') 

616 

617 if items[0][0] == '\n': 617 ↛ 620line 617 didn't jump to line 620 because the condition on line 617 was always true

618 items.pop(0) 

619 

620 for i in range(0, len(items)): 

621 export_list.append(Export(items[i])) 

622 return export_list 

623 

624 @mutils.retry(retry_param=exception.HNASConnException, wait_random=True) 

625 def _execute(self, commands): 

626 command = ['ssc', '127.0.0.1'] 

627 if self.admin_ip0 is not None: 627 ↛ 630line 627 didn't jump to line 630 because the condition on line 627 was always true

628 command = ['ssc', '--smuauth', self.admin_ip0] 

629 

630 command += ['console-context', '--evs', self.evs_id] 

631 commands = command + commands 

632 

633 mutils.check_ssh_injection(commands) 

634 commands = ' '.join(commands) 

635 

636 if not self.sshpool: 

637 self.sshpool = ssh_utils.SSHPool(ip=self.ip, 

638 port=self.port, 

639 conn_timeout=None, 

640 login=self.user, 

641 password=self.password, 

642 privatekey=self.priv_key) 

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

644 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 

645 try: 

646 out, err = processutils.ssh_execute(ssh, commands, 

647 check_exit_code=True) 

648 LOG.debug("Command %(cmd)s result: out = %(out)s - err = " 

649 "%(err)s.", { 

650 'cmd': commands, 

651 'out': out, 

652 'err': err, 

653 }) 

654 return out, err 

655 except processutils.ProcessExecutionError as e: 

656 if 'Failed to establish SSC connection' in e.stderr: 

657 msg = _("Failed to establish SSC connection.") 

658 LOG.debug(msg) 

659 raise exception.HNASConnException(msg=msg) 

660 else: 

661 LOG.debug("Error running SSH command. " 

662 "Command %(cmd)s result: out = %(out)s - err = " 

663 "%(err)s - exit = %(exit)s.", { 

664 'cmd': e.cmd, 

665 'out': e.stdout, 

666 'err': e.stderr, 

667 'exit': e.exit_code, 

668 }) 

669 raise 

670 

671 @mutils.synchronized("hitachi_hnas_select_fs", external=True) 

672 def _locked_selectfs(self, op, path): 

673 if op == 'create': 

674 command = ['selectfs', self.fs_name, '\n', 

675 'ssc', '127.0.0.1', 'console-context', '--evs', 

676 self.evs_id, 'mkdir', '-p', path] 

677 try: 

678 self._execute(command) 

679 except processutils.ProcessExecutionError as e: 

680 if "Current file system invalid: VolumeNotFound" in e.stderr: 

681 msg = _("Command to create directory %s failed due to " 

682 "context change.") % path 

683 LOG.debug(msg) 

684 raise exception.HNASSSCContextChange(msg=msg) 

685 else: 

686 msg = _("Failed to create directory %s.") % path 

687 LOG.exception(msg) 

688 raise exception.HNASBackendException(msg=msg) 

689 

690 if op == 'delete': 

691 command = ['selectfs', self.fs_name, '\n', 

692 'ssc', '127.0.0.1', 'console-context', '--evs', 

693 self.evs_id, 'rmdir', path] 

694 try: 

695 self._execute(command) 

696 except processutils.ProcessExecutionError as e: 

697 if 'DirectoryNotEmpty' in e.stderr: 

698 msg = _("Share %s has more snapshots.") % path 

699 LOG.debug(msg) 

700 raise exception.HNASDirectoryNotEmpty(msg=msg) 

701 elif 'cannot remove' in e.stderr and 'NotFound' in e.stderr: 

702 LOG.warning("Attempted to delete path %s but it does " 

703 "not exist.", path) 

704 elif 'Current file system invalid: VolumeNotFound' in e.stderr: 

705 msg = _("Command to delete empty directory %s failed due " 

706 "to context change.") % path 

707 LOG.debug(msg) 

708 raise exception.HNASSSCContextChange(msg=msg) 

709 else: 

710 msg = _("Failed to delete directory %s.") % path 

711 LOG.exception(msg) 

712 raise exception.HNASBackendException(msg=msg) 

713 

714 

715class Export(object): 

716 def __init__(self, data): 

717 if data: 717 ↛ exitline 717 didn't return from function '__init__' because the condition on line 717 was always true

718 split_data = data.split('Export configuration:\n') 

719 items = split_data[0].split('\n') 

720 

721 self.export_name = items[0].split(':')[1].strip() 

722 self.export_path = items[1].split(':')[1].strip() 

723 

724 if '*** not available ***' in items[2]: 

725 self.file_system_info = items[2].split(':')[1].strip() 

726 index = 0 

727 

728 else: 

729 self.file_system_label = items[2].split(':')[1].strip() 

730 self.file_system_size = items[3].split(':')[1].strip() 

731 self.file_system_free_space = items[4].split(':')[1].strip() 

732 self.file_system_state = items[5].split(':')[1] 

733 self.formatted = items[6].split('=')[1].strip() 

734 self.mounted = items[7].split('=')[1].strip() 

735 self.failed = items[8].split('=')[1].strip() 

736 self.thin_provisioned = items[9].split('=')[1].strip() 

737 index = 7 

738 

739 self.access_snapshots = items[3 + index].split(':')[1].strip() 

740 self.display_snapshots = items[4 + index].split(':')[1].strip() 

741 self.read_caching = items[5 + index].split(':')[1].strip() 

742 self.disaster_recovery_setting = items[6 + index].split(':')[1] 

743 self.recovered = items[7 + index].split('=')[1].strip() 

744 self.transfer_setting = items[8 + index].split('=')[1].strip() 

745 

746 self.export_configuration = [] 

747 export_config = split_data[1].split('\n') 

748 for i in range(0, len(export_config)): 

749 if any(j.isdigit() or j.isalpha() for j in export_config[i]): 

750 self.export_configuration.append(export_config[i]) 

751 

752 

753class JobStatus(object): 

754 def __init__(self, data): 

755 if data: 755 ↛ exitline 755 didn't return from function '__init__' because the condition on line 755 was always true

756 lines = data.split("\n") 

757 

758 self.job_id = lines[0].split()[3] 

759 self.physical_node = lines[2].split()[3] 

760 self.evs = lines[3].split()[2] 

761 self.volume_number = lines[4].split()[3] 

762 self.fs_id = lines[5].split()[4] 

763 self.fs_name = lines[6].split()[4] 

764 self.source_path = lines[7].split()[3] 

765 self.creation_time = " ".join(lines[8].split()[3:5]) 

766 self.destination_path = lines[9].split()[3] 

767 self.ensure_path_exists = lines[10].split()[5] 

768 self.job_state = " ".join(lines[12].split()[3:]) 

769 self.job_started = " ".join(lines[14].split()[2:4]) 

770 self.job_ended = " ".join(lines[15].split()[2:4]) 

771 self.job_status = lines[16].split()[2] 

772 

773 error_details_line = lines[17].split() 

774 if len(error_details_line) > 3: 774 ↛ 775line 774 didn't jump to line 775 because the condition on line 774 was never true

775 self.error_details = " ".join(error_details_line[3:]) 

776 else: 

777 self.error_details = None 

778 

779 self.directories_processed = lines[18].split()[3] 

780 self.files_processed = lines[19].split()[3] 

781 self.data_bytes_processed = lines[20].split()[4] 

782 self.directories_missing = lines[21].split()[4] 

783 self.files_missing = lines[22].split()[4] 

784 self.files_skipped = lines[23].split()[4] 

785 

786 skipping_details_line = lines[24].split() 

787 if len(skipping_details_line) > 3: 787 ↛ 790line 787 didn't jump to line 790 because the condition on line 787 was always true

788 self.skipping_details = " ".join(skipping_details_line[3:]) 

789 else: 

790 self.skipping_details = None 

791 

792 

793class JobSubmit(object): 

794 def __init__(self, data): 

795 if data: 795 ↛ exitline 795 didn't return from function '__init__' because the condition on line 795 was always true

796 split_data = data.replace(".", "").split() 

797 

798 self.request_status = " ".join(split_data[1:4]) 

799 self.job_id = split_data[8] 

800 

801 

802class Filesystem(object): 

803 def __init__(self, data): 

804 if data: 804 ↛ exitline 804 didn't return from function '__init__' because the condition on line 804 was always true

805 items = data.split() 

806 self.id = items[0] 

807 self.label = items[1] 

808 self.evs = items[2] 

809 self.size = float(items[3]) 

810 self.size_measure = items[4] 

811 if self.size_measure == 'TB': 

812 self.size = self.size * units.Ki 

813 if items[5:7] == ["Not", "mounted"]: 

814 self.mounted = False 

815 else: 

816 self.mounted = True 

817 self.used = float(items[5]) 

818 self.used_measure = items[6] 

819 if self.used_measure == 'TB': 

820 self.used = self.used * units.Ki 

821 self.dedupe = 'dedupe enabled' in data 

822 

823 

824class Quota(object): 

825 def __init__(self, data): 

826 if data: 826 ↛ exitline 826 didn't return from function '__init__' because the condition on line 826 was always true

827 if 'No quotas matching' in data: 

828 self.type = None 

829 self.target = None 

830 self.usage = None 

831 self.usage_unit = None 

832 self.limit = None 

833 self.limit_unit = None 

834 else: 

835 items = data.split() 

836 self.type = items[2] 

837 self.target = items[6] 

838 self.usage = items[9] 

839 self.usage_unit = items[10] 

840 if items[13] == 'Unset': 

841 self.limit = None 

842 else: 

843 self.limit = float(items[13]) 

844 self.limit_unit = items[14] 

845 

846 

847class CIFSPermissions(object): 

848 def __init__(self, data): 

849 self.permission_list = [] 

850 hnas_cifs_permissions = [('Allow Read', 'ar'), 

851 ('Allow Change & Read', 'acr'), 

852 ('Allow Full Control', 'af'), 

853 ('Deny Read', 'dr'), 

854 ('Deny Change & Read', 'dcr'), 

855 ('Deny Full Control', 'df')] 

856 

857 lines = data.split('\n') 

858 

859 for line in lines: 

860 filtered = list(filter(lambda x: x[0] in line, 

861 hnas_cifs_permissions)) 

862 

863 if len(filtered) == 1: 

864 token, permission = filtered[0] 

865 user = line.split(token)[1:][0].strip() 

866 self.permission_list.append((user, permission)) 

867 

868 

869class CIFSShare(object): 

870 def __init__(self, data): 

871 lines = data.split('\n') 

872 

873 for line in lines: 

874 if 'File system label' in line: 

875 self.fs = line.split(': ')[1] 

876 elif 'Share users' in line: 

877 users = line.split(': ') 

878 self.is_mounted = users[1] != '0'