Port volumes extension to work in v2.1/v3 framework
Ports v2 volumes extension and adapts it to the v2.1/v3 API framework. API behaviour is identical with the exception that there is no support for XML. Also - unittest code modified to share testing with both v2/v2.1 where appropriate - Adds expected error decorators for API methods Note that there will be further code cleanup in the future but the code currently is mostly as-is from the v2 codebase. Partially implements blueprint v2-on-v3-api Change-Id: If2a9dd1f5233812a1177b54ded6f0ae115c54e97
This commit is contained in:
parent
10a6c6cb0b
commit
115a78d452
doc/v3/api_samples/os-volumes
os-volumes-detail-resp.jsonos-volumes-get-resp.jsonos-volumes-index-resp.jsonos-volumes-post-req.jsonos-volumes-post-resp.jsonserver-post-req.jsonserver-post-resp.jsonsnapshot-create-req.jsonsnapshot-create-resp.jsonsnapshots-detail-resp.jsonsnapshots-list-resp.jsonsnapshots-show-resp.json
etc/nova
nova
api/openstack/compute/plugins/v3
tests
api/openstack/compute/contrib
fake_policy.pyintegrated/v3
api_samples/os-volumes
os-volumes-detail-resp.json.tplos-volumes-get-resp.json.tplos-volumes-index-resp.json.tplos-volumes-post-req.json.tplos-volumes-post-resp.json.tplserver-post-req.json.tplserver-post-resp.json.tplsnapshot-create-req.json.tplsnapshot-create-resp.json.tplsnapshots-detail-resp.json.tplsnapshots-list-resp.json.tplsnapshots-show-resp.json.tpl
test_volumes.py
24
doc/v3/api_samples/os-volumes/os-volumes-detail-resp.json
Normal file
24
doc/v3/api_samples/os-volumes/os-volumes-detail-resp.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"volumes": [
|
||||
{
|
||||
"attachments": [
|
||||
{
|
||||
"device": "/",
|
||||
"id": "a26887c6-c47b-4654-abb5-dfadf7d3f803",
|
||||
"serverId": "3912f2b4-c5ba-4aec-9165-872876fe202e",
|
||||
"volumeId": "a26887c6-c47b-4654-abb5-dfadf7d3f803"
|
||||
}
|
||||
],
|
||||
"availabilityZone": "zone1:host1",
|
||||
"createdAt": "1999-01-01T01:01:01.000000",
|
||||
"displayDescription": "Volume Description",
|
||||
"displayName": "Volume Name",
|
||||
"id": "a26887c6-c47b-4654-abb5-dfadf7d3f803",
|
||||
"metadata": {},
|
||||
"size": 100,
|
||||
"snapshotId": null,
|
||||
"status": "in-use",
|
||||
"volumeType": "Backup"
|
||||
}
|
||||
]
|
||||
}
|
22
doc/v3/api_samples/os-volumes/os-volumes-get-resp.json
Normal file
22
doc/v3/api_samples/os-volumes/os-volumes-get-resp.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"volume": {
|
||||
"attachments": [
|
||||
{
|
||||
"device": "/",
|
||||
"id": "a26887c6-c47b-4654-abb5-dfadf7d3f803",
|
||||
"serverId": "3912f2b4-c5ba-4aec-9165-872876fe202e",
|
||||
"volumeId": "a26887c6-c47b-4654-abb5-dfadf7d3f803"
|
||||
}
|
||||
],
|
||||
"availabilityZone": "zone1:host1",
|
||||
"createdAt": "2013-02-18T14:51:18.528085",
|
||||
"displayDescription": "Volume Description",
|
||||
"displayName": "Volume Name",
|
||||
"id": "a26887c6-c47b-4654-abb5-dfadf7d3f803",
|
||||
"metadata": {},
|
||||
"size": 100,
|
||||
"snapshotId": null,
|
||||
"status": "in-use",
|
||||
"volumeType": "Backup"
|
||||
}
|
||||
}
|
24
doc/v3/api_samples/os-volumes/os-volumes-index-resp.json
Normal file
24
doc/v3/api_samples/os-volumes/os-volumes-index-resp.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"volumes": [
|
||||
{
|
||||
"attachments": [
|
||||
{
|
||||
"device": "/",
|
||||
"id": "a26887c6-c47b-4654-abb5-dfadf7d3f803",
|
||||
"serverId": "3912f2b4-c5ba-4aec-9165-872876fe202e",
|
||||
"volumeId": "a26887c6-c47b-4654-abb5-dfadf7d3f803"
|
||||
}
|
||||
],
|
||||
"availabilityZone": "zone1:host1",
|
||||
"createdAt": "2013-02-19T20:01:40.274897",
|
||||
"displayDescription": "Volume Description",
|
||||
"displayName": "Volume Name",
|
||||
"id": "a26887c6-c47b-4654-abb5-dfadf7d3f803",
|
||||
"metadata": {},
|
||||
"size": 100,
|
||||
"snapshotId": null,
|
||||
"status": "in-use",
|
||||
"volumeType": "Backup"
|
||||
}
|
||||
]
|
||||
}
|
8
doc/v3/api_samples/os-volumes/os-volumes-post-req.json
Normal file
8
doc/v3/api_samples/os-volumes/os-volumes-post-req.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"volume": {
|
||||
"availability_zone": "zone1:host1",
|
||||
"display_name": "Volume Name",
|
||||
"display_description": "Volume Description",
|
||||
"size": 100
|
||||
}
|
||||
}
|
22
doc/v3/api_samples/os-volumes/os-volumes-post-resp.json
Normal file
22
doc/v3/api_samples/os-volumes/os-volumes-post-resp.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"volume": {
|
||||
"attachments": [
|
||||
{
|
||||
"device": "/",
|
||||
"id": "a26887c6-c47b-4654-abb5-dfadf7d3f803",
|
||||
"serverId": "3912f2b4-c5ba-4aec-9165-872876fe202e",
|
||||
"volumeId": "a26887c6-c47b-4654-abb5-dfadf7d3f803"
|
||||
}
|
||||
],
|
||||
"availabilityZone": "zone1:host1",
|
||||
"createdAt": "2013-02-18T14:51:17.970024",
|
||||
"displayDescription": "Volume Description",
|
||||
"displayName": "Volume Name",
|
||||
"id": "a26887c6-c47b-4654-abb5-dfadf7d3f803",
|
||||
"metadata": {},
|
||||
"size": 100,
|
||||
"snapshotId": null,
|
||||
"status": "in-use",
|
||||
"volumeType": "Backup"
|
||||
}
|
||||
}
|
16
doc/v3/api_samples/os-volumes/server-post-req.json
Normal file
16
doc/v3/api_samples/os-volumes/server-post-req.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"server": {
|
||||
"name": "new-server-test",
|
||||
"imageRef": "http://openstack.example.com/openstack/images/70a599e0-31e7-49b7-b260-868f441e862b",
|
||||
"flavorRef": "http://openstack.example.com/openstack/flavors/1",
|
||||
"metadata": {
|
||||
"My Server Name": "Apache1"
|
||||
},
|
||||
"personality": [
|
||||
{
|
||||
"path": "/etc/banner.txt",
|
||||
"contents": "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBpdCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5kIGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVsc2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4gQnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRoZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlvdSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vyc2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6b25zLiINCg0KLVJpY2hhcmQgQmFjaA=="
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
16
doc/v3/api_samples/os-volumes/server-post-resp.json
Normal file
16
doc/v3/api_samples/os-volumes/server-post-resp.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"server": {
|
||||
"adminPass": "8VqALQcVB9MT",
|
||||
"id": "a80b9477-84c1-4242-9731-14a3c2a04241",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://openstack.example.com/v3/servers/a80b9477-84c1-4242-9731-14a3c2a04241",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://openstack.example.com/servers/a80b9477-84c1-4242-9731-14a3c2a04241",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
8
doc/v3/api_samples/os-volumes/snapshot-create-req.json
Normal file
8
doc/v3/api_samples/os-volumes/snapshot-create-req.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"snapshot": {
|
||||
"display_name": "snap-001",
|
||||
"display_description": "Daily backup",
|
||||
"volume_id": "521752a6-acf6-4b2d-bc7a-119f9148cd8c",
|
||||
"force": false
|
||||
}
|
||||
}
|
11
doc/v3/api_samples/os-volumes/snapshot-create-resp.json
Normal file
11
doc/v3/api_samples/os-volumes/snapshot-create-resp.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"snapshot": {
|
||||
"createdAt": "2013-02-25T16:27:54.680544",
|
||||
"displayDescription": "Daily backup",
|
||||
"displayName": "snap-001",
|
||||
"id": 100,
|
||||
"size": 100,
|
||||
"status": "available",
|
||||
"volumeId": "521752a6-acf6-4b2d-bc7a-119f9148cd8c"
|
||||
}
|
||||
}
|
31
doc/v3/api_samples/os-volumes/snapshots-detail-resp.json
Normal file
31
doc/v3/api_samples/os-volumes/snapshots-detail-resp.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"snapshots": [
|
||||
{
|
||||
"createdAt": "2013-02-25T16:27:54.671372",
|
||||
"displayDescription": "Default description",
|
||||
"displayName": "Default name",
|
||||
"id": 100,
|
||||
"size": 100,
|
||||
"status": "available",
|
||||
"volumeId": 12
|
||||
},
|
||||
{
|
||||
"createdAt": "2013-02-25T16:27:54.671378",
|
||||
"displayDescription": "Default description",
|
||||
"displayName": "Default name",
|
||||
"id": 101,
|
||||
"size": 100,
|
||||
"status": "available",
|
||||
"volumeId": 12
|
||||
},
|
||||
{
|
||||
"createdAt": "2013-02-25T16:27:54.671381",
|
||||
"displayDescription": "Default description",
|
||||
"displayName": "Default name",
|
||||
"id": 102,
|
||||
"size": 100,
|
||||
"status": "available",
|
||||
"volumeId": 12
|
||||
}
|
||||
]
|
||||
}
|
31
doc/v3/api_samples/os-volumes/snapshots-list-resp.json
Normal file
31
doc/v3/api_samples/os-volumes/snapshots-list-resp.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"snapshots": [
|
||||
{
|
||||
"createdAt": "2013-02-25T16:27:54.684999",
|
||||
"displayDescription": "Default description",
|
||||
"displayName": "Default name",
|
||||
"id": 100,
|
||||
"size": 100,
|
||||
"status": "available",
|
||||
"volumeId": 12
|
||||
},
|
||||
{
|
||||
"createdAt": "2013-02-25T16:27:54.685005",
|
||||
"displayDescription": "Default description",
|
||||
"displayName": "Default name",
|
||||
"id": 101,
|
||||
"size": 100,
|
||||
"status": "available",
|
||||
"volumeId": 12
|
||||
},
|
||||
{
|
||||
"createdAt": "2013-02-25T16:27:54.685008",
|
||||
"displayDescription": "Default description",
|
||||
"displayName": "Default name",
|
||||
"id": 102,
|
||||
"size": 100,
|
||||
"status": "available",
|
||||
"volumeId": 12
|
||||
}
|
||||
]
|
||||
}
|
11
doc/v3/api_samples/os-volumes/snapshots-show-resp.json
Normal file
11
doc/v3/api_samples/os-volumes/snapshots-show-resp.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"snapshot": {
|
||||
"createdAt": "2013-02-25T16:27:54.724209",
|
||||
"displayDescription": "Default description",
|
||||
"displayName": "Default name",
|
||||
"id": "100",
|
||||
"size": 100,
|
||||
"status": "available",
|
||||
"volumeId": 12
|
||||
}
|
||||
}
|
@ -261,6 +261,8 @@
|
||||
"compute_extension:volume_attachments:create": "",
|
||||
"compute_extension:volume_attachments:update": "",
|
||||
"compute_extension:volume_attachments:delete": "",
|
||||
"compute_extension:v3:os-volumes": "",
|
||||
"compute_extension:v3:os-volumes:discoverable": "",
|
||||
"compute_extension:volumetypes": "",
|
||||
"compute_extension:availability_zone:list": "",
|
||||
"compute_extension:v3:os-availability-zone:list": "",
|
||||
|
356
nova/api/openstack/compute/plugins/v3/volumes.py
Normal file
356
nova/api/openstack/compute/plugins/v3/volumes.py
Normal file
@ -0,0 +1,356 @@
|
||||
# Copyright 2011 Justin Santa Barbara
|
||||
# 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.
|
||||
|
||||
"""The volumes extension."""
|
||||
|
||||
import webob
|
||||
from webob import exc
|
||||
|
||||
from nova.api.openstack import common
|
||||
from nova.api.openstack import extensions
|
||||
from nova.api.openstack import wsgi
|
||||
from nova import exception
|
||||
from nova.i18n import _
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.openstack.common import strutils
|
||||
from nova import volume
|
||||
|
||||
ALIAS = "os-volumes"
|
||||
LOG = logging.getLogger(__name__)
|
||||
authorize = extensions.extension_authorizer('compute', 'v3:' + ALIAS)
|
||||
|
||||
|
||||
def _translate_volume_detail_view(context, vol):
|
||||
"""Maps keys for volumes details view."""
|
||||
|
||||
d = _translate_volume_summary_view(context, vol)
|
||||
|
||||
# No additional data / lookups at the moment
|
||||
|
||||
return d
|
||||
|
||||
|
||||
def _translate_volume_summary_view(context, vol):
|
||||
"""Maps keys for volumes summary view."""
|
||||
d = {}
|
||||
|
||||
d['id'] = vol['id']
|
||||
d['status'] = vol['status']
|
||||
d['size'] = vol['size']
|
||||
d['availabilityZone'] = vol['availability_zone']
|
||||
d['createdAt'] = vol['created_at']
|
||||
|
||||
if vol['attach_status'] == 'attached':
|
||||
d['attachments'] = [_translate_attachment_detail_view(vol['id'],
|
||||
vol['instance_uuid'],
|
||||
vol['mountpoint'])]
|
||||
else:
|
||||
d['attachments'] = [{}]
|
||||
|
||||
d['displayName'] = vol['display_name']
|
||||
d['displayDescription'] = vol['display_description']
|
||||
|
||||
if vol['volume_type_id'] and vol.get('volume_type'):
|
||||
d['volumeType'] = vol['volume_type']['name']
|
||||
else:
|
||||
d['volumeType'] = vol['volume_type_id']
|
||||
|
||||
d['snapshotId'] = vol['snapshot_id']
|
||||
LOG.audit(_("vol=%s"), vol, context=context)
|
||||
|
||||
if vol.get('volume_metadata'):
|
||||
d['metadata'] = vol.get('volume_metadata')
|
||||
else:
|
||||
d['metadata'] = {}
|
||||
|
||||
return d
|
||||
|
||||
|
||||
class VolumeController(wsgi.Controller):
|
||||
"""The Volumes API controller for the OpenStack API."""
|
||||
|
||||
def __init__(self):
|
||||
self.volume_api = volume.API()
|
||||
super(VolumeController, self).__init__()
|
||||
|
||||
@extensions.expected_errors(404)
|
||||
def show(self, req, id):
|
||||
"""Return data about the given volume."""
|
||||
context = req.environ['nova.context']
|
||||
authorize(context)
|
||||
|
||||
try:
|
||||
vol = self.volume_api.get(context, id)
|
||||
except exception.NotFound as e:
|
||||
raise exc.HTTPNotFound(explanation=e.format_message())
|
||||
|
||||
return {'volume': _translate_volume_detail_view(context, vol)}
|
||||
|
||||
@extensions.expected_errors(404)
|
||||
def delete(self, req, id):
|
||||
"""Delete a volume."""
|
||||
context = req.environ['nova.context']
|
||||
authorize(context)
|
||||
|
||||
LOG.audit(_("Delete volume with id: %s"), id, context=context)
|
||||
|
||||
try:
|
||||
self.volume_api.delete(context, id)
|
||||
except exception.NotFound as e:
|
||||
raise exc.HTTPNotFound(explanation=e.format_message())
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@extensions.expected_errors(())
|
||||
def index(self, req):
|
||||
"""Returns a summary list of volumes."""
|
||||
return self._items(req, entity_maker=_translate_volume_summary_view)
|
||||
|
||||
@extensions.expected_errors(())
|
||||
def detail(self, req):
|
||||
"""Returns a detailed list of volumes."""
|
||||
return self._items(req, entity_maker=_translate_volume_detail_view)
|
||||
|
||||
def _items(self, req, entity_maker):
|
||||
"""Returns a list of volumes, transformed through entity_maker."""
|
||||
context = req.environ['nova.context']
|
||||
authorize(context)
|
||||
|
||||
volumes = self.volume_api.get_all(context)
|
||||
limited_list = common.limited(volumes, req)
|
||||
res = [entity_maker(context, vol) for vol in limited_list]
|
||||
return {'volumes': res}
|
||||
|
||||
@extensions.expected_errors(400)
|
||||
def create(self, req, body):
|
||||
"""Creates a new volume."""
|
||||
context = req.environ['nova.context']
|
||||
authorize(context)
|
||||
|
||||
if not self.is_valid_body(body, 'volume'):
|
||||
msg = _("volume not specified")
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
vol = body['volume']
|
||||
|
||||
vol_type = vol.get('volume_type')
|
||||
metadata = vol.get('metadata')
|
||||
snapshot_id = vol.get('snapshot_id')
|
||||
|
||||
if snapshot_id is not None:
|
||||
snapshot = self.volume_api.get_snapshot(context, snapshot_id)
|
||||
else:
|
||||
snapshot = None
|
||||
|
||||
size = vol.get('size', None)
|
||||
if size is None and snapshot is not None:
|
||||
size = snapshot['volume_size']
|
||||
|
||||
LOG.audit(_("Create volume of %s GB"), size, context=context)
|
||||
|
||||
availability_zone = vol.get('availability_zone')
|
||||
|
||||
try:
|
||||
new_volume = self.volume_api.create(
|
||||
context,
|
||||
size,
|
||||
vol.get('display_name'),
|
||||
vol.get('display_description'),
|
||||
snapshot=snapshot,
|
||||
volume_type=vol_type,
|
||||
metadata=metadata,
|
||||
availability_zone=availability_zone
|
||||
)
|
||||
except exception.InvalidInput as err:
|
||||
raise exc.HTTPBadRequest(explanation=err.format_message())
|
||||
|
||||
# TODO(vish): Instance should be None at db layer instead of
|
||||
# trying to lazy load, but for now we turn it into
|
||||
# a dict to avoid an error.
|
||||
retval = _translate_volume_detail_view(context, dict(new_volume))
|
||||
result = {'volume': retval}
|
||||
|
||||
location = '%s/%s' % (req.url, new_volume['id'])
|
||||
|
||||
return wsgi.ResponseObject(result, headers=dict(location=location))
|
||||
|
||||
|
||||
def _translate_attachment_detail_view(volume_id, instance_uuid, mountpoint):
|
||||
"""Maps keys for attachment details view."""
|
||||
|
||||
d = _translate_attachment_summary_view(volume_id,
|
||||
instance_uuid,
|
||||
mountpoint)
|
||||
|
||||
# No additional data / lookups at the moment
|
||||
return d
|
||||
|
||||
|
||||
def _translate_attachment_summary_view(volume_id, instance_uuid, mountpoint):
|
||||
"""Maps keys for attachment summary view."""
|
||||
d = {}
|
||||
|
||||
# NOTE(justinsb): We use the volume id as the id of the attachment object
|
||||
d['id'] = volume_id
|
||||
|
||||
d['volumeId'] = volume_id
|
||||
|
||||
d['serverId'] = instance_uuid
|
||||
if mountpoint:
|
||||
d['device'] = mountpoint
|
||||
|
||||
return d
|
||||
|
||||
|
||||
def _translate_snapshot_detail_view(context, vol):
|
||||
"""Maps keys for snapshots details view."""
|
||||
|
||||
d = _translate_snapshot_summary_view(context, vol)
|
||||
|
||||
# NOTE(gagupta): No additional data / lookups at the moment
|
||||
return d
|
||||
|
||||
|
||||
def _translate_snapshot_summary_view(context, vol):
|
||||
"""Maps keys for snapshots summary view."""
|
||||
d = {}
|
||||
|
||||
d['id'] = vol['id']
|
||||
d['volumeId'] = vol['volume_id']
|
||||
d['status'] = vol['status']
|
||||
# NOTE(gagupta): We map volume_size as the snapshot size
|
||||
d['size'] = vol['volume_size']
|
||||
d['createdAt'] = vol['created_at']
|
||||
d['displayName'] = vol['display_name']
|
||||
d['displayDescription'] = vol['display_description']
|
||||
return d
|
||||
|
||||
|
||||
class SnapshotController(wsgi.Controller):
|
||||
"""The Snapshots API controller for the OpenStack API."""
|
||||
|
||||
def __init__(self):
|
||||
self.volume_api = volume.API()
|
||||
super(SnapshotController, self).__init__()
|
||||
|
||||
@extensions.expected_errors(404)
|
||||
def show(self, req, id):
|
||||
"""Return data about the given snapshot."""
|
||||
context = req.environ['nova.context']
|
||||
authorize(context)
|
||||
|
||||
try:
|
||||
vol = self.volume_api.get_snapshot(context, id)
|
||||
except exception.NotFound as e:
|
||||
raise exc.HTTPNotFound(explanation=e.format_message())
|
||||
|
||||
return {'snapshot': _translate_snapshot_detail_view(context, vol)}
|
||||
|
||||
@extensions.expected_errors(404)
|
||||
def delete(self, req, id):
|
||||
"""Delete a snapshot."""
|
||||
context = req.environ['nova.context']
|
||||
authorize(context)
|
||||
|
||||
LOG.audit(_("Delete snapshot with id: %s"), id, context=context)
|
||||
|
||||
try:
|
||||
self.volume_api.delete_snapshot(context, id)
|
||||
except exception.NotFound as e:
|
||||
raise exc.HTTPNotFound(explanation=e.format_message())
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@extensions.expected_errors(())
|
||||
def index(self, req):
|
||||
"""Returns a summary list of snapshots."""
|
||||
return self._items(req, entity_maker=_translate_snapshot_summary_view)
|
||||
|
||||
@extensions.expected_errors(())
|
||||
def detail(self, req):
|
||||
"""Returns a detailed list of snapshots."""
|
||||
return self._items(req, entity_maker=_translate_snapshot_detail_view)
|
||||
|
||||
def _items(self, req, entity_maker):
|
||||
"""Returns a list of snapshots, transformed through entity_maker."""
|
||||
context = req.environ['nova.context']
|
||||
authorize(context)
|
||||
|
||||
snapshots = self.volume_api.get_all_snapshots(context)
|
||||
limited_list = common.limited(snapshots, req)
|
||||
res = [entity_maker(context, snapshot) for snapshot in limited_list]
|
||||
return {'snapshots': res}
|
||||
|
||||
@extensions.expected_errors(400)
|
||||
def create(self, req, body):
|
||||
"""Creates a new snapshot."""
|
||||
context = req.environ['nova.context']
|
||||
authorize(context)
|
||||
|
||||
if not self.is_valid_body(body, 'snapshot'):
|
||||
msg = _("snapshot not specified")
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
snapshot = body['snapshot']
|
||||
volume_id = snapshot['volume_id']
|
||||
|
||||
LOG.audit(_("Create snapshot from volume %s"), volume_id,
|
||||
context=context)
|
||||
|
||||
force = snapshot.get('force', False)
|
||||
try:
|
||||
force = strutils.bool_from_string(force, strict=True)
|
||||
except ValueError:
|
||||
msg = _("Invalid value '%s' for force.") % force
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
if force:
|
||||
create_func = self.volume_api.create_snapshot_force
|
||||
else:
|
||||
create_func = self.volume_api.create_snapshot
|
||||
|
||||
new_snapshot = create_func(context, volume_id,
|
||||
snapshot.get('display_name'),
|
||||
snapshot.get('display_description'))
|
||||
|
||||
retval = _translate_snapshot_detail_view(context, new_snapshot)
|
||||
return {'snapshot': retval}
|
||||
|
||||
|
||||
class Volumes(extensions.V3APIExtensionBase):
|
||||
"""Volumes support."""
|
||||
|
||||
name = "Volumes"
|
||||
alias = ALIAS
|
||||
version = 1
|
||||
|
||||
def get_resources(self):
|
||||
resources = []
|
||||
|
||||
res = extensions.ResourceExtension(
|
||||
ALIAS, VolumeController(), collection_actions={'detail': 'GET'})
|
||||
resources.append(res)
|
||||
|
||||
res = extensions.ResourceExtension('os-volumes_boot',
|
||||
inherits='servers')
|
||||
resources.append(res)
|
||||
|
||||
res = extensions.ResourceExtension(
|
||||
'os-snapshots', SnapshotController(),
|
||||
collection_actions={'detail': 'GET'})
|
||||
resources.append(res)
|
||||
|
||||
return resources
|
||||
|
||||
def get_controller_extensions(self):
|
||||
return []
|
@ -25,6 +25,7 @@ from webob import exc
|
||||
from nova.api.openstack.compute.contrib import assisted_volume_snapshots as \
|
||||
assisted_snaps
|
||||
from nova.api.openstack.compute.contrib import volumes
|
||||
from nova.api.openstack.compute.plugins.v3 import volumes as volumes_v3
|
||||
from nova.api.openstack import extensions
|
||||
from nova.compute import api as compute_api
|
||||
from nova.compute import flavors
|
||||
@ -217,9 +218,11 @@ class BootFromVolumeTest(test.TestCase):
|
||||
'/dev/vda')
|
||||
|
||||
|
||||
class VolumeApiTest(test.TestCase):
|
||||
class VolumeApiTestV21(test.TestCase):
|
||||
url_prefix = '/v3'
|
||||
|
||||
def setUp(self):
|
||||
super(VolumeApiTest, self).setUp()
|
||||
super(VolumeApiTestV21, self).setUp()
|
||||
fakes.stub_out_networking(self.stubs)
|
||||
fakes.stub_out_rate_limiting(self.stubs)
|
||||
|
||||
@ -232,7 +235,10 @@ class VolumeApiTest(test.TestCase):
|
||||
osapi_compute_ext_list=['Volumes'])
|
||||
|
||||
self.context = context.get_admin_context()
|
||||
self.app = fakes.wsgi_app(init_only=('os-volumes',))
|
||||
self.app = self._get_app()
|
||||
|
||||
def _get_app(self):
|
||||
return fakes.wsgi_app_v3()
|
||||
|
||||
def test_volume_create(self):
|
||||
self.stubs.Set(cinder.API, "create", fakes.stub_volume_create)
|
||||
@ -242,7 +248,7 @@ class VolumeApiTest(test.TestCase):
|
||||
"display_description": "Volume Test Desc",
|
||||
"availability_zone": "zone1:host1"}
|
||||
body = {"volume": vol}
|
||||
req = webob.Request.blank('/v2/fake/os-volumes')
|
||||
req = webob.Request.blank(self.url_prefix + '/os-volumes')
|
||||
req.method = 'POST'
|
||||
req.body = jsonutils.dumps(body)
|
||||
req.headers['content-type'] = 'application/json'
|
||||
@ -274,35 +280,35 @@ class VolumeApiTest(test.TestCase):
|
||||
"availability_zone": "zone1:host1"}
|
||||
body = {"volume": vol}
|
||||
|
||||
req = fakes.HTTPRequest.blank('/v2/fake/os-volumes')
|
||||
req = fakes.HTTPRequest.blank(self.url_prefix + '/os-volumes')
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
volumes.VolumeController().create, req, body)
|
||||
|
||||
def test_volume_index(self):
|
||||
req = webob.Request.blank('/v2/fake/os-volumes')
|
||||
req = webob.Request.blank(self.url_prefix + '/os-volumes')
|
||||
resp = req.get_response(self.app)
|
||||
self.assertEqual(resp.status_int, 200)
|
||||
|
||||
def test_volume_detail(self):
|
||||
req = webob.Request.blank('/v2/fake/os-volumes/detail')
|
||||
req = webob.Request.blank(self.url_prefix + '/os-volumes/detail')
|
||||
resp = req.get_response(self.app)
|
||||
self.assertEqual(resp.status_int, 200)
|
||||
|
||||
def test_volume_show(self):
|
||||
req = webob.Request.blank('/v2/fake/os-volumes/123')
|
||||
req = webob.Request.blank(self.url_prefix + '/os-volumes/123')
|
||||
resp = req.get_response(self.app)
|
||||
self.assertEqual(resp.status_int, 200)
|
||||
|
||||
def test_volume_show_no_volume(self):
|
||||
self.stubs.Set(cinder.API, "get", fakes.stub_volume_notfound)
|
||||
|
||||
req = webob.Request.blank('/v2/fake/os-volumes/456')
|
||||
req = webob.Request.blank(self.url_prefix + '/os-volumes/456')
|
||||
resp = req.get_response(self.app)
|
||||
self.assertEqual(resp.status_int, 404)
|
||||
self.assertIn('Volume 456 could not be found.', resp.body)
|
||||
|
||||
def test_volume_delete(self):
|
||||
req = webob.Request.blank('/v2/fake/os-volumes/123')
|
||||
req = webob.Request.blank(self.url_prefix + '/os-volumes/123')
|
||||
req.method = 'DELETE'
|
||||
resp = req.get_response(self.app)
|
||||
self.assertEqual(resp.status_int, 202)
|
||||
@ -310,13 +316,30 @@ class VolumeApiTest(test.TestCase):
|
||||
def test_volume_delete_no_volume(self):
|
||||
self.stubs.Set(cinder.API, "delete", fakes.stub_volume_notfound)
|
||||
|
||||
req = webob.Request.blank('/v2/fake/os-volumes/456')
|
||||
req = webob.Request.blank(self.url_prefix + '/os-volumes/456')
|
||||
req.method = 'DELETE'
|
||||
resp = req.get_response(self.app)
|
||||
self.assertEqual(resp.status_int, 404)
|
||||
self.assertIn('Volume 456 could not be found.', resp.body)
|
||||
|
||||
|
||||
class VolumeApiTestV2(VolumeApiTestV21):
|
||||
url_prefix = '/v2/fake'
|
||||
|
||||
def setUp(self):
|
||||
super(VolumeApiTestV2, self).setUp()
|
||||
self.flags(
|
||||
osapi_compute_extension=[
|
||||
'nova.api.openstack.compute.contrib.select_extensions'],
|
||||
osapi_compute_ext_list=['Volumes'])
|
||||
|
||||
self.context = context.get_admin_context()
|
||||
self.app = self._get_app()
|
||||
|
||||
def _get_app(self):
|
||||
return fakes.wsgi_app()
|
||||
|
||||
|
||||
class VolumeAttachTests(test.TestCase):
|
||||
def setUp(self):
|
||||
super(VolumeAttachTests, self).setUp()
|
||||
@ -879,24 +902,27 @@ class CommonBadRequestTestCase(object):
|
||||
self._bad_request_create(body=body)
|
||||
|
||||
|
||||
class BadRequestVolumeTestCase(CommonBadRequestTestCase,
|
||||
test.TestCase):
|
||||
class BadRequestVolumeTestCaseV21(CommonBadRequestTestCase,
|
||||
test.TestCase):
|
||||
|
||||
resource = 'os-volumes'
|
||||
entity_name = 'volume'
|
||||
controller_cls = volumes_v3.VolumeController
|
||||
|
||||
|
||||
class BadRequestVolumeTestCaseV2(BadRequestVolumeTestCaseV21):
|
||||
controller_cls = volumes.VolumeController
|
||||
|
||||
|
||||
class BadRequestAttachmentTestCase(CommonBadRequestTestCase,
|
||||
test.TestCase):
|
||||
|
||||
resource = 'servers/' + FAKE_UUID + '/os-volume_attachments'
|
||||
entity_name = 'volumeAttachment'
|
||||
controller_cls = volumes.VolumeAttachmentController
|
||||
kwargs = {'server_id': FAKE_UUID}
|
||||
|
||||
|
||||
class BadRequestSnapshotTestCase(CommonBadRequestTestCase,
|
||||
class BadRequestSnapshotTestCaseV21(CommonBadRequestTestCase,
|
||||
test.TestCase):
|
||||
|
||||
resource = 'os-snapshots'
|
||||
@ -904,10 +930,16 @@ class BadRequestSnapshotTestCase(CommonBadRequestTestCase,
|
||||
controller_cls = volumes.SnapshotController
|
||||
|
||||
|
||||
class ShowSnapshotTestCase(test.TestCase):
|
||||
class BadRequestSnapshotTestCaseV2(BadRequestSnapshotTestCaseV21):
|
||||
controller_cls = volumes_v3.SnapshotController
|
||||
|
||||
|
||||
class ShowSnapshotTestCaseV21(test.TestCase):
|
||||
snapshot_cls = volumes_v3.SnapshotController
|
||||
|
||||
def setUp(self):
|
||||
super(ShowSnapshotTestCase, self).setUp()
|
||||
self.controller = volumes.SnapshotController()
|
||||
super(ShowSnapshotTestCaseV21, self).setUp()
|
||||
self.controller = self.snapshot_cls()
|
||||
self.req = fakes.HTTPRequest.blank('/v2/fake/os-snapshots')
|
||||
self.req.method = 'GET'
|
||||
|
||||
@ -919,10 +951,16 @@ class ShowSnapshotTestCase(test.TestCase):
|
||||
self.controller.show, self.req, FAKE_UUID_A)
|
||||
|
||||
|
||||
class CreateSnapshotTestCase(test.TestCase):
|
||||
class ShowSnapshotTestCaseV2(ShowSnapshotTestCaseV21):
|
||||
snapshot_cls = volumes.SnapshotController
|
||||
|
||||
|
||||
class CreateSnapshotTestCaseV21(test.TestCase):
|
||||
snapshot_cls = volumes_v3.SnapshotController
|
||||
|
||||
def setUp(self):
|
||||
super(CreateSnapshotTestCase, self).setUp()
|
||||
self.controller = volumes.SnapshotController()
|
||||
super(CreateSnapshotTestCaseV21, self).setUp()
|
||||
self.controller = self.snapshot_cls()
|
||||
self.stubs.Set(cinder.API, 'get', fake_get_volume)
|
||||
self.stubs.Set(cinder.API, 'create_snapshot_force',
|
||||
fake_create_snapshot)
|
||||
@ -945,10 +983,16 @@ class CreateSnapshotTestCase(test.TestCase):
|
||||
self.controller.create, self.req, body=self.body)
|
||||
|
||||
|
||||
class DeleteSnapshotTestCase(test.TestCase):
|
||||
class CreateSnapshotTestCaseV2(CreateSnapshotTestCaseV21):
|
||||
snapshot_cls = volumes.SnapshotController
|
||||
|
||||
|
||||
class DeleteSnapshotTestCaseV21(test.TestCase):
|
||||
snapshot_cls = volumes_v3.SnapshotController
|
||||
|
||||
def setUp(self):
|
||||
super(DeleteSnapshotTestCase, self).setUp()
|
||||
self.controller = volumes.SnapshotController()
|
||||
super(DeleteSnapshotTestCaseV21, self).setUp()
|
||||
self.controller = self.snapshot_cls()
|
||||
self.stubs.Set(cinder.API, 'get', fake_get_volume)
|
||||
self.stubs.Set(cinder.API, 'create_snapshot_force',
|
||||
fake_create_snapshot)
|
||||
@ -980,6 +1024,10 @@ class DeleteSnapshotTestCase(test.TestCase):
|
||||
self.req, result['snapshot']['id'])
|
||||
|
||||
|
||||
class DeleteSnapshotTestCaseV2(DeleteSnapshotTestCaseV21):
|
||||
snapshot_cls = volumes.SnapshotController
|
||||
|
||||
|
||||
class AssistedSnapshotCreateTestCase(test.TestCase):
|
||||
def setUp(self):
|
||||
super(AssistedSnapshotCreateTestCase, self).setUp()
|
||||
|
@ -297,6 +297,7 @@ policy_data = """
|
||||
"compute_extension:volume_attachments:create": "",
|
||||
"compute_extension:volume_attachments:update": "",
|
||||
"compute_extension:volume_attachments:delete": "",
|
||||
"compute_extension:v3:os-volumes": "",
|
||||
"compute_extension:volumetypes": "",
|
||||
"compute_extension:zones": "",
|
||||
"compute_extension:availability_zone:list": "",
|
||||
|
@ -0,0 +1,24 @@
|
||||
{
|
||||
"volumes": [
|
||||
{
|
||||
"attachments": [
|
||||
{
|
||||
"device": "/",
|
||||
"id": "%(uuid)s",
|
||||
"serverId": "%(uuid)s",
|
||||
"volumeId": "%(uuid)s"
|
||||
}
|
||||
],
|
||||
"availabilityZone": "zone1:host1",
|
||||
"createdAt": "%(strtime)s",
|
||||
"displayDescription": "%(volume_desc)s",
|
||||
"displayName": "%(volume_name)s",
|
||||
"id": "%(uuid)s",
|
||||
"metadata": {},
|
||||
"size": 100,
|
||||
"snapshotId": null,
|
||||
"status": "in-use",
|
||||
"volumeType": "Backup"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
{
|
||||
"volume": {
|
||||
"attachments": [
|
||||
{
|
||||
"device": "/",
|
||||
"id": "%(uuid)s",
|
||||
"serverId": "%(uuid)s",
|
||||
"volumeId": "%(uuid)s"
|
||||
}
|
||||
],
|
||||
"availabilityZone": "zone1:host1",
|
||||
"createdAt": "%(strtime)s",
|
||||
"displayDescription": "%(volume_desc)s",
|
||||
"displayName": "%(volume_name)s",
|
||||
"id": "%(uuid)s",
|
||||
"metadata": {},
|
||||
"size": 100,
|
||||
"snapshotId": null,
|
||||
"status": "in-use",
|
||||
"volumeType": "Backup"
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
{
|
||||
"volumes": [
|
||||
{
|
||||
"attachments": [
|
||||
{
|
||||
"device": "/",
|
||||
"id": "%(uuid)s",
|
||||
"serverId": "%(uuid)s",
|
||||
"volumeId": "%(uuid)s"
|
||||
}
|
||||
],
|
||||
"availabilityZone": "zone1:host1",
|
||||
"createdAt": "%(strtime)s",
|
||||
"displayDescription": "%(volume_desc)s",
|
||||
"displayName": "%(volume_name)s",
|
||||
"id": "%(uuid)s",
|
||||
"metadata": {},
|
||||
"size": 100,
|
||||
"snapshotId": null,
|
||||
"status": "in-use",
|
||||
"volumeType": "Backup"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
{
|
||||
"volume": {
|
||||
"availability_zone": "zone1:host1",
|
||||
"display_name": "%(volume_name)s",
|
||||
"display_description": "%(volume_desc)s",
|
||||
"size": 100
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
{
|
||||
"volume": {
|
||||
"status": "in-use",
|
||||
"displayDescription": "%(volume_desc)s",
|
||||
"availabilityZone": "zone1:host1",
|
||||
"displayName": "%(volume_name)s",
|
||||
"attachments": [
|
||||
{ "device": "/",
|
||||
"serverId": "%(uuid)s",
|
||||
"id": "%(uuid)s",
|
||||
"volumeId": "%(uuid)s"
|
||||
}
|
||||
],
|
||||
"volumeType": "Backup",
|
||||
"snapshotId": null,
|
||||
"metadata": {},
|
||||
"id": "%(uuid)s",
|
||||
"createdAt": "%(strtime)s",
|
||||
"size": 100
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
{
|
||||
"server": {
|
||||
"name": "new-server-test",
|
||||
"imageRef": "%(host)s/openstack/images/%(image_id)s",
|
||||
"flavorRef": "%(host)s/openstack/flavors/1",
|
||||
"metadata": {
|
||||
"My Server Name": "Apache1"
|
||||
},
|
||||
"personality": [
|
||||
{
|
||||
"path": "/etc/banner.txt",
|
||||
"contents": "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBpdCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5kIGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVsc2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4gQnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRoZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlvdSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vyc2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6b25zLiINCg0KLVJpY2hhcmQgQmFjaA=="
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
{
|
||||
"server": {
|
||||
"adminPass": "%(password)s",
|
||||
"id": "%(id)s",
|
||||
"links": [
|
||||
{
|
||||
"href": "%(host)s/v3/servers/%(uuid)s",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "%(host)s/servers/%(uuid)s",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
{
|
||||
"snapshot": {
|
||||
"display_name": "%(snapshot_name)s",
|
||||
"display_description": "%(description)s",
|
||||
"volume_id": "%(volume_id)s",
|
||||
"force": false
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
{
|
||||
"snapshot": {
|
||||
"createdAt": "%(strtime)s",
|
||||
"displayDescription": "%(description)s",
|
||||
"displayName": "%(snapshot_name)s",
|
||||
"id": 100,
|
||||
"size": 100,
|
||||
"status": "available",
|
||||
"volumeId": "%(uuid)s"
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
{
|
||||
"snapshots": [
|
||||
{
|
||||
"createdAt": "%(strtime)s",
|
||||
"displayDescription": "Default description",
|
||||
"displayName": "Default name",
|
||||
"id": 100,
|
||||
"size": 100,
|
||||
"status": "available",
|
||||
"volumeId": 12
|
||||
},
|
||||
{
|
||||
"createdAt": "%(strtime)s",
|
||||
"displayDescription": "Default description",
|
||||
"displayName": "Default name",
|
||||
"id": 101,
|
||||
"size": 100,
|
||||
"status": "available",
|
||||
"volumeId": 12
|
||||
},
|
||||
{
|
||||
"createdAt": "%(strtime)s",
|
||||
"displayDescription": "Default description",
|
||||
"displayName": "Default name",
|
||||
"id": 102,
|
||||
"size": 100,
|
||||
"status": "available",
|
||||
"volumeId": 12
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
{
|
||||
"snapshots": [
|
||||
{
|
||||
"createdAt": "%(strtime)s",
|
||||
"displayDescription": "%(text)s",
|
||||
"displayName": "%(text)s",
|
||||
"id": 100,
|
||||
"size": 100,
|
||||
"status": "available",
|
||||
"volumeId": 12
|
||||
},
|
||||
{
|
||||
"createdAt": "%(strtime)s",
|
||||
"displayDescription": "%(text)s",
|
||||
"displayName": "%(text)s",
|
||||
"id": 101,
|
||||
"size": 100,
|
||||
"status": "available",
|
||||
"volumeId": 12
|
||||
},
|
||||
{
|
||||
"createdAt": "%(strtime)s",
|
||||
"displayDescription": "%(text)s",
|
||||
"displayName": "%(text)s",
|
||||
"id": 102,
|
||||
"size": 100,
|
||||
"status": "available",
|
||||
"volumeId": 12
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
{
|
||||
"snapshot": {
|
||||
"createdAt": "%(strtime)s",
|
||||
"displayDescription": "%(description)s",
|
||||
"displayName": "%(snapshot_name)s",
|
||||
"id": "100",
|
||||
"size": 100,
|
||||
"status": "available",
|
||||
"volumeId": 12
|
||||
}
|
||||
}
|
184
nova/tests/integrated/v3/test_volumes.py
Normal file
184
nova/tests/integrated/v3/test_volumes.py
Normal file
@ -0,0 +1,184 @@
|
||||
# Copyright 2012 Nebula, Inc.
|
||||
# Copyright 2014 IBM Corp.
|
||||
#
|
||||
# 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 datetime
|
||||
|
||||
from nova.tests.api.openstack import fakes
|
||||
from nova.tests.integrated.v3 import api_sample_base
|
||||
from nova.tests.integrated.v3 import test_servers
|
||||
from nova.volume import cinder
|
||||
|
||||
|
||||
class SnapshotsSampleJsonTests(api_sample_base.ApiSampleTestBaseV3):
|
||||
extension_name = "os-volumes"
|
||||
|
||||
create_subs = {
|
||||
'snapshot_name': 'snap-001',
|
||||
'description': 'Daily backup',
|
||||
'volume_id': '521752a6-acf6-4b2d-bc7a-119f9148cd8c'
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
super(SnapshotsSampleJsonTests, self).setUp()
|
||||
self.stubs.Set(cinder.API, "get_all_snapshots",
|
||||
fakes.stub_snapshot_get_all)
|
||||
self.stubs.Set(cinder.API, "get_snapshot", fakes.stub_snapshot_get)
|
||||
|
||||
def _create_snapshot(self):
|
||||
self.stubs.Set(cinder.API, "create_snapshot",
|
||||
fakes.stub_snapshot_create)
|
||||
|
||||
response = self._do_post("os-snapshots",
|
||||
"snapshot-create-req",
|
||||
self.create_subs)
|
||||
return response
|
||||
|
||||
def test_snapshots_create(self):
|
||||
response = self._create_snapshot()
|
||||
self.create_subs.update(self._get_regexes())
|
||||
self._verify_response("snapshot-create-resp",
|
||||
self.create_subs, response, 200)
|
||||
|
||||
def test_snapshots_delete(self):
|
||||
self.stubs.Set(cinder.API, "delete_snapshot",
|
||||
fakes.stub_snapshot_delete)
|
||||
self._create_snapshot()
|
||||
response = self._do_delete('os-snapshots/100')
|
||||
self.assertEqual(response.status, 202)
|
||||
self.assertEqual(response.read(), '')
|
||||
|
||||
def test_snapshots_detail(self):
|
||||
response = self._do_get('os-snapshots/detail')
|
||||
subs = self._get_regexes()
|
||||
self._verify_response('snapshots-detail-resp', subs, response, 200)
|
||||
|
||||
def test_snapshots_list(self):
|
||||
response = self._do_get('os-snapshots')
|
||||
subs = self._get_regexes()
|
||||
self._verify_response('snapshots-list-resp', subs, response, 200)
|
||||
|
||||
def test_snapshots_show(self):
|
||||
response = self._do_get('os-snapshots/100')
|
||||
subs = {
|
||||
'snapshot_name': 'Default name',
|
||||
'description': 'Default description'
|
||||
}
|
||||
subs.update(self._get_regexes())
|
||||
self._verify_response('snapshots-show-resp', subs, response, 200)
|
||||
|
||||
|
||||
class VolumesSampleJsonTest(test_servers.ServersSampleBase):
|
||||
extension_name = "os-volumes"
|
||||
|
||||
def _get_volume_id(self):
|
||||
return 'a26887c6-c47b-4654-abb5-dfadf7d3f803'
|
||||
|
||||
def _stub_volume(self, id, displayname="Volume Name",
|
||||
displaydesc="Volume Description", size=100):
|
||||
volume = {
|
||||
'id': id,
|
||||
'size': size,
|
||||
'availability_zone': 'zone1:host1',
|
||||
'instance_uuid': '3912f2b4-c5ba-4aec-9165-872876fe202e',
|
||||
'mountpoint': '/',
|
||||
'status': 'in-use',
|
||||
'attach_status': 'attached',
|
||||
'name': 'vol name',
|
||||
'display_name': displayname,
|
||||
'display_description': displaydesc,
|
||||
'created_at': datetime.datetime(2008, 12, 1, 11, 1, 55),
|
||||
'snapshot_id': None,
|
||||
'volume_type_id': 'fakevoltype',
|
||||
'volume_metadata': [],
|
||||
'volume_type': {'name': 'Backup'}
|
||||
}
|
||||
return volume
|
||||
|
||||
def _stub_volume_get(self, context, volume_id):
|
||||
return self._stub_volume(volume_id)
|
||||
|
||||
def _stub_volume_delete(self, context, *args, **param):
|
||||
pass
|
||||
|
||||
def _stub_volume_get_all(self, context, search_opts=None):
|
||||
id = self._get_volume_id()
|
||||
return [self._stub_volume(id)]
|
||||
|
||||
def _stub_volume_create(self, context, size, name, description, snapshot,
|
||||
**param):
|
||||
id = self._get_volume_id()
|
||||
return self._stub_volume(id)
|
||||
|
||||
def setUp(self):
|
||||
super(VolumesSampleJsonTest, self).setUp()
|
||||
fakes.stub_out_networking(self.stubs)
|
||||
fakes.stub_out_rate_limiting(self.stubs)
|
||||
|
||||
self.stubs.Set(cinder.API, "delete", self._stub_volume_delete)
|
||||
self.stubs.Set(cinder.API, "get", self._stub_volume_get)
|
||||
self.stubs.Set(cinder.API, "get_all", self._stub_volume_get_all)
|
||||
|
||||
def _post_volume(self):
|
||||
subs_req = {
|
||||
'volume_name': "Volume Name",
|
||||
'volume_desc': "Volume Description",
|
||||
}
|
||||
|
||||
self.stubs.Set(cinder.API, "create", self._stub_volume_create)
|
||||
response = self._do_post('os-volumes', 'os-volumes-post-req',
|
||||
subs_req)
|
||||
subs = self._get_regexes()
|
||||
subs.update(subs_req)
|
||||
self._verify_response('os-volumes-post-resp', subs, response, 200)
|
||||
|
||||
def test_volumes_show(self):
|
||||
subs = {
|
||||
'volume_name': "Volume Name",
|
||||
'volume_desc': "Volume Description",
|
||||
}
|
||||
vol_id = self._get_volume_id()
|
||||
response = self._do_get('os-volumes/%s' % vol_id)
|
||||
subs.update(self._get_regexes())
|
||||
self._verify_response('os-volumes-get-resp', subs, response, 200)
|
||||
|
||||
def test_volumes_index(self):
|
||||
subs = {
|
||||
'volume_name': "Volume Name",
|
||||
'volume_desc': "Volume Description",
|
||||
}
|
||||
response = self._do_get('os-volumes')
|
||||
subs.update(self._get_regexes())
|
||||
self._verify_response('os-volumes-index-resp', subs, response, 200)
|
||||
|
||||
def test_volumes_detail(self):
|
||||
# For now, index and detail are the same.
|
||||
# See the volumes api
|
||||
subs = {
|
||||
'volume_name': "Volume Name",
|
||||
'volume_desc': "Volume Description",
|
||||
}
|
||||
response = self._do_get('os-volumes/detail')
|
||||
subs.update(self._get_regexes())
|
||||
self._verify_response('os-volumes-detail-resp', subs, response, 200)
|
||||
|
||||
def test_volumes_create(self):
|
||||
self._post_volume()
|
||||
|
||||
def test_volumes_delete(self):
|
||||
self._post_volume()
|
||||
vol_id = self._get_volume_id()
|
||||
response = self._do_delete('os-volumes/%s' % vol_id)
|
||||
self.assertEqual(response.status, 202)
|
||||
self.assertEqual(response.read(), '')
|
@ -114,6 +114,7 @@ nova.api.v3.extensions =
|
||||
simple_tenant_usage = nova.api.openstack.compute.plugins.v3.simple_tenant_usage:SimpleTenantUsage
|
||||
suspend_server = nova.api.openstack.compute.plugins.v3.suspend_server:SuspendServer
|
||||
versions = nova.api.openstack.compute.plugins.v3.versions:Versions
|
||||
volumes = nova.api.openstack.compute.plugins.v3.volumes:Volumes
|
||||
|
||||
nova.api.v3.extensions.server.create =
|
||||
access_ips = nova.api.openstack.compute.plugins.v3.access_ips:AccessIPs
|
||||
|
Loading…
x
Reference in New Issue
Block a user