Convert features not supported error to HTTPBadRequest

There is inconsistency on return code nova API return
for "Feature not supported/implemented'. Current return
code are 400, 409, and 403.
- 400 case: Example: Multiattach Swap Volume Not Supported
- 403 case: Cyborg integration
- 409 case: Example: Operation Not Supported For SEV ,
  Operation Not Supported For VTPM

In xena PTG, we agreed to fix this by returning 400 in all cases
- L446: https://etherpad.opendev.org/p/nova-xena-ptg

This commit convert all the features not supported error to
HTTPBadRequest(400).

To avoid converting every NotSupported inherited exception
in API controller to HTTPBadRequest generic conversion is
added in expected_errors() decorator.

Closes-Bug: #1938093

Change-Id: I410924668a73785f1bfe5c79827915d72e1d9e03
This commit is contained in:
Ghanshyam Mann 2021-08-26 19:46:09 -05:00
parent 7c1ca501ee
commit 1b6a6e3916
21 changed files with 163 additions and 114 deletions

View File

@ -180,7 +180,6 @@ class InterfaceAttachmentController(wsgi.Controller):
exception.ForbiddenPortsWithAccelerator) as e:
raise exc.HTTPBadRequest(explanation=e.format_message())
except (
exception.OperationNotSupportedForVDPAInterface,
exception.InstanceIsLocked,
exception.FixedIpAlreadyInUse,
exception.PortInUse,
@ -218,10 +217,7 @@ class InterfaceAttachmentController(wsgi.Controller):
instance, port_id=port_id)
except exception.PortNotFound as e:
raise exc.HTTPNotFound(explanation=e.format_message())
except (
exception.OperationNotSupportedForVDPAInterface,
exception.InstanceIsLocked,
) as e:
except exception.InstanceIsLocked as e:
raise exc.HTTPConflict(explanation=e.format_message())
except NotImplementedError:
common.raise_feature_not_supported()

View File

@ -130,13 +130,6 @@ class EvacuateController(wsgi.Controller):
exception.ExtendedResourceRequestOldCompute,
) as e:
raise exc.HTTPBadRequest(explanation=e.format_message())
except exception.ForbiddenWithAccelerators as e:
raise exc.HTTPForbidden(explanation=e.format_message())
except (
exception.OperationNotSupportedForVTPM,
exception.OperationNotSupportedForVDPAInterface,
) as e:
raise exc.HTTPConflict(explanation=e.format_message())
if (not api_version_request.is_supported(req, min_version='2.14') and
CONF.api.enable_instance_password):

View File

@ -57,14 +57,12 @@ class MigrateServerController(wsgi.Controller):
try:
self.compute_api.resize(req.environ['nova.context'], instance,
host_name=host_name)
except (exception.TooManyInstances, exception.QuotaError,
exception.ForbiddenWithAccelerators) as e:
except (exception.TooManyInstances, exception.QuotaError) as e:
raise exc.HTTPForbidden(explanation=e.format_message())
except (
exception.InstanceIsLocked,
exception.InstanceNotReady,
exception.ServiceUnavailable,
exception.OperationNotSupportedForVDPAInterface,
) as e:
raise exc.HTTPConflict(explanation=e.format_message())
except exception.InstanceInvalidState as state_error:
@ -143,12 +141,6 @@ class MigrateServerController(wsgi.Controller):
"'%(ex)s'", {'ex': ex})
else:
raise exc.HTTPBadRequest(explanation=ex.format_message())
except (
exception.OperationNotSupportedForSEV,
exception.OperationNotSupportedForVTPM,
exception.OperationNotSupportedForVDPAInterface,
) as e:
raise exc.HTTPConflict(explanation=e.format_message())
except exception.InstanceIsLocked as e:
raise exc.HTTPConflict(explanation=e.format_message())
except (
@ -159,8 +151,6 @@ class MigrateServerController(wsgi.Controller):
except exception.InstanceInvalidState as state_error:
common.raise_http_conflict_for_instance_invalid_state(state_error,
'os-migrateLive', id)
except exception.ForbiddenWithAccelerators as e:
raise exc.HTTPForbidden(explanation=e.format_message())
def _get_force_param_for_live_migration(self, body, host):
force = body["os-migrateLive"].get("force", False)

View File

@ -65,8 +65,6 @@ class RescueController(wsgi.Controller):
allow_bfv_rescue=allow_bfv_rescue)
except (
exception.InstanceIsLocked,
exception.OperationNotSupportedForVTPM,
exception.OperationNotSupportedForVDPAInterface,
exception.InvalidVolume,
) as e:
raise exc.HTTPConflict(explanation=e.format_message())

View File

@ -1039,12 +1039,10 @@ class ServersController(wsgi.Controller):
try:
self.compute_api.resize(context, instance, flavor_id,
auto_disk_config=auto_disk_config)
except (exception.QuotaError,
exception.ForbiddenWithAccelerators) as error:
except exception.QuotaError as error:
raise exc.HTTPForbidden(
explanation=error.format_message())
except (
exception.OperationNotSupportedForVDPAInterface,
exception.InstanceIsLocked,
exception.InstanceNotReady,
exception.MixedInstanceNotSupportByComputeService,
@ -1067,7 +1065,6 @@ class ServersController(wsgi.Controller):
exception.CannotResizeDisk,
exception.CannotResizeToSameFlavor,
exception.FlavorNotFound,
exception.ForbiddenPortsWithAccelerator,
exception.ExtendedResourceRequestOldCompute,
) as e:
raise exc.HTTPBadRequest(explanation=e.format_message())
@ -1212,11 +1209,7 @@ class ServersController(wsgi.Controller):
image_href,
password,
**kwargs)
except (
exception.InstanceIsLocked,
exception.OperationNotSupportedForVTPM,
exception.OperationNotSupportedForVDPAInterface,
) as e:
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,
@ -1230,8 +1223,7 @@ class ServersController(wsgi.Controller):
except exception.KeypairNotFound:
msg = _("Invalid key_name provided.")
raise exc.HTTPBadRequest(explanation=msg)
except (exception.QuotaError,
exception.ForbiddenWithAccelerators) as error:
except exception.QuotaError as error:
raise exc.HTTPForbidden(explanation=error.format_message())
except (exception.AutoDiskConfigDisabledByImage,
exception.CertificateValidationFailed,

View File

@ -49,13 +49,9 @@ class ShelveController(wsgi.Controller):
self.compute_api.shelve(context, instance)
except (
exception.InstanceIsLocked,
exception.OperationNotSupportedForVTPM,
exception.OperationNotSupportedForVDPAInterface,
exception.UnexpectedTaskStateError,
) as e:
raise exc.HTTPConflict(explanation=e.format_message())
except exception.ForbiddenWithAccelerators as e:
raise exc.HTTPForbidden(explanation=e.format_message())
except exception.InstanceInvalidState as state_error:
common.raise_http_conflict_for_instance_invalid_state(state_error,
'shelve', id)

View File

@ -38,17 +38,11 @@ class SuspendServerController(wsgi.Controller):
target={'user_id': server.user_id,
'project_id': server.project_id})
self.compute_api.suspend(context, server)
except (
exception.OperationNotSupportedForSEV,
exception.OperationNotSupportedForVDPAInterface,
exception.InstanceIsLocked,
) as e:
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,
'suspend', id)
except exception.ForbiddenWithAccelerators as e:
raise exc.HTTPForbidden(explanation=e.format_message())
except exception.ForbiddenPortsWithAccelerator as e:
raise exc.HTTPBadRequest(explanation=e.format_message())

View File

@ -392,8 +392,7 @@ class VolumeAttachmentController(wsgi.Controller):
except exception.VolumeNotFound as e:
raise exc.HTTPNotFound(explanation=e.format_message())
except (exception.InstanceIsLocked,
exception.DevicePathInUse,
exception.MultiattachNotSupportedByVirtDriver) as e:
exception.DevicePathInUse) 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,

View File

@ -671,6 +671,15 @@ def expected_errors(errors):
# calls. ResourceExceptionHandler silently
# converts NotAuthorized to HTTPForbidden
raise
elif isinstance(exc, exception.NotSupported):
# Note(gmann): Special case to handle
# NotSupported exceptions. We want to raise 400 BadRequest
# for the NotSupported exception which is basically used
# to raise for not supported features. Converting it here
# will avoid converting every NotSupported inherited
# exception in API controller.
raise webob.exc.HTTPBadRequest(
explanation=exc.format_message())
elif isinstance(exc, exception.ValidationError):
# Note(oomichi): Handle a validation error, which
# happens due to invalid API parameters, as an

View File

@ -155,12 +155,24 @@ class Forbidden(NovaException):
code = 403
class ForbiddenWithAccelerators(Forbidden):
msg_fmt = _("Forbidden with instances that have accelerators.")
class NotSupported(NovaException):
# This exception use return code as 400 and can be used
# directly or as base exception for operations whihc are not
# supported in Nova. Any feature that is not yet implemented
# but plan to implement in future (example: Cyborg
# integration operations), should use this exception as base
# and override the msg_fmt with feature details.
# Example: MultiattachNotSupportedByVirtDriver exception.
msg_fmt = _("Bad Request - Feature is not supported in Nova")
code = 400
class ForbiddenPortsWithAccelerator(Forbidden):
msg_fmt = _("Forbidden with Ports that have accelerators.")
class ForbiddenWithAccelerators(NotSupported):
msg_fmt = _("Feature not supported with instances that have accelerators.")
class ForbiddenPortsWithAccelerator(NotSupported):
msg_fmt = _("Feature not supported with Ports that have accelerators.")
class AdminRequired(Forbidden):
@ -281,14 +293,13 @@ class VolumeExtendFailed(Invalid):
"Reason: %(reason)s")
class MultiattachNotSupportedByVirtDriver(NovaException):
class MultiattachNotSupportedByVirtDriver(NotSupported):
# This exception indicates the compute hosting the instance does not
# support multiattach volumes. This should generally be considered a
# 409 HTTPConflict error in the API since we expect all virt drivers to
# 400 HTTPBadRequest error in the API since we expect all virt drivers to
# eventually support multiattach volumes.
msg_fmt = _("Volume %(volume_id)s has 'multiattach' set, "
"which is not supported for this instance.")
code = 409
class MultiattachNotSupportedOldMicroversion(Invalid):
@ -538,24 +549,21 @@ class UnableToMigrateToSelf(Invalid):
"to current host (%(host)s).")
class OperationNotSupportedForSEV(NovaException):
class OperationNotSupportedForSEV(NotSupported):
msg_fmt = _("Operation '%(operation)s' not supported for SEV-enabled "
"instance (%(instance_uuid)s).")
code = 409
class OperationNotSupportedForVTPM(NovaException):
class OperationNotSupportedForVTPM(NotSupported):
msg_fmt = _("Operation '%(operation)s' not supported for vTPM-enabled "
"instance (%(instance_uuid)s).")
code = 409
class OperationNotSupportedForVDPAInterface(NovaException):
class OperationNotSupportedForVDPAInterface(NotSupported):
msg_fmt = _(
"Operation '%(operation)s' not supported for instance with "
"vDPA ports ((instance_uuid)s)."
)
code = 409
class InvalidHypervisorType(Invalid):

View File

@ -5909,26 +5909,26 @@ class AcceleratorServerOpsTest(AcceleratorServerBase):
ex = self.assertRaises(client.OpenStackApiException,
self.api.post_server_action, self.server['id'],
{'resize': {'flavorRef': '2', 'OS-DCF:diskConfig': 'AUTO'}})
self.assertEqual(403, ex.response.status_code)
self.assertEqual(400, ex.response.status_code)
self._check_allocations_usage(self.server)
def test_suspend_fails(self):
ex = self.assertRaises(client.OpenStackApiException,
self.api.post_server_action, self.server['id'], {'suspend': {}})
self.assertEqual(403, ex.response.status_code)
self.assertEqual(400, ex.response.status_code)
self._check_allocations_usage(self.server)
def test_migrate_fails(self):
ex = self.assertRaises(client.OpenStackApiException,
self.api.post_server_action, self.server['id'], {'migrate': {}})
self.assertEqual(403, ex.response.status_code)
self.assertEqual(400, ex.response.status_code)
self._check_allocations_usage(self.server)
def test_live_migrate_fails(self):
ex = self.assertRaises(client.OpenStackApiException,
self.api.post_server_action, self.server['id'],
{'migrate': {'host': 'accel_host1'}})
self.assertEqual(403, ex.response.status_code)
self.assertEqual(400, ex.response.status_code)
self._check_allocations_usage(self.server)
@mock.patch.object(objects.service, 'get_minimum_version_all_cells')
@ -5945,7 +5945,7 @@ class AcceleratorServerOpsTest(AcceleratorServerBase):
{'evacuate': {
'host': compute_to_evacuate.host,
'adminPass': 'MySecretPass'}})
self.assertEqual(403, ex.response.status_code)
self.assertEqual(400, ex.response.status_code)
self._check_allocations_usage(self.server)
@mock.patch.object(objects.service, 'get_minimum_version_all_cells')
@ -5961,7 +5961,7 @@ class AcceleratorServerOpsTest(AcceleratorServerBase):
{'rebuild': {
'imageRef': rebuild_image_ref,
'OS-DCF:diskConfig': 'AUTO'}})
self.assertEqual(403, ex.response.status_code)
self.assertEqual(400, ex.response.status_code)
self._check_allocations_usage(self.server)
@mock.patch.object(objects.service, 'get_minimum_version_all_cells')
@ -5973,7 +5973,7 @@ class AcceleratorServerOpsTest(AcceleratorServerBase):
ex = self.assertRaises(client.OpenStackApiException,
self.api.post_server_action, self.server['id'],
{'shelve': {}})
self.assertEqual(403, ex.response.status_code)
self.assertEqual(400, ex.response.status_code)
self._check_allocations_usage(self.server)
def _test_shelve_instance_with_compute_rpc_pin(
@ -5990,7 +5990,7 @@ class AcceleratorServerOpsTest(AcceleratorServerBase):
ex = self.assertRaises(
client.OpenStackApiException, self.api.post_server_action,
self.server['id'], {'shelve': {}})
self.assertEqual(403, ex.response.status_code)
self.assertEqual(400, ex.response.status_code)
def test_shelve_instance_5_13(self):
body = {'shelve': {}}

View File

@ -252,6 +252,23 @@ class InterfaceAttachTestsV21(test.NoDBTestCase):
self.assertRaises(exc.HTTPBadRequest, self.attachments.create,
self.req, FAKE_UUID1, body=body)
@mock.patch.object(
compute_api.API, 'attach_interface',
side_effect=exception.OperationNotSupportedForVDPAInterface(
instance_uuid=FAKE_UUID1, operation='foo'))
def test_interface_vdpa_interface_not_supported(self, mock_attach):
body = {'interfaceAttachment': {'net_id': FAKE_NET_ID2}}
self.assertRaises(exc.HTTPBadRequest, self.attachments.create,
self.req, FAKE_UUID1, body=body)
@mock.patch.object(
compute_api.API, 'detach_interface',
side_effect=exception.OperationNotSupportedForVDPAInterface(
instance_uuid=FAKE_UUID1, operation='foo'))
def test_detach_interface_vdpa_interface_not_supported(self, mock_detach):
self.assertRaises(exc.HTTPBadRequest, self.attachments.delete,
self.req, FAKE_UUID1, FAKE_NET_ID1)
def test_attach_interface_with_network_id(self):
self.stub_out('nova.compute.api.API.attach_interface',
fake_attach_interface)

View File

@ -115,20 +115,29 @@ class EvacuateTestV21(test.NoDBTestCase):
uuid='BAD_UUID')
@mock.patch('nova.compute.api.API.evacuate')
def test_evacuate__with_vtpm(self, mock_evacuate):
def test_evacuate_raise_badrequest_with_vtpm(self, mock_evacuate):
mock_evacuate.side_effect = exception.OperationNotSupportedForVTPM(
instance_uuid=uuids.instance, operation='foo')
self._check_evacuate_failure(
webob.exc.HTTPConflict,
webob.exc.HTTPBadRequest,
{'host': 'foo', 'onSharedStorage': 'False', 'adminPass': 'bar'})
@mock.patch('nova.compute.api.API.evacuate')
def test_evacuate__with_vdpa_interface(self, mock_evacuate):
def test_evacuate_raise_badrequest_with_vdpa_interface(
self, mock_evacuate):
mock_evacuate.side_effect = \
exception.OperationNotSupportedForVDPAInterface(
instance_uuid=uuids.instance, operation='foo')
self._check_evacuate_failure(
webob.exc.HTTPConflict,
webob.exc.HTTPBadRequest,
{'host': 'foo', 'onSharedStorage': 'False', 'adminPass': 'bar'})
@mock.patch('nova.compute.api.API.evacuate')
def test_evacuate_raise_badrequest_for_accelerator(self, mock_evacuate):
mock_evacuate.side_effect = \
exception.ForbiddenWithAccelerators()
self._check_evacuate_failure(
webob.exc.HTTPBadRequest,
{'host': 'foo', 'onSharedStorage': 'False', 'adminPass': 'bar'})
def test_evacuate_with_active_service(self):

View File

@ -140,6 +140,15 @@ class MigrateServerTestsV21(admin_only_action_common.CommonTests):
allowed=0)
self._test_migrate_exception(exc_info, webob.exc.HTTPForbidden)
def test_migrate_raise_badrequest_for_accelerator(self):
exc_info = exception.ForbiddenWithAccelerators()
self._test_migrate_exception(exc_info, webob.exc.HTTPBadRequest)
def test_migrate_raise_badrequest_for_vdpainterface(self):
exc_info = exception.OperationNotSupportedForVDPAInterface(
instance_uuid=uuids.instance, operation='foo')
self._test_migrate_exception(exc_info, webob.exc.HTTPBadRequest)
def _test_migrate_live_succeeded(self, param):
instance = self._stub_instance_get()
@ -290,21 +299,21 @@ class MigrateServerTestsV21(admin_only_action_common.CommonTests):
self._test_migrate_live_failed_with_exception(
exception.OperationNotSupportedForSEV(
instance_uuid=uuids.instance, operation='foo'),
expected_exc=webob.exc.HTTPConflict,
expected_exc=webob.exc.HTTPBadRequest,
check_response=False)
def test_migrate_live_vtpm_not_supported(self):
self._test_migrate_live_failed_with_exception(
exception.OperationNotSupportedForVTPM(
instance_uuid=uuids.instance, operation='foo'),
expected_exc=webob.exc.HTTPConflict,
expected_exc=webob.exc.HTTPBadRequest,
check_response=False)
def test_migrate_live_vdpa_interfaces_not_supported(self):
self._test_migrate_live_failed_with_exception(
exception.OperationNotSupportedForVDPAInterface(
instance_uuid=uuids.instance, operation='foo'),
expected_exc=webob.exc.HTTPConflict,
expected_exc=webob.exc.HTTPBadRequest,
check_response=False)
def test_migrate_live_pre_check_error(self):
@ -319,9 +328,10 @@ class MigrateServerTestsV21(admin_only_action_common.CommonTests):
@mock.patch('nova.compute.api.API.live_migrate',
side_effect=exception.ForbiddenWithAccelerators)
def test_live_migration_raises_http_forbidden(self, mock_migrate):
def test_live_migration_raises_badrequest_for_accelerators(
self, mock_migrate):
body = self._get_migration_body(host='hostname')
self.assertRaises(webob.exc.HTTPForbidden,
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller._migrate_live,
self.req, fakes.FAKE_UUID, body=body)

View File

@ -67,10 +67,6 @@ class RescueTestV21(test.NoDBTestCase):
@ddt.data(
exception.InstanceIsLocked(instance_uuid=uuids.instance),
exception.OperationNotSupportedForVTPM(
instance_uuid=uuids.instance, operation='foo'),
exception.OperationNotSupportedForVDPAInterface(
instance_uuid=uuids.instance, operation='foo'),
exception.InvalidVolume(reason='foo'),
)
@mock.patch.object(compute.api.API, 'rescue')
@ -83,6 +79,23 @@ class RescueTestV21(test.NoDBTestCase):
self.fake_req, UUID, body=body)
self.assertTrue(mock_rescue.called)
@ddt.data(
exception.OperationNotSupportedForVTPM(
instance_uuid=uuids.instance, operation='foo'),
exception.OperationNotSupportedForVDPAInterface(
instance_uuid=uuids.instance, operation='foo'),
)
@mock.patch.object(compute.api.API, 'rescue')
def test_rescue_raise_badrequest_for_not_supported_features(
self, exc, mock_rescue):
"""Test that exceptions are translated into HTTP BadRequest errors."""
mock_rescue.side_effect = exc
body = {"rescue": {"adminPass": "AABBCC112233"}}
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller._rescue,
self.fake_req, UUID, body=body)
self.assertTrue(mock_rescue.called)
def test_rescue_with_preset_password(self):
body = {"rescue": {"adminPass": "AABBCC112233"}}
resp = self.controller._rescue(self.fake_req, UUID, body=body)

View File

@ -376,10 +376,6 @@ class ServerActionsControllerTestV21(test.TestCase):
@ddt.data(
exception.InstanceIsLocked(instance_uuid=uuids.instance),
exception.OperationNotSupportedForVTPM(
instance_uuid=uuids.instance, operation='foo'),
exception.OperationNotSupportedForVDPAInterface(
instance_uuid=uuids.instance, operation='foo'),
)
@mock.patch('nova.compute.api.API.rebuild')
def test_rebuild__http_conflict_error(self, exc, mock_rebuild):
@ -390,6 +386,22 @@ class ServerActionsControllerTestV21(test.TestCase):
self.req, uuids.instance,
body={'rebuild': {'imageRef': uuids.image}})
@ddt.data(
exception.ForbiddenWithAccelerators(),
exception.OperationNotSupportedForVTPM(
instance_uuid=uuids.instance, operation='foo'),
exception.OperationNotSupportedForVDPAInterface(
instance_uuid=uuids.instance, operation='foo'),
)
@mock.patch('nova.compute.api.API.rebuild')
def test_rebuild_raises_badrequest_for_not_supported_features(
self, exc, mock_rebuild):
mock_rebuild.side_effect = exc
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller._action_rebuild,
self.req, uuids.instance,
body={'rebuild': {'imageRef': uuids.image}})
def test_rebuild_raises_conflict_on_invalid_state(self):
body = {'rebuild': {'imageRef': uuids.image}}
@ -860,9 +872,18 @@ class ServerActionsControllerTestV21(test.TestCase):
@mock.patch('nova.compute.api.API.resize',
side_effect=exception.ForbiddenWithAccelerators)
def test_resize_raises_http_forbidden(self, mock_resize):
def test_resize_raises_badrequest_for_accelerator(self, mock_resize):
body = dict(resize=dict(flavorRef="http://localhost/3"))
self.assertRaises(webob.exc.HTTPForbidden,
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller._action_resize,
self.req, FAKE_UUID, body=body)
@mock.patch('nova.compute.api.API.resize',
side_effect=exception.OperationNotSupportedForVDPAInterface(
instance_uuid=FAKE_UUID, operation='foo'))
def test_resize_raises_badrequest_for_vdpaInterface(self, mock_resize):
body = dict(resize=dict(flavorRef="http://localhost/3"))
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller._action_resize,
self.req, FAKE_UUID, body=body)

View File

@ -41,10 +41,6 @@ class ShelveControllerTest(test.NoDBTestCase):
@ddt.data(
exception.InstanceIsLocked(instance_uuid=uuids.instance),
exception.OperationNotSupportedForVTPM(
instance_uuid=uuids.instance, operation='foo'),
exception.OperationNotSupportedForVDPAInterface(
instance_uuid=uuids.instance, operation='foo'),
exception.UnexpectedTaskStateError(
instance_uuid=uuids.instance, expected=None,
actual=task_states.SHELVING),
@ -62,17 +58,23 @@ class ShelveControllerTest(test.NoDBTestCase):
webob.exc.HTTPConflict, self.controller._shelve,
self.req, uuids.fake, {})
@ddt.data(
exception.ForbiddenWithAccelerators(),
exception.OperationNotSupportedForVTPM(
instance_uuid=uuids.instance, operation='foo'),
exception.OperationNotSupportedForVDPAInterface(
instance_uuid=uuids.instance, operation='foo'),
)
@mock.patch('nova.compute.api.API.shelve')
@mock.patch('nova.api.openstack.common.get_instance')
def test_shelve_raise_http_forbidden(
self, mock_get_instance, mock_shelve,
def test_shelve_raise_badrequest_for_not_supported_feature(
self, exc, mock_get_instance, mock_shelve,
):
mock_get_instance.return_value = (
fake_instance.fake_instance_obj(self.req.environ['nova.context']))
mock_shelve.side_effect = exception.ForbiddenWithAccelerators
mock_shelve.side_effect = exc
self.assertRaises(
webob.exc.HTTPForbidden, self.controller._shelve,
webob.exc.HTTPBadRequest, self.controller._shelve,
self.req, uuids.fake, {})
@mock.patch('nova.api.openstack.common.get_instance')

View File

@ -21,7 +21,6 @@ from nova.api.openstack.compute import suspend_server as \
suspend_server_v21
from nova import exception
from nova.tests.unit.api.openstack.compute import admin_only_action_common
from nova.tests.unit.api.openstack import fakes
@ddt.ddt
@ -46,12 +45,14 @@ class SuspendServerTestsV21(admin_only_action_common.CommonTests):
instance_uuid=uuids.instance, operation='foo'),
exception.OperationNotSupportedForSEV(
instance_uuid=uuids.instance, operation='foo'),
exception.ForbiddenWithAccelerators(),
)
@mock.patch('nova.compute.api.API.suspend')
def test_suspend__http_conflict_error(self, exc, mock_suspend):
def test_suspend_raise_badrequest_for_not_supported_features(
self, exc, mock_suspend):
mock_suspend.side_effect = exc
self.assertRaises(
webob.exc.HTTPConflict,
webob.exc.HTTPBadRequest,
self.controller._suspend,
self.req, uuids.instance, body={})
self.assertTrue(mock_suspend.called)
@ -65,10 +66,3 @@ class SuspendServerTestsV21(admin_only_action_common.CommonTests):
def test_actions_with_locked_instance(self):
self._test_actions_with_locked_instance(['_suspend', '_resume'])
@mock.patch('nova.compute.api.API.suspend',
side_effect=exception.ForbiddenWithAccelerators)
def test_suspend_raises_http_forbidden(self, mock_suspend):
self.assertRaises(webob.exc.HTTPForbidden,
self.controller._suspend,
self.req, fakes.FAKE_UUID, body={})

View File

@ -977,7 +977,7 @@ class VolumeAttachTestsV260(test.NoDBTestCase):
side_effect=
exception.MultiattachNotSupportedByVirtDriver(
volume_id=FAKE_UUID_A)) as attach:
ex = self.assertRaises(webob.exc.HTTPConflict, self._post_attach)
ex = self.assertRaises(webob.exc.HTTPBadRequest, self._post_attach)
create_kwargs = attach.call_args[1]
self.assertTrue(create_kwargs['supports_multiattach'])
self.assertIn("has 'multiattach' set, which is not supported for "

View File

@ -7830,7 +7830,7 @@ class ComputeAPIUnitTestCase(_ComputeAPIUnitTestMixIn, test.NoDBTestCase):
args_info = {}
self.assertRaisesRegex(exception.ForbiddenWithAccelerators,
'Forbidden with instances that have accelerators.',
'Feature not supported with instances that have accelerators.',
self._test_block_accelerators, instance, args_info)
# myfunc was not called
self.assertEqual({}, args_info)
@ -7861,7 +7861,7 @@ class ComputeAPIUnitTestCase(_ComputeAPIUnitTestMixIn, test.NoDBTestCase):
instance = self._create_instance_obj(flavor=flavor)
args_info = {}
self.assertRaisesRegex(exception.ForbiddenWithAccelerators,
'Forbidden with instances that have accelerators.',
'Feature not supported with instances that have accelerators.',
self._test_block_accelerators, instance, args_info, 53)
# myfunc was not called
self.assertEqual({}, args_info)
@ -7895,7 +7895,7 @@ class ComputeAPIUnitTestCase(_ComputeAPIUnitTestMixIn, test.NoDBTestCase):
instance.info_cache.network_info = nw_info
args_info = {}
self.assertRaisesRegex(exception.ForbiddenPortsWithAccelerator,
'Forbidden with Ports that have accelerators.',
'Feature not supported with Ports that have accelerators.',
self._test_block_port_accelerators, instance, args_info)
self.assertEqual({}, args_info)
@ -7906,7 +7906,7 @@ class ComputeAPIUnitTestCase(_ComputeAPIUnitTestMixIn, test.NoDBTestCase):
instance.info_cache.network_info = nw_info
args_info = {}
self.assertRaisesRegex(exception.ForbiddenPortsWithAccelerator,
'Forbidden with Ports that have accelerators.',
'Feature not supported with Ports that have accelerators.',
self._test_block_port_accelerators, instance, args_info)
self.assertEqual({}, args_info)
@ -8019,7 +8019,7 @@ class ComputeAPIUnitTestCase(_ComputeAPIUnitTestMixIn, test.NoDBTestCase):
flavor = self._create_flavor(extra_specs=extra_specs)
instance = self._create_instance_obj(flavor=flavor)
self.assertRaisesRegex(exception.ForbiddenWithAccelerators,
'Forbidden with instances that have accelerators.',
'Feature not supported with instances that have accelerators.',
self.compute_api.shelve, self.context, instance)
mock_get_arq_uuuids.assert_not_called()

View File

@ -0,0 +1,8 @@
---
upgrade:
- |
A few of the APIs return code was not consistent for the operations/
features not implemented or supported. It was returned as 403, 400, or
409 (for Operation Not Supported For SEV , Operation Not Supported For
VTPM cases). Now we have made it consistent and return 400 always when
any operations/features are not implemented or supported.