firecracker: Revendor firecracker go sdk to 0.12.0

Revendor firecracker go sdk for Firecracker 0.12.0 API changes

git shortlog

9614612 (HEAD -> master, origin/master, origin/HEAD) Merge pull request
653c342 Adding drives builder
3c1f5c3 Merge pr #41
c4151ff Migrate firectl to its own repository
433f262 Merge pull request #23 from xibz/fifo_logging_file
121ef9a add handler lists to handle initialization
0fd9825 Adding support for capturing fifo logs to file.
6b08ec7 Merge branch 'fc-0.12.0'
25878e7 Update for Firecracker 0.12.0 API changes
ea93f77 Regenerate API client from swagger spec
00d8eee Update swagger.yaml for firecracker 0.12.0

Signed-off-by: Manohar Castelino <manohar.r.castelino@intel.com>
This commit is contained in:
Manohar Castelino 2018-12-27 19:23:56 -08:00
parent ec5cf18bd1
commit 5c6d94d756
12 changed files with 519 additions and 321 deletions

4
Gopkg.lock generated
View File

@ -204,7 +204,7 @@
version = "v0.3.3" version = "v0.3.3"
[[projects]] [[projects]]
digest = "1:61eec5f342089feaaa1690354a62102a90a64dcf7bbc4c6d840dc5edd66ae451" digest = "1:58b18038f51b6f79865fd00e3d883949c9507ede577cd9c4e743ed56cc454122"
name = "github.com/firecracker-microvm/firecracker-go-sdk" name = "github.com/firecracker-microvm/firecracker-go-sdk"
packages = [ packages = [
".", ".",
@ -213,7 +213,7 @@
"client/operations", "client/operations",
] ]
pruneopts = "NUT" pruneopts = "NUT"
revision = "840c1e37f5f2bbcbff1fdbfcfcea09d0bf158977" revision = "961461227bddf7e40a1d690634e866c343910f86"
[[projects]] [[projects]]
branch = "master" branch = "master"

View File

@ -72,7 +72,7 @@
[[constraint]] [[constraint]]
name = "github.com/firecracker-microvm/firecracker-go-sdk" name = "github.com/firecracker-microvm/firecracker-go-sdk"
revision = "840c1e37f5f2bbcbff1fdbfcfcea09d0bf158977" revision = "961461227bddf7e40a1d690634e866c343910f86"
[[override]] [[override]]
branch = "master" branch = "master"

View File

@ -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
}

View File

@ -42,6 +42,9 @@ type Logger struct {
// The named pipe where the JSON-formatted metrics will be flushed. // The named pipe where the JSON-formatted metrics will be flushed.
MetricsFifo string `json:"metrics_fifo,omitempty"` 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. // Whether or not to output the level in the logs.
ShowLevel bool `json:"show_level,omitempty"` ShowLevel bool `json:"show_level,omitempty"`

View File

@ -46,9 +46,6 @@ type NetworkInterface struct {
// rx rate limiter // rx rate limiter
RxRateLimiter *RateLimiter `json:"rx_rate_limiter,omitempty"` RxRateLimiter *RateLimiter `json:"rx_rate_limiter,omitempty"`
// state
State DeviceState `json:"state,omitempty"`
// tx rate limiter // tx rate limiter
TxRateLimiter *RateLimiter `json:"tx_rate_limiter,omitempty"` TxRateLimiter *RateLimiter `json:"tx_rate_limiter,omitempty"`
} }
@ -65,10 +62,6 @@ func (m *NetworkInterface) Validate(formats strfmt.Registry) error {
res = append(res, err) res = append(res, err)
} }
if err := m.validateState(formats); err != nil {
res = append(res, err)
}
if err := m.validateTxRateLimiter(formats); err != nil { if err := m.validateTxRateLimiter(formats); err != nil {
res = append(res, err) res = append(res, err)
} }
@ -106,22 +99,6 @@ func (m *NetworkInterface) validateRxRateLimiter(formats strfmt.Registry) error
return nil 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 { func (m *NetworkInterface) validateTxRateLimiter(formats strfmt.Registry) error {
if swag.IsZero(m.TxRateLimiter) { // not required if swag.IsZero(m.TxRateLimiter) { // not required

View File

@ -126,7 +126,7 @@ func (a *Client) PatchMmds(params *PatchMmdsParams) (*PatchMmdsNoContent, error)
/* /*
PutMmds creates a m m d s microvm metadata service data store 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 // TODO: Validate the params before sending
if params == nil { if params == nil {
params = NewPutMmdsParams() params = NewPutMmdsParams()
@ -145,15 +145,9 @@ func (a *Client) PutMmds(params *PutMmdsParams) (*PutMmdsCreated, *PutMmdsNoCont
Client: params.HTTPClient, Client: params.HTTPClient,
}) })
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
switch value := result.(type) { return result.(*PutMmdsNoContent), nil
case *PutMmdsCreated:
return value, nil, nil
case *PutMmdsNoContent:
return nil, value, nil
}
return nil, nil, nil
} }

View File

@ -38,13 +38,6 @@ type PutMmdsReader struct {
func (o *PutMmdsReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) { func (o *PutMmdsReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
switch response.Code() { 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: case 204:
result := NewPutMmdsNoContent() result := NewPutMmdsNoContent()
if err := result.readResponse(response, consumer, o.formats); err != nil { 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 // NewPutMmdsNoContent creates a PutMmdsNoContent with default headers values
func NewPutMmdsNoContent() *PutMmdsNoContent { func NewPutMmdsNoContent() *PutMmdsNoContent {
return &PutMmdsNoContent{} return &PutMmdsNoContent{}
@ -99,7 +71,7 @@ func NewPutMmdsNoContent() *PutMmdsNoContent {
/*PutMmdsNoContent handles this case with default header values. /*PutMmdsNoContent handles this case with default header values.
MMDS data store updated. MMDS data store created/updated.
*/ */
type PutMmdsNoContent struct { type PutMmdsNoContent struct {
} }

View File

@ -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)
}

View File

@ -134,7 +134,7 @@ func (f *FirecrackerClient) CreateSyncAction(ctx context.Context, info *models.I
return f.client.Operations.CreateSyncAction(params) 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 := ops.NewPutMmdsParams()
params.SetContext(ctx) params.SetContext(ctx)
params.SetBody(metadata) params.SetBody(metadata)

View File

@ -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
}

View File

@ -17,6 +17,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"io"
"os" "os"
"os/exec" "os/exec"
"os/signal" "os/signal"
@ -33,17 +34,8 @@ const (
userAgent = "firecracker-go-sdk" userAgent = "firecracker-go-sdk"
) )
// CPUTemplate defines a set of CPU features that are exposed by Firecracker // Firecracker is an interface that can be used to mock
type CPUTemplate = models.CPUTemplate // out an Firecracker agent for testing purposes.
// 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.
type Firecracker interface { type Firecracker interface {
PutLogger(ctx context.Context, logger *models.Logger) (*ops.PutLoggerNoContent, error) PutLogger(ctx context.Context, logger *models.Logger) (*ops.PutLoggerNoContent, error)
PutMachineConfiguration(ctx context.Context, cfg *models.MachineConfiguration) (*ops.PutMachineConfigurationNoContent, 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) 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) PutGuestVsockByID(ctx context.Context, vsockID string, vsock *models.Vsock) (*ops.PutGuestVsockByIDCreated, *ops.PutGuestVsockByIDNoContent, error)
CreateSyncAction(ctx context.Context, info *models.InstanceActionInfo) (*ops.CreateSyncActionNoContent, 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) GetMachineConfig() (*ops.GetMachineConfigOK, error)
} }
@ -82,43 +74,27 @@ type Config struct {
// the kernel. // the kernel.
KernelArgs string KernelArgs string
// CPUCount defines the number of CPU threads that should be available to // Drives specifies BlockDevices that should be made available to the
// the micro-VM.
CPUCount int64
// HtEnabled defines whether hyper-threading should be enabled for the
// microVM. // microVM.
HtEnabled bool Drives []models.Drive
// 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
// NetworkInterfaces specifies the tap devices that should be made available // NetworkInterfaces specifies the tap devices that should be made available
// to the microVM. // to the microVM.
NetworkInterfaces []NetworkInterface 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 // VsockDevices specifies the vsock devices that should be made available to
// the microVM. // the microVM.
VsockDevices []VsockDevice VsockDevices []VsockDevice
// Debug enables debug-level logging for the SDK. // Debug enables debug-level logging for the SDK.
Debug bool Debug bool
machineCfg models.MachineConfiguration
// MachineCfg represents the firecracker microVM process configuration
MachineCfg models.MachineConfiguration
// DisableValidation allows for easier mock testing by disabling the // DisableValidation allows for easier mock testing by disabling the
// validation of configuration performed by the SDK. // validation of configuration performed by the SDK.
@ -135,8 +111,17 @@ func (cfg *Config) Validate() error {
if _, err := os.Stat(cfg.KernelImagePath); err != nil { if _, err := os.Stat(cfg.KernelImagePath); err != nil {
return fmt.Errorf("failed to stat kernal image path, %q: %v", cfg.KernelImagePath, err) 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: // Check the non-existence of some files:
@ -154,6 +139,12 @@ type Machine struct {
cmd *exec.Cmd cmd *exec.Cmd
logger *log.Entry logger *log.Entry
machineConfig models.MachineConfiguration // The actual machine config as reported by Firecracker 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 // Logger returns a logrus logger appropriate for logging hypervisor messages
@ -172,15 +163,6 @@ type NetworkInterface struct {
AllowMDDS bool 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 // VsockDevice represents a vsock connection between the host and the guest
// microVM. // microVM.
type VsockDevice struct { type VsockDevice struct {
@ -209,122 +191,102 @@ func (m Machine) LogLevel() string {
// NewMachine initializes a new Machine instance and performs validation of the // NewMachine initializes a new Machine instance and performs validation of the
// provided Config. // 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 { if err := cfg.Validate(); err != nil {
return nil, err return nil, err
} }
m := &Machine{} m := &Machine{}
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
for _, opt := range opts { for _, opt := range opts {
opt(m) opt(m)
} }
if m.logger == nil {
logger := log.New()
if cfg.Debug {
logger.SetLevel(log.DebugLevel)
}
m.logger = log.NewEntry(logger)
}
if m.client == nil { if m.client == nil {
m.client = NewFirecrackerClient(cfg.SocketPath, m.logger, cfg.Debug) m.client = NewFirecrackerClient(cfg.SocketPath, m.logger, cfg.Debug)
} }
m.logger.Debug("Called NewMachine()")
m.cfg = cfg 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 return m, nil
} }
// Init starts the VMM and attaches drives and network interfaces. // Start will iterate through the handler list and call each handler. If an
func (m *Machine) Init(ctx context.Context) (<-chan error, error) { // error occurred during handler execution, that error will be returned. If the
m.logger.Debug("Called Machine.Init()") // handlers succeed, then this will start the VMM instance.
func (m *Machine) Start(ctx context.Context) error {
if m.cmd == nil { m.logger.Debug("Called Machine.Start()")
m.cmd = defaultFirecrackerVMMCommandBuilder. if err := m.Handlers.Run(ctx, m); err != nil {
WithSocketPath(m.cfg.SocketPath). return err
Build(ctx)
} }
errCh, err := m.startVMM(ctx) return m.StartInstance(ctx)
if err != nil { }
return errCh, err
}
if err := m.setupLogging(ctx); err != nil { // Wait will wait until the firecracker process has finished
m.logger.Warnf("setupLogging() returned %s. Continuing anyway.", err) func (m *Machine) Wait(ctx context.Context) error {
} else { select {
m.logger.Debugf("back from setupLogging") case <-ctx.Done():
return ctx.Err()
case err := <-m.errCh:
return err
} }
}
if err = m.createMachine(ctx); err != nil { func (m *Machine) addVsocks(ctx context.Context, vsocks ...VsockDevice) error {
m.stopVMM() for _, dev := range m.cfg.VsockDevices {
return errCh, err if err := m.addVsock(ctx, dev); 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")
if err = m.attachDrive(ctx, m.cfg.RootDrive, 1, true); err != nil {
m.stopVMM()
return errCh, 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
} }
m.logger.Debugf("attachDrive returned for %s", dev.HostPath)
} }
for id, iface := range m.cfg.NetworkInterfaces { return nil
err = m.createNetworkInterface(ctx, iface, id+1) }
if err != nil {
m.stopVMM() func (m *Machine) createNetworkInterfaces(ctx context.Context, ifaces ...NetworkInterface) error {
return errCh, err 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) m.logger.Debugf("createNetworkInterface returned for %s", iface.HostDevName)
} }
for _, dev := range m.cfg.VsockDevices {
err = m.addVsock(ctx, dev) return nil
if err != nil { }
m.stopVMM()
return errCh, err 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 nil
return errCh, nil
} }
// startVMM starts the firecracker vmm process and configures logging. // 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) 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() err := m.cmd.Start()
if err != nil { if err != nil {
m.logger.Errorf("Failed to start VMM: %s", err) 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) 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.SocketPath)
os.Remove(m.cfg.LogFifo) os.Remove(m.cfg.LogFifo)
os.Remove(m.cfg.MetricsFifo) os.Remove(m.cfg.MetricsFifo)
exitCh <- err m.errCh <- err
}() }()
// Set up a signal handler and pass INT, QUIT, and TERM through to firecracker // 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.logger.Printf("Caught signal %s", sig)
m.cmd.Process.Signal(sig) m.cmd.Process.Signal(sig)
case err = <-vmchan: case err = <-vmchan:
exitCh <- err m.errCh <- err
} }
}() }()
// Wait for firecracker to initialize: // Wait for firecracker to initialize:
err = m.waitForSocket(3*time.Second, exitCh) err = m.waitForSocket(3*time.Second, m.errCh)
if err != nil { if err != nil {
msg := fmt.Sprintf("Firecracker did not create API socket %s: %s", m.cfg.SocketPath, err) msg := fmt.Sprintf("Firecracker did not create API socket %s: %s", m.cfg.SocketPath, err)
err = errors.New(msg) err = errors.New(msg)
return exitCh, err return err
} }
m.logger.Debugf("returning from startVMM()") m.logger.Debugf("returning from startVMM()")
return exitCh, nil return nil
} }
//StopVMM stops the current VMM. //StopVMM stops the current VMM.
@ -391,13 +353,15 @@ func (m *Machine) stopVMM() error {
// createFifos sets up the firecracker logging and metrics FIFOs // createFifos sets up the firecracker logging and metrics FIFOs
func createFifos(logFifo, metricsFifo string) error { func createFifos(logFifo, metricsFifo string) error {
log.Debugf("Creating FIFO %s", logFifo) log.Debugf("Creating FIFO %s", logFifo)
err := syscall.Mkfifo(logFifo, 0700) if err := syscall.Mkfifo(logFifo, 0700); err != nil {
if err != nil { return fmt.Errorf("Failed to create log fifo: %v", err)
return err
} }
log.Debugf("Creating FIFO %s", metricsFifo)
err = syscall.Mkfifo(metricsFifo, 0700) log.Debugf("Creating metric FIFO %s", metricsFifo)
return err 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 { func (m *Machine) setupLogging(ctx context.Context) error {
@ -407,8 +371,7 @@ func (m *Machine) setupLogging(ctx context.Context) error {
return nil return nil
} }
err := createFifos(m.cfg.LogFifo, m.cfg.MetricsFifo) if err := createFifos(m.cfg.LogFifo, m.cfg.MetricsFifo); err != nil {
if err != nil {
m.logger.Errorf("Unable to set up logging: %s", err) m.logger.Errorf("Unable to set up logging: %s", err)
return err return err
} }
@ -423,16 +386,56 @@ func (m *Machine) setupLogging(ctx context.Context) error {
ShowLogOrigin: false, ShowLogOrigin: false,
} }
resp, err := m.client.PutLogger(ctx, &l) _, err := m.client.PutLogger(ctx, &l)
if err == nil { if err != nil {
m.logger.Printf("Configured VMM logging to %s, metrics to %s: %s", return err
m.cfg.LogFifo, m.cfg.MetricsFifo, resp.Error())
} }
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 { 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 { if err != nil {
m.logger.Errorf("PutMachineConfiguration returned %s", resp.Error()) m.logger.Errorf("PutMachineConfiguration returned %s", resp.Error())
return err return err
@ -469,7 +472,6 @@ func (m *Machine) createNetworkInterface(ctx context.Context, iface NetworkInter
IfaceID: &ifaceID, IfaceID: &ifaceID,
GuestMac: iface.MacAddress, GuestMac: iface.MacAddress,
HostDevName: iface.HostDevName, HostDevName: iface.HostDevName,
State: models.DeviceStateAttached,
AllowMmdsRequests: iface.AllowMDDS, AllowMmdsRequests: iface.AllowMDDS,
} }
@ -482,52 +484,25 @@ func (m *Machine) createNetworkInterface(ctx context.Context, iface NetworkInter
} }
// attachDrive attaches a secondary block device // 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 var err error
hostPath := StringValue(dev.PathOnHost)
_, err = os.Stat(dev.HostPath) _, err = os.Stat(hostPath)
if err != nil { if err != nil {
return err return err
} }
readOnly := true 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)
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)
if err == nil { 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 { } else {
m.logger.Errorf("Attach drive failed: %s: %s", dev.HostPath, err) m.logger.Errorf("Attach drive failed: %s: %s", hostPath, err)
} }
return 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 // addVsock adds a vsock to the instance
func (m *Machine) addVsock(ctx context.Context, dev VsockDevice) error { func (m *Machine) addVsock(ctx context.Context, dev VsockDevice) error {
vsockCfg := models.Vsock{ vsockCfg := models.Vsock{
@ -561,15 +536,17 @@ func (m *Machine) startInstance(ctx context.Context) error {
return err 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 // SetMetadata sets the machine's metadata for MDDS
func (m *Machine) SetMetadata(ctx context.Context, metadata interface{}) error { 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 { if err == nil {
var message string var message string
if respcreated != nil {
message = respcreated.Error()
}
if respnocontent != nil { if respnocontent != nil {
message = respnocontent.Error() message = respnocontent.Error()
} }

View File

@ -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
}