Merge "Support deploying application with versioned app tarball"
This commit is contained in:
commit
0e5b63bc57
|
@ -1,2 +1,2 @@
|
|||
SRC_DIR="cgts-client"
|
||||
TIS_PATCH_VER=63
|
||||
TIS_PATCH_VER=64
|
||||
|
|
|
@ -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>',
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
SRC_DIR="sysinv"
|
||||
TIS_PATCH_VER=311
|
||||
TIS_PATCH_VER=312
|
||||
|
|
|
@ -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))))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1481,6 +1481,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'
|
||||
|
@ -1514,6 +1515,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'
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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 """
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue