diff --git a/api-ref/source/api-ref-dcmanager-v1.rst b/api-ref/source/api-ref-dcmanager-v1.rst index fc300bdaa..6504a8322 100644 --- a/api-ref/source/api-ref-dcmanager-v1.rst +++ b/api-ref/source/api-ref-dcmanager-v1.rst @@ -642,6 +642,9 @@ serviceUnavailable (503) :widths: 20, 20, 20, 60 "subcloud", "URI", "xsd:string", "The subcloud reference, name or id." + "sysadmin_password", "plain", "xsd:string", "The sysadmin password of the subcloud. Must be base64 encoded." + "bootstrap_values", "plain", "xsd:string", "The content of a file containing the bootstrap overrides such as subcloud name, management and OAM subnet." + "deploy_config (Optional)", "plain", "xsd:string", "The content of a file containing the resource definitions describing the desired subcloud configuration." **Response parameters** diff --git a/distributedcloud/dcmanager/api/controllers/v1/subclouds.py b/distributedcloud/dcmanager/api/controllers/v1/subclouds.py index f4c2e651d..ebb049d86 100644 --- a/distributedcloud/dcmanager/api/controllers/v1/subclouds.py +++ b/distributedcloud/dcmanager/api/controllers/v1/subclouds.py @@ -428,6 +428,19 @@ class SubcloudsController(object): "%(end)s") % {'start': mgmt_address_start, 'end': mgmt_address_end}) + self._validate_oam_network_config(external_oam_subnet_str, + external_oam_gateway_address_str, + external_oam_floating_address_str, + subcloud_subnets) + self._validate_group_id(context, group_id) + + def _validate_oam_network_config(self, + external_oam_subnet_str, + external_oam_gateway_address_str, + external_oam_floating_address_str, + existing_networks): + """validate whether oam network configuration is valid""" + # Parse/validate the oam subnet MIN_OAM_SUBNET_SIZE = 3 oam_subnet = None @@ -435,7 +448,7 @@ class SubcloudsController(object): oam_subnet = utils.validate_network_str( external_oam_subnet_str, minimum_size=MIN_OAM_SUBNET_SIZE, - existing_networks=subcloud_subnets) + existing_networks=existing_networks) except exceptions.ValidateFail as e: LOG.exception(e) pecan.abort(400, _("external_oam_subnet invalid: %s") % e) @@ -454,7 +467,6 @@ class SubcloudsController(object): except exceptions.ValidateFail as e: LOG.exception(e) pecan.abort(400, _("oam_floating_address invalid: %s") % e) - self._validate_group_id(context, group_id) def _format_ip_address(self, payload): """Format IP addresses in 'bootstrap_values' and 'install_values'. @@ -1080,23 +1092,154 @@ class SubcloudsController(object): LOG.exception("Unable to reconfigure subcloud %s" % subcloud.name) pecan.abort(500, _('Unable to reconfigure subcloud')) elif verb == "reinstall": + payload = self._get_request_data(request) install_values = self._get_subcloud_db_install_values(subcloud) - payload = db_api.subcloud_db_model_to_dict(subcloud) - for k in ['data_install', 'data_upgrade', 'created-at', 'updated-at']: - if k in payload: - del payload[k] + if subcloud.availability_status == consts.AVAILABILITY_ONLINE: + msg = _('Cannot re-install an online subcloud') + LOG.exception(msg) + pecan.abort(400, msg) + + # Validate the bootstrap values with the data in central cloud. + # Stop the process if the boostrap value is not equal to the data + # in DB. Re-use the data from DB if it is not passed. + name = payload.get('name', subcloud.name) + if name != subcloud.name: + pecan.abort(400, _('name is incorrect for the subcloud')) + else: + payload['name'] = name + + system_mode = payload.get('system_mode') + if not system_mode: + pecan.abort(400, _('system_mode required')) + + management_subnet = payload.get('management_subnet', + subcloud.management_subnet) + if management_subnet != subcloud.management_subnet: + pecan.abort(400, _('management_subnet is incorrect for subcloud')) + else: + payload['management_subnet'] = management_subnet + + management_start_ip = payload.get('management_start_address', + subcloud.management_start_ip) + if management_start_ip != subcloud.management_start_ip: + pecan.abort(400, _('management_start_address is incorrect for ' + 'the subcloud')) + else: + payload['management_start_address'] = management_start_ip + + management_end_ip = payload.get('management_end_address', + subcloud.management_end_ip) + if management_end_ip != subcloud.management_end_ip: + pecan.abort(400, _('management_end_address is incorrect for ' + 'the subcloud')) + else: + payload['management_end_address'] = management_end_ip + + management_gateway_ip = payload.get('management_gateway_address', + subcloud.management_gateway_ip) + if management_gateway_ip != subcloud.management_gateway_ip: + pecan.abort(400, _('management_gateway_address is incorrect for ' + 'the subcloud')) + else: + payload['management_gateway_address'] = management_gateway_ip + + systemcontroller_gateway_ip = \ + payload.get('systemcontroller_gateway_address', + subcloud.systemcontroller_gateway_ip) + if systemcontroller_gateway_ip != subcloud.systemcontroller_gateway_ip: + pecan.abort(400, _('systemcontroller_gateway_address is incorrect ' + 'for the subcloud')) + else: + payload['systemcontroller_gateway_address'] = \ + systemcontroller_gateway_ip + + external_oam_subnet = payload.get('external_oam_subnet') + if not external_oam_subnet: + pecan.abort(400, _('external_oam_subnet required')) + + external_oam_gateway_ip = payload.get('external_oam_gateway_address') + if not external_oam_gateway_ip: + pecan.abort(400, _('external_oam_gateway_address required')) + + external_oam_floating_ip = \ + payload.get('external_oam_floating_address') + if not external_oam_floating_ip: + pecan.abort(400, _('external_oam_floating_address required')) + + 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) + + # Search existing subcloud subnets in db + subcloud_subnets = [] + subclouds = db_api.subcloud_get_all(context) + for k in subclouds: + subcloud_subnets.append(IPNetwork(k.management_subnet)) + + self._validate_oam_network_config(external_oam_subnet, + external_oam_gateway_ip, + external_oam_floating_ip, + subcloud_subnets) + + # If the software version of the subcloud is different from the + # central cloud, update the software version in install valuse and + # delete the image path in install values, then the subcloud will + # be reinstalled using the image in dc_vault. + if install_values.get('software_version') != tsc.SW_VERSION: + install_values['software_version'] = tsc.SW_VERSION + install_values.pop('image', None) + + # Confirm the active system controller load is still in dc-vault if + # image not in install values, add the matching image into the + # install values. + if 'image' not in install_values: + matching_iso, matching_sig = \ + SubcloudsController.verify_active_load_in_vault() + LOG.info("image was not in install_values: will reference %s" % + matching_iso) + install_values['image'] = matching_iso + + # Update the install values in payload payload.update({ 'bmc_password': install_values.get('bmc_password'), 'install_values': install_values, }) + # Update data install(software version, image path) + data_install = None + if 'install_values' in payload: + data_install = json.dumps(payload['install_values']) + + # Upload the deploy config files if it is included in the request + self._upload_deploy_config_file(request, payload) + try: + # Align the software version of the subcloud with the central + # cloud. Update description, location and group id if offered, + # update the deploy status as pre-install. + db_api.subcloud_update( + context, + subcloud_id, + description=payload.get('description', subcloud.description), + location=payload.get('location', subcloud.location), + software_version=tsc.SW_VERSION, + management_state=consts.MANAGEMENT_UNMANAGED, + deploy_status=consts.DEPLOY_STATE_PRE_INSTALL, + data_install=data_install) + self.rpc_client.reinstall_subcloud( context, subcloud_id, payload) - # Return deploy_status as pre-install - subcloud.deploy_status = consts.DEPLOY_STATE_PRE_INSTALL return db_api.subcloud_db_model_to_dict(subcloud) except RemoteError as e: pecan.abort(422, e.value) diff --git a/distributedcloud/dcmanager/manager/subcloud_manager.py b/distributedcloud/dcmanager/manager/subcloud_manager.py index 563042917..338c086c9 100644 --- a/distributedcloud/dcmanager/manager/subcloud_manager.py +++ b/distributedcloud/dcmanager/manager/subcloud_manager.py @@ -392,38 +392,7 @@ class SubcloudManager(manager.Manager): # Query system controller keystone admin user/project IDs, # services project id, sysinv and dcmanager user id and store in # payload so they get copied to the override file - admin_user_id = None - sysinv_user_id = None - dcmanager_user_id = None - admin_project_id = None - services_project_id = None - - user_list = m_ks_client.get_enabled_users(id_only=False) - for user in user_list: - if user.name == dccommon_consts.ADMIN_USER_NAME: - admin_user_id = user.id - elif user.name == dccommon_consts.SYSINV_USER_NAME: - sysinv_user_id = user.id - elif user.name == dccommon_consts.DCMANAGER_USER_NAME: - dcmanager_user_id = user.id - - project_list = m_ks_client.get_enabled_projects(id_only=False) - for project in project_list: - if project.name == dccommon_consts.ADMIN_PROJECT_NAME: - admin_project_id = project.id - elif project.name == dccommon_consts.SERVICES_USER_NAME: - services_project_id = project.id - - payload['system_controller_keystone_admin_user_id'] = \ - admin_user_id - payload['system_controller_keystone_admin_project_id'] = \ - admin_project_id - payload['system_controller_keystone_services_project_id'] = \ - services_project_id - payload['system_controller_keystone_sysinv_user_id'] = \ - sysinv_user_id - payload['system_controller_keystone_dcmanager_user_id'] = \ - dcmanager_user_id + self._get_keystone_ids(m_ks_client, payload) # Add the admin and service user passwords to the payload so they # get copied to the override file @@ -550,6 +519,45 @@ class SubcloudManager(manager.Manager): context, subcloud_id, deploy_status=consts.DEPLOY_STATE_DEPLOY_PREP_FAILED) + def _get_keystone_ids(self, keystone_client, payload): + """Get keystone user_id and project_id + + :param keystone_client: keystone client + :param payload: subcloud configuration + """ + admin_user_id = None + sysinv_user_id = None + dcmanager_user_id = None + admin_project_id = None + services_project_id = None + + user_list = keystone_client.get_enabled_users(id_only=False) + for user in user_list: + if user.name == dccommon_consts.ADMIN_USER_NAME: + admin_user_id = user.id + elif user.name == dccommon_consts.SYSINV_USER_NAME: + sysinv_user_id = user.id + elif user.name == dccommon_consts.DCMANAGER_USER_NAME: + dcmanager_user_id = user.id + + project_list = keystone_client.get_enabled_projects(id_only=False) + for project in project_list: + if project.name == dccommon_consts.ADMIN_PROJECT_NAME: + admin_project_id = project.id + elif project.name == dccommon_consts.SERVICES_USER_NAME: + services_project_id = project.id + + payload['system_controller_keystone_admin_user_id'] = \ + admin_user_id + payload['system_controller_keystone_admin_project_id'] = \ + admin_project_id + payload['system_controller_keystone_services_project_id'] = \ + services_project_id + payload['system_controller_keystone_sysinv_user_id'] = \ + sysinv_user_id + payload['system_controller_keystone_dcmanager_user_id'] = \ + dcmanager_user_id + def reinstall_subcloud(self, context, subcloud_id, payload): """Reinstall subcloud @@ -561,51 +569,50 @@ class SubcloudManager(manager.Manager): # Retrieve the subcloud details from the database subcloud = db_api.subcloud_get(context, subcloud_id) - # Semantic checking - if subcloud.availability_status == \ - consts.AVAILABILITY_ONLINE: - raise exceptions.SubcloudNotOffline() - - software_version = str(payload['install_values'].get('software_version')) - LOG.info("The type of sw version is %s" % type(SW_VERSION)) - - if software_version != SW_VERSION: - raise exceptions.BadRequest( - resource='subcloud', - msg='Software version should match the system controller') - - if 'image' not in payload['install_values']: - matching_iso, matching_sig = utils.get_vault_load_files( - SW_VERSION) - payload['install_values'].update({'image': matching_iso}) - LOG.info("Reinstalling subcloud %s." % subcloud_id) - subcloud = db_api.subcloud_update( - context, subcloud_id, - software_version=SW_VERSION, - deploy_status=consts.DEPLOY_STATE_PRE_INSTALL) - try: ansible_subcloud_inventory_file = self._get_ansible_filename( subcloud.name, INVENTORY_FILE_POSTFIX) + m_ks_client = OpenStackDriver( + region_name=consts.DEFAULT_REGION_NAME, + region_clients=None).keystone_client + self._get_keystone_ids(m_ks_client, payload) + payload['admin_password'] = str( keyring.get_password('CGCS', 'admin')) - payload['ansible_become_pass'] = payload['admin_password'] - payload['ansible_ssh_pass'] = payload['admin_password'] + payload['ansible_become_pass'] = payload['sysadmin_password'] + payload['ansible_ssh_pass'] = payload['sysadmin_password'] payload['install_values']['ansible_ssh_pass'] = \ - payload['admin_password'] + payload['sysadmin_password'] payload['install_values']['ansible_become_pass'] = \ - payload['admin_password'] + payload['sysadmin_password'] payload['bootstrap-address'] = \ payload['install_values']['bootstrap_address'] + deploy_command = None + if "deploy_playbook" in payload: + self._prepare_for_deployment(payload, subcloud.name) + deploy_command = self.compose_deploy_command( + subcloud.name, + ansible_subcloud_inventory_file, + payload) + del payload['sysadmin_password'] + + payload['users'] = dict() + for user in USERS_TO_REPLICATE: + payload['users'][user] = \ + str(keyring.get_password( + user, dccommon_consts.SERVICES_USER_NAME)) + utils.create_subcloud_inventory(payload, ansible_subcloud_inventory_file) self._create_intermediate_ca_cert(payload) + self._write_subcloud_ansible_config(context, payload) + install_command = self.compose_install_command( subcloud.name, ansible_subcloud_inventory_file) @@ -615,7 +622,7 @@ class SubcloudManager(manager.Manager): apply_thread = threading.Thread( target=self.run_deploy, args=(subcloud, payload, context, - install_command, apply_command)) + install_command, apply_command, deploy_command)) apply_thread.start() return db_api.subcloud_db_model_to_dict(subcloud) except Exception: @@ -624,7 +631,7 @@ class SubcloudManager(manager.Manager): # deployment status db_api.subcloud_update( context, subcloud_id, - deploy_status=consts.DEPLOY_STATE_DEPLOY_PREP_FAILED) + 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( 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 7d1be1a54..7ece85d9c 100644 --- a/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_subclouds.py +++ b/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_subclouds.py @@ -53,6 +53,7 @@ FAKE_HEADERS = fake_subcloud.FAKE_HEADERS FAKE_SUBCLOUD_DATA = fake_subcloud.FAKE_SUBCLOUD_DATA FAKE_BOOTSTRAP_VALUE = fake_subcloud.FAKE_BOOTSTRAP_VALUE FAKE_SUBCLOUD_INSTALL_VALUES = fake_subcloud.FAKE_SUBCLOUD_INSTALL_VALUES +FAKE_SUBCLOUD_BOOTSTRAP_PAYLOAD = fake_subcloud.FAKE_SUBCLOUD_BOOTSTRAP_PAYLOAD class Subcloud(object): @@ -1266,31 +1267,186 @@ class TestSubcloudAPIOther(testroot.DCManagerApiTest): @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, '_validate_install_values') + @mock.patch.object(subclouds.SubcloudsController, '_validate_oam_network_config') + @mock.patch.object(subclouds.SubcloudsController, '_get_request_data') def test_reinstall_subcloud( - self, moc_validate_install_values, mock_get_subcloud_db_install_values, - mock_rpc_client, mock_get_vault_load_files): + self, mock_get_request_data, mock_validate_oam_network_config, + 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) + reinstall_data = copy.copy(FAKE_SUBCLOUD_BOOTSTRAP_PAYLOAD) + mock_get_request_data.return_value = reinstall_data + encoded_password = base64.b64encode( 'bmc_password'.encode("utf-8")).decode('utf-8') bmc_password = {'bmc_password': encoded_password} install_data.update(bmc_password) - mock_get_subcloud_db_install_values.return_value = install_data + mock_rpc_client().reinstall_subcloud.return_value = True mock_get_vault_load_files.return_value = ('iso_file_path', 'sig_file_path') + response = self.app.patch_json( FAKE_URL + '/' + str(subcloud.id) + '/reinstall', - headers=FAKE_HEADERS) + headers=FAKE_HEADERS, params=reinstall_data) + + mock_validate_oam_network_config.assert_called_once() mock_rpc_client().reinstall_subcloud.assert_called_once_with( mock.ANY, subcloud.id, mock.ANY) self.assertEqual(response.status_int, 200) + @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, '_validate_oam_network_config') + @mock.patch.object(subclouds.SubcloudsController, '_get_request_data') + def test_reinstall_subcloud_no_body( + self, mock_get_request_data, mock_validate_oam_network_config, + 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) + mock_get_request_data.return_value = {} + encoded_password = base64.b64encode( + 'bmc_password'.encode("utf-8")).decode('utf-8') + bmc_password = {'bmc_password': encoded_password} + install_data.update(bmc_password) + + mock_validate_oam_network_config.assert_not_called() + mock_get_subcloud_db_install_values.return_value = install_data + mock_rpc_client().reinstall_subcloud.return_value = True + six.assertRaisesRegex(self, webtest.app.AppError, "400 *", + self.app.patch_json, FAKE_URL + '/' + + str(subcloud.id) + '/reinstall', + headers=FAKE_HEADERS, params={}) + + @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, '_validate_oam_network_config') + @mock.patch.object(subclouds.SubcloudsController, '_get_request_data') + def test_reinstall_online_subcloud( + self, mock_get_request_data, mock_validate_oam_network_config, + mock_get_subcloud_db_install_values, mock_rpc_client, + mock_get_vault_load_files): + + subcloud = fake_subcloud.create_fake_subcloud(self.ctx) + db_api.subcloud_update(self.ctx, + subcloud.id, + availability_status=consts.AVAILABILITY_ONLINE) + install_data = copy.copy(FAKE_SUBCLOUD_INSTALL_VALUES) + reinstall_data = copy.copy(FAKE_SUBCLOUD_BOOTSTRAP_PAYLOAD) + mock_get_request_data.return_value = reinstall_data + encoded_password = base64.b64encode( + 'bmc_password'.encode("utf-8")).decode('utf-8') + bmc_password = {'bmc_password': encoded_password} + install_data.update(bmc_password) + + mock_validate_oam_network_config.assert_not_called() + mock_get_subcloud_db_install_values.return_value = install_data + mock_rpc_client().reinstall_subcloud.return_value = True + six.assertRaisesRegex(self, webtest.app.AppError, "400 *", + self.app.patch_json, FAKE_URL + '/' + + str(subcloud.id) + '/reinstall', + headers=FAKE_HEADERS, params={}) + + @mock.patch.object(rpc_client, 'ManagerClient') + @mock.patch.object(subclouds.SubcloudsController, '_get_subcloud_db_install_values') + @mock.patch.object(subclouds.SubcloudsController, '_get_request_data') + def test_reinstall_subcloud_missing_required_value( + self, mock_get_request_data, mock_get_subcloud_db_install_values, + mock_rpc_client): + + subcloud = fake_subcloud.create_fake_subcloud(self.ctx) + install_data = copy.copy(FAKE_SUBCLOUD_INSTALL_VALUES) + + encoded_password = base64.b64encode( + 'bmc_password'.encode("utf-8")).decode('utf-8') + bmc_password = {'bmc_password': encoded_password} + install_data.update(bmc_password) + mock_get_subcloud_db_install_values.return_value = install_data + mock_rpc_client().reinstall_subcloud.return_value = True + + for k in ['name', 'system_mode', 'external_oam_subnet', + 'external_oam_gateway_address', 'external_oam_floating_address', + 'sysadmin_password']: + reinstall_data = copy.copy(FAKE_SUBCLOUD_BOOTSTRAP_PAYLOAD) + del reinstall_data[k] + mock_get_request_data.return_value = reinstall_data + six.assertRaisesRegex(self, webtest.app.AppError, "400 *", + self.app.patch_json, FAKE_URL + '/' + + str(subcloud.id) + '/reinstall', + headers=FAKE_HEADERS, params=reinstall_data) + + @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, '_validate_oam_network_config') + @mock.patch.object(subclouds.SubcloudsController, '_get_request_data') + def test_reinstall_subcloud_missing_stored_value( + self, mock_get_request_data, mock_validate_oam_network_config, + 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) + + encoded_password = base64.b64encode( + 'bmc_password'.encode("utf-8")).decode('utf-8') + bmc_password = {'bmc_password': encoded_password} + install_data.update(bmc_password) + mock_get_subcloud_db_install_values.return_value = install_data + + mock_rpc_client().reinstall_subcloud.return_value = True + mock_get_vault_load_files.return_value = ('iso_file_path', 'sig_file_path') + + for k in ['management_subnet', 'management_start_address', 'management_end_address', + 'management_gateway_address', 'systemcontroller_gateway_address']: + reinstall_data = copy.copy(FAKE_SUBCLOUD_BOOTSTRAP_PAYLOAD) + del reinstall_data[k] + mock_get_request_data.return_value = reinstall_data + response = self.app.patch_json( + FAKE_URL + '/' + str(subcloud.id) + '/reinstall', + headers=FAKE_HEADERS, params=reinstall_data) + self.assertEqual(response.status_int, 200) + + @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, '_validate_subcloud_config') + @mock.patch.object(subclouds.SubcloudsController, '_get_request_data') + def test_reinstall_subcloud_stored_value_not_match( + self, mock_get_request_data, mock_validate_subcloud_config, + 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) + + encoded_password = base64.b64encode( + 'bmc_password'.encode("utf-8")).decode('utf-8') + bmc_password = {'bmc_password': encoded_password} + install_data.update(bmc_password) + mock_get_subcloud_db_install_values.return_value = install_data + + mock_rpc_client().reinstall_subcloud.return_value = True + mock_get_vault_load_files.return_value = ('iso_file_path', 'sig_file_path') + + for k in ['management_subnet', 'management_start_address', 'management_end_address', + 'management_gateway_address', 'systemcontroller_gateway_address']: + reinstall_data = copy.copy(FAKE_SUBCLOUD_BOOTSTRAP_PAYLOAD) + reinstall_data[k] = 'wrong_value' + mock_get_request_data.return_value = reinstall_data + six.assertRaisesRegex(self, webtest.app.AppError, "400 *", + self.app.patch_json, FAKE_URL + '/' + + 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, diff --git a/distributedcloud/dcmanager/tests/unit/common/fake_subcloud.py b/distributedcloud/dcmanager/tests/unit/common/fake_subcloud.py index 34821d8f5..af8e98abf 100644 --- a/distributedcloud/dcmanager/tests/unit/common/fake_subcloud.py +++ b/distributedcloud/dcmanager/tests/unit/common/fake_subcloud.py @@ -40,9 +40,27 @@ FAKE_BOOTSTRAP_VALUE = { 'sysadmin_password': base64.b64encode('testpass'.encode("utf-8")) } +FAKE_SUBCLOUD_BOOTSTRAP_PAYLOAD = { + 'bootstrap-address': '10.10.10.12', + "system_mode": "simplex", + "name": "subcloud1", + "description": "subcloud1 description", + "location": "subcloud1 location", + "management_subnet": "192.168.101.0/24", + "management_gateway_address": "192.168.101.1", + "management_start_address": "192.168.101.2", + "management_end_address": "192.168.101.50", + "systemcontroller_gateway_address": "192.168.204.101", + "external_oam_subnet": "10.10.10.0/24", + "external_oam_gateway_address": "10.10.10.1", + "external_oam_floating_address": "10.10.10.12", + 'sysadmin_password': + (base64.b64encode('testpass'.encode("utf-8"))).decode('ascii'), +} + FAKE_SUBCLOUD_INSTALL_VALUES = { "image": "http://192.168.101.2:8080/iso/bootimage.iso", - "software_version": "12.34", + "software_version": "18.03", "bootstrap_interface": "eno1", "bootstrap_address": "128.224.151.183", "bootstrap_address_prefix": 23, @@ -68,8 +86,8 @@ def create_fake_subcloud(ctxt, **kwargs): 'software_version': "18.03", "management_subnet": "192.168.101.0/24", "management_gateway_ip": "192.168.101.1", - "management_start_ip": "192.168.101.3", - "management_end_ip": "192.168.101.4", + "management_start_ip": "192.168.101.2", + "management_end_ip": "192.168.101.50", "systemcontroller_gateway_ip": "192.168.204.101", 'deploy_status': consts.DEPLOY_STATE_DONE, 'openstack_installed': False, diff --git a/distributedcloud/dcmanager/tests/unit/manager/test_subcloud_manager.py b/distributedcloud/dcmanager/tests/unit/manager/test_subcloud_manager.py index 9bec14d8f..43d8d0ae6 100644 --- a/distributedcloud/dcmanager/tests/unit/manager/test_subcloud_manager.py +++ b/distributedcloud/dcmanager/tests/unit/manager/test_subcloud_manager.py @@ -17,6 +17,7 @@ # of an applicable Wind River license agreement. # +import copy import mock from oslo_concurrency import lockutils @@ -1169,6 +1170,8 @@ class TestSubcloudManager(base.DCManagerTestCase): ] ) + @mock.patch.object( + subcloud_manager.SubcloudManager, '_write_subcloud_ansible_config') @mock.patch.object( subcloud_manager.SubcloudManager, '_create_intermediate_ca_cert') @mock.patch.object( @@ -1176,104 +1179,70 @@ class TestSubcloudManager(base.DCManagerTestCase): @mock.patch.object( subcloud_manager.SubcloudManager, 'compose_apply_command') @mock.patch.object(cutils, 'create_subcloud_inventory') + @mock.patch.object(subcloud_manager.SubcloudManager, '_get_keystone_ids') + @mock.patch.object(subcloud_manager, 'OpenStackDriver') @mock.patch.object(threading.Thread, 'start') @mock.patch.object(subcloud_manager, 'keyring') - def test_reinstall_subcloud_with_image( + def test_reinstall_subcloud( self, mock_keyring, mock_thread_start, - mock_create_subcloud_inventory, + mock_keystone_client, mock_get_keystone_ids, mock_create_subcloud_inventory, mock_compose_apply_command, mock_compose_install_command, - mock_create_intermediate_ca_cert): + mock_create_intermediate_ca_cert, mock_write_subcloud_ansible_config): subcloud = self.create_subcloud_static( self.ctx, name='subcloud1', - deploy_status=consts.DEPLOY_STATE_PRE_DEPLOY) + deploy_status=consts.DEPLOY_STATE_PRE_INSTALL) - fake_install_values = fake_subcloud.FAKE_SUBCLOUD_INSTALL_VALUES + fake_install_values = \ + copy.copy(fake_subcloud.FAKE_SUBCLOUD_INSTALL_VALUES) fake_install_values['software_version'] = SW_VERSION - fake_payload = { - "bmc_password": "bmc_pass", - "install_values": fake_install_values} + fake_payload = copy.copy(fake_subcloud.FAKE_SUBCLOUD_BOOTSTRAP_PAYLOAD) + fake_payload.update({ + 'bmc_password': 'bmc_pass', + 'software_version': SW_VERSION, + 'install_values': fake_install_values}) sm = subcloud_manager.SubcloudManager() mock_keyring.get_password.return_value = "testpassword" sm.reinstall_subcloud(self.ctx, subcloud.id, payload=fake_payload) - mock_keyring.get_password.assert_called_once() + mock_keystone_client.assert_called_once() + mock_get_keystone_ids.assert_called_once() mock_create_subcloud_inventory.assert_called_once() + mock_create_intermediate_ca_cert.assert_called_once() + mock_write_subcloud_ansible_config.assert_called_once() mock_compose_install_command.assert_called_once() mock_compose_apply_command.assert_called_once() mock_thread_start.assert_called_once() - @mock.patch.object( - subcloud_manager.SubcloudManager, '_create_intermediate_ca_cert') - @mock.patch.object(cutils, "get_vault_load_files") - @mock.patch.object( - subcloud_manager.SubcloudManager, 'compose_install_command') - @mock.patch.object( - subcloud_manager.SubcloudManager, 'compose_apply_command') - @mock.patch.object(cutils, 'create_subcloud_inventory') - @mock.patch.object(threading.Thread, 'start') - @mock.patch.object(subcloud_manager, 'keyring') - def test_reinstall_subcloud_without_image( - self, mock_keyring, mock_thread_start, mock_create_subcloud_inventory, - mock_compose_apply_command, mock_compose_install_command, - mock_get_vault_load_files, mock_create_intermediate_ca_cert): - - subcloud = self.create_subcloud_static( - self.ctx, - name='subcloud1', - deploy_status=consts.DEPLOY_STATE_PRE_DEPLOY) - - fake_install_values = fake_subcloud.FAKE_SUBCLOUD_INSTALL_VALUES - fake_install_values['software_version'] = SW_VERSION - del fake_install_values['image'] - fake_payload = { - "bmc_password": "bmc_pass", - "install_values": fake_install_values} - + def test_get_keystone_get_keystone_ids(self): + keystone_client = FakeKeystoneClient() + payload = dict() sm = subcloud_manager.SubcloudManager() - mock_keyring.get_password.return_value = "testpassword" - mock_get_vault_load_files.return_value = ("iso file path", "sig file path") - - sm.reinstall_subcloud(self.ctx, subcloud.id, payload=fake_payload) - mock_keyring.get_password.assert_called_once() - mock_create_subcloud_inventory.assert_called_once() - mock_compose_install_command.assert_called_once() - mock_compose_apply_command.assert_called_once() - mock_thread_start.assert_called_once() - - def test_reinstall_online_subcloud(self): - subcloud = self.create_subcloud_static( - self.ctx, - name='subcloud1', - deploy_status=consts.DEPLOY_STATE_PRE_DEPLOY) - db_api.subcloud_update(self.ctx, - subcloud.id, - availability_status=consts.AVAILABILITY_ONLINE) - - data = utils.create_subcloud_dict(base.SUBCLOUD_SAMPLE_DATA_0) - sm = subcloud_manager.SubcloudManager() - self.assertRaises(exceptions.SubcloudNotOffline, - sm.reinstall_subcloud, self.ctx, - subcloud.id, data) - - def test_reinstall_subcloud_software_not_match(self): - subcloud = self.create_subcloud_static( - self.ctx, - name='subcloud1', - deploy_status=consts.DEPLOY_STATE_PRE_DEPLOY) - db_api.subcloud_update(self.ctx, - subcloud.id, - availability_status=consts.AVAILABILITY_OFFLINE) - - fake_install_values = fake_subcloud.FAKE_SUBCLOUD_INSTALL_VALUES - data = utils.create_subcloud_dict(base.SUBCLOUD_SAMPLE_DATA_0) - data.update({'install_values': fake_install_values}) - sm = subcloud_manager.SubcloudManager() - self.assertRaises(exceptions.BadRequest, - sm.reinstall_subcloud, self.ctx, - subcloud.id, data) + sm._get_keystone_ids(keystone_client, payload) + for fake_user in FAKE_USERS: + if fake_user.name == dccommon_consts.ADMIN_USER_NAME: + self.assertEqual( + payload['system_controller_keystone_admin_user_id'], + fake_user.id) + elif fake_user.name == dccommon_consts.SYSINV_USER_NAME: + self.assertEqual( + payload['system_controller_keystone_sysinv_user_id'], + fake_user.id) + elif fake_user.name == dccommon_consts.DCMANAGER_USER_NAME: + self.assertEqual( + payload['system_controller_keystone_dcmanager_user_id'], + fake_user.id) + for fake_project in FAKE_PROJECTS: + if fake_project.name == dccommon_consts.ADMIN_PROJECT_NAME: + self.assertEqual( + payload['system_controller_keystone_admin_project_id'], + fake_project.id) + elif fake_project.name == dccommon_consts.SERVICES_USER_NAME: + self.assertEqual( + payload['system_controller_keystone_services_project_id'], + fake_project.id) def test_handle_subcloud_operations_in_progress(self): subcloud1 = self.create_subcloud_static(