diff --git a/firecracker.go b/firecracker.go index 20ab74c3..3102400a 100644 --- a/firecracker.go +++ b/firecracker.go @@ -75,6 +75,22 @@ func NewClient(socketPath string, logger *logrus.Entry, debug bool, opts ...Clie return c } +// GetFirecrackerVersionOpt is a functional option to be used for the +// GetFirecrackerVersion API in setting any additional optional fields. +type GetFirecrackerVersionOpt func(*ops.GetFirecrackerVersionParams) + +// GetFirecrackerVersion is a wrapper for the swagger generated client to make +// calling of the API easier. +func (f *Client) GetFirecrackerVersion(ctx context.Context, opts ...GetFirecrackerVersionOpt) (*ops.GetFirecrackerVersionOK, error) { + params := ops.NewGetFirecrackerVersionParams() + params.SetContext(ctx) + for _, opt := range opts { + opt(params) + } + + return f.client.Operations.GetFirecrackerVersion(params) +} + // PutLoggerOpt is a functional option to be used for the PutLogger API in // setting any additional optional fields. type PutLoggerOpt func(*ops.PutLoggerParams) diff --git a/firecracker_test.go b/firecracker_test.go index 50434650..32647482 100644 --- a/firecracker_test.go +++ b/firecracker_test.go @@ -20,6 +20,7 @@ import ( models "github.com/firecracker-microvm/firecracker-go-sdk/client/models" "github.com/firecracker-microvm/firecracker-go-sdk/fctesting" + "github.com/stretchr/testify/require" ) func TestClient(t *testing.T) { @@ -64,3 +65,38 @@ func TestClient(t *testing.T) { t.Errorf("unexpected error on PutGuestDriveByID, %v", err) } } + +func TestGetFirecrackerVersion(t *testing.T) { + if testing.Short() { + t.Skip() + } + + ctx := context.Background() + socketpath, cleanup := makeSocketPath(t) + defer cleanup() + + cmd := VMCommandBuilder{}. + WithBin(getFirecrackerBinaryPath()). + WithSocketPath(socketpath). + Build(ctx) + + if err := cmd.Start(); err != nil { + t.Fatalf("failed to start firecracker vmm: %v", err) + } + + defer func() { + if err := cmd.Process.Kill(); err != nil { + t.Errorf("failed to kill process: %v", err) + } + }() + + client := NewClient(socketpath, fctesting.NewLogEntry(t), true) + deadlineCtx, deadlineCancel := context.WithTimeout(ctx, 250*time.Millisecond) + defer deadlineCancel() + if err := waitForAliveVMM(deadlineCtx, client); err != nil { + t.Fatal(err) + } + + _, err := client.GetFirecrackerVersion(ctx) + require.NoError(t, err, "failed to get firecracker version") +} diff --git a/machine.go b/machine.go index af55a2b9..31f85dba 100644 --- a/machine.go +++ b/machine.go @@ -429,6 +429,18 @@ func (m *Machine) Wait(ctx context.Context) error { } } +// GetFirecrackerVersion gets the machine's firecracker version and returns it +func (m *Machine) GetFirecrackerVersion(ctx context.Context) (string, error) { + resp, err := m.client.GetFirecrackerVersion(ctx) + if err != nil { + m.logger.Errorf("Getting firecracker version: %s", err) + return "", err + } + + m.logger.Debug("GetFirecrackerVersion successful") + return *resp.Payload.FirecrackerVersion, nil +} + func (m *Machine) setupNetwork(ctx context.Context) error { err, cleanupFuncs := m.Cfg.NetworkInterfaces.setupNetwork(ctx, m.Cfg.VMID, m.Cfg.NetNS, m.logger) m.cleanupFuncs = append(m.cleanupFuncs, cleanupFuncs...) diff --git a/machine_test.go b/machine_test.go index 4f9779bf..5a72571c 100644 --- a/machine_test.go +++ b/machine_test.go @@ -18,6 +18,7 @@ import ( "context" "errors" "flag" + "fmt" "io" "io/ioutil" "net" @@ -25,6 +26,7 @@ import ( "os/exec" "os/signal" "path/filepath" + "regexp" "strconv" "strings" "sync" @@ -366,6 +368,7 @@ func TestMicroVMExecution(t *testing.T) { } t.Run("TestCreateMachine", func(t *testing.T) { testCreateMachine(ctx, t, m) }) + t.Run("TestGetFirecrackerVersion", func(t *testing.T) { testGetFirecrackerVersion(ctx, t, m) }) t.Run("TestMachineConfigApplication", func(t *testing.T) { testMachineConfigApplication(ctx, t, m, cfg) }) t.Run("TestCreateBootSource", func(t *testing.T) { testCreateBootSource(ctx, t, m, vmlinuxPath) }) t.Run("TestCreateNetworkInterface", func(t *testing.T) { testCreateNetworkInterfaceByID(ctx, t, m) }) @@ -620,6 +623,47 @@ func testCreateMachine(ctx context.Context, t *testing.T, m *Machine) { } } +func parseVersionFromStdout(stdout []byte) (string, error) { + pattern := regexp.MustCompile(`Firecracker v(?P[0-9]\.[0-9]\.[0-9]-?.*)`) + groupNames := pattern.SubexpNames() + matches := pattern.FindStringSubmatch(string(stdout)) + + for i, name := range groupNames { + if name == "version" { + return matches[i], nil + } + } + + return "", fmt.Errorf("Unable to parse firecracker version from stdout (Output: %s)", + stdout) +} + +func getFirecrackerVersion() (string, error) { + cmd := exec.Command(getFirecrackerBinaryPath(), "--version") + stdout, err := cmd.Output() + if err != nil { + return "", err + } + return parseVersionFromStdout(stdout) +} + +func testGetFirecrackerVersion(ctx context.Context, t *testing.T, m *Machine) { + version, err := m.GetFirecrackerVersion(ctx) + + if err != nil { + t.Errorf("GetFirecrackerVersion: %v", err) + } + + expectedVersion, err := getFirecrackerVersion() + if err != nil { + t.Errorf("GetFirecrackerVersion: %v", err) + } + + assert.Equalf(t, expectedVersion, version, + "GetFirecrackerVersion: Expected version %v, got version %v", + expectedVersion, version) +} + func testMachineConfigApplication(ctx context.Context, t *testing.T, m *Machine, expectedValues Config) { assert.Equal(t, expectedValues.MachineCfg.VcpuCount, m.machineConfig.VcpuCount, "CPU count should be equal")