Add upgrade status check for incomplete consumers

Since the create_incomplete_consumers online data migration
was copied from nova to placement, and will eventually be
removed from nova (and the nova online data migration won't
help once the placement data is copied over to extracted placement
on upgrade anyway), this adds an upgrade check to make sure
operators completed that online data migration.

Normally we wouldn't add upgrade checks for online data migrations
which should get automatically run by deployment tools, but since
extracted placement is very new, it's nice to provide tooling to
let operators know they have not only properly migrated their
data to extracted placement but whether or not their upgrade
homework is done.

Change-Id: I7f3ba20153a4c1dc8f5b209024edb882fcb726ef
This commit is contained in:
Matt Riedemann 2019-01-17 17:34:58 -05:00
parent 510b48482b
commit 0f97cebedd
7 changed files with 110 additions and 49 deletions

View File

@ -88,4 +88,5 @@ Upgrade
**1.0.0 (Stein)**
* Placeholder to be filled in with checks as they are added in Stein.
* A check was added for incomplete consumers which can be remedied by running
the ``placement-manage db online_data_migrations`` command.

View File

@ -15,6 +15,9 @@ import sys
from oslo_config import cfg
from oslo_upgradecheck import upgradecheck
from placement import context
from placement.db.sqlalchemy import models
from placement import db_api
from placement.i18n import _
@ -24,10 +27,47 @@ class Checks(upgradecheck.UpgradeCommands):
Various upgrade checks should be added as separate methods in this class
and added to _upgrade_checks tuple.
"""
def __init__(self):
self.ctxt = context.RequestContext(config=cfg.CONF)
def _check_placeholder(self):
# This is just a placeholder for upgrade checks, it should be
# removed when the actual checks are added
@db_api.placement_context_manager.reader
def _count_missing_consumers(self, ctxt):
# Count the total number of consumers.
num_consumers = ctxt.session.query(models.Consumer).count()
# Count the total number of unique consumers in the allocations table.
num_alloc_consumers = ctxt.session.query(models.Allocation).group_by(
models.Allocation.consumer_id).count()
return num_alloc_consumers - num_consumers
def _check_incomplete_consumers(self):
"""Allocations created with microversion<1.8 prior to Rocky will not
have an associated consumers table record. Starting in Rocky with
the 1.28 microversion, consumer generations were added to avoid
multiple processes overwriting allocations. Older allocations with
incomplete consumer records will be online migrated when accessed
via the REST API or when the
"placement-manage db online_data_migrations" command is run during
an upgrade. This status check emits a warning if there are incomplete
consumers to remind operators to perform the data migration.
Note that normally we would not add an upgrade status check to simply
mirror an online data migration since online data migrations should
be part of deploying/upgrading placement automation. However, with
placement being freshly extracted from nova, this check serves as a
friendly reminder and because the data migration will eventually be
removed from nova along with the rest of the placement code.
"""
missing_consumer_count = self._count_missing_consumers(self.ctxt)
if missing_consumer_count:
# We found missing consumers for existing allocations so return
# a warning and tell the user to run the online data migrations.
return upgradecheck.Result(
upgradecheck.Code.WARNING,
details=_('There are %s incomplete consumers table records '
'for existing allocations. Run the '
'"placement-manage db online_data_migrations" '
'command.') % missing_consumer_count)
# No missing consumers (or no allocations [fresh install?]) so it's OK.
return upgradecheck.Result(upgradecheck.Code.SUCCESS)
# The format of the check functions is to return an
@ -38,11 +78,7 @@ class Checks(upgradecheck.UpgradeCommands):
# in the returned Result's "details" attribute. The
# summary will be rolled up at the end of the check() method.
_upgrade_checks = (
# TODO(mriedem) In the future there should be some real checks added
# here, for example, making sure all resource providers have a
# root_provider_id set before the nested RPs compatibility code is
# removed. See bug 1799892 for details.
(_('Placeholder'), _check_placeholder),
(_('Incomplete Consumers'), _check_incomplete_consumers),
)

View File

@ -0,0 +1,44 @@
# 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 oslo_upgradecheck import upgradecheck
from placement.cmd import status
from placement.objects import consumer
from placement.tests.functional import base
from placement.tests.functional.db import test_consumer
class UpgradeCheckIncompleteConsumersTestCase(
base.TestCase, test_consumer.CreateIncompleteAllocationsMixin):
"""Tests the "Incomplete Consumers" check for the
"placement-status upgrade check" command.
"""
def setUp(self):
super(UpgradeCheckIncompleteConsumersTestCase, self).setUp()
self.checks = status.Checks()
def test_check_incomplete_consumers(self):
# Create some allocations with 3 missing consumers.
self._create_incomplete_allocations(
self.context, num_of_consumer_allocs=2)
result = self.checks._check_incomplete_consumers()
# Since there are incomplete consumers, there should be a warning.
self.assertEqual(upgradecheck.Code.WARNING, result.code)
# Check the details for the consumer count.
self.assertIn('There are 3 incomplete consumers table records for '
'existing allocations', result.details)
# Run the online data migration (as recommended from the check output).
consumer.create_incomplete_consumers(self.context, batch_size=50)
# Run the check again and it should be successful.
result = self.checks._check_incomplete_consumers()
self.assertEqual(upgradecheck.Code.SUCCESS, result.code)

View File

@ -93,14 +93,9 @@ def _get_allocs_with_no_consumer_relationship(ctx):
return ctx.session.execute(sel).fetchall()
# NOTE(jaypipes): The tb.PlacementDbBaseTestCase creates a project and user
# which is why we don't base off that. We want a completely bare DB for this
# test.
class CreateIncompleteConsumersTestCase(base.TestCase):
def setUp(self):
super(CreateIncompleteConsumersTestCase, self).setUp()
self.ctx = self.context
class CreateIncompleteAllocationsMixin(object):
"""Mixin for test setup to create some allocations with missing consumers
"""
@db_api.placement_context_manager.writer
def _create_incomplete_allocations(self, ctx, num_of_consumer_allocs=1):
@ -133,6 +128,17 @@ class CreateIncompleteConsumersTestCase(base.TestCase):
res = ctx.session.execute(sel).fetchall()
self.assertEqual(0, len(res))
# NOTE(jaypipes): The tb.PlacementDbBaseTestCase creates a project and user
# which is why we don't base off that. We want a completely bare DB for this
# test.
class CreateIncompleteConsumersTestCase(
base.TestCase, CreateIncompleteAllocationsMixin):
def setUp(self):
super(CreateIncompleteConsumersTestCase, self).setUp()
self.ctx = self.context
@db_api.placement_context_manager.reader
def _check_incomplete_consumers(self, ctx):
config = ctx.config

View File

@ -1,32 +0,0 @@
# 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 oslo_upgradecheck.upgradecheck import Code
import testtools
from placement.cmd import status
class TestUpgradeChecks(testtools.TestCase):
"""Basic tests for the upgrade check framework.
The heavy lifting is done in the oslo.upgradecheck library and the
tests here do not attempt to test the internals of the library code.
"""
def setUp(self):
super(TestUpgradeChecks, self).setUp()
self.cmd = status.Checks()
def test_check_placeholder(self):
check_result = self.cmd._check_placeholder()
self.assertEqual(
Code.SUCCESS, check_result.code)

View File

@ -0,0 +1,6 @@
---
upgrade:
- |
An upgrade check was added to the ``placement-status upgrade check``
command for incomplete consumers which can be remedied by running the
``placement-manage db online_data_migrations`` command.