Support deploying application with versioned app tarball

This commit updates the upload workflow to support uploading versioned
application tarball with metadata file. The app name and version are
extracted from the metadata file. If the tarball doesn't contain the
app name and version, they need to be specified when uploading.

If the application has patch dependencies, the required patches
need to be fully applied before uploading. After application
upload/delete is done, sysinv reports the patch dependencies to
patch controller.

Tests conducted:
- upload/apply/remove/delete(stx-openstack and custom app)
- upload via url
- failure tests:
  - metadata file has empty name/version
  - no app name/version provided from CLI or metadata file
  - required patch(es) not applies
  - upload without specify tarball
  ...

Story: 2005350
Task: 30609
Depends-On: https://review.opendev.org/655494
Change-Id: I096d5ac126efc97f9a0a0f54f1e02323d936281c
Signed-off-by: Angie Wang <angie.wang@windriver.com>
This commit is contained in:
Angie Wang 2019-04-18 16:22:12 -04:00
parent 63253961a1
commit bc28061090
13 changed files with 352 additions and 120 deletions

View File

@ -1,2 +1,2 @@
SRC_DIR="cgts-client"
TIS_PATCH_VER=63
TIS_PATCH_VER=64

View File

@ -45,8 +45,8 @@ def _is_url(url_str):
def do_application_list(cc, args):
"""List all containerized applications"""
apps = cc.app.list()
labels = ['application', 'manifest name', 'manifest file', 'status', 'progress']
fields = ['name', 'manifest_name', 'manifest_file', 'status', 'progress']
labels = ['application', 'version', 'manifest name', 'manifest file', 'status', 'progress']
fields = ['name', 'app_version', 'manifest_name', 'manifest_file', 'status', 'progress']
utils.print_list(apps, fields, labels, sortby=0)
@ -61,11 +61,15 @@ def do_application_show(cc, args):
raise exc.CommandError('application not found: %s' % args.name)
@utils.arg('name', metavar='<app name>',
help='Name of the application')
@utils.arg('tarfile', metavar='<tar file>',
help='Tarball containing application manifest, helm charts and'
' config file')
@utils.arg('-n', '--app-name',
metavar='<app name>',
help='Name of the application')
@utils.arg('-v', '--app-version',
metavar='<app version>',
help='Version of the application')
def do_application_upload(cc, args):
"""Upload application Helm chart(s) and manifest"""
tarfile = args.tarfile
@ -81,11 +85,15 @@ def do_application_upload(cc, args):
"extension. Supported extensions are: .tgz "
"and .tar.gz" % tarfile)
data = {'name': args.name,
'tarfile': tarfile}
data = {'tarfile': tarfile}
if args.app_name:
data.update({'name': args.app_name})
if args.app_version:
data.update({'app_version': args.app_version})
response = cc.app.upload(data)
_print_application_show(response)
_print_reminder_msg(args.name)
_print_reminder_msg(response.name)
@utils.arg('name', metavar='<app name>',

View File

@ -1,2 +1,2 @@
SRC_DIR="sysinv"
TIS_PATCH_VER=311
TIS_PATCH_VER=312

View File

@ -17,6 +17,7 @@ from contextlib import contextmanager
from sysinv import objects
from sysinv.api.controllers.v1 import base
from sysinv.api.controllers.v1 import collection
from sysinv.api.controllers.v1 import patch_api
from sysinv.api.controllers.v1 import types
from sysinv.api.controllers.v1 import utils
from sysinv.common import constants
@ -25,6 +26,7 @@ from sysinv.common import utils as cutils
from sysinv.openstack.common import log
from sysinv.openstack.common.gettextutils import _
import cgcs_patch.constants as patch_constants
LOG = log.getLogger(__name__)
@ -50,6 +52,9 @@ class KubeApp(base.APIBase):
name = wtypes.text
"Represents the name of the application"
app_version = wtypes.text
"Represents the version of the application"
created_at = wtypes.datetime.datetime
"Represents the time the application was uploaded"
@ -79,7 +84,7 @@ class KubeApp(base.APIBase):
def convert_with_links(cls, rpc_app, expand=True):
app = KubeApp(**rpc_app.as_dict())
if not expand:
app.unset_fields_except(['name', 'manifest_name',
app.unset_fields_except(['name', 'app_version', 'manifest_name',
'manifest_file', 'status', 'progress'])
# skip the id
@ -118,76 +123,45 @@ class KubeAppController(rest.RestController):
if not utils.is_kubernetes_config():
raise exception.OperationNotPermitted
def _check_tarfile(self, app_name, app_tarfile):
if app_name and app_tarfile:
def _check_tarfile(self, app_tarfile, app_name, app_version):
def _handle_upload_failure(reason):
raise wsme.exc.ClientSideError(_(
"Application-upload rejected: " + reason))
if app_tarfile:
if not os.path.isfile(app_tarfile):
raise wsme.exc.ClientSideError(_(
"Application-upload rejected: application tar file {} does "
"not exist.".format(app_tarfile)))
_handle_upload_failure(
"application tar file {} does not exist.".format(app_tarfile))
if (not app_tarfile.endswith('.tgz') and
not app_tarfile.endswith('.tar.gz')):
raise wsme.exc.ClientSideError(_(
"Application-upload rejected: {} has unrecognizable tar file "
"extension. Supported extensions are: .tgz and .tar.gz.".format(
app_tarfile)))
_handle_upload_failure(
"{} has unrecognizable tar file extension. Supported "
"extensions are: .tgz and .tar.gz.".format(app_tarfile))
with TempDirectory() as app_path:
if not cutils.extract_tarfile(app_path, app_tarfile):
raise wsme.exc.ClientSideError(_(
"Application-upload rejected: failed to extract tar file "
"{}.".format(os.path.basename(app_tarfile))))
_handle_upload_failure(
"failed to extract tar file {}.".format(os.path.basename(app_tarfile)))
# If checksum file is included in the tarball, verify its contents.
if not cutils.verify_checksum(app_path):
raise wsme.exc.ClientSideError(_(
"Application-upload rejected: checksum validation failed."))
_handle_upload_failure("checksum validation failed.")
mname, mfile = self._find_manifest_file(app_path)
app_helper = KubeAppHelper(pecan.request.dbapi)
try:
name, version, patches = app_helper._verify_metadata_file(
app_path, app_name, app_version)
mname, mfile = app_helper._find_manifest_file(app_path)
app_helper._extract_helm_charts(app_path)
LOG.info("Tar file of application %s verified." % name)
except exception.SysinvException as e:
_handle_upload_failure(str(e))
charts_dir = os.path.join(app_path, 'charts')
if os.path.isdir(charts_dir):
tar_filelist = cutils.get_files_matching(app_path, '.tgz')
if len(os.listdir(charts_dir)) == 0:
raise wsme.exc.ClientSideError(_(
"Application-upload rejected: tar file contains no "
"Helm charts."))
if not tar_filelist:
raise wsme.exc.ClientSideError(_(
"Application-upload rejected: tar file contains no "
"Helm charts of expected file extension (.tgz)."))
for p, f in tar_filelist:
if not cutils.extract_tarfile(p, os.path.join(p, f)):
raise wsme.exc.ClientSideError(_(
"Application-upload rejected: failed to extract tar "
"file {}.".format(os.path.basename(f))))
LOG.info("Tar file of application %s verified." % app_name)
return mname, mfile
return name, version, mname, mfile
else:
raise ValueError(_(
"Application-upload rejected: both application name and tar "
"file must be specified."))
def _find_manifest_file(self, app_path):
# It is expected that there is only one manifest file
# per application and the file exists at top level of
# the application path.
mfiles = cutils.find_manifest_file(app_path)
if mfiles is None:
raise wsme.exc.ClientSideError(_(
"Application-upload rejected: manifest file is corrupted."))
if mfiles:
if len(mfiles) == 1:
return mfiles[0]
else:
raise wsme.exc.ClientSideError(_(
"Application-upload rejected: tar file contains more "
"than one manifest file."))
else:
raise wsme.exc.ClientSideError(_(
"Application-upload rejected: manifest file is missing."))
"Application-upload rejected: tar file must be specified."))
def _get_one(self, app_name):
# can result in KubeAppNotFound
@ -213,8 +187,24 @@ class KubeAppController(rest.RestController):
"""Uploading an application to be deployed by Armada"""
self._check_environment()
name = body.get('name')
tarfile = body.get('tarfile')
name = body.get('name', '')
version = body.get('app_version', '')
if not cutils.is_url(tarfile):
name, version, mname, mfile = self._check_tarfile(tarfile, name, version)
else:
# For tarfile that is downloaded remotely, defer the checksum, manifest
# and tarfile content validations to sysinv-conductor as download can
# take some time depending on network traffic, target server and file
# size.
mname = constants.APP_MANIFEST_NAME_PLACEHOLDER
mfile = constants.APP_TARFILE_NAME_PLACEHOLDER
if not name:
name = constants.APP_NAME_PLACEHOLDER
if not version:
version = constants.APP_VERSION_PLACEHOLDER
try:
objects.kube_app.get_by_name(pecan.request.context, name)
@ -224,19 +214,10 @@ class KubeAppController(rest.RestController):
except exception.KubeAppNotFound:
pass
if not cutils.is_url(tarfile):
mname, mfile = self._check_tarfile(name, tarfile)
else:
# For tarfile that is downloaded remotely, defer the checksum, manifest
# and tarfile content validations to sysinv-conductor as download can
# take some time depending on network traffic, target server and file
# size.
mname = constants.APP_MANIFEST_NAME_PLACEHOLDER
mfile = constants.APP_TARFILE_NAME_PLACEHOLDER
# Create a database entry and make an rpc async request to upload
# the application
app_data = {'name': name,
'app_version': version,
'manifest_name': mname,
'manifest_file': os.path.basename(mfile),
'status': constants.APP_UPLOAD_IN_PROGRESS}
@ -324,3 +305,141 @@ class KubeAppController(rest.RestController):
if response:
raise wsme.exc.ClientSideError(_(
"%s." % response))
class KubeAppHelper(object):
def __init__(self, dbapi):
self._dbapi = dbapi
def _check_patching_operation(self):
try:
system = self._dbapi.isystem_get_one()
response = patch_api.patch_query(
token=None,
timeout=constants.PATCH_DEFAULT_TIMEOUT_IN_SECS,
region_name=system.region_name
)
query_patches = response['pd']
except Exception as e:
LOG.error(_("No response from patch api: %s" % e))
return
for patch in query_patches:
patch_state = query_patches[patch].get('patchstate', None)
if (patch_state == patch_constants.PARTIAL_APPLY or
patch_state == patch_constants.PARTIAL_REMOVE):
raise exception.SysinvException(_(
"Patching operation is in progress."))
def _check_patch_is_applied(self, patches):
try:
system = self._dbapi.isystem_get_one()
response = patch_api.patch_is_applied(
token=None,
timeout=constants.PATCH_DEFAULT_TIMEOUT_IN_SECS,
region_name=system.region_name,
patches=patches
)
except Exception as e:
LOG.error(e)
raise exception.SysinvException(_(
"Error while querying patch-controller for the "
"state of the patch(es)."))
return response
def _patch_report_app_dependencies(self, name, patches=[]):
try:
system = self._dbapi.isystem_get_one()
patch_api.patch_report_app_dependencies(
token=None,
timeout=constants.PATCH_DEFAULT_TIMEOUT_IN_SECS,
region_name=system.region_name,
patches=patches,
app_name=name
)
except Exception as e:
LOG.error(e)
raise exception.SysinvException(
"Error while reporting the patch dependencies "
"to patch-controller.")
def _find_manifest_file(self, app_path):
# It is expected that there is only one manifest file
# per application and the file exists at top level of
# the application path.
mfiles = cutils.find_manifest_file(app_path)
if mfiles is None:
raise exception.SysinvException(_(
"manifest file is corrupted."))
if mfiles:
if len(mfiles) == 1:
return mfiles[0]
else:
raise exception.SysinvException(_(
"Application-upload rejected: tar file contains more "
"than one manifest file."))
else:
raise exception.SysinvException(_(
"Application-upload rejected: manifest file is missing."))
def _verify_metadata_file(self, app_path, app_name, app_version):
try:
name, version, patches = cutils.find_metadata_file(
app_path, constants.APP_METADATA_FILE)
except exception.SysinvException as e:
raise exception.SysinvException(_(
"metadata validation failed. {}".format(e)))
if not name:
name = app_name
if not version:
version = app_version
if (not name or not version or
name == constants.APP_VERSION_PLACEHOLDER or
version == constants.APP_VERSION_PLACEHOLDER):
raise exception.SysinvException(_(
"application name or/and version is/are not included "
"in the tar file. Please specify the application name "
"via --app-name or/and version via --app-version."))
if patches:
try:
self._check_patching_operation()
except exception.SysinvException as e:
raise exception.SysinvException(_(
"{}. Please upload after the patching operation "
"is completed.".format(e)))
applied = self._check_patch_is_applied(patches)
if not applied:
raise exception.SysinvException(_(
"the required patch(es) for application {} ({}) "
"must be applied".format(name, version)))
LOG.info("The required patch(es) for application {} ({}) "
"has/have applied.".format(name, version))
else:
LOG.info("No patch required for application {} ({}).".format(name, version))
return name, version, patches
def _extract_helm_charts(self, app_path, demote_user=False):
charts_dir = os.path.join(app_path, 'charts')
if os.path.isdir(charts_dir):
tar_filelist = cutils.get_files_matching(app_path, '.tgz')
if len(os.listdir(charts_dir)) == 0:
raise exception.SysinvException(_(
"tar file contains no Helm charts."))
if not tar_filelist:
raise exception.SysinvException(_(
"tar file contains no Helm charts of "
"expected file extension (.tgz)."))
for p, f in tar_filelist:
if not cutils.extract_tarfile(
p, os.path.join(p, f), demote_user):
raise exception.SysinvException(_(
"failed to extract tar file {}.".format(os.path.basename(f))))

View File

@ -62,3 +62,45 @@ def patch_drop_host(token, timeout, hostname, region_name):
response = rest_api_request(token, "POST", api_cmd, timeout=timeout)
return response
def patch_is_applied(token, timeout, region_name, patches):
"""
Query the applied state for a list of patches
"""
api_cmd = None
if not token:
token = get_token(region_name)
if token:
api_cmd = token.get_service_url("patching", "patching")
patch_dependencies = ""
for patch in patches:
patch_dependencies += "/%s" % patch
api_cmd += "/v1/is_applied%s" % patch_dependencies
response = rest_api_request(token, "GET", api_cmd, timeout=timeout)
return response
def patch_report_app_dependencies(token, timeout, region_name, patches, app_name):
"""
Report the application patch dependencies
"""
api_cmd = None
if not token:
token = get_token(region_name)
if token:
api_cmd = token.get_service_url("patching", "patching")
patch_dependencies = ""
for patch in patches:
patch_dependencies += "/%s" % patch
api_cmd += "/v1/report_app_dependencies%s?app=%s" % (patch_dependencies, app_name)
response = rest_api_request(token, "POST", api_cmd, timeout=timeout)
return response

View File

@ -1484,6 +1484,7 @@ K8S_RBD_PROV_STOR_CLASS_NAME = 'general'
APP_INSTALL_ROOT_PATH = '/scratch'
APP_INSTALL_PATH = APP_INSTALL_ROOT_PATH + '/apps'
APP_SYNCED_DATA_PATH = os.path.join(tsc.PLATFORM_PATH, 'armada', tsc.SW_VERSION)
APP_METADATA_FILE = 'metadata.yaml'
# State constants
APP_UPLOAD_IN_PROGRESS = 'uploading'
@ -1517,6 +1518,8 @@ LABEL_ASSIGN_OP = 'assign'
LABEL_REMOVE_OP = 'remove'
# Placeholder constants
APP_NAME_PLACEHOLDER = 'app-name-placeholder'
APP_VERSION_PLACEHOLDER = 'app-version-placeholder'
APP_MANIFEST_NAME_PLACEHOLDER = 'manifest-placeholder'
APP_TARFILE_NAME_PLACEHOLDER = 'tarfile-placeholder'

View File

@ -225,15 +225,15 @@ class CephPoolSetParamFailure(CephFailure):
class KubeAppUploadFailure(SysinvException):
message = _("Upload of application %(name)s failed: %(reason)s")
message = _("Upload of application %(name)s (%(version)s) failed: %(reason)s")
class KubeAppApplyFailure(SysinvException):
message = _("Deployment of application %(name)s failed: %(reason)s")
message = _("Deployment of application %(name)s (%(version)s) failed: %(reason)s")
class KubeAppDeleteFailure(SysinvException):
message = _("Delete of application %(name)s failed: %(reason)s")
message = _("Delete of application %(name)s (%(version)s) failed: %(reason)s")
class InvalidCPUInfo(Invalid):

View File

@ -1896,6 +1896,46 @@ def verify_checksum(path):
return rc
def find_metadata_file(path, metadata_file):
""" Find and validate the metadata file in a given directory.
Valid keys for metadata file are defined in the following format:
app_name: <name>
app_version: <version>
patch_dependencies:
- <patch.1>
- <patch.2>
...
"""
app_name = ''
app_version = ''
patches = []
metadata_path = os.path.join(path, metadata_file)
if os.path.isfile(metadata_path):
with open(metadata_path, 'r') as f:
try:
doc = yaml.safe_load(f)
app_name = doc['app_name']
app_version = doc['app_version']
patches = doc['patch_dependencies']
except KeyError:
# metadata file does not have the key(s)
pass
if (app_name is None or
app_version is None):
raise exception.SysinvException(_(
"Invalid %s: app_name or/and app_version "
"is/are None." % metadata_file))
if not isinstance(patches, list):
raise exception.SysinvException(_(
"Invalid %s: patch_dependencies should "
"be a list." % metadata_file))
return app_name, app_version, patches
def find_manifest_file(path):
""" Find all manifest files in a given directory. """
def _is_manifest(yaml_file):

View File

@ -29,6 +29,7 @@ from eventlet import queue
from eventlet import Timeout
from oslo_config import cfg
from oslo_log import log as logging
from sysinv.api.controllers.v1 import kube_app
from sysinv.common import constants
from sysinv.common import exception
from sysinv.common import kubernetes
@ -125,6 +126,7 @@ class AppOperator(object):
self._docker = DockerHelper(self._dbapi)
self._helm = helm.HelmOperator(self._dbapi)
self._kube = kubernetes.KubeOperator(self._dbapi)
self._app = kube_app.KubeAppHelper(self._dbapi)
self._lock = threading.Lock()
def _cleanup(self, app):
@ -173,11 +175,12 @@ class AppOperator(object):
from six.moves.urllib.error import HTTPError
from six.moves.urllib.error import URLError
from socket import timeout as socket_timeout
from six.moves.urllib.parse import urlparse
from six.moves.urllib.parse import urlsplit
def _handle_download_failure(reason):
raise exception.KubeAppUploadFailure(
name=app.name,
version=app.version,
reason=reason)
try:
@ -187,7 +190,7 @@ class AppOperator(object):
remote_filename = remote_file.info()['Content-Disposition']
except KeyError:
remote_filename = os.path.basename(
urlparse.urlsplit(remote_file.url).path)
urlsplit(remote_file.url).path)
filename_avail = True if (remote_filename is None or
remote_filename == '') else False
@ -233,23 +236,9 @@ class AppOperator(object):
reason='failed to extract tarfile content.'):
raise exception.KubeAppUploadFailure(
name=app.name,
version=app.version,
reason=reason)
def _find_manifest_file(app_path):
mfiles = cutils.find_manifest_file(app_path)
if mfiles is None:
_handle_extract_failure('manifest file is corrupted.')
if mfiles:
if len(mfiles) == 1:
return mfiles[0]
else:
_handle_extract_failure(
'tarfile contains more than one manifest file.')
else:
_handle_extract_failure('manifest file is missing.')
orig_uid, orig_gid = get_app_install_root_path_ownership()
try:
@ -269,28 +258,28 @@ class AppOperator(object):
_handle_extract_failure()
if app.downloaded_tarfile:
name, version, patches = self._app._verify_metadata_file(
app.path, app.name, app.version)
if (name != app.name or version != app.version):
# Save the official application info. They will be
# persisted in the next status update
app.regenerate_application_info(name, version, patches)
if not cutils.verify_checksum(app.path):
_handle_extract_failure('checksum validation failed.')
mname, mfile = _find_manifest_file(app.path)
mname, mfile = self._app._find_manifest_file(app.path)
# Save the official manifest file info. They will be persisted
# in the next status update
app.regenerate_manifest_filename(mname, os.path.basename(mfile))
else:
name, version, patches = cutils.find_metadata_file(
app.path, constants.APP_METADATA_FILE)
app.patch_dependencies = patches
if os.path.isdir(app.charts_dir):
if len(os.listdir(app.charts_dir)) == 0:
_handle_extract_failure('tarfile contains no Helm charts.')
self._app._extract_helm_charts(app.path)
tar_filelist = cutils.get_files_matching(app.charts_dir,
'.tgz')
if not tar_filelist:
reason = 'tarfile contains no Helm charts of expected ' + \
'file extension (.tgz).'
_handle_extract_failure(reason)
for p, f in tar_filelist:
if not cutils.extract_tarfile(
p, os.path.join(p, f), demote_user=True):
_handle_extract_failure()
except exception.SysinvException as e:
_handle_extract_failure(str(e))
except OSError as e:
LOG.error(e)
_handle_extract_failure()
@ -441,6 +430,7 @@ class AppOperator(object):
"""
raise exception.KubeAppApplyFailure(
name=app.name,
version=app.version,
reason="embedded images are not yet supported.")
def _save_images_list(self, app):
@ -466,6 +456,7 @@ class AppOperator(object):
# an info log and let it advance to the next step.
raise exception.KubeAppUploadFailure(
name=app.name,
version=app.version,
reason="charts specify no docker images.")
with open(app.imgfile_abs, 'ab') as f:
@ -536,6 +527,7 @@ class AppOperator(object):
if failed_count > 0:
raise exception.KubeAppApplyFailure(
name=app.name,
version=app.version,
reason="failed to download one or more image(s).")
else:
LOG.info("All docker images for application %s were successfully "
@ -557,11 +549,11 @@ class AppOperator(object):
failed_charts.append(r)
except Exception as e:
raise exception.KubeAppUploadFailure(
name=app.name, reason=str(e))
name=app.name, version=app.version, reason=str(e))
if len(failed_charts) > 0:
raise exception.KubeAppUploadFailure(
name=app.name, reason="one or more charts failed validation.")
name=app.name, version=app.version, reason="one or more charts failed validation.")
def _upload_helm_charts(self, app):
# Set env path for helm-upload execution
@ -582,7 +574,7 @@ class AppOperator(object):
LOG.info("Helm chart %s uploaded" % os.path.basename(chart))
except Exception as e:
raise exception.KubeAppUploadFailure(
name=app.name, reason=str(e))
name=app.name, version=app.version, reason=str(e))
finally:
os.chown(constants.APP_INSTALL_ROOT_PATH, orig_uid, orig_gid)
@ -949,6 +941,9 @@ class AppOperator(object):
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)
LOG.info("Application (%s) upload completed." % app.name)
except exception.KubeAppUploadFailure as e:
LOG.exception(e)
@ -1080,6 +1075,7 @@ class AppOperator(object):
try:
self._dbapi.kube_app_destroy(app.name)
self._cleanup(app)
self._app._patch_report_app_dependencies(app.name)
LOG.info("Application (%s) has been purged from the system." %
app.name)
msg = None
@ -1118,12 +1114,17 @@ class AppOperator(object):
self.imgfile_abs = generate_images_filename_abs(
self._kube_app.get('name'))
self.patch_dependencies = []
self.charts = []
@property
def name(self):
return self._kube_app.get('name')
@property
def version(self):
return self._kube_app.get('app_version')
@property
def status(self):
return self._kube_app.get('status')
@ -1148,6 +1149,21 @@ class AppOperator(object):
self.mfile_abs = generate_manifest_filename_abs(
self.name, 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_path = os.path.join(
constants.APP_INSTALL_PATH, self.name)
os.rename(self.path, new_path)
self.path = new_path
self.charts_dir = os.path.join(self.path, 'charts')
self.images_dir = os.path.join(self.path, 'images')
self.patch_dependencies = new_patch_dependencies
class DockerHelper(object):
""" Utility class to encapsulate Docker related operations """

View File

@ -7566,14 +7566,14 @@ class Connection(api.Connection):
return self._kube_app_get(name)
@objects.objectify(objects.kube_app)
def kube_app_update(self, name, values):
def kube_app_update(self, app_id, values):
with _session_for_write() as session:
query = model_query(models.KubeApp, session=session)
query = query.filter_by(name=name)
query = query.filter_by(id=app_id)
count = query.update(values, synchronize_session='fetch')
if count == 0:
raise exception.KubeAppNotFound(name)
raise exception.KubeAppNotFound(id)
return query.one()
def kube_app_destroy(self, name):

View File

@ -33,6 +33,7 @@ def upgrade(migrate_engine):
Column('updated_at', DateTime),
Column('id', Integer, primary_key=True),
Column('name', String(255), unique=True, nullable=False),
Column('app_version', String(255), nullable=False),
Column('manifest_name', String(255), nullable=False),
Column('manifest_file', String(255), nullable=True),
Column('status', String(255), nullable=False),

View File

@ -1696,6 +1696,7 @@ class KubeApp(Base):
id = Column(Integer, primary_key=True)
name = Column(String(255), unique=True, nullable=False)
app_version = Column(String(255), nullable=False)
manifest_name = Column(String(255), nullable=False)
manifest_file = Column(String(255), nullable=False)
status = Column(String(255), nullable=False)

View File

@ -17,7 +17,9 @@ class KubeApp(base.SysinvObject):
dbapi = db_api.get_instance()
fields = {'name': utils.str_or_none,
fields = {'id': int,
'name': utils.str_or_none,
'app_version': utils.str_or_none,
'manifest_name': utils.str_or_none,
'manifest_file': utils.str_or_none,
'status': utils.str_or_none,
@ -29,4 +31,4 @@ class KubeApp(base.SysinvObject):
return cls.dbapi.kube_app_get(name)
def save_changes(self, context, updates):
self.dbapi.kube_app_update(self.name, updates)
self.dbapi.kube_app_update(self.id, updates)