Add release parameter to subcloud-backup restore

Add optional --release parameter to subcloud-backup restore so that the
user can specify which loaded image to use during the installation
process triggered by --with-install.
This parameter can only be used if --with-install is also specified.

Test plan:
  PASS: Verify that the subcloud is restored with installation of
        current active release when specified.
  PASS: Verify that the subcloud is restored with installation of
        previous inactive release when specified.
  PASS: Verify that the subcloud is restored with installation of
        current active release if the "--release" parameter is omitted.

Story: 2010611
Task: 47709

Signed-off-by: Victor Romano <victor.gluzromano@windriver.com>
Change-Id: I619a1bf50c221fe6fc8cdfa87724d18393aef9cb
This commit is contained in:
Victor Romano 2023-03-24 01:31:34 -03:00
parent 73c64fc242
commit 9824f80d95
7 changed files with 321 additions and 15 deletions

View File

@ -1031,6 +1031,7 @@ serviceUnavailable (503)
.. rest_parameters:: parameters.yaml
- with_install: with_install
- release: release
- local_only: backup_local_only
- registry_images: backup_registry_images
- sysadmin_password: sysadmin_password

View File

@ -2,7 +2,8 @@
"subcloud": 1,
"local_only": "false",
"registry_images": "false",
"with_install": "false",
"with_install": "true",
"release": "21.12",
"sysadmin_password": "XXXXXXX",
"restore_values": {
"backup_filename": "filename"

View File

@ -1,22 +1,22 @@
#
# Copyright (c) 2022 Wind River Systems, Inc.
# Copyright (c) 2022-2023 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import json
from collections import namedtuple
import json
import os
from oslo_config import cfg
from oslo_log import log as logging
from oslo_messaging import RemoteError
import pecan
import yaml
from pecan import expose
from pecan import request as pecan_request
from pecan import response
import tsconfig.tsconfig as tsc
import yaml
from dcmanager.api.controllers import restcomm
from dcmanager.api.policies import subcloud_backup as subcloud_backup_policy
@ -28,6 +28,7 @@ from dcmanager.common import utils
from dcmanager.db import api as db_api
from dcmanager.rpc import client as rpc_client
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
@ -71,6 +72,7 @@ class SubcloudBackupController(object):
elif verb == 'restore':
expected_params = {
"with_install": "text",
"release": "text",
"local_only": "text",
"registry_images": "text",
"sysadmin_password": "text",
@ -339,6 +341,10 @@ class SubcloudBackupController(object):
pecan.abort(400, _('Option registry_images cannot be used '
'without local_only option.'))
if not payload['with_install'] and payload.get('release'):
pecan.abort(400, _('Option release cannot be used '
'without with_install option.'))
request_entity = self._read_entity_from_request_params(context, payload)
if len(request_entity.subclouds) == 0:
msg = "No subclouds exist under %s %s" % (request_entity.type,
@ -359,8 +365,9 @@ class SubcloudBackupController(object):
'install data.'))
if payload.get('with_install'):
# Confirm the active system controller load is still in dc-vault
matching_iso, err_msg = utils.get_matching_iso()
# Confirm the requested or active load is still in dc-vault
payload['software_version'] = payload.get('release', tsc.SW_VERSION)
matching_iso, err_msg = utils.get_matching_iso(payload['software_version'])
if err_msg:
LOG.exception(err_msg)
pecan.abort(400, _(err_msg))

View File

@ -742,10 +742,14 @@ def _is_valid_for_backup_delete(subcloud):
def _is_valid_for_backup_restore(subcloud):
msg = None
if subcloud.management_state != dccommon_consts.MANAGEMENT_UNMANAGED \
or subcloud.deploy_status in consts.INVALID_DEPLOY_STATES_FOR_RESTORE:
msg = ('Subcloud %s must be unmanaged and in a valid deploy state '
'for the subcloud-backup restore operation.' % subcloud.name)
elif not subcloud.data_install:
msg = ('Data installation on %s is missing.' % subcloud.name)
if msg:
raise exceptions.ValidateFail(msg)
return True

View File

@ -745,8 +745,9 @@ class SubcloudManager(manager.Manager):
def _subcloud_operation_notice(
self, operation, restore_subclouds, failed_subclouds,
invalid_subclouds):
all_failed = not set(restore_subclouds) - set(failed_subclouds)
if restore_subclouds and all_failed:
all_failed = ((not set(restore_subclouds) - set(failed_subclouds))
and not invalid_subclouds)
if all_failed:
LOG.error("Backup %s failed for all applied subclouds" % operation)
raise exceptions.SubcloudBackupOperationFailed(operation=operation)
@ -972,12 +973,13 @@ class SubcloudManager(manager.Manager):
return subcloud, False
if payload.get('with_install'):
software_version = payload.get('software_version')
install_command = self.compose_install_command(
subcloud.name, subcloud_inventory_file, subcloud.software_version)
subcloud.name, subcloud_inventory_file, software_version)
# Update data_install with missing data
matching_iso, _ = utils.get_vault_load_files(subcloud.software_version)
matching_iso, _ = utils.get_vault_load_files(software_version)
data_install['software_version'] = software_version
data_install['image'] = matching_iso
data_install['software_version'] = subcloud.software_version
data_install['ansible_ssh_pass'] = payload['sysadmin_password']
data_install['ansible_become_pass'] = payload['sysadmin_password']
install_success = self._run_subcloud_install(
@ -1371,7 +1373,8 @@ class SubcloudManager(manager.Manager):
db_api.subcloud_update(
context, subcloud.id,
deploy_status=consts.DEPLOY_STATE_INSTALLING,
error_description=consts.ERROR_DESC_EMPTY)
error_description=consts.ERROR_DESC_EMPTY,
software_version=str(payload['software_version']))
try:
install.install(consts.DC_ANSIBLE_LOG_DIR, install_command)
except Exception as e:

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2022 Wind River Systems, Inc.
# Copyright (c) 2022-2023 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@ -1216,3 +1216,101 @@ class TestSubcloudRestore(testroot.DCManagerApiTest):
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.patch_json, FAKE_URL_RESTORE,
headers=FAKE_HEADERS, params=data)
@mock.patch.object(rpc_client, 'ManagerClient')
@mock.patch('os.path.isdir')
@mock.patch('os.listdir')
def test_backup_restore_subcloud_with_install_no_release(self,
mock_listdir,
mock_isdir,
mock_rpc_client):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
data_install = str(fake_subcloud.FAKE_SUBCLOUD_INSTALL_VALUES).replace('\'', '"')
db_api.subcloud_update(self.ctx,
subcloud.id,
availability_status=dccommon_consts.AVAILABILITY_ONLINE,
management_state=dccommon_consts.MANAGEMENT_UNMANAGED,
data_install=data_install)
fake_password = (base64.b64encode('testpass'.encode("utf-8"))).decode('ascii')
data = {'sysadmin_password': fake_password,
'subcloud': '1',
'with_install': 'True'
}
mock_isdir.return_value = True
mock_listdir.return_value = ['test.iso', 'test.sig']
mock_rpc_client().restore_subcloud_backups.return_value = True
response = self.app.patch_json(FAKE_URL_RESTORE,
headers=FAKE_HEADERS,
params=data)
self.assertEqual(response.status_int, 200)
@mock.patch.object(rpc_client, 'ManagerClient')
@mock.patch('os.path.isdir')
@mock.patch('os.listdir')
def test_backup_restore_subcloud_with_install_with_release(self,
mock_listdir,
mock_isdir,
mock_rpc_client):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
data_install = str(fake_subcloud.FAKE_SUBCLOUD_INSTALL_VALUES).replace('\'', '"')
db_api.subcloud_update(self.ctx,
subcloud.id,
availability_status=dccommon_consts.AVAILABILITY_ONLINE,
management_state=dccommon_consts.MANAGEMENT_UNMANAGED,
data_install=data_install)
fake_password = (base64.b64encode('testpass'.encode("utf-8"))).decode('ascii')
data = {'sysadmin_password': fake_password,
'subcloud': '1',
'with_install': 'True',
'release': '22.12'
}
mock_isdir.return_value = True
mock_listdir.return_value = ['test.iso', 'test.sig']
mock_rpc_client().restore_subcloud_backups.return_value = True
response = self.app.patch_json(FAKE_URL_RESTORE,
headers=FAKE_HEADERS,
params=data)
self.assertEqual(response.status_int, 200)
@mock.patch.object(rpc_client, 'ManagerClient')
def test_backup_restore_subcloud_no_install_with_release(self, mock_rpc_client):
fake_password = (base64.b64encode('testpass'.encode("utf-8"))).decode('ascii')
data = {'sysadmin_password': fake_password,
'subcloud': '1',
'release': '22.12'
}
mock_rpc_client().restore_subcloud_backups.return_value = True
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.patch_json, FAKE_URL_RESTORE,
headers=FAKE_HEADERS, params=data)
@mock.patch.object(rpc_client, 'ManagerClient')
@mock.patch('dcmanager.common.utils.get_matching_iso')
def test_backup_restore_subcloud_invalid_release(self,
mock_rpc_client,
mock_matching_iso):
fake_password = (base64.b64encode('testpass'.encode("utf-8"))).decode('ascii')
data = {'sysadmin_password': fake_password,
'subcloud': '1',
'release': '00.00'
}
mock_rpc_client().restore_subcloud_backups.return_value = True
mock_matching_iso.return_value = [None, True]
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.patch_json, FAKE_URL_RESTORE,
headers=FAKE_HEADERS, params=data)

View File

@ -318,6 +318,17 @@ FAKE_BACKUP_CREATE_LOAD_1 = {
"registry_images": False,
}
FAKE_BACKUP_RESTORE_LOAD = {
"sysadmin_password": "testpasswd",
"subcloud": 1
}
FAKE_BACKUP_RESTORE_LOAD_WITH_INSTALL = {
"sysadmin_password": "testpasswd",
"subcloud": 1,
"install_values": fake_subcloud.FAKE_SUBCLOUD_INSTALL_VALUES
}
class Subcloud(object):
def __init__(self, data, is_online):
@ -2187,3 +2198,184 @@ class TestSubcloudManager(base.DCManagerTestCase):
mock_keystone_client, mock_sysinv_client)
expiry2 = cached_regionone_data['expiry']
self.assertEqual(expiry1, expiry2)
@mock.patch.object(subcloud_manager.SubcloudManager,
'_run_subcloud_backup_restore_playbook')
@mock.patch.object(subcloud_manager.SubcloudManager,
'_create_overrides_for_backup_or_restore')
@mock.patch.object(subcloud_manager.SubcloudManager,
'_create_subcloud_inventory_file')
def test_backup_restore_unmanaged_online(self,
mock_create_inventory_file,
mock_create_overrides,
mock_run_playbook
):
mock_create_inventory_file.return_value = 'inventory_file.yml'
mock_create_overrides.return_value = 'overrides_file.yml'
values = copy.copy(FAKE_BACKUP_RESTORE_LOAD)
subcloud = self.create_subcloud_static(
self.ctx,
name='subcloud1',
deploy_status=consts.DEPLOY_STATE_DONE)
data_install = str(fake_subcloud.FAKE_SUBCLOUD_INSTALL_VALUES).replace('\'', '"')
db_api.subcloud_update(self.ctx,
subcloud.id,
availability_status=dccommon_consts.AVAILABILITY_ONLINE,
management_state=dccommon_consts.MANAGEMENT_UNMANAGED,
data_install=data_install)
sm = subcloud_manager.SubcloudManager()
sm.restore_subcloud_backups(self.ctx, payload=values)
mock_create_inventory_file.assert_called_once()
mock_create_overrides.assert_called_once()
mock_run_playbook.assert_called_once()
# Verify that subcloud has the correct deploy status
updated_subcloud = db_api.subcloud_get_by_name(self.ctx, subcloud.name)
self.assertEqual(consts.DEPLOY_STATE_PRE_RESTORE,
updated_subcloud.deploy_status)
@mock.patch.object(subcloud_manager.SubcloudManager,
'_run_subcloud_backup_restore_playbook')
@mock.patch.object(subcloud_manager.SubcloudManager,
'_create_overrides_for_backup_or_restore')
@mock.patch.object(subcloud_manager.SubcloudManager,
'_create_subcloud_inventory_file')
def test_backup_restore_managed_online(self,
mock_create_inventory_file,
mock_create_overrides,
mock_run_playbook
):
values = copy.copy(FAKE_BACKUP_RESTORE_LOAD)
subcloud = self.create_subcloud_static(
self.ctx,
name='subcloud1',
deploy_status=consts.DEPLOY_STATE_NONE)
data_install = str(fake_subcloud.FAKE_SUBCLOUD_INSTALL_VALUES).replace('\'', '"')
db_api.subcloud_update(self.ctx,
subcloud.id,
availability_status=dccommon_consts.AVAILABILITY_ONLINE,
management_state=dccommon_consts.MANAGEMENT_MANAGED,
data_install=data_install)
sm = subcloud_manager.SubcloudManager()
return_log = sm.restore_subcloud_backups(self.ctx, payload=values)
expected_log = 'skipped for local backup restore operation'
self.assertIn(expected_log, return_log)
@mock.patch.object(subcloud_manager.SubcloudManager,
'_run_subcloud_backup_restore_playbook')
@mock.patch.object(subcloud_manager.SubcloudManager,
'_create_overrides_for_backup_or_restore')
@mock.patch.object(subcloud_manager.SubcloudManager,
'_create_subcloud_inventory_file')
def test_backup_restore_unmanaged_offline(self,
mock_create_inventory_file,
mock_create_overrides,
mock_run_playbook
):
values = copy.copy(FAKE_BACKUP_RESTORE_LOAD)
subcloud = self.create_subcloud_static(
self.ctx,
name='subcloud1',
deploy_status=consts.DEPLOY_STATE_NONE)
data_install = str(fake_subcloud.FAKE_SUBCLOUD_INSTALL_VALUES).replace('\'', '"')
db_api.subcloud_update(self.ctx,
subcloud.id,
availability_status=dccommon_consts.AVAILABILITY_OFFLINE,
management_state=dccommon_consts.MANAGEMENT_UNMANAGED,
data_install=data_install)
sm = subcloud_manager.SubcloudManager()
sm.restore_subcloud_backups(self.ctx, payload=values)
mock_create_inventory_file.assert_called_once()
mock_create_overrides.assert_called_once()
mock_run_playbook.assert_called_once()
# Verify that subcloud has the correct deploy status
updated_subcloud = db_api.subcloud_get_by_name(self.ctx, subcloud.name)
self.assertEqual(consts.DEPLOY_STATE_PRE_RESTORE,
updated_subcloud.deploy_status)
def test_backup_restore_managed_offline(self):
values = copy.copy(FAKE_BACKUP_RESTORE_LOAD)
subcloud = self.create_subcloud_static(
self.ctx,
name='subcloud1',
deploy_status=consts.DEPLOY_STATE_NONE)
db_api.subcloud_update(self.ctx,
subcloud.id,
availability_status=dccommon_consts.AVAILABILITY_OFFLINE,
management_state=dccommon_consts.MANAGEMENT_MANAGED)
sm = subcloud_manager.SubcloudManager()
return_log = sm.restore_subcloud_backups(self.ctx, payload=values)
expected_log = 'skipped for local backup restore operation'
self.assertIn(expected_log, return_log)
@mock.patch.object(subcloud_manager.SubcloudManager,
'_run_subcloud_backup_restore_playbook')
@mock.patch.object(subcloud_manager.SubcloudManager, '_run_subcloud_install')
@mock.patch.object(subcloud_manager.SubcloudManager,
'_create_overrides_for_backup_or_restore')
@mock.patch.object(subcloud_manager.SubcloudManager,
'_create_subcloud_inventory_file')
@mock.patch('os.path.isdir')
@mock.patch('os.listdir')
def test_backup_restore_with_install(self,
mock_listdir,
mock_isdir,
mock_create_inventory_file,
mock_create_overrides,
mock_subcloud_install,
mock_run_restore_playbook
):
mock_isdir.return_value = True
mock_listdir.return_value = ['test.iso', 'test.sig']
mock_create_inventory_file.return_value = 'inventory_file.yml'
mock_create_overrides.return_value = 'overrides_file.yml'
mock_subcloud_install.return_value = True
mock_run_restore_playbook.return_value = True
data_install = str(fake_subcloud.FAKE_SUBCLOUD_INSTALL_VALUES).replace('\'', '"')
values = copy.copy(FAKE_BACKUP_RESTORE_LOAD_WITH_INSTALL)
values['with_install'] = True
subcloud = self.create_subcloud_static(
self.ctx,
name='subcloud1',
data_install=data_install,
deploy_status=consts.DEPLOY_STATE_DONE)
db_api.subcloud_update(self.ctx,
subcloud.id,
availability_status=dccommon_consts.AVAILABILITY_ONLINE,
management_state=dccommon_consts.MANAGEMENT_UNMANAGED)
sm = subcloud_manager.SubcloudManager()
sm.restore_subcloud_backups(self.ctx, payload=values)
mock_create_inventory_file.assert_called_once()
mock_create_overrides.assert_called_once()
# Verify that subcloud has the correct deploy status
updated_subcloud = db_api.subcloud_get_by_name(self.ctx, subcloud.name)
self.assertEqual(consts.DEPLOY_STATE_PRE_RESTORE,
updated_subcloud.deploy_status)