gnodet commented on code in PR #26191:
URL: https://github.com/apache/camel/pull/26191#discussion_r3956782736


##########
components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaProducerHealthCheck.java:
##########
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.camel.component.opa;
+
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.time.Duration;
+import java.util.Map;
+
+import org.apache.camel.health.HealthCheckResultBuilder;
+import org.apache.camel.impl.health.AbstractHealthCheck;
+import org.apache.camel.util.ObjectHelper;
+
+/**
+ * Readiness check for the OPA server a producer sends its decisions to.
+ * <p/>
+ * The component fails closed, so an OPA server that cannot be reached fails 
every exchange through the route. This
+ * check probes the server's {@code /health} endpoint so that an unavailable 
policy decision point is visible before
+ * traffic starts failing, rather than only in the error logs afterwards.
+ */
+public class OpaProducerHealthCheck extends AbstractHealthCheck {
+
+    private static final Duration TIMEOUT = Duration.ofSeconds(5);
+
+    private final String serverUrl;
+    private final String bearerToken;
+    private final String policyPath;
+    private final HttpClient httpClient;
+
+    public OpaProducerHealthCheck(String serverUrl, String bearerToken, String 
policyPath, String id) {
+        super("camel", "producer:opa-" + id);
+        this.serverUrl = serverUrl;
+        this.bearerToken = bearerToken;
+        this.policyPath = policyPath;
+        this.httpClient = 
HttpClient.newBuilder().connectTimeout(TIMEOUT).build();

Review Comment:
   ⚠️ **Resource leak — `HttpClient` is never closed.**
   
   Camel targets JDK 17. `java.net.http.HttpClient` did **not** implement 
`AutoCloseable` until Java 21 (JEP 480). On JDK 17 the `HttpClient` created 
here holds an internal thread pool (`HttpClientFacade` → `SelectorManager`) 
that is never shut down. Every `OpaProducer` start leaks one thread pool.
   
   The standard fix is to share a single `HttpClient` at a coarser scope 
(component level, static instance, or via the existing OPA SDK client whose 
lifecycle is already managed). The simplest option that matches the existing 
`KafkaProducerHealthCheck` pattern is to pass the HttpClient in as a 
constructor argument so its lifecycle is owned by `OpaProducer` (which already 
has `doStart` / `doStop`):
   
   ```suggestion
           this.httpClient = 
HttpClient.newBuilder().connectTimeout(TIMEOUT).build();
   ```
   
   → Move `HttpClient` creation to `OpaProducer.doStart()`, store it as a field 
on the producer, pass it into the `OpaProducerHealthCheck` constructor, and 
shut it down in `OpaProducer.doStop()` (on Java 21+ call `httpClient.close()`, 
on 17 there is no shutdown API — which is exactly why the client should be 
shared or the OPA SDK's own transport reused).



##########
components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaProducer.java:
##########
@@ -30,6 +36,41 @@ public OpaEndpoint getEndpoint() {
         return (OpaEndpoint) super.getEndpoint();
     }
 
+    @Override
+    protected void doStart() throws Exception {
+        super.doStart();
+
+        OpaConfiguration configuration = getEndpoint().getConfiguration();
+        // an injected client can point anywhere, and the endpoint has no way 
to ask it where; only probe a
+        // server we were told the address of
+        if (configuration.getOpaClient() != null || 
ObjectHelper.isEmpty(configuration.getServerUrl())) {
+            return;
+        }
+
+        // health-check is optional so discover and resolve
+        healthCheckRepository = HealthCheckHelper.getHealthCheckRepository(
+                getEndpoint().getCamelContext(),
+                "producers",
+                WritableHealthCheckRepository.class);
+
+        if (healthCheckRepository != null) {
+            producerHealthCheck = new OpaProducerHealthCheck(
+                    configuration.getServerUrl(), 
configuration.getBearerToken(),
+                    getEndpoint().getPolicyPath(), 
getEndpoint().getPolicyPath());
+            
producerHealthCheck.setEnabled(getEndpoint().getComponent().isHealthCheckProducerEnabled());
+            healthCheckRepository.addHealthCheck(producerHealthCheck);

Review Comment:
   ⚠️ **Health-check ID collision when multiple OPA endpoints share the same 
policy path.**
   
   The health-check ID is `"producer:opa-" + policyPath`. 
`ProducersHealthCheckRepository` uses a plain `CopyOnWriteArrayList` — 
`addHealthCheck` does not deduplicate by ID. Two endpoints like:
   
   ```
   opa://authz/orders/allow?serverUrl=http://opa-primary:8181
   opa://authz/orders/allow?serverUrl=http://opa-secondary:8181
   ```
   
   …each register `"producer:opa-authz/orders/allow"`. Both appear in 
`stream()` and the second `removeHealthCheck` in `doStop` silently removes the 
first registration (object identity, not by ID), leaving the repository in a 
corrupted state.
   
   Use `getEndpoint().getEndpointUri()` instead — it is guaranteed unique 
within a `CamelContext` and already encodes the full connection coordinates:
   
   ```suggestion
               producerHealthCheck = new OpaProducerHealthCheck(
                       configuration.getServerUrl(), 
configuration.getBearerToken(),
                       getEndpoint().getPolicyPath(), 
getEndpoint().getEndpointUri());
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to