diff --git a/sysinv/sysinv/sysinv/sysinv/common/utils.py b/sysinv/sysinv/sysinv/sysinv/common/utils.py index f1850c5bfe..11029d9ca2 100644 --- a/sysinv/sysinv/sysinv/sysinv/common/utils.py +++ b/sysinv/sysinv/sysinv/sysinv/common/utils.py @@ -2065,3 +2065,11 @@ def is_aio_duplex_system(dbapi): return (system.system_type == constants.TIS_AIO_BUILD and (system.system_mode == constants.SYSTEM_MODE_DUPLEX or system.system_mode == constants.SYSTEM_MODE_DUPLEX_DIRECT)) + + +def generate_armada_manifest_dir(app_name, app_version): + return os.path.join(constants.APP_SYNCED_DATA_PATH, app_name, app_version) + + +def generate_armada_manifest_filename_abs(armada_mfile_dir, app_name, manifest_filename): + return os.path.join(armada_mfile_dir, app_name + '-' + manifest_filename) diff --git a/sysinv/sysinv/sysinv/sysinv/conductor/kube_app.py b/sysinv/sysinv/sysinv/sysinv/conductor/kube_app.py index 2e968cefb7..76b5022d8b 100644 --- a/sysinv/sysinv/sysinv/sysinv/conductor/kube_app.py +++ b/sysinv/sysinv/sysinv/sysinv/conductor/kube_app.py @@ -37,6 +37,7 @@ from sysinv.common import utils as cutils from sysinv.common.storage_backend_conf import K8RbdProvisioner from sysinv.helm import common from sysinv.helm import helm +from sysinv.helm import manifest from sysinv.helm import utils as helm_utils @@ -83,14 +84,6 @@ def generate_armada_manifest_filename(app_name, app_version, manifest_filename): app_name + '-' + manifest_filename) -def generate_armada_manifest_dir(app_name, app_version): - return os.path.join(constants.APP_SYNCED_DATA_PATH, app_name, app_version) - - -def generate_armada_manifest_filename_abs(armada_mfile_dir, app_name, manifest_filename): - return os.path.join(armada_mfile_dir, app_name + '-' + manifest_filename) - - def generate_manifest_filename_abs(app_name, app_version, manifest_filename): return os.path.join(constants.APP_INSTALL_PATH, app_name, app_version, manifest_filename) @@ -1044,39 +1037,43 @@ class AppOperator(object): """Returns list of override files or None, used in application-install and application-delete.""" - missing_overrides = [] - available_overrides = [] + missing_helm_overrides = [] + available_helm_overrides = [] for chart in charts: overrides = chart.namespace + '-' + chart.name + '.yaml' overrides_file = os.path.join(overrides_dir, overrides) if not os.path.exists(overrides_file): - missing_overrides.append(overrides_file) + missing_helm_overrides.append(overrides_file) else: - available_overrides.append(overrides_file) + available_helm_overrides.append(overrides_file) - # Now handle any meta-overrides files. These can affect - # sections of the chart schema other than "values, and can - # affect the chartgroup or even the manifest. - if self._helm.generate_meta_overrides( - chart.name, chart.namespace, app_name, mode): - overrides = chart.namespace + '-' + chart.name + \ - '-meta' + '.yaml' - overrides_file = os.path.join(overrides_dir, overrides) - if not os.path.exists(overrides_file): - missing_overrides.append(overrides_file) - else: - available_overrides.append(overrides_file) - - if missing_overrides: - LOG.error("Missing the following overrides: %s" % missing_overrides) + if missing_helm_overrides: + LOG.error("Missing the following overrides: %s" % missing_helm_overrides) return None - return available_overrides - def _generate_armada_overrides_str(self, app_name, app_version, overrides_files): - return " ".join([' --values /overrides/{0}/{1}/{2}'.format(app_name, app_version, - os.path.basename(i)) - for i in overrides_files]) + # Get the armada manifest overrides files + manifest_op = manifest.ArmadaManifestOperator() + armada_overrides = manifest_op.load_summary(overrides_dir) + + return (available_helm_overrides, armada_overrides) + + def _generate_armada_overrides_str(self, app_name, app_version, + helm_files, armada_files): + overrides_str = "" + if helm_files: + overrides_str += " ".join([ + ' --values /overrides/{0}/{1}/{2}'.format( + app_name, app_version, os.path.basename(i)) + for i in helm_files + ]) + if armada_files: + overrides_str += " ".join([ + ' --values /manifests/{0}/{1}/{2}'.format( + app_name, app_version, os.path.basename(i)) + for i in armada_files + ]) + return overrides_str def _remove_chart_overrides(self, overrides_dir, manifest_file): charts = self._get_list_of_charts(manifest_file) @@ -1335,12 +1332,11 @@ class AppOperator(object): overrides_str = '' old_app.charts = self._get_list_of_charts(old_app.armada_mfile_abs) if old_app.system_app: - overrides_files = self._get_overrides_files(old_app.overrides_dir, - old_app.charts, - old_app.name, mode=None) - overrides_str = \ - self._generate_armada_overrides_str(old_app.name, old_app.version, - overrides_files) + (helm_files, armada_files) = self._get_overrides_files( + old_app.overrides_dir, old_app.charts, old_app.name, mode=None) + + overrides_str = self._generate_armada_overrides_str( + old_app.name, old_app.version, helm_files, armada_files) if self._make_armada_request_with_monitor(old_app, constants.APP_APPLY_OP, @@ -1573,13 +1569,12 @@ class AppOperator(object): self._helm.generate_helm_application_overrides( app.overrides_dir, app.name, mode, cnamespace=None, armada_format=True, armada_chart_info=app.charts, combined=True) - overrides_files = self._get_overrides_files(app.overrides_dir, - app.charts, - app.name, mode) - if overrides_files: + (helm_files, armada_files) = self._get_overrides_files( + app.overrides_dir, app.charts, app.name, mode) + if helm_files or armada_files: LOG.info("Application overrides generated.") overrides_str = self._generate_armada_overrides_str( - app.name, app.version, overrides_files) + app.name, app.version, helm_files, armada_files) self._update_app_status( app, new_progress=constants.APP_PROGRESS_DOWNLOAD_IMAGES) self._download_images(app) @@ -1842,14 +1837,14 @@ class AppOperator(object): self.overrides_dir = generate_overrides_dir( self._kube_app.get('name'), self._kube_app.get('app_version')) - self.armada_mfile_dir = generate_armada_manifest_dir( + self.armada_mfile_dir = cutils.generate_armada_manifest_dir( self._kube_app.get('name'), self._kube_app.get('app_version')) self.armada_mfile = generate_armada_manifest_filename( self._kube_app.get('name'), self._kube_app.get('app_version'), self._kube_app.get('manifest_file')) - self.armada_mfile_abs = generate_armada_manifest_filename_abs( + self.armada_mfile_abs = cutils.generate_armada_manifest_filename_abs( self.armada_mfile_dir, self._kube_app.get('name'), self._kube_app.get('manifest_file')) @@ -1903,7 +1898,7 @@ class AppOperator(object): self._kube_app.manifest_file = new_mfile self.armada_mfile = generate_armada_manifest_filename( self.name, self.version, new_mfile) - self.armada_mfile_abs = generate_armada_manifest_filename_abs( + self.armada_mfile_abs = cutils.generate_armada_manifest_filename_abs( self.armada_mfile_dir, self.name, new_mfile) self.mfile_abs = generate_manifest_filename_abs( self.name, self.version, new_mfile) @@ -1914,7 +1909,7 @@ class AppOperator(object): self.system_app = \ (self.name == constants.HELM_APP_OPENSTACK) - new_armada_dir = generate_armada_manifest_dir( + new_armada_dir = cutils.generate_armada_manifest_dir( self.name, self.version) shutil.move(self.armada_mfile_dir, new_armada_dir) shutil.rmtree(os.path.dirname(self.armada_mfile_dir)) diff --git a/sysinv/sysinv/sysinv/sysinv/helm/barbican.py b/sysinv/sysinv/sysinv/sysinv/helm/barbican.py index a10edde699..62b1298ebf 100644 --- a/sysinv/sysinv/sysinv/sysinv/helm/barbican.py +++ b/sysinv/sysinv/sysinv/sysinv/helm/barbican.py @@ -17,6 +17,11 @@ class BarbicanHelm(openstack.OpenstackBaseHelm): AUTH_USERS = ['barbican'] SERVICE_NAME = constants.HELM_CHART_BARBICAN + def execute_manifest_updates(self, operator, app_name=None): + if not self._is_labeled(common.LABEL_BARBICAN, 'enabled'): + operator.manifest_chart_groups_delete( + 'armada-manifest', 'openstack-barbican') + def get_overrides(self, namespace=None): overrides = { common.HELM_NS_OPENSTACK: { diff --git a/sysinv/sysinv/sysinv/sysinv/helm/base.py b/sysinv/sysinv/sysinv/sysinv/helm/base.py index f09a45fe29..cbab2d6e9f 100644 --- a/sysinv/sysinv/sysinv/sysinv/helm/base.py +++ b/sysinv/sysinv/sysinv/sysinv/helm/base.py @@ -241,19 +241,6 @@ class BaseHelm(object): """ return {} - def get_meta_overrides(self, namespace, app_name=None, mode=None): - """ - Return Armada-formatted chart-specific meta-overrides - - This allows a helm chart class to specify overrides (in Armada format) - for things other than the "values" section of a chart. This includes - other sections of a chart, as well as chart groups or even the - overall manifest itself. - - May be left blank to indicate that there are no additional overrides. - """ - return {} - def version_check(self, app_version): """ Validate application version @@ -261,3 +248,20 @@ class BaseHelm(object): Return False if version is not supported by the plugin. """ return True + + def execute_manifest_updates(self, operator, app_name=None): + """ + Update the elements of the armada manifest. + + This allows a helm chart plugin to use the ArmadaManifestOperator to + make dynamic structural changes to the application manifest based on the + current conditions in the platform + + Changes include updates to manifest documents for the following schemas: + armada/Manifest/v1, armada/ChartGroup/v1, armada/Chart/v1. + + :param operator: an instance of the ArmadaManifestOperator + :parameter app_name: application for which the specific actions are + taken + """ + pass diff --git a/sysinv/sysinv/sysinv/sysinv/helm/ceilometer.py b/sysinv/sysinv/sysinv/sysinv/helm/ceilometer.py index 1b2c9e4024..9f91d906c2 100644 --- a/sysinv/sysinv/sysinv/sysinv/helm/ceilometer.py +++ b/sysinv/sysinv/sysinv/sysinv/helm/ceilometer.py @@ -22,6 +22,11 @@ class CeilometerHelm(openstack.OpenstackBaseHelm): SERVICE_NAME = 'ceilometer' AUTH_USERS = ['ceilometer'] + def execute_manifest_updates(self, operator, app_name=None): + if not self._is_labeled(common.LABEL_TELEMETRY, 'enabled'): + operator.manifest_chart_groups_delete( + 'armada-manifest', 'openstack-telemetry') + def get_overrides(self, namespace=None): overrides = { common.HELM_NS_OPENSTACK: { diff --git a/sysinv/sysinv/sysinv/sysinv/helm/common.py b/sysinv/sysinv/sysinv/sysinv/helm/common.py index e3c469e844..8493e97fb8 100644 --- a/sysinv/sysinv/sysinv/sysinv/helm/common.py +++ b/sysinv/sysinv/sysinv/sysinv/helm/common.py @@ -51,6 +51,8 @@ LABEL_COMPUTE_LABEL = 'openstack-compute-node' LABEL_OPENVSWITCH = 'openvswitch' LABEL_REMOTE_STORAGE = 'remote-storage' LABEL_IRONIC = 'openstack-ironic' +LABEL_BARBICAN = 'openstack-barbican' +LABEL_TELEMETRY = 'openstack-telemetry' # Label values LABEL_VALUE_ENABLED = 'enabled' diff --git a/sysinv/sysinv/sysinv/sysinv/helm/garbd.py b/sysinv/sysinv/sysinv/sysinv/helm/garbd.py index 1442d4d2a9..8d773f7a6d 100644 --- a/sysinv/sysinv/sysinv/sysinv/helm/garbd.py +++ b/sysinv/sysinv/sysinv/sysinv/helm/garbd.py @@ -30,9 +30,8 @@ class GarbdHelm(base.BaseHelm): base.BaseHelm.SUPPORTED_NAMESPACES + [common.HELM_NS_OPENSTACK] } - def get_meta_overrides(self, namespace, app_name=None, mode=None): - - def _meta_overrides(): + def execute_manifest_updates(self, operator, app_name=None): + if app_name == constants.HELM_APP_OPENSTACK: if (self._num_controllers() < 2 or utils.is_aio_duplex_system(self.dbapi) or (self._distributed_cloud_role() == @@ -42,33 +41,8 @@ class GarbdHelm(base.BaseHelm): # we'll use a single mariadb server and so we don't want to # run garbd. This will remove "openstack-garbd" from the # charts in the openstack-mariadb chartgroup. - return { - 'schema': 'armada/ChartGroup/v1', - 'metadata': { - 'schema': 'metadata/Document/v1', - 'name': 'openstack-mariadb', - }, - 'data': { - 'description': 'Mariadb', - 'sequenced': True, - 'chart_group': [ - 'openstack-mariadb', - ] - } - } - else: - return {} - - overrides = { - common.HELM_NS_OPENSTACK: _meta_overrides() - } - if namespace in self.SUPPORTED_NAMESPACES: - return overrides[namespace] - elif namespace: - raise exception.InvalidHelmNamespace(chart=self.CHART, - namespace=namespace) - else: - return overrides + operator.chart_group_chart_delete('openstack-mariadb', + 'openstack-garbd') def get_overrides(self, namespace=None): overrides = { diff --git a/sysinv/sysinv/sysinv/sysinv/helm/helm.py b/sysinv/sysinv/sysinv/sysinv/helm/helm.py index f07707488b..cd806f0ea2 100644 --- a/sysinv/sysinv/sysinv/sysinv/helm/helm.py +++ b/sysinv/sysinv/sysinv/sysinv/helm/helm.py @@ -22,6 +22,7 @@ from sysinv.common import exception from sysinv.common import utils from sysinv.openstack.common import log as logging from sysinv.helm import common +from sysinv.helm import manifest LOG = logging.getLogger(__name__) @@ -448,19 +449,6 @@ class HelmOperator(object): else: LOG.exception("chart name is required") - @helm_context - def generate_meta_overrides(self, chart_name, chart_namespace, - app_name=None, mode=None): - overrides = {} - if chart_name in self.chart_operators: - try: - overrides.update( - self.chart_operators[chart_name].get_meta_overrides( - chart_namespace, app_name, mode)) - except exception.InvalidHelmNamespace: - raise - return overrides - @helm_context def generate_helm_application_overrides(self, path, app_name, mode=None, @@ -495,6 +483,16 @@ class HelmOperator(object): raise if app_name in self.helm_system_applications: + # Get a manifest operator to provide a single point of + # manipulation for the chart, chart group and manifest schemas + manifest_op = manifest.ArmadaManifestOperator() + + # Load the manifest into the operator + armada_manifest = utils.generate_armada_manifest_filename_abs( + utils.generate_armada_manifest_dir(app.name, app.app_version), + app.name, app.manifest_file) + manifest_op.load(armada_manifest) + app_overrides = self._get_helm_application_overrides(app_name, cnamespace) for (chart_name, overrides) in iteritems(app_overrides): @@ -543,17 +541,19 @@ class HelmOperator(object): overrides[key] = new_overrides self._write_chart_overrides(path, chart_name, cnamespace, overrides) - # Write any meta-overrides for this chart. These will be in - # armada format already. - if armada_format: - overrides = self.generate_meta_overrides(chart_name, - cnamespace, - app_name, - mode) - if overrides: - chart_meta_name = chart_name + '-meta' - self._write_chart_overrides( - path, chart_meta_name, cnamespace, overrides) + # Update manifest docs based on the plugin directives + if chart_name in self.chart_operators: + self.chart_operators[chart_name].execute_manifest_updates( + manifest_op, app_name) + + # Update the manifest based on platform conditions + manifest.platform_mode_manifest_updates( + self.dbapi, manifest_op, app_name, mode) + + # Write the manifest doc overrides for the chart, chart group and manifest + manifest_op.save() + manifest_op.save_summary(path=path) + else: # Generic applications for chart in armada_chart_info: diff --git a/sysinv/sysinv/sysinv/sysinv/helm/helm_toolkit.py b/sysinv/sysinv/sysinv/sysinv/helm/helm_toolkit.py index d11445b8cb..9e57d09cc4 100644 --- a/sysinv/sysinv/sysinv/sysinv/helm/helm_toolkit.py +++ b/sysinv/sysinv/sysinv/sysinv/helm/helm_toolkit.py @@ -21,78 +21,6 @@ class HelmToolkitHelm(base.BaseHelm): common.HELM_NS_HELM_TOOLKIT, ] - # (WZ) This code will be moved to a proper place once meta-overrides - # for the manifest can be generated not thru a specific chart. - # Note: The application-apply mode is associated with the application not - # the namespace. So app_name needs to be passed in. - def get_meta_overrides(self, namespace, app_name=None, mode=None): - def _meta_overrides(app_name, mode): - if not app_name: - # Application is unknown, so ignore mode. - LOG.info("App is None. Ignore mode.") - return {} - elif app_name not in constants.HELM_APP_APPLY_MODES.keys(): - LOG.info("App %s is not supported. Ignore mode." % app_name) - return {} - elif mode == constants.OPENSTACK_RESTORE_DB: - # During application restore, first bring up - # MariaDB service. - return { - 'schema': 'armada/Manifest/v1', - 'metadata': { - 'schema': 'metadata/Document/v1', - 'name': 'armada-manifest' - }, - 'data': { - 'release_prefix': 'osh', - 'chart_groups': [ - 'kube-system-ingress', - 'openstack-ingress', - 'provisioner', - 'openstack-mariadb', - ] - } - } - elif mode == constants.OPENSTACK_RESTORE_STORAGE: - # After MariaDB data is restored, restore Keystone, - # Glance and Cinder. - return { - 'schema': 'armada/Manifest/v1', - 'metadata': { - 'schema': 'metadata/Document/v1', - 'name': 'armada-manifest' - }, - 'data': { - 'release_prefix': 'osh', - 'chart_groups': [ - 'kube-system-ingress', - 'openstack-ingress', - 'provisioner', - 'openstack-mariadb', - 'openstack-memcached', - 'openstack-rabbitmq', - 'openstack-keystone', - 'openstack-glance', - 'openstack-cinder', - ] - } - } - else: - # When mode is OPENSTACK_RESTORE_NORMAL or None, - # bring up all the openstack services. - return {} - - overrides = { - common.HELM_NS_HELM_TOOLKIT: _meta_overrides(app_name, mode) - } - if namespace in self.SUPPORTED_NAMESPACES: - return overrides[namespace] - elif namespace: - raise exception.InvalidHelmNamespace(chart=self.CHART, - namespace=namespace) - else: - return overrides - def get_namespaces(self): return self.SUPPORTED_NAMESPACES diff --git a/sysinv/sysinv/sysinv/sysinv/helm/ironic.py b/sysinv/sysinv/sysinv/sysinv/helm/ironic.py index e8f7128748..d5cb484385 100644 --- a/sysinv/sysinv/sysinv/sysinv/helm/ironic.py +++ b/sysinv/sysinv/sysinv/sysinv/helm/ironic.py @@ -20,49 +20,14 @@ class IronicHelm(openstack.OpenstackBaseHelm): SERVICE_USERS = ['glance'] AUTH_USERS = ['ironic'] - def get_meta_overrides(self, namespace, app_name=None, mode=None): - - def _meta_overrides(): - if (self._num_controllers() >= 2 and - self._is_labeled(common.LABEL_IRONIC, 'enabled')): - # If there are fewer than 2 controllers or openstack-ironic - # label was not set, ironic chart won't be added to - # openstack-compute-kit chartgroup - return { - 'schema': 'armada/ChartGroup/v1', - 'metadata': { - 'schema': 'metadata/Document/v1', - 'name': 'openstack-compute-kit', - }, - 'data': { - 'description': - 'Deploy nova/neutron/ironic,' + - 'as well as supporting services', - 'sequenced': False, - 'chart_group': [ - 'openstack-libvirt', - 'openstack-openvswitch', - 'openstack-nova', - 'openstack-nova-api-proxy', - 'openstack-neutron', - 'openstack-placement', - 'openstack-ironic', - ] - } - } - else: - return {} - - overrides = { - common.HELM_NS_OPENSTACK: _meta_overrides() - } - if namespace in self.SUPPORTED_NAMESPACES: - return overrides[namespace] - elif namespace: - raise exception.InvalidHelmNamespace(chart=self.CHART, - namespace=namespace) - else: - return overrides + def execute_manifest_updates(self, operator, app_name=None): + if (self._num_controllers() >= 2 and + self._is_labeled(common.LABEL_IRONIC, 'enabled')): + # If there are fewer than 2 controllers or openstack-ironic + # label was not set, ironic chart won't be added to + # openstack-compute-kit chartgroup + operator.chart_group_chart_insert('openstack-compute-kit', + 'openstack-ironic') def get_overrides(self, namespace=None): overrides = { diff --git a/sysinv/sysinv/sysinv/sysinv/helm/keystone_api_proxy.py b/sysinv/sysinv/sysinv/sysinv/helm/keystone_api_proxy.py index 4bdb7d7634..accdaad37a 100644 --- a/sysinv/sysinv/sysinv/sysinv/helm/keystone_api_proxy.py +++ b/sysinv/sysinv/sysinv/sysinv/helm/keystone_api_proxy.py @@ -21,50 +21,11 @@ class KeystoneApiProxyHelm(openstack.OpenstackBaseHelm): SERVICE_NAME = constants.HELM_CHART_KEYSTONE_API_PROXY DCORCH_SERVICE_NAME = 'dcorch' - def get_meta_overrides(self, namespace, app_name=None, mode=None): - - def _meta_overrides(): - if (self._distributed_cloud_role() == - constants.DISTRIBUTED_CLOUD_ROLE_SYSTEMCONTROLLER): - # If we are on distributed cloud system controller, - # it will only include the required chart groups - # in the armada manifest - return { - 'schema': 'armada/Manifest/v1', - 'metadata': { - 'schema': 'metadata/Document/v1', - 'name': 'armada-manifest' - }, - 'data': { - 'release_prefix': 'osh', - 'chart_groups': [ - 'kube-system-ingress', - 'openstack-ingress', - 'openstack-mariadb', - 'openstack-memcached', - 'openstack-rabbitmq', - 'openstack-keystone', - 'openstack-barbican', - 'openstack-glance', - 'openstack-horizon', - 'openstack-cinder', - 'openstack-keystone-api-proxy', - ] - } - } - else: - return {} - - overrides = { - common.HELM_NS_OPENSTACK: _meta_overrides() - } - if namespace in self.SUPPORTED_NAMESPACES: - return overrides[namespace] - elif namespace: - raise exception.InvalidHelmNamespace(chart=self.CHART, - namespace=namespace) - else: - return overrides + def execute_manifest_updates(self, operator, app_name=None): + if (self._distributed_cloud_role() == + constants.DISTRIBUTED_CLOUD_ROLE_SYSTEMCONTROLLER): + operator.manifest_chart_groups_insert( + 'armada-manifest', 'openstack-keystone-api-proxy') def get_overrides(self, namespace=None): overrides = { diff --git a/sysinv/sysinv/sysinv/sysinv/helm/manifest.py b/sysinv/sysinv/sysinv/sysinv/helm/manifest.py new file mode 100644 index 0000000000..50b0f6dbb4 --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/helm/manifest.py @@ -0,0 +1,520 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright (c) 2019 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# +# All Rights Reserved. +# + +""" System inventory Armada manifest operator.""" + +import os +import json +import ruamel.yaml as yaml +import tempfile + +from glob import glob +from six import iteritems +from sysinv.common import constants +from sysinv.common import exception +from sysinv.openstack.common import log as logging + +LOG = logging.getLogger(__name__) + +KEY_SCHEMA = 'schema' +VAL_SCHEMA_MANIFEST = 'armada/Manifest/v1' +VAL_SCHEMA_CHART_GROUP = 'armada/ChartGroup/v1' +VAL_SCHEMA_CHART = 'armada/Chart/v1' + +KEY_METADATA = 'metadata' +KEY_METADATA_NAME = 'name' + +KEY_DATA = 'data' +KEY_DATA_CHART_GROUPS = 'chart_groups' # for manifest doc updates +KEY_DATA_CHART_GROUP = 'chart_group' # for chart group doc updates +KEY_DATA_CHART_NAME = 'chart_name' # for chart doc updates + +# Attempt to keep a compact filename +FILE_PREFIX = { + KEY_DATA_CHART_GROUPS: 'm-', # for manifest doc overrides + KEY_DATA_CHART_GROUP: 'cg-', # for chart group doc overrides + KEY_DATA_CHART_NAME: 'c-' # for chart doc overrides +} +FILE_SUFFIX = '-meta.yaml' +SUMMARY_FILE = 'armada-overrides.yaml' + + +class ArmadaManifestOperator(object): + + def __init__(self, manifest_fqpn=None): + self.manifest_path = None # Location to write overrides + + self.content = [] # original app manifest content + + self.docs = { + KEY_DATA_CHART_GROUPS: {}, # LUT for all manifest docs + KEY_DATA_CHART_GROUP: {}, # LUT for all chart group docs + KEY_DATA_CHART_NAME: {} # LUT for all chart docs + } + + self.updated = { + KEY_DATA_CHART_GROUPS: set(), # indicate manifest doc change + KEY_DATA_CHART_GROUP: set(), # indicate chart group update + KEY_DATA_CHART_NAME: set() # indicate chart doc update + } + + if manifest_fqpn: + self.load(manifest_fqpn) + + def __str__(self): + return json.dumps({ + 'manifest': self.docs[KEY_DATA_CHART_GROUPS], + 'chart_groups': self.docs[KEY_DATA_CHART_GROUP], + 'charts': self.docs[KEY_DATA_CHART_NAME], + }, indent=2) + + def load_summary(self, path): + """ Load the list of generated overrides files + + Generate a list of override files that were written for the manifest. + This is used to generate Armada --values overrides for the manifest. + + :param path: location of the overrides summary file + :return: a list of override files written + """ + files_written = [] + summary_fqpn = os.path.join(path, SUMMARY_FILE) + if os.path.exists(summary_fqpn): + self.manifest_path = os.path.dirname(summary_fqpn) + with open(summary_fqpn, 'r') as f: + files_written = list(yaml.load_all( + f, Loader=yaml.RoundTripLoader))[0] + return files_written + + def load(self, manifest_fqpn): + """ Load the application manifest for processing + + :param manifest_fqpn: fully qualified path name of the application manifest + """ + if os.path.exists(manifest_fqpn): + self.manifest_path = os.path.dirname(manifest_fqpn) + with open(manifest_fqpn, 'r') as f: + self.content = list(yaml.load_all( + f, Loader=yaml.RoundTripLoader)) + + # Generate the lookup tables + # For the individual chart docs + self.docs[KEY_DATA_CHART_NAME] = { + i[KEY_METADATA][KEY_METADATA_NAME]: i + for i in self.content + if i[KEY_SCHEMA] == VAL_SCHEMA_CHART} + + # For the chart group docs + self.docs[KEY_DATA_CHART_GROUP] = { + i[KEY_METADATA][KEY_METADATA_NAME]: i + for i in self.content + if i[KEY_SCHEMA] == VAL_SCHEMA_CHART_GROUP} + + # For the single manifest doc + self.docs[KEY_DATA_CHART_GROUPS] = { + i[KEY_METADATA][KEY_METADATA_NAME]: i + for i in self.content + if i[KEY_SCHEMA] == VAL_SCHEMA_MANIFEST} + else: + LOG.error("Manifest file %s does not exist" % manifest_fqpn) + + def _cleanup_meta_files(self, path): + """ Remove any previously written overrides files + + :param path: directory containing manifest overrides files + """ + for k, v in iteritems(FILE_PREFIX): + fileregex = "{}*{}".format(v, FILE_SUFFIX) + filepath = os.path.join(self.manifest_path, fileregex) + for f in glob(filepath): + os.remove(f) + + def _write_file(self, path, filename, pathfilename, data): + """ Write a yaml file + + :param path: path to write the file + :param filename: name of the file + :param pathfilename: FQPN of the file + :param data: file data + """ + try: + fd, tmppath = tempfile.mkstemp(dir=path, prefix=filename, + text=True) + + with open(tmppath, 'w') as f: + yaml.dump(data, f, Dumper=yaml.RoundTripDumper, + default_flow_style=False) + os.close(fd) + os.rename(tmppath, pathfilename) + # Change the permission to be readable to non-root + # users(ie.Armada) + os.chmod(pathfilename, 0o644) + except Exception: + if os.path.exists(tmppath): + os.remove(tmppath) + LOG.exception("Failed to write meta overrides %s" % pathfilename) + raise + + def save_summary(self, path=None): + """ Write a yaml file containing the list of override files generated + + :param path: optional alternative location to write the file + """ + files_written = [] + for k, v in iteritems(self.updated): + for i in v: + filename = '{}{}{}'.format(FILE_PREFIX[k], i, FILE_SUFFIX) + filepath = os.path.join(self.manifest_path, filename) + files_written.append(filepath) + + # Write the list of files generated. This can be read to include with + # the Armada overrides + if path and os.path.exists(path): + # if provided, write to an alternate location + self._write_file(path, SUMMARY_FILE, + os.path.join(path, SUMMARY_FILE), + files_written) + else: + # if not provided, write to the armada directory + self._write_file(self.manifest_path, SUMMARY_FILE, + os.path.join(self.manifest_path, SUMMARY_FILE), + files_written) + + def save(self): + """ Save the overrides files + + Write the elements of the manifest (manifest, chart_group, chart) that + was updated into an overrides file. The files are written to the same + directory as the application manifest. + """ + if os.path.exists(self.manifest_path): + + # cleanup any existing meta override files + self._cleanup_meta_files(self.manifest_path) + + # Only write the updated docs as meta overrides + for k, v in iteritems(self.updated): + for i in v: + filename = '{}{}{}'.format(FILE_PREFIX[k], i, FILE_SUFFIX) + filepath = os.path.join(self.manifest_path, filename) + self._write_file(self.manifest_path, filename, filepath, + self.docs[k][i]) + else: + LOG.error("Manifest directory %s does not exist" % self.manifest_path) + + def _validate_manifest(self, manifest): + """ Ensure that the manifest is known + + :param manifest: name of the manifest + """ + if manifest not in self.docs[KEY_DATA_CHART_GROUPS]: + LOG.error("%s is not %s" % (manifest, self.docs[KEY_DATA_CHART_GROUPS].keys())) + return False + return True + + def _validate_chart_group(self, chart_group): + """ Ensure that the chart_group is known + + :param chart_group: name of the chart_group + """ + if chart_group not in self.docs[KEY_DATA_CHART_GROUP]: + LOG.error("%s is an unknown chart_group" % chart_group) + return False + return True + + def _validate_chart_groups_from_list(self, chart_group_list): + """ Ensure that all the charts groups in chart group list are known + + :param chart_group_list: list of chart groups + """ + for cg in chart_group_list: + if not self._validate_chart_group(cg): + return False + return True + + def _validate_chart(self, chart): + """ Ensure that the chart is known + + :param chart: name of the chart + """ + if chart not in self.docs[KEY_DATA_CHART_NAME]: + LOG.error("%s is an unknown chart" % chart) + return False + return True + + def _validate_chart_from_list(self, chart_list): + """ Ensure that all the charts in chart list are known + + :param chart_list: list of charts + """ + for c in chart_list: + if not self._validate_chart(c): + return False + return True + + def manifest_chart_groups_delete(self, manifest, chart_group): + """ Delete a chart group from a manifest + + This method will delete a chart group from a manifest's list of charts + groups. + + :param manifest: manifest containing the list of chart groups + :param chart_group: chart group name to delete + """ + if (not self._validate_manifest(manifest) or + not self._validate_chart_group(chart_group)): + return + + if chart_group not in self.docs[KEY_DATA_CHART_GROUPS][manifest][KEY_DATA][ + KEY_DATA_CHART_GROUPS]: + LOG.info("%s is not currently enabled. Cannot delete." % + chart_group) + return + + self.docs[KEY_DATA_CHART_GROUPS][manifest][KEY_DATA][ + KEY_DATA_CHART_GROUPS].remove(chart_group) + self.updated[KEY_DATA_CHART_GROUPS].update([manifest]) + + def manifest_chart_groups_insert(self, manifest, chart_group, before_group=None): + """ Insert a chart group into a manifest + + This method will insert a chart group into a manifest at the end of the + list of chart groups. If the before_group parameter is used the chart + group can be placed at a specific point in the chart group list. + + :param manifest: manifest containing the list of chart groups + :param chart_group: chart group name to insert + :param before_group: chart group name to be appear after the inserted + chart group in the list + """ + if (not self._validate_manifest(manifest) or + not self._validate_chart_group(chart_group)): + return + + if chart_group in self.docs[KEY_DATA_CHART_GROUPS][manifest][KEY_DATA][KEY_DATA_CHART_GROUPS]: + LOG.error("%s is already enabled. Cannot insert." % + chart_group) + return + + if before_group: + if not self._validate_chart_group(before_group): + return + + if before_group not in self.docs[KEY_DATA_CHART_GROUPS][manifest][KEY_DATA][ + KEY_DATA_CHART_GROUPS]: + LOG.error("%s is not currently enabled. Cannot insert %s" % + (before_group, chart_group)) + return + + cgs = self.docs[KEY_DATA_CHART_GROUPS][manifest][KEY_DATA][KEY_DATA_CHART_GROUPS] + insert_index = cgs.index(before_group) + cgs.insert(insert_index, chart_group) + self.docs[KEY_DATA_CHART_GROUPS][manifest][KEY_DATA][KEY_DATA_CHART_GROUPS] = cgs + else: + self.docs[KEY_DATA_CHART_GROUPS][manifest][KEY_DATA][ + KEY_DATA_CHART_GROUPS].append(chart_group) + + self.updated[KEY_DATA_CHART_GROUPS].update([manifest]) + + def manifest_chart_groups_set(self, manifest, chart_group_list=None): + """ Set the chart groups for a specific manifest + + This will replace the current set of charts groups in the manifest as + specified by the armada/Manifest/v1 schema with the provided list of + chart groups. + + :param manifest: manifest containing the list of chart groups + :param chart_group_list: list of chart groups to replace the current set + of chart groups + """ + if not self._validate_manifest(manifest): + return + + if chart_group_list: + if not self._validate_chart_groups_from_list(chart_group_list): + return + + self.docs[KEY_DATA_CHART_GROUPS][manifest][KEY_DATA][KEY_DATA_CHART_GROUPS] = chart_group_list + + else: + LOG.error("Cannot set the manifest chart_groups to an empty list") + + def chart_group_chart_delete(self, chart_group, chart): + """ Delete a chart from a chart group + + This method will delete a chart from a chart group's list of charts. + + :param chart_group: chart group name + :param chart: chart name to remove from the chart list + """ + if (not self._validate_chart_group(chart_group) or + not self._validate_chart(chart)): + return + + if chart not in self.docs[KEY_DATA_CHART_GROUP][chart_group][KEY_DATA][ + KEY_DATA_CHART_GROUP]: + LOG.info("%s is not currently enabled. Cannot delete." % + chart) + return + + self.docs[KEY_DATA_CHART_GROUP][chart_group][KEY_DATA][ + KEY_DATA_CHART_GROUP].remove(chart) + self.updated[KEY_DATA_CHART_GROUP].update([chart_group]) + + def chart_group_chart_insert(self, chart_group, chart, before_chart=None): + """ Insert a chart into a chart group + + This method will insert a chart into a chart group at the end of the + list of charts. If the before_chart parameter is used the chart can be + placed at a specific point in the chart list. + + :param chart_group: chart group name + :param chart: chart name to insert + :param before_chart: chart name to be appear after the inserted chart in + the list + """ + if (not self._validate_chart_group(chart_group) or + not self._validate_chart(chart)): + return + + if chart in self.docs[KEY_DATA_CHART_GROUP][chart_group][KEY_DATA][KEY_DATA_CHART_GROUP]: + LOG.error("%s is already enabled. Cannot insert." % + chart) + return + + if before_chart: + if not self._validate_chart(before_chart): + return + + if before_chart not in self.docs[KEY_DATA_CHART_GROUP][chart_group][KEY_DATA][ + KEY_DATA_CHART_GROUP]: + LOG.error("%s is not currently enabled. Cannot insert %s" % + (before_chart, chart)) + return + + cg = self.docs[KEY_DATA_CHART_GROUP][chart_group][KEY_DATA][KEY_DATA_CHART_GROUP] + insert_index = cg.index(before_chart) + cg.insert(insert_index, chart) + self.docs[KEY_DATA_CHART_GROUP][chart_group][KEY_DATA][KEY_DATA_CHART_GROUP] = cg + else: + self.docs[KEY_DATA_CHART_GROUP][chart_group][KEY_DATA][ + KEY_DATA_CHART_GROUP].append(chart) + + self.updated[KEY_DATA_CHART_GROUP].update([chart_group]) + + def chart_group_set(self, chart_group, chart_list=None): + """ Set the charts for a specific chart group + + This will replace the current set of charts specified in the chart group + with the provided list. + + :param chart_group: chart group name + :param chart_list: list of charts to replace the current set of charts + """ + if not self._validate_chart_group(chart_group): + return + + if chart_list: + if not self._validate_chart_from_list(chart_list): + return + + self.docs[KEY_DATA_CHART_GROUP][chart_group][KEY_DATA][KEY_DATA_CHART_GROUP] = chart_list + + else: + LOG.error("Cannot set the chart_group charts to an empty list") + + def chart_group_add(self, chart_group, data): + """ Add a new chart group to the manifest. + + To support a self-contained dynamic plugin, this method is called to + introduced a new chart group based on the armada/ChartGroup/v1 schema. + + :param chart_group: chart group name + :param data: chart group data + """ + # Not implemented... yet. + pass + + def chart_add(self, chart, data): + """ Add a new chart to the manifest. + + To support a self-contained dynamic plugin, this method is called to + introduced a new chart based on the armada/Chart/v1 schema. + + :param chart: chart name + :param data: chart data + """ + # Not implemented... yet. + pass + + +def platform_mode_manifest_updates(dbapi, manifest_op, app_name, mode): + """ Update the application manifest based on the platform + + This is used for + + :param dbapi: DB api object + :param manifest_op: ArmadaManifestOperator for updating the application + manifest + :param app_name: application name + :param mode: mode to control how to apply the application manifest + """ + + if not app_name: + LOG.info("App is None. No platform mode based manifest updates taken.") + + elif app_name not in constants.HELM_APP_APPLY_MODES.keys(): + LOG.info("App %s is not supported. No platform mode based manifest " + "updates taken." % app_name) + + elif app_name == constants.HELM_APP_OPENSTACK: + + if mode == constants.OPENSTACK_RESTORE_DB: + # During application restore, first bring up + # MariaDB service. + manifest_op.manifest_chart_groups_set( + 'armada-manifest', + ['kube-system-ingress', + 'openstack-ingress', + 'openstack-mariadb']) + + elif mode == constants.OPENSTACK_RESTORE_STORAGE: + # After MariaDB data is restored, restore Keystone, + # Glance and Cinder. + manifest_op.manifest_chart_groups_set( + 'armada-manifest', + ['kube-system-ingress', + 'openstack-ingress', + 'openstack-mariadb', + 'openstack-memcached', + 'openstack-rabbitmq', + 'openstack-keystone', + 'openstack-glance', + 'openstack-cinder']) + + else: + # When mode is OPENSTACK_RESTORE_NORMAL or None, + # bring up all the openstack services. + try: + system = dbapi.isystem_get_one() + except exception.NotFound: + LOG.exception("System %s not found.") + raise + + if (system.distributed_cloud_role == + constants.DISTRIBUTED_CLOUD_ROLE_SYSTEMCONTROLLER): + # remove the chart_groups not needed in this configuration + manifest_op.manifest_chart_groups_delete( + 'armada-manifest', 'openstack-ceph-rgw') + manifest_op.manifest_chart_groups_delete( + 'armada-manifest', 'openstack-compute-kit') + manifest_op.manifest_chart_groups_delete( + 'armada-manifest', 'openstack-heat') + manifest_op.manifest_chart_groups_delete( + 'armada-manifest', 'openstack-telemetry') diff --git a/sysinv/sysinv/sysinv/sysinv/helm/openvswitch.py b/sysinv/sysinv/sysinv/sysinv/helm/openvswitch.py index b95c143d3e..94348ac17f 100644 --- a/sysinv/sysinv/sysinv/sysinv/helm/openvswitch.py +++ b/sysinv/sysinv/sysinv/sysinv/helm/openvswitch.py @@ -19,45 +19,13 @@ class OpenvswitchHelm(openstack.OpenstackBaseHelm): CHART = constants.HELM_CHART_OPENVSWITCH - # There are already two places at where we generate chartgroup overrides. - # If more chartgroup overrides are needed in future, it's better to do it - # at a fixed place. Distributing the overrides in the chart plugins makes - # it hard to manage chartgroup overrides. - def get_meta_overrides(self, namespace, app_name=None, mode=None): - def _meta_overrides(): - if utils.get_vswitch_type(self.dbapi) == \ - constants.VSWITCH_TYPE_NONE: - # add the openvswitch chart into computekit chart group - return { - 'schema': 'armada/ChartGroup/v1', - 'metadata': { - 'schema': 'metadata/Document/v1', - 'name': 'openstack-compute-kit', - }, - 'data': { - 'chart_group': [ - 'openstack-libvirt', - 'openstack-openvswitch', - 'openstack-nova', - 'openstack-nova-api-proxy', - 'openstack-neutron', - 'openstack-placement', - ] - } - } - else: - return {} - - overrides = { - common.HELM_NS_OPENSTACK: _meta_overrides() - } - if namespace in self.SUPPORTED_NAMESPACES: - return overrides[namespace] - elif namespace: - raise exception.InvalidHelmNamespace(chart=self.CHART, - namespace=namespace) - else: - return overrides + def execute_manifest_updates(self, operator, app_name=None): + if utils.get_vswitch_type(self.dbapi) == constants.VSWITCH_TYPE_NONE: + # add the openvswitch chart into computekit chart group + operator.chart_group_chart_insert( + 'openstack-compute-kit', + 'openstack-openvswitch', + before_chart='openstack-nova') def get_overrides(self, namespace=None): overrides = {