diff options
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) |
