From c1983261506acac67055747aaaf0ce5c0ff8e209 Mon Sep 17 00:00:00 2001 From: Tetsuro Nakamura Date: Tue, 11 Dec 2018 20:21:04 +0000 Subject: [PATCH] Set root_provider_id in the database When nested resource provider feature was added in Rocky, root_provider_id column, which should be non-None value, is created in the resource provider DB. However, online data migration is only done implicitly via listing or showing resource providers. With this patch, executing the cli command `placement-manage db online_data_migrations` makes sure all the resource providers are ready for nested provider feature, that is, all the root_provider_ids in the DB have non-None value. Change-Id: I42a1afa69f379b095417f5eb106fe52ebff15017 Related-Bug:#1803925 --- doc/source/cli/placement-manage.rst | 1 + placement/cmd/manage.py | 2 + placement/objects/resource_provider.py | 35 ++++++++- .../functional/db/test_resource_provider.py | 72 +++++++++++++++++++ ...set_root_provider_id-53930a5d1dbd374f.yaml | 10 +++ 5 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/set_root_provider_id-53930a5d1dbd374f.yaml diff --git a/doc/source/cli/placement-manage.rst b/doc/source/cli/placement-manage.rst index e13050bf7..6c38d9340 100644 --- a/doc/source/cli/placement-manage.rst +++ b/doc/source/cli/placement-manage.rst @@ -100,6 +100,7 @@ Placement Database +---------------------------------------------+-------------+-----------+ | Migration | Total Found | Completed | +---------------------------------------------+-------------+-----------+ + | set_root_provider_ids | 0 | 0 | | create_incomplete_consumers | 2 | 2 | +---------------------------------------------+-------------+-----------+ diff --git a/placement/cmd/manage.py b/placement/cmd/manage.py index ac51c2daf..90204eec6 100644 --- a/placement/cmd/manage.py +++ b/placement/cmd/manage.py @@ -25,6 +25,7 @@ from placement import context from placement.db.sqlalchemy import migration from placement import db_api from placement.i18n import _ +from placement.objects import resource_provider as rp_obj version_info = pbr.version.VersionInfo('openstack-placement') LOG = logging.getLogger(__name__) @@ -44,6 +45,7 @@ online_migrations = ( # have finished. # Added in Stein + rp_obj.set_root_provider_ids, ) diff --git a/placement/objects/resource_provider.py b/placement/objects/resource_provider.py index 4e322c6c1..7bb181c5f 100644 --- a/placement/objects/resource_provider.py +++ b/placement/objects/resource_provider.py @@ -744,7 +744,8 @@ def _has_child_providers(context, rp_id): @db_api.placement_context_manager.writer def _set_root_provider_id(context, rp_id, root_id): """Simply sets the root_provider_id value for a provider identified by - rp_id. Used in online data migration. + rp_id. Used in implicit online data migration via REST API getting + resource providers. :param rp_id: Internal ID of the provider to update :param root_id: Value to set root provider to @@ -754,6 +755,38 @@ def _set_root_provider_id(context, rp_id, root_id): context.session.execute(upd) +@db_api.placement_context_manager.writer +def set_root_provider_ids(context, batch_size): + """Simply sets the root_provider_id value for a provider identified by + rp_id. Used in explicit online data migration via CLI. + + :param rp_id: Internal ID of the provider to update + :param root_id: Value to set root provider to + """ + # UPDATE resource_providers + # SET root_provider_id=resource_providers.id + # WHERE resource_providers.id + # IN (SELECT subq_1.id + # FROM (SELECT resource_providers.id AS id + # FROM resource_providers + # WHERE resource_providers.root_provider_id IS NULL + # LIMIT :param_1) + # AS subq_1) + + subq_1 = context.session.query(_RP_TBL.c.id) + subq_1 = subq_1.filter(_RP_TBL.c.root_provider_id.is_(None)) + subq_1 = subq_1.limit(batch_size) + subq_1 = sa.alias(subq_1.as_scalar(), name="subq_1") + + subq_2 = sa.select([subq_1.c.id]).select_from(subq_1) + + upd = _RP_TBL.update().where(_RP_TBL.c.id.in_(subq_2.as_scalar())) + upd = upd.values(root_provider_id=_RP_TBL.c.id) + res = context.session.execute(upd) + + return res.rowcount, res.rowcount + + ProviderIds = collections.namedtuple( 'ProviderIds', 'id uuid parent_id parent_uuid root_id root_uuid') diff --git a/placement/tests/functional/db/test_resource_provider.py b/placement/tests/functional/db/test_resource_provider.py index 19d9ac9ec..4feaa903d 100644 --- a/placement/tests/functional/db/test_resource_provider.py +++ b/placement/tests/functional/db/test_resource_provider.py @@ -147,6 +147,78 @@ class ResourceProviderTestCase(tb.PlacementDbBaseTestCase): # Make sure the object root_provider_uuid is set on load self.assertEqual(rp.root_provider_uuid, uuidsentinel.rp1) + def test_set_root_provider(self): + """Simulate old resource provider records in the database that has no + root_provider_uuid set and ensure the root_provider_uuid field in the + table is set to the provider's ID via set_root_provider_ids(). + """ + # First, set up records for "old-style" resource providers with + # no root provider UUID. + rp_tbl = rp_obj._RP_TBL + conn = self.placement_db.get_engine().connect() + + ins_stmt1 = rp_tbl.insert().values( + id=1, + uuid=uuidsentinel.rp1, + name='rp-1', + root_provider_id=None, + parent_provider_id=None, + generation=42, + ) + ins_stmt2 = rp_tbl.insert().values( + id=2, + uuid=uuidsentinel.rp2, + name='rp-2', + root_provider_id=None, + parent_provider_id=None, + generation=42, + ) + conn.execute(ins_stmt1) + conn.execute(ins_stmt2) + + # Second, set up records for "new-style" resource providers + # in a tree + self._create_provider('root_rp') + self._create_provider('child_rp', parent=uuidsentinel.root_rp) + self._create_provider('grandchild_rp', parent=uuidsentinel.child_rp) + + # Check rp_1 that it has no root provider id + sel_stmt = sa.select([rp_tbl.c.root_provider_id]).where( + rp_tbl.c.id == 1) + res = conn.execute(sel_stmt).fetchall() + self.assertIsNone(res[0][0]) + # Check rp_2 that it has no root provider id + sel_stmt = sa.select([rp_tbl.c.root_provider_id]).where( + rp_tbl.c.id == 2) + res = conn.execute(sel_stmt).fetchall() + self.assertIsNone(res[0][0]) + + # Run set_root_provider_ids() + found, migrated = rp_obj.set_root_provider_ids(self.ctx, batch_size=10) + self.assertEqual(2, found) + self.assertEqual(2, migrated) + + # Check rp_1 that it has got the root provider id + sel_stmt = sa.select([rp_tbl.c.root_provider_id]).where( + rp_tbl.c.id == 1) + res = conn.execute(sel_stmt).fetchall() + self.assertEqual(1, res[0][0]) + # Check rp_2 that it has got the root provider id + sel_stmt = sa.select([rp_tbl.c.root_provider_id]).where( + rp_tbl.c.id == 2) + res = conn.execute(sel_stmt).fetchall() + self.assertEqual(2, res[0][0]) + + # Check the new-style providers remains in a tree, + # which means the root provider ids are not changed + rps = rp_obj.ResourceProviderList.get_all_by_filters( + self.ctx, + filters={ + 'in_tree': uuidsentinel.root_rp, + } + ) + self.assertEqual(3, len(rps)) + def test_inherit_root_from_parent(self): """Tests that if we update an existing provider's parent provider UUID, that the root provider UUID of the updated provider is automatically diff --git a/releasenotes/notes/set_root_provider_id-53930a5d1dbd374f.yaml b/releasenotes/notes/set_root_provider_id-53930a5d1dbd374f.yaml new file mode 100644 index 000000000..c3bbd782f --- /dev/null +++ b/releasenotes/notes/set_root_provider_id-53930a5d1dbd374f.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + A new online data migration has been added to populate missing + ``root_provider_id`` in the resource_providers table. This can + be run during the normal placement-manage db online_data_migrations + routine. See the `Bug#1803925`_ for more details. + + .. _Bug#1803925: https://bugs.launchpad.net/nova/+bug/1803925 +