diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..e9a948a0d --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @stackhpc/kayobe diff --git a/.github/workflows/tag-and-release.yml b/.github/workflows/tag-and-release.yml new file mode 100644 index 000000000..f11bd2358 --- /dev/null +++ b/.github/workflows/tag-and-release.yml @@ -0,0 +1,12 @@ +--- +name: Tag & Release +'on': + push: + branches: + - stackhpc/wallaby +permissions: + actions: read + contents: write +jobs: + tag-and-release: + uses: stackhpc/.github/.github/workflows/tag-and-release.yml@main diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml new file mode 100644 index 000000000..8713f0e02 --- /dev/null +++ b/.github/workflows/tox.yml @@ -0,0 +1,7 @@ +--- +name: Tox Continuous Integration +'on': + pull_request: +jobs: + tox: + uses: stackhpc/.github/.github/workflows/tox.yml@main diff --git a/ansible/action_plugins/merge_configs.py b/ansible/action_plugins/merge_configs.py new file mode 100644 index 000000000..3cd3551d0 --- /dev/null +++ b/ansible/action_plugins/merge_configs.py @@ -0,0 +1,19 @@ +# Copyright (c) 2021 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.action.merge_configs + +ActionModule = kayobe.plugins.action.merge_configs.ActionModule diff --git a/ansible/action_plugins/merge_yaml.py b/ansible/action_plugins/merge_yaml.py new file mode 100644 index 000000000..e5abd3fa6 --- /dev/null +++ b/ansible/action_plugins/merge_yaml.py @@ -0,0 +1,19 @@ +# Copyright (c) 2021 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.action.merge_yaml + +ActionModule = kayobe.plugins.action.merge_yaml.ActionModule diff --git a/ansible/apt.yml b/ansible/apt.yml index 907e7ec62..08146bce6 100644 --- a/ansible/apt.yml +++ b/ansible/apt.yml @@ -1,6 +1,6 @@ --- - name: Ensure APT is configured - hosts: seed-hypervisor:seed:overcloud + hosts: seed-hypervisor:seed:overcloud:infra-vms vars: ansible_python_interpreter: /usr/bin/python3 tags: diff --git a/ansible/compute-libvirt-host.yml b/ansible/compute-libvirt-host.yml new file mode 100644 index 000000000..9b3900c22 --- /dev/null +++ b/ansible/compute-libvirt-host.yml @@ -0,0 +1,59 @@ +--- +- name: Ensure the libvirt daemon is configured + hosts: compute + tags: + - libvirt-host + tasks: + - name: Ensure Ceph package repository is available + package: + name: "centos-release-ceph-{{ compute_libvirt_ceph_repo_release }}" + state: present + when: + - compute_libvirt_enabled | bool + - ansible_facts.distribution in ['CentOS', 'Rocky'] + - compute_libvirt_ceph_repo_install | bool + become: true + + - name: Include stackhpc.libvirt-host role + include_role: + name: stackhpc.libvirt-host + vars: + libvirt_host_libvirtd_conf: "{{ compute_libvirt_conf }}" + libvirt_host_qemu_conf: "{{ compute_qemu_conf }}" + libvirt_host_enable_sasl_support: "{{ compute_libvirt_enable_sasl | bool }}" + libvirt_host_sasl_authname: nova + libvirt_host_sasl_password: "{{ compute_libvirt_sasl_password }}" + libvirt_host_tcp_listen: "{{ not compute_libvirt_enable_tls | bool }}" + libvirt_host_tcp_listen_address: "{{ internal_net_name | net_ip }}:16509" + libvirt_host_tls_listen: "{{ compute_libvirt_enable_tls | bool }}" + libvirt_host_tls_listen_address: "{{ internal_net_name | net_ip }}:16514" + # TLS server and client certificates. + libvirt_host_tls_server_cert: >- + {{ lookup('file', lookup('first_found', lookup_params | combine({'files': ['servercert.pem']}))) + if libvirt_host_tls_listen | default(False) | bool else '' }} + libvirt_host_tls_server_key: >- + {{ lookup('file', lookup('first_found', lookup_params | combine({'files': ['serverkey.pem']}))) + if libvirt_host_tls_listen | default(False) | bool else '' }} + libvirt_host_tls_client_cert: >- + {{ lookup('file', lookup('first_found', lookup_params | combine({'files': ['clientcert.pem']}))) + if libvirt_host_tls_listen | default(False) | bool else '' }} + libvirt_host_tls_client_key: >- + {{ lookup('file', lookup('first_found', lookup_params | combine({'files': ['clientkey.pem']}))) + if libvirt_host_tls_listen | default(False) | bool else '' }} + libvirt_host_tls_cacert: >- + {{ lookup('file', lookup('first_found', lookup_params | combine({'files': ['cacert.pem']}))) + if libvirt_host_tls_listen | default(False) | bool else '' }} + lookup_params: + paths: "{{ libvirt_tls_cert_paths }}" + skip: true + # Support loading libvirt TLS certificates & keys from per-host and + # global locations. + libvirt_tls_cert_paths: >- + {{ (libvirt_tls_cert_dirs | unique | product([inventory_hostname]) | map('path_join') | list + + libvirt_tls_cert_dirs | unique | list) | list }} + libvirt_tls_cert_dirs: + - "{{ kayobe_env_config_path }}/certificates/libvirt" + - "{{ kayobe_config_path }}/certificates/libvirt" + libvirt_host_enable_efi_support: true + when: + - compute_libvirt_enabled | bool diff --git a/ansible/dev-tools.yml b/ansible/dev-tools.yml index 9fd4e1b55..88617af11 100644 --- a/ansible/dev-tools.yml +++ b/ansible/dev-tools.yml @@ -1,6 +1,6 @@ --- - name: Ensure development tools are installed - hosts: seed-hypervisor:seed:overcloud + hosts: seed-hypervisor:seed:overcloud:infra-vms tags: - dev-tools roles: diff --git a/ansible/disable-cloud-init.yml b/ansible/disable-cloud-init.yml index 6cc23e846..033c7a602 100644 --- a/ansible/disable-cloud-init.yml +++ b/ansible/disable-cloud-init.yml @@ -4,7 +4,7 @@ # In some cases cloud-init reconfigure automatically network interface # and cause some issues in network configuration - name: Disable Cloud-init service - hosts: overcloud + hosts: overcloud:infra-vms tags: - disable-cloud-init roles: diff --git a/ansible/disable-glean.yml b/ansible/disable-glean.yml index 11a3f0108..bf583ebdc 100644 --- a/ansible/disable-glean.yml +++ b/ansible/disable-glean.yml @@ -3,7 +3,7 @@ # servers but gets in the way after this as it tries to enable all network # interfaces. In some cases this can lead to timeouts. - name: Ensure Glean is disabled and its artifacts are removed - hosts: seed:overcloud + hosts: seed:overcloud:infra-vms tags: - disable-glean roles: diff --git a/ansible/disable-selinux.yml b/ansible/disable-selinux.yml index 3c3bed1b1..3ce1706c6 100644 --- a/ansible/disable-selinux.yml +++ b/ansible/disable-selinux.yml @@ -1,6 +1,6 @@ --- - name: Disable SELinux and reboot if required - hosts: seed:overcloud + hosts: seed:overcloud:infra-vms tags: - disable-selinux roles: diff --git a/ansible/dnf.yml b/ansible/dnf.yml index 0c1e4279b..bb5aafb3f 100644 --- a/ansible/dnf.yml +++ b/ansible/dnf.yml @@ -1,6 +1,6 @@ --- - name: Ensure DNF repos are configured - hosts: seed-hypervisor:seed:overcloud + hosts: seed-hypervisor:seed:overcloud:infra-vms vars: ansible_python_interpreter: /usr/bin/python3 tags: diff --git a/ansible/firewall.yml b/ansible/firewall.yml new file mode 100644 index 000000000..c133fb011 --- /dev/null +++ b/ansible/firewall.yml @@ -0,0 +1,12 @@ +--- +- name: Ensure firewall is configured + hosts: seed-hypervisor:seed:overcloud:infra-vms + tags: + - config + - firewall + tasks: + - name: Configure the firewall + include_role: + name: "firewall-{{ ansible_facts.os_family | lower }}" + when: + - ansible_facts.os_family == 'RedHat' diff --git a/ansible/group_vars/all/apt b/ansible/group_vars/all/apt index fad722dcd..46d26de18 100644 --- a/ansible/group_vars/all/apt +++ b/ansible/group_vars/all/apt @@ -10,3 +10,38 @@ apt_proxy_http: # Apt proxy URL for HTTPS. Default is {{ apt_proxy_http }}. apt_proxy_https: "{{ apt_proxy_http }}" + +# List of Apt configuration options. Each item is a dict with the following +# keys: +# * content: free-form configuration file content +# * filename: name of a file in /etc/apt/apt.conf.d/ in which to write the +# configuration +# Default is an empty list. +apt_config: [] + +# List of apt keys. Each item is a dict containing the following keys: +# * url: URL of key +# * filename: Name of a file in which to store the downloaded key. The +# extension should be '.asc' for ASCII-armoured keys, or '.gpg' otherwise. +# Default is an empty list. +apt_keys: [] + +# A list of Apt repositories. Each item is a dict with the following keys: +# * types: whitespace-separated list of repository types, e.g. deb or deb-src +# (optional, default is 'deb') +# * url: URL of the repository +# * suites: whitespace-separated list of suites, e.g. focal (optional, default +# is ansible_facts.distribution_release) +# * components: whitespace-separated list of components, e.g. main (optional, +# default is 'main') +# * signed_by: whitespace-separated list of names of GPG keyring files in +# apt_keys_path (optional, default is unset) +# * architecture: whitespace-separated list of architectures that will be used +# (optional, default is unset) +# Default is an empty list. +apt_repositories: [] + +# Whether to disable repositories in /etc/apt/sources.list. This may be used +# when replacing the distribution repositories via apt_repositories. +# Default is false. +apt_disable_sources_list: false diff --git a/ansible/group_vars/all/bifrost b/ansible/group_vars/all/bifrost index 4f8b3ee84..576b24e9a 100644 --- a/ansible/group_vars/all/bifrost +++ b/ansible/group_vars/all/bifrost @@ -58,6 +58,18 @@ kolla_bifrost_dib_env_vars: "{{ kolla_bifrost_dib_env_vars_default | combine(kol # List of DIB packages to install. kolla_bifrost_dib_packages: [] +############################################################################### +# Disk image deployment configuration. + +# Name of disk image file to deploy. Default is "deployment_image.qcow2". +kolla_bifrost_deploy_image_filename: "deployment_image.qcow2" + +# UUID of the root filesystem contained within the deployment image. +# See below URL for instructions on how to extract it: +# https://docs.openstack.org/ironic/latest/admin/raid.html#image-requirements +# Default is none. +kolla_bifrost_deploy_image_rootfs: + ############################################################################### # Ironic configuration. diff --git a/ansible/group_vars/all/compute b/ansible/group_vars/all/compute index f158b076f..57026fc74 100644 --- a/ansible/group_vars/all/compute +++ b/ansible/group_vars/all/compute @@ -133,3 +133,82 @@ compute_sysctl_parameters: {} # List of users to create. This should be in a format accepted by the # singleplatform-eng.users role. compute_users: "{{ users_default }}" + +############################################################################### +# Compute node firewalld configuration. + +# Whether to install and enable firewalld. +compute_firewalld_enabled: false + +# A list of zones to create. Each item is a dict containing a 'zone' item. +compute_firewalld_zones: [] + +# A firewalld zone to set as the default. Default is unset, in which case the +# default zone will not be changed. +compute_firewalld_default_zone: + +# A list of firewall rules to apply. Each item is a dict containing arguments +# to pass to the firewalld module. Arguments are omitted if not provided, with +# the following exceptions: +# - offline: true +# - permanent: true +# - state: enabled +compute_firewalld_rules: [] + +############################################################################### +# Compute node host libvirt configuration. + +# Whether to enable a host libvirt daemon. Default is true if kolla_enable_nova +# is true and kolla_enable_nova_libvirt_container is false. +compute_libvirt_enabled: "{{ kolla_enable_nova | bool and not kolla_enable_nova_libvirt_container | bool }}" + +# A dict of default configuration options to write to +# /etc/libvirt/libvirtd.conf. +compute_libvirt_conf_default: + auth_tcp: "{{ 'sasl' if compute_libvirt_enable_sasl | bool else 'none' }}" + auth_tls: "{{ 'sasl' if compute_libvirt_enable_sasl | bool else 'none' }}" + log_level: "{{ compute_libvirtd_log_level }}" + +# A dict of additional configuration options to write to +# /etc/libvirt/libvirtd.conf. +compute_libvirt_conf_extra: {} + +# A dict of configuration options to write to /etc/libvirt/libvirtd.conf. +# Default is a combination of compute_libvirt_conf_default and +# compute_libvirt_conf_extra. +compute_libvirt_conf: "{{ compute_libvirt_conf_default | combine(compute_libvirt_conf_extra) }}" + +# Numerical log level for libvirtd. Default is 3. +compute_libvirtd_log_level: 3 + +# A dict of default configuration options to write to +# /etc/libvirt/qemu.conf. +compute_qemu_conf_default: + max_files: 32768 + max_processes: 131072 + +# A dict of additional configuration options to write to +# /etc/libvirt/qemu.conf. +compute_qemu_conf_extra: {} + +# A dict of configuration options to write to /etc/libvirt/qemu.conf. +# Default is a combination of compute_qemu_conf_default and +# compute_qemu_conf_extra. +compute_qemu_conf: "{{ compute_qemu_conf_default | combine(compute_qemu_conf_extra) }}" + +# Whether to enable libvirt SASL authentication. Default is true. +compute_libvirt_enable_sasl: true + +# libvirt SASL password. Default is unset. +compute_libvirt_sasl_password: + +# Whether to enable a libvirt TLS listener. Default is false. +compute_libvirt_enable_tls: false + +# Whether to install a Ceph package repository on CentOS and Rocky hosts. +# Default is true. +compute_libvirt_ceph_repo_install: true + +# Ceph package repository release to install on CentOS and Rocky hosts when +# compute_libvirt_ceph_repo_install is true. Default is 'pacific'. +compute_libvirt_ceph_repo_release: pacific diff --git a/ansible/group_vars/all/controllers b/ansible/group_vars/all/controllers index 208c822ed..15eb9e57f 100644 --- a/ansible/group_vars/all/controllers +++ b/ansible/group_vars/all/controllers @@ -155,3 +155,24 @@ controller_sysctl_parameters: {} # List of users to create. This should be in a format accepted by the # singleplatform-eng.users role. controller_users: "{{ users_default }}" + +############################################################################### +# Controller node firewalld configuration. + +# Whether to install and enable firewalld. +controller_firewalld_enabled: false + +# A list of zones to create. Each item is a dict containing a 'zone' item. +controller_firewalld_zones: [] + +# A firewalld zone to set as the default. Default is unset, in which case the +# default zone will not be changed. +controller_firewalld_default_zone: + +# A list of firewall rules to apply. Each item is a dict containing arguments +# to pass to the firewalld module. Arguments are omitted if not provided, with +# the following exceptions: +# - offline: true +# - permanent: true +# - state: enabled +controller_firewalld_rules: [] diff --git a/ansible/group_vars/all/dnf b/ansible/group_vars/all/dnf index 31145f1a4..8b2b461d4 100644 --- a/ansible/group_vars/all/dnf +++ b/ansible/group_vars/all/dnf @@ -7,12 +7,18 @@ dnf_config: {} # Whether or not to use a local Yum mirror. Default value is 'false'. dnf_use_local_mirror: false -# Mirror FQDN for Yum repos. Default value is 'mirror.centos.org'. +# Mirror FQDN for Yum CentOS repos. Default value is 'mirror.centos.org'. dnf_centos_mirror_host: 'mirror.centos.org' # Mirror directory for Yum CentOS repos. Default value is 'centos'. dnf_centos_mirror_directory: 'centos' +# Mirror FQDN for Yum Rocky repos. Default value is 'dl.rockylinux.org'. +dnf_rocky_mirror_host: 'dl.rockylinux.org' + +# Mirror directory for Yum Rocky repos. Default value is 'pub/rocky'. +dnf_rocky_mirror_directory: 'pub/rocky' + # Mirror FQDN for Yum EPEL repos. Default value is # 'download.fedoraproject.org'. dnf_epel_mirror_host: 'download.fedoraproject.org' diff --git a/ansible/group_vars/all/globals b/ansible/group_vars/all/globals index 3a7f362ce..f84c60c34 100644 --- a/ansible/group_vars/all/globals +++ b/ansible/group_vars/all/globals @@ -44,13 +44,17 @@ kayobe_ansible_user: "stack" ############################################################################### # OS distribution. -# OS distribution name. Valid options are "centos", "ubuntu". Default is -# "centos". +# OS distribution name. Valid options are "centos", "rocky", "ubuntu". Default +# is "centos". os_distribution: "centos" # OS release. Valid options are "8-stream" when os_distribution is "centos", or -# "focal" when os_distribution is "ubuntu". -os_release: "{{ '8-stream' if os_distribution == 'centos' else 'focal' }}" +# "8" when os_distribution is "rocky", or "focal" when os_distribution is +# "ubuntu". +os_release: >- + {{ '8-stream' if os_distribution == 'centos' + else '8' if os_distribution == 'rocky' + else 'focal' }} ############################################################################### # Ansible configuration. diff --git a/ansible/group_vars/all/infra-vms b/ansible/group_vars/all/infra-vms new file mode 100644 index 000000000..1e43011dd --- /dev/null +++ b/ansible/group_vars/all/infra-vms @@ -0,0 +1,199 @@ +--- +############################################################################### +# Infrastructure VM configuration. + +# Name of the infra VM. +infra_vm_name: "{{ inventory_hostname }}" + +# Memory in MB. +infra_vm_memory_mb: "{{ 16 * 1024 }}" + +# Number of vCPUs. +infra_vm_vcpus: 4 + +# List of volumes. +infra_vm_volumes: + - "{{ infra_vm_root_volume }}" + - "{{ infra_vm_data_volume }}" + +# Root volume. +infra_vm_root_volume: + name: "{{ infra_vm_name }}-root" + pool: "{{ infra_vm_pool }}" + capacity: "{{ infra_vm_root_capacity }}" + format: "{{ infra_vm_root_format }}" + image: "{{ infra_vm_root_image }}" + +# Data volume. +infra_vm_data_volume: + name: "{{ infra_vm_name }}-data" + pool: "{{ infra_vm_pool }}" + capacity: "{{ infra_vm_data_capacity }}" + format: "{{ infra_vm_data_format }}" + +# Name of the storage pool for the infra VM volumes. +infra_vm_pool: default + +# Capacity of the infra VM root volume. +infra_vm_root_capacity: 50G + +# Format of the infra VM root volume. +infra_vm_root_format: qcow2 + +# Base image for the infra VM root volume. Default is +# "https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img" +# when os_distribution is "ubuntu", or +# http://dl.rockylinux.org/pub/rocky/8.5/images/Rocky-8-GenericCloud-8.5-20211114.2.x86_64.qcow2 +# when os_distribution is "rocky", +# or +# "https://cloud.centos.org/centos/8-stream/x86_64/images/CentOS-Stream-GenericCloud-8-20210603.0.x86_64.qcow2" +# otherwise. +infra_vm_root_image: >- + {%- if os_distribution == 'ubuntu' %} + https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img + {%- elif os_distribution == 'rocky' %} + http://dl.rockylinux.org/pub/rocky/8.5/images/Rocky-8-GenericCloud-8.5-20211114.2.x86_64.qcow2 + {%- else -%} + https://cloud.centos.org/centos/8-stream/x86_64/images/CentOS-Stream-GenericCloud-8-20210210.0.x86_64.qcow2 + {%- endif %} + +# Capacity of the infra VM data volume. +infra_vm_data_capacity: 100G + +# Format of the infra VM data volume. +infra_vm_data_format: qcow2 + +# List of network interfaces to attach to the infra VM. +infra_vm_interfaces: "{{ network_interfaces | sort | map('net_libvirt_vm_network') | list }}" + +# Hypervisor that the VM runs on. +infra_vm_hypervisor: "{{ groups['seed-hypervisor'] | first }}" + +# Customise ansible_ssh_extra_args for the test that checks SSH connectivity +# after provisioning. Defaults to disabling ssh host key checking. +infra_vm_wait_connection_ssh_extra_args: '-o StrictHostKeyChecking=no' + +# OS family. Needed for config drive generation. +infra_vm_os_family: "{{ 'RedHat' if os_distribution == 'centos' else 'Debian' }}" + +############################################################################### +# Infrastructure VM node configuration. + +# User with which to access the infrastructure vm via SSH during bootstrap, in +# order to setup the Kayobe user account. Default is {{ os_distribution }}. +infra_vm_bootstrap_user: "{{ os_distribution }}" + +############################################################################### +# Infrastructure VM network interface configuration. + +# List of networks to which infrastructure vm nodes are attached. +infra_vm_network_interfaces: > + {{ (infra_vm_default_network_interfaces + + infra_vm_extra_network_interfaces) | select | unique | list }} + +# List of default networks to which infrastructure vm nodes are attached. +infra_vm_default_network_interfaces: > + {{ [admin_oc_net_name] | select | unique | list }} + +# List of extra networks to which infrastructure vm nodes are attached. +infra_vm_extra_network_interfaces: [] + +############################################################################### +# Infrastructure VM node software RAID configuration. + +# List of software RAID arrays. See mrlesmithjr.mdadm role for format. +infra_vm_mdadm_arrays: [] + +############################################################################### +# Infrastructure VM node encryption configuration. + +# List of block devices to encrypt. See stackhpc.luks role for format. +infra_vm_luks_devices: [] + +############################################################################### +# Infrastructure VM node LVM configuration. + +# List of infrastructure vm volume groups. See mrlesmithjr.manage-lvm role for +# format. +infra_vm_lvm_groups: "{{ infra_vm_lvm_groups_default + infra_vm_lvm_groups_extra }}" + +# Default list of infrastructure vm volume groups. See mrlesmithjr.manage-lvm +# role for format. +infra_vm_lvm_groups_default: "{{ [infra_vm_lvm_group_data] if infra_vm_lvm_group_data_enabled | bool else [] }}" + +# Additional list of infrastructure vm volume groups. See mrlesmithjr.manage-lvm +# role for format. +infra_vm_lvm_groups_extra: [] + +# Whether a 'data' LVM volume group should exist on the infrastructure vm. By +# default this contains a 'docker-volumes' logical volume for Docker volume +# storage. It will also be used for Docker container and image storage if # +# 'docker_storage_driver' is set to 'devicemapper'. Default is true if # +# 'docker_storage_driver' is set to 'devicemapper', or false otherwise. +infra_vm_lvm_group_data_enabled: "{{ docker_storage_driver == 'devicemapper' }}" + +# Infrastructure VM LVM volume group for data. See mrlesmithjr.manage-lvm role +# for format. +infra_vm_lvm_group_data: + vgname: data + disks: "{{ infra_vm_lvm_group_data_disks }}" + create: True + lvnames: "{{ infra_vm_lvm_group_data_lvs }}" + +# List of disks for use by infrastructure vm LVM data volume group. Default to +# an invalid value to require configuration. +infra_vm_lvm_group_data_disks: + - changeme + +# List of LVM logical volumes for the data volume group. +infra_vm_lvm_group_data_lvs: + - "{{ infra_vm_lvm_group_data_lv_docker_volumes }}" + +# Docker volumes LVM backing volume. +infra_vm_lvm_group_data_lv_docker_volumes: + lvname: docker-volumes + size: "{{ infra_vm_lvm_group_data_lv_docker_volumes_size }}" + create: True + filesystem: "{{ infra_vm_lvm_group_data_lv_docker_volumes_fs }}" + mount: True + mntp: /var/lib/docker/volumes + +# Size of docker volumes LVM backing volume. +infra_vm_lvm_group_data_lv_docker_volumes_size: 75%VG + +# Filesystem for docker volumes LVM backing volume. ext4 allows for shrinking. +infra_vm_lvm_group_data_lv_docker_volumes_fs: ext4 + +############################################################################### +# Infrastructure VM node sysctl configuration. + +# Dict of sysctl parameters to set. +infra_vm_sysctl_parameters: {} + +############################################################################### +# Infrastructure VM node user configuration. + +# List of users to create. This should be in a format accepted by the +# singleplatform-eng.users role. +infra_vm_users: "{{ users_default }}" + +############################################################################### +# Infrastructure VM node firewalld configuration. + +# Whether to install and enable firewalld. +infra_vm_firewalld_enabled: false + +# A list of zones to create. Each item is a dict containing a 'zone' item. +infra_vm_firewalld_zones: [] + +# A firewalld zone to set as the default. Default is unset, in which case the +# default zone will not be changed. +infra_vm_firewalld_default_zone: + +# A list of firewall rules to apply. Each item is a dict containing arguments +# to pass to the firewalld module. Arguments are omitted if not provided, with +# the following exceptions: +# - offline: true +# - permanent: true +# - state: enabled +infra_vm_firewalld_rules: [] diff --git a/ansible/group_vars/all/kolla b/ansible/group_vars/all/kolla index b1bdd9d16..6059e886d 100644 --- a/ansible/group_vars/all/kolla +++ b/ansible/group_vars/all/kolla @@ -53,8 +53,9 @@ kolla_node_custom_config_path: "{{ kolla_config_path }}/config" # Kolla configuration. # Kolla base container image distribution. Options are "centos", "debian", -# "ubuntu". Default is {{ os_distribution }}. -kolla_base_distro: "{{ os_distribution }}" +# "ubuntu". Default is +# {{ 'centos' if os_distribution == 'rocky' else os_distribution }}. +kolla_base_distro: "{{ 'centos' if os_distribution == 'rocky' else os_distribution }}" # Kolla container image type: binary or source. kolla_install_type: "binary" @@ -121,6 +122,8 @@ overcloud_container_image_regex_map: enabled: "{{ kolla_enable_barbican | bool }}" - regex: ^blazar enabled: "{{ kolla_enable_blazar | bool }}" + - regex: ^caso + enabled: "{{ kolla_enable_caso | bool }}" - regex: ^ceilometer enabled: "{{ kolla_enable_ceilometer | bool }}" - regex: ^chrony @@ -524,6 +527,7 @@ kolla_enable_rabbitmq: "yes" kolla_enable_aodh: "no" kolla_enable_barbican: "no" kolla_enable_blazar: "no" +kolla_enable_caso: "no" kolla_enable_ceilometer: "no" kolla_enable_central_logging: "no" kolla_enable_chrony: "no" @@ -562,6 +566,7 @@ kolla_enable_murano: "no" kolla_enable_neutron_mlnx: "no" kolla_enable_neutron_provider_networks: "no" kolla_enable_neutron_sriov: "no" +kolla_enable_nova_libvirt_container: "yes" kolla_enable_octavia: "no" kolla_enable_openvswitch: "{{ kolla_enable_neutron | bool }}" kolla_enable_ovn: "no" @@ -592,9 +597,9 @@ kolla_enable_zun: "no" ############################################################################### # Passwords and credentials. -# Dictionary containing default custom passwords to add or override in the +# Dictionary containing base custom passwords to add or override in the # Kolla passwords file. -kolla_ansible_default_custom_passwords: +kolla_ansible_base_custom_passwords: # SSH key authorized in hosts deployed by Bifrost. bifrost_ssh_key: private_key: "{{ lookup('file', ssh_private_key_path) }}" @@ -605,6 +610,19 @@ kolla_ansible_default_custom_passwords: public_key: "{{ lookup('file', ssh_public_key_path) }}" docker_registry_password: "{{ kolla_docker_registry_password }}" +# Dictionary containing libvirt custom passwords to add or override in the +# Kolla passwords file. +kolla_ansible_libvirt_custom_passwords: + libvirt_sasl_password: "{{ compute_libvirt_sasl_password }}" + +# Dictionary containing default custom passwords to add or override in the +# Kolla passwords file. +kolla_ansible_default_custom_passwords: >- + {{ kolla_ansible_base_custom_passwords | + combine(kolla_ansible_libvirt_custom_passwords + if compute_libvirt_enabled | bool and compute_libvirt_enable_sasl | bool + else {}) }} + # Dictionary containing custom passwords to add or override in the Kolla # passwords file. kolla_ansible_custom_passwords: "{{ kolla_ansible_default_custom_passwords }}" @@ -660,3 +678,18 @@ kolla_internal_tls_cert: # in admin-openrc.sh file when TLS is enabled, instead of Kolla-Ansible's # default. kolla_internal_fqdn_cacert: + +############################################################################### +# Proxy configuration + +# HTTP proxy URL (format: http(s)://[user:password@]proxy_name:port) used by +# Kolla. Default value is "{{ http_proxy }}". +kolla_http_proxy: "{{ http_proxy }}" + +# HTTPS proxy URL (format: http(s)://[user:password@]proxy_name:port) used by +# Kolla. Default value is "{{ https_proxy }}". +kolla_https_proxy: "{{ https_proxy }}" + +# List of domains, hostnames, IP addresses and networks for which no proxy is +# used. Default value is "{{ no_proxy }}". +kolla_no_proxy: "{{ no_proxy }}" diff --git a/ansible/group_vars/all/monitoring b/ansible/group_vars/all/monitoring index 0668cde78..21d2bf727 100644 --- a/ansible/group_vars/all/monitoring +++ b/ansible/group_vars/all/monitoring @@ -94,3 +94,24 @@ monitoring_sysctl_parameters: "{{ controller_sysctl_parameters }}" # List of users to create. This should be in a format accepted by the # singleplatform-eng.users role. monitoring_users: "{{ controller_users }}" + +############################################################################### +# Monitoring node firewalld configuration. + +# Whether to install and enable firewalld. +monitoring_firewalld_enabled: "{{ controller_firewalld_enabled }}" + +# A list of zones to create. Each item is a dict containing a 'zone' item. +monitoring_firewalld_zones: "{{ controller_firewalld_zones }}" + +# A firewalld zone to set as the default. Default is unset, in which case the +# default zone will not be changed. +monitoring_firewalld_default_zone: "{{ controller_firewalld_default_zone }}" + +# A list of firewall rules to apply. Each item is a dict containing arguments +# to pass to the firewalld module. Arguments are omitted if not provided, with +# the following exceptions: +# - offline: true +# - permanent: true +# - state: enabled +monitoring_firewalld_rules: "{{ controller_firewalld_rules }}" diff --git a/ansible/group_vars/all/overcloud-dib b/ansible/group_vars/all/overcloud-dib new file mode 100644 index 000000000..737d91832 --- /dev/null +++ b/ansible/group_vars/all/overcloud-dib @@ -0,0 +1,92 @@ +--- +# Overcloud host disk image configuration. + +############################################################################### +# Diskimage-builder configuration for overcloud host disk images. + +# Whether to build host disk images with DIB directly instead of through +# Bifrost. Setting it to true disables Bifrost image build and allows images to +# be built with the `kayobe overcloud host image build` command. Default value +# is {{ os_distribution == 'rocky' }}. This will change in a future release. +overcloud_dib_build_host_images: "{{ os_distribution == 'rocky' }}" + +# List of overcloud host disk images to build. Each element is a dict defining +# an image in a format accepted by the stackhpc.os-images role. Default is to +# build an image named "deployment_image" configured with the overcloud_dib_* +# variables defined below: {"name": "deployment_image", "elements": "{{ +# overcloud_dib_elements }}", "env": "{{ overcloud_dib_env_vars }}", +# "packages": "{{ overcloud_dib_packages }}"}. +overcloud_dib_host_images: + - name: "deployment_image" + elements: "{{ overcloud_dib_elements }}" + env: "{{ overcloud_dib_env_vars }}" + packages: "{{ overcloud_dib_packages }}" + +# DIB base OS element. Default is {{ 'rocky-container' if os_distribution == +# 'rocky' else os_distribution }}. +overcloud_dib_os_element: "{{ 'rocky-container' if os_distribution == 'rocky' else os_distribution }}" + +# DIB image OS release. Default is {{ os_release }}. +overcloud_dib_os_release: "{{ os_release }}" + +# List of default DIB elements. Default is ["centos", "cloud-init-datasources", +# "disable-selinux", "enable-serial-console", "vm"] when +# overcloud_dib_os_element is "centos", or ["rocky-container", +# "cloud-init-datasources", "disable-selinux", "enable-serial-console", "vm"] +# when overcloud_dib_os_element is "rocky" or +# ["ubuntu", "cloud-init-datasources", "enable-serial-console", "vm"] +# when overcloud_dib_os_element is "ubuntu". +overcloud_dib_elements_default: + - "{{ overcloud_dib_os_element }}" + - "cloud-init-datasources" + - "{% if overcloud_dib_os_element in ['centos', 'rocky'] %}disable-selinux{% endif %}" + - "enable-serial-console" + - "vm" + +# List of additional DIB elements. Default is none. +overcloud_dib_elements_extra: [] + +# List of DIB elements. Default is a combination of +# overcloud_dib_elements_default and overcloud_dib_elements_extra. +overcloud_dib_elements: "{{ overcloud_dib_elements_default | select | list + overcloud_dib_elements_extra }}" + +# DIB default environment variables. Default is +# {"DIB_BOOTLOADER_DEFAULT_CMDLINE": "nofb nomodeset gfxpayload=text +# net.ifnames=1", "DIB_CLOUD_INIT_DATASOURCES": "ConfigDrive", +# "DIB_CONTAINERFILE_RUNTIME": "docker", "DIB_CONTAINERFILE_NETWORK_DRIVER": +# "host", "DIB_RELEASE": "{{ overcloud_dib_os_release }}"}. +overcloud_dib_env_vars_default: + DIB_BOOTLOADER_DEFAULT_CMDLINE: "nofb nomodeset gfxpayload=text net.ifnames=1" + DIB_CLOUD_INIT_DATASOURCES: "ConfigDrive" + DIB_CONTAINERFILE_RUNTIME: "docker" + DIB_CONTAINERFILE_NETWORK_DRIVER: "host" + DIB_RELEASE: "{{ overcloud_dib_os_release }}" + +# DIB additional environment variables. Default is none. +overcloud_dib_env_vars_extra: {} + +# DIB environment variables. Default is combination of +# overcloud_dib_env_vars_default and overcloud_dib_env_vars_extra. +overcloud_dib_env_vars: "{{ overcloud_dib_env_vars_default | combine(overcloud_dib_env_vars_extra) }}" + +# List of DIB packages to install. Default is to install no extra packages. +overcloud_dib_packages: [] + +# List of default git repositories containing Diskimage Builder (DIB) elements. +# See stackhpc.os-images role for usage. Default is empty. +overcloud_dib_git_elements_default: [] + +# List of additional git repositories containing Diskimage Builder (DIB) +# elements. See stackhpc.os-images role for usage. Default is empty. +overcloud_dib_git_elements_extra: [] + +# List of git repositories containing Diskimage Builder (DIB) elements. See +# stackhpc.os-images role for usage. Default is a combination of +# overcloud_dib_git_elements_default and overcloud_dib_git_elements_extra. +overcloud_dib_git_elements: >- + {{ overcloud_dib_git_elements_default + overcloud_dib_git_elements_extra }} + +# Upper constraints file for installing packages in the virtual environment +# used for building overcloud host disk images. Default is {{ +# pip_upper_constraints_file }}. +overcloud_dib_upper_constraints_file: "{{ pip_upper_constraints_file }}" diff --git a/ansible/group_vars/all/proxy b/ansible/group_vars/all/proxy new file mode 100644 index 000000000..eb791bbef --- /dev/null +++ b/ansible/group_vars/all/proxy @@ -0,0 +1,21 @@ +--- +############################################################################### +# Configuration of HTTP(S) proxies. + +# HTTP proxy URL (format: http(s)://[user:password@]proxy_name:port). By +# default no proxy is used. +http_proxy: "" + +# HTTPS proxy URL (format: http(s)://[user:password@]proxy_name:port). By +# default no proxy is used. +https_proxy: "" + +# List of domains, hostnames, IP addresses and networks for which no proxy is +# used. Defaults to ["127.0.0.1", "localhost", "{{ ('http://' ~ +# docker_registry) | urlsplit('hostname') }}"] if docker_registry is set, or +# ["127.0.0.1", "localhost"] otherwise. This is configured only if either +# http_proxy or https_proxy is set. +no_proxy: + - "127.0.0.1" + - "localhost" + - "{{ ('http://' ~ docker_registry) | urlsplit('hostname') if docker_registry else '' }}" diff --git a/ansible/group_vars/all/seed b/ansible/group_vars/all/seed index 2d75cd5e7..18dde9570 100644 --- a/ansible/group_vars/all/seed +++ b/ansible/group_vars/all/seed @@ -112,3 +112,24 @@ seed_users: "{{ users_default }}" # post: "{{ kayobe_env_config_path }}/containers/squid/post.yml" # seed_containers: {} + +############################################################################### +# Seed node firewalld configuration. + +# Whether to install and enable firewalld. +seed_firewalld_enabled: false + +# A list of zones to create. Each item is a dict containing a 'zone' item. +seed_firewalld_zones: [] + +# A firewalld zone to set as the default. Default is unset, in which case the +# default zone will not be changed. +seed_firewalld_default_zone: + +# A list of firewall rules to apply. Each item is a dict containing arguments +# to pass to the firewalld module. Arguments are omitted if not provided, with +# the following exceptions: +# - offline: true +# - permanent: true +# - state: enabled +seed_firewalld_rules: [] diff --git a/ansible/group_vars/all/seed-hypervisor b/ansible/group_vars/all/seed-hypervisor index 0cbe20b07..89438b796 100644 --- a/ansible/group_vars/all/seed-hypervisor +++ b/ansible/group_vars/all/seed-hypervisor @@ -128,3 +128,24 @@ seed_hypervisor_sysctl_parameters: {} # List of users to create. This should be in a format accepted by the # singleplatform-eng.users role. seed_hypervisor_users: "{{ users_default }}" + +############################################################################### +# Seed hypervisor node firewalld configuration. + +# Whether to install and enable firewalld. +seed_hypervisor_firewalld_enabled: false + +# A list of zones to create. Each item is a dict containing a 'zone' item. +seed_hypervisor_firewalld_zones: [] + +# A firewalld zone to set as the default. Default is unset, in which case the +# default zone will not be changed. +seed_hypervisor_firewalld_default_zone: + +# A list of firewall rules to apply. Each item is a dict containing arguments +# to pass to the firewalld module. Arguments are omitted if not provided, with +# the following exceptions: +# - offline: true +# - permanent: true +# - state: enabled +seed_hypervisor_firewalld_rules: [] diff --git a/ansible/group_vars/all/seed-vm b/ansible/group_vars/all/seed-vm index 77f501cc3..09ff72657 100644 --- a/ansible/group_vars/all/seed-vm +++ b/ansible/group_vars/all/seed-vm @@ -42,12 +42,17 @@ seed_vm_root_format: qcow2 # Base image for the seed VM root volume. Default is # "https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img" -# when os_distribution is "ubuntu", or +# when os_distribution is "ubuntu", +# http://dl.rockylinux.org/pub/rocky/8.5/images/Rocky-8-GenericCloud-8.5-20211114.2.x86_64.qcow2 +# when os_distribution is "rocky", +# or # "https://cloud.centos.org/centos/8-stream/x86_64/images/CentOS-Stream-GenericCloud-8-20220913.0.x86_64.qcow2" # otherwise. seed_vm_root_image: >- {%- if os_distribution == 'ubuntu' %} https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img + {%- elif os_distribution == 'rocky' %} + http://dl.rockylinux.org/pub/rocky/8.5/images/Rocky-8-GenericCloud-8.5-20211114.2.x86_64.qcow2 {%- else -%} https://cloud.centos.org/centos/8-stream/x86_64/images/CentOS-Stream-GenericCloud-8-20220913.0.x86_64.qcow2 {%- endif %} diff --git a/ansible/group_vars/all/storage b/ansible/group_vars/all/storage index 129baf230..7ecb12193 100644 --- a/ansible/group_vars/all/storage +++ b/ansible/group_vars/all/storage @@ -145,3 +145,24 @@ storage_sysctl_parameters: {} # List of users to create. This should be in a format accepted by the # singleplatform-eng.users role. storage_users: "{{ users_default }}" + +############################################################################### +# Storage node firewalld configuration. + +# Whether to install and enable firewalld. +storage_firewalld_enabled: false + +# A list of zones to create. Each item is a dict containing a 'zone' item. +storage_firewalld_zones: [] + +# A firewalld zone to set as the default. Default is unset, in which case the +# default zone will not be changed. +storage_firewalld_default_zone: + +# A list of firewall rules to apply. Each item is a dict containing arguments +# to pass to the firewalld module. Arguments are omitted if not provided, with +# the following exceptions: +# - offline: true +# - permanent: true +# - state: enabled +storage_firewalld_rules: [] diff --git a/ansible/group_vars/compute/firewall b/ansible/group_vars/compute/firewall new file mode 100644 index 000000000..f1d30d51a --- /dev/null +++ b/ansible/group_vars/compute/firewall @@ -0,0 +1,21 @@ +--- +############################################################################### +# Compute node firewalld configuration. + +# Whether to install and enable firewalld. +firewalld_enabled: "{{ compute_firewalld_enabled }}" + +# A list of zones to create. Each item is a dict containing a 'zone' item. +firewalld_zones: "{{ compute_firewalld_zones }}" + +# A firewalld zone to set as the default. Default is unset, in which case the +# default zone will not be changed. +firewalld_default_zone: "{{ compute_firewalld_default_zone }}" + +# A list of firewall rules to apply. Each item is a dict containing arguments +# to pass to the firewalld module. Arguments are omitted if not provided, with +# the following exceptions: +# - offline: true +# - permanent: true +# - state: enabled +firewalld_rules: "{{ compute_firewalld_rules }}" diff --git a/ansible/group_vars/controllers/firewall b/ansible/group_vars/controllers/firewall new file mode 100644 index 000000000..dce2e0e70 --- /dev/null +++ b/ansible/group_vars/controllers/firewall @@ -0,0 +1,21 @@ +--- +############################################################################### +# Controller node firewalld configuration. + +# Whether to install and enable firewalld. +firewalld_enabled: "{{ controller_firewalld_enabled }}" + +# A list of zones to create. Each item is a dict containing a 'zone' item. +firewalld_zones: "{{ controller_firewalld_zones }}" + +# A firewalld zone to set as the default. Default is unset, in which case the +# default zone will not be changed. +firewalld_default_zone: "{{ controller_firewalld_default_zone }}" + +# A list of firewall rules to apply. Each item is a dict containing arguments +# to pass to the firewalld module. Arguments are omitted if not provided, with +# the following exceptions: +# - offline: true +# - permanent: true +# - state: enabled +firewalld_rules: "{{ controller_firewalld_rules }}" diff --git a/ansible/group_vars/infra-vms/ansible-host b/ansible/group_vars/infra-vms/ansible-host new file mode 100644 index 000000000..7692dfac8 --- /dev/null +++ b/ansible/group_vars/infra-vms/ansible-host @@ -0,0 +1,3 @@ +--- +# Host/IP with which to access the infra VMs via SSH. +ansible_host: "{{ admin_oc_net_name | net_ip }}" diff --git a/ansible/group_vars/infra-vms/ansible-user b/ansible/group_vars/infra-vms/ansible-user new file mode 100644 index 000000000..cc7fe96ea --- /dev/null +++ b/ansible/group_vars/infra-vms/ansible-user @@ -0,0 +1,7 @@ +--- +# User with which to access the infra VMs via SSH. +ansible_user: "{{ kayobe_ansible_user }}" + +# User with which to access the infra VMs before the kayobe_ansible_user +# account has been created. +bootstrap_user: "{{ infra_vm_bootstrap_user }}" diff --git a/ansible/group_vars/infra-vms/firewall b/ansible/group_vars/infra-vms/firewall new file mode 100644 index 000000000..089926ec5 --- /dev/null +++ b/ansible/group_vars/infra-vms/firewall @@ -0,0 +1,21 @@ +--- +############################################################################### +# Infra VM node firewalld configuration. + +# Whether to install and enable firewalld. +firewalld_enabled: "{{ infra_vm_firewalld_enabled }}" + +# A list of zones to create. Each item is a dict containing a 'zone' item. +firewalld_zones: "{{ infra_vm_firewalld_zones }}" + +# A firewalld zone to set as the default. Default is unset, in which case the +# default zone will not be changed. +firewalld_default_zone: "{{ infra_vm_firewalld_default_zone }}" + +# A list of firewall rules to apply. Each item is a dict containing arguments +# to pass to the firewalld module. Arguments are omitted if not provided, with +# the following exceptions: +# - offline: true +# - permanent: true +# - state: enabled +firewalld_rules: "{{ infra_vm_firewalld_rules }}" diff --git a/ansible/group_vars/infra-vms/luks b/ansible/group_vars/infra-vms/luks new file mode 100644 index 000000000..94b1e8c58 --- /dev/null +++ b/ansible/group_vars/infra-vms/luks @@ -0,0 +1,6 @@ +--- +############################################################################### +# Infra VM node encryption configuration. + +# List of block devices to encrypt. See stackhpc.luks role for format. +luks_devices: "{{ infra_vm_luks_devices }}" diff --git a/ansible/group_vars/infra-vms/lvm b/ansible/group_vars/infra-vms/lvm new file mode 100644 index 000000000..fd676af20 --- /dev/null +++ b/ansible/group_vars/infra-vms/lvm @@ -0,0 +1,6 @@ +--- +############################################################################### +# Infra VM node LVM configuration. + +# List of LVM volume groups. +lvm_groups: "{{ infra_vm_lvm_groups }}" diff --git a/ansible/group_vars/infra-vms/mdadm b/ansible/group_vars/infra-vms/mdadm new file mode 100644 index 000000000..8c3c89ccb --- /dev/null +++ b/ansible/group_vars/infra-vms/mdadm @@ -0,0 +1,6 @@ +--- +############################################################################### +# Infra VM node software RAID configuration. + +# List of software RAID arrays. See mrlesmithjr.mdadm role for format. +mdadm_arrays: "{{ infra_vm_mdadm_arrays }}" diff --git a/ansible/group_vars/infra-vms/network b/ansible/group_vars/infra-vms/network new file mode 100644 index 000000000..32d5eabb6 --- /dev/null +++ b/ansible/group_vars/infra-vms/network @@ -0,0 +1,6 @@ +--- +############################################################################### +# Network interface attachments. + +# List of networks to which these nodes are attached. +network_interfaces: "{{ infra_vm_network_interfaces | unique | list }}" diff --git a/ansible/group_vars/infra-vms/sysctl b/ansible/group_vars/infra-vms/sysctl new file mode 100644 index 000000000..1a9fd2b75 --- /dev/null +++ b/ansible/group_vars/infra-vms/sysctl @@ -0,0 +1,3 @@ +--- +# Dict of sysctl parameters to set. +sysctl_parameters: "{{ infra_vm_sysctl_parameters }}" diff --git a/ansible/group_vars/infra-vms/users b/ansible/group_vars/infra-vms/users new file mode 100644 index 000000000..f8951d3aa --- /dev/null +++ b/ansible/group_vars/infra-vms/users @@ -0,0 +1,4 @@ +--- +# List of users to create. This should be in a format accepted by the +# singleplatform-eng.users role. +users: "{{ infra_vm_users }}" diff --git a/ansible/group_vars/monitoring/firewall b/ansible/group_vars/monitoring/firewall new file mode 100644 index 000000000..a1b151527 --- /dev/null +++ b/ansible/group_vars/monitoring/firewall @@ -0,0 +1,33 @@ +--- +############################################################################### +# Monitoring node firewalld configuration. + +# Whether to install and enable firewalld. +firewalld_enabled: >- + {{ controller_firewalld_enabled + if inventory_hostname in groups['controllers'] else + monitoring_firewalld_enabled }} + +# A list of zones to create. Each item is a dict containing a 'zone' item. +firewalld_zones: > + {{ controller_firewalld_zones + if inventory_hostname in groups['controllers'] else + monitoring_firewalld_zones }} + +# A firewalld zone to set as the default. Default is unset, in which case the +# default zone will not be changed. +firewalld_default_zone: >- + {{ controller_firewalld_default_zone + if inventory_hostname in groups['controllers'] else + monitoring_firewalld_default_zone }}" + +# A list of firewall rules to apply. Each item is a dict containing arguments +# to pass to the firewalld module. Arguments are omitted if not provided, with +# the following exceptions: +# - offline: true +# - permanent: true +# - state: enabled +firewalld_rules: > + {{ controller_firewalld_rules + if inventory_hostname in groups['controllers'] else + monitoring_firewalld_rules }}" diff --git a/ansible/group_vars/seed-hypervisor/firewall b/ansible/group_vars/seed-hypervisor/firewall new file mode 100644 index 000000000..9de277119 --- /dev/null +++ b/ansible/group_vars/seed-hypervisor/firewall @@ -0,0 +1,21 @@ +--- +############################################################################### +# Seed Hypervisor node firewalld configuration. + +# Whether to install and enable firewalld. +firewalld_enabled: "{{ seed_hypervisor_firewalld_enabled }}" + +# A list of zones to create. Each item is a dict containing a 'zone' item. +firewalld_zones: "{{ seed_hypervisor_firewalld_zones }}" + +# A firewalld zone to set as the default. Default is unset, in which case the +# default zone will not be changed. +firewalld_default_zone: "{{ seed_hypervisor_firewalld_default_zone }}" + +# A list of firewall rules to apply. Each item is a dict containing arguments +# to pass to the firewalld module. Arguments are omitted if not provided, with +# the following exceptions: +# - offline: true +# - permanent: true +# - state: enabled +firewalld_rules: "{{ seed_hypervisor_firewalld_rules }}" diff --git a/ansible/group_vars/seed/firewall b/ansible/group_vars/seed/firewall new file mode 100644 index 000000000..80cd15a27 --- /dev/null +++ b/ansible/group_vars/seed/firewall @@ -0,0 +1,21 @@ +--- +############################################################################### +# Seed node firewalld configuration. + +# Whether to install and enable firewalld. +firewalld_enabled: "{{ seed_firewalld_enabled }}" + +# A list of zones to create. Each item is a dict containing a 'zone' item. +firewalld_zones: "{{ seed_firewalld_zones }}" + +# A firewalld zone to set as the default. Default is unset, in which case the +# default zone will not be changed. +firewalld_default_zone: "{{ seed_firewalld_default_zone }}" + +# A list of firewall rules to apply. Each item is a dict containing arguments +# to pass to the firewalld module. Arguments are omitted if not provided, with +# the following exceptions: +# - offline: true +# - permanent: true +# - state: enabled +firewalld_rules: "{{ seed_firewalld_rules }}" diff --git a/ansible/group_vars/storage/firewall b/ansible/group_vars/storage/firewall new file mode 100644 index 000000000..a3721fef3 --- /dev/null +++ b/ansible/group_vars/storage/firewall @@ -0,0 +1,21 @@ +--- +############################################################################### +# Storage node firewalld configuration. + +# Whether to install and enable firewalld. +firewalld_enabled: "{{ storage_firewalld_enabled }}" + +# A list of zones to create. Each item is a dict containing a 'zone' item. +firewalld_zones: "{{ storage_firewalld_zones }}" + +# A firewalld zone to set as the default. Default is unset, in which case the +# default zone will not be changed. +firewalld_default_zone: "{{ storage_firewalld_default_zone }}" + +# A list of firewall rules to apply. Each item is a dict containing arguments +# to pass to the firewalld module. Arguments are omitted if not provided, with +# the following exceptions: +# - offline: true +# - permanent: true +# - state: enabled +firewalld_rules: "{{ storage_firewalld_rules }}" diff --git a/ansible/host-command-run.yml b/ansible/host-command-run.yml index 18a9dd880..b074ca1ed 100644 --- a/ansible/host-command-run.yml +++ b/ansible/host-command-run.yml @@ -1,7 +1,7 @@ --- - name: Run a command gather_facts: False - hosts: seed-hypervisor:seed:overcloud + hosts: seed-hypervisor:seed:overcloud:infra-vms tasks: - name: Run a command shell: "{{ host_command_to_run }}" diff --git a/ansible/host-package-update.yml b/ansible/host-package-update.yml index 853ed2c9c..06bc73077 100644 --- a/ansible/host-package-update.yml +++ b/ansible/host-package-update.yml @@ -1,6 +1,6 @@ --- - name: Update host packages - hosts: seed-hypervisor:seed:overcloud + hosts: seed-hypervisor:seed:overcloud:infra-vms vars: # Optionally set this to a list of packages to update. Default behaviour is # to update all packages. diff --git a/ansible/infra-vm-deprovision.yml b/ansible/infra-vm-deprovision.yml new file mode 100644 index 000000000..f350f9e0d --- /dev/null +++ b/ansible/infra-vm-deprovision.yml @@ -0,0 +1,38 @@ +--- + +- name: Set facts about infra VMs + gather_facts: false + hosts: "{{ infra_vm_limit | default('infra-vms') }}" + tags: + - always + tasks: + - name: Group virtual machines by hypervisor + group_by: + key: infra_vms_{{ infra_vm_hypervisor }} + # FIXME(mgoddard): Is delegate_to necessary? + delegate_to: "{{ infra_vm_hypervisor }}" + changed_when: false + +- name: Ensure defined infra VMs are destroyed + hosts: hypervisors + tags: + - infra-vm-deprovision + tasks: + - import_role: + name: infra-vms + vars: + infra_vm_action: destroy + infra_vm_vms: "{{ groups['infra_vms_' ~ inventory_hostname ] | default([]) }}" + +- name: Set facts about infra VMs + gather_facts: false + hosts: "{{ infra_vm_limit | default('infra-vms') }}" + tags: + - infra-vm-deprovision + tasks: + - name: Remove host key from known hosts + known_hosts: + name: "{{ ansible_host }}" + state: "absent" + delegate_to: localhost + throttle: 1 diff --git a/ansible/infra-vm-provision.yml b/ansible/infra-vm-provision.yml new file mode 100644 index 000000000..28d4e3253 --- /dev/null +++ b/ansible/infra-vm-provision.yml @@ -0,0 +1,40 @@ +--- + +- name: Set facts about infra VMs + gather_facts: false + hosts: "{{ infra_vm_limit | default('infra-vms') }}" + tags: + - always + tasks: + - name: Group virtual machines by hypervisor + group_by: + key: infra_vms_{{ infra_vm_hypervisor }} + # FIXME(mgoddard): Is delegate_to necessary? + delegate_to: "{{ infra_vm_hypervisor }}" + changed_when: false + +- name: Ensure defined infra VMs are deployed + hosts: hypervisors + tags: + - infra-vm-provision + tasks: + - import_role: + name: infra-vms + vars: + infra_vm_vms: "{{ groups['infra_vms_' ~ inventory_hostname ] | default([]) }}" + +- name: Wait for infra VMs to be accessible + hosts: "{{ infra_vm_limit | default('infra-vms') }}" + gather_facts: false + tags: + - infra-vm-provision + tasks: + - name: Wait for SSH access to the infra VM + wait_for: + host: "{{ hostvars[inventory_hostname].ansible_host }}" + port: 22 + state: started + # NOTE: Ensure we exceed the 5 minute DHCP timeout of the eth0 + # interface if necessary. + timeout: 360 + delegate_to: localhost diff --git a/ansible/ip-allocation.yml b/ansible/ip-allocation.yml index e34c3348c..81038ed5f 100644 --- a/ansible/ip-allocation.yml +++ b/ansible/ip-allocation.yml @@ -1,6 +1,6 @@ --- - name: Ensure IP addresses are allocated - hosts: seed-hypervisor:seed:overcloud + hosts: seed-hypervisor:seed:overcloud:infra-vms tags: - ip-allocation gather_facts: no diff --git a/ansible/kayobe-ansible-user.yml b/ansible/kayobe-ansible-user.yml index 44c9235e0..eea27115d 100644 --- a/ansible/kayobe-ansible-user.yml +++ b/ansible/kayobe-ansible-user.yml @@ -7,7 +7,7 @@ # bootstrap process if the account is inaccessible. - name: Determine whether user bootstrapping is required - hosts: seed-hypervisor:seed:overcloud + hosts: seed-hypervisor:seed:overcloud:infra-vms gather_facts: false tags: - kayobe-ansible-user @@ -86,7 +86,7 @@ become: True - name: Verify that the Kayobe Ansible user account is accessible - hosts: seed-hypervisor:seed:overcloud + hosts: seed-hypervisor:seed:overcloud:infra-vms gather_facts: false tags: - kayobe-ansible-user diff --git a/ansible/kayobe-target-venv.yml b/ansible/kayobe-target-venv.yml index 02bd526f7..14a8b8761 100644 --- a/ansible/kayobe-target-venv.yml +++ b/ansible/kayobe-target-venv.yml @@ -3,7 +3,7 @@ # when running kayobe. - name: Ensure a virtualenv exists for kayobe - hosts: seed:seed-hypervisor:overcloud + hosts: seed:seed-hypervisor:overcloud:infra-vms gather_facts: False tags: - kayobe-target-venv diff --git a/ansible/kolla-ansible.yml b/ansible/kolla-ansible.yml index 7cb71899c..a469e474d 100644 --- a/ansible/kolla-ansible.yml +++ b/ansible/kolla-ansible.yml @@ -51,19 +51,6 @@ - kolla_environment_file.stat.exists - kolla_environment != kayobe_environment - # Configuration of extra user-provided Kolla globals. - - name: Check whether a Kolla extra globals configuration file exists - stat: - path: "{{ kayobe_env_config_path ~ '/kolla/globals.yml' }}" - get_checksum: False - get_md5: False - mime: False - register: globals_stat - - - name: Read the Kolla extra globals configuration file - set_fact: - kolla_extra_globals: "{{ lookup('template', kayobe_env_config_path ~ '/kolla/globals.yml') | from_yaml }}" - when: globals_stat.stat.exists tags: - config @@ -116,8 +103,12 @@ kolla_inspector_netmask: "{{ inspection_net_name | net_mask }}" kolla_inspector_default_gateway: "{{ inspection_net_name | net_inspection_gateway or inspection_net_name | net_gateway }}" kolla_inspector_extra_kernel_options: "{{ inspector_extra_kernel_options }}" + kolla_libvirt_tls: "{{ compute_libvirt_enable_tls | bool }}" kolla_enable_host_ntp: false docker_daemon_mtu: "{{ public_net_name | net_mtu | default }}" + kolla_globals_paths_extra: + - "{{ kayobe_config_path }}" + - "{{ kayobe_env_config_path }}" - name: Generate Kolla Ansible host vars for the seed host hosts: seed diff --git a/ansible/kolla-bifrost-hostvars.yml b/ansible/kolla-bifrost-hostvars.yml index e5ec431de..e43bcd7e6 100644 --- a/ansible/kolla-bifrost-hostvars.yml +++ b/ansible/kolla-bifrost-hostvars.yml @@ -14,6 +14,8 @@ ansible_host: "{{ hostvars[seed_host].ansible_host | default(seed_host) }}" bifrost_hostvars: addressing_mode: static + deploy_image_filename: "{{ kolla_bifrost_deploy_image_filename }}" + deploy_image_rootfs: "{{ kolla_bifrost_deploy_image_rootfs | default(omit, true) }}" ipv4_interface_mac: "{% raw %}{{ extra.pxe_interface_mac | default }}{% endraw %}" ipv4_address: "{{ admin_oc_net_name | net_ip }}" ipv4_subnet_mask: "{{ admin_oc_net_name | net_mask }}" diff --git a/ansible/kolla-bifrost.yml b/ansible/kolla-bifrost.yml index 4a29eeb25..4ddda432a 100644 --- a/ansible/kolla-bifrost.yml +++ b/ansible/kolla-bifrost.yml @@ -3,23 +3,6 @@ hosts: localhost tags: - kolla-bifrost - vars: - kolla_bifrost_extra_globals_path: "{{ kayobe_env_config_path ~ '/kolla/config/bifrost/bifrost.yml' }}" - - pre_tasks: - - name: Check whether a Kolla Bifrost extra globals configuration file exists - stat: - path: "{{ kolla_bifrost_extra_globals_path }}" - get_checksum: False - get_md5: False - mime: False - register: globals_stat - - - name: Read the Kolla Bifrost extra globals configuration file - set_fact: - kolla_bifrost_extra_globals: "{{ lookup('template', kolla_bifrost_extra_globals_path) | from_yaml }}" - when: globals_stat.stat.exists - roles: - role: kolla-bifrost @@ -30,3 +13,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: + - "{{ kayobe_config_path }}" + - "{{ kayobe_env_config_path }}" diff --git a/ansible/kolla-build.yml b/ansible/kolla-build.yml index 37fd6dbd8..a02a533b1 100644 --- a/ansible/kolla-build.yml +++ b/ansible/kolla-build.yml @@ -7,4 +7,6 @@ - role: kolla kolla_install_epel: "{{ dnf_install_epel }}" - role: kolla-build - kolla_build_extra_config_path: "{{ kayobe_env_config_path }}/kolla/kolla-build.conf" + kolla_build_config_paths_extra: + - "{{ kayobe_config_path }}" + - "{{ kayobe_env_config_path }}" diff --git a/ansible/kolla-openstack.yml b/ansible/kolla-openstack.yml index a350b4d25..d5afa1945 100644 --- a/ansible/kolla-openstack.yml +++ b/ansible/kolla-openstack.yml @@ -246,3 +246,5 @@ kolla_extra_sahara: "{{ kolla_extra_config.sahara | default }}" kolla_extra_zookeeper: "{{ kolla_extra_config.zookeeper | default }}" kolla_extra_config_path: "{{ kayobe_env_config_path }}/kolla/config" + kolla_libvirt_tls: "{{ compute_libvirt_enable_tls | bool }}" + kolla_nova_libvirt_certificates_src: "{{ kayobe_env_config_path }}/certificates/libvirt" diff --git a/ansible/luks.yml b/ansible/luks.yml index 6aa65c4b2..8dea1146a 100644 --- a/ansible/luks.yml +++ b/ansible/luks.yml @@ -1,6 +1,6 @@ --- - name: Ensure encryption configuration is applied - hosts: seed-hypervisor:seed:overcloud + hosts: seed-hypervisor:seed:overcloud:infra-vms tags: - luks tasks: diff --git a/ansible/lvm.yml b/ansible/lvm.yml index 369db3f34..53a176eda 100644 --- a/ansible/lvm.yml +++ b/ansible/lvm.yml @@ -1,6 +1,6 @@ --- - name: Ensure LVM configuration is applied - hosts: seed-hypervisor:seed:overcloud + hosts: seed-hypervisor:seed:overcloud:infra-vms tags: - lvm - upgrade-check diff --git a/ansible/mdadm.yml b/ansible/mdadm.yml index 617b65e47..6b907777e 100644 --- a/ansible/mdadm.yml +++ b/ansible/mdadm.yml @@ -1,6 +1,6 @@ --- - name: Ensure software RAID configuration is applied - hosts: seed-hypervisor:seed:overcloud + hosts: seed-hypervisor:seed:overcloud:infra-vms tags: - mdadm roles: diff --git a/ansible/network-connectivity.yml b/ansible/network-connectivity.yml index cf7a4043d..ccd95230e 100644 --- a/ansible/network-connectivity.yml +++ b/ansible/network-connectivity.yml @@ -1,6 +1,6 @@ --- - name: Check network connectivity between hosts - hosts: seed:seed-hypervisor:overcloud + hosts: seed:seed-hypervisor:overcloud:infra-vms vars: # Set this to an external IP address to check. nc_external_ip: 8.8.8.8 diff --git a/ansible/network.yml b/ansible/network.yml index 5ff0b69f1..51d93b91e 100644 --- a/ansible/network.yml +++ b/ansible/network.yml @@ -1,6 +1,6 @@ --- - name: Ensure networking is configured - hosts: seed-hypervisor:seed:overcloud + hosts: seed-hypervisor:seed:overcloud:infra-vms tags: - config - network diff --git a/ansible/overcloud-host-image-build.yml b/ansible/overcloud-host-image-build.yml new file mode 100644 index 000000000..5ac5af42e --- /dev/null +++ b/ansible/overcloud-host-image-build.yml @@ -0,0 +1,44 @@ +--- +# Build and install overcloud host disk images for the seed host's ironic +# service. + +- name: Ensure overcloud host disk images are built and installed + hosts: seed + tags: + - overcloud-host-image-build + vars: + overcloud_host_image_force_rebuild: False + tasks: + - block: + - name: Validate overcloud host disk image configuration + assert: + that: + - overcloud_dib_host_images is sequence + - overcloud_dib_host_images | selectattr('name', 'undefined') | list | length == 0 + - overcloud_dib_host_images | selectattr('elements', 'undefined') | list | length == 0 + msg: "overcloud_dib_host_images set to invalid value" + + - name: Ensure overcloud host disk images are built + include_role: + name: stackhpc.os-images + vars: + os_images_venv: "{{ virtualenv_path }}/overcloud-host-image-dib" + os_images_package_state: latest + os_images_upper_constraints_file: "{{ overcloud_dib_upper_constraints_file }}" + os_images_cache: "{{ image_cache_path }}" + os_images_common: "" + os_images_list: "{{ overcloud_dib_host_images }}" + os_images_git_elements: "{{ overcloud_dib_git_elements }}" + os_images_upload: False + os_images_force_rebuild: "{{ overcloud_host_image_force_rebuild }}" + + - name: Copy overcloud host disk images into /httpboot + copy: + src: "{{ image_cache_path }}/{{ image.name }}/{{ image.name }}.{{ image.type | default('qcow2') }}" + dest: "/var/lib/docker/volumes/bifrost_httpboot/_data/{{ image.name }}.{{ image.type | default('qcow2') }}" + remote_src: True + with_items: "{{ overcloud_dib_host_images }}" + loop_control: + loop_var: image + become: True + when: overcloud_dib_build_host_images | bool diff --git a/ansible/physical-network.yml b/ansible/physical-network.yml index f9137856d..06a4a4ef0 100644 --- a/ansible/physical-network.yml +++ b/ansible/physical-network.yml @@ -27,6 +27,7 @@ - arista - dellos6 - dellos9 + - dellos10 - dell-powerconnect - junos - mellanox @@ -111,7 +112,7 @@ arista_switch_interface_config: "{{ switch_interface_config }}" - name: Ensure DellOS physical switches are configured - hosts: switches_of_type_dellos6:switches_of_type_dellos9:&switches_in_display_mode_False + hosts: switches_of_type_dellos6:switches_of_type_dellos9:switches_of_type_dellos10:&switches_in_display_mode_False gather_facts: no roles: - role: ssh-known-host diff --git a/ansible/pip.yml b/ansible/pip.yml index bd00ec922..f9ca03775 100644 --- a/ansible/pip.yml +++ b/ansible/pip.yml @@ -1,6 +1,6 @@ --- - name: Configure local PyPi mirror - hosts: seed-hypervisor:seed:overcloud + hosts: seed-hypervisor:seed:overcloud:infra-vms tags: - pip vars: diff --git a/ansible/proxy.yml b/ansible/proxy.yml new file mode 100644 index 000000000..e618b9c0d --- /dev/null +++ b/ansible/proxy.yml @@ -0,0 +1,41 @@ +- name: Configure HTTP(S) proxy settings + hosts: seed-hypervisor:seed:overcloud:infra-vms + vars: + ansible_python_interpreter: /usr/bin/python3 + tags: + - proxy + tasks: + - name: Add HTTP proxy configuration to /etc/environment + lineinfile: + path: "/etc/environment" + create: yes + mode: 0644 + state: present + regexp: "^http_proxy=.*" + line: "http_proxy={{ http_proxy }}" + become: True + when: http_proxy | length > 0 + + - name: Add HTTPS proxy configuration to /etc/environment + lineinfile: + path: "/etc/environment" + create: yes + mode: 0644 + state: present + regexp: "^https_proxy=.*" + line: "https_proxy={{ https_proxy }}" + become: True + when: https_proxy | length > 0 + + - name: Add no_proxy configuration to /etc/environment + lineinfile: + path: "/etc/environment" + create: yes + mode: 0644 + state: present + regexp: "^no_proxy=.*" + line: "no_proxy={{ no_proxy | select | join(',') }}" + become: True + when: + - no_proxy | length > 0 + - http_proxy | length > 0 or https_proxy | length > 0 diff --git a/ansible/roles/apt/defaults/main.yml b/ansible/roles/apt/defaults/main.yml index fad722dcd..f818381d7 100644 --- a/ansible/roles/apt/defaults/main.yml +++ b/ansible/roles/apt/defaults/main.yml @@ -10,3 +10,41 @@ apt_proxy_http: # Apt proxy URL for HTTPS. Default is {{ apt_proxy_http }}. apt_proxy_https: "{{ apt_proxy_http }}" + +# List of Apt configuration options. Each item is a dict with the following +# keys: +# * content: free-form configuration file content +# * filename: name of a file in /etc/apt/apt.conf.d/ in which to write the +# configuration +# Default is an empty list. +apt_config: [] + +# Directory containing GPG keyrings for apt repos. +apt_keys_path: "/usr/local/share/keyrings" + +# List of apt keys. Each item is a dict containing the following keys: +# * url: URL of key +# * filename: Name of a file in which to store the downloaded key. The +# extension should be '.asc' for ASCII-armoured keys, or '.gpg' otherwise. +# Default is an empty list. +apt_keys: [] + +# A list of Apt repositories. Each item is a dict with the following keys: +# * types: whitespace-separated list of repository types, e.g. deb or deb-src +# (optional, default is 'deb') +# * url: URL of the repository +# * suites: whitespace-separated list of suites, e.g. focal (optional, default +# is ansible_facts.distribution_release) +# * components: whitespace-separated list of components, e.g. main (optional, +# default is 'main') +# * signed_by: whitespace-separated list of names of GPG keyring files in +# apt_keys_path (optional, default is unset) +# * architecture: whitespace-separated list of architectures that will be used +# (optional, default is unset) +# Default is an empty list. +apt_repositories: [] + +# Whether to disable repositories in /etc/apt/sources.list. This may be used +# when replacing the distribution repositories via apt_repositories. +# Default is false. +apt_disable_sources_list: false diff --git a/ansible/roles/apt/handlers/main.yml b/ansible/roles/apt/handlers/main.yml new file mode 100644 index 000000000..2d39add43 --- /dev/null +++ b/ansible/roles/apt/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: Update apt cache + package: + update_cache: true + become: true diff --git a/ansible/roles/apt/tasks/config.yml b/ansible/roles/apt/tasks/config.yml new file mode 100644 index 000000000..046f6e532 --- /dev/null +++ b/ansible/roles/apt/tasks/config.yml @@ -0,0 +1,14 @@ +--- +- name: Ensure Apt is configured + copy: + content: "{{ item.content }}" + dest: "/etc/apt/apt.conf.d/{{ item.filename }}" + owner: root + group: root + mode: 0664 + loop: "{{ apt_config }}" + loop_control: + label: "{{ item.filename }}" + become: true + notify: + - Update apt cache diff --git a/ansible/roles/apt/tasks/keys.yml b/ansible/roles/apt/tasks/keys.yml new file mode 100644 index 000000000..4c1cda1e0 --- /dev/null +++ b/ansible/roles/apt/tasks/keys.yml @@ -0,0 +1,19 @@ +--- +- name: Ensure keys directory exists + file: + path: "{{ apt_keys_path }}" + owner: root + group: root + mode: 0755 + state: directory + become: true + +- name: Ensure keys exist + get_url: + url: "{{ item.url }}" + dest: "{{ apt_keys_path ~ '/' ~ item.filename | basename }}" + owner: root + group: root + mode: 0644 + loop: "{{ apt_keys }}" + become: true diff --git a/ansible/roles/apt/tasks/main.yml b/ansible/roles/apt/tasks/main.yml index 16205b6be..b4cb8f636 100644 --- a/ansible/roles/apt/tasks/main.yml +++ b/ansible/roles/apt/tasks/main.yml @@ -1,17 +1,8 @@ --- -- name: Configure apt proxy - template: - src: "01proxy.j2" - dest: /etc/apt/apt.conf.d/01proxy - owner: root - group: root - mode: 0664 - become: true - when: apt_proxy_http | default('', true) | length > 0 or apt_proxy_https | default('', true) | length > 0 +- import_tasks: proxy.yml -- name: Remove old apt proxy config - file: - path: /etc/apt/apt.conf.d/01proxy - state: absent - become: true - when: apt_proxy_http | default('', true) | length == 0 and apt_proxy_https | default('', true) | length == 0 +- import_tasks: config.yml + +- import_tasks: keys.yml + +- import_tasks: repos.yml diff --git a/ansible/roles/apt/tasks/proxy.yml b/ansible/roles/apt/tasks/proxy.yml new file mode 100644 index 000000000..16205b6be --- /dev/null +++ b/ansible/roles/apt/tasks/proxy.yml @@ -0,0 +1,17 @@ +--- +- name: Configure apt proxy + template: + src: "01proxy.j2" + dest: /etc/apt/apt.conf.d/01proxy + owner: root + group: root + mode: 0664 + become: true + when: apt_proxy_http | default('', true) | length > 0 or apt_proxy_https | default('', true) | length > 0 + +- name: Remove old apt proxy config + file: + path: /etc/apt/apt.conf.d/01proxy + state: absent + become: true + when: apt_proxy_http | default('', true) | length == 0 and apt_proxy_https | default('', true) | length == 0 diff --git a/ansible/roles/apt/tasks/repos.yml b/ansible/roles/apt/tasks/repos.yml new file mode 100644 index 000000000..8f48f2b92 --- /dev/null +++ b/ansible/roles/apt/tasks/repos.yml @@ -0,0 +1,23 @@ +--- +# NOTE(mgoddard): Use the modern deb822 repository format rather than the old +# format used by the apt_repository module. +- name: Configure apt repositories + template: + src: "kayobe.sources.j2" + dest: "/etc/apt/sources.list.d/kayobe.sources" + owner: root + group: root + mode: 0644 + become: true + notify: + - Update apt cache + +- name: Disable repositories in /etc/apt/sources.list + replace: + # Make a backup, in case we end up with a broken configuration. + backup: true + path: /etc/apt/sources.list + regexp: '^(deb.*)' + replace: '# \1' + when: apt_disable_sources_list | bool + become: true diff --git a/ansible/roles/apt/templates/kayobe.sources.j2 b/ansible/roles/apt/templates/kayobe.sources.j2 new file mode 100644 index 000000000..91f6bf6b4 --- /dev/null +++ b/ansible/roles/apt/templates/kayobe.sources.j2 @@ -0,0 +1,15 @@ +# {{ ansible_managed }} + +{% for repo in apt_repositories %} +Types: {{ repo.types | default('deb') }} +URIs: {{ repo.url }} +Suites: {{ repo.suites | default(ansible_facts.distribution_release) }} +Components: {{ repo.components | default('main') }} +{% if repo.signed_by is defined %} +Signed-by: {{ apt_keys_path }}/{{ repo.signed_by }} +{% endif %} +{% if repo.architecture is defined %} +Architecture: {{ repo.architecture }} +{% endif %} + +{% endfor %} diff --git a/ansible/roles/dell-switch/README.md b/ansible/roles/dell-switch/README.md index dc763d40c..b98d9d7d2 100644 --- a/ansible/roles/dell-switch/README.md +++ b/ansible/roles/dell-switch/README.md @@ -1,10 +1,10 @@ Dell Switch =========== -This role configures Dell switches using the `dellos6` or `dellos9` Ansible -modules. It provides a fairly minimal abstraction of the configuration -interface provided by the `dellos` modules, allowing for application of -arbitrary switch configuration options. +This role configures Dell switches using the `dellos6`, `dellos9`, or +`dellos10` Ansible modules. It provides a fairly minimal abstraction of the +configuration interface provided by the `dellos` modules, allowing for +application of arbitrary switch configuration options. Requirements ------------ @@ -14,7 +14,8 @@ The switches should be configured to allow SSH access. Role Variables -------------- -`dell_switch_type` is the type of Dell switch. One of `dellos6`, `dellos9`. +`dell_switch_type` is the type of Dell switch. One of `dellos6`, `dellos9`, or +`dellos10`. `dell_switch_provider` is authentication provider information passed as the `provider` argument to the `dellos` modules. diff --git a/ansible/roles/dell-switch/defaults/main.yml b/ansible/roles/dell-switch/defaults/main.yml index 07bec1a39..f642dd325 100644 --- a/ansible/roles/dell-switch/defaults/main.yml +++ b/ansible/roles/dell-switch/defaults/main.yml @@ -1,5 +1,5 @@ --- -# Type of Dell switch. One of dellos6, dellos9. +# Type of Dell switch. One of dellos6, dellos9, or dellos10. dell_switch_type: # Authentication provider information. diff --git a/ansible/roles/dell-switch/tasks/main.yml b/ansible/roles/dell-switch/tasks/main.yml index 4166ed714..07fd3d923 100644 --- a/ansible/roles/dell-switch/tasks/main.yml +++ b/ansible/roles/dell-switch/tasks/main.yml @@ -12,3 +12,10 @@ provider: "{{ dell_switch_provider }}" src: dellos9-config.j2 when: dell_switch_type == 'dellos9' + +- name: Ensure DellOS10 switches are configured + local_action: + module: dellos10_config + provider: "{{ dell_switch_provider }}" + src: "{{ lookup('template', 'dellos10-config.j2') }}" + when: dell_switch_type == 'dellos10' diff --git a/ansible/roles/dell-switch/templates/dellos10-config.j2 b/ansible/roles/dell-switch/templates/dellos10-config.j2 new file mode 100644 index 000000000..94c9dc068 --- /dev/null +++ b/ansible/roles/dell-switch/templates/dellos10-config.j2 @@ -0,0 +1,16 @@ +#jinja2: trim_blocks: True,lstrip_blocks: True + +{% for line in dell_switch_config %} +{{ line }} +{% endfor %} + +{% for interface, config in dell_switch_interface_config.items() %} +interface {{ interface }} +{% if config.description is defined %} +description {{ config.description }} +{% endif %} +{% for line in config.config %} +{{ line }} +{% endfor %} +exit +{% endfor %} diff --git a/ansible/roles/dnf/tasks/custom-repo.yml b/ansible/roles/dnf/tasks/custom-repo.yml index cabcb8f9b..244537bb9 100644 --- a/ansible/roles/dnf/tasks/custom-repo.yml +++ b/ansible/roles/dnf/tasks/custom-repo.yml @@ -9,7 +9,9 @@ gpgcheck: "{{ item.value.gpgcheck | default(omit)}}" cost: "{{ item.value.cost | default(omit)}}" enabled: "{{ item.value.enabled | default(omit)}}" + exclude: "{{ item.value.exclude | default(omit)}}" gpgcakey: "{{ item.value.gpgcakey | default(omit)}}" + includepkgs: "{{ item.value.includepkgs | default(omit)}}" metadata_expire: "{{ item.value.metadata_expire | default(omit)}}" metalink: "{{ item.value.metalink | default(omit)}}" mirrorlist: "{{ item.value.mirrorlist | default(omit)}}" diff --git a/ansible/roles/dnf/tasks/local-mirror.yml b/ansible/roles/dnf/tasks/local-mirror.yml index 845d50e96..7dc7b6899 100644 --- a/ansible/roles/dnf/tasks/local-mirror.yml +++ b/ansible/roles/dnf/tasks/local-mirror.yml @@ -1,5 +1,7 @@ --- - name: Copy CentOS repo templates + vars: + repo_file_prefix: "{{ 'CentOS-Stream' if ansible_facts.distribution == 'CentOS' else 'Rocky' }}" template: src: "{{ item }}.j2" dest: /etc/yum.repos.d/{{ item }} @@ -8,9 +10,9 @@ mode: 0664 become: True loop: - - CentOS-Stream-AppStream.repo - - CentOS-Stream-BaseOS.repo - - CentOS-Stream-Extras.repo + - "{{ repo_file_prefix }}-AppStream.repo" + - "{{ repo_file_prefix }}-BaseOS.repo" + - "{{ repo_file_prefix }}-Extras.repo" - name: Remove old (pre CentOS 8.3) repo files file: @@ -21,6 +23,7 @@ - CentOS-AppStream.repo - CentOS-Base.repo - CentOS-Extras.repo + when: ansible_facts.distribution == 'CentOS' - name: Update cache dnf: diff --git a/ansible/roles/dnf/templates/Rocky-AppStream.repo.j2 b/ansible/roles/dnf/templates/Rocky-AppStream.repo.j2 new file mode 100644 index 000000000..e46b38dec --- /dev/null +++ b/ansible/roles/dnf/templates/Rocky-AppStream.repo.j2 @@ -0,0 +1,16 @@ +# Rocky-AppStream.repo +# +# The mirrorlist system uses the connecting IP address of the client and the +# update status of each mirror to pick current mirrors that are geographically +# close to the client. You should use this for Rocky updates unless you are +# manually picking other mirrors. +# +# If the mirrorlist does not work for you, you can try the commented out +# baseurl line instead. + +[appstream] +name=Rocky Linux $releasever - AppStream +baseurl=http://{{ dnf_rocky_mirror_host }}/{{ dnf_rocky_mirror_directory }}/$releasever/AppStream/$basearch/os/ +gpgcheck=1 +enabled=1 +gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-rockyofficial diff --git a/ansible/roles/dnf/templates/Rocky-BaseOS.repo.j2 b/ansible/roles/dnf/templates/Rocky-BaseOS.repo.j2 new file mode 100644 index 000000000..0931719bd --- /dev/null +++ b/ansible/roles/dnf/templates/Rocky-BaseOS.repo.j2 @@ -0,0 +1,16 @@ +# Rocky-BaseOS.repo +# +# The mirrorlist system uses the connecting IP address of the client and the +# update status of each mirror to pick current mirrors that are geographically +# close to the client. You should use this for Rocky updates unless you are +# manually picking other mirrors. +# +# If the mirrorlist does not work for you, you can try the commented out +# baseurl line instead. + +[baseos] +name=Rocky Linux $releasever - BaseOS +baseurl=http://{{ dnf_rocky_mirror_host }}/{{ dnf_rocky_mirror_directory }}/$releasever/BaseOS/$basearch/os/ +gpgcheck=1 +enabled=1 +gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-rockyofficial diff --git a/ansible/roles/dnf/templates/Rocky-Extras.repo.j2 b/ansible/roles/dnf/templates/Rocky-Extras.repo.j2 new file mode 100644 index 000000000..ffb04c8f5 --- /dev/null +++ b/ansible/roles/dnf/templates/Rocky-Extras.repo.j2 @@ -0,0 +1,16 @@ +# Rocky-Extras.repo +# +# The mirrorlist system uses the connecting IP address of the client and the +# update status of each mirror to pick current mirrors that are geographically +# close to the client. You should use this for Rocky updates unless you are +# manually picking other mirrors. +# +# If the mirrorlist does not work for you, you can try the commented out +# baseurl line instead. + +[extras] +name=Rocky Linux $releasever - Extras +baseurl=http://{{ dnf_rocky_mirror_host }}/{{ dnf_rocky_mirror_directory }}/$releasever/extras/$basearch/os/ +gpgcheck=1 +enabled=1 +gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-rockyofficial diff --git a/ansible/roles/firewall-redhat/defaults/main.yml b/ansible/roles/firewall-redhat/defaults/main.yml new file mode 100644 index 000000000..8ca780000 --- /dev/null +++ b/ansible/roles/firewall-redhat/defaults/main.yml @@ -0,0 +1,18 @@ +--- +# Whether to install and enable firewalld. +firewalld_enabled: false + +# A list of zones to create. Each item is a dict containing a 'zone' item. +firewalld_zones: [] + +# A firewalld zone to set as the default. Default is unset, in which case the +# default zone will not be changed. +firewalld_default_zone: + +# A list of firewall rules to apply. Each item is a dict containing arguments +# to pass to the firewalld module. Arguments are omitted if not provided, with +# the following exceptions: +# - offline: true +# - permanent: true +# - state: enabled +firewalld_rules: [] diff --git a/ansible/roles/firewall-redhat/handlers/main.yml b/ansible/roles/firewall-redhat/handlers/main.yml new file mode 100644 index 000000000..a29336dce --- /dev/null +++ b/ansible/roles/firewall-redhat/handlers/main.yml @@ -0,0 +1,10 @@ +--- +- name: Restart firewalld + service: + name: firewalld + state: restarted + become: true + +- name: Check connectivity after firewalld restart + ping: + listen: Restart firewalld diff --git a/ansible/roles/firewall-redhat/tasks/disabled.yml b/ansible/roles/firewall-redhat/tasks/disabled.yml new file mode 100644 index 000000000..af642b5c9 --- /dev/null +++ b/ansible/roles/firewall-redhat/tasks/disabled.yml @@ -0,0 +1,18 @@ +--- +- name: Ensure firewalld service is stopped and disabled + service: + name: firewalld + enabled: false + state: stopped + become: true + register: firewalld_result + failed_when: + - firewalld_result is failed + # Ugh, Ansible's service module doesn't handle uninstalled services. + - "'Could not find the requested service' not in firewalld_result.msg" + +- name: Ensure firewalld package is uninstalled + package: + name: firewalld + state: absent + become: true diff --git a/ansible/roles/firewall-redhat/tasks/enabled.yml b/ansible/roles/firewall-redhat/tasks/enabled.yml new file mode 100644 index 000000000..048645169 --- /dev/null +++ b/ansible/roles/firewall-redhat/tasks/enabled.yml @@ -0,0 +1,71 @@ +--- +- name: Ensure firewalld package is installed + package: + name: firewalld + become: true + +- name: Ensure firewalld service is enabled + service: + name: firewalld + enabled: true + # FIXME: should be possible to configure firewalld offline, but it fails to + # apply config. + state: started + become: true + +- block: + - name: Get firewalld current default zone + command: + cmd: "firewall-offline-cmd --get-default-zone" + changed_when: false + register: current_default_zone + + - name: Set firewalld default zone + command: "firewall-offline-cmd --set-default-zone {{ firewalld_default_zone }}" + when: current_default_zone.stdout != firewalld_default_zone + notify: Restart firewalld + become: true + when: + - firewalld_default_zone is not none + - firewalld_default_zone | length > 0 + +- name: Ensure firewalld zones exist + firewalld: + offline: true + permanent: true + state: "{{ item.state | default('present') }}" + zone: "{{ item.zone }}" + become: true + loop: "{{ firewalld_zones }}" + +- name: Set firewalld zones for network interfaces + firewalld: + interface: "{{ item | net_interface }}" + offline: true + permanent: true + state: enabled + zone: "{{ item | net_zone }}" + become: true + loop: "{{ network_interfaces }}" + when: item | net_zone + notify: Restart firewalld + +- name: Ensure firewalld rules are applied + firewalld: + icmp_block: "{{ item.icmp_block | default(omit) }}" + icmp_block_inversion: "{{ item.icmp_block_inversion | default(omit) }}" + immediate: "{{ item.immediate | default(omit) }}" + interface: "{{ item.interface | default(omit) }}" + masquerade: "{{ item.masquerade | default(omit) }}" + offline: "{{ item.offline | default(true) }}" + permanent: "{{ item.permanent | default(true) }}" + port: "{{ item.port | default(omit) }}" + rich_rule: "{{ item.rich_rule | default(omit) }}" + service: "{{ item.service | default(omit) }}" + source: "{{ item.source | default(omit) }}" + state: "{{ item.state | default('enabled') }}" + timeout: "{{ item.timeout | default(omit) }}" + zone: "{{ item.zone | default(omit) }}" + become: true + loop: "{{ firewalld_rules }}" + notify: Restart firewalld diff --git a/ansible/roles/firewall-redhat/tasks/main.yml b/ansible/roles/firewall-redhat/tasks/main.yml new file mode 100644 index 000000000..108ffc8ce --- /dev/null +++ b/ansible/roles/firewall-redhat/tasks/main.yml @@ -0,0 +1,3 @@ +--- +- name: Include tasks + include_tasks: "{{ 'enabled' if firewalld_enabled | bool else 'disabled' }}.yml" diff --git a/ansible/roles/infra-vms/defaults/main.yml b/ansible/roles/infra-vms/defaults/main.yml new file mode 100644 index 000000000..c81b775d0 --- /dev/null +++ b/ansible/roles/infra-vms/defaults/main.yml @@ -0,0 +1,7 @@ +--- + +# Either 'deploy' or 'destroy'. +infra_vm_action: deploy + +# List of inventory hostnames of infra VMs mapped to this hypervisor. +infra_vm_vms: [] diff --git a/ansible/roles/infra-vms/tasks/deploy.yml b/ansible/roles/infra-vms/tasks/deploy.yml new file mode 100644 index 000000000..e51991786 --- /dev/null +++ b/ansible/roles/infra-vms/tasks/deploy.yml @@ -0,0 +1,93 @@ +--- + +- name: "[{{ vm_name }}] Ensure that the VM configdrive exists" + include_role: + name: jriguera.configdrive + vars: + configdrive_os_family: "{{ vm_hostvars.infra_vm_os_family }}" + configdrive_uuid: "{{ vm_name | to_uuid }}" + # Must set configdrive_instance_dir when using a loop + # https://github.com/jriguera/ansible-role-configdrive/blob/8438592c84585c86e62ae07e526d3da53629b377/tasks/main.yml#L17 + configdrive_instance_dir: "{{ configdrive_uuid }}" + configdrive_fqdn: "{{ vm_name }}" + configdrive_name: "{{ vm_name }}" + configdrive_ssh_public_key: "{{ lookup('file', ssh_public_key_path) }}" + configdrive_config_dir: "{{ image_cache_path }}" + configdrive_volume_path: "{{ image_cache_path }}" + configdrive_config_dir_delete: False + configdrive_resolv: + domain: "{{ vm_hostvars.resolv_domain | default }}" + search: "{{ vm_hostvars.resolv_search | default }}" + dns: "{{ vm_hostvars.resolv_nameservers | default([]) }}" + configdrive_network_device_list: > + {{ vm_hostvars.network_interfaces | + map('net_configdrive_network_device', vm_hostvars.inventory_hostname) | + list }} + +- name: "[{{ vm_name }}] Set a fact containing the configdrive image path" + set_fact: + vm_configdrive_path: "{{ image_cache_path }}/{{ vm_name }}.iso" + +- name: "[{{ vm_name }}] Ensure configdrive is decoded and decompressed" + shell: > + base64 -d {{ image_cache_path }}/{{ vm_name | to_uuid }}.gz + | gunzip + > {{ vm_configdrive_path }} + +- name: "[{{ vm_name }}] Ensure unnecessary files are removed" + file: + path: "{{ item }}" + state: absent + with_items: + - "{{ image_cache_path }}/{{ vm_name | to_uuid }}.gz" + +- name: "[{{ vm_name }}] Check the size of the configdrive image" + stat: + path: "{{ vm_configdrive_path }}" + get_checksum: False + get_md5: False + mime: False + register: stat_result + +# NOTE(mgoddard): Prior to the Xena release, the seed VM was provisioned using +# the stackhpc.livirt-vm role with become=true. This resulted in the cached +# image being owned by root. Since Xena, we execute the role without +# become=true. Correct the image ownership to avoid a permission denied error +# when downloading a new image of the same name. +- name: "[{{ vm_name }}] Stat image files" + stat: + path: "{{ image_cache_path }}/{{ item.image | basename }}" + with_items: "{{ vm_hostvars.infra_vm_volumes | selectattr('image', 'defined') }}" + register: image_stat_result + +- name: "[{{ vm_name }}] Fix image ownership" + file: + path: "{{ image_cache_path }}/{{ item.item.image | basename }}" + owner: "{{ ansible_facts.user_uid }}" + group: "{{ ansible_facts.user_gid }}" + with_items: "{{ image_stat_result.results }}" + when: item.stat.exists + become: true + +- name: "[{{ vm_name }}] Ensure that the VM is provisioned" + include_role: + name: stackhpc.libvirt-vm + vars: + vm_configdrive_device: cdrom + vm_configdrive_volume: + name: "{{ vm_name }}-configdrive" + pool: "{{ vm_hostvars.infra_vm_pool }}" + # Round size up to next multiple of 4096. + capacity: "{{ (stat_result.stat.size + 4095) // 4096 * 4096 }}" + device: "{{ vm_configdrive_device }}" + format: "raw" + image: "{{ vm_configdrive_path }}" + remote_src: true + libvirt_vm_image_cache_path: "{{ image_cache_path }}" + libvirt_vms: + - name: "{{ vm_name }}" + memory_mb: "{{ vm_hostvars.infra_vm_memory_mb }}" + vcpus: "{{ vm_hostvars.infra_vm_vcpus }}" + volumes: "{{ vm_hostvars.infra_vm_volumes + [vm_configdrive_volume] }}" + interfaces: "{{ vm_hostvars.infra_vm_interfaces }}" + console_log_enabled: true diff --git a/ansible/roles/infra-vms/tasks/destroy.yml b/ansible/roles/infra-vms/tasks/destroy.yml new file mode 100644 index 000000000..a621e4e30 --- /dev/null +++ b/ansible/roles/infra-vms/tasks/destroy.yml @@ -0,0 +1,16 @@ +--- + +- name: Destroy VMs + import_role: + name: stackhpc.libvirt-vm + vars: + infra_vm_configdrive_volume: + name: "{{ vm_name }}-configdrive" + pool: "{{ hostvars[vm_hostvars.infra_vm_hypervisor].infra_vm_pool }}" + libvirt_vms: + - name: "{{ vm_name }}" + memory_mb: "{{ vm_hostvars.infra_vm_memory_mb }}" + vcpus: "{{ vm_hostvars.infra_vm_vcpus }}" + volumes: "{{ vm_hostvars.infra_vm_volumes + [infra_vm_configdrive_volume] }}" + state: "absent" + become: True diff --git a/ansible/roles/infra-vms/tasks/main.yml b/ansible/roles/infra-vms/tasks/main.yml new file mode 100644 index 000000000..9fec42e3c --- /dev/null +++ b/ansible/roles/infra-vms/tasks/main.yml @@ -0,0 +1,18 @@ +--- +- import_tasks: prerequisites.yml + +- name: list all VMs on hypervisor + virt: + command: list_vms + register: all_vms + become: true + +- name: "{{ infra_vm_action | capitalize }} infra VMs (loop)" + include_tasks: "{{ infra_vm_action }}.yml" + vars: + vm_name: "{{ vm_hostvars.infra_vm_name }}" + vm_hostvars: "{{ hostvars[vm_item] }}" + loop: "{{ infra_vm_vms }}" + when: (infra_vm_action == "deploy" and vm_name not in all_vms.list_vms) or infra_vm_action == "destroy" + loop_control: + loop_var: vm_item diff --git a/ansible/roles/infra-vms/tasks/prerequisites.yml b/ansible/roles/infra-vms/tasks/prerequisites.yml new file mode 100644 index 000000000..a0600e895 --- /dev/null +++ b/ansible/roles/infra-vms/tasks/prerequisites.yml @@ -0,0 +1,18 @@ +--- +# NOTE(priteau): On seed hypervisors running CentOS 8, the configdrive role +# will fail to install coreutils if coreutils-single is already present. +# Until the role handles it, install it using the --allowerasing option +# which will remove coreutils-single. +- name: Ensure coreutils package is installed + command: "dnf install coreutils -y --allowerasing" + become: True + when: + - ansible_facts.os_family == 'RedHat' + +- name: Ensure the image cache directory exists + file: + path: "{{ image_cache_path }}" + state: directory + owner: "{{ ansible_facts.user_uid }}" + group: "{{ ansible_facts.user_gid }}" + become: True diff --git a/ansible/roles/kolla-ansible/defaults/main.yml b/ansible/roles/kolla-ansible/defaults/main.yml index 166659bbc..9a867069b 100644 --- a/ansible/roles/kolla-ansible/defaults/main.yml +++ b/ansible/roles/kolla-ansible/defaults/main.yml @@ -21,6 +21,11 @@ kolla_ansible_venv_python: python3 # Extra requirements to install inside the kolla-ansible virtualenv. kolla_ansible_venv_extra_requirements: [] +# Pip requirement specifier for the ansible package. NOTE: This limits the +# version of ansible used by kolla-ansible to avoid new releases from breaking +# tested code. Changes to this limit should be tested. +kolla_ansible_venv_ansible: 'ansible>=2.9,<2.11,!=2.9.8,!=2.9.12' + # Virtualenv directory where Kolla-ansible's ansible modules will execute # remotely on the target nodes. If None, no virtualenv will be used. kolla_ansible_target_venv: @@ -132,7 +137,6 @@ kolla_external_vip_address: # kolla_external_vip_address. kolla_external_fqdn: - #################### # Networking options #################### @@ -231,12 +235,32 @@ kolla_openstack_logging_debug: # controllers. kolla_nova_compute_ironic_host: +kolla_libvirt_tls: + +kolla_libvirt_enable_sasl: + ############################################################################### # Extra free-form configuraton. +# Deprecated: # Free form extra configuration to append to {{ kolla_config_path }}/globals.yml. kolla_extra_globals: +# List of paths to YAML files containing extra configuration to merge to {{ +# kolla_config_path }}/globals.yml. Default is include the globals.yml template +# from the role. +kolla_globals_paths_default: + - "{{ role_path }}/templates" + +# List of paths to YAML files containing extra configuration to merge to {{ +# kolla_config_path }}/globals.yml. Default is an empty list. +kolla_globals_paths_extra: [] + +# List of paths to YAML files containing extra configuration to merge to {{ +# kolla_config_path }}/globals.yml. Default is combination of +# kolla_globals_paths_default and kolla_globals_paths_extra. +kolla_globals_paths: "{{ kolla_globals_paths_default + kolla_globals_paths_extra }}" + # Dictionary containing custom passwords to add or override in the Kolla # passwords file. kolla_ansible_custom_passwords: {} @@ -287,3 +311,16 @@ docker_daemon_mtu: 1500 # Enable live-restore on docker daemon docker_daemon_live_restore: false + +############################################################################### +# Proxy configuration + +# HTTP proxy URL (format: http(s)://[user:password@]proxy_name:port). +kolla_http_proxy: + +# HTTPS proxy URL (format: http(s)://[user:password@]proxy_name:port). +kolla_https_proxy: + +# List of domains, hostnames, IP addresses and networks for which no proxy is +# used. +kolla_no_proxy: diff --git a/ansible/roles/kolla-ansible/tasks/config.yml b/ansible/roles/kolla-ansible/tasks/config.yml index f74c148a1..276700f01 100644 --- a/ansible/roles/kolla-ansible/tasks/config.yml +++ b/ansible/roles/kolla-ansible/tasks/config.yml @@ -48,8 +48,8 @@ when: (kayobe_environment | default('')) | length > 0 - name: Ensure the Kolla global configuration file exists - template: - src: "globals.yml.j2" + merge_yaml: + sources: "{{ kolla_globals_paths | product(['/kolla/globals.yml']) | map('join') | unique | list }}" dest: "{{ kolla_config_path }}/globals.yml" mode: 0640 vars: diff --git a/ansible/roles/kolla-ansible/tasks/install.yml b/ansible/roles/kolla-ansible/tasks/install.yml index 5c96170a3..1e8c9170a 100644 --- a/ansible/roles/kolla-ansible/tasks/install.yml +++ b/ansible/roles/kolla-ansible/tasks/install.yml @@ -64,10 +64,7 @@ {% else %} kolla-ansible=={{ kolla_openstack_release }} {% endif %} - # Limit the version of ansible used by kolla-ansible to avoid new - # releases from breaking tested code. Changes to this limit should be - # tested. - - ansible>=2.9,<2.11,!=2.9.8,!=2.9.12 + - "{{ kolla_ansible_venv_ansible }}" - selinux pip: name: "{{ (kolla_ansible_packages + kolla_ansible_venv_extra_requirements) | select | list }}" diff --git a/ansible/roles/kolla-ansible/templates/globals.yml.j2 b/ansible/roles/kolla-ansible/templates/kolla/globals.yml similarity index 94% rename from ansible/roles/kolla-ansible/templates/globals.yml.j2 rename to ansible/roles/kolla-ansible/templates/kolla/globals.yml index 8d68faf3e..22e2fbaf5 100644 --- a/ansible/roles/kolla-ansible/templates/globals.yml.j2 +++ b/ansible/roles/kolla-ansible/templates/kolla/globals.yml @@ -1,6 +1,4 @@ --- -# {{ ansible_managed }} - # You can use this file to override _any_ variable throughout Kolla. # Additional options can be found in the # 'kolla-ansible/ansible/group_vars/all.yml' file. Default value of all the @@ -52,6 +50,17 @@ kolla_external_vip_address: "{{ kolla_external_vip_address }}" kolla_external_fqdn: "{{ kolla_external_fqdn }}" {% endif %} +# Proxy settings for containers such as magnum that need Internet access +{% if kolla_http_proxy is not none and kolla_http_proxy | length > 0 %} +container_http_proxy: "{{ kolla_http_proxy }}" +{% endif %} +{% if kolla_https_proxy is not none and kolla_https_proxy | length > 0 %} +container_https_proxy: "{{ kolla_https_proxy }}" +{% endif %} +{% if kolla_no_proxy is not none and kolla_no_proxy | length > 0 %} +container_no_proxy: "{{ kolla_no_proxy | select | join(',') }}" +{% endif %} + ################ # Docker options ################ @@ -68,6 +77,16 @@ docker_registry_username: "{{ kolla_docker_registry_username }}" docker_storage_driver: "{{ docker_storage_driver }}" docker_custom_config: {{ kolla_docker_custom_config | to_nice_json | indent(2) }} +{% if kolla_http_proxy is not none and kolla_http_proxy | length > 0 %} +docker_http_proxy: "{{ kolla_http_proxy }}" +{% endif %} +{% if kolla_https_proxy is not none and kolla_https_proxy | length > 0 %} +docker_https_proxy: "{{ kolla_https_proxy }}" +{% endif %} +{% if kolla_no_proxy is not none and kolla_no_proxy | length > 0 %} +docker_no_proxy: "{{ kolla_no_proxy | select | join(',') }}" +{% endif %} + #docker_configure_for_zun: "no" ################### @@ -114,7 +133,7 @@ docker_custom_config: {{ kolla_docker_custom_config | to_nice_json | indent(2) } #dns_address_family: "{% raw %}{{ network_address_family }}{% endraw %}" # Valid options are [ openvswitch, linuxbridge ] -neutron_plugin_agent: "openvswitch" +neutron_plugin_agent: "{% if kolla_enable_ovn | default(False) | bool %}ovn{% else %}openvswitch{% endif %}" # Valid options are [ internal, infoblox ] #neutron_ipam_driver: "internal" @@ -377,6 +396,13 @@ enable_{{ feature_flag }}: {{ hostvars[inventory_hostname]['kolla_enable_' ~ fea # Valid options are [ none, novnc, spice, rdp ] #nova_console: "novnc" +{% if kolla_libvirt_tls is not none %} +libvirt_tls: {{ kolla_libvirt_tls | bool }} +{% endif %} + +{% if kolla_libvirt_enable_sasl is not none %} +libvirt_enable_sasl: {{ kolla_libvirt_enable_sasl | bool }} +{% endif %} ################# # Hyper-V options ################# @@ -565,6 +591,9 @@ kolla_group: "{{ kolla_ansible_group }}" virtualenv: {{ kolla_ansible_target_venv }} {% endif %} +# Avoid disabling the firewall on CentOS, since we manage it in Kayobe. +disable_firewall: "{% raw %}{{ ansible_facts.os_family == 'Debian' }}{% endraw %}" + {% if kolla_extra_globals %} ####################### # Extra configuration diff --git a/ansible/roles/kolla-ansible/templates/overcloud-components.j2 b/ansible/roles/kolla-ansible/templates/overcloud-components.j2 index c0487a462..fc1f24270 100644 --- a/ansible/roles/kolla-ansible/templates/overcloud-components.j2 +++ b/ansible/roles/kolla-ansible/templates/overcloud-components.j2 @@ -229,3 +229,6 @@ control [blazar:children] control + +[caso:children] +monitoring diff --git a/ansible/roles/kolla-ansible/templates/overcloud-services.j2 b/ansible/roles/kolla-ansible/templates/overcloud-services.j2 index 5cb8898ee..12d4b1c20 100644 --- a/ansible/roles/kolla-ansible/templates/overcloud-services.j2 +++ b/ansible/roles/kolla-ansible/templates/overcloud-services.j2 @@ -527,6 +527,12 @@ elasticsearch [prometheus-blackbox-exporter:children] monitoring +[prometheus-libvirt-exporter:children] +compute + +[prometheus-msteams:children] +prometheus-alertmanager + [masakari-api:children] control diff --git a/ansible/roles/kolla-ansible/tests/main.yml b/ansible/roles/kolla-ansible/tests/main.yml index 8347fa49c..d4b781c96 100644 --- a/ansible/roles/kolla-ansible/tests/main.yml +++ b/ansible/roles/kolla-ansible/tests/main.yml @@ -9,6 +9,7 @@ - import_playbook: test-defaults.yml - import_playbook: test-extras.yml - import_playbook: test-requirements.yml +- import_playbook: test-globals-merge.yml - hosts: localhost connection: local diff --git a/ansible/roles/kolla-ansible/tests/test-defaults.yml b/ansible/roles/kolla-ansible/tests/test-defaults.yml index 3cf9f6efe..f922d8c5a 100644 --- a/ansible/roles/kolla-ansible/tests/test-defaults.yml +++ b/ansible/roles/kolla-ansible/tests/test-defaults.yml @@ -38,6 +38,8 @@ kolla_enable_tls_internal: False kolla_enable_grafana: False kolla_openstack_logging_debug: False + kolla_globals_paths_extra: + - "{{ tempfile_result.path ~ '/etc/kayobe/' }}" apt_cache_valid_time: 3600 - name: Verify kolla-ansible installation diff --git a/ansible/roles/kolla-ansible/tests/test-extras.yml b/ansible/roles/kolla-ansible/tests/test-extras.yml index 9721bf30c..e4aa189c3 100644 --- a/ansible/roles/kolla-ansible/tests/test-extras.yml +++ b/ansible/roles/kolla-ansible/tests/test-extras.yml @@ -28,6 +28,14 @@ path: "{{ tempfile_result.path ~ '/etc/kayobe/kolla/inventory/group_vars/foo_group' }}" state: directory + - name: Create extra globals file + copy: + content: | + --- + extra-global-1: "extra-val-1" + extra-global-2: "extra-val-2" + dest: "{{ tempfile_result.path ~ '/etc/kayobe/kolla/globals.yml' }}" + - name: Create custom overcloud foo group vars copy: dest: "{{ tempfile_result.path ~ '/etc/kayobe/kolla/inventory/group_vars/foo_group/all' }}" @@ -182,9 +190,8 @@ kolla_enable_watcher: True kolla_enable_zookeeper: True kolla_enable_zun: True - kolla_extra_globals: - extra-global-1: "extra-val-1" - extra-global-2: "extra-val-2" + kolla_globals_paths_extra: + - "{{ tempfile_result.path ~ '/etc/kayobe/' }}" kolla_ansible_custom_passwords: custom-password-1: "custom-password-1" custom-password-2: "custom-password-2" diff --git a/ansible/roles/kolla-ansible/tests/test-globals-merge.yml b/ansible/roles/kolla-ansible/tests/test-globals-merge.yml new file mode 100644 index 000000000..5110836df --- /dev/null +++ b/ansible/roles/kolla-ansible/tests/test-globals-merge.yml @@ -0,0 +1,116 @@ +--- +- name: Test kolla-ansible role defaults + hosts: localhost + connection: local + tasks: + - name: Create a temporary directory + tempfile: + state: directory + register: tempfile_result + + - block: + + - name: Ensure directories exists + file: + state: directory + mode: "0700" + recurse: true + path: "{{ item }}" + with_items: + - "{{ tempfile_result.path }}/etc/kayobe/kolla/" + - "{{ tempfile_result.path }}//etc/kayobe/environments/level1/kolla" + - "{{ tempfile_result.path }}//etc/kayobe/environments/level2/kolla" + + - name: Write contents to base globals.yml + copy: + content: | + _overridden_level_1_var: base + _base_var: base + dest: "{{ tempfile_result.path }}/etc/kayobe/kolla/globals.yml" + + - name: Write contents to level1 globals.yml + copy: + content: | + _overridden_level_1_var: level1 + _overridden_level_2_var: level1 + dest: "{{ tempfile_result.path }}//etc/kayobe/environments/level1/kolla/globals.yml" + + - name: Write contents to level2 globals.yml + copy: + content: | + _overridden_level_2_var: level2 + dest: "{{ tempfile_result.path }}//etc/kayobe/environments/level2/kolla/globals.yml" + + - name: Test the kolla-ansible role with default values + include_role: + name: ../../kolla-ansible + vars: + kolla_ansible_source_path: "{{ temp_path }}/src" + kolla_ansible_ctl_install_type: "source" + kolla_ansible_source_url: "http://github.com/openstack/kolla-ansible" + kolla_ansible_source_version: "{{ openstack_branch }}" + kolla_ansible_venv: "{{ temp_path }}/venv" + kolla_config_path: "{{ temp_path }}/etc/kolla" + kolla_node_custom_config_path: "{{ temp_path }}/etc/kolla/config" + # Purposely does not exist to simulate the case when no group vars + # are provided + kolla_overcloud_group_vars_path: "{{ temp_path }}/etc/kayobe/kolla/inventory/group_vars" + kolla_ansible_passwords_path: "{{ temp_path }}/passwords.yml" + # Required config. + kolla_base_distro: "fake-distro" + kolla_install_type: "fake-install-type" + kolla_docker_namespace: "fake-namespace" + kolla_openstack_release: "fake-release" + kolla_internal_vip_address: "10.0.0.1" + kolla_internal_fqdn: "fake.internal.fqdn" + kolla_external_vip_address: "10.0.0.2" + kolla_external_fqdn: "fake.external.fqdn" + kolla_ansible_certificates_path: "{{ temp_path }}/etc/kayobe/kolla/certificates" + kolla_enable_tls_external: False + kolla_enable_tls_internal: False + kolla_enable_grafana: False + kolla_openstack_logging_debug: False + kolla_globals_paths_extra: + - "{{ tempfile_result.path ~ '/etc/kayobe/' }}" + - "{{ tempfile_result.path ~ '/etc/kayobe/environments/level1/' }}" + - "{{ tempfile_result.path ~ '/etc/kayobe/environments/level2/' }}" + apt_cache_valid_time: 3600 + + - name: Verify kolla-ansible installation + shell: ". {{ temp_path }}/venv/bin/activate && kolla-ansible -h" + changed_when: False + + - name: Verify ansible installation + command: "{{ temp_path }}/venv/bin/ansible -h" + changed_when: False + + - name: Validate globals.yml contents + assert: + that: + - item.key in globals_yml + - globals_yml[item.key] == item.value + msg: > + Unexpected value for variable "{{ item.key }}" in globals.yml. + Expected "{{ item.value }}", actual + "{{ globals_yml.get(item.key, '') }}". + with_dict: "{{ expected_variables }}" + vars: + # NOTE: Can't use set_fact for this, as it causes kolla-ansible + # Jinja expressions to be evaluated. + globals_yml: "{{ lookup('file', temp_path ~ '/etc/kolla/globals.yml') | from_yaml }}" + expected_variables: + _overridden_level_1_var: level1 + _overridden_level_2_var: level2 + _base_var: base + + always: + - name: Ensure the temporary directory is removed + file: + path: "{{ temp_path }}" + state: absent + rescue: + - name: Flag that a failure occurred + set_fact: + test_failures: "{{ test_failures | default(0) | int + 1 }}" + vars: + temp_path: "{{ tempfile_result.path }}" diff --git a/ansible/roles/kolla-ansible/tests/test-requirements.yml b/ansible/roles/kolla-ansible/tests/test-requirements.yml index c9f8fdb43..67d928f18 100644 --- a/ansible/roles/kolla-ansible/tests/test-requirements.yml +++ b/ansible/roles/kolla-ansible/tests/test-requirements.yml @@ -37,6 +37,8 @@ kolla_enable_tls_internal: False kolla_enable_grafana: False kolla_openstack_logging_debug: False + kolla_globals_paths_extra: + - "{{ tempfile_result.path ~ '/etc/kayobe/' }}" apt_cache_valid_time: 3600 - name: List Python packages installed in virtualenv diff --git a/ansible/roles/kolla-ansible/vars/main.yml b/ansible/roles/kolla-ansible/vars/main.yml index adc645143..c68baf7b4 100644 --- a/ansible/roles/kolla-ansible/vars/main.yml +++ b/ansible/roles/kolla-ansible/vars/main.yml @@ -74,6 +74,7 @@ kolla_feature_flags: - aodh - barbican - blazar + - caso - ceilometer - ceilometer_horizon_policy_file - ceilometer_ipmi @@ -181,6 +182,7 @@ kolla_feature_flags: - nova - nova_fake - nova_horizon_policy_file + - nova_libvirt_container - nova_serialconsole_proxy - nova_ssh - octavia @@ -200,6 +202,7 @@ kolla_feature_flags: - prometheus_ceph_mgr_exporter - prometheus_elasticsearch_exporter - prometheus_haproxy_exporter + - prometheus_libvirt_exporter - prometheus_memcached_exporter - prometheus_mysqld_exporter - prometheus_node_exporter diff --git a/ansible/roles/kolla-bifrost/defaults/main.yml b/ansible/roles/kolla-bifrost/defaults/main.yml index 2d4ddb187..93dfc35c3 100644 --- a/ansible/roles/kolla-bifrost/defaults/main.yml +++ b/ansible/roles/kolla-bifrost/defaults/main.yml @@ -74,5 +74,17 @@ kolla_bifrost_ipa_ramdisk_checksum_algorithm: # Server inventory to be configured in {{ kolla_node_custom_config_path }}/bifrost/servers.yml. kolla_bifrost_servers: {} +# Deprecated. # Free form extra configuration to append to {{ kolla_node_custom_config_path }}/bifrost/bifrost.yml. kolla_bifrost_extra_globals: + +# Paths to Kolla Ansible custom configuration. +kolla_bifrost_config_paths_default: + - "{{ role_path }}/templates" + +# Paths to Kolla Ansible custom configuration. +kolla_bifrost_config_paths_extra: [] + +# Paths to Kolla Ansible custom configuration. Defaults to a combination of +# kolla_bifrost_config_paths_default and kolla_bifrost_config_paths_extra. +kolla_bifrost_config_paths: "{{ kolla_bifrost_config_paths_default + kolla_bifrost_config_paths_extra }}" diff --git a/ansible/roles/kolla-bifrost/tasks/main.yml b/ansible/roles/kolla-bifrost/tasks/main.yml index 3f199541f..a455048ff 100644 --- a/ansible/roles/kolla-bifrost/tasks/main.yml +++ b/ansible/roles/kolla-bifrost/tasks/main.yml @@ -6,11 +6,11 @@ mode: 0750 - name: Ensure the Kolla Bifrost configuration files exist - template: - src: "{{ item.src }}" - dest: "{{ kolla_node_custom_config_path }}/bifrost/{{ item.dest }}" + merge_yaml: + sources: "{{ kolla_bifrost_config_paths | product(['/kolla/config/bifrost/' ~ item]) | map('join') | list }}" + dest: "{{ kolla_node_custom_config_path }}/bifrost/{{ item }}" mode: 0640 with_items: - - { src: bifrost.yml.j2, dest: bifrost.yml } - - { src: dib.yml.j2, dest: dib.yml } - - { src: servers.yml.j2, dest: servers.yml } + - bifrost.yml + - dib.yml + - servers.yml diff --git a/ansible/roles/kolla-bifrost/templates/bifrost.yml.j2 b/ansible/roles/kolla-bifrost/templates/kolla/config/bifrost/bifrost.yml similarity index 100% rename from ansible/roles/kolla-bifrost/templates/bifrost.yml.j2 rename to ansible/roles/kolla-bifrost/templates/kolla/config/bifrost/bifrost.yml diff --git a/ansible/roles/kolla-bifrost/templates/dib.yml.j2 b/ansible/roles/kolla-bifrost/templates/kolla/config/bifrost/dib.yml similarity index 62% rename from ansible/roles/kolla-bifrost/templates/dib.yml.j2 rename to ansible/roles/kolla-bifrost/templates/kolla/config/bifrost/dib.yml index e3456414f..a4fe0522b 100644 --- a/ansible/roles/kolla-bifrost/templates/dib.yml.j2 +++ b/ansible/roles/kolla-bifrost/templates/kolla/config/bifrost/dib.yml @@ -1,4 +1,5 @@ --- +{% if not overcloud_dib_build_host_images | bool %} # Diskimage-builder element for base OS. dib_os_element: "{{ kolla_bifrost_dib_os_element }}" @@ -13,3 +14,9 @@ dib_elements: "{{ (kolla_bifrost_dib_elements + [kolla_bifrost_dib_init_element] # List of DIB image packages. dib_packages: "{{ kolla_bifrost_dib_packages | join(',') }}" +{% else %} +# Stop building overcloud host image using Bifrost. This needs to be defined +# here to override the default true value set in kolla-ansible in +# ansible/roles/bifrost/templates/dib.yml.j2. +create_image_via_dib: False +{% endif %} diff --git a/ansible/roles/kolla-bifrost/templates/servers.yml.j2 b/ansible/roles/kolla-bifrost/templates/kolla/config/bifrost/servers.yml similarity index 100% rename from ansible/roles/kolla-bifrost/templates/servers.yml.j2 rename to ansible/roles/kolla-bifrost/templates/kolla/config/bifrost/servers.yml diff --git a/ansible/roles/kolla-build/defaults/main.yml b/ansible/roles/kolla-build/defaults/main.yml index 68ca8c38f..aecb0bfec 100644 --- a/ansible/roles/kolla-build/defaults/main.yml +++ b/ansible/roles/kolla-build/defaults/main.yml @@ -2,8 +2,15 @@ # Directory where Kolla config files will be installed. kolla_build_config_path: -# Path to extra kolla configuration files. -kolla_build_extra_config_path: +# Paths to extra kolla configuration files. +kolla_build_config_paths_default: + - "{{ role_path }}/templates/" + +# Paths to extra kolla configuration files. +kolla_build_config_paths_extra: [] + +# Paths to extra kolla configuration files. +kolla_build_config_paths: "{{ kolla_build_config_paths_default + kolla_build_config_paths_extra }}" # Valid options are [ centos, fedora, oraclelinux, ubuntu ] kolla_base_distro: diff --git a/ansible/roles/kolla-build/tasks/main.yml b/ansible/roles/kolla-build/tasks/main.yml index 08d6a74a2..82d5a2d43 100644 --- a/ansible/roles/kolla-build/tasks/main.yml +++ b/ansible/roles/kolla-build/tasks/main.yml @@ -1,23 +1,12 @@ --- -- name: Check whether a Kolla build extra configuration file exists - local_action: - module: stat - path: "{{ kolla_build_extra_config_path }}" - get_checksum: False - get_md5: False - mime: False - register: stat_result - -- name: Set a fact containing extra configuration - set_fact: - kolla_build_extra_config: "{{ lookup('template', kolla_build_extra_config_path) }}" - when: stat_result.stat.exists +- name: Ensure the Kolla build configuration file exists + merge_configs: + sources: "{{ kolla_build_config_paths | product(['/kolla/kolla-build.conf']) | map('join') | list }}" + dest: "{{ kolla_build_config_path }}/kolla-build.conf" + mode: 0644 -- name: Ensure the Kolla build configuration files exist +- name: Ensure the Kolla build template overrides file exists template: - src: "{{ item.src }}" - dest: "{{ kolla_build_config_path }}/{{ item.dest }}" + src: template-override.j2.j2 + dest: "{{ kolla_build_config_path }}/template-override.j2" mode: 0644 - with_items: - - { src: kolla-build.conf.j2, dest: kolla-build.conf } - - { src: template-override.j2.j2, dest: template-override.j2 } diff --git a/ansible/roles/kolla-build/templates/kolla-build.conf.j2 b/ansible/roles/kolla-build/templates/kolla/kolla-build.conf similarity index 97% rename from ansible/roles/kolla-build/templates/kolla-build.conf.j2 rename to ansible/roles/kolla-build/templates/kolla/kolla-build.conf index 5603bf787..78f5285d0 100644 --- a/ansible/roles/kolla-build/templates/kolla-build.conf.j2 +++ b/ansible/roles/kolla-build/templates/kolla/kolla-build.conf @@ -1,5 +1,3 @@ -# {{ ansible_managed }} - [DEFAULT] # Base container image distribution. diff --git a/ansible/roles/kolla-openstack/defaults/main.yml b/ansible/roles/kolla-openstack/defaults/main.yml index 975eb78af..a843fa749 100644 --- a/ansible/roles/kolla-openstack/defaults/main.yml +++ b/ansible/roles/kolla-openstack/defaults/main.yml @@ -447,9 +447,19 @@ kolla_extra_neutron_ml2: # Whether to enable Nova. kolla_enable_nova: +# Whether to enable Nova libvirt container. +kolla_enable_nova_libvirt_container: + # Free form extra configuration to append to nova.conf. kolla_extra_nova: +# Whether libvirt TLS is enabled. +kolla_libvirt_tls: + +# Directory containing libvirt certificates for nova-compute when running +# libvirt on the host. +kolla_nova_libvirt_certificates_src: + ############################################################################### # Octavia configuration. diff --git a/ansible/roles/kolla-openstack/molecule/enable-everything/molecule.yml b/ansible/roles/kolla-openstack/molecule/enable-everything/molecule.yml index 0309f8c2a..f0c9d3313 100644 --- a/ansible/roles/kolla-openstack/molecule/enable-everything/molecule.yml +++ b/ansible/roles/kolla-openstack/molecule/enable-everything/molecule.yml @@ -15,7 +15,7 @@ provisioner: inventory: group_vars: all: - kolla_extra_config_path: + kolla_extra_config_path: ${MOLECULE_TEMP_PATH:-/tmp}/molecule/kolla/config kolla_enable_aodh: true kolla_extra_aodh: | [extra-aodh.conf] @@ -116,9 +116,12 @@ provisioner: [extra-ml2_conf.ini] foo=bar kolla_enable_nova: true + kolla_enable_nova_libvirt_container: false kolla_extra_nova: | [extra-nova.conf] foo=bar + kolla_libvirt_tls: true + 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 d78cc6940..8514e90f3 100644 --- a/ansible/roles/kolla-openstack/molecule/enable-everything/prepare.yml +++ b/ansible/roles/kolla-openstack/molecule/enable-everything/prepare.yml @@ -25,3 +25,23 @@ with_items: - "{{ kolla_inspector_ipa_kernel_path }}" - "{{ kolla_inspector_ipa_ramdisk_path }}" + + - name: Ensure nova libvirt certificates directory exists + local_action: + module: file + path: "{{ kolla_nova_libvirt_certificates_src }}" + state: directory + + # NOTE(mgoddard): Previously we were creating empty files for the kernel + # and ramdisk, but this was found to cause ansible to hang on recent + # versions of docker. Using non-empty files seems to resolve the issue. + # See https://github.com/ansible/ansible/issues/36725. + - name: Ensure nova libvirt certificates exist + local_action: + module: copy + content: fake cert + dest: "{{ kolla_nova_libvirt_certificates_src }}/{{ item }}" + with_items: + - "cacert.pem" + - "clientcert.pem" + - "clientkey.pem" 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 55c30179c..ce28aafca 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 @@ -50,6 +50,7 @@ 'murano', 'neutron', 'nova', + 'nova/nova-libvirt', 'octavia', 'prometheus', 'sahara', @@ -98,7 +99,10 @@ def test_service_ini_file(host, path): @pytest.mark.parametrize( 'path', ['ironic/ironic-agent.initramfs', - 'ironic/ironic-agent.kernel']) + 'ironic/ironic-agent.kernel', + 'nova/nova-libvirt/cacert.pem', + 'nova/nova-libvirt/clientcert.pem', + 'nova/nova-libvirt/clientkey.pem']) def test_service_non_ini_file(host, path): # TODO(mgoddard): Check config file contents. path = os.path.join('/etc/kolla/config', path) diff --git a/ansible/roles/kolla-openstack/tasks/config.yml b/ansible/roles/kolla-openstack/tasks/config.yml index e1dc3cbc5..9e3f33b8c 100644 --- a/ansible/roles/kolla-openstack/tasks/config.yml +++ b/ansible/roles/kolla-openstack/tasks/config.yml @@ -79,6 +79,7 @@ recurse: true with_items: "{{ kolla_openstack_custom_config }}" register: find_src_result + delegate_to: localhost - name: Find previously generated extra configuration files find: @@ -113,6 +114,7 @@ - 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 untemplated extra configuration files exist copy: @@ -126,7 +128,8 @@ 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([]) + - (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 unnecessary extra configuration files are absent file: diff --git a/ansible/roles/kolla-openstack/vars/main.yml b/ansible/roles/kolla-openstack/vars/main.yml index 207d3d9ad..3db13760d 100644 --- a/ansible/roles/kolla-openstack/vars/main.yml +++ b/ansible/roles/kolla-openstack/vars/main.yml @@ -98,6 +98,9 @@ kolla_openstack_custom_config: 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 }}/" @@ -175,6 +178,27 @@ kolla_openstack_custom_config: dest: "{{ kolla_node_custom_config_path }}/nova" patterns: "*" enabled: "{{ kolla_enable_nova }}" + # 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 + 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" diff --git a/ansible/roles/snat/tasks/main.yml b/ansible/roles/snat/tasks/main.yml index 6fc64aa5c..2909ece4d 100644 --- a/ansible/roles/snat/tasks/main.yml +++ b/ansible/roles/snat/tasks/main.yml @@ -15,5 +15,10 @@ out_interface: "{{ item.interface }}" jump: SNAT to_source: "{{ item.source_ip }}" + destination: "{{ item.destination | default(omit) }}" + destination_port: "{{ item.destination_port | default(omit) }}" + destination_ports: "{{ item.destination_ports | default(omit) }}" + source: "{{ item.source | default(omit) }}" + source_port: "{{ item.source_port | default(omit) }}" with_items: "{{ snat_rules }}" become: True diff --git a/ansible/seed-ipa-build.yml b/ansible/seed-ipa-build.yml index 05297f332..1145bf819 100644 --- a/ansible/seed-ipa-build.yml +++ b/ansible/seed-ipa-build.yml @@ -53,8 +53,8 @@ - name: Copy Ironic Python Agent images into /httpboot command: > docker exec bifrost_deploy - bash -c 'export OS_CLOUD=bifrost && - ansible -vvvv target -i /bifrost/playbooks/inventory/target + bash -c 'ansible -vvvv target + -i /bifrost/playbooks/inventory/target -m copy -a "src=/etc/bifrost/{{ item }} dest=/httpboot/{{ item }}" -e "ansible_python_interpreter=/var/lib/kolla/venv/bin/python"' diff --git a/ansible/seed-vm-provision.yml b/ansible/seed-vm-provision.yml index 284479887..ea547fd71 100644 --- a/ansible/seed-vm-provision.yml +++ b/ansible/seed-vm-provision.yml @@ -29,6 +29,26 @@ group: "{{ ansible_facts.user_gid }}" become: True + # NOTE(mgoddard): Prior to the Xena release, the seed VM was provisioned + # using the stackhpc.livirt-vm role with become=true. This resulted in the + # cached image being owned by root. Since Xena, we execute the role without + # become=true. Correct the image ownership to avoid a permission denied + # error when downloading a new image of the same name. + - name: Stat image files + stat: + path: "{{ image_cache_path }}/{{ item.image | basename }}" + with_items: "{{ hostvars[seed_host].seed_vm_volumes | selectattr('image', 'defined') }}" + register: image_stat_result + + - name: Fix image ownership + file: + path: "{{ image_cache_path }}/{{ item.item.image | basename }}" + owner: "{{ ansible_facts.user_uid }}" + group: "{{ ansible_facts.user_gid }}" + with_items: "{{ image_stat_result.results }}" + when: item.stat.exists + become: true + roles: - role: jriguera.configdrive # For now assume the VM OS family is the same as the hypervisor's. @@ -99,7 +119,6 @@ volumes: "{{ hostvars[seed_host].seed_vm_volumes + [seed_vm_configdrive_volume] }}" interfaces: "{{ hostvars[seed_host].seed_vm_interfaces }}" console_log_enabled: true - become: True tasks: - name: Wait for SSH access to the seed VM diff --git a/ansible/sysctl.yml b/ansible/sysctl.yml index eb8695342..0f4ad7435 100644 --- a/ansible/sysctl.yml +++ b/ansible/sysctl.yml @@ -1,6 +1,6 @@ --- - name: Ensure sysctl parameters are configured - hosts: seed:seed-hypervisor:overcloud + hosts: seed:seed-hypervisor:overcloud:infra-vms tags: - sysctl roles: diff --git a/ansible/time.yml b/ansible/time.yml index 3c88cfa2a..33f8ad7ba 100644 --- a/ansible/time.yml +++ b/ansible/time.yml @@ -1,6 +1,6 @@ --- - name: Ensure timezone is configured - hosts: seed-hypervisor:seed:overcloud + hosts: seed-hypervisor:seed:overcloud:infra-vms tags: - timezone tasks: diff --git a/ansible/users.yml b/ansible/users.yml index 666eaaed3..6a89b7697 100644 --- a/ansible/users.yml +++ b/ansible/users.yml @@ -1,6 +1,6 @@ --- - name: Ensure users exist - hosts: seed:seed-hypervisor:overcloud + hosts: seed:seed-hypervisor:overcloud:infra-vms tags: - users roles: diff --git a/ansible/wipe-disks.yml b/ansible/wipe-disks.yml index 7aec95078..8ade2328e 100644 --- a/ansible/wipe-disks.yml +++ b/ansible/wipe-disks.yml @@ -8,7 +8,7 @@ # also closed and removed from crypttab. - name: Ensure that all unmounted block devices are wiped - hosts: seed-hypervisor:seed:overcloud + hosts: seed-hypervisor:seed:overcloud:infra-vms tags: - wipe-disks roles: diff --git a/dev/functions b/dev/functions index 3319bba83..35f4d90c5 100644 --- a/dev/functions +++ b/dev/functions @@ -10,12 +10,8 @@ set -o pipefail function config_defaults { # Set default values for kayobe development configuration. - # Try to detect if we are running in a vagrant VM. - if [[ -e /vagrant ]]; then - KAYOBE_SOURCE_PATH_DEFAULT=/vagrant - else - KAYOBE_SOURCE_PATH_DEFAULT="$(pwd)" - fi + PARENT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + KAYOBE_SOURCE_PATH_DEFAULT="$(dirname ${PARENT})" # Path to the kayobe source code repository. Typically this will be the # Vagrant shared directory. @@ -121,7 +117,7 @@ function config_init { # Installation function is_dnf { - if [[ -e /etc/centos-release ]]; then + if [[ -e /etc/centos-release || -e /etc/rocky-release ]]; then /usr/bin/which dnf >/dev/null 2>&1 else return 1 @@ -129,7 +125,7 @@ function is_dnf { } function is_yum { - if [[ -e /etc/centos-release ]]; then + if [[ -e /etc/centos-release || -e /etc/rocky-release ]]; then /usr/bin/which yum >/dev/null 2>&1 else return 1 @@ -210,6 +206,11 @@ function is_ironic_enabled { [[ $ironic_enabled =~ ^true$ ]] } +function is_overcloud_host_image_built_by_dib { + overcloud_dib_build_host_images=$(kayobe configuration dump --host controllers[0] --var-name overcloud_dib_build_host_images) + [[ $overcloud_dib_build_host_images =~ ^true$ ]] +} + function environment_setup { # NOTE: Virtualenv's activate script references an unbound variable. set +u @@ -307,6 +308,13 @@ function seed_deploy { else echo "Not building seed deployment images" fi + + if is_overcloud_host_image_built_by_dib; then + echo "Building overcloud host images" + run_kayobe overcloud host image build + else + echo "Not building overcloud host images" + fi } function seed_upgrade { @@ -349,18 +357,26 @@ function overcloud_deploy { control_host_bootstrap - echo "Configuring the controller host" - run_kayobe overcloud host configure - - # FIXME(mgoddard): Perform host upgrade workarounds to ensure hostname - # resolves to IP address of API interface for RabbitMQ. This seems to be - # required since https://review.openstack.org/#/c/584427 was merged. - echo "Workaround: upgrading the controller host" - run_kayobe overcloud host upgrade - if [[ ${KAYOBE_OVERCLOUD_GENERATE_CERTIFICATES} = 1 ]]; then echo "Generate TLS certificates" - run_kayobe kolla ansible run certificates --kolla-extra kolla_certificates_dir=${KAYOBE_CONFIG_PATH}/kolla/certificates + run_kayobe playbook run $KAYOBE_SOURCE_PATH/ansible/kolla-ansible.yml -t config + # NOTE(mgoddard): There is a chicken and egg when generating libvirt + # TLS certificates using the kolla-ansible certificates command, and + # host libvirt. The certificates command needs to be able to gather + # facts for all hosts, but since the host configure step hasn't been + # run, we don't have SSH or the kolla user configured yet. However, we + # can't run host configure without the libvirt TLS certificates. + # Workaround: add the host to SSH known hosts and SSH as $USER. + run_kayobe playbook run $KAYOBE_SOURCE_PATH/ansible/ssh-known-host.yml -l overcloud + + # Avoid populating the fact cache with this weird setup. + export ANSIBLE_CACHE_PLUGIN=memory + run_kayobe kolla ansible run certificates \ + --kolla-extra kolla_certificates_dir=${KAYOBE_CONFIG_PATH}/kolla/certificates \ + --kolla-extra ansible_user=$USER \ + --kolla-extra ansible_python_interpreter=/usr/bin/python3 + unset ANSIBLE_CACHE_PLUGIN + # Add CA cert to trust store. ca_cert=${KAYOBE_CONFIG_PATH}/kolla/certificates/ca/root.crt if [[ -e /etc/debian_version ]]; then @@ -374,6 +390,15 @@ function overcloud_deploy { fi fi + echo "Configuring the controller host" + run_kayobe overcloud host configure + + # FIXME(mgoddard): Perform host upgrade workarounds to ensure hostname + # resolves to IP address of API interface for RabbitMQ. This seems to be + # required since https://review.openstack.org/#/c/584427 was merged. + echo "Workaround: upgrading the controller host" + run_kayobe overcloud host upgrade + # Note: This must currently be before host configure, because host # configure runs kolla-ansible.yml, which validates the presence of the # built deploy images. diff --git a/dev/tenks-deploy-config-compute-libvirt-on-host.yml b/dev/tenks-deploy-config-compute-libvirt-on-host.yml new file mode 100644 index 000000000..d8cd1a14f --- /dev/null +++ b/dev/tenks-deploy-config-compute-libvirt-on-host.yml @@ -0,0 +1,56 @@ +--- +# This file holds the config given to Tenks when running `tenks-deploy.sh`. It +# assumes the existence of the bridge `breth1`. + +node_types: + type0: + memory_mb: 1024 + vcpus: 1 + volumes: + # There is a minimum disk space capacity requirement of 4GiB when using Ironic Python Agent: + # https://github.com/openstack/ironic-python-agent/blob/master/ironic_python_agent/utils.py#L290 + - capacity: 4GiB + physical_networks: + - physnet1 + console_log_enabled: true + # We seem to hit issues with missing cpu features in CI as a result of using host-model, e.g: + # https://zuul.opendev.org/t/openstack/build/02c33ab51664419a88a5a54ad22852a9/log/primary/system_logs/libvirt/qemu/tk0.txt.gz#38 + cpu_mode: + +specs: + - type: type0 + count: 2 + ironic_config: + resource_class: test-rc + network_interface: flat + +nova_flavors: + - resource_class: test-rc + node_type: type0 + +physnet_mappings: + physnet1: breth1 + +deploy_kernel: ipa.kernel +deploy_ramdisk: ipa.initramfs + +default_boot_mode: "bios" + +# Use the libvirt daemon deployed by Kayobe. Tenks will install libvirt client +# packages. +libvirt_host_install_daemon: false + +# Configure AppArmor for the pool on Ubuntu. +libvirt_host_configure_apparmor: true + +# Nested virtualisation is not working well in CI currently. Force the use of +# QEMU. +libvirt_vm_engine: "qemu" + +# QEMU may not be installed on the host, so set the path and avoid +# autodetection. +libvirt_vm_emulator: "{% if ansible_facts.os_family == 'RedHat' %}/usr/libexec/qemu-kvm{% else %}/usr/bin/qemu-system-x86_64{% endif %}" + +# Specify a log path in the kolla_logs Docker volume. It is accessible on the +# host at the same path. +libvirt_vm_default_console_log_dir: "/var/log/kolla/tenks" diff --git a/doc/source/administration/index.rst b/doc/source/administration/index.rst index d1790508b..79cee41ce 100644 --- a/doc/source/administration/index.rst +++ b/doc/source/administration/index.rst @@ -10,5 +10,6 @@ administrative tasks. general seed + infra-vms overcloud bare-metal diff --git a/doc/source/administration/infra-vms.rst b/doc/source/administration/infra-vms.rst new file mode 100644 index 000000000..5633d1a00 --- /dev/null +++ b/doc/source/administration/infra-vms.rst @@ -0,0 +1,79 @@ +======================= +Infra VM Administration +======================= + +Deprovisioning Infrastructure VMs +================================= + +.. note:: + + This step will destroy the infrastructure VMs and associated data volumes. + Make sure you backup any data you want to keep. + +To deprovision all VMs:: + + (kayobe) $ kayobe infra vm deprovision + +This can be limited to a subset of the nodes using the ``--limit`` option:: + + (kayobe) $ kayobe infra vm deprovision --limit example-vm-1 + +Updating Packages +================= + +It is possible to update packages on the infrastructure VMs. + +Package Repositories +-------------------- + +If using custom DNF package repositories on CentOS, it may be necessary to +update these prior to running a package update. To do this, update the +configuration in ``${KAYOBE_CONFIG_PATH}/dnf.yml`` and run the following +command:: + + (kayobe) $ kayobe infra vm host configure --tags dnf + +Package Update +-------------- + +To update one or more packages:: + + (kayobe) $ kayobe infra vm host package update --packages , + +To update all eligible packages, use ``*``, escaping if necessary:: + + (kayobe) $ kayobe infra vm host package update --packages "*" + +To only install updates that have been marked security related:: + + (kayobe) $ kayobe infra vm host package update --packages "*" --security + +Note that these commands do not affect packages installed in containers, only +those installed on the host. + +Kernel Updates +-------------- + +If the kernel has been updated, you will probably want to reboot the host +to boot into the new kernel. This can be done using a command such as the +following:: + + (kayobe) $ kayobe infra vm host command run --command "shutdown -r" --become + +Running Commands +================ + +It is possible to run a command on the host:: + + (kayobe) $ kayobe infra vm host command run --command "" + +For example:: + + (kayobe) $ kayobe infra vm host command run --command "service docker restart" + +Commands can also be run on the seed hypervisor host, if one is in use:: + + (kayobe) $ kayobe seed hypervisor host command run --command "" + +To execute the command with root privileges, add the ``--become`` argument. +Adding the ``--verbose`` argument allows the output of the command to be seen. diff --git a/doc/source/configuration/reference/bifrost.rst b/doc/source/configuration/reference/bifrost.rst index f8411cc27..dc2e72e7c 100644 --- a/doc/source/configuration/reference/bifrost.rst +++ b/doc/source/configuration/reference/bifrost.rst @@ -39,6 +39,11 @@ For example, to install Bifrost from a custom git repository: Overcloud root disk image configuration ======================================= +.. note:: + + This configuration only applies when ``overcloud_dib_build_host_images`` + (set in ``${KAYOBE_CONFIG_PATH}/overcloud-dib.yml``) is not changed to true. + Bifrost uses Diskimage builder (DIB) to build a root disk image that is deployed to overcloud hosts when they are provisioned. The following options configure how this image is built. Consult the @@ -179,6 +184,23 @@ Rather than needing to write a custom DIB element, we can use the kolla_bifrost_dib_packages: - "biosdevname" +.. _configuration-bifrost-image-deployment-config: + +Disk image deployment configuration +=================================== + +The name of the root disk image to deploy can be configured via the +``kolla_bifrost_deploy_image_filename`` option, which defaults to +``deployment_image.qcow2``. It can be defined globally in +``${KAYOBE_CONFIG_PATH}/bifrost.yml``, or defined per-group or per-host in the +Kayobe inventory. This can be used to provision different images across the +overcloud. + +While only a single disk image can be built with Bifrost, starting from the +Yoga 12.0.0 release, Kayobe supports building multiple disk images directly +through Diskimage builder. Consult the :ref:`overcloud host disk image build +documentation ` for more details. + Ironic configuration ==================== @@ -285,7 +307,7 @@ Inventory configuration This feature is currently not well tested. It is advisable to use autodiscovery of overcloud servers instead. -The following options are used to configure a static inventory of servers for +The following option is used to configure a static inventory of servers for Bifrost. ``kolla_bifrost_servers`` diff --git a/doc/source/configuration/reference/hosts.rst b/doc/source/configuration/reference/hosts.rst index 7ecebf175..128c99f4d 100644 --- a/doc/source/configuration/reference/hosts.rst +++ b/doc/source/configuration/reference/hosts.rst @@ -298,8 +298,7 @@ oversight or testing. Apt === -On Ubuntu, Apt is used to manage packages and package repositories. Currently -Kayobe does not provide support for configuring custom Apt repositories. +On Ubuntu, Apt is used to manage packages and package repositories. Apt cache --------- @@ -307,10 +306,124 @@ Apt cache The Apt cache timeout may be configured via ``apt_cache_valid_time`` (in seconds) in ``etc/kayobe/apt.yml``, and defaults to 3600. +Apt proxy +--------- + Apt can be configured to use a proxy via ``apt_proxy_http`` and ``apt_proxy_https`` in ``etc/kayobe/apt.yml``. These should be set to the full URL of the relevant proxy (e.g. ``http://squid.example.com:3128``). +Apt configuration +----------------- + +Arbitrary global configuration options for Apt may be defined via the +``apt_config`` variable in ``etc/kayobe/apt.yml`` since the Yoga release. The +format is a list, with each item mapping to a dict/map with the following +items: + +* ``content``: free-form configuration file content +* ``filename``: name of a file in ``/etc/apt/apt.conf.d/`` in which to write + the configuration + +The default of ``apt_config`` is an empty list. + +For example, the following configuration tells Apt to use 2 attempts when +downloading packages: + +.. code-block:: yaml + + apt_config: + - content: | + Acquire::Retries 1; + filename: 99retries + +Apt repositories +---------------- + +Kayobe supports configuration of custom Apt repositories via the +``apt_repositories`` variable in ``etc/kayobe/apt.yml`` since the Yoga release. +The format is a list, with each item mapping to a dict/map with the following +items: + +* ``types``: whitespace-separated list of repository types, e.g. ``deb`` or + ``deb-src`` (optional, default is ``deb``) +* ``url``: URL of the repository +* ``suites``: whitespace-separated list of suites, e.g. ``focal`` (optional, + default is ``ansible_facts.distribution_release``) +* ``components``: whitespace-separated list of components, e.g. ``main`` + (optional, default is ``main``) +* ``signed_by``: whitespace-separated list of names of GPG keyring files in + ``apt_keys_path`` (optional, default is unset) +* ``architecture``: whitespace-separated list of architectures that will be used + (optional, default is unset) + +The default of ``apt_repositories`` is an empty list. + +For example, the following configuration defines a single Apt repository: + +.. code-block:: yaml + :caption: ``apt.yml`` + + apt_repositories: + - types: deb + url: https://example.com/repo + suites: focal + components: all + +In the following example, the Ubuntu Focal 20.04 repositories are consumed from +a local package mirror. The ``apt_disable_sources_list`` variable is set to +``true``, which disables all repositories in ``/etc/apt/sources.list``, +including the default Ubuntu ones. + +.. code-block:: yaml + :caption: ``apt.yml`` + + apt_repositories: + - url: http://mirror.example.com/ubuntu/ + suites: focal focal-updates + components: main restricted universe multiverse + - url: http://mirror.example.com/ubuntu/ + suites: focal-security + components: main restricted universe multiverse + + apt_disable_sources_list: true + +Apt keys +-------- + +Some repositories may be signed by a key that is not one of Apt's trusted keys. +Kayobe avoids the use of the deprecated ``apt-key`` utility, and instead allows +keys to be downloaded to a directory. This enables repositories to use the +``SignedBy`` option to state that they are signed by a specific key. This +approach is more secure than using globally trusted keys. + +Keys to be downloaded are defined by the ``apt_keys`` variable. The format is a +list, with each item mapping to a dict/map with the following items: + +* ``url``: URL of key +* ``filename``: Name of a file in which to store the downloaded key in + ``apt_keys_path``. The extension should be ``.asc`` for ASCII-armoured keys, + or ``.gpg`` otherwise. + +The default value of ``apt_keys`` is an empty list. + +In the following example, a key is downloaded, and a repository is configured +that is signed by the key. + +.. code-block:: yaml + :caption: ``apt.yml`` + + apt_keys: + - url: https://example.com/GPG-key + filename: example-key.asc + + apt_repositories: + - types: deb + url: https://example.com/repo + suites: focal + components: all + signed_by: example-key.asc + SELinux ======= *tags:* @@ -332,6 +445,102 @@ Network Configuration Configuration of host networking is covered in depth in :ref:`configuration-network`. +Firewalld +========= +*tags:* + | ``firewall`` + +.. note:: Firewalld is supported on CentOS systems only. Currently no + firewall is supported on Ubuntu. + +Firewalld can be used to provide a firewall on CentOS systems. Since the Xena +release, Kayobe provides support for enabling or disabling firewalld, as well +as defining zones and rules. + +The following variables can be used to set whether to enable firewalld: + +* ``seed_hypervisor_firewalld_enabled`` +* ``seed_firewalld_enabled`` +* ``infra_vm_firewalld_enabled`` +* ``compute_firewalld_enabled`` +* ``controller_firewalld_enabled`` +* ``monitoring_firewalld_enabled`` +* ``storage_firewalld_enabled`` + +When firewalld is enabled, the following variables can be used to configure a +list of zones to create. Each item is a dict containing a ``zone`` item: + +* ``seed_hypervisor_firewalld_zones`` +* ``seed_firewalld_zones`` +* ``infra_vm_firewalld_zones`` +* ``compute_firewalld_zones`` +* ``controller_firewalld_zones`` +* ``monitoring_firewalld_zones`` +* ``storage_firewalld_zones`` + +The following variables can be used to set a default zone. The default is +unset, in which case the default zone will not be changed: + +* ``seed_hypervisor_firewalld_default_zone`` +* ``seed_firewalld_default_zone`` +* ``infra_vm_firewalld_default_zone`` +* ``compute_firewalld_default_zone`` +* ``controller_firewalld_default_zone`` +* ``monitoring_firewalld_default_zone`` +* ``storage_firewalld_default_zone`` + +The following variables can be used to set a list of rules to apply. Each item +is a dict containing arguments to pass to the ``firewalld`` module. Arguments +are omitted if not provided, with the following exceptions: ``offline`` +(default ``true``), ``permanent`` (default ``true``), ``state`` (default +``enabled``): + +* ``seed_hypervisor_firewalld_rules`` +* ``seed_firewalld_rules`` +* ``infra_vm_firewalld_rules`` +* ``compute_firewalld_rules`` +* ``controller_firewalld_rules`` +* ``monitoring_firewalld_rules`` +* ``storage_firewalld_rules`` + +In the following example, firewalld is enabled on controllers. ``public`` and +``internal`` zones are created, with their default rules disabled. TCP port +8080 is open in the ``internal`` zone, and the ``http`` service is open in the +``public`` zone: + +.. code-block:: yaml + + controller_firewalld_enabled: true + + controller_firewalld_zones: + - zone: public + - zone: internal + + controller_firewalld_rules: + # Disable default rules in internal zone. + - service: dhcpv6-client + state: disabled + zone: internal + - service: samba-client + state: disabled + zone: internal + - service: ssh + state: disabled + zone: internal + # Disable default rules in public zone. + - service: dhcpv6-client + state: disabled + zone: public + - service: ssh + state: disabled + zone: public + # Enable TCP port 8080 in internal zone. + - port: 8080/tcp + zone: internal + # Enable the HTTP service in the public zone. + - service: http + zone: public + Sysctls ======= *tags:* @@ -754,3 +963,178 @@ Ansible's containers do), but may be necessary when building images. Docker's live restore feature can be configured via ``docker_daemon_live_restore``, although it is disabled by default due to issues observed. + +Compute libvirt daemon +====================== +*tags:* + | ``libvirt-host`` + +.. note:: + + This section is about the libvirt daemon on compute nodes, as opposed to the + seed hypervisor. + +Since Yoga, Kayobe provides support for deploying and configuring a libvirt +host daemon, as an alternative to the ``nova_libvirt`` container support by +Kolla Ansible. The host daemon is not used by default, but it is possible to +enable it by setting ``kolla_enable_nova_libvirt_container`` to ``false`` in +``$KAYOBE_CONFIG_PATH/kolla.yml``. + +Migration of hosts from a containerised libvirt to host libvirt is currently +not supported. + +The following options are available in ``$KAYOBE_CONFIG_PATH/compute.yml`` and +are relevant only when using the libvirt daemon rather than the +``nova_libvirt`` container: + +``compute_libvirt_enabled`` + Whether to enable a host libvirt daemon. Default is true if + ``kolla_enable_nova`` is ``true`` and + ``kolla_enable_nova_libvirt_container`` is ``false``. +``compute_libvirt_conf_default`` + A dict of default configuration options to write to + ``/etc/libvirt/libvirtd.conf``. +``compute_libvirt_conf_extra`` + A dict of additional configuration options to write to + ``/etc/libvirt/libvirtd.conf``. +``compute_libvirt_conf`` + A dict of configuration options to write to ``/etc/libvirt/libvirtd.conf``. + Default is a combination of ``compute_libvirt_conf_default`` and + ``compute_libvirt_conf_extra``. +``compute_libvirtd_log_level`` + Numerical log level for libvirtd. Default is 3. +``compute_qemu_conf_default`` + A dict of default configuration options to write to + ``/etc/libvirt/qemu.conf``. +``compute_qemu_conf_extra`` + A dict of additional configuration options to write to + ``/etc/libvirt/qemu.conf``. +``compute_qemu_conf`` + A dict of configuration options to write to ``/etc/libvirt/qemu.conf``. + Default is a combination of ``compute_qemu_conf_default`` and + ``compute_qemu_conf_extra``. +``compute_libvirt_enable_sasl`` + Whether to enable libvirt SASL authentication. Default is the same as + ``compute_libvirt_tcp_listen``. +``compute_libvirt_sasl_password`` + libvirt SASL password. Default is unset. This must be defined when + ``compute_libvirt_enable_sasl`` is ``true``. +``compute_libvirt_enable_tls`` + Whether to enable a libvirt TLS listener. Default is false. +``compute_libvirt_ceph_repo_install`` + Whether to install a Ceph package repository on CentOS and Rocky hosts. + Default is ``true``. +``compute_libvirt_ceph_repo_release`` + Ceph package repository release to install on CentOS and Rocky hosts when + ``compute_libvirt_ceph_repo_install`` is ``true``. Default is ``pacific``. + +Example: custom libvirtd.conf +----------------------------- + +To customise the libvirt daemon log output to send level 3 to the journal: + +.. code-block:: yaml + :caption: ``compute.yml`` + + compute_libvirt_conf_extra: + log_outputs: "3:journald" + +Example: custom qemu.conf +------------------------- + +To customise QEMU to avoid adding timestamps to logs: + +.. code-block:: yaml + :caption: ``compute.yml`` + + compute_qemu_conf_extra: + log_timestamp: 0 + +Example: SASL +------------- + +SASL authentication is enabled by default. This provides authentication for +TCP and TLS connections to the libvirt API. A password is required, and should +be encrypted using Ansible Vault. + +.. code-block:: yaml + :caption: ``compute.yml`` + + compute_libvirt_sasl_password: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 63363937303539373738356236393563636466313130633435353933613637343231303836343933 + 3463623265653030323665383337376462363434396361320a653737376237353261303066616637 + 66613562316533313632613433643537346463303363376664396661343835373033326261383065 + 3731643633656636360a623534313665343066656161333866613338313266613465336332376463 + 3234 + +Example: enabling libvirt TLS listener +-------------------------------------- + +To enable the libvirt TLS listener: + +.. code-block:: yaml + :caption: ``compute.yml`` + + compute_libvirt_enable_tls: true + +When the TLS listener is enabled, it is necessary to provide client, server and +CA certificates. The following files should be provided: + +``cacert.pem`` + CA certificate used to sign client and server certificates. +``clientcert.pem`` + Client certificate. +``clientkey.pem`` + Client key. +``servercert.pem`` + Server certificate. +``serverkey.pem`` + Server key. + +It is recommended to encrypt the key files using Ansible Vault. + +The following paths are searched for these files: + +* ``$KAYOBE_CONFIG_PATH/certificates/libvirt/{{ inventory_hostname }}/`` +* ``$KAYOBE_CONFIG_PATH/certificates/libvirt/`` + +In this way, certificates may be generated for each host, or shared using +wildcard certificates. + +If using Kayobe environments, certificates in the environment take precedence. + +Kayobe makes the CA certificate and client certificate and key available to +Kolla Ansible, for use by the ``nova_compute`` service. + +Example: disabling Ceph repository installation +----------------------------------------------- + +On CentOS and Rocky hosts, a CentOS Storage SIG Ceph repository is installed +that provides more recent Ceph libraries than those available in CentOS/Rocky +AppStream. This may be necessary when using Ceph for Cinder volumes or Nova +ephemeral block devices. In some cases, such as when using local package +mirrors, the upstream repository may not be appropriate. The installation of +the repository may be disabled as follows: + +.. code-block:: yaml + :caption: ``compute.yml`` + + compute_libvirt_ceph_repo_install: false + +Example: installing additional packages +--------------------------------------- + +In some cases it may be useful to install additional packages on compute hosts +for use by libvirt. The `stackhpc.libvirt-host +`__ Ansible role supports +this via the ``libvirt_host_extra_daemon_packages`` variable. The variable +should be defined via group variables in the Ansible inventory, to avoid +applying the change to the seed hypervisor. For example, to install the +``trousers`` package used for accessing TPM hardware: + +.. code-block:: yaml + :caption: ``inventory/group_vars/compute/libvirt`` + + libvirt_host_extra_daemon_packages: + - trousers diff --git a/doc/source/configuration/reference/index.rst b/doc/source/configuration/reference/index.rst index 380144bce..100b7ae84 100644 --- a/doc/source/configuration/reference/index.rst +++ b/doc/source/configuration/reference/index.rst @@ -18,7 +18,9 @@ options. kolla kolla-ansible bifrost + overcloud-dib ironic-python-agent docker-registry seed-custom-containers + infra-vms nova-cells diff --git a/doc/source/configuration/reference/infra-vms.rst b/doc/source/configuration/reference/infra-vms.rst new file mode 100644 index 000000000..baa46faca --- /dev/null +++ b/doc/source/configuration/reference/infra-vms.rst @@ -0,0 +1,97 @@ +.. _configuration-infra-vms: + +================== +Infrastructure VMs +================== + +Kayobe can deploy infrastructure VMs to the seed-hypervisor. These can be used +to provide supplementary services that do not run well within a containerised +environment or are dependencies of the control plane. + +Configuration +============= + +To deploy an infrastructure VM, add a new host to the the ``infra-vms`` group +in the inventory: + +.. code-block:: ini + :caption: ``$KAYOBE_CONFIG_PATH/inventory/infra-vms`` + + [infra-vms] + an-example-vm + +The configuration of the virtual machine should be done using ``host_vars``. +These override the ``group_vars`` defined for the ``infra-vms`` group. Most +variables have sensible defaults defined, but there are a few variables which +must be set. + +Mandatory variables +------------------- + +All networks must have an interface defined, as described in +:ref:`configuration-network-per-host`. By default the VMs are attached +to the admin overcloud network. If, for example, ``admin_oc_net_name`` was +set to ``example_net``, you would need to define ``example_net_interface``. +It is possible to change the list of networks that a VM is attached to +by modifying ``infra_vm_network_interfaces``. Additional interfaces +can be added by setting ``infra_vm_network_interfaces_extra``. + +List of Kayobe applied defaults to required docker_container variables. +Any of these variables can be overridden with a ``host_var``. + +.. literalinclude:: ../../../../ansible/group_vars/all/infra-vms + :language: yaml + +Customisations +-------------- + +Examples of common customisations are shown below. + +By default the Ansible inventory name is used as the name of the VM. This may +be overridden via ``infra_vm_name``: + +.. code-block:: yaml + :caption: ``$KAYOBE_CONFIG_PATH/inventory/host_vars/an-example-vm`` + + # Name of the infra VM. + infra_vm_name: "the-special-one" + +By default the VM has 16G of RAM. This may be changed via +``infra_vm_memory_mb``: + +.. code-block:: yaml + :caption: ``$KAYOBE_CONFIG_PATH/inventory/host_vars/an-example-vm`` + + # Memory in MB. Defaults to 16GB. + infra_vm_memory_mb: "{{ 8 * 1024 }}" + +The default network configuration attaches infra VMs to the admin network. If +this is not appropriate, modify ``infra_vm_network_interfaces``. At a minimum +the network interface name for the network should be defined. + +.. code-block:: yaml + :caption: ``$KAYOBE_CONFIG_PATH/inventory/host_vars/an-example-vm`` + + # Network interfaces that the VM is attached to. + infra_vm_network_interfaces: + - aio + + # Mandatory: All networks must have an interface defined. + aio_interface: eth0 + + # By default kayobe will connect to a host via ``admin_oc_net``. + # As we have not attached this VM to this network, we must override + # ansible_host. + ansible_host: "{{ 'aio' | net_ip }}" + +Configuration for all VMs can be set using ``extra_vars`` defined in +``$KAYOBE_CONFIG_PATH/infra-vms.yml``. Note that normal Ansible precedence +rules apply and the variables will override any ``host_vars``. If you need to +override the defaults, but still maintain per-host settings, use ``group_vars`` +instead. + +Deploying the virtual machine +============================= + +Once the initial configuration has been done follow the steps in +:ref:`deployment-infrastructure-vms`. diff --git a/doc/source/configuration/reference/network.rst b/doc/source/configuration/reference/network.rst index 31cd90d05..f0fc14b4e 100644 --- a/doc/source/configuration/reference/network.rst +++ b/doc/source/configuration/reference/network.rst @@ -396,6 +396,8 @@ An interface will be assigned an IP address if the associated network has a the ``allocation_pool_start`` and ``allocation_pool_end`` attributes, if one has not been statically assigned in ``network-allocation.yml``. +.. _configuration-network-interface: + Configuring Ethernet Interfaces ------------------------------- diff --git a/doc/source/configuration/reference/os-distribution.rst b/doc/source/configuration/reference/os-distribution.rst index 4e86962b8..7347494cd 100644 --- a/doc/source/configuration/reference/os-distribution.rst +++ b/doc/source/configuration/reference/os-distribution.rst @@ -1,3 +1,5 @@ +.. _os-distribution: + =============== OS Distribution =============== diff --git a/doc/source/configuration/reference/overcloud-dib.rst b/doc/source/configuration/reference/overcloud-dib.rst new file mode 100644 index 000000000..57bd9eb78 --- /dev/null +++ b/doc/source/configuration/reference/overcloud-dib.rst @@ -0,0 +1,242 @@ +.. _overcloud-dib: + +=============================== +Overcloud host disk image build +=============================== + +This section covers configuration for building overcloud host disk images with +Diskimage builder (DIB), which is available from the Yoga 12.0.0 release. This +configuration is applied in ``${KAYOBE_CONFIG_PATH}/overcloud-dib.yml``. + +Enabling host disk image build +============================== + +From the Yoga release, disk images for overcloud hosts can be built directly +using Diskimage builder rather than through Bifrost. This is enabled with the +following option: + +``overcloud_dib_build_host_images`` + Whether to build host disk images with DIB directly instead of through + Bifrost. Setting it to true disables Bifrost image build and allows images + to be built with the ``kayobe overcloud host image build`` command. Default + value is false. This will change in a future release. + +With this option enabled, Bifrost will be configured to stop building a root +disk image. This will become the default behaviour in a future release. + +Overcloud root disk image configuration +======================================= + +Kayobe uses Diskimage builder (DIB) to build root disk images that are deployed +to overcloud hosts when they are provisioned. The following options configure +how these images are built. Consult the +:diskimage-builder-doc:`Diskimage-builder documentation <>` for further +information on building disk images. + +The default configuration builds a whole disk (partitioned) image using the +selected :ref:`OS distribution ` (CentOS Stream 8 by default) +with serial console enabled, and SELinux disabled if CentOS Stream is used. +`Cloud-init `__ is used to process +the configuration drive built by Bifrost during provisioning. + +``overcloud_dib_host_images`` + List of overcloud host disk images to build. Each element is a dict + defining an image in a format accepted by the `stackhpc.os-images + `__ role. Default is to + build an image named ``deployment_image`` configured with the + ``overcloud_dib_*`` variables defined below: ``{"name": "deployment_image", + "elements": "{{ overcloud_dib_elements }}", "env": "{{ + overcloud_dib_env_vars }}", "packages": "{{ overcloud_dib_packages }}"}``. +``overcloud_dib_os_element`` + DIB base OS element. Default is ``{{ os_distribution }}``. +``overcloud_dib_os_release`` + DIB image OS release. Default is ``{{ os_release }}``. +``overcloud_dib_elements_default`` + List of default DIB elements. Default is ``["centos", + "cloud-init-datasources", "disable-selinux", "enable-serial-console", + "vm"]`` when ``overcloud_dib_os_element`` is ``centos``, or ``["ubuntu", + "cloud-init-datasources", "enable-serial-console", "vm"]`` when + ``overcloud_dib_os_element`` is ``ubuntu``. The ``vm`` element is poorly + named, and causes DIB to build a whole disk image rather than a single + partition. +``overcloud_dib_elements_extra`` + List of additional DIB elements. Default is none. +``overcloud_dib_elements`` + List of DIB elements. Default is a combination of ``overcloud_dib_elements_default`` + and ``overcloud_dib_elements_extra``. +``overcloud_dib_env_vars_default`` + DIB default environment variables. Default is + ``{"DIB_BOOTLOADER_DEFAULT_CMDLINE": "nofb nomodeset gfxpayload=text + net.ifnames=1", "DIB_CLOUD_INIT_DATASOURCES": "ConfigDrive", "DIB_RELEASE": + "{{ overcloud_dib_os_release }}"}``. +``overcloud_dib_env_vars_extra`` + DIB additional environment variables. Default is none. +``overcloud_dib_env_vars`` + DIB environment variables. Default is combination of + ``overcloud_dib_env_vars_default`` and + ``overcloud_dib_env_vars_extra``. +``overcloud_dib_packages`` + List of DIB packages to install. Default is to install no extra packages. +``overcloud_dib_git_elements_default`` + List of default git repositories containing Diskimage Builder (DIB) + elements. See stackhpc.os-images role for usage. Default is empty. +``overcloud_dib_git_elements_extra`` + List of additional git repositories containing Diskimage Builder (DIB) + elements. See stackhpc.os-images role for usage. Default is empty. +``overcloud_dib_git_elements`` + List of git repositories containing Diskimage Builder (DIB) elements. See + stackhpc.os-images role for usage. Default is a combination of + ``overcloud_dib_git_elements_default`` and + ``overcloud_dib_git_elements_extra``. +``overcloud_dib_upper_constraints_file`` + Upper constraints file for installing packages in the virtual environment + used for building overcloud host disk images. Default is ``{{ + pip_upper_constraints_file }}``. + +Disk images are built with the following command: + +.. code-block:: console + + (kayobe) $ kayobe overcloud host image build + +It is worth noting that images will not be rebuilt if they already exist. To +force rebuilding images, it is necessary to use the ``--force-rebuild`` +argument. + +.. code-block:: console + + (kayobe) $ kayobe overcloud host image build --force-rebuild + +Example: Adding an element +-------------------------- + +In the following, we extend the list of DIB elements to add the ``growpart`` +element: + +.. code-block:: yaml + :caption: ``dib.yml`` + + overcloud_dib_elements_extra: + - "growpart" + +Example: Building an XFS root filesystem image +---------------------------------------------- + +By default, DIB will format the image as ``ext4``. In some cases it might be +useful to use XFS, for example when using the ``overlay`` Docker storage driver +which can reach the maximum number of hardlinks allowed by ``ext4``. + +In DIB, we achieve this by setting the ``FS_TYPE`` environment variable to +``xfs``. + +.. code-block:: yaml + :caption: ``dib.yml`` + + overcloud_dib_env_vars_extra: + FS_TYPE: "xfs" + +Example: Configuring a development user account +----------------------------------------------- + +.. warning:: + + A development user account should not be used in production. + +When debugging a failed deployment, it can sometimes be necessary to allow +access to the image via a preconfigured user account with a known password. +This can be achieved via the :diskimage-builder-doc:`devuser +` element. + +This example shows how to add the ``devuser`` element, and configure a username +and password for an account that has passwordless sudo: + +.. code-block:: yaml + :caption: ``dib.yml`` + + overcloud_dib_elements_extra: + - "devuser" + + overcloud_dib_env_vars_extra: + DIB_DEV_USER_USERNAME: "devuser" + DIB_DEV_USER_PASSWORD: "correct horse battery staple" + DIB_DEV_USER_PWDLESS_SUDO: "yes" + +Alternatively, the :diskimage-builder-doc:`dynamic-login element +` can be used to authorize SSH keys by appending +them to the kernel arguments. + +Example: Configuring custom DIB elements +---------------------------------------- + +Sometimes it is useful to use custom DIB elements that are not shipped with DIB +itself. This can be done by sharing them in a git repository. + +.. code-block:: yaml + :caption: ``overcloud-dib.yml`` + + overcloud_dib_elements_extra: + - "my-element" + + overcloud_dib_git_elements: + - repo: "https://git.example.com/custom-dib-elements" + local: "{{ source_checkout_path }}/custom-dib-elements" + version: "master" + elements_path: "elements" + +In this example the ``master`` branch of +https://git.example.com/custom-dib-elements would have a top level ``elements`` +directory, containing a ``my-element`` directory for the element. + +Example: Installing a package +----------------------------- + +It can be necessary to install additional packages in the root disk image. +Rather than needing to write a custom DIB element, we can use the +``overcloud_dib_packages`` variable. For example, to install the +``biosdevname`` package: + +.. code-block:: yaml + :caption: ``dib.yml`` + + overcloud_dib_packages: + - "biosdevname" + +Example: Building multiple images +--------------------------------- + +It can be necessary to build multiple images to support the various types of +hardware present in a deployment or the different functions performed by +overcloud hosts. This can be configured with the ``overcloud_dib_host_images`` +variable, using a format accepted by the `stackhpc.os-images +`__ role. Note that image names +should not include the file extension. For example, to build a second image +with a development user account and the ``biosdevname`` package: + +.. code-block:: yaml + :caption: ``dib.yml`` + + overcloud_dib_host_images: + - name: "deployment_image" + elements: "{{ overcloud_dib_elements }}" + env: "{{ overcloud_dib_env_vars }}" + packages: "{{ overcloud_dib_packages }}" + - name: "debug_deployment_image" + elements: "{{ overcloud_dib_elements + ['devuser'] }}" + env: "{{ overcloud_dib_env_vars | combine(devuser_env_vars) }}" + packages: "{{ overcloud_dib_packages + ['biosdevname'] }}" + + devuser_env_vars: + DIB_DEV_USER_USERNAME: "devuser" + DIB_DEV_USER_PASSWORD: "correct horse battery staple" + DIB_DEV_USER_PWDLESS_SUDO: "yes" + +Running the ``kayobe overcloud host image build`` command with this +configuration will create two images: ``deployment_image.qcow2`` and +``debug_deployment_image.qcow2``. + +Disk image deployment configuration +=================================== + +See :ref:`disk image deployment configuration in +Bifrost` for how to configure +the root disk image to be used to provision each host. diff --git a/doc/source/configuration/reference/physical-network.rst b/doc/source/configuration/reference/physical-network.rst index 38f9c9104..a65bbeb59 100644 --- a/doc/source/configuration/reference/physical-network.rst +++ b/doc/source/configuration/reference/physical-network.rst @@ -18,6 +18,7 @@ The following switch operating systems are currently supported: `__) * Dell OS 6 * Dell OS 9 +* Dell OS 10 * Dell PowerConnect * Juniper Junos OS * Mellanox MLNX OS @@ -192,13 +193,13 @@ default connection parameters used by Ansible: * ``ansible_user`` is the SSH username. -Dell OS6 and OS9 ----------------- +Dell OS6, OS9, and OS10 +----------------------- -Configuration for these devices is applied using the ``dellos6_config`` and -``dellos9_config`` Ansible modules. +Configuration for these devices is applied using the ``dellos6_config``, +``dellos9_config``, and ``dellos10_config`` Ansible modules. -``switch_type`` should be set to ``dellos6`` or ``dellos9``. +``switch_type`` should be set to ``dellos6``, ``dellos9``, or ``dellos10``. Provider ^^^^^^^^ diff --git a/doc/source/contributor/development.rst b/doc/source/contributor/development.rst index 48b3737fd..6aede7401 100644 --- a/doc/source/contributor/development.rst +++ b/doc/source/contributor/development.rst @@ -38,25 +38,27 @@ in `etc/kayobe/*.yml `__. A number of custom Jinja filters exist in `ansible/filter_plugins/*.py `__. -Kayobe depends on roles hosted on Ansible Galaxy, and these and their version -requirements are defined in `requirements.yml +Kayobe depends on roles and collections hosted on Ansible Galaxy, and these and +their version requirements are defined in `requirements.yml `__. Ansible Galaxy ============== -Kayobe uses a number of Ansible roles hosted on Ansible Galaxy. The role -dependencies are tracked in ``requirements.yml``, and specify required -versions. The process for changing a Galaxy role is as follows: - -#. If required, develop changes for the role. This may be done outside of - Kayobe, or by modifying the role in place during development. If upstream - changes to the role have already been made, this step can be skipped. -#. Commit changes to the role, typically via a Github pull request. -#. Request that a tagged release of the role be made, or make one if you have - the necessary privileges. -#. Ensure that automatic imports are configured for the role using e.g. a - TravisCI webhook notification, or perform a manual import of the role on - Ansible Galaxy. +Kayobe uses a number of Ansible roles and collections hosted on Ansible Galaxy. +The role dependencies are tracked in ``requirements.yml``, and specify required +versions. The process for changing a Galaxy role or collection is as follows: + +#. If required, develop changes for the role or collection. This may be done + outside of Kayobe, or by modifying the code in place during development. If + upstream changes to the code have already been made, this step can be + skipped. +#. Commit changes to the role or collection, typically via a Github pull + request. +#. Request that a tagged release of the role or collection be made, or make one + if you have the necessary privileges. +#. Ensure that automatic imports are configured for the repository using e.g. a + webhook notification, or perform a manual import of the role on Ansible + Galaxy. #. Modify the version in ``requirements.yml`` to match the new release of the - role. + role or collection. diff --git a/doc/source/custom-ansible-playbooks.rst b/doc/source/custom-ansible-playbooks.rst index 394904c1f..ef82452c6 100644 --- a/doc/source/custom-ansible-playbooks.rst +++ b/doc/source/custom-ansible-playbooks.rst @@ -75,14 +75,16 @@ These symlinks can even be committed to the kayobe-config Git repository. Ansible Galaxy -------------- -Ansible Galaxy provides a means for sharing Ansible roles. Kayobe -configuration may provide a Galaxy requirements file that defines roles to be -installed from Galaxy. These roles may then be used by custom playbooks. +Ansible Galaxy provides a means for sharing Ansible roles and collections. +Kayobe configuration may provide a Galaxy requirements file that defines roles +and collections to be installed from Galaxy. These roles and collections may +then be used by custom playbooks. -Galaxy role dependencies may be defined in -``$KAYOBE_CONFIG_PATH/ansible/requirements.yml``. These roles will be -installed in ``$KAYOBE_CONFIG_PATH/ansible/roles/`` when bootstrapping the -Ansible control host:: +Galaxy dependencies may be defined in +``$KAYOBE_CONFIG_PATH/ansible/requirements.yml``. These roles and collections +will be installed in ``$KAYOBE_CONFIG_PATH/ansible/roles/`` and +``$KAYOBE_CONFIG_PATH/ansible/collections`` when bootstrapping the Ansible +control host:: (kayobe) $ kayobe control host bootstrap @@ -90,8 +92,8 @@ And updated when upgrading the Ansible control host:: (kayobe) $ kayobe control host upgrade -Example -======= +Example: roles +============== The following example adds a ``foo.yml`` playbook to a set of kayobe configuration. The playbook uses a Galaxy role, ``bar.baz``. @@ -116,7 +118,8 @@ Here is the playbook, ``ansible/foo.yml``:: Here is the Galaxy requirements file, ``ansible/requirements.yml``:: --- - - bar.baz + roles: + - bar.baz We should first install the Galaxy role dependencies, to download the ``bar.baz`` role:: @@ -127,6 +130,47 @@ Then, to run the ``foo.yml`` playbook:: (kayobe) $ kayobe playbook run $KAYOBE_CONFIG_PATH/ansible/foo.yml +Example: collections +==================== + +The following example adds a ``foo.yml`` playbook to a set of kayobe +configuration. The playbook uses a role from a Galaxy collection, +``bar.baz.qux``. + +Here is the kayobe configuration repository structure:: + + etc/kayobe/ + ansible/ + collections/ + foo.yml + requirements.yml + bifrost.yml + ... + +Here is the playbook, ``ansible/foo.yml``:: + + --- + - hosts: controllers + roles: + - name: bar.baz.qux + +Here is the Galaxy requirements file, ``ansible/requirements.yml``:: + + --- + collections: + - bar.baz + +We should first install the Galaxy dependencies, to download the ``bar.baz`` +collection:: + + (kayobe) $ kayobe control host bootstrap + +Then, to run the ``foo.yml`` playbook:: + + (kayobe) $ kayobe playbook run $KAYOBE_CONFIG_PATH/ansible/foo.yml + +.. _custom-playbooks-hooks: + Hooks ===== @@ -170,6 +214,24 @@ you could do the following:: The sequence number for the ``foo.yml`` playbook is ``10``. +Hook execution can be disabled with ``--skip-hooks``. ``--skip-hooks all`` will halt hook execution altogether. +``--skip-hooks `` will skip playbooks matching the ````. + +For example, if the following playbooks exist: + +- ``$KAYOBE_CONFIG_PATH/hooks/control-host-bootstrap/pre.d/example1.yml`` +- ``$KAYOBE_CONFIG_PATH/hooks/control-host-bootstrap/pre.d/example2.yml`` +- ``$KAYOBE_CONFIG_PATH/hooks/control-host-bootstrap/post.d/example1.yml`` + +And the following command is used:: + + (kayobe) $ kayobe control host bootstrap --skip-hooks example1 + +Only ``$KAYOBE_CONFIG_PATH/hooks/control-host-bootstrap/pre.d/example2.yml`` will be executed. + +This example assumes that the term ``example1`` does not appear in +``$KAYOBE_CONFIG_PATH``. If it did, all hooks would be skipped. + Failure handling ---------------- diff --git a/doc/source/deployment.rst b/doc/source/deployment.rst index b83324166..38b845495 100644 --- a/doc/source/deployment.rst +++ b/doc/source/deployment.rst @@ -75,6 +75,8 @@ Seed Hypervisor bare metal host or a VM provisioned outside of Kayobe, this section may be skipped. +.. _deployment-seed-hypervisor-host-configure: + Host Configuration ------------------ @@ -110,6 +112,8 @@ volumes. To provision the seed VM:: When this command has completed the seed VM should be active and accessible via SSH. Kayobe will update the Ansible inventory with the IP address of the VM. +.. _deployment-seed-host-configure: + Host Configuration ------------------ @@ -168,7 +172,8 @@ At this point the seed services need to be deployed on the seed VM. These services are deployed in the ``bifrost_deploy`` container. This command will also build the Operating System image that will be used to -deploy the overcloud nodes using Disk Image Builder (DIB). +deploy the overcloud nodes using Disk Image Builder (DIB), unless +``overcloud_dib_build_host_images`` is set to ``True``. To deploy the seed services in containers:: @@ -184,7 +189,7 @@ After this command has completed the seed services will be active. :ref:`configuration-bifrost-overcloud-root-image` provides information on configuring the root disk image build process. See :ref:`here ` for information about deploying - additional, custom containers on seed node. + additional, custom services (containers) on a seed node. Building Deployment Images -------------------------- @@ -213,6 +218,27 @@ rebuilding images, use the ``--force-rebuild`` argument. See :ref:`here ` for information on how to configure the IPA image build process. +Building Overcloud Host Disk Images +----------------------------------- + +.. note:: + + This step is only relevant if ``overcloud_dib_build_host_images`` is set to + ``True``. By default, a host disk image is automatically built by Bifrost. + +Host disk images are deployed on overcloud hosts during provisioning. To build +host disk images:: + + (kayobe) $ kayobe overcloud host image build + +If images have been built previously, they will not be rebuilt. To force +rebuilding images, use the ``--force-rebuild`` argument. + +.. seealso:: + + See :ref:`here ` for information on how to configure the + overcloud host disk image build process. + Accessing the Seed via SSH (Optional) ------------------------------------- @@ -236,6 +262,112 @@ Leave the seed VM and return to the shell on the Ansible control host:: $ exit +.. _deployment-infrastructure-vms: + +Infrastructure VMs +=================== + +.. warning:: + + Support for infrastructure VMs is considered experimental: its + design may change in future versions without a deprecation period. + +.. note:: + + It necessary to perform some configuration before these steps + can be followed. Please see :ref:`configuration-infra-vms`. + +VM Provisioning +--------------- + +The hypervisor used to host a VM is controlled via the ``infra_vm_hypervisor`` +variable. It defaults to use the seed hypervisor. All hypervisors should have +CentOS or Ubuntu with ``libvirt`` installed. It should have ``libvirt`` networks +configured for all networks that the VM needs access to and a ``libvirt`` +storage pool available for the VM's volumes. The steps needed for for the +:ref:`seed` and the +:ref:`seed hypervisor` can be found +above. + +To provision the infra VMs:: + + (kayobe) $ kayobe infra vm provision + +When this command has completed the infra VMs should be active and accessible +via SSH. Kayobe will update the Ansible inventory with the IP address of the +VM. + +Host Configuration +------------------ + +To configure the infra VM host OS:: + + (kayobe) $ kayobe infra vm host configure + +.. note:: + + If the infra VM host uses disks that have been in use in a previous + installation, it may be necessary to wipe partition and LVM data from those + disks. To wipe all disks that are not mounted during host configuration:: + + (kayobe) $ kayobe infra vm host configure --wipe-disks + +.. seealso:: + + Information on configuration of hosts is available :ref:`here + `. + +Using Hooks to deploy services on the VMs +----------------------------------------- + +A no-op service deployment command is provided to perform additional +configuration. The intention is for users to define :ref:`hooks to custom +playbooks ` that define any further configuration or +service deployment necessary. + +To trigger the hooks:: + + (kayobe) $ kayobe infra vm service deploy + +Example +^^^^^^^ + +In this example we have an infra VM host called ``dns01`` that provides DNS +services. The host could be added to a ``dns-servers`` group in the inventory: + +.. code-block:: ini + :caption: ``$KAYOBE_CONFIG_PATH/inventory/infra-vms`` + + [dns-servers] + an-example-vm + + [infra-vms:children] + dns-servers + +We have a custom playbook targeting the ``dns-servers`` group that sets up +the DNS server: + +.. code-block:: yaml + :caption: ``$KAYOBE_CONFIG_PATH/ansible/dns-server.yml`` + + --- + - name: Deploy DNS servers + hosts: dns-servers + tasks: + - name: Install bind packages + package: + name: + - bind + - bind-utils + become: true + +Finally, we add a symlink to set up the playbook as a hook for the ``kayobe +infra vm service deploy`` command:: + + (kayobe) $ mkdir -p ${KAYOBE_CONFIG_PATH}/hooks/infra-vm-host-configure/post.d + (kayobe) $ cd ${KAYOBE_CONFIG_PATH}/hooks/infra-vm-host-configure/post.d + (kayobe) $ ln -s ../../../ansible/dns-server.yml 50-dns-serveryml + Overcloud ========= diff --git a/doc/source/multiple-environments.rst b/doc/source/multiple-environments.rst index 1efc037f8..ecf89dab9 100644 --- a/doc/source/multiple-environments.rst +++ b/doc/source/multiple-environments.rst @@ -70,7 +70,7 @@ Ansible Inventories Each environment can include its own inventory, which overrides any variable declaration done in the shared inventory. Typically, a shared inventory may be used to define groups and group variables, while hosts and host variables would -be set in enviroment inventories. The following layout (ignoring non-inventory +be set in environment inventories. The following layout (ignoring non-inventory files) shows an example of multiple inventories. .. code-block:: text @@ -152,10 +152,31 @@ like the ``network-allocation.yml`` file. Kolla Configuration ------------------- -Kolla configuration is currently independent in each environment. To avoid -duplicating configuration, symbolic links can be used to share common variable -definitions. It is advised to avoid sharing credentials between environments by -making each Kolla ``passwords.yml`` file unique. +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: + +* ``kolla/config/bifrost/bifrost.yml`` +* ``kolla/config/bifrost/dib.yml`` +* ``kolla/config/bifrost/servers.yml`` +* ``kolla/globals.yml`` +* ``kolla/kolla-build.conf`` + +Options in the environment-specific files take precedence over those in the +shared files. + +Managing Independent Environment Files +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +For files that are independent in each environment, i.e. they do not support +combining the environment-specific and shared configuration file content, there +are some techniques that may be used to avoid duplication. + +For example, symbolic links can be used to share common variable definitions. +It is advised to avoid sharing credentials between environments by making each +Kolla ``passwords.yml`` file unique. Custom Ansible Playbooks and Hooks ---------------------------------- diff --git a/doc/source/usage.rst b/doc/source/usage.rst index 047480a95..5c64c17c5 100644 --- a/doc/source/usage.rst +++ b/doc/source/usage.rst @@ -69,3 +69,13 @@ playbooks to be limited to matching plays and tasks. The ``--kolla-tags limited to matching plays and tasks. The ``--skip-tags `` and ``--kolla-skip-tags `` arguments allow for avoiding execution of matching plays and tasks. + +Check and diff mode +------------------- + +Ansible supports `check and diff modes +`_, +which can be used to improve visibility into changes that would be made on +target systems. The Kayobe CLI supports the ``--check`` argument, and since +11.0.0, the ``--diff`` argument. Note that these modes are not always +guaranteed to work, when some tasks are dependent on earlier ones. diff --git a/etc/kayobe/apt.yml b/etc/kayobe/apt.yml index 5f278e322..34bfdd2ef 100644 --- a/etc/kayobe/apt.yml +++ b/etc/kayobe/apt.yml @@ -11,6 +11,41 @@ # Apt proxy URL for HTTPS. Default is {{ apt_proxy_http }}. #apt_proxy_https: +# List of Apt configuration options. Each item is a dict with the following +# keys: +# * content: free-form configuration file content +# * filename: name of a file in /etc/apt/apt.conf.d/ in which to write the +# configuration +# Default is an empty list. +#apt_config: + +# List of apt keys. Each item is a dict containing the following keys: +# * url: URL of key +# * filename: Name of a file in which to store the downloaded key. The +# extension should be '.asc' for ASCII-armoured keys, or '.gpg' otherwise. +# Default is an empty list. +#apt_keys: + +# A list of Apt repositories. Each item is a dict with the following keys: +# * types: whitespace-separated list of repository types, e.g. deb or deb-src +# (optional, default is 'deb') +# * url: URL of the repository +# * suites: whitespace-separated list of suites, e.g. focal (optional, default +# is ansible_facts.distribution_release) +# * components: whitespace-separated list of components, e.g. main (optional, +# default is 'main') +# * signed_by: whitespace-separated list of names of GPG keyring files in +# apt_keys_path (optional, default is unset) +# * architecture: whitespace-separated list of architectures that will be used +# (optional, default is unset) +# Default is an empty list. +#apt_repositories: + +# Whether to disable repositories in /etc/apt/sources.list. This may be used +# when replacing the distribution repositories via apt_repositories. +# Default is false. +#apt_disable_sources_list: + ############################################################################### # Dummy variable to allow Ansible to accept this file. workaround_ansible_issue_8743: yes diff --git a/etc/kayobe/bifrost.yml b/etc/kayobe/bifrost.yml index 192195205..7ce001ddc 100644 --- a/etc/kayobe/bifrost.yml +++ b/etc/kayobe/bifrost.yml @@ -57,6 +57,18 @@ # List of DIB packages to install. Default is to install no extra packages. #kolla_bifrost_dib_packages: +############################################################################### +# Disk image deployment configuration. + +# Name of disk image file to deploy. Default is "deployment_image.qcow2". +#kolla_bifrost_deploy_image_filename: + +# UUID of the root filesystem contained within the deployment image. +# See below URL for instructions on how to extract it: +# https://docs.openstack.org/ironic/latest/admin/raid.html#image-requirements +# Default is none. +#kolla_bifrost_deploy_image_rootfs: + ############################################################################### # Ironic configuration. diff --git a/etc/kayobe/compute.yml b/etc/kayobe/compute.yml index 130aafa30..4f1150b20 100644 --- a/etc/kayobe/compute.yml +++ b/etc/kayobe/compute.yml @@ -115,6 +115,74 @@ # singleplatform-eng.users role. #compute_users: +############################################################################### +# Compute node firewalld configuration. + +# Whether to install and enable firewalld. +#compute_firewalld_enabled: + +# A list of zones to create. Each item is a dict containing a 'zone' item. +#compute_firewalld_zones: + +# A firewalld zone to set as the default. Default is unset, in which case the +# default zone will not be changed. +#compute_firewalld_default_zone: + +# A list of firewall rules to apply. Each item is a dict containing arguments +# to pass to the firewalld module. Arguments are omitted if not provided, with +# the following exceptions: +# - offline: true +# - permanent: true +# - state: enabled +#compute_firewalld_rules: + +############################################################################### +# Compute node host libvirt configuration. + +# Whether to enable a host libvirt daemon. Default is true if kolla_enable_nova +# is true and kolla_enable_nova_libvirt_container is false. +#compute_libvirt_enabled: + +# A dict of default configuration options to write to +# /etc/libvirt/libvirtd.conf. +#compute_libvirt_conf_default: + +# A dict of additional configuration options to write to +# /etc/libvirt/libvirtd.conf. +#compute_libvirt_conf_extra: + +# A dict of configuration options to write to /etc/libvirt/libvirtd.conf. +# Default is a combination of compute_libvirt_conf_default and +# compute_libvirt_conf_extra. +#compute_libvirt_conf: + +# Numerical log level for libvirtd. Default is 3. +#compute_libvirtd_log_level: + +# A dict of default configuration options to write to +# /etc/libvirt/qemu.conf. +#compute_qemu_conf_default: + +# A dict of additional configuration options to write to +# /etc/libvirt/qemu.conf. +#compute_qemu_conf_extra: + +# A dict of configuration options to write to /etc/libvirt/qemu.conf. +# Default is a combination of compute_qemu_conf_default and +# compute_qemu_conf_extra. +#compute_qemu_conf: + +# Whether to enable a libvirt TLS listener. Default is false. +#compute_libvirt_enable_tls: + +# Whether to install a Ceph package repository on CentOS and Rocky hosts. +# Default is true. +#compute_libvirt_ceph_repo_install: + +# Ceph package repository release to install on CentOS and Rocky hosts when +# compute_libvirt_ceph_repo_install is true. Default is 'pacific'. +#compute_libvirt_ceph_repo_release: + ############################################################################### # Dummy variable to allow Ansible to accept this file. workaround_ansible_issue_8743: yes diff --git a/etc/kayobe/controllers.yml b/etc/kayobe/controllers.yml index b9077cd34..735a3ded5 100644 --- a/etc/kayobe/controllers.yml +++ b/etc/kayobe/controllers.yml @@ -124,6 +124,27 @@ # singleplatform-eng.users role. #controller_users: +############################################################################### +# Controller node firewalld configuration. + +# Whether to install and enable firewalld. +#controller_firewalld_enabled: + +# A list of zones to create. Each item is a dict containing a 'zone' item. +#controller_firewalld_zones: + +# A firewalld zone to set as the default. Default is unset, in which case the +# default zone will not be changed. +#controller_firewalld_default_zone: + +# A list of firewall rules to apply. Each item is a dict containing arguments +# to pass to the firewalld module. Arguments are omitted if not provided, with +# the following exceptions: +# - offline: true +# - permanent: true +# - state: enabled +#controller_firewalld_rules: + ############################################################################### # Dummy variable to allow Ansible to accept this file. workaround_ansible_issue_8743: yes diff --git a/etc/kayobe/dnf.yml b/etc/kayobe/dnf.yml index cf09b86fd..ab28060e7 100644 --- a/etc/kayobe/dnf.yml +++ b/etc/kayobe/dnf.yml @@ -12,12 +12,18 @@ # Whether or not to use a local Yum mirror. Default value is 'false'. #dnf_use_local_mirror: -# Mirror FQDN for Yum repos. Default value is 'mirror.centos.org'. +# Mirror FQDN for Yum CentOS repos. Default value is 'mirror.centos.org'. #dnf_centos_mirror_host: # Mirror directory for Yum CentOS repos. Default value is 'centos'. #dnf_centos_mirror_directory: +# Mirror FQDN for Yum Rocky repos. Default value is 'dl.rockylinux.org'. +#dnf_rocky_mirror_host: + +# Mirror directory for Yum Rocky repos. Default value is 'pub/rocky'. +#dnf_rocky_mirror_directory: + # Mirror FQDN for Yum EPEL repos. Default value is # 'download.fedoraproject.org'. #dnf_epel_mirror_host: diff --git a/etc/kayobe/globals.yml b/etc/kayobe/globals.yml index a4150d8ec..b926fc9be 100644 --- a/etc/kayobe/globals.yml +++ b/etc/kayobe/globals.yml @@ -45,12 +45,13 @@ ############################################################################### # OS distribution. -# OS distribution name. Valid options are "centos", "ubuntu". Default is -# "centos". +# OS distribution name. Valid options are "centos", "rocky", "ubuntu". Default +# is "centos". #os_distribution: # OS release. Valid options are "8-stream" when os_distribution is "centos", or -# "focal" when os_distribution is "ubuntu". +# "8" when os_distribution is "rocky", or "focal" when os_distribution is +# "ubuntu". #os_release: ############################################################################### diff --git a/etc/kayobe/infra-vms.yml b/etc/kayobe/infra-vms.yml new file mode 100644 index 000000000..0155a538c --- /dev/null +++ b/etc/kayobe/infra-vms.yml @@ -0,0 +1,170 @@ +--- +############################################################################### +# Infrastructure VM configuration. + +# Name of the infra VM. +#infra_vm_name: + +# Memory in MB. +#infra_vm_memory_mb: + +# Number of vCPUs. +#infra_vm_vcpus: + +# List of volumes. +#infra_vm_volumes: + +# Root volume. +#infra_vm_root_volume: + +# Data volume. +#infra_vm_data_volume: + +# Name of the storage pool for the infra VM volumes. +#infra_vm_pool: + +# Capacity of the infra VM root volume. +#infra_vm_root_capacity: + +# Format of the infra VM root volume. +#infra_vm_root_format: + +# Base image for the infra VM root volume. Default is +# "https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img" +# when os_distribution is "ubuntu", or +# http://dl.rockylinux.org/pub/rocky/8.5/images/Rocky-8-GenericCloud-8.5-20211114.2.x86_64.qcow2 +# when os_distribution is "rocky", +# or +# "https://cloud.centos.org/centos/8-stream/x86_64/images/CentOS-Stream-GenericCloud-8-20210603.0.x86_64.qcow2" +# otherwise. +#infra_vm_root_image: + +# Capacity of the infra VM data volume. +#infra_vm_data_capacity: + +# Format of the infra VM data volume. +#infra_vm_data_format: + +# List of network interfaces to attach to the infra VM. +#infra_vm_interfaces: + +# Hypervisor that the VM runs on. +#infra_vm_hypervisor: + +# Customise ansible_ssh_extra_args for the test that checks SSH connectivity +# after provisioning. Defaults to disabling ssh host key checking. +#infra_vm_wait_connection_ssh_extra_args: + +# OS family. Needed for config drive generation. +# infra_vm_os_family: + +############################################################################### +# Infrastructure VM node configuration. + +# User with which to access the infrastructure vm via SSH during bootstrap, in +# order to setup the Kayobe user account. +#infra_vm_bootstrap_user: + +############################################################################### +# Infrastructure VM network interface configuration. + +# List of networks to which infrastructure vm nodes are attached. +#infra_vm_network_interfaces: + +# List of default networks to which infrastructure vm nodes are attached. +#infra_vm_default_network_interfaces: + +# List of extra networks to which infrastructure vm nodes are attached. +#infra_vm_extra_network_interfaces: + +############################################################################### +# Infrastructure VM node software RAID configuration. + +# List of software RAID arrays. See mrlesmithjr.mdadm role for format. +#infra_vm_mdadm_arrays: + +############################################################################### +# Infrastructure VM node encryption configuration. + +# List of block devices to encrypt. See stackhpc.luks role for format. +#infra_vm_luks_devices: + +############################################################################### +# Infrastructure VM node LVM configuration. + +# List of infrastructure vm volume groups. See mrlesmithjr.manage-lvm role for +# format. +#infra_vm_lvm_groups: + +# Default list of infrastructure vm volume groups. See mrlesmithjr.manage-lvm +# role for format. +#infra_vm_lvm_groups_default: + +# Additional list of infrastructure vm volume groups. See mrlesmithjr.manage-lvm +# role for format. +#infra_vm_lvm_groups_extra: + +# Whether a 'data' LVM volume group should exist on the infrastructure vm. By +# default this contains a 'docker-volumes' logical volume for Docker volume +# storage. It will also be used for Docker container and image storage if +# 'docker_storage_driver' is set to 'devicemapper'. Default is true if +# 'docker_storage_driver' is set to 'devicemapper', or false otherwise. +#infra_vm_lvm_group_data_enabled: + +# Infrastructure VM LVM volume group for data. See mrlesmithjr.manage-lvm role +# for format. +#infra_vm_lvm_group_data: + +# List of disks for use by infrastructure vm LVM data volume group. Default to +# an invalid value to require configuration. +#infra_vm_lvm_group_data_disks: + +# List of LVM logical volumes for the data volume group. +#infra_vm_lvm_group_data_lvs: + +# Docker volumes LVM backing volume. +#infra_vm_lvm_group_data_lv_docker_volumes: + +# Size of docker volumes LVM backing volume. +#infra_vm_lvm_group_data_lv_docker_volumes_size: + +# Filesystem for docker volumes LVM backing volume. ext4 allows for shrinking. +#infra_vm_lvm_group_data_lv_docker_volumes_fs: + +############################################################################### +# Infrastructure VM node sysctl configuration. + +# Dict of sysctl parameters to set. +#infra_vm_sysctl_parameters: + +############################################################################### +# Infrastructure VM node user configuration. + +# List of users to create. This should be in a format accepted by the +# singleplatform-eng.users role. +#infra_vm_users: + +############################################################################### +# Infrastructure VM node firewalld configuration. + +# Whether to install and enable firewalld. +#infra_vm_firewalld_enabled: + +# A list of zones to create. Each item is a dict containing a 'zone' item. +#infra_vm_firewalld_zones: + +# A firewalld zone to set as the default. Default is unset, in which case the +# default zone will not be changed. +#infra_vm_firewalld_default_zone: + +# A list of firewall rules to apply. Each item is a dict containing arguments +# to pass to the firewalld module. Arguments are omitted if not provided, with +# the following exceptions: +# - offline: true +# - permanent: true +# - state: enabled +#infra_vm_firewalld_rules: + +############################################################################### +# Dummy variable to allow Ansible to accept this file. +workaround_ansible_issue_8743: yes diff --git a/etc/kayobe/inventory/groups b/etc/kayobe/inventory/groups index fa1ced47c..16619fed8 100644 --- a/etc/kayobe/inventory/groups +++ b/etc/kayobe/inventory/groups @@ -14,6 +14,16 @@ # Build container images on the seed by default. seed +############################################################################### +# Infra VM groups. + +[hypervisors:children] +# Group that contains all hypervisors used for infra VMs +seed-hypervisor + +[infra-vms] +# Empty group to provide declaration of infra-vms group. + ############################################################################### # Overcloud groups. diff --git a/etc/kayobe/kolla.yml b/etc/kayobe/kolla.yml index 0843df106..8a228e7fc 100644 --- a/etc/kayobe/kolla.yml +++ b/etc/kayobe/kolla.yml @@ -50,6 +50,11 @@ # Extra requirements to install inside the kolla-ansible virtualenv. #kolla_ansible_venv_extra_requirements: +# Pip requirement specifier for the ansible package. NOTE: This limits the +# version of ansible used by kolla-ansible to avoid new releases from breaking +# tested code. Changes to this limit should be tested. +#kolla_ansible_venv_ansible: + # Path to Kolla-ansible configuration directory. Default is $KOLLA_CONFIG_PATH # or /etc/kolla if $KOLLA_CONFIG_PATH is not set. #kolla_config_path: @@ -62,7 +67,8 @@ # Kolla configuration. # Kolla base container image distribution. Options are "centos", "debian", -# "ubuntu". Default is {{ os_distribution }}. +# "ubuntu". Default is +# {{ 'centos' if os_distribution == 'rocky' else os_distribution }}. #kolla_base_distro: # Kolla container image type: binary or source. Default is 'binary'. @@ -379,6 +385,7 @@ #kolla_enable_prometheus_ceph_mgr_exporter: #kolla_enable_prometheus_elasticsearch_exporter: #kolla_enable_prometheus_haproxy_exporter: +#kolla_enable_prometheus_libvirt_exporter: #kolla_enable_prometheus_memcached_exporter: #kolla_enable_prometheus_mysqld_exporter: #kolla_enable_prometheus_node_exporter: @@ -473,6 +480,21 @@ # default. #kolla_internal_fqdn_cacert: +############################################################################### +# Proxy configuration + +# HTTP proxy URL (format: http(s)://[user:password@]proxy_name:port) used by +# Kolla. Default value is "{{ http_proxy }}". +#kolla_http_proxy: + +# HTTPS proxy URL (format: http(s)://[user:password@]proxy_name:port) used by +# Kolla. Default value is "{{ https_proxy }}". +#kolla_https_proxy: + +# List of domains, hostnames, IP addresses and networks for which no proxy is +# used. Default value is "{{ no_proxy }}". +#kolla_no_proxy: + ############################################################################### # Dummy variable to allow Ansible to accept this file. workaround_ansible_issue_8743: yes diff --git a/etc/kayobe/monitoring.yml b/etc/kayobe/monitoring.yml index 60bd80f79..b4ff35a39 100644 --- a/etc/kayobe/monitoring.yml +++ b/etc/kayobe/monitoring.yml @@ -88,6 +88,27 @@ # singleplatform-eng.users role. #monitoring_users: +############################################################################### +# Monitoring node firewalld configuration. + +# Whether to install and enable firewalld. +#monitoring_firewalld_enabled: + +# A list of zones to create. Each item is a dict containing a 'zone' item. +#monitoring_firewalld_zones: + +# A firewalld zone to set as the default. Default is unset, in which case the +# default zone will not be changed. +#monitoring_firewalld_default_zone: + +# A list of firewall rules to apply. Each item is a dict containing arguments +# to pass to the firewalld module. Arguments are omitted if not provided, with +# the following exceptions: +# - offline: true +# - permanent: true +# - state: enabled +#monitoring_firewalld_rules: + ############################################################################### # Dummy variable to allow Ansible to accept this file. workaround_ansible_issue_8743: yes diff --git a/etc/kayobe/overcloud-dib.yml b/etc/kayobe/overcloud-dib.yml new file mode 100644 index 000000000..3d69f8eb1 --- /dev/null +++ b/etc/kayobe/overcloud-dib.yml @@ -0,0 +1,81 @@ +--- +# Overcloud host disk image configuration. + +############################################################################### +# Diskimage-builder configuration for overcloud host disk images. + +# Whether to build host disk images with DIB directly instead of through +# Bifrost. Setting it to true disables Bifrost image build and allows images to +# be built with the `kayobe overcloud host image build` command. Default value +# is {{ os_distribution == 'rocky' }}. This will change in a future release. +#overcloud_dib_build_host_images: + +# List of overcloud host disk images to build. Each element is a dict defining +# an image in a format accepted by the stackhpc.os-images role. Default is to +# build an image named "deployment_image" configured with the overcloud_dib_* +# variables defined below: {"name": "deployment_image", "elements": "{{ +# overcloud_dib_elements }}", "env": "{{ overcloud_dib_env_vars }}", +# "packages": "{{ overcloud_dib_packages }}"}. +#overcloud_dib_host_images: + +# DIB base OS element. Default is {{ 'rocky-container' if os_distribution == +# 'rocky' else os_distribution }}. +#overcloud_dib_os_element: + +# DIB image OS release. Default is {{ os_release }}. +#overcloud_dib_os_release: + +# List of default DIB elements. Default is ["centos", "cloud-init-datasources", +# "disable-selinux", "enable-serial-console", "vm"] when +# overcloud_dib_os_element is "centos", or ["rocky-container", +# "cloud-init-datasources", "disable-selinux", "enable-serial-console", "vm"] +# when overcloud_dib_os_element is "rocky" or +# ["ubuntu", "cloud-init-datasources", "enable-serial-console", "vm"] +# when overcloud_dib_os_element is "ubuntu". +#overcloud_dib_elements_default: + +# List of additional DIB elements. Default is none. +#overcloud_dib_elements_extra: + +# List of DIB elements. Default is a combination of +# overcloud_dib_elements_default and overcloud_dib_elements_extra. +#overcloud_dib_elements: + +# DIB default environment variables. Default is +# {"DIB_BOOTLOADER_DEFAULT_CMDLINE": "nofb nomodeset gfxpayload=text +# net.ifnames=1", "DIB_CLOUD_INIT_DATASOURCES": "ConfigDrive", +# "DIB_CONTAINERFILE_RUNTIME": "docker", "DIB_CONTAINERFILE_NETWORK_DRIVER": +# "host", "DIB_RELEASE": "{{ overcloud_dib_os_release }}"}. +#overcloud_dib_env_vars_default: + +# DIB additional environment variables. Default is none. +#overcloud_dib_env_vars_extra: + +# DIB environment variables. Default is combination of +# overcloud_dib_env_vars_default and overcloud_dib_env_vars_extra. +#overcloud_dib_env_vars: + +# List of DIB packages to install. Default is to install no extra packages. +#overcloud_dib_packages: + +# List of default git repositories containing Diskimage Builder (DIB) elements. +# See stackhpc.os-images role for usage. Default is empty. +#overcloud_dib_git_elements_default: + +# List of additional git repositories containing Diskimage Builder (DIB) +# elements. See stackhpc.os-images role for usage. Default is empty. +#overcloud_dib_git_elements_extra: + +# List of git repositories containing Diskimage Builder (DIB) elements. See +# stackhpc.os-images role for usage. Default is a combination of +# overcloud_dib_git_elements_default and overcloud_dib_git_elements_extra. +#overcloud_dib_git_elements: + +# Upper constraints file for installing packages in the virtual environment +# used for building overcloud host disk images. Default is {{ +# pip_upper_constraints_file }}. +#overcloud_dib_upper_constraints_file: + +############################################################################### +# Dummy variable to allow Ansible to accept this file. +workaround_ansible_issue_8743: yes diff --git a/etc/kayobe/proxy.yml b/etc/kayobe/proxy.yml new file mode 100644 index 000000000..714b9dae5 --- /dev/null +++ b/etc/kayobe/proxy.yml @@ -0,0 +1,22 @@ +--- +############################################################################### +# Configuration of HTTP(S) proxies. + +# HTTP proxy URL (format: http(s)://[user:password@]proxy_name:port). By +# default no proxy is used. +#http_proxy: + +# HTTPS proxy URL (format: http(s)://[user:password@]proxy_name:port). By +# default no proxy is used. +#https_proxy: + +# List of domains, hostnames, IP addresses and networks for which no proxy is +# used. Defaults to ["127.0.0.1", "localhost", "{{ ('http://' ~ +# docker_registry) | urlsplit('hostname') }}"] if docker_registry is set, or +# ["127.0.0.1", "localhost"] otherwise. This is configured only if either +# http_proxy or https_proxy is set. +#no_proxy: + +############################################################################### +# Dummy variable to allow Ansible to accept this file. +workaround_ansible_issue_8743: yes diff --git a/etc/kayobe/seed-hypervisor.yml b/etc/kayobe/seed-hypervisor.yml index d0a893aeb..d460f585f 100644 --- a/etc/kayobe/seed-hypervisor.yml +++ b/etc/kayobe/seed-hypervisor.yml @@ -104,6 +104,27 @@ # singleplatform-eng.users role. #seed_hypervisor_users: +############################################################################### +# Seed hypervisor node firewalld configuration. + +# Whether to install and enable firewalld. +#seed_hypervisor_firewalld_enabled: + +# A list of zones to create. Each item is a dict containing a 'zone' item. +#seed_hypervisor_firewalld_zones: + +# A firewalld zone to set as the default. Default is unset, in which case the +# default zone will not be changed. +#seed_hypervisor_firewalld_default_zone: + +# A list of firewall rules to apply. Each item is a dict containing arguments +# to pass to the firewalld module. Arguments are omitted if not provided, with +# the following exceptions: +# - offline: true +# - permanent: true +# - state: enabled +#seed_hypervisor_firewalld_rules: + ############################################################################### # Dummy variable to allow Ansible to accept this file. workaround_ansible_issue_8743: yes diff --git a/etc/kayobe/seed-vm.yml b/etc/kayobe/seed-vm.yml index 55fea4a7e..4ba96faf1 100644 --- a/etc/kayobe/seed-vm.yml +++ b/etc/kayobe/seed-vm.yml @@ -25,7 +25,10 @@ # Base image for the seed VM root volume. Default is # "https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img" -# when os_distribution is "ubuntu", or +# when os_distribution is "ubuntu", +# http://dl.rockylinux.org/pub/rocky/8.5/images/Rocky-8-GenericCloud-8.5-20211114.2.x86_64.qcow2 +# when os_distribution is "rocky", +# or # "https://cloud.centos.org/centos/8-stream/x86_64/images/CentOS-Stream-GenericCloud-8-20220913.0.x86_64.qcow2" # otherwise. #seed_vm_root_image: diff --git a/etc/kayobe/seed.yml b/etc/kayobe/seed.yml index cdfe3a9f3..f99a5b388 100644 --- a/etc/kayobe/seed.yml +++ b/etc/kayobe/seed.yml @@ -96,6 +96,27 @@ # #seed_containers: +############################################################################### +# Seed node firewalld configuration. + +# Whether to install and enable firewalld. +#seed_firewalld_enabled: + +# A list of zones to create. Each item is a dict containing a 'zone' item. +#seed_firewalld_zones: + +# A firewalld zone to set as the default. Default is unset, in which case the +# default zone will not be changed. +#seed_firewalld_default_zone: + +# A list of firewall rules to apply. Each item is a dict containing arguments +# to pass to the firewalld module. Arguments are omitted if not provided, with +# the following exceptions: +# - offline: true +# - permanent: true +# - state: enabled +#seed_firewalld_rules: + ############################################################################### # Dummy variable to allow Ansible to accept this file. workaround_ansible_issue_8743: yes diff --git a/etc/kayobe/storage.yml b/etc/kayobe/storage.yml index 0d2215d82..ff09323aa 100644 --- a/etc/kayobe/storage.yml +++ b/etc/kayobe/storage.yml @@ -120,6 +120,27 @@ # singleplatform-eng.users role. #storage_users: +############################################################################### +# Storage node firewalld configuration. + +# Whether to install and enable firewalld. +#storage_firewalld_enabled: + +# A list of zones to create. Each item is a dict containing a 'zone' item. +#storage_firewalld_zones: + +# A firewalld zone to set as the default. Default is unset, in which case the +# default zone will not be changed. +#storage_firewalld_default_zone: + +# A list of firewall rules to apply. Each item is a dict containing arguments +# to pass to the firewalld module. Arguments are omitted if not provided, with +# the following exceptions: +# - offline: true +# - permanent: true +# - state: enabled +#storage_firewalld_rules: + ############################################################################### # Dummy variable to allow Ansible to accept this file. workaround_ansible_issue_8743: yes diff --git a/kayobe/ansible.py b/kayobe/ansible.py index 6dbc1eb86..af5396d9a 100644 --- a/kayobe/ansible.py +++ b/kayobe/ansible.py @@ -48,6 +48,10 @@ def add_args(parser): help="path to Kayobe configuration. " "(default=$%s or %s)" % (CONFIG_PATH_ENV, DEFAULT_CONFIG_PATH)) + parser.add_argument("-D", "--diff", action="store_true", + help="when changing (small) files and templates, show " + "the differences in those files; works great " + "with --check") parser.add_argument("--environment", default=default_environment, help="specify environment name (default=$%s or None)" % ENVIRONMENT_ENV) @@ -73,6 +77,9 @@ def add_args(parser): action="store_true", help="only print names of tasks, don't run them, " "note this has no affect on kolla-ansible.") + parser.add_argument("-sh", "--skip-hooks", action="store", default=None, + help="disables hooks. Specify a pattern to skip" + "specific playbooks. \"all\" skips all playbooks") def _get_kayobe_environment_path(parsed_args): @@ -161,7 +168,7 @@ def _get_vars_files(vars_paths): def build_args(parsed_args, playbooks, extra_vars=None, limit=None, tags=None, verbose_level=None, - check=None, ignore_limit=False, list_tasks=None): + check=None, ignore_limit=False, list_tasks=None, diff=None): """Build arguments required for running Ansible playbooks.""" cmd = ["ansible-playbook"] if verbose_level: @@ -193,6 +200,8 @@ def build_args(parsed_args, playbooks, cmd += ["--become"] if check or (parsed_args.check and check is None): cmd += ["--check"] + if diff or (parsed_args.diff and diff is None): + cmd += ["--diff"] if not ignore_limit and (parsed_args.limit or limit): limit_arg = utils.intersect_limits(parsed_args.limit, limit) cmd += ["--limit", limit_arg] @@ -227,13 +236,14 @@ def _get_environment(parsed_args): def run_playbooks(parsed_args, playbooks, extra_vars=None, limit=None, tags=None, quiet=False, check_output=False, verbose_level=None, check=None, - ignore_limit=False, list_tasks=None): + ignore_limit=False, list_tasks=None, diff=None): """Run a Kayobe Ansible playbook.""" _validate_args(parsed_args, playbooks) cmd = build_args(parsed_args, playbooks, extra_vars=extra_vars, limit=limit, tags=tags, verbose_level=verbose_level, check=check, - ignore_limit=ignore_limit, list_tasks=list_tasks) + ignore_limit=ignore_limit, list_tasks=list_tasks, + diff=diff) env = _get_environment(parsed_args) try: utils.run_command(cmd, check_output=check_output, quiet=quiet, env=env) @@ -269,7 +279,7 @@ def config_dump(parsed_args, host=None, hosts=None, var_name=None, run_playbook(parsed_args, playbook_path, extra_vars=extra_vars, tags=tags, check_output=True, verbose_level=verbose_level, check=False, - list_tasks=False) + list_tasks=False, diff=False) hostvars = {} for path in os.listdir(dump_dir): LOG.debug("Found dump file %s", path) @@ -291,7 +301,7 @@ def config_dump(parsed_args, host=None, hosts=None, var_name=None, def install_galaxy_roles(parsed_args, force=False): """Install Ansible Galaxy role dependencies. - Installs dependencies specified in kayobe, and if present, in kayobe + Installs role dependencies specified in kayobe, and if present, in kayobe configuration. :param parsed_args: Parsed command line arguments. @@ -300,7 +310,7 @@ def install_galaxy_roles(parsed_args, force=False): LOG.info("Installing galaxy role dependencies from kayobe") requirements = utils.get_data_files_path("requirements.yml") roles_destination = utils.get_data_files_path('ansible', 'roles') - utils.galaxy_install(requirements, roles_destination, force=force) + utils.galaxy_role_install(requirements, roles_destination, force=force) # Check for requirements in kayobe configuration. kc_reqs_path = os.path.join(parsed_args.config_path, @@ -323,7 +333,49 @@ def install_galaxy_roles(parsed_args, force=False): (parsed_args.config_path, str(e))) # Install roles from kayobe-config. - utils.galaxy_install(kc_reqs_path, kc_roles_path, force=force) + utils.galaxy_role_install(kc_reqs_path, kc_roles_path, force=force) + + +def install_galaxy_collections(parsed_args, force=False): + """Install Ansible Galaxy collection dependencies. + + Installs collection dependencies specified in kayobe, and if present, in + kayobe configuration. + + :param parsed_args: Parsed command line arguments. + :param force: Whether to force reinstallation of roles. + """ + LOG.info("Installing galaxy collection dependencies from kayobe") + requirements = utils.get_data_files_path("requirements.yml") + collections_destination = utils.get_data_files_path('ansible', + 'collections') + utils.galaxy_collection_install(requirements, collections_destination, + force=force) + + # Check for requirements in kayobe configuration. + kc_reqs_path = os.path.join(parsed_args.config_path, + "ansible", "requirements.yml") + if not utils.is_readable_file(kc_reqs_path)["result"]: + LOG.info("Not installing galaxy collection dependencies from kayobe " + "config - requirements.yml not present") + return + + LOG.info("Installing galaxy collection dependencies from kayobe config") + # Ensure a collections directory exists in kayobe-config. + kc_collections_path = os.path.join(parsed_args.config_path, + "ansible", "collections") + try: + os.makedirs(kc_collections_path) + except OSError as e: + if e.errno != errno.EEXIST: + raise exception.Error("Failed to create directory " + "ansible/collections/ " + "in kayobe configuration at %s: %s" % + (parsed_args.config_path, str(e))) + + # Install collections from kayobe-config. + utils.galaxy_collection_install(kc_reqs_path, kc_collections_path, + force=force) def prune_galaxy_roles(parsed_args): diff --git a/kayobe/cli/commands.py b/kayobe/cli/commands.py index db3f31c44..214826eea 100644 --- a/kayobe/cli/commands.py +++ b/kayobe/cli/commands.py @@ -15,6 +15,7 @@ import glob import json import os +import re import sys from cliff.command import Command @@ -190,18 +191,27 @@ def _find_hooks(self, config_path, target): self.logger.debug("Discovered the following hooks: %s" % hooks) return hooks - def hooks(self, config_path, target): - hooks = self._find_hooks(config_path, target) + def hooks(self, config_path, target, filter): + hooks_out = [] + if filter == "all": + self.logger.debug("Skipping all hooks") + return hooks_out + hooks_in = self._find_hooks(config_path, target) # Hooks can be prefixed with a sequence number to adjust running order, # e.g 10-my-custom-playbook.yml. Sort by sequence number. - hooks = sorted(hooks, key=_split_hook_sequence_number) - # Resolve symlinks so that we can reference roles. - hooks = [os.path.realpath(hook) for hook in hooks] - return hooks + hooks_in = sorted(hooks_in, key=_split_hook_sequence_number) + for hook in hooks_in: + # Resolve symlinks so that we can reference roles. + hook = os.path.realpath(hook) + if filter and re.search(filter, hook): + self.logger.debug("Skipping hook: %s", hook) + else: + hooks_out.append(hook) + return hooks_out def run_hooks(self, parsed_args, target): config_path = parsed_args.config_path - hooks = self.hooks(config_path, target) + hooks = self.hooks(config_path, target, parsed_args.skip_hooks) if hooks: self.logger.debug("Running hooks: %s" % hooks) self.command.run_kayobe_playbooks(parsed_args, hooks) @@ -232,6 +242,7 @@ class ControlHostBootstrap(KayobeAnsibleMixin, KollaAnsibleMixin, VaultMixin, def take_action(self, parsed_args): self.app.LOG.debug("Bootstrapping Kayobe Ansible control host") ansible.install_galaxy_roles(parsed_args) + ansible.install_galaxy_collections(parsed_args) playbooks = _build_playbook_list("bootstrap") self.run_kayobe_playbooks(parsed_args, playbooks, ignore_limit=True) @@ -271,8 +282,9 @@ def take_action(self, parsed_args): # Remove roles that are no longer used. Do this before installing new # ones, just in case a custom role dependency includes any. ansible.prune_galaxy_roles(parsed_args) - # Use force to upgrade roles. + # Use force to upgrade roles and collections. ansible.install_galaxy_roles(parsed_args, force=True) + ansible.install_galaxy_collections(parsed_args, force=True) playbooks = _build_playbook_list("bootstrap") self.run_kayobe_playbooks(parsed_args, playbooks, ignore_limit=True) playbooks = _build_playbook_list("kolla-ansible") @@ -407,12 +419,14 @@ class SeedHypervisorHostConfigure(KollaAnsibleMixin, KayobeAnsibleMixin, * Allocate IP addresses for all configured networks. * Add the host to SSH known hosts. * Configure a user account for use by kayobe for SSH access. + * Configure proxy settings. * Configure package repos. * Configure a PyPI mirror. * Optionally, create a virtualenv for remote target hosts. * Optionally, wipe unmounted disk partitions (--wipe-disks). * Configure user accounts, group associations, and authorised SSH keys. * Configure the host's network interfaces. + * Configure a firewall. * Set sysctl parameters. * Configure timezone and ntp. * Optionally, configure software RAID arrays. @@ -438,12 +452,12 @@ def take_action(self, parsed_args): limit="seed-hypervisor") playbooks = _build_playbook_list( - "ssh-known-host", "kayobe-ansible-user", + "ssh-known-host", "kayobe-ansible-user", "proxy", "apt", "dnf", "pip", "kayobe-target-venv") if parsed_args.wipe_disks: playbooks += _build_playbook_list("wipe-disks") playbooks += _build_playbook_list( - "users", "dev-tools", "network", "sysctl", "time", + "users", "dev-tools", "network", "firewall", "sysctl", "time", "mdadm", "luks", "lvm", "seed-hypervisor-libvirt-host") self.run_kayobe_playbooks(parsed_args, playbooks, limit="seed-hypervisor") @@ -554,6 +568,7 @@ class SeedHostConfigure(KollaAnsibleMixin, KayobeAnsibleMixin, VaultMixin, * Allocate IP addresses for all configured networks. * Add the host to SSH known hosts. * Configure a user account for use by kayobe for SSH access. + * Configure proxy settings. * Configure package repos. * Configure a PyPI mirror. * Optionally, create a virtualenv for remote target hosts. @@ -561,6 +576,7 @@ class SeedHostConfigure(KollaAnsibleMixin, KayobeAnsibleMixin, VaultMixin, * Configure user accounts, group associations, and authorised SSH keys. * Disable SELinux. * Configure the host's network interfaces. + * Configure a firewall. * Set sysctl parameters. * Configure IP routing and source NAT. * Disable bootstrap interface configuration. @@ -592,12 +608,12 @@ def take_action(self, parsed_args): # Run kayobe playbooks. playbooks = _build_playbook_list( - "ssh-known-host", "kayobe-ansible-user", + "ssh-known-host", "kayobe-ansible-user", "proxy", "apt", "dnf", "pip", "kayobe-target-venv") if parsed_args.wipe_disks: playbooks += _build_playbook_list("wipe-disks") playbooks += _build_playbook_list( - "users", "dev-tools", "disable-selinux", "network", + "users", "dev-tools", "disable-selinux", "network", "firewall", "sysctl", "ip-routing", "snat", "disable-glean", "time", "mdadm", "luks", "lvm", "docker-devicemapper", "kolla-ansible-user", "kolla-pip", "kolla-target-venv") @@ -811,6 +827,173 @@ def take_action(self, parsed_args): extra_vars=extra_vars) +class InfraVMProvision(KayobeAnsibleMixin, VaultMixin, Command): + """Provisions infra virtual machines + + * Allocate IP addresses for all configured networks. + * Provision a virtual machine using libvirt. + """ + + def take_action(self, parsed_args): + self.app.LOG.debug("Provisioning infra VMs") + self.run_kayobe_playbook(parsed_args, + _get_playbook_path("ip-allocation"), + limit="infra-vms") + + limit_arg = utils.intersect_limits(parsed_args.limit, "infra-vms") + # We want the limit to affect one play only. To do this we use a + # variable to override the hosts list instead of using limit. + extra_vars = { + "infra_vm_limit": limit_arg + } + + self.run_kayobe_playbook(parsed_args, + _get_playbook_path("infra-vm-provision"), + ignore_limit=True, extra_vars=extra_vars) + + +class InfraVMDeprovision(KayobeAnsibleMixin, VaultMixin, Command): + """Deprovisions infra virtual machines. + + This will destroy all infra VMs and all associated volumes. + """ + + def take_action(self, parsed_args): + self.app.LOG.debug("Deprovisioning infra VMs") + # We want the limit to affect one play only. To do this we use a + # variable to override the hosts list instead of using limit. + limit_arg = utils.intersect_limits(parsed_args.limit, "infra-vms") + extra_vars = { + "infra_vm_limit": limit_arg + } + + self.run_kayobe_playbook(parsed_args, + _get_playbook_path("infra-vm-deprovision"), + ignore_limit=True, extra_vars=extra_vars) + + +class InfraVMHostConfigure(KayobeAnsibleMixin, VaultMixin, + Command): + """Configure the infra VMs host OS and services. + + * Allocate IP addresses for all configured networks. + * Add the host to SSH known hosts. + * Configure a user account for use by kayobe for SSH access. + * Configure proxy settings. + * Configure package repos. + * Configure a PyPI mirror. + * Optionally, create a virtualenv for remote target hosts. + * Optionally, wipe unmounted disk partitions (--wipe-disks). + * Configure user accounts, group associations, and authorised SSH keys. + * Disable SELinux. + * Configure the host's network interfaces. + * Configure a firewall. + * Set sysctl parameters. + * Disable bootstrap interface configuration. + * Configure timezone. + * Optionally, configure software RAID arrays. + * Optionally, configure encryption. + * Configure LVM volumes. + """ + + def get_parser(self, prog_name): + parser = super(InfraVMHostConfigure, self).get_parser(prog_name) + group = parser.add_argument_group("Host Configuration") + group.add_argument("--wipe-disks", action='store_true', + help="wipe partition and LVM data from all disks " + "that are not mounted. Warning: this can " + "result in the loss of data") + return parser + + def take_action(self, parsed_args): + self.app.LOG.debug("Configuring Infra VMs host OS") + + # Allocate IP addresses. + playbooks = _build_playbook_list("ip-allocation") + self.run_kayobe_playbooks(parsed_args, playbooks, limit="infra-vms") + + # Kayobe playbooks. + playbooks = _build_playbook_list( + "ssh-known-host", "kayobe-ansible-user", "proxy", + "apt", "dnf", "pip", "kayobe-target-venv") + if parsed_args.wipe_disks: + playbooks += _build_playbook_list("wipe-disks") + playbooks += _build_playbook_list( + "users", "dev-tools", "disable-selinux", "network", "firewall", + "sysctl", "disable-glean", "disable-cloud-init", "time", + "mdadm", "luks", "lvm", "docker-devicemapper", "docker") + self.run_kayobe_playbooks(parsed_args, playbooks, limit="infra-vms") + + +class InfraVMHostPackageUpdate(KayobeAnsibleMixin, VaultMixin, Command): + """Update packages on the infra VMs.""" + + def get_parser(self, prog_name): + parser = super(InfraVMHostPackageUpdate, self).get_parser(prog_name) + group = parser.add_argument_group("Host Package Updates") + group.add_argument("--packages", required=True, + help="List of packages to update. Use '*' to " + "update all packages.") + group.add_argument("--security", action='store_true', + help="Only install updates that have been marked " + "security related.") + return parser + + def take_action(self, parsed_args): + self.app.LOG.debug("Updating infra vm host packages") + extra_vars = { + "host_package_update_packages": parsed_args.packages, + "host_package_update_security": parsed_args.security, + } + playbooks = _build_playbook_list("host-package-update") + self.run_kayobe_playbooks(parsed_args, playbooks, limit="infra-vms", + extra_vars=extra_vars) + + +class InfraVMHostCommandRun(KayobeAnsibleMixin, VaultMixin, Command): + """Run command on the infra VMs.""" + + def get_parser(self, prog_name): + parser = super(InfraVMHostCommandRun, self).get_parser(prog_name) + group = parser.add_argument_group("Host Command Run") + group.add_argument("--command", required=True, + help="Command to run (required).") + group.add_argument("--show-output", action='store_true', + help="Show command output") + return parser + + def take_action(self, parsed_args): + self.app.LOG.debug("Run command on infra VM hosts") + extra_vars = { + "host_command_to_run": utils.escape_jinja(parsed_args.command), + "show_output": parsed_args.show_output} + playbooks = _build_playbook_list("host-command-run") + self.run_kayobe_playbooks(parsed_args, playbooks, limit="infra-vms", + extra_vars=extra_vars) + + +class InfraVMHostUpgrade(KayobeAnsibleMixin, VaultMixin, Command): + """Upgrade the infra VM host services. + + Performs the changes necessary to make the host services suitable for the + configured OpenStack release. + """ + + def take_action(self, parsed_args): + self.app.LOG.debug("Upgrading the infra-vm host services") + playbooks = _build_playbook_list("kayobe-target-venv") + self.run_kayobe_playbooks(parsed_args, playbooks, + limit="infra-vms") + + +class InfraVMServiceDeploy(KayobeAnsibleMixin, VaultMixin, + Command): + """Run hooks for infra structure services.""" + + def take_action(self, parsed_args): + self.app.LOG.debug("Running no-op Infra VM service deploy") + + class OvercloudInventoryDiscover(KayobeAnsibleMixin, VaultMixin, Command): """Discover the overcloud inventory from the seed's Ironic service. @@ -946,6 +1129,7 @@ class OvercloudHostConfigure(KollaAnsibleMixin, KayobeAnsibleMixin, VaultMixin, * Allocate IP addresses for all configured networks. * Add the host to SSH known hosts. * Configure a user account for use by kayobe for SSH access. + * Configure proxy settings. * Configure package repos. * Configure a PyPI mirror. * Optionally, create a virtualenv for remote target hosts. @@ -953,6 +1137,7 @@ class OvercloudHostConfigure(KollaAnsibleMixin, KayobeAnsibleMixin, VaultMixin, * Configure user accounts, group associations, and authorised SSH keys. * Disable SELinux. * Configure the host's network interfaces. + * Configure a firewall. * Set sysctl parameters. * Disable bootstrap interface configuration. * Configure timezone and ntp. @@ -962,6 +1147,7 @@ class OvercloudHostConfigure(KollaAnsibleMixin, KayobeAnsibleMixin, VaultMixin, * Optionally, create a virtualenv for kolla-ansible. * Configure a user account for kolla-ansible. * Configure Docker engine. + * Configure libvirt. """ def get_parser(self, prog_name): @@ -982,12 +1168,12 @@ def take_action(self, parsed_args): # Kayobe playbooks. playbooks = _build_playbook_list( - "ssh-known-host", "kayobe-ansible-user", + "ssh-known-host", "kayobe-ansible-user", "proxy", "apt", "dnf", "pip", "kayobe-target-venv") if parsed_args.wipe_disks: playbooks += _build_playbook_list("wipe-disks") playbooks += _build_playbook_list( - "users", "dev-tools", "disable-selinux", "network", + "users", "dev-tools", "disable-selinux", "network", "firewall", "sysctl", "disable-glean", "disable-cloud-init", "time", "mdadm", "luks", "lvm", "docker-devicemapper", "kolla-ansible-user", "kolla-pip", "kolla-target-venv") @@ -1000,7 +1186,8 @@ def take_action(self, parsed_args): # Further kayobe playbooks. playbooks = _build_playbook_list( - "kolla-host", "docker", "swift-block-devices") + "kolla-host", "docker", "swift-block-devices", + "compute-libvirt-host") self.run_kayobe_playbooks(parsed_args, playbooks, limit="overcloud") @@ -1596,6 +1783,31 @@ def take_action(self, parsed_args): extra_vars=extra_vars) +class OvercloudHostImageBuild(KayobeAnsibleMixin, VaultMixin, Command): + """Build overcloud host disk images. + + Builds host disk images using Diskimage Builder (DIB) for use when + provisioning the overcloud hosts. + """ + + def get_parser(self, prog_name): + parser = super(OvercloudHostImageBuild, self).get_parser( + prog_name) + group = parser.add_argument_group("Host Image Build") + group.add_argument("--force-rebuild", action="store_true", + help="whether to force rebuilding the images") + return parser + + def take_action(self, parsed_args): + self.app.LOG.debug("Building overcloud host disk images") + playbooks = _build_playbook_list("overcloud-host-image-build") + extra_vars = {} + if parsed_args.force_rebuild: + extra_vars["overcloud_host_image_force_rebuild"] = True + self.run_kayobe_playbooks(parsed_args, playbooks, + extra_vars=extra_vars) + + class OvercloudPostConfigure(KayobeAnsibleMixin, VaultMixin, Command): """Perform post-deployment configuration. diff --git a/kayobe/kolla_ansible.py b/kayobe/kolla_ansible.py index 8954dae07..8c5532b82 100644 --- a/kayobe/kolla_ansible.py +++ b/kayobe/kolla_ansible.py @@ -56,6 +56,8 @@ def add_args(parser): parser.add_argument("-kl", "--kolla-limit", metavar="SUBSET", help="further limit selected hosts to an additional " "pattern") + parser.add_argument("-kp", "--kolla-playbook", metavar="PLAYBOOK", + help="path to Ansible playbook file") parser.add_argument("--kolla-skip-tags", metavar="TAGS", help="only run plays and tasks whose tags do not " "match these values in Kolla Ansible") @@ -104,6 +106,13 @@ def _validate_args(parsed_args, inventory_filename): parsed_args.kolla_venv, result["message"]) sys.exit(1) + if parsed_args.kolla_playbook: + result = utils.is_readable_file(parsed_args.kolla_playbook) + if not result["result"]: + LOG.error("Kolla Ansible playbook %s is invalid: %s", + parsed_args.kolla_playbook, result["message"]) + sys.exit(1) + def build_args(parsed_args, command, inventory_filename, extra_vars=None, tags=None, verbose_level=None, extra_args=None, limit=None): @@ -113,6 +122,8 @@ def build_args(parsed_args, command, inventory_filename, extra_vars=None, cmd += ["kolla-ansible", command] if verbose_level: cmd += ["-" + "v" * verbose_level] + if parsed_args.kolla_playbook: + cmd += ["--playbook", parsed_args.kolla_playbook] cmd += vault.build_args(parsed_args, "--key") inventory = _get_inventory_path(parsed_args, inventory_filename) cmd += ["--inventory", inventory] @@ -157,6 +168,14 @@ def _get_environment(parsed_args): ansible_cfg_path = os.path.join(parsed_args.config_path, "ansible.cfg") if utils.is_readable_file(ansible_cfg_path)["result"]: env.setdefault("ANSIBLE_CONFIG", ansible_cfg_path) + # kolla-ansible allows passing additional arguments to ansible-playbook via + # EXTRA_OPTS. + if parsed_args.check or parsed_args.diff: + extra_opts = env.setdefault("EXTRA_OPTS", "") + if parsed_args.check and "--check" not in extra_opts: + env["EXTRA_OPTS"] += " --check" + if parsed_args.diff and "--diff" not in extra_opts: + env["EXTRA_OPTS"] += " --diff" return env diff --git a/kayobe/plugins/action/merge_configs.py b/kayobe/plugins/action/merge_configs.py new file mode 100644 index 000000000..1e7e9da8f --- /dev/null +++ b/kayobe/plugins/action/merge_configs.py @@ -0,0 +1,225 @@ +#!/usr/bin/env python + +# Copyright 2015 Sam Yaple +# Copyright 2017 99Cloud Inc. +# +# 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. + +# This file has been adapted from the merge_configs action plugin in Kolla +# Ansible. +# https://opendev.org/openstack/kolla-ansible/src/branch/master/ansible/action_plugins/merge_configs.py + +import collections +import os +import shutil +import tempfile + +from ansible import constants +from ansible.plugins import action +from io import StringIO + +from oslo_config import iniparser + +_ORPHAN_SECTION = 'TEMPORARY_ORPHAN_VARIABLE_SECTION' + +DOCUMENTATION = ''' +--- +module: merge_configs +short_description: Merge ini-style configs +description: + - ConfigParser is used to merge several ini-style configs into one +options: + dest: + description: + - The destination file name + required: True + type: str + sources: + description: + - A list of files on the destination node to merge together + default: None + required: True + type: str + whitespace: + description: + - Whether whitespace characters should be used around equal signs + default: True + required: False + type: bool +author: Sam Yaple +''' + +EXAMPLES = ''' +Merge multiple configs: + +- hosts: database + tasks: + - name: Merge configs + merge_configs: + sources: + - "/tmp/config_1.cnf" + - "/tmp/config_2.cnf" + - "/tmp/config_3.cnf" + dest: + - "/etc/mysql/my.cnf" +''' + + +class OverrideConfigParser(iniparser.BaseParser): + + def __init__(self, whitespace=True): + self._cur_sections = collections.OrderedDict() + self._sections = collections.OrderedDict() + self._cur_section = None + self._whitespace = ' ' if whitespace else '' + + def assignment(self, key, value): + if self._cur_section is None: + self.new_section(_ORPHAN_SECTION) + cur_value = self._cur_section.get(key) + if len(value) == 1 and value[0] == '': + value = [] + if not cur_value: + self._cur_section[key] = [value] + else: + self._cur_section[key].append(value) + + def parse(self, lineiter): + self._cur_sections = collections.OrderedDict() + self._cur_section = None + super(OverrideConfigParser, self).parse(lineiter) + + # merge _cur_sections into _sections + for section, values in self._cur_sections.items(): + if section not in self._sections: + self._sections[section] = collections.OrderedDict() + for key, value in values.items(): + self._sections[section][key] = value + + def new_section(self, section): + cur_section = self._cur_sections.get(section) + if not cur_section: + cur_section = collections.OrderedDict() + self._cur_sections[section] = cur_section + self._cur_section = cur_section + return cur_section + + def write(self, fp): + def write_key_value(key, values): + for v in values: + if not v: + fp.write('{key}{ws}=\n'.format( + key=key, ws=self._whitespace)) + for index, value in enumerate(v): + if index == 0: + fp.write('{key}{ws}={ws}{value}\n'.format( + key=key, + ws=self._whitespace, + value=value)) + else: + # We want additional values to be written out under the + # first value with the same indentation, like this: + # key = value1 + # value2 + indent_size = len(key) + len(self._whitespace) * 2 + 1 + ws_indent = ' ' * indent_size + fp.write('{ws_indent}{value}\n'.format( + ws_indent=ws_indent, + value=value)) + + def write_section(section): + for key, values in section.items(): + write_key_value(key, values) + + for section in self._sections: + if section != _ORPHAN_SECTION: + fp.write('[{}]\n'.format(section)) + write_section(self._sections[section]) + fp.write('\n') + + +class ActionModule(action.ActionBase): + + TRANSFERS_FILES = True + + def read_config(self, source, config): + # Only use config if present + if os.access(source, os.R_OK): + with open(source, 'r') as f: + template_data = f.read() + + # set search path to mimic 'template' module behavior + searchpath = [ + self._loader._basedir, + os.path.join(self._loader._basedir, 'templates'), + os.path.dirname(source), + ] + self._templar.environment.loader.searchpath = searchpath + + result = self._templar.template(template_data) + fakefile = StringIO(result) + config.parse(fakefile) + fakefile.close() + + def run(self, tmp=None, task_vars=None): + + result = super(ActionModule, self).run(tmp, task_vars) + del tmp # not used + + sources = self._task.args.get('sources', None) + + if not isinstance(sources, list): + sources = [sources] + + config = OverrideConfigParser( + whitespace=self._task.args.get('whitespace', True)) + + for source in sources: + self.read_config(source, config) + + # Dump configparser to string via an emulated file + + fakefile = StringIO() + config.write(fakefile) + full_source = fakefile.getvalue() + fakefile.close() + + local_tempdir = tempfile.mkdtemp(dir=constants.DEFAULT_LOCAL_TMP) + + try: + result_file = os.path.join(local_tempdir, 'source') + with open(result_file, 'w') as f: + f.write(full_source) + + new_task = self._task.copy() + new_task.args.pop('sources', None) + new_task.args.pop('whitespace', None) + + new_task.args.update( + dict( + src=result_file + ) + ) + + copy_action = self._shared_loader_obj.action_loader.get( + 'copy', + task=new_task, + connection=self._connection, + play_context=self._play_context, + loader=self._loader, + templar=self._templar, + shared_loader_obj=self._shared_loader_obj) + result.update(copy_action.run(task_vars=task_vars)) + finally: + shutil.rmtree(local_tempdir) + return result diff --git a/kayobe/plugins/action/merge_yaml.py b/kayobe/plugins/action/merge_yaml.py new file mode 100644 index 000000000..b2e5a621a --- /dev/null +++ b/kayobe/plugins/action/merge_yaml.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python + +# Copyright 2015 Sam Yaple +# Copyright 2016 intel +# +# 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. + +# This file has been adapted from the merge_yaml action plugin in Kolla +# Ansible. +# https://opendev.org/openstack/kolla-ansible/src/branch/master/ansible/action_plugins/merge_yaml.py + +import os +import shutil +import tempfile + +import yaml + +from ansible import constants +from ansible import errors as ansible_errors +from ansible.plugins import action + +DOCUMENTATION = ''' +--- +module: merge_yaml +short_description: Merge yaml-style configs +description: + - PyYAML is used to merge several yaml files into one +options: + dest: + description: + - The destination file name + required: True + type: str + sources: + description: + - A list of files on the destination node to merge together + default: None + required: True + type: str + extend_lists: + description: + - For a given key referencing a list, this determines whether + the list items should be combined with the items in another + document if an equivalent key is found. An equivalent key + has the same parents and value as the first. The default + behaviour is to replace existing entries i.e if you have + two yaml documents that both define a list with an equivalent + key, the value from the document that appears later in the + list of sources will replace the value that appeared in the + earlier one. + default: False + required: False + type: bool +author: Sean Mooney +''' + +EXAMPLES = ''' +Merge multiple yaml files: + +- hosts: localhost + tasks: + - name: Merge yaml files + merge_yaml: + sources: + - "/tmp/default.yml" + - "/tmp/override.yml" + dest: + - "/tmp/out.yml" +''' + + +class ActionModule(action.ActionBase): + + TRANSFERS_FILES = True + + def read_config(self, source): + result = None + # Only use config if present + if source and os.access(source, os.R_OK): + with open(source, 'r') as f: + template_data = f.read() + + # set search path to mimic 'template' module behavior + searchpath = [ + self._loader._basedir, + os.path.join(self._loader._basedir, 'templates'), + os.path.dirname(source), + ] + self._templar.environment.loader.searchpath = searchpath + + template_data = self._templar.template(template_data) + result = yaml.safe_load(template_data) + return result or {} + + def run(self, tmp=None, task_vars=None): + if task_vars is None: + task_vars = dict() + result = super(ActionModule, self).run(tmp, task_vars) + del tmp # not used + + # save template args. + extra_vars = self._task.args.get('vars', list()) + old_vars = self._templar._available_variables + + temp_vars = task_vars.copy() + temp_vars.update(extra_vars) + self._templar.available_variables = temp_vars + + output = {} + sources = self._task.args.get('sources', None) + extend_lists = self._task.args.get('extend_lists', False) + if not isinstance(sources, list): + sources = [sources] + for source in sources: + Utils.update_nested_conf( + output, self.read_config(source), extend_lists) + + # restore original vars + self._templar.available_variables = old_vars + + local_tempdir = tempfile.mkdtemp(dir=constants.DEFAULT_LOCAL_TMP) + + try: + result_file = os.path.join(local_tempdir, 'source') + with open(result_file, 'w') as f: + f.write(yaml.dump(output, default_flow_style=False)) + + new_task = self._task.copy() + new_task.args.pop('sources', None) + new_task.args.pop('extend_lists', None) + new_task.args.update( + dict( + src=result_file + ) + ) + + copy_action = self._shared_loader_obj.action_loader.get( + 'copy', + task=new_task, + connection=self._connection, + play_context=self._play_context, + loader=self._loader, + templar=self._templar, + shared_loader_obj=self._shared_loader_obj) + result.update(copy_action.run(task_vars=task_vars)) + finally: + shutil.rmtree(local_tempdir) + return result + + +class Utils(object): + @staticmethod + def update_nested_conf(conf, update, extend_lists=False): + for k, v in update.items(): + if isinstance(v, dict): + conf[k] = Utils.update_nested_conf( + conf.get(k, {}), v, extend_lists) + elif k in conf and isinstance(conf[k], list) and extend_lists: + if not isinstance(v, list): + errmsg = ( + "Failure merging key `%(key)s` in dictionary " + "`%(dictionary)s`. Expecting a list, but received: " + "`%(value)s`, which is of type: `%(type)s`" % { + "key": k, "dictionary": conf, + "value": v, "type": type(v)} + ) + raise ansible_errors.AnsibleModuleError(errmsg) + conf[k].extend(v) + else: + conf[k] = v + return conf diff --git a/kayobe/tests/unit/cli/test_commands.py b/kayobe/tests/unit/cli/test_commands.py index 587fcb341..9edbd5574 100644 --- a/kayobe/tests/unit/cli/test_commands.py +++ b/kayobe/tests/unit/cli/test_commands.py @@ -35,18 +35,21 @@ def __init__(self): class TestCase(unittest.TestCase): @mock.patch.object(ansible, "install_galaxy_roles", autospec=True) + @mock.patch.object(ansible, "install_galaxy_collections", autospec=True) @mock.patch.object(ansible, "passwords_yml_exists", autospec=True) @mock.patch.object(commands.KayobeAnsibleMixin, "run_kayobe_playbooks") def test_control_host_bootstrap(self, mock_run, mock_passwords, - mock_install): + mock_install_collections, + mock_install_roles): mock_passwords.return_value = False command = commands.ControlHostBootstrap(TestApp(), []) parser = command.get_parser("test") parsed_args = parser.parse_args([]) result = command.run(parsed_args) self.assertEqual(0, result) - mock_install.assert_called_once_with(parsed_args) + mock_install_roles.assert_called_once_with(parsed_args) + mock_install_collections.assert_called_once_with(parsed_args) expected_calls = [ mock.call( mock.ANY, @@ -63,20 +66,23 @@ def test_control_host_bootstrap(self, mock_run, mock_passwords, self.assertEqual(expected_calls, mock_run.call_args_list) @mock.patch.object(ansible, "install_galaxy_roles", autospec=True) + @mock.patch.object(ansible, "install_galaxy_collections", autospec=True) @mock.patch.object(ansible, "passwords_yml_exists", autospec=True) @mock.patch.object(commands.KayobeAnsibleMixin, "run_kayobe_playbooks") @mock.patch.object(commands.KollaAnsibleMixin, "run_kolla_ansible_overcloud") def test_control_host_bootstrap_with_passwords( - self, mock_kolla_run, mock_run, mock_passwords, mock_install): + self, mock_kolla_run, mock_run, mock_passwords, + mock_install_collections, mock_install_roles): mock_passwords.return_value = True command = commands.ControlHostBootstrap(TestApp(), []) parser = command.get_parser("test") parsed_args = parser.parse_args([]) result = command.run(parsed_args) self.assertEqual(0, result) - mock_install.assert_called_once_with(parsed_args) + mock_install_roles.assert_called_once_with(parsed_args) + mock_install_collections.assert_called_once_with(parsed_args) expected_calls = [ mock.call( mock.ANY, @@ -106,16 +112,21 @@ def test_control_host_bootstrap_with_passwords( self.assertEqual(expected_calls, mock_kolla_run.call_args_list) @mock.patch.object(ansible, "install_galaxy_roles", autospec=True) + @mock.patch.object(ansible, "install_galaxy_collections", autospec=True) @mock.patch.object(ansible, "prune_galaxy_roles", autospec=True) @mock.patch.object(commands.KayobeAnsibleMixin, "run_kayobe_playbooks") - def test_control_host_upgrade(self, mock_run, mock_prune, mock_install): + def test_control_host_upgrade(self, mock_run, mock_prune, + mock_install_roles, + mock_install_collections): command = commands.ControlHostUpgrade(TestApp(), []) parser = command.get_parser("test") parsed_args = parser.parse_args([]) result = command.run(parsed_args) self.assertEqual(0, result) - mock_install.assert_called_once_with(parsed_args, force=True) + mock_install_roles.assert_called_once_with(parsed_args, force=True) + mock_install_collections.assert_called_once_with(parsed_args, + force=True) mock_prune.assert_called_once_with(parsed_args) expected_calls = [ mock.call( @@ -308,6 +319,7 @@ def test_seed_hypervisor_host_configure(self, mock_run): utils.get_data_files_path("ansible", "ssh-known-host.yml"), utils.get_data_files_path( "ansible", "kayobe-ansible-user.yml"), + utils.get_data_files_path("ansible", "proxy.yml"), utils.get_data_files_path("ansible", "apt.yml"), utils.get_data_files_path("ansible", "dnf.yml"), utils.get_data_files_path("ansible", "pip.yml"), @@ -316,6 +328,7 @@ def test_seed_hypervisor_host_configure(self, mock_run): utils.get_data_files_path("ansible", "users.yml"), utils.get_data_files_path("ansible", "dev-tools.yml"), utils.get_data_files_path("ansible", "network.yml"), + utils.get_data_files_path("ansible", "firewall.yml"), utils.get_data_files_path("ansible", "sysctl.yml"), utils.get_data_files_path("ansible", "time.yml"), utils.get_data_files_path("ansible", "mdadm.yml"), @@ -479,6 +492,7 @@ def test_seed_host_configure(self, mock_kolla_run, mock_run): utils.get_data_files_path("ansible", "ssh-known-host.yml"), utils.get_data_files_path( "ansible", "kayobe-ansible-user.yml"), + utils.get_data_files_path("ansible", "proxy.yml"), utils.get_data_files_path("ansible", "apt.yml"), utils.get_data_files_path("ansible", "dnf.yml"), utils.get_data_files_path("ansible", "pip.yml"), @@ -489,6 +503,7 @@ def test_seed_host_configure(self, mock_kolla_run, mock_run): utils.get_data_files_path( "ansible", "disable-selinux.yml"), utils.get_data_files_path("ansible", "network.yml"), + utils.get_data_files_path("ansible", "firewall.yml"), utils.get_data_files_path("ansible", "sysctl.yml"), utils.get_data_files_path("ansible", "ip-routing.yml"), utils.get_data_files_path("ansible", "snat.yml"), @@ -899,6 +914,192 @@ def test_seed_service_upgrade(self, mock_kolla_run, mock_run): ] self.assertEqual(expected_calls, mock_kolla_run.call_args_list) + @mock.patch.object(commands.KayobeAnsibleMixin, + "run_kayobe_playbook") + def test_infra_vm_provision(self, mock_run): + command = commands.InfraVMProvision(TestApp(), []) + parser = command.get_parser("test") + parsed_args = parser.parse_args([]) + + result = command.run(parsed_args) + self.assertEqual(0, result) + + expected_calls = [ + mock.call( + mock.ANY, + utils.get_data_files_path( + "ansible", "ip-allocation.yml"), + limit="infra-vms" + ), + mock.call( + mock.ANY, + utils.get_data_files_path( + "ansible", "infra-vm-provision.yml"), + ignore_limit=True, + extra_vars={'infra_vm_limit': 'infra-vms'} + ), + ] + self.assertEqual(expected_calls, mock_run.call_args_list) + + @mock.patch.object(commands.KayobeAnsibleMixin, + "run_kayobe_playbook") + def test_infra_vm_deprovision(self, mock_run): + command = commands.InfraVMDeprovision(TestApp(), []) + parser = command.get_parser("test") + parsed_args = parser.parse_args([]) + + result = command.run(parsed_args) + self.assertEqual(0, result) + + expected_calls = [ + mock.call( + mock.ANY, + utils.get_data_files_path( + "ansible", "infra-vm-deprovision.yml"), + ignore_limit=True, + extra_vars={'infra_vm_limit': 'infra-vms'} + ), + ] + self.assertEqual(expected_calls, mock_run.call_args_list) + + @mock.patch.object(commands.KayobeAnsibleMixin, + "run_kayobe_playbooks") + def test_infra_vm_host_configure(self, mock_run): + command = commands.InfraVMHostConfigure(TestApp(), []) + parser = command.get_parser("test") + parsed_args = parser.parse_args([]) + + result = command.run(parsed_args) + self.assertEqual(0, result) + + expected_calls = [ + mock.call( + mock.ANY, + [utils.get_data_files_path("ansible", "ip-allocation.yml")], + limit="infra-vms", + ), + mock.call( + mock.ANY, + [ + utils.get_data_files_path("ansible", "ssh-known-host.yml"), + utils.get_data_files_path( + "ansible", "kayobe-ansible-user.yml"), + utils.get_data_files_path("ansible", "proxy.yml"), + utils.get_data_files_path("ansible", "apt.yml"), + utils.get_data_files_path("ansible", "dnf.yml"), + utils.get_data_files_path("ansible", "pip.yml"), + utils.get_data_files_path( + "ansible", "kayobe-target-venv.yml"), + utils.get_data_files_path("ansible", "users.yml"), + utils.get_data_files_path("ansible", "dev-tools.yml"), + utils.get_data_files_path( + "ansible", "disable-selinux.yml"), + utils.get_data_files_path("ansible", "network.yml"), + utils.get_data_files_path("ansible", "firewall.yml"), + utils.get_data_files_path("ansible", "sysctl.yml"), + utils.get_data_files_path("ansible", "disable-glean.yml"), + utils.get_data_files_path( + "ansible", "disable-cloud-init.yml"), + utils.get_data_files_path("ansible", "time.yml"), + utils.get_data_files_path("ansible", "mdadm.yml"), + utils.get_data_files_path("ansible", "luks.yml"), + utils.get_data_files_path("ansible", "lvm.yml"), + utils.get_data_files_path("ansible", + "docker-devicemapper.yml"), + utils.get_data_files_path("ansible", "docker.yml"), + ], + limit="infra-vms", + ), + ] + self.assertEqual(expected_calls, mock_run.call_args_list) + + @mock.patch.object(commands.KayobeAnsibleMixin, + "run_kayobe_playbooks") + def test_infra_vm_host_upgrade(self, mock_run): + command = commands.InfraVMHostUpgrade(TestApp(), []) + parser = command.get_parser("test") + parsed_args = parser.parse_args([]) + + result = command.run(parsed_args) + self.assertEqual(0, result) + + expected_calls = [ + mock.call( + mock.ANY, + [ + utils.get_data_files_path( + "ansible", "kayobe-target-venv.yml"), + ], + limit="infra-vms", + ), + ] + self.assertEqual(expected_calls, mock_run.call_args_list) + + @mock.patch.object(commands.KayobeAnsibleMixin, + "run_kayobe_playbooks") + def test_infra_vm_host_command_run(self, mock_run): + command = commands.InfraVMHostCommandRun(TestApp(), []) + parser = command.get_parser("test") + parsed_args = parser.parse_args(["--command", "ls -a", + "--show-output"]) + + result = command.run(parsed_args) + self.assertEqual(0, result) + + expected_calls = [ + mock.call( + mock.ANY, + [ + utils.get_data_files_path("ansible", + "host-command-run.yml"), + ], + limit="infra-vms", + extra_vars={ + "host_command_to_run": utils.escape_jinja("ls -a"), + "show_output": True} + ), + ] + self.assertEqual(expected_calls, mock_run.call_args_list) + + @mock.patch.object(commands.KayobeAnsibleMixin, + "run_kayobe_playbooks") + def test_infra_vm_host_package_update_all(self, mock_run): + command = commands.InfraVMHostPackageUpdate(TestApp(), []) + parser = command.get_parser("test") + parsed_args = parser.parse_args(["--packages", "*"]) + + result = command.run(parsed_args) + self.assertEqual(0, result) + + expected_calls = [ + mock.call( + mock.ANY, + [ + utils.get_data_files_path( + "ansible", "host-package-update.yml"), + ], + limit="infra-vms", + extra_vars={ + "host_package_update_packages": "*", + "host_package_update_security": False, + }, + ), + ] + self.assertEqual(expected_calls, mock_run.call_args_list) + + @mock.patch.object(commands.KayobeAnsibleMixin, + "run_kayobe_playbook") + def test_infra_vm_service_deploy(self, mock_run): + command = commands.InfraVMServiceDeploy(TestApp(), []) + parser = command.get_parser("test") + parsed_args = parser.parse_args([]) + + result = command.run(parsed_args) + self.assertEqual(0, result) + + expected_calls = [] + self.assertEqual(expected_calls, mock_run.call_args_list) + @mock.patch.object(commands.KayobeAnsibleMixin, "run_kayobe_playbooks") @mock.patch.object(commands.KayobeAnsibleMixin, @@ -1063,6 +1264,7 @@ def test_overcloud_host_configure(self, mock_kolla_run, mock_run): utils.get_data_files_path("ansible", "ssh-known-host.yml"), utils.get_data_files_path( "ansible", "kayobe-ansible-user.yml"), + utils.get_data_files_path("ansible", "proxy.yml"), utils.get_data_files_path("ansible", "apt.yml"), utils.get_data_files_path("ansible", "dnf.yml"), utils.get_data_files_path("ansible", "pip.yml"), @@ -1073,6 +1275,7 @@ def test_overcloud_host_configure(self, mock_kolla_run, mock_run): utils.get_data_files_path( "ansible", "disable-selinux.yml"), utils.get_data_files_path("ansible", "network.yml"), + utils.get_data_files_path("ansible", "firewall.yml"), utils.get_data_files_path("ansible", "sysctl.yml"), utils.get_data_files_path("ansible", "disable-glean.yml"), utils.get_data_files_path( @@ -1104,6 +1307,8 @@ def test_overcloud_host_configure(self, mock_kolla_run, mock_run): utils.get_data_files_path("ansible", "docker.yml"), utils.get_data_files_path( "ansible", "swift-block-devices.yml"), + utils.get_data_files_path( + "ansible", "compute-libvirt-host.yml"), ], limit="overcloud", ), @@ -1868,6 +2073,50 @@ def test_overcloud_container_image_build_with_regex(self, mock_run): ] self.assertEqual(expected_calls, mock_run.call_args_list) + @mock.patch.object(commands.KayobeAnsibleMixin, + "run_kayobe_playbooks") + def test_overcloud_host_image_build(self, mock_run): + command = commands.OvercloudHostImageBuild(TestApp(), []) + parser = command.get_parser("test") + parsed_args = parser.parse_args([]) + + result = command.run(parsed_args) + self.assertEqual(0, result) + + expected_calls = [ + mock.call( + mock.ANY, + [ + utils.get_data_files_path( + "ansible", "overcloud-host-image-build.yml"), + ], + extra_vars={}, + ), + ] + self.assertEqual(expected_calls, mock_run.call_args_list) + + @mock.patch.object(commands.KayobeAnsibleMixin, + "run_kayobe_playbooks") + def test_overcloud_host_image_build_force_rebuild(self, mock_run): + command = commands.OvercloudHostImageBuild(TestApp(), []) + parser = command.get_parser("test") + parsed_args = parser.parse_args(["--force-rebuild"]) + + result = command.run(parsed_args) + self.assertEqual(0, result) + + expected_calls = [ + mock.call( + mock.ANY, + [ + utils.get_data_files_path( + "ansible", "overcloud-host-image-build.yml"), + ], + extra_vars={"overcloud_host_image_force_rebuild": True}, + ), + ] + self.assertEqual(expected_calls, mock_run.call_args_list) + @mock.patch.object(commands.KayobeAnsibleMixin, "run_kayobe_playbooks") def test_overcloud_deployment_image_build(self, mock_run): @@ -2207,5 +2456,47 @@ def test_hook_ordering(self, mock_path): "z-test-alphabetical.yml", ] mock_path.realpath.side_effect = lambda x: x - actual = dispatcher.hooks("config/path", "pre") + actual = dispatcher.hooks("config/path", "pre", None) + self.assertListEqual(actual, expected_result) + + @mock.patch('kayobe.cli.commands.os.path') + def test_hook_filter_all(self, mock_path): + mock_command = mock.MagicMock() + dispatcher = commands.HookDispatcher(command=mock_command) + dispatcher._find_hooks = mock.MagicMock() + dispatcher._find_hooks.return_value = [ + "5-hook.yml", + "5-multiple-dashes-in-name.yml", + "10-before-hook.yml", + "10-hook.yml", + "no-prefix.yml", + "z-test-alphabetical.yml", + ] + mock_path.realpath.side_effect = lambda x: x + actual = dispatcher.hooks("config/path", "pre", "all") + self.assertListEqual(actual, []) + + @mock.patch('kayobe.cli.commands.os.path') + def test_hook_filter_one(self, mock_path): + mock_command = mock.MagicMock() + dispatcher = commands.HookDispatcher(command=mock_command) + dispatcher._find_hooks = mock.MagicMock() + dispatcher._find_hooks.return_value = [ + "5-hook.yml", + "5-multiple-dashes-in-name.yml", + "10-before-hook.yml", + "10-hook.yml", + "no-prefix.yml", + "z-test-alphabetical.yml", + ] + expected_result = [ + "5-hook.yml", + "10-before-hook.yml", + "10-hook.yml", + "no-prefix.yml", + "z-test-alphabetical.yml", + ] + mock_path.realpath.side_effect = lambda x: x + actual = dispatcher.hooks("config/path", "pre", + "5-multiple-dashes-in-name.yml") self.assertListEqual(actual, expected_result) diff --git a/kayobe/tests/unit/plugins/action/test_merge_config.py b/kayobe/tests/unit/plugins/action/test_merge_config.py new file mode 100644 index 000000000..3d0d395c6 --- /dev/null +++ b/kayobe/tests/unit/plugins/action/test_merge_config.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python + +# Copyright 2016 99cloud Inc. +# 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 io import StringIO +from oslotest import base + +from kayobe.plugins.action import merge_configs + + +TESTA = '''[DEFAULT] +key1 = b + c +key2 = v1 + v2 +key3 = v3 +key3 = v4 +key4 = v5 + +[b] +b_key1 = 1 +b_key2 = 1 + 2 + +[c] +c_key1 = +c_key2 = 1 2 3 + 4 5 6 + +''' + +TESTB = '''[DEFAULT] +key2 = v3 + v4 + v5 +key4 = v4 +key4 = + +[b] +b_key2 = 2 + +''' + +# TESTC is TESTA + TESTB +TESTC = '''[DEFAULT] +key1 = b + c +key2 = v3 + v4 + v5 +key3 = v3 +key3 = v4 +key4 = v4 +key4 = + +[b] +b_key1 = 1 +b_key2 = 2 + +[c] +c_key1 = +c_key2 = 1 2 3 + 4 5 6 + +''' + +TESTA_NO_SECTIONS = '''key1 = a +key2 = b + +''' + +TESTB_NO_SECTIONS = '''key3 = c + +''' + +# TESTA_NO_SECTIONS and TESTB_NO_SECTIONS combined +TESTC_NO_SECTIONS = '''key1 = a +key2 = b +key3 = c + +''' + +TESTA_NO_DEFAULT_SECTION = '''key1 = a +key2 = b + +[a] +key1 = not_a + +[b] +key3 = not_c + +''' + +TESTB_NO_DEFAULT_SECTION = '''key3 = c + +[b] +key2 = not_b +key3 = override + +''' + +# TESTA_NO_DEFAULT_SECTION and TESTB_NO_DEFAULT_SECTION combined +TESTC_NO_DEFAULT_SECTION = '''key1 = a +key2 = b +key3 = c + +[a] +key1 = not_a + +[b] +key3 = override +key2 = not_b + +''' + +# TESTC_NO_WHITESPACE is TESTA + TESTB without whitespace around equal signs +TESTC_NO_WHITESPACE = '''[DEFAULT] +key1=b + c +key2=v3 + v4 + v5 +key3=v3 +key3=v4 +key4=v4 +key4= + +[b] +b_key1=1 +b_key2=2 + +[c] +c_key1= +c_key2=1 2 3 + 4 5 6 + +''' + + +class OverrideConfigParserTest(base.BaseTestCase): + + def test_read_write(self): + for ini in [TESTA, + TESTB, + TESTC, + TESTA_NO_SECTIONS, + TESTB_NO_SECTIONS, + TESTC_NO_SECTIONS, + TESTA_NO_DEFAULT_SECTION, + TESTB_NO_DEFAULT_SECTION, + TESTC_NO_DEFAULT_SECTION]: + parser = merge_configs.OverrideConfigParser() + parser.parse(StringIO(ini)) + output = StringIO() + parser.write(output) + self.assertEqual(ini, output.getvalue()) + output.close() + + def test_merge(self): + parser = merge_configs.OverrideConfigParser() + parser.parse(StringIO(TESTA)) + parser.parse(StringIO(TESTB)) + output = StringIO() + parser.write(output) + self.assertEqual(TESTC, output.getvalue()) + output.close() + + def test_merge_no_sections(self): + parser = merge_configs.OverrideConfigParser() + parser.parse(StringIO(TESTA_NO_SECTIONS)) + parser.parse(StringIO(TESTB_NO_SECTIONS)) + output = StringIO() + parser.write(output) + self.assertEqual(TESTC_NO_SECTIONS, output.getvalue()) + output.close() + + def test_merge_no_default_section(self): + parser = merge_configs.OverrideConfigParser() + parser.parse(StringIO(TESTA_NO_DEFAULT_SECTION)) + parser.parse(StringIO(TESTB_NO_DEFAULT_SECTION)) + output = StringIO() + parser.write(output) + self.assertEqual(TESTC_NO_DEFAULT_SECTION, output.getvalue()) + output.close() + + def test_merge_no_whitespace(self): + parser = merge_configs.OverrideConfigParser(whitespace=False) + parser.parse(StringIO(TESTA)) + parser.parse(StringIO(TESTB)) + output = StringIO() + parser.write(output) + self.assertEqual(TESTC_NO_WHITESPACE, output.getvalue()) + output.close() diff --git a/kayobe/tests/unit/plugins/action/test_merge_yaml.py b/kayobe/tests/unit/plugins/action/test_merge_yaml.py new file mode 100644 index 000000000..0701e700d --- /dev/null +++ b/kayobe/tests/unit/plugins/action/test_merge_yaml.py @@ -0,0 +1,170 @@ +#!/usr/bin/env python + +# Copyright 2018 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 AnsibleModuleError +from oslotest import base + +from kayobe.plugins.action import merge_yaml + + +class MergeYamlConfigTest(base.BaseTestCase): + + def test_merge_no_update(self): + initial_conf = { + 'foo': 'bar', + 'egg': 'spam' + } + actual = merge_yaml.Utils.update_nested_conf(initial_conf, {}) + expected = { + 'foo': 'bar', + 'egg': 'spam' + } + self.assertDictEqual(actual, expected) + + def test_merge_flat_update_key(self): + initial_conf = { + 'foo': 'bar', + 'egg': 'spam' + } + actual = merge_yaml.Utils.update_nested_conf( + initial_conf, {'egg': 'ham'}) + expected = { + 'foo': 'bar', + 'egg': 'ham' + } + self.assertDictEqual(actual, expected) + + def test_merge_flat_new_key(self): + initial_conf = { + 'foo': 'bar', + 'egg': 'spam' + } + actual = merge_yaml.Utils.update_nested_conf( + initial_conf, {'spam': 'ham'}) + expected = { + 'foo': 'bar', + 'egg': 'spam', + 'spam': 'ham' + } + self.assertDictEqual(actual, expected) + + def test_merge_nested_update_key(self): + initial_conf = { + 'foo': { + 'a': 'b', + }, + 'bar': { + 'a': False, + 'b': 'INFO' + } + } + actual = merge_yaml.Utils.update_nested_conf( + initial_conf, {'bar': {'a': True}}) + expected = { + 'foo': { + 'a': 'b', + }, + 'bar': { + 'a': True, + 'b': 'INFO' + } + } + self.assertDictEqual(actual, expected) + + def test_merge_nested_new_key(self): + initial_conf = { + 'foo': { + 'a': 'b', + 'c': 30 + } + } + actual = merge_yaml.Utils.update_nested_conf( + initial_conf, {'egg': {'spam': 10}}) + expected = { + 'foo': { + 'a': 'b', + 'c': 30, + }, + 'egg': { + 'spam': 10, + } + } + self.assertDictEqual(actual, expected) + + def test_merge_nested_new_nested_key(self): + initial_conf = { + 'foo': { + 'a': 'b', + 'c': 30 + } + } + actual = merge_yaml.Utils.update_nested_conf( + initial_conf, {'foo': {'spam': 10}}) + expected = { + 'foo': { + 'a': 'b', + 'c': 30, + 'spam': 10, + } + } + self.assertDictEqual(actual, expected) + + def test_merge_nested_extend_lists(self): + initial_conf = { + 'level0': { + 'level1': { + "mylist": ["one", "two"] + }, + } + } + + extension = { + 'level0': { + 'level1': { + "mylist": ["three"] + }, + } + } + + actual = merge_yaml.Utils.update_nested_conf( + initial_conf, extension, extend_lists=True) + expected = { + 'level0': { + 'level1': { + "mylist": ["one", "two", "three"] + }, + } + } + self.assertDictEqual(actual, expected) + + def test_merge_nested_extend_lists_mismatch_types(self): + initial_conf = { + 'level0': { + 'level1': { + "mylist": ["one", "two"] + }, + } + } + + extension = { + 'level0': { + 'level1': { + "mylist": "three" + }, + } + } + with self.assertRaisesRegex(AnsibleModuleError, "Failure merging key"): + merge_yaml.Utils.update_nested_conf( + initial_conf, extension, extend_lists=True) diff --git a/kayobe/tests/unit/test_ansible.py b/kayobe/tests/unit/test_ansible.py index 4f9d8945f..1f4a13d52 100644 --- a/kayobe/tests/unit/test_ansible.py +++ b/kayobe/tests/unit/test_ansible.py @@ -68,6 +68,7 @@ def test_run_playbooks_all_the_args(self, mock_validate, mock_vars, "-b", "-C", "--config-path", "/path/to/config", + "-D", "--environment", "test-env", "-e", "ev_name1=ev_value1", "-i", "/path/to/inventory", @@ -88,6 +89,7 @@ def test_run_playbooks_all_the_args(self, mock_validate, mock_vars, "-e", "ev_name1=ev_value1", "--become", "--check", + "--diff", "--limit", "group1:host", "--tags", "tag1,tag2", "playbook1.yml", @@ -117,6 +119,7 @@ def test_run_playbooks_all_the_long_args(self, mock_ask, mock_validate, "--become", "--check", "--config-path", "/path/to/config", + "--diff", "--environment", "test-env", "--extra-vars", "ev_name1=ev_value1", "--inventory", "/path/to/inventory", @@ -138,6 +141,7 @@ def test_run_playbooks_all_the_long_args(self, mock_ask, mock_validate, "-e", "ev_name1=ev_value1", "--become", "--check", + "--diff", "--limit", "group1:host1", "--skip-tags", "tag3,tag4", "--tags", "tag1,tag2", @@ -249,6 +253,7 @@ def test_run_playbooks_func_args(self, mock_validate, mock_vars, mock_run): "tags": "tag3,tag4", "verbose_level": 0, "check": True, + "diff": True, } ansible.run_playbooks(parsed_args, ["playbook1.yml", "playbook2.yml"], **kwargs) @@ -260,6 +265,7 @@ def test_run_playbooks_func_args(self, mock_validate, mock_vars, mock_run): "-e", "ev_name1=ev_value1", "-e", "ev_name2='ev_value2'", "--check", + "--diff", "--limit", "group1:host1:&group2:host2", "--tags", "tag1,tag2,tag3,tag4", "playbook1.yml", @@ -426,7 +432,7 @@ def test_config_dump(self, mock_mkdtemp, mock_run, mock_listdir, mock_read, }, check_output=True, tags=None, verbose_level=None, check=False, - list_tasks=False) + list_tasks=False, diff=False) mock_rmtree.assert_called_once_with(dump_dir) mock_listdir.assert_any_call(dump_dir) mock_read.assert_has_calls([ @@ -434,7 +440,7 @@ def test_config_dump(self, mock_mkdtemp, mock_run, mock_listdir, mock_read, mock.call(os.path.join(dump_dir, "host2.yml")), ]) - @mock.patch.object(utils, 'galaxy_install', autospec=True) + @mock.patch.object(utils, 'galaxy_role_install', autospec=True) @mock.patch.object(utils, 'is_readable_file', autospec=True) @mock.patch.object(os, 'makedirs', autospec=True) def test_install_galaxy_roles(self, mock_mkdirs, mock_is_readable, @@ -453,7 +459,7 @@ def test_install_galaxy_roles(self, mock_mkdirs, mock_is_readable, "/etc/kayobe/ansible/requirements.yml") self.assertFalse(mock_mkdirs.called) - @mock.patch.object(utils, 'galaxy_install', autospec=True) + @mock.patch.object(utils, 'galaxy_role_install', autospec=True) @mock.patch.object(utils, 'is_readable_file', autospec=True) @mock.patch.object(os, 'makedirs', autospec=True) def test_install_galaxy_roles_with_kayobe_config( @@ -476,7 +482,7 @@ def test_install_galaxy_roles_with_kayobe_config( "/etc/kayobe/ansible/requirements.yml") mock_mkdirs.assert_called_once_with("/etc/kayobe/ansible/roles") - @mock.patch.object(utils, 'galaxy_install', autospec=True) + @mock.patch.object(utils, 'galaxy_role_install', autospec=True) @mock.patch.object(utils, 'is_readable_file', autospec=True) @mock.patch.object(os, 'makedirs', autospec=True) def test_install_galaxy_roles_with_kayobe_config_forced( @@ -499,7 +505,7 @@ def test_install_galaxy_roles_with_kayobe_config_forced( "/etc/kayobe/ansible/requirements.yml") mock_mkdirs.assert_called_once_with("/etc/kayobe/ansible/roles") - @mock.patch.object(utils, 'galaxy_install', autospec=True) + @mock.patch.object(utils, 'galaxy_role_install', autospec=True) @mock.patch.object(utils, 'is_readable_file', autospec=True) @mock.patch.object(os, 'makedirs', autospec=True) def test_install_galaxy_roles_with_kayobe_config_mkdirs_failure( @@ -520,6 +526,92 @@ def test_install_galaxy_roles_with_kayobe_config_mkdirs_failure( "/etc/kayobe/ansible/requirements.yml") mock_mkdirs.assert_called_once_with("/etc/kayobe/ansible/roles") + @mock.patch.object(utils, 'galaxy_collection_install', autospec=True) + @mock.patch.object(utils, 'is_readable_file', autospec=True) + @mock.patch.object(os, 'makedirs', autospec=True) + def test_install_galaxy_collections(self, mock_mkdirs, mock_is_readable, + mock_install): + parser = argparse.ArgumentParser() + ansible.add_args(parser) + parsed_args = parser.parse_args([]) + mock_is_readable.return_value = {"result": False} + + ansible.install_galaxy_collections(parsed_args) + + mock_install.assert_called_once_with(utils.get_data_files_path( + "requirements.yml"), utils.get_data_files_path( + "ansible", "collections"), force=False) + mock_is_readable.assert_called_once_with( + "/etc/kayobe/ansible/requirements.yml") + self.assertFalse(mock_mkdirs.called) + + @mock.patch.object(utils, 'galaxy_collection_install', autospec=True) + @mock.patch.object(utils, 'is_readable_file', autospec=True) + @mock.patch.object(os, 'makedirs', autospec=True) + def test_install_galaxy_collections_with_kayobe_config( + self, mock_mkdirs, mock_is_readable, mock_install): + parser = argparse.ArgumentParser() + ansible.add_args(parser) + parsed_args = parser.parse_args([]) + mock_is_readable.return_value = {"result": True} + + ansible.install_galaxy_collections(parsed_args) + + expected_calls = [ + mock.call(utils.get_data_files_path("requirements.yml"), + utils.get_data_files_path("ansible", "collections"), + force=False), + mock.call("/etc/kayobe/ansible/requirements.yml", + "/etc/kayobe/ansible/collections", force=False)] + self.assertEqual(expected_calls, mock_install.call_args_list) + mock_is_readable.assert_called_once_with( + "/etc/kayobe/ansible/requirements.yml") + mock_mkdirs.assert_called_once_with("/etc/kayobe/ansible/collections") + + @mock.patch.object(utils, 'galaxy_collection_install', autospec=True) + @mock.patch.object(utils, 'is_readable_file', autospec=True) + @mock.patch.object(os, 'makedirs', autospec=True) + def test_install_galaxy_collections_with_kayobe_config_forced( + self, mock_mkdirs, mock_is_readable, mock_install): + parser = argparse.ArgumentParser() + ansible.add_args(parser) + parsed_args = parser.parse_args([]) + mock_is_readable.return_value = {"result": True} + + ansible.install_galaxy_collections(parsed_args, force=True) + + expected_calls = [ + mock.call(utils.get_data_files_path("requirements.yml"), + utils.get_data_files_path("ansible", "collections"), + force=True), + mock.call("/etc/kayobe/ansible/requirements.yml", + "/etc/kayobe/ansible/collections", force=True)] + self.assertEqual(expected_calls, mock_install.call_args_list) + mock_is_readable.assert_called_once_with( + "/etc/kayobe/ansible/requirements.yml") + mock_mkdirs.assert_called_once_with("/etc/kayobe/ansible/collections") + + @mock.patch.object(utils, 'galaxy_collection_install', autospec=True) + @mock.patch.object(utils, 'is_readable_file', autospec=True) + @mock.patch.object(os, 'makedirs', autospec=True) + def test_install_galaxy_collections_with_kayobe_config_mkdirs_failure( + self, mock_mkdirs, mock_is_readable, mock_install): + parser = argparse.ArgumentParser() + ansible.add_args(parser) + parsed_args = parser.parse_args([]) + mock_is_readable.return_value = {"result": True} + mock_mkdirs.side_effect = OSError(errno.EPERM) + + self.assertRaises(exception.Error, + ansible.install_galaxy_collections, parsed_args) + + mock_install.assert_called_once_with( + utils.get_data_files_path("requirements.yml"), + utils.get_data_files_path("ansible", "collections"), force=False) + mock_is_readable.assert_called_once_with( + "/etc/kayobe/ansible/requirements.yml") + mock_mkdirs.assert_called_once_with("/etc/kayobe/ansible/collections") + @mock.patch.object(utils, 'galaxy_remove', autospec=True) def test_prune_galaxy_roles(self, mock_remove): parser = argparse.ArgumentParser() diff --git a/kayobe/tests/unit/test_kolla_ansible.py b/kayobe/tests/unit/test_kolla_ansible.py index 3eced9484..8f9650672 100644 --- a/kayobe/tests/unit/test_kolla_ansible.py +++ b/kayobe/tests/unit/test_kolla_ansible.py @@ -54,17 +54,21 @@ def test_run_all_the_args(self, mock_validate, mock_run): kolla_ansible.add_args(parser) vault.add_args(parser) args = [ + "-C", + "-D", "--kolla-config-path", "/path/to/config", "-ke", "ev_name1=ev_value1", "-ki", "/path/to/inventory", "-kl", "host1:host2", "-kt", "tag1,tag2", + "-kp", "/path/to/playbook", ] parsed_args = parser.parse_args(args) kolla_ansible.run(parsed_args, "command", "overcloud") expected_cmd = [ ".", "/path/to/cwd/venvs/kolla-ansible/bin/activate", "&&", "kolla-ansible", "command", + "--playbook", "/path/to/playbook", "--inventory", "/path/to/inventory", "--configdir", "/path/to/config", "--passwords", "/path/to/config/passwords.yml", @@ -73,8 +77,9 @@ def test_run_all_the_args(self, mock_validate, mock_run): "--tags", "tag1,tag2", ] expected_cmd = " ".join(expected_cmd) + expected_env = {"EXTRA_OPTS": " --check --diff"} mock_run.assert_called_once_with(expected_cmd, shell=True, quiet=False, - env={}) + env=expected_env) @mock.patch.object(utils, "run_command") @mock.patch.object(kolla_ansible, "_validate_args") @@ -87,12 +92,15 @@ def test_run_all_the_long_args(self, mock_ask, mock_validate, mock_run): mock_ask.return_value = "test-pass" args = [ "--ask-vault-pass", + "--check", + "--diff", "--kolla-config-path", "/path/to/config", "--kolla-extra-vars", "ev_name1=ev_value1", "--kolla-inventory", "/path/to/inventory", "--kolla-limit", "host1:host2", "--kolla-skip-tags", "tag3,tag4", "--kolla-tags", "tag1,tag2", + "--kolla-playbook", "/path/to/playbook", ] parsed_args = parser.parse_args(args) mock_run.return_value = "/path/to/kayobe-vault-password-helper" @@ -100,6 +108,7 @@ def test_run_all_the_long_args(self, mock_ask, mock_validate, mock_run): expected_cmd = [ ".", "/path/to/cwd/venvs/kolla-ansible/bin/activate", "&&", "kolla-ansible", "command", + "--playbook", "/path/to/playbook", "--key", "/path/to/kayobe-vault-password-helper", "--inventory", "/path/to/inventory", "--configdir", "/path/to/config", @@ -110,7 +119,8 @@ def test_run_all_the_long_args(self, mock_ask, mock_validate, mock_run): "--tags", "tag1,tag2", ] expected_cmd = " ".join(expected_cmd) - expected_env = {"KAYOBE_VAULT_PASSWORD": "test-pass"} + expected_env = {"EXTRA_OPTS": " --check --diff", + "KAYOBE_VAULT_PASSWORD": "test-pass"} expected_calls = [ mock.call(["which", "kayobe-vault-password-helper"], check_output=True, universal_newlines=True), diff --git a/kayobe/tests/unit/test_utils.py b/kayobe/tests/unit/test_utils.py index 5fffdbc19..e1b4e2b02 100644 --- a/kayobe/tests/unit/test_utils.py +++ b/kayobe/tests/unit/test_utils.py @@ -26,23 +26,72 @@ class TestCase(unittest.TestCase): @mock.patch.object(utils, "run_command") - def test_galaxy_install(self, mock_run): - utils.galaxy_install("/path/to/role/file", "/path/to/roles") - mock_run.assert_called_once_with(["ansible-galaxy", "install", + def test_galaxy_role_install(self, mock_run): + utils.galaxy_role_install("/path/to/role/file", "/path/to/roles") + mock_run.assert_called_once_with(["ansible-galaxy", "role", "install", "--roles-path", "/path/to/roles", "--role-file", "/path/to/role/file"]) @mock.patch.object(utils, "run_command") - def test_galaxy_install_failure(self, mock_run): + def test_galaxy_role_install_failure(self, mock_run): mock_run.side_effect = subprocess.CalledProcessError(1, "command") self.assertRaises(SystemExit, - utils.galaxy_install, "/path/to/role/file", + utils.galaxy_role_install, "/path/to/role/file", "/path/to/roles") + @mock.patch.object(utils, "run_command") + @mock.patch.object(utils, "read_yaml_file") + def test_galaxy_collection_install(self, mock_read, mock_run): + mock_read.return_value = {"collections": []} + utils.galaxy_collection_install("/path/to/collection/file", + "/path/to/collections") + mock_run.assert_called_once_with(["ansible-galaxy", "collection", + "install", "--collections-path", + "/path/to/collections", + "--requirements-file", + "/path/to/collection/file"]) + + @mock.patch.object(utils, "run_command") + @mock.patch.object(utils, "read_yaml_file") + def test_galaxy_collection_install_failure(self, mock_read, mock_run): + mock_read.return_value = {"collections": []} + mock_run.side_effect = subprocess.CalledProcessError(1, "command") + self.assertRaises(SystemExit, + utils.galaxy_collection_install, + "/path/to/collection/file", "/path/to/collections") + + @mock.patch.object(utils, "run_command") + @mock.patch.object(utils, "read_file") + def test_galaxy_collection_read_failure(self, mock_read, mock_run): + mock_read.side_effect = IOError + self.assertRaises(SystemExit, + utils.galaxy_collection_install, + "/path/to/collection/file", "/path/to/collections") + + @mock.patch.object(utils, "run_command") + @mock.patch.object(utils, "read_yaml_file") + def test_galaxy_collection_no_collections(self, mock_read, mock_run): + mock_read.return_value = {"roles": []} + utils.galaxy_collection_install("/path/to/collection/file", + "/path/to/collections") + mock_run.assert_called_once_with(["ansible-galaxy", "collection", + "install", "--collections-path", + "/path/to/collections", + "--requirements-file", + "/path/to/collection/file"]) + + @mock.patch.object(utils, "run_command") + @mock.patch.object(utils, "read_yaml_file") + def test_galaxy_collection_legacy_format(self, mock_read, mock_run): + mock_read.return_value = [] + utils.galaxy_collection_install("/path/to/collection/file", + "/path/to/collections") + self.assertFalse(mock_run.called) + @mock.patch.object(utils, "run_command") def test_galaxy_remove(self, mock_run): utils.galaxy_remove(["role1", "role2"], "/path/to/roles") - mock_run.assert_called_once_with(["ansible-galaxy", "remove", + mock_run.assert_called_once_with(["ansible-galaxy", "role", "remove", "--roles-path", "/path/to/roles", "role1", "role2"]) @@ -50,7 +99,7 @@ def test_galaxy_remove(self, mock_run): def test_galaxy_remove_failure(self, mock_run): mock_run.side_effect = subprocess.CalledProcessError(1, "command") self.assertRaises(SystemExit, - utils.galaxy_install, ["role1", "role2"], + utils.galaxy_remove, ["role1", "role2"], "/path/to/roles") @mock.patch.object(utils, "read_file") diff --git a/kayobe/utils.py b/kayobe/utils.py index 4c4855d23..727fd783a 100644 --- a/kayobe/utils.py +++ b/kayobe/utils.py @@ -69,9 +69,9 @@ def _get_base_path(): return os.path.join(os.path.realpath(__file__), "..") -def galaxy_install(role_file, roles_path, force=False): +def galaxy_role_install(role_file, roles_path, force=False): """Install Ansible roles via Ansible Galaxy.""" - cmd = ["ansible-galaxy", "install"] + cmd = ["ansible-galaxy", "role", "install"] cmd += ["--roles-path", roles_path] cmd += ["--role-file", role_file] if force: @@ -84,10 +84,29 @@ def galaxy_install(role_file, roles_path, force=False): sys.exit(e.returncode) +def galaxy_collection_install(requirements_file, collections_path, + force=False): + requirements = read_yaml_file(requirements_file) + if not isinstance(requirements, dict): + # Handle legacy role list format, which causes the command to fail. + return + cmd = ["ansible-galaxy", "collection", "install"] + cmd += ["--collections-path", collections_path] + cmd += ["--requirements-file", requirements_file] + if force: + cmd += ["--force"] + try: + run_command(cmd) + except subprocess.CalledProcessError as e: + LOG.error("Failed to install Ansible collections from %s via Ansible " + "Galaxy: returncode %d", requirements_file, e.returncode) + sys.exit(e.returncode) + + def galaxy_remove(roles_to_remove, roles_path): """Remove Ansible roles via Ansible Galaxy.""" - cmd = ["ansible-galaxy", "remove"] + cmd = ["ansible-galaxy", "role", "remove"] cmd += ["--roles-path", roles_path] cmd += roles_to_remove try: diff --git a/playbooks/kayobe-overcloud-base/globals.yml.j2 b/playbooks/kayobe-overcloud-base/globals.yml.j2 index 03e173614..58c4c7ec9 100644 --- a/playbooks/kayobe-overcloud-base/globals.yml.j2 +++ b/playbooks/kayobe-overcloud-base/globals.yml.j2 @@ -21,4 +21,6 @@ kolla_copy_ca_into_containers: "yes" kolla_enable_tls_backend: "yes" openstack_cacert: "/etc/pki/tls/certs/ca-bundle.crt" kolla_admin_openrc_cacert: "/etc/pki/tls/certs/ca-bundle.crt" +libvirt_tls: "yes" +certificates_libvirt_output_dir: "{% raw %}{{ kayobe_env_config_path }}{% endraw %}/certificates/libvirt" {% endif %} diff --git a/playbooks/kayobe-overcloud-base/overrides.yml.j2 b/playbooks/kayobe-overcloud-base/overrides.yml.j2 index be2905b18..772654cc5 100644 --- a/playbooks/kayobe-overcloud-base/overrides.yml.j2 +++ b/playbooks/kayobe-overcloud-base/overrides.yml.j2 @@ -31,6 +31,9 @@ pip_trusted_hosts: aio_bridge_ports: - dummy1 +# Generate a password for libvirt SASL authentication. +compute_libvirt_sasl_password: "{% raw %}{{ lookup('password', '/tmp/libvirt-sasl-password') }}{% endraw %}" + # Enable ironic for testing baremetal compute. kolla_enable_ironic: true @@ -42,6 +45,9 @@ kolla_ironic_default_boot_interface: ipxe {% endif %} {% if tls_enabled %} +kolla_enable_nova_libvirt_container: false +compute_libvirt_enable_tls: true + kolla_enable_tls_external: "yes" kolla_enable_tls_internal: "yes" diff --git a/playbooks/kayobe-overcloud-base/run.yml b/playbooks/kayobe-overcloud-base/run.yml index 3d77e86aa..350b47dfd 100644 --- a/playbooks/kayobe-overcloud-base/run.yml +++ b/playbooks/kayobe-overcloud-base/run.yml @@ -3,6 +3,8 @@ environment: KAYOBE_CONFIG_SOURCE_PATH: "{{ kayobe_config_src_dir }}" KAYOBE_OVERCLOUD_GENERATE_CERTIFICATES: "{{ tls_enabled | ternary(1, 0) }}" + # TODO(mgoddard): Remove this when libvirt on host is used by default. + TENKS_CONFIG_PATH: "dev/tenks-deploy-config-compute{% if tls_enabled %}-libvirt-on-host{% endif %}.yml" tasks: - name: Ensure overcloud is deployed shell: @@ -18,8 +20,6 @@ executable: /bin/bash - name: Perform testing of the virtualized machines - # We must do this before tenks-deploy as that will stop the nova_libvirt - # container shell: cmd: dev/overcloud-test-vm.sh &> {{ logs_dir }}/ansible/overcloud-test-vm chdir: "{{ kayobe_src_dir }}" diff --git a/playbooks/kayobe-overcloud-host-configure-base/overrides.yml.j2 b/playbooks/kayobe-overcloud-host-configure-base/overrides.yml.j2 index 8a3523e9e..d94a8d74b 100644 --- a/playbooks/kayobe-overcloud-host-configure-base/overrides.yml.j2 +++ b/playbooks/kayobe-overcloud-host-configure-base/overrides.yml.j2 @@ -121,6 +121,29 @@ docker_storage_driver: devicemapper # Set Honolulu time. timezone: Pacific/Honolulu +{% if ansible_os_family == "Debian" %} +apt_config: + - content: | + Acquire::Retries 1; + filename: 99retries +apt_keys: + - url: https://packages.treasuredata.com/GPG-KEY-td-agent + filename: td-agent.asc +apt_repositories: + # Ubuntu focal repositories. + - url: "http://{{ zuul_site_mirror_fqdn }}/ubuntu/" + suites: focal focal-updates + components: main restricted universe multiverse + - url: "http://{{ zuul_site_mirror_fqdn }}/ubuntu/" + suites: focal-security + components: main restricted universe multiverse + # Treasuredata repository. + - url: http://packages.treasuredata.com/4/ubuntu/focal/ + components: contrib + signed_by: td-agent.asc +apt_disable_sources_list: true +{% endif %} + {% if ansible_os_family == 'RedHat' %} # Use a local DNF mirror. dnf_use_local_mirror: true @@ -149,3 +172,6 @@ chrony_ntp_servers: options: - option: maxsources val: 2 + +# Generate a password for libvirt SASL authentication. +compute_libvirt_sasl_password: "{% raw %}{{ lookup('password', '/tmp/libvirt-sasl-password') }}{% endraw %}" diff --git a/playbooks/kayobe-overcloud-host-configure-base/tests/test_overcloud_host_configure.py b/playbooks/kayobe-overcloud-host-configure-base/tests/test_overcloud_host_configure.py index 837cf6faa..40fc766ab 100644 --- a/playbooks/kayobe-overcloud-host-configure-base/tests/test_overcloud_host_configure.py +++ b/playbooks/kayobe-overcloud-host-configure-base/tests/test_overcloud_host_configure.py @@ -10,6 +10,11 @@ import pytest +def _is_apt(): + info = distro.linux_distribution() + return info[0].startswith('Ubuntu') + + def _is_dnf(): info = distro.linux_distribution() return info[0].startswith('CentOS') and info[1].startswith('8') @@ -188,6 +193,20 @@ def test_ntp_clock_synchronized(host): assert "synchronized: yes" in status_output +@pytest.mark.skipif(not _is_apt(), reason="Apt only supported on Ubuntu") +def test_apt_config(host): + apt_config = host.file("/etc/apt/apt.conf.d/99retries") + assert apt_config.exists + assert apt_config.content_string == "Acquire::Retries 1;\n" + + +@pytest.mark.skipif(not _is_apt(), reason="Apt only supported on Ubuntu") +def test_apt_custom_package_repository_is_available(host): + with host.sudo(): + host.check_output("apt -y install td-agent") + assert host.package("td-agent").is_installed + + @pytest.mark.parametrize('repo', ["appstream", "baseos", "extras", "epel", "epel-modular"]) @pytest.mark.skipif(not _is_dnf(), reason="DNF only supported on CentOS 8") diff --git a/playbooks/kayobe-overcloud-upgrade-base/overrides.yml.j2 b/playbooks/kayobe-overcloud-upgrade-base/overrides.yml.j2 index 3b20f69e4..d34cefce5 100644 --- a/playbooks/kayobe-overcloud-upgrade-base/overrides.yml.j2 +++ b/playbooks/kayobe-overcloud-upgrade-base/overrides.yml.j2 @@ -30,6 +30,9 @@ pip_index_url: "http://{{ zuul_site_mirror_fqdn }}/pypi/simple" aio_bridge_ports: - dummy1 +# Generate a password for libvirt SASL authentication. +compute_libvirt_sasl_password: "{% raw %}{{ lookup('password', '/tmp/libvirt-sasl-password') }}{% endraw %}" + # Enable ironic for testing baremetal compute. kolla_enable_ironic: true diff --git a/playbooks/kayobe-seed-base/overrides.yml.j2 b/playbooks/kayobe-seed-base/overrides.yml.j2 index e5d666091..82f177ece 100644 --- a/playbooks/kayobe-seed-base/overrides.yml.j2 +++ b/playbooks/kayobe-seed-base/overrides.yml.j2 @@ -36,3 +36,6 @@ aio_bridge_ports: ipa_build_images: {{ build_images }} ipa_build_dib_elements_extra: - "extra-hardware" + +# Build overcloud host image +overcloud_dib_build_host_images: true diff --git a/releasenotes/notes/add-efi-lvm-dependencies-2358e7930a32fa66.yaml b/releasenotes/notes/add-efi-lvm-dependencies-2358e7930a32fa66.yaml new file mode 100644 index 000000000..0e0ef38c3 --- /dev/null +++ b/releasenotes/notes/add-efi-lvm-dependencies-2358e7930a32fa66.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Adds dependencies for EFI and LVM based overcloud images. diff --git a/releasenotes/notes/add-option-to-skip-hooks-719ba214a6d4b773.yaml b/releasenotes/notes/add-option-to-skip-hooks-719ba214a6d4b773.yaml new file mode 100644 index 000000000..e80cea092 --- /dev/null +++ b/releasenotes/notes/add-option-to-skip-hooks-719ba214a6d4b773.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Adds the --skip-hooks argument to ignore hooks for the execution of a + command. See `story 2009241 + `_ for details. \ No newline at end of file diff --git a/releasenotes/notes/add-support-for-custom-horizon-themes-5da1d99c1b8107b9.yaml b/releasenotes/notes/add-support-for-custom-horizon-themes-5da1d99c1b8107b9.yaml new file mode 100644 index 000000000..a422a837f --- /dev/null +++ b/releasenotes/notes/add-support-for-custom-horizon-themes-5da1d99c1b8107b9.yaml @@ -0,0 +1,3 @@ +--- +features: + - Adds support for custom Horizon themes. diff --git a/releasenotes/notes/add-support-for-custom-seed-vms-a938ffdbedcd7b14.yaml b/releasenotes/notes/add-support-for-custom-seed-vms-a938ffdbedcd7b14.yaml new file mode 100644 index 000000000..63940f976 --- /dev/null +++ b/releasenotes/notes/add-support-for-custom-seed-vms-a938ffdbedcd7b14.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Adds support for deploying infrastructure VMs on the seed hypervisor. + These can be used to provide supplementary services that do not run well + within a containerised environment or are dependencies of the control + plane. See `story 2008741 + ` for details. diff --git a/releasenotes/notes/add-support-for-rootfs-uuid-42f0c551a383799b.yaml b/releasenotes/notes/add-support-for-rootfs-uuid-42f0c551a383799b.yaml new file mode 100644 index 000000000..413adbf20 --- /dev/null +++ b/releasenotes/notes/add-support-for-rootfs-uuid-42f0c551a383799b.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Adds support for setting root filesystem's UUID via a new variable + ``kolla_bifrost_deploy_image_rootfs``. This is useful when deploying + overcloud hosts with software RAID based root disk devices. diff --git a/releasenotes/notes/adds-ansible-requirement-specifier-728e3045fc448715.yaml b/releasenotes/notes/adds-ansible-requirement-specifier-728e3045fc448715.yaml new file mode 100644 index 000000000..d41ac7db9 --- /dev/null +++ b/releasenotes/notes/adds-ansible-requirement-specifier-728e3045fc448715.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Adds the ``kolla_ansible_venv_ansible`` configuration option. This allows + you to override the version of ansible installed in the kolla-ansible + virtualenv. diff --git a/releasenotes/notes/apt-config-bc72fd0bff919888.yaml b/releasenotes/notes/apt-config-bc72fd0bff919888.yaml new file mode 100644 index 000000000..a0b0f516c --- /dev/null +++ b/releasenotes/notes/apt-config-bc72fd0bff919888.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Adds support for global configuration options for Apt in files in + ``/etc/apt/apt.conf.d/`` on Ubuntu systems. See `story 2009655 + `__ for details. diff --git a/releasenotes/notes/apt-repositories-850efef70ba34946.yaml b/releasenotes/notes/apt-repositories-850efef70ba34946.yaml new file mode 100644 index 000000000..1e82c8a1e --- /dev/null +++ b/releasenotes/notes/apt-repositories-850efef70ba34946.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Adds support for configuring Apt repositories on Ubuntu hosts. See `story + 2009655 `__ for details. diff --git a/releasenotes/notes/bifrost-deploy-image-filename-716c12e71c769a27.yaml b/releasenotes/notes/bifrost-deploy-image-filename-716c12e71c769a27.yaml new file mode 100644 index 000000000..813826ca6 --- /dev/null +++ b/releasenotes/notes/bifrost-deploy-image-filename-716c12e71c769a27.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Adds a new ``kolla_bifrost_deploy_image_filename`` variable used to define + the name of the root disk image to provision. This may be used to deploy + different images on different hosts. diff --git a/releasenotes/notes/bugfix-2009080-4c3a5a8acb9de39c.yaml b/releasenotes/notes/bugfix-2009080-4c3a5a8acb9de39c.yaml new file mode 100644 index 000000000..c2bc255a2 --- /dev/null +++ b/releasenotes/notes/bugfix-2009080-4c3a5a8acb9de39c.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Setting `kolla_enable_ovn` in ``kolla.yml`` did not configure Neutron's + integration with OVN. + See `story 2009080 `__ + for details. diff --git a/releasenotes/notes/collections-b1b9a017c843dc1c.yaml b/releasenotes/notes/collections-b1b9a017c843dc1c.yaml new file mode 100644 index 000000000..b235a6049 --- /dev/null +++ b/releasenotes/notes/collections-b1b9a017c843dc1c.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Adds support for installing Ansible collections. See `story 2008391 + `__ for details. diff --git a/releasenotes/notes/dellos10-support-31e209bcdb45552a.yaml b/releasenotes/notes/dellos10-support-31e209bcdb45552a.yaml new file mode 100644 index 000000000..09f03211e --- /dev/null +++ b/releasenotes/notes/dellos10-support-31e209bcdb45552a.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Adds support for configuring Dell OS10 Switches using the `dellemc.os10 + Ansible collection `__. This is + integrated with the ``kayobe physical network configure`` command. diff --git a/releasenotes/notes/dib-host-image-4fe8b1bf078f2d27.yaml b/releasenotes/notes/dib-host-image-4fe8b1bf078f2d27.yaml new file mode 100644 index 000000000..485937990 --- /dev/null +++ b/releasenotes/notes/dib-host-image-4fe8b1bf078f2d27.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Adds support for building overcloud root disk images directly with DIB + rather than through Bifrost. This includes support for building multiple + images, each with a different configuration. See `story 2002098 + ` for details. diff --git a/releasenotes/notes/diff-mode-468e09bbb9185b50.yaml b/releasenotes/notes/diff-mode-468e09bbb9185b50.yaml new file mode 100644 index 000000000..a921d9450 --- /dev/null +++ b/releasenotes/notes/diff-mode-468e09bbb9185b50.yaml @@ -0,0 +1,11 @@ +--- +features: + - | + Adds a ``--diff`` argument to kayobe CLI commands. This is passed through + to ``ansible-playbook`` for Kayobe and Kolla Ansible playbooks, and can be + used with the ``--check`` argument to see changes that would be made to + files. +upgrade: + - | + The ``--check`` argument to kayobe CLI commands is now passed through to + Kolla Ansible playbooks. diff --git a/releasenotes/notes/firewalld-48dd2efd52c79252.yaml b/releasenotes/notes/firewalld-48dd2efd52c79252.yaml new file mode 100644 index 000000000..a6feffa4c --- /dev/null +++ b/releasenotes/notes/firewalld-48dd2efd52c79252.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Adds support for configuring a firewall via firewalld on CentOS. See `story + 2008991 `__ for details. diff --git a/releasenotes/notes/kolla-ansible-playbook-7e4418ca757e81e8.yaml b/releasenotes/notes/kolla-ansible-playbook-7e4418ca757e81e8.yaml new file mode 100644 index 000000000..77d3f0772 --- /dev/null +++ b/releasenotes/notes/kolla-ansible-playbook-7e4418ca757e81e8.yaml @@ -0,0 +1,12 @@ +--- +features: + - | + Adds support for specifying a custom playbook when running Kolla Ansible + commands via a ``--kolla-playbook`` argument. For example: + + .. code-block:: console + + kayobe overcloud service deploy --kolla-playbook /path/to/playbook.yml + + This may be used to specify a playbook that replaces or extends the default + ``site.yml`` playbook, and needs to execute in the Kolla Ansible context. diff --git a/releasenotes/notes/libvirt-on-host-ff83f12923cc1f58.yaml b/releasenotes/notes/libvirt-on-host-ff83f12923cc1f58.yaml new file mode 100644 index 000000000..a16a9cfd2 --- /dev/null +++ b/releasenotes/notes/libvirt-on-host-ff83f12923cc1f58.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Adds support for running a libvirt daemon on the host, rather than in a + container. This is done by setting ``kolla_enable_nova_libvirt_container`` + to ``false``. See `story 2009858 + `__ for details. diff --git a/releasenotes/notes/merge-configs-1f8fb3672e9be404.yaml b/releasenotes/notes/merge-configs-1f8fb3672e9be404.yaml new file mode 100644 index 000000000..e4892d520 --- /dev/null +++ b/releasenotes/notes/merge-configs-1f8fb3672e9be404.yaml @@ -0,0 +1,22 @@ +--- +features: + - | + Adds support for merging the following configuration files from the + environment-specific directory (``etc/kayobe/environments/``) + and the base directory (``etc/kayobe``). + + * ``kolla/config/bifrost/bifrost.yml`` + * ``kolla/config/bifrost/dib.yml`` + * ``kolla/config/bifrost/servers.yml`` + * ``kolla/globals.yml`` + * ``kolla/kolla-build.conf`` + + See `story 2002009 `__ + for details. +deprecations: + - | + The following variables are deprecated, in favour of using configuration + files ``kolla/globals.yml`` and ``kolla/kolla-build.conf`` respectively. + + * ``kolla_extra_globals`` + * ``kolla_bifrost_extra_globals`` diff --git a/releasenotes/notes/proxy-settings-32911948a517b35b.yaml b/releasenotes/notes/proxy-settings-32911948a517b35b.yaml new file mode 100644 index 000000000..8084c8217 --- /dev/null +++ b/releasenotes/notes/proxy-settings-32911948a517b35b.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Adds support for configuring HTTP(S) proxy settings using the + ``http_proxy``, ``https_proxy`` and ``no_proxy`` variables in + ``proxy.yml``. These variables are passed down to Kolla Ansible which uses + them to configure Docker, allowing container image pull operations and + container networking to use HTTP(S) proxies. diff --git a/releasenotes/notes/snat-rules-dst-src-14ce6ca6bec26086.yaml b/releasenotes/notes/snat-rules-dst-src-14ce6ca6bec26086.yaml new file mode 100644 index 000000000..6c71ac082 --- /dev/null +++ b/releasenotes/notes/snat-rules-dst-src-14ce6ca6bec26086.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Adds support for specifying SNAT source and destination filters. This is + useful if forwarded packets need to exit on a different interface depending + on the source or destination IP address or port. diff --git a/releasenotes/notes/story-2009277-84c381a562244fab.yaml b/releasenotes/notes/story-2009277-84c381a562244fab.yaml new file mode 100644 index 000000000..f7d5d6ad5 --- /dev/null +++ b/releasenotes/notes/story-2009277-84c381a562244fab.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fixes an issue where cached seed VM images are unnecessarily owned by root. + See `story 2009277 `__ + for details. diff --git a/releasenotes/notes/support-rockylinux-8-1da50e2f97b918d5.yaml b/releasenotes/notes/support-rockylinux-8-1da50e2f97b918d5.yaml new file mode 100644 index 000000000..35ea22016 --- /dev/null +++ b/releasenotes/notes/support-rockylinux-8-1da50e2f97b918d5.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Adds support for Rocky Linux 8 as Host OS. diff --git a/releasenotes/notes/yum-repository-exclude-includepkgs-6e7b6b988f1f9a9d.yaml b/releasenotes/notes/yum-repository-exclude-includepkgs-6e7b6b988f1f9a9d.yaml new file mode 100644 index 000000000..5103de375 --- /dev/null +++ b/releasenotes/notes/yum-repository-exclude-includepkgs-6e7b6b988f1f9a9d.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Adds support for the ``exclude`` and ``includepkgs`` options in custom DNF + repositories configured with ``dnf_custom_repos`` in ``dnf.yml``. See + `documentation of the yum_repository Ansible module + `__ + for usage. diff --git a/requirements.txt b/requirements.txt index 43ec91035..ec56364a6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,11 @@ pbr>=2.0 # Apache-2.0 ansible>=2.9.0,<2.11.0,!=2.9.8,!=2.9.12 # GPLv3 +ansible-base@git+https://github.com/stackhpc/ansible@stackhpc/2.10/rocky # GPLv3 Jinja2<3.1.0 # BSD cliff>=3.1.0 # Apache netaddr!=0.7.16,>=0.7.13 # BSD PyYAML>=3.10.0 # MIT selinux # MIT +# INI parsing +oslo.config>=5.2.0 # Apache-2.0 +paramiko # LGPL diff --git a/requirements.yml b/requirements.yml index 365da7589..1a6cbd1f3 100644 --- a/requirements.yml +++ b/requirements.yml @@ -1,46 +1,53 @@ --- -- src: ahuffman.resolv - version: 1.3.1 -- src: stackhpc.systemd_networkd - version: v1.0.1 -- src: jriguera.configdrive - # There are no versioned releases of this role. - version: 29871bf3279ef95fc8f7339b9abd13f869980750 -- src: MichaelRigart.interfaces - version: v1.12.0 -- src: mrlesmithjr.chrony - version: v0.1.4 -- src: mrlesmithjr.manage_lvm - version: v0.2.6 -- src: mrlesmithjr.mdadm - version: v0.1.1 -- src: singleplatform-eng.users - version: v1.2.5 -- src: stackhpc.dell-powerconnect-switch - version: v1.1.0 -- src: stackhpc.drac - version: 1.1.6 -- src: stackhpc.drac-facts - version: 1.0.0 -- src: stackhpc.grafana-conf - version: 1.1.1 -- src: stackhpc.libvirt-host - version: v1.8.3 -- src: stackhpc.libvirt-vm - version: v1.14.2 -- src: stackhpc.luks - version: 0.4.1 -- src: stackhpc.mellanox-switch - version: v1.0.0 -- src: stackhpc.os-images - version: v1.10.7 -- src: stackhpc.os-ironic-state - version: v1.3.1 -- src: stackhpc.os-networks - version: v1.5.3 -- src: stackhpc.os-openstackclient - version: v1.4.1 -- src: stackhpc.os_openstacksdk - version: v1.0.1 -- src: stackhpc.timezone - version: 1.2.1 +collections: + - name: ansible.netcommon + version: <2.6 + - name: dellemc.os10 + version: 1.1.1 + +roles: + - src: ahuffman.resolv + version: 1.3.1 + - src: stackhpc.systemd_networkd + version: v1.0.1 + - src: jriguera.configdrive + # There are no versioned releases of this role. + version: 29871bf3279ef95fc8f7339b9abd13f869980750 + - src: MichaelRigart.interfaces + version: v1.12.0 + - src: mrlesmithjr.chrony + version: v0.1.4 + - src: mrlesmithjr.manage_lvm + version: v0.2.6 + - src: mrlesmithjr.mdadm + version: v0.1.1 + - src: singleplatform-eng.users + version: v1.2.5 + - src: stackhpc.dell-powerconnect-switch + version: v1.1.0 + - src: stackhpc.drac + version: 1.1.6 + - src: stackhpc.drac-facts + version: 1.0.0 + - src: stackhpc.grafana-conf + version: 1.1.1 + - src: stackhpc.libvirt-host + version: v1.11.0 + - src: stackhpc.libvirt-vm + version: v1.14.2 + - src: stackhpc.luks + version: 0.4.1 + - src: stackhpc.mellanox-switch + version: v1.0.0 + - src: stackhpc.os-images + version: v1.11.0 + - src: stackhpc.os-ironic-state + version: v1.3.1 + - src: stackhpc.os-networks + version: v1.5.3 + - src: stackhpc.os-openstackclient + version: v1.4.1 + - src: stackhpc.os_openstacksdk + version: v1.0.1 + - src: stackhpc.timezone + version: 1.2.1 diff --git a/roles/kayobe-diagnostics/files/get_logs.sh b/roles/kayobe-diagnostics/files/get_logs.sh index d9cdc376d..b8d2d66f3 100644 --- a/roles/kayobe-diagnostics/files/get_logs.sh +++ b/roles/kayobe-diagnostics/files/get_logs.sh @@ -101,6 +101,12 @@ copy_logs() { cp /opt/kayobe/images/ipa/ipa.stderr /opt/kayobe/images/ipa/ipa.stdout ${LOG_DIR}/kayobe/ fi + # Overcloud host image build logs + if [[ -f /opt/kayobe/images/deployment_image/deployment_image.stderr ]] || [[ -f /opt/kayobe/images/deployment_image/deployment_image.stdout ]]; then + mkdir -p ${LOG_DIR}/kayobe + cp /opt/kayobe/images/deployment_image/deployment_image.stderr /opt/kayobe/images/deployment_image/deployment_image.stdout ${LOG_DIR}/kayobe/ + fi + # Rename files to .txt; this is so that when displayed via # logs.openstack.org clicking results in the browser shows the # files, rather than trying to send it to another app or make you diff --git a/setup.cfg b/setup.cfg index 708ea8191..0c9621ba4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -61,6 +61,7 @@ kayobe.cli= overcloud_facts_gather = kayobe.cli.commands:OvercloudFactsGather overcloud_hardware_inspect = kayobe.cli.commands:OvercloudHardwareInspect overcloud_host_configure = kayobe.cli.commands:OvercloudHostConfigure + overcloud_host_image_build = kayobe.cli.commands:OvercloudHostImageBuild overcloud_host_package_update = kayobe.cli.commands:OvercloudHostPackageUpdate overcloud_host_command_run = kayobe.cli.commands:OvercloudHostCommandRun overcloud_host_upgrade = kayobe.cli.commands:OvercloudHostUpgrade @@ -94,6 +95,13 @@ kayobe.cli= seed_service_upgrade = kayobe.cli.commands:SeedServiceUpgrade seed_vm_deprovision = kayobe.cli.commands:SeedVMDeprovision seed_vm_provision = kayobe.cli.commands:SeedVMProvision + infra_vm_deprovision = kayobe.cli.commands:InfraVMDeprovision + infra_vm_provision = kayobe.cli.commands:InfraVMProvision + infra_vm_host_configure = kayobe.cli.commands:InfraVMHostConfigure + infra_vm_host_upgrade = kayobe.cli.commands:InfraVMHostUpgrade + infra_vm_host_command_run = kayobe.cli.commands:InfraVMHostCommandRun + infra_vm_host_package_update = kayobe.cli.commands:InfraVMHostPackageUpdate + infra_vm_service_deploy = kayobe.cli.commands:InfraVMServiceDeploy kayobe.cli.baremetal_compute_inspect = hooks = kayobe.cli.commands:HookDispatcher @@ -139,6 +147,8 @@ kayobe.cli.overcloud_hardware_inspect = hooks = kayobe.cli.commands:HookDispatcher kayobe.cli.overcloud_host_configure = hooks = kayobe.cli.commands:HookDispatcher +kayobe.cli.overcloud_host_image_build = + hooks = kayobe.cli.commands:HookDispatcher kayobe.cli.overcloud_host_package_update = hooks = kayobe.cli.commands:HookDispatcher kayobe.cli.overcloud_host_command_run = @@ -205,3 +215,17 @@ kayobe.cli.seed_vm_deprovision = hooks = kayobe.cli.commands:HookDispatcher kayobe.cli.seed_vm_provision = hooks = kayobe.cli.commands:HookDispatcher +kayobe.cli.infra_vm_deprovision = + hooks = kayobe.cli.commands:HookDispatcher +kayobe.cli.infra_vm_provision = + hooks = kayobe.cli.commands:HookDispatcher +kayobe.cli.infra_vm_host_configure = + hooks = kayobe.cli.commands:HookDispatcher +kayobe.cli.infra_vm_host_upgrade = + hooks = kayobe.cli.commands:HookDispatcher +kayobe.cli.infra_vm_host_command_run = + hooks = kayobe.cli.commands:HookDispatcher +kayobe.cli.infra_vm_host_package_update = + hooks = kayobe.cli.commands:HookDispatcher +kayobe.cli.infra_vm_service_deploy = + hooks = kayobe.cli.commands:HookDispatcher diff --git a/tools/test-ansible.sh b/tools/test-ansible.sh index 93acd43e4..62f1904cf 100755 --- a/tools/test-ansible.sh +++ b/tools/test-ansible.sh @@ -6,6 +6,7 @@ set -e failed=0 +export ANSIBLE_ACTION_PLUGINS="kayobe/plugins/action:~/.ansible/plugins/action:/usr/share/ansible/plugins/action" for playbook in ansible/roles/*/tests/main.yml; do # We declare extra variables to install the {{ openstack_branch }} version # of kolla-ansible. We should use {{ kolla_ansible_source_version }}, but