github.com/containers/podman/v4@v4.9.4/pkg/machine/hyperv/config.go (about)

     1  //go:build windows
     2  // +build windows
     3  
     4  package hyperv
     5  
     6  import (
     7  	"encoding/json"
     8  	"errors"
     9  	"fmt"
    10  	"io/fs"
    11  	"os"
    12  	"path/filepath"
    13  	"time"
    14  
    15  	"github.com/containers/libhvee/pkg/hypervctl"
    16  	"github.com/containers/podman/v4/pkg/machine"
    17  	"github.com/containers/podman/v4/pkg/machine/compression"
    18  	"github.com/containers/podman/v4/pkg/machine/define"
    19  	"github.com/docker/go-units"
    20  	"github.com/sirupsen/logrus"
    21  )
    22  
    23  type HyperVVirtualization struct {
    24  	machine.Virtualization
    25  }
    26  
    27  func VirtualizationProvider() machine.VirtProvider {
    28  	return &HyperVVirtualization{
    29  		machine.NewVirtualization(define.HyperV, compression.Zip, define.Vhdx, vmtype),
    30  	}
    31  }
    32  
    33  func (v HyperVVirtualization) CheckExclusiveActiveVM() (bool, string, error) {
    34  	vmm := hypervctl.NewVirtualMachineManager()
    35  	// Use of GetAll is OK here because we do not want to use the same name
    36  	// as something already *actually* configured in hyperv
    37  	vms, err := vmm.GetAll()
    38  	if err != nil {
    39  		return false, "", err
    40  	}
    41  	for _, vm := range vms {
    42  		if vm.IsStarting() || vm.State() == hypervctl.Enabled {
    43  			return true, vm.ElementName, nil
    44  		}
    45  	}
    46  	return false, "", nil
    47  }
    48  
    49  func (v HyperVVirtualization) IsValidVMName(name string) (bool, error) {
    50  	var found bool
    51  	vms, err := v.loadFromLocalJson()
    52  	if err != nil {
    53  		return false, err
    54  	}
    55  	for _, vm := range vms {
    56  		if vm.Name == name {
    57  			found = true
    58  			break
    59  		}
    60  	}
    61  	if !found {
    62  		return false, nil
    63  	}
    64  	if _, err := hypervctl.NewVirtualMachineManager().GetMachine(name); err != nil {
    65  		return false, err
    66  	}
    67  	return true, nil
    68  }
    69  
    70  func (v HyperVVirtualization) List(opts machine.ListOptions) ([]*machine.ListResponse, error) {
    71  	mms, err := v.loadFromLocalJson()
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  
    76  	var response []*machine.ListResponse
    77  	vmm := hypervctl.NewVirtualMachineManager()
    78  
    79  	for _, mm := range mms {
    80  		vm, err := vmm.GetMachine(mm.Name)
    81  		if err != nil {
    82  			return nil, err
    83  		}
    84  		mlr := machine.ListResponse{
    85  			Name:           mm.Name,
    86  			CreatedAt:      mm.Created,
    87  			LastUp:         mm.LastUp,
    88  			Running:        vm.State() == hypervctl.Enabled,
    89  			Starting:       mm.isStarting(),
    90  			Stream:         mm.ImageStream,
    91  			VMType:         machine.HyperVVirt.String(),
    92  			CPUs:           mm.CPUs,
    93  			Memory:         mm.Memory * units.MiB,
    94  			DiskSize:       mm.DiskSize * units.GiB,
    95  			Port:           mm.Port,
    96  			RemoteUsername: mm.RemoteUsername,
    97  			IdentityPath:   mm.IdentityPath,
    98  		}
    99  		response = append(response, &mlr)
   100  	}
   101  	return response, err
   102  }
   103  
   104  func (v HyperVVirtualization) LoadVMByName(name string) (machine.VM, error) {
   105  	m := &HyperVMachine{Name: name}
   106  	return m.loadFromFile()
   107  }
   108  
   109  func (v HyperVVirtualization) NewMachine(opts machine.InitOptions) (machine.VM, error) {
   110  	m := HyperVMachine{Name: opts.Name}
   111  	if len(opts.ImagePath) < 1 {
   112  		return nil, errors.New("must define --image-path for hyperv support")
   113  	}
   114  	if len(opts.USBs) > 0 {
   115  		return nil, fmt.Errorf("USB host passtrough not supported for hyperv machines")
   116  	}
   117  
   118  	m.RemoteUsername = opts.Username
   119  
   120  	configDir, err := machine.GetConfDir(machine.HyperVVirt)
   121  	if err != nil {
   122  		return nil, err
   123  	}
   124  
   125  	configPath, err := define.NewMachineFile(getVMConfigPath(configDir, opts.Name), nil)
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  
   130  	m.ConfigPath = *configPath
   131  
   132  	if err := machine.SetIgnitionFile(&m.IgnitionFile, vmtype, m.Name); err != nil {
   133  		return nil, err
   134  	}
   135  
   136  	// Set creation time
   137  	m.Created = time.Now()
   138  
   139  	dataDir, err := machine.GetDataDir(machine.HyperVVirt)
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  
   144  	// Set the proxy pid file
   145  	gvProxyPid, err := define.NewMachineFile(filepath.Join(dataDir, "gvproxy.pid"), nil)
   146  	if err != nil {
   147  		return nil, err
   148  	}
   149  	m.GvProxyPid = *gvProxyPid
   150  
   151  	dl, err := VirtualizationProvider().NewDownload(m.Name)
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  	// Acquire the image
   156  	imagePath, imageStream, err := dl.AcquireVMImage(opts.ImagePath)
   157  	if err != nil {
   158  		return nil, err
   159  	}
   160  
   161  	// assign values to machine
   162  	m.ImagePath = *imagePath
   163  	m.ImageStream = imageStream.String()
   164  
   165  	config := hypervctl.HardwareConfig{
   166  		CPUs:     uint16(opts.CPUS),
   167  		DiskPath: imagePath.GetPath(),
   168  		DiskSize: opts.DiskSize,
   169  		Memory:   opts.Memory,
   170  	}
   171  
   172  	// Write the json configuration file which will be loaded by
   173  	// LoadByName
   174  	b, err := json.MarshalIndent(m, "", " ")
   175  	if err != nil {
   176  		return nil, err
   177  	}
   178  	if err := os.WriteFile(m.ConfigPath.GetPath(), b, 0644); err != nil {
   179  		return nil, err
   180  	}
   181  
   182  	vmm := hypervctl.NewVirtualMachineManager()
   183  	if err := vmm.NewVirtualMachine(opts.Name, &config); err != nil {
   184  		return nil, err
   185  	}
   186  	return v.LoadVMByName(opts.Name)
   187  }
   188  
   189  func (v HyperVVirtualization) RemoveAndCleanMachines() error {
   190  	// Error handling used here is following what qemu did
   191  	var (
   192  		prevErr error
   193  	)
   194  
   195  	// The next three info lookups must succeed or we return
   196  	mms, err := v.loadFromLocalJson()
   197  	if err != nil {
   198  		return err
   199  	}
   200  
   201  	configDir, err := machine.GetConfDir(vmtype)
   202  	if err != nil {
   203  		return err
   204  	}
   205  
   206  	dataDir, err := machine.GetDataDir(vmtype)
   207  	if err != nil {
   208  		return err
   209  	}
   210  
   211  	vmm := hypervctl.NewVirtualMachineManager()
   212  	for _, mm := range mms {
   213  		vm, err := vmm.GetMachine(mm.Name)
   214  		if err != nil {
   215  			prevErr = handlePrevError(err, prevErr)
   216  		}
   217  
   218  		if vm.State() != hypervctl.Disabled {
   219  			if err := vm.StopWithForce(); err != nil {
   220  				prevErr = handlePrevError(err, prevErr)
   221  			}
   222  		}
   223  		if err := vm.Remove(mm.ImagePath.GetPath()); err != nil {
   224  			prevErr = handlePrevError(err, prevErr)
   225  		}
   226  		if err := mm.ReadyHVSock.Remove(); err != nil {
   227  			prevErr = handlePrevError(err, prevErr)
   228  		}
   229  		if err := mm.NetworkHVSock.Remove(); err != nil {
   230  			prevErr = handlePrevError(err, prevErr)
   231  		}
   232  	}
   233  
   234  	// Nuke the config and dataDirs
   235  	if err := os.RemoveAll(configDir); err != nil {
   236  		prevErr = handlePrevError(err, prevErr)
   237  	}
   238  	if err := os.RemoveAll(dataDir); err != nil {
   239  		prevErr = handlePrevError(err, prevErr)
   240  	}
   241  	return prevErr
   242  }
   243  
   244  func (v HyperVVirtualization) VMType() machine.VMType {
   245  	return vmtype
   246  }
   247  
   248  func (v HyperVVirtualization) loadFromLocalJson() ([]*HyperVMachine, error) {
   249  	var (
   250  		jsonFiles []string
   251  		mms       []*HyperVMachine
   252  	)
   253  	configDir, err := machine.GetConfDir(v.VMType())
   254  	if err != nil {
   255  		return nil, err
   256  	}
   257  	if err := filepath.WalkDir(configDir, func(input string, d fs.DirEntry, e error) error {
   258  		if e != nil {
   259  			return e
   260  		}
   261  		if filepath.Ext(d.Name()) == ".json" {
   262  			jsonFiles = append(jsonFiles, input)
   263  		}
   264  		return nil
   265  	}); err != nil {
   266  		return nil, err
   267  	}
   268  
   269  	for _, jsonFile := range jsonFiles {
   270  		mm := HyperVMachine{}
   271  		if err := mm.loadHyperVMachineFromJSON(jsonFile); err != nil {
   272  			return nil, err
   273  		}
   274  		if err != nil {
   275  			return nil, err
   276  		}
   277  		mms = append(mms, &mm)
   278  	}
   279  	return mms, nil
   280  }
   281  
   282  func handlePrevError(e, prevErr error) error {
   283  	if prevErr != nil {
   284  		logrus.Error(e)
   285  	}
   286  	return e
   287  }
   288  
   289  func stateConversion(s hypervctl.EnabledState) (machine.Status, error) {
   290  	switch s {
   291  	case hypervctl.Enabled:
   292  		return machine.Running, nil
   293  	case hypervctl.Disabled:
   294  		return machine.Stopped, nil
   295  	case hypervctl.Starting:
   296  		return machine.Starting, nil
   297  	}
   298  	return machine.Unknown, fmt.Errorf("unknown state: %q", s.String())
   299  }