diff --git a/dev/functions b/dev/functions index 2d8fbeea9..459ffd484 100644 --- a/dev/functions +++ b/dev/functions @@ -35,9 +35,19 @@ function config_defaults { export KAYOBE_SEED_CONTAINER_IMAGE_BUILD=${KAYOBE_SEED_CONTAINER_IMAGE_BUILD:-0} # Whether to build container images for the overcloud services. If 0, they - # will be pulled. + # will be pulled if $KAYOBE_OVERCLOUD_CONTAINER_IMAGE_PULL is 1. export KAYOBE_OVERCLOUD_CONTAINER_IMAGE_BUILD=${KAYOBE_OVERCLOUD_CONTAINER_IMAGE_BUILD:-0} + # Whether to pull container images for the overcloud services if + # $KAYOBE_OVERCLOUD_CONTAINER_IMAGE_BUILD is 0. + export KAYOBE_OVERCLOUD_CONTAINER_IMAGE_PULL=${KAYOBE_OVERCLOUD_CONTAINER_IMAGE_PULL:-1} + + # Whether to deploy overcloud services. + export KAYOBE_OVERCLOUD_SERVICE_DEPLOY=${KAYOBE_OVERCLOUD_SERVICE_DEPLOY:-1} + + # Whether to perform overcloud post configuration. + export KAYOBE_OVERCLOUD_POST_CONFIGURE=${KAYOBE_OVERCLOUD_POST_CONFIGURE:-1} + # Additional arguments to pass to kayobe commands. export KAYOBE_EXTRA_ARGS=${KAYOBE_EXTRA_ARGS:-} @@ -349,17 +359,21 @@ function overcloud_deploy { if [[ ${KAYOBE_OVERCLOUD_CONTAINER_IMAGE_BUILD} = 1 ]]; then echo "Building overcloud container images" run_kayobe overcloud container image build - else + elif [[ ${KAYOBE_OVERCLOUD_CONTAINER_IMAGE_PULL} = 1 ]]; then echo "Pulling overcloud container images" run_kayobe overcloud container image pull fi - echo "Deploying containerised overcloud services" - run_kayobe overcloud service deploy + if [[ ${KAYOBE_OVERCLOUD_SERVICE_DEPLOY} = 1 ]]; then + echo "Deploying containerised overcloud services" + run_kayobe overcloud service deploy + fi - echo "Performing post-deployment configuration" - source "${KOLLA_CONFIG_PATH:-/etc/kolla}/admin-openrc.sh" - run_kayobe overcloud post configure + if [[ ${KAYOBE_OVERCLOUD_POST_CONFIGURE} = 1 ]]; then + echo "Performing post-deployment configuration" + source "${KOLLA_CONFIG_PATH:-/etc/kolla}/admin-openrc.sh" + run_kayobe overcloud post configure + fi echo "Control plane deployment complete" } diff --git a/playbooks/kayobe-overcloud-host-configure-base/overrides.yml.j2 b/playbooks/kayobe-overcloud-host-configure-base/overrides.yml.j2 new file mode 100644 index 000000000..51d1119fe --- /dev/null +++ b/playbooks/kayobe-overcloud-host-configure-base/overrides.yml.j2 @@ -0,0 +1,128 @@ +--- +# The following configuration aims to test some of the 'host configure' +# command. + +# Additional users. +controller_users: + - username: kayobe-test-user + name: Kayobe test user + password: kayobe-test-user-password + groups: + - stack + +# Additional network interfaces, testing a variety of interface configurations. +controller_extra_network_interfaces: + - test_net_eth + - test_net_eth_vlan + - test_net_bridge + - test_net_bridge_vlan + - test_net_bond + - test_net_bond_vlan + +# dummy2: Ethernet interface. +test_net_eth_cidr: 192.168.34.0/24 +test_net_eth_routes: + - cidr: 192.168.40.0/24 + gateway: 192.168.34.254 +test_net_eth_interface: dummy2 + +# dummy2.42: VLAN subinterface of dummy2. +test_net_eth_vlan_cidr: 192.168.35.0/24 +test_net_eth_vlan_interface: "{% raw %}{{ test_net_eth_interface }}.{{ test_net_eth_vlan_vlan }}{% endraw %}" +test_net_eth_vlan_vlan: 42 + +# br0: bridge with ports dummy3, dummy4. +test_net_bridge_cidr: 192.168.36.0/24 +test_net_bridge_interface: br0 +test_net_bridge_bridge_ports: [dummy3, dummy4] + +# br0.43: VLAN subinterface of br0. +test_net_bridge_vlan_cidr: 192.168.37.0/24 +test_net_bridge_vlan_interface: "{% raw %}{{ test_net_bridge_interface }}.{{ test_net_bridge_vlan_vlan }}{% endraw %}" +test_net_bridge_vlan_vlan: 43 + +# bond0: bond with slaves dummy5, dummy6. +test_net_bond_cidr: 192.168.38.0/24 +test_net_bond_interface: bond0 +test_net_bond_bond_slaves: [dummy5, dummy6] + +# bond0.44: VLAN subinterface of bond0. +test_net_bond_vlan_cidr: 192.168.39.0/24 +test_net_bond_vlan_interface: "{% raw %}{{ test_net_bond_interface }}.{{ test_net_bond_vlan_vlan }}{% endraw %}" +test_net_bond_vlan_vlan: 44 + +# Create an LVM volume group for Docker volumes and devicemapper. +controller_lvm_groups: + - "{% raw %}{{ controller_lvm_group_data }}{% endraw %}" + +# Provide a disk for use by LVM. Uses the software RAID device created below. +controller_lvm_group_data_disks: + - /dev/md0 + +# Define a software RAID device consisting of two loopback devices. +controller_mdadm_arrays: + - name: md0 + devices: + - /dev/loop0 + - /dev/loop1 + level: '1' + state: present + +# Set a sysctl. +controller_sysctl_parameters: + fs.mount-max: 99999 + +# Disable cloud-init. +disable_cloud_init: true + +# Use devicemapper storage driver. +docker_storage_driver: devicemapper + +# Set Honolulu time. +timezone: Pacific/Honolulu + +{% if ansible_os_family == 'RedHat' %} +{% if ansible_distribution_major_version | int == 7 %} +# Use a local Yum mirror. +yum_use_local_mirror: true +# Mirror FQDN for Yum repos. +yum_centos_mirror_host: "{{ zuul_site_mirror_fqdn }}" +# Mirror directory for Yum CentOS repos. +yum_centos_mirror_directory: 'centos' +# Mirror FQDN for Yum EPEL repos. +yum_epel_mirror_host: "{{ zuul_site_mirror_fqdn }}" +# Mirror directory for Yum EPEL repos. +yum_epel_mirror_directory: 'epel' +# Configure a custom Yum repository. +yum_custom_repos: + td-agent: + baseurl: http://packages.treasuredata.com/3/redhat/$releasever/$basearch + gpgkey: https://packages.treasuredata.com/GPG-KEY-td-agent + gpgcheck: yes +# Don't install EPEL repositories. +yum_install_epel: false +# Enable yum-cron. +yum_cron_enabled: true +{% else %} +# Use a local DNF mirror. +dnf_use_local_mirror: true +# Mirror FQDN for DNF repos. +dnf_centos_mirror_host: "{{ zuul_site_mirror_fqdn }}" +# Mirror directory for DNF CentOS repos. +dnf_centos_mirror_directory: 'centos' +# Mirror FQDN for DNF EPEL repos. +dnf_epel_mirror_host: "{{ zuul_site_mirror_fqdn }}" +# Mirror directory for DNF EPEL repos. +dnf_epel_mirror_directory: 'epel' +# Configure a custom DNF repository. +dnf_custom_repos: + td-agent: + baseurl: http://packages.treasuredata.com/3/redhat/$releasever/$basearch + gpgkey: https://packages.treasuredata.com/GPG-KEY-td-agent + gpgcheck: yes +# Don't install EPEL repositories. +dnf_install_epel: false +# Enable DNF Automatic. +dnf_automatic_enabled: true +{% endif %} +{% endif %} diff --git a/playbooks/kayobe-overcloud-host-configure-base/pre.yml b/playbooks/kayobe-overcloud-host-configure-base/pre.yml new file mode 100644 index 000000000..c1a372a76 --- /dev/null +++ b/playbooks/kayobe-overcloud-host-configure-base/pre.yml @@ -0,0 +1,42 @@ +--- +- hosts: primary + vars: + testinfra_venv: ~/testinfra-venv + tasks: + - name: Ensure python3 is installed + package: + name: python3 + become: true + + - name: Ensure testinfra is installed + pip: + name: + - distro + - testinfra + - pytest-html + virtualenv: "{{ testinfra_venv }}" + virtualenv_python: python3 + + # NOTE(mgoddard): Use the name zzz-overrides.yml to ensure this takes + # precedence over the standard config files and zz-overrides.yml from + # kayobe-overcloud-base. + - name: Ensure kayobe-config override config file exists + template: + src: overrides.yml.j2 + dest: "{{ kayobe_config_src_dir }}/etc/kayobe/zzz-overrides.yml" + + # NOTE(mgoddard): Create two loopback devices backed by files. These will + # be added to a software RAID volume, then added to an LVM volume group. + - name: Ensure a docker storage backing file exists + command: truncate -s 2G /tmp/docker-storage{{ item }} + loop: [0, 1] + + - name: Ensure the docker storage loopback device is created + command: losetup /dev/loop{{ item }} /tmp/docker-storage{{ item }} + become: true + loop: [0, 1] + + - name: Ensure dummy network interfaces exist + command: ip link add dummy{{ item }} type dummy + become: true + loop: "{{ range(2, 7) | list }}" diff --git a/playbooks/kayobe-overcloud-host-configure-base/run.yml b/playbooks/kayobe-overcloud-host-configure-base/run.yml new file mode 100644 index 000000000..1da3d336e --- /dev/null +++ b/playbooks/kayobe-overcloud-host-configure-base/run.yml @@ -0,0 +1,20 @@ +--- +- hosts: primary + environment: + KAYOBE_CONFIG_SOURCE_PATH: "{{ kayobe_config_src_dir }}" + # Don't run container deployment. + KAYOBE_OVERCLOUD_CONTAINER_IMAGE_PULL: 0 + KAYOBE_OVERCLOUD_SERVICE_DEPLOY: 0 + KAYOBE_OVERCLOUD_POST_CONFIGURE: 0 + vars: + testinfra_venv: ~/testinfra-venv + test_path: "{{ kayobe_src_dir }}/playbooks/kayobe-overcloud-host-configure-base/tests/" + tasks: + - name: Ensure overcloud is deployed + shell: + cmd: "{{ kayobe_src_dir }}/dev/overcloud-deploy.sh > {{ logs_dir }}/ansible/overcloud-deploy" + + - name: Run testinfra tests + command: "{{ testinfra_venv }}/bin/py.test {{ test_path }} --html={{ logs_dir }}/test-results.html --self-contained-html" + environment: + SITE_MIRROR_FQDN: "{{ zuul_site_mirror_fqdn }}" 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 new file mode 100644 index 000000000..1f55ba999 --- /dev/null +++ b/playbooks/kayobe-overcloud-host-configure-base/tests/test_overcloud_host_configure.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 + +# Kayobe overcloud host configure tests. +# Uses py.test and TestInfra. + +import ipaddress +import os + +import distro +import pytest + + +def _is_yum(): + info = distro.linux_distribution() + return info[0] == 'CentOS Linux' and info[1].startswith('7') + + +def _is_dnf(): + info = distro.linux_distribution() + return info[0] == 'CentOS Linux' and info[1].startswith('8') + + +def test_network_ethernet(host): + interface = host.interface('dummy2') + assert interface.exists + assert '192.168.34.1' in interface.addresses + routes = host.check_output('/sbin/ip route show dev dummy2') + assert '192.168.40.0/24 via 192.168.34.254' in routes + + +def test_network_ethernet_vlan(host): + interface = host.interface('dummy2.42') + assert interface.exists + assert '192.168.35.1' in interface.addresses + assert host.file('/sys/class/net/dummy2.42/lower_dummy2').exists + + +def test_network_bridge(host): + interface = host.interface('br0') + assert interface.exists + assert '192.168.36.1' in interface.addresses + ports = ['dummy3', 'dummy4'] + sys_ports = host.check_output('ls -1 /sys/class/net/br0/brif') + assert sys_ports == "\n".join(ports) + for port in ports: + interface = host.interface(port) + assert interface.exists + v4_addresses = [a for a in interface.addresses + if ipaddress.ip_address(a).version == '4'] + assert not v4_addresses + + +def test_network_bridge_vlan(host): + interface = host.interface('br0.43') + assert interface.exists + assert '192.168.37.1' in interface.addresses + assert host.file('/sys/class/net/br0.43/lower_br0').exists + + +def test_network_bond(host): + interface = host.interface('bond0') + assert interface.exists + assert '192.168.38.1' in interface.addresses + sys_slaves = host.check_output('cat /sys/class/net/bond0/bonding/slaves') + slaves = ['dummy5', 'dummy6'] + assert sys_slaves == " ".join(slaves) + for slave in slaves: + interface = host.interface(slave) + assert interface.exists + assert not interface.addresses + + +def test_network_bond_vlan(host): + interface = host.interface('bond0.44') + assert interface.exists + assert '192.168.39.1' in interface.addresses + assert host.file('/sys/class/net/bond0.44/lower_bond0').exists + + +def test_additional_user_account(host): + user = host.user("kayobe-test-user") + assert user.name == "kayobe-test-user" + assert user.group == "kayobe-test-user" + assert set(user.groups) == {"kayobe-test-user", "stack"} + assert user.gecos == "Kayobe test user" + with host.sudo(): + assert user.password == 'kayobe-test-user-password' + + +def test_software_RAID(host): + slaves = host.check_output("ls -1 /sys/class/block/md0/slaves/") + assert slaves == "loop0\nloop1" + + +def test_sysctls(host): + assert host.sysctl("fs.mount-max") == 99999 + + +def test_cloud_init_is_disabled(host): + assert host.file("/etc/cloud/cloud-init.disabled").exists + + +def test_docker_storage_driver_is_devicemapper(host): + with host.sudo("stack"): + info = host.check_output("docker info") + assert "devicemapper" in info + + +@pytest.mark.parametrize('user', ['kolla', 'stack']) +def test_docker_image_download(host, user): + with host.sudo(user): + host.check_output("docker pull alpine") + + +@pytest.mark.parametrize('user', ['kolla', 'stack']) +def test_docker_container_run(host, user): + with host.sudo(user): + host.check_output("docker run --rm alpine /bin/true") + + +def test_timezone(host): + status = host.check_output("timedatectl status") + assert "Pacific/Honolulu" in status + + +@pytest.mark.parametrize('repo', ["base", "extras", "updates", "epel"]) +@pytest.mark.skipif(not _is_yum(), reason="Yum only supported on CentOS 7") +def test_yum_local_package_mirrors(host, repo): + assert os.getenv('SITE_MIRROR_FQDN') + info = host.check_output("yum repoinfo %s", repo) + assert os.getenv('SITE_MIRROR_FQDN') in info + + +@pytest.mark.parametrize('repo', ["AppStream", "BaseOS", "Extras", "epel", + "epel-modular"]) +@pytest.mark.skipif(not _is_dnf(), reason="DNF only supported on CentOS 8") +def test_dnf_local_package_mirrors(host, repo): + # Depends on SITE_MIRROR_FQDN environment variable. + assert os.getenv('SITE_MIRROR_FQDN') + # NOTE(mgoddard): Should not require sudo but some files + # (/var/cache/dnf/expired_repos.json) can have incorrect permissions. + # https://bugzilla.redhat.com/show_bug.cgi?id=1636909 + with host.sudo(): + info = host.check_output("dnf repoinfo %s", repo) + assert os.getenv('SITE_MIRROR_FQDN') in info + + +@pytest.mark.skipif(not _is_yum(), reason="YUM only supported on CentOS 7") +def test_yum_custom_package_repository_is_available(host): + with host.sudo(): + host.check_output("yum -y install td-agent") + assert host.package("td-agent").is_installed + + +@pytest.mark.skipif(not _is_dnf(), reason="DNF only supported on CentOS 8") +def test_dnf_custom_package_repository_is_available(host): + with host.sudo(): + host.check_output("dnf -y install td-agent") + assert host.package("td-agent").is_installed + + +@pytest.mark.skipif(not _is_yum(), reason="YUM only supported on CentOS 7") +def test_yum_cron(host): + assert host.package("yum-cron").is_installed + assert host.service("yum-cron").is_enabled + assert host.service("yum-cron").is_running + + +@pytest.mark.skipif(not _is_dnf(), reason="DNF only supported on CentOS 8") +def test_dnf_automatic(host): + assert host.package("dnf-automatic").is_installed + assert host.service("dnf-automatic.timer").is_enabled + assert host.service("dnf-automatic.timer").is_running diff --git a/zuul.d/jobs.yaml b/zuul.d/jobs.yaml index c08103944..bca1f29a5 100644 --- a/zuul.d/jobs.yaml +++ b/zuul.d/jobs.yaml @@ -130,6 +130,22 @@ parent: kayobe-seed-base nodeset: kayobe-centos8 +- job: + name: kayobe-overcloud-host-configure-base + parent: kayobe-overcloud-base + description: | + Base job for testing overcloud host configure. + + Configures the primary VM as an overcloud controller. + pre-run: playbooks/kayobe-overcloud-host-configure-base/pre.yml + run: playbooks/kayobe-overcloud-host-configure-base/run.yml + timeout: 7200 + +- job: + name: kayobe-overcloud-host-configure-centos8 + parent: kayobe-overcloud-host-configure-base + nodeset: kayobe-centos8 + - job: name: kayobe-seed-upgrade-base parent: kayobe-base diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml index 964203002..45b76d882 100644 --- a/zuul.d/project.yaml +++ b/zuul.d/project.yaml @@ -11,6 +11,7 @@ - kayobe-tox-ansible - kayobe-tox-molecule - kayobe-overcloud-centos8 + - kayobe-overcloud-host-configure-centos8 - kayobe-overcloud-upgrade-centos8 - kayobe-seed-centos8 - kayobe-seed-upgrade-centos8 @@ -22,6 +23,7 @@ - kayobe-tox-ansible - kayobe-tox-molecule - kayobe-overcloud-centos8 + - kayobe-overcloud-host-configure-centos8 - kayobe-overcloud-upgrade-centos8 - kayobe-seed-centos8 - kayobe-seed-upgrade-centos8