aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ansible/action_plugins/merge_configs.py7
-rw-r--r--ansible/action_plugins/merge_yaml.py7
-rw-r--r--ansible/module_utils/kolla_container_worker.py5
-rw-r--r--ansible/module_utils/kolla_docker_worker.py9
-rw-r--r--ansible/roles/cinder/templates/cinder.conf.j212
-rw-r--r--ansible/roles/etcd/tasks/lookup_leader.yml10
-rw-r--r--ansible/roles/grafana/defaults/main.yml5
-rw-r--r--ansible/roles/grafana/tasks/post_config.yml17
-rw-r--r--ansible/roles/grafana/templates/grafana.ini.j25
-rw-r--r--ansible/roles/haproxy-config/templates/haproxy_single_service_split.cfg.j22
-rw-r--r--ansible/roles/horizon/defaults/main.yml10
-rw-r--r--ansible/roles/keystone/tasks/distribute_fernet.yml6
-rw-r--r--ansible/roles/kolla_toolbox/handlers/main.yml4
-rw-r--r--ansible/roles/mariadb/tasks/backup.yml4
-rw-r--r--ansible/roles/mariadb/templates/backup.my.cnf.j24
-rw-r--r--ansible/roles/neutron/templates/ml2_conf.ini.j21
-rw-r--r--ansible/roles/nova/defaults/main.yml6
-rw-r--r--ansible/roles/prometheus/tasks/precheck.yml8
-rw-r--r--ansible/roles/rabbitmq/tasks/precheck.yml2
-rw-r--r--doc/source/reference/logging-and-monitoring/grafana-guide.rst3
-rw-r--r--doc/source/reference/networking/neutron.rst8
-rw-r--r--etc/kolla/globals.yml15
-rw-r--r--releasenotes/notes/bug-1889611-a89ddb10134081f4.yaml7
-rw-r--r--releasenotes/notes/bug-2161971-1f167e47f9740326.yaml9
-rw-r--r--releasenotes/notes/bug-2166048-9707508fca725bd7.yaml5
-rw-r--r--releasenotes/notes/fix-certificates-prechecks-5baa68cd11fbe61c.yaml8
-rw-r--r--releasenotes/notes/fix-cpuset-dimensions-comparison-f2c0e9aaab0f0342.yaml7
-rw-r--r--releasenotes/notes/fix-fernet-distribution-96a52af5a5641871.yaml6
-rw-r--r--releasenotes/notes/fix_prometheus_vault_prechecks-5335880cd18680dd.yaml10
-rw-r--r--releasenotes/notes/mariadb-backup-connect-locally-9f4c2d1e7b3a0c65.yaml16
-rw-r--r--releasenotes/notes/nova-scheduler-healthcheck-retries-12e681b16f6322aa.yaml13
-rw-r--r--tests/kolla_container_tests/test_docker_worker.py60
-rw-r--r--tests/kolla_container_tests/test_podman_worker.py6
-rw-r--r--tests/test_merge_config.py41
-rw-r--r--tests/test_merge_yaml.py32
35 files changed, 323 insertions, 47 deletions
diff --git a/ansible/action_plugins/merge_configs.py b/ansible/action_plugins/merge_configs.py
index 67a7da1cd..09dc1d04e 100644
--- a/ansible/action_plugins/merge_configs.py
+++ b/ansible/action_plugins/merge_configs.py
@@ -169,9 +169,12 @@ class ActionModule(action.ActionBase):
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 d2c15eb70..f3ba8597b 100644
--- a/ansible/action_plugins/merge_yaml.py
+++ b/ansible/action_plugins/merge_yaml.py
@@ -109,9 +109,12 @@ class ActionModule(action.ActionBase):
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/ansible/module_utils/kolla_container_worker.py b/ansible/module_utils/kolla_container_worker.py
index 4c7bedffc..bf0c20b25 100644
--- a/ansible/module_utils/kolla_container_worker.py
+++ b/ansible/module_utils/kolla_container_worker.py
@@ -316,7 +316,10 @@ class ContainerWorker(ABC):
def compare_labels(self, container_info):
new_labels = self.params.get('labels')
current_labels = container_info['Config'].get('Labels', dict())
- image_labels = self.check_image().get('Labels', dict())
+ image_info = self.check_image()
+ if not image_info:
+ return False
+ image_labels = image_info.get('Labels', dict())
for k, v in image_labels.items():
if k in new_labels:
if v != new_labels[k]:
diff --git a/ansible/module_utils/kolla_docker_worker.py b/ansible/module_utils/kolla_docker_worker.py
index 232ec5ba1..f488bab66 100644
--- a/ansible/module_utils/kolla_docker_worker.py
+++ b/ansible/module_utils/kolla_docker_worker.py
@@ -77,6 +77,7 @@ class DockerWorker(ContainerWorker):
for image_name in repo_tags:
if image_name == find_image:
return image
+ return dict()
def check_volume(self):
for vol in self.dc.volumes()['Volumes'] or list():
@@ -184,6 +185,14 @@ class DockerWorker(ContainerWorker):
'ExitCode: %s Message: %s' %
(exec_inspect['ExitCode'], output))
+ def dimensions_differ(self, a, b, key):
+ if key in ('cpuset_cpus', 'cpuset_mems'):
+ a = str(a or '')
+ b = str(b or '')
+ return a != b
+
+ return super().dimensions_differ(a, b, key)
+
def get_image_id(self):
full_image = self.params.get('image')
diff --git a/ansible/roles/cinder/templates/cinder.conf.j2 b/ansible/roles/cinder/templates/cinder.conf.j2
index 5e9fb5337..106091073 100644
--- a/ansible/roles/cinder/templates/cinder.conf.j2
+++ b/ansible/roles/cinder/templates/cinder.conf.j2
@@ -107,6 +107,18 @@ username = {{ nova_keystone_user }}
password = {{ nova_keystone_password }}
cafile = {{ openstack_cacert }}
+[glance]
+interface = internal
+auth_url = {{ keystone_internal_url }}
+auth_type = password
+project_domain_id = {{ default_project_domain_id }}
+user_domain_id = {{ default_user_domain_id }}
+region_name = {{ openstack_region_name }}
+project_name = service
+username = {{ cinder_keystone_user }}
+password = {{ cinder_keystone_password }}
+cafile = {{ openstack_cacert }}
+
[database]
connection = mysql+pymysql://{{ cinder_database_user }}:{{ cinder_database_password }}@{{ cinder_database_address }}/{{ cinder_database_name }}{{ '?ssl_ca=' ~ openstack_cacert if cinder_database_enable_tls_internal | bool }}
connection_recycle_time = {{ database_connection_recycle_time }}
diff --git a/ansible/roles/etcd/tasks/lookup_leader.yml b/ansible/roles/etcd/tasks/lookup_leader.yml
index f74c5df6b..4d661f8fa 100644
--- a/ansible/roles/etcd/tasks/lookup_leader.yml
+++ b/ansible/roles/etcd/tasks/lookup_leader.yml
@@ -1,5 +1,14 @@
---
# NOTE(jan.gutter): These tasks assume a cluster is running
+- name: Get info on etcd container
+ become: true
+ kolla_container_facts:
+ action: get_containers
+ container_engine: "{{ kolla_container_engine }}"
+ name:
+ - etcd
+ register: etcd_container_info
+
- name: Check for the etcd leader
vars:
service_name: "etcd"
@@ -18,6 +27,7 @@
changed_when: false
when:
- inventory_hostname in (groups.etcd_had_volume_True | default([]))
+ - etcd_container_info.containers['etcd'] is defined
register: etcd_endpoint_status_result
- name: Divide hosts by their etcd leader status
diff --git a/ansible/roles/grafana/defaults/main.yml b/ansible/roles/grafana/defaults/main.yml
index e7aac005a..402519d78 100644
--- a/ansible/roles/grafana/defaults/main.yml
+++ b/ansible/roles/grafana/defaults/main.yml
@@ -122,6 +122,11 @@ grafana_start_first_node_retries: 12
grafana_remove_old_volume: true
+grafana_custom_home_dashboard: >-
+ {{ 'grafana_home_dashboard.json' in
+ check_extra_conf_grafana.files | default([], true) |
+ map(attribute='path') | map('basename') }}
+
############
# Prometheus
############
diff --git a/ansible/roles/grafana/tasks/post_config.yml b/ansible/roles/grafana/tasks/post_config.yml
index e90a2ab47..949ef31f6 100644
--- a/ansible/roles/grafana/tasks/post_config.yml
+++ b/ansible/roles/grafana/tasks/post_config.yml
@@ -41,20 +41,3 @@
failed_when: response.status not in [200, 409] or response.status == 409 and ("name already exists" not in response.json.message | default(""))
with_dict: "{{ grafana_data_sources }}"
when: item.value.enabled | bool
-
-- name: Disable Getting Started panel
- become: true
- kolla_toolbox:
- container_engine: "{{ kolla_container_engine }}"
- module_name: uri
- module_args:
- url: "{{ grafana_internal_endpoint }}/api/user/helpflags/1"
- method: PUT
- user: "{{ grafana_admin_username }}"
- password: "{{ grafana_admin_password }}"
- force_basic_auth: true
- status_code: 200
- register: grafana_response
- changed_when: grafana_response.status == 200
- run_once: true
- when: "'grafana_home_dashboard.json' in check_extra_conf_grafana.files | map(attribute='path') | map('basename')"
diff --git a/ansible/roles/grafana/templates/grafana.ini.j2 b/ansible/roles/grafana/templates/grafana.ini.j2
index 763915796..4aae4caa5 100644
--- a/ansible/roles/grafana/templates/grafana.ini.j2
+++ b/ansible/roles/grafana/templates/grafana.ini.j2
@@ -41,3 +41,8 @@ enabled = true
config_file = /etc/grafana/ldap.toml
allow_sign_up = true
{% endif %}
+
+{% if grafana_custom_home_dashboard | bool %}
+[dashboards]
+default_home_dashboard_path = /usr/share/grafana/public/dashboards/home.json
+{% endif %}
diff --git a/ansible/roles/haproxy-config/templates/haproxy_single_service_split.cfg.j2 b/ansible/roles/haproxy-config/templates/haproxy_single_service_split.cfg.j2
index 10498e033..bae946b77 100644
--- a/ansible/roles/haproxy-config/templates/haproxy_single_service_split.cfg.j2
+++ b/ansible/roles/haproxy-config/templates/haproxy_single_service_split.cfg.j2
@@ -50,7 +50,7 @@ frontend {{ service_name }}_front
{{ "bind %s:%s %s" | e | format(vip_address, service_port, tls_option) | trim() }}
{# Redirect mode sets a redirect scheme instead of a backend #}
{% if service_mode == 'redirect' %}
-{% if enable_letsencrypt == 'yes' %}
+{% if enable_letsencrypt | bool %}
redirect scheme https code 301 if !{ ssl_fc } !{ path_reg ^/.well-known/acme-challenge/.+ }
{% else %}
redirect scheme https code 301 if !{ ssl_fc }
diff --git a/ansible/roles/horizon/defaults/main.yml b/ansible/roles/horizon/defaults/main.yml
index 04cd14761..9d7b66e01 100644
--- a/ansible/roles/horizon/defaults/main.yml
+++ b/ansible/roles/horizon/defaults/main.yml
@@ -34,7 +34,7 @@ horizon_services:
port: "{% if kolla_enable_tls_internal | bool %}{{ horizon_tls_port }}{% else %}{{ horizon_port }}{% endif %}"
listen_port: "{{ horizon_listen_port }}"
frontend_http_extra:
- - "{% if enable_letsencrypt == 'yes' %}use_backend acme_client_back if { path_reg ^/.well-known/acme-challenge/.+ }{% endif %}"
+ - "{% if enable_letsencrypt | bool %}use_backend acme_client_back if { path_reg ^/.well-known/acme-challenge/.+ }{% endif %}"
backend_http_extra:
- "balance {{ 'roundrobin' if enable_memcached | bool or horizon_backend_database | bool else 'source' }}"
- "option httpchk"
@@ -46,7 +46,7 @@ horizon_services:
port: "{{ horizon_port }}"
listen_port: "{{ horizon_listen_port }}"
frontend_redirect_extra:
- - "{% if enable_letsencrypt == 'yes' %}use_backend acme_client_back if { path_reg ^/.well-known/acme-challenge/.+ }{% endif %}"
+ - "{% if enable_letsencrypt | bool %}use_backend acme_client_back if { path_reg ^/.well-known/acme-challenge/.+ }{% endif %}"
horizon_external:
enabled: "{{ enable_horizon }}"
mode: "http"
@@ -55,7 +55,7 @@ horizon_services:
port: "{% if kolla_enable_tls_external | bool %}{{ horizon_tls_port }}{% else %}{{ horizon_port }}{% endif %}"
listen_port: "{{ horizon_listen_port }}"
frontend_http_extra:
- - "{% if enable_letsencrypt == 'yes' %}use_backend acme_client_back if { path_reg ^/.well-known/acme-challenge/.+ }{% endif %}"
+ - "{% if enable_letsencrypt | bool %}use_backend acme_client_back if { path_reg ^/.well-known/acme-challenge/.+ }{% endif %}"
backend_http_extra:
- "balance {{ 'roundrobin' if enable_memcached | bool or horizon_backend_database | bool else 'source' }}"
- "option httpchk"
@@ -68,9 +68,9 @@ horizon_services:
port: "{{ horizon_port }}"
listen_port: "{{ horizon_listen_port }}"
frontend_redirect_extra:
- - "{% if enable_letsencrypt == 'yes' %}use_backend acme_client_back if { path_reg ^/.well-known/acme-challenge/.+ }{% endif %}"
+ - "{% if enable_letsencrypt | bool %}use_backend acme_client_back if { path_reg ^/.well-known/acme-challenge/.+ }{% endif %}"
acme_client:
- enabled: "{{ enable_letsencrypt }}"
+ enabled: "{{ enable_letsencrypt | bool }}"
with_frontend: false
custom_member_list: "{{ acme_client_servers }}"
diff --git a/ansible/roles/keystone/tasks/distribute_fernet.yml b/ansible/roles/keystone/tasks/distribute_fernet.yml
index a780cf8da..2bb346da9 100644
--- a/ansible/roles/keystone/tasks/distribute_fernet.yml
+++ b/ansible/roles/keystone/tasks/distribute_fernet.yml
@@ -9,11 +9,9 @@
retries: 10
delay: 5
-- name: Run key distribution
+- name: Run fernet key distribution
become: true
changed_when: true
ansible.builtin.command: "{{ kolla_container_engine }} exec -t {{ keystone_services['keystone-fernet']['container_name'] }} /usr/bin/fernet-push.sh"
run_once: true
- delegate_to: >-
- {% if groups['keystone_fernet_running'] is defined -%}
- {{ groups['keystone_fernet_running'][0] }}{%- else -%}{{ groups['keystone'][0] }}{%- endif %}
+ delegate_to: "{{ (groups['keystone_fernet_running_True'] | default(groups['keystone']))[0] }}"
diff --git a/ansible/roles/kolla_toolbox/handlers/main.yml b/ansible/roles/kolla_toolbox/handlers/main.yml
index 73db9be9d..8acaece93 100644
--- a/ansible/roles/kolla_toolbox/handlers/main.yml
+++ b/ansible/roles/kolla_toolbox/handlers/main.yml
@@ -23,3 +23,7 @@
become: true
ansible.builtin.command: "{{ kolla_container_engine }} exec -t {{ service['container_name'] }} ansible --version"
changed_when: false
+ register: result
+ until: result.rc == 0
+ retries: 5
+ delay: 5
diff --git a/ansible/roles/mariadb/tasks/backup.yml b/ansible/roles/mariadb/tasks/backup.yml
index 55a7fcf51..665fc5a1e 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 0620e046a..8d1ae2593 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/ansible/roles/neutron/templates/ml2_conf.ini.j2 b/ansible/roles/neutron/templates/ml2_conf.ini.j2
index 7a4e72bdb..5af72163a 100644
--- a/ansible/roles/neutron/templates/ml2_conf.ini.j2
+++ b/ansible/roles/neutron/templates/ml2_conf.ini.j2
@@ -40,5 +40,4 @@ ovn_nb_connection = {{ ovn_nb_connection }}
ovn_sb_connection = {{ ovn_sb_connection_no_relay }}
ovn_metadata_enabled = true
enable_distributed_floating_ip = {{ neutron_ovn_distributed_fip | bool }}
-ovn_emit_need_to_frag = true
{% endif %}
diff --git a/ansible/roles/nova/defaults/main.yml b/ansible/roles/nova/defaults/main.yml
index 215826cdd..020048781 100644
--- a/ansible/roles/nova/defaults/main.yml
+++ b/ansible/roles/nova/defaults/main.yml
@@ -168,7 +168,11 @@ nova_metadata_healthcheck:
nova_scheduler_enable_healthchecks: "{{ enable_container_healthchecks }}"
nova_scheduler_healthcheck_interval: "{{ default_container_healthcheck_interval }}"
-nova_scheduler_healthcheck_retries: "{{ default_container_healthcheck_retries }}"
+
+# NOTE(mikal): this one is deliberately overridden because of the issues discussed
+# in releasenotes/notes/nova-scheduler-healthcheck-retries-12e681b16f6322aa.yaml
+nova_scheduler_healthcheck_retries: 8
+
nova_scheduler_healthcheck_start_period: "{{ default_container_healthcheck_start_period }}"
nova_scheduler_healthcheck_test: ["CMD-SHELL", "healthcheck_port nova-scheduler {{ om_rpc_port }}"]
nova_scheduler_healthcheck_timeout: "{{ default_container_healthcheck_timeout }}"
diff --git a/ansible/roles/prometheus/tasks/precheck.yml b/ansible/roles/prometheus/tasks/precheck.yml
index d4bdf0eba..5b98a09b4 100644
--- a/ansible/roles/prometheus/tasks/precheck.yml
+++ b/ansible/roles/prometheus/tasks/precheck.yml
@@ -28,21 +28,21 @@
ansible.builtin.assert:
that:
- prometheus_bcrypt_salt is defined
- - prometheus_bcrypt_salt is string
+ - prometheus_bcrypt_salt is string or prometheus_bcrypt_salt is vault_encrypted
- prometheus_bcrypt_salt | length == 22
- name: Check that prometheus_password is correctly set
ansible.builtin.assert:
that:
- prometheus_password is defined
- - prometheus_password is string
+ - prometheus_password is string or prometheus_password is vault_encrypted
- prometheus_password | length > 0
- name: Check that prometheus_grafana_password is correctly set
ansible.builtin.assert:
that:
- prometheus_grafana_password is defined
- - prometheus_grafana_password is string
+ - prometheus_grafana_password is string or prometheus_grafana_password is vault_encrypted
- prometheus_grafana_password | length > 0
when: enable_grafana | bool
@@ -50,7 +50,7 @@
ansible.builtin.assert:
that:
- prometheus_skyline_password is defined
- - prometheus_skyline_password is string
+ - prometheus_skyline_password is string or prometheus_skyline_password is vault_encrypted
- prometheus_skyline_password | length > 0
when: enable_skyline | bool
diff --git a/ansible/roles/rabbitmq/tasks/precheck.yml b/ansible/roles/rabbitmq/tasks/precheck.yml
index a5c8f6340..827ab66ae 100644
--- a/ansible/roles/rabbitmq/tasks/precheck.yml
+++ b/ansible/roles/rabbitmq/tasks/precheck.yml
@@ -88,6 +88,7 @@
- "{{ kolla_certificates_dir }}/{{ inventory_hostname }}/rabbitmq-cert.pem"
- "{{ kolla_certificates_dir }}/{{ inventory_hostname }}-cert.pem"
- "{{ kolla_certificates_dir }}/rabbitmq-cert.pem"
+ - "{{ kolla_tls_backend_cert }}"
when:
- not kolla_externally_managed_cert | bool
- rabbitmq_enable_tls | bool
@@ -102,6 +103,7 @@
- "{{ kolla_certificates_dir }}/{{ inventory_hostname }}/rabbitmq-key.pem"
- "{{ kolla_certificates_dir }}/{{ inventory_hostname }}-key.pem"
- "{{ kolla_certificates_dir }}/rabbitmq-key.pem"
+ - "{{ kolla_tls_backend_key }}"
when:
- not kolla_externally_managed_cert | bool
- rabbitmq_enable_tls | bool
diff --git a/doc/source/reference/logging-and-monitoring/grafana-guide.rst b/doc/source/reference/logging-and-monitoring/grafana-guide.rst
index 412ca88e1..8b896d1d8 100644
--- a/doc/source/reference/logging-and-monitoring/grafana-guide.rst
+++ b/doc/source/reference/logging-and-monitoring/grafana-guide.rst
@@ -90,6 +90,9 @@ sub-folders is also supported when using a custom ``provisioning.yaml``
file. Dashboards will be imported into the Grafana dashboards 'General'
folder by default.
+To set a custom home dashboard, place the dashboard file in
+``{{ node_custom_config }}/grafana/grafana_home_dashboard.json``.
+
Grafana provisioner config can be altered by placing ``provisioning.yaml`` to
``{{ node_custom_config }}/grafana/`` folder.
diff --git a/doc/source/reference/networking/neutron.rst b/doc/source/reference/networking/neutron.rst
index b4f7a884f..46b565ba8 100644
--- a/doc/source/reference/networking/neutron.rst
+++ b/doc/source/reference/networking/neutron.rst
@@ -25,10 +25,10 @@ value is ``eth1``. These external interfaces are used by hosts in the
``network`` group. They are also used by hosts in the ``compute`` group if
``enable_neutron_provider_networks`` is set or DVR is enabled.
-The external interfaces are each plugged into a bridge (Open vSwitch or Linux
-Bridge, depending on the driver) defined by ``neutron_bridge_name``, which
-defaults to ``br-ex``. When there are multiple external interfaces,
-``neutron_bridge_name`` should be a comma-separated list of the same length.
+The external interfaces are each plugged into an Open vSwitch bridge defined by
+``neutron_bridge_name``, which defaults to ``br-ex``. When there are multiple
+external interfaces, ``neutron_bridge_name`` should be a comma-separated list
+of the same length.
The default Neutron physical network is ``physnet1``, or ``physnet1`` to
``physnetN`` when there are multiple external network interfaces. This may be
diff --git a/etc/kolla/globals.yml b/etc/kolla/globals.yml
index 2d7cdcda4..f5f8cdad2 100644
--- a/etc/kolla/globals.yml
+++ b/etc/kolla/globals.yml
@@ -219,6 +219,21 @@ workaround_ansible_issue_8743: true
#default_container_healthcheck_timeout: 30
#default_container_healthcheck_retries: 3
#default_container_healthcheck_start_period: 5
+# Individual services accept per-service overrides of the options above,
+# named <service>_healthcheck_<option>. nova_scheduler ships with a wider
+# one: its healthcheck asserts an established AMQP connection, but every
+# deploy and every reconfigure signals the scheduler to restart its
+# workers, and they take [DEFAULT]/manager_shutdown_timeout in nova.conf
+# (160 seconds by default) to come back. The default 30 second interval
+# with 3 retries only covers 90 seconds of that.
+#nova_scheduler_healthcheck_retries: 8
+# The conductors wait for the same period, but are only ever stopped by
+# the container engine, which kills them after docker_graceful_timeout
+# and so stays inside what the default healthcheck covers. Raise these
+# to match if docker_graceful_timeout is raised towards
+# manager_shutdown_timeout.
+#nova_conductor_healthcheck_retries: 3
+#nova_super_conductor_healthcheck_retries: 3
##################
# Firewall options
diff --git a/releasenotes/notes/bug-1889611-a89ddb10134081f4.yaml b/releasenotes/notes/bug-1889611-a89ddb10134081f4.yaml
new file mode 100644
index 000000000..05cce09d8
--- /dev/null
+++ b/releasenotes/notes/bug-1889611-a89ddb10134081f4.yaml
@@ -0,0 +1,7 @@
+---
+fixes:
+ - |
+ Fixes image check with containers that don't exist locally. A bug in the
+ image comparison logic would make service-check-containers fail with
+ ``AttributeError: NoneType object has no attribute get``
+ `LP#2166024 <https://launchpad.net/bugs/2166024>`__
diff --git a/releasenotes/notes/bug-2161971-1f167e47f9740326.yaml b/releasenotes/notes/bug-2161971-1f167e47f9740326.yaml
new file mode 100644
index 000000000..e57a8a0bf
--- /dev/null
+++ b/releasenotes/notes/bug-2161971-1f167e47f9740326.yaml
@@ -0,0 +1,9 @@
+---
+fixes:
+ - |
+ Fixes service-to-service authentication between Cinder and
+ Glance. This resolves ``HTTP 403 Forbidden`` errors during
+ ``fetch_image_location`` and restores fast volume cloning
+ (Copy-on-Write) for backends like RBD, preventing an
+ unnecessary fallback to slow image downloads.
+ `LP#2161971 <https://launchpad.net/bugs/2161971>`__
diff --git a/releasenotes/notes/bug-2166048-9707508fca725bd7.yaml b/releasenotes/notes/bug-2166048-9707508fca725bd7.yaml
new file mode 100644
index 000000000..0004097ab
--- /dev/null
+++ b/releasenotes/notes/bug-2166048-9707508fca725bd7.yaml
@@ -0,0 +1,5 @@
+---
+fixes:
+ - |
+ Fixes Grafana deployment with custom home dashboard in Grafana 13.2.
+ `LP#2166048 <https://launchpad.net/bugs/2166048>`__
diff --git a/releasenotes/notes/fix-certificates-prechecks-5baa68cd11fbe61c.yaml b/releasenotes/notes/fix-certificates-prechecks-5baa68cd11fbe61c.yaml
new file mode 100644
index 000000000..3c38e1658
--- /dev/null
+++ b/releasenotes/notes/fix-certificates-prechecks-5baa68cd11fbe61c.yaml
@@ -0,0 +1,8 @@
+---
+fixes:
+ - |
+ Fixes RabbitMQ precheck failing with "No TLS certificate/key provided
+ for RabbitMQ" when relying on the shared ``kolla_tls_backend_cert``/
+ ``kolla_tls_backend_key`` fallback, which ``service-cert-copy`` already
+ supports but the precheck did not check for.
+
diff --git a/releasenotes/notes/fix-cpuset-dimensions-comparison-f2c0e9aaab0f0342.yaml b/releasenotes/notes/fix-cpuset-dimensions-comparison-f2c0e9aaab0f0342.yaml
new file mode 100644
index 000000000..343e7fb5e
--- /dev/null
+++ b/releasenotes/notes/fix-cpuset-dimensions-comparison-f2c0e9aaab0f0342.yaml
@@ -0,0 +1,7 @@
+---
+fixes:
+ - |
+ Fixes an issue in the Docker container worker where reconfiguring
+ containers with ``cpuset_cpus`` or ``cpuset_mems`` dimensions could fail
+ with an ``IndexError`` when the existing container had no explicit cpuset
+ constraint configured.
diff --git a/releasenotes/notes/fix-fernet-distribution-96a52af5a5641871.yaml b/releasenotes/notes/fix-fernet-distribution-96a52af5a5641871.yaml
new file mode 100644
index 000000000..c7cf4d7a4
--- /dev/null
+++ b/releasenotes/notes/fix-fernet-distribution-96a52af5a5641871.yaml
@@ -0,0 +1,6 @@
+---
+fixes:
+ - |
+ Fernet key distribution is now delegated to a host in the correct
+ group when run on existing deployments.
+ `LP#2163158 <https://launchpad.net/bugs/2163158>`__
diff --git a/releasenotes/notes/fix_prometheus_vault_prechecks-5335880cd18680dd.yaml b/releasenotes/notes/fix_prometheus_vault_prechecks-5335880cd18680dd.yaml
new file mode 100644
index 000000000..8a6bc53a9
--- /dev/null
+++ b/releasenotes/notes/fix_prometheus_vault_prechecks-5335880cd18680dd.yaml
@@ -0,0 +1,10 @@
+---
+fixes:
+ - |
+ Fixes Prometheus precheck failing on ``prometheus_bcrypt_salt``,
+ ``prometheus_password``, ``prometheus_grafana_password`` and
+ ``prometheus_skyline_password`` when these are Ansible Vault encrypted
+ values, due to a known Ansible limitation where the ``is string`` Jinja
+ test does not recognise vault-encrypted strings. See
+ `ansible/ansible#33067 <https://github.com/ansible/ansible/issues/33067>`__
+ for details.
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 000000000..6b54520d4
--- /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-<date>.qp.xbc.xbs.gz`` and
+ incrementals ``incremental-<date>-mysqlbackup-<date>.qp.xbc.xbs.gz``,
+ matching the restore documentation. Existing backups remain restorable.
diff --git a/releasenotes/notes/nova-scheduler-healthcheck-retries-12e681b16f6322aa.yaml b/releasenotes/notes/nova-scheduler-healthcheck-retries-12e681b16f6322aa.yaml
new file mode 100644
index 000000000..2cf4e2e8c
--- /dev/null
+++ b/releasenotes/notes/nova-scheduler-healthcheck-retries-12e681b16f6322aa.yaml
@@ -0,0 +1,13 @@
+---
+fixes:
+ - |
+ The ``nova_scheduler`` container healthcheck asserts an established AMQP
+ connection, but every deploy and every reconfigure signals the scheduler
+ to restart its workers and they take
+ ``[DEFAULT]/manager_shutdown_timeout`` (160 seconds by default) to come
+ back. The default 30 second interval with 3 retries only covers 90
+ seconds of that, so the container could be reported unhealthy for the
+ rest of the restart even though the scheduler was fine.
+ ``nova_scheduler_healthcheck_retries`` now defaults to 8, which covers
+ the whole window, and is documented in the sample ``globals.yml``.
+ `LP#2162860 <https://bugs.launchpad.net/kolla/+bug/2162860>`__
diff --git a/tests/kolla_container_tests/test_docker_worker.py b/tests/kolla_container_tests/test_docker_worker.py
index 26fe020f2..e32da66cb 100644
--- a/tests/kolla_container_tests/test_docker_worker.py
+++ b/tests/kolla_container_tests/test_docker_worker.py
@@ -1251,6 +1251,12 @@ class TestAttrComp(base.BaseTestCase):
self.dw = get_DockerWorker({'privileged': False})
self.assertTrue(self.dw.compare_privileged(container_info))
+ def test_compare_labels_image_missing(self):
+ container_info = {'Config': dict(Labels={'kolla_version': '1.0.1'})}
+ self.dw = get_DockerWorker({'labels': {}})
+ self.dw.check_image = mock.MagicMock(return_value=dict())
+ self.assertFalse(self.dw.compare_labels(container_info))
+
def test_compare_labels_neg(self):
container_info = {'Config': dict(Labels={'kolla_version': '2.0.1'})}
self.dw = get_DockerWorker({'labels': {'kolla_version': '2.0.1'}})
@@ -1500,6 +1506,60 @@ class TestAttrComp(base.BaseTestCase):
failed=True, msg=repr("Unsupported dimensions"),
unsupported_dimensions=set(['kernel_memory']))
+ def test_compare_dimensions_cpuset_cpus_empty_current(self):
+ self.fake_data['params']['dimensions'] = {
+ 'cpuset_cpus': '0-1,32-33'}
+ container_info = dict()
+ container_info['HostConfig'] = {
+ 'CpusetCpus': ''}
+ self.dw = get_DockerWorker(self.fake_data['params'])
+ self.assertTrue(self.dw.compare_dimensions(container_info))
+
+ def test_compare_dimensions_cpuset_cpus_same(self):
+ self.fake_data['params']['dimensions'] = {
+ 'cpuset_cpus': '0-1,32-33'}
+ container_info = dict()
+ container_info['HostConfig'] = {
+ 'CpusetCpus': '0-1,32-33'}
+ self.dw = get_DockerWorker(self.fake_data['params'])
+ self.assertFalse(self.dw.compare_dimensions(container_info))
+
+ def test_compare_dimensions_cpuset_cpus_different(self):
+ self.fake_data['params']['dimensions'] = {
+ 'cpuset_cpus': '0-1,32-33'}
+ container_info = dict()
+ container_info['HostConfig'] = {
+ 'CpusetCpus': '0-3'}
+ self.dw = get_DockerWorker(self.fake_data['params'])
+ self.assertTrue(self.dw.compare_dimensions(container_info))
+
+ def test_compare_dimensions_cpuset_mems_empty_current(self):
+ self.fake_data['params']['dimensions'] = {
+ 'cpuset_mems': '0'}
+ container_info = dict()
+ container_info['HostConfig'] = {
+ 'CpusetMems': ''}
+ self.dw = get_DockerWorker(self.fake_data['params'])
+ self.assertTrue(self.dw.compare_dimensions(container_info))
+
+ def test_compare_dimensions_cpuset_mems_same(self):
+ self.fake_data['params']['dimensions'] = {
+ 'cpuset_mems': '0'}
+ container_info = dict()
+ container_info['HostConfig'] = {
+ 'CpusetMems': '0'}
+ self.dw = get_DockerWorker(self.fake_data['params'])
+ self.assertFalse(self.dw.compare_dimensions(container_info))
+
+ def test_compare_dimensions_cpuset_mems_different(self):
+ self.fake_data['params']['dimensions'] = {
+ 'cpuset_mems': '0'}
+ container_info = dict()
+ container_info['HostConfig'] = {
+ 'CpusetMems': '1'}
+ self.dw = get_DockerWorker(self.fake_data['params'])
+ self.assertTrue(self.dw.compare_dimensions(container_info))
+
def test_compare_container_state_pos(self):
container_info = {'State': dict(Status='running')}
self.dw = get_DockerWorker({'state': 'exited'})
diff --git a/tests/kolla_container_tests/test_podman_worker.py b/tests/kolla_container_tests/test_podman_worker.py
index 048a7a797..04d6d987b 100644
--- a/tests/kolla_container_tests/test_podman_worker.py
+++ b/tests/kolla_container_tests/test_podman_worker.py
@@ -1264,6 +1264,12 @@ class TestAttrComp(base.BaseTestCase):
self.pw = get_PodmanWorker({'privileged': False})
self.assertTrue(self.pw.compare_privileged(container_info))
+ def test_compare_labels_image_missing(self):
+ container_info = {'Config': dict(Labels={'kolla_version': '1.0.1'})}
+ self.pw = get_PodmanWorker({'labels': {}})
+ self.pw.check_image = mock.MagicMock(return_value=dict())
+ self.assertFalse(self.pw.compare_labels(container_info))
+
def test_compare_labels_neg(self):
container_info = {'Config': dict(Labels={'kolla_version': '2.0.1'})}
self.pw = get_PodmanWorker({'labels': {'kolla_version': '2.0.1'}})
diff --git a/tests/test_merge_config.py b/tests/test_merge_config.py
index d64b2ae6b..de2c98281 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 @@ class OverrideConfigParserTest(base.BaseTestCase):
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 d6b74ae77..35de91d2c 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 @@ class MergeYamlConfigTest(base.BaseTestCase):
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)