From 845c1deb3803d1ae2496d9af3fd0f192aee176fa Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Tue, 6 Aug 2013 08:18:04 -0700 Subject: [PATCH] Add basic InstanceAction object This adds a basic InstanceAction object with the ability to provide the functionality needed to satisfy compute_api's use of actions. The ability to finish an action by uuid in a sort of out-of-body way (as it is done now) is provided, as well as an instance method for a (hopefully) future scenario where actions are started, carried, and finished when appropriate. Related to blueprint compute-api-objects Change-Id: I094645b065575bbbce35ed18bf6901cbbf4e383d --- nova/objects/instance_action.py | 171 ++++++++++++++ nova/tests/objects/test_instance_action.py | 248 +++++++++++++++++++++ 2 files changed, 419 insertions(+) create mode 100644 nova/objects/instance_action.py create mode 100644 nova/tests/objects/test_instance_action.py diff --git a/nova/objects/instance_action.py b/nova/objects/instance_action.py new file mode 100644 index 000000000000..404572974421 --- /dev/null +++ b/nova/objects/instance_action.py @@ -0,0 +1,171 @@ +# Copyright 2013 IBM Corp. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from nova.compute import utils as compute_utils +from nova import db +from nova.objects import base +from nova.objects import utils + + +class InstanceAction(base.NovaObject): + fields = { + 'id': int, + 'action': utils.str_or_none, + 'instance_uuid': utils.str_or_none, + 'request_id': utils.str_or_none, + 'user_id': utils.str_or_none, + 'project_id': utils.str_or_none, + 'start_time': utils.datetime_or_none, + 'finish_time': utils.datetime_or_none, + 'message': utils.str_or_none, + } + + _attr_start_time_to_primitive = utils.dt_serializer('start_time') + _attr_finish_time_to_primitive = utils.dt_serializer('finish_time') + _attr_start_time_from_primitive = utils.dt_deserializer + _attr_finish_time_from_primitive = utils.dt_deserializer + + @staticmethod + def _from_db_object(context, action, db_action): + for field in action.fields: + action[field] = db_action[field] + action._context = context + action.obj_reset_changes() + return action + + @base.remotable_classmethod + def get_by_request_id(cls, context, instance_uuid, request_id): + db_action = db.action_get_by_request_id(context, instance_uuid, + request_id) + if db_action: + return cls._from_db_object(context, cls(), db_action) + + # NOTE(danms): Eventually the compute_utils.*action* methods + # can be here, I think + + @base.remotable_classmethod + def action_start(cls, context, instance_uuid, action_name, + want_result=True): + values = compute_utils.pack_action_start(context, instance_uuid, + action_name) + db_action = db.action_start(context, values) + if want_result: + return cls._from_db_object(context, cls(), db_action) + + @base.remotable_classmethod + def action_finish(cls, context, instance_uuid, want_result=True): + values = compute_utils.pack_action_finish(context, instance_uuid) + db_action = db.action_finish(context, values) + if want_result: + return cls._from_db_object(context, cls(), db_action) + + @base.remotable + def finish(self, context): + values = compute_utils.pack_action_finish(context, self.instance_uuid) + db_action = db.action_finish(context, values) + self._from_db_object(context, self, db_action) + + +def _make_list(context, list_obj, item_cls, db_list): + list_obj.objects = [] + for db_item in db_list: + item = item_cls._from_db_object(context, item_cls(), db_item) + list_obj.objects.append(item) + list_obj.obj_reset_changes() + return list_obj + + +class InstanceActionList(base.ObjectListBase, base.NovaObject): + @base.remotable_classmethod + def get_by_instance_uuid(cls, context, instance_uuid): + db_actions = db.actions_get(context, instance_uuid) + return _make_list(context, cls(), InstanceAction, db_actions) + + +class InstanceActionEvent(base.NovaObject): + fields = { + 'id': int, + 'event': utils.str_or_none, + 'action_id': utils.int_or_none, + 'start_time': utils.datetime_or_none, + 'finish_time': utils.datetime_or_none, + 'result': utils.str_or_none, + 'traceback': utils.str_or_none, + } + + _attr_start_time_to_primitive = utils.dt_serializer('start_time') + _attr_finish_time_to_primitive = utils.dt_serializer('finish_time') + _attr_start_time_from_primitive = utils.dt_deserializer + _attr_finish_time_from_primitive = utils.dt_deserializer + + @staticmethod + def _from_db_object(context, event, db_event): + for field in event.fields: + event[field] = db_event[field] + event._context = context + event.obj_reset_changes() + return event + + @base.remotable_classmethod + def get_by_id(cls, context, action_id, event_id): + db_event = db.action_event_get_by_id(context, action_id, event_id) + return cls._from_db_object(context, cls(), db_event) + + @base.remotable_classmethod + def event_start(cls, context, instance_uuid, event_name, want_result=True): + values = compute_utils.pack_action_event_start(context, instance_uuid, + event_name) + db_event = db.action_event_start(context, values) + if want_result: + return cls._from_db_object(context, cls(), db_event) + + @base.remotable_classmethod + def event_finish_with_failure(cls, context, instance_uuid, event_name, + exc_val=None, exc_tb=None, want_result=None): + values = compute_utils.pack_action_event_finish(context, instance_uuid, + event_name, + exc_val=exc_val, + exc_tb=exc_tb) + db_event = db.action_event_finish(context, values) + if want_result: + return cls._from_db_object(context, cls(), db_event) + + @base.remotable_classmethod + def event_finish(cls, context, instance_uuid, event_name, + want_result=True): + return cls.event_finish_with_failure(context, instance_uuid, + event_name, exc_val=None, + exc_tb=None, + want_result=want_result) + + @base.remotable + def finish_with_failure(self, context, exc_val, exc_tb): + values = compute_utils.pack_action_event_finish(context, + self.instance_uuid, + self.event, + exc_val=exc_val, + exc_tb=exc_tb) + db_event = db.action_event_finish(context, values) + self._from_db_object(context, self, db_event) + + @base.remotable + def finish(self, context): + self.finish_with_failure(context, exc_val=None, exc_tb=None) + + +class InstanceActionEventList(base.ObjectListBase, base.NovaObject): + @base.remotable_classmethod + def get_by_action(cls, context, action_id): + db_events = db.action_events_get(context, action_id) + return _make_list(context, cls(), InstanceActionEvent, db_events) diff --git a/nova/tests/objects/test_instance_action.py b/nova/tests/objects/test_instance_action.py new file mode 100644 index 000000000000..4e54ea58527b --- /dev/null +++ b/nova/tests/objects/test_instance_action.py @@ -0,0 +1,248 @@ +# Copyright 2013 IBM Corp. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from nova.compute import utils as compute_utils +from nova import context +from nova import db +from nova.objects import instance_action +from nova.openstack.common import timeutils +from nova.tests.objects import test_objects + + +NOW = timeutils.utcnow() +fake_action = { + 'created_at': NOW, + 'deleted_at': None, + 'updated_at': None, + 'deleted': False, + 'id': 123, + 'action': 'fake-action', + 'instance_uuid': 'fake-uuid', + 'request_id': 'fake-request', + 'user_id': 'fake-user', + 'project_id': 'fake-project', + 'start_time': NOW, + 'finish_time': None, + 'message': 'foo', +} +fake_event = { + 'created_at': NOW, + 'deleted_at': None, + 'updated_at': None, + 'deleted': False, + 'id': 123, + 'event': 'fake-event', + 'action_id': 123, + 'start_time': NOW, + 'finish_time': None, + 'result': 'fake-result', + 'traceback': 'fake-tb', +} + + +class _TestInstanceActionObject(object): + def test_get_by_request_id(self): + ctxt = context.get_admin_context() + self.mox.StubOutWithMock(db, 'action_get_by_request_id') + db.action_get_by_request_id(ctxt, 'fake-uuid', 'fake-request' + ).AndReturn(fake_action) + self.mox.ReplayAll() + action = instance_action.InstanceAction.get_by_request_id( + ctxt, 'fake-uuid', 'fake-request') + self.assertEqual(fake_action['id'], action.id) + + def test_action_start(self): + ctxt = context.get_admin_context() + self.mox.StubOutWithMock(db, 'action_start') + db.action_start(ctxt, compute_utils.pack_action_start( + ctxt, 'fake-uuid', 'fake-action')).AndReturn(fake_action) + self.mox.ReplayAll() + action = instance_action.InstanceAction.action_start( + ctxt, 'fake-uuid', 'fake-action') + self.assertEqual(fake_action['id'], action.id) + + def test_action_start_no_result(self): + ctxt = context.get_admin_context() + self.mox.StubOutWithMock(db, 'action_start') + db.action_start(ctxt, compute_utils.pack_action_start( + ctxt, 'fake-uuid', 'fake-action')).AndReturn(fake_action) + self.mox.ReplayAll() + action = instance_action.InstanceAction.action_start( + ctxt, 'fake-uuid', 'fake-action', want_result=False) + self.assertEqual(None, action) + + def test_action_finish(self): + timeutils.set_time_override() + ctxt = context.get_admin_context() + self.mox.StubOutWithMock(db, 'action_finish') + db.action_finish(ctxt, compute_utils.pack_action_finish( + ctxt, 'fake-uuid')).AndReturn(fake_action) + self.mox.ReplayAll() + action = instance_action.InstanceAction.action_finish( + ctxt, 'fake-uuid', want_result=True) + self.assertEqual(fake_action['id'], action.id) + + def test_action_finish_no_result(self): + timeutils.set_time_override() + ctxt = context.get_admin_context() + self.mox.StubOutWithMock(db, 'action_finish') + db.action_finish(ctxt, compute_utils.pack_action_finish( + ctxt, 'fake-uuid')).AndReturn(fake_action) + self.mox.ReplayAll() + action = instance_action.InstanceAction.action_finish( + ctxt, 'fake-uuid', want_result=False) + self.assertEqual(None, action) + + def test_finish(self): + timeutils.set_time_override() + ctxt = context.get_admin_context() + self.mox.StubOutWithMock(db, 'action_start') + self.mox.StubOutWithMock(db, 'action_finish') + db.action_start(ctxt, compute_utils.pack_action_start( + ctxt, 'fake-uuid', 'fake-action')).AndReturn(fake_action) + db.action_finish(ctxt, compute_utils.pack_action_finish( + ctxt, 'fake-uuid')).AndReturn(fake_action) + self.mox.ReplayAll() + action = instance_action.InstanceAction.action_start( + ctxt, 'fake-uuid', 'fake-action') + action.finish() + + def test_get_list(self): + timeutils.set_time_override() + ctxt = context.get_admin_context() + self.mox.StubOutWithMock(db, 'actions_get') + actions = [dict(fake_action, id=1234), + dict(fake_action, id=5678)] + db.actions_get(ctxt, 'fake-uuid').AndReturn(actions) + self.mox.ReplayAll() + action_list = instance_action.InstanceActionList.get_by_instance_uuid( + ctxt, 'fake-uuid') + self.assertEqual(2, len(action_list)) + for index, action in enumerate(action_list): + self.assertEqual(actions[index]['id'], action.id) + + +class TestInstanceActionObject(test_objects._LocalTest, + _TestInstanceActionObject): + pass + + +class TestRemoteInstanceActionObject(test_objects._RemoteTest, + _TestInstanceActionObject): + pass + + +class _TestInstanceActionEventObject(object): + def test_get_by_id(self): + ctxt = context.get_admin_context() + self.mox.StubOutWithMock(db, 'action_event_get_by_id') + db.action_event_get_by_id(ctxt, 'fake-id').AndReturn(fake_event) + self.mox.ReplayAll() + event = instance_action.InstanceActionEvent.get_by_id(ctxt, 'fake-id') + self.assertEqual(fake_event['id'], event.id) + + def test_event_start(self): + ctxt = context.get_admin_context() + self.mox.StubOutWithMock(db, 'action_event_start') + db.action_event_start( + ctxt, + compute_utils.pack_action_event_start( + context, 'fake-uuid', 'fake-event')).AndReturn(fake_event) + self.mox.ReplayAll() + event = instance_action.InstanceActionEvent.event_start(ctxt, + 'fake-uuid', + 'fake-event') + self.assertEqual(fake_event['id'], event.id) + + def test_event_start_no_result(self): + ctxt = context.get_admin_context() + self.mox.StubOutWithMock(db, 'action_event_start') + db.action_event_start( + ctxt, + compute_utils.pack_action_event_start( + 'fake-uuid', 'fake-event')).AndReturn(fake_event) + self.mox.ReplayAll() + event = instance_action.InstanceActionEvent.event_start( + ctxt, 'fake-uuid', 'fake-event', want_result=False) + self.assertEqual(None, event) + + def test_event_finish(self): + ctxt = context.get_admin_context() + self.mox.StubOutWithMock(db, 'action_event_finish') + db.action_event_start( + ctxt, + compute_utils.pack_action_event_finish( + context, 'fake-uuid', 'fake-event', exc_val=None, exc_tb=None + )).AndReturn(fake_event) + self.mox.ReplayAll() + event = instance_action.InstanceActionEvent.event_finish(ctxt, + 'fake-uuid', + 'fake-event') + self.assertEqual(fake_event['id'], event.id) + + def test_event_finish_no_result(self): + ctxt = context.get_admin_context() + self.mox.StubOutWithMock(db, 'action_event_finish') + db.action_event_start( + ctxt, + compute_utils.pack_action_event_finish( + context, 'fake-uuid', 'fake-event', exc_val=None, exc_tb=None + )).AndReturn(fake_event) + self.mox.ReplayAll() + event = instance_action.InstanceActionEvent.event_finish( + ctxt, 'fake-uuid', 'fake-event', want_result=False) + self.assertEqual(None, event) + + def test_event_finish_with_failure(self): + ctxt = context.get_admin_context() + self.mox.StubOutWithMock(db, 'action_event_finish') + db.action_event_start( + ctxt, + compute_utils.pack_action_event_finish( + context, 'fake-uuid', 'fake-event', exc_val='fake-exc', + exc_tb='fake-tb')).AndReturn(fake_event) + self.mox.ReplayAll() + event = instance_action.InstanceActionEvent.event_finish_with_failure( + ctxt, 'fake-uuid', 'fake-event', 'fake-exc', 'fake-tb') + self.assertEqual(fake_event['id'], event.id) + + def test_finish(self): + ctxt = context.get_admin_context() + self.mox.StubOutWithMock(db, 'action_event_finish') + db.action_event_start( + ctxt, + compute_utils.pack_action_event_start( + context, 'fake-uuid', 'fake-event')).AndReturn(fake_event) + db.action_event_finish( + ctxt, + compute_utils.pack_action_event_finish( + context, 'fake-uuid', 'fake-event', exc_val=None, + exc_tb=None)).AndReturn(fake_event) + self.mox.ReplayAll() + event = instance_action.InstanceActionEvent.event_start( + ctxt, 'fake-uuid', 'fake-event') + event.finish() + + def test_get_by_action(self): + ctxt = context.get_admin_context() + self.mox.StubOutWithMock(db, 'action_events_get') + events = [dict(fake_event, id=1234), + dict(fake_event, id=5678)] + db.action_events_get(ctxt, 'fake-action').AndReturn(events) + self.mox.ReplayAll() + event_list = instance_action.InstanceActionEventList.get_by_action( + ctxt, 'fake-action') + self.assertEqual(2, len(event_list)) + for index, event in enumerate(event_list): + self.assertEqual(events[index]['id'], event.id)