Revert use of netapp_lib from NetApp Drivers
This patch cleanly reverts the changes made via the commit:e681ba2a99
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 commitff81307ca4
)
This commit is contained in:
parent
24451969ec
commit
bdfe5fa04e
|
@ -23,8 +23,6 @@ from six.moves import http_client
|
||||||
|
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder import test
|
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.tests.unit.volume.drivers.netapp.dataontap import fakes
|
||||||
from cinder.volume import configuration as conf
|
from cinder.volume import configuration as conf
|
||||||
from cinder.volume.drivers.netapp import common
|
from cinder.volume.drivers.netapp import common
|
||||||
|
@ -561,10 +559,6 @@ class NetAppDirectCmodeISCSIDriverTestCase(test.TestCase):
|
||||||
lambda a, b, c, synchronous: None)
|
lambda a, b, c, synchronous: None)
|
||||||
self.mock_object(utils, 'OpenStackInfo')
|
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())
|
configuration = self._set_config(create_configuration())
|
||||||
driver = common.NetAppDriver(configuration=configuration)
|
driver = common.NetAppDriver(configuration=configuration)
|
||||||
self.stubs.Set(http_client, 'HTTPConnection',
|
self.stubs.Set(http_client, 'HTTPConnection',
|
||||||
|
@ -785,10 +779,6 @@ class NetAppDriverNegativeTestCase(test.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(NetAppDriverNegativeTestCase, self).setUp()
|
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):
|
def test_incorrect_family(self):
|
||||||
self.mock_object(utils, 'OpenStackInfo')
|
self.mock_object(utils, 'OpenStackInfo')
|
||||||
configuration = create_configuration()
|
configuration = create_configuration()
|
||||||
|
@ -1252,10 +1242,6 @@ class NetAppDirect7modeISCSIDriverTestCase_NV(test.TestCase):
|
||||||
def _custom_setup(self):
|
def _custom_setup(self):
|
||||||
self.mock_object(utils, 'OpenStackInfo')
|
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())
|
configuration = self._set_config(create_configuration())
|
||||||
driver = common.NetAppDriver(configuration=configuration)
|
driver = common.NetAppDriver(configuration=configuration)
|
||||||
self.stubs.Set(http_client, 'HTTPConnection',
|
self.stubs.Set(http_client, 'HTTPConnection',
|
||||||
|
@ -1316,10 +1302,6 @@ class NetAppDirect7modeISCSIDriverTestCase_WV(
|
||||||
def _custom_setup(self):
|
def _custom_setup(self):
|
||||||
self.mock_object(utils, 'OpenStackInfo')
|
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())
|
configuration = self._set_config(create_configuration())
|
||||||
driver = common.NetAppDriver(configuration=configuration)
|
driver = common.NetAppDriver(configuration=configuration)
|
||||||
self.stubs.Set(http_client, 'HTTPConnection',
|
self.stubs.Set(http_client, 'HTTPConnection',
|
||||||
|
|
|
@ -668,10 +668,6 @@ class NetAppEseriesISCSIDriverTestCase(test.TestCase):
|
||||||
def _custom_setup(self):
|
def _custom_setup(self):
|
||||||
self.mock_object(na_utils, 'OpenStackInfo')
|
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())
|
configuration = self._set_config(create_configuration())
|
||||||
self.driver = common.NetAppDriver(configuration=configuration)
|
self.driver = common.NetAppDriver(configuration=configuration)
|
||||||
self.library = self.driver.library
|
self.library = self.driver.library
|
||||||
|
|
|
@ -27,8 +27,6 @@ import six
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.image import image_utils
|
from cinder.image import image_utils
|
||||||
from cinder import test
|
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 import utils as cinder_utils
|
||||||
from cinder.volume import configuration as conf
|
from cinder.volume import configuration as conf
|
||||||
from cinder.volume.drivers.netapp import common
|
from cinder.volume.drivers.netapp import common
|
||||||
|
@ -36,6 +34,7 @@ from cinder.volume.drivers.netapp.dataontap import (nfs_7mode
|
||||||
as netapp_nfs_7mode)
|
as netapp_nfs_7mode)
|
||||||
from cinder.volume.drivers.netapp.dataontap import (nfs_cmode
|
from cinder.volume.drivers.netapp.dataontap import (nfs_cmode
|
||||||
as netapp_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_7mode
|
||||||
from cinder.volume.drivers.netapp.dataontap.client import client_base
|
from cinder.volume.drivers.netapp.dataontap.client import client_base
|
||||||
from cinder.volume.drivers.netapp.dataontap.client import client_cmode
|
from cinder.volume.drivers.netapp.dataontap.client import client_cmode
|
||||||
|
@ -159,10 +158,6 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
||||||
kwargs['netapp_mode'] = 'proxy'
|
kwargs['netapp_mode'] = 'proxy'
|
||||||
kwargs['configuration'] = create_configuration()
|
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.mock_object(nfs_base, 'LOG')
|
||||||
self._driver = netapp_nfs_cmode.NetAppCmodeNfsDriver(**kwargs)
|
self._driver = netapp_nfs_cmode.NetAppCmodeNfsDriver(**kwargs)
|
||||||
self._driver.zapi_client = mock.Mock()
|
self._driver.zapi_client = mock.Mock()
|
||||||
|
@ -1472,10 +1467,6 @@ class NetApp7modeNfsDriverTestCase(NetAppCmodeNfsDriverTestCase):
|
||||||
def _custom_setup(self):
|
def _custom_setup(self):
|
||||||
self.mock_object(utils, 'OpenStackInfo')
|
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(common.na_utils, 'LOG')
|
||||||
self.mock_object(nfs_base, 'LOG')
|
self.mock_object(nfs_base, 'LOG')
|
||||||
self._driver = netapp_nfs_7mode.NetApp7modeNfsDriver(
|
self._driver = netapp_nfs_7mode.NetApp7modeNfsDriver(
|
||||||
|
|
|
@ -25,8 +25,7 @@ from six.moves import http_client
|
||||||
|
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder import test
|
from cinder import test
|
||||||
from cinder.tests.unit.volume.drivers.netapp.dataontap.client import (
|
from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
|
||||||
fake_api as netapp_api)
|
|
||||||
from cinder.volume.drivers.netapp.dataontap import ssc_cmode
|
from cinder.volume.drivers.netapp.dataontap import ssc_cmode
|
||||||
|
|
||||||
|
|
||||||
|
@ -375,7 +374,6 @@ class SscUtilsTestCase(test.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(SscUtilsTestCase, self).setUp()
|
super(SscUtilsTestCase, self).setUp()
|
||||||
netapp_api.mock_netapp_lib([ssc_cmode])
|
|
||||||
self.stubs.Set(http_client, 'HTTPConnection',
|
self.stubs.Set(http_client, 'HTTPConnection',
|
||||||
FakeDirectCmodeHTTPConnection)
|
FakeDirectCmodeHTTPConnection)
|
||||||
|
|
||||||
|
|
|
@ -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("<", "<")
|
|
||||||
text = text.replace(">", ">")
|
|
||||||
return text
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_node_with_children(node, **children):
|
|
||||||
"""Creates and returns named node with children."""
|
|
||||||
parent = NaElement(node)
|
|
||||||
for child in children.keys():
|
|
||||||
parent.add_new_child(child, children.get(child, None))
|
|
||||||
return parent
|
|
||||||
|
|
||||||
def add_node_with_children(self, node, **children):
|
|
||||||
"""Creates named node with children."""
|
|
||||||
parent = NaElement.create_node_with_children(node, **children)
|
|
||||||
self.add_child_elem(parent)
|
|
||||||
|
|
||||||
def to_string(self, pretty=False, method='xml', encoding='UTF-8'):
|
|
||||||
"""Prints the element to string."""
|
|
||||||
return etree.tostring(self._element, method=method, encoding=encoding,
|
|
||||||
pretty_print=pretty)
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
"""Dict getter method for NaElement.
|
|
||||||
|
|
||||||
Returns NaElement list if present,
|
|
||||||
text value in case no NaElement node
|
|
||||||
children or attribute value if present.
|
|
||||||
"""
|
|
||||||
|
|
||||||
child = self.get_child_by_name(key)
|
|
||||||
if child:
|
|
||||||
if child.get_children():
|
|
||||||
return child
|
|
||||||
else:
|
|
||||||
return child.get_content()
|
|
||||||
elif self.has_attr(key):
|
|
||||||
return self.get_attr(key)
|
|
||||||
raise KeyError('No element by given name %s.' % key)
|
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
|
||||||
"""Dict setter method for NaElement.
|
|
||||||
|
|
||||||
Accepts dict, list, tuple, str, int, float and long as valid value.
|
|
||||||
"""
|
|
||||||
if key:
|
|
||||||
if value:
|
|
||||||
if isinstance(value, NaElement):
|
|
||||||
child = NaElement(key)
|
|
||||||
child.add_child_elem(value)
|
|
||||||
self.add_child_elem(child)
|
|
||||||
elif isinstance(value, (str, int, float, long)):
|
|
||||||
self.add_new_child(key, six.text_type(value))
|
|
||||||
elif isinstance(value, (list, tuple, dict)):
|
|
||||||
child = NaElement(key)
|
|
||||||
child.translate_struct(value)
|
|
||||||
self.add_child_elem(child)
|
|
||||||
else:
|
|
||||||
raise TypeError('Not a valid value for NaElement.')
|
|
||||||
else:
|
|
||||||
self.add_child_elem(NaElement(key))
|
|
||||||
else:
|
|
||||||
raise KeyError('NaElement name cannot be null.')
|
|
||||||
|
|
||||||
def translate_struct(self, data_struct):
|
|
||||||
"""Convert list, tuple, dict to NaElement and appends."""
|
|
||||||
|
|
||||||
if isinstance(data_struct, (list, tuple)):
|
|
||||||
for el in data_struct:
|
|
||||||
if isinstance(el, (list, tuple, dict)):
|
|
||||||
self.translate_struct(el)
|
|
||||||
else:
|
|
||||||
self.add_child_elem(NaElement(el))
|
|
||||||
elif isinstance(data_struct, dict):
|
|
||||||
for k in data_struct.keys():
|
|
||||||
child = NaElement(k)
|
|
||||||
if isinstance(data_struct[k], (dict, list, tuple)):
|
|
||||||
child.translate_struct(data_struct[k])
|
|
||||||
else:
|
|
||||||
if data_struct[k]:
|
|
||||||
child.set_content(six.text_type(data_struct[k]))
|
|
||||||
self.add_child_elem(child)
|
|
||||||
else:
|
|
||||||
raise ValueError('Type cannot be converted into NaElement.')
|
|
|
@ -14,7 +14,83 @@
|
||||||
|
|
||||||
|
|
||||||
from lxml import etree
|
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("""
|
GET_OPERATIONAL_NETWORK_INTERFACE_ADDRESSES_RESPONSE = etree.XML("""
|
||||||
<results status="passed">
|
<results status="passed">
|
||||||
|
|
|
@ -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())
|
|
@ -21,11 +21,9 @@ import mock
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from cinder import test
|
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.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_7mode
|
||||||
from cinder.volume.drivers.netapp.dataontap.client import client_base
|
|
||||||
from cinder.volume.drivers.netapp import utils as netapp_utils
|
from cinder.volume.drivers.netapp import utils as netapp_utils
|
||||||
|
|
||||||
CONNECTION_INFO = {'hostname': 'hostname',
|
CONNECTION_INFO = {'hostname': 'hostname',
|
||||||
|
@ -42,9 +40,6 @@ class NetApp7modeClientTestCase(test.TestCase):
|
||||||
|
|
||||||
self.fake_volume = six.text_type(uuid.uuid4())
|
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,
|
with mock.patch.object(client_7mode.Client,
|
||||||
'get_ontapi_version',
|
'get_ontapi_version',
|
||||||
return_value=(1, 20)):
|
return_value=(1, 20)):
|
||||||
|
|
|
@ -20,9 +20,8 @@ import mock
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from cinder import test
|
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.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
|
from cinder.volume.drivers.netapp.dataontap.client import client_base
|
||||||
|
|
||||||
|
|
||||||
|
@ -38,9 +37,6 @@ class NetAppBaseClientTestCase(test.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(NetAppBaseClientTestCase, self).setUp()
|
super(NetAppBaseClientTestCase, self).setUp()
|
||||||
|
|
||||||
# Inject fake netapp_lib module classes.
|
|
||||||
netapp_api.mock_netapp_lib([client_base])
|
|
||||||
|
|
||||||
self.mock_object(client_base, 'LOG')
|
self.mock_object(client_base, 'LOG')
|
||||||
self.client = client_base.Client(**CONNECTION_INFO)
|
self.client = client_base.Client(**CONNECTION_INFO)
|
||||||
self.client.connection = mock.MagicMock()
|
self.client.connection = mock.MagicMock()
|
||||||
|
|
|
@ -22,11 +22,10 @@ import six
|
||||||
|
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder import test
|
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 (
|
from cinder.tests.unit.volume.drivers.netapp.dataontap.client import (
|
||||||
fakes as fake_client)
|
fakes as fake_client)
|
||||||
from cinder.tests.unit.volume.drivers.netapp.dataontap import fakes as fake
|
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.dataontap.client import client_cmode
|
||||||
from cinder.volume.drivers.netapp import utils as netapp_utils
|
from cinder.volume.drivers.netapp import utils as netapp_utils
|
||||||
|
|
||||||
|
@ -44,9 +43,6 @@ class NetAppCmodeClientTestCase(test.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(NetAppCmodeClientTestCase, self).setUp()
|
super(NetAppCmodeClientTestCase, self).setUp()
|
||||||
|
|
||||||
# Inject fake netapp_lib module classes.
|
|
||||||
netapp_api.mock_netapp_lib([client_cmode])
|
|
||||||
|
|
||||||
with mock.patch.object(client_cmode.Client,
|
with mock.patch.object(client_cmode.Client,
|
||||||
'get_ontapi_version',
|
'get_ontapi_version',
|
||||||
return_value=(1, 20)):
|
return_value=(1, 20)):
|
||||||
|
|
|
@ -15,8 +15,7 @@
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
from cinder.tests.unit.volume.drivers.netapp.dataontap.client import (
|
from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
|
||||||
fake_api as netapp_api)
|
|
||||||
from cinder.volume.drivers.netapp.dataontap import ssc_cmode
|
from cinder.volume.drivers.netapp.dataontap import ssc_cmode
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -25,14 +25,13 @@ import mock
|
||||||
|
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder import test
|
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 \
|
import cinder.tests.unit.volume.drivers.netapp.dataontap.client.fakes \
|
||||||
as client_fakes
|
as client_fakes
|
||||||
import cinder.tests.unit.volume.drivers.netapp.dataontap.fakes as fake
|
import cinder.tests.unit.volume.drivers.netapp.dataontap.fakes as fake
|
||||||
import cinder.tests.unit.volume.drivers.netapp.fakes as na_fakes
|
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_7mode
|
||||||
from cinder.volume.drivers.netapp.dataontap import block_base
|
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.dataontap.client import client_base
|
||||||
from cinder.volume.drivers.netapp import utils as na_utils
|
from cinder.volume.drivers.netapp import utils as na_utils
|
||||||
|
|
||||||
|
@ -44,9 +43,6 @@ class NetAppBlockStorage7modeLibraryTestCase(test.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(NetAppBlockStorage7modeLibraryTestCase, self).setUp()
|
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()}
|
kwargs = {'configuration': self.get_config_7mode()}
|
||||||
self.library = block_7mode.NetAppBlockStorage7modeLibrary(
|
self.library = block_7mode.NetAppBlockStorage7modeLibrary(
|
||||||
'driver', 'protocol', **kwargs)
|
'driver', 'protocol', **kwargs)
|
||||||
|
|
|
@ -29,11 +29,10 @@ from oslo_utils import units
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
from cinder import test
|
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.dataontap import fakes as fake
|
||||||
import cinder.tests.unit.volume.drivers.netapp.fakes as na_fakes
|
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_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.drivers.netapp import utils as na_utils
|
||||||
from cinder.volume import utils as volume_utils
|
from cinder.volume import utils as volume_utils
|
||||||
|
|
||||||
|
@ -43,9 +42,6 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(NetAppBlockStorageLibraryTestCase, self).setUp()
|
super(NetAppBlockStorageLibraryTestCase, self).setUp()
|
||||||
|
|
||||||
# Inject fake netapp_lib module classes.
|
|
||||||
netapp_api.mock_netapp_lib([block_base])
|
|
||||||
|
|
||||||
kwargs = {'configuration': self.get_config_base()}
|
kwargs = {'configuration': self.get_config_base()}
|
||||||
self.library = block_base.NetAppBlockStorageLibrary(
|
self.library = block_base.NetAppBlockStorageLibrary(
|
||||||
'driver', 'protocol', **kwargs)
|
'driver', 'protocol', **kwargs)
|
||||||
|
|
|
@ -23,12 +23,11 @@ from oslo_service import loopingcall
|
||||||
|
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder import test
|
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.dataontap.fakes as fake
|
||||||
import cinder.tests.unit.volume.drivers.netapp.fakes as na_fakes
|
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_base
|
||||||
from cinder.volume.drivers.netapp.dataontap import block_cmode
|
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.client import client_base
|
||||||
from cinder.volume.drivers.netapp.dataontap import ssc_cmode
|
from cinder.volume.drivers.netapp.dataontap import ssc_cmode
|
||||||
from cinder.volume.drivers.netapp import utils as na_utils
|
from cinder.volume.drivers.netapp import utils as na_utils
|
||||||
|
@ -41,9 +40,6 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(NetAppBlockStorageCmodeLibraryTestCase, self).setUp()
|
super(NetAppBlockStorageCmodeLibraryTestCase, self).setUp()
|
||||||
|
|
||||||
# Inject fake netapp_lib module classes.
|
|
||||||
netapp_api.mock_netapp_lib([block_cmode])
|
|
||||||
|
|
||||||
kwargs = {'configuration': self.get_config_cmode()}
|
kwargs = {'configuration': self.get_config_cmode()}
|
||||||
self.library = block_cmode.NetAppBlockStorageCmodeLibrary(
|
self.library = block_cmode.NetAppBlockStorageCmodeLibrary(
|
||||||
'driver', 'protocol', **kwargs)
|
'driver', 'protocol', **kwargs)
|
||||||
|
|
|
@ -24,11 +24,10 @@ from oslo_utils import units
|
||||||
|
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder import test
|
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.dataontap import fakes as fake
|
||||||
from cinder.tests.unit.volume.drivers.netapp import fakes as na_fakes
|
from cinder.tests.unit.volume.drivers.netapp import fakes as na_fakes
|
||||||
from cinder import utils
|
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.client import client_cmode
|
||||||
from cinder.volume.drivers.netapp.dataontap import nfs_base
|
from cinder.volume.drivers.netapp.dataontap import nfs_base
|
||||||
from cinder.volume.drivers.netapp.dataontap import nfs_cmode
|
from cinder.volume.drivers.netapp.dataontap import nfs_cmode
|
||||||
|
|
|
@ -26,13 +26,6 @@ from cinder.volume.drivers.netapp.eseries import utils
|
||||||
import cinder.volume.drivers.netapp.options as na_opts
|
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 = {
|
MULTIATTACH_HOST_GROUP = {
|
||||||
'clusterRef': '8500000060080E500023C7340036035F515B78FC',
|
'clusterRef': '8500000060080E500023C7340036035F515B78FC',
|
||||||
'label': utils.MULTI_ATTACH_HOST_GROUP_NAME,
|
'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_ENDPOINT_HTTPS = 'https://host:8443/endpoint'
|
||||||
|
|
||||||
|
FAKE_INVOC_MSG = 'success'
|
||||||
|
|
||||||
FAKE_CLIENT_PARAMS = {
|
FAKE_CLIENT_PARAMS = {
|
||||||
'scheme': 'http',
|
'scheme': 'http',
|
||||||
'host': '127.0.0.1',
|
'host': '127.0.0.1',
|
||||||
|
|
|
@ -41,13 +41,10 @@ class NetAppEseriesClientDriverTestCase(test.TestCase):
|
||||||
self.mock_object(client, 'LOG', self.mock_log)
|
self.mock_object(client, 'LOG', self.mock_log)
|
||||||
self.fake_password = 'mysecret'
|
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',
|
self.my_client = client.RestClient('http', 'host', '80', '/test',
|
||||||
'user', self.fake_password,
|
'user', self.fake_password,
|
||||||
system_id='fake_sys_id')
|
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 = mock.Mock()
|
||||||
fake_response.status_code = 200
|
fake_response.status_code = 200
|
||||||
|
@ -67,8 +64,8 @@ class NetAppEseriesClientDriverTestCase(test.TestCase):
|
||||||
fake_resp.status_code = status_code
|
fake_resp.status_code = status_code
|
||||||
expected_msg = "Response error code - %s." % status_code
|
expected_msg = "Response error code - %s." % status_code
|
||||||
|
|
||||||
with self.assertRaisesRegexp(es_exception.WebServiceException,
|
with self.assertRaisesRegex(es_exception.WebServiceException,
|
||||||
expected_msg) as exc:
|
expected_msg) as exc:
|
||||||
self.my_client._eval_response(fake_resp)
|
self.my_client._eval_response(fake_resp)
|
||||||
|
|
||||||
self.assertEqual(status_code, exc.status_code)
|
self.assertEqual(status_code, exc.status_code)
|
||||||
|
@ -81,8 +78,8 @@ class NetAppEseriesClientDriverTestCase(test.TestCase):
|
||||||
fake_resp.text = resp_text
|
fake_resp.text = resp_text
|
||||||
expected_msg = "Response error - %s." % resp_text
|
expected_msg = "Response error - %s." % resp_text
|
||||||
|
|
||||||
with self.assertRaisesRegexp(es_exception.WebServiceException,
|
with self.assertRaisesRegex(es_exception.WebServiceException,
|
||||||
expected_msg) as exc:
|
expected_msg) as exc:
|
||||||
self.my_client._eval_response(fake_resp)
|
self.my_client._eval_response(fake_resp)
|
||||||
|
|
||||||
self.assertEqual(status_code, exc.status_code)
|
self.assertEqual(status_code, exc.status_code)
|
||||||
|
@ -413,7 +410,7 @@ class NetAppEseriesClientDriverTestCase(test.TestCase):
|
||||||
client.RestClient, '_get_resource_url',
|
client.RestClient, '_get_resource_url',
|
||||||
mock.Mock(return_value=eseries_fake.FAKE_RESOURCE_URL))
|
mock.Mock(return_value=eseries_fake.FAKE_RESOURCE_URL))
|
||||||
self.mock_object(
|
self.mock_object(
|
||||||
self.my_client.client, 'invoke_service',
|
self.my_client, 'invoke_service',
|
||||||
mock.Mock(return_value=fake_invoke_service))
|
mock.Mock(return_value=fake_invoke_service))
|
||||||
|
|
||||||
eseries_info = client.RestClient.get_eseries_api_info(
|
eseries_info = client.RestClient.get_eseries_api_info(
|
||||||
|
@ -732,3 +729,42 @@ class NetAppEseriesClientDriverTestCase(test.TestCase):
|
||||||
client.RestClient._init_features(self.my_client)
|
client.RestClient._init_features(self.my_client)
|
||||||
|
|
||||||
self.assertTrue(self.my_client.features.SSC_API_V2.supported)
|
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)
|
||||||
|
|
|
@ -35,7 +35,6 @@ class NetAppDriverFactoryTestCase(test.TestCase):
|
||||||
mock.Mock(return_value='fake_info'))
|
mock.Mock(return_value='fake_info'))
|
||||||
mock_create_driver = self.mock_object(na_common.NetAppDriver,
|
mock_create_driver = self.mock_object(na_common.NetAppDriver,
|
||||||
'create_driver')
|
'create_driver')
|
||||||
mock_check_netapp_lib = self.mock_object(na_utils, 'check_netapp_lib')
|
|
||||||
|
|
||||||
config = na_fakes.create_configuration()
|
config = na_fakes.create_configuration()
|
||||||
config.netapp_storage_family = 'fake_family'
|
config.netapp_storage_family = 'fake_family'
|
||||||
|
@ -47,7 +46,6 @@ class NetAppDriverFactoryTestCase(test.TestCase):
|
||||||
kwargs['app_version'] = 'fake_info'
|
kwargs['app_version'] = 'fake_info'
|
||||||
mock_create_driver.assert_called_with('fake_family', 'fake_protocol',
|
mock_create_driver.assert_called_with('fake_family', 'fake_protocol',
|
||||||
*(), **kwargs)
|
*(), **kwargs)
|
||||||
mock_check_netapp_lib.assert_called_once_with()
|
|
||||||
|
|
||||||
def test_new_missing_config(self):
|
def test_new_missing_config(self):
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,6 @@ import platform
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
from oslo_concurrency import processutils as putils
|
from oslo_concurrency import processutils as putils
|
||||||
from oslo_utils import importutils
|
|
||||||
|
|
||||||
from cinder import context
|
from cinder import context
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
|
@ -64,21 +63,6 @@ class NetAppDriverUtilsTestCase(test.TestCase):
|
||||||
setattr(configuration, 'flag2', 'value2')
|
setattr(configuration, 'flag2', 'value2')
|
||||||
self.assertIsNone(na_utils.check_flags(required_flags, configuration))
|
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):
|
def test_to_bool(self):
|
||||||
self.assertTrue(na_utils.to_bool(True))
|
self.assertTrue(na_utils.to_bool(True))
|
||||||
self.assertTrue(na_utils.to_bool('true'))
|
self.assertTrue(na_utils.to_bool('true'))
|
||||||
|
|
|
@ -73,7 +73,6 @@ class NetAppDriver(driver.ProxyVD):
|
||||||
|
|
||||||
config.append_config_values(options.netapp_proxy_opts)
|
config.append_config_values(options.netapp_proxy_opts)
|
||||||
na_utils.check_flags(NetAppDriver.REQUIRED_FLAGS, config)
|
na_utils.check_flags(NetAppDriver.REQUIRED_FLAGS, config)
|
||||||
na_utils.check_netapp_lib()
|
|
||||||
|
|
||||||
app_version = na_utils.OpenStackInfo().info()
|
app_version = na_utils.OpenStackInfo().info()
|
||||||
LOG.info(_LI('OpenStack OS Version Info: %(info)s'),
|
LOG.info(_LI('OpenStack OS Version Info: %(info)s'),
|
||||||
|
|
|
@ -29,22 +29,18 @@ import uuid
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_log import versionutils
|
from oslo_log import versionutils
|
||||||
from oslo_utils import excutils
|
from oslo_utils import excutils
|
||||||
from oslo_utils import importutils
|
|
||||||
from oslo_utils import units
|
from oslo_utils import units
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _, _LE, _LI, _LW
|
from cinder.i18n import _, _LE, _LI, _LW
|
||||||
from cinder import utils
|
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 options as na_opts
|
||||||
from cinder.volume.drivers.netapp import utils as na_utils
|
from cinder.volume.drivers.netapp import utils as na_utils
|
||||||
from cinder.volume import utils as volume_utils
|
from cinder.volume import utils as volume_utils
|
||||||
from cinder.zonemanager import utils as fczm_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__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
|
@ -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("<", "<")
|
||||||
|
text = text.replace(">", ">")
|
||||||
|
return text
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_node_with_children(node, **children):
|
||||||
|
"""Creates and returns named node with children."""
|
||||||
|
parent = NaElement(node)
|
||||||
|
for child in children.keys():
|
||||||
|
parent.add_new_child(child, children.get(child, None))
|
||||||
|
return parent
|
||||||
|
|
||||||
|
def add_node_with_children(self, node, **children):
|
||||||
|
"""Creates named node with children."""
|
||||||
|
parent = NaElement.create_node_with_children(node, **children)
|
||||||
|
self.add_child_elem(parent)
|
||||||
|
|
||||||
|
def to_string(self, pretty=False, method='xml', encoding='UTF-8'):
|
||||||
|
"""Prints the element to string."""
|
||||||
|
return etree.tostring(self._element, method=method, encoding=encoding,
|
||||||
|
pretty_print=pretty)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.to_string(pretty=True)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return str(self)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
"""Dict getter method for NaElement.
|
||||||
|
|
||||||
|
Returns NaElement list if present,
|
||||||
|
text value in case no NaElement node
|
||||||
|
children or attribute value if present.
|
||||||
|
"""
|
||||||
|
|
||||||
|
child = self.get_child_by_name(key)
|
||||||
|
if child:
|
||||||
|
if child.get_children():
|
||||||
|
return child
|
||||||
|
else:
|
||||||
|
return child.get_content()
|
||||||
|
elif self.has_attr(key):
|
||||||
|
return self.get_attr(key)
|
||||||
|
raise KeyError(_('No element by given name %s.') % (key))
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
"""Dict setter method for NaElement.
|
||||||
|
|
||||||
|
Accepts dict, list, tuple, str, int, float and long as valid value.
|
||||||
|
"""
|
||||||
|
if key:
|
||||||
|
if value:
|
||||||
|
if isinstance(value, NaElement):
|
||||||
|
child = NaElement(key)
|
||||||
|
child.add_child_elem(value)
|
||||||
|
self.add_child_elem(child)
|
||||||
|
elif isinstance(value, (str, int, float, long)):
|
||||||
|
self.add_new_child(key, six.text_type(value))
|
||||||
|
elif isinstance(value, (list, tuple, dict)):
|
||||||
|
child = NaElement(key)
|
||||||
|
child.translate_struct(value)
|
||||||
|
self.add_child_elem(child)
|
||||||
|
else:
|
||||||
|
raise TypeError(_('Not a valid value for NaElement.'))
|
||||||
|
else:
|
||||||
|
self.add_child_elem(NaElement(key))
|
||||||
|
else:
|
||||||
|
raise KeyError(_('NaElement name cannot be null.'))
|
||||||
|
|
||||||
|
def translate_struct(self, data_struct):
|
||||||
|
"""Convert list, tuple, dict to NaElement and appends.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
1.
|
||||||
|
<root>
|
||||||
|
<elem1>vl1</elem1>
|
||||||
|
<elem2>vl2</elem2>
|
||||||
|
<elem3>vl3</elem3>
|
||||||
|
</root>
|
||||||
|
The above can be achieved by doing
|
||||||
|
root = NaElement('root')
|
||||||
|
root.translate_struct({'elem1': 'vl1', 'elem2': 'vl2',
|
||||||
|
'elem3': 'vl3'})
|
||||||
|
2.
|
||||||
|
<root>
|
||||||
|
<elem1>vl1</elem1>
|
||||||
|
<elem2>vl2</elem2>
|
||||||
|
<elem1>vl3</elem1>
|
||||||
|
</root>
|
||||||
|
The above can be achieved by doing
|
||||||
|
root = NaElement('root')
|
||||||
|
root.translate_struct([{'elem1': 'vl1', 'elem2': 'vl2'},
|
||||||
|
{'elem1': 'vl3'}])
|
||||||
|
"""
|
||||||
|
if isinstance(data_struct, (list, tuple)):
|
||||||
|
for el in data_struct:
|
||||||
|
if isinstance(el, (list, tuple, dict)):
|
||||||
|
self.translate_struct(el)
|
||||||
|
else:
|
||||||
|
self.add_child_elem(NaElement(el))
|
||||||
|
elif isinstance(data_struct, dict):
|
||||||
|
for k in data_struct.keys():
|
||||||
|
child = NaElement(k)
|
||||||
|
if isinstance(data_struct[k], (dict, list, tuple)):
|
||||||
|
child.translate_struct(data_struct[k])
|
||||||
|
else:
|
||||||
|
if data_struct[k]:
|
||||||
|
child.set_content(six.text_type(data_struct[k]))
|
||||||
|
self.add_child_elem(child)
|
||||||
|
else:
|
||||||
|
raise ValueError(_('Type cannot be converted into NaElement.'))
|
||||||
|
|
||||||
|
|
||||||
|
class NaApiError(Exception):
|
||||||
|
"""Base exception class for NetApp API errors."""
|
||||||
|
|
||||||
|
def __init__(self, code='unknown', message='unknown'):
|
||||||
|
self.code = code
|
||||||
|
self.message = message
|
||||||
|
|
||||||
|
def __str__(self, *args, **kwargs):
|
||||||
|
return 'NetApp API failed. Reason - %s:%s' % (self.code, self.message)
|
||||||
|
|
||||||
|
|
||||||
|
NaErrors = {'API_NOT_FOUND': NaApiError('13005', 'Unable to find API'),
|
||||||
|
'INSUFFICIENT_PRIVS': NaApiError('13003',
|
||||||
|
'Insufficient privileges')}
|
||||||
|
|
||||||
|
|
||||||
|
def invoke_api(na_server, api_name, api_family='cm', query=None,
|
||||||
|
des_result=None, additional_elems=None,
|
||||||
|
is_iter=False, records=0, tag=None,
|
||||||
|
timeout=0, tunnel=None):
|
||||||
|
"""Invokes any given API call to a NetApp server.
|
||||||
|
|
||||||
|
:param na_server: na_server instance
|
||||||
|
:param api_name: API name string
|
||||||
|
:param api_family: cm or 7m
|
||||||
|
:param query: API query as dict
|
||||||
|
:param des_result: desired result as dict
|
||||||
|
:param additional_elems: dict other than query and des_result
|
||||||
|
:param is_iter: is iterator API
|
||||||
|
:param records: limit for records, 0 for infinite
|
||||||
|
:param timeout: timeout seconds
|
||||||
|
:param tunnel: tunnel entity, vserver or vfiler name
|
||||||
|
"""
|
||||||
|
record_step = 50
|
||||||
|
if not (na_server or isinstance(na_server, NaServer)):
|
||||||
|
msg = _("Requires an NaServer instance.")
|
||||||
|
raise exception.InvalidInput(reason=msg)
|
||||||
|
server = copy.copy(na_server)
|
||||||
|
if api_family == 'cm':
|
||||||
|
server.set_vserver(tunnel)
|
||||||
|
else:
|
||||||
|
server.set_vfiler(tunnel)
|
||||||
|
if timeout > 0:
|
||||||
|
server.set_timeout(timeout)
|
||||||
|
iter_records = 0
|
||||||
|
cond = True
|
||||||
|
while cond:
|
||||||
|
na_element = create_api_request(
|
||||||
|
api_name, query, des_result, additional_elems,
|
||||||
|
is_iter, record_step, tag)
|
||||||
|
result = server.invoke_successfully(na_element, True)
|
||||||
|
if is_iter:
|
||||||
|
if records > 0:
|
||||||
|
iter_records = iter_records + record_step
|
||||||
|
if iter_records >= records:
|
||||||
|
cond = False
|
||||||
|
tag_el = result.get_child_by_name('next-tag')
|
||||||
|
tag = tag_el.get_content() if tag_el else None
|
||||||
|
if not tag:
|
||||||
|
cond = False
|
||||||
|
else:
|
||||||
|
cond = False
|
||||||
|
yield result
|
||||||
|
|
||||||
|
|
||||||
|
def create_api_request(api_name, query=None, des_result=None,
|
||||||
|
additional_elems=None, is_iter=False,
|
||||||
|
record_step=50, tag=None):
|
||||||
|
"""Creates a NetApp API request.
|
||||||
|
|
||||||
|
:param api_name: API name string
|
||||||
|
:param query: API query as dict
|
||||||
|
:param des_result: desired result as dict
|
||||||
|
:param additional_elems: dict other than query and des_result
|
||||||
|
:param is_iter: is iterator API
|
||||||
|
:param record_step: records at a time for iter API
|
||||||
|
:param tag: next tag for iter API
|
||||||
|
"""
|
||||||
|
api_el = NaElement(api_name)
|
||||||
|
if query:
|
||||||
|
query_el = NaElement('query')
|
||||||
|
query_el.translate_struct(query)
|
||||||
|
api_el.add_child_elem(query_el)
|
||||||
|
if des_result:
|
||||||
|
res_el = NaElement('desired-attributes')
|
||||||
|
res_el.translate_struct(des_result)
|
||||||
|
api_el.add_child_elem(res_el)
|
||||||
|
if additional_elems:
|
||||||
|
api_el.translate_struct(additional_elems)
|
||||||
|
if is_iter:
|
||||||
|
api_el.add_new_child('max-records', six.text_type(record_step))
|
||||||
|
if tag:
|
||||||
|
api_el.add_new_child('tag', tag, True)
|
||||||
|
return api_el
|
|
@ -19,18 +19,14 @@ import math
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import importutils
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _, _LW
|
from cinder.i18n import _, _LW
|
||||||
from cinder import utils
|
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.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__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
|
@ -20,19 +20,15 @@ import sys
|
||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import excutils
|
from oslo_utils import excutils
|
||||||
from oslo_utils import importutils
|
|
||||||
from oslo_utils import timeutils
|
from oslo_utils import timeutils
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from cinder.i18n import _LE, _LW, _LI
|
from cinder.i18n import _LE, _LW, _LI
|
||||||
from cinder import utils
|
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
|
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__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
|
@ -19,20 +19,15 @@ import copy
|
||||||
import math
|
import math
|
||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import importutils
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _, _LW
|
from cinder.i18n import _, _LW
|
||||||
from cinder import utils
|
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.dataontap.client import client_base
|
||||||
from cinder.volume.drivers.netapp import utils as na_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.zapi import errors as netapp_error
|
|
||||||
from netapp_lib.api.zapi import zapi as netapp_api
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
DELETED_PREFIX = 'deleted_cinder_'
|
DELETED_PREFIX = 'deleted_cinder_'
|
||||||
|
@ -528,8 +523,10 @@ class Client(client_base.Client):
|
||||||
self.connection.invoke_successfully(na_el)
|
self.connection.invoke_successfully(na_el)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if isinstance(e, netapp_api.NaApiError):
|
if isinstance(e, netapp_api.NaApiError):
|
||||||
if(e.code == netapp_error.EAPINOTFOUND
|
if (e.code == netapp_api.NaErrors
|
||||||
or e.code == netapp_error.EAPIPRIVILEGE):
|
['API_NOT_FOUND'].code or
|
||||||
|
e.code == netapp_api.NaErrors
|
||||||
|
['INSUFFICIENT_PRIVS'].code):
|
||||||
failed_apis.append(api_name)
|
failed_apis.append(api_name)
|
||||||
elif major == 1 and minor >= 20:
|
elif major == 1 and minor >= 20:
|
||||||
failed_apis = copy.copy(api_list)
|
failed_apis = copy.copy(api_list)
|
||||||
|
|
|
@ -24,19 +24,15 @@ import copy
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import importutils
|
|
||||||
from oslo_utils import timeutils
|
from oslo_utils import timeutils
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _, _LI, _LW
|
from cinder.i18n import _, _LI, _LW
|
||||||
from cinder import utils
|
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
|
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__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
|
@ -26,26 +26,72 @@ import json
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import importutils
|
import requests
|
||||||
import six
|
import six
|
||||||
from six.moves import urllib
|
from six.moves import urllib
|
||||||
|
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
|
from cinder.i18n import _LE
|
||||||
import cinder.utils as cinder_utils
|
import cinder.utils as cinder_utils
|
||||||
from cinder.volume.drivers.netapp.eseries import exception as es_exception
|
from cinder.volume.drivers.netapp.eseries import exception as es_exception
|
||||||
from cinder.volume.drivers.netapp.eseries import utils
|
from cinder.volume.drivers.netapp.eseries import utils
|
||||||
from cinder.volume.drivers.netapp import utils as na_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__)
|
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."""
|
"""REST client specific to e-series storage service."""
|
||||||
|
|
||||||
ID = 'id'
|
ID = 'id'
|
||||||
|
@ -66,11 +112,11 @@ class RestClient(object):
|
||||||
def __init__(self, scheme, host, port, service_path, username,
|
def __init__(self, scheme, host, port, service_path, username,
|
||||||
password, **kwargs):
|
password, **kwargs):
|
||||||
|
|
||||||
|
super(RestClient, self).__init__(scheme, host, port, service_path,
|
||||||
|
username, password, **kwargs)
|
||||||
|
|
||||||
kwargs = kwargs or {}
|
kwargs = kwargs or {}
|
||||||
self.client = netapp_restclient.WebserviceClient(scheme, host, port,
|
|
||||||
service_path,
|
|
||||||
username, password,
|
|
||||||
**kwargs)
|
|
||||||
self._system_id = kwargs.get('system_id')
|
self._system_id = kwargs.get('system_id')
|
||||||
self._content_type = kwargs.get('content_type') or 'json'
|
self._content_type = kwargs.get('content_type') or 'json'
|
||||||
|
|
||||||
|
@ -149,9 +195,9 @@ class RestClient(object):
|
||||||
raise exception.NotFound(_('Storage system id not set.'))
|
raise exception.NotFound(_('Storage system id not set.'))
|
||||||
kwargs['system-id'] = self._system_id
|
kwargs['system-id'] = self._system_id
|
||||||
path = path.format(**kwargs)
|
path = path.format(**kwargs)
|
||||||
if not self.client._endpoint.endswith('/'):
|
if not self._endpoint.endswith('/'):
|
||||||
self.client._endpoint = '%s/' % self.client._endpoint
|
self._endpoint = '%s/' % self._endpoint
|
||||||
return urllib.parse.urljoin(self.client._endpoint, path.lstrip('/'))
|
return urllib.parse.urljoin(self._endpoint, path.lstrip('/'))
|
||||||
|
|
||||||
def _invoke(self, method, path, data=None, use_system=True,
|
def _invoke(self, method, path, data=None, use_system=True,
|
||||||
timeout=None, verify=False, **kwargs):
|
timeout=None, verify=False, **kwargs):
|
||||||
|
@ -163,9 +209,9 @@ class RestClient(object):
|
||||||
if cinder_utils.TRACE_API:
|
if cinder_utils.TRACE_API:
|
||||||
self._log_http_request(method, url, headers, data)
|
self._log_http_request(method, url, headers, data)
|
||||||
data = json.dumps(data) if data else None
|
data = json.dumps(data) if data else None
|
||||||
res = self.client.invoke_service(method, url, data=data,
|
res = self.invoke_service(method, url, data=data,
|
||||||
headers=headers,
|
headers=headers,
|
||||||
timeout=timeout, verify=verify)
|
timeout=timeout, verify=verify)
|
||||||
res_dict = res.json() if res.text else None
|
res_dict = res.json() if res.text else None
|
||||||
|
|
||||||
if cinder_utils.TRACE_API:
|
if cinder_utils.TRACE_API:
|
||||||
|
@ -664,9 +710,9 @@ class RestClient(object):
|
||||||
'Accept': 'application/json'}
|
'Accept': 'application/json'}
|
||||||
url = self._get_resource_url(path, True).replace(
|
url = self._get_resource_url(path, True).replace(
|
||||||
'/devmgr/v2', '', 1)
|
'/devmgr/v2', '', 1)
|
||||||
result = self.client.invoke_service(method='GET', url=url,
|
result = self.invoke_service(method='GET', url=url,
|
||||||
headers=headers,
|
headers=headers,
|
||||||
verify=verify)
|
verify=verify)
|
||||||
about_response_dict = result.json()
|
about_response_dict = result.json()
|
||||||
mode_is_proxy = about_response_dict['runningAsProxy']
|
mode_is_proxy = about_response_dict['runningAsProxy']
|
||||||
if mode_is_proxy:
|
if mode_is_proxy:
|
||||||
|
|
|
@ -29,7 +29,6 @@ import socket
|
||||||
|
|
||||||
from oslo_concurrency import processutils as putils
|
from oslo_concurrency import processutils as putils
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import importutils
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from cinder import context
|
from cinder import context
|
||||||
|
@ -76,14 +75,6 @@ def check_flags(required_flags, configuration):
|
||||||
raise exception.InvalidInput(reason=msg)
|
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):
|
def to_bool(val):
|
||||||
"""Converts true, yes, y, 1 to True, False otherwise."""
|
"""Converts true, yes, y, 1 to True, False otherwise."""
|
||||||
if val:
|
if val:
|
||||||
|
|
Loading…
Reference in New Issue