Skip to content

Commit 285afba

Browse files
authored
Merge branch 'main' into main
2 parents 1ea41b7 + 4460ae2 commit 285afba

14 files changed

Lines changed: 827 additions & 7 deletions

File tree

.github/workflows/test-examples.yml

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ jobs:
6161
- python-agent-driver
6262
- powershell
6363
- networking-py
64+
- go-http
65+
- dotnet-http
6466
steps:
6567
- uses: actions/checkout@v4
6668

@@ -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

@@ -345,9 +355,47 @@ 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"
362+
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" ${{ 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
351399
fi
352400
case "${{ matrix.driver }}" in
353401
multifn-test)
@@ -414,6 +462,8 @@ jobs:
414462
- python-agent-driver
415463
- powershell
416464
- networking-py
465+
- go-http
466+
- dotnet-http
417467
steps:
418468
- uses: actions/checkout@v4
419469

@@ -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

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();

examples/dotnet-http/kraft.yaml

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
specification: '0.6'
2+
name: dotnet-http-hyperlight
3+
4+
unikraft:
5+
source: https://github.com/unikraft/unikraft.git
6+
version: plat-hyperlight
7+
kconfig:
8+
# Platform
9+
CONFIG_PLAT_HYPERLIGHT: 'y'
10+
CONFIG_PAGING: 'n'
11+
CONFIG_LIBUKVMEM: 'n'
12+
CONFIG_LIBUKINTCTLR_HYPERLIGHT: 'y'
13+
CONFIG_HYPERLIGHT_MAX_GUEST_LOG_LEVEL: 0
14+
15+
# Suppress kernel logging for performance
16+
CONFIG_LIBUKPRINT_KLVL_CRIT: 'y'
17+
CONFIG_LIBUKPRINT_PRINT_TIME: 'n'
18+
CONFIG_LIBUKPRINT_PRINT_SRCNAME: 'n'
19+
CONFIG_LIBUKBOOT_BANNER_NONE: 'y'
20+
21+
# Size optimization
22+
CONFIG_OPTIMIZE_SIZE: 'y'
23+
CONFIG_OPTIMIZE_PIE: 'y'
24+
25+
# VFS and initrd support
26+
CONFIG_LIBVFSCORE: 'y'
27+
CONFIG_LIBVFSCORE_AUTOMOUNT_CI: 'y'
28+
CONFIG_LIBVFSCORE_AUTOMOUNT_CI_CUSTOM: 'y'
29+
CONFIG_LIBVFSCORE_AUTOMOUNT_CI0_MP: '/'
30+
CONFIG_LIBVFSCORE_AUTOMOUNT_CI0_DRIVER: 'cpiovfs'
31+
CONFIG_LIBVFSCORE_AUTOMOUNT_CI0_UKOPTS_IFINITRD0: 'y'
32+
CONFIG_LIBCPIOVFS: 'y'
33+
CONFIG_LIBUKCPIO: 'y'
34+
35+
# ELF loader - .NET apphost binary
36+
CONFIG_APPELFLOADER: 'y'
37+
CONFIG_APPELFLOADER_VFSEXEC: 'y'
38+
CONFIG_APPELFLOADER_CUSTOMAPPNAME: 'n'
39+
CONFIG_APPELFLOADER_VFSEXEC_ENVPATH: 'n'
40+
CONFIG_APPELFLOADER_VFSEXEC_PATH: '/app/KestrelHyperlight'
41+
CONFIG_APPELFLOADER_VFSEXEC_EXECBIT_CHECK: 'n'
42+
CONFIG_LIBPOSIX_ENVIRON_ENVP0: '"PATH=/app:/usr/bin:/bin"'
43+
CONFIG_LIBPOSIX_ENVIRON_ENVP1: '"DOTNET_GCHeapHardLimit=0x8000000"'
44+
CONFIG_LIBPOSIX_ENVIRON_ENVP2: '"DOTNET_gcServer=0"'
45+
CONFIG_LIBPOSIX_ENVIRON_ENVP3: '"DOTNET_EnableWriteXorExecute=0"'
46+
CONFIG_LIBPOSIX_ENVIRON_ENVP4: '"COMPlus_EnableDiagnostics=0"'
47+
CONFIG_LIBPOSIX_ENVIRON_ENVP5: '"DOTNET_DefaultStackSize=0x10000"'
48+
CONFIG_LIBPOSIX_ENVIRON_ENVP6: '"hostBuilder__reloadConfigOnChange=false"'
49+
CONFIG_LIBELF: 'y'
50+
51+
# Random number support
52+
CONFIG_LIBUKRANDOM_CMDLINE_SEED: 'y'
53+
CONFIG_LIBUKRANDOM_GETRANDOM: 'y'
54+
55+
# Stack size (order 9 = 2^9 pages = 2MB, needed for CoreCLR's 1.5MB alloca)
56+
CONFIG_STACK_SIZE_PAGE_ORDER: 9
57+
58+
# Threading support (required for .NET runtime)
59+
CONFIG_LIBUKBOOT_MAINTHREAD: 'y'
60+
CONFIG_LIBPOSIX_PROCESS_ARCH_PRCTL: 'y'
61+
CONFIG_LIBPOSIX_PROCESS_MULTITHREADING: 'y'
62+
CONFIG_LIBPOSIX_PROCESS_SIGNAL: 'y'
63+
CONFIG_LIBPOSIX_FUTEX: 'y'
64+
CONFIG_LIBUKSCHED: 'y'
65+
CONFIG_LIBUKSCHEDCOOP: 'y'
66+
CONFIG_LIBUKMPI: 'y'
67+
CONFIG_LIBUKLOCK: 'y'
68+
CONFIG_LIBUKLOCK_SEMAPHORE: 'y'
69+
CONFIG_LIBUKLOCK_MUTEX: 'y'
70+
71+
# mmap needed by CoreCLR memory management
72+
CONFIG_LIBUKMMAP: 'y'
73+
74+
# Event/polling support
75+
CONFIG_LIBPOSIX_POLL: 'y'
76+
CONFIG_LIBPOSIX_EVENTFD: 'y'
77+
CONFIG_LIBPOSIX_FDIO: 'y'
78+
CONFIG_LIBPOSIX_FDTAB: 'y'
79+
CONFIG_LIBUKFILE: 'y'
80+
81+
# devfs + /dev/hcall (required for host-proxied networking)
82+
CONFIG_LIBDEVFS: 'y'
83+
CONFIG_LIBDEVFS_AUTOMOUNT: 'y'
84+
CONFIG_HYPERLIGHT_HCALL: 'y'
85+
86+
# Networking: host-proxied sockets
87+
CONFIG_LIBPOSIX_SOCKET: 'y'
88+
CONFIG_LIBHOSTSOCK: 'y'
89+
90+
91+
libraries:
92+
app-elfloader:
93+
source: https://github.com/unikraft/app-elfloader.git
94+
version: plat-hyperlight
95+
libelf:
96+
source: https://github.com/unikraft/lib-libelf.git
97+
version: staging
98+
99+
targets:
100+
- architecture: x86_64
101+
platform: hyperlight

examples/go-http/Dockerfile

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
FROM golang:1.21.3-bookworm AS build
2+
3+
WORKDIR /src
4+
5+
COPY ./main.go /src/main.go
6+
7+
RUN set -xe; \
8+
CGO_ENABLED=0 \
9+
GOARCH=amd64 \
10+
go build \
11+
-buildmode=pie \
12+
-ldflags="-linkmode=internal" \
13+
-o /server main.go \
14+
;
15+
16+
# Strip PT_INTERP from the static PIE binary: Go's internal linker adds
17+
# /lib64/ld-linux-x86-64.so.2 as interpreter for PIE on Linux, but the
18+
# binary is fully self-contained and does not need a dynamic linker.
19+
# Unikraft's elfloader fails when the interpreter is listed but absent.
20+
# Patch the program header: change PT_INTERP (type=3) -> PT_NULL (type=0).
21+
COPY patch_interp.py /patch_interp.py
22+
RUN python3 /patch_interp.py /server
23+
24+
FROM scratch AS rootfs
25+
26+
COPY --from=build /server /bin/server
27+
28+
# --- CPIO rootfs builder (used by: docker build --target cpio) ---
29+
FROM alpine:3.20 AS cpio
30+
RUN apk add --no-cache cpio findutils
31+
COPY --from=rootfs / /rootfs/
32+
RUN cd /rootfs && find . | cpio -o -H newc > /output.cpio 2>/dev/null

0 commit comments

Comments
 (0)