Repository: firecracker-microvm/firecracker-containerd
Pull request #156 (2019-04-08 21:07:57)

Initial FC runner
*Description of changes:*
This PR implement Start/Stop VM in GRPC plugin.

Notes:
- `helpers` and `vsock` files are copy-pasted from Runtime. They need to be refactored in future.
- I didn't modify Runtime/Agent code, but eventually they need to be refactored in favor GRPC plugin.
- This is initial implementation, lots of things need to be done.

TODO:
- Update example to use GRPC plugin
- Generate containerd events (vm start, vm exit)
- Add fake devices reservation (copy-paste from Runtime): https://github.com/firecracker-microvm/firecracker-containerd/pull/154
- Remove TODOs after merging PRs: https://github.com/firecracker-microvm/firecracker-go-sdk/pull/91, https://github.com/firecracker-microvm/firecracker-go-sdk/pull/90
- Throw away obsolete code from Runtime
- Figure out how to reuse containerd Client conn object with grpc plugins
- Handle containerd shutdown properly? (shutdown FC instances)
- Add collision detection for generated IDs.
- Think about persistency.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.


samuelkarp: Can you split https://github.com/firecracker-microvm/firecracker-containerd/commit/82bfc7355089e7e6465fef0583554724d6b9c697 out into a separate PR and also explain a bit more about what it's doing? :smile: 
mxpv: @samuelkarp This is a fix for Travis error: `firecracker-control/local.go:124: G404: Use of weak random number generator (math/rand instead of crypto/rand) (gosec)`
I can't split it because it depends on changes in this PR and this one will remain failed. 

Patch:
diff --git a/firecracker-control/common.go b/firecracker-control/common.go
new file mode 100644
index 0000000..9b7a4eb
--- /dev/null
+++ b/firecracker-control/common.go
@@ -0,0 +1,34 @@
+// Copyright 2018-2019 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 service
+
+import (
+	"time"
+
+	"github.com/pkg/errors"
+)
+
+const (
+	localPluginID = "fc-control"
+	grpcPluginID  = "fc-control-service"
+)
+
+var (
+	// ErrVMNotFound means that a VM with the given ID not found
+	ErrVMNotFound = errors.New("vm not found")
+)
+
+const (
+	firecrackerStartTimeout = 5 * time.Second
+)
diff --git a/firecracker-control/helpers.go b/firecracker-control/helpers.go
new file mode 100644
index 0000000..8c0af45
--- /dev/null
+++ b/firecracker-control/helpers.go
@@ -0,0 +1,173 @@
+// Copyright 2018-2019 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 service
+
+import (
+	"errors"
+	"time"
+
+	"github.com/firecracker-microvm/firecracker-go-sdk"
+	models "github.com/firecracker-microvm/firecracker-go-sdk/client/models"
+
+	"github.com/firecracker-microvm/firecracker-containerd/proto"
+)
+
+// overrideVMConfigFromTaskOpts overrides the firecracker VM config for the
+// task with values set in the protobuf message.
+func overrideVMConfigFromTaskOpts(
+	cfg firecracker.Config,
+	vmConfig *proto.FirecrackerConfig,
+	drivesBuilder firecracker.DrivesBuilder,
+) (firecracker.Config, firecracker.DrivesBuilder, error) {
+	if vmConfig == nil {
+		return cfg, drivesBuilder, nil
+	}
+	var err error
+	if vmConfig.KernelImagePath != "" {
+		// If kernel image path is set, override it.
+		cfg.KernelImagePath = vmConfig.KernelImagePath
+	}
+	if vmConfig.KernelArgs != "" {
+		// If kernel args are set, override it.
+		cfg.KernelArgs = vmConfig.KernelArgs
+	}
+	// Attach network interface specified in the config.
+	if len(vmConfig.NetworkInterfaces) > 0 {
+		nwIfaces := make([]firecracker.NetworkInterface, len(vmConfig.NetworkInterfaces))
+		for i, nw := range vmConfig.NetworkInterfaces {
+			nwIfaces[i] = networkConfigFromProto(nw)
+		}
+		cfg.NetworkInterfaces = nwIfaces
+	}
+	if vmConfig.MachineCfg != nil {
+		cfg.MachineCfg, err = machineConfigFromProto(vmConfig.MachineCfg)
+		if err != nil {
+			return firecracker.Config{}, drivesBuilder, err
+		}
+	}
+	// Overwrite the root drive config if specified.
+	if vmConfig.RootDrive != nil {
+		drivesBuilder = drivesBuilderFromProto(vmConfig.RootDrive, drivesBuilder, true)
+	}
+	// Add additional drives if specified.
+	if len(vmConfig.AdditionalDrives) > 0 {
+		for _, drive := range vmConfig.AdditionalDrives {
+			drivesBuilder = drivesBuilderFromProto(drive, drivesBuilder, false)
+		}
+	}
+
+	return cfg, drivesBuilder, nil
+}
+
+// networkConfigFromProto creates a firecracker NetworkInterface object from
+// the protobuf FirecrackerNetworkInterface message.
+func networkConfigFromProto(nwIface *proto.FirecrackerNetworkInterface) firecracker.NetworkInterface {
+	result := firecracker.NetworkInterface{
+		MacAddress:  nwIface.MacAddress,
+		HostDevName: nwIface.HostDevName,
+		AllowMMDS:   nwIface.AllowMMDS,
+	}
+
+	if nwIface.InRateLimiter != nil {
+		result.InRateLimiter = rateLimiterFromProto(nwIface.InRateLimiter)
+	}
+
+	if nwIface.OutRateLimiter != nil {
+		result.OutRateLimiter = rateLimiterFromProto(nwIface.OutRateLimiter)
+	}
+
+	return result
+}
+
+// machineConfigFromProto creates a firecracker MachineConfiguration object
+// from the protobuf FirecrackerMachineConfiguration message.
+func machineConfigFromProto(mCfg *proto.FirecrackerMachineConfiguration) (models.MachineConfiguration, error) {
+	result := models.MachineConfiguration{}
+
+	if mCfg.MemSizeMib > 0 && mCfg.VcpuCount == 0 {
+		// Only memory is being overridden.
+		return result, errors.New("both vm memory and vcpu count need to be overridden")
+	}
+
+	if mCfg.VcpuCount > 0 && mCfg.MemSizeMib == 0 {
+		// Only cpu is being overridden.
+		return result, errors.New("both vm memory and vcpu count need to be overridden")
+	}
+
+	if mCfg.CPUTemplate != "" {
+		result.CPUTemplate = models.CPUTemplate(mCfg.CPUTemplate)
+	}
+
+	result.HtEnabled = mCfg.HtEnabled
+	result.MemSizeMib = int64(mCfg.MemSizeMib)
+	result.VcpuCount = int64(mCfg.VcpuCount)
+
+	return result, nil
+}
+
+// drivesBuilderFromProto returns a new DrivesBuilder that can be used to
+// build the firecracker Drives config. The new DrivesBuilder object is
+// constructed using the information present in the protobuf message.
+func drivesBuilderFromProto(drive *proto.FirecrackerDrive,
+	drivesBuilder firecracker.DrivesBuilder,
+	isRoot bool,
+) firecracker.DrivesBuilder {
+	opt := func(d *models.Drive) {
+		d.Partuuid = drive.Partuuid
+		if drive.RateLimiter != nil {
+			d.RateLimiter = rateLimiterFromProto(drive.RateLimiter)
+		}
+	}
+
+	if isRoot {
+		return drivesBuilder.WithRootDrive(drive.PathOnHost, opt)
+	}
+
+	return drivesBuilder.AddDrive(drive.PathOnHost, drive.IsReadOnly, opt)
+}
+
+// rateLimiterFromProto creates a firecracker RateLimiter object from the
+// protobuf message.
+func rateLimiterFromProto(rl *proto.FirecrackerRateLimiter) *models.RateLimiter {
+	result := models.RateLimiter{}
+	if rl.Bandwidth != nil {
+		result.Bandwidth = tokenBucketFromProto(rl.Bandwidth)
+	}
+
+	if rl.Ops != nil {
+		result.Ops = tokenBucketFromProto(rl.Ops)
+	}
+
+	return &result
+}
+
+// tokenBucketFromProto creates a firecracker TokenBucket object from the
+// protobuf message.
+func tokenBucketFromProto(bucket *proto.FirecrackerTokenBucket) *models.TokenBucket {
+	builder := firecracker.TokenBucketBuilder{}
+	if bucket.OneTimeBurst > 0 {
+		builder = builder.WithInitialSize(bucket.OneTimeBurst)
+	}
+
+	if bucket.RefillTime > 0 {
+		builder = builder.WithRefillDuration(time.Duration(bucket.RefillTime) * time.Millisecond)
+	}
+
+	if bucket.Capacity > 0 {
+		builder = builder.WithBucketSize(bucket.Capacity)
+	}
+
+	res := builder.Build()
+	return &res
+}
diff --git a/firecracker-control/local.go b/firecracker-control/local.go
new file mode 100644
index 0000000..553c254
--- /dev/null
+++ b/firecracker-control/local.go
@@ -0,0 +1,313 @@
+// Copyright 2018-2019 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 service
+
+import (
+	"crypto/rand"
+	"fmt"
+	"math"
+	"math/big"
+	"os"
+	"path/filepath"
+	"strconv"
+	"sync"
+
+	"github.com/containerd/containerd/log"
+	"github.com/containerd/containerd/plugin"
+	"github.com/firecracker-microvm/firecracker-go-sdk"
+	"github.com/golang/protobuf/ptypes/empty"
+	"github.com/pkg/errors"
+	"golang.org/x/net/context"
+
+	"github.com/firecracker-microvm/firecracker-containerd/proto"
+)
+
+var (
+	_ proto.FirecrackerServer = (*local)(nil)
+)
+
+func init() {
+	plugin.Register(&plugin.Registration{
+		Type: plugin.ServicePlugin,
+		ID:   localPluginID,
+		InitFn: func(ic *plugin.InitContext) (interface{}, error) {
+			log.G(ic.Context).Debugf("initializing %s plugin (root: %q)", localPluginID, ic.Root)
+			return newLocal(ic)
+		},
+	})
+}
+
+// TODO: Remove this interface after merging PR #91
+// See https://github.com/firecracker-microvm/firecracker-go-sdk/pull/91
+type machineIface interface {
+	Start(context.Context) error
+	StopVMM() error
+	Wait(context.Context) error
+	SetMetadata(context.Context, interface{}) error
+}
+
+type instance struct {
+	cfg     *firecracker.Config
+	machine machineIface
+}
+
+type local struct {
+	vm          map[string]*instance
+	vmLock      sync.RWMutex
+	rootPath    string
+	findVsockFn func(context.Context) (uint32, error)
+}
+
+func newLocal(ic *plugin.InitContext) (*local, error) {
+	if err := os.MkdirAll(ic.Root, 0750); err != nil && !os.IsExist(err) {
+		return nil, errors.Wrapf(err, "failed to create root directory: %s", ic.Root)
+	}
+
+	return &local{
+		vm:          make(map[string]*instance),
+		rootPath:    ic.Root,
+		findVsockFn: findNextAvailableVsockCID,
+	}, nil
+}
+
+// CreateVM creates new Firecracker VM instance
+func (s *local) CreateVM(ctx context.Context, req *proto.CreateVMRequest) (*proto.CreateVMResponse, error) {
+	id, err := s.makeID()
+	if err != nil {
+		return nil, err
+	}
+
+	logger := log.G(ctx).WithField("vm_id", id)
+
+	cfg, err := s.buildVMConfiguration(ctx, id, req)
+	if err != nil {
+		return nil, err
+	}
+
+	logger.Info("creating new VM")
+
+	machine, err := firecracker.NewMachine(ctx, *cfg, firecracker.WithLogger(logger))
+	if err != nil {
+		logger.WithError(err).Error("failed to create new machine instance")
+		return nil, err
+	}
+
+	if err := s.startMachine(ctx, id, machine); err != nil {
+		logger.WithError(err).Error("failed to start VM instance")
+		return nil, err
+	}
+
+	logger.Info("successfully started the VM")
+
+	newInstance := &instance{
+		cfg:     cfg,
+		machine: machine,
+	}
+
+	s.vmLock.Lock()
+	s.vm[id] = newInstance
+	s.vmLock.Unlock()
+
+	return &proto.CreateVMResponse{
+		VMID: id,
+	}, nil
+}
+
+func (s *local) makeID() (string, error) {
+	number, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt32))
+	if err != nil {
+		return "", err
+	}
+
+	return strconv.FormatInt(number.Int64(), 10), nil
+}
+
+func (s *local) buildVMConfiguration(ctx context.Context, id string, req *proto.CreateVMRequest) (*firecracker.Config, error) {
+	logger := log.G(ctx).WithField("vm_id", id)
+
+	cid, err := s.findVsockFn(ctx)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to find available cid for vsock")
+	}
+
+	vsockDevices := []firecracker.VsockDevice{
+		{Path: "root", CID: cid},
+	}
+
+	logger.Debugf("using cid: %d", cid)
+
+	cfg := &firecracker.Config{
+		SocketPath:      filepath.Join(s.rootPath, fmt.Sprintf("vm_%s.socket", id)),
+		LogFifo:         filepath.Join(s.rootPath, fmt.Sprintf("vm_%s_log.fifo", id)),
+		MetricsFifo:     filepath.Join(s.rootPath, fmt.Sprintf("vm_%s_metrics.fifo", id)),
+		KernelImagePath: req.GetKernelImagePath(),
+		KernelArgs:      req.GetKernelArgs(),
+		VsockDevices:    vsockDevices,
+	}
+
+	logger.Debugf("using socket path: %s", cfg.SocketPath)
+
+	machineCfg := req.GetMachineCfg()
+	if machineCfg == nil {
+		return nil, errors.New("invalid machine configuration")
+	}
+
+	cfg.MachineCfg, err = machineConfigFromProto(machineCfg)
+	if err != nil {
+		return nil, err
+	}
+
+	// TODO: Specify default Firecracker configuration here?
+
+	rootDrive := req.GetRootDrive()
+	if rootDrive == nil {
+		return nil, errors.Errorf("root drive can't be empty")
+	}
+
+	driveBuilder := drivesBuilderFromProto(rootDrive, firecracker.DrivesBuilder{}, true)
+
+	for _, drive := range req.GetAdditionalDrives() {
+		driveBuilder = drivesBuilderFromProto(drive, driveBuilder, false)
+	}
+
+	// TODO: Reserve fake drives here (https://github.com/firecracker-microvm/firecracker-containerd/pull/154)
+
+	cfg.Drives = driveBuilder.Build()
+
+	for _, ni := range req.GetNetworkInterfaces() {
+		cfg.NetworkInterfaces = append(cfg.NetworkInterfaces, networkConfigFromProto(ni))
+	}
+
+	return cfg, nil
+}
+
+// startMachine attempts to start an instance within timeout and runs a goroutine to monitor VM exit event
+func (s *local) startMachine(ctx context.Context, id string, machine machineIface) error {
+	logger := log.G(ctx).WithField("vm_id", id)
+
+	logger.Info("starting VM instance")
+
+	ctx, cancel := context.WithTimeout(ctx, firecrackerStartTimeout)
+	defer cancel()
+
+	if err := machine.Start(ctx); err != nil {
+		logger.WithError(err).Error("failed to start the VM")
+		return err
+	}
+
+	go func() {
+		if err := machine.Wait(context.Background()); err != nil {
+			logger.WithError(err).Error("failed to wait the VM")
+		}
+
+		logger.Debug("removing machine")
+
+		s.vmLock.Lock()
+		delete(s.vm, id)
+		s.vmLock.Unlock()
+	}()
+
+	return nil
+}
+
+// StopVM stops running VM instance by VM ID
+func (s *local) StopVM(ctx context.Context, req *proto.StopVMRequest) (*empty.Empty, error) {
+	logger := log.G(ctx).WithField("vm_id", req.GetVMID())
+
+	instance, err := s.getVM(req.GetVMID())
+	if err != nil {
+		return nil, err
+	}
+
+	logger.Infof("stopping the VM")
+
+	if err := instance.machine.StopVMM(); err != nil {
+		logger.WithError(err).Error("failed to stop VM")
+		return nil, err
+	}
+
+	logger.Info("successfully stopped the VM")
+	return &empty.Empty{}, nil
+}
+
+// GetVMAddress returns a socket file location of the VM instance
+func (s *local) GetVMAddress(_ context.Context, req *proto.GetVMAddressRequest) (*proto.GetVMAddressResponse, error) {
+	instance, err := s.getVM(req.GetVMID())
+	if err != nil {
+		return nil, err
+	}
+
+	return &proto.GetVMAddressResponse{
+		SocketPath: instance.cfg.SocketPath,
+	}, nil
+}
+
+// GetFifoPath returns FIFO file location of the VM instance
+func (s *local) GetFifoPath(ctx context.Context, req *proto.GetFifoPathRequest) (*proto.GetFifoPathResponse, error) {
+	var (
+		id       = req.GetVMID()
+		fifoType = req.GetFifoType()
+	)
+
+	instance, err := s.getVM(id)
+	if err != nil {
+		return nil, err
+	}
+
+	var path string
+	switch fifoType {
+	case proto.FifoType_LOG:
+		path = instance.cfg.LogFifo
+	case proto.FifoType_METRICS:
+		path = instance.cfg.MetricsFifo
+	default:
+		return nil, fmt.Errorf("unsupported fifo type %q", fifoType.String())
+	}
+
+	return &proto.GetFifoPathResponse{
+		Path: path,
+	}, nil
+}
+
+// SetVMMetadata sets Firecracker instance metadata
+func (s *local) SetVMMetadata(ctx context.Context, req *proto.SetVMMetadataRequest) (*empty.Empty, error) {
+	var (
+		id       = req.GetVMID()
+		metadata = req.GetMetadata()
+	)
+
+	instance, err := s.getVM(id)
+	if err != nil {
+		return nil, err
+	}
+
+	log.G(ctx).WithField("vm_id", id).Info("updating VM metadata")
+	if err := instance.machine.SetMetadata(ctx, metadata); err != nil {
+		return nil, errors.Wrapf(err, "failed to set metadata for VM %q", id)
+	}
+
+	return &empty.Empty{}, nil
+}
+
+func (s *local) getVM(id string) (*instance, error) {
+	s.vmLock.RLock()
+	instance, ok := s.vm[id]
+	s.vmLock.RUnlock()
+
+	if !ok {
+		return nil, ErrVMNotFound
+	}
+
+	return instance, nil
+}
diff --git a/firecracker-control/service.go b/firecracker-control/service.go
index 4e4e2ff..6ee7473 100644
--- a/firecracker-control/service.go
+++ b/firecracker-control/service.go
@@ -15,7 +15,7 @@ package service
 
 import (
 	"context"
-	"errors"
+	"fmt"
 
 	"github.com/containerd/containerd/log"
 	"github.com/containerd/containerd/plugin"
@@ -28,15 +28,35 @@ import (
 func init() {
 	plugin.Register(&plugin.Registration{
 		Type: plugin.GRPCPlugin,
-		ID:   "fc-control",
+		ID:   grpcPluginID,
+		Requires: []plugin.Type{
+			plugin.ServicePlugin,
+		},
 		InitFn: func(ic *plugin.InitContext) (interface{}, error) {
-			log.G(ic.Context).Debug("initializing fc-control plugin")
-			return &service{}, nil
+			log.G(ic.Context).Debugf("initializing %s plugin", grpcPluginID)
+
+			list, err := ic.GetByType(plugin.ServicePlugin)
+			if err != nil {
+				return nil, err
+			}
+
+			item, ok := list[localPluginID]
+			if !ok {
+				return nil, fmt.Errorf("service %q not found", localPluginID)
+			}
+
+			instance, err := item.Instance()
+			if err != nil {
+				return nil, err
+			}
+
+			return &service{local: instance.(proto.FirecrackerServer)}, nil
 		},
 	})
 }
 
 type service struct {
+	local proto.FirecrackerServer
 }
 
 var _ proto.FirecrackerServer = (*service)(nil)
@@ -47,16 +67,26 @@ func (s *service) Register(server *grpc.Server) error {
 }
 
 func (s *service) CreateVM(ctx context.Context, req *proto.CreateVMRequest) (*proto.CreateVMResponse, error) {
-	log.G(ctx).Debug("create VM request: %+v", req)
-	return nil, errors.New("not implemented")
+	log.G(ctx).Debugf("create VM request: %+v", req)
+	return s.local.CreateVM(ctx, req)
 }
 
 func (s *service) StopVM(ctx context.Context, req *proto.StopVMRequest) (*empty.Empty, error) {
-	log.G(ctx).Debug("stop VM: %+v", req)
-	return nil, errors.New("not implemented")
+	log.G(ctx).Debugf("stop VM: %+v", req)
+	return s.local.StopVM(ctx, req)
 }
 
 func (s *service) GetVMAddress(ctx context.Context, req *proto.GetVMAddressRequest) (*proto.GetVMAddressResponse, error) {
-	log.G(ctx).Debug("get VM address: %+v", req)
-	return nil, errors.New("not implemented")
+	log.G(ctx).Debugf("get VM address: %+v", req)
+	return s.local.GetVMAddress(ctx, req)
+}
+
+func (s *service) GetFifoPath(ctx context.Context, req *proto.GetFifoPathRequest) (*proto.GetFifoPathResponse, error) {
+	log.G(ctx).Debugf("get fifo path: %+v", req)
+	return s.local.GetFifoPath(ctx, req)
+}
+
+func (s *service) SetVMMetadata(ctx context.Context, req *proto.SetVMMetadataRequest) (*empty.Empty, error) {
+	log.G(ctx).Debugf("set vm metadata: %+v", req)
+	return s.local.SetVMMetadata(ctx, req)
 }
diff --git a/firecracker-control/vsock.go b/firecracker-control/vsock.go
new file mode 100644
index 0000000..9799d54
--- /dev/null
+++ b/firecracker-control/vsock.go
@@ -0,0 +1,78 @@
+// Copyright 2018-2019 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 service
+
+import (
+	"context"
+	"math"
+	"os"
+	"syscall"
+	"unsafe"
+
+	"github.com/pkg/errors"
+	"golang.org/x/sys/unix"
+)
+
+var (
+	sysCall = syscall.Syscall
+)
+
+// findNextAvailableVsockCID finds first available vsock context ID.
+// It uses VHOST_VSOCK_SET_GUEST_CID ioctl which allows some CID ranges to be statically reserved in advance.
+// The ioctl fails with EADDRINUSE if cid is already taken and with EINVAL if the CID is invalid.
+// Taken from https://bugzilla.redhat.com/show_bug.cgi?id=1291851
+func findNextAvailableVsockCID(ctx context.Context) (uint32, error) {
+	const (
+		// Corresponds to VHOST_VSOCK_SET_GUEST_CID in vhost.h
+		ioctlVsockSetGuestCID = uintptr(0x4008AF60)
+		// 0, 1 and 2 are reserved CIDs, see http://man7.org/linux/man-pages/man7/vsock.7.html
+		startCID        = 3
+		maxCID          = math.MaxUint32
+		vsockDevicePath = "/dev/vhost-vsock"
+	)
+
+	file, err := os.OpenFile(vsockDevicePath, syscall.O_RDWR, 0600)
+	if err != nil {
+		return 0, errors.Wrap(err, "failed to open vsock device")
+	}
+
+	defer file.Close()
+
+	for contextID := startCID; contextID < maxCID; contextID++ {
+		select {
+		case <-ctx.Done():
+			return 0, ctx.Err()
+		default:
+			cid := contextID
+			_, _, err = syscall.Syscall(
+				unix.SYS_IOCTL,
+				file.Fd(),
+				ioctlVsockSetGuestCID,
+				uintptr(unsafe.Pointer(&cid)))
+
+			switch err {
+			case unix.Errno(0):
+				return uint32(contextID), nil
+			case unix.EADDRINUSE:
+				// ID is already taken, try next one
+				continue
+			default:
+				// Fail if we get an error we don't expect
+				return 0, err
+			}
+		}
+	}
+
+	return 0, errors.New("couldn't find any available vsock context id")
+}
diff --git a/go.mod b/go.mod
index 2abd2e9..7ada0a5 100644
--- a/go.mod
+++ b/go.mod
@@ -21,11 +21,12 @@ require (
 	github.com/docker/go-events v0.0.0-20170721190031-9461782956ad // indirect
 	github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82 // indirect
 	github.com/docker/go-units v0.3.3
-	github.com/firecracker-microvm/firecracker-go-sdk v0.15.0
+	github.com/firecracker-microvm/firecracker-go-sdk v0.15.2-0.20190321195537-b704cc3feb1e
 	github.com/go-ole/go-ole v1.2.4 // indirect
 	github.com/godbus/dbus v0.0.0-20181025153459-66d97aec3384 // indirect
 	github.com/gogo/googleapis v1.1.0 // indirect
 	github.com/gogo/protobuf v1.2.1
+	github.com/golang/mock v1.1.1
 	github.com/golang/protobuf v1.2.0
 	github.com/google/go-cmp v0.2.0 // indirect
 	github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
diff --git a/go.sum b/go.sum
index ebc631b..115e985 100644
--- a/go.sum
+++ b/go.sum
@@ -52,6 +52,8 @@ github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk
 github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
 github.com/firecracker-microvm/firecracker-go-sdk v0.15.0 h1:KKbgkZFjtrvMfrv2XyxJW+YQAe2cHHsSTFSi6VTRzuE=
 github.com/firecracker-microvm/firecracker-go-sdk v0.15.0/go.mod h1:UOsA6IaI52MQntB9PrarJFReY1nA9iZg2DI04kxPNMY=
+github.com/firecracker-microvm/firecracker-go-sdk v0.15.2-0.20190321195537-b704cc3feb1e h1:LQKqzqwiUYtucykGP3WDaH8wfULY8v86HFSW95T+k+E=
+github.com/firecracker-microvm/firecracker-go-sdk v0.15.2-0.20190321195537-b704cc3feb1e/go.mod h1:QcNsz2gYkcTI/zAQl3dYeLwf7KxtjKnt7aGklhn9yYk=
 github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb h1:D4uzjWwKYQ5XnAvUbuvHW93esHg7F8N/OYeBBcJoTr0=
 github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
 github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
diff --git a/proto/firecracker.pb.go b/proto/firecracker.pb.go
index 41342af..89dfefa 100644
--- a/proto/firecracker.pb.go
+++ b/proto/firecracker.pb.go
@@ -24,6 +24,29 @@ var _ = math.Inf
 // proto package needs to be updated.
 const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
 
+type FifoType int32
+
+const (
+	FifoType_LOG     FifoType = 0
+	FifoType_METRICS FifoType = 1
+)
+
+var FifoType_name = map[int32]string{
+	0: "LOG",
+	1: "METRICS",
+}
+var FifoType_value = map[string]int32{
+	"LOG":     0,
+	"METRICS": 1,
+}
+
+func (x FifoType) String() string {
+	return proto.EnumName(FifoType_name, int32(x))
+}
+func (FifoType) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_firecracker_f0b40e152181d1b0, []int{0}
+}
+
 // CreateVMRequest specifies creation parameters for a new FC instance
 type CreateVMRequest struct {
 	// Specifies the machine configuration for the VM
@@ -49,7 +72,7 @@ func (m *CreateVMRequest) Reset()         { *m = CreateVMRequest{} }
 func (m *CreateVMRequest) String() string { return proto.CompactTextString(m) }
 func (*CreateVMRequest) ProtoMessage()    {}
 func (*CreateVMRequest) Descriptor() ([]byte, []int) {
-	return fileDescriptor_firecracker_73f6b188d2d4dd0e, []int{0}
+	return fileDescriptor_firecracker_f0b40e152181d1b0, []int{0}
 }
 func (m *CreateVMRequest) XXX_Unmarshal(b []byte) error {
 	return xxx_messageInfo_CreateVMRequest.Unmarshal(m, b)
@@ -129,7 +152,7 @@ func (m *CreateVMResponse) Reset()         { *m = CreateVMResponse{} }
 func (m *CreateVMResponse) String() string { return proto.CompactTextString(m) }
 func (*CreateVMResponse) ProtoMessage()    {}
 func (*CreateVMResponse) Descriptor() ([]byte, []int) {
-	return fileDescriptor_firecracker_73f6b188d2d4dd0e, []int{1}
+	return fileDescriptor_firecracker_f0b40e152181d1b0, []int{1}
 }
 func (m *CreateVMResponse) XXX_Unmarshal(b []byte) error {
 	return xxx_messageInfo_CreateVMResponse.Unmarshal(m, b)
@@ -167,7 +190,7 @@ func (m *StopVMRequest) Reset()         { *m = StopVMRequest{} }
 func (m *StopVMRequest) String() string { return proto.CompactTextString(m) }
 func (*StopVMRequest) ProtoMessage()    {}
 func (*StopVMRequest) Descriptor() ([]byte, []int) {
-	return fileDescriptor_firecracker_73f6b188d2d4dd0e, []int{2}
+	return fileDescriptor_firecracker_f0b40e152181d1b0, []int{2}
 }
 func (m *StopVMRequest) XXX_Unmarshal(b []byte) error {
 	return xxx_messageInfo_StopVMRequest.Unmarshal(m, b)
@@ -205,7 +228,7 @@ func (m *GetVMAddressRequest) Reset()         { *m = GetVMAddressRequest{} }
 func (m *GetVMAddressRequest) String() string { return proto.CompactTextString(m) }
 func (*GetVMAddressRequest) ProtoMessage()    {}
 func (*GetVMAddressRequest) Descriptor() ([]byte, []int) {
-	return fileDescriptor_firecracker_73f6b188d2d4dd0e, []int{3}
+	return fileDescriptor_firecracker_f0b40e152181d1b0, []int{3}
 }
 func (m *GetVMAddressRequest) XXX_Unmarshal(b []byte) error {
 	return xxx_messageInfo_GetVMAddressRequest.Unmarshal(m, b)
@@ -243,7 +266,7 @@ func (m *GetVMAddressResponse) Reset()         { *m = GetVMAddressResponse{} }
 func (m *GetVMAddressResponse) String() string { return proto.CompactTextString(m) }
 func (*GetVMAddressResponse) ProtoMessage()    {}
 func (*GetVMAddressResponse) Descriptor() ([]byte, []int) {
-	return fileDescriptor_firecracker_73f6b188d2d4dd0e, []int{4}
+	return fileDescriptor_firecracker_f0b40e152181d1b0, []int{4}
 }
 func (m *GetVMAddressResponse) XXX_Unmarshal(b []byte) error {
 	return xxx_messageInfo_GetVMAddressResponse.Unmarshal(m, b)
@@ -270,12 +293,146 @@ func (m *GetVMAddressResponse) GetSocketPath() string {
 	return ""
 }
 
+type GetFifoPathRequest struct {
+	VMID                 string   `protobuf:"bytes,1,opt,name=VMID,proto3" json:"VMID,omitempty"`
+	FifoType             FifoType `protobuf:"varint,2,opt,name=FifoType,proto3,enum=FifoType" json:"FifoType,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *GetFifoPathRequest) Reset()         { *m = GetFifoPathRequest{} }
+func (m *GetFifoPathRequest) String() string { return proto.CompactTextString(m) }
+func (*GetFifoPathRequest) ProtoMessage()    {}
+func (*GetFifoPathRequest) Descriptor() ([]byte, []int) {
+	return fileDescriptor_firecracker_f0b40e152181d1b0, []int{5}
+}
+func (m *GetFifoPathRequest) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_GetFifoPathRequest.Unmarshal(m, b)
+}
+func (m *GetFifoPathRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_GetFifoPathRequest.Marshal(b, m, deterministic)
+}
+func (dst *GetFifoPathRequest) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_GetFifoPathRequest.Merge(dst, src)
+}
+func (m *GetFifoPathRequest) XXX_Size() int {
+	return xxx_messageInfo_GetFifoPathRequest.Size(m)
+}
+func (m *GetFifoPathRequest) XXX_DiscardUnknown() {
+	xxx_messageInfo_GetFifoPathRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_GetFifoPathRequest proto.InternalMessageInfo
+
+func (m *GetFifoPathRequest) GetVMID() string {
+	if m != nil {
+		return m.VMID
+	}
+	return ""
+}
+
+func (m *GetFifoPathRequest) GetFifoType() FifoType {
+	if m != nil {
+		return m.FifoType
+	}
+	return FifoType_LOG
+}
+
+type GetFifoPathResponse struct {
+	Path                 string   `protobuf:"bytes,1,opt,name=Path,proto3" json:"Path,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *GetFifoPathResponse) Reset()         { *m = GetFifoPathResponse{} }
+func (m *GetFifoPathResponse) String() string { return proto.CompactTextString(m) }
+func (*GetFifoPathResponse) ProtoMessage()    {}
+func (*GetFifoPathResponse) Descriptor() ([]byte, []int) {
+	return fileDescriptor_firecracker_f0b40e152181d1b0, []int{6}
+}
+func (m *GetFifoPathResponse) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_GetFifoPathResponse.Unmarshal(m, b)
+}
+func (m *GetFifoPathResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_GetFifoPathResponse.Marshal(b, m, deterministic)
+}
+func (dst *GetFifoPathResponse) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_GetFifoPathResponse.Merge(dst, src)
+}
+func (m *GetFifoPathResponse) XXX_Size() int {
+	return xxx_messageInfo_GetFifoPathResponse.Size(m)
+}
+func (m *GetFifoPathResponse) XXX_DiscardUnknown() {
+	xxx_messageInfo_GetFifoPathResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_GetFifoPathResponse proto.InternalMessageInfo
+
+func (m *GetFifoPathResponse) GetPath() string {
+	if m != nil {
+		return m.Path
+	}
+	return ""
+}
+
+type SetVMMetadataRequest struct {
+	VMID                 string   `protobuf:"bytes,1,opt,name=VMID,proto3" json:"VMID,omitempty"`
+	Metadata             string   `protobuf:"bytes,2,opt,name=Metadata,proto3" json:"Metadata,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *SetVMMetadataRequest) Reset()         { *m = SetVMMetadataRequest{} }
+func (m *SetVMMetadataRequest) String() string { return proto.CompactTextString(m) }
+func (*SetVMMetadataRequest) ProtoMessage()    {}
+func (*SetVMMetadataRequest) Descriptor() ([]byte, []int) {
+	return fileDescriptor_firecracker_f0b40e152181d1b0, []int{7}
+}
+func (m *SetVMMetadataRequest) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_SetVMMetadataRequest.Unmarshal(m, b)
+}
+func (m *SetVMMetadataRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_SetVMMetadataRequest.Marshal(b, m, deterministic)
+}
+func (dst *SetVMMetadataRequest) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_SetVMMetadataRequest.Merge(dst, src)
+}
+func (m *SetVMMetadataRequest) XXX_Size() int {
+	return xxx_messageInfo_SetVMMetadataRequest.Size(m)
+}
+func (m *SetVMMetadataRequest) XXX_DiscardUnknown() {
+	xxx_messageInfo_SetVMMetadataRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_SetVMMetadataRequest proto.InternalMessageInfo
+
+func (m *SetVMMetadataRequest) GetVMID() string {
+	if m != nil {
+		return m.VMID
+	}
+	return ""
+}
+
+func (m *SetVMMetadataRequest) GetMetadata() string {
+	if m != nil {
+		return m.Metadata
+	}
+	return ""
+}
+
 func init() {
 	proto.RegisterType((*CreateVMRequest)(nil), "CreateVMRequest")
 	proto.RegisterType((*CreateVMResponse)(nil), "CreateVMResponse")
 	proto.RegisterType((*StopVMRequest)(nil), "StopVMRequest")
 	proto.RegisterType((*GetVMAddressRequest)(nil), "GetVMAddressRequest")
 	proto.RegisterType((*GetVMAddressResponse)(nil), "GetVMAddressResponse")
+	proto.RegisterType((*GetFifoPathRequest)(nil), "GetFifoPathRequest")
+	proto.RegisterType((*GetFifoPathResponse)(nil), "GetFifoPathResponse")
+	proto.RegisterType((*SetVMMetadataRequest)(nil), "SetVMMetadataRequest")
+	proto.RegisterEnum("FifoType", FifoType_name, FifoType_value)
 }
 
 // Reference imports to suppress errors if they are not otherwise used.
@@ -294,8 +451,12 @@ type FirecrackerClient interface {
 	CreateVM(ctx context.Context, in *CreateVMRequest, opts ...grpc.CallOption) (*CreateVMResponse, error)
 	// Stops existing Firecracker instance by VM ID
 	StopVM(ctx context.Context, in *StopVMRequest, opts ...grpc.CallOption) (*empty.Empty, error)
-	// Gets VM instance socket file location
+	// Gets VM's instance socket file location
 	GetVMAddress(ctx context.Context, in *GetVMAddressRequest, opts ...grpc.CallOption) (*GetVMAddressResponse, error)
+	// Gets VM's instance FIFO file location
+	GetFifoPath(ctx context.Context, in *GetFifoPathRequest, opts ...grpc.CallOption) (*GetFifoPathResponse, error)
+	// Sets VM's instance metadata
+	SetVMMetadata(ctx context.Context, in *SetVMMetadataRequest, opts ...grpc.CallOption) (*empty.Empty, error)
 }
 
 type firecrackerClient struct {
@@ -333,14 +494,36 @@ func (c *firecrackerClient) GetVMAddress(ctx context.Context, in *GetVMAddressRe
 	return out, nil
 }
 
+func (c *firecrackerClient) GetFifoPath(ctx context.Context, in *GetFifoPathRequest, opts ...grpc.CallOption) (*GetFifoPathResponse, error) {
+	out := new(GetFifoPathResponse)
+	err := c.cc.Invoke(ctx, "/Firecracker/GetFifoPath", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *firecrackerClient) SetVMMetadata(ctx context.Context, in *SetVMMetadataRequest, opts ...grpc.CallOption) (*empty.Empty, error) {
+	out := new(empty.Empty)
+	err := c.cc.Invoke(ctx, "/Firecracker/SetVMMetadata", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
 // FirecrackerServer is the server API for Firecracker service.
 type FirecrackerServer interface {
 	// Runs new Firecracker VM instance
 	CreateVM(context.Context, *CreateVMRequest) (*CreateVMResponse, error)
 	// Stops existing Firecracker instance by VM ID
 	StopVM(context.Context, *StopVMRequest) (*empty.Empty, error)
-	// Gets VM instance socket file location
+	// Gets VM's instance socket file location
 	GetVMAddress(context.Context, *GetVMAddressRequest) (*GetVMAddressResponse, error)
+	// Gets VM's instance FIFO file location
+	GetFifoPath(context.Context, *GetFifoPathRequest) (*GetFifoPathResponse, error)
+	// Sets VM's instance metadata
+	SetVMMetadata(context.Context, *SetVMMetadataRequest) (*empty.Empty, error)
 }
 
 func RegisterFirecrackerServer(s *grpc.Server, srv FirecrackerServer) {
@@ -401,6 +584,42 @@ func _Firecracker_GetVMAddress_Handler(srv interface{}, ctx context.Context, dec
 	return interceptor(ctx, in, info, handler)
 }
 
+func _Firecracker_GetFifoPath_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(GetFifoPathRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(FirecrackerServer).GetFifoPath(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/Firecracker/GetFifoPath",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(FirecrackerServer).GetFifoPath(ctx, req.(*GetFifoPathRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _Firecracker_SetVMMetadata_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(SetVMMetadataRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(FirecrackerServer).SetVMMetadata(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/Firecracker/SetVMMetadata",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(FirecrackerServer).SetVMMetadata(ctx, req.(*SetVMMetadataRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
 var _Firecracker_serviceDesc = grpc.ServiceDesc{
 	ServiceName: "Firecracker",
 	HandlerType: (*FirecrackerServer)(nil),
@@ -417,40 +636,55 @@ var _Firecracker_serviceDesc = grpc.ServiceDesc{
 			MethodName: "GetVMAddress",
 			Handler:    _Firecracker_GetVMAddress_Handler,
 		},
+		{
+			MethodName: "GetFifoPath",
+			Handler:    _Firecracker_GetFifoPath_Handler,
+		},
+		{
+			MethodName: "SetVMMetadata",
+			Handler:    _Firecracker_SetVMMetadata_Handler,
+		},
 	},
 	Streams:  []grpc.StreamDesc{},
 	Metadata: "firecracker.proto",
 }
 
-func init() { proto.RegisterFile("firecracker.proto", fileDescriptor_firecracker_73f6b188d2d4dd0e) }
-
-var fileDescriptor_firecracker_73f6b188d2d4dd0e = []byte{
-	// 417 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x52, 0xd1, 0x6e, 0xd3, 0x30,
-	0x14, 0x55, 0xe8, 0xda, 0xd1, 0x5b, 0xd8, 0x5a, 0x33, 0x50, 0x14, 0xd0, 0x14, 0x15, 0x69, 0x0a,
-	0x2f, 0x0e, 0x1a, 0x12, 0x2f, 0x08, 0x89, 0x92, 0x01, 0x2a, 0x28, 0x08, 0x79, 0x52, 0x1f, 0x78,
-	0x33, 0xc9, 0x4d, 0x16, 0xb5, 0xb3, 0x83, 0xed, 0x80, 0xf6, 0xc8, 0x07, 0xf1, 0x8f, 0x68, 0xce,
-	0xaa, 0xa4, 0x69, 0xc4, 0x53, 0xe2, 0x73, 0xcf, 0xb1, 0xcf, 0xb9, 0xf7, 0xc2, 0x2c, 0x2b, 0x14,
-	0x26, 0x8a, 0x27, 0x6b, 0x54, 0xb4, 0x54, 0xd2, 0x48, 0xef, 0x69, 0x2e, 0x65, 0xbe, 0xc1, 0xd0,
-	0x9e, 0x7e, 0x54, 0x59, 0x88, 0xd7, 0xa5, 0xb9, 0xb9, 0x2b, 0x4e, 0xcc, 0x4d, 0x89, 0xba, 0x3e,
-	0xcc, 0xff, 0x0c, 0xe0, 0x38, 0x52, 0xc8, 0x0d, 0xae, 0x62, 0x86, 0x3f, 0x2b, 0xd4, 0x86, 0xbc,
-	0x03, 0x88, 0x79, 0x72, 0x55, 0x08, 0x8c, 0xb2, 0xdc, 0x75, 0x7c, 0x27, 0x98, 0x9c, 0xfb, 0xf4,
-	0x63, 0xf3, 0xca, 0xb6, 0x2a, 0x45, 0x56, 0xe4, 0x95, 0xe2, 0xa6, 0x90, 0x82, 0xb5, 0x34, 0x24,
-	0x80, 0xe3, 0x2f, 0xa8, 0x04, 0x6e, 0x96, 0xd7, 0x3c, 0xc7, 0x6f, 0xdc, 0x5c, 0xb9, 0xf7, 0x7c,
-	0x27, 0x18, 0xb3, 0x2e, 0x4c, 0x4e, 0x01, 0x6a, 0x68, 0xa1, 0x72, 0xed, 0x0e, 0x2c, 0xa9, 0x85,
-	0x90, 0x10, 0xc6, 0x4c, 0x4a, 0x73, 0xa1, 0x8a, 0x5f, 0xe8, 0x1e, 0x58, 0x2b, 0xb3, 0xb6, 0x15,
-	0x5b, 0x60, 0x0d, 0x87, 0xbc, 0x85, 0xe9, 0x22, 0x4d, 0x8b, 0x5b, 0x4b, 0x7c, 0x63, 0x21, 0xed,
-	0x0e, 0xfd, 0x41, 0xbf, 0x6e, 0x8f, 0x4a, 0x3e, 0xc3, 0xec, 0x2b, 0x9a, 0xdf, 0x52, 0xad, 0x97,
-	0xc2, 0xa0, 0xca, 0x78, 0x82, 0xda, 0x1d, 0x59, 0xfd, 0xb3, 0xb6, 0xbe, 0x4b, 0x62, 0xfb, 0x32,
-	0x72, 0x06, 0x47, 0x91, 0x14, 0x86, 0x17, 0x02, 0x55, 0x24, 0x2b, 0x61, 0xdc, 0x43, 0xdf, 0x09,
-	0x86, 0xac, 0x83, 0xce, 0xcf, 0x60, 0xda, 0x8c, 0x40, 0x97, 0x52, 0x68, 0x24, 0x04, 0x0e, 0x56,
-	0xf1, 0xf2, 0xc2, 0x76, 0x7f, 0xcc, 0xec, 0xff, 0xfc, 0x39, 0x3c, 0xbc, 0x34, 0xb2, 0x6c, 0x06,
-	0xd5, 0x47, 0x7a, 0x01, 0x8f, 0x3e, 0xa1, 0x59, 0xc5, 0x8b, 0x34, 0x55, 0xa8, 0xf5, 0xff, 0xa8,
-	0xaf, 0xe1, 0x64, 0x97, 0x7a, 0xf7, 0xf6, 0x29, 0xc0, 0xa5, 0x4c, 0xd6, 0x68, 0xec, 0xe0, 0x6a,
-	0x45, 0x0b, 0x39, 0xff, 0xeb, 0xc0, 0xa4, 0xd5, 0x0a, 0x12, 0xc2, 0xfd, 0xad, 0x7f, 0x32, 0xa5,
-	0x9d, 0x6d, 0xf2, 0x66, 0x74, 0x2f, 0xdc, 0x4b, 0x18, 0xd5, 0x41, 0xc8, 0x11, 0xdd, 0x49, 0xe4,
-	0x3d, 0xa1, 0xf5, 0xe6, 0xd2, 0xed, 0xe6, 0xd2, 0x0f, 0xb7, 0x9b, 0x4b, 0xde, 0xc0, 0x83, 0xb6,
-	0x55, 0x72, 0x42, 0x7b, 0x42, 0x7a, 0x8f, 0x69, 0x5f, 0x9e, 0xf7, 0x87, 0xdf, 0x87, 0xf5, 0x75,
-	0x23, 0xfb, 0x79, 0xf5, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x8b, 0x36, 0x3c, 0x79, 0x32, 0x03, 0x00,
-	0x00,
+func init() { proto.RegisterFile("firecracker.proto", fileDescriptor_firecracker_f0b40e152181d1b0) }
+
+var fileDescriptor_firecracker_f0b40e152181d1b0 = []byte{
+	// 530 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x53, 0x5d, 0x6f, 0x12, 0x41,
+	0x14, 0x95, 0xd2, 0xf2, 0x71, 0xb1, 0x14, 0xa6, 0xd4, 0x6c, 0x56, 0xd3, 0x6c, 0x30, 0x36, 0xe8,
+	0xc3, 0x60, 0x30, 0x31, 0x26, 0x46, 0x23, 0xd2, 0x42, 0x50, 0xd7, 0x9a, 0xa1, 0xe1, 0xc1, 0xb7,
+	0x11, 0xee, 0x6e, 0x37, 0xd0, 0x9d, 0x75, 0x76, 0xd0, 0xf0, 0xe8, 0xaf, 0xf1, 0x6f, 0x1a, 0x66,
+	0xd9, 0xee, 0x02, 0x2b, 0x4f, 0xec, 0x3d, 0xf7, 0x5c, 0xee, 0xc9, 0x39, 0x73, 0xa1, 0xee, 0x78,
+	0x12, 0x27, 0x92, 0x4f, 0x66, 0x28, 0x69, 0x20, 0x85, 0x12, 0xe6, 0x63, 0x57, 0x08, 0x77, 0x8e,
+	0x6d, 0x5d, 0xfd, 0x58, 0x38, 0x6d, 0xbc, 0x0b, 0xd4, 0x72, 0xdd, 0xac, 0xa8, 0x65, 0x80, 0x61,
+	0x54, 0x34, 0xff, 0xe4, 0xe1, 0xa4, 0x27, 0x91, 0x2b, 0x1c, 0xdb, 0x0c, 0x7f, 0x2e, 0x30, 0x54,
+	0xe4, 0x03, 0x80, 0xcd, 0x27, 0xb7, 0x9e, 0x8f, 0x3d, 0xc7, 0x35, 0x72, 0x56, 0xae, 0x55, 0xe9,
+	0x58, 0xb4, 0x9f, 0x6c, 0x89, 0xbb, 0xc2, 0x77, 0x3c, 0x77, 0x21, 0xb9, 0xf2, 0x84, 0xcf, 0x52,
+	0x33, 0xa4, 0x05, 0x27, 0x9f, 0x51, 0xfa, 0x38, 0x1f, 0xde, 0x71, 0x17, 0xbf, 0x71, 0x75, 0x6b,
+	0x1c, 0x58, 0xb9, 0x56, 0x99, 0x6d, 0xc3, 0xe4, 0x1c, 0x20, 0x82, 0xba, 0xd2, 0x0d, 0x8d, 0xbc,
+	0x26, 0xa5, 0x10, 0xd2, 0x86, 0x32, 0x13, 0x42, 0x5d, 0x4a, 0xef, 0x17, 0x1a, 0x87, 0x5a, 0x4a,
+	0x3d, 0x2d, 0x45, 0x37, 0x58, 0xc2, 0x21, 0xef, 0xa0, 0xd6, 0x9d, 0x4e, 0xbd, 0x95, 0x24, 0x3e,
+	0xd7, 0x50, 0x68, 0x1c, 0x59, 0xf9, 0xec, 0xb9, 0x1d, 0x2a, 0xf9, 0x04, 0xf5, 0xaf, 0xa8, 0x7e,
+	0x0b, 0x39, 0x1b, 0xfa, 0x0a, 0xa5, 0xc3, 0x27, 0x18, 0x1a, 0x05, 0x3d, 0xff, 0x24, 0x3d, 0xbf,
+	0x4d, 0x62, 0xbb, 0x63, 0xe4, 0x02, 0xaa, 0x3d, 0xe1, 0x2b, 0xee, 0xf9, 0x28, 0x7b, 0x62, 0xe1,
+	0x2b, 0xa3, 0x68, 0xe5, 0x5a, 0x47, 0x6c, 0x0b, 0x6d, 0x5e, 0x40, 0x2d, 0x89, 0x20, 0x0c, 0x84,
+	0x1f, 0x22, 0x21, 0x70, 0x38, 0xb6, 0x87, 0x97, 0xda, 0xfd, 0x32, 0xd3, 0xdf, 0xcd, 0xa7, 0x70,
+	0x3c, 0x52, 0x22, 0x48, 0x82, 0xca, 0x22, 0x3d, 0x87, 0xd3, 0x01, 0xaa, 0xb1, 0xdd, 0x9d, 0x4e,
+	0x25, 0x86, 0xe1, 0x3e, 0xea, 0x6b, 0x68, 0x6c, 0x52, 0xd7, 0xbb, 0xcf, 0x01, 0x46, 0x62, 0x32,
+	0x43, 0xa5, 0x83, 0x8b, 0x26, 0x52, 0x48, 0xf3, 0x1a, 0xc8, 0x00, 0x55, 0xdf, 0x73, 0xc4, 0xaa,
+	0xdc, 0xb3, 0x81, 0x3c, 0x83, 0xd2, 0x8a, 0x76, 0xb3, 0x0c, 0x50, 0x3f, 0x80, 0x6a, 0xa7, 0x4c,
+	0x63, 0x80, 0xdd, 0xb7, 0xd6, 0x9a, 0x93, 0x3f, 0x4c, 0x3c, 0x48, 0x29, 0xd0, 0xdf, 0xcd, 0x3e,
+	0x34, 0x46, 0x2b, 0xcd, 0x36, 0x2a, 0x3e, 0xe5, 0x8a, 0xef, 0xdb, 0x6e, 0x42, 0x29, 0xa6, 0xad,
+	0x9f, 0xdf, 0x7d, 0xfd, 0xc2, 0x4a, 0x94, 0x91, 0x22, 0xe4, 0xbf, 0x5c, 0x0f, 0x6a, 0x0f, 0x48,
+	0x05, 0x8a, 0xf6, 0xd5, 0x0d, 0x1b, 0xf6, 0x46, 0xb5, 0x5c, 0xe7, 0xef, 0x01, 0x54, 0x52, 0x81,
+	0x93, 0x36, 0x94, 0xe2, 0x94, 0x48, 0x8d, 0x6e, 0xdd, 0x8c, 0x59, 0xa7, 0x3b, 0x11, 0xbe, 0x84,
+	0x42, 0x14, 0x17, 0xa9, 0xd2, 0x8d, 0xdc, 0xcc, 0x47, 0x34, 0xba, 0x4f, 0x1a, 0xdf, 0x27, 0xbd,
+	0x5a, 0xdd, 0x27, 0x79, 0x0b, 0x0f, 0xd3, 0x81, 0x90, 0x06, 0xcd, 0x88, 0xd2, 0x3c, 0xa3, 0x99,
+	0xa9, 0xbd, 0x81, 0x4a, 0xca, 0x44, 0x72, 0x4a, 0x77, 0x33, 0x32, 0x1b, 0x34, 0xcb, 0xe7, 0xf7,
+	0x70, 0xbc, 0xe1, 0x29, 0x39, 0xa3, 0x59, 0x1e, 0xff, 0x4f, 0xf6, 0xc7, 0xe2, 0xf7, 0xa3, 0x08,
+	0x29, 0xe8, 0x9f, 0x57, 0xff, 0x02, 0x00, 0x00, 0xff, 0xff, 0xbd, 0xd7, 0x4f, 0x65, 0x92, 0x04,
+	0x00, 0x00,
 }
diff --git a/proto/firecracker.proto b/proto/firecracker.proto
index c0426eb..2cee45f 100644
--- a/proto/firecracker.proto
+++ b/proto/firecracker.proto
@@ -12,8 +12,14 @@ service Firecracker {
     // Stops existing Firecracker instance by VM ID
     rpc StopVM(StopVMRequest) returns (google.protobuf.Empty);
 
-    // Gets VM instance socket file location
+    // Gets VM's instance socket file location
     rpc GetVMAddress(GetVMAddressRequest) returns (GetVMAddressResponse);
+
+    // Gets VM's instance FIFO file location
+    rpc GetFifoPath(GetFifoPathRequest) returns (GetFifoPathResponse);
+
+    // Sets VM's instance metadata
+    rpc SetVMMetadata(SetVMMetadataRequest) returns (google.protobuf.Empty);
 }
 
 // CreateVMRequest specifies creation parameters for a new FC instance
@@ -54,4 +60,23 @@ message GetVMAddressRequest {
 
 message GetVMAddressResponse {
     string SocketPath = 1;
+}
+
+enum FifoType {
+    LOG     = 0;
+    METRICS = 1;
+}
+
+message GetFifoPathRequest {
+    string VMID = 1;
+    FifoType FifoType = 2;
+}
+
+message GetFifoPathResponse {
+    string Path = 1;
+}
+
+message SetVMMetadataRequest {
+    string VMID = 1;
+    string Metadata = 2;
 }
\ No newline at end of file
diff --git a/runtime/service.go b/runtime/service.go
index ea847dd..47704ae 100644
--- a/runtime/service.go
+++ b/runtime/service.go
@@ -120,7 +120,6 @@ func NewService(ctx context.Context, id string, remotePublisher events.Publisher
 }
 
 func (s *service) StartShim(ctx context.Context, id, containerdBinary, containerdAddress string) (string, error) {
-	log.G(ctx).WithField("id", id).Debug("StartShim")
 	cmd, err := s.newCommand(ctx, containerdBinary, containerdAddress)
 	if err != nil {
 		return "", err
@@ -136,53 +135,38 @@ func (s *service) StartShim(ctx context.Context, id, containerdBinary, container
 		return "", err
 	}
 
-	defer func() {
-		log.G(ctx).WithField("id", id).Debug("closing shim socket")
-		socket.Close()
-	}()
+	defer socket.Close()
 
 	f, err := socket.File()
 	if err != nil {
 		return "", err
 	}
 
-	defer func() {
-		log.G(ctx).WithField("id", id).Debug("closing shim socket file")
-		f.Close()
-	}()
+	defer f.Close()
 
 	cmd.ExtraFiles = append(cmd.ExtraFiles, f)
 
 	if err := cmd.Start(); err != nil {
-		log.G(ctx).WithError(err).WithField("id", id).Error("Failed to Start()")
 		return "", err
 	}
 
 	defer func() {
 		if err != nil {
-			log.G(ctx).WithError(err).WithField("id", id).Error("killing shim process")
 			cmd.Process.Kill()
 		}
 	}()
 
 	// make sure to wait after start
-	go func() {
-		log.G(ctx).WithField("id", id).Debug("waiting on shim process")
-		waitErr := cmd.Wait()
-		log.G(ctx).WithError(waitErr).WithField("id", id).Debug("completed waiting on shim process")
-	}()
+	go cmd.Wait()
 	if err := shim.WritePidFile("shim.pid", cmd.Process.Pid); err != nil {
-		log.G(ctx).WithError(err).WithField("id", id).Error("failed to write pid file")
 		return "", err
 	}
 
 	if err := shim.WriteAddress("address", address); err != nil {
-		log.G(ctx).WithError(err).WithField("id", id).Error("failed to write address")
 		return "", err
 	}
 
 	if err := shim.SetScore(cmd.Process.Pid); err != nil {
-		log.G(ctx).WithError(err).WithField("id", id).Error("failed to set OOM score on shim")
 		return "", errors.Wrap(err, "failed to set OOM Score on shim")
 	}
 
@@ -217,10 +201,9 @@ func (s *service) Create(ctx context.Context, request *taskAPI.CreateTaskRequest
 	}
 	// TODO: should there be a lock here
 	if !s.agentStarted {
-		log.G(ctx).WithField("id", request.ID).Debug("calling startVM")
 		client, err := s.startVM(ctx, request, firecrackerConfig)
 		if err != nil {
-			log.G(ctx).WithError(err).WithField("id", request.ID).Error("failed to start VM")
+			log.G(ctx).WithError(err).Error("failed to start VM")
 			return nil, err
 		}
 
@@ -228,7 +211,7 @@ func (s *service) Create(ctx context.Context, request *taskAPI.CreateTaskRequest
 		s.agentStarted = true
 	}
 
-	log.G(ctx).WithField("id", request.ID).Info("creating task")
+	log.G(ctx).Infof("creating task '%s'", request.ID)
 
 	// Generate new anyData with bundle/config.json packed inside
 	anyData, err := packBundle(filepath.Join(request.Bundle, "config.json"), runcOpts)
@@ -244,8 +227,8 @@ func (s *service) Create(ctx context.Context, request *taskAPI.CreateTaskRequest
 		return nil, err
 	}
 	s.ctx, s.cancel = context.WithCancel(ctx)
-	s.proxyStdio(s.ctx, request.Stdin, request.Stdout, request.Stderr, s.machineCID)
-	log.G(ctx).WithField("id", request.ID).WithField("pid", resp.Pid).Info("successfully created task")
+	go s.proxyStdio(s.ctx, request.Stdin, request.Stdout, request.Stderr, s.machineCID)
+	log.G(ctx).Infof("successfully created task with pid %d", resp.Pid)
 
 	return resp, nil
 }
@@ -268,18 +251,17 @@ func (s *service) proxyStdio(ctx context.Context, stdin, stdout, stderr string,
 
 func proxyIO(ctx context.Context, path string, cid, port uint32, in bool) {
 	if path == "" {
-		log.G(ctx).WithField("cid", cid).WithField("port", port).Warn("skipping IO, path is empty")
 		return
 	}
-	log.G(ctx).WithField("path", path).WithField("cid", cid).WithField("port", port).Debug("setting up IO")
+	log.G(ctx).Debug("setting up IO for " + path)
 	f, err := fifo.OpenFifo(ctx, path, syscall.O_RDWR|syscall.O_NONBLOCK, 0700)
 	if err != nil {
-		log.G(ctx).WithError(err).WithField("path", path).Error("error opening fifo")
+		log.G(ctx).WithError(err).Error("error opening fifo")
 		return
 	}
 	conn, err := vsock.Dial(cid, port)
 	if err != nil {
-		log.G(ctx).WithError(err).WithField("path", path).Error("unable to dial agent vsock IO port")
+		log.G(ctx).WithError(err).Error("unable to dial agent vsock")
 		f.Close()
 		return
 	}
@@ -288,7 +270,7 @@ func proxyIO(ctx context.Context, path string, cid, port uint32, in bool) {
 		conn.Close()
 		f.Close()
 	}()
-	log.G(ctx).WithField("path", path).Debug("begin copying io")
+	log.G(ctx).Debug("begin copying io")
 	buf := make([]byte, internal.DefaultBufferSize)
 	if in {
 		_, err = io.CopyBuffer(conn, f, buf)
@@ -296,7 +278,7 @@ func proxyIO(ctx context.Context, path string, cid, port uint32, in bool) {
 		_, err = io.CopyBuffer(f, conn, buf)
 	}
 	if err != nil {
-		log.G(ctx).WithError(err).WithField("path", path).Error("error with stdio")
+		log.G(ctx).WithError(err).Error("error with stdio")
 	}
 }
 


Test patch:
diff --git a/firecracker-control/helpers_test.go b/firecracker-control/helpers_test.go
new file mode 100644
index 0000000..02c6a77
--- /dev/null
+++ b/firecracker-control/helpers_test.go
@@ -0,0 +1,225 @@
+// Copyright 2018-2019 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 service
+
+import (
+	"testing"
+
+	"github.com/firecracker-microvm/firecracker-go-sdk"
+	"github.com/stretchr/testify/assert"
+
+	models "github.com/firecracker-microvm/firecracker-go-sdk/client/models"
+
+	"github.com/firecracker-microvm/firecracker-containerd/proto"
+)
+
+const (
+	oneTimeBurst  = 800
+	refillTime    = 1000
+	capacity      = 400
+	rootDrivePath = "/path/to/root/drive"
+	loopDrivePath = "/path/to/loop/drive"
+	loopDriveRO   = false
+	memSize       = 4096
+	vcpuCount     = 2
+	mac           = "AA:FC:00:00:00:01"
+	hostDevName   = "tap0"
+)
+
+var (
+	defaultDrivesBuilder = firecracker.NewDrivesBuilder(rootDrivePath).AddDrive(
+		loopDrivePath, loopDriveRO)
+	defaultDrives             = defaultDrivesBuilder.Build()
+	overrideRootDrivesBuilder = firecracker.NewDrivesBuilder(loopDrivePath)
+	additionalDrivesBuilder   = firecracker.NewDrivesBuilder(rootDrivePath).AddDrive(
+		loopDrivePath, loopDriveRO).AddDrive(
+		loopDrivePath, loopDriveRO, func(d *models.Drive) {
+			d.RateLimiter = &models.RateLimiter{
+				Ops: &models.TokenBucket{
+					OneTimeBurst: firecracker.Int64(oneTimeBurst),
+					RefillTime:   firecracker.Int64(refillTime),
+					Size:         firecracker.Int64(capacity),
+				},
+			}
+		})
+)
+
+func TestOverrideVMConfigFromTaskOpts(t *testing.T) {
+	var testCases = []struct {
+		name            string
+		in              firecracker.Config
+		inDrivesBuilder firecracker.DrivesBuilder
+		vmConfig        *proto.FirecrackerConfig
+		expectedOut     firecracker.Config
+		expectedDrives  []models.Drive
+		expectedErr     bool
+	}{
+		{
+			name:            "network config",
+			in:              firecracker.Config{},
+			inDrivesBuilder: defaultDrivesBuilder,
+			vmConfig: &proto.FirecrackerConfig{
+				NetworkInterfaces: []*proto.FirecrackerNetworkInterface{
+					{
+						MacAddress:  mac,
+						HostDevName: hostDevName,
+						AllowMMDS:   true,
+						InRateLimiter: &proto.FirecrackerRateLimiter{
+							Bandwidth: &proto.FirecrackerTokenBucket{
+								OneTimeBurst: oneTimeBurst,
+								RefillTime:   refillTime,
+								Capacity:     capacity,
+							},
+						},
+						OutRateLimiter: &proto.FirecrackerRateLimiter{
+							Bandwidth: &proto.FirecrackerTokenBucket{
+								OneTimeBurst: oneTimeBurst,
+								RefillTime:   refillTime,
+								Capacity:     capacity,
+							},
+						},
+					},
+					{
+						MacAddress:  mac,
+						HostDevName: hostDevName,
+					},
+				},
+			},
+			expectedErr: false,
+			expectedOut: firecracker.Config{
+				NetworkInterfaces: []firecracker.NetworkInterface{
+					{
+						MacAddress:  mac,
+						HostDevName: hostDevName,
+						AllowMMDS:   true,
+						InRateLimiter: &models.RateLimiter{
+							Bandwidth: &models.TokenBucket{
+								OneTimeBurst: firecracker.Int64(oneTimeBurst),
+								RefillTime:   firecracker.Int64(refillTime),
+								Size:         firecracker.Int64(capacity),
+							},
+						},
+						OutRateLimiter: &models.RateLimiter{
+							Bandwidth: &models.TokenBucket{
+								OneTimeBurst: firecracker.Int64(oneTimeBurst),
+								RefillTime:   firecracker.Int64(refillTime),
+								Size:         firecracker.Int64(capacity),
+							},
+						},
+					},
+					{
+						MacAddress:  mac,
+						HostDevName: hostDevName,
+					},
+				},
+			},
+			expectedDrives: defaultDrives,
+		},
+		{
+			name:            "machine config",
+			in:              firecracker.Config{},
+			inDrivesBuilder: defaultDrivesBuilder,
+			vmConfig: &proto.FirecrackerConfig{
+				MachineCfg: &proto.FirecrackerMachineConfiguration{
+					MemSizeMib: memSize,
+					VcpuCount:  vcpuCount,
+				},
+			},
+			expectedErr: false,
+			expectedOut: firecracker.Config{
+				MachineCfg: models.MachineConfiguration{
+					MemSizeMib: memSize,
+					VcpuCount:  vcpuCount,
+				},
+			},
+			expectedDrives: defaultDrivesBuilder.Build(),
+		},
+		{
+			name:            "invalid memory in machine config",
+			in:              firecracker.Config{},
+			inDrivesBuilder: defaultDrivesBuilder,
+			vmConfig: &proto.FirecrackerConfig{
+				MachineCfg: &proto.FirecrackerMachineConfiguration{
+					VcpuCount: vcpuCount,
+				},
+			},
+			expectedErr:    true,
+			expectedOut:    firecracker.Config{},
+			expectedDrives: defaultDrivesBuilder.Build(),
+		},
+		{
+			name:            "invalid vcpu count in machine config",
+			in:              firecracker.Config{},
+			inDrivesBuilder: defaultDrivesBuilder,
+			vmConfig: &proto.FirecrackerConfig{
+				MachineCfg: &proto.FirecrackerMachineConfiguration{
+					MemSizeMib: memSize,
+				},
+			},
+			expectedErr:    true,
+			expectedOut:    firecracker.Config{},
+			expectedDrives: defaultDrivesBuilder.Build(),
+		},
+		{
+			name:            "storage config overrides root drive",
+			in:              firecracker.Config{},
+			inDrivesBuilder: overrideRootDrivesBuilder,
+			vmConfig: &proto.FirecrackerConfig{
+				RootDrive: &proto.FirecrackerDrive{
+					PathOnHost: loopDrivePath,
+				},
+			},
+			expectedErr:    false,
+			expectedOut:    firecracker.Config{},
+			expectedDrives: overrideRootDrivesBuilder.Build(),
+		},
+		{
+			name:            "storage config adds additional drives",
+			in:              firecracker.Config{},
+			inDrivesBuilder: defaultDrivesBuilder,
+			vmConfig: &proto.FirecrackerConfig{
+				AdditionalDrives: []*proto.FirecrackerDrive{
+					{
+						PathOnHost: loopDrivePath,
+						RateLimiter: &proto.FirecrackerRateLimiter{
+							Ops: &proto.FirecrackerTokenBucket{
+								OneTimeBurst: oneTimeBurst,
+								RefillTime:   refillTime,
+								Capacity:     capacity,
+							},
+						},
+					},
+				},
+			},
+			expectedErr:    false,
+			expectedOut:    firecracker.Config{},
+			expectedDrives: additionalDrivesBuilder.Build(),
+		},
+	}
+	for _, tc := range testCases {
+		// scopelint gets sad if we use tc directly. The specific complaint is
+		// "Using the variable on range scope in function literal".
+		test := tc
+		t.Run(test.name, func(t *testing.T) {
+			out, drivesBuilder, err := overrideVMConfigFromTaskOpts(test.in, test.vmConfig, test.inDrivesBuilder)
+			if test.expectedErr {
+				assert.Error(t, err)
+			} else {
+				assert.NoError(t, err)
+				assert.Equal(t, test.expectedOut, out)
+				assert.Equal(t, test.expectedDrives, drivesBuilder.Build())
+			}
+		})
+	}
+}
diff --git a/firecracker-control/local_mock_test.go b/firecracker-control/local_mock_test.go
new file mode 100644
index 0000000..179531c
--- /dev/null
+++ b/firecracker-control/local_mock_test.go
@@ -0,0 +1,82 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: local.go
+
+// Package service is a generated GoMock package.
+package service
+
+import (
+	gomock "github.com/golang/mock/gomock"
+	context "golang.org/x/net/context"
+	reflect "reflect"
+)
+
+// MockmachineIface is a mock of machineIface interface
+type MockmachineIface struct {
+	ctrl     *gomock.Controller
+	recorder *MockmachineIfaceMockRecorder
+}
+
+// MockmachineIfaceMockRecorder is the mock recorder for MockmachineIface
+type MockmachineIfaceMockRecorder struct {
+	mock *MockmachineIface
+}
+
+// NewMockmachineIface creates a new mock instance
+func NewMockmachineIface(ctrl *gomock.Controller) *MockmachineIface {
+	mock := &MockmachineIface{ctrl: ctrl}
+	mock.recorder = &MockmachineIfaceMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use
+func (m *MockmachineIface) EXPECT() *MockmachineIfaceMockRecorder {
+	return m.recorder
+}
+
+// Start mocks base method
+func (m *MockmachineIface) Start(arg0 context.Context) error {
+	ret := m.ctrl.Call(m, "Start", arg0)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// Start indicates an expected call of Start
+func (mr *MockmachineIfaceMockRecorder) Start(arg0 interface{}) *gomock.Call {
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockmachineIface)(nil).Start), arg0)
+}
+
+// StopVMM mocks base method
+func (m *MockmachineIface) StopVMM() error {
+	ret := m.ctrl.Call(m, "StopVMM")
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// StopVMM indicates an expected call of StopVMM
+func (mr *MockmachineIfaceMockRecorder) StopVMM() *gomock.Call {
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StopVMM", reflect.TypeOf((*MockmachineIface)(nil).StopVMM))
+}
+
+// Wait mocks base method
+func (m *MockmachineIface) Wait(arg0 context.Context) error {
+	ret := m.ctrl.Call(m, "Wait", arg0)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// Wait indicates an expected call of Wait
+func (mr *MockmachineIfaceMockRecorder) Wait(arg0 interface{}) *gomock.Call {
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Wait", reflect.TypeOf((*MockmachineIface)(nil).Wait), arg0)
+}
+
+// SetMetadata mocks base method
+func (m *MockmachineIface) SetMetadata(arg0 context.Context, arg1 interface{}) error {
+	ret := m.ctrl.Call(m, "SetMetadata", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// SetMetadata indicates an expected call of SetMetadata
+func (mr *MockmachineIfaceMockRecorder) SetMetadata(arg0, arg1 interface{}) *gomock.Call {
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetMetadata", reflect.TypeOf((*MockmachineIface)(nil).SetMetadata), arg0, arg1)
+}
diff --git a/firecracker-control/local_test.go b/firecracker-control/local_test.go
new file mode 100644
index 0000000..9403d56
--- /dev/null
+++ b/firecracker-control/local_test.go
@@ -0,0 +1,182 @@
+// Copyright 2018-2019 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 mockgen -source=local.go -destination=local_mock_test.go -package=service
+
+package service
+
+import (
+	"context"
+	"testing"
+	"time"
+
+	"github.com/firecracker-microvm/firecracker-go-sdk"
+	"github.com/golang/mock/gomock"
+	"github.com/stretchr/testify/assert"
+
+	"github.com/firecracker-microvm/firecracker-containerd/proto"
+)
+
+var testCtx = context.Background()
+
+func TestLocal_buildVMConfiguration(t *testing.T) {
+	request := &proto.CreateVMRequest{
+		KernelArgs:      "kernel_args",
+		KernelImagePath: "kernel_path",
+		MachineCfg:      &proto.FirecrackerMachineConfiguration{},
+		RootDrive: &proto.FirecrackerDrive{
+			PathOnHost: "/",
+		},
+		NetworkInterfaces: []*proto.FirecrackerNetworkInterface{
+			{MacAddress: "mac", HostDevName: "host", AllowMMDS: true},
+		},
+	}
+
+	obj := &local{
+		rootPath:    "/",
+		findVsockFn: func(context.Context) (uint32, error) { return 3, nil },
+	}
+
+	config, err := obj.buildVMConfiguration(testCtx, "1", request)
+	assert.NoError(t, err)
+	assert.NotNil(t, config)
+
+	assert.Len(t, config.VsockDevices, 1)
+	assert.EqualValues(t, 3, config.VsockDevices[0].CID)
+
+	assert.Equal(t, "kernel_args", config.KernelArgs)
+	assert.Equal(t, "kernel_path", config.KernelImagePath)
+
+	assert.Equal(t, "/vm_1.socket", config.SocketPath)
+	assert.Equal(t, "/vm_1_log.fifo", config.LogFifo)
+	assert.Equal(t, "/vm_1_metrics.fifo", config.MetricsFifo)
+
+	assert.Len(t, config.Drives, 1)
+
+	rootDrive := config.Drives[0]
+	assert.True(t, *rootDrive.IsRootDevice)
+	assert.Equal(t, "/", *rootDrive.PathOnHost)
+
+	assert.Len(t, config.NetworkInterfaces, 1)
+
+	ni := config.NetworkInterfaces[0]
+	assert.Equal(t, "mac", ni.MacAddress)
+	assert.Equal(t, "host", ni.HostDevName)
+	assert.True(t, ni.AllowMMDS)
+}
+
+func TestLocal_buildInvalidConfiguration(t *testing.T) {
+	obj := &local{
+		rootPath:    "/",
+		findVsockFn: func(context.Context) (uint32, error) { return 3, nil },
+	}
+
+	request := &proto.CreateVMRequest{}
+
+	_, err := obj.buildVMConfiguration(testCtx, "1", request)
+	assert.EqualError(t, err, "invalid machine configuration")
+
+	request.MachineCfg = &proto.FirecrackerMachineConfiguration{}
+	_, err = obj.buildVMConfiguration(testCtx, "1", request)
+	assert.EqualError(t, err, "root drive can't be empty")
+}
+
+func TestLocal_makeID(t *testing.T) {
+	obj := local{}
+	id1, err := obj.makeID()
+	assert.NoError(t, err)
+	assert.NotEmpty(t, id1)
+
+	id2, err := obj.makeID()
+	assert.NoError(t, err)
+	assert.NotEqual(t, id1, id2)
+}
+
+func TestLocal_startMachine(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
+
+	machine := NewMockmachineIface(ctrl)
+	machine.EXPECT().Start(gomock.Any()).Times(1).Return(nil)
+	machine.EXPECT().Wait(gomock.Any()).Times(1).Return(nil)
+
+	obj := &local{
+		vm: map[string]*instance{"1": {}},
+	}
+
+	err := obj.startMachine(testCtx, "1", machine)
+	time.Sleep(100 * time.Microsecond) // Give it some time to spawn the goroutine
+	assert.NoError(t, err)
+	assert.Empty(t, obj.vm) // Make sure "1" removed from the VM list
+}
+
+func TestLocal_StopVM(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
+
+	machine := NewMockmachineIface(ctrl)
+	machine.EXPECT().StopVMM().Times(1).Return(nil)
+
+	obj := &local{
+		vm: map[string]*instance{"1": {machine: machine}},
+	}
+
+	_, err := obj.StopVM(testCtx, &proto.StopVMRequest{VMID: "1"})
+	assert.NoError(t, err)
+}
+
+func TestLocal_StopInvalidVM(t *testing.T) {
+	obj := local{}
+	_, err := obj.StopVM(testCtx, &proto.StopVMRequest{VMID: "2"})
+	assert.Equal(t, ErrVMNotFound, err)
+}
+
+func TestLocal_GetVMAddress(t *testing.T) {
+	obj := &local{
+		vm: map[string]*instance{"1": {cfg: &firecracker.Config{SocketPath: "2"}}},
+	}
+
+	response, err := obj.GetVMAddress(testCtx, &proto.GetVMAddressRequest{VMID: "1"})
+	assert.NoError(t, err)
+	assert.NotNil(t, response)
+	assert.Equal(t, "2", response.SocketPath)
+}
+
+func TestLocal_GetFifoPath(t *testing.T) {
+	obj := &local{
+		vm: map[string]*instance{"1": {cfg: &firecracker.Config{LogFifo: "logs", MetricsFifo: "metrics"}}},
+	}
+
+	response, err := obj.GetFifoPath(testCtx, &proto.GetFifoPathRequest{VMID: "1", FifoType: proto.FifoType_LOG})
+	assert.NoError(t, err)
+	assert.Equal(t, "logs", response.Path)
+
+	response, err = obj.GetFifoPath(testCtx, &proto.GetFifoPathRequest{VMID: "1", FifoType: proto.FifoType_METRICS})
+	assert.NoError(t, err)
+	assert.Equal(t, "metrics", response.Path)
+}
+
+func TestLocal_SetVMMetadata(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
+
+	machine := NewMockmachineIface(ctrl)
+	machine.EXPECT().SetMetadata(gomock.Any(), gomock.Eq("test")).Times(1).Return(nil)
+
+	obj := &local{
+		vm: map[string]*instance{"1": {machine: machine}},
+	}
+
+	_, err := obj.SetVMMetadata(testCtx, &proto.SetVMMetadataRequest{VMID: "1", Metadata: "test"})
+	assert.NoError(t, err)
+}
diff --git a/firecracker-control/vsock_test.go b/firecracker-control/vsock_test.go
new file mode 100644
index 0000000..601e3e6
--- /dev/null
+++ b/firecracker-control/vsock_test.go
@@ -0,0 +1,44 @@
+// Copyright 2018-2019 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 service
+
+import (
+	"context"
+	"syscall"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestFindNextAvailableVsockCID(t *testing.T) {
+	sysCall = func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) {
+		return 0, 0, 0
+	}
+
+	defer func() {
+		sysCall = syscall.Syscall
+	}()
+
+	cid, err := findNextAvailableVsockCID(context.Background())
+	require.NoError(t, err,
+		"Do you have permission to interact with /dev/vhost-vsock?\n"+
+			"Grant yourself permission with `sudo setfacl -m u:${USER}:rw /dev/vhost-vsock`")
+	require.EqualValues(t, uint32(3), cid)
+
+	ctx, cancel := context.WithCancel(context.Background())
+	cancel()
+
+	_, err = findNextAvailableVsockCID(ctx)
+	require.Equal(t, context.Canceled, err)
+}