config/sysinv/sysinv/sysinv/sysinv/helm/lifecycle_utils.py

322 lines
11 KiB
Python

#
# Copyright (c) 2021 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# All Rights Reserved.
#
import time
from oslo_log import log as logging
from oslo_serialization import base64
from sysinv.common import constants
from sysinv.common import exception
from sysinv.common import utils as cutils
from sysinv.common.storage_backend_conf import K8RbdProvisioner
from sysinv.common.storage_backend_conf import StorageBackendConfig
from sysinv.helm import common
LOG = logging.getLogger(__name__)
def _is_rbd_provisioner_required(app_op):
""" Check if RBD provisioner is required
:param app_op: AppOperator object
"""
if StorageBackendConfig.has_backend(
app_op._dbapi,
constants.SB_TYPE_CEPH
):
return True
else:
return False
# TODO(dvoicule) remove once each app has its lifecycle operator and takes care of its rbd
def _decoupling_app_has_rbd_actions(app):
""" Temporary function for decoupling
:param app: AppOperator.Application object
"""
if app.name in [constants.HELM_APP_CERT_MANAGER,
constants.HELM_APP_OIDC_AUTH,
constants.HELM_APP_NGINX_IC]:
return False
return True
def create_rbd_provisioner_secrets(app_op, app, hook_info):
""" Provide access to the system persistent RBD provisioner.
The rbd-provisioner is installed as part of system provisioning and has
created secrets for all common default namespaces. Copy the secret to
this application's namespace(s) to provide resolution for PVCs
:param app_op: AppOperator object
:param app: AppOperator.Application object
:param hook_info: LifecycleHookInfo object
"""
if not _is_rbd_provisioner_required(app_op) or \
not _decoupling_app_has_rbd_actions(app):
return
# Only set up a secret for the default storage pool (i.e. ignore
# additional storage tiers)
pool_secret = K8RbdProvisioner.get_user_secret_name({
'name': constants.SB_DEFAULT_NAMES[constants.SB_TYPE_CEPH]})
app_ns = app_op._helm.get_helm_application_namespaces(app.name)
namespaces = \
list(set([ns for ns_list in app_ns.values() for ns in ns_list]))
for ns in namespaces:
if (ns in [common.HELM_NS_HELM_TOOLKIT,
common.HELM_NS_RBD_PROVISIONER] or
app_op._kube.kube_get_secret(pool_secret, ns) is not None):
# Secret already exist
continue
try:
if not app_op._kube.kube_get_namespace(ns):
app_op._kube.kube_create_namespace(ns)
app_op._kube.kube_copy_secret(
pool_secret, common.HELM_NS_RBD_PROVISIONER, ns)
except Exception as e:
LOG.error(e)
raise
def delete_rbd_provisioner_secrets(app_op, app, hook_info):
""" Remove access to the system persistent RBD provisioner.
As part of launching a supported application, secrets were created to
allow access to the provisioner from the application namespaces. This
will remove those created secrets.
:param app_op: AppOperator object
:param app: AppOperator.Application object
:param hook_info: LifecycleHookInfo object
"""
if not _is_rbd_provisioner_required(app_op) or \
not _decoupling_app_has_rbd_actions(app):
return
# Only set up a secret for the default storage pool (i.e. ignore
# additional storage tiers)
pool_secret = K8RbdProvisioner.get_user_secret_name({
'name': constants.SB_DEFAULT_NAMES[constants.SB_TYPE_CEPH]})
app_ns = app_op._helm.get_helm_application_namespaces(app.name)
namespaces = \
list(set([ns for ns_list in app_ns.values() for ns in ns_list]))
for ns in namespaces:
if (ns == common.HELM_NS_HELM_TOOLKIT or
ns == common.HELM_NS_RBD_PROVISIONER):
continue
try:
LOG.info("Deleting Secret %s under Namespace "
"%s ..." % (pool_secret, ns))
app_op._kube.kube_delete_secret(
pool_secret, ns, grace_period_seconds=0)
LOG.info("Secret %s under Namespace %s delete "
"completed." % (pool_secret, ns))
except Exception as e:
LOG.error(e)
raise
def delete_namespace(app_op, namespace):
"""Delete application specific resources.
:param app_op: AppOperator object
:param namespace:
"""
loop_timeout = 1
timeout = 300
try:
LOG.info("Deleting Namespace %s ..." % namespace)
app_op._kube.kube_delete_namespace(namespace,
grace_periods_seconds=0)
# Namespace termination timeout 5 mins
while loop_timeout <= timeout:
if not app_op._kube.kube_get_namespace(namespace):
# Namespace has been terminated
break
loop_timeout += 1
time.sleep(1)
if loop_timeout > timeout:
raise exception.KubeNamespaceDeleteTimeout(name=namespace)
LOG.info("Namespace %s delete completed." % namespace)
except Exception as e:
LOG.error(e)
raise
def delete_persistent_volume_claim(app_op, namespace):
"""Delete application specific resources.
:param app_op: AppOperator object
:param namespace:
"""
try:
LOG.info("Deleting Persistent Volume Claim "
"under Namespace %s ..." % namespace)
app_op._kube.kube_delete_persistent_volume_claim(namespace,
timeout_seconds=10)
LOG.info("Persistent Volume Claim delete completed.")
except Exception as e:
LOG.error(e)
raise
def delete_configmap(app_op, namespace, configmap):
"""Delete application specific resources.
:param app_op: AppOperator object
:param namespace:
:param configmap:
"""
try:
LOG.info("Deleting ConfigMap %s"
"under Namespace %s ..." % (configmap, namespace))
app_op._kube.kube_delete_config_map(
configmap,
namespace)
LOG.info("ConfigMap delete completed.")
except Exception as e:
LOG.error(e)
raise
def add_pod_security_admission_controller_labels(app_op, app, hook_info):
""" adds labels to newly created namespaces
Pod security admission controllers uses labels to enforce policies
this method adds labels used by pod security admission controller
this is needed because new applications can create new namespaces
as they are deployed.
:param app_op: AppOperator object
:param app: AppOperator.Application object
:param hook_info: LifecycleHookInfo object
"""
app_ns = app_op._helm.get_helm_application_namespaces(app.name)
namespaces = \
list(set([ns for ns_list in app_ns.values() for ns in ns_list]))
for ns in namespaces:
# we only have privileged namespaces right now.
# if it's not priviliged, it's not a namespace we manage
if ns not in common.PRIVILEGED_NS:
continue
security_level = 'privileged'
body = {
"metadata": {
"labels": {
"pod-security.kubernetes.io/enforce": security_level,
"pod-security.kubernetes.io/warn": security_level,
"pod-security.kubernetes.io/audit": security_level,
"pod-security.kubernetes.io/enforce-version": common.POD_SECURITY_VERSION,
"pod-security.kubernetes.io/warn-version": common.POD_SECURITY_VERSION,
"pod-security.kubernetes.io/audit-version": common.POD_SECURITY_VERSION}
}
}
try:
app_op._kube.kube_patch_namespace(ns, body)
except Exception as e:
LOG.error(e)
raise
def create_local_registry_secrets(app_op, app, hook_info):
# Temporary function to create default registry secret
# which would be used by kubernetes to pull images from
# local registry.
# This should be removed after OSH supports the deployment
# with registry has authentication turned on.
# https://blueprints.launchpad.net/openstack-helm/+spec/
# support-docker-registry-with-authentication-turned-on
body = {
'type': 'kubernetes.io/dockerconfigjson',
'metadata': {},
'data': {}
}
app_ns = app_op._helm.get_helm_application_namespaces(app.name)
namespaces = \
list(set([ns for ns_list in app_ns.values() for ns in ns_list]))
sysinv_registry_secret = app_op._kube.kube_get_secret(app_op.DOCKER_REGISTRY_SECRET,
common.HELM_NS_KUBE_SYSTEM)
for ns in namespaces:
if (ns == common.HELM_NS_HELM_TOOLKIT or
app_op._kube.kube_get_secret(app_op.DOCKER_REGISTRY_SECRET, ns) is not None):
# Secret already exist
continue
try:
if sysinv_registry_secret is not None:
# Use the sysinv token in default_registry_key secret in
# kube-system namespace to create secret in another namespace.
sysinv_registry_token = sysinv_registry_secret.data['.dockerconfigjson']
body['data'].update({'.dockerconfigjson': sysinv_registry_token})
else:
# This must be the first platform app in the kube-system
# namespace (i.e. nginx-ingress-controller app)
local_registry_auth = cutils.get_local_docker_registry_auth()
auth = '{0}:{1}'.format(local_registry_auth['username'],
local_registry_auth['password'])
token = '{{\"auths\": {{\"{0}\": {{\"auth\": \"{1}\"}}}}}}'.format(
constants.DOCKER_REGISTRY_SERVER, base64.encode_as_text(auth))
body['data'].update({'.dockerconfigjson': base64.encode_as_text(token)})
body['metadata'].update({'name': app_op.DOCKER_REGISTRY_SECRET,
'namespace': ns})
if not app_op._kube.kube_get_namespace(ns):
app_op._kube.kube_create_namespace(ns)
app_op._kube.kube_create_secret(ns, body)
LOG.info("Secret %s created under Namespace %s." % (app_op.DOCKER_REGISTRY_SECRET, ns))
except Exception as e:
LOG.error(e)
raise
def delete_local_registry_secrets(app_op, app, hook_info):
app_ns = app_op._helm.get_helm_application_namespaces(app.name)
namespaces = \
list(set([ns for ns_list in app_ns.values() for ns in ns_list]))
for ns in namespaces:
if ns in [common.HELM_NS_HELM_TOOLKIT, common.HELM_NS_KUBE_SYSTEM]:
continue
try:
LOG.info("Deleting Secret %s under Namespace "
"%s ..." % (app_op.DOCKER_REGISTRY_SECRET, ns))
app_op._kube.kube_delete_secret(
app_op.DOCKER_REGISTRY_SECRET, ns, grace_period_seconds=0)
LOG.info("Secret %s under Namespace %s delete "
"completed." % (app_op.DOCKER_REGISTRY_SECRET, ns))
except Exception as e:
LOG.error(e)
raise