Merge pull request #1186 from nitkon/Hotplug

ppc64le: Fix hotplug issue
This commit is contained in:
James O. D. Hunt 2019-01-31 09:26:25 +00:00 committed by GitHub
commit 530360d515
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 81 additions and 1440 deletions

8
Gopkg.lock generated
View File

@ -204,10 +204,9 @@
version = "v0.3.3"
[[projects]]
digest = "1:58b18038f51b6f79865fd00e3d883949c9507ede577cd9c4e743ed56cc454122"
digest = "1:351337e3b022de09e72306f1f9711314cc4bd407c15e8d328e218c655fd55731"
name = "github.com/firecracker-microvm/firecracker-go-sdk"
packages = [
".",
"client",
"client/models",
"client/operations",
@ -377,11 +376,11 @@
revision = "3520598351bb3500a49ae9563f5539666ae0a27c"
[[projects]]
digest = "1:15f0da05538e2445b354c620555231448849b7ece222c45578668d0dfd6bec93"
digest = "1:270961b1d5e664d4939ffae00b990e256d92bb5039cae69208211a84c72fe5f5"
name = "github.com/intel/govmm"
packages = ["qemu"]
pruneopts = "NUT"
revision = "737f03de595e216116264cc74a58e5f2a1df789a"
revision = "78d079db6d1f3e32e3ed7578a54baa6257b058a7"
[[projects]]
digest = "1:590bfb6f8d5741fa38bb3022f55588fb7e06389f6b7b6692a965e0e5a4363fb1"
@ -701,7 +700,6 @@
"github.com/containernetworking/plugins/pkg/ns",
"github.com/dlespiau/covertool/pkg/cover",
"github.com/docker/go-units",
"github.com/firecracker-microvm/firecracker-go-sdk",
"github.com/firecracker-microvm/firecracker-go-sdk/client",
"github.com/firecracker-microvm/firecracker-go-sdk/client/models",
"github.com/firecracker-microvm/firecracker-go-sdk/client/operations",

View File

@ -52,7 +52,7 @@
[[constraint]]
name = "github.com/intel/govmm"
revision = "737f03de595e216116264cc74a58e5f2a1df789a"
revision = "78d079db6d1f3e32e3ed7578a54baa6257b058a7"
[[constraint]]
name = "github.com/kata-containers/agent"

View File

@ -1,155 +0,0 @@
// 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 firecracker
import (
"context"
"io"
"os"
"os/exec"
)
var defaultFirecrackerVMMCommandBuilder = VMCommandBuilder{}.
WithBin("firecracker").
WithStdin(os.Stdin).
WithStdout(os.Stdout).
WithStderr(os.Stderr)
// VMCommandBuilder is a utility for building an exec.Cmd that represents how to
// start the Firecracker VMM.
type VMCommandBuilder struct {
bin string
args []string
socketPath string
stdin io.Reader
stdout io.Writer
stderr io.Writer
}
// Args returns all args that will be passed to exec.Command
func (b VMCommandBuilder) Args() []string {
return b.args
}
// WithArgs specifies which arguments to pass through to the
// firecracker exec.Command
func (b VMCommandBuilder) WithArgs(args []string) VMCommandBuilder {
b.args = args
return b
}
// AddArgs will append the provided args to the given command
func (b VMCommandBuilder) AddArgs(args ...string) VMCommandBuilder {
b.args = append(b.args, args...)
return b
}
// Bin return the bin that was set
func (b VMCommandBuilder) Bin() string {
return b.bin
}
// WithBin specifies which binary for firecracker to use
func (b VMCommandBuilder) WithBin(bin string) VMCommandBuilder {
b.bin = bin
return b
}
// SocketPath returns the specified socket path
func (b VMCommandBuilder) SocketPath() []string {
if len(b.socketPath) == 0 {
return nil
}
return []string{
"--api-sock",
b.socketPath,
}
}
// WithSocketPath specifies the socket path to be used when
// creating the firecracker exec.Command
func (b VMCommandBuilder) WithSocketPath(path string) VMCommandBuilder {
b.socketPath = path
return b
}
// Stdout will return the stdout that will be used when creating
// the firecracker exec.Command
func (b VMCommandBuilder) Stdout() io.Writer {
return b.stdout
}
// WithStdout specifies which io.Writer to use in place of the
// os.Stdout in the firecracker exec.Command.
func (b VMCommandBuilder) WithStdout(stdout io.Writer) VMCommandBuilder {
b.stdout = stdout
return b
}
// Stderr will return the stderr that will be used when creating
// the firecracker exec.Command
func (b VMCommandBuilder) Stderr() io.Writer {
return b.stderr
}
// WithStderr specifies which io.Writer to use in place of the
// os.Stderr in the firecracker exec.Command.
func (b VMCommandBuilder) WithStderr(stderr io.Writer) VMCommandBuilder {
b.stderr = stderr
return b
}
// Stdin will return the stdin that will be used when creating
// the firecracker exec.Command
func (b VMCommandBuilder) Stdin() io.Reader {
return b.stdin
}
// WithStdin specifies which io.Reader to use in place of the
// os.Stdin in the firecracker exec.Command.
func (b VMCommandBuilder) WithStdin(stdin io.Reader) VMCommandBuilder {
b.stdin = stdin
return b
}
// Build will build a firecracker command using the specific arguments
// specified in the builder.
func (b VMCommandBuilder) Build(ctx context.Context) *exec.Cmd {
args := []string{}
if socketPath := b.SocketPath(); socketPath != nil {
args = append(args, socketPath...)
}
if v := b.Args(); v != nil {
args = append(args, v...)
}
cmd := exec.CommandContext(
ctx,
b.Bin(),
args...,
)
if stdout := b.Stdout(); stdout != nil {
cmd.Stdout = stdout
}
if stderr := b.Stderr(); stderr != nil {
cmd.Stderr = stderr
}
if stdin := b.Stdin(); stdin != nil {
cmd.Stdin = stdin
}
return cmd
}

View File

@ -1,29 +0,0 @@
// 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 firecracker provides a library to interact with the Firecracker API.
Firecracker is an open-source virtualization technology that is purpose-built
for creating and managing secure, multi-tenant containers and functions-based
services. See https://firecracker-microvm.github.io/ for more details.
This library requires Go 1.11 and can be used with Go modules.
BUG(aws): There are some Firecracker features that are not yet supported by the
SDK. These are tracked as GitHub issues with the firecracker-feature label:
https://github.com/firecracker-microvm/firecracker-go-sdk/issues?q=is%3Aissue+is%3Aopen+label%3Afirecracker-feature
This library is licensed under the Apache 2.0 License.
*/
package firecracker

View File

@ -1,65 +0,0 @@
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

@ -1,150 +0,0 @@
// 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 firecracker
import (
"context"
"net"
"net/http"
"time"
"github.com/go-openapi/strfmt"
"github.com/sirupsen/logrus"
"github.com/firecracker-microvm/firecracker-go-sdk/client"
models "github.com/firecracker-microvm/firecracker-go-sdk/client/models"
ops "github.com/firecracker-microvm/firecracker-go-sdk/client/operations"
httptransport "github.com/go-openapi/runtime/client"
)
const firecrackerRequestTimeout = 500 * time.Millisecond
// FirecrackerClient is a client for interacting with the Firecracker API
type FirecrackerClient struct {
client *client.Firecracker
}
// NewFirecrackerClient creates a FirecrackerClient
func NewFirecrackerClient(socketPath string, logger *logrus.Entry, debug bool) *FirecrackerClient {
httpClient := client.NewHTTPClient(strfmt.NewFormats())
socketTransport := &http.Transport{
DialContext: func(ctx context.Context, network, path string) (net.Conn, error) {
addr, err := net.ResolveUnixAddr("unix", socketPath)
if err != nil {
return nil, err
}
return net.DialUnix("unix", nil, addr)
},
}
transport := httptransport.New(client.DefaultHost, client.DefaultBasePath, client.DefaultSchemes)
transport.Transport = socketTransport
if debug {
transport.SetDebug(debug)
}
if logger != nil {
transport.SetLogger(logger)
}
httpClient.SetTransport(transport)
return &FirecrackerClient{client: httpClient}
}
func (f *FirecrackerClient) PutLogger(ctx context.Context, logger *models.Logger) (*ops.PutLoggerNoContent, error) {
timeout, cancel := context.WithTimeout(ctx, firecrackerRequestTimeout)
defer cancel()
loggerParams := ops.NewPutLoggerParamsWithContext(timeout)
loggerParams.SetBody(logger)
return f.client.Operations.PutLogger(loggerParams)
}
func (f *FirecrackerClient) PutMachineConfiguration(ctx context.Context, cfg *models.MachineConfiguration) (*ops.PutMachineConfigurationNoContent, error) {
timeout, cancel := context.WithTimeout(ctx, firecrackerRequestTimeout)
defer cancel()
mc := ops.NewPutMachineConfigurationParamsWithContext(timeout)
mc.SetBody(cfg)
return f.client.Operations.PutMachineConfiguration(mc)
}
func (f *FirecrackerClient) PutGuestBootSource(ctx context.Context, source *models.BootSource) (*ops.PutGuestBootSourceNoContent, error) {
timeout, cancel := context.WithTimeout(ctx, firecrackerRequestTimeout)
defer cancel()
bootSource := ops.NewPutGuestBootSourceParamsWithContext(timeout)
bootSource.SetBody(source)
return f.client.Operations.PutGuestBootSource(bootSource)
}
func (f *FirecrackerClient) PutGuestNetworkInterfaceByID(ctx context.Context, ifaceID string, ifaceCfg *models.NetworkInterface) (*ops.PutGuestNetworkInterfaceByIDNoContent, error) {
timeout, cancel := context.WithTimeout(ctx, firecrackerRequestTimeout)
defer cancel()
cfg := ops.NewPutGuestNetworkInterfaceByIDParamsWithContext(timeout)
cfg.SetBody(ifaceCfg)
cfg.SetIfaceID(ifaceID)
return f.client.Operations.PutGuestNetworkInterfaceByID(cfg)
}
func (f *FirecrackerClient) PutGuestDriveByID(ctx context.Context, driveID string, drive *models.Drive) (*ops.PutGuestDriveByIDNoContent, error) {
timeout, cancel := context.WithTimeout(ctx, 250*time.Millisecond)
defer cancel()
params := ops.NewPutGuestDriveByIDParamsWithContext(timeout)
params.SetDriveID(driveID)
params.SetBody(drive)
return f.client.Operations.PutGuestDriveByID(params)
}
func (f *FirecrackerClient) PutGuestVsockByID(ctx context.Context, vsockID string, vsock *models.Vsock) (*ops.PutGuestVsockByIDCreated, *ops.PutGuestVsockByIDNoContent, error) {
params := ops.NewPutGuestVsockByIDParams()
params.SetContext(ctx)
params.SetID(vsockID)
params.SetBody(vsock)
return f.client.Operations.PutGuestVsockByID(params)
}
func (f *FirecrackerClient) CreateSyncAction(ctx context.Context, info *models.InstanceActionInfo) (*ops.CreateSyncActionNoContent, error) {
params := ops.NewCreateSyncActionParams()
params.SetContext(ctx)
params.SetInfo(info)
return f.client.Operations.CreateSyncAction(params)
}
func (f *FirecrackerClient) PutMmds(ctx context.Context, metadata interface{}) (*ops.PutMmdsNoContent, error) {
params := ops.NewPutMmdsParams()
params.SetContext(ctx)
params.SetBody(metadata)
return f.client.Operations.PutMmds(params)
}
func (f *FirecrackerClient) GetMachineConfig() (*ops.GetMachineConfigOK, error) {
p := ops.NewGetMachineConfigParams()
p.SetTimeout(firecrackerRequestTimeout)
return f.client.Operations.GetMachineConfig(p)
}

View File

@ -1,237 +0,0 @@
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

@ -1,607 +0,0 @@
// 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 firecracker
import (
"context"
"errors"
"fmt"
"io"
"os"
"os/exec"
"os/signal"
"strconv"
"syscall"
"time"
models "github.com/firecracker-microvm/firecracker-go-sdk/client/models"
ops "github.com/firecracker-microvm/firecracker-go-sdk/client/operations"
log "github.com/sirupsen/logrus"
)
const (
userAgent = "firecracker-go-sdk"
)
// 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)
PutGuestBootSource(ctx context.Context, source *models.BootSource) (*ops.PutGuestBootSourceNoContent, error)
PutGuestNetworkInterfaceByID(ctx context.Context, ifaceID string, ifaceCfg *models.NetworkInterface) (*ops.PutGuestNetworkInterfaceByIDNoContent, 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)
CreateSyncAction(ctx context.Context, info *models.InstanceActionInfo) (*ops.CreateSyncActionNoContent, error)
PutMmds(ctx context.Context, metadata interface{}) (*ops.PutMmdsNoContent, error)
GetMachineConfig() (*ops.GetMachineConfigOK, error)
}
// Config is a collection of user-configurable VMM settings
type Config struct {
// SocketPath defines the file path where the Firecracker control socket
// should be created.
SocketPath string
// LogFifo defines the file path where the Firecracker log named-pipe should
// be located.
LogFifo string
// LogLevel defines the verbosity of Firecracker logging. Valid values are
// "Error", "Warning", "Info", and "Debug", and are case-sensitive.
LogLevel string
// MetricsFifo defines the file path where the Firecracker metrics
// named-pipe should be located.
MetricsFifo string
// KernelImagePath defines the file path where the kernel image is located.
// The kernel image must be an uncompressed ELF image.
KernelImagePath string
// KernelArgs defines the command-line arguments that should be passed to
// the kernel.
KernelArgs string
// Drives specifies BlockDevices that should be made available to the
// microVM.
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 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.
DisableValidation bool
}
// Validate will ensure that the required fields are set and that
// the fields are valid values.
func (cfg *Config) Validate() error {
if cfg.DisableValidation {
return nil
}
if _, err := os.Stat(cfg.KernelImagePath); err != nil {
return fmt.Errorf("failed to stat kernal image path, %q: %v", cfg.KernelImagePath, 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:
if _, err := os.Stat(cfg.SocketPath); err == nil {
return fmt.Errorf("socket %s already exists", cfg.SocketPath)
}
return nil
}
// Machine is the main object for manipulating Firecracker microVMs
type Machine struct {
cfg Config
client Firecracker
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
func (m *Machine) Logger() *log.Entry {
return m.logger.WithField("subsystem", userAgent)
}
// NetworkInterface represents a Firecracker microVM's network interface.
type NetworkInterface struct {
// MacAddress defines the MAC address that should be assigned to the network
// interface inside the microVM.
MacAddress string
// HostDevName defines the file path of the tap device on the host.
HostDevName string
// AllowMMDS makes the Firecracker MMDS available on this network interface.
AllowMDDS bool
}
// VsockDevice represents a vsock connection between the host and the guest
// microVM.
type VsockDevice struct {
// Path defines the filesystem path of the vsock device on the host.
Path string
// CID defines the 32-bit Context Identifier for the vsock device. See
// the vsock(7) manual page for more information.
CID uint32
}
// SocketPath returns the filesystem path to the socket used for VMM
// communication
func (m Machine) socketPath() string {
return m.cfg.SocketPath
}
// LogFile returns the filesystem path of the VMM log
func (m Machine) LogFile() string {
return m.cfg.LogFifo
}
// LogLevel returns the VMM log level.
func (m Machine) LogLevel() string {
return m.cfg.LogLevel
}
// NewMachine initializes a new Machine instance and performs validation of the
// provided Config.
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()
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 {
opt(m)
}
if m.client == nil {
m.client = NewFirecrackerClient(cfg.SocketPath, m.logger, cfg.Debug)
}
m.cfg = cfg
m.logger.Debug("Called NewMachine()")
return m, nil
}
// 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
}
return m.StartInstance(ctx)
}
// 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
}
}
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
}
}
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)
}
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))
}
return nil
}
// startVMM starts the firecracker vmm process and configures logging.
func (m *Machine) startVMM(ctx context.Context) error {
m.logger.Printf("Called startVMM(), setting up a VMM on %s", m.cfg.SocketPath)
m.errCh = make(chan error)
err := m.cmd.Start()
if err != nil {
m.logger.Errorf("Failed to start VMM: %s", err)
return err
}
m.logger.Debugf("VMM started socket path is %s", m.cfg.SocketPath)
go func() {
if err := m.cmd.Wait(); err != nil {
m.logger.Warnf("firecracker exited: %s", err.Error())
} else {
m.logger.Printf("firecracker exited: status=0")
}
os.Remove(m.cfg.SocketPath)
os.Remove(m.cfg.LogFifo)
os.Remove(m.cfg.MetricsFifo)
m.errCh <- err
}()
// Set up a signal handler and pass INT, QUIT, and TERM through to firecracker
vmchan := make(chan error)
sigchan := make(chan os.Signal)
signal.Notify(sigchan, os.Interrupt,
syscall.SIGQUIT,
syscall.SIGTERM,
syscall.SIGHUP,
syscall.SIGABRT)
m.logger.Debugf("Setting up signal handler")
go func() {
select {
case sig := <-sigchan:
m.logger.Printf("Caught signal %s", sig)
m.cmd.Process.Signal(sig)
case err = <-vmchan:
m.errCh <- err
}
}()
// Wait for firecracker to initialize:
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 err
}
m.logger.Debugf("returning from startVMM()")
return nil
}
//StopVMM stops the current VMM.
func (m *Machine) StopVMM() error {
return m.stopVMM()
}
func (m *Machine) stopVMM() error {
if m.cmd != nil && m.cmd.Process != nil {
log.Debug("stopVMM(): sending sigterm to firecracker")
return m.cmd.Process.Signal(syscall.SIGTERM)
}
log.Debug("stopVMM(): no firecracker process running, not sending a signal")
// don't return an error if the process isn't even running
return nil
}
// createFifos sets up the firecracker logging and metrics FIFOs
func createFifos(logFifo, metricsFifo string) error {
log.Debugf("Creating FIFO %s", logFifo)
if err := syscall.Mkfifo(logFifo, 0700); err != nil {
return fmt.Errorf("Failed to create log fifo: %v", 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 {
if len(m.cfg.LogFifo) == 0 || len(m.cfg.MetricsFifo) == 0 {
// No logging configured
m.logger.Printf("VMM logging and metrics disabled.")
return nil
}
if err := createFifos(m.cfg.LogFifo, m.cfg.MetricsFifo); err != nil {
m.logger.Errorf("Unable to set up logging: %s", err)
return err
}
m.logger.Debug("Created metrics and logging fifos.")
l := models.Logger{
LogFifo: m.cfg.LogFifo,
Level: m.cfg.LogLevel,
MetricsFifo: m.cfg.MetricsFifo,
ShowLevel: true,
ShowLogOrigin: false,
}
_, err := m.client.PutLogger(ctx, &l)
if err != nil {
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)
if err != nil {
m.logger.Errorf("PutMachineConfiguration returned %s", resp.Error())
return err
}
m.logger.Debug("PutMachineConfiguration returned")
err = m.refreshMachineConfig()
if err != nil {
log.Errorf("Unable to inspect Firecracker MachineConfig. Continuing anyway. %s", err)
}
m.logger.Debug("createMachine returning")
return err
}
func (m *Machine) createBootSource(ctx context.Context, imagePath, kernelArgs string) error {
bsrc := models.BootSource{
KernelImagePath: &imagePath,
BootArgs: kernelArgs,
}
resp, err := m.client.PutGuestBootSource(ctx, &bsrc)
if err == nil {
m.logger.Printf("PutGuestBootSource: %s", resp.Error())
}
return err
}
func (m *Machine) createNetworkInterface(ctx context.Context, iface NetworkInterface, iid int) error {
ifaceID := strconv.Itoa(iid)
m.logger.Printf("Attaching NIC %s (hwaddr %s) at index %s", iface.HostDevName, iface.MacAddress, ifaceID)
ifaceCfg := models.NetworkInterface{
IfaceID: &ifaceID,
GuestMac: iface.MacAddress,
HostDevName: iface.HostDevName,
AllowMmdsRequests: iface.AllowMDDS,
}
resp, err := m.client.PutGuestNetworkInterfaceByID(ctx, ifaceID, &ifaceCfg)
if err == nil {
m.logger.Printf("PutGuestNetworkInterfaceByID: %s", resp.Error())
}
return err
}
// attachDrive attaches a secondary block device
func (m *Machine) attachDrive(ctx context.Context, dev models.Drive) error {
var err error
hostPath := StringValue(dev.PathOnHost)
_, err = os.Stat(hostPath)
if err != nil {
return err
}
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", hostPath, respNoContent.Error())
} else {
m.logger.Errorf("Attach drive failed: %s: %s", hostPath, err)
}
return err
}
// addVsock adds a vsock to the instance
func (m *Machine) addVsock(ctx context.Context, dev VsockDevice) error {
vsockCfg := models.Vsock{
GuestCid: int64(dev.CID),
ID: &dev.Path,
}
resp, _, err := m.client.PutGuestVsockByID(ctx, dev.Path, &vsockCfg)
if err != nil {
return err
}
m.logger.Debugf("Attach vsock %s successful: %s", dev.Path, resp.Error())
return nil
}
// StartInstance starts the Firecracker microVM
func (m *Machine) StartInstance(ctx context.Context) error {
return m.startInstance(ctx)
}
func (m *Machine) startInstance(ctx context.Context) error {
info := models.InstanceActionInfo{
ActionType: models.InstanceActionInfoActionTypeInstanceStart,
}
resp, err := m.client.CreateSyncAction(ctx, &info)
if err == nil {
m.logger.Printf("startInstance successful: %s", resp.Error())
} else {
m.logger.Errorf("Starting instance: %s", 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
func (m *Machine) SetMetadata(ctx context.Context, metadata interface{}) error {
respnocontent, err := m.client.PutMmds(ctx, metadata)
if err == nil {
var message string
if respnocontent != nil {
message = respnocontent.Error()
}
m.logger.Printf("SetMetadata successful: %s", message)
} else {
m.logger.Errorf("Setting metadata: %s", err)
}
return err
}
// refreshMachineConfig synchronizes our cached representation of the machine configuration
// with that reported by the Firecracker API
func (m *Machine) refreshMachineConfig() error {
resp, err := m.client.GetMachineConfig()
if err != nil {
return err
}
m.logger.Infof("refreshMachineConfig: %s", resp.Error())
m.machineConfig = *resp.Payload
return nil
}
// waitForSocket waits for the given file to exist
func (m *Machine) waitForSocket(timeout time.Duration, exitchan chan error) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
done := make(chan error)
ticker := time.NewTicker(10 * time.Millisecond)
go func() {
for {
select {
case <-ctx.Done():
done <- ctx.Err()
return
case err := <-exitchan:
done <- err
return
case <-ticker.C:
if _, err := os.Stat(m.cfg.SocketPath); err != nil {
continue
}
// Send test HTTP request to make sure socket is available
if _, err := m.client.GetMachineConfig(); err != nil {
continue
}
done <- nil
return
}
}
}()
return <-done
}

View File

@ -1,49 +0,0 @@
// 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 firecracker
import (
"os/exec"
"github.com/sirupsen/logrus"
)
// Opt represents a functional option to help modify functionality of a Machine.
type Opt func(*Machine)
// WithClient will use the client in place rather than the client constructed
// during bootstrapping of the machine. This option is useful for mocking out
// tests.
func WithClient(client Firecracker) Opt {
return func(machine *Machine) {
machine.client = client
}
}
// WithLogger will allow for the Machine to use the provided logger.
func WithLogger(logger *logrus.Entry) Opt {
return func(machine *Machine) {
machine.logger = logger
}
}
// WithProcessRunner will allow for a specific command to be run instead of the
// default firecracker command.
// For example, this could be used to instead call the jailer instead of
// firecracker directly.
func WithProcessRunner(cmd *exec.Cmd) Opt {
return func(machine *Machine) {
machine.cmd = cmd
}
}

View File

@ -1,46 +0,0 @@
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
}

View File

@ -1,22 +0,0 @@
// 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.
//go:generate find ./client ! -name swagger.yaml -type f -delete
// --skip-validation is used in the command-lines below to remove the network dependency that the swagger generator has
// in attempting to validate that the email address specified in the yaml file is valid.
//go:generate docker run --rm --net=none -v $PWD:/work -w /work quay.io/goswagger/swagger generate model -f ./client/swagger.yaml --model-package=client/models --client-package=client --copyright-file=COPYRIGHT_HEADER --skip-validation
//go:generate docker run --rm --net=none -v $PWD:/work -w /work quay.io/goswagger/swagger generate client -f ./client/swagger.yaml --model-package=client/models --client-package=client --copyright-file=COPYRIGHT_HEADER --skip-validation
package firecracker

View File

@ -51,18 +51,18 @@ func CreateCloudInitISO(ctx context.Context, scratchDir, isoPath string,
err := os.MkdirAll(dataDirPath, 0750)
if err != nil {
return fmt.Errorf("Unable to create config drive directory %s : %v",
return fmt.Errorf("unable to create config drive directory %s : %v",
dataDirPath, err)
}
err = ioutil.WriteFile(metaDataPath, metaData, 0644)
if err != nil {
return fmt.Errorf("Unable to create %s : %v", metaDataPath, err)
return fmt.Errorf("unable to create %s : %v", metaDataPath, err)
}
err = ioutil.WriteFile(userDataPath, userData, 0644)
if err != nil {
return fmt.Errorf("Unable to create %s : %v", userDataPath, err)
return fmt.Errorf("unable to create %s : %v", userDataPath, err)
}
cmd := exec.CommandContext(ctx, "xorriso", "-as", "mkisofs", "-R", "-V", "config-2",
@ -70,7 +70,7 @@ func CreateCloudInitISO(ctx context.Context, scratchDir, isoPath string,
cmd.SysProcAttr = attr
err = cmd.Run()
if err != nil {
return fmt.Errorf("Unable to create cloudinit iso image %v", err)
return fmt.Errorf("unable to create cloudinit iso image %v", err)
}
return nil

View File

@ -260,7 +260,7 @@ func (fsdev FSDevice) QemuParams(config *Config) []string {
var deviceParams []string
var qemuParams []string
deviceParams = append(deviceParams, fmt.Sprintf("%s", fsdev.Driver))
deviceParams = append(deviceParams, string(fsdev.Driver))
if s := fsdev.Driver.disableModern(fsdev.DisableModern); s != "" {
deviceParams = append(deviceParams, fmt.Sprintf(",%s", s))
}
@ -346,7 +346,7 @@ func (cdev CharDevice) QemuParams(config *Config) []string {
var deviceParams []string
var qemuParams []string
deviceParams = append(deviceParams, fmt.Sprintf("%s", cdev.Driver))
deviceParams = append(deviceParams, string(cdev.Driver))
if s := cdev.Driver.disableModern(cdev.DisableModern); s != "" {
deviceParams = append(deviceParams, fmt.Sprintf(",%s", s))
}
@ -534,7 +534,7 @@ func (netdev NetDevice) QemuNetdevParams(config *Config) []string {
netdevParams = append(netdevParams, netdev.Type.QemuNetdevParam())
netdevParams = append(netdevParams, fmt.Sprintf(",id=%s", netdev.ID))
if netdev.VHost == true {
if netdev.VHost {
netdevParams = append(netdevParams, ",vhost=on")
if len(netdev.VhostFDs) > 0 {
var fdParams []string
@ -627,7 +627,7 @@ func (dev SerialDevice) QemuParams(config *Config) []string {
var deviceParams []string
var qemuParams []string
deviceParams = append(deviceParams, fmt.Sprintf("%s", dev.Driver))
deviceParams = append(deviceParams, string(dev.Driver))
if s := dev.Driver.disableModern(dev.DisableModern); s != "" {
deviceParams = append(deviceParams, fmt.Sprintf(",%s", s))
}
@ -705,16 +705,16 @@ func (blkdev BlockDevice) QemuParams(config *Config) []string {
var deviceParams []string
var qemuParams []string
deviceParams = append(deviceParams, fmt.Sprintf("%s", blkdev.Driver))
deviceParams = append(deviceParams, string(blkdev.Driver))
if s := blkdev.Driver.disableModern(blkdev.DisableModern); s != "" {
deviceParams = append(deviceParams, fmt.Sprintf(",%s", s))
}
deviceParams = append(deviceParams, fmt.Sprintf(",drive=%s", blkdev.ID))
if blkdev.SCSI == false {
if !blkdev.SCSI {
deviceParams = append(deviceParams, ",scsi=off")
}
if blkdev.WCE == false {
if !blkdev.WCE {
deviceParams = append(deviceParams, ",config-wce=off")
}
@ -842,11 +842,7 @@ type VFIODevice struct {
// Valid returns true if the VFIODevice structure is valid and complete.
func (vfioDev VFIODevice) Valid() bool {
if vfioDev.BDF == "" {
return false
}
return true
return vfioDev.BDF != ""
}
// QemuParams returns the qemu parameters built out of this vfio device.
@ -889,11 +885,7 @@ type SCSIController struct {
// Valid returns true if the SCSIController structure is valid and complete.
func (scsiCon SCSIController) Valid() bool {
if scsiCon.ID == "" {
return false
}
return true
return scsiCon.ID != ""
}
// QemuParams returns the qemu parameters built out of this SCSIController device.
@ -910,7 +902,7 @@ func (scsiCon SCSIController) QemuParams(config *Config) []string {
devParams = append(devParams, fmt.Sprintf("addr=%s", scsiCon.Addr))
}
if s := driver.disableModern(scsiCon.DisableModern); s != "" {
devParams = append(devParams, fmt.Sprintf("%s", s))
devParams = append(devParams, s)
}
if scsiCon.IOThread != "" {
devParams = append(devParams, fmt.Sprintf("iothread=%s", scsiCon.IOThread))
@ -1057,7 +1049,7 @@ func (vsock VSOCKDevice) QemuParams(config *Config) []string {
var qemuParams []string
driver := VHostVSock
deviceParams = append(deviceParams, fmt.Sprintf("%s", driver))
deviceParams = append(deviceParams, string(driver))
if s := driver.disableModern(vsock.DisableModern); s != "" {
deviceParams = append(deviceParams, fmt.Sprintf(",%s", s))
}
@ -1094,11 +1086,7 @@ type RngDevice struct {
// Valid returns true if the RngDevice structure is valid and complete.
func (v RngDevice) Valid() bool {
if v.ID == "" {
return false
}
return true
return v.ID != ""
}
// QemuParams returns the qemu parameters built out of the RngDevice.
@ -1174,7 +1162,7 @@ func (b BalloonDevice) QemuParams(_ *Config) []string {
deviceParams = append(deviceParams, "deflate-on-oom=off")
}
if s := driver.disableModern(b.DisableModern); s != "" {
deviceParams = append(deviceParams, fmt.Sprintf("%s", s))
deviceParams = append(deviceParams, string(s))
}
qemuParams = append(qemuParams, "-device")
qemuParams = append(qemuParams, strings.Join(deviceParams, ","))
@ -1184,11 +1172,7 @@ func (b BalloonDevice) QemuParams(_ *Config) []string {
// Valid returns true if the balloonDevice structure is valid and complete.
func (b BalloonDevice) Valid() bool {
if b.ID == "" {
return false
}
return true
return b.ID != ""
}
// RTCBaseType is the qemu RTC base time type.
@ -1523,15 +1507,15 @@ func (config *Config) appendCPUModel() {
func (config *Config) appendQMPSockets() {
for _, q := range config.QMPSockets {
if q.Valid() == false {
if !q.Valid() {
continue
}
qmpParams := append([]string{}, fmt.Sprintf("%s:", q.Type))
qmpParams = append(qmpParams, fmt.Sprintf("%s", q.Name))
if q.Server == true {
qmpParams = append(qmpParams, q.Name)
if q.Server {
qmpParams = append(qmpParams, ",server")
if q.NoWait == true {
if q.NoWait {
qmpParams = append(qmpParams, ",nowait")
}
}
@ -1543,7 +1527,7 @@ func (config *Config) appendQMPSockets() {
func (config *Config) appendDevices() {
for _, d := range config.Devices {
if d.Valid() == false {
if !d.Valid() {
continue
}
@ -1611,7 +1595,7 @@ func (config *Config) appendCPUs() error {
}
func (config *Config) appendRTC() {
if config.RTC.Valid() == false {
if !config.RTC.Valid() {
return
}
@ -1663,7 +1647,7 @@ func (config *Config) appendKernel() {
}
func (config *Config) appendMemoryKnobs() {
if config.Knobs.HugePages == true {
if config.Knobs.HugePages {
if config.Memory.Size != "" {
dimmName := "dimm1"
objMemParam := "memory-backend-file,id=" + dimmName + ",size=" + config.Memory.Size + ",mem-path=/dev/hugepages,share=on,prealloc=on"
@ -1675,7 +1659,7 @@ func (config *Config) appendMemoryKnobs() {
config.qemuParams = append(config.qemuParams, "-numa")
config.qemuParams = append(config.qemuParams, numaMemParam)
}
} else if config.Knobs.MemPrealloc == true {
} else if config.Knobs.MemPrealloc {
if config.Memory.Size != "" {
dimmName := "dimm1"
objMemParam := "memory-backend-ram,id=" + dimmName + ",size=" + config.Memory.Size + ",prealloc=on"
@ -1687,11 +1671,11 @@ func (config *Config) appendMemoryKnobs() {
config.qemuParams = append(config.qemuParams, "-numa")
config.qemuParams = append(config.qemuParams, numaMemParam)
}
} else if config.Knobs.FileBackedMem == true {
} else if config.Knobs.FileBackedMem {
if config.Memory.Size != "" && config.Memory.Path != "" {
dimmName := "dimm1"
objMemParam := "memory-backend-file,id=" + dimmName + ",size=" + config.Memory.Size + ",mem-path=" + config.Memory.Path
if config.Knobs.FileBackedMemShared == true {
if config.Knobs.FileBackedMemShared {
objMemParam += ",share=on"
}
numaMemParam := "node,memdev=" + dimmName
@ -1706,45 +1690,45 @@ func (config *Config) appendMemoryKnobs() {
}
func (config *Config) appendKnobs() {
if config.Knobs.NoUserConfig == true {
if config.Knobs.NoUserConfig {
config.qemuParams = append(config.qemuParams, "-no-user-config")
}
if config.Knobs.NoDefaults == true {
if config.Knobs.NoDefaults {
config.qemuParams = append(config.qemuParams, "-nodefaults")
}
if config.Knobs.NoGraphic == true {
if config.Knobs.NoGraphic {
config.qemuParams = append(config.qemuParams, "-nographic")
}
if config.Knobs.Daemonize == true {
if config.Knobs.Daemonize {
config.qemuParams = append(config.qemuParams, "-daemonize")
}
config.appendMemoryKnobs()
if config.Knobs.Realtime == true {
if config.Knobs.Realtime {
config.qemuParams = append(config.qemuParams, "-realtime")
// This path is redundant as the default behaviour is locked memory
// Realtime today does not control any other feature even though
// other features may be added in the future
// https://lists.gnu.org/archive/html/qemu-devel/2012-12/msg03330.html
if config.Knobs.Mlock == true {
if config.Knobs.Mlock {
config.qemuParams = append(config.qemuParams, "mlock=on")
} else {
config.qemuParams = append(config.qemuParams, "mlock=off")
}
} else {
// In order to turn mlock off we need the -realtime option as well
if config.Knobs.Mlock == false {
if !config.Knobs.Mlock {
//Enable realtime anyway just to get the right swapping behaviour
config.qemuParams = append(config.qemuParams, "-realtime")
config.qemuParams = append(config.qemuParams, "mlock=off")
}
}
if config.Knobs.Stopped == true {
if config.Knobs.Stopped {
config.qemuParams = append(config.qemuParams, "-S")
}
}

View File

@ -329,14 +329,14 @@ func (q *QMP) errorDesc(errorData interface{}) (string, error) {
// convert error to json
data, err := json.Marshal(errorData)
if err != nil {
return "", fmt.Errorf("Unable to extract error information: %v", err)
return "", fmt.Errorf("unable to extract error information: %v", err)
}
// see: https://github.com/qemu/qemu/blob/stable-2.12/qapi/qmp-dispatch.c#L125
var qmpErr map[string]string
// convert json to qmpError
if err = json.Unmarshal(data, &qmpErr); err != nil {
return "", fmt.Errorf("Unable to convert json to qmpError: %v", err)
return "", fmt.Errorf("unable to convert json to qmpError: %v", err)
}
return qmpErr["desc"], nil
@ -404,7 +404,7 @@ func (q *QMP) writeNextQMPCommand(cmdQueue *list.List) {
encodedCmd, err := json.Marshal(&cmdData)
if err != nil {
cmd.res <- qmpResult{
err: fmt.Errorf("Unable to marhsall command %s: %v",
err: fmt.Errorf("unable to marhsall command %s: %v",
cmd.name, err),
}
cmdQueue.Remove(cmdEl)
@ -419,7 +419,7 @@ func (q *QMP) writeNextQMPCommand(cmdQueue *list.List) {
if err != nil {
cmd.res <- qmpResult{
err: fmt.Errorf("Unable to write command to qmp socket %v", err),
err: fmt.Errorf("unable to write command to qmp socket %v", err),
}
cmdQueue.Remove(cmdEl)
}
@ -525,7 +525,7 @@ func (q *QMP) mainLoop() {
}
/* #nosec */
_ = q.conn.Close()
_ = <-fromVMCh
<-fromVMCh
failOutstandingCommands(cmdQueue)
close(q.disconnectedCh)
}()
@ -689,12 +689,12 @@ func QMPStart(ctx context.Context, socket string, cfg QMPConfig, disconnectedCh
case <-ctx.Done():
q.Shutdown()
<-disconnectedCh
return nil, nil, fmt.Errorf("Canceled by caller")
return nil, nil, fmt.Errorf("canceled by caller")
case <-disconnectedCh:
return nil, nil, fmt.Errorf("Lost connection to VM")
return nil, nil, fmt.Errorf("lost connection to VM")
case q.version = <-connectedCh:
if q.version == nil {
return nil, nil, fmt.Errorf("Failed to find QMP version information")
return nil, nil, fmt.Errorf("failed to find QMP version information")
}
}
@ -860,7 +860,7 @@ func (q *QMP) ExecuteSCSIDeviceAdd(ctx context.Context, blockdevID, devID, drive
}
if !isSCSIDriver {
return fmt.Errorf("Invalid SCSI driver provided %s", driver)
return fmt.Errorf("invalid SCSI driver provided %s", driver)
}
args := map[string]interface{}{
@ -1144,14 +1144,21 @@ func (q *QMP) ExecutePCIVFIOMediatedDeviceAdd(ctx context.Context, devID, sysfsd
// ExecuteCPUDeviceAdd adds a CPU to a QEMU instance using the device_add command.
// driver is the CPU model, cpuID must be a unique ID to identify the CPU, socketID is the socket number within
// node/board the CPU belongs to, coreID is the core number within socket the CPU belongs to, threadID is the
// thread number within core the CPU belongs to.
// thread number within core the CPU belongs to. Note that socketID and threadID are not a requirement for
// architecures like ppc64le.
func (q *QMP) ExecuteCPUDeviceAdd(ctx context.Context, driver, cpuID, socketID, coreID, threadID, romfile string) error {
args := map[string]interface{}{
"driver": driver,
"id": cpuID,
"socket-id": socketID,
"core-id": coreID,
"thread-id": threadID,
"driver": driver,
"id": cpuID,
"core-id": coreID,
}
if socketID != "" {
args["socket-id"] = socketID
}
if threadID != "" {
args["thread-id"] = threadID
}
if isVirtioPCI[DeviceDriver(driver)] {
@ -1171,13 +1178,13 @@ func (q *QMP) ExecuteQueryHotpluggableCPUs(ctx context.Context) ([]HotpluggableC
// convert response to json
data, err := json.Marshal(response)
if err != nil {
return nil, fmt.Errorf("Unable to extract CPU information: %v", err)
return nil, fmt.Errorf("unable to extract CPU information: %v", err)
}
var cpus []HotpluggableCPU
// convert json to []HotpluggableCPU
if err = json.Unmarshal(data, &cpus); err != nil {
return nil, fmt.Errorf("Unable to convert json to hotpluggable CPU: %v", err)
return nil, fmt.Errorf("unable to convert json to hotpluggable CPU: %v", err)
}
return cpus, nil
@ -1211,7 +1218,7 @@ func (q *QMP) ExecQueryMemoryDevices(ctx context.Context) ([]MemoryDevices, erro
// convert response to json
data, err := json.Marshal(response)
if err != nil {
return nil, fmt.Errorf("Unable to extract memory devices information: %v", err)
return nil, fmt.Errorf("unable to extract memory devices information: %v", err)
}
var memoryDevices []MemoryDevices
@ -1235,7 +1242,7 @@ func (q *QMP) ExecQueryCpus(ctx context.Context) ([]CPUInfo, error) {
// convert response to json
data, err := json.Marshal(response)
if err != nil {
return nil, fmt.Errorf("Unable to extract memory devices information: %v", err)
return nil, fmt.Errorf("unable to extract memory devices information: %v", err)
}
var cpuInfo []CPUInfo
@ -1259,7 +1266,7 @@ func (q *QMP) ExecQueryCpusFast(ctx context.Context) ([]CPUInfoFast, error) {
// convert response to json
data, err := json.Marshal(response)
if err != nil {
return nil, fmt.Errorf("Unable to extract memory devices information: %v", err)
return nil, fmt.Errorf("unable to extract memory devices information: %v", err)
}
var cpuInfoFast []CPUInfoFast
@ -1434,12 +1441,12 @@ func (q *QMP) ExecuteQueryMigration(ctx context.Context) (MigrationStatus, error
data, err := json.Marshal(response)
if err != nil {
return MigrationStatus{}, fmt.Errorf("Unable to extract migrate status information: %v", err)
return MigrationStatus{}, fmt.Errorf("unable to extract migrate status information: %v", err)
}
var status MigrationStatus
if err = json.Unmarshal(data, &status); err != nil {
return MigrationStatus{}, fmt.Errorf("Unable to convert migrate status information: %v", err)
return MigrationStatus{}, fmt.Errorf("unable to convert migrate status information: %v", err)
}
return status, nil

View File

@ -1077,6 +1077,11 @@ func (q *qemu) hotplugAddCPUs(amount uint32) (uint32, error) {
return 0, fmt.Errorf("failed to query hotpluggable CPUs: %v", err)
}
machine, err := q.arch.machine()
if err != nil {
return 0, fmt.Errorf("failed to query machine type: %v", err)
}
var hotpluggedVCPUs uint32
for _, hc := range hotpluggableVCPUs {
// qom-path is the path to the CPU, non-empty means that this CPU is already in use
@ -1090,6 +1095,13 @@ func (q *qemu) hotplugAddCPUs(amount uint32) (uint32, error) {
socketID := fmt.Sprintf("%d", hc.Properties.Socket)
coreID := fmt.Sprintf("%d", hc.Properties.Core)
threadID := fmt.Sprintf("%d", hc.Properties.Thread)
// If CPU type is IBM pSeries, we do not set socketID and threadID
if machine.Type == "pseries" {
socketID = ""
threadID = ""
}
if err := q.qmpMonitorCh.qmp.ExecuteCPUDeviceAdd(q.qmpMonitorCh.ctx, driver, cpuID, socketID, coreID, threadID, romFile); err != nil {
// don't fail, let's try with other CPU
continue