From 1950537a53c8dcb4d98cf71708c071eda1d954c2 Mon Sep 17 00:00:00 2001 From: Yikun Jiang Date: Thu, 7 Dec 2017 09:33:04 -0500 Subject: [PATCH] Add instance action db and obj pagination support. This will be used by instance action pagination API. Add limit/marker/filters support to get_by_instance_uuid of InstanceActionList object, also add limit/marker/filters support to actions_get method of db. Part of blueprint pagination-add-changes-since-for-instance-action-list Change-Id: Ic7dd6480a4b250ae6529d94ee0386b5e95b0ca04 --- nova/db/api.py | 7 ++- nova/db/sqlalchemy/api.py | 30 ++++++++-- nova/objects/instance_action.py | 10 ++-- .../api_sample_tests/test_instance_actions.py | 3 +- .../compute/test_instance_actions.py | 3 +- nova/tests/unit/db/test_db_api.py | 58 +++++++++++++++++++ .../unit/objects/test_instance_action.py | 3 +- nova/tests/unit/objects/test_objects.py | 2 +- 8 files changed, 99 insertions(+), 17 deletions(-) diff --git a/nova/db/api.py b/nova/db/api.py index 9e5c9a649ac7..1f4cdbb63e34 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -1953,9 +1953,10 @@ def action_finish(context, values): return IMPL.action_finish(context, values) -def actions_get(context, uuid): - """Get all instance actions for the provided instance.""" - return IMPL.actions_get(context, uuid) +def actions_get(context, instance_uuid, limit=None, marker=None, + filters=None): + """Get all instance actions for the provided instance and filters.""" + return IMPL.actions_get(context, instance_uuid, limit, marker, filters) def action_get_by_request_id(context, uuid, request_id): diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index edc9aa6bba54..cc5e6c75b7fa 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -6227,12 +6227,30 @@ def action_finish(context, values): @pick_context_manager_reader -def actions_get(context, instance_uuid): - """Get all instance actions for the provided uuid.""" - actions = model_query(context, models.InstanceAction).\ - filter_by(instance_uuid=instance_uuid).\ - order_by(desc("created_at"), desc("id")).\ - all() +def actions_get(context, instance_uuid, limit=None, marker=None, + filters=None): + """Get all instance actions for the provided uuid and filters.""" + if limit == 0: + return [] + + sort_keys = ['created_at', 'id'] + sort_dirs = ['desc', 'desc'] + + query_prefix = model_query(context, models.InstanceAction).\ + filter_by(instance_uuid=instance_uuid) + if filters and 'changes-since' in filters: + changes_since = timeutils.normalize_time(filters['changes-since']) + query_prefix = query_prefix. \ + filter(models.InstanceAction.updated_at >= changes_since) + + if marker is not None: + marker = action_get_by_request_id(context, instance_uuid, marker) + if not marker: + raise exception.MarkerNotFound(marker=marker) + actions = sqlalchemyutils.paginate_query(query_prefix, + models.InstanceAction, limit, + sort_keys, marker=marker, + sort_dirs=sort_dirs).all() return actions diff --git a/nova/objects/instance_action.py b/nova/objects/instance_action.py index 8cc3a214cbac..0341cdbda099 100644 --- a/nova/objects/instance_action.py +++ b/nova/objects/instance_action.py @@ -100,15 +100,17 @@ class InstanceAction(base.NovaPersistentObject, base.NovaObject, @base.NovaObjectRegistry.register class InstanceActionList(base.ObjectListBase, base.NovaObject): # Version 1.0: Initial version - # InstanceAction <= version 1.1 - VERSION = '1.0' + # Version 1.1: get_by_instance_uuid added pagination and filters support + VERSION = '1.1' fields = { 'objects': fields.ListOfObjectsField('InstanceAction'), } @base.remotable_classmethod - def get_by_instance_uuid(cls, context, instance_uuid): - db_actions = db.actions_get(context, instance_uuid) + def get_by_instance_uuid(cls, context, instance_uuid, limit=None, + marker=None, filters=None): + db_actions = db.actions_get( + context, instance_uuid, limit, marker, filters) return base.obj_make_list(context, cls(), InstanceAction, db_actions) diff --git a/nova/tests/functional/api_sample_tests/test_instance_actions.py b/nova/tests/functional/api_sample_tests/test_instance_actions.py index 36c93fa45325..c5f8cb09929d 100644 --- a/nova/tests/functional/api_sample_tests/test_instance_actions.py +++ b/nova/tests/functional/api_sample_tests/test_instance_actions.py @@ -42,7 +42,8 @@ class ServerActionsSampleJsonTest(api_sample_base.ApiSampleTestBaseV21): def fake_instance_action_get_by_request_id(context, uuid, request_id): return copy.deepcopy(self.actions[uuid][request_id]) - def fake_server_actions_get(context, uuid): + def fake_server_actions_get(context, uuid, limit=None, marker=None, + filters=None): return [copy.deepcopy(value) for value in six.itervalues(self.actions[uuid])] diff --git a/nova/tests/unit/api/openstack/compute/test_instance_actions.py b/nova/tests/unit/api/openstack/compute/test_instance_actions.py index d217ef650b2a..631c62cf155b 100644 --- a/nova/tests/unit/api/openstack/compute/test_instance_actions.py +++ b/nova/tests/unit/api/openstack/compute/test_instance_actions.py @@ -133,7 +133,8 @@ class InstanceActionsTestV21(test.NoDBTestCase): policy.set_rules(oslo_policy.Rules.from_dict(rules)) def test_list_actions(self): - def fake_get_actions(context, uuid): + def fake_get_actions(context, uuid, limit=None, marker=None, + filters=None): actions = [] for act in six.itervalues(self.fake_actions[uuid]): action = models.InstanceAction() diff --git a/nova/tests/unit/db/test_db_api.py b/nova/tests/unit/db/test_db_api.py index 02e0cf6352d2..b05c5044f9f9 100644 --- a/nova/tests/unit/db/test_db_api.py +++ b/nova/tests/unit/db/test_db_api.py @@ -4080,6 +4080,64 @@ class InstanceActionTestCase(test.TestCase, ModelsObjectComparatorMixin): self._assertEqualOrderedListOfObjects([action2, action1], actions) + def test_instance_actions_get_with_limit(self): + """Test list instance actions can support pagination.""" + uuid1 = uuidsentinel.uuid1 + + extra = { + 'created_at': timeutils.utcnow() + } + + action_values = self._create_action_values(uuid1, extra=extra) + action1 = db.action_start(self.ctxt, action_values) + + action_values['action'] = 'delete' + action_values['request_id'] = 'req-' + uuidsentinel.reqid1 + db.action_start(self.ctxt, action_values) + + actions = db.actions_get(self.ctxt, uuid1) + self.assertEqual(2, len(actions)) + + actions = db.actions_get(self.ctxt, uuid1, limit=1) + self.assertEqual(1, len(actions)) + + actions = db.actions_get( + self.ctxt, uuid1, limit=1, + marker=action_values['request_id']) + self.assertEqual(1, len(actions)) + self._assertEqualListsOfObjects([action1], actions) + + def test_instance_actions_get_with_changes_since(self): + """Test list instance actions can support timestamp filter.""" + uuid1 = uuidsentinel.uuid1 + + extra = { + 'created_at': timeutils.utcnow() + } + + action_values = self._create_action_values(uuid1, extra=extra) + db.action_start(self.ctxt, action_values) + + timestamp = timeutils.utcnow() + action_values['start_time'] = timestamp + action_values['updated_at'] = timestamp + action_values['action'] = 'delete' + action2 = db.action_start(self.ctxt, action_values) + + actions = db.actions_get(self.ctxt, uuid1) + self.assertEqual(2, len(actions)) + self.assertNotEqual(actions[0]['updated_at'], + actions[1]['updated_at']) + actions = db.actions_get( + self.ctxt, uuid1, filters={'changes-since': timestamp}) + self.assertEqual(1, len(actions)) + self._assertEqualListsOfObjects([action2], actions) + + def test_instance_actions_get_with_not_found_marker(self): + self.assertRaises(exception.MarkerNotFound, + db.actions_get, self.ctxt, uuidsentinel.uuid1, + marker=uuidsentinel.not_found_marker) + def test_instance_action_get_by_instance_and_action(self): """Ensure we can get an action by instance UUID and action id.""" ctxt2 = context.get_admin_context() diff --git a/nova/tests/unit/objects/test_instance_action.py b/nova/tests/unit/objects/test_instance_action.py index 6c4298061462..d2ff70f3ddf8 100644 --- a/nova/tests/unit/objects/test_instance_action.py +++ b/nova/tests/unit/objects/test_instance_action.py @@ -176,7 +176,8 @@ class _TestInstanceActionObject(object): self.context, 'fake-uuid') for index, action in enumerate(obj_list): self.compare_obj(action, fake_actions[index]) - mock_get.assert_called_once_with(self.context, 'fake-uuid') + mock_get.assert_called_once_with(self.context, 'fake-uuid', None, + None, None) class TestInstanceActionObject(test_objects._LocalTest, diff --git a/nova/tests/unit/objects/test_objects.py b/nova/tests/unit/objects/test_objects.py index a9d8a22e5337..07fceb5172d6 100644 --- a/nova/tests/unit/objects/test_objects.py +++ b/nova/tests/unit/objects/test_objects.py @@ -1102,7 +1102,7 @@ object_data = { 'InstanceAction': '1.1-f9f293e526b66fca0d05c3b3a2d13914', 'InstanceActionEvent': '1.1-e56a64fa4710e43ef7af2ad9d6028b33', 'InstanceActionEventList': '1.1-13d92fb953030cdbfee56481756e02be', - 'InstanceActionList': '1.0-4a53826625cc280e15fae64a575e0879', + 'InstanceActionList': '1.1-a2b2fb6006b47c27076d3a1d48baa759', 'InstanceDeviceMetadata': '1.0-74d78dd36aa32d26d2769a1b57caf186', 'InstanceExternalEvent': '1.2-23eb6ba79cde5cd06d3445f845ba4589', 'InstanceFault': '1.2-7ef01f16f1084ad1304a513d6d410a38',