diff --git a/distributedcloud/dcmanager/api/controllers/v1/phased_subcloud_deploy.py b/distributedcloud/dcmanager/api/controllers/v1/phased_subcloud_deploy.py index 27b8744f0..f3f353303 100644 --- a/distributedcloud/dcmanager/api/controllers/v1/phased_subcloud_deploy.py +++ b/distributedcloud/dcmanager/api/controllers/v1/phased_subcloud_deploy.py @@ -34,6 +34,7 @@ CONFIG = consts.DEPLOY_PHASE_CONFIG COMPLETE = consts.DEPLOY_PHASE_COMPLETE ABORT = consts.DEPLOY_PHASE_ABORT RESUME = consts.DEPLOY_PHASE_RESUME +ENROLL = consts.DEPLOY_PHASE_ENROLL SUBCLOUD_CREATE_REQUIRED_PARAMETERS = ( consts.BOOTSTRAP_VALUES, @@ -55,6 +56,10 @@ SUBCLOUD_BOOTSTRAP_GET_FILE_CONTENTS = ( consts.BOOTSTRAP_VALUES, ) +SUBCLOUD_ENROLL_GET_FILE_CONTENTS = ( + consts.BOOTSTRAP_VALUES, +) + SUBCLOUD_CONFIG_GET_FILE_CONTENTS = ( consts.DEPLOY_CONFIG, ) @@ -92,6 +97,12 @@ VALID_STATES_FOR_DEPLOY_ABORT = ( consts.DEPLOY_STATE_CONFIGURING ) +VALID_STATES_FOR_DEPLOY_ENROLL = ( + consts.DEPLOY_STATE_CREATED, + consts.DEPLOY_STATE_ENROLL_FAILED, + consts.DEPLOY_STATE_ENROLLED, +) + FILES_FOR_RESUME_INSTALL = \ SUBCLOUD_INSTALL_GET_FILE_CONTENTS + \ SUBCLOUD_BOOTSTRAP_GET_FILE_CONTENTS + \ @@ -506,6 +517,39 @@ class PhasedSubcloudDeployController(object): LOG.exception("Unable to resume subcloud %s deployment" % subcloud.name) pecan.abort(500, _('Unable to resume subcloud deployment')) + def _deploy_enroll(self, context: RequestContext, + request: pecan.Request, subcloud: models.Subcloud): + if subcloud.deploy_status not in VALID_STATES_FOR_DEPLOY_ENROLL: + valid_states_str = ', '.join(VALID_STATES_FOR_DEPLOY_ENROLL) + msg = f'Subcloud deploy status must be either: {valid_states_str}' + pecan.abort(400, _(msg)) + + has_bootstrap_values = consts.BOOTSTRAP_VALUES in request.POST + + payload = psd_common.get_request_data( + request, subcloud, SUBCLOUD_ENROLL_GET_FILE_CONTENTS) + + psd_common.validate_enroll_parameter(payload, request) + + # Try to load the existing override values + override_file = psd_common.get_config_file_path(subcloud.name) + if os.path.exists(override_file): + if not has_bootstrap_values: + psd_common.populate_payload_with_pre_existing_data( + payload, subcloud, SUBCLOUD_BOOTSTRAP_GET_FILE_CONTENTS) + elif not has_bootstrap_values: + msg = ("Required bootstrap-values file was not provided and it was " + f"not previously available at {override_file}") + pecan.abort(400, _(msg)) + + payload['software_version'] = subcloud.software_version + + pecan.abort(400, "subcloud deploy enrollment is not " + "available yet") + # TODO(glyraper): Enroll function in development + # TODO(glyraper): Create cover tests + return "" + @pecan.expose(generic=True, template='json') def index(self): # Route the request to specific methods with parameters @@ -555,6 +599,8 @@ class PhasedSubcloudDeployController(object): subcloud = self._deploy_config(context, pecan.request, subcloud) elif verb == COMPLETE: subcloud = self._deploy_complete(context, subcloud) + elif verb == ENROLL: + subcloud = self._deploy_enroll(context, pecan.request, subcloud) else: pecan.abort(400, _('Invalid request')) diff --git a/distributedcloud/dcmanager/api/controllers/v1/subclouds.py b/distributedcloud/dcmanager/api/controllers/v1/subclouds.py index c5d0398d3..18f0f2af6 100644 --- a/distributedcloud/dcmanager/api/controllers/v1/subclouds.py +++ b/distributedcloud/dcmanager/api/controllers/v1/subclouds.py @@ -561,6 +561,9 @@ class SubcloudsController(object): psd_common.validate_secondary_parameter(payload, request) + if payload.get('enroll'): + psd_common.validate_enroll_parameter(payload, request) + # Compares to match both supplied and bootstrap name param # of the subcloud if migrate is on if payload.get('migrate') == 'true' and bootstrap_sc_name is not None: diff --git a/distributedcloud/dcmanager/common/consts.py b/distributedcloud/dcmanager/common/consts.py index 63ed5740b..c262efc16 100644 --- a/distributedcloud/dcmanager/common/consts.py +++ b/distributedcloud/dcmanager/common/consts.py @@ -40,6 +40,7 @@ DEPLOY_PHASE_CONFIG = 'configure' DEPLOY_PHASE_COMPLETE = 'complete' DEPLOY_PHASE_ABORT = 'abort' DEPLOY_PHASE_RESUME = 'resume' +DEPLOY_PHASE_ENROLL = 'enroll' # Admin status for hosts ADMIN_LOCKED = 'locked' @@ -217,6 +218,8 @@ DEPLOY_STATE_INSTALL_ABORTED = 'install-aborted' DEPLOY_STATE_ABORTING_BOOTSTRAP = 'aborting-bootstrap' DEPLOY_STATE_ABORTING_CONFIG = 'aborting-config' DEPLOY_STATE_CONFIG_ABORTED = 'config-aborted' +DEPLOY_STATE_ENROLLED = 'enroll-complete' +DEPLOY_STATE_ENROLL_FAILED = 'enroll-failed' DEPLOY_STATE_MIGRATING_DATA = 'migrating-data' DEPLOY_STATE_DATA_MIGRATION_FAILED = 'data-migration-failed' DEPLOY_STATE_MIGRATED = 'migrated' @@ -226,6 +229,7 @@ DEPLOY_STATE_RESTORE_PREP_FAILED = 'restore-prep-failed' DEPLOY_STATE_RESTORING = 'restoring' DEPLOY_STATE_RESTORE_FAILED = 'restore-failed' DEPLOY_STATE_PRE_REHOME = 'pre-rehome' +DEPLOY_STATE_PRE_ENROLL = 'pre-enroll' # If any of the following rehoming or secondary statuses # are modified, cert-mon code will need to be updated. DEPLOY_STATE_REHOMING = 'rehoming' diff --git a/distributedcloud/dcmanager/common/phased_subcloud_deploy.py b/distributedcloud/dcmanager/common/phased_subcloud_deploy.py index 9fe84f8fd..c1a4b0607 100644 --- a/distributedcloud/dcmanager/common/phased_subcloud_deploy.py +++ b/distributedcloud/dcmanager/common/phased_subcloud_deploy.py @@ -162,6 +162,13 @@ def validate_migrate_parameter(payload, request): 'not allowed')) +def validate_enroll_parameter(payload, request): + enroll_str = payload.get('enroll') + if enroll_str and enroll_str not in ["true", "false"]: + pecan.abort(400, _('The enroll option is invalid, ' + 'valid options are true and false.')) + + def validate_secondary_parameter(payload, request): secondary_str = payload.get('secondary') migrate_str = payload.get('migrate') diff --git a/distributedcloud/dcmanager/manager/subcloud_manager.py b/distributedcloud/dcmanager/manager/subcloud_manager.py index b6c5b6c6a..fcc5e8c90 100644 --- a/distributedcloud/dcmanager/manager/subcloud_manager.py +++ b/distributedcloud/dcmanager/manager/subcloud_manager.py @@ -844,6 +844,7 @@ class SubcloudManager(manager.Manager): rehoming = payload.get('migrate', '').lower() == "true" secondary = (payload.get('secondary', '').lower() == "true") + enroll = payload.get('enroll', '').lower() == "true" initial_deployment = True if not rehoming else False # Create the subcloud @@ -873,8 +874,11 @@ class SubcloudManager(manager.Manager): phases_to_run = [] if consts.INSTALL_VALUES in payload: phases_to_run.append(consts.DEPLOY_PHASE_INSTALL) - phases_to_run.append(consts.DEPLOY_PHASE_BOOTSTRAP) - if consts.DEPLOY_CONFIG in payload: + if enroll: + phases_to_run.append(consts.DEPLOY_PHASE_ENROLL) + else: + phases_to_run.append(consts.DEPLOY_PHASE_BOOTSTRAP) + if not enroll and consts.DEPLOY_CONFIG in payload: phases_to_run.append(consts.DEPLOY_PHASE_CONFIG) else: phases_to_run.append(consts.DEPLOY_PHASE_COMPLETE) @@ -1566,6 +1570,11 @@ class SubcloudManager(manager.Manager): deploy_status=consts.DEPLOY_STATE_PRE_INSTALL_FAILED) return False + def subcloud_deploy_enroll(self, context, subcloud_id, payload, + initial_deployment=False): + + raise NotImplementedError + def subcloud_deploy_bootstrap(self, context, subcloud_id, payload, initial_deployment=False): """Bootstrap subcloud @@ -2226,6 +2235,9 @@ class SubcloudManager(manager.Manager): if consts.DEPLOY_PHASE_INSTALL in deploy_phases_to_run: succeeded = self.subcloud_deploy_install( context, subcloud_id, payload, initial_deployment) + if succeeded and consts.DEPLOY_PHASE_ENROLL in deploy_phases_to_run: + succeeded = self.subcloud_deploy_enroll( + context, subcloud_id, payload, initial_deployment) if succeeded and consts.DEPLOY_PHASE_BOOTSTRAP in deploy_phases_to_run: succeeded = self.subcloud_deploy_bootstrap( context, subcloud_id, payload, initial_deployment) diff --git a/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_phased_subcloud_deploy.py b/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_phased_subcloud_deploy.py index c79f1d078..5083c57e9 100644 --- a/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_phased_subcloud_deploy.py +++ b/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_phased_subcloud_deploy.py @@ -1075,3 +1075,32 @@ class TestPhasedSubcloudDeployPatchResume(BaseTestPhasedSubcloudDeployPatch): "Unable to resume subcloud deployment" ) self.mock_rpc_client().subcloud_deploy_resume.assert_called_once() + + +class TestSubcloudDeployEnroll(BaseTestPhasedSubcloudDeployPatch): + def setUp(self): + super().setUp() + + self.url = f"{self.url}/enroll" + + self._update_subcloud( + deploy_status=consts.DEPLOY_STATE_CREATED, software_version=SW_VERSION + ) + + modified_bootstrap_data = copy.copy(fake_subcloud.FAKE_BOOTSTRAP_FILE_DATA) + fake_content = json.dumps(modified_bootstrap_data).encode("utf-8") + + self.upload_files = \ + [("bootstrap_values", "bootstrap_fake_filename", fake_content)] + + def test_patch_enroll_fails(self): + """Test patch enroll fails""" + + response = self._send_request() + + # self._assert_response(response) + # self._assert_payload() + self._assert_pecan_and_response( + response, http.client.BAD_REQUEST, "subcloud deploy enrollment is not " + "available yet" + )