HP 3PAR driver fix for delete snapshot
Attempting to delete a CIFS snapshot raises an AttributeError when attempting to split "shareDir" during dependent share checking. The split was made safe for an empty shareDir and the path indexing was fixed in the dependent share checking. Change-Id: Ieb745df6222817f6c459f62bac3fb66ecc576482 Closes-Bug: #1420055
This commit is contained in:
parent
50a267a3af
commit
58858ec84e
@ -403,11 +403,14 @@ class HP3ParMediator(object):
|
||||
path = share['sharePath'][1:].split('/')
|
||||
dot_snapshot_index = 3
|
||||
else:
|
||||
path = share['shareDir'].split('/')
|
||||
if share['shareDir']:
|
||||
path = share['shareDir'].split('/')
|
||||
else:
|
||||
path = None
|
||||
dot_snapshot_index = 0
|
||||
|
||||
snapshot_index = dot_snapshot_index
|
||||
if len(path) > snapshot_index + 1:
|
||||
snapshot_index = dot_snapshot_index + 1
|
||||
if path and len(path) > snapshot_index:
|
||||
if (path[dot_snapshot_index] == '.snapshot' and
|
||||
path[snapshot_index].endswith(snapshot_tag)):
|
||||
msg = (_('Cannot delete snapshot because it has a '
|
||||
|
@ -31,7 +31,7 @@ EXPECTED_IP_1234 = '1.2.3.4'
|
||||
EXPECTED_IP_127 = '127.0.0.1'
|
||||
EXPECTED_SHARE_ID = 'osf-share-id'
|
||||
EXPECTED_SHARE_NAME = 'share-name'
|
||||
EXPECTED_SHARE_PATH = '/share/path'
|
||||
EXPECTED_SHARE_PATH = '/anyfpg/anyvfs/anyfstore'
|
||||
EXPECTED_SIZE_1 = 1
|
||||
EXPECTED_SIZE_2 = 2
|
||||
EXPECTED_SNAP_NAME = 'osf-snap-name'
|
||||
|
@ -325,6 +325,24 @@ class HP3ParMediatorTestCase(test.TestCase):
|
||||
|
||||
self.mock_client.assert_has_calls(expected_calls)
|
||||
|
||||
def test_mediator_create_share_from_snap_not_found(self):
|
||||
self.init_mediator()
|
||||
|
||||
self.mock_client.getfsnap.return_value = {
|
||||
'message': None,
|
||||
'total': 0,
|
||||
'members': []
|
||||
}
|
||||
|
||||
self.assertRaises(exception.ShareBackendException,
|
||||
self.mediator.create_share_from_snapshot,
|
||||
constants.EXPECTED_SHARE_ID,
|
||||
constants.NFS,
|
||||
constants.EXPECTED_SHARE_ID,
|
||||
constants.EXPECTED_SNAP_ID,
|
||||
constants.EXPECTED_FPG,
|
||||
constants.EXPECTED_VFS)
|
||||
|
||||
def test_mediator_delete_share(self):
|
||||
self.init_mediator()
|
||||
|
||||
@ -362,6 +380,20 @@ class HP3ParMediatorTestCase(test.TestCase):
|
||||
]
|
||||
self.mock_client.assert_has_calls(expected_calls)
|
||||
|
||||
def test_mediator_create_snapshot_backend_exception(self):
|
||||
self.init_mediator()
|
||||
|
||||
# createfsnap exception
|
||||
self.mock_client.createfsnap.side_effect = Exception(
|
||||
'createfsnap fail.')
|
||||
|
||||
self.assertRaises(exception.ShareBackendException,
|
||||
self.mediator.create_snapshot,
|
||||
constants.EXPECTED_SHARE_ID,
|
||||
constants.EXPECTED_SNAP_NAME,
|
||||
constants.EXPECTED_FPG,
|
||||
constants.EXPECTED_VFS)
|
||||
|
||||
def test_mediator_delete_snapshot(self):
|
||||
self.init_mediator()
|
||||
|
||||
@ -369,10 +401,21 @@ class HP3ParMediatorTestCase(test.TestCase):
|
||||
|
||||
self.mock_client.getfsnap.return_value = {
|
||||
'total': 1,
|
||||
'members': [{'snapName': expected_name_from_array}],
|
||||
'message': None
|
||||
'members': [{'snapName': expected_name_from_array}]
|
||||
}
|
||||
|
||||
self.mock_client.getfshare.side_effect = [
|
||||
# some typical independent NFS share (path) and SMB share (dir)
|
||||
{
|
||||
'total': 1,
|
||||
'members': [{'sharePath': '/anyfpg/anyvfs/anyfstore'}]
|
||||
},
|
||||
{
|
||||
'total': 1,
|
||||
'members': [{'shareDir': []}],
|
||||
}
|
||||
]
|
||||
|
||||
self.mediator.delete_snapshot(constants.EXPECTED_SHARE_ID,
|
||||
constants.EXPECTED_SNAP_NAME,
|
||||
constants.EXPECTED_FPG,
|
||||
@ -401,6 +444,178 @@ class HP3ParMediatorTestCase(test.TestCase):
|
||||
]
|
||||
self.mock_client.assert_has_calls(expected_calls)
|
||||
|
||||
def test_mediator_delete_snapshot_not_found(self):
|
||||
self.init_mediator()
|
||||
|
||||
self.mock_client.getfsnap.return_value = {
|
||||
'total': 0,
|
||||
'members': [],
|
||||
}
|
||||
|
||||
self.mediator.delete_snapshot(constants.EXPECTED_SHARE_ID,
|
||||
constants.EXPECTED_SNAP_NAME,
|
||||
constants.EXPECTED_FPG,
|
||||
constants.EXPECTED_VFS)
|
||||
|
||||
expected_calls = [
|
||||
mock.call.getfsnap('*_%s' % constants.EXPECTED_SNAP_NAME,
|
||||
vfs=constants.EXPECTED_VFS,
|
||||
fpg=constants.EXPECTED_FPG,
|
||||
pat=True,
|
||||
fstore=constants.EXPECTED_SHARE_ID),
|
||||
]
|
||||
|
||||
# Code coverage for early exit when nothing to delete.
|
||||
self.mock_client.assert_has_calls(expected_calls)
|
||||
self.assertFalse(self.mock_client.getfshare.called)
|
||||
self.assertFalse(self.mock_client.removefsnap.called)
|
||||
self.assertFalse(self.mock_client.startfsnapclean.called)
|
||||
|
||||
def test_mediator_delete_snapshot_shared_nfs(self):
|
||||
self.init_mediator()
|
||||
|
||||
# Mock a share under this snapshot for NFS
|
||||
snapshot_dir = '.snapshot/DT_%s' % constants.EXPECTED_SNAP_NAME
|
||||
snapshot_path = '%s/%s' % (constants.EXPECTED_SHARE_PATH, snapshot_dir)
|
||||
|
||||
self.mock_client.getfsnap.return_value = {
|
||||
'total': 1,
|
||||
'members': [{'snapName': constants.EXPECTED_SNAP_NAME}]
|
||||
}
|
||||
|
||||
self.mock_client.getfshare.side_effect = [
|
||||
# some typical independent NFS share (path) and SMB share (dir)
|
||||
{
|
||||
'total': 1,
|
||||
'members': [{'sharePath': snapshot_path}],
|
||||
},
|
||||
{
|
||||
'total': 0,
|
||||
'members': [],
|
||||
}
|
||||
]
|
||||
|
||||
self.assertRaises(exception.Invalid,
|
||||
self.mediator.delete_snapshot,
|
||||
constants.EXPECTED_SHARE_ID,
|
||||
constants.EXPECTED_SNAP_NAME,
|
||||
constants.EXPECTED_FPG,
|
||||
constants.EXPECTED_VFS)
|
||||
|
||||
def test_mediator_delete_snapshot_shared_smb(self):
|
||||
self.init_mediator()
|
||||
|
||||
# Mock a share under this snapshot for SMB
|
||||
snapshot_dir = '.snapshot/DT_%s' % constants.EXPECTED_SNAP_NAME
|
||||
|
||||
self.mock_client.getfsnap.return_value = {
|
||||
'total': 1,
|
||||
'members': [{'snapName': constants.EXPECTED_SNAP_NAME}]
|
||||
}
|
||||
|
||||
self.mock_client.getfshare.side_effect = [
|
||||
# some typical independent NFS share (path) and SMB share (dir)
|
||||
{
|
||||
'total': 1,
|
||||
'members': [{'sharePath': constants.EXPECTED_SHARE_PATH}],
|
||||
},
|
||||
{
|
||||
'total': 1,
|
||||
'members': [{'shareDir': snapshot_dir}],
|
||||
}
|
||||
]
|
||||
|
||||
self.assertRaises(exception.Invalid,
|
||||
self.mediator.delete_snapshot,
|
||||
constants.EXPECTED_SHARE_ID,
|
||||
constants.EXPECTED_SNAP_NAME,
|
||||
constants.EXPECTED_FPG,
|
||||
constants.EXPECTED_VFS)
|
||||
|
||||
def _assert_delete_snapshot_raises(self):
|
||||
self.assertRaises(exception.ShareBackendException,
|
||||
self.mediator.delete_snapshot,
|
||||
constants.EXPECTED_SHARE_ID,
|
||||
constants.EXPECTED_SNAP_NAME,
|
||||
constants.EXPECTED_FPG,
|
||||
constants.EXPECTED_VFS)
|
||||
|
||||
def test_mediator_delete_snapshot_backend_exceptions(self):
|
||||
self.init_mediator()
|
||||
|
||||
# getfsnap exception
|
||||
self.mock_client.getfsnap.side_effect = Exception('getfsnap fail.')
|
||||
self._assert_delete_snapshot_raises()
|
||||
|
||||
# getfsnap OK
|
||||
self.mock_client.getfsnap.side_effect = None
|
||||
self.mock_client.getfsnap.return_value = {
|
||||
'total': 1,
|
||||
'members': [{'snapName': constants.EXPECTED_SNAP_NAME}]
|
||||
}
|
||||
|
||||
# getfshare exception
|
||||
self.mock_client.getfshare.side_effect = Exception('getfshare fail.')
|
||||
self._assert_delete_snapshot_raises()
|
||||
|
||||
# getfshare OK
|
||||
def mock_fshare(*args, **kwargs):
|
||||
if args[0] == constants.NFS_LOWER:
|
||||
return {
|
||||
'total': 1,
|
||||
'members': [{'sharePath': '/anyfpg/anyvfs/anyfstore'}]
|
||||
}
|
||||
else:
|
||||
return {
|
||||
'total': 1,
|
||||
'members': [{'shareDir': []}]
|
||||
}
|
||||
|
||||
self.mock_client.getfshare.side_effect = mock_fshare
|
||||
|
||||
# removefsnap exception
|
||||
self.mock_client.removefsnap.side_effect = Exception(
|
||||
'removefsnap fail.')
|
||||
self._assert_delete_snapshot_raises()
|
||||
|
||||
# removefsnap OK
|
||||
self.mock_client.removefsnap.side_effect = None
|
||||
self.mock_client.removefsnap.return_value = []
|
||||
|
||||
# startfsnapclean exception (logged, not raised)
|
||||
self.mock_client.startfsnapclean.side_effect = Exception(
|
||||
'startfsnapclean fail.')
|
||||
mock_log = self.mock_object(hp3parmediator, 'LOG')
|
||||
|
||||
self.mediator.delete_snapshot(constants.EXPECTED_SHARE_ID,
|
||||
constants.EXPECTED_SNAP_NAME,
|
||||
constants.EXPECTED_FPG,
|
||||
constants.EXPECTED_VFS)
|
||||
|
||||
expected_calls = [
|
||||
mock.call.getfsnap('*_%s' % constants.EXPECTED_SNAP_NAME,
|
||||
vfs=constants.EXPECTED_VFS,
|
||||
fpg=constants.EXPECTED_FPG,
|
||||
pat=True,
|
||||
fstore=constants.EXPECTED_SHARE_ID),
|
||||
mock.call.getfshare(constants.NFS_LOWER,
|
||||
fpg=constants.EXPECTED_FPG,
|
||||
vfs=constants.EXPECTED_VFS,
|
||||
fstore=constants.EXPECTED_SHARE_ID),
|
||||
mock.call.getfshare(constants.SMB_LOWER,
|
||||
fpg=constants.EXPECTED_FPG,
|
||||
vfs=constants.EXPECTED_VFS,
|
||||
fstore=constants.EXPECTED_SHARE_ID),
|
||||
mock.call.removefsnap(constants.EXPECTED_VFS,
|
||||
constants.EXPECTED_SHARE_ID,
|
||||
fpg=constants.EXPECTED_FPG,
|
||||
snapname=constants.EXPECTED_SNAP_NAME),
|
||||
mock.call.startfsnapclean(constants.EXPECTED_FPG,
|
||||
reclaimStrategy='maxspeed'),
|
||||
]
|
||||
self.mock_client.assert_has_calls(expected_calls)
|
||||
mock_log.assert_has_calls(mock.call.exception(mock.ANY))
|
||||
|
||||
def test_mediator_get_capacity(self):
|
||||
"""Mediator converts client stats to capacity result."""
|
||||
expected_capacity = constants.EXPECTED_SIZE_2
|
||||
|
Loading…
Reference in New Issue
Block a user