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:
parent
c915ced0a2
commit
6070869bec
@ -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")
|
||||
|
||||
|
@ -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)
|
||||
|
261
nova/tests/test_netapp_nfs.py
Normal file
261
nova/tests/test_netapp_nfs.py
Normal 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
629
nova/tests/test_nfs.py
Normal 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()
|
Loading…
Reference in New Issue
Block a user