]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Revert use of netapp_lib from NetApp Drivers
authorGoutham Pacha Ravi <gouthamr@netapp.com>
Fri, 25 Sep 2015 05:52:43 +0000 (01:52 -0400)
committerThomas Bechtold <tbechtold@suse.com>
Sat, 26 Sep 2015 10:54:26 +0000 (10:54 +0000)
This patch cleanly reverts the changes made via the
commit: e681ba2a99995dcd999e6539bb1222f8a1ac8adc
cleanly and mitigates the conflicts that would
occur with git-revert on the said commit.

The revert is solely for changes pertaining to the use
of the external library, netapp_lib. Minor code refactors
from the prior change are retained.

Unit test coverage has been increased for ZAPI and REST
interface code in netapp/dataontap/client/api.py and
netapp/eseries/client.py.

Closes-Bug: #1499334
Change-Id: Icead7e168e1c7187840de87c69365d26aedd5924
(cherry picked from commit ff81307ca4c796a66f4bd584b4633db07dcf2a16)

28 files changed:
cinder/tests/unit/test_netapp.py
cinder/tests/unit/test_netapp_eseries_iscsi.py
cinder/tests/unit/test_netapp_nfs.py
cinder/tests/unit/test_netapp_ssc.py
cinder/tests/unit/volume/drivers/netapp/dataontap/client/fake_api.py [deleted file]
cinder/tests/unit/volume/drivers/netapp/dataontap/client/fakes.py
cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_api.py [new file with mode: 0644]
cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_client_7mode.py
cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_client_base.py
cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_client_cmode.py
cinder/tests/unit/volume/drivers/netapp/dataontap/fakes.py
cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_7mode.py
cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_base.py
cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_cmode.py
cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_cmode.py
cinder/tests/unit/volume/drivers/netapp/eseries/fakes.py
cinder/tests/unit/volume/drivers/netapp/eseries/test_client.py
cinder/tests/unit/volume/drivers/netapp/test_common.py
cinder/tests/unit/volume/drivers/netapp/test_utils.py
cinder/volume/drivers/netapp/common.py
cinder/volume/drivers/netapp/dataontap/block_base.py
cinder/volume/drivers/netapp/dataontap/client/api.py [new file with mode: 0644]
cinder/volume/drivers/netapp/dataontap/client/client_7mode.py
cinder/volume/drivers/netapp/dataontap/client/client_base.py
cinder/volume/drivers/netapp/dataontap/client/client_cmode.py
cinder/volume/drivers/netapp/dataontap/ssc_cmode.py
cinder/volume/drivers/netapp/eseries/client.py
cinder/volume/drivers/netapp/utils.py

index dbc4bb1e962be911eb44e22a28f7b7182ba87704..39334d793fc76a43d65142ff778ecb329fa49da3 100644 (file)
@@ -23,8 +23,6 @@ from six.moves import http_client
 
 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
@@ -561,10 +559,6 @@ class NetAppDirectCmodeISCSIDriverTestCase(test.TestCase):
             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',
@@ -785,10 +779,6 @@ class NetAppDriverNegativeTestCase(test.TestCase):
     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()
@@ -1252,10 +1242,6 @@ class NetAppDirect7modeISCSIDriverTestCase_NV(test.TestCase):
     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',
@@ -1316,10 +1302,6 @@ class NetAppDirect7modeISCSIDriverTestCase_WV(
     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',
index 11b383b43c407295f309bd7497a4adaa361335e7..dfe87fad58a671c36d9654b30e6cf0262b32c76c 100644 (file)
@@ -668,10 +668,6 @@ class NetAppEseriesISCSIDriverTestCase(test.TestCase):
     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
index 4479651d17c07be01d1b25f37fa3a86e80173dc3..4a66240708d5a3f47b45dc9009902b6d459d3666 100644 (file)
@@ -27,8 +27,6 @@ import six
 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
@@ -36,6 +34,7 @@ from cinder.volume.drivers.netapp.dataontap import (nfs_7mode
                                                     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
@@ -159,10 +158,6 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
         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()
@@ -1472,10 +1467,6 @@ class NetApp7modeNfsDriverTestCase(NetAppCmodeNfsDriverTestCase):
     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(
index 55ee62adc15f65eb57fdd9147e3a348b426e6f9d..4fe5cde8e052c87a7dd6505137c32d5f12a5dca9 100644 (file)
@@ -25,8 +25,7 @@ from six.moves import http_client
 
 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
 
 
@@ -375,7 +374,6 @@ class SscUtilsTestCase(test.TestCase):
 
     def setUp(self):
         super(SscUtilsTestCase, self).setUp()
-        netapp_api.mock_netapp_lib([ssc_cmode])
         self.stubs.Set(http_client, 'HTTPConnection',
                        FakeDirectCmodeHTTPConnection)
 
diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/client/fake_api.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/client/fake_api.py
deleted file mode 100644 (file)
index 71dcbbe..0000000
+++ /dev/null
@@ -1,238 +0,0 @@
-# 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("&lt;", "<")
-        text = text.replace("&gt;", ">")
-        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.')
index 04428ccc0a10dcc977a21972a3a806389df09e36..0bfee43d09308a1d77f4095c6f7a6136c410b985 100644 (file)
 
 
 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">
diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_api.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_api.py
new file mode 100644 (file)
index 0000000..b2ff96d
--- /dev/null
@@ -0,0 +1,509 @@
+# 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())
index 55481ee900639d03704e05729d4527f8c78a4931..4f3467c00d06d777a1a522fce2dbe25305d97f46 100644 (file)
@@ -21,11 +21,9 @@ import mock
 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',
@@ -42,9 +40,6 @@ class NetApp7modeClientTestCase(test.TestCase):
 
         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)):
index 30d8edbcb676adb5ac969dd79401bf8159410fbe..26763026ed0dfd83d8ed650b187731828c678021 100644 (file)
@@ -20,9 +20,8 @@ import mock
 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
 
 
@@ -38,9 +37,6 @@ class NetAppBaseClientTestCase(test.TestCase):
     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()
index e5e69148e5462bf273c4dadcee287439daf9245d..3e0909bfcbff295fe44e683688a83c7e5b60d808 100644 (file)
@@ -22,11 +22,10 @@ import six
 
 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
 
@@ -44,9 +43,6 @@ class NetAppCmodeClientTestCase(test.TestCase):
     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)):
index f097d4943f1db2bdc00b7606a7a950438e6c605a..b110038d305541acf5d1e2487389209f4042d239 100644 (file)
@@ -15,8 +15,7 @@
 
 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
 
 
index a217506c3a10cab73497fcb3a402270565e97eec..be20e7d6d67944681d453e71acd20641b5f8ffc0 100644 (file)
@@ -25,14 +25,13 @@ import mock
 
 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
 
@@ -44,9 +43,6 @@ class NetAppBlockStorage7modeLibraryTestCase(test.TestCase):
     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)
index 2b377bb3631427d40b041ba7e513560697ce6639..5d15706f667650350a9a8b43706ff0a859674f74 100644 (file)
@@ -29,11 +29,10 @@ from oslo_utils import units
 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
 
@@ -43,9 +42,6 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
     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)
index ed26b7535bd5374dd27aca3cdeec059c58041faf..c2bba08884429ea1e882891f9d47222cedc3007b 100644 (file)
@@ -23,12 +23,11 @@ from oslo_service import loopingcall
 
 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
@@ -41,9 +40,6 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
     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)
index 0614b83a8863929d62231d53086d46639f5b9981..c706e9cf0c6f33be931bd39ac0ffd31079f2d38c 100644 (file)
@@ -24,11 +24,10 @@ from oslo_utils import units
 
 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
index d582f1f5802ee7f732a845be94e6c22b5a6d81f0..68b81fd8a7d5f38bd6596f0eff33c9420080587c 100644 (file)
@@ -26,13 +26,6 @@ from cinder.volume.drivers.netapp.eseries import utils
 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,
@@ -855,6 +848,8 @@ FAKE_ENDPOINT_HTTP = 'http://host:80/endpoint'
 
 FAKE_ENDPOINT_HTTPS = 'https://host:8443/endpoint'
 
+FAKE_INVOC_MSG = 'success'
+
 FAKE_CLIENT_PARAMS = {
     'scheme': 'http',
     'host': '127.0.0.1',
index 686166c0cbd1f811e882caf1e599ba0ebe176021..dcd9fca5e78f0ebd43d9b695d5c4b645fb000d70 100644 (file)
@@ -41,13 +41,10 @@ class NetAppEseriesClientDriverTestCase(test.TestCase):
         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
@@ -67,8 +64,8 @@ class NetAppEseriesClientDriverTestCase(test.TestCase):
         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)
@@ -81,8 +78,8 @@ class NetAppEseriesClientDriverTestCase(test.TestCase):
         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)
@@ -413,7 +410,7 @@ class NetAppEseriesClientDriverTestCase(test.TestCase):
             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(
@@ -732,3 +729,42 @@ class NetAppEseriesClientDriverTestCase(test.TestCase):
         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)
index 85f112a6170a6f11b24c2a58f8756ce46acc8f4d..db3a6362743726ca93083fdffd5bbb485def0e7c 100644 (file)
@@ -35,7 +35,6 @@ class NetAppDriverFactoryTestCase(test.TestCase):
                          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'
@@ -47,7 +46,6 @@ class NetAppDriverFactoryTestCase(test.TestCase):
         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):
 
index 4cdd32791a610fe05f45ee6deb4dbdb2dae47653..28c288a8ff0d1b2d60779a56b8c860b722ebf32a 100644 (file)
@@ -23,7 +23,6 @@ import platform
 
 import mock
 from oslo_concurrency import processutils as putils
-from oslo_utils import importutils
 
 from cinder import context
 from cinder import exception
@@ -64,21 +63,6 @@ class NetAppDriverUtilsTestCase(test.TestCase):
         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'))
index 7c2a200fab0be952ecba9f399ec4982dbf50efe1..8414096ad8efe0e2165a28790df3e11b754ffded 100644 (file)
@@ -73,7 +73,6 @@ class NetAppDriver(driver.ProxyVD):
 
         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'),
index d04cdd5f572466c3a721c228160b854ae688053e..8db15678dca846a040ae433ac981497610bda19a 100644 (file)
@@ -29,22 +29,18 @@ import uuid
 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__)
 
diff --git a/cinder/volume/drivers/netapp/dataontap/client/api.py b/cinder/volume/drivers/netapp/dataontap/client/api.py
new file mode 100644 (file)
index 0000000..b3a58c2
--- /dev/null
@@ -0,0 +1,613 @@
+# 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("&lt;", "<")
+        text = text.replace("&gt;", ">")
+        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
index 3a1cdd430e52d3ac8b3bb2bd0c2360f131bbc545..55d2471c7b5fd4b900c371366f7b50cb89c8f5e9 100644 (file)
@@ -19,18 +19,14 @@ import math
 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__)
 
index b37353dea9fc22fc959bf7f2df16a40195627fcc..1e9e2c64fccaa6b89bab477d7765fb74105e7a2e 100644 (file)
@@ -20,19 +20,15 @@ import sys
 
 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__)
 
index f631dd4da6fbfc4a27e686188d38deca9de6c2a9..7a726b0b2b1ec6e49faa540451d52a4306342e8f 100644 (file)
@@ -19,20 +19,15 @@ import copy
 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_'
@@ -528,8 +523,10 @@ class Client(client_base.Client):
                             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)
index 411fcce22fe002af14e5286969d78262e794c060..8a9a321f1ae7f4b87559b9cad6cc322285cc5e63 100644 (file)
@@ -24,19 +24,15 @@ import copy
 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__)
 
index 7b96f02ad11108573cdf0377f477f022a21561ad..a97ced89d564f9376413e7bba3663975a6b8899a 100644 (file)
@@ -26,26 +26,72 @@ import json
 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'
@@ -66,11 +112,11 @@ class RestClient(object):
     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'
 
@@ -149,9 +195,9 @@ class RestClient(object):
                 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):
@@ -163,9 +209,9 @@ class RestClient(object):
             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:
@@ -664,9 +710,9 @@ class RestClient(object):
                    '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:
index b6a13b04d66f3f48bd9d53b32ce213f7c2398a90..0dbb1016c6f847162957e5f95f5fc4f2c706269d 100644 (file)
@@ -29,7 +29,6 @@ import socket
 
 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
@@ -76,14 +75,6 @@ def check_flags(required_flags, configuration):
             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: