diff --git a/test/e2e/helpers.py b/test/e2e/helpers.py index 01bb5e784..998493cf5 100644 --- a/test/e2e/helpers.py +++ b/test/e2e/helpers.py @@ -1,3 +1,5 @@ +import time + import kubernetes from pathlib import Path @@ -117,17 +119,26 @@ def assert_akri_instances_present( ): version = f'v{akri_version.split(".")[0]}' v1_custom = kubernetes.client.CustomObjectsApi() - instances = v1_custom.list_namespaced_custom_object( - "akri.sh", version, "default", "instances" - ) - resource_version = instances["metadata"]["resourceVersion"] - instances = { - instance["metadata"]["name"] - for instance in instances["items"] - if instance["spec"]["configurationName"] == config_name - } + + def get_instances(): + instances = v1_custom.list_namespaced_custom_object( + "akri.sh", version, "default", "instances" + ) + resource_version = instances["metadata"]["resourceVersion"] + instances = { + instance["metadata"]["name"] + for instance in instances["items"] + if instance["spec"]["configurationName"] == config_name + } + return instances, resource_version + + instances, resource_version = get_instances() if len(instances) == count: - return + # Check it is not a transient state + time.sleep(1) + instances, resource_version = get_instances() + if len(instances) == count: + return w = kubernetes.watch.Watch() for e in w.stream( v1_custom.list_namespaced_custom_object, @@ -145,6 +156,10 @@ def assert_akri_instances_present( else: instances.add(e["raw_object"]["metadata"]["name"]) if len(instances) == count: - w.stop() - return + # Check it is not a transient state + time.sleep(1) + instances, _ = get_instances() + if len(instances) == count: + w.stop() + return raise AssertionError(f"{count} != {len(instances)}") diff --git a/test/e2e/test_udev.py b/test/e2e/test_udev.py new file mode 100644 index 000000000..2d80c3e03 --- /dev/null +++ b/test/e2e/test_udev.py @@ -0,0 +1,86 @@ +from pathlib import Path +import time + +import yaml +import pytest +import kubernetes +from helpers import assert_akri_instances_present, check_akri_is_healthy + + +discovery_handlers = ["udev"] + + +@pytest.fixture +def dev_null_config(akri_version): + with open(Path(__file__).parent / "yaml/udevDevNullConfiguration.yaml") as f: + body = yaml.safe_load(f) + client = kubernetes.client.CustomObjectsApi() + version = f'v{akri_version.split(".")[0]}' + client.create_namespaced_custom_object( + "akri.sh", version, "default", "configurations", body + ) + + yield body["metadata"]["name"] + + client.delete_namespaced_custom_object( + "akri.sh", version, "default", "configurations", body["metadata"]["name"] + ) + + +def test_dev_null_config(akri_version, dev_null_config): + check_akri_is_healthy(discovery_handlers) + assert_akri_instances_present(akri_version, dev_null_config, 1) + + +@pytest.fixture +def grouped_config(akri_version): + v1_core = kubernetes.client.CoreV1Api() + pods = v1_core.list_namespaced_pod( + "default", + label_selector=f"app.kubernetes.io/name=akri-udev-discovery", + field_selector="status.phase=Running", + ).items + base_command = ["/bin/sh", "-c"] + # This command will get the ID_PATH to use to get a device with many "subdevices" + command = "grep -hr ID_PATH= /run/udev/data | sort | uniq -cd | sort -h | tail -1 | cut -d '=' -f 2" + paths = set() + # Get the ID_PATH we can use + for pod in pods: + resp = kubernetes.stream.stream( + v1_core.connect_get_namespaced_pod_exec, + pod.metadata.name, + "default", + command=base_command + [command], + stdout=True, + stdin=False, + stderr=True, + tty=False, + _preload_content=False, + ) + try: + paths.add(resp.readline_stdout(timeout=3).strip()) + except: + pytest.skip("No udev ?") + if len(paths) == 0: + pytest.skip("No groupable devices found") + path = paths.pop() + + with open(Path(__file__).parent / "yaml/udevGroupedConfiguration.yaml") as f: + body = yaml.safe_load(f) + body["spec"]["discoveryHandler"]["discoveryDetails"] = body["spec"][ + "discoveryHandler" + ]["discoveryDetails"].format(path) + client = kubernetes.client.CustomObjectsApi() + version = f'v{akri_version.split(".")[0]}' + client.create_namespaced_custom_object( + "akri.sh", version, "default", "configurations", body + ) + yield body["metadata"]["name"] + client.delete_namespaced_custom_object( + "akri.sh", version, "default", "configurations", body["metadata"]["name"] + ) + + +def test_grouped_config(akri_version, grouped_config): + check_akri_is_healthy(discovery_handlers) + assert_akri_instances_present(akri_version, grouped_config, 1) diff --git a/test/e2e/yaml/udevDevNullConfiguration.yaml b/test/e2e/yaml/udevDevNullConfiguration.yaml new file mode 100644 index 000000000..bda7fba1d --- /dev/null +++ b/test/e2e/yaml/udevDevNullConfiguration.yaml @@ -0,0 +1,10 @@ +apiVersion: akri.sh/v0 +kind: Configuration +metadata: + name: akri-udev-dev-null +spec: + discoveryHandler: + name: udev + discoveryDetails: |+ + udevRules: + - KERNEL=="null" \ No newline at end of file diff --git a/test/e2e/yaml/udevGroupedConfiguration.yaml b/test/e2e/yaml/udevGroupedConfiguration.yaml new file mode 100644 index 000000000..1c218d9fd --- /dev/null +++ b/test/e2e/yaml/udevGroupedConfiguration.yaml @@ -0,0 +1,11 @@ +apiVersion: akri.sh/v0 +kind: Configuration +metadata: + name: akri-udev-grouped +spec: + discoveryHandler: + name: udev + discoveryDetails: |+ + groupRecursive: true + udevRules: + - ENV{{ID_PATH}}=="{}" \ No newline at end of file