Skip to content

Commit 1a0d77d

Browse files
committed
networks: Use owner annotation instead of finalizers
Using finalizers to remove dependant objects might lock a user if the operator is not running. Using the owner reference to garbage collect dangling NetworkAttachmentDefinition might clean the above use case. Signed-off-by: Andrea Panattoni <[email protected]>
1 parent 565f6f7 commit 1a0d77d

File tree

3 files changed

+107
-62
lines changed

3 files changed

+107
-62
lines changed

api/v1/helper.go

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
uns "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2020
intstrutil "k8s.io/apimachinery/pkg/util/intstr"
2121
"k8s.io/client-go/kubernetes"
22+
"sigs.k8s.io/controller-runtime/pkg/client"
2223
logf "sigs.k8s.io/controller-runtime/pkg/log"
2324

2425
"github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/consts"
@@ -28,7 +29,7 @@ import (
2829

2930
const (
3031
LASTNETWORKNAMESPACE = "operator.sriovnetwork.openshift.io/last-network-namespace"
31-
NETATTDEFFINALIZERNAME = "netattdef.finalizers.sriovnetwork.openshift.io"
32+
NETATTDEFFINALIZERNAME = "netattdef.finalizers.sriovnetwork.openshift.io" // Deprecated. Keeping this to migrate old cluster through upgrades
3233
POOLCONFIGFINALIZERNAME = "poolconfig.finalizers.sriovnetwork.openshift.io"
3334
OPERATORCONFIGFINALIZERNAME = "operatorconfig.finalizers.sriovnetwork.openshift.io"
3435
ESwithModeLegacy = "legacy"
@@ -651,7 +652,7 @@ func (cr *SriovIBNetwork) RenderNetAttDef() (*uns.Unstructured, error) {
651652
} else {
652653
data.Data["SriovNetworkNamespace"] = cr.Spec.NetworkNamespace
653654
}
654-
data.Data["Owner"] = cr.GroupVersionKind().GroupKind().String() + "/" + cr.GetNamespace() + "/" + cr.Name
655+
data.Data["Owner"] = OwnerRefToString(cr)
655656
data.Data["SriovCniResourceName"] = os.Getenv("RESOURCE_PREFIX") + "/" + cr.Spec.ResourceName
656657

657658
data.Data["StateConfigured"] = true
@@ -720,7 +721,7 @@ func (cr *SriovNetwork) RenderNetAttDef() (*uns.Unstructured, error) {
720721
} else {
721722
data.Data["SriovNetworkNamespace"] = cr.Spec.NetworkNamespace
722723
}
723-
data.Data["Owner"] = cr.GroupVersionKind().GroupKind().String() + "/" + cr.GetNamespace() + "/" + cr.Name
724+
data.Data["Owner"] = OwnerRefToString(cr)
724725
data.Data["SriovCniResourceName"] = os.Getenv("RESOURCE_PREFIX") + "/" + cr.Spec.ResourceName
725726
data.Data["SriovCniVlan"] = cr.Spec.Vlan
726727

@@ -839,7 +840,7 @@ func (cr *OVSNetwork) RenderNetAttDef() (*uns.Unstructured, error) {
839840
} else {
840841
data.Data["NetworkNamespace"] = cr.Spec.NetworkNamespace
841842
}
842-
data.Data["Owner"] = cr.GroupVersionKind().GroupKind().String() + "/" + cr.GetNamespace() + "/" + cr.Name
843+
data.Data["Owner"] = OwnerRefToString(cr)
843844
data.Data["CniResourceName"] = os.Getenv("RESOURCE_PREFIX") + "/" + cr.Spec.ResourceName
844845

845846
if cr.Spec.Capabilities == "" {
@@ -997,3 +998,27 @@ func (s *SriovNetworkNodeState) ResetKeepUntilTime() bool {
997998
s.SetAnnotations(annotations)
998999
return true
9991000
}
1001+
1002+
func OwnerRefToString(cr client.Object) string {
1003+
return cr.GetObjectKind().GroupVersionKind().GroupKind().String() + "/" + cr.GetNamespace() + "/" + cr.GetName()
1004+
}
1005+
1006+
func StringToOwnerRef(ownerRef string) (client.Object, string, string, error) {
1007+
chunks := strings.Split(ownerRef, "/")
1008+
if len(chunks) != 3 {
1009+
return nil, "", "", fmt.Errorf("invalid ownerRef %s", ownerRef)
1010+
}
1011+
groupKind, namespace, name := chunks[0], chunks[1], chunks[2]
1012+
switch groupKind {
1013+
case "SriovNetwork.sriovnetwork.openshift.io":
1014+
return &SriovNetwork{}, namespace, name, nil
1015+
1016+
case "SriovIBNetwork.sriovnetwork.openshift.io":
1017+
return &SriovIBNetwork{}, namespace, name, nil
1018+
1019+
case "OVSNetwork.sriovnetwork.openshift.io":
1020+
return &OVSNetwork{}, namespace, name, nil
1021+
}
1022+
1023+
return nil, "", "", fmt.Errorf("unknown ownerRef groupKind %s", groupKind)
1024+
}

controllers/generic_network_controller.go

Lines changed: 54 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,12 @@ func (r *genericNetworkReconciler) Reconcile(ctx context.Context, req ctrl.Reque
9090
// Request object not found, could have been deleted after reconcile request.
9191
// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
9292
// Return and don't requeue
93-
return reconcile.Result{}, nil
93+
return reconcile.Result{}, r.garbageCollect(ctx)
94+
}
95+
96+
err = r.cleanOldFinalizers(ctx, instance)
97+
if err != nil {
98+
return reconcile.Result{}, err
9499
}
95100

96101
if instance.NetworkNamespace() != "" && instance.GetNamespace() != vars.Namespace {
@@ -104,20 +109,6 @@ func (r *genericNetworkReconciler) Reconcile(ctx context.Context, req ctrl.Reque
104109
return reconcile.Result{}, nil
105110
}
106111

107-
// examine DeletionTimestamp to determine if object is under deletion
108-
if instance.GetDeletionTimestamp().IsZero() {
109-
// The object is not being deleted, so if it does not have our finalizer,
110-
// then lets add the finalizer and update the object. This is equivalent
111-
// registering our finalizer.
112-
err = r.updateFinalizers(ctx, instance)
113-
if err != nil {
114-
return reconcile.Result{}, err
115-
}
116-
} else {
117-
// The object is being deleted
118-
err = r.cleanResourcesAndFinalizers(ctx, instance)
119-
return reconcile.Result{}, err
120-
}
121112
raw, err := instance.RenderNetAttDef()
122113
if err != nil {
123114
return reconcile.Result{}, err
@@ -250,24 +241,6 @@ func (r *genericNetworkReconciler) namespaceHandlerCreate(ctx context.Context, e
250241
})
251242
}
252243

253-
// deleteNetAttDef deletes the generated net-att-def CR
254-
func (r *genericNetworkReconciler) deleteNetAttDef(ctx context.Context, cr NetworkCRInstance) error {
255-
// Fetch the NetworkAttachmentDefinition instance
256-
namespace := cr.NetworkNamespace()
257-
if namespace == "" {
258-
namespace = cr.GetNamespace()
259-
}
260-
instance := &netattdefv1.NetworkAttachmentDefinition{ObjectMeta: metav1.ObjectMeta{Name: cr.GetName(), Namespace: namespace}}
261-
err := r.Delete(ctx, instance)
262-
if err != nil {
263-
if errors.IsNotFound(err) {
264-
return nil
265-
}
266-
return err
267-
}
268-
return nil
269-
}
270-
271244
// fetchObject tries to fectch the request object in its own namespace. If it is not found, then tries
272245
// to fetch from the operator's namespace
273246
func (r *genericNetworkReconciler) fetchObject(ctx context.Context, req ctrl.Request) (NetworkCRInstance, error) {
@@ -296,43 +269,66 @@ func (r *genericNetworkReconciler) fetchObject(ctx context.Context, req ctrl.Req
296269
return nil, nil
297270
}
298271

299-
func (r *genericNetworkReconciler) updateFinalizers(ctx context.Context, instance NetworkCRInstance) error {
300-
if instance.GetNamespace() != vars.Namespace {
301-
// If the resource is in a namespace different than the operator one, then the NetworkAttachmentDefinition will
302-
// be created in the same namespace and its deletion can be handled by OwnerReferences. There is no need for finalizers
303-
return nil
272+
// garbageCollect searches all NetworkAttachmentDefinition objects that has a `sriovnetwork.openshift.io/owner`
273+
// annotation pointing to non existing objects
274+
func (r *genericNetworkReconciler) garbageCollect(ctx context.Context) error {
275+
logger := log.Log.WithName("garbageCollect")
276+
277+
netAttachDefs := &netattdefv1.NetworkAttachmentDefinitionList{}
278+
err := r.Client.List(ctx, netAttachDefs)
279+
if err != nil {
280+
return fmt.Errorf("garbageCollect: failed to list NetworkAttachmentDefinition: %w", err)
304281
}
305282

306-
instanceFinalizers := instance.GetFinalizers()
307-
if !sriovnetworkv1.StringInArray(sriovnetworkv1.NETATTDEFFINALIZERNAME, instanceFinalizers) {
308-
instance.SetFinalizers(append(instanceFinalizers, sriovnetworkv1.NETATTDEFFINALIZERNAME))
309-
if err := r.Update(ctx, instance); err != nil {
310-
return err
283+
for _, netAttDef := range netAttachDefs.Items {
284+
owner, ok := netAttDef.GetAnnotations()[consts.OwnerAnnotation]
285+
if !ok {
286+
continue
287+
}
288+
289+
obj, namespace, name, err := sriovnetworkv1.StringToOwnerRef(owner)
290+
if err != nil {
291+
logger.Error(err, "bad value for `sriovnetwork.openshift.io/owner`", "owner", owner)
292+
continue
293+
}
294+
295+
err = r.Get(ctx, types.NamespacedName{Name: name, Namespace: namespace}, obj)
296+
if err != nil {
297+
if !errors.IsNotFound(err) {
298+
logger.Error(err, "failed to get owner object", "owner", owner)
299+
continue
300+
}
301+
302+
logger.Info("owner object not found. deleting NetworkAttachmentDefinition", "obj", netAttDef)
303+
304+
err := r.Delete(ctx, &netAttDef)
305+
if err != nil {
306+
logger.Error(err, "can't delete NetworkAttachmentDefinition", "obj", netAttDef)
307+
}
311308
}
312309
}
313310

314311
return nil
315312
}
316313

317-
func (r *genericNetworkReconciler) cleanResourcesAndFinalizers(ctx context.Context, instance NetworkCRInstance) error {
314+
func (r *genericNetworkReconciler) cleanOldFinalizers(ctx context.Context, instance NetworkCRInstance) error {
318315
instanceFinalizers := instance.GetFinalizers()
319316

320-
if sriovnetworkv1.StringInArray(sriovnetworkv1.NETATTDEFFINALIZERNAME, instanceFinalizers) {
321-
// our finalizer is present, so lets handle any external dependency
322-
log.FromContext(ctx).Info("delete NetworkAttachmentDefinition CR", "Namespace", instance.NetworkNamespace(), "Name", instance.GetName())
323-
if err := r.deleteNetAttDef(ctx, instance); err != nil {
324-
// if fail to delete the external dependency here, return with error
325-
// so that it can be retried
317+
if !sriovnetworkv1.StringInArray(sriovnetworkv1.NETATTDEFFINALIZERNAME, instanceFinalizers) {
318+
return nil
319+
}
320+
321+
// remove our finalizer from the list and update it.
322+
newFinalizers, found := sriovnetworkv1.RemoveString(sriovnetworkv1.NETATTDEFFINALIZERNAME, instanceFinalizers)
323+
if found {
324+
logger := log.Log.WithName("cleanFinalizers")
325+
326+
instance.SetFinalizers(newFinalizers)
327+
logger.Info("Updating network instance to remove finalizer `netattdef.finalizers.sriovnetwork.openshift.io`", "obj", instance)
328+
if err := r.Update(ctx, instance); err != nil {
326329
return err
327330
}
328-
// remove our finalizer from the list and update it.
329-
newFinalizers, found := sriovnetworkv1.RemoveString(sriovnetworkv1.NETATTDEFFINALIZERNAME, instanceFinalizers)
330-
if found {
331-
instance.SetFinalizers(newFinalizers)
332-
if err := r.Update(ctx, instance); err != nil {
333-
return err
334-
}
335-
}
336331
}
332+
337333
return nil
338334
}

controllers/sriovnetwork_controller_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,30 @@ var _ = Describe("SriovNetwork Controller", Ordered, func() {
381381
Expect(netAttDef.ObjectMeta.OwnerReferences).To(ContainElement(expectedOwnerReference))
382382
})
383383
})
384+
385+
Context("Migration upgrades", func() {
386+
It("should remove finalizer in existing SriovNetworks", func() {
387+
cr := sriovnetworkv1.SriovNetwork{
388+
ObjectMeta: metav1.ObjectMeta{
389+
Name: "sriovnet-old",
390+
Namespace: testNamespace,
391+
Finalizers: []string{
392+
sriovnetworkv1.NETATTDEFFINALIZERNAME,
393+
},
394+
},
395+
}
396+
397+
err := k8sClient.Create(ctx, &cr)
398+
Expect(err).NotTo(HaveOccurred())
399+
400+
Eventually(func(g Gomega) {
401+
err := k8sClient.Get(ctx, types.NamespacedName{Namespace: cr.Namespace, Name: cr.Name}, &cr)
402+
g.Expect(err).ToNot(HaveOccurred())
403+
g.Expect(cr.Finalizers).To(BeEmpty())
404+
}).WithTimeout(time.Second).Should(Succeed())
405+
})
406+
})
407+
384408
})
385409
})
386410

0 commit comments

Comments
 (0)