-
Notifications
You must be signed in to change notification settings - Fork 22
Add manual trigger mode for bundle refresh #112
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
357dc24
0064d08
ecfd524
1a6e0d2
09e254c
7bfed67
fbf3cc5
1da702e
3ed3344
6ccf25c
4a66104
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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
|
||
| private ScheduledExecutorService scheduler; | ||
|
Check failure on line 58 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java
|
||
| ======= | ||
|
Check failure on line 59 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java
|
||
| >>>>>>> 1a6e0d2e887f7d8247cae25ae6cadbdb4c2b718a | ||
|
Check failure on line 60 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java
|
||
| protected String etag; | ||
| protected long lastModifiedTime = 0; | ||
| protected long maxSizeBytes = Config.BundleConfig.DEFAULT_MAX_SIZE_BYTES; | ||
|
|
@@ -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
|
||
| public Config.Trigger getTrigger() { | ||
|
Check failure on line 80 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java
|
||
| return trigger; | ||
| } | ||
|
|
||
| ======= | ||
|
Check failure on line 84 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java
|
||
| >>>>>>> 1a6e0d2e887f7d8247cae25ae6cadbdb4c2b718a | ||
|
Check failure on line 85 in opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/BundleDownloader.java
|
||
| /** | ||
| * Construct a BundleDownloader. | ||
| * | ||
|
|
@@ -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 | ||
|
|
@@ -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() | ||
|
|
@@ -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() { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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()?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I checked how OPA's Go bundle plugin handles this:
So no,
A caller wanting to block until the first manual bundle loads awaits |
||
| <<<<<<< 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. | ||
|
|
@@ -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 | ||
|
|
@@ -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) { | ||
|
|
@@ -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) { | ||
|
|
@@ -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); | ||
| } | ||
|
|
@@ -298,6 +352,8 @@ | |
| activateBundle(bundleData); | ||
| lastModifiedTime = currentModTimeMillis; | ||
|
|
||
| future.complete(null); | ||
|
|
||
| if (!initialActivation.isDone()) { | ||
| initialActivation.complete(null); | ||
| } | ||
|
|
@@ -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) | ||
|
|
@@ -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); | ||
| } | ||
|
|
@@ -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); | ||
| } | ||
|
|
@@ -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; | ||
| } | ||
|
|
@@ -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); | ||
| } | ||
| } | ||
| }); | ||
|
|
||
There was a problem hiding this comment.
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_bytesfeature that's onmain— likely a stale-branch/bad-merge artifact. The diff deletesBundleConfig.maxSizeBytes(the field,getMaxSizeBytes/setMaxSizeBytespublic API, and its Javadoc), drops thebundle.setMaxSizeBytes(...)wiring inBundlePlugin, and switches to the 2-argnew TarballBundleLoader(name, bundleData)(BundlePlugin.java:186). Onmainthis 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 currentmain.