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:
parent
73c64fc242
commit
9824f80d95
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue