From b0c1f7aa0a83bb66e35f4708ba370ee88c3ce1e9 Mon Sep 17 00:00:00 2001 From: sean wibisono Date: Wed, 29 Jul 2026 13:32:16 +1000 Subject: [PATCH 1/3] UID2-7580: add graceful termination support to opt-out Register the shared termination monitor and prepare the image marker directory so Kubernetes can drain targets before shutdown. Co-authored-by: Cursor --- Dockerfile | 2 +- src/main/java/com/uid2/optout/Const.java | 1 + src/main/java/com/uid2/optout/Main.java | 4 ++ src/test/java/com/uid2/optout/MainTest.java | 79 +++++++++++++++++++++ 4 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/uid2/optout/MainTest.java diff --git a/Dockerfile b/Dockerfile index 239e1977..a8d1f6cc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,7 @@ COPY ./run_tool.sh /app COPY ./conf/default-config.json /app/conf/ COPY ./conf/*.xml /app/conf/ -RUN apk add --no-cache --upgrade libpng libcrypto3 libssl3 musl musl-utils gnutls && addgroup --gid 1100 uidusers && adduser -D -G uidusers --uid 1100 uid2-optout && mkdir -p /opt/uid2 && chmod 755 -R /opt/uid2 && mkdir -p /app && chmod 705 -R /app && mkdir -p /app/file-uploads && chmod 777 -R /app/file-uploads +RUN apk add --no-cache --upgrade libpng libcrypto3 libssl3 musl musl-utils gnutls && addgroup --gid 1100 uidusers && adduser -D -G uidusers --uid 1100 uid2-optout && mkdir -p /opt/uid2 && chmod 755 -R /opt/uid2 && mkdir -p /app && chmod 705 -R /app && mkdir -p /app/file-uploads && chmod 777 -R /app/file-uploads && mkdir -p /app/pod_terminating && chmod 777 -R /app/pod_terminating USER uid2-optout CMD java \ diff --git a/src/main/java/com/uid2/optout/Const.java b/src/main/java/com/uid2/optout/Const.java index 24b9d082..f928f321 100644 --- a/src/main/java/com/uid2/optout/Const.java +++ b/src/main/java/com/uid2/optout/Const.java @@ -32,6 +32,7 @@ public static class Config extends com.uid2.shared.Const.Config { public static final String OptOutSqsDeltaWindowSecondsProp = "optout_sqs_delta_window_seconds"; public static final String OptOutSqsMaxQueueSizeProp = "optout_sqs_max_queue_size"; public static final String AwsSqsEndpointProp = "aws_sqs_endpoint"; + public static final String PodTerminationCheckInterval = "pod_termination_check_interval"; } public static class Event { diff --git a/src/main/java/com/uid2/optout/Main.java b/src/main/java/com/uid2/optout/Main.java index 26b11b0e..9ca95f57 100644 --- a/src/main/java/com/uid2/optout/Main.java +++ b/src/main/java/com/uid2/optout/Main.java @@ -11,6 +11,8 @@ import com.uid2.shared.audit.UidInstanceIdProvider; import com.uid2.shared.auth.RotatingOperatorKeyProvider; import com.uid2.shared.cloud.*; +import com.uid2.shared.health.HealthManager; +import com.uid2.shared.health.PodTerminationMonitor; import com.uid2.shared.jmx.AdminApi; import com.uid2.shared.optout.OptOutCloudSync; import com.uid2.shared.store.CloudPath; @@ -65,6 +67,8 @@ public class Main { public Main(Vertx vertx, JsonObject config) throws Exception { this.vertx = vertx; this.config = config; + HealthManager.instance.registerGenericComponent(new PodTerminationMonitor( + config.getInteger(Const.Config.PodTerminationCheckInterval, 3000))); this.observeOnly = config.getBoolean(Const.Config.OptOutObserveOnlyProp); if (this.observeOnly) { LOGGER.warn("Running Observe ONLY mode: no producer, no sender"); diff --git a/src/test/java/com/uid2/optout/MainTest.java b/src/test/java/com/uid2/optout/MainTest.java new file mode 100644 index 00000000..addf13b5 --- /dev/null +++ b/src/test/java/com/uid2/optout/MainTest.java @@ -0,0 +1,79 @@ +package com.uid2.optout; + +import com.uid2.shared.health.HealthManager; +import com.uid2.shared.health.IHealthComponent; +import com.uid2.shared.health.PodTerminationMonitor; +import io.vertx.core.json.JsonObject; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +class MainTest { + @TempDir + Path tempDir; + + private HealthManager originalHealthManager; + private CapturingHealthManager healthManager; + + @BeforeEach + void setUp() { + originalHealthManager = HealthManager.instance; + healthManager = new CapturingHealthManager(); + HealthManager.instance = healthManager; + } + + @AfterEach + void tearDown() { + HealthManager.instance = originalHealthManager; + } + + @Test + void constructorRegistersPodTerminationMonitorWithDefaultInterval() throws Exception { + new Main(null, createConfig()); + + PodTerminationMonitor monitor = assertInstanceOf( + PodTerminationMonitor.class, healthManager.registeredComponent); + assertEquals(3000L, getFileCheckInterval(monitor)); + } + + @Test + void constructorUsesConfiguredPodTerminationCheckInterval() throws Exception { + JsonObject config = createConfig() + .put(Const.Config.PodTerminationCheckInterval, 1250); + + new Main(null, config); + + PodTerminationMonitor monitor = assertInstanceOf( + PodTerminationMonitor.class, healthManager.registeredComponent); + assertEquals(1250L, getFileCheckInterval(monitor)); + } + + private JsonObject createConfig() throws Exception { + JsonObject config = new JsonObject(Files.readString(Path.of("conf/default-config.json"))); + config.mergeIn(new JsonObject(Files.readString(Path.of("conf/local-config.json")))); + return config.put(Const.Config.OptOutDataDirProp, tempDir.toString()); + } + + private static long getFileCheckInterval(PodTerminationMonitor monitor) throws Exception { + var field = PodTerminationMonitor.class.getDeclaredField("fileCheckIntervalMs"); + field.setAccessible(true); + return field.getLong(monitor); + } + + private static class CapturingHealthManager extends HealthManager { + private IHealthComponent registeredComponent; + + @Override + public synchronized T registerGenericComponent(T component) { + registeredComponent = component; + return component; + } + } +} From 5a8f328cfd544e9f30dfccb4b3f5b1267c039f48 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 29 Jul 2026 06:04:03 +0000 Subject: [PATCH 2/3] [CI Pipeline] Released Snapshot version: 4.10.4-alpha-233-SNAPSHOT [no-jira - reason: automated release] Automated release (PR #420). --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6c164f05..af15d7be 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.uid2 uid2-optout - 4.10.3 + 4.10.4-alpha-233-SNAPSHOT uid2-optout https://github.com/IABTechLab/uid2-optout From 166f3f475f28641f7488ab28089b180d17e19294 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 29 Jul 2026 06:12:18 +0000 Subject: [PATCH 3/3] [CI Pipeline] Released Snapshot version: 4.10.5-alpha-234-SNAPSHOT [no-jira - reason: automated release] Automated release (PR #421). --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index af15d7be..b4e444cd 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.uid2 uid2-optout - 4.10.4-alpha-233-SNAPSHOT + 4.10.5-alpha-234-SNAPSHOT uid2-optout https://github.com/IABTechLab/uid2-optout