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:
Victor Romano 2023-05-16 11:10:08 -03:00
parent 32f6fc5805
commit 6b7b012992
13 changed files with 371 additions and 21 deletions

View File

@ -1673,6 +1673,7 @@ Subcloud Deploy
These APIs allow for the display and upload of the deployment manager common 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. files which include deploy playbook, deploy overrides, deploy helm charts, and prestage images list.
************************** **************************
Show Subcloud Deploy Files Show Subcloud Deploy Files
************************** **************************
@ -1910,4 +1911,76 @@ Response Example
---------------- ----------------
.. literalinclude:: samples/phased-subcloud-deploy/phased-subcloud-deploy-post-response.json .. 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 :language: json

View File

@ -0,0 +1,5 @@
{
"deploy_config": "content of deploy_config file",
"subcloud": "subcloud1",
"sysadmin_password": "XXXXXXX"
}

View File

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

View File

@ -22,6 +22,7 @@ from dcmanager.common.context import RequestContext
from dcmanager.common import exceptions from dcmanager.common import exceptions
from dcmanager.common.i18n import _ from dcmanager.common.i18n import _
from dcmanager.common import phased_subcloud_deploy as psd_common from dcmanager.common import phased_subcloud_deploy as psd_common
from dcmanager.common import prestage
from dcmanager.common import utils from dcmanager.common import utils
from dcmanager.db import api as db_api from dcmanager.db import api as db_api
from dcmanager.db.sqlalchemy import models from dcmanager.db.sqlalchemy import models
@ -46,6 +47,10 @@ SUBCLOUD_BOOTSTRAP_GET_FILE_CONTENTS = (
consts.BOOTSTRAP_VALUES, consts.BOOTSTRAP_VALUES,
) )
SUBCLOUD_CONFIG_GET_FILE_CONTENTS = (
consts.DEPLOY_CONFIG,
)
VALID_STATES_FOR_DEPLOY_BOOTSTRAP = [ VALID_STATES_FOR_DEPLOY_BOOTSTRAP = [
consts.DEPLOY_STATE_INSTALLED, consts.DEPLOY_STATE_INSTALLED,
consts.DEPLOY_STATE_BOOTSTRAP_FAILED, consts.DEPLOY_STATE_BOOTSTRAP_FAILED,
@ -56,6 +61,16 @@ VALID_STATES_FOR_DEPLOY_BOOTSTRAP = [
consts.DEPLOY_STATE_CREATED 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: def get_create_payload(request: pecan.Request) -> dict:
payload = dict() payload = dict()
@ -199,6 +214,34 @@ class PhasedSubcloudDeployController(object):
pecan.abort(httpclient.INTERNAL_SERVER_ERROR, pecan.abort(httpclient.INTERNAL_SERVER_ERROR,
_('Unable to bootstrap subcloud')) _('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') @pecan.expose(generic=True, template='json')
def index(self): def index(self):
# Route the request to specific methods with parameters # Route the request to specific methods with parameters
@ -238,6 +281,8 @@ class PhasedSubcloudDeployController(object):
if verb == 'bootstrap': if verb == 'bootstrap':
subcloud = self._deploy_bootstrap(context, pecan.request, subcloud) subcloud = self._deploy_bootstrap(context, pecan.request, subcloud)
elif verb == 'configure':
subcloud = self._deploy_config(context, pecan.request, subcloud)
else: else:
pecan.abort(400, _('Invalid request')) pecan.abort(400, _('Invalid request'))

View File

@ -30,6 +30,10 @@ phased_subcloud_deploy_rules = [
{ {
'method': 'PATCH', 'method': 'PATCH',
'path': '/v1.0/phased-subcloud-deploy/{subcloud}/bootstrap' 'path': '/v1.0/phased-subcloud-deploy/{subcloud}/bootstrap'
},
{
'method': 'PATCH',
'path': '/v1.0/phased-subcloud-deploy/{subcloud}/configure'
} }
] ]
) )

View File

@ -179,6 +179,10 @@ DEPLOY_STATE_BOOTSTRAPPING = 'bootstrapping'
DEPLOY_STATE_BOOTSTRAP_FAILED = 'bootstrap-failed' DEPLOY_STATE_BOOTSTRAP_FAILED = 'bootstrap-failed'
DEPLOY_STATE_BOOTSTRAP_ABORTED = 'bootstrap-aborted' DEPLOY_STATE_BOOTSTRAP_ABORTED = 'bootstrap-aborted'
DEPLOY_STATE_BOOTSTRAPPED = 'bootstrap-complete' 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_DEPLOYING = 'deploying'
DEPLOY_STATE_DEPLOY_FAILED = 'deploy-failed' DEPLOY_STATE_DEPLOY_FAILED = 'deploy-failed'
DEPLOY_STATE_MIGRATING_DATA = 'migrating-data' DEPLOY_STATE_MIGRATING_DATA = 'migrating-data'

View File

@ -692,21 +692,15 @@ def upload_deploy_config_file(request, payload):
def get_config_file_path(subcloud_name, config_file_type=None): def get_config_file_path(subcloud_name, config_file_type=None):
basepath = consts.ANSIBLE_OVERRIDES_PATH
if config_file_type == consts.DEPLOY_CONFIG: if config_file_type == consts.DEPLOY_CONFIG:
file_path = os.path.join( filename = f"{subcloud_name}_{config_file_type}.yml"
consts.ANSIBLE_OVERRIDES_PATH, elif config_file_type == consts.INSTALL_VALUES:
subcloud_name + '_' + config_file_type + '.yml' basepath = os.path.join(basepath, subcloud_name)
) filename = f'{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'
)
else: else:
file_path = os.path.join( filename = f"{subcloud_name}.yml"
consts.ANSIBLE_OVERRIDES_PATH, file_path = os.path.join(basepath, filename)
subcloud_name + '.yml'
)
return file_path 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): def get_common_deploy_files(payload, software_version):
missing_deploy_files = []
for f in consts.DEPLOY_COMMON_FILE_OPTIONS: for f in consts.DEPLOY_COMMON_FILE_OPTIONS:
# Skip the prestage_images option as it is not relevant in this # Skip the prestage_images option as it is
# context # not relevant in this context
if f == consts.DEPLOY_PRESTAGE: if f == consts.DEPLOY_PRESTAGE:
continue continue
filename = None filename = None
dir_path = os.path.join(dccommon_consts.DEPLOY_DIR, software_version) dir_path = os.path.join(dccommon_consts.DEPLOY_DIR, software_version)
if os.path.isdir(dir_path): if os.path.isdir(dir_path):
filename = utils.get_filename_by_prefix(dir_path, f + '_') filename = utils.get_filename_by_prefix(dir_path, f + '_')
if filename is None: if not filename:
pecan.abort(400, _("Missing required deploy file for %s") % f) missing_deploy_files.append(f)
payload.update({f: os.path.join(dir_path, filename)}) 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): 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 = request.POST[f]
file_item.file.seek(0, os.SEEK_SET) file_item.file.seek(0, os.SEEK_SET)
contents = file_item.file.read() 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) fn = get_config_file_path(subcloud.name, f)
upload_config_file(contents, fn, f) upload_config_file(contents, fn, f)
payload.update({f: fn}) payload.update({f: fn})

View File

@ -206,6 +206,14 @@ class DCManagerService(service.Service):
subcloud_id, subcloud_id,
payload) 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): def _stop_rpc_server(self):
# Stop RPC connection to prevent new requests # Stop RPC connection to prevent new requests
LOG.debug(_("Attempting to stop RPC service...")) LOG.debug(_("Attempting to stop RPC service..."))

View File

@ -108,6 +108,8 @@ TRANSITORY_STATES = {
consts.DEPLOY_STATE_PRE_INSTALL: consts.DEPLOY_STATE_PRE_INSTALL_FAILED, consts.DEPLOY_STATE_PRE_INSTALL: consts.DEPLOY_STATE_PRE_INSTALL_FAILED,
consts.DEPLOY_STATE_INSTALLING: consts.DEPLOY_STATE_INSTALL_FAILED, consts.DEPLOY_STATE_INSTALLING: consts.DEPLOY_STATE_INSTALL_FAILED,
consts.DEPLOY_STATE_BOOTSTRAPPING: consts.DEPLOY_STATE_BOOTSTRAP_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_DEPLOYING: consts.DEPLOY_STATE_DEPLOY_FAILED,
consts.DEPLOY_STATE_MIGRATING_DATA: consts.DEPLOY_STATE_DATA_MIGRATION_FAILED, consts.DEPLOY_STATE_MIGRATING_DATA: consts.DEPLOY_STATE_DATA_MIGRATION_FAILED,
consts.DEPLOY_STATE_PRE_RESTORE: consts.DEPLOY_STATE_RESTORE_PREP_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] software_version if software_version else SW_VERSION]
return apply_command 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): def compose_deploy_command(self, subcloud_name, ansible_subcloud_inventory_file, payload):
deploy_command = [ deploy_command = [
"ansible-playbook", payload[consts.DEPLOY_PLAYBOOK], "ansible-playbook", payload[consts.DEPLOY_PLAYBOOK],
@ -974,6 +977,41 @@ class SubcloudManager(manager.Manager):
context, subcloud_id, context, subcloud_id,
deploy_status=consts.DEPLOY_STATE_PRE_BOOTSTRAP_FAILED) 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( def _subcloud_operation_notice(
self, operation, restore_subclouds, failed_subclouds, self, operation, restore_subclouds, failed_subclouds,
invalid_subclouds): invalid_subclouds):
@ -1623,6 +1661,9 @@ class SubcloudManager(manager.Manager):
if apply_command: if apply_command:
self._run_subcloud_bootstrap(context, subcloud, self._run_subcloud_bootstrap(context, subcloud,
apply_command, log_file) apply_command, log_file)
if deploy_command:
self._run_subcloud_config(subcloud, context,
deploy_command, log_file)
except Exception as ex: except Exception as ex:
LOG.exception("run_deploy failed") LOG.exception("run_deploy failed")
raise ex raise ex
@ -1701,6 +1742,32 @@ class SubcloudManager(manager.Manager):
LOG.info("Successfully bootstrapped %s" % subcloud.name) 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): def _create_addn_hosts_dc(self, context):
"""Generate the addn_hosts_dc file for hostname/ip translation""" """Generate the addn_hosts_dc file for hostname/ip translation"""

View File

@ -197,6 +197,11 @@ class ManagerClient(RPCClient):
subcloud_id=subcloud_id, subcloud_id=subcloud_id,
payload=payload)) 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): class DCManagerNotifications(RPCClient):
"""DC Manager Notification interface to broadcast subcloud state changed """DC Manager Notification interface to broadcast subcloud state changed

View File

@ -4,8 +4,10 @@
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
# #
import base64
import copy import copy
import json import json
import mock import mock
from os import path as os_path from os import path as os_path
import six import six
@ -41,7 +43,7 @@ class FakeRPCClient(object):
# Apply the TestSubcloudPost parameter validation tests to the subcloud deploy # Apply the TestSubcloudPost parameter validation tests to the subcloud deploy
# add endpoint as it uses the same parameter validation functions # add endpoint as it uses the same parameter validation functions
class TestSubcloudDeployCreate(TestSubcloudPost): class TestSubcloudDeployCreate(TestSubcloudPost):
API_PREFIX = '/v1.0/phased-subcloud-deploy' API_PREFIX = FAKE_URL
RESULT_KEY = 'phased-subcloud-deploy' RESULT_KEY = 'phased-subcloud-deploy'
def setUp(self): def setUp(self):
@ -229,3 +231,77 @@ class TestSubcloudDeployBootstrap(testroot.DCManagerApiTest):
upload_files=[("bootstrap_values", upload_files=[("bootstrap_values",
"bootstrap_fake_filename", "bootstrap_fake_filename",
fake_content)]) 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)

View File

@ -21,6 +21,7 @@ import webtest
from dcmanager.api.controllers.v1 import subcloud_deploy from dcmanager.api.controllers.v1 import subcloud_deploy
from dcmanager.common import consts from dcmanager.common import consts
from dcmanager.common import phased_subcloud_deploy as psd_common
from dcmanager.common import utils as dutils from dcmanager.common import utils as dutils
from dcmanager.tests.unit.api import test_root_controller as testroot from dcmanager.tests.unit.api import test_root_controller as testroot
from dcmanager.tests import utils from dcmanager.tests import utils
@ -245,3 +246,16 @@ class TestSubcloudDeploy(testroot.DCManagerApiTest):
response.json['subcloud_deploy'][consts.DEPLOY_CHART]) response.json['subcloud_deploy'][consts.DEPLOY_CHART])
self.assertEqual(None, self.assertEqual(None,
response.json['subcloud_deploy'][consts.DEPLOY_PRESTAGE]) 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")

View File

@ -557,6 +557,29 @@ class TestSubcloudManager(base.DCManagerTestCase):
self.assertEqual(consts.DEPLOY_STATE_PRE_BOOTSTRAP_FAILED, self.assertEqual(consts.DEPLOY_STATE_PRE_BOOTSTRAP_FAILED,
updated_subcloud.deploy_status) 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, @mock.patch.object(subcloud_manager.SubcloudManager,
'compose_apply_command') 'compose_apply_command')
@mock.patch.object(subcloud_manager.SubcloudManager, @mock.patch.object(subcloud_manager.SubcloudManager,
@ -2157,12 +2180,14 @@ class TestSubcloudManager(base.DCManagerTestCase):
self.assertTrue('Subcloud does not exist' self.assertTrue('Subcloud does not exist'
in str(e)) in str(e))
@mock.patch.object(os_path, 'isdir')
@mock.patch.object(os_path, 'exists') @mock.patch.object(os_path, 'exists')
@mock.patch.object(cutils, 'get_filename_by_prefix') @mock.patch.object(cutils, 'get_filename_by_prefix')
@mock.patch.object(prestage, '_run_ansible') @mock.patch.object(prestage, '_run_ansible')
def test_prestage_remote_pass(self, mock_run_ansible, def test_prestage_remote_pass(self, mock_run_ansible,
mock_get_filename_by_prefix, mock_get_filename_by_prefix,
mock_file_exists): mock_file_exists,
mock_isdir):
values = copy.copy(FAKE_PRESTAGE_PAYLOAD) values = copy.copy(FAKE_PRESTAGE_PAYLOAD)
subcloud = self.create_subcloud_static(self.ctx, subcloud = self.create_subcloud_static(self.ctx,
@ -2174,6 +2199,7 @@ class TestSubcloudManager(base.DCManagerTestCase):
mock_run_ansible.return_value = None mock_run_ansible.return_value = None
mock_get_filename_by_prefix.return_value = 'prestage_images_list.txt' mock_get_filename_by_prefix.return_value = 'prestage_images_list.txt'
mock_file_exists.return_value = True mock_file_exists.return_value = True
mock_isdir.return_value = True
# Verify that subcloud has the correct deploy status # Verify that subcloud has the correct deploy status
updated_subcloud = db_api.subcloud_get_by_name(self.ctx, subcloud.name) updated_subcloud = db_api.subcloud_get_by_name(self.ctx, subcloud.name)