From 846836a1969cff17aaf9172183b768dd677374aa Mon Sep 17 00:00:00 2001 From: Toure Date: Mon, 20 Apr 2020 15:40:39 -0400 Subject: [PATCH] Add backup functionality to Openstack client for undercloud and overcloud. This will allow administrators to perform a backup of the undercloud or overcloud from the existing backup and restore role which is included in the tripleo-anisble project. Depends-on: https://review.opendev.org/720087 Change-Id: If25b71ae4d12208875d0fbcd9e16188a6f018f99 --- setup.cfg | 3 +- .../overcloud_backup}/__init__.py | 0 .../tests/v1/overcloud_backup/test_backup.py | 147 ++++++++++++++++++ .../{v2 => v1}/undercloud/test_backup.py | 113 +++++++++++--- tripleoclient/v1/overcloud_backup.py | 107 +++++++++++++ tripleoclient/{v2 => v1}/undercloud_backup.py | 72 ++++++++- 6 files changed, 415 insertions(+), 27 deletions(-) rename tripleoclient/tests/{v2/undercloud => v1/overcloud_backup}/__init__.py (100%) create mode 100644 tripleoclient/tests/v1/overcloud_backup/test_backup.py rename tripleoclient/tests/{v2 => v1}/undercloud/test_backup.py (66%) create mode 100644 tripleoclient/v1/overcloud_backup.py rename tripleoclient/{v2 => v1}/undercloud_backup.py (64%) diff --git a/setup.cfg b/setup.cfg index cc0adaeff..01dec943f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -104,6 +104,7 @@ openstack.tripleoclient.v2 = overcloud_ffwd-upgrade_run = tripleoclient.v1.overcloud_ffwd_upgrade:FFWDUpgradeRun overcloud_ffwd-upgrade_converge = tripleoclient.v1.overcloud_ffwd_upgrade:FFWDUpgradeConverge overcloud_generate_fencing = tripleoclient.v1.overcloud_parameters:GenerateFencingParameters + overcloud_backup = tripleoclient.v1.overcloud_backup:BackupOvercloud tripleo_container_image_build = tripleoclient.v2.tripleo_container_image:Build tripleo_container_image_hotfix = tripleoclient.v2.tripleo_container_image:HotFix tripleo_container_image_delete = tripleoclient.v1.container_image:TripleOContainerImageDelete @@ -116,7 +117,7 @@ openstack.tripleoclient.v2 = undercloud_upgrade = tripleoclient.v1.undercloud:UpgradeUndercloud undercloud_minion_install = tripleoclient.v1.undercloud_minion:InstallUndercloudMinion undercloud_minion_upgrade = tripleoclient.v1.undercloud_minion:UpgradeUndercloudMinion - undercloud_backup = tripleoclient.v2.undercloud_backup:BackupUndercloud + undercloud_backup = tripleoclient.v1.undercloud_backup:BackupUndercloud tripleo_validator_group_info = tripleoclient.v1.tripleo_validator:TripleOValidatorGroupInfo tripleo_validator_list = tripleoclient.v1.tripleo_validator:TripleOValidatorList tripleo_validator_run = tripleoclient.v1.tripleo_validator:TripleOValidatorRun diff --git a/tripleoclient/tests/v2/undercloud/__init__.py b/tripleoclient/tests/v1/overcloud_backup/__init__.py similarity index 100% rename from tripleoclient/tests/v2/undercloud/__init__.py rename to tripleoclient/tests/v1/overcloud_backup/__init__.py diff --git a/tripleoclient/tests/v1/overcloud_backup/test_backup.py b/tripleoclient/tests/v1/overcloud_backup/test_backup.py new file mode 100644 index 000000000..2c8eb40aa --- /dev/null +++ b/tripleoclient/tests/v1/overcloud_backup/test_backup.py @@ -0,0 +1,147 @@ +# 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. +# + +import mock + +from osc_lib.tests import utils + +from tripleoclient import constants +from tripleoclient.tests import fakes +from tripleoclient.v1 import overcloud_backup + + +class TestOvercloudBackup(utils.TestCommand): + + def setUp(self): + super(TestOvercloudBackup, self).setUp() + + # Get the command object to test + app_args = mock.Mock() + app_args.verbose_level = 1 + self.app.options = fakes.FakeOptions() + self.cmd = overcloud_backup.BackupOvercloud(self.app, app_args) + self.app.client_manager.workflow_engine = mock.Mock() + self.workflow = self.app.client_manager.workflow_engine + + @mock.patch('tripleoclient.utils.run_ansible_playbook', + autospec=True) + def test_overcloud_backup_noargs(self, mock_playbook): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + mock_playbook.assert_called_once_with( + workdir=mock.ANY, + playbook='cli-overcloud-backup.yaml', + inventory=parsed_args.inventory, + skip_tags=None, + playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS, + verbosity=3, + extra_vars=None + ) + + @mock.patch('tripleoclient.utils.run_ansible_playbook', + autospec=True) + def test_overcloud_backup_init(self, mock_playbook): + arglist = [ + '--init' + ] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + mock_playbook.assert_called_once_with( + workdir=mock.ANY, + playbook='prepare-overcloud-backup.yaml', + inventory=parsed_args.inventory, + skip_tags='bar_create_recover_image, bar_setup_nfs_server', + playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS, + verbosity=3, + extra_vars=None + ) + + @mock.patch('tripleoclient.utils.run_ansible_playbook', + autospec=True) + def test_overcloud_backup_storage_ip(self, mock_playbook): + arglist = [ + '--init', + '--storage-ip', + '192.168.0.100' + ] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + extra_vars = { + "tripleo_backup_and_restore_nfs_server": parsed_args.storage_ip + } + + self.cmd.take_action(parsed_args) + mock_playbook.assert_called_once_with( + workdir=mock.ANY, + playbook='prepare-overcloud-backup.yaml', + inventory=parsed_args.inventory, + skip_tags='bar_create_recover_image, bar_setup_nfs_server', + playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS, + verbosity=3, + extra_vars=extra_vars + ) + + @mock.patch('tripleoclient.utils.run_ansible_playbook', + autospec=True) + def test_overcloud_backup_init_with_inventory(self, mock_playbook): + arglist = [ + '--init', + '--inventory', + '/tmp/test_inventory.yaml' + ] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + mock_playbook.assert_called_once_with( + workdir=mock.ANY, + playbook='prepare-overcloud-backup.yaml', + inventory=parsed_args.inventory, + skip_tags='bar_create_recover_image, bar_setup_nfs_server', + playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS, + verbosity=3, + extra_vars=None + ) + + @mock.patch('tripleoclient.utils.run_ansible_playbook', + autospec=True) + def test_overcloud_backup_inventory(self, mock_playbook): + arglist = [ + '--inventory', + '/tmp/test_inventory.yaml' + ] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + mock_playbook.assert_called_once_with( + workdir=mock.ANY, + playbook='cli-overcloud-backup.yaml', + inventory=parsed_args.inventory, + skip_tags=None, + playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS, + verbosity=3, + extra_vars=None + ) diff --git a/tripleoclient/tests/v2/undercloud/test_backup.py b/tripleoclient/tests/v1/undercloud/test_backup.py similarity index 66% rename from tripleoclient/tests/v2/undercloud/test_backup.py rename to tripleoclient/tests/v1/undercloud/test_backup.py index b606913f3..615d229e4 100644 --- a/tripleoclient/tests/v2/undercloud/test_backup.py +++ b/tripleoclient/tests/v1/undercloud/test_backup.py @@ -19,7 +19,7 @@ from osc_lib.tests import utils from tripleoclient import constants from tripleoclient.tests import fakes -from tripleoclient.v2 import undercloud_backup +from tripleoclient.v1 import undercloud_backup class TestUndercloudBackup(utils.TestCommand): @@ -37,27 +37,7 @@ class TestUndercloudBackup(utils.TestCommand): @mock.patch('tripleoclient.utils.run_ansible_playbook', autospec=True) - def test_undercloud_backup_noargs(self, mock_playbook): - arglist = [] - verifylist = [] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.cmd.take_action(parsed_args) - mock_playbook.assert_called_once_with( - workdir=mock.ANY, - playbook='cli-undercloud-backup.yaml', - inventory='localhost,', - playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS, - verbosity=3, - extra_vars={ - 'sources_path': '/home/stack/' - } - ) - - @mock.patch('tripleoclient.utils.run_ansible_playbook', - autospec=True) - def test_undercloud_backup_withargs(self, mock_playbook): + def test_undercloud_backup_legacy_withargs(self, mock_playbook): arglist = [ '--add-path', '/tmp/foo.yaml', @@ -73,6 +53,7 @@ class TestUndercloudBackup(utils.TestCommand): workdir=mock.ANY, playbook=mock.ANY, inventory=mock.ANY, + skip_tags=None, playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS, verbosity=3, extra_vars={'sources_path': @@ -100,6 +81,7 @@ class TestUndercloudBackup(utils.TestCommand): workdir=mock.ANY, playbook=mock.ANY, inventory=mock.ANY, + skip_tags=None, verbosity=3, playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS, extra_vars={'sources_path': @@ -127,6 +109,7 @@ class TestUndercloudBackup(utils.TestCommand): workdir=mock.ANY, playbook=mock.ANY, inventory=mock.ANY, + skip_tags=None, playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS, verbosity=3, extra_vars={'sources_path': @@ -150,7 +133,93 @@ class TestUndercloudBackup(utils.TestCommand): workdir=mock.ANY, playbook=mock.ANY, inventory=mock.ANY, + skip_tags=None, playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS, verbosity=3, extra_vars={'sources_path': '/home/stack/,/tmp/foo.yaml'}) + + @mock.patch('tripleoclient.utils.run_ansible_playbook', + autospec=True) + def test_undercloud_backup_noargs(self, mock_playbook): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + mock_playbook.assert_called_once_with( + workdir=mock.ANY, + playbook='cli-undercloud-backup.yaml', + inventory=parsed_args.inventory, + skip_tags=None, + playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS, + verbosity=3, + extra_vars=None + ) + + @mock.patch('tripleoclient.utils.run_ansible_playbook', + autospec=True) + def test_undercloud_backup_init(self, mock_playbook): + arglist = [ + '--init' + ] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + mock_playbook.assert_called_once_with( + workdir=mock.ANY, + playbook='prepare-undercloud-backup.yaml', + inventory=parsed_args.inventory, + skip_tags='bar_create_recover_image', + playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS, + verbosity=3, + extra_vars=None + ) + + @mock.patch('tripleoclient.utils.run_ansible_playbook', + autospec=True) + def test_undercloud_backup_init_with_inventory(self, mock_playbook): + arglist = [ + '--init', + '--inventory', + '/tmp/test_inventory.yaml' + ] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + mock_playbook.assert_called_once_with( + workdir=mock.ANY, + playbook='prepare-undercloud-backup.yaml', + inventory=parsed_args.inventory, + skip_tags='bar_create_recover_image', + playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS, + verbosity=3, + extra_vars=None + ) + + @mock.patch('tripleoclient.utils.run_ansible_playbook', + autospec=True) + def test_undercloud_backup_inventory(self, mock_playbook): + arglist = [ + '--inventory', + '/tmp/test_inventory.yaml' + ] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + mock_playbook.assert_called_once_with( + workdir=mock.ANY, + playbook='cli-undercloud-backup.yaml', + inventory=parsed_args.inventory, + skip_tags=None, + playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS, + verbosity=3, + extra_vars=None + ) diff --git a/tripleoclient/v1/overcloud_backup.py b/tripleoclient/v1/overcloud_backup.py new file mode 100644 index 000000000..d7359fa85 --- /dev/null +++ b/tripleoclient/v1/overcloud_backup.py @@ -0,0 +1,107 @@ +# Copyright 2020 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. +# + +import argparse +import logging + +from osc_lib.command import command +from osc_lib.i18n import _ + +from tripleoclient import constants +from tripleoclient import utils + +LOG = logging.getLogger(__name__ + ".BackupOvercloud") + + +class BackupOvercloud(command.Command): + """Backup the Overcloud""" + + def get_parser(self, prog_name): + parser = argparse.ArgumentParser( + description=self.get_description(), + prog=prog_name, + add_help=False + ) + + parser.add_argument( + '--init', + default=False, + action='store_true', + help=_("Initialize enviornment for backup," + "which will check for package install" + "status and configured ReaR.") + ) + + parser.add_argument( + '--inventory', + default='/home/stack/tripleo-inventory.yaml', + help=_("Tripleo inventory file generated with" + "tripleo-ansible-inventory command.") + ) + + parser.add_argument( + '--storage-ip', + help=_("Storage IP is an optional parameter" + "which allows for an ip of a storage" + "server to be specified, overriding the" + "default undercloud.") + ) + + return parser + + def _run_backup_Overcloud(self, parsed_args): + """Backup defined overcloud nodes.""" + + if parsed_args.init is False: + playbook = 'cli-overcloud-backup.yaml' + skip_tags = None + elif parsed_args.init is True: + playbook = 'prepare-overcloud-backup.yaml' + skip_tags = 'bar_create_recover_image, bar_setup_nfs_server' + + if parsed_args.storage_ip: + extra_vars = { + "tripleo_backup_and_restore_nfs_server": parsed_args.storage_ip + } + else: + extra_vars = None + + LOG.debug(_('Starting Overcloud Backup')) + with utils.TempDirs() as tmp: + utils.run_ansible_playbook( + playbook=playbook, + inventory=parsed_args.inventory, + workdir=tmp, + playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS, + skip_tags=skip_tags, + verbosity=utils.playbook_verbosity(self=self), + extra_vars=extra_vars + ) + + def take_action(self, parsed_args): + + self._run_backup_Overcloud(parsed_args) + print( + '\n' + ' #############################################################\n' + ' # Disclaimer #\n' + ' # Backup verification is the End Users responsibility #\n' + ' # Please verify backup integrity before any possible #\n' + ' # disruptive actions against the Overcloud. The resulting #\n' + ' # backup file path will be shown on a successful execution. #\n' + ' # #\n' + ' # .-Stay safe and avoid future issues-. #\n' + ' #############################################################\n' + ) diff --git a/tripleoclient/v2/undercloud_backup.py b/tripleoclient/v1/undercloud_backup.py similarity index 64% rename from tripleoclient/v2/undercloud_backup.py rename to tripleoclient/v1/undercloud_backup.py index 155911ecb..0895fe9ac 100644 --- a/tripleoclient/v2/undercloud_backup.py +++ b/tripleoclient/v1/undercloud_backup.py @@ -1,4 +1,4 @@ -# Copyright 2018 Red Hat, Inc. +# Copyright 2020 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 @@ -35,6 +35,24 @@ class BackupUndercloud(command.Command): add_help=False ) + # New flags for tripleo-ansible backup and restore role. + parser.add_argument( + '--init', + default=False, + action='store_true', + help=_("Initialize enviornment for backup," + "which will check for package install" + "status and configured ReaR.") + ) + + parser.add_argument( + '--inventory', + action='store', + default='/home/stack/tripleo-inventory.yaml', + help=_("Tripleo inventory file generated with" + "tripleo-ansible-inventory command.") + ) + # Parameter to choose the files to backup parser.add_argument( '--add-path', @@ -65,10 +83,32 @@ class BackupUndercloud(command.Command): "Swift itself is backed up if you call this multiple times " "the backup size will grow exponentially") ) + return parser def _run_backup_undercloud(self, parsed_args): + if parsed_args.init is False: + playbook = 'cli-undercloud-backup.yaml' + skip_tags = None + elif parsed_args.init is True: + playbook = 'prepare-undercloud-backup.yaml' + skip_tags = 'bar_create_recover_image' + + self._run_ansible_playbook( + playbook=playbook, + inventory=parsed_args.inventory, + skip_tags=skip_tags, + extra_vars=None + ) + + def _legacy_backup_undercloud(self, parsed_args): + """Legacy backup undercloud. + + This will allow for easier removal once the functionality + is no longer needed. + """ + merge_paths = sorted(list(set(parsed_args.add_path))) for exc in parsed_args.exclude_path: if exc in merge_paths: @@ -84,19 +124,43 @@ class BackupUndercloud(command.Command): extra_vars.update({"save_swift": True}) LOG.debug(_('Launch the Undercloud Backup')) + self._run_ansible_playbook( + playbook='cli-undercloud-backup-legacy.yaml', + inventory='localhost, ', + skip_tags=None, + extra_vars=extra_vars + ) + + def _run_ansible_playbook(self, + playbook, + inventory, + skip_tags, + extra_vars): + """Run ansible playbook""" + with utils.TempDirs() as tmp: utils.run_ansible_playbook( - playbook='cli-undercloud-backup.yaml', - inventory='localhost,', + playbook=playbook, + inventory=inventory, workdir=tmp, playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS, + skip_tags=skip_tags, verbosity=utils.playbook_verbosity(self=self), extra_vars=extra_vars ) def take_action(self, parsed_args): - self._run_backup_undercloud(parsed_args) + if len(parsed_args.add_path) > 1 or parsed_args.save_swift: + + LOG.warning("The following flags will be deprecated:" + "[--add-path, --exclude-path, --save-swift]") + + self._legacy_backup_undercloud(parsed_args) + + else: + self._run_backup_undercloud(parsed_args) + print( '\n' ' #############################################################\n'