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
« 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.
16from oslo_concurrency import processutils
17from oslo_log import log
18from oslo_utils import strutils
19from oslo_utils import units
20import paramiko
22import os
23import time
25from manila import exception
26from manila.i18n import _
27from manila import ssh_utils
28from manila import utils as mutils
30LOG = log.getLogger(__name__)
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.")
49 def get_stats(self):
50 """Get the stats from file-system.
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)
61 except processutils.ProcessExecutionError:
62 msg = _("Could not get HNAS backend stats.")
63 LOG.exception(msg)
64 raise exception.HNASBackendException(msg=msg)
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
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)
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)
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)
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)
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)
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)
138 def get_nfs_host_list(self, share_id):
139 export = self._get_export(share_id)
140 return export[0].export_configuration
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)
152 command = ['nfs-export', 'mod', '-c']
154 if len(host_list) == 0:
155 command.append('127.0.0.1')
156 else:
157 string_command = '"' + str(host_list[0])
159 for i in range(1, len(host_list)):
160 string_command += ',' + (str(host_list[i]))
161 string_command += '"'
162 command.append(string_command)
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)
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]
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"
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)
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 })
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)
222 def cifs_deny_access(self, name, user, is_snapshot=False):
223 command = ['cifs-saa', 'delete', '--target-label', self.fs_name,
224 name, user]
226 entity_type = "share"
227 if is_snapshot:
228 entity_type = "snapshot"
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)
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)
266 permissions = CIFSPermissions(output)
268 return permissions.permission_list
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)
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
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"):
299 command = ['tree-clone-job-status', job_id]
300 output, err = self._execute(command)
301 job_status = JobStatus(output)
303 if job_status.job_state == 'Job failed':
304 break
306 old_progress = progress
307 progress = job_status.data_bytes_processed
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
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'):
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)
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)
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)
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)
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]
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
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
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
519 def check_cifs(self, vvol_name):
520 output = self._cifs_list(vvol_name)
522 cifs_share = CIFSShare(output)
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)
532 def is_cifs_in_use(self, vvol_name):
533 output = self._cifs_list(vvol_name)
535 cifs_share = CIFSShare(output)
537 return cifs_share.is_mounted
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)
556 return output
558 def get_share_quota(self, share_id):
559 command = ['quota', 'list', self.fs_name, share_id]
560 output, err = self._execute(command)
562 quota = Quota(output)
564 if quota.limit is None:
565 return None
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)
577 def get_share_usage(self, share_id):
578 command = ['quota', 'list', self.fs_name, share_id]
579 output, err = self._execute(command)
581 quota = Quota(output)
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
592 def _get_export(self, name, is_snapshot=False):
593 if is_snapshot:
594 name = '/snapshots/' + name
595 else:
596 name = '/shares/' + name
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')
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)
620 for i in range(0, len(items)):
621 export_list.append(Export(items[i]))
622 return export_list
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]
630 command += ['console-context', '--evs', self.evs_id]
631 commands = command + commands
633 mutils.check_ssh_injection(commands)
634 commands = ' '.join(commands)
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
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)
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)
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')
721 self.export_name = items[0].split(':')[1].strip()
722 self.export_path = items[1].split(':')[1].strip()
724 if '*** not available ***' in items[2]:
725 self.file_system_info = items[2].split(':')[1].strip()
726 index = 0
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
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()
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])
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")
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]
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
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]
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
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()
798 self.request_status = " ".join(split_data[1:4])
799 self.job_id = split_data[8]
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
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]
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')]
857 lines = data.split('\n')
859 for line in lines:
860 filtered = list(filter(lambda x: x[0] in line,
861 hnas_cifs_permissions))
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))
869class CIFSShare(object):
870 def __init__(self, data):
871 lines = data.split('\n')
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'