diff --git a/Makefile b/Makefile index cd7f0189..4bfb34a2 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ JAILER_BIN=$(FC_TEST_DATA_PATH)/jailer-main UID = $(shell id -u) GID = $(shell id -g) -firecracker_version=v1.0.0 +firecracker_version=v1.4.0 # The below files are needed and can be downloaded from the internet release_url=https://github.com/firecracker-microvm/firecracker/releases/download/$(firecracker_version)/firecracker-$(firecracker_version)-$(arch).tgz diff --git a/client/models/memory_backend.go b/client/models/memory_backend.go new file mode 100644 index 00000000..c0a58b81 --- /dev/null +++ b/client/models/memory_backend.go @@ -0,0 +1,131 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file 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 models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "encoding/json" + + strfmt "github.com/go-openapi/strfmt" + + "github.com/go-openapi/errors" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// MemoryBackend memory backend +// swagger:model MemoryBackend +type MemoryBackend struct { + + // Based on 'backend_type' it is either 1) Path to the file that contains the guest memory to be loaded 2) Path to the UDS where a process is listening for a UFFD initialization control payload and open file descriptor that it can use to serve this process's guest memory page faults + // Required: true + BackendPath *string `json:"backend_path"` + + // backend type + // Required: true + // Enum: [File Uffd] + BackendType *string `json:"backend_type"` +} + +// Validate validates this memory backend +func (m *MemoryBackend) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateBackendPath(formats); err != nil { + res = append(res, err) + } + + if err := m.validateBackendType(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MemoryBackend) validateBackendPath(formats strfmt.Registry) error { + + if err := validate.Required("backend_path", "body", m.BackendPath); err != nil { + return err + } + + return nil +} + +var memoryBackendTypeBackendTypePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["File","Uffd"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + memoryBackendTypeBackendTypePropEnum = append(memoryBackendTypeBackendTypePropEnum, v) + } +} + +const ( + + // MemoryBackendBackendTypeFile captures enum value "File" + MemoryBackendBackendTypeFile string = "File" + + // MemoryBackendBackendTypeUffd captures enum value "Uffd" + MemoryBackendBackendTypeUffd string = "Uffd" +) + +// prop value enum +func (m *MemoryBackend) validateBackendTypeEnum(path, location string, value string) error { + if err := validate.Enum(path, location, value, memoryBackendTypeBackendTypePropEnum); err != nil { + return err + } + return nil +} + +func (m *MemoryBackend) validateBackendType(formats strfmt.Registry) error { + + if err := validate.Required("backend_type", "body", m.BackendType); err != nil { + return err + } + + // value enum + if err := m.validateBackendTypeEnum("backend_type", "body", *m.BackendType); err != nil { + return err + } + + return nil +} + +// MarshalBinary interface implementation +func (m *MemoryBackend) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *MemoryBackend) UnmarshalBinary(b []byte) error { + var res MemoryBackend + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/client/models/snapshot_load_params.go b/client/models/snapshot_load_params.go index 2e4cf91d..b16beaec 100644 --- a/client/models/snapshot_load_params.go +++ b/client/models/snapshot_load_params.go @@ -26,16 +26,18 @@ import ( "github.com/go-openapi/validate" ) -// SnapshotLoadParams snapshot load params +// SnapshotLoadParams Defines the configuration used for handling snapshot resume. Exactly one of the two `mem_*` fields must be present in the body of the request. // swagger:model SnapshotLoadParams type SnapshotLoadParams struct { // Enable support for incremental (diff) snapshots by tracking dirty guest pages. EnableDiffSnapshots bool `json:"enable_diff_snapshots,omitempty"` - // Path to the file that contains the guest memory to be loaded. - // Required: true - MemFilePath *string `json:"mem_file_path"` + // Configuration for the backend that handles memory load. If this field is specified, `mem_file_path` is forbidden. Either `mem_backend` or `mem_file_path` must be present at a time. + MemBackend *MemoryBackend `json:"mem_backend,omitempty"` + + // Path to the file that contains the guest memory to be loaded. This parameter has been deprecated and is only allowed if `mem_backend` is not present. + MemFilePath string `json:"mem_file_path,omitempty"` // When set to true, the vm is also resumed if the snapshot load is successful. ResumeVM bool `json:"resume_vm,omitempty"` @@ -49,7 +51,7 @@ type SnapshotLoadParams struct { func (m *SnapshotLoadParams) Validate(formats strfmt.Registry) error { var res []error - if err := m.validateMemFilePath(formats); err != nil { + if err := m.validateMemBackend(formats); err != nil { res = append(res, err) } @@ -63,10 +65,19 @@ func (m *SnapshotLoadParams) Validate(formats strfmt.Registry) error { return nil } -func (m *SnapshotLoadParams) validateMemFilePath(formats strfmt.Registry) error { +func (m *SnapshotLoadParams) validateMemBackend(formats strfmt.Registry) error { - if err := validate.Required("mem_file_path", "body", m.MemFilePath); err != nil { - return err + if swag.IsZero(m.MemBackend) { // not required + return nil + } + + if m.MemBackend != nil { + if err := m.MemBackend.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("mem_backend") + } + return err + } } return nil diff --git a/client/swagger.yaml b/client/swagger.yaml index b5361749..cf621895 100644 --- a/client/swagger.yaml +++ b/client/swagger.yaml @@ -953,6 +953,25 @@ definitions: maximum: 32 description: Number of vCPUs (either 1 or an even number) + MemoryBackend: + type: object + required: + - backend_type + - backend_path + properties: + backend_type: + type: string + enum: + - File + - Uffd + backend_path: + type: string + description: Based on 'backend_type' it is either + 1) Path to the file that contains the guest memory to be loaded + 2) Path to the UDS where a process is listening for a UFFD initialization + control payload and open file descriptor that it can use to serve this + process's guest memory page faults + Metrics: type: object description: @@ -1090,8 +1109,10 @@ definitions: SnapshotLoadParams: type: object + description: + Defines the configuration used for handling snapshot resume. Exactly one of + the two `mem_*` fields must be present in the body of the request. required: - - mem_file_path - snapshot_path properties: enable_diff_snapshots: @@ -1100,7 +1121,16 @@ definitions: Enable support for incremental (diff) snapshots by tracking dirty guest pages. mem_file_path: type: string - description: Path to the file that contains the guest memory to be loaded. + description: + Path to the file that contains the guest memory to be loaded. + This parameter has been deprecated and is only allowed if + `mem_backend` is not present. + mem_backend: + $ref: "#/definitions/MemoryBackend" + description: + Configuration for the backend that handles memory load. If this field + is specified, `mem_file_path` is forbidden. Either `mem_backend` or + `mem_file_path` must be present at a time. snapshot_path: type: string description: Path to the file that contains the microVM state to be loaded. diff --git a/machine.go b/machine.go index 56fa3e25..a812b986 100644 --- a/machine.go +++ b/machine.go @@ -172,7 +172,7 @@ type Config struct { } func (cfg *Config) hasSnapshot() bool { - return cfg.Snapshot.MemFilePath != "" || cfg.Snapshot.SnapshotPath != "" + return cfg.Snapshot.GetMemBackendPath() != "" || cfg.Snapshot.SnapshotPath != "" } // Validate will ensure that the required fields are set and that @@ -235,7 +235,7 @@ func (cfg *Config) ValidateLoadSnapshot() error { return fmt.Errorf("socket %s already exists", cfg.SocketPath) } - if _, err := os.Stat(cfg.Snapshot.MemFilePath); err != nil { + if _, err := os.Stat(cfg.Snapshot.GetMemBackendPath()); err != nil { return err } @@ -649,7 +649,7 @@ func (m *Machine) startVMM(ctx context.Context) error { return nil } -//StopVMM stops the current VMM. +// StopVMM stops the current VMM. func (m *Machine) StopVMM() error { return m.stopVMM() } @@ -1171,7 +1171,8 @@ func (m *Machine) CreateSnapshot(ctx context.Context, memFilePath, snapshotPath // loadSnapshot loads a snapshot of the VM func (m *Machine) loadSnapshot(ctx context.Context, snapshot *SnapshotConfig) error { snapshotParams := &models.SnapshotLoadParams{ - MemFilePath: &snapshot.MemFilePath, + MemFilePath: snapshot.MemFilePath, + MemBackend: snapshot.MemBackend, SnapshotPath: &snapshot.SnapshotPath, EnableDiffSnapshots: snapshot.EnableDiffSnapshots, ResumeVM: snapshot.ResumeVM, diff --git a/machine_test.go b/machine_test.go index 34615fa4..b8d7b502 100644 --- a/machine_test.go +++ b/machine_test.go @@ -2125,6 +2125,61 @@ func TestLoadSnapshot(t *testing.T) { require.NoError(t, err) }, }, + { + name: "TestLoadSnapshotWithMemoryBackend", + createSnapshot: func(ctx context.Context, machineLogger *logrus.Logger, socketPath, memPath, snapPath string) { + // Create a snapshot + cfg := createValidConfig(t, socketPath+".create") + m, err := NewMachine(ctx, cfg, func(m *Machine) { + // Rewriting m.cmd partially wouldn't work since Cmd has + // some unexported members + args := m.cmd.Args[1:] + m.cmd = exec.Command(getFirecrackerBinaryPath(), args...) + }, WithLogger(logrus.NewEntry(machineLogger))) + require.NoError(t, err) + + err = m.Start(ctx) + require.NoError(t, err) + + err = m.PauseVM(ctx) + require.NoError(t, err) + + err = m.CreateSnapshot(ctx, memPath, snapPath) + require.NoError(t, err) + + err = m.StopVMM() + require.NoError(t, err) + }, + + loadSnapshot: func(ctx context.Context, machineLogger *logrus.Logger, socketPath, memPath, snapPath string) { + // Note that many fields are not necessary when loading a snapshot + cfg := Config{ + SocketPath: socketPath + ".load", + Drives: []models.Drive{ + { + DriveID: String("root"), + IsRootDevice: Bool(true), + IsReadOnly: Bool(true), + PathOnHost: String(testRootfs), + }, + }, + } + + m, err := NewMachine(ctx, cfg, func(m *Machine) { + // Rewriting m.cmd partially wouldn't work since Cmd has + // some unexported members + args := m.cmd.Args[1:] + m.cmd = exec.Command(getFirecrackerBinaryPath(), args...) + }, WithLogger(logrus.NewEntry(machineLogger)), WithSnapshot("", snapPath, WithMemoryBackend("File", memPath))) + require.NoError(t, err) + + err = m.Start(ctx) + require.NoError(t, err) + + err = m.StopVMM() + require.NoError(t, err) + }, + }, { name: "TestLoadSnapshot without create", createSnapshot: func(ctx context.Context, machineLogger *logrus.Logger, socketPath, memPath, snapPath string) { diff --git a/opts.go b/opts.go index b6d9c1d0..c3a70caf 100644 --- a/opts.go +++ b/opts.go @@ -16,6 +16,7 @@ package firecracker import ( "os/exec" + "github.com/firecracker-microvm/firecracker-go-sdk/client/models" "github.com/sirupsen/logrus" ) @@ -53,6 +54,14 @@ func WithProcessRunner(cmd *exec.Cmd) Opt { type WithSnapshotOpt func(*SnapshotConfig) // WithSnapshot will allow for the machine to start using a given snapshot. +// +// If using the UFFD memory backend, the memFilePath may be empty (it is +// ignored), and instead the UFFD socket should be specified using +// MemoryBackendType, as in the following example: +// +// WithSnapshot( +// "", snapshotPath, +// WithMemoryBackend(models.MemoryBackendBackendTypeUffd, "uffd.sock")) func WithSnapshot(memFilePath, snapshotPath string, opts ...WithSnapshotOpt) Opt { return func(m *Machine) { m.Cfg.Snapshot.MemFilePath = memFilePath @@ -66,3 +75,18 @@ func WithSnapshot(memFilePath, snapshotPath string, opts ...WithSnapshotOpt) Opt m.Handlers.FcInit = loadSnapshotHandlerList } } + +// WithMemoryBackend sets the memory backend to the given type, using the given +// backing file path (a regular file for "File" type, or a UFFD socket path for +// "Uffd" type). +// +// Note that if MemFilePath is already configured for the snapshot config, it +// will be ignored, and the backendPath specified here will be used instead. +func WithMemoryBackend(backendType, backendPath string) WithSnapshotOpt { + return func(cfg *SnapshotConfig) { + cfg.MemBackend = &models.MemoryBackend{ + BackendType: String(backendType), + BackendPath: String(backendPath), + } + } +} diff --git a/snapshot.go b/snapshot.go index eefdc47b..e27cf497 100644 --- a/snapshot.go +++ b/snapshot.go @@ -13,9 +13,21 @@ package firecracker +import "github.com/firecracker-microvm/firecracker-go-sdk/client/models" + type SnapshotConfig struct { MemFilePath string + MemBackend *models.MemoryBackend SnapshotPath string EnableDiffSnapshots bool ResumeVM bool } + +// GetMemBackendPath returns the effective memory backend path. If MemBackend +// is not set, then MemFilePath from SnapshotConfig will be returned. +func (cfg *SnapshotConfig) GetMemBackendPath() string { + if cfg.MemBackend != nil && cfg.MemBackend.BackendPath != nil { + return *cfg.MemBackend.BackendPath + } + return cfg.MemFilePath +}