From a26ff567761926f8c3c42811d89023c82ae96112 Mon Sep 17 00:00:00 2001 From: Igor Soares Date: Thu, 16 Feb 2023 15:24:39 -0500 Subject: [PATCH] Promote apps desired state on application apply Promote application desired state from "uploaded" to "applied" during application apply and demote during application removal. If the original application desired state is "uploaded" the corresponding application metadata field on the database is updated to "applied" during the apply operation. Correspondingly, the desired state is reinstated to "uploaded" during removal. This ensures that applications will remain applied across sysinv restarts, fixing a bug that caused apps to return to uploaded state if the conductor was restarted during a reapply operation. Test Plan: PASS: build-pkgs PASS: build-image PASS: AIO-SX full deployment PASS: upload/apply/remove/delete platform-integ-apps PASS: upload/apply/remove/delete snmp PASS: upload/apply/remove/delete cert-manager with "desired_state" metadata set as "uploaded" PASS: restart sysinv-conductor during cert-manager apply/reapply operations Closes-Bug: 2008014 Signed-off-by: Igor Soares Change-Id: I6b64d3d3983a0571014168124cd61416779d598f --- .../sysinv/sysinv/conductor/kube_app.py | 111 ++++++++++++++++-- 1 file changed, 102 insertions(+), 9 deletions(-) diff --git a/sysinv/sysinv/sysinv/sysinv/conductor/kube_app.py b/sysinv/sysinv/sysinv/sysinv/conductor/kube_app.py index 379f950d06..6438287864 100644 --- a/sysinv/sysinv/sysinv/sysinv/conductor/kube_app.py +++ b/sysinv/sysinv/sysinv/sysinv/conductor/kube_app.py @@ -2801,6 +2801,26 @@ class AppOperator(object): app.name, metadata) + @staticmethod + def retrieve_application_metadata_from_file(sync_metadata_file): + """ Retrieve application metadata from the metadata file of the app + + :param sync_metadata_file: metadata file path + + :return dictionary: metadata fields and respective values + """ + + metadata = {} + if os.path.exists(sync_metadata_file): + with io.open(sync_metadata_file, 'r', encoding='utf-8') as f: + # The RoundTripLoader removes the superfluous quotes by default. + # Set preserve_quotes=True to preserve all the quotes. + # The assumption here: there is just one yaml section + metadata = yaml.load( + f, Loader=yaml.RoundTripLoader, preserve_quotes=True) or {} + + return metadata + def load_application_metadata_from_file(self, rpc_app): """ Load the application metadata from the metadata file of the app @@ -2811,15 +2831,7 @@ class AppOperator(object): "".format(rpc_app.name)) app = AppOperator.Application(rpc_app) - metadata = {} - - if os.path.exists(app.sync_metadata_file): - with io.open(app.sync_metadata_file, 'r', encoding='utf-8') as f: - # The RoundTripLoader removes the superfluous quotes by default. - # Set preserve_quotes=True to preserve all the quotes. - # The assumption here: there is just one yaml section - metadata = yaml.load( - f, Loader=yaml.RoundTripLoader, preserve_quotes=True) or {} + metadata = self.retrieve_application_metadata_from_file(app.sync_metadata_file) AppOperator.update_and_process_app_metadata(self._apps_metadata, app.name, @@ -2829,6 +2841,56 @@ class AppOperator(object): rpc_app.app_metadata = metadata rpc_app.save() + @staticmethod + def get_desired_state_from_metadata(app_metadata): + """ Retrieve desired state from application metadata + + :param app_metadata: full application metadata + + :return string: desired application state + """ + + desired_state = None + behavior = app_metadata.get(constants.APP_METADATA_BEHAVIOR, None) + if behavior is not None: + desired_state = behavior.get(constants.APP_METADATA_DESIRED_STATE, None) + + return desired_state + + def update_desired_state(self, app, required_desired_state, new_desired_state): + """ Update application desired state + + This method updates the application 'desired_state' + metadata field on the database. + + :param app: AppOperator application object + :param required_desired_state: desired state the app is required + to have in the database + :param new_desired_state: new desired state that will be saved + to the database + """ + + current_desired_state = self.get_desired_state_from_metadata(app.app_metadata) + + if current_desired_state == required_desired_state: + metadata = copy.deepcopy(app.app_metadata) + + if new_desired_state is None and \ + constants.APP_METADATA_BEHAVIOR in metadata and \ + constants.APP_METADATA_DESIRED_STATE in metadata[constants.APP_METADATA_BEHAVIOR]: + del metadata[ + constants.APP_METADATA_BEHAVIOR][ + constants.APP_METADATA_DESIRED_STATE] + else: + metadata[ + constants.APP_METADATA_BEHAVIOR][ + constants.APP_METADATA_DESIRED_STATE] = new_desired_state + + app.update_app_metadata(metadata) + AppOperator.update_and_process_app_metadata(self._apps_metadata, + app.name, + metadata) + def perform_app_apply(self, rpc_app, mode, lifecycle_hook_info_app_apply, caller=None): """Process application install request @@ -2854,6 +2916,17 @@ class AppOperator(object): :return boolean: whether application apply was successful """ + def promote_desired_state(app): + """ Promote application desired state from uploaded to applied + + This method makes sure that applied apps will keep the 'applied' + state when reapplying them across sysinv-conductor restarts. + + :param app: AppOperator application object + """ + + self.update_desired_state(app, constants.APP_UPLOAD_SUCCESS, constants.APP_APPLY_SUCCESS) + app = AppOperator.Application(rpc_app) # If apply is called from update method, the app's abort status has @@ -2880,6 +2953,9 @@ class AppOperator(object): if AppOperator.is_app_aborted(app.name): raise exception.KubeAppAbort() + # Promote desired state if needed + promote_desired_state(app) + # Perform app resources actions lifecycle_hook_info_app_apply.relative_timing = constants.APP_LIFECYCLE_TIMING_PRE lifecycle_hook_info_app_apply.lifecycle_type = constants.APP_LIFECYCLE_TYPE_RESOURCE @@ -3269,6 +3345,20 @@ class AppOperator(object): :return boolean: whether application remove was successful """ + def demote_desired_state(app): + """ Demote application desired state + + This method demotes applications that were promoted to the 'applied' + desired state back to their original desired state. + + :param app: AppOperator application object + """ + + metadata = self.retrieve_application_metadata_from_file(app.sync_metadata_file) + original_desired_state = self.get_desired_state_from_metadata(metadata) + + self.update_desired_state(app, constants.APP_APPLY_SUCCESS, original_desired_state) + app = AppOperator.Application(rpc_app) self._register_app_abort(app.name) @@ -3323,6 +3413,9 @@ class AppOperator(object): self._dbapi.kube_app_destroy(app.name, inactive=True) try: + # Restore original desired state if needed + demote_desired_state(app) + # Perform rbd actions lifecycle_hook_info_app_remove.relative_timing = constants.APP_LIFECYCLE_TIMING_POST lifecycle_hook_info_app_remove.lifecycle_type = constants.APP_LIFECYCLE_TYPE_RBD