diff --git a/distributedcloud-client/dcmanagerclient/api/v1/phased_subcloud_deploy_manager.py b/distributedcloud-client/dcmanagerclient/api/v1/phased_subcloud_deploy_manager.py index 55f3225..c366157 100644 --- a/distributedcloud-client/dcmanagerclient/api/v1/phased_subcloud_deploy_manager.py +++ b/distributedcloud-client/dcmanagerclient/api/v1/phased_subcloud_deploy_manager.py @@ -43,6 +43,12 @@ class phased_subcloud_deploy_manager(base.ResourceManager): files = kwargs.get('files') return self._deploy_operation(BASE_URL, files, data) + def subcloud_deploy_install(self, subcloud_ref, **kwargs): + data = kwargs.get('data') + files = kwargs.get('files') + url = BASE_URL + "%s/install" % subcloud_ref + return self._deploy_operation(url, files, data, method='patch') + def subcloud_deploy_bootstrap(self, subcloud_ref, **kwargs): data = kwargs.get('data') files = kwargs.get('files') diff --git a/distributedcloud-client/dcmanagerclient/commands/v1/phased_subcloud_deploy_manager.py b/distributedcloud-client/dcmanagerclient/commands/v1/phased_subcloud_deploy_manager.py index 379caaa..b390ea3 100644 --- a/distributedcloud-client/dcmanagerclient/commands/v1/phased_subcloud_deploy_manager.py +++ b/distributedcloud-client/dcmanagerclient/commands/v1/phased_subcloud_deploy_manager.py @@ -44,8 +44,8 @@ class CreatePhasedSubcloudDeploy(base.DCManagerShowOne): parser.add_argument( '--install-values', required=False, - help='YAML file containing subcloud variables required for remote ' - 'install playbook.' + help='YAML file containing parameters required for the ' + 'remote install of the subcloud.' ) parser.add_argument( @@ -120,6 +120,93 @@ class CreatePhasedSubcloudDeploy(base.DCManagerShowOne): files=files, data=data) +class InstallPhasedSubcloudDeploy(base.DCManagerShowOne): + """Install a subcloud.""" + + def _get_format_function(self): + return utils.subcloud_detail_format + + def get_parser(self, prog_name): + parser = super(InstallPhasedSubcloudDeploy, self).get_parser(prog_name) + + parser.add_argument( + 'subcloud', + help='Name or ID of the subcloud to install.' + ) + + 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( + '--release', + required=False, + help='software release used to install 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.\ + phased_subcloud_deploy_manager.phased_subcloud_deploy_manager + files = dict() + data = dict() + + # 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 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")) + + if parsed_args.release is not None: + data['release'] = parsed_args.release + + try: + return dcmanager_client.subcloud_deploy_install( + subcloud_ref=subcloud_ref, files=files, data=data) + except Exception as e: + print(e) + error_msg = "Unable to install subcloud %s" % (subcloud_ref) + raise exceptions.DCManagerClientException(error_msg) + + class BootstrapPhasedSubcloudDeploy(base.DCManagerShowOne): """Bootstrap a subcloud.""" diff --git a/distributedcloud-client/dcmanagerclient/commands/v1/subcloud_manager.py b/distributedcloud-client/dcmanagerclient/commands/v1/subcloud_manager.py index 6d36e9c..62d9f37 100644 --- a/distributedcloud-client/dcmanagerclient/commands/v1/subcloud_manager.py +++ b/distributedcloud-client/dcmanagerclient/commands/v1/subcloud_manager.py @@ -155,8 +155,8 @@ class AddSubcloud(base.DCManagerShowOne): parser.add_argument( '--install-values', required=False, - help='YAML file containing subcloud variables required for remote ' - 'install playbook.' + help='YAML file containing parameters required for the ' + 'remote install of the subcloud.' ) parser.add_argument( @@ -492,8 +492,8 @@ class UpdateSubcloud(base.DCManagerShowOne): parser.add_argument( '--install-values', required=False, - help='YAML file containing subcloud variables required for remote ' - 'install playbook.' + help='YAML file containing parameters required for the ' + 'remote install of the subcloud.' ) parser.add_argument( diff --git a/distributedcloud-client/dcmanagerclient/shell.py b/distributedcloud-client/dcmanagerclient/shell.py index 2a3660e..fa0ae90 100644 --- a/distributedcloud-client/dcmanagerclient/shell.py +++ b/distributedcloud-client/dcmanagerclient/shell.py @@ -547,6 +547,7 @@ class DCManagerShell(app.App): 'subcloud deploy create': psdm.CreatePhasedSubcloudDeploy, 'subcloud deploy bootstrap': psdm.BootstrapPhasedSubcloudDeploy, 'subcloud deploy config': psdm.ConfigPhasedSubcloudDeploy, + 'subcloud deploy install': psdm.InstallPhasedSubcloudDeploy, 'subcloud-deploy upload': sdm.SubcloudDeployUpload, 'subcloud-deploy show': sdm.SubcloudDeployShow, 'alarm summary': am.ListAlarmSummary, diff --git a/distributedcloud-client/dcmanagerclient/tests/base.py b/distributedcloud-client/dcmanagerclient/tests/base.py index 4d40040..72d9649 100644 --- a/distributedcloud-client/dcmanagerclient/tests/base.py +++ b/distributedcloud-client/dcmanagerclient/tests/base.py @@ -133,6 +133,25 @@ FAKE_BOOTSTRAP_VALUES = { 'backup_datetime': BACKUP_DATETIME } +FAKE_INSTALL_VALUES = { + "image": "http://192.168.101.2:8080/iso/bootimage.iso", + "software_version": SOFTWARE_VERSION, + "bootstrap_interface": "eno1", + "bootstrap_address": "128.224.151.183", + "bootstrap_address_prefix": 23, + "bmc_address": "128.224.64.180", + "bmc_username": "root", + "nexthop_gateway": "128.224.150.1", + "network_address": "128.224.144.0", + "network_mask": "255.255.254.0", + "install_type": 3, + "console_type": "tty0", + "bootstrap_vlan": 128, + "rootfs_device": "/dev/disk/by-path/pci-0000:5c:00.0-scsi-0:1:0:0", + "boot_device": "/dev/disk/by-path/pci-0000:5c:00.0-scsi-0:1:0:0", + "rd.net.timeout.ipv6dad": 300, +} + class FakeResponse(object): """Fake response for testing DC Manager Client.""" diff --git a/distributedcloud-client/dcmanagerclient/tests/v1/test_phased_subcloud_deploy.py b/distributedcloud-client/dcmanagerclient/tests/v1/test_phased_subcloud_deploy.py index b38e155..639ee4b 100644 --- a/distributedcloud-client/dcmanagerclient/tests/v1/test_phased_subcloud_deploy.py +++ b/distributedcloud-client/dcmanagerclient/tests/v1/test_phased_subcloud_deploy.py @@ -8,6 +8,7 @@ import os import tempfile import mock +import yaml from dcmanagerclient.commands.v1 import phased_subcloud_deploy_manager as cmd from dcmanagerclient.exceptions import DCManagerClientException @@ -60,6 +61,62 @@ class TestCLIPhasedSubcloudDeployManagerV1(base.BaseCommandTest): ]) self.assertEqual(base.SUBCLOUD_FIELD_RESULT_LIST, actual_call[1]) + @mock.patch('getpass.getpass', return_value='testpassword') + def test_install_subcloud(self, getpass): + self.client.subcloud_deploy_install.return_value = [ + base.SUBCLOUD_RESOURCE] + + with tempfile.NamedTemporaryFile(mode='w') as f: + yaml.dump(base.FAKE_INSTALL_VALUES, f) + file_path = os.path.abspath(f.name) + actual_call = self.call( + cmd.InstallPhasedSubcloudDeploy, app_args=[ + base.NAME, '--install-values', file_path, + ]) + self.assertEqual(base.SUBCLOUD_FIELD_RESULT_LIST, actual_call[1]) + + @mock.patch('getpass.getpass', return_value='testpassword') + def test_install_subcloud_with_release(self, getpass): + self.client.subcloud_deploy_install.return_value = [ + base.SUBCLOUD_RESOURCE] + + with tempfile.NamedTemporaryFile(mode='w') as f: + yaml.dump(base.FAKE_INSTALL_VALUES, f) + file_path = os.path.abspath(f.name) + actual_call = self.call( + cmd.InstallPhasedSubcloudDeploy, app_args=[ + base.NAME, + '--install-values', file_path, + '--release', base.SOFTWARE_VERSION, + ]) + self.assertEqual(base.SUBCLOUD_FIELD_RESULT_LIST, actual_call[1]) + + @mock.patch('getpass.getpass', return_value='testpassword') + def test_install_subcloud_without_install_values(self, getpass): + self.client.subcloud_deploy_install.return_value = [ + base.SUBCLOUD_RESOURCE] + + actual_call = self.call( + cmd.InstallPhasedSubcloudDeploy, app_args=[base.NAME]) + + self.assertEqual(base.SUBCLOUD_FIELD_RESULT_LIST, actual_call[1]) + + @mock.patch('getpass.getpass', return_value='testpassword') + def test_install_file_does_not_exist(self, getpass): + self.client.subcloud_deploy_install.return_value = [ + base.SUBCLOUD_RESOURCE] + with tempfile.NamedTemporaryFile() as f: + file_path = os.path.abspath(f.name) + + e = self.assertRaises(DCManagerClientException, + self.call, + cmd.InstallPhasedSubcloudDeploy, + app_args=[base.NAME, + '--install-values', file_path] + ) + self.assertTrue('install-values does not exist' + in str(e)) + @mock.patch('getpass.getpass', return_value='testpassword') def test_success_configure_subcloud(self, getpass): self.client.subcloud_deploy_config.return_value = [