diff --git a/sysinv/sysinv/centos/sysinv.spec b/sysinv/sysinv/centos/sysinv.spec index 679876273c..ab156b82d0 100644 --- a/sysinv/sysinv/centos/sysinv.spec +++ b/sysinv/sysinv/centos/sysinv.spec @@ -115,3 +115,4 @@ rm -rf $RPM_BUILD_ROOT %{_bindir}/sysinv-rootwrap %{_bindir}/sysinv-upgrade %{_bindir}/sysinv-puppet +%{_bindir}/sysinv-helm diff --git a/sysinv/sysinv/sysinv/setup.cfg b/sysinv/sysinv/sysinv/setup.cfg index c99663ca64..23132a2d92 100644 --- a/sysinv/sysinv/sysinv/setup.cfg +++ b/sysinv/sysinv/sysinv/setup.cfg @@ -36,6 +36,7 @@ console_scripts = sysinv-dnsmasq-lease-update = sysinv.cmd.dnsmasq_lease_update:main sysinv-upgrade = sysinv.cmd.upgrade:main sysinv-puppet = sysinv.cmd.puppet:main + sysinv-helm = sysinv.cmd.helm:main [pbr] autodoc_index_modules = True diff --git a/sysinv/sysinv/sysinv/sysinv/cmd/helm.py b/sysinv/sysinv/sysinv/sysinv/cmd/helm.py new file mode 100644 index 0000000000..21f3d4ebd1 --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/cmd/helm.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +# +# Copyright (c) 2018 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +""" +System Inventory Helm Utility. +""" + +import sys + +from oslo_config import cfg + +from sysinv.common import service +from sysinv.db import api +from sysinv.helm import helm + +CONF = cfg.CONF + + +def create_app_overrides_action(path, app_name=None, namespace=None): + dbapi = api.get_instance() + operator = helm.HelmOperator(dbapi=dbapi, path=path) + operator.generate_helm_application_overrides(app_name, namespace) + + +def create_chart_override_action(path, chart_name=None, namespace=None): + dbapi = api.get_instance() + operator = helm.HelmOperator(dbapi=dbapi, path=path) + operator.generate_helm_chart_overrides(chart_name, namespace) + + +def add_action_parsers(subparsers): + parser = subparsers.add_parser('create-app-overrides') + parser.set_defaults(func=create_app_overrides_action) + parser.add_argument('path', nargs='?') + parser.add_argument('app_name', nargs='?') + parser.add_argument('namespace', nargs='?') + + parser = subparsers.add_parser('create-chart-overrides') + parser.set_defaults(func=create_chart_override_action) + parser.add_argument('path', nargs='?') + parser.add_argument('chart_name', nargs='?') + parser.add_argument('namespace', nargs='?') + + +CONF.register_cli_opt( + cfg.SubCommandOpt('action', + title='actions', + help='Perform helm override operation', + handler=add_action_parsers)) + + +def main(): + service.prepare_service(sys.argv) + if CONF.action.name == 'create-app-overrides': + CONF.action.func(CONF.action.path, + CONF.action.app_name, + CONF.action.namespace) + elif CONF.action.name == 'create-chart-overrides': + CONF.action.func(CONF.action.path, + CONF.action.chart_name, + CONF.action.namespace) diff --git a/sysinv/sysinv/sysinv/sysinv/common/constants.py b/sysinv/sysinv/sysinv/sysinv/common/constants.py index 0cc644987f..9490331e6d 100644 --- a/sysinv/sysinv/sysinv/sysinv/common/constants.py +++ b/sysinv/sysinv/sysinv/sysinv/common/constants.py @@ -1251,3 +1251,74 @@ SYSTEM_SECURITY_FEATURE_SPECTRE_MELTDOWN_OPTS = { SYSTEM_SECURITY_FEATURE_SPECTRE_MELTDOWN_DEFAULT_OPTS = SYSTEM_SECURITY_FEATURE_SPECTRE_MELTDOWN_V1_OPTS + +# Helm: Supported charts +HELM_CHART_INGRESS = 'ingress' +HELM_CHART_NFS_PROVISIONER = 'nfs-provisioner' +HELM_CHART_MARIADB = 'mariadb' +HELM_CHART_RABBITMQ = 'rabbitmq' +HELM_CHART_MEMCACHED = 'memcached' +HELM_CHART_KEYSTONE = 'keystone' +HELM_CHART_HEAT = 'heat' +HELM_CHART_HORIZON = 'horizon' +HELM_CHART_GLANCE = 'glance' +HELM_CHART_OPENVSWITCH = 'openvswitch' +HELM_CHART_LIBVIRT = 'libvirt' +HELM_CHART_NOVA = 'nova' +HELM_CHART_NEUTRON = 'neutron' + + +SUPPORTED_HELM_CHARTS = [ + HELM_CHART_INGRESS, + HELM_CHART_NFS_PROVISIONER, + HELM_CHART_MARIADB, + HELM_CHART_RABBITMQ, + HELM_CHART_MEMCACHED, + HELM_CHART_KEYSTONE, + HELM_CHART_HEAT, + HELM_CHART_HORIZON, + HELM_CHART_GLANCE, + HELM_CHART_OPENVSWITCH, + HELM_CHART_LIBVIRT, + HELM_CHART_NOVA, + HELM_CHART_NEUTRON +] + +# Helm: Supported application (aka chart bundles) +# TODO (rchurch): remove OSHELM applications.These are temporary chart bundles +# until we fully integrate our k8s openstack application. Not +# sure at this point if we'll need an AIO flavor (without Ceph +# but with NFS support) +HELM_APP_OPENSTACK = 'wr-openstack' +HELM_APP_OSHELM_DEVELOPER = 'openstack-helm-developer' +HELM_APP_OSHELM_MULTINODE = 'openstack-helm-multinode' + +SUPPORTED_HELM_APP_NAMES = [ + HELM_APP_OPENSTACK, + HELM_APP_OSHELM_DEVELOPER, + HELM_APP_OSHELM_MULTINODE +] + +SUPPORTED_HELM_APP_CHARTS = { + HELM_APP_OPENSTACK: [ + HELM_CHART_INGRESS + ], + HELM_APP_OSHELM_DEVELOPER: [ + HELM_CHART_INGRESS, + HELM_CHART_NFS_PROVISIONER, + HELM_CHART_MARIADB, + HELM_CHART_RABBITMQ, + HELM_CHART_MEMCACHED, + HELM_CHART_KEYSTONE, + HELM_CHART_HEAT, + HELM_CHART_HORIZON, + HELM_CHART_GLANCE, + HELM_CHART_OPENVSWITCH, + HELM_CHART_LIBVIRT, + HELM_CHART_NOVA, + HELM_CHART_NEUTRON + ], + HELM_APP_OSHELM_MULTINODE: [ + HELM_CHART_INGRESS + ] +} diff --git a/sysinv/sysinv/sysinv/sysinv/common/exception.py b/sysinv/sysinv/sysinv/sysinv/common/exception.py index a2edca33da..91e72c5452 100644 --- a/sysinv/sysinv/sysinv/sysinv/common/exception.py +++ b/sysinv/sysinv/sysinv/sysinv/common/exception.py @@ -1236,3 +1236,7 @@ class IncompleteCephMonNetworkConfig(CephFailure): message = _("IP address for controller-0, controller-1 and " "storage-0 must be allocated. Expected: %(targets)s, " "found: %(results)s") + + +class InvalidHelmNamespace(Invalid): + message = _("Invalid helm overrides namespace (%(namespace)s) for chart %(chart)s.") diff --git a/sysinv/sysinv/sysinv/sysinv/conductor/manager.py b/sysinv/sysinv/sysinv/sysinv/conductor/manager.py index aa11e144d6..e734890643 100644 --- a/sysinv/sysinv/sysinv/sysinv/conductor/manager.py +++ b/sysinv/sysinv/sysinv/sysinv/conductor/manager.py @@ -90,6 +90,7 @@ from sysinv.openstack.common import uuidutils from sysinv.openstack.common.gettextutils import _ from sysinv.puppet import common as puppet_common from sysinv.puppet import puppet +from sysinv.helm import helm MANAGER_TOPIC = 'sysinv.conductor_manager' @@ -176,6 +177,7 @@ class ConductorManager(service.PeriodicService): self._openstack = openstack.OpenStackOperator(self.dbapi) self._puppet = puppet.PuppetOperator(self.dbapi) self._ceph = iceph.CephOperator(self.dbapi) + self._helm = helm.HelmOperator(self.dbapi) # create /var/run/sysinv if required. On DOR, the manifests # may not run to create this volatile directory. @@ -10009,3 +10011,113 @@ class ConductorManager(service.PeriodicService): f.write(file_content) return signature + + def get_helm_chart_namespaces(self, context, chart_name): + """Get supported chart namespaces. + + This method retrieves the namespace supported by a given chart. + + :param context: request context. + :param chart_name: name of the chart + :returns: list of supported namespaces that associated overrides may be + provided. + """ + return self._helm.get_helm_chart_namespaces(chart_name) + + def get_helm_chart_overrides(self, context, chart_name, cnamespace=None): + """Get the overrides for a supported chart. + + This method retrieves overrides for a supported chart. Overrides for + all supported namespaces will be returned unless a specific namespace + is requested. + + :param context: request context. + :param chart_name: name of a supported chart + :param cnamespace: (optional) namespace + :returns: dict of overrides. + + Example Without a cnamespace parameter: + { + 'kube-system': { + 'deployment': { + 'mode': 'cluster', + 'type': 'DaemonSet' + }, + }, + 'openstack': { + 'pod': { + 'replicas': { + 'server': 1 + } + } + } + } + + Example with a cnamespace parameter: cnamespace='kube-system' + { + 'deployment': { + 'mode': 'cluster', + 'type': 'DaemonSet' + } + } + """ + return self._helm.get_helm_chart_overrides(chart_name, + cnamespace) + + def get_helm_application_namespaces(self, context, app_name): + """Get supported application namespaces. + + This method retrieves a dict of charts and their supported namespaces + for an application. + + :param app_name: name of the bundle of charts required to support an + application + :returns: dict of charts and supported namespaces that associated + overrides may be provided. + """ + return self._helm.get_helm_application_namespaces(app_name) + + def get_helm_application_overrides(self, context, app_name, cnamespace): + """Get the overrides for a supported set of charts. + + This method retrieves overrides for a set of supported charts that + comprise an application. Overrides for all charts and all supported + namespaces will be returned unless a specific namespace is requested. + + If a specific namespace is requested, then only charts that that + support that specified namespace will be returned. + + :param context: request context. + :param app_name: name of a supported application (set of charts) + :param cnamespace: (optional) namespace + :returns: dict of overrides. + + Example: + { + 'ingress': { + 'kube-system': { + 'deployment': { + 'mode': 'cluster', + 'type': 'DaemonSet' + }, + }, + 'openstack': { + 'pod': { + 'replicas': { + 'server': 1 + } + } + } + }, + 'glance': { + 'openstack': { + 'pod': { + 'replicas': { + 'server': 1 + } + } + } + } + } + """ + return self._helm.get_helm_application_overrides(app_name, cnamespace) diff --git a/sysinv/sysinv/sysinv/sysinv/conductor/rpcapi.py b/sysinv/sysinv/sysinv/sysinv/conductor/rpcapi.py index 1e0e720847..661a0582bc 100644 --- a/sysinv/sysinv/sysinv/sysinv/conductor/rpcapi.py +++ b/sysinv/sysinv/sysinv/sysinv/conductor/rpcapi.py @@ -1556,3 +1556,57 @@ class ConductorAPI(sysinv.openstack.common.rpc.proxy.RpcProxy): pem_contents=pem_contents, config_dict=config_dict, )) + + def get_helm_chart_namespaces(self, context, chart_name): + """Get supported chart namespaces. + + This method retrieves the namespace supported by a given chart. + + :param context: request context. + :param chart_name: name of the chart + :returns: list of supported namespaces that associated overrides may be + provided. + """ + return self.call(context, + self.make_msg('get_helm_chart_namespaces', + chart_name=chart_name)) + + def get_helm_chart_overrides(self, context, chart_name, cnamespace=None): + """Get the overrides for a supported chart. + + :param context: request context. + :param chart_name: name of a supported chart + :param cnamespace: (optional) namespace + :returns: dict of overrides. + + """ + return self.call(context, + self.make_msg('get_helm_chart_overrides', + chart_name=chart_name, + cnamespace=cnamespace)) + + def get_helm_application_namespaces(self, context, app_name): + """Get supported application namespaces. + + :param app_name: name of the bundle of charts required to support an + application + :returns: dict of charts and supported namespaces that associated + overrides may be provided. + """ + return self.call(context, + self.make_msg('get_helm_application_namespaces', + app_name=app_name)) + + def get_helm_application_overrides(self, context, app_name, cnamespace=None): + """Get the overrides for a supported set of charts. + + :param context: request context. + :param app_name: name of a supported application (set of charts) + :param cnamespace: (optional) namespace + :returns: dict of overrides. + + """ + return self.call(context, + self.make_msg('get_helm_application_overrides', + app_name=app_name, + cnamespace=cnamespace)) diff --git a/sysinv/sysinv/sysinv/sysinv/helm/__init__.py b/sysinv/sysinv/sysinv/sysinv/helm/__init__.py new file mode 100644 index 0000000000..7653b2c8ef --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/helm/__init__.py @@ -0,0 +1,19 @@ +# +# Copyright (c) 2018 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +import yaml + + +class quoted_str(str): + pass + + +# force strings to be single-quoted to avoid interpretation as numeric values +def quoted_presenter(dumper, data): + return dumper.represent_scalar(u'tag:yaml.org,2002:str', data, style="'") + + +yaml.add_representer(quoted_str, quoted_presenter) diff --git a/sysinv/sysinv/sysinv/sysinv/helm/base.py b/sysinv/sysinv/sysinv/sysinv/helm/base.py new file mode 100644 index 0000000000..59e87520a3 --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/helm/base.py @@ -0,0 +1,24 @@ +# +# Copyright (c) 2018 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +import abc +import six + + +@six.add_metaclass(abc.ABCMeta) +class BaseHelm(object): + """Base class to encapsulate helm operations for chart overrides""" + + def __init__(self, operator): + self._operator = operator + + @property + def dbapi(self): + return self._operator.dbapi + + @property + def context(self): + return self._operator.context diff --git a/sysinv/sysinv/sysinv/sysinv/helm/common.py b/sysinv/sysinv/sysinv/sysinv/helm/common.py new file mode 100644 index 0000000000..c40d01e020 --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/helm/common.py @@ -0,0 +1,23 @@ +# +# Copyright (c) 2018 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +""" System Inventory Helm common top level code.""" + + +import os + +from sysinv.openstack.common import log as logging +from tsconfig import tsconfig + + +LOG = logging.getLogger(__name__) + +HELM_OVERRIDES_PATH = os.path.join(tsconfig.PLATFORM_PATH, 'helm', tsconfig.SW_VERSION) + +# Common Namespaces +HELM_NS_KUBE_SYSTEM = 'kube-system' +HELM_NS_NFS = 'nfs' +HELM_NS_OPENSTACK = 'openstack' diff --git a/sysinv/sysinv/sysinv/sysinv/helm/glance.py b/sysinv/sysinv/sysinv/sysinv/helm/glance.py new file mode 100644 index 0000000000..e22f403a95 --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/helm/glance.py @@ -0,0 +1,40 @@ +# +# Copyright (c) 2018 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +from sysinv.common import constants +from sysinv.common import exception +from sysinv.openstack.common import log as logging +from . import common +from . import openstack + +LOG = logging.getLogger(__name__) + + +class GlanceHelm(openstack.OpenstackBaseHelm): + """Class to encapsulate helm operations for the glance chart""" + + CHART = constants.HELM_CHART_GLANCE + SUPPORTED_NAMESPACES = [ + common.HELM_NS_OPENSTACK + ] + + def get_namespaces(self): + return self.SUPPORTED_NAMESPACES + + def get_overrides(self, namespace=None): + overrides = { + common.HELM_NS_OPENSTACK: { + 'storage': 'pvc' + } + } + + if namespace in self.SUPPORTED_NAMESPACES: + return overrides[namespace] + elif namespace: + raise exception.InvalidHelmNamespace(chart=self.CHART, + namespace=namespace) + else: + return overrides diff --git a/sysinv/sysinv/sysinv/sysinv/helm/heat.py b/sysinv/sysinv/sysinv/sysinv/helm/heat.py new file mode 100644 index 0000000000..672fe5f11d --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/helm/heat.py @@ -0,0 +1,39 @@ +# +# Copyright (c) 2018 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +from sysinv.common import constants +from sysinv.common import exception +from sysinv.openstack.common import log as logging +from . import common +from . import openstack + +LOG = logging.getLogger(__name__) + + +class HeatHelm(openstack.OpenstackBaseHelm): + """Class to encapsulate helm operations for the heat chart""" + + CHART = constants.HELM_CHART_HEAT + SUPPORTED_NAMESPACES = [ + common.HELM_NS_OPENSTACK + ] + + def get_namespaces(self): + return self.SUPPORTED_NAMESPACES + + def get_overrides(self, namespace=None): + overrides = { + common.HELM_NS_OPENSTACK: { + } + } + + if namespace in self.SUPPORTED_NAMESPACES: + return overrides[namespace] + elif namespace: + raise exception.InvalidHelmNamespace(chart=self.CHART, + namespace=namespace) + else: + return overrides diff --git a/sysinv/sysinv/sysinv/sysinv/helm/helm.py b/sysinv/sysinv/sysinv/sysinv/helm/helm.py new file mode 100644 index 0000000000..6416618391 --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/helm/helm.py @@ -0,0 +1,356 @@ +# +# Copyright (c) 2018 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +""" System Inventory Helm Overrides Operator.""" + +from __future__ import absolute_import + +import eventlet +import os +import tempfile +import yaml + +from six import iteritems +from sysinv.common import constants +from sysinv.common import exception +from sysinv.openstack.common import log as logging +from . import common + +# Import Chart Override Helpers: +# Chart source: openstack-helm +from . import glance +from . import heat +from . import horizon +from . import ingress +from . import keystone +from . import libvirt +from . import mariadb +from . import memcached +from . import neutron +from . import nfs_provisioner +from . import nova +from . import openvswitch +from . import rabbitmq + + +LOG = logging.getLogger(__name__) + + +def helm_context(func): + """Decorate to initialize the local threading context""" + + def _wrapper(self, *args, **kwargs): + thread_context = eventlet.greenthread.getcurrent() + setattr(thread_context, '_helm_context', dict()) + func(self, *args, **kwargs) + return _wrapper + + +class HelmOperator(object): + """Class to encapsulate helm override operations for System Inventory""" + + def __init__(self, dbapi=None, path=None): + if path is None: + path = common.HELM_OVERRIDES_PATH + + self.dbapi = dbapi + self.path = path + + # register chart operators for lookup + self.chart_operators = { + constants.HELM_CHART_GLANCE: glance.GlanceHelm(self), + constants.HELM_CHART_HEAT: heat.HeatHelm(self), + constants.HELM_CHART_HORIZON: horizon.HorizonHelm(self), + constants.HELM_CHART_INGRESS: ingress.IngressHelm(self), + constants.HELM_CHART_KEYSTONE: keystone.KeystoneHelm(self), + constants.HELM_CHART_LIBVIRT: libvirt.LibvirtHelm(self), + constants.HELM_CHART_MARIADB: mariadb.MariadbHelm(self), + constants.HELM_CHART_MEMCACHED: memcached.MemcachedHelm(self), + constants.HELM_CHART_NEUTRON: neutron.NeutronHelm(self), + constants.HELM_CHART_NFS_PROVISIONER: + nfs_provisioner.NfsProvisionerHelm(self), + constants.HELM_CHART_NOVA: nova.NovaHelm(self), + constants.HELM_CHART_OPENVSWITCH: + openvswitch.OpenvswitchHelm(self), + constants.HELM_CHART_RABBITMQ: rabbitmq.RabbitmqHelm(self), + } + + # build the list of registered supported charts + self.implemented_charts = [] + for chart in constants.SUPPORTED_HELM_CHARTS: + if chart in self.chart_operators.keys(): + self.implemented_charts.append(chart) + + @property + def context(self): + thread_context = eventlet.greenthread.getcurrent() + return getattr(thread_context, '_helm_context') + + def get_helm_chart_namespaces(self, chart_name): + """Get supported chart namespaces. + + This method retrieves the namespace supported by a given chart. + + :param chart_name: name of the chart + :returns: list of supported namespaces that associated overrides may be + provided. + """ + + namespaces = [] + if chart_name in self.implemented_charts: + namespaces = self.chart_operators[chart_name].get_namespaces() + return namespaces + + def get_helm_chart_overrides(self, chart_name, cnamespace=None): + """Get the overrides for a supported chart. + + This method retrieves overrides for a supported chart. Overrides for + all supported namespaces will be returned unless a specific namespace + is requested. + + :param chart_name: name of a supported chart + :param cnamespace: (optional) namespace + :returns: dict of overrides. + + Example Without a cnamespace parameter: + { + 'kube-system': { + 'deployment': { + 'mode': 'cluster', + 'type': 'DaemonSet' + }, + }, + 'openstack': { + 'pod': { + 'replicas': { + 'server': 1 + } + } + } + } + + Example with a cnamespace parameter: cnamespace='kube-system' + { + 'deployment': { + 'mode': 'cluster', + 'type': 'DaemonSet' + } + } + """ + + overrides = {} + if chart_name in self.implemented_charts: + try: + overrides.update( + self.chart_operators[chart_name].get_overrides( + cnamespace)) + except exception.InvalidHelmNamespace: + raise + return overrides + + def get_helm_application_namespaces(self, app_name): + """Get supported application namespaces. + + This method retrieves a dict of charts and their supported namespaces + for an application. + + :param app_name: name of the bundle of charts required to support an + application + :returns: dict of charts and supported namespaces that associated + overrides may be provided. + """ + + app_namespaces = {} + if app_name in constants.SUPPORTED_HELM_APP_NAMES: + for chart_name in constants.SUPPORTED_HELM_APP_CHARTS[app_name]: + if chart_name in self.implemented_charts: + try: + app_namespaces.update({chart_name: + self.get_helm_chart_namespaces( + chart_name)}) + except exception.InvalidHelmNamespace as e: + LOG.info(e) + return app_namespaces + + def get_helm_application_overrides(self, app_name, cnamespace=None): + """Get the overrides for a supported set of charts. + + This method retrieves overrides for a set of supported charts that + comprise an application. Overrides for all charts and all supported + namespaces will be returned unless a specific namespace is requested. + + If a specific namespace is requested, then only charts that that + support that specified namespace will be returned. + + :param app_name: name of a supported application (set of charts) + :param cnamespace: (optional) namespace + :returns: dict of overrides. + + Example: + { + 'ingress': { + 'kube-system': { + 'deployment': { + 'mode': 'cluster', + 'type': 'DaemonSet' + }, + }, + 'openstack': { + 'pod': { + 'replicas': { + 'server': 1 + } + } + } + }, + 'glance': { + 'openstack': { + 'pod': { + 'replicas': { + 'server': 1 + } + } + } + } + } + """ + + overrides = {} + if app_name in constants.SUPPORTED_HELM_APP_NAMES: + for chart_name in constants.SUPPORTED_HELM_APP_CHARTS[app_name]: + if chart_name in self.implemented_charts: + try: + overrides.update({chart_name: + self.get_helm_chart_overrides( + chart_name, + cnamespace)}) + except exception.InvalidHelmNamespace as e: + LOG.info(e) + return overrides + + @helm_context + def generate_helm_chart_overrides(self, chart_name, cnamespace=None): + """Generate system helm chart overrides + + This method will generate system helm chart override an write them to a + yaml file.for use with the helm command. If the namespace is provided + only the overrides file for that specified namespace will be written. + + :param chart_name: name of a supported chart + :param cnamespace: (optional) namespace + """ + + if chart_name in self.implemented_charts: + namespaces = self.chart_operators[chart_name].get_namespaces() + if cnamespace and cnamespace not in namespaces: + LOG.exception("The %s chart does not support namespace: %s" % + (chart_name, cnamespace)) + return + + try: + overrides = self.get_helm_chart_overrides( + chart_name, + cnamespace) + self._write_chart_overrides(chart_name, + cnamespace, + overrides) + except Exception as e: + LOG.exception("failed to create chart overrides for %s: %s" % + (chart_name, e)) + elif chart_name: + LOG.exception("%s chart is not supported" % chart_name) + else: + LOG.exception("chart name is required") + + @helm_context + def generate_helm_application_overrides(self, app_name, cnamespace=None): + """Create the system overrides files for a supported application + + This method will generate system helm chart overrides yaml files for a + set of supported charts that comprise an application.. If the namespace + is provided only the overrides files for that specified namespace will + be written.. + + :param app_name: name of the bundle of charts required to support an + application + :param cnamespace: (optional) namespace + """ + + if app_name in constants.SUPPORTED_HELM_APP_NAMES: + app_overrides = self.get_helm_application_overrides(app_name, + cnamespace) + + for (chart_name, overrides) in iteritems(app_overrides): + self._write_chart_overrides(chart_name, cnamespace, overrides) + elif app_name: + LOG.exception("%s application is not supported" % app_name) + else: + LOG.exception("application name is required") + + def remove_helm_chart_overrides(self, chart_name, cnamespace=None): + """Remove the overrides files for a chart""" + + if chart_name in self.implemented_charts: + namespaces = self.chart_operators[chart_name].get_namespaces + + filenames = [] + if cnamespace and cnamespace in namespaces: + filenames.append("%s-%s.yaml" % (cnamespace, chart_name)) + else: + for n in namespaces: + filenames.append("%s-%s.yaml" % (n, chart_name)) + + for f in filenames: + try: + self._remove_overrides(f) + except Exception as e: + LOG.exception("failed to remove %s overrides: %s: %s" % ( + chart_name, f, e)) + else: + LOG.exception("chart %s not supported for system overrides" % + chart_name) + + def _write_chart_overrides(self, chart_name, cnamespace, overrides): + """Write a one or more overrides files for a chart. """ + + def _write_file(filename, values): + try: + self._write_overrides(filename, values) + except Exception as e: + LOG.exception("failed to write %s overrides: %s: %s" % ( + chart_name, filename, e)) + + if cnamespace: + _write_file("%s-%s.yaml" % (cnamespace, chart_name), overrides) + else: + for ns in overrides.keys(): + _write_file("%s-%s.yaml" % (ns, chart_name), overrides[ns]) + + def _write_overrides(self, filename, overrides): + """Write a single overrides file. """ + + filepath = os.path.join(self.path, filename) + try: + fd, tmppath = tempfile.mkstemp(dir=self.path, prefix=filename, + text=True) + with open(tmppath, 'w') as f: + yaml.dump(overrides, f, default_flow_style=False) + os.close(fd) + os.rename(tmppath, filepath) + except Exception: + LOG.exception("failed to write overrides file: %s" % filepath) + raise + + def _remove_overrides(self, filename): + """Remove a single overrides file. """ + + filepath = os.path.join(self.path, filename) + try: + if os.path.exists(filepath): + os.unlink(filepath) + except Exception: + LOG.exception("failed to delete overrides file: %s" % filepath) + raise diff --git a/sysinv/sysinv/sysinv/sysinv/helm/horizon.py b/sysinv/sysinv/sysinv/sysinv/helm/horizon.py new file mode 100644 index 0000000000..93bbb63af3 --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/helm/horizon.py @@ -0,0 +1,45 @@ +# +# Copyright (c) 2018 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +from sysinv.common import constants +from sysinv.common import exception +from sysinv.openstack.common import log as logging +from . import common +from . import openstack + +LOG = logging.getLogger(__name__) + + +class HorizonHelm(openstack.OpenstackBaseHelm): + """Class to encapsulate helm operations for the horizon chart""" + + CHART = constants.HELM_CHART_HORIZON + SUPPORTED_NAMESPACES = [ + common.HELM_NS_OPENSTACK + ] + + def get_namespaces(self): + return self.SUPPORTED_NAMESPACES + + def get_overrides(self, namespace=None): + overrides = { + common.HELM_NS_OPENSTACK: { + 'network': { + 'node_port': { + 'enabled': 'true', + 'port': 31000 + } + } + } + } + + if namespace in self.SUPPORTED_NAMESPACES: + return overrides[namespace] + elif namespace: + raise exception.InvalidHelmNamespace(chart=self.CHART, + namespace=namespace) + else: + return overrides diff --git a/sysinv/sysinv/sysinv/sysinv/helm/ingress.py b/sysinv/sysinv/sysinv/sysinv/helm/ingress.py new file mode 100644 index 0000000000..289f24bc71 --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/helm/ingress.py @@ -0,0 +1,59 @@ +# +# Copyright (c) 2018 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +from sysinv.common import constants +from sysinv.common import exception +from sysinv.openstack.common import log as logging +from . import common +from . import openstack + +LOG = logging.getLogger(__name__) + + +class IngressHelm(openstack.OpenstackBaseHelm): + """Class to encapsulate helm operations for the ingress chart""" + + CHART = constants.HELM_CHART_INGRESS + SUPPORTED_NAMESPACES = [ + common.HELM_NS_KUBE_SYSTEM, + common.HELM_NS_OPENSTACK + ] + + def get_namespaces(self): + return self.SUPPORTED_NAMESPACES + + def get_overrides(self, namespace=None): + # Currently have conflicts with ports 80 and 8080, use 8081 for now + overrides = { + common.HELM_NS_KUBE_SYSTEM: { + 'deployment': { + 'mode': 'cluster', + 'type': 'DaemonSet' + }, + 'network': { + 'host_namespace': 'true' + }, + 'endpoints': { + 'ingress': { + 'port': { + 'http': { + 'default': 8081 + } + } + } + } + }, + common.HELM_NS_OPENSTACK: { + } + } + + if namespace in self.SUPPORTED_NAMESPACES: + return overrides[namespace] + elif namespace: + raise exception.InvalidHelmNamespace(chart=self.CHART, + namespace=namespace) + else: + return overrides diff --git a/sysinv/sysinv/sysinv/sysinv/helm/keystone.py b/sysinv/sysinv/sysinv/sysinv/helm/keystone.py new file mode 100644 index 0000000000..c52cf5dc27 --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/helm/keystone.py @@ -0,0 +1,39 @@ +# +# Copyright (c) 2018 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +from sysinv.common import constants +from sysinv.common import exception +from sysinv.openstack.common import log as logging +from . import common +from . import openstack + +LOG = logging.getLogger(__name__) + + +class KeystoneHelm(openstack.OpenstackBaseHelm): + """Class to encapsulate helm operations for the keystone chart""" + + CHART = constants.HELM_CHART_KEYSTONE + SUPPORTED_NAMESPACES = [ + common.HELM_NS_OPENSTACK + ] + + def get_namespaces(self): + return self.SUPPORTED_NAMESPACES + + def get_overrides(self, namespace=None): + overrides = { + common.HELM_NS_OPENSTACK: { + } + } + + if namespace in self.SUPPORTED_NAMESPACES: + return overrides[namespace] + elif namespace: + raise exception.InvalidHelmNamespace(chart=self.CHART, + namespace=namespace) + else: + return overrides diff --git a/sysinv/sysinv/sysinv/sysinv/helm/libvirt.py b/sysinv/sysinv/sysinv/sysinv/helm/libvirt.py new file mode 100644 index 0000000000..395fae1c62 --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/helm/libvirt.py @@ -0,0 +1,44 @@ +# +# Copyright (c) 2018 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +from sysinv.common import constants +from sysinv.common import exception +from sysinv.openstack.common import log as logging +from . import common +from . import openstack + +LOG = logging.getLogger(__name__) + + +class LibvirtHelm(openstack.OpenstackBaseHelm): + """Class to encapsulate helm operations for the libvirt chart""" + + CHART = constants.HELM_CHART_LIBVIRT + SUPPORTED_NAMESPACES = [ + common.HELM_NS_OPENSTACK + ] + + def get_namespaces(self): + return self.SUPPORTED_NAMESPACES + + def get_overrides(self, namespace=None): + overrides = { + common.HELM_NS_OPENSTACK: { + 'conf': { + 'ceph': { + 'enabled': 'false' + } + } + } + } + + if namespace in self.SUPPORTED_NAMESPACES: + return overrides[namespace] + elif namespace: + raise exception.InvalidHelmNamespace(chart=self.CHART, + namespace=namespace) + else: + return overrides diff --git a/sysinv/sysinv/sysinv/sysinv/helm/mariadb.py b/sysinv/sysinv/sysinv/sysinv/helm/mariadb.py new file mode 100644 index 0000000000..d679222a03 --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/helm/mariadb.py @@ -0,0 +1,44 @@ +# +# Copyright (c) 2018 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +from sysinv.common import constants +from sysinv.common import exception +from sysinv.openstack.common import log as logging +from . import common +from . import openstack + +LOG = logging.getLogger(__name__) + + +class MariadbHelm(openstack.OpenstackBaseHelm): + """Class to encapsulate helm operations for the mariadb chart""" + + CHART = constants.HELM_CHART_MARIADB + SUPPORTED_NAMESPACES = [ + common.HELM_NS_OPENSTACK + ] + + def get_namespaces(self): + return self.SUPPORTED_NAMESPACES + + def get_overrides(self, namespace=None): + overrides = { + common.HELM_NS_OPENSTACK: { + 'pod': { + 'replicas': { + 'server': 1 + } + } + } + } + + if namespace in self.SUPPORTED_NAMESPACES: + return overrides[namespace] + elif namespace: + raise exception.InvalidHelmNamespace(chart=self.CHART, + namespace=namespace) + else: + return overrides diff --git a/sysinv/sysinv/sysinv/sysinv/helm/memcached.py b/sysinv/sysinv/sysinv/sysinv/helm/memcached.py new file mode 100644 index 0000000000..91b3484738 --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/helm/memcached.py @@ -0,0 +1,39 @@ +# +# Copyright (c) 2018 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +from sysinv.common import constants +from sysinv.common import exception +from sysinv.openstack.common import log as logging +from . import common +from . import openstack + +LOG = logging.getLogger(__name__) + + +class MemcachedHelm(openstack.OpenstackBaseHelm): + """Class to encapsulate helm operations for the memcached chart""" + + CHART = constants.HELM_CHART_MEMCACHED + SUPPORTED_NAMESPACES = [ + common.HELM_NS_OPENSTACK + ] + + def get_namespaces(self): + return self.SUPPORTED_NAMESPACES + + def get_overrides(self, namespace=None): + overrides = { + common.HELM_NS_OPENSTACK: { + } + } + + if namespace in self.SUPPORTED_NAMESPACES: + return overrides[namespace] + elif namespace: + raise exception.InvalidHelmNamespace(chart=self.CHART, + namespace=namespace) + else: + return overrides diff --git a/sysinv/sysinv/sysinv/sysinv/helm/neutron.py b/sysinv/sysinv/sysinv/sysinv/helm/neutron.py new file mode 100644 index 0000000000..30cb29a979 --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/helm/neutron.py @@ -0,0 +1,75 @@ +# +# Copyright (c) 2018 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +from sysinv.common import constants +from sysinv.common import exception +from sysinv.openstack.common import log as logging +from . import common +from . import openstack + +LOG = logging.getLogger(__name__) + + +class NeutronHelm(openstack.OpenstackBaseHelm): + """Class to encapsulate helm operations for the memcached chart""" + + CHART = constants.HELM_CHART_NEUTRON + SUPPORTED_NAMESPACES = [ + common.HELM_NS_OPENSTACK + ] + + def get_namespaces(self): + return self.SUPPORTED_NAMESPACES + + def get_overrides(self, namespace=None): + overrides = { + common.HELM_NS_OPENSTACK: { + 'network': { + 'interface': { + 'tunnel': 'docker0' + } + }, + 'conf': { + 'neutron': { + 'DEFAULT': { + 'l3_ha': 'False', + 'min_l3_agents_per_router': 1, + 'max_l3_agents_per_router': 1, + 'l3_ha_network_type': 'vxlan', + 'dhcp_agents_per_network': 1 + } + }, + 'plugins': { + 'ml2_conf': { + 'ml2_type_flat': { + 'flat_networks': 'public' + } + }, + 'openvswitch_agent': { + 'agent': { + 'tunnel_types': 'vxlan' + }, + 'ovs': { + 'bridge_mappings': 'public:br-ex' + } + }, + 'linuxbridge_agent': { + 'linux_bridge': { + 'bridge_mappings': 'public:br-ex' + } + } + } + } + } + } + + if namespace in self.SUPPORTED_NAMESPACES: + return overrides[namespace] + elif namespace: + raise exception.InvalidHelmNamespace(chart=self.CHART, + namespace=namespace) + else: + return overrides diff --git a/sysinv/sysinv/sysinv/sysinv/helm/nfs_provisioner.py b/sysinv/sysinv/sysinv/sysinv/helm/nfs_provisioner.py new file mode 100644 index 0000000000..ba7bcdd0f3 --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/helm/nfs_provisioner.py @@ -0,0 +1,42 @@ +# +# Copyright (c) 2018 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +from sysinv.common import constants +from sysinv.common import exception +from sysinv.openstack.common import log as logging +from . import common +from . import openstack + +LOG = logging.getLogger(__name__) + + +class NfsProvisionerHelm(openstack.OpenstackBaseHelm): + """Class to encapsulate helm operations for the nfs-provisioner chart""" + + CHART = constants.HELM_CHART_NFS_PROVISIONER + SUPPORTED_NAMESPACES = [ + common.HELM_NS_NFS + ] + + def get_namespaces(self): + return self.SUPPORTED_NAMESPACES + + def get_overrides(self, namespace=None): + overrides = { + common.HELM_NS_NFS: { + 'storageclass': { + 'name': 'general' + } + } + } + + if namespace in self.SUPPORTED_NAMESPACES: + return overrides[namespace] + elif namespace: + raise exception.InvalidHelmNamespace(chart=self.CHART, + namespace=namespace) + else: + return overrides diff --git a/sysinv/sysinv/sysinv/sysinv/helm/nova.py b/sysinv/sysinv/sysinv/sysinv/helm/nova.py new file mode 100644 index 0000000000..c2dc359480 --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/helm/nova.py @@ -0,0 +1,49 @@ +# +# Copyright (c) 2018 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +from sysinv.common import constants +from sysinv.common import exception +from sysinv.openstack.common import log as logging +from . import common +from . import openstack + +LOG = logging.getLogger(__name__) + + +class NovaHelm(openstack.OpenstackBaseHelm): + """Class to encapsulate helm operations for the nova chart""" + + CHART = constants.HELM_CHART_NOVA + SUPPORTED_NAMESPACES = [ + common.HELM_NS_OPENSTACK + ] + + def get_namespaces(self): + return self.SUPPORTED_NAMESPACES + + def get_overrides(self, namespace=None): + overrides = { + common.HELM_NS_OPENSTACK: { + 'conf': { + 'ceph': { + 'enabled': 'false' + }, + 'nova': { + 'libvirt': { + 'virt_type': 'qemu' + } + } + } + } + } + + if namespace in self.SUPPORTED_NAMESPACES: + return overrides[namespace] + elif namespace: + raise exception.InvalidHelmNamespace(chart=self.CHART, + namespace=namespace) + else: + return overrides diff --git a/sysinv/sysinv/sysinv/sysinv/helm/openstack.py b/sysinv/sysinv/sysinv/sysinv/helm/openstack.py new file mode 100644 index 0000000000..0931b21a23 --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/helm/openstack.py @@ -0,0 +1,13 @@ +# +# Copyright (c) 2018 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +from . import base + + +class OpenstackBaseHelm(base.BaseHelm): + """Class to encapsulate Openstack service operations for helm""" + + pass diff --git a/sysinv/sysinv/sysinv/sysinv/helm/openvswitch.py b/sysinv/sysinv/sysinv/sysinv/helm/openvswitch.py new file mode 100644 index 0000000000..8ee9abdb16 --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/helm/openvswitch.py @@ -0,0 +1,39 @@ +# +# Copyright (c) 2018 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +from sysinv.common import constants +from sysinv.common import exception +from sysinv.openstack.common import log as logging +from . import common +from . import openstack + +LOG = logging.getLogger(__name__) + + +class OpenvswitchHelm(openstack.OpenstackBaseHelm): + """Class to encapsulate helm operations for the openvswitch chart""" + + CHART = constants.HELM_CHART_OPENVSWITCH + SUPPORTED_NAMESPACES = [ + common.HELM_NS_OPENSTACK + ] + + def get_namespaces(self): + return self.SUPPORTED_NAMESPACES + + def get_overrides(self, namespace=None): + overrides = { + common.HELM_NS_OPENSTACK: { + } + } + + if namespace in self.SUPPORTED_NAMESPACES: + return overrides[namespace] + elif namespace: + raise exception.InvalidHelmNamespace(chart=self.CHART, + namespace=namespace) + else: + return overrides diff --git a/sysinv/sysinv/sysinv/sysinv/helm/rabbitmq.py b/sysinv/sysinv/sysinv/sysinv/helm/rabbitmq.py new file mode 100644 index 0000000000..23ee42f934 --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/helm/rabbitmq.py @@ -0,0 +1,44 @@ +# +# Copyright (c) 2018 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +from sysinv.common import constants +from sysinv.common import exception +from sysinv.openstack.common import log as logging +from . import common +from . import openstack + +LOG = logging.getLogger(__name__) + + +class RabbitmqHelm(openstack.OpenstackBaseHelm): + """Class to encapsulate helm operations for the rabbitmq chart""" + + CHART = constants.HELM_CHART_RABBITMQ + SUPPORTED_NAMESPACES = [ + common.HELM_NS_OPENSTACK + ] + + def get_namespaces(self): + return self.SUPPORTED_NAMESPACES + + def get_overrides(self, namespace=None): + overrides = { + common.HELM_NS_OPENSTACK: { + 'pod': { + 'replicas': { + 'server': 1 + } + } + } + } + + if namespace in self.SUPPORTED_NAMESPACES: + return overrides[namespace] + elif namespace: + raise exception.InvalidHelmNamespace(chart=self.CHART, + namespace=namespace) + else: + return overrides