diff --git a/.mk/local.mk b/.mk/local.mk index 13e197291..1fb14a7a8 100644 --- a/.mk/local.mk +++ b/.mk/local.mk @@ -50,7 +50,8 @@ local-deploy-operator: go run ./main.go \ -ebpf-agent-image=quay.io/netobserv/netobserv-ebpf-agent:main \ -flowlogs-pipeline-image=quay.io/netobserv/flowlogs-pipeline:main \ - -console-plugin-image=quay.io/netobserv/network-observability-console-plugin:main & + -console-plugin-image=quay.io/netobserv/network-observability-console-plugin:main \ + -namespace=${NAMESPACE} & @echo "====> Waiting for flowlogs-pipeline pod to be ready" while : ; do kubectl get ds flowlogs-pipeline && break; sleep 1; done kubectl wait --timeout=180s --for=condition=ready pod -l app=flowlogs-pipeline diff --git a/.mk/ocp.mk b/.mk/ocp.mk index a580df580..b9ccf2f12 100644 --- a/.mk/ocp.mk +++ b/.mk/ocp.mk @@ -35,7 +35,8 @@ ocp-deploy-operator: ## run flp from the operator go run ./main.go \ -ebpf-agent-image=quay.io/netobserv/netobserv-ebpf-agent:main \ -flowlogs-pipeline-image=quay.io/netobserv/flowlogs-pipeline:main \ - -console-plugin-image=quay.io/netobserv/network-observability-console-plugin:main + -console-plugin-image=quay.io/netobserv/network-observability-console-plugin:main \ + -namespace=${NAMESPACE} .PHONY: undeploy-operator undeploy-operator: ## stop the operator locally diff --git a/bundle/manifests/netobserv-operator.clusterserviceversion.yaml b/bundle/manifests/netobserv-operator.clusterserviceversion.yaml index 24aaf17f3..f5a7f58df 100644 --- a/bundle/manifests/netobserv-operator.clusterserviceversion.yaml +++ b/bundle/manifests/netobserv-operator.clusterserviceversion.yaml @@ -1243,6 +1243,7 @@ spec: - --flowlogs-pipeline-image=$(RELATED_IMAGE_FLOWLOGS_PIPELINE) - --console-plugin-image=$(RELATED_IMAGE_CONSOLE_PLUGIN) - --console-plugin-compat-image=$(RELATED_IMAGE_CONSOLE_PLUGIN_COMPAT) + - --namespace=$(NAMESPACE) - --downstream-deployment=$(DOWNSTREAM_DEPLOYMENT) - --profiling-bind-address=$(PROFILING_BIND_ADDRESS) - --metrics-cert-file=/etc/tls/private/tls.crt @@ -1261,6 +1262,10 @@ spec: - name: DOWNSTREAM_DEPLOYMENT value: "false" - name: PROFILING_BIND_ADDRESS + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace image: quay.io/netobserv/network-observability-operator:1.9.1-community imagePullPolicy: Always livenessProbe: diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 30a45a484..460a3892a 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -28,6 +28,7 @@ spec: - --flowlogs-pipeline-image=$(RELATED_IMAGE_FLOWLOGS_PIPELINE) - --console-plugin-image=$(RELATED_IMAGE_CONSOLE_PLUGIN) - --console-plugin-compat-image=$(RELATED_IMAGE_CONSOLE_PLUGIN_COMPAT) + - --namespace=$(NAMESPACE) - --downstream-deployment=$(DOWNSTREAM_DEPLOYMENT) - --profiling-bind-address=$(PROFILING_BIND_ADDRESS) env: @@ -43,6 +44,10 @@ spec: value: "false" - name: PROFILING_BIND_ADDRESS value: "" + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace image: controller:latest name: manager imagePullPolicy: Always diff --git a/helm/templates/deployment.yaml b/helm/templates/deployment.yaml index 96aefd633..ccfbf05b4 100644 --- a/helm/templates/deployment.yaml +++ b/helm/templates/deployment.yaml @@ -27,6 +27,7 @@ spec: - --flowlogs-pipeline-image=$(RELATED_IMAGE_FLOWLOGS_PIPELINE) - --console-plugin-image=$(RELATED_IMAGE_CONSOLE_PLUGIN) - --console-plugin-compat-image=$(RELATED_IMAGE_CONSOLE_PLUGIN_COMPAT) + - --namespace=$(NAMESPACE) - --downstream-deployment=$(DOWNSTREAM_DEPLOYMENT) - --profiling-bind-address=$(PROFILING_BIND_ADDRESS) - --metrics-cert-file=/etc/tls/private/tls.crt @@ -43,6 +44,10 @@ spec: - name: DOWNSTREAM_DEPLOYMENT value: "false" - name: PROFILING_BIND_ADDRESS + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace image: '{{ .Values.operator.image }}:{{ .Values.operator.version }}' imagePullPolicy: Always livenessProbe: diff --git a/internal/controller/consoleplugin/consoleplugin_objects.go b/internal/controller/consoleplugin/consoleplugin_objects.go index 6c14de3be..d92b3ce66 100644 --- a/internal/controller/consoleplugin/consoleplugin_objects.go +++ b/internal/controller/consoleplugin/consoleplugin_objects.go @@ -31,8 +31,6 @@ import ( "github.com/netobserv/network-observability-operator/internal/pkg/volumes" ) -const secretName = "console-serving-cert" -const displayName = "NetObserv plugin" const proxyAlias = "backend" const configMapName = "console-plugin-config" @@ -53,7 +51,7 @@ type builder struct { volumes volumes.Builder } -func newBuilder(info *reconcilers.Instance, desired *flowslatest.FlowCollectorSpec) builder { +func newBuilder(info *reconcilers.Instance, desired *flowslatest.FlowCollectorSpec, name string) builder { imageToUse := reconcilers.MainImage needsPF4, err := info.ClusterInfo.IsOpenShiftVersionLessThan("4.15.0") if err == nil && needsPF4 { @@ -66,28 +64,28 @@ func newBuilder(info *reconcilers.Instance, desired *flowslatest.FlowCollectorSp info: info, imageRef: imageToUse, labels: map[string]string{ - "app": constants.PluginName, + "app": name, "version": helper.MaxLabelLength(version), }, selector: map[string]string{ - "app": constants.PluginName, + "app": name, }, desired: desired, advanced: &advanced, } } -func (b *builder) consolePlugin() *osv1.ConsolePlugin { +func (b *builder) consolePlugin(name, displayName string) *osv1.ConsolePlugin { return &osv1.ConsolePlugin{ ObjectMeta: metav1.ObjectMeta{ - Name: constants.PluginName, + Name: name, }, Spec: osv1.ConsolePluginSpec{ DisplayName: displayName, Backend: osv1.ConsolePluginBackend{ Type: osv1.Service, Service: &osv1.ConsolePluginService{ - Name: constants.PluginName, + Name: name, Namespace: b.info.Namespace, Port: *b.advanced.Port, BasePath: "/"}, @@ -97,7 +95,7 @@ func (b *builder) consolePlugin() *osv1.ConsolePlugin { Endpoint: osv1.ConsolePluginProxyEndpoint{ Type: osv1.ProxyTypeService, Service: &osv1.ConsolePluginProxyServiceConfig{ - Name: constants.PluginName, + Name: name, Namespace: b.info.Namespace, Port: *b.advanced.Port}}, Alias: proxyAlias, @@ -139,14 +137,14 @@ func (b *builder) serviceMonitor() *monitoringv1.ServiceMonitor { Cert: monitoringv1.SecretOrConfigMap{ Secret: &corev1.SecretKeySelector{ LocalObjectReference: corev1.LocalObjectReference{ - Name: secretName, + Name: fmt.Sprintf("%s-cert", constants.PluginName), }, Key: "tls.crt", }, }, KeySecret: &corev1.SecretKeySelector{ LocalObjectReference: corev1.LocalObjectReference{ - Name: secretName, + Name: fmt.Sprintf("%s-cert", constants.PluginName), }, Key: "tls.key", }, @@ -168,10 +166,10 @@ func (b *builder) serviceMonitor() *monitoringv1.ServiceMonitor { } } -func (b *builder) deployment(cmDigest string) *appsv1.Deployment { +func (b *builder) deployment(name, cmDigest string) *appsv1.Deployment { return &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ - Name: constants.PluginName, + Name: name, Namespace: b.info.Namespace, Labels: b.labels, }, @@ -180,14 +178,27 @@ func (b *builder) deployment(cmDigest string) *appsv1.Deployment { Selector: &metav1.LabelSelector{ MatchLabels: b.selector, }, - Template: *b.podTemplate(cmDigest), + Template: *b.podTemplate(name, cmDigest), }, } } -func (b *builder) podTemplate(cmDigest string) *corev1.PodTemplateSpec { - volumes := []corev1.Volume{ - { +func (b *builder) podTemplate(name, cmDigest string) *corev1.PodTemplateSpec { + var sa string + annotations := map[string]string{} + args := []string{ + "-loglevel", b.desired.ConsolePlugin.LogLevel, + } + volumes := []corev1.Volume{} + volumeMounts := []corev1.VolumeMount{} + + if cmDigest != "" { + sa = name + annotations[constants.PodConfigurationDigest] = cmDigest + + args = append(args, "-config", filepath.Join(configPath, configFile)) + + volumes = append(volumes, corev1.Volume{ Name: configVolume, VolumeSource: corev1.VolumeSource{ ConfigMap: &corev1.ConfigMapVolumeSource{ @@ -196,28 +207,26 @@ func (b *builder) podTemplate(cmDigest string) *corev1.PodTemplateSpec { }, }, }, - }, - } + }) - volumeMounts := []corev1.VolumeMount{ - { + volumeMounts = append(volumeMounts, corev1.VolumeMount{ Name: configVolume, MountPath: configPath, ReadOnly: true, - }, + }) } if !helper.UseTestConsolePlugin(b.desired) { volumes = append(volumes, corev1.Volume{ - Name: secretName, + Name: fmt.Sprintf("%s-cert", name), VolumeSource: corev1.VolumeSource{ Secret: &corev1.SecretVolumeSource{ - SecretName: secretName, + SecretName: fmt.Sprintf("%s-cert", name), }, }, }) volumeMounts = append(volumeMounts, corev1.VolumeMount{ - Name: secretName, + Name: fmt.Sprintf("%s-cert", name), MountPath: "/var/serving-cert", ReadOnly: true, }) @@ -225,28 +234,22 @@ func (b *builder) podTemplate(cmDigest string) *corev1.PodTemplateSpec { return &corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ - Labels: b.labels, - Annotations: map[string]string{ - constants.PodConfigurationDigest: cmDigest, - }, + Labels: b.labels, + Annotations: annotations, }, Spec: corev1.PodSpec{ Containers: []corev1.Container{{ - Name: constants.PluginName, + Name: name, Image: b.info.Images[b.imageRef], ImagePullPolicy: corev1.PullPolicy(b.desired.ConsolePlugin.ImagePullPolicy), Resources: *b.desired.ConsolePlugin.Resources.DeepCopy(), VolumeMounts: b.volumes.AppendMounts(volumeMounts), Env: []corev1.EnvVar{constants.EnvNoHTTP2}, - Args: []string{ - - "-loglevel", b.desired.ConsolePlugin.LogLevel, - "-config", filepath.Join(configPath, configFile), - }, + Args: args, SecurityContext: helper.ContainerDefaultSecurityContext(), }}, Volumes: b.volumes.AppendVolumes(volumes), - ServiceAccountName: constants.PluginName, + ServiceAccountName: sa, NodeSelector: b.advanced.Scheduling.NodeSelector, Tolerations: b.advanced.Scheduling.Tolerations, Affinity: b.advanced.Scheduling.Affinity, @@ -275,14 +278,14 @@ func (b *builder) autoScaler() *ascv2.HorizontalPodAutoscaler { } } -func (b *builder) mainService() *corev1.Service { +func (b *builder) mainService(name string) *corev1.Service { return &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ - Name: constants.PluginName, + Name: name, Namespace: b.info.Namespace, Labels: b.labels, Annotations: map[string]string{ - constants.OpenShiftCertificateAnnotation: "console-serving-cert", + constants.OpenShiftCertificateAnnotation: fmt.Sprintf("%s-cert", name), }, }, Spec: corev1.ServiceSpec{ @@ -550,13 +553,13 @@ func (b *builder) configMap(ctx context.Context) (*corev1.ConfigMap, string, err return &configMap, digest, nil } -func (b *builder) serviceAccount() *corev1.ServiceAccount { +func (b *builder) serviceAccount(name string) *corev1.ServiceAccount { return &corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ - Name: constants.PluginName, + Name: name, Namespace: b.info.Namespace, Labels: map[string]string{ - "app": constants.PluginName, + "app": name, }, }, } diff --git a/internal/controller/consoleplugin/consoleplugin_reconciler.go b/internal/controller/consoleplugin/consoleplugin_reconciler.go index e78ed74a2..59e7362c1 100644 --- a/internal/controller/consoleplugin/consoleplugin_reconciler.go +++ b/internal/controller/consoleplugin/consoleplugin_reconciler.go @@ -64,21 +64,21 @@ func (r *CPReconciler) Reconcile(ctx context.Context, desired *flowslatest.FlowC } if r.ClusterInfo.HasConsolePlugin() { - if err = r.checkAutoPatch(ctx, desired); err != nil { + if err = r.checkAutoPatch(ctx, desired, constants.PluginName); err != nil { return err } } if helper.UseConsolePlugin(&desired.Spec) && (r.ClusterInfo.HasConsolePlugin() || helper.UseTestConsolePlugin(&desired.Spec)) { // Create object builder - builder := newBuilder(r.Instance, &desired.Spec) + builder := newBuilder(r.Instance, &desired.Spec, constants.PluginName) - if err := r.reconcilePermissions(ctx, &builder); err != nil { + if err := r.reconcilePermissions(ctx, &builder, constants.PluginName); err != nil { return err } if r.ClusterInfo.HasConsolePlugin() { - if err = r.reconcilePlugin(ctx, &builder, &desired.Spec); err != nil { + if err = r.reconcilePlugin(ctx, &builder, &desired.Spec, constants.PluginName, "NetObserv plugin"); err != nil { return err } } @@ -88,11 +88,11 @@ func (r *CPReconciler) Reconcile(ctx context.Context, desired *flowslatest.FlowC return err } - if err = r.reconcileDeployment(ctx, &builder, &desired.Spec, cmDigest); err != nil { + if err = r.reconcileDeployment(ctx, &builder, &desired.Spec, constants.PluginName, cmDigest); err != nil { return err } - if err = r.reconcileServices(ctx, &builder); err != nil { + if err = r.reconcileServices(ctx, &builder, constants.PluginName); err != nil { return err } @@ -118,7 +118,7 @@ func (r *CPReconciler) Reconcile(ctx context.Context, desired *flowslatest.FlowC return nil } -func (r *CPReconciler) checkAutoPatch(ctx context.Context, desired *flowslatest.FlowCollector) error { +func (r *CPReconciler) checkAutoPatch(ctx context.Context, desired *flowslatest.FlowCollector, name string) error { console := operatorsv1.Console{} advancedConfig := helper.GetAdvancedPluginConfig(desired.Spec.ConsolePlugin.Advanced) reg := helper.UseConsolePlugin(&desired.Spec) && *advancedConfig.Register @@ -129,34 +129,34 @@ func (r *CPReconciler) checkAutoPatch(ctx context.Context, desired *flowslatest. } return nil } - registered := helper.ContainsString(console.Spec.Plugins, constants.PluginName) + registered := helper.ContainsString(console.Spec.Plugins, name) if reg && !registered { - console.Spec.Plugins = append(console.Spec.Plugins, constants.PluginName) + console.Spec.Plugins = append(console.Spec.Plugins, name) return r.Client.Update(ctx, &console) } return nil } -func (r *CPReconciler) reconcilePermissions(ctx context.Context, builder *builder) error { +func (r *CPReconciler) reconcilePermissions(ctx context.Context, builder *builder, name string) error { if !r.Managed.Exists(r.serviceAccount) { - return r.CreateOwned(ctx, builder.serviceAccount()) + return r.CreateOwned(ctx, builder.serviceAccount(name)) } // update not needed for now binding := resources.GetClusterRoleBinding( r.Namespace, constants.PluginShortName, - constants.PluginName, - constants.PluginName, + name, + name, constants.ConsoleTokenReviewRole, ) return r.ReconcileClusterRoleBinding(ctx, binding) } -func (r *CPReconciler) reconcilePlugin(ctx context.Context, builder *builder, desired *flowslatest.FlowCollectorSpec) error { +func (r *CPReconciler) reconcilePlugin(ctx context.Context, builder *builder, desired *flowslatest.FlowCollectorSpec, name, displayName string) error { // Console plugin is cluster-scope (it's not deployed in our namespace) however it must still be updated if our namespace changes oldPlg := osv1.ConsolePlugin{} pluginExists := true - err := r.Get(ctx, types.NamespacedName{Name: constants.PluginName}, &oldPlg) + err := r.Get(ctx, types.NamespacedName{Name: name}, &oldPlg) if err != nil { if errors.IsNotFound(err) { pluginExists = false @@ -166,7 +166,7 @@ func (r *CPReconciler) reconcilePlugin(ctx context.Context, builder *builder, de } // Check if objects need update - consolePlugin := builder.consolePlugin() + consolePlugin := builder.consolePlugin(name, displayName) if !pluginExists { if err := r.CreateOwned(ctx, consolePlugin); err != nil { return err @@ -196,7 +196,7 @@ func (r *CPReconciler) reconcileConfigMap(ctx context.Context, builder *builder) return configDigest, nil } -func (r *CPReconciler) reconcileDeployment(ctx context.Context, builder *builder, desired *flowslatest.FlowCollectorSpec, cmDigest string) error { +func (r *CPReconciler) reconcileDeployment(ctx context.Context, builder *builder, desired *flowslatest.FlowCollectorSpec, name string, cmDigest string) error { report := helper.NewChangeReport("Console deployment") defer report.LogIfNeeded(ctx) @@ -204,25 +204,27 @@ func (r *CPReconciler) reconcileDeployment(ctx context.Context, builder *builder ctx, r.Instance, r.deployment, - builder.deployment(cmDigest), - constants.PluginName, + builder.deployment(name, cmDigest), + name, helper.PtrInt32(desired.ConsolePlugin.Replicas), &desired.ConsolePlugin.Autoscaler, &report, ) } -func (r *CPReconciler) reconcileServices(ctx context.Context, builder *builder) error { +func (r *CPReconciler) reconcileServices(ctx context.Context, builder *builder, name string) error { report := helper.NewChangeReport("Console services") defer report.LogIfNeeded(ctx) - if err := r.ReconcileService(ctx, r.service, builder.mainService(), &report); err != nil { + if err := r.ReconcileService(ctx, r.service, builder.mainService(name), &report); err != nil { return err } - if err := r.ReconcileService(ctx, r.metricsService, builder.metricsService(), &report); err != nil { - return err + if r.metricsService != nil { + if err := r.ReconcileService(ctx, r.metricsService, builder.metricsService(), &report); err != nil { + return err + } } - if r.ClusterInfo.HasSvcMonitor() { + if r.serviceMonitor != nil && r.ClusterInfo.HasSvcMonitor() { serviceMonitor := builder.serviceMonitor() if err := reconcilers.GenericReconcile(ctx, r.Managed, &r.Client, r.serviceMonitor, serviceMonitor, &report, helper.ServiceMonitorChanged); err != nil { return err diff --git a/internal/controller/consoleplugin/consoleplugin_static_reconciler.go b/internal/controller/consoleplugin/consoleplugin_static_reconciler.go new file mode 100644 index 000000000..4b786c1b1 --- /dev/null +++ b/internal/controller/consoleplugin/consoleplugin_static_reconciler.go @@ -0,0 +1,77 @@ +package consoleplugin + +import ( + "context" + + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/log" + + flowslatest "github.com/netobserv/network-observability-operator/api/flowcollector/v1beta2" + "github.com/netobserv/network-observability-operator/internal/controller/constants" + "github.com/netobserv/network-observability-operator/internal/controller/reconcilers" +) + +func NewStaticReconciler(cmn *reconcilers.Instance) CPReconciler { + rec := CPReconciler{ + Instance: cmn, + deployment: cmn.Managed.NewDeployment(constants.StaticPluginName), + service: cmn.Managed.NewService(constants.StaticPluginName), + serviceAccount: cmn.Managed.NewServiceAccount(constants.StaticPluginName), + } + return rec +} + +func (r *CPReconciler) ReconcileStaticPlugin(ctx context.Context, enable bool) error { + // Fake a FlowCollector to create console plugin and expose forms + return r.reconcileStatic(ctx, &flowslatest.FlowCollector{ + Spec: flowslatest.FlowCollectorSpec{ + ConsolePlugin: flowslatest.FlowCollectorConsolePlugin{ + Enable: ptr.To(enable), + LogLevel: "info", + Advanced: &flowslatest.AdvancedPluginConfig{ + Register: ptr.To(true), + }, + }, + }, + }) +} + +// Reconcile is the reconciler entry point to reconcile the static plugin state with the desired configuration +func (r *CPReconciler) reconcileStatic(ctx context.Context, desired *flowslatest.FlowCollector) error { + l := log.FromContext(ctx).WithName("console-plugin") + ctx = log.IntoContext(ctx, l) + + // Retrieve current owned objects + err := r.Managed.FetchAll(ctx) + if err != nil { + return err + } + + if r.ClusterInfo.HasConsolePlugin() { + if err = r.checkAutoPatch(ctx, desired, constants.StaticPluginName); err != nil { + return err + } + } + + if r.ClusterInfo.HasConsolePlugin() { + // Create object builder + builder := newBuilder(r.Instance, &desired.Spec, constants.StaticPluginName) + + if err = r.reconcilePlugin(ctx, &builder, &desired.Spec, constants.StaticPluginName, "NetObserv static plugin"); err != nil { + return err + } + + if err = r.reconcileDeployment(ctx, &builder, &desired.Spec, constants.StaticPluginName, ""); err != nil { + return err + } + + if err = r.reconcileServices(ctx, &builder, constants.StaticPluginName); err != nil { + return err + } + } else { + // delete any existing owned object + r.Managed.TryDeleteAll(ctx) + } + + return nil +} diff --git a/internal/controller/consoleplugin/consoleplugin_test.go b/internal/controller/consoleplugin/consoleplugin_test.go index a5781dd74..8567406f6 100644 --- a/internal/controller/consoleplugin/consoleplugin_test.go +++ b/internal/controller/consoleplugin/consoleplugin_test.go @@ -109,7 +109,7 @@ func getAutoScalerSpecs() (ascv2.HorizontalPodAutoscaler, flowslatest.FlowCollec func getBuilder(spec *flowslatest.FlowCollectorSpec, lk *helper.LokiConfig) builder { info := reconcilers.Common{Namespace: testNamespace, Loki: lk, ClusterInfo: &cluster.Info{}} - b := newBuilder(info.NewInstance(map[reconcilers.ImageRef]string{reconcilers.MainImage: testImage}, status.Instance{}), spec) + b := newBuilder(info.NewInstance(map[reconcilers.ImageRef]string{reconcilers.MainImage: testImage}, status.Instance{}), spec, constants.PluginName) _, _, _ = b.configMap(context.Background()) // build configmap to update builder's volumes return b } @@ -124,8 +124,8 @@ func TestContainerUpdateCheck(t *testing.T) { } spec := flowslatest.FlowCollectorSpec{ConsolePlugin: plugin} builder := getBuilder(&spec, &loki) - old := builder.deployment("digest") - nEw := builder.deployment("digest") + old := builder.deployment(constants.PluginName, "digest") + nEw := builder.deployment(constants.PluginName, "digest") report := helper.NewChangeReport("") assert.False(helper.PodChanged(&old.Spec.Template, &nEw.Spec.Template, constants.PluginName, &report)) assert.Contains(report.String(), "no change") @@ -135,7 +135,7 @@ func TestContainerUpdateCheck(t *testing.T) { corev1.ResourceCPU: resource.MustParse("500m"), corev1.ResourceMemory: resource.MustParse("500Gi"), } - nEw = builder.deployment("digest") + nEw = builder.deployment(constants.PluginName, "digest") report = helper.NewChangeReport("") assert.True(helper.PodChanged(&old.Spec.Template, &nEw.Spec.Template, constants.PluginName, &report)) assert.Contains(report.String(), "req/limit changed") @@ -143,7 +143,7 @@ func TestContainerUpdateCheck(t *testing.T) { // new image builder.info.Images[reconcilers.MainImage] = "quay.io/netobserv/network-observability-console-plugin:latest" - nEw = builder.deployment("digest") + nEw = builder.deployment(constants.PluginName, "digest") report = helper.NewChangeReport("") assert.True(helper.PodChanged(&old.Spec.Template, &nEw.Spec.Template, constants.PluginName, &report)) assert.Contains(report.String(), "Image changed") @@ -151,7 +151,7 @@ func TestContainerUpdateCheck(t *testing.T) { // new pull policy spec.ConsolePlugin.ImagePullPolicy = string(corev1.PullAlways) - nEw = builder.deployment("digest") + nEw = builder.deployment(constants.PluginName, "digest") report = helper.NewChangeReport("") assert.True(helper.PodChanged(&old.Spec.Template, &nEw.Spec.Template, constants.PluginName, &report)) assert.Contains(report.String(), "Pull policy changed") @@ -159,7 +159,7 @@ func TestContainerUpdateCheck(t *testing.T) { // new log level spec.ConsolePlugin.LogLevel = "debug" - nEw = builder.deployment("digest") + nEw = builder.deployment(constants.PluginName, "digest") report = helper.NewChangeReport("") assert.True(helper.PodChanged(&old.Spec.Template, &nEw.Spec.Template, constants.PluginName, &report)) assert.Contains(report.String(), "Args changed") @@ -176,7 +176,7 @@ func TestContainerUpdateCheck(t *testing.T) { }, }}} builder = getBuilder(&spec, &loki) - nEw = builder.deployment("digest") + nEw = builder.deployment(constants.PluginName, "digest") report = helper.NewChangeReport("") assert.True(helper.PodChanged(&old.Spec.Template, &nEw.Spec.Template, constants.PluginName, &report)) assert.Contains(report.String(), "Volumes changed") @@ -185,7 +185,7 @@ func TestContainerUpdateCheck(t *testing.T) { // new loki cert name loki.LokiManualParams.TLS.CACert.Name = "cm-name-2" builder = getBuilder(&spec, &loki) - nEw = builder.deployment("digest") + nEw = builder.deployment(constants.PluginName, "digest") report = helper.NewChangeReport("") assert.True(helper.PodChanged(&old.Spec.Template, &nEw.Spec.Template, constants.PluginName, &report)) assert.Contains(report.String(), "Volumes changed") @@ -194,7 +194,7 @@ func TestContainerUpdateCheck(t *testing.T) { // test again no change loki.LokiManualParams.TLS.CACert.Name = "cm-name-2" builder = getBuilder(&spec, &loki) - nEw = builder.deployment("digest") + nEw = builder.deployment(constants.PluginName, "digest") report = helper.NewChangeReport("") assert.False(helper.PodChanged(&old.Spec.Template, &nEw.Spec.Template, constants.PluginName, &report)) assert.Contains(report.String(), "no change") @@ -373,8 +373,8 @@ func TestBuiltService(t *testing.T) { loki := helper.LokiConfig{LokiManualParams: flowslatest.LokiManualParams{IngesterURL: "http://foo:1234"}} spec := flowslatest.FlowCollectorSpec{ConsolePlugin: plugin} builder := getBuilder(&spec, &loki) - old := builder.mainService() - nEw := builder.mainService() + old := builder.mainService(constants.PluginName) + nEw := builder.mainService(constants.PluginName) report := helper.NewChangeReport("") assert.Equal(helper.ServiceChanged(old, nEw, &report), false) assert.Contains(report.String(), "no change") @@ -389,14 +389,14 @@ func TestLabels(t *testing.T) { builder := getBuilder(&spec, &loki) // Deployment - depl := builder.deployment("digest") + depl := builder.deployment(constants.PluginName, "digest") assert.Equal("netobserv-plugin", depl.Labels["app"]) assert.Equal("netobserv-plugin", depl.Spec.Template.Labels["app"]) assert.Equal("dev", depl.Labels["version"]) assert.Equal("dev", depl.Spec.Template.Labels["version"]) // Service - svc := builder.mainService() + svc := builder.mainService(constants.PluginName) assert.Equal("netobserv-plugin", svc.Labels["app"]) assert.Equal("netobserv-plugin", svc.Spec.Selector["app"]) assert.Equal("dev", svc.Labels["version"]) @@ -407,30 +407,30 @@ func TestAutoScalerUpdateCheck(t *testing.T) { assert := assert.New(t) // equals specs - autoScalerSpec, plugin := getAutoScalerSpecs() + autoScaler, plugin := getAutoScalerSpecs() report := helper.NewChangeReport("") - assert.Equal(helper.AutoScalerChanged(&autoScalerSpec, plugin.Autoscaler, &report), false) + assert.Equal(helper.AutoScalerChanged(&autoScaler, plugin.Autoscaler, &report), false) assert.Contains(report.String(), "no change") // wrong max replicas - autoScalerSpec, plugin = getAutoScalerSpecs() - autoScalerSpec.Spec.MaxReplicas = 10 + autoScaler, plugin = getAutoScalerSpecs() + autoScaler.Spec.MaxReplicas = 10 report = helper.NewChangeReport("") - assert.Equal(helper.AutoScalerChanged(&autoScalerSpec, plugin.Autoscaler, &report), true) + assert.Equal(helper.AutoScalerChanged(&autoScaler, plugin.Autoscaler, &report), true) assert.Contains(report.String(), "Max replicas changed") // missing min replicas - autoScalerSpec, plugin = getAutoScalerSpecs() - autoScalerSpec.Spec.MinReplicas = nil + autoScaler, plugin = getAutoScalerSpecs() + autoScaler.Spec.MinReplicas = nil report = helper.NewChangeReport("") - assert.Equal(helper.AutoScalerChanged(&autoScalerSpec, plugin.Autoscaler, &report), true) + assert.Equal(helper.AutoScalerChanged(&autoScaler, plugin.Autoscaler, &report), true) assert.Contains(report.String(), "Min replicas changed") // missing metrics - autoScalerSpec, plugin = getAutoScalerSpecs() - autoScalerSpec.Spec.Metrics = []ascv2.MetricSpec{} + autoScaler, plugin = getAutoScalerSpecs() + autoScaler.Spec.Metrics = []ascv2.MetricSpec{} report = helper.NewChangeReport("") - assert.Equal(helper.AutoScalerChanged(&autoScalerSpec, plugin.Autoscaler, &report), true) + assert.Equal(helper.AutoScalerChanged(&autoScaler, plugin.Autoscaler, &report), true) assert.Contains(report.String(), "Metrics changed") } diff --git a/internal/controller/constants/constants.go b/internal/controller/constants/constants.go index f9ae4801f..a435da893 100644 --- a/internal/controller/constants/constants.go +++ b/internal/controller/constants/constants.go @@ -12,12 +12,14 @@ type RoleName string const ( DefaultOperatorNamespace = "netobserv" OperatorName = "netobserv-operator" + ControllerName = "netobserv-controller-manager" WebhookPort = 9443 FLPName = "flowlogs-pipeline" FLPShortName = "flp" FLPPortName = "flp" // must be <15 chars FLPMetricsPort = 9401 PluginName = "netobserv-plugin" + StaticPluginName = "netobserv-plugin-static" PluginShortName = "plugin" // EBPFAgentName and other constants for it diff --git a/internal/controller/controllers.go b/internal/controller/controllers.go index 92ee40347..1aca294a9 100644 --- a/internal/controller/controllers.go +++ b/internal/controller/controllers.go @@ -4,7 +4,8 @@ import ( "github.com/netobserv/network-observability-operator/internal/controller/flp" "github.com/netobserv/network-observability-operator/internal/controller/monitoring" "github.com/netobserv/network-observability-operator/internal/controller/networkpolicy" + "github.com/netobserv/network-observability-operator/internal/controller/static" "github.com/netobserv/network-observability-operator/internal/pkg/manager" ) -var Registerers = []manager.Registerer{Start, flp.Start, monitoring.Start, networkpolicy.Start} +var Registerers = []manager.Registerer{Start, flp.Start, monitoring.Start, networkpolicy.Start, static.Start} diff --git a/internal/controller/flowcollector_controller_certificates_test.go b/internal/controller/flowcollector_controller_certificates_test.go index 8cd68ec94..ee0503d98 100644 --- a/internal/controller/flowcollector_controller_certificates_test.go +++ b/internal/controller/flowcollector_controller_certificates_test.go @@ -278,7 +278,7 @@ func flowCollectorCertificatesSpecs() { } return test.VolumeNames(plugin.Spec.Template.Spec.Volumes) }, timeout, interval).Should(ContainElements( - "console-serving-cert", + "netobserv-plugin-cert", "config-volume", "loki-certs-ca", "loki-status-certs-ca", diff --git a/internal/controller/flowcollector_controller_console_test.go b/internal/controller/flowcollector_controller_console_test.go index 15bdb5dcf..420aca527 100644 --- a/internal/controller/flowcollector_controller_console_test.go +++ b/internal/controller/flowcollector_controller_console_test.go @@ -27,6 +27,10 @@ const cpNamespace = "namespace-console-specs" // nolint:cyclop func flowCollectorConsolePluginSpecs() { + staticCpKey := types.NamespacedName{ + Name: "netobserv-plugin-static", + Namespace: "main-namespace", + } cpKey := types.NamespacedName{ Name: "netobserv-plugin", Namespace: cpNamespace, @@ -52,6 +56,10 @@ func flowCollectorConsolePluginSpecs() { }) Context("Console plugin test init", func() { + It("Should create controller pod owner", func() { + createFakeController() + }) + It("Should create Console CR", func() { created := &operatorsv1.Console{ ObjectMeta: metav1.ObjectMeta{ @@ -75,7 +83,25 @@ func flowCollectorConsolePluginSpecs() { // Create Expect(k8sClient.Create(ctx, created)).Should(Succeed()) }) + }) + + Context("Deploying the static console plugin", func() { + It("Should create successfully", func() { + By("Expecting to create the static console plugin Deployment") + dp := appsv1.Deployment{} + Eventually(func() interface{} { + return k8sClient.Get(ctx, staticCpKey, &dp) + }, timeout, interval).Should(Succeed()) + + By("Expecting to create the static console plugin Service") + svc := v1.Service{} + Eventually(func() interface{} { + return k8sClient.Get(ctx, staticCpKey, &svc) + }, timeout, interval).Should(Succeed()) + }) + }) + Context("Create FlowCollector CR", func() { It("Should create CR successfully", func() { created := &flowslatest.FlowCollector{ ObjectMeta: metav1.ObjectMeta{ @@ -246,14 +272,14 @@ func flowCollectorConsolePluginSpecs() { }) Context("Registering to the Console CR", func() { - It("Should start unregistered", func() { + It("Should start with static plugin registered", func() { Eventually(func() interface{} { cr := operatorsv1.Console{} if err := k8sClient.Get(ctx, consoleCRKey, &cr); err != nil { return err } return cr.Spec.Plugins - }, timeout, interval).Should(BeEmpty()) + }, timeout, interval).Should(Equal([]string{"netobserv-plugin-static"})) }) It("Should be registered", func() { @@ -262,14 +288,14 @@ func flowCollectorConsolePluginSpecs() { fc.Spec.ConsolePlugin.Advanced.Register = ptr.To(true) }) - By("Expecting the Console CR to not have plugin registered") + By("Expecting the Console CR to have both plugins registered") Eventually(func() interface{} { cr := operatorsv1.Console{} if err := k8sClient.Get(ctx, consoleCRKey, &cr); err != nil { return err } return cr.Spec.Plugins - }, timeout, interval).Should(Equal([]string{"netobserv-plugin"})) + }, timeout, interval).Should(Equal([]string{"netobserv-plugin-static", "netobserv-plugin"})) }) }) @@ -356,6 +382,33 @@ func flowCollectorConsolePluginSpecs() { }) }) + Context("Checking controller ownership", func() { + It("Should be garbage collected", func() { + dp := appsv1.Deployment{} + By("Getting controller deployment") + Eventually(func() interface{} { + return k8sClient.Get(ctx, types.NamespacedName{ + Name: "netobserv-controller-manager", + Namespace: "main-namespace", + }, &dp) + }, timeout, interval).Should(Succeed()) + + By("Expecting static console plugin deployment to be garbage collected") + Eventually(func() interface{} { + d := appsv1.Deployment{} + _ = k8sClient.Get(ctx, staticCpKey, &d) + return &d + }, timeout, interval).Should(BeGarbageCollectedBy(&dp)) + + By("Expecting static console plugin service to be garbage collected") + Eventually(func() interface{} { + svc := v1.Service{} + _ = k8sClient.Get(ctx, staticCpKey, &svc) + return &svc + }, timeout, interval).Should(BeGarbageCollectedBy(&dp)) + }) + }) + Context("Cleanup", func() { It("Should delete CR", func() { cleanupCR(crKey) @@ -370,6 +423,22 @@ func flowCollectorConsolePluginSpecs() { }) }, timeout, interval).Should(Succeed()) }) + + It("Should delete fake controller", func() { + dp := appsv1.Deployment{} + By("Retreive controller deployment") + Eventually(func() interface{} { + return k8sClient.Get(ctx, types.NamespacedName{ + Name: "netobserv-controller-manager", + Namespace: "main-namespace", + }, &dp) + }, timeout, interval).Should(Succeed()) + + By("Delete controller deployment") + Eventually(func() error { + return k8sClient.Delete(ctx, &dp) + }, timeout, interval).Should(Succeed()) + }) }) } diff --git a/internal/controller/flowcollector_controller_test.go b/internal/controller/flowcollector_controller_test.go index a64f39bdd..2a043e436 100644 --- a/internal/controller/flowcollector_controller_test.go +++ b/internal/controller/flowcollector_controller_test.go @@ -13,6 +13,9 @@ const ( ) var ( + createFakeController = func() { + test.CreateFakeController(ctx, k8sClient) + } updateCR = func(key types.NamespacedName, updater func(*flowslatest.FlowCollector)) { test.UpdateCR(ctx, k8sClient, key, updater) } diff --git a/internal/controller/flp/flp_test.go b/internal/controller/flp/flp_test.go index 46016b317..3c7f467a6 100644 --- a/internal/controller/flp/flp_test.go +++ b/internal/controller/flp/flp_test.go @@ -726,30 +726,30 @@ func TestAutoScalerUpdateCheck(t *testing.T) { assert := assert.New(t) // Equals specs - autoScalerSpec, hpa := getAutoScalerSpecs() + autoScaler, hpa := getAutoScalerSpecs() report := helper.NewChangeReport("") - assert.False(helper.AutoScalerChanged(&autoScalerSpec, hpa, &report)) + assert.False(helper.AutoScalerChanged(&autoScaler, hpa, &report)) assert.Contains(report.String(), "no change") // Wrong max replicas - autoScalerSpec, hpa = getAutoScalerSpecs() - autoScalerSpec.Spec.MaxReplicas = 10 + autoScaler, hpa = getAutoScalerSpecs() + autoScaler.Spec.MaxReplicas = 10 report = helper.NewChangeReport("") - assert.True(helper.AutoScalerChanged(&autoScalerSpec, hpa, &report)) + assert.True(helper.AutoScalerChanged(&autoScaler, hpa, &report)) assert.Contains(report.String(), "Max replicas changed") // Missing min replicas - autoScalerSpec, hpa = getAutoScalerSpecs() - autoScalerSpec.Spec.MinReplicas = nil + autoScaler, hpa = getAutoScalerSpecs() + autoScaler.Spec.MinReplicas = nil report = helper.NewChangeReport("") - assert.True(helper.AutoScalerChanged(&autoScalerSpec, hpa, &report)) + assert.True(helper.AutoScalerChanged(&autoScaler, hpa, &report)) assert.Contains(report.String(), "Min replicas changed") // Missing metrics - autoScalerSpec, hpa = getAutoScalerSpecs() - autoScalerSpec.Spec.Metrics = []ascv2.MetricSpec{} + autoScaler, hpa = getAutoScalerSpecs() + autoScaler.Spec.Metrics = []ascv2.MetricSpec{} report = helper.NewChangeReport("") - assert.True(helper.AutoScalerChanged(&autoScalerSpec, hpa, &report)) + assert.True(helper.AutoScalerChanged(&autoScaler, hpa, &report)) assert.Contains(report.String(), "Metrics changed") } diff --git a/internal/controller/monitoring/monitoring_controller.go b/internal/controller/monitoring/monitoring_controller.go index 13468ba0d..e24534e49 100644 --- a/internal/controller/monitoring/monitoring_controller.go +++ b/internal/controller/monitoring/monitoring_controller.go @@ -99,13 +99,13 @@ func (r *Reconciler) reconcile(ctx context.Context, clh *helper.Client, desired } desiredNs := buildNamespace(ns, r.mgr.Config.DownstreamDeployment) // always add owned label to desired namespace as we expect it to be created - helper.AddOwnedLabel(desiredNs) + helper.AddManagedLabel(desiredNs) if nsExist == nil { err = r.Create(ctx, desiredNs) if err != nil { return err } - } else if !helper.SkipOwnership(nsExist) && !helper.IsSubSet(nsExist.ObjectMeta.Labels, desiredNs.ObjectMeta.Labels) { + } else if helper.IsManaged(nsExist) && !helper.IsSubSet(nsExist.ObjectMeta.Labels, desiredNs.ObjectMeta.Labels) { err = r.Update(ctx, desiredNs) if err != nil { return err diff --git a/internal/controller/static/static_controller.go b/internal/controller/static/static_controller.go new file mode 100644 index 000000000..5361add06 --- /dev/null +++ b/internal/controller/static/static_controller.go @@ -0,0 +1,106 @@ +package static + +import ( + "context" + "fmt" + "time" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + flowslatest "github.com/netobserv/network-observability-operator/api/flowcollector/v1beta2" + "github.com/netobserv/network-observability-operator/internal/controller/consoleplugin" + "github.com/netobserv/network-observability-operator/internal/controller/reconcilers" + "github.com/netobserv/network-observability-operator/internal/pkg/helper" + "github.com/netobserv/network-observability-operator/internal/pkg/manager" + "github.com/netobserv/network-observability-operator/internal/pkg/manager/status" +) + +const ( + initReconcileAttempts = 5 +) + +type Reconciler struct { + client.Client + mgr *manager.Manager + status status.Instance +} + +func Start(ctx context.Context, mgr *manager.Manager) error { + log := log.FromContext(ctx) + log.Info("Starting Static controller") + r := Reconciler{ + Client: mgr.Client, + mgr: mgr, + status: mgr.Status.ForComponent(status.StaticPlugin), + } + + // force reconcile at startup + go r.InitReconcile(ctx) + + return ctrl.NewControllerManagedBy(mgr). + For(&flowslatest.FlowCollector{}, reconcilers.IgnoreStatusChange). + Named("staticPlugin"). + Complete(&r) +} + +func (r *Reconciler) InitReconcile(ctx context.Context) { + log := log.FromContext(ctx) + log.Info("Initializing resources...") + + for attempt := range initReconcileAttempts { + // delay the reconcile calls to let some time to the cache to load + time.Sleep(5 * time.Second) + _, err := r.Reconcile(ctx, ctrl.Request{}) + if err != nil { + log.Error(err, "Error while doing initial reconcile", "attempt", attempt) + } else { + return + } + } +} + +// 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) { + l := log.Log.WithName("staticPlugin") // clear context (too noisy) + ctx = log.IntoContext(ctx, l) + + r.status.SetUnknown() + defer r.status.Commit(ctx, r.Client) + + // always reconcile static console plugin + scp, err := helper.NewControllerClientHelper(ctx, r.mgr.Config.Namespace, r.Client) + if err != nil { + return ctrl.Result{}, fmt.Errorf("failed to get controller deployment: %w", err) + } + staticPluginReconciler := consoleplugin.NewStaticReconciler(r.newDefaultReconcilerInstance(scp)) + if err := staticPluginReconciler.ReconcileStaticPlugin(ctx, true); err != nil { + l.Error(err, "Static plugin reconcile failure") + // Set status failure unless it was already set + if !r.status.HasFailure() { + r.status.SetFailure("StaticPluginError", err.Error()) + } + return ctrl.Result{}, err + } + + r.status.SetReady() + return ctrl.Result{}, nil +} + +func (r *Reconciler) newDefaultReconcilerInstance(clh *helper.Client) *reconcilers.Instance { + // force default namespace + reconcilersInfo := reconcilers.Common{ + Client: *clh, + Namespace: r.mgr.Config.Namespace, + ClusterInfo: r.mgr.ClusterInfo, + Watcher: nil, + Loki: &helper.LokiConfig{}, + IsDownstream: r.mgr.Config.DownstreamDeployment, + } + return reconcilersInfo.NewInstance(map[reconcilers.ImageRef]string{ + reconcilers.MainImage: r.mgr.Config.ConsolePluginImage, + reconcilers.ConsolePluginCompatImage: r.mgr.Config.ConsolePluginCompatImage, + }, r.status) +} diff --git a/internal/pkg/helper/client_helper.go b/internal/pkg/helper/client_helper.go index 2a2771b3c..0d02ec1e5 100644 --- a/internal/pkg/helper/client_helper.go +++ b/internal/pkg/helper/client_helper.go @@ -4,7 +4,9 @@ import ( "context" "reflect" + appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/log" @@ -16,16 +18,33 @@ import ( // Client includes a kube client with some additional helper functions type Client struct { client.Client - SetControllerReference func(client.Object) error + SetOwnerReference func(client.Object) error } func UnmanagedClient(cl client.Client) Client { return Client{ - Client: cl, - SetControllerReference: func(_ client.Object) error { return nil }, + Client: cl, + SetOwnerReference: func(_ client.Object) error { return nil }, } } +func NewControllerClientHelper(ctx context.Context, ns string, c client.Client) (*Client, error) { + dpl, err := getControllerDeployment(ctx, ns, c) + if err != nil { + return nil, err + } + return &Client{ + Client: c, + SetOwnerReference: func(obj client.Object) error { + // can't apply ownership on cluster wide objects such as ClusterRole + if obj.GetNamespace() == "" { + return nil + } + return controllerutil.SetControllerReference(dpl, obj, c.Scheme(), controllerutil.WithBlockOwnerDeletion(false)) + }, + }, nil +} + func NewFlowCollectorClientHelper(ctx context.Context, c client.Client) (*Client, *flowslatest.FlowCollector, error) { fc, err := getFlowCollector(ctx, c) if err != nil || fc == nil { @@ -33,7 +52,7 @@ func NewFlowCollectorClientHelper(ctx context.Context, c client.Client) (*Client } return &Client{ Client: c, - SetControllerReference: func(obj client.Object) error { + SetOwnerReference: func(obj client.Object) error { return controllerutil.SetControllerReference(fc, obj, c.Scheme()) }, }, fc, nil @@ -42,12 +61,12 @@ func NewFlowCollectorClientHelper(ctx context.Context, c client.Client) (*Client // CreateOwned is an helper function that creates an object, sets owner reference and writes info & errors logs func (c *Client) CreateOwned(ctx context.Context, obj client.Object) error { log := log.FromContext(ctx) - err := c.SetControllerReference(obj) + err := c.SetOwnerReference(obj) if err != nil { log.Error(err, "Failed to set controller reference") return err } - AddOwnedLabel(obj) + AddManagedLabel(obj) kind := reflect.TypeOf(obj).String() log.Info("CREATING a new "+kind, "Namespace", obj.GetNamespace(), "Name", obj.GetName()) err = c.Create(ctx, obj) @@ -64,11 +83,12 @@ func (c *Client) UpdateOwned(ctx context.Context, old, obj client.Object) error if old != nil { obj.SetResourceVersion(old.GetResourceVersion()) } - err := c.SetControllerReference(obj) + err := c.SetOwnerReference(obj) if err != nil { log.Error(err, "Failed to set controller reference") return err } + AddManagedLabel(obj) kind := reflect.TypeOf(obj).String() log.Info("UPDATING "+kind, "Namespace", obj.GetNamespace(), "Name", obj.GetName()) err = c.Update(ctx, obj) @@ -115,3 +135,14 @@ func getFlowCollector(ctx context.Context, c client.Client) (*flowslatest.FlowCo } return desired, nil } + +func getControllerDeployment(ctx context.Context, ns string, c client.Client) (*appsv1.Deployment, error) { + dpl := &appsv1.Deployment{} + if err := c.Get(ctx, types.NamespacedName{ + Name: constants.ControllerName, + Namespace: ns, + }, dpl); err != nil { + return nil, err + } + return dpl, nil +} diff --git a/internal/pkg/helper/flowcollector.go b/internal/pkg/helper/flowcollector.go index 457f80a7d..f289f1440 100644 --- a/internal/pkg/helper/flowcollector.go +++ b/internal/pkg/helper/flowcollector.go @@ -200,7 +200,7 @@ func PtrInt32(i *int32) int32 { return *i } -func AddOwnedLabel(obj client.Object) { +func AddManagedLabel(obj client.Object) { // set netobserv-managed label to true so users can easily switch to false if they want to skip ownership labels := obj.GetLabels() if labels == nil { @@ -210,16 +210,20 @@ func AddOwnedLabel(obj client.Object) { obj.SetLabels(labels) } -func SkipOwnership(obj client.Object) bool { - // ownership is ignored if netobserv-managed label is explicitly set to false +func IsManaged(obj client.Object) bool { labels := obj.GetLabels() - return labels != nil && labels[netobservManagedLabel] == "false" + if labels == nil { + return false + } + return labels[netobservManagedLabel] == "true" } func IsOwned(obj client.Object) bool { - if SkipOwnership(obj) { - return false + // ownership is forced if netobserv-managed label is explicitly set to true + if IsManaged(obj) { + return true } + // else we check for owner references refs := obj.GetOwnerReferences() return len(refs) > 0 && strings.HasPrefix(refs[0].APIVersion, flowslatest.GroupVersion.Group) } diff --git a/internal/pkg/manager/config.go b/internal/pkg/manager/config.go index f581fcfe7..56679d33a 100644 --- a/internal/pkg/manager/config.go +++ b/internal/pkg/manager/config.go @@ -16,6 +16,8 @@ type Config struct { ConsolePluginCompatImage string // EBPFByteCodeImage is the ebpf byte code image used by EBPF Manager EBPFByteCodeImage string + // Default namespace + Namespace string // Release kind is either upstream or downstream DownstreamDeployment bool } @@ -30,5 +32,8 @@ func (cfg *Config) Validate() error { if cfg.ConsolePluginImage == "" { return errors.New("console plugin image argument can't be empty") } + if cfg.Namespace == "" { + return errors.New("namespace argument can't be empty") + } return nil } diff --git a/internal/pkg/manager/status/status_manager.go b/internal/pkg/manager/status/status_manager.go index aecdbcc9f..49454eaf8 100644 --- a/internal/pkg/manager/status/status_manager.go +++ b/internal/pkg/manager/status/status_manager.go @@ -29,12 +29,13 @@ const ( FLPMonolith ComponentName = "FLPMonolith" FLPTransformer ComponentName = "FLPTransformer" Monitoring ComponentName = "Monitoring" + StaticPlugin ComponentName = "StaticPlugin" NetworkPolicy ComponentName = "NetworkPolicy" ConditionConfigurationIssue = "ConfigurationIssue" LokiIssue = "LokiIssue" ) -var allNames = []ComponentName{FlowCollectorLegacy, Monitoring} +var allNames = []ComponentName{FlowCollectorLegacy, Monitoring, StaticPlugin} type Manager struct { statuses sync.Map diff --git a/internal/pkg/manager/status/status_manager_test.go b/internal/pkg/manager/status/status_manager_test.go index 4bd09e868..621ccc0c0 100644 --- a/internal/pkg/manager/status/status_manager_test.go +++ b/internal/pkg/manager/status/status_manager_test.go @@ -18,7 +18,7 @@ func TestStatusWorkflow(t *testing.T) { sm.SetFailure("AnError", "bad one") conds := s.getConditions() - assert.Len(t, conds, 3) + assert.Len(t, conds, 4) assertHasCondition(t, conds, "Ready", "Failure", metav1.ConditionFalse) assertHasCondition(t, conds, "WaitingFlowCollectorLegacy", "CreatingDaemonSet", metav1.ConditionTrue) assertHasCondition(t, conds, "WaitingMonitoring", "AnError", metav1.ConditionTrue) @@ -31,7 +31,7 @@ func TestStatusWorkflow(t *testing.T) { sm.SetUnknown() conds = s.getConditions() - assert.Len(t, conds, 3) + assert.Len(t, conds, 4) assertHasCondition(t, conds, "Ready", "Pending", metav1.ConditionFalse) assertHasCondition(t, conds, "WaitingFlowCollectorLegacy", "DaemonSetNotReady", metav1.ConditionTrue) assertHasCondition(t, conds, "WaitingMonitoring", "Unused", metav1.ConditionUnknown) @@ -44,7 +44,7 @@ func TestStatusWorkflow(t *testing.T) { sm.SetUnused("message") conds = s.getConditions() - assert.Len(t, conds, 3) + assert.Len(t, conds, 4) assertHasCondition(t, conds, "Ready", "Ready", metav1.ConditionTrue) assertHasCondition(t, conds, "WaitingFlowCollectorLegacy", "Ready", metav1.ConditionFalse) assertHasCondition(t, conds, "WaitingMonitoring", "ComponentUnused", metav1.ConditionUnknown) @@ -57,7 +57,7 @@ func TestStatusWorkflow(t *testing.T) { sm.SetReady() conds = s.getConditions() - assert.Len(t, conds, 3) + assert.Len(t, conds, 4) assertHasCondition(t, conds, "Ready", "Ready", metav1.ConditionTrue) assertHasCondition(t, conds, "WaitingFlowCollectorLegacy", "Ready", metav1.ConditionFalse) assertHasCondition(t, conds, "WaitingMonitoring", "Ready", metav1.ConditionFalse) diff --git a/internal/pkg/test/envtest.go b/internal/pkg/test/envtest.go index 3a53fdea5..ef37acd17 100644 --- a/internal/pkg/test/envtest.go +++ b/internal/pkg/test/envtest.go @@ -17,6 +17,7 @@ import ( operatorsv1 "github.com/openshift/api/operator/v1" securityv1 "github.com/openshift/api/security/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" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -145,6 +146,7 @@ func PrepareEnvTest(controllers []manager.Registerer, namespaces []string, baseP FlowlogsPipelineImage: "registry-proxy.engineering.redhat.com/rh-osbs/network-observability-flowlogs-pipeline@sha256:6481481ba23375107233f8d0a4f839436e34e50c2ec550ead0a16c361ae6654e", ConsolePluginImage: "registry-proxy.engineering.redhat.com/rh-osbs/network-observability-console-plugin@sha256:6481481ba23375107233f8d0a4f839436e34e50c2ec550ead0a16c361ae6654e", DownstreamDeployment: false, + Namespace: "main-namespace", }, &ctrl.Options{ Scheme: scheme.Scheme, @@ -177,6 +179,40 @@ func TeardownEnvTest(testEnv *envtest.Environment, cancel context.CancelFunc) { Expect(err).NotTo(HaveOccurred()) } +func CreateFakeController(ctx context.Context, k8sClient client.Client) { + created := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "netobserv-controller-manager", + Namespace: "main-namespace", + }, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "controller": "dummy", + }, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "controller": "dummy", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "controller", + Image: "nginx:latest", + }, + }, + }, + }, + }, + } + + // Create + Eventually(k8sClient.Create(ctx, created)).Should(Succeed()) +} + func GetCR(ctx context.Context, k8sClient client.Client, key types.NamespacedName) *flowsv1beta2.FlowCollector { cr := flowsv1beta2.FlowCollector{} Eventually(func() error { diff --git a/main.go b/main.go index 8dbe3bdf8..a8c8e652f 100644 --- a/main.go +++ b/main.go @@ -32,6 +32,7 @@ import ( securityv1 "github.com/openshift/api/security/v1" monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" "go.uber.org/zap/zapcore" + appsv1 "k8s.io/api/apps/v1" ascv2 "k8s.io/api/autoscaling/v2" corev1 "k8s.io/api/core/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" @@ -86,6 +87,7 @@ func init() { utilruntime.Must(apiextensionsv1.AddToScheme(scheme)) utilruntime.Must(bpfmaniov1alpha1.Install(scheme)) utilruntime.Must(lokiv1.AddToScheme(scheme)) + utilruntime.Must(appsv1.AddToScheme(scheme)) //+kubebuilder:scaffold:scheme } @@ -114,6 +116,7 @@ func main() { flag.StringVar(&config.ConsolePluginImage, "console-plugin-image", "quay.io/netobserv/network-observability-console-plugin:main", "The image of the Console Plugin") flag.StringVar(&config.ConsolePluginCompatImage, "console-plugin-compat-image", "quay.io/netobserv/network-observability-console-plugin-pf4:main", "A backward compatible image of the Console Plugin (e.g. Patterfly 4 variant)") flag.StringVar(&config.EBPFByteCodeImage, "ebpf-bytecode-image", "quay.io/netobserv/ebpf-bytecode:main", "The EBPF bytecode for the eBPF agent") + flag.StringVar(&config.Namespace, "namespace", "netobserv", "Current controller namespace") flag.BoolVar(&config.DownstreamDeployment, "downstream-deployment", false, "Either this deployment is a downstream deployment ot not") flag.BoolVar(&enableHTTP2, "enable-http2", enableHTTP2, "If HTTP/2 should be enabled for the metrics and webhook servers.") flag.BoolVar(&versionFlag, "v", false, "print version")