From 0ef02efc047f83acf46714ba184baa142ad49839 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Wed, 15 May 2024 12:35:33 +0100 Subject: [PATCH 1/5] Add initial Docker tests Adds a new host module to include Docker and other host-level tests that will use testinfra. --- requirements.txt | 2 + stackhpc_openstack_tests/host/__init__.py | 13 +++ stackhpc_openstack_tests/host/test_docker.py | 97 ++++++++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 stackhpc_openstack_tests/host/__init__.py create mode 100644 stackhpc_openstack_tests/host/test_docker.py diff --git a/requirements.txt b/requirements.txt index df795ca..9074553 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,7 @@ grafana-client==4.1.* opensearch-py==2.5.* +packaging==24.* prometheus-api-client==0.5.* +pytest-subtests==0.12.* pytest-testinfra==10.1.* requests==2.31.* diff --git a/stackhpc_openstack_tests/host/__init__.py b/stackhpc_openstack_tests/host/__init__.py new file mode 100644 index 0000000..0e8b8f8 --- /dev/null +++ b/stackhpc_openstack_tests/host/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2024 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. diff --git a/stackhpc_openstack_tests/host/test_docker.py b/stackhpc_openstack_tests/host/test_docker.py new file mode 100644 index 0000000..b590c2e --- /dev/null +++ b/stackhpc_openstack_tests/host/test_docker.py @@ -0,0 +1,97 @@ +# Copyright (c) 2024 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. + +import json +import os +from packaging.version import parse +import pytest + + +@pytest.fixture +def docker_info(host, scope="session"): + """Pytest fixture that provides the output of 'docker info'.""" + with host.sudo("stack"): + docker_info = host.check_output("docker info --format json") + return json.loads(docker_info) + + +def test_docker_version(host): + """Check that Docker is accessible and optionally check version.""" + # An optional inclusive minimum version. + min_version = os.environ.get("DOCKER_VERSION_MIN") + # An optional exclusive maximum version. + max_version = os.environ.get("DOCKER_VERSION_MAX") + with host.sudo("stack"): + client_version = parse(host.docker.client_version()) + server_version = parse(host.docker.server_version()) + if min_version: + min_version = parse(min_version) + assert client_version >= min_version + assert server_version >= min_version + if max_version: + max_version = parse(max_version) + assert client_version < max_version + assert server_version < max_version + + +def test_docker_containers(subtests, host): + """Check that Docker containers are healthy.""" + with host.sudo("stack"): + docker_containers = host.docker.get_containers() + for container in docker_containers: + # Use the subtests fixture to create a dynamically parametrised test + # based on the containers on the system. + with subtests.test(msg="container=" + container.name): + state = container.inspect()["State"] + assert state["Running"] + assert not state["Restarting"] + assert not state["Dead"] + assert not state["OOMKilled"] + if "Health" in state: + assert state["Health"]["Status"] == "healthy" + if "HostConfig" in state: + assert state["HostConfig"]["LogConfig"]["Type"] == "json-file" + assert "max-file" in state["HostConfig"]["LogConfig"]["Config"] + assert "max-size" in state["HostConfig"]["LogConfig"]["Config"] + + +def test_docker_driver(docker_info): + """Check that Docker is using the overlay2 storage driver.""" + assert docker_info["Driver"] == "overlay2" + + +def test_no_bridge_network_exists(host): + """Check that no bridge network exists.""" + with host.sudo("stack"): + docker_networks = host.check_output("docker network ls --format json") + for network in docker_networks.splitlines(): + network = json.loads(network) + assert network["Name"] != "bridge" + assert network["Driver"] != "bridge" + + +def test_ip_forwarding_disabled(docker_info): + """Check that IP forwarding is disabled.""" + assert not docker_info["IPv4Forwarding"] + + +def test_iptables_disabled(docker_info): + """Check that IPTables manipulation is disabled.""" + assert not docker_info["BridgeNfIptables"] + assert not docker_info["BridgeNfIp6tables"] + + +def test_live_restore_enabled(docker_info): + """Check that live restore is enabled.""" + assert docker_info["LiveRestoreEnabled"] From ded3b28ee859a805540159c4e685b51b8597fffc Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Thu, 16 May 2024 14:00:07 +0100 Subject: [PATCH 2/5] Add SELinux test Checks that SELinux is enabled and permissive on supported systems. --- stackhpc_openstack_tests/host/test_selinux.py | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 stackhpc_openstack_tests/host/test_selinux.py diff --git a/stackhpc_openstack_tests/host/test_selinux.py b/stackhpc_openstack_tests/host/test_selinux.py new file mode 100644 index 0000000..1576031 --- /dev/null +++ b/stackhpc_openstack_tests/host/test_selinux.py @@ -0,0 +1,38 @@ +# Copyright (c) 2024 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. + +import os +import pytest + + +def test_selinux(host): + """Check that SELinux is enabled and permissive on supported systems.""" + # Adapted from Kayobe host configure tests: + # https://opendev.org/openstack/kayobe/src/branch/master/playbooks/kayobe-overcloud-host-configure-base/tests/test_overcloud_host_configure.py + if host.system_info.distribution in {"debian", "ubuntu"}: + pytest.skip(reason="SELinux is not supported on Debian or Ubuntu") + # Desired state: enforcing, permissive or disabled + expected_state = os.environ["SELINUX_STATE"] + assert expected_state in {"enforcing", "permissive", "disabled"} + expected_status = "disabled" if expected_state == "disabled" else "enabled" + expected_mode = expected_state + selinux = host.check_output("sestatus") + selinux = selinux.splitlines() + # Remove duplicate whitespace characters in output + selinux = [" ".join(x.split()) for x in selinux] + + assert f"SELinux status: {expected_status}" in selinux + if expected_status == "enabled": + assert f"Current mode: {expected_mode}" in selinux + assert f"Mode from config file: {expected_mode}" in selinux From 15879621543927e8a74fa88f5239b38c311c7f2b Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Fri, 17 May 2024 09:33:37 +0100 Subject: [PATCH 3/5] Bump version to 0.0.2 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 7147947..4d2fab9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = stackhpc-openstack-tests -version = 0.1.0 +version = 0.0.2 summary = Automated testing for StackHPC OpenStack description-file = README.md author = Mark Goddard From b1f1735ce9c4a33aa07a639fa55d06aa7e4b48f7 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Mon, 20 May 2024 15:40:38 +0100 Subject: [PATCH 4/5] Add OpenSearch Dashboards CA certificate --- stackhpc_openstack_tests/monitoring/test_opensearch.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/stackhpc_openstack_tests/monitoring/test_opensearch.py b/stackhpc_openstack_tests/monitoring/test_opensearch.py index 300b7c0..3f810f2 100644 --- a/stackhpc_openstack_tests/monitoring/test_opensearch.py +++ b/stackhpc_openstack_tests/monitoring/test_opensearch.py @@ -68,7 +68,11 @@ def test_opensearch_dashboards_status(): dashboard_username = os.environ["OPENSEARCH_DASHBOARDS_USERNAME"] dashboard_password = os.environ["OPENSEARCH_DASHBOARDS_PASSWORD"] dashboard_url += "/api/status" - result = requests.get(dashboard_url, auth=(dashboard_username, dashboard_password)) + dashboard_cacert = os.environ.get("OPENSEARCH_DASHBOARDS_CACERT") + kwargs = {} + if dashboard_cacert: + kwargs["verify"] = dashboard_cacert + result = requests.get(dashboard_url, auth=(dashboard_username, dashboard_password), **kwargs) assert result.ok result = result.json() assert result["status"]["overall"]["state"] == "green" From e8c052b573d756b6c2ef267ec0135f3f16e5b390 Mon Sep 17 00:00:00 2001 From: Max Norton Date: Mon, 3 Feb 2025 16:45:09 +0000 Subject: [PATCH 5/5] Fix docker test - checking iptables is disabled. docker system info (v27.4.1) always reports true for "BridgeNfIptables" and "BridgeNfIp6tables" regardless of the value we set for "iptables" in /etc/docker/daemon.json. The iptables rules correctly follow the configuration so we test on their presence instead. --- stackhpc_cloud_tests/host/test_docker.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/stackhpc_cloud_tests/host/test_docker.py b/stackhpc_cloud_tests/host/test_docker.py index b590c2e..a4fc71d 100644 --- a/stackhpc_cloud_tests/host/test_docker.py +++ b/stackhpc_cloud_tests/host/test_docker.py @@ -86,11 +86,14 @@ def test_ip_forwarding_disabled(docker_info): assert not docker_info["IPv4Forwarding"] -def test_iptables_disabled(docker_info): +def test_iptables_disabled(host): """Check that IPTables manipulation is disabled.""" - assert not docker_info["BridgeNfIptables"] - assert not docker_info["BridgeNfIp6tables"] - + # (MaxN) "docker system info" for version 27.4.1 will report "true" for "BridgeNfIptables" and "BridgeNfIp6tables" + # regardless of the setting of "iptables" in /etc/docker/daemon.json, + # however correct creation of iptables rules will follow this setting - so test on the iptables rules instead. + iptables_chains = host.check_output("iptables -L") + assert "FORWARD" in iptables_chains + assert "DOCKER" not in iptables_chains def test_live_restore_enabled(docker_info): """Check that live restore is enabled."""