cki_lib.inttests
Integration tests that require other services can be run via kind (Kubernetes in Docker).
Requirements
The kind executable needs to be installed and available in the PATH.
When running tests locally with rootless Podman, it might be necessary to run them in a new scope via systemd-run:
KIND_EXPERIMENTAL_PROVIDER=podman \
systemd-run --scope --user \
python3 -m unittest discover inttests
Set CKI_INTEGRATION_TESTS_CLEANUP to skip or logs to keep all resources
in the cluster after the tests finish. With logs, all pod logs are dumped
at the end of the test run.
The resources on the cluster can be further inspected via kubectl:
kind export kubeconfig --name cki
kubectl get ns
kubectl get all -n <namespace>
kubectl logs -n <namespace> <pod>
Creating an integration test
For an integration test, create a unit test in /inttests, and derive the
TestCase from cki_lib.inttests.cluster.KubernetesCluster or one of the
derived classes. As an example, for an integration test that needs a RabbitMQ
server:
from cki_lib.inttests import cluster
from cki_lib.inttests.rabbitmq import RabbitMQServer
@cluster.skip_without_requirements()
class TestStompClient(RabbitMQServer):
pass
During setupClass(), a Kubernetes cluster and any required services will be
created and configured. The services will be removed in tearDownClass(), but
the cluster will be kept running to speed up successive tests.
API
KubernetesCluster - Kubernetes cluster running in Docker
- test case class:
inttests.cluster.KubernetesCluster
The cluster is able to run locally and in GitLab CI as long as the kind and
docker executables are available. This can be checked with the
@skip_without_requirements decorator. For GitLab CI, jobs need to have the
Docker-in-Docker service configured in .gitlab-ci.yml:
integration-tests:
services:
- docker:dind
variables:
DOCKER_HOST: 'tcp://docker:2375'
Fields provided by KubernetesCluster
cls.cluster_name: str:kindcluster namecls.hostname: str: host name for all services usable locally and in GitLab CIcls.api_client: client.ApiClient: PythonkubernetesAPI clientcls.dynamic_client: dynamic.client.DynamicClient: PythonkubernetesDynamic clientcls.core_v1: client.CoreV1Api: PythonkubernetesCore v1 API
Methods provided by KubernetesCluster
cls.k8s_namespace(namespace: str): context manager that creates and cleans up a Kubernetes namespace.cls.k8s_apply(namespace: str, body: typing.Any): apply a Kubernetes resource update via server-side apply.cls.k8s_delete_all(namespace: str, resources: Iterable[str]): delete all resources of the given types from a namespace.
Configuration via environment variables
| Name | Description |
|---|---|
CKI_INTEGRATION_TESTS_CLEANUP |
skip to not remove namespaces, logs to also dump all pod logs |
RabbitMQServer - RabbitMQ server
- test case class:
inttests.rabbitmq.RabbitMQServer
The server is configured with AMQP and STOMP access with and without TLS. For
AMQP and STOMP, authentication can be done via password and client certificates
(from cert.common_name) methods.
Ports provided by RabbitMQServer
| Port | Description |
|---|---|
| 5671 | AMQP via TLS |
| 5672 | AMQP |
| 15671 | HTTP management API via TLS |
| 15672 | HTTP management API |
| 61613 | Stomp |
| 61614 | Stomp via TLS |
Test environment variables provided by RabbitMQServer
| Name | Description |
|---|---|
RABBITMQ_HOST |
host name |
RABBITMQ_PORT |
5671 |
RABBITMQ_MANAGEMENT_PORT |
15671 |
RABBITMQ_VIRTUAL_HOST |
/ |
RABBITMQ_USER |
guest |
RABBITMQ_PASSWORD |
guest |
RABBITMQ_CAFILE |
CA certificate |
RABBITMQ_CERTFILE |
client key and certificate |
STOMP_HOST |
host name |
STOMP_PORT |
61614 |
STOMP_CERTFILE |
client key and certificate |
HashiCorpVaultServer - HashiCorp Vault server
- test case class:
inttests.vault.HashiCorpVaultServer
The server is started in dev mode.
Ports provided by HashiCorpVaultServer
| Port | Description |
|---|---|
| 8200 | HTTP API |
Test environment variables provided by HashiCorpVaultServer
| Name | Description |
|---|---|
VAULT_ADDR |
server URL |
VAULT_MOUNT_POINT |
mount point of the KV2 engine |
VAULT_TOKEN |
secret token ID |
MariaDBServer - MariaDB server
- test case class:
inttests.mariadb.MariaDBServer
The database db is created on startup.
Ports provided by MariaDBServer
| Port | Description |
|---|---|
| 3306 | SQL |
Test environment variables provided by MariaDBServer
| Name | Description |
|---|---|
MARIADB_HOST |
host name |
MARIADB_PORT |
3306 |
MARIADB_USER |
root |
MARIADB_PASSWORD |
root |
MARIADB_DATABASE |
db |
MinioServer - Minio server
- test case class:
inttests.minio.MinioServer
Ports provided by MinioServer
| Port | Description |
|---|---|
| 9000 | S3 |
Test environment variables provided by MinioServer
| Name | Description |
|---|---|
AWS_ENDPOINT_URL |
server URL |
AWS_DEFAULT_REGION |
us-east-1 |
AWS_ACCESS_KEY_ID |
access-key |
AWS_SECRET_ACCESS_KEY |
secret-key |
SQSServer - SQS server
- test case class:
cki_lib.inttests.sqs.SQSServer
The server runs LocalStack with the SQS service enabled.
Fields provided by SQSServer
The cls.sqs_client field contains a boto3.client("sqs") instance.
The cls.sqs_queue_url field contains the URL of a pre-created SQS queue.
Ports provided by SQSServer
| Port | Description |
|---|---|
| 4566 | LocalStack unified endpoint |
Test environment variables provided by SQSServer
| Name | Description |
|---|---|
AWS_ENDPOINT_URL |
http://host:4566 |
AWS_ACCESS_KEY_ID |
test |
AWS_SECRET_ACCESS_KEY |
test |
AWS_DEFAULT_REGION |
us-east-1 |
RemoteResponsesServer - Remote Responses server
- test case class:
inttests.remote_responses.RemoteResponsesServer
Provides a remote URL request mocking endpoint with an API similar to responses.
Fields provided by RemoteResponsesServer
The cls.responses field contains a wrapper class with a subset of the responses API:
add(method, url, body=None, json=None, status=200, headers=None, match=[...])replace(method, url, body=None, json=None, status=200, headers=None, match=[...])upsert(method, url, body=None, json=None, status=200, headers=None, match=[...])reset()json_params_matcher(params, strict_match=True)calls
The return values of add/replace/upsert are proxied BaseResponse
instances and provide all expected fields:
call_countcalls- …
This allows to set up test beds with
# GET request
data = {'a': 'b'}
self.responses.add('GET', f'http://ignored/pooh', json=data)
# POST request with match filter
data = {'data': {'namespace': {'fullPath': 'foo', 'rootStorageStatistics': {
'repositorySize': 1}, 'projects': {'nodes': []}, }}}
self.responses.add('POST', f'http://ignored/api/graphql', json=data, match=[
self.responses.json_params_matcher({"operationName": "pooh"}, strict_match=False)])
The mocked responses are available from REMOTE_RESPONSES_URL_EXTERNAL when
accessed from the test bed, or from REMOTE_RESPONSES_URL when accessed from
other services inside the cluster.
The mocked responses are reset before each test. For sub tests, responses can
be manually reset via self.responses.reset() as well.
Ports provided by RemoteResponsesServer
| Port | Description |
|---|---|
| 7999 | Responses |
Test environment variables provided by RemoteResponsesServer
| Name | Description |
|---|---|
REMOTE_RESPONSES_URL |
server URL reachable from other services deployed in the cluster |
REMOTE_RESPONSES_URL_EXTERNAL |
server URL reachable from the test itself |