diff --git a/Gopkg.lock b/Gopkg.lock index 8587e56ca3..d36d090841 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -204,7 +204,7 @@ version = "v0.3.3" [[projects]] - digest = "1:61eec5f342089feaaa1690354a62102a90a64dcf7bbc4c6d840dc5edd66ae451" + digest = "1:58b18038f51b6f79865fd00e3d883949c9507ede577cd9c4e743ed56cc454122" name = "github.com/firecracker-microvm/firecracker-go-sdk" packages = [ ".", @@ -213,7 +213,7 @@ "client/operations", ] pruneopts = "NUT" - revision = "840c1e37f5f2bbcbff1fdbfcfcea09d0bf158977" + revision = "961461227bddf7e40a1d690634e866c343910f86" [[projects]] branch = "master" diff --git a/Gopkg.toml b/Gopkg.toml index 9313c1ece3..4af6a00602 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -72,7 +72,7 @@ [[constraint]] name = "github.com/firecracker-microvm/firecracker-go-sdk" - revision = "840c1e37f5f2bbcbff1fdbfcfcea09d0bf158977" + revision = "961461227bddf7e40a1d690634e866c343910f86" [[override]] branch = "master" diff --git a/vendor/github.com/firecracker-microvm/firecracker-go-sdk/client/models/device_state.go b/vendor/github.com/firecracker-microvm/firecracker-go-sdk/client/models/device_state.go deleted file mode 100644 index e94e747cc5..0000000000 --- a/vendor/github.com/firecracker-microvm/firecracker-go-sdk/client/models/device_state.go +++ /dev/null @@ -1,73 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -// Copyright 2018 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 client_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/validate" -) - -// DeviceState The valid states for a Device. So far, a device can only be in the Attached mode. Future valid values will be Detaching and Detached. -// swagger:model DeviceState -type DeviceState string - -const ( - - // DeviceStateAttached captures enum value "Attached" - DeviceStateAttached DeviceState = "Attached" -) - -// for schema -var deviceStateEnum []interface{} - -func init() { - var res []DeviceState - if err := json.Unmarshal([]byte(`["Attached"]`), &res); err != nil { - panic(err) - } - for _, v := range res { - deviceStateEnum = append(deviceStateEnum, v) - } -} - -func (m DeviceState) validateDeviceStateEnum(path, location string, value DeviceState) error { - if err := validate.Enum(path, location, value, deviceStateEnum); err != nil { - return err - } - return nil -} - -// Validate validates this device state -func (m DeviceState) Validate(formats strfmt.Registry) error { - var res []error - - // value enum - if err := m.validateDeviceStateEnum("", "body", m); err != nil { - return err - } - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} diff --git a/vendor/github.com/firecracker-microvm/firecracker-go-sdk/client/models/logger.go b/vendor/github.com/firecracker-microvm/firecracker-go-sdk/client/models/logger.go index 2d8bce4744..44ffa1c540 100644 --- a/vendor/github.com/firecracker-microvm/firecracker-go-sdk/client/models/logger.go +++ b/vendor/github.com/firecracker-microvm/firecracker-go-sdk/client/models/logger.go @@ -42,6 +42,9 @@ type Logger struct { // The named pipe where the JSON-formatted metrics will be flushed. MetricsFifo string `json:"metrics_fifo,omitempty"` + // Additional logging options. Only "LogDirtyPages" is supported. + Options []string `json:"options"` + // Whether or not to output the level in the logs. ShowLevel bool `json:"show_level,omitempty"` diff --git a/vendor/github.com/firecracker-microvm/firecracker-go-sdk/client/models/network_interface.go b/vendor/github.com/firecracker-microvm/firecracker-go-sdk/client/models/network_interface.go index 957b78a44b..ba062af3b0 100644 --- a/vendor/github.com/firecracker-microvm/firecracker-go-sdk/client/models/network_interface.go +++ b/vendor/github.com/firecracker-microvm/firecracker-go-sdk/client/models/network_interface.go @@ -46,9 +46,6 @@ type NetworkInterface struct { // rx rate limiter RxRateLimiter *RateLimiter `json:"rx_rate_limiter,omitempty"` - // state - State DeviceState `json:"state,omitempty"` - // tx rate limiter TxRateLimiter *RateLimiter `json:"tx_rate_limiter,omitempty"` } @@ -65,10 +62,6 @@ func (m *NetworkInterface) Validate(formats strfmt.Registry) error { res = append(res, err) } - if err := m.validateState(formats); err != nil { - res = append(res, err) - } - if err := m.validateTxRateLimiter(formats); err != nil { res = append(res, err) } @@ -106,22 +99,6 @@ func (m *NetworkInterface) validateRxRateLimiter(formats strfmt.Registry) error return nil } -func (m *NetworkInterface) validateState(formats strfmt.Registry) error { - - if swag.IsZero(m.State) { // not required - return nil - } - - if err := m.State.Validate(formats); err != nil { - if ve, ok := err.(*errors.Validation); ok { - return ve.ValidateName("state") - } - return err - } - - return nil -} - func (m *NetworkInterface) validateTxRateLimiter(formats strfmt.Registry) error { if swag.IsZero(m.TxRateLimiter) { // not required diff --git a/vendor/github.com/firecracker-microvm/firecracker-go-sdk/client/operations/operations_client.go b/vendor/github.com/firecracker-microvm/firecracker-go-sdk/client/operations/operations_client.go index 13be0eb596..4a2bacd8f4 100644 --- a/vendor/github.com/firecracker-microvm/firecracker-go-sdk/client/operations/operations_client.go +++ b/vendor/github.com/firecracker-microvm/firecracker-go-sdk/client/operations/operations_client.go @@ -126,7 +126,7 @@ func (a *Client) PatchMmds(params *PatchMmdsParams) (*PatchMmdsNoContent, error) /* PutMmds creates a m m d s microvm metadata service data store */ -func (a *Client) PutMmds(params *PutMmdsParams) (*PutMmdsCreated, *PutMmdsNoContent, error) { +func (a *Client) PutMmds(params *PutMmdsParams) (*PutMmdsNoContent, error) { // TODO: Validate the params before sending if params == nil { params = NewPutMmdsParams() @@ -145,15 +145,9 @@ func (a *Client) PutMmds(params *PutMmdsParams) (*PutMmdsCreated, *PutMmdsNoCont Client: params.HTTPClient, }) if err != nil { - return nil, nil, err - } - switch value := result.(type) { - case *PutMmdsCreated: - return value, nil, nil - case *PutMmdsNoContent: - return nil, value, nil + return nil, err } - return nil, nil, nil + return result.(*PutMmdsNoContent), nil } diff --git a/vendor/github.com/firecracker-microvm/firecracker-go-sdk/client/operations/put_mmds_responses.go b/vendor/github.com/firecracker-microvm/firecracker-go-sdk/client/operations/put_mmds_responses.go index 1355653fee..0d5b415ca0 100644 --- a/vendor/github.com/firecracker-microvm/firecracker-go-sdk/client/operations/put_mmds_responses.go +++ b/vendor/github.com/firecracker-microvm/firecracker-go-sdk/client/operations/put_mmds_responses.go @@ -38,13 +38,6 @@ type PutMmdsReader struct { func (o *PutMmdsReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) { switch response.Code() { - case 201: - result := NewPutMmdsCreated() - if err := result.readResponse(response, consumer, o.formats); err != nil { - return nil, err - } - return result, nil - case 204: result := NewPutMmdsNoContent() if err := result.readResponse(response, consumer, o.formats); err != nil { @@ -71,27 +64,6 @@ func (o *PutMmdsReader) ReadResponse(response runtime.ClientResponse, consumer r } } -// NewPutMmdsCreated creates a PutMmdsCreated with default headers values -func NewPutMmdsCreated() *PutMmdsCreated { - return &PutMmdsCreated{} -} - -/*PutMmdsCreated handles this case with default header values. - -MMDS data store created -*/ -type PutMmdsCreated struct { -} - -func (o *PutMmdsCreated) Error() string { - return fmt.Sprintf("[PUT /mmds][%d] putMmdsCreated ", 201) -} - -func (o *PutMmdsCreated) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { - - return nil -} - // NewPutMmdsNoContent creates a PutMmdsNoContent with default headers values func NewPutMmdsNoContent() *PutMmdsNoContent { return &PutMmdsNoContent{} @@ -99,7 +71,7 @@ func NewPutMmdsNoContent() *PutMmdsNoContent { /*PutMmdsNoContent handles this case with default header values. -MMDS data store updated. +MMDS data store created/updated. */ type PutMmdsNoContent struct { } diff --git a/vendor/github.com/firecracker-microvm/firecracker-go-sdk/drives.go b/vendor/github.com/firecracker-microvm/firecracker-go-sdk/drives.go new file mode 100644 index 0000000000..37c262b5cf --- /dev/null +++ b/vendor/github.com/firecracker-microvm/firecracker-go-sdk/drives.go @@ -0,0 +1,65 @@ +package firecracker + +import ( + "strconv" + + models "github.com/firecracker-microvm/firecracker-go-sdk/client/models" +) + +const rootDriveName = "root-drive" + +// DrivesBuilder is a builder that will build an array of drives used to set up +// the firecracker microVM. The DriveID will be an incrementing number starting +// at one +type DrivesBuilder struct { + rootDrive models.Drive + drives []models.Drive +} + +// NewDrivesBuilder will return a new DrivesBuilder with a given rootfs. +func NewDrivesBuilder(rootDrivePath string) DrivesBuilder { + return DrivesBuilder{}.WithRootDrive(rootDrivePath) +} + +// DriveOpt represents an optional function used to allow for specific +// customization of the models.Drive structure. +type DriveOpt func(*models.Drive) + +// WithRootDrive will set the given builder with the a new root path. The root +// drive will be set to read and write by default. +func (b DrivesBuilder) WithRootDrive(rootDrivePath string, opts ...DriveOpt) DrivesBuilder { + b.rootDrive = models.Drive{ + DriveID: String(rootDriveName), + PathOnHost: &rootDrivePath, + IsRootDevice: Bool(true), + IsReadOnly: Bool(false), + } + + for _, opt := range opts { + opt(&b.rootDrive) + } + + return b +} + +// AddDrive will add a new drive to the given builder. +func (b DrivesBuilder) AddDrive(path string, readOnly bool, opts ...DriveOpt) DrivesBuilder { + drive := models.Drive{ + DriveID: String(strconv.Itoa(len(b.drives))), + PathOnHost: &path, + IsRootDevice: Bool(false), + IsReadOnly: &readOnly, + } + + for _, opt := range opts { + opt(&drive) + } + + b.drives = append(b.drives, drive) + return b +} + +// Build will construct an array of drives with the root drive at the very end. +func (b DrivesBuilder) Build() []models.Drive { + return append(b.drives, b.rootDrive) +} diff --git a/vendor/github.com/firecracker-microvm/firecracker-go-sdk/firecracker.go b/vendor/github.com/firecracker-microvm/firecracker-go-sdk/firecracker.go index 8c8dc4332c..958a272747 100644 --- a/vendor/github.com/firecracker-microvm/firecracker-go-sdk/firecracker.go +++ b/vendor/github.com/firecracker-microvm/firecracker-go-sdk/firecracker.go @@ -134,7 +134,7 @@ func (f *FirecrackerClient) CreateSyncAction(ctx context.Context, info *models.I return f.client.Operations.CreateSyncAction(params) } -func (f *FirecrackerClient) PutMmds(ctx context.Context, metadata interface{}) (*ops.PutMmdsCreated, *ops.PutMmdsNoContent, error) { +func (f *FirecrackerClient) PutMmds(ctx context.Context, metadata interface{}) (*ops.PutMmdsNoContent, error) { params := ops.NewPutMmdsParams() params.SetContext(ctx) params.SetBody(metadata) diff --git a/vendor/github.com/firecracker-microvm/firecracker-go-sdk/handlers.go b/vendor/github.com/firecracker-microvm/firecracker-go-sdk/handlers.go new file mode 100644 index 0000000000..f7f1bd0c96 --- /dev/null +++ b/vendor/github.com/firecracker-microvm/firecracker-go-sdk/handlers.go @@ -0,0 +1,237 @@ +package firecracker + +import ( + "context" +) + +// Handler name constants +const ( + StartVMMHandlerName = "fcinit.StartVMM" + BootstrapLoggingHandlerName = "fcinit.BootstrapLogging" + CreateMachineHandlerName = "fcinit.CreateMachine" + CreateBootSourceHandlerName = "fcinit.CreateBootSource" + AttachDrivesHandlerName = "fcinit.AttachDrives" + CreateNetworkInterfacesHandlerName = "fcinit.CreateNetworkInterfaces" + AddVsocksHandlerName = "fcinit.AddVsocks" + SetMetadataHandlerName = "fcinit.SetMetadata" + + ValidateCfgHandlerName = "validate.Cfg" +) + +// StartVMMHandler is a named handler that will handle starting of the VMM. +// This handler will also set the exit channel on completion. +var StartVMMHandler = Handler{ + Name: StartVMMHandlerName, + Fn: func(ctx context.Context, m *Machine) error { + return m.startVMM(ctx) + }, +} + +// BootstrapLoggingHandler is a named handler that will set up fifo logging of +// firecracker process. +var BootstrapLoggingHandler = Handler{ + Name: BootstrapLoggingHandlerName, + Fn: func(ctx context.Context, m *Machine) error { + if err := m.setupLogging(ctx); err != nil { + m.logger.Warnf("setupLogging() returned %s. Continuing anyway.", err) + } else { + m.logger.Debugf("setup logging: success") + } + + return nil + }, +} + +// CreateMachineHandler is a named handler that will "create" the machine and +// upload any necessary configuration to the firecracker process. +var CreateMachineHandler = Handler{ + Name: CreateMachineHandlerName, + Fn: func(ctx context.Context, m *Machine) error { + return m.createMachine(ctx) + }, +} + +// CreateBootSourceHandler is a named handler that will set up the booting +// process of the firecracker process. +var CreateBootSourceHandler = Handler{ + Name: CreateBootSourceHandlerName, + Fn: func(ctx context.Context, m *Machine) error { + return m.createBootSource(ctx, m.cfg.KernelImagePath, m.cfg.KernelArgs) + }, +} + +// AttachDrivesHandler is a named handler that will attach all drives for the +// firecracker process. +var AttachDrivesHandler = Handler{ + Name: AttachDrivesHandlerName, + Fn: func(ctx context.Context, m *Machine) error { + return m.attachDrives(ctx, m.cfg.Drives...) + }, +} + +// CreateNetworkInterfacesHandler is a named handler that sets up network +// interfaces to the firecracker process. +var CreateNetworkInterfacesHandler = Handler{ + Name: CreateNetworkInterfacesHandlerName, + Fn: func(ctx context.Context, m *Machine) error { + return m.createNetworkInterfaces(ctx, m.cfg.NetworkInterfaces...) + }, +} + +// AddVsocksHandler is a named handler that adds vsocks to the firecracker +// process. +var AddVsocksHandler = Handler{ + Name: AddVsocksHandlerName, + Fn: func(ctx context.Context, m *Machine) error { + return m.addVsocks(ctx, m.cfg.VsockDevices...) + }, +} + +// NewSetMetadataHandler is a named handler that puts the metadata into the +// firecracker process. +func NewSetMetadataHandler(metadata interface{}) Handler { + return Handler{ + Name: SetMetadataHandlerName, + Fn: func(ctx context.Context, m *Machine) error { + return m.SetMetadata(ctx, m.Metadata) + }, + } +} + +var defaultValidationHandlerList = HandlerList{}.Append( + Handler{ + Name: ValidateCfgHandlerName, + Fn: func(ctx context.Context, m *Machine) error { + // ensure that the configuration is valid for the + // FcInit handlers. + return m.cfg.Validate() + }, + }, +) + +var defaultFcInitHandlerList = HandlerList{}.Append( + StartVMMHandler, + BootstrapLoggingHandler, + CreateMachineHandler, + CreateBootSourceHandler, + AttachDrivesHandler, + CreateNetworkInterfacesHandler, + AddVsocksHandler, +) + +var defaultHandlers = Handlers{ + Validation: defaultValidationHandlerList, + FcInit: defaultFcInitHandlerList, +} + +// Handler represents a named handler that contains a name and a function which +// is used to execute during the initialization process of a machine. +type Handler struct { + Name string + Fn func(context.Context, *Machine) error +} + +// Handlers is a container that houses categories of handler lists. +type Handlers struct { + Validation HandlerList + FcInit HandlerList +} + +// Run will execute all handlers in the Handlers object by flattening the lists +// into a single list and running. +func (h Handlers) Run(ctx context.Context, m *Machine) error { + l := HandlerList{}.Append( + h.Validation.list..., + ).Append( + h.FcInit.list..., + ) + + return l.Run(ctx, m) +} + +// HandlerList represents a list of named handler that can be used to execute a +// flow of instructions for a given machine. +type HandlerList struct { + list []Handler +} + +// Append will append a new handler to the handler list. +func (l HandlerList) Append(handlers ...Handler) HandlerList { + l.list = append(l.list, handlers...) + + return l +} + +// Len return the length of the given handler list +func (l HandlerList) Len() int { + return len(l.list) +} + +// Has will iterate through the handler list and check to see if the the named +// handler exists. +func (l HandlerList) Has(name string) bool { + for _, h := range l.list { + if h.Name == name { + return true + } + } + + return false +} + +// Swap will replace all elements of the given name with the new handler. +func (l HandlerList) Swap(handler Handler) HandlerList { + newList := HandlerList{} + for _, h := range l.list { + if h.Name == handler.Name { + newList.list = append(newList.list, handler) + continue + } + + newList.list = append(newList.list, h) + } + + return newList +} + +// Swappend will either append, if there isn't an element within the handler +// list, otherwise it will replace all elements with the given name. +func (l HandlerList) Swappend(handler Handler) HandlerList { + if l.Has(handler.Name) { + return l.Swap(handler) + } + + return l.Append(handler) +} + +// Remove will return an updated handler with all instances of the specific +// named handler being removed. +func (l HandlerList) Remove(name string) HandlerList { + newList := HandlerList{} + for _, h := range l.list { + if h.Name != name { + newList.list = append(newList.list, h) + } + } + + return newList +} + +// Clear clears all named handler in the list. +func (l HandlerList) Clear() HandlerList { + l.list = l.list[0:0] + return l +} + +// Run will execute each instruction in the handler list. If an error occurs in +// any of the handlers, then the list will halt execution and return the error. +func (l HandlerList) Run(ctx context.Context, m *Machine) error { + for _, handler := range l.list { + m.logger.Debugf("Running handler %s", handler.Name) + if err := handler.Fn(ctx, m); err != nil { + return err + } + } + + return nil +} diff --git a/vendor/github.com/firecracker-microvm/firecracker-go-sdk/machine.go b/vendor/github.com/firecracker-microvm/firecracker-go-sdk/machine.go index c67ce8d57b..c19f0a7f80 100644 --- a/vendor/github.com/firecracker-microvm/firecracker-go-sdk/machine.go +++ b/vendor/github.com/firecracker-microvm/firecracker-go-sdk/machine.go @@ -17,6 +17,7 @@ import ( "context" "errors" "fmt" + "io" "os" "os/exec" "os/signal" @@ -33,17 +34,8 @@ const ( userAgent = "firecracker-go-sdk" ) -// CPUTemplate defines a set of CPU features that are exposed by Firecracker -type CPUTemplate = models.CPUTemplate - -// CPUTemplates known by Firecracker. These are passed through directly from the model. -const ( - CPUTemplateT2 = models.CPUTemplateT2 - CPUTemplateC3 = models.CPUTemplateC3 -) - -// Firecracker is an interface that can be used to mock out a Firecracker agent -// for testing purposes. +// Firecracker is an interface that can be used to mock +// out an Firecracker agent for testing purposes. type Firecracker interface { PutLogger(ctx context.Context, logger *models.Logger) (*ops.PutLoggerNoContent, error) PutMachineConfiguration(ctx context.Context, cfg *models.MachineConfiguration) (*ops.PutMachineConfigurationNoContent, error) @@ -52,7 +44,7 @@ type Firecracker interface { PutGuestDriveByID(ctx context.Context, driveID string, drive *models.Drive) (*ops.PutGuestDriveByIDNoContent, error) PutGuestVsockByID(ctx context.Context, vsockID string, vsock *models.Vsock) (*ops.PutGuestVsockByIDCreated, *ops.PutGuestVsockByIDNoContent, error) CreateSyncAction(ctx context.Context, info *models.InstanceActionInfo) (*ops.CreateSyncActionNoContent, error) - PutMmds(ctx context.Context, metadata interface{}) (*ops.PutMmdsCreated, *ops.PutMmdsNoContent, error) + PutMmds(ctx context.Context, metadata interface{}) (*ops.PutMmdsNoContent, error) GetMachineConfig() (*ops.GetMachineConfigOK, error) } @@ -82,43 +74,27 @@ type Config struct { // the kernel. KernelArgs string - // CPUCount defines the number of CPU threads that should be available to - // the micro-VM. - CPUCount int64 - - // HtEnabled defines whether hyper-threading should be enabled for the + // Drives specifies BlockDevices that should be made available to the // microVM. - HtEnabled bool - - // CPUTemplate defines the Firecracker CPU template to use. Valid values - // are CPUTemplateT2 and CPUTemplateC3, - CPUTemplate CPUTemplate - - // MemInMiB defines the amount of memory that should be made available to - // the microVM. - MemInMiB int64 - - // RootDrive specifies the BlockDevice that contains the root filesystem. - RootDrive BlockDevice - - // RootPartitionUUID defines the UUID that specifies the root partition. - RootPartitionUUID string - - // AdditionalDrives specifies additional BlockDevices that should be made - // available to the microVM. - AdditionalDrives []BlockDevice + Drives []models.Drive // NetworkInterfaces specifies the tap devices that should be made available // to the microVM. NetworkInterfaces []NetworkInterface + // FifoLogWriter is an io.Writer that is used to redirect the contents of the + // fifo log to the writer. + FifoLogWriter io.Writer + // VsockDevices specifies the vsock devices that should be made available to // the microVM. VsockDevices []VsockDevice // Debug enables debug-level logging for the SDK. - Debug bool - machineCfg models.MachineConfiguration + Debug bool + + // MachineCfg represents the firecracker microVM process configuration + MachineCfg models.MachineConfiguration // DisableValidation allows for easier mock testing by disabling the // validation of configuration performed by the SDK. @@ -135,8 +111,17 @@ func (cfg *Config) Validate() error { if _, err := os.Stat(cfg.KernelImagePath); err != nil { return fmt.Errorf("failed to stat kernal image path, %q: %v", cfg.KernelImagePath, err) } - if _, err := os.Stat(cfg.RootDrive.HostPath); err != nil { - return fmt.Errorf("failed to stat host path, %q: %v", cfg.RootDrive.HostPath, err) + + rootPath := "" + for _, drive := range cfg.Drives { + if BoolValue(drive.IsRootDevice) { + rootPath = StringValue(drive.PathOnHost) + break + } + } + + if _, err := os.Stat(rootPath); err != nil { + return fmt.Errorf("failed to stat host path, %q: %v", rootPath, err) } // Check the non-existence of some files: @@ -154,6 +139,12 @@ type Machine struct { cmd *exec.Cmd logger *log.Entry machineConfig models.MachineConfiguration // The actual machine config as reported by Firecracker + + // Metadata is the associated metadata that will be sent to the firecracker + // process + Metadata interface{} + errCh chan error + Handlers Handlers } // Logger returns a logrus logger appropriate for logging hypervisor messages @@ -172,15 +163,6 @@ type NetworkInterface struct { AllowMDDS bool } -// BlockDevice represents a host block device mapped to the Firecracker microVM. -type BlockDevice struct { - // HostPath defines the filesystem path of the block device on the host. - HostPath string - // Mode defines whether the device is writable. Valid values are "ro" and - // "rw". - Mode string -} - // VsockDevice represents a vsock connection between the host and the guest // microVM. type VsockDevice struct { @@ -209,122 +191,102 @@ func (m Machine) LogLevel() string { // NewMachine initializes a new Machine instance and performs validation of the // provided Config. -func NewMachine(cfg Config, opts ...Opt) (*Machine, error) { +func NewMachine(ctx context.Context, cfg Config, opts ...Opt) (*Machine, error) { if err := cfg.Validate(); err != nil { return nil, err } m := &Machine{} + logger := log.New() - for _, opt := range opts { - opt(m) + if cfg.Debug { + logger.SetLevel(log.DebugLevel) } - if m.logger == nil { - logger := log.New() - - if cfg.Debug { - logger.SetLevel(log.DebugLevel) - } + m.logger = log.NewEntry(logger) + m.cmd = defaultFirecrackerVMMCommandBuilder. + WithSocketPath(cfg.SocketPath). + Build(ctx) + m.Handlers = defaultHandlers - m.logger = log.NewEntry(logger) + for _, opt := range opts { + opt(m) } if m.client == nil { m.client = NewFirecrackerClient(cfg.SocketPath, m.logger, cfg.Debug) } - m.logger.Debug("Called NewMachine()") - m.cfg = cfg - m.cfg.machineCfg = models.MachineConfiguration{ - VcpuCount: cfg.CPUCount, - MemSizeMib: cfg.MemInMiB, - HtEnabled: cfg.HtEnabled, - CPUTemplate: models.CPUTemplate(cfg.CPUTemplate), - } + m.logger.Debug("Called NewMachine()") return m, nil } -// Init starts the VMM and attaches drives and network interfaces. -func (m *Machine) Init(ctx context.Context) (<-chan error, error) { - m.logger.Debug("Called Machine.Init()") - - if m.cmd == nil { - m.cmd = defaultFirecrackerVMMCommandBuilder. - WithSocketPath(m.cfg.SocketPath). - Build(ctx) - } - - errCh, err := m.startVMM(ctx) - if err != nil { - return errCh, err - } - - if err := m.setupLogging(ctx); err != nil { - m.logger.Warnf("setupLogging() returned %s. Continuing anyway.", err) - } else { - m.logger.Debugf("back from setupLogging") - } - - if err = m.createMachine(ctx); err != nil { - m.stopVMM() - return errCh, err +// Start will iterate through the handler list and call each handler. If an +// error occurred during handler execution, that error will be returned. If the +// handlers succeed, then this will start the VMM instance. +func (m *Machine) Start(ctx context.Context) error { + m.logger.Debug("Called Machine.Start()") + if err := m.Handlers.Run(ctx, m); err != nil { + return err } - m.logger.Debug("createMachine returned") - if err = m.createBootSource(ctx, m.cfg.KernelImagePath, m.cfg.KernelArgs); err != nil { - m.stopVMM() - return errCh, err - } - m.logger.Debug("createBootSource returned") + return m.StartInstance(ctx) +} - if err = m.attachDrive(ctx, m.cfg.RootDrive, 1, true); err != nil { - m.stopVMM() - return errCh, err +// Wait will wait until the firecracker process has finished +func (m *Machine) Wait(ctx context.Context) error { + select { + case <-ctx.Done(): + return ctx.Err() + case err := <-m.errCh: + return err } - m.logger.Debug("Root drive attachment complete") +} - for id, dev := range m.cfg.AdditionalDrives { - // id must be increased by 2 because firecracker uses 1-indexed arrays and the root drive occupies position 1. - err = m.attachDrive(ctx, dev, id+2, false) - if err != nil { - m.logger.Errorf("While attaching secondary drive %s, got error %s", dev.HostPath, err) - m.stopVMM() - return errCh, err +func (m *Machine) addVsocks(ctx context.Context, vsocks ...VsockDevice) error { + for _, dev := range m.cfg.VsockDevices { + if err := m.addVsock(ctx, dev); err != nil { + return err } - m.logger.Debugf("attachDrive returned for %s", dev.HostPath) } - for id, iface := range m.cfg.NetworkInterfaces { - err = m.createNetworkInterface(ctx, iface, id+1) - if err != nil { - m.stopVMM() - return errCh, err + return nil +} + +func (m *Machine) createNetworkInterfaces(ctx context.Context, ifaces ...NetworkInterface) error { + for id, iface := range ifaces { + if err := m.createNetworkInterface(ctx, iface, id+1); err != nil { + return err } m.logger.Debugf("createNetworkInterface returned for %s", iface.HostDevName) } - for _, dev := range m.cfg.VsockDevices { - err = m.addVsock(ctx, dev) - if err != nil { - m.stopVMM() - return errCh, err + + return nil +} + +func (m *Machine) attachDrives(ctx context.Context, drives ...models.Drive) error { + for _, dev := range drives { + if err := m.attachDrive(ctx, dev); err != nil { + m.logger.Errorf("While attaching drive %s, got error %s", StringValue(dev.PathOnHost), err) + return err } + m.logger.Debugf("attachDrive returned for %s", StringValue(dev.PathOnHost)) } - m.logger.Debugf("returning from Machine.Init(), RootDrive=%s", m.cfg.RootDrive.HostPath) - return errCh, nil + return nil } // startVMM starts the firecracker vmm process and configures logging. -func (m *Machine) startVMM(ctx context.Context) (<-chan error, error) { +func (m *Machine) startVMM(ctx context.Context) error { m.logger.Printf("Called startVMM(), setting up a VMM on %s", m.cfg.SocketPath) - exitCh := make(chan error) + m.errCh = make(chan error) + err := m.cmd.Start() if err != nil { m.logger.Errorf("Failed to start VMM: %s", err) - return exitCh, err + return err } m.logger.Debugf("VMM started socket path is %s", m.cfg.SocketPath) @@ -338,7 +300,7 @@ func (m *Machine) startVMM(ctx context.Context) (<-chan error, error) { os.Remove(m.cfg.SocketPath) os.Remove(m.cfg.LogFifo) os.Remove(m.cfg.MetricsFifo) - exitCh <- err + m.errCh <- err }() // Set up a signal handler and pass INT, QUIT, and TERM through to firecracker @@ -356,20 +318,20 @@ func (m *Machine) startVMM(ctx context.Context) (<-chan error, error) { m.logger.Printf("Caught signal %s", sig) m.cmd.Process.Signal(sig) case err = <-vmchan: - exitCh <- err + m.errCh <- err } }() // Wait for firecracker to initialize: - err = m.waitForSocket(3*time.Second, exitCh) + err = m.waitForSocket(3*time.Second, m.errCh) if err != nil { msg := fmt.Sprintf("Firecracker did not create API socket %s: %s", m.cfg.SocketPath, err) err = errors.New(msg) - return exitCh, err + return err } m.logger.Debugf("returning from startVMM()") - return exitCh, nil + return nil } //StopVMM stops the current VMM. @@ -391,13 +353,15 @@ func (m *Machine) stopVMM() error { // createFifos sets up the firecracker logging and metrics FIFOs func createFifos(logFifo, metricsFifo string) error { log.Debugf("Creating FIFO %s", logFifo) - err := syscall.Mkfifo(logFifo, 0700) - if err != nil { - return err + if err := syscall.Mkfifo(logFifo, 0700); err != nil { + return fmt.Errorf("Failed to create log fifo: %v", err) } - log.Debugf("Creating FIFO %s", metricsFifo) - err = syscall.Mkfifo(metricsFifo, 0700) - return err + + log.Debugf("Creating metric FIFO %s", metricsFifo) + if err := syscall.Mkfifo(metricsFifo, 0700); err != nil { + return fmt.Errorf("Failed to create metric fifo: %v", err) + } + return nil } func (m *Machine) setupLogging(ctx context.Context) error { @@ -407,8 +371,7 @@ func (m *Machine) setupLogging(ctx context.Context) error { return nil } - err := createFifos(m.cfg.LogFifo, m.cfg.MetricsFifo) - if err != nil { + if err := createFifos(m.cfg.LogFifo, m.cfg.MetricsFifo); err != nil { m.logger.Errorf("Unable to set up logging: %s", err) return err } @@ -423,16 +386,56 @@ func (m *Machine) setupLogging(ctx context.Context) error { ShowLogOrigin: false, } - resp, err := m.client.PutLogger(ctx, &l) - if err == nil { - m.logger.Printf("Configured VMM logging to %s, metrics to %s: %s", - m.cfg.LogFifo, m.cfg.MetricsFifo, resp.Error()) + _, err := m.client.PutLogger(ctx, &l) + if err != nil { + return err } - return err + + m.logger.Debugf("Configured VMM logging to %s, metrics to %s", + m.cfg.LogFifo, + m.cfg.MetricsFifo, + ) + + if m.cfg.FifoLogWriter != nil { + if err := captureFifoToFile(m.logger, m.cfg.LogFifo, m.cfg.FifoLogWriter); err != nil { + return err + } + } + + return nil +} + +func captureFifoToFile(logger *log.Entry, fifoPath string, fifo io.Writer) error { + // create the fifo pipe which will be used + // to write its contents to a file. + fifoPipe, err := os.OpenFile(fifoPath, os.O_RDONLY, 0600) + if err != nil { + return fmt.Errorf("Failed to open fifo path at %q: %v", fifoPath, err) + } + + if err := syscall.Unlink(fifoPath); err != nil { + logger.Warnf("Failed to unlink %s", fifoPath) + } + + logger.Debugf("Capturing %q to writer", fifoPath) + + // Uses a go routine to do a non-blocking io.Copy. The fifo + // file should be closed when the appication has finished, since + // the forked firecracker application will be closed resulting + // in the pipe to return an io.EOF + go func() { + defer fifoPipe.Close() + + if _, err := io.Copy(fifo, fifoPipe); err != nil { + logger.Warnf("io.Copy failed to copy contents of fifo pipe: %v", err) + } + }() + + return nil } func (m *Machine) createMachine(ctx context.Context) error { - resp, err := m.client.PutMachineConfiguration(ctx, &m.cfg.machineCfg) + resp, err := m.client.PutMachineConfiguration(ctx, &m.cfg.MachineCfg) if err != nil { m.logger.Errorf("PutMachineConfiguration returned %s", resp.Error()) return err @@ -469,7 +472,6 @@ func (m *Machine) createNetworkInterface(ctx context.Context, iface NetworkInter IfaceID: &ifaceID, GuestMac: iface.MacAddress, HostDevName: iface.HostDevName, - State: models.DeviceStateAttached, AllowMmdsRequests: iface.AllowMDDS, } @@ -482,52 +484,25 @@ func (m *Machine) createNetworkInterface(ctx context.Context, iface NetworkInter } // attachDrive attaches a secondary block device -func (m *Machine) attachDrive(ctx context.Context, dev BlockDevice, index int, root bool) error { +func (m *Machine) attachDrive(ctx context.Context, dev models.Drive) error { var err error + hostPath := StringValue(dev.PathOnHost) - _, err = os.Stat(dev.HostPath) + _, err = os.Stat(hostPath) if err != nil { return err } - readOnly := true - - switch dev.Mode { - case "ro": - readOnly = true - case "rw": - readOnly = false - default: - return errors.New("invalid drive permissions") - } - - driveID := strconv.Itoa(index) - d := models.Drive{ - DriveID: &driveID, - PathOnHost: &dev.HostPath, - IsRootDevice: &root, - IsReadOnly: &readOnly, - } - - if len(m.cfg.RootPartitionUUID) > 0 && root { - d.Partuuid = m.cfg.RootPartitionUUID - } - - log.Infof("Attaching drive %s, mode %s, slot %s, root %t.", dev.HostPath, dev.Mode, driveID, root) - - respNoContent, err := m.client.PutGuestDriveByID(ctx, driveID, &d) + log.Infof("Attaching drive %s, slot %s, root %t.", hostPath, StringValue(dev.DriveID), BoolValue(dev.IsRootDevice)) + respNoContent, err := m.client.PutGuestDriveByID(ctx, StringValue(dev.DriveID), &dev) if err == nil { - m.logger.Printf("Attached drive %s: %s", dev.HostPath, respNoContent.Error()) + m.logger.Printf("Attached drive %s: %s", hostPath, respNoContent.Error()) } else { - m.logger.Errorf("Attach drive failed: %s: %s", dev.HostPath, err) + m.logger.Errorf("Attach drive failed: %s: %s", hostPath, err) } return err } -func (m *Machine) attachRootDrive(ctx context.Context, dev BlockDevice) error { - return m.attachDrive(ctx, dev, 1, true) -} - // addVsock adds a vsock to the instance func (m *Machine) addVsock(ctx context.Context, dev VsockDevice) error { vsockCfg := models.Vsock{ @@ -561,15 +536,17 @@ func (m *Machine) startInstance(ctx context.Context) error { return err } +// EnableMetadata will append or replace the metadata handler. +func (m *Machine) EnableMetadata(metadata interface{}) { + m.Handlers.FcInit = m.Handlers.FcInit.Swappend(NewSetMetadataHandler(metadata)) +} + // SetMetadata sets the machine's metadata for MDDS func (m *Machine) SetMetadata(ctx context.Context, metadata interface{}) error { - respcreated, respnocontent, err := m.client.PutMmds(ctx, metadata) + respnocontent, err := m.client.PutMmds(ctx, metadata) if err == nil { var message string - if respcreated != nil { - message = respcreated.Error() - } if respnocontent != nil { message = respnocontent.Error() } diff --git a/vendor/github.com/firecracker-microvm/firecracker-go-sdk/pointer_helpers.go b/vendor/github.com/firecracker-microvm/firecracker-go-sdk/pointer_helpers.go new file mode 100644 index 0000000000..6dd58f0276 --- /dev/null +++ b/vendor/github.com/firecracker-microvm/firecracker-go-sdk/pointer_helpers.go @@ -0,0 +1,46 @@ +package firecracker + +// BoolValue will return a boolean value. If the pointer is nil, then false +// will be returned. +func BoolValue(b *bool) bool { + if b == nil { + return false + } + + return *b +} + +// Bool will return a pointer value of the given parameter. +func Bool(b bool) *bool { + return &b +} + +// StringValue will return a string value. If the pointer is nil, then an empty +// string will be returned. +func StringValue(str *string) string { + if str == nil { + return "" + } + + return *str +} + +// String will return a pointer value of the given parameter. +func String(str string) *string { + return &str +} + +// Int64 will return a pointer value of the given parameter. +func Int64(v int64) *int64 { + return &v +} + +// Int64Value will return an int64 value. If the pointer is nil, then zero will +// be returned. +func Int64Value(v *int64) int64 { + if v == nil { + return 0 + } + + return *v +}