diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b81fa41b6d..e37ab5d8d5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,10 @@ jobs: strategy: matrix: java: [ 17, 21, 24 ] - kubernetes: [ '1.30.12', '1.31.8', '1.32.4','1.33.0' ] + # 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. + kubernetes: [ '1.30.12', '1.31.8', '1.32.4','1.33.1' ] uses: ./.github/workflows/integration-tests.yml with: java-version: ${{ matrix.java }} @@ -35,9 +38,9 @@ jobs: matrix: java: [ 17, 21, 24 ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Java and Maven - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: ${{ matrix.java }} diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index e06b427960..f72a0af43b 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -1,6 +1,6 @@ # Integration and end to end tests which runs locally and deploys the Operator to a Kubernetes # (Minikube) cluster and creates custom resources to verify the operator's functionality -name: Integration & End to End tests +name: End to End tests on: pull_request: paths-ignore: @@ -27,18 +27,21 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Minikube-Kubernetes uses: manusa/actions-setup-minikube@v2.14.0 with: - minikube version: v1.34.0 - kubernetes version: v1.33.0 + minikube version: v1.36.0 + # 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. + kubernetes version: v1.33.1 github token: ${{ secrets.GITHUB_TOKEN }} driver: docker - name: Set up Java and Maven - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 17 distribution: temurin diff --git a/.github/workflows/hugo.yaml b/.github/workflows/hugo.yaml index 511f10a8e0..c1b967616d 100644 --- a/.github/workflows/hugo.yaml +++ b/.github/workflows/hugo.yaml @@ -41,7 +41,7 @@ jobs: - name: Install Dart Sass run: sudo snap install dart-sass - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive fetch-depth: 0 diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 75a6093371..fdb8897c07 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -29,11 +29,11 @@ jobs: continue-on-error: ${{ inputs.experimental }} timeout-minutes: 40 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: ref: ${{ inputs.checkout-ref }} - name: Set up Java and Maven - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: ${{ inputs.java-version }} @@ -41,10 +41,10 @@ jobs: - name: Set up Minikube uses: manusa/actions-setup-minikube@v2.14.0 with: - minikube version: 'v1.34.0' + minikube version: 'v1.36.0' kubernetes version: '${{ inputs.kube-version }}' - driver: 'docker' - github token: ${{ secrets.GITHUB_TOKEN }} + github token: ${{ github.token }} + - name: "${{inputs.it-category}} integration tests (kube: ${{ inputs.kube-version }} / java: ${{ inputs.java-version }} / client: ${{ inputs.http-client }})" run: | if [ -z "${{inputs.it-category}}" ]; then diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 27742bf7c2..df5819f33d 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -17,9 +17,9 @@ jobs: check_format_and_unit_tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Java and Maven - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: 21 diff --git a/.github/workflows/release-project-in-dir.yml b/.github/workflows/release-project-in-dir.yml index dc79b6f6c2..0683b01b1f 100644 --- a/.github/workflows/release-project-in-dir.yml +++ b/.github/workflows/release-project-in-dir.yml @@ -19,16 +19,21 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout "${{inputs.version_branch}}" branch - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: ref: "${{inputs.version_branch}}" - name: Set up Java and Maven - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 17 distribution: temurin cache: 'maven' + server-id: central + server-username: MAVEN_USERNAME + server-password: MAVEN_CENTRAL_TOKEN + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} + gpg-passphrase: MAVEN_GPG_PASSPHRASE - name: Change version to release version # Assume that RELEASE_VERSION will have form like: "v1.0.1". So we cut the "v" @@ -37,15 +42,12 @@ jobs: env: RELEASE_VERSION: ${{ github.event.release.tag_name }} - - name: Release Maven package - uses: samuelmeuli/action-maven-publish@v1 - with: - maven_profiles: "release" - maven_args: ${{ env.MAVEN_ARGS }} - gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} - gpg_passphrase: ${{ secrets.GPG_PASSPHRASE }} - nexus_username: ${{ secrets.OSSRH_USERNAME }} - nexus_password: ${{ secrets.OSSRH_TOKEN }} + - name: Publish to Apache Maven Central + run: mvn package deploy -Prelease + env: + MAVEN_USERNAME: ${{ secrets.NEXUS_USERNAME }} + MAVEN_CENTRAL_TOKEN: ${{ secrets.NEXUS_PASSWORD }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} # This is separate job because there were issues with git after release step, was not able to commit changes. update-working-version: @@ -54,12 +56,12 @@ jobs: if: "!contains(github.event.release.tag_name, 'RC')" # not sure we should keep this the RC part steps: - name: Checkout "${{inputs.version_branch}}" branch - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: ref: "${{inputs.version_branch}}" - name: Set up Java and Maven - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 17 distribution: temurin diff --git a/.github/workflows/snapshot-releases.yml b/.github/workflows/snapshot-releases.yml index 66fe9d25a3..a12d9aaed5 100644 --- a/.github/workflows/snapshot-releases.yml +++ b/.github/workflows/snapshot-releases.yml @@ -16,9 +16,9 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Java and Maven - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: 17 @@ -29,18 +29,22 @@ jobs: runs-on: ubuntu-latest needs: test steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Java and Maven - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: - distribution: temurin java-version: 17 + distribution: temurin cache: 'maven' - - name: Release Maven package - uses: samuelmeuli/action-maven-publish@v1 - with: - maven_profiles: "release" - gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} - gpg_passphrase: ${{ secrets.GPG_PASSPHRASE }} - nexus_username: ${{ secrets.OSSRH_USERNAME }} - nexus_password: ${{ secrets.OSSRH_TOKEN }} + server-id: central + server-username: MAVEN_USERNAME + server-password: MAVEN_CENTRAL_TOKEN + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} + gpg-passphrase: MAVEN_GPG_PASSPHRASE + + - name: Publish to Apache Maven Central + run: mvn package deploy -Prelease + env: + MAVEN_USERNAME: ${{ secrets.NEXUS_USERNAME }} + MAVEN_CENTRAL_TOKEN: ${{ secrets.NEXUS_PASSWORD }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index b7a96edef6..370f36303c 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -23,9 +23,9 @@ jobs: 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' ) }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Java and Maven - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: 17 diff --git a/README.md b/README.md index 86edf232b0..72eb2f7974 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,10 @@ conversion hooks and dynamic admission controllers are supported as a separate p Under the hood it uses the excellent [Fabric8 Kubernetes Client](https://github.com/fabric8io/kubernetes-client), which provides additional facilities, like generating CRD from source code (and vice versa). +Icon + +Java Operator SDK is a CNCF project as part of [Operator Framework](https://github.com/operator-framework). + ## Documentation Documentation can be found on the **[JOSDK WebSite](https://javaoperatorsdk.io/)**. @@ -63,6 +67,7 @@ projects want to advertise that fact here. For this reason, we ask that if you'd to be featured in this section, please open a PR, adding a link to and short description of your project, as shown below: +- [kroxylicious](https://github.com/kroxylicious/kroxylicious/tree/main/kroxylicious-operator) Kafka proxy operator - [ExposedApp operator](https://github.com/halkyonio/exposedapp-rhdblog): a sample operator written to illustrate JOSDK concepts and its Quarkus extension in the ["Write Kubernetes Operators in Java with the Java Operator SDK" blog series](https://developers.redhat.com/articles/2022/02/15/write-kubernetes-java-java-operator-sdk#). diff --git a/bootstrapper-maven-plugin/pom.xml b/bootstrapper-maven-plugin/pom.xml index 44b50a3d81..3e292e810b 100644 --- a/bootstrapper-maven-plugin/pom.xml +++ b/bootstrapper-maven-plugin/pom.xml @@ -5,7 +5,7 @@ io.javaoperatorsdk java-operator-sdk - 5.0.5-SNAPSHOT + 5.1.3-SNAPSHOT bootstrapper @@ -15,7 +15,7 @@ 3.15.1 - 3.9.9 + 3.9.11 3.0.0 3.15.1 @@ -58,7 +58,7 @@ commons-io commons-io - 2.19.0 + 2.20.0 com.github.spullara.mustache.java diff --git a/bootstrapper-maven-plugin/src/main/resources/templates/ConfigMapDependentResource.java b/bootstrapper-maven-plugin/src/main/resources/templates/ConfigMapDependentResource.java index a8d43c60db..59eae8b01c 100644 --- a/bootstrapper-maven-plugin/src/main/resources/templates/ConfigMapDependentResource.java +++ b/bootstrapper-maven-plugin/src/main/resources/templates/ConfigMapDependentResource.java @@ -17,10 +17,6 @@ public class ConfigMapDependentResource public static final String KEY = "key"; - public ConfigMapDependentResource() { - super(ConfigMap.class); - } - @Override protected ConfigMap desired({{artifactClassId}}CustomResource primary, Context<{{artifactClassId}}CustomResource> context) { diff --git a/caffeine-bounded-cache-support/pom.xml b/caffeine-bounded-cache-support/pom.xml index 924164c6cb..14e19dd85e 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.0.5-SNAPSHOT + 5.1.3-SNAPSHOT caffeine-bounded-cache-support diff --git a/docs/content/en/blog/news/primary-cache-for-next-recon.md b/docs/content/en/blog/news/primary-cache-for-next-recon.md new file mode 100644 index 0000000000..67326a6f17 --- /dev/null +++ b/docs/content/en/blog/news/primary-cache-for-next-recon.md @@ -0,0 +1,92 @@ +--- +title: How to guarantee allocated values for next reconciliation +date: 2025-05-22 +author: >- + [Attila Mészáros](https://github.com/csviri) and [Chris Laprun](https://github.com/metacosm) +--- + +We recently released v5.1 of Java Operator SDK (JOSDK). One of the highlights of this release is related to a topic of +so-called +[allocated values](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#representing-allocated-values +). + +To describe the problem, let's say that our controller needs to create a resource that has a generated identifier, i.e. +a resource which identifier cannot be directly derived from the custom resource's desired state as specified in its +`spec` field. To record the fact that the resource was successfully created, and to avoid attempting to +recreate the resource again in subsequent reconciliations, it is typical for this type of controller to store the +generated identifier in the custom resource's `status` field. + +The Java Operator SDK relies on the informers' cache to retrieve resources. These caches, however, are only guaranteed +to be eventually consistent. It could happen that, if some other event occurs, that would result in a new +reconciliation, **before** the update that's been made to our resource status has the chance to be propagated first to +the cluster and then back to the informer cache, that the resource in the informer cache does **not** contain the latest +version as modified by the reconciler. This would result in a new reconciliation where the generated identifier would be +missing from the resource status and, therefore, another attempt to create the resource by the reconciler, which is not +what we'd like. + +Java Operator SDK now provides a utility class [ +`PrimaryUpdateAndCacheUtils`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/PrimaryUpdateAndCacheUtils.java) +to handle this particular use case. Using that overlay cache, your reconciler is guaranteed to see the most up-to-date +version of the resource on the next reconciliation: + +```java + +@Override +public UpdateControl reconcile( + StatusPatchCacheCustomResource resource, + Context context) { + + // omitted code + + var freshCopy = createFreshCopy(resource); // need fresh copy just because we use the SSA version of update + freshCopy + .getStatus() + .setValue(statusWithAllocatedValue()); + + // using the utility instead of update control to patch the resource status + var updated = + PrimaryUpdateAndCacheUtils.ssaPatchStatusAndCacheResource(resource, freshCopy, context); + return UpdateControl.noUpdate(); +} +``` + +How does `PrimaryUpdateAndCacheUtils` work? +There are multiple ways to solve this problem, but ultimately, we only provide the solution described below. If you +want to dig deep in alternatives, see +this [PR](https://github.com/operator-framework/java-operator-sdk/pull/2800/files). + +The trick is to intercept the resource that the reconciler updated and cache that version in an additional cache on top +of the informer's cache. Subsequently, if the reconciler needs to read the resource, the SDK will first check if it is +in the overlay cache and read it from there if present, otherwise read it from the informer's cache. If the informer +receives an event with a fresh resource, we always remove the resource from the overlay cache, since that is a more +recent resource. But this **works only** if the reconciler updates the resource using **optimistic locking**. +If the update fails on conflict, because the resource has already been updated on the cluster before we got +the chance to get our update in, we simply wait and poll the informer cache until the new resource version from the +server appears in the informer's cache, +and then try to apply our updates to the resource again using the updated version from the server, again with optimistic +locking. + +So why is optimistic locking required? We hinted at it above, but the gist of it, is that if another party updates the +resource before we get a chance to, we wouldn't be able to properly handle the resulting situation correctly in all +cases. The informer would receive that new event before our own update would get a chance to propagate. Without +optimistic locking, there wouldn't be a fail-proof way to determine which update should prevail (i.e. which occurred +first), in particular in the event of the informer losing the connection to the cluster or other edge cases (the joys of +distributed computing!). + +Optimistic locking simplifies the situation and provides us with stronger guarantees: if the update succeeds, then we +can be sure we have the proper resource version in our caches. The next event will contain our update in all cases. +Because we know that, we can also be sure that we can evict the cached resource in the overlay cache whenever we receive +a new event. The overlay cache is only used if the SDK detects that the original resource (i.e. the one before we +applied our status update in the example above) is still in the informer's cache. + +The following diagram sums up the process: + +```mermaid +flowchart TD + A["Update Resource with Lock"] --> B{"Is Successful"} + B -- Fails on conflict --> D["Poll the Informer cache until resource updated"] + D --> A + B -- Yes --> n2{"Original resource still in informer cache?"} + n2 -- Yes --> C["Cache the resource in overlay cache"] + n2 -- No --> n3["Informer cache already contains up-to-date version, do not use overlay cache"] +``` diff --git a/docs/content/en/docs/documentation/configuration.md b/docs/content/en/docs/documentation/configuration.md index 052c0e0f19..06eda5f2a2 100644 --- a/docs/content/en/docs/documentation/configuration.md +++ b/docs/content/en/docs/documentation/configuration.md @@ -113,14 +113,53 @@ for this feature. ## DependentResource-level configuration -`DependentResource` implementations can implement the `DependentResourceConfigurator` interface -to pass information to the implementation. For example, the SDK -provides specific support for the `KubernetesDependentResource`, which can be configured via the -`@KubernetesDependent` annotation. This annotation is, in turn, converted into a -`KubernetesDependentResourceConfig` instance, which is then passed to the `configureWith` method -implementation. +It is possible to define custom annotations to configure custom `DependentResource` implementations. In order to provide +such a configuration mechanism for your own `DependentResource` implementations, they must be annotated with the +`@Configured` annotation. This annotation defines 3 fields that tie everything together: + +- `by`, which specifies which annotation class will be used to configure your dependents, +- `with`, which specifies the class holding the configuration object for your dependents and +- `converter`, which specifies the `ConfigurationConverter` implementation in charge of converting the annotation + specified by the `by` field into objects of the class specified by the `with` field. + +`ConfigurationConverter` instances implement a single `configFrom` method, which will receive, as expected, the +annotation instance annotating the dependent resource instance to be configured, but it can also extract information +from the `DependentResourceSpec` instance associated with the `DependentResource` class so that metadata from it can be +used in the configuration, as well as the parent `ControllerConfiguration`, if needed. The role of +`ConfigurationConverter` implementations is to extract the annotation information, augment it with metadata from the +`DependentResourceSpec` and the configuration from the parent controller on which the dependent is defined, to finally +create the configuration object that the `DependentResource` instances will use. + +However, one last element is required to finish the configuration process: the target `DependentResource` class must +implement the `ConfiguredDependentResource` interface, parameterized with the annotation class defined by the +`@Configured` annotation `by` field. This interface is called by the framework to inject the configuration at the +appropriate time and retrieve the configuration, if it's available. + +For example, `KubernetesDependentResource`, a core implementation that the framework provides, can be configured via the +`@KubernetesDependent` annotation. This set up is configured as follows: -TODO +```java + +@Configured( + by = KubernetesDependent.class, + with = KubernetesDependentResourceConfig.class, + converter = KubernetesDependentConverter.class) +public abstract class KubernetesDependentResource + extends AbstractEventSourceHolderDependentResource> + implements ConfiguredDependentResource> { + // code omitted +} +``` + +The `@Configured` annotation specifies that `KubernetesDependentResource` instances can be configured by using the +`@KubernetesDependent` annotation, which gets converted into a `KubernetesDependentResourceConfig` object by a +`KubernetesDependentConverter`. That configuration object is then injected by the framework in the +`KubernetesDependentResource` instance, after it's been created, because the class implements the +`ConfiguredDependentResource` interface, properly parameterized. + +For more information on how to use this feature, we recommend looking at how this mechanism is implemented for +`KubernetesDependentResource` in the core framework, `SchemaDependentResource` in the samples or `CustomAnnotationDep` +in the `BaseConfigurationServiceTest` test class. ## EventSource-level configuration diff --git a/docs/content/en/docs/documentation/dependent-resource-and-workflows/dependent-resources.md b/docs/content/en/docs/documentation/dependent-resource-and-workflows/dependent-resources.md index b9fcb7acf5..7416949869 100644 --- a/docs/content/en/docs/documentation/dependent-resource-and-workflows/dependent-resources.md +++ b/docs/content/en/docs/documentation/dependent-resource-and-workflows/dependent-resources.md @@ -133,13 +133,9 @@ Deleted (or set to be garbage collected). The following example shows how to cre ```java -@KubernetesDependent(labelSelector = WebPageManagedDependentsReconciler.SELECTOR) +@KubernetesDependent(informer = @Informer(labelSelector = SELECTOR)) class DeploymentDependentResource extends CRUDKubernetesDependentResource { - public DeploymentDependentResource() { - super(Deployment.class); - } - @Override protected Deployment desired(WebPage webPage, Context context) { var deploymentName = deploymentName(webPage); @@ -178,9 +174,10 @@ JOSDK will take the appropriate steps to wire everything together and call your `DependentResource` implementations `reconcile` method before your primary resource is reconciled. This makes sense in most use cases where the logic associated with the primary resource is usually limited to status handling based on the state of the secondary resources and the -resources are not dependent on each other. +resources are not dependent on each other. As an alternative, you can also invoke reconciliation explicitly, +event for managed workflows. -See [Workflows](https://javaoperatorsdk.io/docs/workflows) for more details on how the dependent +See [Workflows](https://javaoperatorsdk.io/docs/documentation/dependent-resource-and-workflows/workflows/) for more details on how the dependent resources are reconciled. This behavior and automated handling is referred to as "managed" because the `DependentResource` @@ -188,12 +185,14 @@ instances are managed by JOSDK, an example of which can be seen below: ```java -@ControllerConfiguration( - labelSelector = SELECTOR, +@Workflow( dependents = { @Dependent(type = ConfigMapDependentResource.class), @Dependent(type = DeploymentDependentResource.class), - @Dependent(type = ServiceDependentResource.class) + @Dependent(type = ServiceDependentResource.class), + @Dependent( + type = IngressDependentResource.class, + reconcilePrecondition = ExposedIngressCondition.class) }) public class WebPageManagedDependentsReconciler implements Reconciler, ErrorStatusHandler { @@ -208,7 +207,6 @@ public class WebPageManagedDependentsReconciler webPage.setStatus(createStatus(name)); return UpdateControl.patchStatus(webPage); } - } ``` @@ -222,7 +220,7 @@ It is also possible to wire dependent resources programmatically. In practice th developer is responsible for initializing and managing the dependent resources as well as calling their `reconcile` method. However, this makes it possible for developers to fully customize the reconciliation process. Standalone dependent resources should be used in cases when the managed use -case does not fit. You can, of course, also use [Workflows](https://javaoperatorsdk.io/docs/workflows) when managing +case does not fit. You can, of course, also use [Workflows](https://javaoperatorsdk.io/docs/documentation/dependent-resource-and-workflows/workflows/) when managing resources programmatically. You can see a commented example of how to do diff --git a/docs/content/en/docs/documentation/dependent-resource-and-workflows/workflows.md b/docs/content/en/docs/documentation/dependent-resource-and-workflows/workflows.md index 4b1bea6790..c5ee83a446 100644 --- a/docs/content/en/docs/documentation/dependent-resource-and-workflows/workflows.md +++ b/docs/content/en/docs/documentation/dependent-resource-and-workflows/workflows.md @@ -12,12 +12,12 @@ depends on the state of other resources or cannot be processed until these other a given state or some condition holds true for them. Dealing with such scenarios are therefore rather common for operators and the purpose of the workflow feature of the Java Operator SDK (JOSDK) is to simplify supporting such cases in a declarative way. Workflows build on top of the -[dependent resources](https://javaoperatorsdk.io/docs/dependent-resources) feature. +[dependent resources](https://javaoperatorsdk.io/docs/documentation/dependent-resource-and-workflows/dependent-resources/) feature. While dependent resources focus on how a given secondary resource should be reconciled, workflows focus on orchestrating how these dependent resources should be reconciled. Workflows describe how as a set of -[dependent resources](https://javaoperatorsdk.io/docs/dependent-resources) (DR) depend on one +[dependent resources](https://javaoperatorsdk.io/docs/documentation/dependent-resource-and-workflows/dependent-resources/) (DR) depend on one another, along with the conditions that need to hold true at certain stages of the reconciliation process. @@ -135,7 +135,7 @@ public class SampleWorkflowReconciler implements Reconciler reconcile( StatusPatchCacheCustomResource resource, Context context) { @@ -201,85 +201,22 @@ public UpdateControl reconcile( var freshCopy = createFreshCopy(primary); freshCopy.getStatus().setValue(statusWithState()); - var updatedResource = PrimaryUpdateAndCacheUtils.ssaPatchAndCacheStatus(resource, freshCopy, context); - - return UpdateControl.noUpdate(); - } -``` - -In the background `PrimaryUpdateAndCacheUtils.ssaPatchAndCacheStatus` puts the result of the update into an internal -cache and will make sure that the next reconciliation will contain the most recent version of the resource. Note that it -is not necessarily the version of the resource you got as response from the update, it can be newer since other parties -can do additional updates meanwhile, but if not explicitly modified, it will contain the up-to-date status. - -See related integration test [here](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/internal). - -This approach works with the default configuration of the framework and should be good to go in most of the cases. -Without going further into the details, this won't work if `ConfigurationService.parseResourceVersionsForEventFilteringAndCaching` -is set to `false` (more precisely there are some edge cases when it won't work). For that case framework provides the following solution: - -#### Fallback approach: using `PrimaryResourceCache` cache - -As an alternative, for very rare cases when `ConfigurationService.parseResourceVersionsForEventFilteringAndCaching` -needs to be set to `false` you can use an explicit caching approach: - -```java - -// We on purpose don't use the provided predicate to show what a custom one could look like. - private final PrimaryResourceCache cache = - new PrimaryResourceCache<>( - (statusPatchCacheCustomResourcePair, statusPatchCacheCustomResource) -> - statusPatchCacheCustomResource.getStatus().getValue() - >= statusPatchCacheCustomResourcePair.afterUpdate().getStatus().getValue()); - - @Override - public UpdateControl reconcile( - StatusPatchPrimaryCacheCustomResource primary, - Context context) { - - // cache will compare the current and the cached resource and return the more recent. (And evict the old) - primary = cache.getFreshResource(primary); - - // omitted logic - - var freshCopy = createFreshCopy(primary); - - freshCopy.getStatus().setValue(statusWithState()); + var updatedResource = PrimaryUpdateAndCacheUtils.ssaPatchStatusAndCacheResource(resource, freshCopy, context); - var updated = - PrimaryUpdateAndCacheUtils.ssaPatchAndCacheStatus(primary, freshCopy, context, cache); - + // the resource was updated transparently via the utils, no further action is required via UpdateControl in this case return UpdateControl.noUpdate(); } - - @Override - public DeleteControl cleanup( - StatusPatchPrimaryCacheCustomResource resource, - Context context) - throws Exception { - // cleanup the cache on resource deletion - cache.cleanup(resource); - return DeleteControl.defaultDelete(); - } - ``` -[`PrimaryResourceCache`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/support/PrimaryResourceCache.java) -is designed for this purpose. As shown in the example above, it is up to you to provide a predicate to determine if the -resource is more recent than the one available. In other words, when to evict the resource from the cache. Typically, as -shown in -the [integration test](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/primarycache) -you can have a counter in status to check on that. - -Since all of this happens explicitly, you cannot use this approach for managed dependent resources and workflows and -will need to use the unmanaged approach instead. This is due to the fact that managed dependent resources always get -their associated primary resource from the underlying informer event source cache. - -#### Additional remarks +After the update `PrimaryUpdateAndCacheUtils.ssaPatchStatusAndCacheResource` puts the result of the update into an internal +cache and the framework will make sure that the next reconciliation contains the most recent version of the resource. +Note that it is not necessarily the same version returned as response from the update, it can be a newer version since other parties +can do additional updates meanwhile. However, unless it has been explicitly modified, that +resource will contain the up-to-date status. -As shown in the integration tests, there is no optimistic locking used when updating the -[resource](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/internal/StatusPatchCacheReconciler.java#L41) -(in other words `metadata.resourceVersion` is set to `null`). This is desired since you don't want the patch to fail on -update. +Note that you can also perform additional updates after the `PrimaryUpdateAndCacheUtils.*PatchStatusAndCacheResource` is +called, either by calling any of the `PrimeUpdateAndCacheUtils` methods again or via `UpdateControl`. Using +`PrimaryUpdateAndCacheUtils` guarantees that the next reconciliation will see a resource state no older than the version +updated via `PrimaryUpdateAndCacheUtils`. -In addition, you can configure the [Fabric8 client retry](https://github.com/fabric8io/kubernetes-client?tab=readme-ov-file#configuring-the-client). +See related integration test [here](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache). diff --git a/docs/content/en/docs/faq/_index.md b/docs/content/en/docs/faq/_index.md index 9308ce4cfa..1c3d82fe35 100644 --- a/docs/content/en/docs/faq/_index.md +++ b/docs/content/en/docs/faq/_index.md @@ -90,8 +90,38 @@ reconciliation. For example if you patch the status at the end of the reconcilia it is not guaranteed that during the next reconciliation you will see the fresh resource. Therefore, controllers which do this, usually cache the updated status in memory to make sure it is present for next reconciliation. -Dependent Resources feature supports the [first approach](../dependent-resources/_index.md#external-state-tracking-dependent-resources). +From version 5.1 you can use [this utility](../documentation/reconciler.md#making-sure-the-primary-resource-is-up-to-date-for-the-next-reconciliation) +to make sure an updated status is present for the next reconciliation. + +Dependent Resources feature supports the [first approach](../documentation/dependent-resource-and-workflows/dependent-resources.md#external-state-tracking-dependent-resources). +### How can I make the status update of my custom resource trigger a reconciliation? + +The framework checks, by default, when an event occurs, that could trigger a reconciliation, if the event increased the +`generation` field of the primary resource's metadata and filters out the event if it did not. `generation` is typically +only increased when the `.spec` field of a resource is changed. As a result, a change in the `.status` field would not +normally trigger a reconciliation. + +To change this behavior, you can set the [ +`generationAwareEventProcessing`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java#L43) +to `false`: + +```java + +@ControllerConfiguration(generationAwareEventProcessing = false) +static class TestCustomReconciler implements Reconciler { + + @Override + public UpdateControl reconcile(TestCustomResource resource, Context context) { + // code omitted + } +} +``` + +For secondary resources, every change should trigger a reconciliation by default, except when you add explicit filters +or use dependent resource implementations that filter out changes they trigger themselves by default, +see [related docs](../documentation/dependent-resource-and-workflows/dependent-resources.md#caching-and-event-handling-in-kubernetesdependentresource). + ### How can I skip the reconciliation of a dependent resource? Skipping workflow reconciliation altogether is possible with the explicit invocation feature since v5. diff --git a/docs/content/en/docs/migration/v3-1-migration.md b/docs/content/en/docs/migration/v3-1-migration.md index e42c4a206a..b4b42d9a5e 100644 --- a/docs/content/en/docs/migration/v3-1-migration.md +++ b/docs/content/en/docs/migration/v3-1-migration.md @@ -13,7 +13,7 @@ renamed accordingly. Version 3.1 comes with a workflow engine that replaces the previous behavior of managed dependent resources. -See [Workflows documentation](https://javaoperatorsdk.io/docs/workflows) for further details. +See [Workflows documentation](https://javaoperatorsdk.io/docs/documentation/dependent-resource-and-workflows/workflows/) for further details. The primary impact after upgrade is a change of the order in which managed dependent resources are reconciled. They are now reconciled in parallel with optional ordering defined using the ['depends_on'](https://github.com/java-operator-sdk/java-operator-sdk/blob/df44917ef81725c10bbcb772ab7b434d511b13b9/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/Dependent.java#L23-L23) diff --git a/docs/content/en/docs/migration/v4-4-migration.md b/docs/content/en/docs/migration/v4-4-migration.md index 998e6ddf9a..913c08b843 100644 --- a/docs/content/en/docs/migration/v4-4-migration.md +++ b/docs/content/en/docs/migration/v4-4-migration.md @@ -55,7 +55,7 @@ explicitly to the Operator constructor, it is now recommended to provide that va ## Using Server-Side Apply in Dependent Resources From this version by -default [Dependent Resources](https://javaoperatorsdk.io/docs/dependent-resources) use +default [Dependent Resources](https://javaoperatorsdk.io/docs/documentation/dependent-resource-and-workflows/dependent-resources/) use [Server Side Apply (SSA)](https://kubernetes.io/docs/reference/using-api/server-side-apply/) to create and update Kubernetes resources. A diff --git a/docs/content/en/docs/migration/v4-5-migration.md b/docs/content/en/docs/migration/v4-5-migration.md index eff1581d87..42e78d76dc 100644 --- a/docs/content/en/docs/migration/v4-5-migration.md +++ b/docs/content/en/docs/migration/v4-5-migration.md @@ -5,7 +5,7 @@ permalink: /docs/v4-5-migration --- Version 4.5 introduces improvements related to event handling for Dependent Resources, more precisely the -[caching and event handling](https://javaoperatorsdk.io/docs/dependent-resources#caching-and-event-handling-in-kubernetesdependentresource) +[caching and event handling](https://javaoperatorsdk.io/docs/documentation/dependent-resource-and-workflows/dependent-resources/#caching-and-event-handling-in-kubernetesdependentresource) features. As a result the Kubernetes resources managed using [KubernetesDependentResource](https://github.com/java-operator-sdk/java-operator-sdk/blob/73b1d8db926a24502c3a70da34f6bcac4f66b4eb/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSource.java#L72-L72) or its subclasses, will add an annotation recording the resource's version whenever JOSDK updates or creates such diff --git a/docs/static/images/cncf_logo2.png b/docs/static/images/cncf_logo2.png new file mode 100644 index 0000000000..e1236b7e87 Binary files /dev/null and b/docs/static/images/cncf_logo2.png differ diff --git a/micrometer-support/pom.xml b/micrometer-support/pom.xml index 3c568e76fd..ea18d07ce7 100644 --- a/micrometer-support/pom.xml +++ b/micrometer-support/pom.xml @@ -4,7 +4,7 @@ io.javaoperatorsdk java-operator-sdk - 5.0.5-SNAPSHOT + 5.1.3-SNAPSHOT micrometer-support diff --git a/operator-framework-bom/pom.xml b/operator-framework-bom/pom.xml index e1cff7980d..5809eed20f 100644 --- a/operator-framework-bom/pom.xml +++ b/operator-framework-bom/pom.xml @@ -4,7 +4,7 @@ io.javaoperatorsdk operator-framework-bom - 5.0.5-SNAPSHOT + 5.1.3-SNAPSHOT pom Operator SDK - Bill of Materials Java SDK for implementing Kubernetes operators @@ -33,19 +33,12 @@ https://github.com/operator-framework/java-operator-sdk/tree/master - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - - 1.7.0 - 3.2.7 + 3.2.8 3.3.1 - 3.11.2 + 3.11.3 2.44.3 + 0.8.0 @@ -115,6 +108,17 @@ release + + org.apache.maven.plugins + maven-surefire-plugin + + + **/*IT.java + **/*E2E.java + **/InformerRelatedBehaviorTest.java + + + org.apache.maven.plugins maven-javadoc-plugin @@ -138,13 +142,13 @@ jar + verify org.apache.maven.plugins maven-gpg-plugin - ${maven-gpg-plugin.version} sign-artifacts @@ -162,14 +166,15 @@ - org.sonatype.plugins - nexus-staging-maven-plugin - ${nexus-staging-maven-plugin.version} + org.sonatype.central + central-publishing-maven-plugin + ${central-publishing-maven-plugin.version} true - ossrh - https://oss.sonatype.org/ - true + central + true + true + published diff --git a/operator-framework-core/pom.xml b/operator-framework-core/pom.xml index cad50ebc32..c99b609113 100644 --- a/operator-framework-core/pom.xml +++ b/operator-framework-core/pom.xml @@ -4,7 +4,7 @@ io.javaoperatorsdk java-operator-sdk - 5.0.5-SNAPSHOT + 5.1.3-SNAPSHOT ../pom.xml diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/RuntimeInfo.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/RuntimeInfo.java index b7fbce7f07..0495131d79 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/RuntimeInfo.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/RuntimeInfo.java @@ -5,6 +5,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.health.EventSourceHealthIndicator; import io.javaoperatorsdk.operator.health.InformerWrappingEventSourceHealthIndicator; import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerEventSource; @@ -22,7 +23,7 @@ public class RuntimeInfo { private final Operator operator; public RuntimeInfo(Operator operator) { - this.registeredControllers = operator.getRegisteredControllers(); + this.registeredControllers = Collections.unmodifiableSet(operator.getRegisteredControllers()); this.operator = operator; } @@ -30,6 +31,7 @@ public boolean isStarted() { return operator.isStarted(); } + @SuppressWarnings("unused") public Set getRegisteredControllers() { checkIfStarted(); return registeredControllers; @@ -80,4 +82,23 @@ public Map> unhealthyEventSource } return res; } + + /** + * Retrieves the {@link RegisteredController} associated with the specified controller name or + * {@code null} if no such controller is registered. + * + * @param controllerName the name of the {@link RegisteredController} to retrieve + * @return the {@link RegisteredController} associated with the specified controller name or + * {@code null} if no such controller is registered + * @since 5.1.2 + */ + @SuppressWarnings({"unchecked", "unused"}) + public RegisteredController getRegisteredController( + String controllerName) { + checkIfStarted(); + return registeredControllers.stream() + .filter(rc -> rc.getConfiguration().getName().equals(controllerName)) + .findFirst() + .orElse(null); + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java index 438f7d91a9..891f199dbe 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java @@ -30,6 +30,12 @@ import static io.javaoperatorsdk.operator.api.config.ControllerConfiguration.CONTROLLER_NAME_AS_FIELD_MANAGER; +/** + * A default {@link ConfigurationService} implementation, resolving {@link Reconciler}s + * configuration when it has already been resolved before. If this behavior is not adequate, please + * use {@link AbstractConfigurationService} instead as a base for your {@code ConfigurationService} + * implementation. + */ public class BaseConfigurationService extends AbstractConfigurationService { private static final String LOGGER_NAME = "Default ConfigurationService implementation"; @@ -149,10 +155,12 @@ private static void configureFromAnnotatedReconciler( @Override protected void logMissingReconcilerWarning(String reconcilerKey, String reconcilersNameMessage) { - logger.warn( - "Configuration for reconciler '{}' was not found. {}", - reconcilerKey, - reconcilersNameMessage); + if (!createIfNeeded()) { + logger.warn( + "Configuration for reconciler '{}' was not found. {}", + reconcilerKey, + reconcilersNameMessage); + } } @SuppressWarnings("unused") @@ -318,6 +326,13 @@ private

ResolvedControllerConfiguration

controllerCon informerConfig); } + /** + * @deprecated This method was meant to allow subclasses to prevent automatic creation of the + * configuration when not found. This functionality is now removed, if you want to be able to + * prevent automated, on-demand creation of a reconciler's configuration, please use the + * {@link AbstractConfigurationService} implementation instead as base for your extension. + */ + @Deprecated(forRemoval = true) protected boolean createIfNeeded() { return true; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java index 3ffc913c5e..41134e64ac 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java @@ -13,6 +13,8 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.Secret; +import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.fabric8.kubernetes.api.model.apps.StatefulSet; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.ConfigBuilder; import io.fabric8.kubernetes.client.CustomResource; @@ -26,7 +28,6 @@ import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfig; -import io.javaoperatorsdk.operator.processing.dependent.kubernetes.ResourceUpdaterMatcher; import io.javaoperatorsdk.operator.processing.dependent.workflow.ManagedWorkflowFactory; import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerEventSource; @@ -394,9 +395,6 @@ default boolean shouldUseSSA( Class dependentResourceType, Class resourceType, KubernetesDependentResourceConfig config) { - if (ResourceUpdaterMatcher.class.isAssignableFrom(dependentResourceType)) { - return false; - } Boolean useSSAConfig = Optional.ofNullable(config).map(KubernetesDependentResourceConfig::useSSA).orElse(null); // don't use SSA for certain resources by default, only if explicitly overridden @@ -442,12 +440,40 @@ default Set> defaultNonSSAResource() { * * @return if special annotation should be used for dependent resource to filter events * @since 4.5.0 - * @return if special annotation should be used for dependent resource to filter events */ default boolean previousAnnotationForDependentResourcesEventFiltering() { return true; } + /** + * For dependent resources, the framework can add an annotation to filter out events resulting + * directly from the framework's operation. There are, however, some resources that do not follow + * the Kubernetes API conventions that changes in metadata should not increase the generation of + * the resource (as recorded in the {@code generation} field of the resource's {@code metadata}). + * For these resources, this convention is not respected and results in a new event for the + * framework to process. If that particular case is not handled correctly in the resource matcher, + * the framework will consider that the resource doesn't match the desired state and therefore + * triggers an update, which in turn, will re-add the annotation, thus starting the loop again, + * infinitely. + * + *

As a workaround, we automatically skip adding previous annotation for those well-known + * resources. Note that if you are sure that the matcher works for your use case, and it should in + * most instances, you can remove the resource type from the blocklist. + * + *

The consequence of adding a resource type to the set is that the framework will not use + * event filtering to prevent events, initiated by changes made by the framework itself as a + * result of its processing of dependent resources, to trigger the associated reconciler again. + * + *

Note that this method only takes effect if annotating dependent resources to prevent + * dependent resources events from triggering the associated reconciler again is activated as + * controlled by {@link #previousAnnotationForDependentResourcesEventFiltering()} + * + * @return a Set of resource classes where the previous version annotation won't be used. + */ + default Set> withPreviousAnnotationForDependentResourcesBlocklist() { + return Set.of(Deployment.class, StatefulSet.class); + } + /** * If the event logic should parse the resourceVersion to determine the ordering of dependent * resource events. This is typically not needed. @@ -459,7 +485,6 @@ default boolean previousAnnotationForDependentResourcesEventFiltering() { * * @return if resource version should be parsed (as integer) * @since 4.5.0 - * @return if resource version should be parsed (as integer) */ default boolean parseResourceVersionsForEventFilteringAndCaching() { return false; diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationServiceOverrider.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationServiceOverrider.java index f420be0fff..be86cbe312 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationServiceOverrider.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationServiceOverrider.java @@ -40,6 +40,7 @@ public class ConfigurationServiceOverrider { private Boolean parseResourceVersions; private Boolean useSSAToPatchPrimaryResource; private Boolean cloneSecondaryResourcesWhenGettingFromCache; + private Set> previousAnnotationUsageBlocklist; @SuppressWarnings("rawtypes") private DependentResourceFactory dependentResourceFactory; @@ -188,6 +189,12 @@ public ConfigurationServiceOverrider withCloneSecondaryResourcesWhenGettingFromC return this; } + public ConfigurationServiceOverrider withPreviousAnnotationForDependentResourcesBlocklist( + Set> blocklist) { + this.previousAnnotationUsageBlocklist = blocklist; + return this; + } + public ConfigurationService build() { return new BaseConfigurationService(original.getVersion(), cloner, client) { @Override @@ -247,13 +254,20 @@ public boolean closeClientOnStop() { @Override public ExecutorService getExecutorService() { - return overriddenValueOrDefault(executorService, ConfigurationService::getExecutorService); + if (executorService != null) { + return executorService; + } else { + return super.getExecutorService(); + } } @Override public ExecutorService getWorkflowExecutorService() { - return overriddenValueOrDefault( - workflowExecutorService, ConfigurationService::getWorkflowExecutorService); + if (workflowExecutorService != null) { + return workflowExecutorService; + } else { + return super.getWorkflowExecutorService(); + } } @Override @@ -328,6 +342,14 @@ public boolean cloneSecondaryResourcesWhenGettingFromCache() { cloneSecondaryResourcesWhenGettingFromCache, ConfigurationService::cloneSecondaryResourcesWhenGettingFromCache); } + + @Override + public Set> + withPreviousAnnotationForDependentResourcesBlocklist() { + return overriddenValueOrDefault( + previousAnnotationUsageBlocklist, + ConfigurationService::withPreviousAnnotationForDependentResourcesBlocklist); + } }; } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/Utils.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/Utils.java index f11fc47eef..3b6f94a025 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/Utils.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/Utils.java @@ -134,6 +134,39 @@ public static Class getTypeArgumentFromExtendedClassByIndex(Class clazz, i } } + public static Class getTypeArgumentFromHierarchyByIndex(Class clazz, int index) { + return getTypeArgumentFromHierarchyByIndex(clazz, null, index); + } + + public static Class getTypeArgumentFromHierarchyByIndex( + Class clazz, Class expectedImplementedInterface, int index) { + Class c = clazz; + while (!(c.getGenericSuperclass() instanceof ParameterizedType)) { + c = c.getSuperclass(); + } + Class actualTypeArgument = + (Class) ((ParameterizedType) c.getGenericSuperclass()).getActualTypeArguments()[index]; + if (expectedImplementedInterface != null + && !expectedImplementedInterface.isAssignableFrom(actualTypeArgument)) { + throw new IllegalArgumentException( + GENERIC_PARAMETER_TYPE_ERROR_PREFIX + + clazz.getName() + + "because it doesn't extend a class that is parametrized with the type that" + + " implements " + + expectedImplementedInterface.getSimpleName() + + ". Please provide the resource type in the constructor (e.g.," + + " super(Deployment.class)."); + } else if (expectedImplementedInterface == null && actualTypeArgument.equals(Object.class)) { + throw new IllegalArgumentException( + GENERIC_PARAMETER_TYPE_ERROR_PREFIX + + clazz.getName() + + " because it doesn't extend a class that is parametrized with the type we want to" + + " retrieve or because it's Object.class. Please provide the resource type in the " + + "constructor (e.g., super(Deployment.class)."); + } + return actualTypeArgument; + } + public static Class getFirstTypeArgumentFromInterface( Class clazz, Class expectedImplementedInterface) { return getTypeArgumentFromInterfaceByIndex(clazz, expectedImplementedInterface, 0); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceConfigurationProvider.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceConfigurationProvider.java deleted file mode 100644 index a0c9dc67ae..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceConfigurationProvider.java +++ /dev/null @@ -1,6 +0,0 @@ -package io.javaoperatorsdk.operator.api.config.dependent; - -public interface DependentResourceConfigurationProvider { - @SuppressWarnings("rawtypes") - Object getConfigurationFor(DependentResourceSpec spec); -} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerEventSourceConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerEventSourceConfiguration.java index 9fb5ad4c82..2369d5f523 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerEventSourceConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerEventSourceConfiguration.java @@ -194,6 +194,14 @@ public Builder withNamespaces(Set namespaces) { return this; } + /** + * @since 5.1.1 + */ + public Builder withNamespaces(String... namespaces) { + config.withNamespaces(Set.of(namespaces)); + return this; + } + public Builder withNamespacesInheritedFromController() { withNamespaces(SAME_AS_CONTROLLER_NAMESPACES_SET); return this; diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/PrimaryUpdateAndCacheUtils.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/PrimaryUpdateAndCacheUtils.java index 174f7667f6..c61cc837c1 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/PrimaryUpdateAndCacheUtils.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/PrimaryUpdateAndCacheUtils.java @@ -1,146 +1,89 @@ package io.javaoperatorsdk.operator.api.reconciler; -import java.util.function.Supplier; +import java.time.LocalTime; +import java.time.temporal.ChronoUnit; import java.util.function.UnaryOperator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.kubernetes.client.dsl.base.PatchContext; import io.fabric8.kubernetes.client.dsl.base.PatchType; -import io.javaoperatorsdk.operator.api.reconciler.support.PrimaryResourceCache; +import io.javaoperatorsdk.operator.OperatorException; import io.javaoperatorsdk.operator.processing.event.ResourceID; /** * Utility methods to patch the primary resource state and store it to the related cache, to make - * sure that fresh resource is present for the next reconciliation. The main use case for such - * updates is to store state is resource status. Use of optimistic locking is not desired for such - * updates, since we don't want to patch fail and lose information that we want to store. + * sure that the latest version of the resource is present for the next reconciliation. The main use + * case for such updates is to store state is resource status. + * + *

The way the framework handles this is with retryable updates with optimistic locking, and + * caches the updated resource from the response in an overlay cache on top of the Informer cache. + * If the update fails, it reads the primary resource from the cluster, applies the modifications + * again and retries the update. */ public class PrimaryUpdateAndCacheUtils { + public static final int DEFAULT_MAX_RETRY = 10; + public static final int DEFAULT_RESOURCE_CACHE_TIMEOUT_MILLIS = 10000; + public static final int DEFAULT_RESOURCE_CACHE_POLL_PERIOD_MILLIS = 50; + private PrimaryUpdateAndCacheUtils() {} private static final Logger log = LoggerFactory.getLogger(PrimaryUpdateAndCacheUtils.class); /** - * Makes sure that the up-to-date primary resource will be present during the next reconciliation. - * Using update (PUT) method. - * - * @param primary resource - * @param context of reconciliation - * @return updated resource - * @param

primary resource type - */ - public static

P updateAndCacheStatus(P primary, Context

context) { - logWarnIfResourceVersionPresent(primary); - return patchAndCacheStatus( - primary, context, () -> context.getClient().resource(primary).updateStatus()); - } - - /** - * Makes sure that the up-to-date primary resource will be present during the next reconciliation. - * Using JSON Merge patch. - * - * @param primary resource - * @param context of reconciliation - * @return updated resource - * @param

primary resource type + * Updates the status with optimistic locking and caches the result for next reconciliation. For + * details see {@link #updateAndCacheResource}. */ - public static

P patchAndCacheStatus(P primary, Context

context) { - logWarnIfResourceVersionPresent(primary); - return patchAndCacheStatus( - primary, context, () -> context.getClient().resource(primary).patchStatus()); + public static

P updateStatusAndCacheResource( + P primary, Context

context, UnaryOperator

modificationFunction) { + return updateAndCacheResource( + primary, + context, + modificationFunction, + r -> context.getClient().resource(r).updateStatus()); } /** - * Makes sure that the up-to-date primary resource will be present during the next reconciliation. - * Using JSON Patch. - * - * @param primary resource - * @param context of reconciliation - * @return updated resource - * @param

primary resource type + * Patches the status using JSON Merge Patch with optimistic locking and caches the result for + * next reconciliation. For details see {@link #updateAndCacheResource}. */ - public static

P editAndCacheStatus( - P primary, Context

context, UnaryOperator

operation) { - logWarnIfResourceVersionPresent(primary); - return patchAndCacheStatus( - primary, context, () -> context.getClient().resource(primary).editStatus(operation)); + public static

P mergePatchStatusAndCacheResource( + P primary, Context

context, UnaryOperator

modificationFunction) { + return updateAndCacheResource( + primary, context, modificationFunction, r -> context.getClient().resource(r).patchStatus()); } /** - * Makes sure that the up-to-date primary resource will be present during the next reconciliation. - * - * @param primary resource - * @param context of reconciliation - * @param patch free implementation of cache - * @return the updated resource. - * @param

primary resource type + * Patches the status using JSON Patch with optimistic locking and caches the result for next + * reconciliation. For details see {@link #updateAndCacheResource}. */ - public static

P patchAndCacheStatus( - P primary, Context

context, Supplier

patch) { - var updatedResource = patch.get(); - context - .eventSourceRetriever() - .getControllerEventSource() - .handleRecentResourceUpdate(ResourceID.fromResource(primary), updatedResource, primary); - return updatedResource; + public static

P patchStatusAndCacheResource( + P primary, Context

context, UnaryOperator

modificationFunction) { + return updateAndCacheResource( + primary, + context, + UnaryOperator.identity(), + r -> context.getClient().resource(r).editStatus(modificationFunction)); } /** - * Makes sure that the up-to-date primary resource will be present during the next reconciliation. - * Using Server Side Apply. - * - * @param primary resource - * @param freshResourceWithStatus - fresh resource with target state - * @param context of reconciliation - * @return the updated resource. - * @param

primary resource type + * Patches the status using Server Side Apply with optimistic locking and caches the result for + * next reconciliation. For details see {@link #updateAndCacheResource}. */ - public static

P ssaPatchAndCacheStatus( + public static

P ssaPatchStatusAndCacheResource( P primary, P freshResourceWithStatus, Context

context) { - logWarnIfResourceVersionPresent(freshResourceWithStatus); - var res = - context - .getClient() - .resource(freshResourceWithStatus) - .subresource("status") - .patch( - new PatchContext.Builder() - .withForce(true) - .withFieldManager(context.getControllerConfiguration().fieldManager()) - .withPatchType(PatchType.SERVER_SIDE_APPLY) - .build()); - - context - .eventSourceRetriever() - .getControllerEventSource() - .handleRecentResourceUpdate(ResourceID.fromResource(primary), res, primary); - return res; - } - - /** - * Patches the resource and adds it to the {@link PrimaryResourceCache}. - * - * @param primary resource - * @param freshResourceWithStatus - fresh resource with target state - * @param context of reconciliation - * @param cache - resource cache managed by user - * @return the updated resource. - * @param

primary resource type - */ - public static

P ssaPatchAndCacheStatus( - P primary, P freshResourceWithStatus, Context

context, PrimaryResourceCache

cache) { - logWarnIfResourceVersionPresent(freshResourceWithStatus); - return patchAndCacheStatus( + return updateAndCacheResource( primary, - cache, - () -> + context, + r -> freshResourceWithStatus, + r -> context .getClient() - .resource(freshResourceWithStatus) + .resource(r) .subresource("status") .patch( new PatchContext.Builder() @@ -151,75 +94,139 @@ public static

P ssaPatchAndCacheStatus( } /** - * Patches the resource with JSON Patch and adds it to the {@link PrimaryResourceCache}. + * Same as {@link #updateAndCacheResource(HasMetadata, Context, UnaryOperator, UnaryOperator, int, + * long,long)} using the default maximum retry number as defined by {@link #DEFAULT_MAX_RETRY} and + * default cache maximum polling time and period as defined, respectively by {@link + * #DEFAULT_RESOURCE_CACHE_TIMEOUT_MILLIS} and {@link #DEFAULT_RESOURCE_CACHE_POLL_PERIOD_MILLIS}. * - * @param primary resource + * @param resourceToUpdate original resource to update * @param context of reconciliation - * @param cache - resource cache managed by user - * @return the updated resource. - * @param

primary resource type + * @param modificationFunction modifications to make on primary + * @param updateMethod the update method implementation + * @param

primary type + * @return the updated resource */ - public static

P editAndCacheStatus( - P primary, Context

context, PrimaryResourceCache

cache, UnaryOperator

operation) { - logWarnIfResourceVersionPresent(primary); - return patchAndCacheStatus( - primary, cache, () -> context.getClient().resource(primary).editStatus(operation)); + public static

P updateAndCacheResource( + P resourceToUpdate, + Context

context, + UnaryOperator

modificationFunction, + UnaryOperator

updateMethod) { + return updateAndCacheResource( + resourceToUpdate, + context, + modificationFunction, + updateMethod, + DEFAULT_MAX_RETRY, + DEFAULT_RESOURCE_CACHE_TIMEOUT_MILLIS, + DEFAULT_RESOURCE_CACHE_POLL_PERIOD_MILLIS); } /** - * Patches the resource with JSON Merge patch and adds it to the {@link PrimaryResourceCache} - * provided. + * Modifies the primary using the specified modification function, then uses the modified resource + * for the request to update with provided update method. As the {@code resourceVersion} field of + * the modified resource is set to the value found in the specified resource to update, the update + * operation will therefore use optimistic locking on the server. If the request fails on + * optimistic update, we read the resource again from the K8S API server and retry the whole + * process. In short, we make sure we always update the resource with optimistic locking, then we + * cache the resource in an internal cache. Without further going into details, the optimistic + * locking is needed so we can reliably handle the caching. * - * @param primary resource + * @param resourceToUpdate original resource to update * @param context of reconciliation - * @param cache - resource cache managed by user - * @return the updated resource. - * @param

primary resource type + * @param modificationFunction modifications to make on primary + * @param updateMethod the update method implementation + * @param maxRetry maximum number of retries before giving up + * @param cachePollTimeoutMillis maximum amount of milliseconds to wait for the updated resource + * to appear in cache + * @param cachePollPeriodMillis cache polling period, in milliseconds + * @param

primary type + * @return the updated resource */ - public static

P patchAndCacheStatus( - P primary, Context

context, PrimaryResourceCache

cache) { - logWarnIfResourceVersionPresent(primary); - return patchAndCacheStatus( - primary, cache, () -> context.getClient().resource(primary).patchStatus()); - } - - /** - * Updates the resource and adds it to the {@link PrimaryResourceCache}. - * - * @param primary resource - * @param context of reconciliation - * @param cache - resource cache managed by user - * @return the updated resource. - * @param

primary resource type - */ - public static

P updateAndCacheStatus( - P primary, Context

context, PrimaryResourceCache

cache) { - logWarnIfResourceVersionPresent(primary); - return patchAndCacheStatus( - primary, cache, () -> context.getClient().resource(primary).updateStatus()); - } - - /** - * Updates the resource using the user provided implementation anc caches the result. - * - * @param primary resource - * @param cache resource cache managed by user - * @param patch implementation of resource update* - * @return the updated resource. - * @param

primary resource type - */ - public static

P patchAndCacheStatus( - P primary, PrimaryResourceCache

cache, Supplier

patch) { - var updatedResource = patch.get(); - cache.cacheResource(primary, updatedResource); - return updatedResource; + public static

P updateAndCacheResource( + P resourceToUpdate, + Context

context, + UnaryOperator

modificationFunction, + UnaryOperator

updateMethod, + int maxRetry, + long cachePollTimeoutMillis, + long cachePollPeriodMillis) { + + if (log.isDebugEnabled()) { + log.debug("Conflict retrying update for: {}", ResourceID.fromResource(resourceToUpdate)); + } + P modified = null; + int retryIndex = 0; + while (true) { + try { + modified = modificationFunction.apply(resourceToUpdate); + modified + .getMetadata() + .setResourceVersion(resourceToUpdate.getMetadata().getResourceVersion()); + var updated = updateMethod.apply(modified); + context + .eventSourceRetriever() + .getControllerEventSource() + .handleRecentResourceUpdate( + ResourceID.fromResource(resourceToUpdate), updated, resourceToUpdate); + return updated; + } catch (KubernetesClientException e) { + log.trace("Exception during patch for resource: {}", resourceToUpdate); + retryIndex++; + // only retry on conflict (409) and unprocessable content (422) which + // can happen if JSON Patch is not a valid request since there was + // a concurrent request which already removed another finalizer: + // List element removal from a list is by index in JSON Patch + // so if addressing a second finalizer but first is meanwhile removed + // it is a wrong request. + if (e.getCode() != 409 && e.getCode() != 422) { + throw e; + } + if (retryIndex > maxRetry) { + log.warn("Retry exhausted, last desired resource: {}", modified); + throw new OperatorException( + "Exceeded maximum (" + + maxRetry + + ") retry attempts to patch resource: " + + ResourceID.fromResource(resourceToUpdate), + e); + } + log.debug( + "Retrying patch for resource name: {}, namespace: {}; HTTP code: {}", + resourceToUpdate.getMetadata().getName(), + resourceToUpdate.getMetadata().getNamespace(), + e.getCode()); + resourceToUpdate = + pollLocalCache( + context, resourceToUpdate, cachePollTimeoutMillis, cachePollPeriodMillis); + } + } } - private static

void logWarnIfResourceVersionPresent(P primary) { - if (primary.getMetadata().getResourceVersion() != null) { - log.warn( - "The metadata.resourceVersion of primary resource is NOT null, " - + "using optimistic locking is discouraged for this purpose. "); + private static

P pollLocalCache( + Context

context, P staleResource, long timeoutMillis, long pollDelayMillis) { + try { + var resourceId = ResourceID.fromResource(staleResource); + var startTime = LocalTime.now(); + final var timeoutTime = startTime.plus(timeoutMillis, ChronoUnit.MILLIS); + while (timeoutTime.isAfter(LocalTime.now())) { + log.debug("Polling cache for resource: {}", resourceId); + var cachedResource = context.getPrimaryCache().get(resourceId).orElseThrow(); + if (!cachedResource + .getMetadata() + .getResourceVersion() + .equals(staleResource.getMetadata().getResourceVersion())) { + return context + .getControllerConfiguration() + .getConfigurationService() + .getResourceCloner() + .clone(cachedResource); + } + Thread.sleep(pollDelayMillis); + } + throw new OperatorException("Timeout of resource polling from cache for resource"); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new OperatorException(e); } } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/UpdateControl.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/UpdateControl.java index 1b5eefd7ff..1bd98c12d6 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/UpdateControl.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/UpdateControl.java @@ -21,8 +21,7 @@ private UpdateControl(P resource, boolean patchResource, boolean patchStatus) { } /** - * Preferred way to update the status. It does not do optimistic locking. Uses JSON Patch to patch - * the resource. + * Preferred way to update the status. Uses JSON Patch to patch the resource. * *

Note that this does not work, if the {@link CustomResource#initStatus()} is implemented, * since it breaks the diffing process. Don't implement it if using this method. There is also an diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/support/PrimaryResourceCache.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/support/PrimaryResourceCache.java deleted file mode 100644 index 4da73ab8b1..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/support/PrimaryResourceCache.java +++ /dev/null @@ -1,65 +0,0 @@ -package io.javaoperatorsdk.operator.api.reconciler.support; - -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.BiPredicate; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.processing.event.ResourceID; - -public class PrimaryResourceCache

{ - - private final BiPredicate, P> evictionPredicate; - private final ConcurrentHashMap> cache = new ConcurrentHashMap<>(); - - public PrimaryResourceCache(BiPredicate, P> evictionPredicate) { - this.evictionPredicate = evictionPredicate; - } - - public PrimaryResourceCache() { - this(new ResourceVersionParsingEvictionPredicate<>()); - } - - public void cacheResource(P afterUpdate) { - var resourceId = ResourceID.fromResource(afterUpdate); - cache.put(resourceId, new Pair<>(null, afterUpdate)); - } - - public void cacheResource(P beforeUpdate, P afterUpdate) { - var resourceId = ResourceID.fromResource(beforeUpdate); - cache.put(resourceId, new Pair<>(beforeUpdate, afterUpdate)); - } - - public P getFreshResource(P newVersion) { - var resourceId = ResourceID.fromResource(newVersion); - var pair = cache.get(resourceId); - if (pair == null) { - return newVersion; - } - if (!newVersion.getMetadata().getUid().equals(pair.afterUpdate().getMetadata().getUid())) { - cache.remove(resourceId); - return newVersion; - } - if (evictionPredicate.test(pair, newVersion)) { - cache.remove(resourceId); - return newVersion; - } else { - return pair.afterUpdate(); - } - } - - public void cleanup(P resource) { - cache.remove(ResourceID.fromResource(resource)); - } - - public record Pair(T beforeUpdate, T afterUpdate) {} - - /** This works in general, but it does not strictly follow the contract with k8s API */ - public static class ResourceVersionParsingEvictionPredicate - implements BiPredicate, T> { - @Override - public boolean test(Pair updatePair, T newVersion) { - return Long.parseLong(updatePair.afterUpdate().getMetadata().getResourceVersion()) - <= Long.parseLong(newVersion.getMetadata().getResourceVersion()); - } - } -} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractEventSourceHolderDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractEventSourceHolderDependentResource.java index 5cee9467f1..7f2674892f 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractEventSourceHolderDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractEventSourceHolderDependentResource.java @@ -3,6 +3,7 @@ import java.util.Optional; import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.api.config.Utils; import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; import io.javaoperatorsdk.operator.api.reconciler.Ignore; @@ -23,13 +24,22 @@ public abstract class AbstractEventSourceHolderDependentResource< private boolean isCacheFillerEventSource; protected String eventSourceNameToUse; + @SuppressWarnings("unchecked") + protected AbstractEventSourceHolderDependentResource() { + this(null, null); + } + protected AbstractEventSourceHolderDependentResource(Class resourceType) { this(resourceType, null); } protected AbstractEventSourceHolderDependentResource(Class resourceType, String name) { super(name); - this.resourceType = resourceType; + if (resourceType == null) { + this.resourceType = (Class) Utils.getTypeArgumentFromHierarchyByIndex(getClass(), 0); + } else { + this.resourceType = resourceType; + } } /** diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractExternalDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractExternalDependentResource.java index 1148895709..4c828b7eb9 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractExternalDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractExternalDependentResource.java @@ -21,6 +21,8 @@ public abstract class AbstractExternalDependentResource< private InformerEventSource externalStateEventSource; + protected AbstractExternalDependentResource() {} + @SuppressWarnings("unchecked") protected AbstractExternalDependentResource(Class resourceType) { super(resourceType); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractPollingDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractPollingDependentResource.java index 659b8b4720..3cf93cba53 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractPollingDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractPollingDependentResource.java @@ -16,6 +16,8 @@ public abstract class AbstractPollingDependentResource public static final Duration DEFAULT_POLLING_PERIOD = Duration.ofMillis(5000); private Duration pollingPeriod; + protected AbstractPollingDependentResource() {} + protected AbstractPollingDependentResource(Class resourceType) { this(resourceType, DEFAULT_POLLING_PERIOD); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/external/PerResourcePollingDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/external/PerResourcePollingDependentResource.java index 8cbe9f48d5..c0181207d8 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/external/PerResourcePollingDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/external/PerResourcePollingDependentResource.java @@ -14,6 +14,8 @@ public abstract class PerResourcePollingDependentResource implements PerResourcePollingEventSource.ResourceFetcher { + public PerResourcePollingDependentResource() {} + public PerResourcePollingDependentResource(Class resourceType) { super(resourceType); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/CRUDKubernetesDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/CRUDKubernetesDependentResource.java index afe4302fc3..392ac6d894 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/CRUDKubernetesDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/CRUDKubernetesDependentResource.java @@ -18,6 +18,8 @@ public abstract class CRUDKubernetesDependentResource implements Creator, Updater, GarbageCollected

{ + public CRUDKubernetesDependentResource() {} + public CRUDKubernetesDependentResource(Class resourceType) { super(resourceType); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/CRUDNoGCKubernetesDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/CRUDNoGCKubernetesDependentResource.java index 549f26437a..3b3c11b006 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/CRUDNoGCKubernetesDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/CRUDNoGCKubernetesDependentResource.java @@ -20,6 +20,8 @@ public class CRUDNoGCKubernetesDependentResource extends KubernetesDependentResource implements Creator, Updater, Deleter

{ + public CRUDNoGCKubernetesDependentResource() {} + public CRUDNoGCKubernetesDependentResource(Class resourceType) { super(resourceType); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java index ea7edbc1a0..ebd6089aa7 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java @@ -41,6 +41,9 @@ public abstract class KubernetesDependentResource kubernetesDependentResourceConfig; private volatile Boolean useSSA; + private volatile Boolean usePreviousAnnotationForEventFiltering; + + public KubernetesDependentResource() {} public KubernetesDependentResource(Class resourceType) { this(resourceType, null); @@ -163,10 +166,19 @@ protected boolean useSSA(Context

context) { } private boolean usePreviousAnnotation(Context

context) { - return context - .getControllerConfiguration() - .getConfigurationService() - .previousAnnotationForDependentResourcesEventFiltering(); + if (usePreviousAnnotationForEventFiltering == null) { + usePreviousAnnotationForEventFiltering = + context + .getControllerConfiguration() + .getConfigurationService() + .previousAnnotationForDependentResourcesEventFiltering() + && !context + .getControllerConfiguration() + .getConfigurationService() + .withPreviousAnnotationForDependentResourcesBlocklist() + .contains(this.resourceType()); + } + return usePreviousAnnotationForEventFiltering; } @Override diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceConfig.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceConfig.java index bcfe2f9fe6..6f626d2628 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceConfig.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceConfig.java @@ -12,6 +12,13 @@ public class KubernetesDependentResourceConfig { private final InformerConfiguration informerConfig; private final SSABasedGenericKubernetesResourceMatcher matcher; + public KubernetesDependentResourceConfig( + Boolean useSSA, + boolean createResourceOnlyIfNotExistingWithSSA, + InformerConfiguration informerConfig) { + this(useSSA, createResourceOnlyIfNotExistingWithSSA, informerConfig, null); + } + public KubernetesDependentResourceConfig( Boolean useSSA, boolean createResourceOnlyIfNotExistingWithSSA, diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/ResourceRequirementsSanitizer.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/PodTemplateSpecSanitizer.java similarity index 57% rename from operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/ResourceRequirementsSanitizer.java rename to operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/PodTemplateSpecSanitizer.java index 7193085b63..962059961e 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/ResourceRequirementsSanitizer.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/PodTemplateSpecSanitizer.java @@ -2,32 +2,34 @@ import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import io.fabric8.kubernetes.api.model.Container; +import io.fabric8.kubernetes.api.model.EnvVar; import io.fabric8.kubernetes.api.model.GenericKubernetesResource; import io.fabric8.kubernetes.api.model.PodTemplateSpec; import io.fabric8.kubernetes.api.model.Quantity; import io.fabric8.kubernetes.api.model.ResourceRequirements; /** - * Sanitizes the {@link ResourceRequirements} in the containers of a pair of {@link PodTemplateSpec} - * instances. + * Sanitizes the {@link ResourceRequirements} and the {@link EnvVar} in the containers of a pair of + * {@link PodTemplateSpec} instances. * *

When the sanitizer finds a mismatch in the structure of the given templates, before it gets to - * the nested resource limits and requests, it returns early without fixing the actual map. This is - * an optimization because the given templates will anyway differ at this point. This means we do - * not have to attempt to sanitize the resources for these use cases, since there will anyway be an - * update of the K8s resource. + * the nested fields, it returns early without fixing the actual map. This is an optimization + * because the given templates will anyway differ at this point. This means we do not have to + * attempt to sanitize the fields for these use cases, since there will anyway be an update of the + * K8s resource. * *

The algorithm traverses the whole template structure because we need the actual and desired - * {@link Quantity} instances to compare their numerical amount. Using the {@link + * {@link Quantity} and {@link EnvVar} instances. Using the {@link * GenericKubernetesResource#get(Map, Object...)} shortcut would need to create new instances just * for the sanitization check. */ -class ResourceRequirementsSanitizer { +class PodTemplateSpecSanitizer { - static void sanitizeResourceRequirements( + static void sanitizePodTemplateSpec( final Map actualMap, final PodTemplateSpec actualTemplate, final PodTemplateSpec desiredTemplate) { @@ -37,19 +39,19 @@ static void sanitizeResourceRequirements( if (actualTemplate.getSpec() == null || desiredTemplate.getSpec() == null) { return; } - sanitizeResourceRequirements( + sanitizePodTemplateSpec( actualMap, actualTemplate.getSpec().getInitContainers(), desiredTemplate.getSpec().getInitContainers(), "initContainers"); - sanitizeResourceRequirements( + sanitizePodTemplateSpec( actualMap, actualTemplate.getSpec().getContainers(), desiredTemplate.getSpec().getContainers(), "containers"); } - private static void sanitizeResourceRequirements( + private static void sanitizePodTemplateSpec( final Map actualMap, final List actualContainers, final List desiredContainers, @@ -57,11 +59,17 @@ private static void sanitizeResourceRequirements( int containers = desiredContainers.size(); if (containers == actualContainers.size()) { for (int containerIndex = 0; containerIndex < containers; containerIndex++) { - var desiredContainer = desiredContainers.get(containerIndex); - var actualContainer = actualContainers.get(containerIndex); + final var desiredContainer = desiredContainers.get(containerIndex); + final var actualContainer = actualContainers.get(containerIndex); if (!desiredContainer.getName().equals(actualContainer.getName())) { return; } + sanitizeEnvVars( + actualMap, + actualContainer.getEnv(), + desiredContainer.getEnv(), + containerPath, + containerIndex); sanitizeResourceRequirements( actualMap, actualContainer.getResources(), @@ -121,7 +129,7 @@ private static void sanitizeQuantities( m -> actualResource.forEach( (key, actualQuantity) -> { - var desiredQuantity = desiredResource.get(key); + final var desiredQuantity = desiredResource.get(key); if (desiredQuantity == null) { return; } @@ -138,4 +146,53 @@ private static void sanitizeQuantities( } })); } + + @SuppressWarnings("unchecked") + private static void sanitizeEnvVars( + final Map actualMap, + final List actualEnvVars, + final List desiredEnvVars, + final String containerPath, + final int containerIndex) { + if (desiredEnvVars.isEmpty() || actualEnvVars.isEmpty()) { + return; + } + Optional.ofNullable( + GenericKubernetesResource.get( + actualMap, "spec", "template", "spec", containerPath, containerIndex, "env")) + .map(List.class::cast) + .ifPresent( + envVars -> + actualEnvVars.forEach( + actualEnvVar -> { + final var actualEnvVarName = actualEnvVar.getName(); + final var actualEnvVarValue = actualEnvVar.getValue(); + // check if the actual EnvVar value string is not null or the desired EnvVar + // already contains the same EnvVar name with a non empty EnvVar value + final var isDesiredEnvVarEmpty = + hasEnvVarNoEmptyValue(actualEnvVarName, desiredEnvVars); + if (actualEnvVarValue != null || isDesiredEnvVarEmpty) { + return; + } + envVars.stream() + .filter( + envVar -> + ((Map) envVar) + .get("name") + .equals(actualEnvVarName)) + // add the actual EnvVar value with an empty string to prevent a + // resource update + .forEach(envVar -> ((Map) envVar).put("value", "")); + })); + } + + private static boolean hasEnvVarNoEmptyValue( + final String envVarName, final List envVars) { + return envVars.stream() + .anyMatch( + envVar -> + Objects.equals(envVarName, envVar.getName()) + && envVar.getValue() != null + && !envVar.getValue().isEmpty()); + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/ResourceUpdaterMatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/ResourceUpdaterMatcher.java deleted file mode 100644 index d893ff3e86..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/ResourceUpdaterMatcher.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.javaoperatorsdk.operator.processing.dependent.kubernetes; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.api.reconciler.Context; - -public interface ResourceUpdaterMatcher { - - R updateResource(R actual, R desired, Context context); - - boolean matches(R actual, R desired, Context context); -} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java index 3c051acfb4..4954dfd17a 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java @@ -31,7 +31,7 @@ import com.github.difflib.DiffUtils; import com.github.difflib.UnifiedDiffUtils; -import static io.javaoperatorsdk.operator.processing.dependent.kubernetes.ResourceRequirementsSanitizer.sanitizeResourceRequirements; +import static io.javaoperatorsdk.operator.processing.dependent.kubernetes.PodTemplateSpecSanitizer.sanitizePodTemplateSpec; /** * Matches the actual state on the server vs the desired state. Based on the managedFields of SSA. @@ -179,7 +179,7 @@ private Optional checkIfFieldManagerExists(R actual, String } /** Correct for known issue with SSA */ - private void sanitizeState(R actual, R desired, Map actualMap) { + protected void sanitizeState(R actual, R desired, Map actualMap) { if (actual instanceof StatefulSet actualStatefulSet && desired instanceof StatefulSet desiredStatefulSet) { var actualSpec = actualStatefulSet.getSpec(); @@ -203,22 +203,22 @@ private void sanitizeState(R actual, R desired, Map actualMap) { } } } - sanitizeResourceRequirements(actualMap, actualSpec.getTemplate(), desiredSpec.getTemplate()); + sanitizePodTemplateSpec(actualMap, actualSpec.getTemplate(), desiredSpec.getTemplate()); } else if (actual instanceof Deployment actualDeployment && desired instanceof Deployment desiredDeployment) { - sanitizeResourceRequirements( + sanitizePodTemplateSpec( actualMap, actualDeployment.getSpec().getTemplate(), desiredDeployment.getSpec().getTemplate()); } else if (actual instanceof ReplicaSet actualReplicaSet && desired instanceof ReplicaSet desiredReplicaSet) { - sanitizeResourceRequirements( + sanitizePodTemplateSpec( actualMap, actualReplicaSet.getSpec().getTemplate(), desiredReplicaSet.getSpec().getTemplate()); } else if (actual instanceof DaemonSet actualDaemonSet && desired instanceof DaemonSet desiredDaemonSet) { - sanitizeResourceRequirements( + sanitizePodTemplateSpec( actualMap, actualDaemonSet.getSpec().getTemplate(), desiredDaemonSet.getSpec().getTemplate()); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java index 065e790ba4..9e29305b51 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java @@ -144,9 +144,11 @@ protected void doRun(DependentResourceNode dependentResourceNode) { log.debug("Reconciling for primary: {} node: {} ", primaryID, dependentResourceNode); ReconcileResult reconcileResult = dependentResource.reconcile(primary, context); final var detailBuilder = createOrGetResultFor(dependentResourceNode); - detailBuilder.withReconcileResult(reconcileResult).markAsVisited(); - if (isConditionMet(dependentResourceNode.getReadyPostcondition(), dependentResourceNode)) { + boolean isReadyPostconditionMet = + isConditionMet(dependentResourceNode.getReadyPostcondition(), dependentResourceNode); + detailBuilder.withReconcileResult(reconcileResult).markAsVisited(); + if (isReadyPostconditionMet) { log.debug( "Setting already reconciled for: {} primaryID: {}", dependentResourceNode, primaryID); handleDependentsReconcile(dependentResourceNode); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventProcessor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventProcessor.java index bdaf575814..e029e287a0 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventProcessor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventProcessor.java @@ -102,8 +102,16 @@ public synchronized void handleEvent(Event event) { try { log.debug("Received event: {}", event); + final var optionalState = resourceStateManager.getOrCreateOnResourceEvent(event); + if (optionalState.isEmpty()) { + log.debug( + "Skipping event, since no state present and it is not a resource event. Resource ID:" + + " {}", + event.getRelatedCustomResourceID()); + return; + } + var state = optionalState.orElseThrow(); final var resourceID = event.getRelatedCustomResourceID(); - final var state = resourceStateManager.getOrCreate(event.getRelatedCustomResourceID()); MDCUtils.addResourceIDInfo(resourceID); metrics.receivedEvent(event, metricsMetadata); handleEventMarking(event, state); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ResourceStateManager.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ResourceStateManager.java index 6932e1ca5e..481fd317ff 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ResourceStateManager.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ResourceStateManager.java @@ -2,15 +2,33 @@ import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; +import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEvent; + class ResourceStateManager { // maybe we should have a way for users to specify a hint on the amount of CRs their reconciler // will process to avoid under- or over-sizing the state maps and avoid too many resizing that // take time and memory? private final Map states = new ConcurrentHashMap<>(100); + public Optional getOrCreateOnResourceEvent(Event event) { + var resourceId = event.getRelatedCustomResourceID(); + var state = states.get(event.getRelatedCustomResourceID()); + if (state != null) { + return Optional.of(state); + } + if (event instanceof ResourceEvent) { + state = new ResourceState(resourceId); + states.put(resourceId, state); + return Optional.of(state); + } else { + return Optional.empty(); + } + } + public ResourceState getOrCreate(ResourceID resourceID) { return states.computeIfAbsent(resourceID, ResourceState::new); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSource.java index b52dc278f2..c029a54170 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSource.java @@ -95,7 +95,7 @@ private InformerEventSource( parseResourceVersions); // If there is a primary to secondary mapper there is no need for primary to secondary index. primaryToSecondaryMapper = configuration.getPrimaryToSecondaryMapper(); - if (primaryToSecondaryMapper == null) { + if (useSecondaryToPrimaryIndex()) { primaryToSecondaryIndex = // The index uses the secondary to primary mapper (always present) to build the index new DefaultPrimaryToSecondaryIndex<>(configuration.getSecondaryToPrimaryMapper()); @@ -157,6 +157,14 @@ public void onDelete(R resource, boolean b) { } } + @Override + public synchronized void start() { + super.start(); + // this makes sure that on first reconciliation all resources are + // present on the index + manager().list().forEach(primaryToSecondaryIndex::onAddOrUpdate); + } + private synchronized void onAddOrUpdate( Operation operation, R newObject, R oldObject, Runnable superOnOp) { var resourceID = ResourceID.fromResource(newObject); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/TemporaryResourceCache.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/TemporaryResourceCache.java index 9ec5b3694c..af75a5abc4 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/TemporaryResourceCache.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/TemporaryResourceCache.java @@ -126,9 +126,7 @@ public synchronized void putResource(T newResource, String previousResourceVersi knownResourceVersions.add(newResource.getMetadata().getResourceVersion()); } var resourceId = ResourceID.fromResource(newResource); - var cachedResource = - getResourceFromCache(resourceId) - .orElse(managedInformerEventSource.get(resourceId).orElse(null)); + var cachedResource = managedInformerEventSource.get(resourceId).orElse(null); boolean moveAhead = false; if (previousResourceVersion == null && cachedResource == null) { diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/ConfigurationServiceOverriderTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/ConfigurationServiceOverriderTest.java index 2467df75aa..4f30458d68 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/ConfigurationServiceOverriderTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/ConfigurationServiceOverriderTest.java @@ -3,12 +3,14 @@ import java.time.Duration; import java.util.Optional; import java.util.concurrent.Executors; +import java.util.concurrent.ThreadPoolExecutor; import org.junit.jupiter.api.Test; import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.monitoring.Metrics; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertNotEquals; class ConfigurationServiceOverriderTest { @@ -26,30 +28,32 @@ public R clone(R object) { } }; + final BaseConfigurationService config = + new BaseConfigurationService(null) { + @Override + public boolean checkCRDAndValidateLocalModel() { + return false; + } + + @Override + public Metrics getMetrics() { + return METRICS; + } + + @Override + public Cloner getResourceCloner() { + return CLONER; + } + + @Override + public Optional getLeaderElectionConfiguration() { + return Optional.of(LEADER_ELECTION_CONFIGURATION); + } + }; + @Test void overrideShouldWork() { - final var config = - new BaseConfigurationService(null) { - @Override - public boolean checkCRDAndValidateLocalModel() { - return false; - } - - @Override - public Metrics getMetrics() { - return METRICS; - } - - @Override - public Cloner getResourceCloner() { - return CLONER; - } - - @Override - public Optional getLeaderElectionConfiguration() { - return Optional.of(LEADER_ELECTION_CONFIGURATION); - } - }; + final var overridden = new ConfigurationServiceOverrider(config) .checkingCRDAndValidateLocalModel(true) @@ -86,4 +90,17 @@ public R clone(R object) { assertNotEquals( config.reconciliationTerminationTimeout(), overridden.reconciliationTerminationTimeout()); } + + @Test + void threadCountConfiguredProperly() { + final var overridden = + new ConfigurationServiceOverrider(config) + .withConcurrentReconciliationThreads(13) + .withConcurrentWorkflowExecutorThreads(14) + .build(); + assertThat(((ThreadPoolExecutor) overridden.getExecutorService()).getMaximumPoolSize()) + .isEqualTo(13); + assertThat(((ThreadPoolExecutor) overridden.getWorkflowExecutorService()).getMaximumPoolSize()) + .isEqualTo(14); + } } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverriderTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverriderTest.java index 33191a8141..837ad7463a 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverriderTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverriderTest.java @@ -89,6 +89,22 @@ private io.javaoperatorsdk.operator.api.config.ControllerConfiguration create return configurationService.configFor(reconciler); } + @Test + void overridingNamespacesShouldNotThrowNPE() { + var configuration = createConfiguration(new NullReconciler()); + configuration = + ControllerConfigurationOverrider.override(configuration).settingNamespaces().build(); + assertTrue(configuration.getInformerConfig().watchAllNamespaces()); + } + + private static class NullReconciler implements Reconciler { + @Override + public UpdateControl reconcile(HasMetadata resource, Context context) + throws Exception { + return null; + } + } + @Test void overridingNamespacesShouldWork() { var configuration = createConfiguration(new WatchCurrentReconciler()); @@ -359,21 +375,11 @@ public UpdateControl reconcile(ConfigMap resource, Context } public static class ReadOnlyDependent extends KubernetesDependentResource - implements GarbageCollected { - - public ReadOnlyDependent() { - super(ConfigMap.class); - } - } + implements GarbageCollected {} @KubernetesDependent(informer = @Informer(namespaces = Constants.WATCH_ALL_NAMESPACES)) public static class WatchAllNSDependent extends KubernetesDependentResource - implements GarbageCollected { - - public WatchAllNSDependent() { - super(ConfigMap.class); - } - } + implements GarbageCollected {} @Workflow(dependents = @Dependent(type = OverriddenNSDependent.class)) @ControllerConfiguration( @@ -394,10 +400,6 @@ public static class OverriddenNSDependent implements GarbageCollected { private static final String DEP_NS = "dependentNS"; - - public OverriddenNSDependent() { - super(ConfigMap.class); - } } @Workflow( @@ -415,12 +417,7 @@ public UpdateControl reconcile(ConfigMap resource, Context private static class NamedDependentResource extends KubernetesDependentResource - implements GarbageCollected { - - public NamedDependentResource() { - super(ConfigMap.class); - } - } + implements GarbageCollected {} private static class ExternalDependentResource implements DependentResource, diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/UtilsTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/UtilsTest.java index 2b75b399c2..a2246f018a 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/UtilsTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/UtilsTest.java @@ -118,10 +118,5 @@ public UpdateControl reconcile(ConfigMap resource, Context } public static class TestKubernetesDependentResource - extends KubernetesDependentResource { - - public TestKubernetesDependentResource() { - super(Deployment.class); - } - } + extends KubernetesDependentResource {} } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceConfigurationResolverTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceConfigurationResolverTest.java index dd3caf0bd0..27bd2b9dae 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceConfigurationResolverTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceConfigurationResolverTest.java @@ -144,20 +144,10 @@ public UpdateControl reconcile(ConfigMap resource, Context } public static class ConfigMapDep extends KubernetesDependentResource - implements GarbageCollected { - - public ConfigMapDep() { - super(ConfigMap.class); - } - } + implements GarbageCollected {} public static class ServiceDep extends KubernetesDependentResource - implements GarbageCollected { - - public ServiceDep() { - super(Service.class); - } - } + implements GarbageCollected {} @CustomAnnotation(value = CustomAnnotatedDep.PROVIDED_VALUE) @Configured( diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/reconciler/PrimaryUpdateAndCacheUtilsTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/reconciler/PrimaryUpdateAndCacheUtilsTest.java new file mode 100644 index 0000000000..80a254b50f --- /dev/null +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/reconciler/PrimaryUpdateAndCacheUtilsTest.java @@ -0,0 +1,164 @@ +package io.javaoperatorsdk.operator.api.reconciler; + +import java.util.Optional; +import java.util.function.UnaryOperator; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClientException; +import io.fabric8.kubernetes.client.dsl.MixedOperation; +import io.fabric8.kubernetes.client.dsl.Resource; +import io.fabric8.kubernetes.client.utils.KubernetesSerialization; +import io.javaoperatorsdk.operator.OperatorException; +import io.javaoperatorsdk.operator.TestUtils; +import io.javaoperatorsdk.operator.api.config.Cloner; +import io.javaoperatorsdk.operator.api.config.ConfigurationService; +import io.javaoperatorsdk.operator.api.config.ControllerConfiguration; +import io.javaoperatorsdk.operator.processing.event.EventSourceRetriever; +import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerEventSource; +import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; + +import static io.javaoperatorsdk.operator.api.reconciler.PrimaryUpdateAndCacheUtils.DEFAULT_MAX_RETRY; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class PrimaryUpdateAndCacheUtilsTest { + + Context context = mock(Context.class); + KubernetesClient client = mock(KubernetesClient.class); + Resource resource = mock(Resource.class); + IndexedResourceCache primaryCache = mock(IndexedResourceCache.class); + + @BeforeEach + void setup() { + when(context.getClient()).thenReturn(client); + var esr = mock(EventSourceRetriever.class); + when(context.eventSourceRetriever()).thenReturn(esr); + when(esr.getControllerEventSource()).thenReturn(mock(ControllerEventSource.class)); + var mixedOp = mock(MixedOperation.class); + when(client.resources(any())).thenReturn(mixedOp); + when(mixedOp.inNamespace(any())).thenReturn(mixedOp); + when(mixedOp.withName(any())).thenReturn(resource); + when(resource.get()).thenReturn(TestUtils.testCustomResource1()); + when(context.getPrimaryCache()).thenReturn(primaryCache); + + var controllerConfiguration = mock(ControllerConfiguration.class); + when(context.getControllerConfiguration()).thenReturn(controllerConfiguration); + var configService = mock(ConfigurationService.class); + when(controllerConfiguration.getConfigurationService()).thenReturn(configService); + when(configService.getResourceCloner()) + .thenReturn( + new Cloner() { + @Override + public R clone(R object) { + return new KubernetesSerialization().clone(object); + } + }); + } + + @Test + void handlesUpdate() { + var updated = + PrimaryUpdateAndCacheUtils.updateAndCacheResource( + TestUtils.testCustomResource1(), + context, + r -> { + var res = TestUtils.testCustomResource1(); + // setting this to null to test if value set in the implementation + res.getMetadata().setResourceVersion(null); + res.getSpec().setValue("updatedValue"); + return res; + }, + r -> { + // checks if the resource version is set from the original resource + assertThat(r.getMetadata().getResourceVersion()).isEqualTo("1"); + var res = TestUtils.testCustomResource1(); + res.setSpec(r.getSpec()); + res.getMetadata().setResourceVersion("2"); + return res; + }); + + assertThat(updated.getMetadata().getResourceVersion()).isEqualTo("2"); + assertThat(updated.getSpec().getValue()).isEqualTo("updatedValue"); + } + + @Test + void retriesConflicts() { + var updateOperation = mock(UnaryOperator.class); + + when(updateOperation.apply(any())) + .thenThrow(new KubernetesClientException("", 409, null)) + .thenReturn(TestUtils.testCustomResource1()); + var freshResource = TestUtils.testCustomResource1(); + + freshResource.getMetadata().setResourceVersion("2"); + when(primaryCache.get(any())).thenReturn(Optional.of(freshResource)); + + var updated = + PrimaryUpdateAndCacheUtils.updateAndCacheResource( + TestUtils.testCustomResource1(), + context, + r -> { + var res = TestUtils.testCustomResource1(); + res.getSpec().setValue("updatedValue"); + return res; + }, + updateOperation); + + assertThat(updated).isNotNull(); + verify(primaryCache, times(1)).get(any()); + } + + @Test + void throwsIfRetryExhausted() { + var updateOperation = mock(UnaryOperator.class); + + when(updateOperation.apply(any())).thenThrow(new KubernetesClientException("", 409, null)); + var stubbing = when(primaryCache.get(any())); + + for (int i = 0; i < DEFAULT_MAX_RETRY; i++) { + var resource = TestUtils.testCustomResource1(); + resource.getMetadata().setResourceVersion("" + i); + stubbing = stubbing.thenReturn(Optional.of(resource)); + } + assertThrows( + OperatorException.class, + () -> + PrimaryUpdateAndCacheUtils.updateAndCacheResource( + TestUtils.testCustomResource1(), + context, + UnaryOperator.identity(), + updateOperation)); + verify(primaryCache, times(DEFAULT_MAX_RETRY)).get(any()); + } + + @Test + void cachePollTimeouts() { + var updateOperation = mock(UnaryOperator.class); + + when(updateOperation.apply(any())).thenThrow(new KubernetesClientException("", 409, null)); + when(primaryCache.get(any())).thenReturn(Optional.of(TestUtils.testCustomResource1())); + + var ex = + assertThrows( + OperatorException.class, + () -> + PrimaryUpdateAndCacheUtils.updateAndCacheResource( + TestUtils.testCustomResource1(), + context, + UnaryOperator.identity(), + updateOperation, + 2, + 50L, + 10L)); + assertThat(ex.getMessage()).contains("Timeout"); + } +} diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/reconciler/support/PrimaryResourceCacheTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/reconciler/support/PrimaryResourceCacheTest.java deleted file mode 100644 index 58e3ce8a0a..0000000000 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/reconciler/support/PrimaryResourceCacheTest.java +++ /dev/null @@ -1,87 +0,0 @@ -package io.javaoperatorsdk.operator.api.reconciler.support; - -import org.junit.jupiter.api.Test; - -import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; -import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; -import io.javaoperatorsdk.operator.sample.simple.TestCustomResourceSpec; - -import static org.assertj.core.api.Assertions.assertThat; - -class PrimaryResourceCacheTest { - - PrimaryResourceCache versionParsingCache = - new PrimaryResourceCache<>( - new PrimaryResourceCache.ResourceVersionParsingEvictionPredicate<>()); - - @Test - void returnsThePassedValueIfCacheIsEmpty() { - var cr = customResource("1"); - - var res = versionParsingCache.getFreshResource(cr); - - assertThat(cr).isSameAs(res); - } - - @Test - void returnsTheCachedIfNotEvictedAccordingToPredicate() { - var cr = customResource("2"); - - versionParsingCache.cacheResource(cr); - - var res = versionParsingCache.getFreshResource(customResource("1")); - assertThat(cr).isSameAs(res); - } - - @Test - void ifMoreFreshPassedCachedIsEvicted() { - var cr = customResource("2"); - versionParsingCache.cacheResource(cr); - var newCR = customResource("3"); - - var res = versionParsingCache.getFreshResource(newCR); - var resOnOlder = versionParsingCache.getFreshResource(cr); - - assertThat(newCR).isSameAs(res); - assertThat(resOnOlder).isSameAs(cr); - assertThat(newCR).isNotSameAs(cr); - } - - @Test - void cleanupRemovesCachedResources() { - var cr = customResource("2"); - versionParsingCache.cacheResource(cr); - - versionParsingCache.cleanup(customResource("3")); - - var olderCR = customResource("1"); - var res = versionParsingCache.getFreshResource(olderCR); - assertThat(olderCR).isSameAs(res); - } - - @Test - void removesIfNewResourceWithDifferentUid() { - var cr = customResource("2"); - versionParsingCache.cacheResource(cr); - var crWithDifferentUid = customResource("1"); - cr.getMetadata().setUid("otheruid"); - - var res = versionParsingCache.getFreshResource(crWithDifferentUid); - - assertThat(res).isSameAs(crWithDifferentUid); - } - - private TestCustomResource customResource(String resourceVersion) { - var cr = new TestCustomResource(); - cr.setMetadata( - new ObjectMetaBuilder() - .withName("test1") - .withNamespace("default") - .withUid("uid") - .withResourceVersion(resourceVersion) - .build()); - cr.setSpec(new TestCustomResourceSpec()); - cr.getSpec().setKey("key"); - return cr; - } -} diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcherTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcherTest.java index 0d85ee7225..3062e360e2 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcherTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcherTest.java @@ -186,10 +186,6 @@ HasMetadata createPrimary(String caseName) { private static class ServiceAccountDR extends KubernetesDependentResource { - public ServiceAccountDR() { - super(ServiceAccount.class); - } - @Override protected ServiceAccount desired(HasMetadata primary, Context context) { return new ServiceAccountBuilder() diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceTest.java new file mode 100644 index 0000000000..6c48311f4b --- /dev/null +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceTest.java @@ -0,0 +1,76 @@ +package io.javaoperatorsdk.operator.processing.dependent.kubernetes; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; + +import static io.javaoperatorsdk.operator.api.config.Utils.GENERIC_PARAMETER_TYPE_ERROR_PREFIX; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class KubernetesDependentResourceTest { + + @ParameterizedTest + @ValueSource( + classes = { + TestDeploymentDependentResource.class, + ChildTestDeploymentDependentResource.class, + GrandChildTestDeploymentDependentResource.class, + ChildTypeWithValidKubernetesDependentResource.class, + ConstructorOverridedCorrectDeployementDependentResource.class + }) + void checkResourceTypeDerivationWithInheritance(Class clazz) throws Exception { + KubernetesDependentResource dependentResource = + (KubernetesDependentResource) clazz.getDeclaredConstructor().newInstance(); + assertThat(dependentResource).isInstanceOf(KubernetesDependentResource.class); + assertThat(dependentResource.resourceType()).isEqualTo(Deployment.class); + } + + private static class TestDeploymentDependentResource + extends KubernetesDependentResource {} + + private static class ChildTestDeploymentDependentResource + extends TestDeploymentDependentResource {} + + private static class GrandChildTestDeploymentDependentResource + extends ChildTestDeploymentDependentResource {} + + private static class ChildTypeWithValidKubernetesDependentResource + extends KubernetesDependentResource {} + + private static class ConstructorOverridedCorrectDeployementDependentResource + extends KubernetesDependentResource { + public ConstructorOverridedCorrectDeployementDependentResource() { + super(Deployment.class); + } + } + + @Test + void validateInvalidTypeDerivationTypesThrowException() { + assertThatThrownBy(() -> new InvalidChildTestDeploymentDependentResource()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage( + GENERIC_PARAMETER_TYPE_ERROR_PREFIX + + InvalidChildTestDeploymentDependentResource.class.getName() + + " because it doesn't extend a class that is parametrized with the type we want to" + + " retrieve or because it's Object.class. Please provide the resource type in the " + + "constructor (e.g., super(Deployment.class)."); + assertThatThrownBy(() -> new InvalidGrandChildTestDeploymentDependentResource()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage( + GENERIC_PARAMETER_TYPE_ERROR_PREFIX + + InvalidGrandChildTestDeploymentDependentResource.class.getName() + + " because it doesn't extend a class that is parametrized with the type we want to" + + " retrieve or because it's Object.class. Please provide the resource type in the " + + "constructor (e.g., super(Deployment.class)."); + } + + private static class InvalidChildTestDeploymentDependentResource + extends ChildTypeWithValidKubernetesDependentResource {} + + private static class InvalidGrandChildTestDeploymentDependentResource + extends InvalidChildTestDeploymentDependentResource {} +} diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/ResourceRequirementsSanitizerTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/PodTemplateSpecSanitizerTest.java similarity index 53% rename from operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/ResourceRequirementsSanitizerTest.java rename to operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/PodTemplateSpecSanitizerTest.java index 79f3640883..091a1a666c 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/ResourceRequirementsSanitizerTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/PodTemplateSpecSanitizerTest.java @@ -1,10 +1,14 @@ package io.javaoperatorsdk.operator.processing.dependent.kubernetes; +import java.util.List; import java.util.Map; +import org.assertj.core.api.ListAssert; import org.assertj.core.api.MapAssert; import org.junit.jupiter.api.Test; +import io.fabric8.kubernetes.api.model.EnvVar; +import io.fabric8.kubernetes.api.model.EnvVarBuilder; import io.fabric8.kubernetes.api.model.GenericKubernetesResource; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.PodTemplateSpecBuilder; @@ -15,17 +19,17 @@ import io.fabric8.kubernetes.client.utils.KubernetesSerialization; import io.javaoperatorsdk.operator.MockKubernetesClient; -import static io.javaoperatorsdk.operator.processing.dependent.kubernetes.ResourceRequirementsSanitizer.sanitizeResourceRequirements; +import static io.javaoperatorsdk.operator.processing.dependent.kubernetes.PodTemplateSpecSanitizer.sanitizePodTemplateSpec; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verifyNoInteractions; /** - * Tests the {@link ResourceRequirementsSanitizer} with combinations of matching and mismatching K8s - * resources, using a mix of containers and init containers, as well as resource requests and - * limits. + * Tests the {@link PodTemplateSpecSanitizer} with combinations of matching and mismatching K8s + * resources, using a mix of containers and init containers, as well as resource requests and limits + * along with environment variables. */ -class ResourceRequirementsSanitizerTest { +class PodTemplateSpecSanitizerTest { private final Map actualMap = mock(); @@ -33,26 +37,26 @@ class ResourceRequirementsSanitizerTest { private final KubernetesSerialization serialization = client.getKubernetesSerialization(); @Test - void testSanitizeResourceRequirements_whenTemplateIsNull_doNothing() { + void testSanitizePodTemplateSpec_whenTemplateIsNull_doNothing() { final var template = new PodTemplateSpecBuilder().build(); - sanitizeResourceRequirements(actualMap, null, template); - sanitizeResourceRequirements(actualMap, template, null); + sanitizePodTemplateSpec(actualMap, null, template); + sanitizePodTemplateSpec(actualMap, template, null); verifyNoInteractions(actualMap); } @Test - void testSanitizeResourceRequirements_whenTemplateSpecIsNull_doNothing() { + void testSanitizePodTemplateSpec_whenTemplateSpecIsNull_doNothing() { final var template = new PodTemplateSpecBuilder().withSpec(null).build(); final var templateWithSpec = new PodTemplateSpecBuilder().withNewSpec().endSpec().build(); - sanitizeResourceRequirements(actualMap, template, templateWithSpec); - sanitizeResourceRequirements(actualMap, templateWithSpec, template); + sanitizePodTemplateSpec(actualMap, template, templateWithSpec); + sanitizePodTemplateSpec(actualMap, templateWithSpec, template); verifyNoInteractions(actualMap); } @Test - void testSanitizeResourceRequirements_whenContainerSizeMismatch_doNothing() { + void testSanitizePodTemplateSpec_whenContainerSizeMismatch_doNothing() { final var template = new PodTemplateSpecBuilder() .withNewSpec() @@ -73,13 +77,13 @@ void testSanitizeResourceRequirements_whenContainerSizeMismatch_doNothing() { .endSpec() .build(); - sanitizeResourceRequirements(actualMap, template, templateWithTwoContainers); - sanitizeResourceRequirements(actualMap, templateWithTwoContainers, template); + sanitizePodTemplateSpec(actualMap, template, templateWithTwoContainers); + sanitizePodTemplateSpec(actualMap, templateWithTwoContainers, template); verifyNoInteractions(actualMap); } @Test - void testSanitizeResourceRequirements_whenContainerNameMismatch_doNothing() { + void testSanitizePodTemplateSpec_whenContainerNameMismatch_doNothing() { final var template = new PodTemplateSpecBuilder() .withNewSpec() @@ -97,13 +101,13 @@ void testSanitizeResourceRequirements_whenContainerNameMismatch_doNothing() { .endSpec() .build(); - sanitizeResourceRequirements(actualMap, template, templateWithNewContainerName); - sanitizeResourceRequirements(actualMap, templateWithNewContainerName, template); + sanitizePodTemplateSpec(actualMap, template, templateWithNewContainerName); + sanitizePodTemplateSpec(actualMap, templateWithNewContainerName, template); verifyNoInteractions(actualMap); } @Test - void testSanitizeResourceRequirements_whenResourceIsNull_doNothing() { + void testSanitizePodTemplateSpec_whenResourceIsNull_doNothing() { final var template = new PodTemplateSpecBuilder() .withNewSpec() @@ -123,8 +127,8 @@ void testSanitizeResourceRequirements_whenResourceIsNull_doNothing() { .endSpec() .build(); - sanitizeResourceRequirements(actualMap, template, templateWithResource); - sanitizeResourceRequirements(actualMap, templateWithResource, template); + sanitizePodTemplateSpec(actualMap, template, templateWithResource); + sanitizePodTemplateSpec(actualMap, templateWithResource, template); verifyNoInteractions(actualMap); } @@ -155,7 +159,7 @@ void testSanitizeResourceRequirements_whenResourceKeyMismatch_doNothing() { } @Test - void testSanitizeResourceRequirements_whenResourcesHaveSameAmountAndFormat_doNothing() { + void testSanitizePodTemplateSpec_whenResourcesHaveSameAmountAndFormat_doNothing() { final var actualMap = sanitizeRequestsAndLimits( ContainerType.CONTAINER, @@ -168,7 +172,7 @@ void testSanitizeResourceRequirements_whenResourcesHaveSameAmountAndFormat_doNot } @Test - void testSanitizeResourceRequirements_whenResourcesHaveNumericalAmountMismatch_doNothing() { + void testSanitizePodTemplateSpec_whenResourcesHaveNumericalAmountMismatch_doNothing() { final var actualMap = sanitizeRequestsAndLimits( ContainerType.INIT_CONTAINER, @@ -200,17 +204,139 @@ void testSanitizeResourceRequirements_whenResourcesHaveNumericalAmountMismatch_d assertContainerResources(actualMap, "limits").hasSize(1).containsEntry("cpu", "4000m"); } - @SuppressWarnings("unchecked") + @Test + void testSanitizePodTemplateSpec_whenEnvVarsIsEmpty_doNothing() { + final var template = + new PodTemplateSpecBuilder() + .withNewSpec() + .addNewContainer() + .withName("test") + .endContainer() + .endSpec() + .build(); + final var templateWithEnvVars = + new PodTemplateSpecBuilder() + .withNewSpec() + .addNewContainer() + .withName("test") + .withEnv(List.of(new EnvVarBuilder().withName("FOO").withValue("foobar").build())) + .endContainer() + .endSpec() + .build(); + + sanitizePodTemplateSpec(actualMap, template, templateWithEnvVars); + sanitizePodTemplateSpec(actualMap, templateWithEnvVars, template); + verifyNoInteractions(actualMap); + } + + @Test + void testSanitizePodTemplateSpec_whenActualEnvVarValueIsNotEmpty_doNothing() { + final var actualMap = + sanitizeEnvVars( + ContainerType.CONTAINER, + List.of( + new EnvVarBuilder().withName("FOO").withValue("foo").build(), + new EnvVarBuilder().withName("BAR").withValue("bar").build()), + List.of( + new EnvVarBuilder().withName("FOO").withValue("bar").build(), + new EnvVarBuilder().withName("BAR").withValue("foo").build())); + assertContainerEnvVars(actualMap) + .hasSize(2) + .containsExactly( + Map.of("name", "FOO", "value", "foo"), Map.of("name", "BAR", "value", "bar")); + } + + @Test + void testSanitizePodTemplateSpec_whenActualAndDesiredEnvVarsAreDifferent_doNothing() { + final var actualMap = + sanitizeEnvVars( + ContainerType.INIT_CONTAINER, + List.of(new EnvVarBuilder().withName("FOO").withValue("foo").build()), + List.of(new EnvVarBuilder().withName("BAR").withValue("bar").build())); + assertInitContainerEnvVars(actualMap) + .hasSize(1) + .containsExactly(Map.of("name", "FOO", "value", "foo")); + } + + @Test + void testSanitizePodTemplateSpec_whenActualEnvVarIsEmpty_doNothing() { + final var actualMap = + sanitizeEnvVars( + ContainerType.INIT_CONTAINER, + List.of( + new EnvVarBuilder().withName("FOO").withValue("").build(), + new EnvVarBuilder().withName("BAR").withValue("").build()), + List.of( + new EnvVarBuilder().withName("FOO").withValue("foo").build(), + new EnvVarBuilder().withName("BAR").withValue("").build())); + assertInitContainerEnvVars(actualMap) + .hasSize(2) + .containsExactly(Map.of("name", "FOO", "value", ""), Map.of("name", "BAR", "value", "")); + } + + @Test + void testSanitizePodTemplateSpec_whenActualEnvVarIsNull_doNothing() { + final var actualMap = + sanitizeEnvVars( + ContainerType.CONTAINER, + List.of( + new EnvVarBuilder().withName("FOO").withValue(null).build(), + new EnvVarBuilder().withName("BAR").withValue(null).build()), + List.of( + new EnvVarBuilder().withName("FOO").withValue("foo").build(), + new EnvVarBuilder().withName("BAR").withValue(" ").build())); + assertContainerEnvVars(actualMap) + .hasSize(2) + .containsExactly(Map.of("name", "FOO"), Map.of("name", "BAR")); + } + + @Test + void + testSanitizePodTemplateSpec_whenActualEnvVarIsNull_withDesiredEnvVarEmpty_thenSanitizeActualMap() { + final var actualMap = + sanitizeEnvVars( + ContainerType.CONTAINER, + List.of( + new EnvVarBuilder().withName("FOO").withValue(null).build(), + new EnvVarBuilder().withName("BAR").withValue(null).build()), + List.of( + new EnvVarBuilder().withName("FOO").withValue("").build(), + new EnvVarBuilder().withName("BAR").withValue("").build())); + assertContainerEnvVars(actualMap) + .hasSize(2) + .containsExactly(Map.of("name", "FOO", "value", ""), Map.of("name", "BAR", "value", "")); + } + private Map sanitizeRequestsAndLimits( final ContainerType type, final Map actualRequests, final Map desiredRequests, final Map actualLimits, final Map desiredLimits) { - final var actual = createStatefulSet(type, actualRequests, actualLimits); - final var desired = createStatefulSet(type, desiredRequests, desiredLimits); + return sanitize( + type, actualRequests, desiredRequests, actualLimits, desiredLimits, List.of(), List.of()); + } + + private Map sanitizeEnvVars( + final ContainerType type, + final List actualEnvVars, + final List desiredEnvVars) { + return sanitize(type, Map.of(), Map.of(), Map.of(), Map.of(), actualEnvVars, desiredEnvVars); + } + + @SuppressWarnings("unchecked") + private Map sanitize( + final ContainerType type, + final Map actualRequests, + final Map desiredRequests, + final Map actualLimits, + final Map desiredLimits, + final List actualEnvVars, + final List desiredEnvVars) { + final var actual = createStatefulSet(type, actualRequests, actualLimits, actualEnvVars); + final var desired = createStatefulSet(type, desiredRequests, desiredLimits, desiredEnvVars); final var actualMap = serialization.convertValue(actual, Map.class); - sanitizeResourceRequirements( + sanitizePodTemplateSpec( actualMap, actual.getSpec().getTemplate(), desired.getSpec().getTemplate()); return actualMap; } @@ -223,7 +349,8 @@ private enum ContainerType { private static StatefulSet createStatefulSet( final ContainerType type, final Map requests, - final Map limits) { + final Map limits, + final List envVars) { var builder = new StatefulSetBuilder().withNewSpec().withNewTemplate().withNewSpec(); if (type == ContainerType.CONTAINER) { builder = @@ -234,6 +361,7 @@ private static StatefulSet createStatefulSet( .withRequests(requests) .withLimits(limits) .endResources() + .withEnv(envVars) .endContainer(); } else { builder = @@ -244,6 +372,7 @@ private static StatefulSet createStatefulSet( .withRequests(requests) .withLimits(limits) .endResources() + .withEnv(envVars) .endInitContainer(); } return builder.endSpec().endTemplate().endSpec().build(); @@ -262,4 +391,18 @@ private static MapAssert assertInitContainerResources( GenericKubernetesResource.>get( actualMap, "spec", "template", "spec", "initContainers", 0, "resources", resourceName)); } + + private static ListAssert> assertContainerEnvVars( + final Map actualMap) { + return assertThat( + GenericKubernetesResource.>>get( + actualMap, "spec", "template", "spec", "containers", 0, "env")); + } + + private static ListAssert> assertInitContainerEnvVars( + final Map actualMap) { + return assertThat( + GenericKubernetesResource.>>get( + actualMap, "spec", "template", "spec", "initContainers", 0, "env")); + } } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/EventProcessorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/EventProcessorTest.java index fe2e6e9514..9819eb7ee9 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/EventProcessorTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/EventProcessorTest.java @@ -276,6 +276,30 @@ void cancelScheduleOnceEventsOnSuccessfulExecution() { verify(retryTimerEventSourceMock, times(1)).cancelOnceSchedule(eq(crID)); } + @Test + void skipsGenericEventIfNoResourceEventReceivedBefore() { + var crID = new ResourceID("test-cr", TEST_NAMESPACE); + eventProcessor = + spy( + new EventProcessor( + controllerConfiguration(null, LinearRateLimiter.deactivatedRateLimiter()), + reconciliationDispatcherMock, + eventSourceManagerMock, + metricsMock)); + + verify(reconciliationDispatcherMock, timeout(100).times(0)).handleExecution(any()); + + eventProcessor.start(); + eventProcessor.handleEvent(new Event(crID)); + + await() + .pollDelay(Duration.ofMillis(100)) + .untilAsserted( + () -> { + verify(reconciliationDispatcherMock, never()).handleExecution(any()); + }); + } + @Test void startProcessedMarkedEventReceivedBefore() { var crID = new ResourceID("test-cr", TEST_NAMESPACE); @@ -287,7 +311,7 @@ void startProcessedMarkedEventReceivedBefore() { eventSourceManagerMock, metricsMock)); when(controllerEventSourceMock.get(eq(crID))).thenReturn(Optional.of(testCustomResource())); - eventProcessor.handleEvent(new Event(crID)); + eventProcessor.handleEvent(new ResourceEvent(ResourceAction.ADDED, crID, testCustomResource())); verify(reconciliationDispatcherMock, timeout(100).times(0)).handleExecution(any()); diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ResourceStateManagerTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ResourceStateManagerTest.java index 2c4d9fa4f3..487ba25885 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ResourceStateManagerTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ResourceStateManagerTest.java @@ -4,6 +4,10 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import io.javaoperatorsdk.operator.TestUtils; +import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceAction; +import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEvent; + import static org.assertj.core.api.Assertions.assertThat; class ResourceStateManagerTest { @@ -87,4 +91,26 @@ public void listsResourceIDSWithEventsPresent() { assertThat(res).hasSize(1); assertThat(res.get(0).getId()).isEqualTo(sampleResourceID2); } + + @Test + void createStateOnlyOnResourceEvent() { + var state = manager.getOrCreateOnResourceEvent(new Event(new ResourceID("newEvent"))); + + assertThat(state).isEmpty(); + + state = + manager.getOrCreateOnResourceEvent( + new ResourceEvent( + ResourceAction.ADDED, new ResourceID("newEvent"), TestUtils.testCustomResource())); + + assertThat(state).isNotNull(); + } + + @Test + void createsOnlyResourceEventReturnsPreviouslyCreatedState() { + manager.getOrCreate(new ResourceID("newEvent")); + + var res = manager.getOrCreateOnResourceEvent(new Event(new ResourceID("newEvent"))); + assertThat(res).isNotNull(); + } } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSourceTest.java index 3205bca523..a08989c8ce 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSourceTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSourceTest.java @@ -57,7 +57,12 @@ void setup() { .thenReturn(mock(SecondaryToPrimaryMapper.class)); when(informerEventSourceConfiguration.getResourceClass()).thenReturn(Deployment.class); - informerEventSource = new InformerEventSource<>(informerEventSourceConfiguration, clientMock); + informerEventSource = + new InformerEventSource<>(informerEventSourceConfiguration, clientMock) { + // mocking start + @Override + public synchronized void start() {} + }; var mockControllerConfig = mock(ControllerConfiguration.class); when(mockControllerConfig.getConfigurationService()).thenReturn(new BaseConfigurationService()); diff --git a/operator-framework-junit5/pom.xml b/operator-framework-junit5/pom.xml index 7e68616edf..8c8a349af0 100644 --- a/operator-framework-junit5/pom.xml +++ b/operator-framework-junit5/pom.xml @@ -4,7 +4,7 @@ io.javaoperatorsdk java-operator-sdk - 5.0.5-SNAPSHOT + 5.1.3-SNAPSHOT operator-framework-junit-5 diff --git a/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/AbstractOperatorExtension.java b/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/AbstractOperatorExtension.java index 00bf7e8380..794bc11d9a 100644 --- a/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/AbstractOperatorExtension.java +++ b/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/AbstractOperatorExtension.java @@ -111,6 +111,10 @@ public T create(T resource) { return kubernetesClient.resource(resource).inNamespace(namespace).create(); } + public T serverSideApply(T resource) { + return kubernetesClient.resource(resource).inNamespace(namespace).serverSideApply(); + } + public T replace(T resource) { return kubernetesClient.resource(resource).inNamespace(namespace).replace(); } diff --git a/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtension.java b/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtension.java index 3e6ad35e52..54cb57544d 100644 --- a/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtension.java +++ b/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtension.java @@ -54,6 +54,7 @@ public class LocallyRunOperatorExtension extends AbstractOperatorExtension { private final List> additionalCustomResourceDefinitions; private final Map registeredControllers; private final Map crdMappings; + private final Consumer beforeStartHook; private LocallyRunOperatorExtension( List reconcilers, @@ -68,7 +69,8 @@ private LocallyRunOperatorExtension( Consumer configurationServiceOverrider, Function namespaceNameSupplier, Function perClassNamespaceNameSupplier, - List additionalCrds) { + List additionalCrds, + Consumer beforeStartHook) { super( infrastructure, infrastructureTimeout, @@ -82,6 +84,7 @@ private LocallyRunOperatorExtension( this.portForwards = portForwards; this.localPortForwards = new ArrayList<>(portForwards.size()); this.additionalCustomResourceDefinitions = additionalCustomResourceDefinitions; + this.beforeStartHook = beforeStartHook; configurationServiceOverrider = configurationServiceOverrider != null ? configurationServiceOverrider.andThen( @@ -298,6 +301,10 @@ protected void before(ExtensionContext context) { }); crdMappings.clear(); + if (beforeStartHook != null) { + beforeStartHook.accept(this); + } + LOGGER.debug("Starting the operator locally"); this.operator.start(); } @@ -356,6 +363,7 @@ public static class Builder extends AbstractBuilder { private final List portForwards; private final List> additionalCustomResourceDefinitions; private final List additionalCRDs = new ArrayList<>(); + private Consumer beforeStartHook; private KubernetesClient kubernetesClient; protected Builder() { @@ -424,6 +432,15 @@ public Builder withAdditionalCRD(String... paths) { return this; } + /** + * Used to initialize resources when the namespace is generated but the operator is not started + * yet. + */ + public Builder withBeforeStartHook(Consumer beforeStartHook) { + this.beforeStartHook = beforeStartHook; + return this; + } + public LocallyRunOperatorExtension build() { return new LocallyRunOperatorExtension( reconcilers, @@ -438,7 +455,8 @@ public LocallyRunOperatorExtension build() { configurationServiceOverrider, namespaceNameSupplier, perClassNamespaceNameSupplier, - additionalCRDs); + additionalCRDs, + beforeStartHook); } } diff --git a/operator-framework/pom.xml b/operator-framework/pom.xml index d72f91d293..9324f16835 100644 --- a/operator-framework/pom.xml +++ b/operator-framework/pom.xml @@ -4,7 +4,7 @@ io.javaoperatorsdk java-operator-sdk - 5.0.5-SNAPSHOT + 5.1.3-SNAPSHOT operator-framework diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/primarycache/StatusPatchPrimaryCacheCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/startsecondaryaccess/StartupSecondaryAccessCustomResource.java similarity index 60% rename from operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/primarycache/StatusPatchPrimaryCacheCustomResource.java rename to operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/startsecondaryaccess/StartupSecondaryAccessCustomResource.java index 84b145cac3..b9701c94bd 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/primarycache/StatusPatchPrimaryCacheCustomResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/startsecondaryaccess/StartupSecondaryAccessCustomResource.java @@ -1,4 +1,4 @@ -package io.javaoperatorsdk.operator.baseapi.statuscache.primarycache; +package io.javaoperatorsdk.operator.baseapi.startsecondaryaccess; import io.fabric8.kubernetes.api.model.Namespaced; import io.fabric8.kubernetes.client.CustomResource; @@ -8,7 +8,6 @@ @Group("sample.javaoperatorsdk") @Version("v1") -@ShortNames("spc") -public class StatusPatchPrimaryCacheCustomResource - extends CustomResource +@ShortNames("ssac") +public class StartupSecondaryAccessCustomResource extends CustomResource implements Namespaced {} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/startsecondaryaccess/StartupSecondaryAccessIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/startsecondaryaccess/StartupSecondaryAccessIT.java new file mode 100644 index 0000000000..61fc40803c --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/startsecondaryaccess/StartupSecondaryAccessIT.java @@ -0,0 +1,53 @@ +package io.javaoperatorsdk.operator.baseapi.startsecondaryaccess; + +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; + +import static io.javaoperatorsdk.operator.baseapi.startsecondaryaccess.StartupSecondaryAccessReconciler.LABEL_KEY; +import static io.javaoperatorsdk.operator.baseapi.startsecondaryaccess.StartupSecondaryAccessReconciler.LABEL_VALUE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +class StartupSecondaryAccessIT { + + public static final int SECONDARY_NUMBER = 200; + + @RegisterExtension + static LocallyRunOperatorExtension extension = + LocallyRunOperatorExtension.builder() + .withReconciler(new StartupSecondaryAccessReconciler()) + .withBeforeStartHook( + ex -> { + var primary = new StartupSecondaryAccessCustomResource(); + primary.setMetadata(new ObjectMetaBuilder().withName("test1").build()); + primary = ex.serverSideApply(primary); + + for (int i = 0; i < SECONDARY_NUMBER; i++) { + ConfigMap cm = new ConfigMap(); + cm.setMetadata( + new ObjectMetaBuilder() + .withLabels(Map.of(LABEL_KEY, LABEL_VALUE)) + .withNamespace(ex.getNamespace()) + .withName("cm" + i) + .build()); + cm.addOwnerReference(primary); + ex.serverSideApply(cm); + } + }) + .build(); + + @Test + void reconcilerSeeAllSecondaryResources() { + var reconciler = extension.getReconcilerOfType(StartupSecondaryAccessReconciler.class); + + await().untilAsserted(() -> assertThat(reconciler.isReconciled()).isTrue()); + + assertThat(reconciler.isSecondaryAndCacheSameAmount()).isTrue(); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/startsecondaryaccess/StartupSecondaryAccessReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/startsecondaryaccess/StartupSecondaryAccessReconciler.java new file mode 100644 index 0000000000..a2c51fdafd --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/startsecondaryaccess/StartupSecondaryAccessReconciler.java @@ -0,0 +1,75 @@ +package io.javaoperatorsdk.operator.baseapi.startsecondaryaccess; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.javaoperatorsdk.operator.api.config.informer.InformerEventSourceConfiguration; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; +import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; +import io.javaoperatorsdk.operator.api.reconciler.Reconciler; +import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; +import io.javaoperatorsdk.operator.processing.event.source.EventSource; +import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; + +import static io.javaoperatorsdk.operator.baseapi.startsecondaryaccess.StartupSecondaryAccessIT.SECONDARY_NUMBER; + +@ControllerConfiguration +public class StartupSecondaryAccessReconciler + implements Reconciler { + + private static final Logger log = LoggerFactory.getLogger(StartupSecondaryAccessReconciler.class); + + public static final String LABEL_KEY = "app"; + public static final String LABEL_VALUE = "secondary-test"; + + private InformerEventSource cmInformer; + + private boolean secondaryAndCacheSameAmount = true; + private boolean reconciled = false; + + @Override + public UpdateControl reconcile( + StartupSecondaryAccessCustomResource resource, + Context context) { + + var secondary = context.getSecondaryResources(ConfigMap.class); + var cached = cmInformer.list().toList(); + + log.info( + "Secondary number: {}, cached: {}, expected: {}", + secondary.size(), + cached.size(), + SECONDARY_NUMBER); + + if (secondary.size() != cached.size()) { + secondaryAndCacheSameAmount = false; + } + reconciled = true; + return UpdateControl.noUpdate(); + } + + @Override + public List> prepareEventSources( + EventSourceContext context) { + cmInformer = + new InformerEventSource<>( + InformerEventSourceConfiguration.from( + ConfigMap.class, StartupSecondaryAccessCustomResource.class) + .withLabelSelector(LABEL_KEY + "=" + LABEL_VALUE) + .build(), + context); + return List.of(cmInformer); + } + + public boolean isSecondaryAndCacheSameAmount() { + return secondaryAndCacheSameAmount; + } + + public boolean isReconciled() { + return reconciled; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/internal/StatusPatchCacheCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/StatusPatchCacheCustomResource.java similarity index 84% rename from operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/internal/StatusPatchCacheCustomResource.java rename to operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/StatusPatchCacheCustomResource.java index 2a2d8b83fd..e87d8e8714 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/internal/StatusPatchCacheCustomResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/StatusPatchCacheCustomResource.java @@ -1,4 +1,4 @@ -package io.javaoperatorsdk.operator.baseapi.statuscache.internal; +package io.javaoperatorsdk.operator.baseapi.statuscache; import io.fabric8.kubernetes.api.model.Namespaced; import io.fabric8.kubernetes.client.CustomResource; @@ -8,6 +8,6 @@ @Group("sample.javaoperatorsdk") @Version("v1") -@ShortNames("spcl") +@ShortNames("spwl") public class StatusPatchCacheCustomResource extends CustomResource implements Namespaced {} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/internal/StatusPatchCacheIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/StatusPatchCacheIT.java similarity index 95% rename from operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/internal/StatusPatchCacheIT.java rename to operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/StatusPatchCacheIT.java index f78511f250..9d0b923056 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/internal/StatusPatchCacheIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/StatusPatchCacheIT.java @@ -1,4 +1,4 @@ -package io.javaoperatorsdk.operator.baseapi.statuscache.internal; +package io.javaoperatorsdk.operator.baseapi.statuscache; import java.time.Duration; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/internal/StatusPatchCacheReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/StatusPatchCacheReconciler.java similarity index 86% rename from operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/internal/StatusPatchCacheReconciler.java rename to operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/StatusPatchCacheReconciler.java index 8a3a72a901..69215d6d01 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/internal/StatusPatchCacheReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/StatusPatchCacheReconciler.java @@ -1,4 +1,4 @@ -package io.javaoperatorsdk.operator.baseapi.statuscache.internal; +package io.javaoperatorsdk.operator.baseapi.statuscache; import java.util.List; @@ -9,7 +9,6 @@ import io.javaoperatorsdk.operator.api.reconciler.PrimaryUpdateAndCacheUtils; import io.javaoperatorsdk.operator.api.reconciler.Reconciler; import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; -import io.javaoperatorsdk.operator.baseapi.statuscache.PeriodicTriggerEventSource; import io.javaoperatorsdk.operator.processing.event.source.EventSource; @ControllerConfiguration @@ -31,13 +30,18 @@ public UpdateControl reconcile( + resource.getStatus().getValue()); } + // test also resource update happening meanwhile reconciliation + resource.getSpec().setCounter(resource.getSpec().getCounter() + 1); + context.getClient().resource(resource).update(); + var freshCopy = createFreshCopy(resource); freshCopy .getStatus() .setValue(resource.getStatus() == null ? 1 : resource.getStatus().getValue() + 1); - var updated = PrimaryUpdateAndCacheUtils.ssaPatchAndCacheStatus(resource, freshCopy, context); + var updated = + PrimaryUpdateAndCacheUtils.ssaPatchStatusAndCacheResource(resource, freshCopy, context); latestValue = updated.getStatus().getValue(); return UpdateControl.noUpdate(); @@ -58,7 +62,6 @@ private StatusPatchCacheCustomResource createFreshCopy(StatusPatchCacheCustomRes .withNamespace(resource.getMetadata().getNamespace()) .build()); res.setStatus(new StatusPatchCacheStatus()); - return res; } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/internal/StatusPatchCacheSpec.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/StatusPatchCacheSpec.java similarity index 74% rename from operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/internal/StatusPatchCacheSpec.java rename to operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/StatusPatchCacheSpec.java index d1426fd943..0885b6a858 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/internal/StatusPatchCacheSpec.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/StatusPatchCacheSpec.java @@ -1,4 +1,4 @@ -package io.javaoperatorsdk.operator.baseapi.statuscache.internal; +package io.javaoperatorsdk.operator.baseapi.statuscache; public class StatusPatchCacheSpec { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/internal/StatusPatchCacheStatus.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/StatusPatchCacheStatus.java similarity index 77% rename from operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/internal/StatusPatchCacheStatus.java rename to operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/StatusPatchCacheStatus.java index 00bc4b6f04..5918b2e3b8 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/internal/StatusPatchCacheStatus.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/StatusPatchCacheStatus.java @@ -1,4 +1,4 @@ -package io.javaoperatorsdk.operator.baseapi.statuscache.internal; +package io.javaoperatorsdk.operator.baseapi.statuscache; public class StatusPatchCacheStatus { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/primarycache/StatusPatchPrimaryCacheIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/primarycache/StatusPatchPrimaryCacheIT.java deleted file mode 100644 index a884ec0758..0000000000 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/primarycache/StatusPatchPrimaryCacheIT.java +++ /dev/null @@ -1,48 +0,0 @@ -package io.javaoperatorsdk.operator.baseapi.statuscache.primarycache; - -import java.time.Duration; - -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 StatusPatchPrimaryCacheIT { - - public static final String TEST_1 = "test1"; - - @RegisterExtension - LocallyRunOperatorExtension extension = - LocallyRunOperatorExtension.builder() - .withReconciler(StatusPatchPrimaryCacheReconciler.class) - .build(); - - @Test - void testStatusAlwaysUpToDate() { - var reconciler = extension.getReconcilerOfType(StatusPatchPrimaryCacheReconciler.class); - - extension.create(testResource()); - - // the reconciliation is periodically triggered, the status values should be increasing - // monotonically - await() - .pollDelay(Duration.ofSeconds(1)) - .pollInterval(Duration.ofMillis(30)) - .untilAsserted( - () -> { - assertThat(reconciler.errorPresent).isFalse(); - assertThat(reconciler.latestValue).isGreaterThan(10); - }); - } - - StatusPatchPrimaryCacheCustomResource testResource() { - var res = new StatusPatchPrimaryCacheCustomResource(); - res.setMetadata(new ObjectMetaBuilder().withName(TEST_1).build()); - res.setSpec(new StatusPatchPrimaryCacheSpec()); - return res; - } -} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/primarycache/StatusPatchPrimaryCacheReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/primarycache/StatusPatchPrimaryCacheReconciler.java deleted file mode 100644 index c25fcddfec..0000000000 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/primarycache/StatusPatchPrimaryCacheReconciler.java +++ /dev/null @@ -1,89 +0,0 @@ -package io.javaoperatorsdk.operator.baseapi.statuscache.primarycache; - -import java.util.List; - -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.EventSourceContext; -import io.javaoperatorsdk.operator.api.reconciler.PrimaryUpdateAndCacheUtils; -import io.javaoperatorsdk.operator.api.reconciler.Reconciler; -import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; -import io.javaoperatorsdk.operator.api.reconciler.support.PrimaryResourceCache; -import io.javaoperatorsdk.operator.baseapi.statuscache.PeriodicTriggerEventSource; -import io.javaoperatorsdk.operator.processing.event.source.EventSource; - -@ControllerConfiguration -public class StatusPatchPrimaryCacheReconciler - implements Reconciler, - Cleaner { - - public volatile int latestValue = 0; - public volatile boolean errorPresent = false; - - // We on purpose don't use the provided predicate to show what a custom one could look like. - private final PrimaryResourceCache cache = - new PrimaryResourceCache<>( - (statusPatchCacheCustomResourcePair, statusPatchCacheCustomResource) -> - statusPatchCacheCustomResource.getStatus().getValue() - >= statusPatchCacheCustomResourcePair.afterUpdate().getStatus().getValue()); - - @Override - public UpdateControl reconcile( - StatusPatchPrimaryCacheCustomResource primary, - Context context) { - - primary = cache.getFreshResource(primary); - - if (primary.getStatus() != null && primary.getStatus().getValue() != latestValue) { - errorPresent = true; - throw new IllegalStateException( - "status is not up to date. Latest value: " - + latestValue - + " status values: " - + primary.getStatus().getValue()); - } - - var freshCopy = createFreshCopy(primary); - freshCopy - .getStatus() - .setValue(primary.getStatus() == null ? 1 : primary.getStatus().getValue() + 1); - - var updated = - PrimaryUpdateAndCacheUtils.ssaPatchAndCacheStatus(primary, freshCopy, context, cache); - latestValue = updated.getStatus().getValue(); - - return UpdateControl.noUpdate(); - } - - @Override - public List> prepareEventSources( - EventSourceContext context) { - // periodic event triggering for testing purposes - return List.of(new PeriodicTriggerEventSource<>(context.getPrimaryCache())); - } - - private StatusPatchPrimaryCacheCustomResource createFreshCopy( - StatusPatchPrimaryCacheCustomResource resource) { - var res = new StatusPatchPrimaryCacheCustomResource(); - res.setMetadata( - new ObjectMetaBuilder() - .withName(resource.getMetadata().getName()) - .withNamespace(resource.getMetadata().getNamespace()) - .build()); - res.setStatus(new StatusPatchPrimaryCacheStatus()); - - return res; - } - - @Override - public DeleteControl cleanup( - StatusPatchPrimaryCacheCustomResource resource, - Context context) - throws Exception { - cache.cleanup(resource); - return DeleteControl.defaultDelete(); - } -} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/primarycache/StatusPatchPrimaryCacheSpec.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/primarycache/StatusPatchPrimaryCacheSpec.java deleted file mode 100644 index 90630c1ae8..0000000000 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/primarycache/StatusPatchPrimaryCacheSpec.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.javaoperatorsdk.operator.baseapi.statuscache.primarycache; - -public class StatusPatchPrimaryCacheSpec { - - private boolean messageInStatus = true; - - public boolean isMessageInStatus() { - return messageInStatus; - } - - public StatusPatchPrimaryCacheSpec setMessageInStatus(boolean messageInStatus) { - this.messageInStatus = messageInStatus; - return this; - } -} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/primarycache/StatusPatchPrimaryCacheStatus.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/primarycache/StatusPatchPrimaryCacheStatus.java deleted file mode 100644 index 0687d5576a..0000000000 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/primarycache/StatusPatchPrimaryCacheStatus.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.javaoperatorsdk.operator.baseapi.statuscache.primarycache; - -public class StatusPatchPrimaryCacheStatus { - - private Integer value = 0; - - public Integer getValue() { - return value; - } - - public StatusPatchPrimaryCacheStatus setValue(Integer value) { - this.value = value; - return this; - } -} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/unmodifiabledependentpart/UnmodifiablePartConfigMapDependent.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/unmodifiabledependentpart/UnmodifiablePartConfigMapDependent.java index a4559055a4..c6f0759410 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/unmodifiabledependentpart/UnmodifiablePartConfigMapDependent.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/unmodifiabledependentpart/UnmodifiablePartConfigMapDependent.java @@ -14,10 +14,6 @@ public class UnmodifiablePartConfigMapDependent public static final String UNMODIFIABLE_INITIAL_DATA_KEY = "initialDataKey"; public static final String ACTUAL_DATA_KEY = "actualDataKey"; - public UnmodifiablePartConfigMapDependent() { - super(ConfigMap.class); - } - @Override protected ConfigMap desired( UnmodifiableDependentPartCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/BaseConfigurationServiceTest.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/BaseConfigurationServiceTest.java index 2f202436ff..25926e6405 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/BaseConfigurationServiceTest.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/BaseConfigurationServiceTest.java @@ -414,12 +414,7 @@ public UpdateControl reconcile( @KubernetesDependent(useSSA = BooleanWithUndefined.TRUE) public static class WithAnnotation - extends CRUDKubernetesDependentResource { - - public WithAnnotation() { - super(ConfigMap.class); - } - } + extends CRUDKubernetesDependentResource {} } public static class MissingAnnotationReconciler implements Reconciler { @@ -443,18 +438,10 @@ public UpdateControl reconcile(ConfigMap resource, Context } private static class DefaultDependent - extends KubernetesDependentResource { - public DefaultDependent() { - super(ConfigMapReader.class); - } - } + extends KubernetesDependentResource {} @KubernetesDependent(useSSA = BooleanWithUndefined.FALSE) - private static class NonSSADependent extends KubernetesDependentResource { - public NonSSADependent() { - super(Service.class); - } - } + private static class NonSSADependent extends KubernetesDependentResource {} } public static class TestRetry implements Retry, AnnotationConfigurable { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/bulkdependent/ConfigMapDeleterBulkDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/bulkdependent/ConfigMapDeleterBulkDependentResource.java index a9163a4ec3..cf3c96b82a 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/bulkdependent/ConfigMapDeleterBulkDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/bulkdependent/ConfigMapDeleterBulkDependentResource.java @@ -21,10 +21,6 @@ public class ConfigMapDeleterBulkDependentResource public static final String ADDITIONAL_DATA_KEY = "additionalData"; public static final String INDEX_DELIMITER = "-"; - public ConfigMapDeleterBulkDependentResource() { - super(ConfigMap.class); - } - @Override public Map desiredResources( BulkDependentTestCustomResource primary, Context context) { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/bulkdependent/readonly/ReadOnlyBulkDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/bulkdependent/readonly/ReadOnlyBulkDependentResource.java index b61ad7c230..1eab400888 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/bulkdependent/readonly/ReadOnlyBulkDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/bulkdependent/readonly/ReadOnlyBulkDependentResource.java @@ -23,10 +23,6 @@ public class ReadOnlyBulkDependentResource public static final String INDEX_DELIMITER = "-"; - public ReadOnlyBulkDependentResource() { - super(ConfigMap.class); - } - @Override public Map getSecondaryResources( BulkDependentTestCustomResource primary, Context context) { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/cleanermanageddependent/ConfigMapDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/cleanermanageddependent/ConfigMapDependentResource.java index dfb3cf2ee2..3c94775045 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/cleanermanageddependent/ConfigMapDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/cleanermanageddependent/ConfigMapDependentResource.java @@ -19,10 +19,6 @@ public class ConfigMapDependentResource private static final AtomicInteger numberOfCleanupExecutions = new AtomicInteger(0); - public ConfigMapDependentResource() { - super(ConfigMap.class); - } - @Override protected ConfigMap desired( CleanerForManagedDependentCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/createonlyifnotexistsdependentwithssa/ConfigMapDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/createonlyifnotexistsdependentwithssa/ConfigMapDependentResource.java index 14ba61513a..67532ee159 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/createonlyifnotexistsdependentwithssa/ConfigMapDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/createonlyifnotexistsdependentwithssa/ConfigMapDependentResource.java @@ -11,10 +11,6 @@ public class ConfigMapDependentResource extends CRUDKubernetesDependentResource< ConfigMap, CreateOnlyIfNotExistingDependentWithSSACustomResource> { - public ConfigMapDependentResource() { - super(ConfigMap.class); - } - @Override protected ConfigMap desired( CreateOnlyIfNotExistingDependentWithSSACustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentannotationsecondarymapper/DependentAnnotationSecondaryMapperReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentannotationsecondarymapper/DependentAnnotationSecondaryMapperReconciler.java index 2ba4ee5ef0..71146df638 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentannotationsecondarymapper/DependentAnnotationSecondaryMapperReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentannotationsecondarymapper/DependentAnnotationSecondaryMapperReconciler.java @@ -41,10 +41,6 @@ public static class ConfigMapDependentResource Updater, Deleter { - public ConfigMapDependentResource() { - super(ConfigMap.class); - } - @Override protected ConfigMap desired( DependentAnnotationSecondaryMapperResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentcustommappingannotation/CustomMappingConfigMapDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentcustommappingannotation/CustomMappingConfigMapDependentResource.java index 0a3aeba0e1..081cf31dbd 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentcustommappingannotation/CustomMappingConfigMapDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentcustommappingannotation/CustomMappingConfigMapDependentResource.java @@ -30,10 +30,6 @@ public class CustomMappingConfigMapDependentResource CUSTOM_TYPE_KEY, DependentCustomMappingCustomResource.class); - public CustomMappingConfigMapDependentResource() { - super(ConfigMap.class); - } - @Override protected ConfigMap desired( DependentCustomMappingCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentdifferentnamespace/ConfigMapDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentdifferentnamespace/ConfigMapDependentResource.java index f4dc408825..30e0de5b7d 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentdifferentnamespace/ConfigMapDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentdifferentnamespace/ConfigMapDependentResource.java @@ -15,10 +15,6 @@ public class ConfigMapDependentResource public static final String NAMESPACE = "default"; - public ConfigMapDependentResource() { - super(ConfigMap.class); - } - @Override protected ConfigMap desired( DependentDifferentNamespaceCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentfilter/FilteredDependentConfigMap.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentfilter/FilteredDependentConfigMap.java index 3ba4df63f4..3b12673b4c 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentfilter/FilteredDependentConfigMap.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentfilter/FilteredDependentConfigMap.java @@ -15,10 +15,6 @@ public class FilteredDependentConfigMap extends CRUDKubernetesDependentResource { - public FilteredDependentConfigMap() { - super(ConfigMap.class); - } - @Override protected ConfigMap desired( DependentFilterTestCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentoperationeventfiltering/ConfigMapDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentoperationeventfiltering/ConfigMapDependentResource.java index 13879227e1..87b827c527 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentoperationeventfiltering/ConfigMapDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentoperationeventfiltering/ConfigMapDependentResource.java @@ -13,10 +13,6 @@ public class ConfigMapDependentResource public static final String KEY = "key1"; - public ConfigMapDependentResource() { - super(ConfigMap.class); - } - @Override protected ConfigMap desired( DependentOperationEventFilterCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentreinitialization/ConfigMapDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentreinitialization/ConfigMapDependentResource.java index 704febbf67..2a245a3721 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentreinitialization/ConfigMapDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentreinitialization/ConfigMapDependentResource.java @@ -11,10 +11,6 @@ public class ConfigMapDependentResource extends CRUDKubernetesDependentResource { - public ConfigMapDependentResource() { - super(ConfigMap.class); - } - @Override protected ConfigMap desired( DependentReInitializationCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentresourcecrossref/DependentResourceCrossRefIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentresourcecrossref/DependentResourceCrossRefIT.java index ae5cd25895..1b71c79448 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentresourcecrossref/DependentResourceCrossRefIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentresourcecrossref/DependentResourceCrossRefIT.java @@ -44,6 +44,22 @@ void dependentResourceCanReferenceEachOther() { assertThat(operator.get(Secret.class, TEST_RESOURCE_NAME + i)).isNotNull(); } }); + + for (int i = 0; i < EXECUTION_NUMBER; i++) { + operator.delete(testResource(i)); + } + await() + .timeout(Duration.ofSeconds(30)) + .untilAsserted( + () -> { + for (int i = 0; i < EXECUTION_NUMBER; i++) { + assertThat( + operator.get( + DependentResourceCrossRefResource.class, + testResource(i).getMetadata().getName())) + .isNull(); + } + }); } DependentResourceCrossRefResource testResource(int n) { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentresourcecrossref/DependentResourceCrossRefReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentresourcecrossref/DependentResourceCrossRefReconciler.java index 51a285aa4b..247174838c 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentresourcecrossref/DependentResourceCrossRefReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentresourcecrossref/DependentResourceCrossRefReconciler.java @@ -5,6 +5,9 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.Secret; @@ -26,6 +29,8 @@ @ControllerConfiguration public class DependentResourceCrossRefReconciler implements Reconciler { + private static final Logger log = + LoggerFactory.getLogger(DependentResourceCrossRefReconciler.class); public static final String SECRET_NAME = "secret"; private final AtomicInteger numberOfExecutions = new AtomicInteger(0); @@ -48,6 +53,7 @@ public ErrorStatusUpdateControl updateErrorSt DependentResourceCrossRefResource resource, Context context, Exception e) { + log.error("Status update on error", e); errorHappened = true; return ErrorStatusUpdateControl.noStatusUpdate(); } @@ -59,10 +65,6 @@ public boolean isErrorHappened() { public static class SecretDependentResource extends CRUDKubernetesDependentResource { - public SecretDependentResource() { - super(Secret.class); - } - @Override protected Secret desired( DependentResourceCrossRefResource primary, @@ -81,10 +83,6 @@ protected Secret desired( public static class ConfigMapDependentResource extends CRUDKubernetesDependentResource { - public ConfigMapDependentResource() { - super(ConfigMap.class); - } - @Override protected ConfigMap desired( DependentResourceCrossRefResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentssa/SSAConfigMapDependent.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentssa/SSAConfigMapDependent.java index dc47f1f8df..49d2c1de44 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentssa/SSAConfigMapDependent.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentssa/SSAConfigMapDependent.java @@ -16,10 +16,6 @@ public class SSAConfigMapDependent public static final String DATA_KEY = "key1"; - public SSAConfigMapDependent() { - super(ConfigMap.class); - } - @Override protected ConfigMap desired( DependentSSACustomResource primary, Context context) { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/ConfigMapDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/ConfigMapDependentResource.java index 57427a3537..348921cd93 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/ConfigMapDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/ConfigMapDependentResource.java @@ -16,10 +16,6 @@ public class ConfigMapDependentResource public static final String DATA_KEY = "key"; - public ConfigMapDependentResource() { - super(ConfigMap.class); - } - @Override protected ConfigMap desired( InformerRelatedBehaviorTestCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/InformerRelatedBehaviorITS.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/InformerRelatedBehaviorITS.java index 8686d6f33b..ec50e058b9 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/InformerRelatedBehaviorITS.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/InformerRelatedBehaviorITS.java @@ -368,35 +368,34 @@ Operator startOperator( } private void setNoConfigMapAccess() { - applyClusterRole("rback-test-no-configmap-access.yaml"); + applyClusterRole("rbac-test-no-configmap-access.yaml"); applyClusterRoleBinding(); } private void setNoCustomResourceAccess() { - applyClusterRole("rback-test-no-cr-access.yaml"); + applyClusterRole("rbac-test-no-cr-access.yaml"); applyClusterRoleBinding(); } private void setFullResourcesAccess() { - applyClusterRole("rback-test-full-access-role.yaml"); + applyClusterRole("rbac-test-full-access-role.yaml"); applyClusterRoleBinding(); } private void addRoleBindingsToTestNamespaces() { var role = - ReconcilerUtils.loadYaml( - Role.class, this.getClass(), "rback-test-only-main-ns-access.yaml"); + ReconcilerUtils.loadYaml(Role.class, this.getClass(), "rbac-test-only-main-ns-access.yaml"); adminClient.resource(role).inNamespace(actualNamespace).createOrReplace(); var roleBinding = ReconcilerUtils.loadYaml( - RoleBinding.class, this.getClass(), "rback-test-only-main-ns-access-binding.yaml"); + RoleBinding.class, this.getClass(), "rbac-test-only-main-ns-access-binding.yaml"); adminClient.resource(roleBinding).inNamespace(actualNamespace).createOrReplace(); } private void applyClusterRoleBinding() { var clusterRoleBinding = ReconcilerUtils.loadYaml( - ClusterRoleBinding.class, this.getClass(), "rback-test-role-binding.yaml"); + ClusterRoleBinding.class, this.getClass(), "rbac-test-role-binding.yaml"); adminClient.resource(clusterRoleBinding).createOrReplace(); } @@ -418,7 +417,7 @@ private Namespace namespace(String name) { private void removeClusterRoleBinding() { var clusterRoleBinding = ReconcilerUtils.loadYaml( - ClusterRoleBinding.class, this.getClass(), "rback-test-role-binding.yaml"); + ClusterRoleBinding.class, this.getClass(), "rbac-test-role-binding.yaml"); adminClient.resource(clusterRoleBinding).delete(); } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/kubernetesdependentgarbagecollection/DependentGarbageCollectionTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/kubernetesdependentgarbagecollection/DependentGarbageCollectionTestReconciler.java index 36af4fadb4..880aaa6884 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/kubernetesdependentgarbagecollection/DependentGarbageCollectionTestReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/kubernetesdependentgarbagecollection/DependentGarbageCollectionTestReconciler.java @@ -70,10 +70,6 @@ private static class ConfigMapDependentResource Updater, GarbageCollected { - public ConfigMapDependentResource() { - super(ConfigMap.class); - } - @Override protected ConfigMap desired( DependentGarbageCollectionTestCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multipledependentsametypemultiinformer/MultipleManagedDependentResourceMultiInformerConfigMap1.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multipledependentsametypemultiinformer/MultipleManagedDependentResourceMultiInformerConfigMap1.java index 2ea2b1daba..855944ef98 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multipledependentsametypemultiinformer/MultipleManagedDependentResourceMultiInformerConfigMap1.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multipledependentsametypemultiinformer/MultipleManagedDependentResourceMultiInformerConfigMap1.java @@ -17,10 +17,6 @@ public class MultipleManagedDependentResourceMultiInformerConfigMap1 public static final String NAME_SUFFIX = "-1"; - public MultipleManagedDependentResourceMultiInformerConfigMap1() { - super(ConfigMap.class); - } - @Override protected ConfigMap desired( MultipleManagedDependentResourceMultiInformerCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multipledependentsametypemultiinformer/MultipleManagedDependentResourceMultiInformerConfigMap2.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multipledependentsametypemultiinformer/MultipleManagedDependentResourceMultiInformerConfigMap2.java index dbc0934ada..7b28b322b7 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multipledependentsametypemultiinformer/MultipleManagedDependentResourceMultiInformerConfigMap2.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multipledependentsametypemultiinformer/MultipleManagedDependentResourceMultiInformerConfigMap2.java @@ -18,10 +18,6 @@ public class MultipleManagedDependentResourceMultiInformerConfigMap2 public static final String NAME_SUFFIX = "-2"; - public MultipleManagedDependentResourceMultiInformerConfigMap2() { - super(ConfigMap.class); - } - @Override protected ConfigMap desired( MultipleManagedDependentResourceMultiInformerCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multipledrsametypenodiscriminator/MultipleManagedDependentNoDiscriminatorConfigMap1.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multipledrsametypenodiscriminator/MultipleManagedDependentNoDiscriminatorConfigMap1.java index 94584e8172..2fb65c6dde 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multipledrsametypenodiscriminator/MultipleManagedDependentNoDiscriminatorConfigMap1.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multipledrsametypenodiscriminator/MultipleManagedDependentNoDiscriminatorConfigMap1.java @@ -17,10 +17,6 @@ public class MultipleManagedDependentNoDiscriminatorConfigMap1 public static final String NAME_SUFFIX = "-1"; - public MultipleManagedDependentNoDiscriminatorConfigMap1() { - super(ConfigMap.class); - } - /* * Showcases optimization to avoid computing the whole desired state by providing the ResourceID * of the target resource. In this particular case this would not be necessary, since desired diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multipledrsametypenodiscriminator/MultipleManagedDependentNoDiscriminatorConfigMap2.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multipledrsametypenodiscriminator/MultipleManagedDependentNoDiscriminatorConfigMap2.java index 8836badb1f..4455ef5d9b 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multipledrsametypenodiscriminator/MultipleManagedDependentNoDiscriminatorConfigMap2.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multipledrsametypenodiscriminator/MultipleManagedDependentNoDiscriminatorConfigMap2.java @@ -18,10 +18,6 @@ public class MultipleManagedDependentNoDiscriminatorConfigMap2 public static final String NAME_SUFFIX = "-2"; - public MultipleManagedDependentNoDiscriminatorConfigMap2() { - super(ConfigMap.class); - } - @Override protected ConfigMap desired( MultipleManagedDependentNoDiscriminatorCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multiplemanageddependentsametype/MultipleManagedDependentResourceConfigMap1.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multiplemanageddependentsametype/MultipleManagedDependentResourceConfigMap1.java index 38e2acc050..687bfcb5ac 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multiplemanageddependentsametype/MultipleManagedDependentResourceConfigMap1.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multiplemanageddependentsametype/MultipleManagedDependentResourceConfigMap1.java @@ -16,10 +16,6 @@ public class MultipleManagedDependentResourceConfigMap1 public static final String NAME_SUFFIX = "-1"; - public MultipleManagedDependentResourceConfigMap1() { - super(ConfigMap.class); - } - @Override protected ConfigMap desired( MultipleManagedDependentResourceCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multiplemanageddependentsametype/MultipleManagedDependentResourceConfigMap2.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multiplemanageddependentsametype/MultipleManagedDependentResourceConfigMap2.java index 95dcc66490..1a1d6b51c0 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multiplemanageddependentsametype/MultipleManagedDependentResourceConfigMap2.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multiplemanageddependentsametype/MultipleManagedDependentResourceConfigMap2.java @@ -16,10 +16,6 @@ public class MultipleManagedDependentResourceConfigMap2 public static final String NAME_SUFFIX = "-2"; - public MultipleManagedDependentResourceConfigMap2() { - super(ConfigMap.class); - } - @Override protected ConfigMap desired( MultipleManagedDependentResourceCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multipleupdateondependent/MultipleOwnerDependentConfigMap.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multipleupdateondependent/MultipleOwnerDependentConfigMap.java index 781451bba0..28ddfcc907 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multipleupdateondependent/MultipleOwnerDependentConfigMap.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multipleupdateondependent/MultipleOwnerDependentConfigMap.java @@ -19,10 +19,6 @@ public class MultipleOwnerDependentConfigMap public static final String RESOURCE_NAME = "test1"; - public MultipleOwnerDependentConfigMap() { - super(ConfigMap.class); - } - @Override protected ConfigMap desired( MultipleOwnerDependentCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/prevblocklist/DeploymentDependent.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/prevblocklist/DeploymentDependent.java new file mode 100644 index 0000000000..5cfb66f67e --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/prevblocklist/DeploymentDependent.java @@ -0,0 +1,95 @@ +package io.javaoperatorsdk.operator.dependent.prevblocklist; + +import java.util.Map; + +import io.fabric8.kubernetes.api.model.ContainerBuilder; +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.LabelSelectorBuilder; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.fabric8.kubernetes.api.model.PodSpecBuilder; +import io.fabric8.kubernetes.api.model.PodTemplateSpecBuilder; +import io.fabric8.kubernetes.api.model.Quantity; +import io.fabric8.kubernetes.api.model.ResourceRequirementsBuilder; +import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder; +import io.fabric8.kubernetes.api.model.apps.DeploymentSpecBuilder; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.GenericKubernetesResourceMatcher; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.SSABasedGenericKubernetesResourceMatcher; + +@KubernetesDependent +public class DeploymentDependent + extends CRUDKubernetesDependentResource { + + public static final String RESOURCE_NAME = "test1"; + + public DeploymentDependent() { + super(Deployment.class); + } + + @Override + protected Deployment desired( + PrevAnnotationBlockCustomResource primary, + Context context) { + + return new DeploymentBuilder() + .withMetadata( + new ObjectMetaBuilder() + .withName(primary.getMetadata().getName()) + .withNamespace(primary.getMetadata().getNamespace()) + .build()) + .withSpec( + new DeploymentSpecBuilder() + .withReplicas(1) + .withSelector( + new LabelSelectorBuilder().withMatchLabels(Map.of("app", "nginx")).build()) + .withTemplate( + new PodTemplateSpecBuilder() + .withMetadata( + new ObjectMetaBuilder().withLabels(Map.of("app", "nginx")).build()) + .withSpec( + new PodSpecBuilder() + .withContainers( + new ContainerBuilder() + .withName("nginx") + .withImage("nginx:1.14.2") + .withResources( + new ResourceRequirementsBuilder() + .withLimits(Map.of("cpu", new Quantity("2000m"))) + .build()) + .build()) + .build()) + .build()) + .build()) + .build(); + } + + // for testing purposes replicating the matching logic but with the special matcher + @Override + public Result match( + Deployment actualResource, + Deployment desired, + PrevAnnotationBlockCustomResource primary, + Context context) { + final boolean matches; + addMetadata(true, actualResource, desired, primary, context); + if (useSSA(context)) { + matches = new SSAMatcherWithoutSanitization().matches(actualResource, desired, context); + } else { + matches = + GenericKubernetesResourceMatcher.match(desired, actualResource, false, false, context) + .matched(); + } + return Result.computed(matches, desired); + } + + // using this matcher, so we are able to reproduce issue with resource limits + static class SSAMatcherWithoutSanitization + extends SSABasedGenericKubernetesResourceMatcher { + + @Override + protected void sanitizeState(R actual, R desired, Map actualMap) {} + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/prevblocklist/PrevAnnotationBlockCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/prevblocklist/PrevAnnotationBlockCustomResource.java new file mode 100644 index 0000000000..7aa3194672 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/prevblocklist/PrevAnnotationBlockCustomResource.java @@ -0,0 +1,13 @@ +package io.javaoperatorsdk.operator.dependent.prevblocklist; + +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("pabc") +public class PrevAnnotationBlockCustomResource extends CustomResource + implements Namespaced {} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/prevblocklist/PrevAnnotationBlockReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/prevblocklist/PrevAnnotationBlockReconciler.java new file mode 100644 index 0000000000..7f3dab61fe --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/prevblocklist/PrevAnnotationBlockReconciler.java @@ -0,0 +1,34 @@ +package io.javaoperatorsdk.operator.dependent.prevblocklist; + +import java.util.concurrent.atomic.AtomicInteger; + +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; +import io.javaoperatorsdk.operator.api.reconciler.Reconciler; +import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; +import io.javaoperatorsdk.operator.api.reconciler.Workflow; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; +import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider; + +@Workflow(dependents = {@Dependent(type = DeploymentDependent.class)}) +@ControllerConfiguration() +public class PrevAnnotationBlockReconciler + implements Reconciler, TestExecutionInfoProvider { + + private final AtomicInteger numberOfExecutions = new AtomicInteger(0); + + public PrevAnnotationBlockReconciler() {} + + @Override + public UpdateControl reconcile( + PrevAnnotationBlockCustomResource resource, + Context context) { + numberOfExecutions.getAndIncrement(); + + return UpdateControl.noUpdate(); + } + + public int getNumberOfExecutions() { + return numberOfExecutions.get(); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/prevblocklist/PrevAnnotationBlockReconcilerIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/prevblocklist/PrevAnnotationBlockReconcilerIT.java new file mode 100644 index 0000000000..137e2ba663 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/prevblocklist/PrevAnnotationBlockReconcilerIT.java @@ -0,0 +1,50 @@ +package io.javaoperatorsdk.operator.dependent.prevblocklist; + +import java.time.Duration; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +class PrevAnnotationBlockReconcilerIT { + + public static final String TEST_1 = "test1"; + + @RegisterExtension + LocallyRunOperatorExtension extension = + LocallyRunOperatorExtension.builder() + // Removing resource from blocklist List would result in test failure + // .withConfigurationService( + // o -> o.previousAnnotationUsageBlocklist(Collections.emptyList())) + .withReconciler(PrevAnnotationBlockReconciler.class) + .build(); + + @Test + void doNotUsePrevAnnotationForDeploymentDependent() { + extension.create(testResource(TEST_1)); + + var reconciler = extension.getReconcilerOfType(PrevAnnotationBlockReconciler.class); + await() + .pollDelay(Duration.ofMillis(200)) + .untilAsserted( + () -> { + var deployment = extension.get(Deployment.class, TEST_1); + assertThat(deployment).isNotNull(); + assertThat(reconciler.getNumberOfExecutions()).isGreaterThan(0).isLessThan(10); + }); + } + + PrevAnnotationBlockCustomResource testResource(String name) { + var res = new PrevAnnotationBlockCustomResource(); + res.setMetadata(new ObjectMetaBuilder().withName(name).build()); + res.setSpec(new PrevAnnotationBlockSpec()); + res.getSpec().setValue("value"); + return res; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/prevblocklist/PrevAnnotationBlockSpec.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/prevblocklist/PrevAnnotationBlockSpec.java new file mode 100644 index 0000000000..9d80e14bc1 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/prevblocklist/PrevAnnotationBlockSpec.java @@ -0,0 +1,15 @@ +package io.javaoperatorsdk.operator.dependent.prevblocklist; + +public class PrevAnnotationBlockSpec { + + private String value; + + public String getValue() { + return value; + } + + public PrevAnnotationBlockSpec setValue(String value) { + this.value = value; + return this; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/primaryindexer/DependentPrimaryIndexerTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/primaryindexer/DependentPrimaryIndexerTestReconciler.java index a7e72e592b..52094972da 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/primaryindexer/DependentPrimaryIndexerTestReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/primaryindexer/DependentPrimaryIndexerTestReconciler.java @@ -61,10 +61,6 @@ public List> prepareEventSource public static class ReadOnlyConfigMapDependent extends KubernetesDependentResource { - public ReadOnlyConfigMapDependent() { - super(ConfigMap.class); - } - @Override protected ConfigMap desired( PrimaryIndexerTestCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/primarytosecondaydependent/ConfigMapDependent.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/primarytosecondaydependent/ConfigMapDependent.java index 806b87dfa1..2b63bbf6b1 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/primarytosecondaydependent/ConfigMapDependent.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/primarytosecondaydependent/ConfigMapDependent.java @@ -11,10 +11,6 @@ public class ConfigMapDependent public static final String TEST_CONFIG_MAP_NAME = "testconfigmap"; - public ConfigMapDependent() { - super(ConfigMap.class); - } - @Override protected ConfigMap desired( PrimaryToSecondaryDependentCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/primarytosecondaydependent/SecretDependent.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/primarytosecondaydependent/SecretDependent.java index c3a4f5b77b..6371f453d7 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/primarytosecondaydependent/SecretDependent.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/primarytosecondaydependent/SecretDependent.java @@ -13,10 +13,6 @@ public class SecretDependent extends CRUDKubernetesDependentResource { - public SecretDependent() { - super(Secret.class); - } - @Override protected Secret desired( PrimaryToSecondaryDependentCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/readonly/ReadOnlyDependent.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/readonly/ReadOnlyDependent.java index 63e43fef95..a6f0662948 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/readonly/ReadOnlyDependent.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/readonly/ReadOnlyDependent.java @@ -5,9 +5,4 @@ import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource; @KubernetesDependent -public class ReadOnlyDependent extends KubernetesDependentResource { - - public ReadOnlyDependent() { - super(ConfigMap.class); - } -} +public class ReadOnlyDependent extends KubernetesDependentResource {} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/restart/ConfigMapDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/restart/ConfigMapDependentResource.java index a7df64f117..358718b107 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/restart/ConfigMapDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/restart/ConfigMapDependentResource.java @@ -16,10 +16,6 @@ public class ConfigMapDependentResource public static final String DATA_KEY = "key"; - public ConfigMapDependentResource() { - super(ConfigMap.class); - } - @Override protected ConfigMap desired( RestartTestCustomResource primary, Context context) { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/servicestrictmatcher/ServiceDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/servicestrictmatcher/ServiceDependentResource.java index df7a275122..56e34330e1 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/servicestrictmatcher/ServiceDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/servicestrictmatcher/ServiceDependentResource.java @@ -19,10 +19,6 @@ public class ServiceDependentResource public static AtomicInteger updated = new AtomicInteger(0); - public ServiceDependentResource() { - super(Service.class); - } - @Override protected Service desired( ServiceStrictMatcherTestCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/specialresourcesdependent/ServiceAccountDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/specialresourcesdependent/ServiceAccountDependentResource.java index 9808b364a6..1a598992b5 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/specialresourcesdependent/ServiceAccountDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/specialresourcesdependent/ServiceAccountDependentResource.java @@ -12,10 +12,6 @@ public class ServiceAccountDependentResource extends CRUDKubernetesDependentResource { - public ServiceAccountDependentResource() { - super(ServiceAccount.class); - } - @Override protected ServiceAccount desired( SpecialResourceCustomResource primary, Context context) { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/ssalegacymatcher/ServiceDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/ssalegacymatcher/ServiceDependentResource.java index 510aefddcf..f4007b6151 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/ssalegacymatcher/ServiceDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/ssalegacymatcher/ServiceDependentResource.java @@ -18,10 +18,6 @@ public class ServiceDependentResource public static AtomicInteger createUpdateCount = new AtomicInteger(0); - public ServiceDependentResource() { - super(Service.class); - } - @Override protected Service desired( SSALegacyMatcherCustomResource primary, Context context) { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/standalonedependent/StandaloneDependentTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/standalonedependent/StandaloneDependentTestReconciler.java index 43125eef90..f5d9571711 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/standalonedependent/StandaloneDependentTestReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/standalonedependent/StandaloneDependentTestReconciler.java @@ -70,10 +70,6 @@ public boolean isErrorOccurred() { private static class DeploymentDependentResource extends CRUDKubernetesDependentResource { - public DeploymentDependentResource() { - super(Deployment.class); - } - @Override protected Deployment desired( StandaloneDependentTestCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/statefulsetdesiredsanitizer/StatefulSetDesiredSanitizerDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/statefulsetdesiredsanitizer/StatefulSetDesiredSanitizerDependentResource.java index 72bcc8d8a3..fb8e4a6880 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/statefulsetdesiredsanitizer/StatefulSetDesiredSanitizerDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/statefulsetdesiredsanitizer/StatefulSetDesiredSanitizerDependentResource.java @@ -12,10 +12,6 @@ public class StatefulSetDesiredSanitizerDependentResource public static volatile Boolean nonMatchedAtLeastOnce; - public StatefulSetDesiredSanitizerDependentResource() { - super(StatefulSet.class); - } - @Override protected StatefulSet desired( StatefulSetDesiredSanitizerCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/crdpresentactivation/CRDPresentActivationDependent.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/crdpresentactivation/CRDPresentActivationDependent.java index 715caf5314..11923e274b 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/crdpresentactivation/CRDPresentActivationDependent.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/crdpresentactivation/CRDPresentActivationDependent.java @@ -8,10 +8,6 @@ public class CRDPresentActivationDependent extends CRUDNoGCKubernetesDependentResource< CRDPresentActivationDependentCustomResource, CRDPresentActivationCustomResource> { - public CRDPresentActivationDependent() { - super(CRDPresentActivationDependentCustomResource.class); - } - @Override protected CRDPresentActivationDependentCustomResource desired( CRDPresentActivationCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/getnonactivesecondary/ConfigMapDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/getnonactivesecondary/ConfigMapDependentResource.java index feacf06cb5..c9078848b4 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/getnonactivesecondary/ConfigMapDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/getnonactivesecondary/ConfigMapDependentResource.java @@ -10,10 +10,6 @@ public class ConfigMapDependentResource public static final String DATA_KEY = "data"; - public ConfigMapDependentResource() { - super(ConfigMap.class); - } - @Override protected ConfigMap desired( GetNonActiveSecondaryCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/getnonactivesecondary/RouteDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/getnonactivesecondary/RouteDependentResource.java index 99e34df514..77ebef373a 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/getnonactivesecondary/RouteDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/getnonactivesecondary/RouteDependentResource.java @@ -8,10 +8,6 @@ public class RouteDependentResource extends CRUDKubernetesDependentResource { - public RouteDependentResource() { - super(Route.class); - } - @Override protected Route desired( GetNonActiveSecondaryCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/manageddependentdeletecondition/ConfigMapDependent.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/manageddependentdeletecondition/ConfigMapDependent.java index b4eebf36f0..adc633b877 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/manageddependentdeletecondition/ConfigMapDependent.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/manageddependentdeletecondition/ConfigMapDependent.java @@ -11,10 +11,6 @@ public class ConfigMapDependent extends CRUDNoGCKubernetesDependentResource< ConfigMap, ManagedDependentDefaultDeleteConditionCustomResource> { - public ConfigMapDependent() { - super(ConfigMap.class); - } - @Override protected ConfigMap desired( ManagedDependentDefaultDeleteConditionCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/manageddependentdeletecondition/SecretDependent.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/manageddependentdeletecondition/SecretDependent.java index 2ae036e7ee..a7d52511ea 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/manageddependentdeletecondition/SecretDependent.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/manageddependentdeletecondition/SecretDependent.java @@ -13,10 +13,6 @@ public class SecretDependent extends CRUDNoGCKubernetesDependentResource< Secret, ManagedDependentDefaultDeleteConditionCustomResource> { - public SecretDependent() { - super(Secret.class); - } - @Override protected Secret desired( ManagedDependentDefaultDeleteConditionCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/multipledependentwithactivation/ConfigMapDependentResource1.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/multipledependentwithactivation/ConfigMapDependentResource1.java index cce3eb8ad4..ed83b870ab 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/multipledependentwithactivation/ConfigMapDependentResource1.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/multipledependentwithactivation/ConfigMapDependentResource1.java @@ -17,10 +17,6 @@ public class ConfigMapDependentResource1 public static final String DATA_KEY = "data"; public static final String SUFFIX = "1"; - public ConfigMapDependentResource1() { - super(ConfigMap.class); - } - @Override protected ConfigMap desired( MultipleDependentActivationCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/multipledependentwithactivation/ConfigMapDependentResource2.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/multipledependentwithactivation/ConfigMapDependentResource2.java index 8b0a4d89bb..73ccb55cdb 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/multipledependentwithactivation/ConfigMapDependentResource2.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/multipledependentwithactivation/ConfigMapDependentResource2.java @@ -17,10 +17,6 @@ public class ConfigMapDependentResource2 public static final String DATA_KEY = "data"; public static final String SUFFIX = "2"; - public ConfigMapDependentResource2() { - super(ConfigMap.class); - } - @Override protected ConfigMap desired( MultipleDependentActivationCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/multipledependentwithactivation/SecretDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/multipledependentwithactivation/SecretDependentResource.java index 9b629c5af4..330f0e3c0f 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/multipledependentwithactivation/SecretDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/multipledependentwithactivation/SecretDependentResource.java @@ -11,10 +11,6 @@ public class SecretDependentResource extends CRUDKubernetesDependentResource { - public SecretDependentResource() { - super(Secret.class); - } - @Override protected Secret desired( MultipleDependentActivationCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/orderedmanageddependent/ConfigMapDependentResource1.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/orderedmanageddependent/ConfigMapDependentResource1.java index f3cc3144c6..eec904d2c7 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/orderedmanageddependent/ConfigMapDependentResource1.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/orderedmanageddependent/ConfigMapDependentResource1.java @@ -15,10 +15,6 @@ public class ConfigMapDependentResource1 extends CRUDKubernetesDependentResource { - public ConfigMapDependentResource1() { - super(ConfigMap.class); - } - @Override public ReconcileResult reconcile( OrderedManagedDependentCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/orderedmanageddependent/ConfigMapDependentResource2.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/orderedmanageddependent/ConfigMapDependentResource2.java index aae0fe79b3..8e4a1467ec 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/orderedmanageddependent/ConfigMapDependentResource2.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/orderedmanageddependent/ConfigMapDependentResource2.java @@ -15,10 +15,6 @@ public class ConfigMapDependentResource2 extends CRUDKubernetesDependentResource { - public ConfigMapDependentResource2() { - super(ConfigMap.class); - } - @Override public ReconcileResult reconcile( OrderedManagedDependentCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowactivationcleanup/ConfigMapDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowactivationcleanup/ConfigMapDependentResource.java index 4edba945a8..cb2357bf8b 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowactivationcleanup/ConfigMapDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowactivationcleanup/ConfigMapDependentResource.java @@ -13,10 +13,6 @@ public class ConfigMapDependentResource public static final String DATA_KEY = "data"; - public ConfigMapDependentResource() { - super(ConfigMap.class); - } - @Override protected ConfigMap desired( WorkflowActivationCleanupCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowactivationcondition/ConfigMapDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowactivationcondition/ConfigMapDependentResource.java index 181e35eb2d..5f2e92ed55 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowactivationcondition/ConfigMapDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowactivationcondition/ConfigMapDependentResource.java @@ -12,10 +12,6 @@ public class ConfigMapDependentResource public static final String DATA_KEY = "data"; - public ConfigMapDependentResource() { - super(ConfigMap.class); - } - @Override protected ConfigMap desired( WorkflowActivationConditionCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowactivationcondition/RouteDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowactivationcondition/RouteDependentResource.java index 64a0c9e299..7d2d091c94 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowactivationcondition/RouteDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowactivationcondition/RouteDependentResource.java @@ -8,10 +8,6 @@ public class RouteDependentResource extends CRUDKubernetesDependentResource { - public RouteDependentResource() { - super(Route.class); - } - @Override protected Route desired( WorkflowActivationConditionCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowallfeature/ConfigMapDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowallfeature/ConfigMapDependentResource.java index 29866202fc..fac6ecae88 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowallfeature/ConfigMapDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowallfeature/ConfigMapDependentResource.java @@ -25,10 +25,6 @@ public class ConfigMapDependentResource private static final Logger log = LoggerFactory.getLogger(ConfigMapDependentResource.class); - public ConfigMapDependentResource() { - super(ConfigMap.class); - } - @Override protected ConfigMap desired( WorkflowAllFeatureCustomResource primary, Context context) { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowallfeature/DeploymentDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowallfeature/DeploymentDependentResource.java index 36e3a95b92..92956e05f6 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowallfeature/DeploymentDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowallfeature/DeploymentDependentResource.java @@ -8,10 +8,6 @@ public class DeploymentDependentResource extends CRUDNoGCKubernetesDependentResource { - public DeploymentDependentResource() { - super(Deployment.class); - } - @Override protected Deployment desired( WorkflowAllFeatureCustomResource primary, Context context) { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowexplicitcleanup/ConfigMapDependent.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowexplicitcleanup/ConfigMapDependent.java index b42f14cace..17190c5a92 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowexplicitcleanup/ConfigMapDependent.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowexplicitcleanup/ConfigMapDependent.java @@ -11,10 +11,6 @@ public class ConfigMapDependent extends CRUDNoGCKubernetesDependentResource { - public ConfigMapDependent() { - super(ConfigMap.class); - } - @Override protected ConfigMap desired( WorkflowExplicitCleanupCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowexplicitinvocation/ConfigMapDependent.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowexplicitinvocation/ConfigMapDependent.java index cc404328a9..a2638b48b5 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowexplicitinvocation/ConfigMapDependent.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowexplicitinvocation/ConfigMapDependent.java @@ -12,10 +12,6 @@ public class ConfigMapDependent extends CRUDNoGCKubernetesDependentResource< ConfigMap, WorkflowExplicitInvocationCustomResource> { - public ConfigMapDependent() { - super(ConfigMap.class); - } - @Override protected ConfigMap desired( WorkflowExplicitInvocationCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowmultipleactivation/ConfigMapDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowmultipleactivation/ConfigMapDependentResource.java index 2bd666b64c..3366a61a1f 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowmultipleactivation/ConfigMapDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowmultipleactivation/ConfigMapDependentResource.java @@ -13,10 +13,6 @@ public class ConfigMapDependentResource public static final String DATA_KEY = "data"; - public ConfigMapDependentResource() { - super(ConfigMap.class); - } - @Override protected ConfigMap desired( WorkflowMultipleActivationCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowmultipleactivation/SecretDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowmultipleactivation/SecretDependentResource.java index b392259ca9..cd1fbfcedc 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowmultipleactivation/SecretDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowmultipleactivation/SecretDependentResource.java @@ -11,10 +11,6 @@ public class SecretDependentResource extends CRUDKubernetesDependentResource { - public SecretDependentResource() { - super(Secret.class); - } - @Override protected Secret desired( WorkflowMultipleActivationCustomResource primary, diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowsilentexceptionhandling/ConfigMapDependent.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowsilentexceptionhandling/ConfigMapDependent.java index bd8d4099ff..6dbac41f8c 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowsilentexceptionhandling/ConfigMapDependent.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowsilentexceptionhandling/ConfigMapDependent.java @@ -9,10 +9,6 @@ public class ConfigMapDependent extends CRUDNoGCKubernetesDependentResource< ConfigMap, HandleWorkflowExceptionsInReconcilerCustomResource> { - public ConfigMapDependent() { - super(ConfigMap.class); - } - @Override public ReconcileResult reconcile( HandleWorkflowExceptionsInReconcilerCustomResource primary, diff --git a/operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rback-test-full-access-role.yaml b/operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rbac-test-full-access-role.yaml similarity index 100% rename from operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rback-test-full-access-role.yaml rename to operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rbac-test-full-access-role.yaml diff --git a/operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rback-test-no-configmap-access.yaml b/operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rbac-test-no-configmap-access.yaml similarity index 100% rename from operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rback-test-no-configmap-access.yaml rename to operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rbac-test-no-configmap-access.yaml diff --git a/operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rback-test-no-cr-access.yaml b/operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rbac-test-no-cr-access.yaml similarity index 100% rename from operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rback-test-no-cr-access.yaml rename to operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rbac-test-no-cr-access.yaml diff --git a/operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rback-test-only-main-ns-access-binding.yaml b/operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rbac-test-only-main-ns-access-binding.yaml similarity index 100% rename from operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rback-test-only-main-ns-access-binding.yaml rename to operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rbac-test-only-main-ns-access-binding.yaml diff --git a/operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rback-test-only-main-ns-access.yaml b/operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rbac-test-only-main-ns-access.yaml similarity index 100% rename from operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rback-test-only-main-ns-access.yaml rename to operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rbac-test-only-main-ns-access.yaml diff --git a/operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rback-test-role-binding.yaml b/operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rbac-test-role-binding.yaml similarity index 100% rename from operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rback-test-role-binding.yaml rename to operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rbac-test-role-binding.yaml diff --git a/operator-framework/src/test/resources/io/javaoperatorsdk/operator/statefulset.yaml b/operator-framework/src/test/resources/io/javaoperatorsdk/operator/statefulset.yaml index f40fbeb607..bb8a2df04b 100644 --- a/operator-framework/src/test/resources/io/javaoperatorsdk/operator/statefulset.yaml +++ b/operator-framework/src/test/resources/io/javaoperatorsdk/operator/statefulset.yaml @@ -21,6 +21,13 @@ spec: ports: - containerPort: 80 name: web + env: + - name: APP1_HOST_NAME + value: "" + - name: APP2_HOST_NAME + value: "localhost" + - name: APP3_HOST_NAME + value: " " volumeMounts: - name: www mountPath: /usr/share/nginx/html diff --git a/pom.xml b/pom.xml index 3daa583203..4a91625c2c 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ io.javaoperatorsdk java-operator-sdk - 5.0.5-SNAPSHOT + 5.1.3-SNAPSHOT pom Operator SDK for Java Java SDK for implementing Kubernetes operators @@ -44,13 +44,6 @@ https://github.com/operator-framework/java-operator-sdk/tree/main - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - UTF-8 17 @@ -59,39 +52,39 @@ java-operator-sdk https://sonarcloud.io jdk - - 5.12.2 - 7.2.0 - 2.0.12 - 2.24.3 - 5.17.0 - 3.17.0 + 5.13.4 + 7.3.1 + 2.0.17 + 2.25.1 + 5.19.0 + 3.18.0 0.21.0 1.13.0 - 3.27.3 + 3.27.4 4.3.0 2.7.3 - 1.15.0 - 3.2.0 + 1.15.3 + 3.2.2 0.9.14 - 2.19.0 - 4.15 + 2.20.0 + 4.16 2.11 3.14.0 3.5.3 - 3.11.2 + 0.8.0 + 3.11.3 3.3.1 3.3.1 3.4.2 - 3.4.1 - 3.2.7 + 3.5.0 + 3.2.8 1.7.0 3.0.0 3.1.4 - 9.0.1 - 3.4.5 - 2.44.4 + 9.0.2 + 3.4.6 + 2.46.1 @@ -294,6 +287,15 @@ org.apache.maven.plugins maven-source-plugin ${maven-source-plugin.version} + + + attach-sources + + jar + + verify + + org.apache.maven.plugins @@ -516,14 +518,6 @@ org.apache.maven.plugins maven-source-plugin - - - attach-sources - - jar - - - org.apache.maven.plugins @@ -545,14 +539,15 @@ - org.sonatype.plugins - nexus-staging-maven-plugin - ${nexus-staging-maven-plugin.version} + org.sonatype.central + central-publishing-maven-plugin + ${central-publishing-maven-plugin.version} true - ossrh - https://oss.sonatype.org/ - true + central + true + true + published diff --git a/sample-operators/controller-namespace-deletion/pom.xml b/sample-operators/controller-namespace-deletion/pom.xml index ee0d5bb3d2..312e2fb199 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.0.5-SNAPSHOT + 5.1.3-SNAPSHOT sample-controller-namespace-deletion diff --git a/sample-operators/leader-election/pom.xml b/sample-operators/leader-election/pom.xml index 4b1088fa3b..f01406b132 100644 --- a/sample-operators/leader-election/pom.xml +++ b/sample-operators/leader-election/pom.xml @@ -5,7 +5,7 @@ io.javaoperatorsdk sample-operators - 5.0.5-SNAPSHOT + 5.1.3-SNAPSHOT sample-leader-election diff --git a/sample-operators/mysql-schema/pom.xml b/sample-operators/mysql-schema/pom.xml index 92a5cb5c45..cf1be19cbb 100644 --- a/sample-operators/mysql-schema/pom.xml +++ b/sample-operators/mysql-schema/pom.xml @@ -5,7 +5,7 @@ io.javaoperatorsdk sample-operators - 5.0.5-SNAPSHOT + 5.1.3-SNAPSHOT sample-mysql-schema-operator diff --git a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/dependent/SchemaDependentResource.java b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/dependent/SchemaDependentResource.java index 5bc210f7d4..ec2b03325c 100644 --- a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/dependent/SchemaDependentResource.java +++ b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/dependent/SchemaDependentResource.java @@ -54,10 +54,6 @@ public class SchemaDependentResource private MySQLDbConfig dbConfig; - public SchemaDependentResource() { - super(Schema.class); - } - @Override public Optional configuration() { return Optional.of(new ResourcePollerConfig(getPollingPeriod(), dbConfig)); diff --git a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/dependent/SecretDependentResource.java b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/dependent/SecretDependentResource.java index 092ac22e24..cff28feadd 100644 --- a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/dependent/SecretDependentResource.java +++ b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/dependent/SecretDependentResource.java @@ -27,10 +27,6 @@ public class SecretDependentResource extends KubernetesDependentResource io.javaoperatorsdk java-operator-sdk - 5.0.5-SNAPSHOT + 5.1.3-SNAPSHOT sample-operators diff --git a/sample-operators/tomcat-operator/pom.xml b/sample-operators/tomcat-operator/pom.xml index cd340c525d..3a9b640db8 100644 --- a/sample-operators/tomcat-operator/pom.xml +++ b/sample-operators/tomcat-operator/pom.xml @@ -5,7 +5,7 @@ io.javaoperatorsdk sample-operators - 5.0.5-SNAPSHOT + 5.1.3-SNAPSHOT sample-tomcat-operator diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java index 6a18111b27..c7f25e996c 100644 --- a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java @@ -14,10 +14,6 @@ public class DeploymentDependentResource extends CRUDKubernetesDependentResource { - public DeploymentDependentResource() { - super(Deployment.class); - } - private static String tomcatImage(Tomcat tomcat) { return "tomcat:" + tomcat.getSpec().getVersion(); } diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/ServiceDependentResource.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/ServiceDependentResource.java index b42a42257d..bb0359458e 100644 --- a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/ServiceDependentResource.java +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/ServiceDependentResource.java @@ -13,10 +13,6 @@ informer = @Informer(labelSelector = "app.kubernetes.io/managed-by=tomcat-operator")) public class ServiceDependentResource extends CRUDKubernetesDependentResource { - public ServiceDependentResource() { - super(Service.class); - } - @Override protected Service desired(Tomcat tomcat, Context context) { final ObjectMeta tomcatMetadata = tomcat.getMetadata(); diff --git a/sample-operators/webpage/pom.xml b/sample-operators/webpage/pom.xml index 6ae09c835d..d3a691a93a 100644 --- a/sample-operators/webpage/pom.xml +++ b/sample-operators/webpage/pom.xml @@ -5,7 +5,7 @@ io.javaoperatorsdk sample-operators - 5.0.5-SNAPSHOT + 5.1.3-SNAPSHOT sample-webpage-operator diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/dependentresource/ConfigMapDependentResource.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/dependentresource/ConfigMapDependentResource.java index 816db3688e..0cf8faad7c 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/dependentresource/ConfigMapDependentResource.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/dependentresource/ConfigMapDependentResource.java @@ -20,10 +20,6 @@ public class ConfigMapDependentResource extends CRUDKubernetesDependentResource { - public ConfigMapDependentResource() { - super(ConfigMap.class); - } - @Override protected ConfigMap desired(WebPage webPage, Context context) { Map data = new HashMap<>(); diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/dependentresource/DeploymentDependentResource.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/dependentresource/DeploymentDependentResource.java index 3476464f1f..4deef0f1c0 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/dependentresource/DeploymentDependentResource.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/dependentresource/DeploymentDependentResource.java @@ -22,10 +22,6 @@ public class DeploymentDependentResource extends CRUDKubernetesDependentResource { - public DeploymentDependentResource() { - super(Deployment.class); - } - @Override protected Deployment desired(WebPage webPage, Context context) { Map labels = new HashMap<>(); diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/dependentresource/IngressDependentResource.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/dependentresource/IngressDependentResource.java index 994a35c98c..3f3e64e8ed 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/dependentresource/IngressDependentResource.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/dependentresource/IngressDependentResource.java @@ -15,10 +15,6 @@ informer = @Informer(labelSelector = WebPageManagedDependentsReconciler.SELECTOR)) public class IngressDependentResource extends CRUDKubernetesDependentResource { - public IngressDependentResource() { - super(Ingress.class); - } - @Override protected Ingress desired(WebPage webPage, Context context) { return makeDesiredIngress(webPage); diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/dependentresource/ServiceDependentResource.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/dependentresource/ServiceDependentResource.java index b1ab856f62..01e8953fa9 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/dependentresource/ServiceDependentResource.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/dependentresource/ServiceDependentResource.java @@ -22,10 +22,6 @@ public class ServiceDependentResource .CRUDKubernetesDependentResource< Service, WebPage> { - public ServiceDependentResource() { - super(Service.class); - } - @Override protected Service desired(WebPage webPage, Context context) { Map serviceLabels = new HashMap<>();