diff --git a/govc/USAGE.md b/govc/USAGE.md index f3008713d..b51e69d89 100644 --- a/govc/USAGE.md +++ b/govc/USAGE.md @@ -2739,6 +2739,7 @@ Change VM configuration. To add ExtraConfig variables that can read within the guest, use the 'guestinfo.' prefix. Examples: + govc vm.change -vm $vm -mem.reservation 2048 govc vm.change -vm $vm -e smc.present=TRUE -e ich7m.present=TRUE govc vm.change -vm $vm -e guestinfo.vmname $vm # Read the variable set above inside the guest: @@ -2746,9 +2747,15 @@ Examples: Options: -c=0 Number of CPUs + -cpu.limit= CPU limit in MHz + -cpu.reservation= CPU reservation in MHz + -cpu.shares= CPU shares level or number -e=[] ExtraConfig. = -g= Guest OS -m=0 Size in MB of memory + -mem.limit= Memory limit in MB + -mem.reservation= Memory reservation in MB + -mem.shares= Memory shares level or number -name= Display name -nested-hv-enabled= Enable nested hardware-assisted virtualization -sync-time-with-host= Enable SyncTimeWithHost diff --git a/govc/flags/resource_allocation_info.go b/govc/flags/resource_allocation_info.go new file mode 100644 index 000000000..99a262ec4 --- /dev/null +++ b/govc/flags/resource_allocation_info.go @@ -0,0 +1,85 @@ +/* +Copyright (c) 2017 VMware, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package flags + +import ( + "context" + "flag" + "strconv" + "strings" + + "github.com/vmware/govmomi/vim25/types" +) + +type sharesInfo types.SharesInfo + +func (s *sharesInfo) String() string { + return string(s.Level) +} + +func (s *sharesInfo) Set(val string) error { + switch val { + case string(types.SharesLevelNormal), string(types.SharesLevelLow), string(types.SharesLevelHigh): + s.Level = types.SharesLevel(val) + default: + n, err := strconv.Atoi(val) + if err != nil { + return err + } + + s.Level = types.SharesLevelCustom + s.Shares = int32(n) + } + + return nil +} + +type ResourceAllocationFlag struct { + cpu, mem *types.ResourceAllocationInfo + ExpandableReservation bool +} + +func NewResourceAllocationFlag(cpu, mem *types.ResourceAllocationInfo) *ResourceAllocationFlag { + return &ResourceAllocationFlag{cpu, mem, true} +} + +func (r *ResourceAllocationFlag) Register(ctx context.Context, f *flag.FlagSet) { + opts := []struct { + name string + units string + *types.ResourceAllocationInfo + }{ + {"CPU", "MHz", r.cpu}, + {"Memory", "MB", r.mem}, + } + + for _, opt := range opts { + prefix := strings.ToLower(opt.name)[:3] + shares := (*sharesInfo)(opt.Shares) + + f.Var(NewOptionalInt64(&opt.Limit), prefix+".limit", opt.name+" limit in "+opt.units) + f.Var(NewOptionalInt64(&opt.Reservation), prefix+".reservation", opt.name+" reservation in "+opt.units) + if r.ExpandableReservation { + f.Var(NewOptionalBool(&opt.ExpandableReservation), prefix+".expandable", opt.name+" expandable reservation") + } + f.Var(shares, prefix+".shares", opt.name+" shares level or number") + } +} + +func (s *ResourceAllocationFlag) Process(ctx context.Context) error { + return nil +} diff --git a/govc/flags/version.go b/govc/flags/version.go index f1ea204d0..6333e6622 100644 --- a/govc/flags/version.go +++ b/govc/flags/version.go @@ -21,7 +21,7 @@ import ( "strings" ) -const Version = "0.16.0" +const Version = "0.16.1" type version []int diff --git a/govc/pool/resource_config_spec.go b/govc/pool/resource_config_spec.go index 45522ae7f..c4e933ccd 100644 --- a/govc/pool/resource_config_spec.go +++ b/govc/pool/resource_config_spec.go @@ -19,63 +19,23 @@ package pool import ( "context" "flag" - "strconv" - "strings" "github.com/vmware/govmomi/govc/flags" "github.com/vmware/govmomi/vim25/types" ) -type sharesInfo types.SharesInfo - -func (s *sharesInfo) String() string { - return string(s.Level) -} - -func (s *sharesInfo) Set(val string) error { - switch val { - case string(types.SharesLevelNormal), string(types.SharesLevelLow), string(types.SharesLevelHigh): - s.Level = types.SharesLevel(val) - default: - n, err := strconv.Atoi(val) - if err != nil { - return err - } - - s.Level = types.SharesLevelCustom - s.Shares = int32(n) - } - - return nil -} - func NewResourceConfigSpecFlag() *ResourceConfigSpecFlag { - return &ResourceConfigSpecFlag{types.DefaultResourceConfigSpec()} + return &ResourceConfigSpecFlag{types.DefaultResourceConfigSpec(), nil} } type ResourceConfigSpecFlag struct { types.ResourceConfigSpec + *flags.ResourceAllocationFlag } func (s *ResourceConfigSpecFlag) Register(ctx context.Context, f *flag.FlagSet) { - opts := []struct { - name string - units string - *types.ResourceAllocationInfo - }{ - {"CPU", "MHz", &s.CpuAllocation}, - {"Memory", "MB", &s.MemoryAllocation}, - } - - for _, opt := range opts { - prefix := strings.ToLower(opt.name)[:3] - shares := (*sharesInfo)(opt.Shares) - - f.Var(flags.NewOptionalInt64(&opt.Limit), prefix+".limit", opt.name+" limit in "+opt.units) - f.Var(flags.NewOptionalInt64(&opt.Reservation), prefix+".reservation", opt.name+" reservation in "+opt.units) - f.Var(flags.NewOptionalBool(&opt.ExpandableReservation), prefix+".expandable", opt.name+" expandable reservation") - f.Var(shares, prefix+".shares", opt.name+" shares level or number") - } + s.ResourceAllocationFlag = flags.NewResourceAllocationFlag(&s.CpuAllocation, &s.MemoryAllocation) + s.ResourceAllocationFlag.Register(ctx, f) } func (s *ResourceConfigSpecFlag) Process(ctx context.Context) error { diff --git a/govc/test/vm.bats b/govc/test/vm.bats index 672b8566d..391ddc034 100755 --- a/govc/test/vm.bats +++ b/govc/test/vm.bats @@ -107,6 +107,14 @@ load test_helper assert_success assert_line "SyncTimeWithHost: true" + run govc object.collect -s "vm/$id" config.memoryAllocation.reservation + assert_success 0 + + govc vm.change -vm "$id" -mem.reservation 1024 + + run govc object.collect -s "vm/$id" config.memoryAllocation.reservation + assert_success 1024 + nid=$(new_id) run govc vm.change -name $nid -vm $id assert_success diff --git a/govc/vm/change.go b/govc/vm/change.go index af00b36c9..6b4bea2df 100644 --- a/govc/vm/change.go +++ b/govc/vm/change.go @@ -44,6 +44,7 @@ func (e *extraConfig) Set(v string) error { type change struct { *flags.VirtualMachineFlag + *flags.ResourceAllocationFlag types.VirtualMachineConfigSpec extraConfig extraConfig @@ -53,10 +54,39 @@ func init() { cli.Register("vm.change", &change{}) } +// setAllocation sets *info=nil if none of the fields have been set. +// We need non-nil fields for use with flag.FlagSet, but we want the +// VirtualMachineConfigSpec fields to be nil if none of the related flags were given. +func setAllocation(info **types.ResourceAllocationInfo) { + r := *info + + if r.Shares.Level == "" { + r.Shares = nil + } else { + return + } + + if r.Limit != nil { + return + } + + if r.Reservation != nil { + return + } + + *info = nil +} + func (cmd *change) Register(ctx context.Context, f *flag.FlagSet) { cmd.VirtualMachineFlag, ctx = flags.NewVirtualMachineFlag(ctx) cmd.VirtualMachineFlag.Register(ctx, f) + cmd.CpuAllocation = &types.ResourceAllocationInfo{Shares: new(types.SharesInfo)} + cmd.MemoryAllocation = &types.ResourceAllocationInfo{Shares: new(types.SharesInfo)} + cmd.ResourceAllocationFlag = flags.NewResourceAllocationFlag(cmd.CpuAllocation, cmd.MemoryAllocation) + cmd.ResourceAllocationFlag.ExpandableReservation = false + cmd.ResourceAllocationFlag.Register(ctx, f) + f.Int64Var(&cmd.MemoryMB, "m", 0, "Size in MB of memory") f.Var(flags.NewInt32(&cmd.NumCPUs), "c", "Number of CPUs") f.StringVar(&cmd.GuestId, "g", "", "Guest OS") @@ -74,6 +104,7 @@ func (cmd *change) Description() string { To add ExtraConfig variables that can read within the guest, use the 'guestinfo.' prefix. Examples: + govc vm.change -vm $vm -mem.reservation 2048 govc vm.change -vm $vm -e smc.present=TRUE -e ich7m.present=TRUE govc vm.change -vm $vm -e guestinfo.vmname $vm # Read the variable set above inside the guest: @@ -101,6 +132,9 @@ func (cmd *change) Run(ctx context.Context, f *flag.FlagSet) error { cmd.VirtualMachineConfigSpec.ExtraConfig = cmd.extraConfig } + setAllocation(&cmd.CpuAllocation) + setAllocation(&cmd.MemoryAllocation) + task, err := vm.Reconfigure(ctx, cmd.VirtualMachineConfigSpec) if err != nil { return err diff --git a/simulator/resource_pool.go b/simulator/resource_pool.go index 1b42580f3..604f7b44d 100644 --- a/simulator/resource_pool.go +++ b/simulator/resource_pool.go @@ -43,14 +43,14 @@ func NewResourcePool() *ResourcePool { return pool } -func (p *ResourcePool) allFieldsSet(info types.ResourceAllocationInfo) bool { +func allResourceFieldsSet(info *types.ResourceAllocationInfo) bool { return info.Reservation != nil && info.Limit != nil && info.ExpandableReservation != nil && info.Shares != nil } -func (p *ResourcePool) allFieldsValid(info types.ResourceAllocationInfo) bool { +func allResourceFieldsValid(info *types.ResourceAllocationInfo) bool { if info.Reservation != nil { if *info.Reservation < 0 { return false @@ -86,13 +86,13 @@ func (p *ResourcePool) createChild(name string, spec types.ResourceConfigSpec) ( }) } - if !(p.allFieldsSet(spec.CpuAllocation) && p.allFieldsValid(spec.CpuAllocation)) { + if !(allResourceFieldsSet(&spec.CpuAllocation) && allResourceFieldsValid(&spec.CpuAllocation)) { return nil, Fault("", &types.InvalidArgument{ InvalidProperty: "spec.cpuAllocation", }) } - if !(p.allFieldsSet(spec.MemoryAllocation) && p.allFieldsValid(spec.MemoryAllocation)) { + if !(allResourceFieldsSet(&spec.MemoryAllocation) && allResourceFieldsValid(&spec.MemoryAllocation)) { return nil, Fault("", &types.InvalidArgument{ InvalidProperty: "spec.memoryAllocation", }) @@ -130,11 +130,11 @@ func (p *ResourcePool) CreateResourcePool(c *types.CreateResourcePool) soap.HasF return body } -func (p *ResourcePool) updateAllocation(kind string, src types.ResourceAllocationInfo, dst *types.ResourceAllocationInfo) *soap.Fault { - if !p.allFieldsValid(src) { - return Fault("", &types.InvalidArgument{ +func updateResourceAllocation(kind string, src, dst *types.ResourceAllocationInfo) types.BaseMethodFault { + if !allResourceFieldsValid(src) { + return &types.InvalidArgument{ InvalidProperty: fmt.Sprintf("spec.%sAllocation", kind), - }) + } } if src.Reservation != nil { @@ -170,13 +170,13 @@ func (p *ResourcePool) UpdateConfig(c *types.UpdateConfig) soap.HasFault { spec := c.Config if spec != nil { - if err := p.updateAllocation("memory", spec.MemoryAllocation, &p.Config.MemoryAllocation); err != nil { - body.Fault_ = err + if err := updateResourceAllocation("memory", &spec.MemoryAllocation, &p.Config.MemoryAllocation); err != nil { + body.Fault_ = Fault("", err) return body } - if err := p.updateAllocation("cpu", spec.CpuAllocation, &p.Config.CpuAllocation); err != nil { - body.Fault_ = err + if err := updateResourceAllocation("cpu", &spec.CpuAllocation, &p.Config.CpuAllocation); err != nil { + body.Fault_ = Fault("", err) return body } } diff --git a/simulator/resource_pool_test.go b/simulator/resource_pool_test.go index 16d8f0034..2f8116912 100644 --- a/simulator/resource_pool_test.go +++ b/simulator/resource_pool_test.go @@ -271,48 +271,46 @@ func TestCreateVAppVPX(t *testing.T) { } func TestResourcePoolValidation(t *testing.T) { - var pool ResourcePool - tests := []func() bool{ func() bool { - return pool.allFieldsSet(types.ResourceAllocationInfo{}) + return allResourceFieldsSet(&types.ResourceAllocationInfo{}) }, func() bool { spec := types.DefaultResourceConfigSpec() spec.CpuAllocation.Limit = nil - return pool.allFieldsSet(spec.CpuAllocation) + return allResourceFieldsSet(&spec.CpuAllocation) }, func() bool { spec := types.DefaultResourceConfigSpec() spec.CpuAllocation.Reservation = nil - return pool.allFieldsSet(spec.CpuAllocation) + return allResourceFieldsSet(&spec.CpuAllocation) }, func() bool { spec := types.DefaultResourceConfigSpec() spec.CpuAllocation.ExpandableReservation = nil - return pool.allFieldsSet(spec.CpuAllocation) + return allResourceFieldsSet(&spec.CpuAllocation) }, func() bool { spec := types.DefaultResourceConfigSpec() spec.CpuAllocation.Shares = nil - return pool.allFieldsSet(spec.CpuAllocation) + return allResourceFieldsSet(&spec.CpuAllocation) }, func() bool { spec := types.DefaultResourceConfigSpec() spec.CpuAllocation.Reservation = types.NewInt64(-1) - return pool.allFieldsValid(spec.CpuAllocation) + return allResourceFieldsValid(&spec.CpuAllocation) }, func() bool { spec := types.DefaultResourceConfigSpec() spec.CpuAllocation.Limit = types.NewInt64(-100) - return pool.allFieldsValid(spec.CpuAllocation) + return allResourceFieldsValid(&spec.CpuAllocation) }, func() bool { spec := types.DefaultResourceConfigSpec() shares := spec.CpuAllocation.Shares shares.Level = types.SharesLevelCustom shares.Shares = -1 - return pool.allFieldsValid(spec.CpuAllocation) + return allResourceFieldsValid(&spec.CpuAllocation) }, } diff --git a/simulator/virtual_machine.go b/simulator/virtual_machine.go index 1414ea471..52f324329 100644 --- a/simulator/virtual_machine.go +++ b/simulator/virtual_machine.go @@ -57,9 +57,12 @@ func NewVirtualMachine(parent types.ManagedObjectReference, spec *types.VirtualM return nil, &types.InvalidVmConfig{Property: "configSpec.files.vmPathName"} } + rspec := types.DefaultResourceConfigSpec() vm.Config = &types.VirtualMachineConfigInfo{ - ExtraConfig: []types.BaseOptionValue{&types.OptionValue{Key: "govcsim", Value: "TRUE"}}, - Tools: &types.ToolsConfigInfo{}, + ExtraConfig: []types.BaseOptionValue{&types.OptionValue{Key: "govcsim", Value: "TRUE"}}, + Tools: &types.ToolsConfigInfo{}, + MemoryAllocation: &rspec.MemoryAllocation, + CpuAllocation: &rspec.CpuAllocation, } vm.Summary.Guest = &types.VirtualMachineGuestSummary{} vm.Summary.Storage = &types.VirtualMachineStorageSummary{} @@ -159,6 +162,18 @@ func (vm *VirtualMachine) apply(spec *types.VirtualMachineConfigSpec) { func (vm *VirtualMachine) configure(spec *types.VirtualMachineConfigSpec) types.BaseMethodFault { vm.apply(spec) + if spec.MemoryAllocation != nil { + if err := updateResourceAllocation("memory", spec.MemoryAllocation, vm.Config.MemoryAllocation); err != nil { + return err + } + } + + if spec.CpuAllocation != nil { + if err := updateResourceAllocation("cpu", spec.CpuAllocation, vm.Config.CpuAllocation); err != nil { + return err + } + } + return vm.configureDevices(spec) } diff --git a/simulator/virtual_machine_test.go b/simulator/virtual_machine_test.go index a83eb774c..d1842debc 100644 --- a/simulator/virtual_machine_test.go +++ b/simulator/virtual_machine_test.go @@ -241,7 +241,7 @@ func TestCreateVm(t *testing.T) { } } -func TestReconfigVm(t *testing.T) { +func TestReconfigVmDevice(t *testing.T) { ctx := context.Background() m := ESX() @@ -335,6 +335,58 @@ func TestReconfigVm(t *testing.T) { } } +func TestReconfigVm(t *testing.T) { + ctx := context.Background() + + m := ESX() + defer m.Remove() + err := m.Create() + if err != nil { + t.Fatal(err) + } + + s := m.Service.NewServer() + defer s.Close() + + c, err := govmomi.NewClient(ctx, s.URL, true) + if err != nil { + t.Fatal(err) + } + + vm := object.NewVirtualMachine(c.Client, Map.Any("VirtualMachine").Reference()) + + tests := []struct { + fail bool + spec types.VirtualMachineConfigSpec + }{ + { + true, types.VirtualMachineConfigSpec{ + CpuAllocation: &types.ResourceAllocationInfo{Reservation: types.NewInt64(-1)}, + }, + }, + { + false, types.VirtualMachineConfigSpec{ + CpuAllocation: &types.ResourceAllocationInfo{Reservation: types.NewInt64(100)}, + }, + }, + } + + for i, test := range tests { + rtask, _ := vm.Reconfigure(ctx, test.spec) + + err := rtask.Wait(ctx) + if test.fail { + if err == nil { + t.Errorf("%d: expected failure", i) + } + } else { + if err != nil { + t.Errorf("unexpected failure: %s", err) + } + } + } +} + func TestCreateVmWithDevices(t *testing.T) { ctx := context.Background()