//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 }