Remove attach/detach/swap from V2.1 extended_volumes
V2.1 must be identical with V2 and must not contain any new attributes and new method. And those methods have same methods in the volumes extensions. Those methods are added in the early stage of v3 works, because the in early stage v3 works decides to delete any volume related proxy API, then adds those method instead of them. This commit remove these methods from V2.1 API. Partially implements blueprint v2-on-v3-api Change-Id: I20e0498ec187e9feafd2bfb1aa9646426b3609af
This commit is contained in:
parent
0476bed304
commit
7007ab20f6
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"attach": {
|
||||
"volume_id": "a26887c6-c47b-4654-abb5-dfadf7d3f803",
|
||||
"device": "/dev/vdd",
|
||||
"disk_bus": "ide",
|
||||
"device_type": "cdrom"
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"detach": {
|
||||
"volume_id": "a26887c6-c47b-4654-abb5-dfadf7d3f803"
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"swap_volume_attachment": {
|
||||
"old_volume_id": "a26887c6-c47b-4654-abb5-dfadf7d3f803",
|
||||
"new_volume_id": "a26887c6-c47b-4654-abb5-dfadf7d3f805"
|
||||
}
|
||||
}
|
|
@ -13,27 +13,14 @@
|
|||
# under the License.
|
||||
|
||||
"""The Extended Volumes API extension."""
|
||||
from webob import exc
|
||||
|
||||
from nova.api.openstack import common
|
||||
from nova.api.openstack.compute.schemas.v3 import extended_volumes
|
||||
from nova.api.openstack import extensions
|
||||
from nova.api.openstack import wsgi
|
||||
from nova.api import validation
|
||||
from nova import compute
|
||||
from nova import exception
|
||||
from nova.i18n import _
|
||||
from nova import objects
|
||||
from nova import volume
|
||||
|
||||
ALIAS = "os-extended-volumes"
|
||||
authorize = extensions.soft_extension_authorizer('compute', 'v3:' + ALIAS)
|
||||
authorize_attach = extensions.extension_authorizer('compute',
|
||||
'v3:%s:attach' % ALIAS)
|
||||
authorize_detach = extensions.extension_authorizer('compute',
|
||||
'v3:%s:detach' % ALIAS)
|
||||
authorize_swap = extensions.extension_authorizer('compute',
|
||||
'v3:%s:swap' % ALIAS)
|
||||
|
||||
|
||||
class ExtendedVolumesController(wsgi.Controller):
|
||||
|
@ -49,55 +36,6 @@ class ExtendedVolumesController(wsgi.Controller):
|
|||
key = "%s:volumes_attached" % ExtendedVolumes.alias
|
||||
server[key] = [{'id': volume_id} for volume_id in volume_ids]
|
||||
|
||||
@wsgi.response(202)
|
||||
@extensions.expected_errors((400, 404, 409))
|
||||
@wsgi.action('swap_volume_attachment')
|
||||
@validation.schema(extended_volumes.swap_volume_attachment)
|
||||
def swap(self, req, id, body):
|
||||
context = req.environ['nova.context']
|
||||
authorize_swap(context)
|
||||
|
||||
old_volume_id = body['swap_volume_attachment']['old_volume_id']
|
||||
new_volume_id = body['swap_volume_attachment']['new_volume_id']
|
||||
|
||||
try:
|
||||
old_volume = self.volume_api.get(context, old_volume_id)
|
||||
new_volume = self.volume_api.get(context, new_volume_id)
|
||||
except exception.VolumeNotFound as e:
|
||||
raise exc.HTTPNotFound(explanation=e.format_message())
|
||||
|
||||
instance = common.get_instance(self.compute_api, context, id,
|
||||
want_objects=True)
|
||||
bdms = objects.BlockDeviceMappingList.get_by_instance_uuid(
|
||||
context, instance.uuid)
|
||||
found = False
|
||||
try:
|
||||
for bdm in bdms:
|
||||
if bdm.volume_id != old_volume_id:
|
||||
continue
|
||||
try:
|
||||
self.compute_api.swap_volume(context, instance, old_volume,
|
||||
new_volume)
|
||||
found = True
|
||||
break
|
||||
except exception.VolumeUnattached:
|
||||
# The volume is not attached. Treat it as NotFound
|
||||
# by falling through.
|
||||
pass
|
||||
except exception.InvalidVolume as e:
|
||||
raise exc.HTTPBadRequest(explanation=e.format_message())
|
||||
except exception.InstanceIsLocked as e:
|
||||
raise exc.HTTPConflict(explanation=e.format_message())
|
||||
except exception.InstanceInvalidState as state_error:
|
||||
common.raise_http_conflict_for_instance_invalid_state(state_error,
|
||||
'swap_volume',
|
||||
id)
|
||||
|
||||
if not found:
|
||||
msg = _("The volume was either invalid or not attached to the "
|
||||
"instance.")
|
||||
raise exc.HTTPNotFound(explanation=msg)
|
||||
|
||||
@wsgi.extends
|
||||
def show(self, req, resp_obj, id):
|
||||
context = req.environ['nova.context']
|
||||
|
@ -119,91 +57,6 @@ class ExtendedVolumesController(wsgi.Controller):
|
|||
# the core API adding it in its 'detail' method.
|
||||
self._extend_server(context, server, db_instance)
|
||||
|
||||
@extensions.expected_errors((400, 404, 409))
|
||||
@wsgi.response(202)
|
||||
@wsgi.action('attach')
|
||||
@validation.schema(extended_volumes.attach)
|
||||
def attach(self, req, id, body):
|
||||
server_id = id
|
||||
context = req.environ['nova.context']
|
||||
authorize_attach(context)
|
||||
|
||||
volume_id = body['attach']['volume_id']
|
||||
device = body['attach'].get('device')
|
||||
disk_bus = body['attach'].get('disk_bus')
|
||||
device_type = body['attach'].get('device_type')
|
||||
|
||||
instance = common.get_instance(self.compute_api, context, server_id,
|
||||
want_objects=True)
|
||||
try:
|
||||
self.compute_api.attach_volume(context, instance,
|
||||
volume_id, device,
|
||||
disk_bus=disk_bus,
|
||||
device_type=device_type)
|
||||
except exception.VolumeNotFound as e:
|
||||
raise exc.HTTPNotFound(explanation=e.format_message())
|
||||
except exception.InstanceIsLocked as e:
|
||||
raise exc.HTTPConflict(explanation=e.format_message())
|
||||
except exception.InstanceInvalidState as state_error:
|
||||
common.raise_http_conflict_for_instance_invalid_state(
|
||||
state_error, 'attach_volume', server_id)
|
||||
except exception.InvalidVolume as e:
|
||||
raise exc.HTTPBadRequest(explanation=e.format_message())
|
||||
except exception.InvalidDevicePath as e:
|
||||
raise exc.HTTPBadRequest(explanation=e.format_message())
|
||||
|
||||
@extensions.expected_errors((400, 403, 404, 409))
|
||||
@wsgi.response(202)
|
||||
@wsgi.action('detach')
|
||||
@validation.schema(extended_volumes.detach)
|
||||
def detach(self, req, id, body):
|
||||
server_id = id
|
||||
context = req.environ['nova.context']
|
||||
authorize_detach(context)
|
||||
|
||||
volume_id = body['detach']['volume_id']
|
||||
|
||||
instance = common.get_instance(self.compute_api, context, server_id,
|
||||
want_objects=True)
|
||||
try:
|
||||
volume = self.volume_api.get(context, volume_id)
|
||||
except exception.VolumeNotFound as e:
|
||||
raise exc.HTTPNotFound(explanation=e.format_message())
|
||||
|
||||
bdms = objects.BlockDeviceMappingList.get_by_instance_uuid(
|
||||
context, instance.uuid)
|
||||
if not bdms:
|
||||
msg = _("Volume %(volume_id)s is not attached to the "
|
||||
"instance %(server_id)s") % {'server_id': server_id,
|
||||
'volume_id': volume_id}
|
||||
raise exc.HTTPNotFound(explanation=msg)
|
||||
|
||||
for bdm in bdms:
|
||||
if bdm.volume_id != volume_id:
|
||||
continue
|
||||
if bdm.is_root:
|
||||
msg = _("Can't detach root device volume")
|
||||
raise exc.HTTPForbidden(explanation=msg)
|
||||
try:
|
||||
self.compute_api.detach_volume(context, instance, volume)
|
||||
break
|
||||
except exception.VolumeUnattached:
|
||||
# The volume is not attached. Treat it as NotFound
|
||||
# by falling through.
|
||||
pass
|
||||
except exception.InvalidVolume as e:
|
||||
raise exc.HTTPBadRequest(explanation=e.format_message())
|
||||
except exception.InstanceIsLocked as e:
|
||||
raise exc.HTTPConflict(explanation=e.format_message())
|
||||
except exception.InstanceInvalidState as state_error:
|
||||
common.raise_http_conflict_for_instance_invalid_state(
|
||||
state_error, 'detach_volume', server_id)
|
||||
else:
|
||||
msg = _("Volume %(volume_id)s is not attached to the "
|
||||
"instance %(server_id)s") % {'server_id': server_id,
|
||||
'volume_id': volume_id}
|
||||
raise exc.HTTPNotFound(explanation=msg)
|
||||
|
||||
|
||||
class ExtendedVolumes(extensions.V3APIExtensionBase):
|
||||
"""Extended Volumes support."""
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
# Copyright 2013 NEC Corporation. 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.api.validation import parameter_types
|
||||
|
||||
|
||||
swap_volume_attachment = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'swap_volume_attachment': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'old_volume_id': parameter_types.volume_id,
|
||||
'new_volume_id': parameter_types.volume_id
|
||||
},
|
||||
'required': ['old_volume_id', 'new_volume_id'],
|
||||
'additionalProperties': False,
|
||||
},
|
||||
},
|
||||
'required': ['swap_volume_attachment'],
|
||||
'additionalProperties': False,
|
||||
}
|
||||
|
||||
|
||||
attach = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'attach': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'volume_id': parameter_types.volume_id,
|
||||
'device': {
|
||||
'type': 'string',
|
||||
# NOTE: The validation pattern from match_device() in
|
||||
# nova/block_device.py.
|
||||
'pattern': '(^/dev/x{0,1}[a-z]{0,1}d{0,1})([a-z]+)[0-9]*$'
|
||||
},
|
||||
'disk_bus': {
|
||||
'type': 'string'
|
||||
},
|
||||
'device_type': {
|
||||
'type': 'string',
|
||||
}
|
||||
},
|
||||
'required': ['volume_id'],
|
||||
'additionalProperties': False,
|
||||
},
|
||||
},
|
||||
'required': ['attach'],
|
||||
'additionalProperties': False,
|
||||
}
|
||||
|
||||
|
||||
detach = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'detach': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'volume_id': parameter_types.volume_id
|
||||
},
|
||||
'required': ['volume_id'],
|
||||
'additionalProperties': False,
|
||||
},
|
||||
},
|
||||
'required': ['detach'],
|
||||
'additionalProperties': False,
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"attach": {
|
||||
"volume_id": "%(volume_id)s",
|
||||
"device": "%(device)s",
|
||||
"disk_bus": "%(disk_bus)s",
|
||||
"device_type": "%(device_type)s"
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"detach": {
|
||||
"volume_id": "%(volume_id)s"
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"swap_volume_attachment": {
|
||||
"old_volume_id": "%(old_volume_id)s",
|
||||
"new_volume_id": "%(new_volume_id)s"
|
||||
}
|
||||
}
|
|
@ -14,15 +14,11 @@
|
|||
# under the License.
|
||||
|
||||
from nova.compute import api as compute_api
|
||||
from nova.compute import manager as compute_manager
|
||||
from nova import context
|
||||
from nova import db
|
||||
from nova import objects
|
||||
from nova.tests.functional.v3 import test_servers
|
||||
from nova.tests.unit.api.openstack import fakes
|
||||
from nova.tests.unit import fake_block_device
|
||||
from nova.tests.unit import fake_instance
|
||||
from nova.volume import cinder
|
||||
|
||||
|
||||
class ExtendedVolumesSampleJsonTests(test_servers.ServersSampleBase):
|
||||
|
@ -76,76 +72,3 @@ class ExtendedVolumesSampleJsonTests(test_servers.ServersSampleBase):
|
|||
subs['id'] = uuid
|
||||
subs['hostid'] = '[a-f0-9]+'
|
||||
self._verify_response('servers-detail-resp', subs, response, 200)
|
||||
|
||||
def test_attach_volume(self):
|
||||
bdm = objects.BlockDeviceMapping()
|
||||
device_name = '/dev/vdd'
|
||||
bdm['device_name'] = device_name
|
||||
self.stubs.Set(cinder.API, 'get', fakes.stub_volume_get)
|
||||
self.stubs.Set(cinder.API, 'check_attach', lambda *a, **k: None)
|
||||
self.stubs.Set(cinder.API, 'reserve_volume', lambda *a, **k: None)
|
||||
self.stubs.Set(compute_manager.ComputeManager,
|
||||
"reserve_block_device_name",
|
||||
lambda *a, **k: bdm)
|
||||
self.stubs.Set(compute_manager.ComputeManager,
|
||||
'attach_volume',
|
||||
lambda *a, **k: None)
|
||||
|
||||
volume = fakes.stub_volume_get(None, context.get_admin_context(),
|
||||
'a26887c6-c47b-4654-abb5-dfadf7d3f803')
|
||||
subs = {
|
||||
'volume_id': volume['id'],
|
||||
'device': device_name,
|
||||
'disk_bus': 'ide',
|
||||
'device_type': 'cdrom'
|
||||
}
|
||||
server_id = self._post_server()
|
||||
response = self._do_post('servers/%s/action'
|
||||
% server_id,
|
||||
'attach-volume-req', subs)
|
||||
self.assertEqual(response.status_code, 202)
|
||||
self.assertEqual(response.content, '')
|
||||
|
||||
def test_detach_volume(self):
|
||||
server_id = self._post_server()
|
||||
attach_id = "a26887c6-c47b-4654-abb5-dfadf7d3f803"
|
||||
self._stub_compute_api_get_instance_bdms(server_id)
|
||||
self._stub_compute_api_get()
|
||||
self.stubs.Set(cinder.API, 'get', fakes.stub_volume_get)
|
||||
self.stubs.Set(compute_api.API, 'detach_volume', lambda *a, **k: None)
|
||||
subs = {
|
||||
'volume_id': attach_id,
|
||||
}
|
||||
response = self._do_post('servers/%s/action'
|
||||
% server_id, 'detach-volume-req', subs)
|
||||
self.assertEqual(response.status_code, 202)
|
||||
self.assertEqual(response.content, '')
|
||||
|
||||
def test_swap_volume(self):
|
||||
server_id = self._post_server()
|
||||
old_volume_id = "a26887c6-c47b-4654-abb5-dfadf7d3f803"
|
||||
old_new_volume = 'a26887c6-c47b-4654-abb5-dfadf7d3f805'
|
||||
self._stub_compute_api_get_instance_bdms(server_id)
|
||||
|
||||
def stub_volume_get(self, context, volume_id):
|
||||
if volume_id == old_volume_id:
|
||||
return fakes.stub_volume(volume_id, instance_uuid=server_id)
|
||||
else:
|
||||
return fakes.stub_volume(volume_id, instance_uuid=None,
|
||||
attach_status='detached')
|
||||
|
||||
self.stubs.Set(cinder.API, 'get', stub_volume_get)
|
||||
self.stubs.Set(cinder.API, 'begin_detaching', lambda *a, **k: None)
|
||||
self.stubs.Set(cinder.API, 'check_attach', lambda *a, **k: None)
|
||||
self.stubs.Set(cinder.API, 'check_detach', lambda *a, **k: None)
|
||||
self.stubs.Set(cinder.API, 'reserve_volume', lambda *a, **k: None)
|
||||
self.stubs.Set(compute_manager.ComputeManager, 'swap_volume',
|
||||
lambda *a, **k: None)
|
||||
subs = {
|
||||
'old_volume_id': old_volume_id,
|
||||
'new_volume_id': old_new_volume
|
||||
}
|
||||
response = self._do_post('servers/%s/action' % server_id,
|
||||
'swap-volume-req', subs)
|
||||
self.assertEqual(response.status_code, 202)
|
||||
self.assertEqual(response.content, '')
|
||||
|
|
|
@ -13,16 +13,13 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
from oslo_serialization import jsonutils
|
||||
import webob
|
||||
|
||||
from nova.api.openstack.compute.plugins.v3 import (extended_volumes
|
||||
as extended_volumes_v21)
|
||||
from nova import compute
|
||||
from nova import context
|
||||
from nova import db
|
||||
from nova import exception
|
||||
from nova import objects
|
||||
from nova.objects import instance as instance_obj
|
||||
from nova import test
|
||||
|
@ -58,15 +55,6 @@ def fake_bdms_get_all_by_instance(*args, **kwargs):
|
|||
'destination_type': 'volume', 'id': 2})]
|
||||
|
||||
|
||||
def fake_attach_volume(self, context, instance, volume_id,
|
||||
device, disk_bus, device_type):
|
||||
pass
|
||||
|
||||
|
||||
def fake_detach_volume(self, context, instance, volume):
|
||||
pass
|
||||
|
||||
|
||||
def fake_volume_get(*args, **kwargs):
|
||||
pass
|
||||
|
||||
|
@ -95,8 +83,6 @@ class ExtendedVolumesTestV21(test.TestCase):
|
|||
def _setUp(self):
|
||||
self.Controller = extended_volumes_v21.ExtendedVolumesController()
|
||||
self.stubs.Set(volume.cinder.API, 'get', fake_volume_get)
|
||||
self.stubs.Set(compute.api.API, 'detach_volume', fake_detach_volume)
|
||||
self.stubs.Set(compute.api.API, 'attach_volume', fake_attach_volume)
|
||||
self.action_url = "/%s/action" % UUID1
|
||||
|
||||
def _make_request(self, url, body=None):
|
||||
|
@ -132,218 +118,6 @@ class ExtendedVolumesTestV21(test.TestCase):
|
|||
self.assertEqual(self.exp_volumes, actual)
|
||||
|
||||
|
||||
class ExtendedVolumesAdditionTestV21(ExtendedVolumesTestV21):
|
||||
|
||||
def test_show(self):
|
||||
pass
|
||||
|
||||
def test_detail(self):
|
||||
pass
|
||||
|
||||
def test_detach(self):
|
||||
res = self._make_request(self.action_url,
|
||||
{"detach": {"volume_id": UUID1}})
|
||||
self.assertEqual(202, res.status_int)
|
||||
|
||||
def test_detach_volume_from_locked_server(self):
|
||||
self.stubs.Set(compute.api.API, 'detach_volume',
|
||||
fakes.fake_actions_to_locked_server)
|
||||
res = self._make_request(self.action_url,
|
||||
{"detach": {"volume_id": UUID1}})
|
||||
self.assertEqual(409, res.status_int)
|
||||
|
||||
@mock.patch('nova.volume.cinder.API.get',
|
||||
side_effect=exception.VolumeNotFound(volume_id=UUID1))
|
||||
def test_detach_with_non_existed_vol(self, mock_detach):
|
||||
res = self._make_request(self.action_url,
|
||||
{"detach": {"volume_id": UUID2}})
|
||||
self.assertEqual(404, res.status_int)
|
||||
|
||||
@mock.patch('nova.compute.api.API.get',
|
||||
side_effect=exception.InstanceNotFound(instance_id=UUID1))
|
||||
def test_detach_with_non_existed_instance(self, mock_detach):
|
||||
res = self._make_request(self.action_url,
|
||||
{"detach": {"volume_id": UUID2}})
|
||||
self.assertEqual(404, res.status_int)
|
||||
|
||||
@mock.patch('nova.compute.api.API.detach_volume',
|
||||
side_effect=exception.InvalidVolume(reason=''))
|
||||
def test_detach_with_invalid_vol(self, mock_detach):
|
||||
res = self._make_request(self.action_url,
|
||||
{"detach": {"volume_id": UUID2}})
|
||||
self.assertEqual(400, res.status_int)
|
||||
|
||||
def test_detach_with_bad_id(self):
|
||||
res = self._make_request(self.action_url,
|
||||
{"detach": {"volume_id": 'xxx'}})
|
||||
self.assertEqual(400, res.status_int)
|
||||
|
||||
def test_detach_without_id(self):
|
||||
res = self._make_request(self.action_url, {"detach": {}})
|
||||
self.assertEqual(400, res.status_int)
|
||||
|
||||
def test_detach_volume_with_invalid_request(self):
|
||||
res = self._make_request(self.action_url,
|
||||
{"detach": None})
|
||||
self.assertEqual(400, res.status_int)
|
||||
|
||||
@mock.patch('nova.objects.BlockDeviceMapping.is_root',
|
||||
new_callable=mock.PropertyMock)
|
||||
def test_detach_volume_root(self, mock_isroot):
|
||||
mock_isroot.return_value = True
|
||||
res = self._make_request(self.action_url,
|
||||
{"detach": {"volume_id": UUID1}})
|
||||
self.assertEqual(403, res.status_int)
|
||||
|
||||
def test_attach_volume(self):
|
||||
res = self._make_request(self.action_url,
|
||||
{"attach": {"volume_id": UUID1}})
|
||||
self.assertEqual(202, res.status_int)
|
||||
|
||||
def test_attach_volume_to_locked_server(self):
|
||||
self.stubs.Set(compute.api.API, 'attach_volume',
|
||||
fakes.fake_actions_to_locked_server)
|
||||
res = self._make_request(self.action_url,
|
||||
{"attach": {"volume_id": UUID1}})
|
||||
self.assertEqual(409, res.status_int)
|
||||
|
||||
def test_attach_volume_disk_bus_and_disk_dev(self):
|
||||
self._make_request(self.action_url,
|
||||
{"attach": {"volume_id": UUID1,
|
||||
"device": "/dev/vdb",
|
||||
"disk_bus": "ide",
|
||||
"device_type": "cdrom"}})
|
||||
|
||||
def test_attach_volume_with_bad_id(self):
|
||||
res = self._make_request(self.action_url,
|
||||
{"attach": {"volume_id": 'xxx'}})
|
||||
self.assertEqual(400, res.status_int)
|
||||
|
||||
def test_attach_volume_without_id(self):
|
||||
res = self._make_request(self.action_url, {"attach": {}})
|
||||
self.assertEqual(400, res.status_int)
|
||||
|
||||
def test_attach_volume_with_invalid_request(self):
|
||||
res = self._make_request(self.action_url,
|
||||
{"attach": None})
|
||||
self.assertEqual(400, res.status_int)
|
||||
|
||||
@mock.patch('nova.compute.api.API.attach_volume',
|
||||
side_effect=exception.VolumeNotFound(volume_id=UUID1))
|
||||
def test_attach_volume_with_non_existe_vol(self, mock_attach):
|
||||
res = self._make_request(self.action_url,
|
||||
{"attach": {"volume_id": UUID1}})
|
||||
self.assertEqual(404, res.status_int)
|
||||
|
||||
@mock.patch('nova.compute.api.API.get',
|
||||
side_effect=exception.InstanceNotFound(instance_id=UUID1))
|
||||
def test_attach_volume_with_non_existed_instance(self, mock_attach):
|
||||
res = self._make_request(self.action_url,
|
||||
{"attach": {"volume_id": UUID1}})
|
||||
self.assertEqual(404, res.status_int)
|
||||
|
||||
@mock.patch('nova.compute.api.API.attach_volume',
|
||||
side_effect=exception.InvalidDevicePath(path='xxx'))
|
||||
def test_attach_volume_with_invalid_device_path(self, mock_attach):
|
||||
res = self._make_request(self.action_url,
|
||||
{"attach": {"volume_id": UUID1,
|
||||
'device': 'xxx'}})
|
||||
self.assertEqual(400, res.status_int)
|
||||
|
||||
@mock.patch('nova.compute.api.API.attach_volume',
|
||||
side_effect=exception.InstanceInvalidState(instance_uuid=UUID1,
|
||||
state='',
|
||||
method='', attr=''))
|
||||
def test_attach_volume_with_instance_invalid_state(self, mock_attach):
|
||||
res = self._make_request(self.action_url,
|
||||
{"attach": {"volume_id": UUID1}})
|
||||
self.assertEqual(409, res.status_int)
|
||||
|
||||
@mock.patch('nova.compute.api.API.attach_volume',
|
||||
side_effect=exception.InvalidVolume(reason=''))
|
||||
def test_attach_volume_with_invalid_volume(self, mock_attach):
|
||||
res = self._make_request(self.action_url,
|
||||
{"attach": {"volume_id": UUID1}})
|
||||
self.assertEqual(400, res.status_int)
|
||||
|
||||
@mock.patch('nova.compute.api.API.attach_volume',
|
||||
side_effect=exception.InvalidVolume(reason=''))
|
||||
def test_attach_volume_with_invalid_request_body(self, mock_attach):
|
||||
res = self._make_request(self.action_url, {"attach": None})
|
||||
self.assertEqual(400, res.status_int)
|
||||
|
||||
def _test_swap(self, uuid=UUID1, body=None):
|
||||
body = body or {'swap_volume_attachment': {'old_volume_id': uuid,
|
||||
'new_volume_id': UUID2}}
|
||||
req = fakes.HTTPRequest.blank('/v2/fake/servers/%s/action' % UUID1)
|
||||
req.method = 'PUT'
|
||||
req.body = jsonutils.dumps({})
|
||||
req.headers['content-type'] = 'application/json'
|
||||
req.environ['nova.context'] = context.get_admin_context()
|
||||
return self.Controller.swap(req, UUID1, body=body)
|
||||
|
||||
@mock.patch('nova.compute.api.API.swap_volume')
|
||||
def test_swap_volume(self, mock_swap):
|
||||
# Check any exceptions don't happen and status code
|
||||
self._test_swap()
|
||||
self.assertEqual(202, self.Controller.swap.wsgi_code)
|
||||
|
||||
@mock.patch('nova.compute.api.API.swap_volume',
|
||||
side_effect=exception.InstanceIsLocked(instance_uuid=UUID1))
|
||||
def test_swap_volume_for_locked_server(self, mock_swap):
|
||||
self.assertRaises(webob.exc.HTTPConflict, self._test_swap)
|
||||
|
||||
@mock.patch('nova.compute.api.API.swap_volume',
|
||||
side_effect=exception.InstanceIsLocked(instance_uuid=UUID1))
|
||||
def test_swap_volume_for_locked_server_new(self, mock_swap):
|
||||
self.assertRaises(webob.exc.HTTPConflict, self._test_swap)
|
||||
|
||||
@mock.patch('nova.compute.api.API.get',
|
||||
side_effect=exception.InstanceNotFound(instance_id=UUID1))
|
||||
def test_swap_volume_instance_not_found(self, mock_swap):
|
||||
self.assertRaises(webob.exc.HTTPNotFound, self._test_swap)
|
||||
|
||||
@mock.patch('nova.compute.api.API.swap_volume')
|
||||
def test_swap_volume_with_bad_action(self, mock_swap):
|
||||
body = {'swap_volume_attachment_bad_action': None}
|
||||
self.assertRaises(exception.ValidationError, self._test_swap,
|
||||
body=body)
|
||||
|
||||
@mock.patch('nova.compute.api.API.swap_volume')
|
||||
def test_swap_volume_with_invalid_body(self, mock_swap):
|
||||
body = {'swap_volume_attachment': {'bad_volume_id_body': UUID1,
|
||||
'new_volume_id': UUID2}}
|
||||
self.assertRaises(exception.ValidationError, self._test_swap,
|
||||
body=body)
|
||||
|
||||
@mock.patch('nova.compute.api.API.swap_volume',
|
||||
side_effect=exception.InvalidVolume(reason=''))
|
||||
def test_swap_volume_with_invalid_volume(self, mock_swap):
|
||||
self.assertRaises(webob.exc.HTTPBadRequest, self._test_swap)
|
||||
|
||||
@mock.patch('nova.compute.api.API.swap_volume',
|
||||
side_effect=exception.VolumeUnattached(volume_id='fake'))
|
||||
def test_swap_volume_with_unattached_volume(self, mock_swap):
|
||||
self.assertRaises(webob.exc.HTTPNotFound, self._test_swap)
|
||||
|
||||
@mock.patch('nova.compute.api.API.swap_volume',
|
||||
side_effect=exception.InstanceInvalidState(instance_uuid=UUID1,
|
||||
state='',
|
||||
method='', attr=''))
|
||||
def test_swap_volume_with_bad_state_instance(self, mock_swap):
|
||||
self.assertRaises(webob.exc.HTTPConflict, self._test_swap)
|
||||
|
||||
@mock.patch('nova.compute.api.API.swap_volume')
|
||||
def test_swap_volume_no_attachment(self, mock_swap):
|
||||
self.assertRaises(webob.exc.HTTPNotFound, self._test_swap, UUID3)
|
||||
|
||||
@mock.patch('nova.compute.api.API.swap_volume')
|
||||
@mock.patch('nova.volume.cinder.API.get',
|
||||
side_effect=exception.VolumeNotFound(volume_id=UUID1))
|
||||
def test_swap_volume_not_found(self, mock_swap, mock_cinder_get):
|
||||
self.assertRaises(webob.exc.HTTPNotFound, self._test_swap)
|
||||
|
||||
|
||||
class ExtendedVolumesTestV2(ExtendedVolumesTestV21):
|
||||
|
||||
def _setup_app(self):
|
||||
|
|
Loading…
Reference in New Issue