Skip to content

Commit

Permalink
✨ Persist PVC related info in VM ExtraConfig for backup (#257)
Browse files Browse the repository at this point in the history
This PR stores a VM's disk UUID to PVC mapping info in ExtraConfig during the backup process. This will enable us to register the volumes of the VM with the same PVC name and access modes during the restore. The change has also been added to the v1a2 package.

A helper function is added in vmprovider_vm_utils.go to retrieve the disk UUID to PVC mapping. This eliminates the need to pass a K8s client in the backup.go file. Also, the BackupVirtualMachine function is updated to have all the necessary params from the new BackupOptions struct.
  • Loading branch information
dilyar85 authored Oct 31, 2023
1 parent 98bfbf7 commit a4d06bd
Show file tree
Hide file tree
Showing 11 changed files with 567 additions and 123 deletions.
38 changes: 38 additions & 0 deletions pkg/context/backupvirtualmachine_context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) 2023 VMware, Inc. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package context

import (
"fmt"

corev1 "k8s.io/api/core/v1"

"github.com/vmware/govmomi/object"
)

// BackupVirtualMachineContext is the context used for storing backup data of VM
// and its related objects.
type BackupVirtualMachineContext struct {
VMCtx VirtualMachineContext
VcVM *object.VirtualMachine
BootstrapData map[string]string
DiskUUIDToPVC map[string]corev1.PersistentVolumeClaim
}

func (c *BackupVirtualMachineContext) String() string {
return fmt.Sprintf("Backup %s", c.VMCtx.String())
}

// BackupVirtualMachineContextA2 is the context used for storing backup data of
// VM and its related objects.
type BackupVirtualMachineContextA2 struct {
VMCtx VirtualMachineContextA2
VcVM *object.VirtualMachine
BootstrapData map[string]string
DiskUUIDToPVC map[string]corev1.PersistentVolumeClaim
}

func (c *BackupVirtualMachineContextA2) String() string {
return fmt.Sprintf("Backup %s", c.VMCtx.String())
}
82 changes: 43 additions & 39 deletions pkg/vmprovider/providers/vsphere/virtualmachine/backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@
package virtualmachine

import (
goctx "context"
"encoding/json"

corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/yaml"

"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"

Expand All @@ -20,80 +19,83 @@ import (
)

type VMDiskData struct {
// ID of the virtual disk object (only set for FCDs).
VDiskID string
// Filename contains the datastore path to the virtual disk.
FileName string
// PVCName is the name of the PVC backed by the virtual disk.
PVCName string
// AccessMode is the access modes of the PVC backed by the virtual disk.
AccessModes []corev1.PersistentVolumeAccessMode
}

// BackupVirtualMachine backs up the required data of a VM into its ExtraConfig.
// Currently, the following data is backed up:
// - Kubernetes VirtualMachine object in YAML format (without its .status field).
// - VM bootstrap data in JSON (if provided).
// - List of VM disk data in JSON (including FCDs attached to the VM).
func BackupVirtualMachine(
vmCtx context.VirtualMachineContext,
vcVM *object.VirtualMachine,
bootstrapData map[string]string) error {
// - VM disk data in JSON (if created and attached by PVCs).
func BackupVirtualMachine(ctx context.BackupVirtualMachineContext) error {
var moVM mo.VirtualMachine
if err := vcVM.Properties(vmCtx, vcVM.Reference(),
if err := ctx.VcVM.Properties(ctx.VMCtx, ctx.VcVM.Reference(),
[]string{"config.extraConfig"}, &moVM); err != nil {
vmCtx.Logger.Error(err, "Failed to get VM properties for backup")
ctx.VMCtx.Logger.Error(err, "Failed to get VM properties for backup")
return err
}
curEcMap := util.ExtraConfigToMap(moVM.Config.ExtraConfig)

var ecToUpdate []types.BaseOptionValue

vmKubeDataBackup, err := getDesiredVMKubeDataForBackup(vmCtx.VM, curEcMap)
vmKubeDataBackup, err := getDesiredVMKubeDataForBackup(ctx.VMCtx.VM, curEcMap)
if err != nil {
vmCtx.Logger.Error(err, "Failed to get VM kube data for backup")
ctx.VMCtx.Logger.Error(err, "Failed to get VM kube data for backup")
return err
}

if vmKubeDataBackup == "" {
vmCtx.Logger.V(4).Info("Skipping VM kube data backup as unchanged")
ctx.VMCtx.Logger.V(4).Info("Skipping VM kube data backup as unchanged")
} else {
ecToUpdate = append(ecToUpdate, &types.OptionValue{
Key: constants.BackupVMKubeDataExtraConfigKey,
Value: vmKubeDataBackup,
})
}

instanceIDBackup, err := getDesiredCloudInitInstanceIDForBackup(vmCtx.VM, curEcMap)
instanceIDBackup, err := getDesiredCloudInitInstanceIDForBackup(ctx.VMCtx.VM, curEcMap)
if err != nil {
vmCtx.Logger.Error(err, "Failed to get cloud-init instance ID for backup")
ctx.VMCtx.Logger.Error(err, "Failed to get cloud-init instance ID for backup")
return err
}

if instanceIDBackup == "" {
vmCtx.Logger.V(4).Info("Skipping cloud-init instance ID as already stored")
ctx.VMCtx.Logger.V(4).Info("Skipping cloud-init instance ID as already stored")
} else {
ecToUpdate = append(ecToUpdate, &types.OptionValue{
Key: constants.BackupVMCloudInitInstanceIDExtraConfigKey,
Value: instanceIDBackup,
})
}

bootstrapDataBackup, err := getDesiredBootstrapDataForBackup(bootstrapData, curEcMap)
bootstrapDataBackup, err := getDesiredBootstrapDataForBackup(ctx.BootstrapData, curEcMap)
if err != nil {
vmCtx.Logger.Error(err, "Failed to get VM bootstrap data for backup")
ctx.VMCtx.Logger.Error(err, "Failed to get VM bootstrap data for backup")
return err
}

if bootstrapDataBackup == "" {
vmCtx.Logger.V(4).Info("Skipping VM bootstrap data backup as unchanged")
ctx.VMCtx.Logger.V(4).Info("Skipping VM bootstrap data backup as unchanged")
} else {
ecToUpdate = append(ecToUpdate, &types.OptionValue{
Key: constants.BackupVMBootstrapDataExtraConfigKey,
Value: bootstrapDataBackup,
})
}

diskDataBackup, err := getDesiredDiskDataForBackup(vmCtx, vcVM, curEcMap)
diskDataBackup, err := getDesiredDiskDataForBackup(ctx, curEcMap)
if err != nil {
vmCtx.Logger.Error(err, "Failed to get VM disk data for backup")
ctx.VMCtx.Logger.Error(err, "Failed to get VM disk data for backup")
return err
}

if diskDataBackup == "" {
vmCtx.Logger.V(4).Info("Skipping VM disk data backup as unchanged")
ctx.VMCtx.Logger.V(4).Info("Skipping VM disk data backup as unchanged")
} else {
ecToUpdate = append(ecToUpdate, &types.OptionValue{
Key: constants.BackupVMDiskDataExtraConfigKey,
Expand All @@ -102,12 +104,12 @@ func BackupVirtualMachine(
}

if len(ecToUpdate) != 0 {
vmCtx.Logger.Info("Updating VM ExtraConfig with backup data")
vmCtx.Logger.V(4).Info("", "ExtraConfig", ecToUpdate)
if _, err := vcVM.Reconfigure(vmCtx, types.VirtualMachineConfigSpec{
ctx.VMCtx.Logger.Info("Updating VM ExtraConfig with backup data")
ctx.VMCtx.Logger.V(4).Info("", "ExtraConfig", ecToUpdate)
if _, err := ctx.VcVM.Reconfigure(ctx.VMCtx, types.VirtualMachineConfigSpec{
ExtraConfig: ecToUpdate,
}); err != nil {
vmCtx.Logger.Error(err, "Failed to update VM ExtraConfig for backup")
ctx.VMCtx.Logger.Error(err, "Failed to update VM ExtraConfig for backup")
return err
}
}
Expand Down Expand Up @@ -194,27 +196,29 @@ func getDesiredBootstrapDataForBackup(
}

func getDesiredDiskDataForBackup(
ctx goctx.Context,
vcVM *object.VirtualMachine,
ctx context.BackupVirtualMachineContext,
ecMap map[string]string) (string, error) {
deviceList, err := vcVM.Device(ctx)
// Return an empty string to skip backup if no disk uuid to PVC is specified.
if len(ctx.DiskUUIDToPVC) == 0 {
return "", nil
}

deviceList, err := ctx.VcVM.Device(ctx.VMCtx)
if err != nil {
return "", err
}

var diskData []VMDiskData
for _, device := range deviceList.SelectByType((*types.VirtualDisk)(nil)) {
if disk, ok := device.(*types.VirtualDisk); ok {
vmDiskData := VMDiskData{}
if disk.VDiskId != nil {
vmDiskData.VDiskID = disk.VDiskId.Id
}
if b, ok := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo); ok {
vmDiskData.FileName = b.FileName
}
// Only add the disk data if it's not empty.
if vmDiskData != (VMDiskData{}) {
diskData = append(diskData, vmDiskData)
if pvc, ok := ctx.DiskUUIDToPVC[b.Uuid]; ok {
diskData = append(diskData, VMDiskData{
FileName: b.FileName,
PVCName: pvc.Name,
AccessModes: pvc.Spec.AccessModes,
})
}
}
}
}
Expand Down
Loading

0 comments on commit a4d06bd

Please sign in to comment.