diff --git a/sysinv/sysinv/sysinv/sysinv/common/app_metadata.py b/sysinv/sysinv/sysinv/sysinv/common/app_metadata.py index 686032344c..a82de4b6b6 100644 --- a/sysinv/sysinv/sysinv/sysinv/common/app_metadata.py +++ b/sysinv/sysinv/sysinv/sysinv/common/app_metadata.py @@ -125,7 +125,7 @@ def validate_metadata_file(path, metadata_file, upgrade_from_release=None): if not error_message: error_message = _("Invalid boolean value: {}" .format(value)) - raise exception.SysinvException(error_message) + raise exception.SysinvException(error_message) def validate_dict(value, error_message=None): """Validate dictionary types""" @@ -381,6 +381,13 @@ def validate_metadata_file(path, metadata_file, upgrade_from_release=None): for version in from_versions: validate_string(version) + # Downgrades section validation + downgrades = validate_dict_field(doc, constants.APP_METADATA_DOWNGRADES) + if downgrades: + validate_boolstr_field( + downgrades, + constants.APP_METADATA_AUTO_DOWNGRADE) + # Kubernetes version section validation k8s_version = validate_k8s_version(doc) if k8s_version: diff --git a/sysinv/sysinv/sysinv/sysinv/common/constants.py b/sysinv/sysinv/sysinv/sysinv/common/constants.py index e6aad8cb51..ed22d1ae18 100644 --- a/sysinv/sysinv/sysinv/sysinv/common/constants.py +++ b/sysinv/sysinv/sysinv/sysinv/common/constants.py @@ -1982,6 +1982,9 @@ APP_METADATA_UPGRADES = 'upgrades' APP_METADATA_UPDATE_FAILURE_SKIP_RECOVERY = 'update_failure_no_rollback' APP_METADATA_AUTO_UPDATE = 'auto_update' APP_METADATA_AUTO_UPDATE_DEFAULT_VALUE = True +APP_METADATA_DOWNGRADES = 'downgrades' +APP_METADATA_AUTO_DOWNGRADE = 'auto_downgrade' +APP_METADATA_AUTO_DOWNGRADE_DEFAULT_VALUE = True APP_METADATA_FAILED_VERSIONS = 'failed_versions' APP_METADATA_FROM_VERSIONS = 'from_versions' APP_METADATA_SUPPORTED_K8S_VERSION = 'supported_k8s_version' diff --git a/sysinv/sysinv/sysinv/sysinv/conductor/manager.py b/sysinv/sysinv/sysinv/sysinv/conductor/manager.py index e6626f05e1..8536a32989 100644 --- a/sysinv/sysinv/sysinv/sysinv/conductor/manager.py +++ b/sysinv/sysinv/sysinv/sysinv/conductor/manager.py @@ -53,6 +53,7 @@ import xml.etree.ElementTree as ElementTree from contextlib import contextmanager from datetime import datetime from datetime import timedelta +from distutils.util import strtobool from distutils.version import LooseVersion from copy import deepcopy from urllib3.exceptions import MaxRetryError @@ -7782,6 +7783,11 @@ class ConductorManager(service.PeriodicService): True, k8s_upgrade_timing) + auto_downgrade = strtobool(app.app_metadata.get(constants.APP_METADATA_DOWNGRADES, {}) + .get(constants.APP_METADATA_AUTO_DOWNGRADE, + str(constants.APP_METADATA_AUTO_DOWNGRADE_DEFAULT_VALUE))) + latest_downgrade_bundle = None + available_versions = set() latest_version_bundle = None if k8s_version is None: @@ -7790,15 +7796,8 @@ class ConductorManager(service.PeriodicService): k8s_version = k8s_version.strip().lstrip('v') for bundle_metadata in bundle_metadata_list: - if LooseVersion(bundle_metadata.version) <= LooseVersion(app.app_version): - LOG.debug("Bundle {} version {} lower than installed app version ({})" - .format(bundle_metadata.file_path, - bundle_metadata.version, - app.app_version)) - elif not bundle_metadata.auto_update: - LOG.debug("Application auto update disabled for bundle {}" - .format(bundle_metadata.file_path)) - elif LooseVersion(k8s_version) < LooseVersion(bundle_metadata.k8s_minimum_version): + available_versions.add(bundle_metadata.version) + if LooseVersion(k8s_version) < LooseVersion(bundle_metadata.k8s_minimum_version): LOG.debug("Kubernetes version {} is lower than {} which is " "the minimum required for bundle {}" .format(k8s_version, @@ -7811,6 +7810,21 @@ class ConductorManager(service.PeriodicService): .format(k8s_version, bundle_metadata.k8s_maximum_version, bundle_metadata.file_path)) + elif LooseVersion(bundle_metadata.version) == LooseVersion(app.app_version): + LOG.debug("Bundle {} version and installed app version are the same ({})" + .format(bundle_metadata.file_path, + app.app_version)) + elif LooseVersion(bundle_metadata.version) < LooseVersion(app.app_version): + LOG.debug("Bundle {} version {} is lower than installed app version ({})" + .format(bundle_metadata.file_path, + bundle_metadata.version, + app.app_version)) + if (latest_downgrade_bundle is None or LooseVersion(bundle_metadata.version) > + LooseVersion(latest_downgrade_bundle.version)): + latest_downgrade_bundle = bundle_metadata + elif not bundle_metadata.auto_update: + LOG.debug("Application auto update disabled for bundle {}" + .format(bundle_metadata.file_path)) elif ((latest_version_bundle is None) or (LooseVersion(bundle_metadata.version) > LooseVersion(latest_version_bundle.version))): @@ -7818,6 +7832,15 @@ class ConductorManager(service.PeriodicService): # of the current one is higher than the one previously set. latest_version_bundle = bundle_metadata + # Downgrade if the installed app version is not available anymore and an older compatible + # bundle is available instead. + if (auto_downgrade and + app.app_version not in available_versions and + latest_downgrade_bundle is not None): + LOG.info("Application {} will be downgraded from version {} to {}" + .format(app.name, app.app_version, latest_downgrade_bundle.version)) + return latest_downgrade_bundle + return latest_version_bundle def _auto_update_app(self,