This is an automated email from the ASF dual-hosted git repository.

oscerd pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 72c3c6fde1d9 CAMEL-24743: camel-opa - skip the producer health check 
in wasm mode and warn on ignored server options (#26669)
72c3c6fde1d9 is described below

commit 72c3c6fde1d98cc66562f4c8f60221cb8a216e41
Author: Andrea Cosentino <[email protected]>
AuthorDate: Mon Sep 21 18:21:13 2026 +0200

    CAMEL-24743: camel-opa - skip the producer health check in wasm mode and 
warn on ignored server options (#26669)
    
    Co-authored-by: Claude Opus 4.8 <[email protected]>
---
 .../apache/camel/catalog/docs/opa-component.adoc   | 11 +--
 .../camel-opa/src/main/docs/opa-component.adoc     | 11 +--
 .../camel/component/opa/OpaConfiguration.java      |  4 +-
 .../apache/camel/component/opa/OpaEndpoint.java    | 25 ++++++-
 .../apache/camel/component/opa/OpaProducer.java    |  7 +-
 .../component/opa/OpaWasmModeValidationTest.java   | 80 ++++++++++++++++++++++
 6 files changed, 125 insertions(+), 13 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/opa-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/opa-component.adoc
index 0a373925f776..89752125d723 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/opa-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/opa-component.adoc
@@ -259,9 +259,11 @@ Which to choose:
 | Unreachable decision point | a real failure mode | cannot happen
 |===
 
-`serverUrl`, `bearerToken` and `failOpen` have no meaning in `wasm` mode — 
there is no server to address,
-authenticate to, or fail to reach — and no health check is registered, because 
there is nothing to probe. An
-absent health check is not a healthy one.
+`serverUrl` and `bearerToken` have no meaning in `wasm` mode — there is no 
server to address or authenticate
+to, and the endpoint warns at startup if either was set — and no health check 
is registered, because there is
+nothing to probe. An absent health check is not a healthy one. `failOpen` 
still applies: a `wasm` evaluation
+can fail (a busy pool, a bad bundle), and `failOpen` governs whether that 
failure denies the exchange or lets
+it through, exactly as in `rest` mode.
 
 The decision contract is identical in both modes: the same headers, the same 
`allowKey` handling, and an
 undefined decision fails closed the same way. A route does not need to know 
which engine evaluated it.
@@ -350,7 +352,8 @@ That is why the policy's check is on by default rather than 
opt-in like the prod
 
 Neither check is registered when an `opaClient` was injected: that client may 
point anywhere and neither the
 endpoint nor the policy has a way to ask it where, so probing the configured 
`serverUrl` would report on a server
-they may never talk to. The endpoint check is also skipped when no `serverUrl` 
was given.
+they may never talk to. The endpoint check is also skipped when no `serverUrl` 
was given, and in `wasm` mode,
+where the policy is evaluated in-process and there is no server to probe.
 
 == Security notes
 
diff --git a/components/camel-opa/src/main/docs/opa-component.adoc 
b/components/camel-opa/src/main/docs/opa-component.adoc
index 0a373925f776..89752125d723 100644
--- a/components/camel-opa/src/main/docs/opa-component.adoc
+++ b/components/camel-opa/src/main/docs/opa-component.adoc
@@ -259,9 +259,11 @@ Which to choose:
 | Unreachable decision point | a real failure mode | cannot happen
 |===
 
-`serverUrl`, `bearerToken` and `failOpen` have no meaning in `wasm` mode — 
there is no server to address,
-authenticate to, or fail to reach — and no health check is registered, because 
there is nothing to probe. An
-absent health check is not a healthy one.
+`serverUrl` and `bearerToken` have no meaning in `wasm` mode — there is no 
server to address or authenticate
+to, and the endpoint warns at startup if either was set — and no health check 
is registered, because there is
+nothing to probe. An absent health check is not a healthy one. `failOpen` 
still applies: a `wasm` evaluation
+can fail (a busy pool, a bad bundle), and `failOpen` governs whether that 
failure denies the exchange or lets
+it through, exactly as in `rest` mode.
 
 The decision contract is identical in both modes: the same headers, the same 
`allowKey` handling, and an
 undefined decision fails closed the same way. A route does not need to know 
which engine evaluated it.
@@ -350,7 +352,8 @@ That is why the policy's check is on by default rather than 
opt-in like the prod
 
 Neither check is registered when an `opaClient` was injected: that client may 
point anywhere and neither the
 endpoint nor the policy has a way to ask it where, so probing the configured 
`serverUrl` would report on a server
-they may never talk to. The endpoint check is also skipped when no `serverUrl` 
was given.
+they may never talk to. The endpoint check is also skipped when no `serverUrl` 
was given, and in `wasm` mode,
+where the policy is evaluated in-process and there is no server to probe.
 
 == Security notes
 
diff --git 
a/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaConfiguration.java
 
b/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaConfiguration.java
index 9c3753d796c0..392e98257a4f 100644
--- 
a/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaConfiguration.java
+++ 
b/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaConfiguration.java
@@ -26,8 +26,10 @@ import org.apache.camel.support.jsse.SSLContextParameters;
 @UriParams
 public class OpaConfiguration implements Cloneable {
 
+    static final String DEFAULT_SERVER_URL = "http://localhost:8181";;
+
     @UriParam(defaultValue = "http://localhost:8181";)
-    private String serverUrl = "http://localhost:8181";;
+    private String serverUrl = DEFAULT_SERVER_URL;
 
     @UriParam(defaultValue = "allow")
     private String allowKey = "allow";
diff --git 
a/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaEndpoint.java
 
b/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaEndpoint.java
index 495f78f3184b..d8bafb72c338 100644
--- 
a/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaEndpoint.java
+++ 
b/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaEndpoint.java
@@ -31,6 +31,8 @@ import org.apache.camel.spi.UriPath;
 import org.apache.camel.support.DefaultEndpoint;
 import org.apache.camel.support.jsse.SSLContextParameters;
 import org.apache.camel.util.ObjectHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Evaluate Open Policy Agent (Rego) policies against an Exchange and record 
the allow/deny decision on it.
@@ -40,9 +42,11 @@ import org.apache.camel.util.ObjectHelper;
              headersClass = OpaConstants.class)
 public class OpaEndpoint extends DefaultEndpoint {
 
-    private static final String WASM_MODE = "wasm";
+    static final String WASM_MODE = "wasm";
     private static final String REST_MODE = "rest";
 
+    private static final Logger LOG = 
LoggerFactory.getLogger(OpaEndpoint.class);
+
     @UriPath(description = "Path of the Rego rule head to evaluate, relative 
to the OPA data document. For a rule"
                            + " named allow in a policy declaring package 
authz.orders, this is authz/orders/allow."
                            + " The path is taken from the endpoint only: it is 
deliberately not overridable by a"
@@ -67,6 +71,7 @@ public class OpaEndpoint extends DefaultEndpoint {
         super.doStart();
         String mode = configuration.getEvaluationMode();
         if (WASM_MODE.equalsIgnoreCase(mode)) {
+            warnAboutIgnoredServerOptions();
             evaluator = createWasmEvaluator();
         } else if (!REST_MODE.equalsIgnoreCase(mode)) {
             // silently falling back to rest would leave a typo'd mode running 
against a server while quietly
@@ -115,6 +120,24 @@ public class OpaEndpoint extends DefaultEndpoint {
         return sslContext;
     }
 
+    /**
+     * {@code serverUrl} and {@code bearerToken} address and authenticate to 
an OPA server, of which there is none in
+     * {@code wasm} mode, so they are ignored - a startup warning is clearer 
than silence for an operator who set one
+     * and expects it to take effect. {@code failOpen} is deliberately not 
among these: a {@code wasm} evaluation can
+     * still fail (a busy pool, a bad bundle), and {@code failOpen} governs 
that outcome exactly as in {@code rest}
+     * mode, so it applies in both.
+     */
+    private void warnAboutIgnoredServerOptions() {
+        if (ObjectHelper.isNotEmpty(configuration.getBearerToken())) {
+            LOG.warn("bearerToken is ignored when evaluationMode=wasm: there 
is no server to authenticate to");
+        }
+        if (ObjectHelper.isNotEmpty(configuration.getServerUrl())
+                && 
!OpaConfiguration.DEFAULT_SERVER_URL.equals(configuration.getServerUrl())) {
+            LOG.warn("serverUrl '{}' is ignored when evaluationMode=wasm: the 
policy is evaluated in-process",
+                    configuration.getServerUrl());
+        }
+    }
+
     private OpaPolicyEvaluator createWasmEvaluator() throws Exception {
         if (ObjectHelper.isEmpty(configuration.getPolicyBundle())) {
             throw new IllegalArgumentException(
diff --git 
a/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaProducer.java
 
b/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaProducer.java
index 3d6e2e4c25a2..110d8367f88f 100644
--- 
a/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaProducer.java
+++ 
b/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaProducer.java
@@ -41,9 +41,10 @@ public class OpaProducer extends DefaultProducer {
         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())) {
+        // an injected client can point anywhere, and the endpoint has no way 
to ask it where; wasm mode evaluates
+        // in-process with no server at all - in neither case is there a 
server we know the address of to probe
+        if (configuration.getOpaClient() != null || 
ObjectHelper.isEmpty(configuration.getServerUrl())
+                || 
OpaEndpoint.WASM_MODE.equalsIgnoreCase(configuration.getEvaluationMode())) {
             return;
         }
 
diff --git 
a/components/camel-opa/src/test/java/org/apache/camel/component/opa/OpaWasmModeValidationTest.java
 
b/components/camel-opa/src/test/java/org/apache/camel/component/opa/OpaWasmModeValidationTest.java
new file mode 100644
index 000000000000..e4661972652d
--- /dev/null
+++ 
b/components/camel-opa/src/test/java/org/apache/camel/component/opa/OpaWasmModeValidationTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.util.List;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.health.HealthCheck;
+import org.apache.camel.health.HealthCheckHelper;
+import org.apache.camel.health.WritableHealthCheckRepository;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+/**
+ * In {@code wasm} mode the policy is evaluated in-process, so there is no OPA 
server to probe and no producer health
+ * check is registered. {@code failOpen}, on the other hand, still governs an 
evaluation failure (a busy pool, a bad
+ * bundle) in {@code wasm} mode too, so it must not be rejected (CAMEL-24743).
+ */
+public class OpaWasmModeValidationTest extends CamelTestSupport {
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                // a rest-mode route registers a producer readiness check 
(positive control), a wasm-mode route
+                // sharing the same policy path must not - the difference is 
exactly what this test asserts
+                
from("direct:rest").to("opa:authz/allow?serverUrl=http://opa-rest:8181";);
+                
from("direct:wasm").to("opa:authz/allow?evaluationMode=wasm&policyBundle=classpath:authz.wasm");
+            }
+        };
+    }
+
+    private List<HealthCheck> producerChecks() {
+        WritableHealthCheckRepository repository = 
HealthCheckHelper.getHealthCheckRepository(
+                context, "producers", WritableHealthCheckRepository.class);
+        assertThat(repository).isNotNull();
+        // producer health checks are disabled globally by default, so enable 
the repository to read them back
+        repository.setEnabled(true);
+        return repository.stream().toList();
+    }
+
+    @Test
+    void registersTheCheckForTheRestRouteButNotTheWasmRoute() {
+        List<HealthCheck> checks = producerChecks();
+        // exactly one check, and it is the rest route's - the wasm route 
evaluates in-process with no server to probe
+        assertThat(checks).hasSize(1);
+        assertThat(checks.get(0).getId()).contains("opa-rest");
+    }
+
+    @Test
+    void acceptsFailOpenInWasmMode() {
+        // failOpen governs an evaluation failure (a busy pool, a bad bundle), 
which happens in wasm too, so it is a
+        // valid option here and starting the route must not throw
+        assertThatCode(() -> context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:failopen")
+                        
.to("opa:authz/allow?evaluationMode=wasm&policyBundle=classpath:authz.wasm&failOpen=true");
+            }
+        })).doesNotThrowAnyException();
+    }
+}

Reply via email to