Skip to content

Commit 3afa0dc

Browse files
committed
Add eco-gotests step for SNO day-2 worker validation (kni-qe-107)
Add telcov10n-functional-cnf-ran-eco-gotests-sno-worker step to the cnf-ran-sno-day2-worker-4.20 job. The step runs eco-gotests against the spoke cluster's day-2 worker node after ZTP provisioning. The step: - Builds Ansible inventory from its own credential mounts - Extracts the spoke kubeconfig from the hub cluster - Waits for the worker node to reach Ready state (30m timeout) - Generates and runs eco-gotests per feature via Ansible and SSH - Mirrors eco-gotests images to the disconnected registry - Collects JUnit and Polarion XML reports as CI artifacts - Uses a failure flag so all features run even if one fails Also adds ECO_GOTESTS_FEATURES and MIRROR_REGISTRY env vars to the test configuration for explicit control of test scope and registry.
1 parent a406a04 commit 3afa0dc

6 files changed

Lines changed: 328 additions & 0 deletions

ci-operator/config/openshift-kni/eco-ci-cd/openshift-kni-eco-ci-cd-main__cnf-ran-sno-day2-worker-4.20.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ tests:
2626
CLUSTER_NAME: kni-qe-106
2727
DISABLE_INSIGHTS: "true"
2828
DISCONNECTED: "true"
29+
ECO_GOTESTS_FEATURES: gitopsztp deploymenttypes
30+
HUB_CLUSTER: kni-qe-106
2931
HUB_OPERATORS: |
3032
[
3133
{"name":"local-storage-operator","catalog":"redhat-operators","nsname":"openshift-local-storage","channel":"stable","og_name":"local-operator-group","subscription_name":"local-storage-operator","deploy_default_config":false,"ns_annotations":{"workload.openshift.io/allowed":"management"}},
@@ -35,6 +37,7 @@ tests:
3537
{"name":"topology-aware-lifecycle-manager","catalog":"topology-aware-lifecycle-manager-fbc","og_name":"global-operators","nsname":"openshift-operators","fbc_iib_repo":"latest","channel":"stable","deploy_default_config":false,"ocp_operator_mirror_fbc_image_base":"quay.io/redhat-user-workloads/telco-5g-tenant/topology-aware-lifecycle-manager-fbc-4-20"},
3638
{"name":"cluster-logging","catalog":"redhat-operators","nsname":"openshift-logging","channel":"stable-6.2","default_channel":"stable-6.5","og_name":"cluster-logging","subscription_name":"cluster-logging","og_spec":{"targetNamespaces":[]}}
3739
]
40+
MIRROR_REGISTRY: disconnected.registry.local:5000
3841
SPOKE_CLUSTER: '[''kni-qe-107'']'
3942
SPOKE_OPERATORS: |
4043
[
@@ -54,6 +57,7 @@ tests:
5457
- ref: telcov10n-functional-cnf-ran-mirror-spoke-operators
5558
- ref: telcov10n-functional-cnf-ran-deploy-spoke-sno
5659
- ref: telcov10n-functional-cnf-ran-deploy-spoke-sno-day2-worker
60+
- ref: telcov10n-functional-cnf-ran-eco-gotests-sno-worker
5761
zz_generated_metadata:
5862
branch: main
5963
org: openshift-kni
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"path": "telcov10n/functional/cnf-ran/cnf-tests-sno-worker/telcov10n-functional-cnf-ran-cnf-tests-sno-worker-ref.yaml",
3+
"owners": {
4+
"approvers": [
5+
"shaior",
6+
"kononovn",
7+
"eifrach"
8+
]
9+
}
10+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
approvers:
2+
- shaior
3+
- kononovn
4+
- eifrach
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
#!/bin/bash
2+
set -e
3+
set -o pipefail
4+
5+
echo "Checking if the job should be skipped..."
6+
if [ -f "${SHARED_DIR}/skip.txt" ]; then
7+
echo "Detected skip.txt file — skipping the job"
8+
exit 0
9+
fi
10+
11+
ECO_CI_CD_INVENTORY_PATH="/eco-ci-cd/inventories/cnf"
12+
13+
process_inventory() {
14+
local directory="$1"
15+
local dest_file="$2"
16+
17+
if [ -z "$directory" ]; then
18+
echo "Usage: process_inventory <directory> <dest_file>"
19+
return 1
20+
fi
21+
22+
if [ ! -d "$directory" ]; then
23+
echo "Error: '$directory' is not a valid directory"
24+
return 1
25+
fi
26+
27+
find "$directory" -type f | while IFS= read -r filename; do
28+
if [[ $filename == *"secretsync-vault-source-path"* ]]; then
29+
continue
30+
fi
31+
local content
32+
content=$(cat "$filename")
33+
local varname
34+
varname=$(basename "${filename}")
35+
# Check if content has newlines - if so, use literal block scalar (|)
36+
if [[ "$content" == *$'\n'* ]]; then
37+
echo "${varname}: |"
38+
echo "$content" | sed 's/^/ /'
39+
else
40+
echo "${varname}: '${content//\'/''}'"
41+
fi
42+
done > "${dest_file}"
43+
44+
echo "Processing complete. Check \"${dest_file}\""
45+
}
46+
47+
# SPOKE_CLUSTER may arrive as a JSON array (e.g. "['kni-qe-107']" or '["kni-qe-107"]') from test-level env
48+
SPOKE_CLUSTER=$(echo "${SPOKE_CLUSTER}" | tr -d "[]'\" ")
49+
if [[ "${SPOKE_CLUSTER}" == *,* ]]; then
50+
echo "Error: SPOKE_CLUSTER must resolve to exactly one cluster name, got: '${SPOKE_CLUSTER}'"
51+
exit 1
52+
fi
53+
if [[ -z "${SPOKE_CLUSTER}" ]]; then
54+
echo "Error: SPOKE_CLUSTER is empty after normalization"
55+
exit 1
56+
fi
57+
if [[ ! "${SPOKE_CLUSTER}" =~ ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$ ]]; then
58+
echo "Error: SPOKE_CLUSTER contains invalid characters: '${SPOKE_CLUSTER}' (only lowercase alphanumerics and hyphens allowed)"
59+
exit 1
60+
fi
61+
62+
# HUB_CLUSTER may arrive as a JSON array (same as SPOKE_CLUSTER)
63+
HUB_CLUSTER=$(echo "${HUB_CLUSTER}" | tr -d "[]'\" ")
64+
if [[ "${HUB_CLUSTER}" == *,* ]]; then
65+
echo "Error: HUB_CLUSTER must resolve to exactly one cluster name, got: '${HUB_CLUSTER}'"
66+
exit 1
67+
fi
68+
if [[ -z "${HUB_CLUSTER}" ]]; then
69+
echo "Error: HUB_CLUSTER is empty after normalization"
70+
exit 1
71+
fi
72+
if [[ ! "${HUB_CLUSTER}" =~ ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$ ]]; then
73+
echo "Error: HUB_CLUSTER contains invalid characters: '${HUB_CLUSTER}' (only lowercase alphanumerics and hyphens allowed)"
74+
exit 1
75+
fi
76+
77+
echo "SPOKE_CLUSTER=${SPOKE_CLUSTER}"
78+
echo "HUB_CLUSTER=${HUB_CLUSTER}"
79+
echo "ECO_GOTESTS_FEATURES=${ECO_GOTESTS_FEATURES}"
80+
echo "MIRROR_REGISTRY=${MIRROR_REGISTRY}"
81+
82+
echo "Create group_vars directory"
83+
mkdir -p "${ECO_CI_CD_INVENTORY_PATH}/group_vars"
84+
85+
echo "Process common group variables (all, bastions)"
86+
while read -r dir; do
87+
echo "Process group inventory file: ${dir}"
88+
process_inventory "$dir" "${ECO_CI_CD_INVENTORY_PATH}/group_vars/$(basename "${dir}")"
89+
done < <(find /var/group_variables/common/ -mindepth 1 -maxdepth 1 -type d ! -name '..*' 2>/dev/null)
90+
91+
echo "Process spoke cluster group variables"
92+
while read -r dir; do
93+
echo "Process group inventory file: ${dir}"
94+
process_inventory "$dir" "${ECO_CI_CD_INVENTORY_PATH}/group_vars/$(basename "${dir}")"
95+
done < <(find "/var/group_variables/${SPOKE_CLUSTER}/" -mindepth 1 -maxdepth 1 -type d ! -name '..*' 2>/dev/null)
96+
97+
echo "Create host_vars directory"
98+
mkdir -p "${ECO_CI_CD_INVENTORY_PATH}/host_vars"
99+
100+
echo "Process bastion host variables (from hub ${HUB_CLUSTER})"
101+
while read -r dir; do
102+
echo "Process host inventory file: ${dir}"
103+
process_inventory "$dir" "${ECO_CI_CD_INVENTORY_PATH}/host_vars/$(basename "${dir}")"
104+
done < <(find "/var/host_variables/${HUB_CLUSTER}/" -mindepth 1 -maxdepth 1 -type d ! -name '..*' 2>/dev/null)
105+
106+
echo "Process spoke cluster host variables"
107+
while read -r dir; do
108+
echo "Process host inventory file: ${dir}"
109+
process_inventory "$dir" "${ECO_CI_CD_INVENTORY_PATH}/host_vars/$(basename "${dir}")"
110+
done < <(find "/var/host_variables/${SPOKE_CLUSTER}/" -mindepth 1 -maxdepth 1 -type d ! -name '..*' 2>/dev/null)
111+
112+
WORKDIR=$(mktemp -d)
113+
HUB_CLUSTERCONFIGS_PATH="/home/telcov10n/project/generated/${HUB_CLUSTER}"
114+
HUB_KUBECONFIG_PATH="${HUB_CLUSTERCONFIGS_PATH}/auth/kubeconfig"
115+
116+
echo "Set bastion ssh configuration"
117+
cat /var/group_variables/common/all/ansible_ssh_private_key > "${WORKDIR}/temp_ssh_key"
118+
119+
chmod 600 "${WORKDIR}/temp_ssh_key"
120+
BASTION_IP=$(grep -oP '(?<=ansible_host: ).*' "${ECO_CI_CD_INVENTORY_PATH}/host_vars/bastion" | sed "s/'//g")
121+
BASTION_USER=$(grep -oP '(?<=ansible_user: ).*' "${ECO_CI_CD_INVENTORY_PATH}/group_vars/all" | sed "s/'//g")
122+
123+
SSH_OPTS=(-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null)
124+
SSH_OPTS_KEEPALIVE=(-o ServerAliveInterval=60 -o ServerAliveCountMax=3 "${SSH_OPTS[@]}")
125+
126+
echo "Create remote working directory"
127+
REMOTE_WORKDIR=$(ssh "${SSH_OPTS[@]}" "${BASTION_USER}@${BASTION_IP}" -i "${WORKDIR}/temp_ssh_key" "mktemp -d")
128+
129+
SPOKE_KUBECONFIG="${REMOTE_WORKDIR}/${SPOKE_CLUSTER}-kubeconfig"
130+
131+
cleanup() {
132+
ssh "${SSH_OPTS[@]}" "${BASTION_USER}@${BASTION_IP}" -i "${WORKDIR}/temp_ssh_key" \
133+
"rm -rf '${REMOTE_WORKDIR}'" 2>/dev/null || true
134+
rm -rf "${WORKDIR}"
135+
}
136+
trap cleanup EXIT
137+
138+
echo "Extract spoke kubeconfig from hub via bastion"
139+
ssh "${SSH_OPTS[@]}" "${BASTION_USER}@${BASTION_IP}" -i "${WORKDIR}/temp_ssh_key" \
140+
"oc --kubeconfig='${HUB_KUBECONFIG_PATH}' \
141+
get secret ${SPOKE_CLUSTER}-admin-kubeconfig \
142+
-n ${SPOKE_CLUSTER} \
143+
-o jsonpath='{.data.kubeconfig}' | base64 -d > '${SPOKE_KUBECONFIG}'"
144+
145+
echo "Wait for spoke worker node to be Ready"
146+
ssh "${SSH_OPTS[@]}" "${BASTION_USER}@${BASTION_IP}" -i "${WORKDIR}/temp_ssh_key" \
147+
"oc --kubeconfig='${SPOKE_KUBECONFIG}' \
148+
wait --for=condition=Ready node \
149+
--selector=node-role.kubernetes.io/worker,\!node-role.kubernetes.io/master \
150+
--timeout=30m"
151+
152+
ACM_OPERATOR_NAMESPACE="open-cluster-management"
153+
154+
ADDITIONAL_TEST_ENV_VARS="\
155+
-e ECO_CNF_RAN_SKIP_TLS_VERIFY=true \
156+
-e ECO_TEST_LABELS='!no-container' \
157+
-e ECO_CNF_RAN_ACM_OPERATOR_NAMESPACE=${ACM_OPERATOR_NAMESPACE} \
158+
-e ECO_TEST_TRACE=true \
159+
-e ECO_VERBOSE_SCRIPT=true \
160+
"
161+
162+
cd /eco-ci-cd
163+
164+
step_failed=0
165+
166+
for feature in ${ECO_GOTESTS_FEATURES}; do
167+
ECO_GOTEST_DIR="${REMOTE_WORKDIR}/eco_gotests_${feature}"
168+
echo "Generate eco-gotests scripts for feature: ${feature}"
169+
170+
playbook_rc=0
171+
ansible-playbook ./playbooks/deploy-run-eco-gotests.yaml \
172+
-i ./inventories/cnf/switch-config.yaml \
173+
--extra-vars "kubeconfig=${SPOKE_KUBECONFIG} features=${feature} labels='' eco_gotest_dir=${ECO_GOTEST_DIR}" \
174+
--extra-vars "eco_gotests_tag=latest eco_worker_label=worker" \
175+
--extra-vars "hub_clusterconfigs_path=${HUB_CLUSTERCONFIGS_PATH}" \
176+
--extra-vars "mirror_registry=${MIRROR_REGISTRY}" \
177+
--extra-vars "additional_test_env_variables='${ADDITIONAL_TEST_ENV_VARS}'" \
178+
-vv || playbook_rc=$?
179+
if [[ ${playbook_rc} -ne 0 ]]; then
180+
echo "ERROR: ansible-playbook failed for feature ${feature} (exit code ${playbook_rc})"
181+
step_failed=1
182+
fi
183+
done
184+
185+
echo "Run eco-gotests via SSH"
186+
for feature in ${ECO_GOTESTS_FEATURES}; do
187+
ECO_GOTEST_DIR="${REMOTE_WORKDIR}/eco_gotests_${feature}"
188+
echo "Run eco-gotests ${feature} tests via SSH"
189+
feature_rc=0
190+
ssh "${SSH_OPTS_KEEPALIVE[@]}" "${BASTION_USER}@${BASTION_IP}" -i "${WORKDIR}/temp_ssh_key" \
191+
"cd ${ECO_GOTEST_DIR} && ./eco-gotests-run.sh" || feature_rc=$?
192+
if [[ ${feature_rc} -ne 0 ]]; then
193+
echo "ERROR: eco-gotests ${feature} exited with code ${feature_rc}"
194+
step_failed=1
195+
fi
196+
done
197+
198+
echo "Collect artifacts from bastion"
199+
for feature in ${ECO_GOTESTS_FEATURES}; do
200+
ECO_GOTEST_DIR="${REMOTE_WORKDIR}/eco_gotests_${feature}"
201+
ARTIFACT_SUBDIR="${ARTIFACT_DIR}/junit_eco_gotests_${feature}"
202+
mkdir -p "${ARTIFACT_SUBDIR}"
203+
204+
scp_stderr=$(mktemp)
205+
scp_rc=0
206+
scp -r "${SSH_OPTS[@]}" -i "${WORKDIR}/temp_ssh_key" \
207+
"${BASTION_USER}@${BASTION_IP}:${ECO_GOTEST_DIR}/report/*.xml" \
208+
"${ARTIFACT_SUBDIR}/" 2>"${scp_stderr}" || scp_rc=$?
209+
if [[ ${scp_rc} -ne 0 ]]; then
210+
scp_err_msg=$(cat "${scp_stderr}")
211+
if [[ "${scp_err_msg}" == *"No such file"* || "${scp_err_msg}" == *"not found"* ]]; then
212+
echo "No report files found for feature ${feature} (non-fatal): ${scp_err_msg}"
213+
else
214+
echo "WARNING: scp failed for feature ${feature} (exit code ${scp_rc}): ${scp_err_msg}"
215+
fi
216+
fi
217+
rm -f "${scp_stderr}"
218+
done
219+
220+
echo "Copy reports to SHARED_DIR with prefixes"
221+
for feature in ${ECO_GOTESTS_FEATURES}; do
222+
ARTIFACT_SUBDIR="${ARTIFACT_DIR}/junit_eco_gotests_${feature}"
223+
224+
# Polarion reports
225+
for f in "${ARTIFACT_SUBDIR}"/report_*.xml; do
226+
if [[ -f "$f" ]]; then
227+
filename=$(basename "$f")
228+
echo "Copying polarion report: ${feature}/${filename} -> polarion_${feature}_${filename}"
229+
cp "$f" "${SHARED_DIR}/polarion_${feature}_${filename}"
230+
fi
231+
done
232+
233+
# Junit reports
234+
for f in "${ARTIFACT_SUBDIR}"/*.xml; do
235+
if [[ -f "$f" ]]; then
236+
filename=$(basename "$f")
237+
if [[ "$filename" == *junit*.xml || "$filename" == *_suite_*.xml ]] && [[ "$filename" != report_*.xml ]]; then
238+
echo "Copying junit report: ${feature}/${filename} -> junit_${feature}_${filename}"
239+
cp "$f" "${SHARED_DIR}/junit_${feature}_${filename}"
240+
fi
241+
fi
242+
done
243+
done
244+
245+
exit "${step_failed}"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"path": "telcov10n/functional/cnf-ran/eco-gotests-sno-worker/telcov10n-functional-cnf-ran-eco-gotests-sno-worker-ref.yaml",
3+
"owners": {
4+
"approvers": [
5+
"shaior",
6+
"kononovn",
7+
"eifrach"
8+
]
9+
}
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
ref:
2+
as: telcov10n-functional-cnf-ran-eco-gotests-sno-worker
3+
from_image:
4+
namespace: telcov10n-ci
5+
name: eco-ci-cd
6+
tag: eco-ci-cd
7+
commands: telcov10n-functional-cnf-ran-eco-gotests-sno-worker-commands.sh
8+
documentation: |-
9+
Run eco-gotests on SNO spoke cluster with day-2 worker node (kni-qe-107)
10+
grace_period: 10s
11+
timeout: 24h0m0s
12+
resources:
13+
requests:
14+
cpu: 100m
15+
memory: 200Mi
16+
17+
env:
18+
- name: SPOKE_CLUSTER
19+
default: "kni-qe-107"
20+
documentation: Spoke SNO cluster name with added worker node
21+
- name: HUB_CLUSTER
22+
default: "kni-qe-106"
23+
documentation: Hub cluster name
24+
- name: ECO_GOTESTS_FEATURES
25+
default: "gitopsztp deploymenttypes"
26+
documentation: Space-separated list of eco-gotests features to run
27+
- name: MIRROR_REGISTRY
28+
default: "disconnected.registry.local:5000"
29+
documentation: Disconnected mirror registry address for eco-gotests images
30+
31+
credentials:
32+
- namespace: test-credentials
33+
name: telcov10n-ansible-group-all
34+
mount_path: /var/group_variables/common/all
35+
- namespace: test-credentials
36+
name: telcov10n-ansible-group-bastions
37+
mount_path: /var/group_variables/common/bastions
38+
- namespace: test-credentials
39+
name: telcov10n-ansible-group-hypervisors
40+
mount_path: /var/group_variables/common/hypervisors
41+
- namespace: test-credentials
42+
name: telcov10n-ansible-group-kni-qe-106-nodes
43+
mount_path: /var/group_variables/kni-qe-106/nodes
44+
- namespace: test-credentials
45+
name: telcov10n-ansible-group-kni-qe-106-masters
46+
mount_path: /var/group_variables/kni-qe-106/masters
47+
- namespace: test-credentials
48+
name: telcov10n-ansible-kni-qe-106-bastion
49+
mount_path: /var/host_variables/kni-qe-106/bastion
50+
- namespace: test-credentials
51+
name: telcov10n-ansible-kni-qe-106-master0
52+
mount_path: /var/host_variables/kni-qe-106/master0
53+
- namespace: test-credentials
54+
name: telcov10n-ansible-hypervisors-hv16
55+
mount_path: /var/host_variables/fthub-01/hypervisor

0 commit comments

Comments
 (0)