diff --git a/distributedcloud/dccommon/consts.py b/distributedcloud/dccommon/consts.py index fde8aa10f..039bcf472 100644 --- a/distributedcloud/dccommon/consts.py +++ b/distributedcloud/dccommon/consts.py @@ -216,3 +216,6 @@ ANSIBLE_SUBCLOUD_ENROLL_PLAYBOOK = \ # Sysinv client default timeout SYSINV_CLIENT_REST_DEFAULT_TIMEOUT = 600 + +SUBCLOUD_ISO_PATH = '/opt/platform/iso' +SUBCLOUD_FEED_PATH = '/var/www/pages/feed' diff --git a/distributedcloud/dccommon/ostree_mount.py b/distributedcloud/dccommon/ostree_mount.py index f76a8d105..99cf75281 100644 --- a/distributedcloud/dccommon/ostree_mount.py +++ b/distributedcloud/dccommon/ostree_mount.py @@ -8,6 +8,7 @@ import os from oslo_log import log as logging import sh +from dccommon import consts from dcmanager.common import utils # The 'sh' library is magical - it looks up CLI functions dynamically. @@ -53,15 +54,21 @@ def check_stale_bind_mount(mount_path, source_path): # TODO(kmacleod): utils.synchronized should be moved into dccommon @utils.synchronized("ostree-mount-subclouds", external=True) -def validate_ostree_iso_mount(www_iso_root, source_path): +def validate_ostree_iso_mount(software_version): """Ensure the ostree_repo is properly mounted under the iso path. Validity check includes if the mount is stale. If stale, the bind mount is recreated. Note that ostree_repo is mounted in a location not specific to a subcloud. """ - ostree_repo_mount_path = os.path.join(www_iso_root, "ostree_repo") - ostree_repo_source_path = os.path.join(source_path, "ostree_repo") + ostree_repo_mount_path = os.path.join( + consts.SUBCLOUD_ISO_PATH, software_version, "ostree_repo" + ) + ostree_repo_source_path = os.path.join( + consts.SUBCLOUD_FEED_PATH, + "rel-{version}".format(version=software_version), + "ostree_repo", + ) LOG.debug( "Checking ostree_repo mount: %s against %s", ostree_repo_mount_path, @@ -78,7 +85,7 @@ def validate_ostree_iso_mount(www_iso_root, source_path): os.makedirs(ostree_repo_mount_path, mode=0o755) mount_args = ( "--bind", - "%s/ostree_repo" % source_path, + ostree_repo_source_path, ostree_repo_mount_path, ) try: diff --git a/distributedcloud/dccommon/subcloud_install.py b/distributedcloud/dccommon/subcloud_install.py index a9190e4e9..54f68ef92 100644 --- a/distributedcloud/dccommon/subcloud_install.py +++ b/distributedcloud/dccommon/subcloud_install.py @@ -36,6 +36,7 @@ from dccommon.drivers.openstack.sysinv_v1 import SysinvClient from dccommon import exceptions from dccommon import ostree_mount from dccommon import utils as dccommon_utils + from dcmanager.common import consts as dcmanager_consts from dcmanager.common import utils @@ -45,9 +46,7 @@ LOG = logging.getLogger(__name__) CONF = cfg.CONF BOOT_MENU_TIMEOUT = '5' -SUBCLOUD_ISO_PATH = '/opt/platform/iso' SUBCLOUD_ISO_DOWNLOAD_PATH = '/var/www/pages/iso' -SUBCLOUD_FEED_PATH = '/var/www/pages/feed' DCVAULT_BOOTIMAGE_PATH = '/opt/dc-vault/loads/' PACKAGE_LIST_PATH = '/usr/local/share/pkg-list' GEN_ISO_COMMAND = '/usr/local/bin/gen-bootloader-iso.sh' @@ -510,16 +509,10 @@ class SubcloudInstall(object): if not os.path.isdir(override_path): os.mkdir(override_path, 0o755) - self.www_iso_root = os.path.join(SUBCLOUD_ISO_PATH, software_version) - - feed_path_rel_version = os.path.join(SUBCLOUD_FEED_PATH, - "rel-{version}".format( - version=software_version)) + self.www_iso_root = os.path.join(consts.SUBCLOUD_ISO_PATH, software_version) if dccommon_utils.is_debian(software_version): - ostree_mount.validate_ostree_iso_mount( - self.www_iso_root, feed_path_rel_version - ) + ostree_mount.validate_ostree_iso_mount(software_version) # Clean up iso directory if it already exists # This may happen if a previous installation attempt was abruptly @@ -571,7 +564,7 @@ class SubcloudInstall(object): self.name, software_version + "_packages_list.txt") - pkg_file_src = os.path.join(SUBCLOUD_FEED_PATH, + pkg_file_src = os.path.join(consts.SUBCLOUD_FEED_PATH, "rel-{version}".format( version=software_version), 'package_checksums') diff --git a/distributedcloud/dcmanager/api/controllers/v1/phased_subcloud_deploy.py b/distributedcloud/dcmanager/api/controllers/v1/phased_subcloud_deploy.py index 5714a4b13..018fb45c8 100644 --- a/distributedcloud/dcmanager/api/controllers/v1/phased_subcloud_deploy.py +++ b/distributedcloud/dcmanager/api/controllers/v1/phased_subcloud_deploy.py @@ -12,6 +12,7 @@ from oslo_messaging import RemoteError import pecan from dcmanager.api.controllers import restcomm +from dcmanager.api.controllers.v1.subclouds import SubcloudsController from dcmanager.api.policies import phased_subcloud_deploy as \ phased_subcloud_deploy_policy from dcmanager.api import policy @@ -210,6 +211,8 @@ class PhasedSubcloudDeployController(object): if not payload: pecan.abort(400, _('Body required')) + SubcloudsController.validate_software_deploy_state() + if subcloud.deploy_status not in VALID_STATES_FOR_DEPLOY_INSTALL: allowed_states_str = ', '.join(VALID_STATES_FOR_DEPLOY_INSTALL) pecan.abort(400, _('Subcloud deploy status must be either: %s') @@ -471,6 +474,7 @@ class PhasedSubcloudDeployController(object): # Consider the incoming release parameter only if install is one # of the pending deploy states if INSTALL in deploy_states_to_run: + SubcloudsController.validate_software_deploy_state() unvalidated_sw_version = \ payload.get('release', subcloud.software_version) else: diff --git a/distributedcloud/dcmanager/api/controllers/v1/subclouds.py b/distributedcloud/dcmanager/api/controllers/v1/subclouds.py index 65409763c..108b9ab05 100644 --- a/distributedcloud/dcmanager/api/controllers/v1/subclouds.py +++ b/distributedcloud/dcmanager/api/controllers/v1/subclouds.py @@ -44,6 +44,7 @@ from dccommon.drivers.openstack.fm import FmClient from dccommon.drivers.openstack.sdk_platform import ( OptimizedOpenStackDriver as OpenStackDriver ) +from dccommon.drivers.openstack import software_v1 from dccommon.drivers.openstack.sysinv_v1 import SysinvClient from dccommon.drivers.openstack import vim from dccommon import exceptions as dccommon_exceptions @@ -129,36 +130,54 @@ class SubcloudsController(object): @staticmethod def _get_prestage_payload(request): - fields = ['sysadmin_password', 'force', consts.PRESTAGE_REQUEST_RELEASE] - payload = { - 'force': False - } + fields = [ + "sysadmin_password", + "force", + consts.PRESTAGE_REQUEST_RELEASE, + consts.PRESTAGE_FOR_INSTALL, + consts.PRESTAGE_FOR_SW_DEPLOY, + ] + payload = {"force": False} try: body = json.loads(request.body) except Exception: - pecan.abort(400, _('Request body is malformed.')) + pecan.abort(400, _("Request body is malformed.")) for field in fields: val = body.get(field) if val is None: - if field == 'sysadmin_password': + if field == "sysadmin_password": pecan.abort(400, _("%s is required." % field)) else: - if field == 'sysadmin_password': + if field == "sysadmin_password": try: - base64.b64decode(val).decode('utf-8') - payload['sysadmin_password'] = val + base64.b64decode(val).decode("utf-8") + payload["sysadmin_password"] = val except Exception: pecan.abort( 400, - _('Failed to decode subcloud sysadmin_password, ' - 'verify the password is base64 encoded')) - elif field == 'force': - if val.lower() in ('true', 'false', 't', 'f'): - payload['force'] = val.lower() in ('true', 't') + _( + "Failed to decode subcloud sysadmin_password, " + "verify the password is base64 encoded" + ), + ) + elif field == "force": + if val.lower() in ("true", "false", "t", "f"): + payload["force"] = val.lower() in ("true", "t") else: pecan.abort( - 400, _('Invalid value for force option: %s' % val)) + 400, _("Invalid value for force option: %s" % val) + ) + elif field in ( + consts.PRESTAGE_FOR_INSTALL, + consts.PRESTAGE_FOR_SW_DEPLOY + ): + if val.lower() in ("true", "false", "t", "f"): + payload[field] = val.lower() in ("true", "t") + else: + errmsg = f"Invalid value for {field} option: {val}" + pecan.abort(400, _(errmsg)) + elif field == consts.PRESTAGE_REQUEST_RELEASE: payload[consts.PRESTAGE_REQUEST_RELEASE] = val return payload @@ -522,6 +541,53 @@ class SubcloudsController(object): subcloud_dict.update(extra_details) return subcloud_dict + @staticmethod + def is_valid_software_deploy_state(): + try: + m_os_ks_client = OpenStackDriver( + region_name=dccommon_consts.DEFAULT_REGION_NAME, region_clients=None + ).keystone_client + software_endpoint = m_os_ks_client.endpoint_cache.get_endpoint( + dccommon_consts.ENDPOINT_TYPE_SOFTWARE + ) + software_client = software_v1.SoftwareClient( + dccommon_consts.DEFAULT_REGION_NAME, + m_os_ks_client.session, + endpoint=software_endpoint, + ) + software_list = software_client.list() + for release in software_list: + if release["state"] not in ( + software_v1.AVAILABLE, + software_v1.COMMITTED, + software_v1.DEPLOYED, + software_v1.UNAVAILABLE, + ): + LOG.info( + "is_valid_software_deploy_state, not valid for: %s", + software_list + ) + return False + LOG.debug("is_valid_software_deploy_state, valid: %s", software_list) + + except Exception: + LOG.exception("Failure initializing OS Client, disallowing.") + return False + + return True + + @staticmethod + def validate_software_deploy_state(): + if not SubcloudsController.is_valid_software_deploy_state(): + pecan.abort( + 400, + _( + "A local software deployment operation is in progress. " + "Please finish the software deployment operation before " + "(re)installing/updating the subcloud." + ), + ) + @utils.synchronized(LOCK_NAME) @index.when(method='POST', template='json') def post(self): @@ -531,6 +597,18 @@ class SubcloudsController(object): restcomm.extract_credentials_for_policy()) context = restcomm.extract_context_from_environ() + self.validate_software_deploy_state() + + if not SubcloudsController.is_valid_software_deploy_state(): + pecan.abort( + 400, + _( + "A local software deployment operation is in progress. " + "Please finish the software deployment opearation before " + "(re)installing/updating the subcloud." + ), + ) + bootstrap_sc_name = psd_common.get_bootstrap_subcloud_name(request) payload = psd_common.get_request_data(request, None, @@ -1003,6 +1081,7 @@ class SubcloudsController(object): if utils.subcloud_is_secondary_state(subcloud.deploy_status): pecan.abort(500, _("Cannot perform on %s " "state subcloud" % subcloud.deploy_status)) + self.validate_software_deploy_state() config_file = psd_common.get_config_file_path(subcloud.name, consts.DEPLOY_CONFIG) has_bootstrap_values = consts.BOOTSTRAP_VALUES in request.POST @@ -1106,8 +1185,11 @@ class SubcloudsController(object): LOG.exception("validate_prestage failed") pecan.abort(400, _(str(exc))) + for_install = True + if consts.PRESTAGE_FOR_SW_DEPLOY in payload: + for_install = False prestage_software_version = utils.get_sw_version( - payload.get(consts.PRESTAGE_REQUEST_RELEASE)) + payload.get(consts.PRESTAGE_REQUEST_RELEASE), for_install) try: self.dcmanager_rpc_client.prestage_subcloud(context, payload) diff --git a/distributedcloud/dcmanager/common/consts.py b/distributedcloud/dcmanager/common/consts.py index 55a2d1625..7d85dd202 100644 --- a/distributedcloud/dcmanager/common/consts.py +++ b/distributedcloud/dcmanager/common/consts.py @@ -399,6 +399,8 @@ EXTRA_ARGS_RELEASE = 'release' # http request/response arguments for prestage PRESTAGE_SOFTWARE_VERSION = 'prestage-software-version' PRESTAGE_REQUEST_RELEASE = 'release' +PRESTAGE_FOR_INSTALL = 'for_install' +PRESTAGE_FOR_SW_DEPLOY = 'for_sw_deploy' # Device Image Bitstream Types BITSTREAM_TYPE_ROOT_KEY = 'root-key' diff --git a/distributedcloud/dcmanager/common/prestage.py b/distributedcloud/dcmanager/common/prestage.py index 96ff814d4..84e58b94b 100644 --- a/distributedcloud/dcmanager/common/prestage.py +++ b/distributedcloud/dcmanager/common/prestage.py @@ -36,6 +36,7 @@ from dccommon.drivers.openstack.sdk_platform import ( from dccommon.drivers.openstack.sysinv_v1 import SysinvClient from dccommon.exceptions import PlaybookExecutionFailed from dccommon.exceptions import PlaybookExecutionTimeout +from dccommon import ostree_mount from dccommon.utils import AnsiblePlaybook from dcmanager.common import consts @@ -110,7 +111,12 @@ def global_prestage_validate(payload): " Details: %s" % ex) -def initial_subcloud_validate(subcloud, installed_loads, software_version): +def initial_subcloud_validate( + subcloud, + installed_loads, + software_major_release, # format: YY.MM + for_sw_deploy, +): """Basic validation a subcloud prestage operation. Raises a PrestageCheckFailedException on failure. @@ -158,14 +164,14 @@ def initial_subcloud_validate(subcloud, installed_loads, software_version): # The request software version must be either the same as the software version # of the subcloud or any active/inactive/imported load on the system controller # (can be checked with "system load-list" command). - if software_version and \ - software_version != subcloud.software_version and \ - software_version not in installed_loads: + if not for_sw_deploy and software_major_release and \ + software_major_release != subcloud.software_version and \ + software_major_release not in installed_loads: raise exceptions.PrestagePreCheckFailedException( subcloud=subcloud.name, orch_skip=True, details="Specified release is not supported. " - "%s version must first be imported" % software_version) + f"{software_major_release} version must first be imported") def validate_prestage(subcloud, payload): @@ -185,12 +191,21 @@ def validate_prestage(subcloud, payload): installed_loads = [] software_version = None + software_major_release = None if payload.get(consts.PRESTAGE_REQUEST_RELEASE): software_version = payload.get(consts.PRESTAGE_REQUEST_RELEASE) + software_major_release = utils.get_major_release(software_version) installed_loads = utils.get_systemcontroller_installed_loads() + for_sw_deploy = is_prestage_for_sw_deploy(payload) + # re-run the initial validation - initial_subcloud_validate(subcloud, installed_loads, software_version) + initial_subcloud_validate( + subcloud, + installed_loads, + software_major_release, + for_sw_deploy, + ) subcloud_type, system_health, oam_floating_ip = \ _get_prestage_subcloud_info(subcloud) @@ -238,6 +253,13 @@ def is_local(subcloud_version, specified_version): return subcloud_version == specified_version +def is_prestage_for_sw_deploy(payload): + # The default is for_install, so we can simply check if the payload is + # tagged with for_sw_deploy + for_sw_deploy = payload.get(consts.PRESTAGE_FOR_SW_DEPLOY, False) + return for_sw_deploy + + def prestage_subcloud(context, payload): """Subcloud prestaging @@ -253,8 +275,11 @@ def prestage_subcloud(context, payload): - run prestage_images.yml ansible playbook """ subcloud_name = payload['subcloud_name'] - LOG.info("Prestaging subcloud: %s, force=%s" % (subcloud_name, - payload['force'])) + for_sw_deploy = is_prestage_for_sw_deploy(payload) + LOG.info( + f"Prestaging subcloud: {subcloud_name}, " + f"force={payload['force']}, for_sw_deploy={for_sw_deploy}" + ) try: subcloud = db_api.subcloud_get_by_name(context, subcloud_name) except exceptions.SubcloudNameNotFound: @@ -282,6 +307,7 @@ def prestage_subcloud(context, payload): def _prestage_standalone_thread(context, subcloud, payload): """Run the prestage operations inside a separate thread""" log_file = utils.get_subcloud_ansible_log_file(subcloud.name) + for_sw_deploy = is_prestage_for_sw_deploy(payload) try: prestage_packages(context, subcloud, payload) # Get the prestage versions from the logs generated by @@ -289,11 +315,17 @@ def _prestage_standalone_thread(context, subcloud, payload): prestage_versions = utils.get_msg_output_info( log_file, PRINT_PRESTAGE_VERSIONS_TASK, PRESTAGE_VERSIONS_KEY_STR) - prestage_images(context, subcloud, payload) + # TODO(kmacleod) need to invoke this for retagging images + if not for_sw_deploy: + prestage_images(context, subcloud, payload) + prestage_complete(context, subcloud.id, prestage_versions) LOG.info("Prestage complete: %s", subcloud.name) except Exception: + LOG.exception( + f"Subcloud prestaging failed (in standalone thread) {subcloud.name}" + ) prestage_fail(context, subcloud.id) raise @@ -382,8 +414,24 @@ def prestage_packages(context, subcloud, payload): ANSIBLE_PRESTAGE_INVENTORY_SUFFIX) prestage_software_version = payload.get( - consts.PRESTAGE_REQUEST_RELEASE, SW_VERSION) - extra_vars_str = "software_version=%s" % prestage_software_version + consts.PRESTAGE_REQUEST_RELEASE, SW_VERSION + ) + prestage_major_release = utils.get_major_release(prestage_software_version) + extra_vars_str = f"software_version={prestage_software_version} " + extra_vars_str += f"software_major_release={prestage_major_release}" + + if is_prestage_for_sw_deploy(payload): + extra_vars_str += ( + f" prestage_install={consts.PRESTAGE_FOR_SW_DEPLOY}" + ) + else: + # default + extra_vars_str += ( + f" prestage_install={consts.PRESTAGE_FOR_INSTALL}" + ) + + ostree_mount.validate_ostree_iso_mount(prestage_major_release) + _run_ansible(context, ["ansible-playbook", ANSIBLE_PRESTAGE_SUBCLOUD_PACKAGES_PLAYBOOK, @@ -414,10 +462,21 @@ def prestage_images(context, subcloud, payload): """ prestage_software_version = payload.get( consts.PRESTAGE_REQUEST_RELEASE, SW_VERSION) - extra_vars_str = "software_version=%s" % prestage_software_version + prestage_major_release = utils.get_major_release( + prestage_software_version + ) + extra_vars_str = f"software_version={prestage_software_version} " + extra_vars_str += f"software_major_release={prestage_major_release}" + + # TODO(kmacleod) we may not need these if images are not prestaged + # if for_sw_deploy + if consts.PRESTAGE_FOR_INSTALL in payload: + extra_vars_str += f" for_install={payload[consts.PRESTAGE_FOR_INSTALL]}" + elif consts.PRESTAGE_FOR_SW_DEPLOY in payload: + extra_vars_str += f" for_sw_deploy={payload[consts.PRESTAGE_FOR_SW_DEPLOY]}" image_list_filename = None - deploy_dir = os.path.join(DEPLOY_BASE_DIR, prestage_software_version) + deploy_dir = os.path.join(DEPLOY_BASE_DIR, prestage_major_release) if os.path.isdir(deploy_dir): image_list_filename = utils.get_filename_by_prefix(deploy_dir, 'prestage_images') @@ -428,14 +487,14 @@ def prestage_images(context, subcloud, payload): LOG.debug("prestage images list file: %s", image_list_file) else: LOG.debug("prestage images list file does not exist") - if prestage_software_version != subcloud.software_version: + if prestage_major_release != subcloud.software_version: # Prestage source is remote but there is no images list file so # skip the images prestage. LOG.info("Images prestage is skipped for %s as the prestage images " "list for release %s has not been uploaded and the " "subcloud is running a different load than %s." - % (subcloud.name, prestage_software_version, - prestage_software_version)) + % (subcloud.name, prestage_major_release, + prestage_major_release)) return # Ansible inventory filename for the specified subcloud diff --git a/distributedcloud/dcmanager/common/utils.py b/distributedcloud/dcmanager/common/utils.py index 5a4b575fa..ca349e8b7 100644 --- a/distributedcloud/dcmanager/common/utils.py +++ b/distributedcloud/dcmanager/common/utils.py @@ -1406,7 +1406,7 @@ def create_subcloud_rehome_data_template(): return {'saved_payload': {}} -def get_sw_version(release=None): +def get_sw_version(release=None, for_install=True): """Get the sw_version to be used. Return the sw_version by first validating a set release version. @@ -1416,7 +1416,10 @@ def get_sw_version(release=None): if release: try: - validate_release_version_supported(release) + if for_install: + validate_major_release_version_supported(release) + else: + validate_minor_release_version_exists(release) return release except exceptions.ValidateFail as e: pecan.abort(400, @@ -1428,7 +1431,7 @@ def get_sw_version(release=None): return tsc.SW_VERSION -def validate_release_version_supported(release_version_to_check): +def validate_major_release_version_supported(release_version_to_check): """Check if a release version is supported by the current active version. :param release_version_to_check: version string to validate @@ -1452,6 +1455,37 @@ def validate_release_version_supported(release_version_to_check): return True +def is_major_release(version): + return not is_minor_release(version) + + +def is_minor_release(version): + split_version = version.split('.') + if len(split_version) == 2: + return False + if len(split_version) == 3: + if split_version[2] == '0': + return False + return True + LOG.error(f"Unexpected release version found: {version}, assuming major release") + return False + + +def get_major_release(version): + """Returns the YY.MM portion of the given version string""" + split_version = version.split('.') + return '.'.join(split_version[0:2]) + + +def validate_minor_release_version_exists(release_version_to_check): + # TODO(kmacleod): For minor releases (for_sw_deploy) we need to + # validate the given minor release + + # This should lookup all the minor releases and validate the input version + # exists + LOG.warn("TODO: validate_minor_release_version_exists") + + def get_current_supported_upgrade_versions(): """Parse the upgrades metadata file to build a list of supported versions. diff --git a/distributedcloud/dcmanager/orchestrator/sw_update_manager.py b/distributedcloud/dcmanager/orchestrator/sw_update_manager.py index af89ad380..eb27e4a29 100644 --- a/distributedcloud/dcmanager/orchestrator/sw_update_manager.py +++ b/distributedcloud/dcmanager/orchestrator/sw_update_manager.py @@ -340,9 +340,16 @@ class SwUpdateManager(manager.Manager): patch_file = payload.get('patch') installed_loads = [] software_version = None + software_major_release = None + for_sw_deploy = False if payload.get(consts.PRESTAGE_REQUEST_RELEASE): software_version = payload.get(consts.PRESTAGE_REQUEST_RELEASE) + software_major_release = utils.get_major_release(software_version) installed_loads = utils.get_systemcontroller_installed_loads() + # TODO(kmacleod): Hugo: we need to say whether this is a + # for-install or for-fw-deploy prestaging operation Setting this to + # a for-install operation for now (since that is the default) + for_sw_deploy = False # Has the user specified a specific subcloud? # todo(abailey): refactor this code to use classes @@ -426,8 +433,13 @@ class SwUpdateManager(manager.Manager): try: prestage.global_prestage_validate(payload) prestage_global_validated = True + installed_loads = utils.get_systemcontroller_installed_loads() prestage.initial_subcloud_validate( - subcloud, installed_loads, software_version) + subcloud, + installed_loads, + software_major_release, + for_sw_deploy + ) except exceptions.PrestagePreCheckFailedException as ex: raise exceptions.BadRequest(resource='strategy', msg=str(ex)) @@ -597,7 +609,11 @@ class SwUpdateManager(manager.Manager): # Do initial validation for subcloud try: prestage.initial_subcloud_validate( - subcloud, installed_loads, software_version) + subcloud, + installed_loads, + software_major_release, + for_sw_deploy, + ) except exceptions.PrestagePreCheckFailedException: LOG.warn("Excluding subcloud from prestage strategy: %s", subcloud.name) diff --git a/distributedcloud/dcmanager/tests/base.py b/distributedcloud/dcmanager/tests/base.py index 0f86c9503..866f6dbb6 100644 --- a/distributedcloud/dcmanager/tests/base.py +++ b/distributedcloud/dcmanager/tests/base.py @@ -31,6 +31,7 @@ from sqlalchemy.engine import Engine from sqlalchemy import event from dccommon.utils import AnsiblePlaybook +from dcmanager.api.controllers.v1.subclouds import SubcloudsController from dcmanager.audit import rpcapi from dcmanager.audit import subcloud_audit_manager from dcmanager.common import consts @@ -382,3 +383,11 @@ class DCManagerTestCase(base.BaseTestCase): mock_patch_object = mock.patch.object(AnsiblePlaybook, 'run_playbook') self.mock_ansible_run_playbook = mock_patch_object.start() self.addCleanup(mock_patch_object.stop) + + def _mock_valid_software_deploy_state(self, return_value=True): + mock_patch_object = mock.patch.object( + SubcloudsController, "is_valid_software_deploy_state" + ) + self.mock_valid_software_deploy_state = mock_patch_object.start() + self.addCleanup(mock_patch_object.stop) + self.mock_valid_software_deploy_state.return_value = return_value diff --git a/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_phased_subcloud_deploy.py b/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_phased_subcloud_deploy.py index c61080d5c..d835fdd13 100644 --- a/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_phased_subcloud_deploy.py +++ b/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_phased_subcloud_deploy.py @@ -14,6 +14,7 @@ from tsconfig.tsconfig import SW_VERSION from dccommon import consts as dccommon_consts from dcmanager.api.controllers.v1 import phased_subcloud_deploy as psd_api +from dcmanager.api.controllers.v1.subclouds import SubcloudsController from dcmanager.common import consts from dcmanager.common import phased_subcloud_deploy as psd_common from dcmanager.common import utils as dutils @@ -66,6 +67,13 @@ class BaseTestPhasedSubcloudDeployController(DCManagerApiTest): self.mock_is_initial_deployment = mock_patch_object.start() self.addCleanup(mock_patch_object.stop) + def _mock_is_valid_software_deploy_state(self): + mock_patch_object = mock.patch.object( + SubcloudsController, "is_valid_software_deploy_state" + ) + self.mock_is_valid_software_deploy_state = mock_patch_object.start() + self.addCleanup(mock_patch_object.stop) + class TestPhasedSubcloudDeployController(BaseTestPhasedSubcloudDeployController): """Test class for PhasedSubcloudDeployController""" @@ -197,6 +205,7 @@ class BaseTestPhasedSubcloudDeployPatch(BaseTestPhasedSubcloudDeployController): self._mock_get_vault_load_files() self._mock_is_initial_deployment() + self._mock_is_valid_software_deploy_state() self._mock_get_network_address_pool() self.mock_get_vault_load_files.return_value = \ diff --git a/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_subclouds.py b/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_subclouds.py index 6e01290c6..f686120b3 100644 --- a/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_subclouds.py +++ b/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_subclouds.py @@ -230,6 +230,7 @@ class BaseTestSubcloudsController(DCManagerApiTest, SubcloudAPIMixin): self._mock_rpc_subcloud_state_client() self._mock_get_ks_client() self._mock_query() + self._mock_valid_software_deploy_state() def _update_subcloud(self, **kwargs): self.subcloud = sql_api.subcloud_update(self.ctx, self.subcloud.id, **kwargs) diff --git a/distributedcloud/dcmanager/tests/unit/orchestrator/test_sw_update_manager.py b/distributedcloud/dcmanager/tests/unit/orchestrator/test_sw_update_manager.py index 094b4855c..39cc55d49 100644 --- a/distributedcloud/dcmanager/tests/unit/orchestrator/test_sw_update_manager.py +++ b/distributedcloud/dcmanager/tests/unit/orchestrator/test_sw_update_manager.py @@ -370,6 +370,7 @@ class TestSwUpdateManager(base.DCManagerTestCase): for index, strategy_step in enumerate(strategy_step_list): self.assertEqual(subcloud_ids[index], strategy_step.subcloud_id) + @mock.patch.object(cutils, "get_systemcontroller_installed_loads") @mock.patch.object(prestage, "initial_subcloud_validate") @mock.patch.object(prestage, "global_prestage_validate") @mock.patch.object(sw_update_manager, "PatchOrchThread") @@ -378,6 +379,7 @@ class TestSwUpdateManager(base.DCManagerTestCase): mock_patch_orch_thread, mock_global_prestage_validate, mock_initial_subcloud_validate, + mock_installed_loads, ): # Create fake subclouds and respective status fake_subcloud1 = self.create_subcloud( @@ -404,6 +406,7 @@ class TestSwUpdateManager(base.DCManagerTestCase): mock_global_prestage_validate.return_value = None mock_initial_subcloud_validate.return_value = None + mock_installed_loads.return_value = ['24.09'] data = copy.copy(FAKE_SW_PRESTAGE_DATA) fake_password = (base64.b64encode("testpass".encode("utf-8"))).decode( @@ -428,6 +431,7 @@ class TestSwUpdateManager(base.DCManagerTestCase): for index, strategy_step in enumerate(strategy_step_list): self.assertEqual(subcloud_ids[index], strategy_step.subcloud_id) + @mock.patch.object(cutils, "get_systemcontroller_installed_loads") @mock.patch.object(prestage, "initial_subcloud_validate") @mock.patch.object(prestage, "global_prestage_validate") @mock.patch.object(sw_update_manager, "PatchOrchThread") @@ -436,6 +440,7 @@ class TestSwUpdateManager(base.DCManagerTestCase): mock_patch_orch_thread, mock_global_prestage_validate, mock_initial_subcloud_validate, + mock_installed_loads, ): # Create fake subclouds and respective status # Subcloud1 will be prestaged load in sync @@ -481,6 +486,7 @@ class TestSwUpdateManager(base.DCManagerTestCase): mock_global_prestage_validate.return_value = None mock_initial_subcloud_validate.return_value = None + mock_installed_loads.return_value = ['24.09'] data = copy.copy(FAKE_SW_PRESTAGE_DATA) fake_password = (base64.b64encode("testpass".encode("utf-8"))).decode( @@ -504,6 +510,7 @@ class TestSwUpdateManager(base.DCManagerTestCase): for index, strategy_step in enumerate(strategy_step_list): self.assertEqual(subcloud_ids[index], strategy_step.subcloud_id) + @mock.patch.object(cutils, "get_systemcontroller_installed_loads") @mock.patch.object(prestage, "initial_subcloud_validate") @mock.patch.object(prestage, "_get_system_controller_upgrades") @mock.patch.object(sw_update_manager, "PatchOrchThread") @@ -512,6 +519,7 @@ class TestSwUpdateManager(base.DCManagerTestCase): mock_patch_orch_thread, mock_controller_upgrade, mock_initial_subcloud_validate, + mock_installed_loads, ): # Create fake subclouds and respective status fake_subcloud1 = self.create_subcloud( @@ -538,6 +546,7 @@ class TestSwUpdateManager(base.DCManagerTestCase): mock_initial_subcloud_validate.return_value = None mock_controller_upgrade.return_value = list() + mock_installed_loads.return_value = ['24.09'] data = copy.copy(FAKE_SW_PRESTAGE_DATA) data["sysadmin_password"] = "" @@ -551,12 +560,17 @@ class TestSwUpdateManager(base.DCManagerTestCase): payload=data, ) + @mock.patch.object(cutils, "get_systemcontroller_installed_loads") @mock.patch.object(prestage, "_get_system_controller_upgrades") @mock.patch.object(sw_update_manager, "PatchOrchThread") def test_create_sw_prestage_strategy_backup_in_progress( - self, mock_patch_orch_thread, mock_controller_upgrade + self, + mock_patch_orch_thread, + mock_controller_upgrade, + mock_installed_loads, ): mock_controller_upgrade.return_value = list() + mock_installed_loads.return_value = ['24.09'] # Create fake subcloud and respective status (managed & online) fake_subcloud1 = self.create_subcloud(