Enable snapshots on the CLI

There is a new feature for Backup and Restore that replaces the ReaR
operations with a simple and fast LVM snapshot. This change enables the
operator to take snapshots using the openstack overcloud backup
snapshot command.

Change-Id: I975dada7dc559f708f4b9b0cba6328c87befb5bd
(cherry picked from commit 80290ad9b8)
This commit is contained in:
Juan Larriba 2022-05-12 13:04:00 +02:00
parent 1cc14bef83
commit 17de18b40e
3 changed files with 284 additions and 41 deletions

View File

@ -91,6 +91,7 @@ openstack.tripleoclient.v2 =
overcloud_external-upgrade_run = tripleoclient.v1.overcloud_external_upgrade:ExternalUpgradeRun
overcloud_generate_fencing = tripleoclient.v1.overcloud_parameters:GenerateFencingParameters
overcloud_backup = tripleoclient.v1.overcloud_backup:BackupOvercloud
overcloud_backup_snapshot = tripleoclient.v1.overcloud_backup:BackupSnapshot
overcloud_restore = tripleoclient.v1.overcloud_restore:RestoreOvercloud
tripleo_container_image_build = tripleoclient.v2.tripleo_container_image:Build
tripleo_container_image_hotfix = tripleoclient.v2.tripleo_container_image:HotFix

View File

@ -366,3 +366,137 @@ class TestOvercloudBackup(utils.TestCommand):
'The inventory file',
self.cmd.take_action,
parsed_args)
class TestOvercloudSnapshot(utils.TestCommand):
def setUp(self):
super(TestOvercloudSnapshot, 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.BackupSnapshot(self.app, app_args)
self.app.client_manager.workflow_engine = mock.Mock()
self.workflow = self.app.client_manager.workflow_engine
self.inventory = '/tmp/test_inventory.yaml'
self.file = open(self.inventory, 'w').close()
@mock.patch('os.path.isfile')
@mock.patch('os.access')
@mock.patch('tripleoclient.utils.run_ansible_playbook',
autospec=True)
def test_overcloud_snapshot_noargs(self,
mock_playbook,
mock_access,
mock_isfile):
arglist = []
verifylist = []
mock_isfile.return_value = True
mock_access.return_value = True
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-snapshot.yaml',
inventory=parsed_args.inventory,
tags=None,
skip_tags=None,
playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS,
verbosity=3,
extra_vars={}
)
@mock.patch('tripleoclient.utils.run_ansible_playbook', autospec=True)
def test_overcloud_snapshot_inventory(self, mock_playbook):
arglist = [
'--inventory',
self.inventory
]
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-snapshot.yaml',
inventory=parsed_args.inventory,
tags=None,
skip_tags=None,
playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS,
verbosity=3,
extra_vars={}
)
@mock.patch('os.path.isfile')
@mock.patch('os.access')
@mock.patch('tripleoclient.utils.run_ansible_playbook',
autospec=True)
def test_overcloud_snapshot_extra_vars_inline(self,
mock_playbook,
mock_access,
mock_isfile):
arglist = [
'--extra-vars',
'{"tripleo_snapshot_revert_var_size": "2G"}'
]
verifylist = []
mock_isfile.return_value = True
mock_access.return_value = True
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
extra_vars_dict = {
'tripleo_snapshot_revert_var_size': '2G'
}
self.cmd.take_action(parsed_args)
mock_playbook.assert_called_once_with(
workdir=mock.ANY,
playbook='cli-overcloud-snapshot.yaml',
inventory=parsed_args.inventory,
tags=None,
skip_tags=None,
playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS,
verbosity=3,
extra_vars=extra_vars_dict
)
@mock.patch('tripleoclient.utils.run_ansible_playbook',
autospec=True)
def test_overcloud_backup_no_inventory(self, mock_playbook):
arglist = [
'--inventory',
'/tmp/no_inventory.yaml'
]
verifylist = []
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaisesRegex(
RuntimeError,
'The inventory file',
self.cmd.take_action,
parsed_args)
@mock.patch('os.access')
@mock.patch('tripleoclient.utils.run_ansible_playbook',
autospec=True)
def test_overcloud_backup_no_readable_inventory(self,
mock_playbook,
mock_access):
arglist = [
'--inventory',
self.inventory
]
verifylist = []
mock_access.return_value = False
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaisesRegex(
RuntimeError,
'The inventory file',
self.cmd.take_action,
parsed_args)

View File

@ -24,14 +24,14 @@ from osc_lib.i18n import _
from tripleoclient import constants
from tripleoclient import utils
LOG = logging.getLogger(__name__ + ".BackupOvercloud")
INVENTORY = constants.ANSIBLE_INVENTORY.format('overcloud')
class BackupOvercloud(command.Command):
"""Backup the Overcloud"""
log = logging.getLogger(__name__ + ".BackupOvercloud")
def get_parser(self, prog_name):
parser = argparse.ArgumentParser(
description=self.get_description(),
@ -160,52 +160,52 @@ class BackupOvercloud(command.Command):
if not (os.path.isfile(parsed_args.inventory) and
os.access(parsed_args.inventory, os.R_OK)):
raise RuntimeError(
_('The inventory file {} does not exist or is not '
'readable'.format(parsed_args.inventory)))
_('The inventory file {} does not exist or is not '
'readable'.format(parsed_args.inventory)))
if parsed_args.setup_nfs is True or parsed_args.init == 'nfs':
LOG.debug(_('Setting up NFS Backup node'))
self.log.debug(_('Setting up NFS Backup node'))
self._run_ansible_playbook(
playbook='prepare-nfs-backup.yaml',
inventory=parsed_args.inventory,
tags='bar_setup_nfs_server',
skip_tags=None,
extra_vars=extra_vars
)
playbook='prepare-nfs-backup.yaml',
inventory=parsed_args.inventory,
tags='bar_setup_nfs_server',
skip_tags=None,
extra_vars=extra_vars
)
if parsed_args.setup_rear is True or parsed_args.init == 'rear':
LOG.debug(_('Installing ReaR on controller nodes'))
self.log.debug(_('Installing ReaR on controller nodes'))
self._run_ansible_playbook(
playbook='prepare-overcloud-backup.yaml',
inventory=parsed_args.inventory,
tags='bar_setup_rear',
skip_tags=None,
extra_vars=extra_vars
)
playbook='prepare-overcloud-backup.yaml',
inventory=parsed_args.inventory,
tags='bar_setup_rear',
skip_tags=None,
extra_vars=extra_vars
)
if parsed_args.setup_ironic is True or parsed_args.init == 'ironic':
LOG.debug(_('Installing and configuring Rear/Ironic on nodes'))
self.log.debug(_('Installing Rear/Ironic on nodes'))
self._run_ansible_playbook(
playbook='cli-overcloud-conf-ironic.yaml',
inventory=parsed_args.inventory,
tags='bar_setup_rear',
skip_tags=None,
extra_vars=extra_vars
)
playbook='cli-overcloud-conf-ironic.yaml',
inventory=parsed_args.inventory,
tags='bar_setup_rear',
skip_tags=None,
extra_vars=extra_vars
)
if parsed_args.cron is True:
LOG.debug(_('Programming cron backup'))
self.log.debug(_('Programming cron backup'))
self._run_ansible_playbook(
playbook='cli-overcloud-backup-cron.yaml',
inventory=parsed_args.inventory,
tags=None,
skip_tags=None,
extra_vars=extra_vars
)
playbook='cli-overcloud-backup-cron.yaml',
inventory=parsed_args.inventory,
tags=None,
skip_tags=None,
extra_vars=extra_vars
)
if (parsed_args.setup_nfs is False and
parsed_args.setup_rear is False and
@ -213,14 +213,14 @@ class BackupOvercloud(command.Command):
parsed_args.cron is False and
parsed_args.init is None):
LOG.debug(_('Starting Overcloud Backup'))
self.log.debug(_('Starting Overcloud Backup'))
self._run_ansible_playbook(
playbook='cli-overcloud-backup.yaml',
inventory=parsed_args.inventory,
tags='bar_create_recover_image',
skip_tags=None,
extra_vars=extra_vars
)
playbook='cli-overcloud-backup.yaml',
inventory=parsed_args.inventory,
tags='bar_create_recover_image',
skip_tags=None,
extra_vars=extra_vars
)
def _run_ansible_playbook(self,
playbook,
@ -246,8 +246,8 @@ class BackupOvercloud(command.Command):
if parsed_args.init:
LOG.warning("The following flags will be deprecated:"
"[--init, --storage-ip]")
self.log.warning("The following flags will be deprecated:"
"[--init, --storage-ip]")
self._run_backup_overcloud(parsed_args)
print(
@ -262,3 +262,111 @@ class BackupOvercloud(command.Command):
' # .-Stay safe and avoid future issues-. #\n'
' #############################################################\n'
)
class BackupSnapshot(command.Command):
"""Takes and LVM snapshot ignoring all the rest
of the parameters passed. To be able to take
a snapshot, the following conditions must
be met:
- The disk must be configured to use LVM
- There must be an lv called lv_snapshot
- lv_snapshot must be 8GB or more
This operation will destroy the lv_snapshot volume
and replace it with snapshots of the disks.
"""
log = logging.getLogger(__name__ + ".BackupSnapshotOvercloud")
def get_parser(self, prog_name):
parser = argparse.ArgumentParser(
description=self.get_description(),
prog=prog_name,
add_help=False
)
parser.add_argument(
'--inventory',
default=INVENTORY,
help=_("Tripleo inventory file generated with "
"tripleo-ansible-inventory command. "
"Defaults to: " + INVENTORY)
)
parser.add_argument(
'--extra-vars',
default=None,
action='store',
help=_("Set additional variables as Dict or as "
"an absolute path of a JSON or YAML file type. "
"i.e. --extra-vars '{\"key\": \"val\", "
" \"key2\": \"val2\"}' "
"i.e. --extra-vars /path/to/my_vars.yaml "
"i.e. --extra-vars /path/to/my_vars.json. "
"For more information about the variables that "
"can be passed, visit: https://opendev.org/openstack/"
"tripleo-ansible/src/branch/master/tripleo_ansible/"
"roles/backup_and_restore/defaults/main.yml.")
)
return parser
def _parse_extra_vars(self, raw_extra_vars):
if raw_extra_vars is None:
extra_vars = {}
elif os.path.exists(raw_extra_vars):
with open(raw_extra_vars, 'r') as fp:
extra_vars = yaml.safe_load(fp.read())
else:
try:
extra_vars = yaml.safe_load(raw_extra_vars)
except yaml.YAMLError as exc:
raise RuntimeError(
_('--extra-vars is not an existing file and cannot be '
'parsed as YAML / JSON: %s') % exc)
return extra_vars
def _run_snapshot_overcloud(self, parsed_args):
"""Snapshot defined overcloud nodes."""
extra_vars = self._parse_extra_vars(parsed_args.extra_vars)
if not (os.path.isfile(parsed_args.inventory) and
os.access(parsed_args.inventory, os.R_OK)):
raise RuntimeError(
_('The inventory file {} does not exist or is not '
'readable'.format(parsed_args.inventory)))
self.log.debug(_('Starting Overcloud Snapshot'))
self._run_ansible_playbook(
playbook='cli-overcloud-snapshot.yaml',
inventory=parsed_args.inventory,
tags=None,
skip_tags=None,
extra_vars=extra_vars
)
def _run_ansible_playbook(self,
playbook,
inventory,
tags,
skip_tags,
extra_vars):
"""Run ansible playbook"""
with utils.TempDirs() as tmp:
utils.run_ansible_playbook(
playbook=playbook,
inventory=inventory,
workdir=tmp,
playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS,
tags=tags,
skip_tags=skip_tags,
verbosity=utils.playbook_verbosity(self=self),
extra_vars=extra_vars
)
def take_action(self, parsed_args):
self._run_snapshot_overcloud(parsed_args)