Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -325,34 +325,26 @@ public String toString() {
+ '}';
}
}

public enum Trigger {
PERIODIC,
MANUAL
}
public static class BundleConfig {
/** Default maximum bundle size (compressed download and decompressed contents): 512 MB. */
public static final long DEFAULT_MAX_SIZE_BYTES = 512L * 1024 * 1024;

private PollingConfig polling;
private String service;
private String resource;
<<<<<<< HEAD

@JsonProperty("trigger")
private Trigger trigger = Trigger.PERIODIC;

public static final long DEFAULT_MAX_SIZE_BYTES = 1024L * 1024L * 1024L;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reverts the max_size_bytes feature that's on main — likely a stale-branch/bad-merge artifact. The diff deletes BundleConfig.maxSizeBytes (the field, getMaxSizeBytes/setMaxSizeBytes public API, and its Javadoc), drops the bundle.setMaxSizeBytes(...) wiring in BundlePlugin, and switches to the 2-arg new TarballBundleLoader(name, bundleData) (BundlePlugin.java:186). On main this is a configurable DoS safeguard (512 MB default, capping both the compressed download and decompressed contents). Net effect: the configurable limit is gone (falls back to the loader's built-in default) and this line silently changes the default 512 MB → 1 GB. This is unrelated to the trigger feature and shouldn't land; please rebase on current main.


/**
* Maximum bundle size in bytes, applied both to the compressed HTTP download and to the
* decompressed tarball contents. Defaults to {@link #DEFAULT_MAX_SIZE_BYTES} (512 MB).
*
* <p><strong>Effective ceiling differs by path</strong>, because both paths buffer payloads
* into a Java {@code byte[]}, whose maximum length is {@link Integer#MAX_VALUE} (~2 GB):
*
* <ul>
* <li><strong>HTTP download:</strong> The entire response is buffered in a single {@code
* byte[]}, so the effective limit is capped at {@code Integer.MAX_VALUE} (~2 GB).
* Configured values above that are silently clamped to {@code Integer.MAX_VALUE} on the
* download path.
* <li><strong>Decompressed tarball contents:</strong> The cumulative budget is tracked as a
* {@code long} and may exceed 2 GB. Only each individual entry is bounded by the {@code
* byte[]} ceiling (~2 GB per entry); the sum across all entries can be larger.
* </ul>
*/
@JsonProperty("max_size_bytes")
private long maxSizeBytes = DEFAULT_MAX_SIZE_BYTES;
=======
public static final long DEFAULT_MAX_SIZE_BYTES = 1024L * 1024L * 1024L;
>>>>>>> 1a6e0d2e887f7d8247cae25ae6cadbdb4c2b718a

public PollingConfig getPolling() {
return polling;
Expand Down Expand Up @@ -381,20 +373,42 @@ public BundleConfig setResource(String resource) {
return this;
}

<<<<<<< HEAD
public Trigger getTrigger() {
return trigger;
}

public BundleConfig setTrigger(Trigger trigger) {
this.trigger = trigger;
return this;
}

public long getMaxSizeBytes() {
return maxSizeBytes;
}

/**
* Set the maximum bundle size in bytes. Note that the HTTP download enforcement is capped at
* {@link Integer#MAX_VALUE} (~2 GB); values above that are clamped on the download path. See
* {@link #maxSizeBytes} for details.
*/
public BundleConfig setMaxSizeBytes(long maxSizeBytes) {
this.maxSizeBytes = maxSizeBytes;
return this;
}

@Override
public String toString() {
return "BundleConfig{"
+ "polling="
+ polling
+ ", service='"
+ service
+ '\''
+ ", resource='"
+ resource
+ '\''
+ ", trigger="
+ trigger
+ ", maxSizeBytes="
+ maxSizeBytes
+ '}';
=======
@Override
public String toString() {
return "BundleConfig{"
Expand All @@ -406,9 +420,8 @@ public String toString() {
+ ", resource='"
+ resource
+ '\''
+ ", maxSizeBytes="
+ maxSizeBytes
+ '}';
>>>>>>> 1a6e0d2e887f7d8247cae25ae6cadbdb4c2b718a
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@
protected String service;
protected String resource;
protected Config.PollingConfig polling;
protected Config.Trigger trigger = Config.Trigger.PERIODIC;
<<<<<<< HEAD

Check failure on line 57 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / Build All Modules

> expected

Check failure on line 57 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / Build All Modules

illegal start of type

Check failure on line 57 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / Checkstyle & PMD

> expected

Check failure on line 57 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / Checkstyle & PMD

illegal start of type

Check failure on line 57 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / test-opa-services (17)

> expected

Check failure on line 57 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / test-opa-services (17)

illegal start of type

Check failure on line 57 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / test-opa-services (21)

> expected

Check failure on line 57 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / test-opa-services (21)

illegal start of type

Check failure on line 57 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / Analyze (Java)

> expected

Check failure on line 57 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / Analyze (Java)

illegal start of type
private ScheduledExecutorService scheduler;

Check failure on line 58 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / Build All Modules

illegal start of type

Check failure on line 58 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / Checkstyle & PMD

illegal start of type

Check failure on line 58 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / test-opa-services (17)

illegal start of type

Check failure on line 58 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / test-opa-services (21)

illegal start of type

Check failure on line 58 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / Analyze (Java)

illegal start of type
=======

Check failure on line 59 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / Build All Modules

illegal start of type

Check failure on line 59 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / Checkstyle & PMD

illegal start of type

Check failure on line 59 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / test-opa-services (17)

illegal start of type

Check failure on line 59 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / test-opa-services (21)

illegal start of type

Check failure on line 59 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / Analyze (Java)

illegal start of type
>>>>>>> 1a6e0d2e887f7d8247cae25ae6cadbdb4c2b718a

Check failure on line 60 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / Build All Modules

<identifier> expected

Check failure on line 60 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / Checkstyle & PMD

<identifier> expected

Check failure on line 60 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / test-opa-services (17)

<identifier> expected

Check failure on line 60 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / test-opa-services (21)

<identifier> expected

Check failure on line 60 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / Analyze (Java)

<identifier> expected
protected String etag;
protected long lastModifiedTime = 0;
protected long maxSizeBytes = Config.BundleConfig.DEFAULT_MAX_SIZE_BYTES;
Expand All @@ -66,6 +71,18 @@
"binary/octet-stream",
"application/x-tar");

public BundleDownloader setTrigger(Config.Trigger trigger) {
this.trigger = trigger;
return this;
}

<<<<<<< HEAD

Check failure on line 79 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / Build All Modules

> expected

Check failure on line 79 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / Build All Modules

illegal start of type

Check failure on line 79 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / Checkstyle & PMD

> expected

Check failure on line 79 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / Checkstyle & PMD

illegal start of type

Check failure on line 79 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / test-opa-services (17)

> expected

Check failure on line 79 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / test-opa-services (17)

illegal start of type

Check failure on line 79 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / test-opa-services (21)

> expected

Check failure on line 79 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / test-opa-services (21)

illegal start of type

Check failure on line 79 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / Analyze (Java)

> expected

Check failure on line 79 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / Analyze (Java)

illegal start of type
public Config.Trigger getTrigger() {

Check failure on line 80 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / Build All Modules

illegal start of type

Check failure on line 80 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / Checkstyle & PMD

illegal start of type

Check failure on line 80 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / test-opa-services (17)

illegal start of type

Check failure on line 80 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / test-opa-services (21)

illegal start of type

Check failure on line 80 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / Analyze (Java)

illegal start of type
return trigger;
}

=======

Check failure on line 84 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / Build All Modules

illegal start of type

Check failure on line 84 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / Checkstyle & PMD

illegal start of type

Check failure on line 84 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / test-opa-services (17)

illegal start of type

Check failure on line 84 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / test-opa-services (21)

illegal start of type

Check failure on line 84 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / Analyze (Java)

illegal start of type
>>>>>>> 1a6e0d2e887f7d8247cae25ae6cadbdb4c2b718a

Check failure on line 85 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / Build All Modules

<identifier> expected

Check failure on line 85 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / Checkstyle & PMD

<identifier> expected

Check failure on line 85 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / test-opa-services (17)

<identifier> expected

Check failure on line 85 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / test-opa-services (21)

<identifier> expected

Check failure on line 85 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java

View workflow job for this annotation

GitHub Actions / Analyze (Java)

<identifier> expected
/**
* Construct a BundleDownloader.
*
Expand Down Expand Up @@ -165,6 +182,10 @@
return this;
}

public long getMaxSizeBytes() {
return maxSizeBytes;
}

/**
* @return a future that completes when the first bundle download succeeds, or completes
* exceptionally with the underlying download/activation error
Expand All @@ -179,7 +200,20 @@
* @param scheduler the scheduler to use for periodic downloads
* @return a future that completes when the initial bundle is downloaded and activated
*/


public CompletableFuture<Void> startPolling(ScheduledExecutorService scheduler) {
<<<<<<< HEAD
this.scheduler = scheduler;
=======

>>>>>>> 1a6e0d2e887f7d8247cae25ae6cadbdb4c2b718a
if (trigger == Config.Trigger.MANUAL) {
manager.getLogger().info(
"Bundle '%s': Manual trigger mode enabled; waiting for refresh()", name);
return initialActivation;
}

int minDelay =
(polling != null && polling.getMinDelaySeconds() != null)
? polling.getMinDelaySeconds()
Expand All @@ -189,12 +223,31 @@
? polling.getMaxDelaySeconds()
: 120;



<<<<<<< HEAD
scheduler.schedule(() -> downloadBundle(initialActivation), 0, TimeUnit.SECONDS);
=======
scheduler.schedule(this::downloadBundle, 0, TimeUnit.SECONDS);
>>>>>>> 1a6e0d2e887f7d8247cae25ae6cadbdb4c2b718a
scheduleNextPoll(scheduler, minDelay, maxDelay);

return initialActivation;
}

public CompletableFuture<Void> refresh() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refresh() is broken for repeated use and blocks the caller. It calls downloadBundle() synchronously on the caller's thread (every other download runs on the scheduler, doing blocking network I/O), and returns initialActivation — a one-shot CompletableFuture whose completion sites are all guarded by if (!initialActivation.isDone()). So the returned future reflects only the first activation: after the first success it's already completed, and later refresh() calls return an already-done future that doesn't track that refresh's outcome (success or failure). Also, in MANUAL mode startPolling returns initialActivation without ever scheduling a download, so getInitialActivation() never completes until a refresh() occurs — which can hang plugin startup if lifecycle code awaits it. Consider returning a fresh future per refresh and dispatching the download on the scheduler.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review! I've addressed the compile issues, trigger propagation, validation, and rebased onto the latest main. For the refresh() redesign, I understand the goal of returning a fresh future per refresh while keeping initialActivation for the first activation only. Before I refactor it, could you clarify the expected behavior for manual mode? Should startPolling() return an already-completed initialActivation in manual mode, or should initialActivation only complete after the first successful refresh()?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked how OPA's Go bundle plugin handles this:

  1. Manual mode does no download at startup — the downloader's Start() is a no-op unless the trigger is periodic; a fetch only happens on Trigger().
  2. Readiness = a bundle actually activated — the plugin stays NotReady until every bundle has activated once. But OPA's Start() never blocks on this; readiness is just reported status.

So no, startPolling() shouldn't return a pre-completed initialActivation. Keep it meaningful (first successful activation). The reason a never-completing future hangs us is SDK-specific: build() blocks up to 60s for bundles to reach OK, and BundlePlugin.start() couples that status to allOf(initialActivation). The fix is to decouple them, like OPA:

  • start() shouldn't gate the plugin's OK on a manual bundle's initialActivation — loaders are started and awaiting triggers, so the plugin reaches OK and build() won't hang.
  • initialActivation completes only on the first successful refresh — don't complete it eagerly.
  • refresh() returns a fresh future per call, dispatched on the scheduler (not synchronously on the caller's thread).

A caller wanting to block until the first manual bundle loads awaits refresh(), not getInitialActivation().

<<<<<<< HEAD
CompletableFuture<Void> refreshFuture = new CompletableFuture<>();

scheduler.execute(() -> downloadBundle(refreshFuture));

return refreshFuture;
=======
downloadBundle();
return initialActivation;
>>>>>>> 1a6e0d2e887f7d8247cae25ae6cadbdb4c2b718a
}

// Re-schedules the next download with a uniformly random delay in [minDelay, maxDelay],
// matching Go-OPA's jittered polling. ScheduledExecutorService has no built-in jitter, so the
// task chains itself. RejectedExecutionException after a shutdown breaks the chain cleanly.
Expand All @@ -207,7 +260,7 @@
scheduler.schedule(
() -> {
try {
downloadBundle();
downloadBundle(initialActivation);
} catch (Exception e) {
// downloadBundle() handles its own logging; swallow so the chain keeps polling.
// Only Exception is caught here — Errors (OOM, etc.) propagate and let the
Expand All @@ -230,7 +283,7 @@
* <p>Handles HTTP/HTTPS downloads with ETag caching, file:// URIs, and filesystem paths. Calls
* {@link #activateBundle(byte[])} when new bundle data is available.
*/
protected void downloadBundle() {
protected void downloadBundle(CompletableFuture<Void> future) {
try {
Config.ServiceConfig serviceConfig = manager.getConfig().getService(service);
if (serviceConfig == null) {
Expand All @@ -256,17 +309,17 @@

// Handle file:// URIs
if ("file".equalsIgnoreCase(uri.getScheme())) {
handleFileDownload(Paths.get(uri));
handleFileDownload(Paths.get(uri), future);
return;
}

// Handle HTTP/HTTPS URIs
handleHttpDownload(uri);
handleHttpDownload(uri, future);
} else {
// It's a file path (relative or absolute)
Path basePath = Paths.get(baseUrl);
Path filePath = basePath.resolve(resource);
handleFileDownload(filePath);
handleFileDownload(filePath, future);
}

} catch (Exception e) {
Expand All @@ -282,12 +335,13 @@
*
* @param filePath the path to the file
*/
private void handleFileDownload(Path filePath) throws IOException {
private void handleFileDownload(Path filePath, CompletableFuture<Void> future) throws IOException{
FileTime currentModTime = Files.getLastModifiedTime(filePath);
long currentModTimeMillis = currentModTime.toMillis();

if (lastModifiedTime != 0 && lastModifiedTime == currentModTimeMillis) {
manager.getLogger().debug("Bundle '%s': File not modified, skipping activation", name);
future.complete(null);
if (!initialActivation.isDone()) {
initialActivation.complete(null);
}
Expand All @@ -298,6 +352,8 @@
activateBundle(bundleData);
lastModifiedTime = currentModTimeMillis;

future.complete(null);

if (!initialActivation.isDone()) {
initialActivation.complete(null);
}
Expand All @@ -308,7 +364,7 @@
*
* @param uri the URI to download from
*/
private void handleHttpDownload(URI uri) {
private void handleHttpDownload(URI uri, CompletableFuture<Void> future) {
HttpRequest.Builder requestBuilder =
HttpRequest.newBuilder()
.uri(uri)
Expand All @@ -335,6 +391,8 @@
? throwable.getCause()
: throwable;
manager.getLogger().error("Bundle '%s': Download error: %s", name, cause.getMessage());
future.completeExceptionally(cause);

if (!initialActivation.isDone()) {
initialActivation.completeExceptionally(cause);
}
Expand All @@ -343,6 +401,8 @@

if (response.statusCode() == 304) {
manager.getLogger().debug("Bundle '%s': Not modified (ETag match)", name);
future.complete(null);

if (!initialActivation.isDone()) {
initialActivation.complete(null);
}
Expand All @@ -354,8 +414,12 @@
if (!isAcceptableContentType(contentType)) {
String errorMsg = "Unexpected Content-Type: '" + contentType + "'";
manager.getLogger().error("Bundle '%s': %s", name, errorMsg);
RuntimeException ex = new RuntimeException(errorMsg);

future.completeExceptionally(ex);

if (!initialActivation.isDone()) {
initialActivation.completeExceptionally(new RuntimeException(errorMsg));
initialActivation.completeExceptionally(ex);
}
return;
}
Expand All @@ -364,19 +428,27 @@
activateBundle(response.body());
} catch (Exception e) {
manager.getLogger().error("Bundle '%s': Activation failed: %s", name, e.getMessage());
future.completeExceptionally(e);

if (!initialActivation.isDone()) {
initialActivation.completeExceptionally(e);
}
return;
}
future.complete(null);

if (!initialActivation.isDone()) {
initialActivation.complete(null);
}
} else {
String errorMsg = "Download failed with status " + response.statusCode();
manager.getLogger().error("Bundle '%s': %s", name, errorMsg);
RuntimeException ex = new RuntimeException(errorMsg);

future.completeExceptionally(ex);

if (!initialActivation.isDone()) {
initialActivation.completeExceptionally(new RuntimeException(errorMsg));
initialActivation.completeExceptionally(ex);
}
}
});
Expand Down
Loading
Loading