From 35ccf7da7140a77c098e038f9a68c4aafee70a8c Mon Sep 17 00:00:00 2001 From: Yevgeny Shnaidman Date: Wed, 20 Mar 2024 16:07:00 +0200 Subject: [PATCH] Adding GC deployment implementation On every reconciliation loop, the controller updates/creates GarbageCollector deployment, based on the NFD CR spec --- internal/configmap/mock_configmap.go | 8 +- .../mock_nodefeaturediscovery_reconciler.go | 24 +++--- .../nodefeaturediscovery_reconciler.go | 13 +++- .../nodefeaturediscovery_reconciler_test.go | 73 ++++++++++++++++++- internal/daemonset/mock_daemonset.go | 10 ++- internal/deployment/deployment.go | 55 ++++++++++++-- internal/deployment/deployment_test.go | 49 +++++++++++-- internal/deployment/mock_deployment.go | 29 ++++++-- .../testdata/test_gc_deployment.yaml | 43 +++++++++++ 9 files changed, 269 insertions(+), 35 deletions(-) create mode 100644 internal/deployment/testdata/test_gc_deployment.yaml diff --git a/internal/configmap/mock_configmap.go b/internal/configmap/mock_configmap.go index 19803d34..60531723 100644 --- a/internal/configmap/mock_configmap.go +++ b/internal/configmap/mock_configmap.go @@ -1,6 +1,10 @@ // Code generated by MockGen. DO NOT EDIT. // Source: configmap.go - +// +// Generated by this command: +// +// mockgen -source=configmap.go -package=configmap -destination=mock_configmap.go ConfigMapAPI +// // Package configmap is a generated GoMock package. package configmap @@ -45,7 +49,7 @@ func (m *MockConfigMapAPI) SetWorkerConfigMapAsDesired(ctx context.Context, nfdI } // SetWorkerConfigMapAsDesired indicates an expected call of SetWorkerConfigMapAsDesired. -func (mr *MockConfigMapAPIMockRecorder) SetWorkerConfigMapAsDesired(ctx, nfdInstance, workerCM interface{}) *gomock.Call { +func (mr *MockConfigMapAPIMockRecorder) SetWorkerConfigMapAsDesired(ctx, nfdInstance, workerCM any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetWorkerConfigMapAsDesired", reflect.TypeOf((*MockConfigMapAPI)(nil).SetWorkerConfigMapAsDesired), ctx, nfdInstance, workerCM) } diff --git a/internal/controllers/mock_nodefeaturediscovery_reconciler.go b/internal/controllers/mock_nodefeaturediscovery_reconciler.go index 6c345b83..a0fdad2b 100644 --- a/internal/controllers/mock_nodefeaturediscovery_reconciler.go +++ b/internal/controllers/mock_nodefeaturediscovery_reconciler.go @@ -1,6 +1,10 @@ // Code generated by MockGen. DO NOT EDIT. // Source: nodefeaturediscovery_reconciler.go - +// +// Generated by this command: +// +// mockgen -source=nodefeaturediscovery_reconciler.go -package=new_controllers -destination=mock_nodefeaturediscovery_reconciler.go nodeFeatureDiscoveryHelperAPI +// // Package new_controllers is a generated GoMock package. package new_controllers @@ -44,7 +48,7 @@ func (m *MocknodeFeatureDiscoveryHelperAPI) finalizeComponents(ctx context.Conte } // finalizeComponents indicates an expected call of finalizeComponents. -func (mr *MocknodeFeatureDiscoveryHelperAPIMockRecorder) finalizeComponents(ctx, nfdInstance interface{}) *gomock.Call { +func (mr *MocknodeFeatureDiscoveryHelperAPIMockRecorder) finalizeComponents(ctx, nfdInstance any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "finalizeComponents", reflect.TypeOf((*MocknodeFeatureDiscoveryHelperAPI)(nil).finalizeComponents), ctx, nfdInstance) } @@ -58,7 +62,7 @@ func (m *MocknodeFeatureDiscoveryHelperAPI) handleGC(ctx context.Context, nfdIns } // handleGC indicates an expected call of handleGC. -func (mr *MocknodeFeatureDiscoveryHelperAPIMockRecorder) handleGC(ctx, nfdInstance interface{}) *gomock.Call { +func (mr *MocknodeFeatureDiscoveryHelperAPIMockRecorder) handleGC(ctx, nfdInstance any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "handleGC", reflect.TypeOf((*MocknodeFeatureDiscoveryHelperAPI)(nil).handleGC), ctx, nfdInstance) } @@ -72,7 +76,7 @@ func (m *MocknodeFeatureDiscoveryHelperAPI) handleMaster(ctx context.Context, nf } // handleMaster indicates an expected call of handleMaster. -func (mr *MocknodeFeatureDiscoveryHelperAPIMockRecorder) handleMaster(ctx, nfdInstance interface{}) *gomock.Call { +func (mr *MocknodeFeatureDiscoveryHelperAPIMockRecorder) handleMaster(ctx, nfdInstance any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "handleMaster", reflect.TypeOf((*MocknodeFeatureDiscoveryHelperAPI)(nil).handleMaster), ctx, nfdInstance) } @@ -86,7 +90,7 @@ func (m *MocknodeFeatureDiscoveryHelperAPI) handlePrune(ctx context.Context, nfd } // handlePrune indicates an expected call of handlePrune. -func (mr *MocknodeFeatureDiscoveryHelperAPIMockRecorder) handlePrune(ctx, nfdInstance interface{}) *gomock.Call { +func (mr *MocknodeFeatureDiscoveryHelperAPIMockRecorder) handlePrune(ctx, nfdInstance any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "handlePrune", reflect.TypeOf((*MocknodeFeatureDiscoveryHelperAPI)(nil).handlePrune), ctx, nfdInstance) } @@ -100,7 +104,7 @@ func (m *MocknodeFeatureDiscoveryHelperAPI) handleStatus(ctx context.Context, nf } // handleStatus indicates an expected call of handleStatus. -func (mr *MocknodeFeatureDiscoveryHelperAPIMockRecorder) handleStatus(ctx, nfdInstance interface{}) *gomock.Call { +func (mr *MocknodeFeatureDiscoveryHelperAPIMockRecorder) handleStatus(ctx, nfdInstance any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "handleStatus", reflect.TypeOf((*MocknodeFeatureDiscoveryHelperAPI)(nil).handleStatus), ctx, nfdInstance) } @@ -114,7 +118,7 @@ func (m *MocknodeFeatureDiscoveryHelperAPI) handleTopology(ctx context.Context, } // handleTopology indicates an expected call of handleTopology. -func (mr *MocknodeFeatureDiscoveryHelperAPIMockRecorder) handleTopology(ctx, nfdInstance interface{}) *gomock.Call { +func (mr *MocknodeFeatureDiscoveryHelperAPIMockRecorder) handleTopology(ctx, nfdInstance any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "handleTopology", reflect.TypeOf((*MocknodeFeatureDiscoveryHelperAPI)(nil).handleTopology), ctx, nfdInstance) } @@ -128,7 +132,7 @@ func (m *MocknodeFeatureDiscoveryHelperAPI) handleWorker(ctx context.Context, nf } // handleWorker indicates an expected call of handleWorker. -func (mr *MocknodeFeatureDiscoveryHelperAPIMockRecorder) handleWorker(ctx, nfdInstance interface{}) *gomock.Call { +func (mr *MocknodeFeatureDiscoveryHelperAPIMockRecorder) handleWorker(ctx, nfdInstance any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "handleWorker", reflect.TypeOf((*MocknodeFeatureDiscoveryHelperAPI)(nil).handleWorker), ctx, nfdInstance) } @@ -142,7 +146,7 @@ func (m *MocknodeFeatureDiscoveryHelperAPI) hasFinalizer(nfdInstance *v1.NodeFea } // hasFinalizer indicates an expected call of hasFinalizer. -func (mr *MocknodeFeatureDiscoveryHelperAPIMockRecorder) hasFinalizer(nfdInstance interface{}) *gomock.Call { +func (mr *MocknodeFeatureDiscoveryHelperAPIMockRecorder) hasFinalizer(nfdInstance any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "hasFinalizer", reflect.TypeOf((*MocknodeFeatureDiscoveryHelperAPI)(nil).hasFinalizer), nfdInstance) } @@ -156,7 +160,7 @@ func (m *MocknodeFeatureDiscoveryHelperAPI) setFinalizer(ctx context.Context, in } // setFinalizer indicates an expected call of setFinalizer. -func (mr *MocknodeFeatureDiscoveryHelperAPIMockRecorder) setFinalizer(ctx, instance interface{}) *gomock.Call { +func (mr *MocknodeFeatureDiscoveryHelperAPIMockRecorder) setFinalizer(ctx, instance any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "setFinalizer", reflect.TypeOf((*MocknodeFeatureDiscoveryHelperAPI)(nil).setFinalizer), ctx, instance) } diff --git a/internal/controllers/nodefeaturediscovery_reconciler.go b/internal/controllers/nodefeaturediscovery_reconciler.go index f95de5ef..d1d6bfef 100644 --- a/internal/controllers/nodefeaturediscovery_reconciler.go +++ b/internal/controllers/nodefeaturediscovery_reconciler.go @@ -174,7 +174,7 @@ func (nfdh *nodeFeatureDiscoveryHelper) handleMaster(ctx context.Context, nfdIns ObjectMeta: metav1.ObjectMeta{Name: "nfd-master", Namespace: nfdInstance.Namespace}, } opRes, err := controllerutil.CreateOrPatch(ctx, nfdh.client, &masterDep, func() error { - return nfdh.deploymentAPI.SetMasterDeploymentAsDesired(ctx, nfdInstance, &masterDep) + return nfdh.deploymentAPI.SetMasterDeploymentAsDesired(nfdInstance, &masterDep) }) if err != nil { @@ -232,6 +232,17 @@ func (nfdh *nodeFeatureDiscoveryHelper) handleTopology(ctx context.Context, nfdI } func (nfdh *nodeFeatureDiscoveryHelper) handleGC(ctx context.Context, nfdInstance *nfdv1.NodeFeatureDiscovery) error { + gcDep := appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "nfd-gc", Namespace: nfdInstance.Namespace}, + } + opRes, err := controllerutil.CreateOrPatch(ctx, nfdh.client, &gcDep, func() error { + return nfdh.deploymentAPI.SetGCDeploymentAsDesired(nfdInstance, &gcDep) + }) + + if err != nil { + return fmt.Errorf("failed to reconcile nfd-gc deployment %s/%s: %w", nfdInstance.Namespace, nfdInstance.Name, err) + } + ctrl.LoggerFrom(ctx).Info("reconciled nfd-gc deployment", "namespace", nfdInstance.Namespace, "name", nfdInstance.Name, "result", opRes) return nil } diff --git a/internal/controllers/nodefeaturediscovery_reconciler_test.go b/internal/controllers/nodefeaturediscovery_reconciler_test.go index 98c90e3d..c57d8271 100644 --- a/internal/controllers/nodefeaturediscovery_reconciler_test.go +++ b/internal/controllers/nodefeaturediscovery_reconciler_test.go @@ -162,7 +162,7 @@ var _ = Describe("handleMaster", func() { nfdCR := nfdv1.NodeFeatureDiscovery{} gomock.InOrder( clnt.EXPECT().Get(ctx, gomock.Any(), gomock.Any()).Return(apierrors.NewNotFound(schema.GroupResource{}, "whatever")), - mockDeployment.EXPECT().SetMasterDeploymentAsDesired(ctx, &nfdCR, gomock.Any()).Return(nil), + mockDeployment.EXPECT().SetMasterDeploymentAsDesired(&nfdCR, gomock.Any()).Return(nil), clnt.EXPECT().Create(ctx, gomock.Any()).Return(nil), ) @@ -188,7 +188,7 @@ var _ = Describe("handleMaster", func() { return nil }, ), - mockDeployment.EXPECT().SetMasterDeploymentAsDesired(ctx, &nfdCR, &existingDeployment).Return(nil), + mockDeployment.EXPECT().SetMasterDeploymentAsDesired(&nfdCR, &existingDeployment).Return(nil), ) err := nfdh.handleMaster(ctx, &nfdCR) @@ -199,7 +199,7 @@ var _ = Describe("handleMaster", func() { nfdCR := nfdv1.NodeFeatureDiscovery{} gomock.InOrder( clnt.EXPECT().Get(ctx, gomock.Any(), gomock.Any()).Return(apierrors.NewNotFound(schema.GroupResource{}, "whatever")), - mockDeployment.EXPECT().SetMasterDeploymentAsDesired(ctx, &nfdCR, gomock.Any()).Return(fmt.Errorf("some error")), + mockDeployment.EXPECT().SetMasterDeploymentAsDesired(&nfdCR, gomock.Any()).Return(fmt.Errorf("some error")), ) err := nfdh.handleMaster(ctx, &nfdCR) @@ -291,3 +291,70 @@ var _ = Describe("handleTopology", func() { Expect(err).To(BeNil()) }) }) + +var _ = Describe("handleGC", func() { + var ( + ctrl *gomock.Controller + clnt *client.MockClient + mockDeployment *deployment.MockDeploymentAPI + nfdh nodeFeatureDiscoveryHelperAPI + ) + + BeforeEach(func() { + ctrl = gomock.NewController(GinkgoT()) + clnt = client.NewMockClient(ctrl) + mockDeployment = deployment.NewMockDeploymentAPI(ctrl) + + nfdh = newNodeFeatureDiscoveryHelperAPI(clnt, mockDeployment, nil, nil, scheme) + }) + + ctx := context.Background() + + It("should create new nfd-gc deployment if it does not exist", func() { + nfdCR := nfdv1.NodeFeatureDiscovery{} + gomock.InOrder( + clnt.EXPECT().Get(ctx, gomock.Any(), gomock.Any()).Return(apierrors.NewNotFound(schema.GroupResource{}, "whatever")), + mockDeployment.EXPECT().SetGCDeploymentAsDesired(&nfdCR, gomock.Any()).Return(nil), + clnt.EXPECT().Create(ctx, gomock.Any()).Return(nil), + ) + + err := nfdh.handleGC(ctx, &nfdCR) + Expect(err).To(BeNil()) + }) + + It("nfd-gc deployment exists, no need to create it, update is not executed", func() { + nfdCR := nfdv1.NodeFeatureDiscovery{ + ObjectMeta: metav1.ObjectMeta{ + Name: "nfd-cr", + Namespace: "test-namespace", + }, + } + existingDeployment := appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Namespace: nfdCR.Namespace, Name: "nfd-gc"}, + } + gomock.InOrder( + clnt.EXPECT().Get(ctx, gomock.Any(), gomock.Any()).DoAndReturn( + func(_ interface{}, _ interface{}, dp *appsv1.Deployment, _ ...ctrlclient.GetOption) error { + dp.SetName(existingDeployment.Name) + dp.SetNamespace(existingDeployment.Namespace) + return nil + }, + ), + mockDeployment.EXPECT().SetGCDeploymentAsDesired(&nfdCR, &existingDeployment).Return(nil), + ) + + err := nfdh.handleGC(ctx, &nfdCR) + Expect(err).To(BeNil()) + }) + + It("error flow, failed to populate nfd-gc deployment object", func() { + nfdCR := nfdv1.NodeFeatureDiscovery{} + gomock.InOrder( + clnt.EXPECT().Get(ctx, gomock.Any(), gomock.Any()).Return(apierrors.NewNotFound(schema.GroupResource{}, "whatever")), + mockDeployment.EXPECT().SetGCDeploymentAsDesired(&nfdCR, gomock.Any()).Return(fmt.Errorf("some error")), + ) + + err := nfdh.handleGC(ctx, &nfdCR) + Expect(err).To(HaveOccurred()) + }) +}) diff --git a/internal/daemonset/mock_daemonset.go b/internal/daemonset/mock_daemonset.go index af55d7ef..e6196bf0 100644 --- a/internal/daemonset/mock_daemonset.go +++ b/internal/daemonset/mock_daemonset.go @@ -1,6 +1,10 @@ // Code generated by MockGen. DO NOT EDIT. // Source: daemonset.go - +// +// Generated by this command: +// +// mockgen -source=daemonset.go -package=daemonset -destination=mock_daemonset.go DaemonsetAPI +// // Package daemonset is a generated GoMock package. package daemonset @@ -45,7 +49,7 @@ func (m *MockDaemonsetAPI) SetTopologyDaemonsetAsDesired(ctx context.Context, nf } // SetTopologyDaemonsetAsDesired indicates an expected call of SetTopologyDaemonsetAsDesired. -func (mr *MockDaemonsetAPIMockRecorder) SetTopologyDaemonsetAsDesired(ctx, nfdInstance, topologyDS interface{}) *gomock.Call { +func (mr *MockDaemonsetAPIMockRecorder) SetTopologyDaemonsetAsDesired(ctx, nfdInstance, topologyDS any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTopologyDaemonsetAsDesired", reflect.TypeOf((*MockDaemonsetAPI)(nil).SetTopologyDaemonsetAsDesired), ctx, nfdInstance, topologyDS) } @@ -59,7 +63,7 @@ func (m *MockDaemonsetAPI) SetWorkerDaemonsetAsDesired(ctx context.Context, nfdI } // SetWorkerDaemonsetAsDesired indicates an expected call of SetWorkerDaemonsetAsDesired. -func (mr *MockDaemonsetAPIMockRecorder) SetWorkerDaemonsetAsDesired(ctx, nfdInstance, workerDS interface{}) *gomock.Call { +func (mr *MockDaemonsetAPIMockRecorder) SetWorkerDaemonsetAsDesired(ctx, nfdInstance, workerDS any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetWorkerDaemonsetAsDesired", reflect.TypeOf((*MockDaemonsetAPI)(nil).SetWorkerDaemonsetAsDesired), ctx, nfdInstance, workerDS) } diff --git a/internal/deployment/deployment.go b/internal/deployment/deployment.go index 7af12dd1..c4e5654a 100644 --- a/internal/deployment/deployment.go +++ b/internal/deployment/deployment.go @@ -17,7 +17,6 @@ limitations under the License. package deployment import ( - "context" "fmt" "strings" @@ -38,7 +37,8 @@ const ( //go:generate mockgen -source=deployment.go -package=deployment -destination=mock_deployment.go DeploymentAPI type DeploymentAPI interface { - SetMasterDeploymentAsDesired(ctx context.Context, nfdInstance *nfdv1.NodeFeatureDiscovery, masterDep *v1.Deployment) error + SetMasterDeploymentAsDesired(nfdInstance *nfdv1.NodeFeatureDiscovery, masterDep *v1.Deployment) error + SetGCDeploymentAsDesired(nfdInstance *nfdv1.NodeFeatureDiscovery, gcDep *v1.Deployment) error } type deployment struct { @@ -51,7 +51,7 @@ func NewDeploymentAPI(scheme *runtime.Scheme) DeploymentAPI { } } -func (d *deployment) SetMasterDeploymentAsDesired(ctx context.Context, nfdInstance *nfdv1.NodeFeatureDiscovery, masterDep *v1.Deployment) error { +func (d *deployment) SetMasterDeploymentAsDesired(nfdInstance *nfdv1.NodeFeatureDiscovery, masterDep *v1.Deployment) error { standartLabels := map[string]string{"app": "nfd-master"} masterDep.ObjectMeta.Labels = standartLabels @@ -80,7 +80,7 @@ func (d *deployment) SetMasterDeploymentAsDesired(ctx context.Context, nfdInstan }, Args: getArgs(nfdInstance), Env: getEnvs(), - SecurityContext: getSecurityContext(), + SecurityContext: getMasterSecurityContext(), LivenessProbe: getLivenessProbe(), ReadinessProbe: getReadinessProbe(), }, @@ -91,6 +91,40 @@ func (d *deployment) SetMasterDeploymentAsDesired(ctx context.Context, nfdInstan return controllerutil.SetControllerReference(nfdInstance, masterDep, d.scheme) } +func (d *deployment) SetGCDeploymentAsDesired(nfdInstance *nfdv1.NodeFeatureDiscovery, gcDep *v1.Deployment) error { + gcDep.ObjectMeta.Labels = map[string]string{"app": "nfd"} + matchLabels := map[string]string{"app": "nfd-gc"} + gcDep.Spec = v1.DeploymentSpec{ + Replicas: ptr.To[int32](1), + Selector: &metav1.LabelSelector{ + MatchLabels: matchLabels, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: matchLabels, + }, + Spec: corev1.PodSpec{ + ServiceAccountName: "nfd-gc", + DNSPolicy: corev1.DNSClusterFirstWithHostNet, + RestartPolicy: corev1.RestartPolicyAlways, + Containers: []corev1.Container{ + { + Name: "nfd-gc", + Image: nfdInstance.Spec.Operand.ImagePath(), + ImagePullPolicy: corev1.PullAlways, + Command: []string{ + "nfd-gc", + }, + Env: getEnvs(), + SecurityContext: getGCSecurityContext(), + }, + }, + }, + }, + } + return controllerutil.SetControllerReference(nfdInstance, gcDep, d.scheme) +} + func getPodsTolerations() []corev1.Toleration { return []corev1.Toleration{ { @@ -180,7 +214,7 @@ func getEnvs() []corev1.EnvVar { } } -func getSecurityContext() *corev1.SecurityContext { +func getMasterSecurityContext() *corev1.SecurityContext { return &corev1.SecurityContext{ RunAsNonRoot: ptr.To(true), SeccompProfile: &corev1.SeccompProfile{ @@ -194,6 +228,17 @@ func getSecurityContext() *corev1.SecurityContext { } } +func getGCSecurityContext() *corev1.SecurityContext { + return &corev1.SecurityContext{ + RunAsNonRoot: ptr.To(true), + ReadOnlyRootFilesystem: ptr.To(true), + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + AllowPrivilegeEscalation: ptr.To(false), + } +} + func getLivenessProbe() *corev1.Probe { return &corev1.Probe{ InitialDelaySeconds: 10, diff --git a/internal/deployment/deployment_test.go b/internal/deployment/deployment_test.go index 896a70ca..7a53aad9 100644 --- a/internal/deployment/deployment_test.go +++ b/internal/deployment/deployment_test.go @@ -17,7 +17,6 @@ limitations under the License. package deployment import ( - "context" "os" . "github.com/onsi/ginkgo/v2" @@ -37,9 +36,7 @@ var _ = Describe("SetMasterDeploymentAsDesired", func() { deploymentAPI = NewDeploymentAPI(scheme) }) - ctx := context.Background() - - It("good flow, deployment object populated with correct values", func() { + It("good flow, master deployment object populated with correct values", func() { nfdCR := nfdv1.NodeFeatureDiscovery{ Spec: nfdv1.NodeFeatureDiscoverySpec{ Operand: nfdv1.OperandSpec{ @@ -58,7 +55,7 @@ var _ = Describe("SetMasterDeploymentAsDesired", func() { }, } - err := deploymentAPI.SetMasterDeploymentAsDesired(ctx, &nfdCR, &masterDep) + err := deploymentAPI.SetMasterDeploymentAsDesired(&nfdCR, &masterDep) Expect(err).To(BeNil()) expectedYAMLFile, err := os.ReadFile("testdata/test_master_deployment.yaml") @@ -71,3 +68,45 @@ var _ = Describe("SetMasterDeploymentAsDesired", func() { Expect(masterDep).To(BeComparableTo(testMasterDep)) }) }) + +var _ = Describe("SetGCDeploymentAsDesired", func() { + var ( + deploymentAPI DeploymentAPI + ) + + BeforeEach(func() { + deploymentAPI = NewDeploymentAPI(scheme) + }) + + It("good flow, GC deployment object populated with correct values", func() { + nfdCR := nfdv1.NodeFeatureDiscovery{ + Spec: nfdv1.NodeFeatureDiscoverySpec{ + Operand: nfdv1.OperandSpec{ + Image: "test-image", + }, + }, + } + masterDep := appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "nfd-gc", + Namespace: "test-namespace", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "Deployment", + APIVersion: "apps/v1", + }, + } + + err := deploymentAPI.SetGCDeploymentAsDesired(&nfdCR, &masterDep) + + Expect(err).To(BeNil()) + expectedYAMLFile, err := os.ReadFile("testdata/test_gc_deployment.yaml") + Expect(err).To(BeNil()) + expectedJSON, err := yaml.YAMLToJSON(expectedYAMLFile) + Expect(err).To(BeNil()) + testMasterDep := appsv1.Deployment{} + err = yaml.Unmarshal(expectedJSON, &testMasterDep) + Expect(err).To(BeNil()) + Expect(masterDep).To(BeComparableTo(testMasterDep)) + }) +}) diff --git a/internal/deployment/mock_deployment.go b/internal/deployment/mock_deployment.go index 8c394c37..d64a965f 100644 --- a/internal/deployment/mock_deployment.go +++ b/internal/deployment/mock_deployment.go @@ -1,11 +1,14 @@ // Code generated by MockGen. DO NOT EDIT. // Source: deployment.go - +// +// Generated by this command: +// +// mockgen -source=deployment.go -package=deployment -destination=mock_deployment.go DeploymentAPI +// // Package deployment is a generated GoMock package. package deployment import ( - context "context" reflect "reflect" gomock "go.uber.org/mock/gomock" @@ -36,16 +39,30 @@ func (m *MockDeploymentAPI) EXPECT() *MockDeploymentAPIMockRecorder { return m.recorder } +// SetGCDeploymentAsDesired mocks base method. +func (m *MockDeploymentAPI) SetGCDeploymentAsDesired(nfdInstance *v10.NodeFeatureDiscovery, gcDep *v1.Deployment) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetGCDeploymentAsDesired", nfdInstance, gcDep) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetGCDeploymentAsDesired indicates an expected call of SetGCDeploymentAsDesired. +func (mr *MockDeploymentAPIMockRecorder) SetGCDeploymentAsDesired(nfdInstance, gcDep any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetGCDeploymentAsDesired", reflect.TypeOf((*MockDeploymentAPI)(nil).SetGCDeploymentAsDesired), nfdInstance, gcDep) +} + // SetMasterDeploymentAsDesired mocks base method. -func (m *MockDeploymentAPI) SetMasterDeploymentAsDesired(ctx context.Context, nfdInstance *v10.NodeFeatureDiscovery, masterDep *v1.Deployment) error { +func (m *MockDeploymentAPI) SetMasterDeploymentAsDesired(nfdInstance *v10.NodeFeatureDiscovery, masterDep *v1.Deployment) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SetMasterDeploymentAsDesired", ctx, nfdInstance, masterDep) + ret := m.ctrl.Call(m, "SetMasterDeploymentAsDesired", nfdInstance, masterDep) ret0, _ := ret[0].(error) return ret0 } // SetMasterDeploymentAsDesired indicates an expected call of SetMasterDeploymentAsDesired. -func (mr *MockDeploymentAPIMockRecorder) SetMasterDeploymentAsDesired(ctx, nfdInstance, masterDep interface{}) *gomock.Call { +func (mr *MockDeploymentAPIMockRecorder) SetMasterDeploymentAsDesired(nfdInstance, masterDep any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetMasterDeploymentAsDesired", reflect.TypeOf((*MockDeploymentAPI)(nil).SetMasterDeploymentAsDesired), ctx, nfdInstance, masterDep) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetMasterDeploymentAsDesired", reflect.TypeOf((*MockDeploymentAPI)(nil).SetMasterDeploymentAsDesired), nfdInstance, masterDep) } diff --git a/internal/deployment/testdata/test_gc_deployment.yaml b/internal/deployment/testdata/test_gc_deployment.yaml new file mode 100644 index 00000000..1ea9fc94 --- /dev/null +++ b/internal/deployment/testdata/test_gc_deployment.yaml @@ -0,0 +1,43 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: nfd + name: nfd-gc + namespace: test-namespace + ownerReferences: + - apiVersion: nfd.kubernetes.io/v1 + kind: NodeFeatureDiscovery + controller: true + blockOwnerDeletion: true +spec: + replicas: 1 + selector: + matchLabels: + app: nfd-gc + template: + metadata: + labels: + app: nfd-gc + spec: + serviceAccountName: nfd-gc + dnsPolicy: ClusterFirstWithHostNet + restartPolicy: Always + containers: + - name: nfd-gc + env: + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + image: test-image + imagePullPolicy: Always + command: + - "nfd-gc" + securityContext: + runAsNonRoot: true + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true