From daeee6b97fc084c8825f45fb7427696774226899 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Mon, 13 Apr 2026 14:47:13 +0200 Subject: [PATCH 01/35] improve: metrics processing to showcase all operations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - rename of the module to operations - showcase health probes Signed-off-by: Attila Mészáros --- .github/workflows/e2e-test.yml | 2 +- docs/content/en/blog/releases/v5-3-release.md | 2 +- .../en/docs/documentation/operations/helm-chart.md | 10 +++++----- .../en/docs/documentation/operations/metrics.md | 4 ++-- .../{metrics-processing => operations}/pom.xml | 8 ++++---- .../metrics/AbstractMetricsHandlingReconciler.java | 0 .../sample/metrics/MetricsHandlingReconciler1.java | 0 .../sample/metrics/MetricsHandlingReconciler2.java | 0 .../sample/metrics/MetricsHandlingSampleOperator.java | 2 +- .../customresource/MetricsHandlingCustomResource1.java | 0 .../customresource/MetricsHandlingCustomResource2.java | 0 .../metrics/customresource/MetricsHandlingSpec.java | 0 .../metrics/customresource/MetricsHandlingStatus.java | 0 .../io/javaoperatorsdk/operator/sample/deployment.yaml | 0 .../src/main/resources/log4j2.xml | 0 .../src/main/resources/otlp-config.yaml | 0 .../operator/sample/metrics/MetricsHandlingE2E.java | 2 +- .../src/test/resources/helm-values.yaml | 6 +++--- sample-operators/pom.xml | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) rename sample-operators/{metrics-processing => operations}/pom.xml (93%) rename sample-operators/{metrics-processing => operations}/src/main/java/io/javaoperatorsdk/operator/sample/metrics/AbstractMetricsHandlingReconciler.java (100%) rename sample-operators/{metrics-processing => operations}/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingReconciler1.java (100%) rename sample-operators/{metrics-processing => operations}/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingReconciler2.java (100%) rename sample-operators/{metrics-processing => operations}/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingSampleOperator.java (99%) rename sample-operators/{metrics-processing => operations}/src/main/java/io/javaoperatorsdk/operator/sample/metrics/customresource/MetricsHandlingCustomResource1.java (100%) rename sample-operators/{metrics-processing => operations}/src/main/java/io/javaoperatorsdk/operator/sample/metrics/customresource/MetricsHandlingCustomResource2.java (100%) rename sample-operators/{metrics-processing => operations}/src/main/java/io/javaoperatorsdk/operator/sample/metrics/customresource/MetricsHandlingSpec.java (100%) rename sample-operators/{metrics-processing => operations}/src/main/java/io/javaoperatorsdk/operator/sample/metrics/customresource/MetricsHandlingStatus.java (100%) rename sample-operators/{metrics-processing => operations}/src/main/resources/io/javaoperatorsdk/operator/sample/deployment.yaml (100%) rename sample-operators/{metrics-processing => operations}/src/main/resources/log4j2.xml (100%) rename sample-operators/{metrics-processing => operations}/src/main/resources/otlp-config.yaml (100%) rename sample-operators/{metrics-processing => operations}/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java (99%) rename sample-operators/{metrics-processing => operations}/src/test/resources/helm-values.yaml (86%) diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index 5138d991b7..d3dc3f9ff9 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -24,7 +24,7 @@ jobs: - "sample-operators/tomcat-operator" - "sample-operators/webpage" - "sample-operators/leader-election" - - "sample-operators/metrics-processing" + - "sample-operators/operations" runs-on: ubuntu-latest steps: - name: Checkout diff --git a/docs/content/en/blog/releases/v5-3-release.md b/docs/content/en/blog/releases/v5-3-release.md index 12b9bfd30e..632d82e5b1 100644 --- a/docs/content/en/blog/releases/v5-3-release.md +++ b/docs/content/en/blog/releases/v5-3-release.md @@ -97,7 +97,7 @@ A ready-to-use **Grafana dashboard** is included at [`observability/josdk-operator-metrics-dashboard.json`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/observability/josdk-operator-metrics-dashboard.json). The -[`metrics-processing` sample operator](https://github.com/java-operator-sdk/java-operator-sdk/tree/main/sample-operators/metrics-processing) +[`operations` sample operator](https://github.com/java-operator-sdk/java-operator-sdk/tree/main/sample-operators/operations) provides a complete end-to-end setup with Prometheus, Grafana, and an OpenTelemetry Collector, installable via `observability/install-observability.sh`. This is a good starting point for verifying metrics in a real cluster. diff --git a/docs/content/en/docs/documentation/operations/helm-chart.md b/docs/content/en/docs/documentation/operations/helm-chart.md index 1758ac20af..ed76e387aa 100644 --- a/docs/content/en/docs/documentation/operations/helm-chart.md +++ b/docs/content/en/docs/documentation/operations/helm-chart.md @@ -11,7 +11,7 @@ patterns so you don't have to write a chart from scratch. The chart is maintaine Contributions are more than welcome. The chart is used in the -[`metrics-processing` sample operator E2E test](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/sample-operators/metrics-processing/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java) +[`operations` sample operator E2E test](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java) to deploy the operator to a cluster via Helm. ## What the Chart Provides @@ -80,16 +80,16 @@ for all available options. ## Usage Example -A working example of how to use the chart can be found in the metrics-processing sample operator's -[`helm-values.yaml`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/sample-operators/metrics-processing/src/test/resources/helm-values.yaml): +A working example of how to use the chart can be found in the operations sample operator's +[`helm-values.yaml`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/sample-operators/operations/src/test/resources/helm-values.yaml): ```yaml image: - repository: metrics-processing-operator + repository: operations-operator pullPolicy: Never tag: "latest" -nameOverride: "metrics-processing-operator" +nameOverride: "operations-operator" resources: {} diff --git a/docs/content/en/docs/documentation/operations/metrics.md b/docs/content/en/docs/documentation/operations/metrics.md index fc40070e46..6ac241ec8e 100644 --- a/docs/content/en/docs/documentation/operations/metrics.md +++ b/docs/content/en/docs/documentation/operations/metrics.md @@ -103,9 +103,9 @@ observability sample (see below). #### Exploring metrics end-to-end The -[`metrics-processing` sample operator](https://github.com/java-operator-sdk/java-operator-sdk/tree/main/sample-operators/metrics-processing) +[`operations` sample operator](https://github.com/java-operator-sdk/java-operator-sdk/tree/main/sample-operators/operations) includes a full end-to-end test, -[`MetricsHandlingE2E`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/sample-operators/metrics-processing/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java), +[`MetricsHandlingE2E`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java), that: 1. Installs a local observability stack (Prometheus, Grafana, OpenTelemetry Collector) via diff --git a/sample-operators/metrics-processing/pom.xml b/sample-operators/operations/pom.xml similarity index 93% rename from sample-operators/metrics-processing/pom.xml rename to sample-operators/operations/pom.xml index c67f623e33..01d7949117 100644 --- a/sample-operators/metrics-processing/pom.xml +++ b/sample-operators/operations/pom.xml @@ -25,10 +25,10 @@ 5.3.4-SNAPSHOT - sample-metrics-processing + sample-operations jar - Operator SDK - Samples - Metrics processing - Showcases to handle metrics setup and deploys related tooling and dashboards + Operator SDK - Samples - Operations + Showcases operations related features setup like metrics, and deploys related tooling and dashboards @@ -99,7 +99,7 @@ gcr.io/distroless/java17-debian11 - metrics-processing-operator + operations-operator diff --git a/sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/AbstractMetricsHandlingReconciler.java b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/AbstractMetricsHandlingReconciler.java similarity index 100% rename from sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/AbstractMetricsHandlingReconciler.java rename to sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/AbstractMetricsHandlingReconciler.java diff --git a/sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingReconciler1.java b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingReconciler1.java similarity index 100% rename from sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingReconciler1.java rename to sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingReconciler1.java diff --git a/sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingReconciler2.java b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingReconciler2.java similarity index 100% rename from sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingReconciler2.java rename to sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingReconciler2.java diff --git a/sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingSampleOperator.java b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingSampleOperator.java similarity index 99% rename from sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingSampleOperator.java rename to sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingSampleOperator.java index 2c6b9c3e90..2b0e3b1df5 100644 --- a/sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingSampleOperator.java +++ b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingSampleOperator.java @@ -96,7 +96,7 @@ public static void main(String[] args) { @Override public Map resourceAttributes() { - return Map.of("service.name", "josdk", "operator", "metrics-processing"); + return Map.of("service.name", "josdk", "operator", "operations"); } }; diff --git a/sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/customresource/MetricsHandlingCustomResource1.java b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/customresource/MetricsHandlingCustomResource1.java similarity index 100% rename from sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/customresource/MetricsHandlingCustomResource1.java rename to sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/customresource/MetricsHandlingCustomResource1.java diff --git a/sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/customresource/MetricsHandlingCustomResource2.java b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/customresource/MetricsHandlingCustomResource2.java similarity index 100% rename from sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/customresource/MetricsHandlingCustomResource2.java rename to sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/customresource/MetricsHandlingCustomResource2.java diff --git a/sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/customresource/MetricsHandlingSpec.java b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/customresource/MetricsHandlingSpec.java similarity index 100% rename from sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/customresource/MetricsHandlingSpec.java rename to sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/customresource/MetricsHandlingSpec.java diff --git a/sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/customresource/MetricsHandlingStatus.java b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/customresource/MetricsHandlingStatus.java similarity index 100% rename from sample-operators/metrics-processing/src/main/java/io/javaoperatorsdk/operator/sample/metrics/customresource/MetricsHandlingStatus.java rename to sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/customresource/MetricsHandlingStatus.java diff --git a/sample-operators/metrics-processing/src/main/resources/io/javaoperatorsdk/operator/sample/deployment.yaml b/sample-operators/operations/src/main/resources/io/javaoperatorsdk/operator/sample/deployment.yaml similarity index 100% rename from sample-operators/metrics-processing/src/main/resources/io/javaoperatorsdk/operator/sample/deployment.yaml rename to sample-operators/operations/src/main/resources/io/javaoperatorsdk/operator/sample/deployment.yaml diff --git a/sample-operators/metrics-processing/src/main/resources/log4j2.xml b/sample-operators/operations/src/main/resources/log4j2.xml similarity index 100% rename from sample-operators/metrics-processing/src/main/resources/log4j2.xml rename to sample-operators/operations/src/main/resources/log4j2.xml diff --git a/sample-operators/metrics-processing/src/main/resources/otlp-config.yaml b/sample-operators/operations/src/main/resources/otlp-config.yaml similarity index 100% rename from sample-operators/metrics-processing/src/main/resources/otlp-config.yaml rename to sample-operators/operations/src/main/resources/otlp-config.yaml diff --git a/sample-operators/metrics-processing/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java b/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java similarity index 99% rename from sample-operators/metrics-processing/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java rename to sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java index 34e96a5870..8fbe442c8c 100644 --- a/sample-operators/metrics-processing/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java +++ b/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java @@ -59,7 +59,7 @@ class MetricsHandlingE2E { static final int OTEL_COLLECTOR_PORT = 4318; public static final Duration TEST_DURATION = Duration.ofSeconds(60); public static final String NAME_LABEL_KEY = "app.kubernetes.io/name"; - static final String HELM_RELEASE_NAME = "metrics-processing"; + static final String HELM_RELEASE_NAME = "operations"; private LocalPortForward prometheusPortForward; private LocalPortForward otelCollectorPortForward; diff --git a/sample-operators/metrics-processing/src/test/resources/helm-values.yaml b/sample-operators/operations/src/test/resources/helm-values.yaml similarity index 86% rename from sample-operators/metrics-processing/src/test/resources/helm-values.yaml rename to sample-operators/operations/src/test/resources/helm-values.yaml index bb8e251139..856168fae1 100644 --- a/sample-operators/metrics-processing/src/test/resources/helm-values.yaml +++ b/sample-operators/operations/src/test/resources/helm-values.yaml @@ -14,15 +14,15 @@ # limitations under the License. # -# Helm values for metrics-processing operator E2E test deployment +# Helm values for operations operator E2E test deployment # Used with the generic-operator-chart from helm/generic-helm-chart/ image: - repository: metrics-processing-operator + repository: operations-operator pullPolicy: Never tag: "latest" -nameOverride: "metrics-processing-operator" +nameOverride: "operations-operator" resources: {} diff --git a/sample-operators/pom.xml b/sample-operators/pom.xml index 25a745012c..504aba5e78 100644 --- a/sample-operators/pom.xml +++ b/sample-operators/pom.xml @@ -35,6 +35,6 @@ mysql-schema leader-election controller-namespace-deletion - metrics-processing + operations From 65e9984d188e991fe7cbcaae007d4b76de153b8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Mon, 13 Apr 2026 15:42:50 +0200 Subject: [PATCH 02/35] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../documentation/operations/health-probes.md | 104 ++++++++++++++++++ .../templates/deployment.yaml | 24 ++++ .../tests/deployment_test.yaml | 54 +++++++++ helm/generic-helm-chart/values.yaml | 16 +++ sample-operators/operations/pom.xml | 5 + .../MetricsHandlingSampleOperator.java | 12 +- .../sample/metrics/ReadinessHandler.java | 44 ++++++++ .../sample/metrics/StartupHandler.java | 51 +++++++++ .../src/test/resources/helm-values.yaml | 6 + 9 files changed, 315 insertions(+), 1 deletion(-) create mode 100644 docs/content/en/docs/documentation/operations/health-probes.md create mode 100644 sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/ReadinessHandler.java create mode 100644 sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/StartupHandler.java diff --git a/docs/content/en/docs/documentation/operations/health-probes.md b/docs/content/en/docs/documentation/operations/health-probes.md new file mode 100644 index 0000000000..1b76a6238b --- /dev/null +++ b/docs/content/en/docs/documentation/operations/health-probes.md @@ -0,0 +1,104 @@ +--- +title: Health Probes +weight: 75 +--- + +Operators running in Kubernetes should expose health probe endpoints so that the kubelet can detect startup +failures and runtime degradation. JOSDK provides the building blocks through its +[`RuntimeInfo`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/RuntimeInfo.java) +API. + +## RuntimeInfo + +`RuntimeInfo` is available via `operator.getRuntimeInfo()` and exposes: + +| Method | Purpose | +|---|---| +| `isStarted()` | `true` once the operator and all its controllers have fully started | +| `allEventSourcesAreHealthy()` | `true` when every registered event source (informers, polling sources, etc.) reports a healthy status | +| `unhealthyEventSources()` | returns a map of controller name → unhealthy event sources, useful for diagnostics | + +These map naturally to Kubernetes probes: + +- **Startup probe** → `isStarted()` — fails until all informers have synced and the operator is ready to + reconcile. +- **Readiness probe** → `allEventSourcesAreHealthy()` — fails if an informer loses its watch connection + or any event source reports an unhealthy status. + +## Setting Up Probe Endpoints + +The example below uses [Jetty](https://eclipse.dev/jetty/) to expose health probe endpoints. Any HTTP +server library works — the key is calling the `RuntimeInfo` methods to determine the response code. + +```java +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.handler.ContextHandlerCollection; + +Operator operator = new Operator(); +operator.register(new MyReconciler()); +operator.start(); + +var startup = new ContextHandler(new StartupHandler(operator), "/startup"); +var readiness = new ContextHandler(new ReadinessHandler(operator), "/ready"); +Server server = new Server(8080); +server.setHandler(new ContextHandlerCollection(startup, readiness)); +server.start(); +``` + +Where `StartupHandler` and `ReadinessHandler` extend `org.eclipse.jetty.server.Handler.Abstract` and +check `operator.getRuntimeInfo().isStarted()` and +`operator.getRuntimeInfo().allEventSourcesAreHealthy()` respectively. + +See the +[`operations` sample operator](https://github.com/java-operator-sdk/java-operator-sdk/tree/main/sample-operators/operations) +for a complete working example. + +## Kubernetes Deployment Configuration + +Once your operator exposes probe endpoints, configure them in your Deployment manifest: + +```yaml +containers: +- name: operator + ports: + - name: probes + containerPort: 8080 + startupProbe: + httpGet: + path: /startup + port: probes + initialDelaySeconds: 1 + periodSeconds: 3 + failureThreshold: 20 + readinessProbe: + httpGet: + path: /ready + port: probes + initialDelaySeconds: 5 + periodSeconds: 5 + failureThreshold: 3 +``` + +The startup probe gives the operator time to start (up to ~60 s with the settings above). Once the startup +probe succeeds, the readiness probe takes over and will mark the pod as not-ready if any event source +becomes unhealthy. + +## Helm Chart Support + +The [generic Helm chart](/docs/documentation/operations/helm-chart) supports health probes out of the box. +Enable them in your `values.yaml`: + +```yaml +probes: + port: 8080 + startup: + enabled: true + path: /startup + readiness: + enabled: true + path: /ready +``` + +All probe timing parameters (`initialDelaySeconds`, `periodSeconds`, `failureThreshold`) have sensible +defaults and can be overridden. diff --git a/helm/generic-helm-chart/templates/deployment.yaml b/helm/generic-helm-chart/templates/deployment.yaml index dd06916155..30f28274bc 100644 --- a/helm/generic-helm-chart/templates/deployment.yaml +++ b/helm/generic-helm-chart/templates/deployment.yaml @@ -54,6 +54,30 @@ spec: {{- toYaml .Values.securityContext | nindent 12 }} image: "{{ required "A valid .Values.image.repository is required" .Values.image.repository }}:{{ include "generic-operator.imageTag" . }}" imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- if or .Values.probes.startup.enabled .Values.probes.readiness.enabled }} + ports: + - name: probes + containerPort: {{ .Values.probes.port }} + protocol: TCP + {{- end }} + {{- if .Values.probes.startup.enabled }} + startupProbe: + httpGet: + path: {{ .Values.probes.startup.path }} + port: probes + initialDelaySeconds: {{ .Values.probes.startup.initialDelaySeconds }} + periodSeconds: {{ .Values.probes.startup.periodSeconds }} + failureThreshold: {{ .Values.probes.startup.failureThreshold }} + {{- end }} + {{- if .Values.probes.readiness.enabled }} + readinessProbe: + httpGet: + path: {{ .Values.probes.readiness.path }} + port: probes + initialDelaySeconds: {{ .Values.probes.readiness.initialDelaySeconds }} + periodSeconds: {{ .Values.probes.readiness.periodSeconds }} + failureThreshold: {{ .Values.probes.readiness.failureThreshold }} + {{- end }} env: - name: OPERATOR_NAMESPACE valueFrom: diff --git a/helm/generic-helm-chart/tests/deployment_test.yaml b/helm/generic-helm-chart/tests/deployment_test.yaml index bdc8845ae9..61d68c0d00 100644 --- a/helm/generic-helm-chart/tests/deployment_test.yaml +++ b/helm/generic-helm-chart/tests/deployment_test.yaml @@ -288,3 +288,57 @@ tests: - equal: path: spec.template.spec.serviceAccountName value: my-operator + + - it: should not include probes by default + asserts: + - isNull: + path: spec.template.spec.containers[0].startupProbe + - isNull: + path: spec.template.spec.containers[0].readinessProbe + + - it: should add startup probe when enabled + documentSelector: + path: kind + value: Deployment + set: + probes.startup.enabled: true + asserts: + - equal: + path: spec.template.spec.containers[0].startupProbe.httpGet.path + value: /startup + - equal: + path: spec.template.spec.containers[0].startupProbe.httpGet.port + value: probes + - contains: + path: spec.template.spec.containers[0].ports + content: + name: probes + containerPort: 8080 + protocol: TCP + + - it: should add readiness probe when enabled + documentSelector: + path: kind + value: Deployment + set: + probes.readiness.enabled: true + asserts: + - equal: + path: spec.template.spec.containers[0].readinessProbe.httpGet.path + value: /ready + - equal: + path: spec.template.spec.containers[0].readinessProbe.httpGet.port + value: probes + + - it: should add both probes when both enabled + documentSelector: + path: kind + value: Deployment + set: + probes.startup.enabled: true + probes.readiness.enabled: true + asserts: + - isNotNull: + path: spec.template.spec.containers[0].startupProbe + - isNotNull: + path: spec.template.spec.containers[0].readinessProbe diff --git a/helm/generic-helm-chart/values.yaml b/helm/generic-helm-chart/values.yaml index 8ab452059c..c623e215f9 100644 --- a/helm/generic-helm-chart/values.yaml +++ b/helm/generic-helm-chart/values.yaml @@ -128,3 +128,19 @@ extraVolumeMounts: [] # RBAC configuration rbac: create: true + +# Health probes configuration +probes: + port: 8080 + startup: + enabled: false + path: /startup + initialDelaySeconds: 1 + periodSeconds: 3 + failureThreshold: 20 + readiness: + enabled: false + path: /ready + initialDelaySeconds: 5 + periodSeconds: 5 + failureThreshold: 3 diff --git a/sample-operators/operations/pom.xml b/sample-operators/operations/pom.xml index 01d7949117..fbfef08eff 100644 --- a/sample-operators/operations/pom.xml +++ b/sample-operators/operations/pom.xml @@ -82,6 +82,11 @@ awaitility compile + + org.eclipse.jetty + jetty-server + 12.1.0 + io.javaoperatorsdk operator-framework-junit diff --git a/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingSampleOperator.java b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingSampleOperator.java index 2b0e3b1df5..be5cd1b986 100644 --- a/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingSampleOperator.java +++ b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingSampleOperator.java @@ -23,6 +23,9 @@ import java.util.HashMap; import java.util.Map; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import org.slf4j.Logger; @@ -59,7 +62,7 @@ public class MetricsHandlingSampleOperator { * Based on env variables a different flavor of Reconciler is used, showcasing how the same logic * can be implemented using the low level and higher level APIs. */ - public static void main(String[] args) { + public static void main(String[] args) throws Exception { log.info("Metrics Handling Sample Operator starting!"); var configProviders = new ArrayList(); @@ -77,6 +80,13 @@ public static void main(String[] args) { new MetricsHandlingReconciler2(), configLoader.applyControllerConfigs(MetricsHandlingReconciler2.NAME)); operator.start(); + + var startup = new ContextHandler(new StartupHandler(operator), "/startup"); + var readiness = new ContextHandler(new ReadinessHandler(operator), "/ready"); + Server server = new Server(8080); + server.setHandler(new ContextHandlerCollection(startup, readiness)); + server.start(); + log.info("Health probe server started on port 8080"); } public static @NonNull Metrics initOTLPMetrics(boolean localRun) { diff --git a/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/ReadinessHandler.java b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/ReadinessHandler.java new file mode 100644 index 0000000000..8f28dc9e31 --- /dev/null +++ b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/ReadinessHandler.java @@ -0,0 +1,44 @@ +/* + * Copyright Java Operator SDK Authors + * + * 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 io.javaoperatorsdk.operator.sample.metrics; + +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; + +import io.javaoperatorsdk.operator.Operator; + +import static io.javaoperatorsdk.operator.sample.metrics.StartupHandler.sendMessage; + +public class ReadinessHandler extends Handler.Abstract { + + private final Operator operator; + + public ReadinessHandler(Operator operator) { + this.operator = operator; + } + + @Override + public boolean handle(Request request, Response response, Callback callback) { + if (operator.getRuntimeInfo().allEventSourcesAreHealthy()) { + sendMessage(response, 200, "ready", callback); + } else { + sendMessage(response, 400, "not ready: an event source is not healthy", callback); + } + return true; + } +} diff --git a/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/StartupHandler.java b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/StartupHandler.java new file mode 100644 index 0000000000..6051b10f06 --- /dev/null +++ b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/StartupHandler.java @@ -0,0 +1,51 @@ +/* + * Copyright Java Operator SDK Authors + * + * 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 io.javaoperatorsdk.operator.sample.metrics; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; + +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; + +import io.javaoperatorsdk.operator.Operator; + +public class StartupHandler extends Handler.Abstract { + + private final Operator operator; + + public StartupHandler(Operator operator) { + this.operator = operator; + } + + @Override + public boolean handle(Request request, Response response, Callback callback) { + if (operator.getRuntimeInfo().isStarted()) { + sendMessage(response, 200, "started", callback); + } else { + sendMessage(response, 400, "not started yet", callback); + } + return true; + } + + static void sendMessage(Response response, int code, String message, Callback callback) { + response.setStatus(code); + response.getHeaders().put("Content-Type", "text/plain; charset=utf-8"); + response.write(true, ByteBuffer.wrap(message.getBytes(StandardCharsets.UTF_8)), callback); + } +} diff --git a/sample-operators/operations/src/test/resources/helm-values.yaml b/sample-operators/operations/src/test/resources/helm-values.yaml index 856168fae1..269a840c3a 100644 --- a/sample-operators/operations/src/test/resources/helm-values.yaml +++ b/sample-operators/operations/src/test/resources/helm-values.yaml @@ -33,3 +33,9 @@ primaryResources: - metricshandlingcustomresource1s - metricshandlingcustomresource2s +probes: + startup: + enabled: true + readiness: + enabled: true + From 7345b47df7e25667932410a01434451bd3965262 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Fri, 17 Apr 2026 11:01:24 +0200 Subject: [PATCH 03/35] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../sample/metrics/MetricsHandlingSampleOperator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingSampleOperator.java b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingSampleOperator.java index be5cd1b986..be621acab3 100644 --- a/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingSampleOperator.java +++ b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingSampleOperator.java @@ -79,14 +79,14 @@ public static void main(String[] args) throws Exception { operator.register( new MetricsHandlingReconciler2(), configLoader.applyControllerConfigs(MetricsHandlingReconciler2.NAME)); - operator.start(); - var startup = new ContextHandler(new StartupHandler(operator), "/startup"); var readiness = new ContextHandler(new ReadinessHandler(operator), "/ready"); Server server = new Server(8080); server.setHandler(new ContextHandlerCollection(startup, readiness)); server.start(); log.info("Health probe server started on port 8080"); + + operator.start(); } public static @NonNull Metrics initOTLPMetrics(boolean localRun) { From 7db8e883b22f2eddad21972a9c4377efb3bc732a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Fri, 17 Apr 2026 11:35:06 +0200 Subject: [PATCH 04/35] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../documentation/operations/health-probes.md | 56 +++++++++---------- .../tests/deployment_test.yaml | 4 +- helm/generic-helm-chart/values.yaml | 4 +- sample-operators/operations/pom.xml | 2 +- ...StartupHandler.java => HealthHandler.java} | 16 ++++-- .../MetricsHandlingSampleOperator.java | 6 +- .../sample/metrics/ReadinessHandler.java | 44 --------------- .../src/test/resources/helm-values.yaml | 3 +- 8 files changed, 44 insertions(+), 91 deletions(-) rename sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/{StartupHandler.java => HealthHandler.java} (70%) delete mode 100644 sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/ReadinessHandler.java diff --git a/docs/content/en/docs/documentation/operations/health-probes.md b/docs/content/en/docs/documentation/operations/health-probes.md index 1b76a6238b..2627d3a66d 100644 --- a/docs/content/en/docs/documentation/operations/health-probes.md +++ b/docs/content/en/docs/documentation/operations/health-probes.md @@ -17,38 +17,43 @@ API. | `isStarted()` | `true` once the operator and all its controllers have fully started | | `allEventSourcesAreHealthy()` | `true` when every registered event source (informers, polling sources, etc.) reports a healthy status | | `unhealthyEventSources()` | returns a map of controller name → unhealthy event sources, useful for diagnostics | +| `unhealthyInformerWrappingEventSourceHealthIndicator()` | returns a map of controller name → unhealthy informer-wrapping event sources, each exposing per-informer details via `InformerHealthIndicator` (`hasSynced()`, `isWatching()`, `isRunning()`, `getTargetNamespace()`) | -These map naturally to Kubernetes probes: +In most cases a single readiness probe backed by `allEventSourcesAreHealthy()` is sufficient: before the +operator has fully started the informers will not have synced yet, so the check naturally covers the startup +case as well. Once running, it detects runtime degradation such as a lost watch connection. -- **Startup probe** → `isStarted()` — fails until all informers have synced and the operator is ready to - reconcile. -- **Readiness probe** → `allEventSourcesAreHealthy()` — fails if an informer loses its watch connection - or any event source reports an unhealthy status. +### Fine-Grained Informer Diagnostics -## Setting Up Probe Endpoints +For advanced use cases — such as exposing per-informer health in a diagnostic endpoint or logging which +specific namespace lost its watch — `unhealthyInformerWrappingEventSourceHealthIndicator()` gives access to +individual `InformerHealthIndicator` instances. Each indicator exposes `hasSynced()`, `isWatching()`, +`isRunning()`, and `getTargetNamespace()`. This is typically not needed for a standard health probe but can +be valuable for operational dashboards or troubleshooting. -The example below uses [Jetty](https://eclipse.dev/jetty/) to expose health probe endpoints. Any HTTP +## Setting Up a Probe Endpoint + +The example below uses [Jetty](https://eclipse.dev/jetty/) to expose a `/healthz` endpoint. Any HTTP server library works — the key is calling the `RuntimeInfo` methods to determine the response code. ```java import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.ContextHandler; -import org.eclipse.jetty.server.handler.ContextHandlerCollection; Operator operator = new Operator(); operator.register(new MyReconciler()); -operator.start(); -var startup = new ContextHandler(new StartupHandler(operator), "/startup"); -var readiness = new ContextHandler(new ReadinessHandler(operator), "/ready"); +// start the health server before the operator so probes can be queried during startup +var health = new ContextHandler(new HealthHandler(operator), "/healthz"); Server server = new Server(8080); -server.setHandler(new ContextHandlerCollection(startup, readiness)); +server.setHandler(health); server.start(); + +operator.start(); ``` -Where `StartupHandler` and `ReadinessHandler` extend `org.eclipse.jetty.server.Handler.Abstract` and -check `operator.getRuntimeInfo().isStarted()` and -`operator.getRuntimeInfo().allEventSourcesAreHealthy()` respectively. +Where `HealthHandler` extends `org.eclipse.jetty.server.Handler.Abstract` and checks +`operator.getRuntimeInfo().allEventSourcesAreHealthy()`. See the [`operations` sample operator](https://github.com/java-operator-sdk/java-operator-sdk/tree/main/sample-operators/operations) @@ -56,7 +61,7 @@ for a complete working example. ## Kubernetes Deployment Configuration -Once your operator exposes probe endpoints, configure them in your Deployment manifest: +Once your operator exposes the probe endpoint, configure a readiness probe in your Deployment manifest: ```yaml containers: @@ -64,25 +69,17 @@ containers: ports: - name: probes containerPort: 8080 - startupProbe: - httpGet: - path: /startup - port: probes - initialDelaySeconds: 1 - periodSeconds: 3 - failureThreshold: 20 readinessProbe: httpGet: - path: /ready + path: /healthz port: probes initialDelaySeconds: 5 periodSeconds: 5 failureThreshold: 3 ``` -The startup probe gives the operator time to start (up to ~60 s with the settings above). Once the startup -probe succeeds, the readiness probe takes over and will mark the pod as not-ready if any event source -becomes unhealthy. +The readiness probe will mark the pod as not-ready until all informers have synced. After that, it +continues to monitor event source health at runtime. ## Helm Chart Support @@ -92,12 +89,9 @@ Enable them in your `values.yaml`: ```yaml probes: port: 8080 - startup: - enabled: true - path: /startup readiness: enabled: true - path: /ready + path: /healthz ``` All probe timing parameters (`initialDelaySeconds`, `periodSeconds`, `failureThreshold`) have sensible diff --git a/helm/generic-helm-chart/tests/deployment_test.yaml b/helm/generic-helm-chart/tests/deployment_test.yaml index 61d68c0d00..972feef7d1 100644 --- a/helm/generic-helm-chart/tests/deployment_test.yaml +++ b/helm/generic-helm-chart/tests/deployment_test.yaml @@ -305,7 +305,7 @@ tests: asserts: - equal: path: spec.template.spec.containers[0].startupProbe.httpGet.path - value: /startup + value: /healthz - equal: path: spec.template.spec.containers[0].startupProbe.httpGet.port value: probes @@ -325,7 +325,7 @@ tests: asserts: - equal: path: spec.template.spec.containers[0].readinessProbe.httpGet.path - value: /ready + value: /healthz - equal: path: spec.template.spec.containers[0].readinessProbe.httpGet.port value: probes diff --git a/helm/generic-helm-chart/values.yaml b/helm/generic-helm-chart/values.yaml index c623e215f9..fb325ad8d4 100644 --- a/helm/generic-helm-chart/values.yaml +++ b/helm/generic-helm-chart/values.yaml @@ -134,13 +134,13 @@ probes: port: 8080 startup: enabled: false - path: /startup + path: /healthz initialDelaySeconds: 1 periodSeconds: 3 failureThreshold: 20 readiness: enabled: false - path: /ready + path: /healthz initialDelaySeconds: 5 periodSeconds: 5 failureThreshold: 3 diff --git a/sample-operators/operations/pom.xml b/sample-operators/operations/pom.xml index fbfef08eff..8667604f96 100644 --- a/sample-operators/operations/pom.xml +++ b/sample-operators/operations/pom.xml @@ -85,7 +85,7 @@ org.eclipse.jetty jetty-server - 12.1.0 + 12.1.8 io.javaoperatorsdk diff --git a/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/StartupHandler.java b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/HealthHandler.java similarity index 70% rename from sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/StartupHandler.java rename to sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/HealthHandler.java index 6051b10f06..10dfae4cdf 100644 --- a/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/StartupHandler.java +++ b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/HealthHandler.java @@ -25,20 +25,26 @@ import io.javaoperatorsdk.operator.Operator; -public class StartupHandler extends Handler.Abstract { +/** + * Combined health endpoint that checks whether all event sources (informers, polling sources, etc.) + * are healthy. Before the operator has fully started the informers will not have synced yet, so + * this endpoint naturally covers the startup case as well. + */ +public class HealthHandler extends Handler.Abstract { private final Operator operator; - public StartupHandler(Operator operator) { + public HealthHandler(Operator operator) { this.operator = operator; } @Override public boolean handle(Request request, Response response, Callback callback) { - if (operator.getRuntimeInfo().isStarted()) { - sendMessage(response, 200, "started", callback); + var runtimeInfo = operator.getRuntimeInfo(); + if (runtimeInfo.isStarted() && runtimeInfo.allEventSourcesAreHealthy()) { + sendMessage(response, 200, "healthy", callback); } else { - sendMessage(response, 400, "not started yet", callback); + sendMessage(response, 503, "not healthy", callback); } return true; } diff --git a/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingSampleOperator.java b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingSampleOperator.java index be621acab3..348b5518b2 100644 --- a/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingSampleOperator.java +++ b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingSampleOperator.java @@ -25,7 +25,6 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.ContextHandler; -import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import org.slf4j.Logger; @@ -79,10 +78,9 @@ public static void main(String[] args) throws Exception { operator.register( new MetricsHandlingReconciler2(), configLoader.applyControllerConfigs(MetricsHandlingReconciler2.NAME)); - var startup = new ContextHandler(new StartupHandler(operator), "/startup"); - var readiness = new ContextHandler(new ReadinessHandler(operator), "/ready"); + var health = new ContextHandler(new HealthHandler(operator), "/healthz"); Server server = new Server(8080); - server.setHandler(new ContextHandlerCollection(startup, readiness)); + server.setHandler(health); server.start(); log.info("Health probe server started on port 8080"); diff --git a/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/ReadinessHandler.java b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/ReadinessHandler.java deleted file mode 100644 index 8f28dc9e31..0000000000 --- a/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/ReadinessHandler.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright Java Operator SDK Authors - * - * 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 io.javaoperatorsdk.operator.sample.metrics; - -import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.Response; -import org.eclipse.jetty.util.Callback; - -import io.javaoperatorsdk.operator.Operator; - -import static io.javaoperatorsdk.operator.sample.metrics.StartupHandler.sendMessage; - -public class ReadinessHandler extends Handler.Abstract { - - private final Operator operator; - - public ReadinessHandler(Operator operator) { - this.operator = operator; - } - - @Override - public boolean handle(Request request, Response response, Callback callback) { - if (operator.getRuntimeInfo().allEventSourcesAreHealthy()) { - sendMessage(response, 200, "ready", callback); - } else { - sendMessage(response, 400, "not ready: an event source is not healthy", callback); - } - return true; - } -} diff --git a/sample-operators/operations/src/test/resources/helm-values.yaml b/sample-operators/operations/src/test/resources/helm-values.yaml index 269a840c3a..c2d729947a 100644 --- a/sample-operators/operations/src/test/resources/helm-values.yaml +++ b/sample-operators/operations/src/test/resources/helm-values.yaml @@ -34,8 +34,7 @@ primaryResources: - metricshandlingcustomresource2s probes: - startup: - enabled: true readiness: enabled: true + path: /healthz From efb84c4273411467902259ac95b85bb86e138693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Fri, 17 Apr 2026 11:57:51 +0200 Subject: [PATCH 05/35] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../docs/documentation/operations/_index.md | 4 ++++ .../documentation/operations/health-probes.md | 19 ++++++++++++++++--- .../src/test/resources/helm-values.yaml | 3 +++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/docs/content/en/docs/documentation/operations/_index.md b/docs/content/en/docs/documentation/operations/_index.md index 1056b33c24..82dcde49f1 100644 --- a/docs/content/en/docs/documentation/operations/_index.md +++ b/docs/content/en/docs/documentation/operations/_index.md @@ -4,3 +4,7 @@ weight: 80 --- This section covers operations-related features for running and managing operators in production. + +See the +[`operations` sample operator](https://github.com/java-operator-sdk/java-operator-sdk/tree/main/sample-operators/operations) +for a complete working example that demonstrates health probes, metrics, and Helm-based deployment. diff --git a/docs/content/en/docs/documentation/operations/health-probes.md b/docs/content/en/docs/documentation/operations/health-probes.md index 2627d3a66d..ee300a7198 100644 --- a/docs/content/en/docs/documentation/operations/health-probes.md +++ b/docs/content/en/docs/documentation/operations/health-probes.md @@ -61,7 +61,9 @@ for a complete working example. ## Kubernetes Deployment Configuration -Once your operator exposes the probe endpoint, configure a readiness probe in your Deployment manifest: +Once your operator exposes the probe endpoint, configure probes in your Deployment manifest. Both the +startup and readiness probes can point to the same `/healthz` endpoint — the startup probe simply uses a +higher `failureThreshold` to give the operator time to initialize: ```yaml containers: @@ -69,6 +71,13 @@ containers: ports: - name: probes containerPort: 8080 + startupProbe: + httpGet: + path: /healthz + port: probes + initialDelaySeconds: 1 + periodSeconds: 3 + failureThreshold: 20 readinessProbe: httpGet: path: /healthz @@ -78,8 +87,9 @@ containers: failureThreshold: 3 ``` -The readiness probe will mark the pod as not-ready until all informers have synced. After that, it -continues to monitor event source health at runtime. +The startup probe gives the operator time to start (up to ~60 s with the settings above). Once the startup +probe succeeds, the readiness probe takes over and will mark the pod as not-ready if any event source +becomes unhealthy. ## Helm Chart Support @@ -89,6 +99,9 @@ Enable them in your `values.yaml`: ```yaml probes: port: 8080 + startup: + enabled: true + path: /healthz readiness: enabled: true path: /healthz diff --git a/sample-operators/operations/src/test/resources/helm-values.yaml b/sample-operators/operations/src/test/resources/helm-values.yaml index c2d729947a..4b255385c6 100644 --- a/sample-operators/operations/src/test/resources/helm-values.yaml +++ b/sample-operators/operations/src/test/resources/helm-values.yaml @@ -34,6 +34,9 @@ primaryResources: - metricshandlingcustomresource2s probes: + startup: + enabled: true + path: /healthz readiness: enabled: true path: /healthz From 587c308f96bb62c7207a46d67011f6d9856cb468 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Fri, 17 Apr 2026 13:10:48 +0200 Subject: [PATCH 06/35] fix handling empty yaml config file with a comment only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../operator/config/loader/provider/YamlConfigProvider.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/loader/provider/YamlConfigProvider.java b/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/loader/provider/YamlConfigProvider.java index fc80e46a43..6c9e64b8c5 100644 --- a/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/loader/provider/YamlConfigProvider.java +++ b/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/loader/provider/YamlConfigProvider.java @@ -111,6 +111,9 @@ private static Map load(Path path) { } Map result = MAPPER.readValue(parser, Map.class); return result != null ? result : Map.of(); + } catch (com.fasterxml.jackson.databind.exc.MismatchedInputException e) { + log.warn("{} contains no configuration data", path); + return Map.of(); } catch (IOException e) { throw new UncheckedIOException("Failed to load config YAML from " + path, e); } From 3a24a6b3972d094838b64982b48f9c8ec69f7bbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Fri, 17 Apr 2026 13:24:03 +0200 Subject: [PATCH 07/35] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- docs/content/en/docs/documentation/operations/health-probes.md | 2 +- helm/generic-helm-chart/values.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/en/docs/documentation/operations/health-probes.md b/docs/content/en/docs/documentation/operations/health-probes.md index ee300a7198..6766c59be0 100644 --- a/docs/content/en/docs/documentation/operations/health-probes.md +++ b/docs/content/en/docs/documentation/operations/health-probes.md @@ -1,6 +1,6 @@ --- title: Health Probes -weight: 75 +weight: 85 --- Operators running in Kubernetes should expose health probe endpoints so that the kubelet can detect startup diff --git a/helm/generic-helm-chart/values.yaml b/helm/generic-helm-chart/values.yaml index fb325ad8d4..9552fadd2f 100644 --- a/helm/generic-helm-chart/values.yaml +++ b/helm/generic-helm-chart/values.yaml @@ -136,7 +136,7 @@ probes: enabled: false path: /healthz initialDelaySeconds: 1 - periodSeconds: 3 + periodSeconds: 10 failureThreshold: 20 readiness: enabled: false From 37e81d6eeba8916a3438cf3bf20d168b84a5f656 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Fri, 17 Apr 2026 13:45:05 +0200 Subject: [PATCH 08/35] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- ...ricsHandlingE2E.java => OperationsE2E.java} | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) rename sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/metrics/{MetricsHandlingE2E.java => OperationsE2E.java} (96%) diff --git a/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java b/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/metrics/OperationsE2E.java similarity index 96% rename from sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java rename to sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/metrics/OperationsE2E.java index 8fbe442c8c..6d69053dce 100644 --- a/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java +++ b/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/metrics/OperationsE2E.java @@ -51,9 +51,9 @@ import static org.awaitility.Awaitility.await; @TestInstance(TestInstance.Lifecycle.PER_CLASS) -class MetricsHandlingE2E { +class OperationsE2E { - static final Logger log = LoggerFactory.getLogger(MetricsHandlingE2E.class); + static final Logger log = LoggerFactory.getLogger(OperationsE2E.class); static final String OBSERVABILITY_NAMESPACE = "observability"; static final int PROMETHEUS_PORT = 9090; static final int OTEL_COLLECTOR_PORT = 4318; @@ -66,7 +66,7 @@ class MetricsHandlingE2E { static final KubernetesClient client = new KubernetesClientBuilder().build(); - MetricsHandlingE2E() {} + OperationsE2E() {} @RegisterExtension AbstractOperatorExtension operator = @@ -106,7 +106,7 @@ private void helmInstall() { try { var chartPath = findProjectRoot("helm").toPath().resolve("helm/generic-helm-chart").toString(); - var valuesUrl = MetricsHandlingE2E.class.getClassLoader().getResource("helm-values.yaml"); + var valuesUrl = OperationsE2E.class.getClassLoader().getResource("helm-values.yaml"); if (valuesUrl == null) { throw new IllegalStateException("helm-values.yaml not found on classpath"); } @@ -388,12 +388,16 @@ private static File findProjectRoot(String marker) throws IOException { } private static void runCommand(String... command) throws IOException, InterruptedException { - var process = new ProcessBuilder(command).redirectErrorStream(true).start(); - try (var reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + var process = new ProcessBuilder(command).start(); + try (var stdoutReader = new BufferedReader(new InputStreamReader(process.getInputStream())); + var stderrReader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) { String line; - while ((line = reader.readLine()) != null) { + while ((line = stdoutReader.readLine()) != null) { log.info("{}: {}", command[0], line); } + while ((line = stderrReader.readLine()) != null) { + log.error("{}: {}", command[0], line); + } } int exitCode = process.waitFor(); if (exitCode != 0) { From 5cbc3f6807b88083bd6a6a8ab5f7294adb4920a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Fri, 17 Apr 2026 14:29:17 +0200 Subject: [PATCH 09/35] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- observability/install-observability.sh | 2 +- .../operator/sample/metrics/OperationsE2E.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/observability/install-observability.sh b/observability/install-observability.sh index dd57e7b352..00df607ca3 100755 --- a/observability/install-observability.sh +++ b/observability/install-observability.sh @@ -237,7 +237,7 @@ kubectl wait --for=condition=ready pod --all -n cert-manager --timeout=300s 2>/d # Wait for observability pods echo -e "${YELLOW}Checking observability pods...${NC}" -kubectl wait --for=condition=ready pod --all -n observability --timeout=300s +kubectl wait --for=condition=ready pod --all -n observability --timeout=480s echo -e "${GREEN}✓ All pods are ready${NC}" diff --git a/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/metrics/OperationsE2E.java b/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/metrics/OperationsE2E.java index 6d69053dce..23f6ef90c7 100644 --- a/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/metrics/OperationsE2E.java +++ b/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/metrics/OperationsE2E.java @@ -96,7 +96,7 @@ void setup() { @AfterAll void cleanup() throws IOException { if (!isLocal()) { - helmUninstall(); +// helmUninstall(); } closePortForward(prometheusPortForward); closePortForward(otelCollectorPortForward); @@ -125,7 +125,7 @@ private void helmInstall() { namespace, "--wait", "--timeout", - "2m"); + "5m"); log.info("Helm release '{}' installed successfully", HELM_RELEASE_NAME); } catch (Exception e) { throw new RuntimeException("Failed to install helm chart", e); From ec4572976d326d91f869475c60fc7bf7c8cf4c22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Fri, 17 Apr 2026 14:51:11 +0200 Subject: [PATCH 10/35] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../javaoperatorsdk/operator/sample/metrics/OperationsE2E.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/metrics/OperationsE2E.java b/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/metrics/OperationsE2E.java index 23f6ef90c7..986ec09496 100644 --- a/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/metrics/OperationsE2E.java +++ b/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/metrics/OperationsE2E.java @@ -96,7 +96,7 @@ void setup() { @AfterAll void cleanup() throws IOException { if (!isLocal()) { -// helmUninstall(); + // helmUninstall(); } closePortForward(prometheusPortForward); closePortForward(otelCollectorPortForward); From 96bc016598efec8fc57030804a669ba7f897b3c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Fri, 17 Apr 2026 15:03:21 +0200 Subject: [PATCH 11/35] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../AbstractOperationsReconciler.java} | 17 ++++---- .../HealthHandler.java | 2 +- .../OperationsReconciler1.java} | 23 +++++------ .../OperationsReconciler2.java} | 11 +++-- .../OperationsSampleOperator.java} | 16 ++++---- .../OperationsCustomResource1.java} | 8 ++-- .../OperationsCustomResource2.java} | 8 ++-- .../customresource/OperationsSpec.java} | 4 +- .../customresource/OperationsStatus.java} | 4 +- .../OperationsE2E.java | 40 +++++++++---------- 10 files changed, 65 insertions(+), 68 deletions(-) rename sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/{metrics/AbstractMetricsHandlingReconciler.java => operations/AbstractOperationsReconciler.java} (77%) rename sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/{metrics => operations}/HealthHandler.java (97%) rename sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/{metrics/MetricsHandlingReconciler1.java => operations/OperationsReconciler1.java} (64%) rename sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/{metrics/MetricsHandlingReconciler2.java => operations/OperationsReconciler2.java} (66%) rename sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/{metrics/MetricsHandlingSampleOperator.java => operations/OperationsSampleOperator.java} (92%) rename sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/{metrics/customresource/MetricsHandlingCustomResource1.java => operations/customresource/OperationsCustomResource1.java} (75%) rename sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/{metrics/customresource/MetricsHandlingCustomResource2.java => operations/customresource/OperationsCustomResource2.java} (75%) rename sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/{metrics/customresource/MetricsHandlingSpec.java => operations/customresource/OperationsSpec.java} (88%) rename sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/{metrics/customresource/MetricsHandlingStatus.java => operations/customresource/OperationsStatus.java} (88%) rename sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/{metrics => operations}/OperationsE2E.java (89%) diff --git a/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/AbstractMetricsHandlingReconciler.java b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/operations/AbstractOperationsReconciler.java similarity index 77% rename from sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/AbstractMetricsHandlingReconciler.java rename to sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/operations/AbstractOperationsReconciler.java index df83afdd6b..5f380da3fd 100644 --- a/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/AbstractMetricsHandlingReconciler.java +++ b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/operations/AbstractOperationsReconciler.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.javaoperatorsdk.operator.sample.metrics; +package io.javaoperatorsdk.operator.sample.operations; import java.util.concurrent.ThreadLocalRandom; @@ -24,19 +24,18 @@ import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.Reconciler; import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; -import io.javaoperatorsdk.operator.sample.metrics.customresource.MetricsHandlingSpec; -import io.javaoperatorsdk.operator.sample.metrics.customresource.MetricsHandlingStatus; +import io.javaoperatorsdk.operator.sample.operations.customresource.OperationsSpec; +import io.javaoperatorsdk.operator.sample.operations.customresource.OperationsStatus; -public abstract class AbstractMetricsHandlingReconciler< - R extends CustomResource> +public abstract class AbstractOperationsReconciler< + R extends CustomResource> implements Reconciler { - private static final Logger log = - LoggerFactory.getLogger(AbstractMetricsHandlingReconciler.class); + private static final Logger log = LoggerFactory.getLogger(AbstractOperationsReconciler.class); private final long sleepMillis; - protected AbstractMetricsHandlingReconciler(long sleepMillis) { + protected AbstractOperationsReconciler(long sleepMillis) { this.sleepMillis = sleepMillis; } @@ -59,7 +58,7 @@ public UpdateControl reconcile(R resource, Context context) { var status = resource.getStatus(); if (status == null) { - status = new MetricsHandlingStatus(); + status = new OperationsStatus(); resource.setStatus(status); } diff --git a/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/HealthHandler.java b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/operations/HealthHandler.java similarity index 97% rename from sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/HealthHandler.java rename to sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/operations/HealthHandler.java index 10dfae4cdf..156930b7f3 100644 --- a/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/HealthHandler.java +++ b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/operations/HealthHandler.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.javaoperatorsdk.operator.sample.metrics; +package io.javaoperatorsdk.operator.sample.operations; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; diff --git a/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingReconciler1.java b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/operations/OperationsReconciler1.java similarity index 64% rename from sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingReconciler1.java rename to sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/operations/OperationsReconciler1.java index 3234deedaf..1720169c38 100644 --- a/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingReconciler1.java +++ b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/operations/OperationsReconciler1.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.javaoperatorsdk.operator.sample.metrics; +package io.javaoperatorsdk.operator.sample.operations; import java.util.List; @@ -24,35 +24,34 @@ 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; +import io.javaoperatorsdk.operator.sample.operations.customresource.OperationsCustomResource1; -import static io.javaoperatorsdk.operator.sample.metrics.MetricsHandlingReconciler1.NAME; +import static io.javaoperatorsdk.operator.sample.operations.OperationsReconciler1.NAME; @ControllerConfiguration(name = NAME) -public class MetricsHandlingReconciler1 - extends AbstractMetricsHandlingReconciler { +public class OperationsReconciler1 extends AbstractOperationsReconciler { - public static final String NAME = "MetricsHandlingReconciler1"; + public static final String NAME = "OperationsReconciler1"; private static final long TIMER_DELAY = 5000; - private final TimerEventSource timerEventSource; + private final TimerEventSource timerEventSource; - public MetricsHandlingReconciler1() { + public OperationsReconciler1() { super(100); timerEventSource = new TimerEventSource<>(); } @SuppressWarnings("unchecked") @Override - public List> prepareEventSources( - EventSourceContext context) { + public List> prepareEventSources( + EventSourceContext context) { return List.of((EventSource) timerEventSource); } @Override - public UpdateControl reconcile( - MetricsHandlingCustomResource1 resource, Context context) { + public UpdateControl reconcile( + OperationsCustomResource1 resource, Context context) { var result = super.reconcile(resource, context); timerEventSource.scheduleOnce(ResourceID.fromResource(resource), TIMER_DELAY); return result; diff --git a/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingReconciler2.java b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/operations/OperationsReconciler2.java similarity index 66% rename from sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingReconciler2.java rename to sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/operations/OperationsReconciler2.java index 0484d2848e..9565296bd0 100644 --- a/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingReconciler2.java +++ b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/operations/OperationsReconciler2.java @@ -13,18 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.javaoperatorsdk.operator.sample.metrics; +package io.javaoperatorsdk.operator.sample.operations; import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; -import io.javaoperatorsdk.operator.sample.metrics.customresource.MetricsHandlingCustomResource2; +import io.javaoperatorsdk.operator.sample.operations.customresource.OperationsCustomResource2; @ControllerConfiguration -public class MetricsHandlingReconciler2 - extends AbstractMetricsHandlingReconciler { +public class OperationsReconciler2 extends AbstractOperationsReconciler { - public static final String NAME = "MetricsHandlingReconciler2"; + public static final String NAME = "OperationsReconciler2"; - public MetricsHandlingReconciler2() { + public OperationsReconciler2() { super(150); } } diff --git a/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingSampleOperator.java b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/operations/OperationsSampleOperator.java similarity index 92% rename from sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingSampleOperator.java rename to sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/operations/OperationsSampleOperator.java index 348b5518b2..0e5617d6de 100644 --- a/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingSampleOperator.java +++ b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/operations/OperationsSampleOperator.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.javaoperatorsdk.operator.sample.metrics; +package io.javaoperatorsdk.operator.sample.operations; import java.io.IOException; import java.io.InputStream; @@ -53,9 +53,9 @@ import io.micrometer.registry.otlp.OtlpConfig; import io.micrometer.registry.otlp.OtlpMeterRegistry; -public class MetricsHandlingSampleOperator { +public class OperationsSampleOperator { - private static final Logger log = LoggerFactory.getLogger(MetricsHandlingSampleOperator.class); + private static final Logger log = LoggerFactory.getLogger(OperationsSampleOperator.class); /** * Based on env variables a different flavor of Reconciler is used, showcasing how the same logic @@ -73,11 +73,11 @@ public static void main(String[] args) throws Exception { Operator operator = new Operator(o -> configLoader.applyConfigs().andThen(k -> k.withMetrics(metrics))); operator.register( - new MetricsHandlingReconciler1(), - configLoader.applyControllerConfigs(MetricsHandlingReconciler1.NAME)); + new OperationsReconciler1(), + configLoader.applyControllerConfigs(OperationsReconciler1.NAME)); operator.register( - new MetricsHandlingReconciler2(), - configLoader.applyControllerConfigs(MetricsHandlingReconciler2.NAME)); + new OperationsReconciler2(), + configLoader.applyControllerConfigs(OperationsReconciler2.NAME)); var health = new ContextHandler(new HealthHandler(operator), "/healthz"); Server server = new Server(8080); server.setHandler(health); @@ -147,7 +147,7 @@ public Duration step() { private static Map loadConfigFromYaml() { Map configMap = new HashMap<>(); try (InputStream inputStream = - MetricsHandlingSampleOperator.class.getResourceAsStream("/otlp-config.yaml")) { + OperationsSampleOperator.class.getResourceAsStream("/otlp-config.yaml")) { if (inputStream == null) { log.warn("otlp-config.yaml not found in resources, using default OTLP configuration"); return configMap; diff --git a/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/customresource/MetricsHandlingCustomResource1.java b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/operations/customresource/OperationsCustomResource1.java similarity index 75% rename from sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/customresource/MetricsHandlingCustomResource1.java rename to sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/operations/customresource/OperationsCustomResource1.java index 892f663175..ba94f0c4cd 100644 --- a/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/customresource/MetricsHandlingCustomResource1.java +++ b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/operations/customresource/OperationsCustomResource1.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.javaoperatorsdk.operator.sample.metrics.customresource; +package io.javaoperatorsdk.operator.sample.operations.customresource; import io.fabric8.kubernetes.api.model.Namespaced; import io.fabric8.kubernetes.client.CustomResource; @@ -22,11 +22,11 @@ @Group("sample.javaoperatorsdk") @Version("v1") -public class MetricsHandlingCustomResource1 - extends CustomResource implements Namespaced { +public class OperationsCustomResource1 extends CustomResource + implements Namespaced { @Override public String toString() { - return "MetricsHandlingCustomResource1{" + "spec=" + spec + ", status=" + status + '}'; + return "OperationsCustomResource1{" + "spec=" + spec + ", status=" + status + '}'; } } diff --git a/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/customresource/MetricsHandlingCustomResource2.java b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/operations/customresource/OperationsCustomResource2.java similarity index 75% rename from sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/customresource/MetricsHandlingCustomResource2.java rename to sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/operations/customresource/OperationsCustomResource2.java index 38abf2a322..9a3f2815d6 100644 --- a/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/customresource/MetricsHandlingCustomResource2.java +++ b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/operations/customresource/OperationsCustomResource2.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.javaoperatorsdk.operator.sample.metrics.customresource; +package io.javaoperatorsdk.operator.sample.operations.customresource; import io.fabric8.kubernetes.api.model.Namespaced; import io.fabric8.kubernetes.client.CustomResource; @@ -22,11 +22,11 @@ @Group("sample.javaoperatorsdk") @Version("v1") -public class MetricsHandlingCustomResource2 - extends CustomResource implements Namespaced { +public class OperationsCustomResource2 extends CustomResource + implements Namespaced { @Override public String toString() { - return "MetricsHandlingCustomResource2{" + "spec=" + spec + ", status=" + status + '}'; + return "OperationsCustomResource2{" + "spec=" + spec + ", status=" + status + '}'; } } diff --git a/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/customresource/MetricsHandlingSpec.java b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/operations/customresource/OperationsSpec.java similarity index 88% rename from sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/customresource/MetricsHandlingSpec.java rename to sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/operations/customresource/OperationsSpec.java index 50016f03e0..cc3cda1edd 100644 --- a/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/customresource/MetricsHandlingSpec.java +++ b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/operations/customresource/OperationsSpec.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.javaoperatorsdk.operator.sample.metrics.customresource; +package io.javaoperatorsdk.operator.sample.operations.customresource; -public class MetricsHandlingSpec { +public class OperationsSpec { private int number; diff --git a/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/customresource/MetricsHandlingStatus.java b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/operations/customresource/OperationsStatus.java similarity index 88% rename from sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/customresource/MetricsHandlingStatus.java rename to sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/operations/customresource/OperationsStatus.java index 76c286cf80..dd7df45bdc 100644 --- a/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/metrics/customresource/MetricsHandlingStatus.java +++ b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/operations/customresource/OperationsStatus.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.javaoperatorsdk.operator.sample.metrics.customresource; +package io.javaoperatorsdk.operator.sample.operations.customresource; -public class MetricsHandlingStatus { +public class OperationsStatus { private int observedNumber; diff --git a/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/metrics/OperationsE2E.java b/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/operations/OperationsE2E.java similarity index 89% rename from sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/metrics/OperationsE2E.java rename to sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/operations/OperationsE2E.java index 986ec09496..8982cd6ed4 100644 --- a/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/metrics/OperationsE2E.java +++ b/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/operations/OperationsE2E.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.javaoperatorsdk.operator.sample.metrics; +package io.javaoperatorsdk.operator.sample.operations; import java.io.*; import java.net.HttpURLConnection; @@ -42,11 +42,11 @@ import io.javaoperatorsdk.operator.junit.AbstractOperatorExtension; import io.javaoperatorsdk.operator.junit.ClusterDeployedOperatorExtension; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; -import io.javaoperatorsdk.operator.sample.metrics.customresource.MetricsHandlingCustomResource1; -import io.javaoperatorsdk.operator.sample.metrics.customresource.MetricsHandlingCustomResource2; -import io.javaoperatorsdk.operator.sample.metrics.customresource.MetricsHandlingSpec; +import io.javaoperatorsdk.operator.sample.operations.customresource.OperationsCustomResource1; +import io.javaoperatorsdk.operator.sample.operations.customresource.OperationsCustomResource2; +import io.javaoperatorsdk.operator.sample.operations.customresource.OperationsSpec; -import static io.javaoperatorsdk.operator.sample.metrics.MetricsHandlingSampleOperator.isLocal; +import static io.javaoperatorsdk.operator.sample.operations.OperationsSampleOperator.isLocal; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; @@ -72,12 +72,12 @@ class OperationsE2E { AbstractOperatorExtension operator = isLocal() ? LocallyRunOperatorExtension.builder() - .withReconciler(new MetricsHandlingReconciler1()) - .withReconciler(new MetricsHandlingReconciler2()) + .withReconciler(new OperationsReconciler1()) + .withReconciler(new OperationsReconciler2()) .withConfigurationService( - c -> c.withMetrics(MetricsHandlingSampleOperator.initOTLPMetrics(true))) + c -> c.withMetrics(OperationsSampleOperator.initOTLPMetrics(true))) .build() - : ClusterDeployedOperatorExtension.builder().build(); + : ClusterDeployedOperatorExtension.builder().oneNamespacePerClass(true).build(); @BeforeAll void setup() { @@ -210,10 +210,10 @@ void testPropagatedMetrics() throws Exception { "Starting longevity metrics test (running for {} seconds)", TEST_DURATION.getSeconds()); // Create initial resources including ones that trigger failures - operator.create(createResource(MetricsHandlingCustomResource1.class, "test-success-1", 1)); - operator.create(createResource(MetricsHandlingCustomResource2.class, "test-success-2", 1)); - operator.create(createResource(MetricsHandlingCustomResource1.class, "test-fail-1", 1)); - operator.create(createResource(MetricsHandlingCustomResource2.class, "test-fail-2", 1)); + operator.create(createResource(OperationsCustomResource1.class, "test-success-1", 1)); + operator.create(createResource(OperationsCustomResource2.class, "test-success-2", 1)); + operator.create(createResource(OperationsCustomResource1.class, "test-fail-1", 1)); + operator.create(createResource(OperationsCustomResource2.class, "test-fail-2", 1)); // Continuously trigger reconciliations for ~50 seconds by alternating between // creating new resources, updating specs of existing ones, and deleting older dynamic ones @@ -226,19 +226,19 @@ void testPropagatedMetrics() throws Exception { switch (counter % 4) { case 0 -> { String name = "test-dynamic-1-" + counter; - operator.create(createResource(MetricsHandlingCustomResource1.class, name, counter * 3)); + operator.create(createResource(OperationsCustomResource1.class, name, counter * 3)); createdResource1Names.addLast(name); log.info("Iteration {}: created {}", counter, name); } case 1 -> { - var r1 = operator.get(MetricsHandlingCustomResource1.class, "test-success-1"); + var r1 = operator.get(OperationsCustomResource1.class, "test-success-1"); r1.getSpec().setNumber(counter * 7); operator.replace(r1); log.info("Iteration {}: updated test-success-1 number to {}", counter, counter * 7); } case 2 -> { String name = "test-dynamic-2-" + counter; - operator.create(createResource(MetricsHandlingCustomResource2.class, name, counter * 5)); + operator.create(createResource(OperationsCustomResource2.class, name, counter * 5)); createdResource2Names.addLast(name); log.info("Iteration {}: created {}", counter, name); } @@ -248,14 +248,14 @@ void testPropagatedMetrics() throws Exception { && (createdResource2Names.isEmpty() || createdResource1Names.size() >= createdResource2Names.size())) { String name = createdResource1Names.pollFirst(); - var r = operator.get(MetricsHandlingCustomResource1.class, name); + var r = operator.get(OperationsCustomResource1.class, name); if (r != null) { operator.delete(r); log.info("Iteration {}: deleted {} ", counter, name); } } else if (!createdResource2Names.isEmpty()) { String name = createdResource2Names.pollFirst(); - var r = operator.get(MetricsHandlingCustomResource2.class, name); + var r = operator.get(OperationsCustomResource2.class, name); if (r != null) { operator.delete(r); log.info("Iteration {}: deleted {}", counter, name); @@ -346,12 +346,12 @@ private String queryPrometheus(String prometheusUrl, String query) throws IOExce } } - private > R createResource( + private > R createResource( Class type, String name, int number) { try { R resource = type.getDeclaredConstructor().newInstance(); resource.getMetadata().setName(name); - MetricsHandlingSpec spec = new MetricsHandlingSpec(); + OperationsSpec spec = new OperationsSpec(); spec.setNumber(number); resource.setSpec(spec); return resource; From cf6c880baedea4a84fbba19a1c615d7b5f652463 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Fri, 17 Apr 2026 15:59:01 +0200 Subject: [PATCH 12/35] 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/operations/OperationsE2E.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/operations/OperationsE2E.java b/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/operations/OperationsE2E.java index 8982cd6ed4..44d16a47ca 100644 --- a/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/operations/OperationsE2E.java +++ b/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/operations/OperationsE2E.java @@ -96,7 +96,7 @@ void setup() { @AfterAll void cleanup() throws IOException { if (!isLocal()) { - // helmUninstall(); + helmUninstall(); } closePortForward(prometheusPortForward); closePortForward(otelCollectorPortForward); @@ -368,7 +368,7 @@ private void installObservabilityServices() { .resolve("observability/install-observability.sh") .toFile(); log.info("Running observability setup script: {}", scriptFile.getAbsolutePath()); - runCommand("/bin/sh", scriptFile.getAbsolutePath()); + runCommand("/bin/bash", scriptFile.getAbsolutePath()); log.info("Observability stack is ready"); } catch (Exception e) { log.error("Failed to setup observability stack", e); From 327b1d0d5d6e6b15ebf08cc160a40d2aca823e11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Fri, 24 Apr 2026 13:37:51 +0200 Subject: [PATCH 13/35] 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/operations/OperationsE2E.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/operations/OperationsE2E.java b/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/operations/OperationsE2E.java index 44d16a47ca..b02f4edc93 100644 --- a/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/operations/OperationsE2E.java +++ b/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/operations/OperationsE2E.java @@ -77,7 +77,7 @@ class OperationsE2E { .withConfigurationService( c -> c.withMetrics(OperationsSampleOperator.initOTLPMetrics(true))) .build() - : ClusterDeployedOperatorExtension.builder().oneNamespacePerClass(true).build(); + : ClusterDeployedOperatorExtension.builder().build(); @BeforeAll void setup() { From 9a11f07cea821107731e444d6c7a3b6906f4e042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Fri, 24 Apr 2026 14:10:48 +0200 Subject: [PATCH 14/35] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../operator/config/loader/provider/YamlConfigProvider.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/loader/provider/YamlConfigProvider.java b/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/loader/provider/YamlConfigProvider.java index 6c9e64b8c5..fc80e46a43 100644 --- a/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/loader/provider/YamlConfigProvider.java +++ b/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/loader/provider/YamlConfigProvider.java @@ -111,9 +111,6 @@ private static Map load(Path path) { } Map result = MAPPER.readValue(parser, Map.class); return result != null ? result : Map.of(); - } catch (com.fasterxml.jackson.databind.exc.MismatchedInputException e) { - log.warn("{} contains no configuration data", path); - return Map.of(); } catch (IOException e) { throw new UncheckedIOException("Failed to load config YAML from " + path, e); } From c1bfc77c5457ea5ca93921a1ceba0ba76fb7cab9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Mon, 27 Apr 2026 15:07:20 +0200 Subject: [PATCH 15/35] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../junit/AbstractOperatorExtension.java | 116 ++++++++++++++++++ .../ClusterDeployedOperatorExtension.java | 113 +---------------- .../sample/operations/OperationsE2E.java | 32 ++--- 3 files changed, 133 insertions(+), 128 deletions(-) diff --git a/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/AbstractOperatorExtension.java b/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/AbstractOperatorExtension.java index 0609850713..f6af705899 100644 --- a/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/AbstractOperatorExtension.java +++ b/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/AbstractOperatorExtension.java @@ -23,6 +23,7 @@ import java.util.function.Consumer; import java.util.function.Function; +import io.fabric8.kubernetes.api.model.apps.Deployment; import org.awaitility.Awaitility; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.AfterEachCallback; @@ -323,4 +324,119 @@ public AbstractBuilder withPerClassNamespaceNameSupplier( return this; } } + + public void logDiagnosticInfo(String namespace) { + try { + // Log deployment status + var deployments = + getKubernetesClient().apps().deployments().inNamespace(namespace).list().getItems(); + for (Deployment deployment : deployments) { + var status = deployment.getStatus(); + LOGGER.error( + "Deployment '{}': replicas={}, readyReplicas={}, availableReplicas={}," + + " unavailableReplicas={}, conditions={}", + deployment.getMetadata().getName(), + status != null ? status.getReplicas() : "null", + status != null ? status.getReadyReplicas() : "null", + status != null ? status.getAvailableReplicas() : "null", + status != null ? status.getUnavailableReplicas() : "null", + status != null ? status.getConditions() : "null"); + } + + // Log pod status and container details + var pods = kubernetesClient.pods().inNamespace(namespace).list().getItems(); + for (Pod pod : pods) { + var podStatus = pod.getStatus(); + LOGGER.error( + "Pod '{}': phase={}, reason={}, message={}", + pod.getMetadata().getName(), + podStatus != null ? podStatus.getPhase() : "null", + podStatus != null ? podStatus.getReason() : "null", + podStatus != null ? podStatus.getMessage() : "null"); + + if (podStatus != null && podStatus.getContainerStatuses() != null) { + for (ContainerStatus cs : podStatus.getContainerStatuses()) { + LOGGER.error( + " Container '{}': ready={}, restartCount={}, state={}", + cs.getName(), + cs.getReady(), + cs.getRestartCount(), + cs.getState()); + } + } + if (podStatus != null && podStatus.getInitContainerStatuses() != null) { + for (ContainerStatus cs : podStatus.getInitContainerStatuses()) { + LOGGER.error( + " InitContainer '{}': ready={}, restartCount={}, state={}", + cs.getName(), + cs.getReady(), + cs.getRestartCount(), + cs.getState()); + } + } + + // Log pod events + var events = + kubernetesClient + .v1() + .events() + .inNamespace(namespace) + .withField("involvedObject.name", pod.getMetadata().getName()) + .list() + .getItems(); + for (var event : events) { + LOGGER.error( + " Event: type={}, reason={}, message={}", + event.getType(), + event.getReason(), + event.getMessage()); + } + + // Try to get container logs + try { + String logs = + kubernetesClient + .pods() + .inNamespace(namespace) + .withName(pod.getMetadata().getName()) + .tailingLines(50) + .getLog(); + if (logs != null && !logs.isEmpty()) { + LOGGER.error(" Logs for pod '{}':\n{}", pod.getMetadata().getName(), logs); + } + } catch (Exception logEx) { + LOGGER.error( + " Could not retrieve logs for pod '{}'", pod.getMetadata().getName(), logEx); + } + } + + if (pods.isEmpty()) { + LOGGER.error( + "No pods found in namespace '{}'. The deployment may have failed to" + + " create pods. Check if the image exists and is pullable.", + namespace); + + // Log deployment events when no pods exist + for (Deployment deployment : deployments) { + var events = + kubernetesClient + .v1() + .events() + .inNamespace(namespace) + .withField("involvedObject.name", deployment.getMetadata().getName()) + .list() + .getItems(); + for (var event : events) { + LOGGER.error( + " Deployment event: type={}, reason={}, message={}", + event.getType(), + event.getReason(), + event.getMessage()); + } + } + } + } catch (Exception diagEx) { + LOGGER.error("Failed to collect diagnostic info: {}", diagEx.getMessage(), diagEx); + } + } } diff --git a/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java b/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java index 8e4e3d64d6..03fa29cbc4 100644 --- a/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java +++ b/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java @@ -173,118 +173,7 @@ private void logDiagnosticInfo(KubernetesClient kubernetesClient) { "Operator deployment timed out after {} seconds in namespace: {}", operatorDeploymentTimeout.getSeconds(), namespace); - try { - // Log deployment status - var deployments = - kubernetesClient.apps().deployments().inNamespace(namespace).list().getItems(); - for (Deployment deployment : deployments) { - var status = deployment.getStatus(); - LOGGER.error( - "Deployment '{}': replicas={}, readyReplicas={}, availableReplicas={}," - + " unavailableReplicas={}, conditions={}", - deployment.getMetadata().getName(), - status != null ? status.getReplicas() : "null", - status != null ? status.getReadyReplicas() : "null", - status != null ? status.getAvailableReplicas() : "null", - status != null ? status.getUnavailableReplicas() : "null", - status != null ? status.getConditions() : "null"); - } - - // Log pod status and container details - var pods = kubernetesClient.pods().inNamespace(namespace).list().getItems(); - for (Pod pod : pods) { - var podStatus = pod.getStatus(); - LOGGER.error( - "Pod '{}': phase={}, reason={}, message={}", - pod.getMetadata().getName(), - podStatus != null ? podStatus.getPhase() : "null", - podStatus != null ? podStatus.getReason() : "null", - podStatus != null ? podStatus.getMessage() : "null"); - - if (podStatus != null && podStatus.getContainerStatuses() != null) { - for (ContainerStatus cs : podStatus.getContainerStatuses()) { - LOGGER.error( - " Container '{}': ready={}, restartCount={}, state={}", - cs.getName(), - cs.getReady(), - cs.getRestartCount(), - cs.getState()); - } - } - if (podStatus != null && podStatus.getInitContainerStatuses() != null) { - for (ContainerStatus cs : podStatus.getInitContainerStatuses()) { - LOGGER.error( - " InitContainer '{}': ready={}, restartCount={}, state={}", - cs.getName(), - cs.getReady(), - cs.getRestartCount(), - cs.getState()); - } - } - - // Log pod events - var events = - kubernetesClient - .v1() - .events() - .inNamespace(namespace) - .withField("involvedObject.name", pod.getMetadata().getName()) - .list() - .getItems(); - for (var event : events) { - LOGGER.error( - " Event: type={}, reason={}, message={}", - event.getType(), - event.getReason(), - event.getMessage()); - } - - // Try to get container logs - try { - String logs = - kubernetesClient - .pods() - .inNamespace(namespace) - .withName(pod.getMetadata().getName()) - .tailingLines(50) - .getLog(); - if (logs != null && !logs.isEmpty()) { - LOGGER.error(" Logs for pod '{}':\n{}", pod.getMetadata().getName(), logs); - } - } catch (Exception logEx) { - LOGGER.error( - " Could not retrieve logs for pod '{}'", pod.getMetadata().getName(), logEx); - } - } - - if (pods.isEmpty()) { - LOGGER.error( - "No pods found in namespace '{}'. The deployment may have failed to" - + " create pods. Check if the image exists and is pullable.", - namespace); - - // Log deployment events when no pods exist - for (Deployment deployment : deployments) { - var events = - kubernetesClient - .v1() - .events() - .inNamespace(namespace) - .withField("involvedObject.name", deployment.getMetadata().getName()) - .list() - .getItems(); - for (var event : events) { - LOGGER.error( - " Deployment event: type={}, reason={}, message={}", - event.getType(), - event.getReason(), - event.getMessage()); - } - } - } - } catch (Exception diagEx) { - LOGGER.error("Failed to collect diagnostic info: {}", diagEx.getMessage(), diagEx); - } + logDiagnosticInfo(namespace); } public static class Builder extends AbstractBuilder { diff --git a/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/operations/OperationsE2E.java b/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/operations/OperationsE2E.java index b02f4edc93..e30032eb8d 100644 --- a/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/operations/OperationsE2E.java +++ b/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/operations/OperationsE2E.java @@ -69,7 +69,7 @@ class OperationsE2E { OperationsE2E() {} @RegisterExtension - AbstractOperatorExtension operator = + AbstractOperatorExtension extension = isLocal() ? LocallyRunOperatorExtension.builder() .withReconciler(new OperationsReconciler1()) @@ -103,6 +103,7 @@ void cleanup() throws IOException { } private void helmInstall() { + var namespace = getNamespace(); try { var chartPath = findProjectRoot("helm").toPath().resolve("helm/generic-helm-chart").toString(); @@ -111,8 +112,6 @@ private void helmInstall() { throw new IllegalStateException("helm-values.yaml not found on classpath"); } var valuesPath = new File(valuesUrl.toURI()).getAbsolutePath(); - var namespace = getNamespace(); - log.info("Installing helm release '{}' into namespace '{}'", HELM_RELEASE_NAME, namespace); runCommand( "helm", @@ -128,12 +127,13 @@ private void helmInstall() { "5m"); log.info("Helm release '{}' installed successfully", HELM_RELEASE_NAME); } catch (Exception e) { + extension.logDiagnosticInfo(namespace); throw new RuntimeException("Failed to install helm chart", e); } } private String getNamespace() { - var ns = operator.getNamespace(); + var ns = extension.getNamespace(); return ns == null ? "default" : ns; } @@ -210,10 +210,10 @@ void testPropagatedMetrics() throws Exception { "Starting longevity metrics test (running for {} seconds)", TEST_DURATION.getSeconds()); // Create initial resources including ones that trigger failures - operator.create(createResource(OperationsCustomResource1.class, "test-success-1", 1)); - operator.create(createResource(OperationsCustomResource2.class, "test-success-2", 1)); - operator.create(createResource(OperationsCustomResource1.class, "test-fail-1", 1)); - operator.create(createResource(OperationsCustomResource2.class, "test-fail-2", 1)); + extension.create(createResource(OperationsCustomResource1.class, "test-success-1", 1)); + extension.create(createResource(OperationsCustomResource2.class, "test-success-2", 1)); + extension.create(createResource(OperationsCustomResource1.class, "test-fail-1", 1)); + extension.create(createResource(OperationsCustomResource2.class, "test-fail-2", 1)); // Continuously trigger reconciliations for ~50 seconds by alternating between // creating new resources, updating specs of existing ones, and deleting older dynamic ones @@ -226,19 +226,19 @@ void testPropagatedMetrics() throws Exception { switch (counter % 4) { case 0 -> { String name = "test-dynamic-1-" + counter; - operator.create(createResource(OperationsCustomResource1.class, name, counter * 3)); + extension.create(createResource(OperationsCustomResource1.class, name, counter * 3)); createdResource1Names.addLast(name); log.info("Iteration {}: created {}", counter, name); } case 1 -> { - var r1 = operator.get(OperationsCustomResource1.class, "test-success-1"); + var r1 = extension.get(OperationsCustomResource1.class, "test-success-1"); r1.getSpec().setNumber(counter * 7); - operator.replace(r1); + extension.replace(r1); log.info("Iteration {}: updated test-success-1 number to {}", counter, counter * 7); } case 2 -> { String name = "test-dynamic-2-" + counter; - operator.create(createResource(OperationsCustomResource2.class, name, counter * 5)); + extension.create(createResource(OperationsCustomResource2.class, name, counter * 5)); createdResource2Names.addLast(name); log.info("Iteration {}: created {}", counter, name); } @@ -248,16 +248,16 @@ void testPropagatedMetrics() throws Exception { && (createdResource2Names.isEmpty() || createdResource1Names.size() >= createdResource2Names.size())) { String name = createdResource1Names.pollFirst(); - var r = operator.get(OperationsCustomResource1.class, name); + var r = extension.get(OperationsCustomResource1.class, name); if (r != null) { - operator.delete(r); + extension.delete(r); log.info("Iteration {}: deleted {} ", counter, name); } } else if (!createdResource2Names.isEmpty()) { String name = createdResource2Names.pollFirst(); - var r = operator.get(OperationsCustomResource2.class, name); + var r = extension.get(OperationsCustomResource2.class, name); if (r != null) { - operator.delete(r); + extension.delete(r); log.info("Iteration {}: deleted {}", counter, name); } } From 78fa979b53a7318060c00d00916b34877f36c838 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Mon, 27 Apr 2026 15:18:30 +0200 Subject: [PATCH 16/35] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../junit/AbstractOperatorExtension.java | 114 +++++++++--------- .../ClusterDeployedOperatorExtension.java | 3 - 2 files changed, 57 insertions(+), 60 deletions(-) diff --git a/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/AbstractOperatorExtension.java b/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/AbstractOperatorExtension.java index f6af705899..17364ba265 100644 --- a/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/AbstractOperatorExtension.java +++ b/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/AbstractOperatorExtension.java @@ -23,7 +23,6 @@ import java.util.function.Consumer; import java.util.function.Function; -import io.fabric8.kubernetes.api.model.apps.Deployment; import org.awaitility.Awaitility; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.AfterEachCallback; @@ -34,6 +33,7 @@ import org.slf4j.LoggerFactory; import io.fabric8.kubernetes.api.model.*; +import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.KubernetesClientBuilder; import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; @@ -329,18 +329,18 @@ public void logDiagnosticInfo(String namespace) { try { // Log deployment status var deployments = - getKubernetesClient().apps().deployments().inNamespace(namespace).list().getItems(); + getKubernetesClient().apps().deployments().inNamespace(namespace).list().getItems(); for (Deployment deployment : deployments) { var status = deployment.getStatus(); LOGGER.error( - "Deployment '{}': replicas={}, readyReplicas={}, availableReplicas={}," - + " unavailableReplicas={}, conditions={}", - deployment.getMetadata().getName(), - status != null ? status.getReplicas() : "null", - status != null ? status.getReadyReplicas() : "null", - status != null ? status.getAvailableReplicas() : "null", - status != null ? status.getUnavailableReplicas() : "null", - status != null ? status.getConditions() : "null"); + "Deployment '{}': replicas={}, readyReplicas={}, availableReplicas={}," + + " unavailableReplicas={}, conditions={}", + deployment.getMetadata().getName(), + status != null ? status.getReplicas() : "null", + status != null ? status.getReadyReplicas() : "null", + status != null ? status.getAvailableReplicas() : "null", + status != null ? status.getUnavailableReplicas() : "null", + status != null ? status.getConditions() : "null"); } // Log pod status and container details @@ -348,90 +348,90 @@ public void logDiagnosticInfo(String namespace) { for (Pod pod : pods) { var podStatus = pod.getStatus(); LOGGER.error( - "Pod '{}': phase={}, reason={}, message={}", - pod.getMetadata().getName(), - podStatus != null ? podStatus.getPhase() : "null", - podStatus != null ? podStatus.getReason() : "null", - podStatus != null ? podStatus.getMessage() : "null"); + "Pod '{}': phase={}, reason={}, message={}", + pod.getMetadata().getName(), + podStatus != null ? podStatus.getPhase() : "null", + podStatus != null ? podStatus.getReason() : "null", + podStatus != null ? podStatus.getMessage() : "null"); if (podStatus != null && podStatus.getContainerStatuses() != null) { for (ContainerStatus cs : podStatus.getContainerStatuses()) { LOGGER.error( - " Container '{}': ready={}, restartCount={}, state={}", - cs.getName(), - cs.getReady(), - cs.getRestartCount(), - cs.getState()); + " Container '{}': ready={}, restartCount={}, state={}", + cs.getName(), + cs.getReady(), + cs.getRestartCount(), + cs.getState()); } } if (podStatus != null && podStatus.getInitContainerStatuses() != null) { for (ContainerStatus cs : podStatus.getInitContainerStatuses()) { LOGGER.error( - " InitContainer '{}': ready={}, restartCount={}, state={}", - cs.getName(), - cs.getReady(), - cs.getRestartCount(), - cs.getState()); + " InitContainer '{}': ready={}, restartCount={}, state={}", + cs.getName(), + cs.getReady(), + cs.getRestartCount(), + cs.getState()); } } // Log pod events var events = - kubernetesClient - .v1() - .events() - .inNamespace(namespace) - .withField("involvedObject.name", pod.getMetadata().getName()) - .list() - .getItems(); + kubernetesClient + .v1() + .events() + .inNamespace(namespace) + .withField("involvedObject.name", pod.getMetadata().getName()) + .list() + .getItems(); for (var event : events) { LOGGER.error( - " Event: type={}, reason={}, message={}", - event.getType(), - event.getReason(), - event.getMessage()); + " Event: type={}, reason={}, message={}", + event.getType(), + event.getReason(), + event.getMessage()); } // Try to get container logs try { String logs = - kubernetesClient - .pods() - .inNamespace(namespace) - .withName(pod.getMetadata().getName()) - .tailingLines(50) - .getLog(); + kubernetesClient + .pods() + .inNamespace(namespace) + .withName(pod.getMetadata().getName()) + .tailingLines(50) + .getLog(); if (logs != null && !logs.isEmpty()) { LOGGER.error(" Logs for pod '{}':\n{}", pod.getMetadata().getName(), logs); } } catch (Exception logEx) { LOGGER.error( - " Could not retrieve logs for pod '{}'", pod.getMetadata().getName(), logEx); + " Could not retrieve logs for pod '{}'", pod.getMetadata().getName(), logEx); } } if (pods.isEmpty()) { LOGGER.error( - "No pods found in namespace '{}'. The deployment may have failed to" - + " create pods. Check if the image exists and is pullable.", - namespace); + "No pods found in namespace '{}'. The deployment may have failed to" + + " create pods. Check if the image exists and is pullable.", + namespace); // Log deployment events when no pods exist for (Deployment deployment : deployments) { var events = - kubernetesClient - .v1() - .events() - .inNamespace(namespace) - .withField("involvedObject.name", deployment.getMetadata().getName()) - .list() - .getItems(); + kubernetesClient + .v1() + .events() + .inNamespace(namespace) + .withField("involvedObject.name", deployment.getMetadata().getName()) + .list() + .getItems(); for (var event : events) { LOGGER.error( - " Deployment event: type={}, reason={}, message={}", - event.getType(), - event.getReason(), - event.getMessage()); + " Deployment event: type={}, reason={}, message={}", + event.getType(), + event.getReason(), + event.getMessage()); } } } diff --git a/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java b/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java index 03fa29cbc4..cc3143b302 100644 --- a/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java +++ b/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java @@ -31,10 +31,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.fabric8.kubernetes.api.model.ContainerStatus; import io.fabric8.kubernetes.api.model.HasMetadata; -import io.fabric8.kubernetes.api.model.Pod; -import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.rbac.ClusterRoleBinding; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.KubernetesClientBuilder; From 378719c1e04b998caf7ba44d450f1eb4b74492f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Mon, 27 Apr 2026 16:18:06 +0200 Subject: [PATCH 17/35] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- helm/generic-helm-chart/templates/deployment.yaml | 2 ++ helm/generic-helm-chart/values.yaml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/helm/generic-helm-chart/templates/deployment.yaml b/helm/generic-helm-chart/templates/deployment.yaml index 30f28274bc..a47ace6c75 100644 --- a/helm/generic-helm-chart/templates/deployment.yaml +++ b/helm/generic-helm-chart/templates/deployment.yaml @@ -67,6 +67,7 @@ spec: port: probes initialDelaySeconds: {{ .Values.probes.startup.initialDelaySeconds }} periodSeconds: {{ .Values.probes.startup.periodSeconds }} + timeoutSeconds: {{ .Values.probes.startup.timeoutSeconds }} failureThreshold: {{ .Values.probes.startup.failureThreshold }} {{- end }} {{- if .Values.probes.readiness.enabled }} @@ -76,6 +77,7 @@ spec: port: probes initialDelaySeconds: {{ .Values.probes.readiness.initialDelaySeconds }} periodSeconds: {{ .Values.probes.readiness.periodSeconds }} + timeoutSeconds: {{ .Values.probes.readiness.timeoutSeconds }} failureThreshold: {{ .Values.probes.readiness.failureThreshold }} {{- end }} env: diff --git a/helm/generic-helm-chart/values.yaml b/helm/generic-helm-chart/values.yaml index 9552fadd2f..c20f1372f8 100644 --- a/helm/generic-helm-chart/values.yaml +++ b/helm/generic-helm-chart/values.yaml @@ -137,10 +137,12 @@ probes: path: /healthz initialDelaySeconds: 1 periodSeconds: 10 + timeoutSeconds: 5 failureThreshold: 20 readiness: enabled: false path: /healthz initialDelaySeconds: 5 periodSeconds: 5 + timeoutSeconds: 5 failureThreshold: 3 From f01aab2dada3669461a0e434b6dbd00535287293 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Mon, 27 Apr 2026 16:38:21 +0200 Subject: [PATCH 18/35] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- sample-operators/operations/src/main/resources/log4j2.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sample-operators/operations/src/main/resources/log4j2.xml b/sample-operators/operations/src/main/resources/log4j2.xml index 593f120e0b..190336f7d9 100644 --- a/sample-operators/operations/src/main/resources/log4j2.xml +++ b/sample-operators/operations/src/main/resources/log4j2.xml @@ -26,6 +26,9 @@ + + + From a143acef090dfcf99c585f6ad5bbb5d35bb36b41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Mon, 27 Apr 2026 16:53:01 +0200 Subject: [PATCH 19/35] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../operator/junit/ClusterDeployedOperatorExtension.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java b/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java index cc3143b302..2597df5070 100644 --- a/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java +++ b/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java @@ -116,7 +116,7 @@ protected void applyCrds(ExtensionContext context) { Thread.sleep(CRD_READY_WAIT); // readiness is not applicable for CRD, just wait a little var crdList = crd.get(); LOGGER.debug( - "Applied CRD with name: {}", + "Applied CRD with on path: {} name: {}",crdFile.getPath(), (crdList != null && !crdList.isEmpty() && crdList.get(0) != null) ? crdList.get(0).getMetadata().getName() : crdFile.getName()); From e0b608da45df6117e27420e9a3b36f41a93e6d5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Mon, 27 Apr 2026 17:00:11 +0200 Subject: [PATCH 20/35] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../operator/junit/ClusterDeployedOperatorExtension.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java b/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java index 2597df5070..6084365174 100644 --- a/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java +++ b/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java @@ -116,7 +116,8 @@ protected void applyCrds(ExtensionContext context) { Thread.sleep(CRD_READY_WAIT); // readiness is not applicable for CRD, just wait a little var crdList = crd.get(); LOGGER.debug( - "Applied CRD with on path: {} name: {}",crdFile.getPath(), + "Applied CRD with on path: {} name: {}", + crdFile.getPath(), (crdList != null && !crdList.isEmpty() && crdList.get(0) != null) ? crdList.get(0).getMetadata().getName() : crdFile.getName()); From e1d280040a61e1dd41a26c2d01e2ef9a4279b5f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Mon, 27 Apr 2026 17:26:46 +0200 Subject: [PATCH 21/35] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- helm/generic-helm-chart/values.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/helm/generic-helm-chart/values.yaml b/helm/generic-helm-chart/values.yaml index c20f1372f8..5448924877 100644 --- a/helm/generic-helm-chart/values.yaml +++ b/helm/generic-helm-chart/values.yaml @@ -86,6 +86,9 @@ operatorConfig: + + + From 2803f3bbbef16f2556809b4db509cc95976abb64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Mon, 27 Apr 2026 17:43:26 +0200 Subject: [PATCH 22/35] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../operations/src/test/resources/helm-values.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sample-operators/operations/src/test/resources/helm-values.yaml b/sample-operators/operations/src/test/resources/helm-values.yaml index 4b255385c6..4bad9ea65f 100644 --- a/sample-operators/operations/src/test/resources/helm-values.yaml +++ b/sample-operators/operations/src/test/resources/helm-values.yaml @@ -30,8 +30,8 @@ resources: {} primaryResources: - apiGroup: "sample.javaoperatorsdk" resources: - - metricshandlingcustomresource1s - - metricshandlingcustomresource2s + - operationscustomresource1s + - operationscustomresource2s probes: startup: From 63557365a43c85c3637d785df09dbb3cbd8b513f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Tue, 28 Apr 2026 09:15:16 +0200 Subject: [PATCH 23/35] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- docs/content/en/docs/documentation/operations/metrics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/en/docs/documentation/operations/metrics.md b/docs/content/en/docs/documentation/operations/metrics.md index 6ac241ec8e..1bf8e38368 100644 --- a/docs/content/en/docs/documentation/operations/metrics.md +++ b/docs/content/en/docs/documentation/operations/metrics.md @@ -105,7 +105,7 @@ observability sample (see below). The [`operations` sample operator](https://github.com/java-operator-sdk/java-operator-sdk/tree/main/sample-operators/operations) includes a full end-to-end test, -[`MetricsHandlingE2E`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java), +[`OperationsE2E`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/metrics/OperationsE2E.java), that: 1. Installs a local observability stack (Prometheus, Grafana, OpenTelemetry Collector) via From 1bebe64466efb70158ca8145127a58eb2d5aabba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Tue, 28 Apr 2026 09:56:22 +0200 Subject: [PATCH 24/35] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- helm/generic-helm-chart/templates/deployment.yaml | 12 +++++++++++- helm/generic-helm-chart/values.yaml | 11 +++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/helm/generic-helm-chart/templates/deployment.yaml b/helm/generic-helm-chart/templates/deployment.yaml index a47ace6c75..e4ae86a930 100644 --- a/helm/generic-helm-chart/templates/deployment.yaml +++ b/helm/generic-helm-chart/templates/deployment.yaml @@ -54,7 +54,7 @@ spec: {{- toYaml .Values.securityContext | nindent 12 }} image: "{{ required "A valid .Values.image.repository is required" .Values.image.repository }}:{{ include "generic-operator.imageTag" . }}" imagePullPolicy: {{ .Values.image.pullPolicy }} - {{- if or .Values.probes.startup.enabled .Values.probes.readiness.enabled }} + {{- if or .Values.probes.startup.enabled .Values.probes.readiness.enabled .Values.probes.liveness.enabled }} ports: - name: probes containerPort: {{ .Values.probes.port }} @@ -80,6 +80,16 @@ spec: timeoutSeconds: {{ .Values.probes.readiness.timeoutSeconds }} failureThreshold: {{ .Values.probes.readiness.failureThreshold }} {{- end }} + {{- if .Values.probes.liveness.enabled }} + livenessProbe: + httpGet: + path: {{ .Values.probes.liveness.path }} + port: probes + initialDelaySeconds: {{ .Values.probes.liveness.initialDelaySeconds }} + periodSeconds: {{ .Values.probes.liveness.periodSeconds }} + timeoutSeconds: {{ .Values.probes.liveness.timeoutSeconds }} + failureThreshold: {{ .Values.probes.liveness.failureThreshold }} + {{- end }} env: - name: OPERATOR_NAMESPACE valueFrom: diff --git a/helm/generic-helm-chart/values.yaml b/helm/generic-helm-chart/values.yaml index 5448924877..7c23f1b368 100644 --- a/helm/generic-helm-chart/values.yaml +++ b/helm/generic-helm-chart/values.yaml @@ -149,3 +149,14 @@ probes: periodSeconds: 5 timeoutSeconds: 5 failureThreshold: 3 +# We provide an option to sepcify liveness probes. +# However, the framework itself does not define any runtime +# information what such probe should check. The only purpose here +# is to cover your domain specific use case. +# liveness: +# enabled: false +# path: /healthz +# initialDelaySeconds: 15 +# periodSeconds: 10 +# timeoutSeconds: 5 +# failureThreshold: 3 From d27b5637cb9b9d7d4d6bfcd1dd1c515f3c4fd7eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Tue, 28 Apr 2026 10:05:43 +0200 Subject: [PATCH 25/35] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- helm/generic-helm-chart/values.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/helm/generic-helm-chart/values.yaml b/helm/generic-helm-chart/values.yaml index 7c23f1b368..dd757ed7f6 100644 --- a/helm/generic-helm-chart/values.yaml +++ b/helm/generic-helm-chart/values.yaml @@ -153,10 +153,10 @@ probes: # However, the framework itself does not define any runtime # information what such probe should check. The only purpose here # is to cover your domain specific use case. -# liveness: -# enabled: false -# path: /healthz -# initialDelaySeconds: 15 -# periodSeconds: 10 -# timeoutSeconds: 5 -# failureThreshold: 3 + liveness: + enabled: false + path: /healthz + initialDelaySeconds: 15 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 From dde1d97b861018ecce01548e8b82fbca76276b38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Tue, 28 Apr 2026 12:59:29 +0200 Subject: [PATCH 26/35] Update operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Attila Mészáros --- .../ClusterDeployedOperatorExtension.java | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java b/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java index 6084365174..5424e8562f 100644 --- a/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java +++ b/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java @@ -171,9 +171,28 @@ private void logDiagnosticInfo(KubernetesClient kubernetesClient) { "Operator deployment timed out after {} seconds in namespace: {}", operatorDeploymentTimeout.getSeconds(), namespace); - logDiagnosticInfo(namespace); + logDiagnosticInfo(kubernetesClient, namespace); } + private void logDiagnosticInfo(KubernetesClient kubernetesClient, String namespace) { + if (Objects.equals(kubernetesClient, getKubernetesClient())) { + logDiagnosticInfo(namespace); + return; + } + + kubernetesClient.pods().inNamespace(namespace).list().getItems().forEach(pod -> LOGGER.error( + "Pod {} phase={} reason={} message={}", + pod.getMetadata().getName(), + pod.getStatus() != null ? pod.getStatus().getPhase() : null, + pod.getStatus() != null ? pod.getStatus().getReason() : null, + pod.getStatus() != null ? pod.getStatus().getMessage() : null)); + + kubernetesClient.resourceList(operatorDeployment).inNamespace(namespace).forEach(resource -> LOGGER + .error( + "Resource {} {}", + resource.getKind(), + resource.getMetadata() != null ? resource.getMetadata().getName() : null)); + } public static class Builder extends AbstractBuilder { private final List operatorDeployment; private Duration deploymentTimeout; From c3e67a3e322e916ca5371fd30ed3ecc7587764e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Tue, 28 Apr 2026 12:59:48 +0200 Subject: [PATCH 27/35] Update docs/content/en/docs/documentation/operations/helm-chart.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Attila Mészáros --- docs/content/en/docs/documentation/operations/helm-chart.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/en/docs/documentation/operations/helm-chart.md b/docs/content/en/docs/documentation/operations/helm-chart.md index ed76e387aa..a0901f31f7 100644 --- a/docs/content/en/docs/documentation/operations/helm-chart.md +++ b/docs/content/en/docs/documentation/operations/helm-chart.md @@ -11,7 +11,7 @@ patterns so you don't have to write a chart from scratch. The chart is maintaine Contributions are more than welcome. The chart is used in the -[`operations` sample operator E2E test](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/metrics/MetricsHandlingE2E.java) +[`operations` sample operator E2E test](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/sample-operators/operations/src/test/java/io/javaoperatorsdk/operator/sample/operations/OperationsE2E.java) to deploy the operator to a cluster via Helm. ## What the Chart Provides From adb41b8c3dc2e87cd98113892ef6326a0b1abfa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Tue, 28 Apr 2026 12:59:58 +0200 Subject: [PATCH 28/35] Update helm/generic-helm-chart/values.yaml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Attila Mészáros --- helm/generic-helm-chart/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm/generic-helm-chart/values.yaml b/helm/generic-helm-chart/values.yaml index dd757ed7f6..ab35a95ee9 100644 --- a/helm/generic-helm-chart/values.yaml +++ b/helm/generic-helm-chart/values.yaml @@ -149,7 +149,7 @@ probes: periodSeconds: 5 timeoutSeconds: 5 failureThreshold: 3 -# We provide an option to sepcify liveness probes. +# We provide an option to specify liveness probes. # However, the framework itself does not define any runtime # information what such probe should check. The only purpose here # is to cover your domain specific use case. From 623cfe6a10ba03958034aba4e87f17984396a47b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Tue, 28 Apr 2026 13:00:07 +0200 Subject: [PATCH 29/35] Update operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Attila Mészáros --- .../operator/junit/ClusterDeployedOperatorExtension.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java b/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java index 5424e8562f..e3a3f19b1d 100644 --- a/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java +++ b/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java @@ -116,7 +116,7 @@ protected void applyCrds(ExtensionContext context) { Thread.sleep(CRD_READY_WAIT); // readiness is not applicable for CRD, just wait a little var crdList = crd.get(); LOGGER.debug( - "Applied CRD with on path: {} name: {}", + "Applied CRD from path: {} name: {}", crdFile.getPath(), (crdList != null && !crdList.isEmpty() && crdList.get(0) != null) ? crdList.get(0).getMetadata().getName() From c04275c94ef393ae82e5b798f12c488e52889b69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Tue, 28 Apr 2026 13:10:02 +0200 Subject: [PATCH 30/35] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../ClusterDeployedOperatorExtension.java | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java b/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java index e3a3f19b1d..c7c436306a 100644 --- a/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java +++ b/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java @@ -180,19 +180,32 @@ private void logDiagnosticInfo(KubernetesClient kubernetesClient, String namespa return; } - kubernetesClient.pods().inNamespace(namespace).list().getItems().forEach(pod -> LOGGER.error( - "Pod {} phase={} reason={} message={}", - pod.getMetadata().getName(), - pod.getStatus() != null ? pod.getStatus().getPhase() : null, - pod.getStatus() != null ? pod.getStatus().getReason() : null, - pod.getStatus() != null ? pod.getStatus().getMessage() : null)); + kubernetesClient + .pods() + .inNamespace(namespace) + .list() + .getItems() + .forEach( + pod -> + LOGGER.error( + "Pod {} phase={} reason={} message={}", + pod.getMetadata().getName(), + pod.getStatus() != null ? pod.getStatus().getPhase() : null, + pod.getStatus() != null ? pod.getStatus().getReason() : null, + pod.getStatus() != null ? pod.getStatus().getMessage() : null)); - kubernetesClient.resourceList(operatorDeployment).inNamespace(namespace).forEach(resource -> LOGGER - .error( - "Resource {} {}", - resource.getKind(), - resource.getMetadata() != null ? resource.getMetadata().getName() : null)); + kubernetesClient + .resourceList(operatorDeployment) + .inNamespace(namespace) + .get() + .forEach( + resource -> + LOGGER.error( + "Resource {} {}", + resource.getKind(), + resource.getMetadata() != null ? resource.getMetadata().getName() : null)); } + public static class Builder extends AbstractBuilder { private final List operatorDeployment; private Duration deploymentTimeout; From 7e595bcf4c136a394b38bc53fe54ac1e83077f44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Tue, 28 Apr 2026 13:27:08 +0200 Subject: [PATCH 31/35] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../junit/AbstractOperatorExtension.java | 10 ++++-- .../ClusterDeployedOperatorExtension.java | 36 ++++--------------- 2 files changed, 14 insertions(+), 32 deletions(-) diff --git a/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/AbstractOperatorExtension.java b/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/AbstractOperatorExtension.java index 17364ba265..57d867ab00 100644 --- a/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/AbstractOperatorExtension.java +++ b/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/AbstractOperatorExtension.java @@ -326,6 +326,10 @@ public AbstractBuilder withPerClassNamespaceNameSupplier( } public void logDiagnosticInfo(String namespace) { + logDiagnosticInfo(getInfrastructureKubernetesClient(), namespace); + } + + public void logDiagnosticInfo(KubernetesClient client, String namespace) { try { // Log deployment status var deployments = @@ -344,7 +348,7 @@ public void logDiagnosticInfo(String namespace) { } // Log pod status and container details - var pods = kubernetesClient.pods().inNamespace(namespace).list().getItems(); + var pods = client.pods().inNamespace(namespace).list().getItems(); for (Pod pod : pods) { var podStatus = pod.getStatus(); LOGGER.error( @@ -377,7 +381,7 @@ public void logDiagnosticInfo(String namespace) { // Log pod events var events = - kubernetesClient + client .v1() .events() .inNamespace(namespace) @@ -395,7 +399,7 @@ public void logDiagnosticInfo(String namespace) { // Try to get container logs try { String logs = - kubernetesClient + client .pods() .inNamespace(namespace) .withName(pod.getMetadata().getName()) diff --git a/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java b/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java index c7c436306a..36d5aeb0ce 100644 --- a/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java +++ b/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java @@ -152,7 +152,11 @@ protected void before(ExtensionContext context) { .resourceList(operatorDeployment) .waitUntilReady(operatorDeploymentTimeout.toMillis(), TimeUnit.MILLISECONDS); } catch (KubernetesClientTimeoutException e) { - logDiagnosticInfo(kubernetesClient); + LOGGER.error( + "Operator deployment timed out after {} seconds in namespace: {}", + operatorDeploymentTimeout.getSeconds(), + namespace); + logDiagnosticInfo(getInfrastructureKubernetesClient(), namespace); throw e; } LOGGER.debug("Operator resources deployed."); @@ -166,34 +170,8 @@ protected void deleteOperator() { .delete(); } - private void logDiagnosticInfo(KubernetesClient kubernetesClient) { - LOGGER.error( - "Operator deployment timed out after {} seconds in namespace: {}", - operatorDeploymentTimeout.getSeconds(), - namespace); - logDiagnosticInfo(kubernetesClient, namespace); - } - - private void logDiagnosticInfo(KubernetesClient kubernetesClient, String namespace) { - if (Objects.equals(kubernetesClient, getKubernetesClient())) { - logDiagnosticInfo(namespace); - return; - } - - kubernetesClient - .pods() - .inNamespace(namespace) - .list() - .getItems() - .forEach( - pod -> - LOGGER.error( - "Pod {} phase={} reason={} message={}", - pod.getMetadata().getName(), - pod.getStatus() != null ? pod.getStatus().getPhase() : null, - pod.getStatus() != null ? pod.getStatus().getReason() : null, - pod.getStatus() != null ? pod.getStatus().getMessage() : null)); - + @Override + public void logDiagnosticInfo(KubernetesClient kubernetesClient, String namespace) { kubernetesClient .resourceList(operatorDeployment) .inNamespace(namespace) From dcf610b6d8c843b9dd4d31800ddbe884c7ee1a4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Tue, 28 Apr 2026 14:12:53 +0200 Subject: [PATCH 32/35] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../operator/junit/AbstractOperatorExtension.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/AbstractOperatorExtension.java b/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/AbstractOperatorExtension.java index 57d867ab00..b11853c331 100644 --- a/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/AbstractOperatorExtension.java +++ b/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/AbstractOperatorExtension.java @@ -332,8 +332,7 @@ public void logDiagnosticInfo(String namespace) { public void logDiagnosticInfo(KubernetesClient client, String namespace) { try { // Log deployment status - var deployments = - getKubernetesClient().apps().deployments().inNamespace(namespace).list().getItems(); + var deployments = client.apps().deployments().inNamespace(namespace).list().getItems(); for (Deployment deployment : deployments) { var status = deployment.getStatus(); LOGGER.error( @@ -423,7 +422,7 @@ public void logDiagnosticInfo(KubernetesClient client, String namespace) { // Log deployment events when no pods exist for (Deployment deployment : deployments) { var events = - kubernetesClient + client .v1() .events() .inNamespace(namespace) From 9a0a405c3f923d8d753cd8f7f388cafaa164f8bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Wed, 29 Apr 2026 14:06:10 +0200 Subject: [PATCH 33/35] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../junit/ClusterDeployedOperatorExtension.java | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java b/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java index 36d5aeb0ce..4bbfa3258d 100644 --- a/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java +++ b/operator-framework-junit/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java @@ -170,20 +170,6 @@ protected void deleteOperator() { .delete(); } - @Override - public void logDiagnosticInfo(KubernetesClient kubernetesClient, String namespace) { - kubernetesClient - .resourceList(operatorDeployment) - .inNamespace(namespace) - .get() - .forEach( - resource -> - LOGGER.error( - "Resource {} {}", - resource.getKind(), - resource.getMetadata() != null ? resource.getMetadata().getName() : null)); - } - public static class Builder extends AbstractBuilder { private final List operatorDeployment; private Duration deploymentTimeout; From df8bb9e4cc26293dd5fe186f577f7e7015826fa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Wed, 29 Apr 2026 14:11:55 +0200 Subject: [PATCH 34/35] health endpoint naming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- helm/generic-helm-chart/values.yaml | 6 +++--- .../sample/operations/OperationsSampleOperator.java | 2 +- .../operations/src/test/resources/helm-values.yaml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/helm/generic-helm-chart/values.yaml b/helm/generic-helm-chart/values.yaml index ab35a95ee9..b398a6357d 100644 --- a/helm/generic-helm-chart/values.yaml +++ b/helm/generic-helm-chart/values.yaml @@ -137,14 +137,14 @@ probes: port: 8080 startup: enabled: false - path: /healthz + path: /health/startup initialDelaySeconds: 1 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 20 readiness: enabled: false - path: /healthz + path: /health/ready initialDelaySeconds: 5 periodSeconds: 5 timeoutSeconds: 5 @@ -155,7 +155,7 @@ probes: # is to cover your domain specific use case. liveness: enabled: false - path: /healthz + path: /health/live initialDelaySeconds: 15 periodSeconds: 10 timeoutSeconds: 5 diff --git a/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/operations/OperationsSampleOperator.java b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/operations/OperationsSampleOperator.java index 0e5617d6de..ba1cbec10a 100644 --- a/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/operations/OperationsSampleOperator.java +++ b/sample-operators/operations/src/main/java/io/javaoperatorsdk/operator/sample/operations/OperationsSampleOperator.java @@ -78,7 +78,7 @@ public static void main(String[] args) throws Exception { operator.register( new OperationsReconciler2(), configLoader.applyControllerConfigs(OperationsReconciler2.NAME)); - var health = new ContextHandler(new HealthHandler(operator), "/healthz"); + var health = new ContextHandler(new HealthHandler(operator), "/health"); Server server = new Server(8080); server.setHandler(health); server.start(); diff --git a/sample-operators/operations/src/test/resources/helm-values.yaml b/sample-operators/operations/src/test/resources/helm-values.yaml index 4bad9ea65f..ee9ac59823 100644 --- a/sample-operators/operations/src/test/resources/helm-values.yaml +++ b/sample-operators/operations/src/test/resources/helm-values.yaml @@ -36,8 +36,8 @@ primaryResources: probes: startup: enabled: true - path: /healthz + path: /health readiness: enabled: true - path: /healthz + path: /health From 64b690bea0e092cc10d0df2f19875ced3c1954eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Wed, 29 Apr 2026 14:14:43 +0200 Subject: [PATCH 35/35] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- helm/generic-helm-chart/tests/deployment_test.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helm/generic-helm-chart/tests/deployment_test.yaml b/helm/generic-helm-chart/tests/deployment_test.yaml index 972feef7d1..a57f58dd13 100644 --- a/helm/generic-helm-chart/tests/deployment_test.yaml +++ b/helm/generic-helm-chart/tests/deployment_test.yaml @@ -305,7 +305,7 @@ tests: asserts: - equal: path: spec.template.spec.containers[0].startupProbe.httpGet.path - value: /healthz + value: /health/startup - equal: path: spec.template.spec.containers[0].startupProbe.httpGet.port value: probes @@ -325,7 +325,7 @@ tests: asserts: - equal: path: spec.template.spec.containers[0].readinessProbe.httpGet.path - value: /healthz + value: /health/ready - equal: path: spec.template.spec.containers[0].readinessProbe.httpGet.port value: probes