From 1ba2b9327cb9912cc84d0ebe3bd445c28cef8fc2 Mon Sep 17 00:00:00 2001 From: Steven Miller Date: Thu, 26 Feb 2026 10:08:56 -0500 Subject: [PATCH 1/4] Prevent default hotplug memory --- cmd/api/api/instances.go | 2 +- cmd/api/api/instances_test.go | 62 +++++++++++++++++++++++++++++++++++ lib/instances/create.go | 3 -- lib/instances/types.go | 2 +- openapi.yaml | 3 +- 5 files changed, 65 insertions(+), 7 deletions(-) diff --git a/cmd/api/api/instances.go b/cmd/api/api/instances.go index 10da1074..50deb885 100644 --- a/cmd/api/api/instances.go +++ b/cmd/api/api/instances.go @@ -72,7 +72,7 @@ func (s *ApiService) CreateInstance(ctx context.Context, request oapi.CreateInst size = int64(sizeBytes) } - // Parse hotplug_size (default: 3GB) + // Parse hotplug_size (optional; omitted/empty means no hotplug memory) hotplugSize := int64(0) if request.Body.HotplugSize != nil && *request.Body.HotplugSize != "" { var hotplugBytes datasize.ByteSize diff --git a/cmd/api/api/instances_test.go b/cmd/api/api/instances_test.go index 81af2bd5..8c74cf89 100644 --- a/cmd/api/api/instances_test.go +++ b/cmd/api/api/instances_test.go @@ -1,10 +1,14 @@ package api import ( + "context" "os" "testing" "time" + "github.com/c2h5oh/datasize" + "github.com/kernel/hypeman/lib/hypervisor" + "github.com/kernel/hypeman/lib/instances" "github.com/kernel/hypeman/lib/oapi" "github.com/kernel/hypeman/lib/paths" "github.com/kernel/hypeman/lib/system" @@ -128,6 +132,64 @@ func TestCreateInstance_InvalidSizeFormat(t *testing.T) { assert.Contains(t, badReq.Message, "invalid size format") } +type captureCreateManager struct { + instances.Manager + lastReq *instances.CreateInstanceRequest +} + +func (m *captureCreateManager) CreateInstance(ctx context.Context, req instances.CreateInstanceRequest) (*instances.Instance, error) { + reqCopy := req + m.lastReq = &reqCopy + + now := time.Now() + return &instances.Instance{ + StoredMetadata: instances.StoredMetadata{ + Id: "inst-hotplug-default", + Name: req.Name, + Image: req.Image, + Size: req.Size, + HotplugSize: req.HotplugSize, + OverlaySize: req.OverlaySize, + Vcpus: req.Vcpus, + CreatedAt: now, + HypervisorType: hypervisor.TypeCloudHypervisor, + }, + State: instances.StateRunning, + }, nil +} + +func TestCreateInstance_OmittedHotplugSizeDefaultsToZero(t *testing.T) { + svc := newTestService(t) + + origMgr := svc.InstanceManager + mockMgr := &captureCreateManager{Manager: origMgr} + svc.InstanceManager = mockMgr + + size := "1GB" + overlaySize := "10GB" + resp, err := svc.CreateInstance(ctx(), oapi.CreateInstanceRequestObject{ + Body: &oapi.CreateInstanceRequest{ + Name: "test-no-hotplug", + Image: "docker.io/library/alpine:latest", + Size: &size, + OverlaySize: &overlaySize, + }, + }) + require.NoError(t, err) + + created, ok := resp.(oapi.CreateInstance201JSONResponse) + require.True(t, ok, "expected 201 response") + assert.NotNil(t, mockMgr.lastReq, "CreateInstance should be called") + assert.Equal(t, int64(0), mockMgr.lastReq.HotplugSize, "omitted hotplug_size should not allocate default memory") + + instance := oapi.Instance(created) + require.NotNil(t, instance.HotplugSize) + + var hotplugBytes datasize.ByteSize + require.NoError(t, hotplugBytes.UnmarshalText([]byte(*instance.HotplugSize))) + assert.Equal(t, int64(0), int64(hotplugBytes), "response should report zero hotplug_size when omitted") +} + func TestInstanceLifecycle_StopStart(t *testing.T) { // Require KVM access for VM creation if _, err := os.Stat("/dev/kvm"); os.IsNotExist(err) { diff --git a/lib/instances/create.go b/lib/instances/create.go index 003a984c..46e9ac30 100644 --- a/lib/instances/create.go +++ b/lib/instances/create.go @@ -129,9 +129,6 @@ func (m *manager) createInstance( size = 1 * 1024 * 1024 * 1024 // 1GB default } hotplugSize := req.HotplugSize - if hotplugSize == 0 { - hotplugSize = 3 * 1024 * 1024 * 1024 // 3GB default - } overlaySize := req.OverlaySize if overlaySize == 0 { overlaySize = 10 * 1024 * 1024 * 1024 // 10GB default diff --git a/lib/instances/types.go b/lib/instances/types.go index 1b270e6a..9afbccc2 100644 --- a/lib/instances/types.go +++ b/lib/instances/types.go @@ -150,7 +150,7 @@ type CreateInstanceRequest struct { Name string // Required Image string // Required: OCI reference Size int64 // Base memory in bytes (default: 1GB) - HotplugSize int64 // Hotplug memory in bytes (default: 3GB) + HotplugSize int64 // Hotplug memory in bytes (default: 0, set explicitly to enable) OverlaySize int64 // Overlay disk size in bytes (default: 10GB) Vcpus int // Default 2 NetworkBandwidthDownload int64 // Download rate limit bytes/sec (0 = auto, proportional to CPU) diff --git a/openapi.yaml b/openapi.yaml index af4abb38..2e6977fd 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -119,8 +119,7 @@ components: example: "2GB" hotplug_size: type: string - description: Additional memory for hotplug (human-readable format like "3GB", "1G") - default: "3GB" + description: Additional memory for hotplug (human-readable format like "3GB", "1G"). Omit to disable hotplug memory. example: "2GB" overlay_size: type: string From 854cee0838d9c35cc49c51e62865ae26fe35a68b Mon Sep 17 00:00:00 2001 From: Steven Miller Date: Thu, 26 Feb 2026 10:11:35 -0500 Subject: [PATCH 2/4] ci: pin GitHub Actions Go version to 1.25.4 --- .github/workflows/release.yml | 2 +- .github/workflows/test.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1b22d451..22b499b0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,7 +23,7 @@ jobs: # Not necessary to upload cache on self-hosted runner(s) # ~/go/pkg/mod and ~/.cache/go-build stay on disk between runs automatically. cache: false - go-version: '1.25' + go-version: '1.25.4' - name: Run GoReleaser uses: goreleaser/goreleaser-action@v6 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9ff9862d..17352818 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ jobs: # Not necessary to upload cache on self-hosted runner(s) # ~/go/pkg/mod and ~/.cache/go-build stay on disk between runs automatically. cache: false - go-version: '1.25' + go-version: '1.25.4' - name: Install dependencies run: | @@ -67,7 +67,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-go@v6 with: - go-version: '1.25' + go-version: '1.25.4' cache: false - name: Login to Docker Hub uses: docker/login-action@v3 @@ -107,7 +107,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-go@v6 with: - go-version: '1.25' + go-version: '1.25.4' cache: false - name: Install dependencies run: brew list caddy &>/dev/null || brew install caddy From b47e5139aea5f8fe4009341e538c7e037835f25b Mon Sep 17 00:00:00 2001 From: Steven Miller Date: Thu, 26 Feb 2026 10:16:37 -0500 Subject: [PATCH 3/4] ci: install ext4 and iptables deps for linux tests --- .github/workflows/test.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 17352818..2c04026b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,9 +20,11 @@ jobs: - name: Install dependencies run: | set -xe - if ! command -v mkfs.erofs &> /dev/null; then + if ! command -v mkfs.erofs &> /dev/null || \ + ! command -v mkfs.ext4 &> /dev/null || \ + ! command -v iptables &> /dev/null; then sudo apt-get update - sudo apt-get install -y erofs-utils + sudo apt-get install -y erofs-utils e2fsprogs iptables fi go mod download From 9ce19961d06e9807df5aa59fe7cc273fb0ac49c2 Mon Sep 17 00:00:00 2001 From: Steven Miller Date: Thu, 26 Feb 2026 10:20:41 -0500 Subject: [PATCH 4/4] ci: include sbin paths when running linux tests --- Makefile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index f3e1cf91..e2656e14 100644 --- a/Makefile +++ b/Makefile @@ -228,12 +228,13 @@ endif # Linux tests (as root for network capabilities) test-linux: ensure-ch-binaries ensure-caddy-binaries build-embedded @VERBOSE_FLAG=""; \ + TEST_PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$$PATH"; \ if [ -n "$(VERBOSE)" ]; then VERBOSE_FLAG="-v"; fi; \ if [ -n "$(TEST)" ]; then \ echo "Running specific test: $(TEST)"; \ - sudo env "PATH=$$PATH" "DOCKER_CONFIG=$${DOCKER_CONFIG:-$$HOME/.docker}" go test -tags containers_image_openpgp -run=$(TEST) $$VERBOSE_FLAG -timeout=300s ./...; \ + sudo env "PATH=$$TEST_PATH" "DOCKER_CONFIG=$${DOCKER_CONFIG:-$$HOME/.docker}" go test -tags containers_image_openpgp -run=$(TEST) $$VERBOSE_FLAG -timeout=300s ./...; \ else \ - sudo env "PATH=$$PATH" "DOCKER_CONFIG=$${DOCKER_CONFIG:-$$HOME/.docker}" go test -tags containers_image_openpgp $$VERBOSE_FLAG -timeout=300s ./...; \ + sudo env "PATH=$$TEST_PATH" "DOCKER_CONFIG=$${DOCKER_CONFIG:-$$HOME/.docker}" go test -tags containers_image_openpgp $$VERBOSE_FLAG -timeout=300s ./...; \ fi # macOS tests (no sudo needed, adds e2fsprogs to PATH)