Skip to content

Commit c1d3077

Browse files
authored
Merge pull request #222 from yevgeny-shnaidman/yevgeny/finalization-flow
adding finalizer flow
2 parents 3dcfb2b + 68189b3 commit c1d3077

11 files changed

+382
-8
lines changed

internal/configmap/configmap.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@ package configmap
1818

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

2223
corev1 "k8s.io/api/core/v1"
24+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2325
"k8s.io/apimachinery/pkg/runtime"
26+
"sigs.k8s.io/controller-runtime/pkg/client"
2427

2528
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
2629
nfdv1 "sigs.k8s.io/node-feature-discovery-operator/api/v1"
@@ -30,14 +33,17 @@ import (
3033

3134
type ConfigMapAPI interface {
3235
SetWorkerConfigMapAsDesired(ctx context.Context, nfdInstance *nfdv1.NodeFeatureDiscovery, workerCM *corev1.ConfigMap) error
36+
DeleteConfigMap(ctx context.Context, namespace, name string) error
3337
}
3438

3539
type configMap struct {
40+
client client.Client
3641
scheme *runtime.Scheme
3742
}
3843

39-
func NewConfigMapAPI(scheme *runtime.Scheme) ConfigMapAPI {
44+
func NewConfigMapAPI(client client.Client, scheme *runtime.Scheme) ConfigMapAPI {
4045
return &configMap{
46+
client: client,
4147
scheme: scheme,
4248
}
4349
}
@@ -48,3 +54,17 @@ func (c *configMap) SetWorkerConfigMapAsDesired(ctx context.Context, nfdInstance
4854

4955
return controllerutil.SetControllerReference(nfdInstance, cm, c.scheme)
5056
}
57+
58+
func (c *configMap) DeleteConfigMap(ctx context.Context, namespace, name string) error {
59+
cm := corev1.ConfigMap{
60+
ObjectMeta: metav1.ObjectMeta{
61+
Namespace: namespace,
62+
Name: name,
63+
},
64+
}
65+
err := c.client.Delete(ctx, &cm)
66+
if err != nil && client.IgnoreNotFound(err) != nil {
67+
return fmt.Errorf("failed to delete configmap %s/%s: %w", namespace, name, err)
68+
}
69+
return nil
70+
}

internal/configmap/configmap_test.go

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,18 @@ package configmap
1818

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

2324
. "github.com/onsi/ginkgo/v2"
2425
. "github.com/onsi/gomega"
26+
"go.uber.org/mock/gomock"
2527
corev1 "k8s.io/api/core/v1"
28+
apierrors "k8s.io/apimachinery/pkg/api/errors"
2629
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30+
"k8s.io/apimachinery/pkg/runtime/schema"
2731
nfdv1 "sigs.k8s.io/node-feature-discovery-operator/api/v1"
32+
"sigs.k8s.io/node-feature-discovery-operator/internal/client"
2833
"sigs.k8s.io/yaml"
2934
)
3035

@@ -34,7 +39,7 @@ var _ = Describe("SetWorkerDaemonsetAsDesired", func() {
3439
)
3540

3641
BeforeEach(func() {
37-
configmapAPI = NewConfigMapAPI(scheme)
42+
configmapAPI = NewConfigMapAPI(nil, scheme)
3843
})
3944

4045
ctx := context.Background()
@@ -71,3 +76,48 @@ var _ = Describe("SetWorkerDaemonsetAsDesired", func() {
7176
Expect(&expectedWorkerCM).To(BeComparableTo(&actualWorkerCM))
7277
})
7378
})
79+
80+
var _ = Describe("DeleteConfigMap", func() {
81+
var (
82+
ctrl *gomock.Controller
83+
clnt *client.MockClient
84+
cmAPI ConfigMapAPI
85+
)
86+
87+
BeforeEach(func() {
88+
ctrl = gomock.NewController(GinkgoT())
89+
clnt = client.NewMockClient(ctrl)
90+
cmAPI = NewConfigMapAPI(clnt, scheme)
91+
})
92+
93+
ctx := context.Background()
94+
name := "cm-name"
95+
namespace := "cm-namespace"
96+
expectedCM := &corev1.ConfigMap{
97+
ObjectMeta: metav1.ObjectMeta{
98+
Namespace: namespace,
99+
Name: name,
100+
},
101+
}
102+
103+
It("failure to delete configmap from the cluster", func() {
104+
clnt.EXPECT().Delete(ctx, expectedCM).Return(fmt.Errorf("some error"))
105+
106+
err := cmAPI.DeleteConfigMap(ctx, namespace, name)
107+
Expect(err).To(HaveOccurred())
108+
})
109+
110+
It("configmap is not present in the cluster", func() {
111+
clnt.EXPECT().Delete(ctx, expectedCM).Return(apierrors.NewNotFound(schema.GroupResource{}, "whatever"))
112+
113+
err := cmAPI.DeleteConfigMap(ctx, namespace, name)
114+
Expect(err).To(BeNil())
115+
})
116+
117+
It("configmap deleted successfully", func() {
118+
clnt.EXPECT().Delete(ctx, expectedCM).Return(nil)
119+
120+
err := cmAPI.DeleteConfigMap(ctx, namespace, name)
121+
Expect(err).To(BeNil())
122+
})
123+
})

internal/configmap/mock_configmap.go

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/controllers/nodefeaturediscovery_reconciler.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,36 @@ func newNodeFeatureDiscoveryHelperAPI(client client.Client, deploymentAPI deploy
160160
}
161161

162162
func (nfdh *nodeFeatureDiscoveryHelper) finalizeComponents(ctx context.Context, nfdInstance *nfdv1.NodeFeatureDiscovery) error {
163+
err := nfdh.daemonsetAPI.DeleteDaemonSet(ctx, nfdInstance.Namespace, "nfd-worker")
164+
if err != nil {
165+
return fmt.Errorf("failed to delete worker daemonset: %w", err)
166+
}
167+
168+
err = nfdh.configmapAPI.DeleteConfigMap(ctx, nfdInstance.Namespace, "nfd-worker")
169+
if err != nil {
170+
return fmt.Errorf("failed to delete worker config map: %w", err)
171+
}
172+
173+
if nfdInstance.Spec.TopologyUpdater {
174+
err = nfdh.daemonsetAPI.DeleteDaemonSet(ctx, nfdInstance.Namespace, "nfd-topology-updater")
175+
if err != nil {
176+
return fmt.Errorf("failed to delete topology-updater daemonset: %w", err)
177+
}
178+
}
179+
err = nfdh.deploymentAPI.DeleteDeployment(ctx, nfdInstance.Namespace, "nfd-master")
180+
if err != nil {
181+
return fmt.Errorf("failed to delete master deployment: %w", err)
182+
}
183+
184+
err = nfdh.deploymentAPI.DeleteDeployment(ctx, nfdInstance.Namespace, "nfd-gc")
185+
if err != nil {
186+
return fmt.Errorf("failed to delete GC deployment: %w", err)
187+
}
188+
189+
updated := controllerutil.RemoveFinalizer(nfdInstance, finalizerLabel)
190+
if updated {
191+
return nfdh.client.Update(ctx, nfdInstance)
192+
}
163193
return nil
164194
}
165195

internal/controllers/nodefeaturediscovery_reconciler_test.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3030
"k8s.io/apimachinery/pkg/runtime/schema"
3131
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
32+
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
3233
"sigs.k8s.io/controller-runtime/pkg/reconcile"
3334
nfdv1 "sigs.k8s.io/node-feature-discovery-operator/api/v1"
3435

@@ -546,3 +547,93 @@ var _ = Describe("setFinalizer", func() {
546547
Expect(err).To(BeNil())
547548
})
548549
})
550+
551+
var _ = Describe("finalizeComponents", func() {
552+
var (
553+
ctrl *gomock.Controller
554+
clnt *client.MockClient
555+
mockDeployment *deployment.MockDeploymentAPI
556+
mockDS *daemonset.MockDaemonsetAPI
557+
mockCM *configmap.MockConfigMapAPI
558+
nfdh nodeFeatureDiscoveryHelperAPI
559+
)
560+
561+
BeforeEach(func() {
562+
ctrl = gomock.NewController(GinkgoT())
563+
clnt = client.NewMockClient(ctrl)
564+
mockDeployment = deployment.NewMockDeploymentAPI(ctrl)
565+
mockDS = daemonset.NewMockDaemonsetAPI(ctrl)
566+
mockCM = configmap.NewMockConfigMapAPI(ctrl)
567+
568+
nfdh = newNodeFeatureDiscoveryHelperAPI(clnt, mockDeployment, mockDS, mockCM, scheme)
569+
})
570+
571+
ctx := context.Background()
572+
namespace := "test-namespace"
573+
nfdCR := nfdv1.NodeFeatureDiscovery{
574+
ObjectMeta: metav1.ObjectMeta{Namespace: namespace},
575+
Spec: nfdv1.NodeFeatureDiscoverySpec{
576+
TopologyUpdater: true,
577+
},
578+
}
579+
580+
DescribeTable("check finalization normal and error flows", func(deleteWorkerDSError,
581+
deleteWorkerCMError,
582+
deleteTopologyDSError,
583+
deleteMasterDeploymentError,
584+
deleteGCDeploymentError,
585+
updateError bool) {
586+
587+
controllerutil.AddFinalizer(&nfdCR, finalizerLabel)
588+
589+
if deleteWorkerDSError {
590+
mockDS.EXPECT().DeleteDaemonSet(ctx, namespace, "nfd-worker").Return(fmt.Errorf("some error"))
591+
goto executeTestFunction
592+
}
593+
mockDS.EXPECT().DeleteDaemonSet(ctx, namespace, "nfd-worker").Return(nil)
594+
if deleteWorkerCMError {
595+
mockCM.EXPECT().DeleteConfigMap(ctx, namespace, "nfd-worker").Return(fmt.Errorf("some error"))
596+
goto executeTestFunction
597+
}
598+
mockCM.EXPECT().DeleteConfigMap(ctx, namespace, "nfd-worker").Return(nil)
599+
if deleteTopologyDSError {
600+
mockDS.EXPECT().DeleteDaemonSet(ctx, namespace, "nfd-topology-updater").Return(fmt.Errorf("some error"))
601+
goto executeTestFunction
602+
}
603+
mockDS.EXPECT().DeleteDaemonSet(ctx, namespace, "nfd-topology-updater").Return(nil)
604+
if deleteMasterDeploymentError {
605+
mockDeployment.EXPECT().DeleteDeployment(ctx, namespace, "nfd-master").Return(fmt.Errorf("some error"))
606+
goto executeTestFunction
607+
}
608+
mockDeployment.EXPECT().DeleteDeployment(ctx, namespace, "nfd-master").Return(nil)
609+
if deleteGCDeploymentError {
610+
mockDeployment.EXPECT().DeleteDeployment(ctx, namespace, "nfd-gc").Return(fmt.Errorf("some error"))
611+
goto executeTestFunction
612+
}
613+
mockDeployment.EXPECT().DeleteDeployment(ctx, namespace, "nfd-gc").Return(nil)
614+
if updateError {
615+
clnt.EXPECT().Update(ctx, gomock.Any()).Return(fmt.Errorf("some error"))
616+
goto executeTestFunction
617+
}
618+
clnt.EXPECT().Update(ctx, gomock.Any()).Return(nil)
619+
620+
executeTestFunction:
621+
622+
err := nfdh.finalizeComponents(ctx, &nfdCR)
623+
624+
if deleteGCDeploymentError || deleteWorkerDSError || deleteWorkerCMError ||
625+
deleteTopologyDSError || deleteMasterDeploymentError || updateError {
626+
Expect(err).To(HaveOccurred())
627+
} else {
628+
Expect(err).To(BeNil())
629+
}
630+
},
631+
Entry("delete worker daemonset failed", true, false, false, false, false, false),
632+
Entry("delete worker configmap failed", false, true, false, false, false, false),
633+
Entry("delete topology daemonset failed", false, false, true, false, false, false),
634+
Entry("delete master deployment failed", false, false, false, true, false, false),
635+
Entry("delete gc deployment failed", false, false, false, false, true, false),
636+
Entry("updating removed finalizer failed", false, false, false, false, false, true),
637+
Entry("finalization flow was succesful", false, false, false, false, false, false),
638+
)
639+
})

internal/daemonset/daemonset.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@ package daemonset
1818

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

2223
appsv1 "k8s.io/api/apps/v1"
2324
corev1 "k8s.io/api/core/v1"
2425
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2526
"k8s.io/apimachinery/pkg/runtime"
2627
"k8s.io/utils/ptr"
28+
"sigs.k8s.io/controller-runtime/pkg/client"
2729
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
2830

2931
nfdv1 "sigs.k8s.io/node-feature-discovery-operator/api/v1"
@@ -34,14 +36,17 @@ import (
3436
type DaemonsetAPI interface {
3537
SetTopologyDaemonsetAsDesired(ctx context.Context, nfdInstance *nfdv1.NodeFeatureDiscovery, topologyDS *appsv1.DaemonSet) error
3638
SetWorkerDaemonsetAsDesired(ctx context.Context, nfdInstance *nfdv1.NodeFeatureDiscovery, workerDS *appsv1.DaemonSet) error
39+
DeleteDaemonSet(ctx context.Context, namespace, name string) error
3740
}
3841

3942
type daemonset struct {
43+
client client.Client
4044
scheme *runtime.Scheme
4145
}
4246

43-
func NewDaemonsetAPI(scheme *runtime.Scheme) DaemonsetAPI {
47+
func NewDaemonsetAPI(client client.Client, scheme *runtime.Scheme) DaemonsetAPI {
4448
return &daemonset{
49+
client: client,
4550
scheme: scheme,
4651
}
4752
}
@@ -82,6 +87,20 @@ func (d *daemonset) SetTopologyDaemonsetAsDesired(ctx context.Context, nfdInstan
8287
return controllerutil.SetControllerReference(nfdInstance, topologyDS, d.scheme)
8388
}
8489

90+
func (d *daemonset) DeleteDaemonSet(ctx context.Context, namespace, name string) error {
91+
ds := appsv1.DaemonSet{
92+
ObjectMeta: metav1.ObjectMeta{
93+
Namespace: namespace,
94+
Name: name,
95+
},
96+
}
97+
err := d.client.Delete(ctx, &ds)
98+
if err != nil && client.IgnoreNotFound(err) != nil {
99+
return fmt.Errorf("failed to delete daemonset %s/%s: %w", namespace, name, err)
100+
}
101+
return nil
102+
}
103+
85104
func getImagePullPolicy(nfdInstance *nfdv1.NodeFeatureDiscovery) corev1.PullPolicy {
86105
if nfdInstance.Spec.Operand.ImagePullPolicy != "" {
87106
return corev1.PullPolicy(nfdInstance.Spec.Operand.ImagePullPolicy)

0 commit comments

Comments
 (0)