Skip to content

Commit 3ead298

Browse files
vSphere: support for VM Groups (#1847)
* vSphere: support for VM Groups Signed-off-by: Waleed Malik <ahmedwaleedmalik@gmail.com> * Use break instead of continue to exit loop Signed-off-by: Waleed Malik <ahmedwaleedmalik@gmail.com> --------- Signed-off-by: Waleed Malik <ahmedwaleedmalik@gmail.com>
1 parent 107e351 commit 3ead298

File tree

8 files changed

+167
-21
lines changed

8 files changed

+167
-21
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@ examples/*.srl
1515
/vendor
1616
.vscode
1717
.gitpod.yml
18-
cmd/machine-controller/__debug_bin
18+
cmd/machine-controller/__debug_bin*
1919
!pkg

.golangci.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ issues:
5555
- func Convert_MachineDeployment_ProviderConfig_To_ProviderSpec should be ConvertMachineDeploymentProviderConfigToProviderSpec
5656
- func Convert_MachineSet_ProviderConfig_To_ProviderSpec should be ConvertMachineSetProviderConfigToProviderSpec
5757
- func Convert_Machine_ProviderConfig_To_ProviderSpec should be ConvertMachineProviderConfigToProviderSpec
58-
- 'cyclomatic complexity 33 of func `\(\*provider\)\.Create` is high'
58+
- 'cyclomatic complexity [0-9]+ of func `\(\*provider\)\.Create` is high'
59+
- 'cyclomatic complexity [0-9]+ of func `\(\*provider\)\.Validate` is high'
5960
- "SA1019: s.server.IPv6 is deprecated"
6061
exclude-dirs:
6162
- pkg/machines

examples/vsphere-machinedeployment.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ spec:
6262
cluster: cl-1
6363
# Automatically create anti affinity rules for machines
6464
vmAntiAffinity: true
65+
# Optional. Sets the VM group for the Machines in the MachineDeployment.
66+
# vmGroup: "vmgroup-name"
6567
cpus: 2
6668
memoryMB: 2048
6769
# Optional: Resize the root disk to this size. Must be bigger than the existing size

go.mod

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ require (
4040
github.com/spf13/pflag v1.0.5
4141
github.com/tinkerbell/tink v0.10.0
4242
github.com/vmware/go-vcloud-director/v2 v2.25.0
43-
github.com/vmware/govmomi v0.38.0
43+
github.com/vmware/govmomi v0.42.0
4444
github.com/vultr/govultr/v3 v3.9.0
4545
go.anx.io/go-anxcloud v0.7.2
4646
go.uber.org/zap v1.27.0
@@ -154,10 +154,10 @@ require (
154154
go.uber.org/multierr v1.11.0 // indirect
155155
golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect
156156
golang.org/x/net v0.27.0 // indirect
157-
golang.org/x/sync v0.7.0 // indirect
157+
golang.org/x/sync v0.8.0 // indirect
158158
golang.org/x/sys v0.22.0 // indirect
159159
golang.org/x/term v0.22.0 // indirect
160-
golang.org/x/text v0.16.0 // indirect
160+
golang.org/x/text v0.17.0 // indirect
161161
golang.org/x/time v0.5.0 // indirect
162162
golang.org/x/tools v0.23.0 // indirect
163163
google.golang.org/genproto v0.0.0-20240701130421-f6361c86f094 // indirect

go.sum

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -452,8 +452,8 @@ github.com/vektah/gqlparser/v2 v2.2.0 h1:bAc3slekAAJW6sZTi07aGq0OrfaCjj4jxARAaC7
452452
github.com/vektah/gqlparser/v2 v2.2.0/go.mod h1:i3mQIGIrbK2PD1RrCeMTlVbkF2FJ6WkU1KJlJlC+3F4=
453453
github.com/vmware/go-vcloud-director/v2 v2.25.0 h1:RcJ5FQRku3FvQktTi8YOZsRfvhfLm315Cme50M9x9MQ=
454454
github.com/vmware/go-vcloud-director/v2 v2.25.0/go.mod h1:7Of1qJja+LLNKVegjZG7uuhhy6xgGg3q7Fkw2CEP+Tw=
455-
github.com/vmware/govmomi v0.38.0 h1:UvQpLAOjDpO0JUxoPCXnEzOlEa/9kejO6K58qOFr6cM=
456-
github.com/vmware/govmomi v0.38.0/go.mod h1:mtGWtM+YhTADHlCgJBiskSRPOZRsN9MSjPzaZLte/oQ=
455+
github.com/vmware/govmomi v0.42.0 h1:MbvAlVfjNBE1mHMaQ7yOSop1KLB0/93x6VAGuCtjqtI=
456+
github.com/vmware/govmomi v0.42.0/go.mod h1:1H5LWwsBif8HKZqbFp0FdoKTHyJE4FzL6ACequMKYQg=
457457
github.com/vultr/govultr/v3 v3.9.0 h1:63V/22mpfquRA5DenJ9EF0VozHg0k+X4dhUWcDXHPyc=
458458
github.com/vultr/govultr/v3 v3.9.0/go.mod h1:Rd8ebpXm7jxH3MDmhnEs+zrlYW212ouhx+HeUMfHm2o=
459459
github.com/wI2L/jsondiff v0.2.0 h1:dE00WemBa1uCjrzQUUTE/17I6m5qAaN0EMFOg2Ynr/k=
@@ -580,8 +580,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
580580
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
581581
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
582582
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
583-
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
584-
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
583+
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
584+
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
585585
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
586586
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
587587
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -634,8 +634,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
634634
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
635635
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
636636
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
637-
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
638-
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
637+
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
638+
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
639639
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
640640
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
641641
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

pkg/cloudprovider/provider/vsphere/provider.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ type Config struct {
7575
MemoryMB int64
7676
DiskSizeGB *int64
7777
Tags []tags.Tag
78+
VMGroup string
7879
}
7980

8081
// Ensures that Server implements Instance interface.
@@ -210,6 +211,11 @@ func (p *provider) getConfig(provSpec clusterv1alpha1.ProviderSpec) (*Config, *p
210211
return nil, nil, nil, err
211212
}
212213

214+
c.VMGroup, err = p.configVarResolver.GetConfigVarStringValue(rawConfig.VMGroup)
215+
if err != nil {
216+
return nil, nil, nil, err
217+
}
218+
213219
c.CPUs = rawConfig.CPUs
214220
c.MemoryMB = rawConfig.MemoryMB
215221
c.DiskSizeGB = rawConfig.DiskSizeGB
@@ -311,10 +317,13 @@ func (p *provider) Validate(ctx context.Context, log *zap.SugaredLogger, spec cl
311317
}
312318
}
313319

314-
if config.VMAntiAffinity {
315-
if config.Cluster == "" {
316-
return fmt.Errorf("cluster is required for vm anti affinity")
317-
}
320+
if config.VMAntiAffinity && config.Cluster == "" {
321+
return fmt.Errorf("cluster is required for vm anti affinity")
322+
} else if config.VMGroup != "" && config.Cluster == "" {
323+
return fmt.Errorf("cluster is required for vm group")
324+
}
325+
326+
if config.Cluster != "" {
318327
_, err = session.Finder.ClusterComputeResource(ctx, config.Cluster)
319328
if err != nil {
320329
return fmt.Errorf("failed to get cluster %q, %w", config.Cluster, err)
@@ -376,6 +385,12 @@ func (p *provider) create(ctx context.Context, log *zap.SugaredLogger, machine *
376385
return nil, fmt.Errorf("failed to attach tags: %w", err)
377386
}
378387

388+
if config.VMGroup != "" {
389+
if err := p.addToVMGroup(ctx, log, session, machine, config); err != nil {
390+
return nil, fmt.Errorf("failed to add VM to VM group: %w", err)
391+
}
392+
}
393+
379394
if config.VMAntiAffinity {
380395
if err := p.createOrUpdateVMAntiAffinityRule(ctx, log, session, machine, config); err != nil {
381396
return nil, fmt.Errorf("failed to add VM to anti affinity rule: %w", err)

pkg/cloudprovider/provider/vsphere/types/types.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,15 @@ type RawConfig struct {
4343
DatastoreCluster providerconfigtypes.ConfigVarString `json:"datastoreCluster"`
4444
Datastore providerconfigtypes.ConfigVarString `json:"datastore"`
4545

46-
CPUs int32 `json:"cpus"`
47-
MemoryMB int64 `json:"memoryMB"`
48-
DiskSizeGB *int64 `json:"diskSizeGB,omitempty"`
49-
Tags []Tag `json:"tags,omitempty"`
50-
AllowInsecure providerconfigtypes.ConfigVarBool `json:"allowInsecure"`
51-
VMAntiAffinity providerconfigtypes.ConfigVarBool `json:"vmAntiAffinity"`
46+
CPUs int32 `json:"cpus"`
47+
MemoryMB int64 `json:"memoryMB"`
48+
DiskSizeGB *int64 `json:"diskSizeGB,omitempty"`
49+
Tags []Tag `json:"tags,omitempty"`
50+
AllowInsecure providerconfigtypes.ConfigVarBool `json:"allowInsecure"`
51+
52+
// Placement rules
53+
VMAntiAffinity providerconfigtypes.ConfigVarBool `json:"vmAntiAffinity"`
54+
VMGroup providerconfigtypes.ConfigVarString `json:"vmGroup,omitempty"`
5255
}
5356

5457
// Tag represents vsphere tag.
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
Copyright 2024 The Machine Controller Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package vsphere
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"strings"
23+
24+
clusterv1alpha1 "github.com/kubermatic/machine-controller/pkg/apis/cluster/v1alpha1"
25+
26+
"github.com/vmware/govmomi/vim25/types"
27+
"go.uber.org/zap"
28+
)
29+
30+
func (p *provider) addToVMGroup(ctx context.Context, log *zap.SugaredLogger, session *Session, machine *clusterv1alpha1.Machine, config *Config) error {
31+
lock.Lock()
32+
defer lock.Unlock()
33+
34+
// Check if the VM group exists
35+
vmGroup, err := findVMGroup(ctx, session, config.Cluster, config.VMGroup)
36+
if err != nil {
37+
return err
38+
}
39+
40+
// We have to find all VMs in the folder and add them to the VM group. VMGroup only contains VM reference ID which is not enough to
41+
// identify the VM by name.
42+
machineSetName := machine.Name[:strings.LastIndex(machine.Name, "-")]
43+
vmsInFolder, err := session.Finder.VirtualMachineList(ctx, strings.Join([]string{config.Folder, "*"}, "/"))
44+
if err != nil {
45+
return fmt.Errorf("failed to find VMs in folder: %w", err)
46+
}
47+
48+
var vmRefs []types.ManagedObjectReference
49+
for _, vm := range vmsInFolder {
50+
// Only add VMs with the same machineSetName to the rule and exclude the machine itself if it is being deleted
51+
if strings.HasPrefix(vm.Name(), machineSetName) && !(vm.Name() == machine.Name && machine.DeletionTimestamp != nil) {
52+
vmRefs = append(vmRefs, vm.Reference())
53+
}
54+
}
55+
56+
var vmRefsToAdd []types.ManagedObjectReference
57+
for _, vm := range vmRefs {
58+
found := false
59+
for _, existingVM := range vmGroup.Vm {
60+
if existingVM.Value == vm.Value {
61+
log.Debugf("VM %s already in VM group %s", machine.Name, config.VMGroup)
62+
found = true
63+
break
64+
}
65+
}
66+
if !found {
67+
vmRefsToAdd = append(vmRefsToAdd, vm)
68+
}
69+
}
70+
71+
// Add the VM to the VM group
72+
vmGroup.Vm = append(vmGroup.Vm, vmRefsToAdd...)
73+
cluster, err := session.Finder.ClusterComputeResource(ctx, config.Cluster)
74+
if err != nil {
75+
return err
76+
}
77+
78+
spec := &types.ClusterConfigSpecEx{
79+
GroupSpec: []types.ClusterGroupSpec{
80+
{
81+
ArrayUpdateSpec: types.ArrayUpdateSpec{
82+
Operation: types.ArrayUpdateOperationEdit,
83+
},
84+
Info: vmGroup,
85+
},
86+
},
87+
}
88+
89+
log.Debugf("Adding VM %s in VM group %s", machine.Name, config.VMGroup)
90+
task, err := cluster.Reconfigure(ctx, spec, true)
91+
if err != nil {
92+
return err
93+
}
94+
95+
taskResult, err := task.WaitForResultEx(ctx)
96+
if err != nil {
97+
return fmt.Errorf("error waiting for cluster %v reconfiguration to complete", cluster.Name())
98+
}
99+
if taskResult.State != types.TaskInfoStateSuccess {
100+
return fmt.Errorf("cluster %v reconfiguration task was not successful", cluster.Name())
101+
}
102+
log.Debugf("Successfully added VM %s in VM group %s", machine.Name, config.VMGroup)
103+
return nil
104+
}
105+
106+
func findVMGroup(ctx context.Context, session *Session, clusterName, vmGroup string) (*types.ClusterVmGroup, error) {
107+
cluster, err := session.Finder.ClusterComputeResource(ctx, clusterName)
108+
if err != nil {
109+
return nil, err
110+
}
111+
112+
clusterConfigInfoEx, err := cluster.Configuration(ctx)
113+
if err != nil {
114+
return nil, err
115+
}
116+
117+
for _, group := range clusterConfigInfoEx.Group {
118+
if clusterVMGroup, ok := group.(*types.ClusterVmGroup); ok {
119+
if clusterVMGroup.Name == vmGroup {
120+
return clusterVMGroup, nil
121+
}
122+
}
123+
}
124+
return nil, fmt.Errorf("cannot find VM group %s", vmGroup)
125+
}

0 commit comments

Comments
 (0)