Create subcloud enroll API

This commit introduces the enroll command endpoint.

Test Plan:

PASS: Deploy a system controller and run subcloud add
enroll without bootstrap-values. Verify that the API
returns an error.

PASS: Deploy a system controller and run subcloud add
enroll passing all required parameters. Verify in
dcmanager log that the API returned a success code.

Change-Id: I525d26166dbb7d7afcb26b96191b5045eee7b52d
Signed-off-by: Gustavo Pereira <gustavo.lyrapereira@windriver.com>
This commit is contained in:
Gustavo Pereira 2024-04-15 18:42:20 -03:00
parent 9bf56e9e30
commit 49de17a185
6 changed files with 103 additions and 2 deletions

View File

@ -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'))

View File

@ -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:

View File

@ -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'

View File

@ -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')

View File

@ -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)

View File

@ -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"
)