diff options
author | Andrew Laski <andrew.laski@rackspace.com> | 2013-02-21 17:30:06 -0500 |
---|---|---|
committer | Andrew Laski <andrew.laski@rackspace.com> | 2013-02-25 19:33:02 -0500 |
commit | ab2920726c0e2633c033a31a324f30a97fdce6bd (patch) | |
tree | 2c93e8a84654049f3420b19a537a0bd392f68001 | |
parent | 73a58f9cc85bf8d7fc745d4260577d764e8bc81c (diff) | |
download | nova-ab2920726c0e2633c033a31a324f30a97fdce6bd.tar.gz nova-ab2920726c0e2633c033a31a324f30a97fdce6bd.tar.xz nova-ab2920726c0e2633c033a31a324f30a97fdce6bd.zip |
Rework instance actions to work with cells
In a cells setup an instance action is recorded at the global cell level
while events try to get recorded in a child cell compute node or
scheduler. The event recording fails because it can't find an action to
link to. This patch adds the recording of actions at the child cell
level, and changes the API extension to query the db in a child cell for
the record of actions and events.
This does not address the fact that an action is recorded at the global
cell level.
Bug 1132935
Change-Id: I5831f146397e7afa2d93d26c5d6f9abb9bc6670d
-rw-r--r-- | nova/api/openstack/compute/contrib/instance_actions.py | 10 | ||||
-rw-r--r-- | nova/cells/manager.py | 18 | ||||
-rw-r--r-- | nova/cells/messaging.py | 36 | ||||
-rw-r--r-- | nova/cells/rpcapi.py | 28 | ||||
-rw-r--r-- | nova/cells/scheduler.py | 11 | ||||
-rw-r--r-- | nova/compute/__init__.py | 12 | ||||
-rw-r--r-- | nova/compute/api.py | 14 | ||||
-rw-r--r-- | nova/compute/cells_api.py | 18 | ||||
-rw-r--r-- | nova/tests/api/openstack/compute/contrib/test_instance_actions.py | 5 | ||||
-rw-r--r-- | nova/tests/cells/test_cells_manager.py | 44 | ||||
-rw-r--r-- | nova/tests/cells/test_cells_messaging.py | 47 | ||||
-rw-r--r-- | nova/tests/cells/test_cells_rpcapi.py | 55 | ||||
-rw-r--r-- | nova/tests/integrated/test_api_samples.py | 4 |
13 files changed, 297 insertions, 5 deletions
diff --git a/nova/api/openstack/compute/contrib/instance_actions.py b/nova/api/openstack/compute/contrib/instance_actions.py index ecacde7bf..4eaa9a1ee 100644 --- a/nova/api/openstack/compute/contrib/instance_actions.py +++ b/nova/api/openstack/compute/contrib/instance_actions.py @@ -19,7 +19,6 @@ from nova.api.openstack import extensions from nova.api.openstack import wsgi from nova.api.openstack import xmlutil from nova import compute -from nova import db authorize_actions = extensions.extension_authorizer('compute', 'instance_actions') @@ -67,6 +66,7 @@ class InstanceActionsController(wsgi.Controller): def __init__(self): super(InstanceActionsController, self).__init__() self.compute_api = compute.API() + self.action_api = compute.InstanceActionAPI() def _format_action(self, action_raw): action = {} @@ -86,7 +86,7 @@ class InstanceActionsController(wsgi.Controller): context = req.environ["nova.context"] instance = self.compute_api.get(context, server_id) authorize_actions(context, target=instance) - actions_raw = db.actions_get(context, server_id) + actions_raw = self.action_api.actions_get(context, instance) actions = [self._format_action(action) for action in actions_raw] return {'instanceActions': actions} @@ -96,14 +96,16 @@ class InstanceActionsController(wsgi.Controller): context = req.environ['nova.context'] instance = self.compute_api.get(context, server_id) authorize_actions(context, target=instance) - action = db.action_get_by_request_id(context, server_id, id) + action = self.action_api.action_get_by_request_id(context, instance, + id) if action is None: raise exc.HTTPNotFound() action_id = action['id'] action = self._format_action(action) if authorize_events(context): - events_raw = db.action_events_get(context, action_id) + events_raw = self.action_api.action_events_get(context, instance, + action_id) action['events'] = [self._format_event(evt) for evt in events_raw] return {'instanceAction': action} diff --git a/nova/cells/manager.py b/nova/cells/manager.py index 55b3dadf7..ec4bc447f 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.4' + RPC_API_VERSION = '1.5' def __init__(self, *args, **kwargs): # Mostly for tests. @@ -332,3 +332,19 @@ class CellsManager(manager.Manager): totals.setdefault(key, 0) totals[key] += val return totals + + def actions_get(self, ctxt, cell_name, instance_uuid): + response = self.msg_runner.actions_get(ctxt, cell_name, instance_uuid) + return response.value_or_raise() + + def action_get_by_request_id(self, ctxt, cell_name, instance_uuid, + request_id): + response = self.msg_runner.action_get_by_request_id(ctxt, cell_name, + instance_uuid, + request_id) + return response.value_or_raise() + + def action_events_get(self, ctxt, cell_name, action_id): + response = self.msg_runner.action_events_get(ctxt, cell_name, + action_id) + return response.value_or_raise() diff --git a/nova/cells/messaging.py b/nova/cells/messaging.py index 50a673464..db49cc8fd 100644 --- a/nova/cells/messaging.py +++ b/nova/cells/messaging.py @@ -717,6 +717,19 @@ class _TargetedMessageMethods(_BaseMessageMethods): compute_id) return jsonutils.to_primitive(compute_node) + def actions_get(self, message, instance_uuid): + actions = self.db.actions_get(message.ctxt, instance_uuid) + return jsonutils.to_primitive(actions) + + def action_get_by_request_id(self, message, instance_uuid, request_id): + action = self.db.action_get_by_request_id(message.ctxt, instance_uuid, + request_id) + return jsonutils.to_primitive(action) + + def action_events_get(self, message, action_id): + action_events = self.db.action_events_get(message.ctxt, action_id) + return jsonutils.to_primitive(action_events) + class _BroadcastMessageMethods(_BaseMessageMethods): """These are the methods that can be called as a part of a broadcast @@ -1183,6 +1196,29 @@ class MessageRunner(object): cell_name, need_response=True) return message.process() + def actions_get(self, ctxt, cell_name, instance_uuid): + method_kwargs = dict(instance_uuid=instance_uuid) + message = _TargetedMessage(self, ctxt, 'actions_get', + method_kwargs, 'down', + cell_name, need_response=True) + return message.process() + + def action_get_by_request_id(self, ctxt, cell_name, instance_uuid, + request_id): + method_kwargs = dict(instance_uuid=instance_uuid, + request_id=request_id) + message = _TargetedMessage(self, ctxt, 'action_get_by_request_id', + method_kwargs, 'down', + cell_name, need_response=True) + return message.process() + + def action_events_get(self, ctxt, cell_name, action_id): + method_kwargs = dict(action_id=action_id) + message = _TargetedMessage(self, ctxt, 'action_events_get', + 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 910c4ab9d..be45e8f03 100644 --- a/nova/cells/rpcapi.py +++ b/nova/cells/rpcapi.py @@ -24,6 +24,7 @@ messging module. from oslo.config import cfg +from nova import exception from nova.openstack.common import jsonutils from nova.openstack.common import log as logging from nova.openstack.common.rpc import proxy as rpc_proxy @@ -47,6 +48,8 @@ class CellsAPI(rpc_proxy.RpcProxy): 1.3 - Adds task_log_get_all() 1.4 - Adds compute_node_get(), compute_node_get_all(), and compute_node_stats() + 1.5 - Adds actions_get(), action_get_by_request_id(), and + action_events_get() ''' BASE_RPC_API_VERSION = '1.0' @@ -219,3 +222,28 @@ class CellsAPI(rpc_proxy.RpcProxy): """Return compute node stats from all cells.""" return self.call(ctxt, self.make_msg('compute_node_stats'), version='1.4') + + def actions_get(self, ctxt, instance): + if not instance['cell_name']: + raise exception.InstanceUnknownCell(instance_uuid=instance['uuid']) + return self.call(ctxt, self.make_msg('actions_get', + cell_name=instance['cell_name'], + instance_uuid=instance['uuid']), + version='1.5') + + def action_get_by_request_id(self, ctxt, instance, request_id): + if not instance['cell_name']: + raise exception.InstanceUnknownCell(instance_uuid=instance['uuid']) + return self.call(ctxt, self.make_msg('action_get_by_request_id', + cell_name=instance['cell_name'], + instance_uuid=instance['uuid'], + request_id=request_id), + version='1.5') + + def action_events_get(self, ctxt, instance, action_id): + if not instance['cell_name']: + raise exception.InstanceUnknownCell(instance_uuid=instance['uuid']) + return self.call(ctxt, self.make_msg('action_events_get', + cell_name=instance['cell_name'], + action_id=action_id), + version='1.5') diff --git a/nova/cells/scheduler.py b/nova/cells/scheduler.py index 3b69b2eac..8ec65b2d0 100644 --- a/nova/cells/scheduler.py +++ b/nova/cells/scheduler.py @@ -22,6 +22,8 @@ import time from oslo.config import cfg from nova import compute +from nova.compute import instance_actions +from nova.compute import utils as compute_utils from nova.compute import vm_states from nova.db import base from nova import exception @@ -70,6 +72,12 @@ class CellsScheduler(base.Base): self.msg_runner.instance_update_at_top(ctxt, instance) + def _create_action_here(self, ctxt, instance_uuids): + for instance_uuid in instance_uuids: + action = compute_utils.pack_action_start(ctxt, instance_uuid, + instance_actions.CREATE) + self.db.action_start(ctxt, action) + def _get_possible_cells(self): cells = set(self.state_manager.get_child_cells()) our_cell = self.state_manager.get_my_state() @@ -102,6 +110,9 @@ class CellsScheduler(base.Base): # Need to create instance DB entries as the host scheduler # expects that the instance(s) already exists. self._create_instances_here(ctxt, request_spec) + # Need to record the create action in the db as the scheduler + # expects it to already exist. + self._create_action_here(ctxt, request_spec['instance_uuids']) self.scheduler_rpcapi.run_instance(ctxt, **host_sched_kwargs) return diff --git a/nova/compute/__init__.py b/nova/compute/__init__.py index f1451aab3..baa957ca8 100644 --- a/nova/compute/__init__.py +++ b/nova/compute/__init__.py @@ -48,3 +48,15 @@ def HostAPI(*args, **kwargs): compute_api_class = importutils.import_class(compute_api_class_name) class_name = compute_api_class.__module__ + ".HostAPI" return importutils.import_object(class_name, *args, **kwargs) + + +def InstanceActionAPI(*args, **kwargs): + """ + Returns the 'InstanceActionAPI' class from the same module as the + configured compute api. + """ + importutils = nova.openstack.common.importutils + compute_api_class_name = oslo.config.cfg.CONF.compute_api_class + compute_api_class = importutils.import_class(compute_api_class_name) + class_name = compute_api_class.__module__ + ".InstanceActionAPI" + return importutils.import_object(class_name, *args, **kwargs) diff --git a/nova/compute/api.py b/nova/compute/api.py index bba6ee1eb..d45ee6703 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -2557,6 +2557,20 @@ class HostAPI(base.Base): return self.db.compute_node_statistics(context) +class InstanceActionAPI(base.Base): + """Sub-set of the Compute Manager API for managing instance actions.""" + + def actions_get(self, context, instance): + return self.db.actions_get(context, instance['uuid']) + + def action_get_by_request_id(self, context, instance, request_id): + return self.db.action_get_by_request_id(context, instance['uuid'], + request_id) + + def action_events_get(self, context, instance, action_id): + return self.db.action_events_get(context, action_id) + + class AggregateAPI(base.Base): """Sub-set of the Compute Manager API for managing host aggregates.""" def __init__(self, **kwargs): diff --git a/nova/compute/cells_api.py b/nova/compute/cells_api.py index 22e31a8e1..32f09ee90 100644 --- a/nova/compute/cells_api.py +++ b/nova/compute/cells_api.py @@ -667,3 +667,21 @@ class HostAPI(compute_api.HostAPI): def compute_node_statistics(self, context): return self.cells_rpcapi.compute_node_stats(context) + + +class InstanceActionAPI(compute_api.InstanceActionAPI): + """InstanceActionAPI() class for cells.""" + def __init__(self): + super(InstanceActionAPI, self).__init__() + self.cells_rpcapi = cells_rpcapi.CellsAPI() + + def actions_get(self, context, instance): + return self.cells_rpcapi.actions_get(context, instance) + + def action_get_by_request_id(self, context, instance, request_id): + return self.cells_rpcapi.action_get_by_request_id(context, instance, + request_id) + + def action_events_get(self, context, instance, action_id): + return self.cells_rpcapi.action_events_get(context, instance, + action_id) diff --git a/nova/tests/api/openstack/compute/contrib/test_instance_actions.py b/nova/tests/api/openstack/compute/contrib/test_instance_actions.py index 8650275a7..573385a52 100644 --- a/nova/tests/api/openstack/compute/contrib/test_instance_actions.py +++ b/nova/tests/api/openstack/compute/contrib/test_instance_actions.py @@ -20,6 +20,7 @@ from lxml import etree from webob import exc from nova.api.openstack.compute.contrib import instance_actions +from nova.compute import api as compute_api from nova import db from nova.db.sqlalchemy import models from nova import exception @@ -92,9 +93,13 @@ class InstanceActionsTest(test.TestCase): self.fake_actions = copy.deepcopy(fake_instance_actions.FAKE_ACTIONS) self.fake_events = copy.deepcopy(fake_instance_actions.FAKE_EVENTS) + def fake_get(self, context, instance_uuid): + return {'uuid': instance_uuid} + def fake_instance_get_by_uuid(context, instance_id): return {'name': 'fake', 'project_id': context.project_id} + self.stubs.Set(compute_api.API, 'get', fake_get) self.stubs.Set(db, 'instance_get_by_uuid', fake_instance_get_by_uuid) def test_list_actions(self): diff --git a/nova/tests/cells/test_cells_manager.py b/nova/tests/cells/test_cells_manager.py index d53fdbb7f..7dfb0fa02 100644 --- a/nova/tests/cells/test_cells_manager.py +++ b/nova/tests/cells/test_cells_manager.py @@ -27,6 +27,7 @@ from nova.openstack.common import rpc from nova.openstack.common import timeutils from nova import test from nova.tests.cells import fakes +from nova.tests import fake_instance_actions CONF = cfg.CONF CONF.import_opt('compute_topic', 'nova.compute.rpcapi') @@ -429,3 +430,46 @@ class CellsManagerClassTestCase(test.TestCase): response = self.cells_manager.compute_node_get(self.ctxt, compute_id=cell_and_id) self.assertEqual(expected_response, response) + + def test_actions_get(self): + fake_uuid = fake_instance_actions.FAKE_UUID + fake_req_id = fake_instance_actions.FAKE_REQUEST_ID1 + fake_act = fake_instance_actions.FAKE_ACTIONS[fake_uuid][fake_req_id] + fake_response = messaging.Response('fake-cell', [fake_act], False) + expected_response = [fake_act] + self.mox.StubOutWithMock(self.msg_runner, 'actions_get') + self.msg_runner.actions_get(self.ctxt, 'fake-cell', + 'fake-uuid').AndReturn(fake_response) + self.mox.ReplayAll() + response = self.cells_manager.actions_get(self.ctxt, 'fake-cell', + 'fake-uuid') + self.assertEqual(expected_response, response) + + def test_action_get_by_request_id(self): + fake_uuid = fake_instance_actions.FAKE_UUID + fake_req_id = fake_instance_actions.FAKE_REQUEST_ID1 + fake_act = fake_instance_actions.FAKE_ACTIONS[fake_uuid][fake_req_id] + fake_response = messaging.Response('fake-cell', fake_act, False) + expected_response = fake_act + self.mox.StubOutWithMock(self.msg_runner, 'action_get_by_request_id') + self.msg_runner.action_get_by_request_id(self.ctxt, 'fake-cell', + 'fake-uuid', 'req-fake').AndReturn(fake_response) + self.mox.ReplayAll() + response = self.cells_manager.action_get_by_request_id(self.ctxt, + 'fake-cell', + 'fake-uuid', + 'req-fake') + self.assertEqual(expected_response, response) + + def test_action_events_get(self): + fake_action_id = fake_instance_actions.FAKE_ACTION_ID1 + fake_events = fake_instance_actions.FAKE_EVENTS[fake_action_id] + fake_response = messaging.Response('fake-cell', fake_events, False) + expected_response = fake_events + self.mox.StubOutWithMock(self.msg_runner, 'action_events_get') + self.msg_runner.action_events_get(self.ctxt, 'fake-cell', + 'fake-action').AndReturn(fake_response) + self.mox.ReplayAll() + response = self.cells_manager.action_events_get(self.ctxt, 'fake-cell', + 'fake-action') + self.assertEqual(expected_response, response) diff --git a/nova/tests/cells/test_cells_messaging.py b/nova/tests/cells/test_cells_messaging.py index 30adfdcd7..effe27660 100644 --- a/nova/tests/cells/test_cells_messaging.py +++ b/nova/tests/cells/test_cells_messaging.py @@ -25,6 +25,7 @@ from nova.openstack.common import rpc from nova.openstack.common import timeutils from nova import test from nova.tests.cells import fakes +from nova.tests import fake_instance_actions CONF = cfg.CONF CONF.import_opt('name', 'nova.cells.opts', group='cells') @@ -825,6 +826,52 @@ class CellsTargetedMethodsTestCase(test.TestCase): result = response.value_or_raise() self.assertEqual('fake_result', result) + def test_actions_get(self): + fake_uuid = fake_instance_actions.FAKE_UUID + fake_req_id = fake_instance_actions.FAKE_REQUEST_ID1 + fake_act = fake_instance_actions.FAKE_ACTIONS[fake_uuid][fake_req_id] + + self.mox.StubOutWithMock(self.tgt_db_inst, 'actions_get') + self.tgt_db_inst.actions_get(self.ctxt, + 'fake-uuid').AndReturn([fake_act]) + self.mox.ReplayAll() + + response = self.src_msg_runner.actions_get(self.ctxt, + self.tgt_cell_name, + 'fake-uuid') + result = response.value_or_raise() + self.assertEqual([fake_act], result) + + def test_action_get_by_request_id(self): + fake_uuid = fake_instance_actions.FAKE_UUID + fake_req_id = fake_instance_actions.FAKE_REQUEST_ID1 + fake_act = fake_instance_actions.FAKE_ACTIONS[fake_uuid][fake_req_id] + + self.mox.StubOutWithMock(self.tgt_db_inst, 'action_get_by_request_id') + self.tgt_db_inst.action_get_by_request_id(self.ctxt, + 'fake-uuid', 'req-fake').AndReturn(fake_act) + self.mox.ReplayAll() + + response = self.src_msg_runner.action_get_by_request_id(self.ctxt, + self.tgt_cell_name, 'fake-uuid', 'req-fake') + result = response.value_or_raise() + self.assertEqual(fake_act, result) + + def test_action_events_get(self): + fake_action_id = fake_instance_actions.FAKE_ACTION_ID1 + fake_events = fake_instance_actions.FAKE_EVENTS[fake_action_id] + + self.mox.StubOutWithMock(self.tgt_db_inst, 'action_events_get') + self.tgt_db_inst.action_events_get(self.ctxt, + 'fake-action').AndReturn(fake_events) + self.mox.ReplayAll() + + response = self.src_msg_runner.action_events_get(self.ctxt, + self.tgt_cell_name, + 'fake-action') + result = response.value_or_raise() + self.assertEqual(fake_events, result) + class CellsBroadcastMethodsTestCase(test.TestCase): """Test case for _BroadcastMessageMethods class. Most of these diff --git a/nova/tests/cells/test_cells_rpcapi.py b/nova/tests/cells/test_cells_rpcapi.py index f00b1b290..c915a5bae 100644 --- a/nova/tests/cells/test_cells_rpcapi.py +++ b/nova/tests/cells/test_cells_rpcapi.py @@ -19,6 +19,7 @@ Tests For Cells RPCAPI from oslo.config import cfg from nova.cells import rpcapi as cells_rpcapi +from nova import exception from nova.openstack.common import rpc from nova import test @@ -305,3 +306,57 @@ class CellsAPITestCase(test.TestCase): self._check_result(call_info, 'compute_node_get', expected_args, version='1.4') self.assertEqual(result, 'fake_response') + + def test_actions_get(self): + fake_instance = {'uuid': 'fake-uuid', 'cell_name': 'region!child'} + call_info = self._stub_rpc_method('call', 'fake_response') + result = self.cells_rpcapi.actions_get(self.fake_context, + fake_instance) + expected_args = {'cell_name': 'region!child', + 'instance_uuid': fake_instance['uuid']} + self._check_result(call_info, 'actions_get', expected_args, + version='1.5') + self.assertEqual(result, 'fake_response') + + def test_actions_get_no_cell(self): + fake_instance = {'uuid': 'fake-uuid', 'cell_name': None} + self.assertRaises(exception.InstanceUnknownCell, + self.cells_rpcapi.actions_get, self.fake_context, + fake_instance) + + def test_action_get_by_request_id(self): + fake_instance = {'uuid': 'fake-uuid', 'cell_name': 'region!child'} + call_info = self._stub_rpc_method('call', 'fake_response') + result = self.cells_rpcapi.action_get_by_request_id(self.fake_context, + fake_instance, + 'req-fake') + expected_args = {'cell_name': 'region!child', + 'instance_uuid': fake_instance['uuid'], + 'request_id': 'req-fake'} + self._check_result(call_info, 'action_get_by_request_id', + expected_args, version='1.5') + self.assertEqual(result, 'fake_response') + + def test_action_get_by_request_id_no_cell(self): + fake_instance = {'uuid': 'fake-uuid', 'cell_name': None} + self.assertRaises(exception.InstanceUnknownCell, + self.cells_rpcapi.action_get_by_request_id, + self.fake_context, fake_instance, 'req-fake') + + def test_action_events_get(self): + fake_instance = {'uuid': 'fake-uuid', 'cell_name': 'region!child'} + call_info = self._stub_rpc_method('call', 'fake_response') + result = self.cells_rpcapi.action_events_get(self.fake_context, + fake_instance, + 'fake-action') + expected_args = {'cell_name': 'region!child', + 'action_id': 'fake-action'} + self._check_result(call_info, 'action_events_get', expected_args, + version='1.5') + self.assertEqual(result, 'fake_response') + + def test_action_events_get_no_cell(self): + fake_instance = {'uuid': 'fake-uuid', 'cell_name': None} + self.assertRaises(exception.InstanceUnknownCell, + self.cells_rpcapi.action_events_get, + self.fake_context, fake_instance, 'fake-action') diff --git a/nova/tests/integrated/test_api_samples.py b/nova/tests/integrated/test_api_samples.py index aaf22ff9d..d48ef5091 100644 --- a/nova/tests/integrated/test_api_samples.py +++ b/nova/tests/integrated/test_api_samples.py @@ -3165,12 +3165,16 @@ class InstanceActionsSampleJsonTest(ApiSampleTestBase): def fake_instance_get_by_uuid(context, instance_id): return self.instance + def fake_get(self, context, instance_uuid): + return {'uuid': instance_uuid} + self.stubs.Set(db, 'action_get_by_request_id', fake_instance_action_get_by_request_id) self.stubs.Set(db, 'actions_get', fake_instance_actions_get) self.stubs.Set(db, 'action_events_get', fake_instance_action_events_get) self.stubs.Set(db, 'instance_get_by_uuid', fake_instance_get_by_uuid) + self.stubs.Set(compute_api.API, 'get', fake_get) def test_instance_action_get(self): fake_uuid = fake_instance_actions.FAKE_UUID |