Merge "Hitachi HNAS driver refactoring"
This commit is contained in:
commit
1461eb7e4d
|
@ -664,6 +664,14 @@ class HNASConnException(ManilaException):
|
|||
message = _("HNAS Connection Exception: %(msg)s")
|
||||
|
||||
|
||||
class HNASItemNotFoundException(StorageResourceNotFound):
|
||||
message = _("HNAS Item Not Found Exception: %(msg)s")
|
||||
|
||||
|
||||
class HNASNothingToCloneException(ManilaException):
|
||||
message = _("HNAS Nothing To Clone Exception: %(msg)s")
|
||||
|
||||
|
||||
# ConsistencyGroup
|
||||
class ConsistencyGroupNotFound(NotFound):
|
||||
message = _("ConsistencyGroup %(consistency_group_id)s could not be "
|
||||
|
|
|
@ -15,13 +15,15 @@
|
|||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
from oslo_utils import excutils
|
||||
from oslo_utils import importutils
|
||||
import six
|
||||
|
||||
from manila import exception
|
||||
from manila.i18n import _
|
||||
from manila.i18n import _LI
|
||||
from manila.i18n import _LW
|
||||
from manila.share import driver
|
||||
from manila.share.drivers.hitachi import ssh
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
@ -36,7 +38,7 @@ hds_hnas_opts = [
|
|||
secret=True,
|
||||
help="HNAS user password. Required only if private key is not "
|
||||
"provided."),
|
||||
cfg.StrOpt('hds_hnas_evs_id',
|
||||
cfg.IntOpt('hds_hnas_evs_id',
|
||||
help="Specify which EVS this backend is assigned to."),
|
||||
cfg.StrOpt('hds_hnas_evs_ip',
|
||||
help="Specify IP for mounting shares."),
|
||||
|
@ -53,6 +55,9 @@ hds_hnas_opts = [
|
|||
default=30,
|
||||
help="The time (in seconds) to wait for stalled HNAS jobs "
|
||||
"before aborting."),
|
||||
cfg.StrOpt('hds_hnas_driver_helper',
|
||||
default='manila.share.drivers.hitachi.hds_hnas.HDSHNASDriver',
|
||||
help="Python class to be used for driver helper."),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
@ -62,7 +67,8 @@ CONF.register_opts(hds_hnas_opts)
|
|||
class HDSHNASDriver(driver.ShareDriver):
|
||||
"""Manila HNAS Driver implementation.
|
||||
|
||||
1.0 - Initial Version
|
||||
1.0.0 - Initial Version
|
||||
1.1.0 - Refactoring and bugfixes
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
@ -76,12 +82,13 @@ class HDSHNASDriver(driver.ShareDriver):
|
|||
|
||||
LOG.debug("Reading config parameters for Manila HDS HNAS Driver.")
|
||||
self.backend_name = self.configuration.safe_get('share_backend_name')
|
||||
hnas_helper = self.configuration.safe_get('hds_hnas_driver_helper')
|
||||
hnas_ip = self.configuration.safe_get('hds_hnas_ip')
|
||||
hnas_username = self.configuration.safe_get('hds_hnas_user')
|
||||
hnas_password = self.configuration.safe_get('hds_hnas_password')
|
||||
hnas_evs_id = self.configuration.safe_get('hds_hnas_evs_id')
|
||||
self.hnas_evs_ip = self.configuration.safe_get('hds_hnas_evs_ip')
|
||||
fs_name = self.configuration.safe_get('hds_hnas_file_system_name')
|
||||
self.fs_name = self.configuration.safe_get('hds_hnas_file_system_name')
|
||||
ssh_private_key = self.configuration.safe_get(
|
||||
'hds_hnas_ssh_private_key')
|
||||
cluster_admin_ip0 = self.configuration.safe_get(
|
||||
|
@ -90,6 +97,10 @@ class HDSHNASDriver(driver.ShareDriver):
|
|||
job_timeout = self.configuration.safe_get(
|
||||
'hds_hnas_stalled_job_timeout')
|
||||
|
||||
if hnas_helper is None:
|
||||
msg = _("The config parameter hds_hnas_driver_helper is not set.")
|
||||
raise exception.InvalidParameterValue(err=msg)
|
||||
|
||||
if hnas_evs_id is None:
|
||||
msg = _("The config parameter hds_hnas_evs_id is not set.")
|
||||
raise exception.InvalidParameterValue(err=msg)
|
||||
|
@ -114,10 +125,12 @@ class HDSHNASDriver(driver.ShareDriver):
|
|||
|
||||
LOG.debug("Initializing HNAS Layer.")
|
||||
|
||||
self.hnas = ssh.HNASSSHBackend(hnas_ip, hnas_username, hnas_password,
|
||||
ssh_private_key, cluster_admin_ip0,
|
||||
hnas_evs_id, self.hnas_evs_ip, fs_name,
|
||||
job_timeout)
|
||||
helper = importutils.import_class(hnas_helper)
|
||||
|
||||
self.hnas = helper(hnas_ip, hnas_username, hnas_password,
|
||||
ssh_private_key, cluster_admin_ip0,
|
||||
hnas_evs_id, self.hnas_evs_ip, self.fs_name,
|
||||
job_timeout)
|
||||
|
||||
def allow_access(self, context, share, access, share_server=None):
|
||||
"""Allow access to a share.
|
||||
|
@ -140,9 +153,8 @@ class HDSHNASDriver(driver.ShareDriver):
|
|||
|
||||
share_id = self._get_hnas_share_id(share['id'])
|
||||
|
||||
self.hnas.allow_access(share_id, access['access_to'],
|
||||
share['share_proto'],
|
||||
access['access_level'])
|
||||
self._allow_access(share_id, access['access_to'],
|
||||
access['access_level'])
|
||||
|
||||
LOG.info(_LI("Access allowed successfully to share: %(shr)s."),
|
||||
{'shr': six.text_type(share['id'])})
|
||||
|
@ -169,8 +181,8 @@ class HDSHNASDriver(driver.ShareDriver):
|
|||
|
||||
share_id = self._get_hnas_share_id(share['id'])
|
||||
|
||||
self.hnas.deny_access(share_id, access['access_to'],
|
||||
share['share_proto'], access['access_level'])
|
||||
self._deny_access(share_id, access['access_to'],
|
||||
access['access_level'])
|
||||
|
||||
LOG.info(_LI("Access denied successfully to share: %(shr)s."),
|
||||
{'shr': six.text_type(share['id'])})
|
||||
|
@ -192,14 +204,12 @@ class HDSHNASDriver(driver.ShareDriver):
|
|||
msg = _("Only NFS protocol is currently supported.")
|
||||
raise exception.ShareBackendException(msg=msg)
|
||||
|
||||
ip = self.hnas_evs_ip
|
||||
path = self._create_share(share['id'], share['size'])
|
||||
uri = self.hnas_evs_ip + ":" + path
|
||||
|
||||
path = self.hnas.create_share(share['id'], share['size'],
|
||||
share['share_proto'])
|
||||
|
||||
LOG.debug("Share created successfully on path: %(ip)s:%(path)s.",
|
||||
{'ip': ip, 'path': path})
|
||||
return ip + ":" + path
|
||||
LOG.debug("Share created successfully on path: %(uri)s.",
|
||||
{'uri': uri})
|
||||
return uri
|
||||
|
||||
def delete_share(self, context, share, share_server=None):
|
||||
"""Deletes share.
|
||||
|
@ -214,7 +224,7 @@ class HDSHNASDriver(driver.ShareDriver):
|
|||
LOG.debug("Deleting share in HNAS: %(shr)s.",
|
||||
{'shr': six.text_type(share['id'])})
|
||||
|
||||
self.hnas.delete_share(share_id, share['share_proto'])
|
||||
self._delete_share(share_id)
|
||||
|
||||
def create_snapshot(self, context, snapshot, share_server=None):
|
||||
"""Creates snapshot.
|
||||
|
@ -230,7 +240,7 @@ class HDSHNASDriver(driver.ShareDriver):
|
|||
"id %(ss_id)s.", {'ss_sid': snapshot['share_id'],
|
||||
'ss_id': snapshot['id']})
|
||||
|
||||
self.hnas.create_snapshot(share_id, snapshot['id'])
|
||||
self._create_snapshot(share_id, snapshot['id'])
|
||||
LOG.info(_LI("Snapshot %(id)s successfully created."),
|
||||
{'id': snapshot['id']})
|
||||
|
||||
|
@ -248,7 +258,7 @@ class HDSHNASDriver(driver.ShareDriver):
|
|||
"share ID is %(ss_id)s.",
|
||||
{'ss_sid': snapshot['share_id'], 'ss_id': snapshot['id']})
|
||||
|
||||
self.hnas.delete_snapshot(share_id, snapshot['id'])
|
||||
self._delete_snapshot(share_id, snapshot['id'])
|
||||
LOG.info(_LI("Snapshot %(id)s successfully deleted."),
|
||||
{'id': snapshot['id']})
|
||||
|
||||
|
@ -268,12 +278,12 @@ class HDSHNASDriver(driver.ShareDriver):
|
|||
LOG.debug("Creating a new share from snapshot: %(ss_id)s.",
|
||||
{'ss_id': six.text_type(snapshot['id'])})
|
||||
|
||||
ip = self.hnas_evs_ip
|
||||
path = self.hnas.create_share_from_snapshot(share, snapshot)
|
||||
path = self._create_share_from_snapshot(share, snapshot)
|
||||
uri = self.hnas_evs_ip + ":" + path
|
||||
|
||||
LOG.debug("Share created successfully on path: %(ip)s:%(path)s.",
|
||||
{'ip': ip, 'path': path})
|
||||
return ip + ":" + path
|
||||
LOG.debug("Share created successfully on path: %(uri)s.",
|
||||
{'uri': uri})
|
||||
return uri
|
||||
|
||||
def ensure_share(self, context, share, share_server=None):
|
||||
"""Ensure that share is exported.
|
||||
|
@ -288,11 +298,9 @@ class HDSHNASDriver(driver.ShareDriver):
|
|||
LOG.debug("Ensuring share in HNAS: %(shr)s.",
|
||||
{'shr': six.text_type(share['id'])})
|
||||
|
||||
if share['share_proto'].lower() != 'nfs':
|
||||
msg = _("Only NFS protocol is currently supported.")
|
||||
raise exception.ShareBackendException(msg=msg)
|
||||
share_id = self._get_hnas_share_id(share['id'])
|
||||
|
||||
path = self.hnas.ensure_share(share['id'], share['share_proto'])
|
||||
path = self._ensure_share(share_id)
|
||||
|
||||
export = self.hnas_evs_ip + ":" + path
|
||||
export_list = [export]
|
||||
|
@ -314,11 +322,7 @@ class HDSHNASDriver(driver.ShareDriver):
|
|||
LOG.debug("Expanding share in HNAS: %(shr_id)s.",
|
||||
{'shr_id': six.text_type(share['id'])})
|
||||
|
||||
if share['share_proto'].lower() != 'nfs':
|
||||
msg = _("Only NFS protocol is currently supported.")
|
||||
raise exception.ShareBackendException(msg=msg)
|
||||
|
||||
self.hnas.extend_share(share_id, new_size, share['share_proto'])
|
||||
self._extend_share(share_id, share['size'], new_size)
|
||||
LOG.info(_LI("Share %(shr_id)s successfully extended to "
|
||||
"%(shr_size)s."),
|
||||
{'shr_id': six.text_type(share['id']),
|
||||
|
@ -334,7 +338,7 @@ class HDSHNASDriver(driver.ShareDriver):
|
|||
"""
|
||||
return 0
|
||||
|
||||
def _update_share_stats(self):
|
||||
def _update_share_stats(self, data=None):
|
||||
"""Updates the Capability of Backend."""
|
||||
LOG.debug("Updating Backend Capability Information - HDS HNAS.")
|
||||
|
||||
|
@ -369,10 +373,14 @@ class HDSHNASDriver(driver.ShareDriver):
|
|||
"""
|
||||
share_id = self._get_hnas_share_id(share['id'])
|
||||
|
||||
if share_id != share['id']:
|
||||
msg = _("Share ID %s already exists, cannot manage.") % share_id
|
||||
raise exception.HNASBackendException(msg=msg)
|
||||
|
||||
LOG.info(_LI("Share %(shr_path)s will be managed with ID %(shr_id)s."),
|
||||
{'shr_path': six.text_type(
|
||||
share['export_locations'][0]['path']),
|
||||
'shr_id': six.text_type(share_id)})
|
||||
'shr_id': six.text_type(share['id'])})
|
||||
|
||||
old_path_info = share['export_locations'][0]['path'].split(':')
|
||||
old_path = old_path_info[1].split('/')
|
||||
|
@ -395,7 +403,7 @@ class HDSHNASDriver(driver.ShareDriver):
|
|||
"not configured.") % {'shr': share['host']}
|
||||
raise exception.ShareBackendException(msg=msg)
|
||||
|
||||
output = self.hnas.manage_existing(share, share_id)
|
||||
output = self._manage_existing(share_id)
|
||||
self.private_storage.update(
|
||||
share['id'], {'hnas_id': share_id})
|
||||
|
||||
|
@ -408,11 +416,15 @@ class HDSHNASDriver(driver.ShareDriver):
|
|||
"""
|
||||
self.private_storage.delete(share['id'])
|
||||
|
||||
LOG.info(_LI("The share with current path %(shr_path)s and ID "
|
||||
"%(shr_id)s is no longer being managed."),
|
||||
{'shr_path': six.text_type(
|
||||
share['export_locations'][0]['path']),
|
||||
'shr_id': six.text_type(share['id'])})
|
||||
if len(share['export_locations']) == 0:
|
||||
LOG.info(_LI("The share with ID %(shr_id)s is no longer being "
|
||||
"managed."), {'shr_id': six.text_type(share['id'])})
|
||||
else:
|
||||
LOG.info(_LI("The share with current path %(shr_path)s and ID "
|
||||
"%(shr_id)s is no longer being managed."),
|
||||
{'shr_path': six.text_type(
|
||||
share['export_locations'][0]['path']),
|
||||
'shr_id': six.text_type(share['id'])})
|
||||
|
||||
def _get_hnas_share_id(self, share_id):
|
||||
hnas_id = self.private_storage.get(share_id, 'hnas_id')
|
||||
|
@ -420,3 +432,244 @@ class HDSHNASDriver(driver.ShareDriver):
|
|||
if hnas_id is None:
|
||||
hnas_id = share_id
|
||||
return hnas_id
|
||||
|
||||
def _create_share(self, share_id, share_size):
|
||||
"""Creates share.
|
||||
|
||||
Creates a virtual-volume, adds a quota limit and exports it.
|
||||
:param share_id: ID of share that will be created.
|
||||
:param share_size: Size limit of share.
|
||||
:returns: Returns a path of /shares/share_id if the export was
|
||||
created successfully.
|
||||
"""
|
||||
path = '/shares/' + share_id
|
||||
|
||||
self._check_fs_mounted()
|
||||
|
||||
self.hnas.vvol_create(share_id)
|
||||
|
||||
self.hnas.quota_add(share_id, share_size)
|
||||
|
||||
LOG.debug("Share created with id %(shr)s, size %(size)sG.",
|
||||
{'shr': share_id, 'size': share_size})
|
||||
|
||||
try:
|
||||
# Create NFS export
|
||||
self.hnas.nfs_export_add(share_id)
|
||||
LOG.debug("NFS Export created to %(shr)s.",
|
||||
{'shr': share_id})
|
||||
return path
|
||||
except exception.HNASBackendException as e:
|
||||
with excutils.save_and_reraise_exception():
|
||||
self.hnas.vvol_delete(share_id)
|
||||
msg = six.text_type(e)
|
||||
LOG.exception(msg)
|
||||
|
||||
def _check_fs_mounted(self):
|
||||
if not self.hnas.check_fs_mounted():
|
||||
LOG.debug("Filesystem %(fs)s is unmounted. Mounting...",
|
||||
{'fs': self.fs_name})
|
||||
self.hnas.mount()
|
||||
|
||||
def _allow_access(self, share_id, host, permission='rw'):
|
||||
"""Allow access to the share.
|
||||
|
||||
:param share_id: ID of share that access will be allowed.
|
||||
:param host: Host to which access will be allowed.
|
||||
:param permission: permission (e.g. 'rw', 'ro') that will be allowed.
|
||||
"""
|
||||
# check if the share exists
|
||||
self._ensure_share(share_id)
|
||||
|
||||
# get the list that contains all the hosts allowed on the share
|
||||
host_list = self.hnas.get_host_list(share_id)
|
||||
|
||||
if permission in ('ro', 'rw'):
|
||||
host_access = host + '(' + permission + ')'
|
||||
else:
|
||||
msg = (_("Permission should be 'ro' or 'rw' instead "
|
||||
"of %s") % permission)
|
||||
raise exception.HNASBackendException(msg=msg)
|
||||
|
||||
# check if the host(s) is already allowed
|
||||
if any(host in x for x in host_list):
|
||||
if host_access in host_list:
|
||||
LOG.debug("Host: %(host)s is already allowed.",
|
||||
{'host': host})
|
||||
else:
|
||||
# remove all the hosts with different permissions
|
||||
host_list = [
|
||||
x for x in host_list if not x.startswith(host)]
|
||||
# add the host with new permission
|
||||
host_list.append(host_access)
|
||||
self.hnas.update_access_rule(share_id, host_list)
|
||||
else:
|
||||
host_list.append(host_access)
|
||||
self.hnas.update_access_rule(share_id, host_list)
|
||||
|
||||
def _deny_access(self, share_id, host, permission):
|
||||
"""Deny access to the share.
|
||||
|
||||
:param share_id: ID of share that access will be denied.
|
||||
:param host: Host to which access will be denied.
|
||||
:param permission: permission (e.g. 'rw', 'ro') that will be denied.
|
||||
"""
|
||||
# check if the share exists
|
||||
self._ensure_share(share_id)
|
||||
|
||||
# get the list that contains all the hosts allowed on the share
|
||||
host_list = self.hnas.get_host_list(share_id)
|
||||
|
||||
if permission in ('ro', 'rw'):
|
||||
host_access = host + '(' + permission + ')'
|
||||
else:
|
||||
msg = (_("Permission should be 'ro' or 'rw' instead "
|
||||
"of %s") % permission)
|
||||
raise exception.HNASBackendException(msg=msg)
|
||||
|
||||
# check if the host(s) is already not allowed
|
||||
if host_access not in host_list:
|
||||
LOG.debug("Host: %(host)s is already not allowed.",
|
||||
{'host': host})
|
||||
else:
|
||||
# remove the host on host_list
|
||||
host_list.remove(host_access)
|
||||
self.hnas.update_access_rule(share_id, host_list)
|
||||
|
||||
def _ensure_share(self, share_id):
|
||||
"""Ensure that share is exported.
|
||||
|
||||
:param share_id: ID of share that will be checked.
|
||||
:returns: Returns a path of /shares/share_id if the export is ok.
|
||||
"""
|
||||
path = '/shares/' + share_id
|
||||
|
||||
self._check_fs_mounted()
|
||||
|
||||
self.hnas.check_vvol(share_id)
|
||||
self.hnas.check_quota(share_id)
|
||||
self.hnas.check_export(share_id)
|
||||
return path
|
||||
|
||||
def _extend_share(self, share_id, old_size, new_size):
|
||||
"""Extends a share to new size.
|
||||
|
||||
:param share_id: ID of share that will be extended.
|
||||
:param old_size: Current size of share that will be extended.
|
||||
:param new_size: New size of share after extend operation.
|
||||
"""
|
||||
self._ensure_share(share_id)
|
||||
|
||||
total, available_space = self.hnas.get_stats()
|
||||
|
||||
LOG.debug("Available space in filesystem: %(space)sG.",
|
||||
{'space': available_space})
|
||||
|
||||
if (new_size - old_size) < available_space:
|
||||
self.hnas.modify_quota(share_id, new_size)
|
||||
else:
|
||||
msg = (_("Share %s cannot be extended due to insufficient space.")
|
||||
% share_id)
|
||||
raise exception.HNASBackendException(msg=msg)
|
||||
|
||||
def _delete_share(self, share_id):
|
||||
"""Deletes share.
|
||||
|
||||
It uses tree-delete-job-submit to format and delete virtual-volumes.
|
||||
Quota is deleted with virtual-volume.
|
||||
:param share_id: ID of share that will be deleted.
|
||||
"""
|
||||
self._check_fs_mounted()
|
||||
|
||||
self.hnas.nfs_export_del(share_id)
|
||||
self.hnas.vvol_delete(share_id)
|
||||
|
||||
LOG.debug("Export and share successfully deleted: %(shr)s on Manila.",
|
||||
{'shr': share_id})
|
||||
|
||||
def _manage_existing(self, share_id):
|
||||
"""Manages a share that exists on backend.
|
||||
|
||||
:param share_id: ID of share that will be managed.
|
||||
:returns: Returns a dict with size of share managed
|
||||
and its location (your path in file-system).
|
||||
"""
|
||||
self._ensure_share(share_id)
|
||||
|
||||
share_size = self.hnas.get_share_quota(share_id)
|
||||
if share_size is None:
|
||||
msg = (_("The share %s trying to be managed does not have a "
|
||||
"quota limit, please set it before manage.") % share_id)
|
||||
raise exception.ManageInvalidShare(msg)
|
||||
|
||||
path = six.text_type(self.hnas_evs_ip) + ':/shares/' + share_id
|
||||
|
||||
return {'size': share_size, 'export_locations': [path]}
|
||||
|
||||
def _create_snapshot(self, share_id, snapshot_id):
|
||||
"""Creates a snapshot of share.
|
||||
|
||||
It copies the directory and all files to a new directory inside
|
||||
/snapshots/share_id/.
|
||||
:param share_id: ID of share for snapshot.
|
||||
:param snapshot_id: ID of new snapshot.
|
||||
"""
|
||||
|
||||
saved_list = self.hnas.get_host_list(share_id)
|
||||
new_list = []
|
||||
for access in saved_list:
|
||||
new_list.append(access.replace('(rw)', '(ro)'))
|
||||
self.hnas.update_access_rule(share_id, new_list)
|
||||
|
||||
src_path = '/shares/' + share_id
|
||||
dest_path = '/snapshots/' + share_id + '/' + snapshot_id
|
||||
try:
|
||||
self.hnas.tree_clone(src_path, dest_path)
|
||||
except exception.HNASNothingToCloneException:
|
||||
LOG.warning(_LW("Source directory is empty, creating an empty "
|
||||
"directory."))
|
||||
self.hnas.create_directory(dest_path)
|
||||
finally:
|
||||
self.hnas.update_access_rule(share_id, saved_list)
|
||||
|
||||
def _delete_snapshot(self, share_id, snapshot_id):
|
||||
"""Deletes snapshot.
|
||||
|
||||
It receives the share_id only to mount the path for snapshot.
|
||||
:param share_id: ID of share that snapshot was created.
|
||||
:param snapshot_id: ID of snapshot.
|
||||
"""
|
||||
path = '/snapshots/' + share_id + '/' + snapshot_id
|
||||
self.hnas.tree_delete(path)
|
||||
path = '/snapshots/' + share_id
|
||||
self.hnas.delete_directory(path)
|
||||
|
||||
def _create_share_from_snapshot(self, share, snapshot):
|
||||
"""Creates a new share from snapshot.
|
||||
|
||||
It copies everything from snapshot directory to a new vvol,
|
||||
set a quota limit for it and export.
|
||||
:param share: a dict from new share.
|
||||
:param snapshot: a dict from snapshot that will be copied to
|
||||
new share.
|
||||
:returns: Returns the path for new share.
|
||||
"""
|
||||
dest_path = '/shares/' + share['id']
|
||||
src_path = '/snapshots/' + snapshot['share_id'] + '/' + snapshot['id']
|
||||
|
||||
# Before copying everything to new vvol, we need to create it,
|
||||
# because we only can transform an empty directory into a vvol.
|
||||
|
||||
self._check_fs_mounted()
|
||||
|
||||
self.hnas.vvol_create(share['id'])
|
||||
|
||||
self.hnas.quota_add(share['id'], share['size'])
|
||||
|
||||
try:
|
||||
self.hnas.tree_clone(src_path, dest_path)
|
||||
except exception.HNASNothingToCloneException:
|
||||
LOG.warning(_LW("Source directory is empty, exporting "
|
||||
"directory."))
|
||||
self.hnas.nfs_export_add(share['id'])
|
||||
return dest_path
|
||||
|
|
|
@ -44,6 +44,7 @@ class HNASSSHBackend(object):
|
|||
self.evs_ip = evs_ip
|
||||
self.sshpool = None
|
||||
self.job_timeout = job_timeout
|
||||
LOG.debug("Hitachi HNAS Driver using SSH backend.")
|
||||
|
||||
def get_stats(self):
|
||||
"""Get the stats from file-system.
|
||||
|
@ -60,354 +61,286 @@ class HNASSSHBackend(object):
|
|||
|
||||
available_space = fs_capacity.size - fs_capacity.used
|
||||
|
||||
LOG.debug("Total space in file system: %(total)s GB.",
|
||||
{'total': fs_capacity.size})
|
||||
LOG.debug("Used space in the file system: %(used)s GB.",
|
||||
{'used': fs_capacity.used})
|
||||
LOG.debug("Available space in the file system: %(space)s GB.",
|
||||
{'space': available_space})
|
||||
|
||||
return fs_capacity.size, available_space
|
||||
|
||||
def allow_access(self, share_id, host, share_proto, permission='rw'):
|
||||
"""Allow access to the share.
|
||||
|
||||
:param share_id: ID of share that access will be allowed.
|
||||
:param host: Host to which access will be allowed.
|
||||
:param share_proto: Storage protocol of share. Currently,
|
||||
only NFS storage protocol is supported.
|
||||
:param permission: permission (e.g. 'rw', 'ro') that will be allowed.
|
||||
"""
|
||||
# check if the share exists
|
||||
self.ensure_share(share_id, share_proto)
|
||||
export = self._nfs_export_list(share_id)
|
||||
|
||||
# get the list that contains all the hosts allowed on the share
|
||||
host_list = export[0].export_configuration
|
||||
|
||||
if permission in ('ro', 'rw'):
|
||||
host_access = host + '(' + permission + ')'
|
||||
else:
|
||||
msg = (_("Permission should be 'ro' or 'rw' instead "
|
||||
"of %s") % permission)
|
||||
raise exception.HNASBackendException(msg=msg)
|
||||
|
||||
# check if the host(s) is already allowed
|
||||
if any(host in x for x in host_list):
|
||||
if host_access in host_list:
|
||||
LOG.debug("Host: %(host)s is already allowed.",
|
||||
{'host': host})
|
||||
else:
|
||||
# remove all the hosts with different permissions
|
||||
host_list = [
|
||||
x for x in host_list if not x.startswith(host)]
|
||||
# add the host with new permission
|
||||
host_list.append(host_access)
|
||||
self._update_access_rule(share_id, host_list)
|
||||
else:
|
||||
host_list.append(host_access)
|
||||
self._update_access_rule(share_id, host_list)
|
||||
|
||||
def deny_access(self, share_id, host, share_proto, permission):
|
||||
"""Deny access to the share.
|
||||
|
||||
:param share_id: ID of share that access will be denied.
|
||||
:param host: Host to which access will be denied.
|
||||
:param share_proto: Storage protocol of share. Currently,
|
||||
only NFS storage protocol is supported.
|
||||
:param permission: permission (e.g. 'rw', 'ro') that will be denied.
|
||||
"""
|
||||
# check if the share exists
|
||||
self.ensure_share(share_id, share_proto)
|
||||
export = self._nfs_export_list(share_id)
|
||||
|
||||
# get the list that contains all the hosts allowed on the share
|
||||
host_list = export[0].export_configuration
|
||||
|
||||
if permission in ('ro', 'rw'):
|
||||
host_access = host + '(' + permission + ')'
|
||||
else:
|
||||
msg = (_("Permission should be 'ro' or 'rw' instead "
|
||||
"of %s") % permission)
|
||||
raise exception.HNASBackendException(msg=msg)
|
||||
|
||||
# check if the host(s) is already not allowed
|
||||
if host_access not in host_list:
|
||||
LOG.debug("Host: %(host)s is already not allowed.",
|
||||
{'host': host})
|
||||
else:
|
||||
# remove the host on host_list
|
||||
host_list.remove(host_access)
|
||||
self._update_access_rule(share_id, host_list)
|
||||
|
||||
def delete_share(self, share_id, share_proto):
|
||||
"""Deletes share.
|
||||
|
||||
It uses tree-delete-job-submit to format and delete virtual-volumes.
|
||||
Quota is deleted with virtual-volume.
|
||||
:param share_id: ID of share that will be deleted.
|
||||
:param share_proto: Storage protocol of share. Currently,
|
||||
only NFS storage protocol is supported.
|
||||
"""
|
||||
try:
|
||||
self.ensure_share(share_id, share_proto)
|
||||
except exception.HNASBackendException as e:
|
||||
LOG.warning(_LW("Share %s does not exist on backend anymore."),
|
||||
share_id)
|
||||
LOG.exception(six.text_type(e))
|
||||
|
||||
self._nfs_export_del(share_id)
|
||||
self._vvol_delete(share_id)
|
||||
|
||||
LOG.debug("Export and share successfully deleted: %(shr)s on Manila.",
|
||||
{'shr': share_id})
|
||||
|
||||
def ensure_share(self, share_id, share_proto):
|
||||
"""Ensure that share is exported.
|
||||
|
||||
:param share_id: ID of share that will be checked.
|
||||
:param share_proto: Storage protocol of share. Currently,
|
||||
only NFS storage protocol is supported.
|
||||
:returns: Returns a path of /shares/share_id if the export is ok.
|
||||
"""
|
||||
def nfs_export_add(self, share_id):
|
||||
path = '/shares/' + share_id
|
||||
command = ['nfs-export', 'add', '-S', 'disable', '-c', '127.0.0.1',
|
||||
path, self.fs_name, path]
|
||||
self._execute(command)
|
||||
|
||||
if not self._check_fs_mounted(self.fs_name):
|
||||
self._mount(self.fs_name)
|
||||
LOG.debug("Filesystem %(fs)s is unmounted. Mounting...",
|
||||
{'fs': self.fs_name})
|
||||
self._check_vvol(share_id)
|
||||
self._check_quota(share_id)
|
||||
self._check_export(share_id)
|
||||
return path
|
||||
|
||||
def create_share(self, share_id, share_size, share_proto):
|
||||
"""Creates share.
|
||||
|
||||
Creates a virtual-volume, adds a quota limit and exports it.
|
||||
:param share_id: ID of share that will be created.
|
||||
:param share_size: Size limit of share.
|
||||
:param share_proto: Storage protocol of share. Currently,
|
||||
only NFS storage protocol is supported.
|
||||
:returns: Returns a path of /shares/share_id if the export was
|
||||
created successfully.
|
||||
"""
|
||||
def nfs_export_del(self, share_id):
|
||||
path = '/shares/' + share_id
|
||||
self._vvol_create(share_id, share_size)
|
||||
LOG.debug("Share created with id %(shr)s, size %(size)sG.",
|
||||
{'shr': share_id, 'size': share_size})
|
||||
command = ['nfs-export', 'del', path]
|
||||
try:
|
||||
# Create NFS export
|
||||
self._nfs_export_add(share_id)
|
||||
LOG.debug("NFS Export created to %(shr)s.",
|
||||
{'shr': share_id})
|
||||
return path
|
||||
self._execute(command)
|
||||
except processutils.ProcessExecutionError as e:
|
||||
self._vvol_delete(share_id)
|
||||
msg = six.text_type(e)
|
||||
LOG.exception(msg)
|
||||
raise e
|
||||
|
||||
def extend_share(self, share_id, share_size, share_proto):
|
||||
"""Extends a share to new size.
|
||||
|
||||
:param share_id: ID of share that will be extended.
|
||||
:param share_size: New size of share.
|
||||
:param share_proto: Storage protocol of share. Currently,
|
||||
only NFS storage protocol is supported.
|
||||
"""
|
||||
self.ensure_share(share_id, share_proto)
|
||||
|
||||
total, available_space = self.get_stats()
|
||||
|
||||
LOG.debug("Available space in filesystem: %(space)s.",
|
||||
{'space': available_space})
|
||||
|
||||
if share_size < available_space:
|
||||
self._extend_quota(share_id, share_size)
|
||||
else:
|
||||
msg = (_("Failed to extend share %s.") % share_id)
|
||||
raise exception.HNASBackendException(msg=msg)
|
||||
|
||||
def manage_existing(self, share_proto, share_id):
|
||||
"""Manages a share that exists on backend.
|
||||
|
||||
:param share_proto: Storage protocol of share. Currently,
|
||||
only NFS storage protocol is supported.
|
||||
:param share_id: ID of share that will be managed.
|
||||
:returns: Returns a dict with size of share managed
|
||||
and its location (your path in file-system).
|
||||
"""
|
||||
self.ensure_share(share_id, share_proto)
|
||||
|
||||
share_size = self._get_share_quota(share_id)
|
||||
if share_size is None:
|
||||
msg = (_("The share %s trying to be managed does not have a "
|
||||
"quota limit, please set it before manage.") % share_id)
|
||||
raise exception.HNASBackendException(msg=msg)
|
||||
|
||||
path = six.text_type(self.evs_ip) + ':/shares/' + share_id
|
||||
|
||||
return {'size': share_size, 'export_locations': [path]}
|
||||
|
||||
def create_snapshot(self, share_id, snapshot_id):
|
||||
"""Creates a snapshot of share.
|
||||
|
||||
It copies the directory and all files to a new directory inside
|
||||
/snapshots/share_id/.
|
||||
:param share_id: ID of share for snapshot.
|
||||
:param snapshot_id: ID of new snapshot.
|
||||
"""
|
||||
|
||||
export = self._nfs_export_list(share_id)
|
||||
saved_list = export[0].export_configuration
|
||||
new_list = []
|
||||
for access in saved_list:
|
||||
new_list.append(access.replace('(rw)', '(ro)'))
|
||||
self._update_access_rule(share_id, new_list)
|
||||
|
||||
src_path = '/shares/' + share_id
|
||||
snap_path = '/snapshots/' + share_id + '/' + snapshot_id
|
||||
|
||||
try:
|
||||
command = ['tree-clone-job-submit', '-e', '-f', self.fs_name,
|
||||
src_path, snap_path]
|
||||
|
||||
output, err = self._execute(command)
|
||||
job_submit = JobSubmit(output)
|
||||
if job_submit.request_status == 'Request submitted successfully':
|
||||
job_id = job_submit.job_id
|
||||
|
||||
job_status = None
|
||||
progress = ''
|
||||
job_rechecks = 0
|
||||
starttime = time.time()
|
||||
deadline = starttime + self.job_timeout
|
||||
while not job_status or \
|
||||
job_status.job_state != "Job was completed":
|
||||
|
||||
command = ['tree-clone-job-status', job_id]
|
||||
output, err = self._execute(command)
|
||||
job_status = JobStatus(output)
|
||||
|
||||
if job_status.job_state == 'Job failed':
|
||||
break
|
||||
|
||||
old_progress = progress
|
||||
progress = job_status.data_bytes_processed
|
||||
|
||||
if old_progress == progress:
|
||||
job_rechecks += 1
|
||||
now = time.time()
|
||||
if now > deadline:
|
||||
command = ['tree-clone-job-abort', job_id]
|
||||
output, err = self._execute(command)
|
||||
LOG.error(_LE("Timeout in snapshot %s creation.") %
|
||||
snapshot_id)
|
||||
msg = (_("Share snapshot %s was not created.")
|
||||
% snapshot_id)
|
||||
raise exception.HNASBackendException(msg=msg)
|
||||
else:
|
||||
time.sleep(job_rechecks ** 2)
|
||||
else:
|
||||
job_rechecks = 0
|
||||
|
||||
if (job_status.job_state, job_status.job_status,
|
||||
job_status.directories_missing,
|
||||
job_status.files_missing) == ("Job was completed",
|
||||
"Success", '0', '0'):
|
||||
|
||||
LOG.debug("Snapshot %(snapshot_id)s from share "
|
||||
"%(share_id)s created successfully.",
|
||||
{'snapshot_id': snapshot_id,
|
||||
'share_id': share_id})
|
||||
else:
|
||||
LOG.error(_LE('Error in snapshot %s creation.'),
|
||||
snapshot_id)
|
||||
msg = (_('Share snapshot %s was not created.') %
|
||||
snapshot_id)
|
||||
raise exception.HNASBackendException(msg=msg)
|
||||
|
||||
except processutils.ProcessExecutionError as e:
|
||||
if ('Cannot find any clonable files in the source directory' in
|
||||
e.stderr):
|
||||
|
||||
LOG.warning(_LW("Source directory is empty, creating an empty "
|
||||
"snapshot."))
|
||||
self._locked_selectfs('create', snap_path)
|
||||
if 'does not exist' in e.stderr:
|
||||
LOG.warning(_LW("Export %s does not exist on "
|
||||
"backend anymore."), path)
|
||||
else:
|
||||
msg = six.text_type(e)
|
||||
LOG.exception(msg)
|
||||
raise exception.HNASBackendException(msg=msg)
|
||||
finally:
|
||||
self._update_access_rule(share_id, saved_list)
|
||||
|
||||
def delete_snapshot(self, share_id, snapshot_id):
|
||||
"""Deletes snapshot.
|
||||
def get_host_list(self, share_id):
|
||||
export = self._get_share_export(share_id)
|
||||
return export[0].export_configuration
|
||||
|
||||
It receives the share_id only to mount the path for snapshot.
|
||||
:param share_id: ID of share that snapshot was created.
|
||||
:param snapshot_id: ID of snapshot.
|
||||
"""
|
||||
path = '/snapshots/' + share_id + '/' + snapshot_id
|
||||
command = ['tree-delete-job-submit', '--confirm', '-f', self.fs_name,
|
||||
path]
|
||||
def update_access_rule(self, share_id, host_list):
|
||||
command = ['nfs-export', 'mod', '-c']
|
||||
|
||||
if len(host_list) == 0:
|
||||
command.append('127.0.0.1')
|
||||
else:
|
||||
string_command = '"' + six.text_type(host_list[0])
|
||||
|
||||
for i in range(1, len(host_list)):
|
||||
string_command += ',' + (six.text_type(host_list[i]))
|
||||
string_command += '"'
|
||||
command.append(string_command)
|
||||
|
||||
path = '/shares/' + share_id
|
||||
command.append(path)
|
||||
self._execute(command)
|
||||
|
||||
def tree_clone(self, src_path, dest_path):
|
||||
command = ['tree-clone-job-submit', '-e', '-f', self.fs_name,
|
||||
src_path, dest_path]
|
||||
try:
|
||||
output, err = self._execute(command)
|
||||
path = '/snapshots/' + share_id
|
||||
if 'Request submitted successfully' in output:
|
||||
self._locked_selectfs('delete', path)
|
||||
|
||||
except processutils.ProcessExecutionError as e:
|
||||
if 'Source path: Cannot access' not in e.stderr:
|
||||
msg = six.text_type(e)
|
||||
LOG.exception(msg)
|
||||
raise e
|
||||
|
||||
def create_share_from_snapshot(self, share, snapshot):
|
||||
"""Creates a new share from snapshot.
|
||||
|
||||
It copies everything from snapshot directory to a new vvol,
|
||||
set a quota limit for it and export.
|
||||
:param share: a dict from new share.
|
||||
:param snapshot: a dict from snapshot that will be copied to
|
||||
new share.
|
||||
:returns: Returns the path for new share.
|
||||
"""
|
||||
output = ''
|
||||
dst_path = '/shares/' + share['id']
|
||||
src_path = '/snapshots/' + snapshot['share_id'] + '/' + snapshot['id']
|
||||
|
||||
# Before copying everything to new vvol, we need to create it,
|
||||
# because we only can transform an empty directory into a vvol.
|
||||
|
||||
self._vvol_create(share['id'], share['size'])
|
||||
|
||||
try:
|
||||
# Copy the directory to new vvol
|
||||
# Syntax: tree-clone-job-submit <source-directory> <new-share>
|
||||
LOG.debug("Started share create from: %(shr)s.",
|
||||
{'shr': six.text_type(snapshot['share_id'])})
|
||||
command = ['tree-clone-job-submit', '-f', self.fs_name,
|
||||
src_path, dst_path]
|
||||
output, err = self._execute(command)
|
||||
except processutils.ProcessExecutionError as e:
|
||||
if ('Cannot find any clonable files in the source directory' in
|
||||
e.stderr):
|
||||
LOG.warning(_LW("Source directory is empty, exporting "
|
||||
"directory."))
|
||||
if self._nfs_export_add(share['id']):
|
||||
return dst_path
|
||||
msg = _("Source path %s is empty") % src_path
|
||||
raise exception.HNASNothingToCloneException(msg)
|
||||
else:
|
||||
msg = six.text_type(e)
|
||||
LOG.exception(msg)
|
||||
raise exception.HNASBackendException(msg=msg)
|
||||
|
||||
if 'Request submitted successfully' in output:
|
||||
# Create NFS export
|
||||
if self._nfs_export_add(share['id']):
|
||||
# Return export path
|
||||
return dst_path
|
||||
job_submit = JobSubmit(output)
|
||||
if job_submit.request_status == 'Request submitted successfully':
|
||||
job_id = job_submit.job_id
|
||||
|
||||
job_status = None
|
||||
progress = ''
|
||||
job_rechecks = 0
|
||||
starttime = time.time()
|
||||
deadline = starttime + self.job_timeout
|
||||
while (not job_status or
|
||||
job_status.job_state != "Job was completed"):
|
||||
|
||||
command = ['tree-clone-job-status', job_id]
|
||||
output, err = self._execute(command)
|
||||
job_status = JobStatus(output)
|
||||
|
||||
if job_status.job_state == 'Job failed':
|
||||
break
|
||||
|
||||
old_progress = progress
|
||||
progress = job_status.data_bytes_processed
|
||||
|
||||
if old_progress == progress:
|
||||
job_rechecks += 1
|
||||
now = time.time()
|
||||
if now > deadline:
|
||||
command = ['tree-clone-job-abort', job_id]
|
||||
self._execute(command)
|
||||
LOG.error(_LE("Timeout in snapshot creation from "
|
||||
"source path %s.") % src_path)
|
||||
msg = (_("Share snapshot of source path %s "
|
||||
"was not created.") % src_path)
|
||||
raise exception.HNASBackendException(msg=msg)
|
||||
else:
|
||||
time.sleep(job_rechecks ** 2)
|
||||
else:
|
||||
job_rechecks = 0
|
||||
|
||||
if (job_status.job_state, job_status.job_status,
|
||||
job_status.directories_missing,
|
||||
job_status.files_missing) == ("Job was completed",
|
||||
"Success", '0', '0'):
|
||||
|
||||
LOG.debug("Snapshot of source path %(src)s to destination"
|
||||
"path %(dest)s created successfully.",
|
||||
{'src': src_path,
|
||||
'dest': dest_path})
|
||||
else:
|
||||
LOG.error(_LE('Error creating snapshot of source path %s.'),
|
||||
src_path)
|
||||
msg = (_('Snapshot of source path %s was not created.') %
|
||||
src_path)
|
||||
raise exception.HNASBackendException(msg=msg)
|
||||
|
||||
def tree_delete(self, path):
|
||||
command = ['tree-delete-job-submit', '--confirm', '-f', self.fs_name,
|
||||
path]
|
||||
try:
|
||||
self._execute(command)
|
||||
except processutils.ProcessExecutionError as e:
|
||||
if 'Source path: Cannot access' in e.stderr:
|
||||
LOG.warning(_LW("Attempted to delete path %s "
|
||||
"but it does not exist."), path)
|
||||
else:
|
||||
msg = six.text_type(e)
|
||||
LOG.exception(msg)
|
||||
raise e
|
||||
|
||||
def create_directory(self, dest_path):
|
||||
self._locked_selectfs('create', dest_path)
|
||||
|
||||
def delete_directory(self, path):
|
||||
self._locked_selectfs('delete', path)
|
||||
|
||||
def check_fs_mounted(self):
|
||||
fs_list = self._get_filesystem_list()
|
||||
for i in range(0, len(fs_list)):
|
||||
if fs_list[i].name == self.fs_name:
|
||||
if fs_list[i].state == 'Mount':
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
msg = (_("Filesystem %s does not exist or it is not available "
|
||||
"in the current EVS context.") % self.fs_name)
|
||||
raise exception.HNASItemNotFoundException(msg=msg)
|
||||
|
||||
def mount(self):
|
||||
command = ['mount', self.fs_name]
|
||||
try:
|
||||
self._execute(command)
|
||||
except processutils.ProcessExecutionError as e:
|
||||
if 'file system is already mounted' not in e.stderr:
|
||||
msg = six.text_type(e)
|
||||
LOG.exception(msg)
|
||||
raise e
|
||||
|
||||
def vvol_create(self, vvol_name):
|
||||
# create a virtual-volume inside directory
|
||||
path = '/shares/' + vvol_name
|
||||
command = ['virtual-volume', 'add', '--ensure', self.fs_name,
|
||||
vvol_name, path]
|
||||
self._execute(command)
|
||||
|
||||
def vvol_delete(self, vvol_name):
|
||||
path = '/shares/' + vvol_name
|
||||
# Virtual-volume and quota are deleted together
|
||||
command = ['tree-delete-job-submit', '--confirm', '-f',
|
||||
self.fs_name, path]
|
||||
try:
|
||||
self._execute(command)
|
||||
except processutils.ProcessExecutionError as e:
|
||||
if 'Source path: Cannot access' in e.stderr:
|
||||
LOG.debug("Share %(shr)s does not exist.",
|
||||
{'shr': six.text_type(vvol_name)})
|
||||
else:
|
||||
msg = six.text_type(e)
|
||||
LOG.exception(msg)
|
||||
raise e
|
||||
|
||||
def quota_add(self, vvol_name, vvol_quota):
|
||||
str_quota = six.text_type(vvol_quota) + 'G'
|
||||
command = ['quota', 'add', '--usage-limit',
|
||||
str_quota, '--usage-hard-limit',
|
||||
'yes', self.fs_name, vvol_name]
|
||||
self._execute(command)
|
||||
|
||||
def modify_quota(self, vvol_name, new_size):
|
||||
str_quota = six.text_type(new_size) + 'G'
|
||||
command = ['quota', 'mod', '--usage-limit', str_quota,
|
||||
self.fs_name, vvol_name]
|
||||
self._execute(command)
|
||||
|
||||
def check_vvol(self, vvol_name):
|
||||
command = ['virtual-volume', 'list', '--verbose', self.fs_name,
|
||||
vvol_name]
|
||||
try:
|
||||
self._execute(command)
|
||||
except processutils.ProcessExecutionError as e:
|
||||
msg = six.text_type(e)
|
||||
LOG.exception(msg)
|
||||
msg = (_("Virtual volume %s does not exist.") % vvol_name)
|
||||
raise exception.HNASItemNotFoundException(msg=msg)
|
||||
|
||||
def check_quota(self, vvol_name):
|
||||
command = ['quota', 'list', '--verbose', self.fs_name, vvol_name]
|
||||
output, err = self._execute(command)
|
||||
|
||||
if 'No quotas matching specified filter criteria' in output:
|
||||
msg = (_("Virtual volume %s does not have any quota.") % vvol_name)
|
||||
raise exception.HNASItemNotFoundException(msg=msg)
|
||||
|
||||
def check_export(self, vvol_name):
|
||||
export = self._get_share_export(vvol_name)
|
||||
if (vvol_name in export[0].export_name and
|
||||
self.fs_name in export[0].file_system_label):
|
||||
return
|
||||
else:
|
||||
msg = (_("Share %s was not created.") % share['id'])
|
||||
msg = _("Export %s does not exist.") % export[0].export_name
|
||||
raise exception.HNASItemNotFoundException(msg=msg)
|
||||
|
||||
def get_share_quota(self, share_id):
|
||||
command = ['quota', 'list', self.fs_name, six.text_type(share_id)]
|
||||
output, err = self._execute(command)
|
||||
|
||||
quota = Quota(output)
|
||||
|
||||
if quota.limit is None:
|
||||
return None
|
||||
|
||||
if quota.limit_unit == 'TB':
|
||||
return quota.limit * units.Ki
|
||||
elif quota.limit_unit == 'GB':
|
||||
return quota.limit
|
||||
else:
|
||||
msg = (_("Share %s does not support quota values "
|
||||
"below 1G.") % share_id)
|
||||
raise exception.HNASBackendException(msg=msg)
|
||||
|
||||
def _get_share_export(self, share_id):
|
||||
share_id = '/shares/' + share_id
|
||||
command = ['nfs-export', 'list ', six.text_type(share_id)]
|
||||
output, err = self._execute(command)
|
||||
export_list = []
|
||||
|
||||
if 'No exports are currently configured' in output:
|
||||
msg = _("Export %(share)s was not found in EVS "
|
||||
"%(evs_id)s") % {'share': share_id,
|
||||
'evs_id': self.evs_id}
|
||||
raise exception.HNASItemNotFoundException(msg=msg)
|
||||
else:
|
||||
items = output.split('Export name')
|
||||
|
||||
if items[0][0] == '\n':
|
||||
items.pop(0)
|
||||
|
||||
for i in range(0, len(items)):
|
||||
export_list.append(Export(items[i]))
|
||||
return export_list
|
||||
|
||||
def _get_filesystem_list(self):
|
||||
command = ['filesystem-list', self.fs_name]
|
||||
output, err = self._execute(command)
|
||||
items = output.split('\n')
|
||||
filesystem_list = []
|
||||
fs_name = None
|
||||
if len(items) > 2:
|
||||
j = 0
|
||||
for i in range(2, len(items) - 1):
|
||||
if "Filesystem " in items[i] and len(items[i].split()) == 2:
|
||||
description, fs_name = items[i].split()
|
||||
fs_name = fs_name[:len(fs_name) - 1]
|
||||
elif "NoEVS" not in items[i]:
|
||||
# Not considering FS without EVS
|
||||
filesystem_list.append(FileSystem(items[i]))
|
||||
if fs_name is not None:
|
||||
filesystem_list[j].name = fs_name
|
||||
fs_name = None
|
||||
j += 1
|
||||
else:
|
||||
LOG.debug("Ignoring filesystems without EVS.")
|
||||
return filesystem_list
|
||||
|
||||
@mutils.retry(exception=exception.HNASConnException, wait_random=True)
|
||||
def _execute(self, commands):
|
||||
command = ['ssc', '127.0.0.1']
|
||||
|
@ -451,243 +384,28 @@ class HNASSSHBackend(object):
|
|||
LOG.error(_LE("Error running SSH command."))
|
||||
raise
|
||||
|
||||
def _check_fs_mounted(self, fs_name):
|
||||
self._check_fs()
|
||||
fs_list = self._get_filesystem_list()
|
||||
for i in range(0, len(fs_list)):
|
||||
if fs_list[i].name == fs_name and fs_list[i].state == 'Mount':
|
||||
return True
|
||||
return False
|
||||
|
||||
def _get_filesystem_list(self):
|
||||
command = ['filesystem-list']
|
||||
output, err = self._execute(command)
|
||||
items = output.split('\n')
|
||||
filesystem_list = []
|
||||
fs_name = None
|
||||
if len(items) > 2:
|
||||
j = 0
|
||||
for i in range(2, len(items) - 1):
|
||||
if "Filesystem " in items[i] and len(items[i].split()) == 2:
|
||||
description, fs_name = items[i].split()
|
||||
fs_name = fs_name[:len(fs_name) - 1]
|
||||
elif "NoEVS" not in items[i]:
|
||||
# Not considering FS without EVS
|
||||
filesystem_list.append(FileSystem(items[i]))
|
||||
if fs_name is not None:
|
||||
filesystem_list[j].name = fs_name
|
||||
fs_name = None
|
||||
j += 1
|
||||
else:
|
||||
LOG.debug("Ignoring filesystems without EVS.")
|
||||
|
||||
return filesystem_list
|
||||
|
||||
def _nfs_export_add(self, share_id):
|
||||
path = '/shares/' + share_id
|
||||
# nfs-export add -S disable -c <export-name> <file-system> <path>
|
||||
command = ['nfs-export', 'add', '-S', 'disable', '-c', '127.0.0.1',
|
||||
path, self.fs_name, path]
|
||||
output, err = self._execute(command)
|
||||
return True
|
||||
|
||||
def _nfs_export_del(self, share_id):
|
||||
path = '/shares/' + share_id
|
||||
command = ['nfs-export', 'del', path]
|
||||
|
||||
try:
|
||||
output, err = self._execute(command)
|
||||
except exception.HNASBackendException as e:
|
||||
LOG.warning(_LW("Export %s does not exist on backend anymore."),
|
||||
path)
|
||||
LOG.exception(six.text_type(e))
|
||||
|
||||
def _update_access_rule(self, share_id, host_list):
|
||||
# mount the command line
|
||||
command = ['nfs-export', 'mod', '-c']
|
||||
|
||||
if len(host_list) == 0:
|
||||
command.append('127.0.0.1')
|
||||
else:
|
||||
string_command = '"' + six.text_type(host_list[0])
|
||||
|
||||
for i in range(1, len(host_list)):
|
||||
string_command += ',' + (six.text_type(host_list[i]))
|
||||
string_command += '"'
|
||||
command.append(string_command)
|
||||
|
||||
path = '/shares/' + share_id
|
||||
command.append(path)
|
||||
output, err = self._execute(command)
|
||||
|
||||
if ("Export modified successfully" in output or
|
||||
"Export modified successfully" in err):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def _nfs_export_list(self, share_id=''):
|
||||
if share_id is not '':
|
||||
share_id = '/shares/' + share_id
|
||||
command = ['nfs-export', 'list ', six.text_type(share_id)]
|
||||
output, err = self._execute(command)
|
||||
nfs_export_list = []
|
||||
|
||||
if 'No exports are currently configured' not in output:
|
||||
items = output.split('Export name')
|
||||
|
||||
if items[0][0] == '\n':
|
||||
items.pop(0)
|
||||
|
||||
for i in range(0, len(items)):
|
||||
nfs_export_list.append(Export(items[i]))
|
||||
|
||||
return nfs_export_list
|
||||
|
||||
def _mount(self, fs):
|
||||
command = ['mount', fs]
|
||||
try:
|
||||
output, err = self._execute(command)
|
||||
if 'successfully mounted' in output:
|
||||
return True
|
||||
except processutils.ProcessExecutionError as e:
|
||||
if 'file system is already mounted' in e.stderr:
|
||||
return True
|
||||
else:
|
||||
msg = six.text_type(e)
|
||||
LOG.exception(msg)
|
||||
raise e
|
||||
|
||||
def _vvol_create(self, vvol_name, vvol_quota):
|
||||
# create a virtual-volume inside directory
|
||||
if self._check_fs():
|
||||
path = '/shares/' + vvol_name
|
||||
command = ['virtual-volume', 'add', '--ensure', self.fs_name,
|
||||
vvol_name, path]
|
||||
output, err = self._execute(command)
|
||||
|
||||
# put a quota limit in virtual-volume to deny expand abuses
|
||||
self._quota_add(vvol_name, vvol_quota)
|
||||
return True
|
||||
else:
|
||||
msg = (_("Filesystem %s does not exist or it is not available "
|
||||
"in the current EVS context.") % self.fs_name)
|
||||
raise exception.HNASBackendException(msg=msg)
|
||||
|
||||
def _quota_add(self, vvol_name, vvol_quota):
|
||||
if vvol_quota > 0:
|
||||
str_quota = six.text_type(vvol_quota) + 'G'
|
||||
command = ['quota', 'add', '--usage-limit',
|
||||
str_quota, '--usage-hard-limit',
|
||||
'yes', self.fs_name, vvol_name]
|
||||
output, err = self._execute(command)
|
||||
return True
|
||||
return False
|
||||
|
||||
def _vvol_delete(self, vvol_name):
|
||||
path = '/shares/' + vvol_name
|
||||
# Virtual-volume and quota are deleted together
|
||||
command = ['tree-delete-job-submit', '--confirm', '-f',
|
||||
self.fs_name, path]
|
||||
try:
|
||||
output, err = self._execute(command)
|
||||
return True
|
||||
except processutils.ProcessExecutionError as e:
|
||||
if 'Source path: Cannot access' in e.stderr:
|
||||
LOG.debug("Share %(shr)s does not exist.",
|
||||
{'shr': six.text_type(vvol_name)})
|
||||
else:
|
||||
msg = six.text_type(e)
|
||||
LOG.exception(msg)
|
||||
raise e
|
||||
|
||||
def _extend_quota(self, vvol_name, new_size):
|
||||
str_quota = six.text_type(new_size) + 'G'
|
||||
command = ['quota', 'mod', '--usage-limit', str_quota,
|
||||
self.fs_name, vvol_name]
|
||||
output, err = self._execute(command)
|
||||
return True
|
||||
|
||||
def _check_fs(self):
|
||||
fs_list = self._get_filesystem_list()
|
||||
fs_name_list = []
|
||||
for i in range(0, len(fs_list)):
|
||||
fs_name_list.append(fs_list[i].name)
|
||||
if fs_list[i].name == self.fs_name:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _check_vvol(self, vvol_name):
|
||||
command = ['virtual-volume', 'list', '--verbose', self.fs_name,
|
||||
vvol_name]
|
||||
try:
|
||||
output, err = self._execute(command)
|
||||
return True
|
||||
except processutils.ProcessExecutionError as e:
|
||||
msg = six.text_type(e)
|
||||
LOG.exception(msg)
|
||||
msg = (_("Virtual volume %s does not exist.") % vvol_name)
|
||||
raise exception.HNASBackendException(msg=msg)
|
||||
|
||||
def _check_quota(self, vvol_name):
|
||||
command = ['quota', 'list', '--verbose', self.fs_name, vvol_name]
|
||||
output, err = self._execute(command)
|
||||
|
||||
if 'No quotas matching specified filter criteria' not in output:
|
||||
return True
|
||||
else:
|
||||
msg = (_("Virtual volume %s does not have any quota.") % vvol_name)
|
||||
raise exception.HNASBackendException(msg=msg)
|
||||
|
||||
def _check_export(self, vvol_name):
|
||||
export = self._nfs_export_list(vvol_name)
|
||||
if (vvol_name in export[0].export_name and
|
||||
self.fs_name in export[0].file_system_label):
|
||||
return True
|
||||
else:
|
||||
msg = (_("Export %s does not exist.") % export[0].export_name)
|
||||
raise exception.HNASBackendException(msg=msg)
|
||||
|
||||
def _get_share_quota(self, share_id):
|
||||
command = ['quota', 'list', self.fs_name, six.text_type(share_id)]
|
||||
output, err = self._execute(command)
|
||||
items = output.split('\n')
|
||||
|
||||
for i in range(0, len(items) - 1):
|
||||
if ('Unset' not in items[i] and
|
||||
'No quotas matching' not in items[i]):
|
||||
if 'Limit' in items[i] and 'Hard' in items[i]:
|
||||
quota = float(items[i].split(' ')[12])
|
||||
size_unit = items[i].split(' ')[13]
|
||||
if size_unit in ('TB', 'GB'):
|
||||
# If the quota is 1 or more TB, converts to GB
|
||||
if size_unit == 'TB':
|
||||
return quota * units.Ki
|
||||
return quota
|
||||
else:
|
||||
msg = (_("Share %s does not support quota values "
|
||||
"below 1GB.") % share_id)
|
||||
raise exception.HNASBackendException(msg=msg)
|
||||
else:
|
||||
# Returns None if the quota is unset
|
||||
return None
|
||||
|
||||
@mutils.synchronized("hds_hnas_select_fs", external=True)
|
||||
def _locked_selectfs(self, op, path):
|
||||
if op == 'create':
|
||||
command = ['selectfs', self.fs_name, '\n',
|
||||
'ssc', '127.0.0.1', 'console-context', '--evs',
|
||||
self.evs_id, 'mkdir', '-p', path]
|
||||
output, err = self._execute(command)
|
||||
self._execute(command)
|
||||
|
||||
if op == 'delete':
|
||||
command = ['selectfs', self.fs_name, '\n',
|
||||
'ssc', '127.0.0.1', 'console-context', '--evs',
|
||||
self.evs_id, 'rmdir', path]
|
||||
try:
|
||||
output, err = self._execute(command)
|
||||
except processutils.ProcessExecutionError:
|
||||
LOG.debug("Share %(path)s has more snapshots.", {'path': path})
|
||||
self._execute(command)
|
||||
except processutils.ProcessExecutionError as e:
|
||||
if 'DirectoryNotEmpty' in e.stderr:
|
||||
LOG.debug("Share %(path)s has more snapshots.",
|
||||
{'path': path})
|
||||
else:
|
||||
msg = six.text_type(e)
|
||||
LOG.exception(msg)
|
||||
raise e
|
||||
|
||||
|
||||
class FileSystem(object):
|
||||
|
@ -810,3 +528,26 @@ class Capacity(object):
|
|||
self.used_measure = items[6]
|
||||
if self.used_measure == 'TB':
|
||||
self.used = self.used * units.Ki
|
||||
|
||||
|
||||
class Quota(object):
|
||||
def __init__(self, data):
|
||||
if data:
|
||||
if 'No quotas matching' in data:
|
||||
self.type = None
|
||||
self.target = None
|
||||
self.usage = None
|
||||
self.usage_unit = None
|
||||
self.limit = None
|
||||
self.limit_unit = None
|
||||
else:
|
||||
items = data.split()
|
||||
self.type = items[2]
|
||||
self.target = items[6]
|
||||
self.usage = items[9]
|
||||
self.usage_unit = items[10]
|
||||
if items[13] == 'Unset':
|
||||
self.limit = None
|
||||
else:
|
||||
self.limit = float(items[13])
|
||||
self.limit_unit = items[14]
|
||||
|
|
|
@ -17,42 +17,83 @@ import ddt
|
|||
import mock
|
||||
from oslo_config import cfg
|
||||
|
||||
from manila import context
|
||||
from manila import exception
|
||||
import manila.share.configuration
|
||||
import manila.share.driver
|
||||
from manila.share.drivers.hitachi import hds_hnas
|
||||
from manila.share.drivers.hitachi import ssh
|
||||
from manila.share import share_types
|
||||
from manila import test
|
||||
from manila.tests.db import fakes as db_fakes
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
share = {
|
||||
'id': 'aa4a7710-f326-41fb-ad18-b4ad587fc87a',
|
||||
'name': 'aa4a7710-f326-41fb-ad18-b4ad587fc87a',
|
||||
'size': 50,
|
||||
'host': 'hnas',
|
||||
'share_proto': 'NFS',
|
||||
'share_type_id': 1,
|
||||
'share_network_id': 'bb329e24-3bdb-491d-acfd-dfe70c09b98d',
|
||||
'share_server_id': 'cc345a53-491d-acfd-3bdb-dfe70c09b98d',
|
||||
'export_locations': [{'path': '172.24.44.10:/shares/'
|
||||
'aa4a7710-f326-41fb-ad18-b4ad587fc87a'}],
|
||||
}
|
||||
|
||||
def fake_share(**kwargs):
|
||||
share = {
|
||||
'id': 'fake_id',
|
||||
'size': 1,
|
||||
'share_type_id': '7450f16e-4c7f-42ab-90f1-c1cfb2a6bc70',
|
||||
'share_proto': 'nfs',
|
||||
'share_network_id': 'fake_network_id',
|
||||
'share_server_id': 'fake_server_id',
|
||||
'host': ['None'],
|
||||
'export_locations': [{'path': '172.24.44.10:/nfs/volume-00002'}],
|
||||
}
|
||||
share.update(kwargs)
|
||||
return db_fakes.FakeModel(share)
|
||||
share_invalid_host = {
|
||||
'id': 'aa4a7710-f326-41fb-ad18-b4ad587fc87a',
|
||||
'name': 'aa4a7710-f326-41fb-ad18-b4ad587fc87a',
|
||||
'size': 50,
|
||||
'host': 'invalid',
|
||||
'share_proto': 'NFS',
|
||||
'share_type_id': 1,
|
||||
'share_network_id': 'bb329e24-3bdb-491d-acfd-dfe70c09b98d',
|
||||
'share_server_id': 'cc345a53-491d-acfd-3bdb-dfe70c09b98d',
|
||||
'export_locations': [{'path': '172.24.44.10:/shares/'
|
||||
'aa4a7710-f326-41fb-ad18-b4ad587fc87a'}],
|
||||
}
|
||||
|
||||
access = {
|
||||
'id': 'acdc7172b-fe07-46c4-b78f-df3e0324ccd0',
|
||||
'access_type': 'ip',
|
||||
'access_to': '172.24.44.200',
|
||||
'access_level': 'rw',
|
||||
'state': 'active',
|
||||
}
|
||||
|
||||
snapshot = {
|
||||
'id': 'abba6d9b-f29c-4bf7-aac1-618cda7aaf0f',
|
||||
'share_id': 'aa4a7710-f326-41fb-ad18-b4ad587fc87a',
|
||||
}
|
||||
|
||||
invalid_share = {
|
||||
'id': 'aa4a7710-f326-41fb-ad18-b4ad587fc87a',
|
||||
'name': 'aa4a7710-f326-41fb-ad18-b4ad587fc87a',
|
||||
'size': 100,
|
||||
'host': 'hnas',
|
||||
'share_proto': 'CIFS',
|
||||
}
|
||||
|
||||
invalid_access_type = {
|
||||
'id': 'acdc7172b-fe07-46c4-b78f-df3e0324ccd0',
|
||||
'access_type': 'user',
|
||||
'access_to': 'manila_user',
|
||||
'access_level': 'rw',
|
||||
'state': 'active',
|
||||
}
|
||||
|
||||
invalid_access_level = {
|
||||
'id': 'acdc7172b-fe07-46c4-b78f-df3e0324ccd0',
|
||||
'access_type': 'ip',
|
||||
'access_to': 'manila_user',
|
||||
'access_level': '777',
|
||||
'state': 'active',
|
||||
}
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class HDSHNASTestCase(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(HDSHNASTestCase, self).setUp()
|
||||
|
||||
self._context = context.get_admin_context()
|
||||
self._execute = mock.Mock(return_value=('', ''))
|
||||
CONF.set_default('driver_handles_share_servers', False)
|
||||
CONF.hds_hnas_evs_id = '2'
|
||||
CONF.hds_hnas_evs_ip = '172.24.44.10'
|
||||
|
@ -63,9 +104,10 @@ class HDSHNASTestCase(test.TestCase):
|
|||
CONF.hds_hnas_file_system = 'file_system'
|
||||
CONF.hds_hnas_ssh_private_key = 'private_key'
|
||||
CONF.hds_hnas_cluster_admin_ip0 = None
|
||||
self.const_dhss = 'driver_handles_share_servers'
|
||||
CONF.hds_hnas_stalled_job_timeout = 10
|
||||
CONF.hds_hnas_driver_helper = ('manila.share.drivers.hitachi.ssh.'
|
||||
'HNASSSHBackend')
|
||||
self.fake_conf = manila.share.configuration.Configuration(None)
|
||||
self._db = mock.Mock()
|
||||
|
||||
self.fake_private_storage = mock.Mock()
|
||||
self.mock_object(self.fake_private_storage, 'get',
|
||||
|
@ -73,63 +115,13 @@ class HDSHNASTestCase(test.TestCase):
|
|||
self.mock_object(self.fake_private_storage, 'delete',
|
||||
mock.Mock(return_value=None))
|
||||
|
||||
self.mock_log = self.mock_object(manila.share.drivers.hitachi.hds_hnas,
|
||||
'LOG')
|
||||
|
||||
self._driver = hds_hnas.HDSHNASDriver(
|
||||
private_storage=self.fake_private_storage,
|
||||
configuration=self.fake_conf)
|
||||
self._driver.backend_name = "hnas"
|
||||
self.mock_log = self.mock_object(hds_hnas, 'LOG')
|
||||
|
||||
self.server = {
|
||||
'instance_id': 'fake_instance_id',
|
||||
'ip': 'fake_ip',
|
||||
'username': 'fake_username',
|
||||
'password': 'fake_password',
|
||||
'pk_path': 'fake_pk_path',
|
||||
'backend_details': {
|
||||
'public_address': '1.2.3.4',
|
||||
'instance_id': 'fake',
|
||||
},
|
||||
}
|
||||
|
||||
self.invalid_server = {
|
||||
'backend_details': {
|
||||
'ip': '1.1.1.1',
|
||||
'instance_id': 'fake',
|
||||
},
|
||||
}
|
||||
|
||||
self.nfs_export_list = {'export_configuration': 'fake_export'}
|
||||
|
||||
self.share = fake_share()
|
||||
|
||||
self.invalid_share = {
|
||||
'id': 'fakeid',
|
||||
'name': 'fakename',
|
||||
'size': 1,
|
||||
'host': 'hnas',
|
||||
'share_proto': 'CIFS',
|
||||
'share_type_id': 1,
|
||||
'share_network_id': 'fake share network id',
|
||||
'share_server_id': 'fake share server id',
|
||||
'export_locations': [{'path': '172.24.44.110:'
|
||||
'/mnt/nfs/volume-00002'}],
|
||||
}
|
||||
|
||||
self.access = {
|
||||
'id': 'fakeaccid',
|
||||
'access_type': 'ip',
|
||||
'access_to': '10.0.0.2',
|
||||
'access_level': 'fake_level',
|
||||
'state': 'active',
|
||||
}
|
||||
|
||||
self.snapshot = {
|
||||
'id': 'snap_name',
|
||||
'share_id': 'fake_name',
|
||||
}
|
||||
|
||||
@ddt.data('hds_hnas_evs_id', 'hds_hnas_evs_ip',
|
||||
@ddt.data('hds_hnas_driver_helper', 'hds_hnas_evs_id', 'hds_hnas_evs_ip',
|
||||
'hds_hnas_ip', 'hds_hnas_user')
|
||||
def test_init_invalid_conf_parameters(self, attr_name):
|
||||
self.mock_object(manila.share.driver.ShareDriver,
|
||||
|
@ -149,260 +141,413 @@ class HDSHNASTestCase(test.TestCase):
|
|||
self._driver.__init__)
|
||||
|
||||
def test_allow_access(self):
|
||||
self.mock_object(ssh.HNASSSHBackend, 'allow_access')
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id",
|
||||
mock.Mock(return_value=share['id']))
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_ensure_share", mock.Mock())
|
||||
self.mock_object(ssh.HNASSSHBackend, "get_host_list", mock.Mock(
|
||||
return_value=['127.0.0.1 (rw)']))
|
||||
self.mock_object(ssh.HNASSSHBackend, "update_access_rule", mock.Mock())
|
||||
|
||||
self._driver.allow_access(self._context, self.share,
|
||||
self.access, self.server)
|
||||
self._driver.allow_access('context', share, access)
|
||||
|
||||
ssh.HNASSSHBackend.allow_access.assert_called_once_with('fake_id',
|
||||
'10.0.0.2',
|
||||
'nfs',
|
||||
'fake_level')
|
||||
self.assertTrue(self.mock_log.debug.called)
|
||||
ssh.HNASSSHBackend.update_access_rule.assert_called_once_with(
|
||||
share['id'], ['127.0.0.1 (rw)', access['access_to'] + '(' +
|
||||
access['access_level'] + ')'])
|
||||
ssh.HNASSSHBackend.get_host_list.assert_called_once_with(share['id'])
|
||||
self.assertTrue(self.mock_log.info.called)
|
||||
|
||||
def test_allow_access_wrong_permission(self):
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id",
|
||||
mock.Mock(return_value=share['id']))
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_ensure_share", mock.Mock())
|
||||
self.mock_object(ssh.HNASSSHBackend, "get_host_list", mock.Mock(
|
||||
return_value=['127.0.0.1 (rw)']))
|
||||
|
||||
self.assertRaises(exception.HNASBackendException,
|
||||
self._driver.allow_access, 'context', share,
|
||||
invalid_access_level)
|
||||
ssh.HNASSSHBackend.get_host_list.assert_called_once_with(share['id'])
|
||||
|
||||
def test_allow_access_host_allowed(self):
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id",
|
||||
mock.Mock(return_value=share['id']))
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_ensure_share", mock.Mock())
|
||||
self.mock_object(ssh.HNASSSHBackend, "get_host_list", mock.Mock(
|
||||
return_value=['172.24.44.200(rw)']))
|
||||
|
||||
self._driver.allow_access('context', share, access)
|
||||
|
||||
ssh.HNASSSHBackend.get_host_list.assert_called_once_with(share['id'])
|
||||
self.assertTrue(self.mock_log.debug.called)
|
||||
|
||||
def test_allow_access_host_allowed_different_permission(self):
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id",
|
||||
mock.Mock(return_value=share['id']))
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_ensure_share", mock.Mock())
|
||||
self.mock_object(ssh.HNASSSHBackend, "get_host_list", mock.Mock(
|
||||
return_value=['172.24.44.200(ro)']))
|
||||
self.mock_object(ssh.HNASSSHBackend, "update_access_rule", mock.Mock())
|
||||
|
||||
self._driver.allow_access('context', share, access)
|
||||
|
||||
ssh.HNASSSHBackend.get_host_list.assert_called_once_with(share['id'])
|
||||
ssh.HNASSSHBackend.update_access_rule.assert_called_once_with(
|
||||
share['id'], [access['access_to'] + '(' + access['access_level']
|
||||
+ ')'])
|
||||
|
||||
def test_allow_access_invalid_access_type(self):
|
||||
access = {'access_type': 'user', 'access_to': 'fake_dest'}
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id",
|
||||
mock.Mock(return_value=share['id']))
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_allow_access", mock.Mock())
|
||||
|
||||
self.assertRaises(exception.InvalidShareAccess,
|
||||
self._driver.allow_access, self._context,
|
||||
self.share, access, self.server)
|
||||
|
||||
def test_allow_access_invalid_share_protocol(self):
|
||||
self.assertRaises(exception.InvalidShareAccess,
|
||||
self._driver.allow_access, self._context,
|
||||
self.invalid_share, self.access, self.server)
|
||||
self._driver.allow_access, 'context', invalid_share,
|
||||
invalid_access_type)
|
||||
|
||||
def test_deny_access(self):
|
||||
self.mock_object(ssh.HNASSSHBackend, 'deny_access')
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id",
|
||||
mock.Mock(return_value=share['id']))
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_ensure_share", mock.Mock())
|
||||
self.mock_object(ssh.HNASSSHBackend, "get_host_list", mock.Mock(
|
||||
return_value=['172.24.44.200(rw)']))
|
||||
self.mock_object(ssh.HNASSSHBackend, "update_access_rule", mock.Mock())
|
||||
|
||||
self._driver.deny_access(self._context, self.share,
|
||||
self.access, self.server)
|
||||
self._driver.deny_access('context', share, access)
|
||||
|
||||
ssh.HNASSSHBackend.deny_access.assert_called_once_with('fake_id',
|
||||
'10.0.0.2',
|
||||
'nfs',
|
||||
'fake_level')
|
||||
ssh.HNASSSHBackend.get_host_list.assert_called_once_with(share['id'])
|
||||
ssh.HNASSSHBackend.update_access_rule.assert_called_once_with(
|
||||
share['id'], [])
|
||||
|
||||
def test_deny_access_already_not_allowed(self):
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id",
|
||||
mock.Mock(return_value=share['id']))
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_ensure_share", mock.Mock())
|
||||
self.mock_object(ssh.HNASSSHBackend, "get_host_list", mock.Mock(
|
||||
return_value=[]))
|
||||
|
||||
self._driver.deny_access('context', share, access)
|
||||
|
||||
ssh.HNASSSHBackend.get_host_list.assert_called_once_with(share['id'])
|
||||
self.assertTrue(self.mock_log.debug.called)
|
||||
self.assertTrue(self.mock_log.info.called)
|
||||
|
||||
def test_deny_access_invalid_share_protocol(self):
|
||||
def test_deny_access_invalid_access_level(self):
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id",
|
||||
mock.Mock(return_value=share['id']))
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_ensure_share", mock.Mock())
|
||||
self.mock_object(ssh.HNASSSHBackend, "get_host_list", mock.Mock(
|
||||
return_value=[]))
|
||||
|
||||
self.assertRaises(exception.HNASBackendException,
|
||||
self._driver.deny_access, 'context', share,
|
||||
invalid_access_level)
|
||||
ssh.HNASSSHBackend.get_host_list.assert_called_once_with(share['id'])
|
||||
|
||||
def test_deny_access_invalid_access_type(self):
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id",
|
||||
mock.Mock(return_value=share['id']))
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_deny_access", mock.Mock())
|
||||
|
||||
self.assertRaises(exception.InvalidShareAccess,
|
||||
self._driver.deny_access, self._context,
|
||||
self.invalid_share, self.access, self.server)
|
||||
self._driver.deny_access, 'context', invalid_share,
|
||||
invalid_access_type)
|
||||
|
||||
def test_create_share(self):
|
||||
# share server none
|
||||
path = '/' + self.share['id']
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_check_fs_mounted",
|
||||
mock.Mock())
|
||||
self.mock_object(ssh.HNASSSHBackend, "vvol_create", mock.Mock())
|
||||
self.mock_object(ssh.HNASSSHBackend, "quota_add", mock.Mock())
|
||||
self.mock_object(ssh.HNASSSHBackend, "nfs_export_add", mock.Mock())
|
||||
|
||||
self.mock_object(ssh.HNASSSHBackend, 'create_share',
|
||||
mock.Mock(return_value=path))
|
||||
result = self._driver.create_share('context', share)
|
||||
|
||||
result = self._driver.create_share(self._context,
|
||||
self.share)
|
||||
|
||||
ssh.HNASSSHBackend.create_share.assert_called_once_with('fake_id', 1,
|
||||
'nfs')
|
||||
self.assertEqual('172.24.44.10:/fake_id', result)
|
||||
self.assertEqual(self._driver.hnas_evs_ip + ":/shares/" + share['id'],
|
||||
result)
|
||||
self.assertTrue(self.mock_log.debug.called)
|
||||
ssh.HNASSSHBackend.vvol_create.assert_called_once_with(share['id'])
|
||||
ssh.HNASSSHBackend.quota_add.assert_called_once_with(share['id'],
|
||||
share['size'])
|
||||
ssh.HNASSSHBackend.nfs_export_add.assert_called_once_with(share['id'])
|
||||
|
||||
def test_create_share_export_error(self):
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_check_fs_mounted",
|
||||
mock.Mock())
|
||||
self.mock_object(ssh.HNASSSHBackend, "vvol_create", mock.Mock())
|
||||
self.mock_object(ssh.HNASSSHBackend, "quota_add", mock.Mock())
|
||||
self.mock_object(ssh.HNASSSHBackend, "nfs_export_add", mock.Mock(
|
||||
side_effect=exception.HNASBackendException('msg')))
|
||||
self.mock_object(ssh.HNASSSHBackend, "vvol_delete", mock.Mock())
|
||||
|
||||
self.assertRaises(exception.HNASBackendException,
|
||||
self._driver.create_share, 'context', share)
|
||||
self.assertTrue(self.mock_log.debug.called)
|
||||
self.assertTrue(self.mock_log.exception.called)
|
||||
ssh.HNASSSHBackend.vvol_create.assert_called_once_with(share['id'])
|
||||
ssh.HNASSSHBackend.quota_add.assert_called_once_with(share['id'],
|
||||
share['size'])
|
||||
ssh.HNASSSHBackend.nfs_export_add.assert_called_once_with(share['id'])
|
||||
ssh.HNASSSHBackend.vvol_delete.assert_called_once_with(share['id'])
|
||||
|
||||
def test_create_share_invalid_share_protocol(self):
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_create_share",
|
||||
mock.Mock(return_value="path"))
|
||||
|
||||
self.assertRaises(exception.ShareBackendException,
|
||||
self._driver.create_share,
|
||||
self._context, self.invalid_share)
|
||||
self.assertTrue(self.mock_log.debug.called)
|
||||
self._driver.create_share, 'context', invalid_share)
|
||||
|
||||
def test_delete_share(self):
|
||||
self.mock_object(ssh.HNASSSHBackend, 'delete_share')
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id",
|
||||
mock.Mock(return_value=share['id']))
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_check_fs_mounted",
|
||||
mock.Mock())
|
||||
self.mock_object(ssh.HNASSSHBackend, "nfs_export_del", mock.Mock())
|
||||
self.mock_object(ssh.HNASSSHBackend, "vvol_delete", mock.Mock())
|
||||
|
||||
self._driver.delete_share(self._context, self.share)
|
||||
self._driver.delete_share('context', share)
|
||||
|
||||
ssh.HNASSSHBackend.delete_share.assert_called_once_with('fake_id',
|
||||
'nfs')
|
||||
self.assertTrue(self.mock_log.debug.called)
|
||||
|
||||
def test_ensure_share(self):
|
||||
export_list = ['172.24.44.10:/shares/fake_id']
|
||||
path = '/shares/fake_id'
|
||||
|
||||
self.mock_object(ssh.HNASSSHBackend, 'ensure_share',
|
||||
mock.Mock(return_value=path))
|
||||
|
||||
out = self._driver.ensure_share(self._context, self.share)
|
||||
|
||||
ssh.HNASSSHBackend.ensure_share.assert_called_once_with('fake_id',
|
||||
'nfs')
|
||||
self.assertTrue(self.mock_log.debug.called)
|
||||
self.assertEqual(export_list, out)
|
||||
|
||||
def test_ensure_share_invalid_share_protocol(self):
|
||||
# invalid share proto
|
||||
self.assertRaises(exception.ShareBackendException,
|
||||
self._driver.ensure_share,
|
||||
self._context, self.invalid_share)
|
||||
self.assertTrue(self.mock_log.debug.called)
|
||||
|
||||
def test_extend_share(self):
|
||||
self.mock_object(ssh.HNASSSHBackend, 'extend_share')
|
||||
|
||||
self._driver.extend_share(self.share, 5)
|
||||
|
||||
ssh.HNASSSHBackend.extend_share.assert_called_once_with('fake_id', 5,
|
||||
'nfs')
|
||||
self.assertTrue(self.mock_log.debug.called)
|
||||
self.assertTrue(self.mock_log.info.called)
|
||||
|
||||
def test_extend_share_invalid_share_protocol(self):
|
||||
# invalid share with proto != nfs
|
||||
m_extend = self.mock_object(ssh.HNASSSHBackend, 'extend_share')
|
||||
|
||||
self.assertRaises(exception.ShareBackendException,
|
||||
self._driver.extend_share,
|
||||
self.invalid_share, 5)
|
||||
self.assertFalse(m_extend.called)
|
||||
self.assertTrue(self.mock_log.debug.called)
|
||||
|
||||
# TODO(alyson): Implement network tests in DHSS = true mode
|
||||
def test_get_network_allocations_number(self):
|
||||
self.assertEqual(0, self._driver.get_network_allocations_number())
|
||||
ssh.HNASSSHBackend.nfs_export_del.assert_called_once_with(share['id'])
|
||||
ssh.HNASSSHBackend.vvol_delete.assert_called_once_with(share['id'])
|
||||
|
||||
def test_create_snapshot(self):
|
||||
# tests when hnas.create_snapshot returns successfully
|
||||
self.mock_object(ssh.HNASSSHBackend, 'create_snapshot')
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id",
|
||||
mock.Mock(return_value=share['id']))
|
||||
self.mock_object(ssh.HNASSSHBackend, "get_host_list", mock.Mock(
|
||||
return_value=['172.24.44.200(rw)']))
|
||||
self.mock_object(ssh.HNASSSHBackend, "update_access_rule", mock.Mock())
|
||||
self.mock_object(ssh.HNASSSHBackend, "tree_clone", mock.Mock())
|
||||
|
||||
self._driver.create_snapshot(self._context, self.snapshot)
|
||||
self._driver.create_snapshot('context', snapshot)
|
||||
|
||||
ssh.HNASSSHBackend.create_snapshot.assert_called_once_with('fake_name',
|
||||
'snap_name')
|
||||
self.assertTrue(self.mock_log.debug.called)
|
||||
self.assertTrue(self.mock_log.info.called)
|
||||
ssh.HNASSSHBackend.get_host_list.assert_called_once_with(share['id'])
|
||||
ssh.HNASSSHBackend.update_access_rule.assert_any_call(
|
||||
share['id'], ['172.24.44.200(ro)'])
|
||||
ssh.HNASSSHBackend.update_access_rule.assert_any_call(
|
||||
share['id'], ['172.24.44.200(rw)'])
|
||||
ssh.HNASSSHBackend.tree_clone.assert_called_once_with(
|
||||
'/shares/' + share['id'], '/snapshots/' + share['id'] + '/' +
|
||||
snapshot['id'])
|
||||
|
||||
def test_create_snapshot_first_snapshot(self):
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id",
|
||||
mock.Mock(return_value=share['id']))
|
||||
self.mock_object(ssh.HNASSSHBackend, "get_host_list", mock.Mock(
|
||||
return_value=['172.24.44.200(rw)']))
|
||||
self.mock_object(ssh.HNASSSHBackend, "update_access_rule", mock.Mock())
|
||||
self.mock_object(ssh.HNASSSHBackend, "tree_clone", mock.Mock(
|
||||
side_effect=exception.HNASNothingToCloneException('msg')))
|
||||
self.mock_object(ssh.HNASSSHBackend, "create_directory", mock.Mock())
|
||||
|
||||
self._driver.create_snapshot('context', snapshot)
|
||||
|
||||
self.assertTrue(self.mock_log.warning.called)
|
||||
ssh.HNASSSHBackend.get_host_list.assert_called_once_with(share['id'])
|
||||
ssh.HNASSSHBackend.update_access_rule.assert_any_call(
|
||||
share['id'], ['172.24.44.200(ro)'])
|
||||
ssh.HNASSSHBackend.update_access_rule.assert_any_call(
|
||||
share['id'], ['172.24.44.200(rw)'])
|
||||
ssh.HNASSSHBackend.create_directory.assert_called_once_with(
|
||||
'/snapshots/' + share['id'] + '/' + snapshot['id'])
|
||||
|
||||
def test_delete_snapshot(self):
|
||||
# tests when hnas.delete_snapshot returns True
|
||||
self.mock_object(ssh.HNASSSHBackend, 'delete_snapshot')
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id",
|
||||
mock.Mock(return_value=share['id']))
|
||||
self.mock_object(ssh.HNASSSHBackend, "tree_delete", mock.Mock())
|
||||
self.mock_object(ssh.HNASSSHBackend, "delete_directory", mock.Mock())
|
||||
|
||||
self._driver.delete_snapshot(self._context, self.snapshot)
|
||||
self._driver.delete_snapshot('context', snapshot)
|
||||
|
||||
ssh.HNASSSHBackend.delete_snapshot.assert_called_once_with('fake_name',
|
||||
'snap_name')
|
||||
self.assertTrue(self.mock_log.debug.called)
|
||||
self.assertTrue(self.mock_log.info.called)
|
||||
ssh.HNASSSHBackend.tree_delete.assert_called_once_with(
|
||||
'/snapshots/' + share['id'] + '/' + snapshot['id'])
|
||||
ssh.HNASSSHBackend.delete_directory.assert_called_once_with(
|
||||
'/snapshots/' + share['id'])
|
||||
|
||||
def test_create_share_from_snapshot(self):
|
||||
# share server none
|
||||
path = '/' + self.share['id']
|
||||
def test_ensure_share(self):
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id",
|
||||
mock.Mock(return_value=share['id']))
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_check_fs_mounted",
|
||||
mock.Mock())
|
||||
self.mock_object(ssh.HNASSSHBackend, "check_vvol", mock.Mock())
|
||||
self.mock_object(ssh.HNASSSHBackend, "check_quota", mock.Mock())
|
||||
self.mock_object(ssh.HNASSSHBackend, "check_export", mock.Mock())
|
||||
|
||||
self.mock_object(ssh.HNASSSHBackend, 'create_share_from_snapshot',
|
||||
mock.Mock(return_value=path))
|
||||
result = self._driver.ensure_share('context', share)
|
||||
|
||||
result = self._driver.create_share_from_snapshot(self._context,
|
||||
self.share,
|
||||
self.snapshot)
|
||||
self.assertEqual(['172.24.44.10:/shares/' + share['id']], result)
|
||||
ssh.HNASSSHBackend.check_vvol.assert_called_once_with(share['id'])
|
||||
ssh.HNASSSHBackend.check_quota.assert_called_once_with(share['id'])
|
||||
ssh.HNASSSHBackend.check_export.assert_called_once_with(share['id'])
|
||||
|
||||
(ssh.HNASSSHBackend.create_share_from_snapshot.
|
||||
assert_called_with(self.share, self.snapshot))
|
||||
self.assertEqual('172.24.44.10:/fake_id', result)
|
||||
self.assertTrue(self.mock_log.debug.called)
|
||||
def test_extend_share(self):
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id",
|
||||
mock.Mock(return_value=share['id']))
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_ensure_share", mock.Mock())
|
||||
self.mock_object(ssh.HNASSSHBackend, "get_stats", mock.Mock(
|
||||
return_value=(500, 200)))
|
||||
self.mock_object(ssh.HNASSSHBackend, "modify_quota", mock.Mock())
|
||||
|
||||
self._driver.extend_share(share, 150)
|
||||
|
||||
ssh.HNASSSHBackend.get_stats.assert_called_once_with()
|
||||
ssh.HNASSSHBackend.modify_quota.assert_called_once_with(share['id'],
|
||||
150)
|
||||
|
||||
def test_extend_share_with_no_available_space_in_fs(self):
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id",
|
||||
mock.Mock(return_value=share['id']))
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_ensure_share", mock.Mock())
|
||||
self.mock_object(ssh.HNASSSHBackend, "get_stats", mock.Mock(
|
||||
return_value=(500, 200)))
|
||||
self.mock_object(ssh.HNASSSHBackend, "modify_quota", mock.Mock())
|
||||
|
||||
self.assertRaises(exception.HNASBackendException,
|
||||
self._driver.extend_share, share, 1000)
|
||||
ssh.HNASSSHBackend.get_stats.assert_called_once_with()
|
||||
|
||||
def test_manage_existing(self):
|
||||
driver_op = 'fake'
|
||||
local_id = 'volume-00002'
|
||||
manage_return = {
|
||||
'size': 1,
|
||||
'export_locations': '172.24.44.10:/mnt/nfs/volume-00002',
|
||||
}
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id",
|
||||
mock.Mock(return_value=share['id']))
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_ensure_share", mock.Mock())
|
||||
self.mock_object(ssh.HNASSSHBackend, "get_share_quota", mock.Mock(
|
||||
return_value=1))
|
||||
|
||||
CONF.set_default('share_backend_name', 'HDS1')
|
||||
self.mock_object(share_types, 'get_share_type_extra_specs',
|
||||
mock.Mock(return_value='False'))
|
||||
self.mock_object(ssh.HNASSSHBackend, 'manage_existing',
|
||||
mock.Mock(return_value=manage_return))
|
||||
self._driver.manage_existing(share, 'option')
|
||||
|
||||
output = self._driver.manage_existing(self.share, driver_op)
|
||||
ssh.HNASSSHBackend.get_share_quota.assert_called_once_with(share['id'])
|
||||
|
||||
self.assertEqual(manage_return, output)
|
||||
ssh.HNASSSHBackend.manage_existing.assert_called_once_with(self.share,
|
||||
local_id)
|
||||
self.assertTrue(self.mock_log.info.called)
|
||||
def test_manage_existing_no_quota(self):
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id",
|
||||
mock.Mock(return_value=share['id']))
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_ensure_share", mock.Mock())
|
||||
self.mock_object(ssh.HNASSSHBackend, "get_share_quota", mock.Mock(
|
||||
return_value=None))
|
||||
|
||||
CONF._unset_defaults_and_overrides()
|
||||
self.assertRaises(exception.ManageInvalidShare,
|
||||
self._driver.manage_existing, share, 'option')
|
||||
ssh.HNASSSHBackend.get_share_quota.assert_called_once_with(share['id'])
|
||||
|
||||
def test_manage_invalid_host(self):
|
||||
driver_op = 'fake'
|
||||
self.share_invalid_host = {
|
||||
'id': 'fake_id',
|
||||
'size': 1,
|
||||
'share_type_id': '7450f16e-4c7f-42ab-90f1-c1cfb2a6bc70',
|
||||
'share_proto': 'nfs',
|
||||
'share_network_id': 'fake_network_id',
|
||||
'share_server_id': 'fake_server_id',
|
||||
'host': 'fake@INVALID#fake_pool',
|
||||
'export_locations': [{'path': '172.24.44.10:/nfs/volume-00002'}],
|
||||
}
|
||||
def test_manage_existing_wrong_share_id(self):
|
||||
self.mock_object(self.fake_private_storage, 'get',
|
||||
mock.Mock(return_value='Wrong_share_id'))
|
||||
|
||||
self.mock_object(share_types, 'get_share_type_extra_specs',
|
||||
mock.Mock(return_value='False'))
|
||||
self.assertRaises(exception.HNASBackendException,
|
||||
self._driver.manage_existing, share, 'option')
|
||||
|
||||
def test_manage_existing_wrong_path_format(self):
|
||||
share['export_locations'] = [{'path': ':/'}]
|
||||
|
||||
self.assertRaises(exception.ShareBackendException,
|
||||
self._driver.manage_existing,
|
||||
self.share_invalid_host, driver_op)
|
||||
self._driver.manage_existing, share,
|
||||
'option')
|
||||
|
||||
def test_manage_invalid_path(self):
|
||||
driver_op = 'fake'
|
||||
self.share_invalid_path = {
|
||||
'id': 'fake_id',
|
||||
'size': 1,
|
||||
'share_type_id': '7450f16e-4c7f-42ab-90f1-c1cfb2a6bc70',
|
||||
'share_proto': 'nfs',
|
||||
'share_network_id': 'fake_network_id',
|
||||
'share_server_id': 'fake_server_id',
|
||||
'host': 'fake@INVALID#fake_pool',
|
||||
'export_locations': [{'path': '172.24.44.10:/volume-00002'}],
|
||||
}
|
||||
|
||||
self.mock_object(share_types, 'get_share_type_extra_specs',
|
||||
mock.Mock(return_value='False'))
|
||||
def test_manage_existing_wrong_evs_ip(self):
|
||||
share['export_locations'] = [{'path': '172.24.44.189:/shares/'
|
||||
'aa4a7710-f326-41fb-ad18-'}]
|
||||
|
||||
self.assertRaises(exception.ShareBackendException,
|
||||
self._driver.manage_existing,
|
||||
self.share_invalid_path, driver_op)
|
||||
|
||||
def test_manage_invalid_evs_ip(self):
|
||||
driver_op = 'fake'
|
||||
self.share_invalid_ip = {
|
||||
'id': 'fake_id',
|
||||
'size': 1,
|
||||
'share_type_id': '7450f16e-4c7f-42ab-90f1-c1cfb2a6bc70',
|
||||
'share_proto': 'nfs',
|
||||
'share_network_id': 'fake_network_id',
|
||||
'share_server_id': 'fake_server_id',
|
||||
'host': 'fake@HDS1#fake_pool',
|
||||
'export_locations': [{'path': '9.9.9.9:/nfs/volume-00002'}],
|
||||
}
|
||||
|
||||
self.mock_object(share_types, 'get_share_type_extra_specs',
|
||||
mock.Mock(return_value='False'))
|
||||
self._driver.manage_existing, share,
|
||||
'option')
|
||||
|
||||
def test_manage_existing_invalid_host(self):
|
||||
self.assertRaises(exception.ShareBackendException,
|
||||
self._driver.manage_existing,
|
||||
self.share_invalid_ip, driver_op)
|
||||
self._driver.manage_existing, share_invalid_host,
|
||||
'option')
|
||||
|
||||
def test_unmanage(self):
|
||||
self._driver.unmanage(self.share)
|
||||
self._driver.unmanage(share)
|
||||
|
||||
self.assertTrue(self.fake_private_storage.delete.called)
|
||||
self.assertTrue(self.mock_log.info.called)
|
||||
self.fake_private_storage.delete.assert_called_once_with(
|
||||
self.share['id'])
|
||||
|
||||
def test_update_share_stats(self):
|
||||
self.mock_object(ssh.HNASSSHBackend, 'get_stats',
|
||||
mock.Mock(return_value=[100, 30]))
|
||||
def test_get_network_allocations_number(self):
|
||||
result = self._driver.get_network_allocations_number()
|
||||
|
||||
self.assertEqual(0, result)
|
||||
|
||||
def test_create_share_from_snapshot(self):
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_check_fs_mounted",
|
||||
mock.Mock())
|
||||
self.mock_object(ssh.HNASSSHBackend, "vvol_create", mock.Mock())
|
||||
self.mock_object(ssh.HNASSSHBackend, "quota_add", mock.Mock())
|
||||
self.mock_object(ssh.HNASSSHBackend, "tree_clone", mock.Mock())
|
||||
self.mock_object(ssh.HNASSSHBackend, "nfs_export_add", mock.Mock())
|
||||
|
||||
result = self._driver.create_share_from_snapshot('context',
|
||||
share, snapshot)
|
||||
|
||||
self.assertEqual('172.24.44.10:/shares/' + share['id'], result)
|
||||
ssh.HNASSSHBackend.vvol_create.assert_called_once_with(share['id'])
|
||||
ssh.HNASSSHBackend.quota_add.assert_called_once_with(share['id'],
|
||||
share['size'])
|
||||
ssh.HNASSSHBackend.tree_clone.assert_called_once_with(
|
||||
'/snapshots/' + snapshot['share_id'] + '/' + snapshot['id'],
|
||||
'/shares/' + share['id'])
|
||||
ssh.HNASSSHBackend.nfs_export_add.assert_called_once_with(share['id'])
|
||||
|
||||
def test_create_share_from_snapshot_empty_snapshot(self):
|
||||
self.mock_object(hds_hnas.HDSHNASDriver, "_check_fs_mounted",
|
||||
mock.Mock())
|
||||
self.mock_object(ssh.HNASSSHBackend, "vvol_create", mock.Mock())
|
||||
self.mock_object(ssh.HNASSSHBackend, "quota_add", mock.Mock())
|
||||
self.mock_object(ssh.HNASSSHBackend, "tree_clone", mock.Mock(
|
||||
side_effect=exception.HNASNothingToCloneException('msg')))
|
||||
self.mock_object(ssh.HNASSSHBackend, "nfs_export_add", mock.Mock())
|
||||
|
||||
result = self._driver.create_share_from_snapshot('context', share,
|
||||
snapshot)
|
||||
|
||||
self.assertEqual('172.24.44.10:/shares/' + share['id'], result)
|
||||
self.assertTrue(self.mock_log.warning.called)
|
||||
ssh.HNASSSHBackend.vvol_create.assert_called_once_with(share['id'])
|
||||
ssh.HNASSSHBackend.quota_add.assert_called_once_with(share['id'],
|
||||
share['size'])
|
||||
ssh.HNASSSHBackend.tree_clone.assert_called_once_with(
|
||||
'/snapshots/' + snapshot['share_id'] + '/' + snapshot['id'],
|
||||
'/shares/' + share['id'])
|
||||
ssh.HNASSSHBackend.nfs_export_add.assert_called_once_with(share['id'])
|
||||
|
||||
def test__check_fs_mounted(self):
|
||||
self.mock_object(ssh.HNASSSHBackend, 'check_fs_mounted', mock.Mock(
|
||||
return_value=True))
|
||||
|
||||
self._driver._check_fs_mounted()
|
||||
|
||||
ssh.HNASSSHBackend.check_fs_mounted.assert_called_once_with()
|
||||
|
||||
def test__check_fs_mounted_not_mounted(self):
|
||||
self.mock_object(ssh.HNASSSHBackend, 'check_fs_mounted', mock.Mock(
|
||||
return_value=False))
|
||||
self.mock_object(ssh.HNASSSHBackend, 'mount', mock.Mock())
|
||||
|
||||
self._driver._check_fs_mounted()
|
||||
|
||||
ssh.HNASSSHBackend.check_fs_mounted.assert_called_once_with()
|
||||
ssh.HNASSSHBackend.mount.assert_called_once_with()
|
||||
self.assertTrue(self.mock_log.debug.called)
|
||||
|
||||
def test__update_share_stats(self):
|
||||
fake_data = {
|
||||
'share_backend_name': self._driver.backend_name,
|
||||
'driver_handles_share_servers':
|
||||
self._driver.driver_handles_share_servers,
|
||||
'vendor_name': 'HDS',
|
||||
'driver_version': '1.0',
|
||||
'storage_protocol': 'NFS',
|
||||
'total_capacity_gb': 1000,
|
||||
'free_capacity_gb': 200,
|
||||
'reserved_percentage': hds_hnas.CONF.reserved_share_percentage,
|
||||
'qos': False,
|
||||
}
|
||||
|
||||
self.mock_object(ssh.HNASSSHBackend, 'get_stats', mock.Mock(
|
||||
return_value=(1000, 200)))
|
||||
self.mock_object(manila.share.driver.ShareDriver,
|
||||
'_update_share_stats', mock.Mock())
|
||||
|
||||
self._driver._update_share_stats()
|
||||
self.assertFalse(self._driver._stats['driver_handles_share_servers'])
|
||||
self.assertEqual(100, self._driver._stats['total_capacity_gb'])
|
||||
self.assertEqual(30, self._driver._stats['free_capacity_gb'])
|
||||
self.assertEqual(0, self._driver._stats['reserved_percentage'])
|
||||
self.assertTrue(self._driver._stats['snapshot_support'])
|
||||
ssh.HNASSSHBackend.get_stats.assert_called_once_with()
|
||||
|
||||
self.assertTrue(self._driver.hnas.get_stats.called)
|
||||
(manila.share.driver.ShareDriver._update_share_stats.
|
||||
assert_called_once_with(fake_data))
|
||||
self.assertTrue(self.mock_log.info.called)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue