Add release optionality to subcloud add/reinstall

Add an optional --release parameter to subcloud add and reinstall
commands to enable release optionality in subcloud add and subcloud
reinstall.

Test Plan:
1. Verify successful subcloud add which includes remote install with
specified (previous/current) release
2. Verify successful subcloud reinstall with the specified
(previous/current) release
3. Verify the subcloud is successfully installed with the active
release when the release parameter is absent
4. Verify the subcloud is successfully reinstalled with the active
release when the release parameter is absent
5. Verify the subcloud install request was rejected
when the software_version in the install_values doesn't
match the specified release
6. Verify the subcloud install/reinstall request was rejected
when the kubernetes_version value specified in the subcloud bootstrap
yaml file doesn't match the value of the fresh_install_k8s_version of
the specified previous release

Depends-On: https://review.opendev.org/c/starlingx/utilities/+/878545
            https://review.opendev.org/c/starlingx/ansible-playbooks/+/878922

Story: 2010611
Task: 47684

Signed-off-by: lzhu1 <li.zhu@windriver.com>
Change-Id: Ic4193c2901d8bfa485eeb683c08422d946802bcb
This commit is contained in:
Li Zhu 2023-03-31 20:02:28 -04:00
parent d2b8556aa6
commit ad1f05ac5f
16 changed files with 548 additions and 152 deletions

View File

@ -140,6 +140,7 @@ serviceUnavailable (503)
- management_subnet: management_subnet
- migrate: migrate
- name: subcloud_name
- release: release
- sysadmin_password: sysadmin_password
- systemcontroller_gateway_address: systemcontroller_gateway_ip
- system_mode: system_mode
@ -493,6 +494,7 @@ serviceUnavailable (503)
- subcloud: subcloud_uri
- bootstrap_values: bootstrap_values
- deploy_config: deploy_config
- release: release
- sysadmin_password: sysadmin_password
Request Example
@ -1601,9 +1603,7 @@ files which include deploy playbook, deploy overrides, deploy helm charts, and p
Show Subcloud Deploy Files
**************************
.. rest_method:: GET /v1.0/subcloud-deploy
This operation does not accept a request body.
.. rest_method:: GET /v1.0/subcloud-deploy/{release}
**Normal response codes**
@ -1615,6 +1615,13 @@ badRequest (400), unauthorized (401), forbidden
(403), badMethod (405), HTTPUnprocessableEntity (422),
internalServerError (500), serviceUnavailable (503)
**Request parameters**
.. rest_parameters:: parameters.yaml
- release: release_uri
This operation does not accept a request body.
**Response parameters**
@ -1625,6 +1632,7 @@ internalServerError (500), serviceUnavailable (503)
- deploy_playbook: subcloud_deploy_playbook
- deploy_overrides: subcloud_deploy_overrides
- prestage_images: subcloud_deploy_prestage_images
- software_version: software_version
Response Example
----------------
@ -1659,6 +1667,7 @@ serviceUnavailable (503)
- deploy_playbook: subcloud_deploy_playbook_content
- deploy_overrides: subcloud_deploy_overrides_content
- prestage_images: subcloud_deploy_prestage_images_content
- release: release
Request Example
----------------
@ -1674,6 +1683,7 @@ Request Example
- deploy_playbook: subcloud_deploy_playbook
- deploy_overrides: subcloud_deploy_overrides
- prestage_images: subcloud_deploy_prestage_images
- software_version: software_version
Response Example
----------------

View File

@ -6,6 +6,12 @@ backup_delete_release:
in: path
required: true
type: string
release_uri:
description: |
The subcloud software version.
in: path
required: false
type: string
subcloud_group_uri:
description: |
The subcloud group reference, name or id.
@ -317,6 +323,12 @@ region_name:
in: body
required: true
type: string
release:
description: |
The subcloud software version.
in: body
required: false
type: string
restore_values:
description: |
The content of a file containing restore parameters (e.g.

View File

@ -3,6 +3,7 @@
{
"deploy_chart": "deployment-manager.tgz",
"deploy_playbook": "deployment-manager-playbook.yaml",
"deploy_overrides": "deployment-manager-overrides-subcloud.yaml"
"deploy_overrides": "deployment-manager-overrides-subcloud.yaml",
"software_version": "22.12"
}
}

View File

@ -1,5 +1,6 @@
{
"deploy_chart": "deployment manager contents",
"deploy_playbook": "deployment manager playbook contents",
"deploy_overrides": "deployment manager overrides contents"
"deploy_overrides": "deployment manager overrides contents",
"release": "22.12"
}

View File

@ -1,5 +1,6 @@
{
"deploy_chart": "deployment-manager.tgz",
"deploy_playbook": "deployment-manager-playbook.yaml",
"deploy_overrides": "deployment-manager-overrides-subcloud.yaml"
"deploy_overrides": "deployment-manager-overrides-subcloud.yaml",
"software_version": "22.12"
}

View File

@ -1,4 +1,4 @@
# Copyright (c) 2020-2021 Wind River Systems, Inc.
# Copyright (c) 2020-2023 Wind River Systems, Inc.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
@ -16,8 +16,6 @@
SUPPORTED_INSTALL_TYPES = 6
MANDATORY_INSTALL_VALUES = [
'image',
'software_version',
'bootstrap_interface',
'bootstrap_address',
'bootstrap_address_prefix',

View File

@ -105,12 +105,12 @@ class SubcloudDeployController(object):
error_msg = "error: argument %s is required" % missing_str.rstrip()
pecan.abort(httpclient.BAD_REQUEST, error_msg)
release = tsc.SW_VERSION
if request.POST.get('release_version'):
release = request.POST.get('release_version')
deploy_dicts['release_version'] = release
software_version = tsc.SW_VERSION
if request.POST.get('release'):
software_version = request.POST.get('release')
deploy_dicts['software_version'] = software_version
dir_path = os.path.join(dccommon_consts.DEPLOY_DIR, release)
dir_path = os.path.join(dccommon_consts.DEPLOY_DIR, software_version)
for f in consts.DEPLOY_COMMON_FILE_OPTIONS:
if f not in request.POST:
continue
@ -145,7 +145,7 @@ class SubcloudDeployController(object):
deploy_dicts = dict()
if not release:
release = tsc.SW_VERSION
deploy_dicts['release_version'] = release
deploy_dicts['software_version'] = release
dir_path = os.path.join(dccommon_consts.DEPLOY_DIR, release)
for f in consts.DEPLOY_COMMON_FILE_OPTIONS:
filename = None

View File

@ -96,6 +96,13 @@ INSTALL_VALUES_ADDRESSES = [
'network_address'
]
ANSIBLE_BOOTSTRAP_VALIDATE_CONFIG_VARS = \
consts.ANSIBLE_CURRENT_VERSION_BASE_PATH + \
'/roles/bootstrap/validate-config/vars/main.yml'
FRESH_INSTALL_K8S_VERSION = 'fresh_install_k8s_version'
KUBERNETES_VERSION = 'kubernetes_version'
def _get_multipart_field_name(part):
content = part.headers[b"Content-Disposition"].decode("utf8")
@ -132,14 +139,14 @@ class SubcloudsController(object):
pecan.abort(400, _("Invalid group_id"))
@staticmethod
def _get_common_deploy_files(payload):
def _get_common_deploy_files(payload, software_version):
for f in consts.DEPLOY_COMMON_FILE_OPTIONS:
# Skip the prestage_images option as it is not relevant in this
# context
if f == consts.DEPLOY_PRESTAGE:
continue
filename = None
dir_path = tsc.DEPLOY_PATH
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:
@ -159,7 +166,7 @@ class SubcloudsController(object):
fn = self._get_config_file_path(payload['name'], consts.DEPLOY_CONFIG)
self._upload_config_file(contents, fn, consts.DEPLOY_CONFIG)
payload.update({consts.DEPLOY_CONFIG: fn})
self._get_common_deploy_files(payload)
self._get_common_deploy_files(payload, payload['software_version'])
@staticmethod
def _get_request_data(request):
@ -242,7 +249,7 @@ class SubcloudsController(object):
LOG.exception(msg)
pecan.abort(400, msg)
def _get_reconfig_payload(self, request, subcloud_name):
def _get_reconfig_payload(self, request, subcloud_name, software_version):
payload = dict()
multipart_data = decoder.MultipartDecoder(
request.body, pecan.request.headers.get('Content-Type'))
@ -260,7 +267,7 @@ class SubcloudsController(object):
payload.update({consts.DEPLOY_CONFIG: fn})
elif "sysadmin_password" in hv:
payload.update({'sysadmin_password': part.content})
self._get_common_deploy_files(payload)
self._get_common_deploy_files(payload, software_version)
return payload
def _get_config_file_path(self, subcloud_name, config_file_type=None):
@ -615,7 +622,7 @@ class SubcloudsController(object):
"""Validate install values if 'install_values' is present in payload.
The image in payload install values is optional, and if not provided,
the image is set to the available active load image.
the image is set to the available active/inactive load image.
:return boolean: True if bmc install requested, otherwise False
"""
@ -640,15 +647,18 @@ class SubcloudsController(object):
pecan.abort(400, msg)
payload['install_values'].update({'bmc_password': bmc_password})
software_version = payload.get('software_version')
if not software_version and subcloud:
software_version = subcloud.software_version
if 'software_version' in install_values:
software_version = str(install_values.get('software_version'))
else:
if original_install_values:
pecan.abort(400, _("Mandatory install value software_version not present, "
"existing software_version in DB: %s") %
original_install_values.get("software_version"))
else:
pecan.abort(400, _("Mandatory install value software_version not present"))
install_software_version = str(install_values.get('software_version'))
if software_version and software_version != install_software_version:
pecan.abort(400,
_("The software_version value %s in the install values "
"yaml file does not match with the specified/current "
"software version of %s. Please correct or remove "
"this parameter from the yaml file and try again.") %
(install_software_version, software_version))
if 'persistent_size' in install_values:
persistent_size = install_values.get('persistent_size')
if not isinstance(persistent_size, int):
@ -671,27 +681,21 @@ class SubcloudsController(object):
for k in install_consts.MANDATORY_INSTALL_VALUES:
if k not in install_values:
if k == 'image':
if software_version == tsc.SW_VERSION:
# check for the image at load vault load location
matching_iso, err_msg = utils.get_matching_iso()
if err_msg:
LOG.exception(err_msg)
pecan.abort(400, _(err_msg))
LOG.info("image was not in install_values: will reference %s" %
matching_iso)
else:
pecan.abort(400, _("Image was not in install_values, and "
"software version %s in install values "
"did not match the active load %s") %
(software_version, tsc.SW_VERSION))
if original_install_values:
pecan.abort(400, _("Mandatory install value %s not present, "
"existing %s in DB: %s") %
(k, k, original_install_values.get(k)))
else:
if original_install_values:
pecan.abort(400, _("Mandatory install value %s not present, "
"existing %s in DB: %s") %
(k, k, original_install_values.get(k)))
else:
pecan.abort(400, _("Mandatory install value %s not present") % k)
pecan.abort(400,
_("Mandatory install value %s not present") % k)
# check for the image at load vault load location
matching_iso, err_msg = utils.get_matching_iso(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)
payload['install_values'].update({'image': matching_iso})
if (install_values['install_type'] not in
list(range(install_consts.SUPPORTED_INSTALL_TYPES))):
@ -759,6 +763,45 @@ class SubcloudsController(object):
return True
@staticmethod
def _validate_k8s_version(payload):
"""Validate k8s version.
If the specified release in the payload is not the active release,
the kubernetes_version value if specified in the subcloud bootstrap
yaml file must be of the same value as fresh_install_k8s_version of
the specified release.
"""
if payload['software_version'] == tsc.SW_VERSION:
return
kubernetes_version = payload.get(KUBERNETES_VERSION)
if kubernetes_version:
try:
bootstrap_var_file = utils.get_playbook_for_software_version(
ANSIBLE_BOOTSTRAP_VALIDATE_CONFIG_VARS,
payload['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))
if kubernetes_version != fresh_install_k8s_version:
pecan.abort(400, _("The kubernetes_version value (%s) "
"specified in the subcloud bootstrap "
"yaml file doesn't match "
"fresh_install_k8s_version value (%s) "
"of the specified release %s")
% (kubernetes_version,
fresh_install_k8s_version,
payload['software_version']))
except exceptions.PlaybookNotFound:
pecan.abort(400, _("The bootstrap playbook validate-config vars "
"not found for %s software version")
% payload['software_version'])
def _get_subcloud_users(self):
"""Get the subcloud users and passwords from keyring"""
DEFAULT_SERVICE_PROJECT_NAME = 'services'
@ -855,15 +898,11 @@ class SubcloudsController(object):
resource='subcloud',
msg='Subcloud with that name already exists')
# Subcloud is added with software version that matches system
# controller.
software_version = tsc.SW_VERSION
# if group_id has been omitted from payload, use 'Default'.
group_id = payload.get('group_id',
consts.DEFAULT_SUBCLOUD_GROUP_ID)
data_install = None
if 'install_values' in payload:
software_version = payload['install_values']['software_version']
data_install = json.dumps(payload['install_values'])
subcloud = db_api.subcloud_create(
@ -871,7 +910,7 @@ class SubcloudsController(object):
payload['name'],
payload.get('description'),
payload.get('location'),
software_version,
payload.get('software_version'),
utils.get_management_subnet(payload),
utils.get_management_gateway_address(payload),
utils.get_management_start_address(payload),
@ -1140,6 +1179,10 @@ class SubcloudsController(object):
group_id = payload.get('group_id',
consts.DEFAULT_SUBCLOUD_GROUP_ID)
# If a subcloud release is not passed, use the current
# system controller software_version
payload['software_version'] = payload.get('release', tsc.SW_VERSION)
self._validate_system_controller_patch_status()
self._validate_subcloud_config(context,
@ -1160,6 +1203,8 @@ class SubcloudsController(object):
self._validate_install_values(payload)
self._validate_k8s_version(payload)
self._format_ip_address(payload)
# Upload the deploy config files if it is included in the request
@ -1219,6 +1264,7 @@ class SubcloudsController(object):
subcloud_id = subcloud.id
if verb is None:
# subcloud update
payload = self._get_patch_data(request)
if not payload:
pecan.abort(400, _('Body required'))
@ -1317,7 +1363,8 @@ class SubcloudsController(object):
LOG.exception(e)
pecan.abort(500, _('Unable to update subcloud'))
elif verb == 'reconfigure':
payload = self._get_reconfig_payload(request, subcloud.name)
payload = self._get_reconfig_payload(
request, subcloud.name, subcloud.software_version)
if not payload:
pecan.abort(400, _('Body required'))
@ -1454,25 +1501,31 @@ class SubcloudsController(object):
external_oam_floating_ip,
subcloud_subnets)
# If a subcloud release is not passed, use the current
# system controller software_version
payload['software_version'] = payload.get('release', tsc.SW_VERSION)
self._validate_k8s_version(payload)
# If the software version of the subcloud is different from the
# central cloud, update the software version in install valuse 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') != tsc.SW_VERSION:
install_values['software_version'] = tsc.SW_VERSION
# 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 active system controller load is still in dc-vault if
# 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.
if 'image' not in install_values:
matching_iso, err_msg = utils.get_matching_iso()
if err_msg:
LOG.exception(err_msg)
pecan.abort(400, _(err_msg))
LOG.info("image was not in install_values: will reference %s" %
matching_iso)
install_values['image'] = matching_iso
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
payload.update({
@ -1489,15 +1542,15 @@ class SubcloudsController(object):
self._upload_deploy_config_file(request, payload)
try:
# Align the software version of the subcloud with the central
# cloud. Update description, location and group id if offered,
# Align the software version of the subcloud with reinstall
# version. Update description, location and group id if offered,
# update the deploy status as pre-install.
db_api.subcloud_update(
subcloud = db_api.subcloud_update(
context,
subcloud_id,
description=payload.get('description', subcloud.description),
location=payload.get('location', subcloud.location),
software_version=tsc.SW_VERSION,
software_version=payload['software_version'],
management_state=dccommon_consts.MANAGEMENT_UNMANAGED,
deploy_status=consts.DEPLOY_STATE_PRE_INSTALL,
data_install=data_install)

View File

@ -360,3 +360,7 @@ STATES_FOR_ONGOING_BACKUP = [BACKUP_STATE_INITIAL,
OPENLDAP_CA_CERT_SECRET_NAME = "system-local-ca"
CERT_NAMESPACE_PLATFORM_CA_CERTS = 'cert-manager'
# The ansible playbook base directories
ANSIBLE_CURRENT_VERSION_BASE_PATH = '/usr/share/ansible/stx-ansible/playbooks'
ANSIBLE_PREVIOUS_VERSION_BASE_PATH = '/opt/dc-vault/playbooks'

View File

@ -1,6 +1,6 @@
# Copyright 2015 Huawei Technologies Co., Ltd.
# Copyright 2015 Ericsson AB.
# Copyright (c) 2017-2022 Wind River Systems, Inc.
# Copyright (c) 2017-2023 Wind River Systems, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -243,3 +243,7 @@ class StrategySkippedException(DCManagerException):
class StrategyStoppedException(DCManagerException):
message = _("Strategy has been stopped")
class PlaybookNotFound(NotFound):
message = _("Playbook %(playbook_name)s not found")

View File

@ -26,6 +26,7 @@ import resource as sys_resource
import six.moves
import subprocess
import tsconfig.tsconfig as tsc
import yaml
from keystoneauth1 import exceptions as keystone_exceptions
from oslo_concurrency import lockutils
@ -749,14 +750,16 @@ def _is_valid_for_backup_restore(subcloud):
return True
def get_matching_iso():
def get_matching_iso(software_version=None):
try:
matching_iso, _ = get_vault_load_files(tsc.SW_VERSION)
if not software_version:
software_version = tsc.SW_VERSION
matching_iso, _ = get_vault_load_files(software_version)
if not matching_iso:
error_msg = ('Failed to get active load image. Provide '
'active load image via '
error_msg = ('Failed to get %s load image. Provide '
'active/inactive load image via '
'"system --os-region-name SystemController '
'load-import --active"')
'load-import --active/--inactive"' % software_version)
LOG.exception(error_msg)
return None, error_msg
return matching_iso, None
@ -900,3 +903,42 @@ def set_open_file_limit(new_soft_limit: int):
(new_soft_limit, current_hard))
except Exception as ex:
LOG.exception(f'Failed to set NOFILE resource limit: {ex}')
def get_playbook_for_software_version(playbook_filename, software_version=None):
"""Get the ansible playbook filename in corresponding software version.
:param playbook_filename: ansible playbook filename
:param software_version: software version
:raises PlaybookNotFound: If the playbook is not found
Returns the unchanged ansible playbook filename if the software version
parameter is not provided or the same as active release, otherwise, returns
the filename in corresponding software version.
"""
if software_version and software_version != tsc.SW_VERSION:
software_version_path = os.path.join(
consts.ANSIBLE_PREVIOUS_VERSION_BASE_PATH, software_version)
playbook_filename = playbook_filename.replace(
consts.ANSIBLE_CURRENT_VERSION_BASE_PATH,
software_version_path)
if not os.path.isfile(playbook_filename):
raise exceptions.PlaybookNotFound(playbook_name=playbook_filename)
return playbook_filename
def get_value_from_yaml_file(filename, key):
"""Get corresponding value for a key in the given yaml file.
:param filename: the yaml filename
:param key: the path for the value
Returns the value or None if not found.
"""
value = None
if os.path.isfile(filename):
with open(os.path.abspath(filename), 'r') as f:
data = f.read()
data = yaml.load(data, Loader=yaml.SafeLoader)
value = data.get(key)
return value

View File

@ -218,19 +218,28 @@ class SubcloudManager(manager.Manager):
subcloud_name + postfix)
return ansible_filename
def compose_install_command(self, subcloud_name, ansible_subcloud_inventory_file):
def compose_install_command(self, subcloud_name,
ansible_subcloud_inventory_file,
software_version=None):
install_command = [
"ansible-playbook", ANSIBLE_SUBCLOUD_INSTALL_PLAYBOOK,
"-i", ansible_subcloud_inventory_file,
"--limit", subcloud_name,
"-e", "@%s" % consts.ANSIBLE_OVERRIDES_PATH + "/" +
subcloud_name + '/' + "install_values.yml"]
if software_version and software_version != SW_VERSION:
install_command += [
"-e", "install_release_version=%s" % software_version]
return install_command
def compose_apply_command(self, subcloud_name, ansible_subcloud_inventory_file):
def compose_apply_command(self, subcloud_name,
ansible_subcloud_inventory_file,
software_version=None):
apply_command = [
"ansible-playbook", ANSIBLE_SUBCLOUD_PLAYBOOK, "-i",
ansible_subcloud_inventory_file,
"ansible-playbook",
utils.get_playbook_for_software_version(
ANSIBLE_SUBCLOUD_PLAYBOOK, software_version),
"-i", ansible_subcloud_inventory_file,
"--limit", subcloud_name
]
# Add the overrides dir and region_name so the playbook knows
@ -291,9 +300,13 @@ class SubcloudManager(manager.Manager):
subcloud_name + "_update_values.yml"]
return subcloud_update_command
def compose_rehome_command(self, subcloud_name, ansible_subcloud_inventory_file):
def compose_rehome_command(self, subcloud_name,
ansible_subcloud_inventory_file,
software_version):
rehome_command = [
"ansible-playbook", ANSIBLE_SUBCLOUD_REHOME_PLAYBOOK,
"ansible-playbook",
utils.get_playbook_for_software_version(
ANSIBLE_SUBCLOUD_REHOME_PLAYBOOK, software_version),
"-i", ansible_subcloud_inventory_file,
"--limit", subcloud_name,
"--timeout", REHOME_PLAYBOOK_TIMEOUT,
@ -426,10 +439,6 @@ class SubcloudManager(manager.Manager):
if "install_values" in payload:
payload['install_values']['ansible_ssh_pass'] = \
payload['sysadmin_password']
if 'image' not in payload['install_values']:
matching_iso, matching_sig = utils.get_vault_load_files(
SW_VERSION)
payload['install_values'].update({'image': matching_iso})
deploy_command = None
if "deploy_playbook" in payload:
@ -461,7 +470,8 @@ class SubcloudManager(manager.Manager):
if migrate_flag:
rehome_command = self.compose_rehome_command(
subcloud.name,
ansible_subcloud_inventory_file)
ansible_subcloud_inventory_file,
subcloud.software_version)
apply_thread = threading.Thread(
target=self.run_deploy,
args=(subcloud, payload, context,
@ -471,10 +481,12 @@ class SubcloudManager(manager.Manager):
if "install_values" in payload:
install_command = self.compose_install_command(
subcloud.name,
ansible_subcloud_inventory_file)
ansible_subcloud_inventory_file,
subcloud.software_version)
apply_command = self.compose_apply_command(
subcloud.name,
ansible_subcloud_inventory_file)
ansible_subcloud_inventory_file,
subcloud.software_version)
apply_thread = threading.Thread(
target=self.run_deploy,
args=(subcloud, payload, context,
@ -590,10 +602,12 @@ class SubcloudManager(manager.Manager):
install_command = self.compose_install_command(
subcloud.name,
ansible_subcloud_inventory_file)
ansible_subcloud_inventory_file,
payload['software_version'])
apply_command = self.compose_apply_command(
subcloud.name,
ansible_subcloud_inventory_file)
ansible_subcloud_inventory_file,
payload['software_version'])
apply_thread = threading.Thread(
target=self.run_deploy,
args=(subcloud, payload, context,
@ -958,11 +972,11 @@ class SubcloudManager(manager.Manager):
if payload.get('with_install'):
install_command = self.compose_install_command(
subcloud.name, subcloud_inventory_file)
subcloud.name, subcloud_inventory_file, subcloud.software_version)
# Update data_install with missing data
matching_iso, _ = utils.get_vault_load_files(SW_VERSION)
matching_iso, _ = utils.get_vault_load_files(subcloud.software_version)
data_install['image'] = matching_iso
data_install['software_version'] = SW_VERSION
data_install['software_version'] = subcloud.software_version
data_install['ansible_ssh_pass'] = payload['sysadmin_password']
data_install['ansible_become_pass'] = payload['sysadmin_password']
install_success = self._run_subcloud_install(

View File

@ -27,7 +27,7 @@ from dcmanager.tests import utils
from tsconfig.tsconfig import SW_VERSION
FAKE_RELEASE = '21.12'
FAKE_SOFTWARE_VERSION = '21.12'
FAKE_TENANT = utils.UUID1
FAKE_ID = '1'
FAKE_URL = '/v1.0/subcloud-deploy'
@ -55,7 +55,7 @@ class TestSubcloudDeploy(testroot.DCManagerApiTest):
@mock.patch.object(subcloud_deploy.SubcloudDeployController,
'_upload_files')
def test_post_subcloud_deploy(self, mock_upload_files):
params = [('release_version', FAKE_RELEASE)]
params = [('release', FAKE_SOFTWARE_VERSION)]
fields = list()
for opt in consts.DEPLOY_COMMON_FILE_OPTIONS:
fake_name = opt + "_fake"
@ -67,7 +67,7 @@ class TestSubcloudDeploy(testroot.DCManagerApiTest):
headers=FAKE_HEADERS,
params=params)
self.assertEqual(response.status_code, http_client.OK)
self.assertEqual(FAKE_RELEASE, response.json['release_version'])
self.assertEqual(FAKE_SOFTWARE_VERSION, response.json['software_version'])
@mock.patch.object(subcloud_deploy.SubcloudDeployController,
'_upload_files')
@ -83,7 +83,7 @@ class TestSubcloudDeploy(testroot.DCManagerApiTest):
upload_files=fields)
self.assertEqual(response.status_code, http_client.OK)
# Verify the active release will be returned if release doesn't present
self.assertEqual(SW_VERSION, response.json['release_version'])
self.assertEqual(SW_VERSION, response.json['software_version'])
@mock.patch.object(subcloud_deploy.SubcloudDeployController,
'_upload_files')
@ -206,11 +206,11 @@ class TestSubcloudDeploy(testroot.DCManagerApiTest):
os.path.isdir = mock.Mock(return_value=True)
mock_get_filename_by_prefix.side_effect = \
get_filename_by_prefix_side_effect
url = FAKE_URL + '/' + FAKE_RELEASE
url = FAKE_URL + '/' + FAKE_SOFTWARE_VERSION
response = self.app.get(url, headers=FAKE_HEADERS)
self.assertEqual(response.status_code, http_client.OK)
self.assertEqual(FAKE_RELEASE,
response.json['subcloud_deploy']['release_version'])
self.assertEqual(FAKE_SOFTWARE_VERSION,
response.json['subcloud_deploy']['software_version'])
self.assertEqual(FAKE_DEPLOY_PLAYBOOK_FILE,
response.json['subcloud_deploy'][consts.DEPLOY_PLAYBOOK])
self.assertEqual(FAKE_DEPLOY_OVERRIDES_FILE,
@ -236,7 +236,7 @@ class TestSubcloudDeploy(testroot.DCManagerApiTest):
response = self.app.get(FAKE_URL, headers=FAKE_HEADERS)
self.assertEqual(response.status_code, http_client.OK)
self.assertEqual(SW_VERSION,
response.json['subcloud_deploy']['release_version'])
response.json['subcloud_deploy']['software_version'])
self.assertEqual(FAKE_DEPLOY_PLAYBOOK_FILE,
response.json['subcloud_deploy'][consts.DEPLOY_PLAYBOOK])
self.assertEqual(FAKE_DEPLOY_OVERRIDES_FILE,

View File

@ -40,6 +40,8 @@ from dcmanager.tests.unit.api.v1.controllers.mixins import PostMixin
from dcmanager.tests.unit.common import fake_subcloud
from dcmanager.tests import utils
from tsconfig.tsconfig import SW_VERSION
SAMPLE_SUBCLOUD_NAME = 'SubcloudX'
SAMPLE_SUBCLOUD_DESCRIPTION = 'A Subcloud of mystery'
@ -186,8 +188,6 @@ class SubcloudAPIMixin(APIMixin):
# based off MANDATORY_INSTALL_VALUES
# bmc_password must be passed as a param
FAKE_INSTALL_DATA = {
"image": "fake image",
"software_version": "123.456",
"bootstrap_interface": "fake interface",
"bootstrap_address": "10.10.10.12",
"bootstrap_address_prefix": "10.10.10.12",
@ -494,9 +494,12 @@ class TestSubcloudPost(testroot.DCManagerApiTest,
bad_values,
good_value)
def test_post_subcloud_install_values(self):
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_post_subcloud_install_values(self, mock_vault_files):
"""Test POST operation with install values is supported by the API."""
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
# pass a different "install" list of files for this POST
self.set_list_of_post_files(subclouds.SUBCLOUD_ADD_GET_FILE_CONTENTS)
upload_files = self.get_post_upload_files()
@ -513,6 +516,86 @@ class TestSubcloudPost(testroot.DCManagerApiTest,
headers=self.get_api_headers())
self._verify_post_success(response)
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_post_subcloud_without_release_parameter(self, mock_vault_files):
"""Test POST operation without release parameter."""
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
self.set_list_of_post_files(subclouds.SUBCLOUD_ADD_GET_FILE_CONTENTS)
upload_files = self.get_post_upload_files()
params = self.get_post_params()
# add bmc_password to params
params.update(
{'bmc_password':
base64.b64encode('fake pass'.encode("utf-8")).decode("utf-8")})
response = self.app.post(self.get_api_prefix(),
params=params,
upload_files=upload_files,
headers=self.get_api_headers())
self._verify_post_success(response)
# Verify that the subcloud installed with the active release
# when no release parameter provided.
self.assertEqual(SW_VERSION, response.json['software-version'])
def test_post_subcloud_release_not_match_install_values_sw(self):
"""Release parameter not match software_version in the install_values."""
self.set_list_of_post_files(subclouds.SUBCLOUD_ADD_GET_FILE_CONTENTS)
upload_files = self.get_post_upload_files()
params = self.get_post_params()
# add bmc_password and release to params
params.update(
{'bmc_password':
base64.b64encode('fake pass'.encode("utf-8")).decode("utf-8"),
'release': '21.12'})
response = self.app.post(self.get_api_prefix(),
params=params,
upload_files=upload_files,
headers=self.get_api_headers(),
expect_errors=True)
# Verify the request was rejected
self.assertEqual(response.status_code, http_client.BAD_REQUEST)
@mock.patch.object(subclouds.SubcloudsController, '_validate_k8s_version')
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_post_subcloud_with_release_parameter(self, mock_vault_files,
mock_validate_k8s_version):
"""Test POST operation with release parameter."""
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
software_version = '21.12'
# Update the software_version value to match the release parameter value,
# otherwise, the request will be rejected
self.install_data['software_version'] = software_version
self.set_list_of_post_files(subclouds.SUBCLOUD_ADD_GET_FILE_CONTENTS)
upload_files = self.get_post_upload_files()
params = self.get_post_params()
# add bmc_password and release to params
params.update(
{'bmc_password':
base64.b64encode('fake pass'.encode("utf-8")).decode("utf-8"),
'release': software_version})
response = self.app.post(self.get_api_prefix(),
params=params,
upload_files=upload_files,
headers=self.get_api_headers(),
expect_errors=True)
self.assertEqual(response.status_code, http_client.OK)
self.assertEqual(software_version, response.json['software-version'])
# Revert the software_version value
self.install_data['software_version'] = SW_VERSION
@mock.patch.object(subclouds.PatchingClient, 'query')
def test_post_subcloud_when_partial_applied_patch(self, mock_query):
"""Test POST operation when there is a partial-applied patch."""
@ -528,9 +611,12 @@ class TestSubcloudPost(testroot.DCManagerApiTest,
self.assertEqual(http_client.UNPROCESSABLE_ENTITY, response.status_code)
self.assertEqual('text/plain', response.content_type)
def test_post_subcloud_install_values_no_bmc_password(self):
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_post_subcloud_install_values_no_bmc_password(self, mock_vault_files):
"""Test POST operation with install values is supported by the API."""
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
# pass a different "install" list of files for this POST
self.set_list_of_post_files(subclouds.SUBCLOUD_ADD_GET_FILE_CONTENTS)
upload_files = self.get_post_upload_files()
@ -555,12 +641,33 @@ class TestSubcloudPost(testroot.DCManagerApiTest,
headers=self.get_api_headers())
self._verify_post_success(response)
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_post_subcloud_missing_image(self, mock_vault_files):
"""Test POST operation without image in install values and vault files."""
mock_vault_files.return_value = (None, None)
params = self.get_post_params()
# add bmc_password to params
params.update(
{'bmc_password':
base64.b64encode('fake pass'.encode("utf-8")).decode("utf-8")})
self.set_list_of_post_files(subclouds.SUBCLOUD_ADD_GET_FILE_CONTENTS)
self.install_data = copy.copy(self.FAKE_INSTALL_DATA)
upload_files = self.get_post_upload_files()
response = self.app.post(self.get_api_prefix(),
params=params,
upload_files=upload_files,
headers=self.get_api_headers(),
expect_errors=True)
self.assertEqual(response.status_code, http_client.BAD_REQUEST)
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_post_subcloud_install_values_missing(self, mock_vault_files):
"""Test POST operation with install values fails if data missing."""
# todo(abailey): add a new unit test with no image and no vault files
mock_vault_files.return_value = (None, None)
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
params = self.get_post_params()
# add bmc_password to params
@ -581,15 +688,45 @@ class TestSubcloudPost(testroot.DCManagerApiTest,
expect_errors=True)
self._verify_post_failure(response, key, None)
@mock.patch('dcmanager.common.utils.get_vault_load_files')
@mock.patch.object(cutils, 'get_playbook_for_software_version')
@mock.patch.object(cutils, 'get_value_from_yaml_file')
def test_post_subcloud_bad_kubernetes_version(self,
mock_get_value_from_yaml_file,
mock_get_playbook_for_software_version,
mock_vault_files):
"""Test POST operation with bad kubernetes_version."""
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
# try with nothing removed and verify it works
software_version = '21.12'
# Update the software_version value to match the release parameter value,
# otherwise, the request will be rejected
self.install_data['software_version'] = software_version
params = self.get_post_params()
# add bmc_password to params
params.update(
{'bmc_password':
base64.b64encode('fake pass'.encode("utf-8")).decode("utf-8"),
'release': software_version})
# Add kubernetes version to bootstrap_data
self.bootstrap_data['kubernetes_version'] = '1.21.8'
mock_get_value_from_yaml_file.return_value = '1.23.1'
self.set_list_of_post_files(subclouds.SUBCLOUD_ADD_GET_FILE_CONTENTS)
self.install_data = copy.copy(self.FAKE_INSTALL_DATA)
upload_files = self.get_post_upload_files()
response = self.app.post(self.get_api_prefix(),
params=params,
upload_files=upload_files,
headers=self.get_api_headers())
self._verify_post_success(response)
headers=self.get_api_headers(),
expect_errors=True)
self.assertEqual(response.status_code, http_client.BAD_REQUEST)
# Revert the change of bootstrap_data
del self.bootstrap_data['kubernetes_version']
def _test_post_input_value_inputs(self,
setup_overrides,
@ -614,6 +751,7 @@ class TestSubcloudPost(testroot.DCManagerApiTest,
starting_data = copy.copy(self.FAKE_INSTALL_DATA)
for key, val in setup_overrides.items():
starting_data[key] = val
starting_data['image'] = 'fake image'
# Test all the bad param values
for bad_value in bad_values:
@ -661,9 +799,11 @@ class TestSubcloudPost(testroot.DCManagerApiTest,
headers=self.get_api_headers())
self._verify_post_success(response)
def test_post_subcloud_install_values_invalid_type(self):
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_post_subcloud_install_values_invalid_type(self, mock_vault_files):
"""Test POST with an invalid type specified in install values."""
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
setup_overrides = {}
required_overrides = {}
# the install_type must a number 0 <= X <=5
@ -679,9 +819,11 @@ class TestSubcloudPost(testroot.DCManagerApiTest,
self._test_post_input_value_inputs(setup_overrides, required_overrides,
install_key, bad_values, good_value)
def test_post_subcloud_install_bad_bootstrap_ip(self):
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_post_subcloud_install_bad_bootstrap_ip(self, mock_vault_files):
"""Test POST with invalid boostrap ip specified in install values."""
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
setup_overrides = {}
required_overrides = {}
install_key = "bootstrap_address"
@ -694,9 +836,11 @@ class TestSubcloudPost(testroot.DCManagerApiTest,
self._test_post_input_value_inputs(setup_overrides, required_overrides,
install_key, bad_values, good_value)
def test_post_subcloud_install_bad_bmc_ip(self):
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_post_subcloud_install_bad_bmc_ip(self, mock_vault_files):
"""Test POST with invalid bmc ip specified in install values."""
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
setup_overrides = {}
required_overrides = {}
install_key = "bmc_address"
@ -708,9 +852,11 @@ class TestSubcloudPost(testroot.DCManagerApiTest,
self._test_post_input_value_inputs(setup_overrides, required_overrides,
install_key, bad_values, good_value)
def test_post_subcloud_install_bad_persistent_size(self):
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_post_subcloud_install_bad_persistent_size(self, mock_vault_files):
"""Test POST with invalid persistent_size specified in install values."""
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
setup_overrides = {}
required_overrides = {}
install_key = "persistent_size"
@ -723,9 +869,11 @@ class TestSubcloudPost(testroot.DCManagerApiTest,
self._test_post_input_value_inputs(setup_overrides, required_overrides,
install_key, bad_values, good_value)
def test_post_subcloud_install_bad_nexthop_gateway(self):
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_post_subcloud_install_bad_nexthop_gateway(self, mock_vault_files):
"""Test POST with invalid nexthop_gateway in install values."""
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
setup_overrides = {}
required_overrides = {}
# nexthop_gateway is not required. but if provided, it must be valid
@ -738,9 +886,11 @@ class TestSubcloudPost(testroot.DCManagerApiTest,
self._test_post_input_value_inputs(setup_overrides, required_overrides,
install_key, bad_values, good_value)
def test_post_subcloud_install_bad_network_address(self):
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_post_subcloud_install_bad_network_address(self, mock_vault_files):
"""Test POST with invalid network_address in install values."""
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
setup_overrides = {}
# The nexthop_gateway is required when network_address is present
# The network mask is required when network address is present
@ -757,9 +907,11 @@ class TestSubcloudPost(testroot.DCManagerApiTest,
self._test_post_input_value_inputs(setup_overrides, required_overrides,
install_key, bad_values, good_value)
def test_post_subcloud_install_bad_network_mask(self):
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_post_subcloud_install_bad_network_mask(self, mock_vault_files):
"""Test POST with invalid network_mask in install values."""
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
# network_address is not required. but if provided a valid network_mask
# is needed
setup_overrides = {
@ -778,9 +930,11 @@ class TestSubcloudPost(testroot.DCManagerApiTest,
self._test_post_input_value_inputs(setup_overrides, required_overrides,
install_key, bad_values, good_value)
def test_post_subcloud_install_diff_bmc_ip_version(self):
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_post_subcloud_install_diff_bmc_ip_version(self, mock_vault_files):
"""Test POST install values with mismatched(ipv4/ipv6) bmc ip."""
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
setup_overrides = {
"bootstrap_address": "192.168.1.2"
}
@ -795,9 +949,11 @@ class TestSubcloudPost(testroot.DCManagerApiTest,
self._test_post_input_value_inputs(setup_overrides, required_overrides,
install_key, bad_values, good_value)
def test_post_subcloud_install_diff_bmc_ip_version_ipv6(self):
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_post_subcloud_install_diff_bmc_ip_version_ipv6(self, mock_vault_files):
"""Test POST install values with mismatched(ipv6/ipv4) bmc ip."""
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
# version of bootstrap address must be same as bmc_address
setup_overrides = {
"bootstrap_address": "fd01:6::7"
@ -812,9 +968,11 @@ class TestSubcloudPost(testroot.DCManagerApiTest,
self._test_post_input_value_inputs(setup_overrides, required_overrides,
install_key, bad_values, good_value)
def test_post_subcloud_install_diff_nexthop_ip_version(self):
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_post_subcloud_install_diff_nexthop_ip_version(self, mock_vault_files):
"""Test POST install values mismatched(ipv4/ipv6) nexthop_gateway."""
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
# ip version of bootstrap address must be same as nexthop_gateway
# All required addresses (like bmc address) much match bootstrap
# default bmc address is ipv4
@ -828,9 +986,12 @@ class TestSubcloudPost(testroot.DCManagerApiTest,
self._test_post_input_value_inputs(setup_overrides, required_overrides,
install_key, bad_values, good_value)
def test_post_subcloud_install_diff_nexthop_ip_version_ipv6(self):
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_post_subcloud_install_diff_nexthop_ip_version_ipv6(self,
mock_vault_files):
"""Test POST install values with mismatched(ipv6/ipv4) bmc ip."""
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
# version of bootstrap address must be same as nexthop_gateway
# All required addresses must also be setup ipv6 such as bmc_address
# default bmc address is ipv4
@ -1037,9 +1198,11 @@ class TestSubcloudAPIOther(testroot.DCManagerApiTest):
@mock.patch.object(rpc_client, 'ManagerClient')
@mock.patch.object(subclouds.SubcloudsController, '_get_patch_data')
def test_update_subcloud_install_values_persistent_size(self,
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_update_subcloud_install_values_persistent_size(self, mock_vault_files,
mock_get_patch_data,
mock_rpc_client):
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
subcloud = fake_subcloud.create_fake_subcloud(self.ctx, data_install=None)
payload = {}
install_data = copy.copy(FAKE_SUBCLOUD_INSTALL_VALUES_WITH_PERSISTENT_SIZE)
@ -1102,8 +1265,11 @@ class TestSubcloudAPIOther(testroot.DCManagerApiTest):
@mock.patch.object(rpc_client, 'ManagerClient')
@mock.patch.object(subclouds.SubcloudsController, '_get_patch_data')
def test_patch_subcloud_install_values(self, mock_get_patch_data,
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_patch_subcloud_install_values(self, mock_vault_files,
mock_get_patch_data,
mock_rpc_client):
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
subcloud = fake_subcloud.create_fake_subcloud(self.ctx, data_install=None)
payload = {}
install_data = copy.copy(FAKE_SUBCLOUD_INSTALL_VALUES)
@ -1136,12 +1302,14 @@ class TestSubcloudAPIOther(testroot.DCManagerApiTest):
@mock.patch.object(rpc_client, 'ManagerClient')
@mock.patch.object(subclouds.SubcloudsController, '_get_patch_data')
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_patch_subcloud_install_values_with_existing_data_install(
self, mock_get_patch_data, mock_rpc_client):
self, mock_vault_files, mock_get_patch_data, mock_rpc_client):
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
install_data = copy.copy(FAKE_SUBCLOUD_INSTALL_VALUES)
subcloud = fake_subcloud.create_fake_subcloud(
self.ctx, data_install=json.dumps(install_data))
install_data.update({"software_version": "18.04"})
install_data.update({"install_type": 2})
payload = {}
encoded_password = base64.b64encode(
'bmc_password'.encode("utf-8")).decode('utf-8')
@ -1455,8 +1623,10 @@ class TestSubcloudAPIOther(testroot.DCManagerApiTest):
@mock.patch.object(subclouds.SubcloudsController, '_get_subcloud_db_install_values')
@mock.patch.object(subclouds.SubcloudsController, '_validate_oam_network_config')
@mock.patch.object(subclouds.SubcloudsController, '_get_request_data')
@mock.patch.object(subclouds.SubcloudsController, '_upload_deploy_config_file')
def test_reinstall_subcloud(
self, mock_get_request_data, mock_validate_oam_network_config,
self, mock_upload_deploy_config_file,
mock_get_request_data, mock_validate_oam_network_config,
mock_get_subcloud_db_install_values, mock_rpc_client,
mock_get_vault_load_files):
@ -1485,6 +1655,56 @@ class TestSubcloudAPIOther(testroot.DCManagerApiTest):
mock.ANY)
self.assertEqual(response.status_int, 200)
mock_upload_deploy_config_file.assert_called_once()
self.assertEqual(SW_VERSION, response.json['software-version'])
@mock.patch.object(cutils, 'get_vault_load_files')
@mock.patch.object(rpc_client, 'ManagerClient')
@mock.patch.object(subclouds.SubcloudsController,
'_get_subcloud_db_install_values')
@mock.patch.object(subclouds.SubcloudsController, '_validate_oam_network_config')
@mock.patch.object(subclouds.SubcloudsController, '_get_request_data')
@mock.patch.object(subclouds.SubcloudsController, '_upload_deploy_config_file')
@mock.patch.object(subclouds.SubcloudsController, '_validate_k8s_version')
def test_reinstall_subcloud_with_release_parameter(
self, mock_validate_k8s_version, mock_upload_deploy_config_file,
mock_get_request_data, mock_validate_oam_network_config,
mock_get_subcloud_db_install_values, mock_rpc_client,
mock_get_vault_load_files):
software_version = '21.12'
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
install_data = copy.copy(FAKE_SUBCLOUD_INSTALL_VALUES)
reinstall_data = copy.copy(FAKE_SUBCLOUD_BOOTSTRAP_PAYLOAD)
reinstall_data['release'] = software_version
mock_get_request_data.return_value = reinstall_data
encoded_password = base64.b64encode(
'bmc_password'.encode("utf-8")).decode('utf-8')
bmc_password = {'bmc_password': encoded_password}
install_data.update(bmc_password)
mock_get_subcloud_db_install_values.return_value = install_data
mock_rpc_client().reinstall_subcloud.return_value = True
mock_get_vault_load_files.return_value = ('iso_file_path', 'sig_file_path')
response = self.app.patch_json(
FAKE_URL + '/' + str(subcloud.id) + '/reinstall',
headers=FAKE_HEADERS, params=reinstall_data)
mock_validate_oam_network_config.assert_called_once()
mock_rpc_client().reinstall_subcloud.assert_called_once_with(
mock.ANY,
subcloud.id,
mock.ANY)
self.assertEqual(response.status_int, 200)
mock_validate_k8s_version.assert_called_once()
mock_upload_deploy_config_file.assert_called_once()
self.assertEqual(software_version, response.json['software-version'])
self.assertIn(software_version,
json.loads(response.json['data_install'])['software_version'])
@mock.patch.object(cutils, 'get_vault_load_files')
@mock.patch.object(rpc_client, 'ManagerClient')
@mock.patch.object(subclouds.SubcloudsController, '_get_subcloud_db_install_values')

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2020-2022 Wind River Systems, Inc.
# Copyright (c) 2020-2023 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@ -16,6 +16,8 @@ FAKE_ID = '1'
FAKE_URL = '/v1.0/subclouds'
WRONG_URL = '/v1.0/wrong'
FAKE_SOFTWARE_VERSION = '18.03'
FAKE_HEADERS = {'X-Tenant-Id': FAKE_TENANT, 'X_ROLE': 'admin,member,reader',
'X-Identity-Status': 'Confirmed', 'X-Project-Name': 'admin'}
@ -61,7 +63,7 @@ FAKE_SUBCLOUD_BOOTSTRAP_PAYLOAD = {
FAKE_SUBCLOUD_INSTALL_VALUES = {
"image": "http://192.168.101.2:8080/iso/bootimage.iso",
"software_version": "18.03",
"software_version": FAKE_SOFTWARE_VERSION,
"bootstrap_interface": "eno1",
"bootstrap_address": "128.224.151.183",
"bootstrap_address_prefix": 23,
@ -81,7 +83,7 @@ FAKE_SUBCLOUD_INSTALL_VALUES = {
FAKE_SUBCLOUD_INSTALL_VALUES_WITH_PERSISTENT_SIZE = {
"image": "http://192.168.101.2:8080/iso/bootimage.iso",
"software_version": "18.03",
"software_version": FAKE_SOFTWARE_VERSION,
"bootstrap_interface": "eno1",
"bootstrap_address": "128.224.151.183",
"bootstrap_address_prefix": 23,
@ -105,7 +107,7 @@ def create_fake_subcloud(ctxt, **kwargs):
"name": "subcloud1",
"description": "subcloud1 description",
"location": "subcloud1 location",
'software_version': "18.03",
'software_version': FAKE_SOFTWARE_VERSION,
"management_subnet": "192.168.101.0/24",
"management_gateway_ip": "192.168.101.1",
"management_start_ip": "192.168.101.2",

View File

@ -14,6 +14,7 @@
import copy
import datetime
import mock
from os import path as os_path
@ -40,6 +41,7 @@ from dcmanager.tests import utils
from tsconfig.tsconfig import SW_VERSION
LAST_SW_VERSION_IN_CENTOS = "22.06"
FAKE_PREVIOUS_SW_VERSION = '21.12'
FAKE_ADMIN_USER_ID = 1
@ -408,6 +410,8 @@ class TestSubcloudManager(base.DCManagerTestCase):
self.assertEqual('localhost', sm.host)
self.assertEqual(self.ctx, sm.context)
@mock.patch.object(subcloud_manager.SubcloudManager,
'compose_apply_command')
@mock.patch.object(subcloud_manager.SubcloudManager,
'compose_rehome_command')
@mock.patch.object(subcloud_manager.SubcloudManager,
@ -435,12 +439,13 @@ class TestSubcloudManager(base.DCManagerTestCase):
mock_keystone_client,
mock_delete_subcloud_inventory,
mock_create_intermediate_ca_cert,
mock_compose_rehome_command):
mock_compose_rehome_command,
mock_compose_apply_command):
values = utils.create_subcloud_dict(base.SUBCLOUD_SAMPLE_DATA_0)
values['deploy_status'] = consts.DEPLOY_STATE_NONE
# dcmanager add_subcloud queries the data from the db
self.create_subcloud_static(self.ctx, name=values['name'])
subcloud = self.create_subcloud_static(self.ctx, name=values['name'])
mock_keystone_client().keystone_client = FakeKeystoneClient()
mock_keyring.get_password.return_value = "testpassword"
@ -458,6 +463,10 @@ class TestSubcloudManager(base.DCManagerTestCase):
mock_thread_start.assert_called_once()
mock_create_intermediate_ca_cert.assert_called_once()
mock_compose_rehome_command.assert_not_called()
mock_compose_apply_command.assert_called_once_with(
values['name'],
sm._get_ansible_filename(values['name'], consts.INVENTORY_FILE_POSTFIX),
subcloud['software_version'])
# Verify subcloud was updated with correct values
self.assertEqual(consts.DEPLOY_STATE_PRE_DEPLOY,
@ -502,7 +511,7 @@ class TestSubcloudManager(base.DCManagerTestCase):
values['migrate'] = 'true'
# dcmanager add_subcloud queries the data from the db
self.create_subcloud_static(self.ctx, name=values['name'])
subcloud = self.create_subcloud_static(self.ctx, name=values['name'])
mock_keystone_client().keystone_client = FakeKeystoneClient()
mock_keyring.get_password.return_value = "testpassword"
@ -518,7 +527,10 @@ class TestSubcloudManager(base.DCManagerTestCase):
mock_write_subcloud_ansible_config.assert_called_once()
mock_thread_start.assert_called_once()
mock_create_intermediate_ca_cert.assert_called_once()
mock_compose_rehome_command.assert_called_once()
mock_compose_rehome_command.assert_called_once_with(
values['name'],
sm._get_ansible_filename(values['name'], consts.INVENTORY_FILE_POSTFIX),
subcloud['software_version'])
# Verify subcloud was updated with correct values
self.assertEqual(consts.DEPLOY_STATE_PRE_REHOME,
@ -1379,7 +1391,9 @@ class TestSubcloudManager(base.DCManagerTestCase):
def test_compose_install_command(self):
sm = subcloud_manager.SubcloudManager()
install_command = sm.compose_install_command(
'subcloud1', '/var/opt/dc/ansible/subcloud1_inventory.yml')
'subcloud1',
'/var/opt/dc/ansible/subcloud1_inventory.yml',
FAKE_PREVIOUS_SW_VERSION)
self.assertEqual(
install_command,
[
@ -1387,20 +1401,27 @@ class TestSubcloudManager(base.DCManagerTestCase):
subcloud_manager.ANSIBLE_SUBCLOUD_INSTALL_PLAYBOOK,
'-i', '/var/opt/dc/ansible/subcloud1_inventory.yml',
'--limit', 'subcloud1',
'-e', "@/var/opt/dc/ansible/subcloud1/install_values.yml"
'-e', "@/var/opt/dc/ansible/subcloud1/install_values.yml",
'-e', "install_release_version=%s" % FAKE_PREVIOUS_SW_VERSION
]
)
def test_compose_apply_command(self):
@mock.patch('os.path.isfile')
def test_compose_apply_command(self, mock_isfile):
mock_isfile.return_value = True
sm = subcloud_manager.SubcloudManager()
apply_command = sm.compose_apply_command(
'subcloud1', '/var/opt/dc/ansible/subcloud1_inventory.yml')
'subcloud1',
'/var/opt/dc/ansible/subcloud1_inventory.yml',
FAKE_PREVIOUS_SW_VERSION)
self.assertEqual(
apply_command,
[
'ansible-playbook',
subcloud_manager.ANSIBLE_SUBCLOUD_PLAYBOOK, '-i',
'/var/opt/dc/ansible/subcloud1_inventory.yml',
cutils.get_playbook_for_software_version(
subcloud_manager.ANSIBLE_SUBCLOUD_PLAYBOOK,
FAKE_PREVIOUS_SW_VERSION),
'-i', '/var/opt/dc/ansible/subcloud1_inventory.yml',
'--limit', 'subcloud1', '-e',
"override_files_dir='/var/opt/dc/ansible' region_name=subcloud1"
]
@ -1427,16 +1448,22 @@ class TestSubcloudManager(base.DCManagerTestCase):
]
)
def test_compose_rehome_command(self):
@mock.patch('os.path.isfile')
def test_compose_rehome_command(self, mock_isfile):
mock_isfile.return_value = True
sm = subcloud_manager.SubcloudManager()
rehome_command = sm.compose_rehome_command(
'subcloud1', '/var/opt/dc/ansible/subcloud1_inventory.yml')
'subcloud1',
'/var/opt/dc/ansible/subcloud1_inventory.yml',
FAKE_PREVIOUS_SW_VERSION)
self.assertEqual(
rehome_command,
[
'ansible-playbook',
subcloud_manager.ANSIBLE_SUBCLOUD_REHOME_PLAYBOOK, '-i',
'/var/opt/dc/ansible/subcloud1_inventory.yml',
cutils.get_playbook_for_software_version(
subcloud_manager.ANSIBLE_SUBCLOUD_REHOME_PLAYBOOK,
FAKE_PREVIOUS_SW_VERSION),
'-i', '/var/opt/dc/ansible/subcloud1_inventory.yml',
'--limit', 'subcloud1',
'--timeout', subcloud_manager.REHOME_PLAYBOOK_TIMEOUT,
'-e',
@ -1463,9 +1490,10 @@ class TestSubcloudManager(base.DCManagerTestCase):
mock_compose_apply_command, mock_compose_install_command,
mock_create_intermediate_ca_cert, mock_write_subcloud_ansible_config):
subcloud_name = 'subcloud1'
subcloud = self.create_subcloud_static(
self.ctx,
name='subcloud1',
name=subcloud_name,
deploy_status=consts.DEPLOY_STATE_PRE_INSTALL)
fake_install_values = \
@ -1474,7 +1502,7 @@ class TestSubcloudManager(base.DCManagerTestCase):
fake_payload = copy.copy(fake_subcloud.FAKE_SUBCLOUD_BOOTSTRAP_PAYLOAD)
fake_payload.update({
'bmc_password': 'bmc_pass',
'software_version': SW_VERSION,
'software_version': FAKE_PREVIOUS_SW_VERSION,
'install_values': fake_install_values})
sm = subcloud_manager.SubcloudManager()
@ -1487,8 +1515,14 @@ class TestSubcloudManager(base.DCManagerTestCase):
mock_create_subcloud_inventory.assert_called_once()
mock_create_intermediate_ca_cert.assert_called_once()
mock_write_subcloud_ansible_config.assert_called_once()
mock_compose_install_command.assert_called_once()
mock_compose_apply_command.assert_called_once()
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_compose_apply_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()
def test_handle_subcloud_operations_in_progress(self):