Skip to content

Commit

Permalink
Introduce a new annotation to identify failed over VMs
Browse files Browse the repository at this point in the history
We need a new annotation for failed over VMs so we can skip some
webhook checks.  This is different from imported, or registered VMs
since we will need to relax additional webhook checks such as allowing
for network manipulation.

Remove the test failover annotation since that is no longer needed.
  • Loading branch information
aruneshpa committed Dec 16, 2024
1 parent 32a7325 commit efad2b4
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 45 deletions.
7 changes: 7 additions & 0 deletions api/v1alpha3/virtualmachine_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,13 @@ const (
// annotation is used to bypass some validation checks that are otherwise applicable
// to all VirtualMachine create/update requests.
ImportedVMAnnotation = GroupName + "/imported-vm"

// FailedOverVMAnnotation on a VirtualMachine resource represents that a virtual
// machine has been failed over from one site to the other, typically as part of a
// disaster recovery workflow. The presence of this annotation is used to bypass
// some validation checks that are otherwise applicable to all VirtualMachine
// create/update requests.
FailedOverVMAnnotation = GroupName + "/failed-over-vm"
)

const (
Expand Down
16 changes: 0 additions & 16 deletions pkg/backup/api/backup_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,20 +108,4 @@ const (
// specifying this key, backup/restore and/or disaster recovery solutions are
// responsible to register the VM with Supervisor.
DisableAutoRegistrationExtraConfigKey = "vmservice.virtualmachine.disableAutomaticRegistration"

// TestFailoverLabelKey label signifies that a VirtualMachine is going through
// a test recovery plan scenario. Typically, this involves failing over a VM
// to a dedicated temporary network so as to not exhaust, and cause IP address
// collisions with the production network. This label on a VirtualMachine
// custom resource allows vendors to change the VM's network interface to
// connect to the test network after the VM is registered post a failover.
//
// VM operator does not support mutable networks, so this label allows only
// select vendors (e.g., SRM) to support recovery plan test workflows while we
// build support for true network mutability.
//
// The value of this label does not matter. Its presence is considered a
// sufficient evidence to indicate that the virtual machine is going through
// test fail-over.
TestFailoverLabelKey = "virtualmachine." + GroupName + "/test-failover"
)
4 changes: 4 additions & 0 deletions pkg/util/annotations/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ func HasRegisterVM(o metav1.Object) bool {
return hasAnnotation(o, vmopv1.RegisteredVMAnnotation)
}

func HasFailOverVM(o metav1.Object) bool {
return hasAnnotation(o, vmopv1.FailedOverVMAnnotation)
}

func HasImportVM(o metav1.Object) bool {
return hasAnnotation(o, vmopv1.ImportedVMAnnotation)
}
Expand Down
17 changes: 17 additions & 0 deletions pkg/util/annotations/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,20 @@ var _ = DescribeTable(
Entry("present but empty ", map[string]string{vmopv1.ImportedVMAnnotation: ""}, true),
Entry("present and not empty ", map[string]string{vmopv1.ImportedVMAnnotation: "true"}, true),
)

var _ = DescribeTable(
"HasFailOverVM",
func(in map[string]string, out bool) {
vm := &vmopv1.VirtualMachine{
ObjectMeta: metav1.ObjectMeta{
Annotations: in,
},
}
actual := annotations.HasFailOverVM(vm)
Expect(actual).To(Equal(out))
},
Entry("nil", nil, false),
Entry("not present", map[string]string{"foo": "bar"}, false),
Entry("present but empty ", map[string]string{vmopv1.FailedOverVMAnnotation: ""}, true),
Entry("present and not empty ", map[string]string{vmopv1.FailedOverVMAnnotation: "true"}, true),
)
21 changes: 12 additions & 9 deletions webhooks/virtualmachine/validation/virtualmachine_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import (

vmopv1 "github.com/vmware-tanzu/vm-operator/api/v1alpha3"
"github.com/vmware-tanzu/vm-operator/api/v1alpha3/sysprep"
backupapi "github.com/vmware-tanzu/vm-operator/pkg/backup/api"
"github.com/vmware-tanzu/vm-operator/pkg/builder"
pkgcfg "github.com/vmware-tanzu/vm-operator/pkg/config"
"github.com/vmware-tanzu/vm-operator/pkg/constants"
Expand Down Expand Up @@ -154,13 +153,13 @@ func (v validator) ValidateDelete(*pkgctx.WebhookRequestContext) admission.Respo
return admission.Allowed("")
}

// Updates to VM's image are only allowed if it is a Registered VM (used for failover
// in disaster recovery).
// Updates to VM's image are only allowed if it is a failed over VM.
func (v validator) validateImageOnUpdate(ctx *pkgctx.WebhookRequestContext, vm, oldVM *vmopv1.VirtualMachine) field.ErrorList {
var allErrs field.ErrorList

// Allow resetting of image if this is a failover operation.
if vmopv1util.IsImagelessVM(*vm) && pkgcfg.FromContext(ctx).Features.VMIncrementalRestore {
if annotations.HasRegisterVM(vm) {
if annotations.HasFailOverVM(vm) {
if !vmopv1util.ImageRefsEqual(vm.Spec.Image, oldVM.Spec.Image) {
if !ctx.IsPrivilegedAccount {
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "image"), restrictedToPrivUsers))
Expand Down Expand Up @@ -402,8 +401,8 @@ func (v validator) validateImageOnCreate(ctx *pkgctx.WebhookRequestContext, vm *
(pkgcfg.FromContext(ctx).Features.VMImportNewNet ||
pkgcfg.FromContext(ctx).Features.VMIncrementalRestore):
// TODO: Simplify this once mobility operator starts creating VMs with correct annotation.
// Skip validations on images if it is a VM created using ImportVM, or RegisterVM.
if annotations.HasImportVM(vm) || annotations.HasRegisterVM(vm) {
// Skip validations on images if it is a VM that is imported, registered, or failed over.
if annotations.HasImportVM(vm) || annotations.HasRegisterVM(vm) || annotations.HasFailOverVM(vm) {
// Restrict creating imageless VM resources to privileged users.
if !ctx.IsPrivilegedAccount {
allErrs = append(allErrs, field.Forbidden(f, restrictedToPrivUsers))
Expand Down Expand Up @@ -1237,9 +1236,9 @@ func (v validator) validateImmutableNetwork(ctx *pkgctx.WebhookRequestContext, v
return append(allErrs, field.Forbidden(p.Child("interfaces"), "network interfaces cannot be added or removed"))
}

// Skip comparing interfaces if this is a test fail-over to allow vendors to
// connect network each interface to test network.
if _, ok := vm.Labels[backupapi.TestFailoverLabelKey]; ok {
// Skip comparing interfaces if this is a fail-over to allow vendors to
// connect network each interface to the test, or the production network.
if _, ok := vm.Annotations[vmopv1.FailedOverVMAnnotation]; ok {
return allErrs
}

Expand Down Expand Up @@ -1341,6 +1340,10 @@ func (v validator) validateAnnotation(ctx *pkgctx.WebhookRequestContext, vm, old
allErrs = append(allErrs, field.Forbidden(annotationPath.Key(vmopv1.RegisteredVMAnnotation), modifyAnnotationNotAllowedForNonAdmin))
}

if vm.Annotations[vmopv1.FailedOverVMAnnotation] != oldVM.Annotations[vmopv1.FailedOverVMAnnotation] {
allErrs = append(allErrs, field.Forbidden(annotationPath.Key(vmopv1.FailedOverVMAnnotation), modifyAnnotationNotAllowedForNonAdmin))
}

if vm.Annotations[vmopv1.ImportedVMAnnotation] != oldVM.Annotations[vmopv1.ImportedVMAnnotation] {
allErrs = append(allErrs, field.Forbidden(annotationPath.Key(vmopv1.ImportedVMAnnotation), modifyAnnotationNotAllowedForNonAdmin))
}
Expand Down
Loading

0 comments on commit efad2b4

Please sign in to comment.