from cinder import exception
from cinder import test
-from cinder.tests.unit.volume.drivers.netapp.dataontap.client import (
- fake_api as netapp_api)
from cinder.tests.unit.volume.drivers.netapp.dataontap import fakes
from cinder.volume import configuration as conf
from cinder.volume.drivers.netapp import common
lambda a, b, c, synchronous: None)
self.mock_object(utils, 'OpenStackInfo')
- # Inject fake netapp_lib module classes.
- netapp_api.mock_netapp_lib([common, client_cmode, client_base])
- self.mock_object(common.na_utils, 'check_netapp_lib')
-
configuration = self._set_config(create_configuration())
driver = common.NetAppDriver(configuration=configuration)
self.stubs.Set(http_client, 'HTTPConnection',
def setUp(self):
super(NetAppDriverNegativeTestCase, self).setUp()
- # Inject fake netapp_lib module classes.
- netapp_api.mock_netapp_lib([common])
- self.mock_object(common.na_utils, 'check_netapp_lib')
-
def test_incorrect_family(self):
self.mock_object(utils, 'OpenStackInfo')
configuration = create_configuration()
def _custom_setup(self):
self.mock_object(utils, 'OpenStackInfo')
- # Inject fake netapp_lib module classes.
- netapp_api.mock_netapp_lib([common, client_base, client_7mode])
- self.mock_object(common.na_utils, 'check_netapp_lib')
-
configuration = self._set_config(create_configuration())
driver = common.NetAppDriver(configuration=configuration)
self.stubs.Set(http_client, 'HTTPConnection',
def _custom_setup(self):
self.mock_object(utils, 'OpenStackInfo')
- # Inject fake netapp_lib module classes.
- netapp_api.mock_netapp_lib([common, client_base, client_7mode])
- self.mock_object(common.na_utils, 'check_netapp_lib')
-
configuration = self._set_config(create_configuration())
driver = common.NetAppDriver(configuration=configuration)
self.stubs.Set(http_client, 'HTTPConnection',
def _custom_setup(self):
self.mock_object(na_utils, 'OpenStackInfo')
- # Inject fake netapp_lib module classes.
- fakes.mock_netapp_lib([client])
- self.mock_object(common.na_utils, 'check_netapp_lib')
-
configuration = self._set_config(create_configuration())
self.driver = common.NetAppDriver(configuration=configuration)
self.library = self.driver.library
from cinder import exception
from cinder.image import image_utils
from cinder import test
-from cinder.tests.unit.volume.drivers.netapp.dataontap.client import (
- fake_api as netapp_api)
from cinder import utils as cinder_utils
from cinder.volume import configuration as conf
from cinder.volume.drivers.netapp import common
as netapp_nfs_7mode)
from cinder.volume.drivers.netapp.dataontap import (nfs_cmode
as netapp_nfs_cmode)
+from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
from cinder.volume.drivers.netapp.dataontap.client import client_7mode
from cinder.volume.drivers.netapp.dataontap.client import client_base
from cinder.volume.drivers.netapp.dataontap.client import client_cmode
kwargs['netapp_mode'] = 'proxy'
kwargs['configuration'] = create_configuration()
- # Inject fake netapp_lib module classes.
- netapp_api.mock_netapp_lib([client_cmode, client_base])
- self.mock_object(common.na_utils, 'check_netapp_lib')
-
self.mock_object(nfs_base, 'LOG')
self._driver = netapp_nfs_cmode.NetAppCmodeNfsDriver(**kwargs)
self._driver.zapi_client = mock.Mock()
def _custom_setup(self):
self.mock_object(utils, 'OpenStackInfo')
- # Inject fake netapp_lib module classes.
- netapp_api.mock_netapp_lib([client_cmode, client_base])
- self.mock_object(common.na_utils, 'check_netapp_lib')
-
self.mock_object(common.na_utils, 'LOG')
self.mock_object(nfs_base, 'LOG')
self._driver = netapp_nfs_7mode.NetApp7modeNfsDriver(
from cinder import exception
from cinder import test
-from cinder.tests.unit.volume.drivers.netapp.dataontap.client import (
- fake_api as netapp_api)
+from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
from cinder.volume.drivers.netapp.dataontap import ssc_cmode
def setUp(self):
super(SscUtilsTestCase, self).setUp()
- netapp_api.mock_netapp_lib([ssc_cmode])
self.stubs.Set(http_client, 'HTTPConnection',
FakeDirectCmodeHTTPConnection)
+++ /dev/null
-# Copyright (c) 2015 Clinton Knight. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import sys
-
-from lxml import etree
-import mock
-import six
-
-from cinder import exception
-
-
-EONTAPI_EINVAL = '22'
-EAPIERROR = '13001'
-EAPINOTFOUND = '13005'
-ESNAPSHOTNOTALLOWED = '13023'
-EVOLUMEOFFLINE = '13042'
-EINTERNALERROR = '13114'
-EDUPLICATEENTRY = '13130'
-EVOLNOTCLONE = '13170'
-EVOL_NOT_MOUNTED = '14716'
-ESIS_CLONE_NOT_LICENSED = '14956'
-EOBJECTNOTFOUND = '15661'
-E_VIFMGR_PORT_ALREADY_ASSIGNED_TO_BROADCAST_DOMAIN = '18605'
-
-
-def mock_netapp_lib(modules):
- """Inject fake netapp_lib module classes."""
-
- netapp_lib = mock.Mock()
- netapp_lib.api.zapi.zapi.NaElement = NaElement
- netapp_lib.api.zapi.zapi.NaApiError = NaApiError
- netapp_lib.api.zapi.zapi.NaServer = mock.Mock()
- netapp_lib.api.zapi.errors = sys.modules[__name__]
- for module in modules:
- setattr(module, 'netapp_api', netapp_lib.api.zapi.zapi)
- setattr(module, 'netapp_error', netapp_lib.api.zapi.errors)
-
-
-class NaApiError(exception.CinderException):
- """Fake NetApi API invocation error."""
-
- def __init__(self, code=None, message=None):
- if not code:
- code = 'unknown'
- if not message:
- message = 'unknown'
- self.code = code
- self.message = message
- super(NaApiError, self).__init__(message=message)
-
-
-class NaServer(object):
- """Fake XML wrapper class for NetApp Server"""
- def __init__(self, host):
- self._host = host
-
-
-class NaElement(object):
- """Fake XML wrapper class for NetApp API."""
-
- def __init__(self, name):
- """Name of the element or etree.Element."""
- if isinstance(name, etree._Element):
- self._element = name
- else:
- self._element = etree.Element(name)
-
- def get_name(self):
- """Returns the tag name of the element."""
- return self._element.tag
-
- def set_content(self, text):
- """Set the text string for the element."""
- self._element.text = text
-
- def get_content(self):
- """Get the text for the element."""
- return self._element.text
-
- def add_attr(self, name, value):
- """Add the attribute to the element."""
- self._element.set(name, value)
-
- def add_attrs(self, **attrs):
- """Add multiple attributes to the element."""
- for attr in attrs.keys():
- self._element.set(attr, attrs.get(attr))
-
- def add_child_elem(self, na_element):
- """Add the child element to the element."""
- if isinstance(na_element, NaElement):
- self._element.append(na_element._element)
- return
- raise
-
- def get_child_by_name(self, name):
- """Get the child element by the tag name."""
- for child in self._element.iterchildren():
- if child.tag == name or etree.QName(child.tag).localname == name:
- return NaElement(child)
- return None
-
- def get_child_content(self, name):
- """Get the content of the child."""
- for child in self._element.iterchildren():
- if child.tag == name or etree.QName(child.tag).localname == name:
- return child.text
- return None
-
- def get_children(self):
- """Get the children for the element."""
- return [NaElement(el) for el in self._element.iterchildren()]
-
- def has_attr(self, name):
- """Checks whether element has attribute."""
- attributes = self._element.attrib or {}
- return name in attributes.keys()
-
- def get_attr(self, name):
- """Get the attribute with the given name."""
- attributes = self._element.attrib or {}
- return attributes.get(name)
-
- def get_attr_names(self):
- """Returns the list of attribute names."""
- attributes = self._element.attrib or {}
- return attributes.keys()
-
- def add_new_child(self, name, content, convert=False):
- """Add child with tag name and context.
-
- Convert replaces entity refs to chars.
- """
- child = NaElement(name)
- if convert:
- content = NaElement._convert_entity_refs(content)
- child.set_content(content)
- self.add_child_elem(child)
-
- @staticmethod
- def _convert_entity_refs(text):
- """Converts entity refs to chars to handle etree auto conversions."""
- text = text.replace("<", "<")
- text = text.replace(">", ">")
- return text
-
- @staticmethod
- def create_node_with_children(node, **children):
- """Creates and returns named node with children."""
- parent = NaElement(node)
- for child in children.keys():
- parent.add_new_child(child, children.get(child, None))
- return parent
-
- def add_node_with_children(self, node, **children):
- """Creates named node with children."""
- parent = NaElement.create_node_with_children(node, **children)
- self.add_child_elem(parent)
-
- def to_string(self, pretty=False, method='xml', encoding='UTF-8'):
- """Prints the element to string."""
- return etree.tostring(self._element, method=method, encoding=encoding,
- pretty_print=pretty)
-
- def __getitem__(self, key):
- """Dict getter method for NaElement.
-
- Returns NaElement list if present,
- text value in case no NaElement node
- children or attribute value if present.
- """
-
- child = self.get_child_by_name(key)
- if child:
- if child.get_children():
- return child
- else:
- return child.get_content()
- elif self.has_attr(key):
- return self.get_attr(key)
- raise KeyError('No element by given name %s.' % key)
-
- def __setitem__(self, key, value):
- """Dict setter method for NaElement.
-
- Accepts dict, list, tuple, str, int, float and long as valid value.
- """
- if key:
- if value:
- if isinstance(value, NaElement):
- child = NaElement(key)
- child.add_child_elem(value)
- self.add_child_elem(child)
- elif isinstance(value, (str, int, float, long)):
- self.add_new_child(key, six.text_type(value))
- elif isinstance(value, (list, tuple, dict)):
- child = NaElement(key)
- child.translate_struct(value)
- self.add_child_elem(child)
- else:
- raise TypeError('Not a valid value for NaElement.')
- else:
- self.add_child_elem(NaElement(key))
- else:
- raise KeyError('NaElement name cannot be null.')
-
- def translate_struct(self, data_struct):
- """Convert list, tuple, dict to NaElement and appends."""
-
- if isinstance(data_struct, (list, tuple)):
- for el in data_struct:
- if isinstance(el, (list, tuple, dict)):
- self.translate_struct(el)
- else:
- self.add_child_elem(NaElement(el))
- elif isinstance(data_struct, dict):
- for k in data_struct.keys():
- child = NaElement(k)
- if isinstance(data_struct[k], (dict, list, tuple)):
- child.translate_struct(data_struct[k])
- else:
- if data_struct[k]:
- child.set_content(six.text_type(data_struct[k]))
- self.add_child_elem(child)
- else:
- raise ValueError('Type cannot be converted into NaElement.')
from lxml import etree
+import mock
+from six.moves import urllib
+import cinder.volume.drivers.netapp.dataontap.client.api as netapp_api
+
+
+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 = netapp_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 = netapp_api.NaElement(FAKE_API_NAME)
+
+FAKE_NA_SERVER_STR = '127.0.0.1'
+
+FAKE_NA_SERVER = netapp_api.NaServer(FAKE_NA_SERVER_STR)
+
+FAKE_NA_SERVER_API_1_5 = netapp_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 = netapp_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 = netapp_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 = netapp_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 = netapp_api.NaElement('result')
+FAKE_RESULT_API_ERRNO_INVALID.add_attr('errno', '000')
+
+FAKE_RESULT_API_ERRNO_VALID = netapp_api.NaElement('result')
+FAKE_RESULT_API_ERRNO_VALID.add_attr('errno', '14956')
+
+FAKE_RESULT_SUCCESS = netapp_api.NaElement('result')
+FAKE_RESULT_SUCCESS.add_attr('status', 'passed')
+
+FAKE_HTTP_OPENER = urllib.request.build_opener()
GET_OPERATIONAL_NETWORK_INTERFACE_ADDRESSES_RESPONSE = etree.XML("""
<results status="passed">
--- /dev/null
+# Copyright (c) 2014 Ben Swartzlander. All rights reserved.
+# Copyright (c) 2014 Navneet Singh. All rights reserved.
+# Copyright (c) 2014 Clinton Knight. All rights reserved.
+# Copyright (c) 2014 Alex Meade. All rights reserved.
+# Copyright (c) 2014 Bob Callaway. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+"""
+Tests for NetApp API layer
+"""
+import ddt
+from lxml import etree
+import mock
+import six
+from six.moves import urllib
+
+from cinder import exception
+from cinder.i18n import _
+from cinder import test
+from cinder.tests.unit.volume.drivers.netapp.dataontap.client import (
+ fakes as zapi_fakes)
+from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
+
+
+@ddt.ddt
+class NetAppApiServerTests(test.TestCase):
+ """Test case for NetApp API server methods"""
+ def setUp(self):
+ self.root = netapp_api.NaServer('127.0.0.1')
+ super(NetAppApiServerTests, self).setUp()
+
+ @ddt.data(None, 'ftp')
+ def test_set_transport_type_value_error(self, transport_type):
+ """Tests setting an invalid transport type"""
+ self.assertRaises(ValueError, self.root.set_transport_type,
+ transport_type)
+
+ @ddt.data({'params': {'transport_type': 'http',
+ 'server_type_filer': 'filer'}},
+ {'params': {'transport_type': 'http',
+ 'server_type_filer': 'xyz'}},
+ {'params': {'transport_type': 'https',
+ 'server_type_filer': 'filer'}},
+ {'params': {'transport_type': 'https',
+ 'server_type_filer': 'xyz'}})
+ @ddt.unpack
+ def test_set_transport_type_valid(self, params):
+ """Tests setting a valid transport type"""
+ self.root._server_type = params['server_type_filer']
+ mock_invoke = self.mock_object(self.root, 'set_port')
+
+ self.root.set_transport_type(params['transport_type'])
+
+ expected_call_args = zapi_fakes.FAKE_CALL_ARGS_LIST
+
+ self.assertTrue(mock_invoke.call_args in expected_call_args)
+
+ @ddt.data('stor', 'STORE', '')
+ def test_set_server_type_value_error(self, server_type):
+ """Tests Value Error on setting the wrong server type"""
+ self.assertRaises(ValueError, self.root.set_server_type, server_type)
+
+ @ddt.data('!&', '80na', '')
+ def test_set_port__value_error(self, port):
+ """Tests Value Error on trying to set port with a non-integer"""
+ self.assertRaises(ValueError, self.root.set_port, port)
+
+ @ddt.data('!&', '80na', '')
+ def test_set_timeout_value_error(self, timeout):
+ """Tests Value Error on trying to set port with a non-integer"""
+ self.assertRaises(ValueError, self.root.set_timeout, timeout)
+
+ @ddt.data({'params': {'major': 1, 'minor': '20a'}},
+ {'params': {'major': '20a', 'minor': 1}},
+ {'params': {'major': '!*', 'minor': '20a'}})
+ @ddt.unpack
+ def test_set_api_version_value_error(self, params):
+ """Tests Value Error on setting non-integer version"""
+ self.assertRaises(ValueError, self.root.set_api_version, **params)
+
+ def test_set_api_version_valid(self):
+ """Tests Value Error on setting non-integer version"""
+ args = {'major': '20', 'minor': 1}
+
+ expected_call_args_list = [mock.call('20'), mock.call(1)]
+
+ mock_invoke = self.mock_object(six, 'text_type',
+ mock.Mock(return_value='str'))
+ self.root.set_api_version(**args)
+
+ self.assertEqual(expected_call_args_list, mock_invoke.call_args_list)
+
+ @ddt.data({'params': {'result': zapi_fakes.FAKE_RESULT_API_ERR_REASON}},
+ {'params': {'result': zapi_fakes.FAKE_RESULT_API_ERRNO_INVALID}},
+ {'params': {'result': zapi_fakes.FAKE_RESULT_API_ERRNO_VALID}})
+ @ddt.unpack
+ def test_invoke_successfully_naapi_error(self, params):
+ """Tests invoke successfully raising NaApiError"""
+ self.mock_object(self.root, 'invoke_elem',
+ mock.Mock(return_value=params['result']))
+
+ self.assertRaises(netapp_api.NaApiError,
+ self.root.invoke_successfully,
+ zapi_fakes.FAKE_NA_ELEMENT)
+
+ def test_invoke_successfully_no_error(self):
+ """Tests invoke successfully with no errors"""
+ self.mock_object(self.root, 'invoke_elem', mock.Mock(
+ return_value=zapi_fakes.FAKE_RESULT_SUCCESS))
+
+ self.assertEqual(zapi_fakes.FAKE_RESULT_SUCCESS.to_string(),
+ self.root.invoke_successfully(
+ zapi_fakes.FAKE_NA_ELEMENT).to_string())
+
+ def test__create_request(self):
+ """Tests method _create_request"""
+ self.root._ns = zapi_fakes.FAKE_XML_STR
+ self.root._api_version = '1.20'
+ self.mock_object(self.root, '_enable_tunnel_request')
+ self.mock_object(netapp_api.NaElement, 'add_child_elem')
+ self.mock_object(netapp_api.NaElement, 'to_string',
+ mock.Mock(return_value=zapi_fakes.FAKE_XML_STR))
+ mock_invoke = self.mock_object(urllib.request, 'Request')
+
+ self.root._create_request(zapi_fakes.FAKE_NA_ELEMENT, True)
+
+ self.assertTrue(mock_invoke.called)
+
+ @ddt.data({'params': {'server': zapi_fakes.FAKE_NA_SERVER_API_1_5}},
+ {'params': {'server': zapi_fakes.FAKE_NA_SERVER_API_1_14}})
+ @ddt.unpack
+ def test__enable_tunnel_request__value_error(self, params):
+ """Tests value errors with creating tunnel request"""
+
+ self.assertRaises(ValueError, params['server']._enable_tunnel_request,
+ 'test')
+
+ def test__enable_tunnel_request_valid(self):
+ """Tests creating tunnel request with correct values"""
+ netapp_elem = zapi_fakes.FAKE_NA_ELEMENT
+ server = zapi_fakes.FAKE_NA_SERVER_API_1_20
+ mock_invoke = self.mock_object(netapp_elem, 'add_attr')
+ expected_call_args = [mock.call('vfiler', 'filer'),
+ mock.call('vfiler', 'server')]
+
+ server._enable_tunnel_request(netapp_elem)
+
+ self.assertEqual(expected_call_args, mock_invoke.call_args_list)
+
+ def test__parse_response__naapi_error(self):
+ """Tests NaApiError on no response"""
+ self.assertRaises(netapp_api.NaApiError,
+ self.root._parse_response, None)
+
+ def test__parse_response_no_error(self):
+ """Tests parse function with appropriate response"""
+ mock_invoke = self.mock_object(etree, 'XML', mock.Mock(
+ return_value='xml'))
+
+ self.root._parse_response(zapi_fakes.FAKE_XML_STR)
+
+ mock_invoke.assert_called_with(zapi_fakes.FAKE_XML_STR)
+
+ def test__build_opener_not_implemented_error(self):
+ """Tests whether certificate style authorization raises Exception"""
+ self.root._auth_style = 'not_basic_auth'
+
+ self.assertRaises(NotImplementedError, self.root._build_opener)
+
+ def test__build_opener_valid(self):
+ """Tests whether build opener works with valid parameters"""
+ self.root._auth_style = 'basic_auth'
+ mock_invoke = self.mock_object(urllib.request, 'build_opener')
+
+ self.root._build_opener()
+
+ self.assertTrue(mock_invoke.called)
+
+ @ddt.data(None, zapi_fakes.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 = zapi_fakes.FAKE_NA_ELEMENT
+ self.mock_object(self.root, '_create_request', mock.Mock(
+ return_value=('abc', zapi_fakes.FAKE_NA_ELEMENT)))
+ self.mock_object(netapp_api, 'LOG')
+ self.root._opener = zapi_fakes.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(netapp_api.NaApiError, self.root.invoke_elem,
+ na_element)
+
+ def test_invoke_elem_unknown_exception(self):
+ """Tests handling of Unknown Exception"""
+ na_element = zapi_fakes.FAKE_NA_ELEMENT
+ self.mock_object(self.root, '_create_request', mock.Mock(
+ return_value=('abc', zapi_fakes.FAKE_NA_ELEMENT)))
+ self.mock_object(netapp_api, 'LOG')
+ self.root._opener = zapi_fakes.FAKE_HTTP_OPENER
+ self.mock_object(self.root, '_build_opener')
+ self.mock_object(self.root._opener, 'open', mock.Mock(
+ side_effect=Exception))
+
+ self.assertRaises(netapp_api.NaApiError, self.root.invoke_elem,
+ na_element)
+
+ def test_invoke_elem_valid(self):
+ """Tests the method invoke_elem with valid parameters"""
+ na_element = zapi_fakes.FAKE_NA_ELEMENT
+ self.root._trace = True
+ self.mock_object(self.root, '_create_request', mock.Mock(
+ return_value=('abc', zapi_fakes.FAKE_NA_ELEMENT)))
+ self.mock_object(netapp_api, 'LOG')
+ self.root._opener = zapi_fakes.FAKE_HTTP_OPENER
+ self.mock_object(self.root, '_build_opener')
+ self.mock_object(self.root, '_get_result', mock.Mock(
+ return_value=zapi_fakes.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)
+
+
+class NetAppApiElementTransTests(test.TestCase):
+ """Test case for NetApp API element translations."""
+
+ def setUp(self):
+ super(NetAppApiElementTransTests, self).setUp()
+
+ def test_translate_struct_dict_unique_key(self):
+ """Tests if dict gets properly converted to NaElements."""
+ root = netapp_api.NaElement('root')
+ child = {'e1': 'v1', 'e2': 'v2', 'e3': 'v3'}
+ root.translate_struct(child)
+ self.assertEqual(3, len(root.get_children()))
+ self.assertEqual('v1', root.get_child_content('e1'))
+ self.assertEqual('v2', root.get_child_content('e2'))
+ self.assertEqual('v3', root.get_child_content('e3'))
+
+ def test_translate_struct_dict_nonunique_key(self):
+ """Tests if list/dict gets properly converted to NaElements."""
+ root = netapp_api.NaElement('root')
+ child = [{'e1': 'v1', 'e2': 'v2'}, {'e1': 'v3'}]
+ root.translate_struct(child)
+ self.assertEqual(3, len(root.get_children()))
+ children = root.get_children()
+ for c in children:
+ if c.get_name() == 'e1':
+ self.assertIn(c.get_content(), ['v1', 'v3'])
+ else:
+ self.assertEqual('v2', c.get_content())
+
+ def test_translate_struct_list(self):
+ """Tests if list gets properly converted to NaElements."""
+ root = netapp_api.NaElement('root')
+ child = ['e1', 'e2']
+ root.translate_struct(child)
+ self.assertEqual(2, len(root.get_children()))
+ self.assertIsNone(root.get_child_content('e1'))
+ self.assertIsNone(root.get_child_content('e2'))
+
+ def test_translate_struct_tuple(self):
+ """Tests if tuple gets properly converted to NaElements."""
+ root = netapp_api.NaElement('root')
+ child = ('e1', 'e2')
+ root.translate_struct(child)
+ self.assertEqual(2, len(root.get_children()))
+ self.assertIsNone(root.get_child_content('e1'))
+ self.assertIsNone(root.get_child_content('e2'))
+
+ def test_translate_invalid_struct(self):
+ """Tests if invalid data structure raises exception."""
+ root = netapp_api.NaElement('root')
+ child = 'random child element'
+ self.assertRaises(ValueError, root.translate_struct, child)
+
+ def test_setter_builtin_types(self):
+ """Tests str, int, float get converted to NaElement."""
+ root = netapp_api.NaElement('root')
+ root['e1'] = 'v1'
+ root['e2'] = 1
+ root['e3'] = 2.0
+ root['e4'] = 8l
+ self.assertEqual(4, len(root.get_children()))
+ self.assertEqual('v1', root.get_child_content('e1'))
+ self.assertEqual('1', root.get_child_content('e2'))
+ self.assertEqual('2.0', root.get_child_content('e3'))
+ self.assertEqual('8', root.get_child_content('e4'))
+
+ def test_setter_na_element(self):
+ """Tests na_element gets appended as child."""
+ root = netapp_api.NaElement('root')
+ root['e1'] = netapp_api.NaElement('nested')
+ self.assertEqual(1, len(root.get_children()))
+ e1 = root.get_child_by_name('e1')
+ self.assertIsInstance(e1, netapp_api.NaElement)
+ self.assertIsInstance(e1.get_child_by_name('nested'),
+ netapp_api.NaElement)
+
+ def test_setter_child_dict(self):
+ """Tests dict is appended as child to root."""
+ root = netapp_api.NaElement('root')
+ root['d'] = {'e1': 'v1', 'e2': 'v2'}
+ e1 = root.get_child_by_name('d')
+ self.assertIsInstance(e1, netapp_api.NaElement)
+ sub_ch = e1.get_children()
+ self.assertEqual(2, len(sub_ch))
+ for c in sub_ch:
+ self.assertIn(c.get_name(), ['e1', 'e2'])
+ if c.get_name() == 'e1':
+ self.assertEqual('v1', c.get_content())
+ else:
+ self.assertEqual('v2', c.get_content())
+
+ def test_setter_child_list_tuple(self):
+ """Tests list/tuple are appended as child to root."""
+ root = netapp_api.NaElement('root')
+ root['l'] = ['l1', 'l2']
+ root['t'] = ('t1', 't2')
+ l = root.get_child_by_name('l')
+ self.assertIsInstance(l, netapp_api.NaElement)
+ t = root.get_child_by_name('t')
+ self.assertIsInstance(t, netapp_api.NaElement)
+ for le in l.get_children():
+ self.assertIn(le.get_name(), ['l1', 'l2'])
+ for te in t.get_children():
+ self.assertIn(te.get_name(), ['t1', 't2'])
+
+ def test_setter_no_value(self):
+ """Tests key with None value."""
+ root = netapp_api.NaElement('root')
+ root['k'] = None
+ self.assertIsNone(root.get_child_content('k'))
+
+ def test_setter_invalid_value(self):
+ """Tests invalid value raises exception."""
+ root = netapp_api.NaElement('root')
+ try:
+ root['k'] = netapp_api.NaServer('localhost')
+ except Exception as e:
+ if not isinstance(e, TypeError):
+ self.fail(_('Error not a TypeError.'))
+
+ def test_setter_invalid_key(self):
+ """Tests invalid value raises exception."""
+ root = netapp_api.NaElement('root')
+ try:
+ root[None] = 'value'
+ except Exception as e:
+ if not isinstance(e, KeyError):
+ self.fail(_('Error not a KeyError.'))
+
+ def test_getter_key_error(self):
+ """Tests invalid key raises exception"""
+ root = netapp_api.NaElement('root')
+ self.mock_object(root, 'get_child_by_name',
+ mock.Mock(return_value=None))
+ self.mock_object(root, 'has_attr',
+ mock.Mock(return_value=None))
+
+ self.assertRaises(KeyError,
+ netapp_api.NaElement.__getitem__,
+ root, '123')
+
+ def test_getter_na_element_list(self):
+ """Tests returning NaElement list"""
+ root = netapp_api.NaElement('root')
+ root['key'] = ['val1', 'val2']
+
+ self.assertEqual(root.get_child_by_name('key').get_name(),
+ root.__getitem__('key').get_name())
+
+ def test_getter_child_text(self):
+ """Tests NaElement having no children"""
+ root = netapp_api.NaElement('root')
+ root.set_content('FAKE_CONTENT')
+ self.mock_object(root, 'get_child_by_name',
+ mock.Mock(return_value=root))
+
+ self.assertEqual('FAKE_CONTENT',
+ root.__getitem__('root'))
+
+ def test_getter_child_attr(self):
+ """Tests invalid key raises exception"""
+ root = netapp_api.NaElement('root')
+ root.add_attr('val', 'FAKE_VALUE')
+
+ self.assertEqual('FAKE_VALUE',
+ root.__getitem__('val'))
+
+ def test_add_node_with_children(self):
+ """Tests adding a child node with its own children"""
+ root = netapp_api.NaElement('root')
+ self.mock_object(netapp_api.NaElement,
+ 'create_node_with_children',
+ mock.Mock(return_value=zapi_fakes.FAKE_INVOKE_DATA))
+ mock_invoke = self.mock_object(root, 'add_child_elem')
+
+ root.add_node_with_children('options')
+
+ mock_invoke.assert_called_with(zapi_fakes.FAKE_INVOKE_DATA)
+
+ def test_create_node_with_children(self):
+ """Tests adding a child node with its own children"""
+ root = netapp_api.NaElement('root')
+ self.mock_object(root, 'add_new_child', mock.Mock(return_value='abc'))
+
+ self.assertEqual(zapi_fakes.FAKE_XML1, root.create_node_with_children(
+ 'options', test1=zapi_fakes.FAKE_XML_STR,
+ test2=zapi_fakes.FAKE_XML_STR).to_string())
+
+ def test_add_new_child(self):
+ """Tests adding a child node with its own children"""
+ root = netapp_api.NaElement('root')
+ self.mock_object(netapp_api.NaElement,
+ '_convert_entity_refs',
+ mock.Mock(return_value=zapi_fakes.FAKE_INVOKE_DATA))
+
+ root.add_new_child('options', zapi_fakes.FAKE_INVOKE_DATA)
+
+ self.assertEqual(zapi_fakes.FAKE_XML2, root.to_string())
+
+ def test_get_attr_names_empty_attr(self):
+ """Tests _elements.attrib being empty"""
+ root = netapp_api.NaElement('root')
+
+ self.assertEqual([], root.get_attr_names())
+
+ def test_get_attr_names(self):
+ """Tests _elements.attrib being non-empty"""
+ root = netapp_api.NaElement('root')
+ root.add_attr('attr1', 'a1')
+ root.add_attr('attr2', 'a2')
+
+ self.assertEqual(['attr1', 'attr2'], root.get_attr_names())
+
+
+@ddt.ddt
+class NetAppApiInvokeTests(test.TestCase):
+ """Test Cases for api request creation and invocation"""
+
+ def setUp(self):
+ super(NetAppApiInvokeTests, self).setUp()
+
+ @ddt.data(None, zapi_fakes.FAKE_XML_STR)
+ def test_invoke_api_invalid_input(self, na_server):
+ """Tests Zapi Invocation Type Error"""
+ na_server = None
+ api_name = zapi_fakes.FAKE_API_NAME
+ invoke_generator = netapp_api.invoke_api(na_server, api_name)
+
+ self.assertRaises(exception.InvalidInput, invoke_generator.next)
+
+ @ddt.data({'params': {'na_server': zapi_fakes.FAKE_NA_SERVER,
+ 'api_name': zapi_fakes.FAKE_API_NAME}},
+ {'params': {'na_server': zapi_fakes.FAKE_NA_SERVER,
+ 'api_name': zapi_fakes.FAKE_API_NAME,
+ 'api_family': 'cm',
+ 'query': zapi_fakes.FAKE_QUERY,
+ 'des_result': zapi_fakes.FAKE_DES_ATTR,
+ 'additional_elems': None,
+ 'is_iter': True}})
+ @ddt.unpack
+ def test_invoke_api_valid(self, params):
+ """Test invoke_api with valid naserver"""
+ self.mock_object(netapp_api, 'create_api_request', mock.Mock(
+ return_value='success'))
+ self.mock_object(netapp_api.NaServer, 'invoke_successfully',
+ mock.Mock(
+ return_value=netapp_api.NaElement('success')))
+
+ invoke_generator = netapp_api.invoke_api(**params)
+
+ self.assertEqual(netapp_api.NaElement('success').to_string(),
+ invoke_generator.next().to_string())
+
+ def test_create_api_request(self):
+ """"Tests creating api request"""
+ self.mock_object(netapp_api.NaElement, 'translate_struct')
+ self.mock_object(netapp_api.NaElement, 'add_child_elem')
+
+ params = {'api_name': zapi_fakes.FAKE_API_NAME,
+ 'query': zapi_fakes.FAKE_QUERY,
+ 'des_result': zapi_fakes.FAKE_DES_ATTR,
+ 'additional_elems': zapi_fakes.FAKE_XML_STR,
+ 'is_iter': True,
+ 'tag': 'tag'}
+
+ self.assertEqual(zapi_fakes.FAKE_API_NAME_ELEMENT.to_string(),
+ netapp_api.create_api_request(**params).to_string())
import six
from cinder import test
-from cinder.tests.unit.volume.drivers.netapp.dataontap.client import (
- fake_api as netapp_api)
from cinder.tests.unit.volume.drivers.netapp.dataontap import fakes as fake
+from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
from cinder.volume.drivers.netapp.dataontap.client import client_7mode
-from cinder.volume.drivers.netapp.dataontap.client import client_base
from cinder.volume.drivers.netapp import utils as netapp_utils
CONNECTION_INFO = {'hostname': 'hostname',
self.fake_volume = six.text_type(uuid.uuid4())
- # Inject fake netapp_lib module classes.
- netapp_api.mock_netapp_lib([client_7mode, netapp_utils, client_base])
-
with mock.patch.object(client_7mode.Client,
'get_ontapi_version',
return_value=(1, 20)):
import six
from cinder import test
-from cinder.tests.unit.volume.drivers.netapp.dataontap.client import (
- fake_api as netapp_api)
import cinder.tests.unit.volume.drivers.netapp.dataontap.fakes as fake
+from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
from cinder.volume.drivers.netapp.dataontap.client import client_base
def setUp(self):
super(NetAppBaseClientTestCase, self).setUp()
- # Inject fake netapp_lib module classes.
- netapp_api.mock_netapp_lib([client_base])
-
self.mock_object(client_base, 'LOG')
self.client = client_base.Client(**CONNECTION_INFO)
self.client.connection = mock.MagicMock()
from cinder import exception
from cinder import test
-from cinder.tests.unit.volume.drivers.netapp.dataontap.client import (
- fake_api as netapp_api)
from cinder.tests.unit.volume.drivers.netapp.dataontap.client import (
fakes as fake_client)
from cinder.tests.unit.volume.drivers.netapp.dataontap import fakes as fake
+from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
from cinder.volume.drivers.netapp.dataontap.client import client_cmode
from cinder.volume.drivers.netapp import utils as netapp_utils
def setUp(self):
super(NetAppCmodeClientTestCase, self).setUp()
- # Inject fake netapp_lib module classes.
- netapp_api.mock_netapp_lib([client_cmode])
-
with mock.patch.object(client_cmode.Client,
'get_ontapi_version',
return_value=(1, 20)):
from lxml import etree
-from cinder.tests.unit.volume.drivers.netapp.dataontap.client import (
- fake_api as netapp_api)
+from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
from cinder.volume.drivers.netapp.dataontap import ssc_cmode
from cinder import exception
from cinder import test
-from cinder.tests.unit.volume.drivers.netapp.dataontap.client import (
- fake_api as netapp_api)
import cinder.tests.unit.volume.drivers.netapp.dataontap.client.fakes \
as client_fakes
import cinder.tests.unit.volume.drivers.netapp.dataontap.fakes as fake
import cinder.tests.unit.volume.drivers.netapp.fakes as na_fakes
from cinder.volume.drivers.netapp.dataontap import block_7mode
from cinder.volume.drivers.netapp.dataontap import block_base
+from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
from cinder.volume.drivers.netapp.dataontap.client import client_base
from cinder.volume.drivers.netapp import utils as na_utils
def setUp(self):
super(NetAppBlockStorage7modeLibraryTestCase, self).setUp()
- # Inject fake netapp_lib module classes.
- netapp_api.mock_netapp_lib([block_7mode, client_base])
-
kwargs = {'configuration': self.get_config_7mode()}
self.library = block_7mode.NetAppBlockStorage7modeLibrary(
'driver', 'protocol', **kwargs)
from cinder import exception
from cinder.i18n import _
from cinder import test
-from cinder.tests.unit.volume.drivers.netapp.dataontap.client import (
- fake_api as netapp_api)
from cinder.tests.unit.volume.drivers.netapp.dataontap import fakes as fake
import cinder.tests.unit.volume.drivers.netapp.fakes as na_fakes
from cinder.volume.drivers.netapp.dataontap import block_base
+from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
from cinder.volume.drivers.netapp import utils as na_utils
from cinder.volume import utils as volume_utils
def setUp(self):
super(NetAppBlockStorageLibraryTestCase, self).setUp()
- # Inject fake netapp_lib module classes.
- netapp_api.mock_netapp_lib([block_base])
-
kwargs = {'configuration': self.get_config_base()}
self.library = block_base.NetAppBlockStorageLibrary(
'driver', 'protocol', **kwargs)
from cinder import exception
from cinder import test
-from cinder.tests.unit.volume.drivers.netapp.dataontap.client import (
- fake_api as netapp_api)
import cinder.tests.unit.volume.drivers.netapp.dataontap.fakes as fake
import cinder.tests.unit.volume.drivers.netapp.fakes as na_fakes
from cinder.volume.drivers.netapp.dataontap import block_base
from cinder.volume.drivers.netapp.dataontap import block_cmode
+from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
from cinder.volume.drivers.netapp.dataontap.client import client_base
from cinder.volume.drivers.netapp.dataontap import ssc_cmode
from cinder.volume.drivers.netapp import utils as na_utils
def setUp(self):
super(NetAppBlockStorageCmodeLibraryTestCase, self).setUp()
- # Inject fake netapp_lib module classes.
- netapp_api.mock_netapp_lib([block_cmode])
-
kwargs = {'configuration': self.get_config_cmode()}
self.library = block_cmode.NetAppBlockStorageCmodeLibrary(
'driver', 'protocol', **kwargs)
from cinder import exception
from cinder import test
-from cinder.tests.unit.volume.drivers.netapp.dataontap.client import (
- fake_api as netapp_api)
from cinder.tests.unit.volume.drivers.netapp.dataontap import fakes as fake
from cinder.tests.unit.volume.drivers.netapp import fakes as na_fakes
from cinder import utils
+from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
from cinder.volume.drivers.netapp.dataontap.client import client_cmode
from cinder.volume.drivers.netapp.dataontap import nfs_base
from cinder.volume.drivers.netapp.dataontap import nfs_cmode
import cinder.volume.drivers.netapp.options as na_opts
-def mock_netapp_lib(modules):
- """Inject fake netapp_lib module classes."""
- netapp_lib = mock.Mock()
- netapp_lib.api.rest.rest.WebserviceClient = mock.Mock()
- for module in modules:
- setattr(module, 'netapp_restclient', netapp_lib.api.rest.rest)
-
MULTIATTACH_HOST_GROUP = {
'clusterRef': '8500000060080E500023C7340036035F515B78FC',
'label': utils.MULTI_ATTACH_HOST_GROUP_NAME,
FAKE_ENDPOINT_HTTPS = 'https://host:8443/endpoint'
+FAKE_INVOC_MSG = 'success'
+
FAKE_CLIENT_PARAMS = {
'scheme': 'http',
'host': '127.0.0.1',
self.mock_object(client, 'LOG', self.mock_log)
self.fake_password = 'mysecret'
- # Inject fake netapp_lib module classes.
- eseries_fake.mock_netapp_lib([client])
-
self.my_client = client.RestClient('http', 'host', '80', '/test',
'user', self.fake_password,
system_id='fake_sys_id')
- self.my_client.client._endpoint = eseries_fake.FAKE_ENDPOINT_HTTP
+ self.my_client._endpoint = eseries_fake.FAKE_ENDPOINT_HTTP
fake_response = mock.Mock()
fake_response.status_code = 200
fake_resp.status_code = status_code
expected_msg = "Response error code - %s." % status_code
- with self.assertRaisesRegexp(es_exception.WebServiceException,
- expected_msg) as exc:
+ with self.assertRaisesRegex(es_exception.WebServiceException,
+ expected_msg) as exc:
self.my_client._eval_response(fake_resp)
self.assertEqual(status_code, exc.status_code)
fake_resp.text = resp_text
expected_msg = "Response error - %s." % resp_text
- with self.assertRaisesRegexp(es_exception.WebServiceException,
- expected_msg) as exc:
+ with self.assertRaisesRegex(es_exception.WebServiceException,
+ expected_msg) as exc:
self.my_client._eval_response(fake_resp)
self.assertEqual(status_code, exc.status_code)
client.RestClient, '_get_resource_url',
mock.Mock(return_value=eseries_fake.FAKE_RESOURCE_URL))
self.mock_object(
- self.my_client.client, 'invoke_service',
+ self.my_client, 'invoke_service',
mock.Mock(return_value=fake_invoke_service))
eseries_info = client.RestClient.get_eseries_api_info(
client.RestClient._init_features(self.my_client)
self.assertTrue(self.my_client.features.SSC_API_V2.supported)
+
+
+@ddt.ddt
+class TestWebserviceClientTestCase(test.TestCase):
+
+ def setUp(self):
+ """sets up the mock tests"""
+ super(TestWebserviceClientTestCase, self).setUp()
+ self.mock_log = mock.Mock()
+ self.mock_object(client, 'LOG', self.mock_log)
+ self.webclient = client.WebserviceClient('http', 'host', '80',
+ '/test', 'user', '****')
+
+ @ddt.data({'params': {'host': None, 'scheme': 'https', 'port': '80'}},
+ {'params': {'host': 'host', 'scheme': None, 'port': '80'}},
+ {'params': {'host': 'host', 'scheme': 'http', 'port': None}})
+ @ddt.unpack
+ def test__validate_params_value_error(self, params):
+ """Tests various scenarios for ValueError in validate method"""
+ self.assertRaises(exception.InvalidInput,
+ self.webclient._validate_params, **params)
+
+ def test_invoke_service_no_endpoint_error(self):
+ """Tests Exception and Log error if no endpoint is provided"""
+ self.webclient._endpoint = None
+ log_error = 'Unexpected error while invoking web service'
+
+ self.assertRaises(exception.NetAppDriverException,
+ self.webclient.invoke_service)
+ self.assertTrue(self.mock_log.exception.find(log_error))
+
+ def test_invoke_service(self):
+ """Tests if invoke_service evaluates the right response"""
+ self.webclient._endpoint = eseries_fake.FAKE_ENDPOINT_HTTP
+ self.mock_object(self.webclient.conn, 'request',
+ mock.Mock(return_value=eseries_fake.FAKE_INVOC_MSG))
+ result = self.webclient.invoke_service()
+
+ self.assertIsNotNone(result)
mock.Mock(return_value='fake_info'))
mock_create_driver = self.mock_object(na_common.NetAppDriver,
'create_driver')
- mock_check_netapp_lib = self.mock_object(na_utils, 'check_netapp_lib')
config = na_fakes.create_configuration()
config.netapp_storage_family = 'fake_family'
kwargs['app_version'] = 'fake_info'
mock_create_driver.assert_called_with('fake_family', 'fake_protocol',
*(), **kwargs)
- mock_check_netapp_lib.assert_called_once_with()
def test_new_missing_config(self):
import mock
from oslo_concurrency import processutils as putils
-from oslo_utils import importutils
from cinder import context
from cinder import exception
setattr(configuration, 'flag2', 'value2')
self.assertIsNone(na_utils.check_flags(required_flags, configuration))
- def test_check_netapp_lib(self):
- mock_try_import = self.mock_object(importutils, 'try_import')
-
- na_utils.check_netapp_lib()
-
- mock_try_import.assert_called_once_with('netapp_lib')
-
- def test_check_netapp_lib_not_found(self):
- self.mock_object(importutils,
- 'try_import',
- mock.Mock(return_value=None))
-
- self.assertRaises(exception.NetAppDriverException,
- na_utils.check_netapp_lib)
-
def test_to_bool(self):
self.assertTrue(na_utils.to_bool(True))
self.assertTrue(na_utils.to_bool('true'))
config.append_config_values(options.netapp_proxy_opts)
na_utils.check_flags(NetAppDriver.REQUIRED_FLAGS, config)
- na_utils.check_netapp_lib()
app_version = na_utils.OpenStackInfo().info()
LOG.info(_LI('OpenStack OS Version Info: %(info)s'),
from oslo_log import log as logging
from oslo_log import versionutils
from oslo_utils import excutils
-from oslo_utils import importutils
from oslo_utils import units
import six
from cinder import exception
from cinder.i18n import _, _LE, _LI, _LW
from cinder import utils
+from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
from cinder.volume.drivers.netapp import options as na_opts
from cinder.volume.drivers.netapp import utils as na_utils
from cinder.volume import utils as volume_utils
from cinder.zonemanager import utils as fczm_utils
-netapp_lib = importutils.try_import('netapp_lib')
-if netapp_lib:
- from netapp_lib.api.zapi import zapi as netapp_api
-
LOG = logging.getLogger(__name__)
--- /dev/null
+# Copyright (c) 2012 NetApp, Inc. All rights reserved.
+# Copyright (c) 2014 Navneet Singh. All rights reserved.
+# Copyright (c) 2014 Glenn Gobeli. All rights reserved.
+# Copyright (c) 2014 Clinton Knight. All rights reserved.
+# Copyright (c) 2015 Alex Meade. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+"""
+NetApp API for Data ONTAP and OnCommand DFM.
+
+Contains classes required to issue API calls to Data ONTAP and OnCommand DFM.
+"""
+
+import copy
+
+from lxml import etree
+from oslo_log import log as logging
+import six
+from six.moves import urllib
+
+from cinder import exception
+from cinder.i18n import _
+
+LOG = logging.getLogger(__name__)
+
+ESIS_CLONE_NOT_LICENSED = '14956'
+
+
+class NaServer(object):
+ """Encapsulates server connection logic."""
+
+ TRANSPORT_TYPE_HTTP = 'http'
+ TRANSPORT_TYPE_HTTPS = 'https'
+ SERVER_TYPE_FILER = 'filer'
+ SERVER_TYPE_DFM = 'dfm'
+ URL_FILER = 'servlets/netapp.servlets.admin.XMLrequest_filer'
+ URL_DFM = 'apis/XMLrequest'
+ NETAPP_NS = 'http://www.netapp.com/filer/admin'
+ STYLE_LOGIN_PASSWORD = 'basic_auth'
+ STYLE_CERTIFICATE = 'certificate_auth'
+
+ def __init__(self, host, server_type=SERVER_TYPE_FILER,
+ transport_type=TRANSPORT_TYPE_HTTP,
+ style=STYLE_LOGIN_PASSWORD, username=None,
+ password=None, port=None):
+ self._host = host
+ self.set_server_type(server_type)
+ self.set_transport_type(transport_type)
+ self.set_style(style)
+ if port:
+ self.set_port(port)
+ self._username = username
+ self._password = password
+ self._refresh_conn = True
+
+ LOG.debug('Using NetApp controller: %s', self._host)
+
+ def get_transport_type(self):
+ """Get the transport type protocol."""
+ return self._protocol
+
+ def set_transport_type(self, transport_type):
+ """Set the transport type protocol for API.
+
+ Supports http and https transport types.
+ """
+ if not transport_type:
+ raise ValueError('No transport type specified')
+ if transport_type.lower() not in (
+ NaServer.TRANSPORT_TYPE_HTTP,
+ NaServer.TRANSPORT_TYPE_HTTPS):
+ raise ValueError('Unsupported transport type')
+ self._protocol = transport_type.lower()
+ if self._protocol == NaServer.TRANSPORT_TYPE_HTTP:
+ if self._server_type == NaServer.SERVER_TYPE_FILER:
+ self.set_port(80)
+ else:
+ self.set_port(8088)
+ else:
+ if self._server_type == NaServer.SERVER_TYPE_FILER:
+ self.set_port(443)
+ else:
+ self.set_port(8488)
+ self._refresh_conn = True
+
+ def get_style(self):
+ """Get the authorization style for communicating with the server."""
+ return self._auth_style
+
+ def set_style(self, style):
+ """Set the authorization style for communicating with the server.
+
+ Supports basic_auth for now. Certificate_auth mode to be done.
+ """
+ if style.lower() not in (NaServer.STYLE_LOGIN_PASSWORD,
+ NaServer.STYLE_CERTIFICATE):
+ raise ValueError('Unsupported authentication style')
+ self._auth_style = style.lower()
+
+ def get_server_type(self):
+ """Get the target server type."""
+ return self._server_type
+
+ def set_server_type(self, server_type):
+ """Set the target server type.
+
+ Supports filer and dfm server types.
+ """
+ if server_type.lower() not in (NaServer.SERVER_TYPE_FILER,
+ NaServer.SERVER_TYPE_DFM):
+ raise ValueError('Unsupported server type')
+ self._server_type = server_type.lower()
+ if self._server_type == NaServer.SERVER_TYPE_FILER:
+ self._url = NaServer.URL_FILER
+ else:
+ self._url = NaServer.URL_DFM
+ self._ns = NaServer.NETAPP_NS
+ self._refresh_conn = True
+
+ def set_api_version(self, major, minor):
+ """Set the API version."""
+ try:
+ self._api_major_version = int(major)
+ self._api_minor_version = int(minor)
+ self._api_version = six.text_type(major) + "." + \
+ six.text_type(minor)
+ except ValueError:
+ raise ValueError('Major and minor versions must be integers')
+ self._refresh_conn = True
+
+ def get_api_version(self):
+ """Gets the API version tuple."""
+ if hasattr(self, '_api_version'):
+ return (self._api_major_version, self._api_minor_version)
+ return None
+
+ def set_port(self, port):
+ """Set the server communication port."""
+ try:
+ int(port)
+ except ValueError:
+ raise ValueError('Port must be integer')
+ self._port = six.text_type(port)
+ self._refresh_conn = True
+
+ def get_port(self):
+ """Get the server communication port."""
+ return self._port
+
+ def set_timeout(self, seconds):
+ """Sets the timeout in seconds."""
+ try:
+ self._timeout = int(seconds)
+ except ValueError:
+ raise ValueError('timeout in seconds must be integer')
+
+ def get_timeout(self):
+ """Gets the timeout in seconds if set."""
+ if hasattr(self, '_timeout'):
+ return self._timeout
+ return None
+
+ def get_vfiler(self):
+ """Get the vfiler to use in tunneling."""
+ return self._vfiler
+
+ def set_vfiler(self, vfiler):
+ """Set the vfiler to use if tunneling gets enabled."""
+ self._vfiler = vfiler
+
+ def get_vserver(self):
+ """Get the vserver to use in tunneling."""
+ return self._vserver
+
+ def set_vserver(self, vserver):
+ """Set the vserver to use if tunneling gets enabled."""
+ self._vserver = vserver
+
+ def set_username(self, username):
+ """Set the user name for authentication."""
+ self._username = username
+ self._refresh_conn = True
+
+ def set_password(self, password):
+ """Set the password for authentication."""
+ self._password = password
+ self._refresh_conn = True
+
+ def invoke_elem(self, na_element, enable_tunneling=False):
+ """Invoke the API on the server."""
+ if not na_element or not isinstance(na_element, NaElement):
+ raise ValueError('NaElement must be supplied to invoke API')
+
+ request, request_element = self._create_request(na_element,
+ enable_tunneling)
+
+ if not hasattr(self, '_opener') or not self._opener \
+ or self._refresh_conn:
+ self._build_opener()
+ try:
+ if hasattr(self, '_timeout'):
+ response = self._opener.open(request, timeout=self._timeout)
+ else:
+ response = self._opener.open(request)
+ except urllib.error.HTTPError as e:
+ raise NaApiError(e.code, e.msg)
+ except Exception:
+ raise NaApiError('Unexpected error')
+
+ response_xml = response.read()
+ response_element = self._get_result(response_xml)
+
+ return response_element
+
+ def invoke_successfully(self, na_element, enable_tunneling=False):
+ """Invokes API and checks execution status as success.
+
+ Need to set enable_tunneling to True explicitly to achieve it.
+ This helps to use same connection instance to enable or disable
+ tunneling. The vserver or vfiler should be set before this call
+ otherwise tunneling remains disabled.
+ """
+ result = self.invoke_elem(na_element, enable_tunneling)
+ if result.has_attr('status') and result.get_attr('status') == 'passed':
+ return result
+ code = result.get_attr('errno')\
+ or result.get_child_content('errorno')\
+ or 'ESTATUSFAILED'
+ if code == ESIS_CLONE_NOT_LICENSED:
+ msg = 'Clone operation failed: FlexClone not licensed.'
+ else:
+ msg = result.get_attr('reason')\
+ or result.get_child_content('reason')\
+ or 'Execution status is failed due to unknown reason'
+ raise NaApiError(code, msg)
+
+ def _create_request(self, na_element, enable_tunneling=False):
+ """Creates request in the desired format."""
+ netapp_elem = NaElement('netapp')
+ netapp_elem.add_attr('xmlns', self._ns)
+ if hasattr(self, '_api_version'):
+ netapp_elem.add_attr('version', self._api_version)
+ if enable_tunneling:
+ self._enable_tunnel_request(netapp_elem)
+ netapp_elem.add_child_elem(na_element)
+ request_d = netapp_elem.to_string()
+ request = urllib.request.Request(
+ self._get_url(), data=request_d,
+ headers={'Content-Type': 'text/xml', 'charset': 'utf-8'})
+ return request, netapp_elem
+
+ def _enable_tunnel_request(self, netapp_elem):
+ """Enables vserver or vfiler tunneling."""
+ if hasattr(self, '_vfiler') and self._vfiler:
+ if hasattr(self, '_api_major_version') and \
+ hasattr(self, '_api_minor_version') and \
+ self._api_major_version >= 1 and \
+ self._api_minor_version >= 7:
+ netapp_elem.add_attr('vfiler', self._vfiler)
+ else:
+ raise ValueError('ontapi version has to be atleast 1.7'
+ ' to send request to vfiler')
+ if hasattr(self, '_vserver') and self._vserver:
+ if hasattr(self, '_api_major_version') and \
+ hasattr(self, '_api_minor_version') and \
+ self._api_major_version >= 1 and \
+ self._api_minor_version >= 15:
+ netapp_elem.add_attr('vfiler', self._vserver)
+ else:
+ raise ValueError('ontapi version has to be atleast 1.15'
+ ' to send request to vserver')
+
+ def _parse_response(self, response):
+ """Get the NaElement for the response."""
+ if not response:
+ raise NaApiError('No response received')
+ xml = etree.XML(response)
+ return NaElement(xml)
+
+ def _get_result(self, response):
+ """Gets the call result."""
+ processed_response = self._parse_response(response)
+ return processed_response.get_child_by_name('results')
+
+ def _get_url(self):
+ return '%s://%s:%s/%s' % (self._protocol, self._host, self._port,
+ self._url)
+
+ def _build_opener(self):
+ if self._auth_style == NaServer.STYLE_LOGIN_PASSWORD:
+ auth_handler = self._create_basic_auth_handler()
+ else:
+ auth_handler = self._create_certificate_auth_handler()
+ opener = urllib.request.build_opener(auth_handler)
+ self._opener = opener
+
+ def _create_basic_auth_handler(self):
+ password_man = urllib.request.HTTPPasswordMgrWithDefaultRealm()
+ password_man.add_password(None, self._get_url(), self._username,
+ self._password)
+ auth_handler = urllib.request.HTTPBasicAuthHandler(password_man)
+ return auth_handler
+
+ def _create_certificate_auth_handler(self):
+ raise NotImplementedError()
+
+ def __str__(self):
+ return "server: %s" % self._host
+
+
+class NaElement(object):
+ """Class wraps basic building block for NetApp API request."""
+
+ def __init__(self, name):
+ """Name of the element or etree.Element."""
+ if isinstance(name, etree._Element):
+ self._element = name
+ else:
+ self._element = etree.Element(name)
+
+ def get_name(self):
+ """Returns the tag name of the element."""
+ return self._element.tag
+
+ def set_content(self, text):
+ """Set the text string for the element."""
+ self._element.text = text
+
+ def get_content(self):
+ """Get the text for the element."""
+ return self._element.text
+
+ def add_attr(self, name, value):
+ """Add the attribute to the element."""
+ self._element.set(name, value)
+
+ def add_attrs(self, **attrs):
+ """Add multiple attributes to the element."""
+ for attr in attrs.keys():
+ self._element.set(attr, attrs.get(attr))
+
+ def add_child_elem(self, na_element):
+ """Add the child element to the element."""
+ if isinstance(na_element, NaElement):
+ self._element.append(na_element._element)
+ return
+ raise
+
+ def get_child_by_name(self, name):
+ """Get the child element by the tag name."""
+ for child in self._element.iterchildren():
+ if child.tag == name or etree.QName(child.tag).localname == name:
+ return NaElement(child)
+ return None
+
+ def get_child_content(self, name):
+ """Get the content of the child."""
+ for child in self._element.iterchildren():
+ if child.tag == name or etree.QName(child.tag).localname == name:
+ return child.text
+ return None
+
+ def get_children(self):
+ """Get the children for the element."""
+ return [NaElement(el) for el in self._element.iterchildren()]
+
+ def has_attr(self, name):
+ """Checks whether element has attribute."""
+ attributes = self._element.attrib or {}
+ return name in attributes.keys()
+
+ def get_attr(self, name):
+ """Get the attribute with the given name."""
+ attributes = self._element.attrib or {}
+ return attributes.get(name)
+
+ def get_attr_names(self):
+ """Returns the list of attribute names."""
+ attributes = self._element.attrib or {}
+ return attributes.keys()
+
+ def add_new_child(self, name, content, convert=False):
+ """Add child with tag name and context.
+
+ Convert replaces entity refs to chars.
+ """
+ child = NaElement(name)
+ if convert:
+ content = NaElement._convert_entity_refs(content)
+ child.set_content(content)
+ self.add_child_elem(child)
+
+ @staticmethod
+ def _convert_entity_refs(text):
+ """Converts entity refs to chars to handle etree auto conversions."""
+ text = text.replace("<", "<")
+ text = text.replace(">", ">")
+ return text
+
+ @staticmethod
+ def create_node_with_children(node, **children):
+ """Creates and returns named node with children."""
+ parent = NaElement(node)
+ for child in children.keys():
+ parent.add_new_child(child, children.get(child, None))
+ return parent
+
+ def add_node_with_children(self, node, **children):
+ """Creates named node with children."""
+ parent = NaElement.create_node_with_children(node, **children)
+ self.add_child_elem(parent)
+
+ def to_string(self, pretty=False, method='xml', encoding='UTF-8'):
+ """Prints the element to string."""
+ return etree.tostring(self._element, method=method, encoding=encoding,
+ pretty_print=pretty)
+
+ def __str__(self):
+ return self.to_string(pretty=True)
+
+ def __repr__(self):
+ return str(self)
+
+ def __getitem__(self, key):
+ """Dict getter method for NaElement.
+
+ Returns NaElement list if present,
+ text value in case no NaElement node
+ children or attribute value if present.
+ """
+
+ child = self.get_child_by_name(key)
+ if child:
+ if child.get_children():
+ return child
+ else:
+ return child.get_content()
+ elif self.has_attr(key):
+ return self.get_attr(key)
+ raise KeyError(_('No element by given name %s.') % (key))
+
+ def __setitem__(self, key, value):
+ """Dict setter method for NaElement.
+
+ Accepts dict, list, tuple, str, int, float and long as valid value.
+ """
+ if key:
+ if value:
+ if isinstance(value, NaElement):
+ child = NaElement(key)
+ child.add_child_elem(value)
+ self.add_child_elem(child)
+ elif isinstance(value, (str, int, float, long)):
+ self.add_new_child(key, six.text_type(value))
+ elif isinstance(value, (list, tuple, dict)):
+ child = NaElement(key)
+ child.translate_struct(value)
+ self.add_child_elem(child)
+ else:
+ raise TypeError(_('Not a valid value for NaElement.'))
+ else:
+ self.add_child_elem(NaElement(key))
+ else:
+ raise KeyError(_('NaElement name cannot be null.'))
+
+ def translate_struct(self, data_struct):
+ """Convert list, tuple, dict to NaElement and appends.
+
+ Example usage:
+ 1.
+ <root>
+ <elem1>vl1</elem1>
+ <elem2>vl2</elem2>
+ <elem3>vl3</elem3>
+ </root>
+ The above can be achieved by doing
+ root = NaElement('root')
+ root.translate_struct({'elem1': 'vl1', 'elem2': 'vl2',
+ 'elem3': 'vl3'})
+ 2.
+ <root>
+ <elem1>vl1</elem1>
+ <elem2>vl2</elem2>
+ <elem1>vl3</elem1>
+ </root>
+ The above can be achieved by doing
+ root = NaElement('root')
+ root.translate_struct([{'elem1': 'vl1', 'elem2': 'vl2'},
+ {'elem1': 'vl3'}])
+ """
+ if isinstance(data_struct, (list, tuple)):
+ for el in data_struct:
+ if isinstance(el, (list, tuple, dict)):
+ self.translate_struct(el)
+ else:
+ self.add_child_elem(NaElement(el))
+ elif isinstance(data_struct, dict):
+ for k in data_struct.keys():
+ child = NaElement(k)
+ if isinstance(data_struct[k], (dict, list, tuple)):
+ child.translate_struct(data_struct[k])
+ else:
+ if data_struct[k]:
+ child.set_content(six.text_type(data_struct[k]))
+ self.add_child_elem(child)
+ else:
+ raise ValueError(_('Type cannot be converted into NaElement.'))
+
+
+class NaApiError(Exception):
+ """Base exception class for NetApp API errors."""
+
+ def __init__(self, code='unknown', message='unknown'):
+ self.code = code
+ self.message = message
+
+ def __str__(self, *args, **kwargs):
+ return 'NetApp API failed. Reason - %s:%s' % (self.code, self.message)
+
+
+NaErrors = {'API_NOT_FOUND': NaApiError('13005', 'Unable to find API'),
+ 'INSUFFICIENT_PRIVS': NaApiError('13003',
+ 'Insufficient privileges')}
+
+
+def invoke_api(na_server, api_name, api_family='cm', query=None,
+ des_result=None, additional_elems=None,
+ is_iter=False, records=0, tag=None,
+ timeout=0, tunnel=None):
+ """Invokes any given API call to a NetApp server.
+
+ :param na_server: na_server instance
+ :param api_name: API name string
+ :param api_family: cm or 7m
+ :param query: API query as dict
+ :param des_result: desired result as dict
+ :param additional_elems: dict other than query and des_result
+ :param is_iter: is iterator API
+ :param records: limit for records, 0 for infinite
+ :param timeout: timeout seconds
+ :param tunnel: tunnel entity, vserver or vfiler name
+ """
+ record_step = 50
+ if not (na_server or isinstance(na_server, NaServer)):
+ msg = _("Requires an NaServer instance.")
+ raise exception.InvalidInput(reason=msg)
+ server = copy.copy(na_server)
+ if api_family == 'cm':
+ server.set_vserver(tunnel)
+ else:
+ server.set_vfiler(tunnel)
+ if timeout > 0:
+ server.set_timeout(timeout)
+ iter_records = 0
+ cond = True
+ while cond:
+ na_element = create_api_request(
+ api_name, query, des_result, additional_elems,
+ is_iter, record_step, tag)
+ result = server.invoke_successfully(na_element, True)
+ if is_iter:
+ if records > 0:
+ iter_records = iter_records + record_step
+ if iter_records >= records:
+ cond = False
+ tag_el = result.get_child_by_name('next-tag')
+ tag = tag_el.get_content() if tag_el else None
+ if not tag:
+ cond = False
+ else:
+ cond = False
+ yield result
+
+
+def create_api_request(api_name, query=None, des_result=None,
+ additional_elems=None, is_iter=False,
+ record_step=50, tag=None):
+ """Creates a NetApp API request.
+
+ :param api_name: API name string
+ :param query: API query as dict
+ :param des_result: desired result as dict
+ :param additional_elems: dict other than query and des_result
+ :param is_iter: is iterator API
+ :param record_step: records at a time for iter API
+ :param tag: next tag for iter API
+ """
+ api_el = NaElement(api_name)
+ if query:
+ query_el = NaElement('query')
+ query_el.translate_struct(query)
+ api_el.add_child_elem(query_el)
+ if des_result:
+ res_el = NaElement('desired-attributes')
+ res_el.translate_struct(des_result)
+ api_el.add_child_elem(res_el)
+ if additional_elems:
+ api_el.translate_struct(additional_elems)
+ if is_iter:
+ api_el.add_new_child('max-records', six.text_type(record_step))
+ if tag:
+ api_el.add_new_child('tag', tag, True)
+ return api_el
import time
from oslo_log import log as logging
-from oslo_utils import importutils
import six
from cinder import exception
from cinder.i18n import _, _LW
from cinder import utils
+from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
from cinder.volume.drivers.netapp.dataontap.client import client_base
-netapp_lib = importutils.try_import('netapp_lib')
-if netapp_lib:
- from netapp_lib.api.zapi import zapi as netapp_api
-
LOG = logging.getLogger(__name__)
from oslo_log import log as logging
from oslo_utils import excutils
-from oslo_utils import importutils
from oslo_utils import timeutils
import six
from cinder.i18n import _LE, _LW, _LI
from cinder import utils
+from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
from cinder.volume.drivers.netapp import utils as na_utils
-netapp_lib = importutils.try_import('netapp_lib')
-if netapp_lib:
- from netapp_lib.api.zapi import zapi as netapp_api
-
LOG = logging.getLogger(__name__)
import math
from oslo_log import log as logging
-from oslo_utils import importutils
import six
from cinder import exception
from cinder.i18n import _, _LW
from cinder import utils
+from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
from cinder.volume.drivers.netapp.dataontap.client import client_base
from cinder.volume.drivers.netapp import utils as na_utils
-netapp_lib = importutils.try_import('netapp_lib')
-if netapp_lib:
- from netapp_lib.api.zapi import errors as netapp_error
- from netapp_lib.api.zapi import zapi as netapp_api
-
LOG = logging.getLogger(__name__)
DELETED_PREFIX = 'deleted_cinder_'
self.connection.invoke_successfully(na_el)
except Exception as e:
if isinstance(e, netapp_api.NaApiError):
- if(e.code == netapp_error.EAPINOTFOUND
- or e.code == netapp_error.EAPIPRIVILEGE):
+ if (e.code == netapp_api.NaErrors
+ ['API_NOT_FOUND'].code or
+ e.code == netapp_api.NaErrors
+ ['INSUFFICIENT_PRIVS'].code):
failed_apis.append(api_name)
elif major == 1 and minor >= 20:
failed_apis = copy.copy(api_list)
import threading
from oslo_log import log as logging
-from oslo_utils import importutils
from oslo_utils import timeutils
import six
from cinder import exception
from cinder.i18n import _, _LI, _LW
from cinder import utils
+from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
from cinder.volume.drivers.netapp import utils as na_utils
-netapp_lib = importutils.try_import('netapp_lib')
-if netapp_lib:
- from netapp_lib.api.zapi import zapi as netapp_api
-
LOG = logging.getLogger(__name__)
import uuid
from oslo_log import log as logging
-from oslo_utils import importutils
+import requests
import six
from six.moves import urllib
from cinder import exception
from cinder.i18n import _
+from cinder.i18n import _LE
import cinder.utils as cinder_utils
from cinder.volume.drivers.netapp.eseries import exception as es_exception
from cinder.volume.drivers.netapp.eseries import utils
from cinder.volume.drivers.netapp import utils as na_utils
-netapp_lib = importutils.try_import('netapp_lib')
-if netapp_lib:
- from netapp_lib.api.rest import rest as netapp_restclient
-
LOG = logging.getLogger(__name__)
-class RestClient(object):
+class WebserviceClient(object):
+ """Base client for NetApp Storage web services."""
+
+ def __init__(self, scheme, host, port, service_path, username,
+ password, **kwargs):
+ self._validate_params(scheme, host, port)
+ self._create_endpoint(scheme, host, port, service_path)
+ self._username = username
+ self._password = password
+ self._init_connection()
+
+ def _validate_params(self, scheme, host, port):
+ """Does some basic validation for web service params."""
+ if host is None or port is None or scheme is None:
+ msg = _('One of the required inputs from host, '
+ 'port or scheme was not found.')
+ raise exception.InvalidInput(reason=msg)
+ if scheme not in ('http', 'https'):
+ raise exception.InvalidInput(reason=_("Invalid transport type."))
+
+ def _create_endpoint(self, scheme, host, port, service_path):
+ """Creates end point url for the service."""
+ netloc = '%s:%s' % (host, port)
+ self._endpoint = urllib.parse.urlunparse((scheme, netloc, service_path,
+ None, None, None))
+
+ def _init_connection(self):
+ """Do client specific set up for session and connection pooling."""
+ self.conn = requests.Session()
+ if self._username and self._password:
+ self.conn.auth = (self._username, self._password)
+
+ def invoke_service(self, method='GET', url=None, params=None, data=None,
+ headers=None, timeout=None, verify=False):
+ url = url or self._endpoint
+ try:
+ response = self.conn.request(method, url, params, data,
+ headers=headers, timeout=timeout,
+ verify=verify)
+ # Catching error conditions other than the perceived ones.
+ # Helps propagating only known exceptions back to the caller.
+ except Exception as e:
+ LOG.exception(_LE("Unexpected error while invoking web service."
+ " Error - %s."), e)
+ raise exception.NetAppDriverException(
+ _("Invoking web service failed."))
+ return response
+
+
+class RestClient(WebserviceClient):
"""REST client specific to e-series storage service."""
ID = 'id'
def __init__(self, scheme, host, port, service_path, username,
password, **kwargs):
+ super(RestClient, self).__init__(scheme, host, port, service_path,
+ username, password, **kwargs)
+
kwargs = kwargs or {}
- self.client = netapp_restclient.WebserviceClient(scheme, host, port,
- service_path,
- username, password,
- **kwargs)
+
self._system_id = kwargs.get('system_id')
self._content_type = kwargs.get('content_type') or 'json'
raise exception.NotFound(_('Storage system id not set.'))
kwargs['system-id'] = self._system_id
path = path.format(**kwargs)
- if not self.client._endpoint.endswith('/'):
- self.client._endpoint = '%s/' % self.client._endpoint
- return urllib.parse.urljoin(self.client._endpoint, path.lstrip('/'))
+ if not self._endpoint.endswith('/'):
+ self._endpoint = '%s/' % self._endpoint
+ return urllib.parse.urljoin(self._endpoint, path.lstrip('/'))
def _invoke(self, method, path, data=None, use_system=True,
timeout=None, verify=False, **kwargs):
if cinder_utils.TRACE_API:
self._log_http_request(method, url, headers, data)
data = json.dumps(data) if data else None
- res = self.client.invoke_service(method, url, data=data,
- headers=headers,
- timeout=timeout, verify=verify)
+ res = self.invoke_service(method, url, data=data,
+ headers=headers,
+ timeout=timeout, verify=verify)
res_dict = res.json() if res.text else None
if cinder_utils.TRACE_API:
'Accept': 'application/json'}
url = self._get_resource_url(path, True).replace(
'/devmgr/v2', '', 1)
- result = self.client.invoke_service(method='GET', url=url,
- headers=headers,
- verify=verify)
+ result = self.invoke_service(method='GET', url=url,
+ headers=headers,
+ verify=verify)
about_response_dict = result.json()
mode_is_proxy = about_response_dict['runningAsProxy']
if mode_is_proxy:
from oslo_concurrency import processutils as putils
from oslo_log import log as logging
-from oslo_utils import importutils
import six
from cinder import context
raise exception.InvalidInput(reason=msg)
-def check_netapp_lib():
- if not importutils.try_import('netapp_lib'):
- msg = ('You have not installed the NetApp API Library for OpenStack. '
- 'Please install it using "sudo pip install netapp-lib" and '
- 'restart this service!')
- raise exception.NetAppDriverException(msg)
-
-
def to_bool(val):
"""Converts true, yes, y, 1 to True, False otherwise."""
if val: