diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index e37ab5d8d5..25b234846a 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -10,7 +10,7 @@ jobs:
integration_tests:
strategy:
matrix:
- java: [ 17, 21, 24 ]
+ java: [ 17, 21, 25 ]
# Use the latest versions supported by minikube, otherwise GitHub it will
# end up in a throttling requests from minikube and workflow will fail.
# Minikube does such requests only if a version is not officially supported.
@@ -26,7 +26,7 @@ jobs:
httpclient: [ 'vertx', 'jdk', 'jetty' ]
uses: ./.github/workflows/integration-tests.yml
with:
- java-version: 24
+ java-version: 25
kube-version: '1.32.0'
http-client: ${{ matrix.httpclient }}
experimental: true
@@ -36,7 +36,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- java: [ 17, 21, 24 ]
+ java: [ 17, 21, 25 ]
steps:
- uses: actions/checkout@v5
- name: Set up Java and Maven
diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml
index f72a0af43b..7aa92a409c 100644
--- a/.github/workflows/e2e-test.yml
+++ b/.github/workflows/e2e-test.yml
@@ -43,7 +43,7 @@ jobs:
- name: Set up Java and Maven
uses: actions/setup-java@v5
with:
- java-version: 17
+ java-version: 25
distribution: temurin
cache: 'maven'
diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml
index df5819f33d..79660cfb1b 100644
--- a/.github/workflows/pr.yml
+++ b/.github/workflows/pr.yml
@@ -22,7 +22,7 @@ jobs:
uses: actions/setup-java@v5
with:
distribution: temurin
- java-version: 21
+ java-version: 25
cache: 'maven'
- name: Check code format
run: |
diff --git a/.github/workflows/release-project-in-dir.yml b/.github/workflows/release-project-in-dir.yml
index 0683b01b1f..0313aebe4d 100644
--- a/.github/workflows/release-project-in-dir.yml
+++ b/.github/workflows/release-project-in-dir.yml
@@ -80,4 +80,4 @@ jobs:
uses: ad-m/github-push-action@master
with:
branch: "${{inputs.version_branch}}"
- github_token: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
+ github_token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/snapshot-releases.yml b/.github/workflows/snapshot-releases.yml
index a12d9aaed5..0f560dd2cb 100644
--- a/.github/workflows/snapshot-releases.yml
+++ b/.github/workflows/snapshot-releases.yml
@@ -21,7 +21,7 @@ jobs:
uses: actions/setup-java@v5
with:
distribution: temurin
- java-version: 17
+ java-version: 21
cache: 'maven'
- name: Build and test project
run: ./mvnw ${MAVEN_ARGS} clean install --file pom.xml
@@ -33,7 +33,7 @@ jobs:
- name: Set up Java and Maven
uses: actions/setup-java@v5
with:
- java-version: 17
+ java-version: 21
distribution: temurin
cache: 'maven'
server-id: central
diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml
index 370f36303c..132575edaa 100644
--- a/.github/workflows/sonar.yml
+++ b/.github/workflows/sonar.yml
@@ -28,7 +28,7 @@ jobs:
uses: actions/setup-java@v5
with:
distribution: temurin
- java-version: 17
+ java-version: 25
cache: 'maven'
- name: Cache SonarCloud packages
uses: actions/cache@v4
diff --git a/bootstrapper-maven-plugin/pom.xml b/bootstrapper-maven-plugin/pom.xml
index ccdc62f905..5952379112 100644
--- a/bootstrapper-maven-plugin/pom.xml
+++ b/bootstrapper-maven-plugin/pom.xml
@@ -5,7 +5,7 @@
io.javaoperatorsdk
java-operator-sdk
- 5.1.4-SNAPSHOT
+ 5.1.5-SNAPSHOT
bootstrapper
@@ -14,10 +14,10 @@
Operator SDK - Bootstrapper Maven Plugin
- 3.15.1
+ 3.15.2
3.9.11
3.0.0
- 3.15.1
+ 3.15.2
diff --git a/bootstrapper-maven-plugin/src/test/java/io/javaoperatorsdk/bootstrapper/BootstrapperTest.java b/bootstrapper-maven-plugin/src/test/java/io/javaoperatorsdk/bootstrapper/BootstrapperIT.java
similarity index 96%
rename from bootstrapper-maven-plugin/src/test/java/io/javaoperatorsdk/bootstrapper/BootstrapperTest.java
rename to bootstrapper-maven-plugin/src/test/java/io/javaoperatorsdk/bootstrapper/BootstrapperIT.java
index f7840c1585..ec1399fcf8 100644
--- a/bootstrapper-maven-plugin/src/test/java/io/javaoperatorsdk/bootstrapper/BootstrapperTest.java
+++ b/bootstrapper-maven-plugin/src/test/java/io/javaoperatorsdk/bootstrapper/BootstrapperIT.java
@@ -13,9 +13,9 @@
import static org.assertj.core.api.Assertions.assertThat;
-class BootstrapperTest {
+class BootstrapperIT {
- private static final Logger log = LoggerFactory.getLogger(BootstrapperTest.class);
+ private static final Logger log = LoggerFactory.getLogger(BootstrapperIT.class);
Bootstrapper bootstrapper = new Bootstrapper();
diff --git a/caffeine-bounded-cache-support/pom.xml b/caffeine-bounded-cache-support/pom.xml
index bd8c2ccce9..76f3db9abc 100644
--- a/caffeine-bounded-cache-support/pom.xml
+++ b/caffeine-bounded-cache-support/pom.xml
@@ -4,7 +4,7 @@
io.javaoperatorsdk
java-operator-sdk
- 5.1.4-SNAPSHOT
+ 5.1.5-SNAPSHOT
caffeine-bounded-cache-support
diff --git a/micrometer-support/pom.xml b/micrometer-support/pom.xml
index c748690459..c66a2d339f 100644
--- a/micrometer-support/pom.xml
+++ b/micrometer-support/pom.xml
@@ -4,7 +4,7 @@
io.javaoperatorsdk
java-operator-sdk
- 5.1.4-SNAPSHOT
+ 5.1.5-SNAPSHOT
micrometer-support
diff --git a/operator-framework-bom/pom.xml b/operator-framework-bom/pom.xml
index 18548eaee3..7770b05ab8 100644
--- a/operator-framework-bom/pom.xml
+++ b/operator-framework-bom/pom.xml
@@ -4,7 +4,7 @@
io.javaoperatorsdk
operator-framework-bom
- 5.1.4-SNAPSHOT
+ 5.1.5-SNAPSHOT
pom
Operator SDK - Bill of Materials
Java SDK for implementing Kubernetes operators
diff --git a/operator-framework-core/pom.xml b/operator-framework-core/pom.xml
index c242becc0a..5b4281a1ec 100644
--- a/operator-framework-core/pom.xml
+++ b/operator-framework-core/pom.xml
@@ -4,7 +4,7 @@
io.javaoperatorsdk
java-operator-sdk
- 5.1.4-SNAPSHOT
+ 5.1.5-SNAPSHOT
../pom.xml
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/health/InformerWrappingEventSourceHealthIndicator.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/health/InformerWrappingEventSourceHealthIndicator.java
index 2c337f3cd7..720ce0227c 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/health/InformerWrappingEventSourceHealthIndicator.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/health/InformerWrappingEventSourceHealthIndicator.java
@@ -11,11 +11,8 @@ public interface InformerWrappingEventSourceHealthIndicator i.getStatus() != Status.HEALTHY)
- .findAny();
-
- return nonUp.isPresent() ? Status.UNHEALTHY : Status.HEALTHY;
+ var hasNonHealthy =
+ informerHealthIndicators().values().stream().anyMatch(i -> i.getStatus() != Status.HEALTHY);
+ return hasNonHealthy ? Status.UNHEALTHY : Status.HEALTHY;
}
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/PodTemplateSpecSanitizer.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/PodTemplateSpecSanitizer.java
index 962059961e..fd1dcff49c 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/PodTemplateSpecSanitizer.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/PodTemplateSpecSanitizer.java
@@ -124,7 +124,6 @@ private static void sanitizeQuantities(
"resources",
quantityPath))
.map(Map.class::cast)
- .filter(m -> m.size() == desiredResource.size())
.ifPresent(
m ->
actualResource.forEach(
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java
index 8e0293f3b4..90bdc93979 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java
@@ -1,10 +1,12 @@
package io.javaoperatorsdk.operator.processing.event;
import java.lang.reflect.InvocationTargetException;
+import java.net.HttpURLConnection;
import java.util.function.Function;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.slf4j.event.Level;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.KubernetesResourceList;
@@ -216,12 +218,35 @@ public boolean isLastAttempt() {
customResourceFacade.patchStatus(
errorStatusUpdateControl.getResource().orElseThrow(), originalResource);
} catch (Exception ex) {
- log.error(
- "updateErrorStatus failed for resource: {} with version: {} for error {}",
- getUID(resource),
- getVersion(resource),
- e.getMessage(),
- ex);
+ int code = ex instanceof KubernetesClientException kcex ? kcex.getCode() : -1;
+ Level exceptionLevel = Level.ERROR;
+ String failedMessage = "";
+ if (context.isNextReconciliationImminent()
+ || !(errorStatusUpdateControl.isNoRetry() || retryInfo.isLastAttempt())) {
+ if (code == HttpURLConnection.HTTP_CONFLICT
+ || (originalResource.getMetadata().getResourceVersion() != null && code == 422)) {
+ exceptionLevel = Level.DEBUG;
+ failedMessage = " due to conflict";
+ log.info(
+ "ErrorStatusUpdateControl.patchStatus of {} failed due to a conflict, but the next"
+ + " reconciliation is imminent.",
+ ResourceID.fromResource(originalResource));
+ } else {
+ exceptionLevel = Level.WARN;
+ failedMessage = ", but will be retried soon,";
+ }
+ }
+
+ log.atLevel(exceptionLevel)
+ .log(
+ "ErrorStatusUpdateControl.patchStatus failed{} for {} with UID: {} and version: {}"
+ + " for error {}",
+ failedMessage,
+ ResourceID.fromResource(originalResource),
+ getUID(resource),
+ getVersion(resource),
+ e.getMessage(),
+ ex);
}
}
if (errorStatusUpdateControl.isNoRetry()) {
@@ -478,7 +503,6 @@ public R patchStatus(R resource, R originalResource) {
}
}
- @SuppressWarnings("unchecked")
private R editStatus(R resource, R originalResource) {
String resourceVersion = resource.getMetadata().getResourceVersion();
// the cached resource should not be changed in any circumstances
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerWrapper.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerWrapper.java
index c07ffdbf46..2bb6dcbc75 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerWrapper.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerWrapper.java
@@ -204,8 +204,8 @@ public boolean isRunning() {
public Status getStatus() {
var status = isRunning() && hasSynced() && isWatching() ? Status.HEALTHY : Status.UNHEALTHY;
log.debug(
- "Informer status: {} for for type: {}, namespace: {}, details[ is running: {}, has synced:"
- + " {}, is watching: {} ]",
+ "Informer status: {} for type: {}, namespace: {}, details [is running: {}, has synced: {},"
+ + " is watching: {}]",
status,
informer.getApiTypeClass().getSimpleName(),
namespaceIdentifier,
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/timer/TimerEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/timer/TimerEventSource.java
index 53c0d328a8..a6801b5f39 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/timer/TimerEventSource.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/timer/TimerEventSource.java
@@ -10,6 +10,7 @@
import org.slf4j.LoggerFactory;
import io.fabric8.kubernetes.api.model.HasMetadata;
+import io.javaoperatorsdk.operator.health.Status;
import io.javaoperatorsdk.operator.processing.event.Event;
import io.javaoperatorsdk.operator.processing.event.ResourceID;
import io.javaoperatorsdk.operator.processing.event.source.AbstractEventSource;
@@ -77,6 +78,11 @@ public void stop() {
}
}
+ @Override
+ public Status getStatus() {
+ return isRunning() ? Status.HEALTHY : Status.UNHEALTHY;
+ }
+
@Override
public Set getSecondaryResources(HasMetadata primary) {
return Set.of();
diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/OperatorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/OperatorIT.java
similarity index 99%
rename from operator-framework-core/src/test/java/io/javaoperatorsdk/operator/OperatorTest.java
rename to operator-framework-core/src/test/java/io/javaoperatorsdk/operator/OperatorIT.java
index 39fc98f6b0..42309d5c44 100644
--- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/OperatorTest.java
+++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/OperatorIT.java
@@ -15,7 +15,8 @@
import static org.junit.jupiter.api.Assertions.*;
@SuppressWarnings("rawtypes")
-class OperatorTest {
+class OperatorIT {
+
@Test
void shouldBePossibleToRetrieveNumberOfRegisteredControllers() {
final var operator = new Operator();
diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/PodTemplateSpecSanitizerTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/PodTemplateSpecSanitizerTest.java
index 091a1a666c..e7756b45bc 100644
--- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/PodTemplateSpecSanitizerTest.java
+++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/PodTemplateSpecSanitizerTest.java
@@ -132,19 +132,6 @@ void testSanitizePodTemplateSpec_whenResourceIsNull_doNothing() {
verifyNoInteractions(actualMap);
}
- @Test
- void testSanitizeResourceRequirements_whenResourceSizeMismatch_doNothing() {
- final var actualMap =
- sanitizeRequestsAndLimits(
- ContainerType.CONTAINER,
- Map.of("cpu", new Quantity("2")),
- Map.of(),
- Map.of("cpu", new Quantity("4")),
- Map.of("cpu", new Quantity("4"), "memory", new Quantity("4Gi")));
- assertContainerResources(actualMap, "requests").hasSize(1).containsEntry("cpu", "2");
- assertContainerResources(actualMap, "limits").hasSize(1).containsEntry("cpu", "4");
- }
-
@Test
void testSanitizeResourceRequirements_whenResourceKeyMismatch_doNothing() {
final var actualMap =
@@ -187,6 +174,34 @@ void testSanitizePodTemplateSpec_whenResourcesHaveNumericalAmountMismatch_doNoth
assertInitContainerResources(actualMap, "limits").hasSize(1).containsEntry("cpu", "2");
}
+ @Test
+ void
+ testSanitizePodTemplateSpec_whenResourcesHaveNumericalAmountMismatch_withEphemeralStorageAddedByOtherOperator_doNothing() {
+ // mimics an environment like GKE Autopilot that enforces ephemeral-storage requests and limits
+ final var actualMap =
+ sanitizeRequestsAndLimits(
+ ContainerType.INIT_CONTAINER,
+ Map.of(
+ "cpu",
+ new Quantity("2"),
+ "memory",
+ new Quantity("4Gi"),
+ "ephemeral-storage",
+ new Quantity("1Gi")),
+ Map.of("cpu", new Quantity("4"), "memory", new Quantity("4Ti")),
+ Map.of("cpu", new Quantity("2"), "ephemeral-storage", new Quantity("1Gi")),
+ Map.of("cpu", new Quantity("4000m")));
+ assertInitContainerResources(actualMap, "requests")
+ .hasSize(3)
+ .containsEntry("cpu", "2")
+ .containsEntry("memory", "4Gi")
+ .containsEntry("ephemeral-storage", "1Gi");
+ assertInitContainerResources(actualMap, "limits")
+ .hasSize(2)
+ .containsEntry("cpu", "2")
+ .containsEntry("ephemeral-storage", "1Gi");
+ }
+
@Test
void
testSanitizeResourceRequirements_whenResourcesHaveAmountAndFormatMismatchWithSameNumericalAmount_thenSanitizeActualMap() {
@@ -204,6 +219,34 @@ void testSanitizePodTemplateSpec_whenResourcesHaveNumericalAmountMismatch_doNoth
assertContainerResources(actualMap, "limits").hasSize(1).containsEntry("cpu", "4000m");
}
+ @Test
+ void
+ testSanitizeResourceRequirements_whenResourcesHaveAmountAndFormatMismatchWithSameNumericalAmount_withEphemeralStorageAddedByOtherOperator_thenSanitizeActualMap() {
+ // mimics an environment like GKE Autopilot that enforces ephemeral-storage requests and limits
+ final var actualMap =
+ sanitizeRequestsAndLimits(
+ ContainerType.CONTAINER,
+ Map.of(
+ "cpu",
+ new Quantity("2"),
+ "memory",
+ new Quantity("4Gi"),
+ "ephemeral-storage",
+ new Quantity("1Gi")),
+ Map.of("cpu", new Quantity("2000m"), "memory", new Quantity("4096Mi")),
+ Map.of("cpu", new Quantity("4"), "ephemeral-storage", new Quantity("1Gi")),
+ Map.of("cpu", new Quantity("4000m")));
+ assertContainerResources(actualMap, "requests")
+ .hasSize(3)
+ .containsEntry("cpu", "2000m")
+ .containsEntry("memory", "4096Mi")
+ .containsEntry("ephemeral-storage", "1Gi");
+ assertContainerResources(actualMap, "limits")
+ .hasSize(2)
+ .containsEntry("cpu", "4000m")
+ .containsEntry("ephemeral-storage", "1Gi");
+ }
+
@Test
void testSanitizePodTemplateSpec_whenEnvVarsIsEmpty_doNothing() {
final var template =
diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcherTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcherTest.java
index c339e5ebf6..e441516d46 100644
--- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcherTest.java
+++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcherTest.java
@@ -30,7 +30,7 @@
class SSABasedGenericKubernetesResourceMatcherTest {
- private final Context> mockedContext = mock();
+ private final Context mockedContext = mock();
private final SSABasedGenericKubernetesResourceMatcher matcher =
SSABasedGenericKubernetesResourceMatcher.getInstance();
@@ -325,8 +325,8 @@ void testSanitizeState_daemonSet_withResourceTypeMismatch() {
void testCustomMatcher_returnsExpectedMatchBasedOnReadOnlyLabel(boolean readOnly) {
var dr = new ConfigMapDR();
dr.configureWith(
- new KubernetesDependentResourceConfigBuilder()
- .withSSAMatcher(new ReadOnlyAwareMatcher())
+ new KubernetesDependentResourceConfigBuilder()
+ .withSSAMatcher(new ReadOnlyAwareMatcher<>())
.build());
var desiredConfigMap =
loadResource("configmap.empty-owner-reference-desired.yaml", ConfigMap.class);
@@ -334,14 +334,8 @@ void testCustomMatcher_returnsExpectedMatchBasedOnReadOnlyLabel(boolean readOnly
var actualConfigMap = loadResource("configmap.empty-owner-reference.yaml", ConfigMap.class);
actualConfigMap.getMetadata().getLabels().put("readonly", Boolean.toString(readOnly));
- ConfigMap ignoredPrimary = null;
- assertThat(
- dr.match(
- actualConfigMap,
- desiredConfigMap,
- ignoredPrimary,
- (Context) mockedContext)
- .matched())
+ HasMetadata primary = mock();
+ assertThat(dr.match(actualConfigMap, desiredConfigMap, primary, mockedContext).matched())
.isEqualTo(readOnly);
}
@@ -391,7 +385,7 @@ private static R loadResource(String fileName, Class clazz) {
clazz, SSABasedGenericKubernetesResourceMatcherTest.class, fileName);
}
- private static class ConfigMapDR extends KubernetesDependentResource {
+ private static class ConfigMapDR extends KubernetesDependentResource {
public ConfigMapDR() {
super(ConfigMap.class);
}
diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/polling/PollingEventSourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/polling/PollingEventSourceTest.java
index 0f7d26446d..448537e9aa 100644
--- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/polling/PollingEventSourceTest.java
+++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/polling/PollingEventSourceTest.java
@@ -87,7 +87,7 @@ void propagatesEventOnNewResourceForPrimary() throws InterruptedException {
}
@Test
- void updatesHealthIndicatorBasedOnExceptionsInFetcher() throws InterruptedException {
+ void updatesHealthIndicatorBasedOnExceptionsInFetcher() {
when(resourceFetcher.fetchResources()).thenReturn(testResponseWithOneValue());
pollingEventSource.start();
assertThat(pollingEventSource.getStatus()).isEqualTo(Status.HEALTHY);
diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/timer/TimerEventSourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/timer/TimerEventSourceTest.java
index 9396411777..825bc6adab 100644
--- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/timer/TimerEventSourceTest.java
+++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/timer/TimerEventSourceTest.java
@@ -1,6 +1,5 @@
package io.javaoperatorsdk.operator.processing.event.source.timer;
-import java.io.IOException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
@@ -12,6 +11,7 @@
import org.junit.jupiter.api.Test;
import io.javaoperatorsdk.operator.TestUtils;
+import io.javaoperatorsdk.operator.health.Status;
import io.javaoperatorsdk.operator.processing.event.Event;
import io.javaoperatorsdk.operator.processing.event.EventHandler;
import io.javaoperatorsdk.operator.processing.event.ResourceID;
@@ -42,6 +42,7 @@ public void schedulesOnce() {
untilAsserted(() -> assertThat(eventHandler.events).hasSize(1));
untilAsserted(PERIOD * 2, 0, () -> assertThat(eventHandler.events).hasSize(1));
+ assertThat(source.getStatus()).isEqualTo(Status.HEALTHY);
}
@Test
@@ -52,6 +53,7 @@ public void canCancelOnce() {
source.cancelOnceSchedule(resourceID);
untilAsserted(() -> assertThat(eventHandler.events).isEmpty());
+ assertThat(source.getStatus()).isEqualTo(Status.HEALTHY);
}
@Test
@@ -62,6 +64,7 @@ public void canRescheduleOnceEvent() {
source.scheduleOnce(resourceID, 2 * PERIOD);
untilAsserted(PERIOD * 2, PERIOD, () -> assertThat(eventHandler.events).hasSize(1));
+ assertThat(source.getStatus()).isEqualTo(Status.HEALTHY);
}
@Test
@@ -72,23 +75,29 @@ public void deRegistersOnceEventSources() {
source.onResourceDeleted(customResource);
untilAsserted(() -> assertThat(eventHandler.events).isEmpty());
+ assertThat(source.getStatus()).isEqualTo(Status.HEALTHY);
}
@Test
- public void eventNotRegisteredIfStopped() throws IOException {
+ public void eventNotRegisteredIfStopped() {
var resourceID = ResourceID.fromResource(TestUtils.testCustomResource());
+ assertThat(source.getStatus()).isEqualTo(Status.HEALTHY);
source.stop();
assertThatExceptionOfType(IllegalStateException.class)
.isThrownBy(() -> source.scheduleOnce(resourceID, PERIOD));
+ assertThat(source.getStatus()).isEqualTo(Status.UNHEALTHY);
}
@Test
- public void eventNotFiredIfStopped() throws IOException {
+ public void eventNotFiredIfStopped() {
source.scheduleOnce(ResourceID.fromResource(TestUtils.testCustomResource()), PERIOD);
+ assertThat(source.getStatus()).isEqualTo(Status.HEALTHY);
+
source.stop();
untilAsserted(() -> assertThat(eventHandler.events).isEmpty());
+ assertThat(source.getStatus()).isEqualTo(Status.UNHEALTHY);
}
private void untilAsserted(ThrowingRunnable assertion) {
diff --git a/operator-framework-junit5/pom.xml b/operator-framework-junit5/pom.xml
index 37c52d11c5..5aeeb92d45 100644
--- a/operator-framework-junit5/pom.xml
+++ b/operator-framework-junit5/pom.xml
@@ -4,7 +4,7 @@
io.javaoperatorsdk
java-operator-sdk
- 5.1.4-SNAPSHOT
+ 5.1.5-SNAPSHOT
operator-framework-junit-5
diff --git a/operator-framework-junit5/src/test/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtensionTest.java b/operator-framework-junit5/src/test/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtensionIT.java
similarity index 95%
rename from operator-framework-junit5/src/test/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtensionTest.java
rename to operator-framework-junit5/src/test/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtensionIT.java
index 04ac7a91ae..e195ce8406 100644
--- a/operator-framework-junit5/src/test/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtensionTest.java
+++ b/operator-framework-junit5/src/test/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtensionIT.java
@@ -9,7 +9,7 @@
import static org.junit.jupiter.api.Assertions.*;
-class LocallyRunOperatorExtensionTest {
+class LocallyRunOperatorExtensionIT {
@Test
void getAdditionalCRDsFromFiles() {
diff --git a/operator-framework/pom.xml b/operator-framework/pom.xml
index cb26ce74c6..f1a100eb75 100644
--- a/operator-framework/pom.xml
+++ b/operator-framework/pom.xml
@@ -4,7 +4,7 @@
io.javaoperatorsdk
java-operator-sdk
- 5.1.4-SNAPSHOT
+ 5.1.5-SNAPSHOT
operator-framework
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ssaissue/finalizer/SSAFinalizerIssueCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ssaissue/finalizer/SSAFinalizerIssueCustomResource.java
new file mode 100644
index 0000000000..0e31be2dfb
--- /dev/null
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ssaissue/finalizer/SSAFinalizerIssueCustomResource.java
@@ -0,0 +1,13 @@
+package io.javaoperatorsdk.operator.baseapi.ssaissue.finalizer;
+
+import io.fabric8.kubernetes.api.model.Namespaced;
+import io.fabric8.kubernetes.client.CustomResource;
+import io.fabric8.kubernetes.model.annotation.Group;
+import io.fabric8.kubernetes.model.annotation.ShortNames;
+import io.fabric8.kubernetes.model.annotation.Version;
+
+@Group("sample.javaoperatorsdk")
+@Version("v1")
+@ShortNames("ssfi")
+public class SSAFinalizerIssueCustomResource
+ extends CustomResource implements Namespaced {}
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ssaissue/finalizer/SSAFinalizerIssueIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ssaissue/finalizer/SSAFinalizerIssueIT.java
new file mode 100644
index 0000000000..a830675518
--- /dev/null
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ssaissue/finalizer/SSAFinalizerIssueIT.java
@@ -0,0 +1,66 @@
+package io.javaoperatorsdk.operator.baseapi.ssaissue.finalizer;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
+import io.fabric8.kubernetes.client.dsl.base.PatchContext;
+import io.fabric8.kubernetes.client.dsl.base.PatchType;
+import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.await;
+
+class SSAFinalizerIssueIT {
+
+ public static final String TEST_1 = "test1";
+
+ @RegisterExtension
+ LocallyRunOperatorExtension extension =
+ LocallyRunOperatorExtension.builder()
+ .withReconciler(new SSAFinalizerIssueReconciler())
+ .build();
+
+ /**
+ * Showcases possibly a case with SSA: When the resource is created with the same field manager
+ * that used by the controller, when adding a finalizer, it deletes other parts of the spec.
+ */
+ @Test
+ void addingFinalizerRemoveListValues() {
+ var fieldManager =
+ extension
+ .getRegisteredControllerForReconcile(SSAFinalizerIssueReconciler.class)
+ .getConfiguration()
+ .fieldManager();
+
+ extension
+ .getKubernetesClient()
+ .resource(testResource())
+ .inNamespace(extension.getNamespace())
+ .patch(
+ new PatchContext.Builder()
+ .withFieldManager(fieldManager)
+ .withForce(true)
+ .withPatchType(PatchType.SERVER_SIDE_APPLY)
+ .build());
+
+ await()
+ .untilAsserted(
+ () -> {
+ var actual = extension.get(SSAFinalizerIssueCustomResource.class, TEST_1);
+ assertThat(actual.getFinalizers()).hasSize(1);
+ assertThat(actual.getSpec()).isNull();
+ });
+ }
+
+ SSAFinalizerIssueCustomResource testResource() {
+ var res = new SSAFinalizerIssueCustomResource();
+ res.setMetadata(new ObjectMetaBuilder().withName(TEST_1).build());
+ res.setSpec(new SSAFinalizerIssueSpec());
+ res.getSpec().setValue("val");
+ res.getSpec().setList(List.of("val1", "val2"));
+ return res;
+ }
+}
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ssaissue/finalizer/SSAFinalizerIssueReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ssaissue/finalizer/SSAFinalizerIssueReconciler.java
new file mode 100644
index 0000000000..441819834a
--- /dev/null
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ssaissue/finalizer/SSAFinalizerIssueReconciler.java
@@ -0,0 +1,26 @@
+package io.javaoperatorsdk.operator.baseapi.ssaissue.finalizer;
+
+import io.javaoperatorsdk.operator.api.reconciler.Cleaner;
+import io.javaoperatorsdk.operator.api.reconciler.Context;
+import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
+import io.javaoperatorsdk.operator.api.reconciler.DeleteControl;
+import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
+import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
+
+@ControllerConfiguration
+public class SSAFinalizerIssueReconciler
+ implements Reconciler,
+ Cleaner {
+
+ @Override
+ public DeleteControl cleanup(
+ SSAFinalizerIssueCustomResource resource, Context context) {
+ return DeleteControl.defaultDelete();
+ }
+
+ @Override
+ public UpdateControl reconcile(
+ SSAFinalizerIssueCustomResource resource, Context context) {
+ return UpdateControl.noUpdate();
+ }
+}
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ssaissue/finalizer/SSAFinalizerIssueSpec.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ssaissue/finalizer/SSAFinalizerIssueSpec.java
new file mode 100644
index 0000000000..d1c9dd5966
--- /dev/null
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ssaissue/finalizer/SSAFinalizerIssueSpec.java
@@ -0,0 +1,26 @@
+package io.javaoperatorsdk.operator.baseapi.ssaissue.finalizer;
+
+import java.util.List;
+
+public class SSAFinalizerIssueSpec {
+
+ private String value;
+
+ private List list = null;
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public List getList() {
+ return list;
+ }
+
+ public void setList(List list) {
+ this.list = list;
+ }
+}
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ssaissue/finalizer/SSAFinalizerIssueStatus.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ssaissue/finalizer/SSAFinalizerIssueStatus.java
new file mode 100644
index 0000000000..471372af40
--- /dev/null
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ssaissue/finalizer/SSAFinalizerIssueStatus.java
@@ -0,0 +1,19 @@
+package io.javaoperatorsdk.operator.baseapi.ssaissue.finalizer;
+
+public class SSAFinalizerIssueStatus {
+
+ private String configMapStatus;
+
+ public String getConfigMapStatus() {
+ return configMapStatus;
+ }
+
+ public void setConfigMapStatus(String configMapStatus) {
+ this.configMapStatus = configMapStatus;
+ }
+
+ @Override
+ public String toString() {
+ return "TestCustomResourceStatus{" + "configMapStatus='" + configMapStatus + '\'' + '}';
+ }
+}
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ssaissue/specupdate/SSASpecUpdateCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ssaissue/specupdate/SSASpecUpdateCustomResource.java
new file mode 100644
index 0000000000..c4ee2dd1a5
--- /dev/null
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ssaissue/specupdate/SSASpecUpdateCustomResource.java
@@ -0,0 +1,14 @@
+package io.javaoperatorsdk.operator.baseapi.ssaissue.specupdate;
+
+import io.fabric8.kubernetes.api.model.Namespaced;
+import io.fabric8.kubernetes.client.CustomResource;
+import io.fabric8.kubernetes.model.annotation.Group;
+import io.fabric8.kubernetes.model.annotation.ShortNames;
+import io.fabric8.kubernetes.model.annotation.Version;
+
+@Group("sample.javaoperatorsdk")
+@Version("v1")
+@ShortNames("ssul")
+public class SSASpecUpdateCustomResource
+ extends CustomResource
+ implements Namespaced {}
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ssaissue/specupdate/SSASpecUpdateCustomResourceSpec.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ssaissue/specupdate/SSASpecUpdateCustomResourceSpec.java
new file mode 100644
index 0000000000..47953ccce0
--- /dev/null
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ssaissue/specupdate/SSASpecUpdateCustomResourceSpec.java
@@ -0,0 +1,14 @@
+package io.javaoperatorsdk.operator.baseapi.ssaissue.specupdate;
+
+public class SSASpecUpdateCustomResourceSpec {
+
+ private String value;
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+}
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ssaissue/specupdate/SSASpecUpdateCustomResourceStatus.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ssaissue/specupdate/SSASpecUpdateCustomResourceStatus.java
new file mode 100644
index 0000000000..e5f103dc69
--- /dev/null
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ssaissue/specupdate/SSASpecUpdateCustomResourceStatus.java
@@ -0,0 +1,15 @@
+package io.javaoperatorsdk.operator.baseapi.ssaissue.specupdate;
+
+public class SSASpecUpdateCustomResourceStatus {
+
+ private Integer value = 0;
+
+ public Integer getValue() {
+ return value;
+ }
+
+ public SSASpecUpdateCustomResourceStatus setValue(Integer value) {
+ this.value = value;
+ return this;
+ }
+}
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ssaissue/specupdate/SSASpecUpdateIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ssaissue/specupdate/SSASpecUpdateIT.java
new file mode 100644
index 0000000000..9cccd32e3c
--- /dev/null
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ssaissue/specupdate/SSASpecUpdateIT.java
@@ -0,0 +1,41 @@
+package io.javaoperatorsdk.operator.baseapi.ssaissue.specupdate;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
+import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.await;
+
+class SSASpecUpdateIT {
+
+ public static final String TEST_RESOURCE_NAME = "test";
+
+ @RegisterExtension
+ LocallyRunOperatorExtension operator =
+ LocallyRunOperatorExtension.builder().withReconciler(SSASpecUpdateReconciler.class).build();
+
+ // showcases that if the spec of the resources is updated with SSA, but the finalizer
+ // is not explicitly added to the fresh resource, the update removes the finalizer
+ @Test
+ void showFinalizerRemovalWhenSpecUpdated() {
+ SSASpecUpdateCustomResource res = createResource();
+ operator.create(res);
+
+ await()
+ .untilAsserted(
+ () -> {
+ var actual = operator.get(SSASpecUpdateCustomResource.class, TEST_RESOURCE_NAME);
+ assertThat(actual.getSpec()).isNotNull();
+ assertThat(actual.getFinalizers()).isEmpty();
+ });
+ }
+
+ SSASpecUpdateCustomResource createResource() {
+ SSASpecUpdateCustomResource res = new SSASpecUpdateCustomResource();
+ res.setMetadata(new ObjectMetaBuilder().withName(TEST_RESOURCE_NAME).build());
+ return res;
+ }
+}
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ssaissue/specupdate/SSASpecUpdateReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ssaissue/specupdate/SSASpecUpdateReconciler.java
new file mode 100644
index 0000000000..483060ad59
--- /dev/null
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ssaissue/specupdate/SSASpecUpdateReconciler.java
@@ -0,0 +1,47 @@
+package io.javaoperatorsdk.operator.baseapi.ssaissue.specupdate;
+
+import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
+import io.javaoperatorsdk.operator.api.reconciler.Cleaner;
+import io.javaoperatorsdk.operator.api.reconciler.Context;
+import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
+import io.javaoperatorsdk.operator.api.reconciler.DeleteControl;
+import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
+import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
+
+@ControllerConfiguration
+public class SSASpecUpdateReconciler
+ implements Reconciler, Cleaner {
+
+ @Override
+ public UpdateControl reconcile(
+ SSASpecUpdateCustomResource resource, Context context) {
+
+ var copy = createFreshCopy(resource);
+ copy.getSpec().setValue("value");
+ context
+ .getClient()
+ .resource(copy)
+ .fieldManager(context.getControllerConfiguration().fieldManager())
+ .serverSideApply();
+
+ return UpdateControl.noUpdate();
+ }
+
+ SSASpecUpdateCustomResource createFreshCopy(SSASpecUpdateCustomResource resource) {
+ var res = new SSASpecUpdateCustomResource();
+ res.setMetadata(
+ new ObjectMetaBuilder()
+ .withName(resource.getMetadata().getName())
+ .withNamespace(resource.getMetadata().getNamespace())
+ .build());
+ res.setSpec(new SSASpecUpdateCustomResourceSpec());
+ return res;
+ }
+
+ @Override
+ public DeleteControl cleanup(
+ SSASpecUpdateCustomResource resource, Context context) {
+
+ return DeleteControl.defaultDelete();
+ }
+}
diff --git a/pom.xml b/pom.xml
index 6a99236079..ca4dd282d6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
io.javaoperatorsdk
java-operator-sdk
- 5.1.4-SNAPSHOT
+ 5.1.5-SNAPSHOT
pom
Operator SDK for Java
Java SDK for implementing Kubernetes operators
@@ -63,8 +63,8 @@
3.27.6
4.3.0
2.7.3
- 1.15.4
- 3.2.2
+ 1.15.5
+ 3.2.3
0.9.14
2.20.0
4.16
@@ -279,7 +279,7 @@
me.fabriciorby
maven-surefire-junit5-tree-reporter
- 1.4.0
+ 1.5.1
@@ -344,6 +344,7 @@
+ 1.28.0
true
diff --git a/sample-operators/controller-namespace-deletion/pom.xml b/sample-operators/controller-namespace-deletion/pom.xml
index 3fd18e45d7..bb2a8fe099 100644
--- a/sample-operators/controller-namespace-deletion/pom.xml
+++ b/sample-operators/controller-namespace-deletion/pom.xml
@@ -5,7 +5,7 @@
io.javaoperatorsdk
sample-operators
- 5.1.4-SNAPSHOT
+ 5.1.5-SNAPSHOT
sample-controller-namespace-deletion
diff --git a/sample-operators/leader-election/pom.xml b/sample-operators/leader-election/pom.xml
index 87b85fb312..23873c5d45 100644
--- a/sample-operators/leader-election/pom.xml
+++ b/sample-operators/leader-election/pom.xml
@@ -5,7 +5,7 @@
io.javaoperatorsdk
sample-operators
- 5.1.4-SNAPSHOT
+ 5.1.5-SNAPSHOT
sample-leader-election
diff --git a/sample-operators/mysql-schema/pom.xml b/sample-operators/mysql-schema/pom.xml
index 5ac2a62d85..8201c1148e 100644
--- a/sample-operators/mysql-schema/pom.xml
+++ b/sample-operators/mysql-schema/pom.xml
@@ -5,7 +5,7 @@
io.javaoperatorsdk
sample-operators
- 5.1.4-SNAPSHOT
+ 5.1.5-SNAPSHOT
sample-mysql-schema-operator
diff --git a/sample-operators/pom.xml b/sample-operators/pom.xml
index 508d420ce4..c19bb7f3f6 100644
--- a/sample-operators/pom.xml
+++ b/sample-operators/pom.xml
@@ -5,7 +5,7 @@
io.javaoperatorsdk
java-operator-sdk
- 5.1.4-SNAPSHOT
+ 5.1.5-SNAPSHOT
sample-operators
diff --git a/sample-operators/tomcat-operator/pom.xml b/sample-operators/tomcat-operator/pom.xml
index e3620b5fa8..0c43071f16 100644
--- a/sample-operators/tomcat-operator/pom.xml
+++ b/sample-operators/tomcat-operator/pom.xml
@@ -5,7 +5,7 @@
io.javaoperatorsdk
sample-operators
- 5.1.4-SNAPSHOT
+ 5.1.5-SNAPSHOT
sample-tomcat-operator
diff --git a/sample-operators/webpage/pom.xml b/sample-operators/webpage/pom.xml
index 8acea5adcb..55eafa8490 100644
--- a/sample-operators/webpage/pom.xml
+++ b/sample-operators/webpage/pom.xml
@@ -5,7 +5,7 @@
io.javaoperatorsdk
sample-operators
- 5.1.4-SNAPSHOT
+ 5.1.5-SNAPSHOT
sample-webpage-operator