Add the subcloud deploy config option to dcmanager
This commit adds the command "subcloud deploy config" to dcmanager. It provides similar options as dcmanager subcloud reconfig. However, the --deploy-config file is optional if it has been provided previously via subcloud deploy config or subcloud deploy create command. Test Plan: Success cases: - PASS: Bootstrap a subcloud then issue "dcmanager subcloud deploy config" command with --deploy-config option to apply initial config. Verify that the subcloud is successfully configured. - PASS: Create a deploy config using dcmanager subcloud deploy create with --deploy-config option. Install and bootstrap the subcloud then config the subcloud using dcmanger subcloud deploy config with --deploy-config option. Verify that the subcloud is successfully configured with config options provided last. - PASS: Bootstrap a subcloud then issue "dcmanager subcloud deploy config" command with --deploy-config option to apply an erroneous config. Verify that the subcloud fails to be configured. Repeat the command this time with a good config file and verify that the subcloud is successfully configured. - PASS: Apply config passing deploy_config file for a subcloud running a previous release (21.12) and verify that the subcloud was successfully configured. - PASS: Create a subcloud deploy with --deploy-config option, install and bootstrap the subcloud then issue "dcmanager subcloud deploy config" command without --deploy-config option. Verify that the subcloud is successfully configured. - PASS: Repeat previous tests but directly call the API (using CURL) instead of using the CLI. Failure cases: - PASS: Verify that it's not possible to run the config if deploy state is not 'complete', 'pre-config-failed', 'config-failed', 'deploy-failed', 'bootstrapped' or in a prestaging state. ('deploy-failed' will be removed once 'subcloud reconfig' is deprecated) - PASS: Verify that it's not possible to run "dcmanager subcloud deploy config" command without providing a deploy config file if this has never been provided before. - PASS: Verify that it's not possible to run the config without previously uploading deploy files. - PASS: Verify that it's not possible to configure without passing the 'sysadmin-password' parameter (using CURL, since the CLI will prompt for the password if it's omited) - PASS: Call the API directly, passing sysadmin-password as plain text as opposed to b64encoded and verify that the response contains the correct error code and message. Story: 2010756 Task: 48022 Signed-off-by: Victor Romano <victor.gluzromano@windriver.com> Change-Id: I65e1cbea1879d49066c2add69cabd04e64216b8f
This commit is contained in:
parent
32f6fc5805
commit
6b7b012992
|
@ -1673,6 +1673,7 @@ Subcloud Deploy
|
|||
These APIs allow for the display and upload of the deployment manager common
|
||||
files which include deploy playbook, deploy overrides, deploy helm charts, and prestage images list.
|
||||
|
||||
|
||||
**************************
|
||||
Show Subcloud Deploy Files
|
||||
**************************
|
||||
|
@ -1910,4 +1911,76 @@ Response Example
|
|||
----------------
|
||||
|
||||
.. literalinclude:: samples/phased-subcloud-deploy/phased-subcloud-deploy-post-response.json
|
||||
:language: json
|
||||
|
||||
**********************************
|
||||
Configures a subcloud
|
||||
**********************************
|
||||
|
||||
.. rest_method:: PATCH /v1.0/phased-subcloud-deploy/{subcloud}/configure
|
||||
|
||||
The attributes of a subcloud which are modifiable:
|
||||
|
||||
- subcloud configuration (which is provided through deploy_config file)
|
||||
|
||||
**Normal response codes**
|
||||
|
||||
200
|
||||
|
||||
**Error response codes**
|
||||
|
||||
badRequest (400), unauthorized (401), forbidden (403), badMethod (405),
|
||||
HTTPUnprocessableEntity (422), internalServerError (500),
|
||||
serviceUnavailable (503)
|
||||
|
||||
**Request parameters**
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- subcloud: subcloud_uri
|
||||
- deploy_config: deploy_config
|
||||
- sysadmin_password: sysadmin_password
|
||||
|
||||
Accepts Content-Type multipart/form-data
|
||||
|
||||
Request Example
|
||||
----------------
|
||||
|
||||
.. literalinclude:: samples/phased-subcloud-deploy/phased-subcloud-deploy-patch-configure-request.json
|
||||
:language: json
|
||||
|
||||
**Response parameters**
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- id: subcloud_id
|
||||
- group_id: group_id
|
||||
- name: subcloud_name
|
||||
- description: subcloud_description
|
||||
- location: subcloud_location
|
||||
- software-version: software_version
|
||||
- availability-status: availability_status
|
||||
- error-description: error_description
|
||||
- deploy-status: deploy_status
|
||||
- backup-status: backup_status
|
||||
- backup-datetime: backup_datetime
|
||||
- openstack-installed: openstack_installed
|
||||
- management-state: management_state
|
||||
- systemcontroller-gateway-ip: systemcontroller_gateway_ip
|
||||
- management-start-ip: management_start_ip
|
||||
- management-end-ip: management_end_ip
|
||||
- management-subnet: management_subnet
|
||||
- management-gateway-ip: management_gateway_ip
|
||||
- created-at: created_at
|
||||
- updated-at: updated_at
|
||||
- data_install: data_install
|
||||
- data_upgrade: data_upgrade
|
||||
- endpoint_sync_status: endpoint_sync_status
|
||||
- sync_status: sync_status
|
||||
- endpoint_type: sync_status_type
|
||||
|
||||
Response Example
|
||||
----------------
|
||||
|
||||
.. literalinclude:: samples/phased-subcloud-deploy/phased-subcloud-deploy-patch-configure-response.json
|
||||
:language: json
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"deploy_config": "content of deploy_config file",
|
||||
"subcloud": "subcloud1",
|
||||
"sysadmin_password": "XXXXXXX"
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"id": 1,
|
||||
"name": "subcloud1",
|
||||
"created-at": "2023-01-02T03:04:05.678987",
|
||||
"updated-at": "2023-04-08T15:16:23.424851",
|
||||
"availability-status": "online",
|
||||
"data_install": null,
|
||||
"data_upgrade": null,
|
||||
"deploy-status": "complete",
|
||||
"backup-status": "complete",
|
||||
"backup-datetime": "2023-05-02 11:23:58.132134",
|
||||
"description": "Ottawa Site",
|
||||
"group_id": 1,
|
||||
"location": "YOW",
|
||||
"management-end-ip": "192.168.101.50",
|
||||
"management-gateway-ip": "192.168.101.1",
|
||||
"management-start-ip": "192.168.101.2",
|
||||
"management-state": "unmanaged",
|
||||
"management-subnet": "192.168.101.0/24",
|
||||
"openstack-installed": false,
|
||||
"software-version": "21.12",
|
||||
"systemcontroller-gateway-ip": "192.168.204.101"
|
||||
}
|
|
@ -22,6 +22,7 @@ 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
|
||||
|
@ -46,6 +47,10 @@ SUBCLOUD_BOOTSTRAP_GET_FILE_CONTENTS = (
|
|||
consts.BOOTSTRAP_VALUES,
|
||||
)
|
||||
|
||||
SUBCLOUD_CONFIG_GET_FILE_CONTENTS = (
|
||||
consts.DEPLOY_CONFIG,
|
||||
)
|
||||
|
||||
VALID_STATES_FOR_DEPLOY_BOOTSTRAP = [
|
||||
consts.DEPLOY_STATE_INSTALLED,
|
||||
consts.DEPLOY_STATE_BOOTSTRAP_FAILED,
|
||||
|
@ -56,6 +61,16 @@ VALID_STATES_FOR_DEPLOY_BOOTSTRAP = [
|
|||
consts.DEPLOY_STATE_CREATED
|
||||
]
|
||||
|
||||
# TODO(vgluzrom): remove deploy_failed once 'subcloud reconfig'
|
||||
# has been deprecated
|
||||
VALID_STATES_FOR_DEPLOY_CONFIG = (
|
||||
consts.DEPLOY_STATE_DONE,
|
||||
consts.DEPLOY_STATE_PRE_CONFIG_FAILED,
|
||||
consts.DEPLOY_STATE_CONFIG_FAILED,
|
||||
consts.DEPLOY_STATE_DEPLOY_FAILED,
|
||||
consts.DEPLOY_STATE_BOOTSTRAPPED
|
||||
)
|
||||
|
||||
|
||||
def get_create_payload(request: pecan.Request) -> dict:
|
||||
payload = dict()
|
||||
|
@ -199,6 +214,34 @@ class PhasedSubcloudDeployController(object):
|
|||
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:
|
||||
subcloud = self.dcmanager_rpc_client.subcloud_deploy_config(
|
||||
context, subcloud.id, payload)
|
||||
return subcloud
|
||||
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'))
|
||||
|
||||
@pecan.expose(generic=True, template='json')
|
||||
def index(self):
|
||||
# Route the request to specific methods with parameters
|
||||
|
@ -238,6 +281,8 @@ class PhasedSubcloudDeployController(object):
|
|||
|
||||
if verb == 'bootstrap':
|
||||
subcloud = self._deploy_bootstrap(context, pecan.request, subcloud)
|
||||
elif verb == 'configure':
|
||||
subcloud = self._deploy_config(context, pecan.request, subcloud)
|
||||
else:
|
||||
pecan.abort(400, _('Invalid request'))
|
||||
|
||||
|
|
|
@ -30,6 +30,10 @@ phased_subcloud_deploy_rules = [
|
|||
{
|
||||
'method': 'PATCH',
|
||||
'path': '/v1.0/phased-subcloud-deploy/{subcloud}/bootstrap'
|
||||
},
|
||||
{
|
||||
'method': 'PATCH',
|
||||
'path': '/v1.0/phased-subcloud-deploy/{subcloud}/configure'
|
||||
}
|
||||
]
|
||||
)
|
||||
|
|
|
@ -179,6 +179,10 @@ DEPLOY_STATE_BOOTSTRAPPING = 'bootstrapping'
|
|||
DEPLOY_STATE_BOOTSTRAP_FAILED = 'bootstrap-failed'
|
||||
DEPLOY_STATE_BOOTSTRAP_ABORTED = 'bootstrap-aborted'
|
||||
DEPLOY_STATE_BOOTSTRAPPED = 'bootstrap-complete'
|
||||
DEPLOY_STATE_PRE_CONFIG = 'pre-config'
|
||||
DEPLOY_STATE_PRE_CONFIG_FAILED = 'pre-config-failed'
|
||||
DEPLOY_STATE_CONFIGURING = 'configuring'
|
||||
DEPLOY_STATE_CONFIG_FAILED = 'config-failed'
|
||||
DEPLOY_STATE_DEPLOYING = 'deploying'
|
||||
DEPLOY_STATE_DEPLOY_FAILED = 'deploy-failed'
|
||||
DEPLOY_STATE_MIGRATING_DATA = 'migrating-data'
|
||||
|
|
|
@ -692,21 +692,15 @@ def upload_deploy_config_file(request, payload):
|
|||
|
||||
|
||||
def get_config_file_path(subcloud_name, config_file_type=None):
|
||||
basepath = consts.ANSIBLE_OVERRIDES_PATH
|
||||
if config_file_type == consts.DEPLOY_CONFIG:
|
||||
file_path = os.path.join(
|
||||
consts.ANSIBLE_OVERRIDES_PATH,
|
||||
subcloud_name + '_' + config_file_type + '.yml'
|
||||
)
|
||||
elif config_file_type == INSTALL_VALUES:
|
||||
file_path = os.path.join(
|
||||
consts.ANSIBLE_OVERRIDES_PATH + '/' + subcloud_name,
|
||||
config_file_type + '.yml'
|
||||
)
|
||||
filename = f"{subcloud_name}_{config_file_type}.yml"
|
||||
elif config_file_type == consts.INSTALL_VALUES:
|
||||
basepath = os.path.join(basepath, subcloud_name)
|
||||
filename = f'{config_file_type}.yml'
|
||||
else:
|
||||
file_path = os.path.join(
|
||||
consts.ANSIBLE_OVERRIDES_PATH,
|
||||
subcloud_name + '.yml'
|
||||
)
|
||||
filename = f"{subcloud_name}.yml"
|
||||
file_path = os.path.join(basepath, filename)
|
||||
return file_path
|
||||
|
||||
|
||||
|
@ -721,18 +715,24 @@ def upload_config_file(file_item, config_file, config_type):
|
|||
|
||||
|
||||
def get_common_deploy_files(payload, software_version):
|
||||
missing_deploy_files = []
|
||||
for f in consts.DEPLOY_COMMON_FILE_OPTIONS:
|
||||
# Skip the prestage_images option as it is not relevant in this
|
||||
# context
|
||||
# Skip the prestage_images option as it is
|
||||
# not relevant in this context
|
||||
if f == consts.DEPLOY_PRESTAGE:
|
||||
continue
|
||||
filename = None
|
||||
dir_path = os.path.join(dccommon_consts.DEPLOY_DIR, software_version)
|
||||
if os.path.isdir(dir_path):
|
||||
filename = utils.get_filename_by_prefix(dir_path, f + '_')
|
||||
if filename is None:
|
||||
pecan.abort(400, _("Missing required deploy file for %s") % f)
|
||||
payload.update({f: os.path.join(dir_path, filename)})
|
||||
if not filename:
|
||||
missing_deploy_files.append(f)
|
||||
else:
|
||||
payload.update({f: os.path.join(dir_path, filename)})
|
||||
if missing_deploy_files:
|
||||
missing_deploy_files_str = ', '.join(missing_deploy_files)
|
||||
msg = _("Missing required deploy files: %s" % missing_deploy_files_str)
|
||||
pecan.abort(400, msg)
|
||||
|
||||
|
||||
def validate_subcloud_name_availability(context, subcloud_name):
|
||||
|
@ -794,7 +794,7 @@ def get_request_data(request: pecan.Request,
|
|||
file_item = request.POST[f]
|
||||
file_item.file.seek(0, os.SEEK_SET)
|
||||
contents = file_item.file.read()
|
||||
if subcloud.name and f == consts.DEPLOY_CONFIG:
|
||||
if f == consts.DEPLOY_CONFIG:
|
||||
fn = get_config_file_path(subcloud.name, f)
|
||||
upload_config_file(contents, fn, f)
|
||||
payload.update({f: fn})
|
||||
|
|
|
@ -206,6 +206,14 @@ class DCManagerService(service.Service):
|
|||
subcloud_id,
|
||||
payload)
|
||||
|
||||
@request_context
|
||||
def subcloud_deploy_config(self, context, subcloud_id, payload):
|
||||
# Configures a subcloud
|
||||
LOG.info("Handling subcloud_deploy_config request for: %s" % subcloud_id)
|
||||
return self.subcloud_manager.subcloud_deploy_config(context,
|
||||
subcloud_id,
|
||||
payload)
|
||||
|
||||
def _stop_rpc_server(self):
|
||||
# Stop RPC connection to prevent new requests
|
||||
LOG.debug(_("Attempting to stop RPC service..."))
|
||||
|
|
|
@ -108,6 +108,8 @@ TRANSITORY_STATES = {
|
|||
consts.DEPLOY_STATE_PRE_INSTALL: consts.DEPLOY_STATE_PRE_INSTALL_FAILED,
|
||||
consts.DEPLOY_STATE_INSTALLING: consts.DEPLOY_STATE_INSTALL_FAILED,
|
||||
consts.DEPLOY_STATE_BOOTSTRAPPING: consts.DEPLOY_STATE_BOOTSTRAP_FAILED,
|
||||
consts.DEPLOY_STATE_PRE_CONFIG: consts.DEPLOY_STATE_PRE_CONFIG_FAILED,
|
||||
consts.DEPLOY_STATE_CONFIGURING: consts.DEPLOY_STATE_CONFIG_FAILED,
|
||||
consts.DEPLOY_STATE_DEPLOYING: consts.DEPLOY_STATE_DEPLOY_FAILED,
|
||||
consts.DEPLOY_STATE_MIGRATING_DATA: consts.DEPLOY_STATE_DATA_MIGRATION_FAILED,
|
||||
consts.DEPLOY_STATE_PRE_RESTORE: consts.DEPLOY_STATE_RESTORE_PREP_FAILED,
|
||||
|
@ -260,6 +262,7 @@ class SubcloudManager(manager.Manager):
|
|||
software_version if software_version else SW_VERSION]
|
||||
return apply_command
|
||||
|
||||
# TODO(vgluzrom): rename compose_deploy_command to compose_config_command
|
||||
def compose_deploy_command(self, subcloud_name, ansible_subcloud_inventory_file, payload):
|
||||
deploy_command = [
|
||||
"ansible-playbook", payload[consts.DEPLOY_PLAYBOOK],
|
||||
|
@ -974,6 +977,41 @@ class SubcloudManager(manager.Manager):
|
|||
context, subcloud_id,
|
||||
deploy_status=consts.DEPLOY_STATE_PRE_BOOTSTRAP_FAILED)
|
||||
|
||||
def subcloud_deploy_config(self, context, subcloud_id, payload: dict) -> dict:
|
||||
"""Configure subcloud
|
||||
|
||||
:param context: request context object
|
||||
:param payload: subcloud configuration
|
||||
"""
|
||||
LOG.info("Configuring subcloud %s." % subcloud_id)
|
||||
|
||||
subcloud = db_api.subcloud_update(
|
||||
context, subcloud_id,
|
||||
deploy_status=consts.DEPLOY_STATE_PRE_CONFIG)
|
||||
try:
|
||||
# Ansible inventory filename for the specified subcloud
|
||||
ansible_subcloud_inventory_file = self._get_ansible_filename(
|
||||
subcloud.name, INVENTORY_FILE_POSTFIX)
|
||||
|
||||
self._prepare_for_deployment(payload, subcloud.name)
|
||||
deploy_command = self.compose_deploy_command(
|
||||
subcloud.name,
|
||||
ansible_subcloud_inventory_file,
|
||||
payload)
|
||||
|
||||
del payload['sysadmin_password']
|
||||
apply_thread = threading.Thread(
|
||||
target=self.run_deploy_commands,
|
||||
args=(subcloud, payload, context),
|
||||
kwargs={'deploy_command': deploy_command})
|
||||
apply_thread.start()
|
||||
return db_api.subcloud_db_model_to_dict(subcloud)
|
||||
except Exception:
|
||||
LOG.exception("Failed to configure %s" % subcloud.name)
|
||||
db_api.subcloud_update(
|
||||
context, subcloud_id,
|
||||
deploy_status=consts.DEPLOY_STATE_PRE_CONFIG_FAILED)
|
||||
|
||||
def _subcloud_operation_notice(
|
||||
self, operation, restore_subclouds, failed_subclouds,
|
||||
invalid_subclouds):
|
||||
|
@ -1623,6 +1661,9 @@ class SubcloudManager(manager.Manager):
|
|||
if apply_command:
|
||||
self._run_subcloud_bootstrap(context, subcloud,
|
||||
apply_command, log_file)
|
||||
if deploy_command:
|
||||
self._run_subcloud_config(subcloud, context,
|
||||
deploy_command, log_file)
|
||||
except Exception as ex:
|
||||
LOG.exception("run_deploy failed")
|
||||
raise ex
|
||||
|
@ -1701,6 +1742,32 @@ class SubcloudManager(manager.Manager):
|
|||
|
||||
LOG.info("Successfully bootstrapped %s" % subcloud.name)
|
||||
|
||||
def _run_subcloud_config(self, subcloud, context,
|
||||
deploy_command, log_file):
|
||||
# Run the custom deploy playbook
|
||||
LOG.info("Starting deploy of %s" % subcloud.name)
|
||||
db_api.subcloud_update(
|
||||
context, subcloud.id,
|
||||
deploy_status=consts.DEPLOY_STATE_CONFIGURING,
|
||||
error_description=consts.ERROR_DESC_EMPTY)
|
||||
|
||||
try:
|
||||
run_playbook(log_file, deploy_command)
|
||||
except PlaybookExecutionFailed:
|
||||
msg = utils.find_ansible_error_msg(
|
||||
subcloud.name, log_file, consts.DEPLOY_STATE_CONFIGURING)
|
||||
LOG.error(msg)
|
||||
db_api.subcloud_update(
|
||||
context, subcloud.id,
|
||||
deploy_status=consts.DEPLOY_STATE_CONFIG_FAILED,
|
||||
error_description=msg[0:consts.ERROR_DESCRIPTION_LENGTH])
|
||||
return
|
||||
LOG.info("Successfully deployed %s" % subcloud.name)
|
||||
db_api.subcloud_update(
|
||||
context, subcloud.id,
|
||||
deploy_status=consts.DEPLOY_STATE_DONE,
|
||||
error_description=consts.ERROR_DESC_EMPTY)
|
||||
|
||||
def _create_addn_hosts_dc(self, context):
|
||||
"""Generate the addn_hosts_dc file for hostname/ip translation"""
|
||||
|
||||
|
|
|
@ -197,6 +197,11 @@ class ManagerClient(RPCClient):
|
|||
subcloud_id=subcloud_id,
|
||||
payload=payload))
|
||||
|
||||
def subcloud_deploy_config(self, ctxt, subcloud_id, payload):
|
||||
return self.call(ctxt, self.make_msg('subcloud_deploy_config',
|
||||
subcloud_id=subcloud_id,
|
||||
payload=payload))
|
||||
|
||||
|
||||
class DCManagerNotifications(RPCClient):
|
||||
"""DC Manager Notification interface to broadcast subcloud state changed
|
||||
|
|
|
@ -4,8 +4,10 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
import base64
|
||||
import copy
|
||||
import json
|
||||
|
||||
import mock
|
||||
from os import path as os_path
|
||||
import six
|
||||
|
@ -41,7 +43,7 @@ class FakeRPCClient(object):
|
|||
# Apply the TestSubcloudPost parameter validation tests to the subcloud deploy
|
||||
# add endpoint as it uses the same parameter validation functions
|
||||
class TestSubcloudDeployCreate(TestSubcloudPost):
|
||||
API_PREFIX = '/v1.0/phased-subcloud-deploy'
|
||||
API_PREFIX = FAKE_URL
|
||||
RESULT_KEY = 'phased-subcloud-deploy'
|
||||
|
||||
def setUp(self):
|
||||
|
@ -229,3 +231,77 @@ class TestSubcloudDeployBootstrap(testroot.DCManagerApiTest):
|
|||
upload_files=[("bootstrap_values",
|
||||
"bootstrap_fake_filename",
|
||||
fake_content)])
|
||||
|
||||
|
||||
class TestSubcloudDeployConfig(testroot.DCManagerApiTest):
|
||||
def setUp(self):
|
||||
super(TestSubcloudDeployConfig, self).setUp()
|
||||
self.ctx = utils.dummy_context()
|
||||
|
||||
p = mock.patch.object(rpc_client, 'ManagerClient')
|
||||
self.mock_rpc_client = p.start()
|
||||
self.addCleanup(p.stop)
|
||||
|
||||
p = mock.patch.object(psd_common, 'populate_payload_with_pre_existing_data')
|
||||
self.mock_populate_payload = p.start()
|
||||
self.addCleanup(p.stop)
|
||||
|
||||
p = mock.patch.object(psd_common, 'get_request_data')
|
||||
self.mock_get_request_data = p.start()
|
||||
self.addCleanup(p.stop)
|
||||
|
||||
def test_configure_subcloud(self):
|
||||
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
|
||||
fake_password = (base64.b64encode('testpass'.encode("utf-8"))).decode('ascii')
|
||||
data = {'sysadmin_password': fake_password}
|
||||
|
||||
self.mock_rpc_client().subcloud_deploy_config.return_value = True
|
||||
self.mock_get_request_data.return_value = data
|
||||
|
||||
response = self.app.patch_json(FAKE_URL + '/' + str(subcloud.id) +
|
||||
'/configure',
|
||||
headers=FAKE_HEADERS,
|
||||
params=data)
|
||||
self.mock_rpc_client().subcloud_deploy_config.assert_called_once_with(
|
||||
mock.ANY,
|
||||
subcloud.id,
|
||||
mock.ANY)
|
||||
self.assertEqual(response.status_int, 200)
|
||||
|
||||
def test_configure_subcloud_no_body(self):
|
||||
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
|
||||
# Pass an empty request body
|
||||
data = {}
|
||||
self.mock_rpc_client().subcloud_deploy_config.return_value = True
|
||||
self.mock_get_request_data.return_value = data
|
||||
|
||||
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
|
||||
self.app.patch_json, FAKE_URL + '/' +
|
||||
str(subcloud.id) + '/configure',
|
||||
headers=FAKE_HEADERS, params=data)
|
||||
|
||||
def test_configure_subcloud_bad_password(self):
|
||||
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
|
||||
# Pass a sysadmin_password which is not base64 encoded
|
||||
data = {'sysadmin_password': 'not_base64'}
|
||||
self.mock_rpc_client().subcloud_deploy_config.return_value = True
|
||||
self.mock_get_request_data.return_value = data
|
||||
|
||||
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
|
||||
self.app.patch_json, FAKE_URL + '/' +
|
||||
str(subcloud.id) + '/configure',
|
||||
headers=FAKE_HEADERS, params=data)
|
||||
|
||||
def test_configure_invalid_deploy_status(self):
|
||||
subcloud = fake_subcloud.create_fake_subcloud(
|
||||
self.ctx,
|
||||
deploy_status=consts.DEPLOY_STATE_BOOTSTRAP_FAILED)
|
||||
fake_password = base64.b64encode('testpass'.encode("utf-8")).decode("utf-8")
|
||||
data = {'sysadmin_password': fake_password}
|
||||
self.mock_rpc_client().subcloud_deploy_config.return_value = True
|
||||
self.mock_get_request_data.return_value = data
|
||||
|
||||
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
|
||||
self.app.patch_json, FAKE_URL + '/' +
|
||||
str(subcloud.id) + '/configure',
|
||||
headers=FAKE_HEADERS, params=data)
|
||||
|
|
|
@ -21,6 +21,7 @@ import webtest
|
|||
|
||||
from dcmanager.api.controllers.v1 import subcloud_deploy
|
||||
from dcmanager.common import consts
|
||||
from dcmanager.common import phased_subcloud_deploy as psd_common
|
||||
from dcmanager.common import utils as dutils
|
||||
from dcmanager.tests.unit.api import test_root_controller as testroot
|
||||
from dcmanager.tests import utils
|
||||
|
@ -245,3 +246,16 @@ class TestSubcloudDeploy(testroot.DCManagerApiTest):
|
|||
response.json['subcloud_deploy'][consts.DEPLOY_CHART])
|
||||
self.assertEqual(None,
|
||||
response.json['subcloud_deploy'][consts.DEPLOY_PRESTAGE])
|
||||
|
||||
def test_get_config_file_path(self):
|
||||
bootstrap_file = psd_common.get_config_file_path("subcloud1")
|
||||
install_values = psd_common.get_config_file_path("subcloud1",
|
||||
consts.INSTALL_VALUES)
|
||||
deploy_config = psd_common.get_config_file_path("subcloud1",
|
||||
consts.DEPLOY_CONFIG)
|
||||
self.assertEqual(bootstrap_file,
|
||||
"/var/opt/dc/ansible/subcloud1.yml")
|
||||
self.assertEqual(install_values,
|
||||
"/var/opt/dc/ansible/subcloud1/install_values.yml")
|
||||
self.assertEqual(deploy_config,
|
||||
"/var/opt/dc/ansible/subcloud1_deploy_config.yml")
|
||||
|
|
|
@ -557,6 +557,29 @@ class TestSubcloudManager(base.DCManagerTestCase):
|
|||
self.assertEqual(consts.DEPLOY_STATE_PRE_BOOTSTRAP_FAILED,
|
||||
updated_subcloud.deploy_status)
|
||||
|
||||
@mock.patch.object(subcloud_manager.SubcloudManager,
|
||||
'_prepare_for_deployment')
|
||||
@mock.patch.object(threading.Thread,
|
||||
'start')
|
||||
def test_configure_subcloud(self, mock_thread_start,
|
||||
mock_prepare_for_deployment):
|
||||
subcloud = self.create_subcloud_static(
|
||||
self.ctx,
|
||||
name='subcloud1',
|
||||
deploy_status=consts.DEPLOY_STATE_PRE_CONFIG)
|
||||
|
||||
fake_payload = {"sysadmin_password": "testpass",
|
||||
"deploy_playbook": "test_playbook.yaml",
|
||||
"deploy_overrides": "test_overrides.yaml",
|
||||
"deploy_chart": "test_chart.yaml",
|
||||
"deploy_config": "subcloud1.yaml"}
|
||||
sm = subcloud_manager.SubcloudManager()
|
||||
sm.subcloud_deploy_config(self.ctx,
|
||||
subcloud.id,
|
||||
payload=fake_payload)
|
||||
mock_thread_start.assert_called_once()
|
||||
mock_prepare_for_deployment.assert_called_once()
|
||||
|
||||
@mock.patch.object(subcloud_manager.SubcloudManager,
|
||||
'compose_apply_command')
|
||||
@mock.patch.object(subcloud_manager.SubcloudManager,
|
||||
|
@ -2157,12 +2180,14 @@ class TestSubcloudManager(base.DCManagerTestCase):
|
|||
self.assertTrue('Subcloud does not exist'
|
||||
in str(e))
|
||||
|
||||
@mock.patch.object(os_path, 'isdir')
|
||||
@mock.patch.object(os_path, 'exists')
|
||||
@mock.patch.object(cutils, 'get_filename_by_prefix')
|
||||
@mock.patch.object(prestage, '_run_ansible')
|
||||
def test_prestage_remote_pass(self, mock_run_ansible,
|
||||
mock_get_filename_by_prefix,
|
||||
mock_file_exists):
|
||||
mock_file_exists,
|
||||
mock_isdir):
|
||||
|
||||
values = copy.copy(FAKE_PRESTAGE_PAYLOAD)
|
||||
subcloud = self.create_subcloud_static(self.ctx,
|
||||
|
@ -2174,6 +2199,7 @@ class TestSubcloudManager(base.DCManagerTestCase):
|
|||
mock_run_ansible.return_value = None
|
||||
mock_get_filename_by_prefix.return_value = 'prestage_images_list.txt'
|
||||
mock_file_exists.return_value = True
|
||||
mock_isdir.return_value = True
|
||||
|
||||
# Verify that subcloud has the correct deploy status
|
||||
updated_subcloud = db_api.subcloud_get_by_name(self.ctx, subcloud.name)
|
||||
|
|
Loading…
Reference in New Issue