diff --git a/controllers/sriovibnetwork_controller.go b/controllers/sriovibnetwork_controller.go index b67bdec35..86caf92fe 100644 --- a/controllers/sriovibnetwork_controller.go +++ b/controllers/sriovibnetwork_controller.go @@ -21,12 +21,15 @@ import ( "reflect" netattdefv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/workqueue" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -136,10 +139,16 @@ func (r *SriovIBNetworkReconciler) Reconcile(ctx context.Context, req ctrl.Reque } // Check if this NetworkAttachmentDefinition already exists found := &netattdefv1.NetworkAttachmentDefinition{} - err = r.Get(ctx, types.NamespacedName{Name: netAttDef.Name, Namespace: netAttDef.Namespace}, found) if err != nil { if errors.IsNotFound(err) { + targetNamespace := &corev1.Namespace{} + err = r.Get(ctx, types.NamespacedName{Name: netAttDef.Namespace}, targetNamespace) + if errors.IsNotFound(err) { + reqLogger.Info("Target namespace doesn't exist, NetworkAttachmentDefinition will be created when namespace is available", "Namespace", netAttDef.Namespace, "Name", netAttDef.Name) + return reconcile.Result{}, nil + } + reqLogger.Info("NetworkAttachmentDefinition CR not exist, creating") err = r.Create(ctx, netAttDef) if err != nil { @@ -172,8 +181,30 @@ func (r *SriovIBNetworkReconciler) Reconcile(ctx context.Context, req ctrl.Reque // SetupWithManager sets up the controller with the Manager. func (r *SriovIBNetworkReconciler) SetupWithManager(mgr ctrl.Manager) error { + // Reconcile when the target namespace is created after the SriovNetwork object. + namespaceHandler := handler.Funcs{ + CreateFunc: func(ctx context.Context, e event.CreateEvent, q workqueue.RateLimitingInterface) { + ibNetworkList := sriovnetworkv1.SriovIBNetworkList{} + err := r.List(ctx, + &ibNetworkList, + client.MatchingFields{"spec.networkNamespace": e.Object.GetName()}, + ) + if err != nil { + log.Log.WithName("SriovIBNetworkReconciler"). + Info("Can't list SriovIBNetworkReconciler for namespace", "resource", e.Object.GetName(), "error", err) + } + + for _, network := range ibNetworkList.Items { + q.Add(reconcile.Request{NamespacedName: types.NamespacedName{ + Namespace: network.Namespace, + Name: network.Name, + }}) + } + }, + } return ctrl.NewControllerManagedBy(mgr). For(&sriovnetworkv1.SriovIBNetwork{}). Watches(&netattdefv1.NetworkAttachmentDefinition{}, &handler.EnqueueRequestForObject{}). + Watches(&corev1.Namespace{}, &namespaceHandler). Complete(r) } diff --git a/controllers/sriovibnetwork_controller_test.go b/controllers/sriovibnetwork_controller_test.go index 5ee52c856..fa676c369 100644 --- a/controllers/sriovibnetwork_controller_test.go +++ b/controllers/sriovibnetwork_controller_test.go @@ -8,6 +8,7 @@ import ( "time" netattdefv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/util/retry" @@ -189,6 +190,52 @@ var _ = Describe("SriovIBNetwork Controller", func() { Expect(err).NotTo(HaveOccurred()) }) }) + + Context("When the target NetworkNamespace doesn't exists", func() { + It("should create the NetAttachDef when the namespace is created", func() { + cr := sriovnetworkv1.SriovIBNetwork{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-missing-namespace", + Namespace: testNamespace, + }, + Spec: sriovnetworkv1.SriovIBNetworkSpec{ + NetworkNamespace: "ib-ns-xxx", + ResourceName: "resource_missing_namespace", + IPAM: `{"type":"dhcp"}`, + }, + } + var err error + expect := generateExpectedIBNetConfig(&cr) + + err = k8sClient.Create(goctx.TODO(), &cr) + Expect(err).NotTo(HaveOccurred()) + + DeferCleanup(func() { + err = k8sClient.Delete(goctx.TODO(), &cr) + Expect(err).NotTo(HaveOccurred()) + }) + + // Sleep 3 seconds to be sure the Reconcile loop has been invoked. This can be improved by exposing some information (e.g. the error) + // in the SriovIBNetwork.Status field. + time.Sleep(3 * time.Second) + + netAttDef := &netattdefv1.NetworkAttachmentDefinition{} + err = k8sClient.Get(ctx, types.NamespacedName{Name: cr.GetName(), Namespace: "ib-ns-xxx"}, netAttDef) + Expect(err).To(HaveOccurred()) + + err = k8sClient.Create(goctx.TODO(), &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: "ib-ns-xxx"}, + }) + Expect(err).NotTo(HaveOccurred()) + + err = util.WaitForNamespacedObject(netAttDef, k8sClient, "ib-ns-xxx", cr.GetName(), util.RetryInterval, util.Timeout) + Expect(err).NotTo(HaveOccurred()) + + anno := netAttDef.GetAnnotations() + Expect(anno["k8s.v1.cni.cncf.io/resourceName"]).To(Equal("openshift.io/" + cr.Spec.ResourceName)) + Expect(strings.TrimSpace(netAttDef.Spec.Config)).To(Equal(expect)) + }) + }) }) func generateExpectedIBNetConfig(cr *sriovnetworkv1.SriovIBNetwork) string { diff --git a/controllers/suite_test.go b/controllers/suite_test.go index 74b38a2e9..c3eda1a90 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -113,6 +113,10 @@ var _ = BeforeSuite(func(done Done) { return []string{o.(*sriovnetworkv1.SriovNetwork).Spec.NetworkNamespace} }) + k8sManager.GetCache().IndexField(context.Background(), &sriovnetworkv1.SriovIBNetwork{}, "spec.networkNamespace", func(o client.Object) []string { + return []string{o.(*sriovnetworkv1.SriovIBNetwork).Spec.NetworkNamespace} + }) + err = (&SriovNetworkReconciler{ Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), diff --git a/main.go b/main.go index 41d3a2bb1..c8225b54f 100644 --- a/main.go +++ b/main.go @@ -130,6 +130,10 @@ func main() { return []string{o.(*sriovnetworkv1.SriovNetwork).Spec.NetworkNamespace} }) + mgrGlobal.GetCache().IndexField(context.Background(), &sriovnetworkv1.SriovIBNetwork{}, "spec.networkNamespace", func(o client.Object) []string { + return []string{o.(*sriovnetworkv1.SriovIBNetwork).Spec.NetworkNamespace} + }) + if err := initNicIDMap(); err != nil { setupLog.Error(err, "unable to init NicIdMap") os.Exit(1)