diff --git a/controllers/virtualmachine/virtualmachine/virtualmachine_controller.go b/controllers/virtualmachine/virtualmachine/virtualmachine_controller.go index 80965b00e..304951500 100644 --- a/controllers/virtualmachine/virtualmachine/virtualmachine_controller.go +++ b/controllers/virtualmachine/virtualmachine/virtualmachine_controller.go @@ -280,6 +280,8 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Re ctx = ctxop.WithContext(ctx) + ctx = record.WithContext(ctx, r.Recorder) + vm := &vmopv1.VirtualMachine{} if err := r.Get(ctx, req.NamespacedName, vm); err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) @@ -451,9 +453,20 @@ func (r *Reconciler) ReconcileNormal(ctx *pkgctx.VirtualMachineContext) (reterr r.Recorder.EmitEvent(ctx.VM, "ReconcileNormal", err, true) } - // Always check if this VM needs to be in the probe queue. We want to do this even if - // the VM hasn't been created yet so the condition is updated. - r.Prober.AddToProberManager(ctx.VM) + if !pkgcfg.FromContext(ctx).Features.WorkloadDomainIsolation || + pkgcfg.FromContext(ctx).AsyncSignalDisabled { + + // Add the VM to the probe manager. This is idempotent. + r.Prober.AddToProberManager(ctx.VM) + + } else if p := ctx.VM.Spec.ReadinessProbe; p != nil && p.TCPSocket != nil { + // TCP probes still use the probe manager. + r.Prober.AddToProberManager(ctx.VM) + } else { + // Remove the probe in case it *was* a TCP probe but switched to one + // of the other types. + r.Prober.RemoveFromProberManager(ctx.VM) + } if err != nil { return err diff --git a/pkg/providers/vsphere/vmlifecycle/update_status.go b/pkg/providers/vsphere/vmlifecycle/update_status.go index e930b0720..5b37aeca6 100644 --- a/pkg/providers/vsphere/vmlifecycle/update_status.go +++ b/pkg/providers/vsphere/vmlifecycle/update_status.go @@ -9,9 +9,11 @@ import ( "net" "path" "reflect" + "regexp" "slices" "strings" + "github.com/go-logr/logr" "github.com/vmware/govmomi/object" "github.com/vmware/govmomi/vim25/mo" vimtypes "github.com/vmware/govmomi/vim25/types" @@ -29,6 +31,7 @@ import ( pkgctx "github.com/vmware-tanzu/vm-operator/pkg/context" "github.com/vmware-tanzu/vm-operator/pkg/providers/vsphere/network" "github.com/vmware-tanzu/vm-operator/pkg/providers/vsphere/vcenter" + vmoprecord "github.com/vmware-tanzu/vm-operator/pkg/record" "github.com/vmware-tanzu/vm-operator/pkg/topology" "github.com/vmware-tanzu/vm-operator/pkg/util" "github.com/vmware-tanzu/vm-operator/pkg/util/ptr" @@ -95,6 +98,12 @@ func UpdateStatus( updateGuestNetworkStatus(vmCtx.VM, vmCtx.MoVM.Guest) updateStorageStatus(vmCtx.VM, vmCtx.MoVM) + if pkgcfg.FromContext(vmCtx).Features.WorkloadDomainIsolation && + !pkgcfg.FromContext(vmCtx).AsyncSignalDisabled { + + updateProbeStatus(vmCtx, vm, vmCtx.MoVM) + } + vm.Status.Host, err = getRuntimeHostHostname(vmCtx, vcVM, summary.Runtime.Host) if err != nil { errs = append(errs, err) @@ -880,3 +889,161 @@ const byteToGiB = 1 /* B */ * 1024 /* KiB */ * 1024 /* MiB */ * 1024 /* GiB */ func BytesToResourceGiB(b int64) *resource.Quantity { return ptr.To(resource.MustParse(fmt.Sprintf("%dGi", b/byteToGiB))) } + +type probeResult uint8 + +const ( + probeResultUnknown probeResult = iota + probeResultSuccess + probeResultFailure +) + +const ( + probeReasonReady string = "Ready" + probeReasonNotReady string = "NotReady" + probeReasonUnknown string = "Unknown" +) + +func heartbeatValue(value string) int { + switch value { + case string(vmopv1.GreenHeartbeatStatus): + return 3 + case string(vmopv1.YellowHeartbeatStatus): + return 2 + case string(vmopv1.RedHeartbeatStatus): + return 1 + case string(vmopv1.GrayHeartbeatStatus): + return 0 + default: + return -1 + } +} + +// updateProbeStatus updates a VM's status with the results of the configured +// readiness probes. +// Please note, this function returns early if the configured probe is TCP. +func updateProbeStatus( + ctx context.Context, + vm *vmopv1.VirtualMachine, + moVM mo.VirtualMachine) { + + p := vm.Spec.ReadinessProbe + if p == nil || p.TCPSocket != nil { + return + } + + var ( + result probeResult + resultMsg string + ) + + switch { + case p.GuestHeartbeat != nil: + result, resultMsg = updateProbeStatusHeartbeat(vm, moVM) + case p.GuestInfo != nil: + result, resultMsg = updateProbeStatusGuestInfo(vm, moVM) + } + + var cond *metav1.Condition + switch result { + case probeResultSuccess: + cond = conditions.TrueCondition(vmopv1.ReadyConditionType) + case probeResultFailure: + cond = conditions.FalseCondition( + vmopv1.ReadyConditionType, probeReasonNotReady, resultMsg) + default: + cond = conditions.UnknownCondition( + vmopv1.ReadyConditionType, probeReasonUnknown, resultMsg) + } + + // Emit event whe the condition is added or its status changes. + if c := conditions.Get(vm, cond.Type); c == nil || c.Status != cond.Status { + recorder := vmoprecord.FromContext(ctx) + if cond.Status == metav1.ConditionTrue { + recorder.Eventf(vm, probeReasonReady, "") + } else { + recorder.Eventf(vm, cond.Reason, cond.Message) + } + + // Log the time when the VM changes its readiness condition. + logr.FromContextOrDiscard(ctx).Info( + "VM resource readiness probe condition updated", + "condition.status", cond.Status, + "time", cond.LastTransitionTime, + "reason", cond.Reason) + } + + conditions.Set(vm, cond) +} + +func updateProbeStatusHeartbeat( + vm *vmopv1.VirtualMachine, + moVM mo.VirtualMachine) (probeResult, string) { + + chb := string(moVM.GuestHeartbeatStatus) + mhb := string(vm.Spec.ReadinessProbe.GuestHeartbeat.ThresholdStatus) + chv := heartbeatValue(chb) + if chv < 0 { + return probeResultUnknown, "" + } + if mhv := heartbeatValue(mhb); chv < mhv { + return probeResultFailure, fmt.Sprintf( + "heartbeat status %q is below threshold", chb) + } + return probeResultSuccess, "" +} + +func updateProbeStatusGuestInfo( + vm *vmopv1.VirtualMachine, + moVM mo.VirtualMachine) (probeResult, string) { //nolint:unparam + + numProbes := len(vm.Spec.ReadinessProbe.GuestInfo) + if numProbes == 0 { + return probeResultUnknown, "" + } + + // Build the list of guestinfo keys. + var ( + guestInfoKeys = make([]string, numProbes) + guestInfoKeyVals = make(map[string]string, numProbes) + ) + for i := range vm.Spec.ReadinessProbe.GuestInfo { + gi := vm.Spec.ReadinessProbe.GuestInfo[i] + giKey := fmt.Sprintf("guestinfo.%s", gi.Key) + guestInfoKeys[i] = giKey + guestInfoKeyVals[giKey] = gi.Value + } + + if moVM.Config == nil { + panic("moVM.Config is nil") + } + + results := object.OptionValueList(moVM.Config.ExtraConfig).StringMap() + + for i := range guestInfoKeys { + key := guestInfoKeys[i] + expectedVal := guestInfoKeyVals[key] + + actualVal, ok := results[key] + if !ok { + return probeResultFailure, "" + } + + if expectedVal == "" { + // Matches everything. + continue + } + + expectedValRx, err := regexp.Compile(expectedVal) + if err != nil { + // Treat an invalid expressions as a wildcard too. + continue + } + + if !expectedValRx.MatchString(actualVal) { + return probeResultFailure, "" + } + } + + return probeResultSuccess, "" +} diff --git a/pkg/providers/vsphere/vmlifecycle/update_status_test.go b/pkg/providers/vsphere/vmlifecycle/update_status_test.go index 65625539e..692bedb4a 100644 --- a/pkg/providers/vsphere/vmlifecycle/update_status_test.go +++ b/pkg/providers/vsphere/vmlifecycle/update_status_test.go @@ -14,6 +14,7 @@ import ( "github.com/vmware/govmomi/vim25/mo" vimtypes "github.com/vmware/govmomi/vim25/types" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + apirecord "k8s.io/client-go/tools/record" vmopv1 "github.com/vmware-tanzu/vm-operator/api/v1alpha3" vmopv1common "github.com/vmware-tanzu/vm-operator/api/v1alpha3/common" @@ -23,6 +24,7 @@ import ( "github.com/vmware-tanzu/vm-operator/pkg/providers/vsphere" "github.com/vmware-tanzu/vm-operator/pkg/providers/vsphere/network" "github.com/vmware-tanzu/vm-operator/pkg/providers/vsphere/vmlifecycle" + "github.com/vmware-tanzu/vm-operator/pkg/record" "github.com/vmware-tanzu/vm-operator/pkg/util" "github.com/vmware-tanzu/vm-operator/pkg/util/ptr" "github.com/vmware-tanzu/vm-operator/test/builder" @@ -467,601 +469,823 @@ var _ = Describe("UpdateStatus", func() { }) }) - Context("Storage", func() { - const oneGiBInBytes = 1 /* B */ * 1024 /* KiB */ * 1024 /* MiB */ * 1024 /* GiB */ + }) + + Context("Storage", func() { + const oneGiBInBytes = 1 /* B */ * 1024 /* KiB */ * 1024 /* MiB */ * 1024 /* GiB */ - Context("status.changeBlockTracking", func() { - When("moVM.config is nil", func() { + Context("status.changeBlockTracking", func() { + When("moVM.config is nil", func() { + BeforeEach(func() { + vmCtx.MoVM.Config = nil + }) + Specify("status.changeBlockTracking is nil", func() { + Expect(vmCtx.VM.Status.ChangeBlockTracking).To(BeNil()) + }) + }) + When("moVM.config is not nil", func() { + When("moVM.config.changeTrackingEnabled is nil", func() { BeforeEach(func() { - vmCtx.MoVM.Config = nil + vmCtx.MoVM.Config = &vimtypes.VirtualMachineConfigInfo{ + ChangeTrackingEnabled: nil, + } }) Specify("status.changeBlockTracking is nil", func() { Expect(vmCtx.VM.Status.ChangeBlockTracking).To(BeNil()) }) }) - When("moVM.config is not nil", func() { - When("moVM.config.changeTrackingEnabled is nil", func() { - BeforeEach(func() { - vmCtx.MoVM.Config = &vimtypes.VirtualMachineConfigInfo{ - ChangeTrackingEnabled: nil, - } - }) - Specify("status.changeBlockTracking is nil", func() { - Expect(vmCtx.VM.Status.ChangeBlockTracking).To(BeNil()) - }) + When("moVM.config.changeTrackingEnabled is true", func() { + BeforeEach(func() { + vmCtx.MoVM.Config = &vimtypes.VirtualMachineConfigInfo{ + ChangeTrackingEnabled: ptr.To(true), + } }) - When("moVM.config.changeTrackingEnabled is true", func() { - BeforeEach(func() { - vmCtx.MoVM.Config = &vimtypes.VirtualMachineConfigInfo{ - ChangeTrackingEnabled: ptr.To(true), - } - }) - Specify("status.changeBlockTracking is true", func() { - Expect(vmCtx.VM.Status.ChangeBlockTracking).To(Equal(ptr.To(true))) - }) + Specify("status.changeBlockTracking is true", func() { + Expect(vmCtx.VM.Status.ChangeBlockTracking).To(Equal(ptr.To(true))) }) - When("moVM.config.changeTrackingEnabled is false", func() { - BeforeEach(func() { - vmCtx.MoVM.Config = &vimtypes.VirtualMachineConfigInfo{ - ChangeTrackingEnabled: ptr.To(false), - } - }) - Specify("status.changeBlockTracking is false", func() { - Expect(vmCtx.VM.Status.ChangeBlockTracking).To(Equal(ptr.To(false))) - }) + }) + When("moVM.config.changeTrackingEnabled is false", func() { + BeforeEach(func() { + vmCtx.MoVM.Config = &vimtypes.VirtualMachineConfigInfo{ + ChangeTrackingEnabled: ptr.To(false), + } + }) + Specify("status.changeBlockTracking is false", func() { + Expect(vmCtx.VM.Status.ChangeBlockTracking).To(Equal(ptr.To(false))) }) }) }) + }) - Context("status.storage", func() { - When("moVM.summary.storage is nil", func() { + Context("status.storage", func() { + When("moVM.summary.storage is nil", func() { + BeforeEach(func() { + vmCtx.MoVM.Summary.Storage = nil + }) + When("status.storage is nil", func() { BeforeEach(func() { - vmCtx.MoVM.Summary.Storage = nil + vmCtx.VM.Status.Storage = nil }) - When("status.storage is nil", func() { - BeforeEach(func() { - vmCtx.VM.Status.Storage = nil - }) - Specify("status.storage to be unchanged", func() { - Expect(vmCtx.VM.Status.Storage).To(BeNil()) - }) + Specify("status.storage to be unchanged", func() { + Expect(vmCtx.VM.Status.Storage).To(BeNil()) }) - When("status.storage is not nil", func() { - BeforeEach(func() { - vmCtx.VM.Status.Storage = &vmopv1.VirtualMachineStorageStatus{} - }) - Specify("status.storage to be unchanged", func() { - Expect(vmCtx.VM.Status.Storage).To(Equal(&vmopv1.VirtualMachineStorageStatus{})) - }) + }) + When("status.storage is not nil", func() { + BeforeEach(func() { + vmCtx.VM.Status.Storage = &vmopv1.VirtualMachineStorageStatus{} }) + Specify("status.storage to be unchanged", func() { + Expect(vmCtx.VM.Status.Storage).To(Equal(&vmopv1.VirtualMachineStorageStatus{})) + }) + }) + }) + When("moVM.summary.storage is not nil", func() { + BeforeEach(func() { + vmCtx.MoVM.Summary.Storage = &vimtypes.VirtualMachineStorageSummary{ + Committed: 10 * oneGiBInBytes, + Uncommitted: 20 * oneGiBInBytes, + Unshared: 5 * oneGiBInBytes, + } }) - When("moVM.summary.storage is not nil", func() { + When("status.storage is nil", func() { BeforeEach(func() { - vmCtx.MoVM.Summary.Storage = &vimtypes.VirtualMachineStorageSummary{ - Committed: 10 * oneGiBInBytes, - Uncommitted: 20 * oneGiBInBytes, - Unshared: 5 * oneGiBInBytes, - } + vmCtx.VM.Status.Storage = nil }) - When("status.storage is nil", func() { - BeforeEach(func() { - vmCtx.VM.Status.Storage = nil - }) - Specify("status.storage to be initialized", func() { - Expect(vmCtx.VM.Status.Storage).To(Equal(&vmopv1.VirtualMachineStorageStatus{ - Committed: vmlifecycle.BytesToResourceGiB(10 * oneGiBInBytes), - Uncommitted: vmlifecycle.BytesToResourceGiB(20 * oneGiBInBytes), - Unshared: vmlifecycle.BytesToResourceGiB(5 * oneGiBInBytes), - })) - }) + Specify("status.storage to be initialized", func() { + Expect(vmCtx.VM.Status.Storage).To(Equal(&vmopv1.VirtualMachineStorageStatus{ + Committed: vmlifecycle.BytesToResourceGiB(10 * oneGiBInBytes), + Uncommitted: vmlifecycle.BytesToResourceGiB(20 * oneGiBInBytes), + Unshared: vmlifecycle.BytesToResourceGiB(5 * oneGiBInBytes), + })) }) - When("status.storage is not nil", func() { - BeforeEach(func() { - vmCtx.VM.Status.Storage = &vmopv1.VirtualMachineStorageStatus{ - Committed: vmlifecycle.BytesToResourceGiB(5 * oneGiBInBytes), - Uncommitted: vmlifecycle.BytesToResourceGiB(6 * oneGiBInBytes), - Unshared: vmlifecycle.BytesToResourceGiB(2 * oneGiBInBytes), - } - }) - Specify("status.storage to be updated", func() { - Expect(vmCtx.VM.Status.Storage).To(Equal(&vmopv1.VirtualMachineStorageStatus{ - Committed: vmlifecycle.BytesToResourceGiB(10 * oneGiBInBytes), - Uncommitted: vmlifecycle.BytesToResourceGiB(20 * oneGiBInBytes), - Unshared: vmlifecycle.BytesToResourceGiB(5 * oneGiBInBytes), - })) - }) + }) + When("status.storage is not nil", func() { + BeforeEach(func() { + vmCtx.VM.Status.Storage = &vmopv1.VirtualMachineStorageStatus{ + Committed: vmlifecycle.BytesToResourceGiB(5 * oneGiBInBytes), + Uncommitted: vmlifecycle.BytesToResourceGiB(6 * oneGiBInBytes), + Unshared: vmlifecycle.BytesToResourceGiB(2 * oneGiBInBytes), + } + }) + Specify("status.storage to be updated", func() { + Expect(vmCtx.VM.Status.Storage).To(Equal(&vmopv1.VirtualMachineStorageStatus{ + Committed: vmlifecycle.BytesToResourceGiB(10 * oneGiBInBytes), + Uncommitted: vmlifecycle.BytesToResourceGiB(20 * oneGiBInBytes), + Unshared: vmlifecycle.BytesToResourceGiB(5 * oneGiBInBytes), + })) }) }) }) + }) - Context("status.volumes", func() { - BeforeEach(func() { - vmCtx.MoVM.Config = &vimtypes.VirtualMachineConfigInfo{ - Hardware: vimtypes.VirtualHardware{ - Device: []vimtypes.BaseVirtualDevice{ - // classic - &vimtypes.VirtualDisk{ - VirtualDevice: vimtypes.VirtualDevice{ - Backing: &vimtypes.VirtualDiskFlatVer2BackingInfo{ - VirtualDeviceFileBackingInfo: vimtypes.VirtualDeviceFileBackingInfo{ - FileName: "[datastore] vm/my-disk-100.vmdk", - }, - Uuid: "100", - KeyId: &vimtypes.CryptoKeyId{ - KeyId: "my-key-id", - ProviderId: &vimtypes.KeyProviderId{ - Id: "my-provider-id", - }, + Context("status.volumes", func() { + BeforeEach(func() { + vmCtx.MoVM.Config = &vimtypes.VirtualMachineConfigInfo{ + Hardware: vimtypes.VirtualHardware{ + Device: []vimtypes.BaseVirtualDevice{ + // classic + &vimtypes.VirtualDisk{ + VirtualDevice: vimtypes.VirtualDevice{ + Backing: &vimtypes.VirtualDiskFlatVer2BackingInfo{ + VirtualDeviceFileBackingInfo: vimtypes.VirtualDeviceFileBackingInfo{ + FileName: "[datastore] vm/my-disk-100.vmdk", + }, + Uuid: "100", + KeyId: &vimtypes.CryptoKeyId{ + KeyId: "my-key-id", + ProviderId: &vimtypes.KeyProviderId{ + Id: "my-provider-id", }, }, - Key: 100, }, - CapacityInBytes: 10 * oneGiBInBytes, + Key: 100, }, - // classic - &vimtypes.VirtualDisk{ - VirtualDevice: vimtypes.VirtualDevice{ - Backing: &vimtypes.VirtualDiskSeSparseBackingInfo{ - VirtualDeviceFileBackingInfo: vimtypes.VirtualDeviceFileBackingInfo{ - FileName: "[datastore] vm/my-disk-101.vmdk", - }, - Uuid: "101", + CapacityInBytes: 10 * oneGiBInBytes, + }, + // classic + &vimtypes.VirtualDisk{ + VirtualDevice: vimtypes.VirtualDevice{ + Backing: &vimtypes.VirtualDiskSeSparseBackingInfo{ + VirtualDeviceFileBackingInfo: vimtypes.VirtualDeviceFileBackingInfo{ + FileName: "[datastore] vm/my-disk-101.vmdk", }, - Key: 101, + Uuid: "101", }, - CapacityInBytes: 1 * oneGiBInBytes, + Key: 101, }, - // classic - &vimtypes.VirtualDisk{ - VirtualDevice: vimtypes.VirtualDevice{ - Backing: &vimtypes.VirtualDiskRawDiskMappingVer1BackingInfo{ - VirtualDeviceFileBackingInfo: vimtypes.VirtualDeviceFileBackingInfo{ - FileName: "[datastore] vm/my-disk-102.vmdk", - }, - Uuid: "102", + CapacityInBytes: 1 * oneGiBInBytes, + }, + // classic + &vimtypes.VirtualDisk{ + VirtualDevice: vimtypes.VirtualDevice{ + Backing: &vimtypes.VirtualDiskRawDiskMappingVer1BackingInfo{ + VirtualDeviceFileBackingInfo: vimtypes.VirtualDeviceFileBackingInfo{ + FileName: "[datastore] vm/my-disk-102.vmdk", }, - Key: 102, + Uuid: "102", }, - CapacityInBytes: 2 * oneGiBInBytes, + Key: 102, }, - // classic - &vimtypes.VirtualDisk{ - VirtualDevice: vimtypes.VirtualDevice{ - Backing: &vimtypes.VirtualDiskSparseVer2BackingInfo{ - VirtualDeviceFileBackingInfo: vimtypes.VirtualDeviceFileBackingInfo{ - FileName: "[datastore] vm/my-disk-103.vmdk", - }, - Uuid: "103", + CapacityInBytes: 2 * oneGiBInBytes, + }, + // classic + &vimtypes.VirtualDisk{ + VirtualDevice: vimtypes.VirtualDevice{ + Backing: &vimtypes.VirtualDiskSparseVer2BackingInfo{ + VirtualDeviceFileBackingInfo: vimtypes.VirtualDeviceFileBackingInfo{ + FileName: "[datastore] vm/my-disk-103.vmdk", }, - Key: 103, + Uuid: "103", }, - CapacityInBytes: 3 * oneGiBInBytes, + Key: 103, }, - // classic - &vimtypes.VirtualDisk{ - VirtualDevice: vimtypes.VirtualDevice{ - Backing: &vimtypes.VirtualDiskRawDiskVer2BackingInfo{ - DescriptorFileName: "[datastore] vm/my-disk-104.vmdk", - Uuid: "104", - }, - Key: 104, + CapacityInBytes: 3 * oneGiBInBytes, + }, + // classic + &vimtypes.VirtualDisk{ + VirtualDevice: vimtypes.VirtualDevice{ + Backing: &vimtypes.VirtualDiskRawDiskVer2BackingInfo{ + DescriptorFileName: "[datastore] vm/my-disk-104.vmdk", + Uuid: "104", }, - CapacityInBytes: 4 * oneGiBInBytes, + Key: 104, }, - // managed - &vimtypes.VirtualDisk{ - VirtualDevice: vimtypes.VirtualDevice{ - Backing: &vimtypes.VirtualDiskSeSparseBackingInfo{ - VirtualDeviceFileBackingInfo: vimtypes.VirtualDeviceFileBackingInfo{ - FileName: "[datastore] vm/my-disk-105.vmdk", - }, - Uuid: "105", - KeyId: &vimtypes.CryptoKeyId{ - KeyId: "my-key-id", - ProviderId: &vimtypes.KeyProviderId{ - Id: "my-provider-id", - }, + CapacityInBytes: 4 * oneGiBInBytes, + }, + // managed + &vimtypes.VirtualDisk{ + VirtualDevice: vimtypes.VirtualDevice{ + Backing: &vimtypes.VirtualDiskSeSparseBackingInfo{ + VirtualDeviceFileBackingInfo: vimtypes.VirtualDeviceFileBackingInfo{ + FileName: "[datastore] vm/my-disk-105.vmdk", + }, + Uuid: "105", + KeyId: &vimtypes.CryptoKeyId{ + KeyId: "my-key-id", + ProviderId: &vimtypes.KeyProviderId{ + Id: "my-provider-id", }, }, - Key: 105, - }, - VDiskId: &vimtypes.ID{ - Id: "my-fcd-1", }, + Key: 105, + }, + VDiskId: &vimtypes.ID{ + Id: "my-fcd-1", }, }, }, - } - vmCtx.MoVM.LayoutEx = &vimtypes.VirtualMachineFileLayoutEx{ - Disk: []vimtypes.VirtualMachineFileLayoutExDiskLayout{ - { - Key: 100, - Chain: []vimtypes.VirtualMachineFileLayoutExDiskUnit{ - { - FileKey: []int32{0, 10}, - }, + }, + } + vmCtx.MoVM.LayoutEx = &vimtypes.VirtualMachineFileLayoutEx{ + Disk: []vimtypes.VirtualMachineFileLayoutExDiskLayout{ + { + Key: 100, + Chain: []vimtypes.VirtualMachineFileLayoutExDiskUnit{ + { + FileKey: []int32{0, 10}, }, }, - { - Key: 101, - Chain: []vimtypes.VirtualMachineFileLayoutExDiskUnit{ - { - FileKey: []int32{1, 11}, - }, + }, + { + Key: 101, + Chain: []vimtypes.VirtualMachineFileLayoutExDiskUnit{ + { + FileKey: []int32{1, 11}, }, }, - { - Key: 102, - Chain: []vimtypes.VirtualMachineFileLayoutExDiskUnit{ - { - FileKey: []int32{2, 12}, - }, + }, + { + Key: 102, + Chain: []vimtypes.VirtualMachineFileLayoutExDiskUnit{ + { + FileKey: []int32{2, 12}, }, }, - { - Key: 103, - Chain: []vimtypes.VirtualMachineFileLayoutExDiskUnit{ - { - FileKey: []int32{3, 13}, - }, + }, + { + Key: 103, + Chain: []vimtypes.VirtualMachineFileLayoutExDiskUnit{ + { + FileKey: []int32{3, 13}, }, }, - { - Key: 104, - Chain: []vimtypes.VirtualMachineFileLayoutExDiskUnit{ - { - FileKey: []int32{4, 14}, - }, + }, + { + Key: 104, + Chain: []vimtypes.VirtualMachineFileLayoutExDiskUnit{ + { + FileKey: []int32{4, 14}, }, }, - { - Key: 105, - Chain: []vimtypes.VirtualMachineFileLayoutExDiskUnit{ - { - FileKey: []int32{5, 15}, - }, + }, + { + Key: 105, + Chain: []vimtypes.VirtualMachineFileLayoutExDiskUnit{ + { + FileKey: []int32{5, 15}, }, }, }, - File: []vimtypes.VirtualMachineFileLayoutExFileInfo{ - { - Key: 0, - Size: 500, - UniqueSize: 500, - }, - { - Key: 10, - Size: 2 * oneGiBInBytes, - UniqueSize: 1 * oneGiBInBytes, - }, + }, + File: []vimtypes.VirtualMachineFileLayoutExFileInfo{ + { + Key: 0, + Size: 500, + UniqueSize: 500, + }, + { + Key: 10, + Size: 2 * oneGiBInBytes, + UniqueSize: 1 * oneGiBInBytes, + }, - { - Key: 1, - Size: 500, - UniqueSize: 500, - }, - { - Key: 11, - Size: 0.5 * oneGiBInBytes, - UniqueSize: 0.25 * oneGiBInBytes, - }, + { + Key: 1, + Size: 500, + UniqueSize: 500, + }, + { + Key: 11, + Size: 0.5 * oneGiBInBytes, + UniqueSize: 0.25 * oneGiBInBytes, + }, - { - Key: 2, - Size: 500, - UniqueSize: 500, - }, - { - Key: 12, - Size: 1 * oneGiBInBytes, - UniqueSize: 0.5 * oneGiBInBytes, - }, + { + Key: 2, + Size: 500, + UniqueSize: 500, + }, + { + Key: 12, + Size: 1 * oneGiBInBytes, + UniqueSize: 0.5 * oneGiBInBytes, + }, - { - Key: 3, - Size: 500, - UniqueSize: 500, - }, - { - Key: 13, - Size: 2 * oneGiBInBytes, - UniqueSize: 1 * oneGiBInBytes, - }, + { + Key: 3, + Size: 500, + UniqueSize: 500, + }, + { + Key: 13, + Size: 2 * oneGiBInBytes, + UniqueSize: 1 * oneGiBInBytes, + }, - { - Key: 4, - Size: 500, - UniqueSize: 500, - }, - { - Key: 14, - Size: 3 * oneGiBInBytes, - UniqueSize: 2 * oneGiBInBytes, + { + Key: 4, + Size: 500, + UniqueSize: 500, + }, + { + Key: 14, + Size: 3 * oneGiBInBytes, + UniqueSize: 2 * oneGiBInBytes, + }, + + { + Key: 5, + Size: 500, + UniqueSize: 500, + }, + { + Key: 15, + Size: 50 * oneGiBInBytes, + UniqueSize: 50 * oneGiBInBytes, + }, + }, + } + vmCtx.VM.Status.Volumes = []vmopv1.VirtualMachineVolumeStatus{} + }) + When("moVM.config is nil", func() { + BeforeEach(func() { + vmCtx.MoVM.Config = nil + }) + Specify("status.volumes is unchanged", func() { + Expect(vmCtx.VM.Status.Volumes).To(Equal([]vmopv1.VirtualMachineVolumeStatus{})) + }) + }) + When("moVM.config.hardware.device is empty", func() { + BeforeEach(func() { + vmCtx.MoVM.Config.Hardware.Device = nil + }) + Specify("status.volumes is unchanged", func() { + Expect(vmCtx.VM.Status.Volumes).To(Equal([]vmopv1.VirtualMachineVolumeStatus{})) + }) + }) + When("moVM.layoutEx is nil", func() { + BeforeEach(func() { + vmCtx.MoVM.LayoutEx = nil + }) + Specify("status.volumes is unchanged", func() { + Expect(vmCtx.VM.Status.Volumes).To(Equal([]vmopv1.VirtualMachineVolumeStatus{})) + }) + }) + When("moVM.layoutEx.disk is empty", func() { + BeforeEach(func() { + vmCtx.MoVM.LayoutEx.Disk = nil + }) + Specify("status.volumes is unchanged", func() { + Expect(vmCtx.VM.Status.Volumes).To(Equal([]vmopv1.VirtualMachineVolumeStatus{})) + }) + }) + When("moVM.layoutEx.file is empty", func() { + BeforeEach(func() { + vmCtx.MoVM.LayoutEx.Disk = nil + }) + Specify("status.volumes is unchanged", func() { + Expect(vmCtx.VM.Status.Volumes).To(Equal([]vmopv1.VirtualMachineVolumeStatus{})) + }) + }) + When("vm.status.volumes does not have pvcs", func() { + Specify("status.volumes includes the classic disks", func() { + Expect(vmCtx.VM.Status.Volumes).To(Equal([]vmopv1.VirtualMachineVolumeStatus{ + { + Name: "my-disk-100", + DiskUUID: "100", + Type: vmopv1.VirtualMachineStorageDiskTypeClassic, + Crypto: &vmopv1.VirtualMachineVolumeCryptoStatus{ + KeyID: "my-key-id", + ProviderID: "my-provider-id", }, + Attached: true, + Limit: vmlifecycle.BytesToResourceGiB(10 * oneGiBInBytes), + Used: vmlifecycle.BytesToResourceGiB(500 + (1 * oneGiBInBytes)), + }, + { + Name: "my-disk-101", + DiskUUID: "101", + Type: vmopv1.VirtualMachineStorageDiskTypeClassic, + Attached: true, + Limit: vmlifecycle.BytesToResourceGiB(1 * oneGiBInBytes), + Used: vmlifecycle.BytesToResourceGiB(500 + (0.25 * oneGiBInBytes)), + }, + { + Name: "my-disk-102", + DiskUUID: "102", + Type: vmopv1.VirtualMachineStorageDiskTypeClassic, + Attached: true, + Limit: vmlifecycle.BytesToResourceGiB(2 * oneGiBInBytes), + Used: vmlifecycle.BytesToResourceGiB(500 + (0.5 * oneGiBInBytes)), + }, + { + Name: "my-disk-103", + DiskUUID: "103", + Type: vmopv1.VirtualMachineStorageDiskTypeClassic, + Attached: true, + Limit: vmlifecycle.BytesToResourceGiB(3 * oneGiBInBytes), + Used: vmlifecycle.BytesToResourceGiB(500 + (1 * oneGiBInBytes)), + }, + { + Name: "my-disk-104", + DiskUUID: "104", + Type: vmopv1.VirtualMachineStorageDiskTypeClassic, + Attached: true, + Limit: vmlifecycle.BytesToResourceGiB(4 * oneGiBInBytes), + Used: vmlifecycle.BytesToResourceGiB(500 + (2 * oneGiBInBytes)), + }, + })) + }) + }) - { - Key: 5, - Size: 500, - UniqueSize: 500, + When("vm.status.volumes has a pvc", func() { + BeforeEach(func() { + vmCtx.VM.Status.Volumes = []vmopv1.VirtualMachineVolumeStatus{ + { + Name: "my-disk-105", + DiskUUID: "105", + Type: vmopv1.VirtualMachineStorageDiskTypeManaged, + Attached: false, + Limit: vmlifecycle.BytesToResourceGiB(100 * oneGiBInBytes), + }, + } + }) + Specify("status.volumes includes the pvc and classic disks", func() { + Expect(vmCtx.VM.Status.Volumes).To(Equal([]vmopv1.VirtualMachineVolumeStatus{ + { + Name: "my-disk-100", + DiskUUID: "100", + Type: vmopv1.VirtualMachineStorageDiskTypeClassic, + Crypto: &vmopv1.VirtualMachineVolumeCryptoStatus{ + KeyID: "my-key-id", + ProviderID: "my-provider-id", }, - { - Key: 15, - Size: 50 * oneGiBInBytes, - UniqueSize: 50 * oneGiBInBytes, + Attached: true, + Limit: vmlifecycle.BytesToResourceGiB(10 * oneGiBInBytes), + Used: vmlifecycle.BytesToResourceGiB(500 + (1 * oneGiBInBytes)), + }, + { + Name: "my-disk-101", + DiskUUID: "101", + Type: vmopv1.VirtualMachineStorageDiskTypeClassic, + Attached: true, + Limit: vmlifecycle.BytesToResourceGiB(1 * oneGiBInBytes), + Used: vmlifecycle.BytesToResourceGiB(500 + (0.25 * oneGiBInBytes)), + }, + { + Name: "my-disk-102", + DiskUUID: "102", + Type: vmopv1.VirtualMachineStorageDiskTypeClassic, + Attached: true, + Limit: vmlifecycle.BytesToResourceGiB(2 * oneGiBInBytes), + Used: vmlifecycle.BytesToResourceGiB(500 + (0.5 * oneGiBInBytes)), + }, + { + Name: "my-disk-103", + DiskUUID: "103", + Type: vmopv1.VirtualMachineStorageDiskTypeClassic, + Attached: true, + Limit: vmlifecycle.BytesToResourceGiB(3 * oneGiBInBytes), + Used: vmlifecycle.BytesToResourceGiB(500 + (1 * oneGiBInBytes)), + }, + { + Name: "my-disk-104", + DiskUUID: "104", + Type: vmopv1.VirtualMachineStorageDiskTypeClassic, + Attached: true, + Limit: vmlifecycle.BytesToResourceGiB(4 * oneGiBInBytes), + Used: vmlifecycle.BytesToResourceGiB(500 + (2 * oneGiBInBytes)), + }, + { + Name: "my-disk-105", + DiskUUID: "105", + Type: vmopv1.VirtualMachineStorageDiskTypeManaged, + Crypto: &vmopv1.VirtualMachineVolumeCryptoStatus{ + KeyID: "my-key-id", + ProviderID: "my-provider-id", }, + Attached: false, + Limit: vmlifecycle.BytesToResourceGiB(100 * oneGiBInBytes), + Used: vmlifecycle.BytesToResourceGiB(500 + (50 * oneGiBInBytes)), + }, + })) + }) + }) + + When("vm.status.volumes has a stale classic disk", func() { + BeforeEach(func() { + vmCtx.VM.Status.Volumes = []vmopv1.VirtualMachineVolumeStatus{ + { + Name: "my-disk-106", + DiskUUID: "106", + Type: vmopv1.VirtualMachineStorageDiskTypeClassic, + Attached: true, + Limit: vmlifecycle.BytesToResourceGiB(10 * oneGiBInBytes), }, } - vmCtx.VM.Status.Volumes = []vmopv1.VirtualMachineVolumeStatus{} }) - When("moVM.config is nil", func() { + Specify("status.volumes no longer includes the stale classic disk", func() { + Expect(vmCtx.VM.Status.Volumes).To(Equal([]vmopv1.VirtualMachineVolumeStatus{ + { + Name: "my-disk-100", + DiskUUID: "100", + Type: vmopv1.VirtualMachineStorageDiskTypeClassic, + Crypto: &vmopv1.VirtualMachineVolumeCryptoStatus{ + ProviderID: "my-provider-id", + KeyID: "my-key-id", + }, + Attached: true, + Limit: vmlifecycle.BytesToResourceGiB(10 * oneGiBInBytes), + Used: vmlifecycle.BytesToResourceGiB(500 + (1 * oneGiBInBytes)), + }, + { + Name: "my-disk-101", + DiskUUID: "101", + Type: vmopv1.VirtualMachineStorageDiskTypeClassic, + Attached: true, + Limit: vmlifecycle.BytesToResourceGiB(1 * oneGiBInBytes), + Used: vmlifecycle.BytesToResourceGiB(500 + (0.25 * oneGiBInBytes)), + }, + { + Name: "my-disk-102", + DiskUUID: "102", + Type: vmopv1.VirtualMachineStorageDiskTypeClassic, + Attached: true, + Limit: vmlifecycle.BytesToResourceGiB(2 * oneGiBInBytes), + Used: vmlifecycle.BytesToResourceGiB(500 + (0.5 * oneGiBInBytes)), + }, + { + Name: "my-disk-103", + DiskUUID: "103", + Type: vmopv1.VirtualMachineStorageDiskTypeClassic, + Attached: true, + Limit: vmlifecycle.BytesToResourceGiB(3 * oneGiBInBytes), + Used: vmlifecycle.BytesToResourceGiB(500 + (1 * oneGiBInBytes)), + }, + { + Name: "my-disk-104", + DiskUUID: "104", + Type: vmopv1.VirtualMachineStorageDiskTypeClassic, + Attached: true, + Limit: vmlifecycle.BytesToResourceGiB(4 * oneGiBInBytes), + Used: vmlifecycle.BytesToResourceGiB(500 + (2 * oneGiBInBytes)), + }, + })) + }) + }) + + When("there is a classic disk w an invalid path", func() { + BeforeEach(func() { + vmCtx.MoVM.Config.Hardware. + Device[0].(*vimtypes.VirtualDisk). + Backing.(*vimtypes.VirtualDiskFlatVer2BackingInfo). + FileName = "invalid" + }) + Specify("status.volumes omits the classic disk w invalid path", func() { + Expect(vmCtx.VM.Status.Volumes).To(Equal([]vmopv1.VirtualMachineVolumeStatus{ + { + Name: "my-disk-101", + DiskUUID: "101", + Type: vmopv1.VirtualMachineStorageDiskTypeClassic, + Attached: true, + Limit: vmlifecycle.BytesToResourceGiB(1 * oneGiBInBytes), + Used: vmlifecycle.BytesToResourceGiB(500 + (0.25 * oneGiBInBytes)), + }, + { + Name: "my-disk-102", + DiskUUID: "102", + Type: vmopv1.VirtualMachineStorageDiskTypeClassic, + Attached: true, + Limit: vmlifecycle.BytesToResourceGiB(2 * oneGiBInBytes), + Used: vmlifecycle.BytesToResourceGiB(500 + (0.5 * oneGiBInBytes)), + }, + { + Name: "my-disk-103", + DiskUUID: "103", + Type: vmopv1.VirtualMachineStorageDiskTypeClassic, + Attached: true, + Limit: vmlifecycle.BytesToResourceGiB(3 * oneGiBInBytes), + Used: vmlifecycle.BytesToResourceGiB(500 + (1 * oneGiBInBytes)), + }, + { + Name: "my-disk-104", + DiskUUID: "104", + Type: vmopv1.VirtualMachineStorageDiskTypeClassic, + Attached: true, + Limit: vmlifecycle.BytesToResourceGiB(4 * oneGiBInBytes), + Used: vmlifecycle.BytesToResourceGiB(500 + (2 * oneGiBInBytes)), + }, + })) + }) + }) + }) + }) + + Context("ReadinessProbe", func() { + + var ( + chanRecord chan string + ) + + BeforeEach(func() { + chanRecord = make(chan string, 10) + + vmCtx.Context = record.WithContext( + vmCtx.Context, + record.New(&apirecord.FakeRecorder{Events: chanRecord})) + + pkgcfg.SetContext(vmCtx, func(config *pkgcfg.Config) { + config.Features.WorkloadDomainIsolation = true + config.AsyncSignalDisabled = false + }) + }) + + assertEvent := func(msg string) { + var e string + EventuallyWithOffset(1, chanRecord).Should(Receive(&e, Equal(msg))) + } + + When("there is no probe", func() { + BeforeEach(func() { + vmCtx.VM.Spec.ReadinessProbe = nil + }) + It("should not update status", func() { + Expect(conditions.Has(vmCtx.VM, vmopv1.ReadyConditionType)).To(BeFalse()) + }) + }) + + When("there is a TCP probe", func() { + BeforeEach(func() { + vmCtx.VM.Spec.ReadinessProbe = &vmopv1.VirtualMachineReadinessProbeSpec{ + TCPSocket: &vmopv1.TCPSocketAction{}, + } + }) + It("should not update status", func() { + Expect(conditions.Has(vmCtx.VM, vmopv1.ReadyConditionType)).To(BeFalse()) + }) + }) + + When("there is a GuestHeartbeat probe", func() { + BeforeEach(func() { + vmCtx.VM.Spec.ReadinessProbe = &vmopv1.VirtualMachineReadinessProbeSpec{ + GuestHeartbeat: &vmopv1.GuestHeartbeatAction{}, + } + }) + When("threshold is green", func() { + BeforeEach(func() { + vmCtx.VM.Spec.ReadinessProbe.GuestHeartbeat.ThresholdStatus = vmopv1.GreenHeartbeatStatus + }) + When("vm is green", func() { BeforeEach(func() { - vmCtx.MoVM.Config = nil + vmCtx.MoVM.GuestHeartbeatStatus = vimtypes.ManagedEntityStatusGreen }) - Specify("status.volumes is unchanged", func() { - Expect(vmCtx.VM.Status.Volumes).To(Equal([]vmopv1.VirtualMachineVolumeStatus{})) + It("should mark ready=true", func() { + Expect(conditions.IsTrue(vmCtx.VM, vmopv1.ReadyConditionType)).To(BeTrue()) + assertEvent("Normal Ready ") }) }) - When("moVM.config.hardware.device is empty", func() { + When("vm is red", func() { BeforeEach(func() { - vmCtx.MoVM.Config.Hardware.Device = nil + vmCtx.MoVM.GuestHeartbeatStatus = vimtypes.ManagedEntityStatusRed }) - Specify("status.volumes is unchanged", func() { - Expect(vmCtx.VM.Status.Volumes).To(Equal([]vmopv1.VirtualMachineVolumeStatus{})) + It("should mark ready=false", func() { + c := conditions.Get(vmCtx.VM, vmopv1.ReadyConditionType) + Expect(c).ToNot(BeNil()) + Expect(c.Status).To(Equal(metav1.ConditionFalse)) + Expect(c.Reason).To(Equal("NotReady")) + Expect(c.Message).To(Equal("heartbeat status \"red\" is below threshold")) + assertEvent("Normal NotReady heartbeat status \"red\" is below threshold") }) }) - When("moVM.layoutEx is nil", func() { + When("vm is unknown color", func() { BeforeEach(func() { - vmCtx.MoVM.LayoutEx = nil + vmCtx.MoVM.GuestHeartbeatStatus = "unknown" }) - Specify("status.volumes is unchanged", func() { - Expect(vmCtx.VM.Status.Volumes).To(Equal([]vmopv1.VirtualMachineVolumeStatus{})) + It("should mark ready=Unknown", func() { + c := conditions.Get(vmCtx.VM, vmopv1.ReadyConditionType) + Expect(c).ToNot(BeNil()) + Expect(c.Status).To(Equal(metav1.ConditionUnknown)) + assertEvent("Normal Unknown ") }) }) - When("moVM.layoutEx.disk is empty", func() { + }) + }) + + When("there is a GuestInfo probe", func() { + BeforeEach(func() { + vmCtx.MoVM.Config = &vimtypes.VirtualMachineConfigInfo{} + }) + When("specific match", func() { + BeforeEach(func() { + vmCtx.VM.Spec.ReadinessProbe = &vmopv1.VirtualMachineReadinessProbeSpec{ + GuestInfo: []vmopv1.GuestInfoAction{ + { + Key: "hello", + Value: "^world$", + }, + }, + } + }) + When("match exists", func() { BeforeEach(func() { - vmCtx.MoVM.LayoutEx.Disk = nil + vmCtx.MoVM.Config.ExtraConfig = []vimtypes.BaseOptionValue{ + &vimtypes.OptionValue{ + Key: "guestinfo.hello", + Value: "world", + }, + } }) - Specify("status.volumes is unchanged", func() { - Expect(vmCtx.VM.Status.Volumes).To(Equal([]vmopv1.VirtualMachineVolumeStatus{})) + It("should mark ready=true", func() { + Expect(conditions.IsTrue(vmCtx.VM, vmopv1.ReadyConditionType)).To(BeTrue()) + assertEvent("Normal Ready ") }) }) - When("moVM.layoutEx.file is empty", func() { - BeforeEach(func() { - vmCtx.MoVM.LayoutEx.Disk = nil - }) - Specify("status.volumes is unchanged", func() { - Expect(vmCtx.VM.Status.Volumes).To(Equal([]vmopv1.VirtualMachineVolumeStatus{})) + + When("match does not exist", func() { + It("should mark ready=false", func() { + c := conditions.Get(vmCtx.VM, vmopv1.ReadyConditionType) + Expect(c).ToNot(BeNil()) + Expect(c.Status).To(Equal(metav1.ConditionFalse)) + Expect(c.Reason).To(Equal("NotReady")) + assertEvent("Normal NotReady ") }) }) - When("vm.status.volumes does not have pvcs", func() { - Specify("status.volumes includes the classic disks", func() { - Expect(vmCtx.VM.Status.Volumes).To(Equal([]vmopv1.VirtualMachineVolumeStatus{ - { - Name: "my-disk-100", - DiskUUID: "100", - Type: vmopv1.VirtualMachineStorageDiskTypeClassic, - Crypto: &vmopv1.VirtualMachineVolumeCryptoStatus{ - KeyID: "my-key-id", - ProviderID: "my-provider-id", - }, - Attached: true, - Limit: vmlifecycle.BytesToResourceGiB(10 * oneGiBInBytes), - Used: vmlifecycle.BytesToResourceGiB(500 + (1 * oneGiBInBytes)), - }, - { - Name: "my-disk-101", - DiskUUID: "101", - Type: vmopv1.VirtualMachineStorageDiskTypeClassic, - Attached: true, - Limit: vmlifecycle.BytesToResourceGiB(1 * oneGiBInBytes), - Used: vmlifecycle.BytesToResourceGiB(500 + (0.25 * oneGiBInBytes)), - }, - { - Name: "my-disk-102", - DiskUUID: "102", - Type: vmopv1.VirtualMachineStorageDiskTypeClassic, - Attached: true, - Limit: vmlifecycle.BytesToResourceGiB(2 * oneGiBInBytes), - Used: vmlifecycle.BytesToResourceGiB(500 + (0.5 * oneGiBInBytes)), - }, - { - Name: "my-disk-103", - DiskUUID: "103", - Type: vmopv1.VirtualMachineStorageDiskTypeClassic, - Attached: true, - Limit: vmlifecycle.BytesToResourceGiB(3 * oneGiBInBytes), - Used: vmlifecycle.BytesToResourceGiB(500 + (1 * oneGiBInBytes)), - }, + }) + When("wildcard match", func() { + BeforeEach(func() { + vmCtx.VM.Spec.ReadinessProbe = &vmopv1.VirtualMachineReadinessProbeSpec{ + GuestInfo: []vmopv1.GuestInfoAction{ { - Name: "my-disk-104", - DiskUUID: "104", - Type: vmopv1.VirtualMachineStorageDiskTypeClassic, - Attached: true, - Limit: vmlifecycle.BytesToResourceGiB(4 * oneGiBInBytes), - Used: vmlifecycle.BytesToResourceGiB(500 + (2 * oneGiBInBytes)), + Key: "hello", }, - })) - }) + }, + } }) - - When("vm.status.volumes has a pvc", func() { + When("match exists", func() { BeforeEach(func() { - vmCtx.VM.Status.Volumes = []vmopv1.VirtualMachineVolumeStatus{ - { - Name: "my-disk-105", - DiskUUID: "105", - Type: vmopv1.VirtualMachineStorageDiskTypeManaged, - Attached: false, - Limit: vmlifecycle.BytesToResourceGiB(100 * oneGiBInBytes), + vmCtx.MoVM.Config.ExtraConfig = []vimtypes.BaseOptionValue{ + &vimtypes.OptionValue{ + Key: "guestinfo.hello", + Value: "there", }, } }) - Specify("status.volumes includes the pvc and classic disks", func() { - Expect(vmCtx.VM.Status.Volumes).To(Equal([]vmopv1.VirtualMachineVolumeStatus{ - { - Name: "my-disk-100", - DiskUUID: "100", - Type: vmopv1.VirtualMachineStorageDiskTypeClassic, - Crypto: &vmopv1.VirtualMachineVolumeCryptoStatus{ - KeyID: "my-key-id", - ProviderID: "my-provider-id", - }, - Attached: true, - Limit: vmlifecycle.BytesToResourceGiB(10 * oneGiBInBytes), - Used: vmlifecycle.BytesToResourceGiB(500 + (1 * oneGiBInBytes)), - }, - { - Name: "my-disk-101", - DiskUUID: "101", - Type: vmopv1.VirtualMachineStorageDiskTypeClassic, - Attached: true, - Limit: vmlifecycle.BytesToResourceGiB(1 * oneGiBInBytes), - Used: vmlifecycle.BytesToResourceGiB(500 + (0.25 * oneGiBInBytes)), - }, - { - Name: "my-disk-102", - DiskUUID: "102", - Type: vmopv1.VirtualMachineStorageDiskTypeClassic, - Attached: true, - Limit: vmlifecycle.BytesToResourceGiB(2 * oneGiBInBytes), - Used: vmlifecycle.BytesToResourceGiB(500 + (0.5 * oneGiBInBytes)), - }, - { - Name: "my-disk-103", - DiskUUID: "103", - Type: vmopv1.VirtualMachineStorageDiskTypeClassic, - Attached: true, - Limit: vmlifecycle.BytesToResourceGiB(3 * oneGiBInBytes), - Used: vmlifecycle.BytesToResourceGiB(500 + (1 * oneGiBInBytes)), - }, - { - Name: "my-disk-104", - DiskUUID: "104", - Type: vmopv1.VirtualMachineStorageDiskTypeClassic, - Attached: true, - Limit: vmlifecycle.BytesToResourceGiB(4 * oneGiBInBytes), - Used: vmlifecycle.BytesToResourceGiB(500 + (2 * oneGiBInBytes)), - }, - { - Name: "my-disk-105", - DiskUUID: "105", - Type: vmopv1.VirtualMachineStorageDiskTypeManaged, - Crypto: &vmopv1.VirtualMachineVolumeCryptoStatus{ - KeyID: "my-key-id", - ProviderID: "my-provider-id", - }, - Attached: false, - Limit: vmlifecycle.BytesToResourceGiB(100 * oneGiBInBytes), - Used: vmlifecycle.BytesToResourceGiB(500 + (50 * oneGiBInBytes)), - }, - })) + It("should mark ready=true", func() { + Expect(conditions.IsTrue(vmCtx.VM, vmopv1.ReadyConditionType)).To(BeTrue()) + assertEvent("Normal Ready ") }) }) - When("vm.status.volumes has a stale classic disk", func() { - BeforeEach(func() { - vmCtx.VM.Status.Volumes = []vmopv1.VirtualMachineVolumeStatus{ - { - Name: "my-disk-106", - DiskUUID: "106", - Type: vmopv1.VirtualMachineStorageDiskTypeClassic, - Attached: true, - Limit: vmlifecycle.BytesToResourceGiB(10 * oneGiBInBytes), - }, - } + When("match does not exist", func() { + It("should mark ready=false", func() { + c := conditions.Get(vmCtx.VM, vmopv1.ReadyConditionType) + Expect(c).ToNot(BeNil()) + Expect(c.Status).To(Equal(metav1.ConditionFalse)) + Expect(c.Reason).To(Equal("NotReady")) + assertEvent("Normal NotReady ") }) - Specify("status.volumes no longer includes the stale classic disk", func() { - Expect(vmCtx.VM.Status.Volumes).To(Equal([]vmopv1.VirtualMachineVolumeStatus{ - { - Name: "my-disk-100", - DiskUUID: "100", - Type: vmopv1.VirtualMachineStorageDiskTypeClassic, - Crypto: &vmopv1.VirtualMachineVolumeCryptoStatus{ - ProviderID: "my-provider-id", - KeyID: "my-key-id", - }, - Attached: true, - Limit: vmlifecycle.BytesToResourceGiB(10 * oneGiBInBytes), - Used: vmlifecycle.BytesToResourceGiB(500 + (1 * oneGiBInBytes)), - }, + }) + }) + + When("multiple actions", func() { + BeforeEach(func() { + vmCtx.VM.Spec.ReadinessProbe = &vmopv1.VirtualMachineReadinessProbeSpec{ + GuestInfo: []vmopv1.GuestInfoAction{ { - Name: "my-disk-101", - DiskUUID: "101", - Type: vmopv1.VirtualMachineStorageDiskTypeClassic, - Attached: true, - Limit: vmlifecycle.BytesToResourceGiB(1 * oneGiBInBytes), - Used: vmlifecycle.BytesToResourceGiB(500 + (0.25 * oneGiBInBytes)), + Key: "hello", + Value: "world|there", }, { - Name: "my-disk-102", - DiskUUID: "102", - Type: vmopv1.VirtualMachineStorageDiskTypeClassic, - Attached: true, - Limit: vmlifecycle.BytesToResourceGiB(2 * oneGiBInBytes), - Used: vmlifecycle.BytesToResourceGiB(500 + (0.5 * oneGiBInBytes)), + Key: "fu", + Value: "bar", }, - { - Name: "my-disk-103", - DiskUUID: "103", - Type: vmopv1.VirtualMachineStorageDiskTypeClassic, - Attached: true, - Limit: vmlifecycle.BytesToResourceGiB(3 * oneGiBInBytes), - Used: vmlifecycle.BytesToResourceGiB(500 + (1 * oneGiBInBytes)), + }, + } + }) + When("match exists", func() { + BeforeEach(func() { + vmCtx.MoVM.Config.ExtraConfig = []vimtypes.BaseOptionValue{ + &vimtypes.OptionValue{ + Key: "guestinfo.hello", + Value: "world", }, - { - Name: "my-disk-104", - DiskUUID: "104", - Type: vmopv1.VirtualMachineStorageDiskTypeClassic, - Attached: true, - Limit: vmlifecycle.BytesToResourceGiB(4 * oneGiBInBytes), - Used: vmlifecycle.BytesToResourceGiB(500 + (2 * oneGiBInBytes)), + &vimtypes.OptionValue{ + Key: "guestinfo.fu", + Value: "high bar", }, - })) + } + }) + It("should mark ready=true", func() { + Expect(conditions.IsTrue(vmCtx.VM, vmopv1.ReadyConditionType)).To(BeTrue()) + assertEvent("Normal Ready ") }) }) - When("there is a classic disk w an invalid path", func() { + When("match does not exist", func() { BeforeEach(func() { - vmCtx.MoVM.Config.Hardware. - Device[0].(*vimtypes.VirtualDisk). - Backing.(*vimtypes.VirtualDiskFlatVer2BackingInfo). - FileName = "invalid" - }) - Specify("status.volumes omits the classic disk w invalid path", func() { - Expect(vmCtx.VM.Status.Volumes).To(Equal([]vmopv1.VirtualMachineVolumeStatus{ - { - Name: "my-disk-101", - DiskUUID: "101", - Type: vmopv1.VirtualMachineStorageDiskTypeClassic, - Attached: true, - Limit: vmlifecycle.BytesToResourceGiB(1 * oneGiBInBytes), - Used: vmlifecycle.BytesToResourceGiB(500 + (0.25 * oneGiBInBytes)), - }, - { - Name: "my-disk-102", - DiskUUID: "102", - Type: vmopv1.VirtualMachineStorageDiskTypeClassic, - Attached: true, - Limit: vmlifecycle.BytesToResourceGiB(2 * oneGiBInBytes), - Used: vmlifecycle.BytesToResourceGiB(500 + (0.5 * oneGiBInBytes)), - }, - { - Name: "my-disk-103", - DiskUUID: "103", - Type: vmopv1.VirtualMachineStorageDiskTypeClassic, - Attached: true, - Limit: vmlifecycle.BytesToResourceGiB(3 * oneGiBInBytes), - Used: vmlifecycle.BytesToResourceGiB(500 + (1 * oneGiBInBytes)), + vmCtx.MoVM.Config.ExtraConfig = []vimtypes.BaseOptionValue{ + &vimtypes.OptionValue{ + Key: "guestinfo.hello", + Value: "there", }, - { - Name: "my-disk-104", - DiskUUID: "104", - Type: vmopv1.VirtualMachineStorageDiskTypeClassic, - Attached: true, - Limit: vmlifecycle.BytesToResourceGiB(4 * oneGiBInBytes), - Used: vmlifecycle.BytesToResourceGiB(500 + (2 * oneGiBInBytes)), - }, - })) + } + }) + It("should mark ready=false", func() { + c := conditions.Get(vmCtx.VM, vmopv1.ReadyConditionType) + Expect(c).ToNot(BeNil()) + Expect(c.Status).To(Equal(metav1.ConditionFalse)) + Expect(c.Reason).To(Equal("NotReady")) + assertEvent("Normal NotReady ") }) }) }) diff --git a/pkg/record/recorder_context.go b/pkg/record/recorder_context.go new file mode 100644 index 000000000..8f9c443bb --- /dev/null +++ b/pkg/record/recorder_context.go @@ -0,0 +1,37 @@ +// Copyright (c) 2024 VMware, Inc. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package record + +import ( + "context" + + ctxgen "github.com/vmware-tanzu/vm-operator/pkg/context/generic" +) + +type contextKeyType uint8 + +const contextKeyValue contextKeyType = 0 + +type contextValueType = Recorder + +// FromContext returns the recorder from the specified context. +func FromContext(ctx context.Context) contextValueType { + return ctxgen.FromContext( + ctx, + contextKeyValue, + func(val contextValueType) contextValueType { + return val + }) +} + +// WithContext returns a new recorder context. +func WithContext(parent context.Context, val contextValueType) context.Context { + if val == nil { + panic("recorder is nil") + } + return ctxgen.WithContext( + parent, + contextKeyValue, + func() contextValueType { return val }) +} diff --git a/pkg/record/recorder_context_test.go b/pkg/record/recorder_context_test.go new file mode 100644 index 000000000..23cf9689f --- /dev/null +++ b/pkg/record/recorder_context_test.go @@ -0,0 +1,57 @@ +// Copyright (c) 2024 VMware, Inc. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package record_test + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + apirecord "k8s.io/client-go/tools/record" + + "github.com/vmware-tanzu/vm-operator/pkg/record" +) + +var _ = Describe("WithContext", func() { + var ( + left context.Context + leftVal record.Recorder + ) + BeforeEach(func() { + left = context.Background() + leftVal = record.New(apirecord.NewFakeRecorder(0)) + }) + When("parent is nil", func() { + BeforeEach(func() { + left = nil + }) + It("should panic", func() { + fn := func() { + _ = record.WithContext(left, leftVal) + } + Expect(fn).To(PanicWith("parent context is nil")) + }) + }) + When("parent is not nil", func() { + When("value is nil", func() { + BeforeEach(func() { + leftVal = nil + }) + It("should panic", func() { + fn := func() { + _ = record.WithContext(left, leftVal) + } + Expect(fn).To(PanicWith("recorder is nil")) + }) + }) + When("value is not nil", func() { + It("should return a new context", func() { + ctx := record.WithContext(left, leftVal) + Expect(ctx).ToNot(BeNil()) + Expect(record.FromContext(ctx)).To(Equal(leftVal)) + }) + }) + }) +}) diff --git a/pkg/util/vsphere/watcher/watcher.go b/pkg/util/vsphere/watcher/watcher.go index 2f7f97a72..1dc9bfff9 100644 --- a/pkg/util/vsphere/watcher/watcher.go +++ b/pkg/util/vsphere/watcher/watcher.go @@ -29,6 +29,7 @@ func DefaultWatchedPropertyPaths() []string { "config.keyId", "guest.ipStack", "guest.net", + "guestHeartbeatStatus", "summary.config.name", "summary.guest", "summary.overallStatus", diff --git a/test/builder/vcsim_test_context.go b/test/builder/vcsim_test_context.go index 276afce6d..82c5d6943 100644 --- a/test/builder/vcsim_test_context.go +++ b/test/builder/vcsim_test_context.go @@ -297,6 +297,8 @@ func newTestContextForVCSim( fakeRecorder, _ := NewFakeRecorder() + parentCtx = record.WithContext(parentCtx, fakeRecorder) + ctx := &TestContextForVCSim{ UnitTestContext: NewUnitTestContextWithParentContext(parentCtx, initObjects...), PodNamespace: "vmop-pod-test", @@ -312,6 +314,7 @@ func newTestContextForVCSim( ctx.ClustersPerZone = clustersPerZone // TODO: this can be removed once FSS_WCP_WORKLOAD_DOMAIN_ISOLATION enabled. ctx.withWorkloadIsolation = config.WithWorkloadIsolation + return ctx }