/
finalizer_controller.go
109 lines (97 loc) · 3.78 KB
/
finalizer_controller.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
package nsfinalizercontroller
import (
"context"
"fmt"
"github.com/openshift/library-go/pkg/controller/factory"
"reflect"
"time"
"github.com/openshift/library-go/pkg/operator/events"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
kubeinformers "k8s.io/client-go/informers"
v1 "k8s.io/client-go/kubernetes/typed/core/v1"
appsv1lister "k8s.io/client-go/listers/apps/v1"
corev1listers "k8s.io/client-go/listers/core/v1"
)
type finalizerController struct {
name string
namespaceName string
namespaceGetter v1.NamespacesGetter
podLister corev1listers.PodLister
deployLister appsv1lister.DeploymentLister
}
// NewFinalizerController is here because
// When running an aggregated API on the platform, you delete the namespace hosting the aggregated API. Doing that the
// namespace controller starts by doing complete discovery and then deleting all objects, but pods have a grace period,
// so it deletes the rest and requeues. The ns controller starts again and does a complete discovery and.... fails. The
// failure means it refuses to complete the cleanup. Now, we don't actually want to delete the resoruces from our
// aggregated API, only the server plus config if we remove the apiservices to unstick it, GC will start cleaning
// everything. For now, we can unbork 4.0, but clearing the finalizer after the pod and deployment we created are gone.
func NewFinalizerController(
namespaceName string,
kubeInformersForTargetNamespace kubeinformers.SharedInformerFactory,
namespaceGetter v1.NamespacesGetter,
eventRecorder events.Recorder,
) factory.Controller {
fullname := "NamespaceFinalizerController_" + namespaceName
c := &finalizerController{
name: fullname,
namespaceName: namespaceName,
namespaceGetter: namespaceGetter,
podLister: kubeInformersForTargetNamespace.Core().V1().Pods().Lister(),
deployLister: kubeInformersForTargetNamespace.Apps().V1().Deployments().Lister(),
}
return factory.New().WithInformers(
kubeInformersForTargetNamespace.Core().V1().Pods().Informer(),
kubeInformersForTargetNamespace.Apps().V1().Deployments().Informer(),
).ResyncEvery(time.Minute*5).WithSync(c.sync).ToController(fullname, eventRecorder.WithComponentSuffix("finalizer-controller"))
}
func (c finalizerController) sync(ctx context.Context, syncContext factory.SyncContext) error {
ns, err := c.namespaceGetter.Namespaces().Get(ctx, c.namespaceName, metav1.GetOptions{})
if apierrors.IsNotFound(err) {
return nil
}
if err != nil {
return err
}
if ns.DeletionTimestamp == nil {
return nil
}
// allow one minute of grace for most things to terminate.
// TODO now that we have conditions, we may be able to check specific conditions
deletedMoreThanAMinute := ns.DeletionTimestamp.Time.Add(1 * time.Minute).Before(time.Now())
if !deletedMoreThanAMinute {
syncContext.Queue().AddAfter(c.namespaceName, 1*time.Minute)
return nil
}
pods, err := c.podLister.Pods(c.namespaceName).List(labels.Everything())
if err != nil {
return err
}
if len(pods) > 0 {
return nil
}
deployments, err := c.deployLister.Deployments(c.namespaceName).List(labels.Everything())
if err != nil {
return err
}
if len(deployments) > 0 {
return nil
}
newFinalizers := []corev1.FinalizerName{}
for _, curr := range ns.Spec.Finalizers {
if curr == corev1.FinalizerKubernetes {
continue
}
newFinalizers = append(newFinalizers, curr)
}
if reflect.DeepEqual(newFinalizers, ns.Spec.Finalizers) {
return nil
}
ns.Spec.Finalizers = newFinalizers
syncContext.Recorder().Event("NamespaceFinalization", fmt.Sprintf("clearing namespace finalizer on %q", c.namespaceName))
_, err = c.namespaceGetter.Namespaces().Finalize(ctx, ns, metav1.UpdateOptions{})
return err
}