diff --git a/README.md b/README.md index 529b9f37cd..e0237994b4 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,8 @@ The SR-IOV network operator introduces following new CRDs: - SriovNetwork +- OVSNetwork + - SriovNetworkNodeState - SriovNetworkNodePolicy @@ -101,6 +103,43 @@ spec: } ``` +### OVSNetwork + +A custom resource of OVSNetwork could represent the a layer-2 broadcast domain attached to Open vSwitch that works in HW-offloading mode. +It is primarily used to generate a NetworkAttachmentDefinition CR with an OVS CNI plugin configuration. + +The OVSNetwork CR also contains the `resourceName` which is aligned with the `resourceName` of SR-IOV device plugin. One OVSNetwork obj maps to one `resourceName`, but one `resourceName` can be shared by different OVSNetwork CRs. + +It is expected that `resourceName` contains name of the resource pool which holds Virtual Functions of a NIC in the switchdev mode. +A Physical function of the NIC should be attached to an OVS bridge before any workload which uses OVSNetwork starts. + +_Note: If `OVSNetwork.Spec.bridge` is not set, then ovs-cni will try to automatically select the right OVS bridge by resolving: VF>PF>OVS-bridge_ + +Example: + +```yaml +apiVersion: sriovnetwork.openshift.io/v1 +kind: OVSNetwork +metadata: + name: example-network + namespace: example-namespace +spec: + ipam: | + { + "type": "host-local", + "subnet": "10.56.217.0/24", + "rangeStart": "10.56.217.171", + "rangeEnd": "10.56.217.181", + "routes": [{ + "dst": "0.0.0.0/0" + }], + "gateway": "10.56.217.1" + } + vlan: 100 + mtu: 2500 + resourceName: switchdevnics +``` + ### SriovNetworkNodeState The custom resource to represent the SR-IOV interface states of each host, which should only be managed by the operator itself. diff --git a/api/v1/helper.go b/api/v1/helper.go index 9f21394371..98d12b4df7 100644 --- a/api/v1/helper.go +++ b/api/v1/helper.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "os" + "path/filepath" "regexp" "sort" "strconv" @@ -630,7 +631,7 @@ func (cr *SriovIBNetwork) RenderNetAttDef() (*uns.Unstructured, error) { data.Data["LogLevelConfigured"] = false data.Data["LogFileConfigured"] = false - objs, err := render.RenderDir(ManifestsPath, &data) + objs, err := render.RenderDir(filepath.Join(ManifestsPath, "sriov"), &data) if err != nil { return nil, err } @@ -748,7 +749,7 @@ func (cr *SriovNetwork) RenderNetAttDef() (*uns.Unstructured, error) { data.Data["LogFileConfigured"] = (cr.Spec.LogFile != "") data.Data["LogFile"] = cr.Spec.LogFile - objs, err := render.RenderDir(ManifestsPath, &data) + objs, err := render.RenderDir(filepath.Join(ManifestsPath, "sriov"), &data) if err != nil { return nil, err } @@ -764,6 +765,68 @@ func (cr *SriovNetwork) NetworkNamespace() string { return cr.Spec.NetworkNamespace } +// RenderNetAttDef renders a net-att-def for sriov CNI +func (cr *OVSNetwork) RenderNetAttDef() (*uns.Unstructured, error) { + logger := log.WithName("RenderNetAttDef") + logger.Info("Start to render OVS CNI NetworkAttachmentDefinition") + + // render RawCNIConfig manifests + data := render.MakeRenderData() + data.Data["CniType"] = "ovs" + data.Data["NetworkName"] = cr.Name + if cr.Spec.NetworkNamespace == "" { + data.Data["NetworkNamespace"] = cr.Namespace + } else { + data.Data["NetworkNamespace"] = cr.Spec.NetworkNamespace + } + data.Data["CniResourceName"] = os.Getenv("RESOURCE_PREFIX") + "/" + cr.Spec.ResourceName + + if cr.Spec.Capabilities == "" { + data.Data["CapabilitiesConfigured"] = false + } else { + data.Data["CapabilitiesConfigured"] = true + data.Data["CniCapabilities"] = cr.Spec.Capabilities + } + + data.Data["Bridge"] = cr.Spec.Bridge + data.Data["VlanTag"] = cr.Spec.Vlan + data.Data["MTU"] = cr.Spec.MTU + if len(cr.Spec.Trunk) > 0 { + trunkConfRaw, _ := json.Marshal(cr.Spec.Trunk) + data.Data["Trunk"] = string(trunkConfRaw) + } else { + data.Data["Trunk"] = "" + } + data.Data["InterfaceType"] = cr.Spec.InterfaceType + + if cr.Spec.IPAM != "" { + data.Data["CniIpam"] = SriovCniIpam + ":" + strings.Join(strings.Fields(cr.Spec.IPAM), "") + } else { + data.Data["CniIpam"] = SriovCniIpamEmpty + } + + data.Data["MetaPluginsConfigured"] = false + if cr.Spec.MetaPluginsConfig != "" { + data.Data["MetaPluginsConfigured"] = true + data.Data["MetaPlugins"] = cr.Spec.MetaPluginsConfig + } + + objs, err := render.RenderDir(filepath.Join(ManifestsPath, "ovs"), &data) + if err != nil { + return nil, err + } + for _, obj := range objs { + raw, _ := json.Marshal(obj) + logger.Info("render NetworkAttachmentDefinition output", "raw", string(raw)) + } + return objs[0], nil +} + +// NetworkNamespace returns target network namespace for the network +func (cr *OVSNetwork) NetworkNamespace() string { + return cr.Spec.NetworkNamespace +} + // NetFilterMatch -- parse netFilter and check for a match func NetFilterMatch(netFilter string, netValue string) (isMatch bool) { logger := log.WithName("NetFilterMatch") diff --git a/api/v1/helper_test.go b/api/v1/helper_test.go index 860827f639..cae4e2a001 100644 --- a/api/v1/helper_test.go +++ b/api/v1/helper_test.go @@ -249,6 +249,99 @@ func TestIBRendering(t *testing.T) { } } +func TestOVSRendering(t *testing.T) { + testtable := []struct { + tname string + network v1.OVSNetwork + }{ + { + tname: "simpleovs", + network: v1.OVSNetwork{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: v1.OVSNetworkSpec{ + NetworkNamespace: "testnamespace", + ResourceName: "testresource", + }, + }, + }, + { + tname: "chained", + network: v1.OVSNetwork{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: v1.OVSNetworkSpec{ + NetworkNamespace: "testnamespace", + ResourceName: "testresource", + MTU: 1500, + MetaPluginsConfig: ` + { + "type": "vrf", + "vrfname": "blue" + } + `, + }, + }, + }, + { + tname: "complexconf", + network: v1.OVSNetwork{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: v1.OVSNetworkSpec{ + NetworkNamespace: "testnamespace", + ResourceName: "testresource", + Capabilities: `{"foo": "bar"}`, + Bridge: "test", + Vlan: 100, + MTU: 1500, + Trunk: []*v1.TrunkConfig{ + { + ID: func(i uint) *uint { return &i }(120)}, + { + MinID: func(i uint) *uint { return &i }(500), + MaxID: func(i uint) *uint { return &i }(550)}, + }, + InterfaceType: "netdev", + IPAM: `{"type": "foo"}`, + }, + }, + }, + } + for _, tc := range testtable { + t.Run(tc.tname, func(t *testing.T) { + var b bytes.Buffer + w := bufio.NewWriter(&b) + rendered, err := tc.network.RenderNetAttDef() + if err != nil { + t.Fatal("failed rendering network attachment definition", err) + } + encoder := json.NewEncoder(w) + encoder.SetIndent("", " ") + encoder.Encode(rendered) + w.Flush() + gp := filepath.Join("testdata", filepath.FromSlash(t.Name())+".golden") + if *update { + t.Log("update golden file") + if err := os.WriteFile(gp, b.Bytes(), 0644); err != nil { + t.Fatalf("failed to update golden file: %s", err) + } + } + g, err := os.ReadFile(gp) + if err != nil { + t.Fatalf("failed reading .golden: %s", err) + } + t.Log(b.String()) + if !bytes.Equal(b.Bytes(), g) { + t.Errorf("bytes do not match .golden file") + } + }) + } +} + func TestSriovNetworkNodePolicyApply(t *testing.T) { testtable := []struct { tname string diff --git a/api/v1/testdata/TestOVSRendering/chained.golden b/api/v1/testdata/TestOVSRendering/chained.golden new file mode 100644 index 0000000000..3589e3daf2 --- /dev/null +++ b/api/v1/testdata/TestOVSRendering/chained.golden @@ -0,0 +1,14 @@ +{ + "apiVersion": "k8s.cni.cncf.io/v1", + "kind": "NetworkAttachmentDefinition", + "metadata": { + "annotations": { + "k8s.v1.cni.cncf.io/resourceName": "/testresource" + }, + "name": "test", + "namespace": "testnamespace" + }, + "spec": { + "config": "{ \"cniVersion\":\"0.3.1\", \"name\":\"test\",\"plugins\": [ {\"type\":\"ovs\",\"mtu\":1500,\"ipam\":{} },\n{ \"type\": \"vrf\", \"vrfname\": \"blue\" }\n] }" + } +} diff --git a/api/v1/testdata/TestOVSRendering/complexconf.golden b/api/v1/testdata/TestOVSRendering/complexconf.golden new file mode 100644 index 0000000000..9806ba8416 --- /dev/null +++ b/api/v1/testdata/TestOVSRendering/complexconf.golden @@ -0,0 +1,14 @@ +{ + "apiVersion": "k8s.cni.cncf.io/v1", + "kind": "NetworkAttachmentDefinition", + "metadata": { + "annotations": { + "k8s.v1.cni.cncf.io/resourceName": "/testresource" + }, + "name": "test", + "namespace": "testnamespace" + }, + "spec": { + "config": "{ \"cniVersion\":\"0.3.1\", \"name\":\"test\",\"type\":\"ovs\",\"capabilities\":{\"foo\": \"bar\"},\"bridge\":\"test\",\"vlan\":100,\"mtu\":1500,\"trunk\":[{\"id\":120},{\"minID\":500,\"maxID\":550}],\"interface_type\":\"netdev\",\"ipam\":{\"type\":\"foo\"} }" + } +} diff --git a/api/v1/testdata/TestOVSRendering/simpleovs.golden b/api/v1/testdata/TestOVSRendering/simpleovs.golden new file mode 100644 index 0000000000..1f286da7cf --- /dev/null +++ b/api/v1/testdata/TestOVSRendering/simpleovs.golden @@ -0,0 +1,14 @@ +{ + "apiVersion": "k8s.cni.cncf.io/v1", + "kind": "NetworkAttachmentDefinition", + "metadata": { + "annotations": { + "k8s.v1.cni.cncf.io/resourceName": "/testresource" + }, + "name": "test", + "namespace": "testnamespace" + }, + "spec": { + "config": "{ \"cniVersion\":\"0.3.1\", \"name\":\"test\",\"type\":\"ovs\",\"ipam\":{} }" + } +} diff --git a/bindata/manifests/cni-config/ovs/ovs-cni-config.yaml b/bindata/manifests/cni-config/ovs/ovs-cni-config.yaml new file mode 100644 index 0000000000..30a83b7f3c --- /dev/null +++ b/bindata/manifests/cni-config/ovs/ovs-cni-config.yaml @@ -0,0 +1,43 @@ +apiVersion: "k8s.cni.cncf.io/v1" +kind: NetworkAttachmentDefinition +metadata: + name: {{.NetworkName}} + namespace: {{.NetworkNamespace}} + annotations: + k8s.v1.cni.cncf.io/resourceName: {{.CniResourceName}} +spec: + config: '{ + "cniVersion":"0.3.1", + "name":"{{.NetworkName}}", +{{- if .MetaPluginsConfigured -}} + "plugins": [ + { +{{- end -}} + "type":"{{.CniType}}", +{{- if .CapabilitiesConfigured -}} + "capabilities":{{.CniCapabilities}}, +{{- end -}} +{{- if .Bridge -}} + "bridge":"{{.Bridge}}", +{{- end -}} +{{- if .VlanTag -}} + "vlan":{{.VlanTag}}, +{{- end -}} +{{- if .MTU -}} + "mtu":{{.MTU}}, +{{- end -}} +{{- if .Trunk -}} + "trunk":{{.Trunk}}, +{{- end -}} +{{- if .InterfaceType -}} + "interface_type":"{{.InterfaceType}}", +{{- end -}} + {{.CniIpam}} +} +{{- if .MetaPluginsConfigured -}} + , + {{.MetaPlugins}} + ] +} +{{- end -}} +' diff --git a/bindata/manifests/cni-config/sriov-cni-config.yaml b/bindata/manifests/cni-config/sriov/sriov-cni-config.yaml similarity index 100% rename from bindata/manifests/cni-config/sriov-cni-config.yaml rename to bindata/manifests/cni-config/sriov/sriov-cni-config.yaml diff --git a/controllers/ovsnetwork_controller.go b/controllers/ovsnetwork_controller.go index 5e06e1fb91..2f2eaee1eb 100644 --- a/controllers/ovsnetwork_controller.go +++ b/controllers/ovsnetwork_controller.go @@ -1,5 +1,5 @@ /* -Copyright 2021. +Copyright 2024. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -22,7 +22,6 @@ import ( "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" sriovnetworkv1 "github.com/k8snetworkplumbingwg/sriov-network-operator/api/v1" ) @@ -30,33 +29,36 @@ import ( // OVSNetworkReconciler reconciles a OVSNetwork object type OVSNetworkReconciler struct { client.Client - Scheme *runtime.Scheme + Scheme *runtime.Scheme + genericReconciler *genericNetworkReconciler } //+kubebuilder:rbac:groups=sriovnetwork.openshift.io,resources=ovsnetworks,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=sriovnetwork.openshift.io,resources=ovsnetworks/status,verbs=get;update;patch //+kubebuilder:rbac:groups=sriovnetwork.openshift.io,resources=ovsnetworks/finalizers,verbs=update -// Reconcile is part of the main kubernetes reconciliation loop which aims to -// move the current state of the cluster closer to the desired state. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the OVSNetwork object against the actual cluster state, and then -// perform operations to make the cluster state reflect the state specified by -// the user. -// -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.1/pkg/reconcile +// Reconcile loop for OVSNetwork CRs func (r *OVSNetworkReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - _ = log.FromContext(ctx) + return r.genericReconciler.Reconcile(ctx, req) +} + +// return name of the controller +func (r *OVSNetworkReconciler) Name() string { + return "OVSNetwork" +} - // TODO(user): your logic here +// return empty instance of the OVSNetwork CR +func (r *OVSNetworkReconciler) GetObject() networkCRInstance { + return &sriovnetworkv1.OVSNetwork{} +} - return ctrl.Result{}, nil +// return empty list of the OVSNetwork CRs +func (r *OVSNetworkReconciler) GetObjectList() client.ObjectList { + return &sriovnetworkv1.OVSNetworkList{} } // SetupWithManager sets up the controller with the Manager. func (r *OVSNetworkReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&sriovnetworkv1.OVSNetwork{}). - Complete(r) + r.genericReconciler = newGenericNetworkReconciler(r.Client, r.Scheme, r) + return r.genericReconciler.SetupWithManager(mgr) } diff --git a/controllers/ovsnetwork_controller_test.go b/controllers/ovsnetwork_controller_test.go new file mode 100644 index 0000000000..ef464e5e9b --- /dev/null +++ b/controllers/ovsnetwork_controller_test.go @@ -0,0 +1,189 @@ +package controllers + +import ( + "context" + "sync" + "time" + + 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/types" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + v1 "github.com/k8snetworkplumbingwg/sriov-network-operator/api/v1" + "github.com/k8snetworkplumbingwg/sriov-network-operator/test/util" +) + +func getOvsNetworkCR() *v1.OVSNetwork { + return &v1.OVSNetwork{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: testNamespace, + }, + Spec: v1.OVSNetworkSpec{ + ResourceName: "test", + }, + } +} + +func removeOVSNetwork(ctx context.Context, cr *v1.OVSNetwork) { + err := k8sClient.Delete(ctx, cr) + if err != nil { + ExpectWithOffset(1, errors.IsNotFound(err)).To(BeTrue()) + } + EventuallyWithOffset(1, func(g Gomega) { + g.Expect(errors.IsNotFound( + k8sClient.Get(ctx, types.NamespacedName{ + Namespace: cr.Namespace, + Name: cr.Name}, &v1.OVSNetwork{}))).To(BeTrue()) + }, util.APITimeout, util.RetryInterval).Should(Succeed()) +} + +var _ = Describe("OVSNetwork Controller", Ordered, func() { + var cancel context.CancelFunc + var ctx context.Context + + BeforeAll(func() { + By("Setup controller manager") + k8sManager, err := setupK8sManagerForTest() + Expect(err).NotTo(HaveOccurred()) + + err = (&OVSNetworkReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + }).SetupWithManager(k8sManager) + Expect(err).NotTo(HaveOccurred()) + + ctx, cancel = context.WithCancel(context.Background()) + + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + defer wg.Done() + defer GinkgoRecover() + By("Start controller manager") + err := k8sManager.Start(ctx) + Expect(err).NotTo(HaveOccurred()) + }() + + DeferCleanup(func() { + By("Shutdown controller manager") + cancel() + wg.Wait() + }) + }) + + Context("OVSNetwork", func() { + It("create/delete net-att-def", func() { + netCR := getOvsNetworkCR() + + By("Create OVSNetwork CR") + Expect(k8sClient.Create(ctx, netCR)).NotTo(HaveOccurred()) + DeferCleanup(func() { removeOVSNetwork(ctx, netCR) }) + + By("Check NetworkAttachmentDefinition is created") + Eventually(func(g Gomega) { + netAttDef := &netattdefv1.NetworkAttachmentDefinition{} + g.Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "test", Namespace: testNamespace}, netAttDef)).NotTo(HaveOccurred()) + g.Expect(netAttDef.GetAnnotations()["k8s.v1.cni.cncf.io/resourceName"]).To(ContainSubstring("test")) + }, util.APITimeout, util.RetryInterval).Should(Succeed()) + + By("Remove OVSNetwork CR") + Expect(k8sClient.Delete(ctx, netCR)).NotTo(HaveOccurred()) + + By("Check NetworkAttachmentDefinition is removed") + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, types.NamespacedName{Name: "test", + Namespace: testNamespace}, &netattdefv1.NetworkAttachmentDefinition{}) + g.Expect(errors.IsNotFound(err)).To(BeTrue()) + }, util.APITimeout, util.RetryInterval).Should(Succeed()) + }) + It("update net-att-def", func() { + netCR := getOvsNetworkCR() + + By("Create OVSNetwork CR") + Expect(k8sClient.Create(ctx, netCR)).NotTo(HaveOccurred()) + DeferCleanup(func() { removeOVSNetwork(ctx, netCR) }) + + By("Check NetworkAttachmentDefinition is created") + Eventually(func(g Gomega) { + netAttDef := &netattdefv1.NetworkAttachmentDefinition{} + g.Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "test", + Namespace: testNamespace}, netAttDef)).NotTo(HaveOccurred()) + }, util.APITimeout, util.RetryInterval).Should(Succeed()) + + By("Update OVSNetwork CR") + Expect(k8sClient.Get(ctx, types.NamespacedName{Name: netCR.Name, + Namespace: netCR.Namespace}, netCR)).NotTo(HaveOccurred()) + netCR.Spec.Vlan = 200 + Expect(k8sClient.Update(ctx, netCR)).NotTo(HaveOccurred()) + + By("Check NetworkAttachmentDefinition is updated") + Eventually(func(g Gomega) { + netAttDef := &netattdefv1.NetworkAttachmentDefinition{} + g.Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "test", + Namespace: testNamespace}, netAttDef)).NotTo(HaveOccurred()) + g.Expect(netAttDef.Spec.Config).To(ContainSubstring(`"vlan": 200`)) + }, util.APITimeout, util.RetryInterval).Should(Succeed()) + }) + It("re-create net-att-def", func() { + netCR := getOvsNetworkCR() + + By("Create OVSNetwork CR") + Expect(k8sClient.Create(ctx, netCR)).NotTo(HaveOccurred()) + DeferCleanup(func() { removeOVSNetwork(ctx, netCR) }) + + var origUID types.UID + netAttDef := &netattdefv1.NetworkAttachmentDefinition{} + By("Check NetworkAttachmentDefinition is created") + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "test", + Namespace: testNamespace}, netAttDef)).NotTo(HaveOccurred()) + origUID = netAttDef.GetUID() + }, util.APITimeout, util.RetryInterval).Should(Succeed()) + + By("Remove NetworkAttachmentDefinition CR") + Expect(k8sClient.Delete(ctx, netAttDef)).NotTo(HaveOccurred()) + + By("Check NetworkAttachmentDefinition is recreated") + Eventually(func(g Gomega) { + netAttDef := &netattdefv1.NetworkAttachmentDefinition{} + g.Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "test", + Namespace: testNamespace}, netAttDef)).NotTo(HaveOccurred()) + g.Expect(netAttDef.GetUID()).NotTo(Equal(origUID)) + }, util.APITimeout, util.RetryInterval).Should(Succeed()) + }) + It("namespace is not yet created", func() { + newNSName := "test-ns" + netCR := getOvsNetworkCR() + netCR.Spec.NetworkNamespace = newNSName + + By("Create OVSNetwork CR") + Expect(k8sClient.Create(ctx, netCR)).NotTo(HaveOccurred()) + DeferCleanup(func() { removeOVSNetwork(ctx, netCR) }) + + // 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 SriovNetwork.Status field. + time.Sleep(3 * time.Second) + + By("Create Namespace") + nsObj := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: newNSName}, + } + Expect(k8sClient.Create(ctx, nsObj)).NotTo(HaveOccurred()) + DeferCleanup(func() { + Expect(k8sClient.Delete(ctx, nsObj)).NotTo(HaveOccurred()) + }) + + By("Check NetworkAttachmentDefinition is created") + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "test", Namespace: newNSName}, + &netattdefv1.NetworkAttachmentDefinition{})).NotTo(HaveOccurred()) + }, util.APITimeout, util.RetryInterval).Should(Succeed()) + }) + }) +}) diff --git a/controllers/suite_test.go b/controllers/suite_test.go index bc5870f34c..43caa67cb4 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -77,6 +77,10 @@ func setupK8sManagerForTest() (manager.Manager, error) { return []string{o.(*sriovnetworkv1.SriovIBNetwork).Spec.NetworkNamespace} }) + k8sManager.GetCache().IndexField(context.Background(), &sriovnetworkv1.OVSNetwork{}, "spec.networkNamespace", func(o client.Object) []string { + return []string{o.(*sriovnetworkv1.OVSNetwork).Spec.NetworkNamespace} + }) + return k8sManager, nil } diff --git a/main.go b/main.go index 86e7b313aa..820cf86124 100644 --- a/main.go +++ b/main.go @@ -141,6 +141,15 @@ func main() { os.Exit(1) } + err = mgrGlobal.GetCache().IndexField(context.Background(), &sriovnetworkv1.OVSNetwork{}, "spec.networkNamespace", func(o client.Object) []string { + return []string{o.(*sriovnetworkv1.OVSNetwork).Spec.NetworkNamespace} + }) + + if err != nil { + setupLog.Error(err, "unable to create index field for cache") + os.Exit(1) + } + if err := initNicIDMap(); err != nil { setupLog.Error(err, "unable to init NicIdMap") os.Exit(1) @@ -170,6 +179,13 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "SriovIBNetwork") os.Exit(1) } + if err = (&controllers.OVSNetworkReconciler{ + Client: mgrGlobal.GetClient(), + Scheme: mgrGlobal.GetScheme(), + }).SetupWithManager(mgrGlobal); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "OVSNetwork") + os.Exit(1) + } if err = (&controllers.SriovNetworkNodePolicyReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), @@ -193,13 +209,6 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "SriovNetworkPoolConfig") os.Exit(1) } - if err = (&controllers.OVSNetworkReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "OVSNetwork") - os.Exit(1) - } // we need a client that doesn't use the local cache for the objects drainKClient, err := client.New(restConfig, client.Options{