diff --git a/test/assets/router/error-page-404.http b/test/assets/router/error-page-404.http
new file mode 100644
index 0000000000..9cce4eddd6
--- /dev/null
+++ b/test/assets/router/error-page-404.http
@@ -0,0 +1,10 @@
+HTTP/1.0 404 Not Found
+Connection: close
+Content-Type: text/html
+
+
+
Custom:Not Found
+
+Custom error page:The requested document was not found.
+
+
diff --git a/test/assets/router/error-page-503.http b/test/assets/router/error-page-503.http
new file mode 100644
index 0000000000..90c349904e
--- /dev/null
+++ b/test/assets/router/error-page-503.http
@@ -0,0 +1,10 @@
+HTTP/1.0 503 Service Unavailable
+Connection: close
+Content-Type: text/html
+
+
+Custom:Application Unavailable
+
+Custom error page:The requested application is not available.
+
+
diff --git a/test/assets/router/microshift-ingress-destca.yaml b/test/assets/router/microshift-ingress-destca.yaml
new file mode 100644
index 0000000000..ac3452ff65
--- /dev/null
+++ b/test/assets/router/microshift-ingress-destca.yaml
@@ -0,0 +1,19 @@
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: ingress-ms-reen
+ annotations:
+ route.openshift.io/destination-ca-certificate-secret: service-secret
+ route.openshift.io/termination: reencrypt
+spec:
+ rules:
+ - host: service-secure-test.example.com
+ http:
+ paths:
+ - backend:
+ service:
+ name: service-secure
+ port:
+ number: 27443
+ path: "/"
+ pathType: Prefix
diff --git a/test/assets/router/microshift-ingress-http.yaml b/test/assets/router/microshift-ingress-http.yaml
new file mode 100644
index 0000000000..16fdb304d9
--- /dev/null
+++ b/test/assets/router/microshift-ingress-http.yaml
@@ -0,0 +1,16 @@
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: ingress-on-microshift
+spec:
+ rules:
+ - host: service-unsecure-test.example.com
+ http:
+ paths:
+ - backend:
+ service:
+ name: service-unsecure
+ port:
+ number: 27017
+ path: "/"
+ pathType: Prefix
diff --git a/test/assets/router/rsyslogd-pod.yaml b/test/assets/router/rsyslogd-pod.yaml
new file mode 100644
index 0000000000..07ba1425bc
--- /dev/null
+++ b/test/assets/router/rsyslogd-pod.yaml
@@ -0,0 +1,20 @@
+kind: Pod
+apiVersion: v1
+metadata:
+ name: rsyslogd-pod
+ labels:
+ name: rsyslogd
+spec:
+ containers:
+ - image: quay.io/openshifttest/rsyslogd-container@sha256:e806eb41f05d7cc6eec96bf09c7bcb692f97562d4a983cb019289bd048d9aee2
+ name: rsyslogd-container
+ securityContext:
+ privileged: true
+ ports:
+ - containerPort: 514
+ protocol: TCP
+ - containerPort: 514
+ protocol: UDP
+ resources:
+ limits:
+ memory: 340Mi
diff --git a/test/assets/router/test-client-pod.yaml b/test/assets/router/test-client-pod.yaml
new file mode 100644
index 0000000000..05c2e6638d
--- /dev/null
+++ b/test/assets/router/test-client-pod.yaml
@@ -0,0 +1,22 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ labels:
+ app: hello-pod
+ name: hello-pod
+spec:
+ securityContext:
+ runAsNonRoot: true
+ seccompProfile:
+ type: RuntimeDefault
+ containers:
+ - image: quay.io/openshifttest/nginx-alpine@sha256:cee6930776b92dc1e93b73f9e5965925d49cff3d2e91e1d071c2f0ff72cbca29
+ name: hello-pod
+ securityContext:
+ allowPrivilegeEscalation: false
+ capabilities:
+ drop:
+ - ALL
+ ports:
+ - containerPort: 8080
+ - containerPort: 8443
diff --git a/test/assets/router/web-server-deploy.yaml b/test/assets/router/web-server-deploy.yaml
new file mode 100644
index 0000000000..9c31424f20
--- /dev/null
+++ b/test/assets/router/web-server-deploy.yaml
@@ -0,0 +1,57 @@
+apiVersion: v1
+kind: List
+items:
+- apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ name: web-server-deploy
+ labels:
+ app: web-server-deploy
+ spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ name: web-server-deploy
+ template:
+ metadata:
+ labels:
+ name: web-server-deploy
+ spec:
+ containers:
+ - name: nginx
+ image: quay.io/openshifttest/nginx-alpine@sha256:cee6930776b92dc1e93b73f9e5965925d49cff3d2e91e1d071c2f0ff72cbca29
+ ports:
+ - containerPort: 8080
+ name: http
+ protocol: TCP
+ - containerPort: 8443
+ name: https
+ protocol: TCP
+- kind: Service
+ apiVersion: v1
+ metadata:
+ labels:
+ name: service-secure
+ name: service-secure
+ spec:
+ ports:
+ - name: https
+ protocol: TCP
+ port: 27443
+ targetPort: 8443
+ selector:
+ name: web-server-deploy
+- apiVersion: v1
+ kind: Service
+ metadata:
+ labels:
+ name: service-unsecure
+ name: service-unsecure
+ spec:
+ ports:
+ - name: http
+ port: 27017
+ protocol: TCP
+ targetPort: 8080
+ selector:
+ name: web-server-deploy
\ No newline at end of file
diff --git a/test/assets/router/web-server-signed-deploy.yaml b/test/assets/router/web-server-signed-deploy.yaml
new file mode 100644
index 0000000000..1a77bb7c09
--- /dev/null
+++ b/test/assets/router/web-server-signed-deploy.yaml
@@ -0,0 +1,94 @@
+apiVersion: v1
+kind: List
+items:
+- apiVersion: v1
+ kind: ConfigMap
+ metadata:
+ name: nginx-config
+ data:
+ nginx.conf: |
+ events {
+ worker_connections 1024;
+ }
+
+ http {
+ server {
+ listen 8080;
+ listen [::]:8080;
+ location / {
+ root /data/http;
+ }
+ }
+
+ server {
+ listen 8443 ssl http2 default;
+ listen [::]:8443 ssl http2 default;
+ server_name _;
+ ssl_certificate certs/tls.crt;
+ ssl_certificate_key certs/tls.key;
+ location / {
+ root /data/https-default;
+ }
+ }
+ }
+- apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ name: web-server-deploy
+ labels:
+ name: web-server-deploy
+ spec:
+ replicas: 1
+ selector:
+ matchExpressions:
+ - {key: name, operator: In, values: [web-server-deploy]}
+ template:
+ metadata:
+ labels:
+ name: web-server-deploy
+ spec:
+ containers:
+ - name: nginx
+ image: quay.io/openshifttest/nginx-alpine@sha256:cee6930776b92dc1e93b73f9e5965925d49cff3d2e91e1d071c2f0ff72cbca29
+ volumeMounts:
+ - name: service-secret
+ mountPath: /etc/nginx/certs/
+ - name: nginx-config
+ mountPath: /etc/nginx/
+ volumes:
+ - name: service-secret
+ secret:
+ secretName: service-secret
+ - name: nginx-config
+ configMap:
+ name: nginx-config
+- kind: Service
+ apiVersion: v1
+ metadata:
+ annotations:
+ service.beta.openshift.io/serving-cert-secret-name: service-secret
+ labels:
+ name: service-secure
+ name: service-secure
+ spec:
+ ports:
+ - name: https
+ protocol: TCP
+ port: 27443
+ targetPort: 8443
+ selector:
+ name: web-server-deploy
+- apiVersion: v1
+ kind: Service
+ metadata:
+ labels:
+ name: service-unsecure
+ name: service-unsecure
+ spec:
+ ports:
+ - name: http
+ port: 27017
+ protocol: TCP
+ targetPort: 8080
+ selector:
+ name: web-server-deploy
diff --git a/test/bin/run-single-test.sh b/test/bin/run-single-test.sh
new file mode 100755
index 0000000000..632a50fdb9
--- /dev/null
+++ b/test/bin/run-single-test.sh
@@ -0,0 +1,70 @@
+#!/bin/bash
+set -euo pipefail
+
+# Run a single Robot Framework test against a remote MicroShift cluster.
+#
+# Usage:
+# ./test/bin/run-single-test.sh [test-name]
+#
+# Examples:
+# ./test/bin/run-single-test.sh test/variables.yaml suites/router/router-config-infra.robot
+# ./test/bin/run-single-test.sh test/variables.yaml suites/router/router-config-infra.robot 'Custom Listening IPs And Ports'
+
+VARIABLES_FILE="$(cd "$(dirname "${1:?Usage: $0 [test-name]}")" && pwd)/$(basename "$1")"
+ROBOT_FILE="${2:?Usage: $0 [test-name]}"
+TEST_NAME="${3:-}"
+
+ROOTDIR="$(cd "$(dirname "$0")/../.." && pwd)"
+TESTDIR="${ROOTDIR}/test"
+RF_VENV="${ROOTDIR}/_output/robotenv"
+OUTDIR="${ROOTDIR}/_output/rf-single-$(date +%Y%m%d-%H%M%S)"
+
+if [[ ! -f "${VARIABLES_FILE}" ]]; then
+ echo "ERROR: Variables file not found: ${VARIABLES_FILE}"
+ echo ""
+ echo "Create one from the example:"
+ echo " cp test/variables.yaml.example test/variables.yaml"
+ echo ""
+ echo "Required fields:"
+ echo " USHIFT_HOST: "
+ echo " USHIFT_USER: "
+ echo " SSH_PRIV_KEY: "
+ echo " SSH_PORT: 22"
+ echo " API_PORT: 6443"
+ exit 1
+fi
+
+if [[ ! -d "${RF_VENV}" ]]; then
+ echo "Setting up Robot Framework venv..."
+ "${ROOTDIR}/scripts/fetch_tools.sh" robotframework
+fi
+
+RF_BINARY="${RF_VENV}/bin/robot"
+if [[ ! -f "${RF_BINARY}" ]]; then
+ echo "ERROR: robot binary not found at ${RF_BINARY}"
+ exit 1
+fi
+
+mkdir -p "${OUTDIR}"
+
+TEST_FLAG=()
+if [[ -n "${TEST_NAME}" ]]; then
+ TEST_FLAG=(-t "${TEST_NAME}")
+ echo "=== Running test: ${TEST_NAME} ==="
+else
+ echo "=== Running all tests ==="
+fi
+echo "Variables: ${VARIABLES_FILE}"
+echo "Robot file: ${ROBOT_FILE}"
+echo "Output dir: ${OUTDIR}"
+echo ""
+
+cd "${TESTDIR}"
+
+exec "${RF_BINARY}" \
+ --loglevel TRACE \
+ -V "${VARIABLES_FILE}" \
+ --outputdir "${OUTDIR}" \
+ --pythonpath resources \
+ ${TEST_FLAG+"${TEST_FLAG[@]}"} \
+ "${ROBOT_FILE}"
diff --git a/test/resources/router.resource b/test/resources/router.resource
new file mode 100644
index 0000000000..07066621af
--- /dev/null
+++ b/test/resources/router.resource
@@ -0,0 +1,412 @@
+*** Settings ***
+Documentation Keywords for router end-to-end testing
+
+Library Collections
+Library OperatingSystem
+Library Process
+Library String
+Library SSHLibrary
+Resource oc.resource
+Resource common.resource
+Resource kubeconfig.resource
+Resource microshift-config.resource
+Resource microshift-process.resource
+
+
+*** Variables ***
+${WEB_SERVER_DEPLOY} ./assets/router/web-server-deploy.yaml
+${WEB_SERVER_SIGNED_DEPLOY} ./assets/router/web-server-signed-deploy.yaml
+${TEST_CLIENT_POD} ./assets/router/test-client-pod.yaml
+${INGRESS_HTTP} ./assets/router/microshift-ingress-http.yaml
+${INGRESS_DESTCA} ./assets/router/microshift-ingress-destca.yaml
+${RSYSLOGD_POD} ./assets/router/rsyslogd-pod.yaml
+${ROUTER_NS} openshift-ingress
+${CLIENT_POD_NAME} hello-pod
+${CLIENT_POD_LABEL} app\=hello-pod
+${WEB_SERVER_LABEL} name\=web-server-deploy
+
+
+*** Keywords ***
+Deploy Web Server
+ [Documentation] Deploy the nginx web server (unsecured) with services.
+ [Arguments] ${ns}=${NAMESPACE}
+ Oc Create -f ${WEB_SERVER_DEPLOY} -n ${ns}
+ Labeled Pod Should Be Ready ${WEB_SERVER_LABEL} ns=${ns} timeout=120s
+
+Deploy Web Server Signed
+ [Documentation] Deploy the nginx web server with TLS (service-serving-cert) and services.
+ [Arguments] ${ns}=${NAMESPACE}
+ Oc Create -f ${WEB_SERVER_SIGNED_DEPLOY} -n ${ns}
+ Labeled Pod Should Be Ready ${WEB_SERVER_LABEL} ns=${ns} timeout=120s
+
+Deploy Test Client Pod
+ [Documentation] Deploy the test client pod for running curl commands.
+ [Arguments] ${ns}=${NAMESPACE}
+ Oc Create -f ${TEST_CLIENT_POD} -n ${ns}
+ Labeled Pod Should Be Ready ${CLIENT_POD_LABEL} ns=${ns} timeout=120s
+
+Deploy Rsyslogd Pod
+ [Documentation] Deploy the rsyslogd pod for syslog testing.
+ [Arguments] ${ns}=${NAMESPACE}
+ Oc Create -f ${RSYSLOGD_POD} -n ${ns}
+ Labeled Pod Should Be Ready name\=rsyslogd ns=${ns} timeout=120s
+
+Get Web Server Pod Name
+ [Documentation] Get the name of the first web-server-deploy pod.
+ [Arguments] ${ns}=${NAMESPACE}
+ ${name}= Run With Kubeconfig
+ ... oc get pod -l name=web-server-deploy -n ${ns} -o jsonpath='{.items[0].metadata.name}'
+ RETURN ${name}
+
+Create OC Route
+ [Documentation] Create an OpenShift route of the given type.
+ ... route_type: http (uses oc expose), or edge/passthrough/reencrypt (uses oc create route)
+ [Arguments] ${ns} ${route_type} ${route_name} ${service_name} @{extra_args}
+ IF "${route_type}" == "http"
+ ${extra}= Catenate SEPARATOR=${SPACE} @{extra_args}
+ Run With Kubeconfig oc expose -n ${ns} service ${service_name} --name=${route_name} ${extra}
+ ELSE
+ ${extra}= Catenate SEPARATOR=${SPACE} @{extra_args}
+ Run With Kubeconfig oc create route ${route_type} ${route_name} --service=${service_name} -n ${ns} ${extra}
+ END
+
+Route Should Be Admitted
+ [Documentation] Verify the route is admitted by the default ingress controller.
+ [Arguments] ${route_name} ${ns}=${NAMESPACE} ${timeout}=120s
+ Wait Until Keyword Succeeds ${timeout} 2s
+ ... Route Admission Status Should Be ${route_name} ${ns} True
+
+Route Should Not Be Admitted
+ [Documentation] Verify the route is NOT admitted by the default ingress controller.
+ [Arguments] ${route_name} ${ns}=${NAMESPACE}
+ Wait Until Keyword Succeeds 60s 2s
+ ... Route Admission Status Should Be ${route_name} ${ns} False
+
+Route Admission Status Should Be
+ [Documentation] Helper: assert route admission status equals expected.
+ [Arguments] ${route_name} ${ns} ${expected}
+ ${status}= Oc Get JsonPath route ${ns} ${route_name}
+ ... .status.ingress[0].conditions[0].status
+ Should Be Equal As Strings ${status} ${expected}
+
+Create And Admit Four Route Types
+ [Documentation] Create HTTP, edge, passthrough, and reencrypt routes and wait for reencrypt to be admitted.
+ [Arguments] ${http_host} ${edge_host} ${pass_host} ${reen_host}
+ Create OC Route ${NAMESPACE} http route-http service-unsecure --hostname=${http_host}
+ Create OC Route ${NAMESPACE} edge route-edge service-unsecure --hostname=${edge_host}
+ Create OC Route ${NAMESPACE} passthrough route-passth service-secure --hostname=${pass_host}
+ Create OC Route ${NAMESPACE} reencrypt route-reen service-secure --hostname=${reen_host}
+ Route Should Be Admitted route-http
+ Route Should Be Admitted route-edge
+ Route Should Be Admitted route-passth
+ Route Should Be Admitted route-reen
+
+Curl From Pod
+ [Documentation] Run curl from a pod via oc exec. Returns stdout.
+ [Arguments] ${pod_name} ${ns} ${url} ${resolve} ${flags}=${EMPTY}
+ ${output}= Run With Kubeconfig
+ ... oc exec -n ${ns} ${pod_name} -- curl ${url} -sI --resolve ${resolve} --connect-timeout 10 ${flags}
+ RETURN ${output}
+
+Curl From Pod Should Contain
+ [Documentation] Curl from a pod and assert response contains expected string (no extra flags).
+ [Arguments] ${pod_name} ${ns} ${url} ${resolve} ${expected}=200
+ ${output}= Curl From Pod ${pod_name} ${ns} ${url} ${resolve}
+ Should Contain ${output} ${expected}
+
+Wait Until Curl Succeeds From Pod
+ [Documentation] Retry curl from a pod (no extra flags) until response contains expected string.
+ [Arguments] ${pod_name} ${ns} ${url} ${resolve} ${expected}=200
+ Wait Until Keyword Succeeds 120s 2s
+ ... Curl From Pod Should Contain ${pod_name} ${ns} ${url} ${resolve} ${expected}
+
+Curl HTTPS From Pod Should Contain
+ [Documentation] Curl HTTPS from a pod using -k, assert response contains expected string.
+ [Arguments] ${pod_name} ${ns} ${url} ${resolve} ${expected}=200
+ ${output}= Curl From Pod ${pod_name} ${ns} ${url} ${resolve} -k
+ Should Contain ${output} ${expected}
+
+Wait Until HTTPS Curl Succeeds From Pod
+ [Documentation] Retry HTTPS curl (with -k) from a pod until response contains expected string.
+ [Arguments] ${pod_name} ${ns} ${url} ${resolve} ${expected}=200
+ Wait Until Keyword Succeeds 120s 2s
+ ... Curl HTTPS From Pod Should Contain ${pod_name} ${ns} ${url} ${resolve} ${expected}
+
+Curl With Cookie Should Return 200
+ [Documentation] Curl from a pod with a cookie and assert 200 response.
+ [Arguments] ${pod_name} ${ns} ${url} ${resolve} ${cookie}
+ ${output}= Curl From Pod ${pod_name} ${ns} ${url} ${resolve} -b ${cookie}
+ Should Contain ${output} 200
+
+Wait Until Curl With Cookie Succeeds From Pod
+ [Documentation] Retry curl with a cookie until 200 response.
+ [Arguments] ${pod_name} ${ns} ${url} ${resolve} ${cookie}
+ Wait Until Keyword Succeeds 120s 2s
+ ... Curl With Cookie Should Return 200 ${pod_name} ${ns} ${url} ${resolve} ${cookie}
+
+HTTPS Curl With Cookie Should Return 200
+ [Documentation] Curl HTTPS from a pod with a cookie (using -k) and assert 200 response.
+ [Arguments] ${pod_name} ${ns} ${url} ${resolve} ${cookie}
+ ${output}= Curl From Pod ${pod_name} ${ns} ${url} ${resolve} -k -b ${cookie}
+ Should Contain ${output} 200
+
+Wait Until HTTPS Curl With Cookie Succeeds From Pod
+ [Documentation] Retry HTTPS curl with a cookie until 200 response.
+ [Arguments] ${pod_name} ${ns} ${url} ${resolve} ${cookie}
+ Wait Until Keyword Succeeds 120s 2s
+ ... HTTPS Curl With Cookie Should Return 200 ${pod_name} ${ns} ${url} ${resolve} ${cookie}
+
+Curl With Client Cert Should Return
+ [Documentation] Curl with client cert from /data/certs/ and assert expected response.
+ [Arguments] ${pod_name} ${ns} ${url} ${resolve} ${expected}=200 OK
+ ${output}= Curl From Pod ${pod_name} ${ns} ${url} ${resolve}
+ ... --cacert /data/certs/ca.crt --cert /data/certs/usr.crt --key /data/certs/usr.key
+ Should Contain ${output} ${expected}
+
+Wait Until Curl With Client Cert Succeeds
+ [Documentation] Retry curl with client cert until expected response.
+ [Arguments] ${pod_name} ${ns} ${url} ${resolve} ${expected}=200 OK
+ Wait Until Keyword Succeeds 120s 2s
+ ... Curl With Client Cert Should Return ${pod_name} ${ns} ${url} ${resolve} ${expected}
+
+Curl With Cert File Should Return 200
+ [Documentation] Curl with a specific cert file prefix from /data/certs/ and assert 200 response.
+ [Arguments] ${pod_name} ${ns} ${url} ${resolve} ${cert_prefix}
+ ${output}= Curl From Pod ${pod_name} ${ns} ${url} ${resolve}
+ ... --cacert /data/certs/ca.crt --cert /data/certs/${cert_prefix}.crt --key /data/certs/${cert_prefix}.key
+ Should Contain ${output} 200 OK
+
+Wait Until Curl With Cert File Succeeds
+ [Documentation] Retry curl with specific cert file until 200 response.
+ [Arguments] ${pod_name} ${ns} ${url} ${resolve} ${cert_prefix}
+ Wait Until Keyword Succeeds 120s 2s
+ ... Curl With Cert File Should Return 200 ${pod_name} ${ns} ${url} ${resolve} ${cert_prefix}
+
+Curl With Cert File Should Return 403
+ [Documentation] Curl with a specific cert file prefix from /data/certs/ and assert 403 response.
+ [Arguments] ${pod_name} ${ns} ${url} ${resolve} ${cert_prefix}
+ ${output}= Curl From Pod ${pod_name} ${ns} ${url} ${resolve}
+ ... --cacert /data/certs/ca.crt --cert /data/certs/${cert_prefix}.crt --key /data/certs/${cert_prefix}.key
+ Should Contain ${output} 403
+
+Wait Until Curl With Cert File Returns 403
+ [Documentation] Retry curl with specific cert file until 403 response.
+ [Arguments] ${pod_name} ${ns} ${url} ${resolve} ${cert_prefix}
+ Wait Until Keyword Succeeds 120s 2s
+ ... Curl With Cert File Should Return 403 ${pod_name} ${ns} ${url} ${resolve} ${cert_prefix}
+
+Curl Without Cert Should Return SSL Error
+ [Documentation] Curl without a client cert and assert SSL handshake error (for mTLS Required).
+ [Arguments] ${pod_name} ${ns} ${url} ${resolve}
+ ${output}= Run With Kubeconfig
+ ... oc exec -n ${ns} ${pod_name} -- curl ${url} -sIkv --resolve ${resolve} --connect-timeout 10
+ ... allow_fail=True
+ Should Contain ${output} SSL_read
+
+Get Router Pod Name
+ [Documentation] Get the name of the router-default pod.
+ ${name}= Run With Kubeconfig
+ ... oc get pod -l ingresscontroller.operator.openshift.io/deployment-ingresscontroller=default -n ${ROUTER_NS} -o jsonpath='{.items[0].metadata.name}'
+ RETURN ${name}
+
+Get Router Pod IP
+ [Documentation] Get the pod IP of the router pod.
+ ${pod_name}= Get Router Pod Name
+ ${ip}= Oc Get JsonPath pod ${ROUTER_NS} ${pod_name} .status.podIP
+ RETURN ${ip}
+
+Router Pod Env Should Have Value
+ [Documentation] Check an env var value in the router pod.
+ [Arguments] ${env_name} ${expected_value}
+ ${actual}= Oc Get JsonPath
+ ... pod ${ROUTER_NS} ${EMPTY}
+ ... .items[*].spec.containers[*].env[?(@.name=="${env_name}")].value
+ Should Be Equal As Strings ${actual} ${expected_value}
+
+Read Haproxy Config
+ [Documentation] Read haproxy.config from the router pod.
+ ${pod_name}= Get Router Pod Name
+ ${output}= Run With Kubeconfig
+ ... oc exec -n ${ROUTER_NS} ${pod_name} -- cat haproxy.config
+ RETURN ${output}
+
+Get Router Access Logs
+ [Documentation] Get access logs from the router pod's logs container.
+ ${output}= Oc Logs deployment/router-default -c logs ${ROUTER_NS}
+ RETURN ${output}
+
+Wait For Router Logs To Contain
+ [Documentation] Wait for the router access logs to contain a specific pattern.
+ [Arguments] ${pattern} ${timeout}=120s
+ Wait Until Keyword Succeeds ${timeout} 5s
+ ... Router Logs Should Contain ${pattern}
+
+Router Logs Should Contain
+ [Documentation] Check that router access logs contain a pattern.
+ [Arguments] ${pattern}
+ ${logs}= Get Router Access Logs
+ Should Contain ${logs} ${pattern}
+
+Wait For Router Ready
+ [Documentation] Wait for the default router deployment to be available.
+ Oc Wait namespace/openshift-ingress --for jsonpath='{.status.phase}=Active' --timeout=5m
+ Named Deployment Should Be Available router-default ${ROUTER_NS} 5m
+
+Setup Router Config And Restart
+ [Documentation] Apply drop-in config and restart MicroShift, then wait for router ready.
+ [Arguments] ${config_content}
+ Drop In MicroShift Config ${config_content} 10-router
+ Restart MicroShift
+ Wait For Router Ready
+
+Remove Router Config And Restart
+ [Documentation] Remove the drop-in config and restart MicroShift, then wait for router ready.
+ Remove Drop In MicroShift Config 10-router
+ Restart MicroShift
+ Wait For Router Ready
+
+Cleanup Test Workloads
+ [Documentation] Delete all routes, deployments, services, configmaps, and pods created by tests in the namespace.
+ Run With Kubeconfig oc delete route,deployment,service,configmap,pod --all -n ${NAMESPACE} --ignore-not-found
+ ... allow_fail=True
+
+Get LB IPs
+ [Documentation] Get all load balancer IPs of the router-default service as a space-separated string.
+ ${ips}= Oc Get JsonPath
+ ... service ${ROUTER_NS} router-default
+ ... .status.loadBalancer.ingress[*].ip
+ RETURN ${ips}
+
+Get LB Port
+ [Documentation] Get a specific named port of the router-default service.
+ [Arguments] ${port_name}
+ ${port}= Oc Get JsonPath
+ ... service ${ROUTER_NS} router-default
+ ... .spec.ports[?(@.name=="${port_name}")].port
+ RETURN ${port}
+
+Create Configmap From Files
+ [Documentation] Create a configmap from one or more local file paths.
+ [Arguments] ${ns} ${name} @{from_files}
+ ${file_args}= Catenate SEPARATOR=${SPACE} @{from_files}
+ Run With Kubeconfig oc create configmap ${name} ${file_args} -n ${ns}
+
+Scale Deployment
+ [Documentation] Scale a deployment to a given number of replicas.
+ [Arguments] ${ns} ${deploy_name} ${replicas}
+ Run With Kubeconfig oc scale deployment/${deploy_name} --replicas=${replicas} -n ${ns}
+
+Copy Files To Pod
+ [Documentation] Copy a local path to a pod path via oc cp.
+ [Arguments] ${ns} ${pod_name} ${src} ${dest}
+ Run With Kubeconfig oc cp -n ${ns} ${src} ${ns}/${pod_name}:${dest}
+
+Generate CA Certificate
+ [Documentation] Generate a self-signed CA key and certificate.
+ [Arguments] ${ca_key} ${ca_crt} ${ca_subj}
+ ${result}= Run Process openssl genrsa -out ${ca_key} 2048 shell=True
+ Should Be Equal As Integers ${result.rc} 0
+ ${result}= Run Process
+ ... openssl req -x509 -new -nodes -key ${ca_key} -sha256 -days 30 -subj "${ca_subj}" -out ${ca_crt}
+ ... shell=True
+ Should Be Equal As Integers ${result.rc} 0
+
+Generate CSR And Key
+ [Documentation] Generate a private key and a CSR for the given subject.
+ [Arguments] ${key_file} ${csr_file} ${subj}
+ ${result}= Run Process openssl genrsa -out ${key_file} 2048 shell=True
+ Should Be Equal As Integers ${result.rc} 0
+ ${result}= Run Process
+ ... openssl req -new -key ${key_file} -subj "${subj}" -out ${csr_file}
+ ... shell=True
+ Should Be Equal As Integers ${result.rc} 0
+
+Sign CSR With CA
+ [Documentation] Sign a CSR with a CA, writing SAN config to a temp file when provided.
+ [Arguments] ${csr_file} ${ca_crt} ${ca_key} ${signed_crt} ${san}=${EMPTY}
+ IF "${san}" != "${EMPTY}"
+ VAR ${ext_file}= /tmp/router-san-${signed_crt.replace('/', '-')}.cnf
+ Create File ${ext_file} ${san}\n
+ ${result}= Run Process
+ ... openssl x509 -req -in ${csr_file} -CA ${ca_crt} -CAkey ${ca_key} -CAcreateserial -out ${signed_crt} -days 30 -sha256 -extfile ${ext_file}
+ ... shell=True
+ Remove File ${ext_file}
+ ELSE
+ ${result}= Run Process
+ ... openssl x509 -req -in ${csr_file} -CA ${ca_crt} -CAkey ${ca_key} -CAcreateserial -out ${signed_crt} -days 30 -sha256
+ ... shell=True
+ END
+ Should Be Equal As Integers ${result.rc} 0
+
+Generate MTLS Client Cert
+ [Documentation] Generate a CA and a single client cert in the given directory.
+ [Arguments] ${tmpdir} ${edge_host}
+ Generate CA Certificate ${tmpdir}/ca.key ${tmpdir}/ca.crt /CN=MS-Test-Root-CA
+ Generate CSR And Key ${tmpdir}/usr.key ${tmpdir}/usr.csr /CN=example-test.com
+ VAR ${san}= subjectAltName = DNS.1:*.${BASE_DOMAIN},DNS.2:${edge_host}
+ Sign CSR With CA ${tmpdir}/usr.csr ${tmpdir}/ca.crt ${tmpdir}/ca.key ${tmpdir}/usr.crt ${san}
+
+Generate Client Cert File In Dir
+ [Documentation] Generate a CSR, key, and signed cert in the given directory using a name prefix.
+ [Arguments] ${tmpdir} ${host} ${cn} ${cert_prefix}
+ Generate CSR And Key ${tmpdir}/${cert_prefix}.key ${tmpdir}/${cert_prefix}.csr /CN=${cn}
+ VAR ${san}= subjectAltName = DNS.1:*.${BASE_DOMAIN},DNS.2:${host}
+ Sign CSR With CA
+ ... ${tmpdir}/${cert_prefix}.csr
+ ... ${tmpdir}/ca.crt
+ ... ${tmpdir}/ca.key
+ ... ${tmpdir}/${cert_prefix}.crt
+ ... ${san}
+
+Get First LB IP
+ [Documentation] Get the first load balancer IP of the router-default service.
+ ${ip}= Oc Get JsonPath
+ ... service ${ROUTER_NS} router-default
+ ... .status.loadBalancer.ingress[0].ip
+ RETURN ${ip}
+
+Get Router Deployment Generation
+ [Documentation] Get the current generation of the router-default deployment.
+ ${gen}= Oc Get JsonPath
+ ... deployment ${ROUTER_NS} router-default
+ ... .metadata.generation
+ RETURN ${gen}
+
+Get Host IPs Via SSH
+ [Documentation] Get all global IPv4 addresses from the MicroShift host.
+ ${stdout} ${stderr} ${rc}= SSHLibrary.Execute Command
+ ... ip -4 addr show scope global | grep -oP 'inet \\K[\\d.]+'
+ ... sudo=False return_rc=True return_stderr=True return_stdout=True
+ Should Be Equal As Integers 0 ${rc}
+ @{ips}= Split String ${stdout} \n
+ RETURN @{ips}
+
+Get First Host Interface And IP Via SSH
+ [Documentation] Get the first non-loopback IPv4 interface name and its IP from the host.
+ ${stdout} ${stderr} ${rc}= SSHLibrary.Execute Command
+ ... ip -4 addr show scope global | awk '/^[0-9]+:/{iface=$2; gsub(":","",iface)} /inet /{print iface, gensub(/\\/.*/, "", 1, $2); exit}'
+ ... sudo=False
+ ... return_rc=True
+ ... return_stderr=True
+ ... return_stdout=True
+ Should Be Equal As Integers 0 ${rc}
+ @{parts}= Split String ${stdout}
+ ${iface}= Get From List ${parts} 0
+ ${ip}= Get From List ${parts} 1
+ RETURN ${iface} ${ip}
+
+Show Invalid Drop In Config Should Fail With
+ [Documentation] Write an invalid drop-in config, run show-config, verify it fails with expected message, then remove the drop-in.
+ [Arguments] ${config_content} ${expected_error}
+ Drop In MicroShift Config ${config_content} 10-router-invalid
+ ${stdout} ${stderr} ${rc}= SSHLibrary.Execute Command
+ ... microshift show-config --mode effective
+ ... sudo=True return_rc=True return_stderr=True return_stdout=True
+ Remove Drop In MicroShift Config 10-router-invalid
+ Should Not Be Equal As Integers ${rc} 0
+ Should Contain ${stderr}${stdout} ${expected_error}
+
+Privileged Namespace
+ [Documentation] Grant privileged SCC to the default service account in a namespace.
+ [Arguments] ${ns}=${NAMESPACE}
+ Run With Kubeconfig oc adm policy add-scc-to-user privileged -z default -n ${ns}
diff --git a/test/scenarios-bootc/el10/presubmits/el102-src@router.sh b/test/scenarios-bootc/el10/presubmits/el102-src@router.sh
index 5185849f03..bc88766316 100644
--- a/test/scenarios-bootc/el10/presubmits/el102-src@router.sh
+++ b/test/scenarios-bootc/el10/presubmits/el102-src@router.sh
@@ -13,5 +13,5 @@ scenario_remove_vms() {
scenario_run_tests() {
run_tests host1 \
- suites/router
+ suites/router/router-basic.robot
}
diff --git a/test/scenarios-bootc/el10/releases/el102-lrel@osconfig-router.sh b/test/scenarios-bootc/el10/releases/el102-lrel@osconfig-router.sh
index 177e9ad3e6..24e4f58f7e 100644
--- a/test/scenarios-bootc/el10/releases/el102-lrel@osconfig-router.sh
+++ b/test/scenarios-bootc/el10/releases/el102-lrel@osconfig-router.sh
@@ -23,7 +23,7 @@ scenario_run_tests() {
run_tests host1 \
suites/osconfig/clusterid.robot \
suites/osconfig/systemd-resolved.robot \
- suites/router/ \
+ suites/router/router-basic.robot \
suites/otp-workloads/oc-cli.robot \
suites/otp-workloads/statefulset-pvc.robot
}
diff --git a/test/scenarios-bootc/el10/releases/el102-lrel@router-config-infra.sh b/test/scenarios-bootc/el10/releases/el102-lrel@router-config-infra.sh
new file mode 100644
index 0000000000..62a6f5d6a1
--- /dev/null
+++ b/test/scenarios-bootc/el10/releases/el102-lrel@router-config-infra.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+# Sourced from scenario.sh and uses functions defined there.
+
+start_image="rhel102-bootc-brew-lrel-optional"
+
+scenario_create_vms() {
+ exit_if_image_not_found "${start_image}"
+
+ prepare_kickstart host1 kickstart-bootc.ks.template "${start_image}"
+ launch_vm rhel102-bootc --vm_vcpus 4
+}
+
+scenario_remove_vms() {
+ exit_if_image_not_found "${start_image}"
+
+ remove_vm host1
+}
+
+scenario_run_tests() {
+ exit_if_image_not_found "${start_image}"
+
+ run_tests host1 \
+ suites/router/router-config-infra.robot
+}
diff --git a/test/scenarios-bootc/el10/releases/el102-lrel@router-config-logging.sh b/test/scenarios-bootc/el10/releases/el102-lrel@router-config-logging.sh
new file mode 100644
index 0000000000..7cadef9b7d
--- /dev/null
+++ b/test/scenarios-bootc/el10/releases/el102-lrel@router-config-logging.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+# Sourced from scenario.sh and uses functions defined there.
+
+start_image="rhel102-bootc-brew-lrel-optional"
+
+scenario_create_vms() {
+ exit_if_image_not_found "${start_image}"
+
+ prepare_kickstart host1 kickstart-bootc.ks.template "${start_image}"
+ launch_vm rhel102-bootc --vm_vcpus 4
+}
+
+scenario_remove_vms() {
+ exit_if_image_not_found "${start_image}"
+
+ remove_vm host1
+}
+
+scenario_run_tests() {
+ exit_if_image_not_found "${start_image}"
+
+ run_tests host1 \
+ suites/router/router-config-logging.robot
+}
diff --git a/test/scenarios-bootc/el10/releases/el102-lrel@router-config-policies.sh b/test/scenarios-bootc/el10/releases/el102-lrel@router-config-policies.sh
new file mode 100644
index 0000000000..4ff8478abf
--- /dev/null
+++ b/test/scenarios-bootc/el10/releases/el102-lrel@router-config-policies.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+# Sourced from scenario.sh and uses functions defined there.
+
+start_image="rhel102-bootc-brew-lrel-optional"
+
+scenario_create_vms() {
+ exit_if_image_not_found "${start_image}"
+
+ prepare_kickstart host1 kickstart-bootc.ks.template "${start_image}"
+ launch_vm rhel102-bootc --vm_vcpus 4
+}
+
+scenario_remove_vms() {
+ exit_if_image_not_found "${start_image}"
+
+ remove_vm host1
+}
+
+scenario_run_tests() {
+ exit_if_image_not_found "${start_image}"
+
+ run_tests host1 \
+ suites/router/router-config-policies.robot
+}
diff --git a/test/scenarios-bootc/el10/releases/el102-lrel@router-config-tls.sh b/test/scenarios-bootc/el10/releases/el102-lrel@router-config-tls.sh
new file mode 100644
index 0000000000..ebe2dc933a
--- /dev/null
+++ b/test/scenarios-bootc/el10/releases/el102-lrel@router-config-tls.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+# Sourced from scenario.sh and uses functions defined there.
+
+start_image="rhel102-bootc-brew-lrel-optional"
+
+scenario_create_vms() {
+ exit_if_image_not_found "${start_image}"
+
+ prepare_kickstart host1 kickstart-bootc.ks.template "${start_image}"
+ launch_vm rhel102-bootc --vm_vcpus 4
+}
+
+scenario_remove_vms() {
+ exit_if_image_not_found "${start_image}"
+
+ remove_vm host1
+}
+
+scenario_run_tests() {
+ exit_if_image_not_found "${start_image}"
+
+ run_tests host1 \
+ suites/router/router-config-tls.robot
+}
diff --git a/test/scenarios-bootc/el10/releases/el102-lrel@router-routes.sh b/test/scenarios-bootc/el10/releases/el102-lrel@router-routes.sh
new file mode 100644
index 0000000000..66463ec24a
--- /dev/null
+++ b/test/scenarios-bootc/el10/releases/el102-lrel@router-routes.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+# Sourced from scenario.sh and uses functions defined there.
+
+start_image="rhel102-bootc-brew-lrel-optional"
+
+scenario_create_vms() {
+ exit_if_image_not_found "${start_image}"
+
+ prepare_kickstart host1 kickstart-bootc.ks.template "${start_image}"
+ launch_vm rhel102-bootc --vm_vcpus 4
+}
+
+scenario_remove_vms() {
+ exit_if_image_not_found "${start_image}"
+
+ remove_vm host1
+}
+
+scenario_run_tests() {
+ exit_if_image_not_found "${start_image}"
+
+ run_tests host1 \
+ suites/router/router-routes.robot
+}
diff --git a/test/scenarios-bootc/el9/presubmits/el98-src@router.sh b/test/scenarios-bootc/el9/presubmits/el98-src@router.sh
index 1f2bc33ce9..e0f7dcc963 100644
--- a/test/scenarios-bootc/el9/presubmits/el98-src@router.sh
+++ b/test/scenarios-bootc/el9/presubmits/el98-src@router.sh
@@ -13,5 +13,5 @@ scenario_remove_vms() {
scenario_run_tests() {
run_tests host1 \
- suites/router
+ suites/router/router-basic.robot
}
diff --git a/test/scenarios-bootc/el9/releases/el98-lrel@osconfig-router.sh b/test/scenarios-bootc/el9/releases/el98-lrel@osconfig-router.sh
index 629ce5417e..13d366d80a 100644
--- a/test/scenarios-bootc/el9/releases/el98-lrel@osconfig-router.sh
+++ b/test/scenarios-bootc/el9/releases/el98-lrel@osconfig-router.sh
@@ -23,7 +23,7 @@ scenario_run_tests() {
run_tests host1 \
suites/osconfig/clusterid.robot \
suites/osconfig/systemd-resolved.robot \
- suites/router/ \
+ suites/router/router-basic.robot \
suites/otp-workloads/oc-cli.robot \
suites/otp-workloads/statefulset-pvc.robot
}
diff --git a/test/scenarios-bootc/el9/releases/el98-lrel@router-config-infra.sh b/test/scenarios-bootc/el9/releases/el98-lrel@router-config-infra.sh
new file mode 100644
index 0000000000..f41bf0e336
--- /dev/null
+++ b/test/scenarios-bootc/el9/releases/el98-lrel@router-config-infra.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+# Sourced from scenario.sh and uses functions defined there.
+
+start_image="rhel98-bootc-brew-lrel-optional"
+
+scenario_create_vms() {
+ exit_if_image_not_found "${start_image}"
+
+ prepare_kickstart host1 kickstart-bootc.ks.template "${start_image}"
+ launch_vm rhel98-bootc --vm_vcpus 4
+}
+
+scenario_remove_vms() {
+ exit_if_image_not_found "${start_image}"
+
+ remove_vm host1
+}
+
+scenario_run_tests() {
+ exit_if_image_not_found "${start_image}"
+
+ run_tests host1 \
+ suites/router/router-config-infra.robot
+}
diff --git a/test/scenarios-bootc/el9/releases/el98-lrel@router-config-logging.sh b/test/scenarios-bootc/el9/releases/el98-lrel@router-config-logging.sh
new file mode 100644
index 0000000000..e2d1d3b472
--- /dev/null
+++ b/test/scenarios-bootc/el9/releases/el98-lrel@router-config-logging.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+# Sourced from scenario.sh and uses functions defined there.
+
+start_image="rhel98-bootc-brew-lrel-optional"
+
+scenario_create_vms() {
+ exit_if_image_not_found "${start_image}"
+
+ prepare_kickstart host1 kickstart-bootc.ks.template "${start_image}"
+ launch_vm rhel98-bootc --vm_vcpus 4
+}
+
+scenario_remove_vms() {
+ exit_if_image_not_found "${start_image}"
+
+ remove_vm host1
+}
+
+scenario_run_tests() {
+ exit_if_image_not_found "${start_image}"
+
+ run_tests host1 \
+ suites/router/router-config-logging.robot
+}
diff --git a/test/scenarios-bootc/el9/releases/el98-lrel@router-config-policies.sh b/test/scenarios-bootc/el9/releases/el98-lrel@router-config-policies.sh
new file mode 100644
index 0000000000..78f4b97cfe
--- /dev/null
+++ b/test/scenarios-bootc/el9/releases/el98-lrel@router-config-policies.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+# Sourced from scenario.sh and uses functions defined there.
+
+start_image="rhel98-bootc-brew-lrel-optional"
+
+scenario_create_vms() {
+ exit_if_image_not_found "${start_image}"
+
+ prepare_kickstart host1 kickstart-bootc.ks.template "${start_image}"
+ launch_vm rhel98-bootc --vm_vcpus 4
+}
+
+scenario_remove_vms() {
+ exit_if_image_not_found "${start_image}"
+
+ remove_vm host1
+}
+
+scenario_run_tests() {
+ exit_if_image_not_found "${start_image}"
+
+ run_tests host1 \
+ suites/router/router-config-policies.robot
+}
diff --git a/test/scenarios-bootc/el9/releases/el98-lrel@router-config-tls.sh b/test/scenarios-bootc/el9/releases/el98-lrel@router-config-tls.sh
new file mode 100644
index 0000000000..ee5079f5b1
--- /dev/null
+++ b/test/scenarios-bootc/el9/releases/el98-lrel@router-config-tls.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+# Sourced from scenario.sh and uses functions defined there.
+
+start_image="rhel98-bootc-brew-lrel-optional"
+
+scenario_create_vms() {
+ exit_if_image_not_found "${start_image}"
+
+ prepare_kickstart host1 kickstart-bootc.ks.template "${start_image}"
+ launch_vm rhel98-bootc --vm_vcpus 4
+}
+
+scenario_remove_vms() {
+ exit_if_image_not_found "${start_image}"
+
+ remove_vm host1
+}
+
+scenario_run_tests() {
+ exit_if_image_not_found "${start_image}"
+
+ run_tests host1 \
+ suites/router/router-config-tls.robot
+}
diff --git a/test/scenarios-bootc/el9/releases/el98-lrel@router-routes.sh b/test/scenarios-bootc/el9/releases/el98-lrel@router-routes.sh
new file mode 100644
index 0000000000..ecd58d4277
--- /dev/null
+++ b/test/scenarios-bootc/el9/releases/el98-lrel@router-routes.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+# Sourced from scenario.sh and uses functions defined there.
+
+start_image="rhel98-bootc-brew-lrel-optional"
+
+scenario_create_vms() {
+ exit_if_image_not_found "${start_image}"
+
+ prepare_kickstart host1 kickstart-bootc.ks.template "${start_image}"
+ launch_vm rhel98-bootc --vm_vcpus 4
+}
+
+scenario_remove_vms() {
+ exit_if_image_not_found "${start_image}"
+
+ remove_vm host1
+}
+
+scenario_run_tests() {
+ exit_if_image_not_found "${start_image}"
+
+ run_tests host1 \
+ suites/router/router-routes.robot
+}
diff --git a/test/scenarios/presubmits/el98-src@router.sh b/test/scenarios/presubmits/el98-src@router.sh
index 820af8566d..ede8c9cc2a 100644
--- a/test/scenarios/presubmits/el98-src@router.sh
+++ b/test/scenarios/presubmits/el98-src@router.sh
@@ -12,5 +12,5 @@ scenario_remove_vms() {
}
scenario_run_tests() {
- run_tests host1 suites/router/
+ run_tests host1 suites/router/router-basic.robot
}
diff --git a/test/scenarios/releases/el98-lrel@router-config-infra.sh b/test/scenarios/releases/el98-lrel@router-config-infra.sh
new file mode 100644
index 0000000000..574983f814
--- /dev/null
+++ b/test/scenarios/releases/el98-lrel@router-config-infra.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+# Sourced from scenario.sh and uses functions defined there.
+
+start_image="rhel98-brew-lrel-optional"
+
+scenario_create_vms() {
+ exit_if_commit_not_found "${start_image}"
+
+ prepare_kickstart host1 kickstart.ks.template "${start_image}"
+ launch_vm rhel-9.8 --vm_vcpus 4
+}
+
+scenario_remove_vms() {
+ exit_if_commit_not_found "${start_image}"
+
+ remove_vm host1
+}
+
+scenario_run_tests() {
+ exit_if_commit_not_found "${start_image}"
+
+ run_tests host1 \
+ suites/router/router-config-infra.robot
+}
diff --git a/test/scenarios/releases/el98-lrel@router-config-logging.sh b/test/scenarios/releases/el98-lrel@router-config-logging.sh
new file mode 100644
index 0000000000..ca124d8099
--- /dev/null
+++ b/test/scenarios/releases/el98-lrel@router-config-logging.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+# Sourced from scenario.sh and uses functions defined there.
+
+start_image="rhel98-brew-lrel-optional"
+
+scenario_create_vms() {
+ exit_if_commit_not_found "${start_image}"
+
+ prepare_kickstart host1 kickstart.ks.template "${start_image}"
+ launch_vm rhel-9.8 --vm_vcpus 4
+}
+
+scenario_remove_vms() {
+ exit_if_commit_not_found "${start_image}"
+
+ remove_vm host1
+}
+
+scenario_run_tests() {
+ exit_if_commit_not_found "${start_image}"
+
+ run_tests host1 \
+ suites/router/router-config-logging.robot
+}
diff --git a/test/scenarios/releases/el98-lrel@router-config-policies.sh b/test/scenarios/releases/el98-lrel@router-config-policies.sh
new file mode 100644
index 0000000000..b9ec834724
--- /dev/null
+++ b/test/scenarios/releases/el98-lrel@router-config-policies.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+# Sourced from scenario.sh and uses functions defined there.
+
+start_image="rhel98-brew-lrel-optional"
+
+scenario_create_vms() {
+ exit_if_commit_not_found "${start_image}"
+
+ prepare_kickstart host1 kickstart.ks.template "${start_image}"
+ launch_vm rhel-9.8 --vm_vcpus 4
+}
+
+scenario_remove_vms() {
+ exit_if_commit_not_found "${start_image}"
+
+ remove_vm host1
+}
+
+scenario_run_tests() {
+ exit_if_commit_not_found "${start_image}"
+
+ run_tests host1 \
+ suites/router/router-config-policies.robot
+}
diff --git a/test/scenarios/releases/el98-lrel@router-config-tls.sh b/test/scenarios/releases/el98-lrel@router-config-tls.sh
new file mode 100644
index 0000000000..fc29afb6fb
--- /dev/null
+++ b/test/scenarios/releases/el98-lrel@router-config-tls.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+# Sourced from scenario.sh and uses functions defined there.
+
+start_image="rhel98-brew-lrel-optional"
+
+scenario_create_vms() {
+ exit_if_commit_not_found "${start_image}"
+
+ prepare_kickstart host1 kickstart.ks.template "${start_image}"
+ launch_vm rhel-9.8 --vm_vcpus 4
+}
+
+scenario_remove_vms() {
+ exit_if_commit_not_found "${start_image}"
+
+ remove_vm host1
+}
+
+scenario_run_tests() {
+ exit_if_commit_not_found "${start_image}"
+
+ run_tests host1 \
+ suites/router/router-config-tls.robot
+}
diff --git a/test/scenarios/releases/el98-lrel@router-routes.sh b/test/scenarios/releases/el98-lrel@router-routes.sh
new file mode 100644
index 0000000000..0224f3adf0
--- /dev/null
+++ b/test/scenarios/releases/el98-lrel@router-routes.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+# Sourced from scenario.sh and uses functions defined there.
+
+start_image="rhel98-brew-lrel-optional"
+
+scenario_create_vms() {
+ exit_if_commit_not_found "${start_image}"
+
+ prepare_kickstart host1 kickstart.ks.template "${start_image}"
+ launch_vm rhel-9.8 --vm_vcpus 4
+}
+
+scenario_remove_vms() {
+ exit_if_commit_not_found "${start_image}"
+
+ remove_vm host1
+}
+
+scenario_run_tests() {
+ exit_if_commit_not_found "${start_image}"
+
+ run_tests host1 \
+ suites/router/router-routes.robot
+}
diff --git a/test/scenarios/releases/el98-lrel@router.sh b/test/scenarios/releases/el98-lrel@router.sh
index 91501d6085..4550672aa3 100644
--- a/test/scenarios/releases/el98-lrel@router.sh
+++ b/test/scenarios/releases/el98-lrel@router.sh
@@ -20,5 +20,5 @@ scenario_remove_vms() {
scenario_run_tests() {
exit_if_commit_not_found "${start_image}"
- run_tests host1 suites/router/
+ run_tests host1 suites/router/router-basic.robot
}
diff --git a/test/suites/router/router.robot b/test/suites/router/router-basic.robot
similarity index 100%
rename from test/suites/router/router.robot
rename to test/suites/router/router-basic.robot
diff --git a/test/suites/router/router-config-infra.robot b/test/suites/router/router-config-infra.robot
new file mode 100644
index 0000000000..aec6465bcc
--- /dev/null
+++ b/test/suites/router/router-config-infra.robot
@@ -0,0 +1,263 @@
+*** Settings ***
+Documentation Router infrastructure configuration tests (disruptive)
+... Migrated from openshift-tests-private:
+... OCP-73203, OCP-73209, OCP-82015, OCP-84260
+
+Resource ../../resources/common.resource
+Resource ../../resources/oc.resource
+Resource ../../resources/microshift-config.resource
+Resource ../../resources/microshift-process.resource
+Resource ../../resources/ostree-health.resource
+Resource ../../resources/router.resource
+
+Suite Setup Setup Suite With Namespace
+Suite Teardown Teardown Suite With Namespace
+
+Test Tags restart slow
+
+
+*** Variables ***
+${BASE_DOMAIN} apps.example.com
+${ALT_HTTP_PORT} 10080
+${ALT_HTTPS_PORT} 10443
+
+${LOGGING_INVALID_MAXLENGTH_NEG1} SEPARATOR=\n
+... ---
+... ingress:
+... \ \ accessLogging:
+... \ \ \ \ httpCaptureCookies:
+... \ \ \ \ - matchType: Exact
+... \ \ \ \ \ \ maxLength: -1
+... \ \ \ \ \ \ name: foo
+... \ \ \ \ status: Enabled
+
+${LOGGING_INVALID_MAXLENGTH_ZERO} SEPARATOR=\n
+... ---
+... ingress:
+... \ \ accessLogging:
+... \ \ \ \ httpCaptureCookies:
+... \ \ \ \ - matchType: Exact
+... \ \ \ \ \ \ maxLength: 0
+... \ \ \ \ \ \ name: foo
+... \ \ \ \ status: Enabled
+
+${LOGGING_INVALID_COOKIE_NAME} SEPARATOR=\n
+... ---
+... ingress:
+... \ \ accessLogging:
+... \ \ \ \ httpCaptureCookies:
+... \ \ \ \ - matchType: Exact
+... \ \ \ \ \ \ maxLength: 100
+... \ \ \ \ \ \ name: "foo 33#?-"
+... \ \ \ \ status: Enabled
+
+${LOGGING_INVALID_HEADER_MAXLENGTH} SEPARATOR=\n
+... ---
+... ingress:
+... \ \ accessLogging:
+... \ \ \ \ httpCaptureHeaders:
+... \ \ \ \ \ \ request:
+... \ \ \ \ \ \ - maxLength: -1
+... \ \ \ \ \ \ \ \ name: Host
+... \ \ \ \ \ \ response:
+... \ \ \ \ \ \ - maxLength: 10
+... \ \ \ \ \ \ \ \ name: "Server"
+... \ \ \ \ status: Enabled
+
+${LOGGING_INVALID_STATUS} SEPARATOR=\n
+... ---
+... ingress:
+... \ \ accessLogging:
+... \ \ \ \ httpCaptureHeaders:
+... \ \ \ \ \ \ request:
+... \ \ \ \ \ \ - maxLength: 10
+... \ \ \ \ \ \ \ \ name: Host
+... \ \ \ \ status: Enable
+
+${LOGGING_COOKIES_NO_STATUS} SEPARATOR=\n
+... ---
+... ingress:
+... \ \ accessLogging:
+... \ \ \ \ httpCaptureCookies:
+... \ \ \ \ - matchType: Prefix
+... \ \ \ \ \ \ maxLength: 100
+... \ \ \ \ \ \ namePrefix: foo
+
+
+*** Test Cases ***
+Custom Listening IPs And Ports
+ [Documentation] Verify configuring a specific listen address and custom HTTP/HTTPS ports
+ ... causes the router LB to expose only that IP on the custom ports, and routes
+ ... are reachable through those ports.
+ ... OCP-73203
+
+ ${iface} ${host_ip}= Get First Host Interface And IP Via SSH
+ ${config}= Catenate SEPARATOR=\n
+ ... ---
+ ... ingress:
+ ... \ \ listenAddress:
+ ... \ \ - ${iface}
+ ... \ \ ports:
+ ... \ \ \ \ http: ${ALT_HTTP_PORT}
+ ... \ \ \ \ https: ${ALT_HTTPS_PORT}
+ Setup Router Config And Restart ${config}
+
+ Verify Custom LB Ports And IP ${host_ip}
+
+ Deploy Web Server Signed
+ Deploy Test Client Pod
+ VAR ${HTTP_HOST}= service-unsecure-ocp73203.${BASE_DOMAIN} scope=TEST
+ VAR ${EDGE_HOST}= route-edge-ocp73203.${BASE_DOMAIN} scope=TEST
+ VAR ${PASS_HOST}= route-passth-ocp73203.${BASE_DOMAIN} scope=TEST
+ VAR ${REEN_HOST}= route-reen-ocp73203.${BASE_DOMAIN} scope=TEST
+ Create And Admit Four Route Types ${HTTP_HOST} ${EDGE_HOST} ${PASS_HOST} ${REEN_HOST}
+ Curl Four Routes Via Custom Ports ${host_ip}
+ [Teardown] Run Keywords
+ ... Remove Router Config And Restart
+ ... AND Cleanup Test Workloads
+
+Enable Disable Router
+ [Documentation] Verify setting ingress status to Removed deletes the openshift-ingress namespace,
+ ... and setting it back to Managed restores the router LB with correct IPs and ports.
+ ... OCP-73209
+
+ ${config_removed}= Catenate SEPARATOR=\n
+ ... ---
+ ... ingress:
+ ... \ \ status: Removed
+ Setup Router Config And Restart ${config_removed}
+ Oc Wait namespace/openshift-ingress --for=delete --timeout=300s
+
+ ${config_managed}= Catenate SEPARATOR=\n
+ ... ---
+ ... ingress:
+ ... \ \ status: Managed
+ Setup Router Config And Restart ${config_managed}
+
+ ${svc_type}= Oc Get JsonPath service ${ROUTER_NS} router-default .spec.type
+ Should Be Equal As Strings ${svc_type} LoadBalancer
+ ${http_port}= Get LB Port http
+ Should Be Equal As Strings ${http_port} 80
+ ${https_port}= Get LB Port https
+ Should Be Equal As Strings ${https_port} 443
+ ${lb_ips}= Get LB IPs
+ Should Not Be Empty ${lb_ips}
+ [Teardown] Remove Router Config And Restart
+
+Syslog Logging Destination
+ [Documentation] Verify logging to a syslog server delivers router access logs to the syslog pod,
+ ... and changing the facility is reflected in the haproxy global config.
+ ... OCP-82015
+
+ Privileged Namespace
+ Deploy Rsyslogd Pod
+ Deploy Web Server
+ Deploy Test Client Pod
+ ${syslog_ip}= Oc Get JsonPath pod ${NAMESPACE} rsyslogd-pod .status.podIP
+ Verify Syslog Logging ${syslog_ip}
+ [Teardown] Run Keywords
+ ... Remove Router Config And Restart
+ ... AND Cleanup Test Workloads
+
+Negative Logging Config Validation
+ [Documentation] Verify invalid logging configurations are rejected by microshift show-config,
+ ... and that setting httpCaptureCookies without status: Enabled does not activate logging.
+ ... OCP-84260
+
+ Show Invalid Drop In Config Should Fail With ${LOGGING_INVALID_MAXLENGTH_NEG1} Must be between 1 and 1024
+ Show Invalid Drop In Config Should Fail With ${LOGGING_INVALID_MAXLENGTH_ZERO} Must be between 1 and 1024
+ Show Invalid Drop In Config Should Fail With ${LOGGING_INVALID_COOKIE_NAME} contains invalid characters
+ Show Invalid Drop In Config Should Fail With ${LOGGING_INVALID_HEADER_MAXLENGTH} maxLength must be at least 1
+ Show Invalid Drop In Config Should Fail With ${LOGGING_INVALID_STATUS} invalid access logging status: Enable
+
+ ${gen_before}= Get Router Deployment Generation
+ Drop In MicroShift Config ${LOGGING_COOKIES_NO_STATUS} 10-router
+ Restart MicroShift
+ Wait For Router Ready
+ ${gen_after}= Get Router Deployment Generation
+ Should Be Equal As Strings ${gen_before} ${gen_after}
+ ${haproxy}= Read Haproxy Config
+ Should Not Contain ${haproxy} capture cookie foo len 100
+ [Teardown] Remove Router Config And Restart
+
+
+*** Keywords ***
+Verify Custom LB Ports And IP
+ [Documentation] Check the router-default LB has the expected IP and custom port numbers.
+ [Arguments] ${host_ip}
+ ${lb_ips}= Get LB IPs
+ Should Be Equal As Strings ${lb_ips} ${host_ip}
+ ${http_port}= Get LB Port http
+ Should Be Equal As Strings ${http_port} ${ALT_HTTP_PORT}
+ ${https_port}= Get LB Port https
+ Should Be Equal As Strings ${https_port} ${ALT_HTTPS_PORT}
+
+Curl Four Routes Via Custom Ports
+ [Documentation] Curl all four route types through the custom ports.
+ [Arguments] ${host_ip}
+ Wait Until Curl Succeeds From Pod
+ ... ${CLIENT_POD_NAME} ${NAMESPACE}
+ ... http://${HTTP_HOST}:${ALT_HTTP_PORT} ${HTTP_HOST}:${ALT_HTTP_PORT}:${host_ip}
+ Wait Until HTTPS Curl Succeeds From Pod
+ ... ${CLIENT_POD_NAME} ${NAMESPACE}
+ ... https://${EDGE_HOST}:${ALT_HTTPS_PORT} ${EDGE_HOST}:${ALT_HTTPS_PORT}:${host_ip}
+ Wait Until HTTPS Curl Succeeds From Pod
+ ... ${CLIENT_POD_NAME} ${NAMESPACE}
+ ... https://${PASS_HOST}:${ALT_HTTPS_PORT} ${PASS_HOST}:${ALT_HTTPS_PORT}:${host_ip}
+ Wait Until HTTPS Curl Succeeds From Pod
+ ... ${CLIENT_POD_NAME} ${NAMESPACE}
+ ... https://${REEN_HOST}:${ALT_HTTPS_PORT} ${REEN_HOST}:${ALT_HTTPS_PORT}:${host_ip}
+
+Verify Syslog Logging
+ [Documentation] Apply syslog config and verify log delivery, then verify facility change.
+ [Arguments] ${syslog_ip}
+ VAR ${config1}= SEPARATOR=\n
+ ... ---
+ ... ingress:
+ ... \ \ accessLogging:
+ ... \ \ \ \ destination:
+ ... \ \ \ \ \ \ syslog:
+ ... \ \ \ \ \ \ \ \ address: ${syslog_ip}
+ ... \ \ \ \ \ \ \ \ port: 514
+ ... \ \ \ \ \ \ type: Syslog
+ ... \ \ \ \ status: Enabled
+ Setup Router Config And Restart ${config1}
+ VAR ${routehost}= route-unsec82015.${BASE_DOMAIN}
+ Create OC Route ${NAMESPACE} http route-http service-unsecure --hostname=${routehost}
+ Route Should Be Admitted route-http
+ Verify Syslog Haproxy Config ${syslog_ip} local1
+ Verify Syslog Log Delivery ${routehost}
+ VAR ${config2}= SEPARATOR=\n
+ ... ---
+ ... ingress:
+ ... \ \ accessLogging:
+ ... \ \ \ \ destination:
+ ... \ \ \ \ \ \ syslog:
+ ... \ \ \ \ \ \ \ \ address: ${syslog_ip}
+ ... \ \ \ \ \ \ \ \ port: 514
+ ... \ \ \ \ \ \ \ \ facility: local2
+ ... \ \ \ \ \ \ type: Syslog
+ ... \ \ \ \ status: Enabled
+ Setup Router Config And Restart ${config2}
+ Verify Syslog Haproxy Config ${syslog_ip} local2
+
+Verify Syslog Haproxy Config
+ [Documentation] Verify the syslog server address and facility in haproxy global config.
+ [Arguments] ${syslog_ip} ${facility}
+ ${haproxy}= Read Haproxy Config
+ Should Contain ${haproxy} log ${syslog_ip}:514 len 1024 ${facility} info
+
+Verify Syslog Log Delivery
+ [Documentation] Curl a route and verify the log entry appears in the syslog pod.
+ [Arguments] ${routehost}
+ ${router_ip}= Get Router Pod IP
+ Wait Until Curl Succeeds From Pod
+ ... ${CLIENT_POD_NAME} ${NAMESPACE}
+ ... http://${routehost}/path/second/index.html ${routehost}:80:${router_ip}
+ Wait Until Keyword Succeeds 60s 3s Syslog Pod Should Contain /path/second/index.html
+
+Syslog Pod Should Contain
+ [Documentation] Check that the rsyslogd pod logs contain a pattern.
+ [Arguments] ${pattern}
+ ${logs}= Oc Logs rsyslogd-pod --tail=20 ${NAMESPACE}
+ Should Contain ${logs} ${pattern}
diff --git a/test/suites/router/router-config-logging.robot b/test/suites/router/router-config-logging.robot
new file mode 100644
index 0000000000..c5eca276ef
--- /dev/null
+++ b/test/suites/router/router-config-logging.robot
@@ -0,0 +1,309 @@
+*** Settings ***
+Documentation Router access logging configuration tests (disruptive)
+... Migrated from openshift-tests-private:
+... OCP-81996, OCP-81997, OCP-82000, OCP-82003, OCP-82004
+
+Resource ../../resources/common.resource
+Resource ../../resources/oc.resource
+Resource ../../resources/microshift-config.resource
+Resource ../../resources/microshift-process.resource
+Resource ../../resources/ostree-health.resource
+Resource ../../resources/router.resource
+
+Suite Setup Setup Suite With Namespace
+Suite Teardown Teardown Suite With Namespace
+
+Test Tags restart slow
+
+
+*** Variables ***
+${BASE_DOMAIN} apps.example.com
+
+${CONFIG_COOKIE_EXACT_100} SEPARATOR=\n
+... ---
+... ingress:
+... \ \ accessLogging:
+... \ \ \ \ httpCaptureCookies:
+... \ \ \ \ - matchType: Exact
+... \ \ \ \ \ \ maxLength: 100
+... \ \ \ \ \ \ name: foo
+... \ \ \ \ status: Enabled
+
+${CONFIG_COOKIE_EXACT_10} SEPARATOR=\n
+... ---
+... ingress:
+... \ \ accessLogging:
+... \ \ \ \ httpCaptureCookies:
+... \ \ \ \ - matchType: Exact
+... \ \ \ \ \ \ maxLength: 10
+... \ \ \ \ \ \ name: foo
+... \ \ \ \ status: Enabled
+
+
+*** Test Cases ***
+HTTP Capture Cookies Prefix Match
+ [Documentation] Verify httpCaptureCookies with Prefix match captures cookies matching the
+ ... prefix in router logs across HTTP, edge, and reencrypt routes.
+ ... OCP-81996
+
+ ${config}= Catenate SEPARATOR=\n
+ ... ---
+ ... ingress:
+ ... \ \ accessLogging:
+ ... \ \ \ \ httpCaptureCookies:
+ ... \ \ \ \ - matchType: Prefix
+ ... \ \ \ \ \ \ maxLength: 100
+ ... \ \ \ \ \ \ namePrefix: foo
+ ... \ \ \ \ status: Enabled
+ Setup Router Config And Restart ${config}
+
+ Deploy Web Server Signed
+ Deploy Test Client Pod
+ VAR ${routehost}= route-unsec81996.${BASE_DOMAIN}
+ VAR ${edge_host}= route-edge81996.${BASE_DOMAIN}
+ VAR ${reen_host}= route-reen81996.${BASE_DOMAIN}
+ Create OC Route ${NAMESPACE} http route-http service-unsecure --hostname=${routehost}
+ Route Should Be Admitted route-http
+ Create OC Route ${NAMESPACE} edge route-edge service-unsecure --hostname=${edge_host}
+ Route Should Be Admitted route-edge
+ Create OC Route ${NAMESPACE} reencrypt route-reen service-secure --hostname=${reen_host}
+ Route Should Be Admitted route-reen
+ ${router_ip}= Get Router Pod IP
+ Curl All Cookie Routes And Verify Logs ${routehost} ${edge_host} ${reen_host} ${router_ip}
+ [Teardown] Run Keywords
+ ... Remove Router Config And Restart
+ ... AND Cleanup Test Workloads
+
+HTTP Capture Cookies Exact Match And MaxLength
+ [Documentation] Verify httpCaptureCookies with Exact match captures only exact-named cookies,
+ ... and maxLength truncates cookie values in logs.
+ ... OCP-81997
+
+ Setup Router Config And Restart ${CONFIG_COOKIE_EXACT_100}
+ Deploy Web Server
+ Deploy Test Client Pod
+ VAR ${routehost}= route-unsec81997.${BASE_DOMAIN}
+ Create OC Route ${NAMESPACE} http route-http service-unsecure --hostname=${routehost}
+ Route Should Be Admitted route-http
+ ${router_ip}= Get Router Pod IP
+ Wait Until Curl With Cookie Succeeds From Pod
+ ... ${CLIENT_POD_NAME}
+ ... ${NAMESPACE}
+ ... http://${routehost}/index.html
+ ... ${routehost}:80:${router_ip}
+ ... fooor=nobar
+ Wait Until Curl With Cookie Succeeds From Pod
+ ... ${CLIENT_POD_NAME}
+ ... ${NAMESPACE}
+ ... http://${routehost}/index.html
+ ... ${routehost}:80:${router_ip}
+ ... foo=bar
+ Wait For Router Logs To Contain foo=bar
+ Router Logs Should Not Contain fooor=nobar
+
+ Setup Router Config And Restart ${CONFIG_COOKIE_EXACT_10}
+ ${router_ip}= Get Router Pod IP
+ Wait Until Curl With Cookie Succeeds From Pod
+ ... ${CLIENT_POD_NAME}
+ ... ${NAMESPACE}
+ ... http://${routehost}/index.html
+ ... ${routehost}:80:${router_ip}
+ ... foo=bar89abdef
+ Wait For Router Logs To Contain foo=bar89a
+ Router Logs Should Not Contain foo=bar89ab
+ [Teardown] Run Keywords
+ ... Remove Router Config And Restart
+ ... AND Cleanup Test Workloads
+
+HTTP Capture Headers Request And Response
+ [Documentation] Verify httpCaptureHeaders captures request Host and response Server headers
+ ... in router logs, including for edge and reencrypt routes.
+ ... OCP-82000
+
+ ${config}= Catenate SEPARATOR=\n
+ ... ---
+ ... ingress:
+ ... \ \ accessLogging:
+ ... \ \ \ \ httpCaptureHeaders:
+ ... \ \ \ \ \ \ request:
+ ... \ \ \ \ \ \ - maxLength: 120
+ ... \ \ \ \ \ \ \ \ name: Host
+ ... \ \ \ \ \ \ response:
+ ... \ \ \ \ \ \ - maxLength: 120
+ ... \ \ \ \ \ \ \ \ name: "Server"
+ ... \ \ \ \ status: Enabled
+ Setup Router Config And Restart ${config}
+
+ Deploy Web Server Signed
+ Deploy Test Client Pod
+ VAR ${routehost}= route-unsec82000.${BASE_DOMAIN}
+ VAR ${edge_host}= route-edge82000.${BASE_DOMAIN}
+ VAR ${reen_host}= route-reen82000.${BASE_DOMAIN}
+ Create OC Route ${NAMESPACE} http route-http service-unsecure --hostname=${routehost}
+ Route Should Be Admitted route-http
+ Create OC Route ${NAMESPACE} edge route-edge service-unsecure --hostname=${edge_host}
+ Route Should Be Admitted route-edge
+ Create OC Route ${NAMESPACE} reencrypt route-reen service-secure --hostname=${reen_host}
+ Route Should Be Admitted route-reen
+ Verify Header Capture Config And Logs ${routehost} ${edge_host} ${reen_host}
+ [Teardown] Run Keywords
+ ... Remove Router Config And Restart
+ ... AND Cleanup Test Workloads
+
+HTTP Capture Headers MaxLength Adherence
+ [Documentation] Verify httpCaptureHeaders maxLength truncates captured header values in logs.
+ ... OCP-82003
+
+ VAR ${routehost}= route-unsec82003.${BASE_DOMAIN}
+ ${config}= Catenate SEPARATOR=\n
+ ... ---
+ ... ingress:
+ ... \ \ accessLogging:
+ ... \ \ \ \ httpCaptureHeaders:
+ ... \ \ \ \ \ \ request:
+ ... \ \ \ \ \ \ - maxLength: 16
+ ... \ \ \ \ \ \ \ \ name: Host
+ ... \ \ \ \ \ \ response:
+ ... \ \ \ \ \ \ - maxLength: 5
+ ... \ \ \ \ \ \ \ \ name: "Server"
+ ... \ \ \ \ status: Enabled
+ Setup Router Config And Restart ${config}
+
+ Deploy Web Server
+ Deploy Test Client Pod
+ Create OC Route ${NAMESPACE} http route-http service-unsecure --hostname=${routehost}
+ Route Should Be Admitted route-http
+ ${router_ip}= Get Router Pod IP
+ Wait Until Curl Succeeds From Pod
+ ... ${CLIENT_POD_NAME} ${NAMESPACE} http://${routehost}/index.html ${routehost}:80:${router_ip}
+ # maxLength is 16, so hostname is truncated to exactly 16 chars: route-unsec82003
+ Wait For Router Logs To Contain route-unsec82003
+ ${logs}= Get Router Access Logs
+ Should Not Contain ${logs} ${routehost}
+ # nginx server version is 5+ chars, so only "nginx" should appear without the version
+ Should Contain ${logs} nginx
+ Should Not Contain ${logs} nginx/
+ [Teardown] Run Keywords
+ ... Remove Router Config And Restart
+ ... AND Cleanup Test Workloads
+
+Custom HTTP Error Pages
+ [Documentation] Verify custom 503 and 404 error pages are served when configured via
+ ... httpErrorCodePages configmap.
+ ... OCP-82004
+
+ Create Configmap From Files ${ROUTER_NS} custom-82004-error-code-pages
+ ... --from-file=./assets/router/error-page-503.http --from-file=./assets/router/error-page-404.http
+ ${config}= Catenate SEPARATOR=\n
+ ... ---
+ ... ingress:
+ ... \ \ httpErrorCodePages:
+ ... \ \ \ \ name: custom-82004-error-code-pages
+ Setup Router Config And Restart ${config}
+
+ Deploy Web Server
+ Deploy Test Client Pod
+ VAR ${routehost}= route-unsec82004.${BASE_DOMAIN}
+ VAR ${noexist_host}= not-exist82004.${BASE_DOMAIN}
+ Create OC Route ${NAMESPACE} http route-http service-unsecure --hostname=${routehost}
+ Route Should Be Admitted route-http
+ Verify Custom Error Pages ${routehost} ${noexist_host}
+ [Teardown] Run Keywords
+ ... Remove Router Config And Restart
+ ... AND Run With Kubeconfig oc delete configmap custom-82004-error-code-pages -n ${ROUTER_NS} --ignore-not-found
+ ... AND Cleanup Test Workloads
+
+
+*** Keywords ***
+Curl All Cookie Routes And Verify Logs
+ [Documentation] Curl all routes with cookies and verify correct log entries.
+ [Arguments] ${routehost} ${edge_host} ${reen_host} ${router_ip}
+ Wait Until Curl With Cookie Succeeds From Pod
+ ... ${CLIENT_POD_NAME}
+ ... ${NAMESPACE}
+ ... http://${routehost}/index.html
+ ... ${routehost}:80:${router_ip}
+ ... fo=nobar
+ Wait Until Curl With Cookie Succeeds From Pod
+ ... ${CLIENT_POD_NAME}
+ ... ${NAMESPACE}
+ ... http://${routehost}/index.html
+ ... ${routehost}:80:${router_ip}
+ ... foo=bar
+ Wait Until Curl With Cookie Succeeds From Pod
+ ... ${CLIENT_POD_NAME}
+ ... ${NAMESPACE}
+ ... http://${routehost}/index.html
+ ... ${routehost}:80:${router_ip}
+ ... foo22=bar22
+ Wait Until HTTPS Curl With Cookie Succeeds From Pod
+ ... ${CLIENT_POD_NAME}
+ ... ${NAMESPACE}
+ ... https://${edge_host}/index.html
+ ... ${edge_host}:443:${router_ip}
+ ... foo=barforedge
+ Wait Until HTTPS Curl With Cookie Succeeds From Pod
+ ... ${CLIENT_POD_NAME}
+ ... ${NAMESPACE}
+ ... https://${reen_host}/index.html
+ ... ${reen_host}:443:${router_ip}
+ ... foo=barforreen
+ Wait For Router Logs To Contain foo=bar
+ Wait For Router Logs To Contain foo22=bar22
+ Wait For Router Logs To Contain foo=barforedge
+ Wait For Router Logs To Contain foo=barforreen
+ Router Logs Should Not Contain fo=nobar
+
+Verify Header Capture Config And Logs
+ [Documentation] Verify haproxy header capture config and check captured headers in logs.
+ [Arguments] ${routehost} ${edge_host} ${reen_host}
+ Verify Header Capture In Haproxy
+ ${router_ip}= Get Router Pod IP
+ Wait Until Curl Succeeds From Pod
+ ... ${CLIENT_POD_NAME} ${NAMESPACE} http://${routehost}/index.html ${routehost}:80:${router_ip}
+ Wait Until HTTPS Curl Succeeds From Pod
+ ... ${CLIENT_POD_NAME} ${NAMESPACE} https://${edge_host}/index.html ${edge_host}:443:${router_ip}
+ Wait Until HTTPS Curl Succeeds From Pod
+ ... ${CLIENT_POD_NAME} ${NAMESPACE} https://${reen_host}/index.html ${reen_host}:443:${router_ip}
+ Wait For Router Logs To Contain ${routehost}
+ Wait For Router Logs To Contain ${edge_host}
+ Wait For Router Logs To Contain ${reen_host}
+
+Verify Header Capture In Haproxy
+ [Documentation] Verify the haproxy frontend has the expected header capture configuration.
+ ${router_pod}= Get Router Pod Name
+ ${haproxy}= Run With Kubeconfig
+ ... oc exec -n ${ROUTER_NS} ${router_pod} -- grep -A 20 "frontend fe_sni" haproxy.config
+ Should Contain ${haproxy} capture request header Host len 120
+ Should Contain ${haproxy} capture response header Server len 120
+
+Verify Custom Error Pages
+ [Documentation] Verify custom 503 and 404 error pages are served by the router.
+ [Arguments] ${routehost} ${noexist_host}
+ ${router_pod}= Get Router Pod Name
+ ${error503}= Run With Kubeconfig
+ ... oc exec -n ${ROUTER_NS} ${router_pod} -- cat /var/lib/haproxy/errorfiles/error-page-503.http
+ Should Contain ${error503} Custom:Application Unavailable
+ ${error404}= Run With Kubeconfig
+ ... oc exec -n ${ROUTER_NS} ${router_pod} -- cat /var/lib/haproxy/errorfiles/error-page-404.http
+ Should Contain ${error404} Custom:Not Found
+ ${router_ip}= Get Router Pod IP
+ ${output}= Run With Kubeconfig
+ ... oc exec -n ${NAMESPACE} ${CLIENT_POD_NAME} -- curl http://${noexist_host} -s --resolve ${noexist_host}:80:${router_ip} --connect-timeout 10
+ Should Contain ${output} Custom:Not Found
+ Scale Deployment ${NAMESPACE} web-server-deploy 0
+ Wait Until Keyword Succeeds 60s 2s
+ ... Custom 503 Body Should Contain ${routehost} ${router_ip}
+
+Custom 503 Body Should Contain
+ [Documentation] Curl a route (GET, full response body) and assert the custom 503 error page body is returned.
+ [Arguments] ${routehost} ${router_ip}
+ ${output}= Run With Kubeconfig
+ ... oc exec -n ${NAMESPACE} ${CLIENT_POD_NAME} -- curl http://${routehost} -s --resolve ${routehost}:80:${router_ip} --connect-timeout 10
+ Should Contain ${output} Custom:Application Unavailable
+
+Router Logs Should Not Contain
+ [Documentation] Verify the router access logs do NOT contain a given pattern.
+ [Arguments] ${pattern}
+ ${logs}= Get Router Access Logs
+ Should Not Contain ${logs} ${pattern}
diff --git a/test/suites/router/router-config-policies.robot b/test/suites/router/router-config-policies.robot
new file mode 100644
index 0000000000..cd7dc92a3f
--- /dev/null
+++ b/test/suites/router/router-config-policies.robot
@@ -0,0 +1,263 @@
+*** Settings ***
+Documentation Router tuning, admission policies, and log format tests (disruptive)
+... Migrated from openshift-tests-private:
+... OCP-77349, OCP-80518, OCP-80520, OCP-82014
+
+Resource ../../resources/common.resource
+Resource ../../resources/oc.resource
+Resource ../../resources/microshift-config.resource
+Resource ../../resources/microshift-process.resource
+Resource ../../resources/ostree-health.resource
+Resource ../../resources/router.resource
+
+Suite Setup Setup Suite With Namespace
+Suite Teardown Teardown Suite With Namespace
+
+Test Tags restart slow
+
+
+*** Variables ***
+${BASE_DOMAIN} apps.example.com
+
+${CONFIG_TUNING_CUSTOM} SEPARATOR=\n
+... ---
+... ingress:
+... \ \ forwardedHeaderPolicy: "Replace"
+... \ \ httpCompression:
+... \ \ \ \ mimeTypes:
+... \ \ \ \ - "image"
+... \ \ logEmptyRequests: "Ignore"
+... \ \ tuningOptions:
+... \ \ \ \ clientFinTimeout: "2s"
+... \ \ \ \ clientTimeout: "60s"
+... \ \ \ \ headerBufferBytes: 65536
+... \ \ \ \ headerBufferMaxRewriteBytes: 16384
+... \ \ \ \ healthCheckInterval: "10s"
+... \ \ \ \ maxConnections: 100000
+... \ \ \ \ serverFinTimeout: "2s"
+... \ \ \ \ serverTimeout: "60s"
+... \ \ \ \ threadCount: 8
+... \ \ \ \ tlsInspectDelay: "10s"
+... \ \ \ \ tunnelTimeout: "2h"
+
+${CONFIG_MTLS18_SUBJECT_FILTER} SEPARATOR=\n
+... ---
+... ingress:
+... \ \ clientTLS:
+... \ \ \ \ allowedSubjectPatterns: ["/CN=example-test.com"]
+... \ \ \ \ clientCA:
+... \ \ \ \ \ \ name: "ocp80518"
+... \ \ \ \ clientCertificatePolicy: "Required"
+
+${CONFIG_WILDCARD_ALLOWED} SEPARATOR=\n
+... ---
+... ingress:
+... \ \ routeAdmissionPolicy:
+... \ \ \ \ wildcardPolicy: "WildcardsAllowed"
+
+${CONFIG_WILDCARD_DISALLOWED} SEPARATOR=\n
+... ---
+... ingress:
+... \ \ routeAdmissionPolicy:
+... \ \ \ \ wildcardPolicy: "WildcardsDisallowed"
+
+${CONFIG_LOG_FORMAT_1} SEPARATOR=\n
+... ---
+... ingress:
+... \ \ accessLogging:
+... \ \ \ \ httpLogFormat: "%{+Q}r"
+... \ \ \ \ status: Enabled
+
+${CONFIG_LOG_FORMAT_2} SEPARATOR=\n
+... ---
+... ingress:
+... \ \ accessLogging:
+... \ \ \ \ httpLogFormat: "%ci:%cp %si:%sp %HU %ST"
+... \ \ \ \ status: Enabled
+
+
+*** Test Cases ***
+Tuning Options Customization
+ [Documentation] Verify default tuning env vars and haproxy config values, then apply
+ ... custom tuning and verify all values are updated.
+ ... OCP-77349
+
+ VAR ${http_host}= service-unsecure-ocp77349.${BASE_DOMAIN}
+ Deploy Web Server
+ Create OC Route ${NAMESPACE} http route-http service-unsecure --hostname=${http_host}
+ Verify Default Router Tuning Env Vars
+ Verify Default Router Tuning Haproxy
+ Setup Router Config And Restart ${CONFIG_TUNING_CUSTOM}
+ Verify Custom Router Tuning Env Vars
+ Verify Custom Router Tuning Haproxy
+ [Teardown] Run Keywords
+ ... Remove Router Config And Restart
+ ... AND Cleanup Test Workloads
+
+MTLS Subject Filter
+ [Documentation] Verify mTLS with allowedSubjectPatterns allows a cert matching the CN filter
+ ... and blocks a cert not matching it.
+ ... OCP-80518
+ [Setup] Prepare Two MTLS Certs For Test 80518 route-edge80518.${BASE_DOMAIN} route2-edge80518.${BASE_DOMAIN}
+
+ Run With Kubeconfig
+ ... oc create configmap ocp80518 --from-file=ca-bundle.pem=${MTLS2_TMPDIR}/ca.crt -n ${ROUTER_NS}
+ Setup Router Config And Restart ${CONFIG_MTLS18_SUBJECT_FILTER}
+
+ ${env}= Oc Get JsonPath
+ ... pod
+ ... ${ROUTER_NS}
+ ... ${EMPTY}
+ ... .items[*].spec.containers[*].env[?(@.name=="ROUTER_MUTUAL_TLS_AUTH_FILTER")].value
+ Should Contain ${env} example-test.com
+
+ Deploy Web Server
+ Deploy Test Client Pod
+ Copy Files To Pod ${NAMESPACE} ${CLIENT_POD_NAME} ${MTLS2_TMPDIR} /data/certs
+ Create OC Route ${NAMESPACE} edge route-edge service-unsecure
+ ... --hostname=${MTLS2_HOST1} --cert=${MTLS2_TMPDIR}/usr1.crt --key=${MTLS2_TMPDIR}/usr1.key
+ Create OC Route ${NAMESPACE} edge route-edge2 service-unsecure
+ ... --hostname=${MTLS2_HOST2} --cert=${MTLS2_TMPDIR}/usr2.crt --key=${MTLS2_TMPDIR}/usr2.key
+ Route Should Be Admitted route-edge
+ Route Should Be Admitted route-edge2
+
+ ${router_ip}= Get Router Pod IP
+ Wait Until Curl With Cert File Succeeds
+ ... ${CLIENT_POD_NAME} ${NAMESPACE} https://${MTLS2_HOST1} ${MTLS2_HOST1}:443:${router_ip} usr1
+ Wait Until Curl With Cert File Returns 403
+ ... ${CLIENT_POD_NAME} ${NAMESPACE} https://${MTLS2_HOST2} ${MTLS2_HOST2}:443:${router_ip} usr2
+ [Teardown] Run Keywords
+ ... Remove Router Config And Restart
+ ... AND Run With Kubeconfig oc delete configmap ocp80518 -n ${ROUTER_NS} --ignore-not-found
+ ... AND Cleanup Test Workloads
+
+Wildcard Route Admission Policy
+ [Documentation] Verify WildcardsDisallowed rejects wildcard routes, WildcardsAllowed admits them,
+ ... and reverting to WildcardsDisallowed rejects them again.
+ ... OCP-80520
+ [Setup] Run Keywords
+ ... Deploy Web Server
+ ... AND Deploy Test Client Pod
+
+ VAR ${wildcard_host}= wildcard.${BASE_DOMAIN}
+ VAR ${any_host}= any.${BASE_DOMAIN}
+
+ ${env}= Oc Get JsonPath
+ ... pod
+ ... ${ROUTER_NS}
+ ... ${EMPTY}
+ ... .items[*].spec.containers[*].env[?(@.name=="ROUTER_ALLOW_WILDCARD_ROUTES")].value
+ Should Be Equal As Strings ${env} false
+ Create OC Route ${NAMESPACE} http unsecure80520 service-unsecure
+ ... --hostname=${wildcard_host} --wildcard-policy=Subdomain
+ Route Should Not Be Admitted unsecure80520
+
+ Setup Router Config And Restart ${CONFIG_WILDCARD_ALLOWED}
+ Router Pod Env Should Have Value ROUTER_ALLOW_WILDCARD_ROUTES true
+ Route Should Be Admitted unsecure80520
+ ${router_ip}= Get Router Pod IP
+ Wait Until Curl Succeeds From Pod
+ ... ${CLIENT_POD_NAME} ${NAMESPACE} http://${wildcard_host} ${wildcard_host}:80:${router_ip}
+ Wait Until Curl Succeeds From Pod
+ ... ${CLIENT_POD_NAME} ${NAMESPACE} http://${any_host} ${any_host}:80:${router_ip}
+
+ Setup Router Config And Restart ${CONFIG_WILDCARD_DISALLOWED}
+ Router Pod Env Should Have Value ROUTER_ALLOW_WILDCARD_ROUTES false
+ Route Should Not Be Admitted unsecure80520
+ [Teardown] Run Keywords
+ ... Remove Router Config And Restart
+ ... AND Cleanup Test Workloads
+
+HTTP Log Format
+ [Documentation] Verify httpLogFormat with HAProxy format directives produces structured log
+ ... output with the correct format.
+ ... OCP-82014
+
+ Setup Router Config And Restart ${CONFIG_LOG_FORMAT_1}
+ Deploy Web Server
+ Deploy Test Client Pod
+ VAR ${routehost}= route-unsec82014.${BASE_DOMAIN}
+ Create OC Route ${NAMESPACE} http route-http service-unsecure --hostname=${routehost}
+ Route Should Be Admitted route-http
+ ${router_ip}= Get Router Pod IP
+ Wait Until Curl Succeeds From Pod
+ ... ${CLIENT_POD_NAME} ${NAMESPACE}
+ ... http://${routehost}/path/second/index.html ${routehost}:80:${router_ip}
+ Wait For Router Logs To Contain /path/second/index.html
+ ${logs}= Get Router Access Logs
+ Should Match Regexp ${logs} haproxy\\[[0-9]+\\]: "HEAD /path/second/index.html HTTP
+
+ Setup Router Config And Restart ${CONFIG_LOG_FORMAT_2}
+ ${router_ip}= Get Router Pod IP
+ Wait Until Curl Succeeds From Pod
+ ... ${CLIENT_POD_NAME} ${NAMESPACE}
+ ... http://${routehost}/path/second/index.html ${routehost}:80:${router_ip}
+ Wait For Router Logs To Contain /path/second/index.html
+ ${logs}= Get Router Access Logs
+ Should Match Regexp ${logs}
+ ... haproxy\\[[0-9]+\\]: [0-9\\.a-fA-F:]+:[0-9]+ [0-9\\.a-fA-F:]+:8080 /path/second/index.html 200
+ [Teardown] Run Keywords
+ ... Remove Router Config And Restart
+ ... AND Cleanup Test Workloads
+
+
+*** Keywords ***
+Verify Default Router Tuning Env Vars
+ [Documentation] Verify the default router tuning environment variable values.
+ Router Pod Env Should Have Value ROUTER_BUF_SIZE 32768
+ Router Pod Env Should Have Value ROUTER_MAX_REWRITE_SIZE 8192
+ Router Pod Env Should Have Value ROUTER_DEFAULT_CLIENT_TIMEOUT 30s
+ Router Pod Env Should Have Value ROUTER_CLIENT_FIN_TIMEOUT 1s
+ Router Pod Env Should Have Value ROUTER_DEFAULT_SERVER_TIMEOUT 30s
+ Router Pod Env Should Have Value ROUTER_DEFAULT_SERVER_FIN_TIMEOUT 1s
+ Router Pod Env Should Have Value ROUTER_DEFAULT_TUNNEL_TIMEOUT 1h
+ Router Pod Env Should Have Value ROUTER_INSPECT_DELAY 5s
+ Router Pod Env Should Have Value ROUTER_THREADS 4
+ Router Pod Env Should Have Value ROUTER_MAX_CONNECTIONS 50000
+
+Verify Default Router Tuning Haproxy
+ [Documentation] Verify the default tuning values are present in haproxy.config.
+ ${haproxy}= Read Haproxy Config
+ Should Contain ${haproxy} tune.bufsize 32768
+ Should Contain ${haproxy} tune.maxrewrite 8192
+ Should Contain ${haproxy} timeout client 30s
+ Should Contain ${haproxy} timeout server 30s
+ Should Contain ${haproxy} timeout tunnel 1h
+ Should Contain ${haproxy} nbthread 4
+ Should Contain ${haproxy} maxconn 50000
+
+Verify Custom Router Tuning Env Vars
+ [Documentation] Verify the custom tuning environment variable values after config change.
+ Router Pod Env Should Have Value ROUTER_BUF_SIZE 65536
+ Router Pod Env Should Have Value ROUTER_MAX_REWRITE_SIZE 16384
+ Router Pod Env Should Have Value ROUTER_DEFAULT_CLIENT_TIMEOUT 1m
+ Router Pod Env Should Have Value ROUTER_CLIENT_FIN_TIMEOUT 2s
+ Router Pod Env Should Have Value ROUTER_DEFAULT_SERVER_TIMEOUT 1m
+ Router Pod Env Should Have Value ROUTER_DEFAULT_SERVER_FIN_TIMEOUT 2s
+ Router Pod Env Should Have Value ROUTER_DEFAULT_TUNNEL_TIMEOUT 2h
+ Router Pod Env Should Have Value ROUTER_INSPECT_DELAY 10s
+ Router Pod Env Should Have Value ROUTER_THREADS 8
+ Router Pod Env Should Have Value ROUTER_MAX_CONNECTIONS 100000
+
+Verify Custom Router Tuning Haproxy
+ [Documentation] Verify the custom tuning values are present in haproxy.config after config change.
+ ${haproxy}= Read Haproxy Config
+ Should Contain ${haproxy} tune.bufsize 65536
+ Should Contain ${haproxy} tune.maxrewrite 16384
+ Should Contain ${haproxy} timeout client 1m
+ Should Contain ${haproxy} timeout server 1m
+ Should Contain ${haproxy} timeout tunnel 2h
+ Should Contain ${haproxy} nbthread 8
+ Should Contain ${haproxy} maxconn 100000
+
+Prepare Two MTLS Certs For Test
+ [Documentation] Generate CA and two client certs with different subjects for mTLS subject filter test.
+ [Arguments] ${case_id} ${host1} ${host2}
+ VAR ${tmpdir}= /tmp/ocp-${case_id}-ca
+ Create Directory ${tmpdir}
+ VAR ${MTLS2_TMPDIR}= ${tmpdir} scope=TEST
+ VAR ${MTLS2_HOST1}= ${host1} scope=TEST
+ VAR ${MTLS2_HOST2}= ${host2} scope=TEST
+ Generate CA Certificate ${tmpdir}/ca.key ${tmpdir}/ca.crt /CN=MS-Test-Root-CA
+ Generate Client Cert File In Dir ${tmpdir} ${host1} example-test.com usr1
+ Generate Client Cert File In Dir ${tmpdir} ${host2} example-test2.com usr2
diff --git a/test/suites/router/router-config-tls.robot b/test/suites/router/router-config-tls.robot
new file mode 100644
index 0000000000..b9cf2b06e7
--- /dev/null
+++ b/test/suites/router/router-config-tls.robot
@@ -0,0 +1,223 @@
+*** Settings ***
+Documentation Router TLS and certificate configuration tests (disruptive)
+... Migrated from openshift-tests-private:
+... OCP-80508, OCP-80510, OCP-80514, OCP-80517
+
+Resource ../../resources/common.resource
+Resource ../../resources/oc.resource
+Resource ../../resources/microshift-config.resource
+Resource ../../resources/microshift-process.resource
+Resource ../../resources/ostree-health.resource
+Resource ../../resources/router.resource
+
+Suite Setup Setup Suite With Namespace
+Suite Teardown Teardown Suite With Namespace
+
+Test Tags restart slow
+
+
+*** Variables ***
+${BASE_DOMAIN} apps.example.com
+
+${CONFIG_OLD_TLS} SEPARATOR=\n
+... ---
+... ingress:
+... \ \ tlsSecurityProfile:
+... \ \ \ \ old: {}
+... \ \ \ \ type: Old
+
+${CONFIG_INTERMEDIATE_TLS} SEPARATOR=\n
+... ---
+... ingress:
+... \ \ tlsSecurityProfile:
+... \ \ \ \ intermediate: {}
+... \ \ \ \ type: Intermediate
+
+${CONFIG_MODERN_TLS} SEPARATOR=\n
+... ---
+... ingress:
+... \ \ tlsSecurityProfile:
+... \ \ \ \ modern: {}
+... \ \ \ \ type: Modern
+
+${CONFIG_CUSTOM_TLS} SEPARATOR=\n
+... ---
+... ingress:
+... \ \ tlsSecurityProfile:
+... \ \ \ \ custom:
+... \ \ \ \ \ \ ciphers:
+... \ \ \ \ \ \ - DHE-RSA-AES256-GCM-SHA384
+... \ \ \ \ \ \ - ECDHE-ECDSA-AES256-GCM-SHA384
+... \ \ \ \ \ \ minTLSVersion: VersionTLS12
+... \ \ \ \ type: Custom
+
+${CONFIG_MTLS17_REQUIRED} SEPARATOR=\n
+... ---
+... ingress:
+... \ \ clientTLS:
+... \ \ \ \ clientCA:
+... \ \ \ \ \ \ name: "ocp80517"
+... \ \ \ \ clientCertificatePolicy: "Required"
+
+${CONFIG_MTLS17_OPTIONAL} SEPARATOR=\n
+... ---
+... ingress:
+... \ \ clientTLS:
+... \ \ \ \ clientCA:
+... \ \ \ \ \ \ name: "ocp80517"
+... \ \ \ \ clientCertificatePolicy: "Optional"
+
+
+*** Test Cases ***
+Custom Default Certificate
+ [Documentation] Verify a custom TLS certificate secret can be configured as the default
+ ... ingress certificate, and that routes use the custom cert for TLS.
+ ... OCP-80508
+ [Setup] Prepare Custom Cert For Test 80508 route-edge80508.${BASE_DOMAIN}
+
+ ${config}= Catenate SEPARATOR=\n
+ ... ---
+ ... ingress:
+ ... \ \ certificateSecret: "router-test-cert"
+ Setup Router Config And Restart ${config}
+
+ Create OC Route ${NAMESPACE} edge route-edge service-unsecure --hostname=${CERT_EDGE_HOST}
+ Route Should Be Admitted route-edge
+ Verify Custom Cert Is Active
+ Deploy Test Client Pod
+ Copy Files To Pod ${NAMESPACE} ${CLIENT_POD_NAME} ${CERT_TMPDIR} /data/certs
+ ${router_ip}= Get Router Pod IP
+ VAR ${resolve}= ${CERT_EDGE_HOST}:443:${router_ip}
+ Wait Until Curl With Client Cert Succeeds
+ ... ${CLIENT_POD_NAME} ${NAMESPACE} https://${CERT_EDGE_HOST} ${resolve}
+ Wait Until HTTPS Curl Succeeds From Pod
+ ... ${CLIENT_POD_NAME} ${NAMESPACE} https://${CERT_EDGE_HOST} ${resolve}
+ [Teardown] Run Keywords
+ ... Remove Router Config And Restart
+ ... AND Run With Kubeconfig oc delete secret router-test-cert -n ${ROUTER_NS} --ignore-not-found
+ ... AND Cleanup Test Workloads
+
+Old And Intermediate TLS Profiles
+ [Documentation] Verify the default Intermediate TLS profile cipher settings, then apply Old
+ ... profile and verify updated cipher settings, then restore Intermediate.
+ ... OCP-80510
+
+ Router Pod Env Should Have Value SSL_MIN_VERSION TLSv1.2
+ ${ciphers}= Oc Get JsonPath
+ ... pod ${ROUTER_NS} ${EMPTY} .items[*].spec.containers[*].env[?(@.name=="ROUTER_CIPHERSUITES")].value
+ Should Contain ${ciphers} TLS_AES_128_GCM_SHA256
+
+ Setup Router Config And Restart ${CONFIG_OLD_TLS}
+ Router Pod Env Should Have Value SSL_MIN_VERSION TLSv1.1
+ ${ciphers}= Oc Get JsonPath
+ ... pod ${ROUTER_NS} ${EMPTY} .items[*].spec.containers[*].env[?(@.name=="ROUTER_CIPHERS")].value
+ Should Contain ${ciphers} DES-CBC3-SHA
+
+ Setup Router Config And Restart ${CONFIG_INTERMEDIATE_TLS}
+ Router Pod Env Should Have Value SSL_MIN_VERSION TLSv1.2
+ ${ciphers}= Oc Get JsonPath
+ ... pod ${ROUTER_NS} ${EMPTY} .items[*].spec.containers[*].env[?(@.name=="ROUTER_CIPHERSUITES")].value
+ Should Contain ${ciphers} TLS_AES_128_GCM_SHA256
+ [Teardown] Remove Router Config And Restart
+
+Modern And Custom TLS Profiles
+ [Documentation] Verify Modern TLS profile enforces TLSv1.3 in env vars and haproxy config,
+ ... then apply a Custom profile with specific ciphers.
+ ... OCP-80514
+
+ Setup Router Config And Restart ${CONFIG_MODERN_TLS}
+ Router Pod Env Should Have Value SSL_MIN_VERSION TLSv1.3
+ ${ciphers}= Oc Get JsonPath
+ ... pod ${ROUTER_NS} ${EMPTY} .items[*].spec.containers[*].env[?(@.name=="ROUTER_CIPHERSUITES")].value
+ Should Contain ${ciphers} TLS_AES_128_GCM_SHA256
+ ${haproxy}= Read Haproxy Config
+ Should Contain ${haproxy} ssl-default-bind-options ssl-min-ver TLSv1.3
+
+ Setup Router Config And Restart ${CONFIG_CUSTOM_TLS}
+ Router Pod Env Should Have Value SSL_MIN_VERSION TLSv1.2
+ ${ciphers}= Oc Get JsonPath
+ ... pod ${ROUTER_NS} ${EMPTY} .items[*].spec.containers[*].env[?(@.name=="ROUTER_CIPHERS")].value
+ Should Contain ${ciphers} DHE-RSA-AES256-GCM-SHA384
+ [Teardown] Remove Router Config And Restart
+
+MTLS Optional And Required Policy
+ [Documentation] Verify mTLS with clientCertificatePolicy Required rejects connections without
+ ... client cert, and Optional policy allows them.
+ ... OCP-80517
+ [Setup] Prepare MTLS Cert For Test 80517 route-edge80517.${BASE_DOMAIN}
+
+ Run With Kubeconfig oc create configmap ocp80517 --from-file=ca-bundle.pem=${MTLS_TMPDIR}/ca.crt -n ${ROUTER_NS}
+ Setup Router Config And Restart ${CONFIG_MTLS17_REQUIRED}
+ Router Pod Env Should Have Value ROUTER_MUTUAL_TLS_AUTH required
+ Deploy MTLS Test Workloads
+ ${router_ip}= Get Router Pod IP
+ VAR ${resolve}= ${MTLS_EDGE_HOST}:443:${router_ip}
+ Wait Until Curl With Client Cert Succeeds
+ ... ${CLIENT_POD_NAME} ${NAMESPACE} https://${MTLS_EDGE_HOST} ${resolve}
+ Curl Without Cert Should Return SSL Error
+ ... ${CLIENT_POD_NAME} ${NAMESPACE} https://${MTLS_EDGE_HOST} ${resolve}
+
+ Setup Router Config And Restart ${CONFIG_MTLS17_OPTIONAL}
+ Router Pod Env Should Have Value ROUTER_MUTUAL_TLS_AUTH optional
+ ${router_ip}= Get Router Pod IP
+ VAR ${resolve}= ${MTLS_EDGE_HOST}:443:${router_ip}
+ Wait Until Curl With Client Cert Succeeds
+ ... ${CLIENT_POD_NAME} ${NAMESPACE} https://${MTLS_EDGE_HOST} ${resolve}
+ Wait Until HTTPS Curl Succeeds From Pod
+ ... ${CLIENT_POD_NAME} ${NAMESPACE} https://${MTLS_EDGE_HOST} ${resolve}
+ [Teardown] Run Keywords
+ ... Remove Router Config And Restart
+ ... AND Run With Kubeconfig oc delete configmap ocp80517 -n ${ROUTER_NS} --ignore-not-found
+ ... AND Cleanup Test Workloads
+
+
+*** Keywords ***
+Prepare Custom Cert For Test
+ [Documentation] Generate CA and user cert for custom certificate test, create TLS secret.
+ [Arguments] ${case_id} ${edge_host}
+ VAR ${tmpdir}= /tmp/ocp-${case_id}
+ Create Directory ${tmpdir}
+ VAR ${CERT_TMPDIR}= ${tmpdir} scope=TEST
+ VAR ${CERT_EDGE_HOST}= ${edge_host} scope=TEST
+ Generate CA Certificate ${tmpdir}/ca.key ${tmpdir}/ca.crt /CN=MS-default-CA
+ ${san}= Catenate SEPARATOR=\n
+ ... [ v3_req ]
+ ... subjectAltName = @alt_names
+ ... [ alt_names ]
+ ... DNS.1 = *.${BASE_DOMAIN}
+ Generate CSR And Key ${tmpdir}/usr.key ${tmpdir}/usr.csr /CN=example-ne.com
+ Sign CSR With CA ${tmpdir}/usr.csr ${tmpdir}/ca.crt ${tmpdir}/ca.key ${tmpdir}/usr.crt ${san}
+ Run With Kubeconfig
+ ... oc create secret tls router-test-cert --cert=${tmpdir}/ca.crt --key=${tmpdir}/ca.key -n ${ROUTER_NS}
+ Deploy Web Server
+
+Verify Custom Cert Is Active
+ [Documentation] Verify the custom cert secret is mounted and the cert issuer is correct.
+ ${vol}= Oc Get JsonPath
+ ... deployment
+ ... ${ROUTER_NS}
+ ... router-default
+ ... ..volumes[?(@.name=="default-certificate")].secret.secretName
+ Should Contain ${vol} router-test-cert
+ ${router_pod}= Get Router Pod Name
+ ${cert_info}= Run With Kubeconfig
+ ... oc exec -n ${ROUTER_NS} ${router_pod} -- openssl x509 -noout -in /etc/pki/tls/private/tls.crt -text
+ Should Contain ${cert_info} CN = MS-default-CA
+
+Prepare MTLS Cert For Test
+ [Documentation] Generate CA and client cert for mTLS tests.
+ [Arguments] ${case_id} ${edge_host}
+ VAR ${tmpdir}= /tmp/ocp-${case_id}-ca
+ Create Directory ${tmpdir}
+ VAR ${MTLS_TMPDIR}= ${tmpdir} scope=TEST
+ VAR ${MTLS_EDGE_HOST}= ${edge_host} scope=TEST
+ Generate MTLS Client Cert ${tmpdir} ${edge_host}
+
+Deploy MTLS Test Workloads
+ [Documentation] Deploy workloads and create the mTLS edge route.
+ Deploy Web Server
+ Deploy Test Client Pod
+ Copy Files To Pod ${NAMESPACE} ${CLIENT_POD_NAME} ${MTLS_TMPDIR} /data/certs
+ Create OC Route ${NAMESPACE} edge route-edge service-unsecure
+ ... --hostname=${MTLS_EDGE_HOST} --cert=${MTLS_TMPDIR}/usr.crt --key=${MTLS_TMPDIR}/usr.key
+ Route Should Be Admitted route-edge
diff --git a/test/suites/router/router-routes.robot b/test/suites/router/router-routes.robot
new file mode 100644
index 0000000000..ff79b66535
--- /dev/null
+++ b/test/suites/router/router-routes.robot
@@ -0,0 +1,249 @@
+*** Settings ***
+Documentation Router end-to-end route tests
+... Migrated from openshift-tests-private:
+... OCP-60136, OCP-60266, OCP-60283, OCP-72802, OCP-73152, OCP-73202
+
+Resource ../../resources/common.resource
+Resource ../../resources/oc.resource
+Resource ../../resources/microshift-network.resource
+Resource ../../resources/router.resource
+
+Suite Setup Setup Suite With Namespace
+Suite Teardown Teardown Suite With Namespace
+
+Test Tags slow
+
+
+*** Variables ***
+${BASE_DOMAIN} apps.example.com
+
+
+*** Test Cases ***
+Reencrypt Route Via Ingress With Destination CA
+ [Documentation] Verify a reencrypt route created via a Kubernetes Ingress resource with
+ ... destination CA certificate is admitted and reachable.
+ ... OCP-60136
+ [Setup] Setup Reencrypt Ingress Test
+
+ ${router_ip}= Get Router Pod IP
+ ${srv_pod}= Get Web Server Pod Name
+ Wait Until HTTPS Curl Succeeds From Pod
+ ... ${srv_pod} ${NAMESPACE}
+ ... https://service-secure-test.example.com:443 service-secure-test.example.com:443:${router_ip}
+ ${haproxy}= Read Haproxy Config
+ Should Contain ${haproxy} backend be_secure:${NAMESPACE}:ingress-ms-reen
+ [Teardown] Teardown Reencrypt Ingress Test
+
+Edge And Passthrough Routes
+ [Documentation] Verify edge-terminated and passthrough route creation and connectivity.
+ ... OCP-60266
+ [Setup] Deploy Web Server
+
+ ${router_ip}= Get Router Pod IP
+ ${srv_pod}= Get Web Server Pod Name
+ VAR ${pass_host}= route-pass-60266.${BASE_DOMAIN}
+ VAR ${edge_host}= route-edge-60266.${BASE_DOMAIN}
+
+ Create OC Route ${NAMESPACE} passthrough ms-pass service-secure --hostname=${pass_host}
+ Route Should Be Admitted ms-pass
+ Wait Until HTTPS Curl Succeeds From Pod
+ ... ${srv_pod} ${NAMESPACE} https://${pass_host}:443 ${pass_host}:443:${router_ip}
+ ${haproxy}= Read Haproxy Config
+ Should Contain ${haproxy} backend be_tcp:${NAMESPACE}:ms-pass
+
+ Create OC Route ${NAMESPACE} edge ms-edge service-unsecure --hostname=${edge_host}
+ Route Should Be Admitted ms-edge
+ Wait Until HTTPS Curl Succeeds From Pod
+ ... ${srv_pod} ${NAMESPACE} https://${edge_host}:443 ${edge_host}:443:${router_ip}
+ ${haproxy}= Read Haproxy Config
+ Should Contain ${haproxy} backend be_edge_http:${NAMESPACE}:ms-edge
+ [Teardown] Run Keywords
+ ... Oc Delete route/ms-pass route/ms-edge -n ${NAMESPACE}
+ ... AND Oc Delete -f ${WEB_SERVER_DEPLOY} -n ${NAMESPACE}
+
+HTTP And Reencrypt Routes
+ [Documentation] Verify HTTP route via oc expose and reencrypt route creation and connectivity.
+ ... OCP-60283
+ [Setup] Deploy Web Server Signed
+
+ ${router_ip}= Get Router Pod IP
+ ${srv_pod}= Get Web Server Pod Name
+ VAR ${http_host}= route-http-60283.${BASE_DOMAIN}
+ VAR ${reen_host}= route-reen-60283.${BASE_DOMAIN}
+
+ Create OC Route ${NAMESPACE} http ms-http service-unsecure --hostname=${http_host}
+ Route Should Be Admitted ms-http
+ Wait Until Curl Succeeds From Pod
+ ... ${srv_pod} ${NAMESPACE} http://${http_host}:80 ${http_host}:80:${router_ip}
+ ${haproxy}= Read Haproxy Config
+ Should Contain ${haproxy} backend be_http:${NAMESPACE}:ms-http
+
+ Create OC Route ${NAMESPACE} reencrypt ms-reen service-secure --hostname=${reen_host}
+ Route Should Be Admitted ms-reen
+ Wait Until HTTPS Curl Succeeds From Pod
+ ... ${srv_pod} ${NAMESPACE} https://${reen_host}:443 ${reen_host}:443:${router_ip}
+ ${haproxy}= Read Haproxy Config
+ Should Contain ${haproxy} backend be_secure:${NAMESPACE}:ms-reen
+ [Teardown] Run Keywords
+ ... Oc Delete route/ms-http route/ms-reen -n ${NAMESPACE}
+ ... AND Oc Delete -f ${WEB_SERVER_SIGNED_DEPLOY} -n ${NAMESPACE}
+
+Namespace Ownership Default Config
+ [Documentation] Verify the default InterNamespaceAllowed config allows routes from different
+ ... namespaces to share the same hostname with different paths.
+ ... OCP-72802
+ [Setup] Setup Two Namespace Test
+
+ ${router_ip}= Get Router Pod IP
+ VAR ${HTTP_HOST}= service-unsecure-ocp72802.${BASE_DOMAIN} scope=TEST
+ VAR ${EDGE_HOST}= route-edge-ocp72802.${BASE_DOMAIN} scope=TEST
+ VAR ${REEN_HOST}= route-reen-ocp72802.${BASE_DOMAIN} scope=TEST
+
+ ${env}= Oc Get JsonPath
+ ... pod ${ROUTER_NS} ${EMPTY}
+ ... .items[*].spec.containers[*].env[?(@.name=="ROUTER_DISABLE_NAMESPACE_OWNERSHIP_CHECK")].value
+ Should Be Equal As Strings ${env} true
+
+ Create NS Ownership Routes
+ All NS Ownership Routes Should Be Admitted
+
+ Wait Until Curl Succeeds From Pod
+ ... ${CLIENT_POD_NAME} ${NS1} http://${HTTP_HOST}/path/index.html ${HTTP_HOST}:80:${router_ip}
+ Wait Until Curl Succeeds From Pod
+ ... ${CLIENT_POD_NAME} ${NS1} http://${HTTP_HOST}/test/index.html ${HTTP_HOST}:80:${router_ip}
+ [Teardown] Teardown Two Namespace Test
+
+Router Load Balancer Service Type
+ [Documentation] Verify the router-default service is of type LoadBalancer with IPs assigned,
+ ... and that all route types are reachable through the LB IP.
+ ... OCP-73152
+ [Setup] Run Keywords
+ ... Deploy Web Server Signed
+ ... AND Deploy Test Client Pod
+
+ ${svc_type}= Oc Get JsonPath service ${ROUTER_NS} router-default .spec.type
+ Should Be Equal As Strings ${svc_type} LoadBalancer
+ ${lb_ips}= Get LB IPs
+ Should Not Be Empty ${lb_ips}
+
+ VAR ${HTTP_HOST}= service-unsecure-ocp73152.${BASE_DOMAIN} scope=TEST
+ VAR ${EDGE_HOST}= route-edge-ocp73152.${BASE_DOMAIN} scope=TEST
+ VAR ${PASS_HOST}= route-passth-ocp73152.${BASE_DOMAIN} scope=TEST
+ VAR ${REEN_HOST}= route-reen-ocp73152.${BASE_DOMAIN} scope=TEST
+
+ Create And Admit Four Route Types ${HTTP_HOST} ${EDGE_HOST} ${PASS_HOST} ${REEN_HOST}
+
+ ${lb_ip}= Fetch From Left ${lb_ips} ${SPACE}
+ Curl All LB Route Types Via IP ${lb_ip}
+ [Teardown] Run Keywords
+ ... Oc Delete route/route-http route/route-edge route/route-passth route/route-reen -n ${NAMESPACE}
+ ... AND Oc Delete -f ${WEB_SERVER_SIGNED_DEPLOY} -n ${NAMESPACE}
+ ... AND Oc Delete -f ${TEST_CLIENT_POD} -n ${NAMESPACE}
+
+Default Listening IPs And Ports
+ [Documentation] Verify the router-default service LB IPs match all host IPs, default ports
+ ... are 80 and 443, and all route types are reachable through each LB IP.
+ ... OCP-73202
+ [Setup] Run Keywords
+ ... Deploy Web Server Signed
+ ... AND Deploy Test Client Pod
+
+ Verify Default LB IPs And Ports
+
+ VAR ${HTTP_HOST}= service-unsecure-ocp73202.${BASE_DOMAIN} scope=TEST
+ VAR ${EDGE_HOST}= route-edge-ocp73202.${BASE_DOMAIN} scope=TEST
+ VAR ${PASS_HOST}= route-passth-ocp73202.${BASE_DOMAIN} scope=TEST
+ VAR ${REEN_HOST}= route-reen-ocp73202.${BASE_DOMAIN} scope=TEST
+
+ Create And Admit Four Route Types ${HTTP_HOST} ${EDGE_HOST} ${PASS_HOST} ${REEN_HOST}
+
+ ${lb_ips}= Get LB IPs
+ @{ips}= Split String ${lb_ips}
+ FOR ${lb_ip} IN @{ips}
+ Curl All LB Route Types Via IP ${lb_ip}
+ END
+ [Teardown] Run Keywords
+ ... Oc Delete route/route-http route/route-edge route/route-passth route/route-reen -n ${NAMESPACE}
+ ... AND Oc Delete -f ${WEB_SERVER_SIGNED_DEPLOY} -n ${NAMESPACE}
+ ... AND Oc Delete -f ${TEST_CLIENT_POD} -n ${NAMESPACE}
+
+
+*** Keywords ***
+Setup Reencrypt Ingress Test
+ [Documentation] Deploy web-server-signed and apply the destCA ingress.
+ Deploy Web Server Signed
+ Oc Create -f ${INGRESS_DESTCA} -n ${NAMESPACE}
+ Route Should Be Admitted ingress-ms-reen
+
+Teardown Reencrypt Ingress Test
+ [Documentation] Delete the destCA ingress and web-server deployment.
+ Oc Delete -f ${INGRESS_DESTCA} -n ${NAMESPACE}
+ Oc Delete -f ${WEB_SERVER_SIGNED_DEPLOY} -n ${NAMESPACE}
+
+Setup Two Namespace Test
+ [Documentation] Create two extra namespaces and deploy workloads in each.
+ VAR ${NS1}= ${NAMESPACE}-ocp72802-1 scope=TEST
+ VAR ${NS2}= ${NAMESPACE}-ocp72802-2 scope=TEST
+ Create Namespace ${NS1}
+ Create Namespace ${NS2}
+ Deploy Test Client Pod ${NS1}
+ Deploy Web Server ${NS1}
+ Deploy Test Client Pod ${NS2}
+ Deploy Web Server ${NS2}
+
+Teardown Two Namespace Test
+ [Documentation] Delete the extra namespaces.
+ Remove Namespace ${NS1}
+ Remove Namespace ${NS2}
+
+Create NS Ownership Routes
+ [Documentation] Create HTTP, edge, and reencrypt routes in both test namespaces.
+ Create OC Route ${NS1} http service-unsecure service-unsecure
+ ... --hostname=${HTTP_HOST} --path=/path
+ Create OC Route ${NS1} edge route-edge service-unsecure
+ ... --hostname=${EDGE_HOST} --path=/path
+ Create OC Route ${NS1} reencrypt route-reen service-secure
+ ... --hostname=${REEN_HOST} --path=/path
+ Create OC Route ${NS2} http service-unsecure service-unsecure
+ ... --hostname=${HTTP_HOST} --path=/test
+ Create OC Route ${NS2} edge route-edge service-unsecure
+ ... --hostname=${EDGE_HOST} --path=/test
+ Create OC Route ${NS2} reencrypt route-reen service-secure
+ ... --hostname=${REEN_HOST} --path=/test
+
+All NS Ownership Routes Should Be Admitted
+ [Documentation] Verify all six routes across both namespaces are admitted.
+ Route Should Be Admitted service-unsecure ${NS1}
+ Route Should Be Admitted route-edge ${NS1}
+ Route Should Be Admitted route-reen ${NS1}
+ Route Should Be Admitted service-unsecure ${NS2}
+ Route Should Be Admitted route-edge ${NS2}
+ Route Should Be Admitted route-reen ${NS2}
+
+Curl All LB Route Types Via IP
+ [Documentation] Curl all four route types through a single LB IP.
+ [Arguments] ${ip}
+ Wait Until Curl Succeeds From Pod
+ ... ${CLIENT_POD_NAME}
+ ... ${NAMESPACE}
+ ... http://${HTTP_HOST}
+ ... ${HTTP_HOST}:80:${ip}
+ Wait Until HTTPS Curl Succeeds From Pod
+ ... ${CLIENT_POD_NAME} ${NAMESPACE} https://${EDGE_HOST} ${EDGE_HOST}:443:${ip}
+ Wait Until HTTPS Curl Succeeds From Pod
+ ... ${CLIENT_POD_NAME} ${NAMESPACE} https://${PASS_HOST} ${PASS_HOST}:443:${ip}
+ Wait Until HTTPS Curl Succeeds From Pod
+ ... ${CLIENT_POD_NAME} ${NAMESPACE} https://${REEN_HOST} ${REEN_HOST}:443:${ip}
+
+Verify Default LB IPs And Ports
+ [Documentation] Verify the router-default service LB IPs match all host IPs and ports are 80/443.
+ ${http_port}= Get LB Port http
+ Should Be Equal As Strings ${http_port} 80
+ ${https_port}= Get LB Port https
+ Should Be Equal As Strings ${https_port} 443
+ ${lb_ips}= Get LB IPs
+ Should Not Be Empty ${lb_ips}
+ @{host_ips}= Get Host IPs Via SSH
+ ${sorted_host_ips}= Evaluate " ".join(sorted(${host_ips}))
+ ${sorted_lb_ips}= Evaluate " ".join(sorted("${lb_ips}".split()))
+ Should Be Equal As Strings ${sorted_lb_ips} ${sorted_host_ips}