From 4b1bf293743453bfd6eb2e09ad5864522a6c77ed Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Tue, 10 Aug 2021 11:53:16 +0100 Subject: [PATCH 1/5] Use merge_configs and merge_yaml to generate Kolla custom config This patch adds new functionality - merging base & environment specific kolla config. This allows you to place common settings in the base configuration and only keep environment specific settings in the environment directories. Change-Id: Id4588f4529a4522e68e22ce58711cb927fa68a9d Story: 2002009 Task: 42903 (cherry picked from commit 428ef10fa4d4a281f0c690876fc3cbfff41c2bbb) --- ansible/inventory/group_vars/all/kolla | 10 + ansible/kolla-openstack.yml | 83 +--- ansible/roles/image-download/tasks/main.yml | 5 + .../kolla_custom_config_info.py | 229 +++++++++ .../roles/kolla-openstack/defaults/main.yml | 438 ++++++++++++++++-- .../molecule/default/tests/test_default.py | 13 +- .../molecule/enable-everything/destroy.yml | 5 + .../molecule/enable-everything/molecule.yml | 19 +- .../molecule/enable-everything/prepare.yml | 93 ++++ .../enable-everything/tests/test_default.py | 96 ++-- .../roles/kolla-openstack/tasks/config.yml | 200 ++++---- .../{aodh.conf.j2 => kolla/config/aodh.conf} | 2 - .../config/backup.my.cnf} | 2 - .../config/barbican.conf} | 2 - .../config/blazar.conf} | 2 - .../config/ceilometer.conf} | 2 - .../config/cinder.conf} | 2 - .../config/cloudkitty.conf} | 2 - .../config/designate.conf} | 2 - .../config/galera.cnf} | 2 - .../config/glance.conf} | 2 - .../config/global.conf} | 2 - .../config/gnocchi.conf} | 2 - .../config/grafana.ini} | 2 - .../{heat.conf.j2 => kolla/config/heat.conf} | 2 - .../config/ironic-inspector.conf} | 0 .../config/ironic.conf} | 2 - .../config/keystone.conf} | 1 - .../config/magnum.conf} | 2 - .../config/manila.conf} | 2 - .../config/masakari.conf} | 2 - .../config/multipath.conf} | 0 .../config/murano.conf} | 2 - .../config/neutron.conf} | 2 - .../config/neutron/ml2_conf.ini} | 2 - .../{nova.conf.j2 => kolla/config/nova.conf} | 2 - .../config/octavia.conf} | 2 - .../config/placement.conf} | 2 - .../config/sahara.conf} | 2 - ansible/roles/kolla-openstack/vars/main.yml | 234 +--------- doc/source/multiple-environments.rst | 158 ++++++- etc/kayobe/kolla.yml | 73 +++ kayobe/tests/molecule/utils.py | 17 + ...-merge-kolla-configs-99773e2f0af2ea4b.yaml | 52 +++ requirements.txt | 1 + tools/test-molecule.sh | 6 + 46 files changed, 1226 insertions(+), 557 deletions(-) create mode 100644 ansible/roles/kolla-openstack/action_plugins/kolla_custom_config_info.py rename ansible/roles/kolla-openstack/templates/{aodh.conf.j2 => kolla/config/aodh.conf} (84%) rename ansible/roles/kolla-openstack/templates/{backup.my.cnf.j2 => kolla/config/backup.my.cnf} (85%) rename ansible/roles/kolla-openstack/templates/{barbican.conf.j2 => kolla/config/barbican.conf} (84%) rename ansible/roles/kolla-openstack/templates/{blazar.conf.j2 => kolla/config/blazar.conf} (84%) rename ansible/roles/kolla-openstack/templates/{ceilometer.conf.j2 => kolla/config/ceilometer.conf} (85%) rename ansible/roles/kolla-openstack/templates/{cinder.conf.j2 => kolla/config/cinder.conf} (84%) rename ansible/roles/kolla-openstack/templates/{cloudkitty.conf.j2 => kolla/config/cloudkitty.conf} (85%) rename ansible/roles/kolla-openstack/templates/{designate.conf.j2 => kolla/config/designate.conf} (85%) rename ansible/roles/kolla-openstack/templates/{galera.cnf.j2 => kolla/config/galera.cnf} (84%) rename ansible/roles/kolla-openstack/templates/{glance.conf.j2 => kolla/config/glance.conf} (84%) rename ansible/roles/kolla-openstack/templates/{global.conf.j2 => kolla/config/global.conf} (84%) rename ansible/roles/kolla-openstack/templates/{gnocchi.conf.j2 => kolla/config/gnocchi.conf} (84%) rename ansible/roles/kolla-openstack/templates/{grafana.ini.j2 => kolla/config/grafana.ini} (84%) rename ansible/roles/kolla-openstack/templates/{heat.conf.j2 => kolla/config/heat.conf} (84%) rename ansible/roles/kolla-openstack/templates/{ironic-inspector.conf.j2 => kolla/config/ironic-inspector.conf} (100%) rename ansible/roles/kolla-openstack/templates/{ironic.conf.j2 => kolla/config/ironic.conf} (98%) rename ansible/roles/kolla-openstack/templates/{keystone.conf.j2 => kolla/config/keystone.conf} (85%) rename ansible/roles/kolla-openstack/templates/{magnum.conf.j2 => kolla/config/magnum.conf} (84%) rename ansible/roles/kolla-openstack/templates/{manila.conf.j2 => kolla/config/manila.conf} (84%) rename ansible/roles/kolla-openstack/templates/{masakari.conf.j2 => kolla/config/masakari.conf} (84%) rename ansible/roles/kolla-openstack/templates/{multipath.conf.j2 => kolla/config/multipath.conf} (100%) rename ansible/roles/kolla-openstack/templates/{murano.conf.j2 => kolla/config/murano.conf} (84%) rename ansible/roles/kolla-openstack/templates/{neutron.conf.j2 => kolla/config/neutron.conf} (84%) rename ansible/roles/kolla-openstack/templates/{ml2_conf.ini.j2 => kolla/config/neutron/ml2_conf.ini} (98%) rename ansible/roles/kolla-openstack/templates/{nova.conf.j2 => kolla/config/nova.conf} (84%) rename ansible/roles/kolla-openstack/templates/{octavia.conf.j2 => kolla/config/octavia.conf} (84%) rename ansible/roles/kolla-openstack/templates/{placement.conf.j2 => kolla/config/placement.conf} (85%) rename ansible/roles/kolla-openstack/templates/{sahara.conf.j2 => kolla/config/sahara.conf} (84%) create mode 100644 releasenotes/notes/feature-merge-kolla-configs-99773e2f0af2ea4b.yaml diff --git a/ansible/inventory/group_vars/all/kolla b/ansible/inventory/group_vars/all/kolla index d11544748..8bd5a4d90 100644 --- a/ansible/inventory/group_vars/all/kolla +++ b/ansible/inventory/group_vars/all/kolla @@ -574,6 +574,16 @@ kolla_enable_vitrage: "no" kolla_enable_watcher: "no" kolla_enable_zun: "no" +############################################################################### +# Kolla custom config generation. + +# Feature flag to add $KAYOBE_CONFIG_PATH to the list of search paths used +# when searching for Kolla custom service configuration. Only has an effect in +# a multiple environments setup. This allows you to configure merging between +# your environment and the base layer. Defaults to true. Set to false for +# backwards compatibility. +kolla_openstack_custom_config_environment_merging_enabled: true + ############################################################################### # Passwords and credentials. diff --git a/ansible/kolla-openstack.yml b/ansible/kolla-openstack.yml index cd331f15e..b6daaf0c1 100644 --- a/ansible/kolla-openstack.yml +++ b/ansible/kolla-openstack.yml @@ -100,53 +100,6 @@ ipa_image_name: "ipa" pre_tasks: - block: - - name: Check whether Kolla extra configuration files exist - stat: - path: "{{ kayobe_env_config_path }}/kolla/config/{{ item.file }}" - get_checksum: False - get_md5: False - mime: False - register: stat_result - with_items: - - { name: aodh, file: aodh.conf } - - { name: barbican, file: barbican.conf } - - { name: blazar, file: blazar.conf } - - { name: ceilometer, file: ceilometer.conf } - - { name: cinder, file: cinder.conf } - - { name: cloudkitty, file: cloudkitty.conf } - - { name: designate, file: designate.conf } - - { name: glance, file: glance.conf } - - { name: global, file: global.conf } - - { name: gnocchi, file: gnocchi.conf } - - { name: grafana, file: grafana.ini } - - { name: heat, file: heat.conf } - - { name: inspector, file: ironic-inspector.conf } - - { name: ironic, file: ironic.conf } - - { name: keystone, file: keystone.conf } - - { name: magnum, file: magnum.conf } - - { name: manila, file: manila.conf } - - { name: mariabackup, file: backup.my.cnf } - - { name: mariadb, file: galera.cnf } - - { name: masakari, file: masakari.conf } - - { name: multipathd, file: multipath.conf } - - { name: murano, file: murano.conf } - - { name: neutron, file: neutron.conf } - - { name: neutron_ml2, file: neutron/ml2_conf.ini } - - { name: nova, file: nova.conf } - - { name: octavia, file: octavia.conf } - - { name: placement, file: placement.conf } - - { name: sahara, file: sahara.conf } - - - name: Initialise a fact containing extra configuration - set_fact: - kolla_extra_config: {} - - - name: Update a fact containing extra configuration - set_fact: - kolla_extra_config: "{{ kolla_extra_config | combine({item.item.name: lookup('template', '{{ item.stat.path }}')}) }}" - with_items: "{{ stat_result.results }}" - when: item.stat.exists - - name: Validate switch configuration for Neutron ML2 genericswitch driver fail: msg: > @@ -217,35 +170,11 @@ kolla_inspector_swift_auth: auth_type: none endpoint_override: "http://{% raw %}{{ api_interface_address }}{% endraw %}:{{ inspector_store_port }}" - # Extra free-form user-provided configuration. - kolla_extra_aodh: "{{ kolla_extra_config.aodh | default }}" - kolla_extra_barbican: "{{ kolla_extra_config.barbican | default }}" - kolla_extra_blazar: "{{ kolla_extra_config.blazar | default }}" - kolla_extra_ceilometer: "{{ kolla_extra_config.ceilometer | default }}" - kolla_extra_cinder: "{{ kolla_extra_config.cinder | default }}" - kolla_extra_cloudkitty: "{{ kolla_extra_config.cloudkitty | default }}" - kolla_extra_designate: "{{ kolla_extra_config.designate | default }}" - kolla_extra_glance: "{{ kolla_extra_config.glance | default }}" - kolla_extra_global: "{{ kolla_extra_config.global | default }}" - kolla_extra_gnocchi: "{{ kolla_extra_config.gnocchi | default }}" - kolla_extra_grafana: "{{ kolla_extra_config.grafana | default }}" - kolla_extra_heat: "{{ kolla_extra_config.heat | default }}" - kolla_extra_inspector: "{{ kolla_extra_config.inspector | default }}" - kolla_extra_ironic: "{{ kolla_extra_config.ironic | default }}" - kolla_extra_keystone: "{{ kolla_extra_config.keystone | default }}" - kolla_extra_magnum: "{{ kolla_extra_config.magnum | default }}" - kolla_extra_manila: "{{ kolla_extra_config.manila | default }}" - kolla_extra_mariabackup: "{{ kolla_extra_config.mariabackup | default }}" - kolla_extra_mariadb: "{{ kolla_extra_config.mariadb | default }}" - kolla_extra_masakari: "{{ kolla_extra_config.masakari | default }}" - kolla_extra_multipathd: "{{ kolla_extra_config.multipathd | default }}" - kolla_extra_murano: "{{ kolla_extra_config.murano | default }}" - kolla_extra_neutron: "{{ kolla_extra_config.neutron | default }}" - kolla_extra_neutron_ml2: "{{ kolla_extra_config.neutron_ml2 | default }}" - kolla_extra_nova: "{{ kolla_extra_config.nova | default }}" - kolla_extra_octavia: "{{ kolla_extra_config.octavia | default }}" - kolla_extra_placement: "{{ kolla_extra_config.placement | default }}" - kolla_extra_sahara: "{{ kolla_extra_config.sahara | default }}" - kolla_extra_config_path: "{{ kayobe_env_config_path }}/kolla/config" + kolla_openstack_custom_config_paths_extra_multi_env: + - "{{ kayobe_config_path }}" + - "{{ kayobe_env_config_path }}" + kolla_openstack_custom_config_paths_extra_legacy: + - "{{ kayobe_env_config_path }}" + kolla_openstack_custom_config_paths_extra: "{{ kolla_openstack_custom_config_paths_extra_multi_env if kolla_openstack_custom_config_environment_merging_enabled | bool else kolla_openstack_custom_config_paths_extra_legacy }}" kolla_libvirt_tls: "{{ compute_libvirt_enable_tls | bool }}" kolla_nova_libvirt_certificates_src: "{{ kayobe_env_config_path }}/certificates/libvirt" diff --git a/ansible/roles/image-download/tasks/main.yml b/ansible/roles/image-download/tasks/main.yml index 3f905b8bf..4d3f6dc1e 100644 --- a/ansible/roles/image-download/tasks/main.yml +++ b/ansible/roles/image-download/tasks/main.yml @@ -1,4 +1,9 @@ --- +- name: Ensure destination directory exists + file: + state: directory + path: "{{ image_download_dest | dirname }}" + - block: - block: - name: Fail if the checksum algorithm is not set diff --git a/ansible/roles/kolla-openstack/action_plugins/kolla_custom_config_info.py b/ansible/roles/kolla-openstack/action_plugins/kolla_custom_config_info.py new file mode 100644 index 000000000..f5ad15429 --- /dev/null +++ b/ansible/roles/kolla-openstack/action_plugins/kolla_custom_config_info.py @@ -0,0 +1,229 @@ +# Copyright (c) 2023 StackHPC Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from ansible.plugins.action import ActionBase +import os +from collections import defaultdict +import pathlib + +from wcmatch import glob + +def _dedup(xs): + # Deduplicate a list whilst maintaining order + seen = set() + result = [] + for x in xs: + if x not in seen: + seen.add(x) + result.append(x) + return result + +class ConfigCollector(object): + def __init__(self, include_globs, ignore_globs, destination, search_paths, + rules): + # This variable groups together files in the search paths with + # the same relative path, for example if the search paths were: + # - {{ kayobe_config_env_path }}/ + # - {{ kayobe_config_path }}/ + # - {{ role_path }}/templates/ + # and one of the include_globs matched nova.conf. You'd end up + # with the following files grouped together: + # - {{ kayobe_env_path }}/etc/kolla/nova.conf + # - {{ kayobe_config_path }}/etc/kolla/nova.conf + # - {{ role_path }}/templates/etc/kolla/nova.conf + # The key in the dictionary is the relative path of the file. The + # value is a list of absolute paths. This gets populated by the + # collect() method. + self.files_in_source = defaultdict(list) + # Set of files in destination. This is used to cleanup up files + # from a previous run that are no longer templated. Before any templating + # this variable is populated with all the files in the output directory. + # Each file that would be templated in the current run will be removed + # from this set as they are discovered. + self.files_in_destination = set() + # Determines which files are candidates for templating + self.include_globs = include_globs + # Some files are templated by external tasks. This is a list of files + # to not clean up. + self.ignore_globs = ignore_globs + # Where the files are being templated to + self.destination = destination + # Where to search for the source files + self.search_paths = search_paths + + # Rules to determine merging strategy when multiple files are found + # with the same relative path. Lower priority numbers win. + self.rules = sorted(rules, key=lambda d: d['priority']) + + def filter_files_in_destination(self): + ignored = set() + for f in self.files_in_destination: + for item in self.ignore_globs: + if not item["enabled"]: + continue + if glob.globmatch(f, item["glob"], flags=glob.GLOBSTAR): + ignored.add(f) + result = set(self.files_in_destination) - ignored + return list(result) + + def _find_matching_rule(self, relative_path): + # First match wins + for rule in self.rules: + if not rule.get('enabled', True): + continue + glob_ = rule["glob"] + if glob.globmatch(relative_path, glob_, flags=glob.GLOBSTAR): + return rule + + def partition_into_actions(self): + actions = { + "merge_yaml": [], + "merge_configs": [], + "template": [], + "copy": [], + "concat": [], + "create_dir": [], + "delete": [] + } + missing_directories = set() + files_to_delete = self.filter_files_in_destination() + + # Convert to absolute paths + files_to_delete = { + os.path.join(self.destination, x) for x in files_to_delete + } + + for relative_path, sources in self.files_in_source.items(): + found_match = False + destination = os.path.join(self.destination, relative_path) + # Don't delete any files we are templating + files_to_delete.discard(destination) + + dirname = os.path.dirname(destination) + if not os.path.exists(dirname): + missing_directories.add(dirname) + + rule = self._find_matching_rule(relative_path) + + if not rule: + continue + + if rule["strategy"] == 'copy': + copy = { + "src": sources[-1], + "dest": destination, + "params": rule.get('params', []) + } + actions["copy"].append(copy) + continue + + if rule["strategy"] == "merge_yaml": + merge_yaml = { + "sources": sources, + "dest": destination, + "params": rule.get('params', []) + } + actions["merge_yaml"].append(merge_yaml) + continue + + if rule["strategy"] == "merge_configs": + merge_configs = { + "sources": sources, + "dest": destination, + "params": rule.get('params', []) + } + actions["merge_configs"].append(merge_configs) + continue + + if rule["strategy"] == "concat": + concat = { + "sources": sources, + "dest": destination, + "params": rule.get('params', []) + } + actions["concat"].append(concat) + continue + + if rule["strategy"] == "template": + template = { + "src": sources[-1], + "dest": destination, + "params": rule.get('params', []) + } + actions["template"].append(template) + continue + + actions["create_dir"] = list(missing_directories) + # Sort by length so that subdirectories are created after the parent + actions["create_dir"].sort(key=len) + + actions["delete"] = list(files_to_delete) + return actions + + def collect(self): + for item in self.include_globs: + self._collect_source(item) + self._collect_destination(item) + + def _collect_source(self, item): + enabled = item.get("enabled", False) + if not isinstance(enabled, bool): + raise ValueError("Expecting a boolean: %s" % item) + if not enabled: + return + for search_path in self.search_paths: + abs_glob = os.path.join(search_path, item["glob"]) + files = glob.glob(abs_glob, flags=glob.GLOBSTAR) + for abs_path in files: + if not os.path.isfile(abs_path): + continue + relative_path = os.path.relpath(abs_path, search_path) + self.files_in_source[relative_path].append(abs_path) + + def _collect_destination(self, item): + abs_glob = os.path.join(self.destination, item["glob"]) + files = glob.glob(abs_glob, flags=glob.GLOBSTAR) + for abs_path in files: + if not os.path.isfile(abs_path): + continue + relative_path = os.path.relpath(abs_path, self.destination) + self.files_in_destination.add(relative_path) + +class ActionModule(ActionBase): + + def run(self, tmp=None, task_vars=None): + if task_vars is None: + task_vars = dict() + + result = super(ActionModule, self).run(tmp, task_vars) + + # This class never changes anything. We only collect the extra config + # files and group by action. + result['changed'] = False + + args = self._task.args + + collector = ConfigCollector( + destination=args.get("destination"), + ignore_globs=args.get("ignore_globs"), + include_globs=args.get("include_globs"), + rules=args.get("rules"), + search_paths=_dedup(args["search_paths"]) + ) + + collector.collect() + + result.update(collector.partition_into_actions()) + + return result diff --git a/ansible/roles/kolla-openstack/defaults/main.yml b/ansible/roles/kolla-openstack/defaults/main.yml index ed3fb18a4..5a7a96b09 100644 --- a/ansible/roles/kolla-openstack/defaults/main.yml +++ b/ansible/roles/kolla-openstack/defaults/main.yml @@ -1,6 +1,19 @@ --- -# Path to extra kolla-ansible configuration files. -kolla_extra_config_path: +# Ordered list of paths to default kolla-ansible configuration files. Least +# specific first. Default is search the role templates in +# templates/kolla/config. +kolla_openstack_custom_config_paths_default: + - "{{ role_path }}/templates" + +# Ordered list of paths to extra kolla-ansible configuration files. Least +# specific first. Default is an empty list. +kolla_openstack_custom_config_paths_extra: [] + +# Ordered list of paths to kolla-ansible configuration files. Least specific +# first. Default is a combination of +# kolla_openstack_custom_config_paths_default and +# kolla_openstack_custom_config_paths_extra. +kolla_openstack_custom_config_paths: "{{ kolla_openstack_custom_config_paths_default + kolla_openstack_custom_config_paths_extra }}" # Directory where Kolla custom configuration files will be installed. kolla_node_custom_config_path: /etc/kolla/config @@ -8,15 +21,307 @@ kolla_node_custom_config_path: /etc/kolla/config ############################################################################### # Global configuration. +# Deprecated: # Free form extra configuration to append to global.conf. kolla_extra_global: +############################################################################### +# Kolla custom config generation. + +# Default value for kolla_openstack_custom_config_include_globs. +kolla_openstack_custom_config_include_globs_default: + - enabled: '{{ kolla_enable_aodh | bool }}' + glob: aodh.conf + - enabled: '{{ kolla_enable_aodh | bool }}' + glob: aodh/** + - enabled: '{{ kolla_enable_barbican | bool }}' + glob: barbican.conf + - enabled: '{{ kolla_enable_barbican | bool }}' + glob: barbican/** + - enabled: '{{ kolla_enable_barbican | bool }}' + glob: barbican-api/** + - enabled: '{{ kolla_enable_blazar | bool }}' + glob: blazar.conf + - enabled: '{{ kolla_enable_blazar | bool }}' + glob: blazar/** + - enabled: '{{ kolla_enable_ceilometer | bool }}' + glob: ceilometer.conf + - enabled: '{{ kolla_enable_ceilometer | bool }}' + glob: ceilometer/** + - enabled: '{{ kolla_enable_cinder | bool }}' + glob: cinder.conf + - enabled: '{{ kolla_enable_cinder | bool }}' + glob: nfs_shares + - enabled: '{{ kolla_enable_cinder | bool }}' + glob: cinder/** + - enabled: '{{ kolla_enable_cloudkitty | bool }}' + glob: cloudkitty.conf + - enabled: '{{ kolla_enable_cloudkitty | bool }}' + glob: cloudkitty/** + - enabled: '{{ kolla_enable_designate | bool }}' + glob: designate.conf + - enabled: '{{ kolla_enable_designate | bool }}' + glob: designate/** + - enabled: '{{ kolla_enable_fluentd | bool }}' + glob: fluentd/**/*.conf + - enabled: '{{ kolla_enable_mariadb | bool }}' + glob: galera.cnf + - enabled: '{{ kolla_enable_glance | bool }}' + glob: glance*.conf + - enabled: '{{ kolla_enable_glance | bool }}' + glob: glance/** + - enabled: true + glob: global.conf + - enabled: '{{ kolla_enable_gnocchi | bool }}' + glob: gnocchi.conf + - enabled: '{{ kolla_enable_gnocchi | bool }}' + glob: gnocchi/** + - enabled: '{{ kolla_enable_grafana | bool }}' + glob: grafana.ini + - enabled: '{{ kolla_enable_grafana | bool }}' + glob: grafana/** + - enabled: '{{ kolla_enable_haproxy | bool }}' + glob: haproxy-config/** + - enabled: '{{ kolla_enable_haproxy | bool }}' + glob: haproxy/** + - enabled: '{{ kolla_enable_heat | bool }}' + glob: heat.conf + - enabled: '{{ kolla_enable_heat | bool }}' + glob: heat/** + - enabled: '{{ kolla_enable_horizon | bool }}' + glob: horizon/** + - enabled: '{{ kolla_enable_influxdb | bool }}' + glob: influx* + - enabled: '{{ kolla_enable_ironic | bool }}' + glob: ironic-inspector.conf + - enabled: '{{ kolla_enable_ironic | bool }}' + glob: ironic.conf + - enabled: '{{ kolla_enable_ironic | bool }}' + glob: ironic/** + - enabled: '{{ kolla_enable_keepalived | bool }}' + glob: keepalived/** + - enabled: '{{ kolla_enable_keystone | bool }}' + glob: keystone.conf + - enabled: '{{ kolla_enable_keystone | bool }}' + glob: keystone/** + - enabled: true + glob: kolla-toolbox/** + - enabled: '{{ kolla_enable_magnum | bool }}' + glob: magnum.conf + - enabled: '{{ kolla_enable_magnum | bool }}' + glob: magnum/** + - enabled: '{{ kolla_enable_manila | bool }}' + glob: manila.conf + - enabled: '{{ kolla_enable_manila | bool }}' + glob: manila/** + - enabled: '{{ kolla_enable_mariadb | bool }}' + glob: backup.my.cnf + - enabled: '{{ kolla_enable_mariadb | bool }}' + glob: mariadb/** + - enabled: '{{ kolla_enable_masakari | bool }}' + glob: masakari.conf + - enabled: '{{ kolla_enable_masakari | bool }}' + glob: masakari/** + - enabled: '{{ kolla_enable_multipathd | bool }}' + glob: multipath.conf + - enabled: '{{ kolla_enable_multipathd | bool }}' + glob: multipath/** + - enabled: '{{ kolla_enable_murano | bool }}' + glob: murano.conf + - enabled: '{{ kolla_enable_murano | bool }}' + glob: murano/** + - enabled: '{{ kolla_enable_neutron | bool }}' + glob: neutron.conf + - enabled: '{{ kolla_enable_neutron | bool }}' + glob: neutron/** + - enabled: '{{ kolla_enable_nova | bool }}' + glob: nova.conf + - enabled: '{{ kolla_enable_nova | bool }}' + glob: nova/** + - enabled: '{{ kolla_enable_nova | bool }}' + glob: nova_compute/** + - enabled: '{{ kolla_enable_octavia | bool }}' + glob: octavia.conf + - enabled: '{{ kolla_enable_octavia | bool }}' + glob: octavia/** + - enabled: '{{ kolla_enable_opensearch | bool }}' + glob: opensearch.yml + - enabled: '{{ kolla_enable_opensearch | bool }}' + glob: opensearch/** + - enabled: '{{ kolla_enable_placement | bool }}' + glob: placement.conf + - enabled: '{{ kolla_enable_placement | bool }}' + glob: placement/** + - enabled: '{{ kolla_enable_prometheus | bool }}' + glob: prometheus/** + - enabled: '{{ kolla_enable_sahara | bool }}' + glob: sahara.conf + - enabled: '{{ kolla_enable_sahara | bool }}' + glob: sahara/** + - enabled: '{{ kolla_enable_swift | bool }}' + glob: swift/** + +# Extra items to add to kolla_openstack_custom_config_include_globs_default +# to produce kolla_openstack_custom_config_include_globs. +kolla_openstack_custom_config_include_globs_extra: [] + +# List of dictionaries with the following keys: +# glob: a glob pattern. Any files matching this pattern will be copied to the +# the kolla custom config directory +# enabled: boolean to disable the glob. +# This determines the list of files to copy to the generated kolla config +# directory. +kolla_openstack_custom_config_include_globs: "{{ + kolla_openstack_custom_config_include_globs_default + + kolla_openstack_custom_config_include_globs_extra }}" + +# Kolla config generation rules. These operate on the list of files produced by +# applying kolla_openstack_custom_config_include_globs. Each of the paths in +# kolla_openstack_custom_config_paths is searched for files matching one of the +# globs. If a match is found, any files with the same relative path are grouped +# together. The rules determine what to do with these matching files e.g copy +# the most specific file without templating, merge the files with +# merge_configs, etc. +# List of dictionaries with the following keys: +# glob: A glob matching files for this rule to match on (relative to the +# search path) +# priority: The rules are processed in increasing priority order with the +# first rule matching taking effect. +# strategy: How to process the matched file. One of copy, concat, template, +# merge_configs, merge_yaml +# params: List of params to pass to module enacting the strategy +# Strategies: +# copy: Copy most specific file to kolla config without templating +# template: Template most specific file to kolla config +# concat: Concatenate files and copy the result to generated kolla config +# merge_configs: Use the merge_configs module to merge an ini file, before +# copying to the generated kolla-config. +# merge_yaml: Use the merge_yaml module to merge a file, before copying to +# the generated kolla-config. +kolla_openstack_custom_config_rules: "{{ kolla_openstack_custom_config_rules_default | rejectattr('glob', 'in', kolla_openstack_custom_config_rules_default_remove) + kolla_openstack_custom_config_rules_extra }}" + +# Whether to enable ini merging rules in +# kolla_openstack_custom_config_rules_default. Default is true. +kolla_openstack_custom_config_merge_configs_enabled: true + +# Whether to enable yaml merging rules in +# kolla_openstack_custom_config_rules_default. Default is true. +kolla_openstack_custom_config_merge_yaml_enabled: true + +# Default merge strategy for ini files in +# kolla_openstack_custom_config_rules_default. Default is concat. +kolla_openstack_custom_config_ini_merge_strategy_default: concat + +# Default value for kolla_openstack_custom_config_rules. +kolla_openstack_custom_config_rules_default: + - glob: horizon/themes/** + strategy: copy + priority: 1000 + - glob: ironic/ironic-agent.initramfs + strategy: copy + priority: 1000 + - glob: ironic/ironic-agent.kernel + strategy: copy + priority: 1000 + - glob: swift/*.builder + strategy: copy + priority: 1000 + - glob: swift/*.ring.gz + strategy: copy + priority: 1000 + - glob: '**/*.pem' + strategy: copy + priority: 1000 + # Exceptions for *.conf files which are not INI format + - glob: "**/collectd.conf" + strategy: template + priority: 1000 + - glob: designate/**/named.conf + strategy: template + priority: 1000 + - glob: designate/**/rndc.conf + strategy: template + priority: 1000 + - glob: "**/dnsmasq.conf" + strategy: template + priority: 1000 + - glob: fluentd/**/*.conf + strategy: template + priority: 1000 + - glob: hacluster-corosync/**/corosync.conf + strategy: template + priority: 1000 + - glob: horizon/**/horizon.conf + strategy: template + priority: 1000 + - glob: "**/*httpd.conf" + strategy: template + priority: 1000 + - glob: "**/influxdb.conf" + strategy: template + priority: 1000 + - glob: "**/keepalived.conf" + strategy: template + priority: 1000 + - glob: "**/multipath.conf" + strategy: template + priority: 1000 + - glob: "**/rabbitmq*.conf" + strategy: template + priority: 1000 + - glob: "**/*wsgi*.conf" + strategy: template + priority: 1000 + # INI files + - glob: "**/*.conf" + strategy: "{{ kolla_openstack_custom_config_ini_merge_strategy_default }}" + priority: 2000 + enabled: "{{ kolla_openstack_custom_config_merge_configs_enabled | bool }}" + - glob: "**/*.ini" + strategy: "{{ kolla_openstack_custom_config_ini_merge_strategy_default }}" + priority: 2000 + enabled: "{{ kolla_openstack_custom_config_merge_configs_enabled | bool }}" + - glob: "**/galera.cnf" + strategy: "{{ kolla_openstack_custom_config_ini_merge_strategy_default }}" + priority: 2000 + enabled: "{{ kolla_openstack_custom_config_merge_configs_enabled | bool }}" + - glob: "**/kafka.server.properties" + strategy: "{{ kolla_openstack_custom_config_ini_merge_strategy_default }}" + priority: 2000 + enabled: "{{ kolla_openstack_custom_config_merge_configs_enabled | bool }}" + - glob: "**/*my.cnf" + strategy: "{{ kolla_openstack_custom_config_ini_merge_strategy_default }}" + priority: 2000 + enabled: "{{ kolla_openstack_custom_config_merge_configs_enabled | bool }}" + # YAML files + - glob: "**/*.yml" + strategy: merge_yaml + priority: 2000 + enabled: "{{ kolla_openstack_custom_config_merge_yaml_enabled | bool }}" + - glob: "**/*.yaml" + strategy: merge_yaml + priority: 2000 + enabled: "{{ kolla_openstack_custom_config_merge_yaml_enabled | bool }}" + # Catch all. Fallback to templating to match legacy behaviour. + - glob: '**' + strategy: template + priority: 65535 + +# List of globs to filter from kolla_openstack_custom_config_rules_default. +# Default is an empty list. +kolla_openstack_custom_config_rules_default_remove: [] + +# Extra items to add to kolla_openstack_custom_config_rules_default +# to produce kolla_openstack_custom_config_rules. +kolla_openstack_custom_config_rules_extra: [] + ############################################################################### # Aodh configuration. # Whether to enable Aodh. -kolla_enable_aodh: +kolla_enable_aodh: false +# Deprecated: # Free form extra configuration to append to aodh.conf. kolla_extra_aodh: @@ -24,8 +329,9 @@ kolla_extra_aodh: # Barbican configuration. # Whether to enable Barbican. -kolla_enable_barbican: +kolla_enable_barbican: false +# Deprecated: # Free form extra configuration to append to barbican.conf. kolla_extra_barbican: @@ -33,8 +339,9 @@ kolla_extra_barbican: # Blazar configuration. # Whether to enable Blazar. -kolla_enable_blazar: +kolla_enable_blazar: false +# Deprecated: # Free form extra configuration to append to blazar.conf. kolla_extra_blazar: @@ -42,8 +349,9 @@ kolla_extra_blazar: # Ceilometer configuration. # Whether to enable Ceilometer. -kolla_enable_ceilometer: +kolla_enable_ceilometer: false +# Deprecated: # Free form extra configuration to append to ceilometer.conf. kolla_extra_ceilometer: @@ -51,8 +359,9 @@ kolla_extra_ceilometer: # cinder configuration. # Whether to enable cinder. -kolla_enable_cinder: +kolla_enable_cinder: false +# Deprecated: # Free form extra configuration to append to cinder.conf. kolla_extra_cinder: @@ -60,8 +369,9 @@ kolla_extra_cinder: # CloudKitty configuration. # Whether to enable CloudKitty. -kolla_enable_cloudkitty: +kolla_enable_cloudkitty: false +# Deprecated: # Free form extra configuration to append to cloudkitty.conf. kolla_extra_cloudkitty: @@ -69,17 +379,25 @@ kolla_extra_cloudkitty: # designate configuration. # Whether to enable designate. -kolla_enable_designate: +kolla_enable_designate: false +# Deprecated: # Free form extra configuration to append to designate.conf. kolla_extra_designate: +############################################################################### +# Fluentd configuration. + +# Whether to enable Fluentd. +kolla_enable_fluentd: false + ############################################################################### # Glance configuration. # Whether to enable Glance. -kolla_enable_glance: +kolla_enable_glance: false +# Deprecated: # Free form extra configuration to append to glance-api.conf and # glance-registry.conf. kolla_extra_glance: @@ -88,8 +406,9 @@ kolla_extra_glance: # Gnocchi configuration. # Whether to enable Gnocchi. -kolla_enable_gnocchi: +kolla_enable_gnocchi: false +# Deprecated: # Free form extra configuration to append to gnocchi.conf. kolla_extra_gnocchi: @@ -97,11 +416,12 @@ kolla_extra_gnocchi: # Grafana configuration. # Whether to enable Grafana. -kolla_enable_grafana: +kolla_enable_grafana: false # Name of the admin user for Grafana. grafana_local_admin_user_name: +# Deprecated: # Free form extra configuration to append to grafana.ini. kolla_extra_grafana: @@ -109,23 +429,15 @@ kolla_extra_grafana: # HAProxy configuration. # Whether to enable HAProxy. -kolla_enable_haproxy: - -############################################################################### -# Keystone configuration. - -# Whether to enable Keystone. -kolla_enable_keystone: - -# Free form extra configuration to append to Keystone.conf -kolla_extra_keystone: +kolla_enable_haproxy: false ############################################################################## # Heat configuration. # Whether to enable Heat. -kolla_enable_heat: +kolla_enable_heat: false +# Deprecated: # Free form extra configuration to append to heat.conf. kolla_extra_heat: @@ -133,19 +445,19 @@ kolla_extra_heat: # Horizon configuration. # Whether to enable Horizon. -kolla_enable_horizon: +kolla_enable_horizon: false ############################################################################### # InfluxDB configuration. # Whether to enable InfluxDB. -kolla_enable_influxdb: +kolla_enable_influxdb: false ############################################################################### # Ironic configuration. # Whether to enable Ironic. -kolla_enable_ironic: +kolla_enable_ironic: false # List of enabled Ironic drivers. kolla_ironic_drivers: @@ -252,6 +564,7 @@ kolla_ironic_provisioning_network: # List of additional append parameters for baremetal PXE boot. kolla_ironic_pxe_append_params: [] +# Deprecated: # Free form extra configuration to append to ironic.conf. kolla_extra_ironic: @@ -313,15 +626,33 @@ kolla_inspector_enable_swift: # store. kolla_inspector_swift_auth: {} +# Deprecated: # Free form extra configuration to append to ironic-inspector.conf. kolla_extra_inspector: +############################################################################### +# Keepalived configuration. + +# Whether to enable Keepalived. +kolla_enable_keepalived: false + +############################################################################### +# Keystone configuration. + +# Whether to enable Keystone. +kolla_enable_keystone: false + +# Deprecated: +# Free form extra configuration to append to Keystone.conf +kolla_extra_keystone: + ############################################################################### # Magnum configuration. # Whether to enable Magnum. -kolla_enable_magnum: +kolla_enable_magnum: false +# Deprecated: # Free form extra configuration to append to magnum.conf. kolla_extra_magnum: @@ -329,8 +660,9 @@ kolla_extra_magnum: # Mariabackup configuration. # Whether to enable Mariabackup. -kolla_enable_mariabackup: +kolla_enable_mariabackup: false +# Deprecated: # Free form extra configuration to append to backup.my.cnf. kolla_extra_mariabackup: @@ -338,8 +670,9 @@ kolla_extra_mariabackup: # MariaDB configuration. # Whether to enable MariaDB. -kolla_enable_mariadb: +kolla_enable_mariadb: false +# Deprecated: # Free form extra configuration to append to galera.cnf. kolla_extra_mariadb: @@ -347,14 +680,19 @@ kolla_extra_mariadb: # Manila configuration. # Whether to enable Manila. -kolla_enable_manila: +kolla_enable_manila: false + +# Deprecated: +# Free form extra configuration to append to manila.conf. +kolla_extra_manila: ############################################################################### # Masakari configuration. # Whether to enable Masakari. -kolla_enable_masakari: +kolla_enable_masakari: false +# Deprecated: # Free form extra configuration to append to masakari.conf. kolla_extra_masakari: @@ -362,7 +700,7 @@ kolla_extra_masakari: # Multipathd configuration. # Whether to enable Multipathd. -kolla_enable_multipathd: +kolla_enable_multipathd: false # Free form extra configuration to append to multipath.conf. kolla_extra_multipathd: @@ -371,8 +709,9 @@ kolla_extra_multipathd: # Murano configuration. # Whether to enable Murano. -kolla_enable_murano: +kolla_enable_murano: false +# Deprecated: # Free form extra configuration to append to murano.conf. kolla_extra_murano: @@ -380,7 +719,7 @@ kolla_extra_murano: # Neutron configuration. # Whether to enable Neutron. -kolla_enable_neutron: +kolla_enable_neutron: false # List of Neutron ML2 mechanism drivers to use. kolla_neutron_ml2_mechanism_drivers: [] @@ -416,9 +755,11 @@ kolla_neutron_ml2_generic_switches: [] # secret: not currently supported kolla_neutron_ml2_generic_switch_hosts: [] +# Deprecated: # Free form extra configuration to append to neutron.conf. kolla_extra_neutron: +# Deprecated: # Free form extra configuration to append to ml2_conf.ini. kolla_extra_neutron_ml2: @@ -426,11 +767,12 @@ kolla_extra_neutron_ml2: # Nova configuration. # Whether to enable Nova. -kolla_enable_nova: +kolla_enable_nova: false # Whether to enable Nova libvirt container. kolla_enable_nova_libvirt_container: +# Deprecated: # Free form extra configuration to append to nova.conf. kolla_extra_nova: @@ -442,23 +784,28 @@ kolla_libvirt_tls: kolla_nova_libvirt_certificates_src: ############################################################################### -# Octavia configuration. +# OpenSearch configuration. -# Whether to enable Octavia. -kolla_enable_octavia: +# Whether to enable OpenSearch. +kolla_enable_opensearch: false ############################################################################### -# OpenSearch configuration. +# Octavia configuration. + +# Whether to enable Octavia. +kolla_enable_octavia: false -# Whether to enable opensearch. -kolla_enable_opensearch: +# Deprecated: +# Free form extra configuration to append to octavia.conf +kolla_extra_octavia: ############################################################################### # Placement configuration. # Whether to enable placement. -kolla_enable_placement: +kolla_enable_placement: false +# Deprecated: # Free form extra configuration to append to placement.conf. kolla_extra_placement: @@ -466,14 +813,15 @@ kolla_extra_placement: # Prometheus configuration. # Whether to enable Prometheus. -kolla_enable_prometheus: +kolla_enable_prometheus: false ############################################################################### # Sahara configuration. # Whether to enable sahara. -kolla_enable_sahara: +kolla_enable_sahara: false +# Deprecated: # Free form extra configuration to append to sahara.conf. kolla_extra_sahara: @@ -481,4 +829,4 @@ kolla_extra_sahara: # Swift configuration. # Whether to enable swift. -kolla_enable_swift: +kolla_enable_swift: false diff --git a/ansible/roles/kolla-openstack/molecule/default/tests/test_default.py b/ansible/roles/kolla-openstack/molecule/default/tests/test_default.py index ebd6309f2..e6c0071f8 100644 --- a/ansible/roles/kolla-openstack/molecule/default/tests/test_default.py +++ b/ansible/roles/kolla-openstack/molecule/default/tests/test_default.py @@ -25,22 +25,15 @@ os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all') -@pytest.mark.parametrize( - 'path', - ['fluentd/filter', - 'fluentd/input', - 'fluentd/output']) -def test_service_config_directory(host, path): - path = os.path.join('/etc/kolla/config', path) - utils.test_directory(host, path) - - @pytest.mark.parametrize( 'path', ['aodh', 'cinder', 'cloudkitty', 'designate', + 'fluentd/filter', + 'fluentd/input', + 'fluentd/output', 'glance', 'grafana', 'heat', diff --git a/ansible/roles/kolla-openstack/molecule/enable-everything/destroy.yml b/ansible/roles/kolla-openstack/molecule/enable-everything/destroy.yml index 59c2929ce..681850a0e 100644 --- a/ansible/roles/kolla-openstack/molecule/enable-everything/destroy.yml +++ b/ansible/roles/kolla-openstack/molecule/enable-everything/destroy.yml @@ -25,3 +25,8 @@ until: docker_jobs.finished retries: 300 with_items: "{{ server.results }}" + + - name: Clean up temporary path + file: + path: "{{ lookup('env', 'MOLECULE_TEMP_PATH') | default('/tmp/molecule', true) }}" + state: absent diff --git a/ansible/roles/kolla-openstack/molecule/enable-everything/molecule.yml b/ansible/roles/kolla-openstack/molecule/enable-everything/molecule.yml index 264ef1cb1..45574e2e9 100644 --- a/ansible/roles/kolla-openstack/molecule/enable-everything/molecule.yml +++ b/ansible/roles/kolla-openstack/molecule/enable-everything/molecule.yml @@ -15,7 +15,16 @@ provisioner: inventory: group_vars: all: - kolla_extra_config_path: ${MOLECULE_TEMP_PATH:-/tmp}/molecule/kolla/config + kolla_extra_config_path: ${MOLECULE_TEMP_PATH:-/tmp/molecule}/kolla/config + kolla_openstack_custom_config_paths_extra: + - "{{ kolla_extra_config_path }}/../.." + kolla_openstack_custom_config_rules_extra: + - glob: aodh/dummy.yml + strategy: merge_yaml + priority: 1000 + - glob: aodh/dummy.ini + strategy: merge_configs + priority: 1000 kolla_enable_aodh: true kolla_extra_aodh: | [extra-aodh.conf] @@ -44,6 +53,7 @@ provisioner: kolla_extra_designate: | [extra-designate.conf] foo=bar + kolla_enable_fluentd: true kolla_enable_glance: true kolla_extra_glance: | [extra-glance.conf] @@ -72,8 +82,9 @@ provisioner: kolla_extra_inspector: | [extra-ironic-inspector.conf] foo=bar - kolla_inspector_ipa_kernel_path: ${MOLECULE_TEMP_PATH:-/tmp}/ironic-agent.kernel - kolla_inspector_ipa_ramdisk_path: ${MOLECULE_TEMP_PATH:-/tmp}/ironic-agent.initramfs + kolla_inspector_ipa_kernel_path: ${MOLECULE_TEMP_PATH:-/tmp/molecule}/ironic-agent.kernel + kolla_inspector_ipa_ramdisk_path: ${MOLECULE_TEMP_PATH:-/tmp/molecule}/ironic-agent.initramfs + kolla_enable_keepalived: true kolla_enable_keystone: true kolla_extra_keystone: | [extra-keystone.conf] @@ -119,7 +130,7 @@ provisioner: [extra-nova.conf] foo=bar kolla_libvirt_tls: true - kolla_nova_libvirt_certificates_src: ${MOLECULE_TEMP_PATH:-/tmp}/molecule/nova-libvirt/certificates + kolla_nova_libvirt_certificates_src: ${MOLECULE_TEMP_PATH:-/tmp/molecule}/nova-libvirt/certificates kolla_enable_octavia: true kolla_extra_octavia: | [extra-octavia.conf] diff --git a/ansible/roles/kolla-openstack/molecule/enable-everything/prepare.yml b/ansible/roles/kolla-openstack/molecule/enable-everything/prepare.yml index 8514e90f3..66be34434 100644 --- a/ansible/roles/kolla-openstack/molecule/enable-everything/prepare.yml +++ b/ansible/roles/kolla-openstack/molecule/enable-everything/prepare.yml @@ -26,6 +26,99 @@ - "{{ kolla_inspector_ipa_kernel_path }}" - "{{ kolla_inspector_ipa_ramdisk_path }}" + - name: Ensure parent directories of configuration files exist + file: + path: "{{ kolla_extra_config_path }}/{{ item }}" + state: directory + recurse: yes + delegate_to: localhost + run_once: true + with_items: + - neutron + - aodh + # To check that subdirectories are handled correctly + - prometheus/prometheus.yml.d + # Example of non-ini files that should be templated but not merged + - fluentd/input/ + + - name: Ensure extra INI configuration files exist + copy: + content: | + [extra-file-{{ item | basename }}] + bar=baz + dest: "{{ kolla_extra_config_path }}/{{ item }}" + run_once: true + delegate_to: localhost + loop: + - aodh.conf + - barbican.conf + - blazar.conf + - ceilometer.conf + - cinder.conf + - cloudkitty.conf + - designate.conf + - glance.conf + - global.conf + - gnocchi.conf + - grafana.ini + - heat.conf + - ironic.conf + - ironic-inspector.conf + - keystone.conf + - magnum.conf + - manila.conf + - murano.conf + - backup.my.cnf + - galera.cnf + - masakari.conf + - neutron.conf + - neutron/ml2_conf.ini + - nova.conf + - octavia.conf + - sahara.conf + - placement.conf + + - name: Ensure extra YAML configuration files exist + copy: + content: | + dummy_variable: 123 + dest: "{{ kolla_extra_config_path }}/{{ item }}" + run_once: true + delegate_to: localhost + loop: + - aodh/dummy.yml + - opensearch.yml + - prometheus/prometheus.yml.d/dummy.yml + + - name: Template extra custom config files + # These correspond to globs defined in molecule.yml + copy: + content: "{{ item.content }}" + dest: "{{ kolla_extra_config_path }}/{{ item.dest }}" + run_once: true + delegate_to: localhost + with_items: + - dest: aodh/dummy.ini + content: | + [dummy-section] + dummy_variable = 123 + - dest: fluentd/input/01-test.conf + content: | + + @type tail + path /grepme + pos_file /var/run/td-agent/rabbit.pos + tag infra.rabbit + enable_watch_timer false + + @type multiline + format_firstline /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}/ + format1 /^(?\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}) \[(?\w+)\] (?.*)/ + + + loop_control: + label: "{{ item.dest }}" + - name: Ensure nova libvirt certificates directory exists local_action: module: file diff --git a/ansible/roles/kolla-openstack/molecule/enable-everything/tests/test_default.py b/ansible/roles/kolla-openstack/molecule/enable-everything/tests/test_default.py index aabd19d10..664100501 100644 --- a/ansible/roles/kolla-openstack/molecule/enable-everything/tests/test_default.py +++ b/ansible/roles/kolla-openstack/molecule/enable-everything/tests/test_default.py @@ -19,46 +19,12 @@ import pytest import testinfra.utils.ansible_runner +import yaml testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all') - -@pytest.mark.parametrize( - 'path', - ['aodh', - 'barbican', - 'cinder', - 'cloudkitty', - 'designate', - 'fluentd/filter', - 'fluentd/input', - 'fluentd/output', - 'glance', - 'grafana', - 'heat', - 'horizon', - 'ironic', - 'keystone', - 'magnum', - 'manila', - 'mariadb', - 'masakari', - 'murano', - 'neutron', - 'nova', - 'nova/nova-libvirt', - 'octavia', - 'placement', - 'prometheus', - 'sahara', - 'swift']) -def test_service_config_directory(host, path): - path = os.path.join('/etc/kolla/config', path) - utils.test_directory(host, path) - - @pytest.mark.parametrize( 'path', ['aodh.conf', @@ -87,11 +53,44 @@ def test_service_config_directory(host, path): 'backup.my.cnf']) def test_service_ini_file(host, path): # TODO(mgoddard): Check more of config file contents. + # Tests config added with extra vars e.g kolla_extra_aodh. I.e the + # the internal role templates. path = os.path.join('/etc/kolla/config', path) extra_section = 'extra-%s' % os.path.basename(path) expected = {extra_section: {'foo': 'bar'}} utils.test_ini_file(host, path, expected=expected) +@pytest.mark.parametrize( + 'path', + ['aodh.conf', + 'barbican.conf', + 'cinder.conf', + 'cloudkitty.conf', + 'designate.conf', + 'galera.cnf', + 'glance.conf', + 'grafana.ini', + 'heat.conf', + 'ironic.conf', + 'ironic-inspector.conf', + 'keystone.conf', + 'magnum.conf', + 'manila.conf', + 'masakari.conf', + 'murano.conf', + 'neutron/ml2_conf.ini', + 'neutron.conf', + 'nova.conf', + 'octavia.conf', + 'placement.conf', + 'sahara.conf', + 'backup.my.cnf']) +def test_service_ini_file_extra_confs(host, path): + # Tests config added via extra config files + path = os.path.join('/etc/kolla/config', path) + extra_section = 'extra-file-%s' % os.path.basename(path) + expected = {extra_section: {'bar': 'baz'}} + utils.test_ini_file(host, path, expected=expected) @pytest.mark.parametrize( 'path', @@ -104,3 +103,30 @@ def test_service_non_ini_file(host, path): # TODO(mgoddard): Check config file contents. path = os.path.join('/etc/kolla/config', path) utils.test_file(host, path) + +@pytest.mark.parametrize( + 'path,regex', + [('fluentd/input/01-test.conf', 'grepme')]) +def test_service_non_ini_file_regex(host, path, regex): + path = os.path.join('/etc/kolla/config', path) + utils.test_regex_in_file(host, path, regex=regex) + +@pytest.mark.parametrize( + 'relative_path', + ['aodh/dummy.yml', + 'opensearch.yml', + 'prometheus/prometheus.yml.d/dummy.yml']) +def test_service_extra_yml_config(host, relative_path): + path = os.path.join('/etc/kolla/config', relative_path) + utils.test_file(host, path) + content = yaml.safe_load(host.file(path).content_string) + assert content["dummy_variable"] == 123 + +def test_service_extra_ini_config(host): + relative_path = "aodh/dummy.ini" + path = os.path.join('/etc/kolla/config', relative_path) + utils.test_file(host, path) + expected = { + "dummy-section": {"dummy_variable": "123"} + } + utils.test_ini_file(host, path, expected=expected) diff --git a/ansible/roles/kolla-openstack/tasks/config.yml b/ansible/roles/kolla-openstack/tasks/config.yml index 42f1775ea..ea999ad7b 100644 --- a/ansible/roles/kolla-openstack/tasks/config.yml +++ b/ansible/roles/kolla-openstack/tasks/config.yml @@ -1,48 +1,4 @@ --- -- name: Ensure the Kolla OpenStack configuration directories exist - file: - path: "{{ item.dest }}" - state: directory - mode: 0750 - with_items: "{{ kolla_openstack_custom_config }}" - when: item.enabled | bool - -- name: Ensure the Kolla OpenStack configuration files exist - template: - src: "{{ item.src }}" - dest: "{{ kolla_node_custom_config_path }}/{{ item.dest }}" - mode: 0640 - with_items: - - { src: aodh.conf.j2, dest: aodh.conf, enabled: "{{ kolla_enable_aodh }}" } - - { src: barbican.conf.j2, dest: barbican.conf, enabled: "{{ kolla_enable_barbican }}" } - - { src: blazar.conf.j2, dest: blazar.conf, enabled: "{{ kolla_enable_blazar }}" } - - { src: ceilometer.conf.j2, dest: ceilometer.conf, enabled: "{{ kolla_enable_ceilometer }}" } - - { src: cinder.conf.j2, dest: cinder.conf, enabled: "{{ kolla_enable_cinder }}" } - - { src: cloudkitty.conf.j2, dest: cloudkitty.conf, enabled: "{{ kolla_enable_cloudkitty }}" } - - { src: designate.conf.j2, dest: designate.conf, enabled: "{{ kolla_enable_designate }}" } - - { src: galera.cnf.j2, dest: galera.cnf, enabled: "{{ kolla_enable_mariadb }}" } - - { src: glance.conf.j2, dest: glance.conf, enabled: "{{ kolla_enable_glance }}" } - - { src: global.conf.j2, dest: global.conf, enabled: true } - - { src: gnocchi.conf.j2, dest: gnocchi.conf, enabled: "{{ kolla_enable_gnocchi }}" } - - { src: grafana.ini.j2, dest: grafana.ini, enabled: "{{ kolla_enable_grafana }}" } - - { src: heat.conf.j2, dest: heat.conf, enabled: "{{ kolla_enable_heat }}" } - - { src: ironic.conf.j2, dest: ironic.conf, enabled: "{{ kolla_enable_ironic }}" } - - { src: ironic-inspector.conf.j2, dest: ironic-inspector.conf, enabled: "{{ kolla_enable_ironic }}" } - - { src: keystone.conf.j2, dest: keystone.conf, enabled: "{{ kolla_enable_keystone }}" } - - { src: magnum.conf.j2, dest: magnum.conf, enabled: "{{ kolla_enable_magnum }}" } - - { src: manila.conf.j2, dest: manila.conf, enabled: "{{ kolla_enable_manila }}" } - - { src: backup.my.cnf.j2, dest: backup.my.cnf, enabled: "{{ kolla_enable_mariabackup }}" } - - { src: masakari.conf.j2, dest: masakari.conf, enabled: "{{ kolla_enable_masakari }}" } - - { src: ml2_conf.ini.j2, dest: neutron/ml2_conf.ini, enabled: "{{ kolla_enable_neutron }}" } - - { src: multipath.conf.j2, dest: multipath.conf, enabled: "{{ kolla_enable_multipathd }}" } - - { src: murano.conf.j2, dest: murano.conf, enabled: "{{ kolla_enable_murano }}" } - - { src: neutron.conf.j2, dest: neutron.conf, enabled: "{{ kolla_enable_neutron }}" } - - { src: nova.conf.j2, dest: nova.conf, enabled: "{{ kolla_enable_nova }}" } - - { src: octavia.conf.j2, dest: octavia.conf, enabled: "{{ kolla_enable_octavia }}" } - - { src: placement.conf.j2, dest: placement.conf, enabled: "{{ kolla_enable_placement }}" } - - { src: sahara.conf.j2, dest: sahara.conf, enabled: "{{ kolla_enable_sahara }}" } - when: item.enabled | bool - - name: Ensure ironic inspector kernel and ramdisk images are present vars: image_download_url: "{{ item.url }}" @@ -67,84 +23,110 @@ loop_control: label: "{{ item.dest }}" +- name: Make destination directory for Nova certificates + file: + state: directory + path: "{{ kolla_node_custom_config_path }}/nova/nova-libvirt/" + when: kolla_enable_nova | bool and kolla_libvirt_tls | bool + +- name: Copy client TLS certificates for Nova + vars: + certificates: + - clientcert.pem + - clientkey.pem + - cacert.pem + copy: + src: "{{ kolla_nova_libvirt_certificates_src }}/{{ item }}" + dest: "{{ kolla_node_custom_config_path }}/nova/nova-libvirt/{{ item }}" + loop: "{{ certificates if kolla_enable_nova | bool and kolla_libvirt_tls | bool else [] }}" + +- name: Copy server TLS certificates for Nova + vars: + certificates: + - servercert.pem + - serverkey.pem + copy: + src: "{{ kolla_nova_libvirt_certificates_src }}/{{ item }}" + dest: "{{ kolla_node_custom_config_path }}/nova/nova-libvirt/{{ item }}" + loop: "{{ certificates if kolla_enable_nova | bool and kolla_enable_nova_libvirt_container | bool and kolla_libvirt_tls | bool else [] }}" + # We support a fairly flexible mechanism of dropping config file templates into # an 'extra' config directory, and passing these through to kolla-ansible. We # look for matching files in the source directory to template, and also remove # any unexpected files from the destination, to support removal of files. -- name: Find extra configuration files - find: - path: "{{ item.src }}" - patterns: "{{ item.patterns }}" - recurse: true - with_items: "{{ kolla_openstack_custom_config }}" - register: find_src_result - delegate_to: localhost +- name: Collect details about custom config + kolla_custom_config_info: + destination: "{{ kolla_node_custom_config_path }}" + ignore_globs: "{{ _kolla_openstack_custom_config_cleanup_ignore_globs }}" + include_globs: "{{ kolla_openstack_custom_config_include_globs }}" + rules: "{{ kolla_openstack_custom_config_rules }}" + search_paths: "{{ kolla_openstack_custom_config_paths | product(['/kolla/config']) | map('join') | list }}" + register: kolla_custom_config_info -- name: Find previously generated extra configuration files - find: - path: "{{ item.dest }}" - patterns: "{{ item.patterns }}" - with_items: "{{ kolla_openstack_custom_config }}" - register: find_dest_result +- name: Print kolla_custom_config_info when using -v + debug: + msg: "{{ kolla_custom_config_info }}" + verbosity: 1 - name: Ensure extra configuration parent directories are present file: - path: "{{ item.0.item.dest }}/{{ item.1.path | relpath(item.0.item.src) | dirname }}" + path: "{{ item }}" + recurse: true state: directory - mode: 0750 - with_subelements: - - "{{ find_src_result.results }}" - - files - - skip_missing: true - when: - - item.0.item.enabled | bool - - item.1.path | basename not in item.0.item.ignore | default([]) + with_items: "{{ kolla_custom_config_info.create_dir }}" -- name: Ensure templated extra configuration files exist - template: - src: "{{ item.1.path }}" - dest: "{{ item.0.item.dest }}/{{ item.1.path | relpath(item.0.item.src) }}" - mode: 0640 - with_subelements: - - "{{ find_src_result.results }}" - - files - - skip_missing: true - when: - - item.0.item.enabled | bool - - item.1.path | basename not in item.0.item.ignore | default([]) - - item.1.path | basename not in item.0.item.untemplated | default([]) - - (item.1.path | dirname | relpath(item.0.item.src)).split("/")[0] not in item.0.item.untemplated_dirs | default([]) +- name: "Ensure extra configuration files exist (strategy: template)" + vars: + params: + src: "{{ item.src }}" + dest: "{{ item.dest }}" + mode: 0640 + template: "{{ params | combine(item.params) }}" + with_items: "{{ kolla_custom_config_info.template }}" -- name: Ensure untemplated extra configuration files exist - copy: - src: "{{ item.1.path }}" - dest: "{{ item.0.item.dest }}/{{ item.1.path | relpath(item.0.item.src) }}" - mode: 0640 - with_subelements: - - "{{ find_src_result.results }}" - - files - - skip_missing: true - when: - - item.0.item.enabled | bool - - item.1.path | basename not in item.0.item.ignore | default([]) - - (item.1.path | basename in item.0.item.untemplated | default([])) or - ((item.1.path | dirname | relpath(item.0.item.src)).split("/")[0] in item.0.item.untemplated_dirs | default([])) +- name: "Ensure extra configuration files exist (strategy: copy)" + vars: + params: + src: "{{ item.src }}" + dest: "{{ item.dest }}" + mode: 0640 + copy: "{{ params | combine(item.params) }}" + # NOTE: .copy is ambiguous with copy method + with_items: "{{ kolla_custom_config_info['copy'] }}" + +- name: "Ensure extra configuration files exist (strategy: merge_configs)" + vars: + params: + sources: "{{ item.sources }}" + dest: "{{ item.dest }}" + mode: 0640 + merge_configs: "{{ params | combine(item.params) }}" + with_items: "{{ kolla_custom_config_info.merge_configs }}" + +- name: "Ensure extra configuration files exist (strategy: merge_yaml)" + vars: + params: + sources: "{{ item.sources }}" + dest: "{{ item.dest }}" + mode: 0640 + merge_yaml: "{{ params | combine(item.params) }}" + with_items: "{{ kolla_custom_config_info.merge_yaml }}" + +- name: "Ensure extra configuration files exist (strategy: concat)" + vars: + params: + content: | + {%- for path in item.sources -%} + {{ lookup('template', path) }} + {%- endfor -%} + dest: "{{ item.dest }}" + mode: 0640 + copy: "{{ params | combine(item.params) }}" + with_items: "{{ kolla_custom_config_info.concat }}" - name: Ensure unnecessary extra configuration files are absent file: - path: "{{ item.1.path }}" + path: "{{ item }}" state: absent - with_subelements: - - "{{ find_dest_result.results }}" - - files - - skip_missing: true - when: - - not item.0.item.enabled or - item.1.path | basename not in src_files - - item.1.path | basename not in item.0.item.ignore | default([]) - vars: - # Find the source result that corresponds to this one. - src_result: "{{ (find_src_result.results | selectattr('item', 'equalto', item.0.item) | list)[0] }}" - # Find the list of files in the source. - src_files: "{{ src_result.files | map(attribute='path') | map('basename') | list }}" + with_items: "{{ kolla_custom_config_info.delete }}" diff --git a/ansible/roles/kolla-openstack/templates/aodh.conf.j2 b/ansible/roles/kolla-openstack/templates/kolla/config/aodh.conf similarity index 84% rename from ansible/roles/kolla-openstack/templates/aodh.conf.j2 rename to ansible/roles/kolla-openstack/templates/kolla/config/aodh.conf index 5d7d4c1e0..50df0b62c 100644 --- a/ansible/roles/kolla-openstack/templates/aodh.conf.j2 +++ b/ansible/roles/kolla-openstack/templates/kolla/config/aodh.conf @@ -1,5 +1,3 @@ -# {{ ansible_managed }} - {% if kolla_extra_aodh %} ####################### # Extra configuration diff --git a/ansible/roles/kolla-openstack/templates/backup.my.cnf.j2 b/ansible/roles/kolla-openstack/templates/kolla/config/backup.my.cnf similarity index 85% rename from ansible/roles/kolla-openstack/templates/backup.my.cnf.j2 rename to ansible/roles/kolla-openstack/templates/kolla/config/backup.my.cnf index 7213d824d..95d5cd004 100644 --- a/ansible/roles/kolla-openstack/templates/backup.my.cnf.j2 +++ b/ansible/roles/kolla-openstack/templates/kolla/config/backup.my.cnf @@ -1,5 +1,3 @@ -# {{ ansible_managed }} - {% if kolla_extra_mariabackup %} ####################### # Extra configuration diff --git a/ansible/roles/kolla-openstack/templates/barbican.conf.j2 b/ansible/roles/kolla-openstack/templates/kolla/config/barbican.conf similarity index 84% rename from ansible/roles/kolla-openstack/templates/barbican.conf.j2 rename to ansible/roles/kolla-openstack/templates/kolla/config/barbican.conf index 2a33517a8..eb4138b3d 100644 --- a/ansible/roles/kolla-openstack/templates/barbican.conf.j2 +++ b/ansible/roles/kolla-openstack/templates/kolla/config/barbican.conf @@ -1,5 +1,3 @@ -# {{ ansible_managed }} - {% if kolla_extra_barbican %} ####################### # Extra configuration diff --git a/ansible/roles/kolla-openstack/templates/blazar.conf.j2 b/ansible/roles/kolla-openstack/templates/kolla/config/blazar.conf similarity index 84% rename from ansible/roles/kolla-openstack/templates/blazar.conf.j2 rename to ansible/roles/kolla-openstack/templates/kolla/config/blazar.conf index aab01021d..f6c35e852 100644 --- a/ansible/roles/kolla-openstack/templates/blazar.conf.j2 +++ b/ansible/roles/kolla-openstack/templates/kolla/config/blazar.conf @@ -1,5 +1,3 @@ -# {{ ansible_managed }} - {% if kolla_extra_blazar %} ####################### # Extra configuration diff --git a/ansible/roles/kolla-openstack/templates/ceilometer.conf.j2 b/ansible/roles/kolla-openstack/templates/kolla/config/ceilometer.conf similarity index 85% rename from ansible/roles/kolla-openstack/templates/ceilometer.conf.j2 rename to ansible/roles/kolla-openstack/templates/kolla/config/ceilometer.conf index 5a9187e19..474bbc564 100644 --- a/ansible/roles/kolla-openstack/templates/ceilometer.conf.j2 +++ b/ansible/roles/kolla-openstack/templates/kolla/config/ceilometer.conf @@ -1,5 +1,3 @@ -# {{ ansible_managed }} - {% if kolla_extra_ceilometer %} ####################### # Extra configuration diff --git a/ansible/roles/kolla-openstack/templates/cinder.conf.j2 b/ansible/roles/kolla-openstack/templates/kolla/config/cinder.conf similarity index 84% rename from ansible/roles/kolla-openstack/templates/cinder.conf.j2 rename to ansible/roles/kolla-openstack/templates/kolla/config/cinder.conf index 9acf122df..5f0bc7cde 100644 --- a/ansible/roles/kolla-openstack/templates/cinder.conf.j2 +++ b/ansible/roles/kolla-openstack/templates/kolla/config/cinder.conf @@ -1,5 +1,3 @@ -# {{ ansible_managed }} - {% if kolla_extra_cinder %} ####################### # Extra configuration diff --git a/ansible/roles/kolla-openstack/templates/cloudkitty.conf.j2 b/ansible/roles/kolla-openstack/templates/kolla/config/cloudkitty.conf similarity index 85% rename from ansible/roles/kolla-openstack/templates/cloudkitty.conf.j2 rename to ansible/roles/kolla-openstack/templates/kolla/config/cloudkitty.conf index 75929ef15..c2d5a35b8 100644 --- a/ansible/roles/kolla-openstack/templates/cloudkitty.conf.j2 +++ b/ansible/roles/kolla-openstack/templates/kolla/config/cloudkitty.conf @@ -1,5 +1,3 @@ -# {{ ansible_managed }} - {% if kolla_extra_cloudkitty %} ####################### # Extra configuration diff --git a/ansible/roles/kolla-openstack/templates/designate.conf.j2 b/ansible/roles/kolla-openstack/templates/kolla/config/designate.conf similarity index 85% rename from ansible/roles/kolla-openstack/templates/designate.conf.j2 rename to ansible/roles/kolla-openstack/templates/kolla/config/designate.conf index 96386b077..632ecb9ff 100644 --- a/ansible/roles/kolla-openstack/templates/designate.conf.j2 +++ b/ansible/roles/kolla-openstack/templates/kolla/config/designate.conf @@ -1,5 +1,3 @@ -# {{ ansible_managed }} - {% if kolla_extra_designate %} ####################### # Extra configuration diff --git a/ansible/roles/kolla-openstack/templates/galera.cnf.j2 b/ansible/roles/kolla-openstack/templates/kolla/config/galera.cnf similarity index 84% rename from ansible/roles/kolla-openstack/templates/galera.cnf.j2 rename to ansible/roles/kolla-openstack/templates/kolla/config/galera.cnf index 55122ebb0..49f1df7dc 100644 --- a/ansible/roles/kolla-openstack/templates/galera.cnf.j2 +++ b/ansible/roles/kolla-openstack/templates/kolla/config/galera.cnf @@ -1,5 +1,3 @@ -# {{ ansible_managed }} - {% if kolla_extra_mariadb %} ####################### # Extra configuration diff --git a/ansible/roles/kolla-openstack/templates/glance.conf.j2 b/ansible/roles/kolla-openstack/templates/kolla/config/glance.conf similarity index 84% rename from ansible/roles/kolla-openstack/templates/glance.conf.j2 rename to ansible/roles/kolla-openstack/templates/kolla/config/glance.conf index 34692b6d3..837aef50f 100644 --- a/ansible/roles/kolla-openstack/templates/glance.conf.j2 +++ b/ansible/roles/kolla-openstack/templates/kolla/config/glance.conf @@ -1,5 +1,3 @@ -# {{ ansible_managed }} - {% if kolla_extra_glance %} ####################### # Extra configuration diff --git a/ansible/roles/kolla-openstack/templates/global.conf.j2 b/ansible/roles/kolla-openstack/templates/kolla/config/global.conf similarity index 84% rename from ansible/roles/kolla-openstack/templates/global.conf.j2 rename to ansible/roles/kolla-openstack/templates/kolla/config/global.conf index 5b27f3623..d6be39c4b 100644 --- a/ansible/roles/kolla-openstack/templates/global.conf.j2 +++ b/ansible/roles/kolla-openstack/templates/kolla/config/global.conf @@ -1,5 +1,3 @@ -# {{ ansible_managed }} - {% if kolla_extra_global %} ####################### # Extra configuration diff --git a/ansible/roles/kolla-openstack/templates/gnocchi.conf.j2 b/ansible/roles/kolla-openstack/templates/kolla/config/gnocchi.conf similarity index 84% rename from ansible/roles/kolla-openstack/templates/gnocchi.conf.j2 rename to ansible/roles/kolla-openstack/templates/kolla/config/gnocchi.conf index a9781dfca..a44f667eb 100644 --- a/ansible/roles/kolla-openstack/templates/gnocchi.conf.j2 +++ b/ansible/roles/kolla-openstack/templates/kolla/config/gnocchi.conf @@ -1,5 +1,3 @@ -# {{ ansible_managed }} - {% if kolla_extra_gnocchi %} ####################### # Extra configuration diff --git a/ansible/roles/kolla-openstack/templates/grafana.ini.j2 b/ansible/roles/kolla-openstack/templates/kolla/config/grafana.ini similarity index 84% rename from ansible/roles/kolla-openstack/templates/grafana.ini.j2 rename to ansible/roles/kolla-openstack/templates/kolla/config/grafana.ini index d1c34be6a..efcc475a3 100644 --- a/ansible/roles/kolla-openstack/templates/grafana.ini.j2 +++ b/ansible/roles/kolla-openstack/templates/kolla/config/grafana.ini @@ -1,5 +1,3 @@ -# {{ ansible_managed }} - {% if kolla_extra_grafana %} ####################### # Extra configuration diff --git a/ansible/roles/kolla-openstack/templates/heat.conf.j2 b/ansible/roles/kolla-openstack/templates/kolla/config/heat.conf similarity index 84% rename from ansible/roles/kolla-openstack/templates/heat.conf.j2 rename to ansible/roles/kolla-openstack/templates/kolla/config/heat.conf index 586fcc32a..70312bf44 100644 --- a/ansible/roles/kolla-openstack/templates/heat.conf.j2 +++ b/ansible/roles/kolla-openstack/templates/kolla/config/heat.conf @@ -1,5 +1,3 @@ -# {{ ansible_managed }} - {% if kolla_extra_heat %} ####################### # Extra configuration diff --git a/ansible/roles/kolla-openstack/templates/ironic-inspector.conf.j2 b/ansible/roles/kolla-openstack/templates/kolla/config/ironic-inspector.conf similarity index 100% rename from ansible/roles/kolla-openstack/templates/ironic-inspector.conf.j2 rename to ansible/roles/kolla-openstack/templates/kolla/config/ironic-inspector.conf diff --git a/ansible/roles/kolla-openstack/templates/ironic.conf.j2 b/ansible/roles/kolla-openstack/templates/kolla/config/ironic.conf similarity index 98% rename from ansible/roles/kolla-openstack/templates/ironic.conf.j2 rename to ansible/roles/kolla-openstack/templates/kolla/config/ironic.conf index cdf5e401d..49c5203e8 100644 --- a/ansible/roles/kolla-openstack/templates/ironic.conf.j2 +++ b/ansible/roles/kolla-openstack/templates/kolla/config/ironic.conf @@ -1,5 +1,3 @@ -# {{ ansible_managed }} - [DEFAULT] enabled_hardware_types: {{ kolla_ironic_enabled_hardware_types | join(',') }} diff --git a/ansible/roles/kolla-openstack/templates/keystone.conf.j2 b/ansible/roles/kolla-openstack/templates/kolla/config/keystone.conf similarity index 85% rename from ansible/roles/kolla-openstack/templates/keystone.conf.j2 rename to ansible/roles/kolla-openstack/templates/kolla/config/keystone.conf index 753e98bb8..47c9b31bf 100644 --- a/ansible/roles/kolla-openstack/templates/keystone.conf.j2 +++ b/ansible/roles/kolla-openstack/templates/kolla/config/keystone.conf @@ -1,4 +1,3 @@ -# {{ ansible_managed }} {% if kolla_extra_keystone %} ####################### # Extra configuration diff --git a/ansible/roles/kolla-openstack/templates/magnum.conf.j2 b/ansible/roles/kolla-openstack/templates/kolla/config/magnum.conf similarity index 84% rename from ansible/roles/kolla-openstack/templates/magnum.conf.j2 rename to ansible/roles/kolla-openstack/templates/kolla/config/magnum.conf index 03e40fc9e..574afcbb1 100644 --- a/ansible/roles/kolla-openstack/templates/magnum.conf.j2 +++ b/ansible/roles/kolla-openstack/templates/kolla/config/magnum.conf @@ -1,5 +1,3 @@ -# {{ ansible_managed }} - {% if kolla_extra_magnum %} ####################### # Extra configuration diff --git a/ansible/roles/kolla-openstack/templates/manila.conf.j2 b/ansible/roles/kolla-openstack/templates/kolla/config/manila.conf similarity index 84% rename from ansible/roles/kolla-openstack/templates/manila.conf.j2 rename to ansible/roles/kolla-openstack/templates/kolla/config/manila.conf index 63faff851..fa0a7f62c 100644 --- a/ansible/roles/kolla-openstack/templates/manila.conf.j2 +++ b/ansible/roles/kolla-openstack/templates/kolla/config/manila.conf @@ -1,5 +1,3 @@ -# {{ ansible_managed }} - {% if kolla_extra_manila %} ####################### # Extra configuration diff --git a/ansible/roles/kolla-openstack/templates/masakari.conf.j2 b/ansible/roles/kolla-openstack/templates/kolla/config/masakari.conf similarity index 84% rename from ansible/roles/kolla-openstack/templates/masakari.conf.j2 rename to ansible/roles/kolla-openstack/templates/kolla/config/masakari.conf index 5a6848c8f..3a36fc1ac 100644 --- a/ansible/roles/kolla-openstack/templates/masakari.conf.j2 +++ b/ansible/roles/kolla-openstack/templates/kolla/config/masakari.conf @@ -1,5 +1,3 @@ -# {{ ansible_managed }} - {% if kolla_extra_masakari %} ####################### # Extra configuration diff --git a/ansible/roles/kolla-openstack/templates/multipath.conf.j2 b/ansible/roles/kolla-openstack/templates/kolla/config/multipath.conf similarity index 100% rename from ansible/roles/kolla-openstack/templates/multipath.conf.j2 rename to ansible/roles/kolla-openstack/templates/kolla/config/multipath.conf diff --git a/ansible/roles/kolla-openstack/templates/murano.conf.j2 b/ansible/roles/kolla-openstack/templates/kolla/config/murano.conf similarity index 84% rename from ansible/roles/kolla-openstack/templates/murano.conf.j2 rename to ansible/roles/kolla-openstack/templates/kolla/config/murano.conf index 5d6af4b97..effbc2fd2 100644 --- a/ansible/roles/kolla-openstack/templates/murano.conf.j2 +++ b/ansible/roles/kolla-openstack/templates/kolla/config/murano.conf @@ -1,5 +1,3 @@ -# {{ ansible_managed }} - {% if kolla_extra_murano %} ####################### # Extra configuration diff --git a/ansible/roles/kolla-openstack/templates/neutron.conf.j2 b/ansible/roles/kolla-openstack/templates/kolla/config/neutron.conf similarity index 84% rename from ansible/roles/kolla-openstack/templates/neutron.conf.j2 rename to ansible/roles/kolla-openstack/templates/kolla/config/neutron.conf index 1cf183d8f..f1af1f710 100644 --- a/ansible/roles/kolla-openstack/templates/neutron.conf.j2 +++ b/ansible/roles/kolla-openstack/templates/kolla/config/neutron.conf @@ -1,5 +1,3 @@ -# {{ ansible_managed }} - {% if kolla_extra_neutron %} ####################### # Extra configuration diff --git a/ansible/roles/kolla-openstack/templates/ml2_conf.ini.j2 b/ansible/roles/kolla-openstack/templates/kolla/config/neutron/ml2_conf.ini similarity index 98% rename from ansible/roles/kolla-openstack/templates/ml2_conf.ini.j2 rename to ansible/roles/kolla-openstack/templates/kolla/config/neutron/ml2_conf.ini index 1e49ae17a..b6674fd1f 100644 --- a/ansible/roles/kolla-openstack/templates/ml2_conf.ini.j2 +++ b/ansible/roles/kolla-openstack/templates/kolla/config/neutron/ml2_conf.ini @@ -1,5 +1,3 @@ -# {{ ansible_managed }} - [ml2] {% if kolla_neutron_ml2_mechanism_drivers %} mechanism_drivers = {{ kolla_neutron_ml2_mechanism_drivers | join(',') }} diff --git a/ansible/roles/kolla-openstack/templates/nova.conf.j2 b/ansible/roles/kolla-openstack/templates/kolla/config/nova.conf similarity index 84% rename from ansible/roles/kolla-openstack/templates/nova.conf.j2 rename to ansible/roles/kolla-openstack/templates/kolla/config/nova.conf index 772261a59..b70bbb3ff 100644 --- a/ansible/roles/kolla-openstack/templates/nova.conf.j2 +++ b/ansible/roles/kolla-openstack/templates/kolla/config/nova.conf @@ -1,5 +1,3 @@ -# {{ ansible_managed }} - {% if kolla_extra_nova %} ####################### # Extra configuration diff --git a/ansible/roles/kolla-openstack/templates/octavia.conf.j2 b/ansible/roles/kolla-openstack/templates/kolla/config/octavia.conf similarity index 84% rename from ansible/roles/kolla-openstack/templates/octavia.conf.j2 rename to ansible/roles/kolla-openstack/templates/kolla/config/octavia.conf index 4210d8485..58131fef7 100644 --- a/ansible/roles/kolla-openstack/templates/octavia.conf.j2 +++ b/ansible/roles/kolla-openstack/templates/kolla/config/octavia.conf @@ -1,5 +1,3 @@ -# {{ ansible_managed }} - {% if kolla_extra_octavia %} ####################### # Extra configuration diff --git a/ansible/roles/kolla-openstack/templates/placement.conf.j2 b/ansible/roles/kolla-openstack/templates/kolla/config/placement.conf similarity index 85% rename from ansible/roles/kolla-openstack/templates/placement.conf.j2 rename to ansible/roles/kolla-openstack/templates/kolla/config/placement.conf index 287a13d03..cdbee0070 100644 --- a/ansible/roles/kolla-openstack/templates/placement.conf.j2 +++ b/ansible/roles/kolla-openstack/templates/kolla/config/placement.conf @@ -1,5 +1,3 @@ -# {{ ansible_managed }} - {% if kolla_extra_placement %} ####################### # Extra configuration diff --git a/ansible/roles/kolla-openstack/templates/sahara.conf.j2 b/ansible/roles/kolla-openstack/templates/kolla/config/sahara.conf similarity index 84% rename from ansible/roles/kolla-openstack/templates/sahara.conf.j2 rename to ansible/roles/kolla-openstack/templates/kolla/config/sahara.conf index 995cfe9bb..d850f95ec 100644 --- a/ansible/roles/kolla-openstack/templates/sahara.conf.j2 +++ b/ansible/roles/kolla-openstack/templates/kolla/config/sahara.conf @@ -1,5 +1,3 @@ -# {{ ansible_managed }} - {% if kolla_extra_sahara %} ####################### # Extra configuration diff --git a/ansible/roles/kolla-openstack/vars/main.yml b/ansible/roles/kolla-openstack/vars/main.yml index a72a7da4b..40b892b01 100644 --- a/ansible/roles/kolla-openstack/vars/main.yml +++ b/ansible/roles/kolla-openstack/vars/main.yml @@ -1,222 +1,16 @@ --- -# List of custom configuration directories. -# Each item is a dict containing the following items: -# src: Path to directory containing configuration file templates. -# dest: Path to directory in which generated files will be created. -# patterns: One or more file name patterns to match. -# enabled: Whether these files should be templated. -# ignore: Optional list of files to ignore. These files will not be copied to -# the destination, and will not be removed from the destination, even -# if disabled or unexpected. -kolla_openstack_custom_config: - # Aodh. - - src: "{{ kolla_extra_config_path }}/aodh" - dest: "{{ kolla_node_custom_config_path }}/aodh" - patterns: "*" - enabled: "{{ kolla_enable_aodh }}" - # Barbican. - - src: "{{ kolla_extra_config_path }}/barbican" - dest: "{{ kolla_node_custom_config_path }}/barbican" - patterns: "*" - enabled: "{{ kolla_enable_barbican }}" - # Blazar. - - src: "{{ kolla_extra_config_path }}/blazar" - dest: "{{ kolla_node_custom_config_path }}/blazar" - patterns: "*" - enabled: "{{ kolla_enable_blazar }}" - # Ceilometer. - - src: "{{ kolla_extra_config_path }}/ceilometer" - dest: "{{ kolla_node_custom_config_path }}/ceilometer" - patterns: "*" - enabled: "{{ kolla_enable_ceilometer }}" - # Cinder. - - src: "{{ kolla_extra_config_path }}/cinder" - dest: "{{ kolla_node_custom_config_path }}/cinder" - patterns: "*" - enabled: "{{ kolla_enable_cinder }}" - # CloudKitty. - - src: "{{ kolla_extra_config_path }}/cloudkitty" - dest: "{{ kolla_node_custom_config_path }}/cloudkitty" - patterns: "*" - enabled: "{{ kolla_enable_cloudkitty }}" - # Designate. - - src: "{{ kolla_extra_config_path }}/designate" - dest: "{{ kolla_node_custom_config_path }}/designate" - patterns: "*" - enabled: "{{ kolla_enable_designate }}" - # Fluentd filters. - - src: "{{ kolla_extra_config_path }}//fluentd/filter" - dest: "{{ kolla_node_custom_config_path }}/fluentd/filter" - patterns: "*.conf" - enabled: true - # Fluentd inputs. - - src: "{{ kolla_extra_config_path }}//fluentd/input" - dest: "{{ kolla_node_custom_config_path }}/fluentd/input" - patterns: "*.conf" - enabled: true - # Fluentd outputs. - - src: "{{ kolla_extra_config_path }}/fluentd/output" - dest: "{{ kolla_node_custom_config_path }}/fluentd/output" - patterns: "*.conf" - enabled: true - # Glance. - - src: "{{ kolla_extra_config_path }}/glance" - dest: "{{ kolla_node_custom_config_path }}/glance" - patterns: "*" - enabled: "{{ kolla_enable_glance }}" - # Gnocchi. - - src: "{{ kolla_extra_config_path }}/gnocchi" - dest: "{{ kolla_node_custom_config_path }}/gnocchi" - patterns: "*" - enabled: "{{ kolla_enable_gnocchi }}" - # Grafana. - - src: "{{ kolla_extra_config_path }}/grafana" - dest: "{{ kolla_node_custom_config_path }}/grafana" - patterns: "*" - enabled: "{{ kolla_enable_grafana }}" - # HAProxy. - - src: "{{ kolla_extra_config_path }}/haproxy" - dest: "{{ kolla_node_custom_config_path }}/haproxy" - patterns: "*" - enabled: "{{ kolla_enable_haproxy }}" - - src: "{{ kolla_extra_config_path }}/haproxy-config" - dest: "{{ kolla_node_custom_config_path }}/haproxy-config" - patterns: "*" - enabled: "{{ kolla_enable_haproxy }}" - # Heat. - - src: "{{ kolla_extra_config_path }}/heat" - dest: "{{ kolla_node_custom_config_path }}/heat" - patterns: "*" - enabled: "{{ kolla_enable_heat }}" - # Horizon. - - src: "{{ kolla_extra_config_path }}/horizon" - dest: "{{ kolla_node_custom_config_path }}/horizon" - patterns: "*" - enabled: "{{ kolla_enable_horizon }}" - untemplated_dirs: - # Do not attempt to template themes directory. - - "themes" - # InfluxDB. - - src: "{{ kolla_extra_config_path }}/" - dest: "{{ kolla_node_custom_config_path }}/" - patterns: "influx*" - enabled: "{{ kolla_enable_influxdb }}" - # Ironic. - - src: "{{ kolla_extra_config_path }}/ironic" - dest: "{{ kolla_node_custom_config_path }}/ironic" - patterns: "*" - enabled: "{{ kolla_enable_ironic }}" - ignore: - # These are templated by kayobe, so don't remove them. - - ironic-agent.initramfs - - ironic-agent.kernel - # Keystone. - - src: "{{ kolla_extra_config_path }}/keystone" - dest: "{{ kolla_node_custom_config_path }}/keystone" - patterns: "*" - enabled: "{{ kolla_enable_keystone }}" - # Keepalived. - - src: "{{ kolla_extra_config_path }}/keepalived" - dest: "{{ kolla_node_custom_config_path }}/keepalived" - patterns: "*" - enabled: "{{ kolla_enable_haproxy }}" - # Magnum. - - src: "{{ kolla_extra_config_path }}/magnum" - dest: "{{ kolla_node_custom_config_path }}/magnum" - patterns: "*" - enabled: "{{ kolla_enable_magnum }}" - # Manila. - - src: "{{ kolla_extra_config_path }}/manila" - dest: "{{ kolla_node_custom_config_path }}/manila" - patterns: "*" - enabled: "{{ kolla_enable_manila }}" - # MariaDB. - - src: "{{ kolla_extra_config_path }}/mariadb" - dest: "{{ kolla_node_custom_config_path }}/mariadb" - patterns: "*" - enabled: "{{ kolla_enable_mariadb }}" - # Masakari. - - src: "{{ kolla_extra_config_path }}/masakari" - dest: "{{ kolla_node_custom_config_path }}/masakari" - patterns: "*" - enabled: "{{ kolla_enable_masakari }}" - # Murano. - - src: "{{ kolla_extra_config_path }}/murano" - dest: "{{ kolla_node_custom_config_path }}/murano" - patterns: "*" - enabled: "{{ kolla_enable_murano }}" - # Neutron. - - src: "{{ kolla_extra_config_path }}/neutron" - dest: "{{ kolla_node_custom_config_path }}/neutron" - patterns: "*" - enabled: "{{ kolla_enable_neutron }}" - ignore: - # These are templated by kayobe, so don't remove them. - - ml2_conf.ini - # Nova. - - src: "{{ kolla_extra_config_path }}/nova" - dest: "{{ kolla_node_custom_config_path }}/nova" - patterns: "*" - enabled: "{{ kolla_enable_nova }}" - - src: "{{ kolla_extra_config_path }}/nova_compute" - dest: "{{ kolla_node_custom_config_path }}/nova_compute" - patterns: "*" - enabled: "{{ kolla_enable_nova }}" - - src: "{{ kolla_nova_libvirt_certificates_src }}" - dest: "{{ kolla_node_custom_config_path }}/nova/nova-libvirt" - patterns: - - clientcert.pem - - clientkey.pem - - cacert.pem - enabled: "{{ kolla_enable_nova | bool and kolla_libvirt_tls | bool }}" - untemplated: - - clientcert.pem - - clientkey.pem - - cacert.pem - - src: "{{ kolla_nova_libvirt_certificates_src }}" - dest: "{{ kolla_node_custom_config_path }}/nova/nova-libvirt" - patterns: - - servercert.pem - - serverkey.pem + +# An internal variable used for cleaning files from the generated config +# directory. Should not be exposed as we may wish to change the cleanup +# mechanism to something more robust. +_kolla_openstack_custom_config_cleanup_ignore_globs: + - glob: ironic/ironic-agent.initramfs + enabled: "{{ kolla_enable_ironic | bool }}" + - glob: ironic/ironic-agent.kernel + enabled: "{{ kolla_enable_ironic | bool }}" + - glob: nova/nova-libvirt/server*.pem enabled: "{{ kolla_enable_nova | bool and kolla_enable_nova_libvirt_container | bool and kolla_libvirt_tls | bool }}" - untemplated: - - servercert.pem - - serverkey.pem - # Octavia. - - src: "{{ kolla_extra_config_path }}/octavia" - dest: "{{ kolla_node_custom_config_path }}/octavia" - patterns: "*" - enabled: "{{ kolla_enable_octavia }}" - # OpenSearch. - - src: "{{ kolla_extra_config_path }}/opensearch" - dest: "{{ kolla_node_custom_config_path }}/opensearch" - patterns: "*" - enabled: "{{ kolla_enable_opensearch }}" - # Placement - - src: "{{ kolla_extra_config_path }}/placement" - dest: "{{ kolla_node_custom_config_path }}/placement" - patterns: "*" - enabled: "{{ kolla_enable_placement }}" - # Prometheus config - - src: "{{ kolla_extra_config_path }}/prometheus" - dest: "{{ kolla_node_custom_config_path }}/prometheus" - patterns: "*" - enabled: "{{ kolla_enable_prometheus }}" - # Sahara. - - src: "{{ kolla_extra_config_path }}/sahara" - dest: "{{ kolla_node_custom_config_path }}/sahara" - patterns: "*" - enabled: "{{ kolla_enable_sahara }}" - # Swift. - - src: "{{ kolla_extra_config_path }}/swift" - dest: "{{ kolla_node_custom_config_path }}/swift" - patterns: "*" - enabled: "{{ kolla_enable_swift }}" - untemplated: - # These are binary files, and should not be templated. - - account.builder - - account.ring.gz - - container.builder - - container.ring.gz - - object.builder - - object.ring.gz + - glob: nova/nova-libvirt/client*.pem + enabled: "{{ kolla_enable_nova | bool and kolla_libvirt_tls | bool }}" + - glob: nova/nova-libvirt/cacert.pem + enabled: "{{ kolla_enable_nova | bool and kolla_libvirt_tls | bool }}" diff --git a/doc/source/multiple-environments.rst b/doc/source/multiple-environments.rst index bf2097d98..86ba0e328 100644 --- a/doc/source/multiple-environments.rst +++ b/doc/source/multiple-environments.rst @@ -177,10 +177,8 @@ Kolla Configuration ------------------- In the Wallaby release, Kolla configuration was independent in each -environment. - -As of the Xena release, the following files support combining the -environment-specific and shared configuration file content: +environment. The Xena release supported combining environment-specific +and shared configuration file content for the following subset of the files: * ``kolla/config/bifrost/bifrost.yml`` * ``kolla/config/bifrost/dib.yml`` @@ -189,8 +187,156 @@ environment-specific and shared configuration file content: * ``kolla/kolla-build.conf`` * ``kolla/repos.yml`` or ``kolla/repos.yaml`` -Options in the environment-specific files take precedence over those in the -shared files. +The Antelope release expands upon this list to add support for combining Kolla +Ansible custom service configuration. This behaviour is configured using two +variables: + +* ``kolla_openstack_custom_config_include_globs``: Specifies which files are + considered when templating the Kolla configuration. The Kayobe defaults + are set using ``kolla_openstack_custom_config_include_globs_default``. + An optional list of additional globs can be set using: + ``kolla_openstack_custom_config_include_globs_extra``. These are + combined with ``kolla_openstack_custom_config_include_globs_default`` + to produce ``kolla_openstack_custom_config_include_globs``. + Each list entry is a dictionary with the following keys: + + * ``enabled``: Boolean which determines if this rule is used. Set to + ``false`` to disable the rule. + * ``glob``: String glob matching a relative path in the ``kolla/config`` + directory + + An example of such a rule: + + .. code-block:: yaml + + enabled: '{{ kolla_enable_aodh | bool }}' + glob: aodh/** + +* ``kolla_openstack_custom_config_rules``: List of rules that specify the + strategy to use when generating a particular file. The Kayobe defaults + are set using ``kolla_openstack_custom_config_rules_default``. + An optional list of additional rules can be set using: + ``kolla_openstack_custom_config_rules_extra``. These are + combined with ``kolla_openstack_custom_config_rules_default`` + to produce ``kolla_openstack_custom_config_rules``. + Each list entry is a dictionary with the format: + + * ``glob``: A glob matching files for this rule to match on (relative to the + search path) + * ``priority``: The rules are processed in increasing priority order with the + first rule matching taking effect + * ``strategy``: How to process the matched file. One of ``copy``, + ``concat``, ``template``, ``merge_configs``, ``merge_yaml`` + * ``params``: Optional list of additional params to pass to module enacting + the strategy + + An example of such a rule: + + .. code-block:: yaml + + glob: a/path/test.yml + strategy: merge_yaml + priority: 1000 + params: + extend_lists: true + +The Kayobe defaults fallback to using the ``template`` strategy, with a +priority of 65535. To override this behaviour configure a rule with a lower +priority e.g: + + .. code-block:: yaml + + glob: horizon/themes/** + strategy: copy + priority: 1000 + +The default INI merging strategy can be configured using: +``kolla_openstack_custom_config_ini_merge_strategy_default``. It defaults to ``concat`` +for backwards compatibility. An alternative strategy is ``merge_configs`` which will +merge the two INI files so that values set in the environment take precedence over values +set in the shared files. The caveat with the ``merge_configs`` strategy is that files +must template to valid INI. This is mostly an issue when you use raw Jinja +tags, for example: + + .. code-block:: ini + + [defaults] + {% raw %} + {% if inventory_hostname in 'compute' %} + foo=bar + {% else %} + foo=baz + {% endif %} + {% endraw %} + +After the first round of templating by Kayobe the raw tags are stripped. This leaves: + + .. code-block:: ini + + [defaults] + {% if inventory_hostname in 'compute' %} + foo=bar + {% else %} + foo=baz + {% endif %} + +Which isn't valid INI (due to the Jinja if blocks) and cannot be merged. In most cases +the templating can be refactored: + + .. code-block:: ini + + [defaults] + {% raw %} + foo={{ 'bar' if inventory_hostname in 'compute' else 'baz' }} + {% endraw %} + +Alternatively, you can use Kolla host or group variables. + +Disabling the default rules +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +There are some convenience variables to disable a subset of the +rules in ``kolla_openstack_custom_config_rules_default``: + +* ``kolla_openstack_custom_config_rules_default_remove``: Allows you remove + a rule by matching on the glob: + + .. code-block:: yaml + + kolla_openstack_custom_config_rules_default_remove: + - "**/*.ini" + +* ``kolla_openstack_custom_config_merge_configs_enabled``: Enables rules for + matching INI files. Default is ``true``. + +* ``kolla_openstack_custom_config_merge_yaml_enabled``: Enables rules for + matching YAML files. Default is ``true``. + +These allow you to more easily keep in sync with the upstream defaults. If +you had an override on ``kolla_openstack_custom_config_rules``, that +replicated most of ``kolla_openstack_custom_config_rules_default`` you'd have +to keep this in sync with the upstream kayobe defaults. + +Search paths +^^^^^^^^^^^^ + +When merging config files the following locations are "searched" to find +files with an identical relative path: + +- ``/kolla/config`` +- ``/kolla/config`` +- ``/templates/kolla/config`` + +Not all strategies use all of the files when generating the kolla config. +For instance, the copy strategy will use the first file found when searching +each of the paths. + +There is a feature flag: ``kolla_openstack_custom_config_environment_merging_enabled``, +that may be set to ``false`` to prevent Kayobe searching the shared files path +when merging configs. This is to replicate the legacy behaviour where the +environment Kolla custom service configuration was not merged with the base +layer. We still merge the files with Kayobe's defaults in the +``kolla-openstack`` role's internal templates. Managing Independent Environment Files ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/etc/kayobe/kolla.yml b/etc/kayobe/kolla.yml index 81e48c55b..19e54c809 100644 --- a/etc/kayobe/kolla.yml +++ b/etc/kayobe/kolla.yml @@ -447,6 +447,79 @@ #kolla_enable_watcher: #kolla_enable_zun: +############################################################################### +# Kolla custom config generation. + +# Feature flag to add $KAYOBE_CONFIG_PATH to the list of search paths used +# when searching for Kolla custom service configuration. Only has an effect in +# a multiple environments setup. This allows you to configure merging between +# your environment and the base layer. Defaults to true. Set to false to for +# backwards compatability. +#kolla_openstack_custom_config_environment_merging_enabled: + +# Default value for kolla_openstack_custom_config_include_globs. +#kolla_openstack_custom_config_include_globs_default: + +# Extra items to add to kolla_openstack_custom_config_include_globs_default +# to produce kolla_openstack_custom_config_include_globs. +#kolla_openstack_custom_config_include_globs_extra: + +# List of dictionaries with the following keys: +# glob: a glob pattern. Any files matching this pattern will be copied to the +# the kolla custom config directory +# enabled: boolean to disable the glob. +# This determines the list of files to copy to the generated kolla config +# directory. +#kolla_openstack_custom_config_include_globs: + +# Kolla config generation rules. These operate on the list of files produced by +# applying kolla_openstack_custom_config_include_globs. Each of the paths in +# kolla_openstack_custom_config_paths is searched for files matching one of the +# globs. If a match is found, any files with the same relative path are grouped +# together. The rules determine what to do with these matching files e.g copy +# the most specific file without templating, merge the files with +# merge_configs, etc. +# List of dictionaries with the following keys: +# glob: A glob matching files for this rule to match on (relative to the +# search path) +# priority: The rules are processed in increasing priority order with the +# first rule matching taking effect. +# strategy: How to process the matched file. One of copy, concat, template, +# merge_configs, merge_yaml +# params: List of params to pass to module enacting the strategy +# Strategies: +# copy: Copy most specific file to kolla config without templating +# template: Template most specific file to kolla config +# concat: Concatenate files and copy the result to generated kolla config +# merge_configs: Use the merge_configs module to merge an ini file, before +# copying to the generated kolla-config. +# merge_yaml: Use the merge_yaml module to merge a file, before copying to +# the generated kolla-config. +#kolla_openstack_custom_config_rules: + +# Whether to enable ini merging rules in +# kolla_openstack_custom_config_rules_default. Default is true. +#kolla_openstack_custom_config_merge_configs_enabled: + +# Whether to enable yaml merging rules in +# kolla_openstack_custom_config_rules_default. Default is true. +#kolla_openstack_custom_config_merge_yaml_enabled: + +# Default merge strategy for ini files in +# kolla_openstack_custom_config_rules_default. Default is concat. +#kolla_openstack_custom_config_ini_merge_strategy_default: + +# Default value for kolla_openstack_custom_config_rules. +#kolla_openstack_custom_config_rules_default: + +# List of globs to filter from kolla_openstack_custom_config_rules_default. +# Default is an empty list. +#kolla_openstack_custom_config_rules_default_remove: + +# Extra items to add to kolla_openstack_custom_config_rules_default +# to produce kolla_openstack_custom_config_rules. +#kolla_openstack_custom_config_rules_extra: + ############################################################################### # Passwords and credentials. diff --git a/kayobe/tests/molecule/utils.py b/kayobe/tests/molecule/utils.py index 07e311b7f..fa129e93d 100644 --- a/kayobe/tests/molecule/utils.py +++ b/kayobe/tests/molecule/utils.py @@ -12,6 +12,8 @@ # License for the specific language governing permissions and limitations # under the License. +import re + import configparser from io import StringIO @@ -53,6 +55,21 @@ def test_ini_file(host, path, owner='root', group='root', expected=None): assert parser.get(exp_section_name, exp_key) == exp_value +def test_regex_in_file(host, path, owner='root', group='root', regex=None): + """Test that a regex exists in file + + Validate that the file exists, has the correct ownership, format and + expected contents. + + :param regex to search for in file + """ + test_file(host, path, owner, group) + + matches = re.findall(regex, host.file(path).content_string) + + assert len(matches) > 0 + + def test_directory(host, path, owner='root', group='root'): """Test an expected directory. diff --git a/releasenotes/notes/feature-merge-kolla-configs-99773e2f0af2ea4b.yaml b/releasenotes/notes/feature-merge-kolla-configs-99773e2f0af2ea4b.yaml new file mode 100644 index 000000000..74f5773f0 --- /dev/null +++ b/releasenotes/notes/feature-merge-kolla-configs-99773e2f0af2ea4b.yaml @@ -0,0 +1,52 @@ +--- +features: + - | + Adds new functionality to merge Kolla custom service configuration in a + Kayobe environment with Kolla configuration in the base configuration + layer. +upgrade: + - | + Environment-specific Kolla custom service configuration is now merged with + Kolla configuration in the base configuration layer. Config options + duplicated in the base layer and the environment will need to be + de-deduplicated to avoid the config option showing up multiple times in the + generated output (although in general this should not be a problem). + + Set ``kolla_openstack_custom_config_environment_merging_enabled`` to + ``false`` to revert back to the previous behavior where only the config in + the environment was considered. +deprecations: + - | + Deprecates the following variables for removal in the Bobcat release: + + * ``kolla_extra_global`` + * ``kolla_extra_aodh`` + * ``kolla_extra_barbican`` + * ``kolla_extra_blazar`` + * ``kolla_extra_ceilometer`` + * ``kolla_extra_cinder`` + * ``kolla_extra_cloudkitty`` + * ``kolla_extra_designate`` + * ``kolla_extra_gnocchi`` + * ``kolla_extra_grafana`` + * ``kolla_extra_heat`` + * ``kolla_extra_ironic`` + * ``kolla_extra_inspector`` + * ``kolla_extra_keystone`` + * ``kolla_extra_magnum`` + * ``kolla_extra_mariabackup`` + * ``kolla_extra_mariadb`` + * ``kolla_extra_manila`` + * ``kolla_extra_masakari`` + * ``kolla_extra_murano`` + * ``kolla_extra_neutron`` + * ``kolla_extra_neutron_ml2`` + * ``kolla_extra_nova`` + * ``kolla_extra_octavia`` + * ``kolla_extra_placement`` + * ``kolla_extra_sahara`` + + Use of Kolla custom service configuration files in + ``etc/kayobe/kolla/config`` and + ``etc/kayobe/environments//kolla/config`` should be used + instead. diff --git a/requirements.txt b/requirements.txt index 752bc6d20..4ab09abd0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,4 +9,5 @@ selinux # MIT oslo.config>=5.2.0 # Apache-2.0 paramiko # LGPL jsonschema<5 # MIT +wcmatch>=8.2,<=9.0 # MIT hvac>=0.10.1 diff --git a/tools/test-molecule.sh b/tools/test-molecule.sh index 41b9f8a77..89b4a24c4 100755 --- a/tools/test-molecule.sh +++ b/tools/test-molecule.sh @@ -7,6 +7,12 @@ set -e molecules="$(find ansible/roles/ -name molecule -type d)" +PARENT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# FIXME: doesn't get passed through to linter. +export ANSIBLE_ACTION_PLUGINS="$PARENT/../kayobe/plugins/action:~/.ansible/plugins/action:/usr/share/ansible/plugins/action" +export ANSIBLE_FORCE_COLOR=True + failed=0 ran=0 for molecule in $molecules; do From 7267db3df63ba4fbff3c6004e48cfa5c7b05719c Mon Sep 17 00:00:00 2001 From: Kayobe Automation Date: Mon, 5 Jul 2021 17:47:52 +0100 Subject: [PATCH 2/5] Add cached plugin This is useful to cache expensive lookups. Change-Id: I0748f7bd3e6f7f91e35b2178acb6973e99710f88 (cherry picked from commit a46b3ebc8e5ffbc4f175fa23cd103d595ea1ffed) --- ansible/lookup_plugins/cached.py | 1 + requirements.txt | 1 + 2 files changed, 2 insertions(+) create mode 100644 ansible/lookup_plugins/cached.py diff --git a/ansible/lookup_plugins/cached.py b/ansible/lookup_plugins/cached.py new file mode 100644 index 000000000..69192580b --- /dev/null +++ b/ansible/lookup_plugins/cached.py @@ -0,0 +1 @@ +from ansible_cached_lookup import LookupModule diff --git a/requirements.txt b/requirements.txt index 4ab09abd0..cf7a6a735 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,3 +11,4 @@ paramiko # LGPL jsonschema<5 # MIT wcmatch>=8.2,<=9.0 # MIT hvac>=0.10.1 +ansible-cached-lookup<=2.0.0 # MIT From c3e3a3d8abaaa2893337af0985e277a434be075a Mon Sep 17 00:00:00 2001 From: Will Szumski Date: Fri, 23 Jul 2021 11:18:41 +0000 Subject: [PATCH 3/5] Kayobe environment dependencies Allows you to combine multiple environments by declaring any dependencies. Story: 2002009 Task: 42911 Change-Id: I4d9f96ec4cf3c6cd0d28dfe5ddb239d863498a72 (cherry picked from commit 0ae9e8d4892c5e7a0fd3477452b1a134f5dc11d0) --- ansible/inventory/group_vars/all/globals | 4 + ansible/kolla-ansible.yml | 8 +- ansible/kolla-bifrost.yml | 4 +- ansible/kolla-build.yml | 4 +- ansible/kolla-openstack.yml | 4 +- ansible/lookup_plugins/kayobe_environments.py | 19 ++ doc/source/multiple-environments.rst | 57 ++++++ kayobe/ansible.py | 66 +++---- kayobe/kolla_ansible.py | 25 +-- kayobe/plugins/lookup/environments.py | 49 +++++ kayobe/tests/unit/test_ansible.py | 185 ++++++++++++++---- kayobe/tests/unit/test_utils.py | 53 +++++ kayobe/utils.py | 98 ++++++++++ ...ronment-dependencies-22df2a38a653425b.yaml | 5 + requirements.txt | 2 + 15 files changed, 494 insertions(+), 89 deletions(-) create mode 100644 ansible/lookup_plugins/kayobe_environments.py create mode 100644 kayobe/plugins/lookup/environments.py create mode 100644 releasenotes/notes/environment-dependencies-22df2a38a653425b.yaml diff --git a/ansible/inventory/group_vars/all/globals b/ansible/inventory/group_vars/all/globals index 821d7cbbc..4f0fa02a2 100644 --- a/ansible/inventory/group_vars/all/globals +++ b/ansible/inventory/group_vars/all/globals @@ -16,6 +16,10 @@ kayobe_environment: "{{ lookup('env', 'KAYOBE_ENVIRONMENT') }}" # environment path appended if kayobe_environment is set. kayobe_env_config_path: "{{ kayobe_config_path ~ ('/environments/' ~ kayobe_environment if kayobe_environment else '') }}" +# Ordered list of paths containing kayobe_env_config_path and all its dependent +# environments. +kayobe_env_search_paths: "{{ query('cached', 'kayobe_environments') }}" + ############################################################################### # Remote path configuration (seed, seed-hypervisor and overcloud hosts). diff --git a/ansible/kolla-ansible.yml b/ansible/kolla-ansible.yml index 723459df1..5113dfe0b 100644 --- a/ansible/kolla-ansible.yml +++ b/ansible/kolla-ansible.yml @@ -91,9 +91,9 @@ kolla_external_fqdn_cert: "{{ kolla_config_path }}/certificates/haproxy.pem" kolla_internal_fqdn_cert: "{{ kolla_config_path }}/certificates/haproxy-internal.pem" kolla_ansible_passwords_path: "{{ kayobe_env_config_path }}/kolla/passwords.yml" - kolla_overcloud_inventory_search_paths: + kolla_overcloud_inventory_search_paths_static: - "{{ kayobe_config_path }}" - - "{{ kayobe_env_config_path }}" + kolla_overcloud_inventory_search_paths: "{{ kolla_overcloud_inventory_search_paths_static + kayobe_env_search_paths }}" kolla_ansible_certificates_path: "{{ kayobe_env_config_path }}/kolla/certificates" # NOTE: This differs from the default SELinux mode in kolla ansible, # which is permissive. The justification for using this mode is twofold: @@ -109,9 +109,9 @@ kolla_inspector_extra_kernel_options: "{{ inspector_extra_kernel_options }}" kolla_libvirt_tls: "{{ compute_libvirt_enable_tls | bool }}" kolla_enable_host_ntp: false - kolla_globals_paths_extra: + kolla_globals_paths_static: - "{{ kayobe_config_path }}" - - "{{ kayobe_env_config_path }}" + kolla_globals_paths_extra: "{{ kolla_globals_paths_static + kayobe_env_search_paths }}" - name: Generate Kolla Ansible host vars for the seed host hosts: seed diff --git a/ansible/kolla-bifrost.yml b/ansible/kolla-bifrost.yml index acda10647..b22c9eb42 100644 --- a/ansible/kolla-bifrost.yml +++ b/ansible/kolla-bifrost.yml @@ -14,6 +14,6 @@ kolla_bifrost_dnsmasq_dns_servers: "{{ resolv_nameservers | default([]) }}" kolla_bifrost_domain: "{{ resolv_domain | default }}" kolla_bifrost_download_ipa: "{{ not ipa_build_images | bool }}" - kolla_bifrost_config_paths_extra: + kolla_bifrost_config_paths_static: - "{{ kayobe_config_path }}" - - "{{ kayobe_env_config_path }}" + kolla_bifrost_config_paths_extra: "{{ kolla_bifrost_config_paths_static + kayobe_env_search_paths }}" diff --git a/ansible/kolla-build.yml b/ansible/kolla-build.yml index 388709eaa..3a415b8dd 100644 --- a/ansible/kolla-build.yml +++ b/ansible/kolla-build.yml @@ -7,7 +7,7 @@ - role: kolla kolla_install_epel: "{{ dnf_install_epel }}" - role: kolla-build - kolla_build_config_paths_extra: + kolla_build_config_paths_static: - "{{ kayobe_config_path }}" - - "{{ kayobe_env_config_path }}" + kolla_build_config_paths_extra: "{{ kolla_build_config_paths_static + kayobe_env_search_paths }}" kolla_base_tag: "{{ kolla_base_distro_version }}" diff --git a/ansible/kolla-openstack.yml b/ansible/kolla-openstack.yml index b6daaf0c1..a87e0eb45 100644 --- a/ansible/kolla-openstack.yml +++ b/ansible/kolla-openstack.yml @@ -170,9 +170,9 @@ kolla_inspector_swift_auth: auth_type: none endpoint_override: "http://{% raw %}{{ api_interface_address }}{% endraw %}:{{ inspector_store_port }}" - kolla_openstack_custom_config_paths_extra_multi_env: + kolla_openstack_custom_config_paths_extra_multi_env_static: - "{{ kayobe_config_path }}" - - "{{ kayobe_env_config_path }}" + kolla_openstack_custom_config_paths_extra_multi_env: "{{ kolla_openstack_custom_config_paths_extra_multi_env_static + kayobe_env_search_paths }}" kolla_openstack_custom_config_paths_extra_legacy: - "{{ kayobe_env_config_path }}" kolla_openstack_custom_config_paths_extra: "{{ kolla_openstack_custom_config_paths_extra_multi_env if kolla_openstack_custom_config_environment_merging_enabled | bool else kolla_openstack_custom_config_paths_extra_legacy }}" diff --git a/ansible/lookup_plugins/kayobe_environments.py b/ansible/lookup_plugins/kayobe_environments.py new file mode 100644 index 000000000..646587c34 --- /dev/null +++ b/ansible/lookup_plugins/kayobe_environments.py @@ -0,0 +1,19 @@ +# Copyright (c) 2023 StackHPC Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +__metaclass__ = type + +import kayobe.plugins.lookup.environments + +LookupModule = kayobe.plugins.lookup.environments.LookupModule diff --git a/doc/source/multiple-environments.rst b/doc/source/multiple-environments.rst index 86ba0e328..b3b655295 100644 --- a/doc/source/multiple-environments.rst +++ b/doc/source/multiple-environments.rst @@ -379,6 +379,63 @@ This would configure the external FQDN for the staging environment at ``staging-api.example.com``, while the production external FQDN would be at ``production-api.example.com``. +Environment Dependencies +------------------------ + +.. warning:: + + This is an experimental feature and is still subject to change whilst + the design is finalised. + +Since the Antelope 14.0.0 release, multiple environments can be layered on top +of each of each other by declaring dependencies in a ``.kayobe-environment`` +file located in the environment subdirectory. For example: + +.. code-block:: yaml + :caption: ``$KAYOBE_CONFIG_PATH/environments/environment-C/.kayobe-environment`` + + dependencies: + - environment-B + +.. code-block:: yaml + :caption: ``$KAYOBE_CONFIG_PATH/environments/environment-B/.kayobe-environment`` + + dependencies: + - environment-A + +Kayobe uses a dependency resolver to order these environments into a linear +chain. Any dependency cycles in will result in an error. Using the example +above the chain would be resolved to: + +.. code-block:: text + + C -> B -> A + +Where C is the environment with highest precedence. Kayobe will make sure to +include the inventory and extra-vars in an order matching this chain when +running any playbooks. + +Mixin environments +^^^^^^^^^^^^^^^^^^ + +Environment dependencies can be used to design fragments of re-useable +configuration that can be shared across multiple environments. For example: + +.. code-block:: yaml + :caption: ``$KAYOBE_CONFIG_PATH/environments/environment-A/.kayobe-environment`` + + dependencies: + - environment-mixin-1 + - environment-mixin-2 + - environment-mixin-3 + +In this case, each environment dependency could provide the configuration +necessary for one or more features. The mixin environments do not necessarily +need to define any dependencies between them, however Kayobe will perform a +topological sort to determine a suitable precedence. Care should be taken to +make sure that environments without an explicit ordering do not modify the same +variables. + Final Considerations -------------------- diff --git a/kayobe/ansible.py b/kayobe/ansible.py index f0e4e8473..fed57ea4f 100644 --- a/kayobe/ansible.py +++ b/kayobe/ansible.py @@ -84,34 +84,26 @@ def add_args(parser): "specific playbooks. \"all\" skips all playbooks") -def _get_kayobe_environment_path(parsed_args): - """Return the path to the Kayobe environment or None if not specified.""" - env_path = None - if parsed_args.environment: - # Specified via --environment or KAYOBE_ENVIRONMENT. - kc_environments = os.path.join(parsed_args.config_path, "environments") - env_path = os.path.join(kc_environments, parsed_args.environment) - return env_path - - -def _get_inventories_paths(parsed_args, env_path): +def _get_inventories_paths(parsed_args, env_paths): """Return the paths to the Kayobe inventories.""" default_inventory = utils.get_data_files_path("ansible", "inventory") inventories = [default_inventory] if parsed_args.inventory: inventories.extend(parsed_args.inventory) - else: - shared_inventory = os.path.join(parsed_args.config_path, "inventory") - if env_path: - if os.path.exists(shared_inventory): - inventories.append(shared_inventory) - env_inventory = os.path.join(env_path, "inventory") - if os.path.exists(env_inventory): - inventories.append(env_inventory) - else: - # Preserve existing behaviour: don't check if an inventory - # directory exists when no environment is specified + return inventories + + shared_inventory = os.path.join(parsed_args.config_path, "inventory") + if env_paths: + if os.path.exists(shared_inventory): inventories.append(shared_inventory) + else: + # Preserve existing behaviour: don't check if an inventory + # directory exists when no environment is specified + inventories.append(shared_inventory) + for env_path in env_paths or []: + env_inventory = os.path.join(env_path, "inventory") + if os.path.exists(env_inventory): + inventories.append(env_inventory) return inventories @@ -129,15 +121,18 @@ def _validate_args(parsed_args, playbooks): "use.") sys.exit(1) - env_path = _get_kayobe_environment_path(parsed_args) - if env_path: - result = utils.is_readable_dir(env_path) - if not result["result"]: - LOG.error("Kayobe environment %s is invalid: %s", - env_path, result["message"]) - sys.exit(1) + environment_finder = utils.EnvironmentFinder( + parsed_args.config_path, parsed_args.environment) + env_paths = environment_finder.ordered_paths() + for env_path in env_paths: + if env_path: + result = utils.is_readable_dir(env_path) + if not result["result"]: + LOG.error("Kayobe environment %s is invalid: %s", + env_path, result["message"]) + sys.exit(1) - inventories = _get_inventories_paths(parsed_args, env_path) + inventories = _get_inventories_paths(parsed_args, env_paths) for inventory in inventories: result = utils.is_readable_dir(inventory) if not result["result"]: @@ -184,12 +179,14 @@ def build_args(parsed_args, playbooks, if list_tasks or (parsed_args.list_tasks and list_tasks is None): cmd += ["--list-tasks"] cmd += vault.build_args(parsed_args, "--vault-password-file") - env_path = _get_kayobe_environment_path(parsed_args) - inventories = _get_inventories_paths(parsed_args, env_path) + environment_finder = utils.EnvironmentFinder( + parsed_args.config_path, parsed_args.environment) + env_paths = environment_finder.ordered_paths() + inventories = _get_inventories_paths(parsed_args, env_paths) for inventory in inventories: cmd += ["--inventory", inventory] vars_paths = [parsed_args.config_path] - if env_path: + for env_path in env_paths: vars_paths.append(env_path) vars_files = _get_vars_files(vars_paths) for vars_file in vars_files: @@ -438,7 +435,8 @@ def prune_galaxy_roles(parsed_args): def passwords_yml_exists(parsed_args): """Return whether passwords.yml exists in the kayobe configuration.""" - env_path = _get_kayobe_environment_path(parsed_args) + env_path = utils.get_kayobe_environment_path( + parsed_args.config_path, parsed_args.environment) path = env_path if env_path else parsed_args.config_path passwords_path = os.path.join(path, 'kolla', 'passwords.yml') return utils.is_readable_file(passwords_path)["result"] diff --git a/kayobe/kolla_ansible.py b/kayobe/kolla_ansible.py index 6af29f6fd..1d23647c1 100644 --- a/kayobe/kolla_ansible.py +++ b/kayobe/kolla_ansible.py @@ -78,22 +78,23 @@ def _get_inventory_paths(parsed_args, inventory_filename): else: paths = [os.path.join(parsed_args.kolla_config_path, "inventory", inventory_filename)] - - def append_path(directory): - candidate_path = os.path.join( - parsed_args.kolla_config_path, "extra-inventories", - directory) - if utils.is_readable_dir(candidate_path)["result"]: - paths.append(candidate_path) - # Inventory in the base layer is placed in the "kayobe" # directory. This means that you can't have an environment # called kayobe as it would conflict. - append_path("kayobe") - + environments = ["kayobe"] if parsed_args.environment: - append_path(parsed_args.environment) - + environments.append(parsed_args.environment) + else: + environment_finder = utils.EnvironmentFinder( + parsed_args.config_path, parsed_args.environment) + for environment in environment_finder.ordered(): + environments.append(environment) + for environment in environments: + candidate_path = os.path.join( + parsed_args.kolla_config_path, "extra-inventories", + environment) + if utils.is_readable_dir(candidate_path)["result"]: + paths.append(candidate_path) return paths diff --git a/kayobe/plugins/lookup/environments.py b/kayobe/plugins/lookup/environments.py new file mode 100644 index 000000000..cbeec811f --- /dev/null +++ b/kayobe/plugins/lookup/environments.py @@ -0,0 +1,49 @@ +# Copyright (c) 2023 StackHPC Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from ansible.errors import AnsibleError +from ansible.plugins.loader import lookup_loader +from ansible.plugins.lookup import LookupBase + +from kayobe.utils import EnvironmentFinder + +__version__ = "1.0.0" + +try: + from __main__ import display +except ImportError: + from ansible.utils.display import Display + + display = Display() + + +class LookupModule(LookupBase): + def run(self, terms, variables=None, **kwargs): + lookup = lookup_loader.get( + 'vars', loader=self._loader, templar=self._templar + ) + # Values in variables are untemplated, e.g: + # {{ lookup('env', 'KAYOBE_CONFIG_PATH') | default('/etc/kayobe', true) }} # noqa + environment = lookup.run( + ["kayobe_environment"], + variables=variables, default='')[0] + kayobe_config_path = lookup.run( + ["kayobe_config_path"], + variables=variables, default='')[0] + if not environment: + return [] + if not kayobe_config_path: + raise AnsibleError("kayobe_config_path is unset") + environment_finder = EnvironmentFinder(kayobe_config_path, environment) + return environment_finder.ordered_paths() diff --git a/kayobe/tests/unit/test_ansible.py b/kayobe/tests/unit/test_ansible.py index 4b34cd3c3..667bd6325 100644 --- a/kayobe/tests/unit/test_ansible.py +++ b/kayobe/tests/unit/test_ansible.py @@ -877,11 +877,14 @@ def test_multiple_inventory_args(self, mock_validate, mock_vars, mock_run): def test_multiple_inventories(self, mock_validate, mock_vars, mock_run, mock_exists): mock_vars.return_value = [] - # os.path.exists gets called three times: - # 1) shared inventory - # 2) environment inventory - # 3) ansible.cfg - mock_exists.side_effect = [True, True, False] + + def exists_replacement(path): + if path == "/etc/kayobe/inventory": + return True + if path == "/etc/kayobe/environments/test-env/inventory": + return True + return False + mock_exists.side_effect = exists_replacement parser = argparse.ArgumentParser() ansible.add_args(parser) vault.add_args(parser) @@ -907,12 +910,6 @@ def test_multiple_inventories(self, mock_validate, mock_vars, mock_run, "ANSIBLE_FILTER_PLUGINS": mock.ANY, "ANSIBLE_TEST_PLUGINS": mock.ANY, } - expected_calls = [ - mock.call("/etc/kayobe/inventory"), - mock.call("/etc/kayobe/environments/test-env/inventory"), - mock.call("/etc/kayobe/ansible.cfg"), - ] - self.assertListEqual(expected_calls, mock_exists.mock_calls) mock_run.assert_called_once_with(expected_cmd, check_output=False, quiet=False, env=expected_env) mock_vars.assert_called_once_with( @@ -925,11 +922,12 @@ def test_multiple_inventories(self, mock_validate, mock_vars, mock_run, def test_shared_inventory_only(self, mock_validate, mock_vars, mock_run, mock_exists): mock_vars.return_value = [] - # os.path.exists gets called three times: - # 1) shared inventory - # 2) environment inventory - # 3) ansible.cfg - mock_exists.side_effect = [True, False, False] + + def exists_replacement(path): + if path == "/etc/kayobe/inventory": + return True + return False + mock_exists.side_effect = exists_replacement parser = argparse.ArgumentParser() ansible.add_args(parser) vault.add_args(parser) @@ -954,12 +952,6 @@ def test_shared_inventory_only(self, mock_validate, mock_vars, mock_run, "ANSIBLE_FILTER_PLUGINS": mock.ANY, "ANSIBLE_TEST_PLUGINS": mock.ANY, } - expected_calls = [ - mock.call("/etc/kayobe/inventory"), - mock.call("/etc/kayobe/environments/test-env/inventory"), - mock.call("/etc/kayobe/ansible.cfg"), - ] - self.assertListEqual(expected_calls, mock_exists.mock_calls) mock_run.assert_called_once_with(expected_cmd, check_output=False, quiet=False, env=expected_env) mock_vars.assert_called_once_with( @@ -972,11 +964,13 @@ def test_shared_inventory_only(self, mock_validate, mock_vars, mock_run, def test_env_inventory_only(self, mock_validate, mock_vars, mock_run, mock_exists): mock_vars.return_value = [] - # os.path.exists gets called three times: - # 1) shared inventory - # 2) environment inventory - # 3) ansible.cfg - mock_exists.side_effect = [False, True, False] + # We only want it to find the inventory in the environment + + def exists_replacement(path): + if path == "/etc/kayobe/environments/test-env/inventory": + return True + return False + mock_exists.side_effect = exists_replacement parser = argparse.ArgumentParser() ansible.add_args(parser) vault.add_args(parser) @@ -1001,13 +995,138 @@ def test_env_inventory_only(self, mock_validate, mock_vars, mock_run, "ANSIBLE_FILTER_PLUGINS": mock.ANY, "ANSIBLE_TEST_PLUGINS": mock.ANY, } - expected_calls = [ - mock.call("/etc/kayobe/inventory"), - mock.call("/etc/kayobe/environments/test-env/inventory"), - mock.call("/etc/kayobe/ansible.cfg"), - ] - self.assertListEqual(expected_calls, mock_exists.mock_calls) mock_run.assert_called_once_with(expected_cmd, check_output=False, quiet=False, env=expected_env) mock_vars.assert_called_once_with( ["/etc/kayobe", "/etc/kayobe/environments/test-env"]) + + @mock.patch.object(utils.EnvironmentFinder, "ordered") + @mock.patch.object(os.path, "exists") + @mock.patch.object(utils, "run_command") + @mock.patch.object(ansible, "_get_vars_files") + @mock.patch.object(ansible, "_validate_args") + def test_multi_env_inventory_only(self, mock_validate, mock_vars, + mock_run, mock_exists, mock_finder): + mock_vars.return_value = [] + mock_finder.return_value = ["dependency-env", "test-env"] + + def exists_replacement(path): + if path == "/etc/kayobe/environments/test-env/inventory": + return True + if path == "/etc/kayobe/environments/dependency-env/inventory": + return True + return False + mock_exists.side_effect = exists_replacement + + parser = argparse.ArgumentParser() + ansible.add_args(parser) + vault.add_args(parser) + args = [ + "--environment", "test-env", + ] + parsed_args = parser.parse_args(args) + ansible.run_playbooks(parsed_args, ["playbook1.yml", "playbook2.yml"]) + expected_cmd = [ + "ansible-playbook", + "--inventory", utils.get_data_files_path("ansible", "inventory"), + "--inventory", "/etc/kayobe/environments/dependency-env/inventory", + "--inventory", "/etc/kayobe/environments/test-env/inventory", + "playbook1.yml", + "playbook2.yml", + ] + expected_env = { + "KAYOBE_CONFIG_PATH": "/etc/kayobe", + "KAYOBE_ENVIRONMENT": "test-env", + "ANSIBLE_ROLES_PATH": mock.ANY, + "ANSIBLE_COLLECTIONS_PATH": mock.ANY, + "ANSIBLE_ACTION_PLUGINS": mock.ANY, + "ANSIBLE_FILTER_PLUGINS": mock.ANY, + "ANSIBLE_TEST_PLUGINS": mock.ANY, + } + mock_run.assert_called_once_with(expected_cmd, check_output=False, + quiet=False, env=expected_env) + mock_vars.assert_called_once_with( + ["/etc/kayobe", + "/etc/kayobe/environments/dependency-env", + "/etc/kayobe/environments/test-env"] + ) + + @mock.patch.object(utils.EnvironmentFinder, "ordered") + @mock.patch.object(os.path, "exists") + @mock.patch.object(utils, "run_command") + @mock.patch.object(ansible, "_get_vars_files") + @mock.patch.object(ansible, "_validate_args") + def test_multi_env_vars(self, mock_validate, mock_vars, + mock_run, mock_exists, mock_finder): + + def get_vars_replacement(paths): + result = [] + for path in paths: + if path == "/etc/kayobe/environments/test-env": + result.extend( + ["vars-test-env-1.yml", "vars-test-env-2.yml"] + ) + continue + if path == "/etc/kayobe/environments/dependency-env": + result.extend( + ["vars-dependency-env-1.yml", + "vars-dependency-env-2.yml"] + ) + continue + if path == "/etc/kayobe": + result.extend( + ["vars-1.yml", "vars-2.yml"] + ) + continue + return result + mock_vars.side_effect = get_vars_replacement + + mock_finder.return_value = ["dependency-env", "test-env"] + + def exists_replacement(path): + if path == "/etc/kayobe/environments/test-env/inventory": + return True + if path == "/etc/kayobe/environments/dependency-env/inventory": + return True + return False + + mock_exists.side_effect = exists_replacement + + parser = argparse.ArgumentParser() + ansible.add_args(parser) + vault.add_args(parser) + args = [ + "--environment", "test-env", + ] + parsed_args = parser.parse_args(args) + ansible.run_playbooks(parsed_args, ["playbook1.yml", "playbook2.yml"]) + expected_cmd = [ + "ansible-playbook", + "--inventory", utils.get_data_files_path("ansible", "inventory"), + "--inventory", "/etc/kayobe/environments/dependency-env/inventory", + "--inventory", "/etc/kayobe/environments/test-env/inventory", + '-e', '@vars-1.yml', + '-e', '@vars-2.yml', + '-e', '@vars-dependency-env-1.yml', + '-e', '@vars-dependency-env-2.yml', + '-e', '@vars-test-env-1.yml', + '-e', '@vars-test-env-2.yml', + "playbook1.yml", + "playbook2.yml", + ] + expected_env = { + "KAYOBE_CONFIG_PATH": "/etc/kayobe", + "KAYOBE_ENVIRONMENT": "test-env", + "ANSIBLE_ROLES_PATH": mock.ANY, + "ANSIBLE_COLLECTIONS_PATH": mock.ANY, + "ANSIBLE_ACTION_PLUGINS": mock.ANY, + "ANSIBLE_FILTER_PLUGINS": mock.ANY, + "ANSIBLE_TEST_PLUGINS": mock.ANY, + } + mock_run.assert_called_once_with(expected_cmd, check_output=False, + quiet=False, env=expected_env) + mock_vars.assert_called_once_with( + ["/etc/kayobe", + "/etc/kayobe/environments/dependency-env", + "/etc/kayobe/environments/test-env"] + ) diff --git a/kayobe/tests/unit/test_utils.py b/kayobe/tests/unit/test_utils.py index b63ce1808..89263ef82 100644 --- a/kayobe/tests/unit/test_utils.py +++ b/kayobe/tests/unit/test_utils.py @@ -223,3 +223,56 @@ def test_intersect_limits_arg_and_cli_comma(self): def test_intersect_limits_arg_and_cli_colon(self): result = utils.intersect_limits("foo:bar", "baz") self.assertEqual("foo:bar:&baz", result) + + def test_environment_finder_with_single_environment(self): + finder = utils.EnvironmentFinder('/etc/kayobe', 'environment-A') + environments = finder.ordered() + expected = ["environment-A"] + self.assertEqual(expected, environments) + + expected = ["/etc/kayobe/environments/environment-A"] + paths = finder.ordered_paths() + self.assertEqual(expected, paths) + + @mock.patch.object(utils.EnvironmentFinder, "_read_metadata") + def test_environment_finder_with_dependency_chain(self, mock_yaml): + def yaml_replacement(path): + if path == ("/etc/kayobe/environments/environment-C/" + ".kayobe-environment"): + return {"dependencies": ["environment-A", "environment-B"]} + if path == ("/etc/kayobe/environments/environment-B/" + ".kayobe-environment"): + return {"dependencies": ["environment-A"]} + return {} + mock_yaml.side_effect = yaml_replacement + finder = utils.EnvironmentFinder('/etc/kayobe', 'environment-C') + result = finder.ordered() + expected = ["environment-A", "environment-B", "environment-C"] + self.assertEqual(expected, result) + + expected = ["/etc/kayobe/environments/environment-A", + "/etc/kayobe/environments/environment-B", + "/etc/kayobe/environments/environment-C"] + paths = finder.ordered_paths() + self.assertEqual(expected, paths) + + @mock.patch.object(utils.EnvironmentFinder, "_read_metadata") + def test_environment_finder_with_cycle(self, mock_yaml): + # The cycle is: C - B - C + def yaml_replacement(path): + if path == ("/etc/kayobe/environments/environment-C/" + ".kayobe-environment"): + return {"dependencies": ["environment-A", "environment-B"]} + if path == ("/etc/kayobe/environments/environment-B/" + ".kayobe-environment"): + return {"dependencies": ["environment-A", "environment-C"]} + return {} + mock_yaml.side_effect = yaml_replacement + finder = utils.EnvironmentFinder('/etc/kayobe', 'environment-C') + self.assertRaises(exception.Error, finder.ordered) + self.assertRaises(exception.Error, finder.ordered_paths) + + def test_environment_finder_no_environment(self): + finder = utils.EnvironmentFinder('/etc/kayobe', None) + self.assertEqual([], finder.ordered()) + self.assertEqual([], finder.ordered_paths()) diff --git a/kayobe/utils.py b/kayobe/utils.py index 727fd783a..023e64988 100644 --- a/kayobe/utils.py +++ b/kayobe/utils.py @@ -13,7 +13,9 @@ # under the License. import base64 +from collections import defaultdict import glob +import graphlib import logging import os import shutil @@ -265,3 +267,99 @@ def copy_dir(src, dest, exclude=None): copy_dir(src_path, dest_path) else: shutil.copy2(src_path, dest_path) + + +def get_kayobe_environment_path(base_path, environment): + """Return the path to the Kayobe environment or None if not specified.""" + env_path = None + if environment: + # Specified via --environment or KAYOBE_ENVIRONMENT. + kc_environments = os.path.join(base_path, "environments") + env_path = os.path.join(kc_environments, environment) + return env_path + + +class EnvironmentFinder(object): + """Dependency resolver for kayobe environments + + The constraints are specified via a .kayobe-environment file. + """ + + def __new__(cls, base_path, environment): + # Singleton instance so we don't have to resolve dependencies multiple + # times or pass round a single instance. + it = cls.__dict__.get("__it__") + if it is None: + it = {} + if (base_path, environment) in it: + return it[(base_path, environment)] + singleton = object.__new__(cls) + singleton._init(base_path, environment) + it[(base_path, environment)] = singleton + return singleton + + def _init(self, base_path, environment): + self._base_path = base_path + self._environment = environment + self._ordering = None + + @staticmethod + def _read_metadata(path): + if os.path.exists(path) and os.path.isfile(path): + metadata = read_yaml_file(path) + return metadata + return {} + + def _collect(self, environment, result, visited): + # Updates result to contain dependency graph + base = self._base_path + env_path = os.path.join(base, 'environments', environment) + dot_environment_path = os.path.join(env_path, '.kayobe-environment') + if dot_environment_path in visited: + return + visited.add(dot_environment_path) + metadata = EnvironmentFinder._read_metadata(dot_environment_path) + dependencies = metadata.get("dependencies", []) + if not isinstance(dependencies, list): + raise exception.Error(".kayobe-environment: dependencies field " + "should be a list") + result[environment] |= set(dependencies) + for dependency in dependencies: + if not isinstance(dependency, str): + raise exception.Error("Kayobe environment dependency items " + "should be strings") + self._collect(dependency, result, visited) + + def ordered(self): + """List of environments ordered by the constraints""" + environment = self._environment + if not environment: + return [] + if self._ordering is not None: + return self._ordering.copy() + graph = defaultdict(set) + self._collect(environment, graph, set()) + ts = graphlib.TopologicalSorter(graph) + try: + ordering = list(ts.static_order()) + except graphlib.CycleError as e: + # https://docs.python.org/3/library/graphlib.html#graphlib.CycleError + cycle = e.args[1] + raise exception.Error("You have created a cycle with your " + "environment dependencies. Please break " + "this cycle and try again. The cycle is: %s" + % cycle) + self._ordering = ordering if ordering else [environment] + return self._ordering.copy() + + def ordered_paths(self): + """Paths to each environment ordered by the constraints""" + result = [] + environments = self.ordered() + for environment in environments: + full_path = get_kayobe_environment_path( + self._base_path, + environment + ) + result.append(full_path) + return result diff --git a/releasenotes/notes/environment-dependencies-22df2a38a653425b.yaml b/releasenotes/notes/environment-dependencies-22df2a38a653425b.yaml new file mode 100644 index 000000000..cf71fcc47 --- /dev/null +++ b/releasenotes/notes/environment-dependencies-22df2a38a653425b.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Adds a experimental support for layering multiple environments using a + .kayobe-environment file. diff --git a/requirements.txt b/requirements.txt index cf7a6a735..8f453aa4d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,3 +12,5 @@ jsonschema<5 # MIT wcmatch>=8.2,<=9.0 # MIT hvac>=0.10.1 ansible-cached-lookup<=2.0.0 # MIT +# NOTE(wszusmki): Remove this when min python>=3.9 +graphlib-backport<2.0.0; python_version<"3.9" # PSF From 45a268114247071ba5eb4325ce45ff1fd0b02659 Mon Sep 17 00:00:00 2001 From: Will Szumski Date: Fri, 3 Nov 2023 17:42:49 +0000 Subject: [PATCH 4/5] Fallback to templating when only one source exists This is a backwards compatability improvement, since it was previously permitted to have a config file that templated to invalid yaml/ini. Change-Id: I53a0894cc64e83c7342a82ad987b7e5a4696ece2 Closes-Bug: #2042690 (cherry picked from commit 86d9a533e80c06a3b53067f60b72f9c998713b4a) --- .../action_plugins/kolla_custom_config_info.py | 18 ++++++++++++++++-- ...nly-one-source-exists-5eb19c0f6b8820d5.yaml | 7 +++++++ 2 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/fallback-to-templating-when-only-one-source-exists-5eb19c0f6b8820d5.yaml diff --git a/ansible/roles/kolla-openstack/action_plugins/kolla_custom_config_info.py b/ansible/roles/kolla-openstack/action_plugins/kolla_custom_config_info.py index f5ad15429..b58b679e1 100644 --- a/ansible/roles/kolla-openstack/action_plugins/kolla_custom_config_info.py +++ b/ansible/roles/kolla-openstack/action_plugins/kolla_custom_config_info.py @@ -77,13 +77,27 @@ def filter_files_in_destination(self): result = set(self.files_in_destination) - ignored return list(result) - def _find_matching_rule(self, relative_path): + def _find_matching_rule(self, relative_path, sources): # First match wins for rule in self.rules: if not rule.get('enabled', True): continue glob_ = rule["glob"] if glob.globmatch(relative_path, glob_, flags=glob.GLOBSTAR): + requires_merge = (rule["strategy"] in + ["merge_configs", "merge_yaml"]) + # Fallback to templating when there is only one source. This + # allows you to have config files that template to invalid + # yaml/ini. This was allowed prior to config merging so + # improves backwards compatibility. + if requires_merge and len(sources) == 1: + # The rule can be used again to match a different file + # so don't modify in place. + rule = rule.copy() + rule["strategy"] = 'template' + # Strip parameters as they may not be compatible with + # template module. + rule['params'] = {} return rule def partition_into_actions(self): @@ -114,7 +128,7 @@ def partition_into_actions(self): if not os.path.exists(dirname): missing_directories.add(dirname) - rule = self._find_matching_rule(relative_path) + rule = self._find_matching_rule(relative_path, sources) if not rule: continue diff --git a/releasenotes/notes/fallback-to-templating-when-only-one-source-exists-5eb19c0f6b8820d5.yaml b/releasenotes/notes/fallback-to-templating-when-only-one-source-exists-5eb19c0f6b8820d5.yaml new file mode 100644 index 000000000..6edcb55ca --- /dev/null +++ b/releasenotes/notes/fallback-to-templating-when-only-one-source-exists-5eb19c0f6b8820d5.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + When merging kolla config, fallback to templating when only one source file + exists. This allows you to use config that templates to invalid yaml/ini as + long as there isn't an environment override. This improves backwards + compatability where it was permitted to use such constructs. From 8e58753cf20faf8474bee268a2b05f8a48912d79 Mon Sep 17 00:00:00 2001 From: Will Szumski Date: Fri, 3 Nov 2023 17:42:35 +0000 Subject: [PATCH 5/5] Stop merging config when symlink points to same file This can result in duplicated configuration options. Change-Id: I4dd6ffae027345b8c1cc217a63e85f8af7fc9746 Closes-Bug: #2042689 (cherry picked from commit 2f4b45cd17ca348f699cdcf6a3e63e3d3cc35184) --- .../action_plugins/kolla_custom_config_info.py | 3 +++ ...erging-symlinks-to-identical-files-dfa3593fcf405510.yaml | 6 ++++++ 2 files changed, 9 insertions(+) create mode 100644 releasenotes/notes/stop-merging-symlinks-to-identical-files-dfa3593fcf405510.yaml diff --git a/ansible/roles/kolla-openstack/action_plugins/kolla_custom_config_info.py b/ansible/roles/kolla-openstack/action_plugins/kolla_custom_config_info.py index b58b679e1..13b8b26cd 100644 --- a/ansible/roles/kolla-openstack/action_plugins/kolla_custom_config_info.py +++ b/ansible/roles/kolla-openstack/action_plugins/kolla_custom_config_info.py @@ -133,6 +133,9 @@ def partition_into_actions(self): if not rule: continue + sources = map(os.path.realpath, sources) + sources = _dedup(sources) + if rule["strategy"] == 'copy': copy = { "src": sources[-1], diff --git a/releasenotes/notes/stop-merging-symlinks-to-identical-files-dfa3593fcf405510.yaml b/releasenotes/notes/stop-merging-symlinks-to-identical-files-dfa3593fcf405510.yaml new file mode 100644 index 000000000..cc4198f52 --- /dev/null +++ b/releasenotes/notes/stop-merging-symlinks-to-identical-files-dfa3593fcf405510.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Stop merging config when a symlink points to the same file. This + was a common pattern before the merging of kolla config was introduced + and results in duplicated config options.