Merge "NetApp cDOT: Fix status updates for replicas"

This commit is contained in:
Jenkins
2016-03-18 14:11:13 +00:00
committed by Gerrit Code Review
6 changed files with 324 additions and 16 deletions

View File

@@ -774,3 +774,7 @@ class ShareReplicaNotFound(NotFound):
class TegileAPIException(ShareBackendException):
message = _("Unexpected response from Tegile IntelliFlash API: "
"%(response)s")
class StorageCommunicationException(ShareBackendException):
message = _("Could not communicate with storage array.")

View File

@@ -28,7 +28,6 @@ from six.moves import urllib
from manila import exception
from manila.i18n import _
LOG = log.getLogger(__name__)
EONTAPI_EINVAL = '22'
@@ -237,8 +236,10 @@ class NaServer(object):
response = self._opener.open(request)
except urllib.error.HTTPError as e:
raise NaApiError(e.code, e.msg)
except urllib.error.URLError as e:
raise exception.StorageCommunicationException(six.text_type(e))
except Exception as e:
raise NaApiError('Unexpected error', e)
raise NaApiError(message=e)
response_xml = response.read()
response_element = self._get_result(response_xml)
@@ -380,7 +381,7 @@ class NaElement(object):
if isinstance(na_element, NaElement):
self._element.append(na_element._element)
return
raise
raise ValueError(_("Can only add elements of type NaElement."))
def get_child_by_name(self, name):
"""Get the child element by the tag name."""

View File

@@ -1159,7 +1159,8 @@ class NetAppCmodeFileStorageLibrary(object):
vserver_client = data_motion.get_client_for_backend(
dest_backend, vserver_name=vserver)
share_name = self._get_backend_share_name(replica['id'])
self._deallocate_container(share_name, vserver_client)
if self._share_exists(share_name, vserver_client):
self._deallocate_container(share_name, vserver_client)
def update_replica_state(self, context, replica_list, replica,
access_rules, share_server=None):
@@ -1168,6 +1169,13 @@ class NetAppCmodeFileStorageLibrary(object):
share_name = self._get_backend_share_name(replica['id'])
vserver, vserver_client = self._get_vserver(share_server=share_server)
if not vserver_client.volume_exists(share_name):
msg = _("Volume %(share_name)s does not exist on vserver "
"%(vserver)s.")
msg_args = {'share_name': share_name, 'vserver': vserver}
raise exception.ShareResourceNotFound(msg % msg_args)
dm_session = data_motion.DataMotionSession()
try:
snapmirrors = dm_session.get_snapmirrors(active_replica, replica)
@@ -1242,10 +1250,21 @@ class NetAppCmodeFileStorageLibrary(object):
new_replica_list = []
# Setup the new active replica
new_active_replica = (
self._convert_destination_replica_to_independent(
context, dm_session, orig_active_replica, replica,
access_rules, share_server=share_server))
try:
new_active_replica = (
self._convert_destination_replica_to_independent(
context, dm_session, orig_active_replica, replica,
access_rules, share_server=share_server))
except exception.StorageCommunicationException:
LOG.exception(_LE("Could not communicate with the backend "
"for replica %s during promotion."),
replica['id'])
new_active_replica = copy.deepcopy(replica)
new_active_replica['replica_state'] = (
constants.STATUS_ERROR)
new_active_replica['status'] = constants.STATUS_ERROR
return [new_active_replica]
new_replica_list.append(new_active_replica)
# Change the source replica for all destinations to the new
@@ -1283,7 +1302,7 @@ class NetAppCmodeFileStorageLibrary(object):
# 1. Start an update to try to get a last minute transfer before we
# quiesce and break
dm_session.update_snapmirror(orig_active_replica, replica)
except netapp_api.NaApiError:
except exception.StorageCommunicationException:
# Ignore any errors since the current source replica may be
# unreachable
pass
@@ -1324,12 +1343,21 @@ class NetAppCmodeFileStorageLibrary(object):
orig_source_replica,
new_source_replica,
replica_list)
except Exception:
except exception.StorageCommunicationException:
replica['status'] = constants.STATUS_ERROR
replica['replica_state'] = constants.STATUS_ERROR
replica['export_locations'] = []
msg = _LE("Failed to change replica (%s) to a SnapMirror "
"destination."), replica['id']
LOG.exception(msg)
"destination. Replica backend is unreachable.")
LOG.exception(msg, replica['id'])
return replica
except netapp_api.NaApiError:
replica['replica_state'] = constants.STATUS_ERROR
replica['export_locations'] = []
msg = _LE("Failed to change replica (%s) to a SnapMirror "
"destination.")
LOG.exception(msg, replica['id'])
return replica
replica['replica_state'] = constants.REPLICA_STATE_OUT_OF_SYNC

View File

@@ -13,6 +13,10 @@
# under the License.
from lxml import etree
import mock
from six.moves import urllib
from manila.share.drivers.netapp.dataontap.client import api
CONNECTION_INFO = {
@@ -1879,3 +1883,75 @@ SNAPMIRROR_INITIALIZE_RESULT = etree.XML("""
<result-status>succeeded</result-status>
</results>
""")
FAKE_VOL_XML = """<volume-info xmlns='http://www.netapp.com/filer/admin'>
<name>open123</name>
<state>online</state>
<size-total>0</size-total>
<size-used>0</size-used>
<size-available>0</size-available>
<is-inconsistent>false</is-inconsistent>
<is-invalid>false</is-invalid>
</volume-info>"""
FAKE_XML1 = """<options>\
<test1>abc</test1>\
<test2>abc</test2>\
</options>"""
FAKE_XML2 = """<root><options>somecontent</options></root>"""
FAKE_NA_ELEMENT = api.NaElement(etree.XML(FAKE_VOL_XML))
FAKE_INVOKE_DATA = 'somecontent'
FAKE_XML_STR = 'abc'
FAKE_API_NAME = 'volume-get-iter'
FAKE_API_NAME_ELEMENT = api.NaElement(FAKE_API_NAME)
FAKE_NA_SERVER_STR = '127.0.0.1'
FAKE_NA_SERVER = api.NaServer(FAKE_NA_SERVER_STR)
FAKE_NA_SERVER_API_1_5 = api.NaServer(FAKE_NA_SERVER_STR)
FAKE_NA_SERVER_API_1_5.set_vfiler('filer')
FAKE_NA_SERVER_API_1_5.set_api_version(1, 5)
FAKE_NA_SERVER_API_1_14 = api.NaServer(FAKE_NA_SERVER_STR)
FAKE_NA_SERVER_API_1_14.set_vserver('server')
FAKE_NA_SERVER_API_1_14.set_api_version(1, 14)
FAKE_NA_SERVER_API_1_20 = api.NaServer(FAKE_NA_SERVER_STR)
FAKE_NA_SERVER_API_1_20.set_vfiler('filer')
FAKE_NA_SERVER_API_1_20.set_vserver('server')
FAKE_NA_SERVER_API_1_20.set_api_version(1, 20)
FAKE_QUERY = {'volume-attributes': None}
FAKE_DES_ATTR = {'volume-attributes': ['volume-id-attributes',
'volume-space-attributes',
'volume-state-attributes',
'volume-qos-attributes']}
FAKE_CALL_ARGS_LIST = [mock.call(80), mock.call(8088), mock.call(443),
mock.call(8488)]
FAKE_RESULT_API_ERR_REASON = api.NaElement('result')
FAKE_RESULT_API_ERR_REASON.add_attr('errno', '000')
FAKE_RESULT_API_ERR_REASON.add_attr('reason', 'fake_reason')
FAKE_RESULT_API_ERRNO_INVALID = api.NaElement('result')
FAKE_RESULT_API_ERRNO_INVALID.add_attr('errno', '000')
FAKE_RESULT_API_ERRNO_VALID = api.NaElement('result')
FAKE_RESULT_API_ERRNO_VALID.add_attr('errno', '14956')
FAKE_RESULT_SUCCESS = api.NaElement('result')
FAKE_RESULT_SUCCESS.add_attr('status', 'passed')
FAKE_HTTP_OPENER = urllib.request.build_opener()

View File

@@ -18,9 +18,14 @@
"""
Tests for NetApp API layer
"""
import ddt
import mock
from six.moves import urllib
from manila import exception
from manila.share.drivers.netapp.dataontap.client import api
from manila import test
from manila.tests.share.drivers.netapp.dataontap.client import fakes as fake
class NetAppApiElementTransTests(test.TestCase):
@@ -154,3 +159,82 @@ class NetAppApiElementTransTests(test.TestCase):
api.NaElement('root').__setitem__,
None,
'value')
@ddt.ddt
class NetAppApiServerTests(test.TestCase):
"""Test case for NetApp API server methods"""
def setUp(self):
self.root = api.NaServer('127.0.0.1')
super(NetAppApiServerTests, self).setUp()
@ddt.data(None, fake.FAKE_XML_STR)
def test_invoke_elem_value_error(self, na_element):
"""Tests whether invalid NaElement parameter causes error"""
self.assertRaises(ValueError, self.root.invoke_elem, na_element)
def test_invoke_elem_http_error(self):
"""Tests handling of HTTPError"""
na_element = fake.FAKE_NA_ELEMENT
self.mock_object(self.root, '_create_request', mock.Mock(
return_value=('abc', fake.FAKE_NA_ELEMENT)))
self.mock_object(api, 'LOG')
self.root._opener = fake.FAKE_HTTP_OPENER
self.mock_object(self.root, '_build_opener')
self.mock_object(self.root._opener, 'open', mock.Mock(
side_effect=urllib.error.HTTPError(url='', hdrs='',
fp=None, code='401',
msg='httperror')))
self.assertRaises(api.NaApiError, self.root.invoke_elem,
na_element)
def test_invoke_elem_urlerror(self):
"""Tests handling of URLError"""
na_element = fake.FAKE_NA_ELEMENT
self.mock_object(self.root, '_create_request', mock.Mock(
return_value=('abc', fake.FAKE_NA_ELEMENT)))
self.mock_object(api, 'LOG')
self.root._opener = fake.FAKE_HTTP_OPENER
self.mock_object(self.root, '_build_opener')
self.mock_object(self.root._opener, 'open', mock.Mock(
side_effect=urllib.error.URLError(reason='urlerror')))
self.assertRaises(exception.StorageCommunicationException,
self.root.invoke_elem,
na_element)
def test_invoke_elem_unknown_exception(self):
"""Tests handling of Unknown Exception"""
na_element = fake.FAKE_NA_ELEMENT
self.mock_object(self.root, '_create_request', mock.Mock(
return_value=('abc', fake.FAKE_NA_ELEMENT)))
self.mock_object(api, 'LOG')
self.root._opener = fake.FAKE_HTTP_OPENER
self.mock_object(self.root, '_build_opener')
self.mock_object(self.root._opener, 'open', mock.Mock(
side_effect=Exception))
exception = self.assertRaises(api.NaApiError, self.root.invoke_elem,
na_element)
self.assertEqual('unknown', exception.code)
def test_invoke_elem_valid(self):
"""Tests the method invoke_elem with valid parameters"""
na_element = fake.FAKE_NA_ELEMENT
self.root._trace = True
self.mock_object(self.root, '_create_request', mock.Mock(
return_value=('abc', fake.FAKE_NA_ELEMENT)))
self.mock_object(api, 'LOG')
self.root._opener = fake.FAKE_HTTP_OPENER
self.mock_object(self.root, '_build_opener')
self.mock_object(self.root, '_get_result', mock.Mock(
return_value=fake.FAKE_NA_ELEMENT))
opener_mock = self.mock_object(
self.root._opener, 'open', mock.Mock())
opener_mock.read.side_effect = ['resp1', 'resp2']
self.root.invoke_elem(na_element)
self.assertEqual(2, api.LOG.debug.call_count)

View File

@@ -2103,6 +2103,9 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.mock_object(self.library,
'_deallocate_container',
mock.Mock())
self.mock_object(self.library,
'_share_exists',
mock.Mock(return_value=False))
mock_dm_session = mock.Mock()
self.mock_object(data_motion, "DataMotionSession",
mock.Mock(return_value=mock_dm_session))
@@ -2126,6 +2129,9 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.mock_object(self.library,
'_deallocate_container',
mock.Mock())
self.mock_object(self.library,
'_share_exists',
mock.Mock(return_value=False))
mock_dm_session = mock.Mock()
self.mock_object(data_motion, "DataMotionSession",
mock.Mock(return_value=mock_dm_session))
@@ -2144,8 +2150,40 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
data_motion.get_client_for_backend.assert_called_once_with(
fake.BACKEND_NAME, vserver_name=fake.VSERVER1)
def test_delete_replica_share_absent_on_backend(self):
self.mock_object(self.library,
'_deallocate_container',
mock.Mock())
self.mock_object(self.library,
'_share_exists',
mock.Mock(return_value=False))
mock_dm_session = mock.Mock()
self.mock_object(data_motion,
"DataMotionSession",
mock.Mock(return_value=mock_dm_session))
self.mock_object(data_motion, 'get_client_for_backend')
self.mock_object(mock_dm_session,
'get_vserver_from_share',
mock.Mock(return_value=fake.VSERVER1))
result = self.library.delete_replica(None,
[fake.SHARE],
fake.SHARE,
share_server=None)
self.assertEqual(None, result)
self.assertFalse(self.library._deallocate_container.called)
mock_dm_session.delete_snapmirror.assert_called_with(fake.SHARE,
fake.SHARE)
self.assertEqual(2, mock_dm_session.delete_snapmirror.call_count)
data_motion.get_client_for_backend.assert_called_with(
fake.BACKEND_NAME, vserver_name=mock.ANY)
self.assertEqual(1, data_motion.get_client_for_backend.call_count)
def test_update_replica_state_no_snapmirror_share_creating(self):
vserver_client = mock.Mock()
self.mock_object(vserver_client, 'volume_exists',
mock.Mock(return_value=True))
self.mock_object(self.library,
'_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
@@ -2163,6 +2201,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
def test_update_replica_state_no_snapmirror_create_failed(self):
vserver_client = mock.Mock()
self.mock_object(vserver_client, 'volume_exists',
mock.Mock(return_value=True))
self.mock_object(self.library,
'_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
@@ -2183,6 +2223,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
@ddt.data(constants.STATUS_ERROR, constants.STATUS_AVAILABLE)
def test_update_replica_state_no_snapmirror(self, status):
vserver_client = mock.Mock()
self.mock_object(vserver_client, 'volume_exists',
mock.Mock(return_value=True))
self.mock_object(self.library,
'_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
@@ -2207,6 +2249,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
'last-transfer-end-timestamp': '%s' % float(time.time() - 10000)
}
vserver_client = mock.Mock()
self.mock_object(vserver_client, 'volume_exists',
mock.Mock(return_value=True))
self.mock_object(self.library,
'_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
@@ -2233,6 +2277,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
'last-transfer-end-timestamp': '%s' % float(time.time() - 10000)
}
vserver_client = mock.Mock()
self.mock_object(vserver_client, 'volume_exists',
mock.Mock(return_value=True))
self.mock_object(self.library,
'_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
@@ -2248,6 +2294,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
def test_update_replica_state_fail_to_get_snapmirrors(self):
vserver_client = mock.Mock()
self.mock_object(vserver_client, 'volume_exists',
mock.Mock(return_value=True))
self.mock_object(self.library,
'_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
@@ -2270,6 +2318,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
'last-transfer-end-timestamp': '%s' % float(time.time() - 10000)
}
vserver_client = mock.Mock()
self.mock_object(vserver_client, 'volume_exists',
mock.Mock(return_value=True))
self.mock_object(self.library,
'_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
@@ -2295,6 +2345,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
timeutils.utcnow_ts() - 10000)
}
vserver_client = mock.Mock()
self.mock_object(vserver_client, 'volume_exists',
mock.Mock(return_value=True))
self.mock_object(self.library,
'_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
@@ -2315,6 +2367,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
'last-transfer-end-timestamp': '%s' % float(time.time())
}
vserver_client = mock.Mock()
self.mock_object(vserver_client, 'volume_exists',
mock.Mock(return_value=True))
self.mock_object(self.library,
'_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
@@ -2328,6 +2382,20 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.assertEqual(constants.REPLICA_STATE_IN_SYNC, result)
def test_update_replica_state_backend_volume_absent(self):
vserver_client = mock.Mock()
self.mock_object(vserver_client, 'volume_exists',
mock.Mock(return_value=False))
self.mock_object(self.library,
'_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
vserver_client)))
self.assertRaises(exception.ShareResourceNotFound,
self.library.update_replica_state,
None, [fake.SHARE], fake.SHARE, None,
share_server=None)
def test_promote_replica(self):
self.mock_object(self.library,
'_get_vserver',
@@ -2362,6 +2430,31 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.assertEqual(constants.STATUS_ACTIVE,
actual_replica_2['access_rules_status'])
def test_promote_replica_destination_unreachable(self):
self.mock_object(self.library,
'_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
mock.Mock())))
self.mock_object(self.library,
'_get_helper',
mock.Mock(return_value=mock.Mock()))
self.mock_object(self.library, '_create_export',
mock.Mock(return_value='fake_export_location'))
self.mock_object(
self.library, '_convert_destination_replica_to_independent',
mock.Mock(side_effect=exception.StorageCommunicationException))
replicas = self.library.promote_replica(
None, [self.fake_replica, self.fake_replica_2],
self.fake_replica_2, [], share_server=None)
self.assertEqual(1, len(replicas))
actual_replica = replicas[0]
self.assertEqual(constants.STATUS_ERROR,
actual_replica['replica_state'])
self.assertEqual(constants.STATUS_ERROR,
actual_replica['status'])
def test_promote_replica_more_than_two_replicas(self):
fake_replica_3 = copy.deepcopy(self.fake_replica_2)
fake_replica_3['id'] = fake.SHARE_ID3
@@ -2466,8 +2559,9 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
mock.Mock(return_value=mock.Mock()))
self.mock_object(self.library, '_create_export',
mock.Mock(return_value='fake_export_location'))
self.mock_object(self.mock_dm_session, 'update_snapmirror',
mock.Mock(side_effect=netapp_api.NaApiError(code=0)))
self.mock_object(
self.mock_dm_session, 'update_snapmirror',
mock.Mock(side_effect=exception.StorageCommunicationException))
replica = self.library._convert_destination_replica_to_independent(
None, self.mock_dm_session, self.fake_replica,
@@ -2614,8 +2708,29 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.assertEqual(constants.REPLICA_STATE_OUT_OF_SYNC,
replica['replica_state'])
def test_safe_change_replica_source_error(self):
self.mock_dm_session.change_snapmirror_source.side_effect = Exception
def test_safe_change_replica_source_destination_unreachable(self):
self.mock_dm_session.change_snapmirror_source.side_effect = (
exception.StorageCommunicationException
)
fake_replica_3 = copy.deepcopy(self.fake_replica_2)
fake_replica_3['id'] = fake.SHARE_ID3
fake_replica_3['replica_state'] = constants.REPLICA_STATE_OUT_OF_SYNC
replica = self.library._safe_change_replica_source(
self.mock_dm_session, self.fake_replica, self.fake_replica_2,
fake_replica_3, [self.fake_replica, self.fake_replica_2,
fake_replica_3]
)
self.assertEqual([], replica['export_locations'])
self.assertEqual(constants.STATUS_ERROR,
replica['replica_state'])
self.assertEqual(constants.STATUS_ERROR,
replica['status'])
def test_safe_change_replica_source_error(self):
self.mock_dm_session.change_snapmirror_source.side_effect = (
netapp_api.NaApiError(code=0)
)
fake_replica_3 = copy.deepcopy(self.fake_replica_2)
fake_replica_3['id'] = fake.SHARE_ID3