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
This commit is contained in:
Andrew Laski 2013-02-21 17:30:06 -05:00
parent 73a58f9cc8
commit ab2920726c
13 changed files with 297 additions and 5 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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