From 5881bc403b23fd76bc7179d4e997f25c2397aedb Mon Sep 17 00:00:00 2001 From: Joel Takvorian Date: Thu, 7 Mar 2024 13:14:03 +0100 Subject: [PATCH] Refactor to remove old reconcilers + tests refactoring - Removing the "subReconcilers" in FLP controller, as FLP is now only deployed with Kafka ... - ... but still keep a disctinct pseudo-reconciler "in-process", which has FLP logic but is triggered from Agent controller - Big tests refactoring, started due to the reconciler removal and went further by factorizing some patterns like checking that a list of objects are deleted/created/owned/etc. --- .../v1beta1/flowcollector_types.go | 4 +- .../v1beta2/flowcollector_types.go | 4 +- .../flows.netobserv.io_flowcollectors.yaml | 12 +- ...observ-operator.clusterserviceversion.yaml | 8 - .../flows.netobserv.io_flowcollectors.yaml | 12 +- config/rbac/role.yaml | 8 - .../consoleplugin/consoleplugin_objects.go | 2 +- .../consoleplugin/consoleplugin_test.go | 51 -- controllers/ebpf/agent_controller.go | 27 +- .../ebpf/internal/permissions/permissions.go | 26 +- ...wcollector_controller_certificates_test.go | 33 +- .../flowcollector_controller_console_test.go | 211 +++----- .../flowcollector_controller_ebpf_test.go | 265 ++++------ .../flowcollector_controller_minimal_test.go | 11 - controllers/flowcollector_controller_test.go | 17 +- controllers/flp/flp_controller.go | 74 ++- .../flp/flp_controller_flowmetrics_test.go | 8 +- controllers/flp/flp_controller_test.go | 484 ++++++------------ controllers/flp/flp_monolith_objects.go | 61 --- controllers/flp/flp_monolith_reconciler.go | 199 ------- .../{flp_common_objects.go => flp_objects.go} | 250 ++++----- ...ransfo_reconciler.go => flp_reconciler.go} | 105 +--- controllers/flp/flp_test.go | 306 +++-------- controllers/flp/flp_transfo_objects.go | 109 ---- controllers/flp/in_process.go | 114 +++++ controllers/flp/in_process_reconciler.go | 124 ----- controllers/flp/metrics_api_test.go | 5 +- .../monitoring/monitoring_controller_test.go | 80 ++- controllers/reconcilers/common.go | 4 + controllers/reconcilers/reconcilers.go | 17 + docs/FlowCollector.md | 4 +- go.mod | 4 +- hack/resources-inventory.sh | 11 + pkg/helper/flowcollector.go | 4 - pkg/manager/manager.go | 2 +- pkg/manager/status/status_manager.go | 4 +- pkg/test/envtest.go | 6 +- .../test}/garbage_collected_matcher.go | 2 +- pkg/test/util.go | 114 +++++ 39 files changed, 947 insertions(+), 1835 deletions(-) delete mode 100644 controllers/flp/flp_monolith_objects.go delete mode 100644 controllers/flp/flp_monolith_reconciler.go rename controllers/flp/{flp_common_objects.go => flp_objects.go} (76%) rename controllers/flp/{flp_transfo_reconciler.go => flp_reconciler.go} (50%) delete mode 100644 controllers/flp/flp_transfo_objects.go create mode 100644 controllers/flp/in_process.go delete mode 100644 controllers/flp/in_process_reconciler.go create mode 100755 hack/resources-inventory.sh rename {controllers/controllerstest => pkg/test}/garbage_collected_matcher.go (98%) create mode 100644 pkg/test/util.go diff --git a/apis/flowcollector/v1beta1/flowcollector_types.go b/apis/flowcollector/v1beta1/flowcollector_types.go index 3c2cd4f67..d4a149c99 100644 --- a/apis/flowcollector/v1beta1/flowcollector_types.go +++ b/apis/flowcollector/v1beta1/flowcollector_types.go @@ -400,9 +400,7 @@ type FlowCollectorFLP struct { //+kubebuilder:validation:Minimum=1025 //+kubebuilder:validation:Maximum=65535 //+kubebuilder:default:=2055 - // Port of the flow collector (host port). - // By convention, some values are forbidden. It must be greater than 1024 and different from - // 4500, 4789 and 6081. + // [Deprecated (*)] Port of the flow collector (host port). It is not used anymore and will be removed in a future version. Port int32 `json:"port,omitempty"` //+kubebuilder:validation:Minimum=1 diff --git a/apis/flowcollector/v1beta2/flowcollector_types.go b/apis/flowcollector/v1beta2/flowcollector_types.go index 1f553e64e..264fdd727 100644 --- a/apis/flowcollector/v1beta2/flowcollector_types.go +++ b/apis/flowcollector/v1beta2/flowcollector_types.go @@ -892,9 +892,7 @@ type AdvancedProcessorConfig struct { //+kubebuilder:validation:Maximum=65535 //+kubebuilder:default:=2055 //+optional - // Port of the flow collector (host port). - // By convention, some values are forbidden. It must be greater than 1024 and different from - // 4500, 4789 and 6081. + // [Deprecated (*)] Port of the flow collector (host port). It is not used anymore and will be removed in a future version. Port *int32 `json:"port,omitempty"` //+kubebuilder:validation:Minimum=1 diff --git a/bundle/manifests/flows.netobserv.io_flowcollectors.yaml b/bundle/manifests/flows.netobserv.io_flowcollectors.yaml index 718ac95cd..c59bfc90a 100644 --- a/bundle/manifests/flows.netobserv.io_flowcollectors.yaml +++ b/bundle/manifests/flows.netobserv.io_flowcollectors.yaml @@ -2674,9 +2674,9 @@ spec: type: boolean port: default: 2055 - description: Port of the flow collector (host port). By convention, - some values are forbidden. It must be greater than 1024 and - different from 4500, 4789 and 6081. + description: '[Deprecated (*)] Port of the flow collector (host + port). It is not used anymore and will be removed in a future + version.' format: int32 maximum: 65535 minimum: 1025 @@ -4968,9 +4968,9 @@ spec: type: integer port: default: 2055 - description: Port of the flow collector (host port). By convention, - some values are forbidden. It must be greater than 1024 - and different from 4500, 4789 and 6081. + description: '[Deprecated (*)] Port of the flow collector + (host port). It is not used anymore and will be removed + in a future version.' format: int32 maximum: 65535 minimum: 1025 diff --git a/bundle/manifests/netobserv-operator.clusterserviceversion.yaml b/bundle/manifests/netobserv-operator.clusterserviceversion.yaml index 61536818c..66ef04620 100644 --- a/bundle/manifests/netobserv-operator.clusterserviceversion.yaml +++ b/bundle/manifests/netobserv-operator.clusterserviceversion.yaml @@ -1041,14 +1041,6 @@ spec: - list - update - watch - - apiGroups: - - security.openshift.io - resourceNames: - - hostnetwork - resources: - - securitycontextconstraints - verbs: - - use - apiGroups: - authentication.k8s.io resources: diff --git a/config/crd/bases/flows.netobserv.io_flowcollectors.yaml b/config/crd/bases/flows.netobserv.io_flowcollectors.yaml index 3287c13cb..857b566c1 100644 --- a/config/crd/bases/flows.netobserv.io_flowcollectors.yaml +++ b/config/crd/bases/flows.netobserv.io_flowcollectors.yaml @@ -2661,9 +2661,9 @@ spec: type: boolean port: default: 2055 - description: Port of the flow collector (host port). By convention, - some values are forbidden. It must be greater than 1024 and - different from 4500, 4789 and 6081. + description: '[Deprecated (*)] Port of the flow collector (host + port). It is not used anymore and will be removed in a future + version.' format: int32 maximum: 65535 minimum: 1025 @@ -4955,9 +4955,9 @@ spec: type: integer port: default: 2055 - description: Port of the flow collector (host port). By convention, - some values are forbidden. It must be greater than 1024 - and different from 4500, 4789 and 6081. + description: '[Deprecated (*)] Port of the flow collector + (host port). It is not used anymore and will be removed + in a future version.' format: int32 maximum: 65535 minimum: 1025 diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 707342f2f..966d07c76 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -199,11 +199,3 @@ rules: - list - update - watch -- apiGroups: - - security.openshift.io - resourceNames: - - hostnetwork - resources: - - securitycontextconstraints - verbs: - - use diff --git a/controllers/consoleplugin/consoleplugin_objects.go b/controllers/consoleplugin/consoleplugin_objects.go index 7f4d3194d..fe87aaf06 100644 --- a/controllers/consoleplugin/consoleplugin_objects.go +++ b/controllers/consoleplugin/consoleplugin_objects.go @@ -9,7 +9,7 @@ import ( osv1alpha1 "github.com/openshift/api/console/v1alpha1" monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" - "gopkg.in/yaml.v2" + "gopkg.in/yaml.v3" appsv1 "k8s.io/api/apps/v1" ascv2 "k8s.io/api/autoscaling/v2" corev1 "k8s.io/api/core/v1" diff --git a/controllers/consoleplugin/consoleplugin_test.go b/controllers/consoleplugin/consoleplugin_test.go index 911808cf6..ce89e76be 100644 --- a/controllers/consoleplugin/consoleplugin_test.go +++ b/controllers/consoleplugin/consoleplugin_test.go @@ -330,57 +330,6 @@ func TestConfigMapContent(t *testing.T) { assert.Equal(config.Frontend.Deduper.Merge, false) } -func TestConfigMapError(t *testing.T) { - assert := assert.New(t) - - agentSpec := flowslatest.FlowCollectorAgent{ - Type: "eBPF", - EBPF: flowslatest.FlowCollectorEBPF{ - Sampling: ptr.To(int32(1)), - Advanced: &flowslatest.AdvancedAgentConfig{ - Env: map[string]string{ - "DEDUPER_JUST_MARK": "invalid", - }, - }, - }, - } - lokiSpec := flowslatest.FlowCollectorLoki{} - loki := helper.NewLokiConfig(&lokiSpec, "any") - - // spec with invalid flag - spec := flowslatest.FlowCollectorSpec{ - Agent: agentSpec, - ConsolePlugin: getPluginConfig(), - Loki: lokiSpec, - } - builder := newBuilder(testNamespace, testImage, &spec, &loki) - cm, _, err := builder.configMap() - assert.Nil(cm) - assert.NotNil(err) - - // update to valid flags - agentSpec.EBPF.Advanced.Env = map[string]string{ - "DEDUPER_JUST_MARK": "false", - "DEDUPER_MERGE": "true", - } - spec = flowslatest.FlowCollectorSpec{ - Agent: agentSpec, - ConsolePlugin: getPluginConfig(), - Loki: lokiSpec, - } - builder = newBuilder(testNamespace, testImage, &spec, &loki) - cm, _, err = builder.configMap() - assert.NotNil(cm) - assert.Nil(err) - - // parse output config and check expected values - var config config.PluginConfig - err = yaml.Unmarshal([]byte(cm.Data["config.yaml"]), &config) - assert.Nil(err) - assert.Equal(config.Frontend.Deduper.Mark, false) - assert.Equal(config.Frontend.Deduper.Merge, true) -} - func TestServiceUpdateCheck(t *testing.T) { assert := assert.New(t) old := getServiceSpecs() diff --git a/controllers/ebpf/agent_controller.go b/controllers/ebpf/agent_controller.go index d21d5ae4e..631f206c9 100644 --- a/controllers/ebpf/agent_controller.go +++ b/controllers/ebpf/agent_controller.go @@ -33,8 +33,6 @@ const ( envExcludeInterfaces = "EXCLUDE_INTERFACES" envInterfaces = "INTERFACES" envAgentIP = "AGENT_IP" - envFlowsTargetHost = "FLOWS_TARGET_HOST" - envFlowsTargetPort = "FLOWS_TARGET_PORT" envSampling = "SAMPLING" envExport = "EXPORT" envKafkaBrokers = "KAFKA_BROKERS" @@ -143,13 +141,10 @@ func (c *AgentController) Reconcile(ctx context.Context, target *flowslatest.Flo return fmt.Errorf("reconciling permissions: %w", err) } - var inprocFLPInfo *flp.InProcessInfo - if helper.UseMergedAgentFLP(&target.Spec) { - // Direct-FLP mode - inprocFLPInfo, err = flp.ReconcileInProcess(ctx, c.Instance, target) - if err != nil { - return fmt.Errorf("reconciling in-process FLP: %w", err) - } + // Direct-FLP mode + inprocFLPInfo, err := flp.ReconcileInProcess(ctx, c.Instance, target, constants.EBPFAgentName) + if err != nil { + return fmt.Errorf("reconciling in-process FLP: %w", err) } desired, err := c.desired(ctx, target, inprocFLPInfo, rlog) @@ -364,7 +359,6 @@ func (c *AgentController) envConfig(ctx context.Context, coll *flowslatest.FlowC ) } } else { - debugConfig := helper.GetAdvancedProcessorConfig(coll.Spec.Processor.Advanced) config = append(config, corev1.EnvVar{ Name: envExport, @@ -374,19 +368,6 @@ func (c *AgentController) envConfig(ctx context.Context, coll *flowslatest.FlowC Name: envFLPConfig, Value: inprocFLPInfo.JSONConfig, }, - corev1.EnvVar{ - Name: envFlowsTargetHost, - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "status.hostIP", - }, - }, - }, - corev1.EnvVar{ - Name: envFlowsTargetPort, - Value: strconv.Itoa(int(*debugConfig.Port)), - }, ) } return config, nil diff --git a/controllers/ebpf/internal/permissions/permissions.go b/controllers/ebpf/internal/permissions/permissions.go index 6d81909e5..83ca884b5 100644 --- a/controllers/ebpf/internal/permissions/permissions.go +++ b/controllers/ebpf/internal/permissions/permissions.go @@ -74,23 +74,23 @@ func (c *Reconciler) reconcileNamespace(ctx context.Context) error { }, }, } - if actual == nil && desired != nil { + if actual == nil { rlog.Info("creating namespace") return c.CreateOwned(ctx, desired) } - if actual != nil && desired != nil { - // We noticed that audit labels are automatically removed - // in some configurations of K8s, so to avoid an infinite update loop, we just ignore - // it (if the user removes it manually, it's at their own risk) - if !helper.IsSubSet(actual.ObjectMeta.Labels, - map[string]string{ - "app": constants.OperatorName, - "pod-security.kubernetes.io/enforce": "privileged", - }) { - rlog.Info("updating namespace") - return c.UpdateIfOwned(ctx, actual, desired) - } + + // We noticed that audit labels are automatically removed + // in some configurations of K8s, so to avoid an infinite update loop, we just ignore + // it (if the user removes it manually, it's at their own risk) + if !helper.IsSubSet(actual.ObjectMeta.Labels, + map[string]string{ + "app": constants.OperatorName, + "pod-security.kubernetes.io/enforce": "privileged", + }) { + rlog.Info("updating namespace") + return c.UpdateIfOwned(ctx, actual, desired) } + rlog.Info("namespace is already reconciled. Doing nothing") return nil } diff --git a/controllers/flowcollector_controller_certificates_test.go b/controllers/flowcollector_controller_certificates_test.go index 2b10387ae..035dc1de4 100644 --- a/controllers/flowcollector_controller_certificates_test.go +++ b/controllers/flowcollector_controller_certificates_test.go @@ -13,8 +13,6 @@ import ( flowslatest "github.com/netobserv/network-observability-operator/apis/flowcollector/v1beta2" "github.com/netobserv/network-observability-operator/controllers/constants" - . "github.com/netobserv/network-observability-operator/controllers/controllerstest" - "github.com/netobserv/network-observability-operator/controllers/flp" "github.com/netobserv/network-observability-operator/pkg/test" "github.com/netobserv/network-observability-operator/pkg/watchers" ) @@ -33,7 +31,7 @@ func flowCollectorCertificatesSpecs() { Name: "cluster", } flpKey := types.NamespacedName{ - Name: constants.FLPName + flp.FlpConfSuffix[flp.ConfKafkaTransformer], + Name: constants.FLPName, Namespace: operatorNamespace, } pluginKey := types.NamespacedName{ @@ -471,30 +469,11 @@ func flowCollectorCertificatesSpecs() { Context("Checking CR ownership", func() { It("Should be garbage collected", func() { - // Retrieve CR to get its UID - By("Getting the CR") - flowCR := getCR(crKey) - - By("Expecting flowlogs-pipeline deployment to be garbage collected") - Eventually(func() interface{} { - d := appsv1.Deployment{} - _ = k8sClient.Get(ctx, flpKey, &d) - return &d - }, timeout, interval).Should(BeGarbageCollectedBy(flowCR)) - - By("Expecting agent daemonset to be garbage collected") - Eventually(func() interface{} { - d := appsv1.DaemonSet{} - _ = k8sClient.Get(ctx, agentKey, &d) - return &d - }, timeout, interval).Should(BeGarbageCollectedBy(flowCR)) - - By("Expecting console plugin deployment to be garbage collected") - Eventually(func() interface{} { - d := appsv1.Deployment{} - _ = k8sClient.Get(ctx, pluginKey, &d) - return &d - }, timeout, interval).Should(BeGarbageCollectedBy(flowCR)) + expectOwnership(operatorNamespace, + test.Deployment(flpKey.Name), + test.Deployment(pluginKey.Name), + ) + expectOwnership(agentKey.Namespace, test.DaemonSet(agentKey.Name)) }) }) diff --git a/controllers/flowcollector_controller_console_test.go b/controllers/flowcollector_controller_console_test.go index 46c077daa..44cafec2d 100644 --- a/controllers/flowcollector_controller_console_test.go +++ b/controllers/flowcollector_controller_console_test.go @@ -4,19 +4,17 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" operatorsv1 "github.com/openshift/api/operator/v1" - monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" appsv1 "k8s.io/api/apps/v1" ascv2 "k8s.io/api/autoscaling/v2" v1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/utils/ptr" flowslatest "github.com/netobserv/network-observability-operator/apis/flowcollector/v1beta2" "github.com/netobserv/network-observability-operator/controllers/constants" - . "github.com/netobserv/network-observability-operator/controllers/controllerstest" + "github.com/netobserv/network-observability-operator/pkg/test" ) // Because the simulated Kube server doesn't manage automatic resource cleanup like an actual Kube would do, @@ -26,21 +24,15 @@ const cpNamespace = "namespace-console-specs" // nolint:cyclop func flowCollectorConsolePluginSpecs() { - cpKey := types.NamespacedName{ - Name: "netobserv-plugin", - Namespace: cpNamespace, - } - configKey := types.NamespacedName{ - Name: "console-plugin-config", - Namespace: cpNamespace, - } - crKey := types.NamespacedName{ - Name: "cluster", - } - consoleCRKey := types.NamespacedName{ - Name: "cluster", - } - rbKeyPlugin := types.NamespacedName{Name: constants.PluginName} + cpDepl := test.Deployment(constants.PluginName) + cpCM := test.ConfigMap("console-plugin-config") + cpSvc := test.Service(constants.PluginName) + cpSA := test.ServiceAccount(constants.PluginName) + cpCRB := test.ClusterRoleBinding(constants.PluginName) + cpSM := test.ServiceMonitor(constants.PluginName) + crKey := types.NamespacedName{Name: "cluster"} + consoleCRKey := types.NamespacedName{Name: "cluster"} + configKey := cpCM.GetKey(cpNamespace) BeforeEach(func() { // Add any setup steps that needs to be executed before each test @@ -120,33 +112,25 @@ func flowCollectorConsolePluginSpecs() { // test Kubernetes API server, which isn't the goal here. Context("Deploying the console plugin", func() { It("Should create successfully", func() { - By("Expecting to create the console plugin Deployment") - Eventually(func() interface{} { - dp := appsv1.Deployment{} - if err := k8sClient.Get(ctx, cpKey, &dp); err != nil { - return err - } - return *dp.Spec.Replicas - }, timeout, interval).Should(Equal(int32(1))) - By("Expecting to create the console plugin Service") - Eventually(func() interface{} { - svc := v1.Service{} - if err := k8sClient.Get(ctx, cpKey, &svc); err != nil { - return err - } - return svc.Spec.Ports[0].Port - }, timeout, interval).Should(Equal(int32(9001))) + objs := expectCreation(cpNamespace, + cpDepl, + cpSvc, + cpCM, + cpSA, + cpCRB, + cpSM, + ) + Expect(objs).To(HaveLen(6)) + Expect(*objs[0].(*appsv1.Deployment).Spec.Replicas).To(Equal(int32(1))) + Expect(objs[1].(*v1.Service).Spec.Ports[0].Port).To(Equal(int32(9001))) By("Creating the console plugin configmap") Eventually(getConfigMapData(configKey), timeout, interval).Should(ContainSubstring("url: http://loki:3100/")) By("Expecting to create console plugin role binding") - rb := rbacv1.ClusterRoleBinding{} - Eventually(func() interface{} { - return k8sClient.Get(ctx, rbKeyPlugin, &rb) - }, timeout, interval).Should(Succeed()) + rb := objs[4].(*rbacv1.ClusterRoleBinding) Expect(rb.Subjects).Should(HaveLen(1)) Expect(rb.Subjects[0].Name).Should(Equal("netobserv-plugin")) Expect(rb.RoleRef.Name).Should(Equal("netobserv-plugin")) @@ -167,7 +151,7 @@ func flowCollectorConsolePluginSpecs() { By("Expecting the console plugin Deployment to be scaled up") Eventually(func() interface{} { dp := appsv1.Deployment{} - if err := k8sClient.Get(ctx, cpKey, &dp); err != nil { + if err := k8sClient.Get(ctx, cpDepl.GetKey(cpNamespace), &dp); err != nil { return err } return *dp.Spec.Replicas @@ -176,7 +160,7 @@ func flowCollectorConsolePluginSpecs() { By("Expecting the console plugin Service to be updated") Eventually(func() interface{} { svc := v1.Service{} - if err := k8sClient.Get(ctx, cpKey, &svc); err != nil { + if err := k8sClient.Get(ctx, cpSvc.GetKey(cpNamespace), &svc); err != nil { return err } return svc.Spec.Ports[0].Port @@ -184,33 +168,17 @@ func flowCollectorConsolePluginSpecs() { }) It("Should create desired objects when they're not found (e.g. case of an operator upgrade)", func() { - sm := monitoringv1.ServiceMonitor{} - - By("Expecting ServiceMonitor to exist") - Eventually(func() interface{} { - return k8sClient.Get(ctx, types.NamespacedName{ - Name: "netobserv-plugin", - Namespace: cpNamespace, - }, &sm) - }, timeout, interval).Should(Succeed()) - // Manually delete ServiceMonitor By("Deleting ServiceMonitor") - Eventually(func() error { - return k8sClient.Delete(ctx, &sm) - }, timeout, interval).Should(Succeed()) + Eventually(func() error { return k8sClient.Delete(ctx, cpSM.Resource) }, timeout, interval).Should(Succeed()) // Do a dummy change that will trigger reconcile, and make sure SM is created again updateCR(crKey, func(fc *flowslatest.FlowCollector) { fc.Spec.Processor.LogLevel = "trace" }) + By("Expecting ServiceMonitor to exist") - Eventually(func() interface{} { - return k8sClient.Get(ctx, types.NamespacedName{ - Name: "netobserv-plugin", - Namespace: cpNamespace, - }, &sm) - }, timeout, interval).Should(Succeed()) + expectCreation(cpNamespace, cpSM) }) }) @@ -279,80 +247,45 @@ func flowCollectorConsolePluginSpecs() { updateCR(crKey, func(fc *flowslatest.FlowCollector) { fc.Spec.ConsolePlugin.Enable = ptr.To(false) }) - Eventually(func() error { - d := appsv1.Deployment{} - return k8sClient.Get(ctx, cpKey, &d) - }).WithTimeout(timeout).WithPolling(interval). - Should(Satisfy(errors.IsNotFound)) - Eventually(func() error { - d := v1.Service{} - return k8sClient.Get(ctx, cpKey, &d) - }).WithTimeout(timeout).WithPolling(interval). - Should(Satisfy(errors.IsNotFound)) - Eventually(func() error { - d := v1.ServiceAccount{} - return k8sClient.Get(ctx, cpKey, &d) - }).WithTimeout(timeout).WithPolling(interval). - Should(Satisfy(errors.IsNotFound)) + + expectDeletion(cpNamespace, + cpDepl, + cpSvc, + cpSA, + cpCM, + cpSM, + ) }) It("Should recreate console plugin if enabled back", func() { updateCR(crKey, func(fc *flowslatest.FlowCollector) { fc.Spec.ConsolePlugin.Enable = ptr.To(true) }) - Eventually(func() error { - d := appsv1.Deployment{} - return k8sClient.Get(ctx, cpKey, &d) - }).WithTimeout(timeout).WithPolling(interval). - Should(Succeed()) - Eventually(func() error { - d := v1.Service{} - return k8sClient.Get(ctx, cpKey, &d) - }).WithTimeout(timeout).WithPolling(interval). - Should(Succeed()) - Eventually(func() error { - d := v1.ServiceAccount{} - return k8sClient.Get(ctx, cpKey, &d) - }).WithTimeout(timeout).WithPolling(interval). - Should(Succeed()) + + expectCreation(cpNamespace, + cpDepl, + cpSvc, + cpSA, + cpCM, + cpSM, + ) }) }) Context("Checking CR ownership", func() { It("Should be garbage collected", func() { - // Retrieve CR to get its UID - By("Getting the CR") - flowCR := getCR(crKey) - - By("Expecting console plugin deployment to be garbage collected") - Eventually(func() interface{} { - d := appsv1.Deployment{} - _ = k8sClient.Get(ctx, cpKey, &d) - return &d - }, timeout, interval).Should(BeGarbageCollectedBy(flowCR)) - - By("Expecting console plugin service to be garbage collected") - Eventually(func() interface{} { - svc := v1.Service{} - _ = k8sClient.Get(ctx, cpKey, &svc) - return &svc - }, timeout, interval).Should(BeGarbageCollectedBy(flowCR)) - - By("Expecting console plugin service account to be garbage collected") - Eventually(func() interface{} { - svcAcc := v1.ServiceAccount{} - _ = k8sClient.Get(ctx, cpKey, &svcAcc) - return &svcAcc - }, timeout, interval).Should(BeGarbageCollectedBy(flowCR)) + expectOwnership(cpNamespace, + cpDepl, + cpSvc, + cpSA, + cpCM, + cpSM, + ) }) }) Context("Changing namespace", func() { const otherNamespace = "other-namespace" - cpKey2 := types.NamespacedName{ - Name: "netobserv-plugin", - Namespace: otherNamespace, - } It("Should update namespace successfully", func() { updateCR(crKey, func(fc *flowslatest.FlowCollector) { @@ -361,35 +294,21 @@ func flowCollectorConsolePluginSpecs() { }) It("Should redeploy console plugin in new namespace", func() { - By("Expecting deployment in previous namespace to be deleted") - Eventually(func() interface{} { - return k8sClient.Get(ctx, cpKey, &appsv1.Deployment{}) - }, timeout, interval).Should(MatchError(`deployments.apps "netobserv-plugin" not found`)) - - By("Expecting service in previous namespace to be deleted") - Eventually(func() interface{} { - return k8sClient.Get(ctx, cpKey, &v1.Service{}) - }, timeout, interval).Should(MatchError(`services "netobserv-plugin" not found`)) - - By("Expecting service account in previous namespace to be deleted") - Eventually(func() interface{} { - return k8sClient.Get(ctx, cpKey, &v1.ServiceAccount{}) - }, timeout, interval).Should(MatchError(`serviceaccounts "netobserv-plugin" not found`)) - - By("Expecting deployment to be created in new namespace") - Eventually(func() interface{} { - return k8sClient.Get(ctx, cpKey2, &appsv1.Deployment{}) - }, timeout, interval).Should(Succeed()) - - By("Expecting service to be created in new namespace") - Eventually(func() interface{} { - return k8sClient.Get(ctx, cpKey2, &v1.Service{}) - }, timeout, interval).Should(Succeed()) - - By("Expecting service account to be created in new namespace") - Eventually(func() interface{} { - return k8sClient.Get(ctx, cpKey2, &v1.ServiceAccount{}) - }, timeout, interval).Should(Succeed()) + expectDeletion(cpNamespace, + cpDepl, + cpSvc, + cpSA, + cpCM, + cpSM, + ) + + expectCreation(otherNamespace, + cpDepl, + cpSvc, + cpSA, + cpCM, + cpSM, + ) }) }) diff --git a/controllers/flowcollector_controller_ebpf_test.go b/controllers/flowcollector_controller_ebpf_test.go index 3aaaf5ec3..1eb452cca 100644 --- a/controllers/flowcollector_controller_ebpf_test.go +++ b/controllers/flowcollector_controller_ebpf_test.go @@ -5,6 +5,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "gopkg.in/yaml.v3" appsv1 "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" @@ -12,10 +13,11 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/utils/ptr" + "github.com/netobserv/flowlogs-pipeline/pkg/config" flowslatest "github.com/netobserv/network-observability-operator/apis/flowcollector/v1beta2" "github.com/netobserv/network-observability-operator/controllers/constants" - . "github.com/netobserv/network-observability-operator/controllers/controllerstest" "github.com/netobserv/network-observability-operator/pkg/helper" + "github.com/netobserv/network-observability-operator/pkg/test" ) func flowCollectorEBPFSpecs() { @@ -23,34 +25,24 @@ func flowCollectorEBPFSpecs() { // we need either to cleanup all created resources manually, or to use different namespaces between tests // For simplicity, we'll use a different namespace operatorNamespace := "namespace-ebpf-specs" + operatorPrivilegedNamespace := operatorNamespace + "-privileged" agentKey := types.NamespacedName{ Name: "netobserv-ebpf-agent", - Namespace: operatorNamespace + "-privileged", + Namespace: operatorPrivilegedNamespace, } operatorNamespace2 := "namespace-ebpf-specs2" - agentKey2 := types.NamespacedName{ - Name: "netobserv-ebpf-agent", - Namespace: operatorNamespace2 + "-privileged", - } + operatorPrivilegedNamespace2 := operatorNamespace2 + "-privileged" + + dsRef := test.DaemonSet(constants.EBPFAgentName) + saRef := test.ServiceAccount(constants.EBPFServiceAccount) + svcMetricsRef := test.Service(constants.EBPFAgentMetricsSvcName) + svcFLPMetricsRef := test.Service("netobserv-ebpf-agent-prom") + smRef := test.ServiceMonitor(constants.EBPFAgentMetricsSvcMonitoringName) + smFLPRef := test.ServiceMonitor(constants.EBPFAgentName + "-monitor") + ruleFLPRef := test.PrometheusRule(constants.EBPFAgentName + "-alert") + nsRef := test.Namespace(operatorPrivilegedNamespace) + crKey := types.NamespacedName{Name: "cluster"} - flpKey2 := types.NamespacedName{ - Name: constants.FLPName, - Namespace: operatorNamespace2, - } - saKey := types.NamespacedName{ - Name: constants.EBPFServiceAccount, - Namespace: agentKey.Namespace, - } - saKey2 := types.NamespacedName{ - Name: constants.EBPFServiceAccount, - Namespace: agentKey2.Namespace, - } - promSvcKey := types.NamespacedName{ - Name: constants.EBPFAgentMetricsSvcName, - Namespace: operatorNamespace + "-privileged", - } - nsKey := types.NamespacedName{Name: agentKey.Namespace} - nsKey2 := types.NamespacedName{Name: agentKey2.Namespace} Context("Netobserv eBPF Agent Reconciler", func() { It("Should deploy when it does not exist", func() { @@ -62,9 +54,6 @@ func flowCollectorEBPFSpecs() { Processor: flowslatest.FlowCollectorFLP{ ImagePullPolicy: "Never", LogLevel: "error", - Advanced: &flowslatest.AdvancedProcessorConfig{ - Port: ptr.To(int32(9999)), - }, }, Agent: flowslatest.FlowCollectorAgent{ Type: "eBPF", @@ -89,13 +78,21 @@ func flowCollectorEBPFSpecs() { return k8sClient.Create(ctx, desired) }).WithTimeout(timeout).WithPolling(interval).Should(Succeed()) - ds := appsv1.DaemonSet{} - By("Expecting to create the netobserv-ebpf-agent DaemonSet") - Eventually(func() interface{} { - return k8sClient.Get(ctx, agentKey, &ds) - }).WithTimeout(timeout).WithPolling(interval).Should(Succeed()) + objs := expectCreation(operatorPrivilegedNamespace, + dsRef, + saRef, + svcMetricsRef, + svcFLPMetricsRef, + smRef, + smFLPRef, + ruleFLPRef, + nsRef, + ) + Expect(objs).To(HaveLen(8)) + + spec := objs[0].(*appsv1.DaemonSet).Spec.Template.Spec + ns := objs[7].(*v1.Namespace) - spec := ds.Spec.Template.Spec By("expecting that the netobserv-ebpf-agent daemonset is properly configured") Expect(spec.HostNetwork).To(BeTrue()) Expect(spec.DNSPolicy).To(Equal(v1.DNSClusterFirstWithHostNet)) @@ -108,7 +105,7 @@ func flowCollectorEBPFSpecs() { Expect(spec.Containers[0].SecurityContext.RunAsUser).To(Not(BeNil())) Expect(*spec.Containers[0].SecurityContext.RunAsUser).To(Equal(int64(0))) Expect(spec.Containers[0].Env).To(ContainElements( - v1.EnvVar{Name: "EXPORT", Value: "grpc"}, + v1.EnvVar{Name: "EXPORT", Value: "direct-flp"}, v1.EnvVar{Name: "CACHE_ACTIVE_TIMEOUT", Value: "15s"}, v1.EnvVar{Name: "CACHE_MAX_FLOWS", Value: "100"}, v1.EnvVar{Name: "LOG_LEVEL", Value: "trace"}, @@ -117,28 +114,25 @@ func flowCollectorEBPFSpecs() { v1.EnvVar{Name: "BUFFERS_LENGTH", Value: "100"}, v1.EnvVar{Name: "GOGC", Value: "400"}, v1.EnvVar{Name: "SAMPLING", Value: "123"}, - v1.EnvVar{Name: "FLOWS_TARGET_PORT", Value: "9999"}, )) - hostFound := false + var flpConfig string for _, env := range spec.Containers[0].Env { - if env.Name == "FLOWS_TARGET_HOST" { - if env.ValueFrom == nil || - env.ValueFrom.FieldRef == nil || - env.ValueFrom.FieldRef.FieldPath != "status.hostIP" { - Fail(fmt.Sprintf("FLOWS_TARGET_HOST expected to refer to \"status.hostIP\"."+ - " Got: %+v", env.ValueFrom)) - } else { - hostFound = true - break - } + if env.Name == "FLP_CONFIG" { + flpConfig = env.Value } } - Expect(hostFound).To(BeTrue(), - fmt.Sprintf("expected FLOWS_TARGET_HOST env var in %+v", spec.Containers[0].Env)) + Expect(flpConfig).NotTo(BeEmpty()) + + // Parse config + var cfs config.ConfigFileStruct + err := yaml.Unmarshal([]byte(flpConfig), &cfs) + Expect(err).To(BeNil()) + Expect(cfs.Pipeline).To(Equal([]config.Stage{ + {Name: "enrich", Follows: "preset-ingester"}, + {Name: "loki", Follows: "enrich"}, + {Name: "prometheus", Follows: "enrich"}, + })) - ns := v1.Namespace{} - By("expecting to create the netobserv-privileged namespace") - Expect(k8sClient.Get(ctx, nsKey, &ns)).To(Succeed()) Expect(ns.Labels).To(Satisfy(func(labels map[string]string) bool { return helper.IsSubSet(ns.Labels, map[string]string{ "app": constants.OperatorName, @@ -146,14 +140,6 @@ func flowCollectorEBPFSpecs() { "pod-security.kubernetes.io/audit": "privileged", }) })) - - By("expecting to create the netobserv-ebpf-agent service account") - Expect(k8sClient.Get(ctx, saKey, &v1.ServiceAccount{})).To(Succeed()) - - By("Expecting to create the netobserv-ebpf-agent prometheus service") - Eventually(func() interface{} { - return k8sClient.Get(ctx, promSvcKey, &v1.Service{}) - }).WithTimeout(timeout).WithPolling(interval).Should(Succeed()) }) It("Should update fields that have changed", func() { @@ -162,6 +148,7 @@ func flowCollectorEBPFSpecs() { *fc.Spec.Agent.EBPF.Sampling = 4 fc.Spec.Agent.EBPF.Privileged = true fc.Spec.Agent.EBPF.Metrics.Enable = ptr.To(false) + fc.Spec.DeploymentModel = flowslatest.DeploymentModelKafka }) ds := appsv1.DaemonSet{} @@ -185,10 +172,13 @@ func flowCollectorEBPFSpecs() { Expect(*container.SecurityContext.Privileged).To(BeTrue()) Expect(container.SecurityContext.Capabilities).To(BeNil()) - By("Expecting to delete the netobserv-ebpf-agent prometheus service") - Eventually(func() interface{} { - return k8sClient.Get(ctx, promSvcKey, &v1.Service{}) - }).WithTimeout(timeout).WithPolling(interval).Should(MatchError(`services "ebpf-agent-svc-prom" not found`)) + expectDeletion(operatorNamespace+"-privileged", + svcMetricsRef, + smRef, + svcFLPMetricsRef, + smFLPRef, + ruleFLPRef, + ) }) It("Should redeploy all when changing namespace", func() { @@ -196,25 +186,22 @@ func flowCollectorEBPFSpecs() { fc.Spec.Namespace = operatorNamespace2 }) - By("Expecting daemonset in previous namespace to be deleted") - Eventually(func() interface{} { - return k8sClient.Get(ctx, agentKey, &appsv1.DaemonSet{}) - }, timeout, interval).Should(MatchError(`daemonsets.apps "netobserv-ebpf-agent" not found`)) - - By("Expecting service account in previous namespace to be deleted") - Eventually(func() interface{} { - return k8sClient.Get(ctx, saKey, &v1.ServiceAccount{}) - }, timeout, interval).Should(MatchError(`serviceaccounts "netobserv-ebpf-agent" not found`)) - - By("Expecting daemonset to be created in new namespace") - Eventually(func() interface{} { - return k8sClient.Get(ctx, agentKey2, &appsv1.DaemonSet{}) - }, timeout, interval).Should(Succeed()) + expectDeletion(operatorPrivilegedNamespace, + dsRef, + saRef, + ) + expectCreation(operatorPrivilegedNamespace2, + dsRef, + saRef, + ) + }) - By("Expecting service account to be created in new namespace") - Eventually(func() interface{} { - return k8sClient.Get(ctx, saKey2, &v1.ServiceAccount{}) - }, timeout, interval).Should(Succeed()) + It("Should be garbage collected", func() { + expectOwnership(operatorPrivilegedNamespace2, + dsRef, + test.Namespace(operatorPrivilegedNamespace2), + saRef, + ) }) It("Should undeploy everything when deleted", func() { @@ -236,61 +223,20 @@ func flowCollectorEBPFSpecs() { ) }).WithTimeout(timeout).WithPolling(interval). Should(Satisfy(errors.IsNotFound)) - - By("expecting to delete the flowlogs-pipeline deployment") - Eventually(func() error { - return k8sClient.Get(ctx, flpKey2, &flowslatest.FlowCollector{}) - }).WithTimeout(timeout).WithPolling(interval). - Should(Satisfy(errors.IsNotFound)) - - By("expecting to delete netobserv-ebpf-agent daemonset") - Eventually(func() interface{} { - ds := &appsv1.DaemonSet{} - if err := k8sClient.Get(ctx, agentKey2, ds); err != nil { - return err - } - return ds - }).WithTimeout(timeout).WithPolling(interval). - Should(BeGarbageCollectedBy(flowCR)) - - By("expecting to delete the netobserv-privileged namespace") - Eventually(func() interface{} { - ns := &v1.Namespace{} - if err := k8sClient.Get(ctx, nsKey2, ns); err != nil { - return err - } - return ns - }).WithTimeout(timeout).WithPolling(interval). - Should(BeGarbageCollectedBy(flowCR)) - - By("expecting to delete the netobserv-ebpf-agent service account") - Eventually(func() interface{} { - sa := &v1.ServiceAccount{} - if err := k8sClient.Get(ctx, saKey2, sa); err != nil { - return err - } - return sa - }).WithTimeout(timeout).WithPolling(interval). - Should(BeGarbageCollectedBy(flowCR)) }) }) } func flowCollectorEBPFKafkaSpecs() { operatorNamespace := "ebpf-kafka-specs" - agentKey := types.NamespacedName{ - Name: "netobserv-ebpf-agent", - Namespace: operatorNamespace + "-privileged", - } + operatorPrivilegedNamespace := operatorNamespace + "-privileged" + dsRef := test.DaemonSet(constants.EBPFAgentName) + saRef := test.ServiceAccount(constants.EBPFServiceAccount) + flpRef := test.Deployment(constants.FLPName) + flpSvcRef := test.Service(constants.FLPName + "-prom") + flpSMRef := test.ServiceMonitor(constants.FLPName + "-monitor") crKey := types.NamespacedName{Name: "cluster"} - flpIngesterKey := types.NamespacedName{ - Name: constants.FLPName + "-ingester", - Namespace: operatorNamespace, - } - flpTransformerKey := types.NamespacedName{ - Name: constants.FLPName + "-transformer", - Namespace: operatorNamespace, - } + Context("Netobserv eBPF Agent Reconciler", func() { It("Should deploy the agent with the proper configuration", func() { descriptor := &flowslatest.FlowCollector{ @@ -307,13 +253,14 @@ func flowCollectorEBPFKafkaSpecs() { } Expect(k8sClient.Create(ctx, descriptor)).Should(Succeed()) - ds := appsv1.DaemonSet{} - By("making sure that the proper environment variables have been passed to the agent") - Eventually(func() interface{} { - return k8sClient.Get(ctx, agentKey, &ds) - }).WithTimeout(timeout).WithPolling(interval).Should(Succeed()) + objs := expectCreation(operatorPrivilegedNamespace, + dsRef, + saRef, + ) + Expect(objs).To(HaveLen(2)) + + spec := objs[0].(*appsv1.DaemonSet).Spec.Template.Spec - spec := ds.Spec.Template.Spec Expect(len(spec.Containers)).To(Equal(1)) Expect(spec.Containers[0].Env).To(ContainElements( v1.EnvVar{Name: "EXPORT", Value: "kafka"}, @@ -321,23 +268,25 @@ func flowCollectorEBPFKafkaSpecs() { v1.EnvVar{Name: "KAFKA_TOPIC", Value: "network-flows"}, )) }) + It("Should properly deploy flowlogs-pipeline", func() { - By("deploying flowlogs-pipeline-transformer") - Eventually(func() interface{} { - return k8sClient.Get(ctx, flpTransformerKey, &appsv1.Deployment{}) - }, timeout, interval).Should(Succeed()) + objs := expectCreation(operatorNamespace, + flpRef, + flpSvcRef, + flpSMRef, + ) + Expect(objs).To(HaveLen(3)) + }) - By("not deploying flowlogs-pipeline-ingester") - Expect(k8sClient.Get(ctx, flpIngesterKey, &appsv1.DaemonSet{})). - Should(Not(Succeed())) + It("Should be garbage collected", func() { + expectOwnership(operatorNamespace, + dsRef, + flpRef, + saRef, + ) }) - It("Should correctly undeploy", func() { - // Retrieve CR to get its UID - flowCR := &flowslatest.FlowCollector{} - Eventually(func() error { - return k8sClient.Get(ctx, crKey, flowCR) - }, timeout, interval).Should(Succeed()) + It("Should correctly undeploy", func() { Expect(k8sClient.Delete(ctx, &flowslatest.FlowCollector{ ObjectMeta: metav1.ObjectMeta{Name: crKey.Name}, })).Should(Succeed()) @@ -350,26 +299,6 @@ func flowCollectorEBPFKafkaSpecs() { ) }).WithTimeout(timeout).WithPolling(interval). Should(Satisfy(errors.IsNotFound)) - - By("expecting to delete the flowlogs-pipeline-transformer deployment") - Eventually(func() interface{} { - dp := &appsv1.Deployment{} - if err := k8sClient.Get(ctx, flpTransformerKey, dp); err != nil { - return err - } - return dp - }).WithTimeout(timeout).WithPolling(interval). - Should(BeGarbageCollectedBy(flowCR)) - - By("expecting to delete netobserv-ebpf-agent daemonset") - Eventually(func() interface{} { - ds := &appsv1.DaemonSet{} - if err := k8sClient.Get(ctx, agentKey, ds); err != nil { - return err - } - return ds - }).WithTimeout(timeout).WithPolling(interval). - Should(BeGarbageCollectedBy(flowCR)) }) }) } diff --git a/controllers/flowcollector_controller_minimal_test.go b/controllers/flowcollector_controller_minimal_test.go index e80fd011a..eac015b3b 100644 --- a/controllers/flowcollector_controller_minimal_test.go +++ b/controllers/flowcollector_controller_minimal_test.go @@ -9,7 +9,6 @@ import ( "k8s.io/apimachinery/pkg/types" flowslatest "github.com/netobserv/network-observability-operator/apis/flowcollector/v1beta2" - "github.com/netobserv/network-observability-operator/controllers/constants" ) // nolint:cyclop @@ -21,10 +20,6 @@ func flowCollectorMinimalSpecs() { Name: "netobserv-ebpf-agent", Namespace: "netobserv-privileged", } - flpKey := types.NamespacedName{ - Name: constants.FLPName, - Namespace: "netobserv", - } cpKey := types.NamespacedName{ Name: "netobserv-plugin", Namespace: "netobserv", @@ -56,12 +51,6 @@ func flowCollectorMinimalSpecs() { return k8sClient.Get(ctx, agentKey, &ds) }, timeout, interval).Should(Succeed()) - By("Expecting to create the flowlogs-pipeline DaemonSet") - Eventually(func() error { - ds := appsv1.DaemonSet{} - return k8sClient.Get(ctx, flpKey, &ds) - }, timeout, interval).Should(Succeed()) - By("Expecting to create the console plugin Deployment") Eventually(func() interface{} { dp := appsv1.Deployment{} diff --git a/controllers/flowcollector_controller_test.go b/controllers/flowcollector_controller_test.go index 0b149f205..7f216726f 100644 --- a/controllers/flowcollector_controller_test.go +++ b/controllers/flowcollector_controller_test.go @@ -2,9 +2,11 @@ package controllers import ( "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" flowslatest "github.com/netobserv/network-observability-operator/apis/flowcollector/v1beta2" "github.com/netobserv/network-observability-operator/pkg/test" + "github.com/onsi/ginkgo/v2" ) const ( @@ -16,10 +18,19 @@ var ( updateCR = func(key types.NamespacedName, updater func(*flowslatest.FlowCollector)) { test.UpdateCR(ctx, k8sClient, key, updater) } - getCR = func(key types.NamespacedName) *flowslatest.FlowCollector { - return test.GetCR(ctx, k8sClient, key) - } cleanupCR = func(key types.NamespacedName) { test.CleanupCR(ctx, k8sClient, key) } + expectCreation = func(namespace string, objs ...test.ResourceRef) []client.Object { + ginkgo.GinkgoHelper() + return test.ExpectCreation(ctx, k8sClient, namespace, objs...) + } + expectDeletion = func(namespace string, objs ...test.ResourceRef) { + ginkgo.GinkgoHelper() + test.ExpectDeletion(ctx, k8sClient, namespace, objs...) + } + expectOwnership = func(namespace string, objs ...test.ResourceRef) { + ginkgo.GinkgoHelper() + test.ExpectOwnership(ctx, k8sClient, namespace, objs...) + } ) diff --git a/controllers/flp/flp_controller.go b/controllers/flp/flp_controller.go index f0fa536db..4edd5cdb5 100644 --- a/controllers/flp/flp_controller.go +++ b/controllers/flp/flp_controller.go @@ -14,6 +14,7 @@ import ( "github.com/netobserv/network-observability-operator/pkg/manager/status" "github.com/netobserv/network-observability-operator/pkg/watchers" configv1 "github.com/openshift/api/config/v1" + monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" appsv1 "k8s.io/api/apps/v1" ascv2 "k8s.io/api/autoscaling/v2" corev1 "k8s.io/api/core/v1" @@ -41,15 +42,13 @@ func Start(ctx context.Context, mgr *manager.Manager) error { r := Reconciler{ Client: mgr.Client, mgr: mgr, - status: mgr.Status.ForComponent(status.FLPParent), + status: mgr.Status.ForComponent(status.FLP), } builder := ctrl.NewControllerManagedBy(mgr). For(&flowslatest.FlowCollector{}, reconcilers.IgnoreStatusChange). Named("flp"). Owns(&appsv1.Deployment{}). - Owns(&appsv1.DaemonSet{}). Owns(&ascv2.HorizontalPodAutoscaler{}). - Owns(&corev1.Namespace{}). Owns(&corev1.Service{}). Owns(&corev1.ServiceAccount{}). Watches( @@ -71,13 +70,6 @@ func Start(ctx context.Context, mgr *manager.Manager) error { return nil } -type subReconciler interface { - context(context.Context) context.Context - cleanupNamespace(context.Context) - reconcile(context.Context, *flowslatest.FlowCollector, *metricslatest.FlowMetricList) error - getStatus() *status.Instance -} - // Reconcile is the controller entry point for reconciling current state with desired state. // It manages the controller status at a high level. Business logic is delegated into `reconcile`. func (r *Reconciler) Reconcile(ctx context.Context, _ ctrl.Request) (ctrl.Result, error) { @@ -138,21 +130,13 @@ func (r *Reconciler) reconcile(ctx context.Context, clh *helper.Client, fc *flow return r.status.Error("CantListFlowMetrics", err) } - // Create sub-reconcilers - // TODO: refactor to move these subReconciler allocations in `Start`. It will involve some decoupling work, as currently - // `reconcilers.Common` is dependent on the FlowCollector object, which isn't known at start time. - reconcilers := []subReconciler{ - newMonolithReconciler(cmn.NewInstance(r.mgr.Config.FlowlogsPipelineImage, r.mgr.Status.ForComponent(status.FLPMonolith))), - newTransformerReconciler(cmn.NewInstance(r.mgr.Config.FlowlogsPipelineImage, r.mgr.Status.ForComponent(status.FLPTransformOnly))), - } + objs := newFLPObjects(cmn.NewInstance(r.mgr.Config.FlowlogsPipelineImage, r.status)) // Check namespace changed if ns != previousNamespace { if previousNamespace != "" { log.Info("FlowCollector namespace change detected: cleaning up previous namespace", "old", previousNamespace, "new", ns) - for _, sr := range reconcilers { - sr.cleanupNamespace(sr.context(ctx)) - } + objs.cleanupNamespace(ctx) } // Update namespace in status if err := r.status.SetDeployedNamespace(ctx, r.Client, ns); err != nil { @@ -160,10 +144,8 @@ func (r *Reconciler) reconcile(ctx context.Context, clh *helper.Client, fc *flow } } - for _, sr := range reconcilers { - if err := sr.reconcile(sr.context(ctx), fc, &fm); err != nil { - return sr.getStatus().Error("FLPReconcileError", err) - } + if err := objs.reconcile(ctx, fc, &fm); err != nil { + return r.status.Error("FLPReconcileError", err) } return nil @@ -236,15 +218,53 @@ func reconcileMonitoringCerts(ctx context.Context, info *reconcilers.Common, tls return nil } -func ReconcileLokiRoles(ctx context.Context, r *reconcilers.Common, spec *flowslatest.FlowCollectorSpec, appName, saName, saNamespace string) error { - roles := loki.ClusterRoles(spec.Loki.Mode) +func reconcilePrometheusService( + ctx context.Context, + r *reconcilers.Instance, + promService *corev1.Service, + serviceMonitor *monitoringv1.ServiceMonitor, + prometheusRule *monitoringv1.PrometheusRule, + builder *Builder) error { + + report := helper.NewChangeReport("FLP prometheus service") + defer report.LogIfNeeded(ctx) + + if err := r.ReconcileService(ctx, promService, builder.promService(), &report); err != nil { + return err + } + if r.AvailableAPIs.HasSvcMonitor() { + if err := reconcilers.GenericReconcile(ctx, r.Managed, &r.Client, serviceMonitor, builder.serviceMonitor(), &report, helper.ServiceMonitorChanged); err != nil { + return err + } + } + if r.AvailableAPIs.HasPromRule() { + if err := reconcilers.GenericReconcile(ctx, r.Managed, &r.Client, prometheusRule, builder.prometheusRule(), &report, helper.PrometheusRuleChanged); err != nil { + return err + } + } + return nil +} + +func reconcileRBAC(ctx context.Context, r *reconcilers.Common, appName, saName, saNamespace string, lokiMode flowslatest.LokiMode) error { + sa, cr, crb := rbacInfo(appName, saName, saNamespace) + + if err := r.ReconcileServiceAccount(ctx, sa); err != nil { + return err + } + if err := r.ReconcileClusterRole(ctx, cr); err != nil { + return err + } + if err := r.ReconcileClusterRoleBinding(ctx, crb); err != nil { + return err + } + + roles := loki.ClusterRoles(lokiMode) if len(roles) > 0 { for i := range roles { if err := r.ReconcileClusterRole(ctx, &roles[i]); err != nil { return err } } - // Binding crb := loki.ClusterRoleBinding(appName, saName, saNamespace) if err := r.ReconcileClusterRoleBinding(ctx, crb); err != nil { return err diff --git a/controllers/flp/flp_controller_flowmetrics_test.go b/controllers/flp/flp_controller_flowmetrics_test.go index bd93b8578..fb8b890b5 100644 --- a/controllers/flp/flp_controller_flowmetrics_test.go +++ b/controllers/flp/flp_controller_flowmetrics_test.go @@ -70,23 +70,23 @@ func ControllerFlowMetricsSpecs() { }) Context("Deploying default FLP", func() { - ds := appsv1.DaemonSet{} + depl := appsv1.Deployment{} cm := v1.ConfigMap{} It("Should create successfully", func() { created := &flowslatest.FlowCollector{ ObjectMeta: metav1.ObjectMeta{Name: crKey.Name}, Spec: flowslatest.FlowCollectorSpec{ Namespace: operatorNamespace, - DeploymentModel: flowslatest.DeploymentModelDirect, + DeploymentModel: flowslatest.DeploymentModelKafka, }, } // Create Expect(k8sClient.Create(ctx, created)).Should(Succeed()) - By("Expecting to create the flowlogs-pipeline DaemonSet") + By("Expecting to create the flowlogs-pipeline Deployment") Eventually(func() error { - return k8sClient.Get(ctx, flpKey, &ds) + return k8sClient.Get(ctx, flpKey, &depl) }, timeout, interval).Should(Succeed()) By("Expecting flowlogs-pipeline-config configmap to be created") diff --git a/controllers/flp/flp_controller_test.go b/controllers/flp/flp_controller_test.go index 8aef2e59d..8d738d8c6 100644 --- a/controllers/flp/flp_controller_test.go +++ b/controllers/flp/flp_controller_test.go @@ -18,7 +18,6 @@ import ( flowslatest "github.com/netobserv/network-observability-operator/apis/flowcollector/v1beta2" "github.com/netobserv/network-observability-operator/controllers/constants" - . "github.com/netobserv/network-observability-operator/controllers/controllerstest" "github.com/netobserv/network-observability-operator/pkg/test" ) @@ -35,12 +34,25 @@ var ( updateCR = func(key types.NamespacedName, updater func(*flowslatest.FlowCollector)) { test.UpdateCR(ctx, k8sClient, key, updater) } - getCR = func(key types.NamespacedName) *flowslatest.FlowCollector { - return test.GetCR(ctx, k8sClient, key) - } cleanupCR = func(key types.NamespacedName) { test.CleanupCR(ctx, k8sClient, key) } + expectCreation = func(namespace string, objs ...test.ResourceRef) []client.Object { + GinkgoHelper() + return test.ExpectCreation(ctx, k8sClient, namespace, objs...) + } + expectDeletion = func(namespace string, objs ...test.ResourceRef) { + GinkgoHelper() + test.ExpectDeletion(ctx, k8sClient, namespace, objs...) + } + expectNoCreation = func(namespace string, objs ...test.ResourceRef) { + GinkgoHelper() + test.ExpectNoCreation(ctx, k8sClient, namespace, objs...) + } + expectOwnership = func(namespace string, objs ...test.ResourceRef) { + GinkgoHelper() + test.ExpectOwnership(ctx, k8sClient, namespace, objs...) + } ) // nolint:cyclop @@ -50,22 +62,10 @@ func ControllerSpecs() { crKey := types.NamespacedName{ Name: "cluster", } - flpKey1 := types.NamespacedName{ - Name: constants.FLPName, - Namespace: operatorNamespace, - } - flpKey2 := types.NamespacedName{ - Name: constants.FLPName, - Namespace: otherNamespace, - } - flpKeyKafkaTransformer := types.NamespacedName{ - Name: constants.FLPName + FlpConfSuffix[ConfKafkaTransformer], - Namespace: operatorNamespace, - } - rbKeyIngest := types.NamespacedName{Name: RoleBindingName(ConfKafkaIngester)} - rbKeyTransform := types.NamespacedName{Name: RoleBindingName(ConfKafkaTransformer)} - rbKeyIngestMono := types.NamespacedName{Name: RoleBindingMonoName(ConfKafkaIngester)} - rbKeyTransformMono := types.NamespacedName{Name: RoleBindingMonoName(ConfKafkaTransformer)} + deplRef := test.Deployment(constants.FLPName) + cmRef := test.ConfigMap(constants.FLPName + "-config") + saRef := test.ServiceAccount(constants.FLPName) + crbRef := test.ClusterRoleBinding(constants.FLPName) // Created objects to cleanup cleanupList := []client.Object{} @@ -78,10 +78,8 @@ func ControllerSpecs() { // Add any teardown steps that needs to be executed after each test }) - Context("Deploying as DaemonSet", func() { - var digest string - ds := appsv1.DaemonSet{} - It("Should create successfully", func() { + Context("Direct mode / direct-flp", func() { + It("Should create CR successfully", func() { created := &flowslatest.FlowCollector{ ObjectMeta: metav1.ObjectMeta{Name: crKey.Name}, Spec: flowslatest.FlowCollectorSpec{ @@ -114,65 +112,54 @@ func ControllerSpecs() { // Create Expect(k8sClient.Create(ctx, created)).Should(Succeed()) + }) - By("Expecting to create the flowlogs-pipeline DaemonSet") - Eventually(func() error { - if err := k8sClient.Get(ctx, flpKey1, &ds); err != nil { - return err - } - digest = ds.Spec.Template.Annotations[constants.PodConfigurationDigest] - if digest == "" { - return fmt.Errorf("%q annotation can't be empty", constants.PodConfigurationDigest) - } - return nil - }, timeout, interval).Should(Succeed()) + It("Should not create flowlogs-pipeline when using agent direct-flp", func() { + expectNoCreation(operatorNamespace, + deplRef, + cmRef, + test.DaemonSet(constants.FLPName), + ) + }) + }) - By("Expecting to create the flowlogs-pipeline ServiceAccount") - Eventually(func() interface{} { - svcAcc := v1.ServiceAccount{} - if err := k8sClient.Get(ctx, flpKey1, &svcAcc); err != nil { - return err + Context("With Kafka", func() { + It("Should update kafka config successfully", func() { + updateCR(crKey, func(fc *flowslatest.FlowCollector) { + fc.Spec.DeploymentModel = flowslatest.DeploymentModelKafka + fc.Spec.Kafka = flowslatest.FlowCollectorKafka{ + Address: "localhost:9092", + Topic: "FLP", + TLS: flowslatest.ClientTLS{ + CACert: flowslatest.CertificateReference{ + Type: "secret", + Name: "some-secret", + CertFile: "ca.crt", + }, + }, } - return svcAcc - }, timeout, interval).Should(Satisfy(func(svcAcc v1.ServiceAccount) bool { - return svcAcc.Labels != nil && svcAcc.Labels["app"] == constants.FLPName - })) - - By("Expecting to create two flowlogs-pipeline role binding") - rb1 := rbacv1.ClusterRoleBinding{} - Eventually(func() interface{} { - return k8sClient.Get(ctx, rbKeyIngestMono, &rb1) - }, timeout, interval).Should(Succeed()) - Expect(rb1.Subjects).Should(HaveLen(1)) - Expect(rb1.Subjects[0].Name).Should(Equal("flowlogs-pipeline")) - Expect(rb1.RoleRef.Name).Should(Equal("flowlogs-pipeline-ingester")) - - rb2 := rbacv1.ClusterRoleBinding{} - Eventually(func() interface{} { - return k8sClient.Get(ctx, rbKeyTransformMono, &rb2) - }, timeout, interval).Should(Succeed()) - Expect(rb2.Subjects).Should(HaveLen(1)) - Expect(rb2.Subjects[0].Name).Should(Equal("flowlogs-pipeline")) - Expect(rb2.RoleRef.Name).Should(Equal("flowlogs-pipeline-transformer")) - - By("Not expecting ingester role binding") - Eventually(func() interface{} { - return k8sClient.Get(ctx, rbKeyIngest, &rbacv1.ClusterRoleBinding{}) - }, timeout, interval).Should(MatchError(`clusterrolebindings.rbac.authorization.k8s.io "flowlogs-pipeline-ingester-role" not found`)) + }) + }) - By("Not expecting transformer role binding") - Eventually(func() interface{} { - return k8sClient.Get(ctx, rbKeyTransform, &rbacv1.ClusterRoleBinding{}) - }, timeout, interval).Should(MatchError(`clusterrolebindings.rbac.authorization.k8s.io "flowlogs-pipeline-transformer-role" not found`)) + var depl *appsv1.Deployment + var digest string - By("Expecting flowlogs-pipeline-config configmap to be created") - Eventually(func() interface{} { - cm := v1.ConfigMap{} - return k8sClient.Get(ctx, types.NamespacedName{ - Name: "flowlogs-pipeline-config", - Namespace: operatorNamespace, - }, &cm) - }, timeout, interval).Should(Succeed()) + It("Should deploy kafka transformer", func() { + objs := expectCreation(operatorNamespace, + deplRef, + cmRef, + saRef, + crbRef, + ) + Expect(objs).To(HaveLen(4)) + depl = objs[0].(*appsv1.Deployment) + digest = depl.Spec.Template.Annotations[constants.PodConfigurationDigest] + Expect(digest).NotTo(BeEmpty()) + + rb := objs[3].(*rbacv1.ClusterRoleBinding) + Expect(rb.Subjects).Should(HaveLen(1)) + Expect(rb.Subjects[0].Name).Should(Equal("flowlogs-pipeline")) + Expect(rb.RoleRef.Name).Should(Equal("flowlogs-pipeline")) }) It("Should update successfully", func() { @@ -208,46 +195,13 @@ func ControllerSpecs() { By("CR updated", func() { Eventually(func() error { - err := k8sClient.Get(ctx, flpKey1, &ds) + err := k8sClient.Get(ctx, deplRef.GetKey(operatorNamespace), depl) if err != nil { return err } - return checkDigestUpdate(&digest, ds.Spec.Template.Annotations) + return checkDigestUpdate(&digest, depl.Spec.Template.Annotations) }, timeout, interval).Should(Succeed()) }) - - By("Creating the required HostPort to access flowlogs-pipeline through the NodeIP", func() { - var cnt *v1.Container - for i := range ds.Spec.Template.Spec.Containers { - if ds.Spec.Template.Spec.Containers[i].Name == constants.FLPName { - cnt = &ds.Spec.Template.Spec.Containers[i] - break - } - } - Expect(cnt).ToNot(BeNil(), "can't find a container named", constants.FLPName) - var cp *v1.ContainerPort - for i := range cnt.Ports { - if cnt.Ports[i].Name == constants.FLPPortName { - cp = &cnt.Ports[i] - break - } - } - Expect(cp).ToNot(BeNil(), "can't find a container port named", constants.FLPPortName) - Expect(*cp).To(Equal(v1.ContainerPort{ - Name: constants.FLPPortName, - HostPort: 7891, - ContainerPort: 7891, - Protocol: "TCP", - })) - Expect(cnt.Env).To(Equal([]v1.EnvVar{ - {Name: "GOGC", Value: "400"}, {Name: "GOMAXPROCS", Value: "33"}, {Name: "GODEBUG", Value: "http2server=0"}, - })) - }) - - By("Allocating the proper toleration to allow its placement in the master nodes", func() { - Expect(ds.Spec.Template.Spec.Tolerations). - To(ContainElement(v1.Toleration{Operator: v1.TolerationOpExists})) - }) }) It("Should redeploy if the spec doesn't change but the external flowlogs-pipeline-config does", func() { @@ -259,75 +213,14 @@ func ControllerSpecs() { By("Expecting that the flowlogsPipeline.PodConfigurationDigest attribute has changed") Eventually(func() error { - if err := k8sClient.Get(ctx, flpKey1, &ds); err != nil { + if err := k8sClient.Get(ctx, deplRef.GetKey(operatorNamespace), depl); err != nil { return err } - return checkDigestUpdate(&digest, ds.Spec.Template.Annotations) + return checkDigestUpdate(&digest, depl.Spec.Template.Annotations) }).Should(Succeed()) }) }) - Context("With Kafka", func() { - It("Should update kafka config successfully", func() { - updateCR(crKey, func(fc *flowslatest.FlowCollector) { - fc.Spec.DeploymentModel = flowslatest.DeploymentModelKafka - fc.Spec.Kafka = flowslatest.FlowCollectorKafka{ - Address: "localhost:9092", - Topic: "FLP", - TLS: flowslatest.ClientTLS{ - CACert: flowslatest.CertificateReference{ - Type: "secret", - Name: "some-secret", - CertFile: "ca.crt", - }, - }, - } - }) - }) - - It("Should deploy kafka transformer", func() { - By("Expecting transformer deployment to be created") - Eventually(func() interface{} { - return k8sClient.Get(ctx, flpKeyKafkaTransformer, &appsv1.Deployment{}) - }, timeout, interval).Should(Succeed()) - - By("Not Expecting transformer service to be created") - Eventually(func() interface{} { - return k8sClient.Get(ctx, flpKeyKafkaTransformer, &v1.Service{}) - }, timeout, interval).Should(MatchError(`services "flowlogs-pipeline-transformer" not found`)) - - By("Expecting to create transformer flowlogs-pipeline role bindings") - Eventually(func() interface{} { - return k8sClient.Get(ctx, rbKeyIngest, &rbacv1.ClusterRoleBinding{}) - }, timeout, interval).Should(MatchError(`clusterrolebindings.rbac.authorization.k8s.io "flowlogs-pipeline-ingester-role" not found`)) - - rb2 := rbacv1.ClusterRoleBinding{} - Eventually(func() interface{} { - return k8sClient.Get(ctx, rbKeyTransform, &rb2) - }, timeout, interval).Should(Succeed()) - Expect(rb2.Subjects).Should(HaveLen(1)) - Expect(rb2.Subjects[0].Name).Should(Equal("flowlogs-pipeline-transformer")) - Expect(rb2.RoleRef.Name).Should(Equal("flowlogs-pipeline-transformer")) - - By("Not expecting mono-transformer role binding") - Eventually(func() interface{} { - return k8sClient.Get(ctx, rbKeyIngestMono, &rbacv1.ClusterRoleBinding{}) - }, timeout, interval).Should(MatchError(`clusterrolebindings.rbac.authorization.k8s.io "flowlogs-pipeline-ingester-role-mono" not found`)) - - By("Not expecting mono-ingester role binding") - Eventually(func() interface{} { - return k8sClient.Get(ctx, rbKeyTransformMono, &rbacv1.ClusterRoleBinding{}) - }, timeout, interval).Should(MatchError(`clusterrolebindings.rbac.authorization.k8s.io "flowlogs-pipeline-transformer-role-mono" not found`)) - }) - - It("Should delete previous flp deployment", func() { - By("Expecting monolith to be deleted") - Eventually(func() interface{} { - return k8sClient.Get(ctx, flpKey1, &appsv1.DaemonSet{}) - }, timeout, interval).Should(MatchError(`daemonsets.apps "flowlogs-pipeline" not found`)) - }) - }) - Context("Adding auto-scaling", func() { hpa := ascv2.HorizontalPodAutoscaler{} It("Should update with HPA", func() { @@ -353,7 +246,7 @@ func ControllerSpecs() { It("Should have HPA installed", func() { By("Expecting HPA to be created") Eventually(func() interface{} { - return k8sClient.Get(ctx, flpKeyKafkaTransformer, &hpa) + return k8sClient.Get(ctx, deplRef.GetKey(operatorNamespace), &hpa) }, timeout, interval).Should(Succeed()) Expect(*hpa.Spec.MinReplicas).To(Equal(int32(1))) Expect(hpa.Spec.MaxReplicas).To(Equal(int32(1))) @@ -368,7 +261,7 @@ func ControllerSpecs() { By("Changing the Horizontal Pod Autoscaler instance") Eventually(func() error { - if err := k8sClient.Get(ctx, flpKeyKafkaTransformer, &hpa); err != nil { + if err := k8sClient.Get(ctx, deplRef.GetKey(operatorNamespace), &hpa); err != nil { return err } if *hpa.Spec.MinReplicas != int32(2) || hpa.Spec.MaxReplicas != int32(2) || @@ -382,99 +275,47 @@ func ControllerSpecs() { }) }) - Context("Back without Kafka", func() { - It("Should remove kafka config successfully", func() { - updateCR(crKey, func(fc *flowslatest.FlowCollector) { - fc.Spec.DeploymentModel = flowslatest.DeploymentModelDirect - }) - }) - - It("Should deploy single flp again", func() { - By("Expecting daemonset to be created") - Eventually(func() interface{} { - return k8sClient.Get(ctx, flpKey1, &appsv1.DaemonSet{}) - }, timeout, interval).Should(Succeed()) - }) - - It("Should delete kafka transformer", func() { - By("Expecting transformer deployment to be deleted") - Eventually(func() interface{} { - return k8sClient.Get(ctx, flpKeyKafkaTransformer, &appsv1.Deployment{}) - }, timeout, interval).Should(MatchError(`deployments.apps "flowlogs-pipeline-transformer" not found`)) - }) - }) - Context("Checking monitoring resources", func() { It("Should create desired objects when they're not found (e.g. case of an operator upgrade)", func() { - psvc := v1.Service{} - sm := monitoringv1.ServiceMonitor{} - pr := monitoringv1.PrometheusRule{} - By("Expecting prometheus service to exist") - Eventually(func() interface{} { - return k8sClient.Get(ctx, types.NamespacedName{ - Name: "flowlogs-pipeline-prom", - Namespace: operatorNamespace, - }, &psvc) - }, timeout, interval).Should(Succeed()) - - By("Expecting ServiceMonitor to exist") - Eventually(func() interface{} { - return k8sClient.Get(ctx, types.NamespacedName{ - Name: "flowlogs-pipeline-monitor", - Namespace: operatorNamespace, - }, &sm) - }, timeout, interval).Should(Succeed()) - - By("Expecting PrometheusRule to exist and be updated") - Eventually(func() interface{} { - return k8sClient.Get(ctx, types.NamespacedName{ - Name: "flowlogs-pipeline-alert", - Namespace: operatorNamespace, - }, &pr) - }, timeout, interval).Should(Succeed()) + objs := expectCreation(operatorNamespace, + test.Service("flowlogs-pipeline-prom"), + test.ServiceMonitor("flowlogs-pipeline-monitor"), + test.PrometheusRule("flowlogs-pipeline-alert"), + ) + Expect(objs).To(HaveLen(3)) + sm := objs[1].(*monitoringv1.ServiceMonitor) + pr := objs[2].(*monitoringv1.PrometheusRule) Expect(pr.Spec.Groups).Should(HaveLen(1)) Expect(pr.Spec.Groups[0].Rules).Should(HaveLen(1)) // Manually delete ServiceMonitor By("Deleting ServiceMonitor") - Eventually(func() error { - return k8sClient.Delete(ctx, &sm) - }, timeout, interval).Should(Succeed()) + Eventually(func() error { return k8sClient.Delete(ctx, sm) }, timeout, interval).Should(Succeed()) // Do a dummy change that will trigger reconcile, and make sure SM is created again updateCR(crKey, func(fc *flowslatest.FlowCollector) { fc.Spec.Processor.LogLevel = "trace" }) + By("Expecting ServiceMonitor to exist") - Eventually(func() interface{} { - return k8sClient.Get(ctx, types.NamespacedName{ - Name: "flowlogs-pipeline-monitor", - Namespace: operatorNamespace, - }, &sm) - }, timeout, interval).Should(Succeed()) + expectCreation(operatorNamespace, test.ServiceMonitor("flowlogs-pipeline-monitor")) // Manually delete Rule By("Deleting prom rule") - Eventually(func() error { - return k8sClient.Delete(ctx, &pr) - }, timeout, interval).Should(Succeed()) + Eventually(func() error { return k8sClient.Delete(ctx, pr) }, timeout, interval).Should(Succeed()) // Do a dummy change that will trigger reconcile, and make sure Rule is created again updateCR(crKey, func(fc *flowslatest.FlowCollector) { fc.Spec.Processor.LogLevel = "debug" }) By("Expecting PrometheusRule to exist") - Eventually(func() interface{} { - return k8sClient.Get(ctx, types.NamespacedName{ - Name: "flowlogs-pipeline-alert", - Namespace: operatorNamespace, - }, &pr) - }, timeout, interval).Should(Succeed()) + expectCreation(operatorNamespace, test.PrometheusRule("flowlogs-pipeline-alert")) }) }) Context("Using certificates with loki manual mode", func() { - flpDS := appsv1.DaemonSet{} + flpKey := deplRef.GetKey(operatorNamespace) + depl := appsv1.Deployment{} It("Should update Loki to use TLS", func() { // Create CM certificate cm := &v1.ConfigMap{ @@ -502,13 +343,13 @@ func ControllerSpecs() { It("Should have certificate mounted", func() { By("Expecting certificate mounted") Eventually(func() interface{} { - if err := k8sClient.Get(ctx, flpKey1, &flpDS); err != nil { + if err := k8sClient.Get(ctx, flpKey, &depl); err != nil { return err } - return flpDS.Spec.Template.Spec.Volumes + return depl.Spec.Template.Spec.Volumes }, timeout, interval).Should(HaveLen(2)) - Expect(flpDS.Spec.Template.Spec.Volumes[0].Name).To(Equal("config-volume")) - Expect(flpDS.Spec.Template.Spec.Volumes[1].Name).To(Equal("loki-certs-ca")) + Expect(depl.Spec.Template.Spec.Volumes[0].Name).To(Equal("config-volume")) + Expect(depl.Spec.Template.Spec.Volumes[1].Name).To(Equal("loki-certs-ca")) }) It("Should restore no TLS config", func() { @@ -518,17 +359,18 @@ func ControllerSpecs() { } }) Eventually(func() interface{} { - if err := k8sClient.Get(ctx, flpKey1, &flpDS); err != nil { + if err := k8sClient.Get(ctx, flpKey, &depl); err != nil { return err } - return flpDS.Spec.Template.Spec.Volumes + return depl.Spec.Template.Spec.Volumes }, timeout, interval).Should(HaveLen(1)) - Expect(flpDS.Spec.Template.Spec.Volumes[0].Name).To(Equal("config-volume")) + Expect(depl.Spec.Template.Spec.Volumes[0].Name).To(Equal("config-volume")) }) }) Context("Using certificates with loki distributed mode", func() { - flpDS := appsv1.DaemonSet{} + flpKey := deplRef.GetKey(operatorNamespace) + depl := appsv1.Deployment{} It("Should update Loki to use TLS", func() { // Create CM certificate cm := &v1.ConfigMap{ @@ -560,13 +402,13 @@ func ControllerSpecs() { It("Should have certificate mounted", func() { By("Expecting certificate mounted") Eventually(func() interface{} { - if err := k8sClient.Get(ctx, flpKey1, &flpDS); err != nil { + if err := k8sClient.Get(ctx, flpKey, &depl); err != nil { return err } - return flpDS.Spec.Template.Spec.Volumes + return depl.Spec.Template.Spec.Volumes }, timeout, interval).Should(HaveLen(2)) - Expect(flpDS.Spec.Template.Spec.Volumes[0].Name).To(Equal("config-volume")) - Expect(flpDS.Spec.Template.Spec.Volumes[1].Name).To(Equal("loki-certs-ca")) + Expect(depl.Spec.Template.Spec.Volumes[0].Name).To(Equal("config-volume")) + Expect(depl.Spec.Template.Spec.Volumes[1].Name).To(Equal("loki-certs-ca")) }) It("Should restore no TLS config", func() { @@ -576,17 +418,18 @@ func ControllerSpecs() { } }) Eventually(func() interface{} { - if err := k8sClient.Get(ctx, flpKey1, &flpDS); err != nil { + if err := k8sClient.Get(ctx, flpKey, &depl); err != nil { return err } - return flpDS.Spec.Template.Spec.Volumes + return depl.Spec.Template.Spec.Volumes }, timeout, interval).Should(HaveLen(1)) - Expect(flpDS.Spec.Template.Spec.Volumes[0].Name).To(Equal("config-volume")) + Expect(depl.Spec.Template.Spec.Volumes[0].Name).To(Equal("config-volume")) }) }) Context("Using certificates with loki monolithic mode", func() { - flpDS := appsv1.DaemonSet{} + flpKey := deplRef.GetKey(operatorNamespace) + depl := appsv1.Deployment{} It("Should update Loki to use TLS", func() { // Create CM certificate cm := &v1.ConfigMap{ @@ -617,13 +460,13 @@ func ControllerSpecs() { It("Should have certificate mounted", func() { By("Expecting certificate mounted") Eventually(func() interface{} { - if err := k8sClient.Get(ctx, flpKey1, &flpDS); err != nil { + if err := k8sClient.Get(ctx, flpKey, &depl); err != nil { return err } - return flpDS.Spec.Template.Spec.Volumes + return depl.Spec.Template.Spec.Volumes }, timeout, interval).Should(HaveLen(2)) - Expect(flpDS.Spec.Template.Spec.Volumes[0].Name).To(Equal("config-volume")) - Expect(flpDS.Spec.Template.Spec.Volumes[1].Name).To(Equal("loki-certs-ca")) + Expect(depl.Spec.Template.Spec.Volumes[0].Name).To(Equal("config-volume")) + Expect(depl.Spec.Template.Spec.Volumes[1].Name).To(Equal("loki-certs-ca")) }) It("Should restore no TLS config", func() { @@ -633,17 +476,18 @@ func ControllerSpecs() { } }) Eventually(func() interface{} { - if err := k8sClient.Get(ctx, flpKey1, &flpDS); err != nil { + if err := k8sClient.Get(ctx, flpKey, &depl); err != nil { return err } - return flpDS.Spec.Template.Spec.Volumes + return depl.Spec.Template.Spec.Volumes }, timeout, interval).Should(HaveLen(1)) - Expect(flpDS.Spec.Template.Spec.Volumes[0].Name).To(Equal("config-volume")) + Expect(depl.Spec.Template.Spec.Volumes[0].Name).To(Equal("config-volume")) }) }) Context("Using Certificates With Loki in LokiStack Mode", func() { - flpDS := appsv1.DaemonSet{} + flpKey := deplRef.GetKey(operatorNamespace) + depl := appsv1.Deployment{} It("Should update Loki config successfully", func() { // Create CM certificate cm := &v1.ConfigMap{ @@ -667,14 +511,14 @@ func ControllerSpecs() { It("Should have certificate mounted", func() { By("Expecting certificate mounted") Eventually(func() interface{} { - if err := k8sClient.Get(ctx, flpKey1, &flpDS); err != nil { + if err := k8sClient.Get(ctx, flpKey, &depl); err != nil { return err } - return flpDS.Spec.Template.Spec.Volumes + return depl.Spec.Template.Spec.Volumes }, timeout, interval).Should(HaveLen(3)) - Expect(flpDS.Spec.Template.Spec.Volumes[0].Name).To(Equal("config-volume")) - Expect(flpDS.Spec.Template.Spec.Volumes[1].Name).To(Equal("flowlogs-pipeline")) - Expect(flpDS.Spec.Template.Spec.Volumes[2].Name).To(Equal("loki-certs-ca")) + Expect(depl.Spec.Template.Spec.Volumes[0].Name).To(Equal("config-volume")) + Expect(depl.Spec.Template.Spec.Volumes[1].Name).To(Equal("flowlogs-pipeline")) + Expect(depl.Spec.Template.Spec.Volumes[2].Name).To(Equal("loki-certs-ca")) }) It("Should deploy Loki roles", func() { @@ -703,80 +547,66 @@ func ControllerSpecs() { } }) Eventually(func() interface{} { - if err := k8sClient.Get(ctx, flpKey1, &flpDS); err != nil { + if err := k8sClient.Get(ctx, flpKey, &depl); err != nil { return err } - return flpDS.Spec.Template.Spec.Volumes + return depl.Spec.Template.Spec.Volumes }, timeout, interval).Should(HaveLen(1)) - Expect(flpDS.Spec.Template.Spec.Volumes[0].Name).To(Equal("config-volume")) + Expect(depl.Spec.Template.Spec.Volumes[0].Name).To(Equal("config-volume")) }) }) Context("Changing namespace", func() { It("Should update namespace successfully", func() { updateCR(crKey, func(fc *flowslatest.FlowCollector) { - fc.Spec.Processor.Advanced.Port = ptr.To(int32(9999)) fc.Spec.Namespace = otherNamespace }) }) It("Should redeploy FLP in new namespace", func() { - By("Expecting daemonset in previous namespace to be deleted") - Eventually(func() interface{} { - return k8sClient.Get(ctx, flpKey1, &appsv1.DaemonSet{}) - }, timeout, interval).Should(MatchError(`daemonsets.apps "flowlogs-pipeline" not found`)) - - By("Expecting deployment in previous namespace to be deleted") - Eventually(func() interface{} { - return k8sClient.Get(ctx, flpKey1, &appsv1.Deployment{}) - }, timeout, interval).Should(MatchError(`deployments.apps "flowlogs-pipeline" not found`)) - - By("Expecting service account in previous namespace to be deleted") - Eventually(func() interface{} { - return k8sClient.Get(ctx, flpKey1, &v1.ServiceAccount{}) - }, timeout, interval).Should(MatchError(`serviceaccounts "flowlogs-pipeline" not found`)) - - By("Expecting daemonset to be created in new namespace") - Eventually(func() interface{} { - return k8sClient.Get(ctx, flpKey2, &appsv1.DaemonSet{}) - }, timeout, interval).Should(Succeed()) - - By("Expecting service account to be created in new namespace") - Eventually(func() interface{} { - return k8sClient.Get(ctx, flpKey2, &v1.ServiceAccount{}) - }, timeout, interval).Should(Succeed()) + By("Expecting resources in previous namespace to be deleted") + expectDeletion(operatorNamespace, + deplRef, + cmRef, + saRef, + ) + + objs := expectCreation(otherNamespace, + deplRef, + cmRef, + saRef, + crbRef, + ) + Expect(objs).To(HaveLen(4)) + crb := objs[3].(*rbacv1.ClusterRoleBinding) + Expect(crb.Subjects).To(HaveLen(1)) + Expect(crb.Subjects[0].Namespace).To(Equal(otherNamespace)) }) }) Context("Checking CR ownership", func() { It("Should be garbage collected", func() { - // Retrieve CR to get its UID - By("Getting the CR") - flowCR := getCR(crKey) - - By("Expecting flowlogs-pipeline daemonset to be garbage collected") - Eventually(func() interface{} { - d := appsv1.DaemonSet{} - _ = k8sClient.Get(ctx, flpKey2, &d) - return &d - }, timeout, interval).Should(BeGarbageCollectedBy(flowCR)) + expectOwnership(otherNamespace, + deplRef, + cmRef, + saRef, + ) + }) + }) - By("Expecting flowlogs-pipeline service account to be garbage collected") - Eventually(func() interface{} { - svcAcc := v1.ServiceAccount{} - _ = k8sClient.Get(ctx, flpKey2, &svcAcc) - return &svcAcc - }, timeout, interval).Should(BeGarbageCollectedBy(flowCR)) + Context("Back without Kafka", func() { + It("Should remove kafka config successfully", func() { + updateCR(crKey, func(fc *flowslatest.FlowCollector) { + fc.Spec.DeploymentModel = flowslatest.DeploymentModelDirect + }) + }) - By("Expecting flowlogs-pipeline configmap to be garbage collected") - Eventually(func() interface{} { - cm := v1.ConfigMap{} - _ = k8sClient.Get(ctx, types.NamespacedName{ - Name: "flowlogs-pipeline-config", - Namespace: otherNamespace, - }, &cm) - return &cm - }, timeout, interval).Should(BeGarbageCollectedBy(flowCR)) + It("Should delete kafka transformer", func() { + expectDeletion(otherNamespace, + deplRef, + cmRef, + saRef, + ) }) }) diff --git a/controllers/flp/flp_monolith_objects.go b/controllers/flp/flp_monolith_objects.go deleted file mode 100644 index 6c6311e8e..000000000 --- a/controllers/flp/flp_monolith_objects.go +++ /dev/null @@ -1,61 +0,0 @@ -package flp - -import ( - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - flowslatest "github.com/netobserv/network-observability-operator/apis/flowcollector/v1beta2" - metricslatest "github.com/netobserv/network-observability-operator/apis/flowmetrics/v1alpha1" - "github.com/netobserv/network-observability-operator/controllers/reconcilers" -) - -type monolithBuilder struct { - generic builder -} - -func newMonolithBuilder(info *reconcilers.Instance, desired *flowslatest.FlowCollectorSpec, flowMetrics *metricslatest.FlowMetricList) (monolithBuilder, error) { - gen, err := NewBuilder(info, desired, flowMetrics, ConfMonolith) - return monolithBuilder{ - generic: gen, - }, err -} - -func (b *monolithBuilder) daemonSet(annotations map[string]string) *appsv1.DaemonSet { - pod := b.generic.podTemplate(true /*listens*/, !b.generic.info.UseOpenShiftSCC, annotations) - return &appsv1.DaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: b.generic.name(), - Namespace: b.generic.info.Namespace, - Labels: b.generic.labels, - }, - Spec: appsv1.DaemonSetSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: b.generic.selector, - }, - Template: pod, - }, - } -} - -func (b *monolithBuilder) configMap() (*corev1.ConfigMap, string, error) { - pipeline := b.generic.NewGRPCPipeline() - err := pipeline.AddProcessorStages() - if err != nil { - return nil, "", err - } - return b.generic.ConfigMap() -} - -func (b *monolithBuilder) promService() *corev1.Service { - return b.generic.promService() -} - -func (b *monolithBuilder) serviceAccount() *corev1.ServiceAccount { - return b.generic.serviceAccount() -} - -func (b *monolithBuilder) clusterRoleBinding(ck ConfKind) *rbacv1.ClusterRoleBinding { - return b.generic.clusterRoleBinding(ck, true) -} diff --git a/controllers/flp/flp_monolith_reconciler.go b/controllers/flp/flp_monolith_reconciler.go deleted file mode 100644 index 7530b0926..000000000 --- a/controllers/flp/flp_monolith_reconciler.go +++ /dev/null @@ -1,199 +0,0 @@ -package flp - -import ( - "context" - - monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/api/equality" - "sigs.k8s.io/controller-runtime/pkg/log" - - flowslatest "github.com/netobserv/network-observability-operator/apis/flowcollector/v1beta2" - metricslatest "github.com/netobserv/network-observability-operator/apis/flowmetrics/v1alpha1" - "github.com/netobserv/network-observability-operator/controllers/constants" - "github.com/netobserv/network-observability-operator/controllers/reconcilers" - "github.com/netobserv/network-observability-operator/pkg/helper" - "github.com/netobserv/network-observability-operator/pkg/manager/status" -) - -type monolithReconciler struct { - *reconcilers.Instance - daemonSet *appsv1.DaemonSet - promService *corev1.Service - serviceAccount *corev1.ServiceAccount - configMap *corev1.ConfigMap - roleBindingIn *rbacv1.ClusterRoleBinding - roleBindingTr *rbacv1.ClusterRoleBinding - serviceMonitor *monitoringv1.ServiceMonitor - prometheusRule *monitoringv1.PrometheusRule -} - -func newMonolithReconciler(cmn *reconcilers.Instance) *monolithReconciler { - name := name(ConfMonolith) - rec := monolithReconciler{ - Instance: cmn, - daemonSet: cmn.Managed.NewDaemonSet(name), - promService: cmn.Managed.NewService(promServiceName(ConfMonolith)), - serviceAccount: cmn.Managed.NewServiceAccount(name), - configMap: cmn.Managed.NewConfigMap(configMapName(ConfMonolith)), - roleBindingIn: cmn.Managed.NewCRB(RoleBindingMonoName(ConfKafkaIngester)), - roleBindingTr: cmn.Managed.NewCRB(RoleBindingMonoName(ConfKafkaTransformer)), - } - if cmn.AvailableAPIs.HasSvcMonitor() { - rec.serviceMonitor = cmn.Managed.NewServiceMonitor(serviceMonitorName(ConfMonolith)) - } - if cmn.AvailableAPIs.HasPromRule() { - rec.prometheusRule = cmn.Managed.NewPrometheusRule(prometheusRuleName(ConfMonolith)) - } - return &rec -} - -func (r *monolithReconciler) context(ctx context.Context) context.Context { - l := log.FromContext(ctx).WithName("monolith") - return log.IntoContext(ctx, l) -} - -// cleanupNamespace cleans up old namespace -func (r *monolithReconciler) cleanupNamespace(ctx context.Context) { - r.Managed.CleanupPreviousNamespace(ctx) -} - -func (r *monolithReconciler) getStatus() *status.Instance { - return &r.Status -} - -func (r *monolithReconciler) reconcile(ctx context.Context, desired *flowslatest.FlowCollector, flowMetrics *metricslatest.FlowMetricList) error { - // Retrieve current owned objects - err := r.Managed.FetchAll(ctx) - if err != nil { - return err - } - - if helper.UseKafka(&desired.Spec) || helper.UseMergedAgentFLP(&desired.Spec) { - r.Status.SetUnused("Monolith only used with IPFIX and without Kafka") - r.Managed.TryDeleteAll(ctx) - return nil - } - - r.Status.SetReady() // will be overidden if necessary, as error or pending - - builder, err := newMonolithBuilder(r.Instance, &desired.Spec, flowMetrics) - if err != nil { - return err - } - newCM, configDigest, err := builder.configMap() - if err != nil { - return err - } - annotations := map[string]string{ - constants.PodConfigurationDigest: configDigest, - } - if !r.Managed.Exists(r.configMap) { - if err := r.CreateOwned(ctx, newCM); err != nil { - return err - } - } else if !equality.Semantic.DeepDerivative(newCM.Data, r.configMap.Data) { - if err := r.UpdateIfOwned(ctx, r.configMap, newCM); err != nil { - return err - } - } - - if err := r.reconcilePermissions(ctx, &builder); err != nil { - return err - } - - err = r.reconcilePrometheusService(ctx, &builder) - if err != nil { - return err - } - - err = r.reconcileCertificates(ctx, desired, annotations) - if err != nil { - return err - } - - return r.reconcileDaemonSet(ctx, builder.daemonSet(annotations)) -} - -func (r *monolithReconciler) reconcileCertificates(ctx context.Context, desired *flowslatest.FlowCollector, annotations map[string]string) error { - // Watch for Loki certificate if necessary; we'll ignore in that case the returned digest, as we don't need to restart pods on cert rotation - // because certificate is always reloaded from file - if _, err := r.Watcher.ProcessCACert(ctx, r.Client, &r.Loki.TLS, r.Namespace); err != nil { - return err - } - // Watch for Kafka exporter certificate if necessary; need to restart pods in case of cert rotation - if err := annotateKafkaExporterCerts(ctx, r.Common, desired.Spec.Exporters, annotations); err != nil { - return err - } - // Watch for monitoring caCert - return reconcileMonitoringCerts(ctx, r.Common, &desired.Spec.Processor.Metrics.Server.TLS, r.Namespace) -} - -func (r *monolithReconciler) reconcilePrometheusService(ctx context.Context, builder *monolithBuilder) error { - report := helper.NewChangeReport("FLP prometheus service") - defer report.LogIfNeeded(ctx) - - if err := r.ReconcileService(ctx, r.promService, builder.promService(), &report); err != nil { - return err - } - if r.AvailableAPIs.HasSvcMonitor() { - serviceMonitor := builder.generic.serviceMonitor() - if err := reconcilers.GenericReconcile(ctx, r.Managed, &r.Client, r.serviceMonitor, serviceMonitor, &report, helper.ServiceMonitorChanged); err != nil { - return err - } - } - if r.AvailableAPIs.HasPromRule() { - promRules := builder.generic.prometheusRule() - if err := reconcilers.GenericReconcile(ctx, r.Managed, &r.Client, r.prometheusRule, promRules, &report, helper.PrometheusRuleChanged); err != nil { - return err - } - } - return nil -} - -func (r *monolithReconciler) reconcileDaemonSet(ctx context.Context, desiredDS *appsv1.DaemonSet) error { - report := helper.NewChangeReport("FLP DaemonSet") - defer report.LogIfNeeded(ctx) - - return reconcilers.ReconcileDaemonSet( - ctx, - r.Instance, - r.daemonSet, - desiredDS, - constants.FLPName, - &report, - ) -} - -func (r *monolithReconciler) reconcilePermissions(ctx context.Context, builder *monolithBuilder) error { - if !r.Managed.Exists(r.serviceAccount) { - return r.CreateOwned(ctx, builder.serviceAccount()) - } // We only configure name, update is not needed for now - - cr := buildClusterRoleIngester(r.UseOpenShiftSCC) - if err := r.ReconcileClusterRole(ctx, cr); err != nil { - return err - } - cr = BuildClusterRoleTransformer() - if err := r.ReconcileClusterRole(ctx, cr); err != nil { - return err - } - // Monolith uses ingester + transformer cluster roles - for _, kind := range []ConfKind{ConfKafkaIngester, ConfKafkaTransformer} { - desired := builder.clusterRoleBinding(kind) - if err := r.ReconcileClusterRoleBinding(ctx, desired); err != nil { - return err - } - } - - return ReconcileLokiRoles( - ctx, - r.Common, - builder.generic.desired, - builder.generic.name(), - builder.generic.name(), - r.Common.Namespace, - ) -} diff --git a/controllers/flp/flp_common_objects.go b/controllers/flp/flp_objects.go similarity index 76% rename from controllers/flp/flp_common_objects.go rename to controllers/flp/flp_objects.go index c8a73bd23..7c175b289 100644 --- a/controllers/flp/flp_common_objects.go +++ b/controllers/flp/flp_objects.go @@ -9,6 +9,8 @@ import ( "github.com/netobserv/flowlogs-pipeline/pkg/api" "github.com/netobserv/flowlogs-pipeline/pkg/config" monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" + appsv1 "k8s.io/api/apps/v1" + ascv2 "k8s.io/api/autoscaling/v2" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -36,28 +38,14 @@ const ( appLabel = "app" ) -type ConfKind string - -const ( - ConfMonolith ConfKind = "allInOne" - ConfKafkaIngester ConfKind = "kafkaIngester" - ConfKafkaTransformer ConfKind = "kafkaTransformer" -) - -var FlpConfSuffix = map[ConfKind]string{ - ConfMonolith: "", - ConfKafkaIngester: "-ingester", - ConfKafkaTransformer: "-transformer", -} - type Builder struct { info *reconcilers.Instance + appName string labels map[string]string selector map[string]string desired *flowslatest.FlowCollectorSpec flowMetrics *metricslatest.FlowMetricList promTLS *flowslatest.CertificateReference - confKind ConfKind volumes volumes.Builder loki *helper.LokiConfig pipeline *PipelineBuilder @@ -65,9 +53,33 @@ type Builder struct { type builder = Builder -func NewBuilder(info *reconcilers.Instance, desired *flowslatest.FlowCollectorSpec, flowMetrics *metricslatest.FlowMetricList, ck ConfKind) (Builder, error) { +func newInProcessBuilder(info *reconcilers.Instance, appName string, desired *flowslatest.FlowCollectorSpec, flowMetrics *metricslatest.FlowMetricList) (*Builder, error) { + b, err := newBuilder(info, appName, desired, flowMetrics) + if err != nil { + return nil, err + } + + pipeline := b.createInProcessPipeline() + if err = pipeline.AddProcessorStages(); err != nil { + return nil, err + } + return &b, nil +} + +func newKafkaConsumerBuilder(info *reconcilers.Instance, desired *flowslatest.FlowCollectorSpec, flowMetrics *metricslatest.FlowMetricList) (*Builder, error) { + b, err := newBuilder(info, constants.FLPName, desired, flowMetrics) + if err != nil { + return nil, err + } + pipeline := b.createKafkaPipeline() + if err = pipeline.AddProcessorStages(); err != nil { + return nil, err + } + return &b, nil +} + +func newBuilder(info *reconcilers.Instance, appName string, desired *flowslatest.FlowCollectorSpec, flowMetrics *metricslatest.FlowMetricList) (Builder, error) { version := helper.ExtractVersion(info.Image) - name := name(ck) var promTLS *flowslatest.CertificateReference switch desired.Processor.Metrics.Server.TLS.Type { case flowslatest.ServerTLSProvided: @@ -78,7 +90,7 @@ func NewBuilder(info *reconcilers.Instance, desired *flowslatest.FlowCollectorSp case flowslatest.ServerTLSAuto: promTLS = &flowslatest.CertificateReference{ Type: "secret", - Name: promServiceName(ck), + Name: appName, CertFile: "tls.crt", CertKey: "tls.key", } @@ -86,48 +98,39 @@ func NewBuilder(info *reconcilers.Instance, desired *flowslatest.FlowCollectorSp // nothing to do there } return builder{ - info: info, + info: info, + appName: appName, labels: map[string]string{ - appLabel: name, + appLabel: appName, "version": helper.MaxLabelLength(version), }, selector: map[string]string{ - appLabel: name, + appLabel: appName, }, desired: desired, flowMetrics: flowMetrics, - confKind: ck, promTLS: promTLS, loki: info.Loki, }, nil } -func name(ck ConfKind) string { return constants.FLPName + FlpConfSuffix[ck] } -func RoleBindingName(ck ConfKind) string { return name(ck) + "-role" } -func RoleBindingMonoName(ck ConfKind) string { return name(ck) + "-role-mono" } -func promServiceName(ck ConfKind) string { return name(ck) + "-prom" } -func configMapName(ck ConfKind) string { return name(ck) + "-config" } -func serviceMonitorName(ck ConfKind) string { return name(ck) + "-monitor" } -func prometheusRuleName(ck ConfKind) string { return name(ck) + "-alert" } -func (b *builder) name() string { return name(b.confKind) } -func (b *builder) promServiceName() string { return promServiceName(b.confKind) } -func (b *builder) configMapName() string { return configMapName(b.confKind) } -func (b *builder) serviceMonitorName() string { return serviceMonitorName(b.confKind) } -func (b *builder) prometheusRuleName() string { return prometheusRuleName(b.confKind) } -func (b *builder) Pipeline() *PipelineBuilder { return b.pipeline } +func promServiceName(appName string) string { return appName + "-prom" } +func configMapName(appName string) string { return appName + "-config" } +func serviceMonitorName(appName string) string { return appName + "-monitor" } +func prometheusRuleName(appName string) string { return appName + "-alert" } +func (b *builder) promServiceName() string { return promServiceName(b.appName) } +func (b *builder) configMapName() string { return configMapName(b.appName) } +func (b *builder) serviceMonitorName() string { return serviceMonitorName(b.appName) } +func (b *builder) prometheusRuleName() string { return prometheusRuleName(b.appName) } -func (b *builder) NewGRPCPipeline() PipelineBuilder { - return b.initPipeline(config.NewGRPCPipeline("grpc", api.IngestGRPCProto{ - Port: int(*helper.GetAdvancedProcessorConfig(b.desired.Processor.Advanced).Port), - })) -} +func (b *builder) Pipeline() *PipelineBuilder { return b.pipeline } -func (b *builder) NewKafkaPipeline() PipelineBuilder { +func (b *builder) createKafkaPipeline() PipelineBuilder { decoder := api.Decoder{Type: "protobuf"} return b.initPipeline(config.NewKafkaPipeline("kafka-read", api.IngestKafka{ Brokers: []string{b.desired.Kafka.Address}, Topic: b.desired.Kafka.Topic, - GroupId: b.name(), // Without groupid, each message is delivered to each consumers + GroupId: b.appName, // Without groupid, each message is delivered to each consumers Decoder: decoder, TLS: getKafkaTLS(&b.desired.Kafka.TLS, "kafka-cert", &b.volumes), SASL: getKafkaSASL(&b.desired.Kafka.SASL, "kafka-ingest", &b.volumes), @@ -136,7 +139,7 @@ func (b *builder) NewKafkaPipeline() PipelineBuilder { })) } -func (b *builder) NewInProcessPipeline() PipelineBuilder { +func (b *builder) createInProcessPipeline() PipelineBuilder { return b.initPipeline(config.NewPresetIngesterPipeline()) } @@ -146,26 +149,9 @@ func (b *builder) initPipeline(ingest config.PipelineBuilderStage) PipelineBuild return pipeline } -func (b *builder) overrideApp(app string) { - b.labels[appLabel] = app - b.selector[appLabel] = app -} - -func (b *builder) podTemplate(hasHostPort, hostNetwork bool, annotations map[string]string) corev1.PodTemplateSpec { +func (b *builder) podTemplate(annotations map[string]string) corev1.PodTemplateSpec { debugConfig := helper.GetAdvancedProcessorConfig(b.desired.Processor.Advanced) var ports []corev1.ContainerPort - var tolerations []corev1.Toleration - if hasHostPort { - ports = []corev1.ContainerPort{{ - Name: constants.FLPPortName, - HostPort: *debugConfig.Port, - ContainerPort: *debugConfig.Port, - Protocol: corev1.ProtocolTCP, - }} - // This allows deploying an instance in the master node, the same technique used in the - // companion ovnkube-node daemonset definition - tolerations = []corev1.Toleration{{Operator: corev1.TolerationOpExists}} - } ports = append(ports, corev1.ContainerPort{ Name: healthServiceName, @@ -243,10 +229,6 @@ func (b *builder) podTemplate(hasHostPort, hostNetwork bool, annotations map[str FailureThreshold: startupFailureThreshold, } } - dnsPolicy := corev1.DNSClusterFirst - if hostNetwork { - dnsPolicy = corev1.DNSClusterFirstWithHostNet - } annotations["prometheus.io/scrape"] = "true" annotations["prometheus.io/scrape_port"] = fmt.Sprint(b.desired.Processor.Metrics.Server.Port) @@ -256,19 +238,17 @@ func (b *builder) podTemplate(hasHostPort, hostNetwork bool, annotations map[str Annotations: annotations, }, Spec: corev1.PodSpec{ - Tolerations: tolerations, Volumes: volumes, Containers: []corev1.Container{container}, - ServiceAccountName: b.name(), - HostNetwork: hostNetwork, - DNSPolicy: dnsPolicy, + ServiceAccountName: b.appName, + DNSPolicy: corev1.DNSClusterFirst, }, } } // returns a configmap with a digest of its configuration contents, which will be used to // detect any configuration change -func (b *builder) ConfigMap() (*corev1.ConfigMap, string, error) { +func (b *builder) configMap() (*corev1.ConfigMap, string, error) { configStr, err := b.GetJSONConfig() if err != nil { return nil, "", err @@ -358,45 +338,6 @@ func (b *builder) promService() *corev1.Service { return &svc } -func (b *builder) serviceAccount() *corev1.ServiceAccount { - return &corev1.ServiceAccount{ - ObjectMeta: metav1.ObjectMeta{ - Name: b.name(), - Namespace: b.info.Namespace, - Labels: map[string]string{ - appLabel: b.name(), - }, - }, - } -} - -func (b *builder) clusterRoleBinding(ck ConfKind, mono bool) *rbacv1.ClusterRoleBinding { - var rbName string - if mono { - rbName = RoleBindingMonoName(ck) - } else { - rbName = RoleBindingName(ck) - } - return &rbacv1.ClusterRoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: rbName, - Labels: map[string]string{ - appLabel: b.name(), - }, - }, - RoleRef: rbacv1.RoleRef{ - APIGroup: "rbac.authorization.k8s.io", - Kind: "ClusterRole", - Name: name(ck), - }, - Subjects: []rbacv1.Subject{{ - Kind: "ServiceAccount", - Name: b.name(), - Namespace: b.info.Namespace, - }}, - } -} - func (b *builder) serviceMonitor() *monitoringv1.ServiceMonitor { serverName := fmt.Sprintf("%s.%s.svc", b.promServiceName(), b.info.Namespace) flpServiceMonitorObject := monitoringv1.ServiceMonitor{ @@ -516,20 +457,89 @@ func (b *builder) prometheusRule() *monitoringv1.PrometheusRule { return &flpPrometheusRuleObject } -func buildClusterRoleIngester(useOpenShiftSCC bool) *rbacv1.ClusterRole { +func (b *Builder) deployment(annotations map[string]string) *appsv1.Deployment { + pod := b.podTemplate(annotations) + return &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: b.appName, + Namespace: b.info.Namespace, + Labels: b.labels, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: b.desired.Processor.KafkaConsumerReplicas, + Selector: &metav1.LabelSelector{ + MatchLabels: b.selector, + }, + Template: pod, + }, + } +} + +func (b *Builder) autoScaler() *ascv2.HorizontalPodAutoscaler { + return &ascv2.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: b.appName, + Namespace: b.info.Namespace, + Labels: b.labels, + }, + Spec: ascv2.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: ascv2.CrossVersionObjectReference{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: b.appName, + }, + MinReplicas: b.desired.Processor.KafkaConsumerAutoscaler.MinReplicas, + MaxReplicas: b.desired.Processor.KafkaConsumerAutoscaler.MaxReplicas, + Metrics: b.desired.Processor.KafkaConsumerAutoscaler.Metrics, + }, + } +} + +// The operator needs to have at least the same permissions as flowlogs-pipeline in order to grant them +//+kubebuilder:rbac:groups=apps,resources=replicasets,verbs=get;list;watch +//+kubebuilder:rbac:groups=core,resources=pods;services;nodes,verbs=get;list;watch + +func rbacInfo(appName, saName, saNamespace string) (*corev1.ServiceAccount, *rbacv1.ClusterRole, *rbacv1.ClusterRoleBinding) { + sa := corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: saName, + Namespace: saNamespace, + Labels: map[string]string{ + appLabel: appName, + }, + }, + } cr := rbacv1.ClusterRole{ ObjectMeta: metav1.ObjectMeta{ - Name: name(ConfKafkaIngester), + Name: constants.FLPName, }, - Rules: []rbacv1.PolicyRule{}, + Rules: []rbacv1.PolicyRule{{ + APIGroups: []string{""}, + Verbs: []string{"list", "get", "watch"}, + Resources: []string{"pods", "services", "nodes"}, + }, { + APIGroups: []string{"apps"}, + Verbs: []string{"list", "get", "watch"}, + Resources: []string{"replicasets"}, + }}, } - if useOpenShiftSCC { - cr.Rules = append(cr.Rules, rbacv1.PolicyRule{ - APIGroups: []string{"security.openshift.io"}, - Verbs: []string{"use"}, - Resources: []string{"securitycontextconstraints"}, - ResourceNames: []string{"hostnetwork"}, - }) + crb := rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: constants.FLPName, + Labels: map[string]string{ + appLabel: appName, + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "ClusterRole", + Name: cr.Name, + }, + Subjects: []rbacv1.Subject{{ + Kind: "ServiceAccount", + Name: saName, + Namespace: saNamespace, + }}, } - return &cr + return &sa, &cr, &crb } diff --git a/controllers/flp/flp_transfo_reconciler.go b/controllers/flp/flp_reconciler.go similarity index 50% rename from controllers/flp/flp_transfo_reconciler.go rename to controllers/flp/flp_reconciler.go index 75c280790..a78baca72 100644 --- a/controllers/flp/flp_transfo_reconciler.go +++ b/controllers/flp/flp_reconciler.go @@ -3,23 +3,21 @@ package flp import ( "context" - monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" appsv1 "k8s.io/api/apps/v1" ascv2 "k8s.io/api/autoscaling/v2" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/api/equality" - "sigs.k8s.io/controller-runtime/pkg/log" flowslatest "github.com/netobserv/network-observability-operator/apis/flowcollector/v1beta2" metricslatest "github.com/netobserv/network-observability-operator/apis/flowmetrics/v1alpha1" "github.com/netobserv/network-observability-operator/controllers/constants" "github.com/netobserv/network-observability-operator/controllers/reconcilers" "github.com/netobserv/network-observability-operator/pkg/helper" - "github.com/netobserv/network-observability-operator/pkg/manager/status" + monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" ) -type transformerReconciler struct { +type flpObjects struct { *reconcilers.Instance deployment *appsv1.Deployment promService *corev1.Service @@ -31,41 +29,31 @@ type transformerReconciler struct { prometheusRule *monitoringv1.PrometheusRule } -func newTransformerReconciler(cmn *reconcilers.Instance) *transformerReconciler { - name := name(ConfKafkaTransformer) - rec := transformerReconciler{ +func newFLPObjects(cmn *reconcilers.Instance) *flpObjects { + rec := flpObjects{ Instance: cmn, - deployment: cmn.Managed.NewDeployment(name), - promService: cmn.Managed.NewService(promServiceName(ConfKafkaTransformer)), - hpa: cmn.Managed.NewHPA(name), - serviceAccount: cmn.Managed.NewServiceAccount(name), - configMap: cmn.Managed.NewConfigMap(configMapName(ConfKafkaTransformer)), - roleBinding: cmn.Managed.NewCRB(RoleBindingName(ConfKafkaTransformer)), + deployment: cmn.Managed.NewDeployment(constants.FLPName), + promService: cmn.Managed.NewService(promServiceName(constants.FLPName)), + hpa: cmn.Managed.NewHPA(constants.FLPName), + serviceAccount: cmn.Managed.NewServiceAccount(constants.FLPName), + configMap: cmn.Managed.NewConfigMap(configMapName(constants.FLPName)), + roleBinding: cmn.Managed.NewCRB(constants.FLPName), } if cmn.AvailableAPIs.HasSvcMonitor() { - rec.serviceMonitor = cmn.Managed.NewServiceMonitor(serviceMonitorName(ConfKafkaTransformer)) + rec.serviceMonitor = cmn.Managed.NewServiceMonitor(serviceMonitorName(constants.FLPName)) } if cmn.AvailableAPIs.HasPromRule() { - rec.prometheusRule = cmn.Managed.NewPrometheusRule(prometheusRuleName(ConfKafkaTransformer)) + rec.prometheusRule = cmn.Managed.NewPrometheusRule(prometheusRuleName(constants.FLPName)) } return &rec } -func (r *transformerReconciler) context(ctx context.Context) context.Context { - l := log.FromContext(ctx).WithName("transformer") - return log.IntoContext(ctx, l) -} - // cleanupNamespace cleans up old namespace -func (r *transformerReconciler) cleanupNamespace(ctx context.Context) { +func (r *flpObjects) cleanupNamespace(ctx context.Context) { r.Managed.CleanupPreviousNamespace(ctx) } -func (r *transformerReconciler) getStatus() *status.Instance { - return &r.Status -} - -func (r *transformerReconciler) reconcile(ctx context.Context, desired *flowslatest.FlowCollector, flowMetrics *metricslatest.FlowMetricList) error { +func (r *flpObjects) reconcile(ctx context.Context, desired *flowslatest.FlowCollector, flowMetrics *metricslatest.FlowMetricList) error { // Retrieve current owned objects err := r.Managed.FetchAll(ctx) if err != nil { @@ -73,14 +61,14 @@ func (r *transformerReconciler) reconcile(ctx context.Context, desired *flowslat } if !helper.UseKafka(&desired.Spec) { - r.Status.SetUnused("Transformer only used with Kafka") + r.Status.SetUnused("FLP only used with Kafka") r.Managed.TryDeleteAll(ctx) return nil } r.Status.SetReady() // will be overidden if necessary, as error or pending - builder, err := newTransfoBuilder(r.Instance, &desired.Spec, flowMetrics) + builder, err := newKafkaConsumerBuilder(r.Instance, &desired.Spec, flowMetrics) if err != nil { return err } @@ -100,12 +88,12 @@ func (r *transformerReconciler) reconcile(ctx context.Context, desired *flowslat return err } } - if err := r.reconcilePermissions(ctx, &builder); err != nil { + + if err := reconcileRBAC(ctx, r.Common, constants.FLPName, constants.FLPName, r.Common.Namespace, builder.desired.Loki.Mode); err != nil { return err } - err = r.reconcilePrometheusService(ctx, &builder) - if err != nil { + if err := reconcilePrometheusService(ctx, r.Instance, r.promService, r.serviceMonitor, r.prometheusRule, builder); err != nil { return err } @@ -128,14 +116,14 @@ func (r *transformerReconciler) reconcile(ctx context.Context, desired *flowslat return err } - if err = r.reconcileDeployment(ctx, &desired.Spec.Processor, &builder, annotations); err != nil { + if err = r.reconcileDeployment(ctx, &desired.Spec.Processor, builder, annotations); err != nil { return err } - return r.reconcileHPA(ctx, &desired.Spec.Processor, &builder) + return r.reconcileHPA(ctx, &desired.Spec.Processor, builder) } -func (r *transformerReconciler) reconcileDeployment(ctx context.Context, desiredFLP *flowslatest.FlowCollectorFLP, builder *transfoBuilder, annotations map[string]string) error { +func (r *flpObjects) reconcileDeployment(ctx context.Context, desiredFLP *flowslatest.FlowCollectorFLP, builder *Builder, annotations map[string]string) error { report := helper.NewChangeReport("FLP Deployment") defer report.LogIfNeeded(ctx) @@ -151,7 +139,7 @@ func (r *transformerReconciler) reconcileDeployment(ctx context.Context, desired ) } -func (r *transformerReconciler) reconcileHPA(ctx context.Context, desiredFLP *flowslatest.FlowCollectorFLP, builder *transfoBuilder) error { +func (r *flpObjects) reconcileHPA(ctx context.Context, desiredFLP *flowslatest.FlowCollectorFLP, builder *Builder) error { report := helper.NewChangeReport("FLP autoscaler") defer report.LogIfNeeded(ctx) @@ -164,50 +152,3 @@ func (r *transformerReconciler) reconcileHPA(ctx context.Context, desiredFLP *fl &report, ) } - -func (r *transformerReconciler) reconcilePrometheusService(ctx context.Context, builder *transfoBuilder) error { - report := helper.NewChangeReport("FLP prometheus service") - defer report.LogIfNeeded(ctx) - - if err := r.ReconcileService(ctx, r.promService, builder.promService(), &report); err != nil { - return err - } - if r.AvailableAPIs.HasSvcMonitor() { - serviceMonitor := builder.generic.serviceMonitor() - if err := reconcilers.GenericReconcile(ctx, r.Managed, &r.Client, r.serviceMonitor, serviceMonitor, &report, helper.ServiceMonitorChanged); err != nil { - return err - } - } - if r.AvailableAPIs.HasPromRule() { - promRules := builder.generic.prometheusRule() - if err := reconcilers.GenericReconcile(ctx, r.Managed, &r.Client, r.prometheusRule, promRules, &report, helper.PrometheusRuleChanged); err != nil { - return err - } - } - return nil -} - -func (r *transformerReconciler) reconcilePermissions(ctx context.Context, builder *transfoBuilder) error { - if !r.Managed.Exists(r.serviceAccount) { - return r.CreateOwned(ctx, builder.serviceAccount()) - } // We only configure name, update is not needed for now - - cr := BuildClusterRoleTransformer() - if err := r.ReconcileClusterRole(ctx, cr); err != nil { - return err - } - - desired := builder.clusterRoleBinding() - if err := r.ReconcileClusterRoleBinding(ctx, desired); err != nil { - return err - } - - return ReconcileLokiRoles( - ctx, - r.Common, - builder.generic.desired, - builder.generic.name(), - builder.generic.name(), - r.Common.Namespace, - ) -} diff --git a/controllers/flp/flp_test.go b/controllers/flp/flp_test.go index e57f5d14d..3c9fae440 100644 --- a/controllers/flp/flp_test.go +++ b/controllers/flp/flp_test.go @@ -1,19 +1,3 @@ -/* -Copyright 2021. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - package flp import ( @@ -30,7 +14,6 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/utils/ptr" flowslatest "github.com/netobserv/network-observability-operator/apis/flowcollector/v1beta2" @@ -168,17 +151,21 @@ func getAutoScalerSpecs() (ascv2.HorizontalPodAutoscaler, flowslatest.FlowCollec return autoScaler, getConfig().Processor.KafkaConsumerAutoscaler } -func monoBuilder(ns string, cfg *flowslatest.FlowCollectorSpec) monolithBuilder { +func inProcessForImage(img string, ns string, cfg *flowslatest.FlowCollectorSpec) *Builder { loki := helper.NewLokiConfig(&cfg.Loki, "any") info := reconcilers.Common{Namespace: ns, Loki: &loki} - b, _ := newMonolithBuilder(info.NewInstance(image, status.Instance{}), cfg, &metricslatest.FlowMetricList{}) + b, _ := newInProcessBuilder(info.NewInstance(img, status.Instance{}), constants.FLPName, cfg, &metricslatest.FlowMetricList{}) return b } -func transfBuilder(ns string, cfg *flowslatest.FlowCollectorSpec) transfoBuilder { +func inProcessBuilder(ns string, cfg *flowslatest.FlowCollectorSpec) *Builder { + return inProcessForImage(image, ns, cfg) +} + +func transfBuilder(ns string, cfg *flowslatest.FlowCollectorSpec) *Builder { loki := helper.NewLokiConfig(&cfg.Loki, "any") info := reconcilers.Common{Namespace: ns, Loki: &loki} - b, _ := newTransfoBuilder(info.NewInstance(image, status.Instance{}), cfg, &metricslatest.FlowMetricList{}) + b, _ := newKafkaConsumerBuilder(info.NewInstance(image, status.Instance{}), cfg, &metricslatest.FlowMetricList{}) return b } @@ -188,150 +175,6 @@ func annotate(digest string) map[string]string { } } -func TestDaemonSetNoChange(t *testing.T) { - assert := assert.New(t) - - // Get first - ns := "namespace" - cfg := getConfig() - b := monoBuilder(ns, &cfg) - _, digest, err := b.configMap() - assert.NoError(err) - first := b.daemonSet(annotate(digest)) - - // Check no change - cfg = getConfig() - b = monoBuilder(ns, &cfg) - _, digest, err = b.configMap() - assert.NoError(err) - second := b.daemonSet(annotate(digest)) - - report := helper.NewChangeReport("") - assert.False(helper.PodChanged(&first.Spec.Template, &second.Spec.Template, constants.FLPName, &report)) - assert.Contains(report.String(), "no change") -} - -func TestDaemonSetChanged(t *testing.T) { - assert := assert.New(t) - - // Get first - ns := "namespace" - cfg := getConfig() - b := monoBuilder(ns, &cfg) - _, digest, err := b.configMap() - assert.NoError(err) - first := b.daemonSet(annotate(digest)) - - // Check probes enabled change - cfg.Processor.Advanced.EnableKubeProbes = ptr.To(true) - b = monoBuilder(ns, &cfg) - _, digest, err = b.configMap() - assert.NoError(err) - second := b.daemonSet(annotate(digest)) - - report := helper.NewChangeReport("") - assert.True(helper.PodChanged(&first.Spec.Template, &second.Spec.Template, constants.FLPName, &report)) - assert.Contains(report.String(), "probe changed") - - // Check probes DON'T change infinitely (bc DeepEqual/Derivative checks won't work there) - assert.NoError(err) - secondBis := b.daemonSet(annotate(digest)) - secondBis.Spec.Template.Spec.Containers[0].LivenessProbe = &corev1.Probe{ - FailureThreshold: 3, - PeriodSeconds: 10, - SuccessThreshold: 1, - TimeoutSeconds: 5, - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Path: "/live", - Port: intstr.FromString("health"), - Scheme: "http", - }, - }, - } - report = helper.NewChangeReport("") - assert.False(helper.PodChanged(&second.Spec.Template, &secondBis.Spec.Template, constants.FLPName, &report)) - assert.Contains(report.String(), "no change") - - // Check log level change - cfg.Processor.LogLevel = "info" - b = monoBuilder(ns, &cfg) - _, digest, err = b.configMap() - assert.NoError(err) - third := b.daemonSet(annotate(digest)) - - report = helper.NewChangeReport("") - assert.True(helper.PodChanged(&second.Spec.Template, &third.Spec.Template, constants.FLPName, &report)) - assert.Contains(report.String(), "config-digest") - - // Check resource change - cfg.Processor.Resources.Limits = map[corev1.ResourceName]resource.Quantity{ - corev1.ResourceCPU: resource.MustParse("500m"), - corev1.ResourceMemory: resource.MustParse("500Gi"), - } - b = monoBuilder(ns, &cfg) - _, digest, err = b.configMap() - assert.NoError(err) - fourth := b.daemonSet(annotate(digest)) - - report = helper.NewChangeReport("") - assert.True(helper.PodChanged(&third.Spec.Template, &fourth.Spec.Template, constants.FLPName, &report)) - assert.Contains(report.String(), "req/limit changed") - - // Check reverting limits - cfg.Processor.Resources.Limits = map[corev1.ResourceName]resource.Quantity{ - corev1.ResourceCPU: resource.MustParse("1"), - corev1.ResourceMemory: resource.MustParse("512Mi"), - } - b = monoBuilder(ns, &cfg) - _, digest, err = b.configMap() - assert.NoError(err) - fifth := b.daemonSet(annotate(digest)) - - report = helper.NewChangeReport("") - assert.True(helper.PodChanged(&fourth.Spec.Template, &fifth.Spec.Template, constants.FLPName, &report)) - assert.Contains(report.String(), "req/limit changed") - report = helper.NewChangeReport("") - assert.False(helper.PodChanged(&third.Spec.Template, &fifth.Spec.Template, constants.FLPName, &report)) - assert.Contains(report.String(), "no change") - - // Check Loki config change - cfg.Loki.Manual.TLS = flowslatest.ClientTLS{ - Enable: true, - CACert: flowslatest.CertificateReference{ - Type: "configmap", - Name: "loki-cert", - CertFile: "ca.crt", - }, - } - b = monoBuilder(ns, &cfg) - _, digest, err = b.configMap() - assert.NoError(err) - sixth := b.daemonSet(annotate(digest)) - - report = helper.NewChangeReport("") - assert.True(helper.PodChanged(&fifth.Spec.Template, &sixth.Spec.Template, constants.FLPName, &report)) - assert.Contains(report.String(), "config-digest") - - // Check volumes change - cfg.Loki.Manual.TLS = flowslatest.ClientTLS{ - Enable: true, - CACert: flowslatest.CertificateReference{ - Type: "configmap", - Name: "loki-cert-2", - CertFile: "ca.crt", - }, - } - b = monoBuilder(ns, &cfg) - _, digest, err = b.configMap() - assert.NoError(err) - seventh := b.daemonSet(annotate(digest)) - - report = helper.NewChangeReport("") - assert.True(helper.PodChanged(&sixth.Spec.Template, &seventh.Spec.Template, constants.FLPName, &report)) - assert.Contains(report.String(), "Volumes changed") -} - func TestDeploymentNoChange(t *testing.T) { assert := assert.New(t) @@ -466,7 +309,7 @@ func TestServiceNoChange(t *testing.T) { // Get first ns := "namespace" cfg := getConfig() - b := monoBuilder(ns, &cfg) + b := inProcessBuilder(ns, &cfg) first := b.promService() // Check no change @@ -483,12 +326,12 @@ func TestServiceChanged(t *testing.T) { // Get first ns := "namespace" cfg := getConfig() - b := monoBuilder(ns, &cfg) + b := inProcessBuilder(ns, &cfg) first := b.promService() // Check port changed cfg.Processor.Metrics.Server.Port = 9999 - b = monoBuilder(ns, &cfg) + b = inProcessBuilder(ns, &cfg) second := b.promService() report := helper.NewChangeReport("") @@ -497,7 +340,7 @@ func TestServiceChanged(t *testing.T) { // Make sure non-service settings doesn't trigger service update cfg.Processor.LogLevel = "error" - b = monoBuilder(ns, &cfg) + b = inProcessBuilder(ns, &cfg) third := b.promService() report = helper.NewChangeReport("") @@ -506,7 +349,7 @@ func TestServiceChanged(t *testing.T) { // Check annotations change cfg.Processor.LogLevel = "error" - b = monoBuilder(ns, &cfg) + b = inProcessBuilder(ns, &cfg) fourth := b.promService() fourth.ObjectMeta.Annotations = map[string]string{ "name": "value", @@ -523,8 +366,8 @@ func TestServiceMonitorNoChange(t *testing.T) { // Get first ns := "namespace" cfg := getConfig() - b := monoBuilder(ns, &cfg) - first := b.generic.serviceMonitor() + b := inProcessBuilder(ns, &cfg) + first := b.serviceMonitor() // Check no change newServiceMonitor := first.DeepCopy() @@ -540,29 +383,28 @@ func TestServiceMonitorChanged(t *testing.T) { // Get first ns := "namespace" cfg := getConfig() - b := monoBuilder(ns, &cfg) - first := b.generic.serviceMonitor() + b := inProcessBuilder(ns, &cfg) + first := b.serviceMonitor() // Check namespace change - b = monoBuilder("namespace2", &cfg) - second := b.generic.serviceMonitor() + b = inProcessBuilder("namespace2", &cfg) + second := b.serviceMonitor() report := helper.NewChangeReport("") assert.True(helper.ServiceMonitorChanged(first, second, &report)) assert.Contains(report.String(), "ServiceMonitor spec changed") // Check labels change - info := reconcilers.Common{Namespace: "namespace2"} - b, _ = newMonolithBuilder(info.NewInstance(image2, status.Instance{}), &cfg, b.generic.flowMetrics) - third := b.generic.serviceMonitor() + b = inProcessForImage(image2, "namespace2", &cfg) + third := b.serviceMonitor() report = helper.NewChangeReport("") assert.True(helper.ServiceMonitorChanged(second, third, &report)) assert.Contains(report.String(), "ServiceMonitor labels changed") // Check scheme changed - b, _ = newMonolithBuilder(info.NewInstance(image2, status.Instance{}), &cfg, b.generic.flowMetrics) - fourth := b.generic.serviceMonitor() + b = inProcessForImage(image2, "namespace2", &cfg) + fourth := b.serviceMonitor() fourth.Spec.Endpoints[0].Scheme = "https" report = helper.NewChangeReport("") @@ -576,8 +418,8 @@ func TestPrometheusRuleNoChange(t *testing.T) { // Get first ns := "namespace" cfg := getConfig() - b := monoBuilder(ns, &cfg) - first := b.generic.prometheusRule() + b := inProcessBuilder(ns, &cfg) + first := b.prometheusRule() // Check no change newServiceMonitor := first.DeepCopy() @@ -592,22 +434,21 @@ func TestPrometheusRuleChanged(t *testing.T) { // Get first cfg := getConfig() - b := monoBuilder("namespace", &cfg) - first := b.generic.prometheusRule() + b := inProcessBuilder("namespace", &cfg) + first := b.prometheusRule() // Check enabled rule change cfg.Processor.Metrics.DisableAlerts = []flowslatest.FLPAlert{flowslatest.AlertNoFlows} - b = monoBuilder("namespace", &cfg) - second := b.generic.prometheusRule() + b = inProcessBuilder("namespace", &cfg) + second := b.prometheusRule() report := helper.NewChangeReport("") assert.True(helper.PrometheusRuleChanged(first, second, &report)) assert.Contains(report.String(), "PrometheusRule spec changed") // Check labels change - info := reconcilers.Common{Namespace: "namespace2"} - b, _ = newMonolithBuilder(info.NewInstance(image2, status.Instance{}), &cfg, b.generic.flowMetrics) - third := b.generic.prometheusRule() + b = inProcessForImage(image2, "namespace2", &cfg) + third := b.prometheusRule() report = helper.NewChangeReport("") assert.True(helper.PrometheusRuleChanged(second, third, &report)) @@ -620,7 +461,7 @@ func TestConfigMapShouldDeserializeAsJSONWithLokiManual(t *testing.T) { ns := "namespace" cfg := getConfig() loki := cfg.Loki - b := monoBuilder(ns, &cfg) + b := inProcessBuilder(ns, &cfg) cm, digest, err := b.configMap() assert.NoError(err) assert.NotEmpty(t, digest) @@ -637,10 +478,9 @@ func TestConfigMapShouldDeserializeAsJSONWithLokiManual(t *testing.T) { assert.Equal("trace", decoded.LogLevel) params := decoded.Parameters - assert.Len(params, 6) - assert.Equal(*cfg.Processor.Advanced.Port, int32(params[0].Ingest.GRPC.Port)) + assert.Len(params, 5) - lokiCfg := params[3].Write.Loki + lokiCfg := params[2].Write.Loki assert.Equal(loki.Manual.IngesterURL, lokiCfg.URL) assert.Equal(cfg.Loki.WriteBatchWait.Duration.String(), lokiCfg.BatchWait) assert.EqualValues(cfg.Loki.WriteBatchSize, lokiCfg.BatchSize) @@ -670,7 +510,7 @@ func TestConfigMapShouldDeserializeAsJSONWithLokiStack(t *testing.T) { cfg := getConfig() useLokiStack(&cfg) cfg.Agent.Type = flowslatest.AgentEBPF - b := monoBuilder(ns, &cfg) + b := inProcessBuilder(ns, &cfg) cm, digest, err := b.configMap() assert.NoError(err) assert.NotEmpty(t, digest) @@ -687,9 +527,9 @@ func TestConfigMapShouldDeserializeAsJSONWithLokiStack(t *testing.T) { assert.Equal("trace", decoded.LogLevel) params := decoded.Parameters - assert.Len(params, 6) + assert.Len(params, 5) - lokiCfg := params[3].Write.Loki + lokiCfg := params[2].Write.Loki assert.Equal("https://lokistack-gateway-http.ls-namespace.svc:8080/api/logs/v1/network/", lokiCfg.URL) assert.Equal("network", lokiCfg.TenantID) assert.Equal("Bearer", lokiCfg.ClientConfig.Authorization.Type) @@ -754,24 +594,16 @@ func TestLabels(t *testing.T) { assert := assert.New(t) cfg := getConfig() - info := reconcilers.Common{Namespace: "ns"} - builder, _ := newMonolithBuilder(info.NewInstance(image, status.Instance{}), &cfg, &metricslatest.FlowMetricList{}) - tBuilder, _ := newTransfoBuilder(info.NewInstance(image, status.Instance{}), &cfg, &metricslatest.FlowMetricList{}) + builder := inProcessBuilder("ns", &cfg) + tBuilder := transfBuilder("ns", &cfg) // Deployment depl := tBuilder.deployment(annotate("digest")) - assert.Equal("flowlogs-pipeline-transformer", depl.Labels["app"]) - assert.Equal("flowlogs-pipeline-transformer", depl.Spec.Template.Labels["app"]) + assert.Equal("flowlogs-pipeline", depl.Labels["app"]) + assert.Equal("flowlogs-pipeline", depl.Spec.Template.Labels["app"]) assert.Equal("dev", depl.Labels["version"]) assert.Equal("dev", depl.Spec.Template.Labels["version"]) - // DaemonSet - ds := builder.daemonSet(annotate("digest")) - assert.Equal("flowlogs-pipeline", ds.Labels["app"]) - assert.Equal("flowlogs-pipeline", ds.Spec.Template.Labels["app"]) - assert.Equal("dev", ds.Labels["version"]) - assert.Equal("dev", ds.Spec.Template.Labels["version"]) - // Service svc := builder.promService() assert.Equal("flowlogs-pipeline", svc.Labels["app"]) @@ -780,12 +612,12 @@ func TestLabels(t *testing.T) { assert.Empty(svc.Spec.Selector["version"]) // ServiceMonitor - smMono := builder.generic.serviceMonitor() + smMono := builder.serviceMonitor() assert.Equal("flowlogs-pipeline-monitor", smMono.Name) assert.Equal("flowlogs-pipeline", smMono.Spec.Selector.MatchLabels["app"]) - smTrans := tBuilder.generic.serviceMonitor() - assert.Equal("flowlogs-pipeline-transformer-monitor", smTrans.Name) - assert.Equal("flowlogs-pipeline-transformer", smTrans.Spec.Selector.MatchLabels["app"]) + smTrans := tBuilder.serviceMonitor() + assert.Equal("flowlogs-pipeline-monitor", smTrans.Name) + assert.Equal("flowlogs-pipeline", smTrans.Spec.Selector.MatchLabels["app"]) } // This function validate that each stage has its matching parameter @@ -817,12 +649,12 @@ func TestPipelineConfig(t *testing.T) { ns := "namespace" cfg := getConfig() cfg.Processor.LogLevel = "info" - b := monoBuilder(ns, &cfg) + b := inProcessBuilder(ns, &cfg) cm, _, err := b.configMap() assert.NoError(err) _, pipeline := validatePipelineConfig(t, cm) assert.Equal( - `[{"name":"grpc"},{"name":"extract_conntrack","follows":"grpc"},{"name":"enrich","follows":"extract_conntrack"},{"name":"loki","follows":"enrich"},{"name":"prometheus","follows":"enrich"}]`, + `[{"name":"extract_conntrack","follows":"preset-ingester"},{"name":"enrich","follows":"extract_conntrack"},{"name":"loki","follows":"enrich"},{"name":"prometheus","follows":"enrich"}]`, pipeline, ) @@ -843,12 +675,12 @@ func TestPipelineTraceStage(t *testing.T) { cfg := getConfig() - b := monoBuilder("namespace", &cfg) + b := inProcessBuilder("namespace", &cfg) cm, _, err := b.configMap() assert.NoError(err) _, pipeline := validatePipelineConfig(t, cm) assert.Equal( - `[{"name":"grpc"},{"name":"extract_conntrack","follows":"grpc"},{"name":"enrich","follows":"extract_conntrack"},{"name":"loki","follows":"enrich"},{"name":"stdout","follows":"enrich"},{"name":"prometheus","follows":"enrich"}]`, + `[{"name":"extract_conntrack","follows":"preset-ingester"},{"name":"enrich","follows":"extract_conntrack"},{"name":"loki","follows":"enrich"},{"name":"stdout","follows":"enrich"},{"name":"prometheus","follows":"enrich"}]`, pipeline, ) } @@ -867,17 +699,17 @@ func TestMergeMetricsConfiguration_Default(t *testing.T) { cfg := getConfig() - b := monoBuilder("namespace", &cfg) + b := inProcessBuilder("namespace", &cfg) cm, _, err := b.configMap() assert.NoError(err) cfs, _ := validatePipelineConfig(t, cm) - names := getSortedMetricsNames(cfs.Parameters[5].Encode.Prom.Metrics) + names := getSortedMetricsNames(cfs.Parameters[4].Encode.Prom.Metrics) assert.Equal([]string{ "namespace_flows_total", "node_ingress_bytes_total", "workload_ingress_bytes_total", }, names) - assert.Equal("netobserv_", cfs.Parameters[5].Encode.Prom.Prefix) + assert.Equal("netobserv_", cfs.Parameters[4].Encode.Prom.Prefix) } func TestMergeMetricsConfiguration_DefaultWithFeatures(t *testing.T) { @@ -887,11 +719,11 @@ func TestMergeMetricsConfiguration_DefaultWithFeatures(t *testing.T) { cfg.Agent.EBPF.Privileged = true cfg.Agent.EBPF.Features = []flowslatest.AgentFeature{flowslatest.DNSTracking, flowslatest.FlowRTT, flowslatest.PacketDrop} - b := monoBuilder("namespace", &cfg) + b := inProcessBuilder("namespace", &cfg) cm, _, err := b.configMap() assert.NoError(err) cfs, _ := validatePipelineConfig(t, cm) - names := getSortedMetricsNames(cfs.Parameters[5].Encode.Prom.Metrics) + names := getSortedMetricsNames(cfs.Parameters[4].Encode.Prom.Metrics) assert.Equal([]string{ "namespace_dns_latency_seconds", "namespace_drop_packets_total", @@ -900,7 +732,7 @@ func TestMergeMetricsConfiguration_DefaultWithFeatures(t *testing.T) { "node_ingress_bytes_total", "workload_ingress_bytes_total", }, names) - assert.Equal("netobserv_", cfs.Parameters[5].Encode.Prom.Prefix) + assert.Equal("netobserv_", cfs.Parameters[4].Encode.Prom.Prefix) } func TestMergeMetricsConfiguration_WithList(t *testing.T) { @@ -909,15 +741,15 @@ func TestMergeMetricsConfiguration_WithList(t *testing.T) { cfg := getConfig() cfg.Processor.Metrics.IncludeList = &[]flowslatest.FLPMetric{"namespace_egress_bytes_total", "namespace_ingress_bytes_total"} - b := monoBuilder("namespace", &cfg) + b := inProcessBuilder("namespace", &cfg) cm, _, err := b.configMap() assert.NoError(err) cfs, _ := validatePipelineConfig(t, cm) - names := getSortedMetricsNames(cfs.Parameters[5].Encode.Prom.Metrics) + names := getSortedMetricsNames(cfs.Parameters[4].Encode.Prom.Metrics) assert.Len(names, 2) assert.Equal("namespace_egress_bytes_total", names[0]) assert.Equal("namespace_ingress_bytes_total", names[1]) - assert.Equal("netobserv_", cfs.Parameters[5].Encode.Prom.Prefix) + assert.Equal("netobserv_", cfs.Parameters[4].Encode.Prom.Prefix) } func TestMergeMetricsConfiguration_EmptyList(t *testing.T) { @@ -926,11 +758,11 @@ func TestMergeMetricsConfiguration_EmptyList(t *testing.T) { cfg := getConfig() cfg.Processor.Metrics.IncludeList = &[]flowslatest.FLPMetric{} - b := monoBuilder("namespace", &cfg) + b := inProcessBuilder("namespace", &cfg) cm, _, err := b.configMap() assert.NoError(err) cfs, _ := validatePipelineConfig(t, cm) - assert.Len(cfs.Parameters, 5) + assert.Len(cfs.Parameters, 4) } func TestPipelineWithExporter(t *testing.T) { @@ -951,21 +783,21 @@ func TestPipelineWithExporter(t *testing.T) { }, }) - b := monoBuilder("namespace", &cfg) + b := inProcessBuilder("namespace", &cfg) cm, _, err := b.configMap() assert.NoError(err) cfs, pipeline := validatePipelineConfig(t, cm) assert.Equal( - `[{"name":"grpc"},{"name":"extract_conntrack","follows":"grpc"},{"name":"enrich","follows":"extract_conntrack"},{"name":"loki","follows":"enrich"},{"name":"stdout","follows":"enrich"},{"name":"prometheus","follows":"enrich"},{"name":"kafka-export-0","follows":"enrich"},{"name":"IPFIX-export-1","follows":"enrich"}]`, + `[{"name":"extract_conntrack","follows":"preset-ingester"},{"name":"enrich","follows":"extract_conntrack"},{"name":"loki","follows":"enrich"},{"name":"stdout","follows":"enrich"},{"name":"prometheus","follows":"enrich"},{"name":"kafka-export-0","follows":"enrich"},{"name":"IPFIX-export-1","follows":"enrich"}]`, pipeline, ) - assert.Equal("kafka-test", cfs.Parameters[6].Encode.Kafka.Address) - assert.Equal("topic-test", cfs.Parameters[6].Encode.Kafka.Topic) + assert.Equal("kafka-test", cfs.Parameters[5].Encode.Kafka.Address) + assert.Equal("topic-test", cfs.Parameters[5].Encode.Kafka.Topic) - assert.Equal("ipfix-receiver-test", cfs.Parameters[7].Write.Ipfix.TargetHost) - assert.Equal(9999, cfs.Parameters[7].Write.Ipfix.TargetPort) - assert.Equal("tcp", cfs.Parameters[7].Write.Ipfix.Transport) + assert.Equal("ipfix-receiver-test", cfs.Parameters[6].Write.Ipfix.TargetHost) + assert.Equal(9999, cfs.Parameters[6].Write.Ipfix.TargetPort) + assert.Equal("tcp", cfs.Parameters[6].Write.Ipfix.Transport) } func TestPipelineWithoutLoki(t *testing.T) { @@ -974,12 +806,12 @@ func TestPipelineWithoutLoki(t *testing.T) { cfg := getConfig() cfg.Loki.Enable = ptr.To(false) - b := monoBuilder("namespace", &cfg) + b := inProcessBuilder("namespace", &cfg) cm, _, err := b.configMap() assert.NoError(err) _, pipeline := validatePipelineConfig(t, cm) assert.Equal( - `[{"name":"grpc"},{"name":"extract_conntrack","follows":"grpc"},{"name":"enrich","follows":"extract_conntrack"},{"name":"stdout","follows":"enrich"},{"name":"prometheus","follows":"enrich"}]`, + `[{"name":"extract_conntrack","follows":"preset-ingester"},{"name":"enrich","follows":"extract_conntrack"},{"name":"stdout","follows":"enrich"},{"name":"prometheus","follows":"enrich"}]`, pipeline, ) } diff --git a/controllers/flp/flp_transfo_objects.go b/controllers/flp/flp_transfo_objects.go deleted file mode 100644 index 70138afe8..000000000 --- a/controllers/flp/flp_transfo_objects.go +++ /dev/null @@ -1,109 +0,0 @@ -package flp - -import ( - appsv1 "k8s.io/api/apps/v1" - ascv2 "k8s.io/api/autoscaling/v2" - corev1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - flowslatest "github.com/netobserv/network-observability-operator/apis/flowcollector/v1beta2" - metricslatest "github.com/netobserv/network-observability-operator/apis/flowmetrics/v1alpha1" - "github.com/netobserv/network-observability-operator/controllers/reconcilers" -) - -type transfoBuilder struct { - generic builder -} - -func newTransfoBuilder(info *reconcilers.Instance, desired *flowslatest.FlowCollectorSpec, flowMetrics *metricslatest.FlowMetricList) (transfoBuilder, error) { - gen, err := NewBuilder(info, desired, flowMetrics, ConfKafkaTransformer) - return transfoBuilder{ - generic: gen, - }, err -} - -func (b *transfoBuilder) deployment(annotations map[string]string) *appsv1.Deployment { - pod := b.generic.podTemplate(false /*no listen*/, false /*no host network*/, annotations) - return &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: b.generic.name(), - Namespace: b.generic.info.Namespace, - Labels: b.generic.labels, - }, - Spec: appsv1.DeploymentSpec{ - Replicas: b.generic.desired.Processor.KafkaConsumerReplicas, - Selector: &metav1.LabelSelector{ - MatchLabels: b.generic.selector, - }, - Template: pod, - }, - } -} - -func (b *transfoBuilder) configMap() (*corev1.ConfigMap, string, error) { - pipeline := b.generic.NewKafkaPipeline() - err := pipeline.AddProcessorStages() - if err != nil { - return nil, "", err - } - return b.generic.ConfigMap() -} - -func (b *transfoBuilder) promService() *corev1.Service { - return b.generic.promService() -} - -func (b *transfoBuilder) autoScaler() *ascv2.HorizontalPodAutoscaler { - return &ascv2.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{ - Name: b.generic.name(), - Namespace: b.generic.info.Namespace, - Labels: b.generic.labels, - }, - Spec: ascv2.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: ascv2.CrossVersionObjectReference{ - APIVersion: "apps/v1", - Kind: "Deployment", - Name: b.generic.name(), - }, - MinReplicas: b.generic.desired.Processor.KafkaConsumerAutoscaler.MinReplicas, - MaxReplicas: b.generic.desired.Processor.KafkaConsumerAutoscaler.MaxReplicas, - Metrics: b.generic.desired.Processor.KafkaConsumerAutoscaler.Metrics, - }, - } -} - -// The operator needs to have at least the same permissions as flowlogs-pipeline in order to grant them -//+kubebuilder:rbac:groups=apps,resources=replicasets,verbs=get;list;watch -//+kubebuilder:rbac:groups=autoscaling,resources=horizontalpodautoscalers,verbs=create;delete;patch;update;get;watch;list -//+kubebuilder:rbac:groups=core,resources=pods;services;nodes,verbs=get;list;watch - -func BuildClusterRoleTransformer() *rbacv1.ClusterRole { - return &rbacv1.ClusterRole{ - ObjectMeta: metav1.ObjectMeta{ - Name: name(ConfKafkaTransformer), - }, - Rules: []rbacv1.PolicyRule{{ - APIGroups: []string{""}, - Verbs: []string{"list", "get", "watch"}, - Resources: []string{"pods", "services", "nodes"}, - }, { - APIGroups: []string{"apps"}, - Verbs: []string{"list", "get", "watch"}, - Resources: []string{"replicasets"}, - }, { - APIGroups: []string{"autoscaling"}, - Verbs: []string{"create", "delete", "patch", "update", "get", "watch", "list"}, - Resources: []string{"horizontalpodautoscalers"}, - }}, - } -} - -func (b *transfoBuilder) serviceAccount() *corev1.ServiceAccount { - return b.generic.serviceAccount() -} - -func (b *transfoBuilder) clusterRoleBinding() *rbacv1.ClusterRoleBinding { - return b.generic.clusterRoleBinding(ConfKafkaTransformer, false) -} diff --git a/controllers/flp/in_process.go b/controllers/flp/in_process.go new file mode 100644 index 000000000..135cde733 --- /dev/null +++ b/controllers/flp/in_process.go @@ -0,0 +1,114 @@ +package flp + +import ( + "context" + + flowslatest "github.com/netobserv/network-observability-operator/apis/flowcollector/v1beta2" + metricslatest "github.com/netobserv/network-observability-operator/apis/flowmetrics/v1alpha1" + "github.com/netobserv/network-observability-operator/controllers/constants" + "github.com/netobserv/network-observability-operator/controllers/reconcilers" + "github.com/netobserv/network-observability-operator/pkg/helper" + "github.com/netobserv/network-observability-operator/pkg/volumes" + monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type inProcessObjects struct { + *reconcilers.Instance + appName string + promService *corev1.Service + roleBinding *rbacv1.ClusterRoleBinding + serviceMonitor *monitoringv1.ServiceMonitor + prometheusRule *monitoringv1.PrometheusRule +} + +type InProcessInfo struct { + JSONConfig string + Annotations map[string]string + Volumes volumes.Builder +} + +func ReconcileInProcess(ctx context.Context, parent *reconcilers.Instance, desired *flowslatest.FlowCollector, appName string) (*InProcessInfo, error) { + objs := newInProcessObjects(parent, appName) + return objs.reconcile(ctx, desired) +} + +func newInProcessObjects(parent *reconcilers.Instance, appName string) *inProcessObjects { + cloneInfo := *parent.Common + cloneInfo.Namespace = parent.PrivilegedNamespace() + inst := cloneInfo.NewInstance(parent.Image, parent.Status) + + objs := inProcessObjects{ + Instance: inst, + appName: appName, + promService: inst.Managed.NewService(promServiceName(appName)), + roleBinding: inst.Managed.NewCRB(appName), + } + if parent.AvailableAPIs.HasSvcMonitor() { + objs.serviceMonitor = inst.Managed.NewServiceMonitor(serviceMonitorName(appName)) + } + if parent.AvailableAPIs.HasPromRule() { + objs.prometheusRule = inst.Managed.NewPrometheusRule(prometheusRuleName(appName)) + } + return &objs +} + +func (i *inProcessObjects) reconcile(ctx context.Context, desired *flowslatest.FlowCollector) (*InProcessInfo, error) { + // Retrieve current owned objects + err := i.Managed.FetchAll(ctx) + if err != nil { + return nil, err + } + + if helper.UseKafka(&desired.Spec) { + // No in-process with Kafka; remove attached resource + i.Managed.TryDeleteAll(ctx) + return nil, nil + } + + fm := metricslatest.FlowMetricList{} + if err := i.List(ctx, &fm, &client.ListOptions{Namespace: desired.Namespace}); err != nil { + return nil, i.Status.Error("CantListFlowMetrics", err) + } + + builder, err := newInProcessBuilder(i.Instance, i.appName, &desired.Spec, &fm) + if err != nil { + return nil, err + } + + cfg, err := builder.GetJSONConfig() + if err != nil { + return nil, err + } + + if err := reconcileRBAC(ctx, i.Common, constants.EBPFAgentName, constants.EBPFServiceAccount, i.Namespace, builder.desired.Loki.Mode); err != nil { + return nil, err + } + + if err := reconcilePrometheusService(ctx, i.Instance, i.promService, i.serviceMonitor, i.prometheusRule, builder); err != nil { + return nil, err + } + + annotations := map[string]string{} + // Watch for Loki certificate if necessary; we'll ignore in that case the returned digest, as we don't need to restart pods on cert rotation + // because certificate is always reloaded from file + if _, err := i.Watcher.ProcessCACert(ctx, i.Client, &i.Loki.TLS, i.Namespace); err != nil { + return nil, err + } + // Watch for Kafka exporter certificate if necessary; need to restart pods in case of cert rotation + if err := annotateKafkaExporterCerts(ctx, i.Common, desired.Spec.Exporters, annotations); err != nil { + return nil, err + } + // Watch for monitoring caCert + if err := reconcileMonitoringCerts(ctx, i.Common, &desired.Spec.Processor.Metrics.Server.TLS, i.Namespace); err != nil { + return nil, err + } + + return &InProcessInfo{ + Annotations: annotations, + Volumes: builder.volumes, + JSONConfig: cfg, + }, nil +} diff --git a/controllers/flp/in_process_reconciler.go b/controllers/flp/in_process_reconciler.go deleted file mode 100644 index 7b34bdbec..000000000 --- a/controllers/flp/in_process_reconciler.go +++ /dev/null @@ -1,124 +0,0 @@ -package flp - -import ( - "context" - - flowslatest "github.com/netobserv/network-observability-operator/apis/flowcollector/v1beta2" - metricslatest "github.com/netobserv/network-observability-operator/apis/flowmetrics/v1alpha1" - "github.com/netobserv/network-observability-operator/controllers/constants" - "github.com/netobserv/network-observability-operator/controllers/reconcilers" - "github.com/netobserv/network-observability-operator/pkg/volumes" - rbacv1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -type inProcessReconciler struct { - monolith *monolithReconciler -} - -type InProcessInfo struct { - JSONConfig string - Annotations map[string]string - Volumes volumes.Builder -} - -func ReconcileInProcess(ctx context.Context, parent *reconcilers.Instance, desired *flowslatest.FlowCollector) (*InProcessInfo, error) { - i := newInProcessReconciler(parent) - return i.reconcileInProcess(ctx, desired) -} - -func newInProcessReconciler(parent *reconcilers.Instance) *inProcessReconciler { - cloneInfo := *parent.Common - cloneInfo.Namespace = parent.PrivilegedNamespace() - inst := cloneInfo.NewInstance(parent.Image, parent.Status) - m := newMonolithReconciler(inst) - return &inProcessReconciler{monolith: m} -} - -func (i *inProcessReconciler) reconcileInProcess(ctx context.Context, desired *flowslatest.FlowCollector) (*InProcessInfo, error) { - result := InProcessInfo{} - - // Retrieve current owned objects - err := i.monolith.Managed.FetchAll(ctx) - if err != nil { - return nil, err - } - - fm := metricslatest.FlowMetricList{} - if err := i.monolith.List(ctx, &fm, &client.ListOptions{Namespace: desired.Namespace}); err != nil { - return nil, i.monolith.Status.Error("CantListFlowMetrics", err) - } - - builder, err := newMonolithBuilder(i.monolith.Instance, &desired.Spec, &fm) - if err != nil { - return nil, err - } - - // Override target app - builder.generic.overrideApp(constants.EBPFAgentName) - // Build pipeline - pipeline := builder.generic.NewInProcessPipeline() - err = pipeline.AddProcessorStages() - if err != nil { - return nil, err - } - cfg, err := builder.generic.GetJSONConfig() - if err != nil { - return nil, err - } - result.JSONConfig = cfg - - err = i.reconcileRoles(ctx, &builder) - if err != nil { - return nil, err - } - - err = i.monolith.reconcilePrometheusService(ctx, &builder) - if err != nil { - return nil, err - } - - annotations := map[string]string{} - err = i.monolith.reconcileCertificates(ctx, desired, annotations) - if err != nil { - return nil, err - } - result.Annotations = annotations - result.Volumes = builder.generic.volumes - return &result, nil -} - -func (i *inProcessReconciler) reconcileRoles(ctx context.Context, builder *monolithBuilder) error { - cr := BuildClusterRoleTransformer() - if err := i.monolith.ReconcileClusterRole(ctx, cr); err != nil { - return err - } - crb := &rbacv1.ClusterRoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: cr.Name + "-agent", - }, - RoleRef: rbacv1.RoleRef{ - APIGroup: "rbac.authorization.k8s.io", - Kind: "ClusterRole", - Name: cr.Name, - }, - Subjects: []rbacv1.Subject{{ - Kind: "ServiceAccount", - Name: constants.EBPFServiceAccount, - Namespace: i.monolith.Namespace, - }}, - } - if err := i.monolith.ReconcileClusterRoleBinding(ctx, crb); err != nil { - return err - } - - return ReconcileLokiRoles( - ctx, - i.monolith.Common, - builder.generic.desired, - constants.EBPFAgentName, - constants.EBPFServiceAccount, - i.monolith.Namespace, - ) -} diff --git a/controllers/flp/metrics_api_test.go b/controllers/flp/metrics_api_test.go index 022d54d14..b5b27ef18 100644 --- a/controllers/flp/metrics_api_test.go +++ b/controllers/flp/metrics_api_test.go @@ -11,6 +11,7 @@ import ( corev1 "k8s.io/api/core/v1" metricslatest "github.com/netobserv/network-observability-operator/apis/flowmetrics/v1alpha1" + "github.com/netobserv/network-observability-operator/controllers/constants" "github.com/netobserv/network-observability-operator/controllers/reconcilers" "github.com/netobserv/network-observability-operator/pkg/helper" "github.com/netobserv/network-observability-operator/pkg/manager/status" @@ -30,11 +31,11 @@ func getConfiguredMetrics(cm *corev1.ConfigMap) (api.MetricsItems, error) { return nil, errors.New("prom encode stage not found") } -func defaultBuilderWithMetrics(metrics *metricslatest.FlowMetricList) (monolithBuilder, error) { +func defaultBuilderWithMetrics(metrics *metricslatest.FlowMetricList) (*Builder, error) { cfg := getConfig() loki := helper.NewLokiConfig(&cfg.Loki, "any") info := reconcilers.Common{Namespace: "namespace", Loki: &loki} - return newMonolithBuilder(info.NewInstance(image, status.Instance{}), &cfg, metrics) + return newInProcessBuilder(info.NewInstance(image, status.Instance{}), constants.FLPName, &cfg, metrics) } func metric(metrics api.MetricsItems, name string) *api.MetricsItem { diff --git a/controllers/monitoring/monitoring_controller_test.go b/controllers/monitoring/monitoring_controller_test.go index 84b9628d0..528f193ea 100644 --- a/controllers/monitoring/monitoring_controller_test.go +++ b/controllers/monitoring/monitoring_controller_test.go @@ -8,9 +8,9 @@ import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" flowslatest "github.com/netobserv/network-observability-operator/apis/flowcollector/v1beta2" - . "github.com/netobserv/network-observability-operator/controllers/controllerstest" "github.com/netobserv/network-observability-operator/pkg/dashboards" "github.com/netobserv/network-observability-operator/pkg/test" ) @@ -27,21 +27,28 @@ var ( updateCR = func(key types.NamespacedName, updater func(*flowslatest.FlowCollector)) { test.UpdateCR(ctx, k8sClient, key, updater) } - getCR = func(key types.NamespacedName) *flowslatest.FlowCollector { - return test.GetCR(ctx, k8sClient, key) - } cleanupCR = func(key types.NamespacedName) { test.CleanupCR(ctx, k8sClient, key) } + expectCreation = func(namespace string, objs ...test.ResourceRef) []client.Object { + GinkgoHelper() + return test.ExpectCreation(ctx, k8sClient, namespace, objs...) + } + expectDeletion = func(namespace string, objs ...test.ResourceRef) { + GinkgoHelper() + test.ExpectDeletion(ctx, k8sClient, namespace, objs...) + } + expectOwnership = func(namespace string, objs ...test.ResourceRef) { + GinkgoHelper() + test.ExpectOwnership(ctx, k8sClient, namespace, objs...) + } ) // nolint:cyclop func ControllerSpecs() { const operatorNamespace = "main-namespace" - crKey := types.NamespacedName{ - Name: "cluster", - } + crKey := types.NamespacedName{Name: "cluster"} BeforeEach(func() { // Add any setup steps that needs to be executed before each test @@ -66,30 +73,15 @@ func ControllerSpecs() { // Create Expect(k8sClient.Create(ctx, created)).Should(Succeed()) - By("Expecting the monitoring dashboards configmap to be created") - Eventually(func() interface{} { - cm := v1.ConfigMap{} - return k8sClient.Get(ctx, types.NamespacedName{ - Name: "grafana-dashboard-netobserv-flow-metrics", - Namespace: "openshift-config-managed", - }, &cm) - }, timeout, interval).Should(Succeed()) - - By("Expecting the infra health dashboards configmap to be created") - Eventually(func() interface{} { - cm := v1.ConfigMap{} - if err := k8sClient.Get(ctx, types.NamespacedName{ - Name: "grafana-dashboard-netobserv-health", - Namespace: "openshift-config-managed", - }, &cm); err != nil { - return err - } - d, err := dashboards.FromBytes([]byte(cm.Data["netobserv-health-metrics.json"])) - if err != nil { - return err - } - return d.Titles() - }, timeout, interval).Should(Equal([]string{ + objs := expectCreation("openshift-config-managed", + test.ConfigMap("grafana-dashboard-netobserv-flow-metrics"), + test.ConfigMap("grafana-dashboard-netobserv-health"), + ) + Expect(objs).To(HaveLen(2)) + healthCM := objs[1].(*v1.ConfigMap) + d, err := dashboards.FromBytes([]byte(healthCM.Data["netobserv-health-metrics.json"])) + Expect(err).To(BeNil()) + Expect(d.Titles()).To(Equal([]string{ "Flows", "Flows Overhead", "Top flow rates per source and destination namespaces", @@ -109,13 +101,9 @@ func ControllerSpecs() { } }) - By("Expecting the flow dashboards configmap to be deleted") - Eventually(func() interface{} { - return k8sClient.Get(ctx, types.NamespacedName{ - Name: "grafana-dashboard-netobserv-flow-metrics", - Namespace: "openshift-config-managed", - }, &v1.ConfigMap{}) - }, timeout, interval).Should(MatchError(`configmaps "grafana-dashboard-netobserv-flow-metrics" not found`)) + expectDeletion("openshift-config-managed", + test.ConfigMap("grafana-dashboard-netobserv-flow-metrics"), + ) By("Expecting the health dashboards rows to be filtered") Eventually(func() interface{} { @@ -142,19 +130,9 @@ func ControllerSpecs() { Context("Checking CR ownership", func() { It("Should be garbage collected", func() { - // Retrieve CR to get its UID - By("Getting the CR") - flowCR := getCR(crKey) - - By("Expecting the health dashboards configmap to be garbage collected") - Eventually(func() interface{} { - cm := v1.ConfigMap{} - _ = k8sClient.Get(ctx, types.NamespacedName{ - Name: "grafana-dashboard-netobserv-health", - Namespace: "openshift-config-managed", - }, &cm) - return &cm - }, timeout, interval).Should(BeGarbageCollectedBy(flowCR)) + expectOwnership("openshift-config-managed", + test.ConfigMap("grafana-dashboard-netobserv-health"), + ) }) }) diff --git a/controllers/reconcilers/common.go b/controllers/reconcilers/common.go index 3b5111877..6f0483bf2 100644 --- a/controllers/reconcilers/common.go +++ b/controllers/reconcilers/common.go @@ -64,6 +64,10 @@ func (c *Common) ReconcileRole(ctx context.Context, desired *rbacv1.Role) error return ReconcileRole(ctx, &c.Client, desired) } +func (c *Common) ReconcileServiceAccount(ctx context.Context, desired *corev1.ServiceAccount) error { + return ReconcileServiceAccount(ctx, &c.Client, desired) +} + func (c *Common) ReconcileConfigMap(ctx context.Context, desired *corev1.ConfigMap, delete bool) error { return ReconcileConfigMap(ctx, &c.Client, desired, delete) } diff --git a/controllers/reconcilers/reconcilers.go b/controllers/reconcilers/reconcilers.go index a2305a052..fe8dbe8c1 100644 --- a/controllers/reconcilers/reconcilers.go +++ b/controllers/reconcilers/reconcilers.go @@ -125,6 +125,23 @@ func ReconcileRole(ctx context.Context, cl *helper.Client, desired *rbacv1.Role) return cl.UpdateIfOwned(ctx, &actual, desired) } +func ReconcileServiceAccount(ctx context.Context, cl *helper.Client, desired *corev1.ServiceAccount) error { + actual := corev1.ServiceAccount{} + if err := cl.Get(ctx, types.NamespacedName{Name: desired.Name, Namespace: desired.Namespace}, &actual); err != nil { + if errors.IsNotFound(err) { + return cl.CreateOwned(ctx, desired) + } + return fmt.Errorf("can't reconcile Service Account %s: %w", desired.Name, err) + } + + if helper.IsSubSet(actual.Labels, desired.Labels) { + // sa already reconciled. Exiting + return nil + } + + return cl.UpdateIfOwned(ctx, &actual, desired) +} + func ReconcileConfigMap(ctx context.Context, cl *helper.Client, desired *corev1.ConfigMap, delete bool) error { actual := corev1.ConfigMap{} if err := cl.Get(ctx, types.NamespacedName{Name: desired.Name, Namespace: desired.Namespace}, &actual); err != nil { diff --git a/docs/FlowCollector.md b/docs/FlowCollector.md index 4fcc554e8..72bd9c208 100644 --- a/docs/FlowCollector.md +++ b/docs/FlowCollector.md @@ -3503,7 +3503,7 @@ TLS client configuration for Loki URL. port integer - Port of the flow collector (host port). By convention, some values are forbidden. It must be greater than 1024 and different from 4500, 4789 and 6081.
+ [Deprecated (*)] Port of the flow collector (host port). It is not used anymore and will be removed in a future version.

Format: int32
Default: 2055
@@ -9046,7 +9046,7 @@ TLS client configuration for Loki URL. port integer - Port of the flow collector (host port). By convention, some values are forbidden. It must be greater than 1024 and different from 4500, 4789 and 6081.
+ [Deprecated (*)] Port of the flow collector (host port). It is not used anymore and will be removed in a future version.

Format: int32
Default: 2055
diff --git a/go.mod b/go.mod index 8b05f217a..f8dba5d5f 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/prometheus/common v0.48.0 github.com/stretchr/testify v1.8.4 go.uber.org/zap v1.26.0 - gopkg.in/yaml.v2 v2.4.0 + gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.29.2 k8s.io/apiextensions-apiserver v0.29.2 k8s.io/apimachinery v0.29.2 @@ -74,7 +74,7 @@ require ( google.golang.org/appengine v1.6.8 // indirect google.golang.org/protobuf v1.32.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/component-base v0.29.2 // indirect k8s.io/klog/v2 v2.110.1 // indirect k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect diff --git a/hack/resources-inventory.sh b/hack/resources-inventory.sh new file mode 100755 index 000000000..0c0ad9b98 --- /dev/null +++ b/hack/resources-inventory.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +namespaced_res="daemonsets,deployments,horizontalpodautoscalers,configmaps,secrets,serviceaccounts,services,pods,services,prometheusrules,servicemonitors" + +kubectl get ${namespaced_res} --show-kind --ignore-not-found -n netobserv +echo "" +kubectl get ${namespaced_res} --show-kind --ignore-not-found -n netobserv-privileged +echo "" +echo "CLUSTER ROLES AND BINDINGS" +kubectl get clusterroles,clusterrolebindings --show-kind --ignore-not-found | grep netobserv +kubectl get clusterroles,clusterrolebindings --show-kind --ignore-not-found | grep flowlogs diff --git a/pkg/helper/flowcollector.go b/pkg/helper/flowcollector.go index 855d2b8a8..21ce2c6e1 100644 --- a/pkg/helper/flowcollector.go +++ b/pkg/helper/flowcollector.go @@ -43,10 +43,6 @@ func UseKafka(spec *flowslatest.FlowCollectorSpec) bool { return spec.DeploymentModel == flowslatest.DeploymentModelKafka } -func UseMergedAgentFLP(spec *flowslatest.FlowCollectorSpec) bool { - return spec.DeploymentModel == flowslatest.DeploymentModelDirect && spec.Agent.Type == flowslatest.AgentEBPF -} - func HasKafkaExporter(spec *flowslatest.FlowCollectorSpec) bool { for _, ex := range spec.Exporters { if ex.Type == flowslatest.KafkaExporter { diff --git a/pkg/manager/manager.go b/pkg/manager/manager.go index 6c90cea59..1f48bebe7 100644 --- a/pkg/manager/manager.go +++ b/pkg/manager/manager.go @@ -18,6 +18,7 @@ import ( //+kubebuilder:rbac:groups=apps,resources=deployments;daemonsets,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=core,resources=namespaces;services;serviceaccounts;configmaps;secrets,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=core,resources=endpoints,verbs=get;list;watch +//+kubebuilder:rbac:groups=autoscaling,resources=horizontalpodautoscalers,verbs=create;delete;patch;update;get;watch;list //+kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterrolebindings;clusterroles;rolebindings;roles,verbs=get;list;create;delete;update;watch //+kubebuilder:rbac:groups=console.openshift.io,resources=consoleplugins,verbs=get;create;delete;update;patch;list;watch //+kubebuilder:rbac:groups=operator.openshift.io,resources=consoles,verbs=get;update;list;update;watch @@ -25,7 +26,6 @@ import ( //+kubebuilder:rbac:groups=flows.netobserv.io,resources=flowcollectors/status,verbs=get;update;patch //+kubebuilder:rbac:groups=flows.netobserv.io,resources=flowcollectors/finalizers,verbs=update //+kubebuilder:rbac:groups=flows.netobserv.io,resources=flowmetrics,verbs=get;list;watch -//+kubebuilder:rbac:groups=security.openshift.io,resources=securitycontextconstraints,resourceNames=hostnetwork,verbs=use //+kubebuilder:rbac:groups=security.openshift.io,resources=securitycontextconstraints,verbs=list;create;update;watch //+kubebuilder:rbac:groups=apiregistration.k8s.io,resources=apiservices,verbs=list;get;watch //+kubebuilder:rbac:groups=monitoring.coreos.com,resources=servicemonitors;prometheusrules,verbs=get;create;delete;update;patch;list;watch diff --git a/pkg/manager/status/status_manager.go b/pkg/manager/status/status_manager.go index 1742ace0a..0f7e89f01 100644 --- a/pkg/manager/status/status_manager.go +++ b/pkg/manager/status/status_manager.go @@ -21,9 +21,7 @@ type ComponentName string const ( FlowCollectorLegacy ComponentName = "FlowCollectorLegacy" - FLPParent ComponentName = "FLPParent" - FLPMonolith ComponentName = "FLPMonolith" - FLPTransformOnly ComponentName = "FLPTransformOnly" + FLP ComponentName = "FLP" Monitoring ComponentName = "Monitoring" ) diff --git a/pkg/test/envtest.go b/pkg/test/envtest.go index d1975ca54..06c0ac158 100644 --- a/pkg/test/envtest.go +++ b/pkg/test/envtest.go @@ -37,8 +37,10 @@ import ( ) const ( - Timeout = time.Second * 10 - Interval = 1 * time.Second + Timeout = time.Second * 10 + Interval = 1 * time.Second + ConsistentlyTimeout = 2 * time.Second + ConsistentlyInterval = 500 * time.Millisecond ) func PrepareEnvTest(controllers []manager.Registerer, namespaces []string, basePath string) (context.Context, client.Client, *envtest.Environment, context.CancelFunc) { diff --git a/controllers/controllerstest/garbage_collected_matcher.go b/pkg/test/garbage_collected_matcher.go similarity index 98% rename from controllers/controllerstest/garbage_collected_matcher.go rename to pkg/test/garbage_collected_matcher.go index e2c52b8f3..0bdbbf7f6 100644 --- a/controllers/controllerstest/garbage_collected_matcher.go +++ b/pkg/test/garbage_collected_matcher.go @@ -1,4 +1,4 @@ -package controllerstest +package test import ( "fmt" diff --git a/pkg/test/util.go b/pkg/test/util.go new file mode 100644 index 000000000..9b088de6c --- /dev/null +++ b/pkg/test/util.go @@ -0,0 +1,114 @@ +package test + +import ( + "context" + "fmt" + + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" + monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" + appsv1 "k8s.io/api/apps/v1" + v1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type ResourceRef struct { + name string + Resource client.Object + kind string + pluralKind string +} + +func (r *ResourceRef) GetKey(ns string) types.NamespacedName { + return types.NamespacedName{Name: r.name, Namespace: ns} +} + +func ExpectCreation(ctx context.Context, k8sClient client.Client, namespace string, objs ...ResourceRef) []client.Object { + ginkgo.GinkgoHelper() + var refs []client.Object + for _, obj := range objs { + refs = append(refs, obj.Resource) + } + for _, obj := range objs { + ginkgo.By(fmt.Sprintf(`Expecting to create "%s" %s`, obj.name, obj.kind)) + gomega.Eventually(func() interface{} { + return k8sClient.Get(ctx, types.NamespacedName{Name: obj.name, Namespace: namespace}, obj.Resource) + }).WithTimeout(Timeout).WithPolling(Interval).Should(gomega.Succeed()) + } + return refs +} + +func ExpectDeletion(ctx context.Context, k8sClient client.Client, namespace string, objs ...ResourceRef) { + ginkgo.GinkgoHelper() + for _, obj := range objs { + ginkgo.By(fmt.Sprintf(`Expecting to delete "%s" %s`, obj.name, obj.kind)) + gomega.Eventually(func() interface{} { + return k8sClient.Get(ctx, types.NamespacedName{Name: obj.name, Namespace: namespace}, obj.Resource) + }).WithTimeout(Timeout).WithPolling(Interval).Should(gomega.MatchError(fmt.Sprintf(`%s "%s" not found`, obj.pluralKind, obj.name))) + } +} + +func ExpectNoCreation(ctx context.Context, k8sClient client.Client, namespace string, objs ...ResourceRef) { + ginkgo.GinkgoHelper() + for _, obj := range objs { + ginkgo.By(fmt.Sprintf(`Expecting to not create "%s" %s`, obj.name, obj.kind)) + gomega.Consistently(func() error { + return k8sClient.Get(ctx, types.NamespacedName{Name: obj.name, Namespace: namespace}, obj.Resource) + }, ConsistentlyTimeout, ConsistentlyInterval).Should(gomega.MatchError(fmt.Sprintf(`%s "%s" not found`, obj.pluralKind, obj.name))) + } +} + +func ExpectOwnership(ctx context.Context, k8sClient client.Client, namespace string, objs ...ResourceRef) { + ginkgo.GinkgoHelper() + // Retrieve CR to get its UID + ginkgo.By("Getting the CR") + flowCR := GetCR(ctx, k8sClient, types.NamespacedName{Name: "cluster"}) + for _, obj := range objs { + gomega.Eventually(func() interface{} { + _ = k8sClient.Get(ctx, types.NamespacedName{Name: obj.name, Namespace: namespace}, obj.Resource) + return obj.Resource + }).WithTimeout(Timeout).WithPolling(Interval).Should(BeGarbageCollectedBy(flowCR)) + } +} + +func Namespace(name string) ResourceRef { + return ResourceRef{name: name, Resource: &v1.Namespace{}, kind: "Namespace", pluralKind: "namespaces"} +} + +func ConfigMap(name string) ResourceRef { + return ResourceRef{name: name, Resource: &v1.ConfigMap{}, kind: "ConfigMap", pluralKind: "configmaps"} +} + +func Service(name string) ResourceRef { + return ResourceRef{name: name, Resource: &v1.Service{}, kind: "Service", pluralKind: "services"} +} + +func ServiceAccount(name string) ResourceRef { + return ResourceRef{name: name, Resource: &v1.ServiceAccount{}, kind: "ServiceAccount", pluralKind: "serviceaccounts"} +} + +func Deployment(name string) ResourceRef { + return ResourceRef{name: name, Resource: &appsv1.Deployment{}, kind: "Deployment", pluralKind: "deployments.apps"} +} + +func DaemonSet(name string) ResourceRef { + return ResourceRef{name: name, Resource: &appsv1.DaemonSet{}, kind: "DaemonSet", pluralKind: "daemonsets.apps"} +} + +func ClusterRole(name string) ResourceRef { + return ResourceRef{name: name, Resource: &rbacv1.ClusterRole{}, kind: "ClusterRole", pluralKind: "clusterroles"} +} + +func ClusterRoleBinding(name string) ResourceRef { + return ResourceRef{name: name, Resource: &rbacv1.ClusterRoleBinding{}, kind: "ClusterRoleBinding", pluralKind: "clusterrolebindings"} +} + +func ServiceMonitor(name string) ResourceRef { + return ResourceRef{name: name, Resource: &monitoringv1.ServiceMonitor{}, kind: "ServiceMonitor", pluralKind: "servicemonitors.monitoring.coreos.com"} +} + +func PrometheusRule(name string) ResourceRef { + return ResourceRef{name: name, Resource: &monitoringv1.PrometheusRule{}, kind: "PrometheusRule", pluralKind: "prometheusrules.monitoring.coreos.com"} +}