
This change adds the capability to rename the subcloud after bootstrap or during subcloud rehome operation. Added a field in the database to separate the region name from the subcloud name. The region name determines the subcloud reference in the Openstack core, through which it is possible to access the endpoints of a given subcloud. Since the region name cannot be changed, this commit adds the ability to maintain a unique region name based on the UUID format, and allows subcloud renaming when necessary without any endpoint impact. The region is randomly generated to configure the subcloud when it is created and only applies to future subclouds. For those systems that have existing subclouds, the region will be the same as on day 0, that is, region will keep the same name as the subcloud, but subclouds can be renamed. This topic involves changes to dcmanager, dcmanager-client and GUI. To ensure the region name reference needed by the cert-monitor, a mechanism to determine if the request is coming from the cert-monitor has been created. Usage for subcloud rename: dcmanager subcloud update <subcloud-name> --name <new-name> Usage for subcloud rehoming: dcmanager subcloud add --name <subcloud-name> --migrate ... Note: Upgrade test from StarlingX 8 -> 9 for this commit is deferred until upgrade functionality in master is restored. Any issue found during upgrade test will be addressed in a separate commit Test Plan: PASS: Run dcmanager subcloud passing subcommands: - add/delete/migrate/list/show/show --detail - errors/manage/unmanage/reinstall/reconfig - update/deploy PASS: Run dcmanager subcloud add supplying --name parameter and validate the operation is not allowed PASS: Run dcmanager supplying subcommands: - kube/patch/prestage strategies PASS: Run dcmanager to apply patch and remove it PASS: Run dcmanager subcloud-backup: - create/delete/restore/show/upload PASS: Run subcloud-group: - add/delete/list/list-subclouds/show/update PASS: Run dcmanager subcloud strategy for: - patch/kubernetes/firmware PASS: Run dcmanager subcloud update command passing --name parameter supplying the following values: - current subcloud name (not changed) - different existing subcloud name PASS: Run dcmanager to migrate a subcloud passing --name parameter supplying a new subcloud name PASS: Run dcmanager to migrate a subcloud without --name parameter PASS: Run dcmanager to migrate a subcloud passing --name parameter supplying a new subcloud name and different subcloud name in bootstrap file PASS: Test dcmanager API response using cURL command line to validate new region name field PASS: Run full DC sanity and regression Story: 2010788 Task: 48217 Signed-off-by: Cristian Mondo <cristian.mondo@windriver.com> Change-Id: Id04f42504b8e325d9ec3880c240fe4a06e3a20b7
487 lines
20 KiB
Python
487 lines
20 KiB
Python
#
|
|
# Copyright (c) 2023 Wind River Systems, Inc.
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
|
|
import http.client as httpclient
|
|
import os
|
|
|
|
from oslo_log import log as logging
|
|
from oslo_messaging import RemoteError
|
|
import pecan
|
|
import yaml
|
|
|
|
from dcmanager.api.controllers import restcomm
|
|
from dcmanager.api.policies import phased_subcloud_deploy as \
|
|
phased_subcloud_deploy_policy
|
|
from dcmanager.api import policy
|
|
from dcmanager.common import consts
|
|
from dcmanager.common.context import RequestContext
|
|
from dcmanager.common import exceptions
|
|
from dcmanager.common.i18n import _
|
|
from dcmanager.common import phased_subcloud_deploy as psd_common
|
|
from dcmanager.common import prestage
|
|
from dcmanager.common import utils
|
|
from dcmanager.db import api as db_api
|
|
from dcmanager.db.sqlalchemy import models
|
|
from dcmanager.rpc import client as rpc_client
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
LOCK_NAME = 'PhasedSubcloudDeployController'
|
|
|
|
INSTALL = consts.DEPLOY_PHASE_INSTALL
|
|
BOOTSTRAP = consts.DEPLOY_PHASE_BOOTSTRAP
|
|
CONFIG = consts.DEPLOY_PHASE_CONFIG
|
|
COMPLETE = consts.DEPLOY_PHASE_COMPLETE
|
|
ABORT = consts.DEPLOY_PHASE_ABORT
|
|
RESUME = consts.DEPLOY_PHASE_RESUME
|
|
|
|
SUBCLOUD_CREATE_REQUIRED_PARAMETERS = (
|
|
consts.BOOTSTRAP_VALUES,
|
|
consts.BOOTSTRAP_ADDRESS
|
|
)
|
|
|
|
# The consts.DEPLOY_CONFIG is missing here because it's handled differently
|
|
# by the upload_deploy_config_file() function
|
|
SUBCLOUD_CREATE_GET_FILE_CONTENTS = (
|
|
consts.BOOTSTRAP_VALUES,
|
|
consts.INSTALL_VALUES,
|
|
)
|
|
|
|
SUBCLOUD_INSTALL_GET_FILE_CONTENTS = (
|
|
consts.INSTALL_VALUES,
|
|
)
|
|
|
|
SUBCLOUD_BOOTSTRAP_GET_FILE_CONTENTS = (
|
|
consts.BOOTSTRAP_VALUES,
|
|
)
|
|
|
|
SUBCLOUD_CONFIG_GET_FILE_CONTENTS = (
|
|
consts.DEPLOY_CONFIG,
|
|
)
|
|
|
|
VALID_STATES_FOR_DEPLOY_INSTALL = (
|
|
consts.DEPLOY_STATE_CREATED,
|
|
consts.DEPLOY_STATE_PRE_INSTALL_FAILED,
|
|
consts.DEPLOY_STATE_INSTALL_FAILED,
|
|
consts.DEPLOY_STATE_INSTALLED,
|
|
consts.DEPLOY_STATE_INSTALL_ABORTED
|
|
)
|
|
|
|
VALID_STATES_FOR_DEPLOY_BOOTSTRAP = [
|
|
consts.DEPLOY_STATE_INSTALLED,
|
|
consts.DEPLOY_STATE_PRE_BOOTSTRAP_FAILED,
|
|
consts.DEPLOY_STATE_BOOTSTRAP_FAILED,
|
|
consts.DEPLOY_STATE_BOOTSTRAP_ABORTED,
|
|
consts.DEPLOY_STATE_BOOTSTRAPPED,
|
|
# The subcloud can be installed manually (without remote install) so we need
|
|
# to allow the bootstrap operation when the state == DEPLOY_STATE_CREATED
|
|
consts.DEPLOY_STATE_CREATED
|
|
]
|
|
|
|
VALID_STATES_FOR_DEPLOY_CONFIG = (
|
|
consts.DEPLOY_STATE_DONE,
|
|
consts.DEPLOY_STATE_PRE_CONFIG_FAILED,
|
|
consts.DEPLOY_STATE_CONFIG_FAILED,
|
|
consts.DEPLOY_STATE_BOOTSTRAPPED,
|
|
consts.DEPLOY_STATE_CONFIG_ABORTED,
|
|
# The next two states are needed due to upgrade scenario:
|
|
# TODO(gherzman): remove states when they are no longer needed
|
|
consts.DEPLOY_STATE_DEPLOY_FAILED,
|
|
consts.DEPLOY_STATE_DEPLOY_PREP_FAILED,
|
|
)
|
|
|
|
VALID_STATES_FOR_DEPLOY_ABORT = (
|
|
consts.DEPLOY_STATE_INSTALLING,
|
|
consts.DEPLOY_STATE_BOOTSTRAPPING,
|
|
consts.DEPLOY_STATE_CONFIGURING
|
|
)
|
|
|
|
FILES_FOR_RESUME_INSTALL = \
|
|
SUBCLOUD_INSTALL_GET_FILE_CONTENTS + \
|
|
SUBCLOUD_BOOTSTRAP_GET_FILE_CONTENTS + \
|
|
SUBCLOUD_CONFIG_GET_FILE_CONTENTS
|
|
|
|
|
|
FILES_FOR_RESUME_BOOTSTRAP = \
|
|
SUBCLOUD_BOOTSTRAP_GET_FILE_CONTENTS + \
|
|
SUBCLOUD_CONFIG_GET_FILE_CONTENTS
|
|
|
|
|
|
FILES_FOR_RESUME_CONFIG = SUBCLOUD_CONFIG_GET_FILE_CONTENTS
|
|
|
|
RESUMABLE_STATES = {
|
|
consts.DEPLOY_STATE_CREATED: [INSTALL, BOOTSTRAP, CONFIG],
|
|
consts.DEPLOY_STATE_INSTALLED: [BOOTSTRAP, CONFIG],
|
|
consts.DEPLOY_STATE_PRE_INSTALL_FAILED: [INSTALL, BOOTSTRAP, CONFIG],
|
|
consts.DEPLOY_STATE_INSTALL_FAILED: [INSTALL, BOOTSTRAP, CONFIG],
|
|
consts.DEPLOY_STATE_INSTALL_ABORTED: [INSTALL, BOOTSTRAP, CONFIG],
|
|
consts.DEPLOY_STATE_BOOTSTRAPPED: [CONFIG],
|
|
consts.DEPLOY_STATE_PRE_BOOTSTRAP_FAILED: [BOOTSTRAP, CONFIG],
|
|
consts.DEPLOY_STATE_BOOTSTRAP_FAILED: [BOOTSTRAP, CONFIG],
|
|
consts.DEPLOY_STATE_BOOTSTRAP_ABORTED: [BOOTSTRAP, CONFIG],
|
|
consts.DEPLOY_STATE_PRE_CONFIG_FAILED: [CONFIG],
|
|
consts.DEPLOY_STATE_CONFIG_FAILED: [CONFIG],
|
|
consts.DEPLOY_STATE_CONFIG_ABORTED: [CONFIG]
|
|
}
|
|
|
|
FILES_MAPPING = {
|
|
INSTALL: SUBCLOUD_INSTALL_GET_FILE_CONTENTS,
|
|
BOOTSTRAP: SUBCLOUD_BOOTSTRAP_GET_FILE_CONTENTS,
|
|
CONFIG: SUBCLOUD_CONFIG_GET_FILE_CONTENTS
|
|
}
|
|
|
|
RESUME_PREP_UPDATE_STATUS = {
|
|
INSTALL: consts.DEPLOY_STATE_PRE_INSTALL,
|
|
BOOTSTRAP: consts.DEPLOY_STATE_PRE_BOOTSTRAP,
|
|
CONFIG: consts.DEPLOY_STATE_PRE_CONFIG
|
|
}
|
|
|
|
|
|
def get_create_payload(request: pecan.Request) -> dict:
|
|
payload = dict()
|
|
|
|
for f in SUBCLOUD_CREATE_GET_FILE_CONTENTS:
|
|
if f in request.POST:
|
|
file_item = request.POST[f]
|
|
file_item.file.seek(0, os.SEEK_SET)
|
|
data = yaml.safe_load(file_item.file.read().decode('utf8'))
|
|
if f == consts.BOOTSTRAP_VALUES:
|
|
payload.update(data)
|
|
else:
|
|
payload.update({f: data})
|
|
del request.POST[f]
|
|
payload.update(request.POST)
|
|
|
|
return payload
|
|
|
|
|
|
class PhasedSubcloudDeployController(object):
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.dcmanager_rpc_client = rpc_client.ManagerClient()
|
|
|
|
def _deploy_create(self, context: RequestContext, request: pecan.Request):
|
|
policy.authorize(phased_subcloud_deploy_policy.POLICY_ROOT % "create",
|
|
{}, restcomm.extract_credentials_for_policy())
|
|
psd_common.check_required_parameters(
|
|
request, SUBCLOUD_CREATE_REQUIRED_PARAMETERS)
|
|
|
|
payload = get_create_payload(request)
|
|
|
|
psd_common.subcloud_region_create(payload, context)
|
|
|
|
psd_common.pre_deploy_create(payload, context, request)
|
|
|
|
try:
|
|
# Add the subcloud details to the database
|
|
subcloud = psd_common.add_subcloud_to_database(context, payload)
|
|
|
|
# Ask dcmanager-manager to create the subcloud.
|
|
# It will do all the real work...
|
|
subcloud_dict = self.dcmanager_rpc_client.subcloud_deploy_create(
|
|
context, subcloud.id, payload)
|
|
|
|
return subcloud_dict
|
|
|
|
except RemoteError as e:
|
|
pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value)
|
|
except Exception:
|
|
LOG.exception("Unable to create subcloud %s" % payload.get('name'))
|
|
pecan.abort(httpclient.INTERNAL_SERVER_ERROR,
|
|
_('Unable to create subcloud'))
|
|
|
|
def _deploy_install(self, context: RequestContext,
|
|
request: pecan.Request, subcloud):
|
|
payload = psd_common.get_request_data(
|
|
request, subcloud, SUBCLOUD_INSTALL_GET_FILE_CONTENTS)
|
|
if not payload:
|
|
pecan.abort(400, _('Body required'))
|
|
|
|
if subcloud.deploy_status not in VALID_STATES_FOR_DEPLOY_INSTALL:
|
|
allowed_states_str = ', '.join(VALID_STATES_FOR_DEPLOY_INSTALL)
|
|
pecan.abort(400, _('Subcloud deploy status must be either: %s')
|
|
% allowed_states_str)
|
|
|
|
payload['software_version'] = payload.get('release', subcloud.software_version)
|
|
psd_common.populate_payload_with_pre_existing_data(
|
|
payload, subcloud, SUBCLOUD_INSTALL_GET_FILE_CONTENTS)
|
|
|
|
psd_common.pre_deploy_install(payload, subcloud)
|
|
|
|
try:
|
|
# Align the software version of the subcloud with install
|
|
# version. Update the deploy status as pre-install.
|
|
|
|
self.dcmanager_rpc_client.subcloud_deploy_install(
|
|
context, subcloud.id, payload)
|
|
subcloud_dict = db_api.subcloud_db_model_to_dict(subcloud)
|
|
subcloud_dict['deploy-status'] = consts.DEPLOY_STATE_PRE_INSTALL
|
|
subcloud_dict['software-version'] = payload['software_version']
|
|
|
|
return subcloud_dict
|
|
except RemoteError as e:
|
|
pecan.abort(422, e.value)
|
|
except Exception:
|
|
LOG.exception("Unable to install subcloud %s" % subcloud.name)
|
|
pecan.abort(500, _('Unable to install subcloud'))
|
|
|
|
def _deploy_bootstrap(self, context: RequestContext,
|
|
request: pecan.Request,
|
|
subcloud: models.Subcloud):
|
|
if subcloud.deploy_status not in VALID_STATES_FOR_DEPLOY_BOOTSTRAP:
|
|
valid_states_str = ', '.join(VALID_STATES_FOR_DEPLOY_BOOTSTRAP)
|
|
pecan.abort(400, _('Subcloud deploy status must be either: %s')
|
|
% valid_states_str)
|
|
|
|
has_bootstrap_values = consts.BOOTSTRAP_VALUES in request.POST
|
|
payload = {}
|
|
|
|
# Try to load the existing override values
|
|
override_file = psd_common.get_config_file_path(subcloud.name)
|
|
if os.path.exists(override_file):
|
|
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"
|
|
" not previously available at %s") % (override_file)
|
|
pecan.abort(400, msg)
|
|
|
|
request_data = psd_common.get_request_data(
|
|
request, subcloud, SUBCLOUD_BOOTSTRAP_GET_FILE_CONTENTS)
|
|
|
|
# Update the existing values with new ones from the request
|
|
payload.update(request_data)
|
|
|
|
psd_common.pre_deploy_bootstrap(context, payload, subcloud,
|
|
has_bootstrap_values)
|
|
|
|
try:
|
|
# Ask dcmanager-manager to bootstrap the subcloud.
|
|
self.dcmanager_rpc_client.subcloud_deploy_bootstrap(
|
|
context, subcloud.id, payload)
|
|
return db_api.subcloud_db_model_to_dict(subcloud)
|
|
|
|
except RemoteError as e:
|
|
pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value)
|
|
except Exception:
|
|
LOG.exception("Unable to bootstrap subcloud %s" %
|
|
payload.get('name'))
|
|
pecan.abort(httpclient.INTERNAL_SERVER_ERROR,
|
|
_('Unable to bootstrap subcloud'))
|
|
|
|
def _deploy_config(self, context: RequestContext,
|
|
request: pecan.Request, subcloud):
|
|
payload = psd_common.get_request_data(
|
|
request, subcloud, SUBCLOUD_CONFIG_GET_FILE_CONTENTS)
|
|
if not payload:
|
|
pecan.abort(400, _('Body required'))
|
|
|
|
if not (subcloud.deploy_status in VALID_STATES_FOR_DEPLOY_CONFIG or
|
|
prestage.is_deploy_status_prestage(subcloud.deploy_status)):
|
|
allowed_states_str = ', '.join(VALID_STATES_FOR_DEPLOY_CONFIG)
|
|
pecan.abort(400, _('Subcloud deploy status must be either '
|
|
'%s or prestage-...') % allowed_states_str)
|
|
|
|
psd_common.populate_payload_with_pre_existing_data(
|
|
payload, subcloud, SUBCLOUD_CONFIG_GET_FILE_CONTENTS)
|
|
|
|
psd_common.validate_sysadmin_password(payload)
|
|
|
|
try:
|
|
self.dcmanager_rpc_client.subcloud_deploy_config(
|
|
context, subcloud.id, payload)
|
|
subcloud_dict = db_api.subcloud_db_model_to_dict(subcloud)
|
|
subcloud_dict['deploy-status'] = consts.DEPLOY_STATE_PRE_CONFIG
|
|
return subcloud_dict
|
|
except RemoteError as e:
|
|
pecan.abort(422, e.value)
|
|
except Exception:
|
|
LOG.exception("Unable to configure subcloud %s" % subcloud.name)
|
|
pecan.abort(500, _('Unable to configure subcloud'))
|
|
|
|
def _deploy_complete(self, context: RequestContext, subcloud):
|
|
|
|
# The deployment should be able to be completed when the deploy state
|
|
# is consts.DEPLOY_STATE_BOOTSTRAPPED because the user could have
|
|
# configured the subcloud manually
|
|
if subcloud.deploy_status != consts.DEPLOY_STATE_BOOTSTRAPPED:
|
|
pecan.abort(400, _('Subcloud deploy can only be completed when'
|
|
' its deploy status is: %s')
|
|
% consts.DEPLOY_STATE_BOOTSTRAPPED)
|
|
|
|
try:
|
|
# Ask dcmanager-manager to complete the subcloud deployment
|
|
subcloud = self.dcmanager_rpc_client.subcloud_deploy_complete(
|
|
context, subcloud.id)
|
|
return subcloud
|
|
|
|
except RemoteError as e:
|
|
pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value)
|
|
except Exception:
|
|
LOG.exception("Unable to complete subcloud %s deployment" %
|
|
subcloud.name)
|
|
pecan.abort(httpclient.INTERNAL_SERVER_ERROR,
|
|
_('Unable to complete subcloud deployment'))
|
|
|
|
def _deploy_abort(self, context, subcloud):
|
|
|
|
if subcloud.deploy_status not in VALID_STATES_FOR_DEPLOY_ABORT:
|
|
allowed_states_str = ', '.join(VALID_STATES_FOR_DEPLOY_ABORT)
|
|
pecan.abort(400, _('Subcloud deploy status must be in one '
|
|
'of the following states: %s')
|
|
% allowed_states_str)
|
|
|
|
try:
|
|
self.dcmanager_rpc_client.subcloud_deploy_abort(
|
|
context, subcloud.id, subcloud.deploy_status)
|
|
subcloud_dict = db_api.subcloud_db_model_to_dict(subcloud)
|
|
subcloud_dict['deploy-status'] = \
|
|
utils.ABORT_UPDATE_STATUS[subcloud.deploy_status]
|
|
return subcloud_dict
|
|
except RemoteError as e:
|
|
pecan.abort(422, e.value)
|
|
except Exception:
|
|
LOG.exception("Unable to abort subcloud %s deployment" % subcloud.name)
|
|
pecan.abort(500, _('Unable to abort subcloud deploy'))
|
|
|
|
def _deploy_resume(self, context: RequestContext,
|
|
request: pecan.Request, subcloud):
|
|
|
|
if subcloud.deploy_status not in RESUMABLE_STATES:
|
|
allowed_states_str = ', '.join(RESUMABLE_STATES)
|
|
pecan.abort(400, _('Subcloud deploy status must be either: %s')
|
|
% allowed_states_str)
|
|
|
|
# Since both install and config are optional phases,
|
|
# it's necessary to check if they should be executed
|
|
config_file = psd_common.get_config_file_path(subcloud.name,
|
|
consts.DEPLOY_CONFIG)
|
|
has_original_install_values = subcloud.data_install
|
|
has_original_config_values = os.path.exists(config_file)
|
|
has_new_install_values = consts.INSTALL_VALUES in request.POST
|
|
has_new_config_values = consts.DEPLOY_CONFIG in request.POST
|
|
has_bootstrap_values = consts.BOOTSTRAP_VALUES in request.POST
|
|
has_config_values = has_original_config_values or has_new_config_values
|
|
has_install_values = has_original_install_values or has_new_install_values
|
|
|
|
deploy_states_to_run = RESUMABLE_STATES[subcloud.deploy_status]
|
|
if deploy_states_to_run == [CONFIG] and not has_config_values:
|
|
msg = _("Only deploy phase left is deploy config. "
|
|
"Required %s file was not provided and it was not "
|
|
"previously available.") % consts.DEPLOY_CONFIG
|
|
pecan.abort(400, msg)
|
|
|
|
# Since the subcloud can be installed manually and the config is optional,
|
|
# skip those phases if the user doesn't provide the install or config values
|
|
# and they are not available from previous executions.
|
|
files_for_resume = []
|
|
for state in deploy_states_to_run:
|
|
if state == INSTALL and not has_install_values:
|
|
deploy_states_to_run.remove(state)
|
|
elif state == CONFIG and not has_config_values:
|
|
deploy_states_to_run.remove(state)
|
|
else:
|
|
files_for_resume.extend(FILES_MAPPING[state])
|
|
|
|
payload = psd_common.get_request_data(request, subcloud, files_for_resume)
|
|
|
|
# Consider the incoming release parameter only if install is one
|
|
# of the pending deploy states
|
|
if INSTALL in deploy_states_to_run:
|
|
payload['software_version'] = payload.get('release', subcloud.software_version)
|
|
else:
|
|
payload['software_version'] = subcloud.software_version
|
|
|
|
# Need to remove bootstrap_values from the list of files to populate
|
|
# pre existing data so it does not overwrite newly loaded values
|
|
if has_bootstrap_values:
|
|
files_for_resume = [f for f in files_for_resume if f
|
|
not in FILES_MAPPING[BOOTSTRAP]]
|
|
psd_common.populate_payload_with_pre_existing_data(
|
|
payload, subcloud, files_for_resume)
|
|
|
|
psd_common.validate_sysadmin_password(payload)
|
|
for state in deploy_states_to_run:
|
|
if state == INSTALL:
|
|
psd_common.pre_deploy_install(payload, validate_password=False)
|
|
elif state == BOOTSTRAP:
|
|
psd_common.pre_deploy_bootstrap(context, payload, subcloud,
|
|
has_bootstrap_values,
|
|
validate_password=False)
|
|
elif state == CONFIG:
|
|
# Currently the only pre_deploy_config step is validate_sysadmin_password
|
|
# which can't be executed more than once
|
|
pass
|
|
|
|
try:
|
|
self.dcmanager_rpc_client.subcloud_deploy_resume(
|
|
context, subcloud.id, subcloud.name, payload, deploy_states_to_run)
|
|
subcloud_dict = db_api.subcloud_db_model_to_dict(subcloud)
|
|
next_deploy_phase = RESUMABLE_STATES[subcloud.deploy_status][0]
|
|
next_deploy_state = RESUME_PREP_UPDATE_STATUS[next_deploy_phase]
|
|
subcloud_dict['deploy-status'] = next_deploy_state
|
|
subcloud_dict['software-version'] = payload['software_version']
|
|
return subcloud_dict
|
|
except RemoteError as e:
|
|
pecan.abort(422, e.value)
|
|
except Exception:
|
|
LOG.exception("Unable to resume subcloud %s deployment" % subcloud.name)
|
|
pecan.abort(500, _('Unable to resume subcloud deployment'))
|
|
|
|
@pecan.expose(generic=True, template='json')
|
|
def index(self):
|
|
# Route the request to specific methods with parameters
|
|
pass
|
|
|
|
@utils.synchronized(LOCK_NAME)
|
|
@index.when(method='POST', template='json')
|
|
def post(self):
|
|
context = restcomm.extract_context_from_environ()
|
|
return self._deploy_create(context, pecan.request)
|
|
|
|
@utils.synchronized(LOCK_NAME)
|
|
@index.when(method='PATCH', template='json')
|
|
def patch(self, subcloud_ref=None, verb=None):
|
|
"""Modify the subcloud deployment.
|
|
|
|
:param subcloud_ref: ID or name of subcloud to update
|
|
|
|
:param verb: Specifies the patch action to be taken
|
|
or subcloud operation
|
|
"""
|
|
|
|
policy.authorize(phased_subcloud_deploy_policy.POLICY_ROOT % "modify", {},
|
|
restcomm.extract_credentials_for_policy())
|
|
context = restcomm.extract_context_from_environ()
|
|
|
|
if not subcloud_ref:
|
|
pecan.abort(400, _('Subcloud ID required'))
|
|
|
|
try:
|
|
if subcloud_ref.isdigit():
|
|
subcloud = db_api.subcloud_get(context, subcloud_ref)
|
|
else:
|
|
subcloud = db_api.subcloud_get_by_name(context, subcloud_ref)
|
|
except (exceptions.SubcloudNotFound, exceptions.SubcloudNameNotFound):
|
|
pecan.abort(404, _('Subcloud not found'))
|
|
|
|
if verb == ABORT:
|
|
subcloud = self._deploy_abort(context, subcloud)
|
|
elif verb == RESUME:
|
|
subcloud = self._deploy_resume(context, pecan.request, subcloud)
|
|
elif verb == INSTALL:
|
|
subcloud = self._deploy_install(context, pecan.request, subcloud)
|
|
elif verb == BOOTSTRAP:
|
|
subcloud = self._deploy_bootstrap(context, pecan.request, subcloud)
|
|
elif verb == CONFIG:
|
|
subcloud = self._deploy_config(context, pecan.request, subcloud)
|
|
elif verb == COMPLETE:
|
|
subcloud = self._deploy_complete(context, subcloud)
|
|
else:
|
|
pecan.abort(400, _('Invalid request'))
|
|
|
|
return subcloud
|