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  })