Files
cocos/manager/qemu/vm.go
T
Sammy Kerata Oina 8eb1fac9ad NOISSUE - Refactor and update dependencies in the project (#491)
* Refactor and update dependencies in the project

- Updated go.sum to replace `github.com/absmach/magistrala` with `github.com/absmach/supermq` across various modules.
- Removed VSock configuration from environment variables and QEMU arguments.
- Updated QEMU configuration and related tests to remove references to guest CID and VSock.
- Added new HTTP transport layer for API endpoints in the manager.
- Introduced Prometheus monitoring configuration with alert rules and Alertmanager setup.
- Updated service and VM interfaces to remove unused methods and references.
- Refactored tests to align with the new structure and dependencies.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Add MaxVMs configuration and enforce limit on VM creation

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Add comprehensive tests for HTTP transport handlers and endpoints

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Add test case for exceeding maximum number of VMs in TestRun

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Improve error handling in TestHandlerWithCustomRouter to ensure response writing is checked

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Update dependencies to latest versions

- Upgrade cel.dev/expr from v0.23.0 to v0.24.0
- Upgrade github.com/absmach/supermq from v0.16.0 to v0.17.0
- Upgrade github.com/cenkalti/backoff from v4.3.0 to v5.0.2
- Upgrade github.com/cncf/xds/go to v0.0.0-20250501225837-2ac532fd4443
- Upgrade github.com/go-chi/chi/v5 from v5.2.1 to v5.2.2
- Upgrade github.com/go-jose/go-jose/v3 from v3.0.3 to v3.0.4
- Upgrade github.com/gofrs/uuid/v5 from v5.3.0 to v5.3.2
- Upgrade github.com/prometheus/client_golang from v1.22.0 to v1.23.0
- Upgrade github.com/prometheus/client_model from v0.6.1 to v0.6.2
- Upgrade github.com/prometheus/common from v0.62.0 to v0.65.0
- Upgrade github.com/prometheus/procfs from v0.15.1 to v0.16.1
- Upgrade go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp from v0.60.0 to v0.62.0
- Upgrade go.opentelemetry.io/otel/exporters/otlp/otlptrace from v1.36.0 to v1.37.0
- Upgrade golang.org/x/crypto from v0.39.0 to v0.40.0
- Upgrade golang.org/x/sys from v0.33.0 to v0.34.0
- Upgrade golang.org/x/text from v0.26.0 to v0.27.0
- Upgrade golang.org/x/time from v0.11.0 to v0.12.0
- Upgrade google.golang.org/grpc from v1.73.0 to v1.74.2

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

---------

Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2025-08-05 11:22:02 +02:00

234 lines
5.1 KiB
Go

// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package qemu
import (
"fmt"
"log/slog"
"os"
"os/exec"
"strings"
"syscall"
"time"
"github.com/gofrs/uuid"
"github.com/ultravioletrs/cocos/internal"
"github.com/ultravioletrs/cocos/manager/vm"
"github.com/ultravioletrs/cocos/pkg/manager"
)
const (
firmwareVars = "OVMF_VARS"
KernelFile = "bzImage"
rootfsFile = "rootfs.cpio"
tmpDir = "/tmp"
interval = 5 * time.Second
shutdownTimeout = 30 * time.Second
)
type VMInfo struct {
Config Config
LaunchTCB uint64 `env:"LAUNCH_TCB" envDefault:"0"`
}
type qemuVM struct {
vmi VMInfo
cmd *exec.Cmd
cvmId string
logger *slog.Logger
vm.StateMachine
}
func NewVM(config interface{}, cvmId string, logger *slog.Logger) vm.VM {
return &qemuVM{
vmi: config.(VMInfo),
cvmId: cvmId,
StateMachine: vm.NewStateMachine(),
logger: logger,
}
}
func (v *qemuVM) Start() (err error) {
defer func() {
if err == nil {
go v.checkVMProcessPeriodically()
}
}()
// Create unique qemu device identifiers
id, err := uuid.NewV4()
if err != nil {
return err
}
v.vmi.Config.NetDevConfig.ID = fmt.Sprintf("%s-%s", v.vmi.Config.NetDevConfig.ID, id)
v.vmi.Config.SEVSNPConfig.ID = fmt.Sprintf("%s-%s", v.vmi.Config.SEVSNPConfig.ID, id)
v.vmi.Config.TDXConfig.ID = fmt.Sprintf("%s-%s", v.vmi.Config.TDXConfig.ID, id)
if !v.vmi.Config.EnableSEVSNP && !v.vmi.Config.EnableTDX {
// Copy firmware vars file.
srcFile := v.vmi.Config.OVMFVarsConfig.File
dstFile := fmt.Sprintf("%s/%s-%s.fd", tmpDir, firmwareVars, id)
err = internal.CopyFile(srcFile, dstFile)
if err != nil {
return err
}
v.vmi.Config.OVMFVarsConfig.File = dstFile
}
exe, args, err := v.executableAndArgs()
if err != nil {
return err
}
v.cmd = exec.Command(exe, args...)
v.cmd.Stdout = &vm.Stdout{StateMachine: v.StateMachine, Logger: v.logger.With(slog.String("cvm", v.cvmId))}
v.cmd.Stderr = &vm.Stderr{StateMachine: v.StateMachine, Logger: v.logger.With(slog.String("cvm", v.cvmId))}
return v.cmd.Start()
}
func (v *qemuVM) Stop() error {
defer func() {
err := v.StateMachine.Transition(manager.StopComputationRun)
if err != nil {
return
}
}()
err := v.cmd.Process.Signal(syscall.SIGTERM)
if err != nil {
return fmt.Errorf("failed to send SIGTERM: %v", err)
}
if v.vmi.Config.CertsMount != "" {
if err := os.RemoveAll(v.vmi.Config.CertsMount); err != nil {
return fmt.Errorf("failed to remove certs mount: %v", err)
}
}
if v.vmi.Config.EnvMount != "" {
if err := os.RemoveAll(v.vmi.Config.EnvMount); err != nil {
return fmt.Errorf("failed to remove env mount: %v", err)
}
}
done := make(chan error, 1)
go func() {
_, err := v.cmd.Process.Wait()
done <- err
}()
select {
case err := <-done:
return err
case <-time.After(shutdownTimeout):
err := v.cmd.Process.Kill()
if err != nil {
return fmt.Errorf("failed to kill process: %v", err)
}
}
return nil
}
func (v *qemuVM) SetProcess(pid int) error {
process, err := os.FindProcess(pid)
if err != nil {
return err
}
exe, args, err := v.executableAndArgs()
if err != nil {
return err
}
v.cmd = exec.Command(exe, args...)
v.cmd.Process = process
return nil
}
func (v *qemuVM) GetProcess() int {
return v.cmd.Process.Pid
}
func (v *qemuVM) executableAndArgs() (string, []string, error) {
exe, err := exec.LookPath(v.vmi.Config.QemuBinPath)
if err != nil {
return "", nil, err
}
args := v.vmi.Config.ConstructQemuArgs()
if v.vmi.Config.UseSudo {
args = append([]string{exe}, args...)
exe = "sudo"
}
return exe, args, nil
}
func (v *qemuVM) checkVMProcessPeriodically() {
for {
if !processExists(v.GetProcess()) {
break
}
time.Sleep(interval)
}
}
func processExists(pid int) bool {
process, err := os.FindProcess(pid)
if err != nil {
return false
}
// On Unix systems, FindProcess always succeeds and returns a Process for the given pid, regardless of whether the process exists.
// To test whether the process actually exists, see whether p.Signal(syscall.Signal(0)) reports an error.
if err = process.Signal(syscall.Signal(0)); err == nil {
return true
}
if err == syscall.ESRCH {
return false
}
return false
}
func (v *qemuVM) GetConfig() interface{} {
return v.vmi
}
func SEVSNPEnabled(cpuinfo, kernelParam string) bool {
return strings.Contains(cpuinfo, "sev_snp") && strings.TrimSpace(kernelParam) == "Y"
}
func TDXEnabled(cpuinfo, kernelParam string) bool {
return strings.Contains(cpuinfo, "tdx_host_platform") && strings.TrimSpace(kernelParam) == "Y"
}
func SEVSNPEnabledOnHost() bool {
cpuinfo, err := os.ReadFile("/proc/cpuinfo")
if err != nil {
return false
}
kernelParam, err := os.ReadFile("/sys/module/kvm_amd/parameters/sev_snp")
if err != nil {
return false
}
return SEVSNPEnabled(string(cpuinfo), string(kernelParam))
}
func TDXEnabledOnHost() bool {
cpuinfo, err := os.ReadFile("/proc/cpuinfo")
if err != nil {
return false
}
kernelParam, err := os.ReadFile("/sys/module/kvm_intel/parameters/tdx")
if err != nil {
return false
}
return TDXEnabled(string(cpuinfo), string(kernelParam))
}