From c001f86fc5d8ae500d0bdc972c56bcbac3509507 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Thu, 26 Mar 2026 17:08:33 +0100 Subject: [PATCH 1/8] fix: adding missing tags for non k8s events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../operator/monitoring/micrometer/MicrometerMetricsV2.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/micrometer-support/src/main/java/io/javaoperatorsdk/operator/monitoring/micrometer/MicrometerMetricsV2.java b/micrometer-support/src/main/java/io/javaoperatorsdk/operator/monitoring/micrometer/MicrometerMetricsV2.java index 2e7824a65a..6020c3e6ba 100644 --- a/micrometer-support/src/main/java/io/javaoperatorsdk/operator/monitoring/micrometer/MicrometerMetricsV2.java +++ b/micrometer-support/src/main/java/io/javaoperatorsdk/operator/monitoring/micrometer/MicrometerMetricsV2.java @@ -176,7 +176,11 @@ public void eventReceived(Event event, Map metadata) { Tag.of(ACTION, resourceEvent.getAction().toString())); } else { incrementCounter( - EVENTS_RECEIVED, null, metadata, Tag.of(EVENT, event.getClass().getSimpleName())); + EVENTS_RECEIVED, + "NO_NAMESPACE", + metadata, + Tag.of(EVENT, event.getClass().getSimpleName()), + Tag.of(ACTION, "UNKNOWN")); } } From f59d07b558c5a52cd5041ac4d80bde5e05511ff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Thu, 26 Mar 2026 17:11:24 +0100 Subject: [PATCH 2/8] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../monitoring/micrometer/MicrometerMetricsV2.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/micrometer-support/src/main/java/io/javaoperatorsdk/operator/monitoring/micrometer/MicrometerMetricsV2.java b/micrometer-support/src/main/java/io/javaoperatorsdk/operator/monitoring/micrometer/MicrometerMetricsV2.java index 6020c3e6ba..60fcbb105a 100644 --- a/micrometer-support/src/main/java/io/javaoperatorsdk/operator/monitoring/micrometer/MicrometerMetricsV2.java +++ b/micrometer-support/src/main/java/io/javaoperatorsdk/operator/monitoring/micrometer/MicrometerMetricsV2.java @@ -62,6 +62,8 @@ public class MicrometerMetricsV2 implements Metrics { public static final String RECONCILIATION_EXECUTION_DURATION = RECONCILIATIONS + "execution.duration"; + public static final String NO_NAMESPACE = "NO_NAMESPCAVE"; + public static final String UNKNOWN_ACTION = "UNKNOWN"; private final MeterRegistry registry; private final Map gauges = new ConcurrentHashMap<>(); @@ -177,10 +179,10 @@ public void eventReceived(Event event, Map metadata) { } else { incrementCounter( EVENTS_RECEIVED, - "NO_NAMESPACE", + null, metadata, Tag.of(EVENT, event.getClass().getSimpleName()), - Tag.of(ACTION, "UNKNOWN")); + Tag.of(ACTION, UNKNOWN_ACTION)); } } @@ -250,6 +252,8 @@ private static void addControllerNameTag(String name, List tags) { private void addNamespaceTag(String namespace, List tags) { if (includeNamespaceTag && namespace != null && !namespace.isBlank()) { addTag(NAMESPACE, namespace, tags); + } else { + addTag(NAMESPACE, NO_NAMESPACE, tags); } } From 39e58ccb7f5b48fab8891b1480d1897c25f20dab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Thu, 26 Mar 2026 17:11:55 +0100 Subject: [PATCH 3/8] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../operator/monitoring/micrometer/MicrometerMetricsV2.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micrometer-support/src/main/java/io/javaoperatorsdk/operator/monitoring/micrometer/MicrometerMetricsV2.java b/micrometer-support/src/main/java/io/javaoperatorsdk/operator/monitoring/micrometer/MicrometerMetricsV2.java index 60fcbb105a..984eba705d 100644 --- a/micrometer-support/src/main/java/io/javaoperatorsdk/operator/monitoring/micrometer/MicrometerMetricsV2.java +++ b/micrometer-support/src/main/java/io/javaoperatorsdk/operator/monitoring/micrometer/MicrometerMetricsV2.java @@ -62,7 +62,7 @@ public class MicrometerMetricsV2 implements Metrics { public static final String RECONCILIATION_EXECUTION_DURATION = RECONCILIATIONS + "execution.duration"; - public static final String NO_NAMESPACE = "NO_NAMESPCAVE"; + public static final String NO_NAMESPACE = "NO_NAMESPACE"; public static final String UNKNOWN_ACTION = "UNKNOWN"; private final MeterRegistry registry; From 258fc0cef533af0ff37071b8ddaddad8875e9e38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Fri, 27 Mar 2026 09:16:48 +0100 Subject: [PATCH 4/8] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../monitoring/micrometer/MicrometerMetricsV2.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/micrometer-support/src/main/java/io/javaoperatorsdk/operator/monitoring/micrometer/MicrometerMetricsV2.java b/micrometer-support/src/main/java/io/javaoperatorsdk/operator/monitoring/micrometer/MicrometerMetricsV2.java index 984eba705d..b5a2804fa7 100644 --- a/micrometer-support/src/main/java/io/javaoperatorsdk/operator/monitoring/micrometer/MicrometerMetricsV2.java +++ b/micrometer-support/src/main/java/io/javaoperatorsdk/operator/monitoring/micrometer/MicrometerMetricsV2.java @@ -250,10 +250,12 @@ private static void addControllerNameTag(String name, List tags) { } private void addNamespaceTag(String namespace, List tags) { - if (includeNamespaceTag && namespace != null && !namespace.isBlank()) { - addTag(NAMESPACE, namespace, tags); - } else { - addTag(NAMESPACE, NO_NAMESPACE, tags); + if (includeNamespaceTag) { + if (namespace != null && !namespace.isBlank()) { + addTag(NAMESPACE, namespace, tags); + } else { + addTag(NAMESPACE, NO_NAMESPACE, tags); + } } } From 5a44c3c369d1e86dc843fb8458019431a0c0a60c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Fri, 27 Mar 2026 09:55:33 +0100 Subject: [PATCH 5/8] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../micrometer/MicrometerMetricsV2.java | 8 +++--- .../metrics/MetricsHandlingReconciler1.java | 28 +++++++++++++++++++ .../MetricsHandlingSampleOperator.java | 2 +- .../sample/metrics/MetricsHandlingE2E.java | 26 ++++++++++++++--- 4 files changed, 55 insertions(+), 9 deletions(-) diff --git a/micrometer-support/src/main/java/io/javaoperatorsdk/operator/monitoring/micrometer/MicrometerMetricsV2.java b/micrometer-support/src/main/java/io/javaoperatorsdk/operator/monitoring/micrometer/MicrometerMetricsV2.java index b5a2804fa7..02b67648fe 100644 --- a/micrometer-support/src/main/java/io/javaoperatorsdk/operator/monitoring/micrometer/MicrometerMetricsV2.java +++ b/micrometer-support/src/main/java/io/javaoperatorsdk/operator/monitoring/micrometer/MicrometerMetricsV2.java @@ -62,8 +62,8 @@ public class MicrometerMetricsV2 implements Metrics { public static final String RECONCILIATION_EXECUTION_DURATION = RECONCILIATIONS + "execution.duration"; - public static final String NO_NAMESPACE = "NO_NAMESPACE"; - public static final String UNKNOWN_ACTION = "UNKNOWN"; + public static final String NO_NAMESPACE_TAG = "no_namespace"; + public static final String UNKNOWN_ACTION_TAG = "unknown"; private final MeterRegistry registry; private final Map gauges = new ConcurrentHashMap<>(); @@ -182,7 +182,7 @@ public void eventReceived(Event event, Map metadata) { null, metadata, Tag.of(EVENT, event.getClass().getSimpleName()), - Tag.of(ACTION, UNKNOWN_ACTION)); + Tag.of(ACTION, UNKNOWN_ACTION_TAG)); } } @@ -254,7 +254,7 @@ private void addNamespaceTag(String namespace, List tags) { if (namespace != null && !namespace.isBlank()) { addTag(NAMESPACE, namespace, tags); } else { - addTag(NAMESPACE, NO_NAMESPACE, tags); + addTag(NAMESPACE, NO_NAMESPACE_TAG, tags); } } } diff --git a/sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingReconciler1.java b/sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingReconciler1.java index aee20abbad..ed6d65a4a9 100644 --- a/sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingReconciler1.java +++ b/sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingReconciler1.java @@ -15,14 +15,42 @@ */ package io.javaoperatorsdk.operator.sample.metrics; +import java.util.List; + +import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; +import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; +import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; +import io.javaoperatorsdk.operator.processing.event.ResourceID; +import io.javaoperatorsdk.operator.processing.event.source.EventSource; +import io.javaoperatorsdk.operator.processing.event.source.timer.TimerEventSource; import io.javaoperatorsdk.operator.sample.metrics.customresource.MetricsHandlingCustomResource1; @ControllerConfiguration public class MetricsHandlingReconciler1 extends AbstractMetricsHandlingReconciler { + private static final long TIMER_DELAY = 5000; + + private final TimerEventSource timerEventSource; + public MetricsHandlingReconciler1() { super(100); + timerEventSource = new TimerEventSource<>(); + } + + @SuppressWarnings("unchecked") + @Override + public List> prepareEventSources( + EventSourceContext context) { + return List.of((EventSource) timerEventSource); + } + + @Override + public UpdateControl reconcile( + MetricsHandlingCustomResource1 resource, Context context) { + var result = super.reconcile(resource, context); + timerEventSource.scheduleOnce(ResourceID.fromResource(resource), TIMER_DELAY); + return result; } } diff --git a/sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingSampleOperator.java b/sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingSampleOperator.java index 9979e4cd6b..9f563bec9c 100644 --- a/sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingSampleOperator.java +++ b/sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingSampleOperator.java @@ -123,7 +123,7 @@ public Duration step() { new ProcessorMetrics().bindTo(compositeRegistry); new UptimeMetrics().bindTo(compositeRegistry); - return MicrometerMetricsV2.newBuilder(compositeRegistry).build(); + return MicrometerMetricsV2.newBuilder(compositeRegistry).withNamespaceAsTag().build(); } @SuppressWarnings("unchecked") diff --git a/sample-operators/metrics-processing/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java b/sample-operators/metrics-processing/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java index 3c17f36ac1..51b908714b 100644 --- a/sample-operators/metrics-processing/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java +++ b/sample-operators/metrics-processing/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java @@ -18,6 +18,7 @@ import java.io.*; import java.net.HttpURLConnection; import java.net.URL; +import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.ArrayDeque; @@ -226,24 +227,41 @@ private void verifyPrometheusMetrics() { "reconciliations_execution_duration_milliseconds_count", Duration.ofSeconds(30)); + // Verify timer event source events are recorded with "no_namespace" tag + // Timer events are not ResourceEvents so they have no namespace + assertMetricPresent( + prometheusUrl, + "events_received_total{namespace=\"no_namespace\"}", + Duration.ofSeconds(30), + "events_received_total", + "no_namespace"); + log.info("All metrics verified successfully in Prometheus"); } private void assertMetricPresent(String prometheusUrl, String metricName, Duration timeout) { + assertMetricPresent(prometheusUrl, metricName, timeout, metricName); + } + + private void assertMetricPresent( + String prometheusUrl, String query, Duration timeout, String... expectedSubstrings) { await() .atMost(timeout) .pollInterval(Duration.ofSeconds(5)) .untilAsserted( () -> { - String result = queryPrometheus(prometheusUrl, metricName); - log.info("{}: {}", metricName, result); + String result = queryPrometheus(prometheusUrl, query); + log.info("{}: {}", query, result); assertThat(result).contains("\"status\":\"success\""); - assertThat(result).contains(metricName); + for (String expected : expectedSubstrings) { + assertThat(result).contains(expected); + } }); } private String queryPrometheus(String prometheusUrl, String query) throws IOException { - String urlString = prometheusUrl + "/api/v1/query?query=" + query; + String urlString = + prometheusUrl + "/api/v1/query?query=" + URLEncoder.encode(query, StandardCharsets.UTF_8); URL url = new URL(urlString); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); From 0685dd66fb27947f17ee6e8599c534b3d9f5f377 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Fri, 27 Mar 2026 13:16:05 +0100 Subject: [PATCH 6/8] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../operator/sample/metrics/MetricsHandlingE2E.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/sample-operators/metrics-processing/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java b/sample-operators/metrics-processing/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java index 51b908714b..a681655150 100644 --- a/sample-operators/metrics-processing/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java +++ b/sample-operators/metrics-processing/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java @@ -227,14 +227,18 @@ private void verifyPrometheusMetrics() { "reconciliations_execution_duration_milliseconds_count", Duration.ofSeconds(30)); + // First verify events_received_total exists at all (from ResourceEvents) + assertMetricPresent(prometheusUrl, "events_received_total", Duration.ofSeconds(30)); + // Verify timer event source events are recorded with "no_namespace" tag - // Timer events are not ResourceEvents so they have no namespace + // Timer events are not ResourceEvents so they have no namespace. + // exported_namespace is needed because of otel collector. assertMetricPresent( prometheusUrl, - "events_received_total{namespace=\"no_namespace\"}", + "events_received_total{namespace=\"exported_namespace\"}", Duration.ofSeconds(30), "events_received_total", - "no_namespace"); + "exported_namespace"); log.info("All metrics verified successfully in Prometheus"); } @@ -254,6 +258,7 @@ private void assertMetricPresent( log.info("{}: {}", query, result); assertThat(result).contains("\"status\":\"success\""); for (String expected : expectedSubstrings) { + log.info("Checking if result: {} contains expected: {}", result, expected); assertThat(result).contains(expected); } }); From c3f86989b724028d944b8af0ce3cd3956a5d273c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Fri, 27 Mar 2026 13:28:07 +0100 Subject: [PATCH 7/8] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../operator/sample/metrics/MetricsHandlingE2E.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sample-operators/metrics-processing/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java b/sample-operators/metrics-processing/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java index a681655150..9837e09308 100644 --- a/sample-operators/metrics-processing/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java +++ b/sample-operators/metrics-processing/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java @@ -235,7 +235,7 @@ private void verifyPrometheusMetrics() { // exported_namespace is needed because of otel collector. assertMetricPresent( prometheusUrl, - "events_received_total{namespace=\"exported_namespace\"}", + "events_received_total{exported_namespace=\"no_namespace\"}", Duration.ofSeconds(30), "events_received_total", "exported_namespace"); From 4ddc85af610715e744d9e3b001061ba43480c45c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Fri, 27 Mar 2026 13:42:10 +0100 Subject: [PATCH 8/8] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../monitoring/micrometer/MicrometerMetricsV2.java | 2 +- .../operator/sample/metrics/MetricsHandlingE2E.java | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/micrometer-support/src/main/java/io/javaoperatorsdk/operator/monitoring/micrometer/MicrometerMetricsV2.java b/micrometer-support/src/main/java/io/javaoperatorsdk/operator/monitoring/micrometer/MicrometerMetricsV2.java index 02b67648fe..deb6c98c9e 100644 --- a/micrometer-support/src/main/java/io/javaoperatorsdk/operator/monitoring/micrometer/MicrometerMetricsV2.java +++ b/micrometer-support/src/main/java/io/javaoperatorsdk/operator/monitoring/micrometer/MicrometerMetricsV2.java @@ -179,7 +179,7 @@ public void eventReceived(Event event, Map metadata) { } else { incrementCounter( EVENTS_RECEIVED, - null, + event.getRelatedCustomResourceID().getNamespace().orElse(null), metadata, Tag.of(EVENT, event.getClass().getSimpleName()), Tag.of(ACTION, UNKNOWN_ACTION_TAG)); diff --git a/sample-operators/metrics-processing/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java b/sample-operators/metrics-processing/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java index 9837e09308..29d05b8138 100644 --- a/sample-operators/metrics-processing/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java +++ b/sample-operators/metrics-processing/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java @@ -230,15 +230,17 @@ private void verifyPrometheusMetrics() { // First verify events_received_total exists at all (from ResourceEvents) assertMetricPresent(prometheusUrl, "events_received_total", Duration.ofSeconds(30)); - // Verify timer event source events are recorded with "no_namespace" tag - // Timer events are not ResourceEvents so they have no namespace. - // exported_namespace is needed because of otel collector. + // Verify timer event source events are recorded. + // Timer events are not ResourceEvents, so they get action="unknown". + // The namespace comes from the event's ResourceID (same as the associated resource). + // The "exported_namespace" label is used because OTel collector's + // resource_to_telemetry_conversion renames Micrometer's "namespace" tag. assertMetricPresent( prometheusUrl, - "events_received_total{exported_namespace=\"no_namespace\"}", + "events_received_total{action=\"unknown\"}", Duration.ofSeconds(30), "events_received_total", - "exported_namespace"); + "unknown"); log.info("All metrics verified successfully in Prometheus"); }