diff --git a/find/finder.go b/find/finder.go index 04d0e891a..2e9727e48 100644 --- a/find/finder.go +++ b/find/finder.go @@ -625,6 +625,15 @@ func (f *Finder) ClusterComputeResourceList(ctx context.Context, path string) ([ return ccrs, nil } +func (f *Finder) DefaultClusterComputeResource(ctx context.Context) (*object.ClusterComputeResource, error) { + cr, err := f.ClusterComputeResource(ctx, "*") + if err != nil { + return nil, toDefaultError(err) + } + + return cr, nil +} + func (f *Finder) ClusterComputeResource(ctx context.Context, path string) (*object.ClusterComputeResource, error) { ccrs, err := f.ClusterComputeResourceList(ctx, path) if err != nil { @@ -638,6 +647,18 @@ func (f *Finder) ClusterComputeResource(ctx context.Context, path string) (*obje return ccrs[0], nil } +func (f *Finder) ClusterComputeResourceOrDefault(ctx context.Context, path string) (*object.ClusterComputeResource, error) { + if path != "" { + cr, err := f.ClusterComputeResource(ctx, path) + if err != nil { + return nil, err + } + return cr, nil + } + + return f.DefaultClusterComputeResource(ctx) +} + func (f *Finder) HostSystemList(ctx context.Context, path string) ([]*object.HostSystem, error) { s := &spec{ Relative: f.hostFolder, diff --git a/govc/USAGE.md b/govc/USAGE.md index b51e69d89..0d20e5470 100644 --- a/govc/USAGE.md +++ b/govc/USAGE.md @@ -83,7 +83,7 @@ Examples: govc cluster.add -cluster ClusterB -hostname 10.0.6.1 -username root -password pass -noverify Options: - -cluster=* Path to cluster + -cluster= Cluster [GOVC_CLUSTER] -connect=true Immediately connect to host -force=false Force when host is managed by another VC -hostname= Hostname or IP address of the host @@ -131,6 +131,158 @@ Options: -folder= Inventory folder [GOVC_FOLDER] ``` +## cluster.group.change + +``` +Usage: govc cluster.group.change [OPTIONS] NAME... + +Set cluster group members. + +Examples: + govc cluster.group.change -name my_group vm_a vm_b vm_c # set + govc cluster.group.change -name my_group vm_a vm_b vm_c $(govc cluster.group.ls -name my_group) vm_d # add + govc cluster.group.ls -name my_group | grep -v vm_b | xargs govc cluster.group.change -name my_group vm_a vm_b vm_c # remove + +Options: + -cluster= Cluster [GOVC_CLUSTER] + -name= Cluster group name +``` + +## cluster.group.create + +``` +Usage: govc cluster.group.create [OPTIONS] + +Create cluster group. + +One of '-vm' or '-host' must be provided to specify the group type. + +Examples: + govc cluster.group.create -name my_vm_group -vm vm_a vm_b vm_c + govc cluster.group.create -name my_host_group -host host_a host_b host_c + +Options: + -cluster= Cluster [GOVC_CLUSTER] + -host=false Create cluster Host group + -name= Cluster group name + -vm=false Create cluster VM group +``` + +## cluster.group.ls + +``` +Usage: govc cluster.group.ls [OPTIONS] + +List cluster groups and group members. + +Examples: + govc cluster.group.ls -cluster my_cluster + govc cluster.group.ls -cluster my_cluster -name my_group + +Options: + -cluster= Cluster [GOVC_CLUSTER] + -name= Cluster group name +``` + +## cluster.group.remove + +``` +Usage: govc cluster.group.remove [OPTIONS] + +Remove cluster group. + +Examples: + govc cluster.group.remove -cluster my_cluster -name my_group + +Options: + -cluster= Cluster [GOVC_CLUSTER] + -name= Cluster group name +``` + +## cluster.rule.change + +``` +Usage: govc cluster.rule.change [OPTIONS] NAME... + +Change cluster rule. + +Examples: + govc cluster.rule.change -cluster my_cluster -name my_rule -enable=false + +Options: + -cluster= Cluster [GOVC_CLUSTER] + -enable= Enable rule + -host-affine-group= Host affine group name + -host-anti-affine-group= Host anti-affine group name + -mandatory= Enforce rule compliance + -name= Cluster rule name + -vm-group= VM group name +``` + +## cluster.rule.create + +``` +Usage: govc cluster.rule.create [OPTIONS] NAME... + +Create cluster rule. + +Rules are not enabled by default, use the 'enable' flag to enable upon creation or cluster.rule.change after creation. + +One of '-affinity', '-anti-affinity' or '-vm-host' must be provided to specify the rule type. + +With '-affinity' or '-anti-affinity', at least 2 vm NAME arguments must be specified. + +With '-vm-host', use the '-vm-group' flag combined with the '-host-affine-group' and/or '-host-anti-affine-group' flags. + +Examples: + govc cluster.rule.create -name pod1 -enable -affinity vm_a vm_b vm_c + govc cluster.rule.create -name pod2 -enable -anti-affinity vm_d vm_e vm_f + govc cluster.rule.create -name pod3 -enable -mandatory -vm-host -vm-group my_vms -host-affine-group my_hosts + +Options: + -affinity=false Keep Virtual Machines Together + -anti-affinity=false Separate Virtual Machines + -cluster= Cluster [GOVC_CLUSTER] + -enable= Enable rule + -host-affine-group= Host affine group name + -host-anti-affine-group= Host anti-affine group name + -mandatory= Enforce rule compliance + -name= Cluster rule name + -vm-group= VM group name + -vm-host=false Virtual Machines to Hosts +``` + +## cluster.rule.ls + +``` +Usage: govc cluster.rule.ls [OPTIONS] + +List cluster rules and rule members. + +Examples: + govc cluster.rule.ls -cluster my_cluster + govc cluster.rule.ls -cluster my_cluster -name my_rule + +Options: + -cluster= Cluster [GOVC_CLUSTER] + -name= Cluster rule name +``` + +## cluster.rule.remove + +``` +Usage: govc cluster.rule.remove [OPTIONS] + +Remove cluster rule. + +Examples: + govc cluster.group.remove -cluster my_cluster -name my_rule + +Options: + -cluster= Cluster [GOVC_CLUSTER] + -name= Cluster rule name +``` + ## datacenter.create ``` @@ -200,6 +352,7 @@ Create VMDK on DS. Examples: govc datastore.mkdir disks govc datastore.disk.create -size 24G disks/disk1.vmdk + govc datastore.disk.create disks/parent.vmdk disk/child.vmdk Options: -ds= Datastore [GOVC_DATASTORE] @@ -3178,6 +3331,23 @@ Remove VM from inventory without removing any of the VM files on disk. Options: ``` +## vm.upgrade + +``` +Usage: govc vm.upgrade [OPTIONS] + +Upgrade VMs to latest hardware version + +Examples: + govc vm.upgrade -vm $vm_name + govc vm.upgrade -version=$version -vm $vm_name + govc vm.upgrade -version=$version -vm.uuid $vm_uuid + +Options: + -version=0 Target vm hardware version, by default -- latest available + -vm= Virtual machine [GOVC_VM] +``` + ## vm.vnc ``` diff --git a/govc/cluster/add.go b/govc/cluster/add.go index e29af5ab8..3b8f7d78a 100644 --- a/govc/cluster/add.go +++ b/govc/cluster/add.go @@ -27,10 +27,9 @@ import ( ) type add struct { - *flags.DatacenterFlag + *flags.ClusterFlag *flags.HostConnectFlag - cluster string connect bool license string } @@ -40,21 +39,19 @@ func init() { } func (cmd *add) Register(ctx context.Context, f *flag.FlagSet) { - cmd.DatacenterFlag, ctx = flags.NewDatacenterFlag(ctx) - cmd.DatacenterFlag.Register(ctx, f) + cmd.ClusterFlag, ctx = flags.NewClusterFlag(ctx) + cmd.ClusterFlag.Register(ctx, f) cmd.HostConnectFlag, ctx = flags.NewHostConnectFlag(ctx) cmd.HostConnectFlag.Register(ctx, f) - f.StringVar(&cmd.cluster, "cluster", "*", "Path to cluster") - f.StringVar(&cmd.license, "license", "", "Assign license key") f.BoolVar(&cmd.connect, "connect", true, "Immediately connect to host") } func (cmd *add) Process(ctx context.Context) error { - if err := cmd.DatacenterFlag.Process(ctx); err != nil { + if err := cmd.ClusterFlag.Process(ctx); err != nil { return err } if err := cmd.HostConnectFlag.Process(ctx); err != nil { @@ -108,12 +105,7 @@ func (cmd *add) Run(ctx context.Context, f *flag.FlagSet) error { return flag.ErrHelp } - finder, err := cmd.Finder() - if err != nil { - return err - } - - cluster, err := finder.ClusterComputeResource(ctx, cmd.cluster) + cluster, err := cmd.Cluster() if err != nil { return err } diff --git a/govc/cluster/group/change.go b/govc/cluster/group/change.go new file mode 100644 index 000000000..ddaffce52 --- /dev/null +++ b/govc/cluster/group/change.go @@ -0,0 +1,75 @@ +/* +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 group + +import ( + "context" + "flag" + + "github.com/vmware/govmomi/govc/cli" + "github.com/vmware/govmomi/vim25/types" +) + +type change struct { + *InfoFlag +} + +func init() { + cli.Register("cluster.group.change", &change{}) +} + +func (cmd *change) Register(ctx context.Context, f *flag.FlagSet) { + cmd.InfoFlag, ctx = NewInfoFlag(ctx) + cmd.InfoFlag.Register(ctx, f) +} + +func (cmd *change) Process(ctx context.Context) error { + if cmd.name == "" { + return flag.ErrHelp + } + return cmd.InfoFlag.Process(ctx) +} + +func (cmd *change) Usage() string { + return `NAME...` +} + +func (cmd *change) Description() string { + return `Set cluster group members. + +Examples: + govc cluster.group.change -name my_group vm_a vm_b vm_c # set + govc cluster.group.change -name my_group vm_a vm_b vm_c $(govc cluster.group.ls -name my_group) vm_d # add + govc cluster.group.ls -name my_group | grep -v vm_b | xargs govc cluster.group.change -name my_group vm_a vm_b vm_c # remove` +} + +func (cmd *change) Run(ctx context.Context, f *flag.FlagSet) error { + update := types.ArrayUpdateSpec{Operation: types.ArrayUpdateOperationEdit} + group, err := cmd.Group(ctx) + if err != nil { + return err + } + + refs, err := cmd.ObjectList(ctx, group.kind, f.Args()) + if err != nil { + return err + } + + *group.refs = refs + + return cmd.Apply(ctx, update, group.info) +} diff --git a/govc/cluster/group/create.go b/govc/cluster/group/create.go new file mode 100644 index 000000000..ef20e0ed5 --- /dev/null +++ b/govc/cluster/group/create.go @@ -0,0 +1,86 @@ +/* +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 group + +import ( + "context" + "flag" + + "github.com/vmware/govmomi/govc/cli" + "github.com/vmware/govmomi/vim25/types" +) + +type create struct { + *InfoFlag + + vm bool + host bool +} + +func init() { + cli.Register("cluster.group.create", &create{}) +} + +func (cmd *create) Register(ctx context.Context, f *flag.FlagSet) { + cmd.InfoFlag, ctx = NewInfoFlag(ctx) + cmd.InfoFlag.Register(ctx, f) + + f.BoolVar(&cmd.vm, "vm", false, "Create cluster VM group") + f.BoolVar(&cmd.host, "host", false, "Create cluster Host group") +} + +func (cmd *create) Process(ctx context.Context) error { + if cmd.name == "" { + return flag.ErrHelp + } + return cmd.InfoFlag.Process(ctx) +} + +func (cmd *create) Description() string { + return `Create cluster group. + +One of '-vm' or '-host' must be provided to specify the group type. + +Examples: + govc cluster.group.create -name my_vm_group -vm vm_a vm_b vm_c + govc cluster.group.create -name my_host_group -host host_a host_b host_c` +} + +func (cmd *create) Run(ctx context.Context, f *flag.FlagSet) error { + update := types.ArrayUpdateSpec{Operation: types.ArrayUpdateOperationAdd} + var info types.BaseClusterGroupInfo + var err error + + switch { + case cmd.vm: + info = new(types.ClusterVmGroup) + case cmd.host: + info = new(types.ClusterHostGroup) + default: + return flag.ErrHelp + } + + info.GetClusterGroupInfo().Name = cmd.name + + group := newGroupInfo(info) + *group.refs, err = cmd.ObjectList(ctx, group.kind, f.Args()) + if err != nil { + return err + } + + return cmd.Apply(ctx, update, info) +} diff --git a/govc/cluster/group/info_flag.go b/govc/cluster/group/info_flag.go new file mode 100644 index 000000000..1efc2f753 --- /dev/null +++ b/govc/cluster/group/info_flag.go @@ -0,0 +1,121 @@ +/* +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 group + +import ( + "context" + "flag" + "fmt" + + "github.com/vmware/govmomi/govc/flags" + "github.com/vmware/govmomi/vim25/types" +) + +type InfoFlag struct { + *flags.ClusterFlag + + groups []types.BaseClusterGroupInfo + + name string +} + +func NewInfoFlag(ctx context.Context) (*InfoFlag, context.Context) { + f := &InfoFlag{} + f.ClusterFlag, ctx = flags.NewClusterFlag(ctx) + return f, ctx +} + +func (f *InfoFlag) Register(ctx context.Context, fs *flag.FlagSet) { + f.ClusterFlag.Register(ctx, fs) + + fs.StringVar(&f.name, "name", "", "Cluster group name") +} + +func (f *InfoFlag) Process(ctx context.Context) error { + return f.ClusterFlag.Process(ctx) +} + +func (f *InfoFlag) Groups(ctx context.Context) ([]types.BaseClusterGroupInfo, error) { + if f.groups != nil { + return f.groups, nil + } + + cluster, err := f.Cluster() + if err != nil { + return nil, err + } + + config, err := cluster.Configuration(ctx) + if err != nil { + return nil, err + } + + f.groups = config.Group + + return f.groups, nil +} + +type ClusterGroupInfo struct { + info types.BaseClusterGroupInfo + + refs *[]types.ManagedObjectReference + + kind string +} + +func newGroupInfo(info types.BaseClusterGroupInfo) *ClusterGroupInfo { + group := &ClusterGroupInfo{info: info} + + switch info := info.(type) { + case *types.ClusterHostGroup: + group.refs = &info.Host + group.kind = "HostSystem" + case *types.ClusterVmGroup: + group.refs = &info.Vm + group.kind = "VirtualMachine" + } + + return group +} + +func (f *InfoFlag) Group(ctx context.Context) (*ClusterGroupInfo, error) { + groups, err := f.Groups(ctx) + if err != nil { + return nil, err + } + + for _, group := range groups { + if group.GetClusterGroupInfo().Name == f.name { + return newGroupInfo(group), nil + } + } + + return nil, fmt.Errorf("group %q not found", f.name) +} + +func (f *InfoFlag) Apply(ctx context.Context, update types.ArrayUpdateSpec, info types.BaseClusterGroupInfo) error { + spec := &types.ClusterConfigSpecEx{ + GroupSpec: []types.ClusterGroupSpec{ + { + ArrayUpdateSpec: update, + Info: info, + }, + }, + } + + return f.Reconfigure(ctx, spec) +} diff --git a/govc/cluster/group/ls.go b/govc/cluster/group/ls.go new file mode 100644 index 000000000..1dbe5e07a --- /dev/null +++ b/govc/cluster/group/ls.go @@ -0,0 +1,92 @@ +/* +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 group + +import ( + "context" + "flag" + "fmt" + "io" + + "github.com/vmware/govmomi/govc/cli" +) + +type ls struct { + *InfoFlag +} + +func init() { + cli.Register("cluster.group.ls", &ls{}) +} + +func (cmd *ls) Register(ctx context.Context, f *flag.FlagSet) { + cmd.InfoFlag, ctx = NewInfoFlag(ctx) + cmd.InfoFlag.Register(ctx, f) +} + +func (cmd *ls) Process(ctx context.Context) error { + return cmd.InfoFlag.Process(ctx) +} + +func (cmd *ls) Description() string { + return `List cluster groups and group members. + +Examples: + govc cluster.group.ls -cluster my_cluster + govc cluster.group.ls -cluster my_cluster -name my_group` +} + +type groupResult []string + +func (r groupResult) Write(w io.Writer) error { + for i := range r { + fmt.Fprintln(w, r[i]) + } + + return nil +} + +func (cmd *ls) Run(ctx context.Context, f *flag.FlagSet) error { + var res groupResult + + if cmd.name == "" { + groups, err := cmd.Groups(ctx) + if err != nil { + return err + } + + for _, g := range groups { + res = append(res, g.GetClusterGroupInfo().Name) + } + } else { + group, err := cmd.Group(ctx) + if err != nil { + return err + } + + names, err := cmd.Names(ctx, *group.refs) + if err != nil { + return err + } + + for _, ref := range *group.refs { + res = append(res, names[ref]) + } + } + + return cmd.WriteResult(res) +} diff --git a/govc/cluster/group/remove.go b/govc/cluster/group/remove.go new file mode 100644 index 000000000..f86477103 --- /dev/null +++ b/govc/cluster/group/remove.go @@ -0,0 +1,61 @@ +/* +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 group + +import ( + "context" + "flag" + + "github.com/vmware/govmomi/govc/cli" + "github.com/vmware/govmomi/vim25/types" +) + +type remove struct { + *InfoFlag +} + +func init() { + cli.Register("cluster.group.remove", &remove{}) +} + +func (cmd *remove) Register(ctx context.Context, f *flag.FlagSet) { + cmd.InfoFlag, ctx = NewInfoFlag(ctx) + cmd.InfoFlag.Register(ctx, f) +} + +func (cmd *remove) Process(ctx context.Context) error { + if cmd.name == "" { + return flag.ErrHelp + } + return cmd.InfoFlag.Process(ctx) +} + +func (cmd *remove) Description() string { + return `Remove cluster group. + +Examples: + govc cluster.group.remove -cluster my_cluster -name my_group` +} + +func (cmd *remove) Run(ctx context.Context, f *flag.FlagSet) error { + update := types.ArrayUpdateSpec{ + Operation: types.ArrayUpdateOperationRemove, + RemoveKey: cmd.name, + } + + return cmd.Apply(ctx, update, nil) +} diff --git a/govc/cluster/rule/change.go b/govc/cluster/rule/change.go new file mode 100644 index 000000000..092efa689 --- /dev/null +++ b/govc/cluster/rule/change.go @@ -0,0 +1,93 @@ +/* +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 rule + +import ( + "context" + "flag" + + "github.com/vmware/govmomi/govc/cli" + "github.com/vmware/govmomi/vim25/types" +) + +type change struct { + *SpecFlag + *InfoFlag +} + +func init() { + cli.Register("cluster.rule.change", &change{}) +} + +func (cmd *change) Register(ctx context.Context, f *flag.FlagSet) { + cmd.SpecFlag = new(SpecFlag) + cmd.SpecFlag.Register(ctx, f) + + cmd.InfoFlag, ctx = NewInfoFlag(ctx) + cmd.InfoFlag.Register(ctx, f) +} + +func (cmd *change) Process(ctx context.Context) error { + if cmd.name == "" { + return flag.ErrHelp + } + return cmd.InfoFlag.Process(ctx) +} + +func (cmd *change) Usage() string { + return `NAME...` +} + +func (cmd *change) Description() string { + return `Change cluster rule. + +Examples: + govc cluster.rule.change -cluster my_cluster -name my_rule -enable=false` +} + +func (cmd *change) Run(ctx context.Context, f *flag.FlagSet) error { + update := types.ArrayUpdateSpec{Operation: types.ArrayUpdateOperationEdit} + rule, err := cmd.Rule(ctx) + if err != nil { + return err + } + + var vms *[]types.ManagedObjectReference + + switch r := rule.info.(type) { + case *types.ClusterAffinityRuleSpec: + vms = &r.Vm + case *types.ClusterAntiAffinityRuleSpec: + vms = &r.Vm + } + + if vms != nil && f.NArg() != 0 { + refs, err := cmd.ObjectList(ctx, rule.kind, f.Args()) + if err != nil { + return err + } + + *vms = refs + } + + info := rule.info.GetClusterRuleInfo() + info.Name = cmd.name + info.Enabled = cmd.Enabled + info.Mandatory = cmd.Mandatory + + return cmd.Apply(ctx, update, rule.info) +} diff --git a/govc/cluster/rule/create.go b/govc/cluster/rule/create.go new file mode 100644 index 000000000..f9590090d --- /dev/null +++ b/govc/cluster/rule/create.go @@ -0,0 +1,118 @@ +/* +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 rule + +import ( + "context" + "flag" + + "github.com/vmware/govmomi/govc/cli" + "github.com/vmware/govmomi/vim25/types" +) + +type create struct { + *SpecFlag + *InfoFlag + + vmhost bool + affinity bool + antiaffinity bool +} + +func init() { + cli.Register("cluster.rule.create", &create{}) +} + +func (cmd *create) Register(ctx context.Context, f *flag.FlagSet) { + cmd.SpecFlag = new(SpecFlag) + cmd.SpecFlag.Register(ctx, f) + + cmd.InfoFlag, ctx = NewInfoFlag(ctx) + cmd.InfoFlag.Register(ctx, f) + + f.BoolVar(&cmd.vmhost, "vm-host", false, "Virtual Machines to Hosts") + f.BoolVar(&cmd.affinity, "affinity", false, "Keep Virtual Machines Together") + f.BoolVar(&cmd.antiaffinity, "anti-affinity", false, "Separate Virtual Machines") +} + +func (cmd *create) Process(ctx context.Context) error { + if cmd.name == "" { + return flag.ErrHelp + } + return cmd.InfoFlag.Process(ctx) +} + +func (cmd *create) Usage() string { + return "NAME..." +} + +func (cmd *create) Description() string { + return `Create cluster rule. + +Rules are not enabled by default, use the 'enable' flag to enable upon creation or cluster.rule.change after creation. + +One of '-affinity', '-anti-affinity' or '-vm-host' must be provided to specify the rule type. + +With '-affinity' or '-anti-affinity', at least 2 vm NAME arguments must be specified. + +With '-vm-host', use the '-vm-group' flag combined with the '-host-affine-group' and/or '-host-anti-affine-group' flags. + +Examples: + govc cluster.rule.create -name pod1 -enable -affinity vm_a vm_b vm_c + govc cluster.rule.create -name pod2 -enable -anti-affinity vm_d vm_e vm_f + govc cluster.rule.create -name pod3 -enable -mandatory -vm-host -vm-group my_vms -host-affine-group my_hosts` +} + +func (cmd *create) Run(ctx context.Context, f *flag.FlagSet) error { + args := f.Args() + update := types.ArrayUpdateSpec{Operation: types.ArrayUpdateOperationAdd} + var rule types.BaseClusterRuleInfo + var err error + + switch { + case cmd.vmhost: + rule = &cmd.ClusterVmHostRuleInfo + case cmd.affinity: + rule = &cmd.ClusterAffinityRuleSpec + if len(args) < 2 { + return flag.ErrHelp // can't create this rule without 2 or more hosts + } + cmd.ClusterAffinityRuleSpec.Vm, err = cmd.ObjectList(ctx, "VirtualMachine", args) + if err != nil { + return err + } + case cmd.antiaffinity: + rule = &cmd.ClusterAntiAffinityRuleSpec + if len(args) < 2 { + return flag.ErrHelp // can't create this rule without 2 or more hosts + } + cmd.ClusterAntiAffinityRuleSpec.Vm, err = cmd.ObjectList(ctx, "VirtualMachine", args) + if err != nil { + return err + } + default: + return flag.ErrHelp + } + + info := rule.GetClusterRuleInfo() + info.Name = cmd.name + info.Enabled = cmd.Enabled + info.Mandatory = cmd.Mandatory + info.UserCreated = types.NewBool(true) + + return cmd.Apply(ctx, update, rule) +} diff --git a/govc/cluster/rule/info_flag.go b/govc/cluster/rule/info_flag.go new file mode 100644 index 000000000..74caad006 --- /dev/null +++ b/govc/cluster/rule/info_flag.go @@ -0,0 +1,135 @@ +/* +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 rule + +import ( + "context" + "flag" + "fmt" + + "github.com/vmware/govmomi/govc/flags" + "github.com/vmware/govmomi/vim25/types" +) + +type InfoFlag struct { + *flags.ClusterFlag + + rules []types.BaseClusterRuleInfo + + name string +} + +func NewInfoFlag(ctx context.Context) (*InfoFlag, context.Context) { + f := &InfoFlag{} + f.ClusterFlag, ctx = flags.NewClusterFlag(ctx) + return f, ctx +} + +func (f *InfoFlag) Register(ctx context.Context, fs *flag.FlagSet) { + f.ClusterFlag.Register(ctx, fs) + + fs.StringVar(&f.name, "name", "", "Cluster rule name") +} + +func (f *InfoFlag) Process(ctx context.Context) error { + return f.ClusterFlag.Process(ctx) +} + +func (f *InfoFlag) Rules(ctx context.Context) ([]types.BaseClusterRuleInfo, error) { + if f.rules != nil { + return f.rules, nil + } + + cluster, err := f.Cluster() + if err != nil { + return nil, err + } + + config, err := cluster.Configuration(ctx) + if err != nil { + return nil, err + } + + f.rules = config.Rule + + return f.rules, nil +} + +type ClusterRuleInfo struct { + info types.BaseClusterRuleInfo + + refs *[]types.ManagedObjectReference + + kind string +} + +func (f *InfoFlag) Rule(ctx context.Context) (*ClusterRuleInfo, error) { + rules, err := f.Rules(ctx) + if err != nil { + return nil, err + } + + for _, rule := range rules { + if rule.GetClusterRuleInfo().Name != f.name { + continue + } + + r := &ClusterRuleInfo{info: rule} + + switch info := rule.(type) { + case *types.ClusterAffinityRuleSpec: + r.refs = &info.Vm + r.kind = "VirtualMachine" + case *types.ClusterAntiAffinityRuleSpec: + r.refs = &info.Vm + r.kind = "VirtualMachine" + } + + return r, nil + } + + return nil, fmt.Errorf("rule %q not found", f.name) +} + +func (f *InfoFlag) Apply(ctx context.Context, update types.ArrayUpdateSpec, info types.BaseClusterRuleInfo) error { + spec := &types.ClusterConfigSpecEx{ + RulesSpec: []types.ClusterRuleSpec{ + { + ArrayUpdateSpec: update, + Info: info, + }, + }, + } + + return f.Reconfigure(ctx, spec) +} + +type SpecFlag struct { + types.ClusterRuleInfo + types.ClusterVmHostRuleInfo + types.ClusterAffinityRuleSpec + types.ClusterAntiAffinityRuleSpec +} + +func (s *SpecFlag) Register(ctx context.Context, f *flag.FlagSet) { + f.Var(flags.NewOptionalBool(&s.Enabled), "enable", "Enable rule") + f.Var(flags.NewOptionalBool(&s.Mandatory), "mandatory", "Enforce rule compliance") + + f.StringVar(&s.VmGroupName, "vm-group", "", "VM group name") + f.StringVar(&s.AffineHostGroupName, "host-affine-group", "", "Host affine group name") + f.StringVar(&s.AntiAffineHostGroupName, "host-anti-affine-group", "", "Host anti-affine group name") +} diff --git a/govc/cluster/rule/ls.go b/govc/cluster/rule/ls.go new file mode 100644 index 000000000..71fafc5a4 --- /dev/null +++ b/govc/cluster/rule/ls.go @@ -0,0 +1,96 @@ +/* +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 rule + +import ( + "context" + "flag" + "fmt" + "io" + + "github.com/vmware/govmomi/govc/cli" +) + +type ls struct { + *InfoFlag +} + +func init() { + cli.Register("cluster.rule.ls", &ls{}) +} + +func (cmd *ls) Register(ctx context.Context, f *flag.FlagSet) { + cmd.InfoFlag, ctx = NewInfoFlag(ctx) + cmd.InfoFlag.Register(ctx, f) +} + +func (cmd *ls) Process(ctx context.Context) error { + return cmd.InfoFlag.Process(ctx) +} + +func (cmd *ls) Description() string { + return `List cluster rules and rule members. + +Examples: + govc cluster.rule.ls -cluster my_cluster + govc cluster.rule.ls -cluster my_cluster -name my_rule` +} + +type ruleResult []string + +func (r ruleResult) Write(w io.Writer) error { + for i := range r { + fmt.Fprintln(w, r[i]) + } + + return nil +} + +func (cmd *ls) Run(ctx context.Context, f *flag.FlagSet) error { + var res ruleResult + + if cmd.name == "" { + rules, err := cmd.Rules(ctx) + if err != nil { + return err + } + + for _, g := range rules { + res = append(res, g.GetClusterRuleInfo().Name) + } + } else { + rule, err := cmd.Rule(ctx) + if err != nil { + return err + } + + if rule.refs == nil { + return nil + } + + names, err := cmd.Names(ctx, *rule.refs) + if err != nil { + return err + } + + for _, ref := range *rule.refs { + res = append(res, names[ref]) + } + } + + return cmd.WriteResult(res) +} diff --git a/govc/cluster/rule/remove.go b/govc/cluster/rule/remove.go new file mode 100644 index 000000000..a38b96b39 --- /dev/null +++ b/govc/cluster/rule/remove.go @@ -0,0 +1,66 @@ +/* +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 rule + +import ( + "context" + "flag" + + "github.com/vmware/govmomi/govc/cli" + "github.com/vmware/govmomi/vim25/types" +) + +type remove struct { + *InfoFlag +} + +func init() { + cli.Register("cluster.rule.remove", &remove{}) +} + +func (cmd *remove) Register(ctx context.Context, f *flag.FlagSet) { + cmd.InfoFlag, ctx = NewInfoFlag(ctx) + cmd.InfoFlag.Register(ctx, f) +} + +func (cmd *remove) Process(ctx context.Context) error { + if cmd.name == "" { + return flag.ErrHelp + } + return cmd.InfoFlag.Process(ctx) +} + +func (cmd *remove) Description() string { + return `Remove cluster rule. + +Examples: + govc cluster.group.remove -cluster my_cluster -name my_rule` +} + +func (cmd *remove) Run(ctx context.Context, f *flag.FlagSet) error { + rule, err := cmd.Rule(ctx) + if err != nil { + return err + } + + update := types.ArrayUpdateSpec{ + Operation: types.ArrayUpdateOperationRemove, + RemoveKey: rule.info.GetClusterRuleInfo().Key, + } + + return cmd.Apply(ctx, update, nil) +} diff --git a/govc/flags/cluster.go b/govc/flags/cluster.go new file mode 100644 index 000000000..618e38666 --- /dev/null +++ b/govc/flags/cluster.go @@ -0,0 +1,184 @@ +/* +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" + "fmt" + "os" + + "github.com/vmware/govmomi/object" + "github.com/vmware/govmomi/property" + "github.com/vmware/govmomi/view" + "github.com/vmware/govmomi/vim25/mo" + "github.com/vmware/govmomi/vim25/types" +) + +type ClusterFlag struct { + common + + *DatacenterFlag + + Name string + + cluster *object.ClusterComputeResource + pc *property.Collector +} + +var clusterFlagKey = flagKey("cluster") + +func NewClusterFlag(ctx context.Context) (*ClusterFlag, context.Context) { + if v := ctx.Value(clusterFlagKey); v != nil { + return v.(*ClusterFlag), ctx + } + + v := &ClusterFlag{} + v.DatacenterFlag, ctx = NewDatacenterFlag(ctx) + ctx = context.WithValue(ctx, clusterFlagKey, v) + return v, ctx +} + +func (f *ClusterFlag) Register(ctx context.Context, fs *flag.FlagSet) { + f.RegisterOnce(func() { + f.DatacenterFlag.Register(ctx, fs) + + env := "GOVC_CLUSTER" + value := os.Getenv(env) + usage := fmt.Sprintf("Cluster [%s]", env) + fs.StringVar(&f.Name, "cluster", value, usage) + }) +} + +func (f *ClusterFlag) Process(ctx context.Context) error { + return f.ProcessOnce(func() error { + if err := f.DatacenterFlag.Process(ctx); err != nil { + return err + } + return nil + }) +} + +func (f *ClusterFlag) Cluster() (*object.ClusterComputeResource, error) { + if f.cluster != nil { + return f.cluster, nil + } + + finder, err := f.Finder() + if err != nil { + return nil, err + } + + if f.cluster, err = finder.ClusterComputeResourceOrDefault(context.TODO(), f.Name); err != nil { + return nil, err + } + + f.pc = property.DefaultCollector(f.cluster.Client()) + + return f.cluster, nil +} + +func (f *ClusterFlag) Reconfigure(ctx context.Context, spec types.BaseComputeResourceConfigSpec) error { + cluster, err := f.Cluster() + if err != nil { + return err + } + + task, err := cluster.Reconfigure(ctx, spec, true) + if err != nil { + return err + } + + logger := f.ProgressLogger(fmt.Sprintf("Reconfigure %s...", cluster.InventoryPath)) + defer logger.Wait() + + _, err = task.WaitForResult(ctx, logger) + return err +} + +func (f *ClusterFlag) objectMap(ctx context.Context, kind string, names []string) (map[string]types.ManagedObjectReference, error) { + cluster, err := f.Cluster() + if err != nil { + return nil, err + } + + objects := make(map[string]types.ManagedObjectReference, len(names)) + for _, name := range names { + objects[name] = types.ManagedObjectReference{} + } + + m := view.NewManager(cluster.Client()) + v, err := m.CreateContainerView(ctx, cluster.Reference(), []string{kind}, true) + if err != nil { + return nil, err + } + defer v.Destroy(ctx) + + var entities []mo.ManagedEntity + + err = v.Retrieve(ctx, []string{"ManagedEntity"}, []string{"name"}, &entities) + if err != nil { + return nil, err + } + + for _, e := range entities { + if _, ok := objects[e.Name]; ok { + objects[e.Name] = e.Self + } + } + + for name, ref := range objects { + if ref.Value == "" { + return nil, fmt.Errorf("%s %q not found", kind, name) + } + } + + return objects, nil +} + +func (f *ClusterFlag) ObjectList(ctx context.Context, kind string, names []string) ([]types.ManagedObjectReference, error) { + objs, err := f.objectMap(ctx, kind, names) + if err != nil { + return nil, err + } + + var refs []types.ManagedObjectReference + + for _, name := range names { // preserve order + refs = append(refs, objs[name]) + } + + return refs, nil +} + +func (f *ClusterFlag) Names(ctx context.Context, refs []types.ManagedObjectReference) (map[types.ManagedObjectReference]string, error) { + names := make(map[types.ManagedObjectReference]string, len(refs)) + + if len(refs) != 0 { + var objs []mo.ManagedEntity + err := f.pc.Retrieve(ctx, refs, []string{"name"}, &objs) + if err != nil { + return nil, err + } + + for _, obj := range objs { + names[obj.Self] = obj.Name + } + } + + return names, nil +} diff --git a/govc/main.go b/govc/main.go index 6df93034b..236499dba 100644 --- a/govc/main.go +++ b/govc/main.go @@ -23,6 +23,8 @@ import ( _ "github.com/vmware/govmomi/govc/about" _ "github.com/vmware/govmomi/govc/cluster" + _ "github.com/vmware/govmomi/govc/cluster/group" + _ "github.com/vmware/govmomi/govc/cluster/rule" _ "github.com/vmware/govmomi/govc/datacenter" _ "github.com/vmware/govmomi/govc/datastore" _ "github.com/vmware/govmomi/govc/datastore/disk" diff --git a/govc/test/cluster.bats b/govc/test/cluster.bats new file mode 100755 index 000000000..477f2e4b3 --- /dev/null +++ b/govc/test/cluster.bats @@ -0,0 +1,104 @@ +#!/usr/bin/env bats + +load test_helper + +@test "cluster.group" { + vcsim_env -cluster 2 -host 4 -vm 8 + + run govc cluster.group.ls -cluster DC0_C0 + assert_success "" # no groups + + run govc cluster.group.ls -cluster DC0_C0 -name my_vm_group + assert_failure # group does not exist + + run govc cluster.group.create -cluster DC0_C0 -name my_vm_group -vm DC0_C0_H{0,1} + assert_failure # -vm or -host required + + run govc cluster.group.create -cluster DC0_C0 -name my_vm_group -vm DC0_C0_H{0,1} + assert_failure # -vm with HostSystem type args + + run govc cluster.group.create -cluster DC0_C0 -name my_vm_group -vm DC0_C0_RP0_VM{0,1} + assert_success + + run govc cluster.group.ls -cluster DC0_C0 + assert_success "my_vm_group" + + run govc cluster.group.create -cluster DC0_C0 -name my_vm_group -vm DC0_C0_RP0_VM{0,1} + assert_failure # group exists + + run govc cluster.group.ls -cluster DC0_C0 -name my_vm_group + assert_success "$(printf "%s\n" DC0_C0_RP0_VM{0,1})" + + run govc cluster.group.change -cluster DC0_C0 -name my_vm_group DC0_C0_RP0_VM{0,1,2} + assert_success + + run govc cluster.group.ls -cluster DC0_C0 -name my_vm_group + assert_success "$(printf "%s\n" DC0_C0_RP0_VM{0,1,2})" + + run govc cluster.group.create -cluster DC0_C0 -name my_host_group -host DC0_C0_RP0_VM{0,1} + assert_failure # -host with VirtualMachine type args + + run govc cluster.group.create -cluster DC0_C0 -name my_host_group -host DC0_C0_H{0,1} + assert_success + + run govc cluster.group.ls -cluster DC0_C0 -name my_host_group + assert_success + + run govc cluster.group.remove -cluster DC0_C0 -name my_vm_group + assert_success + + run govc cluster.group.remove -cluster DC0_C0 -name my_vm_group + assert_failure # group does not exist + + run govc cluster.group.ls -cluster DC0_C0 + assert_success "my_host_group" +} + +@test "cluster.rule" { + vcsim_env -cluster 2 -host 4 -vm 8 + + run govc cluster.rule.ls -cluster DC0_C0 + assert_success "" # no rules + + run govc object.collect -json /DC0/host/DC0_C0 configurationEx.rule + assert_success + + run govc cluster.rule.ls -cluster DC0_C0 -name pod1 + assert_failure # rule does not exist + + run govc cluster.rule.create -cluster DC0_C0 -name pod1 -affinity DC0_C0_RP0_VM0 + assert_failure # requires >= 2 VMs + + run govc cluster.rule.create -cluster DC0_C0 -name pod1 -affinity DC0_C0_RP0_VM{0,1,2,3} + assert_success + + run govc cluster.rule.ls -cluster DC0_C0 -name pod1 + assert_success "$(printf "%s\n" DC0_C0_RP0_VM{0,1,2,3})" + + run govc cluster.rule.change -cluster DC0_C0 -name pod1 DC0_C0_RP0_VM{2,3,4} + assert_success + + run govc cluster.rule.ls -cluster DC0_C0 -name pod1 + assert_success "$(printf "%s\n" DC0_C0_RP0_VM{2,3,4})" + + run govc object.collect -json /DC0/host/DC0_C0 configurationEx.rule + assert_success + + run govc cluster.group.create -cluster DC0_C0 -name my_vms -vm DC0_C0_RP0_VM{0,1,2,3} + assert_success + + run govc cluster.group.create -cluster DC0_C0 -name even_hosts -host DC0_C0_H{0,2} + assert_success + + run govc cluster.group.create -cluster DC0_C0 -name odd_hosts -host DC0_C0_H{1,3} + assert_success + + run govc cluster.rule.create -cluster DC0_C0 -name pod2 -enable -mandatory -vm-host -vm-group my_vms -host-affine-group even_hosts -host-anti-affine-group odd_hosts + assert_success + + run govc cluster.rule.remove -cluster DC0_C0 -name pod1 + assert_success + + run govc cluster.rule.remove -cluster DC0_C0 -name pod1 + assert_failure # rule does not exist +} diff --git a/object/cluster_compute_resource.go b/object/cluster_compute_resource.go index 225f41b6d..c9fe3aa03 100644 --- a/object/cluster_compute_resource.go +++ b/object/cluster_compute_resource.go @@ -21,6 +21,7 @@ import ( "github.com/vmware/govmomi/vim25" "github.com/vmware/govmomi/vim25/methods" + "github.com/vmware/govmomi/vim25/mo" "github.com/vmware/govmomi/vim25/types" ) @@ -34,19 +35,15 @@ func NewClusterComputeResource(c *vim25.Client, ref types.ManagedObjectReference } } -func (c ClusterComputeResource) ReconfigureCluster(ctx context.Context, spec types.ClusterConfigSpec) (*Task, error) { - req := types.ReconfigureCluster_Task{ - This: c.Reference(), - Spec: spec, - Modify: true, - } +func (c ClusterComputeResource) Configuration(ctx context.Context) (*types.ClusterConfigInfoEx, error) { + var obj mo.ClusterComputeResource - res, err := methods.ReconfigureCluster_Task(ctx, c.c, &req) + err := c.Properties(ctx, c.Reference(), []string{"configurationEx"}, &obj) if err != nil { return nil, err } - return NewTask(c.c, res.Returnval), nil + return obj.ConfigurationEx.(*types.ClusterConfigInfoEx), nil } func (c ClusterComputeResource) AddHost(ctx context.Context, spec types.HostConnectSpec, asConnected bool, license *string, resourcePool *types.ManagedObjectReference) (*Task, error) { @@ -71,16 +68,3 @@ func (c ClusterComputeResource) AddHost(ctx context.Context, spec types.HostConn return NewTask(c.c, res.Returnval), nil } - -func (c ClusterComputeResource) Destroy(ctx context.Context) (*Task, error) { - req := types.Destroy_Task{ - This: c.Reference(), - } - - res, err := methods.Destroy_Task(ctx, c.c, &req) - if err != nil { - return nil, err - } - - return NewTask(c.c, res.Returnval), nil -} diff --git a/object/compute_resource.go b/object/compute_resource.go index ac1c73019..7645fddaf 100644 --- a/object/compute_resource.go +++ b/object/compute_resource.go @@ -109,16 +109,3 @@ func (c ComputeResource) Reconfigure(ctx context.Context, spec types.BaseCompute return NewTask(c.c, res.Returnval), nil } - -func (c ComputeResource) Destroy(ctx context.Context) (*Task, error) { - req := types.Destroy_Task{ - This: c.Reference(), - } - - res, err := methods.Destroy_Task(ctx, c.c, &req) - if err != nil { - return nil, err - } - - return NewTask(c.c, res.Returnval), nil -} diff --git a/simulator/cluster_compute_resource.go b/simulator/cluster_compute_resource.go index fe51ae3e7..a4eca42eb 100644 --- a/simulator/cluster_compute_resource.go +++ b/simulator/cluster_compute_resource.go @@ -17,6 +17,9 @@ limitations under the License. package simulator import ( + "sync/atomic" + + "github.com/google/uuid" "github.com/vmware/govmomi/simulator/esx" "github.com/vmware/govmomi/vim25/methods" "github.com/vmware/govmomi/vim25/mo" @@ -26,6 +29,8 @@ import ( type ClusterComputeResource struct { mo.ClusterComputeResource + + ruleKey int32 } type addHost struct { @@ -68,6 +73,125 @@ func (c *ClusterComputeResource) AddHostTask(add *types.AddHost_Task) soap.HasFa } } +func (c *ClusterComputeResource) updateRules(cfg *types.ClusterConfigInfoEx, cspec *types.ClusterConfigSpecEx) types.BaseMethodFault { + for _, spec := range cspec.RulesSpec { + var i int + exists := false + + match := func(info types.BaseClusterRuleInfo) bool { + return info.GetClusterRuleInfo().Name == spec.Info.GetClusterRuleInfo().Name + } + + if spec.Operation == types.ArrayUpdateOperationRemove { + match = func(rule types.BaseClusterRuleInfo) bool { + return rule.GetClusterRuleInfo().Key == spec.ArrayUpdateSpec.RemoveKey.(int32) + } + } + + for i = range cfg.Rule { + if match(cfg.Rule[i].GetClusterRuleInfo()) { + exists = true + break + } + } + + switch spec.Operation { + case types.ArrayUpdateOperationAdd: + if exists { + return new(types.InvalidArgument) + } + info := spec.Info.GetClusterRuleInfo() + info.Key = atomic.AddInt32(&c.ruleKey, 1) + info.RuleUuid = uuid.New().String() + cfg.Rule = append(cfg.Rule, spec.Info) + case types.ArrayUpdateOperationEdit: + if !exists { + return new(types.InvalidArgument) + } + cfg.Rule[i] = spec.Info + case types.ArrayUpdateOperationRemove: + if !exists { + return new(types.InvalidArgument) + } + cfg.Rule = append(cfg.Rule[:i], cfg.Rule[i+1:]...) + } + } + + return nil +} + +func (c *ClusterComputeResource) updateGroups(cfg *types.ClusterConfigInfoEx, cspec *types.ClusterConfigSpecEx) types.BaseMethodFault { + for _, spec := range cspec.GroupSpec { + var i int + exists := false + + match := func(info types.BaseClusterGroupInfo) bool { + return info.GetClusterGroupInfo().Name == spec.Info.GetClusterGroupInfo().Name + } + + if spec.Operation == types.ArrayUpdateOperationRemove { + match = func(info types.BaseClusterGroupInfo) bool { + return info.GetClusterGroupInfo().Name == spec.ArrayUpdateSpec.RemoveKey.(string) + } + } + + for i = range cfg.Group { + if match(cfg.Group[i].GetClusterGroupInfo()) { + exists = true + break + } + } + + switch spec.Operation { + case types.ArrayUpdateOperationAdd: + if exists { + return new(types.InvalidArgument) + } + cfg.Group = append(cfg.Group, spec.Info) + case types.ArrayUpdateOperationEdit: + if !exists { + return new(types.InvalidArgument) + } + cfg.Group[i] = spec.Info + case types.ArrayUpdateOperationRemove: + if !exists { + return new(types.InvalidArgument) + } + cfg.Group = append(cfg.Group[:i], cfg.Group[i+1:]...) + } + } + + return nil +} + +func (c *ClusterComputeResource) ReconfigureComputeResourceTask(req *types.ReconfigureComputeResource_Task) soap.HasFault { + task := CreateTask(c, "reconfigureCluster", func(*Task) (types.AnyType, types.BaseMethodFault) { + spec, ok := req.Spec.(*types.ClusterConfigSpecEx) + if !ok { + return nil, new(types.InvalidArgument) + } + + updates := []func(*types.ClusterConfigInfoEx, *types.ClusterConfigSpecEx) types.BaseMethodFault{ + c.updateRules, + c.updateGroups, + } + + for _, update := range updates { + if err := update(c.ConfigurationEx.(*types.ClusterConfigInfoEx), spec); err != nil { + return nil, err + } + } + + return nil, nil + }) + + return &methods.ReconfigureComputeResource_TaskBody{ + Res: &types.ReconfigureComputeResource_TaskResponse{ + Returnval: task.Run(), + }, + } +} + func CreateClusterComputeResource(f *Folder, name string, spec types.ClusterConfigSpecEx) (*ClusterComputeResource, types.BaseMethodFault) { if e := Map.FindByName(name, f.ChildEntity); e != nil { return nil, &types.DuplicateName{ @@ -85,6 +209,7 @@ func CreateClusterComputeResource(f *Folder, name string, spec types.ClusterConf config := &types.ClusterConfigInfoEx{} cluster.ConfigurationEx = config + config.VmSwapPlacement = string(types.VirtualMachineConfigInfoSwapPlacementTypeVmDirectory) config.DrsConfig.Enabled = types.NewBool(true) pool := NewResourcePool() diff --git a/simulator/host_system.go b/simulator/host_system.go index 600dbcf38..2dd37f1ed 100644 --- a/simulator/host_system.go +++ b/simulator/host_system.go @@ -137,7 +137,12 @@ func CreateStandaloneHost(f *Folder, spec types.HostConnectSpec) (*HostSystem, t summary := new(types.ComputeResourceSummary) addComputeResource(summary, host) - cr := &mo.ComputeResource{Summary: summary} + cr := &mo.ComputeResource{ + ConfigurationEx: &types.ComputeResourceConfigInfo{ + VmSwapPlacement: string(types.VirtualMachineConfigInfoSwapPlacementTypeVmDirectory), + }, + Summary: summary, + } Map.PutEntity(cr, Map.NewEntity(host))