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:
parent
cf173f417d
commit
ac236c2cd7
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -59,6 +59,9 @@ policy_data = """
|
|||
"compute:attach_volume": "",
|
||||
"compute:detach_volume": "",
|
||||
|
||||
"compute:attach_interface": "",
|
||||
"compute:detach_interface": "",
|
||||
|
||||
"compute:set_admin_password": "",
|
||||
|
||||
"compute:rescue": "",
|
||||
|
|
Loading…
Reference in New Issue