diff --git a/internal/controller/cisco/nx/bordergateway_controller_test.go b/internal/controller/cisco/nx/bordergateway_controller_test.go index d72cb015d..604d76dde 100644 --- a/internal/controller/cisco/nx/bordergateway_controller_test.go +++ b/internal/controller/cisco/nx/bordergateway_controller_test.go @@ -8,6 +8,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -76,6 +77,12 @@ var _ = Describe("BorderGateway Controller", func() { bg.Namespace = metav1.NamespaceDefault Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, bg))).To(Succeed()) + By("Waiting for BorderGateway to be fully deleted") + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, key, &nxv1alpha1.BorderGateway{}) + g.Expect(errors.IsNotFound(err)).To(BeTrue()) + }).Should(Succeed()) + By("Ensuring the resource is deleted from the provider") Eventually(func(g Gomega) { g.Expect(testProvider.BorderGateway).To(BeNil(), "Provider BorderGateway settings should be reset after deletion") diff --git a/internal/controller/cisco/nx/system_controller_test.go b/internal/controller/cisco/nx/system_controller_test.go index ebd3670ab..08deb0686 100644 --- a/internal/controller/cisco/nx/system_controller_test.go +++ b/internal/controller/cisco/nx/system_controller_test.go @@ -6,6 +6,7 @@ package nx import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -57,6 +58,12 @@ var _ = Describe("System Controller", func() { system.Namespace = metav1.NamespaceDefault Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, system))).To(Succeed()) + By("Waiting for System to be fully deleted") + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, key, &nxv1alpha1.System{}) + g.Expect(errors.IsNotFound(err)).To(BeTrue()) + }).Should(Succeed()) + By("Ensuring the resource is deleted from the provider") Eventually(func(g Gomega) { g.Expect(testProvider.Settings).To(BeNil(), "Provider System settings should be reset after deletion") diff --git a/internal/controller/cisco/nx/vpcdomain_controller_test.go b/internal/controller/cisco/nx/vpcdomain_controller_test.go index f4a1abd5d..be9de0f01 100644 --- a/internal/controller/cisco/nx/vpcdomain_controller_test.go +++ b/internal/controller/cisco/nx/vpcdomain_controller_test.go @@ -6,6 +6,7 @@ package nx import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -114,6 +115,12 @@ var _ = Describe("VPCDomain Controller", func() { By("Cleanup the specific resource instance VPCDomain") Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) + By("Waiting for VPCDomain to be fully deleted") + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, vpcdomainKey, &nxv1.VPCDomain{}) + g.Expect(errors.IsNotFound(err)).To(BeTrue()) + }).Should(Succeed()) + By("Ensuring the resource is deleted from the provider") Eventually(func(g Gomega) { g.Expect(testProvider.VPCDomain).To(BeNil(), "Provider VPCDomain should be nil") @@ -292,6 +299,12 @@ var _ = Describe("VPCDomain Controller", func() { By("Cleanup the VPCDomain") Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) + By("Waiting for VPCDomain to be fully deleted") + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, vpcdomainKey, &nxv1.VPCDomain{}) + g.Expect(errors.IsNotFound(err)).To(BeTrue()) + }).Should(Succeed()) + By("Cleanup Interface and VRF resources") for _, ifName := range []string{name + "-phys", name + "-po", name + "-phys-b", name + "-po-b", name + "-lo0"} { intf := &corev1.Interface{} diff --git a/internal/controller/core/acl_controller_test.go b/internal/controller/core/acl_controller_test.go index bd2294365..31aa84edf 100644 --- a/internal/controller/core/acl_controller_test.go +++ b/internal/controller/core/acl_controller_test.go @@ -8,6 +8,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -76,7 +77,13 @@ var _ = Describe("AccessControlList Controller", func() { By("Verifying the resource is removed from the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.ACLs.Has(name)).To(BeFalse(), "Provider shouldn't have AccessControlList configured anymore") + g.Expect(testDevices.StateFor(name).ACLs.Has(name)).To(BeFalse(), "Provider shouldn't have AccessControlList configured anymore") + }).Should(Succeed()) + + By("Waiting for the AccessControlList to be fully deleted") + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, key, &v1alpha1.AccessControlList{}) + g.Expect(apierrors.IsNotFound(err)).To(BeTrue()) }).Should(Succeed()) By("Cleaning up the Device resource") @@ -123,7 +130,7 @@ var _ = Describe("AccessControlList Controller", func() { By("Ensuring the resource is created in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.ACLs.Has(name)).To(BeTrue(), "Provider should have AccessControlList configured") + g.Expect(testDevices.StateFor(name).ACLs.Has(name)).To(BeTrue(), "Provider should have AccessControlList configured") }).Should(Succeed()) }) }) diff --git a/internal/controller/core/banner_controller_test.go b/internal/controller/core/banner_controller_test.go index 22cdfc9a2..9fc1f11ee 100644 --- a/internal/controller/core/banner_controller_test.go +++ b/internal/controller/core/banner_controller_test.go @@ -6,6 +6,7 @@ package core import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -45,8 +46,14 @@ var _ = Describe("Banner Controller", func() { By("Verifying the resource is removed from the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.PreLoginBanner).To(BeNil(), "Provider PreLogin Banner should be nil") - g.Expect(testProvider.PostLoginBanner).To(BeNil(), "Provider PostLogin Banner should be nil") + g.Expect(testDevices.StateFor(name).PreLoginBanner).To(BeNil(), "Provider PreLogin Banner should be nil") + g.Expect(testDevices.StateFor(name).PostLoginBanner).To(BeNil(), "Provider PostLogin Banner should be nil") + }).Should(Succeed()) + + By("Waiting for the Banner to be fully deleted") + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, key, &v1alpha1.Banner{}) + g.Expect(apierrors.IsNotFound(err)).To(BeTrue()) }).Should(Succeed()) By("Cleaning up the Device resource") @@ -107,10 +114,10 @@ var _ = Describe("Banner Controller", func() { By("Ensuring the resource is created in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.PreLoginBanner).ToNot(BeNil(), "Provider Banner should not be nil") - g.Expect(testProvider.PostLoginBanner).To(BeNil(), "Provider PostLogin Banner should be nil") - if testProvider.PreLoginBanner != nil { - g.Expect(*testProvider.PreLoginBanner).To(Equal("Test Banner")) + g.Expect(testDevices.StateFor(name).PreLoginBanner).ToNot(BeNil(), "Provider Banner should not be nil") + g.Expect(testDevices.StateFor(name).PostLoginBanner).To(BeNil(), "Provider PostLogin Banner should be nil") + if testDevices.StateFor(name).PreLoginBanner != nil { + g.Expect(*testDevices.StateFor(name).PreLoginBanner).To(Equal("Test Banner")) } }).Should(Succeed()) }) @@ -166,10 +173,10 @@ var _ = Describe("Banner Controller", func() { By("Ensuring the resource is created in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.PreLoginBanner).To(BeNil(), "Provider PreLogin Banner should be nil") - g.Expect(testProvider.PostLoginBanner).ToNot(BeNil(), "Provider PostLogin Banner should not be nil") - if testProvider.PostLoginBanner != nil { - g.Expect(*testProvider.PostLoginBanner).To(Equal("Test Banner")) + g.Expect(testDevices.StateFor(name).PreLoginBanner).To(BeNil(), "Provider PreLogin Banner should be nil") + g.Expect(testDevices.StateFor(name).PostLoginBanner).ToNot(BeNil(), "Provider PostLogin Banner should not be nil") + if testDevices.StateFor(name).PostLoginBanner != nil { + g.Expect(*testDevices.StateFor(name).PostLoginBanner).To(Equal("Test Banner")) } }).Should(Succeed()) }) diff --git a/internal/controller/core/bgp_controller_test.go b/internal/controller/core/bgp_controller_test.go index 8f7238b68..0f958bc2b 100644 --- a/internal/controller/core/bgp_controller_test.go +++ b/internal/controller/core/bgp_controller_test.go @@ -34,23 +34,25 @@ var _ = Describe("BGP Controller", func() { }) AfterEach(func() { + // Use the manager client for MatchingFields queries — the direct k8sClient + // does not have the custom field indexes registered on the API server. By("Cleaning up BGP resources for this device") bgpList := &v1alpha1.BGPList{} - Expect(k8sClient.List(ctx, bgpList, client.InNamespace(metav1.NamespaceDefault), client.MatchingLabels{v1alpha1.DeviceLabel: device.Name})).To(Succeed()) + Expect(k8sManager.GetClient().List(ctx, bgpList, client.InNamespace(metav1.NamespaceDefault), client.MatchingFields{v1alpha1.DeviceRefIndexKey: device.Name})).To(Succeed()) for i := range bgpList.Items { Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, &bgpList.Items[i]))).To(Succeed()) } By("Cleaning up VRF resources for this device") vrfList := &v1alpha1.VRFList{} - Expect(k8sClient.List(ctx, vrfList, client.InNamespace(metav1.NamespaceDefault), client.MatchingLabels{v1alpha1.DeviceLabel: device.Name})).To(Succeed()) + Expect(k8sManager.GetClient().List(ctx, vrfList, client.InNamespace(metav1.NamespaceDefault), client.MatchingFields{v1alpha1.DeviceRefIndexKey: device.Name})).To(Succeed()) for i := range vrfList.Items { Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, &vrfList.Items[i]))).To(Succeed()) } By("Cleaning up RoutingPolicy resources for this device") rpList := &v1alpha1.RoutingPolicyList{} - Expect(k8sClient.List(ctx, rpList, client.InNamespace(metav1.NamespaceDefault), client.MatchingLabels{v1alpha1.DeviceLabel: device.Name})).To(Succeed()) + Expect(k8sManager.GetClient().List(ctx, rpList, client.InNamespace(metav1.NamespaceDefault), client.MatchingFields{v1alpha1.DeviceRefIndexKey: device.Name})).To(Succeed()) for i := range rpList.Items { Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, &rpList.Items[i]))).To(Succeed()) } @@ -58,13 +60,27 @@ var _ = Describe("BGP Controller", func() { By("Waiting for BGP resources to be fully deleted") Eventually(func(g Gomega) { list := &v1alpha1.BGPList{} - g.Expect(k8sClient.List(ctx, list, client.InNamespace(metav1.NamespaceDefault), client.MatchingLabels{v1alpha1.DeviceLabel: device.Name})).To(Succeed()) + g.Expect(k8sManager.GetClient().List(ctx, list, client.InNamespace(metav1.NamespaceDefault), client.MatchingFields{v1alpha1.DeviceRefIndexKey: device.Name})).To(Succeed()) + g.Expect(list.Items).To(BeEmpty()) + }).Should(Succeed()) + + By("Waiting for VRF resources to be fully deleted") + Eventually(func(g Gomega) { + list := &v1alpha1.VRFList{} + g.Expect(k8sManager.GetClient().List(ctx, list, client.InNamespace(metav1.NamespaceDefault), client.MatchingFields{v1alpha1.DeviceRefIndexKey: device.Name})).To(Succeed()) + g.Expect(list.Items).To(BeEmpty()) + }).Should(Succeed()) + + By("Waiting for RoutingPolicy resources to be fully deleted") + Eventually(func(g Gomega) { + list := &v1alpha1.RoutingPolicyList{} + g.Expect(k8sManager.GetClient().List(ctx, list, client.InNamespace(metav1.NamespaceDefault), client.MatchingFields{v1alpha1.DeviceRefIndexKey: device.Name})).To(Succeed()) g.Expect(list.Items).To(BeEmpty()) }).Should(Succeed()) By("Verifying BGP is removed from the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.BGP).To(BeNil(), "Provider should not have BGP instance configured") + g.Expect(testDevices.StateFor(device.Name).BGP).To(BeNil(), "Provider should not have BGP instance configured") }).Should(Succeed()) By("Deleting the Device resource") @@ -120,7 +136,7 @@ var _ = Describe("BGP Controller", func() { By("Ensuring the resource is created in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.BGP).ToNot(BeNil(), "Provider should have BGP instance configured") + g.Expect(testDevices.StateFor(device.Name).BGP).ToNot(BeNil(), "Provider should have BGP instance configured") }).Should(Succeed()) }) @@ -176,8 +192,8 @@ var _ = Describe("BGP Controller", func() { By("Ensuring the provider receives the VRF") Eventually(func(g Gomega) { - g.Expect(testProvider.BGPVRF).ToNot(BeNil()) - g.Expect(testProvider.BGPVRF.Spec.Name).To(Equal("CC-MGMT")) + g.Expect(testDevices.StateFor(device.Name).BGPVRF).ToNot(BeNil()) + g.Expect(testDevices.StateFor(device.Name).BGPVRF.Spec.Name).To(Equal("CC-MGMT")) }).Should(Succeed()) By("Ensuring ReadyCondition is True") diff --git a/internal/controller/core/bgp_peer_controller_test.go b/internal/controller/core/bgp_peer_controller_test.go index ee9fa6b0d..b160575c2 100644 --- a/internal/controller/core/bgp_peer_controller_test.go +++ b/internal/controller/core/bgp_peer_controller_test.go @@ -35,23 +35,25 @@ var _ = Describe("BGPPeer Controller", func() { }) AfterEach(func() { + // Use the manager client for MatchingFields queries — the direct k8sClient + // does not have the custom field indexes registered on the API server. By("Cleaning up BGPPeer resources for this device") peerList := &v1alpha1.BGPPeerList{} - Expect(k8sClient.List(ctx, peerList, client.InNamespace(metav1.NamespaceDefault), client.MatchingLabels{v1alpha1.DeviceLabel: device.Name})).To(Succeed()) + Expect(k8sManager.GetClient().List(ctx, peerList, client.InNamespace(metav1.NamespaceDefault), client.MatchingFields{v1alpha1.DeviceRefIndexKey: device.Name})).To(Succeed()) for i := range peerList.Items { Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, &peerList.Items[i]))).To(Succeed()) } By("Cleaning up BGP resources for this device") bgpList := &v1alpha1.BGPList{} - Expect(k8sClient.List(ctx, bgpList, client.InNamespace(metav1.NamespaceDefault), client.MatchingLabels{v1alpha1.DeviceLabel: device.Name})).To(Succeed()) + Expect(k8sManager.GetClient().List(ctx, bgpList, client.InNamespace(metav1.NamespaceDefault), client.MatchingFields{v1alpha1.DeviceRefIndexKey: device.Name})).To(Succeed()) for i := range bgpList.Items { Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, &bgpList.Items[i]))).To(Succeed()) } By("Cleaning up Interface resources for this device") intfList := &v1alpha1.InterfaceList{} - Expect(k8sClient.List(ctx, intfList, client.InNamespace(metav1.NamespaceDefault), client.MatchingLabels{v1alpha1.DeviceLabel: device.Name})).To(Succeed()) + Expect(k8sManager.GetClient().List(ctx, intfList, client.InNamespace(metav1.NamespaceDefault), client.MatchingFields{v1alpha1.DeviceRefIndexKey: device.Name})).To(Succeed()) for i := range intfList.Items { Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, &intfList.Items[i]))).To(Succeed()) } @@ -59,13 +61,27 @@ var _ = Describe("BGPPeer Controller", func() { By("Waiting for BGPPeer resources to be fully deleted") Eventually(func(g Gomega) { list := &v1alpha1.BGPPeerList{} - g.Expect(k8sClient.List(ctx, list, client.InNamespace(metav1.NamespaceDefault), client.MatchingLabels{v1alpha1.DeviceLabel: device.Name})).To(Succeed()) + g.Expect(k8sManager.GetClient().List(ctx, list, client.InNamespace(metav1.NamespaceDefault), client.MatchingFields{v1alpha1.DeviceRefIndexKey: device.Name})).To(Succeed()) + g.Expect(list.Items).To(BeEmpty()) + }).Should(Succeed()) + + By("Waiting for BGP resources to be fully deleted") + Eventually(func(g Gomega) { + list := &v1alpha1.BGPList{} + g.Expect(k8sManager.GetClient().List(ctx, list, client.InNamespace(metav1.NamespaceDefault), client.MatchingFields{v1alpha1.DeviceRefIndexKey: device.Name})).To(Succeed()) + g.Expect(list.Items).To(BeEmpty()) + }).Should(Succeed()) + + By("Waiting for Interface resources to be fully deleted") + Eventually(func(g Gomega) { + list := &v1alpha1.InterfaceList{} + g.Expect(k8sManager.GetClient().List(ctx, list, client.InNamespace(metav1.NamespaceDefault), client.MatchingFields{v1alpha1.DeviceRefIndexKey: device.Name})).To(Succeed()) g.Expect(list.Items).To(BeEmpty()) }).Should(Succeed()) By("Verifying BGP peer is removed from the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.BGPPeers.Len()).To(Equal(0), "Provider should not have any BGP peers configured") + g.Expect(testDevices.StateFor(device.Name).BGPPeers.Len()).To(Equal(0), "Provider should not have any BGP peers configured") }).Should(Succeed()) By("Deleting the Device resource") @@ -145,7 +161,7 @@ var _ = Describe("BGPPeer Controller", func() { By("Verifying the BGP peer is configured in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.BGPPeers.Has(host)).To(BeTrue(), "Provider should have BGP peer configured") + g.Expect(testDevices.StateFor(device.Name).BGPPeers.Has(host)).To(BeTrue(), "Provider should have BGP peer configured") }).Should(Succeed()) }) @@ -215,7 +231,7 @@ var _ = Describe("BGPPeer Controller", func() { By("Verifying the BGP peer is configured in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.BGPPeers.Has(host)).To(BeTrue(), "Provider should have BGP peer configured") + g.Expect(testDevices.StateFor(device.Name).BGPPeers.Has(host)).To(BeTrue(), "Provider should have BGP peer configured") }).Should(Succeed()) }) @@ -378,7 +394,7 @@ var _ = Describe("BGPPeer Controller", func() { By("Verifying the BGP peer is NOT configured in the provider") Consistently(func(g Gomega) { - g.Expect(testProvider.BGPPeers.Has(host)).To(BeFalse(), "Provider should not have BGP peer configured") + g.Expect(testDevices.StateFor(device.Name).BGPPeers.Has(host)).To(BeFalse(), "Provider should not have BGP peer configured") }).Should(Succeed()) }) @@ -429,7 +445,7 @@ var _ = Describe("BGPPeer Controller", func() { By("Verifying the BGP peer is NOT configured in the provider") Consistently(func(g Gomega) { - g.Expect(testProvider.BGPPeers.Has("10.0.0.3")).To(BeFalse(), "Provider should not have BGP peer configured") + g.Expect(testDevices.StateFor(device.Name).BGPPeers.Has("10.0.0.3")).To(BeFalse(), "Provider should not have BGP peer configured") }).Should(Succeed()) }) diff --git a/internal/controller/core/certificate_controller_test.go b/internal/controller/core/certificate_controller_test.go index 33a2ccdc7..af58bd588 100644 --- a/internal/controller/core/certificate_controller_test.go +++ b/internal/controller/core/certificate_controller_test.go @@ -17,6 +17,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -89,7 +90,13 @@ var _ = Describe("Certificate Controller", func() { By("Verifying the resource is removed from the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.Certs.Has("cert1")).To(BeFalse(), "Certificate should be deleted from the provider") + g.Expect(testDevices.StateFor(name).Certs.Has("cert1")).To(BeFalse(), "Certificate should be deleted from the provider") + }).Should(Succeed()) + + By("Waiting for the Certificate to be fully deleted") + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, key, &v1alpha1.Certificate{}) + g.Expect(apierrors.IsNotFound(err)).To(BeTrue()) }).Should(Succeed()) By("Cleaning up the Device resource") @@ -136,7 +143,7 @@ var _ = Describe("Certificate Controller", func() { By("Ensuring the resource is created in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.Certs.Has("cert1")).To(BeTrue(), "Certificate should be present in the provider") + g.Expect(testDevices.StateFor(name).Certs.Has("cert1")).To(BeTrue(), "Certificate should be present in the provider") }).Should(Succeed()) }) }) diff --git a/internal/controller/core/configbackup_controller_test.go b/internal/controller/core/configbackup_controller_test.go index 0b8df0f71..e409c4814 100644 --- a/internal/controller/core/configbackup_controller_test.go +++ b/internal/controller/core/configbackup_controller_test.go @@ -52,10 +52,10 @@ var _ = Describe("ConfigBackup Controller", func() { }).Should(Succeed()) By("Resetting the test provider state") - testProvider.Lock() - testProvider.ConfigBackups = nil - testProvider.StartupConfig = nil - testProvider.Unlock() + testDevices.StateFor(device.Name).Lock() + testDevices.StateFor(device.Name).ConfigBackups = nil + testDevices.StateFor(device.Name).StartupConfig = nil + testDevices.StateFor(device.Name).Unlock() }) AfterEach(func() { @@ -156,22 +156,22 @@ var _ = Describe("ConfigBackup Controller", func() { By("Verifying the provider received the startup backup") Eventually(func(g Gomega) { - testProvider.Lock() - defer testProvider.Unlock() - g.Expect(testProvider.StartupConfig).NotTo(BeNil()) + testDevices.StateFor(device.Name).Lock() + defer testDevices.StateFor(device.Name).Unlock() + g.Expect(testDevices.StateFor(device.Name).StartupConfig).NotTo(BeNil()) }).Should(Succeed()) }) It("Should rotate old backups according to retention policy", func() { By("Pre-seeding the provider with existing backups") - testProvider.Lock() + testDevices.StateFor(device.Name).Lock() size := int64(1024) - testProvider.ConfigBackups = []*provider.ConfigBackupFile{ + testDevices.StateFor(device.Name).ConfigBackups = []*provider.ConfigBackupFile{ {Path: "bootflash:///backups/configbackup-old-1", SizeBytes: &size, CreatedAt: time.Date(2026, time.April, 10, 2, 0, 0, 0, time.UTC)}, {Path: "bootflash:///backups/configbackup-old-2", SizeBytes: &size, CreatedAt: time.Date(2026, time.April, 11, 2, 0, 0, 0, time.UTC)}, {Path: "bootflash:///backups/configbackup-old-3", SizeBytes: &size, CreatedAt: time.Date(2026, time.April, 12, 2, 0, 0, 0, time.UTC)}, } - testProvider.Unlock() + testDevices.StateFor(device.Name).Unlock() By("Creating a ConfigBackup with retention keepLast: 2") backup = &v1alpha1.ConfigBackup{ @@ -188,10 +188,10 @@ var _ = Describe("ConfigBackup Controller", func() { By("Verifying old backups are rotated") Eventually(func(g Gomega) { - testProvider.Lock() - defer testProvider.Unlock() + testDevices.StateFor(device.Name).Lock() + defer testDevices.StateFor(device.Name).Unlock() // 3 pre-seeded + 1 new = 4, keepLast=2 means 2 oldest deleted → 2 remain - g.Expect(testProvider.ConfigBackups).To(HaveLen(2)) + g.Expect(testDevices.StateFor(device.Name).ConfigBackups).To(HaveLen(2)) }).Should(Succeed()) By("Verifying the status reflects the retained count") @@ -205,13 +205,13 @@ var _ = Describe("ConfigBackup Controller", func() { It("Should block backup when storage threshold is exceeded", func() { By("Pre-seeding the provider to simulate full storage") - testProvider.Lock() + testDevices.StateFor(device.Name).Lock() size := int64(95) - testProvider.ConfigBackups = []*provider.ConfigBackupFile{ + testDevices.StateFor(device.Name).ConfigBackups = []*provider.ConfigBackupFile{ {Path: "bootflash:///backups/existing-file", SizeBytes: &size, CreatedAt: time.Now()}, } - testProvider.StorageTotal = 100 - testProvider.Unlock() + testDevices.StateFor(device.Name).StorageTotal = 100 + testDevices.StateFor(device.Name).Unlock() By("Creating a ConfigBackup with a storage threshold") minFreeBytes := int64(10) @@ -264,9 +264,9 @@ var _ = Describe("ConfigBackup Controller", func() { By("Verifying the provider only has one backup (no re-runs)") Consistently(func(g Gomega) { - testProvider.Lock() - defer testProvider.Unlock() - g.Expect(testProvider.ConfigBackups).To(HaveLen(1)) + testDevices.StateFor(device.Name).Lock() + defer testDevices.StateFor(device.Name).Unlock() + g.Expect(testDevices.StateFor(device.Name).ConfigBackups).To(HaveLen(1)) }).Should(Succeed()) }) diff --git a/internal/controller/core/device_controller_test.go b/internal/controller/core/device_controller_test.go index 4a94dd5bd..8d7af776b 100644 --- a/internal/controller/core/device_controller_test.go +++ b/internal/controller/core/device_controller_test.go @@ -10,6 +10,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" "sigs.k8s.io/controller-runtime/pkg/client" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -47,6 +48,12 @@ var _ = Describe("Device Controller", func() { device.Namespace = metav1.NamespaceDefault Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, device))).To(Succeed()) + By("Waiting for Device to be fully deleted") + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, client.ObjectKey{Name: name, Namespace: metav1.NamespaceDefault}, &v1alpha1.Device{}) + g.Expect(apierrors.IsNotFound(err)).To(BeTrue()) + }).Should(Succeed()) + By("Cleanup the specific resource instance Secret") secret := &corev1.Secret{} secret.Name = name @@ -377,10 +384,10 @@ var _ = Describe("Device Controller", func() { It("Should set Reachable=False and Ready=Unknown when the device is unreachable", func() { By("Making the provider return a connect error") - testProvider.SetConnectError(errors.New("connection refused")) + testDevices.StateFor(name).SetConnectFailure(errors.New("connection refused")) DeferCleanup(func() { - testProvider.SetConnectError(nil) + testDevices.StateFor(name).SetConnectFailure(nil) }) By("Creating the custom resource for the Kind Device") @@ -415,7 +422,7 @@ var _ = Describe("Device Controller", func() { }).Should(Succeed()) By("Clearing the connect error to simulate recovery") - testProvider.SetConnectError(nil) + testDevices.StateFor(name).SetConnectFailure(nil) By("Verifying Reachable=True and Ready=True after recovery") Eventually(func(g Gomega) { @@ -568,9 +575,9 @@ var _ = Describe("Device Controller", func() { By("Advancing the reboot time in the provider to simulate a device reboot") newRebootTime := lastRebootTime.Add(time.Hour) - testProvider.SetLastRebootTime(newRebootTime) + testDevices.StateFor(name).SetLastRebootTime(newRebootTime) DeferCleanup(func() { - testProvider.SetLastRebootTime(lastRebootTime) + testDevices.StateFor(name).SetLastRebootTime(lastRebootTime) }) By("Verifying LastRebootTime in status is updated to the new value") diff --git a/internal/controller/core/dhcprelay_controller_test.go b/internal/controller/core/dhcprelay_controller_test.go index ac066a433..c02c36d73 100644 --- a/internal/controller/core/dhcprelay_controller_test.go +++ b/internal/controller/core/dhcprelay_controller_test.go @@ -128,7 +128,7 @@ var _ = Describe("DHCPRelay Controller", func() { By("Verifying the resource has been deleted") Eventually(func(g Gomega) { - g.Expect(testProvider.DHCPRelay).To(BeNil(), "Provider should have no DHCPRelay configured") + g.Expect(testDevices.StateFor(deviceName).DHCPRelay).To(BeNil(), "Provider should have no DHCPRelay configured") }).Should(Succeed()) By("Cleaning up the Device resource") @@ -197,9 +197,9 @@ var _ = Describe("DHCPRelay Controller", func() { By("Ensuring the DHCPRelay is created in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.DHCPRelay).ToNot(BeNil(), "Provider DHCPRelay should not be nil") - if testProvider.DHCPRelay != nil { - g.Expect(testProvider.DHCPRelay.GetName()).To(Equal(resourceName), "Provider should have DHCPRelay configured") + g.Expect(testDevices.StateFor(deviceName).DHCPRelay).ToNot(BeNil(), "Provider DHCPRelay should not be nil") + if testDevices.StateFor(deviceName).DHCPRelay != nil { + g.Expect(testDevices.StateFor(deviceName).DHCPRelay.GetName()).To(Equal(resourceName), "Provider should have DHCPRelay configured") } }).Should(Succeed()) }) @@ -288,7 +288,7 @@ var _ = Describe("DHCPRelay Controller", func() { By("Verifying DHCPRelay is created in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.DHCPRelay).ToNot(BeNil()) + g.Expect(testDevices.StateFor(deviceName).DHCPRelay).ToNot(BeNil()) }).Should(Succeed()) By("Deleting the DHCPRelay resource") @@ -296,7 +296,7 @@ var _ = Describe("DHCPRelay Controller", func() { By("Verifying the DHCPRelay is removed from the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.DHCPRelay).To(BeNil(), "Provider should have no DHCPRelay configured after deletion") + g.Expect(testDevices.StateFor(deviceName).DHCPRelay).To(BeNil(), "Provider should have no DHCPRelay configured after deletion") }).Should(Succeed()) By("Verifying the resource is fully deleted") @@ -517,12 +517,20 @@ var _ = Describe("DHCPRelay Controller", func() { otherIntf.Name = otherIntfKey.Name otherIntf.Namespace = otherIntfKey.Namespace Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, otherIntf))).To(Succeed()) + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, otherIntfKey, &v1alpha1.Interface{}) + g.Expect(errors.IsNotFound(err)).To(BeTrue()) + }).Should(Succeed()) By("Cleaning up the VLAN resource") otherVlan := &v1alpha1.VLAN{} otherVlan.Name = otherVlanKey.Name otherVlan.Namespace = otherVlanKey.Namespace Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, otherVlan))).To(Succeed()) + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, otherVlanKey, &v1alpha1.VLAN{}) + g.Expect(errors.IsNotFound(err)).To(BeTrue()) + }).Should(Succeed()) By("Cleaning up the Device resources") device := &v1alpha1.Device{} @@ -687,18 +695,30 @@ var _ = Describe("DHCPRelay Controller", func() { otherVrf.Name = otherVrfKey.Name otherVrf.Namespace = otherVrfKey.Namespace Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, otherVrf))).To(Succeed()) + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, otherVrfKey, &v1alpha1.VRF{}) + g.Expect(errors.IsNotFound(err)).To(BeTrue()) + }).Should(Succeed()) By("Cleaning up the Interface resource") intf := &v1alpha1.Interface{} intf.Name = interfaceKey.Name intf.Namespace = interfaceKey.Namespace Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, intf))).To(Succeed()) + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, interfaceKey, &v1alpha1.Interface{}) + g.Expect(errors.IsNotFound(err)).To(BeTrue()) + }).Should(Succeed()) By("Cleaning up the VLAN resource") vlan := &v1alpha1.VLAN{} vlan.Name = vlanKey.Name vlan.Namespace = vlanKey.Namespace Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, vlan))).To(Succeed()) + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, vlanKey, &v1alpha1.VLAN{}) + g.Expect(errors.IsNotFound(err)).To(BeTrue()) + }).Should(Succeed()) By("Cleaning up the Device resources") device := &v1alpha1.Device{} @@ -857,7 +877,7 @@ var _ = Describe("DHCPRelay Controller", func() { By("Verifying the provider has been cleaned up") Eventually(func(g Gomega) { - g.Expect(testProvider.DHCPRelay).To(BeNil(), "Provider should have no DHCPRelay configured") + g.Expect(testDevices.StateFor(deviceName).DHCPRelay).To(BeNil(), "Provider should have no DHCPRelay configured") }).Should(Succeed()) By("Cleaning up the Device resource") @@ -903,7 +923,7 @@ var _ = Describe("DHCPRelay Controller", func() { By("Ensuring the DHCPRelay is created in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.DHCPRelay).ToNot(BeNil(), "Provider DHCPRelay should not be nil") + g.Expect(testDevices.StateFor(deviceName).DHCPRelay).ToNot(BeNil(), "Provider DHCPRelay should not be nil") }).Should(Succeed()) }) }) @@ -1001,12 +1021,20 @@ var _ = Describe("DHCPRelay Controller", func() { i.Name = interfaceKey.Name i.Namespace = interfaceKey.Namespace Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, i))).To(Succeed()) + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, interfaceKey, &v1alpha1.Interface{}) + g.Expect(errors.IsNotFound(err)).To(BeTrue()) + }).Should(Succeed()) By("Cleaning up the VLAN resource") vlan := &v1alpha1.VLAN{} vlan.Name = vlanKey.Name vlan.Namespace = vlanKey.Namespace Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, vlan))).To(Succeed()) + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, vlanKey, &v1alpha1.VLAN{}) + g.Expect(errors.IsNotFound(err)).To(BeTrue()) + }).Should(Succeed()) By("Cleaning up the Device resource") device := &v1alpha1.Device{} diff --git a/internal/controller/core/dns_controller_test.go b/internal/controller/core/dns_controller_test.go index d1eb18aa2..ba91dca17 100644 --- a/internal/controller/core/dns_controller_test.go +++ b/internal/controller/core/dns_controller_test.go @@ -6,6 +6,7 @@ package core import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -62,7 +63,13 @@ var _ = Describe("DNS Controller", func() { By("Verifying the resource is removed from the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.DNS).To(BeNil(), "Provider DNS should be nil") + g.Expect(testDevices.StateFor(name).DNS).To(BeNil(), "Provider DNS should be nil") + }).Should(Succeed()) + + By("Waiting for the DNS to be fully deleted") + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, key, &v1alpha1.DNS{}) + g.Expect(apierrors.IsNotFound(err)).To(BeTrue()) }).Should(Succeed()) By("Cleaning up the Device resource") @@ -109,9 +116,9 @@ var _ = Describe("DNS Controller", func() { By("Ensuring the resource is created in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.DNS).ToNot(BeNil(), "Provider DNS should not be nil") - if testProvider.DNS != nil { - g.Expect(testProvider.DNS.Spec.Domain).To(Equal("example.com")) + g.Expect(testDevices.StateFor(name).DNS).ToNot(BeNil(), "Provider DNS should not be nil") + if testDevices.StateFor(name).DNS != nil { + g.Expect(testDevices.StateFor(name).DNS.Spec.Domain).To(Equal("example.com")) } }).Should(Succeed()) }) diff --git a/internal/controller/core/ethernetsegment_controller_test.go b/internal/controller/core/ethernetsegment_controller_test.go index ff3bdccaa..6b1e70d78 100644 --- a/internal/controller/core/ethernetsegment_controller_test.go +++ b/internal/controller/core/ethernetsegment_controller_test.go @@ -6,6 +6,7 @@ package core import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -17,8 +18,9 @@ var _ = Describe("EthernetSegment Controller", func() { Context("When reconciling a resource", func() { const esi = "00:11:22:33:44:55:66:77:88:01" var ( - name string - key client.ObjectKey + name string + key client.ObjectKey + memberIntf *v1alpha1.Interface ) BeforeEach(func() { @@ -42,6 +44,19 @@ var _ = Describe("EthernetSegment Controller", func() { g.Expect(k8sClient.Get(ctx, key, d)).To(Succeed()) g.Expect(d.Status.Phase).To(Equal(v1alpha1.DevicePhaseRunning)) }).Should(Succeed()) + + By("Creating a Physical member interface for Aggregate references") + memberIntf = &v1alpha1.Interface{ + GenerateName: "test-es-member-", + Namespace: metav1.NamespaceDefault, + Spec: v1alpha1.InterfaceSpec{ + DeviceRef: v1alpha1.LocalObjectReference{Name: name}, + Name: "Ethernet1/1", + AdminState: v1alpha1.AdminStateUp, + Type: v1alpha1.InterfaceTypePhysical, + }, + } + Expect(k8sClient.Create(ctx, memberIntf)).To(Succeed()) }) AfterEach(func() { @@ -51,9 +66,15 @@ var _ = Describe("EthernetSegment Controller", func() { es.Namespace = metav1.NamespaceDefault Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, es))).To(Succeed()) + By("Waiting for EthernetSegment resource to be fully deleted") + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, client.ObjectKey{Name: name, Namespace: metav1.NamespaceDefault}, &v1alpha1.EthernetSegment{}) + g.Expect(errors.IsNotFound(err)).To(BeTrue()) + }).Should(Succeed()) + By("Verifying the EthernetSegment is removed from the provider") Eventually(func(g Gomega) { - _, exists := testProvider.GetEthernetSegment(name) + _, exists := testDevices.StateFor(name).GetEthernetSegment(name) g.Expect(exists).To(BeFalse(), "Provider shouldn't have ESI configured anymore") }).Should(Succeed()) @@ -63,6 +84,12 @@ var _ = Describe("EthernetSegment Controller", func() { intf.Namespace = metav1.NamespaceDefault Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, intf))).To(Succeed()) + By("Waiting for Interface resource to be fully deleted") + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, client.ObjectKey{Name: name, Namespace: metav1.NamespaceDefault}, &v1alpha1.Interface{}) + g.Expect(errors.IsNotFound(err)).To(BeTrue()) + }).Should(Succeed()) + By("Cleaning up the test Device resource") device := &v1alpha1.Device{} device.Name = name @@ -84,7 +111,7 @@ var _ = Describe("EthernetSegment Controller", func() { Mode: v1alpha1.SwitchportModeTrunk, }, Aggregation: &v1alpha1.Aggregation{ - MemberInterfaceRefs: []v1alpha1.LocalObjectReference{{Name: "eth1"}}, + MemberInterfaceRefs: []v1alpha1.LocalObjectReference{{Name: memberIntf.Name}}, ControlProtocol: v1alpha1.ControlProtocol{Mode: v1alpha1.LACPModeActive}, }, }, @@ -145,7 +172,7 @@ var _ = Describe("EthernetSegment Controller", func() { By("Verifying the EthernetSegment is configured in the provider") Eventually(func(g Gomega) { - storedESI, exists := testProvider.GetEthernetSegment(name) + storedESI, exists := testDevices.StateFor(name).GetEthernetSegment(name) g.Expect(exists).To(BeTrue(), "Provider should have ESI configured") g.Expect(storedESI).To(Equal(esi)) }).Should(Succeed()) @@ -204,7 +231,7 @@ var _ = Describe("EthernetSegment Controller", func() { Mode: v1alpha1.SwitchportModeTrunk, }, Aggregation: &v1alpha1.Aggregation{ - MemberInterfaceRefs: []v1alpha1.LocalObjectReference{{Name: "eth1"}}, + MemberInterfaceRefs: []v1alpha1.LocalObjectReference{{Name: memberIntf.Name}}, ControlProtocol: v1alpha1.ControlProtocol{Mode: v1alpha1.LACPModeActive}, }, }, @@ -299,7 +326,7 @@ var _ = Describe("EthernetSegment Controller", func() { Type: v1alpha1.InterfaceTypeAggregate, AdminState: v1alpha1.AdminStateUp, Aggregation: &v1alpha1.Aggregation{ - MemberInterfaceRefs: []v1alpha1.LocalObjectReference{{Name: "eth1"}}, + MemberInterfaceRefs: []v1alpha1.LocalObjectReference{{Name: memberIntf.Name}}, ControlProtocol: v1alpha1.ControlProtocol{Mode: v1alpha1.LACPModeActive}, }, }, @@ -349,7 +376,7 @@ var _ = Describe("EthernetSegment Controller", func() { Mode: v1alpha1.SwitchportModeTrunk, }, Aggregation: &v1alpha1.Aggregation{ - MemberInterfaceRefs: []v1alpha1.LocalObjectReference{{Name: "eth1"}}, + MemberInterfaceRefs: []v1alpha1.LocalObjectReference{{Name: memberIntf.Name}}, ControlProtocol: v1alpha1.ControlProtocol{Mode: v1alpha1.LACPModeActive}, }, }, diff --git a/internal/controller/core/evpninstance_controller_test.go b/internal/controller/core/evpninstance_controller_test.go index dee7b738e..ae155ee29 100644 --- a/internal/controller/core/evpninstance_controller_test.go +++ b/internal/controller/core/evpninstance_controller_test.go @@ -6,6 +6,7 @@ package core import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -43,16 +44,24 @@ var _ = Describe("EVPNInstance Controller", func() { evi.Name = name evi.Namespace = metav1.NamespaceDefault Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, evi))).To(Succeed()) + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, key, &v1alpha1.EVPNInstance{}) + g.Expect(apierrors.IsNotFound(err)).To(BeTrue()) + }).Should(Succeed()) By("Cleaning up test VLAN resource") vlan := &v1alpha1.VLAN{} vlan.Name = name vlan.Namespace = metav1.NamespaceDefault Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, vlan))).To(Succeed()) + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, key, &v1alpha1.VLAN{}) + g.Expect(apierrors.IsNotFound(err)).To(BeTrue()) + }).Should(Succeed()) By("Verifying the EVPNInstance is removed from the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.EVIs.Has(vni)).To(BeFalse(), "Provider shouldn't have VNI configured anymore") + g.Expect(testDevices.StateFor(name).EVIs.Has(vni)).To(BeFalse(), "Provider shouldn't have VNI configured anymore") }).Should(Succeed()) By("Cleaning up the test Device resource") @@ -148,7 +157,7 @@ var _ = Describe("EVPNInstance Controller", func() { By("Verifying the EVPNInstance is configured in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.EVIs.Has(vni)).To(BeTrue(), "Provider should have VNI configured") + g.Expect(testDevices.StateFor(name).EVIs.Has(vni)).To(BeTrue(), "Provider should have VNI configured") }).Should(Succeed()) }) diff --git a/internal/controller/core/interface_controller_test.go b/internal/controller/core/interface_controller_test.go index 3adad9e1a..0ee0d46d9 100644 --- a/internal/controller/core/interface_controller_test.go +++ b/internal/controller/core/interface_controller_test.go @@ -9,6 +9,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -54,7 +55,7 @@ var _ = Describe("Interface Controller", func() { By("Waiting for Interfaces to be fully deleted") Eventually(func(g Gomega) { list := &v1alpha1.InterfaceList{} - g.Expect(k8sClient.List(ctx, list, client.InNamespace(metav1.NamespaceDefault), client.MatchingLabels{v1alpha1.DeviceLabel: name})).To(Succeed()) + g.Expect(k8sManager.GetClient().List(ctx, list, client.InNamespace(metav1.NamespaceDefault), client.MatchingFields{v1alpha1.DeviceRefIndexKey: name})).To(Succeed()) g.Expect(list.Items).To(BeEmpty()) }).Should(Succeed()) @@ -72,7 +73,7 @@ var _ = Describe("Interface Controller", func() { By("Verifying the Interface is removed from the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.Ports.Has(name)).To(BeFalse(), "Provider shouldn't have Interface configured anymore") + g.Expect(testDevices.StateFor(name).Ports.Has(name)).To(BeFalse(), "Provider shouldn't have Interface configured anymore") }).Should(Succeed()) By("Cleaning up the Device resource") @@ -141,7 +142,7 @@ var _ = Describe("Interface Controller", func() { By("Verifying the Interface is configured in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.Ports.Has(name)).To(BeTrue(), "Provider should have Interface configured") + g.Expect(testDevices.StateFor(name).Ports.Has(name)).To(BeTrue(), "Provider should have Interface configured") }).Should(Succeed()) }) @@ -365,7 +366,7 @@ var _ = Describe("Interface Controller", func() { By("Verifying the Aggregate Interface is configured in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.Ports.Has(name)).To(BeTrue(), "Provider should have Aggregate Interface configured") + g.Expect(testDevices.StateFor(name).Ports.Has(name)).To(BeTrue(), "Provider should have Aggregate Interface configured") }).Should(Succeed()) }) @@ -656,7 +657,7 @@ var _ = Describe("Interface Controller", func() { By("Verifying the Aggregate Interface is configured in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.Ports.Has(name)).To(BeTrue(), "Provider should have L3 Aggregate Interface configured") + g.Expect(testDevices.StateFor(name).Ports.Has(name)).To(BeTrue(), "Provider should have L3 Aggregate Interface configured") }).Should(Succeed()) }) @@ -715,7 +716,7 @@ var _ = Describe("Interface Controller", func() { By("Verifying the member Physical interface is configured in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.Ports.Has("eth1-100")).To(BeTrue(), "Provider should have member Physical Interface configured") + g.Expect(testDevices.StateFor(name).Ports.Has("eth1-100")).To(BeTrue(), "Provider should have member Physical Interface configured") }).Should(Succeed()) }) @@ -841,12 +842,12 @@ var _ = Describe("Interface Controller", func() { By("Verifying the Subinterface is configured in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.Ports.Has(parentName+".100")).To(BeTrue(), "Provider should have Subinterface configured") + g.Expect(testDevices.StateFor(name).Ports.Has(parentName+".100")).To(BeTrue(), "Provider should have Subinterface configured") }).Should(Succeed()) By("Verifying the parent Physical interface is configured in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.Ports.Has(parentName)).To(BeTrue(), "Provider should have parent Physical Interface configured") + g.Expect(testDevices.StateFor(name).Ports.Has(parentName)).To(BeTrue(), "Provider should have parent Physical Interface configured") }).Should(Succeed()) }) @@ -960,7 +961,7 @@ var _ = Describe("Interface Controller", func() { By("Verifying the Interface is configured in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.Ports.Has(name)).To(BeTrue(), "Provider should have RoutedVLAN Interface configured") + g.Expect(testDevices.StateFor(name).Ports.Has(name)).To(BeTrue(), "Provider should have RoutedVLAN Interface configured") }).Should(Succeed()) }) @@ -1103,7 +1104,7 @@ var _ = Describe("Interface Controller", func() { By("Verifying the Interface is configured in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.Ports.Has(name)).To(BeTrue(), "Provider should have Interface with VRF configured") + g.Expect(testDevices.StateFor(name).Ports.Has(name)).To(BeTrue(), "Provider should have Interface with VRF configured") }).Should(Succeed()) }) @@ -1263,7 +1264,7 @@ var _ = Describe("Interface Controller", func() { }).Should(Succeed()) By("Configuring LLDP neighbor on the provider for the local interface") - testProvider.SetLLDPNeighbor("Ethernet1/2", "remote-switch.example.com", "aa:bb:cc:dd:ee:ff", "Ethernet1/1", 120) + testDevices.StateFor(localDevice.Name).SetLLDPNeighbor("Ethernet1/2", "remote-switch.example.com", "aa:bb:cc:dd:ee:ff", "Ethernet1/1", 120) By("Creating a local Physical Interface with neighbor label pointing to the remote interface") localIntf = &v1alpha1.Interface{ @@ -1284,9 +1285,9 @@ var _ = Describe("Interface Controller", func() { AfterEach(func() { By("Cleaning up LLDP neighbor configuration") - testProvider.Lock() - delete(testProvider.LLDPNeighbors, "Ethernet1/2") - testProvider.Unlock() + testDevices.StateFor(localDevice.Name).Lock() + delete(testDevices.StateFor(localDevice.Name).LLDPNeighbors, "Ethernet1/2") + testDevices.StateFor(localDevice.Name).Unlock() By("Cleaning up all Interface resources") intfList := &v1alpha1.InterfaceList{} @@ -1307,15 +1308,19 @@ var _ = Describe("Interface Controller", func() { By("Cleaning up DNS resource") if dns != nil { - Expect(k8sClient.Delete(ctx, dns)).To(Succeed()) + Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, dns))).To(Succeed()) + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(dns), &v1alpha1.DNS{}) + g.Expect(errors.IsNotFound(err)).To(BeTrue()) + }).Should(Succeed()) } By("Cleaning up Device resources") if localDevice != nil { - Expect(k8sClient.Delete(ctx, localDevice)).To(Succeed()) + Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, localDevice))).To(Succeed()) } if remoteDevice != nil { - Expect(k8sClient.Delete(ctx, remoteDevice)).To(Succeed()) + Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, remoteDevice))).To(Succeed()) } }) diff --git a/internal/controller/core/isis_controller_test.go b/internal/controller/core/isis_controller_test.go index 47a40abd6..48472b846 100644 --- a/internal/controller/core/isis_controller_test.go +++ b/internal/controller/core/isis_controller_test.go @@ -5,6 +5,7 @@ package core import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" @@ -63,7 +64,13 @@ var _ = Describe("ISIS Controller", func() { By("Verifying the resource is removed from the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.ISIS.Has("UNDERLAY")).To(BeFalse(), "Provider should not have ISIS instance configured") + g.Expect(testDevices.StateFor(name).ISIS.Has("UNDERLAY")).To(BeFalse(), "Provider should not have ISIS instance configured") + }).Should(Succeed()) + + By("Waiting for the ISIS to be fully deleted") + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, key, &v1alpha1.ISIS{}) + g.Expect(apierrors.IsNotFound(err)).To(BeTrue()) }).Should(Succeed()) By("Cleanup the Device resource") @@ -110,7 +117,7 @@ var _ = Describe("ISIS Controller", func() { By("Ensuring the resource is created in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.ISIS.Has("UNDERLAY")).To(BeTrue(), "Provider should have ISIS instance configured") + g.Expect(testDevices.StateFor(name).ISIS.Has("UNDERLAY")).To(BeTrue(), "Provider should have ISIS instance configured") }).Should(Succeed()) }) }) @@ -144,6 +151,12 @@ var _ = Describe("ISIS Controller", func() { isis.Namespace = metav1.NamespaceDefault Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, isis))).To(Succeed()) + By("Waiting for the ISIS to be fully deleted") + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, key, &v1alpha1.ISIS{}) + g.Expect(apierrors.IsNotFound(err)).To(BeTrue()) + }).Should(Succeed()) + By("Cleanup the Device resource") device := &v1alpha1.Device{} device.Name = name diff --git a/internal/controller/core/lldp_controller_test.go b/internal/controller/core/lldp_controller_test.go index aacc05a05..9ce6cafb8 100644 --- a/internal/controller/core/lldp_controller_test.go +++ b/internal/controller/core/lldp_controller_test.go @@ -58,7 +58,7 @@ var _ = Describe("LLDP Controller", func() { By("Verifying the resource has been deleted") Eventually(func(g Gomega) { - g.Expect(testProvider.LLDP).To(BeNil(), "Provider should have no LLDP configured") + g.Expect(testDevices.StateFor(deviceName).LLDP).To(BeNil(), "Provider should have no LLDP configured") }).Should(Succeed()) By("Cleaning up the Device resource") @@ -124,9 +124,9 @@ var _ = Describe("LLDP Controller", func() { By("Ensuring the LLDP is created in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.LLDP).ToNot(BeNil(), "Provider LLDP should not be nil") - if testProvider.LLDP != nil { - g.Expect(testProvider.LLDP.GetName()).To(Equal(deviceName+"-lldp"), "Provider should have LLDP configured") + g.Expect(testDevices.StateFor(deviceName).LLDP).ToNot(BeNil(), "Provider LLDP should not be nil") + if testDevices.StateFor(deviceName).LLDP != nil { + g.Expect(testDevices.StateFor(deviceName).LLDP.GetName()).To(Equal(deviceName+"-lldp"), "Provider should have LLDP configured") } }).Should(Succeed()) }) @@ -163,9 +163,9 @@ var _ = Describe("LLDP Controller", func() { By("Ensuring the LLDP is created in the provider with AdminState Down") Eventually(func(g Gomega) { - g.Expect(testProvider.LLDP).ToNot(BeNil()) - if testProvider.LLDP != nil { - g.Expect(testProvider.LLDP.Spec.AdminState).To(Equal(v1alpha1.AdminStateDown)) + g.Expect(testDevices.StateFor(deviceName).LLDP).ToNot(BeNil()) + if testDevices.StateFor(deviceName).LLDP != nil { + g.Expect(testDevices.StateFor(deviceName).LLDP.Spec.AdminState).To(Equal(v1alpha1.AdminStateDown)) } }).Should(Succeed()) }) @@ -242,7 +242,7 @@ var _ = Describe("LLDP Controller", func() { By("Verifying LLDP is created in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.LLDP).ToNot(BeNil()) + g.Expect(testDevices.StateFor(deviceName).LLDP).ToNot(BeNil()) }).Should(Succeed()) By("Deleting the LLDP resource") @@ -250,7 +250,7 @@ var _ = Describe("LLDP Controller", func() { By("Verifying the LLDP is removed from the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.LLDP).To(BeNil(), "Provider should have no LLDP configured after deletion") + g.Expect(testDevices.StateFor(deviceName).LLDP).To(BeNil(), "Provider should have no LLDP configured after deletion") }).Should(Succeed()) By("Verifying the resource is fully deleted") @@ -335,7 +335,7 @@ var _ = Describe("LLDP Controller", func() { By("Verifying the provider has been cleaned up") Eventually(func(g Gomega) { - g.Expect(testProvider.LLDP).To(BeNil(), "Provider should have no LLDP configured") + g.Expect(testDevices.StateFor(deviceName).LLDP).To(BeNil(), "Provider should have no LLDP configured") }).Should(Succeed()) By("Cleaning up the Device resource") @@ -394,8 +394,8 @@ var _ = Describe("LLDP Controller", func() { By("Verifying the provider still has AdminState Up (reconciliation was skipped)") Consistently(func(g Gomega) { - g.Expect(testProvider.LLDP).ToNot(BeNil()) - g.Expect(testProvider.LLDP.Spec.AdminState).To(Equal(v1alpha1.AdminStateUp)) + g.Expect(testDevices.StateFor(deviceName).LLDP).ToNot(BeNil()) + g.Expect(testDevices.StateFor(deviceName).LLDP.Spec.AdminState).To(Equal(v1alpha1.AdminStateUp)) }).Should(Succeed()) By("Unpausing the Device") @@ -408,8 +408,8 @@ var _ = Describe("LLDP Controller", func() { By("Verifying the provider now has AdminState Down (reconciliation resumed)") Eventually(func(g Gomega) { - g.Expect(testProvider.LLDP).ToNot(BeNil()) - g.Expect(testProvider.LLDP.Spec.AdminState).To(Equal(v1alpha1.AdminStateDown)) + g.Expect(testDevices.StateFor(deviceName).LLDP).ToNot(BeNil()) + g.Expect(testDevices.StateFor(deviceName).LLDP.Spec.AdminState).To(Equal(v1alpha1.AdminStateDown)) }).Should(Succeed()) }) }) @@ -455,7 +455,7 @@ var _ = Describe("LLDP Controller", func() { By("Verifying the resource has been deleted") Eventually(func(g Gomega) { - g.Expect(testProvider.LLDP).To(BeNil(), "Provider should have no LLDP configured") + g.Expect(testDevices.StateFor(deviceName).LLDP).To(BeNil(), "Provider should have no LLDP configured") }).Should(Succeed()) By("Cleaning up the Device resource") @@ -594,7 +594,19 @@ var _ = Describe("LLDP Controller", func() { By("Verifying the resource has been deleted") Eventually(func(g Gomega) { - g.Expect(testProvider.LLDP).To(BeNil(), "Provider should have no LLDP configured") + g.Expect(testDevices.StateFor(deviceName).LLDP).To(BeNil(), "Provider should have no LLDP configured") + }).Should(Succeed()) + + By("Cleaning up Interface resources for this device") + intfList := &v1alpha1.InterfaceList{} + Expect(k8sManager.GetClient().List(ctx, intfList, client.InNamespace(metav1.NamespaceDefault), client.MatchingFields{v1alpha1.DeviceRefIndexKey: deviceName})).To(Succeed()) + for i := range intfList.Items { + Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, &intfList.Items[i]))).To(Succeed()) + } + Eventually(func(g Gomega) { + list := &v1alpha1.InterfaceList{} + g.Expect(k8sManager.GetClient().List(ctx, list, client.InNamespace(metav1.NamespaceDefault), client.MatchingFields{v1alpha1.DeviceRefIndexKey: deviceName})).To(Succeed()) + g.Expect(list.Items).To(BeEmpty()) }).Should(Succeed()) By("Cleaning up the Device resource") @@ -812,7 +824,19 @@ var _ = Describe("LLDP Controller", func() { By("Verifying the resource has been deleted") Eventually(func(g Gomega) { - g.Expect(testProvider.LLDP).To(BeNil(), "Provider should have no LLDP configured") + g.Expect(testDevices.StateFor(deviceName).LLDP).To(BeNil(), "Provider should have no LLDP configured") + }).Should(Succeed()) + + By("Cleaning up Interface resources for this device") + intfList := &v1alpha1.InterfaceList{} + Expect(k8sManager.GetClient().List(ctx, intfList, client.InNamespace(metav1.NamespaceDefault), client.MatchingFields{v1alpha1.DeviceRefIndexKey: deviceName})).To(Succeed()) + for i := range intfList.Items { + Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, &intfList.Items[i]))).To(Succeed()) + } + Eventually(func(g Gomega) { + list := &v1alpha1.InterfaceList{} + g.Expect(k8sManager.GetClient().List(ctx, list, client.InNamespace(metav1.NamespaceDefault), client.MatchingFields{v1alpha1.DeviceRefIndexKey: deviceName})).To(Succeed()) + g.Expect(list.Items).To(BeEmpty()) }).Should(Succeed()) By("Cleaning up the Device resource") @@ -846,8 +870,8 @@ var _ = Describe("LLDP Controller", func() { By("Verifying provider has AdminState Up") Eventually(func(g Gomega) { - g.Expect(testProvider.LLDP).ToNot(BeNil()) - g.Expect(testProvider.LLDP.Spec.AdminState).To(Equal(v1alpha1.AdminStateUp)) + g.Expect(testDevices.StateFor(deviceName).LLDP).ToNot(BeNil()) + g.Expect(testDevices.StateFor(deviceName).LLDP.Spec.AdminState).To(Equal(v1alpha1.AdminStateUp)) }).Should(Succeed()) By("Updating AdminState to Down") @@ -860,8 +884,8 @@ var _ = Describe("LLDP Controller", func() { By("Verifying provider has AdminState Down") Eventually(func(g Gomega) { - g.Expect(testProvider.LLDP).ToNot(BeNil()) - g.Expect(testProvider.LLDP.Spec.AdminState).To(Equal(v1alpha1.AdminStateDown)) + g.Expect(testDevices.StateFor(deviceName).LLDP).ToNot(BeNil()) + g.Expect(testDevices.StateFor(deviceName).LLDP.Spec.AdminState).To(Equal(v1alpha1.AdminStateDown)) }).Should(Succeed()) }) @@ -1053,7 +1077,7 @@ var _ = Describe("LLDP Controller", func() { By("Verifying the provider has been cleaned up") Eventually(func(g Gomega) { - g.Expect(testProvider.LLDP).To(BeNil(), "Provider should have no LLDP configured") + g.Expect(testDevices.StateFor(deviceName).LLDP).To(BeNil(), "Provider should have no LLDP configured") }).Should(Succeed()) By("Cleaning up the Device resource") @@ -1207,9 +1231,9 @@ var _ = Describe("LLDP Controller", func() { AfterEach(func() { By("Resetting provider LLDP operational status to true") - testProvider.Lock() - testProvider.LLDPOperStatus = true - testProvider.Unlock() + testDevices.StateFor(deviceName).Lock() + testDevices.StateFor(deviceName).LLDPOperStatus = true + testDevices.StateFor(deviceName).Unlock() By("Cleaning up the LLDP resource") lldp = &v1alpha1.LLDP{} @@ -1225,7 +1249,7 @@ var _ = Describe("LLDP Controller", func() { By("Verifying the provider has been cleaned up") Eventually(func(g Gomega) { - g.Expect(testProvider.LLDP).To(BeNil(), "Provider should have no LLDP configured") + g.Expect(testDevices.StateFor(deviceName).LLDP).To(BeNil(), "Provider should have no LLDP configured") }).Should(Succeed()) By("Cleaning up the Device resource") @@ -1237,9 +1261,9 @@ var _ = Describe("LLDP Controller", func() { It("Should set OperationalCondition to False when LLDP is operationally down", func() { By("Setting provider to return operational status down") - testProvider.Lock() - testProvider.LLDPOperStatus = false - testProvider.Unlock() + testDevices.StateFor(deviceName).Lock() + testDevices.StateFor(deviceName).LLDPOperStatus = false + testDevices.StateFor(deviceName).Unlock() By("Creating LLDP resource") lldp = &v1alpha1.LLDP{ @@ -1277,9 +1301,9 @@ var _ = Describe("LLDP Controller", func() { It("Should recover when LLDP becomes operationally up", func() { By("Setting provider to return operational status down") - testProvider.Lock() - testProvider.LLDPOperStatus = false - testProvider.Unlock() + testDevices.StateFor(deviceName).Lock() + testDevices.StateFor(deviceName).LLDPOperStatus = false + testDevices.StateFor(deviceName).Unlock() By("Creating LLDP resource") lldp = &v1alpha1.LLDP{ @@ -1303,9 +1327,9 @@ var _ = Describe("LLDP Controller", func() { }).Should(Succeed()) By("Setting provider to return operational status up") - testProvider.Lock() - testProvider.LLDPOperStatus = true - testProvider.Unlock() + testDevices.StateFor(deviceName).Lock() + testDevices.StateFor(deviceName).LLDPOperStatus = true + testDevices.StateFor(deviceName).Unlock() By("Verifying OperationalCondition becomes True after requeue") Eventually(func(g Gomega) { diff --git a/internal/controller/core/managementaccess_controller_test.go b/internal/controller/core/managementaccess_controller_test.go index e896b9693..1efefbe9e 100644 --- a/internal/controller/core/managementaccess_controller_test.go +++ b/internal/controller/core/managementaccess_controller_test.go @@ -6,6 +6,7 @@ package core import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -60,7 +61,13 @@ var _ = Describe("ManagementAccess Controller", func() { By("Verifying the resource is removed from the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.Access).To(BeNil(), "Provider should not have ManagementAccess configured") + g.Expect(testDevices.StateFor(name).Access).To(BeNil(), "Provider should not have ManagementAccess configured") + }).Should(Succeed()) + + By("Waiting for the ManagementAccess to be fully deleted") + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, key, &v1alpha1.ManagementAccess{}) + g.Expect(apierrors.IsNotFound(err)).To(BeTrue()) }).Should(Succeed()) By("Cleaning up the Device resource") @@ -107,7 +114,7 @@ var _ = Describe("ManagementAccess Controller", func() { By("Ensuring the resource is created in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.Access).ToNot(BeNil(), "Provider should have ManagementAccess configured") + g.Expect(testDevices.StateFor(name).Access).ToNot(BeNil(), "Provider should have ManagementAccess configured") }).Should(Succeed()) }) }) diff --git a/internal/controller/core/ntp_controller_test.go b/internal/controller/core/ntp_controller_test.go index 7e1f7ae71..1bbf387cf 100644 --- a/internal/controller/core/ntp_controller_test.go +++ b/internal/controller/core/ntp_controller_test.go @@ -6,6 +6,7 @@ package core import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -63,7 +64,13 @@ var _ = Describe("NTP Controller", func() { By("Verifying the resource is removed from the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.NTP).To(BeNil(), "Provider NTP should be nil") + g.Expect(testDevices.StateFor(name).NTP).To(BeNil(), "Provider NTP should be nil") + }).Should(Succeed()) + + By("Waiting for the NTP to be fully deleted") + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, key, &v1alpha1.NTP{}) + g.Expect(apierrors.IsNotFound(err)).To(BeTrue()) }).Should(Succeed()) By("Cleaning up the Device resource") @@ -110,9 +117,9 @@ var _ = Describe("NTP Controller", func() { By("Ensuring the resource is created in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.NTP).ToNot(BeNil(), "Provider NTP should not be nil") - if testProvider.NTP != nil { - g.Expect(testProvider.NTP.Spec.SourceInterfaceName).To(Equal("mgmt0")) + g.Expect(testDevices.StateFor(name).NTP).ToNot(BeNil(), "Provider NTP should not be nil") + if testDevices.StateFor(name).NTP != nil { + g.Expect(testDevices.StateFor(name).NTP.Spec.SourceInterfaceName).To(Equal("mgmt0")) } }).Should(Succeed()) }) diff --git a/internal/controller/core/nve_controller_test.go b/internal/controller/core/nve_controller_test.go index 45f1c547b..beaef5496 100644 --- a/internal/controller/core/nve_controller_test.go +++ b/internal/controller/core/nve_controller_test.go @@ -106,7 +106,7 @@ var _ = Describe("NVE Controller", func() { }).Should(BeTrue()) Eventually(func(g Gomega) { - g.Expect(testProvider.NVE).To(BeNil(), "Provider NVE should be empty") + g.Expect(testDevices.StateFor(name).NVE).To(BeNil(), "Provider NVE should be empty") }).Should(Succeed()) }) @@ -147,13 +147,13 @@ var _ = Describe("NVE Controller", func() { By("Ensuring the NVE is created in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.NVE).ToNot(BeNil(), "Provider NVE should not be nil") - g.Expect(testProvider.NVE.Spec.AdminState).To(BeEquivalentTo(v1alpha1.AdminStateUp)) - g.Expect(testProvider.NVE.Spec.SuppressARP).To(BeTrue()) - g.Expect(testProvider.NVE.Spec.HostReachability).To(BeEquivalentTo("BGP")) - g.Expect(testProvider.NVE.Spec.SourceInterfaceRef.Name).To(Equal(name + "-lo0")) - g.Expect(testProvider.NVE.Spec.MulticastGroups).ToNot(BeNil()) - g.Expect(testProvider.NVE.Spec.MulticastGroups.L2).To(HaveValue(Equal(v1alpha1.MustParsePrefix("234.0.0.0/8")))) + g.Expect(testDevices.StateFor(name).NVE).ToNot(BeNil(), "Provider NVE should not be nil") + g.Expect(testDevices.StateFor(name).NVE.Spec.AdminState).To(BeEquivalentTo(v1alpha1.AdminStateUp)) + g.Expect(testDevices.StateFor(name).NVE.Spec.SuppressARP).To(BeTrue()) + g.Expect(testDevices.StateFor(name).NVE.Spec.HostReachability).To(BeEquivalentTo("BGP")) + g.Expect(testDevices.StateFor(name).NVE.Spec.SourceInterfaceRef.Name).To(Equal(name + "-lo0")) + g.Expect(testDevices.StateFor(name).NVE.Spec.MulticastGroups).ToNot(BeNil()) + g.Expect(testDevices.StateFor(name).NVE.Spec.MulticastGroups.L2).To(HaveValue(Equal(v1alpha1.MustParsePrefix("234.0.0.0/8")))) }).Should(Succeed()) By("Verifying referenced interfaces exist and are loopbacks") @@ -273,7 +273,7 @@ var _ = Describe("NVE Controller", func() { }).Should(BeTrue()) Eventually(func(g Gomega) { - g.Expect(testProvider.NVE).To(BeNil(), "Provider NVE should be empty") + g.Expect(testDevices.StateFor(name).NVE).To(BeNil(), "Provider NVE should be empty") }).Should(Succeed()) }) @@ -284,9 +284,9 @@ var _ = Describe("NVE Controller", func() { By("Verifying reconciliation modifies provider and status") Eventually(func(g Gomega) { - g.Expect(testProvider.NVE).ToNot(BeNil()) - g.Expect(testProvider.NVE.Spec.SourceInterfaceRef.Name).To(Equal(name + "-lo1")) - g.Expect(testProvider.NVE.Status.SourceInterfaceName).To(Equal(name + "-lo1")) + g.Expect(testDevices.StateFor(name).NVE).ToNot(BeNil()) + g.Expect(testDevices.StateFor(name).NVE.Spec.SourceInterfaceRef.Name).To(Equal(name + "-lo1")) + g.Expect(testDevices.StateFor(name).NVE.Status.SourceInterfaceName).To(Equal(name + "-lo1")) }).Should(Succeed()) }) @@ -297,10 +297,10 @@ var _ = Describe("NVE Controller", func() { By("Verifying reconciliation modifies provider and status") Eventually(func(g Gomega) { - if testProvider.NVE != nil { - g.Expect(testProvider.NVE).ToNot(BeNil()) - g.Expect(testProvider.NVE.Spec.AnycastSourceInterfaceRef.Name).To(Equal(name + "-lo2")) - g.Expect(testProvider.NVE.Status.AnycastSourceInterfaceName).To(Equal(name + "-lo2")) + if testDevices.StateFor(name).NVE != nil { + g.Expect(testDevices.StateFor(name).NVE).ToNot(BeNil()) + g.Expect(testDevices.StateFor(name).NVE.Spec.AnycastSourceInterfaceRef.Name).To(Equal(name + "-lo2")) + g.Expect(testDevices.StateFor(name).NVE.Status.AnycastSourceInterfaceName).To(Equal(name + "-lo2")) } }, 5*time.Second, 100*time.Millisecond).Should(Succeed()) }) @@ -358,7 +358,7 @@ var _ = Describe("NVE Controller", func() { }).Should(BeTrue()) Eventually(func(g Gomega) { - g.Expect(testProvider.NVE).To(BeNil(), "Provider NVE should be empty") + g.Expect(testDevices.StateFor(name).NVE).To(BeNil(), "Provider NVE should be empty") }).Should(Succeed()) }) @@ -450,14 +450,14 @@ var _ = Describe("NVE Controller", func() { }).Should(BeTrue()) Eventually(func(g Gomega) { - g.Expect(testProvider.NVE).To(BeNil(), "Provider NVE should be empty") + g.Expect(testDevices.StateFor(name).NVE).To(BeNil(), "Provider NVE should be empty") }).Should(Succeed()) }) It("Should reconcile with nil anycast and empty status AnycastSourceInterfaceName", func() { Eventually(func(g Gomega) { - g.Expect(testProvider.NVE).NotTo(BeNil()) - g.Expect(testProvider.NVE.Spec.AnycastSourceInterfaceRef).To(BeNil()) + g.Expect(testDevices.StateFor(name).NVE).NotTo(BeNil()) + g.Expect(testDevices.StateFor(name).NVE.Spec.AnycastSourceInterfaceRef).To(BeNil()) }).Should(Succeed()) Eventually(func(g Gomega) { @@ -562,7 +562,7 @@ var _ = Describe("NVE Controller", func() { }).Should(BeTrue()) Eventually(func(g Gomega) { - g.Expect(testProvider.NVE).To(BeNil(), "Provider NVE should be empty") + g.Expect(testDevices.StateFor(name).NVE).To(BeNil(), "Provider NVE should be empty") }).Should(Succeed()) }) @@ -660,7 +660,7 @@ var _ = Describe("NVE Controller", func() { }).Should(BeTrue()) Eventually(func(g Gomega) { - g.Expect(testProvider.NVE).To(BeNil(), "Provider NVE should be empty") + g.Expect(testDevices.StateFor(name).NVE).To(BeNil(), "Provider NVE should be empty") }).Should(Succeed()) }) @@ -776,7 +776,7 @@ var _ = Describe("NVE Controller", func() { }).Should(BeTrue()) Eventually(func(g Gomega) { - g.Expect(testProvider.NVE).To(BeNil(), "Provider NVE should be empty") + g.Expect(testDevices.StateFor(name).NVE).To(BeNil(), "Provider NVE should be empty") }).Should(Succeed()) }) @@ -878,7 +878,7 @@ var _ = Describe("NVE Controller", func() { }).Should(BeTrue()) Eventually(func(g Gomega) { - g.Expect(testProvider.NVE).To(BeNil(), "Provider NVE should be empty") + g.Expect(testDevices.StateFor(name).NVE).To(BeNil(), "Provider NVE should be empty") }).Should(Succeed()) }) diff --git a/internal/controller/core/ospf_controller_test.go b/internal/controller/core/ospf_controller_test.go index 548d41b70..403bab908 100644 --- a/internal/controller/core/ospf_controller_test.go +++ b/internal/controller/core/ospf_controller_test.go @@ -8,6 +8,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" @@ -61,7 +62,13 @@ var _ = Describe("OSPF Controller", func() { By("Verifying the resource is removed from the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.OSPF.Has("UNDERLAY")).ToNot(BeTrue(), "Provider should not have OSPF instance configured") + g.Expect(testDevices.StateFor(name).OSPF.Has("UNDERLAY")).ToNot(BeTrue(), "Provider should not have OSPF instance configured") + }).Should(Succeed()) + + By("Waiting for the OSPF to be fully deleted") + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, key, &v1alpha1.OSPF{}) + g.Expect(apierrors.IsNotFound(err)).To(BeTrue()) }).Should(Succeed()) By("Cleanup the Device resource") @@ -112,7 +119,7 @@ var _ = Describe("OSPF Controller", func() { By("Ensuring the resource is created in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.OSPF.Has("UNDERLAY")).To(BeTrue(), "Provider should have OSPF instance configured") + g.Expect(testDevices.StateFor(name).OSPF.Has("UNDERLAY")).To(BeTrue(), "Provider should have OSPF instance configured") }).Should(Succeed()) }) }) @@ -165,6 +172,10 @@ var _ = Describe("OSPF Controller", func() { AfterEach(func() { Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, &v1alpha1.OSPF{Name: name, Namespace: metav1.NamespaceDefault}))).To(Succeed()) + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, key, &v1alpha1.OSPF{}) + g.Expect(apierrors.IsNotFound(err)).To(BeTrue()) + }).Should(Succeed()) Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, &v1alpha1.Interface{Name: name, Namespace: metav1.NamespaceDefault}))).To(Succeed()) Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, &v1alpha1.Device{Name: name, Namespace: metav1.NamespaceDefault}))).To(Succeed()) }) @@ -227,6 +238,12 @@ var _ = Describe("OSPF Controller", func() { ospf.Namespace = metav1.NamespaceDefault Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, ospf))).To(Succeed()) + By("Waiting for the OSPF to be fully deleted") + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, key, &v1alpha1.OSPF{}) + g.Expect(apierrors.IsNotFound(err)).To(BeTrue()) + }).Should(Succeed()) + By("Cleanup the Device resource") device := &v1alpha1.Device{} device.Name = name diff --git a/internal/controller/core/pim_controller_test.go b/internal/controller/core/pim_controller_test.go index f744b7eb1..1618e9123 100644 --- a/internal/controller/core/pim_controller_test.go +++ b/internal/controller/core/pim_controller_test.go @@ -6,6 +6,7 @@ package core import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" @@ -56,7 +57,13 @@ var _ = Describe("PIM Controller", func() { By("Verifying the resource is removed from the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.PIM).To(BeNil(), "Provider should not have PIM instance configured") + g.Expect(testDevices.StateFor(name).PIM).To(BeNil(), "Provider should not have PIM instance configured") + }).Should(Succeed()) + + By("Waiting for the PIM to be fully deleted") + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, key, &v1alpha1.PIM{}) + g.Expect(apierrors.IsNotFound(err)).To(BeTrue()) }).Should(Succeed()) By("Cleanup the Device resource") @@ -103,7 +110,7 @@ var _ = Describe("PIM Controller", func() { By("Ensuring the resource is created in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.PIM).ToNot(BeNil(), "Provider should have PIM instance configured") + g.Expect(testDevices.StateFor(name).PIM).ToNot(BeNil(), "Provider should have PIM instance configured") }).Should(Succeed()) }) }) @@ -136,6 +143,10 @@ var _ = Describe("PIM Controller", func() { pim.Name = name pim.Namespace = metav1.NamespaceDefault Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, pim))).To(Succeed()) + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, key, &v1alpha1.PIM{}) + g.Expect(apierrors.IsNotFound(err)).To(BeTrue()) + }).Should(Succeed()) By("Cleanup the Device resource") device := &v1alpha1.Device{} diff --git a/internal/controller/core/prefixset_controller_test.go b/internal/controller/core/prefixset_controller_test.go index 4b633b37f..2244f751a 100644 --- a/internal/controller/core/prefixset_controller_test.go +++ b/internal/controller/core/prefixset_controller_test.go @@ -6,6 +6,7 @@ package core import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -67,7 +68,13 @@ var _ = Describe("PrefixSet Controller", func() { By("Verifying the resource is removed from the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.PrefixSets.Has(set)).To(BeFalse(), "Provider should not have PrefixSet configured") + g.Expect(testDevices.StateFor(name).PrefixSets.Has(set)).To(BeFalse(), "Provider should not have PrefixSet configured") + }).Should(Succeed()) + + By("Waiting for the PrefixSet to be fully deleted") + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, key, &v1alpha1.PrefixSet{}) + g.Expect(apierrors.IsNotFound(err)).To(BeTrue()) }).Should(Succeed()) By("Cleaning up the Device resource") @@ -114,7 +121,7 @@ var _ = Describe("PrefixSet Controller", func() { By("Ensuring the resource is created in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.PrefixSets.Has(set)).To(BeTrue(), "Provider should have PrefixSet configured") + g.Expect(testDevices.StateFor(name).PrefixSets.Has(set)).To(BeTrue(), "Provider should have PrefixSet configured") }).Should(Succeed()) }) }) diff --git a/internal/controller/core/probe_controller_test.go b/internal/controller/core/probe_controller_test.go index 9c798329b..da6821288 100644 --- a/internal/controller/core/probe_controller_test.go +++ b/internal/controller/core/probe_controller_test.go @@ -62,8 +62,8 @@ var _ = Describe("Probe Controller", func() { }) It("Should wait for its Device to become reachable", func() { - testProvider.SetConnectError(errors.New("device unreachable")) - DeferCleanup(func() { testProvider.SetConnectError(nil) }) + testDevices.StateFor(name).SetConnectFailure(errors.New("device unreachable")) + DeferCleanup(func() { testDevices.StateFor(name).SetConnectFailure(nil) }) Eventually(func(g Gomega) { device := &v1alpha1.Device{} @@ -98,7 +98,7 @@ var _ = Describe("Probe Controller", func() { ))) }).Should(Succeed()) - testProvider.SetConnectError(nil) + testDevices.StateFor(name).SetConnectFailure(nil) Eventually(func(g Gomega) { resource := &v1alpha1.Probe{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) diff --git a/internal/controller/core/routingpolicy_controller_test.go b/internal/controller/core/routingpolicy_controller_test.go index 4594cd67f..cb0e2e724 100644 --- a/internal/controller/core/routingpolicy_controller_test.go +++ b/internal/controller/core/routingpolicy_controller_test.go @@ -8,6 +8,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" "sigs.k8s.io/controller-runtime/pkg/client" @@ -46,15 +47,27 @@ var _ = Describe("RoutingPolicy Controller", func() { rp.Namespace = metav1.NamespaceDefault Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, rp))).To(Succeed()) + By("Waiting for RoutingPolicy resource to be fully deleted") + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, client.ObjectKey{Name: name, Namespace: metav1.NamespaceDefault}, &v1alpha1.RoutingPolicy{}) + g.Expect(errors.IsNotFound(err)).To(BeTrue()) + }).Should(Succeed()) + By("Cleaning up the PrefixSet resource") ps := &v1alpha1.PrefixSet{} ps.Name = name ps.Namespace = metav1.NamespaceDefault Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, ps))).To(Succeed()) + By("Waiting for PrefixSet resource to be fully deleted") + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, client.ObjectKey{Name: name, Namespace: metav1.NamespaceDefault}, &v1alpha1.PrefixSet{}) + g.Expect(errors.IsNotFound(err)).To(BeTrue()) + }).Should(Succeed()) + By("Verifying the RoutingPolicy is removed from the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.RoutingPolicies.Has(name)).To(BeFalse(), "Provider shouldn't have RoutingPolicy configured anymore") + g.Expect(testDevices.StateFor(name).RoutingPolicies.Has(name)).To(BeFalse(), "Provider shouldn't have RoutingPolicy configured anymore") }).Should(Succeed()) By("Cleaning up the Device resource") @@ -119,7 +132,7 @@ var _ = Describe("RoutingPolicy Controller", func() { By("Ensuring the resource is created in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.RoutingPolicies.Has(name)).To(BeTrue(), "Provider should have RoutingPolicy configured") + g.Expect(testDevices.StateFor(name).RoutingPolicies.Has(name)).To(BeTrue(), "Provider should have RoutingPolicy configured") }).Should(Succeed()) }) @@ -186,7 +199,7 @@ var _ = Describe("RoutingPolicy Controller", func() { By("Verifying the RoutingPolicy is configured in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.RoutingPolicies.Has(name)).To(BeTrue(), "Provider should have RoutingPolicy configured") + g.Expect(testDevices.StateFor(name).RoutingPolicies.Has(name)).To(BeTrue(), "Provider should have RoutingPolicy configured") }).Should(Succeed()) }) @@ -331,7 +344,7 @@ var _ = Describe("RoutingPolicy Controller", func() { By("Verifying the RoutingPolicy is configured in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.RoutingPolicies.Has(name)).To(BeTrue(), "Provider should have RoutingPolicy configured") + g.Expect(testDevices.StateFor(name).RoutingPolicies.Has(name)).To(BeTrue(), "Provider should have RoutingPolicy configured") }).Should(Succeed()) }) diff --git a/internal/controller/core/snmp_controller_test.go b/internal/controller/core/snmp_controller_test.go index 33d26a160..558f074fb 100644 --- a/internal/controller/core/snmp_controller_test.go +++ b/internal/controller/core/snmp_controller_test.go @@ -6,6 +6,7 @@ package core import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -74,7 +75,13 @@ var _ = Describe("SNMP Controller", func() { By("Verifying the resource is removed from the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.SNMP).To(BeNil(), "Provider should not have SNMP configured") + g.Expect(testDevices.StateFor(name).SNMP).To(BeNil(), "Provider should not have SNMP configured") + }).Should(Succeed()) + + By("Waiting for the SNMP to be fully deleted") + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, key, &v1alpha1.SNMP{}) + g.Expect(apierrors.IsNotFound(err)).To(BeTrue()) }).Should(Succeed()) By("Cleaning up the Device resource") @@ -121,9 +128,9 @@ var _ = Describe("SNMP Controller", func() { By("Ensuring the resource is created in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.SNMP).ToNot(BeNil(), "Provider should have SNMP configured") - if testProvider.SNMP != nil { - g.Expect(testProvider.SNMP.Spec.Contact).To(Equal("123")) + g.Expect(testDevices.StateFor(name).SNMP).ToNot(BeNil(), "Provider should have SNMP configured") + if testDevices.StateFor(name).SNMP != nil { + g.Expect(testDevices.StateFor(name).SNMP.Spec.Contact).To(Equal("123")) } }).Should(Succeed()) }) diff --git a/internal/controller/core/suite_test.go b/internal/controller/core/suite_test.go index 15d38f8c5..c98534d2a 100644 --- a/internal/controller/core/suite_test.go +++ b/internal/controller/core/suite_test.go @@ -42,13 +42,13 @@ import ( // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. var ( - ctx context.Context - cancel context.CancelFunc - testEnv *envtest.Environment - k8sClient client.Client - k8sManager ctrl.Manager - testProvider = NewProvider() - testLocker *resourcelock.ResourceLocker + ctx context.Context + cancel context.CancelFunc + testEnv *envtest.Environment + k8sClient client.Client + k8sManager ctrl.Manager + testDevices = NewDeviceStore() + testLocker *resourcelock.ResourceLocker // testEvents is a slice that stores events recorded during the tests. It is used to verify that the expected events are generated by the controllers. testEvents []string testS3Store = NewMockObjectStorage() @@ -121,7 +121,7 @@ var _ = BeforeSuite(func() { _, err = k8sManager.GetCache().GetInformer(ctx, &coordinationv1.Lease{}) Expect(err).NotTo(HaveOccurred()) - prov := func() provider.Provider { return testProvider } + prov := func() provider.Provider { return &Provider{devices: testDevices} } err = (&DeviceReconciler{ Client: k8sManager.GetClient(), @@ -451,8 +451,9 @@ var ( _ provider.ProbeProvider = (*Provider)(nil) ) -// Provider is a simple in-memory provider for testing purposes only. -type Provider struct { +// DeviceState holds per-device mutable state for testing. Each device created in +// a test gets its own DeviceState, preventing state bleed between test suites. +type DeviceState struct { sync.Mutex ConnectError error // if non-nil, Connect returns this error @@ -491,8 +492,8 @@ type Provider struct { StorageTotal int64 } -func NewProvider() *Provider { - return &Provider{ +func NewDeviceState() *DeviceState { + return &DeviceState{ LastRebootTime: lastRebootTime, Ports: sets.New[string](), User: sets.New[string](), @@ -513,26 +514,63 @@ func NewProvider() *Provider { } } -// SetConnectError sets the error that Connect will return on subsequent calls. -// Pass nil to clear the error and allow connections to succeed. -func (p *Provider) SetConnectError(err error) { - p.Lock() - defer p.Unlock() - p.ConnectError = err +// DeviceStore holds per-device state for all test devices, keyed by device name. +type DeviceStore struct { + sync.Mutex + states map[string]*DeviceState } -// SetLastRebootTime sets the time returned by GetLastRebootTime on subsequent calls. -func (p *Provider) SetLastRebootTime(t time.Time) { - p.Lock() - defer p.Unlock() - p.LastRebootTime = t +func NewDeviceStore() *DeviceStore { + return &DeviceStore{states: make(map[string]*DeviceState)} } -func (p *Provider) Connect(_ context.Context, _ *deviceutil.Connection) error { - p.Lock() - defer p.Unlock() - return p.ConnectError +// StateFor returns the DeviceState for the given device name, creating one if it doesn't exist. +func (s *DeviceStore) StateFor(name string) *DeviceState { + s.Lock() + defer s.Unlock() + ds, ok := s.states[name] + if !ok { + ds = NewDeviceState() + s.states[name] = ds + } + return ds } + +// Provider is an in-memory provider for testing. The ProviderFunc factory creates +// a new Provider per reconcile call, sharing the DeviceStore. Connect captures the +// device name from the Connection so subsequent method calls dispatch to the correct +// DeviceState. +type Provider struct { + devices *DeviceStore + deviceName string // set during Connect +} + +// SetConnectFailure sets the error that Connect will return for this device. +func (s *DeviceState) SetConnectFailure(err error) { + s.Lock() + defer s.Unlock() + s.ConnectError = err +} + +// SetLastRebootTime sets the time returned by GetLastRebootTime for this device. +func (s *DeviceState) SetLastRebootTime(t time.Time) { + s.Lock() + defer s.Unlock() + s.LastRebootTime = t +} + +func (p *Provider) Connect(_ context.Context, conn *deviceutil.Connection) error { + s := p.devices.StateFor(conn.DeviceName) + s.Lock() + err := s.ConnectError + s.Unlock() + if err != nil { + return err + } + p.deviceName = conn.DeviceName + return nil +} + func (p *Provider) Disconnect(context.Context, *deviceutil.Connection) error { return nil } func (p *Provider) ListPorts(context.Context) (ports []provider.DevicePort, err error) { @@ -548,9 +586,10 @@ func (p *Provider) ListPorts(context.Context) (ports []provider.DevicePort, err } func (p *Provider) GetLastRebootTime(_ context.Context) (time.Time, error) { - p.Lock() - defer p.Unlock() - return p.LastRebootTime, nil + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + return s.LastRebootTime, nil } func (p *Provider) GetDeviceInfo(context.Context) (*provider.DeviceInfo, error) { @@ -570,44 +609,42 @@ func (p *Provider) VerifyProvisioned(context.Context, *deviceutil.Connection, *v return true } -func (p *Provider) Reboot(ctx context.Context, conn *deviceutil.Connection) error { +func (p *Provider) Reboot(context.Context, *deviceutil.Connection) error { return nil } -func (p *Provider) FactoryReset(ctx context.Context, conn *deviceutil.Connection) error { +func (p *Provider) FactoryReset(context.Context, *deviceutil.Connection) error { return nil } -func (p *Provider) Reprovision(ctx context.Context, conn *deviceutil.Connection) (reterr error) { +func (p *Provider) Reprovision(context.Context, *deviceutil.Connection) error { return nil } -func (p *Provider) EnsureInterface(ctx context.Context, req *provider.EnsureInterfaceRequest) error { - p.Lock() - defer p.Unlock() - p.Ports.Insert(req.Interface.Spec.Name) +func (p *Provider) EnsureInterface(_ context.Context, req *provider.EnsureInterfaceRequest) error { + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.Ports.Insert(req.Interface.Spec.Name) return nil } func (p *Provider) DeleteInterface(_ context.Context, req *provider.InterfaceRequest) error { - p.Lock() - defer p.Unlock() - p.Ports.Delete(req.Interface.Spec.Name) + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.Ports.Delete(req.Interface.Spec.Name) return nil } func (p *Provider) GetInterfaceStatus(_ context.Context, req *provider.InterfaceRequest) (provider.InterfaceStatus, error) { - p.Lock() - defer p.Unlock() - - status := provider.InterfaceStatus{ - OperStatus: true, - } - - if neighbor, ok := p.LLDPNeighbors[req.Interface.Spec.Name]; ok { + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + status := provider.InterfaceStatus{OperStatus: true} + if neighbor, ok := s.LLDPNeighbors[req.Interface.Spec.Name]; ok { status.LLDPAdjacencies = []provider.LLDPAdjacency{*neighbor} } - return status, nil } @@ -620,13 +657,14 @@ func (p *Provider) LoopbackInterfaceName(id int) (string, error) { } func (p *Provider) EnsureBanner(_ context.Context, req *provider.EnsureBannerRequest) error { - p.Lock() - defer p.Unlock() + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() switch req.Type { case v1alpha1.BannerTypePreLogin: - p.PreLoginBanner = &req.Message + s.PreLoginBanner = &req.Message case v1alpha1.BannerTypePostLogin: - p.PostLoginBanner = &req.Message + s.PostLoginBanner = &req.Message default: return errors.New("unknown banner type") } @@ -634,13 +672,14 @@ func (p *Provider) EnsureBanner(_ context.Context, req *provider.EnsureBannerReq } func (p *Provider) DeleteBanner(_ context.Context, req *provider.DeleteBannerRequest) error { - p.Lock() - defer p.Unlock() + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() switch req.Type { case v1alpha1.BannerTypePreLogin: - p.PreLoginBanner = nil + s.PreLoginBanner = nil case v1alpha1.BannerTypePostLogin: - p.PostLoginBanner = nil + s.PostLoginBanner = nil default: return errors.New("unknown banner type") } @@ -648,186 +687,212 @@ func (p *Provider) DeleteBanner(_ context.Context, req *provider.DeleteBannerReq } func (p *Provider) EnsureUser(_ context.Context, req *provider.EnsureUserRequest) error { - p.Lock() - defer p.Unlock() - p.User.Insert(req.Username) + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.User.Insert(req.Username) return nil } func (p *Provider) DeleteUser(_ context.Context, req *provider.DeleteUserRequest) error { - p.Lock() - defer p.Unlock() - p.User.Delete(req.Username) + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.User.Delete(req.Username) return nil } func (p *Provider) EnsureDNS(_ context.Context, req *provider.EnsureDNSRequest) error { - p.Lock() - defer p.Unlock() - p.DNS = req.DNS + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.DNS = req.DNS return nil } func (p *Provider) DeleteDNS(_ context.Context) error { - p.Lock() - defer p.Unlock() - p.DNS = nil + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.DNS = nil return nil } func (p *Provider) EnsureNTP(_ context.Context, req *provider.EnsureNTPRequest) error { - p.Lock() - defer p.Unlock() - p.NTP = req.NTP + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.NTP = req.NTP return nil } func (p *Provider) DeleteNTP(context.Context) error { - p.Lock() - defer p.Unlock() - p.NTP = nil + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.NTP = nil return nil } func (p *Provider) EnsureACL(_ context.Context, req *provider.ACLRequest) error { - p.Lock() - defer p.Unlock() - p.ACLs.Insert(req.ACL.Spec.Name) + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.ACLs.Insert(req.ACL.Spec.Name) return nil } func (p *Provider) DeleteACL(_ context.Context, req *provider.ACLRequest) error { - p.Lock() - defer p.Unlock() - p.ACLs.Delete(req.ACL.Spec.Name) + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.ACLs.Delete(req.ACL.Spec.Name) return nil } func (p *Provider) EnsureCertificate(_ context.Context, req *provider.EnsureCertificateRequest) error { - p.Lock() - defer p.Unlock() - p.Certs.Insert(req.ID) + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.Certs.Insert(req.ID) return nil } func (p *Provider) DeleteCertificate(_ context.Context, req *provider.DeleteCertificateRequest) error { - p.Lock() - defer p.Unlock() - p.Certs.Delete(req.ID) + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.Certs.Delete(req.ID) return nil } func (p *Provider) EnsureSNMP(_ context.Context, req *provider.EnsureSNMPRequest) error { - p.Lock() - defer p.Unlock() - p.SNMP = req.SNMP + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.SNMP = req.SNMP return nil } func (p *Provider) DeleteSNMP(_ context.Context, req *provider.DeleteSNMPRequest) error { - p.Lock() - defer p.Unlock() - p.SNMP = nil + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.SNMP = nil return nil } func (p *Provider) EnsureSyslog(_ context.Context, req *provider.EnsureSyslogRequest) error { - p.Lock() - defer p.Unlock() - p.Syslog = req.Syslog + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.Syslog = req.Syslog return nil } func (p *Provider) DeleteSyslog(_ context.Context) error { - p.Lock() - defer p.Unlock() - p.Syslog = nil + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.Syslog = nil return nil } func (p *Provider) EnsureManagementAccess(_ context.Context, req *provider.EnsureManagementAccessRequest) error { - p.Lock() - defer p.Unlock() - p.Access = req.ManagementAccess + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.Access = req.ManagementAccess return nil } func (p *Provider) DeleteManagementAccess(context.Context, *provider.DeleteManagementAccessRequest) error { - p.Lock() - defer p.Unlock() - p.Access = nil + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.Access = nil return nil } func (p *Provider) EnsureISIS(_ context.Context, req *provider.EnsureISISRequest) error { - p.Lock() - defer p.Unlock() - p.ISIS.Insert(req.ISIS.Spec.Instance) + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.ISIS.Insert(req.ISIS.Spec.Instance) return nil } func (p *Provider) DeleteISIS(_ context.Context, req *provider.DeleteISISRequest) error { - p.Lock() - defer p.Unlock() - p.ISIS.Delete(req.ISIS.Spec.Instance) + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.ISIS.Delete(req.ISIS.Spec.Instance) return nil } func (p *Provider) EnsureVRF(_ context.Context, req *provider.VRFRequest) error { - p.Lock() - defer p.Unlock() - p.VRF.Insert(req.VRF.Spec.Name) + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.VRF.Insert(req.VRF.Spec.Name) return nil } func (p *Provider) DeleteVRF(_ context.Context, req *provider.VRFRequest) error { - p.Lock() - defer p.Unlock() - p.VRF.Delete(req.VRF.Spec.Name) + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.VRF.Delete(req.VRF.Spec.Name) return nil } func (p *Provider) EnsurePIM(_ context.Context, req *provider.EnsurePIMRequest) error { - p.Lock() - defer p.Unlock() - p.PIM = req.PIM + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.PIM = req.PIM return nil } func (p *Provider) DeletePIM(context.Context, *provider.DeletePIMRequest) error { - p.Lock() - defer p.Unlock() - p.PIM = nil + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.PIM = nil return nil } func (p *Provider) EnsureBGP(_ context.Context, req *provider.EnsureBGPRequest) error { - p.Lock() - defer p.Unlock() - p.BGP = req.BGP - p.BGPVRF = req.VRF + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.BGP = req.BGP + s.BGPVRF = req.VRF return nil } func (p *Provider) DeleteBGP(context.Context, *provider.DeleteBGPRequest) error { - p.Lock() - defer p.Unlock() - p.BGP = nil - p.BGPVRF = nil + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.BGP = nil + s.BGPVRF = nil return nil } func (p *Provider) EnsureBGPPeer(_ context.Context, req *provider.EnsureBGPPeerRequest) error { - p.Lock() - defer p.Unlock() - p.BGPPeers.Insert(req.BGPPeer.Spec.Address) + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.BGPPeers.Insert(req.BGPPeer.Spec.Address) return nil } func (p *Provider) DeleteBGPPeer(_ context.Context, req *provider.DeleteBGPPeerRequest) error { - p.Lock() - defer p.Unlock() - p.BGPPeers.Delete(req.BGPPeer.Spec.Address) + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.BGPPeers.Delete(req.BGPPeer.Spec.Address) return nil } @@ -845,112 +910,120 @@ func (p *Provider) GetPeerStatus(context.Context, *provider.BGPPeerStatusRequest } func (p *Provider) EnsureOSPF(_ context.Context, req *provider.EnsureOSPFRequest) error { - p.Lock() - defer p.Unlock() - p.OSPF.Insert(req.OSPF.Spec.Instance) + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.OSPF.Insert(req.OSPF.Spec.Instance) return nil } func (p *Provider) DeleteOSPF(_ context.Context, req *provider.DeleteOSPFRequest) error { - p.Lock() - defer p.Unlock() - p.OSPF.Delete(req.OSPF.Spec.Instance) + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.OSPF.Delete(req.OSPF.Spec.Instance) return nil } func (p *Provider) GetOSPFStatus(context.Context, *provider.OSPFStatusRequest) (provider.OSPFStatus, error) { - return provider.OSPFStatus{ - OperStatus: true, - }, nil + return provider.OSPFStatus{OperStatus: true}, nil } func (p *Provider) EnsureVLAN(_ context.Context, req *provider.VLANRequest) error { - p.Lock() - defer p.Unlock() - p.VLANs.Insert(req.VLAN.Spec.ID) + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.VLANs.Insert(req.VLAN.Spec.ID) return nil } func (p *Provider) DeleteVLAN(_ context.Context, req *provider.VLANRequest) error { - p.Lock() - defer p.Unlock() - p.VLANs.Delete(req.VLAN.Spec.ID) + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.VLANs.Delete(req.VLAN.Spec.ID) return nil } func (p *Provider) GetVLANStatus(context.Context, *provider.VLANRequest) (provider.VLANStatus, error) { - return provider.VLANStatus{ - OperStatus: true, - }, nil + return provider.VLANStatus{OperStatus: true}, nil } func (p *Provider) EnsureEVPNInstance(_ context.Context, req *provider.EVPNInstanceRequest) error { - p.Lock() - defer p.Unlock() - p.EVIs.Insert(req.EVPNInstance.Spec.VNI) + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.EVIs.Insert(req.EVPNInstance.Spec.VNI) return nil } func (p *Provider) DeleteEVPNInstance(_ context.Context, req *provider.EVPNInstanceRequest) error { - p.Lock() - defer p.Unlock() - p.EVIs.Delete(req.EVPNInstance.Spec.VNI) + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.EVIs.Delete(req.EVPNInstance.Spec.VNI) return nil } -// EnsurePrefixSet implements provider.PrefixSetProvider. func (p *Provider) EnsurePrefixSet(_ context.Context, req *provider.PrefixSetRequest) error { - p.Lock() - defer p.Unlock() - p.PrefixSets.Insert(req.PrefixSet.Spec.Name) + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.PrefixSets.Insert(req.PrefixSet.Spec.Name) return nil } func (p *Provider) DeletePrefixSet(_ context.Context, req *provider.PrefixSetRequest) error { - p.Lock() - defer p.Unlock() - p.PrefixSets.Delete(req.PrefixSet.Spec.Name) + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.PrefixSets.Delete(req.PrefixSet.Spec.Name) return nil } func (p *Provider) EnsureRoutingPolicy(_ context.Context, req *provider.EnsureRoutingPolicyRequest) error { - p.Lock() - defer p.Unlock() - p.RoutingPolicies.Insert(req.Name) + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.RoutingPolicies.Insert(req.Name) return nil } func (p *Provider) DeleteRoutingPolicy(_ context.Context, req *provider.DeleteRoutingPolicyRequest) error { - p.Lock() - defer p.Unlock() - p.RoutingPolicies.Delete(req.Name) + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.RoutingPolicies.Delete(req.Name) return nil } func (p *Provider) EnsureNVE(_ context.Context, req *provider.NVERequest) error { - p.Lock() - defer p.Unlock() - p.NVE = req.NVE + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.NVE = req.NVE return nil } func (p *Provider) DeleteNVE(_ context.Context, req *provider.NVERequest) error { - p.Lock() - defer p.Unlock() - p.NVE = nil + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.NVE = nil return nil } func (p *Provider) GetNVEStatus(_ context.Context, _ *provider.NVERequest) (provider.NVEStatus, error) { - status := provider.NVEStatus{ - OperStatus: true, - } - if p.NVE != nil { - if p.NVE.Spec.SourceInterfaceRef.Name != "" { - status.SourceInterfaceName = p.NVE.Spec.SourceInterfaceRef.Name + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + status := provider.NVEStatus{OperStatus: true} + if s.NVE != nil { + if s.NVE.Spec.SourceInterfaceRef.Name != "" { + status.SourceInterfaceName = s.NVE.Spec.SourceInterfaceRef.Name } - if p.NVE.Spec.AnycastSourceInterfaceRef != nil { - status.AnycastSourceInterfaceName = p.NVE.Spec.AnycastSourceInterfaceRef.Name + if s.NVE.Spec.AnycastSourceInterfaceRef != nil { + status.AnycastSourceInterfaceName = s.NVE.Spec.AnycastSourceInterfaceRef.Name } } return status, nil @@ -961,97 +1034,103 @@ func (p *Provider) RunningConfig(context.Context) ([]byte, error) { } func (p *Provider) CreateConfigBackup(_ context.Context, req *provider.ConfigBackupRequest) (*provider.ConfigBackupFile, error) { - p.Lock() - defer p.Unlock() - + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() if req.ConfigBackup.Spec.Type == v1alpha1.ConfigBackupTypeStartup { - p.StartupConfig = req.ConfigBackup + s.StartupConfig = req.ConfigBackup return nil, nil //nolint:nilnil } - file := &provider.ConfigBackupFile{ Path: path.Join(req.ConfigBackup.Spec.Path, fmt.Sprintf("configbackup-%s-", req.ConfigBackup.UID)) + time.Now().Format("20060102T150405Z"), SizeBytes: new(int64(1024)), CreatedAt: time.Now(), } - p.ConfigBackups = append(p.ConfigBackups, file) + s.ConfigBackups = append(s.ConfigBackups, file) return file, nil } func (p *Provider) ListConfigBackups(_ context.Context, _ *provider.ConfigBackupRequest) (*provider.ConfigBackupInventory, error) { - p.Lock() - defer p.Unlock() + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() var used int64 - for _, f := range p.ConfigBackups { + for _, f := range s.ConfigBackups { if f.SizeBytes != nil { used += *f.SizeBytes } } return &provider.ConfigBackupInventory{ - Backups: p.ConfigBackups, - TotalBytes: &p.StorageTotal, + Backups: s.ConfigBackups, + TotalBytes: &s.StorageTotal, UsedBytes: &used, - FreeBytes: new(p.StorageTotal - used), + FreeBytes: new(s.StorageTotal - used), }, nil } func (p *Provider) DeleteConfigBackups(_ context.Context, files ...*provider.ConfigBackupFile) error { - p.Lock() - defer p.Unlock() + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() remove := make(map[string]struct{}, len(files)) for _, f := range files { remove[f.Path] = struct{}{} } - filtered := p.ConfigBackups[:0] - for _, b := range p.ConfigBackups { + filtered := s.ConfigBackups[:0] + for _, b := range s.ConfigBackups { if _, ok := remove[b.Path]; !ok { filtered = append(filtered, b) } } - p.ConfigBackups = filtered + s.ConfigBackups = filtered return nil } func (p *Provider) EnsureLLDP(_ context.Context, req *provider.LLDPRequest) error { - p.Lock() - defer p.Unlock() - p.LLDP = req.LLDP + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.LLDP = req.LLDP return nil } func (p *Provider) DeleteLLDP(_ context.Context, req *provider.LLDPRequest) error { - p.Lock() - defer p.Unlock() - p.LLDP = nil + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.LLDP = nil return nil } func (p *Provider) GetLLDPStatus(_ context.Context, _ *provider.LLDPRequest) (provider.LLDPStatus, error) { - p.Lock() - defer p.Unlock() - return provider.LLDPStatus{OperStatus: p.LLDPOperStatus}, nil + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + return provider.LLDPStatus{OperStatus: s.LLDPOperStatus}, nil } func (p *Provider) EnsureDHCPRelay(_ context.Context, req *provider.DHCPRelayRequest) error { - p.Lock() - defer p.Unlock() - p.DHCPRelay = req.DHCPRelay + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.DHCPRelay = req.DHCPRelay return nil } func (p *Provider) DeleteDHCPRelay(_ context.Context, req *provider.DHCPRelayRequest) error { - p.Lock() - defer p.Unlock() - p.DHCPRelay = nil + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + s.DHCPRelay = nil return nil } func (p *Provider) GetDHCPRelayStatus(_ context.Context, req *provider.DHCPRelayRequest) (provider.DHCPRelayStatus, error) { - p.Lock() - defer p.Unlock() + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() status := provider.DHCPRelayStatus{} - if p.DHCPRelay != nil { - // Return the interface names from the request (simulating what the device would return) + if s.DHCPRelay != nil { for _, intf := range req.Interfaces { status.ConfiguredInterfaces = append(status.ConfiguredInterfaces, intf.Spec.Name) } @@ -1060,35 +1139,39 @@ func (p *Provider) GetDHCPRelayStatus(_ context.Context, req *provider.DHCPRelay } func (p *Provider) EnsureEthernetSegment(_ context.Context, req *provider.EnsureEthernetSegmentRequest) error { - p.Lock() - defer p.Unlock() + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() esi := req.EthernetSegment.Spec.ESI if esi == "" { // Simulate auto-generated ESI (Type 3 MAC-based) esi = "03:aa:bb:cc:dd:ee:ff:00:00:01" } - p.EthernetSegments[req.EthernetSegment.Name] = esi + s.EthernetSegments[req.EthernetSegment.Name] = esi return nil } func (p *Provider) DeleteEthernetSegment(_ context.Context, req *provider.DeleteEthernetSegmentRequest) error { - p.Lock() - defer p.Unlock() - delete(p.EthernetSegments, req.EthernetSegment.Name) + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + delete(s.EthernetSegments, req.EthernetSegment.Name) return nil } func (p *Provider) GetEthernetSegmentStatus(_ context.Context, req *provider.EthernetSegmentStatusRequest) (provider.EthernetSegmentStatus, error) { - p.Lock() - defer p.Unlock() - esi := p.EthernetSegments[req.EthernetSegment.Name] + s := p.devices.StateFor(p.deviceName) + s.Lock() + defer s.Unlock() + esi := s.EthernetSegments[req.EthernetSegment.Name] return provider.EthernetSegmentStatus{ESI: esi, OperStatus: esi != ""}, nil } -func (p *Provider) GetEthernetSegment(name string) (string, bool) { - p.Lock() - defer p.Unlock() - esi, ok := p.EthernetSegments[name] +// GetEthernetSegment is a test helper to retrieve the ESI for an ethernet segment by name. +func (s *DeviceState) GetEthernetSegment(name string) (string, bool) { + s.Lock() + defer s.Unlock() + esi, ok := s.EthernetSegments[name] return esi, ok } @@ -1115,10 +1198,10 @@ func (p *Provider) GetVTEPPeers(context.Context, *provider.VTEPPeersRequest) ([] } // SetLLDPNeighbor is a test helper to configure LLDP neighbor information for an interface. -func (p *Provider) SetLLDPNeighbor(interfaceName, sysName, chassisID, portID string, ttl uint32) { - p.Lock() - defer p.Unlock() - p.LLDPNeighbors[interfaceName] = &provider.LLDPAdjacency{ +func (s *DeviceState) SetLLDPNeighbor(interfaceName, sysName, chassisID, portID string, ttl uint32) { + s.Lock() + defer s.Unlock() + s.LLDPNeighbors[interfaceName] = &provider.LLDPAdjacency{ SysName: sysName, ChassisID: chassisID, ChassisIDType: 4, // MACAddress diff --git a/internal/controller/core/syslog_controller_test.go b/internal/controller/core/syslog_controller_test.go index 9feade252..a79569887 100644 --- a/internal/controller/core/syslog_controller_test.go +++ b/internal/controller/core/syslog_controller_test.go @@ -6,6 +6,7 @@ package core import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -69,7 +70,13 @@ var _ = Describe("Syslog Controller", func() { By("Verifying the resource is removed from the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.Syslog).To(BeNil(), "Provider should not have Syslog configured") + g.Expect(testDevices.StateFor(name).Syslog).To(BeNil(), "Provider should not have Syslog configured") + }).Should(Succeed()) + + By("Waiting for the Syslog to be fully deleted") + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, key, &v1alpha1.Syslog{}) + g.Expect(apierrors.IsNotFound(err)).To(BeTrue()) }).Should(Succeed()) By("Cleaning up the Device resource") @@ -116,7 +123,7 @@ var _ = Describe("Syslog Controller", func() { By("Ensuring the resource is created in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.Syslog).NotTo(BeNil(), "Provider should have Syslog configured") + g.Expect(testDevices.StateFor(name).Syslog).NotTo(BeNil(), "Provider should have Syslog configured") }).Should(Succeed()) }) }) diff --git a/internal/controller/core/user_controller_test.go b/internal/controller/core/user_controller_test.go index a5b1438af..38f378dbc 100644 --- a/internal/controller/core/user_controller_test.go +++ b/internal/controller/core/user_controller_test.go @@ -7,6 +7,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -77,7 +78,13 @@ var _ = Describe("User Controller", func() { By("Verifying the resource is removed from the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.User.Has(username)).To(BeFalse(), "User should not exist") + g.Expect(testDevices.StateFor(name).User.Has(username)).To(BeFalse(), "User should not exist") + }).Should(Succeed()) + + By("Waiting for the User to be fully deleted") + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, key, &v1alpha1.User{}) + g.Expect(apierrors.IsNotFound(err)).To(BeTrue()) }).Should(Succeed()) By("Cleaning up the Device resource") @@ -124,7 +131,7 @@ var _ = Describe("User Controller", func() { By("Ensuring the resource is created in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.User.Has(username)).To(BeTrue(), "User should exist") + g.Expect(testDevices.StateFor(name).User.Has(username)).To(BeTrue(), "User should exist") }).Should(Succeed()) }) }) diff --git a/internal/controller/core/vlan_controller_test.go b/internal/controller/core/vlan_controller_test.go index 985e2f46d..3d6c4554a 100644 --- a/internal/controller/core/vlan_controller_test.go +++ b/internal/controller/core/vlan_controller_test.go @@ -6,6 +6,7 @@ package core import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -59,7 +60,13 @@ var _ = Describe("VLAN Controller", func() { By("Verifying the resource is removed from the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.VLANs.Has(id)).To(BeFalse(), "Provider VLAN should not exist") + g.Expect(testDevices.StateFor(name).VLANs.Has(id)).To(BeFalse(), "Provider VLAN should not exist") + }).Should(Succeed()) + + By("Waiting for the VLAN to be fully deleted") + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, key, &v1alpha1.VLAN{}) + g.Expect(apierrors.IsNotFound(err)).To(BeTrue()) }).Should(Succeed()) By("Cleaning up the Device resource") @@ -110,7 +117,7 @@ var _ = Describe("VLAN Controller", func() { By("Ensuring the resource is created in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.VLANs.Has(id)).To(BeTrue(), "Provider VLAN should exist") + g.Expect(testDevices.StateFor(name).VLANs.Has(id)).To(BeTrue(), "Provider VLAN should exist") }).Should(Succeed()) }) }) diff --git a/internal/controller/core/vrf_controller_test.go b/internal/controller/core/vrf_controller_test.go index d28ef4dbf..a020573a2 100644 --- a/internal/controller/core/vrf_controller_test.go +++ b/internal/controller/core/vrf_controller_test.go @@ -6,11 +6,11 @@ package core import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/ironcore-dev/network-operator/api/core/v1alpha1" ) @@ -78,7 +78,13 @@ var _ = Describe("VRF Controller", func() { By("Verifying the resource is removed from the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.VRF.Has("CC-ADMIN-TEST")).To(BeFalse(), "Provider should not have VRF configured anymore") + g.Expect(testDevices.StateFor(name).VRF.Has("CC-ADMIN-TEST")).To(BeFalse(), "Provider should not have VRF configured anymore") + }).Should(Succeed()) + + By("Waiting for the VRF to be fully deleted") + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, key, &v1alpha1.VRF{}) + g.Expect(apierrors.IsNotFound(err)).To(BeTrue()) }).Should(Succeed()) By("Cleaning up the Device resource") @@ -121,9 +127,9 @@ var _ = Describe("VRF Controller", func() { By("Ensuring the VRF is created in the provider") Eventually(func(g Gomega) { - g.Expect(testProvider.VRF).ToNot(BeNil(), "Provider VRF should not be nil") - if testProvider.VRF != nil { - g.Expect(testProvider.VRF.Has("CC-ADMIN-TEST")).To(BeTrue(), "Provider should have VRF configured") + g.Expect(testDevices.StateFor(name).VRF).ToNot(BeNil(), "Provider VRF should not be nil") + if testDevices.StateFor(name).VRF != nil { + g.Expect(testDevices.StateFor(name).VRF.Has("CC-ADMIN-TEST")).To(BeTrue(), "Provider should have VRF configured") } }).Should(Succeed()) }) diff --git a/internal/deviceutil/deviceutil.go b/internal/deviceutil/deviceutil.go index 44bbb36dd..dac8f3e89 100644 --- a/internal/deviceutil/deviceutil.go +++ b/internal/deviceutil/deviceutil.go @@ -107,6 +107,8 @@ func GetDeviceBySerial(ctx context.Context, r client.Reader, serial string) (*v1 // Connection holds the necessary information to connect to a device's API. type Connection struct { + // DeviceName is the name of the Device object this connection belongs to. + DeviceName string // Address is the API address of the device, in the format "host:port". Address string // Username for basic authentication. Might be empty if the device does not require authentication. @@ -156,9 +158,10 @@ func GetDeviceConnection(ctx context.Context, r client.Reader, obj *v1alpha1.Dev } return &Connection{ - Address: obj.Spec.Endpoint.Address, - Username: string(user), - Password: string(pass), - TLS: conf, + DeviceName: obj.Name, + Address: obj.Spec.Endpoint.Address, + Username: string(user), + Password: string(pass), + TLS: conf, }, nil }