Skip to content

Commit

Permalink
Add PVC name templates
Browse files Browse the repository at this point in the history
Signed-off-by: yaacov <[email protected]>
  • Loading branch information
yaacov committed Jan 30, 2025
1 parent 0429598 commit 0d0ad2e
Show file tree
Hide file tree
Showing 8 changed files with 358 additions and 11 deletions.
14 changes: 14 additions & 0 deletions operator/config/crd/bases/forklift.konveyor.io_migrations.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,20 @@ spec:
- progress
type: object
type: array
pvcNameTemplate:
description: |-
PVCNameTemplate is a template for generating PVC names for VM disks.
It follows Go template syntax and has access to the following variables:
- .VmName: name of the VM
- .PlanName: name of the migration plan
- .DiskIndex: initial volume index of the disk
- .RootDiskIndex: index of the root disk
Note:
This template overrides the plan level template.
Examples:
"{{.VmName}}-disk-{{.DiskIndex}}"
"{{if eq .DiskIndex .RootDiskIndex}}root{{else}}data{{end}}-{{.DiskIndex}}"
type: string
restorePowerState:
description: Source VM power state before migration.
type: string
Expand Down
42 changes: 42 additions & 0 deletions operator/config/crd/bases/forklift.konveyor.io_plans.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,20 @@ spec:
- destination
- source
type: object
pvcNameTemplate:
description: |-
PVCNameTemplate is a template for generating PVC names for VM disks.
It follows Go template syntax and has access to the following variables:
- .VmName: name of the VM
- .PlanName: name of the migration plan
- .DiskIndex: initial volume index of the disk
- .RootDiskIndex: index of the root disk
Note:
This template can be overridden at the individual VM level.
Examples:
"{{.VmName}}-disk-{{.DiskIndex}}"
"{{if eq .DiskIndex .RootDiskIndex}}root{{else}}data{{end}}-{{.DiskIndex}}"
type: string
targetNamespace:
description: Target namespace.
type: string
Expand Down Expand Up @@ -432,6 +446,20 @@ spec:
The VM Namespace
Only relevant for an openshift source.
type: string
pvcNameTemplate:
description: |-
PVCNameTemplate is a template for generating PVC names for VM disks.
It follows Go template syntax and has access to the following variables:
- .VmName: name of the VM
- .PlanName: name of the migration plan
- .DiskIndex: initial volume index of the disk
- .RootDiskIndex: index of the root disk
Note:
This template overrides the plan level template.
Examples:
"{{.VmName}}-disk-{{.DiskIndex}}"
"{{if eq .DiskIndex .RootDiskIndex}}root{{else}}data{{end}}-{{.DiskIndex}}"
type: string
rootDisk:
description: Choose the primary disk the VM boots from
type: string
Expand Down Expand Up @@ -1024,6 +1052,20 @@ spec:
- progress
type: object
type: array
pvcNameTemplate:
description: |-
PVCNameTemplate is a template for generating PVC names for VM disks.
It follows Go template syntax and has access to the following variables:
- .VmName: name of the VM
- .PlanName: name of the migration plan
- .DiskIndex: initial volume index of the disk
- .RootDiskIndex: index of the root disk
Note:
This template overrides the plan level template.
Examples:
"{{.VmName}}-disk-{{.DiskIndex}}"
"{{if eq .DiskIndex .RootDiskIndex}}root{{else}}data{{end}}-{{.DiskIndex}}"
type: string
restorePowerState:
description: Source VM power state before migration.
type: string
Expand Down
21 changes: 21 additions & 0 deletions pkg/apis/forklift/v1beta1/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,19 @@ type PlanSpec struct {
PreserveClusterCPUModel bool `json:"preserveClusterCpuModel,omitempty"`
// Preserve static IPs of VMs in vSphere
PreserveStaticIPs bool `json:"preserveStaticIPs,omitempty"`
// PVCNameTemplate is a template for generating PVC names for VM disks.
// It follows Go template syntax and has access to the following variables:
// - .VmName: name of the VM
// - .PlanName: name of the migration plan
// - .DiskIndex: initial volume index of the disk
// - .RootDiskIndex: index of the root disk
// Note:
// This template can be overridden at the individual VM level.
// Examples:
// "{{.VmName}}-disk-{{.DiskIndex}}"
// "{{if eq .DiskIndex .RootDiskIndex}}root{{else}}data{{end}}-{{.DiskIndex}}"
// +optional
PVCNameTemplate string `json:"pvcNameTemplate,omitempty"`
}

// Find a planned VM.
Expand Down Expand Up @@ -141,3 +154,11 @@ func (r *Plan) IsSourceProviderOCP() bool {
}

func (r *Plan) IsSourceProviderVSphere() bool { return r.Provider.Source.Type() == VSphere }

// PVCNameTemplateData contains fields used in naming templates.
type PVCNameTemplateData struct {
VmName string `json:"vmName"`
PlanName string `json:"planName"`
DiskIndex int `json:"diskIndex"`
RootDiskIndex int `json:"rootDiskIndex"`
}
13 changes: 13 additions & 0 deletions pkg/apis/forklift/v1beta1/plan/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,19 @@ type VM struct {
// Selected InstanceType that will override the VM properties.
// +optional
InstanceType string `json:"instanceType,omitempty"`
// PVCNameTemplate is a template for generating PVC names for VM disks.
// It follows Go template syntax and has access to the following variables:
// - .VmName: name of the VM
// - .PlanName: name of the migration plan
// - .DiskIndex: initial volume index of the disk
// - .RootDiskIndex: index of the root disk
// Note:
// This template overrides the plan level template.
// Examples:
// "{{.VmName}}-disk-{{.DiskIndex}}"
// "{{if eq .DiskIndex .RootDiskIndex}}root{{else}}data{{end}}-{{.DiskIndex}}"
// +optional
PVCNameTemplate string `json:"pvcNameTemplate,omitempty"`
}

// Find a Hook for the specified step.
Expand Down
15 changes: 15 additions & 0 deletions pkg/apis/forklift/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

80 changes: 79 additions & 1 deletion pkg/controller/plan/adapter/vsphere/builder.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package vsphere

import (
"bytes"
"context"
"errors"
"fmt"
Expand All @@ -11,6 +12,7 @@ import (
"regexp"
"sort"
"strings"
"text/template"

api "github.com/konveyor/forklift-controller/pkg/apis/forklift/v1beta1"
"github.com/konveyor/forklift-controller/pkg/apis/forklift/v1beta1/plan"
Expand Down Expand Up @@ -390,6 +392,71 @@ func (r *Builder) Secret(vmRef ref.Ref, in, object *core.Secret) (err error) {
return
}

// Get the plan VM for the given vsphere VM
func (r *Builder) getPlanVM(vm *model.VM) *plan.VM {
for _, planVM := range r.Plan.Spec.VMs {
if planVM.ID == vm.ID {
return &planVM
}
}

return nil
}

// GetPVCNameTemplate returns the PVC name template
func (r *Builder) getPVCNameTemplate(vm *model.VM) string {
// Get plan VM
planVM := r.getPlanVM(vm)
if planVM == nil {
return ""
}

// if vm.PVCNameTemplate is set, use it
if planVM.PVCNameTemplate != "" {
return planVM.PVCNameTemplate
}

// if planSpec.PVCNameTemplate is set, use it
if r.Plan.Spec.PVCNameTemplate != "" {
return r.Plan.Spec.PVCNameTemplate
}

return ""
}

func (r *Builder) getGeneratePVCName(pvcNameTemplate string, vm *model.VM, diskIndex int) (string, error) {
var buf bytes.Buffer

// Get plan VM
planVM := r.getPlanVM(vm)
if planVM == nil {
return "", errors.New("plan VM not found")
}
rootDisk := planVM.RootDisk

// Create template data
templateData := api.PVCNameTemplateData{
VmName: vm.Name,
PlanName: r.Plan.Name,
DiskIndex: diskIndex,
RootDiskIndex: utils.GetDeviceNumber(rootDisk),
}

// Parse template syntax
tmpl, err := template.New("pvcname").Parse(pvcNameTemplate)
if err != nil {
return "", err
}

// Execute template
err = tmpl.Execute(&buf, templateData)
if err != nil {
return "", err
}

return buf.String(), nil
}

// Create DataVolume specs for the VM.
func (r *Builder) DataVolumes(vmRef ref.Ref, secret *core.Secret, _ *core.ConfigMap, dvTemplate *cdi.DataVolume) (dvs []cdi.DataVolume, err error) {
vm := &model.VM{}
Expand Down Expand Up @@ -430,7 +497,7 @@ func (r *Builder) DataVolumes(vmRef ref.Ref, secret *core.Secret, _ *core.Config
err = fErr
return
}
for _, disk := range vm.Disks {
for diskIndex, disk := range vm.Disks {
if disk.Datastore.ID == ds.ID {
storageClass := mapped.Destination.StorageClass
var dvSource cdi.DataVolumeSource
Expand Down Expand Up @@ -483,6 +550,17 @@ func (r *Builder) DataVolumes(vmRef ref.Ref, secret *core.Secret, _ *core.Config
dv.ObjectMeta.Annotations = make(map[string]string)
}
dv.ObjectMeta.Annotations[planbase.AnnDiskSource] = r.baseVolume(disk.File)

// if exists, get the PVC generate name from the PlanSpec, generate the name
// and update the GenerateName field in the DataVolume object.
pvcNameTemplate := r.getPVCNameTemplate(vm)
if pvcNameTemplate != "" {
generatedName, err := r.getGeneratePVCName(pvcNameTemplate, vm, diskIndex)
if err == nil && generatedName != "" {
dv.ObjectMeta.GenerateName = generatedName
}
}

dvs = append(dvs, *dv)
}
}
Expand Down
79 changes: 79 additions & 0 deletions pkg/controller/plan/validation.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package plan

import (
"bytes"
"context"
"crypto/md5"
"encoding/hex"
Expand All @@ -9,6 +10,7 @@ import (
"net"
"path"
"strconv"
"text/template"

k8snet "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
api "github.com/konveyor/forklift-controller/pkg/apis/forklift/v1beta1"
Expand Down Expand Up @@ -164,6 +166,27 @@ func (r *Reconciler) validate(plan *api.Plan) error {
return err
}

// Validate PVC name template
if err := r.validatePVCNameTemplate(plan); err != nil {
return err
}

return nil
}

func (r *Reconciler) validatePVCNameTemplate(plan *api.Plan) error {
if err := r.IsValidPVCNameTemplate(plan.Spec.PVCNameTemplate); err != nil {
invalidPVCNameTemplate := libcnd.Condition{
Type: NotValid,
Status: True,
Category: Critical,
Message: "PVC name template is invalid.",
Items: []string{},
}

plan.Status.SetCondition(invalidPVCNameTemplate)
}

return nil
}

Expand Down Expand Up @@ -454,6 +477,13 @@ func (r *Reconciler) validateVM(plan *api.Plan) error {
Message: "Changed Block Tracking (CBT) has not been enabled on some VM. This feature is a prerequisite for VM warm migration.",
Items: []string{},
}
pvcNameInvalid := libcnd.Condition{
Type: NotValid,
Status: True,
Category: Critical,
Message: "VM PVC name template is invalid.",
Items: []string{},
}

setOf := map[string]bool{}
//
Expand Down Expand Up @@ -589,6 +619,12 @@ func (r *Reconciler) validateVM(plan *api.Plan) error {
missingCbtForWarm.Items = append(missingCbtForWarm.Items, ref.String())
}
}
// is valid vm pvc name template
if plan.Spec.VMs[i].PVCNameTemplate != "" {
if err := r.IsValidPVCNameTemplate(plan.Spec.VMs[i].PVCNameTemplate); err != nil {
pvcNameInvalid.Items = append(pvcNameInvalid.Items, ref.String())
}
}
}
if len(notFound.Items) > 0 {
plan.Status.SetCondition(notFound)
Expand Down Expand Up @@ -626,6 +662,9 @@ func (r *Reconciler) validateVM(plan *api.Plan) error {
if len(missingCbtForWarm.Items) > 0 {
plan.Status.SetCondition(missingCbtForWarm)
}
if len(pvcNameInvalid.Items) > 0 {
plan.Status.SetCondition(pvcNameInvalid)
}

return nil
}
Expand Down Expand Up @@ -1136,3 +1175,43 @@ func (r *Reconciler) checkOCPVersion(clientset kubernetes.Interface) error {

return nil
}

func (r *Reconciler) IsValidPVCNameTemplate(pvcNameTemplate string) error {
if pvcNameTemplate == "" {
return nil
}

// Validate golang template syntax
tmpl, err := template.New("pvcname").Parse(pvcNameTemplate)
if err != nil {
return liberr.Wrap(err, "Invalid template syntax")
}

// Test template with sample data
testData := api.PVCNameTemplateData{
VmName: "test-vm",
PlanName: "test-plan",
DiskIndex: 0,
RootDiskIndex: 0,
}

var buf bytes.Buffer
err = tmpl.Execute(&buf, testData)
if err != nil {
return liberr.Wrap(err, "Template execution failed")
}
result := buf.String()

// Empty output is not valid
if result == "" {
return liberr.New("Template output is empty")
}

// Validate that template output is a valid k8s label
errs := k8svalidation.IsValidLabelValue(result)
if len(errs) > 0 {
return liberr.New("Template output is not a valid k8s label", "errors", errs)
}

return nil
}
Loading

0 comments on commit 0d0ad2e

Please sign in to comment.