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
This commit is contained in:
Tetsuro Nakamura 2018-12-11 20:21:04 +00:00 committed by Matt Riedemann
parent 80fa50187a
commit c198326150
5 changed files with 119 additions and 1 deletions

View File

@ -100,6 +100,7 @@ Placement Database
+---------------------------------------------+-------------+-----------+ +---------------------------------------------+-------------+-----------+
| Migration | Total Found | Completed | | Migration | Total Found | Completed |
+---------------------------------------------+-------------+-----------+ +---------------------------------------------+-------------+-----------+
| set_root_provider_ids | 0 | 0 |
| create_incomplete_consumers | 2 | 2 | | create_incomplete_consumers | 2 | 2 |
+---------------------------------------------+-------------+-----------+ +---------------------------------------------+-------------+-----------+

View File

@ -25,6 +25,7 @@ from placement import context
from placement.db.sqlalchemy import migration from placement.db.sqlalchemy import migration
from placement import db_api from placement import db_api
from placement.i18n import _ from placement.i18n import _
from placement.objects import resource_provider as rp_obj
version_info = pbr.version.VersionInfo('openstack-placement') version_info = pbr.version.VersionInfo('openstack-placement')
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -44,6 +45,7 @@ online_migrations = (
# have finished. # have finished.
# Added in Stein # Added in Stein
rp_obj.set_root_provider_ids,
) )

View File

@ -744,7 +744,8 @@ def _has_child_providers(context, rp_id):
@db_api.placement_context_manager.writer @db_api.placement_context_manager.writer
def _set_root_provider_id(context, rp_id, root_id): def _set_root_provider_id(context, rp_id, root_id):
"""Simply sets the root_provider_id value for a provider identified by """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 rp_id: Internal ID of the provider to update
:param root_id: Value to set root provider to :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) 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 = collections.namedtuple(
'ProviderIds', 'id uuid parent_id parent_uuid root_id root_uuid') 'ProviderIds', 'id uuid parent_id parent_uuid root_id root_uuid')

View File

@ -147,6 +147,78 @@ class ResourceProviderTestCase(tb.PlacementDbBaseTestCase):
# Make sure the object root_provider_uuid is set on load # Make sure the object root_provider_uuid is set on load
self.assertEqual(rp.root_provider_uuid, uuidsentinel.rp1) 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): def test_inherit_root_from_parent(self):
"""Tests that if we update an existing provider's parent provider UUID, """Tests that if we update an existing provider's parent provider UUID,
that the root provider UUID of the updated provider is automatically that the root provider UUID of the updated provider is automatically

View File

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