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 }