Add subcloud redeploy option to dcmanager

This commit adds the command "subcloud redeploy" to dcmanager.
The redeploy operation is similar to "subcloud reinstall",
performing a fresh install, bootstrapping and configuring the
subcloud, but allowing the user to use either previously used
install/bootstrap/config values stored on the system controller
or new values from provided files. Since config is an optional
phase, it will only be executed if respective parameters are
provided in the current request or were provided in a previous
deployment.

Test Plan:
  Success cases:
    - PASS: Redeploy subcloud without passing any new files and
            verify the redeployment was successful and the final
            deploy state is "complete".
    - PASS: Redeploy subcloud passing new install/bootstrap/config
            files and verify the redeployment was successful and
            the final deploy state is "complete".
    - PASS: Redeploy a subcloud with a different management
            subnet and verify that the network reconfiguration
            was executed during the bootstrap phase and the
            redeployment completed successfully.
    - PASS: Redeploy a subcloud that wasn't configure by the
            "deploy config" command passing a config file and
            verify that the subcloud was redeploy and configured.
    - PASS: Redeploy a subcloud that wasn't configure by the
            "deploy config" command without passing a config file.
            and verify that the subcloud was redeployed and no
            configuration attempt was made.
    - PASS: Redeploy a subcloud passing a previous release (21.12)
            and verify the redeployment was successful and the final
            deploy state is "complete".
    - PASS: Abort each one of the three deployment phases. Verify the
            deployment was successfully aborted.
    - PASS: Resume the aborted deployment and verify the subcloud was
            successfully deployed.
    - PASS: Repeat previous tests but directly call the API (using
            CURL) instead of using the CLI.

  Failure cases:
    - PASS: Verify it's not possible to redeploy an online and/or
            managed subcloud.
    - PASS: Call the API directly, passing bmc-password and/or
            sysadmin-password as plain text as opposed to b64encoded
            and verify that the response contains the correct error
            code and message.

Story: 2010756
Task: 48496

Change-Id: I6148096909adda2b95b6bb964bc7a749ac62c20c
Signed-off-by: Victor Romano <victor.gluzromano@windriver.com>
This commit is contained in:
Victor Romano 2023-07-24 09:37:53 -03:00
parent 8b2855a7d2
commit 528f90afec
13 changed files with 688 additions and 49 deletions

View File

@ -541,6 +541,78 @@ Response Example
.. literalinclude:: samples/subclouds/subcloud-patch-reinstall-response.json
:language: json
********************************
Redeploy a specific subcloud
********************************
.. rest_method:: PATCH /v1.0/subclouds/{subcloud}/redeploy
Redeploy and bootstrap a subcloud based on its previous install configurations.
**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
- install_values: install_values
- bootstrap_values: bootstrap_values
- deploy_config: deploy_config
- release: release
- sysadmin_password: sysadmin_password
- bmc_password: bmc_password
Request Example
----------------
.. literalinclude:: samples/subclouds/subcloud-patch-redeploy-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/subclouds/subcloud-patch-redeploy-response.json
:language: json
********************************
Prestage a specific subcloud
********************************

View File

@ -0,0 +1,8 @@
{
"bmc_password": "YYYYYYY",
"bootstrap_values": "content of bootstrap_values file",
"deploy_config": "content of deploy_config file",
"install_values": "content of install_values file",
"release": "22.12",
"sysadmin_password": "XXXXXXX"
}

View File

@ -0,0 +1,25 @@
{
"id": 1,
"name": "subcloud1",
"created-at": "2021-11-08T18:41:19.530228",
"updated-at": "2021-11-15T14:15:59.944851",
"availability-status": "offline",
"data_install": {
"bootstrap_interface": "eno1"
},
"data_upgrade": null,
"deploy-status": "pre-install",
"backup-status": "complete",
"backup-datetime": "2022-07-08 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": "22.12",
"systemcontroller-gateway-ip": "192.168.204.101"
}

View File

@ -23,6 +23,7 @@ from requests_toolbelt.multipart import decoder
import base64
import json
import keyring
import os
from oslo_config import cfg
from oslo_log import log as logging
from oslo_messaging import RemoteError
@ -77,6 +78,12 @@ SUBCLOUD_ADD_GET_FILE_CONTENTS = [
INSTALL_VALUES,
]
SUBCLOUD_REDEPLOY_GET_FILE_CONTENTS = [
INSTALL_VALUES,
BOOTSTRAP_VALUES,
consts.DEPLOY_CONFIG
]
BOOTSTRAP_VALUES_ADDRESSES = [
'bootstrap-address', 'management_start_address', 'management_end_address',
'management_gateway_address', 'systemcontroller_gateway_address',
@ -761,6 +768,70 @@ class SubcloudsController(object):
except Exception:
LOG.exception("Unable to reinstall subcloud %s" % subcloud.name)
pecan.abort(500, _('Unable to reinstall subcloud'))
elif verb == "redeploy":
config_file = psd_common.get_config_file_path(subcloud.name,
consts.DEPLOY_CONFIG)
has_bootstrap_values = consts.BOOTSTRAP_VALUES in request.POST
has_original_config_values = os.path.exists(config_file)
has_new_config_values = consts.DEPLOY_CONFIG in request.POST
has_config_values = has_original_config_values or has_new_config_values
payload = psd_common.get_request_data(
request, subcloud, SUBCLOUD_REDEPLOY_GET_FILE_CONTENTS)
if (subcloud.availability_status == dccommon_consts.AVAILABILITY_ONLINE or
subcloud.management_state == dccommon_consts.MANAGEMENT_MANAGED):
msg = _('Cannot re-deploy an online and/or managed subcloud')
LOG.warning(msg)
pecan.abort(400, msg)
# If a subcloud release is not passed, use the current
# system controller software_version
payload['software_version'] = payload.get('release', tsc.SW_VERSION)
# Don't load previously stored bootstrap_values if they are present in
# the request, as this would override the already loaded values from it.
# As config_values are optional, only attempt to load previously stored
# values if this phase should be executed.
files_for_redeploy = SUBCLOUD_REDEPLOY_GET_FILE_CONTENTS.copy()
if has_bootstrap_values:
files_for_redeploy.remove(BOOTSTRAP_VALUES)
if not has_config_values:
files_for_redeploy.remove(consts.DEPLOY_CONFIG)
psd_common.populate_payload_with_pre_existing_data(
payload, subcloud, files_for_redeploy)
psd_common.validate_sysadmin_password(payload)
psd_common.pre_deploy_install(payload, validate_password=False)
psd_common.pre_deploy_bootstrap(context, payload, subcloud,
has_bootstrap_values,
validate_password=False)
payload['bootstrap-address'] = \
payload['install_values']['bootstrap_address']
try:
# Align the software version of the subcloud with redeploy
# version. Update description, location and group id if offered,
# update the deploy status as pre-install.
subcloud = db_api.subcloud_update(
context,
subcloud_id,
description=payload.get('description'),
location=payload.get('location'),
software_version=payload['software_version'],
deploy_status=consts.DEPLOY_STATE_PRE_INSTALL,
first_identity_sync_complete=False,
data_install=json.dumps(payload['install_values']))
self.dcmanager_rpc_client.redeploy_subcloud(
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 redeploy subcloud %s" % subcloud.name)
pecan.abort(500, _('Unable to redeploy subcloud'))
elif verb == "restore":
pecan.abort(410, _('This API is deprecated. '
'Please use /v1.0/subcloud-backup/restore'))

View File

@ -73,6 +73,10 @@ subclouds_rules = [
'method': 'PATCH',
'path': '/v1.0/subclouds/{subcloud}/reinstall'
},
{
'method': 'PATCH',
'path': '/v1.0/subclouds/{subcloud}/redeploy'
},
{
'method': 'PATCH',
'path': '/v1.0/subclouds/{subcloud}/restore'

View File

@ -491,9 +491,9 @@ def validate_install_values(payload, subcloud=None):
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.") %
"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))
else:
# Only install_values payload will be passed to the subcloud
@ -959,7 +959,7 @@ def pre_deploy_install(payload: dict, validate_password=False):
# 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.
# will be installed using the image in dc_vault.
if install_values.get(
'software_version') != payload['software_version']:
install_values['software_version'] = payload['software_version']

View File

@ -133,7 +133,8 @@ def validate_network_str(network_str, minimum_size, existing_networks=None,
"least %d addresses" % minimum_size)
elif network.version == 6 and network.prefixlen < 64:
raise exceptions.ValidateFail("IPv6 minimum prefix length is 64")
elif existing_networks and operation != 'reinstall':
elif existing_networks and (operation != 'reinstall'
and operation != 'redeploy'):
if any(network.ip in subnet for subnet in existing_networks):
raise exceptions.ValidateFail("Subnet overlaps with another "
"configured subnet")
@ -943,12 +944,14 @@ def has_network_reconfig(payload, subcloud):
start_address = get_management_start_address(payload)
end_address = get_management_end_address(payload)
gateway_address = get_management_gateway_address(payload)
sys_controller_gw_ip = payload.get("systemcontroller_gateway_address")
has_network_reconfig = any([
management_subnet != subcloud.management_subnet,
start_address != subcloud.management_start_ip,
end_address != subcloud.management_end_ip,
gateway_address != subcloud.management_gateway_ip
gateway_address != subcloud.management_gateway_ip,
sys_controller_gw_ip != subcloud.systemcontroller_gateway_ip
])
return has_network_reconfig

View File

@ -144,11 +144,19 @@ class DCManagerService(service.Service):
@request_context
def reinstall_subcloud(self, context, subcloud_id, payload):
# Reinstall a subcloud
LOG.info("Handling reinstall_subcloud request for: %s" % subcloud_id)
LOG.info("Handling reinstall_subcloud request for: %s" % payload.get('name'))
return self.subcloud_manager.reinstall_subcloud(context,
subcloud_id,
payload)
@request_context
def redeploy_subcloud(self, context, subcloud_id, payload):
# Redeploy a subcloud
LOG.info("Handling redeploy_subcloud request for: %s" % subcloud_id)
return self.subcloud_manager.redeploy_subcloud(context,
subcloud_id,
payload)
@request_context
def backup_subclouds(self, context, payload):
# Backup a subcloud or group of subclouds

View File

@ -508,6 +508,35 @@ class SubcloudManager(manager.Manager):
context, subcloud_id,
deploy_status=consts.DEPLOY_STATE_PRE_INSTALL_FAILED)
def redeploy_subcloud(self, context, subcloud_id, payload):
"""Redeploy subcloud
:param context: request context object
:param subcloud_id: subcloud id from db
:param payload: subcloud redeploy
"""
# Retrieve the subcloud details from the database
subcloud = db_api.subcloud_get(context, subcloud_id)
LOG.info("Redeploying subcloud %s." % subcloud.name)
# Define which deploy phases to run
phases_to_run = [consts.DEPLOY_PHASE_INSTALL,
consts.DEPLOY_PHASE_BOOTSTRAP]
if consts.DEPLOY_CONFIG in payload:
phases_to_run.append(consts.DEPLOY_PHASE_CONFIG)
succeeded = self.run_deploy_phases(context, subcloud_id, payload,
phases_to_run)
if succeeded:
db_api.subcloud_update(
context, subcloud.id,
deploy_status=consts.DEPLOY_STATE_DONE,
error_description=consts.ERROR_DESC_EMPTY)
LOG.info(f"Finished redeploying subcloud {subcloud['name']}.")
def create_subcloud_backups(self, context, payload):
"""Backup subcloud or group of subclouds
@ -637,29 +666,18 @@ class SubcloudManager(manager.Manager):
:param ansible_subcloud_inventory_file: the ansible inventory file path
:return: ansible command needed to run the bootstrap playbook
"""
management_subnet = utils.get_management_subnet(payload)
sys_controller_gw_ip = payload.get(
"systemcontroller_gateway_address")
if (management_subnet != subcloud.management_subnet) or (
sys_controller_gw_ip != subcloud.systemcontroller_gateway_ip):
m_ks_client = OpenStackDriver(
region_name=dccommon_consts.DEFAULT_REGION_NAME,
region_clients=None).keystone_client
# Create a new route
self._create_subcloud_route(payload, m_ks_client,
sys_controller_gw_ip)
# Delete previous route
self._delete_subcloud_routes(m_ks_client, subcloud)
# Update endpoints
self._update_services_endpoint(context, payload, subcloud.name,
m_ks_client)
network_reconfig = utils.has_network_reconfig(payload, subcloud)
if network_reconfig:
self._configure_system_controller_network(context, payload, subcloud,
update_db=False)
# Regenerate the addn_hosts_dc file
self._create_addn_hosts_dc(context)
# Update subcloud
subcloud = db_api.subcloud_update(
context,
subcloud.id,
description=payload.get("description", None),
description=payload.get("description"),
management_subnet=utils.get_management_subnet(payload),
management_gateway_ip=utils.get_management_gateway_address(
payload),
@ -667,8 +685,8 @@ class SubcloudManager(manager.Manager):
payload),
management_end_ip=utils.get_management_end_address(payload),
systemcontroller_gateway_ip=payload.get(
"systemcontroller_gateway_address", None),
location=payload.get("location", None),
"systemcontroller_gateway_address"),
location=payload.get("location"),
deploy_status=consts.DEPLOY_STATE_PRE_BOOTSTRAP)
# Populate payload with passwords
@ -959,8 +977,7 @@ class SubcloudManager(manager.Manager):
deploy_status=deploy_state)
# The RPC call must return the subcloud as a dictionary, otherwise it
# should return the DB object for dcmanager internal use (subcloud add,
# resume and redeploy)
# should return the DB object for dcmanager internal use (subcloud add)
if return_as_dict:
subcloud = db_api.subcloud_db_model_to_dict(subcloud)
@ -2308,35 +2325,47 @@ class SubcloudManager(manager.Manager):
# Regenerate the addn_hosts_dc file
self._create_addn_hosts_dc(context)
def _configure_system_controller_network(self, context, payload, subcloud):
def _configure_system_controller_network(self, context, payload, subcloud,
update_db=True):
"""Configure system controller network
:param context: request context object
:param payload: subcloud bootstrap configuration
:param subcloud: subcloud model object
:param update_db: whether it should update the db on success/failure
"""
subcloud_name = subcloud.name
subcloud_id = subcloud.id
sys_controller_gw_ip = payload.get("systemcontroller_gateway_address",
subcloud.systemcontroller_gateway_ip)
try:
m_ks_client = OpenStackDriver(
region_name=dccommon_consts.DEFAULT_REGION_NAME,
region_clients=None).keystone_client
self._create_subcloud_route(payload, m_ks_client,
subcloud.systemcontroller_gateway_ip)
sys_controller_gw_ip)
except Exception:
LOG.exception(
"Failed to create route to subcloud %s." % subcloud_name)
db_api.subcloud_update(
context, subcloud_id,
deploy_status=consts.DEPLOY_STATE_RECONFIGURING_NETWORK_FAILED,
error_description=consts.ERROR_DESC_EMPTY
)
if update_db:
db_api.subcloud_update(
context, subcloud_id,
deploy_status=consts.DEPLOY_STATE_RECONFIGURING_NETWORK_FAILED,
error_description=consts.ERROR_DESC_EMPTY
)
return
try:
self._update_services_endpoint(
context, payload, subcloud_name, m_ks_client)
except Exception:
LOG.exception("Failed to update subcloud %s endpoints" % subcloud_name)
db_api.subcloud_update(
context, subcloud_id,
deploy_status=consts.DEPLOY_STATE_RECONFIGURING_NETWORK_FAILED,
error_description=consts.ERROR_DESC_EMPTY
)
if update_db:
db_api.subcloud_update(
context, subcloud_id,
deploy_status=consts.DEPLOY_STATE_RECONFIGURING_NETWORK_FAILED,
error_description=consts.ERROR_DESC_EMPTY
)
return
# Delete old routes

View File

@ -160,6 +160,11 @@ class ManagerClient(RPCClient):
subcloud_id=subcloud_id,
payload=payload))
def redeploy_subcloud(self, ctxt, subcloud_id, payload):
return self.cast(ctxt, self.make_msg('redeploy_subcloud',
subcloud_id=subcloud_id,
payload=payload))
def backup_subclouds(self, ctxt, payload):
return self.cast(ctxt, self.make_msg('backup_subclouds',
payload=payload))

View File

@ -15,15 +15,17 @@
# under the License.
#
from oslo_utils import timeutils
import base64
import copy
import json
import os
import keyring
import mock
from oslo_utils import timeutils
import six
from six.moves import http_client
from tsconfig.tsconfig import SW_VERSION
import webtest
from dccommon import consts as dccommon_consts
@ -34,15 +36,12 @@ from dcmanager.common import prestage
from dcmanager.common import utils as cutils
from dcmanager.db.sqlalchemy import api as db_api
from dcmanager.rpc import client as rpc_client
from dcmanager.tests.unit.api import test_root_controller as testroot
from dcmanager.tests.unit.api.v1.controllers.mixins import APIMixin
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'
@ -1791,6 +1790,355 @@ class TestSubcloudAPIOther(testroot.DCManagerApiTest):
headers=FAKE_HEADERS, params=reinstall_data)
self.assertEqual(response.status_int, 200)
@mock.patch.object(psd_common, 'upload_config_file')
@mock.patch.object(psd_common.PatchingClient, 'query')
@mock.patch.object(os.path, 'isdir')
@mock.patch.object(os, 'listdir')
@mock.patch.object(cutils, 'get_vault_load_files')
@mock.patch.object(psd_common, 'validate_k8s_version')
@mock.patch.object(psd_common, 'validate_subcloud_config')
@mock.patch.object(psd_common, 'validate_bootstrap_values')
def test_redeploy_subcloud(
self, mock_validate_bootstrap_values, mock_validate_subcloud_config,
mock_validate_k8s_version, mock_get_vault_load_files,
mock_os_listdir, mock_os_isdir, mock_query, mock_upload_config_file):
fake_bmc_password = base64.b64encode(
'bmc_password'.encode("utf-8")).decode('utf-8')
fake_sysadmin_password = base64.b64encode(
'sysadmin_password'.encode("utf-8")).decode('utf-8')
install_data = copy.copy(FAKE_SUBCLOUD_INSTALL_VALUES)
install_data.pop('software_version')
bootstrap_data = copy.copy(fake_subcloud.FAKE_BOOTSTRAP_FILE_DATA)
config_data = {'deploy_config': 'deploy config values'}
redeploy_data = {**install_data, **bootstrap_data, **config_data,
'sysadmin_password': fake_sysadmin_password,
'bmc_password': fake_bmc_password}
subcloud = fake_subcloud.create_fake_subcloud(
self.ctx, name=bootstrap_data["name"])
mock_query.return_value = {}
mock_get_vault_load_files.return_value = ('iso_file_path', 'sig_file_path')
mock_os_isdir.return_value = True
mock_upload_config_file.return_value = True
mock_os_listdir.return_value = ['deploy_chart_fake.tgz',
'deploy_overrides_fake.yaml',
'deploy_playbook_fake.yaml']
upload_files = [("install_values", "install_fake_filename",
json.dumps(install_data).encode("utf-8")),
("bootstrap_values", "bootstrap_fake_filename",
json.dumps(bootstrap_data).encode("utf-8")),
("deploy_config", "config_fake_filename",
json.dumps(config_data).encode("utf-8"))]
response = self.app.patch(
FAKE_URL + '/' + str(subcloud.id) + '/redeploy',
headers=FAKE_HEADERS, params=redeploy_data,
upload_files=upload_files)
mock_validate_bootstrap_values.assert_called_once()
mock_validate_subcloud_config.assert_called_once()
mock_validate_k8s_version.assert_called_once()
self.mock_rpc_client().redeploy_subcloud.assert_called_once_with(
mock.ANY,
subcloud.id,
mock.ANY)
self.assertEqual(response.status_int, 200)
self.assertEqual(SW_VERSION, response.json['software-version'])
@mock.patch.object(cutils, 'load_yaml_file')
@mock.patch.object(psd_common.PatchingClient, 'query')
@mock.patch.object(os.path, 'exists')
@mock.patch.object(os.path, 'isdir')
@mock.patch.object(os, 'listdir')
@mock.patch.object(cutils, 'get_vault_load_files')
@mock.patch.object(psd_common, 'validate_k8s_version')
def test_redeploy_subcloud_no_request_data(
self, mock_validate_k8s_version, mock_get_vault_load_files,
mock_os_listdir, mock_os_isdir, mock_path_exists, mock_query,
mock_load_yaml):
fake_bmc_password = base64.b64encode(
'bmc_password'.encode("utf-8")).decode('utf-8')
fake_sysadmin_password = base64.b64encode(
'sysadmin_password'.encode("utf-8")).decode('utf-8')
install_data = copy.copy(FAKE_SUBCLOUD_INSTALL_VALUES)
install_data.pop('software_version')
install_data['bmc_password'] = fake_bmc_password
redeploy_data = {'sysadmin_password': fake_sysadmin_password}
subcloud = fake_subcloud.create_fake_subcloud(
self.ctx, name=fake_subcloud.FAKE_BOOTSTRAP_FILE_DATA["name"],
data_install=json.dumps(install_data))
config_file = psd_common.get_config_file_path(subcloud.name,
consts.DEPLOY_CONFIG)
mock_query.return_value = {}
mock_get_vault_load_files.return_value = ('iso_file_path', 'sig_file_path')
mock_os_isdir.return_value = True
mock_os_listdir.return_value = ['deploy_chart_fake.tgz',
'deploy_overrides_fake.yaml',
'deploy_playbook_fake.yaml']
mock_path_exists.side_effect = lambda x: True if x == config_file else False
mock_load_yaml.return_value = {"software_version": SW_VERSION}
response = self.app.patch(
FAKE_URL + '/' + str(subcloud.id) + '/redeploy',
headers=FAKE_HEADERS, params=redeploy_data)
mock_validate_k8s_version.assert_called_once()
self.mock_rpc_client().redeploy_subcloud.assert_called_once_with(
mock.ANY,
subcloud.id,
mock.ANY)
self.assertEqual(response.status_int, 200)
self.assertEqual(SW_VERSION, response.json['software-version'])
@mock.patch.object(psd_common, 'upload_config_file')
@mock.patch.object(psd_common.PatchingClient, 'query')
@mock.patch.object(os.path, 'isdir')
@mock.patch.object(os, 'listdir')
@mock.patch.object(cutils, 'get_vault_load_files')
@mock.patch.object(psd_common, 'validate_k8s_version')
@mock.patch.object(psd_common, 'validate_subcloud_config')
@mock.patch.object(psd_common, 'validate_bootstrap_values')
def test_redeploy_subcloud_with_release_version(
self, mock_validate_bootstrap_values, mock_validate_subcloud_config,
mock_validate_k8s_version, mock_get_vault_load_files,
mock_os_listdir, mock_os_isdir, mock_query, mock_upload_config_file):
fake_bmc_password = base64.b64encode(
'bmc_password'.encode("utf-8")).decode('utf-8')
fake_sysadmin_password = base64.b64encode(
'sysadmin_password'.encode("utf-8")).decode('utf-8')
install_data = copy.copy(FAKE_SUBCLOUD_INSTALL_VALUES)
install_data.pop('software_version')
bootstrap_data = copy.copy(fake_subcloud.FAKE_BOOTSTRAP_FILE_DATA)
config_data = {'deploy_config': 'deploy config values'}
redeploy_data = {**install_data, **bootstrap_data, **config_data,
'sysadmin_password': fake_sysadmin_password,
'bmc_password': fake_bmc_password,
'release': fake_subcloud.FAKE_SOFTWARE_VERSION}
subcloud = fake_subcloud.create_fake_subcloud(
self.ctx, name=bootstrap_data["name"],
software_version=SW_VERSION)
mock_query.return_value = {}
mock_get_vault_load_files.return_value = ('iso_file_path', 'sig_file_path')
mock_os_isdir.return_value = True
mock_upload_config_file.return_value = True
mock_os_listdir.return_value = ['deploy_chart_fake.tgz',
'deploy_overrides_fake.yaml',
'deploy_playbook_fake.yaml']
upload_files = [("install_values", "install_fake_filename",
json.dumps(install_data).encode("utf-8")),
("bootstrap_values", "bootstrap_fake_filename",
json.dumps(bootstrap_data).encode("utf-8")),
("deploy_config", "config_fake_filename",
json.dumps(config_data).encode("utf-8"))]
response = self.app.patch(
FAKE_URL + '/' + str(subcloud.id) + '/redeploy',
headers=FAKE_HEADERS, params=redeploy_data,
upload_files=upload_files)
mock_validate_bootstrap_values.assert_called_once()
mock_validate_subcloud_config.assert_called_once()
mock_validate_k8s_version.assert_called_once()
self.mock_rpc_client().redeploy_subcloud.assert_called_once_with(
mock.ANY,
subcloud.id,
mock.ANY)
self.assertEqual(response.status_int, 200)
self.assertEqual(fake_subcloud.FAKE_SOFTWARE_VERSION,
response.json['software-version'])
@mock.patch.object(cutils, 'load_yaml_file')
@mock.patch.object(psd_common.PatchingClient, 'query')
@mock.patch.object(os.path, 'exists')
@mock.patch.object(os.path, 'isdir')
@mock.patch.object(os, 'listdir')
@mock.patch.object(cutils, 'get_vault_load_files')
def test_redeploy_subcloud_no_request_body(
self, mock_get_vault_load_files, mock_os_listdir,
mock_os_isdir, mock_path_exists, mock_query, mock_load_yaml):
fake_bmc_password = base64.b64encode(
'bmc_password'.encode("utf-8")).decode('utf-8')
install_data = copy.copy(FAKE_SUBCLOUD_INSTALL_VALUES)
install_data.pop('software_version')
install_data['bmc_password'] = fake_bmc_password
subcloud = fake_subcloud.create_fake_subcloud(
self.ctx, name=fake_subcloud.FAKE_BOOTSTRAP_FILE_DATA["name"],
data_install=json.dumps(install_data))
config_file = psd_common.get_config_file_path(subcloud.name,
consts.DEPLOY_CONFIG)
mock_query.return_value = {}
mock_get_vault_load_files.return_value = ('iso_file_path', 'sig_file_path')
mock_os_isdir.return_value = True
mock_os_listdir.return_value = ['deploy_chart_fake.tgz',
'deploy_overrides_fake.yaml',
'deploy_playbook_fake.yaml']
mock_path_exists.side_effect = lambda x: True if x == config_file else False
mock_load_yaml.return_value = {"software_version": SW_VERSION}
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.patch_json, FAKE_URL + '/' +
str(subcloud.id) + '/redeploy',
headers=FAKE_HEADERS, params={})
def test_redeploy_online_subcloud(self):
subcloud = fake_subcloud.create_fake_subcloud(
self.ctx, name=fake_subcloud.FAKE_BOOTSTRAP_FILE_DATA["name"])
db_api.subcloud_update(self.ctx, subcloud.id,
availability_status=dccommon_consts.AVAILABILITY_ONLINE)
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.patch_json, FAKE_URL + '/' +
str(subcloud.id) + '/redeploy',
headers=FAKE_HEADERS, params={})
self.mock_rpc_client().redeploy_subcloud.assert_not_called()
def test_redeploy_managed_subcloud(self):
subcloud = fake_subcloud.create_fake_subcloud(
self.ctx, name=fake_subcloud.FAKE_BOOTSTRAP_FILE_DATA["name"])
db_api.subcloud_update(self.ctx, subcloud.id,
management_state=dccommon_consts.MANAGEMENT_MANAGED)
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.patch_json, FAKE_URL + '/' +
str(subcloud.id) + '/redeploy',
headers=FAKE_HEADERS, params={})
self.mock_rpc_client().redeploy_subcloud.assert_not_called()
@mock.patch.object(cutils, 'load_yaml_file')
@mock.patch.object(psd_common.PatchingClient, 'query')
@mock.patch.object(os.path, 'exists')
@mock.patch.object(os.path, 'isdir')
@mock.patch.object(os, 'listdir')
@mock.patch.object(cutils, 'get_vault_load_files')
@mock.patch.object(psd_common, 'validate_k8s_version')
def test_redeploy_subcloud_missing_required_value(
self, mock_validate_k8s_version, mock_get_vault_load_files,
mock_os_listdir, mock_os_isdir, mock_path_exists, mock_query,
mock_load_yaml):
fake_bmc_password = base64.b64encode(
'bmc_password'.encode("utf-8")).decode('utf-8')
fake_sysadmin_password = base64.b64encode(
'sysadmin_password'.encode("utf-8")).decode('utf-8')
install_data = copy.copy(FAKE_SUBCLOUD_INSTALL_VALUES)
install_data.pop('software_version')
install_data['bmc_password'] = fake_bmc_password
subcloud = fake_subcloud.create_fake_subcloud(
self.ctx, name=fake_subcloud.FAKE_BOOTSTRAP_FILE_DATA["name"],
data_install=json.dumps(install_data))
config_file = psd_common.get_config_file_path(subcloud.name,
consts.DEPLOY_CONFIG)
mock_query.return_value = {}
mock_get_vault_load_files.return_value = ('iso_file_path', 'sig_file_path')
mock_os_isdir.return_value = True
mock_os_listdir.return_value = ['deploy_chart_fake.tgz',
'deploy_overrides_fake.yaml',
'deploy_playbook_fake.yaml']
mock_path_exists.side_effect = lambda x: True if x == config_file else False
mock_load_yaml.return_value = {"software_version": SW_VERSION}
for k in ['name', 'system_mode', 'external_oam_subnet',
'external_oam_gateway_address', 'external_oam_floating_address',
'sysadmin_password']:
bootstrap_values = copy.copy(fake_subcloud.FAKE_BOOTSTRAP_FILE_DATA)
redeploy_data = {**bootstrap_values,
'sysadmin_password': fake_sysadmin_password}
del redeploy_data[k]
upload_files = [("bootstrap_values", "bootstrap_fake_filename",
json.dumps(redeploy_data).encode("utf-8"))]
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.patch_json, FAKE_URL + '/' +
str(subcloud.id) + '/redeploy',
headers=FAKE_HEADERS, params=redeploy_data,
upload_files=upload_files)
@mock.patch.object(psd_common, 'upload_config_file')
@mock.patch.object(psd_common.PatchingClient, 'query')
@mock.patch.object(os.path, 'isdir')
@mock.patch.object(os, 'listdir')
@mock.patch.object(cutils, 'get_vault_load_files')
@mock.patch.object(psd_common, 'validate_k8s_version')
@mock.patch.object(psd_common, 'validate_subcloud_config')
@mock.patch.object(psd_common, 'validate_bootstrap_values')
def test_redeploy_subcloud_missing_stored_values(
self, mock_validate_bootstrap_values, mock_validate_subcloud_config,
mock_validate_k8s_version, mock_get_vault_load_files,
mock_os_listdir, mock_os_isdir, mock_query, mock_upload_config_values):
fake_bmc_password = base64.b64encode(
'bmc_password'.encode("utf-8")).decode('utf-8')
fake_sysadmin_password = base64.b64encode(
'sysadmin_password'.encode("utf-8")).decode('utf-8')
install_data = copy.copy(FAKE_SUBCLOUD_INSTALL_VALUES)
install_data.pop('software_version')
bootstrap_data = copy.copy(fake_subcloud.FAKE_BOOTSTRAP_FILE_DATA)
config_data = {'deploy_config': 'deploy config values'}
for k in ['management_subnet', 'management_start_address',
'management_end_address', 'management_gateway_address',
'systemcontroller_gateway_address']:
del bootstrap_data[k]
redeploy_data = {**install_data, **bootstrap_data, **config_data,
'sysadmin_password': fake_sysadmin_password,
'bmc_password': fake_bmc_password}
subcloud = fake_subcloud.create_fake_subcloud(
self.ctx, name=bootstrap_data["name"])
mock_query.return_value = {}
mock_get_vault_load_files.return_value = ('iso_file_path', 'sig_file_path')
mock_os_isdir.return_value = True
mock_upload_config_values.return_value = True
mock_os_listdir.return_value = ['deploy_chart_fake.tgz',
'deploy_overrides_fake.yaml',
'deploy_playbook_fake.yaml']
upload_files = [("install_values", "install_fake_filename",
json.dumps(install_data).encode("utf-8")),
("bootstrap_values", "bootstrap_fake_filename",
json.dumps(bootstrap_data).encode("utf-8")),
("deploy_config", "config_fake_filename",
json.dumps(config_data).encode("utf-8"))]
response = self.app.patch(
FAKE_URL + '/' + str(subcloud.id) + '/redeploy',
headers=FAKE_HEADERS, params=redeploy_data,
upload_files=upload_files)
mock_validate_bootstrap_values.assert_called_once()
mock_validate_subcloud_config.assert_called_once()
mock_validate_k8s_version.assert_called_once()
self.mock_rpc_client().redeploy_subcloud.assert_called_once_with(
mock.ANY,
subcloud.id,
mock.ANY)
self.assertEqual(response.status_int, 200)
self.assertEqual(SW_VERSION, response.json['software-version'])
@mock.patch.object(prestage, '_get_system_controller_upgrades')
@mock.patch.object(prestage, '_get_prestage_subcloud_info')
@mock.patch.object(subclouds.SubcloudsController, '_get_prestage_payload')

View File

@ -417,8 +417,8 @@ class TestSubcloudManager(base.DCManagerTestCase):
'software_version': "18.03",
"management_subnet": "192.168.101.0/24",
"management_gateway_ip": "192.168.101.1",
"management_start_ip": "192.168.101.3",
"management_end_ip": "192.168.101.4",
"management_start_ip": "192.168.101.2",
"management_end_ip": "192.168.101.50",
"systemcontroller_gateway_ip": "192.168.204.101",
'deploy_status': "not-deployed",
'error_description': "No errors present",
@ -1826,6 +1826,61 @@ class TestSubcloudManager(base.DCManagerTestCase):
FAKE_PREVIOUS_SW_VERSION)
mock_thread_start.assert_called_once()
@mock.patch.object(subcloud_manager.SubcloudManager,
'_run_subcloud_install')
@mock.patch.object(subcloud_manager.SubcloudManager,
'_prepare_for_deployment')
@mock.patch.object(cutils, 'create_subcloud_inventory')
@mock.patch.object(subcloud_manager, 'keyring')
@mock.patch.object(cutils, 'get_playbook_for_software_version')
@mock.patch.object(cutils, 'update_values_on_yaml_file')
@mock.patch.object(RunAnsible, 'exec_playbook')
def test_subcloud_redeploy(self, mock_exec_playbook, mock_update_yml,
mock_get_playbook_for_software_version,
mock_keyring, create_subcloud_inventory,
mock_prepare_for_deployment,
mock_run_subcloud_install):
mock_get_playbook_for_software_version.return_value = "22.12"
mock_keyring.get_password.return_value = "testpass"
mock_exec_playbook.return_value = False
mock_run_subcloud_install.return_value = True
subcloud = self.create_subcloud_static(
self.ctx,
name='subcloud1',
deploy_status=consts.DEPLOY_STATE_CREATED)
fake_install_values = \
copy.copy(fake_subcloud.FAKE_SUBCLOUD_INSTALL_VALUES)
fake_install_values['software_version'] = SW_VERSION
fake_payload_install = {'bmc_password': 'bmc_pass',
'install_values': fake_install_values,
'software_version': SW_VERSION,
'sysadmin_password': 'sys_pass'}
fake_payload_bootstrap = {**fake_subcloud.FAKE_BOOTSTRAP_VALUE,
**fake_subcloud.FAKE_BOOTSTRAP_FILE_DATA}
fake_payload_bootstrap["sysadmin_password"] = "testpass"
fake_payload_config = {"sysadmin_password": "testpass",
"deploy_playbook": "test_playbook.yaml",
"deploy_overrides": "test_overrides.yaml",
"deploy_chart": "test_chart.yaml",
"deploy_config": "subcloud1.yaml"}
fake_payload = {**fake_payload_install,
**fake_payload_bootstrap,
**fake_payload_config}
sm = subcloud_manager.SubcloudManager()
sm.redeploy_subcloud(self.ctx, subcloud.id, fake_payload)
# Verify subcloud was updated with correct values
updated_subcloud = db_api.subcloud_get_by_name(self.ctx,
subcloud.name)
self.assertEqual(consts.DEPLOY_STATE_DONE,
updated_subcloud.deploy_status)
def test_handle_subcloud_operations_in_progress(self):
subcloud1 = self.create_subcloud_static(
self.ctx,

View File

@ -30,7 +30,8 @@ class TestUtils(base.DCManagerTestCase):
payload = {"management_subnet": "192.168.101.0/24",
"management_gateway_address": "192.168.101.1",
"management_start_address": "192.168.101.2",
"management_end_address": "192.168.101.50"}
"management_end_address": "192.168.101.50",
"systemcontroller_gateway_address": "192.168.204.101"}
result = utils.has_network_reconfig(payload, subcloud)
self.assertFalse(result)
@ -51,3 +52,13 @@ class TestUtils(base.DCManagerTestCase):
"management_end_address": "192.168.101.50"}
result = utils.has_network_reconfig(payload, subcloud)
self.assertTrue(result)
def test_has_network_reconfig_different_sc_gateway(self):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
payload = {"management_subnet": "192.168.101.0/24",
"management_gateway_address": "192.168.101.1",
"management_start_address": "192.168.101.2",
"management_end_address": "192.168.101.50",
"systemcontroller_gateway_address": "192.168.204.102"}
result = utils.has_network_reconfig(payload, subcloud)
self.assertTrue(result)