Introduce Kubernetes upgrade metadata for stx apps

This commit handles Kubernetes upgrade related metadata for StarlingX
applications. The metadata retrieved is parsed, validated and
stored into the appropriated variables for future use.

The new metadata section introduced has the following form:
k8s_upgrades:
  auto_update: true/false
  timing: pre/post

This new block aims to inform to the Application Framework whether apps
should be automatically updated (auto_update: true/false) if a
Kubernetes upgrade is taking place. It also informs when applications
should be updated, either during kube-upgrade-start (timing: pre) or
during kube-upgrade-complete (timing: post).

In addition, improvements were made to the already existing metadata
section:
supported_k8s_version:
  minimum: <version>
  maximum: <version>

A bug was found on the existing method that checks the supported
Kubernetes version. An exception was being raised when comparing
different formats such as 'v1.0.0' and '1.0.0'. This bug was fixed by
standardizing the formats on the comparison code.

It is not the goal of this commit to implement the logic to check
whether an app should be updated based on the active Kubernetes version.

Test plan:
PASS: Create a test application containing new valid metadata
      Upload the test application
      Apply the test application
PASS: Create a test application without supported_k8s_version:minimum
      Upload the test application
      Check if a warning message was raised on the logs
PASS: Create a test application without supported_k8s_version:minimum
      Move test application tarball to Helm applications folder
      Wait for the auto update process to start
      Check if a warning message was raised on the logs
      Check if the application was successfully updated
PASS: Create a test application without the k8s_upgrades section
      Check if k8s_upgrades:auto_update defaults to true
      Check if k8s_upgrades:timing defaults to false
      Check if a warning message was raised on the logs
      Check if the application was successfully updated

Story: 2010929
Task: 48929

Change-Id: I54362b036b25b6f42a18a2a29e43e2936a8a328d
Signed-off-by: Igor Soares <Igor.PiresSoares@windriver.com>
This commit is contained in:
Igor Soares 2023-10-06 17:53:16 -03:00 committed by Igor Pires Soares
parent 74b727e502
commit 3511174f95
4 changed files with 131 additions and 12 deletions

View File

@ -1958,6 +1958,10 @@ APP_METADATA_SUPPORTED_K8S_VERSION = 'supported_k8s_version'
APP_METADATA_SUPPORTED_RELEASES = 'supported_releases'
APP_METADATA_MINIMUM = 'minimum'
APP_METADATA_MAXIMUM = 'maximum'
APP_METADATA_K8S_UPGRADES = 'k8s_upgrades'
APP_METADATA_K8S_AUTO_UPDATE_DEFAULT_VALUE = True
APP_METADATA_TIMING = 'timing'
APP_METADATA_TIMING_DEFAULT_VALUE = 'post'
APP_EVALUATE_REAPPLY_TYPE_HOST_ADD = 'host-add'
APP_EVALUATE_REAPPLY_TYPE_HOST_DELETE = 'host-delete'

View File

@ -266,8 +266,12 @@ def is_kube_version_supported(kube_version, min_version=None, max_version=None):
:returns bool: True if k8s version is supported
"""
if ((min_version is not None and LooseVersion(kube_version) < LooseVersion(min_version)) or
(max_version is not None and LooseVersion(kube_version) > LooseVersion(max_version))):
kube_version = kube_version.strip().lstrip('v')
if ((min_version is not None and LooseVersion(kube_version) <
LooseVersion(min_version.strip().lstrip('v'))) or
(max_version is not None and LooseVersion(kube_version) >
LooseVersion(max_version.strip().lstrip('v')))):
return False
return True

View File

@ -2224,6 +2224,9 @@ def find_metadata_file(path, metadata_file, upgrade_from_release=None):
supported_k8s_version:
minimum: <version>
maximum: <version>
k8s_upgrades:
auto_update: <true/false/yes/no>
timing: <pre/post>
supported_releases:
<release>:
- <patch.1>
@ -2278,6 +2281,42 @@ def find_metadata_file(path, metadata_file, upgrade_from_release=None):
a monitoring task.
Default value is zero (no adjustment)
"""
def verify_auto_update(parent):
""" Validate the auto_update field of a given parent section
:param parent: parent section that contains the auto_update field
to be verified
"""
try:
auto_update = \
parent[constants.APP_METADATA_AUTO_UPDATE]
if not is_valid_boolstr(auto_update):
raise exception.SysinvException(_(
"Invalid {}: {} expected value is either 'true' "
"or 'false'."
"".format(metadata_file,
constants.APP_METADATA_AUTO_UPDATE)))
except KeyError:
pass
def verify_timing(parent):
""" Validate the timing field of a given parent section
:param parent: parent section that contains the timing field
to be verified
"""
try:
timing = \
parent[constants.APP_METADATA_TIMING]
if timing != "pre" and timing != "post":
raise exception.SysinvException(_(
"Invalid {}: {} expected value is either 'pre' or 'post'."
"".format(metadata_file,
constants.APP_METADATA_TIMING)))
except KeyError:
pass
app_name = ''
app_version = ''
patches = []
@ -2447,16 +2486,7 @@ def find_metadata_file(path, metadata_file, upgrade_from_release=None):
except KeyError:
pass
try:
auto_update = \
upgrades[constants.APP_METADATA_AUTO_UPDATE]
if not is_valid_boolstr(auto_update):
raise exception.SysinvException(_(
"Invalid {}: {} expected value is a boolean string."
"".format(metadata_file,
constants.APP_METADATA_AUTO_UPDATE)))
except KeyError:
pass
verify_auto_update(upgrades)
try:
from_versions = upgrades[constants.APP_METADATA_FROM_VERSIONS]
@ -2511,6 +2541,22 @@ def find_metadata_file(path, metadata_file, upgrade_from_release=None):
except KeyError:
pass
k8s_upgrades = None
try:
k8s_upgrades = doc[constants.APP_METADATA_K8S_UPGRADES]
if not isinstance(k8s_upgrades, dict):
raise exception.SysinvException(_(
"Invalid {}: {} should be a dict."
"".format(metadata_file,
constants.APP_METADATA_K8S_UPGRADES)))
except KeyError:
pass
if k8s_upgrades:
verify_auto_update(k8s_upgrades)
verify_timing(k8s_upgrades)
supported_releases = {}
try:
supported_releases = doc[constants.APP_METADATA_SUPPORTED_RELEASES]

View File

@ -7043,6 +7043,66 @@ class ConductorManager(service.PeriodicService):
self._inner_sync_auto_apply(context, app_name)
@staticmethod
def check_app_k8s_auto_update(app_name, tarball):
""" Check whether an application should be automatically updated
based on its Kubernetes upgrade metadata fields.
:param tarball: tarball object of the application to be checked
"""
minimum_supported_k8s_version = tarball.metadata.get(
constants.APP_METADATA_SUPPORTED_K8S_VERSION, {}).get(
constants.APP_METADATA_MINIMUM, None)
if minimum_supported_k8s_version is None:
# TODO: Turn this into an error message rather than a warning
# when the k8s app upgrade implementation is in place. Also,
# return False in this scenario.
LOG.warning("Minimum supported Kubernetes version missing from "
"{} metadata".format(app_name))
else:
LOG.debug("minimum_supported_k8s_version for {}: {}"
.format(app_name, minimum_supported_k8s_version))
maximum_supported_k8s_version = tarball.metadata.get(
constants.APP_METADATA_SUPPORTED_K8S_VERSION, {}).get(
constants.APP_METADATA_MAXIMUM, None)
if maximum_supported_k8s_version:
LOG.debug("maximum_supported_k8s_version for {}: {}"
.format(app_name, maximum_supported_k8s_version))
k8s_upgrades = tarball.metadata.get(
constants.APP_METADATA_K8S_UPGRADES, None)
if k8s_upgrades is None:
k8s_auto_update = constants.APP_METADATA_K8S_AUTO_UPDATE_DEFAULT_VALUE
k8s_update_timing = constants.APP_METADATA_TIMING_DEFAULT_VALUE
LOG.warning("k8s_upgrades section missing from {} metadata"
.format(app_name))
else:
k8s_auto_update = tarball.metadata.get(
constants.APP_METADATA_K8S_UPGRADES).get(
constants.APP_METADATA_AUTO_UPDATE,
constants.APP_METADATA_K8S_AUTO_UPDATE_DEFAULT_VALUE)
k8s_update_timing = tarball.metadata.get(
constants.APP_METADATA_K8S_UPGRADES).get(
constants.APP_METADATA_TIMING,
constants.APP_METADATA_TIMING_DEFAULT_VALUE)
# TODO: check if the application meets the criteria to be updated
# according to the 'supported_k8s_version' and 'k8s_upgrades'
# metadata sections. This initial implementation is only intended to
# set the default values for each entry.
LOG.debug("k8s_auto_update value for {}: {}"
.format(app_name, k8s_auto_update))
LOG.debug("k8s_update_timing value for {}: {}"
.format(app_name, k8s_update_timing))
return True
def _auto_update_app(self, context, app_name, managed_app):
"""Auto update applications"""
try:
@ -7129,6 +7189,11 @@ class ConductorManager(service.PeriodicService):
% (app.name, tarball.app_version, app.app_version))
return
# Check if the update should proceed based on the application's
# Kubernetes metadata
if not ConductorManager.check_app_k8s_auto_update(app_name, tarball):
return
self._inner_sync_auto_update(context, app, tarball)
@cutils.synchronized(LOCK_APP_AUTO_MANAGE)