# Copyright (c) 2017 Ericsson AB. # Copyright (c) 2017-2023 Wind River Systems, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # import base64 import os import six from osc_lib.command import command from dcmanagerclient.commands.v1 import base from dcmanagerclient import exceptions from dcmanagerclient import utils SET_FIELD_VALUE_DICT = { "region_name": None } def format(subcloud=None): columns = ( 'id', 'name', 'management', 'availability', 'deploy status', 'sync', 'backup status', 'backup datetime' ) if subcloud: data = ( subcloud.subcloud_id, subcloud.name, subcloud.management_state, subcloud.availability_status, subcloud.deploy_status, subcloud.sync_status, subcloud.backup_status, subcloud.backup_datetime, ) else: data = (tuple('' for _ in range(len(columns))),) return columns, data 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', 'peer_group_id', 'rehome_data', '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.peer_group_id, subcloud.rehome_data, 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,) if subcloud.prestage_software_version: columns += ('prestage_software_version',) data += (subcloud.prestage_software_version,) if subcloud.deploy_config_sync_status != "unknown": columns += ('deploy_config_sync_status',) data += (subcloud.deploy_config_sync_status,) if subcloud.region_name is not None: columns += ('region_name',) data += (subcloud.region_name,) else: data = (tuple('' for _ in range(len(columns))),) return columns, data # The API is returning the region_name field, however only the list # and show commands should consider the region name field. # The other commands do not required it, since the output should # not show that field def update_fields_values(result): if len(result) == 0: return for i in range(len(result)): for field, value in SET_FIELD_VALUE_DICT.items(): if field in dir(result[i]): setattr(result[i], field, value) class AddSubcloud(base.DCManagerShowOne): """Add a new subcloud.""" def _get_format_function(self): return detail_format def get_parser(self, prog_name): parser = super(AddSubcloud, self).get_parser(prog_name) parser.add_argument( '--name', required=False, help='Subcloud name' ) parser.add_argument( '--bootstrap-address', required=True, help='IP address for initial subcloud controller.' ) parser.add_argument( '--bootstrap-values', required=True, help='YAML file containing parameters required for the bootstrap ' 'of the subcloud.' ) parser.add_argument( '--deploy-config', required=False, help='YAML file containing parameters required for the initial ' 'configuration and unlock of the subcloud.' ) parser.add_argument( '--install-values', required=False, help='YAML file containing parameters required for the ' 'remote install of the subcloud.' ) parser.add_argument( '--sysadmin-password', required=False, help='sysadmin password of the subcloud to be configured, ' 'if not provided you will be prompted.' ) parser.add_argument( '--bmc-password', required=False, help='bmc password of the subcloud to be configured, ' 'if not provided you will be prompted. This parameter is only' ' valid if the --install-values are specified.' ) parser.add_argument( '--group', required=False, help='Name or ID of subcloud group.' ) parser.add_argument( '--migrate', required=False, action='store_true', help='Migrate a subcloud from another distributed cloud.' ) 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.' ) return parser def _get_resources(self, parsed_args): dcmanager_client = self.app.client_manager.subcloud_manager files = dict() data = dict() data['bootstrap-address'] = parsed_args.bootstrap_address # Get the install values yaml file if parsed_args.install_values is not None: if not os.path.isfile(parsed_args.install_values): error_msg = "install-values does not exist: %s" % \ parsed_args.install_values raise exceptions.DCManagerClientException(error_msg) files['install_values'] = parsed_args.install_values # Get the bootstrap values yaml file if not os.path.isfile(parsed_args.bootstrap_values): error_msg = "bootstrap-values does not exist: %s" % \ parsed_args.bootstrap_values raise exceptions.DCManagerClientException(error_msg) files['bootstrap_values'] = parsed_args.bootstrap_values # Get the deploy config yaml file if parsed_args.deploy_config is not None: if parsed_args.migrate: error_msg = "migrate with deploy-config is not allowed" raise exceptions.DCManagerClientException(error_msg) if not os.path.isfile(parsed_args.deploy_config): error_msg = "deploy-config does not exist: %s" % \ parsed_args.deploy_config raise exceptions.DCManagerClientException(error_msg) files['deploy_config'] = parsed_args.deploy_config # Prompt the user for the subcloud's password if it isn't provided if parsed_args.sysadmin_password is not None: data['sysadmin_password'] = base64.b64encode( parsed_args.sysadmin_password.encode("utf-8")) else: password = utils.prompt_for_password() data["sysadmin_password"] = base64.b64encode( password.encode("utf-8")) if parsed_args.install_values is not None: if parsed_args.bmc_password is not None: data['bmc_password'] = base64.b64encode( parsed_args.bmc_password.encode("utf-8")) else: password = utils.prompt_for_password('bmc') data["bmc_password"] = base64.b64encode( password.encode("utf-8")) if parsed_args.group is not None: data['group_id'] = parsed_args.group if parsed_args.migrate: data['migrate'] = 'true' if parsed_args.release is not None: data['release'] = parsed_args.release if parsed_args.name is not None: if parsed_args.migrate: data['name'] = parsed_args.name else: error_msg = 'The --name option can only be used with \ --migrate option.' raise exceptions.DCManagerClientException(error_msg) result = dcmanager_client.subcloud_manager.add_subcloud(files=files, data=data) update_fields_values(result) return result class ListSubcloud(base.DCManagerLister): """List subclouds.""" def _get_format_function(self): return format def get_parser(self, prog_name): parser = super(ListSubcloud, self).get_parser(prog_name) parser.add_argument( '--all', required=False, action='store_true', help='List all subclouds include "secondary" state subclouds' ) return parser def _get_resources(self, parsed_args): dcmanager_client = self.app.client_manager.subcloud_manager subclouds = dcmanager_client.subcloud_manager.list_subclouds() # for '--all' parameter, show all subclouds. # for no parameter, hidden all 'secondary/secondary-failed' # state subclouds. if parsed_args.all: return subclouds filtered_subclouds = [s for s in subclouds if s.deploy_status not in ('secondary', 'secondary-failed')] return filtered_subclouds class ShowSubcloud(base.DCManagerShowOne): """Show the details of a subcloud.""" def _get_format_function(self): return detail_format def get_parser(self, prog_name): parser = super(ShowSubcloud, self).get_parser(prog_name) parser.add_argument( 'subcloud', help='Name or ID of subcloud to view the details.' ) parser.add_argument( '-d', '--detail', action='store_true', help="Show additional details for a subcloud" ) return parser def _get_resources(self, parsed_args): subcloud_ref = parsed_args.subcloud dcmanager_client = self.app.client_manager.subcloud_manager if parsed_args.detail: return dcmanager_client.subcloud_manager.\ subcloud_additional_details(subcloud_ref) else: return dcmanager_client.subcloud_manager.\ subcloud_detail(subcloud_ref) class ShowSubcloudError(command.Command): """Show the error of the last failed operation.""" def get_parser(self, prog_name): parser = super(ShowSubcloudError, self).get_parser(prog_name) parser.add_argument( 'subcloud', help='Name or ID of subcloud to view the errors details.' ) return parser def take_action(self, parsed_args): subcloud_ref = parsed_args.subcloud dcmanager_client = self.app.client_manager.subcloud_manager ret = dcmanager_client.subcloud_manager.subcloud_detail(subcloud_ref) data = ret[0].error_description print(''.join(data)) class DeleteSubcloud(command.Command): """Delete subcloud details from the database.""" def get_parser(self, prog_name): parser = super(DeleteSubcloud, self).get_parser(prog_name) parser.add_argument( 'subcloud', help='Name or ID of the subcloud to delete.' ) return parser def take_action(self, parsed_args): subcloud_ref = parsed_args.subcloud dcmanager_client = self.app.client_manager.subcloud_manager try: dcmanager_client.subcloud_manager.delete_subcloud(subcloud_ref) except Exception as e: print(e) error_msg = "Unable to delete subcloud %s" % (subcloud_ref) raise exceptions.DCManagerClientException(error_msg) class UnmanageSubcloud(base.DCManagerShowOne): """Unmanage a subcloud.""" def _get_format_function(self): return detail_format def get_parser(self, prog_name): parser = super(UnmanageSubcloud, self).get_parser(prog_name) parser.add_argument( 'subcloud', help='Name or ID of the subcloud to unmanage.' ) return parser def _get_resources(self, parsed_args): subcloud_ref = parsed_args.subcloud dcmanager_client = self.app.client_manager.subcloud_manager kwargs = dict() kwargs['management-state'] = 'unmanaged' try: result = dcmanager_client.subcloud_manager.update_subcloud( subcloud_ref, files=None, data=kwargs) update_fields_values(result) return result except Exception as e: print(e) error_msg = "Unable to unmanage subcloud %s" % (subcloud_ref) raise exceptions.DCManagerClientException(error_msg) class ManageSubcloud(base.DCManagerShowOne): """Manage a subcloud.""" def _get_format_function(self): return detail_format def get_parser(self, prog_name): parser = super(ManageSubcloud, self).get_parser(prog_name) parser.add_argument( 'subcloud', help='Name or ID of the subcloud to manage.' ) parser.add_argument( '--force', required=False, action='store_true', help='Disregard subcloud availability status, intended for \ some upgrade recovery scenarios.' ) return parser def _get_resources(self, parsed_args): subcloud_ref = parsed_args.subcloud dcmanager_client = self.app.client_manager.subcloud_manager kwargs = dict() kwargs['management-state'] = 'managed' if parsed_args.force: kwargs['force'] = 'true' try: result = dcmanager_client.subcloud_manager.update_subcloud( subcloud_ref, files=None, data=kwargs) update_fields_values(result) return result except Exception as e: print(e) error_msg = "Unable to manage subcloud %s" % (subcloud_ref) raise exceptions.DCManagerClientException(error_msg) class UpdateSubcloud(base.DCManagerShowOne): """Update attributes of a subcloud.""" def _get_format_function(self): return detail_format def get_parser(self, prog_name): parser = super(UpdateSubcloud, self).get_parser(prog_name) parser.add_argument( 'subcloud', help='Name or ID of the subcloud to update.' ) parser.add_argument( '--name', required=False, help='Name of subcloud.' ) parser.add_argument( '--description', required=False, help='Description of subcloud.' ) parser.add_argument( '--location', required=False, help='Location of subcloud.' ) parser.add_argument( '--group', required=False, help='Name or ID of subcloud group.' ) parser.add_argument( '--management-subnet', required=False, help='Network subnet of subcloud.' ) parser.add_argument( '--management-gateway-ip', required=False, help='Network gateway IP of subcloud.' ) parser.add_argument( '--management-start-ip', required=False, help='Network start IP of subcloud.' ) parser.add_argument( '--management-end-ip', required=False, help='Network end IP of subcloud.' ) parser.add_argument( '--sysadmin-password', required=False, help='sysadmin password of the subcloud to be updated, ' 'if not provided you will be prompted.' ) parser.add_argument( '--bootstrap-address', required=False, help='bootstrap address of the subcloud to be updated.' ) parser.add_argument( '--install-values', required=False, help='YAML file containing parameters required for the ' 'remote install of the subcloud.' ) parser.add_argument( '--bmc-password', required=False, help='bmc password of the subcloud to be configured, if not ' 'provided you will be prompted. This parameter is only' ' valid if the --install-values are specified.' ) parser.add_argument( '--bootstrap-values', required=False, help='YAML file containing subcloud configuration settings. ' 'Can be either a local file path or a URL.' ) parser.add_argument( '--peer-group', required=False, help='Name or ID of subcloud peer group (for migrate).' ) return parser def _get_resources(self, parsed_args): subcloud_ref = parsed_args.subcloud dcmanager_client = self.app.client_manager.subcloud_manager files = dict() data = dict() if parsed_args.name: data['name'] = parsed_args.name if parsed_args.description: data['description'] = parsed_args.description if parsed_args.location: data['location'] = parsed_args.location if parsed_args.group: data['group_id'] = parsed_args.group if parsed_args.management_subnet: data['management_subnet'] = parsed_args.management_subnet if parsed_args.management_gateway_ip: data['management_gateway_ip'] = parsed_args.management_gateway_ip if parsed_args.management_start_ip: data['management_start_ip'] = parsed_args.management_start_ip if parsed_args.management_end_ip: data['management_end_ip'] = parsed_args.management_end_ip if parsed_args.bootstrap_address: data['bootstrap_address'] = parsed_args.bootstrap_address if parsed_args.peer_group: data['peer_group'] = parsed_args.peer_group subcloud_network_values = [ data.get('management_subnet'), data.get('management_gateway_ip'), data.get('management_start_ip'), data.get('management_end_ip'), data.get('bootstrap_address') ] # Semantic check if the required arguments for updating admin network if all(value is not None for value in subcloud_network_values): # Prompt the user for the subcloud's password if it isn't provided if parsed_args.sysadmin_password is not None: data['sysadmin_password'] = base64.b64encode( parsed_args.sysadmin_password.encode("utf-8")) else: password = utils.prompt_for_password() data["sysadmin_password"] = base64.b64encode( password.encode("utf-8")) # For subcloud network reconfiguration # If any management_* presents, need all # management_subnet/management_gateway_ip/ # management_start_ip/management_end_ip/bootstrap_address # presents. elif any(value is not None and value != parsed_args.bootstrap_address for value in subcloud_network_values): # Not all network values exist error_msg = ( "For subcloud network reconfiguration request all the " "following parameters are necessary: --management-subnet, " "--management-gateway-ip, --management-start-ip, " "--management-end-ip and --bootstrap-address") raise exceptions.DCManagerClientException(error_msg) if parsed_args.install_values: if not os.path.isfile(parsed_args.install_values): error_msg = "install-values does not exist: %s" % \ parsed_args.install_values raise exceptions.DCManagerClientException(error_msg) files['install_values'] = parsed_args.install_values if parsed_args.bmc_password is not None: data['bmc_password'] = base64.b64encode( parsed_args.bmc_password.encode("utf-8")) else: password = utils.prompt_for_password('bmc') data["bmc_password"] = base64.b64encode( password.encode("utf-8")) # Update the bootstrap values from yaml file if parsed_args.bootstrap_values: if not os.path.isfile(parsed_args.bootstrap_values): error_msg = "bootstrap-values does not exist: %s" % \ parsed_args.bootstrap_values raise exceptions.DCManagerClientException(error_msg) files['bootstrap_values'] = parsed_args.bootstrap_values if not (data or files): error_msg = "Nothing to update" raise exceptions.DCManagerClientException(error_msg) try: result = dcmanager_client.subcloud_manager.update_subcloud( subcloud_ref, files=files, data=data) update_fields_values(result) return result except Exception as e: print(e) error_msg = "Unable to update subcloud %s" % (subcloud_ref) raise exceptions.DCManagerClientException(error_msg) class ReconfigSubcloud(base.DCManagerShowOne): """Reconfigure a subcloud.""" def _get_format_function(self): return detail_format def _get_resources(self, parsed_args): deprecation_msg = ("This command has been deprecated. Please use " "'subcloud deploy config' instead.") raise exceptions.DCManagerClientException(deprecation_msg) class ReinstallSubcloud(base.DCManagerShowOne): """Reinstall a subcloud.""" def _get_format_function(self): return detail_format def _get_resources(self, parsed_args): deprecation_msg = ("This command has been deprecated. Please use " "'subcloud redeploy' instead.") raise exceptions.DCManagerClientException(deprecation_msg) class RedeploySubcloud(base.DCManagerShowOne): """Redeploy a subcloud.""" def _get_format_function(self): return detail_format def get_parser(self, prog_name): parser = super(RedeploySubcloud, self).get_parser(prog_name) parser.add_argument( 'subcloud', help='Name or ID of the subcloud to redeploy.' ) parser.add_argument( '--install-values', required=False, help='YAML file containing parameters required for the ' 'remote install of the subcloud.' ) parser.add_argument( '--bootstrap-values', required=False, help='YAML file containing subcloud configuration settings. ' 'Can be either a local file path or a URL.' ) parser.add_argument( '--deploy-config', required=False, help='YAML file containing subcloud variables to be passed to the ' 'deploy playbook.' ) parser.add_argument( '--sysadmin-password', required=False, help='sysadmin password of the subcloud to be configured, ' 'if not provided you will be prompted.' ) parser.add_argument( '--bmc-password', required=False, help='bmc password of the subcloud to be configured, if not ' 'provided you will be prompted. This parameter is only' ' valid if the --install-values are specified.' ) 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.' ) return parser def _get_resources(self, parsed_args): subcloud_ref = parsed_args.subcloud dcmanager_client = self.app.client_manager.subcloud_manager files = dict() data = dict() # Get the install values yaml file if parsed_args.install_values is not None: if not os.path.isfile(parsed_args.install_values): error_msg = "install-values does not exist: %s" % \ parsed_args.install_values raise exceptions.DCManagerClientException(error_msg) files['install_values'] = parsed_args.install_values # Get the bootstrap values yaml file if parsed_args.bootstrap_values is not None: if not os.path.isfile(parsed_args.bootstrap_values): error_msg = "bootstrap-values does not exist: %s" % \ parsed_args.bootstrap_values raise exceptions.DCManagerClientException(error_msg) files['bootstrap_values'] = parsed_args.bootstrap_values # Get the deploy config yaml file if parsed_args.deploy_config is not None: if not os.path.isfile(parsed_args.deploy_config): error_msg = "deploy-config does not exist: %s" % \ parsed_args.deploy_config raise exceptions.DCManagerClientException(error_msg) files['deploy_config'] = parsed_args.deploy_config # Prompt the user for the subcloud's password if it isn't provided if parsed_args.sysadmin_password is not None: data['sysadmin_password'] = base64.b64encode( parsed_args.sysadmin_password.encode("utf-8")) else: password = utils.prompt_for_password() data["sysadmin_password"] = base64.b64encode( password.encode("utf-8")) if parsed_args.install_values: if parsed_args.bmc_password: data['bmc_password'] = base64.b64encode( parsed_args.bmc_password.encode("utf-8")) else: password = utils.prompt_for_password('bmc') data["bmc_password"] = base64.b64encode( password.encode("utf-8")) if parsed_args.release is not None: data['release'] = parsed_args.release # Require user to type redeploy to confirm print("WARNING: This will redeploy the subcloud. " "All applications and data on the subcloud will be lost.") confirm = six.moves.input( "Please type \"redeploy\" to confirm: ").strip().lower() if confirm == 'redeploy': try: return dcmanager_client.subcloud_manager.redeploy_subcloud( subcloud_ref=subcloud_ref, files=files, data=data) except Exception as e: print(e) error_msg = "Unable to redeploy subcloud %s" % (subcloud_ref) raise exceptions.DCManagerClientException(error_msg) else: msg = "Subcloud %s will not be redeployed" % (subcloud_ref) raise exceptions.DCManagerClientException(msg) class RestoreSubcloud(base.DCManagerShowOne): """Restore a subcloud.""" def _get_format_function(self): return detail_format def get_parser(self, prog_name): parser = super(RestoreSubcloud, self).get_parser(prog_name) parser.add_argument( '--restore-values', required=False, help='YAML file containing subcloud restore settings. ' 'Can be either a local file path or a URL.' ) 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( '--with-install', required=False, action='store_true', help='option to reinstall the subcloud as part of restore, ' 'suitable only for subclouds that can be installed remotely.' ) parser.add_argument( 'subcloud', help='Name or ID of the subcloud to update.' ) return parser def _get_resources(self, parsed_args): deprecation_msg = ('This command has been deprecated. Please use ' 'subcloud-backup restore instead.') raise exceptions.DCManagerClientException(deprecation_msg) class PrestageSubcloud(base.DCManagerShowOne): """Prestage a subcloud.""" def _get_format_function(self): return detail_format def get_parser(self, prog_name): parser = super(PrestageSubcloud, self).get_parser(prog_name) parser.add_argument( '--sysadmin-password', required=False, help='sysadmin password of the subcloud to be prestaged, ' 'if not provided you will be prompted.' ) parser.add_argument( 'subcloud', help='Name or ID of the subcloud to prestage.' ) parser.add_argument( '--force', required=False, action='store_true', help='Disregard subcloud management alarm condition' ) parser.add_argument( '--release', required=False, help="software release used to prestage the subcloud with. " "If not specified, the current software release of " "the subcloud will be used." ) return parser def _get_resources(self, parsed_args): subcloud_ref = parsed_args.subcloud dcmanager_client = self.app.client_manager.subcloud_manager data = dict() if parsed_args.force: data['force'] = 'true' 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.release: data['release'] = parsed_args.release try: result = dcmanager_client.subcloud_manager.\ prestage_subcloud( subcloud_ref=subcloud_ref, data=data) update_fields_values(result) return result except Exception as e: print(e) error_msg = "Unable to prestage subcloud %s" % (subcloud_ref) raise exceptions.DCManagerClientException(error_msg) class MigrateSubcloud(base.DCManagerShowOne): """Migrate a secondary status subcloud.""" def _get_format_function(self): return detail_format def get_parser(self, prog_name): parser = super(MigrateSubcloud, self).get_parser(prog_name) parser.add_argument( 'subcloud', help='Name or ID of the subcloud to migrate.' ) parser.add_argument( '--sysadmin-password', required=False, help='sysadmin password of the subcloud to be configured, ' 'if not provided you will be prompted.' ) return parser def _get_resources(self, parsed_args): subcloud_ref = parsed_args.subcloud dcmanager_client = self.app.client_manager.subcloud_manager data = dict() 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") try: result = dcmanager_client.subcloud_manager.migrate_subcloud( subcloud_ref=subcloud_ref, data=data) update_fields_values(result) return result except Exception as e: print(e) error_msg = "Unable to migrate subcloud %s" % (subcloud_ref) raise exceptions.DCManagerClientException(error_msg)