From 8dda2dd7a55e04ffa17a9038de0d975e20d111db Mon Sep 17 00:00:00 2001 From: Peng Tao Date: Fri, 13 Jul 2018 16:47:45 +0800 Subject: [PATCH] virtcontainers: add a vm abstraction layer As representation of a guest without actual sandbox attached to it. This prepares for vm factory support. Signed-off-by: Peng Tao --- virtcontainers/vm.go | 227 ++++++++++++++++++++++++++++++++++++++ virtcontainers/vm_test.go | 83 ++++++++++++++ 2 files changed, 310 insertions(+) create mode 100644 virtcontainers/vm.go create mode 100644 virtcontainers/vm_test.go diff --git a/virtcontainers/vm.go b/virtcontainers/vm.go new file mode 100644 index 0000000000..09c07d9f1d --- /dev/null +++ b/virtcontainers/vm.go @@ -0,0 +1,227 @@ +// Copyright (c) 2018 HyperHQ Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// + +package virtcontainers + +import ( + "os" + "path/filepath" + + "github.com/kata-containers/runtime/virtcontainers/pkg/uuid" + "github.com/sirupsen/logrus" +) + +// VM is abstraction of a virtual machine. +type VM struct { + id string + + hypervisor hypervisor + agent agent + + cpu uint32 + memory uint32 + + cpuDelta uint32 +} + +// VMConfig is a collection of all info that a new blackbox VM needs. +type VMConfig struct { + HypervisorType HypervisorType + HypervisorConfig HypervisorConfig + + AgentType AgentType + AgentConfig interface{} +} + +// Valid check VMConfig validity. +func (c *VMConfig) Valid() error { + return c.HypervisorConfig.valid() +} + +// NewVM creates a new VM based on provided VMConfig. +func NewVM(config VMConfig) (*VM, error) { + hypervisor, err := newHypervisor(config.HypervisorType) + if err != nil { + return nil, err + } + + if err = config.Valid(); err != nil { + return nil, err + } + + id := uuid.Generate().String() + + virtLog.WithField("vm id", id).WithField("config", config).Info("create new vm") + + defer func() { + if err != nil { + virtLog.WithField("vm id", id).WithError(err).Error("failed to create new vm") + } + }() + + if err = hypervisor.init(id, &config.HypervisorConfig, Resources{}, &filesystem{}); err != nil { + return nil, err + } + + if err = hypervisor.createSandbox(); err != nil { + return nil, err + } + + agent := newAgent(config.AgentType) + agentConfig := newAgentConfig(config.AgentType, config.AgentConfig) + // do not keep connection for temp agent + if c, ok := agentConfig.(KataAgentConfig); ok { + c.LongLiveConn = false + } + vmSharePath := buildVMSharePath(id) + err = agent.configure(hypervisor, id, vmSharePath, true, agentConfig) + if err != nil { + return nil, err + } + + if err = hypervisor.startSandbox(); err != nil { + return nil, err + } + + defer func() { + if err != nil { + virtLog.WithField("vm id", id).WithError(err).Info("clean up vm") + hypervisor.stopSandbox() + } + }() + + // VMs booted from template are paused, do not check + if !config.HypervisorConfig.BootFromTemplate { + err = hypervisor.waitSandbox(vmStartTimeout) + if err != nil { + return nil, err + } + + virtLog.WithField("vm id", id).Info("check agent status") + err = agent.check() + if err != nil { + return nil, err + } + } + + return &VM{ + id: id, + hypervisor: hypervisor, + agent: agent, + cpu: config.HypervisorConfig.DefaultVCPUs, + memory: config.HypervisorConfig.DefaultMemSz, + }, nil +} + +func buildVMSharePath(id string) string { + return filepath.Join(RunVMStoragePath, id, "shared") +} + +func (v *VM) logger() logrus.FieldLogger { + return virtLog.WithField("vm id", v.id) +} + +// Pause pauses a VM. +func (v *VM) Pause() error { + v.logger().Info("pause vm") + return v.hypervisor.pauseSandbox() +} + +// Save saves a VM to persistent disk. +func (v *VM) Save() error { + v.logger().Info("save vm") + return v.hypervisor.saveSandbox() +} + +// Resume resumes a paused VM. +func (v *VM) Resume() error { + v.logger().Info("resume vm") + return v.hypervisor.resumeSandbox() +} + +// Start kicks off a configured VM. +func (v *VM) Start() error { + v.logger().Info("start vm") + return v.hypervisor.startSandbox() +} + +// Stop stops a VM process. +func (v *VM) Stop() error { + v.logger().Info("kill vm") + return v.hypervisor.stopSandbox() +} + +// AddCPUs adds num of CPUs to the VM. +func (v *VM) AddCPUs(num uint32) error { + if num > 0 { + v.logger().Infof("hot adding %d vCPUs", num) + if _, err := v.hypervisor.hotplugAddDevice(num, cpuDev); err != nil { + return err + } + v.cpuDelta += num + v.cpu += num + } + + return nil +} + +// AddMemory adds numMB of memory to the VM. +func (v *VM) AddMemory(numMB uint32) error { + if numMB > 0 { + v.logger().Infof("hot adding %d MB memory", numMB) + dev := &memoryDevice{1, int(numMB)} + if _, err := v.hypervisor.hotplugAddDevice(dev, memoryDev); err != nil { + return err + } + } + + return nil +} + +// OnlineCPUMemory puts the hotplugged CPU and memory online. +func (v *VM) OnlineCPUMemory() error { + v.logger().Infof("online CPU %d and memory", v.cpuDelta) + err := v.agent.onlineCPUMem(v.cpuDelta) + if err == nil { + v.cpuDelta = 0 + } + + return err +} + +func (v *VM) assignSandbox(s *Sandbox) error { + // add vm symlinks + // - link vm socket from sandbox dir (/run/vc/vm/sbid/) to vm dir (/run/vc/vm/vmid/) + // - link 9pfs share path from sandbox dir (/run/kata-containers/shared/sandboxes/sbid/) to vm dir (/run/vc/vm/vmid/shared/) + + vmSharePath := buildVMSharePath(v.id) + vmSockDir := v.agent.getVMPath(v.id) + sbSharePath := s.agent.getSharePath(s.id) + sbSockDir := s.agent.getVMPath(s.id) + + v.logger().WithFields(logrus.Fields{ + "vmSharePath": vmSharePath, + "vmSockDir": vmSockDir, + "sbSharePath": sbSharePath, + "sbSockDir": sbSockDir, + }).Infof("assign vm to sandbox %s", s.id) + + // First make sure the symlinks do not exist + os.RemoveAll(sbSharePath) + os.RemoveAll(sbSockDir) + + if err := os.Symlink(vmSharePath, sbSharePath); err != nil { + return err + } + + if err := os.Symlink(vmSockDir, sbSockDir); err != nil { + os.Remove(sbSharePath) + return err + } + + s.hypervisor = v.hypervisor + + return nil +} diff --git a/virtcontainers/vm_test.go b/virtcontainers/vm_test.go new file mode 100644 index 0000000000..3031fc023d --- /dev/null +++ b/virtcontainers/vm_test.go @@ -0,0 +1,83 @@ +// Copyright (c) 2018 HyperHQ Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// + +package virtcontainers + +import ( + "io/ioutil" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewVM(t *testing.T) { + assert := assert.New(t) + + testDir, _ := ioutil.TempDir("", "vmfactory-tmp-") + config := VMConfig{ + HypervisorType: MockHypervisor, + AgentType: NoopAgentType, + } + hyperConfig := HypervisorConfig{ + KernelPath: testDir, + ImagePath: testDir, + } + + var vm *VM + _, err := NewVM(config) + assert.Error(err) + + config.HypervisorConfig = hyperConfig + vm, err = NewVM(config) + assert.Nil(err) + + // VM operations + err = vm.Pause() + assert.Nil(err) + err = vm.Resume() + assert.Nil(err) + err = vm.Start() + assert.Nil(err) + err = vm.Save() + assert.Nil(err) + err = vm.Stop() + assert.Nil(err) + err = vm.AddCPUs(2) + assert.Nil(err) + err = vm.AddMemory(128) + assert.Nil(err) + err = vm.OnlineCPUMemory() + assert.Nil(err) + + // template VM + config.HypervisorConfig.BootFromTemplate = true + _, err = NewVM(config) + assert.Error(err) + + config.HypervisorConfig.MemoryPath = testDir + _, err = NewVM(config) + assert.Error(err) + + config.HypervisorConfig.DevicesStatePath = testDir + _, err = NewVM(config) + assert.Nil(err) +} + +func TestVMConfigValid(t *testing.T) { + assert := assert.New(t) + + config := VMConfig{} + + err := config.Valid() + assert.Error(err) + + testDir, _ := ioutil.TempDir("", "vmfactory-tmp-") + config.HypervisorConfig = HypervisorConfig{ + KernelPath: testDir, + InitrdPath: testDir, + } + err = config.Valid() + assert.Nil(err) +}