Skip to content
4 changes: 2 additions & 2 deletions server/cmd/api/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ type ApiService struct {

// DevTools upstream manager (Chromium supervisord log tailer)
upstreamMgr *devtoolsproxy.UpstreamManager
stz scaletozero.Controller
stz scaletozero.PinnedController

// inputMu serializes input-related operations (mouse, keyboard, screenshot)
inputMu sync.Mutex
Expand Down Expand Up @@ -96,7 +96,7 @@ func New(
recordManager recorder.RecordManager,
factory recorder.FFmpegRecorderFactory,
upstreamMgr *devtoolsproxy.UpstreamManager,
stz scaletozero.Controller,
stz scaletozero.PinnedController,
nekoAuthClient *nekoclient.AuthClient,
captureSession *capturesession.CaptureSession,
eventStream *events.EventStream,
Expand Down
24 changes: 24 additions & 0 deletions server/cmd/api/api/scaletozero.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package api

import (
"context"

"github.com/kernel/kernel-images/server/lib/logger"
oapi "github.com/kernel/kernel-images/server/lib/oapi"
)

func (s *ApiService) DisableScaleToZero(ctx context.Context, _ oapi.DisableScaleToZeroRequestObject) (oapi.DisableScaleToZeroResponseObject, error) {
if err := s.stz.Pin(ctx); err != nil {
logger.FromContext(ctx).Error("failed to disable scale-to-zero", "err", err)
return oapi.DisableScaleToZero500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to disable scale-to-zero"}}, nil
}
return oapi.DisableScaleToZero204Response{}, nil
}

func (s *ApiService) EnableScaleToZero(ctx context.Context, _ oapi.EnableScaleToZeroRequestObject) (oapi.EnableScaleToZeroResponseObject, error) {
if err := s.stz.Unpin(ctx); err != nil {
logger.FromContext(ctx).Error("failed to enable scale-to-zero", "err", err)
return oapi.EnableScaleToZero500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to enable scale-to-zero"}}, nil
}
return oapi.EnableScaleToZero204Response{}, nil
}
60 changes: 60 additions & 0 deletions server/e2e/e2e_scaletozero_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package e2e

import (
"context"
"net/http"
"os/exec"
"testing"
"time"

"github.com/stretchr/testify/require"
)

// TestScaleToZeroDisableEnable exercises POST /scaletozero/{disable,enable}
// against the real built image. The unikraft control file does not exist
// inside the docker test container, so the underlying scale-to-zero write is
// a no-op — this test validates HTTP wiring, idempotency, and that the
// scale-to-zero middleware coexists with the disable/enable handlers.
func TestScaleToZeroDisableEnable(t *testing.T) {
t.Parallel()

if _, err := exec.LookPath("docker"); err != nil {
t.Skipf("docker not available: %v", err)
}

ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
defer cancel()

c := NewTestContainer(t, headlessImage)
require.NoError(t, c.Start(ctx, ContainerConfig{}), "failed to start container")
defer c.Stop(ctx)

require.NoError(t, c.WaitReady(ctx), "api not ready")

client, err := c.APIClient()
require.NoError(t, err, "failed to create API client")

// Idempotent disable.
r1, err := client.DisableScaleToZeroWithResponse(ctx)
require.NoError(t, err, "DisableScaleToZero request failed")
require.Equal(t, http.StatusNoContent, r1.StatusCode(), "unexpected status: %s body=%s", r1.Status(), string(r1.Body))

r2, err := client.DisableScaleToZeroWithResponse(ctx)
require.NoError(t, err, "second DisableScaleToZero request failed")
require.Equal(t, http.StatusNoContent, r2.StatusCode(), "unexpected status: %s body=%s", r2.Status(), string(r2.Body))

// Normal request must still flow while disabled (scaletozero middleware
// runs on every request — the pin must not deadlock or break it).
readResp, err := client.ReadClipboardWithResponse(ctx)
require.NoError(t, err, "ReadClipboard request failed while disabled")
require.Equal(t, http.StatusOK, readResp.StatusCode(), "unexpected read status while disabled: %s body=%s", readResp.Status(), string(readResp.Body))

// Idempotent enable.
r3, err := client.EnableScaleToZeroWithResponse(ctx)
require.NoError(t, err, "EnableScaleToZero request failed")
require.Equal(t, http.StatusNoContent, r3.StatusCode(), "unexpected status: %s body=%s", r3.Status(), string(r3.Body))

r4, err := client.EnableScaleToZeroWithResponse(ctx)
require.NoError(t, err, "second EnableScaleToZero request failed")
require.Equal(t, http.StatusNoContent, r4.StatusCode(), "unexpected status: %s body=%s", r4.Status(), string(r4.Body))
}
Loading
Loading