cinder/cinder/tests/test_netapp.py

1245 lines
53 KiB
Python

# Copyright (c) 2012 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.
"""
Tests for NetApp volume driver
"""
import BaseHTTPServer
import httplib
from lxml import etree
import mock
import six
from cinder import exception
from cinder.openstack.common import log as logging
from cinder import test
from cinder.volume import configuration as conf
from cinder.volume.drivers.netapp import common
from cinder.volume.drivers.netapp.dataontap.client import client_base
from cinder.volume.drivers.netapp.dataontap import ssc_cmode
from cinder.volume.drivers.netapp import options
from cinder.volume.drivers.netapp import utils
LOG = logging.getLogger("cinder.volume.driver")
def create_configuration():
configuration = conf.Configuration(None)
configuration.append_config_values(options.netapp_connection_opts)
configuration.append_config_values(options.netapp_transport_opts)
configuration.append_config_values(options.netapp_basicauth_opts)
configuration.append_config_values(options.netapp_cluster_opts)
configuration.append_config_values(options.netapp_7mode_opts)
configuration.append_config_values(options.netapp_provisioning_opts)
return configuration
class FakeHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
"""HTTP handler that doesn't spam the log."""
def log_message(self, format, *args):
pass
class FakeHttplibSocket(object):
"""A fake socket implementation for httplib.HTTPResponse."""
def __init__(self, value):
self._rbuffer = six.StringIO(value)
self._wbuffer = six.StringIO('')
oldclose = self._wbuffer.close
def newclose():
self.result = self._wbuffer.getvalue()
oldclose()
self._wbuffer.close = newclose
def makefile(self, mode, _other):
"""Returns the socket's internal buffer"""
if mode == 'r' or mode == 'rb':
return self._rbuffer
if mode == 'w' or mode == 'wb':
return self._wbuffer
RESPONSE_PREFIX_DIRECT_CMODE = """<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE netapp SYSTEM 'file:/etc/netapp_gx.dtd'>"""
RESPONSE_PREFIX_DIRECT_7MODE = """<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE netapp SYSTEM "/na_admin/netapp_filer.dtd">"""
RESPONSE_PREFIX_DIRECT = """
<netapp version='1.15' xmlns='http://www.netapp.com/filer/admin'>"""
RESPONSE_SUFFIX_DIRECT = """</netapp>"""
class FakeDirectCMODEServerHandler(FakeHTTPRequestHandler):
"""HTTP handler that fakes enough stuff to allow the driver to run."""
def do_GET(s):
"""Respond to a GET request."""
if '/servlets/netapp.servlets.admin.XMLrequest_filer' not in s.path:
s.send_response(404)
s.end_headers
return
s.send_response(200)
s.send_header("Content-Type", "text/xml; charset=utf-8")
s.end_headers()
out = s.wfile
out.write('<netapp version="1.15">'
'<results reason="Not supported method type"'
' status="failed" errno="Not_Allowed"/></netapp>')
def do_POST(s): # noqa
"""Respond to a POST request."""
if '/servlets/netapp.servlets.admin.XMLrequest_filer' not in s.path:
s.send_response(404)
s.end_headers
return
request_xml = s.rfile.read(int(s.headers['Content-Length']))
root = etree.fromstring(request_xml)
body = [x for x in root.iterchildren()]
request = body[0]
tag = request.tag
api = etree.QName(tag).localname or tag
if 'lun-get-iter' == api:
tag = \
FakeDirectCMODEServerHandler._get_child_by_name(request, 'tag')
if tag is None:
body = """<results status="passed"><attributes-list>
<lun-info>
<alignment>indeterminate</alignment>
<block-size>512</block-size>
<comment></comment><creation-timestamp>1354536362
</creation-timestamp>
<is-space-alloc-enabled>false</is-space-alloc-enabled>
<is-space-reservation-enabled>true
</is-space-reservation-enabled>
<mapped>false</mapped><multiprotocol-type>linux
</multiprotocol-type>
<online>true</online><path>/vol/navneet/lun1</path>
<prefix-size>0</prefix-size><qtree></qtree><read-only>
false</read-only><serial-number>2FfGI$APyN68</serial-number>
<share-state>none</share-state><size>20971520</size>
<size-used>0</size-used><staging>false</staging>
<suffix-size>0</suffix-size>
<uuid>cec1f3d7-3d41-11e2-9cf4-123478563412</uuid>
<volume>navneet</volume><vserver>ben_vserver</vserver>
</lun-info></attributes-list>
<next-tag>&lt;lun-get-iter-key-td&gt;
&lt;key-0&gt;ben_vserver&lt;/key-0&gt;
&lt;key-1&gt;/vol/navneet/lun2&lt;/key-1&gt;
&lt;key-2&gt;navneet&lt;/key-2&gt;
&lt;key-3&gt;&lt;/key-3&gt;
&lt;key-4&gt;lun2&lt;/key-4&gt;
&lt;/lun-get-iter-key-td&gt;
</next-tag><num-records>1</num-records></results>"""
else:
body = """<results status="passed"><attributes-list>
<lun-info>
<alignment>indeterminate</alignment>
<block-size>512</block-size>
<comment></comment><creation-timestamp>1354536362
</creation-timestamp>
<is-space-alloc-enabled>false</is-space-alloc-enabled>
<is-space-reservation-enabled>true
</is-space-reservation-enabled>
<mapped>false</mapped><multiprotocol-type>linux
</multiprotocol-type>
<online>true</online><path>/vol/navneet/lun3</path>
<prefix-size>0</prefix-size><qtree></qtree><read-only>
false</read-only><serial-number>2FfGI$APyN68
</serial-number>
<share-state>none</share-state><size>20971520</size>
<size-used>0</size-used><staging>false</staging>
<suffix-size>0</suffix-size>
<uuid>cec1f3d7-3d41-11e2-9cf4-123478563412</uuid>
<volume>navneet</volume><vserver>ben_vserver</vserver>
</lun-info></attributes-list>
<num-records>1</num-records></results>"""
elif 'volume-get-iter' == api:
tag = \
FakeDirectCMODEServerHandler._get_child_by_name(request, 'tag')
if tag is None:
body = """<results status="passed"><attributes-list>
<volume-attributes>
<volume-id-attributes><name>iscsi</name>
<owning-vserver-name>Openstack</owning-vserver-name>
</volume-id-attributes>
<volume-space-attributes>
<size-available>214748364</size-available>
</volume-space-attributes>
<volume-state-attributes><is-cluster-volume>true
</is-cluster-volume>
<is-vserver-root>false</is-vserver-root><state>online</state>
</volume-state-attributes></volume-attributes>
<volume-attributes>
<volume-id-attributes><name>nfsvol</name>
<owning-vserver-name>openstack</owning-vserver-name>
</volume-id-attributes>
<volume-space-attributes>
<size-available>247483648</size-available>
</volume-space-attributes>
<volume-state-attributes><is-cluster-volume>true
</is-cluster-volume>
<is-vserver-root>false</is-vserver-root><state>online</state>
</volume-state-attributes></volume-attributes>
</attributes-list>
<next-tag>&lt;volume-get-iter-key-td&gt;
&lt;key-0&gt;openstack&lt;/key-0&gt;
&lt;key-1&gt;nfsvol&lt;/key-1&gt;
&lt;/volume-get-iter-key-td&gt;
</next-tag><num-records>2</num-records></results>"""
else:
body = """<results status="passed"><attributes-list>
<volume-attributes>
<volume-id-attributes><name>iscsi</name>
<owning-vserver-name>Openstack</owning-vserver-name>
</volume-id-attributes>
<volume-space-attributes>
<size-available>4147483648</size-available>
</volume-space-attributes>
<volume-state-attributes><is-cluster-volume>true
</is-cluster-volume>
<is-vserver-root>false</is-vserver-root><state>online</state>
</volume-state-attributes></volume-attributes>
<volume-attributes>
<volume-id-attributes><name>nfsvol</name>
<owning-vserver-name>openstack</owning-vserver-name>
</volume-id-attributes>
<volume-space-attributes>
<size-available>8147483648</size-available>
</volume-space-attributes>
<volume-state-attributes><is-cluster-volume>true
</is-cluster-volume>
<is-vserver-root>false</is-vserver-root><state>online</state>
</volume-state-attributes></volume-attributes>
</attributes-list>
<num-records>2</num-records></results>"""
elif 'lun-create-by-size' == api:
body = """<results status="passed">
<actual-size>22020096</actual-size></results>"""
elif 'lun-destroy' == api:
body = """<results status="passed"/>"""
elif 'igroup-get-iter' == api:
init_found = True
query = FakeDirectCMODEServerHandler._get_child_by_name(request,
'query')
if query is not None:
igroup_info = FakeDirectCMODEServerHandler._get_child_by_name(
query, 'initiator-group-info')
if igroup_info is not None:
inits = FakeDirectCMODEServerHandler._get_child_by_name(
igroup_info, 'initiators')
if inits is not None:
init_info = \
FakeDirectCMODEServerHandler._get_child_by_name(
inits, 'initiator-info')
init_name = \
FakeDirectCMODEServerHandler._get_child_content(
init_info,
'initiator-name')
if init_name == 'iqn.1993-08.org.debian:01:10':
init_found = True
else:
init_found = False
if init_found:
tag = \
FakeDirectCMODEServerHandler._get_child_by_name(
request, 'tag')
if tag is None:
body = """<results status="passed"><attributes-list>
<initiator-group-info><initiator-group-name>
openstack-01f5297b-00f7-4170-bf30-69b1314b2118
</initiator-group-name>
<initiator-group-os-type>windows</initiator-group-os-type>
<initiator-group-type>iscsi</initiator-group-type>
<initiators>
<initiator-info>
<initiator-name>iqn.1993-08.org.debian:01:10</initiator-name>
</initiator-info></initiators>
<vserver>openstack</vserver></initiator-group-info>
</attributes-list><next-tag>
&lt;igroup-get-iter-key-td&gt;
&lt;key-0&gt;openstack&lt;/key-0&gt;
&lt;key-1&gt;
openstack-01f5297b-00f7-4170-bf30-69b1314b2118&lt;
/key-1&gt;
&lt;/igroup-get-iter-key-td&gt;
</next-tag><num-records>1</num-records></results>"""
else:
body = """<results status="passed"><attributes-list>
<initiator-group-info><initiator-group-name>
openstack-01f5297b-00f7-4170-bf30-69b1314b2118
</initiator-group-name>
<initiator-group-os-type>linux</initiator-group-os-type>
<initiator-group-type>iscsi</initiator-group-type>
<initiators>
<initiator-info>
<initiator-name>iqn.1993-08.org.debian:01:10</initiator-name>
</initiator-info></initiators>
<vserver>openstack</vserver></initiator-group-info>
</attributes-list><num-records>1</num-records></results>"""
else:
body = """<results status="passed">
<num-records>0</num-records>
</results>"""
elif 'lun-map-get-iter' == api:
tag = \
FakeDirectCMODEServerHandler._get_child_by_name(request, 'tag')
if tag is None:
body = """<results status="passed"><attributes-list>
<lun-map-info>
<initiator-group>openstack-44c5e7e1-3306-4800-9623-259e57d56a83
</initiator-group>
<initiator-group-uuid>948ae304-06e9-11e2</initiator-group-uuid>
<lun-id>0</lun-id>
<lun-uuid>5587e563-06e9-11e2-9cf4-123478563412</lun-uuid>
<path>/vol/openvol/lun1</path>
<vserver>openstack</vserver>
</lun-map-info></attributes-list>
<next-tag>
&lt;lun-map-get-iter-key-td&gt;
&lt;key-0&gt;openstack&lt;/key-0&gt;
&lt;key-1&gt;openstack-01f5297b-00f7-4170-bf30-69b1314b2118&lt;
/key-1&gt;
&lt;/lun-map-get-iter-key-td&gt;
</next-tag>
<num-records>1</num-records>
</results>"""
else:
body = """<results status="passed"><attributes-list>
<lun-map-info>
<initiator-group>openstack-44c5e7e1-3306-4800-9623-259e57d56a83
</initiator-group>
<initiator-group-uuid>948ae304-06e9-11e2</initiator-group-uuid>
<lun-id>0</lun-id>
<lun-uuid>5587e563-06e9-11e2-9cf4-123478563412</lun-uuid>
<path>/vol/openvol/lun1</path>
<vserver>openstack</vserver>
</lun-map-info></attributes-list><num-records>1</num-records>
</results>"""
elif 'lun-map' == api:
body = """<results status="passed"><lun-id-assigned>1
</lun-id-assigned>
</results>"""
elif 'lun-get-geometry' == api:
body = """<results status="passed"><bytes-per-sector>256
</bytes-per-sector><cylinders>512</cylinders><max-resize-size>
3221225472</max-resize-size><sectors-per-track>512
</sectors-per-track><size>2147483648</size>
<tracks-per-cylinder>256</tracks-per-cylinder></results>"""
elif 'iscsi-service-get-iter' == api:
body = """<results status="passed"><attributes-list>
<iscsi-service-info>
<alias-name>openstack</alias-name>
<is-available>true</is-available>
<node-name>iqn.1992-08.com.netapp:sn.fa9:vs.105</node-name>
<vserver>openstack</vserver></iscsi-service-info>
</attributes-list><num-records>1</num-records></results>"""
elif 'iscsi-interface-get-iter' == api:
body = """<results status="passed"><attributes-list>
<iscsi-interface-list-entry-info><current-node>
fas3170rre-cmode-01
</current-node><current-port>e1b-1165</current-port>
<interface-name>
iscsi_data_if</interface-name>
<ip-address>10.63.165.216</ip-address>
<ip-port>3260</ip-port><is-interface-enabled>true
</is-interface-enabled>
<relative-port-id>5</relative-port-id>
<tpgroup-name>iscsi_data_if</tpgroup-name>
<tpgroup-tag>1038</tpgroup-tag><vserver>
openstack</vserver>
</iscsi-interface-list-entry-info></attributes-list>
<num-records>1</num-records></results>"""
elif 'igroup-create' == api:
body = """<results status="passed"/>"""
elif 'igroup-add' == api:
body = """<results status="passed"/>"""
elif 'clone-create' == api:
body = """<results status="passed"/>"""
elif 'lun-unmap' == api:
body = """<results status="passed"/>"""
elif 'system-get-ontapi-version' == api:
body = """<results status="passed">
<major-version>1</major-version>
<minor-version>19</minor-version>
</results>"""
elif 'vserver-get-iter' == api:
body = """<results status="passed"><attributes-list>
<vserver-info>
<vserver-name>vserver</vserver-name>
<vserver-type>node</vserver-type>
</vserver-info>
</attributes-list>
<num-records>1</num-records></results>"""
elif 'ems-autosupport-log' == api:
body = """<results status="passed"/>"""
elif 'lun-resize' == api:
body = """<results status="passed"/>"""
elif 'lun-get-geometry' == api:
body = """<results status="passed">
<size>1</size>
<bytes-per-sector>2</bytes-per-sector>
<sectors-per-track>8</sectors-per-track>
<tracks-per-cylinder>2</tracks-per-cylinder>
<cylinders>4</cylinders>
<max-resize-size>5</max-resize-size>
</results>"""
elif 'volume-options-list-info' == api:
body = """<results status="passed">
<options>
<option>
<name>compression</name>
<value>off</value>
</option>
</options>
</results>"""
elif 'lun-move' == api:
body = """<results status="passed"/>"""
else:
# Unknown API
s.send_response(500)
s.end_headers
return
s.send_response(200)
s.send_header("Content-Type", "text/xml; charset=utf-8")
s.end_headers()
s.wfile.write(RESPONSE_PREFIX_DIRECT_CMODE)
s.wfile.write(RESPONSE_PREFIX_DIRECT)
s.wfile.write(body)
s.wfile.write(RESPONSE_SUFFIX_DIRECT)
@staticmethod
def _get_child_by_name(self, name):
for child in self.iterchildren():
if child.tag == name or etree.QName(child.tag).localname == name:
return child
return None
@staticmethod
def _get_child_content(self, name):
"""Get the content of the child."""
for child in self.iterchildren():
if child.tag == name or etree.QName(child.tag).localname == name:
return child.text
return None
class FakeDirectCmodeHTTPConnection(object):
"""A fake httplib.HTTPConnection for netapp tests
Requests made via this connection actually get translated and routed into
the fake direct handler above, we then turn the response into
the httplib.HTTPResponse that the caller expects.
"""
def __init__(self, host, timeout=None):
self.host = host
def request(self, method, path, data=None, headers=None):
if not headers:
headers = {}
req_str = '%s %s HTTP/1.1\r\n' % (method, path)
for key, value in headers.iteritems():
req_str += "%s: %s\r\n" % (key, value)
if data:
req_str += '\r\n%s' % data
# NOTE(vish): normally the http transport normalizes from unicode
sock = FakeHttplibSocket(req_str.decode("latin-1").encode("utf-8"))
# NOTE(vish): stop the server from trying to look up address from
# the fake socket
FakeDirectCMODEServerHandler.address_string = lambda x: '127.0.0.1'
self.app = FakeDirectCMODEServerHandler(sock, '127.0.0.1:80', None)
self.sock = FakeHttplibSocket(sock.result)
self.http_response = httplib.HTTPResponse(self.sock)
def set_debuglevel(self, level):
pass
def getresponse(self):
self.http_response.begin()
return self.http_response
def getresponsebody(self):
return self.sock.result
class NetAppDirectCmodeISCSIDriverTestCase(test.TestCase):
"""Test case for NetAppISCSIDriver"""
volume = {'name': 'lun1', 'size': 2, 'volume_name': 'lun1',
'os_type': 'linux', 'provider_location': 'lun1',
'id': 'lun1', 'provider_auth': None, 'project_id': 'project',
'display_name': None, 'display_description': 'lun1',
'volume_type_id': None, 'host': 'hostname@backend#vol1'}
snapshot = {'name': 'snapshot1', 'size': 2, 'volume_name': 'lun1',
'volume_size': 2, 'project_id': 'project',
'display_name': None, 'display_description': 'lun1',
'volume_type_id': None}
snapshot_fail = {'name': 'snapshot2', 'size': 2, 'volume_name': 'lun1',
'volume_size': 1, 'project_id': 'project'}
volume_sec = {'name': 'vol_snapshot', 'size': 2, 'volume_name': 'lun1',
'os_type': 'linux', 'provider_location': 'lun1',
'id': 'lun1', 'provider_auth': None, 'project_id': 'project',
'display_name': None, 'display_description': 'lun1',
'volume_type_id': None}
volume_clone = {'name': 'cl_sm', 'size': 3, 'volume_name': 'lun1',
'os_type': 'linux', 'provider_location': 'cl_sm',
'id': 'lun1', 'provider_auth': None,
'project_id': 'project', 'display_name': None,
'display_description': 'lun1',
'volume_type_id': None}
volume_clone_large = {'name': 'cl_lg', 'size': 6, 'volume_name': 'lun1',
'os_type': 'linux', 'provider_location': 'cl_lg',
'id': 'lun1', 'provider_auth': None,
'project_id': 'project', 'display_name': None,
'display_description': 'lun1',
'volume_type_id': None}
connector = {'initiator': 'iqn.1993-08.org.debian:01:10'}
vol_fail = {'name': 'lun_fail', 'size': 10000, 'volume_name': 'lun1',
'os_type': 'linux', 'provider_location': 'lun1',
'id': 'lun1', 'provider_auth': None, 'project_id': 'project',
'display_name': None, 'display_description': 'lun1',
'volume_type_id': None, 'host': 'hostname@backend#vol1'}
vol1 = ssc_cmode.NetAppVolume('lun1', 'openstack')
vol1.state['vserver_root'] = False
vol1.state['status'] = 'online'
vol1.state['junction_active'] = True
vol1.space['size_avl_bytes'] = '4000000000'
vol1.space['size_total_bytes'] = '5000000000'
vol1.space['space-guarantee-enabled'] = False
vol1.space['space-guarantee'] = 'file'
vol1.space['thin_provisioned'] = True
vol1.mirror['mirrored'] = True
vol1.qos['qos_policy_group'] = None
vol1.aggr['name'] = 'aggr1'
vol1.aggr['junction'] = '/vola'
vol1.sis['dedup'] = True
vol1.sis['compression'] = True
vol1.aggr['raid_type'] = 'raiddp'
vol1.aggr['ha_policy'] = 'cfo'
vol1.aggr['disk_type'] = 'SSD'
ssc_map = {'mirrored': set([vol1]), 'dedup': set([vol1]),
'compression': set([vol1]),
'thin': set([vol1]), 'all': set([vol1])}
def setUp(self):
super(NetAppDirectCmodeISCSIDriverTestCase, self).setUp()
self._custom_setup()
def _custom_setup(self):
self.stubs.Set(
ssc_cmode, 'refresh_cluster_ssc',
lambda a, b, c, synchronous: None)
self.mock_object(utils, 'OpenStackInfo')
configuration = self._set_config(create_configuration())
driver = common.NetAppDriver(configuration=configuration)
self.stubs.Set(httplib, 'HTTPConnection',
FakeDirectCmodeHTTPConnection)
driver.do_setup(context='')
self.driver = driver
self.driver.ssc_vols = self.ssc_map
def _set_config(self, configuration):
configuration.netapp_storage_protocol = 'iscsi'
configuration.netapp_login = 'admin'
configuration.netapp_password = 'pass'
configuration.netapp_server_hostname = '127.0.0.1'
configuration.netapp_transport_type = 'http'
configuration.netapp_server_port = None
configuration.netapp_vserver = 'openstack'
return configuration
def test_connect(self):
self.driver.library.zapi_client = mock.MagicMock()
self.driver.library.zapi_client.get_ontapi_version.return_value = \
(1, 20)
self.driver.check_for_setup_error()
def test_do_setup_all_default(self):
self.mock_object(utils, 'OpenStackInfo')
configuration = self._set_config(create_configuration())
driver = common.NetAppDriver(configuration=configuration)
driver.do_setup(context='')
na_server = driver.library.zapi_client.get_connection()
self.assertEqual('80', na_server.get_port())
self.assertEqual('http', na_server.get_transport_type())
@mock.patch.object(client_base.Client, 'get_ontapi_version',
mock.Mock(return_value=(1, 20)))
def test_do_setup_http_default_port(self):
self.mock_object(utils, 'OpenStackInfo')
configuration = self._set_config(create_configuration())
configuration.netapp_transport_type = 'http'
driver = common.NetAppDriver(configuration=configuration)
driver.do_setup(context='')
na_server = driver.library.zapi_client.get_connection()
self.assertEqual('80', na_server.get_port())
self.assertEqual('http', na_server.get_transport_type())
@mock.patch.object(client_base.Client, 'get_ontapi_version',
mock.Mock(return_value=(1, 20)))
def test_do_setup_https_default_port(self):
self.mock_object(utils, 'OpenStackInfo')
configuration = self._set_config(create_configuration())
configuration.netapp_transport_type = 'https'
driver = common.NetAppDriver(configuration=configuration)
driver.library._get_root_volume_name = mock.Mock()
driver.do_setup(context='')
na_server = driver.library.zapi_client.get_connection()
self.assertEqual('443', na_server.get_port())
self.assertEqual('https', na_server.get_transport_type())
@mock.patch.object(client_base.Client, 'get_ontapi_version',
mock.Mock(return_value=(1, 20)))
def test_do_setup_http_non_default_port(self):
self.mock_object(utils, 'OpenStackInfo')
configuration = self._set_config(create_configuration())
configuration.netapp_server_port = 81
driver = common.NetAppDriver(configuration=configuration)
driver.do_setup(context='')
na_server = driver.library.zapi_client.get_connection()
self.assertEqual('81', na_server.get_port())
self.assertEqual('http', na_server.get_transport_type())
@mock.patch.object(client_base.Client, 'get_ontapi_version',
mock.Mock(return_value=(1, 20)))
def test_do_setup_https_non_default_port(self):
self.mock_object(utils, 'OpenStackInfo')
configuration = self._set_config(create_configuration())
configuration.netapp_transport_type = 'https'
configuration.netapp_server_port = 446
driver = common.NetAppDriver(configuration=configuration)
driver.library._get_root_volume_name = mock.Mock()
driver.do_setup(context='')
na_server = driver.library.zapi_client.get_connection()
self.assertEqual('446', na_server.get_port())
self.assertEqual('https', na_server.get_transport_type())
def test_create_destroy(self):
self.driver.create_volume(self.volume)
self.driver.delete_volume(self.volume)
def test_create_vol_snapshot_destroy(self):
self.driver.create_volume(self.volume)
self.driver.create_snapshot(self.snapshot)
self.driver.create_volume_from_snapshot(self.volume_sec, self.snapshot)
self.driver.delete_snapshot(self.snapshot)
self.driver.delete_volume(self.volume)
def test_map_unmap(self):
self.driver.create_volume(self.volume)
updates = self.driver.create_export(None, self.volume)
self.assertTrue(updates['provider_location'])
self.volume['provider_location'] = updates['provider_location']
connection_info = self.driver.initialize_connection(self.volume,
self.connector)
self.assertEqual(connection_info['driver_volume_type'], 'iscsi')
properties = connection_info['data']
if not properties:
raise AssertionError('Target portal is none')
self.driver.terminate_connection(self.volume, self.connector)
self.driver.delete_volume(self.volume)
def test_cloned_volume_destroy(self):
self.driver.create_volume(self.volume)
self.driver.create_cloned_volume(self.snapshot, self.volume)
self.driver.delete_volume(self.snapshot)
self.driver.delete_volume(self.volume)
def test_map_by_creating_igroup(self):
self.driver.create_volume(self.volume)
updates = self.driver.create_export(None, self.volume)
self.assertTrue(updates['provider_location'])
self.volume['provider_location'] = updates['provider_location']
connector_new = {'initiator': 'iqn.1993-08.org.debian:01:1001'}
connection_info = self.driver.initialize_connection(self.volume,
connector_new)
self.assertEqual(connection_info['driver_volume_type'], 'iscsi')
properties = connection_info['data']
if not properties:
raise AssertionError('Target portal is none')
def test_vol_stats(self):
self.mock_object(client_base.Client, 'provide_ems')
stats = self.driver.get_volume_stats(refresh=True)
self.assertEqual(stats['vendor_name'], 'NetApp')
def test_create_vol_snapshot_diff_size_resize(self):
self.driver.create_volume(self.volume)
self.driver.create_snapshot(self.snapshot)
self.driver.create_volume_from_snapshot(
self.volume_clone, self.snapshot)
self.driver.delete_snapshot(self.snapshot)
self.driver.delete_volume(self.volume)
def test_create_vol_snapshot_diff_size_subclone(self):
self.driver.create_volume(self.volume)
self.driver.create_snapshot(self.snapshot)
self.driver.create_volume_from_snapshot(
self.volume_clone_large, self.snapshot)
self.driver.delete_snapshot(self.snapshot)
self.driver.delete_volume(self.volume)
def test_extend_vol_same_size(self):
self.driver.create_volume(self.volume)
self.driver.extend_volume(self.volume, self.volume['size'])
def test_extend_vol_direct_resize(self):
self.driver.create_volume(self.volume)
self.driver.extend_volume(self.volume, 3)
def test_extend_vol_sub_lun_clone(self):
self.driver.create_volume(self.volume)
self.driver.extend_volume(self.volume, 4)
class NetAppDriverNegativeTestCase(test.TestCase):
"""Test case for NetAppDriver"""
def setUp(self):
super(NetAppDriverNegativeTestCase, self).setUp()
def test_incorrect_family(self):
self.mock_object(utils, 'OpenStackInfo')
configuration = create_configuration()
configuration.netapp_storage_family = 'xyz_abc'
try:
common.NetAppDriver(configuration=configuration)
raise AssertionError('Wrong storage family is getting accepted.')
except exception.InvalidInput:
pass
def test_incorrect_protocol(self):
self.mock_object(utils, 'OpenStackInfo')
configuration = create_configuration()
configuration.netapp_storage_family = 'ontap'
configuration.netapp_storage_protocol = 'ontap'
try:
common.NetAppDriver(configuration=configuration)
raise AssertionError('Wrong storage protocol is getting accepted.')
except exception.InvalidInput:
pass
class FakeDirect7MODEServerHandler(FakeHTTPRequestHandler):
"""HTTP handler that fakes enough stuff to allow the driver to run."""
def do_GET(s):
"""Respond to a GET request."""
if '/servlets/netapp.servlets.admin.XMLrequest_filer' not in s.path:
s.send_response(404)
s.end_headers
return
s.send_response(200)
s.send_header("Content-Type", "text/xml; charset=utf-8")
s.end_headers()
out = s.wfile
out.write('<netapp version="1.15">'
'<results reason="Not supported method type"'
' status="failed" errno="Not_Allowed"/></netapp>')
def do_POST(s):
"""Respond to a POST request."""
if '/servlets/netapp.servlets.admin.XMLrequest_filer' not in s.path:
s.send_response(404)
s.end_headers
return
request_xml = s.rfile.read(int(s.headers['Content-Length']))
root = etree.fromstring(request_xml)
body = [x for x in root.iterchildren()]
request = body[0]
tag = request.tag
api = etree.QName(tag).localname or tag
if 'lun-list-info' == api:
body = """<results status="passed">
<are-vols-onlining>false</are-vols-onlining>
<are-vols-busy>false</are-vols-busy>
<luns>
<lun-info>
<path>/vol/vol1/lun1</path>
<size>20971520</size>
<online>true</online>
<mapped>false</mapped>
<read-only>false</read-only>
<staging>false</staging>
<share-state>none</share-state>
<multiprotocol-type>linux</multiprotocol-type>
<uuid>e867d844-c2c0-11e0-9282-00a09825b3b5</uuid>
<serial-number>P3lgP4eTyaNl</serial-number>
<block-size>512</block-size>
<is-space-reservation-enabled>true</is-space-reservation-enabled>
<size-used>0</size-used>
<alignment>indeterminate</alignment>
</lun-info>
<lun-info>
<path>/vol/vol1/lun1</path>
<size>20971520</size>
<online>true</online>
<mapped>false</mapped>
<read-only>false</read-only>
<staging>false</staging>
<share-state>none</share-state>
<multiprotocol-type>linux</multiprotocol-type>
<uuid>8e1e9284-c288-11e0-9282-00a09825b3b5</uuid>
<serial-number>P3lgP4eTc3lp</serial-number>
<block-size>512</block-size>
<is-space-reservation-enabled>true</is-space-reservation-enabled>
<size-used>0</size-used>
<alignment>indeterminate</alignment>
</lun-info>
</luns>
</results>"""
elif 'volume-list-info' == api:
body = """<results status="passed">
<volumes>
<volume-info>
<name>vol0</name>
<uuid>019c8f7a-9243-11e0-9281-00a09825b3b5</uuid>
<type>flex</type>
<block-type>32_bit</block-type>
<state>online</state>
<size-total>576914493440</size-total>
<size-used>13820354560</size-used>
<size-available>563094110208</size-available>
<percentage-used>2</percentage-used>
<snapshot-percent-reserved>20</snapshot-percent-reserved>
<snapshot-blocks-reserved>140848264</snapshot-blocks-reserved>
<reserve-required>0</reserve-required>
<reserve>0</reserve>
<reserve-used>0</reserve-used>
<reserve-used-actual>0</reserve-used-actual>
<files-total>20907162</files-total>
<files-used>7010</files-used>
<files-private-used>518</files-private-used>
<inodefile-public-capacity>31142</inodefile-public-capacity>
<inodefile-private-capacity>31142</inodefile-private-capacity>
<quota-init>0</quota-init>
<is-snaplock>false</is-snaplock>
<containing-aggregate>aggr0</containing-aggregate>
<sis>
<sis-info>
<state>disabled</state>
<status>idle</status>
<progress>idle for 70:36:44</progress>
<type>regular</type>
<schedule>sun-sat@0</schedule>
<last-operation-begin>Mon Aug 8 09:34:15 EST 2011
</last-operation-begin>
<last-operation-end>Mon Aug 8 09:34:15 EST 2011
</last-operation-end>
<last-operation-size>0</last-operation-size>
<size-shared>0</size-shared>
<size-saved>0</size-saved>
<percentage-saved>0</percentage-saved>
<compress-saved>0</compress-saved>
<percent-compress-saved>0</percent-compress-saved>
<dedup-saved>0</dedup-saved>
<percent-dedup-saved>0</percent-dedup-saved>
<total-saved>0</total-saved>
<percent-total-saved>0</percent-total-saved>
</sis-info>
</sis>
<compression-info>
<is-compression-enabled>false</is-compression-enabled>
</compression-info>
<space-reserve>volume</space-reserve>
<space-reserve-enabled>true</space-reserve-enabled>
<raid-size>14</raid-size>
<raid-status>raid_dp,sis</raid-status>
<checksum-style>block</checksum-style>
<is-checksum-enabled>true</is-checksum-enabled>
<is-inconsistent>false</is-inconsistent>
<is-unrecoverable>false</is-unrecoverable>
<is-invalid>false</is-invalid>
<is-in-snapmirror-jumpahead>false</is-in-snapmirror-jumpahead>
<mirror-status>unmirrored</mirror-status>
<disk-count>3</disk-count>
<plex-count>1</plex-count>
<plexes>
<plex-info>
<name>/aggr0/plex0</name>
<is-online>true</is-online>
<is-resyncing>false</is-resyncing>
</plex-info>
</plexes>
</volume-info>
<volume-info>
<name>vol1</name>
<uuid>2d50ecf4-c288-11e0-9282-00a09825b3b5</uuid>
<type>flex</type>
<block-type>32_bit</block-type>
<state>online</state>
<size-total>42949672960</size-total>
<size-used>44089344</size-used>
<size-available>42905583616</size-available>
<percentage-used>0</percentage-used>
<snapshot-percent-reserved>20</snapshot-percent-reserved>
<snapshot-blocks-reserved>10485760</snapshot-blocks-reserved>
<reserve-required>8192</reserve-required>
<reserve>8192</reserve>
<reserve-used>0</reserve-used>
<reserve-used-actual>0</reserve-used-actual>
<files-total>1556480</files-total>
<files-used>110</files-used>
<files-private-used>504</files-private-used>
<inodefile-public-capacity>31142</inodefile-public-capacity>
<inodefile-private-capacity>31142</inodefile-private-capacity>
<quota-init>0</quota-init>
<is-snaplock>false</is-snaplock>
<containing-aggregate>aggr1</containing-aggregate>
<sis>
<sis-info>
<state>disabled</state>
<status>idle</status>
<progress>idle for 89:19:59</progress>
<type>regular</type>
<schedule>sun-sat@0</schedule>
<last-operation-begin>Sun Aug 7 14:51:00 EST 2011
</last-operation-begin>
<last-operation-end>Sun Aug 7 14:51:00 EST 2011
</last-operation-end>
<last-operation-size>0</last-operation-size>
<size-shared>0</size-shared>
<size-saved>0</size-saved>
<percentage-saved>0</percentage-saved>
<compress-saved>0</compress-saved>
<percent-compress-saved>0</percent-compress-saved>
<dedup-saved>0</dedup-saved>
<percent-dedup-saved>0</percent-dedup-saved>
<total-saved>0</total-saved>
<percent-total-saved>0</percent-total-saved>
</sis-info>
</sis>
<compression-info>
<is-compression-enabled>false</is-compression-enabled>
</compression-info>
<space-reserve>volume</space-reserve>
<space-reserve-enabled>true</space-reserve-enabled>
<raid-size>7</raid-size>
<raid-status>raid4,sis</raid-status>
<checksum-style>block</checksum-style>
<is-checksum-enabled>true</is-checksum-enabled>
<is-inconsistent>false</is-inconsistent>
<is-unrecoverable>false</is-unrecoverable>
<is-invalid>false</is-invalid>
<is-in-snapmirror-jumpahead>false</is-in-snapmirror-jumpahead>
<mirror-status>unmirrored</mirror-status>
<disk-count>2</disk-count>
<plex-count>1</plex-count>
<plexes>
<plex-info>
<name>/aggr1/plex0</name>
<is-online>true</is-online>
<is-resyncing>false</is-resyncing>
</plex-info>
</plexes>
</volume-info>
</volumes>
</results>"""
elif 'volume-options-list-info' == api:
body = """<results status="passed">
<options>
<volume-option-info>
<name>snapmirrored</name>
<value>off</value>
</volume-option-info>
<volume-option-info>
<name>root</name>
<value>false</value>
</volume-option-info>
<volume-option-info>
<name>ha_policy</name>
<value>cfo</value>
</volume-option-info>
<volume-option-info>
<name>striping</name>
<value>not_striped</value>
</volume-option-info>
<volume-option-info>
<name>compression</name>
<value>off</value>
</volume-option-info>
</options>
</results>"""
elif 'lun-create-by-size' == api:
body = """<results status="passed">
<actual-size>22020096</actual-size></results>"""
elif 'lun-destroy' == api:
body = """<results status="passed"/>"""
elif 'igroup-list-info' == api:
body = """<results status="passed">
<initiator-groups>
<initiator-group-info>
<initiator-group-name>openstack-8bc96490</initiator-group-name>
<initiator-group-type>iscsi</initiator-group-type>
<initiator-group-uuid>b8e1d274-c378-11e0</initiator-group-uuid>
<initiator-group-os-type>linux</initiator-group-os-type>
<initiator-group-throttle-reserve>0</initiator-group-throttle-reserve>
<initiator-group-throttle-borrow>false
</initiator-group-throttle-borrow>
<initiator-group-vsa-enabled>false</initiator-group-vsa-enabled>
<initiator-group-alua-enabled>false</initiator-group-alua-enabled>
<initiator-group-report-scsi-name-enabled>true
</initiator-group-report-scsi-name-enabled>
<initiators>
<initiator-info>
<initiator-name>iqn.1993-08.org.debian:01:10</initiator-name>
</initiator-info>
</initiators>
</initiator-group-info>
<initiator-group-info>
<initiator-group-name>iscsi_group</initiator-group-name>
<initiator-group-type>iscsi</initiator-group-type>
<initiator-group-uuid>ccb8cbe4-c36f</initiator-group-uuid>
<initiator-group-os-type>linux</initiator-group-os-type>
<initiator-group-throttle-reserve>0</initiator-group-throttle-reserve>
<initiator-group-throttle-borrow>false
</initiator-group-throttle-borrow>
<initiator-group-vsa-enabled>false</initiator-group-vsa-enabled>
<initiator-group-alua-enabled>false</initiator-group-alua-enabled>
<initiator-group-report-scsi-name-enabled>true
</initiator-group-report-scsi-name-enabled>
<initiators>
<initiator-info>
<initiator-name>iqn.1993-08.org.debian:01:10ca</initiator-name>
</initiator-info>
</initiators>
</initiator-group-info>
</initiator-groups>
</results>"""
elif 'lun-map-list-info' == api:
body = """<results status="passed">
<initiator-groups/>
</results>"""
elif 'lun-map' == api:
body = """<results status="passed"><lun-id-assigned>1
</lun-id-assigned>
</results>"""
elif 'iscsi-node-get-name' == api:
body = """<results status="passed">
<node-name>iqn.1992-08.com.netapp:sn.135093938</node-name>
</results>"""
elif 'iscsi-portal-list-info' == api:
body = """<results status="passed">
<iscsi-portal-list-entries>
<iscsi-portal-list-entry-info>
<ip-address>10.61.176.156</ip-address>
<ip-port>3260</ip-port>
<tpgroup-tag>1000</tpgroup-tag>
<interface-name>e0a</interface-name>
</iscsi-portal-list-entry-info>
</iscsi-portal-list-entries>
</results>"""
elif 'igroup-create' == api:
body = """<results status="passed"/>"""
elif 'igroup-add' == api:
body = """<results status="passed"/>"""
elif 'clone-start' == api:
body = """<results status="passed">
<clone-id>
<clone-id-info>
<volume-uuid>2d50ecf4-c288-11e0-9282-00a09825b3b5</volume-uuid>
<clone-op-id>11</clone-op-id>
</clone-id-info>
</clone-id>
</results>"""
elif 'clone-list-status' == api:
body = """<results status="passed">
<status>
<ops-info>
<clone-state>completed</clone-state>
</ops-info>
</status>
</results>"""
elif 'lun-unmap' == api:
body = """<results status="passed"/>"""
elif 'system-get-ontapi-version' == api:
body = """<results status="passed">
<major-version>1</major-version>
<minor-version>8</minor-version>
</results>"""
elif 'lun-set-space-reservation-info' == api:
body = """<results status="passed"/>"""
elif 'ems-autosupport-log' == api:
body = """<results status="passed"/>"""
elif 'lun-resize' == api:
body = """<results status="passed"/>"""
elif 'lun-get-geometry' == api:
body = """<results status="passed">
<size>1</size>
<bytes-per-sector>2</bytes-per-sector>
<sectors-per-track>8</sectors-per-track>
<tracks-per-cylinder>2</tracks-per-cylinder>
<cylinders>4</cylinders>
<max-resize-size>5</max-resize-size>
</results>"""
elif 'volume-options-list-info' == api:
body = """<results status="passed">
<options>
<option>
<name>compression</name>
<value>off</value>
</option>
</options>
</results>"""
elif 'lun-move' == api:
body = """<results status="passed"/>"""
else:
# Unknown API
s.send_response(500)
s.end_headers
return
s.send_response(200)
s.send_header("Content-Type", "text/xml; charset=utf-8")
s.end_headers()
s.wfile.write(RESPONSE_PREFIX_DIRECT_7MODE)
s.wfile.write(RESPONSE_PREFIX_DIRECT)
s.wfile.write(body)
s.wfile.write(RESPONSE_SUFFIX_DIRECT)
class FakeDirect7modeHTTPConnection(object):
"""A fake httplib.HTTPConnection for netapp tests
Requests made via this connection actually get translated and routed into
the fake direct handler above, we then turn the response into
the httplib.HTTPResponse that the caller expects.
"""
def __init__(self, host, timeout=None):
self.host = host
def request(self, method, path, data=None, headers=None):
if not headers:
headers = {}
req_str = '%s %s HTTP/1.1\r\n' % (method, path)
for key, value in headers.iteritems():
req_str += "%s: %s\r\n" % (key, value)
if data:
req_str += '\r\n%s' % data
# NOTE(vish): normally the http transport normailizes from unicode
sock = FakeHttplibSocket(req_str.decode("latin-1").encode("utf-8"))
# NOTE(vish): stop the server from trying to look up address from
# the fake socket
FakeDirect7MODEServerHandler.address_string = lambda x: '127.0.0.1'
self.app = FakeDirect7MODEServerHandler(sock, '127.0.0.1:80', None)
self.sock = FakeHttplibSocket(sock.result)
self.http_response = httplib.HTTPResponse(self.sock)
def set_debuglevel(self, level):
pass
def getresponse(self):
self.http_response.begin()
return self.http_response
def getresponsebody(self):
return self.sock.result
class NetAppDirect7modeISCSIDriverTestCase_NV(
NetAppDirectCmodeISCSIDriverTestCase):
"""Test case for NetAppISCSIDriver
No vfiler
"""
def setUp(self):
super(NetAppDirect7modeISCSIDriverTestCase_NV, self).setUp()
def _custom_setup(self):
self.mock_object(utils, 'OpenStackInfo')
configuration = self._set_config(create_configuration())
driver = common.NetAppDriver(configuration=configuration)
self.stubs.Set(httplib, 'HTTPConnection',
FakeDirect7modeHTTPConnection)
driver.do_setup(context='')
driver.root_volume_name = 'root'
self.driver = driver
def _set_config(self, configuration):
configuration.netapp_storage_family = 'ontap_7mode'
configuration.netapp_storage_protocol = 'iscsi'
configuration.netapp_login = 'admin'
configuration.netapp_password = 'pass'
configuration.netapp_server_hostname = '127.0.0.1'
configuration.netapp_transport_type = 'http'
configuration.netapp_server_port = None
return configuration
def test_create_on_select_vol(self):
self.driver.volume_list = ['vol0', 'vol1']
self.driver.create_volume(self.volume)
self.driver.delete_volume(self.volume)
self.driver.volume_list = []
def test_connect(self):
self.driver.library.zapi_client = mock.MagicMock()
self.driver.library.zapi_client.get_ontapi_version.\
return_value = (1, 20)
self.driver.check_for_setup_error()
def test_check_for_setup_error_version(self):
drv = self.driver
drv.zapi_client = mock.Mock()
drv.zapi_client.get_ontapi_version.return_value = None
# check exception raises when version not found
self.assertRaises(exception.VolumeBackendAPIException,
drv.check_for_setup_error)
drv.zapi_client.get_ontapi_version.return_value = (1, 8)
# check exception raises when not supported version
self.assertRaises(exception.VolumeBackendAPIException,
drv.check_for_setup_error)
class NetAppDirect7modeISCSIDriverTestCase_WV(
NetAppDirect7modeISCSIDriverTestCase_NV):
"""Test case for NetAppISCSIDriver
With vfiler
"""
def setUp(self):
super(NetAppDirect7modeISCSIDriverTestCase_WV, self).setUp()
def _custom_setup(self):
self.mock_object(utils, 'OpenStackInfo')
configuration = self._set_config(create_configuration())
driver = common.NetAppDriver(configuration=configuration)
self.stubs.Set(httplib, 'HTTPConnection',
FakeDirect7modeHTTPConnection)
driver.do_setup(context='')
self.driver = driver
self.driver.root_volume_name = 'root'
def _set_config(self, configuration):
configuration.netapp_storage_family = 'ontap_7mode'
configuration.netapp_storage_protocol = 'iscsi'
configuration.netapp_login = 'admin'
configuration.netapp_password = 'pass'
configuration.netapp_server_hostname = '127.0.0.1'
configuration.netapp_transport_type = 'http'
configuration.netapp_server_port = None
configuration.netapp_vfiler = 'openstack'
return configuration