diff --git a/designate/cmd/status.py b/designate/cmd/status.py new file mode 100644 index 000000000..a9c93eb86 --- /dev/null +++ b/designate/cmd/status.py @@ -0,0 +1,57 @@ +# Copyright 2018 Red Hat Inc. +# +# 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_config import cfg +from oslo_upgradecheck import upgradecheck +from sqlalchemy import MetaData, Table, select, func + +from designate.i18n import _ +from designate.sqlalchemy import session +# This import is not used, but is needed to register the storage:sqlalchemy +# group. +import designate.storage.impl_sqlalchemy # noqa +from designate import utils + + +class Checks(upgradecheck.UpgradeCommands): + def _duplicate_service_status(self): + engine = session.get_engine('storage:sqlalchemy') + metadata = MetaData(bind=engine) + status = Table('service_statuses', metadata, autoload=True) + service_select = (select([func.count()]) + .select_from(status) + .group_by('service_name', 'hostname') + ) + service_counts = engine.execute(service_select).fetchall() + duplicated_services = [i for i in service_counts if i[0] > 1] + if duplicated_services: + return upgradecheck.Result(upgradecheck.Code.FAILURE, + _('Duplicated services found in ' + 'service_statuses table.')) + return upgradecheck.Result(upgradecheck.Code.SUCCESS) + + _upgrade_checks = ((_('Duplicate service status'), + _duplicate_service_status), + ) + + +def main(): + config_files = utils.find_config('designate.conf') + checker = Checks() + return upgradecheck.main( + conf=cfg.CONF, + project='designate', + upgrade_command=checker, + default_config_files=config_files, + ) diff --git a/designate/tests/unit/test_upgrade_checks.py b/designate/tests/unit/test_upgrade_checks.py new file mode 100644 index 000000000..31c21ccd1 --- /dev/null +++ b/designate/tests/unit/test_upgrade_checks.py @@ -0,0 +1,74 @@ +# Copyright 2018 Red Hat Inc. +# +# 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 migrate.changeset.constraint import UniqueConstraint +from oslo_upgradecheck import upgradecheck +from sqlalchemy.schema import MetaData +from sqlalchemy.schema import Table + +from designate.cmd import status +from designate import tests +from designate.sqlalchemy import session + + +class TestDuplicateServiceStatus(tests.TestCase): + def setUp(self): + super(TestDuplicateServiceStatus, self).setUp() + self.engine = session.get_engine('storage:sqlalchemy') + self.meta = MetaData() + self.meta.bind = self.engine + self.service_statuses_table = Table('service_statuses', self.meta, + autoload=True) + + def test_success(self): + fake_record = {'id': '1', + 'service_name': 'worker', + 'hostname': 'localhost', + 'status': 'UP', + 'stats': '', + 'capabilities': '', + } + self.service_statuses_table.insert().execute(fake_record) + # Different hostname should be fine + fake_record['id'] = '2' + fake_record['hostname'] = 'otherhost' + self.service_statuses_table.insert().execute(fake_record) + # Different service_name should be fine + fake_record['id'] = '3' + fake_record['service_name'] = 'producer' + self.service_statuses_table.insert().execute(fake_record) + checks = status.Checks() + self.assertEqual(upgradecheck.Code.SUCCESS, + checks._duplicate_service_status().code) + + def test_failure(self): + # Drop unique constraint so we can test error cases + constraint = UniqueConstraint('service_name', 'hostname', + table=self.service_statuses_table, + name="unique_service_status") + constraint.drop() + fake_record = {'id': '1', + 'service_name': 'worker', + 'hostname': 'localhost', + 'status': 'UP', + 'stats': '', + 'capabilities': '', + } + self.service_statuses_table.insert().execute(fake_record) + fake_record['id'] = '2' + self.service_statuses_table.insert().execute(fake_record) + + checks = status.Checks() + self.assertEqual(upgradecheck.Code.FAILURE, + checks._duplicate_service_status().code) diff --git a/doc/source/admin/upgrades/index.rst b/doc/source/admin/upgrades/index.rst index 631bc528e..c9a47ff48 100644 --- a/doc/source/admin/upgrades/index.rst +++ b/doc/source/admin/upgrades/index.rst @@ -4,6 +4,10 @@ Upgrades In this section, you will find documentation relevant for upgrading Designate. +.. note:: The :ref:`designate-status upgrade check ` + command can be used to verify a deployment before starting services + with new code. + Contents: .. toctree:: diff --git a/doc/source/cli/designate-status.rst b/doc/source/cli/designate-status.rst new file mode 100644 index 000000000..05c108b7b --- /dev/null +++ b/doc/source/cli/designate-status.rst @@ -0,0 +1,86 @@ +==================== +Designate Status CLI +==================== + +This chapter documents :command:`designate-status`. + +For help on a specific :command:`designate-status` command, enter: + +.. code-block:: console + + $ designate-status COMMAND --help + +designate-status +================ + +:program:`designate-status` is a tool that provides routines for checking the +status of a Designate deployment. + +The standard pattern for executing a :program:`designate-status` command is: + +.. code-block:: console + + designate-status [] + +Run without arguments to see a list of available command categories: + +.. code-block:: console + + designate-status + +Categories are: + +* ``upgrade`` + +Detailed descriptions are below. + +You can also run with a category argument such as ``upgrade`` to see a list of +all commands in that category: + +.. code-block:: console + + designate-status upgrade + +The following sections describe the available categories and arguments for +:program:`designate-status`. + +designate-status upgrade +======================== + +.. _designate-status-upgrade-check: + +designate-status upgrade check +------------------------------ + +``designate-status upgrade check`` + Performs a release-specific readiness check before running db sync for the + new version. This command expects to have complete configuration and access + to the database. + + **Return Codes** + + .. list-table:: + :widths: 20 80 + :header-rows: 1 + + * - Return code + - Description + * - 0 + - All upgrade readiness checks passed successfully and there is nothing + to do. + * - 1 + - At least one check encountered an issue and requires further + investigation. This is considered a warning but the upgrade may be OK. + * - 2 + - There was an upgrade status check failure that needs to be + investigated. This should be considered something that stops an + upgrade. + * - 255 + - An unexpected error occurred. + + **History of Checks** + + **8.0.0 (Stein)** + + * Checks that duplicate entries do not exist in the ``service_statuses`` + table. diff --git a/doc/source/cli/index.rst b/doc/source/cli/index.rst index 81369cccc..74809f91a 100644 --- a/doc/source/cli/index.rst +++ b/doc/source/cli/index.rst @@ -8,3 +8,4 @@ Interface (CLI) can be found in this section. :maxdepth: 1 designate-manage + designate-status diff --git a/lower-constraints.txt b/lower-constraints.txt index ede510a5e..4cacf0ef0 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -89,6 +89,7 @@ oslo.reports==1.18.0 oslo.rootwrap==5.8.0 oslo.serialization==2.18.0 oslo.service==1.24.0 +oslo.upgradecheck==0.1.0 oslo.utils==3.33.0 oslo.versionedobjects==1.31.2 oslotest==3.2.0 diff --git a/releasenotes/notes/stein-upgrade-checkers-cli-b8518126fff82be6.yaml b/releasenotes/notes/stein-upgrade-checkers-cli-b8518126fff82be6.yaml new file mode 100644 index 000000000..70b9250b0 --- /dev/null +++ b/releasenotes/notes/stein-upgrade-checkers-cli-b8518126fff82be6.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + A new ``designate-status upgrade check`` command has been added which can + be used to validate a deployment before starting services with new code. + See the documentation for details: + https://docs.openstack.org/designate/latest/cli/designate-status.html diff --git a/requirements.txt b/requirements.txt index 0277b16c4..0afcc00f6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,6 +20,7 @@ oslo.reports>=1.18.0 # Apache-2.0 oslo.rootwrap>=5.8.0 # Apache-2.0 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0 oslo.service!=1.28.1,>=1.24.0 # Apache-2.0 +oslo.upgradecheck>=0.1.0 oslo.utils>=3.33.0 # Apache-2.0 oslo.versionedobjects>=1.31.2 # Apache-2.0 Paste>=2.0.2 # MIT diff --git a/setup.cfg b/setup.cfg index 9b920b8d0..0cceddb54 100644 --- a/setup.cfg +++ b/setup.cfg @@ -63,6 +63,7 @@ console_scripts = designate-agent = designate.cmd.agent:main designate-worker = designate.cmd.worker:main designate-producer = designate.cmd.producer:main + designate-status = designate.cmd.status:main designate.api.admin.extensions =