From a746f6f82598894ebfd4b312f009c4bba7253fec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stav=C4=9Bl=20=40=20RedHat?= Date: Tue, 26 Nov 2024 19:25:34 +0100 Subject: [PATCH] Card ID: CCT-731 - integration tests for DBus Register method --- integration-tests/README.md | 146 ++++++++++++++++++ integration-tests/conftest.py | 47 ++++++ integration-tests/constants.py | 25 +++ integration-tests/pytest.ini | 5 + integration-tests/requirements.txt | 13 +- .../scripts/post-environments.sh | 30 ++++ .../scripts/run-local-candlepin.sh | 9 ++ integration-tests/test_consumer.py | 24 --- integration-tests/test_register.py | 137 ++++++++++++++++ integration-tests/utils.py | 21 +++ systemtest/tests/integration/test.sh | 37 ++++- 11 files changed, 465 insertions(+), 29 deletions(-) create mode 100644 integration-tests/README.md create mode 100644 integration-tests/conftest.py create mode 100644 integration-tests/constants.py create mode 100644 integration-tests/pytest.ini create mode 100755 integration-tests/scripts/post-environments.sh create mode 100755 integration-tests/scripts/run-local-candlepin.sh delete mode 100644 integration-tests/test_consumer.py create mode 100644 integration-tests/test_register.py create mode 100644 integration-tests/utils.py diff --git a/integration-tests/README.md b/integration-tests/README.md new file mode 100644 index 0000000000..e206ade893 --- /dev/null +++ b/integration-tests/README.md @@ -0,0 +1,146 @@ +# Integration Test for subscription-manager + +There are integration tests for all parts of subscription-manager +in this directory. + +DBus tests are presented currently - they verify DBus api of *rhsm.service* +see [DBus objects](https://www.candlepinproject.org/docs/subscription-manager/dbus_objects.html) + +The tests use pytest ecosystem. + +## Installation + +1) Run local candlepin + +```shell +podman run -d --name canlepin -p 8080:8080 -p 8443:8443 --hostname candlepin.local ghcr.io/ptoscano/candlepin-unofficial:latest +``` + +2) Create additional testing data in candlepin + +Environments for *donaldduck* organization + +``` +curl --stderr /dev/null --insecure --user admin:admin --request POST \ +--data '{"id": "env-id-1", "name": "env-name-1", "description": "Testing environment num. 1"}' \ +--header 'accept: application/json' --header 'content-type: application/json' \ +https://localhost:8443/candlepin/owners/donaldduck/environments + +curl --stderr /dev/null --insecure --user admin:admin --request POST \ +--data '{"id": "env-id-2", "name": "env-name-2", "description": "Testing environment num. 2"}' \ +--header 'accept: application/json' --header 'content-type: application/json' \ +https://localhost:8443/candlepin/owners/donaldduck/environments +``` + +> citation from 'man subscription-manager' +> With on-premise subscription services, such as Subscription Asset +> Manager, the infrastructure is more complex. The local +> administrator can define independent groups called organizations +> which represent physical or organizational divisions (--org). +> Those organizations can be subdivided into environments. + +Activation keys for *donaldduck* organization + +> The tests use already installed test activation keys +> They are: +> - *default_key* +> - *awesome_os_pool" + +## Configuration + +Tests use [Dynaconf](https://www.dynaconf.com/) to load config +values. + +They are stored in a file in this directory *settings.toml* + +Config values for _testing_ environment + +```yaml +[testing] +candlepin.host = "localhost" +candlepin.port = 8443 +candlepin.insecure = true +candlepin.prefix = "/candlepin" +candlepin.username = "duey" +candlepin.password = "password" +candlepin.org = "donaldduck" +candlepin.activation_keys = ["default_key","awesome_os_pool"] +candlepin.environment.names = ["env-name-01","env-name-02"] +candlepin.environment.ids = ["env-id-01","env-id-02"] + +insights.legacy_upload = false +console.host = "cert.console.redhat.com" + +auth_proxy.host = +auth_proxy.port = 3127 +auth_proxy.username = "redhat" +auth_proxy.password = "redhat" + +noauth_proxy.host = +noauth_proxy.port = 3129 + +insights.hbi_host = "cert.console.redhat.com" +``` + +Configuration for pytest + +> There is a file *pytest.ini* in the main directory of this repo. +> It has nothing to do with integration-tests. It is a confiuration +> for unittests. + +*integration-tests/pytest.ini* + +```ini +[pytest] +addopts = "-srxv --capture=sys" +testpaths = "./" +log_cli = true +log_level = INFO +``` + +## Python virtual environment for testing + +It is good practice to use python virtual environment to run the +tests. All required packages for pytest are stored in +*requirements.txt*. + +> There is a file *requirements.txt* in the main directory of the +> repo. It is used by unittests. I has nothing to do with +> integration-tests at all. + +```shell +cd integration-tests +python3 -mvenv venv +source venv +pip install -r requirements.txt +deactivate +``` + +## Running the tests + +```shell +cd integration-tests +source venv +pytest +deativate +``` + +> There is a nice help for pytest in [Testing](../TESTING.md). It is +> full of interesting hits to run just a few tests, to increase output +> of a test run ... + +### Runnning integration tests using tmt + +You can use [Testing Farm](https://docs.testing-farm.io/Testing%20Farm/0.1/index.html) +to run the tests. + +It suposes that the package *subscription-manager* is installed at a local box. + +```shell +cd subscription-manager +sudo tmt --feeling-safe run -vvv --all provision --how local +``` + +> All details for tmt to run are stored at directory *systemtest* +> It is a starting point for deeper investigation to understand how +> the tests are run using tmt. diff --git a/integration-tests/conftest.py b/integration-tests/conftest.py new file mode 100644 index 0000000000..ece0bd501b --- /dev/null +++ b/integration-tests/conftest.py @@ -0,0 +1,47 @@ +import logging +import os + +from dasbus.connection import MessageBus +from gi.repository import Gio + +import gi + +gi.require_version("Gio", "2.0") + +logger = logging.getLogger(__name__) + + +class RHSMPrivateBus(MessageBus): + """Representation of RHSM private bus connection that can be used as a context manager.""" + + def __init__(self, rhsm_register_server_proxy, *args, **kwargs): + """Representation of RHSM private bus connection that can be used as a context manager. + + :param rhsm_register_server_proxy: DBus proxy for the RHSM RegisterServer object + """ + super().__init__(*args, **kwargs) + self._rhsm_register_server_proxy = rhsm_register_server_proxy + self._private_bus_address = None + + def __enter__(self): + logger.debug("subscription: starting RHSM private DBus session") + locale = os.environ.get("LANG", "") + self._private_bus_address = self._rhsm_register_server_proxy.Start(locale) + logger.debug("subscription: RHSM private DBus session has been started") + return self + + def __exit__(self, _exc_type, _exc_value, _exc_traceback): + logger.debug("subscription: shutting down the RHSM private DBus session") + self.disconnect() + locale = os.environ.get("LANG", "") + self._rhsm_register_server_proxy.Stop(locale) + logger.debug("subscription: RHSM private DBus session has been shutdown") + + def _get_connection(self): + """Get a connection to RHSM private DBus session.""" + # the RHSM private bus address is potentially sensitive + # so we will not log it + logger.info("Connecting to the RHSM private DBus session.") + return self._provider.get_addressed_bus_connection( + bus_address=self._private_bus_address, flags=Gio.DBusConnectionFlags.AUTHENTICATION_CLIENT + ) diff --git a/integration-tests/constants.py b/integration-tests/constants.py new file mode 100644 index 0000000000..20de67dd35 --- /dev/null +++ b/integration-tests/constants.py @@ -0,0 +1,25 @@ +from dasbus.identifier import DBusObjectIdentifier, DBusServiceIdentifier +from dasbus.connection import SystemMessageBus + + +HOST_DETAILS: str = "/var/lib/insights/host-details.json" +MACHINE_ID_FILE: str = "/etc/insights-client/machine-id" +RHSM_CONFIG_FILE_PATH: str = "/etc/rhsm/rhsm.conf" + +RHSM_NAMESPACE = ("com", "redhat", "RHSM1") + +RHSM = DBusServiceIdentifier(namespace=RHSM_NAMESPACE, message_bus=SystemMessageBus()) + +RHSM_CONFIG = DBusObjectIdentifier(namespace=RHSM_NAMESPACE, basename="Config") + +RHSM_REGISTER_SERVER = DBusObjectIdentifier(namespace=RHSM_NAMESPACE, basename="RegisterServer") + +RHSM_REGISTER = DBusObjectIdentifier(namespace=RHSM_NAMESPACE, basename="Register") + +RHSM_UNREGISTER = DBusObjectIdentifier(namespace=RHSM_NAMESPACE, basename="Unregister") + +RHSM_ENTITLEMENT = DBusObjectIdentifier(namespace=RHSM_NAMESPACE, basename="Entitlement") + +RHSM_SYSPURPOSE = DBusObjectIdentifier(namespace=RHSM_NAMESPACE, basename="Syspurpose") + +RHSM_CONSUMER = DBusObjectIdentifier(namespace=RHSM_NAMESPACE, basename="Consumer") diff --git a/integration-tests/pytest.ini b/integration-tests/pytest.ini new file mode 100644 index 0000000000..cc5ed0d2da --- /dev/null +++ b/integration-tests/pytest.ini @@ -0,0 +1,5 @@ +[pytest] +addopts = "-srxv -vvv --capture=sys" +testpaths = "./" +log_cli = true +log_level = DEBUG diff --git a/integration-tests/requirements.txt b/integration-tests/requirements.txt index 4735d24fcd..448eebeefc 100644 --- a/integration-tests/requirements.txt +++ b/integration-tests/requirements.txt @@ -1,5 +1,14 @@ +# the version of black is specified also in the stylish.yml github workflow; +# please update the version there in case it is bumped here +black==24.3.0 +flake8 git+https://github.com/ptoscano/pytest-client-tools@main +pytest pyyaml -pytest-randomly -pytest-timeout +simplejson +dasbus +pycairo +PyGObject sh +iniparse +funcy diff --git a/integration-tests/scripts/post-environments.sh b/integration-tests/scripts/post-environments.sh new file mode 100755 index 0000000000..830e473485 --- /dev/null +++ b/integration-tests/scripts/post-environments.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +USERNAME=admin +PASSWORD=admin +ORG=donaldduck + + +# The script will create two environments for an organization. +# +# citation from 'man subscription-manager': +# +# With on-premise subscription services, such as Subscription Asset Manager, +# the infrastructure is more complex. The local administrator can define +# independent groups called organizations which represent physical +# or organizational divisions (--org). Those organizations can be subdivided +# into environments (--environment). +# + +curl -k --request POST --user ${USERNAME}:${PASSWORD} \ + --data '{"id": "env-id-01", "name": "env-name-01", "description": "Testing environment num. 1"}' \ + --header 'accept: application/json' \ + --header 'content-type: application/json' \ + https://localhost:8443/candlepin/owners/${ORG}/environments + +curl -k --request POST --user ${USERNAME}:${PASSWORD} \ + --data '{"id": "env-id-02", "name": "env-name-02", "description": "Testing environment num. 2", "type": "content-template"}' \ + --header 'accept: application/json' \ + --header 'content-type: application/json' \ + https://localhost:8443/candlepin/owners/${ORG}/environments + diff --git a/integration-tests/scripts/run-local-candlepin.sh b/integration-tests/scripts/run-local-candlepin.sh new file mode 100755 index 0000000000..b8e19d533e --- /dev/null +++ b/integration-tests/scripts/run-local-candlepin.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# +# A local candlepin is used for most of integration tests. +# It is fast to run the tests and the candlepin comes with testing data. +# So the environment is mostly prepared in the candlepin after we run a contianer. +# +# For most information see https://github.com/ptoscano/candlepin-container-unofficial +# +podman run -d --name candlepin -p 8080:8080 -p 8443:8443 --hostname candlepin.local ghcr.io/ptoscano/candlepin-unofficial:latest diff --git a/integration-tests/test_consumer.py b/integration-tests/test_consumer.py deleted file mode 100644 index aebcbdc9c9..0000000000 --- a/integration-tests/test_consumer.py +++ /dev/null @@ -1,24 +0,0 @@ -""" -This Python module contains integration tests for rhc. -It uses pytest-client-tools Python module.More information about this -module could be found: https://github.com/ptoscano/pytest-client-tools/ -""" - -import contextlib -import sh - - -def test_busctl_get_consumer_uuid(): - """ - Simple smoke test using busctl CLI tool. It tries to call simple D-Bus method. - """ - with contextlib.suppress(Exception): - sh.busctl( - "call", - "com.redhat.RHSM1", - "/com/redhat/RHSM1/Consumer", - "com.redhat.RHSM1.Consumer", - "GetUuid", - "s", - '""', - ) diff --git a/integration-tests/test_register.py b/integration-tests/test_register.py new file mode 100644 index 0000000000..4994a6d3e4 --- /dev/null +++ b/integration-tests/test_register.py @@ -0,0 +1,137 @@ +# Copyright (c) 2024 Red Hat, Inc. +# +# This software is licensed to you under the GNU General Public +# License as published by the Free Software Foundation; either version +# 2 of the License (GPLv2) or (at your option) any later version. +# There is NO WARRANTY for this software, express or implied, +# including the implied warranties of MERCHANTABILITY, +# NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should +# have received a copy of GPLv2 along with this software; if not, see +# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. +# + +import pytest +import json +from conftest import RHSMPrivateBus +from constants import RHSM, RHSM_REGISTER_SERVER, RHSM_REGISTER +from dasbus.error import DBusError +from dasbus.typing import get_variant, Str +from funcy import partial + +import logging + +logger = logging.getLogger(__name__) + +""" +Integration test for DBus RHSM Register Object. + +See https://www.candlepinproject.org/docs/subscription-manager/dbus_objects.html#register +for more details. + +Main usecases are presented in this file. + +Special usecases for registering (with proxy, activation keys, ...) are presented +in its own files. + +It is important to run tests as root. Since RegisterServer is a system dbus service. +And it provides a unix socket connection. +""" + +# each call uses standard english locale +locale = "en_US.UTF-8" + + +def test_register(any_candlepin, subman, test_config): + """ + https://www.candlepinproject.org/docs/subscription-manager/dbus_objects.html#methods-6 + """ + assert not subman.is_registered + proxy = RHSM.get_proxy(RHSM_REGISTER_SERVER) + with RHSMPrivateBus(proxy) as private_bus: + private_proxy = private_bus.get_proxy(RHSM.service_name, RHSM_REGISTER.object_path) + response = private_proxy.Register( + "", + test_config.get("candlepin", "username"), + test_config.get("candlepin", "password"), + {}, + {}, + locale, + ) + response_data = json.loads(response) + assert "idCert" in response_data, "A response contains of consumer certificate" + assert frozenset(["key", "cert", "updated", "created", "id", "serial"]).issubset( + frozenset(response_data["idCert"].keys()) + ) + + assert subman.is_registered + + +def test_register_with_org(external_candlepin, subman, test_config): + """ + https://www.candlepinproject.org/docs/subscription-manager/dbus_objects.html#methods-6 + """ + assert not subman.is_registered + proxy = RHSM.get_proxy(RHSM_REGISTER_SERVER) + with RHSMPrivateBus(proxy) as private_bus: + private_proxy = private_bus.get_proxy(RHSM.service_name, RHSM_REGISTER.object_path) + private_proxy.Register( + test_config.get("candlepin", "org"), + test_config.get("candlepin", "username"), + test_config.get("candlepin", "password"), + {}, + {}, + locale, + ) + assert subman.is_registered + + +@pytest.mark.parametrize("enable_content", ["true", "false"]) +def test_register_with_enable_content(external_candlepin, subman, test_config, enable_content): + """ + https://www.candlepinproject.org/docs/subscription-manager/dbus_objects.html#methods-6 + """ + assert not subman.is_registered + proxy = RHSM.get_proxy(RHSM_REGISTER_SERVER) + with RHSMPrivateBus(proxy) as private_bus: + private_proxy = private_bus.get_proxy(RHSM.service_name, RHSM_REGISTER.object_path) + private_proxy.Register( + test_config.get("candlepin", "org"), + test_config.get("candlepin", "username"), + test_config.get("candlepin", "password"), + {"enable_content": get_variant(Str, enable_content)}, + {}, + locale, + ) + assert subman.is_registered + + +@pytest.mark.parametrize( + "credentials", + [("wrong username", None, None), (None, "wrong password", None), (None, None, "wrong organization")], +) +def test_register_with_wrong_values(external_candlepin, subman, test_config, credentials): + """ + https://www.candlepinproject.org/docs/subscription-manager/dbus_objects.html#methods-6 + """ + assert not subman.is_registered + + candlepin_config = partial(test_config.get, "candlepin") + + proxy = RHSM.get_proxy(RHSM_REGISTER_SERVER) + with RHSMPrivateBus(proxy) as private_bus: + private_proxy = private_bus.get_proxy(RHSM.service_name, RHSM_REGISTER.object_path) + + wrong_org = "wrong-organization" + + username = credentials[0] or candlepin_config("username") + password = credentials[1] or candlepin_config("password") + organization = credentials[2] or "" + + with pytest.raises(DBusError) as excinfo: + private_proxy.Register(organization, username, password, {}, {}, locale) + logger.debug(f"raised exception: {excinfo}") + if credentials.organization == wrong_org: + assert f"Organization {wrong_org} does not exist." in str(excinfo.value) + else: + assert "Invalid Credentials" in str(excinfo.value) + assert not subman.is_registered diff --git a/integration-tests/utils.py b/integration-tests/utils.py new file mode 100644 index 0000000000..ef4b54c9bd --- /dev/null +++ b/integration-tests/utils.py @@ -0,0 +1,21 @@ +import time + + +def loop_until(predicate, poll_sec=5, timeout_sec=120): + """ + An helper function to handle a time period waiting for an external service + to update its state. + + an example: + + assert loop_until(lambda: insights_client.is_registered) + + The loop function will retry to run predicate every 5secs + until the total time exceeds timeout_sec. + """ + start = time.time() + ok = False + while (not ok) and (time.time() - start < timeout_sec): + time.sleep(poll_sec) + ok = predicate() + return ok diff --git a/systemtest/tests/integration/test.sh b/systemtest/tests/integration/test.sh index 2c59a81911..7349616c45 100755 --- a/systemtest/tests/integration/test.sh +++ b/systemtest/tests/integration/test.sh @@ -9,7 +9,11 @@ cd ../../../ [ -z "${ghprbPullId+x}" ] || ./systemtest/copr-setup.sh dnf --setopt install_weak_deps=False install -y \ - podman git-core python3-pip python3-pytest logrotate + podman git-core python3-pip python3-pytest logrotate \ + cairo-gobject-devel gobject-introspection-devel \ + python3-gobject python3-devel + +yum -y groupinstall 'Development Tools' python3 -m venv venv # shellcheck disable=SC1091 @@ -18,8 +22,35 @@ python3 -m venv venv # Install requirements for integration tests pip install -r integration-tests/requirements.txt -# Run all integration tests -pytest --junit-xml=./junit.xml -v integration-tests +# configuration for the tests +cat < settings.toml +[testing] +candlepin.host = "localhost" +candlepin.port = 8443 +candlepin.insecure = true +candlepin.prefix = "/candlepin" +candlepin.username = "duey" +candlepin.password = "password" +candlepin.org = "donaldduck" +candlepin.activation_keys = ["act-key-01","act-key-02"] +candlepin.environment.names = ["env-name-01","env-name-02"] +candlepin.environment.ids = ["env-id-01","env-id-02"] +EOF + + +# run local candlepin for testing purpose +./integration-tests/scripts/run-local-candlepin.sh + +# create testing data in local candlepin +./integration-tests/scripts/post-activation-keys.sh +./integration-tests/scripts/post-environments.sh + +# There is a problem with SELinux in current version of selinux-roles (for rhsm.service) +# it is a temporary fix +setenforce 0 + +# Run all integration tests. They will use 'testing' environment in configuration +ENV_FOR_DYNACONF=testing pytest --junit-xml=./junit.xml -v integration-tests retval=$? # Copy artifacts of integration tests