From ac236c2cd73c9d7e05ef9fbd22a4f8f099262082 Mon Sep 17 00:00:00 2001 From: He Jie Xu Date: Sat, 12 Apr 2014 19:30:12 +0800 Subject: [PATCH] Check instance state before attach/detach interface Currently there isn't any instance's status checking before attach/detach interface, It will fail when some status didn't support it. This patch add checking for it. This patch allow attach/detach interface for ACTIVE, PAUSED and STOPPED instance. * ACTIVE: The interface is hotplug to instance. * PAUSED: The interface can be hotpluged after instance unpaused. It's fixed by commit: a868fcedf8e46070cae6aa8e59e61934fa23db1c * STOPPED: In this status, the instance is destroyed. It just update the instance configuration with new interface. When the start instance, the instance will be recreated with new configuration. Change-Id: I3c038056085be1f655758ed8b6a44bcdbf70cdd5 Closes-bug: #1299333 --- .../compute/contrib/attach_interfaces.py | 7 ++++ .../compute/plugins/v3/attach_interfaces.py | 6 +++ nova/compute/api.py | 6 +++ .../compute/contrib/test_attach_interfaces.py | 39 ++++++++++++++++++ .../plugins/v3/test_attach_interfaces.py | 41 +++++++++++++++++++ nova/tests/compute/test_compute_api.py | 30 ++++++++++++++ nova/tests/fake_policy.py | 3 ++ 7 files changed, 132 insertions(+) diff --git a/nova/api/openstack/compute/contrib/attach_interfaces.py b/nova/api/openstack/compute/contrib/attach_interfaces.py index f3b476172401..01f22c00b596 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 ed805dc5740a..5932a2b845fa 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 1ab06c35d3f2..665fa93d32ca 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -2881,6 +2881,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.""" @@ -2890,6 +2893,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 92792670c485..c1e9d84ce81f 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 51a4212fc2b9..f0ed6e45a6ef 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 c1a986e7ffcd..72a580516c4f 100644 --- a/nova/tests/compute/test_compute_api.py +++ b/nova/tests/compute/test_compute_api.py @@ -2172,6 +2172,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 ede5974b17fe..9d583c363f8d 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": "",