github.com/kata-containers/runtime@v0.0.0-20210505125100-04f29832a923/virtcontainers/vm.go (about)

     1  // Copyright (c) 2018 HyperHQ Inc.
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  //
     5  
     6  package virtcontainers
     7  
     8  import (
     9  	"context"
    10  	"encoding/json"
    11  	"fmt"
    12  	"os"
    13  	"path/filepath"
    14  	"time"
    15  
    16  	pb "github.com/kata-containers/runtime/protocols/cache"
    17  	"github.com/kata-containers/runtime/virtcontainers/persist"
    18  	persistapi "github.com/kata-containers/runtime/virtcontainers/persist/api"
    19  	"github.com/kata-containers/runtime/virtcontainers/pkg/uuid"
    20  	"github.com/sirupsen/logrus"
    21  )
    22  
    23  // VM is abstraction of a virtual machine.
    24  type VM struct {
    25  	id string
    26  
    27  	hypervisor hypervisor
    28  	agent      agent
    29  
    30  	proxy    proxy
    31  	proxyPid int
    32  	proxyURL string
    33  
    34  	cpu    uint32
    35  	memory uint32
    36  
    37  	cpuDelta uint32
    38  
    39  	store persistapi.PersistDriver
    40  }
    41  
    42  // VMConfig is a collection of all info that a new blackbox VM needs.
    43  type VMConfig struct {
    44  	HypervisorType   HypervisorType
    45  	HypervisorConfig HypervisorConfig
    46  
    47  	AgentType   AgentType
    48  	AgentConfig interface{}
    49  
    50  	ProxyType   ProxyType
    51  	ProxyConfig ProxyConfig
    52  }
    53  
    54  // Valid check VMConfig validity.
    55  func (c *VMConfig) Valid() error {
    56  	return c.HypervisorConfig.valid()
    57  }
    58  
    59  // ToGrpc convert VMConfig struct to grpc format pb.GrpcVMConfig.
    60  func (c *VMConfig) ToGrpc() (*pb.GrpcVMConfig, error) {
    61  	data, err := json.Marshal(&c)
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  
    66  	aconf, ok := c.AgentConfig.(KataAgentConfig)
    67  	if !ok {
    68  		return nil, fmt.Errorf("agent type is not supported by VM cache")
    69  	}
    70  
    71  	agentConfig, err := json.Marshal(&aconf)
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  
    76  	return &pb.GrpcVMConfig{
    77  		Data:        data,
    78  		AgentConfig: agentConfig,
    79  	}, nil
    80  }
    81  
    82  // GrpcToVMConfig convert grpc format pb.GrpcVMConfig to VMConfig struct.
    83  func GrpcToVMConfig(j *pb.GrpcVMConfig) (*VMConfig, error) {
    84  	var config VMConfig
    85  	err := json.Unmarshal(j.Data, &config)
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  
    90  	if config.AgentType != KataContainersAgent {
    91  		return nil, fmt.Errorf("agent type %s is not supported by VM cache", config.AgentType)
    92  	}
    93  
    94  	var kataConfig KataAgentConfig
    95  	err = json.Unmarshal(j.AgentConfig, &kataConfig)
    96  	if err == nil {
    97  		config.AgentConfig = kataConfig
    98  	}
    99  
   100  	return &config, nil
   101  }
   102  
   103  func setupProxy(h hypervisor, agent agent, config VMConfig, id string) (int, string, proxy, error) {
   104  	consoleURL, err := h.getSandboxConsole(id)
   105  	if err != nil {
   106  		return -1, "", nil, err
   107  	}
   108  	agentURL, err := agent.getAgentURL()
   109  	if err != nil {
   110  		return -1, "", nil, err
   111  	}
   112  
   113  	proxy, err := newProxy(config.ProxyType)
   114  	if err != nil {
   115  		return -1, "", nil, err
   116  	}
   117  
   118  	proxyParams := proxyParams{
   119  		id:         id,
   120  		path:       config.ProxyConfig.Path,
   121  		agentURL:   agentURL,
   122  		consoleURL: consoleURL,
   123  		logger:     virtLog.WithField("vm", id),
   124  		debug:      config.ProxyConfig.Debug,
   125  	}
   126  	pid, url, err := proxy.start(proxyParams)
   127  	if err != nil {
   128  		virtLog.WithFields(logrus.Fields{
   129  			"vm":         id,
   130  			"proxy type": config.ProxyType,
   131  			"params":     proxyParams,
   132  		}).WithError(err).Error("failed to start proxy")
   133  		return -1, "", nil, err
   134  	}
   135  
   136  	return pid, url, proxy, nil
   137  }
   138  
   139  // NewVM creates a new VM based on provided VMConfig.
   140  func NewVM(ctx context.Context, config VMConfig) (*VM, error) {
   141  	var (
   142  		proxy proxy
   143  		pid   int
   144  		url   string
   145  	)
   146  
   147  	// 1. setup hypervisor
   148  	hypervisor, err := newHypervisor(config.HypervisorType)
   149  	if err != nil {
   150  		return nil, err
   151  	}
   152  
   153  	if err = config.Valid(); err != nil {
   154  		return nil, err
   155  	}
   156  
   157  	id := uuid.Generate().String()
   158  
   159  	virtLog.WithField("vm", id).WithField("config", config).Info("create new vm")
   160  
   161  	store, err := persist.GetDriver()
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  
   166  	defer func() {
   167  		if err != nil {
   168  			virtLog.WithField("vm", id).WithError(err).Error("failed to create new vm")
   169  			virtLog.WithField("vm", id).Errorf("Deleting store for %s", id)
   170  			store.Destroy(id)
   171  		}
   172  	}()
   173  
   174  	if err = hypervisor.createSandbox(ctx, id, NetworkNamespace{}, &config.HypervisorConfig, false); err != nil {
   175  		return nil, err
   176  	}
   177  
   178  	// 2. setup agent
   179  	agent := newAgent(config.AgentType)
   180  	vmSharePath := buildVMSharePath(id, store.RunVMStoragePath())
   181  	err = agent.configure(hypervisor, id, vmSharePath, isProxyBuiltIn(config.ProxyType), config.AgentConfig)
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  
   186  	// 3. boot up guest vm
   187  	if err = hypervisor.startSandbox(vmStartTimeout); err != nil {
   188  		return nil, err
   189  	}
   190  
   191  	defer func() {
   192  		if err != nil {
   193  			virtLog.WithField("vm", id).WithError(err).Info("clean up vm")
   194  			hypervisor.stopSandbox()
   195  		}
   196  	}()
   197  
   198  	// 4. setup proxy
   199  	pid, url, proxy, err = setupProxy(hypervisor, agent, config, id)
   200  	if err != nil {
   201  		return nil, err
   202  	}
   203  	defer func() {
   204  		if err != nil {
   205  			virtLog.WithField("vm", id).WithError(err).Info("clean up proxy")
   206  			proxy.stop(pid)
   207  		}
   208  	}()
   209  	if err = agent.setProxy(nil, proxy, pid, url); err != nil {
   210  		return nil, err
   211  	}
   212  
   213  	// 5. check agent aliveness
   214  	// VMs booted from template are paused, do not check
   215  	if !config.HypervisorConfig.BootFromTemplate {
   216  		virtLog.WithField("vm", id).Info("check agent status")
   217  		err = agent.check()
   218  		if err != nil {
   219  			return nil, err
   220  		}
   221  	}
   222  
   223  	return &VM{
   224  		id:         id,
   225  		hypervisor: hypervisor,
   226  		agent:      agent,
   227  		proxy:      proxy,
   228  		proxyPid:   pid,
   229  		proxyURL:   url,
   230  		cpu:        config.HypervisorConfig.NumVCPUs,
   231  		memory:     config.HypervisorConfig.MemorySize,
   232  		store:      store,
   233  	}, nil
   234  }
   235  
   236  // NewVMFromGrpc creates a new VM based on provided pb.GrpcVM and VMConfig.
   237  func NewVMFromGrpc(ctx context.Context, v *pb.GrpcVM, config VMConfig) (*VM, error) {
   238  	virtLog.WithField("GrpcVM", v).WithField("config", config).Info("create new vm from Grpc")
   239  
   240  	hypervisor, err := newHypervisor(config.HypervisorType)
   241  	if err != nil {
   242  		return nil, err
   243  	}
   244  
   245  	store, err := persist.GetDriver()
   246  	if err != nil {
   247  		return nil, err
   248  	}
   249  
   250  	defer func() {
   251  		if err != nil {
   252  			virtLog.WithField("vm", v.Id).WithError(err).Error("failed to create new vm from Grpc")
   253  			virtLog.WithField("vm", v.Id).Errorf("Deleting store for %s", v.Id)
   254  			store.Destroy(v.Id)
   255  		}
   256  	}()
   257  
   258  	err = hypervisor.fromGrpc(ctx, &config.HypervisorConfig, v.Hypervisor)
   259  	if err != nil {
   260  		return nil, err
   261  	}
   262  
   263  	agent := newAgent(config.AgentType)
   264  	agent.configureFromGrpc(hypervisor, v.Id, isProxyBuiltIn(config.ProxyType), config.AgentConfig)
   265  
   266  	proxy, err := newProxy(config.ProxyType)
   267  	if err != nil {
   268  		return nil, err
   269  	}
   270  	agent.setProxyFromGrpc(proxy, int(v.ProxyPid), v.ProxyURL)
   271  
   272  	return &VM{
   273  		id:         v.Id,
   274  		hypervisor: hypervisor,
   275  		agent:      agent,
   276  		proxy:      proxy,
   277  		proxyPid:   int(v.ProxyPid),
   278  		proxyURL:   v.ProxyURL,
   279  		cpu:        v.Cpu,
   280  		memory:     v.Memory,
   281  		cpuDelta:   v.CpuDelta,
   282  		store:      store,
   283  	}, nil
   284  }
   285  
   286  func buildVMSharePath(id string, vmStoragePath string) string {
   287  	return filepath.Join(vmStoragePath, id, "shared")
   288  }
   289  
   290  func (v *VM) logger() logrus.FieldLogger {
   291  	return virtLog.WithField("vm", v.id)
   292  }
   293  
   294  // Pause pauses a VM.
   295  func (v *VM) Pause() error {
   296  	v.logger().Info("pause vm")
   297  	return v.hypervisor.pauseSandbox()
   298  }
   299  
   300  // Save saves a VM to persistent disk.
   301  func (v *VM) Save() error {
   302  	v.logger().Info("save vm")
   303  	return v.hypervisor.saveSandbox()
   304  }
   305  
   306  // Resume resumes a paused VM.
   307  func (v *VM) Resume() error {
   308  	v.logger().Info("resume vm")
   309  	return v.hypervisor.resumeSandbox()
   310  }
   311  
   312  // Start kicks off a configured VM.
   313  func (v *VM) Start() error {
   314  	v.logger().Info("start vm")
   315  	return v.hypervisor.startSandbox(vmStartTimeout)
   316  }
   317  
   318  // Disconnect agent and proxy connections to a VM
   319  func (v *VM) Disconnect() error {
   320  	v.logger().Info("kill vm")
   321  
   322  	if err := v.agent.disconnect(); err != nil {
   323  		v.logger().WithError(err).Error("failed to disconnect agent")
   324  	}
   325  	if err := v.proxy.stop(v.proxyPid); err != nil {
   326  		v.logger().WithError(err).Error("failed to stop proxy")
   327  	}
   328  
   329  	return nil
   330  }
   331  
   332  // Stop stops a VM process.
   333  func (v *VM) Stop() error {
   334  	v.logger().Info("stop vm")
   335  
   336  	if err := v.hypervisor.stopSandbox(); err != nil {
   337  		return err
   338  	}
   339  
   340  	return v.store.Destroy(v.id)
   341  }
   342  
   343  // AddCPUs adds num of CPUs to the VM.
   344  func (v *VM) AddCPUs(num uint32) error {
   345  	if num > 0 {
   346  		v.logger().Infof("hot adding %d vCPUs", num)
   347  		if _, err := v.hypervisor.hotplugAddDevice(num, cpuDev); err != nil {
   348  			return err
   349  		}
   350  		v.cpuDelta += num
   351  		v.cpu += num
   352  	}
   353  
   354  	return nil
   355  }
   356  
   357  // AddMemory adds numMB of memory to the VM.
   358  func (v *VM) AddMemory(numMB uint32) error {
   359  	if numMB > 0 {
   360  		v.logger().Infof("hot adding %d MB memory", numMB)
   361  		dev := &memoryDevice{1, int(numMB), 0, false}
   362  		if _, err := v.hypervisor.hotplugAddDevice(dev, memoryDev); err != nil {
   363  			return err
   364  		}
   365  	}
   366  
   367  	return nil
   368  }
   369  
   370  // OnlineCPUMemory puts the hotplugged CPU and memory online.
   371  func (v *VM) OnlineCPUMemory() error {
   372  	v.logger().Infof("online CPU %d and memory", v.cpuDelta)
   373  	err := v.agent.onlineCPUMem(v.cpuDelta, false)
   374  	if err == nil {
   375  		v.cpuDelta = 0
   376  	}
   377  
   378  	return err
   379  }
   380  
   381  // ReseedRNG adds random entropy to guest random number generator
   382  // and reseeds it.
   383  func (v *VM) ReseedRNG() error {
   384  	v.logger().Infof("reseed guest random number generator")
   385  	urandomDev := "/dev/urandom"
   386  	data := make([]byte, 512)
   387  	f, err := os.OpenFile(urandomDev, os.O_RDONLY, 0)
   388  	if err != nil {
   389  		v.logger().WithError(err).Warnf("fail to open %s", urandomDev)
   390  		return err
   391  	}
   392  	defer f.Close()
   393  	if _, err = f.Read(data); err != nil {
   394  		v.logger().WithError(err).Warnf("fail to read %s", urandomDev)
   395  		return err
   396  	}
   397  
   398  	return v.agent.reseedRNG(data)
   399  }
   400  
   401  // SyncTime syncs guest time with host time.
   402  func (v *VM) SyncTime() error {
   403  	now := time.Now()
   404  	v.logger().WithField("time", now).Infof("sync guest time")
   405  	return v.agent.setGuestDateTime(now)
   406  }
   407  
   408  func (v *VM) assignSandbox(s *Sandbox) error {
   409  	// add vm symlinks
   410  	// - link vm socket from sandbox dir (/run/vc/vm/sbid/<kata.sock>) to vm dir (/run/vc/vm/vmid/<kata.sock>)
   411  	// - link 9pfs share path from sandbox dir (/run/kata-containers/shared/sandboxes/sbid/) to vm dir (/run/vc/vm/vmid/shared/)
   412  
   413  	vmSharePath := buildVMSharePath(v.id, v.store.RunVMStoragePath())
   414  	vmSockDir := filepath.Join(v.store.RunVMStoragePath(), v.id)
   415  	sbSharePath := getMountPath(s.id)
   416  	sbSockDir := filepath.Join(v.store.RunVMStoragePath(), s.id)
   417  
   418  	v.logger().WithFields(logrus.Fields{
   419  		"vmSharePath": vmSharePath,
   420  		"vmSockDir":   vmSockDir,
   421  		"sbSharePath": sbSharePath,
   422  		"sbSockDir":   sbSockDir,
   423  		"proxy-pid":   v.proxyPid,
   424  		"proxy-url":   v.proxyURL,
   425  	}).Infof("assign vm to sandbox %s", s.id)
   426  
   427  	if err := s.agent.setProxy(s, v.proxy, v.proxyPid, v.proxyURL); err != nil {
   428  		return err
   429  	}
   430  
   431  	if err := s.agent.reuseAgent(v.agent); err != nil {
   432  		return err
   433  	}
   434  
   435  	// First make sure the symlinks do not exist
   436  	os.RemoveAll(sbSharePath)
   437  	os.RemoveAll(sbSockDir)
   438  
   439  	if err := os.Symlink(vmSharePath, sbSharePath); err != nil {
   440  		return err
   441  	}
   442  
   443  	if err := os.Symlink(vmSockDir, sbSockDir); err != nil {
   444  		os.Remove(sbSharePath)
   445  		return err
   446  	}
   447  
   448  	s.hypervisor = v.hypervisor
   449  	s.config.HypervisorConfig.VMid = v.id
   450  
   451  	return nil
   452  }
   453  
   454  // ToGrpc convert VM struct to Grpc format pb.GrpcVM.
   455  func (v *VM) ToGrpc(config VMConfig) (*pb.GrpcVM, error) {
   456  	hJSON, err := v.hypervisor.toGrpc()
   457  	if err != nil {
   458  		return nil, err
   459  	}
   460  
   461  	return &pb.GrpcVM{
   462  		Id:         v.id,
   463  		Hypervisor: hJSON,
   464  
   465  		ProxyPid: int64(v.proxyPid),
   466  		ProxyURL: v.proxyURL,
   467  
   468  		Cpu:      v.cpu,
   469  		Memory:   v.memory,
   470  		CpuDelta: v.cpuDelta,
   471  	}, nil
   472  }
   473  
   474  func (v *VM) GetVMStatus() *pb.GrpcVMStatus {
   475  	return &pb.GrpcVMStatus{
   476  		Pid:    int64(getHypervisorPid(v.hypervisor)),
   477  		Cpu:    v.cpu,
   478  		Memory: v.memory,
   479  	}
   480  }