mirror of
https://github.com/getcompanion-ai/computer-host.git
synced 2026-04-15 08:03:40 +00:00
feat: firecracker mmds identity
This commit is contained in:
parent
500354cd9b
commit
3eb610b703
23 changed files with 1813 additions and 263 deletions
|
|
@ -27,10 +27,12 @@ type bootSourceRequest struct {
|
|||
}
|
||||
|
||||
type driveRequest struct {
|
||||
DriveID string `json:"drive_id"`
|
||||
IsReadOnly bool `json:"is_read_only"`
|
||||
IsRootDevice bool `json:"is_root_device"`
|
||||
PathOnHost string `json:"path_on_host"`
|
||||
DriveID string `json:"drive_id"`
|
||||
IsReadOnly bool `json:"is_read_only"`
|
||||
IsRootDevice bool `json:"is_root_device"`
|
||||
PathOnHost string `json:"path_on_host"`
|
||||
CacheType DriveCacheType `json:"cache_type,omitempty"`
|
||||
IOEngine DriveIOEngine `json:"io_engine,omitempty"`
|
||||
}
|
||||
|
||||
type entropyRequest struct{}
|
||||
|
|
@ -58,6 +60,13 @@ type networkInterfaceRequest struct {
|
|||
IfaceID string `json:"iface_id"`
|
||||
}
|
||||
|
||||
type mmdsConfigRequest struct {
|
||||
IPv4Address string `json:"ipv4_address,omitempty"`
|
||||
NetworkInterfaces []string `json:"network_interfaces"`
|
||||
Version MMDSVersion `json:"version,omitempty"`
|
||||
IMDSCompat bool `json:"imds_compat,omitempty"`
|
||||
}
|
||||
|
||||
type serialRequest struct {
|
||||
SerialOutPath string `json:"serial_out_path"`
|
||||
}
|
||||
|
|
@ -127,6 +136,24 @@ func (c *apiClient) PutNetworkInterface(ctx context.Context, network NetworkAllo
|
|||
return c.do(ctx, http.MethodPut, endpoint, body, nil, http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (c *apiClient) PutMMDSConfig(ctx context.Context, spec MMDSSpec) error {
|
||||
body := mmdsConfigRequest{
|
||||
IPv4Address: strings.TrimSpace(spec.IPv4Address),
|
||||
NetworkInterfaces: append([]string(nil), spec.NetworkInterfaces...),
|
||||
Version: spec.Version,
|
||||
IMDSCompat: spec.IMDSCompat,
|
||||
}
|
||||
return c.do(ctx, http.MethodPut, "/mmds/config", body, nil, http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (c *apiClient) PutMMDS(ctx context.Context, data any) error {
|
||||
return c.do(ctx, http.MethodPut, "/mmds", data, nil, http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (c *apiClient) PatchMMDS(ctx context.Context, data any) error {
|
||||
return c.do(ctx, http.MethodPatch, "/mmds", data, nil, http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (c *apiClient) PutSerial(ctx context.Context, serialOutPath string) error {
|
||||
return c.do(
|
||||
ctx,
|
||||
|
|
|
|||
|
|
@ -83,6 +83,91 @@ func TestConfigureMachineEnablesEntropyAndSerialBeforeStart(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestConfigureMachineConfiguresMMDSBeforeStart(t *testing.T) {
|
||||
var requests []capturedRequest
|
||||
|
||||
socketPath, shutdown := startUnixSocketServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("read request body: %v", err)
|
||||
}
|
||||
requests = append(requests, capturedRequest{
|
||||
Method: r.Method,
|
||||
Path: r.URL.Path,
|
||||
Body: string(body),
|
||||
})
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
})
|
||||
defer shutdown()
|
||||
|
||||
client := newAPIClient(socketPath)
|
||||
spec := MachineSpec{
|
||||
ID: "vm-2",
|
||||
VCPUs: 1,
|
||||
MemoryMiB: 512,
|
||||
KernelImagePath: "/kernel",
|
||||
RootFSPath: "/rootfs",
|
||||
RootDrive: DriveSpec{
|
||||
ID: "root_drive",
|
||||
Path: "/rootfs",
|
||||
CacheType: DriveCacheTypeUnsafe,
|
||||
IOEngine: DriveIOEngineSync,
|
||||
},
|
||||
MMDS: &MMDSSpec{
|
||||
NetworkInterfaces: []string{"net0"},
|
||||
Version: MMDSVersionV2,
|
||||
IPv4Address: "169.254.169.254",
|
||||
Data: map[string]any{
|
||||
"latest": map[string]any{
|
||||
"meta-data": map[string]any{
|
||||
"microagent": map[string]any{"hostname": "vm-2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
paths := machinePaths{JailedSerialLogPath: "/logs/serial.log"}
|
||||
network := NetworkAllocation{
|
||||
InterfaceID: defaultInterfaceID,
|
||||
TapName: "fctap0",
|
||||
GuestMAC: "06:00:ac:10:00:02",
|
||||
}
|
||||
|
||||
if err := configureMachine(context.Background(), client, paths, spec, network); err != nil {
|
||||
t.Fatalf("configure machine: %v", err)
|
||||
}
|
||||
|
||||
gotPaths := make([]string, 0, len(requests))
|
||||
for _, request := range requests {
|
||||
gotPaths = append(gotPaths, request.Path)
|
||||
}
|
||||
wantPaths := []string{
|
||||
"/machine-config",
|
||||
"/boot-source",
|
||||
"/drives/root_drive",
|
||||
"/network-interfaces/net0",
|
||||
"/mmds/config",
|
||||
"/mmds",
|
||||
"/entropy",
|
||||
"/serial",
|
||||
"/actions",
|
||||
}
|
||||
if len(gotPaths) != len(wantPaths) {
|
||||
t.Fatalf("request count mismatch: got %d want %d (%v)", len(gotPaths), len(wantPaths), gotPaths)
|
||||
}
|
||||
for i := range wantPaths {
|
||||
if gotPaths[i] != wantPaths[i] {
|
||||
t.Fatalf("request %d mismatch: got %q want %q", i, gotPaths[i], wantPaths[i])
|
||||
}
|
||||
}
|
||||
if requests[2].Body != "{\"drive_id\":\"root_drive\",\"is_read_only\":false,\"is_root_device\":true,\"path_on_host\":\"/rootfs\",\"cache_type\":\"Unsafe\",\"io_engine\":\"Sync\"}" {
|
||||
t.Fatalf("root drive body mismatch: got %q", requests[2].Body)
|
||||
}
|
||||
if requests[4].Body != "{\"ipv4_address\":\"169.254.169.254\",\"network_interfaces\":[\"net0\"],\"version\":\"V2\"}" {
|
||||
t.Fatalf("mmds config body mismatch: got %q", requests[4].Body)
|
||||
}
|
||||
}
|
||||
|
||||
func startUnixSocketServer(t *testing.T, handler http.HandlerFunc) (string, func()) {
|
||||
t.Helper()
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,16 @@ func configureMachine(ctx context.Context, client *apiClient, paths machinePaths
|
|||
if err := client.PutNetworkInterface(ctx, network); err != nil {
|
||||
return fmt.Errorf("put network interface: %w", err)
|
||||
}
|
||||
if spec.MMDS != nil {
|
||||
if err := client.PutMMDSConfig(ctx, *spec.MMDS); err != nil {
|
||||
return fmt.Errorf("put mmds config: %w", err)
|
||||
}
|
||||
if spec.MMDS.Data != nil {
|
||||
if err := client.PutMMDS(ctx, spec.MMDS.Data); err != nil {
|
||||
return fmt.Errorf("put mmds payload: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := client.PutEntropy(ctx); err != nil {
|
||||
return fmt.Errorf("put entropy device: %w", err)
|
||||
}
|
||||
|
|
@ -97,12 +107,14 @@ func stageMachineFiles(spec MachineSpec, paths machinePaths) (MachineSpec, error
|
|||
|
||||
rootFSPath, err := stagedFileName(spec.RootFSPath)
|
||||
if err != nil {
|
||||
return MachineSpec{}, fmt.Errorf("rootfs path: %w", err)
|
||||
return MachineSpec{}, fmt.Errorf("root drive path: %w", err)
|
||||
}
|
||||
if err := linkMachineFile(spec.RootFSPath, filepath.Join(paths.ChrootRootDir, rootFSPath)); err != nil {
|
||||
return MachineSpec{}, fmt.Errorf("link rootfs into jail: %w", err)
|
||||
return MachineSpec{}, fmt.Errorf("link root drive into jail: %w", err)
|
||||
}
|
||||
staged.RootFSPath = rootFSPath
|
||||
staged.RootDrive = spec.rootDrive()
|
||||
staged.RootDrive.Path = rootFSPath
|
||||
|
||||
staged.Drives = make([]DriveSpec, len(spec.Drives))
|
||||
for i, drive := range spec.Drives {
|
||||
|
|
@ -174,6 +186,8 @@ func additionalDriveRequests(spec MachineSpec) []driveRequest {
|
|||
IsReadOnly: drive.ReadOnly,
|
||||
IsRootDevice: false,
|
||||
PathOnHost: drive.Path,
|
||||
CacheType: drive.CacheType,
|
||||
IOEngine: drive.IOEngine,
|
||||
})
|
||||
}
|
||||
return requests
|
||||
|
|
@ -249,11 +263,14 @@ func linkMachineFile(source string, target string) error {
|
|||
}
|
||||
|
||||
func rootDriveRequest(spec MachineSpec) driveRequest {
|
||||
root := spec.rootDrive()
|
||||
return driveRequest{
|
||||
DriveID: defaultRootDriveID,
|
||||
IsReadOnly: false,
|
||||
DriveID: root.ID,
|
||||
IsReadOnly: root.ReadOnly,
|
||||
IsRootDevice: true,
|
||||
PathOnHost: spec.RootFSPath,
|
||||
PathOnHost: root.Path,
|
||||
CacheType: root.CacheType,
|
||||
IOEngine: root.IOEngine,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,16 +16,50 @@ type MachineSpec struct {
|
|||
MemoryMiB int64
|
||||
KernelImagePath string
|
||||
RootFSPath string
|
||||
RootDrive DriveSpec
|
||||
KernelArgs string
|
||||
Drives []DriveSpec
|
||||
MMDS *MMDSSpec
|
||||
Vsock *VsockSpec
|
||||
}
|
||||
|
||||
// DriveSpec describes an additional guest block device.
|
||||
type DriveSpec struct {
|
||||
ID string
|
||||
Path string
|
||||
ReadOnly bool
|
||||
ID string
|
||||
Path string
|
||||
ReadOnly bool
|
||||
CacheType DriveCacheType
|
||||
IOEngine DriveIOEngine
|
||||
}
|
||||
|
||||
type DriveCacheType string
|
||||
|
||||
const (
|
||||
DriveCacheTypeUnsafe DriveCacheType = "Unsafe"
|
||||
DriveCacheTypeWriteback DriveCacheType = "Writeback"
|
||||
)
|
||||
|
||||
type DriveIOEngine string
|
||||
|
||||
const (
|
||||
DriveIOEngineSync DriveIOEngine = "Sync"
|
||||
DriveIOEngineAsync DriveIOEngine = "Async"
|
||||
)
|
||||
|
||||
type MMDSVersion string
|
||||
|
||||
const (
|
||||
MMDSVersionV1 MMDSVersion = "V1"
|
||||
MMDSVersionV2 MMDSVersion = "V2"
|
||||
)
|
||||
|
||||
// MMDSSpec describes the MMDS network configuration and initial payload.
|
||||
type MMDSSpec struct {
|
||||
NetworkInterfaces []string
|
||||
Version MMDSVersion
|
||||
IPv4Address string
|
||||
IMDSCompat bool
|
||||
Data any
|
||||
}
|
||||
|
||||
// VsockSpec describes a single host-guest vsock device.
|
||||
|
|
@ -49,17 +83,22 @@ func (s MachineSpec) Validate() error {
|
|||
if strings.TrimSpace(s.KernelImagePath) == "" {
|
||||
return fmt.Errorf("machine kernel image path is required")
|
||||
}
|
||||
if strings.TrimSpace(s.RootFSPath) == "" {
|
||||
return fmt.Errorf("machine rootfs path is required")
|
||||
}
|
||||
if filepath.Base(strings.TrimSpace(string(s.ID))) != strings.TrimSpace(string(s.ID)) {
|
||||
return fmt.Errorf("machine id %q must not contain path separators", s.ID)
|
||||
}
|
||||
if err := s.rootDrive().Validate(); err != nil {
|
||||
return fmt.Errorf("root drive: %w", err)
|
||||
}
|
||||
for i, drive := range s.Drives {
|
||||
if err := drive.Validate(); err != nil {
|
||||
return fmt.Errorf("drive %d: %w", i, err)
|
||||
}
|
||||
}
|
||||
if s.MMDS != nil {
|
||||
if err := s.MMDS.Validate(); err != nil {
|
||||
return fmt.Errorf("mmds: %w", err)
|
||||
}
|
||||
}
|
||||
if s.Vsock != nil {
|
||||
if err := s.Vsock.Validate(); err != nil {
|
||||
return fmt.Errorf("vsock: %w", err)
|
||||
|
|
@ -70,11 +109,39 @@ func (s MachineSpec) Validate() error {
|
|||
|
||||
// Validate reports whether the drive specification is usable.
|
||||
func (d DriveSpec) Validate() error {
|
||||
if strings.TrimSpace(d.Path) == "" {
|
||||
return fmt.Errorf("drive path is required")
|
||||
}
|
||||
if strings.TrimSpace(d.ID) == "" {
|
||||
return fmt.Errorf("drive id is required")
|
||||
}
|
||||
if strings.TrimSpace(d.Path) == "" {
|
||||
return fmt.Errorf("drive path is required")
|
||||
switch d.CacheType {
|
||||
case "", DriveCacheTypeUnsafe, DriveCacheTypeWriteback:
|
||||
default:
|
||||
return fmt.Errorf("unsupported drive cache type %q", d.CacheType)
|
||||
}
|
||||
switch d.IOEngine {
|
||||
case "", DriveIOEngineSync, DriveIOEngineAsync:
|
||||
default:
|
||||
return fmt.Errorf("unsupported drive io engine %q", d.IOEngine)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate reports whether the MMDS configuration is usable.
|
||||
func (m MMDSSpec) Validate() error {
|
||||
if len(m.NetworkInterfaces) == 0 {
|
||||
return fmt.Errorf("mmds network interfaces are required")
|
||||
}
|
||||
switch m.Version {
|
||||
case "", MMDSVersionV1, MMDSVersionV2:
|
||||
default:
|
||||
return fmt.Errorf("unsupported mmds version %q", m.Version)
|
||||
}
|
||||
for i, iface := range m.NetworkInterfaces {
|
||||
if strings.TrimSpace(iface) == "" {
|
||||
return fmt.Errorf("mmds network_interfaces[%d] is required", i)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -92,3 +159,14 @@ func (v VsockSpec) Validate() error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s MachineSpec) rootDrive() DriveSpec {
|
||||
root := s.RootDrive
|
||||
if strings.TrimSpace(root.ID) == "" {
|
||||
root.ID = defaultRootDriveID
|
||||
}
|
||||
if strings.TrimSpace(root.Path) == "" {
|
||||
root.Path = s.RootFSPath
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue