Add subcloud deploy install option to dcmanager

This commit adds the command "subcloud deploy install" to dcmanager.
It runs the subcloud install step only. The install values file is
optional if it has already been provided in the previous phase
using subcloud deploy create.

Test Plan:
  Success cases:
  - PASS: Install passing install_values and verify that the subcloud
          was successfully installed.
  - PASS: Install without passing install_values and verify that the
          subcloud was successfully installed using install data
          previously saved in db.
  - PASS: Install passing current release and verify that the
          subcloud was successfully installed.
  - PASS: Install passing previous release and verify that the
          subcloud was successfully installed.
  - 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 install if deploy
          state is not 'create-complete', 'pre-install-failed',
          'install-failed' or 'install-complete'.
  - PASS: Call the API directly, passing bmc-password as plain text
          as opposed to b64encoded and verify that the response
          contains the correct error code and message.

Story: 2010756
Task: 48056

Signed-off-by: Victor Romano <victor.gluzromano@windriver.com>
Change-Id: I3a9f4e8c2f39964b2b0b784181bc78494f3078a2
This commit is contained in:
Victor Romano 2023-05-23 16:33:53 -03:00
parent 6b7b012992
commit 77faee83d2
11 changed files with 507 additions and 8 deletions

View File

@ -1913,6 +1913,76 @@ Response Example
.. literalinclude:: samples/phased-subcloud-deploy/phased-subcloud-deploy-post-response.json
:language: json
**********************************
Installs a subcloud
**********************************
.. rest_method:: POST /v1.0/phased-subcloud-deploy/{subcloud}/install
**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
- release: release
- sysadmin_password: sysadmin_password
- bmc_password: bmc_password
Accepts Content-Type multipart/form-data
Request Example
----------------
.. literalinclude:: samples/phased-subcloud-deploy/phased-subcloud-deploy-post-install-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-post-install-response.json
:language: json
**********************************
Configures a subcloud
**********************************

View File

@ -0,0 +1,7 @@
{
"bmc_password": "YYYYYYY",
"install_values": "content of install_values file",
"release": "22.12",
"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": "offline",
"data_install": null,
"data_upgrade": null,
"deploy-status": "pre-install",
"backup-status": null,
"backup-datetime": null,
"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": "22.12",
"systemcontroller-gateway-ip": "192.168.204.101"
}

View File

@ -5,6 +5,7 @@
#
import http.client as httpclient
import json
import os
from oslo_log import log as logging
@ -13,6 +14,7 @@ import pecan
import tsconfig.tsconfig as tsc
import yaml
from dccommon import consts as dccommon_consts
from dcmanager.api.controllers import restcomm
from dcmanager.api.policies import phased_subcloud_deploy as \
phased_subcloud_deploy_policy
@ -43,6 +45,10 @@ SUBCLOUD_CREATE_GET_FILE_CONTENTS = (
consts.INSTALL_VALUES,
)
SUBCLOUD_INSTALL_GET_FILE_CONTENTS = (
consts.INSTALL_VALUES,
)
SUBCLOUD_BOOTSTRAP_GET_FILE_CONTENTS = (
consts.BOOTSTRAP_VALUES,
)
@ -51,6 +57,13 @@ 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
)
VALID_STATES_FOR_DEPLOY_BOOTSTRAP = [
consts.DEPLOY_STATE_INSTALLED,
consts.DEPLOY_STATE_BOOTSTRAP_FAILED,
@ -147,6 +160,48 @@ class PhasedSubcloudDeployController(object):
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', tsc.SW_VERSION)
psd_common.populate_payload_with_pre_existing_data(
payload, subcloud, SUBCLOUD_INSTALL_GET_FILE_CONTENTS)
psd_common.validate_sysadmin_password(payload)
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.
subcloud = db_api.subcloud_update(
context,
subcloud.id,
description=payload.get('description', subcloud.description),
location=payload.get('location', subcloud.location),
software_version=payload['software_version'],
management_state=dccommon_consts.MANAGEMENT_UNMANAGED,
deploy_status=consts.DEPLOY_STATE_PRE_INSTALL,
data_install=json.dumps(payload['install_values']))
self.dcmanager_rpc_client.subcloud_deploy_install(
context, subcloud.id, payload)
return db_api.subcloud_db_model_to_dict(subcloud)
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):
@ -279,7 +334,9 @@ class PhasedSubcloudDeployController(object):
except (exceptions.SubcloudNotFound, exceptions.SubcloudNameNotFound):
pecan.abort(404, _('Subcloud not found'))
if verb == 'bootstrap':
if verb == 'install':
subcloud = self._deploy_install(context, pecan.request, subcloud)
elif verb == 'bootstrap':
subcloud = self._deploy_bootstrap(context, pecan.request, subcloud)
elif verb == 'configure':
subcloud = self._deploy_config(context, pecan.request, subcloud)

View File

@ -27,6 +27,10 @@ phased_subcloud_deploy_rules = [
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Modify the subcloud deployment.",
operations=[
{
'method': 'PATCH',
'path': '/v1.0/phased-subcloud-deploy/{subcloud}/install'
},
{
'method': 'PATCH',
'path': '/v1.0/phased-subcloud-deploy/{subcloud}/bootstrap'

View File

@ -603,7 +603,8 @@ def validate_k8s_version(payload):
yaml file must be of the same value as fresh_install_k8s_version of
the specified release.
"""
if payload['software_version'] == tsc.SW_VERSION:
software_version = payload['software_version']
if software_version == tsc.SW_VERSION:
return
kubernetes_version = payload.get(KUBERNETES_VERSION)
@ -611,14 +612,14 @@ def validate_k8s_version(payload):
try:
bootstrap_var_file = utils.get_playbook_for_software_version(
ANSIBLE_BOOTSTRAP_VALIDATE_CONFIG_VARS,
payload['software_version'])
software_version)
fresh_install_k8s_version = utils.get_value_from_yaml_file(
bootstrap_var_file,
FRESH_INSTALL_K8S_VERSION)
if not fresh_install_k8s_version:
pecan.abort(400, _("%s not found in %s")
% (FRESH_INSTALL_K8S_VERSION,
bootstrap_var_file))
bootstrap_var_file))
if kubernetes_version != fresh_install_k8s_version:
pecan.abort(400, _("The kubernetes_version value (%s) "
"specified in the subcloud bootstrap "
@ -626,12 +627,12 @@ def validate_k8s_version(payload):
"fresh_install_k8s_version value (%s) "
"of the specified release %s")
% (kubernetes_version,
fresh_install_k8s_version,
payload['software_version']))
fresh_install_k8s_version,
software_version))
except exceptions.PlaybookNotFound:
pecan.abort(400, _("The bootstrap playbook validate-config vars "
"not found for %s software version")
% payload['software_version'])
% software_version)
def validate_sysadmin_password(payload: dict):
@ -809,12 +810,42 @@ def get_request_data(request: pecan.Request,
return payload
def get_subcloud_db_install_values(subcloud):
if not subcloud.data_install:
msg = _("Failed to read data install from db")
LOG.exception(msg)
pecan.abort(400, msg)
install_values = json.loads(subcloud.data_install)
# mandatory install parameters
mandatory_install_parameters = [
'bootstrap_interface',
'bootstrap_address',
'bootstrap_address_prefix',
'bmc_username',
'bmc_address',
'bmc_password',
]
for p in mandatory_install_parameters:
if p not in install_values:
msg = _("Failed to get %s from data_install" % p)
LOG.exception(msg)
pecan.abort(400, msg)
return install_values
def populate_payload_with_pre_existing_data(payload: dict,
subcloud: models.Subcloud,
mandatory_values: typing.Sequence):
for value in mandatory_values:
if value == consts.INSTALL_VALUES:
pass
if not payload.get(consts.INSTALL_VALUES):
install_values = get_subcloud_db_install_values(subcloud)
payload.update({value: install_values})
else:
validate_install_values(payload)
elif value == consts.BOOTSTRAP_VALUES:
filename = get_config_file_path(subcloud.name)
LOG.info("Loading existing bootstrap values from: %s" % filename)
@ -834,3 +865,33 @@ def populate_payload_with_pre_existing_data(payload: dict,
pecan.abort(400, msg)
payload.update({value: fn})
get_common_deploy_files(payload, subcloud.software_version)
def pre_deploy_install(payload: dict,
subcloud: models.Subcloud):
install_values = payload['install_values']
# If the software version of the subcloud is different from the
# specified or active load, update the software version in install
# value and delete the image path in install values, then the subcloud
# will be reinstalled using the image in dc_vault.
if install_values.get(
'software_version') != payload['software_version']:
install_values['software_version'] = payload['software_version']
install_values.pop('image', None)
# Confirm the specified or active load is still in dc-vault if
# image not in install values, add the matching image into the
# install values.
matching_iso, err_msg = utils.get_matching_iso(payload['software_version'])
if err_msg:
LOG.exception(err_msg)
pecan.abort(400, _(err_msg))
LOG.info("Image in install_values is set to %s" % matching_iso)
install_values['image'] = matching_iso
# Update the install values in payload
if not payload.get('bmc_password'):
payload.update({'bmc_password': install_values.get('bmc_password')})
payload.update({'install_values': install_values})

View File

@ -214,6 +214,14 @@ class DCManagerService(service.Service):
subcloud_id,
payload)
@request_context
def subcloud_deploy_install(self, context, subcloud_id, payload):
# Install a subcloud
LOG.info("Handling subcloud_deploy_install request for: %s" % subcloud_id)
return self.subcloud_manager.subcloud_deploy_install(context,
subcloud_id,
payload)
def _stop_rpc_server(self):
# Stop RPC connection to prevent new requests
LOG.debug(_("Attempting to stop RPC service..."))

View File

@ -53,6 +53,7 @@ from dcmanager.common import exceptions
from dcmanager.common.exceptions import DCManagerException
from dcmanager.common.i18n import _
from dcmanager.common import manager
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
@ -758,6 +759,33 @@ class SubcloudManager(manager.Manager):
return self._subcloud_operation_notice('restore', restore_subclouds,
failed_subclouds, invalid_subclouds)
def _deploy_install_prep(self, subcloud, payload: dict,
ansible_subcloud_inventory_file):
payload['install_values']['ansible_ssh_pass'] = \
payload['sysadmin_password']
payload['install_values']['ansible_become_pass'] = \
payload['sysadmin_password']
# If all update_values already exists on override file or are
# the same as the existing ones, the update won't happen
# and the file will remain untouched
bootstrap_file = psd_common.get_config_file_path(subcloud.name,
consts.BOOTSTRAP_VALUES)
update_values = {'software_version': payload['software_version'],
'bmc_password': payload['bmc_password'],
'ansible_ssh_pass': payload['sysadmin_password'],
'ansible_become_pass': payload['sysadmin_password']
}
utils.update_values_on_yaml_file(bootstrap_file,
update_values)
install_command = self.compose_install_command(
subcloud.name,
ansible_subcloud_inventory_file,
payload['software_version'])
return install_command
def subcloud_deploy_create(self, context, subcloud_id, payload):
"""Create subcloud and notify orchestrators.
@ -897,6 +925,40 @@ class SubcloudManager(manager.Manager):
deploy_status=consts.DEPLOY_STATE_CREATE_FAILED)
return db_api.subcloud_db_model_to_dict(subcloud)
def subcloud_deploy_install(self, context, subcloud_id, payload: dict) -> dict:
"""Install subcloud
:param context: request context object
:param subcloud_id: subcloud id from db
:param payload: subcloud Install
"""
# Retrieve the subcloud details from the database
subcloud = db_api.subcloud_get(context, subcloud_id)
LOG.info("Installing subcloud %s." % subcloud_id)
try:
ansible_subcloud_inventory_file = self._get_ansible_filename(
subcloud.name, INVENTORY_FILE_POSTFIX)
install_command = self._deploy_install_prep(
subcloud, payload, ansible_subcloud_inventory_file)
apply_thread = threading.Thread(
target=self.run_deploy_commands,
args=(subcloud, payload, context),
kwargs={'install_command': install_command})
apply_thread.start()
return db_api.subcloud_db_model_to_dict(subcloud)
except Exception:
LOG.exception("Failed to install subcloud %s" % subcloud.name)
# If we failed to install the subcloud,
# update the deployment status
db_api.subcloud_update(
context, subcloud_id,
deploy_status=consts.DEPLOY_STATE_PRE_INSTALL_FAILED)
def subcloud_deploy_bootstrap(self, context, subcloud_id, payload):
"""Bootstrap subcloud
@ -1658,12 +1720,26 @@ class SubcloudManager(manager.Manager):
os.path.join(consts.DC_ANSIBLE_LOG_DIR, subcloud.name)
+ "_playbook_output.log"
)
if install_command:
install_success = self._run_subcloud_install(
context, subcloud, install_command,
log_file, payload['install_values'])
if not install_success:
return
db_api.subcloud_update(
context, subcloud.id,
deploy_status=consts.DEPLOY_STATE_INSTALLED,
error_description=consts.ERROR_DESC_EMPTY)
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

View File

@ -192,6 +192,11 @@ class ManagerClient(RPCClient):
subcloud_id=subcloud_id,
payload=payload))
def subcloud_deploy_install(self, ctxt, subcloud_id, payload):
return self.cast(ctxt, self.make_msg('subcloud_deploy_install',
subcloud_id=subcloud_id,
payload=payload))
def subcloud_deploy_bootstrap(self, ctxt, subcloud_id, payload):
return self.cast(ctxt, self.make_msg('subcloud_deploy_bootstrap',
subcloud_id=subcloud_id,

View File

@ -11,6 +11,7 @@ import json
import mock
from os import path as os_path
import six
from tsconfig.tsconfig import SW_VERSION
import webtest
from dcmanager.common import consts
@ -28,10 +29,12 @@ from dcmanager.tests import utils
FAKE_URL = '/v1.0/phased-subcloud-deploy'
FAKE_SOFTWARE_VERSION = '21.12'
FAKE_TENANT = utils.UUID1
FAKE_HEADERS = {'X-Tenant-Id': FAKE_TENANT, 'X_ROLE': 'admin,member,reader',
'X-Identity-Status': 'Confirmed', 'X-Project-Name': 'admin'}
FAKE_SUBCLOUD_INSTALL_VALUES = fake_subcloud.FAKE_SUBCLOUD_INSTALL_VALUES
class FakeRPCClient(object):
@ -305,3 +308,158 @@ class TestSubcloudDeployConfig(testroot.DCManagerApiTest):
self.app.patch_json, FAKE_URL + '/' +
str(subcloud.id) + '/configure',
headers=FAKE_HEADERS, params=data)
class TestSubcloudDeployInstall(testroot.DCManagerApiTest):
def setUp(self):
super(TestSubcloudDeployInstall, 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(dutils, 'get_vault_load_files')
self.mock_get_vault_load_files = p.start()
self.addCleanup(p.stop)
p = mock.patch.object(psd_common, 'get_subcloud_db_install_values')
self.mock_get_subcloud_db_install_values = p.start()
self.addCleanup(p.stop)
p = mock.patch.object(psd_common, 'validate_k8s_version')
self.mock_validate_k8s_version = 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_install_subcloud(self):
subcloud = fake_subcloud.create_fake_subcloud(
self.ctx,
deploy_status=consts.DEPLOY_STATE_CREATED)
install_data = copy.copy(FAKE_SUBCLOUD_INSTALL_VALUES)
install_data.pop('software_version')
fake_sysadmin_password = base64.b64encode(
'testpass'.encode("utf-8")).decode('utf-8')
fake_bmc_password = base64.b64encode(
'bmc_password'.encode("utf-8")).decode('utf-8')
bmc_password = {'bmc_password': fake_bmc_password}
install_data.update(bmc_password)
install_payload = {'install_values': install_data,
'sysadmin_password': fake_sysadmin_password,
'bmc_password': fake_bmc_password}
self.mock_get_request_data.return_value = install_payload
self.mock_get_subcloud_db_install_values.return_value = install_data
self.mock_rpc_client().subcloud_deploy_install.return_value = True
self.mock_get_vault_load_files.return_value = ('iso_file_path', 'sig_file_path')
response = self.app.patch_json(
FAKE_URL + '/' + str(subcloud.id) + '/install',
headers=FAKE_HEADERS, params=install_payload)
self.assertEqual(response.status_int, 200)
self.assertEqual(consts.DEPLOY_STATE_PRE_INSTALL,
response.json['deploy-status'])
self.assertEqual(SW_VERSION, response.json['software-version'])
def test_install_subcloud_with_release_parameter(self):
subcloud = fake_subcloud.create_fake_subcloud(
self.ctx,
deploy_status=consts.DEPLOY_STATE_CREATED)
install_data = copy.copy(FAKE_SUBCLOUD_INSTALL_VALUES)
install_data.pop('software_version')
fake_sysadmin_password = base64.b64encode(
'testpass'.encode("utf-8")).decode('utf-8')
fake_bmc_password = base64.b64encode(
'bmc_password'.encode("utf-8")).decode('utf-8')
bmc_password = {'bmc_password': fake_bmc_password}
install_data.update(bmc_password)
install_payload = {'install_values': install_data,
'sysadmin_password': fake_sysadmin_password,
'bmc_password': fake_bmc_password,
'release': FAKE_SOFTWARE_VERSION}
self.mock_get_request_data.return_value = install_payload
self.mock_get_subcloud_db_install_values.return_value = install_data
self.mock_rpc_client().subcloud_deploy_install.return_value = True
self.mock_get_vault_load_files.return_value = ('iso_file_path', 'sig_file_path')
response = self.app.patch_json(
FAKE_URL + '/' + str(subcloud.id) + '/install',
headers=FAKE_HEADERS, params=install_payload)
self.assertEqual(response.status_int, 200)
self.assertEqual(consts.DEPLOY_STATE_PRE_INSTALL,
response.json['deploy-status'])
self.assertEqual(FAKE_SOFTWARE_VERSION,
json.loads(response.json['data_install'])['software_version'])
def test_install_subcloud_no_body(self):
subcloud = fake_subcloud.create_fake_subcloud(
self.ctx,
deploy_status=consts.DEPLOY_STATE_CREATED)
self.mock_get_request_data.return_value = {}
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.patch_json, FAKE_URL + '/' +
str(subcloud.id) + '/install',
headers=FAKE_HEADERS, params={})
def test_install_subcloud_no_install_values_on_request_or_db(self):
subcloud = fake_subcloud.create_fake_subcloud(
self.ctx,
deploy_status=consts.DEPLOY_STATE_CREATED,
data_install='')
fake_sysadmin_password = base64.b64encode(
'testpass'.encode("utf-8")).decode('utf-8')
fake_bmc_password = base64.b64encode(
'bmc_password'.encode("utf-8")).decode('utf-8')
install_payload = {'sysadmin_password': fake_sysadmin_password,
'bmc_password': fake_bmc_password}
self.mock_get_request_data.return_value = install_payload
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.patch_json, FAKE_URL + '/' +
str(subcloud.id) + '/install',
headers=FAKE_HEADERS, params=install_payload)
def test_install_subcloud_no_install_values_on_request(self):
subcloud = fake_subcloud.create_fake_subcloud(
self.ctx,
deploy_status=consts.DEPLOY_STATE_CREATED)
install_data = copy.copy(FAKE_SUBCLOUD_INSTALL_VALUES)
install_data.pop('software_version')
fake_sysadmin_password = base64.b64encode(
'testpass'.encode("utf-8")).decode('utf-8')
fake_bmc_password = base64.b64encode(
'bmc_password'.encode("utf-8")).decode('utf-8')
bmc_password = {'bmc_password': fake_bmc_password}
install_data.update(bmc_password)
install_payload = {'sysadmin_password': fake_sysadmin_password}
self.mock_get_request_data.return_value = install_payload
self.mock_get_subcloud_db_install_values.return_value = install_data
self.mock_rpc_client().subcloud_deploy_install.return_value = True
self.mock_get_vault_load_files.return_value = ('iso_file_path', 'sig_file_path')
response = self.app.patch_json(
FAKE_URL + '/' + str(subcloud.id) + '/install',
headers=FAKE_HEADERS, params=install_payload)
self.assertEqual(response.status_int, 200)
self.assertEqual(consts.DEPLOY_STATE_PRE_INSTALL,
response.json['deploy-status'])
self.assertEqual(SW_VERSION, response.json['software-version'])

View File

@ -425,6 +425,36 @@ class TestSubcloudManager(base.DCManagerTestCase):
self.assertEqual('localhost', sm.host)
self.assertEqual(self.ctx, sm.context)
@mock.patch.object(
subcloud_manager.SubcloudManager, 'compose_install_command')
@mock.patch.object(threading.Thread, 'start')
def test_deploy_install_subcloud(self,
mock_thread_start,
mock_compose_install_command):
subcloud_name = 'subcloud1'
subcloud = self.create_subcloud_static(
self.ctx,
name=subcloud_name,
deploy_status=consts.DEPLOY_STATE_PRE_INSTALL)
fake_install_values = \
copy.copy(fake_subcloud.FAKE_SUBCLOUD_INSTALL_VALUES)
fake_install_values['software_version'] = SW_VERSION
fake_payload = {'bmc_password': 'bmc_pass',
'install_values': fake_install_values,
'software_version': FAKE_PREVIOUS_SW_VERSION,
'sysadmin_password': 'sys_pass'}
sm = subcloud_manager.SubcloudManager()
sm.subcloud_deploy_install(self.ctx, subcloud.id, payload=fake_payload)
mock_compose_install_command.assert_called_once_with(
subcloud_name,
sm._get_ansible_filename(subcloud_name, consts.INVENTORY_FILE_POSTFIX),
FAKE_PREVIOUS_SW_VERSION)
mock_thread_start.assert_called_once()
@mock.patch.object(subcloud_manager.SubcloudManager,
'_create_intermediate_ca_cert')
@mock.patch.object(cutils, 'delete_subcloud_inventory')