podman/pkg/machine/hyperv/config.go

285 lines
6.8 KiB
Go

//go:build windows
// +build windows
package hyperv
import (
"encoding/json"
"errors"
"io/fs"
"os"
"path/filepath"
"time"
"github.com/containers/libhvee/pkg/hypervctl"
"github.com/containers/podman/v4/pkg/machine"
"github.com/docker/go-units"
"github.com/sirupsen/logrus"
)
type Virtualization struct {
artifact machine.Artifact
compression machine.ImageCompression
format machine.ImageFormat
}
func (v Virtualization) Artifact() machine.Artifact {
return machine.None
}
func (v Virtualization) CheckExclusiveActiveVM() (bool, string, error) {
vmm := hypervctl.NewVirtualMachineManager()
// Use of GetAll is OK here because we do not want to use the same name
// as something already *actually* configured in hyperv
vms, err := vmm.GetAll()
if err != nil {
return false, "", err
}
for _, vm := range vms {
if vm.IsStarting() || vm.State() == hypervctl.Enabled {
return true, vm.ElementName, nil
}
}
return false, "", nil
}
func (v Virtualization) Compression() machine.ImageCompression {
return v.compression
}
func (v Virtualization) Format() machine.ImageFormat {
return v.format
}
func (v Virtualization) IsValidVMName(name string) (bool, error) {
// We check both the local filesystem and hyperv for the valid name
mm := HyperVMachine{Name: name}
configDir, err := machine.GetConfDir(v.VMType())
if err != nil {
return false, err
}
if err := loadMacMachineFromJSON(configDir, &mm); err != nil {
return false, err
}
// The name is valid for the local filesystem
if _, err := hypervctl.NewVirtualMachineManager().GetMachine(name); err != nil {
return false, err
}
// The lookup in hyperv worked, so it is also valid there
return true, nil
}
func (v Virtualization) List(opts machine.ListOptions) ([]*machine.ListResponse, error) {
mms, err := v.loadFromLocalJson()
if err != nil {
return nil, err
}
var response []*machine.ListResponse
vmm := hypervctl.NewVirtualMachineManager()
for _, mm := range mms {
vm, err := vmm.GetMachine(mm.Name)
if err != nil {
return nil, err
}
mlr := machine.ListResponse{
Name: mm.Name,
CreatedAt: mm.Created,
LastUp: mm.LastUp,
Running: vm.State() == hypervctl.Enabled,
Starting: vm.IsStarting(),
Stream: mm.ImageStream,
VMType: machine.HyperVVirt.String(),
CPUs: mm.CPUs,
Memory: mm.Memory * units.MiB,
DiskSize: mm.DiskSize * units.GiB,
Port: mm.Port,
RemoteUsername: mm.RemoteUsername,
IdentityPath: mm.IdentityPath,
}
response = append(response, &mlr)
}
return response, err
}
func (v Virtualization) LoadVMByName(name string) (machine.VM, error) {
m := &HyperVMachine{Name: name}
return m.loadFromFile()
}
func (v Virtualization) NewMachine(opts machine.InitOptions) (machine.VM, error) {
m := HyperVMachine{Name: opts.Name}
if len(opts.ImagePath) < 1 {
return nil, errors.New("must define --image-path for hyperv support")
}
configDir, err := machine.GetConfDir(machine.HyperVVirt)
if err != nil {
return nil, err
}
configPath, err := machine.NewMachineFile(getVMConfigPath(configDir, opts.Name), nil)
if err != nil {
return nil, err
}
m.ConfigPath = *configPath
ignitionPath, err := machine.NewMachineFile(filepath.Join(configDir, m.Name)+".ign", nil)
if err != nil {
return nil, err
}
m.IgnitionFile = *ignitionPath
// Set creation time
m.Created = time.Now()
dataDir, err := machine.GetDataDir(machine.HyperVVirt)
if err != nil {
return nil, err
}
// Acquire the image
// Until we are producing vhdx images in fcos, all images must be fed to us
// with --image-path. We should, however, accept both a file or url
g, err := machine.NewGenericDownloader(machine.HyperVVirt, opts.Name, opts.ImagePath)
if err != nil {
return nil, err
}
imagePath, err := machine.NewMachineFile(g.Get().GetLocalUncompressedFile(dataDir), nil)
if err != nil {
return nil, err
}
m.ImagePath = *imagePath
if err := machine.DownloadImage(g); err != nil {
return nil, err
}
config := hypervctl.HardwareConfig{
CPUs: uint16(opts.CPUS),
DiskPath: imagePath.GetPath(),
DiskSize: opts.DiskSize,
Memory: int32(opts.Memory),
}
// Write the json configuration file which will be loaded by
// LoadByName
b, err := json.MarshalIndent(m, "", " ")
if err != nil {
return nil, err
}
if err := os.WriteFile(m.ConfigPath.GetPath(), b, 0644); err != nil {
return nil, err
}
vmm := hypervctl.NewVirtualMachineManager()
if err := vmm.NewVirtualMachine(opts.Name, &config); err != nil {
return nil, err
}
return v.LoadVMByName(opts.Name)
}
func (v Virtualization) RemoveAndCleanMachines() error {
// Error handling used here is following what qemu did
var (
prevErr error
)
// The next three info lookups must succeed or we return
mms, err := v.loadFromLocalJson()
if err != nil {
return err
}
configDir, err := machine.GetConfDir(vmtype)
if err != nil {
return err
}
dataDir, err := machine.GetDataDir(vmtype)
if err != nil {
return err
}
vmm := hypervctl.NewVirtualMachineManager()
for _, mm := range mms {
vm, err := vmm.GetMachine(mm.Name)
if err != nil {
prevErr = handlePrevError(err, prevErr)
}
// If the VM is not stopped, we need to stop it
// TODO stop might not be enough if the state is dorked. we may need
// something like forceoff hard switch
if vm.State() != hypervctl.Disabled {
if err := vm.Stop(); err != nil {
prevErr = handlePrevError(err, prevErr)
}
}
if err := vm.Remove(mm.ImagePath.GetPath()); err != nil {
prevErr = handlePrevError(err, prevErr)
}
if err := mm.ReadyHVSock.Remove(); err != nil {
prevErr = handlePrevError(err, prevErr)
}
if err := mm.NetworkHVSock.Remove(); err != nil {
prevErr = handlePrevError(err, prevErr)
}
}
// Nuke the config and dataDirs
if err := os.RemoveAll(configDir); err != nil {
prevErr = handlePrevError(err, prevErr)
}
if err := os.RemoveAll(dataDir); err != nil {
prevErr = handlePrevError(err, prevErr)
}
return prevErr
}
func (v Virtualization) VMType() machine.VMType {
return vmtype
}
func (v Virtualization) loadFromLocalJson() ([]*HyperVMachine, error) {
var (
jsonFiles []string
mms []*HyperVMachine
)
configDir, err := machine.GetConfDir(v.VMType())
if err != nil {
return nil, err
}
if err := filepath.WalkDir(configDir, func(input string, d fs.DirEntry, e error) error {
if e != nil {
return e
}
if filepath.Ext(d.Name()) == ".json" {
jsonFiles = append(jsonFiles, input)
}
return nil
}); err != nil {
return nil, err
}
for _, jsonFile := range jsonFiles {
mm := HyperVMachine{}
if err := loadMacMachineFromJSON(jsonFile, &mm); err != nil {
return nil, err
}
if err != nil {
return nil, err
}
mms = append(mms, &mm)
}
return mms, nil
}
func handlePrevError(e, prevErr error) error {
if prevErr != nil {
logrus.Error(e)
}
return e
}