removes the nova-volume code from nova
This removes the majority of the nova-volume code from the codebase. It updates relevent config options to default to cinder. It updates a number of existing tests that were depending on code that was removed. A few things still need to be removed: * volume/driver.py & volume/iscsi.py These files are used by the libvirt volume driver tests. These tests should be updated to mock the relevant calls. * scheduler/simple.py & scheduler/multi.py These files should no longer be necessary so they can be removed in a subsequent patch * exception.py cleanup Once the above files are removed there are a number of unused exceptions which can be removed * database calls and database tables The database calls have not been removed and the tables have not been dropped. This can be done in a separate migration * additional config options and nova.conf.sample There may be a few extra config options that can be removed and the conf sample can be regenerated Implements bp delete-nova-volume Change-Id: I0b540e54dbabd26901a7530035a38583bb521fda
This commit is contained in:
parent
17f4641ca1
commit
eac2536308
|
@ -70,8 +70,8 @@ if __name__ == '__main__':
|
|||
except (Exception, SystemExit):
|
||||
LOG.exception(_('Failed to load %s') % mod.__name__)
|
||||
|
||||
for binary in ['nova-compute', 'nova-volume',
|
||||
'nova-network', 'nova-scheduler', 'nova-cert']:
|
||||
for binary in ['nova-compute', 'nova-network', 'nova-scheduler',
|
||||
'nova-cert']:
|
||||
try:
|
||||
launcher.launch_server(service.Service.create(binary=binary))
|
||||
except (Exception, SystemExit):
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
|
||||
"""Starter script for Nova OS API."""
|
||||
|
||||
import eventlet
|
||||
eventlet.monkey_patch(os=False)
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(
|
||||
sys.argv[0]), os.pardir, os.pardir))
|
||||
if os.path.exists(os.path.join(possible_topdir, "nova", "__init__.py")):
|
||||
sys.path.insert(0, possible_topdir)
|
||||
|
||||
|
||||
from nova import flags
|
||||
from nova.openstack.common import log as logging
|
||||
from nova import service
|
||||
from nova import utils
|
||||
|
||||
if __name__ == '__main__':
|
||||
flags.parse_args(sys.argv)
|
||||
logging.setup("nova")
|
||||
utils.monkey_patch()
|
||||
server = service.WSGIService('osapi_volume')
|
||||
service.serve(server, workers=server.workers)
|
||||
service.wait()
|
|
@ -777,33 +777,6 @@ class VersionCommands(object):
|
|||
self.list()
|
||||
|
||||
|
||||
class VolumeCommands(object):
|
||||
"""Methods for dealing with a cloud in an odd state"""
|
||||
|
||||
@args('--volume', dest='volume_id', metavar='<volume id>',
|
||||
help='Volume ID')
|
||||
def reattach(self, volume_id):
|
||||
"""Re-attach a volume that has previously been attached
|
||||
to an instance. Typically called after a compute host
|
||||
has been rebooted."""
|
||||
|
||||
if 'cinder' in FLAGS.volume_api_class:
|
||||
print(_("\"nova-manage volume reattach\" only valid "
|
||||
"when using nova-volume service"))
|
||||
sys.exit(1)
|
||||
|
||||
ctxt = context.get_admin_context()
|
||||
volume = db.volume_get(ctxt, param2id(volume_id))
|
||||
|
||||
if not volume['instance_id']:
|
||||
print _("volume is not attached to an instance")
|
||||
return
|
||||
instance = db.instance_get(ctxt, volume['instance_id'])
|
||||
rpcapi = compute_rpcapi.ComputeAPI()
|
||||
rpcapi.attach_volume(ctxt, instance, volume['id'],
|
||||
volume['mountpoint'])
|
||||
|
||||
|
||||
class InstanceTypeCommands(object):
|
||||
"""Class for managing instance types / flavors."""
|
||||
|
||||
|
@ -1207,7 +1180,6 @@ CATEGORIES = [
|
|||
('sm', StorageManagerCommands),
|
||||
('version', VersionCommands),
|
||||
('vm', VmCommands),
|
||||
('volume', VolumeCommands),
|
||||
('vpn', VpnCommands),
|
||||
]
|
||||
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
|
||||
"""Starter script for Nova Volume."""
|
||||
|
||||
import eventlet
|
||||
eventlet.monkey_patch()
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# If ../nova/__init__.py exists, add ../ to Python search path, so that
|
||||
# it will override what happens to be installed in /usr/(local/)lib/python...
|
||||
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
|
||||
os.pardir,
|
||||
os.pardir))
|
||||
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
|
||||
sys.path.insert(0, possible_topdir)
|
||||
|
||||
|
||||
from nova import flags
|
||||
from nova.openstack.common import log as logging
|
||||
from nova import service
|
||||
from nova import utils
|
||||
|
||||
if __name__ == '__main__':
|
||||
flags.parse_args(sys.argv)
|
||||
FLAGS = flags.FLAGS
|
||||
logging.setup("nova")
|
||||
utils.monkey_patch()
|
||||
server = service.Service.create(binary='nova-volume',
|
||||
topic=FLAGS.volume_topic)
|
||||
service.serve(server)
|
||||
service.wait()
|
|
@ -1,81 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright (c) 2011 OpenStack, LLC.
|
||||
# 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.
|
||||
|
||||
"""Cron script to generate usage notifications for volumes existing during
|
||||
the audit period.
|
||||
|
||||
Together with the notifications generated by volumes
|
||||
create/delete/resize, over that time period, this allows an external
|
||||
system consuming usage notification feeds to calculate volume usage
|
||||
for each tenant.
|
||||
|
||||
Time periods are specified as 'hour', 'month', 'day' or 'year'
|
||||
|
||||
hour = previous hour. If run at 9:07am, will generate usage for 8-9am.
|
||||
month = previous month. If the script is run April 1, it will generate
|
||||
usages for March 1 through March 31.
|
||||
day = previous day. if run on July 4th, it generates usages for July 3rd.
|
||||
year = previous year. If run on Jan 1, it generates usages for
|
||||
Jan 1 through Dec 31 of the previous year.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import gettext
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
|
||||
# If ../nova/__init__.py exists, add ../ to Python search path, so that
|
||||
# it will override what happens to be installed in /usr/(local/)lib/python...
|
||||
POSSIBLE_TOPDIR = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
|
||||
os.pardir,
|
||||
os.pardir))
|
||||
if os.path.exists(os.path.join(POSSIBLE_TOPDIR, 'nova', '__init__.py')):
|
||||
sys.path.insert(0, POSSIBLE_TOPDIR)
|
||||
|
||||
gettext.install('nova', unicode=1)
|
||||
from nova import context
|
||||
from nova import db
|
||||
from nova import exception
|
||||
from nova import flags
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.openstack.common import rpc
|
||||
from nova import utils
|
||||
from nova.volume import utils as volume_utils
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
|
||||
if __name__ == '__main__':
|
||||
admin_context = context.get_admin_context()
|
||||
flags.FLAGS(sys.argv)
|
||||
logging.setup("nova")
|
||||
begin, end = utils.last_completed_audit_period()
|
||||
print _("Starting volume usage audit")
|
||||
print _("Creating usages for %s until %s") % (str(begin), str(end))
|
||||
volumes = db.volume_get_active_by_window(admin_context,
|
||||
begin,
|
||||
end)
|
||||
print _("Found %d volumes") % len(volumes)
|
||||
for volume_ref in volumes:
|
||||
try:
|
||||
volume_utils.notify_usage_exists(
|
||||
admin_context, volume_ref)
|
||||
except Exception, e:
|
||||
print traceback.format_exc(e)
|
||||
print _("Volume usage audit completed")
|
|
@ -328,14 +328,6 @@
|
|||
"namespace": "http://docs.openstack.org/compute/ext/virtual_interfaces/api/v1.1",
|
||||
"updated": "2011-08-17T00:00:00+00:00"
|
||||
},
|
||||
{
|
||||
"alias": "os-volume-types",
|
||||
"description": "Volume types support",
|
||||
"links": [],
|
||||
"name": "VolumeTypes",
|
||||
"namespace": "http://docs.openstack.org/compute/ext/volume_types/api/v1.1",
|
||||
"updated": "2011-08-24T00:00:00+00:00"
|
||||
},
|
||||
{
|
||||
"alias": "os-volumes",
|
||||
"description": "Volumes support",
|
||||
|
|
|
@ -137,9 +137,6 @@
|
|||
<extension alias="os-virtual-interfaces" updated="2011-08-17T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/virtual_interfaces/api/v1.1" name="VirtualInterfaces">
|
||||
<description>Virtual interface support</description>
|
||||
</extension>
|
||||
<extension alias="os-volume-types" updated="2011-08-24T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/volume_types/api/v1.1" name="VolumeTypes">
|
||||
<description>Volume types support</description>
|
||||
</extension>
|
||||
<extension alias="os-volumes" updated="2011-03-25T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/volumes/api/v1.1" name="Volumes">
|
||||
<description>Volumes support</description>
|
||||
</extension>
|
||||
|
|
|
@ -127,8 +127,6 @@ man_pages = [
|
|||
[u'OpenStack'], 1),
|
||||
('man/nova-api-os-compute', 'nova-api-os-compute',
|
||||
u'Cloud controller fabric', [u'OpenStack'], 1),
|
||||
('man/nova-api-os-volume', 'nova-api-os-volume',
|
||||
u'Cloud controller fabric', [u'OpenStack'], 1),
|
||||
('man/nova-api', 'nova-api', u'Cloud controller fabric',
|
||||
[u'OpenStack'], 1),
|
||||
('man/nova-cert', 'nova-cert', u'Cloud controller fabric',
|
||||
|
@ -155,10 +153,6 @@ man_pages = [
|
|||
[u'OpenStack'], 1),
|
||||
('man/nova-scheduler', 'nova-scheduler', u'Cloud controller fabric',
|
||||
[u'OpenStack'], 1),
|
||||
('man/nova-volume-usage-audit', 'nova-volume-usage-audit', u'Cloud controller fabric',
|
||||
[u'OpenStack'], 1),
|
||||
('man/nova-volume', 'nova-volume', u'Cloud controller fabric',
|
||||
[u'OpenStack'], 1),
|
||||
('man/nova-xvpvncproxy', 'nova-xvpvncproxy', u'Cloud controller fabric',
|
||||
[u'OpenStack'], 1)
|
||||
]
|
||||
|
|
|
@ -33,8 +33,8 @@ Below you will find a helpful explanation of the different components.
|
|||
[ Auth Manager ] ---
|
||||
| \- ( DB )
|
||||
|
|
||||
| [ scheduler ] - [ volume ] - ( iSCSI )
|
||||
| /
|
||||
|
|
||||
|
|
||||
[ Web Dashboard ] -> [ api ] -- < AMQP > ------ [ network ] - ( Flat/Vlan )
|
||||
| \
|
||||
< HTTP > [ scheduler ] - [ compute ] - ( libvirt/xen )
|
||||
|
@ -46,7 +46,6 @@ Below you will find a helpful explanation of the different components.
|
|||
* api: component that receives http requests, converts commands and communicates with other components via the queue or http (in the case of objectstore)
|
||||
* Auth Manager: component responsible for users/projects/and roles. Can backend to DB or LDAP. This is not a separate binary, but rather a python class that is used by most components in the system.
|
||||
* objectstore: http server that replicates s3 api and allows storage and retrieval of images
|
||||
* scheduler: decides which host gets each vm and volume
|
||||
* volume: manages dynamically attachable block devices.
|
||||
* scheduler: decides which host gets each vm
|
||||
* network: manages ip forwarding, bridges, and vlans
|
||||
* compute: manages communication with hypervisor and virtual machines.
|
||||
|
|
|
@ -67,8 +67,6 @@ Module Reference
|
|||
|
||||
services
|
||||
database
|
||||
volume
|
||||
xensmvolume
|
||||
compute
|
||||
network
|
||||
api
|
||||
|
|
|
@ -30,12 +30,12 @@ Nova uses direct, fanout, and topic-based exchanges. The architecture looks like
|
|||
|
||||
..
|
||||
|
||||
Nova implements RPC (both request+response, and one-way, respectively nicknamed 'rpc.call' and 'rpc.cast') over AMQP by providing an adapter class which take cares of marshaling and unmarshaling of messages into function calls. Each Nova service (for example Compute, Volume, etc.) create two queues at the initialization time, one which accepts messages with routing keys 'NODE-TYPE.NODE-ID' (for example compute.hostname) and another, which accepts messages with routing keys as generic 'NODE-TYPE' (for example compute). The former is used specifically when Nova-API needs to redirect commands to a specific node like 'euca-terminate instance'. In this case, only the compute node whose host's hypervisor is running the virtual machine can kill the instance. The API acts as a consumer when RPC calls are request/response, otherwise is acts as publisher only.
|
||||
Nova implements RPC (both request+response, and one-way, respectively nicknamed 'rpc.call' and 'rpc.cast') over AMQP by providing an adapter class which take cares of marshaling and unmarshaling of messages into function calls. Each Nova service (for example Compute, Scheduler, etc.) create two queues at the initialization time, one which accepts messages with routing keys 'NODE-TYPE.NODE-ID' (for example compute.hostname) and another, which accepts messages with routing keys as generic 'NODE-TYPE' (for example compute). The former is used specifically when Nova-API needs to redirect commands to a specific node like 'euca-terminate instance'. In this case, only the compute node whose host's hypervisor is running the virtual machine can kill the instance. The API acts as a consumer when RPC calls are request/response, otherwise is acts as publisher only.
|
||||
|
||||
Nova RPC Mappings
|
||||
-----------------
|
||||
|
||||
The figure below shows the internals of a message broker node (referred to as a RabbitMQ node in the diagrams) when a single instance is deployed and shared in an OpenStack cloud. Every Nova component connects to the message broker and, depending on its personality (for example a compute node or a network node), may use the queue either as an Invoker (such as API or Scheduler) or a Worker (such as Compute, Volume or Network). Invokers and Workers do not actually exist in the Nova object model, but we are going to use them as an abstraction for sake of clarity. An Invoker is a component that sends messages in the queuing system via two operations: 1) rpc.call and ii) rpc.cast; a Worker is a component that receives messages from the queuing system and reply accordingly to rcp.call operations.
|
||||
The figure below shows the internals of a message broker node (referred to as a RabbitMQ node in the diagrams) when a single instance is deployed and shared in an OpenStack cloud. Every Nova component connects to the message broker and, depending on its personality (for example a compute node or a network node), may use the queue either as an Invoker (such as API or Scheduler) or a Worker (such as Compute or Network). Invokers and Workers do not actually exist in the Nova object model, but we are going to use them as an abstraction for sake of clarity. An Invoker is a component that sends messages in the queuing system via two operations: 1) rpc.call and ii) rpc.cast; a Worker is a component that receives messages from the queuing system and reply accordingly to rcp.call operations.
|
||||
|
||||
Figure 2 shows the following internal elements:
|
||||
|
||||
|
@ -97,10 +97,8 @@ The figure below shows the status of a RabbitMQ node after Nova components' boot
|
|||
2. compute
|
||||
3. network.phantom (phantom is hostname)
|
||||
4. network
|
||||
5. volume.phantom (phantom is hostname)
|
||||
6. volume
|
||||
7. scheduler.phantom (phantom is hostname)
|
||||
8. scheduler
|
||||
5. scheduler.phantom (phantom is hostname)
|
||||
6. scheduler
|
||||
|
||||
.. image:: /images/rpc/state.png
|
||||
:width: 60%
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
..
|
||||
Copyright 2010-2011 United States Government as represented by the
|
||||
Administrator of the National Aeronautics and Space Administration.
|
||||
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.
|
||||
|
||||
Storage Volumes, Disks
|
||||
======================
|
||||
|
||||
.. todo:: rework after iSCSI merge (see 'Old Docs') (todd or vish)
|
||||
|
||||
|
||||
The :mod:`nova.volume.manager` Module
|
||||
-------------------------------------
|
||||
|
||||
.. automodule:: nova.volume.manager
|
||||
:noindex:
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
The :mod:`nova.volume.driver` Module
|
||||
-------------------------------------
|
||||
|
||||
.. automodule:: nova.volume.driver
|
||||
:noindex:
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Tests
|
||||
-----
|
||||
|
||||
The :mod:`volume_unittest` Module
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. automodule:: nova.tests.volume_unittest
|
||||
:noindex:
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
|
@ -1,88 +0,0 @@
|
|||
Xen Storage Manager Volume Driver
|
||||
=================================
|
||||
|
||||
The Xen Storage Manager (xensm) driver for Nova-Volume is based on XenAPI Storage Manager. This will not only provide basic storage functionality (like volume creation, and destruction) on a number of different storage back-ends, such as Netapp, NFS, etc. but it will also enable the capability of using more sophisticated storage back-ends for operations like cloning/snapshotting etc. To have an idea of the benefits of using XenAPI SM to provide back-end storage services, the list below shows some of the storage plugins already supported in XenServer/XCP:
|
||||
|
||||
- NFS VHD: SR plugin which stores disks as VHD files on a remote NFS filesystem
|
||||
- Local VHD on LVM: SR plugin which represents disks as VHD disks on Logical Volumes within a locally-attached Volume Group
|
||||
- HBA LUN-per-VDI driver: SR plugin which represents LUNs as VDIs sourced by hardware HBA adapters, e.g. hardware-based iSCSI or FC support
|
||||
- NetApp: SR driver for mapping of LUNs to VDIs on a NETAPP server, providing use of fast snapshot and clone features on the filer
|
||||
- LVHD over FC: SR plugin which represents disks as VHDs on Logical Volumes within a Volume Group created on an HBA LUN, e.g. hardware-based iSCSI or FC support
|
||||
- iSCSI: Base ISCSI SR driver, provides a LUN-per-VDI. Does not support creation of VDIs but accesses existing LUNs on a target.
|
||||
- LVHD over iSCSI: SR plugin which represents disks as Logical Volumes within a Volume Group created on an iSCSI LUN
|
||||
- EqualLogic: SR driver for mapping of LUNs to VDIs on an EQUALLOGIC array group, providing use of fast snapshot and clone features on the array
|
||||
|
||||
Glossary
|
||||
=========
|
||||
|
||||
XenServer: Commercial, supported product from Citrix
|
||||
|
||||
Xen Cloud Platform (XCP): Open-source equivalent of XenServer (and the development project for the toolstack). Everything said about XenServer below applies equally to XCP
|
||||
|
||||
XenAPI: The management API exposed by XenServer and XCP
|
||||
|
||||
xapi: The primary daemon on XenServer and Xen Cloud Platform; the one that exposes the XenAPI
|
||||
|
||||
|
||||
Design
|
||||
=======
|
||||
|
||||
Definitions
|
||||
-----------
|
||||
|
||||
Backend: A term for a particular storage backend. This could be iSCSI, NFS, Netapp etc.
|
||||
Backend-config: All the parameters required to connect to a specific backend. For e.g. For NFS, this would be the server, path, etc.
|
||||
Flavor: This term is equivalent to volume "types". A user friendly term to specify some notion of quality of service. For example, "gold" might mean that the volumes will use a backend where backups are possible.
|
||||
|
||||
A flavor can be associated with multiple backends. The volume scheduler, with the help of the driver, will decide which backend will be used to create a volume of a particular flavor. Currently, the driver uses a simple "first-fit" policy, where the first backend that can successfully create this volume is the one that is used.
|
||||
|
||||
Operation
|
||||
----------
|
||||
|
||||
Using the nova-manage command detailed in the implementation, an admin can add flavors and backends.
|
||||
|
||||
One or more nova-volume service instances will be deployed per availability zone. When an instance is started, it will create storage repositories (SRs) to connect to the backends available within that zone. All nova-volume instances within a zone can see all the available backends. These instances are completely symmetric and hence should be able to service any create_volume request within the zone.
|
||||
|
||||
|
||||
Commands
|
||||
=========
|
||||
|
||||
A category called "sm" has been added to nova-manage in the class StorageManagerCommands.
|
||||
|
||||
The following actions will be added:
|
||||
|
||||
- flavor_list
|
||||
- flavor_create
|
||||
- flavor_delete
|
||||
- backend_list
|
||||
- backend_add
|
||||
- backend_remove
|
||||
|
||||
Usage:
|
||||
------
|
||||
|
||||
nova-manage sm flavor_create <label> <description>
|
||||
|
||||
nova-manage sm flavor_delete<label>
|
||||
|
||||
nova-manage sm backend_add <flavor label> <SR type> [config connection parameters]
|
||||
|
||||
Note: SR type and config connection parameters are in keeping with the Xen Command Line Interface. http://support.citrix.com/article/CTX124887
|
||||
|
||||
nova-manage sm backend_delete <backend-id>
|
||||
|
||||
Examples:
|
||||
---------
|
||||
|
||||
nova-manage sm flavor_create gold "Not all that glitters"
|
||||
|
||||
nova-manage sm flavor_delete gold
|
||||
|
||||
nova-manage sm backend_add gold nfs name_label=toybox-renuka server=myserver serverpath=/local/scratch/myname
|
||||
|
||||
nova-manage sm backend_remove 1
|
||||
|
||||
API Changes
|
||||
===========
|
||||
|
||||
No API changes have been introduced so far. The existing euca-create-volume and euca-delete-volume commands (or equivalent OpenStack API commands) should be used.
|
|
@ -1,49 +0,0 @@
|
|||
===================
|
||||
nova-api-os-volume
|
||||
===================
|
||||
|
||||
-------------------------------------------
|
||||
Server for the Nova OpenStack Volume APIs
|
||||
-------------------------------------------
|
||||
|
||||
:Author: openstack@lists.launchpad.net
|
||||
:Date: 2012-09-27
|
||||
:Copyright: OpenStack LLC
|
||||
:Version: 2012.1
|
||||
:Manual section: 1
|
||||
:Manual group: cloud computing
|
||||
|
||||
SYNOPSIS
|
||||
========
|
||||
|
||||
nova-api-os-volume [options]
|
||||
|
||||
DESCRIPTION
|
||||
===========
|
||||
|
||||
nova-api-os-volume is a server daemon that serves the Nova OpenStack API
|
||||
|
||||
OPTIONS
|
||||
=======
|
||||
|
||||
**General options**
|
||||
|
||||
FILES
|
||||
========
|
||||
|
||||
* /etc/nova/nova.conf
|
||||
* /etc/nova/api-paste.ini
|
||||
* /etc/nova/policy.json
|
||||
* /etc/nova/rootwrap.conf
|
||||
* /etc/nova/rootwrap.d/
|
||||
|
||||
SEE ALSO
|
||||
========
|
||||
|
||||
* `OpenStack Nova <http://nova.openstack.org>`__
|
||||
* `OpenStack Nova <http://nova.openstack.org>`__
|
||||
|
||||
BUGS
|
||||
====
|
||||
|
||||
* Nova is sourced in Launchpad so you can view current bugs at `OpenStack Nova <http://nova.openstack.org>`__
|
|
@ -30,8 +30,8 @@ You also need to let the nova user run nova-rootwrap as root in sudoers:
|
|||
nova ALL = (root) NOPASSWD: /usr/bin/nova-rootwrap /etc/nova/rootwrap.conf *
|
||||
|
||||
To make allowed commands node-specific, your packaging should only
|
||||
install {compute,network,volume}.filters respectively on compute, network
|
||||
and volume nodes (i.e. nova-api nodes should not have any of those files
|
||||
install {compute,network}.filters respectively on compute and network
|
||||
nodes (i.e. nova-api nodes should not have any of those files
|
||||
installed).
|
||||
|
||||
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
=======================
|
||||
nova-volume-usage-audit
|
||||
=======================
|
||||
|
||||
-------------------------------------------
|
||||
Generate Usage Notifications for Volumes
|
||||
-------------------------------------------
|
||||
|
||||
:Author: openstack@lists.launchpad.net
|
||||
:Date: 2012-09-27
|
||||
:Copyright: OpenStack LLC
|
||||
:Version: 2012.1
|
||||
:Manual section: 1
|
||||
:Manual group: cloud computing
|
||||
|
||||
SYNOPSIS
|
||||
========
|
||||
|
||||
nova-volume-usage-audit [options]
|
||||
|
||||
DESCRIPTION
|
||||
===========
|
||||
|
||||
Cron script to generate usage notifications for volumes existing during
|
||||
the audit period.
|
||||
|
||||
Together with the notifications generated by volumes
|
||||
create/delete/resize, over that time period, this allows an external
|
||||
system consuming usage notification feeds to calculate volume usage
|
||||
for each tenant.
|
||||
|
||||
Time periods are specified as 'hour', 'month', 'day' or 'year'
|
||||
|
||||
hour = previous hour. If run at 9:07am, will generate usage for 8-9am.
|
||||
month = previous month. If the script is run April 1, it will generate
|
||||
usages for March 1 through March 31.
|
||||
day = previous day. if run on July 4th, it generates usages for July 3rd.
|
||||
year = previous year. If run on Jan 1, it generates usages for
|
||||
Jan 1 through Dec 31 of the previous year.
|
||||
|
||||
|
||||
OPTIONS
|
||||
=======
|
||||
|
||||
**General options**
|
||||
|
||||
FILES
|
||||
========
|
||||
|
||||
* /etc/nova/nova.conf
|
||||
|
||||
SEE ALSO
|
||||
========
|
||||
|
||||
* `OpenStack Nova <http://nova.openstack.org>`__
|
||||
* `OpenStack Nova <http://nova.openstack.org>`__
|
||||
|
||||
BUGS
|
||||
====
|
||||
|
||||
* Nova is sourced in Launchpad so you can view current bugs at `OpenStack Nova <http://nova.openstack.org>`__
|
|
@ -1,54 +0,0 @@
|
|||
===========
|
||||
nova-volume
|
||||
===========
|
||||
|
||||
-------------------
|
||||
Nova Volume Server
|
||||
-------------------
|
||||
|
||||
:Author: openstack@lists.launchpad.net
|
||||
:Date: 2012-09-27
|
||||
:Copyright: OpenStack LLC
|
||||
:Version: 2012.1
|
||||
:Manual section: 1
|
||||
:Manual group: cloud computing
|
||||
|
||||
SYNOPSIS
|
||||
========
|
||||
|
||||
nova-volume [options]
|
||||
|
||||
DESCRIPTION
|
||||
===========
|
||||
|
||||
nova-volume manages creating, attaching, detaching, and persistent storage.
|
||||
|
||||
Persistent storage volumes keep their state independent of instances. You can
|
||||
attach to an instance, terminate the instance, spawn a new instance (even
|
||||
one from a different image) and re-attach the volume with the same data
|
||||
intact.
|
||||
|
||||
|
||||
OPTIONS
|
||||
=======
|
||||
|
||||
**General options**
|
||||
|
||||
FILES
|
||||
========
|
||||
|
||||
* /etc/nova/nova.conf
|
||||
* /etc/nova/policy.json
|
||||
* /etc/nova/rootwrap.conf
|
||||
* /etc/nova/rootwrap.d/
|
||||
|
||||
SEE ALSO
|
||||
========
|
||||
|
||||
* `OpenStack Nova <http://nova.openstack.org>`__
|
||||
* `OpenStack Nova <http://nova.openstack.org>`__
|
||||
|
||||
BUGS
|
||||
====
|
||||
|
||||
* Nova is sourced in Launchpad so you can view current bugs at `OpenStack Nova <http://nova.openstack.org>`__
|
|
@ -159,9 +159,6 @@ global_opts = [
|
|||
cfg.StrOpt('scheduler_topic',
|
||||
default='scheduler',
|
||||
help='the topic scheduler nodes listen on'),
|
||||
cfg.StrOpt('volume_topic',
|
||||
default='volume',
|
||||
help='the topic volume nodes listen on'),
|
||||
cfg.StrOpt('network_topic',
|
||||
default='network',
|
||||
help='the topic network nodes listen on'),
|
||||
|
@ -169,7 +166,7 @@ global_opts = [
|
|||
default=True,
|
||||
help='whether to rate limit the api'),
|
||||
cfg.ListOpt('enabled_apis',
|
||||
default=['ec2', 'osapi_compute', 'osapi_volume', 'metadata'],
|
||||
default=['ec2', 'osapi_compute', 'metadata'],
|
||||
help='a list of APIs to enable by default'),
|
||||
cfg.StrOpt('ec2_host',
|
||||
default='$my_ip',
|
||||
|
@ -197,16 +194,6 @@ global_opts = [
|
|||
'nova.api.openstack.compute.contrib.standard_extensions'
|
||||
],
|
||||
help='osapi compute extension to load'),
|
||||
cfg.ListOpt('osapi_volume_ext_list',
|
||||
default=[],
|
||||
help='Specify list of extensions to load when using osapi_'
|
||||
'volume_extension option with nova.api.openstack.'
|
||||
'volume.contrib.select_extensions'),
|
||||
cfg.MultiStrOpt('osapi_volume_extension',
|
||||
default=[
|
||||
'nova.api.openstack.volume.contrib.standard_extensions'
|
||||
],
|
||||
help='osapi volume extension to load'),
|
||||
cfg.StrOpt('osapi_path',
|
||||
default='/v1.1/',
|
||||
help='the path prefix used to call the openstack api server'),
|
||||
|
@ -281,9 +268,6 @@ global_opts = [
|
|||
cfg.StrOpt('network_manager',
|
||||
default='nova.network.manager.VlanManager',
|
||||
help='full class name for the Manager for network'),
|
||||
cfg.StrOpt('volume_manager',
|
||||
default='nova.volume.manager.VolumeManager',
|
||||
help='full class name for the Manager for volume'),
|
||||
cfg.StrOpt('scheduler_manager',
|
||||
default='nova.scheduler.manager.SchedulerManager',
|
||||
help='full class name for the Manager for scheduler'),
|
||||
|
@ -382,7 +366,7 @@ global_opts = [
|
|||
default='nova.network.api.API',
|
||||
help='The full class name of the network API class to use'),
|
||||
cfg.StrOpt('volume_api_class',
|
||||
default='nova.volume.api.API',
|
||||
default='nova.volume.cinder.API',
|
||||
help='The full class name of the volume API class to use'),
|
||||
cfg.StrOpt('security_group_handler',
|
||||
default='nova.network.sg.NullSecurityGroupHandler',
|
||||
|
|
|
@ -87,20 +87,6 @@ def handle_schedule_error(context, ex, instance_uuid, request_spec):
|
|||
'scheduler.run_instance', notifier.ERROR, payload)
|
||||
|
||||
|
||||
def cast_to_volume_host(context, host, method, **kwargs):
|
||||
"""Cast request to a volume host queue"""
|
||||
|
||||
volume_id = kwargs.get('volume_id', None)
|
||||
if volume_id is not None:
|
||||
now = timeutils.utcnow()
|
||||
db.volume_update(context, volume_id,
|
||||
{'host': host, 'scheduled_at': now})
|
||||
rpc.cast(context,
|
||||
rpc.queue_get_for(context, FLAGS.volume_topic, host),
|
||||
{"method": method, "args": kwargs})
|
||||
LOG.debug(_("Casted '%(method)s' to volume '%(host)s'") % locals())
|
||||
|
||||
|
||||
def instance_update_db(context, instance_uuid):
|
||||
'''Clear the host and set the scheduled_at field of an Instance.
|
||||
|
||||
|
@ -127,13 +113,11 @@ def cast_to_compute_host(context, host, method, **kwargs):
|
|||
def cast_to_host(context, topic, host, method, **kwargs):
|
||||
"""Generic cast to host"""
|
||||
|
||||
topic_mapping = {
|
||||
FLAGS.compute_topic: cast_to_compute_host,
|
||||
FLAGS.volume_topic: cast_to_volume_host}
|
||||
topic_mapping = {FLAGS.compute_topic: cast_to_compute_host}
|
||||
|
||||
func = topic_mapping.get(topic)
|
||||
if func:
|
||||
func(context, host, method, **kwargs)
|
||||
cast_to_compute_host(context, host, method, **kwargs)
|
||||
else:
|
||||
rpc.cast(context,
|
||||
rpc.queue_get_for(context, topic, host),
|
||||
|
|
|
@ -88,15 +88,6 @@ service_opts = [
|
|||
cfg.IntOpt('metadata_workers',
|
||||
default=None,
|
||||
help='Number of workers for metadata service'),
|
||||
cfg.StrOpt('osapi_volume_listen',
|
||||
default="0.0.0.0",
|
||||
help='IP address for OpenStack Volume API to listen'),
|
||||
cfg.IntOpt('osapi_volume_listen_port',
|
||||
default=8776,
|
||||
help='port for os volume api to listen'),
|
||||
cfg.IntOpt('osapi_volume_workers',
|
||||
default=None,
|
||||
help='Number of workers for OpenStack Volume API service'),
|
||||
]
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
|
|
|
@ -26,7 +26,6 @@ flags.DECLARE('iscsi_num_targets', 'nova.volume.driver')
|
|||
flags.DECLARE('network_size', 'nova.network.manager')
|
||||
flags.DECLARE('num_networks', 'nova.network.manager')
|
||||
flags.DECLARE('policy_file', 'nova.policy')
|
||||
flags.DECLARE('volume_driver', 'nova.volume.manager')
|
||||
|
||||
|
||||
def set_defaults(conf):
|
||||
|
@ -44,7 +43,6 @@ def set_defaults(conf):
|
|||
conf.set_default('sqlite_synchronous', False)
|
||||
conf.set_default('use_ipv6', True)
|
||||
conf.set_default('verbose', True)
|
||||
conf.set_default('volume_driver', 'nova.volume.driver.FakeISCSIDriver')
|
||||
conf.set_default('api_paste_config', '$state_path/etc/nova/api-paste.ini')
|
||||
conf.set_default('rpc_response_timeout', 5)
|
||||
conf.set_default('rpc_cast_timeout', 5)
|
||||
|
|
|
@ -175,18 +175,6 @@ class SchedulerManagerTestCase(test.TestCase):
|
|||
self.manager.run_instance(self.context, request_spec,
|
||||
None, None, None, None, {})
|
||||
|
||||
def test_create_volume_no_valid_host_puts_volume_in_error(self):
|
||||
self._mox_schedule_method_helper('schedule_create_volume')
|
||||
self.mox.StubOutWithMock(db, 'volume_update')
|
||||
|
||||
self.manager.driver.schedule_create_volume(self.context, '1', '2',
|
||||
None).AndRaise(exception.NoValidHost(reason=''))
|
||||
db.volume_update(self.context, '1', {'status': 'error'})
|
||||
|
||||
self.mox.ReplayAll()
|
||||
self.assertRaises(exception.NoValidHost, self.manager.create_volume,
|
||||
self.context, '1', '2')
|
||||
|
||||
def test_prep_resize_no_valid_host_back_in_active_state(self):
|
||||
fake_instance_uuid = 'fake-instance-id'
|
||||
inst = {"vm_state": "", "task_state": ""}
|
||||
|
@ -305,13 +293,10 @@ class SchedulerTestCase(test.TestCase):
|
|||
self.assertEqual(result, ['host2'])
|
||||
|
||||
def _live_migration_instance(self):
|
||||
volume1 = {'id': 31338}
|
||||
volume2 = {'id': 31339}
|
||||
return {'id': 31337,
|
||||
'uuid': 'fake_uuid',
|
||||
'name': 'fake-instance',
|
||||
'host': 'fake_host1',
|
||||
'volumes': [volume1, volume2],
|
||||
'power_state': power_state.RUNNING,
|
||||
'memory_mb': 1024,
|
||||
'root_gb': 1024,
|
||||
|
@ -656,48 +641,6 @@ class SchedulerDriverModuleTestCase(test.TestCase):
|
|||
super(SchedulerDriverModuleTestCase, self).setUp()
|
||||
self.context = context.RequestContext('fake_user', 'fake_project')
|
||||
|
||||
def test_cast_to_volume_host_update_db_with_volume_id(self):
|
||||
host = 'fake_host1'
|
||||
method = 'fake_method'
|
||||
fake_kwargs = {'volume_id': 31337,
|
||||
'extra_arg': 'meow'}
|
||||
queue = 'fake_queue'
|
||||
|
||||
self.mox.StubOutWithMock(timeutils, 'utcnow')
|
||||
self.mox.StubOutWithMock(db, 'volume_update')
|
||||
self.mox.StubOutWithMock(rpc, 'queue_get_for')
|
||||
self.mox.StubOutWithMock(rpc, 'cast')
|
||||
|
||||
timeutils.utcnow().AndReturn('fake-now')
|
||||
db.volume_update(self.context, 31337,
|
||||
{'host': host, 'scheduled_at': 'fake-now'})
|
||||
rpc.queue_get_for(self.context, 'volume', host).AndReturn(queue)
|
||||
rpc.cast(self.context, queue,
|
||||
{'method': method,
|
||||
'args': fake_kwargs})
|
||||
|
||||
self.mox.ReplayAll()
|
||||
driver.cast_to_volume_host(self.context, host, method,
|
||||
**fake_kwargs)
|
||||
|
||||
def test_cast_to_volume_host_update_db_without_volume_id(self):
|
||||
host = 'fake_host1'
|
||||
method = 'fake_method'
|
||||
fake_kwargs = {'extra_arg': 'meow'}
|
||||
queue = 'fake_queue'
|
||||
|
||||
self.mox.StubOutWithMock(rpc, 'queue_get_for')
|
||||
self.mox.StubOutWithMock(rpc, 'cast')
|
||||
|
||||
rpc.queue_get_for(self.context, 'volume', host).AndReturn(queue)
|
||||
rpc.cast(self.context, queue,
|
||||
{'method': method,
|
||||
'args': fake_kwargs})
|
||||
|
||||
self.mox.ReplayAll()
|
||||
driver.cast_to_volume_host(self.context, host, method,
|
||||
**fake_kwargs)
|
||||
|
||||
def test_cast_to_compute_host_update_db_with_instance_uuid(self):
|
||||
host = 'fake_host1'
|
||||
method = 'fake_method'
|
||||
|
@ -753,19 +696,6 @@ class SchedulerDriverModuleTestCase(test.TestCase):
|
|||
driver.cast_to_host(self.context, 'compute', host, method,
|
||||
**fake_kwargs)
|
||||
|
||||
def test_cast_to_host_volume_topic(self):
|
||||
host = 'fake_host1'
|
||||
method = 'fake_method'
|
||||
fake_kwargs = {'extra_arg': 'meow'}
|
||||
|
||||
self.mox.StubOutWithMock(driver, 'cast_to_volume_host')
|
||||
driver.cast_to_volume_host(self.context, host, method,
|
||||
**fake_kwargs)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
driver.cast_to_host(self.context, 'volume', host, method,
|
||||
**fake_kwargs)
|
||||
|
||||
def test_cast_to_host_unknown_topic(self):
|
||||
host = 'fake_host1'
|
||||
method = 'fake_method'
|
||||
|
|
|
@ -1,121 +0,0 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2011 Red Hat, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import os.path
|
||||
import shutil
|
||||
import string
|
||||
import tempfile
|
||||
|
||||
from nova import test
|
||||
from nova.volume import iscsi
|
||||
|
||||
|
||||
class TargetAdminTestCase(object):
|
||||
|
||||
def setUp(self):
|
||||
self.cmds = []
|
||||
|
||||
self.tid = 1
|
||||
self.target_name = 'iqn.2011-09.org.foo.bar:blaa'
|
||||
self.lun = 10
|
||||
self.path = '/foo'
|
||||
self.vol_id = 'blaa'
|
||||
|
||||
self.script_template = None
|
||||
self.stubs.Set(os.path, 'isfile', lambda _: True)
|
||||
self.stubs.Set(os, 'unlink', lambda _: '')
|
||||
self.stubs.Set(iscsi.TgtAdm, '_get_target', self.fake_get_target)
|
||||
|
||||
def fake_get_target(obj, iqn):
|
||||
return 1
|
||||
|
||||
def get_script_params(self):
|
||||
return {'tid': self.tid,
|
||||
'target_name': self.target_name,
|
||||
'lun': self.lun,
|
||||
'path': self.path}
|
||||
|
||||
def get_script(self):
|
||||
return self.script_template % self.get_script_params()
|
||||
|
||||
def fake_execute(self, *cmd, **kwargs):
|
||||
self.cmds.append(string.join(cmd))
|
||||
return "", None
|
||||
|
||||
def clear_cmds(self):
|
||||
cmds = []
|
||||
|
||||
def verify_cmds(self, cmds):
|
||||
self.assertEqual(len(cmds), len(self.cmds))
|
||||
for a, b in zip(cmds, self.cmds):
|
||||
self.assertEqual(a, b)
|
||||
|
||||
def verify(self):
|
||||
script = self.get_script()
|
||||
cmds = []
|
||||
for line in script.split('\n'):
|
||||
if not line.strip():
|
||||
continue
|
||||
cmds.append(line)
|
||||
self.verify_cmds(cmds)
|
||||
|
||||
def run_commands(self):
|
||||
tgtadm = iscsi.get_target_admin()
|
||||
tgtadm.set_execute(self.fake_execute)
|
||||
tgtadm.create_iscsi_target(self.target_name, self.tid,
|
||||
self.lun, self.path)
|
||||
tgtadm.show_target(self.tid, iqn=self.target_name)
|
||||
tgtadm.remove_iscsi_target(self.tid, self.lun, self.vol_id)
|
||||
|
||||
def test_target_admin(self):
|
||||
self.clear_cmds()
|
||||
self.run_commands()
|
||||
self.verify()
|
||||
|
||||
|
||||
class TgtAdmTestCase(test.TestCase, TargetAdminTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TgtAdmTestCase, self).setUp()
|
||||
TargetAdminTestCase.setUp(self)
|
||||
self.persist_tempdir = tempfile.mkdtemp()
|
||||
self.flags(iscsi_helper='tgtadm')
|
||||
self.flags(volumes_dir=self.persist_tempdir)
|
||||
self.script_template = "\n".join([
|
||||
'tgt-admin --update iqn.2011-09.org.foo.bar:blaa',
|
||||
'tgt-admin --delete iqn.2010-10.org.openstack:volume-blaa'])
|
||||
|
||||
def tearDown(self):
|
||||
try:
|
||||
shutil.rmtree(self.persist_tempdir)
|
||||
except OSError:
|
||||
pass
|
||||
super(TgtAdmTestCase, self).tearDown()
|
||||
|
||||
|
||||
class IetAdmTestCase(test.TestCase, TargetAdminTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(IetAdmTestCase, self).setUp()
|
||||
TargetAdminTestCase.setUp(self)
|
||||
self.flags(iscsi_helper='ietadm')
|
||||
self.script_template = "\n".join([
|
||||
'ietadm --op new --tid=%(tid)s --params Name=%(target_name)s',
|
||||
'ietadm --op new --tid=%(tid)s --lun=%(lun)s '
|
||||
'--params Path=%(path)s,Type=fileio',
|
||||
'ietadm --op show --tid=%(tid)s',
|
||||
'ietadm --op delete --tid=%(tid)s --lun=%(lun)s',
|
||||
'ietadm --op delete --tid=%(tid)s'])
|
|
@ -155,7 +155,6 @@ class LibvirtVolumeTestCase(test.TestCase):
|
|||
}
|
||||
|
||||
def test_libvirt_volume_driver_serial(self):
|
||||
vol_driver = volume_driver.VolumeDriver()
|
||||
libvirt_driver = volume.LibvirtVolumeDriver(self.fake_conn)
|
||||
name = 'volume-00000001'
|
||||
vol = {'id': 1, 'name': name}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,234 +0,0 @@
|
|||
# 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()
|
||||
super(NetappNfsDriverTestCase, self).setUp()
|
||||
|
||||
def test_check_for_setup_error(self):
|
||||
mox = self.mox
|
||||
drv = self._driver
|
||||
|
||||
# check exception raises when flags are not set
|
||||
self.assertRaises(exception.NovaException,
|
||||
drv.check_for_setup_error)
|
||||
|
||||
# set required flags
|
||||
self.flags(netapp_wsdl_url='val',
|
||||
netapp_login='val',
|
||||
netapp_password='val',
|
||||
netapp_server_hostname='val',
|
||||
netapp_server_port='val')
|
||||
|
||||
mox.StubOutWithMock(nfs.NfsDriver, 'check_for_setup_error')
|
||||
nfs.NfsDriver.check_for_setup_error()
|
||||
mox.ReplayAll()
|
||||
|
||||
drv.check_for_setup_error()
|
||||
|
||||
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))
|
||||
|
||||
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())
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
self._prepare_delete_snapshot_mock(True)
|
||||
|
||||
drv.delete_snapshot(FakeSnapshot())
|
||||
|
||||
def test_delete_missing_snapshot(self):
|
||||
drv = self._driver
|
||||
self._prepare_delete_snapshot_mock(False)
|
||||
|
||||
drv.delete_snapshot(FakeSnapshot())
|
||||
|
||||
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)
|
||||
|
||||
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)
|
|
@ -1,278 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2011 Nexenta Systems, 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 OpenStack Nova volume driver
|
||||
"""
|
||||
|
||||
import base64
|
||||
import urllib2
|
||||
|
||||
import nova.flags
|
||||
import nova.test
|
||||
from nova.volume import nexenta
|
||||
from nova.volume.nexenta import jsonrpc
|
||||
from nova.volume.nexenta import volume
|
||||
|
||||
FLAGS = nova.flags.FLAGS
|
||||
|
||||
|
||||
class TestNexentaDriver(nova.test.TestCase):
|
||||
TEST_VOLUME_NAME = 'volume1'
|
||||
TEST_VOLUME_NAME2 = 'volume2'
|
||||
TEST_SNAPSHOT_NAME = 'snapshot1'
|
||||
TEST_VOLUME_REF = {
|
||||
'name': TEST_VOLUME_NAME,
|
||||
'size': 1,
|
||||
}
|
||||
TEST_VOLUME_REF2 = {
|
||||
'name': TEST_VOLUME_NAME2,
|
||||
'size': 1,
|
||||
}
|
||||
TEST_SNAPSHOT_REF = {
|
||||
'name': TEST_SNAPSHOT_NAME,
|
||||
'volume_name': TEST_VOLUME_NAME,
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
super(TestNexentaDriver, self).setUp()
|
||||
self.flags(
|
||||
nexenta_host='1.1.1.1',
|
||||
nexenta_volume='nova',
|
||||
nexenta_target_prefix='iqn:',
|
||||
nexenta_target_group_prefix='nova/',
|
||||
nexenta_blocksize='8K',
|
||||
nexenta_sparse=True,
|
||||
)
|
||||
self.nms_mock = self.mox.CreateMockAnything()
|
||||
for mod in ['volume', 'zvol', 'iscsitarget',
|
||||
'stmf', 'scsidisk', 'snapshot']:
|
||||
setattr(self.nms_mock, mod, self.mox.CreateMockAnything())
|
||||
self.stubs.Set(jsonrpc, 'NexentaJSONProxy',
|
||||
lambda *_, **__: self.nms_mock)
|
||||
self.drv = volume.NexentaDriver()
|
||||
self.drv.do_setup({})
|
||||
|
||||
def test_setup_error(self):
|
||||
self.nms_mock.volume.object_exists('nova').AndReturn(True)
|
||||
self.mox.ReplayAll()
|
||||
self.drv.check_for_setup_error()
|
||||
|
||||
def test_setup_error_fail(self):
|
||||
self.nms_mock.volume.object_exists('nova').AndReturn(False)
|
||||
self.mox.ReplayAll()
|
||||
self.assertRaises(LookupError, self.drv.check_for_setup_error)
|
||||
|
||||
def test_local_path(self):
|
||||
self.assertRaises(NotImplementedError, self.drv.local_path, '')
|
||||
|
||||
def test_create_volume(self):
|
||||
self.nms_mock.zvol.create('nova/volume1', '1G', '8K', True)
|
||||
self.mox.ReplayAll()
|
||||
self.drv.create_volume(self.TEST_VOLUME_REF)
|
||||
|
||||
def test_delete_volume(self):
|
||||
self.nms_mock.zvol.destroy('nova/volume1', '')
|
||||
self.mox.ReplayAll()
|
||||
self.drv.delete_volume(self.TEST_VOLUME_REF)
|
||||
|
||||
def test_create_snapshot(self):
|
||||
self.nms_mock.zvol.create_snapshot('nova/volume1', 'snapshot1', '')
|
||||
self.mox.ReplayAll()
|
||||
self.drv.create_snapshot(self.TEST_SNAPSHOT_REF)
|
||||
|
||||
def test_create_volume_from_snapshot(self):
|
||||
self.nms_mock.zvol.clone('nova/volume1@snapshot1', 'nova/volume2')
|
||||
self.mox.ReplayAll()
|
||||
self.drv.create_volume_from_snapshot(self.TEST_VOLUME_REF2,
|
||||
self.TEST_SNAPSHOT_REF)
|
||||
|
||||
def test_delete_snapshot(self):
|
||||
self.nms_mock.snapshot.destroy('nova/volume1@snapshot1', '')
|
||||
self.mox.ReplayAll()
|
||||
self.drv.delete_snapshot(self.TEST_SNAPSHOT_REF)
|
||||
|
||||
_CREATE_EXPORT_METHODS = [
|
||||
('iscsitarget', 'create_target', ({'target_name': 'iqn:volume1'},),
|
||||
u'Unable to create iscsi target\n'
|
||||
u' iSCSI target iqn.1986-03.com.sun:02:nova-volume1 already'
|
||||
u' configured\n'
|
||||
u' itadm create-target failed with error 17\n',
|
||||
),
|
||||
('stmf', 'create_targetgroup', ('nova/volume1',),
|
||||
u'Unable to create targetgroup: stmfadm: nova/volume1:'
|
||||
u' already exists\n',
|
||||
),
|
||||
('stmf', 'add_targetgroup_member', ('nova/volume1', 'iqn:volume1'),
|
||||
u'Unable to add member to targetgroup: stmfadm:'
|
||||
u' iqn.1986-03.com.sun:02:nova-volume1: already exists\n',
|
||||
),
|
||||
('scsidisk', 'create_lu', ('nova/volume1', {}),
|
||||
u"Unable to create lu with zvol 'nova/volume1':\n"
|
||||
u" sbdadm: filename /dev/zvol/rdsk/nova/volume1: in use\n",
|
||||
),
|
||||
('scsidisk', 'add_lun_mapping_entry', ('nova/volume1', {
|
||||
'target_group': 'nova/volume1', 'lun': '0'}),
|
||||
u"Unable to add view to zvol 'nova/volume1' (LUNs in use: ):\n"
|
||||
u" stmfadm: view entry exists\n",
|
||||
),
|
||||
]
|
||||
|
||||
def _stub_export_method(self, module, method, args, error, fail=False):
|
||||
m = getattr(self.nms_mock, module)
|
||||
m = getattr(m, method)
|
||||
mock = m(*args)
|
||||
if fail:
|
||||
mock.AndRaise(nexenta.NexentaException(error))
|
||||
|
||||
def _stub_all_export_methods(self, fail=False):
|
||||
for params in self._CREATE_EXPORT_METHODS:
|
||||
self._stub_export_method(*params, fail=fail)
|
||||
|
||||
def test_create_export(self):
|
||||
self._stub_all_export_methods()
|
||||
self.mox.ReplayAll()
|
||||
retval = self.drv.create_export({}, self.TEST_VOLUME_REF)
|
||||
self.assertEquals(retval,
|
||||
{'provider_location':
|
||||
'%s:%s,1 %s%s' % (FLAGS.nexenta_host,
|
||||
FLAGS.nexenta_iscsi_target_portal_port,
|
||||
FLAGS.nexenta_target_prefix,
|
||||
self.TEST_VOLUME_NAME)})
|
||||
|
||||
def __get_test(i):
|
||||
def _test_create_export_fail(self):
|
||||
for params in self._CREATE_EXPORT_METHODS[:i]:
|
||||
self._stub_export_method(*params)
|
||||
self._stub_export_method(*self._CREATE_EXPORT_METHODS[i],
|
||||
fail=True)
|
||||
self.mox.ReplayAll()
|
||||
self.assertRaises(nexenta.NexentaException,
|
||||
self.drv.create_export, {}, self.TEST_VOLUME_REF)
|
||||
return _test_create_export_fail
|
||||
|
||||
for i in range(len(_CREATE_EXPORT_METHODS)):
|
||||
locals()['test_create_export_fail_%d' % i] = __get_test(i)
|
||||
|
||||
def test_ensure_export(self):
|
||||
self._stub_all_export_methods(fail=True)
|
||||
self.mox.ReplayAll()
|
||||
self.drv.ensure_export({}, self.TEST_VOLUME_REF)
|
||||
|
||||
def test_remove_export(self):
|
||||
self.nms_mock.scsidisk.delete_lu('nova/volume1')
|
||||
self.nms_mock.stmf.destroy_targetgroup('nova/volume1')
|
||||
self.nms_mock.iscsitarget.delete_target('iqn:volume1')
|
||||
self.mox.ReplayAll()
|
||||
self.drv.remove_export({}, self.TEST_VOLUME_REF)
|
||||
|
||||
def test_remove_export_fail_0(self):
|
||||
self.nms_mock.scsidisk.delete_lu('nova/volume1')
|
||||
self.nms_mock.stmf.destroy_targetgroup('nova/volume1').AndRaise(
|
||||
nexenta.NexentaException())
|
||||
self.nms_mock.iscsitarget.delete_target('iqn:volume1')
|
||||
self.mox.ReplayAll()
|
||||
self.drv.remove_export({}, self.TEST_VOLUME_REF)
|
||||
|
||||
def test_remove_export_fail_1(self):
|
||||
self.nms_mock.scsidisk.delete_lu('nova/volume1')
|
||||
self.nms_mock.stmf.destroy_targetgroup('nova/volume1')
|
||||
self.nms_mock.iscsitarget.delete_target('iqn:volume1').AndRaise(
|
||||
nexenta.NexentaException())
|
||||
self.mox.ReplayAll()
|
||||
self.drv.remove_export({}, self.TEST_VOLUME_REF)
|
||||
|
||||
|
||||
class TestNexentaJSONRPC(nova.test.TestCase):
|
||||
URL = 'http://example.com/'
|
||||
URL_S = 'https://example.com/'
|
||||
USER = 'user'
|
||||
PASSWORD = 'password'
|
||||
HEADERS = {'Authorization': 'Basic %s' % (base64.b64encode(
|
||||
':'.join((USER, PASSWORD))),),
|
||||
'Content-Type': 'application/json'}
|
||||
REQUEST = 'the request'
|
||||
|
||||
def setUp(self):
|
||||
super(TestNexentaJSONRPC, self).setUp()
|
||||
self.proxy = jsonrpc.NexentaJSONProxy(
|
||||
self.URL, self.USER, self.PASSWORD, auto=True)
|
||||
self.mox.StubOutWithMock(urllib2, 'Request', True)
|
||||
self.mox.StubOutWithMock(urllib2, 'urlopen')
|
||||
self.resp_mock = self.mox.CreateMockAnything()
|
||||
self.resp_info_mock = self.mox.CreateMockAnything()
|
||||
self.resp_mock.info().AndReturn(self.resp_info_mock)
|
||||
urllib2.urlopen(self.REQUEST).AndReturn(self.resp_mock)
|
||||
|
||||
def test_call(self):
|
||||
urllib2.Request(self.URL,
|
||||
'{"object": null, "params": ["arg1", "arg2"], "method": null}',
|
||||
self.HEADERS).AndReturn(self.REQUEST)
|
||||
self.resp_info_mock.status = ''
|
||||
self.resp_mock.read().AndReturn(
|
||||
'{"error": null, "result": "the result"}')
|
||||
self.mox.ReplayAll()
|
||||
result = self.proxy('arg1', 'arg2')
|
||||
self.assertEquals("the result", result)
|
||||
|
||||
def test_call_deep(self):
|
||||
urllib2.Request(self.URL,
|
||||
'{"object": "obj1.subobj", "params": ["arg1", "arg2"],'
|
||||
' "method": "meth"}',
|
||||
self.HEADERS).AndReturn(self.REQUEST)
|
||||
self.resp_info_mock.status = ''
|
||||
self.resp_mock.read().AndReturn(
|
||||
'{"error": null, "result": "the result"}')
|
||||
self.mox.ReplayAll()
|
||||
result = self.proxy.obj1.subobj.meth('arg1', 'arg2')
|
||||
self.assertEquals("the result", result)
|
||||
|
||||
def test_call_auto(self):
|
||||
urllib2.Request(self.URL,
|
||||
'{"object": null, "params": ["arg1", "arg2"], "method": null}',
|
||||
self.HEADERS).AndReturn(self.REQUEST)
|
||||
urllib2.Request(self.URL_S,
|
||||
'{"object": null, "params": ["arg1", "arg2"], "method": null}',
|
||||
self.HEADERS).AndReturn(self.REQUEST)
|
||||
self.resp_info_mock.status = 'EOF in headers'
|
||||
self.resp_mock.read().AndReturn(
|
||||
'{"error": null, "result": "the result"}')
|
||||
urllib2.urlopen(self.REQUEST).AndReturn(self.resp_mock)
|
||||
self.mox.ReplayAll()
|
||||
result = self.proxy('arg1', 'arg2')
|
||||
self.assertEquals("the result", result)
|
||||
|
||||
def test_call_error(self):
|
||||
urllib2.Request(self.URL,
|
||||
'{"object": null, "params": ["arg1", "arg2"], "method": null}',
|
||||
self.HEADERS).AndReturn(self.REQUEST)
|
||||
self.resp_info_mock.status = ''
|
||||
self.resp_mock.read().AndReturn(
|
||||
'{"error": {"message": "the error"}, "result": "the result"}')
|
||||
self.mox.ReplayAll()
|
||||
self.assertRaises(jsonrpc.NexentaJSONException,
|
||||
self.proxy, 'arg1', 'arg2')
|
||||
|
||||
def test_call_fail(self):
|
||||
urllib2.Request(self.URL,
|
||||
'{"object": null, "params": ["arg1", "arg2"], "method": null}',
|
||||
self.HEADERS).AndReturn(self.REQUEST)
|
||||
self.resp_info_mock.status = 'EOF in headers'
|
||||
self.proxy.auto = False
|
||||
self.mox.ReplayAll()
|
||||
self.assertRaises(jsonrpc.NexentaJSONException,
|
||||
self.proxy, 'arg1', 'arg2')
|
|
@ -1,569 +0,0 @@
|
|||
# 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()
|
||||
super(NfsDriverTestCase, self).setUp()
|
||||
|
||||
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))
|
||||
|
||||
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))
|
||||
|
||||
def test_local_path(self):
|
||||
"""local_path common use case"""
|
||||
self.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/2f4f60214cf43c595666dd815f0360a4/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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
def test_get_hash_str(self):
|
||||
"""_get_hash_str should calculation correct value"""
|
||||
drv = self._driver
|
||||
|
||||
self.assertEqual('2f4f60214cf43c595666dd815f0360a4',
|
||||
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
|
||||
|
||||
self.flags(nfs_mount_point_base=self.TEST_MNT_POINT_BASE)
|
||||
|
||||
self.assertEqual('/mnt/test/2f4f60214cf43c595666dd815f0360a4',
|
||||
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
|
||||
|
||||
self.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))
|
||||
|
||||
def test_get_available_capacity_with_du(self):
|
||||
"""_get_available_capacity should calculate correct value"""
|
||||
mox = self.mox
|
||||
drv = self._driver
|
||||
|
||||
self.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))
|
||||
|
||||
def test_load_shares_config(self):
|
||||
mox = self.mox
|
||||
drv = self._driver
|
||||
|
||||
self.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)
|
||||
|
||||
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)
|
||||
|
||||
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])
|
||||
|
||||
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))
|
||||
|
||||
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
|
||||
|
||||
self.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
|
||||
|
||||
self.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))
|
||||
|
||||
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))
|
||||
|
||||
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)
|
||||
|
||||
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()
|
||||
|
||||
self.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)
|
||||
|
||||
def test_create_nonsparsed_volume(self):
|
||||
mox = self.mox
|
||||
drv = self._driver
|
||||
volume = self._simple_volume()
|
||||
|
||||
self.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)
|
||||
|
||||
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)
|
||||
|
||||
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'])
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
|
@ -72,7 +72,6 @@ class APITestCase(test.TestCase):
|
|||
|
||||
# Marking out the default extension paths makes this test MUCH faster.
|
||||
self.flags(osapi_compute_extension=[])
|
||||
self.flags(osapi_volume_extension=[])
|
||||
|
||||
found = False
|
||||
mgr = computeextensions.ExtensionManager()
|
||||
|
|
|
@ -1,161 +0,0 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 Josh Durgin
|
||||
# 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.
|
||||
|
||||
from nova import db
|
||||
from nova import exception
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.openstack.common import timeutils
|
||||
from nova import test
|
||||
from nova.tests.image import fake as fake_image
|
||||
from nova.tests.test_volume import DriverTestCase
|
||||
from nova.volume.driver import RBDDriver
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RBDTestCase(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(RBDTestCase, self).setUp()
|
||||
|
||||
def fake_execute(*args):
|
||||
pass
|
||||
self.driver = RBDDriver(execute=fake_execute)
|
||||
|
||||
def test_good_locations(self):
|
||||
locations = [
|
||||
'rbd://fsid/pool/image/snap',
|
||||
'rbd://%2F/%2F/%2F/%2F',
|
||||
]
|
||||
map(self.driver._parse_location, locations)
|
||||
|
||||
def test_bad_locations(self):
|
||||
locations = [
|
||||
'rbd://image',
|
||||
'http://path/to/somewhere/else',
|
||||
'rbd://image/extra',
|
||||
'rbd://image/',
|
||||
'rbd://fsid/pool/image/',
|
||||
'rbd://fsid/pool/image/snap/',
|
||||
'rbd://///',
|
||||
]
|
||||
for loc in locations:
|
||||
self.assertRaises(exception.ImageUnacceptable,
|
||||
self.driver._parse_location,
|
||||
loc)
|
||||
self.assertFalse(self.driver._is_cloneable(loc))
|
||||
|
||||
def test_cloneable(self):
|
||||
self.stubs.Set(self.driver, '_get_fsid', lambda: 'abc')
|
||||
location = 'rbd://abc/pool/image/snap'
|
||||
self.assertTrue(self.driver._is_cloneable(location))
|
||||
|
||||
def test_uncloneable_different_fsid(self):
|
||||
self.stubs.Set(self.driver, '_get_fsid', lambda: 'abc')
|
||||
location = 'rbd://def/pool/image/snap'
|
||||
self.assertFalse(self.driver._is_cloneable(location))
|
||||
|
||||
def test_uncloneable_unreadable(self):
|
||||
def fake_exc(*args):
|
||||
raise exception.ProcessExecutionError()
|
||||
self.stubs.Set(self.driver, '_get_fsid', lambda: 'abc')
|
||||
self.stubs.Set(self.driver, '_execute', fake_exc)
|
||||
location = 'rbd://abc/pool/image/snap'
|
||||
self.assertFalse(self.driver._is_cloneable(location))
|
||||
|
||||
|
||||
class FakeRBDDriver(RBDDriver):
|
||||
|
||||
def _clone(self):
|
||||
pass
|
||||
|
||||
def _resize(self):
|
||||
pass
|
||||
|
||||
|
||||
class ManagedRBDTestCase(DriverTestCase):
|
||||
driver_name = "nova.tests.test_rbd.FakeRBDDriver"
|
||||
|
||||
def setUp(self):
|
||||
super(ManagedRBDTestCase, self).setUp()
|
||||
fake_image.stub_out_image_service(self.stubs)
|
||||
|
||||
def _clone_volume_from_image(self, expected_status,
|
||||
clone_works=True):
|
||||
"""Try to clone a volume from an image, and check the status
|
||||
afterwards"""
|
||||
def fake_clone_image(volume, image_location):
|
||||
pass
|
||||
|
||||
def fake_clone_error(volume, image_location):
|
||||
raise exception.NovaException()
|
||||
|
||||
self.stubs.Set(self.volume.driver, '_is_cloneable', lambda x: True)
|
||||
if clone_works:
|
||||
self.stubs.Set(self.volume.driver, 'clone_image', fake_clone_image)
|
||||
else:
|
||||
self.stubs.Set(self.volume.driver, 'clone_image', fake_clone_error)
|
||||
|
||||
image_id = 'c905cedb-7281-47e4-8a62-f26bc5fc4c77'
|
||||
volume_id = 1
|
||||
# creating volume testdata
|
||||
db.volume_create(self.context, {'id': volume_id,
|
||||
'updated_at': timeutils.utcnow(),
|
||||
'display_description': 'Test Desc',
|
||||
'size': 20,
|
||||
'status': 'creating',
|
||||
'instance_uuid': None,
|
||||
'host': 'dummy'})
|
||||
try:
|
||||
if clone_works:
|
||||
self.volume.create_volume(self.context,
|
||||
volume_id,
|
||||
image_id=image_id)
|
||||
else:
|
||||
self.assertRaises(exception.NovaException,
|
||||
self.volume.create_volume,
|
||||
self.context,
|
||||
volume_id,
|
||||
image_id=image_id)
|
||||
|
||||
volume = db.volume_get(self.context, volume_id)
|
||||
self.assertEqual(volume['status'], expected_status)
|
||||
finally:
|
||||
# cleanup
|
||||
db.volume_destroy(self.context, volume_id)
|
||||
|
||||
def test_clone_image_status_available(self):
|
||||
"""Verify that before cloning, an image is in the available state."""
|
||||
self._clone_volume_from_image('available', True)
|
||||
|
||||
def test_clone_image_status_error(self):
|
||||
"""Verify that before cloning, an image is in the available state."""
|
||||
self._clone_volume_from_image('error', False)
|
||||
|
||||
def test_clone_success(self):
|
||||
self.stubs.Set(self.volume.driver, '_is_cloneable', lambda x: True)
|
||||
self.stubs.Set(self.volume.driver, 'clone_image', lambda a, b: True)
|
||||
image_id = 'c905cedb-7281-47e4-8a62-f26bc5fc4c77'
|
||||
self.assertTrue(self.volume.driver.clone_image({}, image_id))
|
||||
|
||||
def test_clone_bad_image_id(self):
|
||||
self.stubs.Set(self.volume.driver, '_is_cloneable', lambda x: True)
|
||||
self.assertFalse(self.volume.driver.clone_image({}, None))
|
||||
|
||||
def test_clone_uncloneable(self):
|
||||
self.stubs.Set(self.volume.driver, '_is_cloneable', lambda x: False)
|
||||
self.assertFalse(self.volume.driver.clone_image({}, 'dne'))
|
|
@ -1,208 +0,0 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 OpenStack LLC.
|
||||
# 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.
|
||||
|
||||
from nova import exception
|
||||
from nova.openstack.common import log as logging
|
||||
from nova import test
|
||||
from nova.volume.solidfire import SolidFire
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SolidFireVolumeTestCase(test.TestCase):
|
||||
def fake_issue_api_request(obj, method, params):
|
||||
if method is 'GetClusterInfo':
|
||||
LOG.info('Called Fake GetClusterInfo...')
|
||||
results = {'result': {'clusterInfo':
|
||||
{'name': 'fake-cluster',
|
||||
'mvip': '1.1.1.1',
|
||||
'svip': '1.1.1.1',
|
||||
'uniqueID': 'unqid',
|
||||
'repCount': 2,
|
||||
'attributes': {}}}}
|
||||
return results
|
||||
|
||||
elif method is 'AddAccount':
|
||||
LOG.info('Called Fake AddAccount...')
|
||||
return {'result': {'accountID': 25}, 'id': 1}
|
||||
|
||||
elif method is 'GetAccountByName':
|
||||
LOG.info('Called Fake GetAccountByName...')
|
||||
results = {'result': {'account':
|
||||
{'accountID': 25,
|
||||
'username': params['username'],
|
||||
'status': 'active',
|
||||
'initiatorSecret': '123456789012',
|
||||
'targetSecret': '123456789012',
|
||||
'attributes': {},
|
||||
'volumes': [6, 7, 20]}},
|
||||
"id": 1}
|
||||
return results
|
||||
|
||||
elif method is 'CreateVolume':
|
||||
LOG.info('Called Fake CreateVolume...')
|
||||
return {'result': {'volumeID': 5}, 'id': 1}
|
||||
|
||||
elif method is 'DeleteVolume':
|
||||
LOG.info('Called Fake DeleteVolume...')
|
||||
return {'result': {}, 'id': 1}
|
||||
|
||||
elif method is 'ListVolumesForAccount':
|
||||
test_name = 'OS-VOLID-a720b3c0-d1f0-11e1-9b23-0800200c9a66'
|
||||
LOG.info('Called Fake ListVolumesForAccount...')
|
||||
result = {'result': {
|
||||
'volumes': [{'volumeID': 5,
|
||||
'name': test_name,
|
||||
'accountID': 25,
|
||||
'sliceCount': 1,
|
||||
'totalSize': 1048576 * 1024,
|
||||
'enable512e': True,
|
||||
'access': "readWrite",
|
||||
'status': "active",
|
||||
'attributes':None,
|
||||
'qos': None,
|
||||
'iqn': test_name}]}}
|
||||
return result
|
||||
|
||||
else:
|
||||
LOG.error('Crap, unimplemented API call in Fake:%s' % method)
|
||||
|
||||
def fake_issue_api_request_no_volume(obj, method, params):
|
||||
if method is 'ListVolumesForAccount':
|
||||
LOG.info('Called Fake ListVolumesForAccount...')
|
||||
return {'result': {'volumes': []}}
|
||||
else:
|
||||
return obj.fake_issue_api_request(method, params)
|
||||
|
||||
def fake_issue_api_request_fails(obj, method, params):
|
||||
return {'error': {'code': 000,
|
||||
'name': 'DummyError',
|
||||
'message': 'This is a fake error response'},
|
||||
'id': 1}
|
||||
|
||||
def fake_volume_get(obj, key, default=None):
|
||||
return {'qos': 'fast'}
|
||||
|
||||
def test_create_volume(self):
|
||||
self.stubs.Set(SolidFire, '_issue_api_request',
|
||||
self.fake_issue_api_request)
|
||||
testvol = {'project_id': 'testprjid',
|
||||
'name': 'testvol',
|
||||
'size': 1,
|
||||
'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66'}
|
||||
sfv = SolidFire()
|
||||
model_update = sfv.create_volume(testvol)
|
||||
|
||||
def test_create_volume_with_qos(self):
|
||||
preset_qos = {}
|
||||
preset_qos['qos'] = 'fast'
|
||||
self.stubs.Set(SolidFire, '_issue_api_request',
|
||||
self.fake_issue_api_request)
|
||||
|
||||
testvol = {'project_id': 'testprjid',
|
||||
'name': 'testvol',
|
||||
'size': 1,
|
||||
'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66',
|
||||
'metadata': [preset_qos]}
|
||||
|
||||
sfv = SolidFire()
|
||||
model_update = sfv.create_volume(testvol)
|
||||
|
||||
def test_create_volume_fails(self):
|
||||
self.stubs.Set(SolidFire, '_issue_api_request',
|
||||
self.fake_issue_api_request_fails)
|
||||
testvol = {'project_id': 'testprjid',
|
||||
'name': 'testvol',
|
||||
'size': 1,
|
||||
'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66'}
|
||||
sfv = SolidFire()
|
||||
self.assertRaises(exception.SolidFireAPIDataException,
|
||||
sfv.create_volume, testvol)
|
||||
|
||||
def test_create_sfaccount(self):
|
||||
sfv = SolidFire()
|
||||
self.stubs.Set(SolidFire, '_issue_api_request',
|
||||
self.fake_issue_api_request)
|
||||
account = sfv._create_sfaccount('project-id')
|
||||
self.assertNotEqual(account, None)
|
||||
|
||||
def test_create_sfaccount_fails(self):
|
||||
sfv = SolidFire()
|
||||
self.stubs.Set(SolidFire, '_issue_api_request',
|
||||
self.fake_issue_api_request_fails)
|
||||
account = sfv._create_sfaccount('project-id')
|
||||
self.assertEqual(account, None)
|
||||
|
||||
def test_get_sfaccount_by_name(self):
|
||||
sfv = SolidFire()
|
||||
self.stubs.Set(SolidFire, '_issue_api_request',
|
||||
self.fake_issue_api_request)
|
||||
account = sfv._get_sfaccount_by_name('some-name')
|
||||
self.assertNotEqual(account, None)
|
||||
|
||||
def test_get_sfaccount_by_name_fails(self):
|
||||
sfv = SolidFire()
|
||||
self.stubs.Set(SolidFire, '_issue_api_request',
|
||||
self.fake_issue_api_request_fails)
|
||||
account = sfv._get_sfaccount_by_name('some-name')
|
||||
self.assertEqual(account, None)
|
||||
|
||||
def test_delete_volume(self):
|
||||
self.stubs.Set(SolidFire, '_issue_api_request',
|
||||
self.fake_issue_api_request)
|
||||
testvol = {'project_id': 'testprjid',
|
||||
'name': 'test_volume',
|
||||
'size': 1,
|
||||
'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66'}
|
||||
sfv = SolidFire()
|
||||
model_update = sfv.delete_volume(testvol)
|
||||
|
||||
def test_delete_volume_fails_no_volume(self):
|
||||
self.stubs.Set(SolidFire, '_issue_api_request',
|
||||
self.fake_issue_api_request_no_volume)
|
||||
testvol = {'project_id': 'testprjid',
|
||||
'name': 'no-name',
|
||||
'size': 1,
|
||||
'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66'}
|
||||
sfv = SolidFire()
|
||||
self.assertRaises(exception.VolumeNotFound,
|
||||
sfv.delete_volume, testvol)
|
||||
|
||||
def test_delete_volume_fails_account_lookup(self):
|
||||
self.stubs.Set(SolidFire, '_issue_api_request',
|
||||
self.fake_issue_api_request_fails)
|
||||
testvol = {'project_id': 'testprjid',
|
||||
'name': 'no-name',
|
||||
'size': 1,
|
||||
'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66'}
|
||||
sfv = SolidFire()
|
||||
self.assertRaises(exception.SfAccountNotFound,
|
||||
sfv.delete_volume,
|
||||
testvol)
|
||||
|
||||
def test_get_cluster_info(self):
|
||||
self.stubs.Set(SolidFire, '_issue_api_request',
|
||||
self.fake_issue_api_request)
|
||||
sfv = SolidFire()
|
||||
sfv._get_cluster_info()
|
||||
|
||||
def test_get_cluster_info_fail(self):
|
||||
self.stubs.Set(SolidFire, '_issue_api_request',
|
||||
self.fake_issue_api_request_fails)
|
||||
sfv = SolidFire()
|
||||
self.assertRaises(exception.SolidFireAPIException,
|
||||
sfv._get_cluster_info)
|
File diff suppressed because it is too large
Load Diff
|
@ -1,931 +0,0 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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 Volume Code.
|
||||
|
||||
"""
|
||||
|
||||
import cStringIO
|
||||
import datetime
|
||||
|
||||
import mox
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
from nova import context
|
||||
from nova import db
|
||||
from nova import exception
|
||||
from nova import flags
|
||||
from nova.openstack.common import importutils
|
||||
from nova.openstack.common.notifier import api as notifier_api
|
||||
from nova.openstack.common.notifier import test_notifier
|
||||
from nova.openstack.common import rpc
|
||||
import nova.policy
|
||||
from nova import quota
|
||||
from nova import test
|
||||
from nova.tests.image import fake as fake_image
|
||||
import nova.volume.api
|
||||
from nova.volume import iscsi
|
||||
|
||||
QUOTAS = quota.QUOTAS
|
||||
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
|
||||
|
||||
class VolumeTestCase(test.TestCase):
|
||||
"""Test Case for volumes."""
|
||||
|
||||
def setUp(self):
|
||||
super(VolumeTestCase, self).setUp()
|
||||
self.compute = importutils.import_object(FLAGS.compute_manager)
|
||||
vol_tmpdir = tempfile.mkdtemp()
|
||||
self.flags(compute_driver='nova.virt.fake.FakeDriver',
|
||||
volumes_dir=vol_tmpdir,
|
||||
notification_driver=[test_notifier.__name__])
|
||||
self.stubs.Set(iscsi.TgtAdm, '_get_target', self.fake_get_target)
|
||||
self.volume = importutils.import_object(FLAGS.volume_manager)
|
||||
self.context = context.get_admin_context()
|
||||
instance = db.instance_create(self.context, {})
|
||||
self.instance_id = instance['id']
|
||||
self.instance_uuid = instance['uuid']
|
||||
test_notifier.NOTIFICATIONS = []
|
||||
fake_image.stub_out_image_service(self.stubs)
|
||||
|
||||
def tearDown(self):
|
||||
try:
|
||||
shutil.rmtree(FLAGS.volumes_dir)
|
||||
except OSError:
|
||||
pass
|
||||
db.instance_destroy(self.context, self.instance_uuid)
|
||||
notifier_api._reset_drivers()
|
||||
super(VolumeTestCase, self).tearDown()
|
||||
|
||||
def fake_get_target(obj, iqn):
|
||||
return 1
|
||||
|
||||
@staticmethod
|
||||
def _create_volume(size=0, snapshot_id=None, image_id=None, metadata=None):
|
||||
"""Create a volume object."""
|
||||
vol = {}
|
||||
vol['size'] = size
|
||||
vol['snapshot_id'] = snapshot_id
|
||||
vol['image_id'] = image_id
|
||||
vol['user_id'] = 'fake'
|
||||
vol['project_id'] = 'fake'
|
||||
vol['availability_zone'] = FLAGS.storage_availability_zone
|
||||
vol['status'] = "creating"
|
||||
vol['attach_status'] = "detached"
|
||||
if metadata is not None:
|
||||
vol['metadata'] = metadata
|
||||
return db.volume_create(context.get_admin_context(), vol)
|
||||
|
||||
def test_ec2_uuid_mapping(self):
|
||||
ec2_vol = db.ec2_volume_create(context.get_admin_context(),
|
||||
'aaaaaaaa-bbbb-bbbb-bbbb-aaaaaaaaaaaa', 5)
|
||||
self.assertEqual(5, ec2_vol['id'])
|
||||
self.assertEqual('aaaaaaaa-bbbb-bbbb-bbbb-aaaaaaaaaaaa',
|
||||
db.get_volume_uuid_by_ec2_id(context.get_admin_context(), 5))
|
||||
|
||||
ec2_vol = db.ec2_volume_create(context.get_admin_context(),
|
||||
'aaaaaaaa-bbbb-bbbb-bbbb-aaaaaaaaaaaa', 1)
|
||||
self.assertEqual(1, ec2_vol['id'])
|
||||
|
||||
ec2_vol = db.ec2_volume_create(context.get_admin_context(),
|
||||
'aaaaaaaa-bbbb-bbbb-bbbb-aaaaaaaaazzz')
|
||||
self.assertEqual(6, ec2_vol['id'])
|
||||
|
||||
def test_create_delete_volume(self):
|
||||
"""Test volume can be created and deleted."""
|
||||
# Need to stub out reserve, commit, and rollback
|
||||
def fake_reserve(context, expire=None, **deltas):
|
||||
return ["RESERVATION"]
|
||||
|
||||
def fake_commit(context, reservations):
|
||||
pass
|
||||
|
||||
def fake_rollback(context, reservations):
|
||||
pass
|
||||
|
||||
self.stubs.Set(QUOTAS, "reserve", fake_reserve)
|
||||
self.stubs.Set(QUOTAS, "commit", fake_commit)
|
||||
self.stubs.Set(QUOTAS, "rollback", fake_rollback)
|
||||
|
||||
volume = self._create_volume()
|
||||
volume_id = volume['id']
|
||||
self.assertEquals(len(test_notifier.NOTIFICATIONS), 0)
|
||||
self.volume.create_volume(self.context, volume_id)
|
||||
self.assertEquals(len(test_notifier.NOTIFICATIONS), 2)
|
||||
self.assertEqual(volume_id, db.volume_get(context.get_admin_context(),
|
||||
volume_id).id)
|
||||
|
||||
self.volume.delete_volume(self.context, volume_id)
|
||||
self.assertEquals(len(test_notifier.NOTIFICATIONS), 4)
|
||||
self.assertRaises(exception.NotFound,
|
||||
db.volume_get,
|
||||
self.context,
|
||||
volume_id)
|
||||
|
||||
def test_create_delete_volume_with_metadata(self):
|
||||
"""Test volume can be created and deleted."""
|
||||
test_meta = {'fake_key': 'fake_value'}
|
||||
volume = self._create_volume('0', None, metadata=test_meta)
|
||||
volume_id = volume['id']
|
||||
self.volume.create_volume(self.context, volume_id)
|
||||
result_meta = {
|
||||
volume.volume_metadata[0].key: volume.volume_metadata[0].value}
|
||||
self.assertEqual(result_meta, test_meta)
|
||||
|
||||
self.volume.delete_volume(self.context, volume_id)
|
||||
self.assertRaises(exception.NotFound,
|
||||
db.volume_get,
|
||||
self.context,
|
||||
volume_id)
|
||||
|
||||
def _do_test_create_over_quota(self, resource, expected):
|
||||
"""Test volume creation over quota."""
|
||||
|
||||
def fake_reserve(context, **deltas):
|
||||
kwargs = dict(overs=[resource],
|
||||
quotas=dict(gigabytes=1000, volumes=10),
|
||||
usages=dict(gigabytes=dict(reserved=1, in_use=999),
|
||||
volumes=dict(reserved=1, in_use=9)))
|
||||
raise exception.OverQuota(**kwargs)
|
||||
|
||||
def fake_commit(context, reservations):
|
||||
self.fail('should not commit over quota')
|
||||
|
||||
self.stubs.Set(QUOTAS, 'reserve', fake_reserve)
|
||||
self.stubs.Set(QUOTAS, 'commit', fake_commit)
|
||||
|
||||
volume_api = nova.volume.api.API()
|
||||
|
||||
self.assertRaises(expected,
|
||||
volume_api.create,
|
||||
self.context,
|
||||
2,
|
||||
'name',
|
||||
'description')
|
||||
|
||||
def test_create_volumes_over_quota(self):
|
||||
self._do_test_create_over_quota('volumes',
|
||||
exception.VolumeLimitExceeded)
|
||||
|
||||
def test_create_gigabytes_over_quota(self):
|
||||
self._do_test_create_over_quota('gigabytes',
|
||||
exception.VolumeSizeTooLarge)
|
||||
|
||||
def test_delete_busy_volume(self):
|
||||
"""Test volume survives deletion if driver reports it as busy."""
|
||||
volume = self._create_volume()
|
||||
volume_id = volume['id']
|
||||
self.volume.create_volume(self.context, volume_id)
|
||||
|
||||
self.mox.StubOutWithMock(self.volume.driver, 'delete_volume')
|
||||
self.volume.driver.delete_volume(mox.IgnoreArg()).AndRaise(
|
||||
exception.VolumeIsBusy)
|
||||
self.mox.ReplayAll()
|
||||
res = self.volume.delete_volume(self.context, volume_id)
|
||||
self.assertEqual(True, res)
|
||||
volume_ref = db.volume_get(context.get_admin_context(), volume_id)
|
||||
self.assertEqual(volume_id, volume_ref.id)
|
||||
self.assertEqual("available", volume_ref.status)
|
||||
|
||||
self.mox.UnsetStubs()
|
||||
self.volume.delete_volume(self.context, volume_id)
|
||||
|
||||
def test_create_volume_from_snapshot(self):
|
||||
"""Test volume can be created from a snapshot."""
|
||||
volume_src = self._create_volume()
|
||||
self.volume.create_volume(self.context, volume_src['id'])
|
||||
snapshot_id = self._create_snapshot(volume_src['id'])
|
||||
self.volume.create_snapshot(self.context, volume_src['id'],
|
||||
snapshot_id)
|
||||
volume_dst = self._create_volume(0, snapshot_id)
|
||||
self.volume.create_volume(self.context, volume_dst['id'], snapshot_id)
|
||||
self.assertEqual(volume_dst['id'],
|
||||
db.volume_get(
|
||||
context.get_admin_context(),
|
||||
volume_dst['id']).id)
|
||||
self.assertEqual(snapshot_id, db.volume_get(
|
||||
context.get_admin_context(),
|
||||
volume_dst['id']).snapshot_id)
|
||||
|
||||
self.volume.delete_volume(self.context, volume_dst['id'])
|
||||
self.volume.delete_snapshot(self.context, snapshot_id)
|
||||
self.volume.delete_volume(self.context, volume_src['id'])
|
||||
|
||||
def test_too_big_volume(self):
|
||||
"""Ensure failure if a too large of a volume is requested."""
|
||||
# FIXME(vish): validation needs to move into the data layer in
|
||||
# volume_create
|
||||
return True
|
||||
try:
|
||||
volume = self._create_volume('1001')
|
||||
self.volume.create_volume(self.context, volume)
|
||||
self.fail("Should have thrown TypeError")
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
def test_run_attach_detach_volume(self):
|
||||
"""Make sure volume can be attached and detached from instance."""
|
||||
inst = {}
|
||||
inst['image_id'] = 1
|
||||
inst['reservation_id'] = 'r-fakeres'
|
||||
inst['launch_time'] = '10'
|
||||
inst['user_id'] = 'fake'
|
||||
inst['project_id'] = 'fake'
|
||||
inst['instance_type_id'] = '2' # m1.tiny
|
||||
inst['ami_launch_index'] = 0
|
||||
instance = db.instance_create(self.context, {})
|
||||
instance_id = instance['id']
|
||||
instance_uuid = instance['uuid']
|
||||
mountpoint = "/dev/sdf"
|
||||
volume = self._create_volume()
|
||||
volume_id = volume['id']
|
||||
self.volume.create_volume(self.context, volume_id)
|
||||
if FLAGS.fake_tests:
|
||||
db.volume_attached(self.context, volume_id, instance_uuid,
|
||||
mountpoint)
|
||||
else:
|
||||
self.compute.attach_volume(self.context,
|
||||
instance_uuid,
|
||||
volume_id,
|
||||
mountpoint)
|
||||
vol = db.volume_get(context.get_admin_context(), volume_id)
|
||||
self.assertEqual(vol['status'], "in-use")
|
||||
self.assertEqual(vol['attach_status'], "attached")
|
||||
self.assertEqual(vol['mountpoint'], mountpoint)
|
||||
self.assertEqual(vol['instance_uuid'], instance_uuid)
|
||||
self.assertNotEqual(vol['attach_time'], None)
|
||||
|
||||
self.assertRaises(exception.VolumeAttached,
|
||||
self.volume.delete_volume,
|
||||
self.context,
|
||||
volume_id)
|
||||
if FLAGS.fake_tests:
|
||||
db.volume_detached(self.context, volume_id)
|
||||
else:
|
||||
self.compute.detach_volume(self.context,
|
||||
instance_uuid,
|
||||
volume_id)
|
||||
vol = db.volume_get(self.context, volume_id)
|
||||
self.assertEqual(vol['status'], "available")
|
||||
self.assertEqual(vol['attach_time'], None)
|
||||
|
||||
self.volume.delete_volume(self.context, volume_id)
|
||||
self.assertRaises(exception.VolumeNotFound,
|
||||
db.volume_get,
|
||||
self.context,
|
||||
volume_id)
|
||||
db.instance_destroy(self.context, instance_uuid)
|
||||
|
||||
def test_concurrent_volumes_get_different_targets(self):
|
||||
"""Ensure multiple concurrent volumes get different targets."""
|
||||
volume_ids = []
|
||||
targets = []
|
||||
|
||||
def _check(volume_id):
|
||||
"""Make sure targets aren't duplicated."""
|
||||
volume_ids.append(volume_id)
|
||||
admin_context = context.get_admin_context()
|
||||
iscsi_target = db.volume_get_iscsi_target_num(admin_context,
|
||||
volume_id)
|
||||
self.assert_(iscsi_target not in targets)
|
||||
targets.append(iscsi_target)
|
||||
|
||||
total_slots = FLAGS.iscsi_num_targets
|
||||
for _index in xrange(total_slots):
|
||||
self._create_volume()
|
||||
for volume_id in volume_ids:
|
||||
self.volume.delete_volume(self.context, volume_id)
|
||||
|
||||
def test_multi_node(self):
|
||||
# TODO(termie): Figure out how to test with two nodes,
|
||||
# each of them having a different FLAG for storage_node
|
||||
# This will allow us to test cross-node interactions
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def _create_snapshot(volume_id, size='0'):
|
||||
"""Create a snapshot object."""
|
||||
snap = {}
|
||||
snap['volume_size'] = size
|
||||
snap['user_id'] = 'fake'
|
||||
snap['project_id'] = 'fake'
|
||||
snap['volume_id'] = volume_id
|
||||
snap['status'] = "creating"
|
||||
return db.snapshot_create(context.get_admin_context(), snap)['id']
|
||||
|
||||
def test_create_delete_snapshot(self):
|
||||
"""Test snapshot can be created and deleted."""
|
||||
volume = self._create_volume()
|
||||
self.volume.create_volume(self.context, volume['id'])
|
||||
snapshot_id = self._create_snapshot(volume['id'])
|
||||
self.volume.create_snapshot(self.context, volume['id'], snapshot_id)
|
||||
self.assertEqual(snapshot_id,
|
||||
db.snapshot_get(context.get_admin_context(),
|
||||
snapshot_id).id)
|
||||
|
||||
self.volume.delete_snapshot(self.context, snapshot_id)
|
||||
self.assertRaises(exception.NotFound,
|
||||
db.snapshot_get,
|
||||
self.context,
|
||||
snapshot_id)
|
||||
self.volume.delete_volume(self.context, volume['id'])
|
||||
|
||||
def test_cant_delete_volume_in_use(self):
|
||||
"""Test volume can't be deleted in invalid stats."""
|
||||
# create a volume and assign to host
|
||||
volume = self._create_volume()
|
||||
self.volume.create_volume(self.context, volume['id'])
|
||||
volume['status'] = 'in-use'
|
||||
volume['host'] = 'fakehost'
|
||||
|
||||
volume_api = nova.volume.api.API()
|
||||
|
||||
# 'in-use' status raises InvalidVolume
|
||||
self.assertRaises(exception.InvalidVolume,
|
||||
volume_api.delete,
|
||||
self.context,
|
||||
volume)
|
||||
|
||||
# clean up
|
||||
self.volume.delete_volume(self.context, volume['id'])
|
||||
|
||||
def test_force_delete_volume(self):
|
||||
"""Test volume can be forced to delete."""
|
||||
# create a volume and assign to host
|
||||
volume = self._create_volume()
|
||||
self.volume.create_volume(self.context, volume['id'])
|
||||
volume['status'] = 'error_deleting'
|
||||
volume['host'] = 'fakehost'
|
||||
|
||||
volume_api = nova.volume.api.API()
|
||||
|
||||
# 'error_deleting' volumes can't be deleted
|
||||
self.assertRaises(exception.InvalidVolume,
|
||||
volume_api.delete,
|
||||
self.context,
|
||||
volume)
|
||||
|
||||
# delete with force
|
||||
volume_api.delete(self.context, volume, force=True)
|
||||
|
||||
# status is deleting
|
||||
volume = db.volume_get(context.get_admin_context(), volume['id'])
|
||||
self.assertEquals(volume['status'], 'deleting')
|
||||
|
||||
# clean up
|
||||
self.volume.delete_volume(self.context, volume['id'])
|
||||
|
||||
def test_cant_delete_volume_with_snapshots(self):
|
||||
"""Test snapshot can be created and deleted."""
|
||||
volume = self._create_volume()
|
||||
self.volume.create_volume(self.context, volume['id'])
|
||||
snapshot_id = self._create_snapshot(volume['id'])
|
||||
self.volume.create_snapshot(self.context, volume['id'], snapshot_id)
|
||||
self.assertEqual(snapshot_id,
|
||||
db.snapshot_get(context.get_admin_context(),
|
||||
snapshot_id).id)
|
||||
|
||||
volume['status'] = 'available'
|
||||
volume['host'] = 'fakehost'
|
||||
|
||||
volume_api = nova.volume.api.API()
|
||||
|
||||
self.assertRaises(exception.InvalidVolume,
|
||||
volume_api.delete,
|
||||
self.context,
|
||||
volume)
|
||||
self.volume.delete_snapshot(self.context, snapshot_id)
|
||||
self.volume.delete_volume(self.context, volume['id'])
|
||||
|
||||
def test_can_delete_errored_snapshot(self):
|
||||
"""Test snapshot can be created and deleted."""
|
||||
volume = self._create_volume()
|
||||
self.volume.create_volume(self.context, volume['id'])
|
||||
snapshot_id = self._create_snapshot(volume['id'])
|
||||
self.volume.create_snapshot(self.context, volume['id'], snapshot_id)
|
||||
snapshot = db.snapshot_get(context.get_admin_context(),
|
||||
snapshot_id)
|
||||
|
||||
volume_api = nova.volume.api.API()
|
||||
|
||||
snapshot['status'] = 'badstatus'
|
||||
self.assertRaises(exception.InvalidVolume,
|
||||
volume_api.delete_snapshot,
|
||||
self.context,
|
||||
snapshot)
|
||||
|
||||
snapshot['status'] = 'error'
|
||||
self.volume.delete_snapshot(self.context, snapshot_id)
|
||||
self.volume.delete_volume(self.context, volume['id'])
|
||||
|
||||
def test_create_snapshot_force(self):
|
||||
"""Test snapshot in use can be created forcibly."""
|
||||
|
||||
def fake_cast(ctxt, topic, msg):
|
||||
pass
|
||||
self.stubs.Set(rpc, 'cast', fake_cast)
|
||||
|
||||
volume = self._create_volume()
|
||||
self.volume.create_volume(self.context, volume['id'])
|
||||
db.volume_attached(self.context, volume['id'], self.instance_uuid,
|
||||
'/dev/sda1')
|
||||
|
||||
volume_api = nova.volume.api.API()
|
||||
volume = volume_api.get(self.context, volume['id'])
|
||||
self.assertRaises(exception.InvalidVolume,
|
||||
volume_api.create_snapshot,
|
||||
self.context, volume,
|
||||
'fake_name', 'fake_description')
|
||||
snapshot_ref = volume_api.create_snapshot_force(self.context,
|
||||
volume,
|
||||
'fake_name',
|
||||
'fake_description')
|
||||
db.snapshot_destroy(self.context, snapshot_ref['id'])
|
||||
db.volume_destroy(self.context, volume['id'])
|
||||
|
||||
def test_delete_busy_snapshot(self):
|
||||
"""Test snapshot can be created and deleted."""
|
||||
volume = self._create_volume()
|
||||
volume_id = volume['id']
|
||||
self.volume.create_volume(self.context, volume_id)
|
||||
snapshot_id = self._create_snapshot(volume_id)
|
||||
self.volume.create_snapshot(self.context, volume_id, snapshot_id)
|
||||
|
||||
self.mox.StubOutWithMock(self.volume.driver, 'delete_snapshot')
|
||||
self.volume.driver.delete_snapshot(mox.IgnoreArg()).AndRaise(
|
||||
exception.SnapshotIsBusy)
|
||||
self.mox.ReplayAll()
|
||||
self.volume.delete_snapshot(self.context, snapshot_id)
|
||||
snapshot_ref = db.snapshot_get(self.context, snapshot_id)
|
||||
self.assertEqual(snapshot_id, snapshot_ref.id)
|
||||
self.assertEqual("available", snapshot_ref.status)
|
||||
|
||||
self.mox.UnsetStubs()
|
||||
self.volume.delete_snapshot(self.context, snapshot_id)
|
||||
self.volume.delete_volume(self.context, volume_id)
|
||||
|
||||
def test_create_volume_usage_notification(self):
|
||||
"""Ensure create volume generates appropriate usage notification"""
|
||||
volume = self._create_volume()
|
||||
volume_id = volume['id']
|
||||
self.assertEquals(len(test_notifier.NOTIFICATIONS), 0)
|
||||
self.volume.create_volume(self.context, volume_id)
|
||||
self.assertEquals(len(test_notifier.NOTIFICATIONS), 2)
|
||||
msg = test_notifier.NOTIFICATIONS[0]
|
||||
self.assertEquals(msg['event_type'], 'volume.create.start')
|
||||
payload = msg['payload']
|
||||
self.assertEquals(payload['status'], 'creating')
|
||||
msg = test_notifier.NOTIFICATIONS[1]
|
||||
self.assertEquals(msg['priority'], 'INFO')
|
||||
self.assertEquals(msg['event_type'], 'volume.create.end')
|
||||
payload = msg['payload']
|
||||
self.assertEquals(payload['tenant_id'], volume['project_id'])
|
||||
self.assertEquals(payload['user_id'], volume['user_id'])
|
||||
self.assertEquals(payload['volume_id'], volume['id'])
|
||||
self.assertEquals(payload['status'], 'available')
|
||||
self.assertEquals(payload['size'], volume['size'])
|
||||
self.assertTrue('display_name' in payload)
|
||||
self.assertTrue('snapshot_id' in payload)
|
||||
self.assertTrue('launched_at' in payload)
|
||||
self.assertTrue('created_at' in payload)
|
||||
self.volume.delete_volume(self.context, volume_id)
|
||||
|
||||
def _do_test_create_volume_with_size(self, size):
|
||||
def fake_reserve(context, expire=None, **deltas):
|
||||
return ["RESERVATION"]
|
||||
|
||||
def fake_commit(context, reservations):
|
||||
pass
|
||||
|
||||
def fake_rollback(context, reservations):
|
||||
pass
|
||||
|
||||
self.stubs.Set(QUOTAS, "reserve", fake_reserve)
|
||||
self.stubs.Set(QUOTAS, "commit", fake_commit)
|
||||
self.stubs.Set(QUOTAS, "rollback", fake_rollback)
|
||||
|
||||
volume_api = nova.volume.api.API()
|
||||
|
||||
volume = volume_api.create(self.context,
|
||||
size,
|
||||
'name',
|
||||
'description')
|
||||
self.assertEquals(volume['size'], int(size))
|
||||
|
||||
def test_create_volume_int_size(self):
|
||||
"""Test volume creation with int size."""
|
||||
self._do_test_create_volume_with_size(2)
|
||||
|
||||
def test_create_volume_string_size(self):
|
||||
"""Test volume creation with string size."""
|
||||
self._do_test_create_volume_with_size('2')
|
||||
|
||||
def test_create_volume_with_bad_size(self):
|
||||
def fake_reserve(context, expire=None, **deltas):
|
||||
return ["RESERVATION"]
|
||||
|
||||
def fake_commit(context, reservations):
|
||||
pass
|
||||
|
||||
def fake_rollback(context, reservations):
|
||||
pass
|
||||
|
||||
self.stubs.Set(QUOTAS, "reserve", fake_reserve)
|
||||
self.stubs.Set(QUOTAS, "commit", fake_commit)
|
||||
self.stubs.Set(QUOTAS, "rollback", fake_rollback)
|
||||
|
||||
volume_api = nova.volume.api.API()
|
||||
|
||||
self.assertRaises(exception.InvalidInput,
|
||||
volume_api.create,
|
||||
self.context,
|
||||
'2Gb',
|
||||
'name',
|
||||
'description')
|
||||
|
||||
def test_begin_roll_detaching_volume(self):
|
||||
"""Test begin_detaching and roll_detaching functions."""
|
||||
volume = self._create_volume()
|
||||
volume_api = nova.volume.api.API()
|
||||
volume_api.begin_detaching(self.context, volume)
|
||||
volume = db.volume_get(self.context, volume['id'])
|
||||
self.assertEqual(volume['status'], "detaching")
|
||||
volume_api.roll_detaching(self.context, volume)
|
||||
volume = db.volume_get(self.context, volume['id'])
|
||||
self.assertEqual(volume['status'], "in-use")
|
||||
|
||||
def _create_volume_from_image(self, expected_status,
|
||||
fakeout_copy_image_to_volume=False):
|
||||
"""Call copy image to volume, Test the status of volume after calling
|
||||
copying image to volume."""
|
||||
def fake_local_path(volume):
|
||||
return dst_path
|
||||
|
||||
def fake_copy_image_to_volume(context, volume, image_id):
|
||||
pass
|
||||
|
||||
dst_fd, dst_path = tempfile.mkstemp()
|
||||
os.close(dst_fd)
|
||||
self.stubs.Set(self.volume.driver, 'local_path', fake_local_path)
|
||||
if fakeout_copy_image_to_volume:
|
||||
self.stubs.Set(self.volume, '_copy_image_to_volume',
|
||||
fake_copy_image_to_volume)
|
||||
|
||||
image_id = 'c905cedb-7281-47e4-8a62-f26bc5fc4c77'
|
||||
volume_id = 1
|
||||
# creating volume testdata
|
||||
db.volume_create(self.context, {'id': volume_id,
|
||||
'updated_at': datetime.datetime(1, 1, 1, 1, 1, 1),
|
||||
'display_description': 'Test Desc',
|
||||
'size': 20,
|
||||
'status': 'creating',
|
||||
'instance_uuid': None,
|
||||
'host': 'dummy'})
|
||||
try:
|
||||
self.volume.create_volume(self.context,
|
||||
volume_id,
|
||||
image_id=image_id)
|
||||
|
||||
volume = db.volume_get(self.context, volume_id)
|
||||
self.assertEqual(volume['status'], expected_status)
|
||||
finally:
|
||||
# cleanup
|
||||
db.volume_destroy(self.context, volume_id)
|
||||
os.unlink(dst_path)
|
||||
|
||||
def test_create_volume_from_image_status_downloading(self):
|
||||
"""Verify that before copying image to volume, it is in downloading
|
||||
state."""
|
||||
self._create_volume_from_image('downloading', True)
|
||||
|
||||
def test_create_volume_from_image_status_available(self):
|
||||
"""Verify that before copying image to volume, it is in available
|
||||
state."""
|
||||
self._create_volume_from_image('available')
|
||||
|
||||
def test_create_volume_from_image_exception(self):
|
||||
"""Verify that create volume from image, the volume status is
|
||||
'downloading'."""
|
||||
dst_fd, dst_path = tempfile.mkstemp()
|
||||
os.close(dst_fd)
|
||||
|
||||
self.stubs.Set(self.volume.driver, 'local_path', lambda x: dst_path)
|
||||
|
||||
image_id = 'aaaaaaaa-0000-0000-0000-000000000000'
|
||||
# creating volume testdata
|
||||
volume_id = 1
|
||||
db.volume_create(self.context, {'id': volume_id,
|
||||
'updated_at': datetime.datetime(1, 1, 1, 1, 1, 1),
|
||||
'display_description': 'Test Desc',
|
||||
'size': 20,
|
||||
'status': 'creating',
|
||||
'host': 'dummy'})
|
||||
|
||||
self.assertRaises(exception.ImageNotFound,
|
||||
self.volume.create_volume,
|
||||
self.context,
|
||||
volume_id,
|
||||
None,
|
||||
image_id)
|
||||
volume = db.volume_get(self.context, volume_id)
|
||||
self.assertEqual(volume['status'], "error")
|
||||
# cleanup
|
||||
db.volume_destroy(self.context, volume_id)
|
||||
os.unlink(dst_path)
|
||||
|
||||
def test_copy_volume_to_image_status_available(self):
|
||||
dst_fd, dst_path = tempfile.mkstemp()
|
||||
os.close(dst_fd)
|
||||
|
||||
def fake_local_path(volume):
|
||||
return dst_path
|
||||
|
||||
self.stubs.Set(self.volume.driver, 'local_path', fake_local_path)
|
||||
|
||||
image_id = '70a599e0-31e7-49b7-b260-868f441e862b'
|
||||
# creating volume testdata
|
||||
volume_id = 1
|
||||
db.volume_create(self.context, {'id': volume_id,
|
||||
'updated_at': datetime.datetime(1, 1, 1, 1, 1, 1),
|
||||
'display_description': 'Test Desc',
|
||||
'size': 20,
|
||||
'status': 'uploading',
|
||||
'instance_uuid': None,
|
||||
'host': 'dummy'})
|
||||
|
||||
try:
|
||||
# start test
|
||||
self.volume.copy_volume_to_image(self.context,
|
||||
volume_id,
|
||||
image_id)
|
||||
|
||||
volume = db.volume_get(self.context, volume_id)
|
||||
self.assertEqual(volume['status'], 'available')
|
||||
finally:
|
||||
# cleanup
|
||||
db.volume_destroy(self.context, volume_id)
|
||||
os.unlink(dst_path)
|
||||
|
||||
def test_copy_volume_to_image_status_use(self):
|
||||
dst_fd, dst_path = tempfile.mkstemp()
|
||||
os.close(dst_fd)
|
||||
|
||||
def fake_local_path(volume):
|
||||
return dst_path
|
||||
|
||||
self.stubs.Set(self.volume.driver, 'local_path', fake_local_path)
|
||||
|
||||
#image_id = '70a599e0-31e7-49b7-b260-868f441e862b'
|
||||
image_id = 'a440c04b-79fa-479c-bed1-0b816eaec379'
|
||||
# creating volume testdata
|
||||
volume_id = 1
|
||||
db.volume_create(self.context,
|
||||
{'id': volume_id,
|
||||
'updated_at': datetime.datetime(1, 1, 1, 1, 1, 1),
|
||||
'display_description': 'Test Desc',
|
||||
'size': 20,
|
||||
'status': 'uploading',
|
||||
'instance_uuid':
|
||||
'b21f957d-a72f-4b93-b5a5-45b1161abb02',
|
||||
'host': 'dummy'})
|
||||
|
||||
try:
|
||||
# start test
|
||||
self.volume.copy_volume_to_image(self.context,
|
||||
volume_id,
|
||||
image_id)
|
||||
|
||||
volume = db.volume_get(self.context, volume_id)
|
||||
self.assertEqual(volume['status'], 'in-use')
|
||||
finally:
|
||||
# cleanup
|
||||
db.volume_destroy(self.context, volume_id)
|
||||
os.unlink(dst_path)
|
||||
|
||||
def test_copy_volume_to_image_exception(self):
|
||||
dst_fd, dst_path = tempfile.mkstemp()
|
||||
os.close(dst_fd)
|
||||
|
||||
def fake_local_path(volume):
|
||||
return dst_path
|
||||
|
||||
self.stubs.Set(self.volume.driver, 'local_path', fake_local_path)
|
||||
|
||||
image_id = 'aaaaaaaa-0000-0000-0000-000000000000'
|
||||
# creating volume testdata
|
||||
volume_id = 1
|
||||
db.volume_create(self.context, {'id': volume_id,
|
||||
'updated_at': datetime.datetime(1, 1, 1, 1, 1, 1),
|
||||
'display_description': 'Test Desc',
|
||||
'size': 20,
|
||||
'status': 'in-use',
|
||||
'host': 'dummy'})
|
||||
|
||||
try:
|
||||
# start test
|
||||
self.assertRaises(exception.ImageNotFound,
|
||||
self.volume.copy_volume_to_image,
|
||||
self.context,
|
||||
volume_id,
|
||||
image_id)
|
||||
|
||||
volume = db.volume_get(self.context, volume_id)
|
||||
self.assertEqual(volume['status'], 'available')
|
||||
finally:
|
||||
# cleanup
|
||||
db.volume_destroy(self.context, volume_id)
|
||||
os.unlink(dst_path)
|
||||
|
||||
def test_create_volume_from_exact_sized_image(self):
|
||||
"""Verify that an image which is exactly the same size as the
|
||||
volume, will work correctly."""
|
||||
class _FakeImageService:
|
||||
def __init__(self, db_driver=None, image_service=None):
|
||||
pass
|
||||
|
||||
def show(self, context, image_id):
|
||||
return {'size': 2 * 1024 * 1024 * 1024}
|
||||
|
||||
image_id = '70a599e0-31e7-49b7-b260-868f441e862b'
|
||||
|
||||
try:
|
||||
volume_id = None
|
||||
volume_api = nova.volume.api.API(
|
||||
image_service=_FakeImageService())
|
||||
volume = volume_api.create(self.context, 2, 'name', 'description',
|
||||
image_id=1)
|
||||
volume_id = volume['id']
|
||||
self.assertEqual(volume['status'], 'creating')
|
||||
|
||||
finally:
|
||||
# cleanup
|
||||
db.volume_destroy(self.context, volume_id)
|
||||
|
||||
def test_create_volume_from_oversized_image(self):
|
||||
"""Verify that an image which is too big will fail correctly."""
|
||||
class _FakeImageService:
|
||||
def __init__(self, db_driver=None, image_service=None):
|
||||
pass
|
||||
|
||||
def show(self, context, image_id):
|
||||
return {'size': 2 * 1024 * 1024 * 1024 + 1}
|
||||
|
||||
image_id = '70a599e0-31e7-49b7-b260-868f441e862b'
|
||||
|
||||
volume_api = nova.volume.api.API(image_service=_FakeImageService())
|
||||
|
||||
self.assertRaises(exception.InvalidInput,
|
||||
volume_api.create,
|
||||
self.context, 2,
|
||||
'name', 'description', image_id=1)
|
||||
|
||||
|
||||
class DriverTestCase(test.TestCase):
|
||||
"""Base Test class for Drivers."""
|
||||
driver_name = "nova.volume.driver.FakeBaseDriver"
|
||||
|
||||
def setUp(self):
|
||||
super(DriverTestCase, self).setUp()
|
||||
vol_tmpdir = tempfile.mkdtemp()
|
||||
self.flags(volume_driver=self.driver_name,
|
||||
volumes_dir=vol_tmpdir)
|
||||
self.volume = importutils.import_object(FLAGS.volume_manager)
|
||||
self.context = context.get_admin_context()
|
||||
self.output = ""
|
||||
self.stubs.Set(iscsi.TgtAdm, '_get_target', self.fake_get_target)
|
||||
|
||||
def _fake_execute(_command, *_args, **_kwargs):
|
||||
"""Fake _execute."""
|
||||
return self.output, None
|
||||
self.volume.driver.set_execute(_fake_execute)
|
||||
|
||||
instance = db.instance_create(self.context, {})
|
||||
self.instance_id = instance['id']
|
||||
self.instance_uuid = instance['uuid']
|
||||
|
||||
def tearDown(self):
|
||||
try:
|
||||
shutil.rmtree(FLAGS.volumes_dir)
|
||||
except OSError:
|
||||
pass
|
||||
super(DriverTestCase, self).tearDown()
|
||||
|
||||
def fake_get_target(obj, iqn):
|
||||
return 1
|
||||
|
||||
def _attach_volume(self):
|
||||
"""Attach volumes to an instance."""
|
||||
return []
|
||||
|
||||
def _detach_volume(self, volume_id_list):
|
||||
"""Detach volumes from an instance."""
|
||||
for volume_id in volume_id_list:
|
||||
db.volume_detached(self.context, volume_id)
|
||||
self.volume.delete_volume(self.context, volume_id)
|
||||
|
||||
|
||||
class VolumeDriverTestCase(DriverTestCase):
|
||||
"""Test case for VolumeDriver"""
|
||||
driver_name = "nova.volume.driver.VolumeDriver"
|
||||
|
||||
def test_delete_busy_volume(self):
|
||||
"""Test deleting a busy volume."""
|
||||
self.stubs.Set(self.volume.driver, '_volume_not_present',
|
||||
lambda x: False)
|
||||
self.stubs.Set(self.volume.driver, '_delete_volume',
|
||||
lambda x, y: False)
|
||||
# Want DriverTestCase._fake_execute to return 'o' so that
|
||||
# volume.driver.delete_volume() raises the VolumeIsBusy exception.
|
||||
self.output = 'o'
|
||||
self.assertRaises(exception.VolumeIsBusy,
|
||||
self.volume.driver.delete_volume,
|
||||
{'name': 'test1', 'size': 1024})
|
||||
# when DriverTestCase._fake_execute returns something other than
|
||||
# 'o' volume.driver.delete_volume() does not raise an exception.
|
||||
self.output = 'x'
|
||||
self.volume.driver.delete_volume({'name': 'test1', 'size': 1024})
|
||||
|
||||
|
||||
class ISCSITestCase(DriverTestCase):
|
||||
"""Test Case for ISCSIDriver"""
|
||||
driver_name = "nova.volume.driver.ISCSIDriver"
|
||||
|
||||
def _attach_volume(self):
|
||||
"""Attach volumes to an instance. """
|
||||
volume_id_list = []
|
||||
for index in xrange(3):
|
||||
vol = {}
|
||||
vol['size'] = 0
|
||||
vol_ref = db.volume_create(self.context, vol)
|
||||
self.volume.create_volume(self.context, vol_ref['id'])
|
||||
vol_ref = db.volume_get(self.context, vol_ref['id'])
|
||||
|
||||
# each volume has a different mountpoint
|
||||
mountpoint = "/dev/sd" + chr((ord('b') + index))
|
||||
db.volume_attached(self.context, vol_ref['id'], self.instance_uuid,
|
||||
mountpoint)
|
||||
volume_id_list.append(vol_ref['id'])
|
||||
|
||||
return volume_id_list
|
||||
|
||||
def test_check_for_export_with_no_volume(self):
|
||||
self.volume.check_for_export(self.context, self.instance_id)
|
||||
|
||||
|
||||
class VolumePolicyTestCase(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(VolumePolicyTestCase, self).setUp()
|
||||
|
||||
nova.policy.reset()
|
||||
nova.policy.init()
|
||||
|
||||
self.context = context.get_admin_context()
|
||||
|
||||
def tearDown(self):
|
||||
super(VolumePolicyTestCase, self).tearDown()
|
||||
nova.policy.reset()
|
||||
|
||||
def _set_rules(self, rules):
|
||||
nova.common.policy.set_brain(nova.common.policy.HttpBrain(rules))
|
||||
|
||||
def test_check_policy(self):
|
||||
self.mox.StubOutWithMock(nova.policy, 'enforce')
|
||||
target = {
|
||||
'project_id': self.context.project_id,
|
||||
'user_id': self.context.user_id,
|
||||
}
|
||||
nova.policy.enforce(self.context, 'volume:attach', target)
|
||||
self.mox.ReplayAll()
|
||||
nova.volume.api.check_policy(self.context, 'attach')
|
||||
|
||||
def test_check_policy_with_target(self):
|
||||
self.mox.StubOutWithMock(nova.policy, 'enforce')
|
||||
target = {
|
||||
'project_id': self.context.project_id,
|
||||
'user_id': self.context.user_id,
|
||||
'id': 2,
|
||||
}
|
||||
nova.policy.enforce(self.context, 'volume:attach', target)
|
||||
self.mox.ReplayAll()
|
||||
nova.volume.api.check_policy(self.context, 'attach', {'id': 2})
|
|
@ -1,167 +0,0 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright (c) 2011 Zadara Storage Inc.
|
||||
# Copyright (c) 2011 OpenStack LLC.
|
||||
# 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 volume types code
|
||||
"""
|
||||
import time
|
||||
|
||||
from nova import context
|
||||
from nova.db.sqlalchemy import models
|
||||
from nova.db.sqlalchemy import session as sql_session
|
||||
from nova import exception
|
||||
from nova import flags
|
||||
from nova.openstack.common import log as logging
|
||||
from nova import test
|
||||
from nova.volume import volume_types
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class VolumeTypeTestCase(test.TestCase):
|
||||
"""Test cases for volume type code"""
|
||||
def setUp(self):
|
||||
super(VolumeTypeTestCase, self).setUp()
|
||||
|
||||
self.ctxt = context.get_admin_context()
|
||||
self.vol_type1_name = str(int(time.time()))
|
||||
self.vol_type1_specs = dict(
|
||||
type="physical drive",
|
||||
drive_type="SAS",
|
||||
size="300",
|
||||
rpm="7200",
|
||||
visible="True")
|
||||
|
||||
def test_volume_type_create_then_destroy(self):
|
||||
"""Ensure volume types can be created and deleted"""
|
||||
prev_all_vtypes = volume_types.get_all_types(self.ctxt)
|
||||
|
||||
volume_types.create(self.ctxt,
|
||||
self.vol_type1_name,
|
||||
self.vol_type1_specs)
|
||||
new = volume_types.get_volume_type_by_name(self.ctxt,
|
||||
self.vol_type1_name)
|
||||
|
||||
LOG.info(_("Given data: %s"), self.vol_type1_specs)
|
||||
LOG.info(_("Result data: %s"), new)
|
||||
|
||||
for k, v in self.vol_type1_specs.iteritems():
|
||||
self.assertEqual(v, new['extra_specs'][k],
|
||||
'one of fields does not match')
|
||||
|
||||
new_all_vtypes = volume_types.get_all_types(self.ctxt)
|
||||
self.assertEqual(len(prev_all_vtypes) + 1,
|
||||
len(new_all_vtypes),
|
||||
'drive type was not created')
|
||||
|
||||
volume_types.destroy(self.ctxt, self.vol_type1_name)
|
||||
new_all_vtypes = volume_types.get_all_types(self.ctxt)
|
||||
self.assertEqual(prev_all_vtypes,
|
||||
new_all_vtypes,
|
||||
'drive type was not deleted')
|
||||
|
||||
def test_get_all_volume_types(self):
|
||||
"""Ensures that all volume types can be retrieved"""
|
||||
session = sql_session.get_session()
|
||||
total_volume_types = session.query(models.VolumeTypes).count()
|
||||
vol_types = volume_types.get_all_types(self.ctxt)
|
||||
self.assertEqual(total_volume_types, len(vol_types))
|
||||
|
||||
def test_non_existent_vol_type_shouldnt_delete(self):
|
||||
"""Ensures that volume type creation fails with invalid args"""
|
||||
self.assertRaises(exception.VolumeTypeNotFoundByName,
|
||||
volume_types.destroy, self.ctxt, "sfsfsdfdfs")
|
||||
|
||||
def test_repeated_vol_types_shouldnt_raise(self):
|
||||
"""Ensures that volume duplicates don't raise"""
|
||||
new_name = self.vol_type1_name + "dup"
|
||||
volume_types.create(self.ctxt, new_name)
|
||||
volume_types.destroy(self.ctxt, new_name)
|
||||
volume_types.create(self.ctxt, new_name)
|
||||
|
||||
def test_invalid_volume_types_params(self):
|
||||
"""Ensures that volume type creation fails with invalid args"""
|
||||
self.assertRaises(exception.InvalidVolumeType,
|
||||
volume_types.destroy, self.ctxt, None)
|
||||
self.assertRaises(exception.InvalidVolumeType,
|
||||
volume_types.get_volume_type, self.ctxt, None)
|
||||
self.assertRaises(exception.InvalidVolumeType,
|
||||
volume_types.get_volume_type_by_name,
|
||||
self.ctxt, None)
|
||||
|
||||
def test_volume_type_get_by_id_and_name(self):
|
||||
"""Ensure volume types get returns same entry"""
|
||||
volume_types.create(self.ctxt,
|
||||
self.vol_type1_name,
|
||||
self.vol_type1_specs)
|
||||
new = volume_types.get_volume_type_by_name(self.ctxt,
|
||||
self.vol_type1_name)
|
||||
|
||||
new2 = volume_types.get_volume_type(self.ctxt, new['id'])
|
||||
self.assertEqual(new, new2)
|
||||
|
||||
def test_volume_type_search_by_extra_spec(self):
|
||||
"""Ensure volume types get by extra spec returns correct type"""
|
||||
volume_types.create(self.ctxt, "type1", {"key1": "val1",
|
||||
"key2": "val2"})
|
||||
volume_types.create(self.ctxt, "type2", {"key2": "val2",
|
||||
"key3": "val3"})
|
||||
volume_types.create(self.ctxt, "type3", {"key3": "another_value",
|
||||
"key4": "val4"})
|
||||
|
||||
vol_types = volume_types.get_all_types(self.ctxt,
|
||||
search_opts={'extra_specs': {"key1": "val1"}})
|
||||
LOG.info("vol_types: %s" % vol_types)
|
||||
self.assertEqual(len(vol_types), 1)
|
||||
self.assertTrue("type1" in vol_types.keys())
|
||||
self.assertEqual(vol_types['type1']['extra_specs'],
|
||||
{"key1": "val1", "key2": "val2"})
|
||||
|
||||
vol_types = volume_types.get_all_types(self.ctxt,
|
||||
search_opts={'extra_specs': {"key2": "val2"}})
|
||||
LOG.info("vol_types: %s" % vol_types)
|
||||
self.assertEqual(len(vol_types), 2)
|
||||
self.assertTrue("type1" in vol_types.keys())
|
||||
self.assertTrue("type2" in vol_types.keys())
|
||||
|
||||
vol_types = volume_types.get_all_types(self.ctxt,
|
||||
search_opts={'extra_specs': {"key3": "val3"}})
|
||||
LOG.info("vol_types: %s" % vol_types)
|
||||
self.assertEqual(len(vol_types), 1)
|
||||
self.assertTrue("type2" in vol_types.keys())
|
||||
|
||||
def test_volume_type_search_by_extra_spec_multiple(self):
|
||||
"""Ensure volume types get by extra spec returns correct type"""
|
||||
volume_types.create(self.ctxt, "type1", {"key1": "val1",
|
||||
"key2": "val2",
|
||||
"key3": "val3"})
|
||||
volume_types.create(self.ctxt, "type2", {"key2": "val2",
|
||||
"key3": "val3"})
|
||||
volume_types.create(self.ctxt, "type3", {"key1": "val1",
|
||||
"key3": "val3",
|
||||
"key4": "val4"})
|
||||
|
||||
vol_types = volume_types.get_all_types(self.ctxt,
|
||||
search_opts={'extra_specs': {"key1": "val1",
|
||||
"key3": "val3"}})
|
||||
LOG.info("vol_types: %s" % vol_types)
|
||||
self.assertEqual(len(vol_types), 2)
|
||||
self.assertTrue("type1" in vol_types.keys())
|
||||
self.assertTrue("type3" in vol_types.keys())
|
||||
self.assertEqual(vol_types['type1']['extra_specs'],
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"})
|
||||
self.assertEqual(vol_types['type3']['extra_specs'],
|
||||
{"key1": "val1", "key3": "val3", "key4": "val4"})
|
|
@ -1,130 +0,0 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright (c) 2011 Zadara Storage Inc.
|
||||
# Copyright (c) 2011 OpenStack LLC.
|
||||
# Copyright 2011 University of Southern California
|
||||
# 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 volume types extra specs code
|
||||
"""
|
||||
|
||||
from nova import context
|
||||
from nova import db
|
||||
from nova import test
|
||||
|
||||
|
||||
class VolumeTypeExtraSpecsTestCase(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(VolumeTypeExtraSpecsTestCase, self).setUp()
|
||||
self.context = context.get_admin_context()
|
||||
self.vol_type1 = dict(name="TEST: Regular volume test")
|
||||
self.vol_type1_specs = dict(vol_extra1="value1",
|
||||
vol_extra2="value2",
|
||||
vol_extra3=3)
|
||||
self.vol_type1['extra_specs'] = self.vol_type1_specs
|
||||
ref = db.volume_type_create(self.context, self.vol_type1)
|
||||
self.volume_type1_id = ref.id
|
||||
for k, v in self.vol_type1_specs.iteritems():
|
||||
self.vol_type1_specs[k] = str(v)
|
||||
|
||||
self.vol_type2_noextra = dict(name="TEST: Volume type without extra")
|
||||
ref = db.volume_type_create(self.context, self.vol_type2_noextra)
|
||||
self.vol_type2_id = ref.id
|
||||
|
||||
def tearDown(self):
|
||||
# Remove the volume type from the database
|
||||
db.volume_type_destroy(context.get_admin_context(),
|
||||
self.vol_type1['name'])
|
||||
db.volume_type_destroy(context.get_admin_context(),
|
||||
self.vol_type2_noextra['name'])
|
||||
super(VolumeTypeExtraSpecsTestCase, self).tearDown()
|
||||
|
||||
def test_volume_type_specs_get(self):
|
||||
expected_specs = self.vol_type1_specs.copy()
|
||||
actual_specs = db.volume_type_extra_specs_get(
|
||||
context.get_admin_context(),
|
||||
self.volume_type1_id)
|
||||
self.assertEquals(expected_specs, actual_specs)
|
||||
|
||||
def test_volume_type_extra_specs_delete(self):
|
||||
expected_specs = self.vol_type1_specs.copy()
|
||||
del expected_specs['vol_extra2']
|
||||
db.volume_type_extra_specs_delete(context.get_admin_context(),
|
||||
self.volume_type1_id,
|
||||
'vol_extra2')
|
||||
actual_specs = db.volume_type_extra_specs_get(
|
||||
context.get_admin_context(),
|
||||
self.volume_type1_id)
|
||||
self.assertEquals(expected_specs, actual_specs)
|
||||
|
||||
def test_volume_type_extra_specs_update(self):
|
||||
expected_specs = self.vol_type1_specs.copy()
|
||||
expected_specs['vol_extra3'] = "4"
|
||||
db.volume_type_extra_specs_update_or_create(
|
||||
context.get_admin_context(),
|
||||
self.volume_type1_id,
|
||||
dict(vol_extra3=4))
|
||||
actual_specs = db.volume_type_extra_specs_get(
|
||||
context.get_admin_context(),
|
||||
self.volume_type1_id)
|
||||
self.assertEquals(expected_specs, actual_specs)
|
||||
|
||||
def test_volume_type_extra_specs_create(self):
|
||||
expected_specs = self.vol_type1_specs.copy()
|
||||
expected_specs['vol_extra4'] = 'value4'
|
||||
expected_specs['vol_extra5'] = 'value5'
|
||||
db.volume_type_extra_specs_update_or_create(
|
||||
context.get_admin_context(),
|
||||
self.volume_type1_id,
|
||||
dict(vol_extra4="value4",
|
||||
vol_extra5="value5"))
|
||||
actual_specs = db.volume_type_extra_specs_get(
|
||||
context.get_admin_context(),
|
||||
self.volume_type1_id)
|
||||
self.assertEquals(expected_specs, actual_specs)
|
||||
|
||||
def test_volume_type_get_with_extra_specs(self):
|
||||
volume_type = db.volume_type_get(
|
||||
context.get_admin_context(),
|
||||
self.volume_type1_id)
|
||||
self.assertEquals(volume_type['extra_specs'],
|
||||
self.vol_type1_specs)
|
||||
|
||||
volume_type = db.volume_type_get(
|
||||
context.get_admin_context(),
|
||||
self.vol_type2_id)
|
||||
self.assertEquals(volume_type['extra_specs'], {})
|
||||
|
||||
def test_volume_type_get_by_name_with_extra_specs(self):
|
||||
volume_type = db.volume_type_get_by_name(
|
||||
context.get_admin_context(),
|
||||
self.vol_type1['name'])
|
||||
self.assertEquals(volume_type['extra_specs'],
|
||||
self.vol_type1_specs)
|
||||
|
||||
volume_type = db.volume_type_get_by_name(
|
||||
context.get_admin_context(),
|
||||
self.vol_type2_noextra['name'])
|
||||
self.assertEquals(volume_type['extra_specs'], {})
|
||||
|
||||
def test_volume_type_get_all(self):
|
||||
expected_specs = self.vol_type1_specs.copy()
|
||||
|
||||
types = db.volume_type_get_all(context.get_admin_context())
|
||||
|
||||
self.assertEquals(
|
||||
types[self.vol_type1['name']]['extra_specs'], expected_specs)
|
||||
|
||||
self.assertEquals(
|
||||
types[self.vol_type2_noextra['name']]['extra_specs'], {})
|
|
@ -1,91 +0,0 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2011 OpenStack LLC.
|
||||
# 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 miscellaneous util methods used with volume."""
|
||||
|
||||
from nova import context
|
||||
from nova import db
|
||||
from nova import flags
|
||||
from nova.openstack.common import importutils
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.openstack.common.notifier import api as notifier_api
|
||||
from nova.openstack.common.notifier import test_notifier
|
||||
from nova import test
|
||||
from nova.tests import fake_network
|
||||
from nova.volume import utils as volume_utils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
FLAGS = flags.FLAGS
|
||||
|
||||
|
||||
class UsageInfoTestCase(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(UsageInfoTestCase, self).setUp()
|
||||
self.flags(compute_driver='nova.virt.fake.FakeDriver',
|
||||
host='fake',
|
||||
notification_driver=[test_notifier.__name__])
|
||||
fake_network.set_stub_network_methods(self.stubs)
|
||||
|
||||
self.volume = importutils.import_object(FLAGS.volume_manager)
|
||||
self.user_id = 'fake'
|
||||
self.project_id = 'fake'
|
||||
self.snapshot_id = 'fake'
|
||||
self.volume_size = 0
|
||||
self.context = context.RequestContext(self.user_id, self.project_id)
|
||||
test_notifier.NOTIFICATIONS = []
|
||||
|
||||
def tearDown(self):
|
||||
notifier_api._reset_drivers()
|
||||
super(UsageInfoTestCase, self).tearDown()
|
||||
|
||||
def _create_volume(self, params={}):
|
||||
"""Create a test volume"""
|
||||
vol = {}
|
||||
vol['snapshot_id'] = self.snapshot_id
|
||||
vol['user_id'] = self.user_id
|
||||
vol['project_id'] = self.project_id
|
||||
vol['host'] = FLAGS.host
|
||||
vol['availability_zone'] = FLAGS.storage_availability_zone
|
||||
vol['status'] = "creating"
|
||||
vol['attach_status'] = "detached"
|
||||
vol['size'] = self.volume_size
|
||||
vol.update(params)
|
||||
return db.volume_create(self.context, vol)['id']
|
||||
|
||||
def test_notify_usage_exists(self):
|
||||
"""Ensure 'exists' notification generates appropriate usage data."""
|
||||
volume_id = self._create_volume()
|
||||
volume = db.volume_get(self.context, volume_id)
|
||||
volume_utils.notify_usage_exists(self.context, volume)
|
||||
self.assertEquals(len(test_notifier.NOTIFICATIONS), 1)
|
||||
msg = test_notifier.NOTIFICATIONS[0]
|
||||
self.assertEquals(msg['priority'], 'INFO')
|
||||
self.assertEquals(msg['event_type'], 'volume.exists')
|
||||
payload = msg['payload']
|
||||
self.assertEquals(payload['tenant_id'], self.project_id)
|
||||
self.assertEquals(payload['user_id'], self.user_id)
|
||||
self.assertEquals(payload['snapshot_id'], self.snapshot_id)
|
||||
self.assertEquals(payload['volume_id'], volume.id)
|
||||
self.assertEquals(payload['size'], self.volume_size)
|
||||
for attr in ('display_name', 'created_at', 'launched_at',
|
||||
'status', 'audit_period_beginning',
|
||||
'audit_period_ending'):
|
||||
self.assertTrue(attr in payload,
|
||||
msg="Key %s not in payload" % attr)
|
||||
db.volume_destroy(context.get_admin_context(), volume['id'])
|
|
@ -170,7 +170,7 @@ class XenAPIVolumeTestCase(stubs.XenAPITestBase):
|
|||
vol['user_id'] = 'fake'
|
||||
vol['project_id'] = 'fake'
|
||||
vol['host'] = 'localhost'
|
||||
vol['availability_zone'] = FLAGS.storage_availability_zone
|
||||
vol['availability_zone'] = FLAGS.node_availability_zone
|
||||
vol['status'] = "creating"
|
||||
vol['attach_status'] = "detached"
|
||||
return db.volume_create(self.context, vol)
|
||||
|
|
|
@ -1,140 +0,0 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright (c) 2010 Citrix Systems, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Test suite for Xen Storage Manager Volume Driver."""
|
||||
|
||||
|
||||
from nova import context
|
||||
from nova import db
|
||||
from nova import exception
|
||||
from nova import flags
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.tests.xenapi import stubs
|
||||
from nova.virt.xenapi import fake as xenapi_fake
|
||||
from nova.volume import xensm
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
|
||||
|
||||
class XenSMTestCase(stubs.XenAPITestBase):
|
||||
"""Unit tests for Xen Storage Manager Volume operations."""
|
||||
|
||||
def _get_sm_backend_params(self):
|
||||
config_params = ("name_label=testsmbackend "
|
||||
"server=localhost "
|
||||
"serverpath=/tmp/nfspath")
|
||||
params = dict(flavor_id=1,
|
||||
sr_uuid=None,
|
||||
sr_type='nfs',
|
||||
config_params=config_params)
|
||||
return params
|
||||
|
||||
def setUp(self):
|
||||
super(XenSMTestCase, self).setUp()
|
||||
self.user_id = 'fake'
|
||||
self.project_id = 'fake'
|
||||
self.context = context.RequestContext(self.user_id, self.project_id)
|
||||
self.flags(compute_driver='xenapi.XenAPIDriver',
|
||||
xenapi_connection_url='http://test_url',
|
||||
xenapi_connection_username='test_user',
|
||||
xenapi_connection_password='test_pass')
|
||||
stubs.stubout_session(self.stubs, xenapi_fake.SessionBase)
|
||||
xenapi_fake.reset()
|
||||
self.driver = xensm.XenSMDriver()
|
||||
self.driver.db = db
|
||||
|
||||
def _setup_step(self, ctxt):
|
||||
# Create a fake backend conf
|
||||
params = self._get_sm_backend_params()
|
||||
beconf = db.sm_backend_conf_create(ctxt,
|
||||
params)
|
||||
# Call setup, the one time operation that will create a backend SR
|
||||
self.driver.do_setup(ctxt)
|
||||
return beconf
|
||||
|
||||
def test_do_setup(self):
|
||||
ctxt = context.get_admin_context()
|
||||
beconf = self._setup_step(ctxt)
|
||||
beconf = db.sm_backend_conf_get(ctxt, beconf['id'])
|
||||
self.assertIsInstance(beconf['sr_uuid'], basestring)
|
||||
|
||||
def _create_volume(self, size=0):
|
||||
"""Create a volume object."""
|
||||
vol = {}
|
||||
vol['size'] = size
|
||||
vol['user_id'] = 'fake'
|
||||
vol['project_id'] = 'fake'
|
||||
vol['host'] = 'localhost'
|
||||
vol['availability_zone'] = FLAGS.storage_availability_zone
|
||||
vol['status'] = "creating"
|
||||
vol['attach_status'] = "detached"
|
||||
return db.volume_create(self.context, vol)
|
||||
|
||||
def test_create_volume(self):
|
||||
ctxt = context.get_admin_context()
|
||||
beconf = self._setup_step(ctxt)
|
||||
volume = self._create_volume()
|
||||
self.driver.create_volume(volume)
|
||||
db.sm_volume_get(ctxt, volume['id'])
|
||||
|
||||
def test_local_path(self):
|
||||
ctxt = context.get_admin_context()
|
||||
volume = self._create_volume()
|
||||
val = self.driver.local_path(volume)
|
||||
self.assertIsInstance(val, basestring)
|
||||
|
||||
def test_delete_volume(self):
|
||||
ctxt = context.get_admin_context()
|
||||
beconf = self._setup_step(ctxt)
|
||||
volume = self._create_volume()
|
||||
self.driver.create_volume(volume)
|
||||
self.driver.delete_volume(volume)
|
||||
self.assertRaises(exception.NotFound,
|
||||
db.sm_volume_get,
|
||||
ctxt,
|
||||
volume['id'])
|
||||
|
||||
def test_delete_volume_raises_notfound(self):
|
||||
ctxt = context.get_admin_context()
|
||||
beconf = self._setup_step(ctxt)
|
||||
self.assertRaises(exception.NotFound,
|
||||
self.driver.delete_volume,
|
||||
{'id': "FA15E-1D"})
|
||||
|
||||
def _get_expected_conn(self, beconf, vol):
|
||||
expected = {}
|
||||
expected['volume_id'] = unicode(vol['id'])
|
||||
expected['flavor_id'] = beconf['flavor_id']
|
||||
expected['sr_uuid'] = unicode(beconf['sr_uuid'])
|
||||
expected['sr_type'] = unicode(beconf['sr_type'])
|
||||
return expected
|
||||
|
||||
def test_initialize_connection(self):
|
||||
ctxt = context.get_admin_context()
|
||||
beconf = self._setup_step(ctxt)
|
||||
beconf = db.sm_backend_conf_get(ctxt, beconf['id'])
|
||||
volume = self._create_volume()
|
||||
self.driver.create_volume(volume)
|
||||
expected = self._get_expected_conn(beconf, volume)
|
||||
conn = self.driver.initialize_connection(volume, 'fakeConnector')
|
||||
res = {}
|
||||
for key in ['volume_id', 'flavor_id', 'sr_uuid', 'sr_type']:
|
||||
res[key] = conn['data'][key]
|
||||
self.assertDictMatch(expected, res)
|
||||
self.assertEqual(set(conn['data']['introduce_sr_keys']),
|
||||
set([u'sr_type', u'server', u'serverpath']))
|
3
setup.py
3
setup.py
|
@ -49,7 +49,6 @@ setuptools.setup(name='nova',
|
|||
'bin/nova-api-ec2',
|
||||
'bin/nova-api-metadata',
|
||||
'bin/nova-api-os-compute',
|
||||
'bin/nova-api-os-volume',
|
||||
'bin/nova-rpc-zmq-receiver',
|
||||
'bin/nova-cert',
|
||||
'bin/nova-clear-rabbit-queues',
|
||||
|
@ -63,8 +62,6 @@ setuptools.setup(name='nova',
|
|||
'bin/nova-objectstore',
|
||||
'bin/nova-rootwrap',
|
||||
'bin/nova-scheduler',
|
||||
'bin/nova-volume',
|
||||
'bin/nova-volume-usage-audit',
|
||||
'bin/nova-xvpvncproxy',
|
||||
],
|
||||
py_modules=[])
|
||||
|
|
Loading…
Reference in New Issue