From bea4241d064bfdb241612b6ff9da3397160fe940 Mon Sep 17 00:00:00 2001 From: Bartosz Bezak Date: Mon, 17 Aug 2026 14:05:46 +0200 Subject: [PATCH 1/4] cinder: remove volumev3 handling in upgrade Change-Id: Ie369e66ae76560cb52c28fd624404ca7ed1125fe Signed-off-by: Bartosz Bezak --- ansible/roles/cinder/tasks/upgrade.yml | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/ansible/roles/cinder/tasks/upgrade.yml b/ansible/roles/cinder/tasks/upgrade.yml index 7a872aecb3..5b395b1c04 100644 --- a/ansible/roles/cinder/tasks/upgrade.yml +++ b/ansible/roles/cinder/tasks/upgrade.yml @@ -20,20 +20,6 @@ - name: Reload cinder services ansible.builtin.import_tasks: reload.yml -# TODO(mnasiadka): Remove me in 2026.2 -- name: Remove cinderv3 catalog entries - vars: - _cinder_volumev3: - - name: "cinderv3" - type: "volumev3" - description: "Openstack Block Storage (deprecated)" - endpoints: [] - state: absent - service_ks_register_auth: "{{ openstack_cinder_auth }}" - service_ks_register_services: "{{ _cinder_volumev3 }}" - ansible.builtin.import_role: - name: service-ks-register - - name: Running Cinder online schema migration vars: cinder_api: "{{ cinder_services['cinder-api'] }}" From 0f4da209da6fa39739ffc7b7fb1a69dc967734b7 Mon Sep 17 00:00:00 2001 From: Michal Nasiadka Date: Fri, 21 Aug 2026 08:22:06 +0200 Subject: [PATCH 2/4] merge_configs: Add lstrip_blocks Closes-Bug: #2164718 Change-Id: If5b7f30d2467b61c3578d09992faf446b5d25f4e Signed-off-by: Michal Nasiadka --- ansible/action_plugins/merge_configs.py | 7 +++-- ansible/action_plugins/merge_yaml.py | 7 +++-- tests/test_merge_config.py | 41 +++++++++++++++++++++++++ tests/test_merge_yaml.py | 32 +++++++++++++++++++ 4 files changed, 83 insertions(+), 4 deletions(-) diff --git a/ansible/action_plugins/merge_configs.py b/ansible/action_plugins/merge_configs.py index 67a7da1cd5..09dc1d04e3 100644 --- a/ansible/action_plugins/merge_configs.py +++ b/ansible/action_plugins/merge_configs.py @@ -169,9 +169,12 @@ def read_config(self, source, config): os.path.join(self._loader._basedir, 'templates'), os.path.dirname(source), ] - self._templar.environment.loader.searchpath = searchpath + templar = self._templar.copy_with_new_env(searchpath=searchpath) - result = self._templar.template(template_data) + # lstrip_blocks avoids Jinja2 block tags (e.g. {% if %}) leaving + # behind leading whitespace that glues adjacent lines together. + result = templar.template( + template_data, overrides=dict(lstrip_blocks=True)) fakefile = StringIO(result) config.parse(fakefile) fakefile.close() diff --git a/ansible/action_plugins/merge_yaml.py b/ansible/action_plugins/merge_yaml.py index d2c15eb702..f3ba8597b3 100644 --- a/ansible/action_plugins/merge_yaml.py +++ b/ansible/action_plugins/merge_yaml.py @@ -109,9 +109,12 @@ def read_config(self, source): os.path.join(self._loader._basedir, 'templates'), os.path.dirname(source), ] - self._templar.environment.loader.searchpath = searchpath + templar = self._templar.copy_with_new_env(searchpath=searchpath) - template_data = self._templar.template(template_data) + # lstrip_blocks avoids Jinja2 block tags (e.g. {% if %}) leaving + # behind leading whitespace that glues adjacent lines together. + template_data = templar.template( + template_data, overrides=dict(lstrip_blocks=True)) result = yaml.safe_load(template_data) return result or {} diff --git a/tests/test_merge_config.py b/tests/test_merge_config.py index d64b2ae6b9..de2c98281e 100644 --- a/tests/test_merge_config.py +++ b/tests/test_merge_config.py @@ -15,8 +15,12 @@ from importlib.machinery import SourceFileLoader import os +import tempfile from io import StringIO + +from ansible.parsing.dataloader import DataLoader +from ansible.template import Templar from oslotest import base @@ -211,3 +215,40 @@ def test_merge_no_whitespace(self): parser.write(output) self.assertEqual(TESTC_NO_WHITESPACE, output.getvalue()) output.close() + + def test_read_config_lstrip_blocks(self): + # An indented Jinja2 block tag on its own line must not leak its + # leading whitespace into the following line, or the line gets + # parsed as a continuation of the previous key's value instead of + # a new key. + content = ( + "[DEFAULT]\n" + "key1 = value1\n" + " {% if true %}\n" + "key2 = value2\n" + " {% endif %}\n" + "key3 = value3\n" + ) + with tempfile.TemporaryDirectory() as tmpdir: + source = os.path.join(tmpdir, 'source.conf') + with open(source, 'w') as f: + f.write(content) + + loader = DataLoader() + loader.set_basedir(tmpdir) + action = object.__new__(merge_configs.ActionModule) + action._loader = loader + action._templar = Templar(loader=loader) + + config = merge_configs.OverrideConfigParser() + action.read_config(source, config) + + output = StringIO() + config.write(output) + self.assertEqual( + "[DEFAULT]\n" + "key1 = value1\n" + "key2 = value2\n" + "key3 = value3\n\n", + output.getvalue()) + output.close() diff --git a/tests/test_merge_yaml.py b/tests/test_merge_yaml.py index d6b74ae777..35de91d2cb 100644 --- a/tests/test_merge_yaml.py +++ b/tests/test_merge_yaml.py @@ -15,8 +15,11 @@ from importlib.machinery import SourceFileLoader import os +import tempfile from ansible.errors import AnsibleModuleError +from ansible.parsing.dataloader import DataLoader +from ansible.template import Templar from oslotest import base PROJECT_DIR = os.path.abspath(os.path.join(os. path.dirname(__file__), '../')) @@ -174,3 +177,32 @@ def test_merge_nested_extend_lists_mismatch_types(self): with self.assertRaisesRegex(AnsibleModuleError, "Failure merging key"): merge_yaml.Utils.update_nested_conf( initial_conf, extension, extend_lists=True) + + def test_read_config_lstrip_blocks(self): + # An indented Jinja2 block tag on its own line must not leak its + # leading whitespace into the following line, or the resulting + # YAML indentation becomes inconsistent with its siblings. + content = ( + "foo:\n" + " bar: baz\n" + " {% if true %}\n" + " extra: value\n" + " {% endif %}\n" + " qux: quux\n" + ) + with tempfile.TemporaryDirectory() as tmpdir: + source = os.path.join(tmpdir, 'source.yml') + with open(source, 'w') as f: + f.write(content) + + loader = DataLoader() + loader.set_basedir(tmpdir) + action = object.__new__(merge_yaml.ActionModule) + action._loader = loader + action._templar = Templar(loader=loader) + + result = action.read_config(source) + + self.assertEqual( + {'foo': {'bar': 'baz', 'extra': 'value', 'qux': 'quux'}}, + result) From 29c7d10583d380a80047e2bc313cc258f6370fc7 Mon Sep 17 00:00:00 2001 From: Pierre Riteau Date: Mon, 24 Aug 2026 10:34:22 +0200 Subject: [PATCH 3/4] Remove Prometheus v3 volume migration code Change-Id: Ifaa59d3883125d3cdcf1185685fefb92fb8d629f Signed-off-by: Pierre Riteau --- ansible/roles/prometheus/tasks/upgrade.yml | 38 ---------------------- 1 file changed, 38 deletions(-) diff --git a/ansible/roles/prometheus/tasks/upgrade.yml b/ansible/roles/prometheus/tasks/upgrade.yml index daca6860d7..fe8a5bec92 100644 --- a/ansible/roles/prometheus/tasks/upgrade.yml +++ b/ansible/roles/prometheus/tasks/upgrade.yml @@ -2,44 +2,6 @@ - name: Configure prometheus ansible.builtin.import_tasks: config.yml -# NOTE(mnasiadka): Remove me in 2025.2 -- name: Check for the existence of Prometheus v2 container volume - become: true - ansible.builtin.stat: - path: "{{ container_engine_volumes_path }}/prometheus_v2/_data" - register: prometheus_v2_data - -- name: Migrate Prometheus volume - when: prometheus_v2_data.stat.exists - block: - - name: Gracefully stop Prometheus - become: true - kolla_container: - name: "{{ prometheus_services['prometheus-server'].container_name }}" - action: "stop_and_remove_container" - common_options: "{{ docker_common_options }}" - ignore_missing: true - - - name: Create new Prometheus v3 volume - become: true - kolla_container: - action: "create_volume" - name: "prometheus_server" - common_options: "{{ docker_common_options }}" - - - name: Move _data from old to new volume - become: true - ansible.builtin.command: "mv {{ container_engine_volumes_path }}/prometheus_v2/_data {{ container_engine_volumes_path }}/prometheus_server/" - args: - removes: "{{ container_engine_volumes_path }}/prometheus_v2/_data" - - - name: Remove old Prometheus v2 volume - become: true - kolla_container: - action: "remove_volume" - name: "prometheus_v2" - common_options: "{{ docker_common_options }}" - - name: Check prometheus containers ansible.builtin.import_tasks: check-containers.yml From f1f2702bbe812e285f0766a5787159ba92fdce8b Mon Sep 17 00:00:00 2001 From: Bartosz Bezak Date: Tue, 25 Aug 2026 10:24:42 +0200 Subject: [PATCH 4/4] mariadb: Back up from the local database instance Mariabackup copies the data directory of mariadb_backup_host but connected through the load balancer, so backup_replica.sh could redirect it to another Galera member. The backup lock then applied to the wrong node, leaving the copied files unquiesced. Connect to api_interface_address and mariadb_port instead, and always use the local backup script. MariaDB 10.11.19 and 11.4.13 no longer tolerate the mismatch. mariadb_backup_target now has no effect, and full backups are named mysqlbackup-.qp.xbc.xbs.gz, as the restore docs describe. Closes-Bug: #2165011 Related-Bug: #2125124 Change-Id: I33ebd36c728ff50df8ebf13fc5af9882a0e71d23 Signed-off-by: Bartosz Bezak --- ansible/roles/mariadb/tasks/backup.yml | 4 +--- ansible/roles/mariadb/templates/backup.my.cnf.j2 | 4 ++-- ...-backup-connect-locally-9f4c2d1e7b3a0c65.yaml | 16 ++++++++++++++++ 3 files changed, 19 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/mariadb-backup-connect-locally-9f4c2d1e7b3a0c65.yaml diff --git a/ansible/roles/mariadb/tasks/backup.yml b/ansible/roles/mariadb/tasks/backup.yml index ff7ef7ec48..4e209661f0 100644 --- a/ansible/roles/mariadb/tasks/backup.yml +++ b/ansible/roles/mariadb/tasks/backup.yml @@ -10,12 +10,10 @@ register: container_facts - name: Taking database backup via Mariabackup - backup type {{ mariadb_backup_type }} - vars: - cmd: "{{ 'kolla_mariadb_backup.sh' if mariadb_backup_target == 'active' else 'kolla_mariadb_backup_replica.sh' }}" become: true kolla_container: action: "start_container" - command: "bash -c 'sudo -E kolla_set_configs && /usr/local/bin/{{ cmd }}'" + command: "bash -c 'sudo -E kolla_set_configs && /usr/local/bin/kolla_mariadb_backup.sh'" common_options: "{{ docker_common_options }}" detach: false # NOTE(mgoddard): Try to use the same image as the MariaDB server container diff --git a/ansible/roles/mariadb/templates/backup.my.cnf.j2 b/ansible/roles/mariadb/templates/backup.my.cnf.j2 index 0620e046ab..8d1ae25933 100644 --- a/ansible/roles/mariadb/templates/backup.my.cnf.j2 +++ b/ansible/roles/mariadb/templates/backup.my.cnf.j2 @@ -2,5 +2,5 @@ default-character-set=utf8 user={{ mariadb_backup_database_user }} password={{ mariadb_backup_database_password }} -host={{ database_address }} -port={{ database_port }} +host={{ api_interface_address }} +port={{ mariadb_port }} diff --git a/releasenotes/notes/mariadb-backup-connect-locally-9f4c2d1e7b3a0c65.yaml b/releasenotes/notes/mariadb-backup-connect-locally-9f4c2d1e7b3a0c65.yaml new file mode 100644 index 0000000000..6b54520d47 --- /dev/null +++ b/releasenotes/notes/mariadb-backup-connect-locally-9f4c2d1e7b3a0c65.yaml @@ -0,0 +1,16 @@ +--- +fixes: + - | + Fixes MariaDB backups failing since MariaDB 10.11.19 and 11.4.13. + Mariabackup copied the data directory of ``mariadb_backup_host`` but + connected through the load balancer, which could route it to a different + Galera member. The backup lock then applied to the wrong node. It now + connects to the MariaDB instance on the host it backs up. +upgrade: + - | + ``mariadb_backup_target`` no longer has any effect. Backups are always + taken from ``mariadb_backup_host``. + - | + Full backups are now named ``mysqlbackup-.qp.xbc.xbs.gz`` and + incrementals ``incremental--mysqlbackup-.qp.xbc.xbs.gz``, + matching the restore documentation. Existing backups remain restorable.