diff --git a/sysinv/sysinv/sysinv/sysinv/common/constants.py b/sysinv/sysinv/sysinv/sysinv/common/constants.py index 693467b097..4e24ff1f00 100644 --- a/sysinv/sysinv/sysinv/sysinv/common/constants.py +++ b/sysinv/sysinv/sysinv/sysinv/common/constants.py @@ -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' diff --git a/sysinv/sysinv/sysinv/sysinv/common/kubernetes.py b/sysinv/sysinv/sysinv/sysinv/common/kubernetes.py index e770b5ed31..aecbccbd95 100644 --- a/sysinv/sysinv/sysinv/sysinv/common/kubernetes.py +++ b/sysinv/sysinv/sysinv/sysinv/common/kubernetes.py @@ -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 diff --git a/sysinv/sysinv/sysinv/sysinv/common/utils.py b/sysinv/sysinv/sysinv/sysinv/common/utils.py index eb936fe9a7..2ae1b41e3c 100644 --- a/sysinv/sysinv/sysinv/sysinv/common/utils.py +++ b/sysinv/sysinv/sysinv/sysinv/common/utils.py @@ -2224,6 +2224,9 @@ def find_metadata_file(path, metadata_file, upgrade_from_release=None): supported_k8s_version: minimum: maximum: + k8s_upgrades: + auto_update: + timing:
     supported_releases:
       :
       - 
@@ -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]
diff --git a/sysinv/sysinv/sysinv/sysinv/conductor/manager.py b/sysinv/sysinv/sysinv/sysinv/conductor/manager.py
index d6e085487f..7419b419c3 100644
--- a/sysinv/sysinv/sysinv/sysinv/conductor/manager.py
+++ b/sysinv/sysinv/sysinv/sysinv/conductor/manager.py
@@ -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)