From b978111f197599dd82e4a795bb9b2fdcd398f4fb Mon Sep 17 00:00:00 2001 From: Angie Wang Date: Fri, 17 May 2019 16:43:12 -0400 Subject: [PATCH] Application dirs and overrides updates This commit includes the following changes to support updating versioned application: - helm application overrides are tied to a specific application - each application files are uploaded to its own versioned directory e.g. stx-openstack-1.0-13 app files will be located at, /opt/platform/armada/19.01/stx-openstack/1.0-13/... /opt/platform/helm/19.01/stx-openstack/1.0-13/... /scratch/apps/stx-openstack/1.0-13/... Story: 2005350 Task: 33439 Change-Id: I75555deda57181d069f24d458dda5bf08e7e17cc Signed-off-by: Angie Wang --- sysinv/cgts-client/centos/build_srpm.data | 2 +- .../cgts-client/cgtsclient/v1/app.py | 10 ++ .../cgts-client/cgtsclient/v1/helm.py | 25 ++-- .../cgts-client/cgtsclient/v1/helm_shell.py | 62 ++++++--- sysinv/sysinv/centos/build_srpm.data | 2 +- .../sysinv/api/controllers/v1/helm_charts.py | 72 ++++++---- sysinv/sysinv/sysinv/sysinv/cmd/helm.py | 14 +- .../sysinv/sysinv/conductor/kube_app.py | 130 +++++++++++------- .../sysinv/sysinv/sysinv/db/sqlalchemy/api.py | 23 ++-- ...application.py => 073_kube_application.py} | 2 +- ...elm_overrides.py => 081_helm_overrides.py} | 8 +- ...rrides.py => 082_helm_system_overrides.py} | 0 .../sysinv/sysinv/db/sqlalchemy/models.py | 4 +- sysinv/sysinv/sysinv/sysinv/helm/helm.py | 46 ++++--- sysinv/sysinv/sysinv/sysinv/helm/openstack.py | 14 +- .../sysinv/sysinv/objects/helm_overrides.py | 8 +- 16 files changed, 270 insertions(+), 152 deletions(-) rename sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/{082_kube_application.py => 073_kube_application.py} (96%) rename sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/{073_helm_overrides.py => 081_helm_overrides.py} (85%) rename sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/{081_helm_system_overrides.py => 082_helm_system_overrides.py} (100%) diff --git a/sysinv/cgts-client/centos/build_srpm.data b/sysinv/cgts-client/centos/build_srpm.data index 3a42e23c12..3605077812 100644 --- a/sysinv/cgts-client/centos/build_srpm.data +++ b/sysinv/cgts-client/centos/build_srpm.data @@ -1,2 +1,2 @@ SRC_DIR="cgts-client" -TIS_PATCH_VER=65 +TIS_PATCH_VER=66 diff --git a/sysinv/cgts-client/cgts-client/cgtsclient/v1/app.py b/sysinv/cgts-client/cgts-client/cgtsclient/v1/app.py index 6b828e869a..3dbe0e9a58 100644 --- a/sysinv/cgts-client/cgts-client/cgtsclient/v1/app.py +++ b/sysinv/cgts-client/cgts-client/cgtsclient/v1/app.py @@ -7,6 +7,7 @@ # from cgtsclient.common import base +from cgtsclient import exc class App(base.Resource): @@ -66,3 +67,12 @@ class AppManager(base.Manager): :param name: app_name """ return self._delete(self._path(app_name)) + + +def _find_app(cc, app_name): + try: + app = cc.app.get(app_name) + except exc.HTTPNotFound: + raise exc.CommandError('Application not found: %s' % app_name) + else: + return app diff --git a/sysinv/cgts-client/cgts-client/cgtsclient/v1/helm.py b/sysinv/cgts-client/cgts-client/cgtsclient/v1/helm.py index 92b6c080cd..c2c769309f 100644 --- a/sysinv/cgts-client/cgts-client/cgtsclient/v1/helm.py +++ b/sysinv/cgts-client/cgts-client/cgtsclient/v1/helm.py @@ -21,17 +21,18 @@ class HelmManager(base.Manager): def _path(name=''): return '/v1/helm_charts/%s' % name - def list_charts(self): + def list_charts(self, app): """Get list of charts For each chart it will show any overrides for that chart along with the namespace of the overrides. """ - return self._list(self._path(), 'charts') + return self._list(self._path() + '?app_name=' + app, 'charts') - def get_overrides(self, name, namespace): + def get_overrides(self, app, name, namespace): """Get overrides for a given chart. + :param app_name: name of application :param name: name of the chart :param namespace: namespace for the chart overrides @@ -39,14 +40,17 @@ class HelmManager(base.Manager): specified chart. """ try: - return self._list(self._path(name) + '?namespace=' + namespace)[0] + return self._list(self._path(app) + + '?name=' + name + + '&namespace=' + namespace)[0] except IndexError: return None - def update_overrides(self, name, namespace, + def update_overrides(self, app, name, namespace, flag='reset', override_values={}): """Update overrides for a given chart. + :param app_name: name of application :param name: name of the chart :param namespace: namespace for the chart overrides :param flag: 'reuse' or 'reset' to indicate how to handle existing @@ -56,12 +60,17 @@ class HelmManager(base.Manager): This will return the end-user overrides for the specified chart. """ body = {'flag': flag, 'values': override_values} - return self._update(self._path(name) + '?namespace=' + namespace, body) + return self._update(self._path(app) + + '?name=' + name + + '&namespace=' + namespace, body) - def delete_overrides(self, name, namespace): + def delete_overrides(self, app, name, namespace): """Delete overrides for a given chart. + :param app_name: name of application :param name: name of the chart :param namespace: namespace for the chart overrides """ - return self._delete(self._path(name) + '?namespace=' + namespace) + return self._delete(self._path(app) + + '?name=' + name + + '&namespace=' + namespace) diff --git a/sysinv/cgts-client/cgts-client/cgtsclient/v1/helm_shell.py b/sysinv/cgts-client/cgts-client/cgtsclient/v1/helm_shell.py index 6235299fa9..b54d69d677 100755 --- a/sysinv/cgts-client/cgts-client/cgtsclient/v1/helm_shell.py +++ b/sysinv/cgts-client/cgts-client/cgtsclient/v1/helm_shell.py @@ -11,6 +11,7 @@ import yaml from cgtsclient.common import utils from cgtsclient import exc +from cgtsclient.v1 import app as app_utils def _print_helm_chart(chart): @@ -22,12 +23,32 @@ def _print_helm_chart(chart): utils.print_dict(ordereddata) +def _find_overrides(cc, app, chart, namespace): + charts = cc.helm.list_charts(app.name) + for c in charts: + if chart == c.name and namespace in c.namespaces: + break + else: + raise exc.CommandError('Chart overrides %s:%s for application ' + '%s not found' % + (chart, namespace, app.name)) + + return c + + +@utils.arg('app', + metavar='', + help="Name of the application") def do_helm_override_list(cc, args): """List system helm charts.""" - charts = cc.helm.list_charts() + app = app_utils._find_app(cc, args.app) + charts = cc.helm.list_charts(app.name) utils.print_list(charts, ['name', 'namespaces'], ['chart name', 'overrides namespaces'], sortby=0) +@utils.arg('app', + metavar='', + help="Name of the application") @utils.arg('chart', metavar='', help="Name of chart") @utils.arg('namespace', @@ -35,14 +56,15 @@ def do_helm_override_list(cc, args): help="namespace of chart overrides") def do_helm_override_show(cc, args): """Show overrides for chart.""" - try: - chart = cc.helm.get_overrides(args.chart, args.namespace) - _print_helm_chart(chart) - except exc.HTTPNotFound: - raise exc.CommandError('chart overrides not found: %s:%s' % ( - args.chart, args.namespace)) + app = app_utils._find_app(cc, args.app) + _find_overrides(cc, app, args.chart, args.namespace) + chart = cc.helm.get_overrides(args.app, args.chart, args.namespace) + _print_helm_chart(chart) +@utils.arg('app', + metavar='', + help="Name of the application") @utils.arg('chart', metavar='', help="Name of chart") @@ -51,15 +73,16 @@ def do_helm_override_show(cc, args): help="namespace of chart overrides") def do_helm_override_delete(cc, args): """Delete overrides for a chart.""" - try: - cc.helm.delete_overrides(args.chart, args.namespace) - print('Deleted chart overrides for %s:%s' % ( - args.chart, args.namespace)) - except exc.HTTPNotFound: - raise exc.CommandError('chart overrides not found: %s:%s' % ( - args.chart, args.namespace)) + app = app_utils._find_app(cc, args.app) + _find_overrides(cc, app, args.chart, args.namespace) + cc.helm.delete_overrides(args.app, args.chart, args.namespace) + print('Deleted chart overrides %s:%s for application %s' % + (args.chart, args.namespace, args.app)) +@utils.arg('app', + metavar='', + help="Name of the application") @utils.arg('chart', metavar='', help="Name of chart") @@ -85,6 +108,9 @@ def do_helm_override_delete(cc, args): def do_helm_override_update(cc, args): """Update helm chart user overrides.""" + app = app_utils._find_app(cc, args.app) + _find_overrides(cc, app, args.chart, args.namespace) + # This logic results in similar behaviour to "helm upgrade". flag = 'reset' if args.reuse_values and not args.reset_values: @@ -114,10 +140,6 @@ def do_helm_override_update(cc, args): 'set': override_set, } - try: - chart = cc.helm.update_overrides(args.chart, args.namespace, - flag, overrides) - except exc.HTTPNotFound: - raise exc.CommandError('helm chart not found: %s:%s' % ( - args.chart, args.namespace)) + chart = cc.helm.update_overrides(args.app, args.chart, args.namespace, + flag, overrides) _print_helm_chart(chart) diff --git a/sysinv/sysinv/centos/build_srpm.data b/sysinv/sysinv/centos/build_srpm.data index 95236cf2c0..16ac3ca174 100644 --- a/sysinv/sysinv/centos/build_srpm.data +++ b/sysinv/sysinv/centos/build_srpm.data @@ -1,2 +1,2 @@ SRC_DIR="sysinv" -TIS_PATCH_VER=318 +TIS_PATCH_VER=319 diff --git a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/helm_charts.py b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/helm_charts.py index 25e5e0c9ff..0f7e06d4f7 100644 --- a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/helm_charts.py +++ b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/helm_charts.py @@ -23,30 +23,28 @@ LOG = log.getLogger(__name__) class HelmChartsController(rest.RestController): - @wsme_pecan.wsexpose(wtypes.text) - def get_all(self): + @wsme_pecan.wsexpose(wtypes.text, wtypes.text) + def get_all(self, app_name): """Provides information about the available charts to override.""" - supported_apps = pecan.request.rpcapi.get_helm_applications( - pecan.request.context) - all_charts = {} - for app in supported_apps: - namespaces = pecan.request.rpcapi.get_helm_application_namespaces( - pecan.request.context, app) - for chart in namespaces: - if chart not in all_charts: - all_charts[chart] = namespaces[chart] - else: - all_charts[chart] = list(set().union(all_charts[chart], - namespaces[chart])) + try: + objects.kube_app.get_by_name( + pecan.request.context, app_name) + except exception.KubeAppNotFound: + raise wsme.exc.ClientSideError(_("Application %s not found." % app_name)) + + namespaces = pecan.request.rpcapi.get_helm_application_namespaces( + pecan.request.context, app_name) + charts = [{'name': chart, 'namespaces': namespaces[chart]} + for chart in namespaces] - charts = [{'name': c, 'namespaces': ns} for c, ns in all_charts.items()] return {'charts': charts} - @wsme_pecan.wsexpose(wtypes.text, wtypes.text, wtypes.text) - def get_one(self, name, namespace): + @wsme_pecan.wsexpose(wtypes.text, wtypes.text, wtypes.text, wtypes.text) + def get_one(self, app_name, name, namespace): """Retrieve information about the given chart. + :param app_name: name of application :param name: name of helm chart :param namespace: namespace of chart overrides """ @@ -54,9 +52,13 @@ class HelmChartsController(rest.RestController): # Get any user-specified overrides. try: - db_chart = objects.helm_overrides.get_by_name( - pecan.request.context, name, namespace) + app = objects.kube_app.get_by_name( + pecan.request.context, app_name) + db_chart = objects.helm_overrides.get_by_appid_name( + pecan.request.context, app.id, name, namespace) user_overrides = db_chart.user_overrides + except exception.KubeAppNotFound: + raise wsme.exc.ClientSideError(_("Application %s not found." % app_name)) except exception.HelmOverrideNotFound: user_overrides = '' @@ -92,10 +94,12 @@ class HelmChartsController(rest.RestController): if not namespace: raise wsme.exc.ClientSideError(_("Namespace must be specified.")) - @wsme_pecan.wsexpose(wtypes.text, wtypes.text, wtypes.text, wtypes.text, wtypes.text) - def patch(self, name, namespace, flag, values): + @wsme_pecan.wsexpose(wtypes.text, wtypes.text, wtypes.text, + wtypes.text, wtypes.text, wtypes.text) + def patch(self, app_name, name, namespace, flag, values): """ Update user overrides. + :param app_name: name of application :param name: chart name :param namespace: namespace of chart overrides :param flag: one of "reuse" or "reset", describes how to handle @@ -110,15 +114,20 @@ class HelmChartsController(rest.RestController): # Get any stored user overrides for this chart. We'll need this # object later either way. try: - db_chart = objects.helm_overrides.get_by_name( - pecan.request.context, name, namespace) + app = objects.kube_app.get_by_name( + pecan.request.context, app_name) + db_chart = objects.helm_overrides.get_by_appid_name( + pecan.request.context, app.id, name, namespace) + except exception.KubeAppNotFound: + raise wsme.exc.ClientSideError(_("Application %s not found." % app_name)) except exception.HelmOverrideNotFound: pecan.request.dbapi.helm_override_create({ 'name': name, 'namespace': namespace, - 'user_overrides': ''}) - db_chart = objects.helm_overrides.get_by_name( - pecan.request.context, name, namespace) + 'user_overrides': '', + 'app_id': app.id}) + db_chart = objects.helm_overrides.get_by_appid_name( + pecan.request.context, app.id, name, namespace) if flag == 'reuse': if db_chart.user_overrides is not None: @@ -150,16 +159,21 @@ class HelmChartsController(rest.RestController): return chart - @wsme_pecan.wsexpose(None, wtypes.text, wtypes.text, status_code=204) - def delete(self, name, namespace): + @wsme_pecan.wsexpose(None, wtypes.text, wtypes.text, + wtypes.text, status_code=204) + def delete(self, app_name, name, namespace): """Delete user overrides for a chart + :param app_name: name of application :param name: chart name. :param namespace: namespace of chart overrides """ self.validate_name_and_namespace(name, namespace) try: - pecan.request.dbapi.helm_override_update(name, namespace, + app = objects.kube_app.get_by_name(pecan.request.context, app_name) + pecan.request.dbapi.helm_override_update(app.id, name, namespace, {'user_overrides': None}) + except exception.KubeAppNotFound: + raise wsme.exc.ClientSideError(_("Application %s not found." % app_name)) except exception.HelmOverrideNotFound: pass diff --git a/sysinv/sysinv/sysinv/sysinv/cmd/helm.py b/sysinv/sysinv/sysinv/sysinv/cmd/helm.py index 14017ba9ac..3959b9f8af 100644 --- a/sysinv/sysinv/sysinv/sysinv/cmd/helm.py +++ b/sysinv/sysinv/sysinv/sysinv/cmd/helm.py @@ -22,14 +22,16 @@ CONF = cfg.CONF def create_app_overrides_action(path, app_name=None, namespace=None): dbapi = api.get_instance() - operator = helm.HelmOperator(dbapi=dbapi, path=path) - operator.generate_helm_application_overrides(app_name, mode=None, cnamespace=namespace) + operator = helm.HelmOperator(dbapi=dbapi) + operator.generate_helm_application_overrides(path, app_name, mode=None, + cnamespace=namespace) def create_armada_app_overrides_action(path, app_name=None, namespace=None): dbapi = api.get_instance() - operator = helm.HelmOperator(dbapi=dbapi, path=path) - operator.generate_helm_application_overrides(app_name, mode=None, cnamespace=namespace, + operator = helm.HelmOperator(dbapi=dbapi) + operator.generate_helm_application_overrides(path, app_name, mode=None, + cnamespace=namespace, armada_format=True, armada_chart_info=None, combined=False) @@ -37,8 +39,8 @@ def create_armada_app_overrides_action(path, app_name=None, namespace=None): def create_chart_override_action(path, chart_name=None, namespace=None): dbapi = api.get_instance() - operator = helm.HelmOperator(dbapi=dbapi, path=path) - operator.generate_helm_chart_overrides(chart_name, namespace) + operator = helm.HelmOperator(dbapi=dbapi) + operator.generate_helm_chart_overrides(path, chart_name, namespace) def add_action_parsers(subparsers): diff --git a/sysinv/sysinv/sysinv/sysinv/conductor/kube_app.py b/sysinv/sysinv/sysinv/sysinv/conductor/kube_app.py index bb5221fc71..330a4ebbd2 100644 --- a/sysinv/sysinv/sysinv/sysinv/conductor/kube_app.py +++ b/sysinv/sysinv/sysinv/sysinv/conductor/kube_app.py @@ -67,23 +67,30 @@ DOCKER_REGISTRY_SECRET = 'default-registry-key' # Helper functions -def generate_armada_manifest_filename(app_name, manifest_filename): - return os.path.join('/manifests', app_name + '-' + manifest_filename) - - -def generate_armada_manifest_filename_abs(app_name, manifest_filename): - return os.path.join(constants.APP_SYNCED_DATA_PATH, +def generate_armada_manifest_filename(app_name, app_version, manifest_filename): + return os.path.join('/manifests', app_name, app_version, app_name + '-' + manifest_filename) -def generate_manifest_filename_abs(app_name, manifest_filename): +def generate_armada_manifest_dir(app_name, app_version): + return os.path.join(constants.APP_SYNCED_DATA_PATH, app_name, app_version) + + +def generate_armada_manifest_filename_abs(armada_mfile_dir, app_name, manifest_filename): + return os.path.join(armada_mfile_dir, app_name + '-' + manifest_filename) + + +def generate_manifest_filename_abs(app_name, app_version, manifest_filename): return os.path.join(constants.APP_INSTALL_PATH, - app_name, manifest_filename) + app_name, app_version, manifest_filename) -def generate_images_filename_abs(app_name): - return os.path.join(constants.APP_SYNCED_DATA_PATH, - app_name + '-images.yaml') +def generate_images_filename_abs(armada_mfile_dir, app_name): + return os.path.join(armada_mfile_dir, app_name + '-images.yaml') + + +def generate_overrides_dir(app_name, app_version): + return os.path.join(common.HELM_OVERRIDES_PATH, app_name, app_version) def create_app_path(path): @@ -134,18 +141,20 @@ class AppOperator(object): def _cleanup(self, app): """" Remove application directories and override files """ try: - if app.system_app and app.status != constants.APP_UPLOAD_FAILURE: - self._remove_chart_overrides(app.armada_mfile_abs) + if os.path.exists(app.overrides_dir): + shutil.rmtree(os.path.dirname( + app.overrides_dir)) - if os.path.exists(app.armada_mfile_abs): - os.unlink(app.armada_mfile_abs) - if os.path.exists(app.imgfile_abs): - os.unlink(app.imgfile_abs) + if os.path.exists(app.armada_mfile_dir): + shutil.rmtree(os.path.dirname( + app.armada_mfile_dir)) if os.path.exists(app.path): - shutil.rmtree(app.path) + shutil.rmtree(os.path.dirname( + app.path)) except OSError as e: LOG.error(e) + raise def _update_app_status(self, app, new_status=None, new_progress=None): """ Persist new app status """ @@ -244,10 +253,13 @@ class AppOperator(object): orig_uid, orig_gid = get_app_install_root_path_ownership() try: - # One time set up of Armada manifest path for the system + # One time set up of base armada manifest path for the system if not os.path.isdir(constants.APP_SYNCED_DATA_PATH): os.makedirs(constants.APP_SYNCED_DATA_PATH) + if not os.path.isdir(app.armada_mfile_dir): + os.makedirs(app.armada_mfile_dir) + if not os.path.isdir(app.path): create_app_path(app.path) @@ -307,7 +319,7 @@ class AppOperator(object): image_tags.extend(ids) return list(set(image_tags)) - def _get_image_tags_by_charts(self, app_images_file, app_manifest_file): + def _get_image_tags_by_charts(self, app_images_file, app_manifest_file, overrides_dir): """ Mine the image tags for charts from the images file. Add the image tags to the manifest file if the image tags from the charts do not exist in both overrides file and manifest file. Convert @@ -349,7 +361,7 @@ class AppOperator(object): # Get the image tags from the overrides file overrides = chart_namespace + '-' + chart_name + '.yaml' - app_overrides_file = os.path.join(common.HELM_OVERRIDES_PATH, overrides) + app_overrides_file = os.path.join(overrides_dir, overrides) if os.path.exists(app_overrides_file): try: with open(app_overrides_file, 'r') as f: @@ -443,12 +455,12 @@ class AppOperator(object): LOG.info("Generating application overrides...") app.charts = self._get_list_of_charts(app.armada_mfile_abs) self._helm.generate_helm_application_overrides( - app.name, mode=None, cnamespace=None, armada_format=True, - armada_chart_info=app.charts, combined=True) + app.overrides_dir, app.name, mode=None, cnamespace=None, + armada_format=True, armada_chart_info=app.charts, combined=True) self._save_images_list_by_charts(app) # Get the list of images from the updated images overrides images_to_download = self._get_image_tags_by_charts( - app.imgfile_abs, app.armada_mfile_abs) + app.imgfile_abs, app.armada_mfile_abs, app.overrides_dir) else: # For custom apps, mine image tags from application path images_to_download = self._get_image_tags_by_path(app.path) @@ -505,7 +517,7 @@ class AppOperator(object): saved_images_list = self._retrieve_images_list(app.imgfile_abs) saved_download_images_list = list(saved_images_list.get("download_images")) images_to_download = self._get_image_tags_by_charts( - app.imgfile_abs, app.armada_mfile_abs) + app.imgfile_abs, app.armada_mfile_abs, app.overrides_dir) if set(saved_download_images_list) != set(images_to_download): saved_images_list.update({"download_images": images_to_download}) with open(app.imgfile_abs, 'wb') as f: @@ -858,7 +870,7 @@ class AppOperator(object): pass return charts - def _get_overrides_files(self, charts, app_name, mode): + def _get_overrides_files(self, overrides_dir, charts, app_name, mode): """Returns list of override files or None, used in application-install and application-delete.""" @@ -867,8 +879,7 @@ class AppOperator(object): for chart in charts: overrides = chart.namespace + '-' + chart.name + '.yaml' - overrides_file = os.path.join(common.HELM_OVERRIDES_PATH, - overrides) + overrides_file = os.path.join(overrides_dir, overrides) if not os.path.exists(overrides_file): missing_overrides.append(overrides_file) else: @@ -881,8 +892,7 @@ class AppOperator(object): chart.name, chart.namespace, app_name, mode): overrides = chart.namespace + '-' + chart.name + \ '-meta' + '.yaml' - overrides_file = os.path.join(common.HELM_OVERRIDES_PATH, - overrides) + overrides_file = os.path.join(overrides_dir, overrides) if not os.path.exists(overrides_file): missing_overrides.append(overrides_file) else: @@ -893,15 +903,17 @@ class AppOperator(object): return None return available_overrides - def _generate_armada_overrides_str(self, overrides_files): - return " ".join([' --values /overrides/{0}'.format(os.path.basename(i)) + def _generate_armada_overrides_str(self, app_name, app_version, overrides_files): + return " ".join([' --values /overrides/{0}/{1}/{2}'.format(app_name, app_version, + os.path.basename(i)) for i in overrides_files]) - def _remove_chart_overrides(self, manifest_file): + def _remove_chart_overrides(self, overrides_dir, manifest_file): charts = self._get_list_of_charts(manifest_file) for chart in charts: if chart.name in self._helm.chart_operators: - self._helm.remove_helm_chart_overrides(chart.name, + self._helm.remove_helm_chart_overrides(overrides_dir, + chart.name, chart.namespace) def _make_armada_request_with_monitor(self, app, request, overrides_str=None): @@ -1129,10 +1141,10 @@ class AppOperator(object): self._upload_helm_charts(app) self._save_images_list(app) - self._update_app_status(app, constants.APP_UPLOAD_SUCCESS) if app.patch_dependencies: self._app._patch_report_app_dependencies( app.name, app.patch_dependencies) + self._update_app_status(app, constants.APP_UPLOAD_SUCCESS) LOG.info("Application (%s) upload completed." % app.name) except exception.KubeAppUploadFailure as e: LOG.exception(e) @@ -1182,16 +1194,19 @@ class AppOperator(object): app, new_progress=constants.APP_PROGRESS_GENERATE_OVERRIDES) LOG.info("Generating application overrides...") self._helm.generate_helm_application_overrides( - app.name, mode, cnamespace=None, armada_format=True, - armada_chart_info=app.charts, combined=True) - overrides_files = self._get_overrides_files(app.charts, app.name, mode) + app.overrides_dir, app.name, mode, cnamespace=None, + armada_format=True, armada_chart_info=app.charts, combined=True) + overrides_files = self._get_overrides_files(app.overrides_dir, + app.charts, + app.name, mode) if overrides_files: LOG.info("Application overrides generated.") # Ensure all chart overrides are readable by Armada for file in overrides_files: os.chmod(file, 0o644) overrides_str =\ - self._generate_armada_overrides_str(overrides_files) + self._generate_armada_overrides_str(app.name, app.version, + overrides_files) self._update_app_status( app, new_progress=constants.APP_PROGRESS_DOWNLOAD_IMAGES) self._download_images(app) @@ -1321,22 +1336,33 @@ class AppOperator(object): def __init__(self, rpc_app, is_system_app): self._kube_app = rpc_app self.path = os.path.join(constants.APP_INSTALL_PATH, - self._kube_app.get('name')) + self._kube_app.get('name'), + self._kube_app.get('app_version')) self.charts_dir = os.path.join(self.path, 'charts') self.images_dir = os.path.join(self.path, 'images') self.tarfile = None self.downloaded_tarfile = False self.system_app = is_system_app + self.overrides_dir = generate_overrides_dir( + self._kube_app.get('name'), + self._kube_app.get('app_version')) + self.armada_mfile_dir = generate_armada_manifest_dir( + self._kube_app.get('name'), + self._kube_app.get('app_version')) self.armada_mfile = generate_armada_manifest_filename( self._kube_app.get('name'), + self._kube_app.get('app_version'), self._kube_app.get('manifest_file')) self.armada_mfile_abs = generate_armada_manifest_filename_abs( + self.armada_mfile_dir, self._kube_app.get('name'), self._kube_app.get('manifest_file')) self.mfile_abs = generate_manifest_filename_abs( self._kube_app.get('name'), + self._kube_app.get('app_version'), self._kube_app.get('manifest_file')) self.imgfile_abs = generate_images_filename_abs( + self.armada_mfile_dir, self._kube_app.get('name')) self.patch_dependencies = [] @@ -1379,25 +1405,35 @@ class AppOperator(object): self._kube_app.manifest_name = new_mname self._kube_app.manifest_file = new_mfile self.armada_mfile = generate_armada_manifest_filename( - self.name, new_mfile) + self.name, self.version, new_mfile) self.armada_mfile_abs = generate_armada_manifest_filename_abs( - self.name, new_mfile) + self.armada_mfile_dir, self.name, new_mfile) self.mfile_abs = generate_manifest_filename_abs( - self.name, new_mfile) + self.name, self.version, new_mfile) def regenerate_application_info(self, new_name, new_version, new_patch_dependencies): self._kube_app.name = new_name self._kube_app.app_version = new_version self.system_app = \ (self.name == constants.HELM_APP_OPENSTACK) - self.imgfile_abs = \ - generate_images_filename_abs(self.name) + + new_armada_dir = generate_armada_manifest_dir( + self.name, self.version) + shutil.move(self.armada_mfile_dir, new_armada_dir) + shutil.rmtree(os.path.dirname(self.armada_mfile_dir)) + self.armada_mfile_dir = new_armada_dir + new_path = os.path.join( - constants.APP_INSTALL_PATH, self.name) - os.rename(self.path, new_path) + constants.APP_INSTALL_PATH, self.name, self.version) + shutil.move(self.path, new_path) + shutil.rmtree(os.path.dirname(self.path)) self.path = new_path + self.charts_dir = os.path.join(self.path, 'charts') self.images_dir = os.path.join(self.path, 'images') + self.imgfile_abs = \ + generate_images_filename_abs(self.armada_mfile_dir, self.name) + self.overrides_dir = generate_overrides_dir(self.name, self.version) self.patch_dependencies = new_patch_dependencies diff --git a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/api.py b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/api.py index dc68362ad5..662946e11b 100644 --- a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/api.py +++ b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/api.py @@ -7358,9 +7358,10 @@ class Connection(api.Connection): raise exception.CertificateNotFound(uuid) query.delete() - def _helm_override_get(self, name, namespace): + def _helm_override_get(self, app_id, name, namespace): query = model_query(models.HelmOverrides) - query = query.filter_by(name=name, namespace=namespace) + query = query.filter_by( + app_id=app_id, name=name, namespace=namespace) try: return query.one() except NoResultFound: @@ -7382,12 +7383,13 @@ class Connection(api.Connection): (values['name'])) raise exception.HelmOverrideAlreadyExists( name=values['name'], namespace=values['namespace']) - return self._helm_override_get(values['name'], + return self._helm_override_get(values['app_id'], + values['name'], values['namespace']) @objects.objectify(objects.helm_overrides) - def helm_override_get(self, name, namespace): - return self._helm_override_get(name, namespace) + def helm_override_get(self, app_id, name, namespace): + return self._helm_override_get(app_id, name, namespace) @objects.objectify(objects.helm_overrides) def helm_override_get_all(self): @@ -7395,10 +7397,11 @@ class Connection(api.Connection): return query.all() @objects.objectify(objects.helm_overrides) - def helm_override_update(self, name, namespace, values): + def helm_override_update(self, app_id, name, namespace, values): with _session_for_write() as session: query = model_query(models.HelmOverrides, session=session) - query = query.filter_by(name=name, namespace=namespace) + query = query.filter_by( + app_id=app_id, name=name, namespace=namespace) count = query.update(values, synchronize_session='fetch') if count == 0: @@ -7406,10 +7409,11 @@ class Connection(api.Connection): namespace=namespace) return query.one() - def helm_override_destroy(self, name, namespace): + def helm_override_destroy(self, app_id, name, namespace): with _session_for_write() as session: query = model_query(models.HelmOverrides, session=session) - query = query.filter_by(name=name, namespace=namespace) + query = query.filter_by( + app_id=app_id, name=name, namespace=namespace) try: query.one() @@ -7573,6 +7577,7 @@ class Connection(api.Connection): "operation is not allowed while status is " + app.status raise exception.KubeAppDeleteFailure( name=name, + version=app.app_version, reason=failure_reason) except NoResultFound: raise exception.KubeAppNotFound(name) diff --git a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/082_kube_application.py b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/073_kube_application.py similarity index 96% rename from sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/082_kube_application.py rename to sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/073_kube_application.py index 155266f09b..ec56e1aaf7 100644 --- a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/082_kube_application.py +++ b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/073_kube_application.py @@ -25,7 +25,7 @@ def upgrade(migrate_engine): meta = MetaData() meta.bind = migrate_engine - # Define and create the helm_overrides table. + # Define and create the kube_app table. kube_app = Table( 'kube_app', meta, diff --git a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/073_helm_overrides.py b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/081_helm_overrides.py similarity index 85% rename from sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/073_helm_overrides.py rename to sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/081_helm_overrides.py index 124323454b..0a051a98e2 100644 --- a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/073_helm_overrides.py +++ b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/081_helm_overrides.py @@ -6,7 +6,7 @@ # from sqlalchemy import DateTime, String, Text, Integer -from sqlalchemy import Column, MetaData, Table, UniqueConstraint +from sqlalchemy import Column, MetaData, Table, UniqueConstraint, ForeignKey from sysinv.openstack.common import log @@ -25,6 +25,8 @@ def upgrade(migrate_engine): meta = MetaData() meta.bind = migrate_engine + Table('kube_app', meta, autoload=True) + # Define and create the helm_overrides table. helm_overrides = Table( 'helm_overrides', @@ -36,7 +38,9 @@ def upgrade(migrate_engine): Column('name', String(255), nullable=False), Column('namespace', String(255), nullable=False), Column('user_overrides', Text, nullable=True), - UniqueConstraint('name', 'namespace', name='u_name_namespace'), + Column('app_id', Integer, + ForeignKey('kube_app.id', ondelete='CASCADE')), + UniqueConstraint('name', 'namespace', 'app_id', name='u_app_name_namespace'), mysql_engine=ENGINE, mysql_charset=CHARSET, diff --git a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/081_helm_system_overrides.py b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/082_helm_system_overrides.py similarity index 100% rename from sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/081_helm_system_overrides.py rename to sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/082_helm_system_overrides.py diff --git a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/models.py b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/models.py index ff18fb05d0..0ff7c2b0be 100644 --- a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/models.py +++ b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/models.py @@ -1677,7 +1677,9 @@ class HelmOverrides(Base): namespace = Column(String(255), nullable=False) user_overrides = Column(Text, nullable=True) system_overrides = Column(JSONEncodedDict, nullable=True) - UniqueConstraint('name', 'namespace', name='u_name_namespace') + app_id = Column(Integer, ForeignKey('kube_app.id', ondelete='CASCADE')) + kube_app = relationship("KubeApp", lazy="joined", join_depth=1) + UniqueConstraint('name', 'namespace', 'app_id', name='u_app_name_namespace') class Label(Base): diff --git a/sysinv/sysinv/sysinv/sysinv/helm/helm.py b/sysinv/sysinv/sysinv/sysinv/helm/helm.py index 250faf4fcb..b7054aa769 100644 --- a/sysinv/sysinv/sysinv/sysinv/helm/helm.py +++ b/sysinv/sysinv/sysinv/sysinv/helm/helm.py @@ -45,12 +45,8 @@ def helm_context(func): class HelmOperator(object): """Class to encapsulate helm override operations for System Inventory""" - def __init__(self, dbapi=None, path=None): - if path is None: - path = common.HELM_OVERRIDES_PATH - + def __init__(self, dbapi=None): self.dbapi = dbapi - self.path = path # register chart operators for lookup self.chart_operators = {} @@ -377,7 +373,7 @@ class HelmOperator(object): return values @helm_context - def generate_helm_chart_overrides(self, chart_name, cnamespace=None): + def generate_helm_chart_overrides(self, path, chart_name, cnamespace=None): """Generate system helm chart overrides This method will generate system helm chart override an write them to a @@ -399,7 +395,8 @@ class HelmOperator(object): overrides = self._get_helm_chart_overrides( chart_name, cnamespace) - self._write_chart_overrides(chart_name, + self._write_chart_overrides(path, + chart_name, cnamespace, overrides) except Exception as e: @@ -424,7 +421,8 @@ class HelmOperator(object): return overrides @helm_context - def generate_helm_application_overrides(self, app_name, mode=None, + def generate_helm_application_overrides(self, path, app_name, + mode=None, cnamespace=None, armada_format=False, armada_chart_info=None, @@ -450,6 +448,11 @@ class HelmOperator(object): """ if app_name in self.helm_applications: + try: + app = self.dbapi.kube_app_get(app_name) + except exception.KubeAppNotFound: + LOG.exception("Application %s not found." % app_name) + app_overrides = self._get_helm_application_overrides(app_name, cnamespace) for (chart_name, overrides) in iteritems(app_overrides): @@ -468,7 +471,7 @@ class HelmOperator(object): for chart_namespace in overrides.keys(): try: db_chart = self.dbapi.helm_override_get( - chart_name, chart_namespace) + app.id, chart_name, chart_namespace) db_user_overrides = db_chart.user_overrides if db_user_overrides: file_overrides.append(yaml.dump( @@ -495,7 +498,7 @@ class HelmOperator(object): chart_name, armada_chart_repo_name, key, overrides[key]) overrides[key] = new_overrides - self._write_chart_overrides(chart_name, cnamespace, overrides) + self._write_chart_overrides(path, chart_name, cnamespace, overrides) # Write any meta-overrides for this chart. These will be in # armada format already. @@ -507,14 +510,14 @@ class HelmOperator(object): if overrides: chart_meta_name = chart_name + '-meta' self._write_chart_overrides( - chart_meta_name, cnamespace, overrides) + path, chart_meta_name, cnamespace, overrides) elif app_name: LOG.exception("%s application is not supported" % app_name) else: LOG.exception("application name is required") - def remove_helm_chart_overrides(self, chart_name, cnamespace=None): + def remove_helm_chart_overrides(self, path, chart_name, cnamespace=None): """Remove the overrides files for a chart""" if chart_name in self.chart_operators: @@ -529,7 +532,7 @@ class HelmOperator(object): for f in filenames: try: - self._remove_overrides(f) + self._remove_overrides(path, f) except Exception as e: LOG.exception("failed to remove %s overrides: %s: %s" % ( chart_name, f, e)) @@ -537,12 +540,12 @@ class HelmOperator(object): LOG.exception("chart %s not supported for system overrides" % chart_name) - def _write_chart_overrides(self, chart_name, cnamespace, overrides): + def _write_chart_overrides(self, path, chart_name, cnamespace, overrides): """Write a one or more overrides files for a chart. """ def _write_file(filename, values): try: - self._write_overrides(filename, values) + self._write_overrides(path, filename, values) except Exception as e: LOG.exception("failed to write %s overrides: %s: %s" % ( chart_name, filename, e)) @@ -553,12 +556,15 @@ class HelmOperator(object): for ns in overrides.keys(): _write_file("%s-%s.yaml" % (ns, chart_name), overrides[ns]) - def _write_overrides(self, filename, overrides): + def _write_overrides(self, path, filename, overrides): """Write a single overrides file. """ - filepath = os.path.join(self.path, filename) + if not os.path.isdir(path): + os.makedirs(path) + + filepath = os.path.join(path, filename) try: - fd, tmppath = tempfile.mkstemp(dir=self.path, prefix=filename, + fd, tmppath = tempfile.mkstemp(dir=path, prefix=filename, text=True) with open(tmppath, 'w') as f: @@ -569,10 +575,10 @@ class HelmOperator(object): LOG.exception("failed to write overrides file: %s" % filepath) raise - def _remove_overrides(self, filename): + def _remove_overrides(self, path, filename): """Remove a single overrides file. """ - filepath = os.path.join(self.path, filename) + filepath = os.path.join(path, filename) try: if os.path.exists(filepath): os.unlink(filepath) diff --git a/sysinv/sysinv/sysinv/sysinv/helm/openstack.py b/sysinv/sysinv/sysinv/sysinv/helm/openstack.py index 8befbcf292..dcaccf766a 100644 --- a/sysinv/sysinv/sysinv/sysinv/helm/openstack.py +++ b/sysinv/sysinv/sysinv/sysinv/helm/openstack.py @@ -149,7 +149,9 @@ class OpenstackBaseHelm(base.BaseHelm): return None try: - override = self.dbapi.helm_override_get(name=chart, + app = self.dbapi.kube_app_get(constants.HELM_APP_OPENSTACK) + override = self.dbapi.helm_override_get(app_id=app.id, + name=chart, namespace=namespace) except exception.HelmOverrideNotFound: # Override for this chart not found, so create one @@ -157,6 +159,7 @@ class OpenstackBaseHelm(base.BaseHelm): values = { 'name': chart, 'namespace': namespace, + 'app_id': app.id, } override = self.dbapi.helm_override_create(values=values) except Exception as e: @@ -176,7 +179,7 @@ class OpenstackBaseHelm(base.BaseHelm): }) try: self.dbapi.helm_override_update( - name=chart, namespace=namespace, values=values) + app_id=app.id, name=chart, namespace=namespace, values=values) except Exception as e: LOG.exception(e) @@ -337,13 +340,16 @@ class OpenstackBaseHelm(base.BaseHelm): def _get_or_generate_ssh_keys(self, chart, namespace): try: - override = self.dbapi.helm_override_get(name=chart, + app = self.dbapi.kube_app_get(constants.HELM_APP_OPENSTACK) + override = self.dbapi.helm_override_get(app_id=app.id, + name=chart, namespace=namespace) except exception.HelmOverrideNotFound: # Override for this chart not found, so create one values = { 'name': chart, 'namespace': namespace, + 'app_id': app.id } override = self.dbapi.helm_override_create(values=values) @@ -362,7 +368,7 @@ class OpenstackBaseHelm(base.BaseHelm): values['system_overrides'].update({'privatekey': newprivatekey, 'publickey': newpublickey}) self.dbapi.helm_override_update( - name=chart, namespace=namespace, values=values) + app_id=app.id, name=chart, namespace=namespace, values=values) return newprivatekey, newpublickey diff --git a/sysinv/sysinv/sysinv/sysinv/objects/helm_overrides.py b/sysinv/sysinv/sysinv/sysinv/objects/helm_overrides.py index 8b2811f9bf..721b2a41cf 100644 --- a/sysinv/sysinv/sysinv/sysinv/objects/helm_overrides.py +++ b/sysinv/sysinv/sysinv/sysinv/objects/helm_overrides.py @@ -21,11 +21,13 @@ class HelmOverrides(base.SysinvObject): 'namespace': utils.str_or_none, 'user_overrides': utils.str_or_none, 'system_overrides': utils.dict_or_none, + 'app_id': int } @base.remotable_classmethod - def get_by_name(cls, context, name, namespace): - return cls.dbapi.helm_override_get(name, namespace) + def get_by_appid_name(cls, context, app_id, name, namespace): + return cls.dbapi.helm_override_get(app_id, name, namespace) def save_changes(self, context, updates): - self.dbapi.helm_override_update(self.name, self.namespace, updates) + self.dbapi.helm_override_update(self.app_id, self.name, + self.namespace, updates)