forked from AlmaLinux/cloud-images
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathaction.yml
More file actions
403 lines (370 loc) · 15.9 KB
/
Copy pathaction.yml
File metadata and controls
403 lines (370 loc) · 15.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
name: "OpenNebula image test steps"
description: "Boots an OpenNebula .qcow2 under QEMU/KVM with a one-context CONTEXT ISO and runs in-VM smoke tests over SSH"
inputs:
image_url:
description: "Public URL to the OpenNebula .qcow2 image (provide this OR image_file)"
required: false
default: ''
image_file:
description: "Path to a locally-built OpenNebula .qcow2 (provide this OR image_url; takes precedence)"
required: false
default: ''
image_filename:
description: "Image filename (last path segment of image_url)"
required: true
alma_arch:
description: "Canonical runner arch: x86_64 or aarch64"
required: true
alma_arch_full:
description: "Raw arch token from the filename: x86_64, x86_64_v2, or aarch64 (used for the rpm %{ARCH} grep and for reporting)"
required: true
release_string:
description: "Expected /etc/almalinux-release substring (e.g. 'AlmaLinux release 10.1')"
required: true
notify_mattermost:
description: "Send notification to Mattermost (true/false)"
required: true
MATTERMOST_WEBHOOK_URL:
description: "Mattermost incoming webhook URL"
required: false
default: ''
MATTERMOST_CHANNEL:
description: "Mattermost channel for notifications"
required: false
default: ''
runs:
using: composite
steps:
- name: Install hypervisor packages
shell: bash
run: |
# Install hypervisor packages
sudo apt-get update
case "${{ inputs.alma_arch }}" in
x86_64)
sudo apt-get install -y --no-install-recommends \
qemu-system-x86 qemu-utils genisoimage
;;
aarch64)
sudo apt-get install -y --no-install-recommends \
qemu-system-arm qemu-efi-aarch64 qemu-utils genisoimage
;;
*)
echo "[Error] Unsupported alma_arch: '${{ inputs.alma_arch }}'"
exit 1
;;
esac
- name: Verify nested KVM is available
shell: bash
run: |
# Verify nested KVM is available
if [ ! -e /dev/kvm ]; then
echo "[Error] /dev/kvm not present on this runner; nested KVM is required"
exit 1
fi
ls -l /dev/kvm
# The default GH-hosted runner user is in the kvm group already on
# ubuntu-24.04; on RunsOn metal it varies. Loosen perms if /dev/kvm
# is not writable for the current user.
if [ ! -w /dev/kvm ]; then
sudo chmod 666 /dev/kvm
fi
- name: Generate ephemeral SSH keypair
shell: bash
run: |
# Generate ephemeral SSH keypair
mkdir -p ~/.ssh
chmod 700 ~/.ssh
ssh-keygen -t ed25519 -N '' -C "opennebula-test-${GITHUB_RUN_ID}" -f ~/.ssh/opennebula_test
- name: Obtain base image
shell: bash
env:
IMAGE_URL: ${{ inputs.image_url }}
IMAGE_FILE: ${{ inputs.image_file }}
run: |
# Obtain base image: prefer a locally-built file, else download the URL
if [ -n "${IMAGE_FILE}" ]; then
# Packer runs under sudo, so the build output is root-owned; copy it
# out and take ownership before QEMU opens it.
sudo cp "${IMAGE_FILE}" base.qcow2
sudo chown "$(id -u):$(id -g)" base.qcow2
elif [ -n "${IMAGE_URL}" ]; then
curl -fL --retry 5 --retry-delay 5 -o base.qcow2 "${IMAGE_URL}"
else
echo "[Error] one of image_file or image_url must be provided"
exit 1
fi
qemu-img info base.qcow2
- name: Create writable qcow2 overlay (100 GiB)
shell: bash
run: |
# Create writable qcow2 overlay (100 GiB)
# 100 GiB virtual size so one-context's grow-rootfs hook yields a
# root FS comfortably above the 95 GiB assertion below; the overlay
# stays sparse, so this costs no real disk on the runner.
qemu-img create -f qcow2 -F qcow2 -b base.qcow2 disk.qcow2 100G
- name: Build OpenNebula CONTEXT ISO
shell: bash
run: |
# Build OpenNebula CONTEXT ISO
# one-context's get_context_interfaces() only enumerates NICs that
# have ETHx_MAC set; without it, ETH0_METHOD is ignored and the
# NIC is left unconfigured. Pin the QEMU virtio NIC's MAC and pass
# the same value as ETH0_MAC so the two sides agree.
MAC=52:54:00:12:34:56
PUB=$(cat ~/.ssh/opennebula_test.pub)
cat > context.sh <<EOF
SET_HOSTNAME="opennebula-test"
USERNAME="almalinux"
SSH_PUBLIC_KEY="${PUB}"
NETWORK="YES"
START_SSHD="YES"
ETH0_MAC="${MAC}"
ETH0_METHOD="dhcp"
ETH0_IP6_METHOD="skip"
ETH0_DNS="10.0.2.3"
EOF
# ISO9660 with VOLUME LABEL=CONTEXT — that's what one-context looks
# for to mount and source context.sh.
genisoimage -quiet -o context.iso -V CONTEXT -input-charset utf-8 -J -R context.sh
echo "MAC=${MAC}" >> "$GITHUB_ENV"
- name: Boot QEMU/KVM guest (daemonized, hostfwd 2222 -> 22)
shell: bash
env:
ALMA_ARCH: ${{ inputs.alma_arch }}
run: |
# Boot QEMU/KVM guest (daemonized, hostfwd 2222 -> 22)
case "${ALMA_ARCH}" in
x86_64)
QEMU=qemu-system-x86_64
MACHINE_ARGS=(-machine q35,accel=kvm -cpu host)
FW_ARGS=()
;;
aarch64)
QEMU=qemu-system-aarch64
MACHINE_ARGS=(-machine virt,accel=kvm -cpu host)
FW_ARGS=(-bios /usr/share/AAVMF/AAVMF_CODE.fd)
;;
esac
"${QEMU}" \
-name opennebula-test \
"${MACHINE_ARGS[@]}" \
"${FW_ARGS[@]}" \
-smp 2 -m 2048 \
-drive file=disk.qcow2,if=virtio,format=qcow2,cache=writeback \
-drive file=context.iso,if=virtio,format=raw,readonly=on \
-netdev user,id=net0,hostfwd=tcp::2222-:22 \
-device "virtio-net-pci,netdev=net0,mac=${MAC}" \
-display none \
-serial file:console.log \
-pidfile qemu.pid \
-daemonize
sleep 2
echo "QEMU PID: $(cat qemu.pid)"
- name: Wait for SSH on 127.0.0.1:2222
shell: bash
run: |
# Wait for SSH on 127.0.0.1:2222
# QEMU's SLIRP hostfwd accepts the host-side TCP handshake before
# sshd inside the guest is actually ready, so a plain `nc -z` gate
# fires too early. Loop ssh-keyscan instead - it only succeeds
# once the guest's sshd has produced a host-key banner, and
# populates known_hosts in the same step.
for i in $(seq 1 60); do
if ssh-keyscan -p 2222 -T 5 127.0.0.1 > ~/.ssh/known_hosts.tmp 2>/dev/null \
&& [ -s ~/.ssh/known_hosts.tmp ]; then
mv ~/.ssh/known_hosts.tmp ~/.ssh/known_hosts
echo "[Info] SSH ready after ${i} attempt(s)"
break
fi
if [ "${i}" -eq 60 ]; then
echo "[Error] SSH did not become reachable within 10 minutes"
exit 1
fi
sleep 10
done
- name: Run image tests
id: run_tests
shell: bash
env:
ALMA_ARCH: ${{ inputs.alma_arch }}
ALMA_ARCH_FULL: ${{ inputs.alma_arch_full }}
RELEASE_STRING: ${{ inputs.release_string }}
IMAGE_FILENAME: ${{ inputs.image_filename }}
run: |
# Run image tests
SSH=(ssh -i ~/.ssh/opennebula_test -p 2222 \
-o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 \
"almalinux@127.0.0.1")
echo "[Debug] AlmaLinux release:"
ALMA_RELEASE=$("${SSH[@]}" "grep '${RELEASE_STRING}' /etc/almalinux-release")
echo "${ALMA_RELEASE}"
# Owner of /etc/almalinux-release varies by distro track:
# AlmaLinux -> almalinux-release
# AlmaLinux Kitten -> almalinux-release-kitten
# Discover it from the file rather than hardcoding the name.
echo "[Debug] AlmaLinux release package:"
RELEASE_PACKAGE=$("${SSH[@]}" "rpm -qf /etc/almalinux-release")
echo "${RELEASE_PACKAGE}"
echo "[Debug] System architecture:"
# almalinux-release(-kitten) is built per-microarch on v2 images,
# so its %{ARCH} is x86_64_v2 (not x86_64). Match against the raw
# filename token, which carries the v2 suffix when present.
SYSTEM_ARCH=$("${SSH[@]}" "rpm -q --qf='%{ARCH}\n' ${RELEASE_PACKAGE} | grep '${ALMA_ARCH_FULL}'")
echo "${SYSTEM_ARCH}"
echo "[Debug] Disk and filesystems:"
"${SSH[@]}" "sudo lsblk"
# Root-FS size threshold is 95 GiB on a 100 GiB overlay: the cloud
# image's UEFI partition layout (1M BIOS-boot + 200M /boot/efi +
# 1G /boot) eats ~1.2 GiB before the root partition, and xfs
# metadata trims another ~1.5 GiB off the usable FS size, so the
# observed ceiling on a fully-grown root is ~97 GiB. Anything
# under ~10 GiB means one-context's grow-rootfs hook didn't run.
"${SSH[@]}" 'ROOT_SIZE_BYTES=$(df -B1 --output=size / | tail -n 1 | tr -d " "); MIN_SIZE_BYTES=$((95*1024*1024*1024)); [ "${ROOT_SIZE_BYTES}" -gt "${MIN_SIZE_BYTES}" ] || { echo "[Error] Root filesystem resize check failed: ${ROOT_SIZE_BYTES} bytes (expected > ${MIN_SIZE_BYTES} bytes)"; exit 1; }'
# OpenNebula contextualization assertions — these are what makes
# this an OpenNebula test (vs. a plain qcow2 boot test).
echo "[Debug] one-context package:"
ONE_CONTEXT_VERSION=$("${SSH[@]}" "rpm -q --qf='%{VERSION}-%{RELEASE}' one-context")
echo "${ONE_CONTEXT_VERSION}"
# OpenNebula build-time payload (per ansible/roles/opennebula_guest):
# - almalinux-release-opennebula-addons (stable AL) or
# almalinux-kitten-release-opennebula-addons (Kitten) enables
# the addons repo that ships one-context
# - one-context is the contextualization package itself
# - cloud-utils-growpart + parted back the grow-rootfs hook
# - qemu-guest-agent, nfs-utils, rsync, jq, tcpdump, tuned are
# installed by the "additional packages" task
# `rpm -q` prints one line per package (version-or-"not installed")
# and exits non-zero if any are missing.
if [[ "${RELEASE_STRING}" == *Kitten* ]]; then
RELEASE_ADDONS_PKG=almalinux-kitten-release-opennebula-addons
else
RELEASE_ADDONS_PKG=almalinux-release-opennebula-addons
fi
echo "[Debug] OpenNebula payload packages installed:"
"${SSH[@]}" "rpm -q \
${RELEASE_ADDONS_PKG} \
one-context \
cloud-utils-growpart \
parted \
qemu-guest-agent \
nfs-utils \
rsync \
jq \
tcpdump \
tuned"
echo "[Debug] one-context services:"
"${SSH[@]}" "systemctl is-active one-context-local.service one-context-online.service one-context.service"
echo "[Debug] CONTEXT volume detection:"
"${SSH[@]}" "lsblk -o NAME,LABEL,FSTYPE | grep -E ' CONTEXT +iso9660'"
echo "[Debug] SET_HOSTNAME applied:"
GUEST_HOSTNAME=$("${SSH[@]}" "hostname")
if [ "${GUEST_HOSTNAME}" != "opennebula-test" ]; then
echo "[Error] Hostname is '${GUEST_HOSTNAME}', expected 'opennebula-test' (SET_HOSTNAME not applied)"
exit 1
fi
echo "[Debug] eth0 IPv4 + default route (ETH0_METHOD=dhcp applied):"
"${SSH[@]}" "ip -4 -o addr show eth0 | grep -q ' inet '"
"${SSH[@]}" "ip route | grep -q '^default via '"
echo "[Debug] Check for updates:"
# dnf check-update returns 100 when updates are available - treat as success
rc=0
"${SSH[@]}" "sudo dnf check-update" || rc=$?
if [ "${rc}" -ne 0 ] && [ "${rc}" -ne 100 ]; then
echo "[Error] dnf check-update failed with exit code ${rc}"
exit "${rc}"
fi
PKG_FILE="${IMAGE_FILENAME}.txt"
"${SSH[@]}" "rpm -qa --queryformat '%{NAME}\n' | sort > /tmp/${PKG_FILE}"
scp -i ~/.ssh/opennebula_test -P 2222 -o StrictHostKeyChecking=accept-new \
"almalinux@127.0.0.1:/tmp/${PKG_FILE}" "./${PKG_FILE}"
{
echo "PKG_FILE=${PKG_FILE}"
echo "ALMA_RELEASE=${ALMA_RELEASE}"
echo "SYSTEM_ARCH=${SYSTEM_ARCH}"
echo "ONE_CONTEXT_VERSION=${ONE_CONTEXT_VERSION}"
} >> "$GITHUB_ENV"
- name: Upload packages list artifact
if: env.PKG_FILE != ''
uses: actions/upload-artifact@v7
with:
name: ${{ env.PKG_FILE }}
path: ./${{ env.PKG_FILE }}
- name: Show guest console log on failure
if: failure()
shell: bash
run: |
# Show guest console log on failure
echo "===== guest console.log ====="
cat console.log || true
- name: Job summary
if: always()
shell: bash
env:
IMAGE_FILENAME: ${{ inputs.image_filename }}
IMAGE_URL: ${{ inputs.image_url }}
ALMA_ARCH_FULL: ${{ inputs.alma_arch_full }}
# job.status doesn't reflect failures of steps inside the same
# composite action (the parent job's status flips only after this
# composite returns) - so read the test step's own outcome.
# When a step BEFORE run_tests fails (install, KVM probe, ...),
# run_tests is skipped and its outcome is "skipped", which is
# correctly treated as "not success" below.
TESTS_OUTCOME: ${{ steps.run_tests.outcome }}
run: |
# Job summary
{
echo "## OpenNebula Image Test"
echo ""
if [ -n "${IMAGE_URL:-}" ]; then
echo "- **Image**: [${IMAGE_FILENAME}](${IMAGE_URL})"
else
echo "- **Image**: \`${IMAGE_FILENAME}\`"
fi
echo "- **Arch (filename)**: \`${ALMA_ARCH_FULL}\`"
if [ -n "${ALMA_RELEASE:-}" ]; then
echo "- **AlmaLinux release**: \`${ALMA_RELEASE}\`"
fi
if [ -n "${SYSTEM_ARCH:-}" ]; then
echo "- **System architecture**: \`${SYSTEM_ARCH}\`"
fi
if [ -n "${ONE_CONTEXT_VERSION:-}" ]; then
echo "- **one-context**: \`${ONE_CONTEXT_VERSION}\`"
fi
if [ "${TESTS_OUTCOME}" = "success" ]; then
echo "- **Test**: passed ✅"
else
echo "- **Test**: failed ❌"
fi
} >> "$GITHUB_STEP_SUMMARY"
- name: Shut down guest
if: always()
shell: bash
run: |
# Shut down guest
if [ -f qemu.pid ]; then
PID=$(cat qemu.pid)
if kill -0 "${PID}" 2>/dev/null; then
kill "${PID}" 2>/dev/null || true
for _ in $(seq 1 15); do
kill -0 "${PID}" 2>/dev/null || break
sleep 1
done
kill -0 "${PID}" 2>/dev/null && kill -9 "${PID}" || true
fi
fi
- name: Send notification to Mattermost
uses: mattermost/action-mattermost-notify@master
if: always() && inputs.notify_mattermost == 'true' && inputs.MATTERMOST_WEBHOOK_URL != ''
with:
MATTERMOST_WEBHOOK_URL: ${{ inputs.MATTERMOST_WEBHOOK_URL }}
MATTERMOST_CHANNEL: ${{ inputs.MATTERMOST_CHANNEL }}
MATTERMOST_USERNAME: ${{ github.triggering_actor }}
TEXT: |
:almalinux: **${{ inputs.image_filename }}**, OpenNebula image test, by the GitHub [Action](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
${{ inputs.image_url && format('**Image**: [{0}]({1})', inputs.image_filename, inputs.image_url) || format('**Image**: `{0}`', inputs.image_filename) }}
**Arch (filename)**: `${{ inputs.alma_arch_full }}`
${{ env.ALMA_RELEASE && format('**AlmaLinux release**: `{0}`', env.ALMA_RELEASE) || '' }}
${{ env.SYSTEM_ARCH && format('**System architecture**: `{0}`', env.SYSTEM_ARCH) || '' }}
${{ env.ONE_CONTEXT_VERSION && format('**one-context**: `{0}`', env.ONE_CONTEXT_VERSION) || '' }}
**Test**: ${{ steps.run_tests.outcome == 'success' && 'passed ✅' || 'failed ❌' }}