Enable platform managed applications

Add support for uploading and launching applications without user
interaction.

This commit will:
 - provide a conductor periodic audit task that ensures that platform
   managed applications are uploaded and applied.
 - register the HELM_APP_PLATFORM application as a platform managed
   application.
 - define a well-known location for RPM installed applications that are
   only managed by the audit task.

Actions by the audit task are only performed by the unlocked/available
active controller.

Additional work will later enable automatic upgrade of platform managed
applications as a result of the patching

The user can interact with the platform managed applications using
existing CLI commands: 'system helm-override-xxx' and 'system
application-xxx'

Removed @memoized from the get_active_controller() utility check. During
early initial configuration, this was caching active controller
information that had not reached unlocked/enabled/available states. Once
that state was reached, this function was not reporting these states
accurately and causing checks against these states to fail when they
should pass.

Change-Id: Id2df178083961d46d069f3dc1590cb72a2cecd1b
Depends-On: I34ad8789768bfd081ab2dcd45d110d9cd8349875
Story: 2005424
Task: 30647
Signed-off-by: Robert Church <robert.church@windriver.com>
This commit is contained in:
Robert Church 2019-04-25 13:26:59 -04:00
parent e42a0162fb
commit 2300e213bf
5 changed files with 197 additions and 4 deletions

View File

@ -1,2 +1,2 @@
SRC_DIR="sysinv"
TIS_PATCH_VER=314
TIS_PATCH_VER=315

View File

@ -339,8 +339,9 @@ class KubeAppHelper(object):
)
query_patches = response['pd']
except Exception as e:
# Assume that a patching operation is underway, raise an exception.
LOG.error(_("No response from patch api: %s" % e))
return
raise
for patch in query_patches:
patch_state = query_patches[patch].get('patchstate', None)
@ -430,6 +431,10 @@ class KubeAppHelper(object):
raise exception.SysinvException(_(
"{}. Please upload after the patching operation "
"is completed.".format(e)))
except Exception as e:
raise exception.SysinvException(_(
"{}. Communication Error with patching subsytem. "
"Preventing application upload.".format(e)))
applied = self._check_patch_is_applied(patches)
if not applied:

View File

@ -32,7 +32,6 @@ import tsconfig.tsconfig as tsc
from oslo_config import cfg
from sysinv.common import constants
from sysinv.common import exception
from sysinv.common.utils import memoized
from sysinv.helm import common as helm_common
from sysinv.openstack.common.gettextutils import _
from sysinv.openstack.common import log
@ -338,7 +337,6 @@ class SystemHelper(object):
class HostHelper(object):
@staticmethod
@memoized
def get_active_controller(dbapi=None):
"""Returns host object for active controller."""
if not dbapi:

View File

@ -1480,6 +1480,12 @@ HELM_APP_APPLY_MODES = {
HELM_APP_OPENSTACK: OPENSTACK_APP_APPLY_MODES
}
HELM_APPS_PLATFORM_MANAGED = [
HELM_APP_PLATFORM,
]
HELM_APP_ISO_INSTALL_PATH = '/usr/local/share/applications/helm'
# RBD Provisioner Ceph backend capabilities fields
K8S_RBD_PROV_STORAGECLASS_NAME = 'rbd_storageclass_name' # Customer
K8S_RBD_PROV_NAMESPACES = 'rbd_provisioner_namespaces' # Customer
@ -1501,6 +1507,7 @@ APP_SYNCED_DATA_PATH = os.path.join(tsc.PLATFORM_PATH, 'armada', tsc.SW_VERSION)
APP_METADATA_FILE = 'metadata.yaml'
# State constants
APP_NOT_PRESENT = 'missing'
APP_UPLOAD_IN_PROGRESS = 'uploading'
APP_UPLOAD_SUCCESS = 'uploaded'
APP_UPLOAD_FAILURE = 'upload-failed'

View File

@ -31,6 +31,7 @@ collection of inventory data for each host.
import errno
import filecmp
import fnmatch
import glob
import math
import os
@ -45,12 +46,14 @@ import xml.etree.ElementTree as ElementTree
from contextlib import contextmanager
import tsconfig.tsconfig as tsc
from collections import namedtuple
from cgcs_patch.patch_verify import verify_files
from controllerconfig.upgrades import management as upgrades_management
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from eventlet import greenthread
from fm_api import constants as fm_constants
from fm_api import fm_api
from netaddr import IPAddress
@ -62,6 +65,7 @@ from six.moves import http_client as httplib
from sysinv.agent import rpcapi as agent_rpcapi
from sysinv.api.controllers.v1 import address_pool
from sysinv.api.controllers.v1 import cpu_utils
from sysinv.api.controllers.v1 import kube_app as kube_api
from sysinv.api.controllers.v1 import mtce_api
from sysinv.api.controllers.v1 import utils
from sysinv.api.controllers.v1 import vim_api
@ -83,6 +87,7 @@ from sysinv.conductor import kube_app
from sysinv.conductor import openstack
from sysinv.db import api as dbapi
from sysinv.objects import base as objects_base
from sysinv.objects import kube_app as kubeapp_obj
from sysinv.openstack.common import excutils
from sysinv.openstack.common import jsonutils
from sysinv.openstack.common import log
@ -200,6 +205,7 @@ class ConductorManager(service.PeriodicService):
self._ceph = iceph.CephOperator(self.dbapi)
self._helm = helm.HelmOperator(self.dbapi)
self._kube = kubernetes.KubeOperator(self.dbapi)
self._kube_app_helper = kube_api.KubeAppHelper(self.dbapi)
self._fernet = fernet.FernetOperator()
# Upgrade start tasks
@ -4999,6 +5005,183 @@ class ConductorManager(service.PeriodicService):
elif bk.backend in self._stor_bck_op_timeouts:
del self._stor_bck_op_timeouts[bk.backend]
@periodic_task.periodic_task(spacing=CONF.conductor.audit_interval,
run_immediately=True)
def _k8s_application_audit(self, context):
"""Make sure that the required k8s applications are running"""
AppTarBall = namedtuple(
'AppTarBall',
"tarball_name app_name app_version manifest_name manifest_file")
def _check_tarfile(app_name):
tarfiles = []
for f in os.listdir(constants.HELM_APP_ISO_INSTALL_PATH):
if fnmatch.fnmatch(f, '{}-*'.format(app_name)):
tarfiles.append(f)
if not tarfiles:
LOG.error("Failed to find an application tarball for {}.".format(app_name))
return AppTarBall(None, None, None, None, None)
elif len(tarfiles) > 1:
LOG.error("Found multiple application tarballs for {}.".format(app_name))
return AppTarBall(None, None, None, None, None)
tarball_name = '{}/{}'.format(
constants.HELM_APP_ISO_INSTALL_PATH, tarfiles[0])
with kube_api.TempDirectory() as app_path:
if not cutils.extract_tarfile(app_path, tarball_name):
LOG.error("Failed to extract tar file {}.".format(
os.path.basename(tarball_name)))
return AppTarBall(tarball_name, None, None, None, None)
# If checksum file is included in the tarball, verify its contents.
if not cutils.verify_checksum(app_path):
LOG.error("Checksum validation failed for %s." % app_name)
return AppTarBall(tarball_name, None, None, None, None)
try:
name, version, patches = \
self._kube_app_helper._verify_metadata_file(
app_path, app_name, None)
manifest_name, manifest_file = \
self._kube_app_helper._find_manifest_file(app_path)
self._kube_app_helper._extract_helm_charts(app_path)
except exception.SysinvException as e:
LOG.error("Extracting tarfile for %s failed: %s." % (
app_name, str(e)))
return AppTarBall(tarball_name, None, None, None, None)
LOG.debug("Tar file of application %s verified." % app_name)
return AppTarBall(tarball_name, name, version,
manifest_name, manifest_file)
def _patching_operation_is_occurring():
# Makes sure a patching operation is not currently underway. We want
# all hosts to be patch-current before taking any application
# actions
#
# Execute this check in a function as the rest_api has info logs on
# the request/response. Call this only when an action will occur and
# not on in every audit cycle
try:
self._kube_app_helper._check_patching_operation()
return False
except exception.SysinvException as e:
LOG.info("{}. Patching operations are in progress. Suspending "
"actions on platform managed application until patching is "
"completed.".format(e))
except Exception as e:
LOG.error("{}. Communication Error with patching subsystem. "
"Preventing managed application actions.".format(e))
return True
LOG.debug("Periodic Task: _k8s_application_audit: Starting")
# Make sure that the active controller is unlocked/enabled. Only
# install an application if the controller has been provisioned.
active_ctrl = utils.HostHelper.get_active_controller(self.dbapi)
if (active_ctrl is None or
((active_ctrl.administrative != constants.ADMIN_UNLOCKED) or
(active_ctrl.operational != constants.OPERATIONAL_ENABLED))):
return
# Check the application state and take the approprate action
for app_name in constants.HELM_APPS_PLATFORM_MANAGED:
# Handle initial loading states
try:
app = kubeapp_obj.get_by_name(context, app_name)
status = app.status
except exception.KubeAppNotFound:
status = constants.APP_NOT_PRESENT
LOG.debug("Platform managed application %s: %s" % (app_name, status))
if status == constants.APP_NOT_PRESENT:
LOG.info("Platform managed application %s: Creating..." % app_name)
app_data = {'name': app_name,
'app_version': constants.APP_VERSION_PLACEHOLDER,
'manifest_name': constants.APP_MANIFEST_NAME_PLACEHOLDER,
'manifest_file': constants.APP_TARFILE_NAME_PLACEHOLDER,
'status': constants.APP_UPLOAD_IN_PROGRESS}
try:
self.dbapi.kube_app_create(app_data)
app = kubeapp_obj.get_by_name(context, app_name)
except exception.KubeAppAlreadyExists as e:
LOG.exception(e)
continue
except exception.KubeAppNotFound as e:
LOG.exception(e)
continue
tarball = _check_tarfile(app_name)
if ((tarball.manifest_name is None) or
(tarball.manifest_file is None)):
app.status = constants.APP_UPLOAD_FAILURE
app.save()
continue
app.name = tarball.app_name
app.app_version = tarball.app_version
app.manifest_name = tarball.manifest_name
app.manifest_file = os.path.basename(tarball.manifest_file)
app.save()
if _patching_operation_is_occurring():
continue
# Action: Upload.
# Do not block this audit task or any other periodic task. This
# could be long running. The next audit cycle will pick up the
# latest status.
LOG.info("Platform managed application %s: "
"Uploading..." % app_name)
greenthread.spawn(self._app.perform_app_upload, app,
tarball.tarball_name)
elif status == constants.APP_UPLOAD_IN_PROGRESS:
# Action: do nothing
pass
elif status == constants.APP_UPLOAD_FAILURE:
# Action: Raise alarm?
pass
elif status == constants.APP_UPLOAD_SUCCESS:
if _patching_operation_is_occurring():
continue
try:
app = kubeapp_obj.get_by_name(context, app_name)
app.status = constants.APP_APPLY_IN_PROGRESS
except exception.KubeAppNotFound as e:
LOG.exception(e)
continue
# Action: Apply the application
# Do not block this audit task or any other periodic task. This
# could be long running. The next audit cycle will pick up the
# latest status.
LOG.info("Platform managed application %s: "
"Applying..." % app_name)
greenthread.spawn(self._app.perform_app_apply, app, None)
pass
elif status == constants.APP_APPLY_IN_PROGRESS:
# Action: do nothing
pass
elif status == constants.APP_APPLY_FAILURE:
# Action: Raise alarm?
pass
elif status == constants.APP_APPLY_SUCCESS:
# Action: do nothing -> done
# TODO(rchurch): Check to see if an existing application needs
# upgrading. Wait for the proper application versioning
# support to the determine proper action.
pass
LOG.debug("Periodic Task: _k8s_application_audit: Finished")
def get_k8s_namespaces(self, context):
""" Get Kubernetes namespaces
:returns: list of namespaces