Backport changes from Cinder to Nova-Volume

NetApp C-mode driver.
Generic NFS-based block device driver.
NetApp NFS-based block device driver.

blueprint netapp-volume-driver-cmode
blueprint nfs-files-as-virtual-block-devices
blueprint netapp-nfs-cinder-driver
bug 1037619
bug 1037622

Change-Id: I513c3f88bcb03f3b71a453f92f5912d7730a8bbc
This commit is contained in:
Ben Swartzlander 2012-09-01 23:39:39 -04:00
parent c915ced0a2
commit 6070869bec
4 changed files with 1291 additions and 0 deletions

View File

@ -1021,6 +1021,18 @@ class VolumeBackendAPIException(NovaException):
"backend API: data=%(data)s")
class NfsException(NovaException):
message = _("Unknown NFS exception")
class NfsNoSharesMounted(NotFound):
message = _("No mounted NFS shares found")
class NfsNoSuitableShareFound(NotFound):
message = _("There is no share which can host %(volume_size)sG")
class InstanceTypeCreateFailed(NovaException):
message = _("Unable to create instance type")

View File

@ -989,3 +989,392 @@ class NetAppDriverTestCase(test.TestCase):
properties = connection_info['data']
self.driver.terminate_connection(volume, connector)
self.driver._remove_destroy(self.VOLUME_NAME, self.PROJECT_ID)
WSDL_HEADER_CMODE = """<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:na="http://cloud.netapp.com/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.xmlsoap.org/wsdl/"
targetNamespace="http://cloud.netapp.com/" name="CloudStorageService">
"""
WSDL_TYPES_CMODE = """<types>
<xs:schema xmlns:na="http://cloud.netapp.com/"
xmlns:xs="http://www.w3.org/2001/XMLSchema" version="1.0"
targetNamespace="http://cloud.netapp.com/">
<xs:element name="ProvisionLun">
<xs:complexType>
<xs:all>
<xs:element name="Name" type="xs:string"/>
<xs:element name="Size" type="xsd:long"/>
<xs:element name="Metadata" type="na:Metadata" minOccurs="0"
maxOccurs="unbounded"/>
</xs:all>
</xs:complexType>
</xs:element>
<xs:element name="ProvisionLunResult">
<xs:complexType>
<xs:all>
<xs:element name="Lun" type="na:Lun"/>
</xs:all>
</xs:complexType>
</xs:element>
<xs:element name="DestroyLun">
<xs:complexType>
<xs:all>
<xs:element name="Handle" type="xsd:string"/>
</xs:all>
</xs:complexType>
</xs:element>
<xs:element name="DestroyLunResult">
<xs:complexType>
<xs:all/>
</xs:complexType>
</xs:element>
<xs:element name="CloneLun">
<xs:complexType>
<xs:all>
<xs:element name="Handle" type="xsd:string"/>
<xs:element name="NewName" type="xsd:string"/>
<xs:element name="Metadata" type="na:Metadata" minOccurs="0"
maxOccurs="unbounded"/>
</xs:all>
</xs:complexType>
</xs:element>
<xs:element name="CloneLunResult">
<xs:complexType>
<xs:all>
<xs:element name="Lun" type="na:Lun"/>
</xs:all>
</xs:complexType>
</xs:element>
<xs:element name="MapLun">
<xs:complexType>
<xs:all>
<xs:element name="Handle" type="xsd:string"/>
<xs:element name="InitiatorType" type="xsd:string"/>
<xs:element name="InitiatorName" type="xsd:string"/>
</xs:all>
</xs:complexType>
</xs:element>
<xs:element name="MapLunResult">
<xs:complexType>
<xs:all/>
</xs:complexType>
</xs:element>
<xs:element name="UnmapLun">
<xs:complexType>
<xs:all>
<xs:element name="Handle" type="xsd:string"/>
<xs:element name="InitiatorType" type="xsd:string"/>
<xs:element name="InitiatorName" type="xsd:string"/>
</xs:all>
</xs:complexType>
</xs:element>
<xs:element name="UnmapLunResult">
<xs:complexType>
<xs:all/>
</xs:complexType>
</xs:element>
<xs:element name="ListLuns">
<xs:complexType>
<xs:all>
<xs:element name="NameFilter" type="xsd:string" minOccurs="0"/>
</xs:all>
</xs:complexType>
</xs:element>
<xs:element name="ListLunsResult">
<xs:complexType>
<xs:all>
<xs:element name="Lun" type="na:Lun" minOccurs="0"
maxOccurs="unbounded"/>
</xs:all>
</xs:complexType>
</xs:element>
<xs:element name="GetLunTargetDetails">
<xs:complexType>
<xs:all>
<xs:element name="Handle" type="xsd:string"/>
<xs:element name="InitiatorType" type="xsd:string"/>
<xs:element name="InitiatorName" type="xsd:string"/>
</xs:all>
</xs:complexType>
</xs:element>
<xs:element name="GetLunTargetDetailsResult">
<xs:complexType>
<xs:all>
<xs:element name="TargetDetails" type="na:TargetDetails"
minOccurs="0" maxOccurs="unbounded"/>
</xs:all>
</xs:complexType>
</xs:element>
<xs:complexType name="Metadata">
<xs:sequence>
<xs:element name="Key" type="xs:string"/>
<xs:element name="Value" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="Lun">
<xs:sequence>
<xs:element name="Name" type="xs:string"/>
<xs:element name="Size" type="xs:long"/>
<xs:element name="Handle" type="xs:string"/>
<xs:element name="Metadata" type="na:Metadata" minOccurs="0"
maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="TargetDetails">
<xs:sequence>
<xs:element name="Address" type="xs:string"/>
<xs:element name="Port" type="xs:int"/>
<xs:element name="Portal" type="xs:int"/>
<xs:element name="Iqn" type="xs:string"/>
<xs:element name="LunNumber" type="xs:int"/>
</xs:sequence>
</xs:complexType>
</xs:schema></types>"""
WSDL_TRAILER_CMODE = """<service name="CloudStorageService">
<port name="CloudStoragePort" binding="na:CloudStorageBinding">
<soap:address location="http://hostname:8080/ws/ntapcloud"/>
</port>
</service>
</definitions>"""
RESPONSE_PREFIX_CMODE = """<?xml version='1.0' encoding='UTF-8'?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>"""
RESPONSE_SUFFIX_CMODE = """</soapenv:Body></soapenv:Envelope>"""
CMODE_APIS = ['ProvisionLun', 'DestroyLun', 'CloneLun', 'MapLun', 'UnmapLun',
'ListLuns', 'GetLunTargetDetails']
class FakeCMODEServerHandler(BaseHTTPServer.BaseHTTPRequestHandler):
"""HTTP handler that fakes enough stuff to allow the driver to run"""
def do_GET(s):
"""Respond to a GET request."""
if '/ntap_cloud.wsdl' != s.path:
s.send_response(404)
s.end_headers
return
s.send_response(200)
s.send_header("Content-Type", "application/wsdl+xml")
s.end_headers()
out = s.wfile
out.write(WSDL_HEADER_CMODE)
out.write(WSDL_TYPES_CMODE)
for api in CMODE_APIS:
out.write('<message name="%sRequest">' % api)
out.write('<part element="na:%s" name="req"/>' % api)
out.write('</message>')
out.write('<message name="%sResponse">' % api)
out.write('<part element="na:%sResult" name="res"/>' % api)
out.write('</message>')
out.write('<portType name="CloudStorage">')
for api in CMODE_APIS:
out.write('<operation name="%s">' % api)
out.write('<input message="na:%sRequest"/>' % api)
out.write('<output message="na:%sResponse"/>' % api)
out.write('</operation>')
out.write('</portType>')
out.write('<binding name="CloudStorageBinding" '
'type="na:CloudStorage">')
out.write('<soap:binding style="document" ' +
'transport="http://schemas.xmlsoap.org/soap/http"/>')
for api in CMODE_APIS:
out.write('<operation name="%s">' % api)
out.write('<soap:operation soapAction=""/>')
out.write('<input><soap:body use="literal"/></input>')
out.write('<output><soap:body use="literal"/></output>')
out.write('</operation>')
out.write('</binding>')
out.write(WSDL_TRAILER_CMODE)
def do_POST(s):
"""Respond to a POST request."""
if '/ws/ntapcloud' != s.path:
s.send_response(404)
s.end_headers
return
request_xml = s.rfile.read(int(s.headers['Content-Length']))
ntap_ns = 'http://cloud.netapp.com/'
nsmap = {'soapenv': 'http://schemas.xmlsoap.org/soap/envelope/',
'na': ntap_ns}
root = etree.fromstring(request_xml)
body = root.xpath('/soapenv:Envelope/soapenv:Body',
namespaces=nsmap)[0]
request = body.getchildren()[0]
tag = request.tag
if not tag.startswith('{' + ntap_ns + '}'):
s.send_response(500)
s.end_headers
return
api = tag[(2 + len(ntap_ns)):]
if 'ProvisionLun' == api:
body = """<ns:ProvisionLunResult xmlns:ns=
"http://cloud.netapp.com/">
<Lun><Name>lun1</Name><Size>20</Size>
<Handle>1d9c006c-a406-42f6-a23f-5ed7a6dc33e3</Handle>
<Metadata><Key>OsType</Key>
<Value>linux</Value></Metadata></Lun>
</ns:ProvisionLunResult>"""
elif 'DestroyLun' == api:
body = """<ns:DestroyLunResult xmlns:ns="http://cloud.netapp.com/"
/>"""
elif 'CloneLun' == api:
body = """<ns:CloneLunResult xmlns:ns="http://cloud.netapp.com/">
<Lun><Name>lun2</Name><Size>2</Size>
<Handle>98ea1791d228453899d422b4611642c3</Handle>
<Metadata><Key>OsType</Key>
<Value>linux</Value></Metadata>
</Lun></ns:CloneLunResult>"""
elif 'MapLun' == api:
body = """<ns1:MapLunResult xmlns:ns="http://cloud.netapp.com/"
/>"""
elif 'Unmap' == api:
body = """<ns1:UnmapLunResult xmlns:ns="http://cloud.netapp.com/"
/>"""
elif 'ListLuns' == api:
body = """<ns:ListLunsResult xmlns:ns="http://cloud.netapp.com/">
<Lun>
<Name>lun1</Name>
<Size>20</Size>
<Handle>asdjdnsd</Handle>
</Lun>
</ns:ListLunsResult>"""
elif 'GetLunTargetDetails' == api:
body = """<ns:GetLunTargetDetailsResult
xmlns:ns="http://cloud.netapp.com/">
<TargetDetail>
<Address>1.2.3.4</Address>
<Port>3260</Port>
<Portal>1000</Portal>
<Iqn>iqn.199208.com.netapp:sn.123456789</Iqn>
<LunNumber>0</LunNumber>
</TargetDetail>
</ns:GetLunTargetDetailsResult>"""
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_CMODE)
s.wfile.write(body)
s.wfile.write(RESPONSE_SUFFIX_CMODE)
class FakeCmodeHTTPConnection(object):
"""A fake httplib.HTTPConnection for netapp tests
Requests made via this connection actually get translated and routed into
the fake Dfm 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
FakeCMODEServerHandler.address_string = lambda x: '127.0.0.1'
self.app = FakeCMODEServerHandler(sock, '127.0.0.1:8080', 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 NetAppCmodeISCSIDriverTestCase(test.TestCase):
"""Test case for NetAppISCSIDriver"""
volume = {
'name': 'lun1', 'size': 1, '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
}
snapshot = {
'name': 'lun2', 'size': 1, 'volume_name': 'lun1',
'volume_size': 1, 'project_id': 'project'
}
volume_sec = {
'name': 'vol_snapshot', 'size': 1, '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
}
def setUp(self):
super(NetAppCmodeISCSIDriverTestCase, self).setUp()
driver = netapp.NetAppCmodeISCSIDriver()
self.stubs.Set(httplib, 'HTTPConnection', FakeCmodeHTTPConnection)
driver._create_client(wsdl_url='http://localhost:8080/ntap_cloud.wsdl',
login='root', password='password',
hostname='localhost', port=8080, cache=False)
self.driver = driver
def test_connect(self):
self.driver.check_for_setup_error()
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']
connector = {'initiator': 'init1'}
connection_info = self.driver.initialize_connection(self.volume,
connector)
self.assertEqual(connection_info['driver_volume_type'], 'iscsi')
properties = connection_info['data']
self.driver.terminate_connection(self.volume, connector)
self.driver.delete_volume(self.volume)

View File

@ -0,0 +1,261 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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.
"""Unit tests for the NetApp-specific NFS driver module (netapp_nfs)"""
from nova import context
from nova import exception
from nova import test
from nova.volume import netapp
from nova.volume import netapp_nfs
from nova.volume import nfs
from mox import IgnoreArg
from mox import IsA
from mox import MockObject
import mox
import suds
import types
class FakeVolume(object):
def __init__(self, size=0):
self.size = size
self.id = hash(self)
self.name = None
def __getitem__(self, key):
return self.__dict__[key]
class FakeSnapshot(object):
def __init__(self, volume_size=0):
self.volume_name = None
self.name = None
self.volume_id = None
self.volume_size = volume_size
self.user_id = None
self.status = None
def __getitem__(self, key):
return self.__dict__[key]
class FakeResponce(object):
def __init__(self, status):
"""
:param status: Either 'failed' or 'passed'
"""
self.Status = status
if status == 'failed':
self.Reason = 'Sample error'
class NetappNfsDriverTestCase(test.TestCase):
"""Test case for NetApp specific NFS clone driver"""
def setUp(self):
self._driver = netapp_nfs.NetAppNFSDriver()
self._mox = mox.Mox()
def tearDown(self):
self._mox.UnsetStubs()
def test_check_for_setup_error(self):
mox = self._mox
drv = self._driver
required_flags = [
'netapp_wsdl_url',
'netapp_login',
'netapp_password',
'netapp_server_hostname',
'netapp_server_port'
]
# check exception raises when flags are not set
self.assertRaises(exception.NovaException,
drv.check_for_setup_error)
# set required flags
for flag in required_flags:
setattr(netapp.FLAGS, flag, 'val')
mox.StubOutWithMock(nfs.NfsDriver, 'check_for_setup_error')
nfs.NfsDriver.check_for_setup_error()
mox.ReplayAll()
drv.check_for_setup_error()
mox.VerifyAll()
# restore initial FLAGS
for flag in required_flags:
delattr(netapp.FLAGS, flag)
def test_do_setup(self):
mox = self._mox
drv = self._driver
mox.StubOutWithMock(drv, 'check_for_setup_error')
mox.StubOutWithMock(netapp_nfs.NetAppNFSDriver, '_get_client')
drv.check_for_setup_error()
netapp_nfs.NetAppNFSDriver._get_client()
mox.ReplayAll()
drv.do_setup(IsA(context.RequestContext))
mox.VerifyAll()
def test_create_snapshot(self):
"""Test snapshot can be created and deleted"""
mox = self._mox
drv = self._driver
mox.StubOutWithMock(drv, '_clone_volume')
drv._clone_volume(IgnoreArg(), IgnoreArg(), IgnoreArg())
mox.ReplayAll()
drv.create_snapshot(FakeSnapshot())
mox.VerifyAll()
def test_create_volume_from_snapshot(self):
"""Tests volume creation from snapshot"""
drv = self._driver
mox = self._mox
volume = FakeVolume(1)
snapshot = FakeSnapshot(2)
self.assertRaises(exception.NovaException,
drv.create_volume_from_snapshot,
volume,
snapshot)
snapshot = FakeSnapshot(1)
location = '127.0.0.1:/nfs'
expected_result = {'provider_location': location}
mox.StubOutWithMock(drv, '_clone_volume')
mox.StubOutWithMock(drv, '_get_volume_location')
drv._clone_volume(IgnoreArg(), IgnoreArg(), IgnoreArg())
drv._get_volume_location(IgnoreArg()).AndReturn(location)
mox.ReplayAll()
loc = drv.create_volume_from_snapshot(volume, snapshot)
self.assertEquals(loc, expected_result)
mox.VerifyAll()
def _prepare_delete_snapshot_mock(self, snapshot_exists):
drv = self._driver
mox = self._mox
mox.StubOutWithMock(drv, '_get_provider_location')
mox.StubOutWithMock(drv, '_volume_not_present')
if snapshot_exists:
mox.StubOutWithMock(drv, '_execute')
mox.StubOutWithMock(drv, '_get_volume_path')
drv._get_provider_location(IgnoreArg())
drv._volume_not_present(IgnoreArg(), IgnoreArg())\
.AndReturn(not snapshot_exists)
if snapshot_exists:
drv._get_volume_path(IgnoreArg(), IgnoreArg())
drv._execute('rm', None, run_as_root=True)
mox.ReplayAll()
return mox
def test_delete_existing_snapshot(self):
drv = self._driver
mox = self._prepare_delete_snapshot_mock(True)
drv.delete_snapshot(FakeSnapshot())
mox.VerifyAll()
def test_delete_missing_snapshot(self):
drv = self._driver
mox = self._prepare_delete_snapshot_mock(False)
drv.delete_snapshot(FakeSnapshot())
mox.VerifyAll()
def _prepare_clone_mock(self, status):
drv = self._driver
mox = self._mox
volume = FakeVolume()
setattr(volume, 'provider_location', '127.0.0.1:/nfs')
drv._client = MockObject(suds.client.Client)
drv._client.factory = MockObject(suds.client.Factory)
drv._client.service = MockObject(suds.client.ServiceSelector)
# ApiProxy() method is generated by ServiceSelector at runtime from the
# XML, so mocking is impossible.
setattr(drv._client.service,
'ApiProxy',
types.MethodType(lambda *args, **kwargs: FakeResponce(status),
suds.client.ServiceSelector))
mox.StubOutWithMock(drv, '_get_host_id')
mox.StubOutWithMock(drv, '_get_full_export_path')
drv._get_host_id(IgnoreArg()).AndReturn('10')
drv._get_full_export_path(IgnoreArg(), IgnoreArg()).AndReturn('/nfs')
return mox
def test_successfull_clone_volume(self):
drv = self._driver
mox = self._prepare_clone_mock('passed')
mox.ReplayAll()
volume_name = 'volume_name'
clone_name = 'clone_name'
volume_id = volume_name + str(hash(volume_name))
drv._clone_volume(volume_name, clone_name, volume_id)
mox.VerifyAll()
def test_failed_clone_volume(self):
drv = self._driver
mox = self._prepare_clone_mock('failed')
mox.ReplayAll()
volume_name = 'volume_name'
clone_name = 'clone_name'
volume_id = volume_name + str(hash(volume_name))
self.assertRaises(exception.NovaException,
drv._clone_volume,
volume_name, clone_name, volume_id)
mox.VerifyAll()

629
nova/tests/test_nfs.py Normal file
View File

@ -0,0 +1,629 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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.
"""Unit tests for the NFS driver module"""
import __builtin__
import errno
import os
import mox as mox_lib
from mox import IgnoreArg
from mox import IsA
from mox import stubout
from nova import context
from nova import exception
from nova.exception import ProcessExecutionError
from nova import test
from nova.volume import nfs
class DumbVolume(object):
fields = {}
def __setitem__(self, key, value):
self.fields[key] = value
def __getitem__(self, item):
return self.fields[item]
class NfsDriverTestCase(test.TestCase):
"""Test case for NFS driver"""
TEST_NFS_EXPORT1 = 'nfs-host1:/export'
TEST_NFS_EXPORT2 = 'nfs-host2:/export'
TEST_SIZE_IN_GB = 1
TEST_MNT_POINT = '/mnt/nfs'
TEST_MNT_POINT_BASE = '/mnt/test'
TEST_LOCAL_PATH = '/mnt/nfs/volume-123'
TEST_FILE_NAME = 'test.txt'
TEST_SHARES_CONFIG_FILE = '/etc/cinder/test-shares.conf'
ONE_GB_IN_BYTES = 1024 * 1024 * 1024
def setUp(self):
self._driver = nfs.NfsDriver()
self._mox = mox_lib.Mox()
self.stubs = stubout.StubOutForTesting()
def tearDown(self):
self._mox.UnsetStubs()
self.stubs.UnsetAll()
def stub_out_not_replaying(self, obj, attr_name):
attr_to_replace = getattr(obj, attr_name)
stub = mox_lib.MockObject(attr_to_replace)
self.stubs.Set(obj, attr_name, stub)
def test_path_exists_should_return_true(self):
"""_path_exists should return True if stat returns 0"""
mox = self._mox
drv = self._driver
mox.StubOutWithMock(drv, '_execute')
drv._execute('stat', self.TEST_FILE_NAME, run_as_root=True)
mox.ReplayAll()
self.assertTrue(drv._path_exists(self.TEST_FILE_NAME))
mox.VerifyAll()
def test_path_exists_should_return_false(self):
"""_path_exists should return True if stat doesn't return 0"""
mox = self._mox
drv = self._driver
mox.StubOutWithMock(drv, '_execute')
drv._execute('stat', self.TEST_FILE_NAME, run_as_root=True).\
AndRaise(ProcessExecutionError(
stderr="stat: cannot stat `test.txt': No such file or directory"))
mox.ReplayAll()
self.assertFalse(drv._path_exists(self.TEST_FILE_NAME))
mox.VerifyAll()
def test_local_path(self):
"""local_path common use case"""
nfs.FLAGS.nfs_mount_point_base = self.TEST_MNT_POINT_BASE
drv = self._driver
volume = DumbVolume()
volume['provider_location'] = self.TEST_NFS_EXPORT1
volume['name'] = 'volume-123'
self.assertEqual('/mnt/test/12118957640568004265/volume-123',
drv.local_path(volume))
def test_mount_nfs_should_mount_correctly(self):
"""_mount_nfs common case usage"""
mox = self._mox
drv = self._driver
mox.StubOutWithMock(drv, '_path_exists')
drv._path_exists(self.TEST_MNT_POINT).AndReturn(True)
mox.StubOutWithMock(drv, '_execute')
drv._execute('mount', '-t', 'nfs', self.TEST_NFS_EXPORT1,
self.TEST_MNT_POINT, run_as_root=True)
mox.ReplayAll()
drv._mount_nfs(self.TEST_NFS_EXPORT1, self.TEST_MNT_POINT)
mox.VerifyAll()
def test_mount_nfs_should_suppress_already_mounted_error(self):
"""_mount_nfs should suppress already mounted error if ensure=True
"""
mox = self._mox
drv = self._driver
mox.StubOutWithMock(drv, '_path_exists')
drv._path_exists(self.TEST_MNT_POINT).AndReturn(True)
mox.StubOutWithMock(drv, '_execute')
drv._execute('mount', '-t', 'nfs', self.TEST_NFS_EXPORT1,
self.TEST_MNT_POINT, run_as_root=True).\
AndRaise(ProcessExecutionError(
stderr='is busy or already mounted'))
mox.ReplayAll()
drv._mount_nfs(self.TEST_NFS_EXPORT1, self.TEST_MNT_POINT, ensure=True)
mox.VerifyAll()
def test_mount_nfs_should_reraise_already_mounted_error(self):
"""_mount_nfs should not suppress already mounted error if ensure=False
"""
mox = self._mox
drv = self._driver
mox.StubOutWithMock(drv, '_path_exists')
drv._path_exists(self.TEST_MNT_POINT).AndReturn(True)
mox.StubOutWithMock(drv, '_execute')
drv._execute('mount', '-t', 'nfs', self.TEST_NFS_EXPORT1,
self.TEST_MNT_POINT, run_as_root=True).\
AndRaise(ProcessExecutionError(stderr='is busy or already mounted'))
mox.ReplayAll()
self.assertRaises(ProcessExecutionError, drv._mount_nfs,
self.TEST_NFS_EXPORT1, self.TEST_MNT_POINT,
ensure=False)
mox.VerifyAll()
def test_mount_nfs_should_create_mountpoint_if_not_yet(self):
"""_mount_nfs should create mountpoint if it doesn't exist"""
mox = self._mox
drv = self._driver
mox.StubOutWithMock(drv, '_path_exists')
drv._path_exists(self.TEST_MNT_POINT).AndReturn(False)
mox.StubOutWithMock(drv, '_execute')
drv._execute('mkdir', '-p', self.TEST_MNT_POINT)
drv._execute(*([IgnoreArg()] * 5), run_as_root=IgnoreArg())
mox.ReplayAll()
drv._mount_nfs(self.TEST_NFS_EXPORT1, self.TEST_MNT_POINT)
mox.VerifyAll()
def test_mount_nfs_should_not_create_mountpoint_if_already(self):
"""_mount_nfs should not create mountpoint if it already exists"""
mox = self._mox
drv = self._driver
mox.StubOutWithMock(drv, '_path_exists')
drv._path_exists(self.TEST_MNT_POINT).AndReturn(True)
mox.StubOutWithMock(drv, '_execute')
drv._execute(*([IgnoreArg()] * 5), run_as_root=IgnoreArg())
mox.ReplayAll()
drv._mount_nfs(self.TEST_NFS_EXPORT1, self.TEST_MNT_POINT)
mox.VerifyAll()
def test_get_hash_str(self):
"""_get_hash_str should calculation correct value"""
drv = self._driver
self.assertEqual('12118957640568004265',
drv._get_hash_str(self.TEST_NFS_EXPORT1))
def test_get_mount_point_for_share(self):
"""_get_mount_point_for_share should calculate correct value"""
drv = self._driver
nfs.FLAGS.nfs_mount_point_base = self.TEST_MNT_POINT_BASE
self.assertEqual('/mnt/test/12118957640568004265',
drv._get_mount_point_for_share(self.TEST_NFS_EXPORT1))
def test_get_available_capacity_with_df(self):
"""_get_available_capacity should calculate correct value"""
mox = self._mox
drv = self._driver
df_avail = 1490560
df_head = 'Filesystem 1K-blocks Used Available Use% Mounted on\n'
df_data = 'nfs-host:/export 2620544 996864 %d 41%% /mnt' % df_avail
df_output = df_head + df_data
setattr(nfs.FLAGS, 'nfs_disk_util', 'df')
mox.StubOutWithMock(drv, '_get_mount_point_for_share')
drv._get_mount_point_for_share(self.TEST_NFS_EXPORT1).\
AndReturn(self.TEST_MNT_POINT)
mox.StubOutWithMock(drv, '_execute')
drv._execute('df', '-P', '-B', '1', self.TEST_MNT_POINT,
run_as_root=True).AndReturn((df_output, None))
mox.ReplayAll()
self.assertEquals(df_avail,
drv._get_available_capacity(self.TEST_NFS_EXPORT1))
mox.VerifyAll()
delattr(nfs.FLAGS, 'nfs_disk_util')
def test_get_available_capacity_with_du(self):
"""_get_available_capacity should calculate correct value"""
mox = self._mox
drv = self._driver
setattr(nfs.FLAGS, 'nfs_disk_util', 'du')
df_total_size = 2620544
df_used_size = 996864
df_avail_size = 1490560
df_title = 'Filesystem 1-blocks Used Available Use% Mounted on\n'
df_mnt_data = 'nfs-host:/export %d %d %d 41%% /mnt' % (df_total_size,
df_used_size,
df_avail_size)
df_output = df_title + df_mnt_data
du_used = 490560
du_output = '%d /mnt' % du_used
mox.StubOutWithMock(drv, '_get_mount_point_for_share')
drv._get_mount_point_for_share(self.TEST_NFS_EXPORT1).\
AndReturn(self.TEST_MNT_POINT)
mox.StubOutWithMock(drv, '_execute')
drv._execute('df', '-P', '-B', '1', self.TEST_MNT_POINT,
run_as_root=True).\
AndReturn((df_output, None))
drv._execute('du', '-sb', '--apparent-size',
'--exclude', '*snapshot*',
self.TEST_MNT_POINT,
run_as_root=True).AndReturn((du_output, None))
mox.ReplayAll()
self.assertEquals(df_total_size - du_used,
drv._get_available_capacity(self.TEST_NFS_EXPORT1))
mox.VerifyAll()
delattr(nfs.FLAGS, 'nfs_disk_util')
def test_load_shares_config(self):
mox = self._mox
drv = self._driver
nfs.FLAGS.nfs_shares_config = self.TEST_SHARES_CONFIG_FILE
mox.StubOutWithMock(__builtin__, 'open')
config_data = []
config_data.append(self.TEST_NFS_EXPORT1)
config_data.append('#' + self.TEST_NFS_EXPORT2)
config_data.append('')
__builtin__.open(self.TEST_SHARES_CONFIG_FILE).AndReturn(config_data)
mox.ReplayAll()
shares = drv._load_shares_config()
self.assertEqual([self.TEST_NFS_EXPORT1], shares)
mox.VerifyAll()
def test_ensure_share_mounted(self):
"""_ensure_share_mounted simple use case"""
mox = self._mox
drv = self._driver
mox.StubOutWithMock(drv, '_get_mount_point_for_share')
drv._get_mount_point_for_share(self.TEST_NFS_EXPORT1).\
AndReturn(self.TEST_MNT_POINT)
mox.StubOutWithMock(drv, '_mount_nfs')
drv._mount_nfs(self.TEST_NFS_EXPORT1, self.TEST_MNT_POINT, ensure=True)
mox.ReplayAll()
drv._ensure_share_mounted(self.TEST_NFS_EXPORT1)
mox.VerifyAll()
def test_ensure_shares_mounted_should_save_mounting_successfully(self):
"""_ensure_shares_mounted should save share if mounted with success"""
mox = self._mox
drv = self._driver
mox.StubOutWithMock(drv, '_load_shares_config')
drv._load_shares_config().AndReturn([self.TEST_NFS_EXPORT1])
mox.StubOutWithMock(drv, '_ensure_share_mounted')
drv._ensure_share_mounted(self.TEST_NFS_EXPORT1)
mox.ReplayAll()
drv._ensure_shares_mounted()
self.assertEqual(1, len(drv._mounted_shares))
self.assertEqual(self.TEST_NFS_EXPORT1, drv._mounted_shares[0])
mox.VerifyAll()
def test_ensure_shares_mounted_should_not_save_mounting_with_error(self):
"""_ensure_shares_mounted should not save share if failed to mount"""
mox = self._mox
drv = self._driver
mox.StubOutWithMock(drv, '_load_shares_config')
drv._load_shares_config().AndReturn([self.TEST_NFS_EXPORT1])
mox.StubOutWithMock(drv, '_ensure_share_mounted')
drv._ensure_share_mounted(self.TEST_NFS_EXPORT1).AndRaise(Exception())
mox.ReplayAll()
drv._ensure_shares_mounted()
self.assertEqual(0, len(drv._mounted_shares))
mox.VerifyAll()
def test_setup_should_throw_error_if_shares_config_not_configured(self):
"""do_setup should throw error if shares config is not configured """
drv = self._driver
nfs.FLAGS.nfs_shares_config = self.TEST_SHARES_CONFIG_FILE
self.assertRaises(exception.NfsException,
drv.do_setup, IsA(context.RequestContext))
def test_setup_should_throw_exception_if_nfs_client_is_not_installed(self):
"""do_setup should throw error if nfs client is not installed """
mox = self._mox
drv = self._driver
nfs.FLAGS.nfs_shares_config = self.TEST_SHARES_CONFIG_FILE
mox.StubOutWithMock(os.path, 'exists')
os.path.exists(self.TEST_SHARES_CONFIG_FILE).AndReturn(True)
mox.StubOutWithMock(drv, '_execute')
drv._execute('mount.nfs', check_exit_code=False).\
AndRaise(OSError(errno.ENOENT, 'No such file or directory'))
mox.ReplayAll()
self.assertRaises(exception.NfsException,
drv.do_setup, IsA(context.RequestContext))
mox.VerifyAll()
def test_find_share_should_throw_error_if_there_is_no_mounted_shares(self):
"""_find_share should throw error if there is no mounted shares"""
drv = self._driver
drv._mounted_shares = []
self.assertRaises(exception.NotFound, drv._find_share,
self.TEST_SIZE_IN_GB)
def test_find_share(self):
"""_find_share simple use case"""
mox = self._mox
drv = self._driver
drv._mounted_shares = [self.TEST_NFS_EXPORT1, self.TEST_NFS_EXPORT2]
mox.StubOutWithMock(drv, '_get_available_capacity')
drv._get_available_capacity(self.TEST_NFS_EXPORT1).\
AndReturn(2 * self.ONE_GB_IN_BYTES)
drv._get_available_capacity(self.TEST_NFS_EXPORT2).\
AndReturn(3 * self.ONE_GB_IN_BYTES)
mox.ReplayAll()
self.assertEqual(self.TEST_NFS_EXPORT2,
drv._find_share(self.TEST_SIZE_IN_GB))
mox.VerifyAll()
def test_find_share_should_throw_error_if_there_is_no_enough_place(self):
"""_find_share should throw error if there is no share to host vol"""
mox = self._mox
drv = self._driver
drv._mounted_shares = [self.TEST_NFS_EXPORT1, self.TEST_NFS_EXPORT2]
mox.StubOutWithMock(drv, '_get_available_capacity')
drv._get_available_capacity(self.TEST_NFS_EXPORT1).\
AndReturn(0)
drv._get_available_capacity(self.TEST_NFS_EXPORT2).\
AndReturn(0)
mox.ReplayAll()
self.assertRaises(exception.NfsNoSuitableShareFound, drv._find_share,
self.TEST_SIZE_IN_GB)
mox.VerifyAll()
def _simple_volume(self):
volume = DumbVolume()
volume['provider_location'] = '127.0.0.1:/mnt'
volume['name'] = 'volume_name'
volume['size'] = 10
return volume
def test_create_sparsed_volume(self):
mox = self._mox
drv = self._driver
volume = self._simple_volume()
setattr(nfs.FLAGS, 'nfs_sparsed_volumes', True)
mox.StubOutWithMock(drv, '_create_sparsed_file')
mox.StubOutWithMock(drv, '_set_rw_permissions_for_all')
drv._create_sparsed_file(IgnoreArg(), IgnoreArg())
drv._set_rw_permissions_for_all(IgnoreArg())
mox.ReplayAll()
drv._do_create_volume(volume)
mox.VerifyAll()
delattr(nfs.FLAGS, 'nfs_sparsed_volumes')
def test_create_nonsparsed_volume(self):
mox = self._mox
drv = self._driver
volume = self._simple_volume()
setattr(nfs.FLAGS, 'nfs_sparsed_volumes', False)
mox.StubOutWithMock(drv, '_create_regular_file')
mox.StubOutWithMock(drv, '_set_rw_permissions_for_all')
drv._create_regular_file(IgnoreArg(), IgnoreArg())
drv._set_rw_permissions_for_all(IgnoreArg())
mox.ReplayAll()
drv._do_create_volume(volume)
mox.VerifyAll()
delattr(nfs.FLAGS, 'nfs_sparsed_volumes')
def test_create_volume_should_ensure_nfs_mounted(self):
"""create_volume should ensure shares provided in config are mounted"""
mox = self._mox
drv = self._driver
self.stub_out_not_replaying(nfs, 'LOG')
self.stub_out_not_replaying(drv, '_find_share')
self.stub_out_not_replaying(drv, '_do_create_volume')
mox.StubOutWithMock(drv, '_ensure_shares_mounted')
drv._ensure_shares_mounted()
mox.ReplayAll()
volume = DumbVolume()
volume['size'] = self.TEST_SIZE_IN_GB
drv.create_volume(volume)
mox.VerifyAll()
def test_create_volume_should_return_provider_location(self):
"""create_volume should return provider_location with found share """
mox = self._mox
drv = self._driver
self.stub_out_not_replaying(nfs, 'LOG')
self.stub_out_not_replaying(drv, '_ensure_shares_mounted')
self.stub_out_not_replaying(drv, '_do_create_volume')
mox.StubOutWithMock(drv, '_find_share')
drv._find_share(self.TEST_SIZE_IN_GB).AndReturn(self.TEST_NFS_EXPORT1)
mox.ReplayAll()
volume = DumbVolume()
volume['size'] = self.TEST_SIZE_IN_GB
result = drv.create_volume(volume)
self.assertEqual(self.TEST_NFS_EXPORT1, result['provider_location'])
mox.VerifyAll()
def test_delete_volume(self):
"""delete_volume simple test case"""
mox = self._mox
drv = self._driver
self.stub_out_not_replaying(drv, '_ensure_share_mounted')
volume = DumbVolume()
volume['name'] = 'volume-123'
volume['provider_location'] = self.TEST_NFS_EXPORT1
mox.StubOutWithMock(drv, 'local_path')
drv.local_path(volume).AndReturn(self.TEST_LOCAL_PATH)
mox.StubOutWithMock(drv, '_path_exists')
drv._path_exists(self.TEST_LOCAL_PATH).AndReturn(True)
mox.StubOutWithMock(drv, '_execute')
drv._execute('rm', '-f', self.TEST_LOCAL_PATH, run_as_root=True)
mox.ReplayAll()
drv.delete_volume(volume)
mox.VerifyAll()
def test_delete_should_ensure_share_mounted(self):
"""delete_volume should ensure that corresponding share is mounted"""
mox = self._mox
drv = self._driver
self.stub_out_not_replaying(drv, '_execute')
volume = DumbVolume()
volume['name'] = 'volume-123'
volume['provider_location'] = self.TEST_NFS_EXPORT1
mox.StubOutWithMock(drv, '_ensure_share_mounted')
drv._ensure_share_mounted(self.TEST_NFS_EXPORT1)
mox.ReplayAll()
drv.delete_volume(volume)
mox.VerifyAll()
def test_delete_should_not_delete_if_provider_location_not_provided(self):
"""delete_volume shouldn't try to delete if provider_location missed"""
mox = self._mox
drv = self._driver
self.stub_out_not_replaying(drv, '_ensure_share_mounted')
volume = DumbVolume()
volume['name'] = 'volume-123'
volume['provider_location'] = None
mox.StubOutWithMock(drv, '_execute')
mox.ReplayAll()
drv.delete_volume(volume)
mox.VerifyAll()
def test_delete_should_not_delete_if_there_is_no_file(self):
"""delete_volume should not try to delete if file missed"""
mox = self._mox
drv = self._driver
self.stub_out_not_replaying(drv, '_ensure_share_mounted')
volume = DumbVolume()
volume['name'] = 'volume-123'
volume['provider_location'] = self.TEST_NFS_EXPORT1
mox.StubOutWithMock(drv, 'local_path')
drv.local_path(volume).AndReturn(self.TEST_LOCAL_PATH)
mox.StubOutWithMock(drv, '_path_exists')
drv._path_exists(self.TEST_LOCAL_PATH).AndReturn(False)
mox.StubOutWithMock(drv, '_execute')
mox.ReplayAll()
drv.delete_volume(volume)
mox.VerifyAll()