Merge "Update NetApp cDOT Manila drivers to use netapp_lib"
This commit is contained in:
commit
57b9d45f4a
manila
share/drivers/netapp
tests/share/drivers/netapp
@ -67,6 +67,7 @@ class NetAppDriver(object):
|
||||
config.append_config_values(driver.share_opts)
|
||||
config.append_config_values(options.netapp_proxy_opts)
|
||||
na_utils.check_flags(NetAppDriver.REQUIRED_FLAGS, config)
|
||||
na_utils.check_netapp_lib()
|
||||
|
||||
app_version = na_utils.OpenStackInfo().info()
|
||||
LOG.info(_LI('OpenStack OS Version Info: %s'), app_version)
|
||||
|
@ -1,623 +0,0 @@
|
||||
# Copyright (c) 2014 Navneet Singh. All rights reserved.
|
||||
# Copyright (c) 2014 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.
|
||||
"""
|
||||
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
|
||||
import six
|
||||
from six.moves import urllib
|
||||
|
||||
from manila import exception
|
||||
from manila.i18n import _
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
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'
|
||||
|
||||
|
||||
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, trace=False):
|
||||
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._trace = trace
|
||||
self._refresh_conn = True
|
||||
self._trace = trace
|
||||
|
||||
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 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 set_trace(self, trace=True):
|
||||
"""Enable or disable the API tracing facility."""
|
||||
self._trace = trace
|
||||
|
||||
def invoke_elem(self, na_element, enable_tunneling=False):
|
||||
"""Invoke the API on the server."""
|
||||
if na_element and not isinstance(na_element, NaElement):
|
||||
ValueError('NaElement must be supplied to invoke API')
|
||||
|
||||
request, request_element = self._create_request(na_element,
|
||||
enable_tunneling)
|
||||
|
||||
if self._trace:
|
||||
LOG.debug("Request: %s", request_element.to_string(pretty=True))
|
||||
|
||||
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 as e:
|
||||
raise NaApiError('Unexpected error', e)
|
||||
|
||||
response_xml = response.read()
|
||||
response_element = self._get_result(response_xml)
|
||||
|
||||
if self._trace:
|
||||
LOG.debug("Response: %s", response_element.to_string(pretty=True))
|
||||
|
||||
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 __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,
|
||||
six.string_types + six.integer_types + (float, )):
|
||||
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)
|
||||
|
||||
|
||||
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
|
@ -15,11 +15,15 @@
|
||||
|
||||
from oslo_log import log
|
||||
from oslo_utils import excutils
|
||||
from oslo_utils import importutils
|
||||
|
||||
from manila.i18n import _LE
|
||||
from manila.share.drivers.netapp.dataontap.client import api as netapp_api
|
||||
from manila.share.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 = log.getLogger(__name__)
|
||||
|
||||
@ -41,21 +45,16 @@ class NetAppBaseClient(object):
|
||||
if cached:
|
||||
return self.connection.get_api_version()
|
||||
|
||||
ontapi_version = netapp_api.NaElement('system-get-ontapi-version')
|
||||
res = self.connection.invoke_successfully(ontapi_version, False)
|
||||
major = res.get_child_content('major-version')
|
||||
minor = res.get_child_content('minor-version')
|
||||
result = self.send_request('system-get-ontapi-version',
|
||||
enable_tunneling=False)
|
||||
major = result.get_child_content('major-version')
|
||||
minor = result.get_child_content('minor-version')
|
||||
return major, minor
|
||||
|
||||
def _init_features(self):
|
||||
"""Set up the repository of available Data ONTAP features."""
|
||||
self.features = Features()
|
||||
|
||||
def check_is_naelement(self, elem):
|
||||
"""Checks if object is instance of NaElement."""
|
||||
if not isinstance(elem, netapp_api.NaElement):
|
||||
raise ValueError('Expects NaElement')
|
||||
|
||||
def send_request(self, api_name, api_args=None, enable_tunneling=True):
|
||||
"""Sends request to Ontapi."""
|
||||
request = netapp_api.NaElement(api_name)
|
||||
|
@ -20,16 +20,21 @@ import hashlib
|
||||
import time
|
||||
|
||||
from oslo_log import log
|
||||
from oslo_utils import importutils
|
||||
from oslo_utils import strutils
|
||||
from oslo_utils import units
|
||||
import six
|
||||
|
||||
from manila import exception
|
||||
from manila.i18n import _, _LE, _LW
|
||||
from manila.share.drivers.netapp.dataontap.client import api as netapp_api
|
||||
from manila.share.drivers.netapp.dataontap.client import client_base
|
||||
from manila.share.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 = log.getLogger(__name__)
|
||||
DELETED_PREFIX = 'deleted_manila_'
|
||||
@ -212,7 +217,7 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
try:
|
||||
vserver_client.offline_volume(root_volume_name)
|
||||
except netapp_api.NaApiError as e:
|
||||
if e.code == netapp_api.EVOLUMEOFFLINE:
|
||||
if e.code == netapp_error.EVOLUMEOFFLINE:
|
||||
LOG.error(_LE("Volume %s is already offline."),
|
||||
root_volume_name)
|
||||
else:
|
||||
@ -241,7 +246,7 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
try:
|
||||
vserver_client.send_request('cifs-server-delete', api_args)
|
||||
except netapp_api.NaApiError as e:
|
||||
if e.code == netapp_api.EOBJECTNOTFOUND:
|
||||
if e.code == netapp_error.EOBJECTNOTFOUND:
|
||||
LOG.error(_LE('CIFS server does not exist for '
|
||||
'Vserver %s.'), vserver_name)
|
||||
else:
|
||||
@ -403,7 +408,7 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
}
|
||||
self.send_request('net-vlan-create', api_args)
|
||||
except netapp_api.NaApiError as e:
|
||||
if e.code == netapp_api.EDUPLICATEENTRY:
|
||||
if e.code == netapp_error.EDUPLICATEENTRY:
|
||||
LOG.debug('VLAN %(vlan)s already exists on port %(port)s',
|
||||
{'vlan': vlan, 'port': port})
|
||||
else:
|
||||
@ -496,7 +501,7 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
}
|
||||
self.send_request('net-port-broadcast-domain-add-ports', api_args)
|
||||
except netapp_api.NaApiError as e:
|
||||
if e.code == (netapp_api.
|
||||
if e.code == (netapp_error.
|
||||
E_VIFMGR_PORT_ALREADY_ASSIGNED_TO_BROADCAST_DOMAIN):
|
||||
LOG.debug('Port %(port)s already exists in broadcast domain '
|
||||
'%(domain)s', {'port': port, 'domain': domain})
|
||||
@ -820,7 +825,7 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
try:
|
||||
self.send_request('kerberos-realm-create', api_args)
|
||||
except netapp_api.NaApiError as e:
|
||||
if e.code == netapp_api.EDUPLICATEENTRY:
|
||||
if e.code == netapp_error.EDUPLICATEENTRY:
|
||||
LOG.debug('Kerberos realm config already exists.')
|
||||
else:
|
||||
msg = _('Failed to create Kerberos realm. %s')
|
||||
@ -870,7 +875,7 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
try:
|
||||
self.send_request('net-dns-create', api_args)
|
||||
except netapp_api.NaApiError as e:
|
||||
if e.code == netapp_api.EDUPLICATEENTRY:
|
||||
if e.code == netapp_error.EDUPLICATEENTRY:
|
||||
LOG.error(_LE("DNS exists for Vserver."))
|
||||
else:
|
||||
msg = _("Failed to configure DNS. %s")
|
||||
@ -1068,7 +1073,7 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
try:
|
||||
self.send_request('volume-offline', {'name': volume_name})
|
||||
except netapp_api.NaApiError as e:
|
||||
if e.code == netapp_api.EVOLUMEOFFLINE:
|
||||
if e.code == netapp_error.EVOLUMEOFFLINE:
|
||||
return
|
||||
raise
|
||||
|
||||
@ -1082,7 +1087,7 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
try:
|
||||
self.send_request('volume-unmount', api_args)
|
||||
except netapp_api.NaApiError as e:
|
||||
if e.code == netapp_api.EVOL_NOT_MOUNTED:
|
||||
if e.code == netapp_error.EVOL_NOT_MOUNTED:
|
||||
return
|
||||
raise
|
||||
|
||||
@ -1109,7 +1114,7 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
LOG.debug('Volume %s unmounted.', volume_name)
|
||||
return
|
||||
except netapp_api.NaApiError as e:
|
||||
if e.code == netapp_api.EAPIERROR and 'job ID' in e.message:
|
||||
if e.code == netapp_error.EAPIERROR and 'job ID' in e.message:
|
||||
msg = _LW('Could not unmount volume %(volume)s due to '
|
||||
'ongoing volume operation: %(exception)s')
|
||||
msg_args = {'volume': volume_name, 'exception': e}
|
||||
@ -1173,7 +1178,7 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
'code': error_code,
|
||||
'reason': error_reason
|
||||
}
|
||||
if error_code == netapp_api.ESNAPSHOTNOTALLOWED:
|
||||
if error_code == netapp_error.ESNAPSHOTNOTALLOWED:
|
||||
raise exception.SnapshotUnavailable(msg % msg_args)
|
||||
else:
|
||||
raise exception.NetAppException(msg % msg_args)
|
||||
@ -1327,7 +1332,7 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
try:
|
||||
self.send_request('export-rule-destroy', api_args)
|
||||
except netapp_api.NaApiError as e:
|
||||
if e.code != netapp_api.EOBJECTNOTFOUND:
|
||||
if e.code != netapp_error.EOBJECTNOTFOUND:
|
||||
raise
|
||||
|
||||
@na_utils.trace
|
||||
@ -1397,7 +1402,7 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
try:
|
||||
self.send_request('export-policy-create', api_args)
|
||||
except netapp_api.NaApiError as e:
|
||||
if e.code != netapp_api.EDUPLICATEENTRY:
|
||||
if e.code != netapp_error.EDUPLICATEENTRY:
|
||||
raise
|
||||
|
||||
@na_utils.trace
|
||||
@ -1416,7 +1421,7 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
try:
|
||||
self.send_request('export-policy-destroy', api_args)
|
||||
except netapp_api.NaApiError as e:
|
||||
if e.code == netapp_api.EOBJECTNOTFOUND:
|
||||
if e.code == netapp_error.EOBJECTNOTFOUND:
|
||||
return
|
||||
raise
|
||||
|
||||
@ -1585,7 +1590,7 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
# API succeeded, so definitely a cluster management LIF
|
||||
return True
|
||||
except netapp_api.NaApiError as e:
|
||||
if e.code == netapp_api.EAPINOTFOUND:
|
||||
if e.code == netapp_error.EAPINOTFOUND:
|
||||
LOG.debug('Not connected to cluster management LIF.')
|
||||
return False
|
||||
else:
|
||||
|
@ -18,13 +18,17 @@ NetApp CIFS protocol helper class.
|
||||
import re
|
||||
|
||||
from oslo_log import log
|
||||
from oslo_utils import importutils
|
||||
|
||||
from manila import exception
|
||||
from manila.i18n import _, _LE
|
||||
from manila.share.drivers.netapp.dataontap.client import api as netapp_api
|
||||
from manila.share.drivers.netapp.dataontap.protocols import base
|
||||
from manila.share.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 = log.getLogger(__name__)
|
||||
|
||||
@ -58,7 +62,7 @@ class NetAppCmodeCIFSHelper(base.NetAppBaseHelper):
|
||||
try:
|
||||
self._client.add_cifs_share_access(share_name, access['access_to'])
|
||||
except netapp_api.NaApiError as e:
|
||||
if e.code == netapp_api.EDUPLICATEENTRY:
|
||||
if e.code == netapp_error.EDUPLICATEENTRY:
|
||||
# Duplicate entry, so use specific exception.
|
||||
raise exception.ShareAccessExists(
|
||||
access_type=access['access_type'], access=access)
|
||||
@ -72,9 +76,9 @@ class NetAppCmodeCIFSHelper(base.NetAppBaseHelper):
|
||||
try:
|
||||
self._client.remove_cifs_share_access(share_name, user_name)
|
||||
except netapp_api.NaApiError as e:
|
||||
if e.code == netapp_api.EONTAPI_EINVAL:
|
||||
if e.code == netapp_error.EONTAPI_EINVAL:
|
||||
LOG.error(_LE("User %s does not exist."), user_name)
|
||||
elif e.code == netapp_api.EOBJECTNOTFOUND:
|
||||
elif e.code == netapp_error.EOBJECTNOTFOUND:
|
||||
LOG.error(_LE("Rule %s does not exist."), user_name)
|
||||
else:
|
||||
raise e
|
||||
|
@ -21,6 +21,7 @@ import platform
|
||||
|
||||
from oslo_concurrency import processutils as putils
|
||||
from oslo_log import log
|
||||
from oslo_utils import importutils
|
||||
import six
|
||||
|
||||
from manila import exception
|
||||
@ -102,6 +103,14 @@ def convert_to_list(value):
|
||||
return [value]
|
||||
|
||||
|
||||
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.NetAppException(msg)
|
||||
|
||||
|
||||
class OpenStackInfo(object):
|
||||
"""OS/distribution, release, and version.
|
||||
|
||||
|
232
manila/tests/share/drivers/netapp/dataontap/client/fake_api.py
Normal file
232
manila/tests/share/drivers/netapp/dataontap/client/fake_api.py
Normal file
@ -0,0 +1,232 @@
|
||||
# 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 manila 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.ManilaException):
|
||||
"""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 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.')
|
@ -1,156 +0,0 @@
|
||||
# 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
|
||||
"""
|
||||
|
||||
from manila.share.drivers.netapp.dataontap.client import api
|
||||
from manila import test
|
||||
|
||||
|
||||
class NetAppApiElementTransTests(test.TestCase):
|
||||
"""Test case for NetApp API element translations."""
|
||||
|
||||
def test_translate_struct_dict_unique_key(self):
|
||||
"""Tests if dict gets properly converted to NaElements."""
|
||||
root = api.NaElement('root')
|
||||
child = {'e1': 'v1', 'e2': 'v2', 'e3': 'v3'}
|
||||
|
||||
root.translate_struct(child)
|
||||
|
||||
self.assertEqual(3, len(root.get_children()))
|
||||
for key, value in child.items():
|
||||
self.assertEqual(value, root.get_child_content(key))
|
||||
|
||||
def test_translate_struct_dict_nonunique_key(self):
|
||||
"""Tests if list/dict gets properly converted to NaElements."""
|
||||
root = api.NaElement('root')
|
||||
child = [{'e1': 'v1', 'e2': 'v2'}, {'e1': 'v3'}]
|
||||
|
||||
root.translate_struct(child)
|
||||
|
||||
children = root.get_children()
|
||||
self.assertEqual(3, len(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 = 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 = 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 = 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."""
|
||||
update = dict(e1='v1', e2='1', e3='2.0', e4='8')
|
||||
root = api.NaElement('root')
|
||||
|
||||
for key, value in update.items():
|
||||
root[key] = value
|
||||
|
||||
for key, value in update.items():
|
||||
self.assertEqual(value, root.get_child_content(key))
|
||||
|
||||
def test_setter_na_element(self):
|
||||
"""Tests na_element gets appended as child."""
|
||||
root = api.NaElement('root')
|
||||
root['e1'] = api.NaElement('nested')
|
||||
self.assertEqual(1, len(root.get_children()))
|
||||
e1 = root.get_child_by_name('e1')
|
||||
self.assertIsInstance(e1, api.NaElement)
|
||||
self.assertIsInstance(e1.get_child_by_name('nested'), api.NaElement)
|
||||
|
||||
def test_setter_child_dict(self):
|
||||
"""Tests dict is appended as child to root."""
|
||||
root = api.NaElement('root')
|
||||
root['d'] = {'e1': 'v1', 'e2': 'v2'}
|
||||
e1 = root.get_child_by_name('d')
|
||||
self.assertIsInstance(e1, 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 = api.NaElement('root')
|
||||
|
||||
root['l'] = ['l1', 'l2']
|
||||
root['t'] = ('t1', 't2')
|
||||
|
||||
l = root.get_child_by_name('l')
|
||||
self.assertIsInstance(l, api.NaElement)
|
||||
t = root.get_child_by_name('t')
|
||||
self.assertIsInstance(t, api.NaElement)
|
||||
|
||||
self.assertEqual(2, len(l.get_children()))
|
||||
for le in l.get_children():
|
||||
self.assertIn(le.get_name(), ['l1', 'l2'])
|
||||
|
||||
self.assertEqual(2, len(t.get_children()))
|
||||
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 = api.NaElement('root')
|
||||
root['k'] = None
|
||||
self.assertIsNone(root.get_child_content('k'))
|
||||
|
||||
def test_setter_invalid_value(self):
|
||||
"""Tests invalid value raises exception."""
|
||||
self.assertRaises(TypeError,
|
||||
api.NaElement('root').__setitem__,
|
||||
'k',
|
||||
api.NaServer('localhost'))
|
||||
|
||||
def test_setter_invalid_key(self):
|
||||
"""Tests invalid value raises exception."""
|
||||
self.assertRaises(KeyError,
|
||||
api.NaElement('root').__setitem__,
|
||||
None,
|
||||
'value')
|
@ -17,9 +17,10 @@ import ddt
|
||||
import mock
|
||||
from oslo_log import log
|
||||
|
||||
from manila.share.drivers.netapp.dataontap.client import api as netapp_api
|
||||
from manila.share.drivers.netapp.dataontap.client import client_base
|
||||
from manila import test
|
||||
from manila.tests.share.drivers.netapp.dataontap.client import fake_api \
|
||||
as netapp_api
|
||||
from manila.tests.share.drivers.netapp.dataontap.client import fakes as fake
|
||||
|
||||
|
||||
@ -34,6 +35,9 @@ class NetAppBaseClientTestCase(test.TestCase):
|
||||
'error',
|
||||
mock.Mock(side_effect=mock_logger.error))
|
||||
|
||||
# Inject fake netapp_lib module classes.
|
||||
netapp_api.mock_netapp_lib([client_base])
|
||||
|
||||
self.client = client_base.NetAppBaseClient(**fake.CONNECTION_INFO)
|
||||
self.client.connection = mock.MagicMock()
|
||||
self.connection = self.client.connection
|
||||
@ -63,13 +67,6 @@ class NetAppBaseClientTestCase(test.TestCase):
|
||||
|
||||
self.assertSetEqual(set(), self.client.features.defined_features)
|
||||
|
||||
def test_check_is_naelement(self):
|
||||
|
||||
element = netapp_api.NaElement('name')
|
||||
|
||||
self.assertIsNone(self.client.check_is_naelement(element))
|
||||
self.assertRaises(ValueError, self.client.check_is_naelement, None)
|
||||
|
||||
def test_send_request(self):
|
||||
|
||||
element = netapp_api.NaElement('fake-api')
|
||||
|
@ -24,10 +24,11 @@ from oslo_log import log
|
||||
import six
|
||||
|
||||
from manila import exception
|
||||
from manila.share.drivers.netapp.dataontap.client import api as netapp_api
|
||||
from manila.share.drivers.netapp.dataontap.client import client_base
|
||||
from manila.share.drivers.netapp.dataontap.client import client_cmode
|
||||
from manila import test
|
||||
from manila.tests.share.drivers.netapp.dataontap.client import fake_api \
|
||||
as netapp_api
|
||||
from manila.tests.share.drivers.netapp.dataontap.client import fakes as fake
|
||||
|
||||
|
||||
@ -53,6 +54,9 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||
'get_ontapi_version',
|
||||
mock.Mock(return_value=(1, 20)))
|
||||
|
||||
# Inject fake netapp_lib module classes.
|
||||
netapp_api.mock_netapp_lib([client_base, client_cmode])
|
||||
|
||||
self.client = client_cmode.NetAppCmodeClient(**fake.CONNECTION_INFO)
|
||||
self.client.connection = mock.MagicMock()
|
||||
|
||||
@ -653,8 +657,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||
|
||||
self.mock_object(self.client,
|
||||
'send_request',
|
||||
self._mock_api_error(
|
||||
code=netapp_api.EDUPLICATEENTRY))
|
||||
self._mock_api_error(code=netapp_api.EDUPLICATEENTRY))
|
||||
|
||||
vlan_create_args = {
|
||||
'vlan-info': {
|
||||
@ -1466,8 +1469,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||
|
||||
self.mock_object(self.client,
|
||||
'send_request',
|
||||
self._mock_api_error(
|
||||
code=netapp_api.EDUPLICATEENTRY))
|
||||
self._mock_api_error(code=netapp_api.EDUPLICATEENTRY))
|
||||
|
||||
self.client.create_kerberos_realm(fake.KERBEROS_SECURITY_SERVICE)
|
||||
|
||||
@ -1595,8 +1597,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||
|
||||
self.mock_object(self.client,
|
||||
'send_request',
|
||||
self._mock_api_error(
|
||||
code=netapp_api.EDUPLICATEENTRY))
|
||||
self._mock_api_error(code=netapp_api.EDUPLICATEENTRY))
|
||||
|
||||
self.client.configure_dns(fake.KERBEROS_SECURITY_SERVICE)
|
||||
|
||||
|
@ -23,11 +23,12 @@ from oslo_log import log
|
||||
|
||||
from manila import context
|
||||
from manila import exception
|
||||
from manila.share.drivers.netapp.dataontap.client import api as netapp_api
|
||||
from manila.share.drivers.netapp.dataontap.cluster_mode import lib_base
|
||||
from manila.share.drivers.netapp.dataontap.cluster_mode import lib_multi_svm
|
||||
from manila.share.drivers.netapp import utils as na_utils
|
||||
from manila import test
|
||||
from manila.tests.share.drivers.netapp.dataontap.client \
|
||||
import fake_api as netapp_api
|
||||
from manila.tests.share.drivers.netapp.dataontap import fakes as fake
|
||||
|
||||
|
||||
@ -53,6 +54,9 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
'app_version': fake.APP_VERSION
|
||||
}
|
||||
|
||||
# Inject fake netapp_lib module classes.
|
||||
netapp_api.mock_netapp_lib([lib_multi_svm])
|
||||
|
||||
self.library = lib_multi_svm.NetAppCmodeMultiSVMFileStorageLibrary(
|
||||
fake.DRIVER_NAME, **kwargs)
|
||||
self.library._client = mock.Mock()
|
||||
|
@ -15,7 +15,7 @@
|
||||
Mock unit tests for the NetApp driver protocols base class module.
|
||||
"""
|
||||
|
||||
from manila.share.drivers.netapp.dataontap.protocols import cifs_cmode
|
||||
from manila.share.drivers.netapp.dataontap.protocols import nfs_cmode
|
||||
from manila import test
|
||||
|
||||
|
||||
@ -24,7 +24,7 @@ class NetAppNASHelperBaseTestCase(test.TestCase):
|
||||
def test_set_client(self):
|
||||
# The base class is abstract, so we'll use a subclass to test
|
||||
# base class functionality.
|
||||
helper = cifs_cmode.NetAppCmodeCIFSHelper()
|
||||
helper = nfs_cmode.NetAppCmodeNFSHelper()
|
||||
self.assertIsNone(helper._client)
|
||||
|
||||
helper.set_client('fake_client')
|
||||
|
@ -20,9 +20,10 @@ import mock
|
||||
from oslo_log import log
|
||||
|
||||
from manila import exception
|
||||
from manila.share.drivers.netapp.dataontap.client import api as netapp_api
|
||||
from manila.share.drivers.netapp.dataontap.protocols import cifs_cmode
|
||||
from manila import test
|
||||
from manila.tests.share.drivers.netapp.dataontap.client \
|
||||
import fake_api as netapp_api
|
||||
from manila.tests.share.drivers.netapp.dataontap.protocols \
|
||||
import fakes as fake
|
||||
|
||||
@ -41,6 +42,9 @@ class NetAppClusteredCIFSHelperTestCase(test.TestCase):
|
||||
|
||||
self.mock_context = mock.Mock()
|
||||
|
||||
# Inject fake netapp_lib module classes.
|
||||
netapp_api.mock_netapp_lib([cifs_cmode])
|
||||
|
||||
self.mock_client = mock.Mock()
|
||||
self.helper = cifs_cmode.NetAppCmodeCIFSHelper()
|
||||
self.helper.set_client(self.mock_client)
|
||||
|
@ -37,6 +37,8 @@ class NetAppDriverFactoryTestCase(test.TestCase):
|
||||
mock.Mock(return_value='fake_mode'))
|
||||
mock_create_driver = self.mock_object(na_common.NetAppDriver,
|
||||
'_create_driver')
|
||||
mock_check_netapp_lib = self.mock_object(na_utils,
|
||||
'check_netapp_lib')
|
||||
|
||||
config = na_fakes.create_configuration()
|
||||
config.netapp_storage_family = 'fake_family'
|
||||
@ -49,6 +51,7 @@ class NetAppDriverFactoryTestCase(test.TestCase):
|
||||
mock_get_driver_mode.assert_called_once_with('fake_family', True)
|
||||
mock_create_driver.assert_called_once_with('fake_family', 'fake_mode',
|
||||
*(), **kwargs)
|
||||
mock_check_netapp_lib.assert_called_once_with()
|
||||
|
||||
def test_new_missing_config(self):
|
||||
|
||||
|
@ -21,6 +21,7 @@ import platform
|
||||
import mock
|
||||
from oslo_concurrency import processutils as putils
|
||||
from oslo_log import log
|
||||
from oslo_utils import importutils
|
||||
|
||||
from manila import exception
|
||||
from manila.share.drivers.netapp import utils as na_utils
|
||||
@ -130,6 +131,21 @@ class NetAppDriverUtilsTestCase(test.TestCase):
|
||||
sorted(na_utils.convert_to_list({'key1': 'value1',
|
||||
'key2': 'value2'})))
|
||||
|
||||
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.NetAppException,
|
||||
na_utils.check_netapp_lib)
|
||||
|
||||
|
||||
class OpenstackInfoTestCase(test.TestCase):
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user