From 1e2073bb1abe7cb5f84ae03630a42df553c5de70 Mon Sep 17 00:00:00 2001 From: Fabricio Henrique Ramos Date: Tue, 22 Nov 2022 16:37:04 -0300 Subject: [PATCH] Add support to preserve app attributes when updating app Add support to preserve the app attributes from the old version when updating app to a new version. The key "maintain_attributes" in application metadata file indicates if the app attributes will be reused or not during update, user can specify --reuse-attributes to override the metadata preference specified by the application. Database column which stores the app attributes is called system_overrides (table helm_overrides), when attributes is mentioned in the code, it means the property stored in column system_overrides in the database. That property is shown to the user as attributes. The naming confusion will be fixed later. Test Plan: PASS: Update app without specify --reuse-attributes PASS: Update app without specify --reuse-attributes app metadata defaults to maintain_attributes=true PASS: Update app specify --reuse-attributes false PASS: Update app specify --reuse-attributes true PASS: Disabled helm chart stays disabled with update Closes-Bug: https://bugs.launchpad.net/starlingx/+bug/1998499 Signed-off-by: Fabricio Henrique Ramos Change-Id: I0f9c5c7314deb10f89853c9e5c8e15daf99580ed --- .../cgts-client/cgtsclient/v1/app_shell.py | 7 ++- .../sysinv/api/controllers/v1/kube_app.py | 17 +++++- .../sysinv/sysinv/sysinv/common/constants.py | 1 + .../sysinv/sysinv/conductor/kube_app.py | 56 ++++++++++++++++++- .../sysinv/sysinv/sysinv/conductor/manager.py | 7 ++- .../sysinv/sysinv/sysinv/conductor/rpcapi.py | 7 ++- 6 files changed, 88 insertions(+), 7 deletions(-) diff --git a/sysinv/cgts-client/cgts-client/cgtsclient/v1/app_shell.py b/sysinv/cgts-client/cgts-client/cgtsclient/v1/app_shell.py index cec3f37615..e5aa0a4c82 100644 --- a/sysinv/cgts-client/cgts-client/cgtsclient/v1/app_shell.py +++ b/sysinv/cgts-client/cgts-client/cgtsclient/v1/app_shell.py @@ -144,11 +144,16 @@ def do_application_upload(cc, args): help=('Reuse user overrides when updating application' 'to a new version. It will supersede the metadata ' 'preference specified by the application.')) +@utils.arg('--reuse-attributes', + metavar='', + help=('Reuse attributes when updating application ' + 'to a new version. It will supersede the metadata ' + 'preference specified by the application.')) def do_application_update(cc, args): """Update the deployed application to a different version""" data = _application_check(args) - fields_list = ['reuse_user_overrides'] + fields_list = ['reuse_user_overrides', 'reuse_attributes'] fields = dict((k, v) for (k, v) in vars(args).items() if k in fields_list and not (v is None)) data.update(fields) diff --git a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/kube_app.py b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/kube_app.py index efac5ac122..4b5df2b16b 100644 --- a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/kube_app.py +++ b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/kube_app.py @@ -436,6 +436,19 @@ class KubeAppController(rest.RestController): "Application-update rejected: " "invalid reuse_user_overrides setting.")) + reuse_attributes_flag = body.get('reuse_attributes', None) + if reuse_attributes_flag is None: + # None means let the application decide + reuse_attributes = None + elif reuse_attributes_flag in ['true', 'True']: + reuse_attributes = True + elif reuse_attributes_flag in ['false', 'False']: + reuse_attributes = False + else: + raise wsme.exc.ClientSideError(_( + "Application-update rejected: " + "invalid reuse_attributes setting.")) + try: applied_app = objects.kube_app.get_by_name(pecan.request.context, name) except exception.KubeAppNotFound: @@ -525,7 +538,9 @@ class KubeAppController(rest.RestController): pecan.request.rpcapi.perform_app_update(pecan.request.context, applied_app, target_app, tarfile, operation, - lifecycle_hook_info, reuse_overrides) + lifecycle_hook_info, + reuse_overrides, + reuse_attributes) return KubeApp.convert_with_links(target_app) diff --git a/sysinv/sysinv/sysinv/sysinv/common/constants.py b/sysinv/sysinv/sysinv/sysinv/common/constants.py index ae5ee52949..f13dce9570 100644 --- a/sysinv/sysinv/sysinv/sysinv/common/constants.py +++ b/sysinv/sysinv/sysinv/sysinv/common/constants.py @@ -1820,6 +1820,7 @@ HOOK_PARAMETERS_MAP = { } # Application metadata constants +APP_METADATA_MAINTAIN_ATTRIBUTES = 'maintain_attributes' APP_METADATA_MAINTAIN_USER_OVERRIDES = 'maintain_user_overrides' APP_METADATA_APPLY_PROGRESS_ADJUST = 'apply_progress_adjust' APP_METADATA_APPLY_PROGRESS_ADJUST_DEFAULT_VALUE = 0 diff --git a/sysinv/sysinv/sysinv/sysinv/conductor/kube_app.py b/sysinv/sysinv/sysinv/sysinv/conductor/kube_app.py index 1a6d009a07..937049370c 100644 --- a/sysinv/sysinv/sysinv/sysinv/conductor/kube_app.py +++ b/sysinv/sysinv/sysinv/sysinv/conductor/kube_app.py @@ -1657,6 +1657,47 @@ class AppOperator(object): "Chart %s from version %s" % (to_app.name, to_app.version, chart.name, from_app.version)) + def _preserve_attributes(self, from_app, to_app): + """ + In the scenario of updating application to a new version, this + method is used to copy the attributes from the old version + to the new version. + + :param from_app: application object that application updating from + :param to_app: application object that application updating to + """ + to_db_app = self._dbapi.kube_app_get(to_app.name) + from_db_app = self._dbapi.kube_app_get_inactive_by_name_version( + from_app.name, version=from_app.version) + + from_app_db_charts = self._dbapi.helm_override_get_all(from_db_app.id) + from_app_charts = {} + for chart in from_app_db_charts: + from_app_charts.setdefault(chart.name, {}).update( + {chart.namespace: chart.system_overrides}) + + for chart in to_app.charts: + if (chart.name in from_app_charts and + chart.namespace in from_app_charts[chart.name] and + from_app_charts[chart.name][chart.namespace]): + system_overrides = {'system_overrides': from_app_charts[chart.name][chart.namespace]} + try: + self._dbapi.helm_override_update( + app_id=to_db_app.id, name=chart.name, + namespace=chart.namespace, values=system_overrides) + except exception.HelmOverrideNotFound: + # Unexpected + values = { + 'name': chart.name, + 'namespace': chart.namespace, + 'app_id': to_db_app.id + } + values.update(system_overrides) + self._dbapi.helm_override_create(values=values) + LOG.info("Application %s (%s) will apply the attributes for" + "Chart %s from version %s" % (to_app.name, to_app.version, + chart.name, from_app.version)) + def _make_app_request(self, app, request, overrides_str=None): if app.is_fluxcd_app: return self._make_fluxcd_operation_with_monitor(app, request) @@ -3056,7 +3097,8 @@ class AppOperator(object): return False def perform_app_update(self, from_rpc_app, to_rpc_app, tarfile, - operation, lifecycle_hook_info_app_update, reuse_user_overrides=None): + operation, lifecycle_hook_info_app_update, reuse_user_overrides=None, + reuse_attributes=None): """Process application update request This method leverages the existing application upload workflow to @@ -3084,6 +3126,7 @@ class AppOperator(object): :param operation: apply or rollback :param lifecycle_hook_info_app_update: LifecycleHookInfo object :param reuse_user_overrides: (optional) True or False + :param reuse_attributes: (optional) True or False """ @@ -3164,6 +3207,17 @@ class AppOperator(object): if reuse_overrides: self._preserve_user_overrides(from_app, to_app) + reuse_app_attributes = \ + self._get_metadata_value(to_app, + constants.APP_METADATA_MAINTAIN_ATTRIBUTES, + False) + if reuse_attributes is not None: + reuse_app_attributes = reuse_attributes + + # Preserve attributes for the new app + if reuse_app_attributes: + self._preserve_attributes(from_app, to_app) + # The app_apply will generate new versioned overrides for the # app upgrade and will enable the new plugins for that version. diff --git a/sysinv/sysinv/sysinv/sysinv/conductor/manager.py b/sysinv/sysinv/sysinv/sysinv/conductor/manager.py index 62b28f09ee..f5bf9d425b 100644 --- a/sysinv/sysinv/sysinv/sysinv/conductor/manager.py +++ b/sysinv/sysinv/sysinv/sysinv/conductor/manager.py @@ -14254,7 +14254,8 @@ class ConductorManager(service.PeriodicService): return app_applied def perform_app_update(self, context, from_rpc_app, to_rpc_app, tarfile, - operation, lifecycle_hook_info_app_update, reuse_user_overrides=None): + operation, lifecycle_hook_info_app_update, reuse_user_overrides=None, + reuse_attributes=None): """Handling of application update request (via AppOperator) :param context: request context. @@ -14266,12 +14267,14 @@ class ConductorManager(service.PeriodicService): :param operation: apply or rollback :param lifecycle_hook_info_app_update: LifecycleHookInfo object :param reuse_user_overrides: (optional) True or False + :param reuse_attributes: (optional) True or False """ lifecycle_hook_info_app_update.operation = constants.APP_UPDATE_OP self._app.perform_app_update(from_rpc_app, to_rpc_app, tarfile, - operation, lifecycle_hook_info_app_update, reuse_user_overrides) + operation, lifecycle_hook_info_app_update, reuse_user_overrides, + reuse_attributes) def perform_app_remove(self, context, rpc_app, lifecycle_hook_info_app_remove, force=False): """Handling of application removal request (via AppOperator) diff --git a/sysinv/sysinv/sysinv/sysinv/conductor/rpcapi.py b/sysinv/sysinv/sysinv/sysinv/conductor/rpcapi.py index a6cf741ff0..d978398872 100644 --- a/sysinv/sysinv/sysinv/sysinv/conductor/rpcapi.py +++ b/sysinv/sysinv/sysinv/sysinv/conductor/rpcapi.py @@ -1786,7 +1786,8 @@ class ConductorAPI(sysinv.openstack.common.rpc.proxy.RpcProxy): lifecycle_hook_info_app_apply=lifecycle_hook_info)) def perform_app_update(self, context, from_rpc_app, to_rpc_app, tarfile, - operation, lifecycle_hook_info, reuse_user_overrides=None): + operation, lifecycle_hook_info, reuse_user_overrides=None, + reuse_attributes=None): """Handle application update request :param context: request context. @@ -1799,6 +1800,7 @@ class ConductorAPI(sysinv.openstack.common.rpc.proxy.RpcProxy): :param lifecycle_hook_info: LifecycleHookInfo object :param reuse_user_overrides: (optional) True or False + :param reuse_attributes: (optional) True or False """ return self.cast(context, self.make_msg('perform_app_update', @@ -1807,7 +1809,8 @@ class ConductorAPI(sysinv.openstack.common.rpc.proxy.RpcProxy): tarfile=tarfile, operation=operation, lifecycle_hook_info_app_update=lifecycle_hook_info, - reuse_user_overrides=reuse_user_overrides)) + reuse_user_overrides=reuse_user_overrides, + reuse_attributes=reuse_attributes)) def perform_app_remove(self, context, rpc_app, lifecycle_hook_info, force=False): """Handle application remove request