Merge "Hitachi HNAS driver refactoring"

This commit is contained in:
Jenkins 2016-01-21 22:23:20 +00:00 committed by Gerrit Code Review
commit 1461eb7e4d
5 changed files with 1398 additions and 1612 deletions

View File

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

View File

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

View File

@ -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]

View File

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