From 183e62525cb59dbfd43775b0ba0df03d2acee8e2 Mon Sep 17 00:00:00 2001 From: Hugo Brito Date: Tue, 8 Nov 2022 11:55:21 -0300 Subject: [PATCH] Deprecate dcmanager subcloud restore API With the new dcmanager subcloud-backup restore [1][2] the old restore code can be removed. This removes all code related to the legacy restore and returns error 410 if the user tries to access the old API. 1: https://review.opendev.org/c/starlingx/distcloud/+/862431 2: https://review.opendev.org/c/starlingx/distcloud/+/860598 Test Plan: PASS - Test add subcloud Story: 2010116 Task: 46763 Signed-off-by: Hugo Brito Change-Id: I185464424da7b853a644ec905bdb383ce5c857f9 --- api-ref/source/api-ref-dcmanager-v1.rst | 101 ++------ .../subcloud-patch-restore-request.json | 5 - .../subcloud-patch-restore-response.json | 25 -- .../dcmanager/api/controllers/v1/subclouds.py | 137 +---------- distributedcloud/dcmanager/manager/service.py | 8 - .../dcmanager/manager/subcloud_manager.py | 217 +----------------- distributedcloud/dcmanager/rpc/client.py | 5 - .../unit/api/v1/controllers/test_subclouds.py | 146 ------------ .../unit/manager/test_subcloud_manager.py | 111 --------- 9 files changed, 28 insertions(+), 727 deletions(-) delete mode 100644 api-ref/source/samples/subclouds/subcloud-patch-restore-request.json delete mode 100644 api-ref/source/samples/subclouds/subcloud-patch-restore-response.json diff --git a/api-ref/source/api-ref-dcmanager-v1.rst b/api-ref/source/api-ref-dcmanager-v1.rst index e5c9b22dd..e6c0f8772 100644 --- a/api-ref/source/api-ref-dcmanager-v1.rst +++ b/api-ref/source/api-ref-dcmanager-v1.rst @@ -26,7 +26,7 @@ This operation does not accept a request body. **Error response codes** -badRequest (400), unauthorized (401), forbidden (403), +badRequest (400), unauthorized (401), forbidden (403), itemNotFound (404), badMethod (405), HTTPUnprocessableEntity (422), internalServerError (500), serviceUnavailable (503) @@ -57,7 +57,7 @@ This operation does not accept a request body. **Error response codes** -badRequest (400), unauthorized (401), forbidden (403), +badRequest (400), unauthorized (401), forbidden (403), itemNotFound (404), badMethod (405), HTTPUnprocessableEntity (422), internalServerError (500), serviceUnavailable (503) @@ -171,7 +171,7 @@ Request Example - management-gateway-ip: management_gateway_ip - management-start-ip: management_start_ip - management-end-ip: management_end_ip - + Response Example ---------------- @@ -191,7 +191,7 @@ Shows information about a specific subcloud **Error response codes** -badRequest (400), unauthorized (401), forbidden (403), +badRequest (400), unauthorized (401), forbidden (403), itemNotFound (404), badMethod (405), HTTPUnprocessableEntity (422), internalServerError (500), serviceUnavailable (503) @@ -253,7 +253,7 @@ Shows additional information about a specific subcloud **Error response codes** -badRequest (400), unauthorized (401), forbidden (403), +badRequest (400), unauthorized (401), forbidden (403), itemNotFound (404), badMethod (405), HTTPUnprocessableEntity (422), internalServerError (500), serviceUnavailable (503) @@ -525,79 +525,8 @@ Response Example .. literalinclude:: samples/subclouds/subcloud-patch-reinstall-response.json :language: json -******************************************************** -Restores a specific subcloud from platform backup data -******************************************************** - -.. rest_method:: PATCH /v1.0/subclouds/{subcloud}/restore - -Accepts Content-Type multipart/form-data. - - -**Normal response codes** - -200 - -**Error response codes** - -badRequest (400), unauthorized (401), forbidden (403), badMethod (405), -HTTPUnprocessableEntity (422), internalServerError (500), -serviceUnavailable (503) - -**Request parameters** - -.. rest_parameters:: parameters.yaml - - - subcloud: subcloud_uri - - restore_values: restore_values - - sysadmin_password: sysadmin_password - - with_install: with_install - -Request Example ----------------- - -.. literalinclude:: samples/subclouds/subcloud-patch-restore-request.json - :language: json - - -**Response parameters** - -.. rest_parameters:: parameters.yaml - - - id: subcloud_id - - group_id: group_id - - name: subcloud_name - - description: subcloud_description - - location: subcloud_location - - software-version: software_version - - availability-status: availability_status - - error-description: error_description - - deploy-status: deploy_status - - backup-status: backup_status - - backup-datetime: backup_datetime - - openstack-installed: openstack_installed - - management-state: management_state - - systemcontroller-gateway-ip: systemcontroller_gateway_ip - - management-start-ip: management_start_ip - - management-end-ip: management_end_ip - - management-subnet: management_subnet - - management-gateway-ip: management_gateway_ip - - created-at: created_at - - updated-at: updated_at - - data_install: data_install - - data_upgrade: data_upgrade - - endpoint_sync_status: endpoint_sync_status - - sync_status: sync_status - - endpoint_type: sync_status_type - -Response Example ----------------- - -.. literalinclude:: samples/subclouds/subcloud-patch-restore-response.json - :language: json - ***************************************** -Update the status of a specific subcloud +Update the status of a specific subcloud ***************************************** .. rest_method:: PATCH /v1.0/subclouds/{subcloud}/update_status @@ -680,7 +609,7 @@ This operation does not accept a request body. **Error response codes** -badRequest (400), unauthorized (401), forbidden (403), +badRequest (400), unauthorized (401), forbidden (403), badMethod (405), HTTPUnprocessableEntity (422), internalServerError (500), serviceUnavailable (503) @@ -767,7 +696,7 @@ Shows information about a specific subcloud group **Error response codes** -badRequest (400), unauthorized (401), forbidden (403), +badRequest (400), unauthorized (401), forbidden (403), itemNotFound (404), badMethod (405), HTTPUnprocessableEntity (422), internalServerError (500), serviceUnavailable (503) @@ -810,7 +739,7 @@ Shows subclouds that are part of a subcloud group **Error response codes** -badRequest (400), unauthorized (401), forbidden (403), +badRequest (400), unauthorized (401), forbidden (403), itemNotFound (404), badMethod (405), HTTPUnprocessableEntity (422), internalServerError (500), serviceUnavailable (503) @@ -1198,7 +1127,7 @@ Shows the details of the update strategy **Error response codes** -badRequest (400), unauthorized (401), forbidden (403), +badRequest (400), unauthorized (401), forbidden (403), itemNotFound (404), badMethod (405), HTTPUnprocessableEntity (422), internalServerError (500), serviceUnavailable (503) @@ -1303,7 +1232,7 @@ Deletes the update strategy **Error response codes** -badRequest (400), unauthorized (401), forbidden (403), +badRequest (400), unauthorized (401), forbidden (403), itemNotFound (404), badMethod (405), HTTPUnprocessableEntity (422), internalServerError (500), serviceUnavailable (503) @@ -1410,7 +1339,7 @@ This operation does not accept a request body. **Error response codes** -badRequest (400), unauthorized (401), forbidden (403), +badRequest (400), unauthorized (401), forbidden (403), itemNotFound (404), badMethod (405), HTTPUnprocessableEntity (422), internalServerError (500), serviceUnavailable (503) @@ -1448,7 +1377,7 @@ Shows the details of patch strategy steps for a particular cloud **Error response codes** -badRequest (400), unauthorized (401), forbidden (403), +badRequest (400), unauthorized (401), forbidden (403), itemNotFound (404), badMethod (405), HTTPUnprocessableEntity (422), internalServerError (500), serviceUnavailable (503) @@ -1500,7 +1429,7 @@ This operation does not accept a request body. **Error response codes** -badRequest (400), unauthorized (401), forbidden (403), +badRequest (400), unauthorized (401), forbidden (403), itemNotFound (404), badMethod (405), HTTPUnprocessableEntity (422), internalServerError (500), serviceUnavailable (503) @@ -1539,7 +1468,7 @@ Shows sw-update options (defaults or per subcloud). Use ``RegionOne`` as subclou **Error response codes** -badRequest (400), unauthorized (401), forbidden (403), +badRequest (400), unauthorized (401), forbidden (403), itemNotFound (404), badMethod (405), HTTPUnprocessableEntity (422), internalServerError (500), serviceUnavailable (503) diff --git a/api-ref/source/samples/subclouds/subcloud-patch-restore-request.json b/api-ref/source/samples/subclouds/subcloud-patch-restore-request.json deleted file mode 100644 index 0c3ba4cab..000000000 --- a/api-ref/source/samples/subclouds/subcloud-patch-restore-request.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "sysadmin_password": "XXXXXXX", - "restore_values": "path to some file", - "with_install": false -} diff --git a/api-ref/source/samples/subclouds/subcloud-patch-restore-response.json b/api-ref/source/samples/subclouds/subcloud-patch-restore-response.json deleted file mode 100644 index d586f1939..000000000 --- a/api-ref/source/samples/subclouds/subcloud-patch-restore-response.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "id": 1, - "name": "subcloud1", - "created-at": "2021-11-08T18:41:19.530228", - "updated-at": "2021-11-15T14:15:59.944851", - "availability-status": "offline", - "data_install": { - "bootstrap_interface": "eno1" - }, - "data_upgrade": null, - "deploy-status": "pre-restore", - "backup-status": "complete", - "backup-datetime": "2022-07-08 11:23:58.132134", - "description": "Ottawa Site", - "group_id": 1, - "location": "YOW", - "management-end-ip": "192.168.101.50", - "management-gateway-ip": "192.168.101.1", - "management-start-ip": "192.168.101.2", - "management-state": "unmanaged", - "management-subnet": "192.168.101.0/24", - "openstack-installed": false, - "software-version": "21.12", - "systemcontroller-gateway-ip": "192.168.204.101", -} diff --git a/distributedcloud/dcmanager/api/controllers/v1/subclouds.py b/distributedcloud/dcmanager/api/controllers/v1/subclouds.py index e770ed70d..d491dee9d 100644 --- a/distributedcloud/dcmanager/api/controllers/v1/subclouds.py +++ b/distributedcloud/dcmanager/api/controllers/v1/subclouds.py @@ -68,7 +68,6 @@ LOCK_NAME = 'SubcloudsController' BOOTSTRAP_VALUES = 'bootstrap_values' INSTALL_VALUES = 'install_values' -RESTORE_VALUES = 'restore_values' SUBCLOUD_ADD_MANDATORY_FILE = [ BOOTSTRAP_VALUES, @@ -78,10 +77,6 @@ SUBCLOUD_RECONFIG_MANDATORY_FILE = [ consts.DEPLOY_CONFIG, ] -SUBCLOUD_RESTORE_MANDATORY_FILE = [ - RESTORE_VALUES, -] - SUBCLOUD_ADD_GET_FILE_CONTENTS = [ BOOTSTRAP_VALUES, INSTALL_VALUES, @@ -98,19 +93,6 @@ INSTALL_VALUES_ADDRESSES = [ 'network_address' ] -# The following parameters can be provided by the user for -# remote subcloud restore -# - initial_backup_dir (default to /opt/platform-backup) -# - backup_filename (mandatory parameter) -# - ansible_ssh_pass (sysadmin_password) -# - ansible_become_pass (sysadmin_password) -# - on_box_data (default to true) -# - wipe_ceph_osds (default to false) -# - ansible_remote_tmp (default to /tmp) -MANDATORY_RESTORE_VALUES = [ - 'backup_filename', -] - def _get_multipart_field_name(part): content = part.headers[b"Content-Disposition"].decode("utf8") @@ -278,32 +260,6 @@ class SubcloudsController(object): self._get_common_deploy_files(payload) return payload - @staticmethod - def _get_restore_payload(request): - payload = dict() - for f in SUBCLOUD_RESTORE_MANDATORY_FILE: - if f not in request.POST: - pecan.abort(400, _("Missing required file for %s") % f) - - multipart_data = decoder.MultipartDecoder( - request.body, pecan.request.headers.get('Content-Type')) - for f in SUBCLOUD_RESTORE_MANDATORY_FILE: - for part in multipart_data.parts: - for hk, hv in part.headers.items(): - hv = hv.decode('utf8') - if hk.decode('utf8') == 'Content-Disposition': - if f in hv: - file_item = request.POST[f] - file_item.file.seek(0, os.SEEK_SET) - data = yaml.safe_load( - file_item.file.read().decode('utf8')) - payload.update({RESTORE_VALUES: data}) - elif "sysadmin_password" in hv: - payload.update({'sysadmin_password': part.content}) - elif "with_install" in hv: - payload.update({'with_install': part.content}) - return payload - def _get_config_file_path(self, subcloud_name, config_file_type=None): if config_file_type == consts.DEPLOY_CONFIG: file_path = os.path.join( @@ -694,14 +650,6 @@ class SubcloudsController(object): return True - @staticmethod - def _validate_restore_values(payload): - """Validate the restore values to ensure parameters for remote restore are present""" - restore_values = payload.get(RESTORE_VALUES) - for p in MANDATORY_RESTORE_VALUES: - if p not in restore_values: - pecan.abort(400, _('Mandatory restore value %s not present') % p) - def _get_subcloud_users(self): """Get the subcloud users and passwords from keyring""" DEFAULT_SERVICE_PROJECT_NAME = 'services' @@ -1354,89 +1302,8 @@ class SubcloudsController(object): LOG.exception("Unable to reinstall subcloud %s" % subcloud.name) pecan.abort(500, _('Unable to reinstall subcloud')) elif verb == "restore": - payload = self._get_restore_payload(request) - if not payload: - pecan.abort(400, _('Body required')) - - if subcloud.management_state != dccommon_consts.MANAGEMENT_UNMANAGED: - pecan.abort(400, _('Subcloud can not be restored while it is still ' - 'in managed state. Please unmanage the subcloud ' - 'and try again.')) - elif subcloud.deploy_status in [consts.DEPLOY_STATE_INSTALLING, - consts.DEPLOY_STATE_BOOTSTRAPPING, - consts.DEPLOY_STATE_DEPLOYING]: - pecan.abort(400, _('This operation is not allowed while subcloud install, ' - 'bootstrap or deploy is in progress.')) - - sysadmin_password = payload.get('sysadmin_password') - if not sysadmin_password: - pecan.abort(400, _('subcloud sysadmin_password required')) - - try: - payload['sysadmin_password'] = base64.b64decode( - sysadmin_password).decode('utf-8') - except Exception: - msg = _('Failed to decode subcloud sysadmin_password, ' - 'verify the password is base64 encoded') - LOG.exception(msg) - pecan.abort(400, msg) - - with_install = payload.get('with_install') - - if with_install is not None: - if with_install == 'true' or with_install == 'True': - payload.update({'with_install': True}) - elif with_install == 'false' or with_install == 'False': - payload.update({'with_install': False}) - else: - pecan.abort(400, _('Invalid with_install value')) - - self._validate_restore_values(payload) - - if with_install: - # Request to remote install as part of subcloud restore. Confirm the - # subcloud install data in the db still contain the required parameters - # for remote install. - install_values = self._get_subcloud_db_install_values(subcloud) - payload.update({ - 'install_values': install_values, - }) - - # Get the active system controller load is still in dc-vault - matching_iso, err_msg = utils.get_matching_iso() - if err_msg: - LOG.exception(err_msg) - pecan.abort(400, _(err_msg)) - else: - # Not Redfish capable subcloud. The subcloud has been reinstalled - # and required patches have been applied. - # - # Pseudo code: - # - Retrieve install_values of the subcloud from the database. - # If it does not exist, try to retrieve the bootstrap address - # from its ansible inventory file (/var/opt/dc/ansible). - # - If the bootstrap address can be obtained, add install_values - # to the payload and continue. - # - If the bootstrap address cannot be obtained, abort with an - # error message advising the user to run "dcmanager subcloud - # update --bootstrap-address " command - msg = _('This operation is not yet supported for subclouds without ' - 'remote install capability.') - LOG.exception(msg) - pecan.abort(400, msg) - - try: - self.dcmanager_rpc_client.restore_subcloud(context, - subcloud_id, - payload) - # Return deploy_status as pre-restore - subcloud.deploy_status = consts.DEPLOY_STATE_PRE_RESTORE - return db_api.subcloud_db_model_to_dict(subcloud) - except RemoteError as e: - pecan.abort(422, e.value) - except Exception: - LOG.exception("Unable to restore subcloud %s" % subcloud.name) - pecan.abort(500, _('Unable to restore subcloud')) + pecan.abort(410, _('This API is deprecated. ' + 'Please use /v1.0/subcloud-backup/restore')) elif verb == 'update_status': res = self.updatestatus(subcloud.name) return res diff --git a/distributedcloud/dcmanager/manager/service.py b/distributedcloud/dcmanager/manager/service.py index 1c8be4fe4..611ee553d 100644 --- a/distributedcloud/dcmanager/manager/service.py +++ b/distributedcloud/dcmanager/manager/service.py @@ -162,14 +162,6 @@ class DCManagerService(service.Service): (entity, (payload.get('subcloud') or payload.get('group')))) return self.subcloud_manager.restore_subcloud_backups(context, payload) - @request_context - def restore_subcloud(self, context, subcloud_id, payload): - # Restore a subcloud - LOG.info("Handling restore_subcloud request for: %s" % subcloud_id) - return self.subcloud_manager.restore_subcloud(context, - subcloud_id, - payload) - @request_context def update_subcloud_sync_endpoint_type(self, context, subcloud_name, endpoint_type_list, diff --git a/distributedcloud/dcmanager/manager/subcloud_manager.py b/distributedcloud/dcmanager/manager/subcloud_manager.py index e65fc513d..a93632e10 100644 --- a/distributedcloud/dcmanager/manager/subcloud_manager.py +++ b/distributedcloud/dcmanager/manager/subcloud_manager.py @@ -21,16 +21,17 @@ import datetime import filecmp import functools import json -import keyring -import netaddr import os import threading import time from eventlet import greenpool +from fm_api import constants as fm_const +from fm_api import fm_api +import keyring +import netaddr from oslo_log import log as logging from oslo_messaging import RemoteError - from tsconfig.tsconfig import CONFIG_PATH from tsconfig.tsconfig import SW_VERSION @@ -41,25 +42,20 @@ from dccommon.exceptions import PlaybookExecutionFailed from dccommon import kubeoperator from dccommon.subcloud_install import SubcloudInstall from dccommon.utils import run_playbook -from dcmanager.common.exceptions import DCManagerException -from dcmanager.db.sqlalchemy.models import Subcloud - -from dcorch.rpc import client as dcorch_rpc_client - from dcmanager.audit import rpcapi as dcmanager_audit_rpc_client from dcmanager.common import consts from dcmanager.common.consts import INVENTORY_FILE_POSTFIX from dcmanager.common import context as dcmanager_context from dcmanager.common import exceptions +from dcmanager.common.exceptions import DCManagerException from dcmanager.common.i18n import _ from dcmanager.common import manager from dcmanager.common import prestage from dcmanager.common import utils from dcmanager.db import api as db_api +from dcmanager.db.sqlalchemy.models import Subcloud from dcmanager.rpc import client as dcmanager_rpc_client - -from fm_api import constants as fm_const -from fm_api import fm_api +from dcorch.rpc import client as dcorch_rpc_client LOG = logging.getLogger(__name__) @@ -74,16 +70,12 @@ ANSIBLE_SUBCLOUD_BACKUP_DELETE_PLAYBOOK = \ '/usr/share/ansible/stx-ansible/playbooks/delete_subcloud_backup.yml' ANSIBLE_SUBCLOUD_BACKUP_RESTORE_PLAYBOOK = \ '/usr/share/ansible/stx-ansible/playbooks/restore_subcloud_backup.yml' -ANSIBLE_HOST_VALIDATION_PLAYBOOK = \ - '/usr/share/ansible/stx-ansible/playbooks/validate_host.yml' ANSIBLE_SUBCLOUD_PLAYBOOK = \ '/usr/share/ansible/stx-ansible/playbooks/bootstrap.yml' ANSIBLE_SUBCLOUD_INSTALL_PLAYBOOK = \ '/usr/share/ansible/stx-ansible/playbooks/install.yml' ANSIBLE_SUBCLOUD_REHOME_PLAYBOOK = \ '/usr/share/ansible/stx-ansible/playbooks/rehome_subcloud.yml' -ANSIBLE_SUBCLOUD_RESTORE_PLAYBOOK = \ - '/usr/share/ansible/stx-ansible/playbooks/restore_platform.yml' USERS_TO_REPLICATE = [ 'sysinv', @@ -92,7 +84,8 @@ USERS_TO_REPLICATE = [ 'mtce', 'fm', 'barbican', - 'dcmanager'] + 'dcmanager' +] # The timeout of the rehome playbook is set to 180 seconds as it takes a # long time for privilege escalation before resetting the host route and @@ -252,28 +245,6 @@ class SubcloudManager(manager.Manager): ] return deploy_command - def compose_check_target_command(self, subcloud_name, - ansible_subcloud_inventory_file, payload): - check_target_command = [ - "ansible-playbook", ANSIBLE_HOST_VALIDATION_PLAYBOOK, - "-i", ansible_subcloud_inventory_file, - "--limit", subcloud_name, - "-e", "@%s" % consts.ANSIBLE_OVERRIDES_PATH + "/" + - subcloud_name + "_check_target_values.yml"] - - return check_target_command - - def compose_restore_command(self, subcloud_name, - ansible_subcloud_inventory_file, payload): - restore_command = [ - "ansible-playbook", ANSIBLE_SUBCLOUD_RESTORE_PLAYBOOK, - "-i", ansible_subcloud_inventory_file, - "--limit", subcloud_name, - "-e", "@%s" % consts.ANSIBLE_OVERRIDES_PATH + "/" + - subcloud_name + "_restore_values.yml"] - - return restore_command - def compose_backup_command(self, subcloud_name, ansible_subcloud_inventory_file): backup_command = [ "ansible-playbook", ANSIBLE_SUBCLOUD_BACKUP_CREATE_PLAYBOOK, @@ -479,7 +450,7 @@ class SubcloudManager(manager.Manager): apply_thread = threading.Thread( target=self.run_deploy, args=(subcloud, payload, context, - None, None, None, None, None, rehome_command)) + None, None, None, rehome_command)) else: install_command = None if "install_values" in payload: @@ -622,52 +593,6 @@ class SubcloudManager(manager.Manager): context, subcloud_id, deploy_status=consts.DEPLOY_STATE_PRE_INSTALL_FAILED) - def _create_check_target_override_file(self, payload, subcloud_name): - check_target_override_file = os.path.join( - consts.ANSIBLE_OVERRIDES_PATH, subcloud_name + - '_check_target_values.yml') - - with open(check_target_override_file, 'w') as f_out: - f_out.write( - '---\n' - ) - for k, v in payload['check_target_values'].items(): - f_out.write("%s: %s\n" % (k, json.dumps(v))) - - def _create_restore_override_file(self, payload, subcloud_name): - restore_override_file = os.path.join( - consts.ANSIBLE_OVERRIDES_PATH, subcloud_name + - '_restore_values.yml') - - with open(restore_override_file, 'w') as f_out: - f_out.write( - '---\n' - ) - for k, v in payload['restore_values'].items(): - f_out.write("%s: %s\n" % (k, json.dumps(v))) - - def _prepare_for_restore(self, payload, subcloud_name): - payload['check_target_values'] = dict() - payload['check_target_values']['ansible_ssh_pass'] = \ - payload['sysadmin_password'] - payload['check_target_values']['software_version'] = SW_VERSION - payload['check_target_values']['bootstrap_address'] = \ - payload['bootstrap-address'] - payload['check_target_values']['check_bootstrap_address'] = 'true' - payload['check_target_values']['check_patches'] = 'false' - - self._create_check_target_override_file(payload, subcloud_name) - - payload['restore_values']['ansible_ssh_pass'] = \ - payload['sysadmin_password'] - payload['restore_values']['ansible_become_pass'] = \ - payload['sysadmin_password'] - payload['restore_values']['admin_password'] = \ - str(keyring.get_password('CGCS', 'admin')) - payload['restore_values']['skip_patches_restore'] = 'true' - - self._create_restore_override_file(payload, subcloud_name) - def create_subcloud_backups(self, context, payload): """Backup subcloud or group of subclouds @@ -1211,87 +1136,12 @@ class SubcloudManager(manager.Manager): except Exception as e: LOG.exception(e) - def restore_subcloud(self, context, subcloud_id, payload): - """Restore subcloud - - :param context: request context object - :param subcloud_id: subcloud id from db - :param payload: subcloud restore detail - """ - - # Retrieve the subcloud details from the database - subcloud = db_api.subcloud_get(context, subcloud_id) - - if subcloud.management_state != dccommon_consts.MANAGEMENT_UNMANAGED: - raise exceptions.SubcloudNotUnmanaged() - - db_api.subcloud_update(context, subcloud_id, - deploy_status=consts.DEPLOY_STATE_PRE_RESTORE) - - try: - # Ansible inventory filename for the specified subcloud - ansible_subcloud_inventory_file = self._get_ansible_filename( - subcloud.name, INVENTORY_FILE_POSTFIX) - - # Add parameters used to generate inventory - payload['name'] = subcloud.name - payload['bootstrap-address'] = \ - payload['install_values']['bootstrap_address'] - payload['software_version'] = SW_VERSION - - install_command = None - - if payload['with_install']: - # Redfish capable subclouds - LOG.info("Reinstalling subcloud %s." % subcloud.name) - - # Disegard the current 'image' config. Always reinstall with - # the system controller active image in dc-vault. - matching_iso, matching_sig = utils.get_vault_load_files(SW_VERSION) - - payload['install_values'].update({'image': matching_iso}) - payload['install_values']['ansible_ssh_pass'] = \ - payload['sysadmin_password'] - - utils.create_subcloud_inventory(payload, - ansible_subcloud_inventory_file) - - install_command = self.compose_install_command( - subcloud.name, ansible_subcloud_inventory_file) - - else: - # Non Redfish capable subcloud - # Shouldn't get here as the API has already rejected the request. - return - - # Prepare for restore - self._prepare_for_restore(payload, subcloud.name) - check_target_command = self.compose_check_target_command( - subcloud.name, ansible_subcloud_inventory_file, payload) - - restore_command = self.compose_restore_command( - subcloud.name, ansible_subcloud_inventory_file, payload) - - apply_thread = threading.Thread( - target=self.run_deploy, - args=(subcloud, payload, context, - install_command, None, None, check_target_command, restore_command)) - apply_thread.start() - return db_api.subcloud_db_model_to_dict(subcloud) - - except Exception: - LOG.exception("Failed to restore subcloud %s" % subcloud.name) - db_api.subcloud_update( - context, subcloud_id, - deploy_status=consts.DEPLOY_STATE_RESTORE_PREP_FAILED) - # TODO(kmacleod) add outer try/except here to catch and log unexpected # exception. As this stands, any uncaught exception is a silent (unlogged) # failure def run_deploy(self, subcloud, payload, context, install_command=None, apply_command=None, - deploy_command=None, check_target_command=None, - restore_command=None, rehome_command=None): + deploy_command=None, rehome_command=None): log_file = os.path.join(consts.DC_ANSIBLE_LOG_DIR, subcloud.name) + \ '_playbook_output.log' @@ -1302,28 +1152,6 @@ class SubcloudManager(manager.Manager): ) if not install_success: return - - # Leave the following block here in case there is another use - # case besides subcloud restore where validating host post - # fresh install is necessary. - if check_target_command: - try: - run_playbook(log_file, check_target_command) - except PlaybookExecutionFailed: - msg = "Failed to run the validate host playbook" \ - " for subcloud %s, check individual log at " \ - "%s for detailed output." % ( - subcloud.name, - log_file) - LOG.error(msg) - if restore_command: - db_api.subcloud_update( - context, subcloud.id, - deploy_status=consts.DEPLOY_STATE_RESTORE_PREP_FAILED) - return - - LOG.info("Successfully checked subcloud %s" % subcloud.name) - if apply_command: try: # Update the subcloud to bootstrapping @@ -1349,7 +1177,6 @@ class SubcloudManager(manager.Manager): error_description=msg[0:consts.ERROR_DESCRIPTION_LENGTH]) return LOG.info("Successfully bootstrapped %s" % subcloud.name) - if deploy_command: # Run the custom deploy playbook LOG.info("Starting deploy of %s" % subcloud.name) @@ -1370,28 +1197,6 @@ class SubcloudManager(manager.Manager): error_description=msg[0:consts.ERROR_DESCRIPTION_LENGTH]) return LOG.info("Successfully deployed %s" % subcloud.name) - elif restore_command: - db_api.subcloud_update( - context, subcloud.id, - deploy_status=consts.DEPLOY_STATE_RESTORING) - - # Run the restore platform playbook - try: - run_playbook(log_file, restore_command) - except PlaybookExecutionFailed: - msg = "Failed to run the subcloud restore playbook" \ - " for subcloud %s, check individual log at " \ - "%s for detailed output." % ( - subcloud.name, - log_file) - LOG.error(msg) - db_api.subcloud_update( - context, subcloud.id, - deploy_status=consts.DEPLOY_STATE_RESTORE_FAILED) - return - LOG.info("Successfully restored controller-0 of subcloud %s" % - subcloud.name) - if rehome_command: # Update the deploy status to rehoming db_api.subcloud_update( diff --git a/distributedcloud/dcmanager/rpc/client.py b/distributedcloud/dcmanager/rpc/client.py index e7ee8d3e6..7d18eb1fe 100644 --- a/distributedcloud/dcmanager/rpc/client.py +++ b/distributedcloud/dcmanager/rpc/client.py @@ -158,11 +158,6 @@ class ManagerClient(RPCClient): release_version=release_version, payload=payload)) - def restore_subcloud(self, ctxt, subcloud_id, payload): - return self.cast(ctxt, self.make_msg('restore_subcloud', - subcloud_id=subcloud_id, - payload=payload)) - def restore_subcloud_backups(self, ctxt, payload): return self.cast(ctxt, self.make_msg('restore_subcloud_backups', payload=payload)) diff --git a/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_subclouds.py b/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_subclouds.py index b89a3c509..6e969c46f 100644 --- a/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_subclouds.py +++ b/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_subclouds.py @@ -821,14 +821,6 @@ class TestSubcloudPost(testroot.DCManagerApiTest, class TestSubcloudAPIOther(testroot.DCManagerApiTest): - FAKE_RESTORE_PAYLOAD = { - 'sysadmin_password': - (base64.b64encode('testpass'.encode("utf-8"))).decode('ascii'), - 'with_install': 'true', - 'restore_values': {'on_box_data': 'false', - 'backup_filename': 'some_fake_tarfile'} - } - """Test GET, delete and patch API calls""" def setUp(self): super(TestSubcloudAPIOther, self).setUp() @@ -1582,144 +1574,6 @@ class TestSubcloudAPIOther(testroot.DCManagerApiTest): str(subcloud.id) + '/reinstall', headers=FAKE_HEADERS, params=reinstall_data) - @mock.patch.object(rpc_client, 'ManagerClient') - @mock.patch.object(subclouds.SubcloudsController, '_get_restore_payload') - def test_restore_subcloud_no_body(self, mock_get_restore_payload, - mock_rpc_client): - - subcloud = fake_subcloud.create_fake_subcloud(self.ctx) - restore_payload = {} - - mock_rpc_client().restore_subcloud.return_value = True - mock_get_restore_payload.return_value = restore_payload - six.assertRaisesRegex(self, webtest.app.AppError, "400 *", - self.app.patch_json, FAKE_URL + '/' + - str(subcloud.id) + '/restore', - headers=FAKE_HEADERS, params=restore_payload) - - @mock.patch.object(rpc_client, 'ManagerClient') - def test_restore_subcloud_missing_restore_values(self, mock_rpc_client): - - subcloud = fake_subcloud.create_fake_subcloud(self.ctx) - restore_payload = copy.copy(self.FAKE_RESTORE_PAYLOAD) - del restore_payload['restore_values'] - - mock_rpc_client().restore_subcloud.return_value = True - six.assertRaisesRegex(self, webtest.app.AppError, "400 *", - self.app.patch_json, FAKE_URL + '/' + - str(subcloud.id) + '/restore', - headers=FAKE_HEADERS, params=restore_payload) - - @mock.patch.object(rpc_client, 'ManagerClient') - @mock.patch.object(subclouds.SubcloudsController, '_get_restore_payload') - def test_restore_subcloud_in_managed_state(self, mock_get_restore_payload, - mock_rpc_client): - - subcloud = fake_subcloud.create_fake_subcloud(self.ctx) - db_api.subcloud_update(self.ctx, - subcloud.id, - management_state=dccommon_consts.MANAGEMENT_MANAGED) - restore_payload = copy.copy(self.FAKE_RESTORE_PAYLOAD) - - mock_rpc_client().restore_subcloud.return_value = True - mock_get_restore_payload.return_value = restore_payload - six.assertRaisesRegex(self, webtest.app.AppError, "400 *", - self.app.patch_json, FAKE_URL + '/' + - str(subcloud.id) + '/restore', - headers=FAKE_HEADERS, params=restore_payload) - - @mock.patch.object(rpc_client, 'ManagerClient') - @mock.patch.object(subclouds.SubcloudsController, '_get_restore_payload') - def test_restore_subcloud_undergoing_bootstrap(self, mock_get_restore_payload, - mock_rpc_client): - - subcloud = fake_subcloud.create_fake_subcloud(self.ctx) - db_api.subcloud_update(self.ctx, - subcloud.id, - deploy_status=consts.DEPLOY_STATE_BOOTSTRAPPING) - restore_payload = copy.copy(self.FAKE_RESTORE_PAYLOAD) - - mock_rpc_client().restore_subcloud.return_value = True - mock_get_restore_payload.return_value = restore_payload - six.assertRaisesRegex(self, webtest.app.AppError, "400 *", - self.app.patch_json, FAKE_URL + '/' + - str(subcloud.id) + '/restore', - headers=FAKE_HEADERS, params=restore_payload) - - @mock.patch.object(rpc_client, 'ManagerClient') - @mock.patch.object(subclouds.SubcloudsController, '_get_restore_payload') - def test_restore_subcloud_bad_sysadmin_password(self, mock_get_restore_payload, - mock_rpc_client): - - subcloud = fake_subcloud.create_fake_subcloud(self.ctx) - restore_payload = copy.copy(self.FAKE_RESTORE_PAYLOAD) - restore_payload['sysadmin_password'] = 'not_base64_encoded' - - mock_rpc_client().restore_subcloud.return_value = True - mock_get_restore_payload.return_value = restore_payload - six.assertRaisesRegex(self, webtest.app.AppError, "400 *", - self.app.patch_json, FAKE_URL + '/' + - str(subcloud.id) + '/restore', - headers=FAKE_HEADERS, params=restore_payload) - - @mock.patch.object(rpc_client, 'ManagerClient') - @mock.patch.object(subclouds.SubcloudsController, '_get_restore_payload') - def test_restore_subcloud_without_remote_install(self, mock_get_restore_payload, - mock_rpc_client): - - subcloud = fake_subcloud.create_fake_subcloud(self.ctx) - restore_payload = copy.copy(self.FAKE_RESTORE_PAYLOAD) - del restore_payload['with_install'] - - mock_rpc_client().restore_subcloud.return_value = True - mock_get_restore_payload.return_value = restore_payload - six.assertRaisesRegex(self, webtest.app.AppError, "400 *", - self.app.patch_json, FAKE_URL + '/' + - str(subcloud.id) + '/restore', - headers=FAKE_HEADERS, params=restore_payload) - - @mock.patch.object(rpc_client, 'ManagerClient') - @mock.patch.object(subclouds.SubcloudsController, '_get_restore_payload') - def test_restore_subcloud_missing_mandatory_restore_parameter( - self, mock_get_restore_payload, mock_rpc_client): - - subcloud = fake_subcloud.create_fake_subcloud(self.ctx) - restore_payload = copy.copy(self.FAKE_RESTORE_PAYLOAD) - restore_payload['restore_values'] = {'on_box_data': 'false'} - - mock_rpc_client().restore_subcloud.return_value = True - mock_get_restore_payload.return_value = restore_payload - six.assertRaisesRegex(self, webtest.app.AppError, "400 *", - self.app.patch_json, FAKE_URL + '/' + - str(subcloud.id) + '/restore', - headers=FAKE_HEADERS, params=restore_payload) - - @mock.patch.object(cutils, 'get_vault_load_files') - @mock.patch.object(rpc_client, 'ManagerClient') - @mock.patch.object(subclouds.SubcloudsController, '_get_subcloud_db_install_values') - @mock.patch.object(subclouds.SubcloudsController, '_get_restore_payload') - def test_restore_subcloud(self, mock_get_restore_payload, - mock_get_subcloud_db_install_values, - mock_rpc_client, mock_get_vault_load_files): - - subcloud = fake_subcloud.create_fake_subcloud(self.ctx) - install_data = copy.copy(FAKE_SUBCLOUD_INSTALL_VALUES) - restore_payload = copy.copy(self.FAKE_RESTORE_PAYLOAD) - - mock_get_subcloud_db_install_values.return_value = install_data - mock_rpc_client().restore_subcloud.return_value = True - mock_get_restore_payload.return_value = restore_payload - mock_get_vault_load_files.return_value = ('iso_file_path', 'sig_file_path') - response = self.app.patch_json(FAKE_URL + '/' + str(subcloud.id) + - '/restore', - headers=FAKE_HEADERS, - params=restore_payload) - mock_rpc_client().restore_subcloud.assert_called_once_with( - mock.ANY, - subcloud.id, - mock.ANY) - self.assertEqual(response.status_int, 200) - @mock.patch.object(rpc_client, 'ManagerClient') @mock.patch.object(prestage, '_get_system_controller_upgrades') @mock.patch.object(prestage, '_get_prestage_subcloud_info') diff --git a/distributedcloud/dcmanager/tests/unit/manager/test_subcloud_manager.py b/distributedcloud/dcmanager/tests/unit/manager/test_subcloud_manager.py index df95ec8e2..c5de529fe 100644 --- a/distributedcloud/dcmanager/tests/unit/manager/test_subcloud_manager.py +++ b/distributedcloud/dcmanager/tests/unit/manager/test_subcloud_manager.py @@ -251,24 +251,6 @@ class FakeException(Exception): pass -FAKE_RESTORE_VALUES = { - "backup_filename": "subcloud_platform_backup.tgz", - "on_box_data": "false", - "initial_backup_dir": "/home/sysadmin", - "skip_patches_restore": "true" -} - - -FAKE_SUBCLOUD_RESTORE_PAYLOAD = { - "install_values": fake_subcloud.FAKE_SUBCLOUD_INSTALL_VALUES, - "with_install": True, - "bootstrap-address": "bootstrap_ip", - "software_version": "20.12", - "sysadmin_password": "testpasswd", - "restore_values": FAKE_RESTORE_VALUES -} - - FAKE_SUBCLOUD_PRESTAGE_PAYLOAD = { "install_values": fake_subcloud.FAKE_SUBCLOUD_INSTALL_VALUES, "subcloud_name": 'subcloud1', @@ -1620,99 +1602,6 @@ class TestSubcloudManager(base.DCManagerTestCase): self.assertEqual(consts.DEPLOY_STATE_DONE, subcloud.deploy_status) - def test_compose_check_target_command(self): - sm = subcloud_manager.SubcloudManager() - check_target_command = sm.compose_check_target_command( - 'subcloud1', '/var/opt/dc/ansible/subcloud1_inventory.yml', - FAKE_SUBCLOUD_RESTORE_PAYLOAD) - self.assertEqual( - check_target_command, - [ - 'ansible-playbook', - subcloud_manager.ANSIBLE_HOST_VALIDATION_PLAYBOOK, - '-i', '/var/opt/dc/ansible/subcloud1_inventory.yml', - '--limit', 'subcloud1', - '-e', '@/var/opt/dc/ansible/subcloud1_check_target_values.yml' - ] - ) - - def test_compose_restore_command(self): - sm = subcloud_manager.SubcloudManager() - restore_command = sm.compose_restore_command( - 'subcloud1', '/var/opt/dc/ansible/subcloud1_inventory.yml', - FAKE_SUBCLOUD_RESTORE_PAYLOAD) - self.assertEqual( - restore_command, - [ - 'ansible-playbook', - subcloud_manager.ANSIBLE_SUBCLOUD_RESTORE_PLAYBOOK, - '-i', '/var/opt/dc/ansible/subcloud1_inventory.yml', - '--limit', 'subcloud1', - '-e', '@/var/opt/dc/ansible/subcloud1_restore_values.yml' - ] - ) - - def test_restore_managed_subcloud(self): - subcloud = self.create_subcloud_static( - self.ctx, - name='subcloud1', - deploy_status=consts.DEPLOY_STATE_DONE) - db_api.subcloud_update(self.ctx, - subcloud.id, - management_state=dccommon_consts.MANAGEMENT_MANAGED) - - fake_dcmanager_cermon_api = FakeDCManagerNotifications() - - p = mock.patch('dcmanager.rpc.client.DCManagerNotifications') - mock_dcmanager_api = p.start() - mock_dcmanager_api.return_value = fake_dcmanager_cermon_api - - sm = subcloud_manager.SubcloudManager() - self.assertRaises(exceptions.SubcloudNotUnmanaged, - sm.restore_subcloud, self.ctx, - subcloud.id, FAKE_SUBCLOUD_RESTORE_PAYLOAD) - - @mock.patch.object(cutils, 'get_vault_load_files') - @mock.patch.object(cutils, 'create_subcloud_inventory') - @mock.patch.object( - subcloud_manager.SubcloudManager, 'compose_install_command') - @mock.patch.object(subcloud_manager.SubcloudManager, - '_prepare_for_restore') - @mock.patch.object( - subcloud_manager.SubcloudManager, 'compose_check_target_command') - @mock.patch.object( - subcloud_manager.SubcloudManager, 'compose_restore_command') - @mock.patch.object(threading.Thread, 'start') - def test_restore_subcloud( - self, mock_thread_start, - mock_compose_restore_command, mock_compose_check_target_command, - mock_prepare_for_restore, mock_compose_install_command, - mock_create_subcloud_inventory, mock_get_vault_load_files): - - subcloud = self.create_subcloud_static( - self.ctx, - name='subcloud1', - deploy_status=consts.DEPLOY_STATE_PRE_RESTORE) - - sm = subcloud_manager.SubcloudManager() - mock_get_vault_load_files.return_value = ("iso file path", "sig file path") - - sm.restore_subcloud(self.ctx, subcloud.id, FAKE_SUBCLOUD_RESTORE_PAYLOAD) - mock_get_vault_load_files.assert_called_once_with(SW_VERSION) - mock_create_subcloud_inventory.assert_called_once_with( - FAKE_SUBCLOUD_RESTORE_PAYLOAD, mock.ANY) - mock_compose_install_command.assert_called_once_with(subcloud.name, mock.ANY) - mock_compose_check_target_command.assert_called_once_with( - subcloud.name, mock.ANY, FAKE_SUBCLOUD_RESTORE_PAYLOAD) - mock_compose_restore_command.assert_called_once_with( - subcloud.name, mock.ANY, FAKE_SUBCLOUD_RESTORE_PAYLOAD) - mock_thread_start.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_parallel_group_operation') def test_backup_create_managed_online(self, mock_parallel_group_operation):