From af15b3c34afe7ba84af113a821736d8da3c70adc Mon Sep 17 00:00:00 2001 From: naman79820 Date: Fri, 10 Jul 2026 08:16:05 +0000 Subject: [PATCH] fix(firecracker): omit vsock device from config when vAccel is unused The vsock config field was a struct value rather than a pointer, so it was always serialized into the generated Firecracker config even when vAccel was not in use, producing a vsock device with a zero guest CID and an empty socket path. Go's JSON encoder only drops empty pointers, slices and maps, not zero-value structs. Make the field a pointer and populate it only in the vAccel branch, so the device is emitted solely when vsock-based vAccel is requested. Also add a regression test asserting the config omits vsock without vAccel. Fixes: #881 Signed-off-by: naman79820 --- pkg/unikontainers/hypervisors/firecracker.go | 6 +- .../hypervisors/firecracker_test.go | 97 +++++++++++++++++++ 2 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 pkg/unikontainers/hypervisors/firecracker_test.go diff --git a/pkg/unikontainers/hypervisors/firecracker.go b/pkg/unikontainers/hypervisors/firecracker.go index 9588a45b..0f440d4f 100644 --- a/pkg/unikontainers/hypervisors/firecracker.go +++ b/pkg/unikontainers/hypervisors/firecracker.go @@ -73,7 +73,7 @@ type FirecrackerConfig struct { Machine FirecrackerMachine `json:"machine-config"` Drives []FirecrackerDrive `json:"drives"` NetIfs []FirecrackerNet `json:"network-interfaces,omitempty"` - VSock FirecrackerVSockDev `json:"vsock,omitempty"` + VSock *FirecrackerVSockDev `json:"vsock,omitempty"` } func (fc *Firecracker) Signal(pid int, signal unix.Signal) error { @@ -175,9 +175,9 @@ func (fc *Firecracker) BuildExecCmd(args types.ExecArgs, ukernel types.Unikernel InitrdPath: initrdPath, } - var FCVSockDev FirecrackerVSockDev + var FCVSockDev *FirecrackerVSockDev if args.VAccelType == "vsock" { - FCVSockDev = FirecrackerVSockDev{ + FCVSockDev = &FirecrackerVSockDev{ GuestCID: args.VSockDevID, UDSPath: args.VSockDevPath + "/vaccel.sock", VSockID: "root", diff --git a/pkg/unikontainers/hypervisors/firecracker_test.go b/pkg/unikontainers/hypervisors/firecracker_test.go new file mode 100644 index 00000000..578dc349 --- /dev/null +++ b/pkg/unikontainers/hypervisors/firecracker_test.go @@ -0,0 +1,97 @@ +// Copyright (c) 2023-2026, Nubificus LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package hypervisors + +import ( + "encoding/json" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/urunc-dev/urunc/pkg/unikontainers/types" +) + +const testFCBinary = "/usr/bin/firecracker" + +// configPathFromArgs pulls the JSON config path out of the args returned by +// BuildExecCmd (the element right after "--config-file"). +func configPathFromArgs(t *testing.T, args []string) string { + t.Helper() + for i, a := range args { + if a == "--config-file" && i+1 < len(args) { + return args[i+1] + } + } + t.Fatalf("no --config-file argument in %v", args) + return "" +} + +// TestFirecrackerBuildExecCmdVSock verifies that the generated Firecracker +// config only contains a "vsock" device when vAccel/vsock is requested. The +// field is tagged omitempty, so without vAccel the key must be absent. +func TestFirecrackerBuildExecCmdVSock(t *testing.T) { + // Not parallel: BuildExecCmd writes to a fixed /tmp path. + tests := []struct { + name string + args types.ExecArgs + wantVSock bool + }{ + { + name: "no vAccel omits the vsock device", + args: types.ExecArgs{ + UnikernelPath: testKernelPath, + Command: testCommand, + }, + wantVSock: false, + }, + { + name: "vAccel vsock includes the vsock device", + args: types.ExecArgs{ + UnikernelPath: testKernelPath, + Command: testCommand, + VAccelType: "vsock", + VSockDevID: 42, + VSockDevPath: "/run/vaccel", + }, + wantVSock: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fc := &Firecracker{binary: FirecrackerBinary, binaryPath: testFCBinary} + out, err := fc.BuildExecCmd(tt.args, &fakeUnikernel{}) + assert.NoError(t, err) + + cfgPath := configPathFromArgs(t, out) + t.Cleanup(func() { _ = os.Remove(cfgPath) }) + + data, err := os.ReadFile(cfgPath) + assert.NoError(t, err) + + var cfg map[string]json.RawMessage + assert.NoError(t, json.Unmarshal(data, &cfg)) + + _, present := cfg["vsock"] + assert.Equal(t, tt.wantVSock, present, + "unexpected vsock presence in generated config: %s", string(data)) + + if tt.wantVSock { + assert.Contains(t, string(cfg["vsock"]), `"guest_cid":42`, + "vsock device should carry the configured guest CID") + } + }) + } +}