From 01bdc0b3f1ac7c6a7993c97ef99e1fe7ee8b2953 Mon Sep 17 00:00:00 2001 From: Will Szumski Date: Mon, 29 Mar 2021 12:24:44 +0200 Subject: [PATCH 01/43] Add support for infrastructure VMs This change allows you to define additional VMs to deploy on the seed-hypervisor. Co-authored-by: Piotr Parczewski Co-authored-by: Will Szumski Co-authored-by: Mark Goddard Story: 2008741 Task: 42095 Change-Id: I8055fc5eb0a9edadcb35767303c659922f2d07ca --- ansible/dev-tools.yml | 2 +- ansible/disable-cloud-init.yml | 2 +- ansible/disable-glean.yml | 2 +- ansible/disable-selinux.yml | 2 +- ansible/dnf.yml | 2 +- ansible/group_vars/all/infra-vms | 173 +++++++++++++++++ ansible/group_vars/infra-vms/ansible-host | 3 + ansible/group_vars/infra-vms/ansible-user | 7 + ansible/group_vars/infra-vms/luks | 6 + ansible/group_vars/infra-vms/lvm | 6 + ansible/group_vars/infra-vms/mdadm | 6 + ansible/group_vars/infra-vms/network | 6 + ansible/group_vars/infra-vms/sysctl | 3 + ansible/group_vars/infra-vms/users | 4 + ansible/host-command-run.yml | 2 +- ansible/host-package-update.yml | 2 +- ansible/infra-vm-deprovision.yml | 38 ++++ ansible/infra-vm-provision.yml | 44 +++++ ansible/ip-allocation.yml | 2 +- ansible/kayobe-ansible-user.yml | 2 +- ansible/kayobe-target-venv.yml | 2 +- ansible/luks.yml | 2 +- ansible/lvm.yml | 2 +- ansible/mdadm.yml | 2 +- ansible/network.yml | 2 +- ansible/pip.yml | 2 +- ansible/roles/infra-vms/defaults/main.yml | 7 + ansible/roles/infra-vms/tasks/deploy.yml | 73 +++++++ ansible/roles/infra-vms/tasks/destroy.yml | 16 ++ ansible/roles/infra-vms/tasks/main.yml | 18 ++ .../roles/infra-vms/tasks/prerequisites.yml | 18 ++ ansible/sysctl.yml | 2 +- ansible/time.yml | 2 +- ansible/users.yml | 2 +- doc/source/administration/index.rst | 1 + doc/source/administration/infra-vms.rst | 79 ++++++++ doc/source/configuration/reference/index.rst | 1 + .../configuration/reference/infra-vms.rst | 97 ++++++++++ .../configuration/reference/network.rst | 2 + doc/source/custom-ansible-playbooks.rst | 2 + doc/source/deployment.rst | 112 ++++++++++- etc/kayobe/infra-vms.yml | 146 ++++++++++++++ etc/kayobe/inventory/groups | 10 + kayobe/cli/commands.py | 165 ++++++++++++++++ kayobe/tests/unit/cli/test_commands.py | 183 ++++++++++++++++++ ...-for-custom-seed-vms-a938ffdbedcd7b14.yaml | 8 + setup.cfg | 21 ++ 47 files changed, 1272 insertions(+), 19 deletions(-) create mode 100644 ansible/group_vars/all/infra-vms create mode 100644 ansible/group_vars/infra-vms/ansible-host create mode 100644 ansible/group_vars/infra-vms/ansible-user create mode 100644 ansible/group_vars/infra-vms/luks create mode 100644 ansible/group_vars/infra-vms/lvm create mode 100644 ansible/group_vars/infra-vms/mdadm create mode 100644 ansible/group_vars/infra-vms/network create mode 100644 ansible/group_vars/infra-vms/sysctl create mode 100644 ansible/group_vars/infra-vms/users create mode 100644 ansible/infra-vm-deprovision.yml create mode 100644 ansible/infra-vm-provision.yml create mode 100644 ansible/roles/infra-vms/defaults/main.yml create mode 100644 ansible/roles/infra-vms/tasks/deploy.yml create mode 100644 ansible/roles/infra-vms/tasks/destroy.yml create mode 100644 ansible/roles/infra-vms/tasks/main.yml create mode 100644 ansible/roles/infra-vms/tasks/prerequisites.yml create mode 100644 doc/source/administration/infra-vms.rst create mode 100644 doc/source/configuration/reference/infra-vms.rst create mode 100644 etc/kayobe/infra-vms.yml create mode 100644 releasenotes/notes/add-support-for-custom-seed-vms-a938ffdbedcd7b14.yaml 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/group_vars/all/infra-vms b/ansible/group_vars/all/infra-vms new file mode 100644 index 000000000..0d197791d --- /dev/null +++ b/ansible/group_vars/all/infra-vms @@ -0,0 +1,173 @@ +--- +############################################################################### +# 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 +# "https://cloud.centos.org/centos/8-stream/x86_64/images/CentOS-Stream-GenericCloud-8-20210210.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 + {%- 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 }}" 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/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/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 3e68bb3c8..f5e924c26 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..35eba3fa4 --- /dev/null +++ b/ansible/infra-vm-provision.yml @@ -0,0 +1,44 @@ +--- + +- 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 a connection to VM with bootstrap user + wait_for_connection: + # NOTE: Ensure we exceed the 5 minute DHCP timeout of the eth0 + # interface if necessary. + timeout: 600 + vars: + # NOTE(wszumski): ansible_host_key_checking variable doesn't seem to + # work, But it would be nice not to fail if the host_key changes. + # We check the hostkey during host configure. + # https://github.com/ansible/ansible/blob/1c34492413dec09711c430745034db0c108227a9/lib/ansible/plugins/connection/ssh.py#L49 + # https://github.com/ansible/ansible/issues/49254 + ansible_ssh_extra_args: '{{ infra_vm_wait_connection_ssh_extra_args }}' + ansible_user: "{{ bootstrap_user }}" diff --git a/ansible/ip-allocation.yml b/ansible/ip-allocation.yml index 405345a5a..dd31918eb 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 68f716088..5ca11cf02 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 diff --git a/ansible/kayobe-target-venv.yml b/ansible/kayobe-target-venv.yml index b18acf9c6..1d0709d53 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/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 5ca9f8a9c..4403b9d56 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.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/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/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..71268061b --- /dev/null +++ b/ansible/roles/infra-vms/tasks/deploy.yml @@ -0,0 +1,73 @@ +--- + +- 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 + +- 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/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/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/index.rst b/doc/source/configuration/reference/index.rst index 380144bce..2085025a1 100644 --- a/doc/source/configuration/reference/index.rst +++ b/doc/source/configuration/reference/index.rst @@ -21,4 +21,5 @@ options. 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 dcfe6b3e6..7b868501a 100644 --- a/doc/source/configuration/reference/network.rst +++ b/doc/source/configuration/reference/network.rst @@ -390,6 +390,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/custom-ansible-playbooks.rst b/doc/source/custom-ansible-playbooks.rst index 394904c1f..6c2c791d0 100644 --- a/doc/source/custom-ansible-playbooks.rst +++ b/doc/source/custom-ansible-playbooks.rst @@ -127,6 +127,8 @@ Then, to run the ``foo.yml`` playbook:: (kayobe) $ kayobe playbook run $KAYOBE_CONFIG_PATH/ansible/foo.yml +.. _custom-playbooks-hooks: + Hooks ===== diff --git a/doc/source/deployment.rst b/doc/source/deployment.rst index b83324166..b1d28edcc 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 ------------------ @@ -184,7 +188,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 -------------------------- @@ -236,6 +240,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/etc/kayobe/infra-vms.yml b/etc/kayobe/infra-vms.yml new file mode 100644 index 000000000..e5762b161 --- /dev/null +++ b/etc/kayobe/infra-vms.yml @@ -0,0 +1,146 @@ +--- +############################################################################### +# 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 +# "https://cloud.centos.org/centos/8-stream/x86_64/images/CentOS-Stream-GenericCloud-8-20210210.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: + +############################################################################### +# 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/kayobe/cli/commands.py b/kayobe/cli/commands.py index 38afdc6fe..dba7eab7f 100644 --- a/kayobe/cli/commands.py +++ b/kayobe/cli/commands.py @@ -821,6 +821,171 @@ 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 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. + * 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", + "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", + "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. diff --git a/kayobe/tests/unit/cli/test_commands.py b/kayobe/tests/unit/cli/test_commands.py index 4e2a0f55b..1f8f082ac 100644 --- a/kayobe/tests/unit/cli/test_commands.py +++ b/kayobe/tests/unit/cli/test_commands.py @@ -906,6 +906,189 @@ 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", "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", "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, 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/setup.cfg b/setup.cfg index 282c192e7..40842d1c7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -93,6 +93,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 @@ -202,3 +209,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 From 7eb50cfcf72156df1acfcbc3472ccf3cfdf3794b Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Tue, 22 Jun 2021 15:10:50 +0000 Subject: [PATCH 02/43] Support Ansible collections This change adds support for installing Ansible collections via requirements.yml in Kayobe or Kayobe config. Story: 2008391 Task: 41315 Change-Id: I764ff019a18266b593add7ab80ee095d7d07a869 (cherry picked from commit 5535832c100fb73492d8b4f9b5e611e3b94a2fe1) --- doc/source/contributor/development.rst | 34 +++---- doc/source/custom-ansible-playbooks.rst | 62 ++++++++++-- kayobe/ansible.py | 48 +++++++++- kayobe/cli/commands.py | 4 +- kayobe/tests/unit/cli/test_commands.py | 23 +++-- kayobe/tests/unit/test_ansible.py | 94 ++++++++++++++++++- kayobe/tests/unit/test_utils.py | 63 +++++++++++-- kayobe/utils.py | 25 ++++- .../notes/collections-b1b9a017c843dc1c.yaml | 5 + requirements.yml | 91 +++++++++--------- 10 files changed, 354 insertions(+), 95 deletions(-) create mode 100644 releasenotes/notes/collections-b1b9a017c843dc1c.yaml 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 6c2c791d0..7941a0f79 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,45 @@ 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 diff --git a/kayobe/ansible.py b/kayobe/ansible.py index 6dbc1eb86..69e71b615 100644 --- a/kayobe/ansible.py +++ b/kayobe/ansible.py @@ -291,7 +291,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 +300,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 +323,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 dba7eab7f..7494be418 100644 --- a/kayobe/cli/commands.py +++ b/kayobe/cli/commands.py @@ -232,6 +232,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 +272,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") diff --git a/kayobe/tests/unit/cli/test_commands.py b/kayobe/tests/unit/cli/test_commands.py index 1f8f082ac..e4fc92790 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( diff --git a/kayobe/tests/unit/test_ansible.py b/kayobe/tests/unit/test_ansible.py index 4f9d8945f..55da77a2a 100644 --- a/kayobe/tests/unit/test_ansible.py +++ b/kayobe/tests/unit/test_ansible.py @@ -434,7 +434,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 +453,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 +476,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 +499,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 +520,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_utils.py b/kayobe/tests/unit/test_utils.py index 9afe62dad..85e983374 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 deaac12c4..2ded367c4 100644 --- a/kayobe/utils.py +++ b/kayobe/utils.py @@ -72,9 +72,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: @@ -87,10 +87,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/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/requirements.yml b/requirements.yml index de29f1049..2c6e8707d 100644 --- a/requirements.yml +++ b/requirements.yml @@ -1,46 +1,47 @@ --- -- 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: e12d38378ae127c9c61d170fa4ba4729f2c5f2ad -- src: MichaelRigart.interfaces - version: v1.12.0 -- src: mrlesmithjr.chrony - version: v0.1.1 -- src: mrlesmithjr.manage-lvm - version: v0.2.2 -- 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.5 -- 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 +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: e12d38378ae127c9c61d170fa4ba4729f2c5f2ad + - src: MichaelRigart.interfaces + version: v1.12.0 + - src: mrlesmithjr.chrony + version: v0.1.1 + - src: mrlesmithjr.manage-lvm + version: v0.2.2 + - 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.5 + - 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 From c98ba456433ba3e515081ca378d635e5f88a4965 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Wed, 6 Oct 2021 15:07:27 +0100 Subject: [PATCH 03/43] Drop become in stackhpc.libvirt-vm for seed vm provision Prior to this change, the seed VM was provisioned using the stackhpc.livirt-vm role with become=true. This resulted in the cached image being owned by root. The infra VM provisioning uses stackhpc.libvirt-vm without become=true. If an infra VM uses the same image as the seed, this can lead to permission denied errors when downloading a new image of the same name. This change adds a workaround to fix up the ownership of the cached image during infra VM provisioning to avoid this issue. This change also drops become=true from stackhpc.libvirt-vm during seed VM provisioning, and adds the same workaround there. Story: 2009277 Task: 43534 Change-Id: Iade0d74cdb398365a567dbdc4b23de2416f3726d (cherry picked from commit 50e04bb06fe27c0d0e39adda7b8ac4dddc428581) --- ansible/roles/infra-vms/tasks/deploy.yml | 20 ++++++++++++++++++ ansible/seed-vm-provision.yml | 21 ++++++++++++++++++- .../notes/story-2009277-84c381a562244fab.yaml | 6 ++++++ 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/story-2009277-84c381a562244fab.yaml diff --git a/ansible/roles/infra-vms/tasks/deploy.yml b/ansible/roles/infra-vms/tasks/deploy.yml index 71268061b..e51991786 100644 --- a/ansible/roles/infra-vms/tasks/deploy.yml +++ b/ansible/roles/infra-vms/tasks/deploy.yml @@ -49,6 +49,26 @@ 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 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/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. From e28e358fbf77c244c3b4f426687dbe5d39139099 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Fri, 8 Oct 2021 16:52:19 +0100 Subject: [PATCH 04/43] infra VMs: use wait_for rather than wait_for_connection wait_for_connection requires a python interpreter to be available, which may not be the case in some images (CentOS Stream cloud images, cirros etc.). Instead, use wait_for to wait for the SSH port to open, then rely on the bootstrapping process to install an interpreter. This is the same method used for the seed VM. This change also syncs the SSH wait timeout for infra VMs which the one used for seed VM provisioning (360 seconds). Change-Id: I758aff1d3ef714f1c8ef82d29dd2217734a9aae6 (cherry picked from commit b87d89aae39cceedd4f70b447cddf5ada615bd36) --- ansible/infra-vm-provision.yml | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/ansible/infra-vm-provision.yml b/ansible/infra-vm-provision.yml index 35eba3fa4..28d4e3253 100644 --- a/ansible/infra-vm-provision.yml +++ b/ansible/infra-vm-provision.yml @@ -29,16 +29,12 @@ tags: - infra-vm-provision tasks: - - name: Wait for a connection to VM with bootstrap user - wait_for_connection: + - 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: 600 - vars: - # NOTE(wszumski): ansible_host_key_checking variable doesn't seem to - # work, But it would be nice not to fail if the host_key changes. - # We check the hostkey during host configure. - # https://github.com/ansible/ansible/blob/1c34492413dec09711c430745034db0c108227a9/lib/ansible/plugins/connection/ssh.py#L49 - # https://github.com/ansible/ansible/issues/49254 - ansible_ssh_extra_args: '{{ infra_vm_wait_connection_ssh_extra_args }}' - ansible_user: "{{ bootstrap_user }}" + timeout: 360 + delegate_to: localhost From e26a3b20593c1fae355f8384aaee6f4323187ec3 Mon Sep 17 00:00:00 2001 From: Pierre Riteau Date: Fri, 26 Nov 2021 09:28:00 +0100 Subject: [PATCH 05/43] Build overcloud host image directly with DIB As a first step towards supporting multiple overcloud disk images, this change introduces a new command to build a disk image directly with DIB: `kayobe overcloud host image build`. It also disables building a root disk image during Bifrost bootstrap if overcloud_dib_build_host_images is set to true. Change-Id: I93d242889e225b4e60254f6b9cc5eeb457294ac8 Story: 2002098 Task: 41693 --- ansible/group_vars/all/overcloud-dib | 54 +++++++++++++++++++ ansible/overcloud-host-image-build.yml | 51 ++++++++++++++++++ .../roles/kolla-bifrost/templates/dib.yml.j2 | 7 +++ ansible/seed-ipa-build.yml | 4 +- dev/functions | 12 +++++ etc/kayobe/overcloud-dib.yml | 50 +++++++++++++++++ kayobe/cli/commands.py | 25 +++++++++ kayobe/tests/unit/cli/test_commands.py | 44 +++++++++++++++ playbooks/kayobe-seed-base/overrides.yml.j2 | 5 +- roles/kayobe-diagnostics/files/get_logs.sh | 6 +++ setup.cfg | 3 ++ 11 files changed, 258 insertions(+), 3 deletions(-) create mode 100644 ansible/group_vars/all/overcloud-dib create mode 100644 ansible/overcloud-host-image-build.yml create mode 100644 etc/kayobe/overcloud-dib.yml diff --git a/ansible/group_vars/all/overcloud-dib b/ansible/group_vars/all/overcloud-dib new file mode 100644 index 000000000..0f8d76f5b --- /dev/null +++ b/ansible/group_vars/all/overcloud-dib @@ -0,0 +1,54 @@ +--- +# 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 False. This will change in a future release. +overcloud_dib_build_host_images: False + +# DIB base OS element. Default is {{ os_distribution }}. +overcloud_dib_os_element: "{{ 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 ["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 == 'centos' %}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_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_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: [] diff --git a/ansible/overcloud-host-image-build.yml b/ansible/overcloud-host-image-build.yml new file mode 100644 index 000000000..1855607cc --- /dev/null +++ b/ansible/overcloud-host-image-build.yml @@ -0,0 +1,51 @@ +--- +# Build and install a overcloud host disk image for the seed host's ironic +# service. + +- name: Ensure overcloud host disk image is built and installed + hosts: seed + tags: + - overcloud-host-image-build + vars: + overcloud_host_image_name: "deployment_image" + overcloud_host_disk_images: + - "{{ overcloud_host_image_name }}.qcow2" + overcloud_host_image_force_rebuild: False + tasks: + - block: + - name: Ensure overcloud host disk image is 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: "{{ pip_upper_constraints_file }}" + os_images_cache: "{{ image_cache_path }}" + os_images_common: "" + os_images_list: + - name: "{{ overcloud_host_image_name }}" + elements: "{{ overcloud_dib_elements }}" + env: "{{ overcloud_dib_env_vars }}" + packages: "{{ overcloud_dib_packages }}" + type: qcow2 + os_images_upload: False + os_images_force_rebuild: "{{ overcloud_host_image_force_rebuild }}" + + - name: Ensure overcloud host disk image is copied onto seed + copy: + src: "{{ image_cache_path }}/{{ overcloud_host_image_name }}/{{ item }}" + dest: "/etc/kolla/bifrost/{{ item }}" + remote_src: True + with_items: "{{ overcloud_host_disk_images }}" + become: True + + - name: Copy overcloud host disk image into /httpboot + command: > + docker exec bifrost_deploy + 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"' + with_items: "{{ overcloud_host_disk_images }}" + when: overcloud_dib_build_host_images | bool diff --git a/ansible/roles/kolla-bifrost/templates/dib.yml.j2 b/ansible/roles/kolla-bifrost/templates/dib.yml.j2 index e3456414f..a4fe0522b 100644 --- a/ansible/roles/kolla-bifrost/templates/dib.yml.j2 +++ b/ansible/roles/kolla-bifrost/templates/dib.yml.j2 @@ -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/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/dev/functions b/dev/functions index 66e4e65d7..2dcfe1787 100644 --- a/dev/functions +++ b/dev/functions @@ -210,6 +210,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 +312,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 { diff --git a/etc/kayobe/overcloud-dib.yml b/etc/kayobe/overcloud-dib.yml new file mode 100644 index 000000000..41bce702a --- /dev/null +++ b/etc/kayobe/overcloud-dib.yml @@ -0,0 +1,50 @@ +--- +# 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 False. This will change in a future release. +#overcloud_dib_build_host_images: + +# DIB base OS element. Default is {{ 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 ["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_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: + +############################################################################### +# Dummy variable to allow Ansible to accept this file. +workaround_ansible_issue_8743: yes diff --git a/kayobe/cli/commands.py b/kayobe/cli/commands.py index 7494be418..e6278fd16 100644 --- a/kayobe/cli/commands.py +++ b/kayobe/cli/commands.py @@ -1748,6 +1748,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/tests/unit/cli/test_commands.py b/kayobe/tests/unit/cli/test_commands.py index e4fc92790..9478103c3 100644 --- a/kayobe/tests/unit/cli/test_commands.py +++ b/kayobe/tests/unit/cli/test_commands.py @@ -1960,6 +1960,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): diff --git a/playbooks/kayobe-seed-base/overrides.yml.j2 b/playbooks/kayobe-seed-base/overrides.yml.j2 index 0881ebff6..5c9c948c2 100644 --- a/playbooks/kayobe-seed-base/overrides.yml.j2 +++ b/playbooks/kayobe-seed-base/overrides.yml.j2 @@ -35,4 +35,7 @@ aio_bridge_ports: # Build seed deployment images (IPA) with extra-hardware element ipa_build_images: true ipa_build_dib_elements_extra: - - "extra-hardware" \ No newline at end of file + - "extra-hardware" + +# Build overcloud host image +overcloud_dib_build_host_images: true diff --git a/roles/kayobe-diagnostics/files/get_logs.sh b/roles/kayobe-diagnostics/files/get_logs.sh index 7d955d428..5a36fd630 100644 --- a/roles/kayobe-diagnostics/files/get_logs.sh +++ b/roles/kayobe-diagnostics/files/get_logs.sh @@ -100,6 +100,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 40842d1c7..32016f943 100644 --- a/setup.cfg +++ b/setup.cfg @@ -60,6 +60,7 @@ kayobe.cli= overcloud_deprovision = kayobe.cli.commands:OvercloudDeprovision 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 @@ -143,6 +144,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 = From 75594c7d058e31e8474a6329797683a45bc8a471 Mon Sep 17 00:00:00 2001 From: Piotr Parczewski Date: Tue, 30 Nov 2021 15:40:52 +0100 Subject: [PATCH 06/43] Add dependencies for EFI and LVM based overcloud images Change-Id: I2c2378ebd99ac02586518e80e4b86a9d765476e6 --- .../notes/add-efi-lvm-dependencies-2358e7930a32fa66.yaml | 4 ++++ requirements.yml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/add-efi-lvm-dependencies-2358e7930a32fa66.yaml 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/requirements.yml b/requirements.yml index 2c6e8707d..bc04817ab 100644 --- a/requirements.yml +++ b/requirements.yml @@ -34,7 +34,7 @@ roles: - src: stackhpc.mellanox-switch version: v1.0.0 - src: stackhpc.os-images - version: v1.10.7 + version: v1.11.0 - src: stackhpc.os-ironic-state version: v1.3.1 - src: stackhpc.os-networks From 807d4fb9a50fd7ec10bab95ec9df0dc9b28c485d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Nasiadka?= Date: Fri, 18 Jun 2021 14:10:27 +0200 Subject: [PATCH 07/43] kolla: Set neutron_plugin_agent to OVN when it's enabled kolla_enable_ovn currently just builds OVN images and deploys OVN without configuring Neutron to use OVN. Story: 2009080 Task: 42892 Change-Id: Ib756630d50cb9cf89342f32bf1d4e07df31b1a62 --- ansible/roles/kolla-ansible/templates/globals.yml.j2 | 2 +- releasenotes/notes/bugfix-2009080-4c3a5a8acb9de39c.yaml | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/bugfix-2009080-4c3a5a8acb9de39c.yaml diff --git a/ansible/roles/kolla-ansible/templates/globals.yml.j2 b/ansible/roles/kolla-ansible/templates/globals.yml.j2 index 10b809a10..b549adc5b 100644 --- a/ansible/roles/kolla-ansible/templates/globals.yml.j2 +++ b/ansible/roles/kolla-ansible/templates/globals.yml.j2 @@ -114,7 +114,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" 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. From 5dc996dd1265dbba1ab180cb2ebe364a31769f2c Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Fri, 18 Jun 2021 11:26:58 +0100 Subject: [PATCH 08/43] Support configuration of firewalld Adds support for configuring firewalld for CentOS hosts managed by Kayobe. * create zones * set default zone * set zone for interfaces * define rules Change-Id: Id60e25e129e323f3c07e702bb81a11efc530fb3e Story: 2008991 Task: 42644 --- ansible/firewall.yml | 12 +++ ansible/group_vars/all/compute | 21 +++++ ansible/group_vars/all/controllers | 21 +++++ ansible/group_vars/all/monitoring | 21 +++++ ansible/group_vars/all/seed | 21 +++++ ansible/group_vars/all/seed-hypervisor | 21 +++++ ansible/group_vars/all/storage | 21 +++++ ansible/group_vars/compute/firewall | 21 +++++ ansible/group_vars/controllers/firewall | 21 +++++ ansible/group_vars/monitoring/firewall | 33 +++++++ ansible/group_vars/seed-hypervisor/firewall | 21 +++++ ansible/group_vars/seed/firewall | 21 +++++ ansible/group_vars/storage/firewall | 21 +++++ .../roles/firewall-redhat/defaults/main.yml | 18 ++++ .../roles/firewall-redhat/handlers/main.yml | 10 ++ .../roles/firewall-redhat/tasks/disabled.yml | 18 ++++ .../roles/firewall-redhat/tasks/enabled.yml | 71 ++++++++++++++ ansible/roles/firewall-redhat/tasks/main.yml | 3 + .../kolla-ansible/templates/globals.yml.j2 | 3 + doc/source/configuration/reference/hosts.rst | 92 +++++++++++++++++++ etc/kayobe/compute.yml | 21 +++++ etc/kayobe/controllers.yml | 21 +++++ etc/kayobe/monitoring.yml | 21 +++++ etc/kayobe/seed-hypervisor.yml | 21 +++++ etc/kayobe/seed.yml | 21 +++++ etc/kayobe/storage.yml | 21 +++++ kayobe/cli/commands.py | 9 +- kayobe/tests/unit/cli/test_commands.py | 3 + .../notes/firewalld-48dd2efd52c79252.yaml | 5 + 29 files changed, 631 insertions(+), 3 deletions(-) create mode 100644 ansible/firewall.yml create mode 100644 ansible/group_vars/compute/firewall create mode 100644 ansible/group_vars/controllers/firewall create mode 100644 ansible/group_vars/monitoring/firewall create mode 100644 ansible/group_vars/seed-hypervisor/firewall create mode 100644 ansible/group_vars/seed/firewall create mode 100644 ansible/group_vars/storage/firewall create mode 100644 ansible/roles/firewall-redhat/defaults/main.yml create mode 100644 ansible/roles/firewall-redhat/handlers/main.yml create mode 100644 ansible/roles/firewall-redhat/tasks/disabled.yml create mode 100644 ansible/roles/firewall-redhat/tasks/enabled.yml create mode 100644 ansible/roles/firewall-redhat/tasks/main.yml create mode 100644 releasenotes/notes/firewalld-48dd2efd52c79252.yaml diff --git a/ansible/firewall.yml b/ansible/firewall.yml new file mode 100644 index 000000000..d099aaa7e --- /dev/null +++ b/ansible/firewall.yml @@ -0,0 +1,12 @@ +--- +- name: Ensure firewall is configured + hosts: seed-hypervisor:seed:overcloud + 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/compute b/ansible/group_vars/all/compute index 61bbe91f1..92d32b336 100644 --- a/ansible/group_vars/all/compute +++ b/ansible/group_vars/all/compute @@ -133,3 +133,24 @@ 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: [] diff --git a/ansible/group_vars/all/controllers b/ansible/group_vars/all/controllers index 0c09024fa..f0322b885 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/monitoring b/ansible/group_vars/all/monitoring index 2a9a5c0e5..e4315cbcd 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/seed b/ansible/group_vars/all/seed index decdd2a52..cde572110 100644 --- a/ansible/group_vars/all/seed +++ b/ansible/group_vars/all/seed @@ -113,3 +113,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 9ee93d118..711b4cf76 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/storage b/ansible/group_vars/all/storage index b474e3091..eabaa41fa 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/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/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/kolla-ansible/templates/globals.yml.j2 b/ansible/roles/kolla-ansible/templates/globals.yml.j2 index b549adc5b..84de6c1e0 100644 --- a/ansible/roles/kolla-ansible/templates/globals.yml.j2 +++ b/ansible/roles/kolla-ansible/templates/globals.yml.j2 @@ -565,6 +565,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/doc/source/configuration/reference/hosts.rst b/doc/source/configuration/reference/hosts.rst index c8f99d6a7..de19ab631 100644 --- a/doc/source/configuration/reference/hosts.rst +++ b/doc/source/configuration/reference/hosts.rst @@ -332,6 +332,98 @@ 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`` +* ``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`` +* ``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`` +* ``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`` +* ``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:* diff --git a/etc/kayobe/compute.yml b/etc/kayobe/compute.yml index 59a68fa78..af8d35a8f 100644 --- a/etc/kayobe/compute.yml +++ b/etc/kayobe/compute.yml @@ -115,6 +115,27 @@ # 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: + ############################################################################### # 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 6a4e45eeb..62a1524aa 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/monitoring.yml b/etc/kayobe/monitoring.yml index e28e5ccf1..b1018a364 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/seed-hypervisor.yml b/etc/kayobe/seed-hypervisor.yml index b14c82344..792432b28 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.yml b/etc/kayobe/seed.yml index 35f2aadaa..630d57f31 100644 --- a/etc/kayobe/seed.yml +++ b/etc/kayobe/seed.yml @@ -97,6 +97,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 47f63dbaa..7aa0d48e9 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/cli/commands.py b/kayobe/cli/commands.py index 811e9ebfb..15d7bf070 100644 --- a/kayobe/cli/commands.py +++ b/kayobe/cli/commands.py @@ -415,6 +415,7 @@ class SeedHypervisorHostConfigure(KollaAnsibleMixin, KayobeAnsibleMixin, * 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. @@ -445,7 +446,7 @@ def take_action(self, parsed_args): 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") @@ -563,6 +564,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. @@ -599,7 +601,7 @@ def take_action(self, parsed_args): 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") @@ -1119,6 +1121,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. @@ -1153,7 +1156,7 @@ def take_action(self, parsed_args): 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") diff --git a/kayobe/tests/unit/cli/test_commands.py b/kayobe/tests/unit/cli/test_commands.py index 127634f20..a29c0f1b9 100644 --- a/kayobe/tests/unit/cli/test_commands.py +++ b/kayobe/tests/unit/cli/test_commands.py @@ -327,6 +327,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"), @@ -500,6 +501,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"), @@ -1266,6 +1268,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( 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. From 17fbd48670da3348b7b6d59304559ddf9a1be950 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Wed, 6 Oct 2021 12:53:30 +0100 Subject: [PATCH 09/43] firewalld: support infra VMs Follow up to Id60e25e129e323f3c07e702bb81a11efc530fb3e, adds support for firewalld configuration on Infra VMs. Change-Id: Idd1ab982d4bca1cbdb0c4c6041cf3b6c17eae6cb --- ansible/firewall.yml | 2 +- ansible/group_vars/all/infra-vms | 21 ++++++++++++++++++++ ansible/group_vars/infra-vms/firewall | 21 ++++++++++++++++++++ doc/source/configuration/reference/hosts.rst | 4 ++++ etc/kayobe/infra-vms.yml | 21 ++++++++++++++++++++ kayobe/cli/commands.py | 3 ++- kayobe/tests/unit/cli/test_commands.py | 1 + 7 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 ansible/group_vars/infra-vms/firewall diff --git a/ansible/firewall.yml b/ansible/firewall.yml index d099aaa7e..c133fb011 100644 --- a/ansible/firewall.yml +++ b/ansible/firewall.yml @@ -1,6 +1,6 @@ --- - name: Ensure firewall is configured - hosts: seed-hypervisor:seed:overcloud + hosts: seed-hypervisor:seed:overcloud:infra-vms tags: - config - firewall diff --git a/ansible/group_vars/all/infra-vms b/ansible/group_vars/all/infra-vms index 0d197791d..aa9e19e1d 100644 --- a/ansible/group_vars/all/infra-vms +++ b/ansible/group_vars/all/infra-vms @@ -171,3 +171,24 @@ infra_vm_sysctl_parameters: {} # 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/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/doc/source/configuration/reference/hosts.rst b/doc/source/configuration/reference/hosts.rst index de19ab631..1203e6fa0 100644 --- a/doc/source/configuration/reference/hosts.rst +++ b/doc/source/configuration/reference/hosts.rst @@ -348,6 +348,7 @@ 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`` @@ -358,6 +359,7 @@ 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`` @@ -368,6 +370,7 @@ 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`` @@ -381,6 +384,7 @@ are omitted if not provided, with the following exceptions: ``offline`` * ``seed_hypervisor_firewalld_rules`` * ``seed_firewalld_rules`` +* ``infra_vm_firewalld_rules`` * ``compute_firewalld_rules`` * ``controller_firewalld_rules`` * ``monitoring_firewalld_rules`` diff --git a/etc/kayobe/infra-vms.yml b/etc/kayobe/infra-vms.yml index e5762b161..c4dedb8aa 100644 --- a/etc/kayobe/infra-vms.yml +++ b/etc/kayobe/infra-vms.yml @@ -141,6 +141,27 @@ # 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/kayobe/cli/commands.py b/kayobe/cli/commands.py index 15d7bf070..e448a0df5 100644 --- a/kayobe/cli/commands.py +++ b/kayobe/cli/commands.py @@ -874,6 +874,7 @@ class InfraVMHostConfigure(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. @@ -905,7 +906,7 @@ def take_action(self, parsed_args): 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", "docker") self.run_kayobe_playbooks(parsed_args, playbooks, limit="infra-vms") diff --git a/kayobe/tests/unit/cli/test_commands.py b/kayobe/tests/unit/cli/test_commands.py index a29c0f1b9..5173da615 100644 --- a/kayobe/tests/unit/cli/test_commands.py +++ b/kayobe/tests/unit/cli/test_commands.py @@ -991,6 +991,7 @@ def test_infra_vm_host_configure(self, 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( From 8a1e9273811e99edd33aa28aff1887c3fd665464 Mon Sep 17 00:00:00 2001 From: Jakub Darmach Date: Thu, 13 Jan 2022 14:24:52 +0100 Subject: [PATCH 10/43] Support for untemplated dirs in kolla config Variable untemplated_dirs has been added to allow for defining directories under kayobe/kolla/config which should be copied instead of templated by kolla-openstack role. This is needed to support custom themes for horizon - change If9982c8e18be31772cb031ef72b7eebd4d768be5 Change-Id: I350f58c8a82f0f31608b34054e804c5c198d6806 --- ansible/roles/kolla-openstack/tasks/config.yml | 4 +++- ansible/roles/kolla-openstack/vars/main.yml | 3 +++ ...dd-support-for-custom-horizon-themes-5da1d99c1b8107b9.yaml | 3 +++ 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/add-support-for-custom-horizon-themes-5da1d99c1b8107b9.yaml diff --git a/ansible/roles/kolla-openstack/tasks/config.yml b/ansible/roles/kolla-openstack/tasks/config.yml index 37c146760..a67ebf39d 100644 --- a/ansible/roles/kolla-openstack/tasks/config.yml +++ b/ansible/roles/kolla-openstack/tasks/config.yml @@ -114,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: @@ -127,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..67d05c7e6 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 }}/" 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. From 249ee4f532dd56728e22a967327e8200fc1e583a Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Mon, 24 May 2021 09:52:19 +0100 Subject: [PATCH 11/43] Import merge_configs and merge_yaml from Kolla Ansible These action plugins will be useful for generating configuration files on the Ansible control host. Change-Id: I172c8e81936c93c8d6ce4e53c83083a44aa52e6b (cherry picked from commit a04b5d6a2005d7f9ad6cfd921b7728b967280508) --- ansible/action_plugins/merge_configs.py | 19 ++ ansible/action_plugins/merge_yaml.py | 19 ++ kayobe/plugins/action/merge_configs.py | 225 ++++++++++++++++++ kayobe/plugins/action/merge_yaml.py | 181 ++++++++++++++ .../unit/plugins/action/test_merge_config.py | 204 ++++++++++++++++ .../unit/plugins/action/test_merge_yaml.py | 170 +++++++++++++ requirements.txt | 2 + 7 files changed, 820 insertions(+) create mode 100644 ansible/action_plugins/merge_configs.py create mode 100644 ansible/action_plugins/merge_yaml.py create mode 100644 kayobe/plugins/action/merge_configs.py create mode 100644 kayobe/plugins/action/merge_yaml.py create mode 100644 kayobe/tests/unit/plugins/action/test_merge_config.py create mode 100644 kayobe/tests/unit/plugins/action/test_merge_yaml.py 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/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..72dc537be --- /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 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/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/requirements.txt b/requirements.txt index ed81d1d11..023884276 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,5 @@ 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 From 766b0d65085c6c0068acf5859ce0af71be7e3343 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Tue, 10 Aug 2021 11:58:25 +0100 Subject: [PATCH 12/43] Use merge_configs and merge_yaml to generate Kolla configs Supports merging configuration for the following files: * kolla/globals.yml * kolla/config/bifrost/bifrost.yml * kolla/config/bifrost/dib.yml * kolla/config/bifrost/servers.yml * kolla/kolla-build.conf Configuration is merged from the following sources: * Kayobe source code * Base Kayobe config * Kayobe environment Co-Authored-By: Will Szumski Change-Id: I552bd8f7853b2032954b372bf4476676dac3e271 Story: 2002009 Task: 42974 (cherry picked from commit e318cadaa50b20852f84c4264b8372303a01162e) --- ansible/kolla-ansible.yml | 16 +-- ansible/kolla-bifrost.yml | 20 +-- ansible/kolla-build.yml | 4 +- ansible/roles/kolla-ansible/defaults/main.yml | 16 +++ ansible/roles/kolla-ansible/tasks/config.yml | 4 +- .../{globals.yml.j2 => kolla/globals.yml} | 2 - ansible/roles/kolla-ansible/tests/main.yml | 1 + .../kolla-ansible/tests/test-defaults.yml | 2 + .../roles/kolla-ansible/tests/test-extras.yml | 13 +- .../tests/test-globals-merge.yml | 116 ++++++++++++++++++ .../kolla-ansible/tests/test-requirements.yml | 2 + ansible/roles/kolla-bifrost/defaults/main.yml | 12 ++ ansible/roles/kolla-bifrost/tasks/main.yml | 12 +- .../config/bifrost/bifrost.yml} | 0 .../config/bifrost/dib.yml} | 0 .../config/bifrost/servers.yml} | 0 ansible/roles/kolla-build/defaults/main.yml | 11 +- ansible/roles/kolla-build/tasks/main.yml | 27 ++-- .../kolla-build.conf} | 2 - doc/source/multiple-environments.rst | 31 ++++- kayobe/plugins/action/merge_yaml.py | 2 +- .../notes/merge-configs-1f8fb3672e9be404.yaml | 22 ++++ tools/test-ansible.sh | 1 + 23 files changed, 243 insertions(+), 73 deletions(-) rename ansible/roles/kolla-ansible/templates/{globals.yml.j2 => kolla/globals.yml} (99%) create mode 100644 ansible/roles/kolla-ansible/tests/test-globals-merge.yml rename ansible/roles/kolla-bifrost/templates/{bifrost.yml.j2 => kolla/config/bifrost/bifrost.yml} (100%) rename ansible/roles/kolla-bifrost/templates/{dib.yml.j2 => kolla/config/bifrost/dib.yml} (100%) rename ansible/roles/kolla-bifrost/templates/{servers.yml.j2 => kolla/config/bifrost/servers.yml} (100%) rename ansible/roles/kolla-build/templates/{kolla-build.conf.j2 => kolla/kolla-build.conf} (97%) create mode 100644 releasenotes/notes/merge-configs-1f8fb3672e9be404.yaml diff --git a/ansible/kolla-ansible.yml b/ansible/kolla-ansible.yml index 7cb71899c..f2ccc3432 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 @@ -118,6 +105,9 @@ kolla_inspector_extra_kernel_options: "{{ inspector_extra_kernel_options }}" 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.yml b/ansible/kolla-bifrost.yml index 63e8dca0c..2ad73b958 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/roles/kolla-ansible/defaults/main.yml b/ansible/roles/kolla-ansible/defaults/main.yml index 166659bbc..56dd472e6 100644 --- a/ansible/roles/kolla-ansible/defaults/main.yml +++ b/ansible/roles/kolla-ansible/defaults/main.yml @@ -234,9 +234,25 @@ kolla_nova_compute_ironic_host: ############################################################################### # 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: {} 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/templates/globals.yml.j2 b/ansible/roles/kolla-ansible/templates/kolla/globals.yml similarity index 99% rename from ansible/roles/kolla-ansible/templates/globals.yml.j2 rename to ansible/roles/kolla-ansible/templates/kolla/globals.yml index c3057d5bc..f199dd368 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 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-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 100% rename from ansible/roles/kolla-bifrost/templates/dib.yml.j2 rename to ansible/roles/kolla-bifrost/templates/kolla/config/bifrost/dib.yml 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/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/kayobe/plugins/action/merge_yaml.py b/kayobe/plugins/action/merge_yaml.py index 72dc537be..b2e5a621a 100644 --- a/kayobe/plugins/action/merge_yaml.py +++ b/kayobe/plugins/action/merge_yaml.py @@ -86,7 +86,7 @@ class ActionModule(action.ActionBase): def read_config(self, source): result = None # Only use config if present - if os.access(source, os.R_OK): + if source and os.access(source, os.R_OK): with open(source, 'r') as f: template_data = f.read() 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/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 From f4facbf3d7747bd99b0586063afa0bf96751f0bb Mon Sep 17 00:00:00 2001 From: Pierre Riteau Date: Wed, 1 Dec 2021 06:27:55 +0100 Subject: [PATCH 13/43] Add overcloud_dib_upper_constraints_file variable This variable allows to customise the upper constraints file used to install packages inside the overcloud-host-image-dib virtual environment. This can be used when we need a newer version of diskimage-builder than the one available in upper constraints for the current release. Change-Id: I2f6c2f92903815973865ef0f5d6b867d5b995bd5 Story: 2002098 Task: 44101 --- ansible/group_vars/all/overcloud-dib | 5 +++++ ansible/overcloud-host-image-build.yml | 2 +- etc/kayobe/overcloud-dib.yml | 5 +++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/ansible/group_vars/all/overcloud-dib b/ansible/group_vars/all/overcloud-dib index 0f8d76f5b..9c0c0ffdc 100644 --- a/ansible/group_vars/all/overcloud-dib +++ b/ansible/group_vars/all/overcloud-dib @@ -52,3 +52,8 @@ overcloud_dib_env_vars: "{{ overcloud_dib_env_vars_default | combine(overcloud_d # List of DIB packages to install. Default is to install no extra packages. overcloud_dib_packages: [] + +# 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/overcloud-host-image-build.yml b/ansible/overcloud-host-image-build.yml index 1855607cc..82d4b3055 100644 --- a/ansible/overcloud-host-image-build.yml +++ b/ansible/overcloud-host-image-build.yml @@ -19,7 +19,7 @@ vars: os_images_venv: "{{ virtualenv_path }}/overcloud-host-image-dib" os_images_package_state: latest - os_images_upper_constraints_file: "{{ pip_upper_constraints_file }}" + os_images_upper_constraints_file: "{{ overcloud_dib_upper_constraints_file }}" os_images_cache: "{{ image_cache_path }}" os_images_common: "" os_images_list: diff --git a/etc/kayobe/overcloud-dib.yml b/etc/kayobe/overcloud-dib.yml index 41bce702a..0a95c97e2 100644 --- a/etc/kayobe/overcloud-dib.yml +++ b/etc/kayobe/overcloud-dib.yml @@ -45,6 +45,11 @@ # List of DIB packages to install. Default is to install no extra packages. #overcloud_dib_packages: +# 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 From 3e22b4483be5fccaa8864c9ab5c684cbb2dcfbe0 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Wed, 19 Jan 2022 13:51:34 +0000 Subject: [PATCH 14/43] libvirt: deploy libvirt on the host In some cases it may be desirable to run libvirt daemon on the host. For example, when mixing host and container OS distributions. This change makes it possible to disable the nova_libvirt container, by setting kolla_enable_nova_libvirt_container to false. The stackhpc.libvirt-host role is used in order to install and configure a libvirt daemon on compute hosts when kolla_enable_nova_libvirt_container is false. Depends-On: https://review.opendev.org/c/openstack/kolla-ansible/+/825357 Depends-On: https://review.opendev.org/c/openstack/kayobe-config-dev/+/829225 Depends-On: https://github.com/stackhpc/ansible-role-libvirt-host/pull/51 Story: 2009858 Task: 44495 Change-Id: I73fef63fb886a9d543d2f4231fb009523495edb3 (cherry picked from commit c4b74f48014969759d3783ee895f0a7b9cc68a1d) --- ansible/compute-libvirt-host.yml | 56 +++++++ ansible/group_vars/all/compute | 51 ++++++ ansible/group_vars/all/kolla | 1 + ansible/kolla-ansible.yml | 1 + ansible/kolla-openstack.yml | 2 + ansible/roles/kolla-ansible/defaults/main.yml | 2 + .../kolla-ansible/templates/kolla/globals.yml | 4 + ansible/roles/kolla-ansible/vars/main.yml | 1 + .../roles/kolla-openstack/defaults/main.yml | 10 ++ .../molecule/enable-everything/molecule.yml | 5 +- .../molecule/enable-everything/prepare.yml | 20 +++ .../enable-everything/tests/test_default.py | 6 +- .../roles/kolla-openstack/tasks/config.yml | 2 +- ansible/roles/kolla-openstack/vars/main.yml | 21 +++ dev/functions | 45 ++++-- ...-deploy-config-compute-libvirt-on-host.yml | 56 +++++++ doc/source/configuration/reference/hosts.rst | 151 ++++++++++++++++++ etc/kayobe/compute.yml | 47 ++++++ kayobe/cli/commands.py | 4 +- kayobe/tests/unit/cli/test_commands.py | 2 + .../kayobe-overcloud-base/globals.yml.j2 | 2 + .../kayobe-overcloud-base/overrides.yml.j2 | 3 + playbooks/kayobe-overcloud-base/run.yml | 4 +- .../libvirt-on-host-ff83f12923cc1f58.yaml | 7 + requirements.yml | 2 +- 25 files changed, 482 insertions(+), 23 deletions(-) create mode 100644 ansible/compute-libvirt-host.yml create mode 100644 dev/tenks-deploy-config-compute-libvirt-on-host.yml create mode 100644 releasenotes/notes/libvirt-on-host-ff83f12923cc1f58.yaml diff --git a/ansible/compute-libvirt-host.yml b/ansible/compute-libvirt-host.yml new file mode 100644 index 000000000..7e5501938 --- /dev/null +++ b/ansible/compute-libvirt-host.yml @@ -0,0 +1,56 @@ +--- +- 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_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/group_vars/all/compute b/ansible/group_vars/all/compute index 92d32b336..923f348bc 100644 --- a/ansible/group_vars/all/compute +++ b/ansible/group_vars/all/compute @@ -154,3 +154,54 @@ compute_firewalld_default_zone: # - 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: "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 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/kolla b/ansible/group_vars/all/kolla index fcf65be3e..017dff8b0 100644 --- a/ansible/group_vars/all/kolla +++ b/ansible/group_vars/all/kolla @@ -559,6 +559,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" diff --git a/ansible/kolla-ansible.yml b/ansible/kolla-ansible.yml index f2ccc3432..a469e474d 100644 --- a/ansible/kolla-ansible.yml +++ b/ansible/kolla-ansible.yml @@ -103,6 +103,7 @@ 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: 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/roles/kolla-ansible/defaults/main.yml b/ansible/roles/kolla-ansible/defaults/main.yml index 56dd472e6..b3c6609c9 100644 --- a/ansible/roles/kolla-ansible/defaults/main.yml +++ b/ansible/roles/kolla-ansible/defaults/main.yml @@ -231,6 +231,8 @@ kolla_openstack_logging_debug: # controllers. kolla_nova_compute_ironic_host: +kolla_libvirt_tls: + ############################################################################### # Extra free-form configuraton. diff --git a/ansible/roles/kolla-ansible/templates/kolla/globals.yml b/ansible/roles/kolla-ansible/templates/kolla/globals.yml index f199dd368..6bfe21e20 100644 --- a/ansible/roles/kolla-ansible/templates/kolla/globals.yml +++ b/ansible/roles/kolla-ansible/templates/kolla/globals.yml @@ -375,6 +375,10 @@ 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 %} + ################# # Hyper-V options ################# diff --git a/ansible/roles/kolla-ansible/vars/main.yml b/ansible/roles/kolla-ansible/vars/main.yml index adc645143..86eee9255 100644 --- a/ansible/roles/kolla-ansible/vars/main.yml +++ b/ansible/roles/kolla-ansible/vars/main.yml @@ -181,6 +181,7 @@ kolla_feature_flags: - nova - nova_fake - nova_horizon_policy_file + - nova_libvirt_container - nova_serialconsole_proxy - nova_ssh - octavia 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 a67ebf39d..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: @@ -90,7 +91,6 @@ - name: Ensure extra configuration parent directories are present file: path: "{{ item.0.item.dest }}/{{ item.1.path | relpath(item.0.item.src) | dirname }}" - recurse: true state: directory mode: 0750 with_subelements: diff --git a/ansible/roles/kolla-openstack/vars/main.yml b/ansible/roles/kolla-openstack/vars/main.yml index 67d05c7e6..3db13760d 100644 --- a/ansible/roles/kolla-openstack/vars/main.yml +++ b/ansible/roles/kolla-openstack/vars/main.yml @@ -178,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/dev/functions b/dev/functions index c3f3673da..16ae49649 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. @@ -361,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 @@ -386,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/configuration/reference/hosts.rst b/doc/source/configuration/reference/hosts.rst index e6fe733af..6ee0cc9cb 100644 --- a/doc/source/configuration/reference/hosts.rst +++ b/doc/source/configuration/reference/hosts.rst @@ -849,3 +849,154 @@ 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_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: 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/etc/kayobe/compute.yml b/etc/kayobe/compute.yml index af8d35a8f..893aaa2e3 100644 --- a/etc/kayobe/compute.yml +++ b/etc/kayobe/compute.yml @@ -136,6 +136,53 @@ # - 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/kayobe/cli/commands.py b/kayobe/cli/commands.py index df719dd3d..f68b4e9e6 100644 --- a/kayobe/cli/commands.py +++ b/kayobe/cli/commands.py @@ -1132,6 +1132,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): @@ -1170,7 +1171,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") diff --git a/kayobe/tests/unit/cli/test_commands.py b/kayobe/tests/unit/cli/test_commands.py index 3b0c8b708..d0cbbd8d3 100644 --- a/kayobe/tests/unit/cli/test_commands.py +++ b/kayobe/tests/unit/cli/test_commands.py @@ -1301,6 +1301,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", ), 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 3d2012ffd..73a9bb122 100644 --- a/playbooks/kayobe-overcloud-base/overrides.yml.j2 +++ b/playbooks/kayobe-overcloud-base/overrides.yml.j2 @@ -35,6 +35,9 @@ aio_bridge_ports: kolla_enable_ironic: true {% 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 cba5d88af..04e672714 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: @@ -23,8 +25,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/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/requirements.yml b/requirements.yml index c03d9192f..aa8bc7158 100644 --- a/requirements.yml +++ b/requirements.yml @@ -26,7 +26,7 @@ roles: - src: stackhpc.grafana-conf version: 1.1.1 - src: stackhpc.libvirt-host - version: v1.8.3 + version: v1.10.0 - src: stackhpc.libvirt-vm version: v1.14.2 - src: stackhpc.luks From 8592da45bc410c425479e3f5c992a89b88e01692 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Wed, 9 Mar 2022 12:36:35 +0000 Subject: [PATCH 15/43] libvirt: support SASL authentication Adds support for SASL authentication of libvirt TCP and TLS connections when using a compute host libvirt daemon. In line with the dependent Kolla Ansible patch, we enable SASL by default, and use DIGEST-MD5 with TCP and SCRAM-SHA-256 with TLS. Depends-On: https://review.opendev.org/c/openstack/kolla-ansible/+/833022 Depends-On: https://github.com/stackhpc/ansible-role-libvirt-host/pull/52 Story: 2009858 Task: 44735 Change-Id: Id3972c24022aeb6421494c3cccdc8e7cbce802e6 (cherry picked from commit f4493e41ff6383dba8967b881362bee2cb7a5794) --- ansible/compute-libvirt-host.yml | 3 +++ ansible/group_vars/all/compute | 9 ++++++- ansible/group_vars/all/kolla | 17 +++++++++++-- ansible/roles/kolla-ansible/defaults/main.yml | 2 ++ .../kolla-ansible/templates/kolla/globals.yml | 3 +++ doc/source/configuration/reference/hosts.rst | 24 +++++++++++++++++++ .../kayobe-overcloud-base/overrides.yml.j2 | 3 +++ .../overrides.yml.j2 | 3 +++ .../overrides.yml.j2 | 3 +++ requirements.yml | 2 +- 10 files changed, 65 insertions(+), 4 deletions(-) diff --git a/ansible/compute-libvirt-host.yml b/ansible/compute-libvirt-host.yml index 7e5501938..9b3900c22 100644 --- a/ansible/compute-libvirt-host.yml +++ b/ansible/compute-libvirt-host.yml @@ -20,6 +20,9 @@ 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 }}" diff --git a/ansible/group_vars/all/compute b/ansible/group_vars/all/compute index 923f348bc..1d5e228d0 100644 --- a/ansible/group_vars/all/compute +++ b/ansible/group_vars/all/compute @@ -165,7 +165,8 @@ compute_libvirt_enabled: "{{ kolla_enable_nova | bool and not kolla_enable_nova_ # A dict of default configuration options to write to # /etc/libvirt/libvirtd.conf. compute_libvirt_conf_default: - auth_tcp: "none" + 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 @@ -195,6 +196,12 @@ compute_qemu_conf_extra: {} # 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 diff --git a/ansible/group_vars/all/kolla b/ansible/group_vars/all/kolla index 017dff8b0..5e15bd3af 100644 --- a/ansible/group_vars/all/kolla +++ b/ansible/group_vars/all/kolla @@ -590,9 +590,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) }}" @@ -603,6 +603,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 }}" diff --git a/ansible/roles/kolla-ansible/defaults/main.yml b/ansible/roles/kolla-ansible/defaults/main.yml index b3c6609c9..2d7268df9 100644 --- a/ansible/roles/kolla-ansible/defaults/main.yml +++ b/ansible/roles/kolla-ansible/defaults/main.yml @@ -233,6 +233,8 @@ kolla_nova_compute_ironic_host: kolla_libvirt_tls: +kolla_libvirt_enable_sasl: + ############################################################################### # Extra free-form configuraton. diff --git a/ansible/roles/kolla-ansible/templates/kolla/globals.yml b/ansible/roles/kolla-ansible/templates/kolla/globals.yml index 6bfe21e20..cc7df6b2b 100644 --- a/ansible/roles/kolla-ansible/templates/kolla/globals.yml +++ b/ansible/roles/kolla-ansible/templates/kolla/globals.yml @@ -379,6 +379,9 @@ enable_{{ feature_flag }}: {{ hostvars[inventory_hostname]['kolla_enable_' ~ fea 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 ################# diff --git a/doc/source/configuration/reference/hosts.rst b/doc/source/configuration/reference/hosts.rst index 6ee0cc9cb..b8995d952 100644 --- a/doc/source/configuration/reference/hosts.rst +++ b/doc/source/configuration/reference/hosts.rst @@ -899,6 +899,12 @@ are relevant only when using the libvirt daemon rather than the 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`` @@ -930,6 +936,24 @@ To customise QEMU to avoid adding timestamps to logs: 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 -------------------------------------- diff --git a/playbooks/kayobe-overcloud-base/overrides.yml.j2 b/playbooks/kayobe-overcloud-base/overrides.yml.j2 index 73a9bb122..15ee0ae90 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 diff --git a/playbooks/kayobe-overcloud-host-configure-base/overrides.yml.j2 b/playbooks/kayobe-overcloud-host-configure-base/overrides.yml.j2 index 400c8ce54..ff4301b2d 100644 --- a/playbooks/kayobe-overcloud-host-configure-base/overrides.yml.j2 +++ b/playbooks/kayobe-overcloud-host-configure-base/overrides.yml.j2 @@ -130,3 +130,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-upgrade-base/overrides.yml.j2 b/playbooks/kayobe-overcloud-upgrade-base/overrides.yml.j2 index 3f9374d07..89f901258 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/requirements.yml b/requirements.yml index aa8bc7158..f373baddc 100644 --- a/requirements.yml +++ b/requirements.yml @@ -26,7 +26,7 @@ roles: - src: stackhpc.grafana-conf version: 1.1.1 - src: stackhpc.libvirt-host - version: v1.10.0 + version: v1.11.0 - src: stackhpc.libvirt-vm version: v1.14.2 - src: stackhpc.luks From c7ed881e30c59ffdff5f70d65cf478a4bc8eaee2 Mon Sep 17 00:00:00 2001 From: Michal Nasiadka Date: Thu, 3 Mar 2022 11:27:11 +0100 Subject: [PATCH 16/43] Add support for Rocky Linux 8 Change-Id: If7d6e58b19f98ccb7cc4c209e458cb6f4f4765ad (cherry picked from commit 5ef058b70f6aa805d93849e8ddc3ac240a271b40) --- ansible/group_vars/all/dnf | 8 ++- ansible/group_vars/all/globals | 12 +++-- ansible/group_vars/all/infra-vms | 7 ++- ansible/group_vars/all/kolla | 5 +- ansible/group_vars/all/overcloud-dib | 51 +++++++++++++++---- ansible/group_vars/all/seed-vm | 9 +++- ansible/roles/dnf/tasks/local-mirror.yml | 9 ++-- .../dnf/templates/Rocky-AppStream.repo.j2 | 16 ++++++ .../roles/dnf/templates/Rocky-BaseOS.repo.j2 | 16 ++++++ .../roles/dnf/templates/Rocky-Extras.repo.j2 | 16 ++++++ dev/functions | 4 +- etc/kayobe/dnf.yml | 8 ++- etc/kayobe/globals.yml | 7 +-- etc/kayobe/infra-vms.yml | 5 +- etc/kayobe/kolla.yml | 3 +- etc/kayobe/overcloud-dib.yml | 38 +++++++++++--- etc/kayobe/seed-vm.yml | 7 ++- ...support-rockylinux-8-1da50e2f97b918d5.yaml | 4 ++ 18 files changed, 187 insertions(+), 38 deletions(-) create mode 100644 ansible/roles/dnf/templates/Rocky-AppStream.repo.j2 create mode 100644 ansible/roles/dnf/templates/Rocky-BaseOS.repo.j2 create mode 100644 ansible/roles/dnf/templates/Rocky-Extras.repo.j2 create mode 100644 releasenotes/notes/support-rockylinux-8-1da50e2f97b918d5.yaml 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 index aa9e19e1d..1e43011dd 100644 --- a/ansible/group_vars/all/infra-vms +++ b/ansible/group_vars/all/infra-vms @@ -43,11 +43,16 @@ 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 -# "https://cloud.centos.org/centos/8-stream/x86_64/images/CentOS-Stream-GenericCloud-8-20210210.0.x86_64.qcow2" +# 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 %} diff --git a/ansible/group_vars/all/kolla b/ansible/group_vars/all/kolla index 5e15bd3af..9b599fbbf 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" diff --git a/ansible/group_vars/all/overcloud-dib b/ansible/group_vars/all/overcloud-dib index 9c0c0ffdc..737d91832 100644 --- a/ansible/group_vars/all/overcloud-dib +++ b/ansible/group_vars/all/overcloud-dib @@ -7,23 +7,39 @@ # 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. -overcloud_dib_build_host_images: False +# is {{ os_distribution == 'rocky' }}. This will change in a future release. +overcloud_dib_build_host_images: "{{ os_distribution == 'rocky' }}" -# DIB base OS element. Default is {{ os_distribution }}. -overcloud_dib_os_element: "{{ os_distribution }}" +# 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 ["ubuntu", "cloud-init-datasources", -# "enable-serial-console", "vm"] when overcloud_dib_os_element is "ubuntu". +# 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 == 'centos' %}disable-selinux{% endif %}" + - "{% if overcloud_dib_os_element in ['centos', 'rocky'] %}disable-selinux{% endif %}" - "enable-serial-console" - "vm" @@ -36,11 +52,14 @@ overcloud_dib_elements: "{{ overcloud_dib_elements_default | select | list + ove # 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 }}"}. +# 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. @@ -53,6 +72,20 @@ overcloud_dib_env_vars: "{{ overcloud_dib_env_vars_default | combine(overcloud_d # 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 }}. diff --git a/ansible/group_vars/all/seed-vm b/ansible/group_vars/all/seed-vm index 4dcda8285..1d3ea5496 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 -# "https://cloud.centos.org/centos/8-stream/x86_64/images/CentOS-Stream-GenericCloud-8-20210210.0.x86_64.qcow2" +# 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-20210603.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-20210210.0.x86_64.qcow2 {%- endif %} 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/dev/functions b/dev/functions index 16ae49649..35f4d90c5 100644 --- a/dev/functions +++ b/dev/functions @@ -117,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 @@ -125,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 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 index c4dedb8aa..0155a538c 100644 --- a/etc/kayobe/infra-vms.yml +++ b/etc/kayobe/infra-vms.yml @@ -32,7 +32,10 @@ # 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 -# "https://cloud.centos.org/centos/8-stream/x86_64/images/CentOS-Stream-GenericCloud-8-20210210.0.x86_64.qcow2" +# 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: diff --git a/etc/kayobe/kolla.yml b/etc/kayobe/kolla.yml index 0843df106..42af9854f 100644 --- a/etc/kayobe/kolla.yml +++ b/etc/kayobe/kolla.yml @@ -62,7 +62,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'. diff --git a/etc/kayobe/overcloud-dib.yml b/etc/kayobe/overcloud-dib.yml index 0a95c97e2..3d69f8eb1 100644 --- a/etc/kayobe/overcloud-dib.yml +++ b/etc/kayobe/overcloud-dib.yml @@ -7,10 +7,19 @@ # 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. +# is {{ os_distribution == 'rocky' }}. This will change in a future release. #overcloud_dib_build_host_images: -# DIB base OS element. Default is {{ os_distribution }}. +# 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 }}. @@ -18,8 +27,11 @@ # 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". +# 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. @@ -31,8 +43,9 @@ # 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 }}"}. +# 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. @@ -45,6 +58,19 @@ # 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 }}. diff --git a/etc/kayobe/seed-vm.yml b/etc/kayobe/seed-vm.yml index 9ad7a7438..f8c2ab8a9 100644 --- a/etc/kayobe/seed-vm.yml +++ b/etc/kayobe/seed-vm.yml @@ -25,8 +25,11 @@ # 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 -# "https://cloud.centos.org/centos/8-stream/x86_64/images/CentOS-Stream-GenericCloud-8-20210210.0.x86_64.qcow2" +# 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-20210603.0.x86_64.qcow2" # otherwise. #seed_vm_root_image: 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. From e4ddd6527a13fd60de7ac0e9b675db6aab086e1c Mon Sep 17 00:00:00 2001 From: Pierre Riteau Date: Thu, 31 Mar 2022 07:37:21 +0200 Subject: [PATCH 17/43] Sync Kolla Ansible feature flags and inventory Change-Id: I532f43d5fcf67de73f1be30d83157311b474de93 (cherry picked from commit ef3bb4075db61f97c2dc9e89655fb4c155d73439) --- ansible/roles/kolla-ansible/templates/overcloud-services.j2 | 3 +++ ansible/roles/kolla-ansible/vars/main.yml | 1 + etc/kayobe/kolla.yml | 1 + 3 files changed, 5 insertions(+) diff --git a/ansible/roles/kolla-ansible/templates/overcloud-services.j2 b/ansible/roles/kolla-ansible/templates/overcloud-services.j2 index fa3f78d80..954d8a390 100644 --- a/ansible/roles/kolla-ansible/templates/overcloud-services.j2 +++ b/ansible/roles/kolla-ansible/templates/overcloud-services.j2 @@ -526,6 +526,9 @@ elasticsearch [prometheus-blackbox-exporter:children] monitoring +[prometheus-libvirt-exporter:children] +compute + [masakari-api:children] control diff --git a/ansible/roles/kolla-ansible/vars/main.yml b/ansible/roles/kolla-ansible/vars/main.yml index 86eee9255..796cf49e2 100644 --- a/ansible/roles/kolla-ansible/vars/main.yml +++ b/ansible/roles/kolla-ansible/vars/main.yml @@ -201,6 +201,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/etc/kayobe/kolla.yml b/etc/kayobe/kolla.yml index 0843df106..c73fbf907 100644 --- a/etc/kayobe/kolla.yml +++ b/etc/kayobe/kolla.yml @@ -379,6 +379,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: From 785121aed318d13865a0154358d4be5f3d9b4607 Mon Sep 17 00:00:00 2001 From: Will Szumski Date: Tue, 29 Mar 2022 11:30:45 +0100 Subject: [PATCH 18/43] Adds kolla_ansible_venv_ansible This adds a variable that allows you to modify the version of ansible installed in the kolla-ansible virtualenv. This is useful if you want to use a customised version of ansible. Cherry-pick amended with wallaby ansible versions and collection installation related variable removed Change-Id: I319dd51ed3221826f820fbc0ae3639b89e9c82ea (cherry picked from commit 1375517d2b39ecbba539d05749cc01bf8d1114bf) --- ansible/roles/kolla-ansible/defaults/main.yml | 5 +++++ ansible/roles/kolla-ansible/tasks/install.yml | 5 +---- etc/kayobe/kolla.yml | 5 +++++ ...adds-ansible-requirement-specifier-728e3045fc448715.yaml | 6 ++++++ 4 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/adds-ansible-requirement-specifier-728e3045fc448715.yaml diff --git a/ansible/roles/kolla-ansible/defaults/main.yml b/ansible/roles/kolla-ansible/defaults/main.yml index 2d7268df9..d366bac07 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: 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/etc/kayobe/kolla.yml b/etc/kayobe/kolla.yml index 2bcdfc75d..5ef2c460b 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: 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. From 83e102cc795d8cbbaf0039713f6cff14830bff2e Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Wed, 7 Jul 2021 12:39:55 +0000 Subject: [PATCH 19/43] Support Ansible diff mode Adds a '--diff' argument to kayobe CLI commands. This is passed through to ansible-playbook for Kayobe Ansible playbooks, and can be used with the '--check' argument to see changes that would be made to files. This change also passes through --check and --diff arguments to kolla-ansible. Story: 2009038 Task: 42794 Change-Id: I350795c328c0dc0a91a5cd500c252c5b7b1eafc1 --- doc/source/usage.rst | 10 ++++++++++ kayobe/ansible.py | 15 +++++++++++---- kayobe/kolla_ansible.py | 8 ++++++++ kayobe/tests/unit/test_ansible.py | 8 +++++++- kayobe/tests/unit/test_kolla_ansible.py | 10 ++++++++-- .../notes/diff-mode-468e09bbb9185b50.yaml | 11 +++++++++++ 6 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/diff-mode-468e09bbb9185b50.yaml 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/kayobe/ansible.py b/kayobe/ansible.py index 69e71b615..d8dd80054 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) @@ -161,7 +165,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 +197,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 +233,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 +276,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) diff --git a/kayobe/kolla_ansible.py b/kayobe/kolla_ansible.py index 8954dae07..6eed88237 100644 --- a/kayobe/kolla_ansible.py +++ b/kayobe/kolla_ansible.py @@ -157,6 +157,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/tests/unit/test_ansible.py b/kayobe/tests/unit/test_ansible.py index 55da77a2a..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([ diff --git a/kayobe/tests/unit/test_kolla_ansible.py b/kayobe/tests/unit/test_kolla_ansible.py index 3eced9484..6ab0dcd42 100644 --- a/kayobe/tests/unit/test_kolla_ansible.py +++ b/kayobe/tests/unit/test_kolla_ansible.py @@ -54,6 +54,8 @@ 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", @@ -73,8 +75,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,6 +90,8 @@ 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", @@ -110,7 +115,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/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. From 1f8fe5ae399cac8438046e7b3676cdf3c71e9e4b Mon Sep 17 00:00:00 2001 From: stackhpc-ci <22933334+stackhpc-ci@users.noreply.github.com> Date: Fri, 10 Jun 2022 18:47:39 +0000 Subject: [PATCH 20/43] feat: automatic update of workflows stackhpc/wallaby --- .github/workflows/tag-and-release.yml | 11 +++++++++++ .github/workflows/tox.yml | 7 +++++++ 2 files changed, 18 insertions(+) create mode 100644 .github/workflows/tag-and-release.yml create mode 100644 .github/workflows/tox.yml diff --git a/.github/workflows/tag-and-release.yml b/.github/workflows/tag-and-release.yml new file mode 100644 index 000000000..154dbd51b --- /dev/null +++ b/.github/workflows/tag-and-release.yml @@ -0,0 +1,11 @@ +--- +name: Tag & Release +'on': + push: + branches: + - stackhpc/wallaby +permissions: + 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 From c43fb354c004a85d3a7b6230a2071c6a9531f7fd Mon Sep 17 00:00:00 2001 From: Will Szumski Date: Tue, 4 Jan 2022 17:53:18 +0000 Subject: [PATCH 21/43] Add support for dellos10 switch configuration Change-Id: I2823016294e7df63f63be9ab26535b3962a71ebe (cherry picked from commit 1fbb5cb4001e85469e60c392f47e5cca5e4e3c62) --- ansible/physical-network.yml | 3 ++- ansible/roles/dell-switch/README.md | 11 ++++++----- ansible/roles/dell-switch/defaults/main.yml | 2 +- ansible/roles/dell-switch/tasks/main.yml | 7 +++++++ .../dell-switch/templates/dellos10-config.j2 | 16 ++++++++++++++++ .../configuration/reference/physical-network.rst | 11 ++++++----- .../notes/dellos10-support-31e209bcdb45552a.yaml | 6 ++++++ requirements.txt | 1 + requirements.yml | 4 ++++ 9 files changed, 49 insertions(+), 12 deletions(-) create mode 100644 ansible/roles/dell-switch/templates/dellos10-config.j2 create mode 100644 releasenotes/notes/dellos10-support-31e209bcdb45552a.yaml 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/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/doc/source/configuration/reference/physical-network.rst b/doc/source/configuration/reference/physical-network.rst index 6e27d9502..06dd2b331 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/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/requirements.txt b/requirements.txt index 023884276..3ef442294 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,4 @@ 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 f373baddc..631a28a65 100644 --- a/requirements.yml +++ b/requirements.yml @@ -1,4 +1,8 @@ --- +collections: + - name: dellemc.os10 + version: 1.1.1 + roles: - src: ahuffman.resolv version: 1.3.1 From bc66f63c48b6911c15b2290ad0089b83fe34c6af Mon Sep 17 00:00:00 2001 From: Pierre Riteau Date: Tue, 19 Oct 2021 10:58:19 +0200 Subject: [PATCH 22/43] Add support for configuring proxy settings Change-Id: Ic5130a7512d4a26354bd292b0ab51ab4a9279f0a (cherry picked from commit e48960ecf20c696b2a623bdfbfa7fa4637ff4588) --- ansible/group_vars/all/kolla | 15 +++++++ ansible/group_vars/all/proxy | 19 +++++++++ ansible/proxy.yml | 41 +++++++++++++++++++ ansible/roles/kolla-ansible/defaults/main.yml | 14 ++++++- .../kolla-ansible/templates/kolla/globals.yml | 21 ++++++++++ etc/kayobe/kolla.yml | 15 +++++++ etc/kayobe/proxy.yml | 16 ++++++++ kayobe/cli/commands.py | 12 ++++-- kayobe/tests/unit/cli/test_commands.py | 4 ++ .../proxy-settings-32911948a517b35b.yaml | 8 ++++ 10 files changed, 160 insertions(+), 5 deletions(-) create mode 100644 ansible/group_vars/all/proxy create mode 100644 ansible/proxy.yml create mode 100644 etc/kayobe/proxy.yml create mode 100644 releasenotes/notes/proxy-settings-32911948a517b35b.yaml diff --git a/ansible/group_vars/all/kolla b/ansible/group_vars/all/kolla index 0f7e155f4..5eeb81a48 100644 --- a/ansible/group_vars/all/kolla +++ b/ansible/group_vars/all/kolla @@ -675,3 +675,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/proxy b/ansible/group_vars/all/proxy new file mode 100644 index 000000000..ef1f89635 --- /dev/null +++ b/ansible/group_vars/all/proxy @@ -0,0 +1,19 @@ +--- +############################################################################### +# 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", "{{ docker_registry }}"]. This +# is configured only if either http_proxy or https_proxy is set. +no_proxy: + - "127.0.0.1" + - "localhost" + - "{{ docker_registry }}" diff --git a/ansible/proxy.yml b/ansible/proxy.yml new file mode 100644 index 000000000..14c7b72c2 --- /dev/null +++ b/ansible/proxy.yml @@ -0,0 +1,41 @@ +- name: Configure HTTP(S) proxy settings + hosts: seed-hypervisor:seed:overcloud + 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 is defined and 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 is defined and 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 is defined and http_proxy | length > 0 or https_proxy is defined and https_proxy | length > 0 diff --git a/ansible/roles/kolla-ansible/defaults/main.yml b/ansible/roles/kolla-ansible/defaults/main.yml index d366bac07..9a867069b 100644 --- a/ansible/roles/kolla-ansible/defaults/main.yml +++ b/ansible/roles/kolla-ansible/defaults/main.yml @@ -137,7 +137,6 @@ kolla_external_vip_address: # kolla_external_vip_address. kolla_external_fqdn: - #################### # Networking options #################### @@ -312,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/templates/kolla/globals.yml b/ansible/roles/kolla-ansible/templates/kolla/globals.yml index cc7df6b2b..22e2fbaf5 100644 --- a/ansible/roles/kolla-ansible/templates/kolla/globals.yml +++ b/ansible/roles/kolla-ansible/templates/kolla/globals.yml @@ -50,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 ################ @@ -66,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" ################### diff --git a/etc/kayobe/kolla.yml b/etc/kayobe/kolla.yml index 5ef2c460b..8a228e7fc 100644 --- a/etc/kayobe/kolla.yml +++ b/etc/kayobe/kolla.yml @@ -480,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/proxy.yml b/etc/kayobe/proxy.yml new file mode 100644 index 000000000..aaf938983 --- /dev/null +++ b/etc/kayobe/proxy.yml @@ -0,0 +1,16 @@ +--- +############################################################################### +# 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", "{{ docker_registry }}"]. This +# is configured only if either http_proxy or https_proxy is set. +#no_proxy: diff --git a/kayobe/cli/commands.py b/kayobe/cli/commands.py index f68b4e9e6..8bb1d41b0 100644 --- a/kayobe/cli/commands.py +++ b/kayobe/cli/commands.py @@ -409,6 +409,7 @@ 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. @@ -441,7 +442,7 @@ 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") @@ -557,6 +558,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. @@ -596,7 +598,7 @@ 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") @@ -867,6 +869,7 @@ class InfraVMHostConfigure(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. @@ -901,7 +904,7 @@ 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", "dnf", "pip", "kayobe-target-venv") if parsed_args.wipe_disks: playbooks += _build_playbook_list("wipe-disks") @@ -1115,6 +1118,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. @@ -1153,7 +1157,7 @@ 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") diff --git a/kayobe/tests/unit/cli/test_commands.py b/kayobe/tests/unit/cli/test_commands.py index d0cbbd8d3..5bcbab85d 100644 --- a/kayobe/tests/unit/cli/test_commands.py +++ b/kayobe/tests/unit/cli/test_commands.py @@ -319,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"), @@ -491,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"), @@ -982,6 +984,7 @@ def test_infra_vm_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", "dnf.yml"), utils.get_data_files_path("ansible", "pip.yml"), utils.get_data_files_path( @@ -1259,6 +1262,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"), 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. From defbf6684483572751c2c1e1445436412b3df1c3 Mon Sep 17 00:00:00 2001 From: Pierre Riteau Date: Tue, 7 Sep 2021 23:25:07 +0200 Subject: [PATCH 23/43] Allow to configure name of root disk image to provision This may be used to deploy different images on different hosts. Change-Id: I941cc28a914f3a56f50abdde70f13d6616ff52e7 Story: 2002098 Task: 41694 --- ansible/group_vars/all/bifrost | 6 ++++++ ansible/kolla-bifrost-hostvars.yml | 1 + doc/source/configuration/reference/bifrost.rst | 18 +++++++++++++++++- etc/kayobe/bifrost.yml | 6 ++++++ ...deploy-image-filename-716c12e71c769a27.yaml | 6 ++++++ 5 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/bifrost-deploy-image-filename-716c12e71c769a27.yaml diff --git a/ansible/group_vars/all/bifrost b/ansible/group_vars/all/bifrost index 4f8b3ee84..8f9d250bc 100644 --- a/ansible/group_vars/all/bifrost +++ b/ansible/group_vars/all/bifrost @@ -58,6 +58,12 @@ 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" + ############################################################################### # Ironic configuration. diff --git a/ansible/kolla-bifrost-hostvars.yml b/ansible/kolla-bifrost-hostvars.yml index e5ec431de..3926f8f93 100644 --- a/ansible/kolla-bifrost-hostvars.yml +++ b/ansible/kolla-bifrost-hostvars.yml @@ -14,6 +14,7 @@ ansible_host: "{{ hostvars[seed_host].ansible_host | default(seed_host) }}" bifrost_hostvars: addressing_mode: static + deploy_image_filename: "{{ kolla_bifrost_deploy_image_filename }}" 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/doc/source/configuration/reference/bifrost.rst b/doc/source/configuration/reference/bifrost.rst index f8411cc27..92bcf1996 100644 --- a/doc/source/configuration/reference/bifrost.rst +++ b/doc/source/configuration/reference/bifrost.rst @@ -179,6 +179,22 @@ Rather than needing to write a custom DIB element, we can use the kolla_bifrost_dib_packages: - "biosdevname" +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. + +.. note:: + + Support for building multiple disk images is not yet available. Images can + be manually renamed before changing the Kayobe configuration to build a + different image. + Ironic configuration ==================== @@ -285,7 +301,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/etc/kayobe/bifrost.yml b/etc/kayobe/bifrost.yml index 192195205..77eeebe1f 100644 --- a/etc/kayobe/bifrost.yml +++ b/etc/kayobe/bifrost.yml @@ -57,6 +57,12 @@ # 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: + ############################################################################### # Ironic configuration. 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. From 2f4f885eea1107de2788f7b98e8a7fbae3e5c396 Mon Sep 17 00:00:00 2001 From: Piotr Parczewski Date: Tue, 30 Nov 2021 12:18:19 +0100 Subject: [PATCH 24/43] Add support for root filesystem UUID customisation Depends-on: https://review.opendev.org/c/openstack/bifrost/+/819785 Change-Id: I37b9d18f0523c121357c5a37ec6fc458209f8e79 --- ansible/group_vars/all/bifrost | 6 ++++++ ansible/kolla-bifrost-hostvars.yml | 1 + etc/kayobe/bifrost.yml | 6 ++++++ .../notes/add-support-for-rootfs-uuid-42f0c551a383799b.yaml | 6 ++++++ 4 files changed, 19 insertions(+) create mode 100644 releasenotes/notes/add-support-for-rootfs-uuid-42f0c551a383799b.yaml diff --git a/ansible/group_vars/all/bifrost b/ansible/group_vars/all/bifrost index 8f9d250bc..576b24e9a 100644 --- a/ansible/group_vars/all/bifrost +++ b/ansible/group_vars/all/bifrost @@ -64,6 +64,12 @@ kolla_bifrost_dib_packages: [] # 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/kolla-bifrost-hostvars.yml b/ansible/kolla-bifrost-hostvars.yml index 3926f8f93..e43bcd7e6 100644 --- a/ansible/kolla-bifrost-hostvars.yml +++ b/ansible/kolla-bifrost-hostvars.yml @@ -15,6 +15,7 @@ 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/etc/kayobe/bifrost.yml b/etc/kayobe/bifrost.yml index 77eeebe1f..7ce001ddc 100644 --- a/etc/kayobe/bifrost.yml +++ b/etc/kayobe/bifrost.yml @@ -63,6 +63,12 @@ # 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/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. From 29b16119855a7c05d48ca06af309dee6195fb06c Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Mon, 11 Apr 2022 14:38:15 +0100 Subject: [PATCH 25/43] proxy: add ansible issue 8743 workaround Change-Id: I6515d028914e8956eac23662c3714a908fda6ac4 (cherry picked from commit 5ede87656c05a95e34369417a660fd1b6d06bbfc) --- etc/kayobe/proxy.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/etc/kayobe/proxy.yml b/etc/kayobe/proxy.yml index aaf938983..7d63017d6 100644 --- a/etc/kayobe/proxy.yml +++ b/etc/kayobe/proxy.yml @@ -14,3 +14,7 @@ # used. Defaults to ["127.0.0.1", "localhost", "{{ docker_registry }}"]. 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 From 829f87bed621596d6798f6539ee93e2eb928ce06 Mon Sep 17 00:00:00 2001 From: Pierre Riteau Date: Tue, 19 Apr 2022 08:58:22 +0200 Subject: [PATCH 26/43] Fix no_proxy configuration The no_proxy list should only contain domains, hostnames, IP addresses and networks, but docker_registry is often in the form ip_address:port. Use urlsplit to extract the hostname from the docker_registry variable after prepending http:// to turn it into a valid URL. Also add missing infra-vms to hosts in proxy.yml. Change-Id: I6424fc405894514a63fb2b641637bbb9d5c070c0 (cherry picked from commit 71eb21a3ddab249fe733b56c5f9b70d2947308a5) --- ansible/group_vars/all/proxy | 8 +++++--- ansible/proxy.yml | 8 ++++---- etc/kayobe/proxy.yml | 6 ++++-- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/ansible/group_vars/all/proxy b/ansible/group_vars/all/proxy index ef1f89635..eb791bbef 100644 --- a/ansible/group_vars/all/proxy +++ b/ansible/group_vars/all/proxy @@ -11,9 +11,11 @@ http_proxy: "" https_proxy: "" # List of domains, hostnames, IP addresses and networks for which no proxy is -# used. Defaults to ["127.0.0.1", "localhost", "{{ docker_registry }}"]. This -# is configured only if either http_proxy or https_proxy is set. +# 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" - - "{{ docker_registry }}" + - "{{ ('http://' ~ docker_registry) | urlsplit('hostname') if docker_registry else '' }}" diff --git a/ansible/proxy.yml b/ansible/proxy.yml index 14c7b72c2..e618b9c0d 100644 --- a/ansible/proxy.yml +++ b/ansible/proxy.yml @@ -1,5 +1,5 @@ - name: Configure HTTP(S) proxy settings - hosts: seed-hypervisor:seed:overcloud + hosts: seed-hypervisor:seed:overcloud:infra-vms vars: ansible_python_interpreter: /usr/bin/python3 tags: @@ -14,7 +14,7 @@ regexp: "^http_proxy=.*" line: "http_proxy={{ http_proxy }}" become: True - when: http_proxy is defined and http_proxy | length > 0 + when: http_proxy | length > 0 - name: Add HTTPS proxy configuration to /etc/environment lineinfile: @@ -25,7 +25,7 @@ regexp: "^https_proxy=.*" line: "https_proxy={{ https_proxy }}" become: True - when: https_proxy is defined and https_proxy | length > 0 + when: https_proxy | length > 0 - name: Add no_proxy configuration to /etc/environment lineinfile: @@ -38,4 +38,4 @@ become: True when: - no_proxy | length > 0 - - http_proxy is defined and http_proxy | length > 0 or https_proxy is defined and https_proxy | length > 0 + - http_proxy | length > 0 or https_proxy | length > 0 diff --git a/etc/kayobe/proxy.yml b/etc/kayobe/proxy.yml index 7d63017d6..714b9dae5 100644 --- a/etc/kayobe/proxy.yml +++ b/etc/kayobe/proxy.yml @@ -11,8 +11,10 @@ #https_proxy: # List of domains, hostnames, IP addresses and networks for which no proxy is -# used. Defaults to ["127.0.0.1", "localhost", "{{ docker_registry }}"]. This -# is configured only if either http_proxy or https_proxy is set. +# 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: ############################################################################### From bfc21d8f4a76214a05d6251384cb37f75a90a28a Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Tue, 2 Nov 2021 10:40:56 +0000 Subject: [PATCH 27/43] Ubuntu: add support for Apt repository configuration This change adds support for configuring Apt repositories on Ubuntu hosts during host configuration. Repositories are configured in a single file (/etc/apt/sources.list.d/kayobe.sources), using the modern deb822 format [1]. This format is more flexible and readable than the original single-line format, particularly if multiple options are used. Using a single file allows us to more easily keep the set of repositories in sync, since Ansible doesn't make it easy to clean things up. Support is added for marking repositories as signed by a particular GPG key. This approach is now preferred over the deprecated [2] apt-key tool, which resulted in a set of globally trusted keys. It is also possible to disable the repositories in /etc/apt/sources.list via apt_disable_sources_list. This allows for replacing the standard repositories with a local mirror. CI tests and documentation are provided. [1] https://manpages.ubuntu.com/manpages/focal/en/man5/sources.list.5.html [2] https://manpages.ubuntu.com/manpages/groovy/man8/apt-key.8.html Story: 2009655 Task: 43818 Change-Id: I3f821937b0930a0ac9341178de7ae5123d82b957 (cherry picked from commit c603be253641aab2199330c1afb2c83051be3e3a) (cherry picked from commit 98d7cc138170a22101121467324b00466b7fd1d9) --- ansible/group_vars/all/apt | 27 ++++++ ansible/roles/apt/defaults/main.yml | 30 ++++++ ansible/roles/apt/handlers/main.yml | 5 + ansible/roles/apt/tasks/keys.yml | 19 ++++ ansible/roles/apt/tasks/main.yml | 19 +--- ansible/roles/apt/tasks/proxy.yml | 17 ++++ ansible/roles/apt/tasks/repos.yml | 22 +++++ ansible/roles/apt/templates/kayobe.sources.j2 | 15 +++ doc/source/configuration/reference/hosts.rst | 93 ++++++++++++++++++- etc/kayobe/apt.yml | 27 ++++++ .../overrides.yml.j2 | 19 ++++ .../tests/test_overcloud_host_configure.py | 12 +++ .../apt-repositories-850efef70ba34946.yaml | 5 + 13 files changed, 293 insertions(+), 17 deletions(-) create mode 100644 ansible/roles/apt/handlers/main.yml create mode 100644 ansible/roles/apt/tasks/keys.yml create mode 100644 ansible/roles/apt/tasks/proxy.yml create mode 100644 ansible/roles/apt/tasks/repos.yml create mode 100644 ansible/roles/apt/templates/kayobe.sources.j2 create mode 100644 releasenotes/notes/apt-repositories-850efef70ba34946.yaml diff --git a/ansible/group_vars/all/apt b/ansible/group_vars/all/apt index fad722dcd..ded0cdf0a 100644 --- a/ansible/group_vars/all/apt +++ b/ansible/group_vars/all/apt @@ -10,3 +10,30 @@ apt_proxy_http: # Apt proxy URL for HTTPS. Default is {{ apt_proxy_http }}. apt_proxy_https: "{{ apt_proxy_http }}" + +# 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/defaults/main.yml b/ansible/roles/apt/defaults/main.yml index fad722dcd..43ce85b36 100644 --- a/ansible/roles/apt/defaults/main.yml +++ b/ansible/roles/apt/defaults/main.yml @@ -10,3 +10,33 @@ apt_proxy_http: # Apt proxy URL for HTTPS. Default is {{ apt_proxy_http }}. apt_proxy_https: "{{ apt_proxy_http }}" + +# 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/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..4bbd0b665 100644 --- a/ansible/roles/apt/tasks/main.yml +++ b/ansible/roles/apt/tasks/main.yml @@ -1,17 +1,6 @@ --- -- 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: 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..ef401bb5e --- /dev/null +++ b/ansible/roles/apt/tasks/repos.yml @@ -0,0 +1,22 @@ +--- +# 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: + 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/doc/source/configuration/reference/hosts.rst b/doc/source/configuration/reference/hosts.rst index b6c44deef..7964fd8c4 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,100 @@ 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 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:* diff --git a/etc/kayobe/apt.yml b/etc/kayobe/apt.yml index 5f278e322..c4314a0aa 100644 --- a/etc/kayobe/apt.yml +++ b/etc/kayobe/apt.yml @@ -11,6 +11,33 @@ # Apt proxy URL for HTTPS. Default is {{ apt_proxy_http }}. #apt_proxy_https: +# 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/playbooks/kayobe-overcloud-host-configure-base/overrides.yml.j2 b/playbooks/kayobe-overcloud-host-configure-base/overrides.yml.j2 index ff4301b2d..033645106 100644 --- a/playbooks/kayobe-overcloud-host-configure-base/overrides.yml.j2 +++ b/playbooks/kayobe-overcloud-host-configure-base/overrides.yml.j2 @@ -102,6 +102,25 @@ docker_storage_driver: devicemapper # Set Honolulu time. timezone: Pacific/Honolulu +{% if ansible_os_family == "Debian" %} +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 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 993df2b80..6f05a93cb 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') @@ -171,6 +176,13 @@ 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_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/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. From 20e6afd1510c5d0930c82535774fb6363a89380b Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Tue, 16 Nov 2021 11:27:37 +0000 Subject: [PATCH 28/43] Ubuntu: add support for Apt configuration This change adds support for configuration of Apt package manager in /etc/apt/apt.conf.d/. This allows adding arbitrary global configuration options for Apt. Options can be added in different files, allowing for different filename-based priorities. CI tests and documentation are provided. Story: 2009655 Task: 43987 Change-Id: I9d7d18851359e97cd01b4c2287bf79110796b25a (cherry picked from commit 5c661b888e821bcadf5e93fc0b09f86eb9ba901a) (cherry picked from commit 4efb80a10d60268e42e64741da09317e42d5e67e) --- ansible/group_vars/all/apt | 8 +++++++ ansible/roles/apt/defaults/main.yml | 8 +++++++ ansible/roles/apt/tasks/config.yml | 14 +++++++++++ ansible/roles/apt/tasks/main.yml | 2 ++ ansible/roles/apt/tasks/repos.yml | 1 + doc/source/configuration/reference/hosts.rst | 24 +++++++++++++++++++ etc/kayobe/apt.yml | 8 +++++++ .../overrides.yml.j2 | 4 ++++ .../tests/test_overcloud_host_configure.py | 7 ++++++ .../notes/apt-config-bc72fd0bff919888.yaml | 6 +++++ 10 files changed, 82 insertions(+) create mode 100644 ansible/roles/apt/tasks/config.yml create mode 100644 releasenotes/notes/apt-config-bc72fd0bff919888.yaml diff --git a/ansible/group_vars/all/apt b/ansible/group_vars/all/apt index ded0cdf0a..46d26de18 100644 --- a/ansible/group_vars/all/apt +++ b/ansible/group_vars/all/apt @@ -11,6 +11,14 @@ 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 diff --git a/ansible/roles/apt/defaults/main.yml b/ansible/roles/apt/defaults/main.yml index 43ce85b36..f818381d7 100644 --- a/ansible/roles/apt/defaults/main.yml +++ b/ansible/roles/apt/defaults/main.yml @@ -11,6 +11,14 @@ 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" 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/main.yml b/ansible/roles/apt/tasks/main.yml index 4bbd0b665..b4cb8f636 100644 --- a/ansible/roles/apt/tasks/main.yml +++ b/ansible/roles/apt/tasks/main.yml @@ -1,6 +1,8 @@ --- - import_tasks: proxy.yml +- import_tasks: config.yml + - import_tasks: keys.yml - import_tasks: repos.yml diff --git a/ansible/roles/apt/tasks/repos.yml b/ansible/roles/apt/tasks/repos.yml index ef401bb5e..8f48f2b92 100644 --- a/ansible/roles/apt/tasks/repos.yml +++ b/ansible/roles/apt/tasks/repos.yml @@ -14,6 +14,7 @@ - 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.*)' diff --git a/doc/source/configuration/reference/hosts.rst b/doc/source/configuration/reference/hosts.rst index 7964fd8c4..e653a1880 100644 --- a/doc/source/configuration/reference/hosts.rst +++ b/doc/source/configuration/reference/hosts.rst @@ -313,6 +313,30 @@ 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 ---------------- diff --git a/etc/kayobe/apt.yml b/etc/kayobe/apt.yml index c4314a0aa..34bfdd2ef 100644 --- a/etc/kayobe/apt.yml +++ b/etc/kayobe/apt.yml @@ -11,6 +11,14 @@ # 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 diff --git a/playbooks/kayobe-overcloud-host-configure-base/overrides.yml.j2 b/playbooks/kayobe-overcloud-host-configure-base/overrides.yml.j2 index 033645106..155dd49bd 100644 --- a/playbooks/kayobe-overcloud-host-configure-base/overrides.yml.j2 +++ b/playbooks/kayobe-overcloud-host-configure-base/overrides.yml.j2 @@ -103,6 +103,10 @@ docker_storage_driver: devicemapper 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 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 6f05a93cb..804c3841f 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 @@ -176,6 +176,13 @@ 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(): 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. From 00daa5bf8c71ab466db5339e046ad3d068c5fc2a Mon Sep 17 00:00:00 2001 From: Pierre Riteau Date: Fri, 7 Jan 2022 12:25:18 +0100 Subject: [PATCH 29/43] Support building multiple disk images Change-Id: I366fbe98d27fa70b1aeb398c129f626fe042b5df Story: 2002098 Task: 19776 (cherry picked from commit 81758d14c3c1154beae351077c22706d93ba9bb8) --- ansible/overcloud-host-image-build.yml | 44 ++-- .../configuration/reference/bifrost.rst | 16 +- doc/source/configuration/reference/index.rst | 1 + .../reference/os-distribution.rst | 2 + .../configuration/reference/overcloud-dib.rst | 209 ++++++++++++++++++ doc/source/deployment.rst | 24 +- .../dib-host-image-4fe8b1bf078f2d27.yaml | 7 + 7 files changed, 271 insertions(+), 32 deletions(-) create mode 100644 doc/source/configuration/reference/overcloud-dib.rst create mode 100644 releasenotes/notes/dib-host-image-4fe8b1bf078f2d27.yaml diff --git a/ansible/overcloud-host-image-build.yml b/ansible/overcloud-host-image-build.yml index 82d4b3055..4751729b1 100644 --- a/ansible/overcloud-host-image-build.yml +++ b/ansible/overcloud-host-image-build.yml @@ -1,19 +1,24 @@ --- -# Build and install a overcloud host disk image for the seed host's ironic +# Build and install overcloud host disk images for the seed host's ironic # service. -- name: Ensure overcloud host disk image is built and installed +- name: Ensure overcloud host disk images are built and installed hosts: seed tags: - overcloud-host-image-build vars: - overcloud_host_image_name: "deployment_image" - overcloud_host_disk_images: - - "{{ overcloud_host_image_name }}.qcow2" overcloud_host_image_force_rebuild: False tasks: - block: - - name: Ensure overcloud host disk image is built + - 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: @@ -22,30 +27,17 @@ os_images_upper_constraints_file: "{{ overcloud_dib_upper_constraints_file }}" os_images_cache: "{{ image_cache_path }}" os_images_common: "" - os_images_list: - - name: "{{ overcloud_host_image_name }}" - elements: "{{ overcloud_dib_elements }}" - env: "{{ overcloud_dib_env_vars }}" - packages: "{{ overcloud_dib_packages }}" - type: qcow2 + os_images_list: "{{ overcloud_dib_host_images }}" os_images_upload: False os_images_force_rebuild: "{{ overcloud_host_image_force_rebuild }}" - - name: Ensure overcloud host disk image is copied onto seed + - name: Copy overcloud host disk images into /httpboot copy: - src: "{{ image_cache_path }}/{{ overcloud_host_image_name }}/{{ item }}" - dest: "/etc/kolla/bifrost/{{ item }}" + 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_host_disk_images }}" + with_items: "{{ overcloud_dib_host_images }}" + loop_control: + loop_var: image become: True - - - name: Copy overcloud host disk image into /httpboot - command: > - docker exec bifrost_deploy - 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"' - with_items: "{{ overcloud_host_disk_images }}" when: overcloud_dib_build_host_images | bool diff --git a/doc/source/configuration/reference/bifrost.rst b/doc/source/configuration/reference/bifrost.rst index 92bcf1996..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,8 @@ 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 =================================== @@ -189,11 +196,10 @@ The name of the root disk image to deploy can be configured via the Kayobe inventory. This can be used to provision different images across the overcloud. -.. note:: - - Support for building multiple disk images is not yet available. Images can - be manually renamed before changing the Kayobe configuration to build a - different image. +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 ==================== diff --git a/doc/source/configuration/reference/index.rst b/doc/source/configuration/reference/index.rst index 2085025a1..100b7ae84 100644 --- a/doc/source/configuration/reference/index.rst +++ b/doc/source/configuration/reference/index.rst @@ -18,6 +18,7 @@ options. kolla kolla-ansible bifrost + overcloud-dib ironic-python-agent docker-registry seed-custom-containers 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..d20e3d61e --- /dev/null +++ b/doc/source/configuration/reference/overcloud-dib.rst @@ -0,0 +1,209 @@ +.. _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_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: 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/deployment.rst b/doc/source/deployment.rst index b1d28edcc..38b845495 100644 --- a/doc/source/deployment.rst +++ b/doc/source/deployment.rst @@ -172,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:: @@ -217,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) ------------------------------------- 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. From 86c81413f7813e0796410807de168f5405e48aee Mon Sep 17 00:00:00 2001 From: Pierre Riteau Date: Fri, 7 Jan 2022 16:37:29 +0100 Subject: [PATCH 30/43] Support elements repositories for overcloud host images Change-Id: Ia8c927c330b9428a3824a6925f6274cbc54314a0 Story: 2002098 Task: 44165 (cherry picked from commit c4a116e912b148b3f5baef5de1979808a16223d2) --- ansible/overcloud-host-image-build.yml | 1 + .../configuration/reference/overcloud-dib.rst | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/ansible/overcloud-host-image-build.yml b/ansible/overcloud-host-image-build.yml index 4751729b1..5ac5af42e 100644 --- a/ansible/overcloud-host-image-build.yml +++ b/ansible/overcloud-host-image-build.yml @@ -28,6 +28,7 @@ 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 }}" diff --git a/doc/source/configuration/reference/overcloud-dib.rst b/doc/source/configuration/reference/overcloud-dib.rst index d20e3d61e..57bd9eb78 100644 --- a/doc/source/configuration/reference/overcloud-dib.rst +++ b/doc/source/configuration/reference/overcloud-dib.rst @@ -77,6 +77,17 @@ the configuration drive built by Bifrost during provisioning. ``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 ``{{ @@ -154,6 +165,28 @@ 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 ----------------------------- From a90e766c596c9f706dd5ee4ca299b64253970e00 Mon Sep 17 00:00:00 2001 From: Pierre Riteau Date: Fri, 22 Apr 2022 14:18:14 +0200 Subject: [PATCH 31/43] Bump stackhpc.drac role This new version fixes the evaluation of check_mode, which is causing `kayobe overcloud bios raid configure` to fail. Change-Id: I9c6e261dbac067ca3f9218e6ea81813ddc2f6520 (cherry picked from commit 5d6b9714ff3d84afd02d7923ccd5145dd66b9774) --- requirements.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.yml b/requirements.yml index 631a28a65..cabd3bce8 100644 --- a/requirements.yml +++ b/requirements.yml @@ -24,7 +24,7 @@ roles: - src: stackhpc.dell-powerconnect-switch version: v1.1.0 - src: stackhpc.drac - version: 1.1.5 + version: 1.1.6 - src: stackhpc.drac-facts version: 1.0.0 - src: stackhpc.grafana-conf From aa50322d304b0fa53989ac63fe98c952ea0d7d4b Mon Sep 17 00:00:00 2001 From: stackhpc-ci <22933334+stackhpc-ci@users.noreply.github.com> Date: Tue, 2 Aug 2022 19:18:00 +0000 Subject: [PATCH 32/43] feat: automatic update of community files stackhpc/wallaby --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/CODEOWNERS 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 From 0c729d6fc4639f083421ea4bea2cfe3010d7008e Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Mon, 15 Aug 2022 13:41:38 +0100 Subject: [PATCH 33/43] Fix ipaddr redirect with dellemc.os10 backport The dellemc.os10 collection pulls in the latest version of ansible.netcommon, which has dropped the redirect for the ipaddr filter. This breaks use of the filter without an FQCN. This change pins the version of ansible.netcommon to <2.6, to ensure the redirect remains. --- requirements.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements.yml b/requirements.yml index cabd3bce8..d27865706 100644 --- a/requirements.yml +++ b/requirements.yml @@ -1,5 +1,7 @@ --- collections: + - name: ansible.netcommon + version: <2.6 - name: dellemc.os10 version: 1.1.1 From 2c646504247469e64b31e1abd3be569914e8346e Mon Sep 17 00:00:00 2001 From: Pierre Riteau Date: Thu, 11 Aug 2022 12:42:57 +0200 Subject: [PATCH 34/43] Add 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. Change-Id: Ifbfbade4baaa1901b08549e52acc725e45379a16 (cherry picked from commit 3903ca92e92288e026d5923ab9a7c4d65d2e2d4a) --- ansible/roles/snat/tasks/main.yml | 5 +++++ releasenotes/notes/snat-rules-dst-src-14ce6ca6bec26086.yaml | 6 ++++++ 2 files changed, 11 insertions(+) create mode 100644 releasenotes/notes/snat-rules-dst-src-14ce6ca6bec26086.yaml 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/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. From 6a0d2cd56a49e9424724e8674574222862685344 Mon Sep 17 00:00:00 2001 From: Pierre Riteau Date: Wed, 5 Jan 2022 18:06:03 +0100 Subject: [PATCH 35/43] dnf: support exclude and includepkgs arguments Change-Id: I58f7259a956b721052d9adfc935b669ff201b299 (cherry picked from commit 3c8ce468a8b1aae52e22f1b8558402613889e404) --- ansible/roles/dnf/tasks/custom-repo.yml | 2 ++ ...m-repository-exclude-includepkgs-6e7b6b988f1f9a9d.yaml | 8 ++++++++ 2 files changed, 10 insertions(+) create mode 100644 releasenotes/notes/yum-repository-exclude-includepkgs-6e7b6b988f1f9a9d.yaml 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/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. From 233657bf3dad753459e48fed668ecda511e6ae1b Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Tue, 20 Sep 2022 14:35:15 +0100 Subject: [PATCH 36/43] Infra VMs: support APT config The Wallaby backports of infra VMs and APT repository config did not sync up correctly. Change-Id: I1e0eb3201890a0c45f6f2a6cc004ff9304684777 --- ansible/apt.yml | 2 +- kayobe/cli/commands.py | 2 +- kayobe/tests/unit/cli/test_commands.py | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) 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/kayobe/cli/commands.py b/kayobe/cli/commands.py index 034f29958..535a30631 100644 --- a/kayobe/cli/commands.py +++ b/kayobe/cli/commands.py @@ -905,7 +905,7 @@ def take_action(self, parsed_args): # Kayobe playbooks. playbooks = _build_playbook_list( "ssh-known-host", "kayobe-ansible-user", "proxy", - "dnf", "pip", "kayobe-target-venv") + "apt", "dnf", "pip", "kayobe-target-venv") if parsed_args.wipe_disks: playbooks += _build_playbook_list("wipe-disks") playbooks += _build_playbook_list( diff --git a/kayobe/tests/unit/cli/test_commands.py b/kayobe/tests/unit/cli/test_commands.py index bdc37aa4c..3143af1c2 100644 --- a/kayobe/tests/unit/cli/test_commands.py +++ b/kayobe/tests/unit/cli/test_commands.py @@ -985,6 +985,7 @@ def test_infra_vm_host_configure(self, mock_run): 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( From e5c7b3042ca19dd7810b5521e9c278af6cfc4695 Mon Sep 17 00:00:00 2001 From: Alex-Welsh Date: Wed, 5 Oct 2022 13:58:00 +0100 Subject: [PATCH 37/43] add prometheus-msteams to overcloud services --- ansible/roles/kolla-ansible/templates/overcloud-services.j2 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ansible/roles/kolla-ansible/templates/overcloud-services.j2 b/ansible/roles/kolla-ansible/templates/overcloud-services.j2 index 922c97d38..12d4b1c20 100644 --- a/ansible/roles/kolla-ansible/templates/overcloud-services.j2 +++ b/ansible/roles/kolla-ansible/templates/overcloud-services.j2 @@ -530,6 +530,9 @@ monitoring [prometheus-libvirt-exporter:children] compute +[prometheus-msteams:children] +prometheus-alertmanager + [masakari-api:children] control From 37b07ccff2763e7894d9b6683b16162dee606191 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Mon, 15 Nov 2021 20:54:16 +0000 Subject: [PATCH 38/43] Infra VMs: fix some playbook host patterns The infra-vms group was not added to some playbook host patterns where it should have been. This change fixes that. TrivialFix Change-Id: I5df2918035df7577627fd2bd68417beddbcbf848 (cherry picked from commit 5e9affb62c40a885f4d64b0dbcc4bf6576b8779c) --- ansible/kayobe-ansible-user.yml | 2 +- ansible/network-connectivity.yml | 2 +- ansible/wipe-disks.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ansible/kayobe-ansible-user.yml b/ansible/kayobe-ansible-user.yml index cfb6bdcf0..eea27115d 100644 --- a/ansible/kayobe-ansible-user.yml +++ b/ansible/kayobe-ansible-user.yml @@ -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/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/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: From 27f82a4af843047c4c79b0126d98472bd0ebd39b Mon Sep 17 00:00:00 2001 From: Alex-Welsh Date: Fri, 16 Sep 2022 13:39:30 +0100 Subject: [PATCH 39/43] added option to skip hooks adds the argument --skip-hooks/-sh which will stop the execution of hooked ansible playbooks. Either a pattern can be specified to match against or hook execution can be stopped altogether with "all" Story: 2009241 Task: 43390 Change-Id: I4f2176aa056fec62e31d07140e3d05779480a93d (cherry picked from commit d7069283fd89e6164414d83afa688c3d2afae48f) --- doc/source/custom-ansible-playbooks.rst | 18 ++++++++ kayobe/ansible.py | 3 ++ kayobe/cli/commands.py | 24 +++++++--- kayobe/tests/unit/cli/test_commands.py | 44 ++++++++++++++++++- ...option-to-skip-hooks-719ba214a6d4b773.yaml | 6 +++ 5 files changed, 87 insertions(+), 8 deletions(-) create mode 100644 releasenotes/notes/add-option-to-skip-hooks-719ba214a6d4b773.yaml diff --git a/doc/source/custom-ansible-playbooks.rst b/doc/source/custom-ansible-playbooks.rst index 7941a0f79..ef82452c6 100644 --- a/doc/source/custom-ansible-playbooks.rst +++ b/doc/source/custom-ansible-playbooks.rst @@ -214,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/kayobe/ansible.py b/kayobe/ansible.py index d8dd80054..af5396d9a 100644 --- a/kayobe/ansible.py +++ b/kayobe/ansible.py @@ -77,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): diff --git a/kayobe/cli/commands.py b/kayobe/cli/commands.py index 535a30631..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) diff --git a/kayobe/tests/unit/cli/test_commands.py b/kayobe/tests/unit/cli/test_commands.py index 3143af1c2..9edbd5574 100644 --- a/kayobe/tests/unit/cli/test_commands.py +++ b/kayobe/tests/unit/cli/test_commands.py @@ -2456,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/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 From 28a5f724831067e6d20812f3d2a258100be550f8 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Fri, 24 Jun 2022 12:20:41 +0100 Subject: [PATCH 40/43] Support --kolla-playbook argument Adds support for specifying a custom playbook when running Kolla Ansible commands via a '--kolla-playbook' argument. Change-Id: Ie4bba0f1bdc82657572e0f06a71db1140c0bd3a4 (cherry picked from commit 79a8610030b214dd013f167b66464b79be937f67) --- kayobe/kolla_ansible.py | 11 +++++++++++ kayobe/tests/unit/test_kolla_ansible.py | 4 ++++ .../kolla-ansible-playbook-7e4418ca757e81e8.yaml | 12 ++++++++++++ 3 files changed, 27 insertions(+) create mode 100644 releasenotes/notes/kolla-ansible-playbook-7e4418ca757e81e8.yaml diff --git a/kayobe/kolla_ansible.py b/kayobe/kolla_ansible.py index 6eed88237..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] diff --git a/kayobe/tests/unit/test_kolla_ansible.py b/kayobe/tests/unit/test_kolla_ansible.py index 6ab0dcd42..8f9650672 100644 --- a/kayobe/tests/unit/test_kolla_ansible.py +++ b/kayobe/tests/unit/test_kolla_ansible.py @@ -61,12 +61,14 @@ def test_run_all_the_args(self, mock_validate, mock_run): "-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", @@ -98,6 +100,7 @@ def test_run_all_the_long_args(self, mock_ask, mock_validate, mock_run): "--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" @@ -105,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", 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. From 38931bb1d546e7829fb50c2515fb2b3b81c899e9 Mon Sep 17 00:00:00 2001 From: Will Szumski Date: Tue, 15 Nov 2022 10:11:01 +0000 Subject: [PATCH 41/43] Install fork of ansible with Rocky 8 support (#60) * Install fork of ansible with Rocky 8 support This means we would support Rocky 8 out of the box. Otherwise, we need to install the ansible fork as another step. I'm trying to avoid this in the stackhpc AIO scenario to keep it simple. * We need Ansible Community Package https://docs.ansible.com/ansible/latest/reference_appendices/release_and_maintenance.html#ansible-community-changelogs --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index bf934a5a3..ec56364a6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ 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 From 0a57dcd11387989e1c9bc014be22155ad5990b0a Mon Sep 17 00:00:00 2001 From: stackhpc-ci <22933334+stackhpc-ci@users.noreply.github.com> Date: Wed, 8 Feb 2023 09:42:56 +0000 Subject: [PATCH 42/43] feat: automatic update of workflows stackhpc/wallaby --- .github/workflows/tag-and-release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tag-and-release.yml b/.github/workflows/tag-and-release.yml index 154dbd51b..f11bd2358 100644 --- a/.github/workflows/tag-and-release.yml +++ b/.github/workflows/tag-and-release.yml @@ -5,6 +5,7 @@ name: Tag & Release branches: - stackhpc/wallaby permissions: + actions: read contents: write jobs: tag-and-release: From 257468d910e37a14a182250d8dae7a3b3038e388 Mon Sep 17 00:00:00 2001 From: Doug Szumski Date: Fri, 22 Mar 2019 16:05:02 +0000 Subject: [PATCH 43/43] Add support for caso (cherry picked from commit a78a8fdd3b7653d37effb2088bc8aa6e5ce23eaf) (cherry picked from commit 5f57c8c9ac5c0d6a8cfe4a28fd30a5638eb61aa1) Change-Id: I8804bd4647140bc837832a0cb6d8bbf3f8e53ac8 (cherry picked from commit 609f822c3f92625d56cdfff3aed2f793ad569fa6) (cherry picked from commit 6862b1dd293c58f68b780c26b538a675244481f8) (cherry picked from commit 2e9b8dfc3a3cf2c9b72666cbb33fa4507bfdb6c1) (cherry picked from commit 219ed0bc2df928f731858deca0ea67383fafc8e0) --- ansible/group_vars/all/kolla | 3 +++ ansible/roles/kolla-ansible/templates/overcloud-components.j2 | 3 +++ ansible/roles/kolla-ansible/vars/main.yml | 1 + 3 files changed, 7 insertions(+) diff --git a/ansible/group_vars/all/kolla b/ansible/group_vars/all/kolla index 5eeb81a48..6059e886d 100644 --- a/ansible/group_vars/all/kolla +++ b/ansible/group_vars/all/kolla @@ -122,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 @@ -525,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" 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/vars/main.yml b/ansible/roles/kolla-ansible/vars/main.yml index 796cf49e2..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