Skip to content

Commit e2bf19a

Browse files
committed
Resolve test-examples workflow conflict
Signed-off-by: akrm al-hakimi <alhakimiakrmj@gmail.com>
2 parents 1ea41b7 + 4460ae2 commit e2bf19a

14 files changed

Lines changed: 847 additions & 20 deletions

File tree

.github/workflows/test-examples.yml

Lines changed: 126 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ on:
77
push:
88
branches: [main]
99
paths:
10-
- 'examples/**'
11-
- 'runtimes/**'
12-
- 'host/**'
13-
- '.github/workflows/test-examples.yml'
10+
- "examples/**"
11+
- "runtimes/**"
12+
- "host/**"
13+
- ".github/workflows/test-examples.yml"
1414

1515
# Pushing a new commit to the same ref cancels any in-flight run on
1616
# the previous commit — don't waste CI on stale code.
@@ -61,12 +61,14 @@ jobs:
6161
- python-agent-driver
6262
- powershell
6363
- networking-py
64+
- go-http
65+
- dotnet-http
6466
steps:
6567
- uses: actions/checkout@v4
6668

6769
- uses: actions/setup-go@v5
6870
with:
69-
go-version: '1.25.1'
71+
go-version: "1.25.1"
7072
cache: false
7173

7274
- name: Install just
@@ -90,7 +92,7 @@ jobs:
9092
- name: Build local-python-base images (python-agent-driver only)
9193
if: matrix.example == 'python-agent-driver'
9294
env:
93-
DOCKER_BUILDKIT: '0'
95+
DOCKER_BUILDKIT: "0"
9496
run: |
9597
docker build --target base -t local-python-base-dev:latest \
9698
-f runtimes/python.Dockerfile runtimes/
@@ -100,7 +102,7 @@ jobs:
100102
- name: Build rootfs
101103
working-directory: examples/${{ matrix.example }}
102104
env:
103-
DOCKER_BUILDKIT: '0'
105+
DOCKER_BUILDKIT: "0"
104106
run: |
105107
just rootfs
106108
@@ -224,6 +226,14 @@ jobs:
224226
- example: networking-py
225227
args: "--port 8080 -- /echo_server_test.py"
226228
expect: "SUCCESS: bind\\+listen on port 8080 allowed"
229+
- example: go-http
230+
args: "--port 8080 -- /bin/server"
231+
expect: "Hello from Hyperlight-Unikraft!"
232+
http_port: "8080"
233+
- example: dotnet-http
234+
args: "--port 8080 -- /app/KestrelHyperlight"
235+
expect: "Hello from Kestrel on Hyperlight!"
236+
http_port: "8080"
227237
steps:
228238
- uses: actions/checkout@v4
229239

@@ -283,7 +293,7 @@ jobs:
283293
- name: Build local-python-base images (python-agent-driver only)
284294
if: steps.kvm_check.outputs.available == 'true' && matrix.example == 'python-agent-driver'
285295
env:
286-
DOCKER_BUILDKIT: '0'
296+
DOCKER_BUILDKIT: "0"
287297
run: |
288298
docker build --target base -t local-python-base-dev:latest \
289299
-f runtimes/python.Dockerfile runtimes/
@@ -294,7 +304,7 @@ jobs:
294304
if: steps.kvm_check.outputs.available == 'true'
295305
working-directory: examples/${{ matrix.example }}
296306
env:
297-
DOCKER_BUILDKIT: '0'
307+
DOCKER_BUILDKIT: "0"
298308
run: |
299309
just rootfs
300310
kraft-hyperlight --no-prompt build --plat hyperlight --arch x86_64 || true
@@ -345,10 +355,48 @@ jobs:
345355
mkdir -p "$mount_dir"
346356
mount_args="--mount $mount_dir:/host"
347357
fi
358+
348359
tool_args=""
349360
if [ "${{ matrix.needs_echo_tool }}" = "true" ]; then
350361
tool_args="--tool echo=../echo-wasm-host-fxn/target/wasm32-wasip1/release/echo-wasm-host-fxn.wasm"
351362
fi
363+
364+
# HTTP server examples: start in background, poll, curl, kill.
365+
http_port="${{ matrix.http_port }}"
366+
if [ -n "$http_port" ]; then
367+
mem_args=""
368+
if [ -n "$memory" ]; then
369+
mem_args="-m $memory"
370+
fi
371+
hyperlight-unikraft -q $mem_args "$kernel" --initrd "$cpio" $mount_args $tool_args ${{ matrix.args }} &
372+
server_pid=$!
373+
sleep 3
374+
ready=0
375+
for i in $(seq 1 30); do
376+
if curl -s --max-time 10 "http://127.0.0.1:${http_port}" >/dev/null 2>&1; then
377+
ready=1
378+
break
379+
fi
380+
sleep 1
381+
done
382+
if [ "$ready" -ne 1 ]; then
383+
echo "FAIL: server did not become ready within timeout"
384+
kill "$server_pid" 2>/dev/null || true
385+
exit 1
386+
fi
387+
response=$(curl -s --max-time 10 "http://127.0.0.1:${http_port}")
388+
kill "$server_pid" 2>/dev/null || true
389+
wait "$server_pid" 2>/dev/null || true
390+
echo "=== HTTP response ==="
391+
echo "$response"
392+
if echo "$response" | grep -qF "$expect"; then
393+
echo "PASS: matched /$expect/"
394+
else
395+
echo "FAIL: did not match /$expect/"
396+
exit 1
397+
fi
398+
exit 0
399+
fi
352400
case "${{ matrix.driver }}" in
353401
multifn-test)
354402
cmd=(timeout 60 /home/runner/work/hyperlight-unikraft/hyperlight-unikraft/host/target/release/multifn-test "$kernel" "$cpio")
@@ -414,12 +462,14 @@ jobs:
414462
- python-agent-driver
415463
- powershell
416464
- networking-py
465+
- go-http
466+
- dotnet-http
417467
steps:
418468
- uses: actions/checkout@v4
419469

420470
- uses: actions/setup-go@v5
421471
with:
422-
go-version: '1.25.1'
472+
go-version: "1.25.1"
423473
cache: false
424474

425475
- name: Install just
@@ -443,7 +493,7 @@ jobs:
443493
- name: Build local-python-base images (python-agent-driver only)
444494
if: matrix.example == 'python-agent-driver'
445495
env:
446-
DOCKER_BUILDKIT: '0'
496+
DOCKER_BUILDKIT: "0"
447497
run: |
448498
docker build --target base -t local-python-base-dev:latest \
449499
-f runtimes/python.Dockerfile runtimes/
@@ -453,7 +503,7 @@ jobs:
453503
- name: Build rootfs + kernel
454504
working-directory: examples/${{ matrix.example }}
455505
env:
456-
DOCKER_BUILDKIT: '0'
506+
DOCKER_BUILDKIT: "0"
457507
run: |
458508
just rootfs
459509
kraft-hyperlight --no-prompt build --plat hyperlight --arch x86_64 || true
@@ -566,6 +616,14 @@ jobs:
566616
- example: networking-py
567617
args: "--port 8080 -- /echo_server_test.py"
568618
expect: "SUCCESS: bind\\+listen on port 8080 allowed"
619+
- example: go-http
620+
args: "--port 8080 -- /bin/server"
621+
expect: "Hello from Hyperlight-Unikraft!"
622+
http_port: "8080"
623+
- example: dotnet-http
624+
args: "--port 8080 -- /app/KestrelHyperlight"
625+
expect: "Hello from Kestrel on Hyperlight!"
626+
http_port: "8080"
569627
steps:
570628
- uses: actions/checkout@v4
571629

@@ -647,6 +705,54 @@ jobs:
647705
$PSNativeCommandUseErrorActionPreference = $false
648706
$ErrorActionPreference = 'Continue'
649707
708+
# HTTP server examples: start in background, poll, curl, kill.
709+
$httpPort = '${{ matrix.http_port }}'
710+
if ($httpPort -ne '') {
711+
$argList = @()
712+
if ($runArgs -ne '') {
713+
$argList = $runArgs.Split(' ') | Where-Object { $_ -ne '' }
714+
}
715+
$memArgs = @()
716+
if ($memory -ne '') {
717+
$memArgs = @('-m', $memory)
718+
}
719+
$stderrLog = Join-Path $env:RUNNER_TEMP 'hl-stderr.log'
720+
$proc = Start-Process -FilePath 'hyperlight-unikraft' `
721+
-ArgumentList (@('-q') + $memArgs + @($kernel, '--initrd', $cpio) + $argList) `
722+
-PassThru -NoNewWindow -RedirectStandardError $stderrLog
723+
# Give the server time to boot the unikernel and start listening
724+
Start-Sleep -Seconds 5
725+
$ready = $false
726+
for ($i = 0; $i -lt 30; $i++) {
727+
try {
728+
$null = Invoke-WebRequest -Uri "http://127.0.0.1:${httpPort}" -UseBasicParsing -TimeoutSec 10 -ErrorAction Stop
729+
$ready = $true
730+
break
731+
} catch {
732+
Start-Sleep -Seconds 1
733+
}
734+
}
735+
if (-not $ready) {
736+
Write-Host "FAIL: server did not become ready within timeout"
737+
Write-Host "=== stderr (last 200 lines) ==="
738+
Get-Content $stderrLog -Tail 200 -ErrorAction SilentlyContinue
739+
Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue
740+
exit 1
741+
}
742+
$raw = (Invoke-WebRequest -Uri "http://127.0.0.1:${httpPort}" -UseBasicParsing -TimeoutSec 10).Content
743+
if ($raw -is [byte[]]) { $resp = [System.Text.Encoding]::UTF8.GetString($raw) } else { $resp = $raw }
744+
Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue
745+
Write-Host "=== HTTP response ==="
746+
Write-Host $resp
747+
if ($resp -match [regex]::Escape($expect)) {
748+
Write-Host "PASS: matched /$expect/"
749+
exit 0
750+
} else {
751+
Write-Host "FAIL: did not match /$expect/"
752+
exit 1
753+
}
754+
}
755+
650756
switch ($driver) {
651757
'multifn-test' {
652758
$out = & multifn-test $kernel $cpio 2>&1
@@ -842,7 +948,14 @@ jobs:
842948
843949
test-examples-passed:
844950
if: always()
845-
needs: [build-example, runtime-test, package-images-for-windows, runtime-test-windows, pyhl-snapshot-test]
951+
needs:
952+
[
953+
build-example,
954+
runtime-test,
955+
package-images-for-windows,
956+
runtime-test-windows,
957+
pyhl-snapshot-test,
958+
]
846959
runs-on: ubuntu-latest
847960
permissions: {}
848961
steps:

examples/dotnet-http/Dockerfile

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS build
2+
WORKDIR /app
3+
COPY Program.cs KestrelHyperlight.csproj ./
4+
RUN dotnet publish -c Release -r linux-musl-x64 --self-contained \
5+
-p:PublishTrimmed=true \
6+
-p:InvariantGlobalization=true \
7+
-o /out
8+
9+
FROM scratch AS rootfs
10+
11+
# .NET app + runtime (self-contained)
12+
COPY --from=build /out/ /app/
13+
14+
# musl dynamic linker
15+
COPY --from=build /lib/ld-musl-x86_64.so.1 /lib/ld-musl-x86_64.so.1
16+
17+
# C++ runtime libraries (needed by .NET runtime)
18+
COPY --from=build /usr/lib/libstdc++.so.6 /usr/lib/libstdc++.so.6
19+
COPY --from=build /usr/lib/libgcc_s.so.1 /usr/lib/libgcc_s.so.1
20+
21+
# --- CPIO rootfs builder (used by: docker build --target cpio) ---
22+
FROM alpine:3.20 AS cpio
23+
RUN apk add --no-cache cpio findutils
24+
COPY --from=rootfs / /rootfs/
25+
RUN cd /rootfs && find . | cpio -o -H newc > /output.cpio 2>/dev/null

examples/dotnet-http/Justfile

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# dotnet-http (Kestrel) on Hyperlight
2+
#
3+
# just run - Run the HTTP server (--port implies --net)
4+
# just rootfs - Build rootfs via Docker (cross-platform)
5+
# just build - Build/pull kernel
6+
# just clean - Remove build artifacts
7+
8+
set windows-shell := ["powershell.exe", "-NoLogo", "-Command"]
9+
export DOCKER_BUILDKIT := "0"
10+
11+
kernel := ".unikraft/build/dotnet-http-hyperlight_hyperlight-x86_64"
12+
initrd := "initrd.cpio"
13+
memory := "128Mi"
14+
image := "dotnet-http-hyperlight"
15+
16+
# Run the HTTP server (curl http://localhost:8080 to test)
17+
run:
18+
hyperlight-unikraft {{kernel}} --initrd {{initrd}} --memory {{memory}} --port 8080 -- /app/KestrelHyperlight
19+
20+
# Build rootfs via Docker (cross-platform)
21+
rootfs:
22+
docker build --platform linux/amd64 --target cpio -t {{image}}-cpio .
23+
- docker rm -f {{image}}-tmp
24+
docker create --name {{image}}-tmp {{image}}-cpio /bin/true
25+
docker cp {{image}}-tmp:/output.cpio ./{{initrd}}
26+
docker rm -f {{image}}-tmp
27+
28+
# Build kernel via kraft (Linux)
29+
[unix]
30+
build:
31+
-kraft-hyperlight build --plat hyperlight --arch x86_64
32+
33+
# Pull pre-built kernel from GHCR (Windows — no kernel published yet)
34+
[windows]
35+
build:
36+
Write-Host "No pre-built kernel for dotnet-http yet. Build on Linux first."
37+
38+
# Clean + rebuild everything
39+
rebuild: clean build rootfs
40+
41+
# Clean build artifacts
42+
[unix]
43+
clean:
44+
rm -rf .unikraft {{initrd}}
45+
46+
[windows]
47+
clean:
48+
if (Test-Path .unikraft) { Remove-Item -Recurse -Force .unikraft }
49+
if (Test-Path {{initrd}}) { Remove-Item -Force {{initrd}} }
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
<PropertyGroup>
3+
<TargetFramework>net9.0</TargetFramework>
4+
<ImplicitUsings>enable</ImplicitUsings>
5+
<InvariantGlobalization>true</InvariantGlobalization>
6+
</PropertyGroup>
7+
</Project>

examples/dotnet-http/Program.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
var builder = WebApplication.CreateEmptyBuilder(new WebApplicationOptions());
2+
builder.WebHost.UseKestrelCore();
3+
4+
var app = builder.Build();
5+
app.Urls.Add("http://0.0.0.0:8080");
6+
7+
app.Run(async context =>
8+
{
9+
await context.Response.WriteAsync("Hello from Kestrel on Hyperlight!");
10+
});
11+
12+
Console.WriteLine("Listening on :8080");
13+
await app.RunAsync();

0 commit comments

Comments
 (0)