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: a868fcedf8
* 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
This commit is contained in:
He Jie Xu 2014-04-12 19:30:12 +08:00
parent cf173f417d
commit ac236c2cd7
7 changed files with 132 additions and 0 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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,

View File

@ -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):

View File

@ -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):

View File

@ -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):

View File

@ -59,6 +59,9 @@ policy_data = """
"compute:attach_volume": "",
"compute:detach_volume": "",
"compute:attach_interface": "",
"compute:detach_interface": "",
"compute:set_admin_password": "",
"compute:rescue": "",