diff options
-rw-r--r-- | nova/api/openstack/compute/contrib/volumes.py | 19 | ||||
-rw-r--r-- | nova/api/openstack/wsgi.py | 4 | ||||
-rw-r--r-- | nova/cells/manager.py | 17 | ||||
-rw-r--r-- | nova/cells/messaging.py | 48 | ||||
-rw-r--r-- | nova/cells/rpcapi.py | 17 | ||||
-rw-r--r-- | nova/compute/api.py | 12 | ||||
-rwxr-xr-x | nova/compute/manager.py | 11 | ||||
-rw-r--r-- | nova/compute/rpcapi.py | 5 | ||||
-rw-r--r-- | nova/consoleauth/manager.py | 15 | ||||
-rw-r--r-- | nova/consoleauth/rpcapi.py | 2 | ||||
-rw-r--r-- | nova/tests/api/openstack/compute/test_api.py | 2 | ||||
-rw-r--r-- | nova/tests/api/openstack/test_faults.py | 4 | ||||
-rw-r--r-- | nova/tests/api/openstack/test_wsgi.py | 2 | ||||
-rw-r--r-- | nova/tests/cells/test_cells_manager.py | 34 | ||||
-rw-r--r-- | nova/tests/cells/test_cells_messaging.py | 43 | ||||
-rw-r--r-- | nova/tests/cells/test_cells_rpcapi.py | 23 | ||||
-rw-r--r-- | nova/tests/compute/test_compute.py | 84 | ||||
-rw-r--r-- | nova/tests/compute/test_compute_cells.py | 8 | ||||
-rw-r--r-- | nova/tests/consoleauth/test_consoleauth.py | 44 | ||||
-rw-r--r-- | nova/tests/consoleauth/test_rpcapi.py | 12 | ||||
-rw-r--r-- | nova/tests/test_db_api.py | 192 |
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() |