From 51d068ecc73bd64312c3fb70cfc5bae4a6c4c558 Mon Sep 17 00:00:00 2001 From: Brent Baude Date: Mon, 27 Mar 2023 10:06:23 -0500 Subject: [PATCH] implement podman machine set for hyperv add the ability to set cpu and processor counts on an existing vm. Signed-off-by: Brent Baude [NO NEW TESTS NEEDED] --- go.mod | 2 +- go.sum | 4 +- pkg/machine/hyperv/machine.go | 62 +++++++++- .../containers/libhvee/pkg/hypervctl/error.go | 44 ++++++- .../libhvee/pkg/hypervctl/memory_settings.go | 8 +- .../pkg/hypervctl/processor_settings.go | 8 +- .../libhvee/pkg/hypervctl/summary.go | 5 +- .../libhvee/pkg/hypervctl/system_settings.go | 2 +- .../pkg/hypervctl/system_settings_builder.go | 4 +- .../containers/libhvee/pkg/hypervctl/vm.go | 117 ++++++++++++++++-- .../containers/libhvee/pkg/hypervctl/vmm.go | 2 +- .../libhvee/pkg/wmiext/conversion.go | 13 +- .../containers/libhvee/pkg/wmiext/service.go | 14 +++ vendor/modules.txt | 2 +- 14 files changed, 256 insertions(+), 31 deletions(-) diff --git a/go.mod b/go.mod index 4d7c573c91..cb0320799f 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/containers/common v0.51.1-0.20230316131336-0be880eaeb02 github.com/containers/conmon v2.0.20+incompatible github.com/containers/image/v5 v5.24.3-0.20230314083015-0c6d07e02a9a - github.com/containers/libhvee v0.0.1 + github.com/containers/libhvee v0.0.2 github.com/containers/ocicrypt v1.1.7 github.com/containers/psgo v1.8.0 github.com/containers/storage v1.45.5-0.20230315220505-1c6287eea927 diff --git a/go.sum b/go.sum index 1d99f5452d..f37efa7f71 100644 --- a/go.sum +++ b/go.sum @@ -253,8 +253,8 @@ github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6J github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I= github.com/containers/image/v5 v5.24.3-0.20230314083015-0c6d07e02a9a h1:2xIif78r5x2nmdb5uhjXBZuexiDAt1c/XIXFxFhfKSk= github.com/containers/image/v5 v5.24.3-0.20230314083015-0c6d07e02a9a/go.mod h1:9PM/hiCVTh6dt8Swi7eYKXKHIaPabHn8gtFV2YD44Mk= -github.com/containers/libhvee v0.0.1 h1:QpknIQ2VG54HLNoXb0ZXdmah8lw7EC2atD5pX7543pI= -github.com/containers/libhvee v0.0.1/go.mod h1:bV1MfbuXk/ZLWHiWZpm8aePOR6iJGD1q55guYhH4CnA= +github.com/containers/libhvee v0.0.2 h1:eWtbOvpT8bD9jvksMES2yXUmEpcE0zENWkci+bbP7U8= +github.com/containers/libhvee v0.0.2/go.mod h1:bV1MfbuXk/ZLWHiWZpm8aePOR6iJGD1q55guYhH4CnA= github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 h1:Qzk5C6cYglewc+UyGf6lc8Mj2UaPTHy/iF2De0/77CA= github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY= github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= diff --git a/pkg/machine/hyperv/machine.go b/pkg/machine/hyperv/machine.go index b99881171c..a0eb1fbb35 100644 --- a/pkg/machine/hyperv/machine.go +++ b/pkg/machine/hyperv/machine.go @@ -281,7 +281,67 @@ func (m *HyperVMachine) Remove(_ string, opts machine.RemoveOptions) (string, fu } func (m *HyperVMachine) Set(name string, opts machine.SetOptions) ([]error, error) { - return nil, machine.ErrNotImplemented + var ( + cpuChanged, memoryChanged bool + setErrors []error + ) + vmm := hypervctl.NewVirtualMachineManager() + // Considering this a hard return if we cannot lookup the machine + vm, err := vmm.GetMachine(m.Name) + if err != nil { + return setErrors, err + } + if vm.State() != hypervctl.Disabled { + return nil, errors.New("unable to change settings unless vm is stopped") + } + + if opts.Rootful != nil && m.Rootful != *opts.Rootful { + setErrors = append(setErrors, hypervctl.ErrNotImplemented) + } + if opts.DiskSize != nil && m.DiskSize != *opts.DiskSize { + setErrors = append(setErrors, hypervctl.ErrNotImplemented) + } + if opts.CPUs != nil && m.CPUs != *opts.CPUs { + m.CPUs = *opts.CPUs + cpuChanged = true + } + if opts.Memory != nil && m.Memory != *opts.Memory { + m.Memory = *opts.Memory + memoryChanged = true + } + + if !cpuChanged && !memoryChanged { + switch len(setErrors) { + case 0: + return nil, nil + case 1: + return nil, setErrors[0] + default: + return setErrors[1:], setErrors[0] + } + } + // Write the new JSON out + // considering this a hard return if we cannot write the JSON file. + b, err := json.MarshalIndent(m, "", " ") + if err != nil { + return setErrors, err + } + if err := os.WriteFile(m.ConfigPath.GetPath(), b, 0644); err != nil { + return setErrors, err + } + + return setErrors, vm.UpdateProcessorMemSettings(func(ps *hypervctl.ProcessorSettings) { + if cpuChanged { + ps.VirtualQuantity = m.CPUs + } + }, func(ms *hypervctl.MemorySettings) { + if memoryChanged { + ms.DynamicMemoryEnabled = false + ms.VirtualQuantity = m.Memory + ms.Limit = m.Memory + ms.Reservation = m.Memory + } + }) } func (m *HyperVMachine) SSH(name string, opts machine.SSHOptions) error { diff --git a/vendor/github.com/containers/libhvee/pkg/hypervctl/error.go b/vendor/github.com/containers/libhvee/pkg/hypervctl/error.go index fc4bc6dfde..7fe985d979 100644 --- a/vendor/github.com/containers/libhvee/pkg/hypervctl/error.go +++ b/vendor/github.com/containers/libhvee/pkg/hypervctl/error.go @@ -68,7 +68,6 @@ const ( ErrShutdownInProgress = 32782 ) - type shutdownCompError struct { errorCode int message string @@ -114,4 +113,45 @@ func translateShutdownError(code int) error { } return &shutdownCompError{code, message} -} \ No newline at end of file +} + +// Modify resource errors +const ( + ErrModifyResourceNotSupported = 1 + ErrModifyResourceFailed = 2 + ErrModifyResourceTimeout = 3 + ErrModifyResourceInvalidParameter = 4 + ErrModifyResourceInvalidState = 5 + ErrModifyResourceIncompatParam = 6 +) + +type modifyResourceError struct { + errorCode int + message string +} + +func (m *modifyResourceError) Error() string { + return fmt.Sprintf("%s (%d)", m.message, m.errorCode) +} + +func translateModifyError(code int) error { + var message string + switch code { + case ErrModifyResourceNotSupported: + message = "virtual machine does not support modification operations" + case ErrModifyResourceFailed: + message = "resource modification failed" + case ErrModifyResourceTimeout: + message = "timeout modifying resource" + case ErrModifyResourceInvalidParameter: + message = "a modify resource operation was passed an invalid parameter" + case ErrModifyResourceInvalidState: + message = "the requested modification could not be applied due to an invalid state" + case ErrModifyResourceIncompatParam: + message = "an incompatible parameter was passed to a modify resource operation" + default: + message = "unknown error" + } + + return &modifyResourceError{code, message} +} diff --git a/vendor/github.com/containers/libhvee/pkg/hypervctl/memory_settings.go b/vendor/github.com/containers/libhvee/pkg/hypervctl/memory_settings.go index bd76e43be8..960694eb70 100644 --- a/vendor/github.com/containers/libhvee/pkg/hypervctl/memory_settings.go +++ b/vendor/github.com/containers/libhvee/pkg/hypervctl/memory_settings.go @@ -3,6 +3,8 @@ package hypervctl +import "fmt" + const MemoryResourceType = "Microsoft:Hyper-V:Memory" type MemorySettings struct { @@ -41,7 +43,11 @@ type MemorySettings struct { } func createMemorySettings(settings *MemorySettings) (string, error) { - return createResourceSettingGeneric(settings, MemoryResourceType) + str, err := createResourceSettingGeneric(settings, MemoryResourceType) + if err != nil { + err = fmt.Errorf("could not create memory settings: %w", err) + } + return str, err } func fetchDefaultMemorySettings() (*MemorySettings, error) { diff --git a/vendor/github.com/containers/libhvee/pkg/hypervctl/processor_settings.go b/vendor/github.com/containers/libhvee/pkg/hypervctl/processor_settings.go index a7703df4b4..9c0d6d95e8 100644 --- a/vendor/github.com/containers/libhvee/pkg/hypervctl/processor_settings.go +++ b/vendor/github.com/containers/libhvee/pkg/hypervctl/processor_settings.go @@ -3,6 +3,8 @@ package hypervctl +import "fmt" + const ProcessorResourceType = "Microsoft:Hyper-V:Processor" /* @@ -88,5 +90,9 @@ func fetchDefaultProcessorSettings() (*ProcessorSettings, error) { } func createProcessorSettings(settings *ProcessorSettings) (string, error) { - return createResourceSettingGeneric(settings, ProcessorResourceType) + str, err := createResourceSettingGeneric(settings, ProcessorResourceType) + if err != nil { + err = fmt.Errorf("could not create processor settings: %w", err) + } + return str, err } diff --git a/vendor/github.com/containers/libhvee/pkg/hypervctl/summary.go b/vendor/github.com/containers/libhvee/pkg/hypervctl/summary.go index e330434f8c..c8abcef1b0 100644 --- a/vendor/github.com/containers/libhvee/pkg/hypervctl/summary.go +++ b/vendor/github.com/containers/libhvee/pkg/hypervctl/summary.go @@ -46,12 +46,11 @@ const ( SummaryRequestOtherEnabledState = 132 ) - type SummaryRequestSet []uint var ( - // SummaryRequestCommon includes a smaller subset of commonly used fields + // SummaryRequestCommon includes a smaller subset of commonly used fields SummaryRequestCommon = SummaryRequestSet{ SummaryRequestName, SummaryRequestElementName, @@ -72,7 +71,7 @@ var ( SummaryRequestSwapFilesInUse, } - // SummaryRequestNearAll includes everything but load history and thumbnails + // SummaryRequestNearAll includes everything but load history and thumbnails SummaryRequestNearAll = SummaryRequestSet{ SummaryRequestName, SummaryRequestElementName, diff --git a/vendor/github.com/containers/libhvee/pkg/hypervctl/system_settings.go b/vendor/github.com/containers/libhvee/pkg/hypervctl/system_settings.go index 3119a6de37..527a8574c2 100644 --- a/vendor/github.com/containers/libhvee/pkg/hypervctl/system_settings.go +++ b/vendor/github.com/containers/libhvee/pkg/hypervctl/system_settings.go @@ -174,7 +174,7 @@ func addResource(service *wmiext.Service, systemSettingPath string, resourceSett return "", fmt.Errorf("AddResourceSettings failed: %w", err) } - err = waitVMResult(res, service, job) + err = waitVMResult(res, service, job, "failed to add resource", nil) if len(resultingSettings) > 0 { return resultingSettings[0], err diff --git a/vendor/github.com/containers/libhvee/pkg/hypervctl/system_settings_builder.go b/vendor/github.com/containers/libhvee/pkg/hypervctl/system_settings_builder.go index 71b6a6330d..119ba9e6d4 100644 --- a/vendor/github.com/containers/libhvee/pkg/hypervctl/system_settings_builder.go +++ b/vendor/github.com/containers/libhvee/pkg/hypervctl/system_settings_builder.go @@ -139,9 +139,9 @@ func (builder *SystemSettingsBuilder) Build() (*SystemSettings, error) { return nil, fmt.Errorf("failed to define system: %w", err) } - err = waitVMResult(res, service, job) + err = waitVMResult(res, service, job, "failed to define system", nil) if err != nil { - return nil, fmt.Errorf("failed to define system: %w", err) + return nil, err } newSettings, err := service.FindFirstRelatedInstance(resultingSystem, "Msvm_VirtualSystemSettingData") diff --git a/vendor/github.com/containers/libhvee/pkg/hypervctl/vm.go b/vendor/github.com/containers/libhvee/pkg/hypervctl/vm.go index 989f9f0aa0..5636d4058e 100644 --- a/vendor/github.com/containers/libhvee/pkg/hypervctl/vm.go +++ b/vendor/github.com/containers/libhvee/pkg/hypervctl/vm.go @@ -183,18 +183,27 @@ func (vm *VirtualMachine) kvpOperation(op string, key string, value string, ille return err } -func waitVMResult(res int32, service *wmiext.Service, job *wmiext.Instance) error { +func waitVMResult(res int32, service *wmiext.Service, job *wmiext.Instance, errorMsg string, translate func(int) error) error { var err error - if res == 4096 { + switch res { + case 0: + return nil + case 4096: err = wmiext.WaitJob(service, job) defer job.Close() + default: + if translate != nil { + return translate(int(res)) + } + + return fmt.Errorf("%s (result code %d)", errorMsg, res) } if err != nil { desc, _ := job.GetAsString("ErrorDescription") desc = strings.Replace(desc, "\n", " ", -1) - return fmt.Errorf("failed to define system: %w (%s)", err, desc) + return fmt.Errorf("%s: %w (%s)", errorMsg, err, desc) } return err @@ -270,7 +279,7 @@ func (vm *VirtualMachine) Start() error { Out("ReturnValue", &res).End(); err != nil { return err } - return waitVMResult(res, srv, job) + return waitVMResult(res, srv, job, "failed to start vm", nil) } func getService(_ *wmiext.Service) (*wmiext.Service, error) { @@ -328,7 +337,7 @@ func (vm *VirtualMachine) GetSummaryInformation(requestedFields SummaryRequestSe } defer service.Close() - instance, err := service.FindFirstRelatedInstance(vm.Path(), "Msvm_VirtualSystemSettingData") + instance, err := vm.fetchSystemSettingsInstance(service) if err != nil { return nil, err } @@ -418,6 +427,98 @@ func (vmm *VirtualMachineManager) NewVirtualMachine(name string, config *Hardwar return nil } +func (vm *VirtualMachine) fetchSystemSettingsInstance(service *wmiext.Service) (*wmiext.Instance, error) { + // When a settings snapshot is taken there are multiple associations, use only the realized/active version + return service.FindFirstRelatedInstanceThrough(vm.Path(), "Msvm_VirtualSystemSettingData", "Msvm_SettingsDefineState") +} + +func (vm *VirtualMachine) fetchExistingResourceSettings(service *wmiext.Service, resourceType string, resourceSettings interface{}) error { + const errFmt = "could not fetch resource settings (%s): %w" + // When a settings snapshot is taken there are multiple associations, use only the realized/active version + instance, err := vm.fetchSystemSettingsInstance(service) + if err != nil { + return fmt.Errorf(errFmt, resourceType, err) + } + defer instance.Close() + + path, err := instance.Path() + if err != nil { + return fmt.Errorf(errFmt, resourceType, err) + + } + + return service.FindFirstRelatedObject(path, resourceType, resourceSettings) +} + +// Update processor and/or mem +func (vm *VirtualMachine) UpdateProcessorMemSettings(updateProcessor func(*ProcessorSettings), updateMemory func(*MemorySettings)) error { + service, err := wmiext.NewLocalService(HyperVNamespace) + if err != nil { + return err + } + defer service.Close() + + proc := &ProcessorSettings{} + mem := &MemorySettings{} + + var settings []string + if updateProcessor != nil { + err = vm.fetchExistingResourceSettings(service, "Msvm_ProcessorSettingData", proc) + if err != nil { + return err + } + + updateProcessor(proc) + + processorStr, err := createProcessorSettings(proc) + if err != nil { + return err + } + settings = append(settings, processorStr) + } + + if updateMemory != nil { + err = vm.fetchExistingResourceSettings(service, "Msvm_MemorySettingData", mem) + if err != nil { + return err + } + + updateMemory(mem) + + memStr, err := createMemorySettings(mem) + if err != nil { + return err + } + + settings = append(settings, memStr) + } + + if len(settings) < 1 { + return nil + } + + vsms, err := service.GetSingletonInstance("Msvm_VirtualSystemManagementService") + if err != nil { + return err + } + defer vsms.Close() + + var job *wmiext.Instance + var res int32 + err = vsms.BeginInvoke("ModifyResourceSettings"). + In("ResourceSettings", settings). + Execute(). + Out("Job", &job). + Out("ReturnValue", &res). + End() + + if err != nil { + return fmt.Errorf("failed to modify resource settings: %w", err) + } + + return waitVMResult(res, service, job, "failed to modify resource settings", translateModifyError) +} + func (vm *VirtualMachine) remove() (int32, error) { var ( err error @@ -433,7 +534,7 @@ func (vm *VirtualMachine) remove() (int32, error) { return -1, err } - wmiInst, err := srv.FindFirstRelatedInstance(vm.Path(), "Msvm_VirtualSystemSettingData") + wmiInst, err := vm.fetchSystemSettingsInstance(srv) if err != nil { return -1, err } @@ -448,7 +549,7 @@ func (vm *VirtualMachine) remove() (int32, error) { if err != nil { return -1, err } - defer wmiInst.Close() + defer vsms.Close() var ( job *wmiext.Instance @@ -465,7 +566,7 @@ func (vm *VirtualMachine) remove() (int32, error) { } // do i have this correct? you can get an error without a result? - if err := waitVMResult(res, srv, job); err != nil { + if err := waitVMResult(res, srv, job, "failed to remove vm", nil); err != nil { return -1, err } return res, nil diff --git a/vendor/github.com/containers/libhvee/pkg/hypervctl/vmm.go b/vendor/github.com/containers/libhvee/pkg/hypervctl/vmm.go index dadfc2e836..a7cce22081 100644 --- a/vendor/github.com/containers/libhvee/pkg/hypervctl/vmm.go +++ b/vendor/github.com/containers/libhvee/pkg/hypervctl/vmm.go @@ -138,7 +138,7 @@ func (*VirtualMachineManager) CreateVhdxFile(path string, maxSize uint64) error return fmt.Errorf("failed to create vhdx: %w", err) } - return waitVMResult(ret, service, job) + return waitVMResult(ret, service, job, "failed to create vhdx", nil) } // GetSummaryInformation returns the live VM summary information for all virtual machines. diff --git a/vendor/github.com/containers/libhvee/pkg/wmiext/conversion.go b/vendor/github.com/containers/libhvee/pkg/wmiext/conversion.go index 5b9b68ee52..9dc9690606 100644 --- a/vendor/github.com/containers/libhvee/pkg/wmiext/conversion.go +++ b/vendor/github.com/containers/libhvee/pkg/wmiext/conversion.go @@ -17,8 +17,8 @@ import ( ) var ( - unixEpoch = time.Unix(0,0) - zeroTime = time.Time{} + unixEpoch = time.Unix(0, 0) + zeroTime = time.Time{} ) // Automation variants do not follow the OLE rules, instead they use the following mapping: @@ -346,7 +346,7 @@ func convertTimeToDataTime(time *time.Time) ole.VARIANT { return ole.NewVariant(ole.VT_BSTR, int64(uintptr(unsafe.Pointer(ole.SysAllocStringLen(s))))) } -func convertDurationToDateTime(duration time.Duration) ole.VARIANT { +func convertDurationToDateTime(duration time.Duration) ole.VARIANT { const daySeconds = time.Second * 86400 if duration == 0 { @@ -367,7 +367,7 @@ func convertDurationToDateTime(duration time.Duration) ole.VARIANT { micros := duration / time.Microsecond - s:=fmt.Sprintf("%08d%02d%02d%02d.%06d:000", days, hours, mins, seconds, micros) + s := fmt.Sprintf("%08d%02d%02d%02d.%06d:000", days, hours, mins, seconds, micros) return ole.NewVariant(ole.VT_BSTR, int64(uintptr(unsafe.Pointer(ole.SysAllocStringLen(s))))) } @@ -426,7 +426,7 @@ func parseIntervalTime(interval string) (time.Time, error) { } days, err := parseUintChain(interval[0:8], nil) - hours, err := parseUintChain(interval[8:10], err) + hours, err := parseUintChain(interval[8:10], err) mins, err := parseUintChain(interval[10:12], err) secs, err := parseUintChain(interval[12:14], err) micros, err := parseUintChain(interval[15:21], err) @@ -440,7 +440,7 @@ func parseIntervalTime(interval string) (time.Time, error) { stamp += hours * 3600 stamp += mins * 60 - return time.Unix(int64(stamp), int64(micros * 1000)), nil + return time.Unix(int64(stamp), int64(micros*1000)), nil } func convertIntervalToDuration(variant *ole.VARIANT) (time.Duration, error) { @@ -465,7 +465,6 @@ func parseUintChain(str string, err error) (uint64, error) { return strconv.ParseUint(str, 10, 0) } - func abs(num int) int { if num < 0 { return -num diff --git a/vendor/github.com/containers/libhvee/pkg/wmiext/service.go b/vendor/github.com/containers/libhvee/pkg/wmiext/service.go index eec30323eb..60bd42d8e6 100644 --- a/vendor/github.com/containers/libhvee/pkg/wmiext/service.go +++ b/vendor/github.com/containers/libhvee/pkg/wmiext/service.go @@ -306,6 +306,20 @@ func (s *Service) FindFirstRelatedInstance(objPath string, className string) (*I return s.FindFirstInstance(wql) } +// FindFirstRelatedInstanceThrough finds and returns a related associator of the specified WMI object path of the +// expected className type, and only through the expected association type. +func (s *Service) FindFirstRelatedInstanceThrough(objPath string, resultClass string, assocClass string) (*Instance, error) { + wql := fmt.Sprintf("ASSOCIATORS OF {%s} WHERE AssocClass = %s ResultClass = %s ", objPath, assocClass, resultClass) + return s.FindFirstInstance(wql) +} + +// FindFirstRelatedObject finds and returns a related associator of the specified WMI object path of the +// expected className type, and populates the passed in struct with its fields +func (s *Service) FindFirstRelatedObject(objPath string, className string, target interface{}) error { + wql := fmt.Sprintf("ASSOCIATORS OF {%s} WHERE ResultClass = %s", objPath, className) + return s.FindFirstObject(wql, target) +} + // FindFirstObject finds and returns the first WMI Instance in the result set for a WSL query, and // populates the specified struct pointer passed in through the target parameter. func (s *Service) FindFirstObject(wql string, target interface{}) error { diff --git a/vendor/modules.txt b/vendor/modules.txt index a360449a5a..1e622fa764 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -249,7 +249,7 @@ github.com/containers/image/v5/transports github.com/containers/image/v5/transports/alltransports github.com/containers/image/v5/types github.com/containers/image/v5/version -# github.com/containers/libhvee v0.0.1 +# github.com/containers/libhvee v0.0.2 ## explicit; go 1.18 github.com/containers/libhvee/pkg/hypervctl github.com/containers/libhvee/pkg/kvp/ginsu