Fix HDS HNAS errors caused by incorrect IDs

When attempting to allow_access on a managed share, it fails
because the proper share ID is not retrieved from private storage
prior to attempting to validate that the share exists in the
backend. The same happens when trying to create a share from a
snapshot created from a managed share, the proper share ID is
not retrieved from private storage. While we are dealing with
two possible different IDs it is important to properly display
the API share ID in log messages so it can be matched to the
share instances ID, and not all log messages are accurately doing
so.

This change addresses this by retrieving the ID from
private storage first for update_access and
create_share_from_snapshot operations. The proper unit test changes
included in this patch required a refactor of several IDs to ensure
this problem is addressed in unit tests, thus it made sense to
address several bugs caused by the same problem, having the same fix
and requiring modifications to the same lines of code.

Closes-bug: #1581541
Closes-bug: #1584179
Closes-bug: #1583785
Change-Id: I8cdb1a8a72a4ac7e710f57e3c288d48cd2adf0dd
This commit is contained in:
Rodrigo Barbieri 2016-05-13 10:23:46 -03:00
parent ef2a6a8ac6
commit 081fc4860b
3 changed files with 156 additions and 114 deletions

View File

@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import os
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log from oslo_log import log
from oslo_utils import excutils from oslo_utils import excutils
@ -151,13 +153,14 @@ class HDSHNASDriver(driver.ShareDriver):
Not used by this driver. Not used by this driver.
""" """
hnas_share_id = self._get_hnas_share_id(share['id'])
try: try:
self._ensure_share(share['id']) self._ensure_share(hnas_share_id)
except exception.HNASItemNotFoundException: except exception.HNASItemNotFoundException:
raise exception.ShareResourceNotFound(share_id=share['id']) raise exception.ShareResourceNotFound(share_id=share['id'])
host_list = [] host_list = []
share_id = self._get_hnas_share_id(share['id'])
for rule in access_rules: for rule in access_rules:
if rule['access_type'].lower() != 'ip': if rule['access_type'].lower() != 'ip':
@ -172,13 +175,13 @@ class HDSHNASDriver(driver.ShareDriver):
host_list.append(rule['access_to'] + '(' + host_list.append(rule['access_to'] + '(' +
rule['access_level'] + ')') rule['access_level'] + ')')
self.hnas.update_access_rule(share_id, host_list) self.hnas.update_access_rule(hnas_share_id, host_list)
if host_list: if host_list:
LOG.debug("Share %(share)s has the rules: %(rules)s", LOG.debug("Share %(share)s has the rules: %(rules)s",
{'share': share_id, 'rules': ', '.join(host_list)}) {'share': share['id'], 'rules': ', '.join(host_list)})
else: else:
LOG.debug("Share %(share)s has no rules.", {'share': share_id}) LOG.debug("Share %(share)s has no rules.", {'share': share['id']})
def create_share(self, context, share, share_server=None): def create_share(self, context, share, share_server=None):
"""Creates share. """Creates share.
@ -212,12 +215,15 @@ class HDSHNASDriver(driver.ShareDriver):
:param share_server: Data structure with share server information. :param share_server: Data structure with share server information.
Not used by this driver. Not used by this driver.
""" """
share_id = self._get_hnas_share_id(share['id']) hnas_share_id = self._get_hnas_share_id(share['id'])
LOG.debug("Deleting share in HNAS: %(shr)s.", LOG.debug("Deleting share in HNAS: %(shr)s.",
{'shr': share['id']}) {'shr': share['id']})
self._delete_share(share_id) self._delete_share(hnas_share_id)
LOG.debug("Export and share successfully deleted: %(shr)s.",
{'shr': share['id']})
def create_snapshot(self, context, snapshot, share_server=None): def create_snapshot(self, context, snapshot, share_server=None):
"""Creates snapshot. """Creates snapshot.
@ -227,13 +233,13 @@ class HDSHNASDriver(driver.ShareDriver):
:param share_server: Data structure with share server information. :param share_server: Data structure with share server information.
Not used by this driver. Not used by this driver.
""" """
share_id = self._get_hnas_share_id(snapshot['share_id']) hnas_share_id = self._get_hnas_share_id(snapshot['share_id'])
LOG.debug("The snapshot of share %(ss_sid)s will be created with " LOG.debug("The snapshot of share %(ss_sid)s will be created with "
"id %(ss_id)s.", {'ss_sid': snapshot['share_id'], "id %(ss_id)s.", {'ss_sid': snapshot['share_id'],
'ss_id': snapshot['id']}) 'ss_id': snapshot['id']})
self._create_snapshot(share_id, snapshot['id']) self._create_snapshot(hnas_share_id, snapshot['id'])
LOG.info(_LI("Snapshot %(id)s successfully created."), LOG.info(_LI("Snapshot %(id)s successfully created."),
{'id': snapshot['id']}) {'id': snapshot['id']})
@ -245,13 +251,13 @@ class HDSHNASDriver(driver.ShareDriver):
:param share_server: Data structure with share server information. :param share_server: Data structure with share server information.
Not used by this driver. Not used by this driver.
""" """
share_id = self._get_hnas_share_id(snapshot['share_id']) hnas_share_id = self._get_hnas_share_id(snapshot['share_id'])
LOG.debug("The snapshot %(ss_sid)s will be deleted. The related " LOG.debug("The snapshot %(ss_sid)s will be deleted. The related "
"share ID is %(ss_id)s.", "share ID is %(ss_id)s.",
{'ss_sid': snapshot['share_id'], 'ss_id': snapshot['id']}) {'ss_sid': snapshot['share_id'], 'ss_id': snapshot['id']})
self._delete_snapshot(share_id, snapshot['id']) self._delete_snapshot(hnas_share_id, snapshot['id'])
LOG.info(_LI("Snapshot %(id)s successfully deleted."), LOG.info(_LI("Snapshot %(id)s successfully deleted."),
{'id': snapshot['id']}) {'id': snapshot['id']})
@ -271,11 +277,15 @@ class HDSHNASDriver(driver.ShareDriver):
LOG.debug("Creating a new share from snapshot: %(ss_id)s.", LOG.debug("Creating a new share from snapshot: %(ss_id)s.",
{'ss_id': snapshot['id']}) {'ss_id': snapshot['id']})
path = self._create_share_from_snapshot(share, snapshot) hnas_src_share_id = self._get_hnas_share_id(snapshot['share_id'])
path = self._create_share_from_snapshot(share, hnas_src_share_id,
snapshot)
uri = self.hnas_evs_ip + ":" + path uri = self.hnas_evs_ip + ":" + path
LOG.debug("Share created successfully on path: %(uri)s.", LOG.debug("Share %(share)s created successfully on path: %(uri)s.",
{'uri': uri}) {'uri': uri,
'share': share['id']})
return uri return uri
def ensure_share(self, context, share, share_server=None): def ensure_share(self, context, share, share_server=None):
@ -291,9 +301,9 @@ class HDSHNASDriver(driver.ShareDriver):
LOG.debug("Ensuring share in HNAS: %(shr)s.", LOG.debug("Ensuring share in HNAS: %(shr)s.",
{'shr': share['id']}) {'shr': share['id']})
share_id = self._get_hnas_share_id(share['id']) hnas_share_id = self._get_hnas_share_id(share['id'])
path = self._ensure_share(share_id) path = self._ensure_share(hnas_share_id)
export = self.hnas_evs_ip + ":" + path export = self.hnas_evs_ip + ":" + path
export_list = [export] export_list = [export]
@ -310,12 +320,12 @@ class HDSHNASDriver(driver.ShareDriver):
:param share_server: Data structure with share server information. :param share_server: Data structure with share server information.
Not used by this driver. Not used by this driver.
""" """
share_id = self._get_hnas_share_id(share['id']) hnas_share_id = self._get_hnas_share_id(share['id'])
LOG.debug("Expanding share in HNAS: %(shr_id)s.", LOG.debug("Expanding share in HNAS: %(shr_id)s.",
{'shr_id': share['id']}) {'shr_id': share['id']})
self._extend_share(share_id, share['size'], new_size) self._extend_share(hnas_share_id, share, new_size)
LOG.info(_LI("Share %(shr_id)s successfully extended to " LOG.info(_LI("Share %(shr_id)s successfully extended to "
"%(shr_size)s."), "%(shr_size)s."),
{'shr_id': share['id'], {'shr_id': share['id'],
@ -367,10 +377,12 @@ class HDSHNASDriver(driver.ShareDriver):
:returns: Returns a dict with size of share managed :returns: Returns a dict with size of share managed
and its location (your path in file-system). and its location (your path in file-system).
""" """
share_id = self._get_hnas_share_id(share['id']) hnas_share_id = self._get_hnas_share_id(share['id'])
if share_id != share['id']: # Make sure returned value is the same as provided,
msg = _("Share ID %s already exists, cannot manage.") % share_id # confirming it does not exist.
if hnas_share_id != share['id']:
msg = _("Share ID %s already exists, cannot manage.") % share['id']
raise exception.HNASBackendException(msg=msg) raise exception.HNASBackendException(msg=msg)
LOG.info(_LI("Share %(shr_path)s will be managed with ID %(shr_id)s."), LOG.info(_LI("Share %(shr_path)s will be managed with ID %(shr_id)s."),
@ -382,7 +394,7 @@ class HDSHNASDriver(driver.ShareDriver):
if len(old_path) == 3: if len(old_path) == 3:
evs_ip = old_path_info[0] evs_ip = old_path_info[0]
share_id = old_path[2] hnas_share_id = old_path[2]
else: else:
msg = _("Incorrect path. It should have the following format: " msg = _("Incorrect path. It should have the following format: "
"IP:/shares/share_id.") "IP:/shares/share_id.")
@ -398,9 +410,18 @@ class HDSHNASDriver(driver.ShareDriver):
"not configured.") % {'shr': share['host']} "not configured.") % {'shr': share['host']}
raise exception.ShareBackendException(msg=msg) raise exception.ShareBackendException(msg=msg)
output = self._manage_existing(share_id) output = self._manage_existing(share['id'], hnas_share_id)
self.private_storage.update( self.private_storage.update(
share['id'], {'hnas_id': share_id}) share['id'], {'hnas_id': hnas_share_id})
LOG.debug("HNAS ID %(hnas_id)s has been saved to private storage for "
"Share ID %(share_id)s", {'hnas_id': hnas_share_id,
'share_id': share['id']})
LOG.info(_LI("Share %(shr_path)s was successfully managed with ID "
"%(shr_id)s."),
{'shr_path': share['export_locations'][0]['path'],
'shr_id': share['id']})
return output return output
@ -428,12 +449,12 @@ class HDSHNASDriver(driver.ShareDriver):
:param share_server: Data structure with share server information. :param share_server: Data structure with share server information.
Not used by this driver. Not used by this driver.
""" """
share_id = self._get_hnas_share_id(share['id']) hnas_share_id = self._get_hnas_share_id(share['id'])
LOG.debug("Shrinking share in HNAS: %(shr_id)s.", LOG.debug("Shrinking share in HNAS: %(shr_id)s.",
{'shr_id': share['id']}) {'shr_id': share['id']})
self._shrink_share(share_id, new_size) self._shrink_share(hnas_share_id, share, new_size)
LOG.info(_LI("Share %(shr_id)s successfully shrunk to " LOG.info(_LI("Share %(shr_id)s successfully shrunk to "
"%(shr_size)sG."), "%(shr_size)sG."),
{'shr_id': share['id'], {'shr_id': share['id'],
@ -444,18 +465,23 @@ class HDSHNASDriver(driver.ShareDriver):
if hnas_id is None: if hnas_id is None:
hnas_id = share_id hnas_id = share_id
LOG.debug("Share ID is %(shr_id)s and respective HNAS ID "
"is %(hnas_id)s.", {'shr_id': share_id,
'hnas_id': hnas_id})
return hnas_id return hnas_id
def _create_share(self, share_id, share_size): def _create_share(self, share_id, share_size):
"""Creates share. """Creates share.
Creates a virtual-volume, adds a quota limit and exports it. Creates a virtual-volume, adds a quota limit and exports it.
:param share_id: ID of share that will be created. :param share_id: manila's database ID of share that will be created.
:param share_size: Size limit of share. :param share_size: Size limit of share.
:returns: Returns a path of /shares/share_id if the export was :returns: Returns a path of /shares/share_id if the export was
created successfully. created successfully.
""" """
path = '/shares/' + share_id path = os.path.join('/shares', share_id)
self._check_fs_mounted() self._check_fs_mounted()
@ -484,112 +510,113 @@ class HDSHNASDriver(driver.ShareDriver):
msg = _("Filesystem %s is not mounted.") % self.fs_name msg = _("Filesystem %s is not mounted.") % self.fs_name
raise exception.HNASBackendException(msg=msg) raise exception.HNASBackendException(msg=msg)
def _ensure_share(self, share_id): def _ensure_share(self, hnas_share_id):
"""Ensure that share is exported. """Ensure that share is exported.
:param share_id: ID of share that will be checked. :param hnas_share_id: HNAS ID of share that will be checked.
:returns: Returns a path of /shares/share_id if the export is ok. :returns: Returns a path of /shares/share_id if the export is ok.
""" """
path = '/shares/' + share_id path = os.path.join('/shares', hnas_share_id)
self._check_fs_mounted() self._check_fs_mounted()
self.hnas.check_vvol(share_id) self.hnas.check_vvol(hnas_share_id)
self.hnas.check_quota(share_id) self.hnas.check_quota(hnas_share_id)
self.hnas.check_export(share_id) self.hnas.check_export(hnas_share_id)
return path return path
def _shrink_share(self, share_id, new_size): def _shrink_share(self, hnas_share_id, share, new_size):
"""Shrinks a share to new size. """Shrinks a share to new size.
:param share_id: ID of share that will be shrunk. :param hnas_share_id: HNAS ID of share that will be shrunk.
:param share: model of share that will be shrunk.
:param new_size: New size of share after shrink operation. :param new_size: New size of share after shrink operation.
""" """
self._ensure_share(share_id) self._ensure_share(hnas_share_id)
usage = self.hnas.get_share_usage(share_id) usage = self.hnas.get_share_usage(hnas_share_id)
LOG.debug("Usage space in share %(share)s: %(usage)sG", LOG.debug("Usage space in share %(share)s: %(usage)sG",
{'share': share_id, 'usage': usage}) {'share': share['id'], 'usage': usage})
if new_size > usage: if new_size > usage:
self.hnas.modify_quota(share_id, new_size) self.hnas.modify_quota(hnas_share_id, new_size)
else: else:
raise exception.ShareShrinkingPossibleDataLoss(share_id=share_id) raise exception.ShareShrinkingPossibleDataLoss(
share_id=share['id'])
def _extend_share(self, share_id, old_size, new_size): def _extend_share(self, hnas_share_id, share, new_size):
"""Extends a share to new size. """Extends a share to new size.
:param share_id: ID of share that will be extended. :param hnas_share_id: HNAS ID of share that will be extended.
:param old_size: Current size of share that will be extended. :param share: model of share that will be extended.
:param new_size: New size of share after extend operation. :param new_size: New size of share after extend operation.
""" """
self._ensure_share(share_id) self._ensure_share(hnas_share_id)
old_size = share['size']
total, available_space = self.hnas.get_stats() total, available_space = self.hnas.get_stats()
LOG.debug("Available space in filesystem: %(space)sG.", LOG.debug("Available space in filesystem: %(space)sG.",
{'space': available_space}) {'space': available_space})
if (new_size - old_size) < available_space: if (new_size - old_size) < available_space:
self.hnas.modify_quota(share_id, new_size) self.hnas.modify_quota(hnas_share_id, new_size)
else: else:
msg = (_("Share %s cannot be extended due to insufficient space.") msg = (_("Share %s cannot be extended due to insufficient space.")
% share_id) % share['id'])
raise exception.HNASBackendException(msg=msg) raise exception.HNASBackendException(msg=msg)
def _delete_share(self, share_id): def _delete_share(self, hnas_share_id):
"""Deletes share. """Deletes share.
It uses tree-delete-job-submit to format and delete virtual-volumes. It uses tree-delete-job-submit to format and delete virtual-volumes.
Quota is deleted with virtual-volume. Quota is deleted with virtual-volume.
:param share_id: ID of share that will be deleted. :param hnas_share_id: HNAS ID of share that will be deleted.
""" """
self._check_fs_mounted() self._check_fs_mounted()
self.hnas.nfs_export_del(share_id) self.hnas.nfs_export_del(hnas_share_id)
self.hnas.vvol_delete(share_id) self.hnas.vvol_delete(hnas_share_id)
LOG.debug("Export and share successfully deleted: %(shr)s on Manila.", def _manage_existing(self, share_id, hnas_share_id):
{'shr': share_id})
def _manage_existing(self, share_id):
"""Manages a share that exists on backend. """Manages a share that exists on backend.
:param share_id: ID of share that will be managed. :param share_id: manila's database ID of share that will be managed.
:param hnas_share_id: HNAS ID of share that will be managed.
:returns: Returns a dict with size of share managed :returns: Returns a dict with size of share managed
and its location (your path in file-system). and its location (your path in file-system).
""" """
self._ensure_share(share_id) self._ensure_share(hnas_share_id)
share_size = self.hnas.get_share_quota(share_id) share_size = self.hnas.get_share_quota(hnas_share_id)
if share_size is None: if share_size is None:
msg = (_("The share %s trying to be managed does not have a " msg = (_("The share %s trying to be managed does not have a "
"quota limit, please set it before manage.") % share_id) "quota limit, please set it before manage.") % share_id)
raise exception.ManageInvalidShare(reason=msg) raise exception.ManageInvalidShare(reason=msg)
path = self.hnas_evs_ip + ':/shares/' + share_id path = self.hnas_evs_ip + os.path.join(':/shares', hnas_share_id)
return {'size': share_size, 'export_locations': [path]} return {'size': share_size, 'export_locations': [path]}
def _create_snapshot(self, share_id, snapshot_id): def _create_snapshot(self, hnas_share_id, snapshot_id):
"""Creates a snapshot of share. """Creates a snapshot of share.
It copies the directory and all files to a new directory inside It copies the directory and all files to a new directory inside
/snapshots/share_id/. /snapshots/share_id/.
:param share_id: ID of share for snapshot. :param hnas_share_id: HNAS ID of share for snapshot.
:param snapshot_id: ID of new snapshot. :param snapshot_id: ID of new snapshot.
""" """
self._ensure_share(share_id) self._ensure_share(hnas_share_id)
saved_list = self.hnas.get_host_list(share_id) saved_list = self.hnas.get_host_list(hnas_share_id)
new_list = [] new_list = []
for access in saved_list: for access in saved_list:
new_list.append(access.replace('(rw)', '(ro)')) new_list.append(access.replace('(rw)', '(ro)'))
self.hnas.update_access_rule(share_id, new_list) self.hnas.update_access_rule(hnas_share_id, new_list)
src_path = '/shares/' + share_id src_path = os.path.join('/shares', hnas_share_id)
dest_path = '/snapshots/' + share_id + '/' + snapshot_id dest_path = os.path.join('/snapshots', hnas_share_id, snapshot_id)
try: try:
self.hnas.tree_clone(src_path, dest_path) self.hnas.tree_clone(src_path, dest_path)
except exception.HNASNothingToCloneException: except exception.HNASNothingToCloneException:
@ -597,35 +624,37 @@ class HDSHNASDriver(driver.ShareDriver):
"directory.")) "directory."))
self.hnas.create_directory(dest_path) self.hnas.create_directory(dest_path)
finally: finally:
self.hnas.update_access_rule(share_id, saved_list) self.hnas.update_access_rule(hnas_share_id, saved_list)
def _delete_snapshot(self, share_id, snapshot_id): def _delete_snapshot(self, hnas_share_id, snapshot_id):
"""Deletes snapshot. """Deletes snapshot.
It receives the share_id only to mount the path for snapshot. It receives the hnas_share_id only to join the path for snapshot.
:param share_id: ID of share that snapshot was created. :param hnas_share_id: HNAS ID of share from which snapshot was taken.
:param snapshot_id: ID of snapshot. :param snapshot_id: ID of snapshot.
""" """
self._check_fs_mounted() self._check_fs_mounted()
path = '/snapshots/' + share_id + '/' + snapshot_id path = os.path.join('/snapshots', hnas_share_id, snapshot_id)
self.hnas.tree_delete(path) self.hnas.tree_delete(path)
path = '/snapshots/' + share_id path = os.path.join('/snapshots', hnas_share_id)
self.hnas.delete_directory(path) self.hnas.delete_directory(path)
def _create_share_from_snapshot(self, share, snapshot): def _create_share_from_snapshot(self, share, src_hnas_share_id, snapshot):
"""Creates a new share from snapshot. """Creates a new share from snapshot.
It copies everything from snapshot directory to a new vvol, It copies everything from snapshot directory to a new vvol,
set a quota limit for it and export. set a quota limit for it and export.
:param share: a dict from new share. :param share: a dict from new share.
:param src_hnas_share_id: HNAS ID of share from which snapshot was
taken.
:param snapshot: a dict from snapshot that will be copied to :param snapshot: a dict from snapshot that will be copied to
new share. new share.
:returns: Returns the path for new share. :returns: Returns the path for new share.
""" """
dest_path = '/shares/' + share['id'] dest_path = os.path.join('/shares', share['id'])
src_path = '/snapshots/' + snapshot['share_id'] + '/' + snapshot['id'] src_path = os.path.join('/snapshots', src_hnas_share_id,
snapshot['id'])
# Before copying everything to new vvol, we need to create it, # Before copying everything to new vvol, we need to create it,
# because we only can transform an empty directory into a vvol. # because we only can transform an empty directory into a vvol.

View File

@ -153,16 +153,18 @@ class HDSHNASTestCase(test.TestCase):
} }
access_list = [access1, access2] access_list = [access1, access2]
self.mock_object(self._driver, '_get_hnas_share_id',
mock.Mock(return_value='hnas_id'))
self.mock_object(self._driver, '_ensure_share') self.mock_object(self._driver, '_ensure_share')
self.mock_object(ssh.HNASSSHBackend, "update_access_rule", self.mock_object(ssh.HNASSSHBackend, "update_access_rule",
mock.Mock()) mock.Mock())
self._driver.update_access('context', share, access_list, [], []) self._driver.update_access('context', share, access_list, [], [])
ssh.HNASSSHBackend.update_access_rule.assert_called_once_with( ssh.HNASSSHBackend.update_access_rule.assert_called_once_with(
share['id'], [access1['access_to'] + '(' 'hnas_id', [access1['access_to'] + '('
+ access1['access_level'] + ',norootsquash)', + access1['access_level'] + ',norootsquash)',
access2['access_to'] + '(' access2['access_to'] + '('
+ access2['access_level'] + ')']) + access2['access_level'] + ')'])
self.assertTrue(self.mock_log.debug.called) self.assertTrue(self.mock_log.debug.called)
def test_update_access_ip_exception(self): def test_update_access_ip_exception(self):
@ -249,7 +251,7 @@ class HDSHNASTestCase(test.TestCase):
def test_delete_share(self): def test_delete_share(self):
self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id", self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id",
mock.Mock(return_value=share['id'])) mock.Mock(return_value='hnas_id'))
self.mock_object(hds_hnas.HDSHNASDriver, "_check_fs_mounted", self.mock_object(hds_hnas.HDSHNASDriver, "_check_fs_mounted",
mock.Mock()) mock.Mock())
self.mock_object(ssh.HNASSSHBackend, "nfs_export_del", mock.Mock()) self.mock_object(ssh.HNASSSHBackend, "nfs_export_del", mock.Mock())
@ -258,13 +260,13 @@ class HDSHNASTestCase(test.TestCase):
self._driver.delete_share('context', share) self._driver.delete_share('context', share)
self.assertTrue(self.mock_log.debug.called) self.assertTrue(self.mock_log.debug.called)
ssh.HNASSSHBackend.nfs_export_del.assert_called_once_with(share['id']) ssh.HNASSSHBackend.nfs_export_del.assert_called_once_with('hnas_id')
ssh.HNASSSHBackend.vvol_delete.assert_called_once_with(share['id']) ssh.HNASSSHBackend.vvol_delete.assert_called_once_with('hnas_id')
def test_create_snapshot(self): def test_create_snapshot(self):
self.mock_object(hds_hnas.HDSHNASDriver, "_ensure_share") self.mock_object(hds_hnas.HDSHNASDriver, "_ensure_share")
self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id", self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id",
mock.Mock(return_value=share['id'])) mock.Mock(return_value='hnas_id'))
self.mock_object(ssh.HNASSSHBackend, "get_host_list", mock.Mock( self.mock_object(ssh.HNASSSHBackend, "get_host_list", mock.Mock(
return_value=['172.24.44.200(rw)'])) return_value=['172.24.44.200(rw)']))
self.mock_object(ssh.HNASSSHBackend, "update_access_rule", mock.Mock()) self.mock_object(ssh.HNASSSHBackend, "update_access_rule", mock.Mock())
@ -272,19 +274,19 @@ class HDSHNASTestCase(test.TestCase):
self._driver.create_snapshot('context', snapshot) self._driver.create_snapshot('context', snapshot)
ssh.HNASSSHBackend.get_host_list.assert_called_once_with(share['id']) ssh.HNASSSHBackend.get_host_list.assert_called_once_with('hnas_id')
ssh.HNASSSHBackend.update_access_rule.assert_any_call( ssh.HNASSSHBackend.update_access_rule.assert_any_call(
share['id'], ['172.24.44.200(ro)']) 'hnas_id', ['172.24.44.200(ro)'])
ssh.HNASSSHBackend.update_access_rule.assert_any_call( ssh.HNASSSHBackend.update_access_rule.assert_any_call(
share['id'], ['172.24.44.200(rw)']) 'hnas_id', ['172.24.44.200(rw)'])
ssh.HNASSSHBackend.tree_clone.assert_called_once_with( ssh.HNASSSHBackend.tree_clone.assert_called_once_with(
'/shares/' + share['id'], '/snapshots/' + share['id'] + '/' + '/shares/' + 'hnas_id', '/snapshots/' + 'hnas_id' + '/' +
snapshot['id']) snapshot['id'])
def test_create_snapshot_first_snapshot(self): def test_create_snapshot_first_snapshot(self):
self.mock_object(hds_hnas.HDSHNASDriver, "_ensure_share") self.mock_object(hds_hnas.HDSHNASDriver, "_ensure_share")
self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id", self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id",
mock.Mock(return_value=share['id'])) mock.Mock(return_value='hnas_id'))
self.mock_object(ssh.HNASSSHBackend, "get_host_list", mock.Mock( self.mock_object(ssh.HNASSSHBackend, "get_host_list", mock.Mock(
return_value=['172.24.44.200(rw)'])) return_value=['172.24.44.200(rw)']))
self.mock_object(ssh.HNASSSHBackend, "update_access_rule", mock.Mock()) self.mock_object(ssh.HNASSSHBackend, "update_access_rule", mock.Mock())
@ -295,17 +297,17 @@ class HDSHNASTestCase(test.TestCase):
self._driver.create_snapshot('context', snapshot) self._driver.create_snapshot('context', snapshot)
self.assertTrue(self.mock_log.warning.called) self.assertTrue(self.mock_log.warning.called)
ssh.HNASSSHBackend.get_host_list.assert_called_once_with(share['id']) ssh.HNASSSHBackend.get_host_list.assert_called_once_with('hnas_id')
ssh.HNASSSHBackend.update_access_rule.assert_any_call( ssh.HNASSSHBackend.update_access_rule.assert_any_call(
share['id'], ['172.24.44.200(ro)']) 'hnas_id', ['172.24.44.200(ro)'])
ssh.HNASSSHBackend.update_access_rule.assert_any_call( ssh.HNASSSHBackend.update_access_rule.assert_any_call(
share['id'], ['172.24.44.200(rw)']) 'hnas_id', ['172.24.44.200(rw)'])
ssh.HNASSSHBackend.create_directory.assert_called_once_with( ssh.HNASSSHBackend.create_directory.assert_called_once_with(
'/snapshots/' + share['id'] + '/' + snapshot['id']) '/snapshots/' + 'hnas_id' + '/' + snapshot['id'])
def test_delete_snapshot(self): def test_delete_snapshot(self):
self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id", self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id",
mock.Mock(return_value=share['id'])) mock.Mock(return_value='hnas_id'))
self.mock_object(hds_hnas.HDSHNASDriver, "_check_fs_mounted") self.mock_object(hds_hnas.HDSHNASDriver, "_check_fs_mounted")
self.mock_object(ssh.HNASSSHBackend, "tree_delete", mock.Mock()) self.mock_object(ssh.HNASSSHBackend, "tree_delete", mock.Mock())
self.mock_object(ssh.HNASSSHBackend, "delete_directory", mock.Mock()) self.mock_object(ssh.HNASSSHBackend, "delete_directory", mock.Mock())
@ -316,13 +318,13 @@ class HDSHNASTestCase(test.TestCase):
self.assertTrue(self.mock_log.info.called) self.assertTrue(self.mock_log.info.called)
hds_hnas.HDSHNASDriver._check_fs_mounted.assert_called_once_with() hds_hnas.HDSHNASDriver._check_fs_mounted.assert_called_once_with()
ssh.HNASSSHBackend.tree_delete.assert_called_once_with( ssh.HNASSSHBackend.tree_delete.assert_called_once_with(
'/snapshots/' + share['id'] + '/' + snapshot['id']) '/snapshots/' + 'hnas_id' + '/' + snapshot['id'])
ssh.HNASSSHBackend.delete_directory.assert_called_once_with( ssh.HNASSSHBackend.delete_directory.assert_called_once_with(
'/snapshots/' + share['id']) '/snapshots/' + 'hnas_id')
def test_ensure_share(self): def test_ensure_share(self):
self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id", self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id",
mock.Mock(return_value=share['id'])) mock.Mock(return_value='hnas_id'))
self.mock_object(hds_hnas.HDSHNASDriver, "_check_fs_mounted", self.mock_object(hds_hnas.HDSHNASDriver, "_check_fs_mounted",
mock.Mock()) mock.Mock())
self.mock_object(ssh.HNASSSHBackend, "check_vvol", mock.Mock()) self.mock_object(ssh.HNASSSHBackend, "check_vvol", mock.Mock())
@ -331,14 +333,14 @@ class HDSHNASTestCase(test.TestCase):
result = self._driver.ensure_share('context', share) result = self._driver.ensure_share('context', share)
self.assertEqual(['172.24.44.10:/shares/' + share['id']], result) self.assertEqual(['172.24.44.10:/shares/' + 'hnas_id'], result)
ssh.HNASSSHBackend.check_vvol.assert_called_once_with(share['id']) ssh.HNASSSHBackend.check_vvol.assert_called_once_with('hnas_id')
ssh.HNASSSHBackend.check_quota.assert_called_once_with(share['id']) ssh.HNASSSHBackend.check_quota.assert_called_once_with('hnas_id')
ssh.HNASSSHBackend.check_export.assert_called_once_with(share['id']) ssh.HNASSSHBackend.check_export.assert_called_once_with('hnas_id')
def test_shrink_share(self): def test_shrink_share(self):
self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id", self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id",
mock.Mock(return_value=share['id'])) mock.Mock(return_value='hnas_id'))
self.mock_object(hds_hnas.HDSHNASDriver, "_ensure_share", mock.Mock()) self.mock_object(hds_hnas.HDSHNASDriver, "_ensure_share", mock.Mock())
self.mock_object(ssh.HNASSSHBackend, "get_share_usage", mock.Mock( self.mock_object(ssh.HNASSSHBackend, "get_share_usage", mock.Mock(
return_value=10)) return_value=10))
@ -346,24 +348,23 @@ class HDSHNASTestCase(test.TestCase):
self._driver.shrink_share(share, 11) self._driver.shrink_share(share, 11)
ssh.HNASSSHBackend.get_share_usage.assert_called_once_with(share['id']) ssh.HNASSSHBackend.get_share_usage.assert_called_once_with('hnas_id')
ssh.HNASSSHBackend.modify_quota.assert_called_once_with(share['id'], ssh.HNASSSHBackend.modify_quota.assert_called_once_with('hnas_id', 11)
11)
def test_shrink_share_new_size_lower_than_usage(self): def test_shrink_share_new_size_lower_than_usage(self):
self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id", self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id",
mock.Mock(return_value=share['id'])) mock.Mock(return_value='hnas_id'))
self.mock_object(hds_hnas.HDSHNASDriver, "_ensure_share", mock.Mock()) self.mock_object(hds_hnas.HDSHNASDriver, "_ensure_share", mock.Mock())
self.mock_object(ssh.HNASSSHBackend, "get_share_usage", mock.Mock( self.mock_object(ssh.HNASSSHBackend, "get_share_usage", mock.Mock(
return_value=10)) return_value=10))
self.assertRaises(exception.ShareShrinkingPossibleDataLoss, self.assertRaises(exception.ShareShrinkingPossibleDataLoss,
self._driver.shrink_share, share, 9) self._driver.shrink_share, share, 9)
ssh.HNASSSHBackend.get_share_usage.assert_called_once_with(share['id']) ssh.HNASSSHBackend.get_share_usage.assert_called_once_with('hnas_id')
def test_extend_share(self): def test_extend_share(self):
self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id", self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id",
mock.Mock(return_value=share['id'])) mock.Mock(return_value='hnas_id'))
self.mock_object(hds_hnas.HDSHNASDriver, "_ensure_share", mock.Mock()) self.mock_object(hds_hnas.HDSHNASDriver, "_ensure_share", mock.Mock())
self.mock_object(ssh.HNASSSHBackend, "get_stats", mock.Mock( self.mock_object(ssh.HNASSSHBackend, "get_stats", mock.Mock(
return_value=(500, 200))) return_value=(500, 200)))
@ -372,12 +373,11 @@ class HDSHNASTestCase(test.TestCase):
self._driver.extend_share(share, 150) self._driver.extend_share(share, 150)
ssh.HNASSSHBackend.get_stats.assert_called_once_with() ssh.HNASSSHBackend.get_stats.assert_called_once_with()
ssh.HNASSSHBackend.modify_quota.assert_called_once_with(share['id'], ssh.HNASSSHBackend.modify_quota.assert_called_once_with('hnas_id', 150)
150)
def test_extend_share_with_no_available_space_in_fs(self): def test_extend_share_with_no_available_space_in_fs(self):
self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id", self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id",
mock.Mock(return_value=share['id'])) mock.Mock(return_value='hnas_id'))
self.mock_object(hds_hnas.HDSHNASDriver, "_ensure_share", mock.Mock()) self.mock_object(hds_hnas.HDSHNASDriver, "_ensure_share", mock.Mock())
self.mock_object(ssh.HNASSSHBackend, "get_stats", mock.Mock( self.mock_object(ssh.HNASSSHBackend, "get_stats", mock.Mock(
return_value=(500, 200))) return_value=(500, 200)))
@ -450,6 +450,8 @@ class HDSHNASTestCase(test.TestCase):
def test_create_share_from_snapshot(self): def test_create_share_from_snapshot(self):
self.mock_object(hds_hnas.HDSHNASDriver, "_check_fs_mounted", self.mock_object(hds_hnas.HDSHNASDriver, "_check_fs_mounted",
mock.Mock()) mock.Mock())
self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id",
mock.Mock(return_value='hnas_id'))
self.mock_object(ssh.HNASSSHBackend, "vvol_create", 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, "quota_add", mock.Mock())
self.mock_object(ssh.HNASSSHBackend, "tree_clone", mock.Mock()) self.mock_object(ssh.HNASSSHBackend, "tree_clone", mock.Mock())
@ -463,13 +465,15 @@ class HDSHNASTestCase(test.TestCase):
ssh.HNASSSHBackend.quota_add.assert_called_once_with(share['id'], ssh.HNASSSHBackend.quota_add.assert_called_once_with(share['id'],
share['size']) share['size'])
ssh.HNASSSHBackend.tree_clone.assert_called_once_with( ssh.HNASSSHBackend.tree_clone.assert_called_once_with(
'/snapshots/' + snapshot['share_id'] + '/' + snapshot['id'], '/snapshots/' + 'hnas_id' + '/' + snapshot['id'],
'/shares/' + share['id']) '/shares/' + share['id'])
ssh.HNASSSHBackend.nfs_export_add.assert_called_once_with(share['id']) ssh.HNASSSHBackend.nfs_export_add.assert_called_once_with(share['id'])
def test_create_share_from_snapshot_empty_snapshot(self): def test_create_share_from_snapshot_empty_snapshot(self):
self.mock_object(hds_hnas.HDSHNASDriver, "_check_fs_mounted", self.mock_object(hds_hnas.HDSHNASDriver, "_check_fs_mounted",
mock.Mock()) mock.Mock())
self.mock_object(hds_hnas.HDSHNASDriver, "_get_hnas_share_id",
mock.Mock(return_value='hnas_id'))
self.mock_object(ssh.HNASSSHBackend, "vvol_create", 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, "quota_add", mock.Mock())
self.mock_object(ssh.HNASSSHBackend, "tree_clone", mock.Mock( self.mock_object(ssh.HNASSSHBackend, "tree_clone", mock.Mock(
@ -485,7 +489,7 @@ class HDSHNASTestCase(test.TestCase):
ssh.HNASSSHBackend.quota_add.assert_called_once_with(share['id'], ssh.HNASSSHBackend.quota_add.assert_called_once_with(share['id'],
share['size']) share['size'])
ssh.HNASSSHBackend.tree_clone.assert_called_once_with( ssh.HNASSSHBackend.tree_clone.assert_called_once_with(
'/snapshots/' + snapshot['share_id'] + '/' + snapshot['id'], '/snapshots/' + 'hnas_id' + '/' + snapshot['id'],
'/shares/' + share['id']) '/shares/' + share['id'])
ssh.HNASSSHBackend.nfs_export_add.assert_called_once_with(share['id']) ssh.HNASSSHBackend.nfs_export_add.assert_called_once_with(share['id'])

View File

@ -0,0 +1,9 @@
---
fixes:
- Fixed error when allowing access to a managed share in
HDS HNAS driver.
- Fixed error when attempting to create a new share from
a snapshot taken from a managed share in HDS HNAS
driver.
- Fixed ID inconsistencies in log when handling managed
shares in HDS HNAS driver.