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 <angie.wang@windriver.com>
This commit is contained in:
Angie Wang 2019-05-17 16:43:12 -04:00
parent 07587a049a
commit b978111f19
16 changed files with 270 additions and 152 deletions

View File

@ -1,2 +1,2 @@
SRC_DIR="cgts-client" SRC_DIR="cgts-client"
TIS_PATCH_VER=65 TIS_PATCH_VER=66

View File

@ -7,6 +7,7 @@
# #
from cgtsclient.common import base from cgtsclient.common import base
from cgtsclient import exc
class App(base.Resource): class App(base.Resource):
@ -66,3 +67,12 @@ class AppManager(base.Manager):
:param name: app_name :param name: app_name
""" """
return self._delete(self._path(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

View File

@ -21,17 +21,18 @@ class HelmManager(base.Manager):
def _path(name=''): def _path(name=''):
return '/v1/helm_charts/%s' % name return '/v1/helm_charts/%s' % name
def list_charts(self): def list_charts(self, app):
"""Get list of charts """Get list of charts
For each chart it will show any overrides for that chart along For each chart it will show any overrides for that chart along
with the namespace of the overrides. 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. """Get overrides for a given chart.
:param app_name: name of application
:param name: name of the chart :param name: name of the chart
:param namespace: namespace for the chart overrides :param namespace: namespace for the chart overrides
@ -39,14 +40,17 @@ class HelmManager(base.Manager):
specified chart. specified chart.
""" """
try: try:
return self._list(self._path(name) + '?namespace=' + namespace)[0] return self._list(self._path(app) +
'?name=' + name +
'&namespace=' + namespace)[0]
except IndexError: except IndexError:
return None return None
def update_overrides(self, name, namespace, def update_overrides(self, app, name, namespace,
flag='reset', override_values={}): flag='reset', override_values={}):
"""Update overrides for a given chart. """Update overrides for a given chart.
:param app_name: name of application
:param name: name of the chart :param name: name of the chart
:param namespace: namespace for the chart overrides :param namespace: namespace for the chart overrides
:param flag: 'reuse' or 'reset' to indicate how to handle existing :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. This will return the end-user overrides for the specified chart.
""" """
body = {'flag': flag, 'values': override_values} 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. """Delete overrides for a given chart.
:param app_name: name of application
:param name: name of the chart :param name: name of the chart
:param namespace: namespace for the chart overrides :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)

View File

@ -11,6 +11,7 @@ import yaml
from cgtsclient.common import utils from cgtsclient.common import utils
from cgtsclient import exc from cgtsclient import exc
from cgtsclient.v1 import app as app_utils
def _print_helm_chart(chart): def _print_helm_chart(chart):
@ -22,12 +23,32 @@ def _print_helm_chart(chart):
utils.print_dict(ordereddata) 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='<app name>',
help="Name of the application")
def do_helm_override_list(cc, args): def do_helm_override_list(cc, args):
"""List system helm charts.""" """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.print_list(charts, ['name', 'namespaces'], ['chart name', 'overrides namespaces'], sortby=0)
@utils.arg('app',
metavar='<app name>',
help="Name of the application")
@utils.arg('chart', metavar='<chart name>', @utils.arg('chart', metavar='<chart name>',
help="Name of chart") help="Name of chart")
@utils.arg('namespace', @utils.arg('namespace',
@ -35,14 +56,15 @@ def do_helm_override_list(cc, args):
help="namespace of chart overrides") help="namespace of chart overrides")
def do_helm_override_show(cc, args): def do_helm_override_show(cc, args):
"""Show overrides for chart.""" """Show overrides for chart."""
try: app = app_utils._find_app(cc, args.app)
chart = cc.helm.get_overrides(args.chart, args.namespace) _find_overrides(cc, app, args.chart, args.namespace)
_print_helm_chart(chart) chart = cc.helm.get_overrides(args.app, args.chart, args.namespace)
except exc.HTTPNotFound: _print_helm_chart(chart)
raise exc.CommandError('chart overrides not found: %s:%s' % (
args.chart, args.namespace))
@utils.arg('app',
metavar='<app name>',
help="Name of the application")
@utils.arg('chart', @utils.arg('chart',
metavar='<chart name>', metavar='<chart name>',
help="Name of chart") help="Name of chart")
@ -51,15 +73,16 @@ def do_helm_override_show(cc, args):
help="namespace of chart overrides") help="namespace of chart overrides")
def do_helm_override_delete(cc, args): def do_helm_override_delete(cc, args):
"""Delete overrides for a chart.""" """Delete overrides for a chart."""
try: app = app_utils._find_app(cc, args.app)
cc.helm.delete_overrides(args.chart, args.namespace) _find_overrides(cc, app, args.chart, args.namespace)
print('Deleted chart overrides for %s:%s' % ( cc.helm.delete_overrides(args.app, args.chart, args.namespace)
args.chart, args.namespace)) print('Deleted chart overrides %s:%s for application %s' %
except exc.HTTPNotFound: (args.chart, args.namespace, args.app))
raise exc.CommandError('chart overrides not found: %s:%s' % (
args.chart, args.namespace))
@utils.arg('app',
metavar='<app name>',
help="Name of the application")
@utils.arg('chart', @utils.arg('chart',
metavar='<chart name>', metavar='<chart name>',
help="Name of chart") help="Name of chart")
@ -85,6 +108,9 @@ def do_helm_override_delete(cc, args):
def do_helm_override_update(cc, args): def do_helm_override_update(cc, args):
"""Update helm chart user overrides.""" """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". # This logic results in similar behaviour to "helm upgrade".
flag = 'reset' flag = 'reset'
if args.reuse_values and not args.reset_values: if args.reuse_values and not args.reset_values:
@ -114,10 +140,6 @@ def do_helm_override_update(cc, args):
'set': override_set, 'set': override_set,
} }
try: chart = cc.helm.update_overrides(args.app, args.chart, args.namespace,
chart = cc.helm.update_overrides(args.chart, args.namespace, flag, overrides)
flag, overrides)
except exc.HTTPNotFound:
raise exc.CommandError('helm chart not found: %s:%s' % (
args.chart, args.namespace))
_print_helm_chart(chart) _print_helm_chart(chart)

View File

@ -1,2 +1,2 @@
SRC_DIR="sysinv" SRC_DIR="sysinv"
TIS_PATCH_VER=318 TIS_PATCH_VER=319

View File

@ -23,30 +23,28 @@ LOG = log.getLogger(__name__)
class HelmChartsController(rest.RestController): class HelmChartsController(rest.RestController):
@wsme_pecan.wsexpose(wtypes.text) @wsme_pecan.wsexpose(wtypes.text, wtypes.text)
def get_all(self): def get_all(self, app_name):
"""Provides information about the available charts to override.""" """Provides information about the available charts to override."""
supported_apps = pecan.request.rpcapi.get_helm_applications( try:
pecan.request.context) objects.kube_app.get_by_name(
all_charts = {} pecan.request.context, app_name)
for app in supported_apps: except exception.KubeAppNotFound:
namespaces = pecan.request.rpcapi.get_helm_application_namespaces( raise wsme.exc.ClientSideError(_("Application %s not found." % app_name))
pecan.request.context, app)
for chart in namespaces: namespaces = pecan.request.rpcapi.get_helm_application_namespaces(
if chart not in all_charts: pecan.request.context, app_name)
all_charts[chart] = namespaces[chart] charts = [{'name': chart, 'namespaces': namespaces[chart]}
else: for chart in namespaces]
all_charts[chart] = list(set().union(all_charts[chart],
namespaces[chart]))
charts = [{'name': c, 'namespaces': ns} for c, ns in all_charts.items()]
return {'charts': charts} return {'charts': charts}
@wsme_pecan.wsexpose(wtypes.text, wtypes.text, wtypes.text) @wsme_pecan.wsexpose(wtypes.text, wtypes.text, wtypes.text, wtypes.text)
def get_one(self, name, namespace): def get_one(self, app_name, name, namespace):
"""Retrieve information about the given chart. """Retrieve information about the given chart.
:param app_name: name of application
:param name: name of helm chart :param name: name of helm chart
:param namespace: namespace of chart overrides :param namespace: namespace of chart overrides
""" """
@ -54,9 +52,13 @@ class HelmChartsController(rest.RestController):
# Get any user-specified overrides. # Get any user-specified overrides.
try: try:
db_chart = objects.helm_overrides.get_by_name( app = objects.kube_app.get_by_name(
pecan.request.context, name, namespace) 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 user_overrides = db_chart.user_overrides
except exception.KubeAppNotFound:
raise wsme.exc.ClientSideError(_("Application %s not found." % app_name))
except exception.HelmOverrideNotFound: except exception.HelmOverrideNotFound:
user_overrides = '' user_overrides = ''
@ -92,10 +94,12 @@ class HelmChartsController(rest.RestController):
if not namespace: if not namespace:
raise wsme.exc.ClientSideError(_("Namespace must be specified.")) raise wsme.exc.ClientSideError(_("Namespace must be specified."))
@wsme_pecan.wsexpose(wtypes.text, wtypes.text, wtypes.text, wtypes.text, wtypes.text) @wsme_pecan.wsexpose(wtypes.text, wtypes.text, wtypes.text,
def patch(self, name, namespace, flag, values): wtypes.text, wtypes.text, wtypes.text)
def patch(self, app_name, name, namespace, flag, values):
""" Update user overrides. """ Update user overrides.
:param app_name: name of application
:param name: chart name :param name: chart name
:param namespace: namespace of chart overrides :param namespace: namespace of chart overrides
:param flag: one of "reuse" or "reset", describes how to handle :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 # Get any stored user overrides for this chart. We'll need this
# object later either way. # object later either way.
try: try:
db_chart = objects.helm_overrides.get_by_name( app = objects.kube_app.get_by_name(
pecan.request.context, name, namespace) 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: except exception.HelmOverrideNotFound:
pecan.request.dbapi.helm_override_create({ pecan.request.dbapi.helm_override_create({
'name': name, 'name': name,
'namespace': namespace, 'namespace': namespace,
'user_overrides': ''}) 'user_overrides': '',
db_chart = objects.helm_overrides.get_by_name( 'app_id': app.id})
pecan.request.context, name, namespace) db_chart = objects.helm_overrides.get_by_appid_name(
pecan.request.context, app.id, name, namespace)
if flag == 'reuse': if flag == 'reuse':
if db_chart.user_overrides is not None: if db_chart.user_overrides is not None:
@ -150,16 +159,21 @@ class HelmChartsController(rest.RestController):
return chart return chart
@wsme_pecan.wsexpose(None, wtypes.text, wtypes.text, status_code=204) @wsme_pecan.wsexpose(None, wtypes.text, wtypes.text,
def delete(self, name, namespace): wtypes.text, status_code=204)
def delete(self, app_name, name, namespace):
"""Delete user overrides for a chart """Delete user overrides for a chart
:param app_name: name of application
:param name: chart name. :param name: chart name.
:param namespace: namespace of chart overrides :param namespace: namespace of chart overrides
""" """
self.validate_name_and_namespace(name, namespace) self.validate_name_and_namespace(name, namespace)
try: 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}) {'user_overrides': None})
except exception.KubeAppNotFound:
raise wsme.exc.ClientSideError(_("Application %s not found." % app_name))
except exception.HelmOverrideNotFound: except exception.HelmOverrideNotFound:
pass pass

View File

@ -22,14 +22,16 @@ CONF = cfg.CONF
def create_app_overrides_action(path, app_name=None, namespace=None): def create_app_overrides_action(path, app_name=None, namespace=None):
dbapi = api.get_instance() dbapi = api.get_instance()
operator = helm.HelmOperator(dbapi=dbapi, path=path) operator = helm.HelmOperator(dbapi=dbapi)
operator.generate_helm_application_overrides(app_name, mode=None, cnamespace=namespace) operator.generate_helm_application_overrides(path, app_name, mode=None,
cnamespace=namespace)
def create_armada_app_overrides_action(path, app_name=None, namespace=None): def create_armada_app_overrides_action(path, app_name=None, namespace=None):
dbapi = api.get_instance() dbapi = api.get_instance()
operator = helm.HelmOperator(dbapi=dbapi, path=path) operator = helm.HelmOperator(dbapi=dbapi)
operator.generate_helm_application_overrides(app_name, mode=None, cnamespace=namespace, operator.generate_helm_application_overrides(path, app_name, mode=None,
cnamespace=namespace,
armada_format=True, armada_format=True,
armada_chart_info=None, armada_chart_info=None,
combined=False) 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): def create_chart_override_action(path, chart_name=None, namespace=None):
dbapi = api.get_instance() dbapi = api.get_instance()
operator = helm.HelmOperator(dbapi=dbapi, path=path) operator = helm.HelmOperator(dbapi=dbapi)
operator.generate_helm_chart_overrides(chart_name, namespace) operator.generate_helm_chart_overrides(path, chart_name, namespace)
def add_action_parsers(subparsers): def add_action_parsers(subparsers):

View File

@ -67,23 +67,30 @@ DOCKER_REGISTRY_SECRET = 'default-registry-key'
# Helper functions # Helper functions
def generate_armada_manifest_filename(app_name, manifest_filename): def generate_armada_manifest_filename(app_name, app_version, manifest_filename):
return os.path.join('/manifests', app_name + '-' + manifest_filename) return os.path.join('/manifests', app_name, app_version,
def generate_armada_manifest_filename_abs(app_name, manifest_filename):
return os.path.join(constants.APP_SYNCED_DATA_PATH,
app_name + '-' + manifest_filename) 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, 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): def generate_images_filename_abs(armada_mfile_dir, app_name):
return os.path.join(constants.APP_SYNCED_DATA_PATH, return os.path.join(armada_mfile_dir, app_name + '-images.yaml')
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): def create_app_path(path):
@ -134,18 +141,20 @@ class AppOperator(object):
def _cleanup(self, app): def _cleanup(self, app):
"""" Remove application directories and override files """ """" Remove application directories and override files """
try: try:
if app.system_app and app.status != constants.APP_UPLOAD_FAILURE: if os.path.exists(app.overrides_dir):
self._remove_chart_overrides(app.armada_mfile_abs) shutil.rmtree(os.path.dirname(
app.overrides_dir))
if os.path.exists(app.armada_mfile_abs): if os.path.exists(app.armada_mfile_dir):
os.unlink(app.armada_mfile_abs) shutil.rmtree(os.path.dirname(
if os.path.exists(app.imgfile_abs): app.armada_mfile_dir))
os.unlink(app.imgfile_abs)
if os.path.exists(app.path): if os.path.exists(app.path):
shutil.rmtree(app.path) shutil.rmtree(os.path.dirname(
app.path))
except OSError as e: except OSError as e:
LOG.error(e) LOG.error(e)
raise
def _update_app_status(self, app, new_status=None, new_progress=None): def _update_app_status(self, app, new_status=None, new_progress=None):
""" Persist new app status """ """ Persist new app status """
@ -244,10 +253,13 @@ class AppOperator(object):
orig_uid, orig_gid = get_app_install_root_path_ownership() orig_uid, orig_gid = get_app_install_root_path_ownership()
try: 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): if not os.path.isdir(constants.APP_SYNCED_DATA_PATH):
os.makedirs(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): if not os.path.isdir(app.path):
create_app_path(app.path) create_app_path(app.path)
@ -307,7 +319,7 @@ class AppOperator(object):
image_tags.extend(ids) image_tags.extend(ids)
return list(set(image_tags)) 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 """ 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 image tags to the manifest file if the image tags from the charts
do not exist in both overrides file and manifest file. Convert 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 # Get the image tags from the overrides file
overrides = chart_namespace + '-' + chart_name + '.yaml' 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): if os.path.exists(app_overrides_file):
try: try:
with open(app_overrides_file, 'r') as f: with open(app_overrides_file, 'r') as f:
@ -443,12 +455,12 @@ class AppOperator(object):
LOG.info("Generating application overrides...") LOG.info("Generating application overrides...")
app.charts = self._get_list_of_charts(app.armada_mfile_abs) app.charts = self._get_list_of_charts(app.armada_mfile_abs)
self._helm.generate_helm_application_overrides( self._helm.generate_helm_application_overrides(
app.name, mode=None, cnamespace=None, armada_format=True, app.overrides_dir, app.name, mode=None, cnamespace=None,
armada_chart_info=app.charts, combined=True) armada_format=True, armada_chart_info=app.charts, combined=True)
self._save_images_list_by_charts(app) self._save_images_list_by_charts(app)
# Get the list of images from the updated images overrides # Get the list of images from the updated images overrides
images_to_download = self._get_image_tags_by_charts( 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: else:
# For custom apps, mine image tags from application path # For custom apps, mine image tags from application path
images_to_download = self._get_image_tags_by_path(app.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_images_list = self._retrieve_images_list(app.imgfile_abs)
saved_download_images_list = list(saved_images_list.get("download_images")) saved_download_images_list = list(saved_images_list.get("download_images"))
images_to_download = self._get_image_tags_by_charts( 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): if set(saved_download_images_list) != set(images_to_download):
saved_images_list.update({"download_images": images_to_download}) saved_images_list.update({"download_images": images_to_download})
with open(app.imgfile_abs, 'wb') as f: with open(app.imgfile_abs, 'wb') as f:
@ -858,7 +870,7 @@ class AppOperator(object):
pass pass
return charts 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 """Returns list of override files or None, used in
application-install and application-delete.""" application-install and application-delete."""
@ -867,8 +879,7 @@ class AppOperator(object):
for chart in charts: for chart in charts:
overrides = chart.namespace + '-' + chart.name + '.yaml' overrides = chart.namespace + '-' + chart.name + '.yaml'
overrides_file = os.path.join(common.HELM_OVERRIDES_PATH, overrides_file = os.path.join(overrides_dir, overrides)
overrides)
if not os.path.exists(overrides_file): if not os.path.exists(overrides_file):
missing_overrides.append(overrides_file) missing_overrides.append(overrides_file)
else: else:
@ -881,8 +892,7 @@ class AppOperator(object):
chart.name, chart.namespace, app_name, mode): chart.name, chart.namespace, app_name, mode):
overrides = chart.namespace + '-' + chart.name + \ overrides = chart.namespace + '-' + chart.name + \
'-meta' + '.yaml' '-meta' + '.yaml'
overrides_file = os.path.join(common.HELM_OVERRIDES_PATH, overrides_file = os.path.join(overrides_dir, overrides)
overrides)
if not os.path.exists(overrides_file): if not os.path.exists(overrides_file):
missing_overrides.append(overrides_file) missing_overrides.append(overrides_file)
else: else:
@ -893,15 +903,17 @@ class AppOperator(object):
return None return None
return available_overrides return available_overrides
def _generate_armada_overrides_str(self, overrides_files): def _generate_armada_overrides_str(self, app_name, app_version, overrides_files):
return " ".join([' --values /overrides/{0}'.format(os.path.basename(i)) return " ".join([' --values /overrides/{0}/{1}/{2}'.format(app_name, app_version,
os.path.basename(i))
for i in overrides_files]) 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) charts = self._get_list_of_charts(manifest_file)
for chart in charts: for chart in charts:
if chart.name in self._helm.chart_operators: 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) chart.namespace)
def _make_armada_request_with_monitor(self, app, request, overrides_str=None): 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._upload_helm_charts(app)
self._save_images_list(app) self._save_images_list(app)
self._update_app_status(app, constants.APP_UPLOAD_SUCCESS)
if app.patch_dependencies: if app.patch_dependencies:
self._app._patch_report_app_dependencies( self._app._patch_report_app_dependencies(
app.name, app.patch_dependencies) app.name, app.patch_dependencies)
self._update_app_status(app, constants.APP_UPLOAD_SUCCESS)
LOG.info("Application (%s) upload completed." % app.name) LOG.info("Application (%s) upload completed." % app.name)
except exception.KubeAppUploadFailure as e: except exception.KubeAppUploadFailure as e:
LOG.exception(e) LOG.exception(e)
@ -1182,16 +1194,19 @@ class AppOperator(object):
app, new_progress=constants.APP_PROGRESS_GENERATE_OVERRIDES) app, new_progress=constants.APP_PROGRESS_GENERATE_OVERRIDES)
LOG.info("Generating application overrides...") LOG.info("Generating application overrides...")
self._helm.generate_helm_application_overrides( self._helm.generate_helm_application_overrides(
app.name, mode, cnamespace=None, armada_format=True, app.overrides_dir, app.name, mode, cnamespace=None,
armada_chart_info=app.charts, combined=True) armada_format=True, armada_chart_info=app.charts, combined=True)
overrides_files = self._get_overrides_files(app.charts, app.name, mode) overrides_files = self._get_overrides_files(app.overrides_dir,
app.charts,
app.name, mode)
if overrides_files: if overrides_files:
LOG.info("Application overrides generated.") LOG.info("Application overrides generated.")
# Ensure all chart overrides are readable by Armada # Ensure all chart overrides are readable by Armada
for file in overrides_files: for file in overrides_files:
os.chmod(file, 0o644) os.chmod(file, 0o644)
overrides_str =\ overrides_str =\
self._generate_armada_overrides_str(overrides_files) self._generate_armada_overrides_str(app.name, app.version,
overrides_files)
self._update_app_status( self._update_app_status(
app, new_progress=constants.APP_PROGRESS_DOWNLOAD_IMAGES) app, new_progress=constants.APP_PROGRESS_DOWNLOAD_IMAGES)
self._download_images(app) self._download_images(app)
@ -1321,22 +1336,33 @@ class AppOperator(object):
def __init__(self, rpc_app, is_system_app): def __init__(self, rpc_app, is_system_app):
self._kube_app = rpc_app self._kube_app = rpc_app
self.path = os.path.join(constants.APP_INSTALL_PATH, 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.charts_dir = os.path.join(self.path, 'charts')
self.images_dir = os.path.join(self.path, 'images') self.images_dir = os.path.join(self.path, 'images')
self.tarfile = None self.tarfile = None
self.downloaded_tarfile = False self.downloaded_tarfile = False
self.system_app = is_system_app 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.armada_mfile = generate_armada_manifest_filename(
self._kube_app.get('name'), self._kube_app.get('name'),
self._kube_app.get('app_version'),
self._kube_app.get('manifest_file')) self._kube_app.get('manifest_file'))
self.armada_mfile_abs = generate_armada_manifest_filename_abs( self.armada_mfile_abs = generate_armada_manifest_filename_abs(
self.armada_mfile_dir,
self._kube_app.get('name'), self._kube_app.get('name'),
self._kube_app.get('manifest_file')) self._kube_app.get('manifest_file'))
self.mfile_abs = generate_manifest_filename_abs( self.mfile_abs = generate_manifest_filename_abs(
self._kube_app.get('name'), self._kube_app.get('name'),
self._kube_app.get('app_version'),
self._kube_app.get('manifest_file')) self._kube_app.get('manifest_file'))
self.imgfile_abs = generate_images_filename_abs( self.imgfile_abs = generate_images_filename_abs(
self.armada_mfile_dir,
self._kube_app.get('name')) self._kube_app.get('name'))
self.patch_dependencies = [] self.patch_dependencies = []
@ -1379,25 +1405,35 @@ class AppOperator(object):
self._kube_app.manifest_name = new_mname self._kube_app.manifest_name = new_mname
self._kube_app.manifest_file = new_mfile self._kube_app.manifest_file = new_mfile
self.armada_mfile = generate_armada_manifest_filename( 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.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.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): def regenerate_application_info(self, new_name, new_version, new_patch_dependencies):
self._kube_app.name = new_name self._kube_app.name = new_name
self._kube_app.app_version = new_version self._kube_app.app_version = new_version
self.system_app = \ self.system_app = \
(self.name == constants.HELM_APP_OPENSTACK) (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( new_path = os.path.join(
constants.APP_INSTALL_PATH, self.name) constants.APP_INSTALL_PATH, self.name, self.version)
os.rename(self.path, new_path) shutil.move(self.path, new_path)
shutil.rmtree(os.path.dirname(self.path))
self.path = new_path self.path = new_path
self.charts_dir = os.path.join(self.path, 'charts') self.charts_dir = os.path.join(self.path, 'charts')
self.images_dir = os.path.join(self.path, 'images') 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 self.patch_dependencies = new_patch_dependencies

View File

@ -7358,9 +7358,10 @@ class Connection(api.Connection):
raise exception.CertificateNotFound(uuid) raise exception.CertificateNotFound(uuid)
query.delete() query.delete()
def _helm_override_get(self, name, namespace): def _helm_override_get(self, app_id, name, namespace):
query = model_query(models.HelmOverrides) 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: try:
return query.one() return query.one()
except NoResultFound: except NoResultFound:
@ -7382,12 +7383,13 @@ class Connection(api.Connection):
(values['name'])) (values['name']))
raise exception.HelmOverrideAlreadyExists( raise exception.HelmOverrideAlreadyExists(
name=values['name'], namespace=values['namespace']) 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']) values['namespace'])
@objects.objectify(objects.helm_overrides) @objects.objectify(objects.helm_overrides)
def helm_override_get(self, name, namespace): def helm_override_get(self, app_id, name, namespace):
return self._helm_override_get(name, namespace) return self._helm_override_get(app_id, name, namespace)
@objects.objectify(objects.helm_overrides) @objects.objectify(objects.helm_overrides)
def helm_override_get_all(self): def helm_override_get_all(self):
@ -7395,10 +7397,11 @@ class Connection(api.Connection):
return query.all() return query.all()
@objects.objectify(objects.helm_overrides) @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: with _session_for_write() as session:
query = model_query(models.HelmOverrides, session=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') count = query.update(values, synchronize_session='fetch')
if count == 0: if count == 0:
@ -7406,10 +7409,11 @@ class Connection(api.Connection):
namespace=namespace) namespace=namespace)
return query.one() 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: with _session_for_write() as session:
query = model_query(models.HelmOverrides, session=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: try:
query.one() query.one()
@ -7573,6 +7577,7 @@ class Connection(api.Connection):
"operation is not allowed while status is " + app.status "operation is not allowed while status is " + app.status
raise exception.KubeAppDeleteFailure( raise exception.KubeAppDeleteFailure(
name=name, name=name,
version=app.app_version,
reason=failure_reason) reason=failure_reason)
except NoResultFound: except NoResultFound:
raise exception.KubeAppNotFound(name) raise exception.KubeAppNotFound(name)

View File

@ -25,7 +25,7 @@ def upgrade(migrate_engine):
meta = MetaData() meta = MetaData()
meta.bind = migrate_engine meta.bind = migrate_engine
# Define and create the helm_overrides table. # Define and create the kube_app table.
kube_app = Table( kube_app = Table(
'kube_app', 'kube_app',
meta, meta,

View File

@ -6,7 +6,7 @@
# #
from sqlalchemy import DateTime, String, Text, Integer 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 from sysinv.openstack.common import log
@ -25,6 +25,8 @@ def upgrade(migrate_engine):
meta = MetaData() meta = MetaData()
meta.bind = migrate_engine meta.bind = migrate_engine
Table('kube_app', meta, autoload=True)
# Define and create the helm_overrides table. # Define and create the helm_overrides table.
helm_overrides = Table( helm_overrides = Table(
'helm_overrides', 'helm_overrides',
@ -36,7 +38,9 @@ def upgrade(migrate_engine):
Column('name', String(255), nullable=False), Column('name', String(255), nullable=False),
Column('namespace', String(255), nullable=False), Column('namespace', String(255), nullable=False),
Column('user_overrides', Text, nullable=True), 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_engine=ENGINE,
mysql_charset=CHARSET, mysql_charset=CHARSET,

View File

@ -1677,7 +1677,9 @@ class HelmOverrides(Base):
namespace = Column(String(255), nullable=False) namespace = Column(String(255), nullable=False)
user_overrides = Column(Text, nullable=True) user_overrides = Column(Text, nullable=True)
system_overrides = Column(JSONEncodedDict, 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): class Label(Base):

View File

@ -45,12 +45,8 @@ def helm_context(func):
class HelmOperator(object): class HelmOperator(object):
"""Class to encapsulate helm override operations for System Inventory""" """Class to encapsulate helm override operations for System Inventory"""
def __init__(self, dbapi=None, path=None): def __init__(self, dbapi=None):
if path is None:
path = common.HELM_OVERRIDES_PATH
self.dbapi = dbapi self.dbapi = dbapi
self.path = path
# register chart operators for lookup # register chart operators for lookup
self.chart_operators = {} self.chart_operators = {}
@ -377,7 +373,7 @@ class HelmOperator(object):
return values return values
@helm_context @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 """Generate system helm chart overrides
This method will generate system helm chart override an write them to a 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( overrides = self._get_helm_chart_overrides(
chart_name, chart_name,
cnamespace) cnamespace)
self._write_chart_overrides(chart_name, self._write_chart_overrides(path,
chart_name,
cnamespace, cnamespace,
overrides) overrides)
except Exception as e: except Exception as e:
@ -424,7 +421,8 @@ class HelmOperator(object):
return overrides return overrides
@helm_context @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, cnamespace=None,
armada_format=False, armada_format=False,
armada_chart_info=None, armada_chart_info=None,
@ -450,6 +448,11 @@ class HelmOperator(object):
""" """
if app_name in self.helm_applications: 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, app_overrides = self._get_helm_application_overrides(app_name,
cnamespace) cnamespace)
for (chart_name, overrides) in iteritems(app_overrides): for (chart_name, overrides) in iteritems(app_overrides):
@ -468,7 +471,7 @@ class HelmOperator(object):
for chart_namespace in overrides.keys(): for chart_namespace in overrides.keys():
try: try:
db_chart = self.dbapi.helm_override_get( db_chart = self.dbapi.helm_override_get(
chart_name, chart_namespace) app.id, chart_name, chart_namespace)
db_user_overrides = db_chart.user_overrides db_user_overrides = db_chart.user_overrides
if db_user_overrides: if db_user_overrides:
file_overrides.append(yaml.dump( file_overrides.append(yaml.dump(
@ -495,7 +498,7 @@ class HelmOperator(object):
chart_name, armada_chart_repo_name, chart_name, armada_chart_repo_name,
key, overrides[key]) key, overrides[key])
overrides[key] = new_overrides 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 # Write any meta-overrides for this chart. These will be in
# armada format already. # armada format already.
@ -507,14 +510,14 @@ class HelmOperator(object):
if overrides: if overrides:
chart_meta_name = chart_name + '-meta' chart_meta_name = chart_name + '-meta'
self._write_chart_overrides( self._write_chart_overrides(
chart_meta_name, cnamespace, overrides) path, chart_meta_name, cnamespace, overrides)
elif app_name: elif app_name:
LOG.exception("%s application is not supported" % app_name) LOG.exception("%s application is not supported" % app_name)
else: else:
LOG.exception("application name is required") 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""" """Remove the overrides files for a chart"""
if chart_name in self.chart_operators: if chart_name in self.chart_operators:
@ -529,7 +532,7 @@ class HelmOperator(object):
for f in filenames: for f in filenames:
try: try:
self._remove_overrides(f) self._remove_overrides(path, f)
except Exception as e: except Exception as e:
LOG.exception("failed to remove %s overrides: %s: %s" % ( LOG.exception("failed to remove %s overrides: %s: %s" % (
chart_name, f, e)) chart_name, f, e))
@ -537,12 +540,12 @@ class HelmOperator(object):
LOG.exception("chart %s not supported for system overrides" % LOG.exception("chart %s not supported for system overrides" %
chart_name) 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. """ """Write a one or more overrides files for a chart. """
def _write_file(filename, values): def _write_file(filename, values):
try: try:
self._write_overrides(filename, values) self._write_overrides(path, filename, values)
except Exception as e: except Exception as e:
LOG.exception("failed to write %s overrides: %s: %s" % ( LOG.exception("failed to write %s overrides: %s: %s" % (
chart_name, filename, e)) chart_name, filename, e))
@ -553,12 +556,15 @@ class HelmOperator(object):
for ns in overrides.keys(): for ns in overrides.keys():
_write_file("%s-%s.yaml" % (ns, chart_name), overrides[ns]) _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. """ """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: try:
fd, tmppath = tempfile.mkstemp(dir=self.path, prefix=filename, fd, tmppath = tempfile.mkstemp(dir=path, prefix=filename,
text=True) text=True)
with open(tmppath, 'w') as f: with open(tmppath, 'w') as f:
@ -569,10 +575,10 @@ class HelmOperator(object):
LOG.exception("failed to write overrides file: %s" % filepath) LOG.exception("failed to write overrides file: %s" % filepath)
raise raise
def _remove_overrides(self, filename): def _remove_overrides(self, path, filename):
"""Remove a single overrides file. """ """Remove a single overrides file. """
filepath = os.path.join(self.path, filename) filepath = os.path.join(path, filename)
try: try:
if os.path.exists(filepath): if os.path.exists(filepath):
os.unlink(filepath) os.unlink(filepath)

View File

@ -149,7 +149,9 @@ class OpenstackBaseHelm(base.BaseHelm):
return None return None
try: 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) namespace=namespace)
except exception.HelmOverrideNotFound: except exception.HelmOverrideNotFound:
# Override for this chart not found, so create one # Override for this chart not found, so create one
@ -157,6 +159,7 @@ class OpenstackBaseHelm(base.BaseHelm):
values = { values = {
'name': chart, 'name': chart,
'namespace': namespace, 'namespace': namespace,
'app_id': app.id,
} }
override = self.dbapi.helm_override_create(values=values) override = self.dbapi.helm_override_create(values=values)
except Exception as e: except Exception as e:
@ -176,7 +179,7 @@ class OpenstackBaseHelm(base.BaseHelm):
}) })
try: try:
self.dbapi.helm_override_update( 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: except Exception as e:
LOG.exception(e) LOG.exception(e)
@ -337,13 +340,16 @@ class OpenstackBaseHelm(base.BaseHelm):
def _get_or_generate_ssh_keys(self, chart, namespace): def _get_or_generate_ssh_keys(self, chart, namespace):
try: 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) namespace=namespace)
except exception.HelmOverrideNotFound: except exception.HelmOverrideNotFound:
# Override for this chart not found, so create one # Override for this chart not found, so create one
values = { values = {
'name': chart, 'name': chart,
'namespace': namespace, 'namespace': namespace,
'app_id': app.id
} }
override = self.dbapi.helm_override_create(values=values) override = self.dbapi.helm_override_create(values=values)
@ -362,7 +368,7 @@ class OpenstackBaseHelm(base.BaseHelm):
values['system_overrides'].update({'privatekey': newprivatekey, values['system_overrides'].update({'privatekey': newprivatekey,
'publickey': newpublickey}) 'publickey': newpublickey})
self.dbapi.helm_override_update( self.dbapi.helm_override_update(
name=chart, namespace=namespace, values=values) app_id=app.id, name=chart, namespace=namespace, values=values)
return newprivatekey, newpublickey return newprivatekey, newpublickey

View File

@ -21,11 +21,13 @@ class HelmOverrides(base.SysinvObject):
'namespace': utils.str_or_none, 'namespace': utils.str_or_none,
'user_overrides': utils.str_or_none, 'user_overrides': utils.str_or_none,
'system_overrides': utils.dict_or_none, 'system_overrides': utils.dict_or_none,
'app_id': int
} }
@base.remotable_classmethod @base.remotable_classmethod
def get_by_name(cls, context, name, namespace): def get_by_appid_name(cls, context, app_id, name, namespace):
return cls.dbapi.helm_override_get(name, namespace) return cls.dbapi.helm_override_get(app_id, name, namespace)
def save_changes(self, context, updates): 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)