Skip to content

Commit

Permalink
[release-v0.7] Generate new firmware UUID during restore (#323)
Browse files Browse the repository at this point in the history
* Add label to generate new firmware UUIDS for restored VMs

This commit implements a new label, "velero.kubevirt.io/generate-new-firmware-uuid", that when used in the restore object, the velero plugin will generate new firmware UUIDS for restored VMs.

Signed-off-by: Alvaro Romero <[email protected]>

* Add func test coverage for "velero.kubevirt.io/generate-new-firmware-uuid" label

Signed-off-by: Alvaro Romero <[email protected]>

---------

Signed-off-by: Alvaro Romero <[email protected]>
  • Loading branch information
alromeros authored Jan 22, 2025
1 parent 33428ec commit 6e87671
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 0 deletions.
5 changes: 5 additions & 0 deletions pkg/plugin/vm_restore_item_action.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ func (p *VMRestorePlugin) Execute(input *velero.RestoreItemActionExecuteInput) (
util.ClearMacAddress(&vm.Spec.Template.Spec)
}

if util.ShouldGenerateNewFirmwareUUID(input.Restore) {
p.log.Info("Generate new firmware UUID")
util.GenerateNewFirmwareUUID(&vm.Spec.Template.Spec, vm.Name, vm.Namespace, string(vm.UID))
}

item, err := runtime.DefaultUnstructuredConverter.ToUnstructured(vm)
if err != nil {
return nil, errors.WithStack(err)
Expand Down
36 changes: 36 additions & 0 deletions pkg/plugin/vm_restore_item_action_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,27 @@ func TestVmRestoreExecute(t *testing.T) {
},
},
},
"template": map[string]interface{}{
"spec": map[string]interface{}{
"volumes": []map[string]interface{}{
{
"dataVolume": map[string]interface{}{
"name": "test-dv-1",
},
},
{
"dataVolume": map[string]interface{}{
"name": "test-dv-2",
},
},
},
"domain": map[string]interface{}{
"firmware": map[string]interface{}{
"uuid": "original-uuid",
},
},
},
},
},
},
},
Expand Down Expand Up @@ -101,6 +122,21 @@ func TestVmRestoreExecute(t *testing.T) {
assert.Nil(t, spec["running"])
})

t.Run("New firmware UUID should be generated when using appropriate label", func(t *testing.T) {
input.Restore.Labels = map[string]string{"velero.kubevirt.io/generate-new-firmware-uuid": "true"}
originalUUID := input.Item.UnstructuredContent()["spec"].(map[string]interface{})["template"].(map[string]interface{})["spec"].(map[string]interface{})["domain"].(map[string]interface{})["firmware"].(map[string]interface{})["uuid"].(string)
output, err := action.Execute(&input)
assert.Nil(t, err)

spec := output.UpdatedItem.UnstructuredContent()["spec"].(map[string]interface{})
domain := spec["template"].(map[string]interface{})["spec"].(map[string]interface{})["domain"].(map[string]interface{})
firmware := domain["firmware"].(map[string]interface{})
newUUID := firmware["uuid"].(string)

assert.NotEqual(t, originalUUID, newUUID)
assert.NotEmpty(t, newUUID)
})

t.Run("VM should return DVs as additional items", func(t *testing.T) {
output, _ := action.Execute(&input)

Expand Down
5 changes: 5 additions & 0 deletions pkg/plugin/vmi_restore_item_action.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ func (p *VMIRestorePlugin) Execute(input *velero.RestoreItemActionExecuteInput)
util.ClearMacAddress(&vmi.Spec)
}

if util.ShouldGenerateNewFirmwareUUID(input.Restore) {
p.log.Info("Generate new firmware UUID")
util.GenerateNewFirmwareUUID(&vmi.Spec, vmi.Name, vmi.Namespace, string(vmi.UID))
}

// Restricted labels must be cleared otherwise the VMI will be rejected.
// The restricted labels contain runtime information about the underlying KVM object.
labels := removeRestrictedLabels(vmi.GetLabels())
Expand Down
17 changes: 17 additions & 0 deletions pkg/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os"
"strings"

"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"

Expand All @@ -19,6 +20,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
kvv1 "kubevirt.io/api/core/v1"
Expand All @@ -38,6 +40,9 @@ const (
// ClearMacAddressLabel indicates that the MAC address should be cleared as part of the restore workflow.
ClearMacAddressLabel = "velero.kubevirt.io/clear-mac-address"

// GenerateNewFirmwareUUIDLabel indicates that a new firmware UUID should be generated for VMs as part of the restore workflow.
GenerateNewFirmwareUUIDLabel = "velero.kubevirt.io/generate-new-firmware-uuid"

// VeleroExcludeLabel is used to exclude an object from Velero backups.
VeleroExcludeLabel = "velero.io/exclude-from-backup"
)
Expand Down Expand Up @@ -398,3 +403,15 @@ func ClearMacAddress(vmiSpec *kvv1.VirtualMachineInstanceSpec) {
vmiSpec.Domain.Devices.Interfaces[i].MacAddress = ""
}
}

func ShouldGenerateNewFirmwareUUID(restore *velerov1.Restore) bool {
return metav1.HasLabel(restore.ObjectMeta, GenerateNewFirmwareUUIDLabel)
}

// GenerateNewFirmwareUUID generates a new random firmware UUID for the restored VM
func GenerateNewFirmwareUUID(vmiSpec *kvv1.VirtualMachineInstanceSpec, name, namespace, uid string) {
if vmiSpec.Domain.Firmware == nil {
vmiSpec.Domain.Firmware = &kvv1.Firmware{}
}
vmiSpec.Domain.Firmware.UUID = types.UID(uuid.New().String())
}
45 changes: 45 additions & 0 deletions tests/vm_backup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/utils/strings/slices"
kvv1 "kubevirt.io/api/core/v1"
kubecli "kubevirt.io/client-go/kubecli"
Expand Down Expand Up @@ -260,6 +261,50 @@ var _ = Describe("[smoke] VM Backup", func() {
),
)

It("VM should be restored with new firmware UUID when using appropriate label", func() {
By("Starting a VM")
var err error
vm = framework.CreateVmWithGuestAgent("test-vm", f.StorageClass)
vm.Spec.Template.Spec.Domain.Firmware = &kvv1.Firmware{
// Choosing arbitrary UUID
UUID: types.UID("123e4567-e89b-12d3-a456-426614174000"),
}
vm, err = framework.CreateStartedVirtualMachine(f.KvClient, f.Namespace.Name, vm)
Expect(err).ToNot(HaveOccurred())

err = framework.WaitForVirtualMachineStatus(f.KvClient, f.Namespace.Name, vm.Name, kvv1.VirtualMachineStatusRunning)
Expect(err).ToNot(HaveOccurred())

By("Creating backup")
err = framework.CreateBackupForNamespace(timeout, backupName, f.Namespace.Name, snapshotLocation, f.BackupNamespace, true)
Expect(err).ToNot(HaveOccurred())

phase, err := framework.GetBackupPhase(timeout, backupName, f.BackupNamespace)
Expect(err).ToNot(HaveOccurred())
Expect(phase).To(Equal(velerov1api.BackupPhaseCompleted))

By("Deleting VM")
err = framework.DeleteVirtualMachine(f.KvClient, f.Namespace.Name, vm.Name)
Expect(err).ToNot(HaveOccurred())

By("Creating restore with new firmware UUID label")
err = framework.CreateRestoreWithLabels(timeout, backupName, restoreName, f.BackupNamespace, true, map[string]string{"velero.kubevirt.io/generate-new-firmware-uuid": "true"})
Expect(err).ToNot(HaveOccurred())

rPhase, err := framework.GetRestorePhase(timeout, restoreName, f.BackupNamespace)
Expect(err).ToNot(HaveOccurred())
Expect(rPhase).To(Equal(velerov1api.RestorePhaseCompleted))

By("Verifying restored VM")
err = framework.WaitForVirtualMachineStatus(f.KvClient, f.Namespace.Name, vm.Name, kvv1.VirtualMachineStatusRunning)
Expect(err).ToNot(HaveOccurred())

By("Checking new firmware UUID")
restoredVM, err := f.KvClient.VirtualMachine(f.Namespace.Name).Get(context.TODO(), vm.Name, &metav1.GetOptions{})
Expect(err).ToNot(HaveOccurred())
Expect(restoredVM.Spec.Template.Spec.Domain.Firmware.UUID).ToNot(Equal(vm.Spec.Template.Spec.Domain.Firmware.UUID))
})

It("started VM should be restored with new MAC address", func() {
// creating a started VM, so it works correctly also on WFFC storage
var err error
Expand Down

0 comments on commit 6e87671

Please sign in to comment.