github.com/openshift/dpu-operator@v0.0.0-20240502153209-3af840d137c2/internal/controller/dpuoperatorconfig_controller_test.go (about) 1 package controller 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "path/filepath" 8 "sync" 9 "time" 10 11 netattdefv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" 12 . "github.com/onsi/ginkgo/v2" 13 . "github.com/onsi/gomega" 14 configv1 "github.com/openshift/dpu-operator/api/v1" 15 appsv1 "k8s.io/api/apps/v1" 16 corev1 "k8s.io/api/core/v1" 17 "k8s.io/apimachinery/pkg/api/errors" 18 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 19 "k8s.io/apimachinery/pkg/types" 20 "k8s.io/client-go/kubernetes/scheme" 21 ctrl "sigs.k8s.io/controller-runtime" 22 "sigs.k8s.io/controller-runtime/pkg/client" 23 "sigs.k8s.io/controller-runtime/pkg/envtest" 24 ) 25 26 var ( 27 testNamespace = "openshift-dpu-operator" 28 testDpuOperatorConfigName = "default" 29 testDpuOperatorConfigKind = "DpuOperatorConfig" 30 testDpuDaemonName = "dpu-daemon" 31 testSriovDevicePlugin = "sriov-device-plugin" 32 testNetworkFunctionNAD = "dpunfcni-conf" 33 testAPITimeout = time.Second * 20 34 testRetryInterval = time.Second * 1 35 ) 36 37 func WaitForDaemonSetReady(daemonSet *appsv1.DaemonSet, k8sClient client.Client, namespace, name string) { 38 Eventually(func() error { 39 err := k8sClient.Get(context.Background(), types.NamespacedName{Name: name, Namespace: namespace}, daemonSet) 40 if err != nil { 41 return err 42 } 43 if daemonSet.Status.DesiredNumberScheduled != daemonSet.Status.NumberReady { 44 return fmt.Errorf("Desired Number Scheduled(%v) != NumberReady(%v)", daemonSet.Status.DesiredNumberScheduled, daemonSet.Status.NumberReady) 45 } else { 46 return nil 47 } 48 }, testAPITimeout, testRetryInterval).ShouldNot(HaveOccurred()) 49 } 50 51 func createDpuOperatorNameSpace() *corev1.Namespace { 52 namespace := &corev1.Namespace{ 53 TypeMeta: metav1.TypeMeta{}, 54 ObjectMeta: metav1.ObjectMeta{ 55 Name: testNamespace, 56 }, 57 Spec: corev1.NamespaceSpec{}, 58 Status: corev1.NamespaceStatus{}, 59 } 60 return namespace 61 } 62 63 func ensureDpuOperatorNamespace() { 64 By("create test DPU operator namespace") 65 namespace := createDpuOperatorNameSpace() 66 Expect(k8sClient.Create(context.Background(), namespace)).Should(Succeed()) 67 68 By("verify test DPU operator namespace is created") 69 retrieved_namespace := &corev1.Namespace{} 70 err := k8sClient.Get(context.Background(), client.ObjectKey{Name: testNamespace}, retrieved_namespace) 71 Expect(err).NotTo(HaveOccurred()) 72 Expect(retrieved_namespace.ObjectMeta.Name).To(Equal(testNamespace)) 73 } 74 75 func startDPUControllerManager(ctx context.Context, wg *sync.WaitGroup) { 76 By("setting up env variables for tests") 77 err := os.Setenv("DPU_DAEMON_IMAGE", "mock-image") 78 Expect(err).NotTo(HaveOccurred()) 79 80 By("setup controller manager") 81 k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ 82 Scheme: scheme.Scheme, 83 }) 84 Expect(err).ToNot(HaveOccurred()) 85 86 By("setup controller manager reconciler") 87 err = (&DpuOperatorConfigReconciler{ 88 Client: k8sManager.GetClient(), 89 Scheme: k8sManager.GetScheme(), 90 }).SetupWithManager(k8sManager) 91 Expect(err).ToNot(HaveOccurred()) 92 93 wg.Add(1) 94 go func() { 95 defer wg.Done() 96 defer GinkgoRecover() 97 By("start controller manager") 98 err := k8sManager.Start(ctx) 99 Expect(err).ToNot(HaveOccurred()) 100 }() 101 } 102 103 func stopDPUControllerManager(mode string, cancel context.CancelFunc, wg *sync.WaitGroup) { 104 By("shut down controller manager") 105 cancel() 106 wg.Wait() 107 config := createDpuOperatorCR(mode) 108 err := k8sClient.Delete(context.Background(), config) 109 Expect(err).ToNot(HaveOccurred()) 110 } 111 112 func createDpuOperatorCR(mode string) *configv1.DpuOperatorConfig { 113 config := &configv1.DpuOperatorConfig{} 114 config.SetNamespace(testNamespace) 115 config.SetName(testDpuOperatorConfigName) 116 config.Spec = configv1.DpuOperatorConfigSpec{ 117 Mode: mode, 118 LogLevel: 2, 119 } 120 return config 121 } 122 123 func ensureDpuOperatorCR(mode string) { 124 By("create DpuOperatorConfig CR") 125 config := createDpuOperatorCR(mode) 126 Expect(k8sClient.Create(context.Background(), config)).Should(Succeed()) 127 128 By("verify DpuOperatorConfig CR is created") 129 retrieved_config := &configv1.DpuOperatorConfig{} 130 err := k8sClient.Get(context.Background(), client.ObjectKey{Namespace: testNamespace, Name: testDpuOperatorConfigName}, retrieved_config) 131 Expect(err).NotTo(HaveOccurred()) 132 Expect(retrieved_config.ObjectMeta.Namespace).To(Equal(testNamespace)) 133 Expect(retrieved_config.ObjectMeta.Name).To(Equal(testDpuOperatorConfigName)) 134 } 135 136 var _ = Describe("Main Controller", Ordered, func() { 137 var cancel context.CancelFunc 138 var ctx context.Context 139 var wg sync.WaitGroup 140 141 BeforeEach(func() { 142 // IMPORTANT Note: The Envtest has many limitations described here: 143 // https://book.kubebuilder.io/reference/envtest.html#testing-considerations 144 // Thus we need to create and destroy the Envtest environment. Please note 145 // that Envtest does not garbage collect thus owner references do not get 146 // cleaned up!! 147 By("bootstrapping test environment") 148 testEnv = &envtest.Environment{ 149 CRDDirectoryPaths: []string{filepath.Join("config", "crd", "bases"), filepath.Join("test", "crd")}, 150 ErrorIfCRDPathMissing: true, 151 } 152 var err error 153 By("starting the test env") 154 cfg, err = testEnv.Start() 155 Expect(err).NotTo(HaveOccurred()) 156 Expect(cfg).NotTo(BeNil()) 157 158 By("registering schemes") 159 err = configv1.AddToScheme(scheme.Scheme) 160 Expect(err).NotTo(HaveOccurred()) 161 err = netattdefv1.AddToScheme(scheme.Scheme) 162 Expect(err).NotTo(HaveOccurred()) 163 164 By("creating k8s client") 165 k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 166 Expect(err).NotTo(HaveOccurred()) 167 Expect(k8sClient).NotTo(BeNil()) 168 169 // NOTE: Please refer to this limitation of namespace deletion: 170 // https://book.kubebuilder.io/reference/envtest.html#namespace-usage-limitation 171 // Namespaces cannot be cleaned up! 172 ensureDpuOperatorNamespace() 173 }) 174 175 Context("When Host controller manager has started without DpuOperatorConfig CR", func() { 176 mode := "host" 177 BeforeEach(func() { 178 ctx, cancel = context.WithCancel(context.Background()) 179 wg = sync.WaitGroup{} 180 startDPUControllerManager(ctx, &wg) 181 ensureDpuOperatorCR(mode) 182 }) 183 It("should have DPU daemon daemonsets created by controller manager", func() { 184 daemonSet := &appsv1.DaemonSet{} 185 WaitForDaemonSetReady(daemonSet, k8sClient, testNamespace, testDpuDaemonName) 186 Expect(daemonSet.OwnerReferences).To(HaveLen(1)) 187 Expect(daemonSet.OwnerReferences[0].Kind).To(Equal(testDpuOperatorConfigKind)) 188 Expect(daemonSet.OwnerReferences[0].Name).To(Equal(testDpuOperatorConfigName)) 189 Expect(daemonSet.Spec.Template.Spec.Containers[0].Args[1]).To(Equal(mode)) 190 }) 191 It("should have SR-IOV device plugin daemonsets created by controller manager", func() { 192 daemonSet := &appsv1.DaemonSet{} 193 WaitForDaemonSetReady(daemonSet, k8sClient, testNamespace, testSriovDevicePlugin) 194 Expect(daemonSet.OwnerReferences).To(HaveLen(1)) 195 Expect(daemonSet.OwnerReferences[0].Kind).To(Equal(testDpuOperatorConfigKind)) 196 Expect(daemonSet.OwnerReferences[0].Name).To(Equal(testDpuOperatorConfigName)) 197 }) 198 It("should not have the network function NAD created by controller manager", func() { 199 nad := &netattdefv1.NetworkAttachmentDefinition{} 200 err := k8sClient.Get(context.Background(), types.NamespacedName{Namespace: testNamespace, Name: testNetworkFunctionNAD}, nad) 201 Expect(errors.IsNotFound(err)).To(BeTrue()) 202 }) 203 AfterEach(func() { 204 stopDPUControllerManager(mode, cancel, &wg) 205 }) 206 }) 207 208 Context("When DPU controller manager has started without DpuOperatorConfig CR", func() { 209 mode := "dpu" 210 BeforeEach(func() { 211 ctx, cancel = context.WithCancel(context.Background()) 212 wg = sync.WaitGroup{} 213 startDPUControllerManager(ctx, &wg) 214 ensureDpuOperatorCR(mode) 215 }) 216 It("should have DPU daemon daemonsets created by controller manager", func() { 217 daemonSet := &appsv1.DaemonSet{} 218 WaitForDaemonSetReady(daemonSet, k8sClient, testNamespace, testDpuDaemonName) 219 Expect(daemonSet.OwnerReferences).To(HaveLen(1)) 220 Expect(daemonSet.OwnerReferences[0].Kind).To(Equal(testDpuOperatorConfigKind)) 221 Expect(daemonSet.OwnerReferences[0].Name).To(Equal(testDpuOperatorConfigName)) 222 Expect(daemonSet.Spec.Template.Spec.Containers[0].Args[1]).To(Equal(mode)) 223 }) 224 It("should not have SR-IOV device plugin daemonsets created by controller manager", func() { 225 daemonSet := &appsv1.DaemonSet{} 226 err := k8sClient.Get(context.Background(), types.NamespacedName{Namespace: testNamespace, Name: testSriovDevicePlugin}, daemonSet) 227 Expect(errors.IsNotFound(err)).To(BeTrue()) 228 }) 229 It("should have SR-IOV device plugin daemonsets created by controller manager", func() { 230 nad := &netattdefv1.NetworkAttachmentDefinition{} 231 Eventually(func() error { 232 err := k8sClient.Get(context.Background(), types.NamespacedName{Namespace: testNamespace, Name: testNetworkFunctionNAD}, nad) 233 if err != nil { 234 return err 235 } 236 return nil 237 }, testAPITimeout, testRetryInterval).ShouldNot(HaveOccurred()) 238 Expect(nad.OwnerReferences).To(HaveLen(1)) 239 Expect(nad.OwnerReferences[0].Kind).To(Equal(testDpuOperatorConfigKind)) 240 Expect(nad.OwnerReferences[0].Name).To(Equal(testDpuOperatorConfigName)) 241 }) 242 AfterEach(func() { 243 stopDPUControllerManager(mode, cancel, &wg) 244 }) 245 }) 246 247 AfterEach(func() { 248 By("tearing down the test environment") 249 err := testEnv.Stop() 250 Expect(err).NotTo(HaveOccurred()) 251 }) 252 })