diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 34bc6c7d0f..3fa5655cbc 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: 25 + java-version: 17 cache: 'maven' - name: Check code format run: | diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index 7c9d1d24d0..3d4d2032f6 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -21,14 +21,14 @@ on: jobs: test: runs-on: ubuntu-latest - if: ${{ ( github.event_name == 'push' ) || ( github.event_name == 'pull_request' && github.event.pull_request.head.repo.owner.login == 'java-operator-sdk' ) }} + if: ${{ ( github.event_name == 'push' ) || ( github.event_name == 'pull_request' && github.event.pull_request.head.repo.owner.login == 'operator-framework' ) }} steps: - uses: actions/checkout@v6 - name: Set up Java and Maven uses: actions/setup-java@v5 with: distribution: temurin - java-version: 25 + java-version: 17 cache: 'maven' - name: Cache SonarCloud packages uses: actions/cache@v5 diff --git a/AGENTS.md b/AGENTS.md index 62e19f3b69..2c311dac1d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,7 +25,7 @@ Java Operator SDK is a production-ready framework for building Kubernetes Operat java-operator-sdk/ ├── operator-framework-core/ # Core reconciliation engine and API ├── operator-framework/ # Main operator framework implementation -├── operator-framework-junit5/ # Testing utilities and extensions +├── operator-framework-junit/ # Testing utilities and extensions ├── operator-framework-bom/ # Bill of Materials for dependency management ├── micrometer-support/ # Metrics integration ├── open-telemetry-support/ # Distributed tracing support @@ -121,7 +121,7 @@ java-operator-sdk/ ### Integration Tests -- Use `LocallyRunOperatorExtension` or `OperatorExtension` from `operator-framework-junit5` +- Use `LocallyRunOperatorExtension` or `OperatorExtension` from `operator-framework-junit` - Test against real Kubernetes API (typically via test cluster like minikube or kind) - Suffix with `IT` (e.g., `MyReconcilerIT`) - Located in `src/test/java` diff --git a/CLAUDE.md b/CLAUDE.md index ac534a3109..c317064255 120000 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1 +1 @@ -AGENT.md \ No newline at end of file +AGENTS.md diff --git a/bootstrapper-maven-plugin/pom.xml b/bootstrapper-maven-plugin/pom.xml index c56091f0af..957681e199 100644 --- a/bootstrapper-maven-plugin/pom.xml +++ b/bootstrapper-maven-plugin/pom.xml @@ -22,7 +22,7 @@ io.javaoperatorsdk java-operator-sdk - 999-SNAPSHOT + 5.3.1-SNAPSHOT bootstrapper diff --git a/caffeine-bounded-cache-support/pom.xml b/caffeine-bounded-cache-support/pom.xml index be70ab9a2e..f80c44f235 100644 --- a/caffeine-bounded-cache-support/pom.xml +++ b/caffeine-bounded-cache-support/pom.xml @@ -21,7 +21,7 @@ io.javaoperatorsdk java-operator-sdk - 999-SNAPSHOT + 5.3.1-SNAPSHOT caffeine-bounded-cache-support diff --git a/docs/assets/scss/_variables_project.scss b/docs/assets/scss/_variables_project.scss index 3fba3772a2..e2c9097f6a 100644 --- a/docs/assets/scss/_variables_project.scss +++ b/docs/assets/scss/_variables_project.scss @@ -30,7 +30,7 @@ $link-hover-color: darken($link-color, 15%); // Override link colors only in document content .td-content { - a { + a:not(.btn) { color: $link-color !important; &:hover { diff --git a/docs/content/en/blog/news/read-after-write-consistency.md b/docs/content/en/blog/news/read-after-write-consistency.md index b36b3c90a4..30c072e1c2 100644 --- a/docs/content/en/blog/news/read-after-write-consistency.md +++ b/docs/content/en/blog/news/read-after-write-consistency.md @@ -280,3 +280,10 @@ Covering all the edge cases was quite an effort. Just as a highlight, I'll mention the [last one](https://github.com/operator-framework/java-operator-sdk/issues/3208). Thank you! + +## Related + +- Same initiative in golang [controller-runtime](https://github.com/kubernetes-sigs/controller-runtime/issues/3320) +- [Comparable Resource Versions](https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/5504-comparable-resource-version) in Kubernetes +- [Stale Controller Handling](https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/5647-stale-controller-handling) KEP + diff --git a/docs/content/en/blog/releases/v5-3-release.md b/docs/content/en/blog/releases/v5-3-release.md index 512df03a84..12b9bfd30e 100644 --- a/docs/content/en/blog/releases/v5-3-release.md +++ b/docs/content/en/blog/releases/v5-3-release.md @@ -220,6 +220,11 @@ checks. `reconciliationFinished(..)` is extended with `RetryInfo`. `monitorSizeOf(..)` is removed. +### `ResourceAction` relocated + +`ResourceAction` in `io.javaoperatorsdk.operator.processing.event.source.controller` has been +removed. Use `io.javaoperatorsdk.operator.processing.event.source.ResourceAction` instead. + See the full [migration guide](/docs/migration/v5-3-migration) for details. ## Getting Started diff --git a/docs/content/en/docs/migration/v5-3-migration.md b/docs/content/en/docs/migration/v5-3-migration.md index e3d7c34a9d..08d6bca723 100644 --- a/docs/content/en/docs/migration/v5-3-migration.md +++ b/docs/content/en/docs/migration/v5-3-migration.md @@ -3,6 +3,30 @@ title: Migrating from v5.2 to v5.3 description: Migrating from v5.2 to v5.3 --- +## Automated Migration with OpenRewrite + +You can automatically apply all the migration changes described below using [OpenRewrite](https://docs.openrewrite.org/). +Add the following to your `pom.xml` and run `mvn rewrite:run`: + +```xml + + org.openrewrite.maven + rewrite-maven-plugin + 6.33.0 + + + io.javaoperatorsdk.operator.migration.V5_3Migration + + + + + io.javaoperatorsdk + migration + 5.3.1 + + + +``` ## Rename of JUnit module @@ -48,4 +72,10 @@ The following table shows the relevant method renames: Other changes: - `reconciliationFinished(..)` method is extended with `RetryInfo` -- `monitorSizeOf(..)` method is removed. \ No newline at end of file +- `monitorSizeOf(..)` method is removed. + +## ResourceAction relocation + +The `ResourceAction` enum has been removed from +`io.javaoperatorsdk.operator.processing.event.source.controller` use the one in package +`io.javaoperatorsdk.operator.processing.event.source.ResourceAction`; thus update your imports accordingly. \ No newline at end of file diff --git a/micrometer-support/pom.xml b/micrometer-support/pom.xml index ae3c4d0be1..d2ad4e0bc0 100644 --- a/micrometer-support/pom.xml +++ b/micrometer-support/pom.xml @@ -21,7 +21,7 @@ io.javaoperatorsdk java-operator-sdk - 999-SNAPSHOT + 5.3.1-SNAPSHOT micrometer-support diff --git a/migration/pom.xml b/migration/pom.xml new file mode 100644 index 0000000000..1976274023 --- /dev/null +++ b/migration/pom.xml @@ -0,0 +1,75 @@ + + + + 4.0.0 + + io.javaoperatorsdk + java-operator-sdk + 5.3.1-SNAPSHOT + + + migration + Operator SDK - Migration Recipes + OpenRewrite migration recipes for Java Operator SDK + + + 8.46.1 + + + + + org.openrewrite + rewrite-java + ${openrewrite.version} + + + org.openrewrite + rewrite-maven + ${openrewrite.version} + + + org.openrewrite + rewrite-test + ${openrewrite.version} + test + + + org.openrewrite + rewrite-java-17 + ${openrewrite.version} + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.assertj + assertj-core + test + + + + diff --git a/migration/src/main/java/io/javaoperatorsdk/operator/migration/RemoveMethodDeclaration.java b/migration/src/main/java/io/javaoperatorsdk/operator/migration/RemoveMethodDeclaration.java new file mode 100644 index 0000000000..33841d5acc --- /dev/null +++ b/migration/src/main/java/io/javaoperatorsdk/operator/migration/RemoveMethodDeclaration.java @@ -0,0 +1,150 @@ +/* + * Copyright Java Operator SDK Authors + * + * Licensed 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 io.javaoperatorsdk.operator.migration; + +import java.util.Objects; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.NlsRewrite; +import org.openrewrite.Option; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; + +public class RemoveMethodDeclaration extends Recipe { + + @Option( + displayName = "Interface name", + description = "Fully qualified or simple name of the interface.", + example = "com.example.YourInterface") + String interfaceName; + + @Option( + displayName = "Method name", + description = "Name of the method to remove.", + example = "removedMethod") + String methodName; + + @Override + public String getDisplayName() { + return "Remove obsolete method from implementing classes"; + } + + @Override + public @NlsRewrite.Description String getDescription() { + return "Remove obsolete method from implementing classes"; + } + + @Override + public TreeVisitor getVisitor() { + return new JavaIsoVisitor() { + + @Override + public J.ClassDeclaration visitClassDeclaration( + J.ClassDeclaration classDecl, ExecutionContext ctx) { + J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, ctx); + + if (cd.getType() == null || !typeMatchesOrImplements(cd.getType())) { + return cd; + } + + // Mutate the type info in place to remove the method from the declared methods list, + // so all AST nodes sharing this type reference stay consistent. + var type = cd.getType(); + if (type instanceof JavaType.Class classType) { + var updatedMethods = + classType.getMethods().stream().filter(m -> !m.getName().equals(methodName)).toList(); + classType.unsafeSet( + classType.getTypeParameters(), + classType.getSupertype(), + classType.getOwningClass(), + classType.getAnnotations(), + classType.getInterfaces(), + classType.getMembers(), + updatedMethods); + } + + return cd; + } + + @Override + public J.MethodDeclaration visitMethodDeclaration( + J.MethodDeclaration method, ExecutionContext ctx) { + if (!method.getSimpleName().equals(methodName)) { + return super.visitMethodDeclaration(method, ctx); + } + + J.ClassDeclaration classDecl = getCursor().firstEnclosing(J.ClassDeclaration.class); + if (classDecl == null || classDecl.getType() == null) { + return super.visitMethodDeclaration(method, ctx); + } + + if (typeMatchesOrImplements(classDecl.getType())) { + //noinspection DataFlowIssue + return null; + } + + return super.visitMethodDeclaration(method, ctx); + } + + private boolean typeMatchesOrImplements(JavaType.FullyQualified type) { + for (var iface : type.getInterfaces()) { + if (iface.getFullyQualifiedName().equals(interfaceName) + || typeMatchesOrImplements(iface)) { + return true; + } + } + var supertype = type.getSupertype(); + if (supertype != null && !supertype.getFullyQualifiedName().equals("java.lang.Object")) { + return typeMatchesOrImplements(supertype); + } + return false; + } + }; + } + + public String getInterfaceName() { + return interfaceName; + } + + public void setInterfaceName(String interfaceName) { + this.interfaceName = interfaceName; + } + + public String getMethodName() { + return methodName; + } + + public void setMethodName(String methodName) { + this.methodName = methodName; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + RemoveMethodDeclaration that = (RemoveMethodDeclaration) o; + return Objects.equals(interfaceName, that.interfaceName) + && Objects.equals(methodName, that.methodName); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), interfaceName, methodName); + } +} diff --git a/migration/src/main/resources/META-INF/rewrite/v5-3-migration.yml b/migration/src/main/resources/META-INF/rewrite/v5-3-migration.yml new file mode 100644 index 0000000000..cab26f1916 --- /dev/null +++ b/migration/src/main/resources/META-INF/rewrite/v5-3-migration.yml @@ -0,0 +1,124 @@ +# +# Copyright Java Operator SDK Authors +# +# Licensed 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. +# + +--- +type: specs.openrewrite.org/v1beta/recipe +name: io.javaoperatorsdk.operator.migration.V5_3Migration +displayName: Migrate to Java Operator SDK v5.3 +description: >- + Migrates Java Operator SDK from v5.2 to v5.3, including the JUnit module + rename and Metrics interface method renames. +recipeList: + - io.javaoperatorsdk.operator.migration.UpgradeJOSDKVersion + - io.javaoperatorsdk.operator.migration.RenameJUnitModule + - io.javaoperatorsdk.operator.migration.MetricsMethodRenames + - io.javaoperatorsdk.operator.migration.RemoveMonitorSizeOf + - io.javaoperatorsdk.operator.migration.RelocateResourceAction +--- +type: specs.openrewrite.org/v1beta/recipe +name: io.javaoperatorsdk.operator.migration.UpgradeJOSDKVersion +displayName: Upgrade Java Operator SDK version to 5.3.0 +description: >- + Upgrades all io.javaoperatorsdk dependencies from 5.2.x to 5.3.0. +recipeList: + - org.openrewrite.maven.UpgradeDependencyVersion: + groupId: io.javaoperatorsdk + artifactId: "*" + newVersion: 5.3.0 + versionPattern: "5.2.*" +--- +type: specs.openrewrite.org/v1beta/recipe +name: io.javaoperatorsdk.operator.migration.RenameJUnitModule +displayName: Rename JUnit module artifact +description: >- + Renames the operator-framework-junit-5 artifact to operator-framework-junit. +recipeList: + - org.openrewrite.maven.ChangeDependencyGroupIdAndArtifactId: + oldGroupId: io.javaoperatorsdk + oldArtifactId: operator-framework-junit-5 + newArtifactId: operator-framework-junit + newVersion: 5.3.0 +--- +type: specs.openrewrite.org/v1beta/recipe +name: io.javaoperatorsdk.operator.migration.MetricsMethodRenames +displayName: Rename Metrics interface methods +description: >- + Renames methods on the Metrics interface to match the new v5.3 API. +recipeList: + - org.openrewrite.java.ChangeMethodName: + methodPattern: "io.javaoperatorsdk.operator.api.monitoring.Metrics reconcileCustomResource(..)" + newMethodName: reconciliationSubmitted + matchOverrides: true + - org.openrewrite.java.ChangeMethodName: + methodPattern: "io.javaoperatorsdk.operator.api.monitoring.Metrics reconciliationExecutionStarted(..)" + newMethodName: reconciliationStarted + matchOverrides: true + - org.openrewrite.java.ChangeMethodName: + methodPattern: "io.javaoperatorsdk.operator.api.monitoring.Metrics reconciliationExecutionFinished(..)" + newMethodName: reconciliationSucceeded + matchOverrides: true + - org.openrewrite.java.AddNullMethodArgument: + methodPattern: "io.javaoperatorsdk.operator.api.monitoring.Metrics failedReconciliation(..)" + argumentIndex: 1 + parameterType: io.javaoperatorsdk.operator.api.reconciler.RetryInfo + - org.openrewrite.java.ChangeMethodName: + methodPattern: "io.javaoperatorsdk.operator.api.monitoring.Metrics failedReconciliation(..)" + newMethodName: reconciliationFailed + matchOverrides: true + - org.openrewrite.java.AddMethodParameter: + methodPattern: "io.javaoperatorsdk.operator.api.monitoring.Metrics reconciliationFailed(..)" + parameterType: io.javaoperatorsdk.operator.api.reconciler.RetryInfo + parameterName: retryInfo + parameterIndex: 1 + - org.openrewrite.java.AddNullMethodArgument: + methodPattern: "io.javaoperatorsdk.operator.api.monitoring.Metrics finishedReconciliation(..)" + argumentIndex: 1 + parameterType: io.javaoperatorsdk.operator.api.reconciler.RetryInfo + - org.openrewrite.java.ChangeMethodName: + methodPattern: "io.javaoperatorsdk.operator.api.monitoring.Metrics finishedReconciliation(..)" + newMethodName: reconciliationFinished + matchOverrides: true + - org.openrewrite.java.AddMethodParameter: + methodPattern: "io.javaoperatorsdk.operator.api.monitoring.Metrics reconciliationFinished(..)" + parameterType: io.javaoperatorsdk.operator.api.reconciler.RetryInfo + parameterName: retryInfo + parameterIndex: 1 + - org.openrewrite.java.ChangeMethodName: + methodPattern: "io.javaoperatorsdk.operator.api.monitoring.Metrics cleanupDoneFor(..)" + newMethodName: cleanupDone + matchOverrides: true + - org.openrewrite.java.ChangeMethodName: + methodPattern: "io.javaoperatorsdk.operator.api.monitoring.Metrics receivedEvent(..)" + newMethodName: eventReceived + matchOverrides: true +--- +type: specs.openrewrite.org/v1beta/recipe +name: io.javaoperatorsdk.operator.migration.RemoveMonitorSizeOf +displayName: Remove MonitorSizeOf +recipeList: + - io.javaoperatorsdk.operator.migration.RemoveMethodDeclaration: + interfaceName: io.javaoperatorsdk.operator.api.monitoring.Metrics + methodName: monitorSizeOf +--- +type: specs.openrewrite.org/v1beta/recipe +name: io.javaoperatorsdk.operator.migration.RelocateResourceAction +displayName: Relocate ResourceAction class +description: >- + Moves ResourceAction import from the controller sub-package to the event source package. +recipeList: + - org.openrewrite.java.ChangeType: + oldFullyQualifiedTypeName: io.javaoperatorsdk.operator.processing.event.source.controller.ResourceAction + newFullyQualifiedTypeName: io.javaoperatorsdk.operator.processing.event.source.ResourceAction diff --git a/migration/src/test/java/io/javaoperatorsdk/operator/migration/V53MigrationTest.java b/migration/src/test/java/io/javaoperatorsdk/operator/migration/V53MigrationTest.java new file mode 100644 index 0000000000..62ab4a412b --- /dev/null +++ b/migration/src/test/java/io/javaoperatorsdk/operator/migration/V53MigrationTest.java @@ -0,0 +1,393 @@ +/* + * Copyright Java Operator SDK Authors + * + * Licensed 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 io.javaoperatorsdk.operator.migration; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; +import static org.openrewrite.maven.Assertions.pomXml; + +class V53MigrationTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipeFromResources("io.javaoperatorsdk.operator.migration.V5_3Migration"); + } + + @Test + void renamesJUnitModuleInMaven() { + rewriteRun( + pomXml( + """ + + 4.0.0 + com.example + test + 1.0 + + + io.javaoperatorsdk + operator-framework-junit-5 + 5.2.0 + test + + + + """, + """ + + 4.0.0 + com.example + test + 1.0 + + + io.javaoperatorsdk + operator-framework-junit + 5.3.0 + test + + + + """)); + } + + @Test + void renamesMetricsMethods() { + rewriteRun( + // language=java + java( + """ + package io.javaoperatorsdk.operator.api.monitoring; + + import java.util.Map; + + public interface Metrics { + default void receivedEvent(Object event, Map metadata) {} + default void reconcileCustomResource(Object resource, Object retryInfo, Map metadata) {} + default void reconciliationExecutionStarted(Object resource, Map metadata) {} + default void reconciliationExecutionFinished(Object resource, Map metadata) {} + default void failedReconciliation(Object resource, Exception exception, Map metadata) {} + default void finishedReconciliation(Object resource, Map metadata) {} + default void cleanupDoneFor(Object resourceID, Map metadata) {} + } + """, + """ + package io.javaoperatorsdk.operator.api.monitoring; + + import io.javaoperatorsdk.operator.api.reconciler.RetryInfo; + + import java.util.Map; + + public interface Metrics { + default void eventReceived(Object event, Map metadata) {} + default void reconciliationSubmitted(Object resource, Object retryInfo, Map metadata) {} + default void reconciliationStarted(Object resource, Map metadata) {} + default void reconciliationSucceeded(Object resource, Map metadata) {} + + default void reconciliationFailed(Object resource, RetryInfo retryInfo, Exception exception, Map metadata) {} + + default void reconciliationFinished(Object resource, RetryInfo retryInfo, Map metadata) {} + default void cleanupDone(Object resourceID, Map metadata) {} + } + """)); + } + + @Test + void renamesMetricsMethodCallsInImplementation() { + rewriteRun( + // Stub for the Metrics interface + // language=java + java( + """ + package io.javaoperatorsdk.operator.api.monitoring; + + import java.util.Map; + + public interface Metrics { + default void receivedEvent(Object event, Map metadata) {} + default void reconcileCustomResource(Object resource, Object retryInfo, Map metadata) {} + } + """, + """ + package io.javaoperatorsdk.operator.api.monitoring; + + import java.util.Map; + + public interface Metrics { + default void eventReceived(Object event, Map metadata) {} + default void reconciliationSubmitted(Object resource, Object retryInfo, Map metadata) {} + } + """), + // Implementation that overrides the old method names + // language=java + java( + """ + package com.example; + + import java.util.Map; + import io.javaoperatorsdk.operator.api.monitoring.Metrics; + + public class MyMetrics implements Metrics { + @Override + public void receivedEvent(Object event, Map metadata) { + System.out.println("event received"); + } + + @Override + public void reconcileCustomResource(Object resource, Object retryInfo, Map metadata) { + System.out.println("reconcile"); + } + } + """, + """ + package com.example; + + import java.util.Map; + import io.javaoperatorsdk.operator.api.monitoring.Metrics; + + public class MyMetrics implements Metrics { + @Override + public void eventReceived(Object event, Map metadata) { + System.out.println("event received"); + } + + @Override + public void reconciliationSubmitted(Object resource, Object retryInfo, Map metadata) { + System.out.println("reconcile"); + } + } + """)); + } + + @Test + void removesMonitorSizeOfFromImplementationWithGenerics() { + rewriteRun( + // Stub for the Metrics interface (unchanged, from JOSDK library) + // language=java + java( + """ + package io.javaoperatorsdk.operator.api.monitoring; + + import java.util.Map; + + public interface Metrics { + default void eventReceived(Object event, Map metadata) {} + default > T monitorSizeOf(T map, String name) { return map; } + } + """), + // Implementation that overrides monitorSizeOf with generic signature + // language=java + java( + """ + package com.example; + + import java.util.Map; + import io.javaoperatorsdk.operator.api.monitoring.Metrics; + + public class MyMetrics implements Metrics { + @Override + public void eventReceived(Object event, Map metadata) { + System.out.println("event"); + } + + @Override + public > T monitorSizeOf(T map, String name) { + System.out.println("monitoring size"); + return map; + } + } + """, + """ + package com.example; + + import java.util.Map; + import io.javaoperatorsdk.operator.api.monitoring.Metrics; + + public class MyMetrics implements Metrics { + @Override + public void eventReceived(Object event, Map metadata) { + System.out.println("event"); + } + } + """), + // Implementation without @Override annotation + // language=java + java( + """ + package com.example; + + import java.util.Map; + import io.javaoperatorsdk.operator.api.monitoring.Metrics; + + public class AnotherMetrics implements Metrics { + public > T monitorSizeOf(T map, String name) { + return map; + } + } + """, + """ + package com.example; + + import java.util.Map; + import io.javaoperatorsdk.operator.api.monitoring.Metrics; + + public class AnotherMetrics implements Metrics { + } + """)); + } + + @Test + void addsRetryInfoParameterToReconciliationFinished() { + rewriteRun( + // language=java + java( + """ + package io.javaoperatorsdk.operator.api.monitoring; + + import java.util.Map; + + public interface Metrics { + default void finishedReconciliation(Object resource, Map metadata) {} + } + """, + """ + package io.javaoperatorsdk.operator.api.monitoring; + + import io.javaoperatorsdk.operator.api.reconciler.RetryInfo; + + import java.util.Map; + + public interface Metrics { + default void reconciliationFinished(Object resource, RetryInfo retryInfo, Map metadata) {} + } + """)); + } + + @Test + void addsNullRetryInfoArgumentToInvocations() { + rewriteRun( + // Stub for the Metrics interface with old method names + // language=java + java( + """ + package io.javaoperatorsdk.operator.api.monitoring; + + import java.util.Map; + + public interface Metrics { + default void failedReconciliation(Object resource, Exception exception, Map metadata) {} + default void finishedReconciliation(Object resource, Map metadata) {} + } + """, + """ + package io.javaoperatorsdk.operator.api.monitoring; + + import io.javaoperatorsdk.operator.api.reconciler.RetryInfo; + + import java.util.Map; + + public interface Metrics { + default void reconciliationFailed(Object resource, RetryInfo retryInfo, Exception exception, Map metadata) {} + + default void reconciliationFinished(Object resource, RetryInfo retryInfo, Map metadata) {} + } + """), + // Stub for RetryInfo + // language=java + java( + """ + package io.javaoperatorsdk.operator.api.reconciler; + public interface RetryInfo {} + """), + // Class that calls the old methods + // language=java + java( + """ + package com.example; + + import java.util.Map; + import io.javaoperatorsdk.operator.api.monitoring.Metrics; + + public class MetricsCaller { + public void report(Metrics metrics, Object resource, Exception ex, Map meta) { + metrics.failedReconciliation(resource, ex, meta); + metrics.finishedReconciliation(resource, meta); + } + } + """, + """ + package com.example; + + import java.util.Map; + import io.javaoperatorsdk.operator.api.monitoring.Metrics; + + public class MetricsCaller { + public void report(Metrics metrics, Object resource, Exception ex, Map meta) { + metrics.reconciliationFailed(resource, null, ex, meta); + metrics.reconciliationFinished(resource, null, meta); + } + } + """)); + } + + @Test + void relocatesResourceActionImport() { + rewriteRun( + // Stub for the old ResourceAction location + // language=java + java( + """ + package io.javaoperatorsdk.operator.processing.event.source.controller; + + public class ResourceAction { + } + """, + """ + package io.javaoperatorsdk.operator.processing.event.source; + + public class ResourceAction { + } + """), + // Class that imports ResourceAction from the old package + // language=java + java( + """ + package com.example; + + import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceAction; + + public class MyHandler { + public void handle(ResourceAction action) { + System.out.println(action); + } + } + """, + """ + package com.example; + + import io.javaoperatorsdk.operator.processing.event.source.ResourceAction; + + public class MyHandler { + public void handle(ResourceAction action) { + System.out.println(action); + } + } + """)); + } +} diff --git a/operator-framework-bom/pom.xml b/operator-framework-bom/pom.xml index 643d000c2f..5ca3705bc1 100644 --- a/operator-framework-bom/pom.xml +++ b/operator-framework-bom/pom.xml @@ -21,7 +21,7 @@ io.javaoperatorsdk operator-framework-bom - 999-SNAPSHOT + 5.3.1-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 2356433ca9..61ff7fd0f7 100644 --- a/operator-framework-core/pom.xml +++ b/operator-framework-core/pom.xml @@ -21,7 +21,7 @@ io.javaoperatorsdk java-operator-sdk - 999-SNAPSHOT + 5.3.1-SNAPSHOT ../pom.xml diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/ManagedInformerEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/ManagedInformerEventSource.java index 978deda333..26543e8322 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/ManagedInformerEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/ManagedInformerEventSource.java @@ -191,8 +191,8 @@ public void handleRecentResourceCreate(ResourceID resourceID, R resource) { @Override public Optional get(ResourceID resourceID) { + // The order of reading from these caches matters Optional resource = temporaryResourceCache.getResourceFromCache(resourceID); - var res = cache.get(resourceID); if (comparableResourceVersions && resource.isPresent() && ReconcilerUtilsInternal.compareResourceVersions( @@ -201,13 +201,16 @@ public Optional get(ResourceID resourceID) { > 0) { log.debug("Latest resource found in temporary cache for Resource ID: {}", resourceID); return resource; + } else { + // this needs to happen after comparison to ensure correctness + var resFromInformer = cache.get(resourceID); + log.debug( + "Resource {} in temp cache; {}found in informer cache" + ", for Resource ID: {}", + resource.isPresent() ? "obsolete" : "not found", + resFromInformer.isPresent() ? "" : "not ", + resourceID); + return resFromInformer; } - log.debug( - "Resource not found, or older, in temporary cache. Found in informer cache {}, for" - + " Resource ID: {}", - res.isPresent(), - resourceID); - return res; } /** diff --git a/operator-framework-junit/pom.xml b/operator-framework-junit/pom.xml index aa18d5c778..5813f9408c 100644 --- a/operator-framework-junit/pom.xml +++ b/operator-framework-junit/pom.xml @@ -21,7 +21,7 @@ io.javaoperatorsdk java-operator-sdk - 999-SNAPSHOT + 5.3.1-SNAPSHOT operator-framework-junit diff --git a/operator-framework/pom.xml b/operator-framework/pom.xml index f94dfa757d..1588590401 100644 --- a/operator-framework/pom.xml +++ b/operator-framework/pom.xml @@ -21,7 +21,7 @@ io.javaoperatorsdk java-operator-sdk - 999-SNAPSHOT + 5.3.1-SNAPSHOT operator-framework diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cachingfilteringupdate/CachingFilteringUpdateReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cachingfilteringupdate/CachingFilteringUpdateReconciler.java index 1bd60eb2c9..0e9a953129 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cachingfilteringupdate/CachingFilteringUpdateReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cachingfilteringupdate/CachingFilteringUpdateReconciler.java @@ -43,19 +43,31 @@ public UpdateControl reconcile( CachingFilteringUpdateCustomResource resource, Context context) { - context.resourceOperations().serverSideApply(prepareCM(resource)); + context.resourceOperations().serverSideApply(prepareCM(resource, 1)); var cachedCM = context.getSecondaryResource(ConfigMap.class); if (cachedCM.isEmpty()) { issueFound.set(true); throw new IllegalStateException("Error for resource: " + ResourceID.fromResource(resource)); } + var updated = context.resourceOperations().serverSideApply(prepareCM(resource, 2)); + cachedCM = context.getSecondaryResource(ConfigMap.class); + if (!cachedCM + .orElseThrow() + .getMetadata() + .getResourceVersion() + .equals(updated.getMetadata().getResourceVersion())) { + issueFound.set(true); + throw new IllegalStateException( + "Update error for resource: " + ResourceID.fromResource(resource)); + } + ensureStatusExists(resource); resource.getStatus().setUpdated(true); return UpdateControl.patchStatus(resource); } - private static ConfigMap prepareCM(CachingFilteringUpdateCustomResource p) { + private static ConfigMap prepareCM(CachingFilteringUpdateCustomResource p, int num) { var cm = new ConfigMapBuilder() .withMetadata( @@ -63,7 +75,7 @@ private static ConfigMap prepareCM(CachingFilteringUpdateCustomResource p) { .withName(p.getMetadata().getName()) .withNamespace(p.getMetadata().getNamespace()) .build()) - .withData(Map.of("name", p.getMetadata().getName())) + .withData(Map.of("name", p.getMetadata().getName(), "num", "" + num)) .build(); cm.addOwnerReference(p); return cm; diff --git a/pom.xml b/pom.xml index 2c420d93ef..d1562cb1a0 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ io.javaoperatorsdk java-operator-sdk - 999-SNAPSHOT + 5.3.1-SNAPSHOT pom Operator SDK for Java Java SDK for implementing Kubernetes operators @@ -54,6 +54,7 @@ caffeine-bounded-cache-support bootstrapper-maven-plugin test-index-processor + migration diff --git a/sample-operators/controller-namespace-deletion/pom.xml b/sample-operators/controller-namespace-deletion/pom.xml index 4cdfe1f2bd..c5bfaa47bc 100644 --- a/sample-operators/controller-namespace-deletion/pom.xml +++ b/sample-operators/controller-namespace-deletion/pom.xml @@ -22,7 +22,7 @@ io.javaoperatorsdk sample-operators - 999-SNAPSHOT + 5.3.1-SNAPSHOT sample-controller-namespace-deletion diff --git a/sample-operators/leader-election/pom.xml b/sample-operators/leader-election/pom.xml index 4f896485d1..ce6c1bad41 100644 --- a/sample-operators/leader-election/pom.xml +++ b/sample-operators/leader-election/pom.xml @@ -22,7 +22,7 @@ io.javaoperatorsdk sample-operators - 999-SNAPSHOT + 5.3.1-SNAPSHOT sample-leader-election diff --git a/sample-operators/metrics-processing/pom.xml b/sample-operators/metrics-processing/pom.xml index 625a58a9de..33d18acbeb 100644 --- a/sample-operators/metrics-processing/pom.xml +++ b/sample-operators/metrics-processing/pom.xml @@ -22,7 +22,7 @@ io.javaoperatorsdk sample-operators - 999-SNAPSHOT + 5.3.1-SNAPSHOT sample-metrics-processing @@ -66,7 +66,7 @@ org.yaml snakeyaml - 2.3 + 2.6 org.apache.logging.log4j diff --git a/sample-operators/mysql-schema/pom.xml b/sample-operators/mysql-schema/pom.xml index 2055a9933d..c4612cf82a 100644 --- a/sample-operators/mysql-schema/pom.xml +++ b/sample-operators/mysql-schema/pom.xml @@ -22,7 +22,7 @@ io.javaoperatorsdk sample-operators - 999-SNAPSHOT + 5.3.1-SNAPSHOT sample-mysql-schema-operator diff --git a/sample-operators/pom.xml b/sample-operators/pom.xml index c8a70b2701..7abd6048a9 100644 --- a/sample-operators/pom.xml +++ b/sample-operators/pom.xml @@ -22,7 +22,7 @@ io.javaoperatorsdk java-operator-sdk - 999-SNAPSHOT + 5.3.1-SNAPSHOT sample-operators diff --git a/sample-operators/tomcat-operator/pom.xml b/sample-operators/tomcat-operator/pom.xml index b7c3b05c98..6d20175521 100644 --- a/sample-operators/tomcat-operator/pom.xml +++ b/sample-operators/tomcat-operator/pom.xml @@ -22,7 +22,7 @@ io.javaoperatorsdk sample-operators - 999-SNAPSHOT + 5.3.1-SNAPSHOT sample-tomcat-operator diff --git a/sample-operators/webpage/pom.xml b/sample-operators/webpage/pom.xml index c50366e37e..f700c27987 100644 --- a/sample-operators/webpage/pom.xml +++ b/sample-operators/webpage/pom.xml @@ -22,7 +22,7 @@ io.javaoperatorsdk sample-operators - 999-SNAPSHOT + 5.3.1-SNAPSHOT sample-webpage-operator diff --git a/test-index-processor/pom.xml b/test-index-processor/pom.xml index 2ae7c5f454..d42c7b912a 100644 --- a/test-index-processor/pom.xml +++ b/test-index-processor/pom.xml @@ -22,7 +22,7 @@ io.javaoperatorsdk java-operator-sdk - 999-SNAPSHOT + 5.3.1-SNAPSHOT test-index-processor