summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--nova/api/openstack/compute/contrib/volumes.py19
-rw-r--r--nova/api/openstack/wsgi.py4
-rw-r--r--nova/cells/manager.py17
-rw-r--r--nova/cells/messaging.py48
-rw-r--r--nova/cells/rpcapi.py17
-rw-r--r--nova/compute/api.py12
-rwxr-xr-xnova/compute/manager.py11
-rw-r--r--nova/compute/rpcapi.py5
-rw-r--r--nova/consoleauth/manager.py15
-rw-r--r--nova/consoleauth/rpcapi.py2
-rw-r--r--nova/tests/api/openstack/compute/test_api.py2
-rw-r--r--nova/tests/api/openstack/test_faults.py4
-rw-r--r--nova/tests/api/openstack/test_wsgi.py2
-rw-r--r--nova/tests/cells/test_cells_manager.py34
-rw-r--r--nova/tests/cells/test_cells_messaging.py43
-rw-r--r--nova/tests/cells/test_cells_rpcapi.py23
-rw-r--r--nova/tests/compute/test_compute.py84
-rw-r--r--nova/tests/compute/test_compute_cells.py8
-rw-r--r--nova/tests/consoleauth/test_consoleauth.py44
-rw-r--r--nova/tests/consoleauth/test_rpcapi.py12
-rw-r--r--nova/tests/test_db_api.py192
21 files changed, 558 insertions, 40 deletions
diff --git a/nova/api/openstack/compute/contrib/volumes.py b/nova/api/openstack/compute/contrib/volumes.py
index 93d76495f..b16d61852 100644
--- a/nova/api/openstack/compute/contrib/volumes.py
+++ b/nova/api/openstack/compute/contrib/volumes.py
@@ -402,6 +402,9 @@ class VolumeAttachmentController(wsgi.Controller):
volume_id, device)
except exception.NotFound:
raise exc.HTTPNotFound()
+ except exception.InstanceInvalidState as state_error:
+ common.raise_http_conflict_for_instance_invalid_state(state_error,
+ 'attach_volume')
# The attach is async
attachment = {}
@@ -446,12 +449,16 @@ class VolumeAttachmentController(wsgi.Controller):
raise exc.HTTPNotFound()
found = False
- for bdm in bdms:
- if bdm['volume_id'] == volume_id:
- self.compute_api.detach_volume(context,
- volume_id=volume_id)
- found = True
- break
+ try:
+ for bdm in bdms:
+ if bdm['volume_id'] == volume_id:
+ self.compute_api.detach_volume(context,
+ volume_id=volume_id)
+ found = True
+ break
+ except exception.InstanceInvalidState as state_error:
+ common.raise_http_conflict_for_instance_invalid_state(state_error,
+ 'detach_volume')
if not found:
raise exc.HTTPNotFound()
diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py
index 79382d864..91eeef4b1 100644
--- a/nova/api/openstack/wsgi.py
+++ b/nova/api/openstack/wsgi.py
@@ -588,7 +588,7 @@ class ResponseObject(object):
response = webob.Response()
response.status_int = self.code
for hdr, value in self._headers.items():
- response.headers[hdr] = value
+ response.headers[hdr] = str(value)
response.headers['Content-Type'] = content_type
if self.obj is not None:
response.body = serializer.serialize(self.obj)
@@ -1150,6 +1150,8 @@ class Fault(webob.exc.HTTPException):
def __init__(self, exception):
"""Create a Fault for the given webob.exc.exception."""
self.wrapped_exc = exception
+ for key, value in self.wrapped_exc.headers.items():
+ self.wrapped_exc.headers[key] = str(value)
self.status_int = exception.status_int
@webob.dec.wsgify(RequestClass=Request)
diff --git a/nova/cells/manager.py b/nova/cells/manager.py
index c08dfe835..04fabab54 100644
--- a/nova/cells/manager.py
+++ b/nova/cells/manager.py
@@ -66,7 +66,7 @@ class CellsManager(manager.Manager):
Scheduling requests get passed to the scheduler class.
"""
- RPC_API_VERSION = '1.5'
+ RPC_API_VERSION = '1.6'
def __init__(self, *args, **kwargs):
# Mostly for tests.
@@ -347,3 +347,18 @@ class CellsManager(manager.Manager):
response = self.msg_runner.action_events_get(ctxt, cell_name,
action_id)
return response.value_or_raise()
+
+ def consoleauth_delete_tokens(self, ctxt, instance_uuid):
+ """Delete consoleauth tokens for an instance in API cells."""
+ self.msg_runner.consoleauth_delete_tokens(ctxt, instance_uuid)
+
+ def validate_console_port(self, ctxt, instance_uuid, console_port,
+ console_type):
+ """Validate console port with child cell compute node."""
+ instance = self.db.instance_get_by_uuid(ctxt, instance_uuid)
+ if not instance['cell_name']:
+ raise exception.InstanceUnknownCell(instance_uuid=instance_uuid)
+ response = self.msg_runner.validate_console_port(ctxt,
+ instance['cell_name'], instance_uuid, console_port,
+ console_type)
+ return response.value_or_raise()
diff --git a/nova/cells/messaging.py b/nova/cells/messaging.py
index 82f0a6a48..02044ffd5 100644
--- a/nova/cells/messaging.py
+++ b/nova/cells/messaging.py
@@ -30,6 +30,8 @@ from oslo.config import cfg
from nova.cells import state as cells_state
from nova.cells import utils as cells_utils
from nova import compute
+from nova.compute import rpcapi as compute_rpcapi
+from nova.consoleauth import rpcapi as consoleauth_rpcapi
from nova import context
from nova.db import base
from nova import exception
@@ -599,6 +601,8 @@ class _BaseMessageMethods(base.Base):
self.msg_runner = msg_runner
self.state_manager = msg_runner.state_manager
self.compute_api = compute.API()
+ self.compute_rpcapi = compute_rpcapi.ComputeAPI()
+ self.consoleauth_rpcapi = consoleauth_rpcapi.ConsoleAuthAPI()
def task_log_get_all(self, message, task_name, period_beginning,
period_ending, host, state):
@@ -730,6 +734,25 @@ class _TargetedMessageMethods(_BaseMessageMethods):
action_events = self.db.action_events_get(message.ctxt, action_id)
return jsonutils.to_primitive(action_events)
+ def validate_console_port(self, message, instance_uuid, console_port,
+ console_type):
+ """Validate console port with child cell compute node."""
+ # 1st arg is instance_uuid that we need to turn into the
+ # instance object.
+ try:
+ instance = self.db.instance_get_by_uuid(message.ctxt,
+ instance_uuid)
+ except exception.InstanceNotFound:
+ with excutils.save_and_reraise_exception():
+ # Must be a race condition. Let's try to resolve it by
+ # telling the top level cells that this instance doesn't
+ # exist.
+ instance = {'uuid': instance_uuid}
+ self.msg_runner.instance_destroy_at_top(message.ctxt,
+ instance)
+ return self.compute_rpcapi.validate_console_port(message.ctxt,
+ instance, console_port, console_type)
+
class _BroadcastMessageMethods(_BaseMessageMethods):
"""These are the methods that can be called as a part of a broadcast
@@ -885,6 +908,13 @@ class _BroadcastMessageMethods(_BaseMessageMethods):
"""Return compute node stats from this cell."""
return self.db.compute_node_statistics(message.ctxt)
+ def consoleauth_delete_tokens(self, message, instance_uuid):
+ """Delete consoleauth tokens for an instance in API cells."""
+ if not self._at_the_top():
+ return
+ self.consoleauth_rpcapi.delete_tokens_for_instance(message.ctxt,
+ instance_uuid)
+
_CELL_MESSAGE_TYPE_TO_MESSAGE_CLS = {'targeted': _TargetedMessage,
'broadcast': _BroadcastMessage,
@@ -1224,6 +1254,24 @@ class MessageRunner(object):
cell_name, need_response=True)
return message.process()
+ def consoleauth_delete_tokens(self, ctxt, instance_uuid):
+ """Delete consoleauth tokens for an instance in API cells."""
+ message = _BroadcastMessage(self, ctxt, 'consoleauth_delete_tokens',
+ dict(instance_uuid=instance_uuid),
+ 'up', run_locally=False)
+ message.process()
+
+ def validate_console_port(self, ctxt, cell_name, instance_uuid,
+ console_port, console_type):
+ """Validate console port with child cell compute node."""
+ method_kwargs = {'instance_uuid': instance_uuid,
+ 'console_port': console_port,
+ 'console_type': console_type}
+ message = _TargetedMessage(self, ctxt, 'validate_console_port',
+ method_kwargs, 'down',
+ cell_name, need_response=True)
+ return message.process()
+
@staticmethod
def get_message_types():
return _CELL_MESSAGE_TYPE_TO_MESSAGE_CLS.keys()
diff --git a/nova/cells/rpcapi.py b/nova/cells/rpcapi.py
index be45e8f03..616dc0ec3 100644
--- a/nova/cells/rpcapi.py
+++ b/nova/cells/rpcapi.py
@@ -50,6 +50,7 @@ class CellsAPI(rpc_proxy.RpcProxy):
compute_node_stats()
1.5 - Adds actions_get(), action_get_by_request_id(), and
action_events_get()
+ 1.6 - Adds consoleauth_delete_tokens() and validate_console_port()
'''
BASE_RPC_API_VERSION = '1.0'
@@ -247,3 +248,19 @@ class CellsAPI(rpc_proxy.RpcProxy):
cell_name=instance['cell_name'],
action_id=action_id),
version='1.5')
+
+ def consoleauth_delete_tokens(self, ctxt, instance_uuid):
+ """Delete consoleauth tokens for an instance in API cells."""
+ self.cast(ctxt, self.make_msg('consoleauth_delete_tokens',
+ instance_uuid=instance_uuid),
+ version='1.6')
+
+ def validate_console_port(self, ctxt, instance_uuid, console_port,
+ console_type):
+ """Validate console port with child cell compute node."""
+ return self.call(ctxt,
+ self.make_msg('validate_console_port',
+ instance_uuid=instance_uuid,
+ console_port=console_port,
+ console_type=console_type),
+ version='1.6')
diff --git a/nova/compute/api.py b/nova/compute/api.py
index 4abb5e886..3bb4919f5 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -2285,6 +2285,10 @@ class API(base.Base):
@wrap_check_policy
@check_instance_lock
+ @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.PAUSED,
+ vm_states.SUSPENDED, vm_states.STOPPED,
+ vm_states.RESIZED, vm_states.SOFT_DELETED],
+ task_state=None)
def attach_volume(self, context, instance, volume_id, device=None):
"""Attach an existing volume to an existing instance."""
# NOTE(vish): Fail fast if the device is not going to pass. This
@@ -2314,6 +2318,10 @@ class API(base.Base):
return device
@check_instance_lock
+ @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.PAUSED,
+ vm_states.SUSPENDED, vm_states.STOPPED,
+ vm_states.RESIZED, vm_states.SOFT_DELETED],
+ task_state=None)
def _detach_volume(self, context, instance, volume_id):
check_policy(context, 'detach_volume', instance)
@@ -2364,7 +2372,7 @@ class API(base.Base):
@wrap_check_policy
@check_instance_lock
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.PAUSED,
- vm_states.SUSPENDED, vm_states.STOPPED],
+ vm_states.SUSPENDED, vm_states.STOPPED],
task_state=None)
def delete_instance_metadata(self, context, instance, key):
"""Delete the given metadata item from an instance."""
@@ -2378,7 +2386,7 @@ class API(base.Base):
@wrap_check_policy
@check_instance_lock
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.PAUSED,
- vm_states.SUSPENDED, vm_states.STOPPED],
+ vm_states.SUSPENDED, vm_states.STOPPED],
task_state=None)
def update_instance_metadata(self, context, instance,
metadata, delete=False):
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index ad82d31f2..604362783 100755
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -40,6 +40,7 @@ from eventlet import greenthread
from oslo.config import cfg
from nova import block_device
+from nova.cells import rpcapi as cells_rpcapi
from nova.cloudpipe import pipelib
from nova import compute
from nova.compute import instance_types
@@ -176,6 +177,7 @@ CONF.import_opt('host', 'nova.netconf')
CONF.import_opt('my_ip', 'nova.netconf')
CONF.import_opt('vnc_enabled', 'nova.vnc')
CONF.import_opt('enabled', 'nova.spice', group='spice')
+CONF.import_opt('enable', 'nova.cells.opts', group='cells')
LOG = logging.getLogger(__name__)
@@ -340,6 +342,7 @@ class ComputeManager(manager.SchedulerDependentManager):
self.is_quantum_security_groups = (
openstack_driver.is_quantum_security_groups())
self.consoleauth_rpcapi = consoleauth.rpcapi.ConsoleAuthAPI()
+ self.cells_rpcapi = cells_rpcapi.CellsAPI()
super(ComputeManager, self).__init__(service_name="compute",
*args, **kwargs)
@@ -1301,8 +1304,12 @@ class ComputeManager(manager.SchedulerDependentManager):
system_metadata=system_meta)
if CONF.vnc_enabled or CONF.spice.enabled:
- self.consoleauth_rpcapi.delete_tokens_for_instance(context,
- instance['uuid'])
+ if CONF.cells.enable:
+ self.cells_rpcapi.consoleauth_delete_tokens(context,
+ instance['uuid'])
+ else:
+ self.consoleauth_rpcapi.delete_tokens_for_instance(context,
+ instance['uuid'])
@exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
@wrap_instance_event
diff --git a/nova/compute/rpcapi.py b/nova/compute/rpcapi.py
index 62c1ed9a0..697c85eab 100644
--- a/nova/compute/rpcapi.py
+++ b/nova/compute/rpcapi.py
@@ -161,7 +161,7 @@ class ComputeAPI(nova.openstack.common.rpc.proxy.RpcProxy):
2.23 - Remove network_info from reboot_instance
2.24 - Added get_spice_console method
2.25 - Add attach_interface() and detach_interface()
- 2.26 - Add validate_console_token to ensure the service connects to
+ 2.26 - Add validate_console_port to ensure the service connects to
vnc on the correct port
2.27 - Adds 'reservations' to terminate_instance() and
soft_delete_instance()
@@ -329,8 +329,7 @@ class ComputeAPI(nova.openstack.common.rpc.proxy.RpcProxy):
instance_p = jsonutils.to_primitive(instance)
return self.call(ctxt, self.make_msg('validate_console_port',
instance=instance_p, port=port, console_type=console_type),
- topic=_compute_topic(self.topic, ctxt,
- None, instance),
+ topic=_compute_topic(self.topic, ctxt, None, instance),
version='2.26')
def host_maintenance_mode(self, ctxt, host_param, mode, host):
diff --git a/nova/consoleauth/manager.py b/nova/consoleauth/manager.py
index fe5bfd861..80a6d447f 100644
--- a/nova/consoleauth/manager.py
+++ b/nova/consoleauth/manager.py
@@ -22,6 +22,7 @@ import time
from oslo.config import cfg
+from nova.cells import rpcapi as cells_rpcapi
from nova.compute import rpcapi as compute_rpcapi
from nova.conductor import api as conductor_api
from nova import manager
@@ -43,6 +44,7 @@ consoleauth_opts = [
CONF = cfg.CONF
CONF.register_opts(consoleauth_opts)
+CONF.import_opt('enable', 'nova.cells.opts', group='cells')
class ConsoleAuthManager(manager.Manager):
@@ -53,8 +55,9 @@ class ConsoleAuthManager(manager.Manager):
def __init__(self, scheduler_driver=None, *args, **kwargs):
super(ConsoleAuthManager, self).__init__(*args, **kwargs)
self.mc = memorycache.get_client()
- self.compute_rpcapi = compute_rpcapi.ComputeAPI()
self.conductor_api = conductor_api.API()
+ self.compute_rpcapi = compute_rpcapi.ComputeAPI()
+ self.cells_rpcapi = cells_rpcapi.CellsAPI()
def _get_tokens_for_instance(self, instance_uuid):
tokens_str = self.mc.get(instance_uuid.encode('UTF-8'))
@@ -88,8 +91,16 @@ class ConsoleAuthManager(manager.Manager):
instance_uuid = token['instance_uuid']
if instance_uuid is None:
return False
+
+ # NOTE(comstud): consoleauth was meant to run in API cells. So,
+ # if cells is enabled, we must call down to the child cell for
+ # the instance.
+ if CONF.cells.enable:
+ return self.cells_rpcapi.validate_console_port(context,
+ instance_uuid, token['port'], token['console_type'])
+
instance = self.conductor_api.instance_get_by_uuid(context,
- instance_uuid)
+ instance_uuid)
return self.compute_rpcapi.validate_console_port(context,
instance,
token['port'],
diff --git a/nova/consoleauth/rpcapi.py b/nova/consoleauth/rpcapi.py
index 474f3ad19..9ab477340 100644
--- a/nova/consoleauth/rpcapi.py
+++ b/nova/consoleauth/rpcapi.py
@@ -67,7 +67,7 @@ class ConsoleAuthAPI(nova.openstack.common.rpc.proxy.RpcProxy):
return self.call(ctxt, self.make_msg('check_token', token=token))
def delete_tokens_for_instance(self, ctxt, instance_uuid):
- return self.call(ctxt,
+ return self.cast(ctxt,
self.make_msg('delete_tokens_for_instance',
instance_uuid=instance_uuid),
version="1.2")
diff --git a/nova/tests/api/openstack/compute/test_api.py b/nova/tests/api/openstack/compute/test_api.py
index 9061fd397..da1a4eece 100644
--- a/nova/tests/api/openstack/compute/test_api.py
+++ b/nova/tests/api/openstack/compute/test_api.py
@@ -157,7 +157,7 @@ class APITest(test.TestCase):
if hasattr(exception_type, 'headers'):
for (key, value) in exception_type.headers.iteritems():
self.assertTrue(key in resp.headers)
- self.assertEquals(resp.headers[key], value)
+ self.assertEquals(resp.headers[key], str(value))
def test_quota_error_mapping(self):
self._do_test_exception_mapping(exception.QuotaError, 'too many used')
diff --git a/nova/tests/api/openstack/test_faults.py b/nova/tests/api/openstack/test_faults.py
index 1bdb9f8c6..cdec44d23 100644
--- a/nova/tests/api/openstack/test_faults.py
+++ b/nova/tests/api/openstack/test_faults.py
@@ -68,6 +68,8 @@ class TestFaults(test.TestCase):
for request in requests:
exc = webob.exc.HTTPRequestEntityTooLarge
+ # NOTE(aloga): we intentionally pass an integer for the
+ # 'Retry-After' header. It should be then converted to a str
fault = wsgi.Fault(exc(explanation='sorry',
headers={'Retry-After': 4}))
response = request.get_response(fault)
@@ -76,7 +78,7 @@ class TestFaults(test.TestCase):
"overLimit": {
"message": "sorry",
"code": 413,
- "retryAfter": 4,
+ "retryAfter": "4",
},
}
actual = jsonutils.loads(response.body)
diff --git a/nova/tests/api/openstack/test_wsgi.py b/nova/tests/api/openstack/test_wsgi.py
index 374aa1162..a19204dff 100644
--- a/nova/tests/api/openstack/test_wsgi.py
+++ b/nova/tests/api/openstack/test_wsgi.py
@@ -867,6 +867,7 @@ class ResponseObjectTest(test.TestCase):
atom=AtomSerializer)
robj['X-header1'] = 'header1'
robj['X-header2'] = 'header2'
+ robj['X-header3'] = 3
for content_type, mtype in wsgi._MEDIA_TYPE_MAP.items():
request = wsgi.Request.blank('/tests/123')
@@ -875,6 +876,7 @@ class ResponseObjectTest(test.TestCase):
self.assertEqual(response.headers['Content-Type'], content_type)
self.assertEqual(response.headers['X-header1'], 'header1')
self.assertEqual(response.headers['X-header2'], 'header2')
+ self.assertEqual(response.headers['X-header3'], '3')
self.assertEqual(response.status_int, 202)
self.assertEqual(response.body, mtype)
diff --git a/nova/tests/cells/test_cells_manager.py b/nova/tests/cells/test_cells_manager.py
index 7dfb0fa02..74a7e9834 100644
--- a/nova/tests/cells/test_cells_manager.py
+++ b/nova/tests/cells/test_cells_manager.py
@@ -473,3 +473,37 @@ class CellsManagerClassTestCase(test.TestCase):
response = self.cells_manager.action_events_get(self.ctxt, 'fake-cell',
'fake-action')
self.assertEqual(expected_response, response)
+
+ def test_consoleauth_delete_tokens(self):
+ instance_uuid = 'fake-instance-uuid'
+
+ self.mox.StubOutWithMock(self.msg_runner,
+ 'consoleauth_delete_tokens')
+ self.msg_runner.consoleauth_delete_tokens(self.ctxt, instance_uuid)
+ self.mox.ReplayAll()
+ self.cells_manager.consoleauth_delete_tokens(self.ctxt,
+ instance_uuid=instance_uuid)
+
+ def test_validate_console_port(self):
+ instance_uuid = 'fake-instance-uuid'
+ cell_name = 'fake-cell-name'
+ instance = {'cell_name': cell_name}
+ console_port = 'fake-console-port'
+ console_type = 'fake-console-type'
+
+ self.mox.StubOutWithMock(self.msg_runner,
+ 'validate_console_port')
+ self.mox.StubOutWithMock(self.cells_manager.db,
+ 'instance_get_by_uuid')
+ fake_response = self._get_fake_response()
+
+ self.cells_manager.db.instance_get_by_uuid(self.ctxt,
+ instance_uuid).AndReturn(instance)
+ self.msg_runner.validate_console_port(self.ctxt, cell_name,
+ instance_uuid, console_port,
+ console_type).AndReturn(fake_response)
+ self.mox.ReplayAll()
+ response = self.cells_manager.validate_console_port(self.ctxt,
+ instance_uuid=instance_uuid, console_port=console_port,
+ console_type=console_type)
+ self.assertEqual('fake-response', response)
diff --git a/nova/tests/cells/test_cells_messaging.py b/nova/tests/cells/test_cells_messaging.py
index 3c7dd1941..bed27085f 100644
--- a/nova/tests/cells/test_cells_messaging.py
+++ b/nova/tests/cells/test_cells_messaging.py
@@ -593,6 +593,7 @@ class CellsTargetedMethodsTestCase(test.TestCase):
self.tgt_methods_cls = methods_cls
self.tgt_compute_api = methods_cls.compute_api
self.tgt_db_inst = methods_cls.db
+ self.tgt_c_rpcapi = methods_cls.compute_rpcapi
def test_schedule_run_instance(self):
host_sched_kwargs = {'filter_properties': {},
@@ -872,6 +873,28 @@ class CellsTargetedMethodsTestCase(test.TestCase):
result = response.value_or_raise()
self.assertEqual(fake_events, result)
+ def test_validate_console_port(self):
+ instance_uuid = 'fake_instance_uuid'
+ instance = {'uuid': instance_uuid}
+ console_port = 'fake-port'
+ console_type = 'fake-type'
+
+ self.mox.StubOutWithMock(self.tgt_c_rpcapi, 'validate_console_port')
+ self.mox.StubOutWithMock(self.tgt_db_inst, 'instance_get_by_uuid')
+
+ self.tgt_db_inst.instance_get_by_uuid(self.ctxt,
+ instance_uuid).AndReturn(instance)
+ self.tgt_c_rpcapi.validate_console_port(self.ctxt,
+ instance, console_port, console_type).AndReturn('fake_result')
+
+ self.mox.ReplayAll()
+
+ response = self.src_msg_runner.validate_console_port(self.ctxt,
+ self.tgt_cell_name, instance_uuid, console_port,
+ console_type)
+ result = response.value_or_raise()
+ self.assertEqual('fake_result', result)
+
class CellsBroadcastMethodsTestCase(test.TestCase):
"""Test case for _BroadcastMessageMethods class. Most of these
@@ -900,6 +923,7 @@ class CellsBroadcastMethodsTestCase(test.TestCase):
self.src_methods_cls = methods_cls
self.src_db_inst = methods_cls.db
self.src_compute_api = methods_cls.compute_api
+ self.src_ca_rpcapi = methods_cls.consoleauth_rpcapi
if not up:
# fudge things so we only have 1 child to broadcast to
@@ -913,12 +937,14 @@ class CellsBroadcastMethodsTestCase(test.TestCase):
self.mid_methods_cls = methods_cls
self.mid_db_inst = methods_cls.db
self.mid_compute_api = methods_cls.compute_api
+ self.mid_ca_rpcapi = methods_cls.consoleauth_rpcapi
self.tgt_msg_runner = fakes.get_message_runner(tgt_cell)
methods_cls = self.tgt_msg_runner.methods_by_type['broadcast']
self.tgt_methods_cls = methods_cls
self.tgt_db_inst = methods_cls.db
self.tgt_compute_api = methods_cls.compute_api
+ self.tgt_ca_rpcapi = methods_cls.consoleauth_rpcapi
def test_at_the_top(self):
self.assertTrue(self.tgt_methods_cls._at_the_top())
@@ -1283,3 +1309,20 @@ class CellsBroadcastMethodsTestCase(test.TestCase):
('api-cell!child-cell2', [3]),
('api-cell', [1, 2])]
self.assertEqual(expected, response_values)
+
+ def test_consoleauth_delete_tokens(self):
+ fake_uuid = 'fake-instance-uuid'
+
+ # To show these should not be called in src/mid-level cell
+ self.mox.StubOutWithMock(self.src_ca_rpcapi,
+ 'delete_tokens_for_instance')
+ self.mox.StubOutWithMock(self.mid_ca_rpcapi,
+ 'delete_tokens_for_instance')
+
+ self.mox.StubOutWithMock(self.tgt_ca_rpcapi,
+ 'delete_tokens_for_instance')
+ self.tgt_ca_rpcapi.delete_tokens_for_instance(self.ctxt, fake_uuid)
+
+ self.mox.ReplayAll()
+
+ self.src_msg_runner.consoleauth_delete_tokens(self.ctxt, fake_uuid)
diff --git a/nova/tests/cells/test_cells_rpcapi.py b/nova/tests/cells/test_cells_rpcapi.py
index c915a5bae..0a5c6a71b 100644
--- a/nova/tests/cells/test_cells_rpcapi.py
+++ b/nova/tests/cells/test_cells_rpcapi.py
@@ -360,3 +360,26 @@ class CellsAPITestCase(test.TestCase):
self.assertRaises(exception.InstanceUnknownCell,
self.cells_rpcapi.action_events_get,
self.fake_context, fake_instance, 'fake-action')
+
+ def test_consoleauth_delete_tokens(self):
+ call_info = self._stub_rpc_method('cast', None)
+
+ self.cells_rpcapi.consoleauth_delete_tokens(self.fake_context,
+ 'fake-uuid')
+
+ expected_args = {'instance_uuid': 'fake-uuid'}
+ self._check_result(call_info, 'consoleauth_delete_tokens',
+ expected_args, version='1.6')
+
+ def test_validate_console_port(self):
+ call_info = self._stub_rpc_method('call', 'fake_response')
+
+ result = self.cells_rpcapi.validate_console_port(self.fake_context,
+ 'fake-uuid', 'fake-port', 'fake-type')
+
+ expected_args = {'instance_uuid': 'fake-uuid',
+ 'console_port': 'fake-port',
+ 'console_type': 'fake-type'}
+ self._check_result(call_info, 'validate_console_port',
+ expected_args, version='1.6')
+ self.assertEqual(result, 'fake_response')
diff --git a/nova/tests/compute/test_compute.py b/nova/tests/compute/test_compute.py
index 570c7fdea..e92f1b41f 100644
--- a/nova/tests/compute/test_compute.py
+++ b/nova/tests/compute/test_compute.py
@@ -1966,6 +1966,26 @@ class ComputeTestCase(BaseTestCase):
self.assertTrue(self.tokens_deleted)
+ def test_delete_instance_deletes_console_auth_tokens_cells(self):
+ instance = self._create_fake_instance()
+ self.flags(vnc_enabled=True)
+ self.flags(enable=True, group='cells')
+
+ self.tokens_deleted = False
+
+ def fake_delete_tokens(*args, **kwargs):
+ self.tokens_deleted = True
+
+ cells_rpcapi = self.compute.cells_rpcapi
+ self.stubs.Set(cells_rpcapi, 'consoleauth_delete_tokens',
+ fake_delete_tokens)
+
+ self.compute._delete_instance(self.context,
+ instance=jsonutils.to_primitive(instance),
+ bdms={})
+
+ self.assertTrue(self.tokens_deleted)
+
def test_instance_termination_exception_sets_error(self):
"""Test that we handle InstanceTerminationFailure
which is propagated up from the underlying driver.
@@ -5437,6 +5457,14 @@ class ComputeAPITestCase(BaseTestCase):
self.assertEqual(instance_properties['progress'], 0)
self.assertIn('host2', filter_properties['ignore_hosts'])
+ def _noop(*args, **kwargs):
+ pass
+
+ self.stubs.Set(self.compute.cells_rpcapi,
+ 'consoleauth_delete_tokens', _noop)
+ self.stubs.Set(self.compute.consoleauth_rpcapi,
+ 'delete_tokens_for_instance', _noop)
+
self.stubs.Set(rpc, 'cast', _fake_cast)
instance = self._create_fake_instance(dict(host='host2'))
@@ -5472,6 +5500,14 @@ class ComputeAPITestCase(BaseTestCase):
self.assertEqual(instance_properties['progress'], 0)
self.assertNotIn('host2', filter_properties['ignore_hosts'])
+ def _noop(*args, **kwargs):
+ pass
+
+ self.stubs.Set(self.compute.cells_rpcapi,
+ 'consoleauth_delete_tokens', _noop)
+ self.stubs.Set(self.compute.consoleauth_rpcapi,
+ 'delete_tokens_for_instance', _noop)
+
self.stubs.Set(rpc, 'cast', _fake_cast)
self.flags(allow_resize_to_same_host=True)
@@ -6157,10 +6193,56 @@ class ComputeAPITestCase(BaseTestCase):
self.assertRaises(exception.InvalidDevicePath,
self.compute_api.attach_volume,
self.context,
- {'locked': False},
+ {'locked': False, 'vm_state': vm_states.ACTIVE},
None,
'/invalid')
+ def test_no_attach_volume_in_rescue_state(self):
+ def fake(*args, **kwargs):
+ pass
+
+ def fake_volume_get(self, context, volume_id):
+ return {'id': volume_id}
+
+ self.stubs.Set(cinder.API, 'get', fake_volume_get)
+ self.stubs.Set(cinder.API, 'check_attach', fake)
+ self.stubs.Set(cinder.API, 'reserve_volume', fake)
+
+ self.assertRaises(exception.InstanceInvalidState,
+ self.compute_api.attach_volume,
+ self.context,
+ {'uuid': 'fake_uuid', 'locked': False,
+ 'vm_state': vm_states.RESCUED},
+ None,
+ '/dev/vdb')
+
+ def test_no_detach_volume_in_rescue_state(self):
+ # Ensure volume can be detached from instance
+
+ params = {'vm_state': vm_states.RESCUED}
+ instance = self._create_fake_instance(params=params)
+
+ def fake(*args, **kwargs):
+ pass
+
+ def fake_volume_get(self, context, volume_id):
+ pass
+ return {'id': volume_id, 'attach_status': 'in-use',
+ 'instance_uuid': instance['uuid']}
+
+ def fake_rpc_detach_volume(self, context, **kwargs):
+ pass
+
+ self.stubs.Set(cinder.API, 'get', fake_volume_get)
+ self.stubs.Set(cinder.API, 'check_detach', fake)
+ self.stubs.Set(cinder.API, 'begin_detaching', fake)
+ self.stubs.Set(compute_rpcapi.ComputeAPI, 'detach_volume',
+ fake_rpc_detach_volume)
+
+ self.assertRaises(exception.InstanceInvalidState,
+ self.compute_api.detach_volume,
+ self.context, 1)
+
def test_vnc_console(self):
# Make sure we can a vnc console for an instance.
diff --git a/nova/tests/compute/test_compute_cells.py b/nova/tests/compute/test_compute_cells.py
index 78100bcc3..5070690ad 100644
--- a/nova/tests/compute/test_compute_cells.py
+++ b/nova/tests/compute/test_compute_cells.py
@@ -18,6 +18,8 @@ Tests For Compute w/ Cells
"""
import functools
+from oslo.config import cfg
+
from nova.compute import api as compute_api
from nova.compute import cells_api as compute_cells_api
from nova import db
@@ -31,6 +33,7 @@ from nova.tests.compute import test_compute
LOG = logging.getLogger('nova.tests.test_compute_cells')
ORIG_COMPUTE_API = None
+cfg.CONF.import_opt('enable', 'nova.cells.opts', group='cells')
def stub_call_to_cells(context, instance, method, *args, **kwargs):
@@ -112,6 +115,7 @@ class CellsComputeAPITestCase(test_compute.ComputeAPITestCase):
super(CellsComputeAPITestCase, self).setUp()
global ORIG_COMPUTE_API
ORIG_COMPUTE_API = self.compute_api
+ self.flags(enable=True, group='cells')
def _fake_cell_read_only(*args, **kwargs):
return False
@@ -190,6 +194,10 @@ class CellsComputeAPITestCase(test_compute.ComputeAPITestCase):
self.skipTest("This test is failing due to TypeError: "
"detach_volume() takes exactly 3 arguments (4 given).")
+ def test_no_detach_volume_in_rescue_state(self):
+ self.skipTest("This test is failing due to TypeError: "
+ "detach_volume() takes exactly 3 arguments (4 given).")
+
def test_evacuate(self):
self.skipTest("Test is incompatible with cells.")
diff --git a/nova/tests/consoleauth/test_consoleauth.py b/nova/tests/consoleauth/test_consoleauth.py
index 9b9b0c92a..024434c0a 100644
--- a/nova/tests/consoleauth/test_consoleauth.py
+++ b/nova/tests/consoleauth/test_consoleauth.py
@@ -43,11 +43,7 @@ class ConsoleauthTestCase(test.TestCase):
token = 'mytok'
self.flags(console_token_ttl=1)
- def fake_validate_console_port(*args, **kwargs):
- return True
- self.stubs.Set(self.manager.compute_rpcapi,
- "validate_console_port",
- fake_validate_console_port)
+ self._stub_validate_console_port(True)
self.manager.authorize_console(self.context, token, 'novnc',
'127.0.0.1', '8080', 'host',
@@ -56,16 +52,20 @@ class ConsoleauthTestCase(test.TestCase):
timeutils.advance_time_seconds(1)
self.assertFalse(self.manager.check_token(self.context, token))
+ def _stub_validate_console_port(self, result):
+ def fake_validate_console_port(ctxt, instance, port, console_type):
+ return result
+
+ self.stubs.Set(self.manager.compute_rpcapi,
+ 'validate_console_port',
+ fake_validate_console_port)
+
def test_multiple_tokens_for_instance(self):
tokens = ["token" + str(i) for i in xrange(10)]
instance = "12345"
- def fake_validate_console_port(*args, **kwargs):
- return True
+ self._stub_validate_console_port(True)
- self.stubs.Set(self.manager.compute_rpcapi,
- "validate_console_port",
- fake_validate_console_port)
for token in tokens:
self.manager.authorize_console(self.context, token, 'novnc',
'127.0.0.1', '8080', 'host',
@@ -92,12 +92,7 @@ class ConsoleauthTestCase(test.TestCase):
def test_wrong_token_has_port(self):
token = 'mytok'
- def fake_validate_console_port(*args, **kwargs):
- return False
-
- self.stubs.Set(self.manager.compute_rpcapi,
- "validate_console_port",
- fake_validate_console_port)
+ self._stub_validate_console_port(False)
self.manager.authorize_console(self.context, token, 'novnc',
'127.0.0.1', '8080', 'host',
@@ -114,3 +109,20 @@ class ConsoleauthTestCase(test.TestCase):
self.manager.backdoor_port = 59697
port = self.manager.get_backdoor_port(self.context)
self.assertEqual(port, self.manager.backdoor_port)
+
+
+class CellsConsoleauthTestCase(ConsoleauthTestCase):
+ """Test Case for consoleauth w/ cells enabled."""
+
+ def setUp(self):
+ super(CellsConsoleauthTestCase, self).setUp()
+ self.flags(enable=True, group='cells')
+
+ def _stub_validate_console_port(self, result):
+ def fake_validate_console_port(ctxt, instance_uuid, console_port,
+ console_type):
+ return result
+
+ self.stubs.Set(self.manager.cells_rpcapi,
+ 'validate_console_port',
+ fake_validate_console_port)
diff --git a/nova/tests/consoleauth/test_rpcapi.py b/nova/tests/consoleauth/test_rpcapi.py
index 53ca2e5d6..308c0d812 100644
--- a/nova/tests/consoleauth/test_rpcapi.py
+++ b/nova/tests/consoleauth/test_rpcapi.py
@@ -30,6 +30,7 @@ CONF = cfg.CONF
class ConsoleAuthRpcAPITestCase(test.TestCase):
def _test_consoleauth_api(self, method, **kwargs):
+ do_cast = kwargs.pop('_do_cast', False)
ctxt = context.RequestContext('fake_user', 'fake_project')
rpcapi = consoleauth_rpcapi.ConsoleAuthAPI()
expected_retval = 'foo'
@@ -45,18 +46,22 @@ class ConsoleAuthRpcAPITestCase(test.TestCase):
self.call_msg = None
self.call_timeout = None
- def _fake_call(_ctxt, _topic, _msg, _timeout):
+ def _fake_call(_ctxt, _topic, _msg, _timeout=None):
self.call_ctxt = _ctxt
self.call_topic = _topic
self.call_msg = _msg
self.call_timeout = _timeout
return expected_retval
- self.stubs.Set(rpc, 'call', _fake_call)
+ if do_cast:
+ self.stubs.Set(rpc, 'cast', _fake_call)
+ else:
+ self.stubs.Set(rpc, 'call', _fake_call)
retval = getattr(rpcapi, method)(ctxt, **kwargs)
- self.assertEqual(retval, expected_retval)
+ if not do_cast:
+ self.assertEqual(retval, expected_retval)
self.assertEqual(self.call_ctxt, ctxt)
self.assertEqual(self.call_topic, CONF.consoleauth_topic)
self.assertEqual(self.call_msg, expected_msg)
@@ -73,6 +78,7 @@ class ConsoleAuthRpcAPITestCase(test.TestCase):
def test_delete_tokens_for_instnace(self):
self._test_consoleauth_api('delete_tokens_for_instance',
+ _do_cast=True,
instance_uuid="instance",
version='1.2')
diff --git a/nova/tests/test_db_api.py b/nova/tests/test_db_api.py
index 23fff5cc4..4b62d22b7 100644
--- a/nova/tests/test_db_api.py
+++ b/nova/tests/test_db_api.py
@@ -1655,6 +1655,198 @@ class MigrationTestCase(test.TestCase):
self.assertEqual(migration['instance_uuid'], instance['uuid'])
+class ModelsObjectComparatorMixin(object):
+ def _dict_from_object(self, obj, ignored_keys):
+ if ignored_keys is None:
+ ignored_keys = []
+ return dict([(k, v) for k, v in obj.iteritems()
+ if k not in ignored_keys])
+
+ def _assertEqualObjects(self, obj1, obj2, ignored_keys=None):
+ obj1 = self._dict_from_object(obj1, ignored_keys)
+ obj2 = self._dict_from_object(obj2, ignored_keys)
+
+ self.assertEqual(len(obj1), len(obj2))
+ for key, value in obj1.iteritems():
+ self.assertEqual(value, obj2[key])
+
+ def _assertEqualListsOfObjects(self, objs1, objs2, ignored_keys=None):
+ self.assertEqual(len(objs1), len(objs2))
+ objs2 = dict([(o['id'], o) for o in objs2])
+ for o1 in objs1:
+ self._assertEqualObjects(o1, objs2[o1['id']], ignored_keys)
+
+
+class ServiceTestCase(test.TestCase, ModelsObjectComparatorMixin):
+ def setUp(self):
+ super(ServiceTestCase, self).setUp()
+ self.ctxt = context.get_admin_context()
+
+ def _get_base_values(self):
+ return {
+ 'host': 'fake_host',
+ 'binary': 'fake_binary',
+ 'topic': 'fake_topic',
+ 'report_count': 3,
+ 'disabled': False
+ }
+
+ def _create_service(self, values):
+ v = self._get_base_values()
+ v.update(values)
+ return db.service_create(self.ctxt, v)
+
+ def test_service_create(self):
+ service = self._create_service({})
+ self.assertFalse(service['id'] is None)
+ for key, value in self._get_base_values().iteritems():
+ self.assertEqual(value, service[key])
+
+ def test_service_destroy(self):
+ service1 = self._create_service({})
+ service2 = self._create_service({'host': 'fake_host2'})
+
+ db.service_destroy(self.ctxt, service1['id'])
+ self.assertRaises(exception.ServiceNotFound,
+ db.service_get, self.ctxt, service1['id'])
+ self._assertEqualObjects(db.service_get(self.ctxt, service2['id']),
+ service2, ignored_keys=['compute_node'])
+
+ def test_service_update(self):
+ service = self._create_service({})
+ new_values = {
+ 'host': 'fake_host1',
+ 'binary': 'fake_binary1',
+ 'topic': 'fake_topic1',
+ 'report_count': 4,
+ 'disabled': True
+ }
+ db.service_update(self.ctxt, service['id'], new_values)
+ updated_service = db.service_get(self.ctxt, service['id'])
+ for key, value in new_values.iteritems():
+ self.assertEqual(value, updated_service[key])
+
+ def test_service_update_not_found_exception(self):
+ self.assertRaises(exception.ServiceNotFound,
+ db.service_update, self.ctxt, 100500, {})
+
+ def test_service_get(self):
+ service1 = self._create_service({})
+ service2 = self._create_service({'host': 'some_other_fake_host'})
+ real_service1 = db.service_get(self.ctxt, service1['id'])
+ self._assertEqualObjects(service1, real_service1,
+ ignored_keys=['compute_node'])
+
+ def test_service_get_with_compute_node(self):
+ service = self._create_service({})
+ compute_values = dict(vcpus=2, memory_mb=1024, local_gb=2048,
+ vcpus_used=0, memory_mb_used=0,
+ local_gb_used=0, free_ram_mb=1024,
+ free_disk_gb=2048, hypervisor_type="xen",
+ hypervisor_version=1, cpu_info="",
+ running_vms=0, current_workload=0,
+ service_id=service['id'])
+ compute = db.compute_node_create(self.ctxt, compute_values)
+ real_service = db.service_get(self.ctxt, service['id'])
+ real_compute = real_service['compute_node'][0]
+ self.assertEqual(compute['id'], real_compute['id'])
+
+ def test_service_get_not_found_exception(self):
+ self.assertRaises(exception.ServiceNotFound,
+ db.service_get, self.ctxt, 100500)
+
+ def test_service_get_by_host_and_topic(self):
+ service1 = self._create_service({'host': 'host1', 'topic': 'topic1'})
+ service2 = self._create_service({'host': 'host2', 'topic': 'topic2'})
+
+ real_service1 = db.service_get_by_host_and_topic(self.ctxt,
+ host='host1',
+ topic='topic1')
+ self._assertEqualObjects(service1, real_service1)
+
+ def test_service_get_all(self):
+ values = [
+ {'host': 'host1', 'topic': 'topic1'},
+ {'host': 'host2', 'topic': 'topic2'},
+ {'disabled': True}
+ ]
+ services = [self._create_service(vals) for vals in values]
+ disabled_services = [services[-1]]
+ non_disabled_services = services[:-1]
+
+ compares = [
+ (services, db.service_get_all(self.ctxt)),
+ (disabled_services, db.service_get_all(self.ctxt, True)),
+ (non_disabled_services, db.service_get_all(self.ctxt, False))
+ ]
+ for comp in compares:
+ self._assertEqualListsOfObjects(*comp)
+
+ def test_service_get_all_by_topic(self):
+ values = [
+ {'host': 'host1', 'topic': 't1'},
+ {'host': 'host2', 'topic': 't1'},
+ {'disabled': True, 'topic': 't1'},
+ {'host': 'host3', 'topic': 't2'}
+ ]
+ services = [self._create_service(vals) for vals in values]
+ expected = services[:2]
+ real = db.service_get_all_by_topic(self.ctxt, 't1')
+ self._assertEqualListsOfObjects(expected, real)
+
+ def test_service_get_all_by_host(self):
+ values = [
+ {'host': 'host1', 'topic': 't1'},
+ {'host': 'host1', 'topic': 't1'},
+ {'host': 'host2', 'topic': 't1'},
+ {'host': 'host3', 'topic': 't2'}
+ ]
+ services = [self._create_service(vals) for vals in values]
+
+ expected = services[:2]
+ real = db.service_get_all_by_host(self.ctxt, 'host1')
+ self._assertEqualListsOfObjects(expected, real)
+
+ def test_service_get_by_compute_host(self):
+ values = [
+ {'host': 'host1', 'topic': CONF.compute_topic},
+ {'host': 'host2', 'topic': 't1'},
+ {'host': 'host3', 'topic': CONF.compute_topic}
+ ]
+ services = [self._create_service(vals) for vals in values]
+
+ real_service = db.service_get_by_compute_host(self.ctxt, 'host1')
+ self._assertEqualObjects(services[0], real_service,
+ ignored_keys=['compute_node'])
+
+ self.assertRaises(exception.ComputeHostNotFound,
+ db.service_get_by_compute_host,
+ self.ctxt, 'non-exists-host')
+
+ def test_service_get_by_compute_host_not_found(self):
+ self.assertRaises(exception.ComputeHostNotFound,
+ db.service_get_by_compute_host,
+ self.ctxt, 'non-exists-host')
+
+ def test_service_get_by_args(self):
+ values = [
+ {'host': 'host1', 'binary': 'a'},
+ {'host': 'host2', 'binary': 'b'}
+ ]
+ services = [self._create_service(vals) for vals in values]
+
+ service1 = db.service_get_by_args(self.ctxt, 'host1', 'a')
+ self._assertEqualObjects(services[0], service1)
+
+ service2 = db.service_get_by_args(self.ctxt, 'host2', 'b')
+ self._assertEqualObjects(services[1], service2)
+
+ def test_service_get_by_args_not_found_exception(self):
+ self.assertRaises(exception.HostBinaryNotFound,
+ db.service_get_by_args,
+ self.ctxt, 'non-exists-host', 'a')
+
+
class TestFixedIPGetByNetworkHost(test.TestCase):
def test_not_found_exception(self):
ctxt = context.get_admin_context()