Added Cmode driver

Added new module NetAppClusteredShareDriver.
Implements bp add-netapp-clustered-share-driver

Change-Id: Ia35445d77f69e9a17560543fdbe1faa1db85b326
This commit is contained in:
Yulia Portnova 2014-03-18 14:52:21 +02:00
parent 6849850945
commit d41c35947c
11 changed files with 2265 additions and 1176 deletions

View File

@ -1097,7 +1097,7 @@ def share_data_get_for_project(context, project_id, user_id, session=None):
else:
result = query.first()
return (result[0] or 0, result[1] or 0)
return (result[1] or 0, result[2] or 0)
@require_context
@ -1259,7 +1259,7 @@ def snapshot_data_get_for_project(context, project_id, user_id, session=None):
else:
result = query.first()
return (result[0] or 0, result[1] or 0)
return (result[1] or 0, result[2] or 0)
@require_context

View File

@ -541,3 +541,7 @@ class BridgeDoesNotExist(ManilaException):
class ServiceInstanceException(ManilaException):
message = _("Exception in service instance manager occurred.")
class NetAppException(ManilaException):
message = _("Exception due to NetApp failure.")

View File

@ -1,15 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 Openstack Foundation
# 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.

View File

@ -1,4 +1,4 @@
# Copyright 2012 NetApp
# Copyright (c) 2014 NetApp, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -12,116 +12,489 @@
# 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 suds
from suds.sax import text
"""
NetApp api for ONTAP and OnCommand DFM.
Contains classes required to issue api calls to ONTAP and OnCommand DFM.
"""
from lxml import etree
import urllib2
from manila import exception
from manila.openstack.common import log
from oslo.config import cfg
LOG = log.getLogger(__name__)
CONF = cfg.CONF
URL_FILER = 'servlets/netapp.servlets.admin.XMLrequest_filer'
NETAPP_NS = 'http://www.netapp.com/filer/admin'
class NetAppApiClient(object):
"""Wrapper around DFM commands."""
class NaServer(object):
"""Encapsulates server connection logic."""
REQUIRED_FLAGS = ['netapp_nas_wsdl_url',
'netapp_nas_login',
'netapp_nas_password',
'netapp_nas_server_hostname',
'netapp_nas_server_port']
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, configuration):
self.configuration = configuration
self._client = None
def __init__(self, host, server_type=SERVER_TYPE_FILER,
transport_type=TRANSPORT_TYPE_HTTP,
style=STYLE_LOGIN_PASSWORD, username=None,
password=None):
self._host = host
self.set_server_type(server_type)
self.set_transport_type(transport_type)
self.set_style(style)
self._username = username
self._password = password
self._refresh_conn = True
def do_setup(self):
"""Setup suds (web services) client."""
protocol = 'https' if self.configuration.netapp_nas_server_secure \
else 'http'
soap_url = ('%s://%s:%s/apis/soap/v1' %
(protocol,
self.configuration.netapp_nas_server_hostname,
self.configuration.netapp_nas_server_port))
def get_transport_type(self):
"""Get the transport type protocol."""
return self._protocol
self._client = \
suds.client.Client(self.configuration.netapp_nas_wsdl_url,
username=self.configuration.netapp_nas_login,
password=self.configuration.netapp_nas_password,
location=soap_url)
def set_transport_type(self, transport_type):
"""Set the transport type protocol for api.
LOG.info('NetApp RPC client started')
def send_request_to(self, target, request, xml_args=None,
do_response_check=True):
Supports http and https transport types.
"""
Sends RPC :request: to :target:.
:param target: IP address, ID or network name of OnTap device
:param request: API name
:param xml_args: call arguments
:param do_response_check: if set to True and RPC call has failed,
raises exception.
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.
"""
client = self._client
srv = client.service
if style.lower() not in (NaServer.STYLE_LOGIN_PASSWORD,
NaServer.STYLE_CERTIFICATE):
raise ValueError('Unsupported authentication style')
self._auth_style = style.lower()
rpc = client.factory.create('Request')
rpc.Name = request
rpc.Args = text.Raw(xml_args)
response = srv.ApiProxy(Request=rpc, Target=target)
def get_server_type(self):
"""Get the target server type."""
return self._server_type
if do_response_check:
_check_response(rpc, response)
def set_server_type(self, server_type):
"""Set the target server type.
return response
def get_available_aggregates(self):
"""Returns list of aggregates known by DFM."""
srv = self._client.service
resp = srv.AggregateListInfoIterStart()
tag = resp.Tag
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:
avail_aggrs = srv.AggregateListInfoIterNext(Tag=tag,
Maximum=resp.Records)
finally:
srv.AggregateListInfoIterEnd(tag)
self._api_major_version = int(major)
self._api_minor_version = int(minor)
self._api_version = str(major) + "." + str(minor)
except ValueError:
raise ValueError('Major and minor versions must be integers')
self._refresh_conn = True
return avail_aggrs
def get_host_ip_by(self, host_id):
"""Returns IP address of a host known by DFM."""
if (type(host_id) is str or type(host_id) is unicode) and \
len(host_id.split('.')) == 4:
# already IP
return host_id
client = self._client
srv = client.service
filer_filter = client.factory.create('HostListInfoIterStart')
filer_filter.ObjectNameOrId = host_id
resp = srv.HostListInfoIterStart(HostListInfoIterStart=filer_filter)
tag = resp.Tag
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:
filers = srv.HostListInfoIterNext(Tag=tag, Maximum=resp.Records)
finally:
srv.HostListInfoIterEnd(Tag=tag)
int(port)
except ValueError:
raise ValueError('Port must be integer')
self._port = str(port)
self._refresh_conn = True
ip = None
for host in filers.Hosts.HostInfo:
if int(host.HostId) == int(host_id):
ip = host.HostAddress
def get_port(self):
"""Get the server communication port."""
return self._port
return ip
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 na_element and not isinstance(na_element, NaElement):
ValueError('NaElement must be supplied to invoke api')
request = 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 urllib2.HTTPError as e:
raise NaApiError(e.code, e.msg)
except Exception as e:
raise NaApiError('Unexpected error', e)
xml = response.read()
return self._get_result(xml)
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'
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 = urllib2.Request(
self._get_url(), data=request_d,
headers={'Content-Type': 'text/xml', 'charset': 'utf-8'})
return request
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 = urllib2.build_opener(auth_handler)
self._opener = opener
def _create_basic_auth_handler(self):
password_man = urllib2.HTTPPasswordMgrWithDefaultRealm()
password_man.add_password(None, self._get_url(), self._username,
self._password)
auth_handler = urllib2.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 check_configuration(config_object):
"""Ensure that the flags we care about are set."""
for flag in NetAppApiClient.REQUIRED_FLAGS:
if not getattr(config_object, flag, None):
raise exception.Error(_('%s is not set') % flag)
def _convert_entity_refs(text):
"""Converts entity refs to chars to handle etree auto conversions."""
text = text.replace("&lt;", "<")
text = text.replace("&gt;", ">")
return text
@staticmethod
def create_node_with_children(node, **children):
"""Creates and returns named node with children."""
parent = NaElement(node)
for child in children.keys():
parent.add_new_child(child, children.get(child, None))
return parent
def add_node_with_children(self, node, **children):
"""Creates named node with children."""
parent = NaElement.create_node_with_children(node, **children)
self.add_child_elem(parent)
def to_string(self, pretty=False, method='xml', encoding='UTF-8'):
"""Prints the element to string."""
return etree.tostring(self._element, method=method, encoding=encoding,
pretty_print=pretty)
def __getitem__(self, key):
"""Dict getter method for NaElement.
Returns NaElement list if present,
text value in case no NaElement node
children or attribute value if present.
"""
child = self.get_child_by_name(key)
if child:
if child.get_children():
return child
else:
return child.get_content()
elif self.has_attr(key):
return self.get_attr(key)
raise KeyError(_('No element by given name %s.') % (key))
def __setitem__(self, key, value):
"""Dict setter method for NaElement.
Accepts dict, list, tuple, str, int, float and long as valid value.
"""
if key:
if value:
if isinstance(value, NaElement):
child = NaElement(key)
child.add_child_elem(value)
self.add_child_elem(child)
elif isinstance(value, (str, int, float, long)):
self.add_new_child(key, str(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(str(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)

View File

@ -0,0 +1,717 @@
# Copyright (c) 2014 NetApp, Inc.
# 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 specific NAS storage driver. Supports NFS and CIFS protocols.
This driver requires ONTAP Cluster mode storage system
with installed CIFS and NFS licenses.
"""
import hashlib
import os
import re
from oslo.config import cfg
from manila import exception
from manila.openstack.common import excutils
from manila.openstack.common import log
from manila.share.drivers.netapp import api as naapi
from manila.share.drivers.netapp import driver
from manila import utils
NETAPP_NAS_OPTS = [
cfg.StrOpt('netapp_vserver_name_template',
default='os_%(net_id)s',
help='Name template to use for new vserver.'),
cfg.StrOpt('netapp_lif_name_template',
default='os_%(net_allocation_id)s',
help='Lif name template'),
cfg.StrOpt('netapp_aggregate_name_search_pattern',
default='(.*)',
help='Pattern for searching available aggregates'
' for provisioning.'),
cfg.StrOpt('netapp_root_volume_aggregate',
help='Name of aggregate to create root volume on.'),
cfg.StrOpt('netapp_root_volume_name',
default='root',
help='Root volume name.')
]
CONF = cfg.CONF
CONF.register_opts(NETAPP_NAS_OPTS)
LOG = log.getLogger(__name__)
class NetAppClusteredShareDriver(driver.NetAppShareDriver):
"""
NetApp specific ONTAP C-mode driver.
Supports NFS and CIFS protocols.
Uses Ontap devices as backend to create shares
and snapshots.
Sets up vServer for each share_network.
Connectivity between storage and client VM is organized
by plugging vServer's network interfaces into neutron subnet
that VM is using.
"""
def __init__(self, db, *args, **kwargs):
super(NetAppClusteredShareDriver, self).__init__(db, *args, **kwargs)
if self.configuration:
self.configuration.append_config_values(NETAPP_NAS_OPTS)
self.api_version = (1, 15)
def do_setup(self, context):
"""Prepare once the driver.
Called once by the manager after the driver is loaded.
Sets up clients, check licenses, sets up protocol
specific helpers.
"""
self._client = driver.NetAppApiClient(self.api_version,
configuration=self.configuration)
self._setup_helpers()
def check_for_setup_error(self):
"""Raises error if prerequisites are not met."""
self._check_licenses()
def setup_network(self, network_info, metadata=None):
"""Creates and configures new vserver."""
LOG.debug(_('Configuring network %s') % network_info['id'])
self._vserver_create_if_not_exists(network_info)
def _get_cluster_nodes(self):
"""Get all available cluster nodes."""
response = self._client.send_request('system-node-get-iter')
nodes_info_list = response.get_child_by_name('attributes-list')\
.get_children() if response.get_child_by_name('attributes-list') \
else []
nodes = [node_info.get_child_content('node') for node_info
in nodes_info_list]
return nodes
def _get_node_data_port(self, node):
"""Get data port on the node."""
args = {'query': {'net-port-info': {'node': node,
'port-type': 'physical',
'role': 'data'}}}
port_info = self._client.send_request('net-port-get-iter', args)
try:
port = port_info.get_child_by_name('attributes-list')\
.get_child_by_name('net-port-info')\
.get_child_content('port')
except AttributeError:
msg = _("Data port does not exists for node %s") % node
LOG.error(msg)
raise exception.NetAppException(msg)
return port
def _create_vserver(self, vserver_name):
"""Creates new vserver and assigns aggregates."""
create_args = {'vserver-name': vserver_name,
'root-volume-security-style': 'unix',
'root-volume-aggregate':
self.configuration.netapp_root_volume_aggregate,
'root-volume':
self.configuration.netapp_root_volume_name,
'name-server-switch': {'nsswitch': 'file'}}
self._client.send_request('vserver-create', create_args)
aggr_list = self._find_match_aggregates()
modify_args = {'aggr-list': aggr_list,
'vserver-name': vserver_name}
self._client.send_request('vserver-modify', modify_args)
def _find_match_aggregates(self):
"""Find all aggregates match pattern."""
pattern = self.configuration.netapp_aggregate_name_search_pattern
try:
aggrs = self._client.send_request('aggr-get-iter')\
.get_child_by_name('attributes-list').get_children()
except AttributeError:
msg = _("Have not found aggregates match pattern %s")\
% pattern
LOG.error(msg)
raise exception.NetAppException(msg)
aggr_list = [
{'aggr-name': aggr} for aggr in
map(lambda x: x.get_child_content('aggregate-name'), aggrs)
if re.match(pattern, aggr)
]
return aggr_list
def get_network_allocations_number(self):
"""Get number of network interfaces to be created."""
return int(self._client.send_request(
'system-node-get-iter').get_child_content('num-records'))
def _delete_vserver(self, vserver_name, vserver_client):
"""Deletes vserver."""
vserver_client.send_request(
'volume-offline',
{'name': self.configuration.netapp_root_volume_name})
vserver_client.send_request(
'volume-destroy',
{'name': self.configuration.netapp_root_volume_name})
args = {'vserver-name': vserver_name}
self._client.send_request('vserver-destroy', args)
def _create_net_iface(self, ip, netmask, vlan, node, port, vserver_name,
allocation_id):
"""Creates lif on vlan port."""
vlan_iface_name = "%(port)s-%(tag)s" % {'port': port, 'tag': vlan}
try:
args = {
'vlan-info': {
'parent-interface': port,
'node': node,
'vlanid': vlan
}
}
self._client.send_request('net-vlan-create', args)
except naapi.NaApiError as e:
if e.code == '13130':
LOG.debug(_("Vlan %(vlan)s already exists on port %(port)s") %
{'vlan': vlan, 'port': port})
else:
raise exception.NetAppException(
_("Failed to create vlan %(vlan)s on "
"port %(port)s. %(err_msg)") %
{'vlan': vlan, 'port': port, 'err_msg': e.message})
iface_name = self.configuration.netapp_lif_name_template % \
{'node': node, 'net_allocation_id': allocation_id}
LOG.debug(_('Creating LIF %(lif)r for vserver %(vserver)s ')
% {'lif': iface_name, 'vserver': vserver_name})
args = {'address': ip,
'administrative-status': 'up',
'data-protocols': [
{'data-protocol': 'nfs'},
{'data-protocol': 'cifs'}
],
'home-node': node,
'home-port': vlan_iface_name,
'netmask': netmask,
'interface-name': iface_name,
'role': 'data',
'vserver': vserver_name,
}
self._client.send_request('net-interface-create', args)
def _delete_net_iface(self, iface_name):
"""Deletes lif."""
args = {'vserver': None,
'interface-name': iface_name}
self._client.send_request('net-interface-delete', args)
def _setup_helpers(self):
"""Initializes protocol-specific NAS drivers."""
self._helpers = {'CIFS': NetAppClusteredCIFSHelper(),
'NFS': NetAppClusteredNFSHelper()}
def _get_vserver_name(self, net_id):
return self.configuration.netapp_vserver_name_template \
% {'net_id': net_id}
def _vserver_create_if_not_exists(self, network_info):
"""Creates vserver if not exists with given parameters."""
vserver_name = self._get_vserver_name(network_info['id'])
vserver_client = driver.NetAppApiClient(
self.api_version, vserver=vserver_name,
configuration=self.configuration)
args = {'query': {'vserver-info': {'vserver-name': vserver_name}}}
LOG.debug(_('Checking if vserver is configured'))
vserver_info = self._client.send_request('vserver-get-iter', args)
if not int(vserver_info.get_child_content('num-records')):
LOG.debug(_('Vserver %s does not exist, creating') % vserver_name)
self._create_vserver(vserver_name)
nodes = self._get_cluster_nodes()
node_network_info = zip(nodes, network_info['network_allocations'])
netmask = utils.cidr_to_netmask(network_info['cidr'])
try:
for node, net_info in node_network_info:
port = self._get_node_data_port(node)
ip = net_info['ip_address']
self._create_lif_if_not_exists(
vserver_name, net_info['id'],
network_info['segmentation_id'], node, port,
ip, netmask, vserver_client)
except naapi.NaApiError:
with excutils.save_and_reraise_exception():
LOG.error(_("Failed to create network interface"))
self._delete_vserver(vserver_name, vserver_client)
self._enable_nfs(vserver_client)
if network_info.get('security_services'):
for security_service in network_info['security_services']:
if security_service['type'].lower() == "ldap":
self._configure_ldap(security_service, vserver_client)
elif security_service['type'].lower() == "active_directory":
self._configure_active_directory(security_service,
vserver_client)
elif security_service['type'].lower() == "kerberos":
self._configure_kerberos(vserver_name, security_service,
vserver_client)
else:
raise exception.NetAppException(
_('Unsupported protocol %s for NetApp driver')
% security_service['type'])
return vserver_name
def _enable_nfs(self, vserver_client):
"""Enables NFS on vserver."""
vserver_client.send_request('nfs-enable')
args = {'is-nfsv40-enabled': 'true'}
vserver_client.send_request('nfs-service-modify', args)
args = {
'client-match': '0.0.0.0/0',
'policy-name': 'default',
'ro-rule': {
'security-flavor': 'any'
},
'rw-rule': {
'security-flavor': 'any'
}
}
vserver_client.send_request('export-rule-create', args)
def _configure_ldap(self, data, vserver_client):
"""Configures LDAP on vserver."""
config_name = hashlib.md5(data['id']).hexdigest()
args = {'ldap-client-config': config_name,
'servers': {
'ip-address': data['server']
},
'tcp-port': '389',
'schema': 'RFC-2307',
'bind-password': data['password']}
vserver_client.send_request('ldap-client-create', args)
args = {'client-config': config_name,
'client-enabled': 'true'}
vserver_client.send_request('ldap-config-create', args)
def _configure_dns(self, data, vserver_client):
args = {
'domains': {
'string': data['domain']
},
'name-servers': {
'ip-address': data['dns_ip']
},
'dns-state': 'enabled'
}
try:
vserver_client.send_request('net-dns-create', args)
except naapi.NaApiError as e:
if e.code == '13130':
LOG.error(_("Dns exists for vserver"))
else:
raise exception.NetAppException(
_("Failed to configure DNS. %s") % e.message)
def _configure_kerberos(self, vserver, data, vserver_client):
"""Configures Kerberos for NFS on vServer."""
args = {'admin-server-ip': data['server'],
'admin-server-port': '749',
'clock-skew': '5',
'comment': '',
'config-name': data['id'],
'kdc-ip': data['server'],
'kdc-port': '88',
'kdc-vendor': 'other',
'password-server-ip': data['server'],
'password-server-port': '464',
'realm': data['domain'].upper()}
try:
self._client.send_request('kerberos-realm-create', args)
except naapi.NaApiError as e:
if e.code == '13130':
LOG.debug(_("Kerberos realm config already exists"))
else:
raise exception.NetAppException(
_("Failed to configure Kerberos. %s") % e.message)
self._configure_dns(data, vserver_client)
spn = 'nfs/' + vserver.replace('_', '-') + '.' + data['domain'] + '@'\
+ data['domain'].upper()
lifs = self._get_lifs(vserver_client)
if not lifs:
msg = _("Cannot set up kerberos. There are no lifs configured")
LOG.error(msg)
raise Exception(msg)
for lif_name in lifs:
args = {'admin-password': data['password'],
'admin-user-name': data['sid'],
'interface-name': lif_name,
'is-kerberos-enabled': 'true',
'service-principal-name': spn
}
vserver_client.send_request('kerberos-config-modify', args)
def _configure_active_directory(self, data, vserver_client):
"""Configures AD on vserver."""
self._configure_dns(data, vserver_client)
args = {'admin-username': data['sid'],
'admin-password': data['password'],
'force-account-overwrite': 'true',
'cifs-server': data['server'],
'domain': data['domain']}
try:
vserver_client.send_request('cifs-server-create', args)
except naapi.NaApiError as e:
if e.code == '13001':
LOG.debug(_("CIFS server entry already exists"))
else:
raise exception.NetAppException(
_("Failed to create CIFS server entry. %s") % e.message)
def _get_lifs(self, vserver_client):
lifs_info = vserver_client.send_request('net-interface-get-iter')
try:
lif_names = [lif.get_child_content('interface-name') for lif in
lifs_info.get_child_by_name('attributes-list')
.get_children()]
except AttributeError:
lif_names = []
return lif_names
def _create_lif_if_not_exists(self, vserver_name, allocation_id, vlan,
node, port, ip, netmask, vserver_client):
"""Creates lif for vserver."""
args = {
'query': {
'net-interface-info': {
'address': ip,
'home-node': node,
'home-port': port,
'netmask': netmask,
'vserver': vserver_name}
}
}
ifaces = vserver_client.send_request('net-interface-get-iter',
args)
if not ifaces.get_child_content('num_records') or \
ifaces.get_child_content('num_records') == '0':
self._create_net_iface(ip, netmask, vlan, node, port, vserver_name,
allocation_id)
def get_available_aggregates_for_vserver(self, vserver, vserver_client):
"""Returns aggregate list for the vserver."""
LOG.debug(_('Finding available aggreagates for vserver %s') % vserver)
response = vserver_client.send_request('vserver-get')
vserver_info = response.get_child_by_name('attributes')\
.get_child_by_name('vserver-info')
aggr_list_elements = vserver_info\
.get_child_by_name('vserver-aggr-info-list').get_children()
if not aggr_list_elements:
msg = _("No aggregate assigned to vserver %s")
raise exception.NetAppException(msg % vserver)
# return dict of key-value pair of aggr_name:si$
aggr_dict = {}
for aggr_elem in aggr_list_elements:
aggr_name = aggr_elem.get_child_content('aggr-name')
aggr_size = int(aggr_elem.get_child_content('aggr-availsize'))
aggr_dict[aggr_name] = aggr_size
LOG.debug(_("Found available aggregates: %r") % aggr_dict)
return aggr_dict
def create_share(self, context, share):
"""Creates new share."""
if not share.get('network_info'):
msg = _("Cannot create share %s. "
"No share network provided") % share['id']
LOG.error(msg)
raise exception.NetAppException(message=msg)
vserver = self._get_vserver_name(share['share_network_id'])
vserver_client = driver.NetAppApiClient(
self.api_version, vserver=vserver,
configuration=self.configuration)
self._allocate_container(share, vserver, vserver_client)
return self._create_export(share, vserver, vserver_client)
def create_share_from_snapshot(self, context, share, snapshot,
net_details=None):
"""Creates new share form snapshot."""
vserver = self._get_vserver_name(share['share_network_id'])
vserver_client = driver.NetAppApiClient(
self.api_version, vserver=vserver,
configuration=self.configuration)
self._allocate_container_from_snapshot(share, snapshot, vserver,
vserver_client)
return self._create_export(share, vserver, vserver_client)
def _allocate_container(self, share, vserver, vserver_client):
"""Create new share on aggregate."""
share_name = self._get_valid_share_name(share['id'])
aggregates = self.get_available_aggregates_for_vserver(vserver,
vserver_client)
aggregate = max(aggregates, key=lambda m: aggregates[m])
LOG.debug(_('Creating volume %(share_name)s on '
'aggregate %(aggregate)s')
% {'share_name': share_name, 'aggregate': aggregate})
args = {'containing-aggr-name': aggregate,
'size': str(share['size']) + 'g',
'volume': share_name,
'junction-path': '/%s' % share_name
}
vserver_client.send_request('volume-create', args)
def _allocate_container_from_snapshot(self, share, snapshot, vserver,
vserver_client):
"""Clones existing share."""
share_name = self._get_valid_share_name(share['id'])
parent_share_name = self._get_valid_share_name(snapshot['share_id'])
parent_snapshot_name = self._get_valid_snapshot_name(snapshot['id'])
LOG.debug(_('Creating volume from snapshot %s') % snapshot['id'])
args = {'volume': share_name,
'parent-volume': parent_share_name,
'parent-snapshot': parent_snapshot_name,
'junction-path': '/%s' % share_name
}
vserver_client.send_request('volume-clone-create', args)
def _share_exists(self, share_name, vserver_client):
args = {
'query': {
'volume-attributes': {
'volume-id-attributes': {
'name': share_name
}
}
}
}
response = vserver_client.send_request('volume-get-iter', args)
if int(response.get_child_content('num-records')):
return True
def _deallocate_container(self, share, vserver_client):
"""Free share space."""
self._share_unmount(share, vserver_client)
self._offline_share(share, vserver_client)
self._delete_share(share, vserver_client)
def _offline_share(self, share, vserver_client):
"""Sends share offline. Required before deleting a share."""
share_name = self._get_valid_share_name(share['id'])
args = {'name': share_name}
LOG.debug(_('Offline volume %s') % share_name)
vserver_client.send_request('volume-offline', args)
def _delete_share(self, share, vserver_client):
"""Destroys share on a target OnTap device."""
share_name = self._get_valid_share_name(share['id'])
args = {'name': share_name}
LOG.debug(_('Deleting share %s') % share_name)
vserver_client.send_request('volume-destroy', args)
def delete_share(self, context, share):
"""Deletes share."""
share_name = self._get_valid_share_name(share['id'])
vserver = self._get_vserver_name(share['share_network_id'])
vserver_client = driver.NetAppApiClient(
self.api_version, vserver=vserver,
configuration=self.configuration)
if self._share_exists(share_name, vserver_client):
self._remove_export(share, vserver_client)
self._deallocate_container(share, vserver_client)
else:
LOG.info(_("Share %s does not exists") % share['id'])
def _create_export(self, share, vserver, vserver_client):
"""Creates NAS storage."""
helper = self._get_helper(share)
helper.set_client(vserver_client)
share_name = self._get_valid_share_name(share['id'])
network_allocations = share['network_info']['network_allocations']
ip_address = network_allocations[0]['ip_address']
export_location = helper.create_share(share_name, ip_address)
return export_location
def create_snapshot(self, context, snapshot):
"""Creates a snapshot of a share."""
vserver = self._get_vserver_name(snapshot['share']['share_network_id'])
vserver_client = driver.NetAppApiClient(
self.api_version, vserver=vserver,
configuration=self.configuration)
share_name = self._get_valid_share_name(snapshot['share_id'])
snapshot_name = self._get_valid_snapshot_name(snapshot['id'])
args = {'volume': share_name,
'snapshot': snapshot_name}
LOG.debug(_('Creating snapshot %s') % snapshot_name)
vserver_client.send_request('snapshot-create', args)
def _remove_export(self, share, vserver_client):
"""Deletes NAS storage."""
helper = self._get_helper(share)
helper.set_client(vserver_client)
target = helper.get_target(share)
# share may be in error state, so there's no share and target
if target:
helper.delete_share(share)
def delete_snapshot(self, context, snapshot):
"""Deletes a snapshot of a share."""
vserver = self._get_vserver_name(snapshot['share']['share_network_id'])
vserver_client = driver.NetAppApiClient(
self.api_version, vserver=vserver,
configuration=self.configuration)
share_name = self._get_valid_share_name(snapshot['share_id'])
snapshot_name = self._get_valid_snapshot_name(snapshot['id'])
self._is_snapshot_busy(share_name, snapshot_name, vserver_client)
args = {'snapshot': snapshot_name,
'volume': share_name}
LOG.debug(_('Deleting snapshot %s') % snapshot_name)
vserver_client.send_request('snapshot-delete', args)
def _is_snapshot_busy(self, share_name, snapshot_name, vserver_client):
"""Raises ShareSnapshotIsBusy if snapshot is busy."""
args = {'volume': share_name}
snapshots = vserver_client.send_request('snapshot-list-info',
args)
for snap in snapshots.get_child_by_name('snapshots')\
.get_children():
if snap.get_child_by_name('name').get_content() == snapshot_name\
and snap.get_child_by_name('busy').get_content() == 'true':
return True
def _share_unmount(self, share, vserver_client):
"""Unmounts share (required before deleting)."""
share_name = self._get_valid_share_name(share['id'])
args = {'volume-name': share_name}
LOG.debug(_('Unmounting volume %s') % share_name)
vserver_client.send_request('volume-unmount', args)
def allow_access(self, context, share, access):
"""Allows access to a given NAS storage for IPs in access."""
vserver = self._get_vserver_name(share['share_network_id'])
vserver_client = driver.NetAppApiClient(
self.api_version, vserver=vserver,
configuration=self.configuration)
helper = self._get_helper(share)
helper.set_client(vserver_client)
return helper.allow_access(context, share, access)
def deny_access(self, context, share, access):
"""Denies access to a given NAS storage for IPs in access."""
vserver = self._get_vserver_name(share['share_network_id'])
vserver_client = driver.NetAppApiClient(
self.api_version, vserver=vserver,
configuration=self.configuration)
helper = self._get_helper(share)
helper.set_client(vserver_client)
return helper.deny_access(context, share, access)
class NetAppClusteredNFSHelper(driver.NetAppNFSHelper):
"""Netapp specific cluster-mode NFS sharing driver."""
def create_share(self, share_name, export_ip):
"""Creates NFS share."""
export_pathname = os.path.join('/', share_name)
self.add_rules(export_pathname, ['localhost'])
export_location = ':'.join([export_ip, export_pathname])
return export_location
def allow_access_by_sid(self, share, sid):
user, _x, group = sid.partition(':')
args = {
'attributes': {
'volume-attributes': {
'volume-security-attributes': {
'volume-security-unix-attributes': {
'user-id': user,
'group-id': group or 'root'
}
}
}
},
'query': {
'volume-attributes': {
'volume-id-attributes': {
'junction-path': self._get_export_path(share)
}
}
}
}
self._client.send_request('volume-modify-iter', args)
def deny_access_by_sid(self, share, sid):
args = {
'attributes': {
'volume-security-attributes': {
'volume-security-unix-attributes': {
'user': 'root'
}
}
},
'query': {
'volume-attributes': {
'volume-id-attributes': {
'junction-path': self._get_export_path(share)
}
}
}
}
self._client.send_request('volume-modify-iter', args)
class NetAppClusteredCIFSHelper(driver.NetAppCIFSHelper):
"""Netapp specific cluster-mode CIFS sharing driver."""
def create_share(self, share_name, export_ip):
self._add_share(share_name)
cifs_location = self._set_export_location(export_ip, share_name)
self._restrict_access('Everyone', share_name)
return cifs_location
def _add_share(self, share_name):
"""Creates CIFS share on target OnTap host."""
share_path = '/%s' % share_name
args = {'path': share_path,
'share-name': share_name}
self._client.send_request('cifs-share-create', args)
def delete_share(self, share):
"""Deletes CIFS storage."""
host_ip, share_name = self._get_export_location(share)
args = {'share-name': share_name}
self._client.send_request('cifs-share-delete', args)
def _allow_access_for(self, username, share_name):
"""Allows access to the CIFS share for a given user."""
args = {'permission': 'full_control',
'share': share_name,
'user-or-group': username}
self._client.send_request('cifs-share-access-control-create', args)
def _restrict_access(self, user_name, share_name):
args = {'user-or-group': user_name,
'share': share_name}
self._client.send_request('cifs-share-access-control-delete', args)

File diff suppressed because it is too large Load Diff

View File

@ -32,7 +32,6 @@ import nose.plugins.skip
from oslo.config import cfg
import stubout
from manila.common import config
from manila.openstack.common import importutils
from manila.openstack.common import log as logging
from manila.openstack.common import timeutils

View File

View File

@ -0,0 +1,275 @@
# Copyright (c) 2014 NetApp, Inc.
# 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 mock
from oslo.config import cfg
from manila import context
from manila import exception
from manila.share import configuration
from manila.share.drivers.netapp import api as naapi
from manila.share.drivers.netapp import driver
from manila import test
CONF = cfg.CONF
class NetApp7modeDrvTestCase(test.TestCase):
"""Tests for NetApp 7mode driver."""
def setUp(self):
super(NetApp7modeDrvTestCase, self).setUp()
self._context = context.get_admin_context()
self._db = mock.Mock()
self.driver = driver.NetAppShareDriver(
self._db, configuration=configuration.Configuration(None))
self.driver._client = mock.Mock()
self.driver._client.send_request = mock.Mock()
self.share = {'id': 'fake_uuid',
'tenant_id': 'fake_tenant_id',
'name': 'fake_name',
'size': 1,
'share_proto': 'fake'}
self.snapshot = {'id': 'fake_snapshot_uuid',
'tenant_id': 'fake_tenant_id',
'share_id': 'fake_share_id'}
self.helper = mock.Mock()
self.driver._helpers = {'FAKE': self.helper}
self.driver._licenses = ['fake']
def test_check_vfiler_exists(self):
elem = naapi.NaElement('fake')
elem['status'] = 'running'
self.driver._client.send_request = mock.Mock(return_value=elem)
self.driver._check_vfiler_exists()
def test_check_vfiler_exists_error(self):
elem = naapi.NaElement('fake')
elem['status'] = 'error'
self.driver._client.send_request = mock.Mock(return_value=elem)
self.assertRaises(exception.NetAppException,
self.driver._check_vfiler_exists)
def test_check_licenses(self):
root = naapi.NaElement('fake')
elem = naapi.NaElement('licenses')
licenses = ['l1', 'l2']
for license in licenses:
el = naapi.NaElement('license')
el['package'] = license
elem.add_child_elem(el)
root.add_child_elem(elem)
self.driver._client.send_request = mock.Mock(return_value=root)
self.driver._check_licenses()
self.assertEqual(self.driver._licenses, licenses)
def test_create_share(self):
self.driver.configuration.netapp_nas_server_hostname\
= 'fake-netapp-location'
root = naapi.NaElement('root')
aggregates = naapi.NaElement('aggregates')
for i in range(1, 4):
aggregates.add_node_with_children('aggr-attributes',
**{'name': 'fake%s' % i,
'size-available': '%s' % i})
root.add_child_elem(aggregates)
self.driver._client.send_request = mock.Mock(return_value=root)
self.helper.create_share = mock.Mock(return_value="fake-location")
export_location = self.driver.create_share(self._context, self.share)
args = {'containing-aggr-name': 'fake3',
'size': '1g',
'volume': 'share_fake_uuid'}
self.driver._client.send_request.assert_called_with('volume-create',
args)
self.helper.create_share.assert_called_once_with(
"share_%s" % self.share['id'], 'fake-netapp-location')
self.assertEqual(export_location, "fake-location")
def test_create_share_from_snapshot(self):
self.helper.create_share = mock.Mock(return_value="fake-location")
export_location = self.driver.create_share_from_snapshot(self._context,
self.share,
self.snapshot)
args = {'volume': 'share_fake_uuid',
'parent-volume': 'share_fake_share_id',
'parent-snapshot': 'share_snapshot_fake_snapshot_uuid'}
self.driver._client.send_request.assert_called_once_with(
'volume-clone-create', args)
self.assertEqual(export_location, "fake-location")
def test_delete_share(self):
self.driver.delete_share(self._context, self.share)
self.driver._client.send_request.assert_has_calls([
mock.call('volume-list-info', {'volume': 'share_fake_uuid'}),
mock.call('volume-offline', {'name': 'share_fake_uuid'}),
mock.call('volume-destroy', {'name': 'share_fake_uuid'})
])
self.helper.get_target.assert_called_once_with(self.share)
self.helper.delete_share.assert_called_once_with(self.share)
def test_delete_share_not_exists(self):
self.driver._client.send_request = mock.Mock(
side_effect=naapi.NaApiError)
self.driver.delete_share(self._context, self.share)
self.driver._client.send_request.assert_has_calls([
mock.call('volume-list-info', {'volume': 'share_fake_uuid'})
])
def test_create_snapshot(self):
self.driver.create_snapshot(self._context, self.snapshot)
self.driver._client.send_request.assert_called_once_with(
'snapshot-create',
{'volume': 'share_fake_share_id',
'snapshot': 'share_snapshot_fake_snapshot_uuid'})
def test_delete_snapshot(self):
res = mock.Mock()
res.get_child_by_name.return_value = res
snap = naapi.NaElement('snap')
snap.add_new_child('busy', 'true')
snap.add_new_child('name', 'share_snapshot_fake_snapshot_uuid')
res.get_children = mock.Mock(return_value=[snap])
self.driver._client.send_request = mock.Mock(return_value=res)
self.assertRaises(exception.ShareSnapshotIsBusy,
self.driver.delete_snapshot, self._context,
self.snapshot)
def test_delete_snapshot_busy(self):
res = mock.Mock()
res.get_child_by_name.return_value = res
snap = naapi.NaElement('snap')
snap.add_new_child('busy', 'false')
snap.add_new_child('name', 'share_fake_uuid')
res.get_children = mock.Mock(return_value=[snap])
self.driver._client.send_request = mock.Mock(return_value=res)
self.driver.delete_snapshot(self._context, self.snapshot)
self.driver._client.send_request.assert_called_with(
'snapshot-delete',
{'volume': 'share_fake_share_id',
'snapshot': 'share_snapshot_fake_snapshot_uuid'})
def test_allow_access(self):
access = "1.2.3.4"
self.driver.allow_access(self._context, self.share, access)
self.helper.allow_access.assert_called_ince_with(self._context,
self.share, access)
def test_deny_access(self):
access = "1.2.3.4"
self.driver.deny_access(self._context, self.share, access)
self.helper.deny_access.assert_called_ince_with(self._context,
self.share, access)
class NetAppNFSHelperTestCase(test.TestCase):
"""Tests for NetApp 7mode driver.
"""
def setUp(self):
super(NetAppNFSHelperTestCase, self).setUp()
self._context = context.get_admin_context()
self._db = mock.Mock()
self.client = mock.Mock()
self.share = {'id': 'fake_uuid',
'tenant_id': 'fake_tenant_id',
'name': 'fake_name',
'size': 1,
'export_location': 'location:/path',
'share_proto': 'fake'}
self.helper = driver.NetAppNFSHelper()
self.helper._client = mock.Mock()
self.helper._client.send_request = mock.Mock()
def test_create_share(self):
location = self.helper.create_share('share_name', 'location')
self.helper._client.send_request.assert_called_once_with(
'nfs-exportfs-append-rules-2', mock.ANY)
self.assertEqual(location, 'location:/vol/share_name')
def test_delete_share(self):
self.helper.delete_share(self.share)
self.helper._client.send_request.assert_called_once_with(
'nfs-exportfs-delete-rules', mock.ANY)
def test_allow_access(self):
access = {'access_to': '1.2.3.4',
'access_type': 'ip'}
root = naapi.NaElement('root')
rules = naapi.NaElement('rules')
root.add_child_elem(rules)
self.helper._client.send_request = mock.Mock(return_value=root)
self.helper.allow_access(self._context, self.share, access)
self.helper._client.send_request.assert_has_calls([
mock.call('nfs-exportfs-list-rules-2', mock.ANY),
mock.call('nfs-exportfs-append-rules-2', mock.ANY)
])
def test_deny_access(self):
access = {'access_to': '1.2.3.4',
'access_type': 'ip'}
root = naapi.NaElement('root')
rules = naapi.NaElement('rules')
root.add_child_elem(rules)
self.helper._client.send_request = mock.Mock(return_value=root)
self.helper.allow_access(self._context, self.share, access)
self.helper._client.send_request.assert_has_calls([
mock.call('nfs-exportfs-list-rules-2', mock.ANY),
mock.call('nfs-exportfs-append-rules-2', mock.ANY)
])
class NetAppCIFSHelperTestCase(test.TestCase):
"""Tests for NetApp 7mode driver.
"""
def setUp(self):
super(NetAppCIFSHelperTestCase, self).setUp()
self._context = context.get_admin_context()
self._db = mock.Mock()
self.share = {'id': 'fake_uuid',
'tenant_id': 'fake_tenant_id',
'name': 'fake_name',
'size': 1,
'export_location': None,
'share_proto': 'fake'}
self.share_name = 'fake_share_name'
self.helper = driver.NetAppCIFSHelper()
self.helper._client = mock.Mock()
self.helper._client.send_request = mock.Mock()
def test_create_share(self):
self.helper.create_share(self.share_name, 'location')
self.helper._client.send_request.assert_has_calls([
mock.call('cifs-status'),
mock.call().get_child_content('status'),
mock.call('system-cli', mock.ANY),
mock.call('cifs-share-add', mock.ANY),
mock.call('cifs-share-ace-delete', mock.ANY),
])
def test_delete_share(self):
self.helper.delete_share(self.share)
self.helper._client.send_request.assert_called_once_with(
'cifs-share-delete', mock.ANY)
def test_allow_access(self):
access = {'access_to': 'user',
'access_type': 'sid'}
self.helper.allow_access(self._context, self.share, access)
self.helper._client.send_request.assert_called_once_with(
'cifs-share-ace-set', mock.ANY)

View File

@ -0,0 +1,397 @@
# Copyright (c) 2014 NetApp, Inc.
# 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 hashlib
import mock
from manila import context
from manila.share import configuration
from manila.share.drivers.netapp import api as naapi
from manila.share.drivers.netapp import cluster_mode as driver
from manila import test
class NetAppClusteredDrvTestCase(test.TestCase):
"""Tests for NetApp cmode driver.
"""
def setUp(self):
super(NetAppClusteredDrvTestCase, self).setUp()
self._context = context.get_admin_context()
self._db = mock.Mock()
driver.driver.NetAppApiClient = mock.Mock()
self.driver = driver.NetAppClusteredShareDriver(
self._db, configuration=configuration.Configuration(None))
self.driver._client = mock.Mock()
self.driver._client.send_request = mock.Mock()
self._vserver_client = mock.Mock()
self._vserver_client.send_request = mock.Mock()
driver.driver.NetAppApiClient = mock.Mock(
return_value=self._vserver_client)
self.share = {'id': 'fake_uuid',
'project_id': 'fake_tenant_id',
'name': 'fake_name',
'size': 1,
'share_proto': 'fake',
'share_network_id': 'fake_net_id',
'network_info': {
'network_allocations': [
{'ip_address': 'ip'}
]
}
}
self.snapshot = {'id': 'fake_snapshot_uuid',
'project_id': 'fake_tenant_id',
'share_id': 'fake_share_id',
'share': self.share
}
self.security_service = {'id': 'fake_id',
'domain': 'FAKE',
'server': 'fake_server',
'sid': 'fake_sid',
'password': 'fake_password'}
self.helper = mock.Mock()
self.driver._helpers = {'FAKE': self.helper}
self.driver._licenses = ['fake']
def test_create_vserver(self):
res = naapi.NaElement('fake')
res.add_new_child('aggregate-name', 'aggr')
self.driver.configuration.netapp_root_volume_aggregate = 'root'
fake_aggrs = mock.Mock()
fake_aggrs.get_child_by_name.return_value = fake_aggrs
fake_aggrs.get_children.return_value = [res]
self.driver._client.send_request = mock.Mock(return_value=fake_aggrs)
vserver_create_args = {
'vserver-name': 'os_fake_net_id',
'root-volume-security-style': 'unix',
'root-volume-aggregate': 'root',
'root-volume': 'root',
'name-server-switch': {
'nsswitch': 'file'
}
}
vserver_modify_args = {
'aggr-list': [
{'aggr-name': 'aggr'}
],
'vserver-name': 'os_fake_net_id'}
self.driver._create_vserver('os_fake_net_id')
self.driver._client.send_request.assert_has_calls([
mock.call('vserver-create', vserver_create_args),
mock.call('aggr-get-iter'),
mock.call('vserver-modify', vserver_modify_args),
]
)
def test_get_network_allocations_number(self):
res = mock.Mock()
res.get_child_content.return_value = '5'
self.driver._client.send_request = mock.Mock(return_value=res)
self.assertEqual(self.driver.get_network_allocations_number(), 5)
def test_delete_vserver(self):
self.driver._delete_vserver('fake', self._vserver_client)
self._vserver_client.send_request.assert_has_calls([
mock.call('volume-offline', {'name': 'root'}),
mock.call('volume-destroy', {'name': 'root'})
])
self.driver._client.send_request.assert_called_once_with(
'vserver-destroy', {'vserver-name': 'fake'})
def test_create_net_iface(self):
self.driver._create_net_iface('1.1.1.1', '255.255.255.0', '200',
'node', 'port', 'vserver-name', 'all_id')
vlan_args = {
'vlan-info': {
'parent-interface': 'port',
'node': 'node',
'vlanid': '200'}
}
interface_args = {
'address': '1.1.1.1',
'administrative-status': 'up',
'data-protocols': [
{'data-protocol': 'nfs'},
{'data-protocol': 'cifs'}
],
'home-node': 'node',
'home-port': 'port-200',
'netmask': '255.255.255.0',
'interface-name': 'os_all_id',
'role': 'data',
'vserver': 'vserver-name',
}
self.driver._client.send_request.assert_has_calls([
mock.call('net-vlan-create', vlan_args),
mock.call('net-interface-create', interface_args),
])
def test_enable_nfs(self):
self.driver._enable_nfs(self._vserver_client)
export_args = {
'client-match': '0.0.0.0/0',
'policy-name': 'default',
'ro-rule': {
'security-flavor': 'any'
},
'rw-rule': {
'security-flavor': 'any'
}
}
self._vserver_client.send_request.assert_has_calls(
[mock.call('nfs-enable'),
mock.call('nfs-service-modify', {'is-nfsv40-enabled': 'true'}),
mock.call('export-rule-create', export_args)]
)
def test_configure_ldap(self):
conf_name = hashlib.md5('fake_id').hexdigest()
client_args = {
'ldap-client-config': conf_name,
'servers': {
'ip-address': 'fake_server'
},
'tcp-port': '389',
'schema': 'RFC-2307',
'bind-password': 'fake_password'
}
config_args = {'client-config': conf_name,
'client-enabled': 'true'}
self.driver._configure_ldap(self.security_service,
self._vserver_client)
self._vserver_client.send_request.assert_has_calls([
mock.call('ldap-client-create', client_args),
mock.call('ldap-config-create', config_args)])
def test_configure_kerberos(self):
kerberos_args = {'admin-server-ip': 'fake_server',
'admin-server-port': '749',
'clock-skew': '5',
'comment': '',
'config-name': 'fake_id',
'kdc-ip': 'fake_server',
'kdc-port': '88',
'kdc-vendor': 'other',
'password-server-ip': 'fake_server',
'password-server-port': '464',
'realm': 'FAKE'}
spn = 'nfs/fake-vserver.FAKE@FAKE'
kerberos_modify_args = {'admin-password': 'fake_password',
'admin-user-name': 'fake_sid',
'interface-name': 'fake_lif',
'is-kerberos-enabled': 'true',
'service-principal-name': spn
}
self.driver._get_lifs = mock.Mock(return_value=['fake_lif'])
self.driver._configure_dns = mock.Mock(return_value=['fake_lif'])
self.driver._configure_kerberos('fake_vserver', self.security_service,
self._vserver_client)
self.driver._client.send_request.assert_called_once_with(
'kerberos-realm-create', kerberos_args)
self._vserver_client.send_request.assert_called_once_with(
'kerberos-config-modify', kerberos_modify_args)
def test_configure_active_directory(self):
self.driver._configure_dns = mock.Mock()
self.driver._configure_active_directory(self.security_service,
self._vserver_client)
args = {'admin-username': 'fake_sid',
'admin-password': 'fake_password',
'force-account-overwrite': 'true',
'cifs-server': 'fake_server',
'domain': 'FAKE'}
self._vserver_client.send_request.assert_called_with(
'cifs-server-create', args)
def test_allocate_container(self):
root = naapi.NaElement('root')
attributes = naapi.NaElement('attributes')
vserver_info = naapi.NaElement('vserver-info')
vserver_aggr_info_list = naapi.NaElement('vserver-aggr-info-list')
for i in range(1, 4):
vserver_aggr_info_list.add_node_with_children('aggr-attributes',
**{'aggr-name': 'fake%s' % i,
'aggr-availsize': '%s' % i})
vserver_info.add_child_elem(vserver_aggr_info_list)
attributes.add_child_elem(vserver_info)
root.add_child_elem(attributes)
root.add_new_child('attributes', None)
self._vserver_client.send_request = mock.Mock(return_value=root)
self.driver._allocate_container(self.share, 'vserver',
self._vserver_client)
args = {'containing-aggr-name': 'fake3',
'size': '1g',
'volume': 'share_fake_uuid',
'junction-path': '/share_fake_uuid'
}
self._vserver_client.send_request.assert_called_with(
'volume-create', args)
def test_allocate_container_from_snapshot(self):
self.driver._allocate_container_from_snapshot(self.share,
self.snapshot,
'vserver',
self._vserver_client)
args = {'volume': 'share_fake_uuid',
'parent-volume': 'share_fake_share_id',
'parent-snapshot': 'share_snapshot_fake_snapshot_uuid',
'junction-path': '/share_fake_uuid'}
self._vserver_client.send_request.assert_called_with(
'volume-clone-create', args)
def test_deallocate_container(self):
self.driver._deallocate_container(self.share, self._vserver_client)
self._vserver_client.send_request.assert_has_calls([
mock.call('volume-unmount',
{'volume-name': 'share_fake_uuid'}),
mock.call('volume-offline',
{'name': 'share_fake_uuid'}),
mock.call('volume-destroy',
{'name': 'share_fake_uuid'})
])
def test_create_export(self):
self.helper.create_share = mock.Mock(return_value="fake-location")
export_location = self.driver._create_export(
self.share, 'vserver', self._vserver_client)
self.helper.create_share.assert_called_once_with(
"share_%s" % self.share['id'], 'ip')
self.assertEqual(export_location, "fake-location")
def test_create_snapshot(self):
self.driver.create_snapshot(self._context, self.snapshot)
self._vserver_client.send_request.assert_called_once_with(
'snapshot-create',
{'volume': 'share_fake_share_id',
'snapshot': 'share_snapshot_fake_snapshot_uuid'})
def test_delete_share(self):
resp = mock.Mock()
resp.get_child_content.return_value = 1
self._vserver_client.send_request = mock.Mock(return_value=resp)
self.driver.delete_share(self._context, self.share)
self.helper.delete_share.assert_called_once_with(self.share)
def test_allow_access(self):
access = "1.2.3.4"
self.driver.allow_access(self._context, self.share, access)
self.helper.allow_access.assert_called_ince_with(self._context,
self.share, access)
def test_deny_access(self):
access = "1.2.3.4"
self.driver.deny_access(self._context, self.share, access)
self.helper.deny_access.assert_called_ince_with(self._context,
self.share, access)
class NetAppNFSHelperTestCase(test.TestCase):
"""Tests for NetApp 7mode driver.
"""
def setUp(self):
super(NetAppNFSHelperTestCase, self).setUp()
self._context = context.get_admin_context()
self._db = mock.Mock()
self.client = mock.Mock()
self.share = {'id': 'fake_uuid',
'tenant_id': 'fake_tenant_id',
'name': 'fake_name',
'size': 1,
'export_location': 'location:/path',
'share_proto': 'fake'}
self.helper = driver.NetAppClusteredNFSHelper()
self.helper._client = mock.Mock()
self.helper._client.send_request = mock.Mock()
def test_create_share(self):
location = self.helper.create_share('share_name',
'fake-vserver-location')
self.helper._client.send_request.assert_called_once_with(
'nfs-exportfs-append-rules-2', mock.ANY)
self.assertEqual(location, 'fake-vserver-location:/share_name')
def test_delete_share(self):
self.helper.delete_share(self.share)
self.helper._client.send_request.assert_called_once_with(
'nfs-exportfs-delete-rules', mock.ANY)
def test_allow_access(self):
access = {'access_to': '1.2.3.4',
'access_type': 'ip'}
root = naapi.NaElement('root')
rules = naapi.NaElement('rules')
root.add_child_elem(rules)
self.helper._client.send_request = mock.Mock(return_value=root)
self.helper.allow_access(self._context, self.share, access)
self.helper._client.send_request.assert_has_calls([
mock.call('nfs-exportfs-list-rules-2', mock.ANY),
mock.call('nfs-exportfs-append-rules-2', mock.ANY)
])
def test_deny_access(self):
access = {'access_to': '1.2.3.4',
'access_type': 'ip'}
root = naapi.NaElement('root')
rules = naapi.NaElement('rules')
root.add_child_elem(rules)
self.helper._client.send_request = mock.Mock(return_value=root)
self.helper.allow_access(self._context, self.share, access)
self.helper._client.send_request.assert_has_calls([
mock.call('nfs-exportfs-list-rules-2', mock.ANY),
mock.call('nfs-exportfs-append-rules-2', mock.ANY)
])
class NetAppCIFSHelperTestCase(test.TestCase):
"""Tests for NetApp 7mode driver.
"""
def setUp(self):
super(NetAppCIFSHelperTestCase, self).setUp()
self._context = context.get_admin_context()
self._db = mock.Mock()
self.share = {'id': 'fake_uuid',
'tenant_id': 'fake_tenant_id',
'name': 'fake_name',
'size': 1,
'export_location': 'location:/path',
'share_proto': 'fake'}
self.helper = driver.NetAppClusteredCIFSHelper()
self.helper._client = mock.Mock()
self.helper._client.send_request = mock.Mock()
def test_create_share(self):
self.helper.create_share('fake_name', '1.1.1.1')
self.helper._client.send_request.assert_has_calls([
mock.call('cifs-share-create', {'path': '/fake_name',
'share-name': 'fake_name'}),
mock.call('cifs-share-access-control-delete',
{'user-or-group': 'Everyone', 'share': 'fake_name'})
])
def test_delete_share(self):
self.share['export_location'] = "nfs://host/fake_name"
self.helper.delete_share(self.share)
self.helper._client.send_request.assert_called_with(
'cifs-share-delete', {'share-name': 'fake_name'})
def test_allow_access(self):
self.helper._allow_access_for('fake_name', 'fake_share')
self.helper._client.send_request.assert_called_with(
'cifs-share-access-control-create', {'permission': 'full_control',
'share': 'fake_share',
'user-or-group': 'fake_name'})

View File

@ -1,690 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 NetApp
# 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.
"""Unit tests for the NetApp NAS driver module."""
from mox import IgnoreArg
import random
import suds
from manila import context
from manila import exception
from manila.share.configuration import Configuration
from manila.share.drivers.netapp import driver as netapp
from manila import test
class FakeObject(object):
pass
class FakeRequest(object):
def __init__(self, name=None, args=None):
self.Name = name
self.Args = args
class FakeStartResp(object):
def __init__(self):
self.Tag = random.randint(1, 100)
self.Records = random.randint(1, 10)
class FakeStatus(object):
def __init__(self, status):
self.Status = status
class FakeAggregates(object):
def __init__(self, max_aggr_id):
class AggrSizeAvail(object):
def __init__(self, filer_id, avail):
self.AggregateSize = FakeObject()
self.FilerId = filer_id
self.AggregateName = 'filer%d:aggr0' % filer_id
setattr(self.AggregateSize, 'SizeAvailable', avail)
class AggregateInfo(object):
def __init__(self):
self.AggregateInfo = [AggrSizeAvail(1, 10),
AggrSizeAvail(2, 20),
AggrSizeAvail(3, 1),
AggrSizeAvail(max_aggr_id, 50),
AggrSizeAvail(5, 15)]
self.Aggregates = AggregateInfo()
class FakeSnapshots(object):
def __init__(self, snapshot_name, is_busy='false'):
class Result(object):
def __init__(self):
self.snapshots = [{}]
self.snapshots[0]['snapshot-info'] = [
{'name': [snapshot_name], 'busy': [is_busy]},
{'name': ['fakesnapname1'], 'busy': [is_busy]},
{'name': ['fakesnapname2'], 'busy': ['true']},
]
self.Results = Result()
class FakeNfsRules(object):
def __init__(self):
class Rules(object):
def __init__(self):
self.rules = [
{'exports-rule-info-2': [
{'security-rules': [
{'security-rule-info': [
{'root': [
{'exports-hostname-info': [
{'name': 'allowed_host'},
{'name': 'disallowed_host'}]}
]}
]}
]}
]}
]
self.Results = Rules()
class FakeHost(object):
def __init__(self, id):
self.HostId = id
class FakeHostInfo(object):
def __init__(self):
self.Hosts = FakeObject()
setattr(self.Hosts, 'HostInfo', [FakeHost(1), FakeHost(2)])
class FakeFilter(object):
def __init__(self, id=0):
self.ObjectNameOrId = id
class FakeTimestamp(object):
def __init__(self, monitor_name='file_system', last_stamp=1):
self.MonitorName = monitor_name
self.LastMonitoringTimestamp = last_stamp
class NetAppShareDriverTestCase(test.TestCase):
"""Tests Netapp-specific share driver.
"""
def setUp(self):
super(NetAppShareDriverTestCase, self).setUp()
self._context = context.get_admin_context()
self._db = self.mox.CreateMockAnything()
self._driver = netapp.NetAppShareDriver(
self._db,
configuration=Configuration(None))
self._driver._client = self.mox.CreateMock(netapp.NetAppApiClient)
cifs_helper = self.mox.CreateMock(netapp.NetAppCIFSHelper)
nfs_helper = self.mox.CreateMock(netapp.NetAppNFSHelper)
self._driver._helpers = {'CIFS': cifs_helper, 'NFS': nfs_helper}
def test_setup_check(self):
self._driver._client.do_setup()
self.mox.ReplayAll()
self._driver.do_setup(self._context)
def test_load_balancer(self):
drv = self._driver
max_aggr_id = 123
drv._client.get_available_aggregates().AndReturn(
FakeAggregates(max_aggr_id))
self.mox.ReplayAll()
aggr = drv._find_best_aggregate()
self.assertEquals(max_aggr_id, aggr.FilerId)
def test_allocate_container(self):
drv = self._driver
client = drv._client
share = {'id': 'fakeshareid', 'size': 1}
max_aggr_id = 123
client.get_available_aggregates().AndReturn(
FakeAggregates(max_aggr_id))
client.send_request_to(max_aggr_id, 'volume-create', IgnoreArg())
self.mox.ReplayAll()
drv.allocate_container(self._context, share)
self.assertEqual(max_aggr_id, drv._share_table[share['id']])
def test_allocate_container_from_snapshot(self):
drv = self._driver
client = drv._client
share_id = 'fakeshareid'
share = {'id': share_id, 'size': 1}
snapshot = {'id': 'fakesnapshotid', 'size': 1,
'share_id': share_id}
max_aggr_id = 123
drv._share_table[share_id] = max_aggr_id
client.send_request_to(max_aggr_id, 'volume-clone-create', IgnoreArg())
self.mox.ReplayAll()
drv.allocate_container_from_snapshot(self._context, share, snapshot)
self.assertEqual(max_aggr_id, drv._share_table[share['id']])
def test_deallocate_container_target_exists(self):
drv = self._driver
client = drv._client
share_id = 'share-vol_id'
share = {'id': share_id, 'size': 1}
max_aggr_id = 123
client.get_available_aggregates().AndReturn(
FakeAggregates(max_aggr_id))
client.send_request_to(max_aggr_id, 'volume-create', IgnoreArg())
client.send_request_to(max_aggr_id, 'volume-offline', IgnoreArg())
client.send_request_to(max_aggr_id, 'volume-destroy', IgnoreArg())
self.mox.ReplayAll()
drv.allocate_container(self._context, share)
drv.deallocate_container(self._context, share)
self.assertEquals(len(drv._share_table.keys()), 0)
def test_share_create(self):
drv = self._driver
ctx = self._context
share_proto = 'CIFS'
share = {'id': '1234-abcd-5678',
'share_proto': share_proto,
'size': 1}
drv._helpers[share_proto].create_share(IgnoreArg(), share)
self.mox.ReplayAll()
drv.create_share(ctx, share)
def test_share_delete(self):
drv = self._driver
ctx = self._context
share_proto = 'NFS'
helper = drv._helpers[share_proto]
ip = '172.10.0.1'
export = '/export_path'
share = {'id': 'abcd-1234',
'share_proto': share_proto,
'export_location': ':'.join([ip, export])}
fake_access_rules = [1, 2, 3]
helper.get_target(share).AndReturn(ip)
helper.delete_share(share)
self.mox.ReplayAll()
drv.delete_share(ctx, share)
def test_create_snapshot(self):
drv = self._driver
client = drv._client
share_id = 'fakeshareid'
share = {'id': share_id, 'size': 1}
snapshot = {'id': 'fakesnapshotid', 'size': 1,
'share_id': share_id}
max_aggr_id = 123
drv._share_table[share_id] = max_aggr_id
client.send_request_to(max_aggr_id, 'snapshot-create', IgnoreArg())
self.mox.ReplayAll()
drv.create_snapshot(self._context, snapshot)
def test_delete_snapshot(self):
drv = self._driver
client = drv._client
share_id = 'fakeshareid'
share = {'id': share_id, 'size': 1}
snapshot = {'id': 'fakesnapshotid', 'size': 1,
'share_id': share_id}
max_aggr_id = 123
drv._share_table[share_id] = max_aggr_id
client.send_request_to(max_aggr_id, 'snapshot-list-info', IgnoreArg(),
do_response_check=False).\
AndReturn(FakeSnapshots(netapp._get_valid_snapshot_name(
snapshot['id'])))
client.send_request_to(max_aggr_id, 'snapshot-delete', IgnoreArg())
self.mox.ReplayAll()
drv.delete_snapshot(self._context, snapshot)
def test_delete_snapshot_if_busy(self):
drv = self._driver
client = drv._client
share_id = 'fakeshareid'
share = {'id': share_id, 'size': 1}
snapshot = {'id': 'fakesnapshotid', 'size': 1,
'share_id': share_id}
max_aggr_id = 123
drv._share_table[share_id] = max_aggr_id
client.send_request_to(max_aggr_id, 'snapshot-list-info', IgnoreArg(),
do_response_check=False).\
AndReturn(FakeSnapshots(netapp._get_valid_snapshot_name(
snapshot['id']), is_busy='true'))
self.mox.ReplayAll()
self.assertRaises(exception.ShareSnapshotIsBusy, drv.delete_snapshot,
self._context, snapshot)
def test_allow_access(self):
drv = self._driver
share_proto = 'CIFS'
ctx = self._context
share = {'share_proto': share_proto}
access = {}
drv._helpers[share_proto].allow_access(ctx, share, access)
self.mox.ReplayAll()
drv.allow_access(ctx, share, access)
def test_deny_access(self):
drv = self._driver
share_proto = 'CIFS'
ctx = self._context
share = {'share_proto': share_proto}
access = {}
drv._helpers[share_proto].deny_access(ctx, share, access)
self.mox.ReplayAll()
drv.deny_access(ctx, share, access)
def test_no_aggregates_available(self):
drv = self._driver
ctx = self._context
share = None
drv._client.get_available_aggregates().AndReturn(None)
self.mox.ReplayAll()
self.assertRaises(exception.Error, drv.allocate_container, ctx, share)
class NetAppNfsHelperTestCase(test.TestCase):
"""
Tests Netapp-specific NFS driver.
"""
def setUp(self):
super(NetAppNfsHelperTestCase, self).setUp()
fake_client = self.mox.CreateMock(netapp.NetAppApiClient)
fake_conf = self.mox.CreateMock(Configuration)
self._driver = netapp.NetAppNFSHelper(fake_client, fake_conf)
def test_create_share(self):
drv = self._driver
client = drv._client
target = 123
share = {'id': 'abc-1234-567'}
client.send_request_to(target, 'nfs-exportfs-append-rules-2',
IgnoreArg())
client.get_host_ip_by(target).AndReturn('host:export')
self.mox.ReplayAll()
export = drv.create_share(target, share)
self.assertEquals(export.find('-'), -1)
def test_delete_share(self):
drv = self._driver
client = drv._client
share = {'export_location': 'host:export'}
client.send_request_to(IgnoreArg(), 'nfs-exportfs-delete-rules',
IgnoreArg())
self.mox.ReplayAll()
drv.delete_share(share)
def test_invalid_allow_access(self):
drv = self._driver
share = None
access = {'access_type': 'passwd'} # passwd type is not supported
self.assertRaises(exception.Error, drv.allow_access, context, share,
access)
def test_allow_access(self):
drv = self._driver
client = drv._client
share = {'export_location': 'host:export'}
access = {'access_to': ['127.0.0.1', '127.0.0.2'],
'access_type': 'ip'}
client.send_request_to(IgnoreArg(), 'nfs-exportfs-list-rules-2',
IgnoreArg()).AndReturn(FakeNfsRules())
client.send_request_to(IgnoreArg(), 'nfs-exportfs-append-rules-2',
IgnoreArg())
self.mox.ReplayAll()
drv.allow_access(context, share, access)
def test_deny_access(self):
drv = self._driver
client = drv._client
share = {'export_location': 'host:export'}
access = {'access_to': ['127.0.0.1', '127.0.0.2']}
client.send_request_to(IgnoreArg(), 'nfs-exportfs-list-rules-2',
IgnoreArg()).AndReturn(FakeNfsRules())
client.send_request_to(IgnoreArg(), 'nfs-exportfs-append-rules-2',
IgnoreArg())
self.mox.ReplayAll()
drv.deny_access(context, share, access)
def test_get_target(self):
drv = self._driver
ip = '172.18.0.1'
export_path = '/home'
share = {'export_location': ':'.join([ip, export_path])}
self.assertEquals(drv.get_target(share), ip)
class NetAppCifsHelperTestCase(test.TestCase):
"""
Tests Netapp-specific CIFS driver.
"""
def setUp(self):
super(NetAppCifsHelperTestCase, self).setUp()
fake_client = self.mox.CreateMock(netapp.NetAppApiClient)
fake_conf = self.mox.CreateMock(Configuration)
self._driver = netapp.NetAppCIFSHelper(fake_client, fake_conf)
def tearDown(self):
super(NetAppCifsHelperTestCase, self).tearDown()
def test_create_share(self):
drv = self._driver
client = drv._client
target = 123
share = {'id': 'abc-1234-567'}
ip = '172.0.0.1'
client.send_request_to(target, 'cifs-status').AndReturn(
FakeStatus('stopped'))
client.send_request_to(target, 'cifs-start',
do_response_check=False)
client.send_request_to(target, 'system-cli', IgnoreArg())
client.send_request_to(target, 'cifs-share-add', IgnoreArg())
client.send_request_to(target, 'cifs-share-ace-delete', IgnoreArg())
client.get_host_ip_by(target).AndReturn(ip)
self.mox.ReplayAll()
export = drv.create_share(target, share)
self.assertEquals(export.find('-'), -1)
self.assertTrue(export.startswith('//' + ip))
def test_delete_share(self):
drv = self._driver
client = drv._client
ip = '172.10.0.1'
export = 'home'
share = {'export_location': '//%s/%s' % (ip, export)}
client.send_request_to(IgnoreArg(), 'cifs-share-delete', IgnoreArg())
self.mox.ReplayAll()
drv.delete_share(share)
def test_allow_access_by_ip(self):
drv = self._driver
access = {'access_type': 'ip', 'access_to': '123.123.123.123'}
share = None
self.assertRaises(exception.Error, drv.allow_access, context, share,
access)
def test_allow_access_by_passwd_invalid_user(self):
drv = self._driver
client = drv._client
access = {'access_type': 'passwd', 'access_to': 'user:pass'}
ip = '172.0.0.1'
export = 'export_path'
share = {'export_location': '//%s/%s' % (ip, export)}
status = FakeStatus('failed')
client.send_request_to(ip, 'useradmin-user-list', IgnoreArg(),
do_response_check=False).AndReturn(status)
self.mox.ReplayAll()
self.assertRaises(exception.Error, drv.allow_access, context, share,
access)
def test_allow_access_by_passwd_existing_user(self):
drv = self._driver
client = drv._client
access = {'access_type': 'passwd', 'access_to': 'user:pass'}
ip = '172.0.0.1'
export = 'export_path'
share = {'export_location': '//%s/%s' % (ip, export)}
status = FakeStatus('passed')
client.send_request_to(ip, 'useradmin-user-list', IgnoreArg(),
do_response_check=False).AndReturn(status)
client.send_request_to(ip, 'cifs-share-ace-set', IgnoreArg())
self.mox.ReplayAll()
drv.allow_access(context, share, access)
def test_deny_access(self):
drv = self._driver
client = drv._client
access = {'access_type': 'passwd', 'access_to': 'user:pass'}
ip = '172.0.0.1'
export = 'export_path'
share = {'export_location': '//%s/%s' % (ip, export)}
client.send_request_to(ip, 'cifs-share-ace-delete', IgnoreArg())
self.mox.ReplayAll()
drv.deny_access(context, share, access)
def test_get_target(self):
drv = self._driver
ip = '172.10.0.1'
export = 'export_path'
share = {'export_location': '//%s/%s' % (ip, export)}
self.assertEquals(drv.get_target(share), ip)
class NetAppNASHelperTestCase(test.TestCase):
def setUp(self):
super(NetAppNASHelperTestCase, self).setUp()
fake_client = self.mox.CreateMock(suds.client.Client)
fake_conf = self.mox.CreateMock(Configuration)
self._driver = netapp.NetAppNASHelperBase(fake_client, fake_conf)
def tearDown(self):
super(NetAppNASHelperTestCase, self).tearDown()
def test_create_share(self):
drv = self._driver
target_id = None
share = None
self.assertRaises(NotImplementedError, drv.create_share, target_id,
share)
def test_delete_share(self):
drv = self._driver
share = None
self.assertRaises(NotImplementedError, drv.delete_share, share)
def test_allow_access(self):
drv = self._driver
share = None
ctx = None
access = None
self.assertRaises(NotImplementedError, drv.allow_access, ctx, share,
access)
def test_deny_access(self):
drv = self._driver
share = None
ctx = None
access = None
self.assertRaises(NotImplementedError, drv.deny_access, ctx, share,
access)
def test_get_target(self):
drv = self._driver
share = None
self.assertRaises(NotImplementedError, drv.get_target, share)
class NetAppApiClientTestCase(test.TestCase):
"""Tests for NetApp DFM API client.
"""
def setUp(self):
super(NetAppApiClientTestCase, self).setUp()
self.fake_conf = self.mox.CreateMock(Configuration)
self._context = context.get_admin_context()
self._driver = netapp.NetAppApiClient(self.fake_conf)
self._driver._client = self.mox.CreateMock(suds.client.Client)
self._driver._client.factory = self.mox.CreateMock(suds.client.Factory)
# service object is generated dynamically from XML
self._driver._client.service = self.mox.CreateMockAnything(
suds.client.ServiceSelector)
def test_get_host_by_ip(self):
drv = self._driver
client = drv._client
service = client.service
host_id = 123
# can't use 'filter' because it's predefined in Python
fltr = client.factory.create('HostListInfoIterStart').AndReturn(
FakeFilter())
resp = service.HostListInfoIterStart(HostListInfoIterStart=fltr)
resp = resp.AndReturn(FakeStartResp())
service_list = service.HostListInfoIterNext(Tag=resp.Tag,
Maximum=resp.Records)
service_list.AndReturn(FakeHostInfo())
service.HostListInfoIterEnd(Tag=resp.Tag)
self.mox.ReplayAll()
drv.get_host_ip_by(host_id)
def test_get_available_aggregates(self):
drv = self._driver
client = drv._client
service = client.service
resp = service.AggregateListInfoIterStart().AndReturn(FakeStartResp())
service.AggregateListInfoIterNext(Tag=resp.Tag, Maximum=resp.Records)
service.AggregateListInfoIterEnd(resp.Tag)
self.mox.ReplayAll()
drv.get_available_aggregates()
def test_send_successfull_request(self):
drv = self._driver
client = drv._client
service = client.service
factory = client.factory
target = 1
args = '<xml></xml>'
responce_check = False
request = factory.create('Request').AndReturn(FakeRequest())
service.ApiProxy(Target=target, Request=request)
self.mox.ReplayAll()
drv.send_request_to(target, request, args, responce_check)
def test_send_failing_request(self):
drv = self._driver
client = drv._client
service = client.service
factory = client.factory
target = 1
args = '<xml></xml>'
responce_check = True
request = factory.create('Request').AndReturn(FakeRequest())
service.ApiProxy(Target=target, Request=request).AndRaise(
exception.Error())
self.mox.ReplayAll()
self.assertRaises(exception.Error, drv.send_request_to,
target, request, args, responce_check)
def test_successfull_setup(self):
drv = self._driver
for flag in drv.REQUIRED_FLAGS:
setattr(netapp.CONF, flag, 'val')
conf_obj = Configuration(netapp.CONF)
drv.check_configuration(conf_obj)
def test_failing_setup(self):
drv = self._driver
self.assertRaises(exception.Error, drv.check_configuration,
Configuration(netapp.CONF))