Introduce multi-version auto downgrade for apps

Introduce automatic downgrade of StarlingX applications to the
multiple application version feature.

Auto downgrades are triggered by default in scenarios which the applied
application bundle is not available anymore under the applications
folder but an older version of the same app is. For instance, when
platform patches are removed and a previously available ostree is
deployed, thus restoring the old set of available apps under the
/usr/local/share/applications/helm/ directory.

A new section called 'downgrades' can be added to the metadata.yaml file
to disable the default behavior. For example:

downgrades:
  auto_downgrade: false

When auto downgrades are disabled the current applied version remains
unchanged.

Test plan:
PASS: build-pkgs -a && build-image
PASS: AIO-SX fresh install.
PASS: Apply platform-integ-apps.
      Update platform-integ-apps using a tarball that is not available
      under /usr/local/share/applications/helm/ and that does not
      contain the downgrade section.
      Confirm that platform-integ-apps is downgraded.
PASS: Apply platform-integ-apps.
      Update platform-integ-apps using a tarball that is not available
      under /usr/local/share/applications/helm/ and that has the
      auto_downgrade metadata option set to 'true'.
      Confirm that platform-integ-apps is downgraded.
PASS: Apply platform-integ-apps.
      Update platform-integ-apps using a tarball that is not available
      under /usr/local/share/applications/helm/ and that has the
      auto_downgrade metadata option set to 'false'.
      Confirm that the originally applied platform-integ-apps version
      remains unchanged.
PASS: Run a kubernetes upgrade with apps to be pre and post updated.
      Confirm that apps are successfully updated and not downgraded
      after the Kubernetes upgrade has finished.

Story: 2010929
Task: 49847

Change-Id: I33f0e0a5b8db128aef76fb93ba322364881097cf
Signed-off-by: Igor Soares <Igor.PiresSoares@windriver.com>
This commit is contained in:
Igor Soares 2024-04-09 19:34:52 -03:00
parent 82c95f934e
commit 1d228bab28
3 changed files with 43 additions and 10 deletions

View File

@ -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:

View File

@ -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'

View File

@ -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,