diff --git a/test/assets/generic-device-plugin/fake-serial-communication.py b/test/assets/generic-device-plugin/fake-serial-communication.py new file mode 100755 index 0000000000..f256b6349e --- /dev/null +++ b/test/assets/generic-device-plugin/fake-serial-communication.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 + +""" +This script simulates a simple serial communication between a host and a pod. +It supports two modes of operation: 'host' and 'pod': + - Host listens for a message and responds. + - Pod sends a message and waits for a response. +Both sides are verifying the messages they send and receive. +""" + +import serial +import sys + +DEVICE_POD = "/dev/ttyPipeB0" +DEVICE_HOST = "/dev/ttyPipeA0" + +MSG_1 = b"HELLO\n" +MSG_2 = b"THERE\n" + + +def send_msg(ser, msg): + print(f"Sending message: {msg}") + ser.write(msg) + + +def recv_msg(ser, expected_msg): + print(f"Listening for a message. Expecting: {expected_msg}") + line = ser.readline() + print(f"Received message: {line}") + if expected_msg != line: + print("Received message does not match expected one") + sys.exit(1) + + +def host(): + s = serial.Serial(DEVICE_HOST, timeout=60) + recv_msg(s, MSG_1) + send_msg(s, MSG_2) + print("Test successful") + + +def pod(): + s = serial.Serial(DEVICE_POD, timeout=10) + send_msg(s, MSG_1) + recv_msg(s, MSG_2) + print("Test successful") + + +if len(sys.argv) == 1: + print("Not enough args") + sys.exit(1) + +mode = sys.argv[1] +if mode == "host": + host() +elif mode == "pod": + pod() +else: + print(f"Invalid arg: {mode}. Accepted args: host | pod") + sys.exit(1) diff --git a/test/assets/generic-device-plugin/job.yaml b/test/assets/generic-device-plugin/job.yaml new file mode 100644 index 0000000000..8daeffac46 --- /dev/null +++ b/test/assets/generic-device-plugin/job.yaml @@ -0,0 +1,31 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: gdp-test +spec: + template: + spec: + restartPolicy: Never + containers: + - name: gdp-test + image: registry.access.redhat.com/ubi9/ubi:9.6 + command: ["/bin/bash"] + args: ["/script/entrypoint.sh", "pod"] + resources: + limits: + device.microshift.io/fakeserial: 1 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] + runAsNonRoot: true + seccompProfile: + type: "RuntimeDefault" + volumeMounts: + - name: script-volume + mountPath: /script/ + volumes: + - name: script-volume + configMap: + name: gdp-script + defaultMode: 0755 diff --git a/test/image-blueprints-bootc/layer2-presubmit/group2/rhel96-bootc-source-optionals.containerfile b/test/image-blueprints-bootc/layer2-presubmit/group2/rhel96-bootc-source-optionals.containerfile index 701aa4791d..9abea74afc 100644 --- a/test/image-blueprints-bootc/layer2-presubmit/group2/rhel96-bootc-source-optionals.containerfile +++ b/test/image-blueprints-bootc/layer2-presubmit/group2/rhel96-bootc-source-optionals.containerfile @@ -26,3 +26,14 @@ RUN dnf repoinfo --enabled && \ rm -vf /etc/yum.repos.d/microshift-*.repo && \ rm -rvf $USHIFT_RPM_REPO_PATH && \ dnf clean all + +# Prepare system for testing Generic Device Plugin. +# hadolint ignore=DL3003 +RUN KERNEL_VER=$(rpm -q --qf "%{VERSION}-%{RELEASE}" kernel); \ + KERNEL_VER_ARCH="${KERNEL_VER}.$(uname -m)"; \ + dnf install -y git make "kernel-devel-${KERNEL_VER}" python3-pyserial && \ + dnf clean all && \ + git clone https://github.com/pmtk/serialsim.git /tmp/serialsim && \ + cd /tmp/serialsim && \ + make KERNEL="${KERNEL_VER_ARCH}" all install && \ + rm -rf /tmp/serialsim diff --git a/test/suites/optional/generic-device-plugin.robot b/test/suites/optional/generic-device-plugin.robot new file mode 100644 index 0000000000..9c8f21b905 --- /dev/null +++ b/test/suites/optional/generic-device-plugin.robot @@ -0,0 +1,111 @@ +*** Settings *** +Documentation Generic Device Plugin + +Resource ../../resources/microshift-config.resource +Resource ../../resources/microshift-process.resource +Variables strings.py +Library strings.py + +Suite Setup Setup Suite With Namespace +Suite Teardown Teardown Suite With Namespace + + +*** Variables *** +${NAMESPACE} ${EMPTY} + + +*** Test Cases *** +Sanity Test + [Documentation] Performs a simple test of Generic Device Plugin + [Setup] Run Keywords + ... Enable And Configure GDP + ... Enable Serialsim + ... Copy Script To Host + + Wait Until Device Is Allocatable + + Command Should Work crictl pull registry.access.redhat.com/ubi9/ubi:9.6 + Start Script On Host + Create Test Job + + Wait For Job Completion And Check Logs + + [Teardown] Run Keywords + ... Stop Script On Host + ... Disable GDP + + +*** Keywords *** +Enable And Configure GDP + [Documentation] Enables GDP and adds fake device path in MicroShift configuration + Drop In MicroShift Config ${GDP_CONFIG_DROPIN} 10-gdp + Restart MicroShift + +Disable GDP + [Documentation] Removes GDP configuration drop-in + Remove Drop In MicroShift Config 10-gdp + Restart MicroShift + +Enable Serialsim + [Documentation] Enables the serialsim kernel module. + ... serialsim creates echo and pipe devices. + Command Should Work modprobe serialsim + +Copy Script To Host + [Documentation] Starts fake serial communication script to the host + Put File + ... ./assets/generic-device-plugin/fake-serial-communication.py + ... /tmp/fake-serial-communication.py + +Start Script On Host + [Documentation] Starts fake serial communication script on the host in the background + ${cmd}= Catenate + ... systemd-run -u gdp-test-comm + ... python /tmp/fake-serial-communication.py host + Command Should Work ${cmd} + +Stop Script On Host + [Documentation] Attempts to stop the fake serial communication script on the host. + ... If it was successful, the unit is deleted automatically. + Command Execution journalctl -u gdp-test-comm.service + Command Execution systemctl stop gdp-test-comm + Command Execution systemctl reset-failed gdp-test-comm + +Create Test Job + [Documentation] Creates Job that spawns test Pod running to completion. + ${script}= OperatingSystem.Get File ./assets/generic-device-plugin/fake-serial-communication.py + ${configmap}= Append To Preamble ${script} + Log ${configmap} + ${path}= Create Random Temp File ${configmap} + Oc Create -f ${path} -n ${NAMESPACE} + Oc Create -f ./assets/generic-device-plugin/job.yaml -n ${NAMESPACE} + +Wait Until Device Is Allocatable + [Documentation] Waits until device device.microshift.io/fakeserial is allocatable + ${node}= Run With Kubeconfig oc get node -o=name + ${node_name}= Remove String ${node} node/ + + Wait Until Keyword Succeeds 60s 5s + ... Device Should Be Allocatable ${node_name} + +Device Should Be Allocatable + [Documentation] Checks if device device.microshift.io/fakeserial is allocatable + [Arguments] ${node_name} + ${device_amount}= Oc Get JsonPath + ... node + ... ${EMPTY} + ... ${node_name} + ... .status.allocatable.device\\.microshift\\.io/fakeserial + Should Be Equal As Integers ${device_amount} 1 + +Wait For Job Completion And Check Logs + [Documentation] Waits for Job completion and checks Pod logs looking for 'Test successful' message + + Oc Wait -n ${NAMESPACE} job/gdp-test --for=condition=complete --timeout=120s + ${pod}= Oc Get JsonPath + ... pod + ... ${NAMESPACE} + ... --selector=batch.kubernetes.io/job-name=gdp-test + ... .items[*].metadata.name + ${logs}= Oc Logs ${pod} ${NAMESPACE} + Should Contain ${logs} Test successful diff --git a/test/suites/optional/strings.py b/test/suites/optional/strings.py new file mode 100644 index 0000000000..34e9f6e984 --- /dev/null +++ b/test/suites/optional/strings.py @@ -0,0 +1,31 @@ +GDP_CONFIG_DROPIN = ''' +genericDevicePlugin: + status: Enabled + devices: + - name: fakeserial + groups: + - paths: + - path: /dev/ttyPipeB0 +''' + +CONFIGMAP_PREAMBLE = ''' +apiVersion: v1 +kind: ConfigMap +metadata: + name: gdp-script +data: + entrypoint.sh: | + #!/usr/bin/env bash + # Fake user homedir for installing pip and pyserial. + HOME=/tmp python3 -m ensurepip --upgrade + HOME=/tmp python3 -m pip install pyserial + HOME=/tmp /script/fake-serial-communication.py pod + + fake-serial-communication.py: | +''' + + +def append_to_preamble(content: str) -> str: + # Add 4 spaces before each line + content = " " + content.replace("\n", "\n ") + return CONFIGMAP_PREAMBLE + content