From f56d66f196bee808526e86df2c3c063a887c6fef Mon Sep 17 00:00:00 2001
From: Wei Gao <gaowei66@huawei.com>
Date: Sat, 7 Aug 2021 10:39:11 +0800
Subject: [PATCH 1/6] runtime: add support of new sandbox hypervisor type
 StratoVirt.

Signed-off-by: Wei Gao <gaowei66@huawei.com>
---
 src/runtime/pkg/katautils/config.go      |  96 +++-
 src/runtime/virtcontainers/hypervisor.go |  12 +
 src/runtime/virtcontainers/stratovirt.go | 642 +++++++++++++++++++++++
 3 files changed, 749 insertions(+), 1 deletion(-)
 create mode 100644 src/runtime/virtcontainers/stratovirt.go

diff --git a/src/runtime/pkg/katautils/config.go b/src/runtime/pkg/katautils/config.go
index 6114aa39..f94ac4fd 100644
--- a/src/runtime/pkg/katautils/config.go
+++ b/src/runtime/pkg/katautils/config.go
@@ -50,6 +50,7 @@ const (
 	clhHypervisorTableType         = "clh"
 	qemuHypervisorTableType        = "qemu"
 	acrnHypervisorTableType        = "acrn"
+	stratovirtHypervisorTable      = "stratovirt"
 
 	// the maximum amount of PCI bridges that can be cold plugged in a VM
 	maxPCIBridges uint32 = 5
@@ -870,6 +871,96 @@ func newClhHypervisorConfig(h hypervisor) (vc.HypervisorConfig, error) {
 	}, nil
 }
 
+func newStratovirtHypervisorConfig(h hypervisor) (vc.HypervisorConfig, error) {
+	hypervisor, err := h.path()
+	if err != nil {
+		return vc.HypervisorConfig{}, err
+	}
+
+	kernel, err := h.kernel()
+	if err != nil {
+		return vc.HypervisorConfig{}, err
+	}
+
+	initrd, image, err := h.getInitrdAndImage()
+	if err != nil {
+		return vc.HypervisorConfig{}, err
+	}
+
+	if image != "" && initrd != "" {
+		return vc.HypervisorConfig{},
+			errors.New("having both an image and an initrd defined in the configuration file is not supported")
+	}
+
+	if image == "" && initrd == "" {
+		return vc.HypervisorConfig{},
+			errors.New("either image or initrd must be defined in the configuration file")
+	}
+
+	kernelParams := h.kernelParams()
+	machineType := h.machineType()
+
+	blockDriver, err := h.blockDeviceDriver()
+	if err != nil {
+		return vc.HypervisorConfig{}, err
+	}
+
+	sharedFS, err := h.sharedFS()
+	if err != nil {
+		return vc.HypervisorConfig{}, err
+	}
+
+	if sharedFS == config.VirtioFS && h.VirtioFSDaemon == "" {
+		return vc.HypervisorConfig{},
+			errors.New("cannot enable virtio-fs without daemon path in configuration file")
+	}
+
+	if vSock, err := utils.SupportsVsocks(); !vSock {
+		return vc.HypervisorConfig{}, err
+	}
+
+	return vc.HypervisorConfig{
+		HypervisorPath:          hypervisor,
+		HypervisorPathList:      h.HypervisorPathList,
+		KernelPath:              kernel,
+		InitrdPath:              initrd,
+		ImagePath:               image,
+		KernelParams:            vc.DeserializeParams(strings.Fields(kernelParams)),
+		HypervisorMachineType:   machineType,
+		NumVCPUs:                h.defaultVCPUs(),
+		DefaultMaxVCPUs:         h.defaultMaxVCPUs(),
+		MemorySize:              h.defaultMemSz(),
+		MemSlots:                h.defaultMemSlots(),
+		MemOffset:               h.defaultMemOffset(),
+		EntropySource:           h.GetEntropySource(),
+		EntropySourceList:       h.EntropySourceList,
+		DefaultBridges:          h.defaultBridges(),
+		DisableBlockDeviceUse:   h.DisableBlockDeviceUse,
+		SharedFS:                sharedFS,
+		VirtioFSDaemon:          h.VirtioFSDaemon,
+		VirtioFSDaemonList:      h.VirtioFSDaemonList,
+		VirtioFSCacheSize:       h.VirtioFSCacheSize,
+		VirtioFSCache:           h.defaultVirtioFSCache(),
+		VirtioFSExtraArgs:       h.VirtioFSExtraArgs,
+		FileBackedMemRootDir:    h.FileBackedMemRootDir,
+		FileBackedMemRootList:   h.FileBackedMemRootList,
+		Mlock:                   !h.Swap,
+		Debug:                   h.Debug,
+		DisableNestingChecks:    h.DisableNestingChecks,
+		BlockDeviceDriver:       blockDriver,
+		BlockDeviceCacheSet:     h.BlockDeviceCacheSet,
+		BlockDeviceCacheDirect:  h.BlockDeviceCacheDirect,
+		BlockDeviceCacheNoflush: h.BlockDeviceCacheNoflush,
+		EnableIOThreads:         h.EnableIOThreads,
+		DisableVhostNet:         h.DisableVhostNet,
+		EnableVhostUserStore:    h.EnableVhostUserStore,
+		VhostUserStorePath:      h.vhostUserStorePath(),
+		VhostUserStorePathList:  h.VhostUserStorePathList,
+		GuestHookPath:           h.guestHookPath(),
+		EnableAnnotations:       h.EnableAnnotations,
+	}, nil
+}
+
 func newFactoryConfig(f factory) (oci.FactoryConfig, error) {
 	if f.TemplatePath == "" {
 		f.TemplatePath = defaultTemplatePath
@@ -903,6 +994,9 @@ func updateRuntimeConfigHypervisor(configPath string, tomlConf tomlConfig, confi
 		case clhHypervisorTableType:
 			config.HypervisorType = vc.ClhHypervisor
 			hConfig, err = newClhHypervisorConfig(hypervisor)
+		case stratovirtHypervisorTable:
+			config.HypervisorType = vc.StratovirtHypervisor
+			hConfig, err = newStratovirtHypervisorConfig(hypervisor)
 		}
 
 		if err != nil {
@@ -1287,7 +1381,7 @@ func checkHypervisorConfig(config vc.HypervisorConfig) error {
 	memSizeMB := int64(config.MemorySize)
 
 	if memSizeMB == 0 {
-		return errors.New("VM memory cannot be zero")
+		return errors.New(fmt.Sprintf("The VM memory cannot be zero, %s", config.ImagePath))
 	}
 
 	mb := int64(1024 * 1024)
diff --git a/src/runtime/virtcontainers/hypervisor.go b/src/runtime/virtcontainers/hypervisor.go
index 767215b6..615baa80 100644
--- a/src/runtime/virtcontainers/hypervisor.go
+++ b/src/runtime/virtcontainers/hypervisor.go
@@ -44,6 +44,9 @@ const (
 	// ClhHypervisor is the ICH hypervisor.
 	ClhHypervisor HypervisorType = "clh"
 
+	// StratovirtHypervisor is the StratoVirt hypervisor
+	StratovirtHypervisor HypervisorType = "stratovirt"
+
 	// MockHypervisor is a mock hypervisor for testing purposes
 	MockHypervisor HypervisorType = "mock"
 )
@@ -159,6 +162,9 @@ func (hType *HypervisorType) Set(value string) error {
 	case "clh":
 		*hType = ClhHypervisor
 		return nil
+	case "stratovirt":
+		*hType = StratovirtHypervisor
+		return nil
 	case "mock":
 		*hType = MockHypervisor
 		return nil
@@ -178,6 +184,8 @@ func (hType *HypervisorType) String() string {
 		return string(AcrnHypervisor)
 	case ClhHypervisor:
 		return string(ClhHypervisor)
+	case StratovirtHypervisor:
+		return string(StratovirtHypervisor)
 	case MockHypervisor:
 		return string(MockHypervisor)
 	default:
@@ -207,6 +215,10 @@ func newHypervisor(hType HypervisorType) (hypervisor, error) {
 		return &cloudHypervisor{
 			store: store,
 		}, nil
+	case StratovirtHypervisor:
+		return &stratovirt{
+			store: store,
+		}, nil
 	case MockHypervisor:
 		return &mockHypervisor{}, nil
 	default:
diff --git a/src/runtime/virtcontainers/stratovirt.go b/src/runtime/virtcontainers/stratovirt.go
new file mode 100644
index 00000000..4fec96d3
--- /dev/null
+++ b/src/runtime/virtcontainers/stratovirt.go
@@ -0,0 +1,642 @@
+package virtcontainers
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"strconv"
+	"strings"
+	"syscall"
+	"time"
+
+	govmmQemu "github.com/kata-containers/govmm/qemu"
+	"github.com/pkg/errors"
+	"github.com/sirupsen/logrus"
+
+	"github.com/kata-containers/kata-containers/src/runtime/virtcontainers/device/config"
+	persistapi "github.com/kata-containers/kata-containers/src/runtime/virtcontainers/persist/api"
+	"github.com/kata-containers/kata-containers/src/runtime/virtcontainers/types"
+	"github.com/kata-containers/kata-containers/src/runtime/virtcontainers/utils"
+	"go.opentelemetry.io/otel"
+	otelLabel "go.opentelemetry.io/otel/label"
+	otelTrace "go.opentelemetry.io/otel/trace"
+)
+
+const defaultDummyMac = "22:33:44:aa:bb:"
+const mmioBlkCount = 4
+const mmioNetCount = 2
+const randomDevice = "/dev/urandom"
+
+type stratovirtDev struct {
+	dev     interface{}
+	devType deviceType
+}
+
+type stratovirt struct {
+	id              string
+	ctx             context.Context
+	sandbox         *Sandbox
+	store           persistapi.PersistDriver
+	config          HypervisorConfig
+	pid             int
+	consolePath     string
+	socketPath      string
+	qmpMonitorCh    qmpChannel
+	devices         []stratovirtDev
+	HotpluggedVCPUs []CPUDevice
+	mmioBlkSlots    [mmioBlkCount]bool
+	mmioNetSlots    [mmioNetCount]bool
+}
+
+func (s *stratovirt) Logger() *logrus.Entry {
+	return virtLog.WithField("subsystem", "stratovirt")
+}
+
+func (s *stratovirt) trace(parent context.Context, name string) (otelTrace.Span, context.Context) {
+	if parent == nil {
+		s.Logger().WithField("type", "bug").Error("trace called before context set")
+		parent = context.Background()
+	}
+
+	tracer := otel.Tracer("kata")
+	ctx, span := tracer.Start(parent, name, otelTrace.WithAttributes(otelLabel.String("source", "runtime"), otelLabel.String("package", "virtcontainers"), otelLabel.String("subsystem", "hypervisor"), otelLabel.String("type", "stratovirt"), otelLabel.String("sandbox_id", s.id)))
+
+	return span, ctx
+}
+
+func (s *stratovirt) getKernelCmdLine(useImage bool) string {
+	var params []string
+
+	if useImage {
+		params = append(params, "root=/dev/vda")
+	}
+
+	params = append(params, "pci=off")
+	params = append(params, "reboot=k")
+	params = append(params, "panic=1")
+	params = append(params, "iommu=off")
+	params = append(params, "acpi=off")
+	params = append(params, "quiet")
+	params = append(params, "agent.use_vsock=true")
+	params = append(params, "random.trust_cpu=on")
+	params = append(params, "rw")
+	params = append(params, SerializeParams(s.config.KernelParams, "=")...)
+
+	return strings.Join(params, " ")
+}
+
+func (s *stratovirt) hypervisorConfig() HypervisorConfig {
+	return s.config
+}
+
+func (s *stratovirt) createSandbox(ctx context.Context, id string, networkNS NetworkNamespace, hypervisorConfig *HypervisorConfig) error {
+	s.ctx = ctx
+
+	var span otelTrace.Span
+	span, _ = s.trace(ctx, "createSandbox")
+	defer span.End()
+
+	s.id = id
+	s.config = *hypervisorConfig
+
+	s.socketPath = filepath.Join(s.store.RunVMStoragePath(), id, "qmp.sock")
+	s.consolePath = filepath.Join(s.store.RunVMStoragePath(), id, "console.sock")
+	s.qmpMonitorCh = qmpChannel{
+		ctx:  s.ctx,
+		path: s.socketPath,
+	}
+
+	return nil
+}
+
+func (s *stratovirt) waitSandBoxStarted(timeout int) error {
+	timeStart := time.Now()
+	for {
+		err := s.qmpSetup()
+		if err == nil {
+			break
+		}
+
+		if int(time.Since(timeStart).Seconds()) > timeout {
+			return fmt.Errorf("Failed to connect to StratoVirt instance (timeout %ds): %v", timeout, err)
+		}
+
+		time.Sleep(time.Duration(50) * time.Millisecond)
+	}
+
+	if err := s.qmpMonitorCh.qmp.ExecuteQMPCapabilities(s.qmpMonitorCh.ctx); err != nil {
+		s.Logger().WithError(err).Error(qmpCapErrMsg)
+		return err
+	}
+
+	return nil
+}
+
+func (s *stratovirt) startSandbox(ctx context.Context, timeout int) error {
+	span, _ := s.trace(ctx, "startSandbox")
+	defer span.End()
+
+	var params []string
+	var use_image bool
+	params = append(params, "-name", fmt.Sprintf("sandbox-%s", s.id))
+	params = append(params, "-qmp", fmt.Sprintf("unix:%s,server,nowait", s.socketPath))
+
+	if kernelPath, err := s.config.KernelAssetPath(); err == nil {
+		params = append(params, "-kernel", kernelPath)
+	}
+
+	initrdPath, err := s.config.InitrdAssetPath()
+	if err != nil {
+		return err
+	}
+
+	if initrdPath == "" {
+		imagePath, err := s.config.ImageAssetPath()
+		if err != nil {
+			return err
+		}
+		use_image = true
+		s.mmioBlkSlots[0] = true
+		params = append(params, "-device", "virtio-blk-device,drive=rootfs")
+		params = append(params, "-drive", fmt.Sprintf("id=rootfs,file=%s,direct=off", imagePath))
+	} else {
+		use_image = false
+		params = append(params, "-initrd", initrdPath)
+	}
+
+	params = append(params, "-append", s.getKernelCmdLine(use_image))
+	params = append(params, "-smp", fmt.Sprintf("%d", s.config.NumVCPUs))
+	params = append(params, "-m", fmt.Sprintf("%d", uint64(s.config.MemorySize)))
+	params = append(params, "-device", "virtio-serial-device")
+	params = append(params, "-device", "virtconsole,chardev=charconsole0,id=virtioconsole0")
+	params = append(params, "-object", fmt.Sprintf("rng-random,id=objrng0,filename=%s", randomDevice))
+	params = append(params, "-device", "virtio-rng-device,rng=objrng0")
+	params = append(params, "-chardev", fmt.Sprintf("socket,id=charconsole0,path=%s,server,nowait", s.consolePath))
+	params = append(params, "-pidfile", filepath.Join(s.store.RunVMStoragePath(), s.id, "pid"))
+
+	// add devices to cmdline
+	for _, d := range s.devices {
+		switch v := d.dev.(type) {
+		case Endpoint:
+			name := v.Name()
+			mac := v.HardwareAddr()
+			tapName := v.NetworkPair().TapInterface.TAPIface.Name
+			params = append(params, "-device", fmt.Sprintf("virtio-net-device,netdev=%s,id=%s,mac=%s", name, name, mac))
+			params = append(params, "-netdev", fmt.Sprintf("tap,id=%s,ifname=%s", name, tapName))
+		case config.BlockDrive:
+			id := v.ID
+			path := v.File
+			params = append(params, "-device", fmt.Sprintf("virtio-blk-device, drive=%s", id))
+			params = append(params, "-drive", fmt.Sprintf("id=%s,file=%s", id, path))
+		case types.VSock:
+			v.VhostFd.Close()
+			params = append(params, "-device", fmt.Sprintf("vhost-vsock-device,id=vsock-id,guest-cid=%d", v.ContextID))
+		default:
+			s.Logger().Error("Adding device type is unsupported")
+		}
+	}
+
+	// daemonize
+	params = append(params, "-daemonize")
+
+	// append logfile only on debug
+	if s.config.Debug {
+		dir := filepath.Join(s.store.RunVMStoragePath(), s.id)
+		params = append(params, "-D", fmt.Sprintf("%s/stratovirt.log", dir))
+	}
+
+	dir := filepath.Join(s.store.RunVMStoragePath(), s.id)
+	err = os.MkdirAll(dir, DirMode)
+	if err != nil {
+		return err
+	}
+	defer func() {
+		if err != nil {
+			if err := os.RemoveAll(dir); err != nil {
+				s.Logger().WithError(err).Error("Fail to clean up vm dir %s", dir)
+			}
+		}
+	}()
+
+	binPath, err := s.config.HypervisorAssetPath()
+	if err != nil {
+		s.Logger().WithField("Fail to get hypervisor bin path", err).Error()
+		return err
+	}
+
+	cmd := exec.CommandContext(s.ctx, binPath, params...)
+	s.Logger().Info("StratoVirt start with params: ", cmd)
+
+	if err := cmd.Start(); err != nil {
+		s.Logger().WithField("Error starting hypervisor, please check the params", err).Error()
+		return err
+	}
+	s.pid = cmd.Process.Pid
+
+	if err = s.waitSandBoxStarted(timeout); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (s *stratovirt) stopSandbox(ctx context.Context, force bool) error {
+	span, _ := s.trace(ctx, "stopSandbox")
+	defer span.End()
+
+	defer func() {
+		dir := filepath.Join(s.store.RunVMStoragePath(), s.id)
+		link, _ := filepath.EvalSymlinks(dir)
+
+		if err := os.RemoveAll(dir); err != nil {
+			s.Logger().WithError(err).Warnf("Failed to clean up vm dir %s", dir)
+		}
+
+		if link != dir && link != "" {
+			if err := os.RemoveAll(link); err != nil {
+				s.Logger().WithError(err).WithField("link", link).Warn("Failed to remove vm path link %s", link)
+			}
+		}
+	}()
+
+	if !force {
+		err := s.qmpSetup()
+		if err != nil {
+			return err
+		}
+
+		err = s.qmpMonitorCh.qmp.ExecuteQuit(s.qmpMonitorCh.ctx)
+		if err != nil {
+			s.Logger().WithError(err).Error("Fail to execute qmp: QUIT")
+			return err
+		}
+	} else {
+		if s.pid > 0 {
+			syscall.Kill(s.pid, syscall.SIGKILL)
+		}
+	}
+	return nil
+}
+
+func (s *stratovirt) pauseSandbox(ctx context.Context) error {
+	return nil
+}
+
+func (s *stratovirt) saveSandbox() error {
+	return nil
+}
+
+func (s *stratovirt) resumeSandbox(ctx context.Context) error {
+	return nil
+}
+
+func (s *stratovirt) addDevice(ctx context.Context, devInfo interface{}, devType deviceType) error {
+	span, _ := s.trace(ctx, "addDevice")
+	defer span.End()
+
+	dev := stratovirtDev{
+		dev:     devInfo,
+		devType: devType,
+	}
+	s.devices = append(s.devices, dev)
+
+	return nil
+}
+
+func (s *stratovirt) getDevSlot(Name string, isPut bool) (slot int, err error) {
+	Name = filepath.Base(strings.ToLower(Name))
+
+	if strings.HasPrefix(Name, "eth") {
+		idxStr := strings.TrimPrefix(Name, "eth")
+		if idxStr == Name {
+			return 0, fmt.Errorf("Could not parse idx from Name %q", Name)
+		}
+
+		idx, err := strconv.Atoi(idxStr)
+		if err != nil {
+			return 0, fmt.Errorf("Could not convert to int from Str %q", idxStr)
+		}
+
+		if !isPut && s.mmioNetSlots[idx] {
+			return 0, fmt.Errorf("GetDevSlot failed, slot is being used %q", idxStr)
+		}
+		s.mmioNetSlots[idx] = !isPut
+
+		return idx, nil
+	} else if strings.HasPrefix(Name, "vd") {
+		charStr := strings.TrimPrefix(Name, "vd")
+		if charStr == Name {
+			return 0, fmt.Errorf("Could not parse idx from Name %q", Name)
+		}
+
+		char := []rune(charStr)
+		idx := int(char[0] - 'a')
+
+		if !isPut && s.mmioBlkSlots[idx] {
+			return 0, fmt.Errorf("GetDevSlot failed, slot is being used %q", charStr)
+		}
+		s.mmioBlkSlots[idx] = !isPut
+
+		return idx, nil
+	}
+
+	return 0, fmt.Errorf("GetDevSlot failed, Name is invalid %q", Name)
+}
+
+func (s *stratovirt) hotplugNet(endpoint Endpoint, op operation) (err error) {
+	err = s.qmpSetup()
+	if err != nil {
+		return err
+	}
+	var tap TapInterface
+
+	switch endpoint.Type() {
+	case VethEndpointType:
+		drive := endpoint.(*VethEndpoint)
+		tap = drive.NetPair.TapInterface
+	case TapEndpointType:
+		drive := endpoint.(*TapEndpoint)
+		tap = drive.TapInterface
+	default:
+		return fmt.Errorf("Endpoint is not supported")
+	}
+
+	switch op {
+	case addDevice:
+		var (
+			VMFdNames    []string
+			VhostFdNames []string
+		)
+		for i, VMFd := range tap.VMFds {
+			fdName := fmt.Sprintf("fd%d", i)
+			if err := s.qmpMonitorCh.qmp.ExecuteGetFD(s.qmpMonitorCh.ctx, fdName, VMFd); err != nil {
+				return err
+			}
+			VMFdNames = append(VMFdNames, fdName)
+		}
+		for i, VhostFd := range tap.VhostFds {
+			fdName := fmt.Sprintf("vhostfd%d", i)
+			if err := s.qmpMonitorCh.qmp.ExecuteGetFD(s.qmpMonitorCh.ctx, fdName, VhostFd); err != nil {
+				return err
+			}
+			VhostFd.Close()
+			VhostFdNames = append(VhostFdNames, fdName)
+		}
+
+		slot, err := s.getDevSlot(endpoint.Name(), false)
+		if err != nil {
+			return fmt.Errorf("Could not get unused slot for %q", endpoint.Name())
+		}
+
+		if len(VMFdNames) != 0 || len(VhostFdNames) != 0 {
+			if err := s.qmpMonitorCh.qmp.ExecuteNetdevAddByFds(s.qmpMonitorCh.ctx, "tap", tap.ID, VMFdNames, VhostFdNames); err != nil {
+				s.getDevSlot(endpoint.Name(), true)
+				return err
+			}
+		} else {
+			if err := s.qmpMonitorCh.qmp.ExecuteNetdevAdd(s.qmpMonitorCh.ctx, "tap", tap.ID, tap.TAPIface.Name, "no", "no", 0); err != nil {
+				s.getDevSlot(endpoint.Name(), true)
+				return err
+			}
+		}
+		if err := s.qmpMonitorCh.qmp.ExecuteNetPCIDeviceAdd(s.qmpMonitorCh.ctx, tap.Name, tap.ID, endpoint.HardwareAddr(), fmt.Sprintf("%d", slot), "", "", 0, false); err != nil {
+			s.getDevSlot(endpoint.Name(), true)
+			return err
+		}
+	case removeDevice:
+		if err := s.qmpMonitorCh.qmp.ExecuteDeviceDel(s.qmpMonitorCh.ctx, tap.ID); err != nil {
+			return err
+		}
+		if err := s.qmpMonitorCh.qmp.ExecuteNetdevDel(s.qmpMonitorCh.ctx, tap.ID); err != nil {
+			return err
+		}
+	default:
+		return fmt.Errorf("Operation is not supported")
+	}
+
+	return nil
+}
+
+func (s *stratovirt) hotplugBlk(drive *config.BlockDrive, op operation) (err error) {
+	err = s.qmpSetup()
+	if err != nil {
+		return err
+	}
+
+	switch op {
+	case addDevice:
+		driver := "virtio-blk-pci"
+		slot, err := s.getDevSlot(drive.VirtPath, false)
+		if err != nil {
+			return fmt.Errorf("Could not get unused slot for %q", drive.VirtPath)
+		}
+
+		if err := s.qmpMonitorCh.qmp.ExecuteBlockdevAdd(s.qmpMonitorCh.ctx, drive.File, drive.ID, false); err != nil {
+			s.getDevSlot(drive.VirtPath, true)
+			return err
+		}
+
+		if err := s.qmpMonitorCh.qmp.ExecutePCIDeviceAdd(s.qmpMonitorCh.ctx, drive.ID, drive.ID, driver, fmt.Sprintf("%d", slot), "", "", 0, true, false); err != nil {
+			s.getDevSlot(drive.VirtPath, true)
+			return err
+		}
+	case removeDevice:
+		if err := s.qmpMonitorCh.qmp.ExecuteDeviceDel(s.qmpMonitorCh.ctx, drive.ID); err != nil {
+			return err
+		}
+		if err := s.qmpMonitorCh.qmp.ExecuteBlockdevDel(s.qmpMonitorCh.ctx, drive.ID); err != nil {
+			return err
+		}
+
+		s.getDevSlot(drive.VirtPath, true)
+	default:
+		return fmt.Errorf("Operation is not supported")
+	}
+
+	return nil
+}
+
+func (s *stratovirt) hotplugAddDevice(ctx context.Context, devInfo interface{}, devType deviceType) (interface{}, error) {
+	span, _ := s.trace(ctx, "hotplugAddDevice")
+	defer span.End()
+
+	switch devType {
+	case netDev:
+		return nil, s.hotplugNet(devInfo.(Endpoint), addDevice)
+	case blockDev:
+		return nil, s.hotplugBlk(devInfo.(*config.BlockDrive), addDevice)
+	default:
+		return nil, fmt.Errorf("Hotplug add device failed: unsupported device type '%v'", devType)
+	}
+}
+
+func (s *stratovirt) hotplugRemoveDevice(ctx context.Context, devInfo interface{}, devType deviceType) (interface{}, error) {
+	span, _ := s.trace(ctx, "hotplugRemoveDevice")
+	defer span.End()
+
+	switch devType {
+	case netDev:
+		return nil, s.hotplugNet(devInfo.(Endpoint), removeDevice)
+	case blockDev:
+		return nil, s.hotplugBlk(devInfo.(*config.BlockDrive), removeDevice)
+	default:
+		return nil, fmt.Errorf("Hotplug remove device: unsupported device type '%v'", devType)
+	}
+}
+
+func (s *stratovirt) resizeMemory(ctx context.Context, reqMemMB uint32, memoryBlockSizeMB uint32, probe bool) (uint32, memoryDevice, error) {
+	return 0, memoryDevice{}, nil
+}
+
+func (s *stratovirt) resizeVCPUs(ctx context.Context, reqVCPUs uint32) (currentVCPUs uint32, newVCPUs uint32, err error) {
+	return 0, 0, nil
+}
+
+func (s *stratovirt) getSandboxConsole(ctx context.Context, id string) (string, string, error) {
+	span, _ := s.trace(ctx, "getSandboxConsole")
+	defer span.End()
+
+	var consolePath string
+	if s.config.Debug {
+		consolePath = s.consolePath
+	} else {
+		consolePath = ""
+	}
+	consoleURL, err := utils.BuildSocketPath(consolePath)
+	if err != nil {
+		return consoleProtoUnix, "", err
+	}
+	return consoleProtoUnix, consoleURL, nil
+
+}
+
+func (s *stratovirt) disconnect(ctx context.Context) {
+	span, _ := s.trace(ctx, "disconnect")
+	defer span.End()
+
+	s.qmpTeardown()
+}
+
+func (s *stratovirt) capabilities(ctx context.Context) types.Capabilities {
+	span, _ := s.trace(ctx, "capabilities")
+	defer span.End()
+
+	var caps types.Capabilities
+	caps.SetBlockDeviceHotplugSupport()
+
+	return caps
+}
+
+func (s *stratovirt) qmpTeardown() {
+	if s.qmpMonitorCh.qmp != nil {
+		s.qmpMonitorCh.qmp.Shutdown()
+		<-s.qmpMonitorCh.disconn
+		s.qmpMonitorCh.qmp = nil
+		s.qmpMonitorCh.disconn = nil
+	}
+}
+
+func (s *stratovirt) qmpSetup() error {
+	s.qmpTeardown()
+
+	cfg := govmmQemu.QMPConfig{Logger: newQMPLogger()}
+
+	// Auto-closed by QMPStart().
+	disconnectCh := make(chan struct{})
+
+	qmp, _, err := govmmQemu.QMPStart(s.qmpMonitorCh.ctx, s.qmpMonitorCh.path, cfg, disconnectCh)
+	if err != nil {
+		s.Logger().WithError(err).Error("Failed to connect to StratoVirt instance")
+		return err
+	}
+
+	s.qmpMonitorCh.qmp = qmp
+	s.qmpMonitorCh.disconn = disconnectCh
+
+	return nil
+}
+
+func (s *stratovirt) getThreadIDs(ctx context.Context) (vcpuThreadIDs, error) {
+	span, _ := s.trace(ctx, "getThreadIDs")
+	defer span.End()
+
+	tid := vcpuThreadIDs{}
+	if err := s.qmpSetup(); err != nil {
+		return tid, err
+	}
+
+	cpuInfos, err := s.qmpMonitorCh.qmp.ExecQueryCpus(s.qmpMonitorCh.ctx)
+	if err != nil {
+		s.Logger().WithError(err).Error("Failed to query cpu infos")
+		return tid, err
+	}
+
+	tid.vcpus = make(map[int]int, len(cpuInfos))
+	for _, i := range cpuInfos {
+		if i.ThreadID > 0 {
+			tid.vcpus[i.CPU] = i.ThreadID
+		}
+	}
+	return tid, nil
+}
+
+func (s *stratovirt) cleanup(ctx context.Context) error {
+	span, _ := s.trace(ctx, "cleanup")
+	defer span.End()
+
+	s.qmpTeardown()
+
+	return nil
+}
+
+func (s *stratovirt) getPids() []int {
+	return []int{s.pid}
+}
+
+func (s *stratovirt) getVirtioFsPid() *int {
+	return nil
+}
+
+func (s *stratovirt) fromGrpc(ctx context.Context, hypervisorConfig *HypervisorConfig, j []byte) error {
+	return errors.New("stratovirt is not supported by VM cache")
+}
+
+func (s *stratovirt) toGrpc(ctx context.Context) ([]byte, error) {
+	return nil, errors.New("stratovirt is not supported by VM cache")
+}
+
+func (s *stratovirt) check() error {
+	if err := syscall.Kill(s.pid, syscall.Signal(0)); err != nil {
+		return errors.Wrapf(err, "Failed to ping StratoVirt process")
+	}
+
+	return nil
+}
+
+func (s *stratovirt) generateSocket(id string) (interface{}, error) {
+	return generateVMSocket(id, s.store.RunVMStoragePath())
+}
+
+func (s *stratovirt) isRateLimiterBuiltin() bool {
+	return true
+}
+
+func (s *stratovirt) save() (p persistapi.HypervisorState) {
+	pids := s.getPids()
+	p.Pid = pids[0]
+	p.Type = string(StratovirtHypervisor)
+	return
+}
+
+func (s *stratovirt) load(p persistapi.HypervisorState) {
+	s.pid = p.Pid
+
+	return
+}
+
+func (s *stratovirt) setSandbox(sandbox *Sandbox) {
+	s.sandbox = sandbox
+	return
+}
-- 
2.21.1 (Apple Git-122.3)

