diff --git a/cmd/api/api/capabilities.go b/cmd/api/api/capabilities.go new file mode 100644 index 00000000..e661d2ad --- /dev/null +++ b/cmd/api/api/capabilities.go @@ -0,0 +1,240 @@ +package api + +import ( + "context" + "runtime" + "slices" + "sync" + + "github.com/kernel/hypeman/lib/hypervisor" + "github.com/kernel/hypeman/lib/images" + "github.com/kernel/hypeman/lib/logger" + "github.com/kernel/hypeman/lib/network" + "github.com/kernel/hypeman/lib/oapi" +) + +// Stable feature IDs reported by the capabilities endpoint. Clients gate +// behavior on these (and the structured runtime booleans) rather than on +// hypervisor names. +const ( + featureInstances = "instances" + featureImages = "images" + featureBuilds = "builds" + featureVolumes = "volumes" + featureIngress = "ingress" + featureExec = "exec" + featureLogs = "logs" + featureStandby = "standby" + featureSnapshots = "snapshots" + featureFork = "fork" + featurePause = "pause" + featureHotplugMemory = "hotplug-memory" + featureBalloonControl = "balloon-control" + featureVsock = "vsock" + featureGPUPassthrough = "gpu-passthrough" + featureDiskIOLimit = "disk-io-limit" + featureDiskResize = "disk-resize" + featureDevices = "devices" + featureRosettaEmulation = "rosetta-emulation" +) + +// apiVersion is the API contract version from the embedded OpenAPI document. +// The decoded spec is cached: decoding it per request is needlessly expensive. +var apiVersion = sync.OnceValue(func() string { + spec, err := oapi.GetSwagger() + if err != nil || spec.Info == nil { + return "unknown" + } + return spec.Info.Version +}) + +// GetCapabilities reports host, runtime, network, and image capabilities. +func (s *ApiService) GetCapabilities(ctx context.Context, _ oapi.GetCapabilitiesRequestObject) (oapi.GetCapabilitiesResponseObject, error) { + log := logger.FromContext(ctx) + + defaultRuntime := hypervisor.TypeCloudHypervisor + if s.InstanceManager != nil { + defaultRuntime = s.InstanceManager.DefaultHypervisor() + } + supported := supportedRuntimes(runtime.GOOS) + caps, capsKnown := hypervisor.CapabilitiesForType(defaultRuntime) + if capsKnown && !slices.Contains(supported, string(defaultRuntime)) { + // The configured default runtime cannot run on this host platform; + // advertising its features would overstate support. + capsKnown = false + } + if !capsKnown { + // Report zeroed features rather than guessing. + log.WarnContext(ctx, "default runtime is not usable on this host platform", + "runtime", string(defaultRuntime), "host_os", runtime.GOOS) + caps = hypervisor.Capabilities{} + } + + emulation := emulationSupported(runtime.GOOS, runtime.GOARCH, defaultRuntime) + + networkCaps, err := s.networkCapabilities(ctx) + if err != nil { + log.ErrorContext(ctx, "failed to resolve network capabilities", "error", err) + return oapi.GetCapabilities500JSONResponse{ + Code: "internal_error", + Message: "failed to resolve network capabilities", + }, nil + } + + resp := oapi.Capabilities{ + Server: oapi.CapabilitiesServer{ + Version: s.Config.Version, + ApiVersion: apiVersion(), + }, + Host: oapi.CapabilitiesHost{ + Os: runtime.GOOS, + Arch: runtime.GOARCH, + }, + Runtime: oapi.CapabilitiesRuntime{ + Default: string(defaultRuntime), + Supported: supported, + Snapshot: caps.SupportsSnapshot, + Standby: standbySupported(caps), + Pause: caps.SupportsPause, + HotplugMemory: caps.SupportsHotplugMemory, + BalloonControl: caps.SupportsBalloonControl, + Vsock: caps.SupportsVsock, + GpuPassthrough: caps.SupportsGPUPassthrough, + DiskIoLimit: caps.SupportsDiskIOLimit, + DiskResize: caps.SupportsDiskResize, + }, + Network: *networkCaps, + Images: oapi.CapabilitiesImages{ + Platforms: imagePlatforms(runtime.GOARCH, emulation), + DefaultPlatform: images.HostPlatformString(), + }, + Features: assembleFeatures(runtime.GOOS, caps, emulation), + } + + return oapi.GetCapabilities200JSONResponse(resp), nil +} + +// networkCapabilities resolves the guest networking model and the +// guest-visible host gateway from the network manager's effective default +// network. +func (s *ApiService) networkCapabilities(ctx context.Context) (*oapi.CapabilitiesNetwork, error) { + caps := &oapi.CapabilitiesNetwork{ + Model: oapi.CapabilitiesNetworkModel(network.NetworkModel()), + GuestToGuest: false, + } + if s.NetworkManager == nil { + return caps, nil + } + nw, err := s.NetworkManager.DefaultNetwork(ctx) + if err != nil { + return nil, err + } + if nw == nil { + return caps, nil + } + caps.Gateway = nw.Gateway + if nw.Subnet != "" { + subnet := nw.Subnet + caps.Subnet = &subnet + } + caps.GuestToGuest = network.GuestToGuestEnabled(nw) + return caps, nil +} + +// standbySupported reports whether standby (pause + memory snapshot) and +// later restore are supported. Standby requires both snapshot and pause. +func standbySupported(caps hypervisor.Capabilities) bool { + return caps.SupportsSnapshot && caps.SupportsPause +} + +// supportedRuntimes returns the runtime identifiers usable on a host OS. +// This is a platform floor, not a registry listing: a runtime whose package +// registered capabilities but that cannot run on the host (e.g. firecracker +// on macOS) is not listed. +func supportedRuntimes(goos string) []string { + switch goos { + case "darwin": + return []string{string(hypervisor.TypeVZ)} + default: + return []string{ + string(hypervisor.TypeCloudHypervisor), + string(hypervisor.TypeFirecracker), + string(hypervisor.TypeQEMU), + } + } +} + +// emulationSupported reports whether the host can boot images built for the +// other CPU architecture. This mirrors the create-path rule for attaching +// the Rosetta share: vz on Apple Silicon macOS. +func emulationSupported(goos, goarch string, defaultRuntime hypervisor.Type) bool { + return defaultRuntime == hypervisor.TypeVZ && goos == "darwin" && goarch == "arm64" +} + +// imagePlatforms returns the image platforms (os/arch) the host can run: +// the host-native platform plus the emulated one when available. +func imagePlatforms(goarch string, emulation bool) []string { + if goarch == "" { + goarch = runtime.GOARCH + } + platforms := []string{"linux/" + goarch} + if emulation { + switch goarch { + case "arm64": + platforms = append(platforms, "linux/amd64") + case "amd64": + platforms = append(platforms, "linux/arm64") + } + } + return platforms +} + +// assembleFeatures builds the stable feature ID list: always-present base API +// surfaces plus conditional entries derived from the effective default +// runtime's capabilities and the host platform. +func assembleFeatures(goos string, caps hypervisor.Capabilities, emulation bool) []string { + features := []string{ + featureInstances, + featureImages, + featureBuilds, + featureVolumes, + featureIngress, + featureExec, + featureLogs, + } + if standbySupported(caps) { + features = append(features, featureStandby) + } + if caps.SupportsSnapshot { + features = append(features, featureSnapshots, featureFork) + } + if caps.SupportsPause { + features = append(features, featurePause) + } + if caps.SupportsHotplugMemory { + features = append(features, featureHotplugMemory) + } + if caps.SupportsBalloonControl { + features = append(features, featureBalloonControl) + } + if caps.SupportsVsock { + features = append(features, featureVsock) + } + if caps.SupportsGPUPassthrough { + features = append(features, featureGPUPassthrough) + } + if caps.SupportsDiskIOLimit { + features = append(features, featureDiskIOLimit) + } + if caps.SupportsDiskResize { + features = append(features, featureDiskResize) + } + // Device passthrough (GPU/PCI) is only meaningful on Linux hosts. + if goos == "linux" { + features = append(features, featureDevices) + } + if emulation { + features = append(features, featureRosettaEmulation) + } + return features +} diff --git a/cmd/api/api/capabilities_test.go b/cmd/api/api/capabilities_test.go new file mode 100644 index 00000000..2a2e9ba5 --- /dev/null +++ b/cmd/api/api/capabilities_test.go @@ -0,0 +1,186 @@ +package api + +import ( + "context" + "runtime" + "testing" + + "github.com/kernel/hypeman/lib/hypervisor" + "github.com/kernel/hypeman/lib/network" + "github.com/kernel/hypeman/lib/oapi" + "github.com/stretchr/testify/require" +) + +// stubCapabilitiesNetworkManager overrides DefaultNetwork with a fixed +// effective network so the handler test is hermetic (no bridge/netlink). +type stubCapabilitiesNetworkManager struct { + network.Manager + nw *network.Network + err error +} + +func (s *stubCapabilitiesNetworkManager) DefaultNetwork(ctx context.Context) (*network.Network, error) { + return s.nw, s.err +} + +func cloudHypervisorCaps(t *testing.T) hypervisor.Capabilities { + t.Helper() + caps, ok := hypervisor.CapabilitiesForType(hypervisor.TypeCloudHypervisor) + require.True(t, ok, "cloud-hypervisor capabilities must be registered") + return caps +} + +func TestGetCapabilities(t *testing.T) { + t.Parallel() + svc := newTestService(t) + svc.Config.Version = "testsha123" + svc.NetworkManager = &stubCapabilitiesNetworkManager{ + nw: &network.Network{ + Name: "default", + Subnet: "10.100.0.0/16", + Gateway: "10.100.0.1", + Isolated: true, + Default: true, + }, + } + + resp, err := svc.GetCapabilities(ctx(), oapi.GetCapabilitiesRequestObject{}) + require.NoError(t, err) + + okResp, ok := resp.(oapi.GetCapabilities200JSONResponse) + require.True(t, ok, "expected 200 response, got %T", resp) + caps := oapi.Capabilities(okResp) + + // Server identity and version serialization + require.Equal(t, "testsha123", caps.Server.Version) + require.Equal(t, apiVersion(), caps.Server.ApiVersion) + require.NotEmpty(t, caps.Server.ApiVersion) + require.NotEqual(t, "unknown", caps.Server.ApiVersion) + + // Host identity + require.Equal(t, runtime.GOOS, caps.Host.Os) + require.Equal(t, runtime.GOARCH, caps.Host.Arch) + + // Effective default runtime and its capabilities. The test-service manager + // defaults to cloud-hypervisor; on macOS that default cannot run, so the + // endpoint must report zeroed runtime features rather than overstate. + require.Equal(t, string(hypervisor.TypeCloudHypervisor), caps.Runtime.Default) + require.NotEmpty(t, caps.Runtime.Supported) + if runtime.GOOS == "darwin" { + require.Equal(t, []string{string(hypervisor.TypeVZ)}, caps.Runtime.Supported) + require.False(t, caps.Runtime.Standby, "unusable default runtime must not advertise standby") + require.False(t, caps.Runtime.Snapshot) + } else { + require.Contains(t, caps.Runtime.Supported, caps.Runtime.Default) + chCaps := cloudHypervisorCaps(t) + require.Equal(t, chCaps.SupportsSnapshot, caps.Runtime.Snapshot) + require.Equal(t, chCaps.SupportsPause, caps.Runtime.Pause) + require.True(t, caps.Runtime.Standby) + } + + // Network model and guest-visible gateway + require.Equal(t, oapi.CapabilitiesNetworkModel(network.NetworkModel()), caps.Network.Model) + require.Equal(t, "10.100.0.1", caps.Network.Gateway) + require.NotNil(t, caps.Network.Subnet) + require.Equal(t, "10.100.0.0/16", *caps.Network.Subnet) + require.False(t, caps.Network.GuestToGuest, "isolated default network must report guest_to_guest=false") + + // Image platforms always include the host-native guest platform + require.Contains(t, caps.Images.Platforms, caps.Images.DefaultPlatform) + + // Base feature IDs are always present + for _, f := range []string{"instances", "images", "builds", "volumes", "ingress", "exec", "logs"} { + require.Contains(t, caps.Features, f) + } + if runtime.GOOS == "darwin" { + require.NotContains(t, caps.Features, "devices") + require.NotContains(t, caps.Features, "standby") + } else { + require.Contains(t, caps.Features, "standby") + require.Contains(t, caps.Features, "devices") + } +} + +func TestGetCapabilitiesNetworkError(t *testing.T) { + t.Parallel() + svc := newTestService(t) + svc.NetworkManager = &stubCapabilitiesNetworkManager{err: context.DeadlineExceeded} + + resp, err := svc.GetCapabilities(ctx(), oapi.GetCapabilitiesRequestObject{}) + require.NoError(t, err) + _, ok := resp.(oapi.GetCapabilities500JSONResponse) + require.True(t, ok, "expected 500 response when network resolution fails, got %T", resp) +} + +func TestSupportedRuntimes(t *testing.T) { + t.Parallel() + require.Equal(t, + []string{"cloud-hypervisor", "firecracker", "qemu"}, + supportedRuntimes("linux")) + require.Equal(t, []string{"vz"}, supportedRuntimes("darwin")) +} + +func TestEmulationSupported(t *testing.T) { + t.Parallel() + require.True(t, emulationSupported("darwin", "arm64", hypervisor.TypeVZ)) + require.False(t, emulationSupported("darwin", "amd64", hypervisor.TypeVZ)) + require.False(t, emulationSupported("darwin", "arm64", hypervisor.TypeCloudHypervisor)) + require.False(t, emulationSupported("linux", "arm64", hypervisor.TypeVZ)) + require.False(t, emulationSupported("linux", "amd64", hypervisor.TypeCloudHypervisor)) +} + +func TestImagePlatforms(t *testing.T) { + t.Parallel() + require.Equal(t, []string{"linux/amd64"}, imagePlatforms("amd64", false)) + require.Equal(t, []string{"linux/arm64"}, imagePlatforms("arm64", false)) + require.Equal(t, + []string{"linux/arm64", "linux/amd64"}, + imagePlatforms("arm64", true), + "Apple Silicon macOS with vz advertises Rosetta-emulated amd64") +} + +func TestAssembleFeatures(t *testing.T) { + t.Parallel() + + base := []string{"instances", "images", "builds", "volumes", "ingress", "exec", "logs"} + + // Zero capabilities (unknown runtime on this host): base features only. + features := assembleFeatures("linux", hypervisor.Capabilities{}, false) + require.ElementsMatch(t, append(base, "devices"), features) + + // Full Linux cloud-hypervisor capabilities. + features = assembleFeatures("linux", cloudHypervisorCaps(t), false) + require.Subset(t, features, base) + require.Contains(t, features, "standby") + require.Contains(t, features, "snapshots") + require.Contains(t, features, "fork") + require.Contains(t, features, "pause") + require.Contains(t, features, "hotplug-memory") + require.Contains(t, features, "devices") + require.NotContains(t, features, "rosetta-emulation") + + // macOS: no devices feature; emulation adds rosetta. + vzCaps := hypervisor.Capabilities{SupportsSnapshot: true, SupportsPause: true, SupportsBalloonControl: true, SupportsVsock: true} + features = assembleFeatures("darwin", vzCaps, true) + require.NotContains(t, features, "devices") + require.Contains(t, features, "standby") + require.Contains(t, features, "rosetta-emulation") + require.NotContains(t, features, "hotplug-memory") + require.NotContains(t, features, "gpu-passthrough") +} + +func TestStandbyRequiresSnapshotAndPause(t *testing.T) { + t.Parallel() + require.False(t, standbySupported(hypervisor.Capabilities{SupportsSnapshot: true, SupportsPause: false})) + require.False(t, standbySupported(hypervisor.Capabilities{SupportsSnapshot: false, SupportsPause: true})) + require.True(t, standbySupported(hypervisor.Capabilities{SupportsSnapshot: true, SupportsPause: true})) +} + +// TestAPIVersionMatchesSpec guards the version contract: the endpoint must +// serialize the same version as the embedded OpenAPI document. +func TestAPIVersionMatchesSpec(t *testing.T) { + t.Parallel() + spec, err := oapi.GetSwagger() + require.NoError(t, err) + require.Equal(t, spec.Info.Version, apiVersion()) +} diff --git a/lib/builds/manager_test.go b/lib/builds/manager_test.go index c26295ea..63c82f56 100644 --- a/lib/builds/manager_test.go +++ b/lib/builds/manager_test.go @@ -74,6 +74,10 @@ func (m *mockInstanceManager) CreateInstance(ctx context.Context, req instances. return inst, nil } +func (m *mockInstanceManager) DefaultHypervisor() hypervisor.Type { + return hypervisor.TypeCloudHypervisor +} + func (m *mockInstanceManager) GetInstance(ctx context.Context, id string) (*instances.Instance, error) { if m.getFunc != nil { return m.getFunc(ctx, id) diff --git a/lib/hypervisor/vz/client.go b/lib/hypervisor/vz/client.go index 2fb0a62a..f0116bb9 100644 --- a/lib/hypervisor/vz/client.go +++ b/lib/hypervisor/vz/client.go @@ -10,7 +10,6 @@ import ( "io" "net" "net/http" - "runtime" "time" "github.com/kernel/hypeman/lib/hypervisor" @@ -84,7 +83,10 @@ func (c *Client) Capabilities() hypervisor.Capabilities { func capabilities() hypervisor.Capabilities { return hypervisor.Capabilities{ - SupportsSnapshot: runtime.GOARCH == "arm64", + // Snapshot/standby support is runtime-derived: it requires Apple + // Silicon AND macOS 14+. An arm64-only check would overstate + // support on macOS 13 hosts. + SupportsSnapshot: saveRestoreSupported(), SupportsHotplugMemory: false, SupportsBalloonControl: true, SupportsPause: true, diff --git a/lib/hypervisor/vz/save_restore_support.go b/lib/hypervisor/vz/save_restore_support.go new file mode 100644 index 00000000..a6264418 --- /dev/null +++ b/lib/hypervisor/vz/save_restore_support.go @@ -0,0 +1,40 @@ +package vz + +import ( + "strconv" + "strings" +) + +// saveRestoreMinMacOSMajor is the minimum macOS major version whose +// Virtualization.framework exposes VM save/restore (VZVirtualMachine +// saveMachineStateToURL / restoreMachineStateFromURL). +const saveRestoreMinMacOSMajor = 14 + +// SaveRestoreSupported reports whether Virtualization.framework VM +// save/restore (snapshots, and therefore standby) is available on a host with +// the given GOOS, GOARCH, and macOS product version (e.g. "14.5.1"). The +// product version is only consulted on darwin/arm64; an empty or unparsable +// version is treated as unsupported so a failed probe never overstates +// support. +func SaveRestoreSupported(goos, goarch, productVersion string) bool { + if goos != "darwin" || goarch != "arm64" { + return false + } + major, ok := parseMacOSMajorVersion(productVersion) + return ok && major >= saveRestoreMinMacOSMajor +} + +// parseMacOSMajorVersion extracts the major component from a macOS product +// version string like "14", "14.5", or "14.5.1". +func parseMacOSMajorVersion(productVersion string) (int, bool) { + productVersion = strings.TrimSpace(productVersion) + if productVersion == "" { + return 0, false + } + majorStr, _, _ := strings.Cut(productVersion, ".") + major, err := strconv.Atoi(majorStr) + if err != nil || major < 0 { + return 0, false + } + return major, true +} diff --git a/lib/hypervisor/vz/save_restore_support_darwin.go b/lib/hypervisor/vz/save_restore_support_darwin.go new file mode 100644 index 00000000..6b8833e6 --- /dev/null +++ b/lib/hypervisor/vz/save_restore_support_darwin.go @@ -0,0 +1,26 @@ +//go:build darwin + +package vz + +import ( + "runtime" + + "golang.org/x/sys/unix" +) + +// macOSProductVersion returns the host macOS product version (e.g. "14.5.1"). +// It is a variable so tests can stub the probe. +var macOSProductVersion = func() string { + v, err := unix.Sysctl("kern.osproductversion") + if err != nil { + return "" + } + return v +} + +// saveRestoreSupported reports whether this host supports VZ VM save/restore. +// Detection is runtime-derived (Apple Silicon + macOS 14+) so static +// capabilities never overstate snapshot/standby support on older macOS. +func saveRestoreSupported() bool { + return SaveRestoreSupported(runtime.GOOS, runtime.GOARCH, macOSProductVersion()) +} diff --git a/lib/hypervisor/vz/save_restore_support_darwin_test.go b/lib/hypervisor/vz/save_restore_support_darwin_test.go new file mode 100644 index 00000000..5e180be6 --- /dev/null +++ b/lib/hypervisor/vz/save_restore_support_darwin_test.go @@ -0,0 +1,25 @@ +//go:build darwin + +package vz + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +// TestCapabilitiesDeriveSnapshotFromHost proves the hypervisor's advertised +// capabilities track the probed macOS version instead of a static arch check. +func TestCapabilitiesDeriveSnapshotFromHost(t *testing.T) { + orig := macOSProductVersion + t.Cleanup(func() { macOSProductVersion = orig }) + + macOSProductVersion = func() string { return "13.6" } + require.False(t, capabilities().SupportsSnapshot, "macOS 13 must not advertise snapshot/standby support") + + macOSProductVersion = func() string { return "14.5" } + require.Equal(t, saveRestoreSupported(), capabilities().SupportsSnapshot) + + macOSProductVersion = func() string { return "" } + require.False(t, capabilities().SupportsSnapshot, "failed version probe must not advertise snapshot support") +} diff --git a/lib/hypervisor/vz/save_restore_support_test.go b/lib/hypervisor/vz/save_restore_support_test.go new file mode 100644 index 00000000..6b3d4c68 --- /dev/null +++ b/lib/hypervisor/vz/save_restore_support_test.go @@ -0,0 +1,33 @@ +package vz + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestSaveRestoreSupported(t *testing.T) { + tests := []struct { + name string + goos string + goarch string + productVersion string + want bool + }{ + {name: "linux arm64 is never vz", goos: "linux", goarch: "arm64", productVersion: "14.5", want: false}, + {name: "darwin amd64 unsupported", goos: "darwin", goarch: "amd64", productVersion: "14.5", want: false}, + {name: "macOS 13 overstates nothing", goos: "darwin", goarch: "arm64", productVersion: "13.6", want: false}, + {name: "macOS 14 minimum", goos: "darwin", goarch: "arm64", productVersion: "14.0", want: true}, + {name: "macOS 14 patch", goos: "darwin", goarch: "arm64", productVersion: "14.5.1", want: true}, + {name: "macOS 15", goos: "darwin", goarch: "arm64", productVersion: "15.2", want: true}, + {name: "macOS 26", goos: "darwin", goarch: "arm64", productVersion: "26.0", want: true}, + {name: "empty version is unsupported", goos: "darwin", goarch: "arm64", productVersion: "", want: false}, + {name: "unparsable version is unsupported", goos: "darwin", goarch: "arm64", productVersion: "unknown", want: false}, + {name: "major-only version", goos: "darwin", goarch: "arm64", productVersion: "14", want: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.want, SaveRestoreSupported(tt.goos, tt.goarch, tt.productVersion)) + }) + } +} diff --git a/lib/instances/manager.go b/lib/instances/manager.go index 8e8e25f3..98fe43fc 100644 --- a/lib/instances/manager.go +++ b/lib/instances/manager.go @@ -27,6 +27,9 @@ import ( type Manager interface { ListInstances(ctx context.Context, filter *ListInstancesFilter) ([]Instance, error) + // DefaultHypervisor returns the effective default hypervisor type used for + // launches that do not specify one. + DefaultHypervisor() hypervisor.Type ListSnapshots(ctx context.Context, filter *ListSnapshotsFilter) ([]Snapshot, error) GetSnapshot(ctx context.Context, snapshotID string) (*Snapshot, error) CreateInstance(ctx context.Context, req CreateInstanceRequest) (*Instance, error) @@ -690,6 +693,12 @@ func (m *manager) UpdateInstance(ctx context.Context, id string, req UpdateInsta return inst, err } +// DefaultHypervisor returns the effective default hypervisor type used for +// launches that do not specify one. +func (m *manager) DefaultHypervisor() hypervisor.Type { + return m.defaultHypervisor +} + // ListInstances returns instances, optionally filtered by the given criteria. // Pass nil to return all instances. func (m *manager) ListInstances(ctx context.Context, filter *ListInstancesFilter) ([]Instance, error) { diff --git a/lib/instances/wait_test.go b/lib/instances/wait_test.go index bab6f06d..640a6005 100644 --- a/lib/instances/wait_test.go +++ b/lib/instances/wait_test.go @@ -28,6 +28,10 @@ func (s *stubManager) GetInstance(ctx context.Context, id string) (*Instance, er return nil, ErrNotFound } +func (s *stubManager) DefaultHypervisor() hypervisor.Type { + return hypervisor.TypeCloudHypervisor +} + // Unused interface methods — only GetInstance and SubscribeLifecycleEvents are needed. func (s *stubManager) ListInstances(context.Context, *ListInstancesFilter) ([]Instance, error) { return nil, nil diff --git a/lib/network/default_network_test.go b/lib/network/default_network_test.go new file mode 100644 index 00000000..ba7b04e0 --- /dev/null +++ b/lib/network/default_network_test.go @@ -0,0 +1,59 @@ +package network + +import ( + "context" + "testing" + + "github.com/kernel/hypeman/cmd/api/config" + "github.com/kernel/hypeman/lib/paths" + "github.com/stretchr/testify/require" +) + +// TestDefaultNetworkEffectiveGateway proves DefaultNetwork reports the +// guest-visible gateway for the host's networking model: the initialized +// (config-derived) network on Linux, and the vz NAT stub on macOS, where the +// configured subnet/gateway are ignored. +func TestDefaultNetworkEffectiveGateway(t *testing.T) { + t.Parallel() + cfg := &config.Config{} + m := NewManager(paths.New(t.TempDir()), cfg, nil).(*manager) + + want := &Network{ + Name: "default", + Subnet: "10.100.0.0/16", + Gateway: "10.100.0.1", + Isolated: true, + Default: true, + } + m.setDefaultNetwork(want) + + nw, err := m.DefaultNetwork(context.Background()) + require.NoError(t, err) + if preferCachedDefaultNetwork() { + require.Equal(t, "10.100.0.1", nw.Gateway) + require.Equal(t, "10.100.0.0/16", nw.Subnet) + + // Mutating the returned copy must not affect the cached network. + nw.Gateway = " mutated " + again, err := m.DefaultNetwork(context.Background()) + require.NoError(t, err) + require.Equal(t, "10.100.0.1", again.Gateway) + } else { + // macOS: the vz NAT stub is authoritative; the config-derived cache + // is never guest-visible. + require.Equal(t, "192.168.64.1", nw.Gateway) + require.Equal(t, "192.168.64.0/24", nw.Subnet) + } +} + +func TestGuestToGuestEnabled(t *testing.T) { + t.Parallel() + require.False(t, GuestToGuestEnabled(&Network{Isolated: true}), + "isolated default network blocks direct guest-to-guest traffic") + if NetworkModel() == "bridge" { + require.True(t, GuestToGuestEnabled(&Network{Isolated: false})) + } else { + require.False(t, GuestToGuestEnabled(&Network{Isolated: false}), + "NAT networking never provides direct guest-to-guest reachability") + } +} diff --git a/lib/network/manager.go b/lib/network/manager.go index bc657117..7cca0f6b 100644 --- a/lib/network/manager.go +++ b/lib/network/manager.go @@ -35,6 +35,10 @@ type Manager interface { ListAllocations(ctx context.Context) ([]Allocation, error) NameExists(ctx context.Context, name string, excludeInstanceID string) (bool, error) + // DefaultNetwork returns the effective default network, including the + // guest-visible host gateway and subnet for this host's networking model. + DefaultNetwork(ctx context.Context) (*Network, error) + // CleanupOrphanedTAPs removes TAP devices not associated with any preserved // instance. Pass minAge>0 to skip TAPs younger than that, which avoids racing // against in-flight CreateAllocation calls whose metadata hasn't been persisted. @@ -163,6 +167,20 @@ func (m *manager) setDefaultNetwork(network *Network) { m.defaultNetwork = cloneNetwork(network) } +// DefaultNetwork returns the effective default network. On Linux it prefers +// the network established during Initialize (bridge state mirrors config). +// On macOS the configured subnet/gateway are ignored — guests use vz NAT — +// so the live NAT stub from queryNetworkState is authoritative and the +// config-derived cache is never guest-visible. +func (m *manager) DefaultNetwork(ctx context.Context) (*Network, error) { + if preferCachedDefaultNetwork() { + if network := m.cachedDefaultNetwork(); network != nil { + return network, nil + } + } + return m.getDefaultNetwork(ctx) +} + // getDefaultNetwork gets the default network details from kernel state func (m *manager) getDefaultNetwork(ctx context.Context) (*Network, error) { // Query from kernel diff --git a/lib/network/model_darwin.go b/lib/network/model_darwin.go new file mode 100644 index 00000000..db222e7c --- /dev/null +++ b/lib/network/model_darwin.go @@ -0,0 +1,24 @@ +//go:build darwin + +package network + +// NetworkModel identifies the guest networking model in use on this host. +// macOS hosts use Virtualization.framework-provided NAT. +func NetworkModel() string { + return "nat" +} + +// GuestToGuestEnabled reports whether direct VM-to-VM traffic is permitted on +// the given default network. Each vz guest sits behind its own NAT context, +// so direct guest-to-guest reachability is not provided. +func GuestToGuestEnabled(_ *Network) bool { + return false +} + +// preferCachedDefaultNetwork reports whether DefaultNetwork should trust the +// network cached during Initialize. On macOS the configured subnet/gateway +// are ignored (vz NAT assigns 192.168.64.0/24 with host gateway 192.168.64.1), +// so the cache is not guest-visible and live NAT state must win. +func preferCachedDefaultNetwork() bool { + return false +} diff --git a/lib/network/model_linux.go b/lib/network/model_linux.go new file mode 100644 index 00000000..8afa248a --- /dev/null +++ b/lib/network/model_linux.go @@ -0,0 +1,23 @@ +//go:build linux + +package network + +// NetworkModel identifies the guest networking model in use on this host. +// Linux hosts use a bridge with per-VM TAP devices and port isolation. +func NetworkModel() string { + return "bridge" +} + +// GuestToGuestEnabled reports whether direct VM-to-VM traffic is permitted on +// the given default network. Linux default networks use per-TAP port +// isolation, so guests cannot reach each other directly. +func GuestToGuestEnabled(n *Network) bool { + return n != nil && !n.Isolated +} + +// preferCachedDefaultNetwork reports whether DefaultNetwork should trust the +// network cached during Initialize. On Linux the cache mirrors the bridge +// built from config, so it is authoritative. +func preferCachedDefaultNetwork() bool { + return true +} diff --git a/lib/oapi/oapi.go b/lib/oapi/oapi.go index b98a2d00..97c372ea 100644 --- a/lib/oapi/oapi.go +++ b/lib/oapi/oapi.go @@ -73,6 +73,12 @@ const ( BuildStatusReady BuildStatus = "ready" ) +// Defines values for CapabilitiesNetworkModel. +const ( + Bridge CapabilitiesNetworkModel = "bridge" + Nat CapabilitiesNetworkModel = "nat" +) + // Defines values for CreateInstanceRequestHypervisor. const ( CreateInstanceRequestHypervisorCloudHypervisor CreateInstanceRequestHypervisor = "cloud-hypervisor" @@ -429,6 +435,110 @@ type BuildProvenance struct { // BuildStatus Build job status type BuildStatus string +// Capabilities defines model for Capabilities. +type Capabilities struct { + // Features Stable feature IDs clients can gate behavior on. Base IDs are always + // present: "instances", "images", "builds", "volumes", "ingress", + // "exec", "logs". Conditional IDs reflect the effective default + // runtime and host platform: "standby", "snapshots", "fork", + // "pause", "hotplug-memory", "balloon-control", "vsock", + // "gpu-passthrough", "disk-io-limit", "disk-resize", "devices", + // "rosetta-emulation". + Features []string `json:"features"` + Host CapabilitiesHost `json:"host"` + Images CapabilitiesImages `json:"images"` + Network CapabilitiesNetwork `json:"network"` + Runtime CapabilitiesRuntime `json:"runtime"` + Server CapabilitiesServer `json:"server"` +} + +// CapabilitiesHost defines model for CapabilitiesHost. +type CapabilitiesHost struct { + // Arch Host CPU architecture + Arch string `json:"arch"` + + // Os Host operating system + Os string `json:"os"` +} + +// CapabilitiesImages defines model for CapabilitiesImages. +type CapabilitiesImages struct { + // DefaultPlatform Image platform selected when a create request omits one + DefaultPlatform string `json:"default_platform"` + + // Platforms Image platforms (os/arch) this host can run. Includes emulated + // platforms (e.g. linux/amd64 via Rosetta on Apple Silicon macOS). + Platforms []string `json:"platforms"` +} + +// CapabilitiesNetwork defines model for CapabilitiesNetwork. +type CapabilitiesNetwork struct { + // Gateway Guest-visible host gateway IP. Guests reach host services (including + // host ingress) through this address. + Gateway string `json:"gateway"` + + // GuestToGuest Whether direct VM-to-VM traffic is permitted on the default network + GuestToGuest bool `json:"guest_to_guest"` + + // Model Guest networking model. "bridge" is a Linux bridge with per-VM TAP + // devices; "nat" is hypervisor-provided NAT (macOS). + Model CapabilitiesNetworkModel `json:"model"` + + // Subnet Guest subnet CIDR + Subnet *string `json:"subnet,omitempty"` +} + +// CapabilitiesNetworkModel Guest networking model. "bridge" is a Linux bridge with per-VM TAP +// devices; "nat" is hypervisor-provided NAT (macOS). +type CapabilitiesNetworkModel string + +// CapabilitiesRuntime defines model for CapabilitiesRuntime. +type CapabilitiesRuntime struct { + // BalloonControl Runtime memory balloon control is supported by the effective default runtime + BalloonControl bool `json:"balloon_control"` + + // Default Effective default runtime used for launches that do not name one. + // Reported as a capability-bearing runtime identifier; clients should + // gate behavior on the boolean features below rather than this value. + Default string `json:"default"` + + // DiskIoLimit Disk I/O rate limiting is supported by the effective default runtime + DiskIoLimit bool `json:"disk_io_limit"` + + // DiskResize Live disk resize is supported by the effective default runtime + DiskResize bool `json:"disk_resize"` + + // GpuPassthrough GPU/PCI device passthrough is supported by the effective default runtime + GpuPassthrough bool `json:"gpu_passthrough"` + + // HotplugMemory Live memory hotplug resize is supported by the effective default runtime + HotplugMemory bool `json:"hotplug_memory"` + + // Pause Pause/resume is supported by the effective default runtime + Pause bool `json:"pause"` + + // Snapshot Snapshot/restore is supported by the effective default runtime on this host + Snapshot bool `json:"snapshot"` + + // Standby Standby (pause + memory snapshot) and restore are supported on this host + Standby bool `json:"standby"` + + // Supported Runtime identifiers that can run on this host platform + Supported []string `json:"supported"` + + // Vsock Vsock guest communication is supported by the effective default runtime + Vsock bool `json:"vsock"` +} + +// CapabilitiesServer defines model for CapabilitiesServer. +type CapabilitiesServer struct { + // ApiVersion API contract version (matches the OpenAPI document info version) + ApiVersion string `json:"api_version"` + + // Version Server build version (short git revision, with "-dirty" suffix for uncommitted builds, or "unknown") + Version string `json:"version"` +} + // CreateDeviceRequest defines model for CreateDeviceRequest. type CreateDeviceRequest struct { // Name Optional globally unique device name. If not provided, a name is auto-generated from the PCI address (e.g., "pci-0000-a2-00-0") @@ -1943,6 +2053,9 @@ type ClientInterface interface { // GetBuildEvents request GetBuildEvents(ctx context.Context, id string, params *GetBuildEventsParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetCapabilities request + GetCapabilities(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // ListDevices request ListDevices(ctx context.Context, params *ListDevicesParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -2178,6 +2291,18 @@ func (c *Client) GetBuildEvents(ctx context.Context, id string, params *GetBuild return c.Client.Do(req) } +func (c *Client) GetCapabilities(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetCapabilitiesRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) ListDevices(ctx context.Context, params *ListDevicesParams, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewListDevicesRequest(c.Server, params) if err != nil { @@ -3136,6 +3261,33 @@ func NewGetBuildEventsRequest(server string, id string, params *GetBuildEventsPa return req, nil } +// NewGetCapabilitiesRequest generates requests for GetCapabilities +func NewGetCapabilitiesRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/capabilities") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewListDevicesRequest generates requests for ListDevices func NewListDevicesRequest(server string, params *ListDevicesParams) (*http.Request, error) { var err error @@ -5312,6 +5464,9 @@ type ClientWithResponsesInterface interface { // GetBuildEventsWithResponse request GetBuildEventsWithResponse(ctx context.Context, id string, params *GetBuildEventsParams, reqEditors ...RequestEditorFn) (*GetBuildEventsResponse, error) + // GetCapabilitiesWithResponse request + GetCapabilitiesWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetCapabilitiesResponse, error) + // ListDevicesWithResponse request ListDevicesWithResponse(ctx context.Context, params *ListDevicesParams, reqEditors ...RequestEditorFn) (*ListDevicesResponse, error) @@ -5607,6 +5762,29 @@ func (r GetBuildEventsResponse) StatusCode() int { return 0 } +type GetCapabilitiesResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Capabilities + JSON500 *Error +} + +// Status returns HTTPResponse.Status +func (r GetCapabilitiesResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetCapabilitiesResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type ListDevicesResponse struct { Body []byte HTTPResponse *http.Response @@ -6837,6 +7015,15 @@ func (c *ClientWithResponses) GetBuildEventsWithResponse(ctx context.Context, id return ParseGetBuildEventsResponse(rsp) } +// GetCapabilitiesWithResponse request returning *GetCapabilitiesResponse +func (c *ClientWithResponses) GetCapabilitiesWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetCapabilitiesResponse, error) { + rsp, err := c.GetCapabilities(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetCapabilitiesResponse(rsp) +} + // ListDevicesWithResponse request returning *ListDevicesResponse func (c *ClientWithResponses) ListDevicesWithResponse(ctx context.Context, params *ListDevicesParams, reqEditors ...RequestEditorFn) (*ListDevicesResponse, error) { rsp, err := c.ListDevices(ctx, params, reqEditors...) @@ -7589,6 +7776,39 @@ func ParseGetBuildEventsResponse(rsp *http.Response) (*GetBuildEventsResponse, e return response, nil } +// ParseGetCapabilitiesResponse parses an HTTP response from a GetCapabilitiesWithResponse call +func ParseGetCapabilitiesResponse(rsp *http.Response) (*GetCapabilitiesResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetCapabilitiesResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Capabilities + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + // ParseListDevicesResponse parses an HTTP response from a ListDevicesWithResponse call func ParseListDevicesResponse(rsp *http.Response) (*ListDevicesResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -9757,6 +9977,9 @@ type ServerInterface interface { // Stream build events (SSE) // (GET /builds/{id}/events) GetBuildEvents(w http.ResponseWriter, r *http.Request, id string, params GetBuildEventsParams) + // Get host capabilities + // (GET /capabilities) + GetCapabilities(w http.ResponseWriter, r *http.Request) // List registered devices // (GET /devices) ListDevices(w http.ResponseWriter, r *http.Request, params ListDevicesParams) @@ -9937,6 +10160,12 @@ func (_ Unimplemented) GetBuildEvents(w http.ResponseWriter, r *http.Request, id w.WriteHeader(http.StatusNotImplemented) } +// Get host capabilities +// (GET /capabilities) +func (_ Unimplemented) GetCapabilities(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + // List registered devices // (GET /devices) func (_ Unimplemented) ListDevices(w http.ResponseWriter, r *http.Request, params ListDevicesParams) { @@ -10391,6 +10620,26 @@ func (siw *ServerInterfaceWrapper) GetBuildEvents(w http.ResponseWriter, r *http handler.ServeHTTP(w, r) } +// GetCapabilities operation middleware +func (siw *ServerInterfaceWrapper) GetCapabilities(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetCapabilities(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + // ListDevices operation middleware func (siw *ServerInterfaceWrapper) ListDevices(w http.ResponseWriter, r *http.Request) { @@ -12092,6 +12341,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/builds/{id}/events", wrapper.GetBuildEvents) }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/capabilities", wrapper.GetCapabilities) + }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/devices", wrapper.ListDevices) }) @@ -12443,6 +12695,31 @@ func (response GetBuildEvents500JSONResponse) VisitGetBuildEventsResponse(w http return json.NewEncoder(w).Encode(response) } +type GetCapabilitiesRequestObject struct { +} + +type GetCapabilitiesResponseObject interface { + VisitGetCapabilitiesResponse(w http.ResponseWriter) error +} + +type GetCapabilities200JSONResponse Capabilities + +func (response GetCapabilities200JSONResponse) VisitGetCapabilitiesResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type GetCapabilities500JSONResponse Error + +func (response GetCapabilities500JSONResponse) VisitGetCapabilitiesResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + type ListDevicesRequestObject struct { Params ListDevicesParams } @@ -14525,6 +14802,9 @@ type StrictServerInterface interface { // Stream build events (SSE) // (GET /builds/{id}/events) GetBuildEvents(ctx context.Context, request GetBuildEventsRequestObject) (GetBuildEventsResponseObject, error) + // Get host capabilities + // (GET /capabilities) + GetCapabilities(ctx context.Context, request GetCapabilitiesRequestObject) (GetCapabilitiesResponseObject, error) // List registered devices // (GET /devices) ListDevices(ctx context.Context, request ListDevicesRequestObject) (ListDevicesResponseObject, error) @@ -14836,6 +15116,30 @@ func (sh *strictHandler) GetBuildEvents(w http.ResponseWriter, r *http.Request, } } +// GetCapabilities operation middleware +func (sh *strictHandler) GetCapabilities(w http.ResponseWriter, r *http.Request) { + var request GetCapabilitiesRequestObject + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.GetCapabilities(ctx, request.(GetCapabilitiesRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetCapabilities") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(GetCapabilitiesResponseObject); ok { + if err := validResponse.VisitGetCapabilitiesResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + // ListDevices operation middleware func (sh *strictHandler) ListDevices(w http.ResponseWriter, r *http.Request, params ListDevicesParams) { var request ListDevicesRequestObject @@ -16182,310 +16486,334 @@ func (sh *strictHandler) GetVolume(w http.ResponseWriter, r *http.Request, id st // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/lib/scopes/scopes.go b/lib/scopes/scopes.go index ef0bb85d..5588327e 100644 --- a/lib/scopes/scopes.go +++ b/lib/scopes/scopes.go @@ -209,8 +209,9 @@ var RouteScopes = map[string]Scope{ "DELETE /devices/{id}": DeviceDelete, "GET /devices/{id}": DeviceRead, - // Health & Resources + // Health, Capabilities & Resources "GET /health": ResourceRead, + "GET /capabilities": ResourceRead, "GET /resources": ResourceRead, "POST /resources/memory/reclaim": ResourceWrite, diff --git a/openapi.yaml b/openapi.yaml index 14b0eed9..3c1ae034 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -1422,6 +1422,145 @@ components: type: string enum: [ok] example: ok + + Capabilities: + type: object + required: [server, host, runtime, network, images, features] + properties: + server: + $ref: "#/components/schemas/CapabilitiesServer" + host: + $ref: "#/components/schemas/CapabilitiesHost" + runtime: + $ref: "#/components/schemas/CapabilitiesRuntime" + network: + $ref: "#/components/schemas/CapabilitiesNetwork" + images: + $ref: "#/components/schemas/CapabilitiesImages" + features: + type: array + items: + type: string + description: | + Stable feature IDs clients can gate behavior on. Base IDs are always + present: "instances", "images", "builds", "volumes", "ingress", + "exec", "logs". Conditional IDs reflect the effective default + runtime and host platform: "standby", "snapshots", "fork", + "pause", "hotplug-memory", "balloon-control", "vsock", + "gpu-passthrough", "disk-io-limit", "disk-resize", "devices", + "rosetta-emulation". + + CapabilitiesServer: + type: object + required: [version, api_version] + properties: + version: + type: string + description: Server build version (short git revision, with "-dirty" suffix for uncommitted builds, or "unknown") + example: 76a2fdb3 + api_version: + type: string + description: API contract version (matches the OpenAPI document info version) + example: 0.2.0 + + CapabilitiesHost: + type: object + required: [os, arch] + properties: + os: + type: string + description: Host operating system + example: linux + arch: + type: string + description: Host CPU architecture + example: arm64 + + CapabilitiesRuntime: + type: object + required: [default, supported, snapshot, standby, pause, hotplug_memory, balloon_control, vsock, gpu_passthrough, disk_io_limit, disk_resize] + properties: + default: + type: string + description: | + Effective default runtime used for launches that do not name one. + Reported as a capability-bearing runtime identifier; clients should + gate behavior on the boolean features below rather than this value. + example: cloud-hypervisor + supported: + type: array + items: + type: string + description: Runtime identifiers that can run on this host platform + example: [cloud-hypervisor, firecracker, qemu] + snapshot: + type: boolean + description: Snapshot/restore is supported by the effective default runtime on this host + standby: + type: boolean + description: Standby (pause + memory snapshot) and restore are supported on this host + pause: + type: boolean + description: Pause/resume is supported by the effective default runtime + hotplug_memory: + type: boolean + description: Live memory hotplug resize is supported by the effective default runtime + balloon_control: + type: boolean + description: Runtime memory balloon control is supported by the effective default runtime + vsock: + type: boolean + description: Vsock guest communication is supported by the effective default runtime + gpu_passthrough: + type: boolean + description: GPU/PCI device passthrough is supported by the effective default runtime + disk_io_limit: + type: boolean + description: Disk I/O rate limiting is supported by the effective default runtime + disk_resize: + type: boolean + description: Live disk resize is supported by the effective default runtime + + CapabilitiesNetwork: + type: object + required: [model, gateway, guest_to_guest] + properties: + model: + type: string + enum: [bridge, nat] + description: | + Guest networking model. "bridge" is a Linux bridge with per-VM TAP + devices; "nat" is hypervisor-provided NAT (macOS). + gateway: + type: string + description: | + Guest-visible host gateway IP. Guests reach host services (including + host ingress) through this address. + example: 10.100.0.1 + subnet: + type: string + description: Guest subnet CIDR + example: 10.100.0.0/16 + guest_to_guest: + type: boolean + description: Whether direct VM-to-VM traffic is permitted on the default network + + CapabilitiesImages: + type: object + required: [platforms, default_platform] + properties: + platforms: + type: array + items: + type: string + description: | + Image platforms (os/arch) this host can run. Includes emulated + platforms (e.g. linux/amd64 via Rosetta on Apple Silicon macOS). + example: [linux/arm64, linux/amd64] + default_platform: + type: string + description: Image platform selected when a create request omits one + example: linux/arm64 IngressMatch: type: object @@ -2077,6 +2216,35 @@ paths: schema: $ref: "#/components/schemas/Health" + /capabilities: + get: + summary: Get host capabilities + description: | + Returns machine-readable host capabilities: server and API version, host + OS/architecture, effective default-runtime features, guest networking model + and host gateway, supported image platforms, and stable feature IDs. + + Runtime-derived values reflect the actual host (for example, snapshot and + standby support on macOS is gated on the host OS version), so clients can + gate behavior on capabilities without selecting or naming a concrete + hypervisor. + operationId: getCapabilities + security: + - bearerAuth: [] + responses: + 200: + description: Host capabilities + content: + application/json: + schema: + $ref: "#/components/schemas/Capabilities" + 500: + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /resources: get: summary: Get host resource capacity and allocations