# # Copyright (c) 2022-2024 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # import base64 import os from osc_lib.command import command from dcmanagerclient import exceptions, utils from dcmanagerclient.commands.v1 import base def detail_format(subcloud=None): columns = ( "id", "name", "description", "location", "software_version", "management", "availability", "deploy_status", "management_subnet", "management_start_ip", "management_end_ip", "management_gateway_ip", "systemcontroller_gateway_ip", "group_id", "created_at", "updated_at", "backup_status", "backup_datetime", ) if subcloud: data = ( subcloud.subcloud_id, subcloud.name, subcloud.description, subcloud.location, subcloud.software_version, subcloud.management_state, subcloud.availability_status, subcloud.deploy_status, subcloud.management_subnet, subcloud.management_start_ip, subcloud.management_end_ip, subcloud.management_gateway_ip, subcloud.systemcontroller_gateway_ip, subcloud.group_id, subcloud.created_at, subcloud.updated_at, subcloud.backup_status, subcloud.backup_datetime, ) for _listitem, sync_status in enumerate(subcloud.endpoint_sync_status): added_field = (sync_status["endpoint_type"] + "_sync_status",) added_value = (sync_status["sync_status"],) columns += tuple(added_field) data += tuple(added_value) if subcloud.oam_floating_ip != "unavailable": columns += ("oam_floating_ip",) data += (subcloud.oam_floating_ip,) else: data = (tuple("" for _ in range(len(columns))),) return columns, data class CreateSubcloudBackup(base.DCManagerShow): """Backup a subcloud or group of subclouds""" def _get_format_function(self): return detail_format def should_list(self, parsed_args): return parsed_args.group def get_parser(self, prog_name): parser = super(CreateSubcloudBackup, self).get_parser(prog_name) parser.add_argument( "--local-only", required=False, action="store_true", help="If included, backup files will be stored on the subcloud. " "Otherwise, they will be transferred and stored in " "dedicated location on the system controller.", ) parser.add_argument( "--registry-images", required=False, action="store_true", help="If included, container images backup file will also be " "generated. This option can only be used with --local-only " "option.", ) parser.add_argument( "--sysadmin-password", required=False, help="sysadmin password of the subcloud to create backup, " "if not provided you will be prompted.", ) parser.add_argument( "--backup-values", required=False, help="YAML file containing subcloud backup settings. " "Can be either a local file path or a URL.", ) parser.add_argument( "--subcloud", required=False, help="Name or ID of the subcloud to create backup.", ) parser.add_argument( "--group", required=False, help="Name or ID of the group to create backup.", ) return parser def _get_resources(self, parsed_args): dcmanager_client = self.app.client_manager.subcloud_backup_manager data = dict() files = dict() if not parsed_args.subcloud and not parsed_args.group: error_msg = ( "Please provide the subcloud or subcloud group name or id." ) raise exceptions.DCManagerClientException(error_msg) if parsed_args.subcloud and parsed_args.group: error_msg = ( "The command only applies to a single subcloud " "or a subcloud group, not both." ) raise exceptions.DCManagerClientException(error_msg) if parsed_args.subcloud: data["subcloud"] = parsed_args.subcloud if parsed_args.group: data["group"] = parsed_args.group if not parsed_args.local_only and parsed_args.registry_images: error_msg = ( "Option --registry-images can not be used without " "--local-only option." ) raise exceptions.DCManagerClientException(error_msg) if parsed_args.local_only: data["local_only"] = "true" else: data["local_only"] = "false" if parsed_args.registry_images: data["registry_images"] = "true" else: data["registry_images"] = "false" if parsed_args.sysadmin_password is not None: # The binary base64 encoded string (eg. b'dGVzdA==') is not JSON # serializable in Python3.x, so it has to be decoded to a JSON # serializable string (eg. 'dGVzdA=='). data["sysadmin_password"] = base64.b64encode( parsed_args.sysadmin_password.encode("utf-8") ).decode("utf-8") else: password = utils.prompt_for_password() data["sysadmin_password"] = base64.b64encode( password.encode("utf-8") ).decode("utf-8") if parsed_args.backup_values: if not os.path.isfile(parsed_args.backup_values): error_msg = ( f"Backup-values file does not exist: {parsed_args.backup_values}" ) raise exceptions.DCManagerClientException(error_msg) files["backup_values"] = parsed_args.backup_values try: return dcmanager_client.subcloud_backup_manager.backup_subcloud_create( data=data, files=files ) except Exception as e: print(e) error_msg = "Unable to create subcloud backup" raise exceptions.DCManagerClientException(error_msg) class DeleteSubcloudBackup(command.Command): """Delete backup from a subcloud or group of subclouds""" def _get_format_function(self): return detail_format def get_parser(self, prog_name): parser = super(DeleteSubcloudBackup, self).get_parser(prog_name) parser.add_argument( "release", help="Release version that the user is trying to delete." ) parser.add_argument( "--local-only", required=False, action="store_true", help="If included, backup files will be deleted from the " "subcloud. Otherwise, they will be deleted from the " "centralized archive on the system controller.", ) parser.add_argument( "--sysadmin-password", required=False, help="sysadmin password of the subcloud to delete backup, " "if not provided you will be prompted.", ) parser.add_argument( "--subcloud", required=False, help="Name or ID of the subcloud to delete backup.", ) parser.add_argument( "--group", required=False, help="Name or ID of the subcloud to delete backup.", ) return parser def take_action(self, parsed_args): dcmanager_client = self.app.client_manager.subcloud_backup_manager release_version = parsed_args.release subcloud_ref = parsed_args.subcloud data = dict() data["release"] = parsed_args.release if not parsed_args.subcloud and not parsed_args.group: error_msg = ( "Please provide the subcloud or subcloud group name or id." ) raise exceptions.DCManagerClientException(error_msg) if parsed_args.subcloud and parsed_args.group: error_msg = ( "This command only applies to a single subcloud " "or a subcloud group, not both." ) raise exceptions.DCManagerClientException(error_msg) if parsed_args.subcloud: data["subcloud"] = parsed_args.subcloud if parsed_args.group: data["group"] = parsed_args.group if parsed_args.local_only: data["local_only"] = "true" else: data["local_only"] = "false" if parsed_args.sysadmin_password is not None: data["sysadmin_password"] = base64.b64encode( parsed_args.sysadmin_password.encode("utf-8") ).decode("utf-8") elif not parsed_args.sysadmin_password and parsed_args.local_only: password = utils.prompt_for_password() data["sysadmin_password"] = base64.b64encode( password.encode("utf-8") ).decode("utf-8") try: return dcmanager_client.subcloud_backup_manager.backup_subcloud_delete( subcloud_ref=subcloud_ref, release_version=release_version, data=data ) except Exception as e: print(e) error_msg = "Unable to delete backup" raise exceptions.DCManagerClientException(error_msg) class RestoreSubcloudBackup(base.DCManagerShow): """Restore a subcloud or group of subclouds from backup""" def _get_format_function(self): return detail_format def should_list(self, parsed_args): return parsed_args.group def get_parser(self, prog_name): parser = super(RestoreSubcloudBackup, self).get_parser(prog_name) parser.add_argument( "--with-install", required=False, action="store_true", help="If included, the subcloud will be reinstalled prior to " "being restored from backup data.", ) parser.add_argument( "--release", required=False, help="Software release used to install, bootstrap and/or deploy " "the subcloud with. If not specified, the current software " "release of the system controller will be used.", ) parser.add_argument( "--local-only", required=False, action="store_true", help="If included, the subcloud will be restored from backup data " "previously saved on the subcloud. Otherwise, it will be " "restored from backup data previously saved on the system " "controller.", ) parser.add_argument( "--registry-images", required=False, action="store_true", help="If included, user images will be restored post platform " "restore. This option can only be used with --local-only " "option.", ) parser.add_argument( "--restore-values", required=False, help="Reference to the restore playbook overrides yaml file, as " "listed in the product documentation for the ansible restore.", ) parser.add_argument( "--sysadmin-password", required=False, help="sysadmin password of the subcloud to be restored, " "if not provided you will be prompted.", ) parser.add_argument( "--subcloud", required=False, help="Name or ID of the subcloud to restore.", ) parser.add_argument( "--group", required=False, help="Name or ID of the subcloud group to restore.", ) return parser def _get_resources(self, parsed_args): dcmanager_client = self.app.client_manager.subcloud_backup_manager data = dict() files = dict() if not parsed_args.subcloud and not parsed_args.group: error_msg = ( "Please provide the subcloud or subcloud group name or id." ) raise exceptions.DCManagerClientException(error_msg) if parsed_args.subcloud and parsed_args.group: error_msg = ( "The command only applies to a single subcloud " "or a subcloud group, not both." ) raise exceptions.DCManagerClientException(error_msg) if parsed_args.subcloud: data["subcloud"] = parsed_args.subcloud if parsed_args.group: data["group"] = parsed_args.group if not parsed_args.local_only and parsed_args.registry_images: error_msg = ( "Option --registry-images cannot be used without " "--local-only option." ) raise exceptions.DCManagerClientException(error_msg) if not parsed_args.with_install and parsed_args.release: error_msg = ( "Option --release cannot be used without --with-install option." ) raise exceptions.DCManagerClientException(error_msg) if parsed_args.with_install: data["with_install"] = "true" else: data["with_install"] = "false" if parsed_args.local_only: data["local_only"] = "true" else: data["local_only"] = "false" if parsed_args.registry_images: data["registry_images"] = "true" else: data["registry_images"] = "false" if parsed_args.release is not None: data["release"] = parsed_args.release if parsed_args.sysadmin_password is not None: data["sysadmin_password"] = base64.b64encode( parsed_args.sysadmin_password.encode("utf-8") ).decode("utf-8") else: password = utils.prompt_for_password() data["sysadmin_password"] = base64.b64encode( password.encode("utf-8") ).decode("utf-8") if parsed_args.restore_values: if not os.path.isfile(parsed_args.restore_values): error_msg = ( "restore_values file does not exist: " f"{parsed_args.restore_values}" ) raise exceptions.DCManagerClientException(error_msg) files["restore_values"] = parsed_args.restore_values try: return dcmanager_client.subcloud_backup_manager.backup_subcloud_restore( data=data, files=files ) except Exception as e: print(e) error_msg = "Unable to restore subcloud backup" raise exceptions.DCManagerClientException(error_msg)