Skip to content

Commit 35e7319

Browse files
authored
Merge pull request #894 from zeeke/us/namespaced-networks
Support namespaced {Sriov,SriovIB,OVS}Networks
2 parents 0f2f913 + ada185c commit 35e7319

File tree

10 files changed

+661
-230
lines changed

10 files changed

+661
-230
lines changed

bindata/manifests/operator-webhook/003-webhook.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,15 @@ webhooks:
6969
apiGroups: [ "sriovnetwork.openshift.io" ]
7070
apiVersions: [ "v1" ]
7171
resources: [ "sriovnetworkpoolconfigs" ]
72+
- operations: [ "CREATE", "UPDATE", ]
73+
apiGroups: [ "sriovnetwork.openshift.io" ]
74+
apiVersions: [ "v1" ]
75+
resources: [ "sriovnetworks" ]
76+
- operations: [ "CREATE", "UPDATE", ]
77+
apiGroups: [ "sriovnetwork.openshift.io" ]
78+
apiVersions: [ "v1" ]
79+
resources: [ "sriovibnetworks" ]
80+
- operations: [ "CREATE", "UPDATE", ]
81+
apiGroups: [ "sriovnetwork.openshift.io" ]
82+
apiVersions: [ "v1" ]
83+
resources: [ "ovsnetworks" ]

cmd/webhook/main.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"os"
66

77
"github.com/spf13/cobra"
8-
"k8s.io/klog/v2"
98
"sigs.k8s.io/controller-runtime/pkg/log"
109

1110
snolog "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/log"
@@ -24,7 +23,6 @@ var (
2423
)
2524

2625
func init() {
27-
klog.InitFlags(nil)
2826
snolog.BindFlags(flag.CommandLine)
2927
rootCmd.PersistentFlags().AddGoFlagSet(flag.CommandLine)
3028
}

controllers/generic_network_controller.go

Lines changed: 102 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package controllers
1818

1919
import (
2020
"context"
21+
"fmt"
2122

2223
netattdefv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
2324
corev1 "k8s.io/api/core/v1"
@@ -30,6 +31,7 @@ import (
3031
"k8s.io/client-go/util/workqueue"
3132
ctrl "sigs.k8s.io/controller-runtime"
3233
"sigs.k8s.io/controller-runtime/pkg/client"
34+
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
3335
"sigs.k8s.io/controller-runtime/pkg/event"
3436
"sigs.k8s.io/controller-runtime/pkg/handler"
3537
"sigs.k8s.io/controller-runtime/pkg/log"
@@ -72,7 +74,6 @@ type genericNetworkReconciler struct {
7274
}
7375

7476
func (r *genericNetworkReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
75-
req.Namespace = vars.Namespace
7677
reqLogger := log.FromContext(ctx).WithValues(r.controller.Name(), req.NamespacedName)
7778

7879
reqLogger.Info("Reconciling " + r.controller.Name())
@@ -91,37 +92,37 @@ func (r *genericNetworkReconciler) Reconcile(ctx context.Context, req ctrl.Reque
9192
// Error reading the object - requeue the request.
9293
return reconcile.Result{}, err
9394
}
94-
instanceFinalizers := instance.GetFinalizers()
95+
96+
if instance == nil {
97+
// Request object not found, could have been deleted after reconcile request.
98+
// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
99+
// Return and don't requeue
100+
return reconcile.Result{}, nil
101+
}
102+
103+
if instance.NetworkNamespace() != "" && instance.GetNamespace() != vars.Namespace {
104+
reqLogger.Error(
105+
fmt.Errorf("bad value for NetworkNamespace"),
106+
".spec.networkNamespace can't be specified if the resource belongs to a namespace other than the operator's",
107+
"operatorNamespace", vars.Namespace,
108+
".metadata.namespace", instance.GetNamespace(),
109+
".spec.networkNamespace", instance.NetworkNamespace(),
110+
)
111+
return reconcile.Result{}, nil
112+
}
113+
95114
// examine DeletionTimestamp to determine if object is under deletion
96115
if instance.GetDeletionTimestamp().IsZero() {
97116
// The object is not being deleted, so if it does not have our finalizer,
98117
// then lets add the finalizer and update the object. This is equivalent
99118
// registering our finalizer.
100-
if !sriovnetworkv1.StringInArray(sriovnetworkv1.NETATTDEFFINALIZERNAME, instanceFinalizers) {
101-
instance.SetFinalizers(append(instanceFinalizers, sriovnetworkv1.NETATTDEFFINALIZERNAME))
102-
if err := r.Update(ctx, instance); err != nil {
103-
return reconcile.Result{}, err
104-
}
119+
err = r.updateFinalizers(ctx, instance)
120+
if err != nil {
121+
return reconcile.Result{}, err
105122
}
106123
} else {
107124
// The object is being deleted
108-
if sriovnetworkv1.StringInArray(sriovnetworkv1.NETATTDEFFINALIZERNAME, instanceFinalizers) {
109-
// our finalizer is present, so lets handle any external dependency
110-
reqLogger.Info("delete NetworkAttachmentDefinition CR", "Namespace", instance.NetworkNamespace(), "Name", instance.GetName())
111-
if err := r.deleteNetAttDef(ctx, instance); err != nil {
112-
// if fail to delete the external dependency here, return with error
113-
// so that it can be retried
114-
return reconcile.Result{}, err
115-
}
116-
// remove our finalizer from the list and update it.
117-
newFinalizers, found := sriovnetworkv1.RemoveString(sriovnetworkv1.NETATTDEFFINALIZERNAME, instanceFinalizers)
118-
if found {
119-
instance.SetFinalizers(newFinalizers)
120-
if err := r.Update(ctx, instance); err != nil {
121-
return reconcile.Result{}, err
122-
}
123-
}
124-
}
125+
err = r.cleanResourcesAndFinalizers(ctx, instance)
125126
return reconcile.Result{}, err
126127
}
127128
raw, err := instance.RenderNetAttDef()
@@ -151,6 +152,14 @@ func (r *genericNetworkReconciler) Reconcile(ctx context.Context, req ctrl.Reque
151152
return reconcile.Result{}, err
152153
}
153154
}
155+
156+
if instance.GetNamespace() == netAttDef.Namespace {
157+
// If the NetAttachDef is in the same namespace of the resource, then we can leverage the OwnerReference field for garbage collector
158+
if err := controllerutil.SetOwnerReference(instance, netAttDef, r.Scheme); err != nil {
159+
return reconcile.Result{}, err
160+
}
161+
}
162+
154163
// Check if this NetworkAttachmentDefinition already exists
155164
found := &netattdefv1.NetworkAttachmentDefinition{}
156165
err = r.Get(ctx, types.NamespacedName{Name: netAttDef.Name, Namespace: netAttDef.Namespace}, found)
@@ -183,6 +192,7 @@ func (r *genericNetworkReconciler) Reconcile(ctx context.Context, req ctrl.Reque
183192
if !equality.Semantic.DeepEqual(found.Spec, netAttDef.Spec) || !equality.Semantic.DeepEqual(found.GetAnnotations(), netAttDef.GetAnnotations()) {
184193
reqLogger.Info("Update NetworkAttachmentDefinition CR", "Namespace", netAttDef.Namespace, "Name", netAttDef.Name)
185194
netAttDef.SetResourceVersion(found.GetResourceVersion())
195+
186196
err = r.Update(ctx, netAttDef)
187197
if err != nil {
188198
reqLogger.Error(err, "Couldn't update NetworkAttachmentDefinition CR", "Namespace", netAttDef.Namespace, "Name", netAttDef.Name)
@@ -202,11 +212,37 @@ func (r *genericNetworkReconciler) SetupWithManager(mgr ctrl.Manager) error {
202212
}
203213
return ctrl.NewControllerManagedBy(mgr).
204214
For(r.controller.GetObject()).
205-
Watches(&netattdefv1.NetworkAttachmentDefinition{}, &handler.EnqueueRequestForObject{}).
215+
Watches(&netattdefv1.NetworkAttachmentDefinition{}, handler.EnqueueRequestsFromMapFunc(r.handleNetAttDef)).
206216
Watches(&corev1.Namespace{}, &namespaceHandler).
207217
Complete(r.controller)
208218
}
209219

220+
func (r *genericNetworkReconciler) handleNetAttDef(ctx context.Context, obj client.Object) []reconcile.Request {
221+
ret := []reconcile.Request{}
222+
instance := r.controller.GetObject()
223+
nadNamespacedName := types.NamespacedName{Namespace: obj.GetNamespace(), Name: obj.GetName()}
224+
225+
err := r.Get(ctx, nadNamespacedName, instance)
226+
if err == nil {
227+
// Found a NetworkObject in the same namespace as the NetworkAttachmentDefinition, reconcile it
228+
ret = append(ret, reconcile.Request{NamespacedName: nadNamespacedName})
229+
} else if !errors.IsNotFound(err) {
230+
log.Log.WithName(r.controller.Name()+" handleNetAttDef").Error(err, "can't get object", "object", nadNamespacedName)
231+
}
232+
233+
// Not found, try to find the NetworkObject in the operator's namespace
234+
operatorNamespacedName := types.NamespacedName{Namespace: vars.Namespace, Name: obj.GetName()}
235+
err = r.Get(ctx, operatorNamespacedName, instance)
236+
if err == nil {
237+
// Found a NetworkObject in the operator's namespace, reconcile it
238+
ret = append(ret, reconcile.Request{NamespacedName: operatorNamespacedName})
239+
} else if !errors.IsNotFound(err) {
240+
log.Log.WithName(r.controller.Name()+" handleNetAttDef").Error(err, "can't get object", "object", operatorNamespacedName)
241+
}
242+
243+
return ret
244+
}
245+
210246
func (r *genericNetworkReconciler) namespaceHandlerCreate(ctx context.Context, e event.TypedCreateEvent[client.Object], w workqueue.TypedRateLimitingInterface[reconcile.Request]) {
211247
networkList := r.controller.GetObjectList()
212248
err := r.List(ctx,
@@ -252,3 +288,44 @@ func (r *genericNetworkReconciler) deleteNetAttDef(ctx context.Context, cr Netwo
252288
}
253289
return nil
254290
}
291+
292+
func (r *genericNetworkReconciler) updateFinalizers(ctx context.Context, instance NetworkCRInstance) error {
293+
if instance.GetNamespace() != vars.Namespace {
294+
// If the resource is in a namespace different than the operator one, then the NetworkAttachmentDefinition will
295+
// be created in the same namespace and its deletion can be handled by OwnerReferences. There is no need for finalizers
296+
return nil
297+
}
298+
299+
instanceFinalizers := instance.GetFinalizers()
300+
if !sriovnetworkv1.StringInArray(sriovnetworkv1.NETATTDEFFINALIZERNAME, instanceFinalizers) {
301+
instance.SetFinalizers(append(instanceFinalizers, sriovnetworkv1.NETATTDEFFINALIZERNAME))
302+
if err := r.Update(ctx, instance); err != nil {
303+
return err
304+
}
305+
}
306+
307+
return nil
308+
}
309+
310+
func (r *genericNetworkReconciler) cleanResourcesAndFinalizers(ctx context.Context, instance NetworkCRInstance) error {
311+
instanceFinalizers := instance.GetFinalizers()
312+
313+
if sriovnetworkv1.StringInArray(sriovnetworkv1.NETATTDEFFINALIZERNAME, instanceFinalizers) {
314+
// our finalizer is present, so lets handle any external dependency
315+
log.FromContext(ctx).Info("delete NetworkAttachmentDefinition CR", "Namespace", instance.NetworkNamespace(), "Name", instance.GetName())
316+
if err := r.deleteNetAttDef(ctx, instance); err != nil {
317+
// if fail to delete the external dependency here, return with error
318+
// so that it can be retried
319+
return err
320+
}
321+
// remove our finalizer from the list and update it.
322+
newFinalizers, found := sriovnetworkv1.RemoveString(sriovnetworkv1.NETATTDEFFINALIZERNAME, instanceFinalizers)
323+
if found {
324+
instance.SetFinalizers(newFinalizers)
325+
if err := r.Update(ctx, instance); err != nil {
326+
return err
327+
}
328+
}
329+
}
330+
return nil
331+
}

controllers/sriovnetwork_controller_test.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ import (
1212
corev1 "k8s.io/api/core/v1"
1313
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1414

15+
"k8s.io/apimachinery/pkg/api/errors"
1516
"k8s.io/apimachinery/pkg/types"
1617
"k8s.io/client-go/util/retry"
18+
"sigs.k8s.io/controller-runtime/pkg/client"
1719
dynclient "sigs.k8s.io/controller-runtime/pkg/client"
1820

1921
. "github.com/onsi/ginkgo/v2"
@@ -342,6 +344,89 @@ var _ = Describe("SriovNetwork Controller", Ordered, func() {
342344
MustPassRepeatedly(10).
343345
Should(Succeed())
344346
})
347+
348+
Context("When the SriovNetwork namespace is not equal to the operator one", func() {
349+
BeforeAll(func() {
350+
nsBlue := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "ns-blue"}}
351+
Expect(k8sClient.Create(context.Background(), nsBlue)).ToNot(HaveOccurred())
352+
})
353+
354+
AfterEach(func() {
355+
cleanNetworksInNamespace("ns-blue")
356+
})
357+
358+
It("should create the NetAttachDefinition in the same namespace", func() {
359+
cr := sriovnetworkv1.SriovNetwork{
360+
ObjectMeta: metav1.ObjectMeta{
361+
Name: "sriovnet-blue",
362+
Namespace: "ns-blue",
363+
},
364+
Spec: sriovnetworkv1.SriovNetworkSpec{
365+
ResourceName: "resource_x",
366+
},
367+
}
368+
369+
err := k8sClient.Create(ctx, &cr)
370+
Expect(err).NotTo(HaveOccurred())
371+
372+
netAttDef := &netattdefv1.NetworkAttachmentDefinition{}
373+
err = util.WaitForNamespacedObject(netAttDef, k8sClient, "ns-blue", cr.GetName(), util.RetryInterval, util.Timeout)
374+
Expect(err).NotTo(HaveOccurred())
375+
expectedOwnerReference := metav1.OwnerReference{
376+
Kind: "SriovNetwork",
377+
APIVersion: sriovnetworkv1.GroupVersion.String(),
378+
UID: cr.UID,
379+
Name: cr.Name,
380+
}
381+
Expect(netAttDef.GetAnnotations()["k8s.v1.cni.cncf.io/resourceName"]).To(Equal("openshift.io/resource_x"))
382+
383+
Expect(netAttDef.ObjectMeta.OwnerReferences).To(ContainElement(expectedOwnerReference))
384+
385+
// Patch the SriovNetwork
386+
original := cr.DeepCopy()
387+
cr.Spec.ResourceName = "resource_y"
388+
err = k8sClient.Patch(ctx, &cr, dynclient.MergeFrom(original))
389+
Expect(err).NotTo(HaveOccurred())
390+
391+
// Check that the OwnerReference persists
392+
netAttDef = &netattdefv1.NetworkAttachmentDefinition{}
393+
394+
Eventually(func(g Gomega) {
395+
netAttDef = &netattdefv1.NetworkAttachmentDefinition{}
396+
g.Expect(k8sClient.Get(ctx, types.NamespacedName{Name: cr.GetName(), Namespace: "ns-blue"}, netAttDef)).To(Succeed())
397+
g.Expect(netAttDef.GetAnnotations()["k8s.v1.cni.cncf.io/resourceName"]).To(Equal("openshift.io/resource_y"))
398+
g.Expect(netAttDef.ObjectMeta.OwnerReferences).To(ContainElement(expectedOwnerReference))
399+
}).WithPolling(100 * time.Millisecond).WithTimeout(5 * time.Second).Should(Succeed())
400+
401+
// Delete the SriovNetwork
402+
err = k8sClient.Delete(ctx, &cr)
403+
Expect(err).NotTo(HaveOccurred())
404+
})
405+
406+
It("should not create the NetAttachDefinition if the NetworkNamespace field is not empty", func() {
407+
cr := sriovnetworkv1.SriovNetwork{
408+
ObjectMeta: metav1.ObjectMeta{
409+
Name: "sriovnet-blue",
410+
Namespace: "ns-blue",
411+
},
412+
Spec: sriovnetworkv1.SriovNetworkSpec{
413+
NetworkNamespace: "default",
414+
ResourceName: "resource_x",
415+
},
416+
}
417+
418+
err := k8sClient.Create(ctx, &cr)
419+
Expect(err).NotTo(HaveOccurred())
420+
421+
Consistently(func(g Gomega) {
422+
netAttDef := &netattdefv1.NetworkAttachmentDefinition{}
423+
err := k8sClient.Get(ctx, types.NamespacedName{Name: cr.GetName(), Namespace: "default"}, netAttDef)
424+
g.Expect(err).To(HaveOccurred())
425+
g.Expect(errors.IsNotFound(err)).To(BeTrue())
426+
}).WithPolling(100 * time.Millisecond).WithTimeout(1 * time.Second).Should(Succeed())
427+
})
428+
429+
})
345430
})
346431
})
347432

@@ -390,3 +475,24 @@ func generateExpectedNetConfig(cr *sriovnetworkv1.SriovNetwork) string {
390475
}
391476
return configStr
392477
}
478+
479+
func cleanNetworksInNamespace(namespace string) {
480+
ctx := context.Background()
481+
EventuallyWithOffset(1, func(g Gomega) {
482+
err := k8sClient.DeleteAllOf(ctx, &sriovnetworkv1.SriovNetwork{}, client.InNamespace(namespace))
483+
g.Expect(err).NotTo(HaveOccurred())
484+
485+
k8sClient.DeleteAllOf(ctx, &netattdefv1.NetworkAttachmentDefinition{}, client.InNamespace(namespace))
486+
g.Expect(err).NotTo(HaveOccurred())
487+
488+
sriovNetworks := &sriovnetworkv1.SriovNetworkList{}
489+
err = k8sClient.List(ctx, sriovNetworks, client.InNamespace(namespace))
490+
g.Expect(err).NotTo(HaveOccurred())
491+
g.Expect(sriovNetworks.Items).To(BeEmpty())
492+
493+
netAttachDefs := &netattdefv1.NetworkAttachmentDefinitionList{}
494+
err = k8sClient.List(ctx, netAttachDefs, client.InNamespace(namespace))
495+
g.Expect(err).NotTo(HaveOccurred())
496+
g.Expect(netAttachDefs.Items).To(BeEmpty())
497+
}).WithPolling(100 * time.Millisecond).WithTimeout(10 * time.Second).Should(Succeed())
498+
}

controllers/suite_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ var _ = BeforeSuite(func() {
9393

9494
logf.SetLogger(zap.New(
9595
zap.WriteTo(GinkgoWriter),
96+
zap.Level(zapcore.Level(-2)),
9697
zap.UseDevMode(true),
9798
func(o *zap.Options) {
9899
o.TimeEncoder = zapcore.RFC3339NanoTimeEncoder

0 commit comments

Comments
 (0)