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 }