Skip to content

Commit

Permalink
✨ add VM naming strategy for VMs in govami mode
Browse files Browse the repository at this point in the history
- Add VM naming stragey for vsphere vms and VMs in govami mode
  • Loading branch information
viveksyngh committed Dec 8, 2024
1 parent 31fd96a commit 7ca274d
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 6 deletions.
1 change: 1 addition & 0 deletions apis/v1alpha3/vspheremachine_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func (src *VSphereMachine) ConvertTo(dstRaw conversion.Hub) error {
dst.Spec.TagIDs = restored.Spec.TagIDs
dst.Spec.PowerOffMode = restored.Spec.PowerOffMode
dst.Spec.GuestSoftPowerOffTimeout = restored.Spec.GuestSoftPowerOffTimeout
dst.Spec.NamingStrategy = restored.Spec.NamingStrategy
for i := range dst.Spec.Network.Devices {
dst.Spec.Network.Devices[i].AddressesFromPools = restored.Spec.Network.Devices[i].AddressesFromPools
dst.Spec.Network.Devices[i].DHCP4Overrides = restored.Spec.Network.Devices[i].DHCP4Overrides
Expand Down
1 change: 1 addition & 0 deletions apis/v1alpha3/vspheremachinetemplate_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func (src *VSphereMachineTemplate) ConvertTo(dstRaw conversion.Hub) error {
dst.Spec.Template.Spec.AdditionalDisksGiB = restored.Spec.Template.Spec.AdditionalDisksGiB
dst.Spec.Template.Spec.PowerOffMode = restored.Spec.Template.Spec.PowerOffMode
dst.Spec.Template.Spec.GuestSoftPowerOffTimeout = restored.Spec.Template.Spec.GuestSoftPowerOffTimeout
dst.Spec.Template.Spec.NamingStrategy = restored.Spec.Template.Spec.NamingStrategy
for i := range dst.Spec.Template.Spec.Network.Devices {
dst.Spec.Template.Spec.Network.Devices[i].AddressesFromPools = restored.Spec.Template.Spec.Network.Devices[i].AddressesFromPools
dst.Spec.Template.Spec.Network.Devices[i].DHCP4Overrides = restored.Spec.Template.Spec.Network.Devices[i].DHCP4Overrides
Expand Down
1 change: 1 addition & 0 deletions apis/v1alpha4/vspheremachine_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func (src *VSphereMachine) ConvertTo(dstRaw conversion.Hub) error {
dst.Spec.TagIDs = restored.Spec.TagIDs
dst.Spec.PowerOffMode = restored.Spec.PowerOffMode
dst.Spec.GuestSoftPowerOffTimeout = restored.Spec.GuestSoftPowerOffTimeout
dst.Spec.NamingStrategy = restored.Spec.NamingStrategy
for i := range dst.Spec.Network.Devices {
dst.Spec.Network.Devices[i].AddressesFromPools = restored.Spec.Network.Devices[i].AddressesFromPools
dst.Spec.Network.Devices[i].DHCP4Overrides = restored.Spec.Network.Devices[i].DHCP4Overrides
Expand Down
1 change: 1 addition & 0 deletions apis/v1alpha4/vspheremachinetemplate_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func (src *VSphereMachineTemplate) ConvertTo(dstRaw conversion.Hub) error {
dst.Spec.Template.Spec.AdditionalDisksGiB = restored.Spec.Template.Spec.AdditionalDisksGiB
dst.Spec.Template.Spec.PowerOffMode = restored.Spec.Template.Spec.PowerOffMode
dst.Spec.Template.Spec.GuestSoftPowerOffTimeout = restored.Spec.Template.Spec.GuestSoftPowerOffTimeout
dst.Spec.Template.Spec.NamingStrategy = restored.Spec.Template.Spec.NamingStrategy
for i := range dst.Spec.Template.Spec.Network.Devices {
dst.Spec.Template.Spec.Network.Devices[i].AddressesFromPools = restored.Spec.Template.Spec.Network.Devices[i].AddressesFromPools
dst.Spec.Template.Spec.Network.Devices[i].DHCP4Overrides = restored.Spec.Template.Spec.Network.Devices[i].DHCP4Overrides
Expand Down
24 changes: 24 additions & 0 deletions apis/v1beta1/vspheremachine_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,30 @@ type VSphereMachineSpec struct {
//
// +optional
GuestSoftPowerOffTimeout *metav1.Duration `json:"guestSoftPowerOffTimeout,omitempty"`

// NamingStrategy allows configuring the naming strategy used when calculating the name of the VirtualMachine.
// +optional
NamingStrategy *VirtualMachineNamingStrategy `json:"namingStrategy,omitempty"`
}

// VirtualMachineNamingStrategy defines the naming strategy for the VirtualMachines.
type VirtualMachineNamingStrategy struct {
// Template defines the template to use for generating the name of the VirtualMachine object.
// If not defined, it will fall back to `{{ .machine.name }}`.
// The templating has the following data available:
// * `.machine.name`: The name of the Machine object.
// The templating also has the following funcs available:
// * `trimSuffix`: same as strings.TrimSuffix
// * `trunc`: truncates a string, e.g. `trunc 2 "hello"` or `trunc -2 "hello"`
// Notes:
// * While the template offers some flexibility, we would like the name to link to the Machine name
// to ensure better user experience when troubleshooting
// * Generated names must be valid Kubernetes names as they are used to create a VirtualMachine object
// and usually also as the name of the Node object.
// * Names are automatically truncated at 63 characters. Please note that this can lead to name conflicts,
// so we highly recommend to use a template which leads to a name shorter than 63 characters.
// +optional
Template *string `json:"template,omitempty"`
}

// VSphereMachineStatus defines the observed state of VSphereMachine.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1026,6 +1026,28 @@ spec:
virtual machine is cloned.
format: int64
type: integer
namingStrategy:
description: NamingStrategy allows configuring the naming strategy
used when calculating the name of the VirtualMachine.
properties:
template:
description: |-
Template defines the template to use for generating the name of the VirtualMachine object.
If not defined, it will fall back to `{{ .machine.name }}`.
The templating has the following data available:
* `.machine.name`: The name of the Machine object.
The templating also has the following funcs available:
* `trimSuffix`: same as strings.TrimSuffix
* `trunc`: truncates a string, e.g. `trunc 2 "hello"` or `trunc -2 "hello"`
Notes:
* While the template offers some flexibility, we would like the name to link to the Machine name
to ensure better user experience when troubleshooting
* Generated names must be valid Kubernetes names as they are used to create a VirtualMachine object
and usually also as the name of the Node object.
* Names are automatically truncated at 63 characters. Please note that this can lead to name conflicts,
so we highly recommend to use a template which leads to a name shorter than 63 characters.
type: string
type: object
network:
description: Network is the network configuration for this machine's
VM.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -896,6 +896,28 @@ spec:
virtual machine is cloned.
format: int64
type: integer
namingStrategy:
description: NamingStrategy allows configuring the naming
strategy used when calculating the name of the VirtualMachine.
properties:
template:
description: |-
Template defines the template to use for generating the name of the VirtualMachine object.
If not defined, it will fall back to `{{ .machine.name }}`.
The templating has the following data available:
* `.machine.name`: The name of the Machine object.
The templating also has the following funcs available:
* `trimSuffix`: same as strings.TrimSuffix
* `trunc`: truncates a string, e.g. `trunc 2 "hello"` or `trunc -2 "hello"`
Notes:
* While the template offers some flexibility, we would like the name to link to the Machine name
to ensure better user experience when troubleshooting
* Generated names must be valid Kubernetes names as they are used to create a VirtualMachine object
and usually also as the name of the Node object.
* Names are automatically truncated at 63 characters. Please note that this can lead to name conflicts,
so we highly recommend to use a template which leads to a name shorter than 63 characters.
type: string
type: object
network:
description: Network is the network configuration for this
machine's VM.
Expand Down
89 changes: 83 additions & 6 deletions pkg/services/vimmachine.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ limitations under the License.
package services

import (
"bytes"
"context"
"encoding/json"
"strings"
"text/template"

"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -197,10 +199,15 @@ func (v *VimMachineService) GetHostInfo(ctx context.Context, machineCtx capvcont
return "", errors.New("received unexpected VIMMachineContext type")
}

name, err := generateVMObjectName(vimMachineCtx, vimMachineCtx.Machine.Name)
if err != nil {
return "", err
}

vsphereVM := &infrav1.VSphereVM{}
if err := v.Client.Get(ctx, client.ObjectKey{
Namespace: vimMachineCtx.VSphereMachine.Namespace,
Name: generateVMObjectName(vimMachineCtx, vimMachineCtx.Machine.Name),
Name: name,
}, vsphereVM); err != nil {
return "", err
}
Expand All @@ -215,9 +222,14 @@ func (v *VimMachineService) GetHostInfo(ctx context.Context, machineCtx capvcont
func (v *VimMachineService) findVSphereVM(ctx context.Context, vimMachineCtx *capvcontext.VIMMachineContext) (*infrav1.VSphereVM, error) {
// Get ready to find the associated VSphereVM resource.
vm := &infrav1.VSphereVM{}
name, err := generateVMObjectName(vimMachineCtx, vimMachineCtx.Machine.Name)
if err != nil {
return nil, err
}

vmKey := types.NamespacedName{
Namespace: vimMachineCtx.VSphereMachine.Namespace,
Name: generateVMObjectName(vimMachineCtx, vimMachineCtx.Machine.Name),
Name: name,
}
// Attempt to find the associated VSphereVM resource.
if err := v.Client.Get(ctx, vmKey, vm); err != nil {
Expand Down Expand Up @@ -301,10 +313,15 @@ func (v *VimMachineService) reconcileNetwork(ctx context.Context, vimMachineCtx
func (v *VimMachineService) createOrPatchVSphereVM(ctx context.Context, vimMachineCtx *capvcontext.VIMMachineContext, vsphereVM *infrav1.VSphereVM) (*infrav1.VSphereVM, error) {
log := ctrl.LoggerFrom(ctx)
// Create or update the VSphereVM resource.
name, err := generateVMObjectName(vimMachineCtx, vimMachineCtx.Machine.Name)
if err != nil {
return nil, err
}

vm := &infrav1.VSphereVM{
ObjectMeta: metav1.ObjectMeta{
Namespace: vimMachineCtx.VSphereMachine.Namespace,
Name: generateVMObjectName(vimMachineCtx, vimMachineCtx.Machine.Name),
Name: name,
},
}
mutateFn := func() (err error) {
Expand Down Expand Up @@ -393,12 +410,72 @@ func (v *VimMachineService) createOrPatchVSphereVM(ctx context.Context, vimMachi

// generateVMObjectName returns a new VM object name in specific cases, otherwise return the same
// passed in the parameter.
func generateVMObjectName(vimMachineCtx *capvcontext.VIMMachineContext, machineName string) string {
func generateVMObjectName(vimMachineCtx *capvcontext.VIMMachineContext, machineName string) (string, error) {
name, err := GenerateVirtualMachineName(machineName, vimMachineCtx.VSphereMachine.Spec.NamingStrategy)
if err != nil {
return "", err
}
// Windows VM names must have 15 characters length at max.
if vimMachineCtx.VSphereMachine.Spec.OS == infrav1.Windows && len(machineName) > 15 {
return strings.TrimSuffix(machineName[0:9], "-") + "-" + machineName[len(machineName)-5:]
return strings.TrimSuffix(name[0:9], "-") + "-" + name[len(name)-5:], nil
}
return machineName
return name, nil
}

const (
maxNameLength = 63
)

// Note: Inlining these functions from sprig to avoid introducing a dependency.
var nameTemplateFuncs = map[string]any{
"trimSuffix": func(a, b string) string { return strings.TrimSuffix(b, a) },
"trunc": func(c int, s string) string {
if c < 0 && len(s)+c > 0 {
return s[len(s)+c:]
}
if c >= 0 && len(s) > c {
return s[:c]
}
return s
},
}

var nameTpl = template.New("name generator").Funcs(nameTemplateFuncs).Option("missingkey=error")

// GenerateVirtualMachineName generates the name of a VirtualMachine based on the naming strategy.
func GenerateVirtualMachineName(machineName string, namingStrategy *infrav1.VirtualMachineNamingStrategy) (string, error) {
// Per default the name of the VirtualMachine should be equal to the Machine name (this is the same as "{{ .machine.name }}")
if namingStrategy == nil || namingStrategy.Template == nil {
// Note: No need to trim to max length in this case as valid Machine names will also be valid VirtualMachine names.
return machineName, nil
}

nameTemplate := *namingStrategy.Template
data := map[string]interface{}{
"machine": map[string]interface{}{
"name": machineName,
},
}

tpl, err := nameTpl.Parse(nameTemplate)
if err != nil {
return "", errors.Wrapf(err, "failed to generate name for VirtualMachine: failed to parse namingStrategy.template %q", nameTemplate)
}

var buf bytes.Buffer
if err := tpl.Execute(&buf, data); err != nil {
return "", errors.Wrap(err, "failed to generate name for VirtualMachine")
}

name := buf.String()

// If the name exceeds the maxNameLength, trim to maxNameLength.
// Note: we're not adding a random suffix as the name has to be deterministic.
if len(name) > maxNameLength {
name = name[:maxNameLength]
}

return name, nil
}

// generateOverrideFunc returns a function which can override the values in the VSphereVM Spec
Expand Down

0 comments on commit 7ca274d

Please sign in to comment.