diff --git a/nova/api/openstack/compute/contrib/attach_interfaces.py b/nova/api/openstack/compute/contrib/attach_interfaces.py index f3b4761724..01f22c00b5 100644 --- a/nova/api/openstack/compute/contrib/attach_interfaces.py +++ b/nova/api/openstack/compute/contrib/attach_interfaces.py @@ -18,6 +18,7 @@ import webob from webob import exc +from nova.api.openstack import common from nova.api.openstack import extensions from nova import compute from nova import exception @@ -122,6 +123,9 @@ class InterfaceAttachmentController(object): LOG.exception(e) msg = _("Failed to attach interface") raise webob.exc.HTTPInternalServerError(explanation=msg) + except exception.InstanceInvalidState as state_error: + common.raise_http_conflict_for_instance_invalid_state(state_error, + 'attach_interface') return self.show(req, server_id, vif['id']) @@ -153,6 +157,9 @@ class InterfaceAttachmentController(object): except NotImplementedError: msg = _("Network driver does not support this function.") raise webob.exc.HTTPNotImplemented(explanation=msg) + except exception.InstanceInvalidState as state_error: + common.raise_http_conflict_for_instance_invalid_state(state_error, + 'detach_interface') return webob.Response(status_int=202) diff --git a/nova/api/openstack/compute/plugins/v3/attach_interfaces.py b/nova/api/openstack/compute/plugins/v3/attach_interfaces.py index ed805dc574..5932a2b845 100644 --- a/nova/api/openstack/compute/plugins/v3/attach_interfaces.py +++ b/nova/api/openstack/compute/plugins/v3/attach_interfaces.py @@ -123,6 +123,9 @@ class InterfaceAttachmentController(object): LOG.exception(e) raise webob.exc.HTTPInternalServerError( explanation=e.format_message()) + except exception.InstanceInvalidState as state_error: + common.raise_http_conflict_for_instance_invalid_state(state_error, + 'attach_interface') return self.show(req, server_id, vif['id']) @@ -149,6 +152,9 @@ class InterfaceAttachmentController(object): raise exc.HTTPConflict(explanation=e.format_message()) except NotImplementedError as e: raise webob.exc.HTTPNotImplemented(explanation=e.format_message()) + except exception.InstanceInvalidState as state_error: + common.raise_http_conflict_for_instance_invalid_state(state_error, + 'detach_interface') return webob.Response(status_int=202) diff --git a/nova/compute/api.py b/nova/compute/api.py index 004b0688d6..213e7e9b30 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -2880,6 +2880,9 @@ class API(base.Base): @wrap_check_policy @check_instance_lock + @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.PAUSED, + vm_states.STOPPED], + task_state=[None]) def attach_interface(self, context, instance, network_id, port_id, requested_ip): """Use hotplug to add an network adapter to an instance.""" @@ -2889,6 +2892,9 @@ class API(base.Base): @wrap_check_policy @check_instance_lock + @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.PAUSED, + vm_states.STOPPED], + task_state=[None]) def detach_interface(self, context, instance, port_id): """Detach an network adapter from an instance.""" self.compute_rpcapi.detach_interface(context, instance=instance, diff --git a/nova/tests/api/openstack/compute/contrib/test_attach_interfaces.py b/nova/tests/api/openstack/compute/contrib/test_attach_interfaces.py index 92792670c4..c1e9d84ce8 100644 --- a/nova/tests/api/openstack/compute/contrib/test_attach_interfaces.py +++ b/nova/tests/api/openstack/compute/contrib/test_attach_interfaces.py @@ -282,6 +282,45 @@ class InterfaceAttachTests(test.NoDBTestCase): attachments.create, req, FAKE_UUID1, jsonutils.loads(req.body)) + def test_attach_interface_with_invalid_state(self): + def fake_attach_interface_invalid_state(*args, **kwargs): + raise exception.InstanceInvalidState( + instance_uuid='', attr='', state='', + method='attach_interface') + + self.stubs.Set(compute_api.API, 'attach_interface', + fake_attach_interface_invalid_state) + attachments = attach_interfaces.InterfaceAttachmentController() + req = webob.Request.blank('/v2/fake/os-interfaces/attach') + req.method = 'POST' + req.body = jsonutils.dumps({'interfaceAttachment': + {'net_id': FAKE_NET_ID1}}) + req.headers['content-type'] = 'application/json' + req.environ['nova.context'] = self.context + self.assertRaises(exc.HTTPConflict, + attachments.create, req, FAKE_UUID1, + jsonutils.loads(req.body)) + + def test_detach_interface_with_invalid_state(self): + def fake_detach_interface_invalid_state(*args, **kwargs): + raise exception.InstanceInvalidState( + instance_uuid='', attr='', state='', + method='detach_interface') + + self.stubs.Set(compute_api.API, 'detach_interface', + fake_detach_interface_invalid_state) + attachments = attach_interfaces.InterfaceAttachmentController() + req = webob.Request.blank('/v2/fake/os-interfaces/attach') + req.method = 'DELETE' + req.body = jsonutils.dumps({}) + req.headers['content-type'] = 'application/json' + req.environ['nova.context'] = self.context + self.assertRaises(exc.HTTPConflict, + attachments.delete, + req, + FAKE_UUID1, + FAKE_NET_ID1) + class InterfaceAttachTestsWithMock(test.NoDBTestCase): def setUp(self): diff --git a/nova/tests/api/openstack/compute/plugins/v3/test_attach_interfaces.py b/nova/tests/api/openstack/compute/plugins/v3/test_attach_interfaces.py index 51a4212fc2..f0ed6e45a6 100644 --- a/nova/tests/api/openstack/compute/plugins/v3/test_attach_interfaces.py +++ b/nova/tests/api/openstack/compute/plugins/v3/test_attach_interfaces.py @@ -376,6 +376,47 @@ class InterfaceAttachTests(test.NoDBTestCase): param = {'fixed_ips': 'non_array'} self._test_attach_interface_with_invalid_parameter(param) + def test_attach_interface_with_invalid_state(self): + def fake_attach_interface_invalid_state(*args, **kwargs): + raise exception.InstanceInvalidState( + instance_uuid='', attr='', state='', + method='attach_interface') + + self.stubs.Set(compute_api.API, 'attach_interface', + fake_attach_interface_invalid_state) + attachments = attach_interfaces.InterfaceAttachmentController() + req = webob.Request.blank( + '/v3/servers/fake/os-attach-interfaces/attach') + req.method = 'POST' + req.body = jsonutils.dumps({'interface_attachment': + {'net_id': FAKE_NET_ID1}}) + req.headers['content-type'] = 'application/json' + req.environ['nova.context'] = self.context + self.assertRaises(exc.HTTPConflict, + attachments.create, req, FAKE_UUID1, + body=jsonutils.loads(req.body)) + + def test_detach_interface_with_invalid_state(self): + def fake_detach_interface_invalid_state(*args, **kwargs): + raise exception.InstanceInvalidState( + instance_uuid='', attr='', state='', + method='detach_interface') + + self.stubs.Set(compute_api.API, 'detach_interface', + fake_detach_interface_invalid_state) + attachments = attach_interfaces.InterfaceAttachmentController() + req = webob.Request.blank( + '/v3/servers/fake/os-attach-interfaces/delete') + req.method = 'DELETE' + req.body = jsonutils.dumps({}) + req.headers['content-type'] = 'application/json' + req.environ['nova.context'] = self.context + self.assertRaises(exc.HTTPConflict, + attachments.delete, + req, + FAKE_UUID1, + FAKE_NET_ID1) + class InterfaceAttachTestsWithMock(test.NoDBTestCase): def setUp(self): diff --git a/nova/tests/compute/test_compute_api.py b/nova/tests/compute/test_compute_api.py index f67269dde5..fd4fc3129a 100644 --- a/nova/tests/compute/test_compute_api.py +++ b/nova/tests/compute/test_compute_api.py @@ -2214,6 +2214,36 @@ class _ComputeAPIUnitTestMixIn(object): do_test() + def _test_attach_interface_invalid_state(self, state): + instance = self._create_instance_obj( + params={'vm_state': state}) + self.assertRaises(exception.InstanceInvalidState, + self.compute_api.attach_interface, + self.context, instance, '', '', '', []) + + def test_attach_interface_invalid_state(self): + for state in [vm_states.BUILDING, vm_states.DELETED, + vm_states.ERROR, vm_states.RESCUED, + vm_states.RESIZED, vm_states.SOFT_DELETED, + vm_states.SUSPENDED, vm_states.SHELVED, + vm_states.SHELVED_OFFLOADED]: + self._test_attach_interface_invalid_state(state) + + def _test_detach_interface_invalid_state(self, state): + instance = self._create_instance_obj( + params={'vm_state': state}) + self.assertRaises(exception.InstanceInvalidState, + self.compute_api.detach_interface, + self.context, instance, '', '', '', []) + + def test_detach_interface_invalid_state(self): + for state in [vm_states.BUILDING, vm_states.DELETED, + vm_states.ERROR, vm_states.RESCUED, + vm_states.RESIZED, vm_states.SOFT_DELETED, + vm_states.SUSPENDED, vm_states.SHELVED, + vm_states.SHELVED_OFFLOADED]: + self._test_detach_interface_invalid_state(state) + class ComputeAPIUnitTestCase(_ComputeAPIUnitTestMixIn, test.NoDBTestCase): def setUp(self): diff --git a/nova/tests/fake_policy.py b/nova/tests/fake_policy.py index ede5974b17..9d583c363f 100644 --- a/nova/tests/fake_policy.py +++ b/nova/tests/fake_policy.py @@ -59,6 +59,9 @@ policy_data = """ "compute:attach_volume": "", "compute:detach_volume": "", + "compute:attach_interface": "", + "compute:detach_interface": "", + "compute:set_admin_password": "", "compute:rescue": "",