diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index 49ec92013e..fd31e65d5f 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -19,7 +19,7 @@ jobs: sample_operators_tests: strategy: matrix: - sample_dir: + sample: - "sample-operators/mysql-schema" - "sample-operators/tomcat-operator" - "sample-operators/webpage" @@ -48,12 +48,10 @@ jobs: run: mvn install -DskipTests - name: Run integration tests in local mode - working-directory: ${{ matrix.sample_dir }} run: | - mvn test -P end-to-end-tests + mvn test -P end-to-end-tests -pl ${{ matrix.sample }} - name: Run E2E tests as a deployment - working-directory: ${{ matrix.sample_dir }} run: | eval $(minikube -p minikube docker-env) - mvn jib:dockerBuild test -P end-to-end-tests -Dtest.deployment=remote + mvn jib:dockerBuild test -P end-to-end-tests -Dtest.deployment=remote -pl ${{ matrix.sample }} diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index df7bf8436e..8b09a2148e 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -12,7 +12,7 @@ on: http-client: type: string required: false - default: 'okhttp' + default: 'jdk' experimental: type: boolean required: false diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 8206b0d43a..5f56e642b4 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -26,15 +26,14 @@ jobs: cache: 'maven' - name: Check code format run: | - ./mvnw ${MAVEN_ARGS} formatter:validate -Dconfigfile=$PWD/contributing/eclipse-google-style.xml -pl '!operator-framework-bom' --file pom.xml - ./mvnw ${MAVEN_ARGS} impsort:check -pl '!operator-framework-bom' --file pom.xml + ./mvnw ${MAVEN_ARGS} spotless:check --file pom.xml - name: Run unit tests run: ./mvnw ${MAVEN_ARGS} clean install --file pom.xml integration_tests: strategy: matrix: - java: [ 11, 17 ] + java: [ 17, 21 ] kubernetes: [ 'v1.26.13', 'v1.27.10', 'v1.28.6', 'v1.29.1' ] uses: ./.github/workflows/integration-tests.yml with: @@ -56,7 +55,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [ 11, 17 ] + java: [ 17, 21 ] steps: - uses: actions/checkout@v4 - name: Set up Java and Maven diff --git a/.github/workflows/release-project-in-dir.yml b/.github/workflows/release-project-in-dir.yml index b271f05f02..dc79b6f6c2 100644 --- a/.github/workflows/release-project-in-dir.yml +++ b/.github/workflows/release-project-in-dir.yml @@ -26,7 +26,7 @@ jobs: - name: Set up Java and Maven uses: actions/setup-java@v4 with: - java-version: 11 + java-version: 17 distribution: temurin cache: 'maven' @@ -61,7 +61,7 @@ jobs: - name: Set up Java and Maven uses: actions/setup-java@v4 with: - java-version: 11 + java-version: 17 distribution: temurin cache: 'maven' diff --git a/.github/workflows/snapshot-releases.yml b/.github/workflows/snapshot-releases.yml index e5aff55e62..66fe9d25a3 100644 --- a/.github/workflows/snapshot-releases.yml +++ b/.github/workflows/snapshot-releases.yml @@ -21,7 +21,7 @@ jobs: uses: actions/setup-java@v4 with: distribution: temurin - java-version: 11 + java-version: 17 cache: 'maven' - name: Build and test project run: ./mvnw ${MAVEN_ARGS} clean install --file pom.xml @@ -34,7 +34,7 @@ jobs: uses: actions/setup-java@v4 with: distribution: temurin - java-version: 11 + java-version: 17 cache: 'maven' - name: Release Maven package uses: samuelmeuli/action-maven-publish@v1 diff --git a/README.md b/README.md index fd9a6e2646..9d5457ce8a 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,6 @@ It makes it easy to implement best practices and patterns for an Operator. Featu * Handling dependent resources, related events, and caching. * Automatic Retries * Smart event scheduling -* Handling Observed Generations automatically * Easy to use Error Handling * ... and everything that a batteries included framework needs diff --git a/bootstrapper-maven-plugin/pom.xml b/bootstrapper-maven-plugin/pom.xml index a2173e7ce1..1048a27c61 100644 --- a/bootstrapper-maven-plugin/pom.xml +++ b/bootstrapper-maven-plugin/pom.xml @@ -1,11 +1,11 @@ - + + 4.0.0 - java-operator-sdk io.javaoperatorsdk - 4.9.1-SNAPSHOT + java-operator-sdk + 5.0.0-SNAPSHOT bootstrapper @@ -14,10 +14,10 @@ Operator SDK - Bootstrapper Maven Plugin - 3.13.0 + 3.12.0 3.9.6 3.0.0 - 3.13.0 + 3.12.0 @@ -72,29 +72,29 @@ - - - org.apache.maven.plugins - maven-plugin-plugin - ${maven-plugin-plugin.version} - - josdk-bootstrapper - - - - org.codehaus.mojo - templating-maven-plugin - ${templating-maven-plugin.version} - - - filtering-java-templates - - filter-sources - - - - + + + org.apache.maven.plugins + maven-plugin-plugin + ${maven-plugin-plugin.version} + + josdk-bootstrapper + + + + org.codehaus.mojo + templating-maven-plugin + ${templating-maven-plugin.version} + + + filtering-java-templates + + filter-sources + + + + - + diff --git a/bootstrapper-maven-plugin/src/main/java/io/javaoperatorsdk/boostrapper/Bootstrapper.java b/bootstrapper-maven-plugin/src/main/java/io/javaoperatorsdk/boostrapper/Bootstrapper.java index b0dba64c17..1a827a8bae 100644 --- a/bootstrapper-maven-plugin/src/main/java/io/javaoperatorsdk/boostrapper/Bootstrapper.java +++ b/bootstrapper-maven-plugin/src/main/java/io/javaoperatorsdk/boostrapper/Bootstrapper.java @@ -21,7 +21,7 @@ public class Bootstrapper { private static final Logger log = LoggerFactory.getLogger(Bootstrapper.class); - private MustacheFactory mustacheFactory = new DefaultMustacheFactory(); + private final MustacheFactory mustacheFactory = new DefaultMustacheFactory(); private static final List TOP_LEVEL_STATIC_FILES = List.of(".gitignore", "README.md"); diff --git a/bootstrapper-maven-plugin/src/main/resources/templates/Reconciler.java b/bootstrapper-maven-plugin/src/main/resources/templates/Reconciler.java index 03ac06f882..6d03196fe9 100644 --- a/bootstrapper-maven-plugin/src/main/resources/templates/Reconciler.java +++ b/bootstrapper-maven-plugin/src/main/resources/templates/Reconciler.java @@ -5,19 +5,20 @@ import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; import io.javaoperatorsdk.operator.api.reconciler.Reconciler; -import io.javaoperatorsdk.operator.api.reconciler.EventSourceInitializer; import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; +import io.javaoperatorsdk.operator.api.reconciler.Workflow; import io.javaoperatorsdk.operator.processing.event.source.EventSource; import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; import java.util.Map; import java.util.Optional; -@ControllerConfiguration(dependents = {@Dependent(type = ConfigMapDependentResource.class)}) +@Workflow(dependents = {@Dependent(type = ConfigMapDependentResource.class)}) +@ControllerConfiguration public class {{artifactClassId}}Reconciler implements Reconciler<{{artifactClassId}}CustomResource> { public UpdateControl<{{artifactClassId}}CustomResource> reconcile({{artifactClassId}}CustomResource primary, diff --git a/bootstrapper-maven-plugin/src/main/resources/templates/Status.java b/bootstrapper-maven-plugin/src/main/resources/templates/Status.java index 4c37b99947..52bd0fd4d2 100644 --- a/bootstrapper-maven-plugin/src/main/resources/templates/Status.java +++ b/bootstrapper-maven-plugin/src/main/resources/templates/Status.java @@ -1,7 +1,5 @@ package {{groupId}}; -import io.javaoperatorsdk.operator.api.ObservedGenerationAwareStatus; - -public class {{artifactClassId}}Status extends ObservedGenerationAwareStatus { +public class {{artifactClassId}}Status { } diff --git a/bootstrapper-maven-plugin/src/main/resources/templates/pom.xml b/bootstrapper-maven-plugin/src/main/resources/templates/pom.xml index d2ffb66b46..007525a38b 100644 --- a/bootstrapper-maven-plugin/src/main/resources/templates/pom.xml +++ b/bootstrapper-maven-plugin/src/main/resources/templates/pom.xml @@ -11,7 +11,7 @@ jar - 11 + 17 ${java.version} ${java.version} {{josdkVersion}} diff --git a/caffeine-bounded-cache-support/pom.xml b/caffeine-bounded-cache-support/pom.xml index 8172959283..32bfa6ad13 100644 --- a/caffeine-bounded-cache-support/pom.xml +++ b/caffeine-bounded-cache-support/pom.xml @@ -1,22 +1,15 @@ - + + 4.0.0 - java-operator-sdk io.javaoperatorsdk - 4.9.1-SNAPSHOT + java-operator-sdk + 5.0.0-SNAPSHOT - 4.0.0 caffeine-bounded-cache-support Operator SDK - Caffeine Bounded Cache Support - - 11 - 11 - - io.javaoperatorsdk @@ -70,10 +63,10 @@ However, this is needed to compile the tests so let's disable apt just for the compile phase --> default-compile - compile compile + compile -proc:none @@ -85,4 +78,4 @@ - \ No newline at end of file + diff --git a/caffeine-bounded-cache-support/src/test/java/io/javaoperatorsdk/operator/processing/event/source/cache/sample/AbstractTestReconciler.java b/caffeine-bounded-cache-support/src/test/java/io/javaoperatorsdk/operator/processing/event/source/cache/sample/AbstractTestReconciler.java index b6e3ba2c8f..18cd486fb2 100644 --- a/caffeine-bounded-cache-support/src/test/java/io/javaoperatorsdk/operator/processing/event/source/cache/sample/AbstractTestReconciler.java +++ b/caffeine-bounded-cache-support/src/test/java/io/javaoperatorsdk/operator/processing/event/source/cache/sample/AbstractTestReconciler.java @@ -1,6 +1,7 @@ package io.javaoperatorsdk.operator.processing.event.source.cache.sample; import java.time.Duration; +import java.util.List; import java.util.Map; import org.slf4j.Logger; @@ -28,7 +29,7 @@ import com.github.benmanes.caffeine.cache.Caffeine; public abstract class AbstractTestReconciler

> - implements Reconciler

, EventSourceInitializer

{ + implements Reconciler

{ private static final Logger log = LoggerFactory.getLogger(BoundedCacheClusterScopeTestReconciler.class); @@ -69,7 +70,7 @@ protected void createConfigMap(P resource, Context

context) { } @Override - public Map prepareEventSources( + public List prepareEventSources( EventSourceContext

context) { var boundedItemStore = @@ -79,10 +80,11 @@ public Map prepareEventSources( var es = new InformerEventSource<>(InformerConfiguration.from(ConfigMap.class, context) .withItemStore(boundedItemStore) .withSecondaryToPrimaryMapper( - Mappers.fromOwnerReference(this instanceof BoundedCacheClusterScopeTestReconciler)) + Mappers.fromOwnerReferences(context.getPrimaryResourceClass(), + this instanceof BoundedCacheClusterScopeTestReconciler)) .build(), context); - return EventSourceInitializer.nameEventSources(es); + return List.of(es); } private void ensureStatus(P resource) { diff --git a/caffeine-bounded-cache-support/src/test/java/io/javaoperatorsdk/operator/processing/event/source/cache/sample/namespacescope/BoundedCacheTestStatus.java b/caffeine-bounded-cache-support/src/test/java/io/javaoperatorsdk/operator/processing/event/source/cache/sample/namespacescope/BoundedCacheTestStatus.java index 03a311529e..2bdd434d23 100644 --- a/caffeine-bounded-cache-support/src/test/java/io/javaoperatorsdk/operator/processing/event/source/cache/sample/namespacescope/BoundedCacheTestStatus.java +++ b/caffeine-bounded-cache-support/src/test/java/io/javaoperatorsdk/operator/processing/event/source/cache/sample/namespacescope/BoundedCacheTestStatus.java @@ -1,6 +1,4 @@ package io.javaoperatorsdk.operator.processing.event.source.cache.sample.namespacescope; -import io.javaoperatorsdk.operator.api.ObservedGenerationAwareStatus; - -public class BoundedCacheTestStatus extends ObservedGenerationAwareStatus { +public class BoundedCacheTestStatus { } diff --git a/docs/_data/sidebar.yml b/docs/_data/sidebar.yml index 8c083a6cc1..01918ca31f 100644 --- a/docs/_data/sidebar.yml +++ b/docs/_data/sidebar.yml @@ -32,4 +32,6 @@ - title: Migrating from v4.3 to v4.4 url: /docs/v4-4-migration - title: Migrating from v4.4 to v4.5 - url: /docs/v4-5-migration \ No newline at end of file + url: /docs/v4-5-migration + - title: Migrating from v4.7 to v5.0 + url: /docs/v5-0-migration diff --git a/docs/documentation/dependent-resources.md b/docs/documentation/dependent-resources.md index 61e18fd3f2..5c6d1437e9 100644 --- a/docs/documentation/dependent-resources.md +++ b/docs/documentation/dependent-resources.md @@ -101,13 +101,13 @@ and labels, which are ignored by default: ```java public class MyDependentResource extends KubernetesDependentResource - implements Matcher { - // your implementation + implements Matcher { + // your implementation - public Result match(MyDependent actualResource, MyPrimary primary, - Context context) { - return GenericKubernetesResourceMatcher.match(this, actualResource, primary, context, true); - } + public Result match(MyDependent actualResource, MyPrimary primary, + Context context) { + return GenericKubernetesResourceMatcher.match(this, actualResource, primary, context, true); + } } ``` @@ -141,24 +141,24 @@ Deleted (or set to be garbage collected). The following example shows how to cre @KubernetesDependent(labelSelector = WebPageManagedDependentsReconciler.SELECTOR) class DeploymentDependentResource extends CRUDKubernetesDependentResource { - public DeploymentDependentResource() { - super(Deployment.class); - } - - @Override - protected Deployment desired(WebPage webPage, Context context) { - var deploymentName = deploymentName(webPage); - Deployment deployment = loadYaml(Deployment.class, getClass(), "deployment.yaml"); - deployment.getMetadata().setName(deploymentName); - deployment.getMetadata().setNamespace(webPage.getMetadata().getNamespace()); - deployment.getSpec().getSelector().getMatchLabels().put("app", deploymentName); - - deployment.getSpec().getTemplate().getMetadata().getLabels() - .put("app", deploymentName); - deployment.getSpec().getTemplate().getSpec().getVolumes().get(0) - .setConfigMap(new ConfigMapVolumeSourceBuilder().withName(configMapName(webPage)).build()); - return deployment; - } + public DeploymentDependentResource() { + super(Deployment.class); + } + + @Override + protected Deployment desired(WebPage webPage, Context context) { + var deploymentName = deploymentName(webPage); + Deployment deployment = loadYaml(Deployment.class, getClass(), "deployment.yaml"); + deployment.getMetadata().setName(deploymentName); + deployment.getMetadata().setNamespace(webPage.getMetadata().getNamespace()); + deployment.getSpec().getSelector().getMatchLabels().put("app", deploymentName); + + deployment.getSpec().getTemplate().getMetadata().getLabels() + .put("app", deploymentName); + deployment.getSpec().getTemplate().getSpec().getVolumes().get(0) + .setConfigMap(new ConfigMapVolumeSourceBuilder().withName(configMapName(webPage)).build()); + return deployment; + } } ``` @@ -194,25 +194,25 @@ instances are managed by JOSDK, an example of which can be seen below: ```java @ControllerConfiguration( - labelSelector = SELECTOR, - dependents = { - @Dependent(type = ConfigMapDependentResource.class), - @Dependent(type = DeploymentDependentResource.class), - @Dependent(type = ServiceDependentResource.class) - }) + labelSelector = SELECTOR, + dependents = { + @Dependent(type = ConfigMapDependentResource.class), + @Dependent(type = DeploymentDependentResource.class), + @Dependent(type = ServiceDependentResource.class) + }) public class WebPageManagedDependentsReconciler - implements Reconciler, ErrorStatusHandler { + implements Reconciler, ErrorStatusHandler { - // omitted code + // omitted code - @Override - public UpdateControl reconcile(WebPage webPage, Context context) { + @Override + public UpdateControl reconcile(WebPage webPage, Context context) { - final var name = context.getSecondaryResource(ConfigMap.class).orElseThrow() - .getMetadata().getName(); - webPage.setStatus(createStatus(name)); - return UpdateControl.patchStatus(webPage); - } + final var name = context.getSecondaryResource(ConfigMap.class).orElseThrow() + .getMetadata().getName(); + webPage.setStatus(createStatus(name)); + return UpdateControl.patchStatus(webPage); + } } ``` @@ -227,104 +227,11 @@ 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. - -Note that [Workflows](https://javaoperatorsdk.io/docs/workflows) also can be invoked from standalone -resources. +case does not fit. You can, of course, also use [Workflows](https://javaoperatorsdk.io/docs/workflows) when managing +resources programmatically. -The following sample is similar to the one above, simply performing additional checks, and -conditionally creating an `Ingress`: - -```java - -@ControllerConfiguration -public class WebPageStandaloneDependentsReconciler - implements Reconciler, ErrorStatusHandler, - EventSourceInitializer { - - private KubernetesDependentResource configMapDR; - private KubernetesDependentResource deploymentDR; - private KubernetesDependentResource serviceDR; - private KubernetesDependentResource ingressDR; - - public WebPageStandaloneDependentsReconciler(KubernetesClient kubernetesClient) { - // 1. - createDependentResources(kubernetesClient); - } - - @Override - public List prepareEventSources(EventSourceContext context) { - // 2. - return List.of( - configMapDR.initEventSource(context), - deploymentDR.initEventSource(context), - serviceDR.initEventSource(context)); - } - - @Override - public UpdateControl reconcile(WebPage webPage, Context context) { - - // 3. - if (!isValidHtml(webPage.getHtml())) { - return UpdateControl.patchStatus(setInvalidHtmlErrorMessage(webPage)); - } - - // 4. - configMapDR.reconcile(webPage, context); - deploymentDR.reconcile(webPage, context); - serviceDR.reconcile(webPage, context); - - // 5. - if (Boolean.TRUE.equals(webPage.getSpec().getExposed())) { - ingressDR.reconcile(webPage, context); - } else { - ingressDR.delete(webPage, context); - } - - // 6. - webPage.setStatus( - createStatus(configMapDR.getResource(webPage).orElseThrow().getMetadata().getName())); - return UpdateControl.patchStatus(webPage); - } - - private void createDependentResources(KubernetesClient client) { - this.configMapDR = new ConfigMapDependentResource(); - this.deploymentDR = new DeploymentDependentResource(); - this.serviceDR = new ServiceDependentResource(); - this.ingressDR = new IngressDependentResource(); - - Arrays.asList(configMapDR, deploymentDR, serviceDR, ingressDR).forEach(dr -> { - dr.setKubernetesClient(client); - dr.configureWith(new KubernetesDependentResourceConfig() - .setLabelSelector(DEPENDENT_RESOURCE_LABEL_SELECTOR)); - }); - } - - // omitted code -} -``` - -There are multiple things happening here: - -1. Dependent resources are explicitly created and can be access later by reference. -2. Event sources are produced by the dependent resources, but needs to be explicitly registered in - this case by implementing - the [`EventSourceInitializer`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceInitializer.java) - interface. -3. The input html is validated, and error message is set in case it is invalid. -4. Reconciliation of dependent resources is called explicitly, but here the workflow - customization is fully in the hand of the developer. -5. An `Ingress` is created but only in case `exposed` flag set to true on custom resource. Tries to - delete it if not. -6. Status is set in a different way, this is just an alternative way to show, that the actual state - can be read using the reference. This could be written in a same way as in the managed example. - -See the full source code of -sample [here](https://github.com/operator-framework/java-operator-sdk/blob/main/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStandaloneDependentsReconciler.java) -. - -Note also the Workflows feature makes it possible to also support this conditional creation use -case in managed dependent resources. +You can see a commented example of how to do +so [here](https://github.com/operator-framework/java-operator-sdk/blob/main/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStandaloneDependentsReconciler.java). ## Creating/Updating Kubernetes Resources @@ -357,17 +264,17 @@ Since SSA is a complex feature, JOSDK implements a feature flag allowing users t these implementations. See in [ConfigurationService](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java#L332-L358). -It is, however, important to note that these implementations are default, generic -implementations that the framework can provide expected behavior out of the box. In many -situations, these will work just fine but it is also possible to provide matching algorithms +It is, however, important to note that these implementations are default, generic +implementations that the framework can provide expected behavior out of the box. In many +situations, these will work just fine but it is also possible to provide matching algorithms optimized for specific use cases. This is easily done by simply overriding -the `match(...)` [method](https://github.com/java-operator-sdk/java-operator-sdk/blob/e16559fd41bbb8bef6ce9d1f47bffa212a941b09/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java#L156-L156). +the `match(...)` [method](https://github.com/java-operator-sdk/java-operator-sdk/blob/e16559fd41bbb8bef6ce9d1f47bffa212a941b09/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java#L156-L156). -It is also possible to bypass the matching logic altogether to simply rely on the server-side +It is also possible to bypass the matching logic altogether to simply rely on the server-side apply mechanism if always sending potentially unchanged resources to the cluster is not an issue. JOSDK's matching mechanism allows to spare some potentially useless calls to the Kubernetes API -server. To bypass the matching feature completely, simply override the `match` method to always -return `false`, thus telling JOSDK that the actual state never matches the desired one, making +server. To bypass the matching feature completely, simply override the `match` method to always +return `false`, thus telling JOSDK that the actual state never matches the desired one, making it always update the resources using SSA. WARNING: Older versions of Kubernetes before 1.25 would create an additional resource version for every SSA update @@ -394,20 +301,25 @@ tests [here](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/op When dealing with multiple dependent resources of same type, the dependent resource implementation needs to know which specific resource should be targeted when reconciling a given dependent -resource, since there will be multiple instances of that type which could possibly be used, each -associated with the same primary resource. In order to do this, JOSDK relies on the -[resource discriminator](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceDiscriminator.java) -concept. Resource discriminators uniquely identify the target resource of a dependent resource. -In the managed Kubernetes dependent resources case, the discriminator can be declaratively set -using the `@KubernetesDependent` annotation: - -```java - -@KubernetesDependent(resourceDiscriminator = ConfigMap1Discriminator.class) -public class MultipleManagedDependentResourceConfigMap1 { -//... -} -``` +resource, since there could be multiple instances of that type which could possibly be used, each +associated with the same primary resource. In this situation, JOSDK automatically selects the appropriate secondary +resource matching the desired state associated with the primary resource. This makes sense because the desired +state computation already needs to be able to discriminate among multiple related secondary resources to tell JOSDK how +they should be reconciled. + +There might be casees, though, where it might be problematic to call the `desired` method several times (for example, because it is costly to do so), it is always possible to override this automated discrimination using several means: + +- Implement your own `getSecondaryResource` method on your `DependentResource` implementation from scratch. +- Override the `selectManagedSecondaryResource` method, if your `DependentResource` extends `AbstractDependentResource`. + This should be relatively simple to override this method to optimize the matching to your needs. You can see an + example of such an implementation in + the [`ExternalWithStateDependentResource`](https://github.com/operator-framework/java-operator-sdk/blob/6cd0f884a7c9b60c81bd2d52da54adbd64d6e118/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/externalstate/ExternalWithStateDependentResource.java#L43-L49) + class. +- Override the `managedSecondaryResourceID` method, if your `DependentResource` extends `KubernetesDependentResource`, + where it's very often possible to easily determine the `ResourceID` of the secondary resource. This would probably be + the easiest solution if you're working with Kubernetes resources. + +### Sharing an Event Source Between Dependent Resources Dependent resources usually also provide event sources. When dealing with multiple dependents of the same type, one needs to decide whether these dependent resources should track the same @@ -423,10 +335,10 @@ would look as follows: useEventSourceWithName = "configMapSource") ``` -A sample is provided as an integration test both -for [managed](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleManagedDependentSameTypeIT.java) -and -for [standalone](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleDependentResourceIT.java) +A sample is provided as an integration test both: +for [managed](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleManagedDependentNoDiscriminatorIT.java) + +For [standalone](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleDependentResourceIT.java) cases. ## Bulk Dependent Resources @@ -489,15 +401,18 @@ also be created, one per dependent resource. See [integration test](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/ExternalStateBulkIT.java) as a sample. - ## GenericKubernetesResource based Dependent Resources -In rare circumstances resource handling where there is no class representation or just typeless handling might be needed. -Fabric8 Client provides [GenericKubernetesResource](https://github.com/fabric8io/kubernetes-client/blob/main/doc/CHEATSHEET.md#resource-typeless-api) -to support that. +In rare circumstances resource handling where there is no class representation or just typeless handling might be +needed. +Fabric8 Client +provides [GenericKubernetesResource](https://github.com/fabric8io/kubernetes-client/blob/main/doc/CHEATSHEET.md#resource-typeless-api) +to support that. -For dependent resource this is supported by [GenericKubernetesDependentResource](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesDependentResource.java#L8-L8) -. See samples [here](https://github.com/java-operator-sdk/java-operator-sdk/tree/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/generickubernetesresource). +For dependent resource this is supported +by [GenericKubernetesDependentResource](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesDependentResource.java#L8-L8) +. See +samples [here](https://github.com/java-operator-sdk/java-operator-sdk/tree/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/generickubernetesresource). ## Other Dependent Resource Features diff --git a/docs/documentation/features.md b/docs/documentation/features.md index 564b5233f5..68e8422c7c 100644 --- a/docs/documentation/features.md +++ b/docs/documentation/features.md @@ -66,7 +66,8 @@ and/or re-schedule a reconciliation with a desired time delay: @Override public UpdateControl reconcile( EventSourceTestCustomResource resource, Context context) { - ... + // omitted code + return UpdateControl.patchStatus(resource).rescheduleAfter(10, TimeUnit.SECONDS); } ``` @@ -77,7 +78,8 @@ without an update: @Override public UpdateControl reconcile( EventSourceTestCustomResource resource, Context context) { - ... + // omitted code + return UpdateControl.noUpdate().rescheduleAfter(10, TimeUnit.SECONDS); } ``` @@ -89,17 +91,31 @@ Those are the typical use cases of resource updates, however in some cases there the controller wants to update the resource itself (for example to add annotations) or not perform any updates, which is also supported. -It is also possible to update both the status and the resource with the -`updateResourceAndStatus` method. In this case, the resource is updated first followed by the -status, using two separate requests to the Kubernetes API. - -You should always state your intent using `UpdateControl` and let the SDK deal with the actual -updates instead of performing these updates yourself using the actual Kubernetes client so that -the SDK can update its internal state accordingly. - -Resource updates are protected using optimistic version control, to make sure that other updates -that might have occurred in the mean time on the server are not overwritten. This is ensured by -setting the `resourceVersion` field on the processed resources. +It is also possible to update both the status and the resource with the `patchResourceAndStatus` method. In this case, +the resource is updated first followed by the status, using two separate requests to the Kubernetes API. + +From v5 `UpdateControl` only supports patching the resources, by default +using [Server Side Apply (SSA)](https://kubernetes.io/docs/reference/using-api/server-side-apply/). +It is important to understand how SSA works in Kubernetes. Mainly, resources applied using SSA +should contain only the fields identifying the resource and those the user is interested in (a 'fully specified intent' +in Kubernetes parlance), thus usually using a resource created from scratch, see +[sample](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourcewithssa/PatchResourceWithSSAReconciler.java#L18-L22). +To contrast, see the same sample, this time [without SSA](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourceandstatusnossa/PatchResourceAndStatusNoSSAReconciler.java#L16-L16). + +Non-SSA based patch is still supported. +You can control whether or not to use SSA +using [`ConfigurationServcice.useSSAToPatchPrimaryResource()`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java#L385-L385) +and the related `ConfigurationServiceOverrider.withUseSSAToPatchPrimaryResource` method. +Related integration test can be +found [here](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourceandstatusnossa/PatchResourceAndStatusNoSSAReconciler.java). + +Handling resources directly using the client, instead of delegating these updates operations to JOSDK by returning +an `UpdateControl` at the end of your reconciliation, should work appropriately. However, we do recommend to +use `UpdateControl` instead since JOSDK makes sure that the operations are handled properly, since there are subtleties +to be aware of. For example, if you are using a finalizer, JOSDK makes sure to include it in your fully specified intent +so that it is not unintentionally removed from the resource (which would happen if you omit it, since your controller is +the designated manager for that field and Kubernetes interprets the finalizer being gone from the specified intent as a +request for removal). [`DeleteControl`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DeleteControl.java) typically instructs the framework to remove the finalizer after the dependent @@ -108,7 +124,8 @@ resource are cleaned up in `cleanup` implementation. ```java public DeleteControl cleanup(MyCustomResource customResource,Context context){ - ... + // omitted code + return DeleteControl.defaultDelete(); } @@ -167,56 +184,7 @@ You can specify the name of the finalizer to use for your `Reconciler` using the [`@ControllerConfiguration`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java) annotation. If you do not specify a finalizer name, one will be automatically generated for you. -## Automatic Observed Generation Handling - -Having an `.observedGeneration` value on your resources' status is a best practice to -indicate the last generation of the resource which was successfully reconciled by the controller. -This helps users / administrators diagnose potential issues. - -In order to have this feature working: - -- the **status class** (not the resource itself) must implement the - [`ObservedGenerationAware`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/ObservedGenerationAware.java) - interface. See also - the [`ObservedGenerationAwareStatus`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/ObservedGenerationAwareStatus.java) - convenience implementation that you can extend in your own status class implementations. -- The other condition is that the `CustomResource.getStatus()` method should not return `null`. - So the status should be instantiated when the object is returned using the `UpdateControl`. - -If these conditions are fulfilled and generation awareness is activated, the observed generation -is automatically set by the framework after the `reconcile` method is called. Note that the -observed generation is also updated even when `UpdateControl.noUpdate()` is returned from the -reconciler. See this feature at work in -the [WebPage example](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStatus.java#L5) -. - -```java -public class WebPageStatus extends ObservedGenerationAwareStatus { - - private String htmlConfigMap; - - ... -} -``` - -Initializing status automatically on custom resource could be done by overriding the `initStatus` method -of `CustomResource`. However, this is NOT advised, since breaks the status patching if you use: -`UpdateControl.patchStatus`. See -also [javadocs](https://github.com/java-operator-sdk/java-operator-sdk/blob/3994f5ffc1fb000af81aa198abf72a5f75fd3e97/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/UpdateControl.java#L41-L42) -. - -```java -@Group("sample.javaoperatorsdk") -@Version("v1") -public class WebPage extends CustomResource - implements Namespaced { - - @Override - protected WebPageStatus initStatus() { - return new WebPageStatus(); - } -} -``` +From v5 by default finalizer is added using Served Side Apply. See also UpdateControl in docs. ## Generation Awareness and Event Filtering @@ -235,9 +203,7 @@ To turn off this feature, set `generationAwareEventProcessing` to `false` for th ## Support for Well Known (non-custom) Kubernetes Resources A Controller can be registered for a non-custom resource, so well known Kubernetes resources like ( -`Ingress`, `Deployment`,...). Note that automatic observed generation handling is not supported -for these resources, though, in this case, the handling of the observed generation is probably -handled by the primary controller. +`Ingress`, `Deployment`,...). See the [integration test](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/deployment/DeploymentReconciler.java) @@ -247,11 +213,12 @@ for reconciling deployments. public class DeploymentReconciler implements Reconciler, TestExecutionInfoProvider { - @Override - public UpdateControl reconcile( - Deployment resource, Context context) { - ... - } + @Override + public UpdateControl reconcile( + Deployment resource, Context context) { + // omitted code + } +} ``` ## Max Interval Between Reconciliations @@ -269,6 +236,7 @@ standard annotation: @ControllerConfiguration(maxReconciliationInterval = @MaxReconciliationInterval( interval = 50, timeUnit = TimeUnit.MILLISECONDS)) +public class MyReconciler implements Reconciler {} ``` The event is not propagated at a fixed rate, rather it's scheduled after each reconciliation. So the @@ -491,9 +459,8 @@ related [method](https://github.com/java-operator-sdk/java-operator-sdk/blob/mai ### Registering Event Sources -To register event sources, your `Reconciler` has to implement the -[`EventSourceInitializer`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceInitializer.java) -interface and initialize a list of event sources to register. One way to see this in action is +To register event sources, your `Reconciler` has to override the `prepareEventSources` and return +list of event sources to register. One way to see this in action is to look at the [tomcat example](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatReconciler.java) (irrelevant details omitted): @@ -501,8 +468,10 @@ to look at the ```java @ControllerConfiguration -public class TomcatReconciler implements Reconciler, EventSourceInitializer { +public class TomcatReconciler implements Reconciler { + // omitted code + @Override public List prepareEventSources(EventSourceContext context) { var configMapEventSource = @@ -511,9 +480,9 @@ public class TomcatReconciler implements Reconciler, EventSourceInitiali .withSecondaryToPrimaryMapper( Mappers.fromAnnotation(ANNOTATION_NAME, ANNOTATION_NAMESPACE) .build(), context)); - return EventSourceInitializer.nameEventSources(configMapEventSource); + return List.of(configMapEventSource); } - ... + } ``` @@ -657,15 +626,15 @@ registering an associated `Informer` and then calling the `changeNamespaces` met ```java -public static void main(String[]args)throws IOException{ - KubernetesClient client=new DefaultKubernetesClient(); - Operator operator=new Operator(client); - RegisteredController registeredController=operator.register(new WebPageReconciler(client)); - operator.installShutdownHook(); - operator.start(); +public static void main(String[] args) { + KubernetesClient client = new DefaultKubernetesClient(); + Operator operator = new Operator(client); + RegisteredController registeredController = operator.register(new WebPageReconciler(client)); + operator.installShutdownHook(); + operator.start(); - // call registeredController further while operator is running - } + // call registeredController further while operator is running +} ``` @@ -677,8 +646,7 @@ configured appropriately so that the `followControllerNamespaceChanges` method r ```java @ControllerConfiguration -public class MyReconciler - implements Reconciler, EventSourceInitializer { +public class MyReconciler implements Reconciler { @Override public Map prepareEventSources( @@ -689,7 +657,7 @@ public class MyReconciler .withNamespacesInheritedFromController(context) .build(), context); - return EventSourceInitializer.nameEventSources(configMapES); + return EventSourceUtils.nameEventSources(configMapES); } } @@ -768,8 +736,8 @@ You can use a different implementation by overriding the default one provided by follows: ```java -Metrics metrics= …; -Operator operator = new Operator(client, o -> o.withMetrics()); +Metrics metrics; // initialize your metrics implementation +Operator operator = new Operator(client, o -> o.withMetrics(metrics)); ``` ### Micrometer implementation @@ -785,8 +753,8 @@ To create a `MicrometerMetrics` implementation that behaves how it has historica instance via: ```java -MeterRegistry registry= …; -Metrics metrics=new MicrometerMetrics(registry) +MeterRegistry registry; // initialize your registry implementation +Metrics metrics = new MicrometerMetrics(registry); ``` Note, however, that this constructor is deprecated and we encourage you to use the factory methods instead, which either @@ -802,7 +770,7 @@ basis, deleting the associated meters after 5 seconds when a resource is deleted MicrometerMetrics.newPerResourceCollectingMicrometerMetricsBuilder(registry) .withCleanUpDelayInSeconds(5) .withCleaningThreadNumber(2) - .build() + .build(); ``` The micrometer implementation records the following metrics: diff --git a/docs/documentation/v5-0-migration.md b/docs/documentation/v5-0-migration.md new file mode 100644 index 0000000000..a1c1b66120 --- /dev/null +++ b/docs/documentation/v5-0-migration.md @@ -0,0 +1,65 @@ +--- +title: Migrating from v4.7 to v5.0 +description: Migrating from v4.7 to v5.0 +layout: docs +permalink: /docs/v5-0-migration +--- + +# Migrating from v4.7 to v5.0 + +## API Tweaks + +1. [Result of managed dependent resources](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedDependentResourceContext.java#L55-L57) + is not `Optional` anymore. In case you use this result, simply use the result + objects directly. +2. `EventSourceInitializer` is not a separate interface anymore. It is part of the `Reconciler` interface with a + default implementation. You can simply remove this interface from your reconciler. The + [`EventSourceUtils`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceUtils.java#L11-L11) + now contains all the utility methods used for event sources naming that were previously defined in + the `EventSourceInitializer` interface. +3. Similarly, the `EventSourceProvider` interface has been remove, replaced by explicit initialization of the associated + event source on `DependentResource` via the ` + Optional> eventSource(EventSourceContext

eventSourceContext)` method. +4. Event sources are now explicitly named (via the `name` method of the `EventSource` interface). Built-in event sources + implementation have been updated to allow you to specify a name when instantiating them. If you don't provide a name + for your `EventSource` implementation (for example, by using its default, no-arg constructor), one will be + automatically generated. This simplifies the API to define event source to + `List prepareEventSources(EventSourceContext

context)`. + !!! IMPORTANT !!! + If you use dynamic registration of event sources, be sure to name your event sources explicitly as letting JOSDK name + them automatically might result in duplicated event sources being registered as JOSDK relies on the name to identify + event sources and concurrent, dynamic registration might lead to identical event sources having different generated + names, thus leading JOSDK to consider them as different and hence, register them multiple times. +5. Updates through `UpdateControl` now + use [Server Side Apply (SSA)](https://kubernetes.io/docs/reference/using-api/server-side-apply/) by default to add + the finalizer and for all + the patch operations in `UpdateControl`. The update operations were removed. If you do not wish to use SSA, you can + deactivate the feature using `ConfigurationService.useSSAToPatchPrimaryResource` and + related `ConfigurationServiceOverrider.withUseSSAToPatchPrimaryResource`. + + !!! IMPORTANT !!! + + See known issues with migration from non-SSA to SSA based status updates here: + [integration test](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/StatusPatchSSAMigrationIT.java#L71-L82) + where it is demonstrated. Also, the related part of + a [workaround](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/StatusPatchSSAMigrationIT.java#L110-L116). + +6. `ManagedDependentResourceContext` has been renamed to `ManagedWorkflowAndDependentResourceContext` and is accessed + via the accordingly renamed `managedWorkflowAndDependentResourceContext` method. +7. `ResourceDiscriminator` was removed. In most of the cases you can just delete the discriminator, everything should + work without it by default. To optimize and handle special cases see the relevant section + in [Dependent Resource documentation](/docs/dependent-resources#multiple-dependent-resources-of-same-type). +8. `ConfigurationService.getTerminationTimeoutSeconds` and associated overriding mechanism have been removed, + use `Operator.stop(Duration)` instead. +9. `Operator.installShutdownHook()` has been removed, use `Operator.installShutdownHook(Duration)` instead +10. Automated observed generation handling feature was removed (`ObservedGenerationAware` interface + and `ObservedGenerationAwareStatus` class were deleted). Manually handling observed generation is fairly easy to do + in your reconciler, however, it cannot be done automatically when using SSA. We therefore removed the feature since + it would have been confusing to have a different behavior for SSA and non-SSA cases. For an example of how to do + observed generation handling manually in your reconciler, see + [this sample](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/manualobservedgeneration/ManualObservedGenerationReconciler.java). +11. `BulkDependentResource` now supports [read-only mode](https://github.com/operator-framework/java-operator-sdk/issues/2233). + This also means, that `BulkDependentResource` now does not automatically implement `Creator` and `Deleter` as before. + Make sure to implement those interfaces in your bulk dependent resources. You can use also the new helper interface, the + [`CRUDBulkDependentResource`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/CRUDBulkDependentResource.java) + what also implement `BulkUpdater` interface. \ No newline at end of file diff --git a/docs/documentation/workflows.md b/docs/documentation/workflows.md index 7190f8e8e3..8d4434683f 100644 --- a/docs/documentation/workflows.md +++ b/docs/documentation/workflows.md @@ -122,7 +122,7 @@ page sample): @ControllerConfiguration( labelSelector = WebPageDependentsWorkflowReconciler.DEPENDENT_RESOURCE_LABEL_SELECTOR) public class WebPageDependentsWorkflowReconciler - implements Reconciler, ErrorStatusHandler, EventSourceInitializer { + implements Reconciler, ErrorStatusHandler { public static final String DEPENDENT_RESOURCE_LABEL_SELECTOR = "!low-level"; private static final Logger log = @@ -133,7 +133,7 @@ public class WebPageDependentsWorkflowReconciler private KubernetesDependentResource serviceDR; private KubernetesDependentResource ingressDR; - private Workflow workflow; + private final Workflow workflow; public WebPageDependentsWorkflowReconciler(KubernetesClient kubernetesClient) { initDependentResources(kubernetesClient); @@ -147,7 +147,7 @@ public class WebPageDependentsWorkflowReconciler @Override public Map prepareEventSources(EventSourceContext context) { - return EventSourceInitializer.nameEventSources( + return EventSourceUtils.nameEventSources( configMapDR.initEventSource(context), deploymentDR.initEventSource(context), serviceDR.initEventSource(context), @@ -338,6 +338,34 @@ and NOT `CRUDKubernetesDependentResource` since otherwise the Kubernetes Garbage In other words if a Kubernetes Dependent Resource depends on another dependent resource, it should not implement `GargageCollected` interface, otherwise the deletion order won't be guaranteed. + +## Explicit Managed Workflow Invocation + +Managed workflows, i.e. ones that are declared via annotations and therefore completely managed by JOSDK, are reconciled +before the primary resource. Each dependent resource that can be reconciled (according to the workflow configuration) +will therefore be reconciled before the primary reconciler is called to reconcile the primary resource. There are, +however, situations where it would be be useful to perform additional steps before the workflow is reconciled, for +example to validate the current state, execute arbitrary logic or even skip reconciliation altogether. Explicit +invocation of managed workflow was therefore introduced to solve these issues. + +To use this feature, you need to set the `explicitInvocation` field to `true` on the `@Workflow` annotation and then +call the `reconcileManagedWorkflow` method from the ` +ManagedWorkflowAndDependentResourceContext` retrieved from the reconciliation `Context` provided as part of your primary +resource reconciler `reconcile` method arguments. + +See +related [integration test](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowExplicitInvocationIT.java) +for more details. + +For `cleanup`, if the `Cleaner` interface is implemented, the `cleanupManageWorkflow()` needs to be called explicitly. +However, if `Cleaner` interface is not implemented, it will be called implicitly. +See +related [integration test](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowExplicitCleanupIT.java). + +While nothing prevents calling the workflow multiple times in a reconciler, it isn't typical or even recommended to do +so. Conversely, if explicit invocation is requested but `reconcileManagedWorkflow` is not called in the primary resource +reconciler, the workflow won't be reconciled at all. + ## Notes and Caveats - Delete is almost always called on every resource during the cleanup. However, it might be the case diff --git a/docsy/content/en/docs/dependent-resources/_index.md b/docsy/content/en/docs/dependent-resources/_index.md index 51738869e3..f889775913 100644 --- a/docsy/content/en/docs/dependent-resources/_index.md +++ b/docsy/content/en/docs/dependent-resources/_index.md @@ -3,6 +3,8 @@ title: Dependent Resources weight: 60 --- +# Dependent Resources + ## Motivations and Goals Most operators need to deal with secondary resources when trying to realize the desired state @@ -97,13 +99,13 @@ and labels, which are ignored by default: ```java public class MyDependentResource extends KubernetesDependentResource - implements Matcher { - // your implementation + implements Matcher { + // your implementation - public Result match(MyDependent actualResource, MyPrimary primary, - Context context) { - return GenericKubernetesResourceMatcher.match(this, actualResource, primary, context, true); - } + public Result match(MyDependent actualResource, MyPrimary primary, + Context context) { + return GenericKubernetesResourceMatcher.match(this, actualResource, primary, context, true); + } } ``` @@ -137,24 +139,24 @@ Deleted (or set to be garbage collected). The following example shows how to cre @KubernetesDependent(labelSelector = WebPageManagedDependentsReconciler.SELECTOR) class DeploymentDependentResource extends CRUDKubernetesDependentResource { - public DeploymentDependentResource() { - super(Deployment.class); - } - - @Override - protected Deployment desired(WebPage webPage, Context context) { - var deploymentName = deploymentName(webPage); - Deployment deployment = loadYaml(Deployment.class, getClass(), "deployment.yaml"); - deployment.getMetadata().setName(deploymentName); - deployment.getMetadata().setNamespace(webPage.getMetadata().getNamespace()); - deployment.getSpec().getSelector().getMatchLabels().put("app", deploymentName); - - deployment.getSpec().getTemplate().getMetadata().getLabels() - .put("app", deploymentName); - deployment.getSpec().getTemplate().getSpec().getVolumes().get(0) - .setConfigMap(new ConfigMapVolumeSourceBuilder().withName(configMapName(webPage)).build()); - return deployment; - } + public DeploymentDependentResource() { + super(Deployment.class); + } + + @Override + protected Deployment desired(WebPage webPage, Context context) { + var deploymentName = deploymentName(webPage); + Deployment deployment = loadYaml(Deployment.class, getClass(), "deployment.yaml"); + deployment.getMetadata().setName(deploymentName); + deployment.getMetadata().setNamespace(webPage.getMetadata().getNamespace()); + deployment.getSpec().getSelector().getMatchLabels().put("app", deploymentName); + + deployment.getSpec().getTemplate().getMetadata().getLabels() + .put("app", deploymentName); + deployment.getSpec().getTemplate().getSpec().getVolumes().get(0) + .setConfigMap(new ConfigMapVolumeSourceBuilder().withName(configMapName(webPage)).build()); + return deployment; + } } ``` @@ -190,25 +192,25 @@ instances are managed by JOSDK, an example of which can be seen below: ```java @ControllerConfiguration( - labelSelector = SELECTOR, - dependents = { - @Dependent(type = ConfigMapDependentResource.class), - @Dependent(type = DeploymentDependentResource.class), - @Dependent(type = ServiceDependentResource.class) - }) + labelSelector = SELECTOR, + dependents = { + @Dependent(type = ConfigMapDependentResource.class), + @Dependent(type = DeploymentDependentResource.class), + @Dependent(type = ServiceDependentResource.class) + }) public class WebPageManagedDependentsReconciler - implements Reconciler, ErrorStatusHandler { + implements Reconciler, ErrorStatusHandler { - // omitted code + // omitted code - @Override - public UpdateControl reconcile(WebPage webPage, Context context) { + @Override + public UpdateControl reconcile(WebPage webPage, Context context) { - final var name = context.getSecondaryResource(ConfigMap.class).orElseThrow() - .getMetadata().getName(); - webPage.setStatus(createStatus(name)); - return UpdateControl.patchStatus(webPage); - } + final var name = context.getSecondaryResource(ConfigMap.class).orElseThrow() + .getMetadata().getName(); + webPage.setStatus(createStatus(name)); + return UpdateControl.patchStatus(webPage); + } } ``` @@ -223,104 +225,11 @@ 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. - -Note that [Workflows](https://javaoperatorsdk.io/docs/workflows) also can be invoked from standalone -resources. - -The following sample is similar to the one above, simply performing additional checks, and -conditionally creating an `Ingress`: - -```java - -@ControllerConfiguration -public class WebPageStandaloneDependentsReconciler - implements Reconciler, ErrorStatusHandler, - EventSourceInitializer { - - private KubernetesDependentResource configMapDR; - private KubernetesDependentResource deploymentDR; - private KubernetesDependentResource serviceDR; - private KubernetesDependentResource ingressDR; - - public WebPageStandaloneDependentsReconciler(KubernetesClient kubernetesClient) { - // 1. - createDependentResources(kubernetesClient); - } - - @Override - public List prepareEventSources(EventSourceContext context) { - // 2. - return List.of( - configMapDR.initEventSource(context), - deploymentDR.initEventSource(context), - serviceDR.initEventSource(context)); - } - - @Override - public UpdateControl reconcile(WebPage webPage, Context context) { - - // 3. - if (!isValidHtml(webPage.getHtml())) { - return UpdateControl.patchStatus(setInvalidHtmlErrorMessage(webPage)); - } - - // 4. - configMapDR.reconcile(webPage, context); - deploymentDR.reconcile(webPage, context); - serviceDR.reconcile(webPage, context); - - // 5. - if (Boolean.TRUE.equals(webPage.getSpec().getExposed())) { - ingressDR.reconcile(webPage, context); - } else { - ingressDR.delete(webPage, context); - } +case does not fit. You can, of course, also use [Workflows](https://javaoperatorsdk.io/docs/workflows) when managing +resources programmatically. - // 6. - webPage.setStatus( - createStatus(configMapDR.getResource(webPage).orElseThrow().getMetadata().getName())); - return UpdateControl.patchStatus(webPage); - } - - private void createDependentResources(KubernetesClient client) { - this.configMapDR = new ConfigMapDependentResource(); - this.deploymentDR = new DeploymentDependentResource(); - this.serviceDR = new ServiceDependentResource(); - this.ingressDR = new IngressDependentResource(); - - Arrays.asList(configMapDR, deploymentDR, serviceDR, ingressDR).forEach(dr -> { - dr.setKubernetesClient(client); - dr.configureWith(new KubernetesDependentResourceConfig() - .setLabelSelector(DEPENDENT_RESOURCE_LABEL_SELECTOR)); - }); - } - - // omitted code -} -``` - -There are multiple things happening here: - -1. Dependent resources are explicitly created and can be access later by reference. -2. Event sources are produced by the dependent resources, but needs to be explicitly registered in - this case by implementing - the [`EventSourceInitializer`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceInitializer.java) - interface. -3. The input html is validated, and error message is set in case it is invalid. -4. Reconciliation of dependent resources is called explicitly, but here the workflow - customization is fully in the hand of the developer. -5. An `Ingress` is created but only in case `exposed` flag set to true on custom resource. Tries to - delete it if not. -6. Status is set in a different way, this is just an alternative way to show, that the actual state - can be read using the reference. This could be written in a same way as in the managed example. - -See the full source code of -sample [here](https://github.com/operator-framework/java-operator-sdk/blob/main/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStandaloneDependentsReconciler.java) -. - -Note also the Workflows feature makes it possible to also support this conditional creation use -case in managed dependent resources. +You can see a commented example of how to do +so [here](https://github.com/operator-framework/java-operator-sdk/blob/main/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStandaloneDependentsReconciler.java). ## Creating/Updating Kubernetes Resources @@ -353,17 +262,17 @@ Since SSA is a complex feature, JOSDK implements a feature flag allowing users t these implementations. See in [ConfigurationService](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java#L332-L358). -It is, however, important to note that these implementations are default, generic -implementations that the framework can provide expected behavior out of the box. In many -situations, these will work just fine but it is also possible to provide matching algorithms +It is, however, important to note that these implementations are default, generic +implementations that the framework can provide expected behavior out of the box. In many +situations, these will work just fine but it is also possible to provide matching algorithms optimized for specific use cases. This is easily done by simply overriding -the `match(...)` [method](https://github.com/java-operator-sdk/java-operator-sdk/blob/e16559fd41bbb8bef6ce9d1f47bffa212a941b09/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java#L156-L156). +the `match(...)` [method](https://github.com/java-operator-sdk/java-operator-sdk/blob/e16559fd41bbb8bef6ce9d1f47bffa212a941b09/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java#L156-L156). -It is also possible to bypass the matching logic altogether to simply rely on the server-side +It is also possible to bypass the matching logic altogether to simply rely on the server-side apply mechanism if always sending potentially unchanged resources to the cluster is not an issue. JOSDK's matching mechanism allows to spare some potentially useless calls to the Kubernetes API -server. To bypass the matching feature completely, simply override the `match` method to always -return `false`, thus telling JOSDK that the actual state never matches the desired one, making +server. To bypass the matching feature completely, simply override the `match` method to always +return `false`, thus telling JOSDK that the actual state never matches the desired one, making it always update the resources using SSA. WARNING: Older versions of Kubernetes before 1.25 would create an additional resource version for every SSA update @@ -390,20 +299,25 @@ tests [here](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/op When dealing with multiple dependent resources of same type, the dependent resource implementation needs to know which specific resource should be targeted when reconciling a given dependent -resource, since there will be multiple instances of that type which could possibly be used, each -associated with the same primary resource. In order to do this, JOSDK relies on the -[resource discriminator](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceDiscriminator.java) -concept. Resource discriminators uniquely identify the target resource of a dependent resource. -In the managed Kubernetes dependent resources case, the discriminator can be declaratively set -using the `@KubernetesDependent` annotation: - -```java - -@KubernetesDependent(resourceDiscriminator = ConfigMap1Discriminator.class) -public class MultipleManagedDependentResourceConfigMap1 { -//... -} -``` +resource, since there could be multiple instances of that type which could possibly be used, each +associated with the same primary resource. In this situation, JOSDK automatically selects the appropriate secondary +resource matching the desired state associated with the primary resource. This makes sense because the desired +state computation already needs to be able to discriminate among multiple related secondary resources to tell JOSDK how +they should be reconciled. + +There might be casees, though, where it might be problematic to call the `desired` method several times (for example, because it is costly to do so), it is always possible to override this automated discrimination using several means: + +- Implement your own `getSecondaryResource` method on your `DependentResource` implementation from scratch. +- Override the `selectManagedSecondaryResource` method, if your `DependentResource` extends `AbstractDependentResource`. + This should be relatively simple to override this method to optimize the matching to your needs. You can see an + example of such an implementation in + the [`ExternalWithStateDependentResource`](https://github.com/operator-framework/java-operator-sdk/blob/6cd0f884a7c9b60c81bd2d52da54adbd64d6e118/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/externalstate/ExternalWithStateDependentResource.java#L43-L49) + class. +- Override the `managedSecondaryResourceID` method, if your `DependentResource` extends `KubernetesDependentResource`, + where it's very often possible to easily determine the `ResourceID` of the secondary resource. This would probably be + the easiest solution if you're working with Kubernetes resources. + +### Sharing an Event Source Between Dependent Resources Dependent resources usually also provide event sources. When dealing with multiple dependents of the same type, one needs to decide whether these dependent resources should track the same @@ -419,10 +333,10 @@ would look as follows: useEventSourceWithName = "configMapSource") ``` -A sample is provided as an integration test both -for [managed](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleManagedDependentSameTypeIT.java) -and -for [standalone](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleDependentResourceIT.java) +A sample is provided as an integration test both: +for [managed](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleManagedDependentNoDiscriminatorIT.java) + +For [standalone](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleDependentResourceIT.java) cases. ## Bulk Dependent Resources @@ -485,15 +399,18 @@ also be created, one per dependent resource. See [integration test](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/ExternalStateBulkIT.java) as a sample. - ## GenericKubernetesResource based Dependent Resources -In rare circumstances resource handling where there is no class representation or just typeless handling might be needed. -Fabric8 Client provides [GenericKubernetesResource](https://github.com/fabric8io/kubernetes-client/blob/main/doc/CHEATSHEET.md#resource-typeless-api) -to support that. +In rare circumstances resource handling where there is no class representation or just typeless handling might be +needed. +Fabric8 Client +provides [GenericKubernetesResource](https://github.com/fabric8io/kubernetes-client/blob/main/doc/CHEATSHEET.md#resource-typeless-api) +to support that. -For dependent resource this is supported by [GenericKubernetesDependentResource](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesDependentResource.java#L8-L8) -. See samples [here](https://github.com/java-operator-sdk/java-operator-sdk/tree/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/generickubernetesresource). +For dependent resource this is supported +by [GenericKubernetesDependentResource](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesDependentResource.java#L8-L8) +. See +samples [here](https://github.com/java-operator-sdk/java-operator-sdk/tree/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/generickubernetesresource). ## Other Dependent Resource Features diff --git a/docsy/content/en/docs/features/_index.md b/docsy/content/en/docs/features/_index.md index b1f0ff166f..8bd5e305e5 100644 --- a/docsy/content/en/docs/features/_index.md +++ b/docsy/content/en/docs/features/_index.md @@ -3,6 +3,8 @@ title: Features weight: 50 --- +# Features + The Java Operator SDK (JOSDK) is a high level framework and related tooling aimed at facilitating the implementation of Kubernetes operators. The features are by default following the best practices in an opinionated way. However, feature flags and other configuration options @@ -62,7 +64,8 @@ and/or re-schedule a reconciliation with a desired time delay: @Override public UpdateControl reconcile( EventSourceTestCustomResource resource, Context context) { - ... + // omitted code + return UpdateControl.patchStatus(resource).rescheduleAfter(10, TimeUnit.SECONDS); } ``` @@ -73,7 +76,8 @@ without an update: @Override public UpdateControl reconcile( EventSourceTestCustomResource resource, Context context) { - ... + // omitted code + return UpdateControl.noUpdate().rescheduleAfter(10, TimeUnit.SECONDS); } ``` @@ -85,17 +89,31 @@ Those are the typical use cases of resource updates, however in some cases there the controller wants to update the resource itself (for example to add annotations) or not perform any updates, which is also supported. -It is also possible to update both the status and the resource with the -`updateResourceAndStatus` method. In this case, the resource is updated first followed by the -status, using two separate requests to the Kubernetes API. - -You should always state your intent using `UpdateControl` and let the SDK deal with the actual -updates instead of performing these updates yourself using the actual Kubernetes client so that -the SDK can update its internal state accordingly. - -Resource updates are protected using optimistic version control, to make sure that other updates -that might have occurred in the mean time on the server are not overwritten. This is ensured by -setting the `resourceVersion` field on the processed resources. +It is also possible to update both the status and the resource with the `patchResourceAndStatus` method. In this case, +the resource is updated first followed by the status, using two separate requests to the Kubernetes API. + +From v5 `UpdateControl` only supports patching the resources, by default +using [Server Side Apply (SSA)](https://kubernetes.io/docs/reference/using-api/server-side-apply/). +It is important to understand how SSA works in Kubernetes. Mainly, resources applied using SSA +should contain only the fields identifying the resource and those the user is interested in (a 'fully specified intent' +in Kubernetes parlance), thus usually using a resource created from scratch, see +[sample](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourcewithssa/PatchResourceWithSSAReconciler.java#L18-L22). +To contrast, see the same sample, this time [without SSA](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourceandstatusnossa/PatchResourceAndStatusNoSSAReconciler.java#L16-L16). + +Non-SSA based patch is still supported. +You can control whether or not to use SSA +using [`ConfigurationServcice.useSSAToPatchPrimaryResource()`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java#L385-L385) +and the related `ConfigurationServiceOverrider.withUseSSAToPatchPrimaryResource` method. +Related integration test can be +found [here](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourceandstatusnossa/PatchResourceAndStatusNoSSAReconciler.java). + +Handling resources directly using the client, instead of delegating these updates operations to JOSDK by returning +an `UpdateControl` at the end of your reconciliation, should work appropriately. However, we do recommend to +use `UpdateControl` instead since JOSDK makes sure that the operations are handled properly, since there are subtleties +to be aware of. For example, if you are using a finalizer, JOSDK makes sure to include it in your fully specified intent +so that it is not unintentionally removed from the resource (which would happen if you omit it, since your controller is +the designated manager for that field and Kubernetes interprets the finalizer being gone from the specified intent as a +request for removal). [`DeleteControl`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DeleteControl.java) typically instructs the framework to remove the finalizer after the dependent @@ -104,7 +122,8 @@ resource are cleaned up in `cleanup` implementation. ```java public DeleteControl cleanup(MyCustomResource customResource,Context context){ - ... + // omitted code + return DeleteControl.defaultDelete(); } @@ -163,56 +182,7 @@ You can specify the name of the finalizer to use for your `Reconciler` using the [`@ControllerConfiguration`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java) annotation. If you do not specify a finalizer name, one will be automatically generated for you. -## Automatic Observed Generation Handling - -Having an `.observedGeneration` value on your resources' status is a best practice to -indicate the last generation of the resource which was successfully reconciled by the controller. -This helps users / administrators diagnose potential issues. - -In order to have this feature working: - -- the **status class** (not the resource itself) must implement the - [`ObservedGenerationAware`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/ObservedGenerationAware.java) - interface. See also - the [`ObservedGenerationAwareStatus`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/ObservedGenerationAwareStatus.java) - convenience implementation that you can extend in your own status class implementations. -- The other condition is that the `CustomResource.getStatus()` method should not return `null`. - So the status should be instantiated when the object is returned using the `UpdateControl`. - -If these conditions are fulfilled and generation awareness is activated, the observed generation -is automatically set by the framework after the `reconcile` method is called. Note that the -observed generation is also updated even when `UpdateControl.noUpdate()` is returned from the -reconciler. See this feature at work in -the [WebPage example](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStatus.java#L5) -. - -```java -public class WebPageStatus extends ObservedGenerationAwareStatus { - - private String htmlConfigMap; - - ... -} -``` - -Initializing status automatically on custom resource could be done by overriding the `initStatus` method -of `CustomResource`. However, this is NOT advised, since breaks the status patching if you use: -`UpdateControl.patchStatus`. See -also [javadocs](https://github.com/java-operator-sdk/java-operator-sdk/blob/3994f5ffc1fb000af81aa198abf72a5f75fd3e97/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/UpdateControl.java#L41-L42) -. - -```java -@Group("sample.javaoperatorsdk") -@Version("v1") -public class WebPage extends CustomResource - implements Namespaced { - - @Override - protected WebPageStatus initStatus() { - return new WebPageStatus(); - } -} -``` +From v5 by default finalizer is added using Served Side Apply. See also UpdateControl in docs. ## Generation Awareness and Event Filtering @@ -231,9 +201,7 @@ To turn off this feature, set `generationAwareEventProcessing` to `false` for th ## Support for Well Known (non-custom) Kubernetes Resources A Controller can be registered for a non-custom resource, so well known Kubernetes resources like ( -`Ingress`, `Deployment`,...). Note that automatic observed generation handling is not supported -for these resources, though, in this case, the handling of the observed generation is probably -handled by the primary controller. +`Ingress`, `Deployment`,...). See the [integration test](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/deployment/DeploymentReconciler.java) @@ -243,11 +211,12 @@ for reconciling deployments. public class DeploymentReconciler implements Reconciler, TestExecutionInfoProvider { - @Override - public UpdateControl reconcile( - Deployment resource, Context context) { - ... - } + @Override + public UpdateControl reconcile( + Deployment resource, Context context) { + // omitted code + } +} ``` ## Max Interval Between Reconciliations @@ -265,6 +234,7 @@ standard annotation: @ControllerConfiguration(maxReconciliationInterval = @MaxReconciliationInterval( interval = 50, timeUnit = TimeUnit.MILLISECONDS)) +public class MyReconciler implements Reconciler {} ``` The event is not propagated at a fixed rate, rather it's scheduled after each reconciliation. So the @@ -487,9 +457,8 @@ related [method](https://github.com/java-operator-sdk/java-operator-sdk/blob/mai ### Registering Event Sources -To register event sources, your `Reconciler` has to implement the -[`EventSourceInitializer`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceInitializer.java) -interface and initialize a list of event sources to register. One way to see this in action is +To register event sources, your `Reconciler` has to override the `prepareEventSources` and return +list of event sources to register. One way to see this in action is to look at the [tomcat example](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatReconciler.java) (irrelevant details omitted): @@ -497,8 +466,10 @@ to look at the ```java @ControllerConfiguration -public class TomcatReconciler implements Reconciler, EventSourceInitializer { +public class TomcatReconciler implements Reconciler { + // omitted code + @Override public List prepareEventSources(EventSourceContext context) { var configMapEventSource = @@ -507,9 +478,9 @@ public class TomcatReconciler implements Reconciler, EventSourceInitiali .withSecondaryToPrimaryMapper( Mappers.fromAnnotation(ANNOTATION_NAME, ANNOTATION_NAMESPACE) .build(), context)); - return EventSourceInitializer.nameEventSources(configMapEventSource); + return List.of(configMapEventSource); } - ... + } ``` @@ -653,15 +624,15 @@ registering an associated `Informer` and then calling the `changeNamespaces` met ```java -public static void main(String[]args)throws IOException{ - KubernetesClient client=new DefaultKubernetesClient(); - Operator operator=new Operator(client); - RegisteredController registeredController=operator.register(new WebPageReconciler(client)); - operator.installShutdownHook(); - operator.start(); +public static void main(String[] args) { + KubernetesClient client = new DefaultKubernetesClient(); + Operator operator = new Operator(client); + RegisteredController registeredController = operator.register(new WebPageReconciler(client)); + operator.installShutdownHook(); + operator.start(); - // call registeredController further while operator is running - } + // call registeredController further while operator is running +} ``` @@ -673,8 +644,7 @@ configured appropriately so that the `followControllerNamespaceChanges` method r ```java @ControllerConfiguration -public class MyReconciler - implements Reconciler, EventSourceInitializer { +public class MyReconciler implements Reconciler { @Override public Map prepareEventSources( @@ -685,7 +655,7 @@ public class MyReconciler .withNamespacesInheritedFromController(context) .build(), context); - return EventSourceInitializer.nameEventSources(configMapES); + return EventSourceUtils.nameEventSources(configMapES); } } @@ -764,8 +734,8 @@ You can use a different implementation by overriding the default one provided by follows: ```java -Metrics metrics= …; -Operator operator = new Operator(client, o -> o.withMetrics()); +Metrics metrics; // initialize your metrics implementation +Operator operator = new Operator(client, o -> o.withMetrics(metrics)); ``` ### Micrometer implementation @@ -781,8 +751,8 @@ To create a `MicrometerMetrics` implementation that behaves how it has historica instance via: ```java -MeterRegistry registry= …; -Metrics metrics=new MicrometerMetrics(registry) +MeterRegistry registry; // initialize your registry implementation +Metrics metrics = new MicrometerMetrics(registry); ``` Note, however, that this constructor is deprecated and we encourage you to use the factory methods instead, which either @@ -798,7 +768,7 @@ basis, deleting the associated meters after 5 seconds when a resource is deleted MicrometerMetrics.newPerResourceCollectingMicrometerMetricsBuilder(registry) .withCleanUpDelayInSeconds(5) .withCleaningThreadNumber(2) - .build() + .build(); ``` The micrometer implementation records the following metrics: diff --git a/docsy/content/en/docs/migration/v5-0-migration.md b/docsy/content/en/docs/migration/v5-0-migration.md new file mode 100644 index 0000000000..c766d5c770 --- /dev/null +++ b/docsy/content/en/docs/migration/v5-0-migration.md @@ -0,0 +1,63 @@ +--- +title: Migrating from v4.7 to v5.0 +description: Migrating from v4.7 to v5.0 +--- + +# Migrating from v4.7 to v5.0 + +## API Tweaks + +1. [Result of managed dependent resources](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedDependentResourceContext.java#L55-L57) + is not `Optional` anymore. In case you use this result, simply use the result + objects directly. +2. `EventSourceInitializer` is not a separate interface anymore. It is part of the `Reconciler` interface with a + default implementation. You can simply remove this interface from your reconciler. The + [`EventSourceUtils`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceUtils.java#L11-L11) + now contains all the utility methods used for event sources naming that were previously defined in + the `EventSourceInitializer` interface. +3. Similarly, the `EventSourceProvider` interface has been remove, replaced by explicit initialization of the associated + event source on `DependentResource` via the ` + Optional> eventSource(EventSourceContext

eventSourceContext)` method. +4. Event sources are now explicitly named (via the `name` method of the `EventSource` interface). Built-in event sources + implementation have been updated to allow you to specify a name when instantiating them. If you don't provide a name + for your `EventSource` implementation (for example, by using its default, no-arg constructor), one will be + automatically generated. This simplifies the API to define event source to + `List prepareEventSources(EventSourceContext

context)`. + !!! IMPORTANT !!! + If you use dynamic registration of event sources, be sure to name your event sources explicitly as letting JOSDK name + them automatically might result in duplicated event sources being registered as JOSDK relies on the name to identify + event sources and concurrent, dynamic registration might lead to identical event sources having different generated + names, thus leading JOSDK to consider them as different and hence, register them multiple times. +5. Updates through `UpdateControl` now + use [Server Side Apply (SSA)](https://kubernetes.io/docs/reference/using-api/server-side-apply/) by default to add + the finalizer and for all + the patch operations in `UpdateControl`. The update operations were removed. If you do not wish to use SSA, you can + deactivate the feature using `ConfigurationService.useSSAToPatchPrimaryResource` and + related `ConfigurationServiceOverrider.withUseSSAToPatchPrimaryResource`. + + !!! IMPORTANT !!! + + See known issues with migration from non-SSA to SSA based status updates here: + [integration test](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/StatusPatchSSAMigrationIT.java#L71-L82) + where it is demonstrated. Also, the related part of + a [workaround](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/StatusPatchSSAMigrationIT.java#L110-L116). + +6. `ManagedDependentResourceContext` has been renamed to `ManagedWorkflowAndDependentResourceContext` and is accessed + via the accordingly renamed `managedWorkflowAndDependentResourceContext` method. +7. `ResourceDiscriminator` was removed. In most of the cases you can just delete the discriminator, everything should + work without it by default. To optimize and handle special cases see the relevant section + in [Dependent Resource documentation](/docs/dependent-resources#multiple-dependent-resources-of-same-type). +8. `ConfigurationService.getTerminationTimeoutSeconds` and associated overriding mechanism have been removed, + use `Operator.stop(Duration)` instead. +9. `Operator.installShutdownHook()` has been removed, use `Operator.installShutdownHook(Duration)` instead +10. Automated observed generation handling feature was removed (`ObservedGenerationAware` interface + and `ObservedGenerationAwareStatus` class were deleted). Manually handling observed generation is fairly easy to do + in your reconciler, however, it cannot be done automatically when using SSA. We therefore removed the feature since + it would have been confusing to have a different behavior for SSA and non-SSA cases. For an example of how to do + observed generation handling manually in your reconciler, see + [this sample](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/manualobservedgeneration/ManualObservedGenerationReconciler.java). +11. `BulkDependentResource` now supports [read-only mode](https://github.com/operator-framework/java-operator-sdk/issues/2233). + This also means, that `BulkDependentResource` now does not automatically implement `Creator` and `Deleter` as before. + Make sure to implement those interfaces in your bulk dependent resources. You can use also the new helper interface, the + [`CRUDBulkDependentResource`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/CRUDBulkDependentResource.java) + what also implement `BulkUpdater` interface. \ No newline at end of file diff --git a/docsy/content/en/docs/workflows/_index.md b/docsy/content/en/docs/workflows/_index.md index 4eba1f5d07..fa230665ba 100644 --- a/docsy/content/en/docs/workflows/_index.md +++ b/docsy/content/en/docs/workflows/_index.md @@ -41,10 +41,10 @@ reconciliation process. condition holds or not. This is a very useful feature when your operator needs to handle different flavors of the platform (e.g. OpenShift vs plain Kubernetes) and/or change its behavior based on the availability of optional resources / features (e.g. CertManager, a specific Ingress controller, etc.). - - Activation condition is semi-experimental at the moment, and it has its limitations. - For example event sources cannot be shared between multiple managed dependent resources which use activation condition. - The intention is to further improve and explore the possibilities with this approach. + + Activation condition is semi-experimental at the moment, and it has its limitations. + For example event sources cannot be shared between multiple managed dependent resources which use activation condition. + The intention is to further improve and explore the possibilities with this approach. ## Defining Workflows @@ -70,15 +70,16 @@ will only consider the `ConfigMap` deleted until that post-condition becomes `tr ```java -@ControllerConfiguration(dependents = { - @Dependent(name = DEPLOYMENT_NAME, type = DeploymentDependentResource.class, - readyPostcondition = DeploymentReadyCondition.class), - @Dependent(type = ConfigMapDependentResource.class, - reconcilePrecondition = ConfigMapReconcileCondition.class, - deletePostcondition = ConfigMapDeletePostCondition.class, - activationCondition = ConfigMapActivationCondition.class, - dependsOn = DEPLOYMENT_NAME) +@Workflow(dependents = { + @Dependent(name = DEPLOYMENT_NAME, type = DeploymentDependentResource.class, + readyPostcondition = DeploymentReadyCondition.class), + @Dependent(type = ConfigMapDependentResource.class, + reconcilePrecondition = ConfigMapReconcileCondition.class, + deletePostcondition = ConfigMapDeletePostCondition.class, + activationCondition = ConfigMapActivationCondition.class, + dependsOn = DEPLOYMENT_NAME) }) +@ControllerConfiguration public class SampleWorkflowReconciler implements Reconciler, Cleaner { @@ -120,7 +121,7 @@ page sample): @ControllerConfiguration( labelSelector = WebPageDependentsWorkflowReconciler.DEPENDENT_RESOURCE_LABEL_SELECTOR) public class WebPageDependentsWorkflowReconciler - implements Reconciler, ErrorStatusHandler, EventSourceInitializer { + implements Reconciler, ErrorStatusHandler { public static final String DEPENDENT_RESOURCE_LABEL_SELECTOR = "!low-level"; private static final Logger log = @@ -131,7 +132,7 @@ public class WebPageDependentsWorkflowReconciler private KubernetesDependentResource serviceDR; private KubernetesDependentResource ingressDR; - private Workflow workflow; + private final Workflow workflow; public WebPageDependentsWorkflowReconciler(KubernetesClient kubernetesClient) { initDependentResources(kubernetesClient); @@ -145,7 +146,7 @@ public class WebPageDependentsWorkflowReconciler @Override public Map prepareEventSources(EventSourceContext context) { - return EventSourceInitializer.nameEventSources( + return EventSourceUtils.nameEventSources( configMapDR.initEventSource(context), deploymentDR.initEventSource(context), serviceDR.initEventSource(context), @@ -198,7 +199,7 @@ demonstrated using examples: 2. Root nodes, i.e. nodes in the graph that do not depend on other nodes are reconciled first, in a parallel manner. 3. A DR is reconciled if it does not depend on any other DRs, or *ALL* the DRs it depends on are - reconciled and ready. If a DR defines a reconcile pre-condition and/or an activation condition, + reconciled and ready. If a DR defines a reconcile pre-condition and/or an activation condition, then these condition must become `true` before the DR is reconciled. 4. A DR is considered *ready* if it got successfully reconciled and any ready post-condition it might define is `true`. @@ -331,10 +332,38 @@ provides such a delete post-condition implementation in the form of Also, check usage in an [integration test](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/manageddependentdeletecondition/ManagedDependentDefaultDeleteConditionReconciler.java). -In such cases the Kubernetes Dependent Resource should extend `CRUDNoGCKubernetesDependentResource` +In such cases the Kubernetes Dependent Resource should extend `CRUDNoGCKubernetesDependentResource` and NOT `CRUDKubernetesDependentResource` since otherwise the Kubernetes Garbage Collector would delete the resources. In other words if a Kubernetes Dependent Resource depends on another dependent resource, it should not implement -`GargageCollected` interface, otherwise the deletion order won't be guaranteed. +`GargageCollected` interface, otherwise the deletion order won't be guaranteed. + + +## Explicit Managed Workflow Invocation + +Managed workflows, i.e. ones that are declared via annotations and therefore completely managed by JOSDK, are reconciled +before the primary resource. Each dependent resource that can be reconciled (according to the workflow configuration) +will therefore be reconciled before the primary reconciler is called to reconcile the primary resource. There are, +however, situations where it would be be useful to perform additional steps before the workflow is reconciled, for +example to validate the current state, execute arbitrary logic or even skip reconciliation altogether. Explicit +invocation of managed workflow was therefore introduced to solve these issues. + +To use this feature, you need to set the `explicitInvocation` field to `true` on the `@Workflow` annotation and then +call the `reconcileManagedWorkflow` method from the ` +ManagedWorkflowAndDependentResourceContext` retrieved from the reconciliation `Context` provided as part of your primary +resource reconciler `reconcile` method arguments. + +See +related [integration test](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowExplicitInvocationIT.java) +for more details. + +For `cleanup`, if the `Cleaner` interface is implemented, the `cleanupManageWorkflow()` needs to be called explicitly. +However, if `Cleaner` interface is not implemented, it will be called implicitly. +See +related [integration test](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowExplicitCleanupIT.java). + +While nothing prevents calling the workflow multiple times in a reconciler, it isn't typical or even recommended to do +so. Conversely, if explicit invocation is requested but `reconcileManagedWorkflow` is not called in the primary resource +reconciler, the workflow won't be reconciled at all. ## Notes and Caveats diff --git a/micrometer-support/pom.xml b/micrometer-support/pom.xml index e5666469ff..a24acef29e 100644 --- a/micrometer-support/pom.xml +++ b/micrometer-support/pom.xml @@ -1,22 +1,15 @@ - + + 4.0.0 - java-operator-sdk io.javaoperatorsdk - 4.9.1-SNAPSHOT + java-operator-sdk + 5.0.0-SNAPSHOT - 4.0.0 micrometer-support Operator SDK - Micrometer Support - - 11 - 11 - - io.micrometer @@ -42,9 +35,9 @@ test - org.awaitility - awaitility - test + org.awaitility + awaitility + test io.javaoperatorsdk @@ -59,4 +52,4 @@ - \ No newline at end of file + diff --git a/micrometer-support/src/main/java/io/javaoperatorsdk/operator/monitoring/micrometer/MicrometerMetrics.java b/micrometer-support/src/main/java/io/javaoperatorsdk/operator/monitoring/micrometer/MicrometerMetrics.java index b819bd0ca3..07106d9b3c 100644 --- a/micrometer-support/src/main/java/io/javaoperatorsdk/operator/monitoring/micrometer/MicrometerMetrics.java +++ b/micrometer-support/src/main/java/io/javaoperatorsdk/operator/monitoring/micrometer/MicrometerMetrics.java @@ -59,20 +59,6 @@ public class MicrometerMetrics implements Metrics { private final Map gauges = new ConcurrentHashMap<>(); private final Cleaner cleaner; - /** - * Creates a default micrometer-based Metrics implementation, collecting metrics on a per resource - * basis and not dealing with cleaning these after these resources are deleted. Note that this - * probably will change in a future release. If you want more control over what the implementation - * actually does, please use the static factory methods instead. - * - * @param registry the {@link MeterRegistry} instance to use for metrics recording - * @deprecated Use the factory methods / builders instead - */ - @Deprecated - public MicrometerMetrics(MeterRegistry registry) { - this(registry, Cleaner.NOOP, true); - } - /** * Creates a MicrometerMetrics instance configured to not collect per-resource metrics, just * aggregates per resource **type** diff --git a/operator-framework-bom/pom.xml b/operator-framework-bom/pom.xml index 73b941f621..2f4709b3c2 100644 --- a/operator-framework-bom/pom.xml +++ b/operator-framework-bom/pom.xml @@ -1,142 +1,170 @@ - - 4.0.0 + + 4.0.0 - io.javaoperatorsdk - operator-framework-bom - 4.9.1-SNAPSHOT - Operator SDK - Bill of Materials - pom - Java SDK for implementing Kubernetes operators - https://github.com/operator-framework/java-operator-sdk + io.javaoperatorsdk + operator-framework-bom + 5.0.0-SNAPSHOT + pom + Operator SDK - Bill of Materials + Java SDK for implementing Kubernetes operators + https://github.com/operator-framework/java-operator-sdk - - - Apache 2 License - https://www.apache.org/licenses/LICENSE-2.0.html - - - - - Attila Meszaros - csviri@gmail.com - - - Christophe Laprun - claprun@redhat.com - - + + + Apache 2 License + https://www.apache.org/licenses/LICENSE-2.0.html + + + + + Attila Meszaros + csviri@gmail.com + + + Christophe Laprun + claprun@redhat.com + + - - scm:git:git://github.com/java-operator-sdk/java-operator-sdk.git - scm:git:git@github.com/java-operator-sdk/java-operator-sdk.git - https://github.com/operator-framework/java-operator-sdk/tree/master - + + scm:git:git://github.com/java-operator-sdk/java-operator-sdk.git + scm:git:git@github.com/java-operator-sdk/java-operator-sdk.git + https://github.com/operator-framework/java-operator-sdk/tree/master + - - - - io.javaoperatorsdk - operator-framework-core - ${project.version} - - - io.javaoperatorsdk - operator-framework - ${project.version} - - - io.javaoperatorsdk - micrometer-support - ${project.version} - - - io.javaoperatorsdk - operator-framework-junit-5 - ${project.version} - - - + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + - - 1.6.13 - 3.2.4 - 3.3.1 - 3.6.3 - + + 1.6.13 + 3.2.4 + 3.3.1 + 3.6.3 + 2.43.0 + - - - release - - - - org.apache.maven.plugins - maven-javadoc-plugin - ${maven-javadoc-plugin.version} - - - attach-javadocs - - jar - - - - - - org.apache.maven.plugins - maven-source-plugin - ${maven-source-plugin.version} - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-gpg-plugin - ${maven-gpg-plugin.version} - - - sign-artifacts - verify - - sign - - - - --pinentry-mode - loopback - - - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - ${nexus-staging-maven-plugin.version} - true - - ossrh - https://oss.sonatype.org/ - true - - - - - - + + + + io.javaoperatorsdk + operator-framework-core + ${project.version} + + + io.javaoperatorsdk + operator-framework + ${project.version} + + + io.javaoperatorsdk + micrometer-support + ${project.version} + + + io.javaoperatorsdk + operator-framework-junit-5 + ${project.version} + + + - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - + + + + com.diffplug.spotless + spotless-maven-plugin + ${spotless.version} + + + + pom.xml + ./**/pom.xml + + + + + + contributing/eclipse-google-style.xml + + + contributing/eclipse.importorder + + + + + + + + + + + release + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-source-plugin + ${maven-source-plugin.version} + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + ${maven-gpg-plugin.version} + + + sign-artifacts + + sign + + verify + + + --pinentry-mode + loopback + + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + ${nexus-staging-maven-plugin.version} + true + + ossrh + https://oss.sonatype.org/ + true + + + + + + diff --git a/operator-framework-core/pom.xml b/operator-framework-core/pom.xml index a8cb6beac0..0d3b6ff15a 100644 --- a/operator-framework-core/pom.xml +++ b/operator-framework-core/pom.xml @@ -1,68 +1,17 @@ - + 4.0.0 io.javaoperatorsdk java-operator-sdk - 4.9.1-SNAPSHOT + 5.0.0-SNAPSHOT ../pom.xml operator-framework-core + jar Operator SDK - Framework - Core Core framework for implementing Kubernetes operators - jar - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - - io.github.git-commit-id - git-commit-id-maven-plugin - ${git-commit-id-maven-plugin.version} - - - get-the-git-infos - - revision - - initialize - - - - true - ${project.build.outputDirectory}/version.properties - - - ^git.build.time$ - ^git.commit.id.(abbrev|full)$ - git.branch - - full - - - - org.codehaus.mojo - templating-maven-plugin - 3.0.0 - - - filtering-java-templates - - filter-sources - - - - - - - @@ -122,4 +71,51 @@ test + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + io.github.git-commit-id + git-commit-id-maven-plugin + ${git-commit-id-maven-plugin.version} + + true + ${project.build.outputDirectory}/version.properties + + ^git.build.time$ + ^git.commit.id.(abbrev|full)$ + git.branch + + full + + + + get-the-git-infos + + revision + + initialize + + + + + org.codehaus.mojo + templating-maven-plugin + 3.0.0 + + + filtering-java-templates + + filter-sources + + + + + + diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/BuilderUtils.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/BuilderUtils.java index 1cb46bafab..8f33036b74 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/BuilderUtils.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/BuilderUtils.java @@ -10,7 +10,7 @@ public final class BuilderUtils { // prevent instantiation of util class private BuilderUtils() {} - public static final B newBuilder(Class builderType, T item) { + public static B newBuilder(Class builderType, T item) { Class builderTargetType = builderTargetType(builderType); try { Constructor constructor = builderType.getDeclaredConstructor(builderTargetType); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ControllerManager.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ControllerManager.java index e90070d3c2..a755e4db7e 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ControllerManager.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ControllerManager.java @@ -89,4 +89,3 @@ synchronized int size() { return controllers.size(); } } - diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java index 3fc01e1f64..d85de6b1e5 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java @@ -35,7 +35,7 @@ public Operator() { } Operator(KubernetesClient kubernetesClient) { - this(kubernetesClient, null); + this(initConfigurationService(kubernetesClient, null)); } /** @@ -63,19 +63,7 @@ public Operator(ConfigurationService configurationService) { * {@link ConfigurationService} values */ public Operator(Consumer overrider) { - this(null, overrider); - } - - /** - * @param client client to use to all Kubernetes related operations - * @param overrider a {@link ConfigurationServiceOverrider} consumer used to override the default - * {@link ConfigurationService} values - * @deprecated Use {@link Operator#Operator(Consumer)} instead, passing your custom client with - * {@link ConfigurationServiceOverrider#withKubernetesClient(KubernetesClient)} - */ - @Deprecated(since = "4.4.0") - public Operator(KubernetesClient client, Consumer overrider) { - this(initConfigurationService(client, overrider)); + this(initConfigurationService(null, overrider)); } private static ConfigurationService initConfigurationService(KubernetesClient client, @@ -98,17 +86,6 @@ private static ConfigurationService initConfigurationService(KubernetesClient cl return ConfigurationService.newOverriddenConfigurationService(overrider); } - /** - * Uses {@link ConfigurationService#getTerminationTimeoutSeconds()} for graceful shutdown timeout - * - * @deprecated use the overloaded version with graceful shutdown timeout parameter. - * - */ - @Deprecated(forRemoval = true) - public void installShutdownHook() { - installShutdownHook(Duration.ofSeconds(configurationService.getTerminationTimeoutSeconds())); - } - /** * Adds a shutdown hook that automatically calls {@link #stop()} when the app shuts down. Note * that graceful shutdown is usually not needed, but some {@link Reconciler} implementations might @@ -120,6 +97,7 @@ public void installShutdownHook() { * @param gracefulShutdownTimeout timeout to wait for executor threads to complete actual * reconciliations */ + @SuppressWarnings("unused") public void installShutdownHook(Duration gracefulShutdownTimeout) { if (!leaderElectionManager.isLeaderElectionEnabled()) { Runtime.getRuntime().addShutdownHook(new Thread(() -> stop(gracefulShutdownTimeout))); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtils.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtils.java index fec5c4e61e..c2241e4bbb 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtils.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtils.java @@ -9,7 +9,6 @@ import java.util.Objects; import java.util.function.Predicate; import java.util.regex.Pattern; -import java.util.stream.Collectors; import io.fabric8.kubernetes.api.builder.Builder; import io.fabric8.kubernetes.api.model.GenericKubernetesResource; @@ -126,8 +125,7 @@ public static boolean specsEqual(HasMetadata r1, HasMetadata r2) { // will be replaced with: https://github.com/fabric8io/kubernetes-client/issues/3816 public static Object getSpec(HasMetadata resource) { // optimize CustomResource case - if (resource instanceof CustomResource) { - CustomResource cr = (CustomResource) resource; + if (resource instanceof CustomResource cr) { return cr.getSpec(); } @@ -142,8 +140,7 @@ public static Object getSpec(HasMetadata resource) { @SuppressWarnings("unchecked") public static Object setSpec(HasMetadata resource, Object spec) { // optimize CustomResource case - if (resource instanceof CustomResource) { - CustomResource cr = (CustomResource) resource; + if (resource instanceof CustomResource cr) { cr.setSpec(spec); return null; } @@ -191,8 +188,7 @@ public static void handleKubernetesClientException(Exception e, String resourceT throw ((MissingCRDException) e); } - if (e instanceof KubernetesClientException) { - KubernetesClientException ke = (KubernetesClientException) e; + if (e instanceof KubernetesClientException ke) { // only throw MissingCRDException if the 404 error occurs on the target CRD if (404 == ke.getCode() && (resourceTypeName.equals(ke.getFullResourceName()) @@ -217,7 +213,7 @@ private static boolean matchesResourceType(String resourceTypeName, group = group.substring(0, group.length() - 1); } final var segments = Arrays.stream(group.split("/")).filter(Predicate.not(String::isEmpty)) - .collect(Collectors.toUnmodifiableList()); + .toList(); if (segments.size() != 3) { return false; } 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 961e519d62..ee2f4d447e 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 @@ -7,6 +7,7 @@ import io.javaoperatorsdk.operator.health.EventSourceHealthIndicator; import io.javaoperatorsdk.operator.health.InformerWrappingEventSourceHealthIndicator; +import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerEventSource; /** * RuntimeInfo in general is available when operator is fully started. You can use "isStarted" to @@ -64,9 +65,7 @@ public Map> unhealthyEventSource /** * @return Aggregated Map with controller related event sources that wraps an informer. Thus, - * either a - * {@link io.javaoperatorsdk.operator.processing.event.source.controller.ControllerResourceEventSource} - * or an + * either a {@link ControllerEventSource} or an * {@link io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource}. */ public Map> unhealthyInformerWrappingEventSourceHealthIndicator() { diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/ObservedGenerationAware.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/ObservedGenerationAware.java deleted file mode 100644 index 069953a32d..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/ObservedGenerationAware.java +++ /dev/null @@ -1,27 +0,0 @@ -package io.javaoperatorsdk.operator.api; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.fabric8.kubernetes.client.CustomResource; -import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; - -/** - * If the custom resource's status implements this interface, the observed generation will be - * automatically handled. The last observed generation will be updated on status. - *

- * In order for this automatic handling to work the status object returned by - * {@link CustomResource#getStatus()} should not be null. - *

- * The observed generation is updated even when {@link UpdateControl#noUpdate()} or - * {@link UpdateControl#updateResource(HasMetadata)} is called. Although those results call normally - * does not result in a status update, there will be a subsequent status update Kubernetes API call - * in this case. - * - * @see ObservedGenerationAwareStatus - */ -public interface ObservedGenerationAware { - - void setObservedGeneration(Long generation); - - Long getObservedGeneration(); - -} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/ObservedGenerationAwareStatus.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/ObservedGenerationAwareStatus.java deleted file mode 100644 index d2048c9513..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/ObservedGenerationAwareStatus.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.javaoperatorsdk.operator.api; - -/** - * A helper base class for status sub-resources classes to extend to support generate awareness. - */ -public class ObservedGenerationAwareStatus implements ObservedGenerationAware { - - private Long observedGeneration; - - @Override - public void setObservedGeneration(Long generation) { - this.observedGeneration = generation; - } - - @Override - public Long getObservedGeneration() { - return observedGeneration; - } -} 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 d908f52ebe..b6adfd727f 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 @@ -7,7 +7,6 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.function.Function; -import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -19,14 +18,14 @@ import io.javaoperatorsdk.operator.ReconcilerUtils; import io.javaoperatorsdk.operator.api.config.Utils.Configurator; import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec; +import io.javaoperatorsdk.operator.api.config.workflow.WorkflowSpec; import io.javaoperatorsdk.operator.api.reconciler.Constants; import io.javaoperatorsdk.operator.api.reconciler.Reconciler; +import io.javaoperatorsdk.operator.api.reconciler.Workflow; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition; import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter; -import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter; -import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilters; import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter; @@ -99,6 +98,7 @@ public ControllerConfiguration getConfigurationFor( protected

ControllerConfiguration

configFor(Reconciler

reconciler) { final var annotation = reconciler.getClass().getAnnotation( io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration.class); + if (annotation == null) { throw new OperatorException( "Missing mandatory @" @@ -163,49 +163,42 @@ protected

ControllerConfiguration

configFor(Reconcile Utils.instantiate(annotation.itemStore(), ItemStore.class, context), dependentFieldManager, this, informerListLimit); - ResourceEventFilter

answer = deprecatedEventFilter(annotation); - config.setEventFilter(answer != null ? answer : ResourceEventFilters.passthrough()); - List specs = dependentResources(annotation, config); - config.setDependentResources(specs); + final var workflowAnnotation = reconciler.getClass().getAnnotation( + io.javaoperatorsdk.operator.api.reconciler.Workflow.class); + if (workflowAnnotation != null) { + final var specs = dependentResources(workflowAnnotation, config); + WorkflowSpec workflowSpec = new WorkflowSpec() { + @Override + public List getDependentResourceSpecs() { + return specs; + } - return config; - } + @Override + public boolean isExplicitInvocation() { + return workflowAnnotation.explicitInvocation(); + } - @SuppressWarnings("unchecked") - private static

ResourceEventFilter

deprecatedEventFilter( - io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration annotation) { - ResourceEventFilter

answer = null; - - Class>[] filterTypes = - (Class>[]) valueOrDefault(annotation, - io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::eventFilters, - new Object[] {}); - for (var filterType : filterTypes) { - try { - ResourceEventFilter

filter = filterType.getConstructor().newInstance(); - - if (answer == null) { - answer = filter; - } else { - answer = answer.and(filter); + @Override + public boolean handleExceptionsInReconciler() { + return workflowAnnotation.handleExceptionsInReconciler(); } - } catch (Exception e) { - throw new IllegalArgumentException(e); - } + + }; + config.setWorkflowSpec(workflowSpec); } - return answer; + + return config; } @SuppressWarnings({"unchecked", "rawtypes"}) private static List dependentResources( - io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration annotation, + Workflow annotation, ControllerConfiguration parent) { - final var dependents = - valueOrDefault(annotation, - io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::dependents, - new Dependent[] {}); - if (dependents.length == 0) { + final var dependents = annotation.dependents(); + + + if (dependents == null || dependents.length == 0) { return Collections.emptyList(); } @@ -231,10 +224,11 @@ private static List dependentResources( Utils.instantiate(dependent.reconcilePrecondition(), Condition.class, context), Utils.instantiate(dependent.deletePostcondition(), Condition.class, context), Utils.instantiate(dependent.activationCondition(), Condition.class, context), - eventSourceName); + eventSourceName, dependent.optional()); + specsMap.put(dependentName, spec); } - return specsMap.values().stream().collect(Collectors.toUnmodifiableList()); + return specsMap.values().stream().toList(); } protected boolean createIfNeeded() { @@ -273,8 +267,7 @@ private static Configurator configuratorFor(Class instanceType, @SuppressWarnings({"unchecked", "rawtypes"}) private static void configureFromAnnotatedReconciler(Object instance, Reconciler reconciler) { - if (instance instanceof AnnotationConfigurable) { - AnnotationConfigurable configurable = (AnnotationConfigurable) instance; + if (instance instanceof AnnotationConfigurable configurable) { final Class configurationClass = (Class) Utils.getFirstTypeArgumentFromSuperClassOrInterface( instance.getClass(), AnnotationConfigurable.class); 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 92859421eb..fae955db17 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 @@ -20,10 +20,12 @@ import io.fabric8.kubernetes.client.KubernetesClientBuilder; import io.fabric8.kubernetes.client.utils.KubernetesSerialization; import io.javaoperatorsdk.operator.api.monitoring.Metrics; +import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.Reconciler; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResourceFactory; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; import io.javaoperatorsdk.operator.processing.dependent.workflow.ManagedWorkflowFactory; +import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerEventSource; /** An interface from which to retrieve configuration information. */ public interface ConfigurationService { @@ -127,11 +129,6 @@ default boolean checkCRDAndValidateLocalModel() { } int DEFAULT_RECONCILIATION_THREADS_NUMBER = 50; - /** - * @deprecated Not used anymore in the default implementation - */ - @Deprecated(forRemoval = true) - int MIN_DEFAULT_RECONCILIATION_THREADS_NUMBER = 10; /** * The number of threads the operator can spin out to dispatch reconciliation requests to @@ -143,23 +140,7 @@ default int concurrentReconciliationThreads() { return DEFAULT_RECONCILIATION_THREADS_NUMBER; } - /** - * The minimum number of threads the operator starts in the thread pool for reconciliations. - * - * @deprecated not used anymore by default executor implementation - * @return the minimum number of concurrent reconciliation threads - */ - @Deprecated(forRemoval = true) - default int minConcurrentReconciliationThreads() { - return MIN_DEFAULT_RECONCILIATION_THREADS_NUMBER; - } - int DEFAULT_WORKFLOW_EXECUTOR_THREAD_NUMBER = DEFAULT_RECONCILIATION_THREADS_NUMBER; - /** - * @deprecated Not used anymore in the default implementation - */ - @Deprecated(forRemoval = true) - int MIN_DEFAULT_WORKFLOW_EXECUTOR_THREAD_NUMBER = MIN_DEFAULT_RECONCILIATION_THREADS_NUMBER; /** * Number of threads the operator can spin out to be used in the workflows with the default @@ -171,33 +152,6 @@ default int concurrentWorkflowExecutorThreads() { return DEFAULT_WORKFLOW_EXECUTOR_THREAD_NUMBER; } - /** - * The minimum number of threads the operator starts in the thread pool for workflows. - * - * @deprecated not used anymore by default executor implementation - * @return the minimum number of concurrent workflow threads - */ - @Deprecated(forRemoval = true) - default int minConcurrentWorkflowExecutorThreads() { - return MIN_DEFAULT_WORKFLOW_EXECUTOR_THREAD_NUMBER; - } - - int DEFAULT_TERMINATION_TIMEOUT_SECONDS = 10; - - /** - * Retrieves the number of seconds the SDK waits for reconciliation threads to terminate before - * shutting down. - * - * @deprecated use {@link io.javaoperatorsdk.operator.Operator#stop(Duration)} instead. Where the - * parameter can be passed to specify graceful timeout. - * - * @return the number of seconds to wait before terminating reconciliation threads - */ - @Deprecated(forRemoval = true) - default int getTerminationTimeoutSeconds() { - return DEFAULT_TERMINATION_TIMEOUT_SECONDS; - } - default Metrics getMetrics() { return Metrics.NOOP; } @@ -227,8 +181,7 @@ default Optional getLeaderElectionConfiguration() { *

* if true, operator stops if there are some issues with informers * {@link io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource} or - * {@link io.javaoperatorsdk.operator.processing.event.source.controller.ControllerResourceEventSource} - * on startup. Other event sources may also respect this flag. + * {@link ControllerEventSource} on startup. Other event sources may also respect this flag. *

*

* if false, the startup will ignore recoverable errors, caused for example by RBAC issues, and @@ -353,6 +306,8 @@ default ExecutorServiceManager getExecutorServiceManager() { * method of Kubernetes Dependent Resource. * * @since 4.4.0 + * + * @return if SSA should be used for dependent resources */ default boolean ssaBasedCreateUpdateMatchForDependentResources() { return true; @@ -381,6 +336,8 @@ default Set> defaultNonSSAResource() { * Disable this if you want to react to your own dependent resource updates * * @since 4.5.0 + * + * @return if special annotation should be used for dependent resource to filter events */ default boolean previousAnnotationForDependentResourcesEventFiltering() { return true; @@ -392,13 +349,52 @@ default boolean previousAnnotationForDependentResourcesEventFiltering() { *

* Disabled by default as Kubernetes does not support, and discourages, this interpretation of * resourceVersions. Enable only if your api server event processing seems to lag the operator - * logic and you want to further minimize the the amount of work done / updates issued by the + * logic, and you want to further minimize the amount of work done / updates issued by the * operator. * * @since 4.5.0 + * + * @return if resource version should be parsed (as integer) */ default boolean parseResourceVersionsForEventFilteringAndCaching() { return false; } + /** + * {@link io.javaoperatorsdk.operator.api.reconciler.UpdateControl} patch resource or status can + * either use simple patches or SSA. Setting this to {@code true}, controllers will use SSA for + * adding finalizers, patching resources and status. + * + * @return {@code true} if Server-Side Apply (SSA) should be used when patching the primary + * resources, {@code false} otherwise + * @since 5.0.0 + * @see ConfigurationServiceOverrider#withUseSSAToPatchPrimaryResource(boolean) + */ + default boolean useSSAToPatchPrimaryResource() { + return true; + } + + /** + *

+ * Determines whether resources retrieved from caches such as via calls to + * {@link Context#getSecondaryResource(Class)} should be defensively cloned first. + *

+ * + *

+ * Defensive cloning to prevent problematic cache modifications (modifying the resource would + * otherwise modify the stored copy in the cache) was transparently done in previous JOSDK + * versions. This might have performance consequences and, with the more prevalent use of + * Server-Side Apply, where you should create a new copy of your resource with only modified + * fields, such modifications of these resources are less likely to occur. + *

+ * + * @return {@code true} if resources should be defensively cloned before returning them from + * caches, {@code false} otherwise + * + * @since 5.0.0 + */ + default boolean cloneSecondaryResourcesWhenGettingFromCache() { + 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 12a8a5c699..f073b98b8a 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 @@ -4,7 +4,7 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.ExecutorService; -import java.util.function.Consumer; +import java.util.function.Function; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,11 +22,8 @@ public class ConfigurationServiceOverrider { private Metrics metrics; private Boolean checkCR; private Integer concurrentReconciliationThreads; - private Integer minConcurrentReconciliationThreads; private Integer concurrentWorkflowExecutorThreads; - private Integer minConcurrentWorkflowExecutorThreads; private Cloner cloner; - private Integer timeoutSeconds; private Boolean closeClientOnStop; private KubernetesClient client; private ExecutorService executorService; @@ -40,6 +37,9 @@ public class ConfigurationServiceOverrider { private Set> defaultNonSSAResource; private Boolean previousAnnotationForDependentResources; private Boolean parseResourceVersions; + private Boolean useSSAToPatchPrimaryResource; + private Boolean cloneSecondaryResourcesWhenGettingFromCache; + @SuppressWarnings("rawtypes") private DependentResourceFactory dependentResourceFactory; ConfigurationServiceOverrider(ConfigurationService original) { @@ -61,24 +61,7 @@ public ConfigurationServiceOverrider withConcurrentWorkflowExecutorThreads(int t return this; } - private int minimumMaxValueFor(Integer minValue) { - return minValue != null ? (minValue < 0 ? 0 : minValue) + 1 : 1; - } - - public ConfigurationServiceOverrider withMinConcurrentReconciliationThreads(int threadNumber) { - this.minConcurrentReconciliationThreads = Utils.ensureValid(threadNumber, - "minimum reconciliation threads", ExecutorServiceManager.MIN_THREAD_NUMBER, - original.minConcurrentReconciliationThreads()); - return this; - } - - public ConfigurationServiceOverrider withMinConcurrentWorkflowExecutorThreads(int threadNumber) { - this.minConcurrentWorkflowExecutorThreads = Utils.ensureValid(threadNumber, - "minimum workflow execution threads", ExecutorServiceManager.MIN_THREAD_NUMBER, - original.minConcurrentWorkflowExecutorThreads()); - return this; - } - + @SuppressWarnings("rawtypes") public ConfigurationServiceOverrider withDependentResourceFactory( DependentResourceFactory dependentResourceFactory) { this.dependentResourceFactory = dependentResourceFactory; @@ -90,11 +73,6 @@ public ConfigurationServiceOverrider withResourceCloner(Cloner cloner) { return this; } - public ConfigurationServiceOverrider withTerminationTimeoutSeconds(int timeoutSeconds) { - this.timeoutSeconds = timeoutSeconds; - return this; - } - public ConfigurationServiceOverrider withMetrics(Metrics metrics) { this.metrics = metrics; return this; @@ -174,12 +152,23 @@ public ConfigurationServiceOverrider withPreviousAnnotationForDependentResources return this; } - public ConfigurationServiceOverrider wihtParseResourceVersions( + public ConfigurationServiceOverrider withParseResourceVersions( boolean value) { this.parseResourceVersions = value; return this; } + public ConfigurationServiceOverrider withUseSSAToPatchPrimaryResource(boolean value) { + this.useSSAToPatchPrimaryResource = value; + return this; + } + + public ConfigurationServiceOverrider withCloneSecondaryResourcesWhenGettingFromCache( + boolean value) { + this.cloneSecondaryResourcesWhenGettingFromCache = value; + return this; + } + public ConfigurationService build() { return new BaseConfigurationService(original.getVersion(), cloner, client) { @Override @@ -187,82 +176,63 @@ public Set getKnownReconcilerNames() { return original.getKnownReconcilerNames(); } + private T overriddenValueOrDefault(T value, + Function defaultValue) { + return value != null ? value : defaultValue.apply(original); + } + @Override public boolean checkCRDAndValidateLocalModel() { - return checkCR != null ? checkCR : original.checkCRDAndValidateLocalModel(); + return overriddenValueOrDefault(checkCR, + ConfigurationService::checkCRDAndValidateLocalModel); } @Override + @SuppressWarnings("rawtypes") public DependentResourceFactory dependentResourceFactory() { - return dependentResourceFactory != null ? dependentResourceFactory - : DependentResourceFactory.DEFAULT; + return overriddenValueOrDefault(dependentResourceFactory, + ConfigurationService::dependentResourceFactory); } @Override public int concurrentReconciliationThreads() { return Utils.ensureValid( - concurrentReconciliationThreads != null ? concurrentReconciliationThreads - : original.concurrentReconciliationThreads(), + overriddenValueOrDefault(concurrentReconciliationThreads, + ConfigurationService::concurrentReconciliationThreads), "maximum reconciliation threads", - minimumMaxValueFor(minConcurrentReconciliationThreads), + 1, original.concurrentReconciliationThreads()); } @Override public int concurrentWorkflowExecutorThreads() { return Utils.ensureValid( - concurrentWorkflowExecutorThreads != null ? concurrentWorkflowExecutorThreads - : original.concurrentWorkflowExecutorThreads(), + overriddenValueOrDefault(concurrentWorkflowExecutorThreads, + ConfigurationService::concurrentWorkflowExecutorThreads), "maximum workflow execution threads", - minimumMaxValueFor(minConcurrentWorkflowExecutorThreads), + 1, original.concurrentWorkflowExecutorThreads()); } - /** - * @deprecated Not used anymore in the default implementation - */ - @Deprecated(forRemoval = true) - @Override - public int minConcurrentReconciliationThreads() { - return minConcurrentReconciliationThreads != null ? minConcurrentReconciliationThreads - : original.minConcurrentReconciliationThreads(); - } - - /** - * @deprecated Not used anymore in the default implementation - */ - @Override - @Deprecated(forRemoval = true) - public int minConcurrentWorkflowExecutorThreads() { - return minConcurrentWorkflowExecutorThreads != null ? minConcurrentWorkflowExecutorThreads - : original.minConcurrentWorkflowExecutorThreads(); - } - - @Override - public int getTerminationTimeoutSeconds() { - return timeoutSeconds != null ? timeoutSeconds : original.getTerminationTimeoutSeconds(); - } - @Override public Metrics getMetrics() { - return metrics != null ? metrics : original.getMetrics(); + return overriddenValueOrDefault(metrics, ConfigurationService::getMetrics); } @Override public boolean closeClientOnStop() { - return closeClientOnStop != null ? closeClientOnStop : original.closeClientOnStop(); + return overriddenValueOrDefault(closeClientOnStop, ConfigurationService::closeClientOnStop); } @Override public ExecutorService getExecutorService() { - return executorService != null ? executorService - : super.getExecutorService(); + return overriddenValueOrDefault(executorService, ConfigurationService::getExecutorService); } @Override public ExecutorService getWorkflowExecutorService() { - return workflowExecutorService != null ? workflowExecutorService - : super.getWorkflowExecutorService(); + return overriddenValueOrDefault(workflowExecutorService, + ConfigurationService::getWorkflowExecutorService); } @Override @@ -279,59 +249,57 @@ public Optional getInformerStoppedHandler() { @Override public boolean stopOnInformerErrorDuringStartup() { - return stopOnInformerErrorDuringStartup != null ? stopOnInformerErrorDuringStartup - : super.stopOnInformerErrorDuringStartup(); + return overriddenValueOrDefault(stopOnInformerErrorDuringStartup, + ConfigurationService::stopOnInformerErrorDuringStartup); } @Override public Duration cacheSyncTimeout() { - return cacheSyncTimeout != null ? cacheSyncTimeout : super.cacheSyncTimeout(); + return overriddenValueOrDefault(cacheSyncTimeout, ConfigurationService::cacheSyncTimeout); } @Override public ResourceClassResolver getResourceClassResolver() { - return resourceClassResolver != null ? resourceClassResolver - : super.getResourceClassResolver(); + return overriddenValueOrDefault(resourceClassResolver, + ConfigurationService::getResourceClassResolver); } @Override public boolean ssaBasedCreateUpdateMatchForDependentResources() { - return ssaBasedCreateUpdateMatchForDependentResources != null - ? ssaBasedCreateUpdateMatchForDependentResources - : super.ssaBasedCreateUpdateMatchForDependentResources(); + return overriddenValueOrDefault(ssaBasedCreateUpdateMatchForDependentResources, + ConfigurationService::ssaBasedCreateUpdateMatchForDependentResources); } @Override public Set> defaultNonSSAResource() { - return defaultNonSSAResource != null ? defaultNonSSAResource - : super.defaultNonSSAResource(); + return overriddenValueOrDefault(defaultNonSSAResource, + ConfigurationService::defaultNonSSAResource); } @Override public boolean previousAnnotationForDependentResourcesEventFiltering() { - return previousAnnotationForDependentResources != null - ? previousAnnotationForDependentResources - : super.previousAnnotationForDependentResourcesEventFiltering(); + return overriddenValueOrDefault(previousAnnotationForDependentResources, + ConfigurationService::previousAnnotationForDependentResourcesEventFiltering); } @Override public boolean parseResourceVersionsForEventFilteringAndCaching() { - return parseResourceVersions != null - ? parseResourceVersions - : super.parseResourceVersionsForEventFilteringAndCaching(); + return overriddenValueOrDefault(parseResourceVersions, + ConfigurationService::parseResourceVersionsForEventFilteringAndCaching); + } + + @Override + public boolean useSSAToPatchPrimaryResource() { + return overriddenValueOrDefault(useSSAToPatchPrimaryResource, + ConfigurationService::useSSAToPatchPrimaryResource); + } + + @Override + public boolean cloneSecondaryResourcesWhenGettingFromCache() { + return overriddenValueOrDefault(cloneSecondaryResourcesWhenGettingFromCache, + ConfigurationService::cloneSecondaryResourcesWhenGettingFromCache); } }; } - /** - * @deprecated Use - * {@link ConfigurationService#newOverriddenConfigurationService(ConfigurationService, Consumer)} - * instead - * @param original that will be overridden - * @return current overrider - */ - @Deprecated(since = "2.2.0") - public static ConfigurationServiceOverrider override(ConfigurationService original) { - return new ConfigurationServiceOverrider(original); - } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java index 13ddd995ad..2031283f37 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java @@ -1,21 +1,16 @@ package io.javaoperatorsdk.operator.api.config; import java.time.Duration; -import java.util.Collections; -import java.util.List; import java.util.Optional; import java.util.Set; import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.ReconcilerUtils; -import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec; +import io.javaoperatorsdk.operator.api.config.workflow.WorkflowSpec; import io.javaoperatorsdk.operator.api.reconciler.MaxReconciliationInterval; import io.javaoperatorsdk.operator.processing.event.rate.LinearRateLimiter; import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter; -import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter; -import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilters; import io.javaoperatorsdk.operator.processing.retry.GenericRetry; -import io.javaoperatorsdk.operator.processing.retry.GradualRetry; import io.javaoperatorsdk.operator.processing.retry.Retry; public interface ControllerConfiguration

extends ResourceConfiguration

{ @@ -60,22 +55,7 @@ default boolean isGenerationAware() { String getAssociatedReconcilerClassName(); default Retry getRetry() { - final var configuration = getRetryConfiguration(); - return !RetryConfiguration.DEFAULT.equals(configuration) - ? GenericRetry.fromConfiguration(configuration) - : GenericRetry.DEFAULT; // NOSONAR - } - - /** - * Use {@link #getRetry()} instead. - * - * @return configuration for retry. - * @deprecated provide your own {@link Retry} implementation or use the {@link GradualRetry} - * annotation instead - */ - @Deprecated(forRemoval = true) - default RetryConfiguration getRetryConfiguration() { - return RetryConfiguration.DEFAULT; + return GenericRetry.DEFAULT; } @SuppressWarnings("rawtypes") @@ -83,28 +63,8 @@ default RateLimiter getRateLimiter() { return DEFAULT_RATE_LIMITER; } - /** - * Allow controllers to filter events before they are passed to the - * {@link io.javaoperatorsdk.operator.processing.event.EventHandler}. - * - *

- * Resource event filters only applies on events of the main custom resource. Not on events from - * other event sources nor the periodic events. - *

- * - * @return filter - * @deprecated use {@link ResourceConfiguration#onAddFilter()}, - * {@link ResourceConfiguration#onUpdateFilter()} or - * {@link ResourceConfiguration#genericFilter()} instead - */ - @Deprecated(forRemoval = true) - default ResourceEventFilter

getEventFilter() { - return ResourceEventFilters.passthrough(); - } - - @SuppressWarnings("rawtypes") - default List getDependentResources() { - return Collections.emptyList(); + default Optional getWorkflowSpec() { + return Optional.empty(); } default Optional maxReconciliationInterval() { diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java index 328d912109..ad66b8b563 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java @@ -11,11 +11,9 @@ import io.fabric8.kubernetes.client.informers.cache.ItemStore; import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec; import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter; -import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter; -import io.javaoperatorsdk.operator.processing.retry.GenericRetry; import io.javaoperatorsdk.operator.processing.retry.Retry; import static io.javaoperatorsdk.operator.api.reconciler.Constants.DEFAULT_NAMESPACES_SET; @@ -29,7 +27,6 @@ public class ControllerConfigurationOverrider { private Set namespaces; private Retry retry; private String labelSelector; - private ResourceEventFilter customResourcePredicate; private final ControllerConfiguration original; private Duration reconciliationMaxInterval; private OnAddFilter onAddFilter; @@ -48,7 +45,6 @@ private ControllerConfigurationOverrider(ControllerConfiguration original) { this.namespaces = new HashSet<>(original.getNamespaces()); this.retry = original.getRetry(); this.labelSelector = original.getLabelSelector(); - this.customResourcePredicate = original.getEventFilter(); this.reconciliationMaxInterval = original.maxReconciliationInterval().orElse(null); this.onAddFilter = original.onAddFilter().orElse(null); this.onUpdateFilter = original.onUpdateFilter().orElse(null); @@ -110,17 +106,6 @@ public ControllerConfigurationOverrider watchingAllNamespaces() { return this; } - /** - * @param retry configuration - * @return current instance of overrider - * @deprecated Use {@link #withRetry(Retry)} instead - */ - @Deprecated(forRemoval = true) - public ControllerConfigurationOverrider withRetry(RetryConfiguration retry) { - this.retry = GenericRetry.fromConfiguration(retry); - return this; - } - public ControllerConfigurationOverrider withRetry(Retry retry) { this.retry = retry; return this; @@ -136,12 +121,6 @@ public ControllerConfigurationOverrider withLabelSelector(String labelSelecto return this; } - public ControllerConfigurationOverrider withCustomResourcePredicate( - ResourceEventFilter customResourcePredicate) { - this.customResourcePredicate = customResourcePredicate; - return this; - } - public ControllerConfigurationOverrider withReconciliationMaxInterval( Duration reconciliationMaxInterval) { this.reconciliationMaxInterval = reconciliationMaxInterval; @@ -196,7 +175,7 @@ public ControllerConfigurationOverrider withInformerListLimit( public ControllerConfigurationOverrider replacingNamedDependentResourceConfig(String name, Object dependentResourceConfig) { - final var specs = original.getDependentResources(); + final var specs = original.getWorkflowSpec().orElseThrow().getDependentResourceSpecs(); final var spec = specs.stream() .filter(drs -> drs.getName().equals(name)).findFirst() .orElseThrow( @@ -210,15 +189,13 @@ public ControllerConfigurationOverrider replacingNamedDependentResourceConfig } public ControllerConfiguration build() { - final var overridden = new ResolvedControllerConfiguration<>(original.getResourceClass(), + return new ResolvedControllerConfiguration<>(original.getResourceClass(), name, generationAware, original.getAssociatedReconcilerClassName(), retry, rateLimiter, reconciliationMaxInterval, onAddFilter, onUpdateFilter, genericFilter, - original.getDependentResources(), namespaces, finalizer, labelSelector, configurations, itemStore, fieldManager, - original.getConfigurationService(), informerListLimit); - overridden.setEventFilter(customResourcePredicate); - return overridden; + original.getConfigurationService(), informerListLimit, + original.getWorkflowSpec().orElse(null)); } public static ControllerConfigurationOverrider override( diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultResourceConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultResourceConfiguration.java index f8ee9f4e84..61ec044694 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultResourceConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultResourceConfiguration.java @@ -88,4 +88,5 @@ public Optional> getItemStore() { public Optional getInformerListLimit() { return Optional.ofNullable(informerListLimit); } + } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultRetryConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultRetryConfiguration.java deleted file mode 100644 index 40fbb38aa7..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultRetryConfiguration.java +++ /dev/null @@ -1,5 +0,0 @@ -package io.javaoperatorsdk.operator.api.config; - -public class DefaultRetryConfiguration implements RetryConfiguration { - -} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ExecutorServiceManager.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ExecutorServiceManager.java index 112ab7188a..c35281e822 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ExecutorServiceManager.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ExecutorServiceManager.java @@ -22,7 +22,6 @@ public class ExecutorServiceManager { private static final Logger log = LoggerFactory.getLogger(ExecutorServiceManager.class); - public static final int MIN_THREAD_NUMBER = 0; private ExecutorService executor; private ExecutorService workflowExecutor; private ExecutorService cachingExecutorService; diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/LeaderElectionConfigurationBuilder.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/LeaderElectionConfigurationBuilder.java index 4b21dd9d2d..494c9d8e66 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/LeaderElectionConfigurationBuilder.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/LeaderElectionConfigurationBuilder.java @@ -6,9 +6,10 @@ import static io.javaoperatorsdk.operator.api.config.LeaderElectionConfiguration.*; +@SuppressWarnings("unused") public final class LeaderElectionConfigurationBuilder { - private String leaseName; + private final String leaseName; private String leaseNamespace; private String identity; private Duration leaseDuration = LEASE_DURATION_DEFAULT_VALUE; diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResolvedControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResolvedControllerConfiguration.java index 307e75080f..af96604591 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResolvedControllerConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResolvedControllerConfiguration.java @@ -1,16 +1,19 @@ package io.javaoperatorsdk.operator.api.config; import java.time.Duration; -import java.util.*; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.concurrent.TimeUnit; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.client.informers.cache.ItemStore; import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceConfigurationProvider; import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec; +import io.javaoperatorsdk.operator.api.config.workflow.WorkflowSpec; import io.javaoperatorsdk.operator.api.reconciler.Reconciler; import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter; -import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter; @@ -33,9 +36,7 @@ public class ResolvedControllerConfiguration

private final ItemStore

itemStore; private final ConfigurationService configurationService; private final String fieldManager; - - private ResourceEventFilter

eventFilter; - private List dependentResources; + private WorkflowSpec workflowSpec; public ResolvedControllerConfiguration(Class

resourceClass, ControllerConfiguration

other) { this(resourceClass, other.getName(), other.isGenerationAware(), @@ -43,11 +44,11 @@ public ResolvedControllerConfiguration(Class

resourceClass, ControllerConfigu other.maxReconciliationInterval().orElse(null), other.onAddFilter().orElse(null), other.onUpdateFilter().orElse(null), other.genericFilter().orElse(null), - other.getDependentResources(), other.getNamespaces(), + other.getNamespaces(), other.getFinalizerName(), other.getLabelSelector(), Collections.emptyMap(), other.getItemStore().orElse(null), other.fieldManager(), other.getConfigurationService(), - other.getInformerListLimit().orElse(null)); + other.getInformerListLimit().orElse(null), other.getWorkflowSpec().orElse(null)); } public static Duration getMaxReconciliationInterval(long interval, TimeUnit timeUnit) { @@ -72,16 +73,16 @@ public ResolvedControllerConfiguration(Class

resourceClass, String name, RateLimiter rateLimiter, Duration maxReconciliationInterval, OnAddFilter onAddFilter, OnUpdateFilter onUpdateFilter, GenericFilter genericFilter, - List dependentResources, Set namespaces, String finalizer, String labelSelector, Map configurations, ItemStore

itemStore, String fieldManager, - ConfigurationService configurationService, Long informerListLimit) { + ConfigurationService configurationService, Long informerListLimit, + WorkflowSpec workflowSpec) { this(resourceClass, name, generationAware, associatedReconcilerClassName, retry, rateLimiter, maxReconciliationInterval, onAddFilter, onUpdateFilter, genericFilter, namespaces, finalizer, labelSelector, configurations, itemStore, fieldManager, configurationService, informerListLimit); - setDependentResources(dependentResources); + setWorkflowSpec(workflowSpec); } protected ResolvedControllerConfiguration(Class

resourceClass, String name, @@ -146,14 +147,14 @@ public RateLimiter getRateLimiter() { return rateLimiter; } + @Override - public List getDependentResources() { - return dependentResources; + public Optional getWorkflowSpec() { + return Optional.ofNullable(workflowSpec); } - protected void setDependentResources(List dependentResources) { - this.dependentResources = dependentResources == null ? Collections.emptyList() - : Collections.unmodifiableList(dependentResources); + public void setWorkflowSpec(WorkflowSpec workflowSpec) { + this.workflowSpec = workflowSpec; } @Override @@ -166,21 +167,6 @@ public ConfigurationService getConfigurationService() { return configurationService; } - @Override - public ResourceEventFilter

getEventFilter() { - return eventFilter; - } - - /** - * @deprecated Use {@link OnAddFilter}, {@link OnUpdateFilter} and {@link GenericFilter} instead - * - * @param eventFilter generic event filter - */ - @Deprecated(forRemoval = true) - protected void setEventFilter(ResourceEventFilter

eventFilter) { - this.eventFilter = eventFilter; - } - @Override public Object getConfigurationFor(DependentResourceSpec spec) { return configurations.get(spec); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/RetryConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/RetryConfiguration.java deleted file mode 100644 index b293c7e33f..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/RetryConfiguration.java +++ /dev/null @@ -1,33 +0,0 @@ -package io.javaoperatorsdk.operator.api.config; - -import io.javaoperatorsdk.operator.processing.retry.GradualRetry; - -/** - * @deprecated specify your own {@link io.javaoperatorsdk.operator.processing.retry.Retry} - * implementation or use {@link GradualRetry} annotation instead - */ -@Deprecated(forRemoval = true) -public interface RetryConfiguration { - - RetryConfiguration DEFAULT = new DefaultRetryConfiguration(); - - int DEFAULT_MAX_ATTEMPTS = 5; - long DEFAULT_INITIAL_INTERVAL = 2000L; - double DEFAULT_MULTIPLIER = 1.5D; - - default int getMaxAttempts() { - return DEFAULT_MAX_ATTEMPTS; - } - - default long getInitialInterval() { - return DEFAULT_INITIAL_INTERVAL; - } - - default double getIntervalMultiplier() { - return DEFAULT_MULTIPLIER; - } - - default long getMaxInterval() { - return (long) (DEFAULT_INITIAL_INTERVAL * Math.pow(DEFAULT_MULTIPLIER, DEFAULT_MAX_ATTEMPTS)); - } -} 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 6971de6476..ea776f3a6c 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 @@ -33,11 +33,8 @@ public class Utils { * via the {@code git-commit-id-plugin} maven plugin. * * @return a {@link Version} object encapsulating the version information - * @deprecated use {@link #VERSION} instead, as this method will be made internal in a future - * release */ - @Deprecated - public static Version loadFromProperties() { + private static Version loadFromProperties() { final var is = Thread.currentThread().getContextClassLoader().getResourceAsStream("version.properties"); @@ -79,9 +76,8 @@ public static int ensureValid(int value, String description, int minValue, int d throw new IllegalArgumentException( "Default value for " + description + " must be greater than " + minValue); } - log.warn("Requested " + description + " should be greater than " + minValue + ". Requested: " - + value + ", using " + defaultValue + (defaultValue == minValue ? "" : " (default)") + - " instead"); + log.warn("Requested {} should be greater than {}. Requested: {}, using {}{} instead", + description, minValue, value, defaultValue, defaultValue == minValue ? "" : " (default)"); value = defaultValue; } return value; @@ -107,21 +103,22 @@ static boolean getBooleanFromSystemPropsOrDefault(String propertyName, boolean d return defaultValue; } else { property = property.trim().toLowerCase(); - switch (property) { - case "true": - return true; - case "false": - return false; - default: - return defaultValue; - } + return switch (property) { + case "true" -> true; + case "false" -> false; + default -> defaultValue; + }; } } public static Class getFirstTypeArgumentFromExtendedClass(Class clazz) { + return getTypeArgumentFromExtendedClassByIndex(clazz, 0); + } + + public static Class getTypeArgumentFromExtendedClassByIndex(Class clazz, int index) { try { Type type = clazz.getGenericSuperclass(); - return (Class) ((ParameterizedType) type).getActualTypeArguments()[0]; + return (Class) ((ParameterizedType) type).getActualTypeArguments()[index]; } catch (Exception e) { throw new RuntimeException(GENERIC_PARAMETER_TYPE_ERROR_PREFIX + clazz.getSimpleName() @@ -190,27 +187,31 @@ private static Optional> extractType(Class clazz, public static Class getFirstTypeArgumentFromSuperClassOrInterface(Class clazz, Class expectedImplementedInterface) { + return getTypeArgumentFromSuperClassOrInterfaceByIndex(clazz, expectedImplementedInterface, 0); + } + + public static Class getTypeArgumentFromSuperClassOrInterfaceByIndex(Class clazz, + Class expectedImplementedInterface, int index) { // first check super class if it exists try { final Class superclass = clazz.getSuperclass(); if (!superclass.equals(Object.class)) { try { - return getFirstTypeArgumentFromExtendedClass(clazz); + return getTypeArgumentFromExtendedClassByIndex(clazz, index); } catch (Exception e) { // try interfaces try { - return getFirstTypeArgumentFromInterface(clazz, expectedImplementedInterface); + return getTypeArgumentFromInterfaceByIndex(clazz, expectedImplementedInterface, index); } catch (Exception ex) { // try on the parent - return getFirstTypeArgumentFromSuperClassOrInterface(superclass, - expectedImplementedInterface); + return getTypeArgumentFromSuperClassOrInterfaceByIndex(superclass, + expectedImplementedInterface, index); } } } - return getFirstTypeArgumentFromInterface(clazz, expectedImplementedInterface); + return getTypeArgumentFromInterfaceByIndex(clazz, expectedImplementedInterface, index); } catch (Exception e) { - throw new OperatorException( - GENERIC_PARAMETER_TYPE_ERROR_PREFIX + clazz.getSimpleName(), e); + throw new OperatorException(GENERIC_PARAMETER_TYPE_ERROR_PREFIX + clazz.getSimpleName(), e); } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceConfigurationResolver.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceConfigurationResolver.java index ebe8b2ad0e..06ae7ec683 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceConfigurationResolver.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceConfigurationResolver.java @@ -22,8 +22,7 @@ private DependentResourceConfigurationResolver() {} public static > void configure( DependentResource dependentResource, DependentResourceSpec spec, C parentConfiguration) { - if (dependentResource instanceof DependentResourceConfigurator) { - final var configurator = (DependentResourceConfigurator) dependentResource; + if (dependentResource instanceof DependentResourceConfigurator configurator) { final var config = configurationFor(spec, parentConfiguration); configurator.configureWith(config); } @@ -33,8 +32,7 @@ public static > Object DependentResourceSpec spec, C parentConfiguration) { // first check if the parent configuration has potentially already resolved the configuration - if (parentConfiguration instanceof DependentResourceConfigurationProvider) { - final var provider = (DependentResourceConfigurationProvider) parentConfiguration; + if (parentConfiguration instanceof DependentResourceConfigurationProvider provider) { final var configuration = provider.getConfigurationFor(spec); if (configuration != null) { return configuration; diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceSpec.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceSpec.java index 1fcd0709fb..431826a935 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceSpec.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceSpec.java @@ -26,10 +26,12 @@ public class DependentResourceSpec { private final String useEventSourceWithName; + private final boolean optional; + public DependentResourceSpec(Class> dependentResourceClass, String name, Set dependsOn, Condition readyCondition, Condition reconcileCondition, Condition deletePostCondition, - Condition activationCondition, String useEventSourceWithName) { + Condition activationCondition, String useEventSourceWithName, boolean optional) { this.dependentResourceClass = dependentResourceClass; this.name = name; this.dependsOn = dependsOn; @@ -38,6 +40,13 @@ public DependentResourceSpec(Class> dependentR this.deletePostCondition = deletePostCondition; this.activationCondition = activationCondition; this.useEventSourceWithName = useEventSourceWithName; + this.optional = optional; + + if (this.optional && activationCondition != null) { + throw new IllegalArgumentException( + "Dependent resource cannot be both optional and contain activation condition. Dependent resource name: " + + name + " class: " + dependentResourceClass); + } } public Class> getDependentResourceClass() { @@ -98,4 +107,8 @@ public Condition getActivationCondition() { public Optional getUseEventSourceWithName() { return Optional.ofNullable(useEventSourceWithName); } + + public boolean isOptional() { + return optional; + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerConfiguration.java index bca39b189c..5371975ec5 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerConfiguration.java @@ -50,9 +50,7 @@ protected DefaultInformerConfiguration(String labelSelector, this.followControllerNamespaceChanges = followControllerNamespaceChanges; this.groupVersionKind = groupVersionKind; this.primaryToSecondaryMapper = primaryToSecondaryMapper; - this.secondaryToPrimaryMapper = - Objects.requireNonNullElse(secondaryToPrimaryMapper, - Mappers.fromOwnerReference()); + this.secondaryToPrimaryMapper = secondaryToPrimaryMapper; this.onDeleteFilter = onDeleteFilter; } @@ -135,16 +133,24 @@ class InformerConfigurationBuilder { private boolean inheritControllerNamespacesOnChange = false; private ItemStore itemStore; private Long informerListLimit; + private final Class primaryResourceClass; - private InformerConfigurationBuilder(Class resourceClass) { - this.resourceClass = resourceClass; - this.groupVersionKind = null; + private InformerConfigurationBuilder(Class resourceClass, + Class primaryResourceClass) { + this(resourceClass, primaryResourceClass, null); } @SuppressWarnings("unchecked") - private InformerConfigurationBuilder(GroupVersionKind groupVersionKind) { - this.resourceClass = (Class) GenericKubernetesResource.class; + private InformerConfigurationBuilder(GroupVersionKind groupVersionKind, + Class primaryResourceClass) { + this((Class) GenericKubernetesResource.class, primaryResourceClass, groupVersionKind); + } + + private InformerConfigurationBuilder(Class resourceClass, + Class primaryResourceClass, GroupVersionKind groupVersionKind) { + this.resourceClass = resourceClass; this.groupVersionKind = groupVersionKind; + this.primaryResourceClass = primaryResourceClass; } public

InformerConfigurationBuilder withPrimaryToSecondaryMapper( @@ -264,23 +270,17 @@ public InformerConfigurationBuilder withInformerListLimit(Long informerListLi public InformerConfiguration build() { return new DefaultInformerConfiguration<>(labelSelector, resourceClass, groupVersionKind, primaryToSecondaryMapper, - secondaryToPrimaryMapper, + Objects.requireNonNullElse(secondaryToPrimaryMapper, + Mappers.fromOwnerReferences(HasMetadata.getApiVersion(primaryResourceClass), + HasMetadata.getKind(primaryResourceClass), false)), namespaces, inheritControllerNamespacesOnChange, onAddFilter, onUpdateFilter, onDeleteFilter, genericFilter, itemStore, informerListLimit); } } static InformerConfigurationBuilder from( - Class resourceClass) { - return new InformerConfigurationBuilder<>(resourceClass); - } - - /** - * * For the case when want to use {@link GenericKubernetesResource} - */ - static InformerConfigurationBuilder from( - GroupVersionKind groupVersionKind) { - return new InformerConfigurationBuilder<>(groupVersionKind); + Class resourceClass, Class primaryResourceClass) { + return new InformerConfigurationBuilder<>(resourceClass, primaryResourceClass); } /** @@ -294,20 +294,26 @@ static InformerConfigurationBuilder from( */ static InformerConfigurationBuilder from( Class resourceClass, EventSourceContext eventSourceContext) { - return new InformerConfigurationBuilder<>(resourceClass) + return new InformerConfigurationBuilder<>(resourceClass, + eventSourceContext.getPrimaryResourceClass()) .withNamespacesInheritedFromController(eventSourceContext); } /** - * * For the case when want to use {@link GenericKubernetesResource} + * For the case when want to use {@link GenericKubernetesResource} */ - @SuppressWarnings("unchecked") static InformerConfigurationBuilder from( GroupVersionKind groupVersionKind, EventSourceContext eventSourceContext) { - return new InformerConfigurationBuilder(groupVersionKind) + return new InformerConfigurationBuilder(groupVersionKind, + eventSourceContext.getPrimaryResourceClass()) .withNamespacesInheritedFromController(eventSourceContext); } + static InformerConfigurationBuilder from( + GroupVersionKind groupVersionKind, Class primaryResourceClass) { + return new InformerConfigurationBuilder<>(groupVersionKind, primaryResourceClass); + } + @SuppressWarnings("unchecked") @Override default Class getResourceClass() { diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/workflow/WorkflowSpec.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/workflow/WorkflowSpec.java new file mode 100644 index 0000000000..72d50f8050 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/workflow/WorkflowSpec.java @@ -0,0 +1,15 @@ +package io.javaoperatorsdk.operator.api.config.workflow; + +import java.util.List; + +import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec; + +public interface WorkflowSpec { + + @SuppressWarnings("rawtypes") + List getDependentResourceSpecs(); + + boolean isExplicitInvocation(); + + boolean handleExceptionsInReconciler(); +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/monitoring/Metrics.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/monitoring/Metrics.java index 42fee488d7..bb34e5f760 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/monitoring/Metrics.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/monitoring/Metrics.java @@ -1,6 +1,5 @@ package io.javaoperatorsdk.operator.api.monitoring; -import java.util.Collections; import java.util.Map; import io.fabric8.kubernetes.api.model.HasMetadata; @@ -37,16 +36,6 @@ default void controllerRegistered(Controller controller) */ default void receivedEvent(Event event, Map metadata) {} - /** - * @param metadata additional metadata - * @param resourceID of primary resource - * @param retryInfo for current execution - * @deprecated Use {@link #reconcileCustomResource(HasMetadata, RetryInfo, Map)} instead - */ - @Deprecated(forRemoval = true) - @SuppressWarnings("unused") - default void reconcileCustomResource(ResourceID resourceID, RetryInfo retryInfo, - Map metadata) {} /** * Called right before a resource is dispatched to the ExecutorService for reconciliation. @@ -56,19 +45,6 @@ default void reconcileCustomResource(ResourceID resourceID, RetryInfo retryInfo, * @param metadata metadata associated with the resource being processed */ default void reconcileCustomResource(HasMetadata resource, RetryInfo retryInfo, - Map metadata) { - reconcileCustomResource(ResourceID.fromResource(resource), retryInfo, metadata); - } - - /** - * @param exception actual exception - * @param metadata additional metadata - * @param resourceID of primary resource - * @deprecated Use {@link #failedReconciliation(HasMetadata, Exception, Map)} instead - */ - @Deprecated(forRemoval = true) - @SuppressWarnings("unused") - default void failedReconciliation(ResourceID resourceID, Exception exception, Map metadata) {} /** @@ -81,9 +57,7 @@ default void failedReconciliation(ResourceID resourceID, Exception exception, * @param metadata metadata associated with the resource being processed */ default void failedReconciliation(HasMetadata resource, Exception exception, - Map metadata) { - failedReconciliation(ResourceID.fromResource(resource), exception, metadata); - } + Map metadata) {} default void reconciliationExecutionStarted(HasMetadata resource, Map metadata) {} @@ -91,14 +65,6 @@ default void reconciliationExecutionStarted(HasMetadata resource, Map metadata) {} - /** - * @param resourceID of primary resource - * @deprecated Use (and implement) {@link #cleanupDoneFor(ResourceID, Map)} instead - */ - @Deprecated - default void cleanupDoneFor(ResourceID resourceID) { - cleanupDoneFor(resourceID, Collections.emptyMap()); - } /** * Called when the resource associated with the specified {@link ResourceID} has been successfully @@ -109,24 +75,6 @@ default void cleanupDoneFor(ResourceID resourceID) { */ default void cleanupDoneFor(ResourceID resourceID, Map metadata) {} - /** - * @param resourceID of primary resource - * @deprecated Use (and implement) {@link #finishedReconciliation(ResourceID, Map)} instead - */ - @Deprecated - default void finishedReconciliation(ResourceID resourceID) { - finishedReconciliation(resourceID, Collections.emptyMap()); - } - - /** - * @param resourceID of primary resource - * @param metadata additional metadata - * @deprecated Use {@link #finishedReconciliation(HasMetadata, Map)} instead - */ - @Deprecated(forRemoval = true) - @SuppressWarnings("unused") - default void finishedReconciliation(ResourceID resourceID, Map metadata) {} - /** * Called when the * {@link io.javaoperatorsdk.operator.api.reconciler.Reconciler#reconcile(HasMetadata, Context)} @@ -136,9 +84,7 @@ default void finishedReconciliation(ResourceID resourceID, Map m * @param resource the {@link ResourceID} associated with the resource being processed * @param metadata metadata associated with the resource being processed */ - default void finishedReconciliation(HasMetadata resource, Map metadata) { - finishedReconciliation(ResourceID.fromResource(resource), metadata); - } + default void finishedReconciliation(HasMetadata resource, Map metadata) {} /** * Encapsulates the information about a controller execution i.e. a call to either diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Context.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Context.java index e157ed5fd7..0134ea0a04 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Context.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Context.java @@ -8,7 +8,7 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.client.KubernetesClient; import io.javaoperatorsdk.operator.api.config.ControllerConfiguration; -import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.ManagedDependentResourceContext; +import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.ManagedWorkflowAndDependentResourceContext; import io.javaoperatorsdk.operator.processing.event.EventSourceRetriever; import io.javaoperatorsdk.operator.processing.event.source.IndexerResourceCache; @@ -17,7 +17,7 @@ public interface Context

{ Optional getRetryInfo(); default Optional getSecondaryResource(Class expectedType) { - return getSecondaryResource(expectedType, (String) null); + return getSecondaryResource(expectedType, null); } Set getSecondaryResources(Class expectedType); @@ -26,15 +26,18 @@ default Stream getSecondaryResourcesAsStream(Class expectedType) { return getSecondaryResources(expectedType).stream(); } - @Deprecated(forRemoval = true) Optional getSecondaryResource(Class expectedType, String eventSourceName); - Optional getSecondaryResource(Class expectedType, - ResourceDiscriminator discriminator); - ControllerConfiguration

getControllerConfiguration(); - ManagedDependentResourceContext managedDependentResourceContext(); + /** + * Retrieve the {@link ManagedWorkflowAndDependentResourceContext} used to interact with + * {@link io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource}s and associated + * {@link io.javaoperatorsdk.operator.processing.dependent.workflow.Workflow} + * + * @return the {@link ManagedWorkflowAndDependentResourceContext} + */ + ManagedWorkflowAndDependentResourceContext managedWorkflowAndDependentResourceContext(); EventSourceRetriever

eventSourceRetriever(); @@ -51,5 +54,18 @@ Optional getSecondaryResource(Class expectedType, * @return the {@link IndexerResourceCache} associated with the associated {@link Reconciler} for * this context */ + @SuppressWarnings("unused") IndexedResourceCache

getPrimaryCache(); + + /** + * Determines whether a new reconciliation will be triggered right after the current + * reconciliation is finished. This allows to optimize certain situations, helping avoid unneeded + * API calls. A reconciler might, for example, skip updating the status when it's known another + * reconciliation is already scheduled, which would in turn trigger another status update, thus + * rendering the current one moot. + * + * @return {@code true} is another reconciliation is already scheduled, {@code false} otherwise + **/ + boolean isNextReconciliationImminent(); + } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java index c064e669e0..49cf56c1aa 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java @@ -7,11 +7,9 @@ import java.lang.annotation.Target; import io.fabric8.kubernetes.client.informers.cache.ItemStore; -import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; import io.javaoperatorsdk.operator.processing.event.rate.LinearRateLimiter; import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter; import io.javaoperatorsdk.operator.processing.event.source.cache.BoundedItemStore; -import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter; @@ -64,19 +62,6 @@ */ String labelSelector() default Constants.NO_VALUE_SET; - /** - * @deprecated Use onAddFilter, onUpdateFilter instead. - * - *

- * Resource event filters only applies on events of the main custom resource. Not on - * events from other event sources nor the periodic events. - *

- * - * @return the list of event filters. - */ - @Deprecated(forRemoval = true) - Class[] eventFilters() default {}; - /** * Filter of onAdd events of resources. * @@ -107,15 +92,6 @@ MaxReconciliationInterval maxReconciliationInterval() default @MaxReconciliationInterval( interval = MaxReconciliationInterval.DEFAULT_INTERVAL); - - /** - * Optional list of {@link Dependent} configurations which associate a resource type to a - * {@link io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource} implementation - * - * @return the array of {@link Dependent} configurations - */ - Dependent[] dependents() default {}; - /** * Optional {@link Retry} implementation for the associated controller to use. * diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java index 2b0f20ef33..e8f6d475cb 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java @@ -9,10 +9,11 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.client.KubernetesClient; import io.javaoperatorsdk.operator.api.config.ControllerConfiguration; -import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DefaultManagedDependentResourceContext; -import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.ManagedDependentResourceContext; +import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DefaultManagedWorkflowAndDependentResourceContext; +import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.ManagedWorkflowAndDependentResourceContext; import io.javaoperatorsdk.operator.processing.Controller; import io.javaoperatorsdk.operator.processing.event.EventSourceRetriever; +import io.javaoperatorsdk.operator.processing.event.ResourceID; public class DefaultContext

implements Context

{ @@ -20,14 +21,15 @@ public class DefaultContext

implements Context

{ private final Controller

controller; private final P primaryResource; private final ControllerConfiguration

controllerConfiguration; - private final DefaultManagedDependentResourceContext defaultManagedDependentResourceContext; + private final DefaultManagedWorkflowAndDependentResourceContext

defaultManagedDependentResourceContext; public DefaultContext(RetryInfo retryInfo, Controller

controller, P primaryResource) { this.retryInfo = retryInfo; this.controller = controller; this.primaryResource = primaryResource; this.controllerConfiguration = controller.getConfiguration(); - this.defaultManagedDependentResourceContext = new DefaultManagedDependentResourceContext(); + this.defaultManagedDependentResourceContext = + new DefaultManagedWorkflowAndDependentResourceContext<>(controller, primaryResource, this); } @Override @@ -42,12 +44,18 @@ public Set getSecondaryResources(Class expectedType) { @Override public IndexedResourceCache

getPrimaryCache() { - return controller.getEventSourceManager().getControllerResourceEventSource(); + return controller.getEventSourceManager().getControllerEventSource(); + } + + @Override + public boolean isNextReconciliationImminent() { + return controller.getEventProcessor() + .isNextReconciliationImminent(ResourceID.fromResource(primaryResource)); } @Override public Stream getSecondaryResourcesAsStream(Class expectedType) { - return controller.getEventSourceManager().getResourceEventSourcesFor(expectedType).stream() + return controller.getEventSourceManager().getEventSourcesFor(expectedType).stream() .map(es -> es.getSecondaryResources(primaryResource)) .flatMap(Set::stream); } @@ -56,23 +64,17 @@ public Stream getSecondaryResourcesAsStream(Class expectedType) { public Optional getSecondaryResource(Class expectedType, String eventSourceName) { return controller .getEventSourceManager() - .getResourceEventSourceFor(expectedType, eventSourceName) + .getEventSourceFor(expectedType, eventSourceName) .getSecondaryResource(primaryResource); } - @Override - public Optional getSecondaryResource(Class expectedType, - ResourceDiscriminator discriminator) { - return discriminator.distinguish(expectedType, primaryResource, this); - } - @Override public ControllerConfiguration

getControllerConfiguration() { return controllerConfiguration; } @Override - public ManagedDependentResourceContext managedDependentResourceContext() { + public ManagedWorkflowAndDependentResourceContext managedWorkflowAndDependentResourceContext() { return defaultManagedDependentResourceContext; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ErrorStatusUpdateControl.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ErrorStatusUpdateControl.java index 48c0e32946..7236d5898b 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ErrorStatusUpdateControl.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ErrorStatusUpdateControl.java @@ -9,24 +9,18 @@ public class ErrorStatusUpdateControl

extends BaseControl> { private final P resource; - private final boolean patch; private boolean noRetry = false; public static ErrorStatusUpdateControl patchStatus(T resource) { - return new ErrorStatusUpdateControl<>(resource, true); - } - - public static ErrorStatusUpdateControl updateStatus(T resource) { - return new ErrorStatusUpdateControl<>(resource, false); + return new ErrorStatusUpdateControl<>(resource); } public static ErrorStatusUpdateControl noStatusUpdate() { - return new ErrorStatusUpdateControl<>(null, true); + return new ErrorStatusUpdateControl<>(null); } - private ErrorStatusUpdateControl(P resource, boolean patch) { + private ErrorStatusUpdateControl(P resource) { this.resource = resource; - this.patch = patch; } /** @@ -47,10 +41,6 @@ public boolean isNoRetry() { return noRetry; } - public boolean isPatch() { - return patch; - } - /** * If re-scheduled using this method, it is not considered as retry, it effectively cancels retry. * diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceContext.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceContext.java index e8062e9651..4f0f08b3b8 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceContext.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceContext.java @@ -16,13 +16,16 @@ public class EventSourceContext

{ private final IndexerResourceCache

primaryCache; private final ControllerConfiguration

controllerConfiguration; private final KubernetesClient client; + private final Class

primaryResourceClass; public EventSourceContext(IndexerResourceCache

primaryCache, ControllerConfiguration

controllerConfiguration, - KubernetesClient client) { + KubernetesClient client, + Class

primaryResourceClass) { this.primaryCache = primaryCache; this.controllerConfiguration = controllerConfiguration; this.client = client; + this.primaryResourceClass = primaryResourceClass; } /** @@ -54,4 +57,8 @@ public ControllerConfiguration

getControllerConfiguration() { public KubernetesClient getClient() { return client; } + + public Class

getPrimaryResourceClass() { + return primaryResourceClass; + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceInitializer.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceInitializer.java deleted file mode 100644 index 09c1687e1e..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceInitializer.java +++ /dev/null @@ -1,89 +0,0 @@ -package io.javaoperatorsdk.operator.api.reconciler; - -import java.util.*; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; -import io.javaoperatorsdk.operator.processing.dependent.workflow.Workflow; -import io.javaoperatorsdk.operator.processing.event.source.EventSource; -import io.javaoperatorsdk.operator.processing.event.source.ResourceEventSource; - -/** - * An interface that a {@link Reconciler} can implement to have the SDK register the provided - * {@link EventSource} - * - * @param

the primary resource type handled by the associated {@link Reconciler} - */ -public interface EventSourceInitializer

{ - - /** - * Prepares a map of {@link EventSource} implementations keyed by the name with which they need to - * be registered by the SDK. - * - * @param context a {@link EventSourceContext} providing access to information useful to event - * sources - * @return a map of event sources to register - */ - Map prepareEventSources(EventSourceContext

context); - - /** - * Utility method to easily create map with generated name for event sources. This is for the use - * case when the event sources are not access explicitly by name in the reconciler. - * - * @param eventSources to name - * @return even source with default names - */ - static Map nameEventSources(EventSource... eventSources) { - Map eventSourceMap = new HashMap<>(eventSources.length); - for (EventSource eventSource : eventSources) { - eventSourceMap.put(generateNameFor(eventSource), eventSource); - } - return eventSourceMap; - } - - @SuppressWarnings("unchecked") - static Map eventSourcesFromWorkflow( - EventSourceContext context, - Workflow workflow) { - Map result = new HashMap<>(); - for (var e : workflow.getDependentResourcesByNameWithoutActivationCondition().entrySet()) { - var eventSource = e.getValue().eventSource(context); - eventSource.ifPresent(es -> result.put(e.getKey(), (EventSource) es)); - } - return result; - } - - @SuppressWarnings("rawtypes") - static Map nameEventSourcesFromDependentResource( - EventSourceContext context, DependentResource... dependentResources) { - return nameEventSourcesFromDependentResource(context, Arrays.asList(dependentResources)); - } - - @SuppressWarnings("unchecked,rawtypes") - static Map nameEventSourcesFromDependentResource( - EventSourceContext context, Collection dependentResources) { - - if (dependentResources != null) { - Map eventSourceMap = new HashMap<>(dependentResources.size()); - for (DependentResource dependentResource : dependentResources) { - Optional es = dependentResource.eventSource(context); - es.ifPresent(e -> eventSourceMap.put(generateNameFor(e), e)); - } - return eventSourceMap; - } else { - return Collections.emptyMap(); - } - } - - /** - * Used when event sources are not explicitly named when created/registered. - * - * @param eventSource EventSource - * @return generated name - */ - static String generateNameFor(EventSource eventSource) { - // we can have multiple event sources for the same class - return eventSource.getClass().getName() + "#" + eventSource.hashCode(); - } - -} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceUtils.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceUtils.java new file mode 100644 index 0000000000..4294fee405 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceUtils.java @@ -0,0 +1,26 @@ +package io.javaoperatorsdk.operator.api.reconciler; + +import java.util.*; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.processing.dependent.workflow.Workflow; +import io.javaoperatorsdk.operator.processing.event.source.EventSource; + +public class EventSourceUtils { + + @SuppressWarnings("unchecked") + public static List dependentEventSources( + EventSourceContext eventSourceContext, DependentResource... dependentResources) { + return Arrays.stream(dependentResources) + .flatMap(dr -> dr.eventSource(eventSourceContext).stream()).toList(); + } + + @SuppressWarnings("unchecked") + public static List eventSourcesFromWorkflow( + EventSourceContext context, + Workflow workflow) { + return workflow.getDependentResourcesWithoutActivationCondition().stream() + .flatMap(dr -> dr.eventSource(context).stream()).toList(); + } +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/IndexDiscriminator.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/IndexDiscriminator.java deleted file mode 100644 index 7a27397b26..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/IndexDiscriminator.java +++ /dev/null @@ -1,50 +0,0 @@ -package io.javaoperatorsdk.operator.api.reconciler; - -import java.util.Optional; -import java.util.function.Function; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; - -/** - * Uses a custom index of {@link InformerEventSource} to access the target resource. The index needs - * to be explicitly created when the event source is defined. This approach improves the performance - * to access the resource. - */ -public class IndexDiscriminator - implements ResourceDiscriminator { - - private final String indexName; - private final String eventSourceName; - private final Function keyMapper; - - public IndexDiscriminator(String indexName, Function keyMapper) { - this(indexName, null, keyMapper); - } - - public IndexDiscriminator(String indexName, String eventSourceName, - Function keyMapper) { - this.indexName = indexName; - this.eventSourceName = eventSourceName; - this.keyMapper = keyMapper; - } - - @Override - public Optional distinguish(Class resource, - P primary, - Context

context) { - - InformerEventSource eventSource = - (InformerEventSource) context - .eventSourceRetriever() - .getResourceEventSourceFor(resource, eventSourceName); - var resources = eventSource.byIndex(indexName, keyMapper.apply(primary)); - if (resources.isEmpty()) { - return Optional.empty(); - } else if (resources.size() > 1) { - throw new IllegalStateException("More than one resource found"); - } else { - return Optional.of(resources.get(0)); - } - } -} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Reconciler.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Reconciler.java index 55df9d1cea..40a8a3b407 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Reconciler.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Reconciler.java @@ -1,8 +1,11 @@ package io.javaoperatorsdk.operator.api.reconciler; +import java.util.*; + import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.processing.event.source.EventSource; -public interface Reconciler { +public interface Reconciler

{ /** * The implementation of this operation is required to be idempotent. Always use the UpdateControl @@ -14,6 +17,18 @@ public interface Reconciler { * @return UpdateControl to manage updates on the custom resource (usually the status) after * reconciliation. */ - UpdateControl reconcile(R resource, Context context) throws Exception; + UpdateControl

reconcile(P resource, Context

context) throws Exception; + + /** + * Prepares a map of {@link EventSource} implementations keyed by the name with which they need to + * be registered by the SDK. + * + * @param context a {@link EventSourceContext} providing access to information useful to event + * sources + * @return a list of event sources + */ + default List prepareEventSources(EventSourceContext

context) { + return Collections.emptyList(); + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceDiscriminator.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceDiscriminator.java deleted file mode 100644 index 072e7d8078..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceDiscriminator.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.javaoperatorsdk.operator.api.reconciler; - -import java.util.Optional; - -import io.fabric8.kubernetes.api.model.HasMetadata; - -public interface ResourceDiscriminator { - - Optional distinguish(Class resource, P primary, Context

context); - -} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceIDMatcherDiscriminator.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceIDMatcherDiscriminator.java deleted file mode 100644 index da773fc210..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceIDMatcherDiscriminator.java +++ /dev/null @@ -1,45 +0,0 @@ -package io.javaoperatorsdk.operator.api.reconciler; - -import java.util.Optional; -import java.util.function.Function; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.processing.event.ResourceID; -import io.javaoperatorsdk.operator.processing.event.source.Cache; - -public class ResourceIDMatcherDiscriminator - implements ResourceDiscriminator { - - - private final String eventSourceName; - private final Function mapper; - - public ResourceIDMatcherDiscriminator(Function mapper) { - this(null, mapper); - } - - public ResourceIDMatcherDiscriminator(String eventSourceName, Function mapper) { - this.eventSourceName = eventSourceName; - this.mapper = mapper; - } - - @SuppressWarnings("unchecked") - @Override - public Optional distinguish(Class resource, P primary, Context

context) { - var resourceID = mapper.apply(primary); - if (eventSourceName != null) { - return ((Cache) context.eventSourceRetriever().getResourceEventSourceFor(resource, - eventSourceName)) - .get(resourceID); - } else { - var eventSources = context.eventSourceRetriever().getResourceEventSourcesFor(resource); - if (eventSources.size() == 1) { - return ((Cache) eventSources.get(0)).get(resourceID); - } else { - return context.getSecondaryResourcesAsStream(resource) - .filter(resourceID::isSameResource) - .findFirst(); - } - } - } -} 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 7eb7d2ae84..a8ae1331d7 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 @@ -1,51 +1,35 @@ package io.javaoperatorsdk.operator.api.reconciler; +import java.util.Optional; + import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.client.CustomResource; public class UpdateControl

extends BaseControl> { private final P resource; - private final boolean updateStatus; - private final boolean updateResource; + private final boolean patchResource; private final boolean patchStatus; private UpdateControl( - P resource, boolean updateStatus, boolean updateResource, boolean patchStatus) { - if ((updateResource || updateStatus) && resource == null) { + P resource, boolean patchResource, boolean patchStatus) { + if ((patchResource || patchStatus) && resource == null) { throw new IllegalArgumentException("CustomResource cannot be null in case of update"); } this.resource = resource; - this.updateStatus = updateStatus; - this.updateResource = updateResource; + this.patchResource = patchResource; this.patchStatus = patchStatus; } - /** - * Creates an update control instance that instructs the framework to do an update on resource - * itself, not on the status. Note that usually as a results of a reconciliation should be a - * status update not an update to the resource itself. - * - * Using this update makes sure that the resource in the next reconciliation is the updated one - - * this is not guaranteed by default if you do an update on a resource by the Kubernetes client. - * - * @param custom resource type - * @param customResource customResource to use for update - * @return initialized update control - */ - public static UpdateControl updateResource(T customResource) { - return new UpdateControl<>(customResource, false, true, false); - } - /** * Preferred way to update the status. It does not do optimistic locking. Uses JSON Patch to patch * the resource. *

- * Note that this does not work, if the {@link CustomResource#initStatus() initStatus} is - * implemented, since it breaks the diffing process. Don't implement it if using this method. + * 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 issue with setting value to null with older Kubernetes versions (1.19 and - * below). See: https://github.com/fabric8io/kubernetes-client/issues/4158 * * @param resource type @@ -53,86 +37,32 @@ public static UpdateControl updateResource(T customRe * @return UpdateControl instance */ public static UpdateControl patchStatus(T customResource) { - return new UpdateControl<>(customResource, true, false, true); - } - - /** - * Note that usually "patchStatus" is advised to be used instead of this method. - *

- * Updates the status with optimistic locking regarding current resource version reconciled. Note - * that this also ensures that on next reconciliation is the most up-to-date custom resource is - * used. - *

- * - * @param resource type - * @param customResource the custom resource with target status - * @return UpdateControl instance - */ - public static UpdateControl updateStatus(T customResource) { - return new UpdateControl<>(customResource, true, false, false); - } - - /** - * As a results of this there will be two call to K8S API. First the custom resource will be - * updates then the status sub-resource. - * - * Using this update makes sure that the resource in the next reconciliation is the updated one - - * this is not guaranteed by default if you do an update on a resource by the Kubernetes client. - * - * @param resource type - * @param customResource - custom resource to use in both API calls - * @return UpdateControl instance - */ - public static UpdateControl updateResourceAndStatus( - T customResource) { - return new UpdateControl<>(customResource, true, true, false); + return new UpdateControl<>(customResource, false, true); } - /** - * Updates the resource - with optimistic locking - and patches the status without optimistic - * locking in place. - * - * Note that using this method, it is not guaranteed that the most recent updated resource will be - * in case for next reconciliation. - * - * @param customResource to update - * @return UpdateControl instance - * @param resource type - */ - public static UpdateControl updateResourceAndPatchStatus( - T customResource) { - return new UpdateControl<>(customResource, true, true, true); + public static UpdateControl patchResource(T customResource) { + return new UpdateControl<>(customResource, true, false); } /** - * Marked for removal, because of confusing name. It does not patch the resource but rather - * updates it. - * - * @deprecated use {@link UpdateControl#updateResourceAndPatchStatus(HasMetadata)} - * * @param customResource to update * @return UpdateControl instance * @param resource type */ - @Deprecated(forRemoval = true) public static UpdateControl patchResourceAndStatus(T customResource) { - return updateResourceAndStatus(customResource); + return new UpdateControl<>(customResource, true, true); } public static UpdateControl noUpdate() { - return new UpdateControl<>(null, false, false, false); - } - - public P getResource() { - return resource; + return new UpdateControl<>(null, false, false); } - public boolean isUpdateStatus() { - return updateStatus; + public Optional

getResource() { + return Optional.ofNullable(resource); } - public boolean isUpdateResource() { - return updateResource; + public boolean isPatchResource() { + return patchResource; } public boolean isPatchStatus() { @@ -140,11 +70,11 @@ public boolean isPatchStatus() { } public boolean isNoUpdate() { - return !updateResource && !updateStatus; + return !patchResource && !patchStatus; } - public boolean isUpdateResourceAndStatus() { - return updateResource && updateStatus; + public boolean isPatchResourceAndStatus() { + return patchResource && patchStatus; } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Workflow.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Workflow.java new file mode 100644 index 0000000000..a9497a9749 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Workflow.java @@ -0,0 +1,41 @@ +package io.javaoperatorsdk.operator.api.reconciler; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; + +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface Workflow { + + Dependent[] dependents(); + + /** + * If {@code true}, the managed workflow should be explicitly invoked within the reconciler + * implementation. If {@code false}, the workflow is invoked just before the + * {@link Reconciler#reconcile(HasMetadata, Context)} method. + */ + boolean explicitInvocation() default false; + + /** + * If {@code true} and exceptions are thrown during the workflow's execution, the reconciler won't + * throw an {@link io.javaoperatorsdk.operator.AggregatedOperatorException} at the end of the + * execution as would normally be the case. Instead, it will proceed to its + * {@link Reconciler#reconcile(HasMetadata, Context)} method as if no error occurred. It is then + * up to the developer to decide how to proceed by retrieving the errored dependents (and their + * associated exception) via + * {@link io.javaoperatorsdk.operator.processing.dependent.workflow.WorkflowResult#erroredDependents}, + * the workflow result itself being accessed from + * {@link Context#managedWorkflowAndDependentResourceContext()}. If {@code false}, an exception + * will be automatically thrown at the end of the workflow execution, presenting an aggregated + * view of what happened. + */ + boolean handleExceptionsInReconciler() default false; + +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/Dependent.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/Dependent.java index 754e2c85be..45c8e6cd8d 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/Dependent.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/Dependent.java @@ -86,4 +86,6 @@ * @return event source name (if any) provided by the dependent resource should be used. */ String useEventSourceWithName() default NO_VALUE_SET; + + boolean optional() default false; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResource.java index 8230dc4cf9..2b75b0d969 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResource.java @@ -5,7 +5,7 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; -import io.javaoperatorsdk.operator.processing.event.source.ResourceEventSource; +import io.javaoperatorsdk.operator.processing.event.source.EventSource; /** * An interface to implement and provide dependent resource support. @@ -32,8 +32,8 @@ public interface DependentResource { Class resourceType(); /** - * Dependent resources are designed to by default provide event sources. There are cases where - * they might not: + * Dependent resources are designed to provide event sources by default. There are, however, cases + * where they might not: *

    *
  • If an event source is shared between multiple dependent resources. In this case only one or * none of the dependent resources sharing the event source should provide one, if any.
  • @@ -42,13 +42,24 @@ public interface DependentResource { *
* * @param eventSourceContext context of event source initialization - * @return an optional event source + * @return an optional event source initialized from the specified context + * @since 5.0.0 */ - default Optional> eventSource( + default Optional> eventSource( EventSourceContext

eventSourceContext) { return Optional.empty(); } + /** + * Retrieves the secondary resource (if it exists) associated with the specified primary resource + * for this DependentResource. + * + * @param primary the primary resource for which we want to retrieve the secondary resource + * associated with this DependentResource + * @param context the current {@link Context} in which the operation is called + * @return the secondary resource or {@link Optional#empty()} if it doesn't exist + * @throws IllegalStateException if more than one secondary is found to match the primary resource + */ default Optional getSecondaryResource(P primary, Context

context) { return Optional.empty(); } @@ -68,4 +79,9 @@ static String defaultNameFor(Class dependentResourc default boolean isDeletable() { return this instanceof Deleter; } + + default String name() { + return defaultNameFor(getClass()); + } + } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/EventSourceProvider.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/EventSourceProvider.java deleted file mode 100644 index c83af1270a..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/EventSourceProvider.java +++ /dev/null @@ -1,18 +0,0 @@ -package io.javaoperatorsdk.operator.api.reconciler.dependent; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; -import io.javaoperatorsdk.operator.processing.event.source.EventSource; - -/** - * @deprecated now event source related methods are directly on {@link DependentResource} - * @param

primary resource - */ -@Deprecated(forRemoval = true) -public interface EventSourceProvider

{ - /** - * @param context - event source context where the event source is initialized - * @return the initiated event source. - */ - EventSource initEventSource(EventSourceContext

context); -} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/NameSetter.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/NameSetter.java new file mode 100644 index 0000000000..952bf14490 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/NameSetter.java @@ -0,0 +1,7 @@ +package io.javaoperatorsdk.operator.api.reconciler.dependent; + +public interface NameSetter { + + void setName(String name); + +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/DefaultManagedDependentResourceContext.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/DefaultManagedWorkflowAndDependentResourceContext.java similarity index 50% rename from operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/DefaultManagedDependentResourceContext.java rename to operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/DefaultManagedWorkflowAndDependentResourceContext.java index 5b1a21e5dd..f93f45ecf7 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/DefaultManagedDependentResourceContext.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/DefaultManagedWorkflowAndDependentResourceContext.java @@ -3,15 +3,30 @@ import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.processing.Controller; import io.javaoperatorsdk.operator.processing.dependent.workflow.WorkflowCleanupResult; import io.javaoperatorsdk.operator.processing.dependent.workflow.WorkflowReconcileResult; @SuppressWarnings("rawtypes") -public class DefaultManagedDependentResourceContext implements ManagedDependentResourceContext { +public class DefaultManagedWorkflowAndDependentResourceContext

+ implements ManagedWorkflowAndDependentResourceContext { + private final ConcurrentHashMap attributes = new ConcurrentHashMap(); + private final Controller

controller; + private final P primaryResource; + private final Context

context; private WorkflowReconcileResult workflowReconcileResult; private WorkflowCleanupResult workflowCleanupResult; - private final ConcurrentHashMap attributes = new ConcurrentHashMap(); + + public DefaultManagedWorkflowAndDependentResourceContext(Controller

controller, + P primaryResource, + Context

context) { + this.controller = controller; + this.primaryResource = primaryResource; + this.context = context; + } @Override public Optional get(Object key, Class expectedType) { @@ -37,25 +52,42 @@ public T getMandatory(Object key, Class expectedType) { + ") is missing or not of the expected type")); } - public DefaultManagedDependentResourceContext setWorkflowExecutionResult( + public DefaultManagedWorkflowAndDependentResourceContext setWorkflowExecutionResult( WorkflowReconcileResult workflowReconcileResult) { this.workflowReconcileResult = workflowReconcileResult; return this; } - public DefaultManagedDependentResourceContext setWorkflowCleanupResult( + public DefaultManagedWorkflowAndDependentResourceContext setWorkflowCleanupResult( WorkflowCleanupResult workflowCleanupResult) { this.workflowCleanupResult = workflowCleanupResult; return this; } @Override - public Optional getWorkflowReconcileResult() { - return Optional.ofNullable(workflowReconcileResult); + public WorkflowReconcileResult getWorkflowReconcileResult() { + return workflowReconcileResult; } @Override - public Optional getWorkflowCleanupResult() { - return Optional.ofNullable(workflowCleanupResult); + public WorkflowCleanupResult getWorkflowCleanupResult() { + return workflowCleanupResult; } + + @Override + public void reconcileManagedWorkflow() { + if (!controller.isWorkflowExplicitInvocation()) { + throw new IllegalStateException("Workflow explicit invocation is not set."); + } + controller.reconcileManagedWorkflow(primaryResource, context); + } + + @Override + public void cleanupManageWorkflow() { + if (!controller.isWorkflowExplicitInvocation()) { + throw new IllegalStateException("Workflow explicit invocation is not set."); + } + controller.cleanupManagedWorkflow(primaryResource, context); + } + } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/KubernetesClientAware.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/KubernetesClientAware.java deleted file mode 100644 index d6c743f22b..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/KubernetesClientAware.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.javaoperatorsdk.operator.api.reconciler.dependent.managed; - -import io.fabric8.kubernetes.client.KubernetesClient; -import io.javaoperatorsdk.operator.api.reconciler.Context; - -/** - * @deprecated It shouldn't be needed to pass a {@link KubernetesClient} instance anymore as the - * client should be accessed via {@link Context#getClient()} instead. - */ -@Deprecated(since = "4.5.0", forRemoval = true) -public interface KubernetesClientAware { - void setKubernetesClient(KubernetesClient kubernetesClient); - - KubernetesClient getKubernetesClient(); -} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedDependentResourceContext.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedWorkflowAndDependentResourceContext.java similarity index 72% rename from operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedDependentResourceContext.java rename to operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedWorkflowAndDependentResourceContext.java index 9c5b3dddb1..f5a25a6d9c 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedDependentResourceContext.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedWorkflowAndDependentResourceContext.java @@ -10,7 +10,7 @@ * Contextual information related to {@link DependentResource} either to retrieve the actual * implementations to interact with them or to pass information between them and/or the reconciler */ -public interface ManagedDependentResourceContext { +public interface ManagedWorkflowAndDependentResourceContext { /** * Retrieve a contextual object, if it exists and is of the specified expected type, associated @@ -37,7 +37,6 @@ public interface ManagedDependentResourceContext { * @return an Optional containing the previous value associated with the key or * {@link Optional#empty()} if none existed */ - @SuppressWarnings("unchecked") T put(Object key, T value); /** @@ -52,7 +51,27 @@ public interface ManagedDependentResourceContext { @SuppressWarnings("unused") T getMandatory(Object key, Class expectedType); - Optional getWorkflowReconcileResult(); + WorkflowReconcileResult getWorkflowReconcileResult(); + + @SuppressWarnings("unused") + WorkflowCleanupResult getWorkflowCleanupResult(); + + /** + * Explicitly reconcile the declared workflow for the associated + * {@link io.javaoperatorsdk.operator.api.reconciler.Reconciler} + * + * @throws IllegalStateException if called when explicit invocation is not requested + */ + void reconcileManagedWorkflow(); + + /** + * Explicitly clean-up dependent resources in the declared workflow for the associated + * {@link io.javaoperatorsdk.operator.api.reconciler.Reconciler}. Note that calling this method is + * only needed if the associated reconciler implements the + * {@link io.javaoperatorsdk.operator.api.reconciler.Cleaner} interface. + * + * @throws IllegalStateException if called when explicit invocation is not requested + */ + void cleanupManageWorkflow(); - Optional getWorkflowCleanupResult(); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/health/ControllerHealthInfo.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/health/ControllerHealthInfo.java index fe90b99ef3..1d65922f11 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/health/ControllerHealthInfo.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/health/ControllerHealthInfo.java @@ -4,6 +4,8 @@ import java.util.stream.Collectors; import io.javaoperatorsdk.operator.processing.event.EventSourceManager; +import io.javaoperatorsdk.operator.processing.event.source.EventSource; +import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerEventSource; @SuppressWarnings("rawtypes") public class ControllerHealthInfo { @@ -15,36 +17,35 @@ public ControllerHealthInfo(EventSourceManager eventSourceManager) { } public Map eventSourceHealthIndicators() { - return eventSourceManager.allEventSources().entrySet().stream() - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + return eventSourceManager.allEventSources().stream() + .collect(Collectors.toMap(EventSource::name, e -> e)); } public Map unhealthyEventSources() { - return eventSourceManager.allEventSources().entrySet().stream() - .filter(e -> e.getValue().getStatus() == Status.UNHEALTHY) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + return eventSourceManager.allEventSources().stream() + .filter(e -> e.getStatus() == Status.UNHEALTHY) + .collect(Collectors.toMap(EventSource::name, e -> e)); } public Map informerEventSourceHealthIndicators() { - return eventSourceManager.allEventSources().entrySet().stream() - .filter(e -> e.getValue() instanceof InformerWrappingEventSourceHealthIndicator) - .collect(Collectors.toMap(Map.Entry::getKey, - e -> (InformerWrappingEventSourceHealthIndicator) e.getValue())); + return eventSourceManager.allEventSources().stream() + .filter(e -> e instanceof InformerWrappingEventSourceHealthIndicator) + .collect(Collectors.toMap(EventSource::name, + e -> (InformerWrappingEventSourceHealthIndicator) e)); } /** * @return Map with event sources that wraps an informer. Thus, either a - * {@link io.javaoperatorsdk.operator.processing.event.source.controller.ControllerResourceEventSource} - * or an + * {@link ControllerEventSource} or an * {@link io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource}. */ public Map unhealthyInformerEventSourceHealthIndicators() { - return eventSourceManager.allEventSources().entrySet().stream() - .filter(e -> e.getValue().getStatus() == Status.UNHEALTHY) - .filter(e -> e.getValue() instanceof InformerWrappingEventSourceHealthIndicator) - .collect(Collectors.toMap(Map.Entry::getKey, - e -> (InformerWrappingEventSourceHealthIndicator) e.getValue())); + return eventSourceManager.allEventSources().stream() + .filter(e -> e.getStatus() == Status.UNHEALTHY) + .filter(e -> e instanceof InformerWrappingEventSourceHealthIndicator) + .collect(Collectors.toMap(EventSource::name, + e -> (InformerWrappingEventSourceHealthIndicator) e)); } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/Controller.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/Controller.java index bc2d3f9eec..af9c3c8d9f 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/Controller.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/Controller.java @@ -23,6 +23,7 @@ import io.javaoperatorsdk.operator.RegisteredController; import io.javaoperatorsdk.operator.api.config.ControllerConfiguration; import io.javaoperatorsdk.operator.api.config.ExecutorServiceManager; +import io.javaoperatorsdk.operator.api.config.workflow.WorkflowSpec; import io.javaoperatorsdk.operator.api.monitoring.Metrics; import io.javaoperatorsdk.operator.api.monitoring.Metrics.ControllerExecution; import io.javaoperatorsdk.operator.api.reconciler.Cleaner; @@ -31,21 +32,19 @@ import io.javaoperatorsdk.operator.api.reconciler.ContextInitializer; import io.javaoperatorsdk.operator.api.reconciler.DeleteControl; import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; -import io.javaoperatorsdk.operator.api.reconciler.EventSourceInitializer; import io.javaoperatorsdk.operator.api.reconciler.Ignore; import io.javaoperatorsdk.operator.api.reconciler.Reconciler; import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceNotFoundException; -import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceProvider; import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceReferencer; -import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DefaultManagedDependentResourceContext; +import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DefaultManagedWorkflowAndDependentResourceContext; import io.javaoperatorsdk.operator.health.ControllerHealthInfo; import io.javaoperatorsdk.operator.processing.dependent.workflow.Workflow; import io.javaoperatorsdk.operator.processing.dependent.workflow.WorkflowCleanupResult; import io.javaoperatorsdk.operator.processing.event.EventProcessor; import io.javaoperatorsdk.operator.processing.event.EventSourceManager; import io.javaoperatorsdk.operator.processing.event.ResourceID; -import io.javaoperatorsdk.operator.processing.event.source.ResourceEventSource; +import io.javaoperatorsdk.operator.processing.event.source.EventSource; import static io.javaoperatorsdk.operator.api.reconciler.Constants.WATCH_CURRENT_NAMESPACE; @@ -72,6 +71,7 @@ public class Controller

private final boolean isCleaner; private final Metrics metrics; private final Workflow

managedWorkflow; + private final boolean explicitWorkflowInvocation; private final GroupVersionKind associatedGVK; private final EventProcessor

eventProcessor; @@ -94,13 +94,17 @@ public Controller(Reconciler

reconciler, final var managed = configurationService.getWorkflowFactory().workflowFor(configuration); managedWorkflow = managed.resolve(kubernetesClient, configuration); + explicitWorkflowInvocation = + configuration.getWorkflowSpec().map(WorkflowSpec::isExplicitInvocation) + .orElse(false); eventSourceManager = new EventSourceManager<>(this); eventProcessor = new EventProcessor<>(eventSourceManager, configurationService); eventSourceManager.postProcessDefaultEventSourcesAfterProcessorInitializer(); controllerHealthInfo = new ControllerHealthInfo(eventSourceManager); eventSourceContext = new EventSourceContext<>( - eventSourceManager.getControllerResourceEventSource(), configuration, kubernetesClient); + eventSourceManager.getControllerEventSource(), configuration, kubernetesClient, + configuration.getResourceClass()); initAndRegisterEventSources(eventSourceContext); configurationService.getMetrics().controllerRegistered(this); } @@ -122,10 +126,10 @@ public String controllerName() { @Override public String successTypeName(UpdateControl

result) { String successType = RESOURCE; - if (result.isUpdateStatus()) { + if (result.isPatchStatus()) { successType = STATUS; } - if (result.isUpdateResourceAndStatus()) { + if (result.isPatchResourceAndStatus()) { successType = BOTH; } return successType; @@ -144,12 +148,11 @@ public Map metadata() { @Override public UpdateControl

execute() throws Exception { initContextIfNeeded(resource, context); - if (!managedWorkflow.isEmpty()) { - var res = managedWorkflow.reconcile(resource, context); - ((DefaultManagedDependentResourceContext) context.managedDependentResourceContext()) - .setWorkflowExecutionResult(res); - res.throwAggregateExceptionIfErrorsPresent(); - } + configuration.getWorkflowSpec().ifPresent(ws -> { + if (!explicitWorkflowInvocation) { + reconcileManagedWorkflow(resource, context); + } + }); return reconciler.reconcile(resource, context); } }); @@ -189,12 +192,13 @@ public Map metadata() { public DeleteControl execute() { initContextIfNeeded(resource, context); WorkflowCleanupResult workflowCleanupResult = null; - if (managedWorkflow.hasCleaner()) { - workflowCleanupResult = managedWorkflow.cleanup(resource, context); - ((DefaultManagedDependentResourceContext) context.managedDependentResourceContext()) - .setWorkflowCleanupResult(workflowCleanupResult); - workflowCleanupResult.throwAggregateExceptionIfErrorsPresent(); + + // The cleanup is called also when explicit invocation is true, but the cleaner is not + // implemented + if (!isCleaner || !explicitWorkflowInvocation) { + workflowCleanupResult = cleanupManagedWorkflow(resource, context); } + if (isCleaner) { var cleanupResult = ((Cleaner

) reconciler).cleanup(resource, context); if (!cleanupResult.isRemoveFinalizer()) { @@ -230,31 +234,22 @@ private void initContextIfNeeded(P resource, Context

context) { } public void initAndRegisterEventSources(EventSourceContext

context) { - if (reconciler instanceof EventSourceInitializer) { - final var provider = (EventSourceInitializer

) this.reconciler; - final var ownSources = provider.prepareEventSources(context); - ownSources.forEach(eventSourceManager::registerEventSource); - } + final var ownSources = this.reconciler.prepareEventSources(context); + ownSources.forEach(eventSourceManager::registerEventSource); // register created event sources final var dependentResourcesByName = - managedWorkflow.getDependentResourcesByNameWithoutActivationCondition(); + managedWorkflow.getDependentResourcesWithoutActivationCondition(); final var size = dependentResourcesByName.size(); if (size > 0) { - dependentResourcesByName.forEach((key, dependentResource) -> { - if (dependentResource instanceof EventSourceProvider) { - final var provider = (EventSourceProvider) dependentResource; - final var source = provider.initEventSource(context); - eventSourceManager.registerEventSource(key, source); - } else { - Optional eventSource = dependentResource.eventSource(context); - eventSource.ifPresent(es -> eventSourceManager.registerEventSource(key, es)); - } + dependentResourcesByName.forEach(dependentResource -> { + Optional eventSource = dependentResource.eventSource(context); + eventSource.ifPresent(eventSourceManager::registerEventSource); }); // resolve event sources referenced by name for dependents that reuse an existing event source final Map> unresolvable = new HashMap<>(size); - dependentResourcesByName.values().stream() + dependentResourcesByName.stream() .filter(EventSourceReferencer.class::isInstance) .map(EventSourceReferencer.class::cast) .forEach(dr -> { @@ -454,4 +449,30 @@ public ExecutorServiceManager getExecutorServiceManager() { public EventSourceContext

eventSourceContext() { return eventSourceContext; } + + public void reconcileManagedWorkflow(P primary, Context

context) { + if (!managedWorkflow.isEmpty()) { + var res = managedWorkflow.reconcile(primary, context); + ((DefaultManagedWorkflowAndDependentResourceContext) context + .managedWorkflowAndDependentResourceContext()) + .setWorkflowExecutionResult(res); + } + } + + public WorkflowCleanupResult cleanupManagedWorkflow(P resource, Context

context) { + if (managedWorkflow.hasCleaner()) { + var workflowCleanupResult = managedWorkflow.cleanup(resource, context); + ((DefaultManagedWorkflowAndDependentResourceContext) context + .managedWorkflowAndDependentResourceContext()) + .setWorkflowCleanupResult(workflowCleanupResult); + + return workflowCleanupResult; + } else { + return null; + } + } + + public boolean isWorkflowExplicitInvocation() { + return explicitWorkflowInvocation; + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java index ea1e020bfb..5b923b8dca 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java @@ -1,6 +1,7 @@ package io.javaoperatorsdk.operator.processing.dependent; import java.util.Optional; +import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -8,16 +9,16 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.Ignore; -import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.api.reconciler.dependent.NameSetter; import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; import io.javaoperatorsdk.operator.processing.dependent.Matcher.Result; import io.javaoperatorsdk.operator.processing.event.ResourceID; @Ignore public abstract class AbstractDependentResource - implements DependentResource { + implements DependentResource, NameSetter { private static final Logger log = LoggerFactory.getLogger(AbstractDependentResource.class); private final boolean creatable = this instanceof Creator; @@ -26,17 +27,23 @@ public abstract class AbstractDependentResource protected Creator creator; protected Updater updater; - private ResourceDiscriminator resourceDiscriminator; private final DependentResourceReconciler dependentResourceReconciler; + protected String name; + @SuppressWarnings({"unchecked"}) protected AbstractDependentResource() { + this(null); + } + + protected AbstractDependentResource(String name) { creator = creatable ? (Creator) this : null; updater = updatable ? (Updater) this : null; dependentResourceReconciler = this instanceof BulkDependentResource ? new BulkDependentResourceReconciler<>((BulkDependentResource) this) : new SingleDependentResourceReconciler<>(this); + this.name = name == null ? DependentResource.defaultNameFor(this.getClass()) : name; } /** @@ -53,7 +60,7 @@ public ReconcileResult reconcile(P primary, Context

context) { } protected ReconcileResult reconcile(P primary, R actualResource, Context

context) { - if (creatable || updatable) { + if (creatable() || updatable()) { if (actualResource == null) { if (creatable) { var desired = desired(primary, context); @@ -63,7 +70,7 @@ protected ReconcileResult reconcile(P primary, R actualResource, Context

c return ReconcileResult.resourceCreated(createdResource); } } else { - if (updatable) { + if (updatable()) { final Matcher.Result match = match(actualResource, primary, context); if (!match.matched()) { final var desired = match.computedDesired().orElseGet(() -> desired(primary, context)); @@ -89,8 +96,37 @@ protected ReconcileResult reconcile(P primary, R actualResource, Context

c @Override public Optional getSecondaryResource(P primary, Context

context) { - return resourceDiscriminator == null ? context.getSecondaryResource(resourceType()) - : resourceDiscriminator.distinguish(resourceType(), primary, context); + + var secondaryResources = context.getSecondaryResources(resourceType()); + if (secondaryResources.isEmpty()) { + return Optional.empty(); + } else { + return selectManagedSecondaryResource(secondaryResources, primary, context); + } + + } + + /** + * Selects the actual secondary resource matching the desired state derived from the primary + * resource when several resources of the same type are found in the context. This method allows + * for optimized implementations in subclasses since this default implementation will check each + * secondary candidates for equality with the specified desired state, which might end up costly. + * + * @param secondaryResources to select the target resource from + * + * @return the matching secondary resource or {@link Optional#empty()} if none matches + * @throws IllegalStateException if more than one candidate is found, in which case some other + * mechanism might be necessary to distinguish between candidate secondary resources + */ + protected Optional selectManagedSecondaryResource(Set secondaryResources, P primary, + Context

context) { + R desired = desired(primary, context); + var targetResources = secondaryResources.stream().filter(r -> r.equals(desired)).toList(); + if (targetResources.size() > 1) { + throw new IllegalStateException( + "More than one secondary resource related to primary: " + targetResources); + } + return targetResources.isEmpty() ? Optional.empty() : Optional.of(targetResources.get(0)); } private void throwIfNull(R desired, P primary, String descriptor) { @@ -158,11 +194,6 @@ protected void handleDelete(P primary, R secondary, Context

context) { "handleDelete method must be implemented if Deleter trait is supported"); } - public void setResourceDiscriminator( - ResourceDiscriminator resourceDiscriminator) { - this.resourceDiscriminator = resourceDiscriminator; - } - protected boolean isCreatable() { return creatable; } @@ -176,4 +207,21 @@ protected boolean isUpdatable() { public boolean isDeletable() { return deletable; } + + @Override + public String name() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + protected boolean creatable() { + return creatable; + } + + protected boolean updatable() { + return updatable; + } } 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 6562afd09f..6de936a5f8 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 @@ -11,14 +11,14 @@ import io.javaoperatorsdk.operator.api.reconciler.dependent.RecentOperationCacheFiller; import io.javaoperatorsdk.operator.processing.event.EventSourceRetriever; import io.javaoperatorsdk.operator.processing.event.ResourceID; -import io.javaoperatorsdk.operator.processing.event.source.ResourceEventSource; +import io.javaoperatorsdk.operator.processing.event.source.EventSource; import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.OnDeleteFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter; @Ignore -public abstract class AbstractEventSourceHolderDependentResource> +public abstract class AbstractEventSourceHolderDependentResource> extends AbstractDependentResource implements EventSourceReferencer

{ private T eventSource; @@ -31,6 +31,11 @@ public abstract class AbstractEventSourceHolderDependentResource resourceType) { + this(resourceType, null); + } + + protected AbstractEventSourceHolderDependentResource(Class resourceType, String name) { + super(name); this.resourceType = resourceType; } @@ -62,7 +67,7 @@ public synchronized Optional eventSource(EventSourceContext

context) { public void resolveEventSource(EventSourceRetriever

eventSourceRetriever) { if (eventSourceNameToUse != null && eventSource == null) { final var source = - eventSourceRetriever.getResourceEventSourceFor(resourceType(), eventSourceNameToUse); + eventSourceRetriever.getEventSourceFor(resourceType(), eventSourceNameToUse); if (source == null) { throw new EventSourceNotFoundException(eventSourceNameToUse); } 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 ef825ef71f..acb6cb99d3 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 @@ -5,10 +5,10 @@ import io.javaoperatorsdk.operator.api.reconciler.dependent.RecentOperationCacheFiller; import io.javaoperatorsdk.operator.processing.event.EventSourceRetriever; import io.javaoperatorsdk.operator.processing.event.ResourceID; -import io.javaoperatorsdk.operator.processing.event.source.ResourceEventSource; +import io.javaoperatorsdk.operator.processing.event.source.EventSource; import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; -public abstract class AbstractExternalDependentResource> +public abstract class AbstractExternalDependentResource> extends AbstractEventSourceHolderDependentResource { private final boolean isDependentResourceWithExplicitState = @@ -34,7 +34,7 @@ public void resolveEventSource(EventSourceRetriever

eventSourceRetriever) { final var eventSourceName = (String) dependentResourceWithExplicitState .eventSourceName().orElse(null); externalStateEventSource = (InformerEventSource) eventSourceRetriever - .getResourceEventSourceFor(dependentResourceWithExplicitState.stateResourceClass(), + .getEventSourceFor(dependentResourceWithExplicitState.stateResourceClass(), eventSourceName); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResource.java index a86b6ee0e8..313d7115c9 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResource.java @@ -13,8 +13,7 @@ * {@link Creator} and {@link Deleter} interfaces out of the box. A concrete dependent resource can * implement additionally also {@link Updater}. */ -public interface BulkDependentResource - extends Creator, Deleter

{ +public interface BulkDependentResource { /** * Retrieves a map of desired secondary resources associated with the specified primary resource, @@ -26,7 +25,10 @@ public interface BulkDependentResource * @return a Map associating desired secondary resources with the specified primary via arbitrary * identifiers */ - Map desiredResources(P primary, Context

context); + default Map desiredResources(P primary, Context

context) { + throw new IllegalStateException( + "Implement desiredResources in case a non read-only bulk dependent resource"); + } /** * Retrieves the actual secondary resources currently existing on the server and associated with diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResourceReconciler.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResourceReconciler.java index e740ef3773..1ed36edaa2 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResourceReconciler.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResourceReconciler.java @@ -24,18 +24,26 @@ class BulkDependentResourceReconciler @Override public ReconcileResult reconcile(P primary, Context

context) { - final var desiredResources = bulkDependentResource.desiredResources(primary, context); + Map actualResources = bulkDependentResource.getSecondaryResources(primary, context); + if (!(bulkDependentResource instanceof Creator) + && !(bulkDependentResource instanceof Deleter) + && !(bulkDependentResource instanceof Updater)) { + return ReconcileResult + .aggregatedResult(actualResources.values().stream().map(ReconcileResult::noOperation) + .toList()); + } - // remove existing resources that are not needed anymore according to the primary state - deleteExtraResources(desiredResources.keySet(), actualResources, primary, context); + final var desiredResources = bulkDependentResource.desiredResources(primary, context); + + if (bulkDependentResource instanceof Deleter) { + // remove existing resources that are not needed anymore according to the primary state + deleteExtraResources(desiredResources.keySet(), actualResources, primary, context); + } final List> results = new ArrayList<>(desiredResources.size()); - final var updatable = bulkDependentResource instanceof Updater; desiredResources.forEach((key, value) -> { - final var instance = - updatable ? new UpdatableBulkDependentResourceInstance<>(bulkDependentResource, value) - : new BulkDependentResourceInstance<>(bulkDependentResource, value); + final var instance = new BulkDependentResourceInstance<>(bulkDependentResource, value); results.add(instance.reconcile(primary, actualResources.get(key), context)); }); @@ -67,7 +75,7 @@ private void deleteExtraResources(Set expectedKeys, @Ignore private static class BulkDependentResourceInstance extends AbstractDependentResource - implements Creator, Deleter

{ + implements Creator, Deleter

, Updater { private final BulkDependentResource bulkDependentResource; private final R desired; @@ -112,26 +120,24 @@ public Class resourceType() { return asAbstractDependentResource().resourceType(); } - @Override + @SuppressWarnings("unchecked") public R create(R desired, P primary, Context

context) { - return bulkDependentResource.create(desired, primary, context); + return ((Creator) bulkDependentResource).create(desired, primary, context); } - } - /** - * Makes sure that the instance implements Updater if its precursor does as well. - * - * @param - * @param

- */ - @Ignore - private static class UpdatableBulkDependentResourceInstance - extends BulkDependentResourceInstance implements Updater { + @Override + protected boolean isCreatable() { + return bulkDependentResource instanceof Creator; + } - private UpdatableBulkDependentResourceInstance( - BulkDependentResource bulkDependentResource, - R desired) { - super(bulkDependentResource, desired); + @Override + protected boolean isUpdatable() { + return bulkDependentResource instanceof Updater; + } + + @Override + public boolean isDeletable() { + return bulkDependentResource instanceof Deleter; } } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/CRUDBulkDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/CRUDBulkDependentResource.java new file mode 100644 index 0000000000..aa650bf1b5 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/CRUDBulkDependentResource.java @@ -0,0 +1,11 @@ +package io.javaoperatorsdk.operator.processing.dependent; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; + +public interface CRUDBulkDependentResource + extends BulkDependentResource, + Creator, + BulkUpdater, + Deleter

{ +} 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 6355ec39c7..659b8b4720 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 @@ -1,5 +1,7 @@ package io.javaoperatorsdk.operator.processing.dependent.external; +import java.time.Duration; + import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.Ignore; import io.javaoperatorsdk.operator.processing.dependent.AbstractExternalDependentResource; @@ -11,23 +13,23 @@ public abstract class AbstractPollingDependentResource extends AbstractExternalDependentResource> implements CacheKeyMapper { - public static final int DEFAULT_POLLING_PERIOD = 5000; - private long pollingPeriod; + public static final Duration DEFAULT_POLLING_PERIOD = Duration.ofMillis(5000); + private Duration pollingPeriod; protected AbstractPollingDependentResource(Class resourceType) { this(resourceType, DEFAULT_POLLING_PERIOD); } - public AbstractPollingDependentResource(Class resourceType, long pollingPeriod) { + public AbstractPollingDependentResource(Class resourceType, Duration pollingPeriod) { super(resourceType); this.pollingPeriod = pollingPeriod; } - public void setPollingPeriod(long pollingPeriod) { + public void setPollingPeriod(Duration pollingPeriod) { this.pollingPeriod = pollingPeriod; } - public long getPollingPeriod() { + public Duration getPollingPeriod() { return pollingPeriod; } 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 affc63cfd3..581698ffd6 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 @@ -6,6 +6,7 @@ import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; import io.javaoperatorsdk.operator.api.reconciler.Ignore; import io.javaoperatorsdk.operator.processing.event.source.ExternalResourceCachingEventSource; +import io.javaoperatorsdk.operator.processing.event.source.polling.PerResourcePollingConfigurationBuilder; import io.javaoperatorsdk.operator.processing.event.source.polling.PerResourcePollingEventSource; @Ignore @@ -18,15 +19,18 @@ public PerResourcePollingDependentResource(Class resourceType) { super(resourceType); } - public PerResourcePollingDependentResource(Class resourceType, long pollingPeriod) { + public PerResourcePollingDependentResource(Class resourceType, Duration pollingPeriod) { super(resourceType, pollingPeriod); } @Override protected ExternalResourceCachingEventSource createEventSource( EventSourceContext

context) { - return new PerResourcePollingEventSource<>(this, context, - Duration.ofMillis(getPollingPeriod()), resourceType(), this); - } + return new PerResourcePollingEventSource<>(name(), resourceType(), context, + new PerResourcePollingConfigurationBuilder<>( + this, getPollingPeriod()) + .withCacheKeyMapper(this) + .build()); + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/external/PollingDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/external/PollingDependentResource.java index 3df1390d69..519771d82d 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/external/PollingDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/external/PollingDependentResource.java @@ -1,10 +1,13 @@ package io.javaoperatorsdk.operator.processing.dependent.external; +import java.time.Duration; + import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; import io.javaoperatorsdk.operator.api.reconciler.Ignore; import io.javaoperatorsdk.operator.processing.event.source.CacheKeyMapper; import io.javaoperatorsdk.operator.processing.event.source.ExternalResourceCachingEventSource; +import io.javaoperatorsdk.operator.processing.event.source.polling.PollingConfiguration; import io.javaoperatorsdk.operator.processing.event.source.polling.PollingEventSource; @Ignore @@ -19,7 +22,7 @@ public PollingDependentResource(Class resourceType, CacheKeyMapper cacheKe this.cacheKeyMapper = cacheKeyMapper; } - public PollingDependentResource(Class resourceType, long pollingPeriod, + public PollingDependentResource(Class resourceType, Duration pollingPeriod, CacheKeyMapper cacheKeyMapper) { super(resourceType, pollingPeriod); this.cacheKeyMapper = cacheKeyMapper; @@ -28,7 +31,8 @@ public PollingDependentResource(Class resourceType, long pollingPeriod, @Override protected ExternalResourceCachingEventSource createEventSource( EventSourceContext

context) { - return new PollingEventSource<>(this, getPollingPeriod(), resourceType(), cacheKeyMapper); + return new PollingEventSource<>(name(), resourceType(), + new PollingConfiguration<>(this, getPollingPeriod(), cacheKeyMapper)); } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesDependentResource.java index 98f2346577..ee27445231 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesDependentResource.java @@ -2,13 +2,14 @@ import io.fabric8.kubernetes.api.model.GenericKubernetesResource; import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.api.config.Utils; import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; import io.javaoperatorsdk.operator.processing.GroupVersionKind; public class GenericKubernetesDependentResource

extends KubernetesDependentResource { - private GroupVersionKind groupVersionKind; + private final GroupVersionKind groupVersionKind; public GenericKubernetesDependentResource(GroupVersionKind groupVersionKind) { super(GenericKubernetesResource.class); @@ -16,9 +17,16 @@ public GenericKubernetesDependentResource(GroupVersionKind groupVersionKind) { } protected InformerConfiguration.InformerConfigurationBuilder informerConfigurationBuilder() { - return InformerConfiguration.from(groupVersionKind); + return InformerConfiguration.from(groupVersionKind, getPrimaryResourceType()); } + @SuppressWarnings("unchecked") + @Override + protected Class

getPrimaryResourceType() { + return (Class

) Utils.getFirstTypeArgumentFromExtendedClass(getClass()); + } + + @SuppressWarnings("unused") public GroupVersionKind getGroupVersionKind() { return groupVersionKind; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcher.java index c71da5d5ad..05ee05b036 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcher.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcher.java @@ -58,7 +58,7 @@ static Matcher matcherFor( @Override public Result match(R actualResource, P primary, Context

context) { var desired = dependentResource.desired(primary, context); - return match(desired, actualResource, false, false, false, context); + return match(desired, actualResource, false, false, context); } /** @@ -67,9 +67,6 @@ public Result match(R actualResource, P primary, Context

context) { * * @param desired the desired resource * @param actualResource the actual resource - * @param considerLabelsAndAnnotations {@code true} if labels and annotations will be checked for - * equality, {@code false} otherwise (meaning that metadata changes will be ignored for - * matching purposes) * @param labelsAndAnnotationsEquality if true labels and annotation match exactly in the actual * and desired state if false, additional elements are allowed in actual annotations. * Considered only if considerLabelsAndAnnotations is true. @@ -89,9 +86,9 @@ public Result match(R actualResource, P primary, Context

context) { */ public static Result match(R desired, R actualResource, - boolean considerLabelsAndAnnotations, boolean labelsAndAnnotationsEquality, + boolean labelsAndAnnotationsEquality, boolean valuesEquality, Context

context) { - return match(desired, actualResource, considerLabelsAndAnnotations, + return match(desired, actualResource, labelsAndAnnotationsEquality, valuesEquality, context, EMPTY_ARRAY); } @@ -101,9 +98,6 @@ public static Result match(R d * * @param desired the desired resource * @param actualResource the actual resource - * @param considerLabelsAndAnnotations {@code true} if labels and annotations will be checked for - * equality, {@code false} otherwise (meaning that metadata changes will be ignored for - * matching purposes) * @param labelsAndAnnotationsEquality if true labels and annotation match exactly in the actual * and desired state if false, additional elements are allowed in actual annotations. * Considered only if considerLabelsAndAnnotations is true. @@ -116,9 +110,9 @@ public static Result match(R d */ public static Result match(R desired, R actualResource, - boolean considerLabelsAndAnnotations, boolean labelsAndAnnotationsEquality, + boolean labelsAndAnnotationsEquality, Context

context, String... ignorePaths) { - return match(desired, actualResource, considerLabelsAndAnnotations, + return match(desired, actualResource, labelsAndAnnotationsEquality, false, context, ignorePaths); } @@ -133,9 +127,6 @@ public static Result match(R d * matches the desired state or not * @param primary the primary resource from which we want to compute the desired state * @param context the {@link Context} instance within which this method is called - * @param considerLabelsAndAnnotations {@code true} to consider the metadata of the actual - * resource when determining if it matches the desired state, {@code false} if matching - * should occur only considering the spec of the resources * @param labelsAndAnnotationsEquality if true labels and annotation match exactly in the actual * and desired state if false, additional elements are allowed in actual annotations. * Considered only if considerLabelsAndAnnotations is true. @@ -150,28 +141,28 @@ public static Result match(R d */ public static Result match( KubernetesDependentResource dependentResource, R actualResource, P primary, - Context

context, boolean considerLabelsAndAnnotations, + Context

context, boolean labelsAndAnnotationsEquality, String... ignorePaths) { final var desired = dependentResource.desired(primary, context); - return match(desired, actualResource, considerLabelsAndAnnotations, + return match(desired, actualResource, labelsAndAnnotationsEquality, context, ignorePaths); } public static Result match( KubernetesDependentResource dependentResource, R actualResource, P primary, - Context

context, boolean considerLabelsAndAnnotations, + Context

context, + boolean specEquality, boolean labelsAndAnnotationsEquality, - boolean specEquality) { + String... ignorePaths) { final var desired = dependentResource.desired(primary, context); - return match(desired, actualResource, considerLabelsAndAnnotations, - labelsAndAnnotationsEquality, specEquality, context); + return match(desired, actualResource, + labelsAndAnnotationsEquality, specEquality, context, ignorePaths); } public static Result match(R desired, - R actualResource, - boolean considerMetadata, boolean labelsAndAnnotationsEquality, boolean valuesEquality, + R actualResource, boolean labelsAndAnnotationsEquality, boolean valuesEquality, Context

context, String... ignoredPaths) { final List ignoreList = @@ -195,8 +186,7 @@ public static Result match(R d matched = match(valuesEquality, node, ignoreList); } else if (nodeIsChildOf(node, List.of(METADATA))) { // conditionally consider labels and annotations - if (considerMetadata - && nodeIsChildOf(node, List.of(METADATA_LABELS, METADATA_ANNOTATIONS))) { + if (nodeIsChildOf(node, List.of(METADATA_LABELS, METADATA_ANNOTATIONS))) { matched = match(labelsAndAnnotationsEquality, node, Collections.emptyList()); } } else if (!nodeIsChildOf(node, IGNORED_FIELDS)) { @@ -227,20 +217,4 @@ static String getPath(JsonNode n) { return n.get(PATH).asText(); } - @Deprecated(forRemoval = true) - public static Result match( - KubernetesDependentResource dependentResource, R actualResource, P primary, - Context

context, boolean considerLabelsAndAnnotations, boolean specEquality) { - final var desired = dependentResource.desired(primary, context); - return match(desired, actualResource, considerLabelsAndAnnotations, specEquality, context); - } - - @Deprecated(forRemoval = true) - public static Result match( - KubernetesDependentResource dependentResource, R actualResource, P primary, - Context

context, boolean considerLabelsAndAnnotations, String... ignorePaths) { - final var desired = dependentResource.desired(primary, context); - return match(desired, actualResource, considerLabelsAndAnnotations, true, context, ignorePaths); - } - } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependent.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependent.java index eb4c9cf9b0..572741dcbd 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependent.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependent.java @@ -6,7 +6,6 @@ import java.lang.annotation.Target; import io.javaoperatorsdk.operator.api.reconciler.Constants; -import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.OnDeleteFilter; @@ -70,8 +69,6 @@ */ Class genericFilter() default GenericFilter.class; - Class resourceDiscriminator() default ResourceDiscriminator.class; - /** * Creates the resource only if did not exist before, this applies only if SSA is used. */ diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentConverter.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentConverter.java index 7a434aecf1..493f0b0146 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentConverter.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentConverter.java @@ -8,7 +8,6 @@ import io.javaoperatorsdk.operator.api.config.Utils; import io.javaoperatorsdk.operator.api.config.dependent.ConfigurationConverter; import io.javaoperatorsdk.operator.api.reconciler.Constants; -import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.OnDeleteFilter; @@ -33,7 +32,6 @@ public KubernetesDependentResourceConfig configFrom(KubernetesDependent confi OnUpdateFilter onUpdateFilter = null; OnDeleteFilter onDeleteFilter = null; GenericFilter genericFilter = null; - ResourceDiscriminator resourceDiscriminator = null; Boolean useSSA = null; if (configAnnotation != null) { if (!Arrays.equals(KubernetesDependent.DEFAULT_NAMESPACES, configAnnotation.namespaces())) { @@ -54,9 +52,6 @@ public KubernetesDependentResourceConfig configFrom(KubernetesDependent confi genericFilter = Utils.instantiate(configAnnotation.genericFilter(), GenericFilter.class, context); - resourceDiscriminator = - Utils.instantiate(configAnnotation.resourceDiscriminator(), ResourceDiscriminator.class, - context); createResourceOnlyIfNotExistingWithSSA = configAnnotation.createResourceOnlyIfNotExistingWithSSA(); useSSA = configAnnotation.useSSA().asBoolean(); @@ -64,6 +59,6 @@ public KubernetesDependentResourceConfig configFrom(KubernetesDependent confi return new KubernetesDependentResourceConfig(namespaces, labelSelector, configuredNS, createResourceOnlyIfNotExistingWithSSA, - resourceDiscriminator, useSSA, onAddFilter, onUpdateFilter, onDeleteFilter, genericFilter); + useSSA, onAddFilter, onUpdateFilter, onDeleteFilter, genericFilter); } } 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 b804b88a30..f10e4a1af6 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 @@ -1,6 +1,7 @@ package io.javaoperatorsdk.operator.processing.dependent.kubernetes; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -8,9 +9,11 @@ import org.slf4j.LoggerFactory; import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.Namespaced; import io.fabric8.kubernetes.client.dsl.Resource; import io.javaoperatorsdk.operator.OperatorException; import io.javaoperatorsdk.operator.ReconcilerUtils; +import io.javaoperatorsdk.operator.api.config.Utils; import io.javaoperatorsdk.operator.api.config.dependent.Configured; import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; import io.javaoperatorsdk.operator.api.reconciler.Constants; @@ -35,30 +38,39 @@ public abstract class KubernetesDependentResource> { private static final Logger log = LoggerFactory.getLogger(KubernetesDependentResource.class); - private final ResourceUpdaterMatcher updaterMatcher; private final boolean garbageCollected = this instanceof GarbageCollected; + private final boolean usingCustomResourceUpdateMatcher = this instanceof ResourceUpdaterMatcher; + @SuppressWarnings("unchecked") + private final ResourceUpdaterMatcher updaterMatcher = usingCustomResourceUpdateMatcher + ? (ResourceUpdaterMatcher) this + : GenericResourceUpdaterMatcher.updaterMatcherFor(resourceType()); + private final boolean clustered; private KubernetesDependentResourceConfig kubernetesDependentResourceConfig; - private final boolean usingCustomResourceUpdateMatcher; - - @SuppressWarnings("unchecked") public KubernetesDependentResource(Class resourceType) { - super(resourceType); + this(resourceType, null); + } + + public KubernetesDependentResource(Class resourceType, String name) { + super(resourceType, name); + final var primaryResourceType = getPrimaryResourceType(); + clustered = !Namespaced.class.isAssignableFrom(primaryResourceType); + } - usingCustomResourceUpdateMatcher = this instanceof ResourceUpdaterMatcher; - updaterMatcher = usingCustomResourceUpdateMatcher - ? (ResourceUpdaterMatcher) this - : GenericResourceUpdaterMatcher.updaterMatcherFor(resourceType); + protected KubernetesDependentResource(Class resourceType, String name, + boolean primaryIsClustered) { + super(resourceType, name); + clustered = primaryIsClustered; } @SuppressWarnings("unchecked") + protected Class

getPrimaryResourceType() { + return (Class

) Utils.getTypeArgumentFromExtendedClassByIndex(getClass(), 1); + } + @Override public void configureWith(KubernetesDependentResourceConfig config) { this.kubernetesDependentResourceConfig = config; - var discriminator = kubernetesDependentResourceConfig.getResourceDiscriminator(); - if (discriminator != null) { - setResourceDiscriminator(discriminator); - } } private void configureWith(String labelSelector, Set namespaces, @@ -74,12 +86,12 @@ private void configureWith(String labelSelector, Set namespaces, .withNamespaces(namespaces, inheritNamespacesOnChange) .build(); - configureWith(new InformerEventSource<>(ic, context)); + configureWith(new InformerEventSource<>(name(), ic, context)); } // just to seamlessly handle GenericKubernetesDependentResource protected InformerConfiguration.InformerConfigurationBuilder informerConfigurationBuilder() { - return InformerConfiguration.from(resourceType()); + return InformerConfiguration.from(resourceType(), getPrimaryResourceType()); } @SuppressWarnings("unchecked") @@ -87,7 +99,7 @@ private SecondaryToPrimaryMapper getSecondaryToPrimaryMapper() { if (this instanceof SecondaryToPrimaryMapper) { return (SecondaryToPrimaryMapper) this; } else if (garbageCollected) { - return Mappers.fromOwnerReferences(false); + return Mappers.fromOwnerReferences(getPrimaryResourceType(), clustered); } else if (useNonOwnerRefBasedSecondaryToPrimaryMapping()) { return Mappers.fromDefaultAnnotations(); } else { @@ -253,10 +265,6 @@ protected InformerEventSource createEventSource(EventSourceContext

cont onUpdateFilter = kubernetesDependentResourceConfig.onUpdateFilter(); onDeleteFilter = kubernetesDependentResourceConfig.onDeleteFilter(); genericFilter = kubernetesDependentResourceConfig.genericFilter(); - var discriminator = kubernetesDependentResourceConfig.getResourceDiscriminator(); - if (discriminator != null) { - setResourceDiscriminator(discriminator); - } configureWith(kubernetesDependentResourceConfig.labelSelector(), kubernetesDependentResourceConfig.namespaces(), !kubernetesDependentResourceConfig.wereNamespacesConfigured(), context); @@ -289,6 +297,29 @@ protected void addSecondaryToPrimaryMapperAnnotations(R desired, P primary, Stri } } + @Override + protected Optional selectManagedSecondaryResource(Set secondaryResources, P primary, + Context

context) { + ResourceID managedResourceID = managedSecondaryResourceID(primary, context); + return secondaryResources.stream() + .filter(r -> r.getMetadata().getName().equals(managedResourceID.getName()) && + Objects.equals(r.getMetadata().getNamespace(), + managedResourceID.getNamespace().orElse(null))) + .findFirst(); + } + + /** + * Override this method in order to optimize and not compute the desired when selecting the target + * secondary resource. Simply, a static ResourceID can be returned. + * + * @param primary resource + * @param context of current reconciliation + * @return id of the target managed resource + */ + protected ResourceID managedSecondaryResourceID(P primary, Context

context) { + return ResourceID.fromResource(desired(primary, context)); + } + protected boolean addOwnerReference() { return garbageCollected; } 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 9b3838831d..e302ad437a 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 @@ -4,7 +4,6 @@ import java.util.Set; import io.javaoperatorsdk.operator.api.reconciler.Constants; -import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.OnDeleteFilter; @@ -20,7 +19,6 @@ public class KubernetesDependentResourceConfig { private String labelSelector; private final boolean namespacesWereConfigured; private final boolean createResourceOnlyIfNotExistingWithSSA; - private final ResourceDiscriminator resourceDiscriminator; private final Boolean useSSA; private final OnAddFilter onAddFilter; @@ -31,7 +29,7 @@ public class KubernetesDependentResourceConfig { public KubernetesDependentResourceConfig() { this(Constants.SAME_AS_CONTROLLER_NAMESPACES_SET, NO_VALUE_SET, true, DEFAULT_CREATE_RESOURCE_ONLY_IF_NOT_EXISTING_WITH_SSA, - null, null, null, + null, null, null, null, null); } @@ -39,7 +37,6 @@ public KubernetesDependentResourceConfig(Set namespaces, String labelSelector, boolean configuredNS, boolean createResourceOnlyIfNotExistingWithSSA, - ResourceDiscriminator resourceDiscriminator, Boolean useSSA, OnAddFilter onAddFilter, OnUpdateFilter onUpdateFilter, @@ -52,25 +49,9 @@ public KubernetesDependentResourceConfig(Set namespaces, this.onUpdateFilter = onUpdateFilter; this.onDeleteFilter = onDeleteFilter; this.genericFilter = genericFilter; - this.resourceDiscriminator = resourceDiscriminator; this.useSSA = useSSA; } - // use builder instead - @Deprecated(forRemoval = true) - public KubernetesDependentResourceConfig(Set namespaces, String labelSelector) { - this(namespaces, labelSelector, true, DEFAULT_CREATE_RESOURCE_ONLY_IF_NOT_EXISTING_WITH_SSA, - null, null, null, - null, null, null); - } - - // use builder instead - @Deprecated(forRemoval = true) - public KubernetesDependentResourceConfig setLabelSelector(String labelSelector) { - this.labelSelector = labelSelector; - return this; - } - public Set namespaces() { return namespaces; } @@ -104,11 +85,6 @@ public GenericFilter genericFilter() { return genericFilter; } - @SuppressWarnings("rawtypes") - public ResourceDiscriminator getResourceDiscriminator() { - return resourceDiscriminator; - } - @SuppressWarnings("unused") protected void setNamespaces(Set namespaces) { if (!wereNamespacesConfigured() && namespaces != null && !namespaces.isEmpty()) { diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceConfigBuilder.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceConfigBuilder.java index a18d8b8a41..854ec7a56f 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceConfigBuilder.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceConfigBuilder.java @@ -3,7 +3,6 @@ import java.util.Set; import io.javaoperatorsdk.operator.api.reconciler.Constants; -import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter; import io.javaoperatorsdk.operator.processing.event.source.filter.OnDeleteFilter; @@ -14,7 +13,6 @@ public final class KubernetesDependentResourceConfigBuilder { private Set namespaces = Constants.SAME_AS_CONTROLLER_NAMESPACES_SET; private String labelSelector; private boolean createResourceOnlyIfNotExistingWithSSA; - private ResourceDiscriminator resourceDiscriminator; private Boolean useSSA; private OnAddFilter onAddFilter; private OnUpdateFilter onUpdateFilter; @@ -43,12 +41,6 @@ public KubernetesDependentResourceConfigBuilder withCreateResourceOnlyIfNotEx return this; } - public KubernetesDependentResourceConfigBuilder withResourceDiscriminator( - ResourceDiscriminator resourceDiscriminator) { - this.resourceDiscriminator = resourceDiscriminator; - return this; - } - public KubernetesDependentResourceConfigBuilder withUseSSA(Boolean useSSA) { this.useSSA = useSSA; return this; @@ -80,7 +72,7 @@ public KubernetesDependentResourceConfigBuilder withGenericFilter( public KubernetesDependentResourceConfig build() { return new KubernetesDependentResourceConfig<>(namespaces, labelSelector, namespaces != Constants.SAME_AS_CONTROLLER_NAMESPACES_SET, - createResourceOnlyIfNotExistingWithSSA, resourceDiscriminator, useSSA, onAddFilter, + createResourceOnlyIfNotExistingWithSSA, useSSA, onAddFilter, onUpdateFilter, onDeleteFilter, genericFilter); } } 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 bcfaa52d1a..f79f12b49f 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 @@ -1,8 +1,17 @@ package io.javaoperatorsdk.operator.processing.dependent.kubernetes; -import java.util.*; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; -import java.util.stream.Collectors; +import java.util.Optional; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,7 +31,7 @@ *

* The basis of algorithm is to extract the fields managed we convert resources to Map/List * composition. The actual resource (from the server) is pruned, all the fields which are not - * mentioed in managedFields of the target manager is removed. Some irrelevant fields are also + * mentioned in managedFields of the target manager is removed. Some irrelevant fields are also * removed from desired. And the two resulted Maps are compared for equality. The implementation is * a bit nasty since have to deal with some specific cases of managedFields format. *

@@ -104,10 +113,8 @@ public boolean matches(R actual, R desired, Context context) { /** * Correct for known issue with SSA */ - @SuppressWarnings("unchecked") private void sanitizeState(R actual, R desired, Map actualMap) { - if (desired instanceof StatefulSet) { - StatefulSet desiredStatefulSet = (StatefulSet) desired; + if (desired instanceof StatefulSet desiredStatefulSet) { StatefulSet actualStatefulSet = (StatefulSet) actual; int claims = desiredStatefulSet.getSpec().getVolumeClaimTemplates().size(); if (claims == actualStatefulSet.getSpec().getVolumeClaimTemplates().size()) { @@ -321,12 +328,12 @@ private static java.util.Map.Entry> selectListEntry } if (possibleTargets.isEmpty()) { throw new IllegalStateException("Cannot find list element for key:" + key + ", in map: " - + values.stream().map(Map::keySet).collect(Collectors.toList())); + + values.stream().map(Map::keySet).toList()); } if (possibleTargets.size() > 1) { throw new IllegalStateException( "More targets found in list element for key:" + key + ", in map: " - + values.stream().map(Map::keySet).collect(Collectors.toList())); + + values.stream().map(Map::keySet).toList()); } final var finalIndex = index; return new AbstractMap.SimpleEntry<>(finalIndex, possibleTargets.get(0)); @@ -339,7 +346,7 @@ private Optional checkIfFieldManagerExists(R actual, String // field manager name. .filter( f -> f.getManager().equals(fieldManager) && f.getOperation().equals(APPLY_OPERATION)) - .collect(Collectors.toList()); + .toList(); if (targetManagedFields.isEmpty()) { log.debug("No field manager exists for resource {} with name: {} and operation Apply ", actual.getKind(), diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/updatermatcher/GenericResourceUpdaterMatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/updatermatcher/GenericResourceUpdaterMatcher.java index 43d0b2fedf..16b72d6dce 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/updatermatcher/GenericResourceUpdaterMatcher.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/updatermatcher/GenericResourceUpdaterMatcher.java @@ -43,7 +43,7 @@ public R updateResource(R actual, R desired, Context context) { @Override public boolean matches(R actual, R desired, Context context) { - return GenericKubernetesResourceMatcher.match(desired, actual, true, + return GenericKubernetesResourceMatcher.match(desired, actual, false, false, context).matched(); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutor.java index 56e5e17d07..c22bf9d666 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutor.java @@ -18,7 +18,7 @@ import io.javaoperatorsdk.operator.processing.event.ResourceID; @SuppressWarnings("rawtypes") -public abstract class AbstractWorkflowExecutor

{ +abstract class AbstractWorkflowExecutor

{ protected final Workflow

workflow; protected final P primary; @@ -133,17 +133,15 @@ protected void registerOrDeregisterEventSourceBasedOnActivation( boolean activationConditionMet, DependentResourceNode dependentResourceNode) { if (dependentResourceNode.getActivationCondition().isPresent()) { + final var dr = dependentResourceNode.getDependentResource(); + final var eventSourceRetriever = context.eventSourceRetriever(); if (activationConditionMet) { var eventSource = - dependentResourceNode.getDependentResource().eventSource(context.eventSourceRetriever() - .eventSourceContextForDynamicRegistration()); + dr.eventSource(eventSourceRetriever.eventSourceContextForDynamicRegistration()); var es = eventSource.orElseThrow(); - context.eventSourceRetriever() - .dynamicallyRegisterEventSource(dependentResourceNode.getName(), es); - + eventSourceRetriever.dynamicallyRegisterEventSource(es); } else { - context.eventSourceRetriever() - .dynamicallyDeRegisterEventSource(dependentResourceNode.getName()); + eventSourceRetriever.dynamicallyDeRegisterEventSource(dr.name()); } } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/CRDPresentActivationCondition.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/CRDPresentActivationCondition.java new file mode 100644 index 0000000000..6627c62d89 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/CRDPresentActivationCondition.java @@ -0,0 +1,91 @@ +package io.javaoperatorsdk.operator.processing.dependent.workflow; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.processing.expiration.Expiration; +import io.javaoperatorsdk.operator.processing.expiration.ExpirationExecution; +import io.javaoperatorsdk.operator.processing.expiration.RetryExpiration; +import io.javaoperatorsdk.operator.processing.retry.GenericRetry; +import io.javaoperatorsdk.operator.processing.retry.Retry; + +public class CRDPresentActivationCondition implements Condition { + + private static final Logger log = LoggerFactory.getLogger(CRDPresentActivationCondition.class); + + public static final int DEFAULT_EXPIRATION_INITIAL_INTERVAL = 1000; + public static final int DEFAULT_EXPIRATION_INTERVAL_MULTIPLIER = 4; + public static final int DEFAULT_EXPIRATION_MAX_RETRY_ATTEMPTS = 10; + + /** + * The idea behind default expiration is that on cluster start there might be different phases + * when CRDs and controllers are added. For a few times it will be checked if the target CRD is + * not present, after it will just use the cached state. + */ + public static Retry DEFAULT_EXPIRATION_RETRY = + new GenericRetry().setInitialInterval(DEFAULT_EXPIRATION_INITIAL_INTERVAL) + .setIntervalMultiplier(DEFAULT_EXPIRATION_INTERVAL_MULTIPLIER) + .setMaxAttempts(DEFAULT_EXPIRATION_MAX_RETRY_ATTEMPTS); + + private final Map crdPresenceCache = new ConcurrentHashMap<>(); + + private final Expiration expiration; + + public CRDPresentActivationCondition() { + this(new RetryExpiration(DEFAULT_EXPIRATION_RETRY)); + } + + public CRDPresentActivationCondition(Expiration expiration) { + this.expiration = expiration; + } + + @Override + public boolean isMet(DependentResource dependentResource, + HasMetadata primary, Context context) { + + var resourceClass = dependentResource.resourceType(); + final var crdName = HasMetadata.getFullResourceName(resourceClass); + + var crdCheckState = crdPresenceCache.computeIfAbsent(crdName, + g -> new CRDCheckState(expiration.initExecution())); + // in case of parallel execution it is only refreshed once + synchronized (crdCheckState) { + if (crdCheckState.isExpired()) { + log.debug("Refreshing cache for resource: {}", crdName); + final var found = context.getClient().resources(CustomResourceDefinition.class) + .withName(crdName).get() != null; + crdCheckState.refresh(found); + } + } + return crdPresenceCache.get(crdName).isCrdPresent(); + } + + static class CRDCheckState { + private final ExpirationExecution expirationExecution; + private boolean crdPresent; + + public CRDCheckState(ExpirationExecution expirationExecution) { + this.expirationExecution = expirationExecution; + } + + void refresh(boolean found) { + crdPresent = found; + expirationExecution.refreshed(); + } + + boolean isExpired() { + return expirationExecution.isExpired(); + } + + public boolean isCrdPresent() { + return crdPresent; + } + } +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultManagedWorkflow.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultManagedWorkflow.java index 27400230df..48c79e74d8 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultManagedWorkflow.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultManagedWorkflow.java @@ -12,9 +12,9 @@ import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceReferencer; -import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.KubernetesClientAware; +import io.javaoperatorsdk.operator.api.reconciler.dependent.NameSetter; -import static io.javaoperatorsdk.operator.processing.dependent.workflow.Workflow.THROW_EXCEPTION_AUTOMATICALLY_DEFAULT; +import static io.javaoperatorsdk.operator.api.reconciler.Constants.NO_VALUE_SET; @SuppressWarnings("rawtypes") public class DefaultManagedWorkflow

implements ManagedWorkflow

{ @@ -76,14 +76,20 @@ public boolean isEmpty() { public Workflow

resolve(KubernetesClient client, ControllerConfiguration

configuration) { final var alreadyResolved = new HashMap(orderedSpecs.size()); + + // sharing the activation condition so no parallel requests are done for the same CRD + CRDPresentActivationCondition crdPresentActivationCondition = + new CRDPresentActivationCondition(); + for (DependentResourceSpec spec : orderedSpecs) { - final var node = new DependentResourceNode(spec.getName(), + final var dependentResource = resolve(spec, client, configuration); + final var node = new DependentResourceNode( spec.getReconcileCondition(), spec.getDeletePostCondition(), spec.getReadyCondition(), - spec.getActivationCondition(), + spec.isOptional() ? crdPresentActivationCondition : spec.getActivationCondition(), resolve(spec, client, configuration)); - alreadyResolved.put(node.getName(), node); + alreadyResolved.put(dependentResource.name(), node); spec.getDependsOn() .forEach(depend -> node.addDependsOnRelation(alreadyResolved.get(depend))); } @@ -93,7 +99,8 @@ public Workflow

resolve(KubernetesClient client, final var top = topLevelResources.stream().map(alreadyResolved::get).collect(Collectors.toSet()); return new DefaultWorkflow<>(alreadyResolved, bottom, top, - THROW_EXCEPTION_AUTOMATICALLY_DEFAULT, hasCleaner); + configuration.getWorkflowSpec().map(w -> !w.handleExceptionsInReconciler()).orElseThrow(), + hasCleaner); } @SuppressWarnings({"rawtypes", "unchecked"}) @@ -104,8 +111,9 @@ private DependentResource resolve(DependentResourceSpec spec, configuration.getConfigurationService().dependentResourceFactory() .createFrom(spec, configuration); - if (dependentResource instanceof KubernetesClientAware) { - ((KubernetesClientAware) dependentResource).setKubernetesClient(client); + final var name = spec.getName(); + if (name != null && !NO_VALUE_SET.equals(name) && dependentResource instanceof NameSetter) { + ((NameSetter) dependentResource).setName(name); } spec.getUseEventSourceWithName() diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultWorkflow.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultWorkflow.java index d31f42419d..5ed5b5cb9e 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultWorkflow.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultWorkflow.java @@ -20,7 +20,7 @@ * @param

primary resource */ @SuppressWarnings("rawtypes") -public class DefaultWorkflow

implements Workflow

{ +class DefaultWorkflow

implements Workflow

{ private final Map dependentResourceNodes; private final Set topLevelResources; @@ -77,9 +77,9 @@ private Map toMap(Set node bottomLevelResource.remove(dependsOn); } } - map.put(node.getName(), node); + map.put(node.getDependentResource().name(), node); } - if (topLevelResources.size() == 0) { + if (topLevelResources.isEmpty()) { throw new IllegalStateException( "No top-level dependent resources found. This might indicate a cyclic Set of DependentResourceNode has been provided."); } @@ -148,14 +148,10 @@ public Map getDependentResourcesByName() { return resources; } - public Map getDependentResourcesByNameWithoutActivationCondition() { - final var resources = new HashMap(dependentResourceNodes.size()); - dependentResourceNodes - .forEach((name, node) -> { - if (node.getActivationCondition().isEmpty()) { - resources.put(name, node.getDependentResource()); - } - }); - return resources; + public List getDependentResourcesWithoutActivationCondition() { + return dependentResourceNodes.values().stream() + .filter(n -> n.getActivationCondition().isEmpty()) + .map(DependentResourceNode::getDependentResource) + .toList(); } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java index 4c82ee19f3..3e8a762c5e 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java @@ -8,29 +8,24 @@ import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; @SuppressWarnings("rawtypes") -public class DependentResourceNode { +class DependentResourceNode { private final List dependsOn = new LinkedList<>(); private final List parents = new LinkedList<>(); - private final String name; + private Condition reconcilePrecondition; private Condition deletePostcondition; private Condition readyPostcondition; private Condition activationCondition; private final DependentResource dependentResource; - DependentResourceNode(String name, DependentResource dependentResource) { - this(name, null, null, null, null, dependentResource); - } - DependentResourceNode(DependentResource dependentResource) { - this(getNameFor(dependentResource), null, null, null, null, dependentResource); + this(null, null, null, null, dependentResource); } - public DependentResourceNode(String name, Condition reconcilePrecondition, + public DependentResourceNode(Condition reconcilePrecondition, Condition deletePostcondition, Condition readyPostcondition, Condition activationCondition, DependentResource dependentResource) { - this.name = name; this.reconcilePrecondition = reconcilePrecondition; this.deletePostcondition = deletePostcondition; this.readyPostcondition = readyPostcondition; @@ -55,16 +50,10 @@ public List getParents() { return parents; } - public String getName() { - return name; - } - - public Optional> getReconcilePrecondition() { return Optional.ofNullable(reconcilePrecondition); } - public Optional> getDeletePostcondition() { return Optional.ofNullable(deletePostcondition); } @@ -106,18 +95,12 @@ public boolean equals(Object o) { return false; } DependentResourceNode that = (DependentResourceNode) o; - return name.equals(that.name); + return this.getDependentResource().name().equals(that.getDependentResource().name()); } @Override public int hashCode() { - return name.hashCode(); - } - - @SuppressWarnings("rawtypes") - static String getNameFor(DependentResource dependentResource) { - return DependentResource.defaultNameFor(dependentResource.getClass()) + "#" - + dependentResource.hashCode(); + return this.getDependentResource().name().hashCode(); } @Override diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowFactory.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowFactory.java index eb01dcd3f4..ef839896f4 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowFactory.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowFactory.java @@ -1,20 +1,22 @@ package io.javaoperatorsdk.operator.processing.dependent.workflow; +import java.util.Optional; + import io.javaoperatorsdk.operator.api.config.ControllerConfiguration; +import io.javaoperatorsdk.operator.api.config.workflow.WorkflowSpec; public interface ManagedWorkflowFactory> { @SuppressWarnings({"rawtypes", "unchecked"}) ManagedWorkflowFactory DEFAULT = (configuration) -> { - final var dependentResourceSpecs = configuration.getDependentResources(); - if (dependentResourceSpecs == null || dependentResourceSpecs.isEmpty()) { + final Optional workflowSpec = configuration.getWorkflowSpec(); + if (workflowSpec.isEmpty()) { return (ManagedWorkflow) (client, configuration1) -> new DefaultWorkflow(null); } ManagedWorkflowSupport support = new ManagedWorkflowSupport(); - return support.createWorkflow(dependentResourceSpecs); + return support.createWorkflow(workflowSpec.orElseThrow()); }; @SuppressWarnings("rawtypes") ManagedWorkflow workflowFor(C configuration); } - diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowSupport.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowSupport.java index b5a6fd26b2..d6404aa392 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowSupport.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowSupport.java @@ -12,9 +12,10 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.OperatorException; import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec; +import io.javaoperatorsdk.operator.api.config.workflow.WorkflowSpec; @SuppressWarnings({"rawtypes", "unchecked"}) -class ManagedWorkflowSupport { +public class ManagedWorkflowSupport { public void checkForNameDuplication(List dependentResourceSpecs) { if (dependentResourceSpecs == null) { @@ -38,10 +39,8 @@ public void checkForNameDuplication(List dependentResourc } } - - public

ManagedWorkflow

createWorkflow( - List dependentResourceSpecs) { - return createAsDefault(dependentResourceSpecs); + public

ManagedWorkflow

createWorkflow(WorkflowSpec workflowSpec) { + return createAsDefault(workflowSpec.getDependentResourceSpecs()); }

DefaultManagedWorkflow

createAsDefault( diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java index 839844256e..7f30ba6c9e 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/Workflow.java @@ -1,6 +1,7 @@ package io.javaoperatorsdk.operator.processing.dependent.workflow; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Set; @@ -44,7 +45,7 @@ default Map getDependentResourcesByName() { } @SuppressWarnings("rawtypes") - default Map getDependentResourcesByNameWithoutActivationCondition() { - return Collections.emptyMap(); + default List getDependentResourcesWithoutActivationCondition() { + return Collections.emptyList(); } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowBuilder.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowBuilder.java index d65e21659c..90ece70d26 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowBuilder.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowBuilder.java @@ -21,14 +21,9 @@ public class WorkflowBuilder

{ private boolean isCleaner = false; public WorkflowBuilder

addDependentResource(DependentResource dependentResource) { - return addDependentResource(dependentResource, null); - } - - public WorkflowBuilder

addDependentResource(DependentResource dependentResource, String name) { - currentNode = name == null ? new DependentResourceNode<>(dependentResource) - : new DependentResourceNode<>(name, dependentResource); + currentNode = new DependentResourceNode<>(dependentResource); isCleaner = isCleaner || dependentResource.isDeletable(); - final var actualName = currentNode.getName(); + final var actualName = dependentResource.name(); dependentResourceNodes.put(actualName, currentNode); return this; } @@ -70,8 +65,7 @@ public WorkflowBuilder

withActivationCondition(Condition activationCondition) DependentResourceNode getNodeByDependentResource(DependentResource dependentResource) { // first check by name - final var node = - dependentResourceNodes.get(DependentResourceNode.getNameFor(dependentResource)); + final var node = dependentResourceNodes.get(dependentResource.name()); if (node != null) { return node; } else { diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowResult.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowResult.java index 77fc2dcbfe..7014ccd5d4 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowResult.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowResult.java @@ -1,8 +1,9 @@ package io.javaoperatorsdk.operator.processing.dependent.workflow; -import java.util.HashMap; +import java.util.Collections; import java.util.Map; import java.util.Map.Entry; +import java.util.stream.Collectors; import io.javaoperatorsdk.operator.AggregatedOperatorException; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; @@ -10,26 +11,16 @@ @SuppressWarnings("rawtypes") class WorkflowResult { - private static final String NUMBER_DELIMITER = "_"; private final Map erroredDependents; WorkflowResult(Map erroredDependents) { - this.erroredDependents = erroredDependents; + this.erroredDependents = erroredDependents != null ? erroredDependents : Collections.emptyMap(); } public Map getErroredDependents() { return erroredDependents; } - /** - * @deprecated Use {@link #erroredDependentsExist()} instead - * @return if any dependents are in error state - */ - @Deprecated(forRemoval = true) - public boolean erroredDependentsExists() { - return !erroredDependents.isEmpty(); - } - @SuppressWarnings("unused") public boolean erroredDependentsExist() { return !erroredDependents.isEmpty(); @@ -37,22 +28,9 @@ public boolean erroredDependentsExist() { public void throwAggregateExceptionIfErrorsPresent() { if (erroredDependentsExist()) { - Map exceptionMap = new HashMap<>(); - Map numberOfClasses = new HashMap<>(); - - for (Entry entry : erroredDependents.entrySet()) { - String name = entry.getKey().getClass().getName(); - var num = numberOfClasses.getOrDefault(name, 0); - if (num > 0) { - exceptionMap.put(name + NUMBER_DELIMITER + num, entry.getValue()); - } else { - exceptionMap.put(name, entry.getValue()); - } - numberOfClasses.put(name, num + 1); - } - throw new AggregatedOperatorException("Exception(s) during workflow execution.", - exceptionMap); + erroredDependents.entrySet().stream() + .collect(Collectors.toMap(e -> e.getKey().name(), Entry::getValue))); } } } 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 2809efde8a..e05ea4830f 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 @@ -52,7 +52,7 @@ public EventProcessor(EventSourceManager

eventSourceManager, this( eventSourceManager.getController().getConfiguration(), new ReconciliationDispatcher<>(eventSourceManager.getController()), eventSourceManager, - configurationService.getMetrics(), eventSourceManager.getControllerResourceEventSource()); + configurationService.getMetrics(), eventSourceManager.getControllerEventSource()); } @SuppressWarnings("rawtypes") @@ -64,7 +64,7 @@ public EventProcessor(EventSourceManager

eventSourceManager, this( controllerConfiguration, reconciliationDispatcher, eventSourceManager, metrics, - eventSourceManager.getControllerResourceEventSource()); + eventSourceManager.getControllerEventSource()); } @SuppressWarnings({"rawtypes", "unchecked"}) @@ -166,8 +166,7 @@ private void submitReconciliationExecution(ResourceState state) { private void handleEventMarking(Event event, ResourceState state) { final var relatedCustomResourceID = event.getRelatedCustomResourceID(); - if (event instanceof ResourceEvent) { - var resourceEvent = (ResourceEvent) event; + if (event instanceof ResourceEvent resourceEvent) { if (resourceEvent.getAction() == ResourceAction.DELETED) { log.debug("Marking delete event received for: {}", relatedCustomResourceID); state.markDeleteEventReceived(); @@ -245,17 +244,6 @@ synchronized void eventProcessingFinished( state.markProcessedMarkForDeletion(); metrics.cleanupDoneFor(resourceID, metricsMetadata); } else { - postExecutionControl - .getUpdatedCustomResource() - .ifPresent( - p -> { - if (!postExecutionControl.updateIsStatusPatch()) { - eventSourceManager - .getControllerResourceEventSource() - .handleRecentResourceUpdate( - ResourceID.fromResource(p), p, executionScope.getResource()); - } - }); if (state.eventPresent()) { submitReconciliationExecution(state); } else { @@ -342,8 +330,8 @@ private void handleRetryOnException( private void retryAwareErrorLogging(RetryExecution retry, boolean eventPresent, Exception exception, ExecutionScope

executionScope) { - if (!eventPresent && !retry.isLastAttempt() && exception instanceof KubernetesClientException) { - KubernetesClientException ex = (KubernetesClientException) exception; + if (!eventPresent && !retry.isLastAttempt() + && exception instanceof KubernetesClientException ex) { if (ex.getCode() == HttpURLConnection.HTTP_CONFLICT) { log.debug("Full client conflict error during event processing {}", executionScope, exception); @@ -409,6 +397,10 @@ public synchronized void start() throws OperatorException { handleAlreadyMarkedEvents(); } + public boolean isNextReconciliationImminent(ResourceID resourceID) { + return resourceStateManager.getOrCreate(resourceID).eventPresent(); + } + private void handleAlreadyMarkedEvents() { for (var state : resourceStateManager.resourcesWithEventPresent()) { log.debug("Handling already marked event on start. State: {}", state); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSourceManager.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSourceManager.java index bd299b464a..c65d897734 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSourceManager.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSourceManager.java @@ -1,6 +1,10 @@ package io.javaoperatorsdk.operator.processing.event; -import java.util.*; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -14,14 +18,12 @@ import io.javaoperatorsdk.operator.api.config.ExecutorServiceManager; import io.javaoperatorsdk.operator.api.config.NamespaceChangeable; import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; -import io.javaoperatorsdk.operator.api.reconciler.EventSourceInitializer; import io.javaoperatorsdk.operator.processing.Controller; import io.javaoperatorsdk.operator.processing.LifecycleAware; import io.javaoperatorsdk.operator.processing.event.source.EventSource; import io.javaoperatorsdk.operator.processing.event.source.EventSourceStartPriority; import io.javaoperatorsdk.operator.processing.event.source.ResourceEventAware; -import io.javaoperatorsdk.operator.processing.event.source.ResourceEventSource; -import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerResourceEventSource; +import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerEventSource; import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceAction; import io.javaoperatorsdk.operator.processing.event.source.informer.ManagedInformerEventSource; import io.javaoperatorsdk.operator.processing.event.source.timer.TimerEventSource; @@ -49,7 +51,7 @@ public EventSourceManager(Controller

controller) { } public void postProcessDefaultEventSourcesAfterProcessorInitializer() { - eventSources.controllerResourceEventSource().setEventHandler(controller.getEventProcessor()); + eventSources.controllerEventSource().setEventHandler(controller.getEventProcessor()); eventSources.retryEventSource().setEventHandler(controller.getEventProcessor()); } @@ -61,31 +63,28 @@ public void postProcessDefaultEventSourcesAfterProcessorInitializer() { * {@link io.javaoperatorsdk.operator.processing.event.source.polling.PerResourcePollingEventSource}). *

* Now the event sources are also started sequentially, mainly because others might depend on - * {@link ControllerResourceEventSource} , which is started first. + * {@link ControllerEventSource} , which is started first. */ @Override public synchronized void start() { - startEventSource(eventSources.namedControllerResourceEventSource()); + startEventSource(eventSources.controllerEventSource()); executorServiceManager.boundedExecuteAndWaitForAllToComplete( - eventSources.additionalNamedEventSources() + eventSources.additionalEventSources() .filter(es -> es.priority().equals(EventSourceStartPriority.RESOURCE_STATE_LOADER)), this::startEventSource, getThreadNamer("start")); executorServiceManager.boundedExecuteAndWaitForAllToComplete( - eventSources.additionalNamedEventSources() + eventSources.additionalEventSources() .filter(es -> es.priority().equals(EventSourceStartPriority.DEFAULT)), this::startEventSource, getThreadNamer("start")); } - private static Function getThreadNamer(String stage) { - return es -> { - final var name = es.name(); - return es.priority() + " " + stage + " -> " - + (es.isNameSet() ? name + " " + es.original().getClass() : es.original()); - }; + @SuppressWarnings("rawtypes") + private static Function getThreadNamer(String stage) { + return es -> es.priority() + " " + stage + " -> " + es.name(); } private static Function getEventSourceThreadNamer(String stage) { @@ -94,29 +93,22 @@ private static Function getEventSourceThreadNamer(S @Override public synchronized void stop() { - stopEventSource(eventSources.namedControllerResourceEventSource()); + stopEventSource(eventSources.controllerEventSource()); executorServiceManager.boundedExecuteAndWaitForAllToComplete( - eventSources.additionalNamedEventSources(), + eventSources.additionalEventSources(), this::stopEventSource, getThreadNamer("stop")); } @SuppressWarnings("rawtypes") - private void logEventSourceEvent(NamedEventSource eventSource, String event) { + private void logEventSourceEvent(EventSource eventSource, String event) { if (log.isDebugEnabled()) { - if (eventSource.original() instanceof ResourceEventSource) { - ResourceEventSource source = (ResourceEventSource) eventSource.original(); - log.debug("{} event source {} for {}", event, - eventSource.isNameSet() ? eventSource.name() : eventSource, - source.resourceType()); - } else { - log.debug("{} event source {}", event, - eventSource.isNameSet() ? eventSource.name() : eventSource); - } + log.debug("{} event source {} for {}", event, eventSource.name(), + eventSource.resourceType()); } } - private Void startEventSource(NamedEventSource eventSource) { + private Void startEventSource(EventSource eventSource) { try { logEventSourceEvent(eventSource, "Starting"); eventSource.start(); @@ -129,7 +121,7 @@ private Void startEventSource(NamedEventSource eventSource) { return null; } - private Void stopEventSource(NamedEventSource eventSource) { + private Void stopEventSource(EventSource eventSource) { try { logEventSourceEvent(eventSource, "Stopping"); eventSource.stop(); @@ -140,38 +132,28 @@ private Void stopEventSource(NamedEventSource eventSource) { return null; } - public final void registerEventSource(EventSource eventSource) throws OperatorException { - registerEventSource(null, eventSource); - } - @SuppressWarnings("rawtypes") - public final synchronized void registerEventSource(String name, EventSource eventSource) + public final synchronized void registerEventSource(EventSource eventSource) throws OperatorException { Objects.requireNonNull(eventSource, "EventSource must not be null"); try { - if (name == null || name.isBlank()) { - name = EventSourceInitializer.generateNameFor(eventSource); - } - if (eventSource instanceof ManagedInformerEventSource) { - var managedInformerEventSource = ((ManagedInformerEventSource) eventSource); + if (eventSource instanceof ManagedInformerEventSource managedInformerEventSource) { managedInformerEventSource.setConfigurationService( controller.getConfiguration().getConfigurationService()); } - final var named = new NamedEventSource(eventSource, name); - eventSources.add(named); - named.setEventHandler(controller.getEventProcessor()); + eventSources.add(eventSource); + eventSource.setEventHandler(controller.getEventProcessor()); } catch (IllegalStateException | MissingCRDException e) { throw e; // leave untouched } catch (Exception e) { - throw new OperatorException("Couldn't register event source: " + name + " for " + throw new OperatorException("Couldn't register event source: " + eventSource.name() + " for " + controller.getConfiguration().getName() + " controller", e); } } @SuppressWarnings("unchecked") public void broadcastOnResourceEvent(ResourceAction action, P resource, P oldResource) { - eventSources.additionalNamedEventSources() - .map(NamedEventSource::original) + eventSources.additionalEventSources() .forEach(source -> { if (source instanceof ResourceEventAware) { var lifecycleAwareES = ((ResourceEventAware

) source); @@ -191,7 +173,7 @@ public void broadcastOnResourceEvent(ResourceAction action, P resource, P oldRes } public void changeNamespaces(Set namespaces) { - eventSources.controllerResourceEventSource() + eventSources.controllerEventSource() .changeNamespaces(namespaces); executorServiceManager.boundedExecuteAndWaitForAllToComplete(eventSources .additionalEventSources() @@ -204,39 +186,38 @@ public void changeNamespaces(Set namespaces) { getEventSourceThreadNamer("changeNamespace")); } - public Set getRegisteredEventSources() { + public Set> getRegisteredEventSources() { return eventSources.flatMappedSources() - .map(NamedEventSource::original) .collect(Collectors.toCollection(LinkedHashSet::new)); } - public Map allEventSources() { - return eventSources.allNamedEventSources().collect(Collectors.toMap(NamedEventSource::name, - NamedEventSource::original)); + @SuppressWarnings("rawtypes") + public List allEventSources() { + return eventSources.allEventSources().toList(); } + @SuppressWarnings("unused") - public Stream getNamedEventSourcesStream() { + public Stream> getEventSourcesStream() { return eventSources.flatMappedSources(); } - public ControllerResourceEventSource

getControllerResourceEventSource() { - return eventSources.controllerResourceEventSource(); + public ControllerEventSource

getControllerEventSource() { + return eventSources.controllerEventSource(); } - public List> getResourceEventSourcesFor(Class dependentType) { + public List> getEventSourcesFor(Class dependentType) { return eventSources.getEventSources(dependentType); } @Override - public EventSource dynamicallyRegisterEventSource(String name, - EventSource eventSource) { + public EventSource dynamicallyRegisterEventSource(EventSource eventSource) { synchronized (this) { - var actual = eventSources.existing(name, eventSource); + var actual = eventSources.existingEventSourceByName(eventSource.name()); if (actual != null) { - eventSource = actual.eventSource(); + eventSource = actual; } else { - registerEventSource(name, eventSource); + registerEventSource(eventSource); } } // The start itself is blocking thus blocking only the threads which are attempt to start the @@ -246,8 +227,10 @@ public EventSource dynamicallyRegisterEventSource(String name, } @Override - public synchronized Optional dynamicallyDeRegisterEventSource(String name) { - EventSource es = eventSources.remove(name); + public synchronized Optional> dynamicallyDeRegisterEventSource( + String name) { + @SuppressWarnings("unchecked") + EventSource es = eventSources.remove(name); if (es != null) { es.stop(); } @@ -259,23 +242,11 @@ public EventSourceContext

eventSourceContextForDynamicRegistration() { return controller.eventSourceContext(); } - /** - * @deprecated Use {@link #getResourceEventSourceFor(Class)} instead - * - * @param target resource type - * @param dependentType target resource class - * @return list of related event sources - */ - @Deprecated - public List> getEventSourcesFor(Class dependentType) { - return getResourceEventSourcesFor(dependentType); - } - @Override - public ResourceEventSource getResourceEventSourceFor( - Class dependentType, String qualifier) { + public EventSource getEventSourceFor( + Class dependentType, String name) { Objects.requireNonNull(dependentType, "dependentType is Mandatory"); - return eventSources.get(dependentType, qualifier); + return eventSources.get(dependentType, name); } TimerEventSource

retryEventSource() { diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSourceMetadata.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSourceMetadata.java deleted file mode 100644 index 2fd913d481..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSourceMetadata.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.javaoperatorsdk.operator.processing.event; - -import java.util.Optional; - -public interface EventSourceMetadata { - String name(); - - Class type(); - - Optional> resourceType(); - - Optional configuration(); -} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSourceRetriever.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSourceRetriever.java index 7ed2777998..16b03303a4 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSourceRetriever.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSourceRetriever.java @@ -6,23 +6,23 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; import io.javaoperatorsdk.operator.processing.event.source.EventSource; -import io.javaoperatorsdk.operator.processing.event.source.ResourceEventSource; public interface EventSourceRetriever

{ - default ResourceEventSource getResourceEventSourceFor(Class dependentType) { - return getResourceEventSourceFor(dependentType, null); + default EventSource getEventSourceFor(Class dependentType) { + return getEventSourceFor(dependentType, null); } - ResourceEventSource getResourceEventSourceFor(Class dependentType, String name); + EventSource getEventSourceFor(Class dependentType, String name); - List> getResourceEventSourcesFor(Class dependentType); + List> getEventSourcesFor(Class dependentType); /** + *

* Registers (and starts) the specified {@link EventSource} dynamically during the reconciliation. * If an EventSource is already registered with the specified name, the registration will be - * ignored. It is the user's responsibility to handle the naming correctly, thus to not try to - * register different event source with same name that is already registered. + * ignored. It is the user's responsibility to handle the naming correctly. + *

*

* This is only needed when your operator needs to adapt dynamically based on optional resources * that may or may not be present on the target cluster. Even in this situation, it should be @@ -31,20 +31,25 @@ default ResourceEventSource getResourceEventSourceFor(Class depende * activation conditions of dependents, for example. *

*

- * This method will block until the event source is synced, if needed (as is the case for + * This method will block until the event source is synced (if needed, as it is the case for * {@link io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource}). *

*

- * Should multiple reconciliations happen concurrently, only one EventSource with the specified - * name will ever be registered. + * IMPORTANT: Should multiple reconciliations happen concurrently, only one + * EventSource with the specified name will ever be registered. It is therefore important to + * explicitly name the event sources that you want to reuse because the name will be used to + * identify which event sources need to be created or not. If you let JOSDK implicitly name event + * sources, then you might end up with duplicated event sources because concurrent registration of + * event sources will lead to 2 (or more) event sources for the same resource type to be attempted + * to be registered under different, automatically generated names. If you clearly identify your + * event sources with names, then, if the concurrent process determines that an event source with + * the specified name, it won't register it again. *

* - * @param name of the event source * @param eventSource to register * @return the actual event source registered. Might not be the same as the parameter. */ - EventSource dynamicallyRegisterEventSource(String name, EventSource eventSource); - + EventSource dynamicallyRegisterEventSource(EventSource eventSource); /** * De-registers (and stops) the {@link EventSource} associated with the specified name. If no such @@ -62,7 +67,7 @@ default ResourceEventSource getResourceEventSourceFor(Class depende * @param name of the event source * @return the actual event source deregistered if there is one. */ - Optional dynamicallyDeRegisterEventSource(String name); + Optional> dynamicallyDeRegisterEventSource(String name); EventSourceContext

eventSourceContextForDynamicRegistration(); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSources.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSources.java index b53b92e122..e790ae3c32 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSources.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSources.java @@ -7,108 +7,84 @@ import java.util.Objects; import java.util.concurrent.ConcurrentNavigableMap; import java.util.concurrent.ConcurrentSkipListMap; -import java.util.stream.Collectors; import java.util.stream.Stream; import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.processing.Controller; import io.javaoperatorsdk.operator.processing.event.source.EventSource; -import io.javaoperatorsdk.operator.processing.event.source.ResourceEventSource; -import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerResourceEventSource; +import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerEventSource; import io.javaoperatorsdk.operator.processing.event.source.timer.TimerEventSource; -class EventSources { +class EventSources

{ - public static final String CONTROLLER_RESOURCE_EVENT_SOURCE_NAME = - "ControllerResourceEventSource"; - public static final String RETRY_RESCHEDULE_TIMER_EVENT_SOURCE_NAME = - "RetryAndRescheduleTimerEventSource"; - - private final ConcurrentNavigableMap> sources = + private final ConcurrentNavigableMap>> sources = new ConcurrentSkipListMap<>(); - private final TimerEventSource retryAndRescheduleTimerEventSource = new TimerEventSource<>(); - private ControllerResourceEventSource controllerResourceEventSource; - + private final Map sourceByName = new HashMap<>(); - void createControllerEventSource(Controller controller) { - controllerResourceEventSource = new ControllerResourceEventSource<>(controller); - } + private final TimerEventSource

retryAndRescheduleTimerEventSource = + new TimerEventSource<>("RetryAndRescheduleTimerEventSource"); + private ControllerEventSource

controllerEventSource; - ControllerResourceEventSource controllerResourceEventSource() { - return controllerResourceEventSource; + public void add(EventSource eventSource) { + final var name = eventSource.name(); + var existing = sourceByName.get(name); + if (existing != null) { + throw new IllegalArgumentException("Event source " + existing + + " is already registered with name: " + name); + } + sourceByName.put(name, eventSource); + sources.computeIfAbsent(keyFor(eventSource), k -> new HashMap<>()).put(name, eventSource); } - TimerEventSource retryEventSource() { - return retryAndRescheduleTimerEventSource; + public EventSource remove(String name) { + var optionalMap = sources.values().stream().filter(m -> m.containsKey(name)).findFirst(); + sourceByName.remove(name); + return optionalMap.map(m -> m.remove(name)).orElse(null); } - public Stream additionalNamedEventSources() { - return Stream.concat(Stream.of( - new NamedEventSource(retryAndRescheduleTimerEventSource, - RETRY_RESCHEDULE_TIMER_EVENT_SOURCE_NAME)), - flatMappedSources()); + public void clear() { + sources.clear(); + sourceByName.clear(); } - public Stream allNamedEventSources() { - return Stream.concat(Stream.of(namedControllerResourceEventSource(), - new NamedEventSource(retryAndRescheduleTimerEventSource, - RETRY_RESCHEDULE_TIMER_EVENT_SOURCE_NAME)), - flatMappedSources()); + public EventSource existingEventSourceByName(String name) { + return sourceByName.get(name); } - Stream additionalEventSources() { - return Stream.concat( - Stream.of(retryEventSource()).filter(Objects::nonNull), - flatMappedSources().map(NamedEventSource::original)); + void createControllerEventSource(Controller

controller) { + controllerEventSource = new ControllerEventSource<>(controller); } - NamedEventSource namedControllerResourceEventSource() { - return new NamedEventSource(controllerResourceEventSource, - CONTROLLER_RESOURCE_EVENT_SOURCE_NAME); + public ControllerEventSource

controllerEventSource() { + return controllerEventSource; } - Stream flatMappedSources() { - return sources.values().stream().flatMap(c -> c.values().stream()); + TimerEventSource

retryEventSource() { + return retryAndRescheduleTimerEventSource; } - public void clear() { - sources.clear(); + @SuppressWarnings("rawtypes") + public Stream allEventSources() { + return Stream.concat( + Stream.of(controllerEventSource(), retryAndRescheduleTimerEventSource), + flatMappedSources()); } - public NamedEventSource existing(String name, EventSource source) { - final var eventSources = sources.get(keyFor(source)); - if (eventSources == null || eventSources.isEmpty()) { - return null; - } - return eventSources.get(name); + @SuppressWarnings("rawtypes") + Stream additionalEventSources() { + return Stream.concat( + Stream.of(retryEventSource()).filter(Objects::nonNull), + flatMappedSources()); } - public void add(NamedEventSource eventSource) { - final var name = eventSource.name(); - final var original = eventSource.original(); - final var existing = existing(name, original); - if (existing != null && !eventSource.equals(existing)) { - throw new IllegalArgumentException("Event source " + existing.original() - + " is already registered for the " - + keyAsString(getResourceType(original), name) - + " class/name combination"); - } - sources.computeIfAbsent(keyFor(original), k -> new HashMap<>()).put(name, eventSource); + Stream> flatMappedSources() { + return sources.values().stream().flatMap(c -> c.values().stream()); } - @SuppressWarnings("rawtypes") - private Class getResourceType(EventSource source) { - return source instanceof ResourceEventSource - ? ((ResourceEventSource) source).resourceType() - : source.getClass(); - } - private String keyFor(EventSource source) { - if (source instanceof NamedEventSource) { - source = ((NamedEventSource) source).original(); - } - return keyFor(getResourceType(source)); + private String keyFor(EventSource source) { + return keyFor(source.resourceType()); } private String keyFor(Class dependentType) { @@ -116,7 +92,7 @@ private String keyFor(Class dependentType) { } @SuppressWarnings("unchecked") - public ResourceEventSource get(Class dependentType, String name) { + public EventSource get(Class dependentType, String name) { if (dependentType == null) { throw new IllegalArgumentException("Must pass a dependent type to retrieve event sources"); } @@ -128,9 +104,9 @@ public ResourceEventSource get(Class dependentType, String name) { } final var size = sourcesForType.size(); - NamedEventSource source; + EventSource source; if (size == 1 && name == null) { - source = sourcesForType.values().stream().findFirst().orElseThrow(); + source = (EventSource) sourcesForType.values().stream().findFirst().orElseThrow(); } else { if (name == null || name.isBlank()) { throw new IllegalArgumentException("There are multiple EventSources registered for type " @@ -138,7 +114,7 @@ public ResourceEventSource get(Class dependentType, String name) { + ", you need to provide a name to specify which EventSource you want to query. Known names: " + String.join(",", sourcesForType.keySet())); } - source = sourcesForType.get(name); + source = (EventSource) sourcesForType.get(name); if (source == null) { throw new IllegalArgumentException("There is no event source found for class:" + @@ -146,21 +122,14 @@ public ResourceEventSource get(Class dependentType, String name) { } } - EventSource original = source.original(); - if (!(original instanceof ResourceEventSource)) { - throw new IllegalArgumentException(source + " associated with " - + keyAsString(dependentType, name) + " is not a " - + ResourceEventSource.class.getSimpleName()); - } - final var res = (ResourceEventSource) original; - final var resourceClass = res.resourceType(); + final var resourceClass = source.resourceType(); if (!resourceClass.isAssignableFrom(dependentType)) { - throw new IllegalArgumentException(original + " associated with " + throw new IllegalArgumentException(source + " associated with " + keyAsString(dependentType, name) + " is handling " + resourceClass.getName() + " resources but asked for " + dependentType.getName()); } - return res; + return source; } @SuppressWarnings("rawtypes") @@ -171,21 +140,13 @@ private String keyAsString(Class dependentType, String name) { } @SuppressWarnings("unchecked") - public List> getEventSources(Class dependentType) { + public List> getEventSources(Class dependentType) { final var sourcesForType = sources.get(keyFor(dependentType)); if (sourcesForType == null) { return Collections.emptyList(); } return sourcesForType.values().stream() - .map(NamedEventSource::original) - .filter(ResourceEventSource.class::isInstance) - .map(es -> (ResourceEventSource) es) - .collect(Collectors.toList()); - } - - public EventSource remove(String name) { - var optionalMap = sources.values().stream().filter(m -> m.containsKey(name)).findFirst(); - return optionalMap.map(m -> m.remove(name)).orElse(null); + .map(es -> (EventSource) es).toList(); } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/NamedEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/NamedEventSource.java deleted file mode 100644 index a1d1a601e4..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/NamedEventSource.java +++ /dev/null @@ -1,105 +0,0 @@ -package io.javaoperatorsdk.operator.processing.event; - -import java.util.Objects; -import java.util.Optional; - -import io.javaoperatorsdk.operator.OperatorException; -import io.javaoperatorsdk.operator.api.reconciler.EventSourceInitializer; -import io.javaoperatorsdk.operator.processing.event.source.Configurable; -import io.javaoperatorsdk.operator.processing.event.source.EventSource; -import io.javaoperatorsdk.operator.processing.event.source.EventSourceStartPriority; -import io.javaoperatorsdk.operator.processing.event.source.ResourceEventSource; - -class NamedEventSource implements EventSource, EventSourceMetadata { - - private final EventSource original; - private final String name; - private final boolean nameSet; - - NamedEventSource(EventSource original, String name) { - this.original = original; - this.name = name; - nameSet = !name.equals(EventSourceInitializer.generateNameFor(original)); - } - - @Override - public void start() throws OperatorException { - original.start(); - } - - @Override - public void stop() throws OperatorException { - original.stop(); - } - - @Override - public void setEventHandler(EventHandler handler) { - original.setEventHandler(handler); - } - - public String name() { - return name; - } - - @Override - public Class type() { - return original.getClass(); - } - - @Override - @SuppressWarnings({"rawtypes", "unchecked"}) - public Optional> resourceType() { - if (original instanceof ResourceEventSource) { - ResourceEventSource resourceEventSource = (ResourceEventSource) original; - return Optional.of(resourceEventSource.resourceType()); - } - return Optional.empty(); - } - - @Override - @SuppressWarnings("rawtypes") - public Optional configuration() { - if (original instanceof Configurable) { - Configurable configurable = (Configurable) original; - return Optional.ofNullable(configurable.configuration()); - } - return Optional.empty(); - } - - public EventSource eventSource() { - return original; - } - - @Override - public String toString() { - return original + " named: " + name; - } - - public EventSource original() { - return original; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - NamedEventSource that = (NamedEventSource) o; - return Objects.equals(original, that.original) && Objects.equals(name, that.name); - } - - @Override - public int hashCode() { - return Objects.hash(original, name); - } - - @Override - public EventSourceStartPriority priority() { - return original.priority(); - } - - public boolean isNameSet() { - return nameSet; - } -} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/PostExecutionControl.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/PostExecutionControl.java index 6fddd5ad93..3343cff80a 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/PostExecutionControl.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/PostExecutionControl.java @@ -37,7 +37,7 @@ public static PostExecutionControl customResourceStat return new PostExecutionControl<>(false, updatedCustomResource, true, null); } - public static PostExecutionControl customResourceUpdated( + public static PostExecutionControl customResourcePatched( R updatedCustomResource) { return new PostExecutionControl<>(false, updatedCustomResource, false, null); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java index 9189cc6822..2b33133ae4 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java @@ -1,5 +1,6 @@ package io.javaoperatorsdk.operator.processing.event; +import java.lang.reflect.InvocationTargetException; import java.util.function.Function; import org.slf4j.Logger; @@ -8,12 +9,13 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.KubernetesResourceList; import io.fabric8.kubernetes.api.model.Namespaced; -import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.api.model.ObjectMeta; 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.dsl.base.PatchContext; +import io.fabric8.kubernetes.client.dsl.base.PatchType; import io.javaoperatorsdk.operator.OperatorException; -import io.javaoperatorsdk.operator.api.ObservedGenerationAware; import io.javaoperatorsdk.operator.api.config.Cloner; import io.javaoperatorsdk.operator.api.config.ControllerConfiguration; import io.javaoperatorsdk.operator.api.reconciler.BaseControl; @@ -25,9 +27,7 @@ import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; import io.javaoperatorsdk.operator.processing.Controller; -import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getName; -import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getUID; -import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getVersion; +import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.*; /** * Handles calls and results of a Reconciler and finalizer related logic @@ -44,19 +44,22 @@ class ReconciliationDispatcher

{ // Usually for testing purposes. private final boolean retryConfigurationHasZeroAttempts; private final Cloner cloner; + private final boolean useSSA; - ReconciliationDispatcher(Controller

controller, - CustomResourceFacade

customResourceFacade) { + ReconciliationDispatcher(Controller

controller, CustomResourceFacade

customResourceFacade) { this.controller = controller; this.customResourceFacade = customResourceFacade; - this.cloner = controller.getConfiguration().getConfigurationService().getResourceCloner(); + final var configuration = controller.getConfiguration(); + this.cloner = configuration.getConfigurationService().getResourceCloner(); - var retry = controller.getConfiguration().getRetry(); + var retry = configuration.getRetry(); retryConfigurationHasZeroAttempts = retry == null || retry.initExecution().isLastAttempt(); + useSSA = configuration.getConfigurationService().useSSAToPatchPrimaryResource(); } public ReconciliationDispatcher(Controller

controller) { - this(controller, new CustomResourceFacade<>(controller.getCRClient())); + this(controller, + new CustomResourceFacade<>(controller.getCRClient(), controller.getConfiguration())); } public PostExecutionControl

handleExecution(ExecutionScope

executionScope) { @@ -86,7 +89,7 @@ private PostExecutionControl

handleDispatch(ExecutionScope

executionScope) Context

context = new DefaultContext<>(executionScope.getRetryInfo(), controller, originalResource); if (markedForDeletion) { - return handleCleanup(resourceForExecution, context); + return handleCleanup(resourceForExecution, originalResource, context); } else { return handleReconcile(executionScope, resourceForExecution, originalResource, context); } @@ -109,8 +112,13 @@ private PostExecutionControl

handleReconcile( * finalizer add. This will make sure that the resources are not created before there is a * finalizer. */ - var updatedResource = - updateCustomResourceWithFinalizer(resourceForExecution, originalResource); + P updatedResource; + if (useSSA) { + updatedResource = addFinalizerWithSSA(originalResource); + } else { + updatedResource = + updateCustomResourceWithFinalizer(resourceForExecution, originalResource); + } return PostExecutionControl.onlyFinalizerAdded(updatedResource); } else { try { @@ -134,34 +142,30 @@ private PostExecutionControl

reconcileExecution(ExecutionScope

executionSc executionScope); UpdateControl

updateControl = controller.reconcile(resourceForExecution, context); + + final P toUpdate; P updatedCustomResource = null; - if (updateControl.isUpdateResourceAndStatus()) { - updatedCustomResource = - updateCustomResource(updateControl.getResource()); - updateControl - .getResource() - .getMetadata() - .setResourceVersion(updatedCustomResource.getMetadata().getResourceVersion()); - updatedCustomResource = - updateStatusGenerationAware(updateControl.getResource(), originalResource, - updateControl.isPatchStatus()); - } else if (updateControl.isUpdateStatus()) { - updatedCustomResource = - updateStatusGenerationAware(updateControl.getResource(), originalResource, - updateControl.isPatchStatus()); - } else if (updateControl.isUpdateResource()) { - updatedCustomResource = - updateCustomResource(updateControl.getResource()); - if (shouldUpdateObservedGenerationAutomatically(updatedCustomResource)) { - updatedCustomResource = - updateStatusGenerationAware(updateControl.getResource(), originalResource, - updateControl.isPatchStatus()); + if (useSSA) { + if (updateControl.isNoUpdate()) { + return createPostExecutionControl(null, updateControl); + } else { + toUpdate = updateControl.getResource().orElseThrow(); } - } else if (updateControl.isNoUpdate() - && shouldUpdateObservedGenerationAutomatically(resourceForExecution)) { - updatedCustomResource = - updateStatusGenerationAware(originalResource, originalResource, - updateControl.isPatchStatus()); + } else { + toUpdate = + updateControl.isNoUpdate() ? originalResource : updateControl.getResource().orElseThrow(); + } + + if (updateControl.isPatchResource()) { + updatedCustomResource = patchResource(toUpdate, originalResource); + if (!useSSA) { + toUpdate.getMetadata() + .setResourceVersion(updatedCustomResource.getMetadata().getResourceVersion()); + } + } + + if (updateControl.isPatchStatus()) { + customResourceFacade.patchStatus(toUpdate, originalResource); } return createPostExecutionControl(updatedCustomResource, updateControl); } @@ -191,17 +195,14 @@ public boolean isLastAttempt() { P updatedResource = null; if (errorStatusUpdateControl.getResource().isPresent()) { - updatedResource = errorStatusUpdateControl.isPatch() ? customResourceFacade - .patchStatus(errorStatusUpdateControl.getResource().orElseThrow(), originalResource) - : customResourceFacade - .updateStatus(errorStatusUpdateControl.getResource().orElseThrow()); + updatedResource = customResourceFacade + .patchStatus(errorStatusUpdateControl.getResource().orElseThrow(), originalResource); } if (errorStatusUpdateControl.isNoRetry()) { PostExecutionControl

postExecutionControl; if (updatedResource != null) { - postExecutionControl = errorStatusUpdateControl.isPatch() - ? PostExecutionControl.customResourceStatusPatched(updatedResource) - : PostExecutionControl.customResourceUpdated(updatedResource); + postExecutionControl = + PostExecutionControl.customResourceStatusPatched(updatedResource); } else { postExecutionControl = PostExecutionControl.defaultDispatch(); } @@ -220,53 +221,16 @@ private boolean isErrorStatusHandlerPresent() { return controller.getReconciler() instanceof ErrorStatusHandler; } - private P updateStatusGenerationAware(P resource, P originalResource, boolean patch) { - updateStatusObservedGenerationIfRequired(resource); - if (patch) { - return customResourceFacade.patchStatus(resource, originalResource); - } else { - return customResourceFacade.updateStatus(resource); - } - } - - @SuppressWarnings("rawtypes") - private boolean shouldUpdateObservedGenerationAutomatically(P resource) { - if (configuration().isGenerationAware() && resource instanceof CustomResource) { - var customResource = (CustomResource) resource; - var status = customResource.getStatus(); - // Note that if status is null we won't update the observed generation. - if (status instanceof ObservedGenerationAware) { - var observedGen = ((ObservedGenerationAware) status).getObservedGeneration(); - var currentGen = resource.getMetadata().getGeneration(); - return !currentGen.equals(observedGen); - } - } - return false; - } - - @SuppressWarnings("rawtypes") - private void updateStatusObservedGenerationIfRequired(P resource) { - if (configuration().isGenerationAware() && resource instanceof CustomResource) { - var customResource = (CustomResource) resource; - var status = customResource.getStatus(); - // Note that if status is null we won't update the observed generation. - if (status instanceof ObservedGenerationAware) { - ((ObservedGenerationAware) status) - .setObservedGeneration(resource.getMetadata().getGeneration()); - } - } + private P patchStatusGenerationAware(P resource, P originalResource) { + return customResourceFacade.patchStatus(resource, originalResource); } private PostExecutionControl

createPostExecutionControl(P updatedCustomResource, UpdateControl

updateControl) { PostExecutionControl

postExecutionControl; if (updatedCustomResource != null) { - if (updateControl.isUpdateStatus() && updateControl.isPatchStatus()) { - postExecutionControl = - PostExecutionControl.customResourceStatusPatched(updatedCustomResource); - } else { - postExecutionControl = PostExecutionControl.customResourceUpdated(updatedCustomResource); - } + postExecutionControl = + PostExecutionControl.customResourceStatusPatched(updatedCustomResource); } else { postExecutionControl = PostExecutionControl.defaultDispatch(); } @@ -281,7 +245,7 @@ private void updatePostExecutionControlWithReschedule( } private PostExecutionControl

handleCleanup(P resource, - Context

context) { + P originalResource, Context

context) { if (log.isDebugEnabled()) { log.debug( "Executing delete for resource: {} with version: {}", @@ -295,7 +259,7 @@ private PostExecutionControl

handleCleanup(P resource, // cleanup is finished, nothing left to done final var finalizerName = configuration().getFinalizerName(); if (deleteControl.isRemoveFinalizer() && resource.hasFinalizer(finalizerName)) { - P customResource = conflictRetryingUpdate(resource, r -> { + P customResource = conflictRetryingPatch(resource, originalResource, r -> { // the operator might not be allowed to retrieve the resource on a retry, e.g. when its // permissions are removed by deleting the namespace concurrently if (r == null) { @@ -306,7 +270,7 @@ private PostExecutionControl

handleCleanup(P resource, return false; } return r.removeFinalizer(finalizerName); - }); + }, true); return PostExecutionControl.customResourceFinalizerRemoved(customResource); } } @@ -321,27 +285,55 @@ private PostExecutionControl

handleCleanup(P resource, return postExecutionControl; } + @SuppressWarnings("unchecked") + private P addFinalizerWithSSA(P originalResource) { + log.debug( + "Adding finalizer (using SSA) for resource: {} version: {}", + getUID(originalResource), getVersion(originalResource)); + try { + P resource = (P) originalResource.getClass().getConstructor().newInstance(); + ObjectMeta objectMeta = new ObjectMeta(); + objectMeta.setName(originalResource.getMetadata().getName()); + objectMeta.setNamespace(originalResource.getMetadata().getNamespace()); + resource.setMetadata(objectMeta); + resource.addFinalizer(configuration().getFinalizerName()); + return customResourceFacade.patchResourceWithSSA(resource); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException + | NoSuchMethodException e) { + throw new RuntimeException("Issue with creating custom resource instance with reflection." + + " Custom Resources must provide a no-arg constructor. Class: " + + originalResource.getClass().getName(), + e); + } + } + private P updateCustomResourceWithFinalizer(P resourceForExecution, P originalResource) { log.debug( "Adding finalizer for resource: {} version: {}", getUID(originalResource), getVersion(originalResource)); - return conflictRetryingUpdate(resourceForExecution, - r -> r.addFinalizer(configuration().getFinalizerName())); + return conflictRetryingPatch(resourceForExecution, originalResource, + r -> r.addFinalizer(configuration().getFinalizerName()), false); } - private P updateCustomResource(P resource) { + private P patchResource(P resource, P originalResource) { log.debug("Updating resource: {} with version: {}", getUID(resource), getVersion(resource)); log.trace("Resource before update: {}", resource); - return customResourceFacade.updateResource(resource); + final var finalizerName = configuration().getFinalizerName(); + if (useSSA && controller.useFinalizer()) { + // addFinalizer already prevents adding an already present finalizer so no need to check + resource.addFinalizer(finalizerName); + } + return customResourceFacade.patchResource(resource, originalResource); } ControllerConfiguration

configuration() { return controller.getConfiguration(); } - public P conflictRetryingUpdate(P resource, Function modificationFunction) { + public P conflictRetryingPatch(P resource, P originalResource, + Function modificationFunction, boolean forceNotUseSSA) { if (log.isDebugEnabled()) { log.debug("Conflict retrying update for: {}", ResourceID.fromResource(resource)); } @@ -352,7 +344,11 @@ public P conflictRetryingUpdate(P resource, Function modificationFun if (Boolean.FALSE.equals(modified)) { return resource; } - return customResourceFacade.updateResource(resource); + if (forceNotUseSSA) { + return customResourceFacade.patchResourceWithoutSSA(resource, originalResource); + } else { + return customResourceFacade.patchResource(resource, originalResource); + } } catch (KubernetesClientException e) { log.trace("Exception during patch for resource: {}", resource); retryIndex++; @@ -376,10 +372,16 @@ public P conflictRetryingUpdate(P resource, Function modificationFun static class CustomResourceFacade { private final MixedOperation, Resource> resourceOperation; + private final boolean useSSA; + private final String fieldManager; public CustomResourceFacade( - MixedOperation, Resource> resourceOperation) { + MixedOperation, Resource> resourceOperation, + ControllerConfiguration configuration) { this.resourceOperation = resourceOperation; + this.useSSA = + configuration.getConfigurationService().useSSAToPatchPrimaryResource(); + this.fieldManager = configuration.fieldManager(); } public R getResource(String namespace, String name) { @@ -390,33 +392,47 @@ public R getResource(String namespace, String name) { } } - public R updateResource(R resource) { + public R patchResourceWithoutSSA(R resource, R originalResource) { + return resource(originalResource).edit(r -> resource); + } + + public R patchResource(R resource, R originalResource) { if (log.isDebugEnabled()) { log.debug( "Trying to replace resource {}, version: {}", ResourceID.fromResource(resource), resource.getMetadata().getResourceVersion()); } - return resource(resource).lockResourceVersion(resource.getMetadata().getResourceVersion()) - .update(); - } - - public R updateStatus(R resource) { - log.trace("Updating status for resource: {}", resource); - return resource(resource) - .lockResourceVersion() - .updateStatus(); + if (useSSA) { + return patchResourceWithSSA(resource); + } else { + return resource(originalResource).edit(r -> resource); + } } public R patchStatus(R resource, R originalResource) { - log.trace("Updating status for resource: {}", resource); + log.trace("Patching status for resource: {} with ssa: {}", resource, useSSA); String resourceVersion = resource.getMetadata().getResourceVersion(); - // don't do optimistic locking on patch originalResource.getMetadata().setResourceVersion(null); resource.getMetadata().setResourceVersion(null); try { - return resource(originalResource) - .editStatus(r -> resource); + if (useSSA) { + var managedFields = resource.getMetadata().getManagedFields(); + try { + resource.getMetadata().setManagedFields(null); + var res = resource(resource); + return res.subresource("status").patch(new PatchContext.Builder() + .withFieldManager(fieldManager) + .withForce(true) + .withPatchType(PatchType.SERVER_SIDE_APPLY) + .build()); + } finally { + resource.getMetadata().setManagedFields(managedFields); + } + } else { + var res = resource(originalResource); + return res.editStatus(r -> resource); + } } finally { // restore initial resource version originalResource.getMetadata().setResourceVersion(resourceVersion); @@ -424,6 +440,14 @@ public R patchStatus(R resource, R originalResource) { } } + public R patchResourceWithSSA(R resource) { + return resource(resource).patch(new PatchContext.Builder() + .withFieldManager(fieldManager) + .withForce(true) + .withPatchType(PatchType.SERVER_SIDE_APPLY) + .build()); + } + private Resource resource(R resource) { return resource instanceof Namespaced ? resourceOperation .inNamespace(resource.getMetadata().getNamespace()) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ResourceID.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ResourceID.java index 39374cb433..071dd49f29 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ResourceID.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ResourceID.java @@ -14,20 +14,6 @@ public static ResourceID fromResource(HasMetadata resource) { resource.getMetadata().getNamespace()); } - public static Optional fromFirstOwnerReference(HasMetadata resource) { - return fromFirstOwnerReference(resource, false); - } - - public static Optional fromFirstOwnerReference(HasMetadata resource, - boolean clusterScoped) { - var ownerReferences = resource.getMetadata().getOwnerReferences(); - if (!ownerReferences.isEmpty()) { - return Optional.of(fromOwnerReference(resource, ownerReferences.get(0), clusterScoped)); - } else { - return Optional.empty(); - } - } - public static ResourceID fromOwnerReference(HasMetadata resource, OwnerReference ownerReference, boolean clusterScoped) { return new ResourceID(ownerReference.getName(), diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/rate/LinearRateLimiter.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/rate/LinearRateLimiter.java index 02a919f547..2692b60bd0 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/rate/LinearRateLimiter.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/rate/LinearRateLimiter.java @@ -36,11 +36,10 @@ public LinearRateLimiter(Duration refreshPeriod, int limitForPeriod) { @Override public Optional isLimited(RateLimitState rateLimitState) { - if (!isActivated() || !(rateLimitState instanceof RateState)) { + if (!isActivated() || !(rateLimitState instanceof RateState actualState)) { return Optional.empty(); } - var actualState = (RateState) rateLimitState; if (actualState.getCount() < limitForPeriod) { actualState.increaseCount(); return Optional.empty(); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/AbstractEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/AbstractEventSource.java index 4eaee91add..a2306378d4 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/AbstractEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/AbstractEventSource.java @@ -1,12 +1,41 @@ package io.javaoperatorsdk.operator.processing.event.source; + +import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.OperatorException; import io.javaoperatorsdk.operator.processing.event.EventHandler; +import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter; +import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter; +import io.javaoperatorsdk.operator.processing.event.source.filter.OnDeleteFilter; +import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter; + +public abstract class AbstractEventSource implements EventSource { + + private final Class resourceClass; + + protected OnAddFilter onAddFilter; + protected OnUpdateFilter onUpdateFilter; + protected OnDeleteFilter onDeleteFilter; + protected GenericFilter genericFilter; -public abstract class AbstractEventSource implements EventSource { private EventHandler handler; private volatile boolean running = false; private EventSourceStartPriority eventSourceStartPriority = EventSourceStartPriority.DEFAULT; + private final String name; + + protected AbstractEventSource(Class resourceClass) { + this(resourceClass, null); + } + + protected AbstractEventSource(Class resourceClass, String name) { + this.name = name == null ? EventSource.super.name() : name; + this.resourceClass = resourceClass; + } + + @Override + public String name() { + return name; + } protected EventHandler getEventHandler() { return handler; @@ -41,4 +70,28 @@ public AbstractEventSource setEventSourcePriority( this.eventSourceStartPriority = eventSourceStartPriority; return this; } + + @Override + public Class resourceType() { + return resourceClass; + } + + public void setOnAddFilter(OnAddFilter onAddFilter) { + this.onAddFilter = onAddFilter; + } + + public void setOnUpdateFilter( + OnUpdateFilter onUpdateFilter) { + this.onUpdateFilter = onUpdateFilter; + } + + public void setOnDeleteFilter( + OnDeleteFilter onDeleteFilter) { + this.onDeleteFilter = onDeleteFilter; + } + + public void setGenericFilter(GenericFilter genericFilter) { + this.genericFilter = genericFilter; + } + } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/AbstractResourceEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/AbstractResourceEventSource.java deleted file mode 100644 index 65294c1625..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/AbstractResourceEventSource.java +++ /dev/null @@ -1,45 +0,0 @@ -package io.javaoperatorsdk.operator.processing.event.source; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter; -import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter; -import io.javaoperatorsdk.operator.processing.event.source.filter.OnDeleteFilter; -import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter; - -public abstract class AbstractResourceEventSource - extends AbstractEventSource - implements ResourceEventSource { - private final Class resourceClass; - - protected OnAddFilter onAddFilter; - protected OnUpdateFilter onUpdateFilter; - protected OnDeleteFilter onDeleteFilter; - protected GenericFilter genericFilter; - - protected AbstractResourceEventSource(Class resourceClass) { - this.resourceClass = resourceClass; - } - - @Override - public Class resourceType() { - return resourceClass; - } - - public void setOnAddFilter(OnAddFilter onAddFilter) { - this.onAddFilter = onAddFilter; - } - - public void setOnUpdateFilter( - OnUpdateFilter onUpdateFilter) { - this.onUpdateFilter = onUpdateFilter; - } - - public void setOnDeleteFilter( - OnDeleteFilter onDeleteFilter) { - this.onDeleteFilter = onDeleteFilter; - } - - public void setGenericFilter(GenericFilter genericFilter) { - this.genericFilter = genericFilter; - } -} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/EventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/EventSource.java index ec2783f797..11b884bb73 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/EventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/EventSource.java @@ -1,18 +1,25 @@ package io.javaoperatorsdk.operator.processing.event.source; +import java.util.Optional; +import java.util.Set; + +import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.health.EventSourceHealthIndicator; import io.javaoperatorsdk.operator.health.Status; import io.javaoperatorsdk.operator.processing.LifecycleAware; import io.javaoperatorsdk.operator.processing.event.EventHandler; +import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter; +import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter; +import io.javaoperatorsdk.operator.processing.event.source.filter.OnDeleteFilter; +import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter; /** * Creates an event source to trigger your reconciler whenever something happens to a secondary or - * external resource that would not normally trigger your reconciler (as the primary resources are - * not changed). To register EventSources with so that your reconciler is triggered, please make - * your reconciler implement - * {@link io.javaoperatorsdk.operator.api.reconciler.EventSourceInitializer}. + * external resource that should cause a reconciliation of the primary resource. EventSource + * generalizes the concept of Informers and extends it to external (i.e. non Kubernetes) resources. */ -public interface EventSource extends LifecycleAware, EventSourceHealthIndicator { +public interface EventSource + extends LifecycleAware, EventSourceHealthIndicator { /** * Sets the {@link EventHandler} that is linked to your reconciler when this EventSource is @@ -22,12 +29,48 @@ public interface EventSource extends LifecycleAware, EventSourceHealthIndicator */ void setEventHandler(EventHandler handler); + default String name() { + return generateName(this); + } + default EventSourceStartPriority priority() { return EventSourceStartPriority.DEFAULT; } + /** + * Retrieves the resource type associated with this ResourceEventSource + * + * @return the resource type associated with this ResourceEventSource + */ + Class resourceType(); + + default Optional getSecondaryResource(P primary) { + var resources = getSecondaryResources(primary); + if (resources.isEmpty()) { + return Optional.empty(); + } else if (resources.size() == 1) { + return Optional.of(resources.iterator().next()); + } else { + throw new IllegalStateException("More than 1 secondary resource related to primary"); + } + } + + Set getSecondaryResources(P primary); + + void setOnAddFilter(OnAddFilter onAddFilter); + + void setOnUpdateFilter(OnUpdateFilter onUpdateFilter); + + void setOnDeleteFilter(OnDeleteFilter onDeleteFilter); + + void setGenericFilter(GenericFilter genericFilter); + @Override default Status getStatus() { return Status.UNKNOWN; } + + static String generateName(EventSource eventSource) { + return eventSource.getClass().getName() + "@" + Integer.toHexString(eventSource.hashCode()); + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSource.java index 58778edbdf..130e3db179 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSource.java @@ -1,6 +1,13 @@ package io.javaoperatorsdk.operator.processing.event.source; -import java.util.*; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -33,7 +40,7 @@ * @param

primary resource */ public abstract class ExternalResourceCachingEventSource - extends AbstractResourceEventSource implements RecentOperationCacheFiller { + extends AbstractEventSource implements RecentOperationCacheFiller { private static final Logger log = LoggerFactory.getLogger(ExternalResourceCachingEventSource.class); @@ -44,7 +51,12 @@ public abstract class ExternalResourceCachingEventSource resourceClass, CacheKeyMapper cacheKeyMapper) { - super(resourceClass); + this(null, resourceClass, cacheKeyMapper); + } + + protected ExternalResourceCachingEventSource(String name, Class resourceClass, + CacheKeyMapper cacheKeyMapper) { + super(resourceClass, name); this.cacheKeyMapper = cacheKeyMapper; } @@ -90,8 +102,7 @@ protected synchronized void handleResources(ResourceID primaryID, Set newReso } protected synchronized void handleResources(Map> allNewResources) { - var toDelete = cache.keySet().stream().filter(k -> !allNewResources.containsKey(k)) - .collect(Collectors.toList()); + var toDelete = cache.keySet().stream().filter(k -> !allNewResources.containsKey(k)).toList(); toDelete.forEach(this::handleDelete); allNewResources.forEach(this::handleResources); } @@ -153,21 +164,15 @@ private boolean acceptedByFiler(Map cachedResourceMap, .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); if (onUpdateFilter != null || genericFilter != null) { - var anyUpdated = possibleUpdatedResources.entrySet().stream() + return possibleUpdatedResources.entrySet().stream() .anyMatch( entry -> { var newResource = newResourcesMap.get(entry.getKey()); return acceptedByGenericFiler(newResource) && onUpdateFilter.accept(newResource, entry.getValue()); }); - if (anyUpdated) { - return true; - } - } else if (!possibleUpdatedResources.isEmpty()) { - return true; - } - - return false; + } else + return !possibleUpdatedResources.isEmpty(); } private boolean acceptedByGenericFiler(R resource) { diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventSource.java deleted file mode 100644 index 722e260878..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventSource.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.javaoperatorsdk.operator.processing.event.source; - -import java.util.Optional; -import java.util.Set; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter; -import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter; -import io.javaoperatorsdk.operator.processing.event.source.filter.OnDeleteFilter; -import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter; - -public interface ResourceEventSource extends EventSource { - - /** - * Retrieves the resource type associated with this ResourceEventSource - * - * @return the resource type associated with this ResourceEventSource - */ - Class resourceType(); - - default Optional getSecondaryResource(P primary) { - var resources = getSecondaryResources(primary); - if (resources.isEmpty()) { - return Optional.empty(); - } else if (resources.size() == 1) { - return Optional.of(resources.iterator().next()); - } else { - throw new IllegalStateException("More than 1 secondary resource related to primary"); - } - - } - - Set getSecondaryResources(P primary); - - void setOnAddFilter(OnAddFilter onAddFilter); - - void setOnUpdateFilter(OnUpdateFilter onUpdateFilter); - - void setOnDeleteFilter(OnDeleteFilter onDeleteFilter); - - void setGenericFilter(GenericFilter genericFilter); -} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSource.java similarity index 90% rename from operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSource.java rename to operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSource.java index c1ac2d3352..3d66051e16 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSource.java @@ -18,21 +18,21 @@ import io.javaoperatorsdk.operator.processing.event.source.informer.ManagedInformerEventSource; import static io.javaoperatorsdk.operator.ReconcilerUtils.handleKubernetesClientException; -import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.*; +import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getVersion; import static io.javaoperatorsdk.operator.processing.event.source.controller.InternalEventFilters.*; -public class ControllerResourceEventSource +public class ControllerEventSource extends ManagedInformerEventSource> implements ResourceEventHandler { - private static final Logger log = LoggerFactory.getLogger(ControllerResourceEventSource.class); + private static final Logger log = LoggerFactory.getLogger(ControllerEventSource.class); + public static final String NAME = "ControllerResourceEventSource"; private final Controller controller; - private final ResourceEventFilter legacyFilters; @SuppressWarnings({"unchecked", "rawtypes"}) - public ControllerResourceEventSource(Controller controller) { - super(controller.getCRClient(), controller.getConfiguration(), false); + public ControllerEventSource(Controller controller) { + super(NAME, controller.getCRClient(), controller.getConfiguration(), false); this.controller = controller; final var config = controller.getConfiguration(); @@ -42,8 +42,6 @@ public ControllerResourceEventSource(Controller controller) { .or(onUpdateGenerationAware(config.isGenerationAware())) .or(onUpdateMarkedForDeletion()); - legacyFilters = config.getEventFilter(); - // by default the on add should be processed in all cases regarding internal filters config.onAddFilter().ifPresent(this::setOnAddFilter); config.onUpdateFilter() @@ -74,9 +72,7 @@ public void eventReceived(ResourceAction action, T resource, T oldResource) { } MDCUtils.addResourceInfo(resource); controller.getEventSourceManager().broadcastOnResourceEvent(action, resource, oldResource); - if ((legacyFilters == null || - legacyFilters.acceptChange(controller, oldResource, resource)) - && isAcceptedByFilters(action, resource, oldResource)) { + if (isAcceptedByFilters(action, resource, oldResource)) { getEventHandler().handleEvent( new ResourceEvent(action, ResourceID.fromResource(resource), resource)); } else { @@ -135,4 +131,9 @@ public void setOnDeleteFilter(OnDeleteFilter onDeleteFilter) { throw new IllegalStateException( "onDeleteFilter is not supported for controller resource event source"); } + + @Override + public String name() { + return NAME; + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ResourceEventFilter.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ResourceEventFilter.java deleted file mode 100644 index 08a86c92ae..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ResourceEventFilter.java +++ /dev/null @@ -1,59 +0,0 @@ -package io.javaoperatorsdk.operator.processing.event.source.controller; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.processing.Controller; - -/** - * A functional interface to determine whether resource events should be processed by the SDK. This - * allows users to more finely tuned which events trigger a reconciliation than was previously - * possible (where the logic was limited to generation-based checking). - * - * @param

the type of custom resources handled by this filter - */ -@Deprecated(forRemoval = true) -@FunctionalInterface -public interface ResourceEventFilter

{ - - /** - * Determines whether the change between the old version of the resource and the new one needs to - * be propagated to the controller or not. - * - * @param controller the target controller - * @param oldResource the old version of the resource, null if no old resource available - * @param newResource the new version of the resource - * @return {@code true} if the change needs to be propagated to the controller, {@code false} - * otherwise - */ - boolean acceptChange(Controller

controller, P oldResource, P newResource); - - /** - * Combines this filter with the provided one with an AND logic, i.e. the resulting filter will - * only accept the change if both this and the other filter accept it, reject it otherwise. - * - * @param other the possibly {@code null} other filter to combine this one with - * @return a composite filter implementing the AND logic between this and the provided filter - */ - default ResourceEventFilter

and(ResourceEventFilter

other) { - return other == null ? this - : (Controller

controller, P oldResource, P newResource) -> { - boolean result = acceptChange(controller, oldResource, newResource); - return result && other.acceptChange(controller, oldResource, newResource); - }; - } - - /** - * Combines this filter with the provided one with an OR logic, i.e. the resulting filter will - * accept the change if any of this or the other filter accept it, rejecting it only if both - * reject it. - * - * @param other the possibly {@code null} other filter to combine this one with - * @return a composite filter implementing the OR logic between this and the provided filter - */ - default ResourceEventFilter

or(ResourceEventFilter

other) { - return other == null ? this - : (Controller

controller, P oldResource, P newResource) -> { - boolean result = acceptChange(controller, oldResource, newResource); - return result || other.acceptChange(controller, oldResource, newResource); - }; - } -} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ResourceEventFilters.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ResourceEventFilters.java deleted file mode 100644 index 7024388b8b..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ResourceEventFilters.java +++ /dev/null @@ -1,27 +0,0 @@ -package io.javaoperatorsdk.operator.processing.event.source.controller; - -import io.fabric8.kubernetes.api.model.HasMetadata; - -/** - * Convenience implementations of, and utility methods for, {@link ResourceEventFilter}. - */ -@Deprecated -public final class ResourceEventFilters { - - private static final ResourceEventFilter PASSTHROUGH = - (configuration, oldResource, newResource) -> true; - - private ResourceEventFilters() {} - - /** - * Retrieves a filter that accepts all events. - * - * @param the type of custom resource the filter should handle - * @return a filter that accepts all events - */ - @SuppressWarnings("unchecked") - public static ResourceEventFilter passthrough() { - return (ResourceEventFilter) PASSTHROUGH; - } - -} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/inbound/SimpleInboundEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/inbound/SimpleInboundEventSource.java index a441684f0f..7d5f2aa446 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/inbound/SimpleInboundEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/inbound/SimpleInboundEventSource.java @@ -1,16 +1,27 @@ package io.javaoperatorsdk.operator.processing.event.source.inbound; +import java.util.Set; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.processing.event.Event; import io.javaoperatorsdk.operator.processing.event.ResourceID; import io.javaoperatorsdk.operator.processing.event.source.AbstractEventSource; -public class SimpleInboundEventSource extends AbstractEventSource { +public class SimpleInboundEventSource

extends AbstractEventSource { private static final Logger log = LoggerFactory.getLogger(SimpleInboundEventSource.class); + public SimpleInboundEventSource() { + super(Void.class); + } + + public SimpleInboundEventSource(String name) { + super(Void.class, name); + } + public void propagateEvent(ResourceID resourceID) { if (isRunning()) { getEventHandler().handleEvent(new Event(resourceID)); @@ -19,4 +30,8 @@ public void propagateEvent(ResourceID resourceID) { } } + @Override + public Set getSecondaryResources(P primary) { + return Set.of(); + } } 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 81d31f7407..571e02dbc2 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 @@ -76,20 +76,28 @@ public class InformerEventSource private final PrimaryToSecondaryMapper

primaryToSecondaryMapper; private final String id = UUID.randomUUID().toString(); + public InformerEventSource(String name, + InformerConfiguration configuration, EventSourceContext

context) { + this(name, configuration, context.getClient(), + context.getControllerConfiguration().getConfigurationService() + .parseResourceVersionsForEventFilteringAndCaching()); + } + public InformerEventSource( InformerConfiguration configuration, EventSourceContext

context) { - this(configuration, context.getClient(), + this(null, configuration, context.getClient(), context.getControllerConfiguration().getConfigurationService() .parseResourceVersionsForEventFilteringAndCaching()); } public InformerEventSource(InformerConfiguration configuration, KubernetesClient client) { - this(configuration, client, false); + this(null, configuration, client, false); } - public InformerEventSource(InformerConfiguration configuration, KubernetesClient client, + public InformerEventSource(String name, InformerConfiguration configuration, + KubernetesClient client, boolean parseResourceVersions) { - super( + super(name, configuration.getGroupVersionKind() .map(gvk -> client.genericKubernetesResources(gvk.apiVersion(), gvk.getKind())) .orElseGet(() -> (MixedOperation) client.resources(configuration.getResourceClass())), @@ -247,18 +255,6 @@ public Set getSecondaryResources(P primary) { .collect(Collectors.toSet()); } - /** - * Returns the configuration object for the informer. - * - * @return the informer configuration object - * - * @deprecated Use {@link #configuration()} instead - */ - @Deprecated(forRemoval = true) - public InformerConfiguration getConfiguration() { - return configuration(); - } - @Override public synchronized void handleRecentResourceUpdate(ResourceID resourceID, R resource, R previousVersionOfResource) { @@ -316,5 +312,4 @@ public R addPreviousAnnotation(String resourceVersion, R target) { id + Optional.ofNullable(resourceVersion).map(rv -> "," + rv).orElse("")); return target; } - } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerManager.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerManager.java index e68cd3ab25..a97897b1fa 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerManager.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerManager.java @@ -170,7 +170,9 @@ public Stream list(String namespace, Predicate predicate) { public Optional get(ResourceID resourceID) { return getSource(resourceID.getNamespace().orElse(WATCH_ALL_NAMESPACES)) .flatMap(source -> source.get(resourceID)) - .map(r -> configurationService.getResourceCloner().clone(r)); + .map(r -> configurationService.cloneSecondaryResourcesWhenGettingFromCache() + ? configurationService.getResourceCloner().clone(r) + : r); } @Override diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/ManagedInformerEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/ManagedInformerEventSource.java index 7139395e76..dcf0ab3d7e 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/ManagedInformerEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/ManagedInformerEventSource.java @@ -1,6 +1,10 @@ package io.javaoperatorsdk.operator.processing.event.source.informer; -import java.util.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Stream; @@ -20,13 +24,11 @@ import io.javaoperatorsdk.operator.health.InformerWrappingEventSourceHealthIndicator; import io.javaoperatorsdk.operator.health.Status; import io.javaoperatorsdk.operator.processing.event.ResourceID; -import io.javaoperatorsdk.operator.processing.event.source.AbstractResourceEventSource; -import io.javaoperatorsdk.operator.processing.event.source.Cache; -import io.javaoperatorsdk.operator.processing.event.source.Configurable; -import io.javaoperatorsdk.operator.processing.event.source.IndexerResourceCache; +import io.javaoperatorsdk.operator.processing.event.source.*; +@SuppressWarnings("rawtypes") public abstract class ManagedInformerEventSource> - extends AbstractResourceEventSource + extends AbstractEventSource implements ResourceEventHandler, Cache, IndexerResourceCache, RecentOperationCacheFiller, NamespaceChangeable, InformerWrappingEventSourceHealthIndicator, Configurable { @@ -36,14 +38,14 @@ public abstract class ManagedInformerEventSource>> indexers = new HashMap<>(); + private final Map>> indexers = new HashMap<>(); protected TemporaryResourceCache temporaryResourceCache; protected MixedOperation client; - protected ManagedInformerEventSource( + protected ManagedInformerEventSource(String name, MixedOperation client, C configuration, boolean parseResourceVersions) { - super(configuration.getResourceClass()); + super(configuration.getResourceClass(), name); this.parseResourceVersions = parseResourceVersions; this.client = client; this.configuration = configuration; @@ -75,6 +77,7 @@ public void changeNamespaces(Set namespaces) { } } + @SuppressWarnings("unchecked") @Override public synchronized void start() { if (isRunning()) { @@ -124,6 +127,7 @@ public Optional get(ResourceID resourceID) { } } + @SuppressWarnings("unused") public Optional getCachedValue(ResourceID resourceID) { return get(resourceID); } @@ -190,4 +194,5 @@ public String toString() { public void setConfigurationService(ConfigurationService configurationService) { this.configurationService = configurationService; } + } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/Mappers.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/Mappers.java index 65f9c86cb8..24c4d18837 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/Mappers.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/Mappers.java @@ -42,24 +42,37 @@ public static SecondaryToPrimaryMapper fromLabel( return fromMetadata(nameKey, namespaceKey, true); } - public static SecondaryToPrimaryMapper fromOwnerReference() { - return fromOwnerReference(false); + public static SecondaryToPrimaryMapper fromOwnerReferences( + Class primaryResourceType) { + return fromOwnerReferences(primaryResourceType, false); } - /** - * @param clusterScope if the owner is a cluster scoped resource - * @return mapper - * @param type of the secondary resource, where the owner reference is - */ - public static SecondaryToPrimaryMapper fromOwnerReference( - boolean clusterScope) { - return resource -> ResourceID.fromFirstOwnerReference(resource, clusterScope).map(Set::of) - .orElseGet(Collections::emptySet); + public static SecondaryToPrimaryMapper fromOwnerReferences( + Class primaryResourceType, boolean clusterScoped) { + return fromOwnerReferences(HasMetadata.getApiVersion(primaryResourceType), + HasMetadata.getKind(primaryResourceType), + clusterScoped); + } + + public static SecondaryToPrimaryMapper fromOwnerReferences( + HasMetadata primaryResource) { + return fromOwnerReferences(primaryResource, false); + } + + public static SecondaryToPrimaryMapper fromOwnerReferences( + HasMetadata primaryResource, + boolean clusterScoped) { + return fromOwnerReferences(primaryResource.getApiVersion(), primaryResource.getKind(), + clusterScoped); } public static SecondaryToPrimaryMapper fromOwnerReferences( + String apiVersion, String kind, boolean clusterScope) { - return resource -> resource.getMetadata().getOwnerReferences().stream() + return resource -> resource.getMetadata().getOwnerReferences() + .stream() + .filter(r -> r.getKind().equals(kind) + && r.getApiVersion().equals(apiVersion)) .map(or -> ResourceID.fromOwnerReference(resource, or, clusterScope)) .collect(Collectors.toSet()); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/polling/PerResourcePollingConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/polling/PerResourcePollingConfiguration.java new file mode 100644 index 0000000000..23fa9e023a --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/polling/PerResourcePollingConfiguration.java @@ -0,0 +1,30 @@ +package io.javaoperatorsdk.operator.processing.event.source.polling; + +import java.time.Duration; +import java.util.Objects; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.function.Predicate; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.processing.event.source.CacheKeyMapper; + +public record PerResourcePollingConfiguration(ScheduledExecutorService executorService, CacheKeyMapper cacheKeyMapper, + PerResourcePollingEventSource.ResourceFetcher resourceFetcher, + Predicate

registerPredicate, Duration defaultPollingPeriod) { + + public static final int DEFAULT_EXECUTOR_THREAD_NUMBER = 1; + + public PerResourcePollingConfiguration(ScheduledExecutorService executorService, + CacheKeyMapper cacheKeyMapper, + PerResourcePollingEventSource.ResourceFetcher resourceFetcher, + Predicate

registerPredicate, + Duration defaultPollingPeriod) { + this.executorService = executorService == null ? new ScheduledThreadPoolExecutor(DEFAULT_EXECUTOR_THREAD_NUMBER) + : executorService; + this.cacheKeyMapper = cacheKeyMapper == null ? CacheKeyMapper.singleResourceCacheKeyMapper() : cacheKeyMapper; + this.resourceFetcher = Objects.requireNonNull(resourceFetcher); + this.registerPredicate = registerPredicate; + this.defaultPollingPeriod = defaultPollingPeriod; + } +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/polling/PerResourcePollingConfigurationBuilder.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/polling/PerResourcePollingConfigurationBuilder.java new file mode 100644 index 0000000000..ece10d347e --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/polling/PerResourcePollingConfigurationBuilder.java @@ -0,0 +1,49 @@ +package io.javaoperatorsdk.operator.processing.event.source.polling; + +import java.time.Duration; +import java.util.concurrent.ScheduledExecutorService; +import java.util.function.Predicate; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.processing.event.source.CacheKeyMapper; + +public final class PerResourcePollingConfigurationBuilder { + + private final Duration defaultPollingPeriod; + private final PerResourcePollingEventSource.ResourceFetcher resourceFetcher; + + private Predicate

registerPredicate; + private ScheduledExecutorService executorService; + private CacheKeyMapper cacheKeyMapper; + + public PerResourcePollingConfigurationBuilder( + PerResourcePollingEventSource.ResourceFetcher resourceFetcher, + Duration defaultPollingPeriod) { + this.resourceFetcher = resourceFetcher; + this.defaultPollingPeriod = defaultPollingPeriod; + } + + @SuppressWarnings("unused") + public PerResourcePollingConfigurationBuilder withExecutorService( + ScheduledExecutorService executorService) { + this.executorService = executorService; + return this; + } + + public PerResourcePollingConfigurationBuilder withRegisterPredicate( + Predicate

registerPredicate) { + this.registerPredicate = registerPredicate; + return this; + } + + public PerResourcePollingConfigurationBuilder withCacheKeyMapper( + CacheKeyMapper cacheKeyMapper) { + this.cacheKeyMapper = cacheKeyMapper; + return this; + } + + public PerResourcePollingConfiguration build() { + return new PerResourcePollingConfiguration<>(executorService, cacheKeyMapper, + resourceFetcher, registerPredicate, defaultPollingPeriod); + } +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/polling/PerResourcePollingEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/polling/PerResourcePollingEventSource.java index 6da1ec0e58..2288e7eb75 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/polling/PerResourcePollingEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/polling/PerResourcePollingEventSource.java @@ -9,7 +9,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.function.Predicate; @@ -21,10 +20,10 @@ import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; import io.javaoperatorsdk.operator.processing.event.ResourceID; import io.javaoperatorsdk.operator.processing.event.source.Cache; -import io.javaoperatorsdk.operator.processing.event.source.CacheKeyMapper; import io.javaoperatorsdk.operator.processing.event.source.ExternalResourceCachingEventSource; import io.javaoperatorsdk.operator.processing.event.source.ResourceEventAware; + /** * * Polls the supplier for each controlled resource registered. Resource is registered when created @@ -42,138 +41,29 @@ public class PerResourcePollingEventSource private static final Logger log = LoggerFactory.getLogger(PerResourcePollingEventSource.class); - public static final int DEFAULT_EXECUTOR_THREAD_NUMBER = 1; + private final Map> scheduledFutures = new ConcurrentHashMap<>(); + private final Cache

primaryResourceCache; + private final Set fetchedForPrimaries = ConcurrentHashMap.newKeySet(); private final ScheduledExecutorService executorService; - private final Map> scheduledFutures = new ConcurrentHashMap<>(); private final ResourceFetcher resourceFetcher; - private final Cache

resourceCache; private final Predicate

registerPredicate; - private final long period; - private final Set fetchedForPrimaries = ConcurrentHashMap.newKeySet(); - - public PerResourcePollingEventSource(ResourceFetcher resourceFetcher, - EventSourceContext

context, Duration defaultPollingPeriod, - Class resourceClass) { - this(resourceFetcher, context.getPrimaryCache(), defaultPollingPeriod.toMillis(), - null, resourceClass, - CacheKeyMapper.singleResourceCacheKeyMapper()); - } - - /** - * @deprecated use the variant which uses {@link EventSourceContext} instead of {@link Cache} and - * {@link Duration} for period parameter as it provides a more intuitive API. - * - * @param resourceFetcher fetches resource related to a primary resource - * @param resourceCache cache of the primary resource - * @param period default polling period - * @param resourceClass class of the target resource - */ - @Deprecated(forRemoval = true) - public PerResourcePollingEventSource(ResourceFetcher resourceFetcher, - Cache

resourceCache, long period, Class resourceClass) { - this(resourceFetcher, resourceCache, period, null, resourceClass, - CacheKeyMapper.singleResourceCacheKeyMapper()); - } + private final Duration period; - public PerResourcePollingEventSource(ResourceFetcher resourceFetcher, - EventSourceContext

context, - Duration defaultPollingPeriod, - Class resourceClass, - CacheKeyMapper cacheKeyMapper) { - this(resourceFetcher, context.getPrimaryCache(), defaultPollingPeriod.toMillis(), - null, resourceClass, cacheKeyMapper); - } - - /** - * @deprecated use the variant which uses {@link EventSourceContext} instead of {@link Cache} and - * {@link Duration} for period parameter as it provides a more intuitive API. - * - * @param resourceFetcher fetches resource related to a primary resource - * @param resourceCache cache of the primary resource - * @param period default polling period - * @param resourceClass class of the target resource - * @param cacheKeyMapper use to distinguish resource in case more resources are handled for a - * single primary resource - */ - @Deprecated(forRemoval = true) - public PerResourcePollingEventSource(ResourceFetcher resourceFetcher, - Cache

resourceCache, long period, Class resourceClass, - CacheKeyMapper cacheKeyMapper) { - this(resourceFetcher, resourceCache, period, null, resourceClass, cacheKeyMapper); + public PerResourcePollingEventSource(Class resourceClass, EventSourceContext

context, + PerResourcePollingConfiguration config) { + this(null, resourceClass, context, config); } - public PerResourcePollingEventSource(ResourceFetcher resourceFetcher, + public PerResourcePollingEventSource(String name, Class resourceClass, EventSourceContext

context, - Duration defaultPollingPeriod, - Predicate

registerPredicate, - Class resourceClass, - CacheKeyMapper cacheKeyMapper) { - this(resourceFetcher, context.getPrimaryCache(), defaultPollingPeriod.toMillis(), - registerPredicate, resourceClass, cacheKeyMapper, - new ScheduledThreadPoolExecutor(DEFAULT_EXECUTOR_THREAD_NUMBER)); - } - - /** - * @deprecated use the variant which uses {@link EventSourceContext} instead of {@link Cache} and - * {@link Duration} for period parameter as it provides a more intuitive API. - * - * @param resourceFetcher fetches resource related to a primary resource - * @param resourceCache cache of the primary resource - * @param period default polling period - * @param resourceClass class of the target resource - * @param cacheKeyMapper use to distinguish resource in case more resources are handled for a - * single primary resource - * @param registerPredicate used to determine if the related resource for a custom resource should - * be polled or not. - */ - @Deprecated(forRemoval = true) - public PerResourcePollingEventSource(ResourceFetcher resourceFetcher, - Cache

resourceCache, long period, - Predicate

registerPredicate, Class resourceClass, - CacheKeyMapper cacheKeyMapper) { - this(resourceFetcher, resourceCache, period, registerPredicate, resourceClass, cacheKeyMapper, - new ScheduledThreadPoolExecutor(DEFAULT_EXECUTOR_THREAD_NUMBER)); - } - - - public PerResourcePollingEventSource( - ResourceFetcher resourceFetcher, - EventSourceContext

context, Duration defaultPollingPeriod, - Predicate

registerPredicate, Class resourceClass, - CacheKeyMapper cacheKeyMapper, ScheduledExecutorService executorService) { - this(resourceFetcher, context.getPrimaryCache(), defaultPollingPeriod.toMillis(), - registerPredicate, - resourceClass, cacheKeyMapper, executorService); - } - - /** - * @deprecated use the variant which uses {@link EventSourceContext} instead of {@link Cache} and - * {@link Duration} for period parameter as it provides a more intuitive API. - * - * @param resourceFetcher fetches resource related to a primary resource - * @param resourceCache cache of the primary resource - * @param period default polling period - * @param resourceClass class of the target resource - * @param cacheKeyMapper use to distinguish resource in case more resources are handled for a - * single primary resource - * @param registerPredicate used to determine if the related resource for a custom resource should - * be polled or not. - * @param executorService custom executor service - */ - - @Deprecated(forRemoval = true) - public PerResourcePollingEventSource( - ResourceFetcher resourceFetcher, - Cache

resourceCache, long period, - Predicate

registerPredicate, Class resourceClass, - CacheKeyMapper cacheKeyMapper, ScheduledExecutorService executorService) { - super(resourceClass, cacheKeyMapper); - this.resourceFetcher = resourceFetcher; - this.resourceCache = resourceCache; - this.period = period; - this.registerPredicate = registerPredicate; - this.executorService = executorService; + PerResourcePollingConfiguration config) { + super(name, resourceClass, config.cacheKeyMapper()); + this.primaryResourceCache = context.getPrimaryCache(); + this.resourceFetcher = config.resourceFetcher(); + this.registerPredicate = config.registerPredicate(); + this.executorService = config.executorService(); + this.period = config.defaultPollingPeriod(); } private Set getAndCacheResource(P primary, boolean fromGetter) { @@ -187,7 +77,7 @@ private Set getAndCacheResource(P primary, boolean fromGetter) { private void scheduleNextExecution(P primary, Set actualResources) { var primaryID = ResourceID.fromResource(primary); var fetchDelay = resourceFetcher.fetchDelay(actualResources, primary); - var fetchDuration = fetchDelay.orElse(Duration.ofMillis(period)); + var fetchDuration = fetchDelay.orElse(period); ScheduledFuture scheduledFuture = (ScheduledFuture) executorService .schedule(new FetchingExecutor(primaryID), fetchDuration.toMillis(), TimeUnit.MILLISECONDS); @@ -246,7 +136,7 @@ public void run() { return; } // always use up-to-date resource from cache - var primary = resourceCache.get(primaryID); + var primary = primaryResourceCache.get(primaryID); if (primary.isEmpty()) { log.warn("No resource in cache for resource ID: {}", primaryID); // no new execution is scheduled in this case, an on delete event should be received shortly diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/polling/PollingConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/polling/PollingConfiguration.java new file mode 100644 index 0000000000..516d0546f7 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/polling/PollingConfiguration.java @@ -0,0 +1,18 @@ +package io.javaoperatorsdk.operator.processing.event.source.polling; + +import java.time.Duration; +import java.util.Objects; + +import io.javaoperatorsdk.operator.processing.event.source.CacheKeyMapper; + +public record PollingConfiguration(PollingEventSource.GenericResourceFetcher genericResourceFetcher, + Duration period, CacheKeyMapper cacheKeyMapper) { + + public PollingConfiguration(PollingEventSource.GenericResourceFetcher genericResourceFetcher, Duration period, + CacheKeyMapper cacheKeyMapper) { + this.genericResourceFetcher = Objects.requireNonNull(genericResourceFetcher); + this.period = period; + this.cacheKeyMapper = + cacheKeyMapper == null ? CacheKeyMapper.singleResourceCacheKeyMapper() : cacheKeyMapper; + } +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/polling/PollingConfigurationBuilder.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/polling/PollingConfigurationBuilder.java new file mode 100644 index 0000000000..576f8fdb56 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/polling/PollingConfigurationBuilder.java @@ -0,0 +1,26 @@ +package io.javaoperatorsdk.operator.processing.event.source.polling; + +import java.time.Duration; + +import io.javaoperatorsdk.operator.processing.event.source.CacheKeyMapper; + +public final class PollingConfigurationBuilder { + private final Duration period; + private final PollingEventSource.GenericResourceFetcher genericResourceFetcher; + private CacheKeyMapper cacheKeyMapper; + + public PollingConfigurationBuilder(PollingEventSource.GenericResourceFetcher fetcher, + Duration period) { + this.genericResourceFetcher = fetcher; + this.period = period; + } + + public PollingConfigurationBuilder withCacheKeyMapper(CacheKeyMapper cacheKeyMapper) { + this.cacheKeyMapper = cacheKeyMapper; + return this; + } + + public PollingConfiguration build() { + return new PollingConfiguration<>(genericResourceFetcher, period, cacheKeyMapper); + } +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/polling/PollingEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/polling/PollingEventSource.java index 9ef889ecb6..060128576c 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/polling/PollingEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/polling/PollingEventSource.java @@ -1,6 +1,10 @@ package io.javaoperatorsdk.operator.processing.event.source.polling; -import java.util.*; +import java.time.Duration; +import java.util.Map; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; import java.util.concurrent.atomic.AtomicBoolean; import org.slf4j.Logger; @@ -10,7 +14,6 @@ import io.javaoperatorsdk.operator.OperatorException; import io.javaoperatorsdk.operator.health.Status; import io.javaoperatorsdk.operator.processing.event.ResourceID; -import io.javaoperatorsdk.operator.processing.event.source.CacheKeyMapper; import io.javaoperatorsdk.operator.processing.event.source.ExternalResourceCachingEventSource; /** @@ -46,26 +49,17 @@ public class PollingEventSource private final Timer timer = new Timer(); private final GenericResourceFetcher genericResourceFetcher; - private final long period; + private final Duration period; private final AtomicBoolean healthy = new AtomicBoolean(true); - public PollingEventSource( - GenericResourceFetcher supplier, - long period, - Class resourceClass) { - super(resourceClass, CacheKeyMapper.singleResourceCacheKeyMapper()); - this.genericResourceFetcher = supplier; - this.period = period; + public PollingEventSource(Class resourceClass, PollingConfiguration config) { + this(null, resourceClass, config); } - public PollingEventSource( - GenericResourceFetcher supplier, - long period, - Class resourceClass, - CacheKeyMapper cacheKeyMapper) { - super(resourceClass, cacheKeyMapper); - this.genericResourceFetcher = supplier; - this.period = period; + public PollingEventSource(String name, Class resourceClass, PollingConfiguration config) { + super(name, resourceClass, config.cacheKeyMapper()); + this.genericResourceFetcher = config.genericResourceFetcher(); + this.period = config.period(); } @Override @@ -89,8 +83,8 @@ public void run() { } } }, - period, - period); + period.toMillis(), + period.toMillis()); } protected synchronized void getStateAndFillCache() { diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/timer/TimerEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/timer/TimerEventSource.java index fe641e0b0b..b909083a00 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/timer/TimerEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/timer/TimerEventSource.java @@ -1,6 +1,7 @@ package io.javaoperatorsdk.operator.processing.event.source.timer; import java.util.Map; +import java.util.Set; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; @@ -15,13 +16,21 @@ import io.javaoperatorsdk.operator.processing.event.source.ResourceEventAware; public class TimerEventSource - extends AbstractEventSource + extends AbstractEventSource implements ResourceEventAware { private static final Logger log = LoggerFactory.getLogger(TimerEventSource.class); private Timer timer; private final Map onceTasks = new ConcurrentHashMap<>(); + public TimerEventSource() { + super(Void.class); + } + + public TimerEventSource(String name) { + super(Void.class, name); + } + @SuppressWarnings("unused") public void scheduleOnce(R resource, long delay) { scheduleOnce(ResourceID.fromResource(resource), delay); @@ -69,6 +78,11 @@ public void stop() { } } + @Override + public Set getSecondaryResources(HasMetadata primary) { + return Set.of(); + } + public class EventProducerTimeTask extends TimerTask { protected final ResourceID customResourceUid; diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/expiration/Expiration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/expiration/Expiration.java new file mode 100644 index 0000000000..ad813e599d --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/expiration/Expiration.java @@ -0,0 +1,8 @@ +package io.javaoperatorsdk.operator.processing.expiration; + + +public interface Expiration { + + ExpirationExecution initExecution(); + +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/expiration/ExpirationExecution.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/expiration/ExpirationExecution.java new file mode 100644 index 0000000000..23dcd004e9 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/expiration/ExpirationExecution.java @@ -0,0 +1,9 @@ +package io.javaoperatorsdk.operator.processing.expiration; + +public interface ExpirationExecution { + + boolean isExpired(); + + void refreshed(); + +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/expiration/RetryExpiration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/expiration/RetryExpiration.java new file mode 100644 index 0000000000..afe070f62f --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/expiration/RetryExpiration.java @@ -0,0 +1,17 @@ +package io.javaoperatorsdk.operator.processing.expiration; + +import io.javaoperatorsdk.operator.processing.retry.Retry; + +public class RetryExpiration implements Expiration { + + private final Retry retry; + + public RetryExpiration(Retry retry) { + this.retry = retry; + } + + @Override + public ExpirationExecution initExecution() { + return new RetryExpirationExecution(retry.initExecution()); + } +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/expiration/RetryExpirationExecution.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/expiration/RetryExpirationExecution.java new file mode 100644 index 0000000000..49ca377db7 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/expiration/RetryExpirationExecution.java @@ -0,0 +1,39 @@ +package io.javaoperatorsdk.operator.processing.expiration; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Objects; + +import io.javaoperatorsdk.operator.processing.retry.RetryExecution; + +public class RetryExpirationExecution implements ExpirationExecution { + + public static final long NO_MORE_EXPIRATION = -1L; + + private LocalDateTime lastRefreshTime; + private long delayUntilExpiration; + private final RetryExecution retryExecution; + + public RetryExpirationExecution(RetryExecution retryExecution) { + this.retryExecution = retryExecution; + } + + @Override + public boolean isExpired() { + if (lastRefreshTime == null) { + return true; + } + if (Objects.equals(delayUntilExpiration, NO_MORE_EXPIRATION)) { + return false; + } else { + return LocalDateTime.now() + .isAfter(lastRefreshTime.plus(delayUntilExpiration, ChronoUnit.MILLIS)); + } + } + + @Override + public void refreshed() { + lastRefreshTime = LocalDateTime.now(); + delayUntilExpiration = retryExecution.nextDelay().orElse(NO_MORE_EXPIRATION); + } +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/retry/GenericRetry.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/retry/GenericRetry.java index 9d5a83dc51..d1809de566 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/retry/GenericRetry.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/retry/GenericRetry.java @@ -1,7 +1,6 @@ package io.javaoperatorsdk.operator.processing.retry; import io.javaoperatorsdk.operator.api.config.AnnotationConfigurable; -import io.javaoperatorsdk.operator.api.config.RetryConfiguration; public class GenericRetry implements Retry, AnnotationConfigurable { private int maxAttempts = GradualRetry.DEFAULT_MAX_ATTEMPTS; @@ -19,23 +18,6 @@ public static GenericRetry noRetry() { return new GenericRetry().setMaxAttempts(0); } - /** - * @deprecated Use the {@link GradualRetry} annotation instead - * - * @param configuration retry config - * @return Retry instance - */ - @Deprecated(forRemoval = true) - public static Retry fromConfiguration(RetryConfiguration configuration) { - return configuration == null ? defaultLimitedExponentialRetry() - : new GenericRetry() - .setInitialInterval(configuration.getInitialInterval()) - .setMaxAttempts(configuration.getMaxAttempts()) - .setIntervalMultiplier(configuration.getIntervalMultiplier()) - .setMaxInterval(configuration.getMaxInterval()); - } - - public static GenericRetry every10second10TimesRetry() { return new GenericRetry().withLinearRetry().setMaxAttempts(10).setInitialInterval(10000); } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/MockKubernetesClient.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/MockKubernetesClient.java index eebf7a72d4..a5cd478ee4 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/MockKubernetesClient.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/MockKubernetesClient.java @@ -1,6 +1,6 @@ package io.javaoperatorsdk.operator; -import java.util.Arrays; +import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executors; import java.util.function.Consumer; @@ -12,22 +12,21 @@ import io.fabric8.kubernetes.api.model.authorization.v1.SubjectRulesReviewStatus; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.V1ApiextensionAPIGroupDSL; -import io.fabric8.kubernetes.client.dsl.*; +import io.fabric8.kubernetes.client.dsl.AnyNamespaceOperation; +import io.fabric8.kubernetes.client.dsl.ApiextensionsAPIGroupDSL; +import io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable; +import io.fabric8.kubernetes.client.dsl.Informable; +import io.fabric8.kubernetes.client.dsl.MixedOperation; +import io.fabric8.kubernetes.client.dsl.NamespaceableResource; +import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; +import io.fabric8.kubernetes.client.dsl.Resource; import io.fabric8.kubernetes.client.extended.leaderelection.LeaderElectorBuilder; import io.fabric8.kubernetes.client.informers.SharedIndexInformer; import io.fabric8.kubernetes.client.informers.cache.Indexer; import io.fabric8.kubernetes.client.utils.KubernetesSerialization; -import static io.javaoperatorsdk.operator.LeaderElectionManager.COORDINATION_GROUP; -import static io.javaoperatorsdk.operator.LeaderElectionManager.LEASES_RESOURCE; -import static io.javaoperatorsdk.operator.LeaderElectionManager.UNIVERSAL_VERB; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyLong; -import static org.mockito.Mockito.anyString; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.nullable; -import static org.mockito.Mockito.when; +import static io.javaoperatorsdk.operator.LeaderElectionManager.*; +import static org.mockito.Mockito.*; public class MockKubernetesClient { @@ -105,10 +104,10 @@ private static Object allowSelfSubjectReview() { SelfSubjectRulesReview review = new SelfSubjectRulesReview(); review.setStatus(new SubjectRulesReviewStatus()); var resourceRule = new ResourceRule(); - resourceRule.setApiGroups(Arrays.asList(COORDINATION_GROUP)); - resourceRule.setResources(Arrays.asList(LEASES_RESOURCE)); - resourceRule.setVerbs(Arrays.asList(UNIVERSAL_VERB)); - review.getStatus().setResourceRules(Arrays.asList(resourceRule)); + resourceRule.setApiGroups(List.of(COORDINATION_GROUP)); + resourceRule.setResources(List.of(LEASES_RESOURCE)); + resourceRule.setVerbs(List.of(UNIVERSAL_VERB)); + review.getStatus().setResourceRules(List.of(resourceRule)); return review; } } 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 7a4a5793ba..4c374e09f2 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 @@ -8,7 +8,6 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.monitoring.Metrics; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; class ConfigurationServiceOverriderTest { @@ -60,7 +59,6 @@ public R clone(R object) { } }) .withConcurrentReconciliationThreads(25) - .withTerminationTimeoutSeconds(100) .withMetrics(new Metrics() {}) .withLeaderElectionConfiguration(new LeaderElectionConfiguration("newLease", "newLeaseNS")) .withInformerStoppedHandler((informer, ex) -> { @@ -72,8 +70,6 @@ public R clone(R object) { overridden.checkCRDAndValidateLocalModel()); assertNotEquals(config.concurrentReconciliationThreads(), overridden.concurrentReconciliationThreads()); - assertNotEquals(config.getTerminationTimeoutSeconds(), - overridden.getTerminationTimeoutSeconds()); assertNotEquals(config.getExecutorService(), overridden.getExecutorService()); assertNotEquals(config.getWorkflowExecutorService(), overridden.getWorkflowExecutorService()); assertNotEquals(config.getMetrics(), overridden.getMetrics()); @@ -83,24 +79,4 @@ public R clone(R object) { overridden.getLeaderElectionConfiguration()); } - @Test - void shouldReplaceInvalidValues() { - final var original = new BaseConfigurationService(); - - final var service = ConfigurationService.newOverriddenConfigurationService(original, - o -> o - .withConcurrentReconciliationThreads(0) - .withMinConcurrentReconciliationThreads(-1) - .withConcurrentWorkflowExecutorThreads(2) - .withMinConcurrentWorkflowExecutorThreads(3)); - - assertEquals(original.minConcurrentReconciliationThreads(), - service.minConcurrentReconciliationThreads()); - assertEquals(original.concurrentReconciliationThreads(), - service.concurrentReconciliationThreads()); - assertEquals(3, service.minConcurrentWorkflowExecutorThreads()); - assertEquals(original.concurrentWorkflowExecutorThreads(), - service.concurrentWorkflowExecutorThreads()); - } - } 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 1c86886d89..e280009da2 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 @@ -16,6 +16,7 @@ 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.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; @@ -26,11 +27,7 @@ import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfigBuilder; import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; class ControllerConfigurationOverriderTest { private final BaseConfigurationService configurationService = new BaseConfigurationService(); @@ -46,7 +43,8 @@ void overridingNSShouldPreserveUntouchedDependents() { var configuration = createConfiguration(new NamedDependentReconciler()); // check that we have the proper number of dependent configs - var dependentResources = configuration.getDependentResources(); + var dependentResources = + configuration.getWorkflowSpec().orElseThrow().getDependentResourceSpecs(); assertEquals(2, dependentResources.size()); // override the NS @@ -61,60 +59,13 @@ void overridingNSShouldPreserveUntouchedDependents() { assertEquals(Set.of(namespace), configuration.getNamespaces()); // check that we still have the proper number of dependent configs - dependentResources = configuration.getDependentResources(); + dependentResources = configuration.getWorkflowSpec().orElseThrow().getDependentResourceSpecs(); assertEquals(2, dependentResources.size()); final var resourceConfig = extractDependentKubernetesResourceConfig( configuration, 1); assertEquals(stringConfig, resourceConfig); } - @ControllerConfiguration(dependents = { - @Dependent(type = NamedDependentReconciler.NamedDependentResource.class), - @Dependent(type = NamedDependentReconciler.ExternalDependentResource.class) - }) - private static class NamedDependentReconciler implements Reconciler { - - @Override - public UpdateControl reconcile(ConfigMap resource, Context context) - throws Exception { - return null; - } - - private static class NamedDependentResource - extends KubernetesDependentResource { - - public NamedDependentResource() { - super(ConfigMap.class); - } - } - - private static class ExternalDependentResource implements DependentResource, - DependentResourceConfigurator { - - private String config = "UNSET"; - - @Override - public ReconcileResult reconcile(ConfigMap primary, Context context) { - return null; - } - - @Override - public Class resourceType() { - return Object.class; - } - - @Override - public void configureWith(String config) { - this.config = config; - } - - @Override - public Optional configuration() { - return Optional.of(config); - } - } - } - @SuppressWarnings("rawtypes") private KubernetesDependentResourceConfig extractFirstDependentKubernetesResourceConfig( io.javaoperatorsdk.operator.api.config.ControllerConfiguration configuration) { @@ -124,7 +75,8 @@ private KubernetesDependentResourceConfig extractFirstDependentKubernetesResourc private Object extractDependentKubernetesResourceConfig( io.javaoperatorsdk.operator.api.config.ControllerConfiguration configuration, int index) { - final var spec = configuration.getDependentResources().get(index); + final var spec = + configuration.getWorkflowSpec().orElseThrow().getDependentResourceSpecs().get(index); return DependentResourceConfigurationResolver.configurationFor(spec, configuration); } @@ -145,8 +97,7 @@ public MyItemStore() { private static class WatchCurrentReconciler implements Reconciler { @Override - public UpdateControl reconcile(ConfigMap resource, Context context) - throws Exception { + public UpdateControl reconcile(ConfigMap resource, Context context) { return null; } } @@ -317,11 +268,11 @@ void alreadyOverriddenDependentNamespacesShouldNotBePropagated() { assertEquals(Set.of(OverriddenNSDependent.DEP_NS), config.namespaces()); } - @SuppressWarnings({"rawtypes", "unchecked"}) + @SuppressWarnings("rawtypes") @Test void replaceNamedDependentResourceConfigShouldWork() { var configuration = createConfiguration(new OneDepReconciler()); - var dependents = configuration.getDependentResources(); + var dependents = configuration.getWorkflowSpec().orElseThrow().getDependentResourceSpecs(); assertFalse(dependents.isEmpty()); assertEquals(1, dependents.size()); @@ -335,7 +286,7 @@ void replaceNamedDependentResourceConfigShouldWork() { var maybeConfig = DependentResourceConfigurationResolver.configurationFor(dependentSpec, configuration); assertNotNull(maybeConfig); - assertTrue(maybeConfig instanceof KubernetesDependentResourceConfig); + assertInstanceOf(KubernetesDependentResourceConfig.class, maybeConfig); var config = (KubernetesDependentResourceConfig) maybeConfig; // check that the DependentResource inherits the controller's configuration if applicable @@ -354,7 +305,7 @@ void replaceNamedDependentResourceConfigShouldWork() { .withLabelSelector(labelSelector) .build()) .build(); - dependents = overridden.getDependentResources(); + dependents = overridden.getWorkflowSpec().orElseThrow().getDependentResourceSpecs(); dependentSpec = dependents.stream().filter(dr -> dr.getName().equals(dependentResourceName)) .findFirst().orElseThrow(); config = (KubernetesDependentResourceConfig) DependentResourceConfigurationResolver @@ -363,10 +314,11 @@ void replaceNamedDependentResourceConfigShouldWork() { assertEquals(labelSelector, config.labelSelector()); assertEquals(Set.of(overriddenNS), config.namespaces()); // check that we still have the proper workflow configuration - assertTrue(dependentSpec.getReadyCondition() instanceof TestCondition); + assertInstanceOf(TestCondition.class, dependentSpec.getReadyCondition()); } - @ControllerConfiguration(dependents = @Dependent(type = ReadOnlyDependent.class)) + @Workflow(dependents = @Dependent(type = ReadOnlyDependent.class)) + @ControllerConfiguration private static class WatchAllNamespacesReconciler implements Reconciler { @Override @@ -375,7 +327,8 @@ public UpdateControl reconcile(ConfigMap resource, Context } } - @ControllerConfiguration(dependents = @Dependent(type = WatchAllNSDependent.class)) + @Workflow(dependents = @Dependent(type = WatchAllNSDependent.class)) + @ControllerConfiguration private static class DependentWatchesAllNSReconciler implements Reconciler { @Override @@ -394,9 +347,9 @@ public boolean isMet(DependentResource dependentResource, } } - @ControllerConfiguration(namespaces = OneDepReconciler.CONFIGURED_NS, - dependents = @Dependent(type = ReadOnlyDependent.class, - readyPostcondition = TestCondition.class)) + @Workflow(dependents = @Dependent(type = ReadOnlyDependent.class, + readyPostcondition = TestCondition.class)) + @ControllerConfiguration(namespaces = OneDepReconciler.CONFIGURED_NS) private static class OneDepReconciler implements Reconciler { private static final String CONFIGURED_NS = "foo"; @@ -423,8 +376,8 @@ public WatchAllNSDependent() { } } - @ControllerConfiguration(namespaces = OverriddenNSOnDepReconciler.CONFIGURED_NS, - dependents = @Dependent(type = OverriddenNSDependent.class)) + @Workflow(dependents = @Dependent(type = OverriddenNSDependent.class)) + @ControllerConfiguration(namespaces = OverriddenNSOnDepReconciler.CONFIGURED_NS) private static class OverriddenNSOnDepReconciler implements Reconciler { private static final String CONFIGURED_NS = "parentNS"; @@ -445,4 +398,51 @@ public OverriddenNSDependent() { super(ConfigMap.class); } } + + @Workflow(dependents = { + @Dependent(type = NamedDependentReconciler.NamedDependentResource.class), + @Dependent(type = NamedDependentReconciler.ExternalDependentResource.class) + }) + @ControllerConfiguration + private static class NamedDependentReconciler implements Reconciler { + + @Override + public UpdateControl reconcile(ConfigMap resource, Context context) { + return null; + } + + private static class NamedDependentResource + extends KubernetesDependentResource { + + public NamedDependentResource() { + super(ConfigMap.class); + } + } + + private static class ExternalDependentResource implements DependentResource, + DependentResourceConfigurator { + + private String config = "UNSET"; + + @Override + public ReconcileResult reconcile(ConfigMap primary, Context context) { + return null; + } + + @Override + public Class resourceType() { + return Object.class; + } + + @Override + public void configureWith(String config) { + this.config = config; + } + + @Override + public Optional configuration() { + return Optional.of(config); + } + } + } } 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 3187b32645..702a12d124 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 @@ -16,6 +16,7 @@ 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.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; @@ -24,10 +25,7 @@ import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource; import static io.javaoperatorsdk.operator.api.config.dependent.DependentResourceConfigurationResolverTest.CustomAnnotationReconciler.DR_NAME; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; class DependentResourceConfigurationResolverTest { @@ -54,13 +52,13 @@ void controllerConfigurationProvidedShouldBeReturnedIfAvailable() { final var cfg = configFor(new CustomAnnotationReconciler()); final var customConfig = DependentResourceConfigurationResolver .extractConfigurationFromConfigured(CustomAnnotatedDep.class, cfg); - assertTrue(customConfig instanceof CustomConfig); + assertInstanceOf(CustomConfig.class, customConfig); assertEquals(CustomAnnotatedDep.PROVIDED_VALUE, ((CustomConfig) customConfig).getValue()); final var newConfig = new CustomConfig(72); final var overridden = ControllerConfigurationOverrider.override(cfg) .replacingNamedDependentResourceConfig(DR_NAME, newConfig) .build(); - final var spec = cfg.getDependentResources().stream() + final var spec = cfg.getWorkflowSpec().orElseThrow().getDependentResourceSpecs().stream() .filter(s -> DR_NAME.equals(s.getName())) .findFirst() .orElseThrow(); @@ -102,7 +100,7 @@ void registerConverterShouldWork() { DependentResourceConfigurationResolver.extractConfigurationFromConfigured(ConfigMapDep.class, cfg); converter = DependentResourceConfigurationResolver.getConverter(ConfigMapDep.class); - assertTrue(converter instanceof KubernetesDependentConverter); + assertInstanceOf(KubernetesDependentConverter.class, converter); final var overriddenConverter = new ConfigurationConverter() { @Override public Object configFrom(Annotation configAnnotation, @@ -116,7 +114,7 @@ public Object configFrom(Annotation configAnnotation, // already resolved converters are kept unchanged converter = DependentResourceConfigurationResolver.getConverter(ConfigMapDep.class); - assertTrue(converter instanceof KubernetesDependentConverter); + assertInstanceOf(KubernetesDependentConverter.class, converter); // but new converters should use the overridden version DependentResourceConfigurationResolver.extractConfigurationFromConfigured(ServiceDep.class, @@ -125,12 +123,13 @@ public Object configFrom(Annotation configAnnotation, assertEquals(overriddenConverter, converter); } - @ControllerConfiguration(dependents = { + @Workflow(dependents = { @Dependent(type = CustomAnnotatedDep.class, name = DR_NAME), @Dependent(type = ChildCustomAnnotatedDep.class), @Dependent(type = ConfigMapDep.class), @Dependent(type = ServiceDep.class) }) + @ControllerConfiguration static class CustomAnnotationReconciler implements Reconciler { public static final String DR_NAME = "first"; diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceInitializerTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceInitializerTest.java deleted file mode 100644 index b89ae730ee..0000000000 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceInitializerTest.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.javaoperatorsdk.operator.api.reconciler; - -import java.util.HashMap; - -import org.junit.jupiter.api.Test; - -import io.javaoperatorsdk.operator.processing.event.source.polling.PollingEventSource; - -import static org.assertj.core.api.Assertions.assertThat; - -class EventSourceInitializerTest { - - @Test - @SuppressWarnings({"rawtypes", "unchecked"}) - void defaultNameDifferentForOtherInstance() { - var eventSource1 = new PollingEventSource(HashMap::new, 1000, String.class); - var eventSource2 = new PollingEventSource(HashMap::new, 1000, String.class); - var eventSourceName1 = EventSourceInitializer.generateNameFor(eventSource1); - var eventSourceName2 = EventSourceInitializer.generateNameFor(eventSource2); - - assertThat(eventSourceName1).isNotEqualTo(eventSourceName2); - } - -} diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/EmptyTestDependentResource.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/EmptyTestDependentResource.java index 4fe88296ac..25a849e3ab 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/EmptyTestDependentResource.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/EmptyTestDependentResource.java @@ -9,6 +9,8 @@ public class EmptyTestDependentResource implements DependentResource { + private String name; + @Override public ReconcileResult reconcile(TestCustomResource primary, Context context) { @@ -19,5 +21,13 @@ public ReconcileResult reconcile(TestCustomResource primary, public Class resourceType() { return Deployment.class; } -} + @Override + public String name() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} 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 a2eea9279c..370d3d62c1 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 @@ -55,8 +55,7 @@ void matchesAdditiveOnlyChanges() { @Test void matchesWithStrongSpecEquality() { actual.getSpec().getTemplate().getMetadata().getLabels().put("new-key", "val"); - assertThat(match(dependentResource, actual, null, context, true, true, - true) + assertThat(match(desired, actual, true, true, context) .matched()) .withFailMessage("Adding values should fail matching when strong equality is required") .isFalse(); @@ -127,11 +126,11 @@ void matchesMetadata() { .withFailMessage("Annotations shouldn't matter when metadata is not considered") .isTrue(); - assertThat(match(dependentResource, actual, null, context, true, true, true).matched()) + assertThat(match(desired, actual, true, true, context).matched()) .withFailMessage("Annotations should matter when metadata is considered") .isFalse(); - assertThat(match(dependentResource, actual, null, context, true, false).matched()) + assertThat(match(desired, actual, false, false, context).matched()) .withFailMessage( "Should match when strong equality is not considered and only additive changes are made") .isTrue(); @@ -157,7 +156,7 @@ void matchConfigMap() { var actual = createConfigMap(); actual.getData().put("key2", "val2"); - var match = GenericKubernetesResourceMatcher.match(desired, actual, true, + var match = GenericKubernetesResourceMatcher.match(desired, actual, true, false, context); assertThat(match.matched()).isTrue(); } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutorTest.java index 941c3fa434..adebd635f7 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutorTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutorTest.java @@ -35,11 +35,13 @@ public class AbstractWorkflowExecutorTest { public class TestDependent extends KubernetesDependentResource { - private final String name; - public TestDependent(String name) { - super(ConfigMap.class); - this.name = name; + super(ConfigMap.class, name); + } + + @Override + protected Class getPrimaryResourceType() { + return TestCustomResource.class; } @Override @@ -52,7 +54,7 @@ public ReconcileResult reconcile(TestCustomResource primary, @Override public String toString() { - return name; + return name(); } } @@ -110,6 +112,11 @@ public Class resourceType() { return String.class; } + @Override + public String name() { + return name; + } + @Override public String toString() { return name; diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowTest.java index 649e0c3050..e634a368d7 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowTest.java @@ -1,12 +1,14 @@ package io.javaoperatorsdk.operator.processing.dependent.workflow; import java.util.List; +import java.util.Optional; import org.junit.jupiter.api.Test; import io.javaoperatorsdk.operator.api.config.BaseConfigurationService; import io.javaoperatorsdk.operator.api.config.ControllerConfiguration; import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec; +import io.javaoperatorsdk.operator.api.config.workflow.WorkflowSpec; import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; import io.javaoperatorsdk.operator.api.reconciler.dependent.GarbageCollected; @@ -60,9 +62,25 @@ void isCleanerIfHasDeleter() { @SuppressWarnings("unchecked") ManagedWorkflow managedWorkflow(DependentResourceSpec... specs) { final var configuration = mock(ControllerConfiguration.class); - final var specList = List.of(specs); - when(configuration.getDependentResources()).thenReturn(specList); + var ws = new WorkflowSpec() { + @Override + public List getDependentResourceSpecs() { + return List.of(specs); + } + + @Override + public boolean isExplicitInvocation() { + return false; + } + + @Override + public boolean handleExceptionsInReconciler() { + return false; + } + }; + when(configuration.getWorkflowSpec()).thenReturn(Optional.of(ws)); + return new BaseConfigurationService().getWorkflowFactory() .workflowFor(configuration); } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowTestUtils.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowTestUtils.java index b314b5b112..ee416473db 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowTestUtils.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowTestUtils.java @@ -21,7 +21,7 @@ public class ManagedWorkflowTestUtils { @SuppressWarnings("unchecked") public static DependentResourceSpec createDRS(String name, String... dependOns) { return new DependentResourceSpec(EmptyTestDependentResource.class, name, Set.of(dependOns), - null, null, null, null, null); + null, null, null, null, null, false); } public static DependentResourceSpec createDRSWithTraits(String name, diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowBuilderTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowBuilderTest.java index 9a61931a61..b41ee430f7 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowBuilderTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowBuilderTest.java @@ -13,8 +13,10 @@ class WorkflowBuilderTest { @Test void workflowIsCleanerIfAtLeastOneDRIsCleaner() { var dr = mock(DependentResource.class); + when(dr.name()).thenReturn("dr"); var deleter = mock(DependentResource.class); when(deleter.isDeletable()).thenReturn(true); + when(deleter.name()).thenReturn("deleter"); var workflow = new WorkflowBuilder() .addDependentResource(deleter) diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java index 099d5fad2b..c66fcb02d6 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java @@ -538,7 +538,7 @@ void reconcilePreconditionNotCheckedOnNonActiveDependent() { @Test void deletesDependentsOfNonActiveDependentButNotTheNonActive() { TestDeleterDependent drDeleter2 = new TestDeleterDependent("DR_DELETER_2"); - TestDeleterDependent drDeleter3 = new TestDeleterDependent("DR_DELETER_2"); + TestDeleterDependent drDeleter3 = new TestDeleterDependent("DR_DELETER_3"); var workflow = new WorkflowBuilder() .addDependentResource(dr1).withActivationCondition(notMetCondition) diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowResultTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowResultTest.java index 48cf3fa75a..c89ca53f07 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowResultTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowResultTest.java @@ -28,8 +28,8 @@ void throwsExceptionWithoutNumberingIfAllDifferentClass() { @Test void numbersDependentClassNamesIfMoreOfSameType() { - var res = new WorkflowResult(Map.of(new DependentA(), new RuntimeException(), - new DependentA(), new RuntimeException())); + var res = new WorkflowResult(Map.of(new DependentA("name1"), new RuntimeException(), + new DependentA("name2"), new RuntimeException())); try { res.throwAggregateExceptionIfErrorsPresent(); } catch (AggregatedOperatorException e) { @@ -39,6 +39,25 @@ void numbersDependentClassNamesIfMoreOfSameType() { @SuppressWarnings("rawtypes") static class DependentA implements DependentResource { + + private final String name; + + public DependentA() { + this(null); + } + + public DependentA(String name) { + this.name = name; + } + + @Override + public String name() { + if (name == null) { + return DependentResource.super.name(); + } + return name; + } + @Override public ReconcileResult reconcile(HasMetadata primary, Context context) { return null; diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java index bc6ab8515f..ef48fccd6e 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowTest.java @@ -17,8 +17,7 @@ import static org.junit.Assert.assertThrows; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.withSettings; +import static org.mockito.Mockito.*; @SuppressWarnings("rawtypes") class WorkflowTest { @@ -27,9 +26,9 @@ class WorkflowTest { @Test void zeroTopLevelDRShouldThrowException() { - var dr1 = mock(DependentResource.class); - var dr2 = mock(DependentResource.class); - var dr3 = mock(DependentResource.class); + var dr1 = mockDependent("dr1"); + var dr2 = mockDependent("dr2"); + var dr3 = mockDependent("dr3"); var cyclicWorkflowBuilderSetup = new WorkflowBuilder() .addDependentResource(dr1).dependsOn() @@ -43,9 +42,9 @@ void zeroTopLevelDRShouldThrowException() { @Test void calculatesTopLevelResources() { - var dr1 = mock(DependentResource.class); - var dr2 = mock(DependentResource.class); - var independentDR = mock(DependentResource.class); + var dr1 = mockDependent("dr1"); + var dr2 = mockDependent("dr2"); + var independentDR = mockDependent("independentDR"); var workflow = new WorkflowBuilder() .addDependentResource(independentDR) @@ -63,9 +62,9 @@ void calculatesTopLevelResources() { @Test void calculatesBottomLevelResources() { - var dr1 = mock(DependentResource.class); - var dr2 = mock(DependentResource.class); - var independentDR = mock(DependentResource.class); + var dr1 = mockDependent("dr1"); + var dr2 = mockDependent("dr2"); + var independentDR = mockDependent("independentDR"); Workflow workflow = new WorkflowBuilder() .addDependentResource(independentDR) @@ -100,4 +99,11 @@ void isDeletableShouldWork() { GarbageCollected.class)); assertFalse(DefaultWorkflow.isDeletable(dr.getClass())); } + + static DependentResource mockDependent(String name) { + var res = mock(DependentResource.class); + when(res.name()).thenReturn(name); + return res; + } + } 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 93e58a55c6..9f7e390c0a 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 @@ -18,16 +18,16 @@ import io.javaoperatorsdk.operator.api.config.BaseConfigurationService; import io.javaoperatorsdk.operator.api.config.ConfigurationService; import io.javaoperatorsdk.operator.api.config.ControllerConfiguration; -import io.javaoperatorsdk.operator.api.config.RetryConfiguration; import io.javaoperatorsdk.operator.api.monitoring.Metrics; import io.javaoperatorsdk.operator.processing.event.rate.LinearRateLimiter; import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter; import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter.RateLimitState; -import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerResourceEventSource; +import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerEventSource; import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceAction; import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEvent; import io.javaoperatorsdk.operator.processing.event.source.timer.TimerEventSource; import io.javaoperatorsdk.operator.processing.retry.GenericRetry; +import io.javaoperatorsdk.operator.processing.retry.GradualRetry; import io.javaoperatorsdk.operator.processing.retry.Retry; import io.javaoperatorsdk.operator.processing.retry.RetryExecution; import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; @@ -66,8 +66,8 @@ class EventProcessorTest { mock(ReconciliationDispatcher.class); private final EventSourceManager eventSourceManagerMock = mock(EventSourceManager.class); private final TimerEventSource retryTimerEventSourceMock = mock(TimerEventSource.class); - private final ControllerResourceEventSource controllerResourceEventSourceMock = - mock(ControllerResourceEventSource.class); + private final ControllerEventSource controllerEventSourceMock = + mock(ControllerEventSource.class); private final Metrics metricsMock = mock(Metrics.class); private EventProcessor eventProcessor; private EventProcessor eventProcessorWithRetry; @@ -75,8 +75,8 @@ class EventProcessorTest { @BeforeEach void setup() { - when(eventSourceManagerMock.getControllerResourceEventSource()) - .thenReturn(controllerResourceEventSourceMock); + when(eventSourceManagerMock.getControllerEventSource()) + .thenReturn(controllerEventSourceMock); eventProcessor = spy(new EventProcessor(controllerConfiguration(null, rateLimiterMock), reconciliationDispatcherMock, @@ -103,7 +103,7 @@ void dispatchesEventsIfNoExecutionInProgress() { @Test void skipProcessingIfLatestCustomResourceNotInCache() { Event event = prepareCREvent(); - when(controllerResourceEventSourceMock.get(event.getRelatedCustomResourceID())) + when(controllerEventSourceMock.get(event.getRelatedCustomResourceID())) .thenReturn(Optional.empty()); eventProcessor.handleEvent(event); @@ -135,7 +135,7 @@ void schedulesAnEventRetryOnException() { verify(retryTimerEventSourceMock, times(1)) .scheduleOnce(eq(ResourceID.fromResource(customResource)), - eq(RetryConfiguration.DEFAULT_INITIAL_INTERVAL)); + eq(GradualRetry.DEFAULT_INITIAL_INTERVAL)); } @Test @@ -167,7 +167,7 @@ void executesTheControllerInstantlyAfterErrorIfNewEventsReceived() { assertThat(allValues).hasSize(2); verify(retryTimerEventSourceMock, never()) .scheduleOnce(eq(ResourceID.fromResource(customResource)), - eq(RetryConfiguration.DEFAULT_INITIAL_INTERVAL)); + eq(GradualRetry.DEFAULT_INITIAL_INTERVAL)); } @Test @@ -275,7 +275,7 @@ void startProcessedMarkedEventReceivedBefore() { LinearRateLimiter.deactivatedRateLimiter()), reconciliationDispatcherMock, eventSourceManagerMock, metricsMock)); - when(controllerResourceEventSourceMock.get(eq(crID))) + when(controllerEventSourceMock.get(eq(crID))) .thenReturn(Optional.of(testCustomResource())); eventProcessor.handleEvent(new Event(crID)); @@ -287,21 +287,6 @@ void startProcessedMarkedEventReceivedBefore() { verify(metricsMock, times(1)).reconcileCustomResource(any(HasMetadata.class), isNull(), any()); } - @Test - void updatesEventSourceHandlerIfResourceUpdated() { - TestCustomResource customResource = testCustomResource(); - ExecutionScope executionScope = - new ExecutionScope(null).setResource(customResource); - PostExecutionControl postExecutionControl = - PostExecutionControl.customResourceUpdated(customResource); - - eventProcessorWithRetry.eventProcessingFinished(executionScope, postExecutionControl); - - - verify(controllerResourceEventSourceMock, times(1)).handleRecentResourceUpdate(any(), any(), - any()); - } - @Test void notUpdatesEventSourceHandlerIfResourceUpdated() { TestCustomResource customResource = testCustomResource(); @@ -312,7 +297,7 @@ void notUpdatesEventSourceHandlerIfResourceUpdated() { eventProcessorWithRetry.eventProcessingFinished(executionScope, postExecutionControl); - verify(controllerResourceEventSourceMock, times(0)).handleRecentResourceUpdate(any(), any(), + verify(controllerEventSourceMock, times(0)).handleRecentResourceUpdate(any(), any(), any()); } @@ -367,7 +352,7 @@ void newResourceAfterMissedDeleteEvent() { @Test void rateLimitsReconciliationSubmission() { - // the refresh period value does not matter here + // the refresh defaultPollingPeriod value does not matter here var refreshPeriod = Duration.ofMillis(100); var event = prepareCREvent(); @@ -441,8 +426,8 @@ void executionOfReconciliationShouldNotStartIfProcessorStopped() throws Interrup eventSourceManagerMock, null)); eventProcessor.start(); - eventProcessor.handleEvent(prepareCREvent()); - eventProcessor.handleEvent(prepareCREvent()); + eventProcessor.handleEvent(prepareCREvent(new ResourceID("test1","default"))); + eventProcessor.handleEvent(prepareCREvent(new ResourceID("test1","default"))); eventProcessor.stop(); // wait until both event should be handled @@ -483,7 +468,7 @@ private ResourceEvent prepareCREvent() { } private ResourceEvent prepareCREvent(HasMetadata hasMetadata) { - when(controllerResourceEventSourceMock.get(eq(ResourceID.fromResource(hasMetadata)))) + when(controllerEventSourceMock.get(eq(ResourceID.fromResource(hasMetadata)))) .thenReturn(Optional.of(hasMetadata)); return new ResourceEvent(ResourceAction.UPDATED, ResourceID.fromResource(hasMetadata), hasMetadata); @@ -491,7 +476,7 @@ private ResourceEvent prepareCREvent(HasMetadata hasMetadata) { private ResourceEvent prepareCREvent(ResourceID resourceID) { TestCustomResource customResource = testCustomResource(resourceID); - when(controllerResourceEventSourceMock.get(eq(resourceID))) + when(controllerEventSourceMock.get(eq(resourceID))) .thenReturn(Optional.of(customResource)); return new ResourceEvent(ResourceAction.UPDATED, ResourceID.fromResource(customResource), customResource); diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/EventSourceManagerTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/EventSourceManagerTest.java index b56d79e6ed..7e45bda68c 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/EventSourceManagerTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/EventSourceManagerTest.java @@ -13,9 +13,10 @@ import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; import io.javaoperatorsdk.operator.api.reconciler.Reconciler; import io.javaoperatorsdk.operator.processing.Controller; +import io.javaoperatorsdk.operator.processing.event.source.AbstractEventSource; import io.javaoperatorsdk.operator.processing.event.source.EventSource; import io.javaoperatorsdk.operator.processing.event.source.EventSourceStartPriority; -import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerResourceEventSource; +import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerEventSource; import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; import io.javaoperatorsdk.operator.processing.event.source.informer.ManagedInformerEventSource; import io.javaoperatorsdk.operator.processing.event.source.timer.TimerEventSource; @@ -34,10 +35,11 @@ class EventSourceManagerTest { @Test public void registersEventSource() { EventSource eventSource = mock(EventSource.class); + when(eventSource.resourceType()).thenReturn(EventSource.class); eventSourceManager.registerEventSource(eventSource); - Set registeredSources = eventSourceManager.getRegisteredEventSources(); + final var registeredSources = eventSourceManager.getRegisteredEventSources(); assertThat(registeredSources).contains(eventSource); verify(eventSource, times(1)).setEventHandler(any()); @@ -46,7 +48,12 @@ public void registersEventSource() { @Test public void closeShouldCascadeToEventSources() { EventSource eventSource = mock(EventSource.class); + when(eventSource.name()).thenReturn("name1"); + when(eventSource.resourceType()).thenReturn(EventSource.class); + EventSource eventSource2 = mock(TimerEventSource.class); + when(eventSource2.name()).thenReturn("name2"); + when(eventSource2.resourceType()).thenReturn(AbstractEventSource.class); eventSourceManager.registerEventSource(eventSource); eventSourceManager.registerEventSource(eventSource2); @@ -61,8 +68,12 @@ public void closeShouldCascadeToEventSources() { public void startCascadesToEventSources() { EventSource eventSource = mock(EventSource.class); when(eventSource.priority()).thenReturn(EventSourceStartPriority.DEFAULT); + when(eventSource.name()).thenReturn("name1"); + when(eventSource.resourceType()).thenReturn(EventSource.class); EventSource eventSource2 = mock(TimerEventSource.class); when(eventSource2.priority()).thenReturn(EventSourceStartPriority.DEFAULT); + when(eventSource2.name()).thenReturn("name2"); + when(eventSource2.resourceType()).thenReturn(AbstractEventSource.class); eventSourceManager.registerEventSource(eventSource); eventSourceManager.registerEventSource(eventSource2); @@ -75,42 +86,42 @@ public void startCascadesToEventSources() { @Test void retrievingEventSourceForClassShouldWork() { assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> eventSourceManager.getResourceEventSourceFor(Class.class)); + .isThrownBy(() -> eventSourceManager.getEventSourceFor(Class.class)); // manager is initialized with a controller configured to handle HasMetadata EventSourceManager manager = initManager(); assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> manager.getResourceEventSourceFor(HasMetadata.class, "unknown_name")); + .isThrownBy(() -> manager.getEventSourceFor(HasMetadata.class, "unknown_name")); ManagedInformerEventSource eventSource = mock(ManagedInformerEventSource.class); when(eventSource.resourceType()).thenReturn(String.class); manager.registerEventSource(eventSource); - var source = manager.getResourceEventSourceFor(String.class); + var source = manager.getEventSourceFor(String.class); assertThat(source).isNotNull(); assertEquals(eventSource, source); } @Test - void shouldNotBePossibleToAddEventSourcesForSameTypeAndName() { + void notPossibleAddEventSourcesForSameName() { EventSourceManager manager = initManager(); final var name = "name1"; ManagedInformerEventSource eventSource = mock(ManagedInformerEventSource.class); + when(eventSource.name()).thenReturn(name); when(eventSource.resourceType()).thenReturn(TestCustomResource.class); - manager.registerEventSource(name, eventSource); + manager.registerEventSource(eventSource); eventSource = mock(ManagedInformerEventSource.class); when(eventSource.resourceType()).thenReturn(TestCustomResource.class); + when(eventSource.name()).thenReturn(name); final var source = eventSource; final var exception = assertThrows(OperatorException.class, - () -> manager.registerEventSource(name, source)); + () -> manager.registerEventSource(source)); final var cause = exception.getCause(); - assertTrue(cause instanceof IllegalArgumentException); - assertThat(cause.getMessage()).contains( - "is already registered for the (io.javaoperatorsdk.operator.sample.simple.TestCustomResource, " - + name + ") class/name combination"); + assertInstanceOf(IllegalArgumentException.class, cause); + assertThat(cause.getMessage()).contains("is already registered with name"); } @Test @@ -119,20 +130,23 @@ void retrievingAnEventSourceWhenMultipleAreRegisteredForATypeShouldRequireAQuali ManagedInformerEventSource eventSource = mock(ManagedInformerEventSource.class); when(eventSource.resourceType()).thenReturn(TestCustomResource.class); - manager.registerEventSource("name1", eventSource); + when(eventSource.name()).thenReturn("name1"); + manager.registerEventSource(eventSource); + ManagedInformerEventSource eventSource2 = mock(ManagedInformerEventSource.class); + when(eventSource2.name()).thenReturn("name2"); when(eventSource2.resourceType()).thenReturn(TestCustomResource.class); - manager.registerEventSource("name2", eventSource2); + manager.registerEventSource(eventSource2); final var exception = assertThrows(IllegalArgumentException.class, - () -> manager.getResourceEventSourceFor(TestCustomResource.class)); + () -> manager.getEventSourceFor(TestCustomResource.class)); assertTrue(exception.getMessage().contains("name1")); assertTrue(exception.getMessage().contains("name2")); - assertEquals(manager.getResourceEventSourceFor(TestCustomResource.class, "name2"), + assertEquals(manager.getEventSourceFor(TestCustomResource.class, "name2"), eventSource2); - assertEquals(manager.getResourceEventSourceFor(TestCustomResource.class, "name1"), + assertEquals(manager.getEventSourceFor(TestCustomResource.class, "name1"), eventSource); } @@ -149,18 +163,19 @@ void changesNamespacesOnControllerAndInformerEventSources() { MockKubernetesClient.client(HasMetadata.class)); EventSources eventSources = spy(new EventSources()); - var controllerResourceEventSourceMock = mock(ControllerResourceEventSource.class); - doReturn(controllerResourceEventSourceMock).when(eventSources).controllerResourceEventSource(); + var controllerResourceEventSourceMock = mock(ControllerEventSource.class); + doReturn(controllerResourceEventSourceMock).when(eventSources).controllerEventSource(); when(controllerResourceEventSourceMock.allowsNamespaceChanges()).thenCallRealMethod(); var manager = new EventSourceManager(controller, eventSources); InformerConfiguration informerConfigurationMock = mock(InformerConfiguration.class); when(informerConfigurationMock.followControllerNamespaceChanges()).thenReturn(true); InformerEventSource informerEventSource = mock(InformerEventSource.class); + when(informerEventSource.name()).thenReturn("ies"); when(informerEventSource.resourceType()).thenReturn(TestCustomResource.class); when(informerEventSource.configuration()).thenReturn(informerConfigurationMock); when(informerEventSource.allowsNamespaceChanges()).thenCallRealMethod(); - manager.registerEventSource("ies", informerEventSource); + manager.registerEventSource(informerEventSource); manager.changeNamespaces(Set.of(newNamespaces)); diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/EventSourcesTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/EventSourcesTest.java index db2a69609d..3d0d92da4f 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/EventSourcesTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/EventSourcesTest.java @@ -11,9 +11,7 @@ import io.javaoperatorsdk.operator.api.reconciler.Reconciler; import io.javaoperatorsdk.operator.processing.Controller; import io.javaoperatorsdk.operator.processing.event.source.EventSource; -import io.javaoperatorsdk.operator.processing.event.source.ResourceEventSource; -import static io.javaoperatorsdk.operator.processing.event.EventSources.RETRY_RESCHEDULE_TIMER_EVENT_SOURCE_NAME; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -29,99 +27,101 @@ class EventSourcesTest { @Test void cannotAddTwoDifferentEventSourcesWithSameName() { final var eventSources = new EventSources(); + var es1 = mock(EventSource.class); + when(es1.name()).thenReturn(EVENT_SOURCE_NAME); + when(es1.resourceType()).thenReturn(EventSource.class); + var es2 = mock(EventSource.class); + when(es2.name()).thenReturn(EVENT_SOURCE_NAME); + when(es2.resourceType()).thenReturn(EventSource.class); + + eventSources.add(es1); assertThrows(IllegalArgumentException.class, () -> { - eventSources.add(new NamedEventSource(mock(EventSource.class), "name")); - eventSources.add(new NamedEventSource(mock(EventSource.class), "name")); + eventSources.add(es2); }); } @Test - void cannotAddTwoEventSourcesWithSameNameUnlessTheyAreEqual() { + void cannotAddTwoEventSourcesWithSame() { final var eventSources = new EventSources(); final var source = mock(EventSource.class); - eventSources.add(new NamedEventSource(source, "name")); - eventSources.add(new NamedEventSource(source, "name")); - assertThat(eventSources.flatMappedSources()) - .containsExactly(new NamedEventSource(source, "name")); - } + when(source.name()).thenReturn("name"); + when(source.resourceType()).thenReturn(EventSource.class); + eventSources.add(source); + assertThrows(IllegalArgumentException.class, () -> eventSources.add(source)); + } @Test void eventSourcesStreamShouldNotReturnControllerEventSource() { final var eventSources = new EventSources(); final var source = mock(EventSource.class); - final var namedEventSource = new NamedEventSource(source, EVENT_SOURCE_NAME); - eventSources.add(namedEventSource); + when(source.name()).thenReturn(EVENT_SOURCE_NAME); + when(source.resourceType()).thenReturn(EventSource.class); - assertThat(eventSources.additionalNamedEventSources()).containsExactly( - new NamedEventSource(eventSources.retryEventSource(), - RETRY_RESCHEDULE_TIMER_EVENT_SOURCE_NAME), - namedEventSource); + eventSources.add(source); + + assertThat(eventSources.additionalEventSources()).containsExactly( + eventSources.retryEventSource(), + source); } @Test void additionalEventSourcesShouldNotContainNamedEventSources() { final var eventSources = new EventSources(); final var source = mock(EventSource.class); - final var namedEventSource = new NamedEventSource(source, EVENT_SOURCE_NAME); - eventSources.add(namedEventSource); + when(source.name()).thenReturn(EVENT_SOURCE_NAME); + when(source.resourceType()).thenReturn(EventSource.class); + eventSources.add(source); + assertThat(eventSources.additionalEventSources()).containsExactly( eventSources.retryEventSource(), source); } @Test - void checkControllerResourceEventSource() { + void checkControllerEventSource() { final var eventSources = new EventSources(); final var configuration = MockControllerConfiguration.forResource(HasMetadata.class); when(configuration.getConfigurationService()).thenReturn(new BaseConfigurationService()); final var controller = new Controller(mock(Reconciler.class), configuration, MockKubernetesClient.client(HasMetadata.class)); eventSources.createControllerEventSource(controller); - final var controllerResourceEventSource = eventSources.controllerResourceEventSource(); - assertNotNull(controllerResourceEventSource); - assertEquals(HasMetadata.class, controllerResourceEventSource.resourceType()); + final var controllerEventSource = eventSources.controllerEventSource(); + assertNotNull(controllerEventSource); + assertEquals(HasMetadata.class, controllerEventSource.resourceType()); - assertEquals(controllerResourceEventSource, - eventSources.namedControllerResourceEventSource().eventSource()); + assertEquals(controllerEventSource, + eventSources.controllerEventSource()); } @Test void flatMappedSourcesShouldReturnOnlyUserRegisteredEventSources() { final var eventSources = new EventSources(); - final var mock1 = mock(ResourceEventSource.class); - when(mock1.resourceType()).thenReturn(HasMetadata.class); - final var mock2 = mock(ResourceEventSource.class); - when(mock2.resourceType()).thenReturn(HasMetadata.class); - final var mock3 = mock(ResourceEventSource.class); - when(mock3.resourceType()).thenReturn(ConfigMap.class); - - final var named1 = new NamedEventSource(mock1, "name1"); - final var named2 = new NamedEventSource(mock2, "name2"); - final var named3 = new NamedEventSource(mock3, "name2"); - eventSources.add(named1); - eventSources.add(named2); - eventSources.add(named3); - - assertThat(eventSources.flatMappedSources()).contains(named1, named2, named3); + final var mock1 = + eventSourceMockWithName(EventSource.class, "name1", HasMetadata.class); + final var mock2 = + eventSourceMockWithName(EventSource.class, "name2", HasMetadata.class); + final var mock3 = eventSourceMockWithName(EventSource.class, "name3", ConfigMap.class); + + eventSources.add(mock1); + eventSources.add(mock2); + eventSources.add(mock3); + + assertThat(eventSources.flatMappedSources()).contains(mock1, mock2, mock3); } @Test void clearShouldWork() { final var eventSources = new EventSources(); - final var mock1 = mock(ResourceEventSource.class); - when(mock1.resourceType()).thenReturn(HasMetadata.class); - final var mock2 = mock(ResourceEventSource.class); - when(mock2.resourceType()).thenReturn(HasMetadata.class); - final var mock3 = mock(ResourceEventSource.class); - when(mock3.resourceType()).thenReturn(ConfigMap.class); - - final var named1 = new NamedEventSource(mock1, "name1"); - final var named2 = new NamedEventSource(mock2, "name2"); - final var named3 = new NamedEventSource(mock3, "name2"); - eventSources.add(named1); - eventSources.add(named2); - eventSources.add(named3); + final var mock1 = + eventSourceMockWithName(EventSource.class, "name1", HasMetadata.class); + final var mock2 = + eventSourceMockWithName(EventSource.class, "name2", HasMetadata.class); + final var mock3 = eventSourceMockWithName(EventSource.class, "name3", ConfigMap.class); + + eventSources.add(mock1); + eventSources.add(mock2); + eventSources.add(mock3); eventSources.clear(); assertThat(eventSources.flatMappedSources()).isEmpty(); @@ -130,23 +130,19 @@ void clearShouldWork() { @Test void getShouldWork() { final var eventSources = new EventSources(); - final var mock1 = mock(ResourceEventSource.class); - when(mock1.resourceType()).thenReturn(HasMetadata.class); - final var mock2 = mock(ResourceEventSource.class); - when(mock2.resourceType()).thenReturn(HasMetadata.class); - final var mock3 = mock(ResourceEventSource.class); - when(mock3.resourceType()).thenReturn(ConfigMap.class); - - final var named1 = new NamedEventSource(mock1, "name1"); - final var named2 = new NamedEventSource(mock2, "name2"); - final var named3 = new NamedEventSource(mock3, "name2"); - eventSources.add(named1); - eventSources.add(named2); - eventSources.add(named3); + final var mock1 = + eventSourceMockWithName(EventSource.class, "name1", HasMetadata.class); + final var mock2 = + eventSourceMockWithName(EventSource.class, "name2", HasMetadata.class); + final var mock3 = eventSourceMockWithName(EventSource.class, "name3", ConfigMap.class); + + eventSources.add(mock1); + eventSources.add(mock2); + eventSources.add(mock3); assertEquals(mock1, eventSources.get(HasMetadata.class, "name1")); assertEquals(mock2, eventSources.get(HasMetadata.class, "name2")); - assertEquals(mock3, eventSources.get(ConfigMap.class, "name2")); + assertEquals(mock3, eventSources.get(ConfigMap.class, "name3")); assertEquals(mock3, eventSources.get(ConfigMap.class, null)); @@ -160,19 +156,15 @@ void getShouldWork() { @Test void getEventSourcesShouldWork() { final var eventSources = new EventSources(); - final var mock1 = mock(ResourceEventSource.class); - when(mock1.resourceType()).thenReturn(HasMetadata.class); - final var mock2 = mock(ResourceEventSource.class); - when(mock2.resourceType()).thenReturn(HasMetadata.class); - final var mock3 = mock(ResourceEventSource.class); - when(mock3.resourceType()).thenReturn(ConfigMap.class); - - final var named1 = new NamedEventSource(mock1, "name1"); - final var named2 = new NamedEventSource(mock2, "name2"); - final var named3 = new NamedEventSource(mock3, "name2"); - eventSources.add(named1); - eventSources.add(named2); - eventSources.add(named3); + final var mock1 = + eventSourceMockWithName(EventSource.class, "name1", HasMetadata.class); + final var mock2 = + eventSourceMockWithName(EventSource.class, "name2", HasMetadata.class); + final var mock3 = eventSourceMockWithName(EventSource.class, "name3", ConfigMap.class); + + eventSources.add(mock1); + eventSources.add(mock2); + eventSources.add(mock3); var sources = eventSources.getEventSources(HasMetadata.class); assertThat(sources.size()).isEqualTo(2); @@ -184,4 +176,15 @@ void getEventSourcesShouldWork() { assertThat(eventSources.getEventSources(Service.class)).isEmpty(); } + + + + EventSource eventSourceMockWithName(Class clazz, String name, + Class resourceType) { + var mockedES = mock(clazz); + when(mockedES.name()).thenReturn(name); + when(mockedES.resourceType()).thenReturn(resourceType); + return mockedES; + } + } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java index c6aacb9071..7e80f4aedc 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java @@ -6,7 +6,6 @@ import java.util.concurrent.TimeUnit; import java.util.function.BiFunction; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; @@ -43,19 +42,9 @@ import static io.javaoperatorsdk.operator.TestUtils.markForDeletion; import static io.javaoperatorsdk.operator.processing.event.ReconciliationDispatcher.MAX_UPDATE_RETRY; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; @SuppressWarnings({"unchecked", "rawtypes"}) class ReconciliationDispatcherTest { @@ -70,8 +59,16 @@ class ReconciliationDispatcherTest { mock(ReconciliationDispatcher.CustomResourceFacade.class); private static ConfigurationService configurationService; - @BeforeAll - static void classSetup() { + @BeforeEach + void setup() { + initConfigService(true); + testCustomResource = TestUtils.testCustomResource(); + reconciler = spy(new TestReconciler()); + reconciliationDispatcher = + init(testCustomResource, reconciler, null, customResourceFacade, true); + } + + static void initConfigService(boolean useSSA) { /* * We need this for mock reconcilers to properly generate the expected UpdateControl: without * this, calls such as `when(reconciler.reconcile(eq(testCustomResource), @@ -87,15 +84,8 @@ static void classSetup() { public R clone(R object) { return object; } - })); - } - - @BeforeEach - void setup() { - testCustomResource = TestUtils.testCustomResource(); - reconciler = spy(new TestReconciler()); - reconciliationDispatcher = - init(testCustomResource, reconciler, null, customResourceFacade, true); + }) + .withUseSSAToPatchPrimaryResource(useSSA)); } private ReconciliationDispatcher init(R customResource, @@ -137,43 +127,47 @@ void addFinalizerOnNewResource() { verify(reconciler, never()) .reconcile(ArgumentMatchers.eq(testCustomResource), any()); verify(customResourceFacade, times(1)) - .updateResource( + .patchResourceWithSSA( argThat(testCustomResource -> testCustomResource.hasFinalizer(DEFAULT_FINALIZER))); - assertThat(testCustomResource.hasFinalizer(DEFAULT_FINALIZER)).isTrue(); } @Test - void callCreateOrUpdateOnNewResourceIfFinalizerSet() { - testCustomResource.addFinalizer(DEFAULT_FINALIZER); - reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource)); - verify(reconciler, times(1)) + void addFinalizerOnNewResourceWithoutSSA() { + initConfigService(false); + final ReconciliationDispatcher dispatcher = + init(testCustomResource, reconciler, null, customResourceFacade, true); + + assertFalse(testCustomResource.hasFinalizer(DEFAULT_FINALIZER)); + dispatcher.handleExecution(executionScopeWithCREvent(testCustomResource)); + verify(reconciler, never()) .reconcile(ArgumentMatchers.eq(testCustomResource), any()); + verify(customResourceFacade, times(1)) + .patchResource( + argThat(testCustomResource -> testCustomResource.hasFinalizer(DEFAULT_FINALIZER)), + any()); + assertThat(testCustomResource.hasFinalizer(DEFAULT_FINALIZER)).isTrue(); } @Test - void updatesOnlyStatusSubResourceIfFinalizerSet() { + void callCreateOrUpdateOnNewResourceIfFinalizerSet() { testCustomResource.addFinalizer(DEFAULT_FINALIZER); - - reconciler.reconcile = (r, c) -> UpdateControl.patchStatus(testCustomResource); - reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource)); - - verify(customResourceFacade, times(1)).patchStatus(eq(testCustomResource), any()); - verify(customResourceFacade, never()).updateResource(any()); + verify(reconciler, times(1)) + .reconcile(ArgumentMatchers.eq(testCustomResource), any()); } @Test - void updatesBothResourceAndStatusIfFinalizerSet() { + void patchesBothResourceAndStatusIfFinalizerSet() { testCustomResource.addFinalizer(DEFAULT_FINALIZER); - reconciler.reconcile = (r, c) -> UpdateControl.updateResourceAndStatus(testCustomResource); - when(customResourceFacade.updateResource(testCustomResource)) + reconciler.reconcile = (r, c) -> UpdateControl.patchResourceAndStatus(testCustomResource); + when(customResourceFacade.patchResource(eq(testCustomResource), any())) .thenReturn(testCustomResource); reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource)); - verify(customResourceFacade, times(1)).updateResource(testCustomResource); - verify(customResourceFacade, times(1)).updateStatus(testCustomResource); + verify(customResourceFacade, times(1)).patchResource(eq(testCustomResource), any()); + verify(customResourceFacade, times(1)).patchStatus(eq(testCustomResource), any()); } @Test @@ -185,8 +179,7 @@ void patchesStatus() { reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource)); verify(customResourceFacade, times(1)).patchStatus(eq(testCustomResource), any()); - verify(customResourceFacade, never()).updateStatus(any()); - verify(customResourceFacade, never()).updateResource(any()); + verify(customResourceFacade, never()).patchResource(any(), any()); } @Test @@ -218,7 +211,7 @@ void removesDefaultFinalizerOnDeleteIfSet() { reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource)); assertThat(postExecControl.isFinalizerRemoved()).isTrue(); - verify(customResourceFacade, times(1)).updateResource(testCustomResource); + verify(customResourceFacade, times(1)).patchResourceWithoutSSA(eq(testCustomResource), any()); } @Test @@ -227,7 +220,7 @@ void retriesFinalizerRemovalWithFreshResource() { markForDeletion(testCustomResource); var resourceWithFinalizer = TestUtils.testCustomResource(); resourceWithFinalizer.addFinalizer(DEFAULT_FINALIZER); - when(customResourceFacade.updateResource(testCustomResource)) + when(customResourceFacade.patchResourceWithoutSSA(eq(testCustomResource), any())) .thenThrow(new KubernetesClientException(null, 409, null)) .thenReturn(testCustomResource); when(customResourceFacade.getResource(any(), any())).thenReturn(resourceWithFinalizer); @@ -236,7 +229,7 @@ void retriesFinalizerRemovalWithFreshResource() { reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource)); assertThat(postExecControl.isFinalizerRemoved()).isTrue(); - verify(customResourceFacade, times(2)).updateResource(any()); + verify(customResourceFacade, times(2)).patchResourceWithoutSSA(any(), any()); verify(customResourceFacade, times(1)).getResource(any(), any()); } @@ -246,7 +239,7 @@ void nullResourceIsGracefullyHandledOnFinalizerRemovalRetry() { // of the finalizer removal testCustomResource.addFinalizer(DEFAULT_FINALIZER); markForDeletion(testCustomResource); - when(customResourceFacade.updateResource(any())) + when(customResourceFacade.patchResourceWithoutSSA(any(), any())) .thenThrow(new KubernetesClientException(null, 409, null)); when(customResourceFacade.getResource(any(), any())).thenReturn(null); @@ -254,7 +247,7 @@ void nullResourceIsGracefullyHandledOnFinalizerRemovalRetry() { reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource)); assertThat(postExecControl.isFinalizerRemoved()).isTrue(); - verify(customResourceFacade, times(1)).updateResource(testCustomResource); + verify(customResourceFacade, times(1)).patchResourceWithoutSSA(eq(testCustomResource), any()); verify(customResourceFacade, times(1)).getResource(any(), any()); } @@ -262,7 +255,7 @@ void nullResourceIsGracefullyHandledOnFinalizerRemovalRetry() { void throwsExceptionIfFinalizerRemovalRetryExceeded() { testCustomResource.addFinalizer(DEFAULT_FINALIZER); markForDeletion(testCustomResource); - when(customResourceFacade.updateResource(any())) + when(customResourceFacade.patchResourceWithoutSSA(any(), any())) .thenThrow(new KubernetesClientException(null, 409, null)); when(customResourceFacade.getResource(any(), any())) .thenAnswer((Answer) invocationOnMock -> createResourceWithFinalizer()); @@ -274,7 +267,7 @@ void throwsExceptionIfFinalizerRemovalRetryExceeded() { assertThat(postExecControl.getRuntimeException()).isPresent(); assertThat(postExecControl.getRuntimeException().get()) .isInstanceOf(OperatorException.class); - verify(customResourceFacade, times(MAX_UPDATE_RETRY)).updateResource(any()); + verify(customResourceFacade, times(MAX_UPDATE_RETRY)).patchResourceWithoutSSA(any(), any()); verify(customResourceFacade, times(MAX_UPDATE_RETRY - 1)).getResource(any(), any()); } @@ -283,7 +276,7 @@ void throwsExceptionIfFinalizerRemovalRetryExceeded() { void throwsExceptionIfFinalizerRemovalClientExceptionIsNotConflict() { testCustomResource.addFinalizer(DEFAULT_FINALIZER); markForDeletion(testCustomResource); - when(customResourceFacade.updateResource(any())) + when(customResourceFacade.patchResourceWithoutSSA(any(), any())) .thenThrow(new KubernetesClientException(null, 400, null)); var res = @@ -291,7 +284,7 @@ void throwsExceptionIfFinalizerRemovalClientExceptionIsNotConflict() { assertThat(res.getRuntimeException()).isPresent(); assertThat(res.getRuntimeException().get()).isInstanceOf(KubernetesClientException.class); - verify(customResourceFacade, times(1)).updateResource(any()); + verify(customResourceFacade, times(1)).patchResourceWithoutSSA(any(), any()); verify(customResourceFacade, never()).getResource(any(), any()); } @@ -335,7 +328,7 @@ void doesNotRemovesTheSetFinalizerIfTheDeleteNotMethodInstructsIt() { reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource)); assertEquals(1, testCustomResource.getMetadata().getFinalizers().size()); - verify(customResourceFacade, never()).updateResource(any()); + verify(customResourceFacade, never()).patchResource(any(), any()); } @Test @@ -345,22 +338,22 @@ void doesNotUpdateTheResourceIfNoUpdateUpdateControlIfFinalizerSet() { reconciler.reconcile = (r, c) -> UpdateControl.noUpdate(); reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource)); - verify(customResourceFacade, never()).updateResource(any()); - verify(customResourceFacade, never()).updateStatus(testCustomResource); + verify(customResourceFacade, never()).patchResource(any(), any()); + verify(customResourceFacade, never()).patchStatus(eq(testCustomResource), any()); } @Test void addsFinalizerIfNotMarkedForDeletionAndEmptyCustomResourceReturned() { removeFinalizers(testCustomResource); reconciler.reconcile = (r, c) -> UpdateControl.noUpdate(); - when(customResourceFacade.updateResource(any())) + when(customResourceFacade.patchResourceWithSSA(any())) .thenReturn(testCustomResource); var postExecControl = reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource)); - assertEquals(1, testCustomResource.getMetadata().getFinalizers().size()); - verify(customResourceFacade, times(1)).updateResource(any()); + verify(customResourceFacade, times(1)) + .patchResourceWithSSA(argThat(a -> !a.getMetadata().getFinalizers().isEmpty())); assertThat(postExecControl.updateIsStatusPatch()).isFalse(); assertThat(postExecControl.getUpdatedCustomResource()).isPresent(); } @@ -372,7 +365,7 @@ void doesNotCallDeleteIfMarkedForDeletionButNotOurFinalizer() { reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource)); - verify(customResourceFacade, never()).updateResource(any()); + verify(customResourceFacade, never()).patchResource(any(), any()); verify(reconciler, never()).cleanup(eq(testCustomResource), any()); } @@ -443,30 +436,7 @@ void reScheduleOnDeleteWithoutFinalizerRemoval() { } @Test - void setObservedGenerationForStatusIfNeeded() throws Exception { - var observedGenResource = createObservedGenCustomResource(); - - Reconciler reconciler = mock(Reconciler.class); - ControllerConfiguration config = - MockControllerConfiguration.forResource(ObservedGenCustomResource.class); - CustomResourceFacade facade = mock(CustomResourceFacade.class); - var dispatcher = init(observedGenResource, reconciler, config, facade, true); - - when(config.isGenerationAware()).thenReturn(true); - - when(reconciler.reconcile(any(), any())) - .thenReturn(UpdateControl.patchStatus(observedGenResource)); - when(facade.patchStatus(eq(observedGenResource), any())).thenReturn(observedGenResource); - - PostExecutionControl control = dispatcher.handleExecution( - executionScopeWithCREvent(observedGenResource)); - assertThat(control.getUpdatedCustomResource().orElseGet(() -> fail("Missing optional")) - .getStatus().getObservedGeneration()) - .isEqualTo(1L); - } - - @Test - void updatesObservedGenerationOnNoUpdateUpdateControl() throws Exception { + void doesNotUpdatesObservedGenerationIfStatusIsNotPatchedWhenUsingSSA() throws Exception { var observedGenResource = createObservedGenCustomResource(); Reconciler reconciler = mock(Reconciler.class); @@ -475,18 +445,16 @@ void updatesObservedGenerationOnNoUpdateUpdateControl() throws Exception { when(config.isGenerationAware()).thenReturn(true); when(reconciler.reconcile(any(), any())) .thenReturn(UpdateControl.noUpdate()); - when(facade.updateStatus(observedGenResource)).thenReturn(observedGenResource); + when(facade.patchStatus(any(), any())).thenReturn(observedGenResource); var dispatcher = init(observedGenResource, reconciler, config, facade, true); PostExecutionControl control = dispatcher.handleExecution( executionScopeWithCREvent(observedGenResource)); - assertThat(control.getUpdatedCustomResource().orElseGet(() -> fail("Missing optional")) - .getStatus().getObservedGeneration()) - .isEqualTo(1L); + assertThat(control.getUpdatedCustomResource()).isEmpty(); } @Test - void updateObservedGenerationOnCustomResourceUpdate() throws Exception { + void doesNotPatchObservedGenerationOnCustomResourcePatch() throws Exception { var observedGenResource = createObservedGenCustomResource(); Reconciler reconciler = mock(Reconciler.class); @@ -494,16 +462,14 @@ void updateObservedGenerationOnCustomResourceUpdate() throws Exception { CustomResourceFacade facade = mock(CustomResourceFacade.class); when(config.isGenerationAware()).thenReturn(true); when(reconciler.reconcile(any(), any())) - .thenReturn(UpdateControl.updateResource(observedGenResource)); - when(facade.updateResource(any())).thenReturn(observedGenResource); - when(facade.updateStatus(observedGenResource)).thenReturn(observedGenResource); - var dispatcher = init(observedGenResource, reconciler, config, facade, true); + .thenReturn(UpdateControl.patchResource(observedGenResource)); + when(facade.patchResource(any(), any())).thenReturn(observedGenResource); + var dispatcher = init(observedGenResource, reconciler, config, facade, false); - PostExecutionControl control = dispatcher.handleExecution( + dispatcher.handleExecution( executionScopeWithCREvent(observedGenResource)); - assertThat(control.getUpdatedCustomResource().orElseGet(() -> fail("Missing optional")) - .getStatus().getObservedGeneration()) - .isEqualTo(1L); + + verify(facade, never()).patchStatus(any(), any()); } @Test @@ -515,7 +481,7 @@ void callErrorStatusHandlerIfImplemented() { }; reconciler.errorHandler = (r, ri, e) -> { testCustomResource.getStatus().setConfigMapStatus(ERROR_MESSAGE); - return ErrorStatusUpdateControl.updateStatus(testCustomResource); + return ErrorStatusUpdateControl.patchStatus(testCustomResource); }; reconciliationDispatcher.handleExecution( @@ -532,7 +498,7 @@ public boolean isLastAttempt() { } }).setResource(testCustomResource)); - verify(customResourceFacade, times(1)).updateStatus(testCustomResource); + verify(customResourceFacade, times(1)).patchStatus(eq(testCustomResource), any()); verify(((ErrorStatusHandler) reconciler), times(1)).updateErrorStatus(eq(testCustomResource), any(), any()); } @@ -546,12 +512,12 @@ void callErrorStatusHandlerEvenOnFirstError() { }; reconciler.errorHandler = (r, ri, e) -> { testCustomResource.getStatus().setConfigMapStatus(ERROR_MESSAGE); - return ErrorStatusUpdateControl.updateStatus(testCustomResource); + return ErrorStatusUpdateControl.patchStatus(testCustomResource); }; var postExecControl = reconciliationDispatcher.handleExecution( new ExecutionScope(null).setResource(testCustomResource)); - verify(customResourceFacade, times(1)).updateStatus(testCustomResource); + verify(customResourceFacade, times(1)).patchStatus(eq(testCustomResource), any()); verify(((ErrorStatusHandler) reconciler), times(1)).updateErrorStatus(eq(testCustomResource), any(), any()); assertThat(postExecControl.exceptionDuringExecution()).isTrue(); @@ -565,7 +531,7 @@ void errorHandlerCanInstructNoRetryWithUpdate() { }; reconciler.errorHandler = (r, ri, e) -> { testCustomResource.getStatus().setConfigMapStatus(ERROR_MESSAGE); - return ErrorStatusUpdateControl.updateStatus(testCustomResource).withNoRetry(); + return ErrorStatusUpdateControl.patchStatus(testCustomResource).withNoRetry(); }; var postExecControl = reconciliationDispatcher.handleExecution( @@ -573,7 +539,7 @@ void errorHandlerCanInstructNoRetryWithUpdate() { verify(((ErrorStatusHandler) reconciler), times(1)).updateErrorStatus(eq(testCustomResource), any(), any()); - verify(customResourceFacade, times(1)).updateStatus(testCustomResource); + verify(customResourceFacade, times(1)).patchStatus(eq(testCustomResource), any()); assertThat(postExecControl.exceptionDuringExecution()).isFalse(); } @@ -593,7 +559,7 @@ void errorHandlerCanInstructNoRetryNoUpdate() { verify(((ErrorStatusHandler) reconciler), times(1)).updateErrorStatus(eq(testCustomResource), any(), any()); - verify(customResourceFacade, times(0)).updateStatus(testCustomResource); + verify(customResourceFacade, times(0)).patchStatus(eq(testCustomResource), any()); assertThat(postExecControl.exceptionDuringExecution()).isFalse(); } @@ -656,10 +622,14 @@ void canSkipSchedulingMaxDelayIf() { } @Test - void retriesAddingFinalizer() { + void retriesAddingFinalizerWithoutSSA() { + initConfigService(false); + reconciliationDispatcher = + init(testCustomResource, reconciler, null, customResourceFacade, true); + removeFinalizers(testCustomResource); reconciler.reconcile = (r, c) -> UpdateControl.noUpdate(); - when(customResourceFacade.updateResource(any())) + when(customResourceFacade.patchResource(any(), any())) .thenThrow(new KubernetesClientException(null, 409, null)) .thenReturn(testCustomResource); when(customResourceFacade.getResource(any(), any())) @@ -670,7 +640,7 @@ void retriesAddingFinalizer() { reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource)); - verify(customResourceFacade, times(2)).updateResource(any()); + verify(customResourceFacade, times(2)).patchResource(any(), any()); } @Test @@ -691,6 +661,12 @@ void reSchedulesFromErrorHandler() { assertThat(res.getRuntimeException()).isEmpty(); } + @Test + void addsFinalizerToPatchWithSSA() { + + } + + private ObservedGenCustomResource createObservedGenCustomResource() { ObservedGenCustomResource observedGenCustomResource = new ObservedGenCustomResource(); observedGenCustomResource.setMetadata(new ObjectMeta()); diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventFilterTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventFilterTest.java deleted file mode 100644 index c8ec839b59..0000000000 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventFilterTest.java +++ /dev/null @@ -1,166 +0,0 @@ -package io.javaoperatorsdk.operator.processing.event.source; - -import java.util.List; -import java.util.Objects; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.MockKubernetesClient; -import io.javaoperatorsdk.operator.ReconcilerUtils; -import io.javaoperatorsdk.operator.TestUtils; -import io.javaoperatorsdk.operator.api.config.BaseConfigurationService; -import io.javaoperatorsdk.operator.api.config.ControllerConfiguration; -import io.javaoperatorsdk.operator.api.config.ResolvedControllerConfiguration; -import io.javaoperatorsdk.operator.processing.Controller; -import io.javaoperatorsdk.operator.processing.event.EventHandler; -import io.javaoperatorsdk.operator.processing.event.EventSourceManager; -import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerResourceEventSource; -import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceAction; -import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter; -import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; - -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -class ResourceEventFilterTest { - public static final String FINALIZER = - ReconcilerUtils.getDefaultFinalizerName(TestCustomResource.class); - - private EventHandler eventHandler; - - @BeforeEach - public void before() { - this.eventHandler = mock(EventHandler.class); - } - - private ControllerResourceEventSource init(Controller controller) { - var eventSource = new ControllerResourceEventSource<>(controller); - eventSource.setEventHandler(eventHandler); - return eventSource; - } - - @Test - public void eventFilteredByCustomPredicate() { - var config = new TestControllerConfig( - FINALIZER, - false, - (configuration, oldResource, newResource) -> oldResource == null || !Objects.equals( - oldResource.getStatus().getConfigMapStatus(), - newResource.getStatus().getConfigMapStatus())); - - final var eventSource = init(new TestController(config)); - - TestCustomResource cr = TestUtils.testCustomResource(); - cr.getMetadata().setFinalizers(List.of(FINALIZER)); - cr.getMetadata().setGeneration(1L); - cr.getStatus().setConfigMapStatus("1"); - - TestCustomResource cr2 = TestUtils.testCustomResource(); - cr.getMetadata().setFinalizers(List.of(FINALIZER)); - cr.getMetadata().setGeneration(1L); - cr.getStatus().setConfigMapStatus("2"); - - eventSource.eventReceived(ResourceAction.UPDATED, cr, cr2); - verify(eventHandler, times(1)).handleEvent(any()); - - cr.getMetadata().setGeneration(1L); - cr.getStatus().setConfigMapStatus("1"); - - eventSource.eventReceived(ResourceAction.UPDATED, cr, cr); - verify(eventHandler, times(1)).handleEvent(any()); - } - - @Test - public void eventFilteredByCustomPredicateAndGenerationAware() { - var config = new TestControllerConfig( - FINALIZER, - true, - (configuration, oldResource, newResource) -> oldResource == null || !Objects.equals( - oldResource.getStatus().getConfigMapStatus(), - newResource.getStatus().getConfigMapStatus())); - - final var eventSource = init(new TestController(config)); - - TestCustomResource cr = TestUtils.testCustomResource(); - cr.getMetadata().setFinalizers(List.of(FINALIZER)); - cr.getMetadata().setGeneration(1L); - cr.getStatus().setConfigMapStatus("1"); - - TestCustomResource cr2 = TestUtils.testCustomResource(); - cr.getMetadata().setFinalizers(List.of(FINALIZER)); - cr.getMetadata().setGeneration(2L); - cr.getStatus().setConfigMapStatus("1"); - - eventSource.eventReceived(ResourceAction.UPDATED, cr, cr2); - verify(eventHandler, times(1)).handleEvent(any()); - - cr.getMetadata().setGeneration(1L); - cr.getStatus().setConfigMapStatus("2"); - - eventSource.eventReceived(ResourceAction.UPDATED, cr, cr); - verify(eventHandler, times(1)).handleEvent(any()); - } - - @Test - public void eventAlwaysFilteredByCustomPredicate() { - var config = new TestControllerConfig( - FINALIZER, - false, - (configuration, oldResource, newResource) -> !Objects.equals( - oldResource.getStatus().getConfigMapStatus(), - newResource.getStatus().getConfigMapStatus())); - - final var eventSource = init(new TestController(config)); - - TestCustomResource cr = TestUtils.testCustomResource(); - cr.getMetadata().setGeneration(1L); - cr.getStatus().setConfigMapStatus("1"); - - eventSource.eventReceived(ResourceAction.UPDATED, cr, cr); - verify(eventHandler, times(0)).handleEvent(any()); - } - - private static class TestControllerConfig extends ControllerConfig { - public TestControllerConfig(String finalizer, boolean generationAware, - ResourceEventFilter eventFilter) { - super(finalizer, generationAware, eventFilter, TestCustomResource.class); - } - } - - private static class ControllerConfig extends - ResolvedControllerConfiguration { - - public ControllerConfig(String finalizer, boolean generationAware, - ResourceEventFilter eventFilter, Class customResourceClass) { - super(customResourceClass, - "test", - generationAware, - null, - null, - null, - null, - null, - null, - null, null, null, finalizer, null, null, null, new BaseConfigurationService(), null); - setEventFilter(eventFilter); - } - } - - private static class TestController extends Controller { - - public TestController(ControllerConfiguration configuration) { - super(null, configuration, MockKubernetesClient.client(TestCustomResource.class)); - } - - @SuppressWarnings("unchecked") - @Override - public EventSourceManager getEventSourceManager() { - return mock(EventSourceManager.class); - } - } - -} diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSourceTest.java similarity index 89% rename from operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSourceTest.java rename to operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSourceTest.java index 64f0993139..ca6c030a88 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSourceTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSourceTest.java @@ -11,6 +11,8 @@ import io.javaoperatorsdk.operator.TestUtils; import io.javaoperatorsdk.operator.api.config.BaseConfigurationService; import io.javaoperatorsdk.operator.api.config.ResolvedControllerConfiguration; +import io.javaoperatorsdk.operator.api.reconciler.Reconciler; +import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; import io.javaoperatorsdk.operator.processing.Controller; import io.javaoperatorsdk.operator.processing.event.EventHandler; import io.javaoperatorsdk.operator.processing.event.EventSourceManager; @@ -27,8 +29,8 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -class ControllerResourceEventSourceTest extends - AbstractEventSourceTestBase, EventHandler> { +class ControllerEventSourceTest extends + AbstractEventSourceTestBase, EventHandler> { public static final String FINALIZER = ReconcilerUtils.getDefaultFinalizerName(TestCustomResource.class); @@ -37,7 +39,7 @@ class ControllerResourceEventSourceTest extends @BeforeEach public void setup() { - setUpSource(new ControllerResourceEventSource<>(testController), true, + setUpSource(new ControllerEventSource<>(testController), true, new BaseConfigurationService()); } @@ -85,7 +87,7 @@ void normalExecutionIfGenerationChanges() { @Test void handlesAllEventIfNotGenerationAware() { source = - new ControllerResourceEventSource<>(new TestController(false)); + new ControllerEventSource<>(new TestController(false)); setup(); TestCustomResource customResource1 = TestUtils.testCustomResource(); @@ -124,7 +126,7 @@ void filtersOutEventsOnAddAndUpdate() { OnAddFilter onAddFilter = (res) -> false; OnUpdateFilter onUpdatePredicate = (res, res2) -> false; source = - new ControllerResourceEventSource<>( + new ControllerEventSource<>( new TestController(onAddFilter, onUpdatePredicate, null)); setUpSource(source); @@ -139,7 +141,7 @@ void genericFilterFiltersOutAddUpdateAndDeleteEvents() { TestCustomResource cr = TestUtils.testCustomResource(); source = - new ControllerResourceEventSource<>(new TestController(null, null, res -> false)); + new ControllerEventSource<>(new TestController(null, null, res -> false)); setUpSource(source); source.eventReceived(ResourceAction.ADDED, cr, null); @@ -152,18 +154,21 @@ void genericFilterFiltersOutAddUpdateAndDeleteEvents() { @SuppressWarnings("unchecked") private static class TestController extends Controller { + private static final Reconciler reconciler = + (resource, context) -> UpdateControl.noUpdate(); + private final EventSourceManager eventSourceManager = mock(EventSourceManager.class); public TestController(OnAddFilter onAddFilter, OnUpdateFilter onUpdateFilter, GenericFilter genericFilter) { - super(null, new TestConfiguration(true, onAddFilter, onUpdateFilter, genericFilter), + super(reconciler, new TestConfiguration(true, onAddFilter, onUpdateFilter, genericFilter), MockKubernetesClient.client(TestCustomResource.class)); } public TestController(boolean generationAware) { - super(null, new TestConfiguration(generationAware, null, null, null), + super(reconciler, new TestConfiguration(generationAware, null, null, null), MockKubernetesClient.client(TestCustomResource.class)); } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/MappersTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/MappersTest.java new file mode 100644 index 0000000000..12e7b54706 --- /dev/null +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/MappersTest.java @@ -0,0 +1,56 @@ +package io.javaoperatorsdk.operator.processing.event.source.informer; + +import java.util.UUID; + +import org.junit.jupiter.api.Test; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ConfigMapBuilder; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.operator.TestUtils; +import io.javaoperatorsdk.operator.processing.event.ResourceID; +import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; +import io.javaoperatorsdk.operator.sample.simple.TestCustomResourceOtherV1; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +class MappersTest { + + + @Test + void secondaryToPrimaryMapperFromOwnerReference() { + var primary = TestUtils.testCustomResource(); + primary.getMetadata().setUid(UUID.randomUUID().toString()); + var secondary = getConfigMap(primary); + secondary.addOwnerReference(primary); + + var res = Mappers.fromOwnerReferences(TestCustomResource.class) + .toPrimaryResourceIDs(secondary); + + assertThat(res).contains(ResourceID.fromResource(primary)); + } + + @Test + void secondaryToPrimaryMapperFromOwnerReferenceFiltersByType() { + var primary = TestUtils.testCustomResource(); + primary.getMetadata().setUid(UUID.randomUUID().toString()); + var secondary = getConfigMap(primary); + secondary.addOwnerReference(primary); + + var res = Mappers.fromOwnerReferences(TestCustomResourceOtherV1.class) + .toPrimaryResourceIDs(secondary); + + assertThat(res).isEmpty(); + } + + + private static ConfigMap getConfigMap(TestCustomResource primary) { + return new ConfigMapBuilder() + .withMetadata(new ObjectMetaBuilder() + .withName("test1") + .withNamespace(primary.getMetadata().getNamespace()) + .build()) + .build(); + } +} diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/PrimaryToSecondaryIndexTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/PrimaryToSecondaryIndexTest.java index 62f395f2d6..6793b09550 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/PrimaryToSecondaryIndexTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/PrimaryToSecondaryIndexTest.java @@ -23,10 +23,10 @@ class PrimaryToSecondaryIndexTest { private final PrimaryToSecondaryIndex primaryToSecondaryIndex = new DefaultPrimaryToSecondaryIndex<>(secondaryToPrimaryMapperMock); - private ResourceID primaryID1 = new ResourceID("id1", "default"); - private ResourceID primaryID2 = new ResourceID("id2", "default"); - private ConfigMap secondary1 = secondary("secondary1"); - private ConfigMap secondary2 = secondary("secondary2"); + private final ResourceID primaryID1 = new ResourceID("id1", "default"); + private final ResourceID primaryID2 = new ResourceID("id2", "default"); + private final ConfigMap secondary1 = secondary("secondary1"); + private final ConfigMap secondary2 = secondary("secondary2"); @BeforeEach void setup() { diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/polling/PerResourcePollingEventSourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/polling/PerResourcePollingEventSourceTest.java index 70249f6125..fd5b85aa16 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/polling/PerResourcePollingEventSourceTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/polling/PerResourcePollingEventSourceTest.java @@ -11,19 +11,17 @@ import io.javaoperatorsdk.operator.TestUtils; import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; import io.javaoperatorsdk.operator.processing.event.EventHandler; -import io.javaoperatorsdk.operator.processing.event.source.*; +import io.javaoperatorsdk.operator.processing.event.source.AbstractEventSourceTestBase; +import io.javaoperatorsdk.operator.processing.event.source.CacheKeyMapper; +import io.javaoperatorsdk.operator.processing.event.source.IndexerResourceCache; +import io.javaoperatorsdk.operator.processing.event.source.SampleExternalResource; import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.atLeast; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; class PerResourcePollingEventSourceTest extends AbstractEventSourceTestBase, EventHandler> { @@ -45,8 +43,10 @@ public void setup() { .thenReturn(Set.of(SampleExternalResource.testResource1())); when(context.getPrimaryCache()).thenReturn(resourceCache); - setUpSource(new PerResourcePollingEventSource<>(supplier, context, Duration.ofMillis(PERIOD), - SampleExternalResource.class, r -> r.getName() + "#" + r.getValue())); + setUpSource(new PerResourcePollingEventSource<>(SampleExternalResource.class, context, + new PerResourcePollingConfigurationBuilder<>(supplier, Duration.ofMillis(PERIOD)) + .withCacheKeyMapper(r -> r.getName() + "#" + r.getValue()) + .build())); } @Test @@ -62,9 +62,14 @@ void pollsTheResourceAfterAwareOfIt() { @Test void registeringTaskOnAPredicate() { - setUpSource(new PerResourcePollingEventSource<>(supplier, context, Duration.ofMillis(PERIOD), - testCustomResource -> testCustomResource.getMetadata().getGeneration() > 1, - SampleExternalResource.class, CacheKeyMapper.singleResourceCacheKeyMapper())); + setUpSource(new PerResourcePollingEventSource<>(SampleExternalResource.class, context, + new PerResourcePollingConfigurationBuilder<>( + supplier, Duration.ofMillis(PERIOD)) + .withRegisterPredicate( + testCustomResource -> testCustomResource.getMetadata().getGeneration() > 1) + .withCacheKeyMapper(CacheKeyMapper.singleResourceCacheKeyMapper()) + .build())); + source.onResourceCreated(testCustomResource); diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/polling/PollingEventSourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/polling/PollingEventSourceTest.java index bd0179d4cb..5dffa65ae7 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/polling/PollingEventSourceTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/polling/PollingEventSourceTest.java @@ -25,14 +25,15 @@ class PollingEventSourceTest AbstractEventSourceTestBase, EventHandler> { public static final int DEFAULT_WAIT_PERIOD = 100; - public static final long POLL_PERIOD = 30L; + public static final Duration POLL_PERIOD = Duration.ofMillis(30L); @SuppressWarnings("unchecked") private final PollingEventSource.GenericResourceFetcher resourceFetcher = mock(PollingEventSource.GenericResourceFetcher.class); private final PollingEventSource pollingEventSource = - new PollingEventSource<>(resourceFetcher, POLL_PERIOD, SampleExternalResource.class, - (SampleExternalResource er) -> er.getName() + "#" + er.getValue()); + new PollingEventSource<>(SampleExternalResource.class, + new PollingConfiguration<>(resourceFetcher, POLL_PERIOD, + (SampleExternalResource er) -> er.getName() + "#" + er.getValue())); @BeforeEach public void setup() { @@ -92,7 +93,7 @@ void updatesHealthIndicatorBasedOnExceptionsInFetcher() throws InterruptedExcept .thenThrow(new RuntimeException("test exception")) .thenReturn(testResponseWithOneValue()); - await().pollInterval(Duration.ofMillis(POLL_PERIOD)).untilAsserted( + await().pollInterval(POLL_PERIOD).untilAsserted( () -> assertThat(pollingEventSource.getStatus()).isEqualTo(Status.UNHEALTHY)); await() diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/expiration/RetryExpirationTestExecution.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/expiration/RetryExpirationTestExecution.java new file mode 100644 index 0000000000..495077ae80 --- /dev/null +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/expiration/RetryExpirationTestExecution.java @@ -0,0 +1,55 @@ +package io.javaoperatorsdk.operator.processing.expiration; + +import org.junit.jupiter.api.Test; + +import io.javaoperatorsdk.operator.processing.retry.GenericRetry; + +import static org.assertj.core.api.Assertions.assertThat; + +public class RetryExpirationTestExecution { + + public static final int INITIAL_INTERVAL = 25; + public static final int INITIAL_INTERVAL_PLUS_SLACK = INITIAL_INTERVAL + 10; + + RetryExpirationExecution expiration = new RetryExpirationExecution(new GenericRetry() + .setInitialInterval(INITIAL_INTERVAL) + .setMaxAttempts(2) + .initExecution()); + + @Test + public void byDefaultExpired() { + assertThat(expiration.isExpired()).isTrue(); + } + + @Test + public void expiresAfterTime() throws InterruptedException { + expiration.refreshed(); + assertThat(expiration.isExpired()).isFalse(); + + Thread.sleep(INITIAL_INTERVAL_PLUS_SLACK); + assertThat(expiration.isExpired()).isTrue(); + } + + @Test + public void refreshResetsExpiration() throws InterruptedException { + expiration.refreshed(); + Thread.sleep(INITIAL_INTERVAL_PLUS_SLACK); + assertThat(expiration.isExpired()).isTrue(); + + expiration.refreshed(); + + assertThat(expiration.isExpired()).isFalse(); + } + + @Test + public void notExpiresAfterMaxAttempt() throws InterruptedException { + expiration.refreshed(); + expiration.refreshed(); + expiration.refreshed(); + + Thread.sleep(INITIAL_INTERVAL_PLUS_SLACK); + + assertThat(expiration.isExpired()).isFalse(); + } + +} diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/retry/GenericRetryExecutionTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/retry/GenericRetryExecutionTest.java index 1dcd9df464..1659995877 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/retry/GenericRetryExecutionTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/retry/GenericRetryExecutionTest.java @@ -4,38 +4,10 @@ import org.junit.jupiter.api.Test; -import io.javaoperatorsdk.operator.api.config.RetryConfiguration; - import static org.assertj.core.api.Assertions.assertThat; public class GenericRetryExecutionTest { - @Test - public void forFirstBackOffAlwaysReturnsInitialInterval() { - assertThat(getDefaultRetryExecution().nextDelay().get()) - .isEqualTo(RetryConfiguration.DEFAULT_INITIAL_INTERVAL); - } - - @Test - public void delayIsMultipliedEveryNextDelayCall() { - RetryExecution retryExecution = getDefaultRetryExecution(); - - Optional res = callNextDelayNTimes(retryExecution, 1); - assertThat(res.get()).isEqualTo(RetryConfiguration.DEFAULT_INITIAL_INTERVAL); - - res = retryExecution.nextDelay(); - assertThat(res.get()) - .isEqualTo((long) (RetryConfiguration.DEFAULT_INITIAL_INTERVAL - * RetryConfiguration.DEFAULT_MULTIPLIER)); - - res = retryExecution.nextDelay(); - assertThat(res.get()) - .isEqualTo( - (long) (RetryConfiguration.DEFAULT_INITIAL_INTERVAL - * RetryConfiguration.DEFAULT_MULTIPLIER - * RetryConfiguration.DEFAULT_MULTIPLIER)); - } - @Test public void noNextDelayIfMaxAttemptLimitReached() { RetryExecution retryExecution = diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/observedgeneration/ObservedGenStatus.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/observedgeneration/ObservedGenStatus.java index d4ffee5416..81ce9a435d 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/observedgeneration/ObservedGenStatus.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/observedgeneration/ObservedGenStatus.java @@ -1,7 +1,5 @@ package io.javaoperatorsdk.operator.sample.observedgeneration; -import io.javaoperatorsdk.operator.api.ObservedGenerationAwareStatus; - -public class ObservedGenStatus extends ObservedGenerationAwareStatus { +public class ObservedGenStatus { } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/simple/TestCustomReconciler.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/simple/TestCustomReconciler.java index 748b76b72d..be2c80667e 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/simple/TestCustomReconciler.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/simple/TestCustomReconciler.java @@ -104,7 +104,7 @@ public UpdateControl reconcile( } resource.getStatus().setConfigMapStatus("ConfigMap Ready"); } - return UpdateControl.updateResource(resource); + return UpdateControl.patchResource(resource); } private Map configMapData(TestCustomResource resource) { diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/simple/TestCustomResourceOtherV1.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/simple/TestCustomResourceOtherV1.java index f768ba491f..90e226abc8 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/simple/TestCustomResourceOtherV1.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/simple/TestCustomResourceOtherV1.java @@ -7,7 +7,7 @@ @Group("sample.javaoperatorsdk.io") @Version("v1") -@Kind("TestCustomResource") // this is needed to override the automatically generated kind +@Kind("TestCustomResourceOtherV1") // this is needed to override the automatically generated kind public class TestCustomResourceOtherV1 extends CustomResource { diff --git a/operator-framework-junit5/pom.xml b/operator-framework-junit5/pom.xml index 6e98ab0b48..8477be3783 100644 --- a/operator-framework-junit5/pom.xml +++ b/operator-framework-junit5/pom.xml @@ -1,22 +1,15 @@ - + + 4.0.0 - java-operator-sdk io.javaoperatorsdk - 4.9.1-SNAPSHOT + java-operator-sdk + 5.0.0-SNAPSHOT - 4.0.0 operator-framework-junit-5 Operator SDK - Framework - JUnit 5 extension - - 11 - 11 - - io.javaoperatorsdk @@ -46,4 +39,4 @@ - \ No newline at end of file + 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 1ce14b73ed..1f9db8f999 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,30 +111,15 @@ public T create(T resource) { return kubernetesClient.resource(resource).inNamespace(namespace).create(); } - @Deprecated(forRemoval = true) - public T create(Class type, T resource) { - return create(resource); - } - public T replace(T resource) { return kubernetesClient.resource(resource).inNamespace(namespace).replace(); } - @Deprecated(forRemoval = true) - public T replace(Class type, T resource) { - return replace(resource); - } - public boolean delete(T resource) { var res = kubernetesClient.resource(resource).inNamespace(namespace).delete(); return res.size() == 1 && res.get(0).getCauses().isEmpty(); } - @Deprecated(forRemoval = true) - public boolean delete(Class type, T resource) { - return delete(resource); - } - protected void beforeAllImpl(ExtensionContext context) { if (oneNamespacePerClass) { namespace = perClassNamespaceNameSupplier.apply(context); diff --git a/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/KubernetesClientAware.java b/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/KubernetesClientAware.java deleted file mode 100644 index 8e94e71b53..0000000000 --- a/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/KubernetesClientAware.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.javaoperatorsdk.operator.junit; - -import io.fabric8.kubernetes.client.KubernetesClient; -import io.javaoperatorsdk.operator.api.reconciler.Context; - -/** - * @deprecated It shouldn't be needed to pass a {@link KubernetesClient} instance to the reconciler - * anymore as the client should be accessed via {@link Context#getClient()} instead. - */ -@Deprecated(since = "4.5.0", forRemoval = true) -public interface KubernetesClientAware extends HasKubernetesClient { - void setKubernetesClient(KubernetesClient kubernetesClient); -} 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 61916f14bc..71a4e15632 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 @@ -1,6 +1,7 @@ package io.javaoperatorsdk.operator.junit; import java.io.ByteArrayInputStream; +import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.time.Duration; @@ -19,9 +20,11 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.Namespaced; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition; import io.fabric8.kubernetes.client.CustomResource; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.LocalPortForward; +import io.fabric8.kubernetes.client.dsl.NonDeletingOperation; import io.javaoperatorsdk.operator.Operator; import io.javaoperatorsdk.operator.ReconcilerUtils; import io.javaoperatorsdk.operator.RegisteredController; @@ -70,7 +73,10 @@ private LocallyRunOperatorExtension( this.portForwards = portForwards; this.localPortForwards = new ArrayList<>(portForwards.size()); this.additionalCustomResourceDefinitions = additionalCustomResourceDefinitions; - this.operator = new Operator(getKubernetesClient(), configurationServiceOverrider); + this.operator = new Operator( + configurationServiceOverrider == null ? o -> o.withKubernetesClient(getKubernetesClient()) + : configurationServiceOverrider + .andThen(o -> o.withKubernetesClient(getKubernetesClient()))); this.registeredControllers = new HashMap<>(); } @@ -173,7 +179,21 @@ public static void applyCrd(Class resourceClass, Kubernet applyCrd(ReconcilerUtils.getResourceTypeName(resourceClass), client); } - public static void applyCrd(String resourceTypeName, KubernetesClient client) { + public static void deleteCrd(Class resourceClass, + KubernetesClient client) { + try { + var crd = loadCRD(ReconcilerUtils.getResourceTypeName(resourceClass), client); + client.resource(crd).delete(); + + Thread.sleep(CRD_READY_WAIT); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + + private static CustomResourceDefinition loadCRD(String resourceTypeName, + KubernetesClient client) { String path = "/META-INF/fabric8/" + resourceTypeName + "-v1.yml"; try (InputStream is = LocallyRunOperatorExtension.class.getResourceAsStream(path)) { if (is == null) { @@ -181,15 +201,24 @@ public static void applyCrd(String resourceTypeName, KubernetesClient client) { } var crdString = new String(is.readAllBytes(), StandardCharsets.UTF_8); LOGGER.debug("Applying CRD: {}", crdString); - final var crd = client.load(new ByteArrayInputStream(crdString.getBytes())); - crd.createOrReplace(); + final var resources = client.load(new ByteArrayInputStream(crdString.getBytes())); + return (CustomResourceDefinition) resources.items().get(0); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + public static void applyCrd(String resourceTypeName, KubernetesClient client) { + try { + var crd = loadCRD(resourceTypeName, client); + client.resource(crd).createOr(NonDeletingOperation::update); Thread.sleep(CRD_READY_WAIT); // readiness is not applicable for CRD, just wait a little - LOGGER.debug("Applied CRD with path: {}", path); + LOGGER.debug("Applied CRD for type {}", resourceTypeName); } catch (InterruptedException ex) { LOGGER.error("Interrupted.", ex); Thread.currentThread().interrupt(); } catch (Exception ex) { - throw new IllegalStateException("Cannot apply CRD yaml: " + path, ex); + throw new IllegalStateException("Cannot apply CRD for type: " + resourceTypeName, ex); } } diff --git a/operator-framework/pom.xml b/operator-framework/pom.xml index 0ed845d02c..bac97460b0 100644 --- a/operator-framework/pom.xml +++ b/operator-framework/pom.xml @@ -1,13 +1,11 @@ - + + 4.0.0 - java-operator-sdk io.javaoperatorsdk - 4.9.1-SNAPSHOT + java-operator-sdk + 5.0.0-SNAPSHOT - 4.0.0 operator-framework Operator SDK - Framework - Plain Java @@ -103,14 +101,14 @@ However, this is needed to compile the tests so let's disable apt just for the compile phase --> default-compile - compile compile + compile - -proc:none - + -proc:none + diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/CustomResourceFilterIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/CustomResourceFilterIT.java deleted file mode 100644 index 6733abaa47..0000000000 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/CustomResourceFilterIT.java +++ /dev/null @@ -1,48 +0,0 @@ -package io.javaoperatorsdk.operator; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.fabric8.kubernetes.api.model.ObjectMeta; -import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; -import io.javaoperatorsdk.operator.sample.customfilter.CustomFilteringTestReconciler; -import io.javaoperatorsdk.operator.sample.customfilter.CustomFilteringTestResource; -import io.javaoperatorsdk.operator.sample.customfilter.CustomFilteringTestResourceSpec; - -import static org.assertj.core.api.Assertions.assertThat; - -class CustomResourceFilterIT { - - @RegisterExtension - LocallyRunOperatorExtension operator = - LocallyRunOperatorExtension.builder().withReconciler(new CustomFilteringTestReconciler()) - .build(); - - @Test - void doesCustomFiltering() throws InterruptedException { - var filtered1 = createTestResource("filtered1", true, false); - var filtered2 = createTestResource("filtered2", false, true); - var notFiltered = createTestResource("notfiltered", true, true); - operator.create(filtered1); - operator.create(filtered2); - operator.create(notFiltered); - - Thread.sleep(300); - - assertThat( - ((CustomFilteringTestReconciler) operator.getReconcilers().get(0)).getNumberOfExecutions()) - .isEqualTo(1); - } - - - CustomFilteringTestResource createTestResource(String name, boolean filter1, boolean filter2) { - CustomFilteringTestResource resource = new CustomFilteringTestResource(); - resource.setMetadata(new ObjectMeta()); - resource.getMetadata().setName(name); - resource.setSpec(new CustomFilteringTestResourceSpec()); - resource.getSpec().setFilter1(filter1); - resource.getSpec().setFilter2(filter2); - return resource; - } - -} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/ExternalStateBulkIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/ExternalStateBulkIT.java index 7452958b8c..a0cc9c5e8e 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/ExternalStateBulkIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/ExternalStateBulkIT.java @@ -34,7 +34,7 @@ class ExternalStateBulkIT { .build(); @Test - void reconcilesResourceWithPersistentState() throws InterruptedException { + void reconcilesResourceWithPersistentState() { var resource = operator.create(testResource()); assertResources(resource, INITIAL_TEST_DATA, INITIAL_BULK_SIZE); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/IndexDiscriminatorIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/IndexDiscriminatorIT.java deleted file mode 100644 index fe5b63de8a..0000000000 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/IndexDiscriminatorIT.java +++ /dev/null @@ -1,77 +0,0 @@ -package io.javaoperatorsdk.operator; - -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 io.javaoperatorsdk.operator.sample.indexdiscriminator.IndexDiscriminatorTestCustomResource; -import io.javaoperatorsdk.operator.sample.indexdiscriminator.IndexDiscriminatorTestReconciler; -import io.javaoperatorsdk.operator.sample.indexdiscriminator.IndexDiscriminatorTestSpec; - -import static io.javaoperatorsdk.operator.sample.indexdiscriminator.IndexDiscriminatorTestDRConfigMap.DATA_KEY; -import static io.javaoperatorsdk.operator.sample.indexdiscriminator.IndexDiscriminatorTestReconciler.FIRST_CONFIG_MAP_SUFFIX_1; -import static io.javaoperatorsdk.operator.sample.indexdiscriminator.IndexDiscriminatorTestReconciler.FIRST_CONFIG_MAP_SUFFIX_2; -import static org.assertj.core.api.Assertions.assertThat; -import static org.awaitility.Awaitility.await; - -class IndexDiscriminatorIT { - - public static final String TEST_RESOURCE_1 = "test1"; - public static final String CHANGED_SPEC_VALUE = "otherValue"; - @RegisterExtension - LocallyRunOperatorExtension operator = - LocallyRunOperatorExtension.builder().withReconciler(IndexDiscriminatorTestReconciler.class) - .build(); - - @Test - void resourcesFoundAndReconciled() { - var res = operator.create(createTestCustomResource()); - var reconciler = operator.getReconcilerOfType(IndexDiscriminatorTestReconciler.class); - - await().untilAsserted(() -> { - assertThat(reconciler.getNumberOfExecutions()).isEqualTo(1); - assertThat(operator.get(ConfigMap.class, TEST_RESOURCE_1 + FIRST_CONFIG_MAP_SUFFIX_1)) - .isNotNull(); - assertThat(operator.get(ConfigMap.class, TEST_RESOURCE_1 + FIRST_CONFIG_MAP_SUFFIX_2)) - .isNotNull(); - }); - - res.getSpec().setValue(CHANGED_SPEC_VALUE); - res = operator.replace(res); - - await().untilAsserted(() -> { - assertThat(reconciler.getNumberOfExecutions()).isEqualTo(2); - var cm1 = operator.get(ConfigMap.class, TEST_RESOURCE_1 + FIRST_CONFIG_MAP_SUFFIX_1); - var cm2 = operator.get(ConfigMap.class, TEST_RESOURCE_1 + FIRST_CONFIG_MAP_SUFFIX_2); - assertThat(cm1).isNotNull(); - assertThat(cm2).isNotNull(); - assertThat(cm1.getData().get(DATA_KEY)).isEqualTo(CHANGED_SPEC_VALUE); - assertThat(cm2.getData().get(DATA_KEY)).isEqualTo(CHANGED_SPEC_VALUE); - }); - - operator.delete(res); - - await().untilAsserted(() -> { - var cm1 = operator.get(ConfigMap.class, TEST_RESOURCE_1 + FIRST_CONFIG_MAP_SUFFIX_1); - var cm2 = operator.get(ConfigMap.class, TEST_RESOURCE_1 + FIRST_CONFIG_MAP_SUFFIX_2); - assertThat(cm1).isNull(); - assertThat(cm2).isNull(); - }); - } - - public IndexDiscriminatorTestCustomResource createTestCustomResource() { - IndexDiscriminatorTestCustomResource resource = - new IndexDiscriminatorTestCustomResource(); - resource.setMetadata( - new ObjectMetaBuilder() - .withName(TEST_RESOURCE_1) - .withNamespace(operator.getNamespace()) - .build()); - resource.setSpec(new IndexDiscriminatorTestSpec()); - resource.getSpec().setValue("default"); - return resource; - } - -} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/InformerRelatedBehaviorITS.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/InformerRelatedBehaviorITS.java index 36e4fd23f6..3a6f4d05e9 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/InformerRelatedBehaviorITS.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/InformerRelatedBehaviorITS.java @@ -21,8 +21,7 @@ import io.javaoperatorsdk.jenvtest.junit.EnableKubeAPIServer; import io.javaoperatorsdk.operator.health.InformerHealthIndicator; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; -import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerResourceEventSource; -import io.javaoperatorsdk.operator.sample.informerrelatedbehavior.ConfigMapDependentResource; +import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerEventSource; import io.javaoperatorsdk.operator.sample.informerrelatedbehavior.InformerRelatedBehaviorTestCustomResource; import io.javaoperatorsdk.operator.sample.informerrelatedbehavior.InformerRelatedBehaviorTestReconciler; @@ -143,7 +142,7 @@ private void assertInformerNotWatchingForAdditionalNamespace(Operator operator) InformerHealthIndicator controllerHealthIndicator = (InformerHealthIndicator) unhealthyEventSources - .get(ControllerResourceEventSource.class.getSimpleName()) + .get(ControllerEventSource.NAME) .informerHealthIndicators().get(additionalNamespace); assertThat(controllerHealthIndicator).isNotNull(); assertThat(controllerHealthIndicator.getTargetNamespace()).isEqualTo(additionalNamespace); @@ -151,7 +150,7 @@ private void assertInformerNotWatchingForAdditionalNamespace(Operator operator) InformerHealthIndicator configMapHealthIndicator = (InformerHealthIndicator) unhealthyEventSources - .get(ConfigMapDependentResource.class.getSimpleName()) + .get(InformerRelatedBehaviorTestReconciler.CONFIG_MAP_DEPENDENT_RESOURCE) .informerHealthIndicators().get(additionalNamespace); assertThat(configMapHealthIndicator).isNotNull(); assertThat(configMapHealthIndicator.getTargetNamespace()).isEqualTo(additionalNamespace); @@ -270,13 +269,13 @@ private void assertRuntimeInfoNoCRPermission(Operator operator) { operator.getRuntimeInfo().unhealthyEventSources() .get(INFORMER_RELATED_BEHAVIOR_TEST_RECONCILER); assertThat(unhealthyEventSources).isNotEmpty(); - assertThat(unhealthyEventSources.get(ControllerResourceEventSource.class.getSimpleName())) + assertThat(unhealthyEventSources.get(ControllerEventSource.NAME)) .isNotNull(); var informerHealthIndicators = operator.getRuntimeInfo() .unhealthyInformerWrappingEventSourceHealthIndicator() .get(INFORMER_RELATED_BEHAVIOR_TEST_RECONCILER); assertThat(informerHealthIndicators).isNotEmpty(); - assertThat(informerHealthIndicators.get(ControllerResourceEventSource.class.getSimpleName()) + assertThat(informerHealthIndicators.get(ControllerEventSource.NAME) .informerHealthIndicators()) .hasSize(1); } @@ -317,8 +316,9 @@ Operator startOperator(boolean stopOnInformerErrorDuringStartup, boolean addStop reconciler = new InformerRelatedBehaviorTestReconciler(); - Operator operator = new Operator(clientUsingServiceAccount(), + Operator operator = new Operator( co -> { + co.withKubernetesClient(clientUsingServiceAccount()); co.withStopOnInformerErrorDuringStartup(stopOnInformerErrorDuringStartup); co.withCacheSyncTimeout(Duration.ofMillis(3000)); if (addStopHandler) { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/LeaderElectionPermissionIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/LeaderElectionPermissionIT.java index e1c8435b8a..c1b30277c7 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/LeaderElectionPermissionIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/LeaderElectionPermissionIT.java @@ -32,7 +32,8 @@ void operatorStopsIfNoLeaderElectionPermission() { .withImpersonateUsername("leader-elector-stop-noaccess") .build()).build(); - var operator = new Operator(client, o -> { + var operator = new Operator(o -> { + o.withKubernetesClient(client); o.withLeaderElectionConfiguration( new LeaderElectionConfiguration("lease1", "default")); o.withStopOnInformerErrorDuringStartup(false); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/ManualObservedGenerationIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/ManualObservedGenerationIT.java new file mode 100644 index 0000000000..ddfb2370d6 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/ManualObservedGenerationIT.java @@ -0,0 +1,54 @@ +package io.javaoperatorsdk.operator; + +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 io.javaoperatorsdk.operator.sample.manualobservedgeneration.ManualObservedGenerationCustomResource; +import io.javaoperatorsdk.operator.sample.manualobservedgeneration.ManualObservedGenerationReconciler; +import io.javaoperatorsdk.operator.sample.manualobservedgeneration.ManualObservedGenerationSpec; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +public class ManualObservedGenerationIT { + + public static final String RESOURCE_NAME = "test1"; + @RegisterExtension + LocallyRunOperatorExtension extension = + LocallyRunOperatorExtension.builder().withReconciler(new ManualObservedGenerationReconciler()) + .build(); + + @Test + void observedGenerationUpdated() { + extension.create(testResource()); + + await().untilAsserted(() -> { + var r = extension.get(ManualObservedGenerationCustomResource.class, RESOURCE_NAME); + assertThat(r).isNotNull(); + assertThat(r.getStatus().getObservedGeneration()).isEqualTo(1); + assertThat(r.getStatus().getObservedGeneration()).isEqualTo(r.getMetadata().getGeneration()); + }); + + var changed = testResource(); + changed.getSpec().setValue("changed value"); + extension.replace(changed); + + await().untilAsserted(() -> { + var r = extension.get(ManualObservedGenerationCustomResource.class, RESOURCE_NAME); + assertThat(r.getStatus().getObservedGeneration()).isEqualTo(2); + assertThat(r.getStatus().getObservedGeneration()).isEqualTo(r.getMetadata().getGeneration()); + }); + } + + ManualObservedGenerationCustomResource testResource() { + var res = new ManualObservedGenerationCustomResource(); + res.setMetadata(new ObjectMetaBuilder() + .withName(RESOURCE_NAME) + .build()); + res.setSpec(new ManualObservedGenerationSpec()); + res.getSpec().setValue("Initial Value"); + return res; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultiVersionCRDIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultiVersionCRDIT.java index dbb393fec0..de6cc681b6 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultiVersionCRDIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultiVersionCRDIT.java @@ -60,8 +60,7 @@ public void reset() { @Override @SuppressWarnings("rawtypes") public void onStop(SharedIndexInformer informer, Throwable ex) { - if (ex instanceof WatcherException) { - WatcherException watcherEx = (WatcherException) ex; + if (ex instanceof WatcherException watcherEx) { watcherEx.getRawWatchMessage().ifPresent(raw -> { try { // extract the resource at which the version is attempted to be created (i.e. the stored diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleDependentResourceIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleDependentResourceIT.java index d3e7f77fd5..224e9c487a 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleDependentResourceIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleDependentResourceIT.java @@ -1,7 +1,6 @@ package io.javaoperatorsdk.operator; import java.time.Duration; -import java.util.stream.IntStream; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -9,54 +8,73 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; -import io.javaoperatorsdk.operator.sample.multipledependentresource.MultipleDependentResourceConfigMap; import io.javaoperatorsdk.operator.sample.multipledependentresource.MultipleDependentResourceCustomResource; import io.javaoperatorsdk.operator.sample.multipledependentresource.MultipleDependentResourceReconciler; +import io.javaoperatorsdk.operator.sample.multipledependentresource.MultipleDependentResourceSpec; +import io.javaoperatorsdk.operator.sample.multipledrsametypenodiscriminator.*; +import static io.javaoperatorsdk.operator.sample.multipledependentresource.MultipleDependentResourceConfigMap.DATA_KEY; +import static io.javaoperatorsdk.operator.sample.multipledependentresource.MultipleDependentResourceConfigMap.getConfigMapName; +import static io.javaoperatorsdk.operator.sample.multipledependentresource.MultipleDependentResourceReconciler.FIRST_CONFIG_MAP_ID; +import static io.javaoperatorsdk.operator.sample.multipledependentresource.MultipleDependentResourceReconciler.SECOND_CONFIG_MAP_ID; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; -class MultipleDependentResourceIT { +public class MultipleDependentResourceIT { + + public static final String CHANGED_VALUE = "changed value"; + public static final String INITIAL_VALUE = "initial value"; - public static final String TEST_RESOURCE_NAME = "multipledependentresource-testresource"; @RegisterExtension - LocallyRunOperatorExtension operator = + LocallyRunOperatorExtension extension = LocallyRunOperatorExtension.builder() - .withReconciler(MultipleDependentResourceReconciler.class) - .waitForNamespaceDeletion(true) + .withReconciler(new MultipleDependentResourceReconciler()) .build(); @Test - void twoConfigMapsHaveBeenCreated() { - MultipleDependentResourceCustomResource customResource = createTestCustomResource(); - operator.create(customResource); - - var reconciler = operator.getReconcilerOfType(MultipleDependentResourceReconciler.class); - - await().pollDelay(Duration.ofMillis(300)) - .until(() -> reconciler.getNumberOfExecutions() <= 1); - - IntStream.of(MultipleDependentResourceReconciler.FIRST_CONFIG_MAP_ID, - MultipleDependentResourceReconciler.SECOND_CONFIG_MAP_ID).forEach(configMapId -> { - ConfigMap configMap = - operator.get(ConfigMap.class, customResource.getConfigMapName(configMapId)); - assertThat(configMap).isNotNull(); - assertThat(configMap.getMetadata().getName()) - .isEqualTo(customResource.getConfigMapName(configMapId)); - assertThat(configMap.getData().get(MultipleDependentResourceConfigMap.DATA_KEY)) - .isEqualTo(String.valueOf(configMapId)); - }); - } + void handlesCRUDOperations() { + var res = extension.create(testResource()); + + await().untilAsserted(() -> { + var cm1 = extension.get(ConfigMap.class, getConfigMapName(FIRST_CONFIG_MAP_ID)); + var cm2 = extension.get(ConfigMap.class, getConfigMapName(SECOND_CONFIG_MAP_ID)); + + assertThat(cm1).isNotNull(); + assertThat(cm2).isNotNull(); + assertThat(cm1.getData()).containsEntry(DATA_KEY, INITIAL_VALUE); + assertThat(cm2.getData()).containsEntry(DATA_KEY, INITIAL_VALUE); + }); + + res.getSpec().setValue(CHANGED_VALUE); + res = extension.replace(res); + + await().untilAsserted(() -> { + var cm1 = extension.get(ConfigMap.class, getConfigMapName(FIRST_CONFIG_MAP_ID)); + var cm2 = extension.get(ConfigMap.class, getConfigMapName(SECOND_CONFIG_MAP_ID)); - public MultipleDependentResourceCustomResource createTestCustomResource() { - MultipleDependentResourceCustomResource resource = - new MultipleDependentResourceCustomResource(); - resource.setMetadata( - new ObjectMetaBuilder() - .withName(TEST_RESOURCE_NAME) - .withNamespace(operator.getNamespace()) - .build()); - return resource; + assertThat(cm1.getData()).containsEntry(DATA_KEY, CHANGED_VALUE); + assertThat(cm2.getData()).containsEntry(DATA_KEY, CHANGED_VALUE); + }); + + extension.delete(res); + + await().timeout(Duration.ofSeconds(120)).untilAsserted(() -> { + var cm1 = extension.get(ConfigMap.class, getConfigMapName(FIRST_CONFIG_MAP_ID)); + var cm2 = extension.get(ConfigMap.class, getConfigMapName(SECOND_CONFIG_MAP_ID)); + + assertThat(cm1).isNull(); + assertThat(cm2).isNull(); + }); } + MultipleDependentResourceCustomResource testResource() { + var res = new MultipleDependentResourceCustomResource(); + res.setMetadata(new ObjectMetaBuilder() + .withName("test1") + .build()); + res.setSpec(new MultipleDependentResourceSpec()); + res.getSpec().setValue(INITIAL_VALUE); + + return res; + } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleDependentResourceWithNoDiscriminatorIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleDependentResourceWithNoDiscriminatorIT.java new file mode 100644 index 0000000000..908cb58f79 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleDependentResourceWithNoDiscriminatorIT.java @@ -0,0 +1,65 @@ +package io.javaoperatorsdk.operator; + +import java.time.Duration; +import java.util.stream.IntStream; + +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 io.javaoperatorsdk.operator.sample.multipledependentresourcewithdiscriminator.MultipleDependentResourceConfigMap; +import io.javaoperatorsdk.operator.sample.multipledependentresourcewithdiscriminator.MultipleDependentResourceCustomResourceWithDiscriminator; +import io.javaoperatorsdk.operator.sample.multipledependentresourcewithdiscriminator.MultipleDependentResourceWithDiscriminatorReconciler; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +class MultipleDependentResourceWithNoDiscriminatorIT { + + public static final String TEST_RESOURCE_NAME = "multipledependentresource-testresource"; + @RegisterExtension + LocallyRunOperatorExtension operator = + LocallyRunOperatorExtension.builder() + .withReconciler(MultipleDependentResourceWithDiscriminatorReconciler.class) + .waitForNamespaceDeletion(true) + .build(); + + @Test + void twoConfigMapsHaveBeenCreated() { + MultipleDependentResourceCustomResourceWithDiscriminator customResource = + createTestCustomResource(); + operator.create(customResource); + + var reconciler = + operator.getReconcilerOfType(MultipleDependentResourceWithDiscriminatorReconciler.class); + + await().pollDelay(Duration.ofMillis(300)) + .until(() -> reconciler.getNumberOfExecutions() <= 1); + + IntStream.of(MultipleDependentResourceWithDiscriminatorReconciler.FIRST_CONFIG_MAP_ID, + MultipleDependentResourceWithDiscriminatorReconciler.SECOND_CONFIG_MAP_ID) + .forEach(configMapId -> { + ConfigMap configMap = + operator.get(ConfigMap.class, customResource.getConfigMapName(configMapId)); + assertThat(configMap).isNotNull(); + assertThat(configMap.getMetadata().getName()) + .isEqualTo(customResource.getConfigMapName(configMapId)); + assertThat(configMap.getData().get(MultipleDependentResourceConfigMap.DATA_KEY)) + .isEqualTo(String.valueOf(configMapId)); + }); + } + + public MultipleDependentResourceCustomResourceWithDiscriminator createTestCustomResource() { + MultipleDependentResourceCustomResourceWithDiscriminator resource = + new MultipleDependentResourceCustomResourceWithDiscriminator(); + resource.setMetadata( + new ObjectMetaBuilder() + .withName(TEST_RESOURCE_NAME) + .withNamespace(operator.getNamespace()) + .build()); + return resource; + } + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleManagedDependentNoDiscriminatorIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleManagedDependentNoDiscriminatorIT.java new file mode 100644 index 0000000000..361aa099af --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleManagedDependentNoDiscriminatorIT.java @@ -0,0 +1,81 @@ +package io.javaoperatorsdk.operator; + +import java.time.Duration; + +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 io.javaoperatorsdk.operator.sample.multipledrsametypenodiscriminator.*; + +import static io.javaoperatorsdk.operator.sample.multipledrsametypenodiscriminator.MultipleManagedDependentSameTypeNoDiscriminatorReconciler.DATA_KEY; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +public class MultipleManagedDependentNoDiscriminatorIT { + + public static final String RESOURCE_NAME = "test1"; + public static final String INITIAL_VALUE = "initial_value"; + public static final String CHANGED_VALUE = "changed_value"; + + @RegisterExtension + LocallyRunOperatorExtension extension = + LocallyRunOperatorExtension.builder() + .withReconciler(new MultipleManagedDependentSameTypeNoDiscriminatorReconciler()) + .build(); + + @Test + void handlesCRUDOperations() { + var res = extension.create(testResource()); + + await().untilAsserted(() -> { + var cm1 = extension.get(ConfigMap.class, + RESOURCE_NAME + MultipleManagedDependentNoDiscriminatorConfigMap1.NAME_SUFFIX); + var cm2 = extension.get(ConfigMap.class, + RESOURCE_NAME + MultipleManagedDependentNoDiscriminatorConfigMap2.NAME_SUFFIX); + + assertThat(cm1).isNotNull(); + assertThat(cm2).isNotNull(); + assertThat(cm1.getData()).containsEntry(DATA_KEY, INITIAL_VALUE); + assertThat(cm2.getData()).containsEntry(DATA_KEY, INITIAL_VALUE); + }); + + res.getSpec().setValue(CHANGED_VALUE); + res = extension.replace(res); + + await().untilAsserted(() -> { + var cm1 = extension.get(ConfigMap.class, + RESOURCE_NAME + MultipleManagedDependentNoDiscriminatorConfigMap1.NAME_SUFFIX); + var cm2 = extension.get(ConfigMap.class, + RESOURCE_NAME + MultipleManagedDependentNoDiscriminatorConfigMap2.NAME_SUFFIX); + + assertThat(cm1.getData()).containsEntry(DATA_KEY, CHANGED_VALUE); + assertThat(cm2.getData()).containsEntry(DATA_KEY, CHANGED_VALUE); + }); + + extension.delete(res); + + await().timeout(Duration.ofSeconds(60)).untilAsserted(() -> { + var cm1 = extension.get(ConfigMap.class, + RESOURCE_NAME + MultipleManagedDependentNoDiscriminatorConfigMap1.NAME_SUFFIX); + var cm2 = extension.get(ConfigMap.class, + RESOURCE_NAME + MultipleManagedDependentNoDiscriminatorConfigMap2.NAME_SUFFIX); + + assertThat(cm1).isNull(); + assertThat(cm2).isNull(); + }); + } + + MultipleManagedDependentNoDiscriminatorCustomResource testResource() { + var res = new MultipleManagedDependentNoDiscriminatorCustomResource(); + res.setMetadata(new ObjectMetaBuilder() + .withName(RESOURCE_NAME) + .build()); + res.setSpec(new MultipleManagedDependentNoDiscriminatorSpec()); + res.getSpec().setValue(INITIAL_VALUE); + return res; + } + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/NextReconciliationImminentIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/NextReconciliationImminentIT.java new file mode 100644 index 0000000000..9f9b464a83 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/NextReconciliationImminentIT.java @@ -0,0 +1,66 @@ +package io.javaoperatorsdk.operator; + +import java.time.Duration; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; +import io.javaoperatorsdk.operator.sample.nextreconciliationimminent.NextReconciliationImminentCustomResource; +import io.javaoperatorsdk.operator.sample.nextreconciliationimminent.NextReconciliationImminentReconciler; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +public class NextReconciliationImminentIT { + + private static final Logger log = + LoggerFactory.getLogger(NextReconciliationImminentIT.class); + + public static final int WAIT_FOR_EVENT = 300; + public static final String TEST_RESOURCE_NAME = "test1"; + + @RegisterExtension + LocallyRunOperatorExtension extension = + LocallyRunOperatorExtension.builder() + .withReconciler(new NextReconciliationImminentReconciler()) + .build(); + + @Test + void skippingStatusUpdateWithNextReconciliationImminent() throws InterruptedException { + var resource = extension.create(testResource()); + + var reconciler = extension.getReconcilerOfType(NextReconciliationImminentReconciler.class); + await().untilAsserted(() -> assertThat(reconciler.isReconciliationWaiting()).isTrue()); + Thread.sleep(WAIT_FOR_EVENT); + + resource.getMetadata().getAnnotations().put("trigger", "" + System.currentTimeMillis()); + extension.replace(resource); + Thread.sleep(WAIT_FOR_EVENT); + log.info("Made change to trigger event"); + + reconciler.allowReconciliationToProceed(); + Thread.sleep(WAIT_FOR_EVENT); + // second event arrived + await().untilAsserted(() -> assertThat(reconciler.isReconciliationWaiting()).isTrue()); + reconciler.allowReconciliationToProceed(); + + await().pollDelay(Duration.ofMillis(WAIT_FOR_EVENT)).untilAsserted(() -> { + assertThat(extension.get(NextReconciliationImminentCustomResource.class, TEST_RESOURCE_NAME) + .getStatus().getUpdateNumber()).isEqualTo(1); + }); + } + + + NextReconciliationImminentCustomResource testResource() { + var res = new NextReconciliationImminentCustomResource(); + res.setMetadata(new ObjectMetaBuilder() + .withName(TEST_RESOURCE_NAME) + .build()); + return res; + } + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/ObservedGenerationHandlingIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/ObservedGenerationHandlingIT.java deleted file mode 100644 index 0e28ddec4c..0000000000 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/ObservedGenerationHandlingIT.java +++ /dev/null @@ -1,37 +0,0 @@ -package io.javaoperatorsdk.operator; - -import java.util.concurrent.TimeUnit; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.fabric8.kubernetes.api.model.ObjectMeta; -import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; -import io.javaoperatorsdk.operator.sample.observedgeneration.ObservedGenerationTestCustomResource; -import io.javaoperatorsdk.operator.sample.observedgeneration.ObservedGenerationTestReconciler; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.awaitility.Awaitility.await; - -class ObservedGenerationHandlingIT { - @RegisterExtension - LocallyRunOperatorExtension operator = - LocallyRunOperatorExtension.builder().withReconciler(new ObservedGenerationTestReconciler()) - .build(); - - @Test - void testReconciliationOfNonCustomResourceAndStatusUpdate() { - var resource = new ObservedGenerationTestCustomResource(); - resource.setMetadata(new ObjectMeta()); - resource.getMetadata().setName("observed-gen1"); - - var createdResource = operator.create(resource); - - await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> { - var d = operator.get(ObservedGenerationTestCustomResource.class, - createdResource.getMetadata().getName()); - assertThat(d.getStatus().getObservedGeneration()).isNotNull(); - assertThat(d.getStatus().getObservedGeneration()).isEqualTo(1); - }); - } -} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/OptionalDependentIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/OptionalDependentIT.java new file mode 100644 index 0000000000..eed83655e9 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/OptionalDependentIT.java @@ -0,0 +1,77 @@ +package io.javaoperatorsdk.operator; + +import java.time.Duration; + +import org.junit.jupiter.api.AfterEach; +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.apiextensions.v1.CustomResourceDefinition; +import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; +import io.javaoperatorsdk.operator.sample.optionaldependent.OptionalDependentCustomResource; +import io.javaoperatorsdk.operator.sample.optionaldependent.OptionalDependentReconciler; +import io.javaoperatorsdk.operator.sample.optionaldependent.OptionalDependentSecondaryCustomResource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +public class OptionalDependentIT { + + @RegisterExtension + LocallyRunOperatorExtension extension = + LocallyRunOperatorExtension.builder().withReconciler(new OptionalDependentReconciler()) + .build(); + + @AfterEach + void cleanup() { + LocallyRunOperatorExtension.deleteCrd(OptionalDependentSecondaryCustomResource.class, + extension.getKubernetesClient()); + } + + @Test + void activatesResourceAfterCRDApplied() throws InterruptedException { + var r = extension.create(testResource()); + + await().pollDelay(Duration.ofMillis(200)).untilAsserted(() -> { + var secondary = + extension.get(OptionalDependentSecondaryCustomResource.class, r.getMetadata().getName()); + assertThat(secondary).isNull(); + }); + LocallyRunOperatorExtension.applyCrd(OptionalDependentSecondaryCustomResource.class, + extension.getKubernetesClient()); + + await().untilAsserted(() -> { + assertThat(extension.getKubernetesClient().resources(CustomResourceDefinition.class) + .withName("optionaldependentsecondarycustomresources.sample.javaoperatorsdk").get()) + .isNotNull(); + }); + + // triggering reconciliation explicitly + r.getMetadata().getAnnotations().put("trigger", "true"); + extension.replace(r); + + await().untilAsserted(() -> { + var secondary = + extension.get(OptionalDependentSecondaryCustomResource.class, r.getMetadata().getName()); + assertThat(secondary).isNotNull(); + }); + + extension.delete(r); + + await().timeout(Duration.ofSeconds(180)).untilAsserted(() -> { + var secondary = + extension.get(OptionalDependentSecondaryCustomResource.class, r.getMetadata().getName()); + assertThat(secondary).isNull(); + }); + } + + private OptionalDependentCustomResource testResource() { + var res = new OptionalDependentCustomResource(); + res.setMetadata(new ObjectMetaBuilder() + .withName("test1") + .build()); + return res; + } + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/UpdatingResAndSubResIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/PatchResourceAndStatusNoSSAIT.java similarity index 51% rename from operator-framework/src/test/java/io/javaoperatorsdk/operator/UpdatingResAndSubResIT.java rename to operator-framework/src/test/java/io/javaoperatorsdk/operator/PatchResourceAndStatusNoSSAIT.java index 3d7d27b4c3..9583629a7c 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/UpdatingResAndSubResIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/PatchResourceAndStatusNoSSAIT.java @@ -7,44 +7,47 @@ import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; -import io.javaoperatorsdk.operator.sample.doubleupdate.DoubleUpdateTestCustomReconciler; -import io.javaoperatorsdk.operator.sample.doubleupdate.DoubleUpdateTestCustomResource; -import io.javaoperatorsdk.operator.sample.doubleupdate.DoubleUpdateTestCustomResourceSpec; -import io.javaoperatorsdk.operator.sample.doubleupdate.DoubleUpdateTestCustomResourceStatus; +import io.javaoperatorsdk.operator.sample.patchresourceandstatusnossa.PatchResourceAndStatusNoSSACustomResource; +import io.javaoperatorsdk.operator.sample.patchresourceandstatusnossa.PatchResourceAndStatusNoSSAReconciler; +import io.javaoperatorsdk.operator.sample.patchresourceandstatusnossa.PatchResourceAndStatusNoSSASpec; +import io.javaoperatorsdk.operator.sample.patchresourceandstatusnossa.PatchResourceAndStatusNoSSAStatus; import io.javaoperatorsdk.operator.support.TestUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; -class UpdatingResAndSubResIT { +class PatchResourceAndStatusNoSSAIT { @RegisterExtension LocallyRunOperatorExtension operator = - LocallyRunOperatorExtension.builder().withReconciler(DoubleUpdateTestCustomReconciler.class) + + LocallyRunOperatorExtension.builder() + .withConfigurationService(o -> o.withUseSSAToPatchPrimaryResource(false)) + .withReconciler(PatchResourceAndStatusNoSSAReconciler.class) .build(); @Test void updatesSubResourceStatus() { - DoubleUpdateTestCustomResource resource = createTestCustomResource("1"); + PatchResourceAndStatusNoSSACustomResource resource = createTestCustomResource("1"); operator.create(resource); awaitStatusUpdated(resource.getMetadata().getName()); // wait for sure, there are no more events TestUtils.waitXms(300); - DoubleUpdateTestCustomResource customResource = + PatchResourceAndStatusNoSSACustomResource customResource = operator - .get(DoubleUpdateTestCustomResource.class, + .get(PatchResourceAndStatusNoSSACustomResource.class, resource.getMetadata().getName()); assertThat(TestUtils.getNumberOfExecutions(operator)) .isEqualTo(1); assertThat(customResource.getStatus().getState()) - .isEqualTo(DoubleUpdateTestCustomResourceStatus.State.SUCCESS); + .isEqualTo(PatchResourceAndStatusNoSSAStatus.State.SUCCESS); assertThat( customResource .getMetadata() .getAnnotations() - .get(DoubleUpdateTestCustomReconciler.TEST_ANNOTATION)) + .get(PatchResourceAndStatusNoSSAReconciler.TEST_ANNOTATION)) .isNotNull(); } @@ -53,21 +56,22 @@ void awaitStatusUpdated(String name) { .atMost(5, TimeUnit.SECONDS) .untilAsserted( () -> { - DoubleUpdateTestCustomResource cr = - operator.get(DoubleUpdateTestCustomResource.class, name); + PatchResourceAndStatusNoSSACustomResource cr = + operator.get(PatchResourceAndStatusNoSSACustomResource.class, name); assertThat(cr) .isNotNull(); assertThat(cr.getStatus()) .isNotNull(); assertThat(cr.getStatus().getState()) - .isEqualTo(DoubleUpdateTestCustomResourceStatus.State.SUCCESS); + .isEqualTo(PatchResourceAndStatusNoSSAStatus.State.SUCCESS); }); } - public DoubleUpdateTestCustomResource createTestCustomResource(String id) { - DoubleUpdateTestCustomResource resource = new DoubleUpdateTestCustomResource(); + public PatchResourceAndStatusNoSSACustomResource createTestCustomResource(String id) { + PatchResourceAndStatusNoSSACustomResource resource = + new PatchResourceAndStatusNoSSACustomResource(); resource.setMetadata(new ObjectMetaBuilder().withName("doubleupdateresource-" + id).build()); - resource.setSpec(new DoubleUpdateTestCustomResourceSpec()); + resource.setSpec(new PatchResourceAndStatusNoSSASpec()); resource.getSpec().setValue(id); return resource; } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/PatchResourceAndStatusWithSSAIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/PatchResourceAndStatusWithSSAIT.java new file mode 100644 index 0000000000..644316faf2 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/PatchResourceAndStatusWithSSAIT.java @@ -0,0 +1,13 @@ +package io.javaoperatorsdk.operator; + +import io.javaoperatorsdk.operator.api.reconciler.Reconciler; +import io.javaoperatorsdk.operator.sample.patchresourcewithssa.PatchResourceAndStatusWithSSAReconciler; + +public class PatchResourceAndStatusWithSSAIT extends PatchWithSSAITBase { + + @Override + protected Reconciler reconciler() { + return new PatchResourceAndStatusWithSSAReconciler(); + } + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/PatchResourceWithSSAIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/PatchResourceWithSSAIT.java new file mode 100644 index 0000000000..80f81f78d1 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/PatchResourceWithSSAIT.java @@ -0,0 +1,15 @@ +package io.javaoperatorsdk.operator; + + +import io.javaoperatorsdk.operator.api.reconciler.Reconciler; +import io.javaoperatorsdk.operator.sample.patchresourcewithssa.PatchResourceWithSSAReconciler; + + +public class PatchResourceWithSSAIT extends PatchWithSSAITBase { + + @Override + protected Reconciler reconciler() { + return new PatchResourceWithSSAReconciler(); + } + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/PatchWithSSAITBase.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/PatchWithSSAITBase.java new file mode 100644 index 0000000000..b3b6b4fc32 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/PatchWithSSAITBase.java @@ -0,0 +1,59 @@ +package io.javaoperatorsdk.operator; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.operator.api.reconciler.Reconciler; +import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; +import io.javaoperatorsdk.operator.sample.patchresourcewithssa.PatchResourceWithSSACustomResource; +import io.javaoperatorsdk.operator.sample.patchresourcewithssa.PatchResourceWithSSAReconciler; +import io.javaoperatorsdk.operator.sample.patchresourcewithssa.PatchResourceWithSSASpec; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +public abstract class PatchWithSSAITBase { + + public static final String RESOURCE_NAME = "test1"; + public static final String INIT_VALUE = "init value"; + + @RegisterExtension + LocallyRunOperatorExtension extension = + LocallyRunOperatorExtension.builder() + .withReconciler(reconciler()) + .build(); + + @Test + void reconcilerPatchesResourceWithSSA() { + extension.create(testResource()); + + await().untilAsserted(() -> { + var actualResource = extension.get(PatchResourceWithSSACustomResource.class, RESOURCE_NAME); + + assertThat(actualResource.getSpec().getInitValue()).isEqualTo(INIT_VALUE); + assertThat(actualResource.getSpec().getControllerManagedValue()) + .isEqualTo(PatchResourceWithSSAReconciler.ADDED_VALUE); + // finalizer is added to the SSA patch in the background by the framework + assertThat(actualResource.getMetadata().getFinalizers()).isNotEmpty(); + assertThat(actualResource.getStatus().isSuccessfullyReconciled()).isTrue(); + // one for resource, one for subresource + assertThat(actualResource.getMetadata().getManagedFields().stream() + .filter(mf -> mf.getManager() + .equals(reconciler().getClass().getSimpleName().toLowerCase())) + .toList()).hasSize(2); + }); + } + + protected abstract Reconciler reconciler(); + + PatchResourceWithSSACustomResource testResource() { + var res = new PatchResourceWithSSACustomResource(); + res.setMetadata(new ObjectMetaBuilder() + .withName(RESOURCE_NAME) + .build()); + res.setSpec(new PatchResourceWithSSASpec()); + res.getSpec().setInitValue(INIT_VALUE); + return res; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/PerResourcePollingEventSourceIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/PerResourcePollingEventSourceIT.java index a4b850b9d4..3a827572e5 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/PerResourcePollingEventSourceIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/PerResourcePollingEventSourceIT.java @@ -28,8 +28,8 @@ class PerResourcePollingEventSourceIT { **/ @Test void fetchedAndReconciledMultipleTimes() { - operator.create(PerResourceEventSourceCustomResource.class, resource(NAME_1)); - operator.create(PerResourceEventSourceCustomResource.class, resource(NAME_2)); + operator.create(resource(NAME_1)); + operator.create(resource(NAME_2)); var reconciler = operator.getReconcilerOfType(PerResourcePollingEventSourceTestReconciler.class); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/PrimaryIndexerIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/PrimaryIndexerIT.java index 16f223ce38..fb202de390 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/PrimaryIndexerIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/PrimaryIndexerIT.java @@ -13,12 +13,13 @@ import io.javaoperatorsdk.operator.sample.primaryindexer.PrimaryIndexerTestCustomResourceSpec; import io.javaoperatorsdk.operator.sample.primaryindexer.PrimaryIndexerTestReconciler; +import static io.javaoperatorsdk.operator.sample.primaryindexer.AbstractPrimaryIndexerTestReconciler.CONFIG_MAP_NAME; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; class PrimaryIndexerIT { - public static final String CONFIG_MAP_NAME = "common-config-map"; + public static final String RESOURCE_NAME1 = "test1"; public static final String RESOURCE_NAME2 = "test2"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/PrimaryToSecondaryDependentIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/PrimaryToSecondaryDependentIT.java index eaa7e4410f..0e48b726e9 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/PrimaryToSecondaryDependentIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/PrimaryToSecondaryDependentIT.java @@ -14,6 +14,7 @@ import io.javaoperatorsdk.operator.sample.primarytosecondaydependent.PrimaryToSecondaryDependentReconciler; import io.javaoperatorsdk.operator.sample.primarytosecondaydependent.PrimaryToSecondaryDependentSpec; +import static io.javaoperatorsdk.operator.sample.primarytosecondaydependent.ConfigMapDependent.TEST_CONFIG_MAP_NAME; import static io.javaoperatorsdk.operator.sample.primarytosecondaydependent.ConfigMapReconcilePrecondition.DO_NOT_RECONCILE; import static io.javaoperatorsdk.operator.sample.primarytosecondaydependent.PrimaryToSecondaryDependentReconciler.DATA_KEY; import static org.assertj.core.api.Assertions.assertThat; @@ -21,7 +22,7 @@ class PrimaryToSecondaryDependentIT { - public static final String TEST_CONFIG_MAP_NAME = "testconfigmap"; + public static final String TEST_CR_NAME = "test1"; public static final String TEST_DATA = "testData"; public diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/ReadOnlyBulkDependentIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/ReadOnlyBulkDependentIT.java new file mode 100644 index 0000000000..265eb98e4e --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/ReadOnlyBulkDependentIT.java @@ -0,0 +1,73 @@ +package io.javaoperatorsdk.operator; + +import java.time.Duration; + +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 io.javaoperatorsdk.operator.sample.bulkdependent.BulkDependentTestCustomResource; +import io.javaoperatorsdk.operator.sample.bulkdependent.BulkDependentTestSpec; +import io.javaoperatorsdk.operator.sample.bulkdependent.readonly.ReadOnlyBulkDependentResource; +import io.javaoperatorsdk.operator.sample.bulkdependent.readonly.ReadOnlyBulkReconciler; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +public class ReadOnlyBulkDependentIT { + + public static final int EXPECTED_NUMBER_OF_RESOURCES = 2; + public static final String TEST = "test"; + + @RegisterExtension + LocallyRunOperatorExtension extension = + LocallyRunOperatorExtension.builder() + .withReconciler(new ReadOnlyBulkReconciler()) + .build(); + + @Test + void readOnlyBulkDependent() { + var primary = extension.create(testCustomResource()); + + await().pollDelay(Duration.ofMillis(150)).untilAsserted(() -> { + var actualPrimary = extension.get(BulkDependentTestCustomResource.class, TEST); + + assertThat(actualPrimary.getStatus()).isNotNull(); + assertThat(actualPrimary.getStatus().getReady()).isFalse(); + }); + + var configMap1 = createConfigMap(1, primary); + extension.create(configMap1); + var configMap2 = createConfigMap(2, primary); + extension.create(configMap2); + + await().untilAsserted(() -> { + var actualPrimary = extension.get(BulkDependentTestCustomResource.class, TEST); + assertThat(actualPrimary.getStatus().getReady()).isTrue(); + }); + } + + private ConfigMap createConfigMap(int i, BulkDependentTestCustomResource primary) { + ConfigMap configMap = new ConfigMap(); + configMap.setMetadata(new ObjectMetaBuilder() + .withName(TEST + ReadOnlyBulkDependentResource.INDEX_DELIMITER + i) + .withNamespace(primary.getMetadata().getNamespace()) + .build()); + configMap.addOwnerReference(primary); + return configMap; + } + + BulkDependentTestCustomResource testCustomResource() { + BulkDependentTestCustomResource customResource = new BulkDependentTestCustomResource(); + customResource.setMetadata(new ObjectMetaBuilder() + .withName(TEST) + .build()); + customResource.setSpec(new BulkDependentTestSpec()); + customResource.getSpec().setNumberOfResources(EXPECTED_NUMBER_OF_RESOURCES); + + return customResource; + } + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/ReconcilerExecutorIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/ReconcilerExecutorIT.java index 07a022adb1..476bd842a5 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/ReconcilerExecutorIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/ReconcilerExecutorIT.java @@ -35,7 +35,6 @@ void configMapGetsCreatedForTestCustomResource() { @Test void patchesStatusForTestCustomResource() { - operator.getReconcilerOfType(TestReconciler.class).setPatchStatus(true); operator.getReconcilerOfType(TestReconciler.class).setUpdateStatus(true); TestCustomResource resource = TestUtils.testCustomResource(); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/StatusPatchNotLockingIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/StatusPatchNotLockingIT.java index 1746e5737d..0a82bed51a 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/StatusPatchNotLockingIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/StatusPatchNotLockingIT.java @@ -58,10 +58,12 @@ void valuesAreDeletedIfSetToNull() { assertThat(actual.getStatus().getMessage()).isEqualTo(MESSAGE); }); + // resource needs to be read again to we don't replace the with wrong managed fields + resource = operator.get(StatusPatchLockingCustomResource.class, TEST_RESOURCE_NAME); resource.getSpec().setMessageInStatus(false); operator.replace(resource); - await().untilAsserted(() -> { + await().timeout(Duration.ofMinutes(3)).untilAsserted(() -> { var actual = operator.get(StatusPatchLockingCustomResource.class, TEST_RESOURCE_NAME); assertThat(actual.getStatus()).isNotNull(); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/StatusPatchSSAMigrationIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/StatusPatchSSAMigrationIT.java new file mode 100644 index 0000000000..fba39cc03f --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/StatusPatchSSAMigrationIT.java @@ -0,0 +1,155 @@ +package io.javaoperatorsdk.operator; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +import io.fabric8.kubernetes.api.model.Namespace; +import io.fabric8.kubernetes.api.model.NamespaceBuilder; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClientBuilder; +import io.fabric8.kubernetes.client.utils.KubernetesResourceUtil; +import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; +import io.javaoperatorsdk.operator.sample.statuspatchnonlocking.StatusPatchLockingCustomResource; +import io.javaoperatorsdk.operator.sample.statuspatchnonlocking.StatusPatchLockingCustomResourceSpec; +import io.javaoperatorsdk.operator.sample.statuspatchnonlocking.StatusPatchLockingReconciler; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +public class StatusPatchSSAMigrationIT { + + public static final String TEST_RESOURCE_NAME = "test"; + + private final KubernetesClient client = new KubernetesClientBuilder().build(); + private String testNamespace; + + @BeforeEach + void beforeEach(TestInfo testInfo) { + LocallyRunOperatorExtension.applyCrd(StatusPatchLockingCustomResource.class, + client); + testInfo.getTestMethod() + .ifPresent(method -> testNamespace = KubernetesResourceUtil.sanitizeName(method.getName())); + client.namespaces().resource(testNamespace(testNamespace)).create(); + } + + @AfterEach + void afterEach() { + client.namespaces().withName(testNamespace).delete(); + await().untilAsserted(() -> { + var ns = client.namespaces().withName(testNamespace).get(); + assertThat(ns).isNull(); + }); + client.close(); + } + + + @Test + void testMigratingToSSA() { + var operator = startOperator(false); + var testResource = client.resource(testResource()).create(); + + await().untilAsserted(() -> { + var res = client.resource(testResource).get(); + assertThat(res.getStatus()).isNotNull(); + assertThat(res.getStatus().getMessage()).isEqualTo(StatusPatchLockingReconciler.MESSAGE); + assertThat(res.getStatus().getValue()).isEqualTo(1); + }); + operator.stop(); + + // start operator with SSA + operator = startOperator(true); + await().untilAsserted(() -> { + var res = client.resource(testResource).get(); + assertThat(res.getStatus()).isNotNull(); + assertThat(res.getStatus().getMessage()).isEqualTo(StatusPatchLockingReconciler.MESSAGE); + assertThat(res.getStatus().getValue()).isEqualTo(2); + }); + + var actualResource = client.resource(testResource()).get(); + actualResource.getSpec().setMessageInStatus(false); + client.resource(actualResource).update(); + + await().untilAsserted(() -> { + var res = client.resource(testResource).get(); + assertThat(res.getStatus()).isNotNull(); + // !!! This is wrong, the message should be null, + // see issue in Kubernetes: https://github.com/kubernetes/kubernetes/issues/99003 + assertThat(res.getStatus().getMessage()).isNotNull(); + assertThat(res.getStatus().getValue()).isEqualTo(3); + }); + + client.resource(testResource()).delete(); + operator.stop(); + } + + @Test + void workaroundMigratingFromToSSA() { + var operator = startOperator(false); + var testResource = client.resource(testResource()).create(); + + await().untilAsserted(() -> { + var res = client.resource(testResource).get(); + assertThat(res.getStatus()).isNotNull(); + assertThat(res.getStatus().getMessage()).isEqualTo(StatusPatchLockingReconciler.MESSAGE); + assertThat(res.getStatus().getValue()).isEqualTo(1); + }); + operator.stop(); + + // start operator with SSA + operator = startOperator(true); + await().untilAsserted(() -> { + var res = client.resource(testResource).get(); + assertThat(res.getStatus()).isNotNull(); + assertThat(res.getStatus().getMessage()).isEqualTo(StatusPatchLockingReconciler.MESSAGE); + assertThat(res.getStatus().getValue()).isEqualTo(2); + }); + + var actualResource = client.resource(testResource()).get(); + actualResource.getSpec().setMessageInStatus(false); + // removing the managed field entry for former method works + actualResource.getMetadata().setManagedFields(actualResource.getMetadata().getManagedFields() + .stream().filter(r -> !r.getOperation().equals("Update") && r.getSubresource() != null) + .toList()); + client.resource(actualResource).update(); + + await().untilAsserted(() -> { + var res = client.resource(testResource).get(); + assertThat(res.getStatus()).isNotNull(); + assertThat(res.getStatus().getMessage()).isNull(); + assertThat(res.getStatus().getValue()).isEqualTo(3); + }); + + client.resource(testResource()).delete(); + operator.stop(); + } + + + private Operator startOperator(boolean patchStatusWithSSA) { + var operator = new Operator(o -> o.withCloseClientOnStop(false) + .withUseSSAToPatchPrimaryResource(patchStatusWithSSA)); + operator.register(new StatusPatchLockingReconciler(), + o -> o.settingNamespaces(testNamespace)); + + operator.start(); + return operator; + } + + StatusPatchLockingCustomResource testResource() { + StatusPatchLockingCustomResource res = new StatusPatchLockingCustomResource(); + res.setSpec(new StatusPatchLockingCustomResourceSpec()); + res.setMetadata(new ObjectMetaBuilder() + .withName(TEST_RESOURCE_NAME) + .withNamespace(testNamespace) + .build()); + return res; + } + + private Namespace testNamespace(String name) { + return new NamespaceBuilder().withMetadata(new ObjectMetaBuilder() + .withName(name) + .build()).build(); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/StatusUpdateLockingIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/StatusUpdateLockingIT.java index 147c0403c3..e03883d8db 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/StatusUpdateLockingIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/StatusUpdateLockingIT.java @@ -21,23 +21,27 @@ class StatusUpdateLockingIT { @RegisterExtension LocallyRunOperatorExtension operator = - LocallyRunOperatorExtension.builder().withReconciler(StatusUpdateLockingReconciler.class) + LocallyRunOperatorExtension.builder() + .withConfigurationService(o -> o.withUseSSAToPatchPrimaryResource(false)) + .withReconciler(StatusUpdateLockingReconciler.class) .build(); @Test - void optimisticLockingDoneOnStatusUpdate() throws InterruptedException { + void noOptimisticLockingDoneOnStatusPatch() throws InterruptedException { var resource = operator.create(createResource()); Thread.sleep(WAIT_TIME / 2); resource.getMetadata().setAnnotations(Map.of("key", "value")); operator.replace(resource); - await().pollDelay(Duration.ofMillis(WAIT_TIME)).untilAsserted(() -> { - assertThat( - operator.getReconcilerOfType(StatusUpdateLockingReconciler.class).getNumberOfExecutions()) - .isEqualTo(2); - assertThat(operator.get(StatusUpdateLockingCustomResource.class, TEST_RESOURCE_NAME) - .getStatus().getValue()).isEqualTo(1); - }); + await().pollDelay(Duration.ofMillis(WAIT_TIME)).timeout(Duration.ofSeconds(460)) + .untilAsserted(() -> { + assertThat( + operator.getReconcilerOfType(StatusUpdateLockingReconciler.class) + .getNumberOfExecutions()) + .isEqualTo(1); + assertThat(operator.get(StatusUpdateLockingCustomResource.class, TEST_RESOURCE_NAME) + .getStatus().getValue()).isEqualTo(1); + }); } StatusUpdateLockingCustomResource createResource() { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowExplicitCleanupIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowExplicitCleanupIT.java new file mode 100644 index 0000000000..b26bfdd443 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowExplicitCleanupIT.java @@ -0,0 +1,50 @@ +package io.javaoperatorsdk.operator; + +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 io.javaoperatorsdk.operator.sample.workflowexplicitcleanup.WorkflowExplicitCleanupCustomResource; +import io.javaoperatorsdk.operator.sample.workflowexplicitcleanup.WorkflowExplicitCleanupReconciler; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +public class WorkflowExplicitCleanupIT { + + public static final String RESOURCE_NAME = "test1"; + + @RegisterExtension + LocallyRunOperatorExtension extension = + LocallyRunOperatorExtension.builder() + .withReconciler(WorkflowExplicitCleanupReconciler.class) + .build(); + + @Test + void workflowInvokedExplicitly() { + var res = extension.create(testResource()); + + await().untilAsserted(() -> { + assertThat(extension.get(ConfigMap.class, RESOURCE_NAME)).isNotNull(); + }); + + extension.delete(res); + + // The ConfigMap is not garbage collected, this tests that even if the cleaner is not + // implemented the workflow cleanup still called even if there is explicit invocation + await().untilAsserted(() -> { + assertThat(extension.get(ConfigMap.class, RESOURCE_NAME)).isNull(); + }); + } + + WorkflowExplicitCleanupCustomResource testResource() { + var res = new WorkflowExplicitCleanupCustomResource(); + res.setMetadata(new ObjectMetaBuilder() + .withName(RESOURCE_NAME) + .build()); + return res; + } + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowExplicitInvocationIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowExplicitInvocationIT.java new file mode 100644 index 0000000000..dba08faba0 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowExplicitInvocationIT.java @@ -0,0 +1,66 @@ +package io.javaoperatorsdk.operator; + +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 io.javaoperatorsdk.operator.sample.workflowexplicitinvocation.WorkflowExplicitInvocationCustomResource; +import io.javaoperatorsdk.operator.sample.workflowexplicitinvocation.WorkflowExplicitInvocationReconciler; +import io.javaoperatorsdk.operator.sample.workflowexplicitinvocation.WorkflowExplicitInvocationSpec; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +public class WorkflowExplicitInvocationIT { + + public static final String RESOURCE_NAME = "test1"; + + @RegisterExtension + LocallyRunOperatorExtension extension = + LocallyRunOperatorExtension.builder() + .withReconciler(WorkflowExplicitInvocationReconciler.class) + .build(); + + @Test + void workflowInvokedExplicitly() { + var res = extension.create(testResource()); + var reconciler = extension.getReconcilerOfType(WorkflowExplicitInvocationReconciler.class); + + await().untilAsserted(() -> { + assertThat(reconciler.getNumberOfExecutions()).isEqualTo(1); + assertThat(extension.get(ConfigMap.class, RESOURCE_NAME)).isNull(); + }); + + reconciler.setInvokeWorkflow(true); + + // trigger reconciliation + res.getSpec().setValue("changed value"); + res = extension.replace(res); + + await().untilAsserted(() -> { + assertThat(reconciler.getNumberOfExecutions()).isEqualTo(2); + assertThat(extension.get(ConfigMap.class, RESOURCE_NAME)).isNotNull(); + }); + + extension.delete(res); + + // The ConfigMap is not garbage collected, this tests that even if the cleaner is not + // implemented the workflow cleanup still called even if there is explicit invocation + await().untilAsserted(() -> { + assertThat(extension.get(ConfigMap.class, RESOURCE_NAME)).isNull(); + }); + } + + WorkflowExplicitInvocationCustomResource testResource() { + var res = new WorkflowExplicitInvocationCustomResource(); + res.setMetadata(new ObjectMetaBuilder() + .withName(RESOURCE_NAME) + .build()); + res.setSpec(new WorkflowExplicitInvocationSpec()); + res.getSpec().setValue("initial value"); + return res; + } + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowSilentExceptionHandlingIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowSilentExceptionHandlingIT.java new file mode 100644 index 0000000000..cd79283585 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowSilentExceptionHandlingIT.java @@ -0,0 +1,47 @@ +package io.javaoperatorsdk.operator; + +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 io.javaoperatorsdk.operator.sample.workflowsilentexceptionhandling.HandleWorkflowExceptionsInReconcilerCustomResource; +import io.javaoperatorsdk.operator.sample.workflowsilentexceptionhandling.HandleWorkflowExceptionsInReconcilerReconciler; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +public class WorkflowSilentExceptionHandlingIT { + + @RegisterExtension + LocallyRunOperatorExtension extension = + LocallyRunOperatorExtension.builder() + .withReconciler(HandleWorkflowExceptionsInReconcilerReconciler.class) + .build(); + + @Test + void handleExceptionsInReconciler() { + extension.create(testResource()); + var reconciler = + extension.getReconcilerOfType(HandleWorkflowExceptionsInReconcilerReconciler.class); + + await().untilAsserted(() -> { + assertThat(reconciler.isErrorsFoundInReconcilerResult()).isTrue(); + }); + + extension.delete(testResource()); + + await().untilAsserted(() -> { + assertThat(reconciler.isErrorsFoundInCleanupResult()).isTrue(); + }); + } + + HandleWorkflowExceptionsInReconcilerCustomResource testResource() { + var res = new HandleWorkflowExceptionsInReconcilerCustomResource(); + res.setMetadata(new ObjectMetaBuilder() + .withName("test1") + .build()); + return res; + } + +} 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 e107623347..820ce1589f 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 @@ -27,6 +27,7 @@ import io.javaoperatorsdk.operator.api.reconciler.MaxReconciliationInterval; 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.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; @@ -42,13 +43,7 @@ import io.javaoperatorsdk.operator.processing.retry.RetryExecution; import io.javaoperatorsdk.operator.sample.readonly.ReadOnlyDependent; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; class BaseConfigurationServiceTest { @@ -83,7 +78,8 @@ void defaultValuesShouldBeConsistent() { @SuppressWarnings("rawtypes") private KubernetesDependentResourceConfig extractDependentKubernetesResourceConfig( io.javaoperatorsdk.operator.api.config.ControllerConfiguration configuration, int index) { - final var spec = configuration.getDependentResources().get(index); + final var spec = + configuration.getWorkflowSpec().orElseThrow().getDependentResourceSpecs().get(index); return (KubernetesDependentResourceConfig) DependentResourceConfigurationResolver .configurationFor(spec, configuration); } @@ -92,11 +88,11 @@ private KubernetesDependentResourceConfig extractDependentKubernetesResourceConf @SuppressWarnings("rawtypes") void getDependentResources() { var configuration = configFor(new NoDepReconciler()); - var dependents = configuration.getDependentResources(); - assertTrue(dependents.isEmpty()); + var workflowSpec = configuration.getWorkflowSpec(); + assertTrue(workflowSpec.isEmpty()); configuration = configFor(new OneDepReconciler()); - dependents = configuration.getDependentResources(); + var dependents = configuration.getWorkflowSpec().orElseThrow().getDependentResourceSpecs(); assertFalse(dependents.isEmpty()); assertEquals(1, dependents.size()); final var dependentResourceName = DependentResource.defaultNameFor(ReadOnlyDependent.class); @@ -106,14 +102,14 @@ void getDependentResources() { var maybeConfig = DependentResourceConfigurationResolver.configurationFor(dependentSpec, configuration); assertNotNull(maybeConfig); - assertTrue(maybeConfig instanceof KubernetesDependentResourceConfig); + assertInstanceOf(KubernetesDependentResourceConfig.class, maybeConfig); final var config = (KubernetesDependentResourceConfig) maybeConfig; // check that the DependentResource inherits the controller's configuration if applicable assertEquals(1, config.namespaces().size()); assertEquals(Set.of(OneDepReconciler.CONFIGURED_NS), config.namespaces()); configuration = configFor(new NamedDepReconciler()); - dependents = configuration.getDependentResources(); + dependents = configuration.getWorkflowSpec().orElseThrow().getDependentResourceSpecs(); assertFalse(dependents.isEmpty()); assertEquals(1, dependents.size()); dependentSpec = findByName(dependents, NamedDepReconciler.NAME); @@ -121,7 +117,7 @@ void getDependentResources() { maybeConfig = DependentResourceConfigurationResolver.configurationFor(dependentSpec, configuration); assertNotNull(maybeConfig); - assertTrue(maybeConfig instanceof KubernetesDependentResourceConfig); + assertInstanceOf(KubernetesDependentResourceConfig.class, maybeConfig); } @Test @@ -152,7 +148,7 @@ void tryingToAddDuplicatedDependentsWithoutNameShouldFail() { @Test void addingDuplicatedDependentsWithNameShouldWork() { var config = configFor(new NamedDuplicatedDepReconciler()); - var dependents = config.getDependentResources(); + var dependents = config.getWorkflowSpec().orElseThrow().getDependentResourceSpecs(); assertEquals(2, dependents.size()); assertTrue(findByNameOptional(dependents, NamedDuplicatedDepReconciler.NAME).isPresent() && findByNameOptional(dependents, DependentResource.defaultNameFor(ReadOnlyDependent.class)) @@ -237,7 +233,9 @@ void configuringFromCustomAnnotationsShouldWork() { private static int getValue( io.javaoperatorsdk.operator.api.config.ControllerConfiguration configuration, int index) { return ((CustomConfig) DependentResourceConfigurationResolver - .configurationFor(configuration.getDependentResources().get(index), configuration)) + .configurationFor( + configuration.getWorkflowSpec().orElseThrow().getDependentResourceSpecs().get(index), + configuration)) .getValue(); } @@ -247,14 +245,13 @@ private static int getValue( private static class MaxIntervalReconciler implements Reconciler { @Override - public UpdateControl reconcile(ConfigMap resource, Context context) - throws Exception { + public UpdateControl reconcile(ConfigMap resource, Context context) { return null; } } - @ControllerConfiguration(namespaces = OneDepReconciler.CONFIGURED_NS, - dependents = @Dependent(type = ReadOnlyDependent.class)) + @Workflow(dependents = @Dependent(type = ReadOnlyDependent.class)) + @ControllerConfiguration(namespaces = OneDepReconciler.CONFIGURED_NS) private static class OneDepReconciler implements Reconciler { private static final String CONFIGURED_NS = "foo"; @@ -265,8 +262,8 @@ public UpdateControl reconcile(ConfigMap resource, Context } } - @ControllerConfiguration( - dependents = @Dependent(type = ReadOnlyDependent.class, name = NamedDepReconciler.NAME)) + @Workflow(dependents = @Dependent(type = ReadOnlyDependent.class, name = NamedDepReconciler.NAME)) + @ControllerConfiguration private static class NamedDepReconciler implements Reconciler { private static final String NAME = "foo"; @@ -277,11 +274,11 @@ public UpdateControl reconcile(ConfigMap resource, Context } } - @ControllerConfiguration( - dependents = { - @Dependent(type = ReadOnlyDependent.class), - @Dependent(type = ReadOnlyDependent.class) - }) + @Workflow(dependents = { + @Dependent(type = ReadOnlyDependent.class), + @Dependent(type = ReadOnlyDependent.class) + }) + @ControllerConfiguration private static class DuplicatedDepReconciler implements Reconciler { @Override @@ -290,11 +287,11 @@ public UpdateControl reconcile(ConfigMap resource, Context } } - @ControllerConfiguration( - dependents = { - @Dependent(type = ReadOnlyDependent.class, name = NamedDuplicatedDepReconciler.NAME), - @Dependent(type = ReadOnlyDependent.class) - }) + @Workflow(dependents = { + @Dependent(type = ReadOnlyDependent.class, name = NamedDuplicatedDepReconciler.NAME), + @Dependent(type = ReadOnlyDependent.class) + }) + @ControllerConfiguration private static class NamedDuplicatedDepReconciler implements Reconciler { private static final String NAME = "duplicated"; @@ -314,15 +311,15 @@ public UpdateControl reconcile(ConfigMap resource, Context } } - @ControllerConfiguration(dependents = { + @Workflow(dependents = { @Dependent(type = SelectorReconciler.WithAnnotation.class), @Dependent(type = ReadOnlyDependent.class) }) + @ControllerConfiguration private static class SelectorReconciler implements Reconciler { @Override - public UpdateControl reconcile(ConfigMap resource, Context context) - throws Exception { + public UpdateControl reconcile(ConfigMap resource, Context context) { return null; } @@ -377,8 +374,7 @@ public void initFrom(TestRetryConfiguration configuration) { private static class ConfigurableRateLimitAndRetryReconciler implements Reconciler { @Override - public UpdateControl reconcile(ConfigMap resource, Context context) - throws Exception { + public UpdateControl reconcile(ConfigMap resource, Context context) { return UpdateControl.noUpdate(); } } @@ -397,8 +393,7 @@ private static class CheckRetryingGraduallyConfiguration implements Reconciler reconcile(ConfigMap resource, Context context) - throws Exception { + public UpdateControl reconcile(ConfigMap resource, Context context) { return UpdateControl.noUpdate(); } } @@ -438,15 +433,15 @@ public UpdateControl reconcile(ConfigMap resource, Context } } - @ControllerConfiguration(dependents = { + @Workflow(dependents = { @Dependent(type = CustomAnnotatedDep.class), @Dependent(type = ChildCustomAnnotatedDep.class) }) + @ControllerConfiguration() private static class CustomAnnotationReconciler implements Reconciler { @Override - public UpdateControl reconcile(ConfigMap resource, Context context) - throws Exception { + public UpdateControl reconcile(ConfigMap resource, Context context) { return null; } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/runtime/ControllerConfigurationAnnotationProcessorTest.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/runtime/ControllerConfigurationAnnotationProcessorTest.java index ce9637af9e..a7365c19b9 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/runtime/ControllerConfigurationAnnotationProcessorTest.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/runtime/ControllerConfigurationAnnotationProcessorTest.java @@ -33,7 +33,7 @@ public void generateCorrectDoneableClassIfThereIsAbstractBaseController() { } @Test - public void generateDoneableClasswithMultilevelHierarchy() { + public void generateDoneableClassWithMultilevelHierarchy() { Compilation compilation = Compiler.javac() .withProcessors(new ControllerConfigurationAnnotationProcessor()) diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ConfigMapDeleterBulkDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ConfigMapDeleterBulkDependentResource.java index 29a9af89e7..1ee4e3ef24 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ConfigMapDeleterBulkDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ConfigMapDeleterBulkDependentResource.java @@ -8,10 +8,7 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; -import io.javaoperatorsdk.operator.processing.dependent.BulkDependentResource; -import io.javaoperatorsdk.operator.processing.dependent.Creator; -import io.javaoperatorsdk.operator.processing.dependent.Updater; +import io.javaoperatorsdk.operator.processing.dependent.*; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource; /** @@ -20,10 +17,7 @@ public class ConfigMapDeleterBulkDependentResource extends KubernetesDependentResource - implements Creator, - Updater, - Deleter, - BulkDependentResource { + implements CRUDBulkDependentResource { public static final String LABEL_KEY = "bulk"; public static final String LABEL_VALUE = "true"; @@ -34,6 +28,11 @@ public ConfigMapDeleterBulkDependentResource() { super(ConfigMap.class); } + @Override + protected Class getPrimaryResourceType() { + return BulkDependentTestCustomResource.class; + } + @Override public Map desiredResources(BulkDependentTestCustomResource primary, Context context) { @@ -41,13 +40,12 @@ public Map desiredResources(BulkDependentTestCustomResource p Map res = new HashMap<>(); for (int i = 0; i < number; i++) { var key = Integer.toString(i); - res.put(key, desired(primary, key, context)); + res.put(key, desired(primary, key)); } return res; } - public ConfigMap desired(BulkDependentTestCustomResource primary, String key, - Context context) { + public ConfigMap desired(BulkDependentTestCustomResource primary, String key) { ConfigMap configMap = new ConfigMap(); configMap.setMetadata(new ObjectMetaBuilder() .withName(primary.getMetadata().getName() + INDEX_DELIMITER + key) diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ManagedBulkDependentReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ManagedBulkDependentReconciler.java index 3b2acd942e..95be38fc4d 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ManagedBulkDependentReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ManagedBulkDependentReconciler.java @@ -2,13 +2,11 @@ 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.*; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; -@ControllerConfiguration(dependents = @Dependent(type = CRUDConfigMapBulkDependentResource.class)) +@Workflow(dependents = @Dependent(type = CRUDConfigMapBulkDependentResource.class)) +@ControllerConfiguration public class ManagedBulkDependentReconciler implements Reconciler { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ManagedBulkDependentWithReadyConditionReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ManagedBulkDependentWithReadyConditionReconciler.java index aca78d5d25..aca1e98c88 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ManagedBulkDependentWithReadyConditionReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ManagedBulkDependentWithReadyConditionReconciler.java @@ -2,15 +2,12 @@ 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.*; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; -import io.javaoperatorsdk.operator.processing.dependent.workflow.WorkflowReconcileResult; -@ControllerConfiguration(dependents = @Dependent(readyPostcondition = SampleBulkCondition.class, +@Workflow(dependents = @Dependent(readyPostcondition = SampleBulkCondition.class, type = CRUDConfigMapBulkDependentResource.class)) +@ControllerConfiguration() public class ManagedBulkDependentWithReadyConditionReconciler implements Reconciler { @@ -22,8 +19,9 @@ public UpdateControl reconcile( Context context) throws Exception { numberOfExecutions.incrementAndGet(); - var ready = context.managedDependentResourceContext().getWorkflowReconcileResult() - .map(WorkflowReconcileResult::allDependentResourcesReady).orElseThrow(); + var ready = context.managedWorkflowAndDependentResourceContext().getWorkflowReconcileResult() + .allDependentResourcesReady(); + resource.setStatus(new BulkDependentTestStatus()); resource.getStatus().setReady(ready); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ManagedDeleterBulkReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ManagedDeleterBulkReconciler.java index e759bdd200..db5ba60044 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ManagedDeleterBulkReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/ManagedDeleterBulkReconciler.java @@ -1,13 +1,10 @@ package io.javaoperatorsdk.operator.sample.bulkdependent; -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.*; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; -@ControllerConfiguration( - dependents = @Dependent(type = ConfigMapDeleterBulkDependentResource.class)) +@Workflow(dependents = @Dependent(type = ConfigMapDeleterBulkDependentResource.class)) +@ControllerConfiguration public class ManagedDeleterBulkReconciler implements Reconciler { @Override public UpdateControl reconcile( diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/StandaloneBulkDependentReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/StandaloneBulkDependentReconciler.java index 6af93232b4..b8feb5c87e 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/StandaloneBulkDependentReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/StandaloneBulkDependentReconciler.java @@ -1,6 +1,6 @@ package io.javaoperatorsdk.operator.sample.bulkdependent; -import java.util.Map; +import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import io.javaoperatorsdk.operator.api.reconciler.*; @@ -9,8 +9,7 @@ @ControllerConfiguration public class StandaloneBulkDependentReconciler - implements Reconciler, TestExecutionInfoProvider, - EventSourceInitializer { + implements Reconciler, TestExecutionInfoProvider { private final AtomicInteger numberOfExecutions = new AtomicInteger(0); @@ -36,9 +35,8 @@ public int getNumberOfExecutions() { } @Override - public Map prepareEventSources( + public List prepareEventSources( EventSourceContext context) { - return EventSourceInitializer - .nameEventSources(dependent.initEventSource(context)); + return List.of(dependent.initEventSource(context)); } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalBulkDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalBulkDependentResource.java index dbad6dcfb8..7dee7ae962 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalBulkDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalBulkDependentResource.java @@ -7,8 +7,10 @@ import java.util.stream.Collectors; import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; import io.javaoperatorsdk.operator.processing.dependent.BulkDependentResource; import io.javaoperatorsdk.operator.processing.dependent.BulkUpdater; +import io.javaoperatorsdk.operator.processing.dependent.Creator; import io.javaoperatorsdk.operator.processing.dependent.external.PollingDependentResource; import io.javaoperatorsdk.operator.processing.event.ResourceID; import io.javaoperatorsdk.operator.sample.bulkdependent.BulkDependentTestCustomResource; @@ -16,6 +18,8 @@ public class ExternalBulkDependentResource extends PollingDependentResource implements BulkDependentResource, + Creator, + Deleter, BulkUpdater { public static final String EXTERNAL_RESOURCE_NAME_DELIMITER = "#"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalBulkResourceReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalBulkResourceReconciler.java index 2543422d74..f11621e4c2 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalBulkResourceReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/external/ExternalBulkResourceReconciler.java @@ -1,13 +1,11 @@ package io.javaoperatorsdk.operator.sample.bulkdependent.external; -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.*; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; import io.javaoperatorsdk.operator.sample.bulkdependent.BulkDependentTestCustomResource; -@ControllerConfiguration(dependents = @Dependent(type = ExternalBulkDependentResource.class)) +@Workflow(dependents = @Dependent(type = ExternalBulkDependentResource.class)) +@ControllerConfiguration() public class ExternalBulkResourceReconciler implements Reconciler { @Override diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/readonly/ReadOnlyBulkDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/readonly/ReadOnlyBulkDependentResource.java new file mode 100644 index 0000000000..0f4fb80b8a --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/readonly/ReadOnlyBulkDependentResource.java @@ -0,0 +1,54 @@ +package io.javaoperatorsdk.operator.sample.bulkdependent.readonly; + +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.processing.dependent.BulkDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.event.ResourceID; +import io.javaoperatorsdk.operator.processing.event.source.SecondaryToPrimaryMapper; +import io.javaoperatorsdk.operator.processing.event.source.informer.Mappers; +import io.javaoperatorsdk.operator.sample.bulkdependent.BulkDependentTestCustomResource; + + +public class ReadOnlyBulkDependentResource + extends + KubernetesDependentResource + implements BulkDependentResource, + SecondaryToPrimaryMapper { + + public static final String INDEX_DELIMITER = "-"; + + public ReadOnlyBulkDependentResource() { + super(ConfigMap.class); + } + + @Override + protected Class getPrimaryResourceType() { + return BulkDependentTestCustomResource.class; + } + + @Override + public Map getSecondaryResources(BulkDependentTestCustomResource primary, + Context context) { + return context.getSecondaryResourcesAsStream(ConfigMap.class) + .filter(cm -> getName(cm).startsWith(primary.getMetadata().getName())) + .collect(Collectors.toMap( + cm -> getName(cm).substring(getName(cm).lastIndexOf(INDEX_DELIMITER) + 1), + Function.identity())); + } + + private static String getName(ConfigMap cm) { + return cm.getMetadata().getName(); + } + + @Override + public Set toPrimaryResourceIDs(ConfigMap resource) { + return Mappers.fromOwnerReferences(BulkDependentTestCustomResource.class, false) + .toPrimaryResourceIDs(resource); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/readonly/ReadOnlyBulkReadyPostCondition.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/readonly/ReadOnlyBulkReadyPostCondition.java new file mode 100644 index 0000000000..9d036d74bd --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/readonly/ReadOnlyBulkReadyPostCondition.java @@ -0,0 +1,23 @@ +package io.javaoperatorsdk.operator.sample.bulkdependent.readonly; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.processing.dependent.BulkDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition; +import io.javaoperatorsdk.operator.sample.bulkdependent.BulkDependentTestCustomResource; + +public class ReadOnlyBulkReadyPostCondition + implements Condition { + @Override + public boolean isMet( + DependentResource dependentResource, + BulkDependentTestCustomResource primary, Context context) { + var minResourceNumber = primary.getSpec().getNumberOfResources(); + @SuppressWarnings("unchecked") + var secondaryResources = + ((BulkDependentResource) dependentResource) + .getSecondaryResources(primary, context); + return minResourceNumber <= secondaryResources.size(); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/readonly/ReadOnlyBulkReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/readonly/ReadOnlyBulkReconciler.java new file mode 100644 index 0000000000..df3366c115 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/bulkdependent/readonly/ReadOnlyBulkReconciler.java @@ -0,0 +1,33 @@ +package io.javaoperatorsdk.operator.sample.bulkdependent.readonly; + +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.operator.api.reconciler.*; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; +import io.javaoperatorsdk.operator.sample.bulkdependent.BulkDependentTestCustomResource; +import io.javaoperatorsdk.operator.sample.bulkdependent.BulkDependentTestStatus; + +@Workflow(dependents = @Dependent(type = ReadOnlyBulkDependentResource.class, + readyPostcondition = ReadOnlyBulkReadyPostCondition.class)) +@ControllerConfiguration +public class ReadOnlyBulkReconciler implements Reconciler { + @Override + public UpdateControl reconcile( + BulkDependentTestCustomResource resource, Context context) { + + var nonReadyDependents = + context.managedWorkflowAndDependentResourceContext().getWorkflowReconcileResult() + .getNotReadyDependents(); + + + BulkDependentTestCustomResource customResource = new BulkDependentTestCustomResource(); + customResource.setMetadata(new ObjectMetaBuilder() + .withName(resource.getMetadata().getName()) + .withNamespace(resource.getMetadata().getNamespace()) + .build()); + var status = new BulkDependentTestStatus(); + status.setReady(nonReadyDependents.isEmpty()); + customResource.setStatus(status); + + return UpdateControl.patchStatus(customResource); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/changenamespace/ChangeNamespaceTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/changenamespace/ChangeNamespaceTestReconciler.java index 7d51f311e1..00750b30b0 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/changenamespace/ChangeNamespaceTestReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/changenamespace/ChangeNamespaceTestReconciler.java @@ -1,5 +1,6 @@ package io.javaoperatorsdk.operator.sample.changenamespace; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -13,21 +14,20 @@ @ControllerConfiguration public class ChangeNamespaceTestReconciler - implements Reconciler, - EventSourceInitializer { + implements Reconciler { private final ConcurrentHashMap numberOfResourceReconciliations = new ConcurrentHashMap<>(); @Override - public Map prepareEventSources( + public List prepareEventSources( EventSourceContext context) { InformerEventSource configMapES = new InformerEventSource<>(InformerConfiguration.from(ConfigMap.class, context) .build(), context); - return EventSourceInitializer.nameEventSources(configMapES); + return List.of(configMapES); } @Override diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/cleanermanageddependent/CleanerForManagedDependentTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/cleanermanageddependent/CleanerForManagedDependentTestReconciler.java index 6be29c5092..c4bbb3c9f0 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/cleanermanageddependent/CleanerForManagedDependentTestReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/cleanermanageddependent/CleanerForManagedDependentTestReconciler.java @@ -6,7 +6,8 @@ import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider; -@ControllerConfiguration(dependents = {@Dependent(type = ConfigMapDependentResource.class)}) +@Workflow(dependents = {@Dependent(type = ConfigMapDependentResource.class)}) +@ControllerConfiguration public class CleanerForManagedDependentTestReconciler implements Reconciler, TestExecutionInfoProvider { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/clusterscopedresource/ClusterScopedCustomResourceReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/clusterscopedresource/ClusterScopedCustomResourceReconciler.java index a6f5e00c96..ec872bd6b2 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/clusterscopedresource/ClusterScopedCustomResourceReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/clusterscopedresource/ClusterScopedCustomResourceReconciler.java @@ -1,5 +1,6 @@ package io.javaoperatorsdk.operator.sample.clusterscopedresource; +import java.util.List; import java.util.Map; import io.fabric8.kubernetes.api.model.ConfigMap; @@ -13,8 +14,7 @@ @ControllerConfiguration public class ClusterScopedCustomResourceReconciler - implements Reconciler, - EventSourceInitializer { + implements Reconciler { public static final String DATA_KEY = "data-key"; @@ -53,12 +53,13 @@ private ConfigMap desired(ClusterScopedCustomResource resource) { } @Override - public Map prepareEventSources( + public List prepareEventSources( EventSourceContext context) { var ies = new InformerEventSource<>(InformerConfiguration.from(ConfigMap.class, context) - .withSecondaryToPrimaryMapper(Mappers.fromOwnerReference(true)) + .withSecondaryToPrimaryMapper( + Mappers.fromOwnerReferences(context.getPrimaryResourceClass(), true)) .withLabelSelector(TEST_LABEL_KEY + "=" + TEST_LABEL_VALUE) .build(), context); - return EventSourceInitializer.nameEventSources(ies); + return List.of(ies); } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/complexdependent/ComplexDependentReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/complexdependent/ComplexDependentReconciler.java index da0aaf1060..d2259faeaf 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/complexdependent/ComplexDependentReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/complexdependent/ComplexDependentReconciler.java @@ -1,6 +1,6 @@ package io.javaoperatorsdk.operator.sample.complexdependent; -import java.util.Map; +import java.util.List; import java.util.Objects; import io.fabric8.kubernetes.api.model.Service; @@ -15,25 +15,23 @@ import static io.javaoperatorsdk.operator.sample.complexdependent.ComplexDependentReconciler.SERVICE_EVENT_SOURCE_NAME; import static io.javaoperatorsdk.operator.sample.complexdependent.ComplexDependentReconciler.STATEFUL_SET_EVENT_SOURCE_NAME; -@ControllerConfiguration( - name = "project-operator", - dependents = { - @Dependent(name = "first-svc", type = FirstService.class, - useEventSourceWithName = SERVICE_EVENT_SOURCE_NAME), - @Dependent(name = "second-svc", type = SecondService.class, - useEventSourceWithName = SERVICE_EVENT_SOURCE_NAME), - @Dependent(name = "first", type = FirstStatefulSet.class, - useEventSourceWithName = STATEFUL_SET_EVENT_SOURCE_NAME, - dependsOn = {"first-svc"}, - readyPostcondition = StatefulSetReadyCondition.class), - @Dependent(name = "second", - type = SecondStatefulSet.class, - useEventSourceWithName = STATEFUL_SET_EVENT_SOURCE_NAME, - dependsOn = {"second-svc", "first"}, - readyPostcondition = StatefulSetReadyCondition.class), - }) -public class ComplexDependentReconciler implements Reconciler, - EventSourceInitializer { +@Workflow(dependents = { + @Dependent(name = "first-svc", type = FirstService.class, + useEventSourceWithName = SERVICE_EVENT_SOURCE_NAME), + @Dependent(name = "second-svc", type = SecondService.class, + useEventSourceWithName = SERVICE_EVENT_SOURCE_NAME), + @Dependent(name = "first", type = FirstStatefulSet.class, + useEventSourceWithName = STATEFUL_SET_EVENT_SOURCE_NAME, + dependsOn = {"first-svc"}, + readyPostcondition = StatefulSetReadyCondition.class), + @Dependent(name = "second", + type = SecondStatefulSet.class, + useEventSourceWithName = STATEFUL_SET_EVENT_SOURCE_NAME, + dependsOn = {"second-svc", "first"}, + readyPostcondition = StatefulSetReadyCondition.class), +}) +@ControllerConfiguration(name = "project-operator") +public class ComplexDependentReconciler implements Reconciler { public static final String SERVICE_EVENT_SOURCE_NAME = "serviceEventSource"; public static final String STATEFUL_SET_EVENT_SOURCE_NAME = "statefulSetEventSource"; @@ -42,27 +40,28 @@ public class ComplexDependentReconciler implements Reconciler reconcile( ComplexDependentCustomResource resource, Context context) throws Exception { - var ready = context.managedDependentResourceContext().getWorkflowReconcileResult() - .orElseThrow().allDependentResourcesReady(); + var ready = context.managedWorkflowAndDependentResourceContext().getWorkflowReconcileResult() + .allDependentResourcesReady(); var status = Objects.requireNonNullElseGet(resource.getStatus(), ComplexDependentStatus::new); status.setStatus(ready ? RECONCILE_STATUS.READY : RECONCILE_STATUS.NOT_READY); resource.setStatus(status); - return UpdateControl.updateStatus(resource); + return UpdateControl.patchStatus(resource); } @Override - public Map prepareEventSources( + public List prepareEventSources( EventSourceContext context) { InformerEventSource serviceEventSource = - new InformerEventSource<>(InformerConfiguration.from(Service.class, context).build(), + new InformerEventSource<>(SERVICE_EVENT_SOURCE_NAME, + InformerConfiguration.from(Service.class, context).build(), context); InformerEventSource statefulSetEventSource = - new InformerEventSource<>(InformerConfiguration.from(StatefulSet.class, context).build(), + new InformerEventSource<>(STATEFUL_SET_EVENT_SOURCE_NAME, + InformerConfiguration.from(StatefulSet.class, context).build(), context); - return Map.of(SERVICE_EVENT_SOURCE_NAME, serviceEventSource, STATEFUL_SET_EVENT_SOURCE_NAME, - statefulSetEventSource); + return List.of(serviceEventSource, statefulSetEventSource); } public enum RECONCILE_STATUS { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/complexdependent/dependent/BaseDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/complexdependent/dependent/BaseDependentResource.java index 08e7e5fe2e..eee439cbfe 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/complexdependent/dependent/BaseDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/complexdependent/dependent/BaseDependentResource.java @@ -16,6 +16,11 @@ public BaseDependentResource(Class resourceType, String component) { this.component = component; } + @Override + protected Class getPrimaryResourceType() { + return ComplexDependentCustomResource.class; + } + protected String name(ComplexDependentCustomResource primary) { return String.format("%s-%s", component, primary.getSpec().getProjectId()); } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/complexdependent/dependent/FirstService.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/complexdependent/dependent/FirstService.java index b6b0513254..f568ce08e5 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/complexdependent/dependent/FirstService.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/complexdependent/dependent/FirstService.java @@ -1,9 +1,8 @@ package io.javaoperatorsdk.operator.sample.complexdependent.dependent; -import io.fabric8.kubernetes.api.model.Service; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; -@KubernetesDependent(resourceDiscriminator = FirstService.Discriminator.class) +@KubernetesDependent public class FirstService extends BaseService { public static final String DISCRIMINATOR_PREFIX = "first"; @@ -11,10 +10,4 @@ public FirstService() { super(DISCRIMINATOR_PREFIX); } - public static class Discriminator extends NamePrefixResourceDiscriminator { - protected Discriminator() { - super(DISCRIMINATOR_PREFIX); - } - } - } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/complexdependent/dependent/FirstStatefulSet.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/complexdependent/dependent/FirstStatefulSet.java index f50b94fe5f..d5740616b2 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/complexdependent/dependent/FirstStatefulSet.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/complexdependent/dependent/FirstStatefulSet.java @@ -1,9 +1,8 @@ package io.javaoperatorsdk.operator.sample.complexdependent.dependent; -import io.fabric8.kubernetes.api.model.apps.StatefulSet; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; -@KubernetesDependent(resourceDiscriminator = FirstStatefulSet.Discriminator.class) +@KubernetesDependent public class FirstStatefulSet extends BaseStatefulSet { public static final String DISCRIMINATOR_PREFIX = "first"; @@ -12,11 +11,4 @@ public FirstStatefulSet() { super(DISCRIMINATOR_PREFIX); } - - public static class Discriminator extends NamePrefixResourceDiscriminator { - protected Discriminator() { - super(DISCRIMINATOR_PREFIX); - } - } - } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/complexdependent/dependent/NamePrefixResourceDiscriminator.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/complexdependent/dependent/NamePrefixResourceDiscriminator.java deleted file mode 100644 index eef8566c78..0000000000 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/complexdependent/dependent/NamePrefixResourceDiscriminator.java +++ /dev/null @@ -1,31 +0,0 @@ -package io.javaoperatorsdk.operator.sample.complexdependent.dependent; - -import java.util.Optional; -import java.util.stream.Collectors; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; -import io.javaoperatorsdk.operator.sample.complexdependent.ComplexDependentCustomResource; - -public abstract class NamePrefixResourceDiscriminator - implements ResourceDiscriminator { - - private final String prefix; - - protected NamePrefixResourceDiscriminator(String prefix) { - this.prefix = prefix; - } - - @Override - public Optional distinguish(Class resource, ComplexDependentCustomResource primary, - Context context) { - var resources = context.getSecondaryResources(resource); - var filtered = resources.stream().filter(r -> r.getMetadata().getName().startsWith(prefix)) - .collect(Collectors.toList()); - if (filtered.size() > 1) { - throw new IllegalStateException("More resources than expected for" + primary); - } - return filtered.isEmpty() ? Optional.empty() : Optional.of(filtered.get(0)); - } -} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/complexdependent/dependent/SecondService.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/complexdependent/dependent/SecondService.java index c939d1c2e6..ee6f5210d0 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/complexdependent/dependent/SecondService.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/complexdependent/dependent/SecondService.java @@ -1,9 +1,8 @@ package io.javaoperatorsdk.operator.sample.complexdependent.dependent; -import io.fabric8.kubernetes.api.model.Service; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; -@KubernetesDependent(resourceDiscriminator = SecondService.Discriminator.class) +@KubernetesDependent() public class SecondService extends BaseService { public static final String DISCRIMINATOR_PREFIX = "second"; @@ -11,10 +10,4 @@ public class SecondService extends BaseService { public SecondService() { super(DISCRIMINATOR_PREFIX); } - - public static class Discriminator extends NamePrefixResourceDiscriminator { - protected Discriminator() { - super(DISCRIMINATOR_PREFIX); - } - } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/complexdependent/dependent/SecondStatefulSet.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/complexdependent/dependent/SecondStatefulSet.java index 7a07682c57..3786d90c00 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/complexdependent/dependent/SecondStatefulSet.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/complexdependent/dependent/SecondStatefulSet.java @@ -1,9 +1,8 @@ package io.javaoperatorsdk.operator.sample.complexdependent.dependent; -import io.fabric8.kubernetes.api.model.apps.StatefulSet; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; -@KubernetesDependent(resourceDiscriminator = SecondStatefulSet.Discriminator.class) +@KubernetesDependent public class SecondStatefulSet extends BaseStatefulSet { public static final String DISCRIMINATOR_PREFIX = "second"; @@ -12,9 +11,4 @@ public SecondStatefulSet() { super(DISCRIMINATOR_PREFIX); } - public static class Discriminator extends NamePrefixResourceDiscriminator { - protected Discriminator() { - super(DISCRIMINATOR_PREFIX); - } - } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/createonlyifnotexistsdependentwithssa/ConfigMapDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/createonlyifnotexistsdependentwithssa/ConfigMapDependentResource.java index d6947c2834..ac1310998e 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/createonlyifnotexistsdependentwithssa/ConfigMapDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/createonlyifnotexistsdependentwithssa/ConfigMapDependentResource.java @@ -26,5 +26,3 @@ protected ConfigMap desired(CreateOnlyIfNotExistingDependentWithSSACustomResourc return configMap; } } - - diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/createonlyifnotexistsdependentwithssa/CreateOnlyIfNotExistingDependentWithSSAReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/createonlyifnotexistsdependentwithssa/CreateOnlyIfNotExistingDependentWithSSAReconciler.java index 884b5a859d..49091783f8 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/createonlyifnotexistsdependentwithssa/CreateOnlyIfNotExistingDependentWithSSAReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/createonlyifnotexistsdependentwithssa/CreateOnlyIfNotExistingDependentWithSSAReconciler.java @@ -2,14 +2,12 @@ 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.*; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; -@ControllerConfiguration(dependents = { +@Workflow(dependents = { @Dependent(type = ConfigMapDependentResource.class)}) +@ControllerConfiguration() public class CreateOnlyIfNotExistingDependentWithSSAReconciler implements Reconciler { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/createupdateeventfilter/CreateUpdateEventFilterTestCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/createupdateeventfilter/CreateUpdateEventFilterTestCustomResource.java index 5797a44d9d..8a0ee77474 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/createupdateeventfilter/CreateUpdateEventFilterTestCustomResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/createupdateeventfilter/CreateUpdateEventFilterTestCustomResource.java @@ -11,12 +11,7 @@ @ShortNames("cue") public class CreateUpdateEventFilterTestCustomResource extends - CustomResource + CustomResource implements Namespaced { - @Override - protected CreateUpdateEventFilterTestCustomResourceStatus initStatus() { - return new CreateUpdateEventFilterTestCustomResourceStatus(); - } - } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/createupdateeventfilter/CreateUpdateEventFilterTestCustomResourceStatus.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/createupdateeventfilter/CreateUpdateEventFilterTestCustomResourceStatus.java index 6733e1a7cd..e8f8da78f4 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/createupdateeventfilter/CreateUpdateEventFilterTestCustomResourceStatus.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/createupdateeventfilter/CreateUpdateEventFilterTestCustomResourceStatus.java @@ -1,7 +1,5 @@ package io.javaoperatorsdk.operator.sample.createupdateeventfilter; -import io.javaoperatorsdk.operator.api.ObservedGenerationAwareStatus; - -public class CreateUpdateEventFilterTestCustomResourceStatus extends ObservedGenerationAwareStatus { +public class CreateUpdateEventFilterTestCustomResourceStatus { } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/createupdateeventfilter/CreateUpdateEventFilterTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/createupdateeventfilter/CreateUpdateEventFilterTestReconciler.java index ab0369d998..8567f49916 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/createupdateeventfilter/CreateUpdateEventFilterTestReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/createupdateeventfilter/CreateUpdateEventFilterTestReconciler.java @@ -1,22 +1,25 @@ package io.javaoperatorsdk.operator.sample.createupdateeventfilter; import java.util.HashMap; -import java.util.Map; +import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; -import io.javaoperatorsdk.operator.api.reconciler.*; +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.dependent.kubernetes.CRUDKubernetesDependentResource; import io.javaoperatorsdk.operator.processing.event.source.EventSource; import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; @ControllerConfiguration public class CreateUpdateEventFilterTestReconciler - implements Reconciler, - EventSourceInitializer { + implements Reconciler { private static final class DirectConfigMapDependentResource extends @@ -43,8 +46,7 @@ public void setEventSource( public static final String CONFIG_MAP_TEST_DATA_KEY = "key"; private final AtomicInteger numberOfExecutions = new AtomicInteger(0); - private InformerEventSource informerEventSource; - private DirectConfigMapDependentResource configMapDR = + private final DirectConfigMapDependentResource configMapDR = new DirectConfigMapDependentResource(ConfigMap.class); @Override @@ -88,16 +90,18 @@ private ConfigMap createConfigMap(CreateUpdateEventFilterTestCustomResource reso } @Override - public Map prepareEventSources( + public List prepareEventSources( EventSourceContext context) { InformerConfiguration informerConfiguration = - InformerConfiguration.from(ConfigMap.class) + InformerConfiguration.from(ConfigMap.class, context) .withLabelSelector("integrationtest = " + this.getClass().getSimpleName()) .build(); - informerEventSource = new InformerEventSource<>(informerConfiguration, context.getClient()); + final var informerEventSource = + new InformerEventSource( + informerConfiguration, context.getClient()); this.configMapDR.setEventSource(informerEventSource); - return EventSourceInitializer.nameEventSources(informerEventSource); + return List.of(informerEventSource); } public int getNumberOfExecutions() { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/customfilter/CustomFilteringTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/customfilter/CustomFilteringTestReconciler.java deleted file mode 100644 index 1e42e8e6e1..0000000000 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/customfilter/CustomFilteringTestReconciler.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.javaoperatorsdk.operator.sample.customfilter; - -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; - -@ControllerConfiguration(eventFilters = {CustomFlagFilter.class, CustomFlagFilter2.class}) -public class CustomFilteringTestReconciler implements Reconciler { - - private final AtomicInteger numberOfExecutions = new AtomicInteger(0); - - @Override - public UpdateControl reconcile(CustomFilteringTestResource resource, - Context context) { - numberOfExecutions.incrementAndGet(); - return UpdateControl.noUpdate(); - } - - public int getNumberOfExecutions() { - return numberOfExecutions.get(); - } -} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/customfilter/CustomFilteringTestResourceSpec.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/customfilter/CustomFilteringTestResourceSpec.java deleted file mode 100644 index 8bb1f48054..0000000000 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/customfilter/CustomFilteringTestResourceSpec.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.javaoperatorsdk.operator.sample.customfilter; - -public class CustomFilteringTestResourceSpec { - - private boolean filter1; - - private boolean filter2; - - public boolean isFilter1() { - return filter1; - } - - public CustomFilteringTestResourceSpec setFilter1(boolean filter1) { - this.filter1 = filter1; - return this; - } - - public boolean isFilter2() { - return filter2; - } - - public CustomFilteringTestResourceSpec setFilter2(boolean filter2) { - this.filter2 = filter2; - return this; - } -} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/customfilter/CustomFlagFilter.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/customfilter/CustomFlagFilter.java deleted file mode 100644 index bba45a44ac..0000000000 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/customfilter/CustomFlagFilter.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.javaoperatorsdk.operator.sample.customfilter; - -import io.javaoperatorsdk.operator.processing.Controller; -import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter; - -public class CustomFlagFilter implements ResourceEventFilter { - - @Override - public boolean acceptChange(Controller configuration, - CustomFilteringTestResource oldResource, CustomFilteringTestResource newResource) { - return newResource.getSpec().isFilter1(); - } -} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/customfilter/CustomFlagFilter2.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/customfilter/CustomFlagFilter2.java deleted file mode 100644 index ae6b5d684f..0000000000 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/customfilter/CustomFlagFilter2.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.javaoperatorsdk.operator.sample.customfilter; - -import io.javaoperatorsdk.operator.processing.Controller; -import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter; - -public class CustomFlagFilter2 implements ResourceEventFilter { - - @Override - public boolean acceptChange(Controller configuration, - CustomFilteringTestResource oldResource, CustomFilteringTestResource newResource) { - return newResource.getSpec().isFilter2(); - } -} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentannotationsecondarymapper/DependentAnnotationSecondaryMapperReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentannotationsecondarymapper/DependentAnnotationSecondaryMapperReconciler.java index ec4a2c86b9..b8a3168a56 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentannotationsecondarymapper/DependentAnnotationSecondaryMapperReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentannotationsecondarymapper/DependentAnnotationSecondaryMapperReconciler.java @@ -13,8 +13,9 @@ import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource; import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider; -@ControllerConfiguration(dependents = @Dependent( +@Workflow(dependents = @Dependent( type = DependentAnnotationSecondaryMapperReconciler.ConfigMapDependentResource.class)) +@ControllerConfiguration public class DependentAnnotationSecondaryMapperReconciler implements Reconciler, TestExecutionInfoProvider { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentcustommappingannotation/CustomMappingConfigMapDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentcustommappingannotation/CustomMappingConfigMapDependentResource.java index 123d360068..e17fdb8099 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentcustommappingannotation/CustomMappingConfigMapDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentcustommappingannotation/CustomMappingConfigMapDependentResource.java @@ -20,7 +20,7 @@ public class CustomMappingConfigMapDependentResource public static final String CUSTOM_NAMESPACE_KEY = "customNamespaceKey"; public static final String KEY = "key"; - private SecondaryToPrimaryMapper mapper = + private final SecondaryToPrimaryMapper mapper = Mappers.fromAnnotation(CUSTOM_NAME_KEY, CUSTOM_NAMESPACE_KEY); public CustomMappingConfigMapDependentResource() { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentcustommappingannotation/DependentCustomMappingReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentcustommappingannotation/DependentCustomMappingReconciler.java index 8c14f829ff..6ac2626111 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentcustommappingannotation/DependentCustomMappingReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentcustommappingannotation/DependentCustomMappingReconciler.java @@ -3,8 +3,8 @@ import io.javaoperatorsdk.operator.api.reconciler.*; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; -@ControllerConfiguration( - dependents = {@Dependent(type = CustomMappingConfigMapDependentResource.class)}) +@Workflow(dependents = {@Dependent(type = CustomMappingConfigMapDependentResource.class)}) +@ControllerConfiguration public class DependentCustomMappingReconciler implements Reconciler { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentdifferentnamespace/DependentDifferentNamespaceReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentdifferentnamespace/DependentDifferentNamespaceReconciler.java index de9ea20f4a..d858c34223 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentdifferentnamespace/DependentDifferentNamespaceReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentdifferentnamespace/DependentDifferentNamespaceReconciler.java @@ -6,10 +6,10 @@ import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider; -@ControllerConfiguration( - dependents = { - @Dependent(type = ConfigMapDependentResource.class), - }) +@Workflow(dependents = { + @Dependent(type = ConfigMapDependentResource.class), +}) +@ControllerConfiguration public class DependentDifferentNamespaceReconciler implements Reconciler, TestExecutionInfoProvider { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentfilter/DependentFilterTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentfilter/DependentFilterTestReconciler.java index 114491d9b9..97ff1b5484 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentfilter/DependentFilterTestReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentfilter/DependentFilterTestReconciler.java @@ -5,8 +5,8 @@ import io.javaoperatorsdk.operator.api.reconciler.*; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; -@ControllerConfiguration(onUpdateFilter = UpdateFilter.class, - dependents = {@Dependent(type = FilteredDependentConfigMap.class)}) +@Workflow(dependents = {@Dependent(type = FilteredDependentConfigMap.class)}) +@ControllerConfiguration(onUpdateFilter = UpdateFilter.class) public class DependentFilterTestReconciler implements Reconciler { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentoperationeventfiltering/DependentOperationEventFilterCustomResourceTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentoperationeventfiltering/DependentOperationEventFilterCustomResourceTestReconciler.java index 4ce74c75eb..d8551c72e6 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentoperationeventfiltering/DependentOperationEventFilterCustomResourceTestReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentoperationeventfiltering/DependentOperationEventFilterCustomResourceTestReconciler.java @@ -6,11 +6,10 @@ import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider; -@ControllerConfiguration( - namespaces = Constants.WATCH_CURRENT_NAMESPACE, - dependents = { - @Dependent(type = ConfigMapDependentResource.class), - }) +@Workflow(dependents = { + @Dependent(type = ConfigMapDependentResource.class) +}) +@ControllerConfiguration(namespaces = Constants.WATCH_CURRENT_NAMESPACE) public class DependentOperationEventFilterCustomResourceTestReconciler implements Reconciler, TestExecutionInfoProvider { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentreinitialization/DependentReInitializationReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentreinitialization/DependentReInitializationReconciler.java index a8e6a48e6b..75d0a31f41 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentreinitialization/DependentReInitializationReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentreinitialization/DependentReInitializationReconciler.java @@ -1,14 +1,13 @@ package io.javaoperatorsdk.operator.sample.dependentreinitialization; -import java.util.Map; +import java.util.List; import io.javaoperatorsdk.operator.api.reconciler.*; import io.javaoperatorsdk.operator.processing.event.source.EventSource; @ControllerConfiguration public class DependentReInitializationReconciler - implements Reconciler, - EventSourceInitializer { + implements Reconciler { private final ConfigMapDependentResource configMapDependentResource; @@ -25,9 +24,9 @@ public UpdateControl reconcile( } @Override - public Map prepareEventSources( + public List prepareEventSources( EventSourceContext context) { - return EventSourceInitializer.nameEventSourcesFromDependentResource(context, + return EventSourceUtils.dependentEventSources(context, configMapDependentResource); } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentresourcecrossref/DependentResourceCrossRefReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentresourcecrossref/DependentResourceCrossRefReconciler.java index bb319741b3..0d6d63024f 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentresourcecrossref/DependentResourceCrossRefReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentresourcecrossref/DependentResourceCrossRefReconciler.java @@ -14,11 +14,12 @@ import static io.javaoperatorsdk.operator.sample.dependentresourcecrossref.DependentResourceCrossRefReconciler.SECRET_NAME; -@ControllerConfiguration(dependents = { +@Workflow(dependents = { @Dependent(name = SECRET_NAME, type = DependentResourceCrossRefReconciler.SecretDependentResource.class), @Dependent(type = DependentResourceCrossRefReconciler.ConfigMapDependentResource.class, dependsOn = SECRET_NAME)}) +@ControllerConfiguration public class DependentResourceCrossRefReconciler implements Reconciler, ErrorStatusHandler { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentssa/DependentSSAReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentssa/DependentSSAReconciler.java index f1c11dea6d..ed138bb0b9 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentssa/DependentSSAReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentssa/DependentSSAReconciler.java @@ -1,22 +1,26 @@ package io.javaoperatorsdk.operator.sample.dependentssa; -import java.util.Map; +import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import io.fabric8.kubernetes.api.model.ConfigMap; -import io.javaoperatorsdk.operator.api.reconciler.*; +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.EventSourceUtils; +import io.javaoperatorsdk.operator.api.reconciler.Reconciler; +import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfigBuilder; import io.javaoperatorsdk.operator.processing.event.source.EventSource; import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider; @ControllerConfiguration public class DependentSSAReconciler - implements Reconciler, TestExecutionInfoProvider, - EventSourceInitializer { + implements Reconciler, TestExecutionInfoProvider { private final AtomicInteger numberOfExecutions = new AtomicInteger(0); - private SSAConfigMapDependent ssaConfigMapDependent = new SSAConfigMapDependent(); + private final SSAConfigMapDependent ssaConfigMapDependent = new SSAConfigMapDependent(); public DependentSSAReconciler() { this(true); @@ -43,9 +47,9 @@ public int getNumberOfExecutions() { } @Override - public Map prepareEventSources( + public List prepareEventSources( EventSourceContext context) { - return EventSourceInitializer.nameEventSourcesFromDependentResource(context, + return EventSourceUtils.dependentEventSources(context, ssaConfigMapDependent); } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/doubleupdate/DoubleUpdateTestCustomResourceSpec.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/doubleupdate/DoubleUpdateTestCustomResourceSpec.java deleted file mode 100644 index 02212957a9..0000000000 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/doubleupdate/DoubleUpdateTestCustomResourceSpec.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.javaoperatorsdk.operator.sample.doubleupdate; - -public class DoubleUpdateTestCustomResourceSpec { - - private String value; - - public String getValue() { - return value; - } - - public DoubleUpdateTestCustomResourceSpec setValue(String value) { - this.value = value; - return this; - } -} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/doubleupdate/DoubleUpdateTestCustomResourceStatus.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/doubleupdate/DoubleUpdateTestCustomResourceStatus.java deleted file mode 100644 index 3c7b694853..0000000000 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/doubleupdate/DoubleUpdateTestCustomResourceStatus.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.javaoperatorsdk.operator.sample.doubleupdate; - -public class DoubleUpdateTestCustomResourceStatus { - - private State state; - - public State getState() { - return state; - } - - public DoubleUpdateTestCustomResourceStatus setState(State state) { - this.state = state; - return this; - } - - public enum State { - SUCCESS, ERROR - } -} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dynamicgenericeventsourceregistration/DynamicGenericEventSourceRegistrationReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dynamicgenericeventsourceregistration/DynamicGenericEventSourceRegistrationReconciler.java index 53dc3578bb..80153e010a 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dynamicgenericeventsourceregistration/DynamicGenericEventSourceRegistrationReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dynamicgenericeventsourceregistration/DynamicGenericEventSourceRegistrationReconciler.java @@ -28,16 +28,16 @@ public UpdateControl reconc numberOfExecutions.addAndGet(1); - context.eventSourceRetriever().dynamicallyRegisterEventSource(ConfigMap.class.getSimpleName(), + context.eventSourceRetriever().dynamicallyRegisterEventSource( genericInformerFor(ConfigMap.class, context)); - context.eventSourceRetriever().dynamicallyRegisterEventSource(Secret.class.getSimpleName(), + context.eventSourceRetriever().dynamicallyRegisterEventSource( genericInformerFor(Secret.class, context)); context.getClient().resource(secret(primary)).createOr(NonDeletingOperation::update); context.getClient().resource(configMap(primary)).createOr(NonDeletingOperation::update); numberOfEventSources.set(context.eventSourceRetriever() - .getResourceEventSourcesFor(GenericKubernetesResource.class).size()); + .getEventSourcesFor(GenericKubernetesResource.class).size()); return UpdateControl.noUpdate(); } @@ -74,7 +74,7 @@ private InformerEventSource clazz, Context context) { - return new InformerEventSource<>( + return new InformerEventSource<>(clazz.getSimpleName(), InformerConfiguration.from(gvkFor(clazz), context.eventSourceRetriever().eventSourceContextForDynamicRegistration()).build(), context.eventSourceRetriever().eventSourceContextForDynamicRegistration()); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/errorstatushandler/ErrorStatusHandlerTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/errorstatushandler/ErrorStatusHandlerTestReconciler.java index 412a784b7c..4abf982e0f 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/errorstatushandler/ErrorStatusHandlerTestReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/errorstatushandler/ErrorStatusHandlerTestReconciler.java @@ -51,6 +51,6 @@ public ErrorStatusUpdateControl updateErro ensureStatusExists(resource); resource.getStatus().getMessages() .add(ERROR_STATUS_MESSAGE + context.getRetryInfo().orElseThrow().getAttemptCount()); - return ErrorStatusUpdateControl.updateStatus(resource); + return ErrorStatusUpdateControl.patchStatus(resource); } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/externalstate/ExternalStateDependentReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/externalstate/ExternalStateDependentReconciler.java index 8755e7099c..b9c264ba63 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/externalstate/ExternalStateDependentReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/externalstate/ExternalStateDependentReconciler.java @@ -1,6 +1,6 @@ package io.javaoperatorsdk.operator.sample.externalstate; -import java.util.Map; +import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import io.fabric8.kubernetes.api.model.ConfigMap; @@ -11,11 +11,10 @@ import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider; -@ControllerConfiguration( - dependents = @Dependent(type = ExternalWithStateDependentResource.class)) +@Workflow(dependents = @Dependent(type = ExternalWithStateDependentResource.class)) +@ControllerConfiguration public class ExternalStateDependentReconciler implements Reconciler, - EventSourceInitializer, TestExecutionInfoProvider { public static final String ID_KEY = "id"; @@ -35,11 +34,11 @@ public int getNumberOfExecutions() { } @Override - public Map prepareEventSources( + public List prepareEventSources( EventSourceContext context) { var configMapEventSource = new InformerEventSource<>( InformerConfiguration.from(ConfigMap.class, context).build(), context); - return EventSourceInitializer.nameEventSources(configMapEventSource); + return List.of(configMapEventSource); } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/externalstate/ExternalStateReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/externalstate/ExternalStateReconciler.java index 66c53c3971..4f7bb0ce5c 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/externalstate/ExternalStateReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/externalstate/ExternalStateReconciler.java @@ -1,7 +1,9 @@ package io.javaoperatorsdk.operator.sample.externalstate; import java.time.Duration; +import java.util.Arrays; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; @@ -10,20 +12,26 @@ import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; -import io.javaoperatorsdk.operator.api.reconciler.*; +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.Reconciler; +import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; import io.javaoperatorsdk.operator.processing.event.ResourceID; import io.javaoperatorsdk.operator.processing.event.source.EventSource; import io.javaoperatorsdk.operator.processing.event.source.EventSourceStartPriority; import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; +import io.javaoperatorsdk.operator.processing.event.source.polling.PerResourcePollingConfigurationBuilder; import io.javaoperatorsdk.operator.processing.event.source.polling.PerResourcePollingEventSource; import io.javaoperatorsdk.operator.support.ExternalIDGenServiceMock; import io.javaoperatorsdk.operator.support.ExternalResource; import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider; -@ControllerConfiguration() +@ControllerConfiguration public class ExternalStateReconciler implements Reconciler, Cleaner, - EventSourceInitializer, TestExecutionInfoProvider { public static final String ID_KEY = "id"; @@ -99,24 +107,29 @@ public int getNumberOfExecutions() { } @Override - public Map prepareEventSources( + public List prepareEventSources( EventSourceContext context) { configMapEventSource = new InformerEventSource<>( InformerConfiguration.from(ConfigMap.class, context).build(), context); configMapEventSource.setEventSourcePriority(EventSourceStartPriority.RESOURCE_STATE_LOADER); - externalResourceEventSource = new PerResourcePollingEventSource<>(primaryResource -> { - var configMap = configMapEventSource.getSecondaryResource(primaryResource).orElse(null); - if (configMap == null) { - return Collections.emptySet(); - } - var id = configMap.getData().get(ID_KEY); - var externalResource = externalService.read(id); - return externalResource.map(Set::of).orElseGet(Collections::emptySet); - }, context, Duration.ofMillis(300L), ExternalResource.class); - - return EventSourceInitializer.nameEventSources(configMapEventSource, + final PerResourcePollingEventSource.ResourceFetcher fetcher = + (ExternalStateCustomResource primaryResource) -> { + var configMap = + configMapEventSource.getSecondaryResource(primaryResource).orElse(null); + if (configMap == null) { + return Collections.emptySet(); + } + var id = configMap.getData().get(ID_KEY); + var externalResource = externalService.read(id); + return externalResource.map(Set::of).orElseGet(Collections::emptySet); + }; + externalResourceEventSource = + new PerResourcePollingEventSource<>(ExternalResource.class, context, + new PerResourcePollingConfigurationBuilder<>(fetcher, Duration.ofMillis(300L)).build()); + + return Arrays.asList(configMapEventSource, externalResourceEventSource); } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/externalstate/ExternalWithStateDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/externalstate/ExternalWithStateDependentResource.java index c25113b406..b0e0607334 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/externalstate/ExternalWithStateDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/externalstate/ExternalWithStateDependentResource.java @@ -1,5 +1,6 @@ package io.javaoperatorsdk.operator.sample.externalstate; +import java.time.Duration; import java.util.Collections; import java.util.Map; import java.util.Optional; @@ -27,23 +28,33 @@ public class ExternalWithStateDependentResource extends ExternalIDGenServiceMock externalService = ExternalIDGenServiceMock.getInstance(); public ExternalWithStateDependentResource() { - super(ExternalResource.class, 300); + super(ExternalResource.class, Duration.ofMillis(300)); } @Override @SuppressWarnings("unchecked") public Set fetchResources( ExternalStateCustomResource primaryResource) { - Optional configMapOptional = - getExternalStateEventSource().getSecondaryResource(primaryResource); - - return configMapOptional.map(configMap -> { - var id = configMap.getData().get(ID_KEY); + return getResourceID(primaryResource).map(id -> { var externalResource = externalService.read(id); return externalResource.map(Set::of).orElseGet(Collections::emptySet); }).orElseGet(Collections::emptySet); } + @Override + protected Optional selectManagedSecondaryResource( + Set secondaryResources, + ExternalStateCustomResource primary, Context context) { + var id = getResourceID(primary); + return id.flatMap(k -> secondaryResources.stream().filter(e -> e.getId().equals(k)).findAny()); + } + + private Optional getResourceID(ExternalStateCustomResource primaryResource) { + Optional configMapOptional = + getExternalStateEventSource().getSecondaryResource(primaryResource); + return configMapOptional.map(cm -> cm.getData().get(ID_KEY)); + } + @Override protected ExternalResource desired(ExternalStateCustomResource primary, Context context) { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/externalstate/externalstatebulkdependent/BulkDependentResourceExternalWithState.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/externalstate/externalstatebulkdependent/BulkDependentResourceExternalWithState.java index 37684dd39f..48e207616d 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/externalstate/externalstatebulkdependent/BulkDependentResourceExternalWithState.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/externalstate/externalstatebulkdependent/BulkDependentResourceExternalWithState.java @@ -1,5 +1,6 @@ package io.javaoperatorsdk.operator.sample.externalstate.externalstatebulkdependent; +import java.time.Duration; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -10,10 +11,7 @@ import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.processing.dependent.BulkDependentResource; -import io.javaoperatorsdk.operator.processing.dependent.BulkUpdater; -import io.javaoperatorsdk.operator.processing.dependent.DependentResourceWithExplicitState; -import io.javaoperatorsdk.operator.processing.dependent.Matcher; +import io.javaoperatorsdk.operator.processing.dependent.*; import io.javaoperatorsdk.operator.processing.dependent.external.PerResourcePollingDependentResource; import io.javaoperatorsdk.operator.support.ExternalIDGenServiceMock; import io.javaoperatorsdk.operator.support.ExternalResource; @@ -24,14 +22,14 @@ public class BulkDependentResourceExternalWithState extends PerResourcePollingDependentResource implements BulkDependentResource, - DependentResourceWithExplicitState, - BulkUpdater { + CRUDBulkDependentResource, + DependentResourceWithExplicitState { public static final String DELIMITER = "-"; ExternalIDGenServiceMock externalService = ExternalIDGenServiceMock.getInstance(); public BulkDependentResourceExternalWithState() { - super(ExternalResource.class, 300); + super(ExternalResource.class, Duration.ofMillis(300)); } @Override diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/externalstate/externalstatebulkdependent/ExternalStateBulkDependentReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/externalstate/externalstatebulkdependent/ExternalStateBulkDependentReconciler.java index ebc1655c38..0da96c2e32 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/externalstate/externalstatebulkdependent/ExternalStateBulkDependentReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/externalstate/externalstatebulkdependent/ExternalStateBulkDependentReconciler.java @@ -1,26 +1,20 @@ package io.javaoperatorsdk.operator.sample.externalstate.externalstatebulkdependent; -import java.util.Map; +import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import io.fabric8.kubernetes.api.model.ConfigMap; import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; -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.EventSourceInitializer; -import io.javaoperatorsdk.operator.api.reconciler.Reconciler; -import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; +import io.javaoperatorsdk.operator.api.reconciler.*; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; import io.javaoperatorsdk.operator.processing.event.source.EventSource; import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider; -@ControllerConfiguration( - dependents = @Dependent(type = BulkDependentResourceExternalWithState.class)) +@Workflow(dependents = @Dependent(type = BulkDependentResourceExternalWithState.class)) +@ControllerConfiguration public class ExternalStateBulkDependentReconciler implements Reconciler, - EventSourceInitializer, TestExecutionInfoProvider { private final AtomicInteger numberOfExecutions = new AtomicInteger(0); @@ -39,11 +33,11 @@ public int getNumberOfExecutions() { } @Override - public Map prepareEventSources( + public List prepareEventSources( EventSourceContext context) { var configMapEventSource = new InformerEventSource<>( InformerConfiguration.from(ConfigMap.class, context).build(), context); - return EventSourceInitializer.nameEventSources(configMapEventSource); + return List.of(configMapEventSource); } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/filter/FilterTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/filter/FilterTestReconciler.java index ab5c9b7400..e611968766 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/filter/FilterTestReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/filter/FilterTestReconciler.java @@ -1,5 +1,6 @@ package io.javaoperatorsdk.operator.sample.filter; +import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; @@ -12,8 +13,7 @@ @ControllerConfiguration(onUpdateFilter = UpdateFilter.class) public class FilterTestReconciler - implements Reconciler, - EventSourceInitializer { + implements Reconciler { public static final String CONFIG_MAP_FILTER_VALUE = "config_map_skip_this"; public static final String CUSTOM_RESOURCE_FILTER_VALUE = "custom_resource_skip_this"; @@ -49,7 +49,7 @@ public int getNumberOfExecutions() { } @Override - public Map prepareEventSources( + public List prepareEventSources( EventSourceContext context) { InformerEventSource configMapES = @@ -59,6 +59,6 @@ public Map prepareEventSources( .equals(CONFIG_MAP_FILTER_VALUE)) .build(), context); - return EventSourceInitializer.nameEventSources(configMapES); + return List.of(configMapES); } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/generickubernetesresource/generickubernetesdependentresourcemanaged/GenericKubernetesDependentManagedReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/generickubernetesresource/generickubernetesdependentresourcemanaged/GenericKubernetesDependentManagedReconciler.java index 64651ec23e..40d0454eb7 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/generickubernetesresource/generickubernetesdependentresourcemanaged/GenericKubernetesDependentManagedReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/generickubernetesresource/generickubernetesdependentresourcemanaged/GenericKubernetesDependentManagedReconciler.java @@ -1,10 +1,14 @@ package io.javaoperatorsdk.operator.sample.generickubernetesresource.generickubernetesdependentresourcemanaged; -import io.javaoperatorsdk.operator.api.reconciler.*; +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; -@ControllerConfiguration( - dependents = {@Dependent(type = ConfigMapGenericKubernetesDependent.class)}) +@Workflow(dependents = {@Dependent(type = ConfigMapGenericKubernetesDependent.class)}) +@ControllerConfiguration public class GenericKubernetesDependentManagedReconciler implements Reconciler { @@ -13,7 +17,7 @@ public UpdateControl reconcile( GenericKubernetesDependentManagedCustomResource resource, Context context) { - return UpdateControl.noUpdate(); + return UpdateControl.noUpdate(); } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/generickubernetesresource/generickubernetesdependentstandalone/GenericKubernetesDependentStandaloneReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/generickubernetesresource/generickubernetesdependentstandalone/GenericKubernetesDependentStandaloneReconciler.java index 1969ad8f2a..0d1f5b157a 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/generickubernetesresource/generickubernetesdependentstandalone/GenericKubernetesDependentStandaloneReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/generickubernetesresource/generickubernetesdependentstandalone/GenericKubernetesDependentStandaloneReconciler.java @@ -1,14 +1,18 @@ package io.javaoperatorsdk.operator.sample.generickubernetesresource.generickubernetesdependentstandalone; -import java.util.Map; +import java.util.List; import io.javaoperatorsdk.operator.api.reconciler.*; +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; @ControllerConfiguration public class GenericKubernetesDependentStandaloneReconciler - implements Reconciler, - EventSourceInitializer { + implements Reconciler { private final ConfigMapGenericKubernetesDependent dependent = new ConfigMapGenericKubernetesDependent(); @@ -22,12 +26,12 @@ public UpdateControl reconci dependent.reconcile(resource, context); - return UpdateControl.noUpdate(); + return UpdateControl.noUpdate(); } @Override - public Map prepareEventSources( + public List prepareEventSources( EventSourceContext context) { - return EventSourceInitializer.nameEventSources(dependent.eventSource(context).orElseThrow()); + return List.of(dependent.eventSource(context).orElseThrow()); } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/generickubernetesresource/generickubernetesresourcehandling/GenericKubernetesResourceHandlingReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/generickubernetesresource/generickubernetesresourcehandling/GenericKubernetesResourceHandlingReconciler.java index 45be0281f6..f5c66e8d30 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/generickubernetesresource/generickubernetesresourcehandling/GenericKubernetesResourceHandlingReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/generickubernetesresource/generickubernetesresourcehandling/GenericKubernetesResourceHandlingReconciler.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.HashMap; +import java.util.List; import java.util.Map; import io.fabric8.kubernetes.api.model.GenericKubernetesResource; @@ -14,8 +15,7 @@ @ControllerConfiguration public class GenericKubernetesResourceHandlingReconciler - implements Reconciler, - EventSourceInitializer { + implements Reconciler { public static final String VERSION = "v1"; @@ -65,13 +65,13 @@ GenericKubernetesResource desiredConfigMap( @Override - public Map prepareEventSources( + public List prepareEventSources( EventSourceContext context) { var informerEventSource = new InformerEventSource<>(InformerConfiguration.from( new GroupVersionKind("", VERSION, KIND), context).build(), context); - return EventSourceInitializer.nameEventSources(informerEventSource); + return List.of(informerEventSource); } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/gracefulstop/GracefulStopTestCustomResourceStatus.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/gracefulstop/GracefulStopTestCustomResourceStatus.java index f59f5b1163..fa80e79c19 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/gracefulstop/GracefulStopTestCustomResourceStatus.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/gracefulstop/GracefulStopTestCustomResourceStatus.java @@ -1,7 +1,14 @@ package io.javaoperatorsdk.operator.sample.gracefulstop; -import io.javaoperatorsdk.operator.api.ObservedGenerationAwareStatus; +public class GracefulStopTestCustomResourceStatus { -public class GracefulStopTestCustomResourceStatus extends ObservedGenerationAwareStatus { + private long observedGeneration; + public long getObservedGeneration() { + return observedGeneration; + } + + public void setObservedGeneration(long observedGeneration) { + this.observedGeneration = observedGeneration; + } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/gracefulstop/GracefulStopTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/gracefulstop/GracefulStopTestReconciler.java index cf266c0b48..7ff0d9d246 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/gracefulstop/GracefulStopTestReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/gracefulstop/GracefulStopTestReconciler.java @@ -22,6 +22,7 @@ public UpdateControl reconcile( numberOfExecutions.addAndGet(1); resource.setStatus(new GracefulStopTestCustomResourceStatus()); + resource.getStatus().setObservedGeneration(resource.getMetadata().getGeneration()); Thread.sleep(RECONCILER_SLEEP); return UpdateControl.patchStatus(resource); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestDRConfigMap.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestDRConfigMap.java deleted file mode 100644 index 88dc40f55c..0000000000 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestDRConfigMap.java +++ /dev/null @@ -1,38 +0,0 @@ -package io.javaoperatorsdk.operator.sample.indexdiscriminator; - -import java.util.HashMap; -import java.util.Map; - -import io.fabric8.kubernetes.api.model.ConfigMap; -import io.fabric8.kubernetes.api.model.ConfigMapBuilder; -import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDNoGCKubernetesDependentResource; -import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; - -@KubernetesDependent -public class IndexDiscriminatorTestDRConfigMap - extends CRUDNoGCKubernetesDependentResource { - - public static final String DATA_KEY = "key"; - private final String suffix; - - public IndexDiscriminatorTestDRConfigMap(String value) { - super(ConfigMap.class); - this.suffix = value; - } - - @Override - protected ConfigMap desired(IndexDiscriminatorTestCustomResource primary, - Context context) { - Map data = new HashMap<>(); - data.put(DATA_KEY, primary.getSpec().getValue()); - - return new ConfigMapBuilder() - .withNewMetadata() - .withName(primary.getMetadata().getName() + suffix) - .withNamespace(primary.getMetadata().getNamespace()) - .endMetadata() - .withData(data) - .build(); - } -} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestReconciler.java deleted file mode 100644 index 4acda2feee..0000000000 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestReconciler.java +++ /dev/null @@ -1,104 +0,0 @@ -package io.javaoperatorsdk.operator.sample.indexdiscriminator; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; - -import io.fabric8.kubernetes.api.model.ConfigMap; -import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; -import io.javaoperatorsdk.operator.api.reconciler.*; -import io.javaoperatorsdk.operator.processing.event.source.EventSource; -import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; -import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider; - -@ControllerConfiguration -public class IndexDiscriminatorTestReconciler - implements Reconciler, - Cleaner, - TestExecutionInfoProvider, EventSourceInitializer { - - public static final String FIRST_CONFIG_MAP_SUFFIX_1 = "-1"; - public static final String FIRST_CONFIG_MAP_SUFFIX_2 = "-2"; - public static final String CONFIG_MAP_INDEX_1 = "CONFIG_MAP_INDEX1"; - public static final String CONFIG_MAP_INDEX_2 = "CONFIG_MAP_INDEX2"; - - private final AtomicInteger numberOfExecutions = new AtomicInteger(0); - - private final IndexDiscriminatorTestDRConfigMap firstDependentResourceConfigMap; - private final IndexDiscriminatorTestDRConfigMap secondDependentResourceConfigMap; - - public IndexDiscriminatorTestReconciler() { - firstDependentResourceConfigMap = - new IndexDiscriminatorTestDRConfigMap(FIRST_CONFIG_MAP_SUFFIX_1); - secondDependentResourceConfigMap = - new IndexDiscriminatorTestDRConfigMap(FIRST_CONFIG_MAP_SUFFIX_2); - } - - @Override - public UpdateControl reconcile( - IndexDiscriminatorTestCustomResource resource, - Context context) { - numberOfExecutions.getAndIncrement(); - firstDependentResourceConfigMap.reconcile(resource, context); - secondDependentResourceConfigMap.reconcile(resource, context); - return UpdateControl.noUpdate(); - } - - public int getNumberOfExecutions() { - return numberOfExecutions.get(); - } - - @Override - public Map prepareEventSources( - EventSourceContext context) { - - InformerEventSource eventSource = - new InformerEventSource<>(InformerConfiguration.from(ConfigMap.class, context) - .build(), context); - - eventSource.addIndexer(CONFIG_MAP_INDEX_1, cm -> { - if (cm.getMetadata().getName().endsWith(FIRST_CONFIG_MAP_SUFFIX_1)) { - return List.of(configMapKey(cm)); - } else { - return Collections.emptyList(); - } - }); - eventSource.addIndexer(CONFIG_MAP_INDEX_2, cm -> { - if (cm.getMetadata().getName().endsWith(FIRST_CONFIG_MAP_SUFFIX_2)) { - return List.of(configMapKey(cm)); - } else { - return Collections.emptyList(); - } - }); - - firstDependentResourceConfigMap.configureWith(eventSource); - secondDependentResourceConfigMap.configureWith(eventSource); - - firstDependentResourceConfigMap - .setResourceDiscriminator( - new TestIndexDiscriminator(CONFIG_MAP_INDEX_1, FIRST_CONFIG_MAP_SUFFIX_1)); - secondDependentResourceConfigMap - .setResourceDiscriminator( - new TestIndexDiscriminator(CONFIG_MAP_INDEX_2, FIRST_CONFIG_MAP_SUFFIX_2)); - return EventSourceInitializer.nameEventSources(eventSource); - } - - public static String configMapKey(ConfigMap configMap) { - return configMap.getMetadata().getName() + "#" + configMap.getMetadata().getNamespace(); - } - - public static String configMapKeyFromPrimary(IndexDiscriminatorTestCustomResource primary, - String nameSuffix) { - return primary.getMetadata().getName() + nameSuffix + "#" - + primary.getMetadata().getNamespace(); - } - - @Override - public DeleteControl cleanup(IndexDiscriminatorTestCustomResource resource, - Context context) { - firstDependentResourceConfigMap.delete(resource, context); - secondDependentResourceConfigMap.delete(resource, context); - return DeleteControl.defaultDelete(); - } -} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestSpec.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestSpec.java deleted file mode 100644 index fcedd48abe..0000000000 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestSpec.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.javaoperatorsdk.operator.sample.indexdiscriminator; - -public class IndexDiscriminatorTestSpec { - - private String value; - - public String getValue() { - return value; - } - - public IndexDiscriminatorTestSpec setValue(String value) { - this.value = value; - return this; - } -} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestStatus.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestStatus.java deleted file mode 100644 index d31c86e8de..0000000000 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestStatus.java +++ /dev/null @@ -1,5 +0,0 @@ -package io.javaoperatorsdk.operator.sample.indexdiscriminator; - -public class IndexDiscriminatorTestStatus { - -} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/TestIndexDiscriminator.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/TestIndexDiscriminator.java deleted file mode 100644 index a56e44ced8..0000000000 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/TestIndexDiscriminator.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.javaoperatorsdk.operator.sample.indexdiscriminator; - -import io.fabric8.kubernetes.api.model.ConfigMap; -import io.javaoperatorsdk.operator.api.reconciler.IndexDiscriminator; - -import static io.javaoperatorsdk.operator.sample.indexdiscriminator.IndexDiscriminatorTestReconciler.configMapKeyFromPrimary; - -public class TestIndexDiscriminator - extends IndexDiscriminator { - - public TestIndexDiscriminator(String indexName, String nameSuffix) { - super(indexName, p -> configMapKeyFromPrimary(p, nameSuffix)); - } -} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/informereventsource/InformerEventSourceTestCustomReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/informereventsource/InformerEventSourceTestCustomReconciler.java index bf92550542..232426403d 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/informereventsource/InformerEventSourceTestCustomReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/informereventsource/InformerEventSourceTestCustomReconciler.java @@ -1,6 +1,6 @@ package io.javaoperatorsdk.operator.sample.informereventsource; -import java.util.Map; +import java.util.List; import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; @@ -9,12 +9,7 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; -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.EventSourceInitializer; -import io.javaoperatorsdk.operator.api.reconciler.Reconciler; -import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; +import io.javaoperatorsdk.operator.api.reconciler.*; import io.javaoperatorsdk.operator.processing.event.source.EventSource; import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; import io.javaoperatorsdk.operator.processing.event.source.informer.Mappers; @@ -25,8 +20,7 @@ */ @ControllerConfiguration public class InformerEventSourceTestCustomReconciler - implements Reconciler, - EventSourceInitializer { + implements Reconciler { private static final Logger LOGGER = LoggerFactory.getLogger(InformerEventSourceTestCustomReconciler.class); @@ -38,16 +32,15 @@ public class InformerEventSourceTestCustomReconciler private final AtomicInteger numberOfExecutions = new AtomicInteger(0); @Override - public Map prepareEventSources( + public List prepareEventSources( EventSourceContext context) { InformerConfiguration config = - InformerConfiguration.from(ConfigMap.class) + InformerConfiguration.from(ConfigMap.class, context) .withSecondaryToPrimaryMapper(Mappers.fromAnnotation(RELATED_RESOURCE_NAME)) .build(); - return EventSourceInitializer - .nameEventSources(new InformerEventSource<>(config, context)); + return List.of(new InformerEventSource<>(config, context)); } @Override diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/informerrelatedbehavior/InformerRelatedBehaviorTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/informerrelatedbehavior/InformerRelatedBehaviorTestReconciler.java index f71f243c79..e76d980ef0 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/informerrelatedbehavior/InformerRelatedBehaviorTestReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/informerrelatedbehavior/InformerRelatedBehaviorTestReconciler.java @@ -5,20 +5,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.fabric8.kubernetes.client.KubernetesClient; -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.*; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; import io.javaoperatorsdk.operator.processing.event.ResourceID; import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider; +@Workflow(dependents = @Dependent( + name = InformerRelatedBehaviorTestReconciler.CONFIG_MAP_DEPENDENT_RESOURCE, + type = ConfigMapDependentResource.class)) @ControllerConfiguration( - name = InformerRelatedBehaviorTestReconciler.INFORMER_RELATED_BEHAVIOR_TEST_RECONCILER, - dependents = @Dependent( - name = InformerRelatedBehaviorTestReconciler.CONFIG_MAP_DEPENDENT_RESOURCE, - type = ConfigMapDependentResource.class)) + name = InformerRelatedBehaviorTestReconciler.INFORMER_RELATED_BEHAVIOR_TEST_RECONCILER) public class InformerRelatedBehaviorTestReconciler implements Reconciler, TestExecutionInfoProvider { @@ -30,7 +26,6 @@ public class InformerRelatedBehaviorTestReconciler public static final String CONFIG_MAP_DEPENDENT_RESOURCE = "ConfigMapDependentResource"; private final AtomicInteger numberOfExecutions = new AtomicInteger(0); - private KubernetesClient client; @Override public UpdateControl reconcile( diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/kubernetesdependentgarbagecollection/DependentGarbageCollectionTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/kubernetesdependentgarbagecollection/DependentGarbageCollectionTestReconciler.java index fcb2192539..569e2cafb1 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/kubernetesdependentgarbagecollection/DependentGarbageCollectionTestReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/kubernetesdependentgarbagecollection/DependentGarbageCollectionTestReconciler.java @@ -1,5 +1,6 @@ package io.javaoperatorsdk.operator.sample.kubernetesdependentgarbagecollection; +import java.util.List; import java.util.Map; import io.fabric8.kubernetes.api.model.ConfigMap; @@ -16,7 +17,6 @@ @ControllerConfiguration public class DependentGarbageCollectionTestReconciler implements Reconciler, - EventSourceInitializer, ErrorStatusHandler { private KubernetesClient kubernetesClient; @@ -29,10 +29,9 @@ public DependentGarbageCollectionTestReconciler() { } @Override - public Map prepareEventSources( + public List prepareEventSources( EventSourceContext context) { - return EventSourceInitializer.nameEventSourcesFromDependentResource(context, - configMapDependent); + return EventSourceUtils.dependentEventSources(context, configMapDependent); } @Override diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/manageddependentdeletecondition/ManagedDependentDefaultDeleteConditionReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/manageddependentdeletecondition/ManagedDependentDefaultDeleteConditionReconciler.java index 8ef1035e9b..2fa2c6213b 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/manageddependentdeletecondition/ManagedDependentDefaultDeleteConditionReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/manageddependentdeletecondition/ManagedDependentDefaultDeleteConditionReconciler.java @@ -7,11 +7,12 @@ import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; import io.javaoperatorsdk.operator.processing.dependent.workflow.KubernetesResourceDeletedCondition; -@ControllerConfiguration(dependents = { +@Workflow(dependents = { @Dependent(name = "ConfigMap", type = ConfigMapDependent.class), @Dependent(type = SecretDependent.class, dependsOn = "ConfigMap", deletePostcondition = KubernetesResourceDeletedCondition.class) }) +@ControllerConfiguration public class ManagedDependentDefaultDeleteConditionReconciler implements Reconciler { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/manualobservedgeneration/ManualObservedGenerationCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/manualobservedgeneration/ManualObservedGenerationCustomResource.java new file mode 100644 index 0000000000..10b54fe79d --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/manualobservedgeneration/ManualObservedGenerationCustomResource.java @@ -0,0 +1,15 @@ +package io.javaoperatorsdk.operator.sample.manualobservedgeneration; + +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("mog") +public class ManualObservedGenerationCustomResource + extends CustomResource + implements Namespaced { +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/manualobservedgeneration/ManualObservedGenerationReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/manualobservedgeneration/ManualObservedGenerationReconciler.java new file mode 100644 index 0000000000..6e3aa15dce --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/manualobservedgeneration/ManualObservedGenerationReconciler.java @@ -0,0 +1,48 @@ +package io.javaoperatorsdk.operator.sample.manualobservedgeneration; + +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; + +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.operator.api.reconciler.*; + +@ControllerConfiguration +public class ManualObservedGenerationReconciler + implements Reconciler { + + private final AtomicInteger numberOfExecutions = new AtomicInteger(0); + + @Override + public UpdateControl reconcile( + ManualObservedGenerationCustomResource resource, + Context context) { + numberOfExecutions.addAndGet(1); + var resourceForStatusPatch = resourceForStatusPatch(resource); + if (!Objects.equals(resource.getMetadata().getGeneration(), + resourceForStatusPatch.getStatus().getObservedGeneration())) { + resourceForStatusPatch.getStatus() + .setObservedGeneration(resource.getMetadata().getGeneration()); + return UpdateControl.patchStatus(resourceForStatusPatch); + } else { + return UpdateControl.noUpdate(); + } + } + + private ManualObservedGenerationCustomResource resourceForStatusPatch( + ManualObservedGenerationCustomResource original) { + var res = new ManualObservedGenerationCustomResource(); + res.setMetadata(new ObjectMetaBuilder() + .withName(original.getMetadata().getName()) + .withNamespace(original.getMetadata().getNamespace()) + .build()); + res.setStatus(original.getStatus()); + if (res.getStatus() == null) { + res.setStatus(new ManualObservedGenerationStatus()); + } + return res; + } + + public int getNumberOfExecutions() { + return numberOfExecutions.get(); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/manualobservedgeneration/ManualObservedGenerationSpec.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/manualobservedgeneration/ManualObservedGenerationSpec.java new file mode 100644 index 0000000000..35b04685aa --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/manualobservedgeneration/ManualObservedGenerationSpec.java @@ -0,0 +1,14 @@ +package io.javaoperatorsdk.operator.sample.manualobservedgeneration; + +public class ManualObservedGenerationSpec { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/manualobservedgeneration/ManualObservedGenerationStatus.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/manualobservedgeneration/ManualObservedGenerationStatus.java new file mode 100644 index 0000000000..cdb6d56b2e --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/manualobservedgeneration/ManualObservedGenerationStatus.java @@ -0,0 +1,14 @@ +package io.javaoperatorsdk.operator.sample.manualobservedgeneration; + +public class ManualObservedGenerationStatus { + + private long observedGeneration; + + public long getObservedGeneration() { + return observedGeneration; + } + + public void setObservedGeneration(long observedGeneration) { + this.observedGeneration = observedGeneration; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceConfigMap.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceConfigMap.java index 5c2e9974b5..39ed7f9f0b 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceConfigMap.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceConfigMap.java @@ -1,6 +1,5 @@ package io.javaoperatorsdk.operator.sample.multipledependentresource; -import java.util.HashMap; import java.util.Map; import io.fabric8.kubernetes.api.model.ConfigMap; @@ -12,9 +11,9 @@ public class MultipleDependentResourceConfigMap extends CRUDKubernetesDependentResource { public static final String DATA_KEY = "key"; - private final int value; + private final String value; - public MultipleDependentResourceConfigMap(int value) { + public MultipleDependentResourceConfigMap(String value) { super(ConfigMap.class); this.value = value; } @@ -22,15 +21,17 @@ public MultipleDependentResourceConfigMap(int value) { @Override protected ConfigMap desired(MultipleDependentResourceCustomResource primary, Context context) { - Map data = new HashMap<>(); - data.put(DATA_KEY, String.valueOf(value)); return new ConfigMapBuilder() .withNewMetadata() - .withName(primary.getConfigMapName(value)) + .withName(getConfigMapName(value)) .withNamespace(primary.getMetadata().getNamespace()) .endMetadata() - .withData(data) + .withData(Map.of(DATA_KEY, primary.getSpec().getValue())) .build(); } + + public static String getConfigMapName(String id) { + return "configmap" + id; + } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceCustomResource.java index 55f0d60f2d..60ba494e8a 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceCustomResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceCustomResource.java @@ -3,19 +3,13 @@ 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.Kind; import io.fabric8.kubernetes.model.annotation.ShortNames; import io.fabric8.kubernetes.model.annotation.Version; @Group("sample.javaoperatorsdk") @Version("v1") -@Kind("MultipleDependentResourceCustomResource") @ShortNames("mdr") public class MultipleDependentResourceCustomResource - extends CustomResource + extends CustomResource implements Namespaced { - - public String getConfigMapName(int id) { - return "configmap" + id; - } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceReconciler.java index 2dc7f6490f..c61d751079 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceReconciler.java @@ -1,62 +1,39 @@ package io.javaoperatorsdk.operator.sample.multipledependentresource; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.List; import io.fabric8.kubernetes.api.model.ConfigMap; import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; import io.javaoperatorsdk.operator.api.reconciler.*; -import io.javaoperatorsdk.operator.processing.event.ResourceID; import io.javaoperatorsdk.operator.processing.event.source.EventSource; import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; -import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider; @ControllerConfiguration public class MultipleDependentResourceReconciler - implements Reconciler, - TestExecutionInfoProvider, EventSourceInitializer { + implements Reconciler { - public static final int FIRST_CONFIG_MAP_ID = 1; - public static final int SECOND_CONFIG_MAP_ID = 2; - private final AtomicInteger numberOfExecutions = new AtomicInteger(0); + public static final String FIRST_CONFIG_MAP_ID = "1"; + public static final String SECOND_CONFIG_MAP_ID = "2"; private final MultipleDependentResourceConfigMap firstDependentResourceConfigMap; private final MultipleDependentResourceConfigMap secondDependentResourceConfigMap; public MultipleDependentResourceReconciler() { firstDependentResourceConfigMap = new MultipleDependentResourceConfigMap(FIRST_CONFIG_MAP_ID); - secondDependentResourceConfigMap = new MultipleDependentResourceConfigMap(SECOND_CONFIG_MAP_ID); - - firstDependentResourceConfigMap - .setResourceDiscriminator( - new ResourceIDMatcherDiscriminator<>( - p -> new ResourceID(p.getConfigMapName(FIRST_CONFIG_MAP_ID), - p.getMetadata().getNamespace()))); - secondDependentResourceConfigMap - .setResourceDiscriminator( - new ResourceIDMatcherDiscriminator<>( - p -> new ResourceID(p.getConfigMapName(SECOND_CONFIG_MAP_ID), - p.getMetadata().getNamespace()))); } @Override public UpdateControl reconcile( MultipleDependentResourceCustomResource resource, Context context) { - numberOfExecutions.getAndIncrement(); firstDependentResourceConfigMap.reconcile(resource, context); secondDependentResourceConfigMap.reconcile(resource, context); return UpdateControl.noUpdate(); } - - public int getNumberOfExecutions() { - return numberOfExecutions.get(); - } - @Override - public Map prepareEventSources( + public List prepareEventSources( EventSourceContext context) { InformerEventSource eventSource = new InformerEventSource<>(InformerConfiguration.from(ConfigMap.class, context) @@ -64,6 +41,6 @@ public Map prepareEventSources( firstDependentResourceConfigMap.configureWith(eventSource); secondDependentResourceConfigMap.configureWith(eventSource); - return EventSourceInitializer.nameEventSources(eventSource); + return List.of(eventSource); } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceSpec.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceSpec.java new file mode 100644 index 0000000000..def3fa9088 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresource/MultipleDependentResourceSpec.java @@ -0,0 +1,14 @@ +package io.javaoperatorsdk.operator.sample.multipledependentresource; + +public class MultipleDependentResourceSpec { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresourcewithdiscriminator/MultipleDependentResourceConfigMap.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresourcewithdiscriminator/MultipleDependentResourceConfigMap.java new file mode 100644 index 0000000000..661e8e54be --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresourcewithdiscriminator/MultipleDependentResourceConfigMap.java @@ -0,0 +1,37 @@ +package io.javaoperatorsdk.operator.sample.multipledependentresourcewithdiscriminator; + +import java.util.HashMap; +import java.util.Map; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ConfigMapBuilder; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; + +public class MultipleDependentResourceConfigMap + extends + CRUDKubernetesDependentResource { + + public static final String DATA_KEY = "key"; + private final int value; + + public MultipleDependentResourceConfigMap(int value) { + super(ConfigMap.class); + this.value = value; + } + + @Override + protected ConfigMap desired(MultipleDependentResourceCustomResourceWithDiscriminator primary, + Context context) { + Map data = new HashMap<>(); + data.put(DATA_KEY, String.valueOf(value)); + + return new ConfigMapBuilder() + .withNewMetadata() + .withName(primary.getConfigMapName(value)) + .withNamespace(primary.getMetadata().getNamespace()) + .endMetadata() + .withData(data) + .build(); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresourcewithdiscriminator/MultipleDependentResourceCustomResourceWithDiscriminator.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresourcewithdiscriminator/MultipleDependentResourceCustomResourceWithDiscriminator.java new file mode 100644 index 0000000000..7491d5e3ae --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresourcewithdiscriminator/MultipleDependentResourceCustomResourceWithDiscriminator.java @@ -0,0 +1,19 @@ +package io.javaoperatorsdk.operator.sample.multipledependentresourcewithdiscriminator; + +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("mdwd") +public class MultipleDependentResourceCustomResourceWithDiscriminator + extends CustomResource + implements Namespaced { + + public String getConfigMapName(int id) { + return "configmap" + id; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresourcewithdiscriminator/MultipleDependentResourceWithDiscriminatorReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresourcewithdiscriminator/MultipleDependentResourceWithDiscriminatorReconciler.java new file mode 100644 index 0000000000..9e04af6d24 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresourcewithdiscriminator/MultipleDependentResourceWithDiscriminatorReconciler.java @@ -0,0 +1,56 @@ +package io.javaoperatorsdk.operator.sample.multipledependentresourcewithdiscriminator; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; +import io.javaoperatorsdk.operator.api.reconciler.*; +import io.javaoperatorsdk.operator.processing.event.source.EventSource; +import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; +import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider; + +@ControllerConfiguration +public class MultipleDependentResourceWithDiscriminatorReconciler + implements Reconciler, + TestExecutionInfoProvider { + + public static final int FIRST_CONFIG_MAP_ID = 1; + public static final int SECOND_CONFIG_MAP_ID = 2; + private final AtomicInteger numberOfExecutions = new AtomicInteger(0); + + private final MultipleDependentResourceConfigMap firstDependentResourceConfigMap; + private final MultipleDependentResourceConfigMap secondDependentResourceConfigMap; + + public MultipleDependentResourceWithDiscriminatorReconciler() { + firstDependentResourceConfigMap = new MultipleDependentResourceConfigMap(FIRST_CONFIG_MAP_ID); + secondDependentResourceConfigMap = new MultipleDependentResourceConfigMap(SECOND_CONFIG_MAP_ID); + } + + @Override + public UpdateControl reconcile( + MultipleDependentResourceCustomResourceWithDiscriminator resource, + Context context) { + numberOfExecutions.getAndIncrement(); + firstDependentResourceConfigMap.reconcile(resource, context); + secondDependentResourceConfigMap.reconcile(resource, context); + return UpdateControl.noUpdate(); + } + + + public int getNumberOfExecutions() { + return numberOfExecutions.get(); + } + + @Override + public List prepareEventSources( + EventSourceContext context) { + InformerEventSource eventSource = + new InformerEventSource<>(InformerConfiguration.from(ConfigMap.class, context) + .build(), context); + firstDependentResourceConfigMap.configureWith(eventSource); + secondDependentResourceConfigMap.configureWith(eventSource); + + return List.of(eventSource); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresourcewithdiscriminator/MultipleDependentResourceWithDiscriminatorStatus.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresourcewithdiscriminator/MultipleDependentResourceWithDiscriminatorStatus.java new file mode 100644 index 0000000000..84543b8a12 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentresourcewithdiscriminator/MultipleDependentResourceWithDiscriminatorStatus.java @@ -0,0 +1,5 @@ +package io.javaoperatorsdk.operator.sample.multipledependentresourcewithdiscriminator; + +public class MultipleDependentResourceWithDiscriminatorStatus { + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentsametypemultiinformer/ConfigMap1MultiInformerDiscriminator.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentsametypemultiinformer/ConfigMap1MultiInformerDiscriminator.java deleted file mode 100644 index 32cf830bc1..0000000000 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentsametypemultiinformer/ConfigMap1MultiInformerDiscriminator.java +++ /dev/null @@ -1,28 +0,0 @@ -package io.javaoperatorsdk.operator.sample.multipledependentsametypemultiinformer; - -import java.util.Optional; - -import io.fabric8.kubernetes.api.model.ConfigMap; -import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; -import io.javaoperatorsdk.operator.processing.event.ResourceID; -import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; - -import static io.javaoperatorsdk.operator.sample.multiplemanageddependentsametype.MultipleManagedDependentResourceConfigMap1.NAME_SUFFIX; - -public class ConfigMap1MultiInformerDiscriminator - implements - ResourceDiscriminator { - @Override - public Optional distinguish(Class resource, - MultipleManagedDependentResourceMultiInformerCustomResource primary, - Context context) { - InformerEventSource ies = - (InformerEventSource) context - .eventSourceRetriever().getResourceEventSourceFor(ConfigMap.class, - MultipleManagedDependentResourceMultiInformerReconciler.CONFIG_MAP_1_DR); - - return ies.get(new ResourceID(primary.getMetadata().getName() + NAME_SUFFIX, - primary.getMetadata().getNamespace())); - } -} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentsametypemultiinformer/ConfigMap2MultiInformerDiscriminator.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentsametypemultiinformer/ConfigMap2MultiInformerDiscriminator.java deleted file mode 100644 index cc6a0a656e..0000000000 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentsametypemultiinformer/ConfigMap2MultiInformerDiscriminator.java +++ /dev/null @@ -1,28 +0,0 @@ -package io.javaoperatorsdk.operator.sample.multipledependentsametypemultiinformer; - -import java.util.Optional; - -import io.fabric8.kubernetes.api.model.ConfigMap; -import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; -import io.javaoperatorsdk.operator.processing.event.ResourceID; -import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; - -import static io.javaoperatorsdk.operator.sample.multiplemanageddependentsametype.MultipleManagedDependentResourceConfigMap2.NAME_SUFFIX; - -public class ConfigMap2MultiInformerDiscriminator - implements - ResourceDiscriminator { - @Override - public Optional distinguish(Class resource, - MultipleManagedDependentResourceMultiInformerCustomResource primary, - Context context) { - InformerEventSource ies = - (InformerEventSource) context - .eventSourceRetriever().getResourceEventSourceFor(ConfigMap.class, - MultipleManagedDependentResourceMultiInformerReconciler.CONFIG_MAP_2_DR); - - return ies.get(new ResourceID(primary.getMetadata().getName() + NAME_SUFFIX, - primary.getMetadata().getNamespace())); - } -} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentsametypemultiinformer/MultipleManagedDependentResourceMultiInformerConfigMap1.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentsametypemultiinformer/MultipleManagedDependentResourceMultiInformerConfigMap1.java index 2a63b7267e..510ea37075 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentsametypemultiinformer/MultipleManagedDependentResourceMultiInformerConfigMap1.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentsametypemultiinformer/MultipleManagedDependentResourceMultiInformerConfigMap1.java @@ -10,7 +10,7 @@ import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; import io.javaoperatorsdk.operator.sample.multiplemanageddependentsametype.MultipleManagedDependentResourceReconciler; -@KubernetesDependent(resourceDiscriminator = ConfigMap1MultiInformerDiscriminator.class) +@KubernetesDependent public class MultipleManagedDependentResourceMultiInformerConfigMap1 extends CRUDKubernetesDependentResource { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentsametypemultiinformer/MultipleManagedDependentResourceMultiInformerConfigMap2.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentsametypemultiinformer/MultipleManagedDependentResourceMultiInformerConfigMap2.java index 8db20cac14..2d27e162a7 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentsametypemultiinformer/MultipleManagedDependentResourceMultiInformerConfigMap2.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentsametypemultiinformer/MultipleManagedDependentResourceMultiInformerConfigMap2.java @@ -11,7 +11,7 @@ import static io.javaoperatorsdk.operator.sample.multiplemanageddependentsametype.MultipleManagedDependentResourceReconciler.DATA_KEY; -@KubernetesDependent(resourceDiscriminator = ConfigMap2MultiInformerDiscriminator.class) +@KubernetesDependent public class MultipleManagedDependentResourceMultiInformerConfigMap2 extends CRUDKubernetesDependentResource { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentsametypemultiinformer/MultipleManagedDependentResourceMultiInformerReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentsametypemultiinformer/MultipleManagedDependentResourceMultiInformerReconciler.java index 81c2308eb5..29b0db91c9 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentsametypemultiinformer/MultipleManagedDependentResourceMultiInformerReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledependentsametypemultiinformer/MultipleManagedDependentResourceMultiInformerReconciler.java @@ -6,17 +6,17 @@ import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider; -@ControllerConfiguration(dependents = { +@Workflow(dependents = { @Dependent(name = MultipleManagedDependentResourceMultiInformerReconciler.CONFIG_MAP_1_DR, type = MultipleManagedDependentResourceMultiInformerConfigMap1.class), @Dependent(name = MultipleManagedDependentResourceMultiInformerReconciler.CONFIG_MAP_2_DR, type = MultipleManagedDependentResourceMultiInformerConfigMap2.class) }) +@ControllerConfiguration public class MultipleManagedDependentResourceMultiInformerReconciler implements Reconciler, TestExecutionInfoProvider { - public static final String CONFIG_MAP_EVENT_SOURCE = "ConfigMapEventSource"; public static final String DATA_KEY = "key"; public static final String CONFIG_MAP_1_DR = "ConfigMap1"; public static final String CONFIG_MAP_2_DR = "ConfigMap2"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledrsametypenodiscriminator/MultipleManagedDependentNoDiscriminatorConfigMap1.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledrsametypenodiscriminator/MultipleManagedDependentNoDiscriminatorConfigMap1.java new file mode 100644 index 0000000000..6d0bc303df --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledrsametypenodiscriminator/MultipleManagedDependentNoDiscriminatorConfigMap1.java @@ -0,0 +1,38 @@ +package io.javaoperatorsdk.operator.sample.multipledrsametypenodiscriminator; + +import java.util.HashMap; +import java.util.Map; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ConfigMapBuilder; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; + +@KubernetesDependent +public class MultipleManagedDependentNoDiscriminatorConfigMap1 + extends + CRUDKubernetesDependentResource { + + public static final String NAME_SUFFIX = "-1"; + + public MultipleManagedDependentNoDiscriminatorConfigMap1() { + super(ConfigMap.class); + } + + @Override + protected ConfigMap desired(MultipleManagedDependentNoDiscriminatorCustomResource primary, + Context context) { + Map data = new HashMap<>(); + data.put(MultipleManagedDependentSameTypeNoDiscriminatorReconciler.DATA_KEY, + primary.getSpec().getValue()); + + return new ConfigMapBuilder() + .withNewMetadata() + .withName(primary.getMetadata().getName() + NAME_SUFFIX) + .withNamespace(primary.getMetadata().getNamespace()) + .endMetadata() + .withData(data) + .build(); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledrsametypenodiscriminator/MultipleManagedDependentNoDiscriminatorConfigMap2.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledrsametypenodiscriminator/MultipleManagedDependentNoDiscriminatorConfigMap2.java new file mode 100644 index 0000000000..937291d83d --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledrsametypenodiscriminator/MultipleManagedDependentNoDiscriminatorConfigMap2.java @@ -0,0 +1,39 @@ +package io.javaoperatorsdk.operator.sample.multipledrsametypenodiscriminator; + +import java.util.HashMap; +import java.util.Map; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ConfigMapBuilder; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; + +import static io.javaoperatorsdk.operator.sample.multiplemanageddependentsametype.MultipleManagedDependentResourceReconciler.DATA_KEY; + +@KubernetesDependent +public class MultipleManagedDependentNoDiscriminatorConfigMap2 + extends + CRUDKubernetesDependentResource { + + public static final String NAME_SUFFIX = "-2"; + + public MultipleManagedDependentNoDiscriminatorConfigMap2() { + super(ConfigMap.class); + } + + @Override + protected ConfigMap desired(MultipleManagedDependentNoDiscriminatorCustomResource primary, + Context context) { + Map data = new HashMap<>(); + data.put(DATA_KEY, primary.getSpec().getValue()); + + return new ConfigMapBuilder() + .withNewMetadata() + .withName(primary.getMetadata().getName() + NAME_SUFFIX) + .withNamespace(primary.getMetadata().getNamespace()) + .endMetadata() + .withData(data) + .build(); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/observedgeneration/ObservedGenerationTestCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledrsametypenodiscriminator/MultipleManagedDependentNoDiscriminatorCustomResource.java similarity index 53% rename from operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/observedgeneration/ObservedGenerationTestCustomResource.java rename to operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledrsametypenodiscriminator/MultipleManagedDependentNoDiscriminatorCustomResource.java index 8a0ede3a5d..611d96f74e 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/observedgeneration/ObservedGenerationTestCustomResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledrsametypenodiscriminator/MultipleManagedDependentNoDiscriminatorCustomResource.java @@ -1,18 +1,16 @@ -package io.javaoperatorsdk.operator.sample.observedgeneration; +package io.javaoperatorsdk.operator.sample.multipledrsametypenodiscriminator; 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.Kind; import io.fabric8.kubernetes.model.annotation.ShortNames; import io.fabric8.kubernetes.model.annotation.Version; @Group("sample.javaoperatorsdk") @Version("v1") -@Kind("ObservedGenerationTestCustomResource") -@ShortNames("og") -public class ObservedGenerationTestCustomResource - extends CustomResource +@ShortNames("mnd") +public class MultipleManagedDependentNoDiscriminatorCustomResource + extends CustomResource implements Namespaced { } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledrsametypenodiscriminator/MultipleManagedDependentNoDiscriminatorSpec.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledrsametypenodiscriminator/MultipleManagedDependentNoDiscriminatorSpec.java new file mode 100644 index 0000000000..4ccc1d84f4 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledrsametypenodiscriminator/MultipleManagedDependentNoDiscriminatorSpec.java @@ -0,0 +1,15 @@ +package io.javaoperatorsdk.operator.sample.multipledrsametypenodiscriminator; + +public class MultipleManagedDependentNoDiscriminatorSpec { + + private String value; + + public String getValue() { + return value; + } + + public MultipleManagedDependentNoDiscriminatorSpec setValue(String value) { + this.value = value; + return this; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledrsametypenodiscriminator/MultipleManagedDependentSameTypeNoDiscriminatorReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledrsametypenodiscriminator/MultipleManagedDependentSameTypeNoDiscriminatorReconciler.java new file mode 100644 index 0000000000..6377385b6e --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipledrsametypenodiscriminator/MultipleManagedDependentSameTypeNoDiscriminatorReconciler.java @@ -0,0 +1,59 @@ +package io.javaoperatorsdk.operator.sample.multipledrsametypenodiscriminator; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; +import io.javaoperatorsdk.operator.api.reconciler.*; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; +import io.javaoperatorsdk.operator.processing.event.source.EventSource; +import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; +import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider; + +import static io.javaoperatorsdk.operator.sample.multiplemanageddependentsametype.MultipleManagedDependentResourceReconciler.CONFIG_MAP_EVENT_SOURCE; + +@Workflow(dependents = { + @Dependent(type = MultipleManagedDependentNoDiscriminatorConfigMap1.class, + useEventSourceWithName = CONFIG_MAP_EVENT_SOURCE), + @Dependent(type = MultipleManagedDependentNoDiscriminatorConfigMap2.class, + useEventSourceWithName = CONFIG_MAP_EVENT_SOURCE) +}) +@ControllerConfiguration +public class MultipleManagedDependentSameTypeNoDiscriminatorReconciler + implements Reconciler, + TestExecutionInfoProvider { + + public static final String CONFIG_MAP_EVENT_SOURCE = "ConfigMapEventSource"; + public static final String DATA_KEY = "key"; + + private final AtomicInteger numberOfExecutions = new AtomicInteger(0); + + public MultipleManagedDependentSameTypeNoDiscriminatorReconciler() {} + + @Override + public UpdateControl reconcile( + MultipleManagedDependentNoDiscriminatorCustomResource resource, + Context context) { + numberOfExecutions.getAndIncrement(); + + return UpdateControl.noUpdate(); + } + + + public int getNumberOfExecutions() { + return numberOfExecutions.get(); + } + + @Override + public List prepareEventSources( + EventSourceContext context) { + InformerEventSource ies = + new InformerEventSource<>(CONFIG_MAP_EVENT_SOURCE, + InformerConfiguration.from(ConfigMap.class, context) + .build(), + context); + + return List.of(ies); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/ConfigMap1Discriminator.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/ConfigMap1Discriminator.java deleted file mode 100644 index cc20dfa45e..0000000000 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/ConfigMap1Discriminator.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.javaoperatorsdk.operator.sample.multiplemanageddependentsametype; - -import java.util.Optional; - -import io.fabric8.kubernetes.api.model.ConfigMap; -import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; -import io.javaoperatorsdk.operator.processing.event.ResourceID; -import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; - -import static io.javaoperatorsdk.operator.sample.multiplemanageddependentsametype.MultipleManagedDependentResourceConfigMap1.NAME_SUFFIX; - -public class ConfigMap1Discriminator - implements ResourceDiscriminator { - @Override - public Optional distinguish(Class resource, - MultipleManagedDependentResourceCustomResource primary, - Context context) { - InformerEventSource ies = - (InformerEventSource) context - .eventSourceRetriever().getResourceEventSourceFor(ConfigMap.class); - - return ies.get(new ResourceID(primary.getMetadata().getName() + NAME_SUFFIX, - primary.getMetadata().getNamespace())); - } -} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/ConfigMap2Discriminator.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/ConfigMap2Discriminator.java deleted file mode 100644 index 8bda6afcee..0000000000 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/ConfigMap2Discriminator.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.javaoperatorsdk.operator.sample.multiplemanageddependentsametype; - -import java.util.Optional; - -import io.fabric8.kubernetes.api.model.ConfigMap; -import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; -import io.javaoperatorsdk.operator.processing.event.ResourceID; -import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; - -import static io.javaoperatorsdk.operator.sample.multiplemanageddependentsametype.MultipleManagedDependentResourceConfigMap2.NAME_SUFFIX; - -public class ConfigMap2Discriminator - implements ResourceDiscriminator { - @Override - public Optional distinguish(Class resource, - MultipleManagedDependentResourceCustomResource primary, - Context context) { - InformerEventSource ies = - (InformerEventSource) context - .eventSourceRetriever().getResourceEventSourceFor(ConfigMap.class); - - return ies.get(new ResourceID(primary.getMetadata().getName() + NAME_SUFFIX, - primary.getMetadata().getNamespace())); - } -} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceConfigMap1.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceConfigMap1.java index 98f8033076..8fe7cf5330 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceConfigMap1.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceConfigMap1.java @@ -9,7 +9,7 @@ import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; -@KubernetesDependent(resourceDiscriminator = ConfigMap1Discriminator.class) +@KubernetesDependent public class MultipleManagedDependentResourceConfigMap1 extends CRUDKubernetesDependentResource { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceConfigMap2.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceConfigMap2.java index d4cdd4170f..b76f108d6b 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceConfigMap2.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceConfigMap2.java @@ -11,7 +11,7 @@ import static io.javaoperatorsdk.operator.sample.multiplemanageddependentsametype.MultipleManagedDependentResourceReconciler.DATA_KEY; -@KubernetesDependent(resourceDiscriminator = ConfigMap2Discriminator.class) +@KubernetesDependent public class MultipleManagedDependentResourceConfigMap2 extends CRUDKubernetesDependentResource { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceReconciler.java index 2d9b4f3ee9..63c22436d8 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanageddependentsametype/MultipleManagedDependentResourceReconciler.java @@ -1,6 +1,6 @@ package io.javaoperatorsdk.operator.sample.multiplemanageddependentsametype; -import java.util.Map; +import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import io.fabric8.kubernetes.api.model.ConfigMap; @@ -13,16 +13,16 @@ import static io.javaoperatorsdk.operator.sample.multiplemanageddependentsametype.MultipleManagedDependentResourceReconciler.CONFIG_MAP_EVENT_SOURCE; -@ControllerConfiguration(dependents = { +@Workflow(dependents = { @Dependent(type = MultipleManagedDependentResourceConfigMap1.class, useEventSourceWithName = CONFIG_MAP_EVENT_SOURCE), @Dependent(type = MultipleManagedDependentResourceConfigMap2.class, useEventSourceWithName = CONFIG_MAP_EVENT_SOURCE) }) +@ControllerConfiguration public class MultipleManagedDependentResourceReconciler implements Reconciler, - TestExecutionInfoProvider, - EventSourceInitializer { + TestExecutionInfoProvider { public static final String CONFIG_MAP_EVENT_SOURCE = "ConfigMapEventSource"; public static final String DATA_KEY = "key"; @@ -46,12 +46,13 @@ public int getNumberOfExecutions() { } @Override - public Map prepareEventSources( + public List prepareEventSources( EventSourceContext context) { InformerEventSource ies = - new InformerEventSource<>(InformerConfiguration.from(ConfigMap.class, context) - .build(), context); - - return Map.of(CONFIG_MAP_EVENT_SOURCE, ies); + new InformerEventSource<>(CONFIG_MAP_EVENT_SOURCE, + InformerConfiguration.from(ConfigMap.class, context) + .build(), + context); + return List.of(ies); } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/ExternalDependentResource1.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/ExternalDependentResource1.java index cfe67a3796..4dee39e8e6 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/ExternalDependentResource1.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/ExternalDependentResource1.java @@ -4,10 +4,6 @@ public class ExternalDependentResource1 extends AbstractExternalDependentResourc public static final String SUFFIX = "-1"; - public ExternalDependentResource1() { - setResourceDiscriminator(new ExternalResourceDiscriminator(SUFFIX)); - } - @Override protected String resourceIDSuffix() { return SUFFIX; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/ExternalDependentResource2.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/ExternalDependentResource2.java index 29bb237e1a..b37aa65bdf 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/ExternalDependentResource2.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/ExternalDependentResource2.java @@ -4,10 +4,6 @@ public class ExternalDependentResource2 extends AbstractExternalDependentResourc public static final String SUFFIX = "-2"; - public ExternalDependentResource2() { - setResourceDiscriminator(new ExternalResourceDiscriminator(SUFFIX)); - } - @Override protected String resourceIDSuffix() { return SUFFIX; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/ExternalResourceDiscriminator.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/ExternalResourceDiscriminator.java deleted file mode 100644 index 5a394113c1..0000000000 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/ExternalResourceDiscriminator.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.javaoperatorsdk.operator.sample.multiplemanagedexternaldependenttype; - -import java.util.Optional; - -import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; -import io.javaoperatorsdk.operator.support.ExternalResource; - -public class ExternalResourceDiscriminator implements - ResourceDiscriminator { - - private final String suffix; - - public ExternalResourceDiscriminator(String suffix) { - this.suffix = suffix; - } - - @Override - public Optional distinguish(Class resource, - MultipleManagedExternalDependentResourceCustomResource primary, - Context context) { - var resources = context.getSecondaryResources(ExternalResource.class); - return resources.stream().filter(r -> r.getId().endsWith(suffix)).findFirst(); - } -} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/MultipleManagedExternalDependentResourceReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/MultipleManagedExternalDependentResourceReconciler.java index 349409ec73..614f3a0b59 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/MultipleManagedExternalDependentResourceReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplemanagedexternaldependenttype/MultipleManagedExternalDependentResourceReconciler.java @@ -1,34 +1,42 @@ package io.javaoperatorsdk.operator.sample.multiplemanagedexternaldependenttype; +import java.time.Duration; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; -import io.javaoperatorsdk.operator.api.reconciler.*; +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.api.reconciler.Workflow; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; import io.javaoperatorsdk.operator.processing.event.ResourceID; import io.javaoperatorsdk.operator.processing.event.source.EventSource; +import io.javaoperatorsdk.operator.processing.event.source.polling.PollingConfigurationBuilder; import io.javaoperatorsdk.operator.processing.event.source.polling.PollingEventSource; import io.javaoperatorsdk.operator.support.ExternalResource; import io.javaoperatorsdk.operator.support.ExternalServiceMock; import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider; -import static io.javaoperatorsdk.operator.sample.multiplemanagedexternaldependenttype.MultipleManagedExternalDependentResourceReconciler.CONFIG_MAP_EVENT_SOURCE; +import static io.javaoperatorsdk.operator.sample.multiplemanagedexternaldependenttype.MultipleManagedExternalDependentResourceReconciler.EVENT_SOURCE_NAME; -@ControllerConfiguration(dependents = { +@Workflow(dependents = { @Dependent(type = ExternalDependentResource1.class, - useEventSourceWithName = CONFIG_MAP_EVENT_SOURCE), + useEventSourceWithName = EVENT_SOURCE_NAME), @Dependent(type = ExternalDependentResource2.class, - useEventSourceWithName = CONFIG_MAP_EVENT_SOURCE) + useEventSourceWithName = EVENT_SOURCE_NAME) }) +@ControllerConfiguration() public class MultipleManagedExternalDependentResourceReconciler implements Reconciler, - TestExecutionInfoProvider, - EventSourceInitializer { + TestExecutionInfoProvider { - public static final String CONFIG_MAP_EVENT_SOURCE = "ConfigMapEventSource"; + public static final String EVENT_SOURCE_NAME = "ConfigMapEventSource"; protected ExternalServiceMock externalServiceMock = ExternalServiceMock.getInstance(); private final AtomicInteger numberOfExecutions = new AtomicInteger(0); @@ -48,21 +56,26 @@ public int getNumberOfExecutions() { } @Override - public Map prepareEventSources( + public List prepareEventSources( EventSourceContext context) { + final PollingEventSource.GenericResourceFetcher fetcher = () -> { + var lists = externalServiceMock.listResources(); + final Map> res = new HashMap<>(); + lists.forEach(er -> { + var resourceId = er.toResourceID(); + res.computeIfAbsent(resourceId, rid -> new HashSet<>()); + res.get(resourceId).add(er); + }); + return res; + }; + PollingEventSource pollingEventSource = - new PollingEventSource<>(() -> { - var lists = externalServiceMock.listResources(); - Map> res = new HashMap<>(); - lists.forEach(er -> { - var resourceId = er.toResourceID(); - res.computeIfAbsent(resourceId, rid -> new HashSet<>()); - res.get(resourceId).add(er); - }); - return res; - }, 1000L, ExternalResource.class, ExternalResource::getId); + new PollingEventSource<>(EVENT_SOURCE_NAME, ExternalResource.class, + new PollingConfigurationBuilder<>(fetcher, Duration.ofMillis(1000L)) + .withCacheKeyMapper(ExternalResource::getId) + .build()); - return Map.of(CONFIG_MAP_EVENT_SOURCE, pollingEventSource); + return List.of(pollingEventSource); } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplesecondaryeventsource/MultipleSecondaryEventSourceReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplesecondaryeventsource/MultipleSecondaryEventSourceReconciler.java index 8f4ed834aa..88f97a8235 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplesecondaryeventsource/MultipleSecondaryEventSourceReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multiplesecondaryeventsource/MultipleSecondaryEventSourceReconciler.java @@ -1,7 +1,7 @@ package io.javaoperatorsdk.operator.sample.multiplesecondaryeventsource; import java.util.HashMap; -import java.util.Map; +import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; @@ -16,8 +16,7 @@ @ControllerConfiguration public class MultipleSecondaryEventSourceReconciler - implements Reconciler, TestExecutionInfoProvider, - EventSourceInitializer { + implements Reconciler, TestExecutionInfoProvider { private final AtomicInteger numberOfExecutions = new AtomicInteger(0); @@ -62,11 +61,10 @@ public int getNumberOfExecutions() { } @Override - public Map prepareEventSources( + public List prepareEventSources( EventSourceContext context) { - - var config = InformerConfiguration.from(ConfigMap.class) + var config = InformerConfiguration.from(ConfigMap.class, context) .withNamespaces(context.getControllerConfiguration().getNamespaces()) .withLabelSelector("multisecondary") .withSecondaryToPrimaryMapper(s -> { @@ -76,7 +74,7 @@ public Map prepareEventSources( }).build(); InformerEventSource configMapEventSource = new InformerEventSource<>(config, context); - return EventSourceInitializer.nameEventSources(configMapEventSource); + return List.of(configMapEventSource); } ConfigMap configMap(String name, MultipleSecondaryEventSourceCustomResource resource) { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipleupdateondependent/MultipleOwnerDependentConfigMap.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipleupdateondependent/MultipleOwnerDependentConfigMap.java index 698cec88af..a3387477d5 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipleupdateondependent/MultipleOwnerDependentConfigMap.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipleupdateondependent/MultipleOwnerDependentConfigMap.java @@ -49,7 +49,7 @@ public Optional getSecondaryResource(MultipleOwnerDependentCustomReso Context context) { InformerEventSource ies = (InformerEventSource) context - .eventSourceRetriever().getResourceEventSourceFor(ConfigMap.class); + .eventSourceRetriever().getEventSourceFor(ConfigMap.class); return ies.get(new ResourceID(RESOURCE_NAME, primary.getMetadata().getNamespace())); } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipleupdateondependent/MultipleOwnerDependentReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipleupdateondependent/MultipleOwnerDependentReconciler.java index c1f1262414..763f136c8d 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipleupdateondependent/MultipleOwnerDependentReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/multipleupdateondependent/MultipleOwnerDependentReconciler.java @@ -2,16 +2,14 @@ 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.*; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider; -@ControllerConfiguration(dependents = { +@Workflow(dependents = { @Dependent(type = MultipleOwnerDependentConfigMap.class) }) +@ControllerConfiguration() public class MultipleOwnerDependentReconciler implements Reconciler, TestExecutionInfoProvider { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/nextreconciliationimminent/NextReconciliationImminentCustomResource.java similarity index 61% rename from operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestCustomResource.java rename to operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/nextreconciliationimminent/NextReconciliationImminentCustomResource.java index 729b1d80eb..fba4242925 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/indexdiscriminator/IndexDiscriminatorTestCustomResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/nextreconciliationimminent/NextReconciliationImminentCustomResource.java @@ -1,4 +1,4 @@ -package io.javaoperatorsdk.operator.sample.indexdiscriminator; +package io.javaoperatorsdk.operator.sample.nextreconciliationimminent; import io.fabric8.kubernetes.api.model.Namespaced; import io.fabric8.kubernetes.client.CustomResource; @@ -8,9 +8,11 @@ @Group("sample.javaoperatorsdk") @Version("v1") -@ShortNames("idt") -public class IndexDiscriminatorTestCustomResource - extends CustomResource +@ShortNames("nri") +public class NextReconciliationImminentCustomResource + extends CustomResource implements Namespaced { + + } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/nextreconciliationimminent/NextReconciliationImminentReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/nextreconciliationimminent/NextReconciliationImminentReconciler.java new file mode 100644 index 0000000000..be3ad70ee8 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/nextreconciliationimminent/NextReconciliationImminentReconciler.java @@ -0,0 +1,58 @@ +package io.javaoperatorsdk.operator.sample.nextreconciliationimminent; + +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +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; + +@ControllerConfiguration(generationAwareEventProcessing = false) +public class NextReconciliationImminentReconciler + implements Reconciler { + + private static final Logger log = + LoggerFactory.getLogger(NextReconciliationImminentReconciler.class); + + private final SynchronousQueue queue = new SynchronousQueue<>(); + private volatile boolean reconciliationWaiting = false; + + @Override + public UpdateControl reconcile( + NextReconciliationImminentCustomResource resource, + Context context) throws InterruptedException { + log.info("started reconciliation"); + reconciliationWaiting = true; + // wait long enough to get manually allowed + queue.poll(120, TimeUnit.SECONDS); + log.info("Continue after wait"); + reconciliationWaiting = false; + + if (context.isNextReconciliationImminent()) { + return UpdateControl.noUpdate(); + } else { + if (resource.getStatus() == null) { + resource.setStatus(new NextReconciliationImminentStatus()); + } + resource.getStatus().setUpdateNumber(resource.getStatus().getUpdateNumber() + 1); + log.info("Patching status"); + return UpdateControl.patchStatus(resource); + } + } + + public void allowReconciliationToProceed() { + try { + queue.put(true); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + public boolean isReconciliationWaiting() { + return reconciliationWaiting; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/nextreconciliationimminent/NextReconciliationImminentStatus.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/nextreconciliationimminent/NextReconciliationImminentStatus.java new file mode 100644 index 0000000000..ee4528af7a --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/nextreconciliationimminent/NextReconciliationImminentStatus.java @@ -0,0 +1,14 @@ +package io.javaoperatorsdk.operator.sample.nextreconciliationimminent; + +public class NextReconciliationImminentStatus { + + private int updateNumber; + + public int getUpdateNumber() { + return updateNumber; + } + + public void setUpdateNumber(int updateNumber) { + this.updateNumber = updateNumber; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/observedgeneration/ObservedGenerationTestCustomResourceStatus.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/observedgeneration/ObservedGenerationTestCustomResourceStatus.java deleted file mode 100644 index 14071775f3..0000000000 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/observedgeneration/ObservedGenerationTestCustomResourceStatus.java +++ /dev/null @@ -1,7 +0,0 @@ -package io.javaoperatorsdk.operator.sample.observedgeneration; - -import io.javaoperatorsdk.operator.api.ObservedGenerationAwareStatus; - -public class ObservedGenerationTestCustomResourceStatus extends ObservedGenerationAwareStatus { - -} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/observedgeneration/ObservedGenerationTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/observedgeneration/ObservedGenerationTestReconciler.java deleted file mode 100644 index 7e2127ec7c..0000000000 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/observedgeneration/ObservedGenerationTestReconciler.java +++ /dev/null @@ -1,28 +0,0 @@ -package io.javaoperatorsdk.operator.sample.observedgeneration; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -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; - -@ControllerConfiguration -public class ObservedGenerationTestReconciler - implements Reconciler { - - private static final Logger log = LoggerFactory.getLogger(ObservedGenerationTestReconciler.class); - - @Override - public UpdateControl reconcile( - ObservedGenerationTestCustomResource resource, - Context context) { - log.info("Reconcile ObservedGenerationTestCustomResource: {}", - resource.getMetadata().getName()); - if (resource.getStatus() == null) { - resource.setStatus(new ObservedGenerationTestCustomResourceStatus()); - } - return UpdateControl.patchStatus(resource); - } -} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/optionaldependent/OptionalDependent.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/optionaldependent/OptionalDependent.java new file mode 100644 index 0000000000..6ea9f556c7 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/optionaldependent/OptionalDependent.java @@ -0,0 +1,27 @@ +package io.javaoperatorsdk.operator.sample.optionaldependent; + +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; + +@KubernetesDependent +public class OptionalDependent extends + CRUDKubernetesDependentResource { + + public OptionalDependent() { + super(OptionalDependentSecondaryCustomResource.class); + } + + @Override + protected OptionalDependentSecondaryCustomResource desired( + OptionalDependentCustomResource primary, + Context context) { + var res = new OptionalDependentSecondaryCustomResource(); + res.setMetadata(new ObjectMetaBuilder() + .withName(primary.getMetadata().getName()) + .withNamespace(primary.getMetadata().getNamespace()) + .build()); + return res; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/customfilter/CustomFilteringTestResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/optionaldependent/OptionalDependentCustomResource.java similarity index 65% rename from operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/customfilter/CustomFilteringTestResource.java rename to operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/optionaldependent/OptionalDependentCustomResource.java index dec7b6c40a..f2099ba208 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/customfilter/CustomFilteringTestResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/optionaldependent/OptionalDependentCustomResource.java @@ -1,4 +1,4 @@ -package io.javaoperatorsdk.operator.sample.customfilter; +package io.javaoperatorsdk.operator.sample.optionaldependent; import io.fabric8.kubernetes.api.model.Namespaced; import io.fabric8.kubernetes.client.CustomResource; @@ -8,8 +8,8 @@ @Group("sample.javaoperatorsdk") @Version("v1") -@ShortNames("cft") -public class CustomFilteringTestResource - extends CustomResource +@ShortNames("od") +public class OptionalDependentCustomResource + extends CustomResource implements Namespaced { } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/optionaldependent/OptionalDependentReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/optionaldependent/OptionalDependentReconciler.java new file mode 100644 index 0000000000..289a56d420 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/optionaldependent/OptionalDependentReconciler.java @@ -0,0 +1,23 @@ +package io.javaoperatorsdk.operator.sample.optionaldependent; + +import io.javaoperatorsdk.operator.api.reconciler.*; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; + +@Workflow(dependents = { + @Dependent(type = OptionalDependent.class, optional = true), +}) +@ControllerConfiguration( + generationAwareEventProcessing = false, // to easily trigger reconciliation with metadata update + namespaces = Constants.WATCH_CURRENT_NAMESPACE) +public class OptionalDependentReconciler + implements Reconciler { + + @Override + public UpdateControl reconcile( + OptionalDependentCustomResource resource, + Context context) { + return UpdateControl.noUpdate(); + } + + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/optionaldependent/OptionalDependentSecondaryCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/optionaldependent/OptionalDependentSecondaryCustomResource.java new file mode 100644 index 0000000000..0d5db31b89 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/optionaldependent/OptionalDependentSecondaryCustomResource.java @@ -0,0 +1,15 @@ +package io.javaoperatorsdk.operator.sample.optionaldependent; + +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("ods") +public class OptionalDependentSecondaryCustomResource + extends CustomResource + implements Namespaced { +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/orderedmanageddependent/ConfigMapDependentResource1.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/orderedmanageddependent/ConfigMapDependentResource1.java index bf8d60d9c4..14530cf17e 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/orderedmanageddependent/ConfigMapDependentResource1.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/orderedmanageddependent/ConfigMapDependentResource1.java @@ -6,14 +6,11 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.ResourceIDMatcherDiscriminator; import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; -import io.javaoperatorsdk.operator.processing.event.ResourceID; -@KubernetesDependent(labelSelector = "dependent = cm1", - resourceDiscriminator = ConfigMapDependentResource1.CM1ResourceDiscriminator.class) +@KubernetesDependent(labelSelector = "dependent = cm1") public class ConfigMapDependentResource1 extends CRUDKubernetesDependentResource { @@ -45,11 +42,4 @@ protected ConfigMap desired(OrderedManagedDependentCustomResource primary, return configMap; } - public static class CM1ResourceDiscriminator - extends ResourceIDMatcherDiscriminator { - public CM1ResourceDiscriminator() { - super(p -> new ResourceID(p.getMetadata().getName() + "1", p.getMetadata().getNamespace())); - } - } - } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/orderedmanageddependent/ConfigMapDependentResource2.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/orderedmanageddependent/ConfigMapDependentResource2.java index 2b17d615b9..35ae69586e 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/orderedmanageddependent/ConfigMapDependentResource2.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/orderedmanageddependent/ConfigMapDependentResource2.java @@ -6,14 +6,11 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.ResourceIDMatcherDiscriminator; import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; -import io.javaoperatorsdk.operator.processing.event.ResourceID; -@KubernetesDependent(labelSelector = "dependent = cm2", - resourceDiscriminator = ConfigMapDependentResource2.CM2ResourceDiscriminator.class) +@KubernetesDependent(labelSelector = "dependent = cm2") public class ConfigMapDependentResource2 extends CRUDKubernetesDependentResource { @@ -45,11 +42,4 @@ protected ConfigMap desired(OrderedManagedDependentCustomResource primary, return configMap; } - public static class CM2ResourceDiscriminator - extends ResourceIDMatcherDiscriminator { - public CM2ResourceDiscriminator() { - super(p -> new ResourceID(p.getMetadata().getName() + "2", p.getMetadata().getNamespace())); - } - } - } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/orderedmanageddependent/OrderedManagedDependentTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/orderedmanageddependent/OrderedManagedDependentTestReconciler.java index f7172ca44d..5f8595a131 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/orderedmanageddependent/OrderedManagedDependentTestReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/orderedmanageddependent/OrderedManagedDependentTestReconciler.java @@ -5,20 +5,16 @@ import java.util.List; import java.util.concurrent.atomic.AtomicInteger; -import io.javaoperatorsdk.operator.api.reconciler.Constants; -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.*; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider; +@Workflow(dependents = { + @Dependent(type = ConfigMapDependentResource1.class, name = "cm1"), + @Dependent(type = ConfigMapDependentResource2.class, dependsOn = "cm1") +}) @ControllerConfiguration( - namespaces = Constants.WATCH_CURRENT_NAMESPACE, - dependents = { - @Dependent(type = ConfigMapDependentResource1.class, name = "cm1"), - @Dependent(type = ConfigMapDependentResource2.class, dependsOn = "cm1") - }) + namespaces = Constants.WATCH_CURRENT_NAMESPACE) public class OrderedManagedDependentTestReconciler implements Reconciler, TestExecutionInfoProvider { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/doubleupdate/DoubleUpdateTestCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourceandstatusnossa/PatchResourceAndStatusNoSSACustomResource.java similarity index 66% rename from operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/doubleupdate/DoubleUpdateTestCustomResource.java rename to operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourceandstatusnossa/PatchResourceAndStatusNoSSACustomResource.java index 11c543e388..d5273d4e1d 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/doubleupdate/DoubleUpdateTestCustomResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourceandstatusnossa/PatchResourceAndStatusNoSSACustomResource.java @@ -1,4 +1,4 @@ -package io.javaoperatorsdk.operator.sample.doubleupdate; +package io.javaoperatorsdk.operator.sample.patchresourceandstatusnossa; import io.fabric8.kubernetes.api.model.Namespaced; import io.fabric8.kubernetes.client.CustomResource; @@ -11,7 +11,7 @@ @Version("v1") @Kind("DoubleUpdateSample") @ShortNames("du") -public class DoubleUpdateTestCustomResource - extends CustomResource +public class PatchResourceAndStatusNoSSACustomResource + extends CustomResource implements Namespaced { } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/doubleupdate/DoubleUpdateTestCustomReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourceandstatusnossa/PatchResourceAndStatusNoSSAReconciler.java similarity index 58% rename from operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/doubleupdate/DoubleUpdateTestCustomReconciler.java rename to operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourceandstatusnossa/PatchResourceAndStatusNoSSAReconciler.java index 11f0a54f3e..ecbceb8f65 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/doubleupdate/DoubleUpdateTestCustomReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourceandstatusnossa/PatchResourceAndStatusNoSSAReconciler.java @@ -1,4 +1,4 @@ -package io.javaoperatorsdk.operator.sample.doubleupdate; +package io.javaoperatorsdk.operator.sample.patchresourceandstatusnossa; import java.util.HashMap; import java.util.concurrent.atomic.AtomicInteger; @@ -13,18 +13,19 @@ import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider; @ControllerConfiguration -public class DoubleUpdateTestCustomReconciler - implements Reconciler, TestExecutionInfoProvider { +public class PatchResourceAndStatusNoSSAReconciler + implements Reconciler, TestExecutionInfoProvider { private static final Logger log = - LoggerFactory.getLogger(DoubleUpdateTestCustomReconciler.class); + LoggerFactory.getLogger(PatchResourceAndStatusNoSSAReconciler.class); public static final String TEST_ANNOTATION = "TestAnnotation"; public static final String TEST_ANNOTATION_VALUE = "TestAnnotationValue"; private final AtomicInteger numberOfExecutions = new AtomicInteger(0); @Override - public UpdateControl reconcile( - DoubleUpdateTestCustomResource resource, Context context) { + public UpdateControl reconcile( + PatchResourceAndStatusNoSSACustomResource resource, + Context context) { numberOfExecutions.addAndGet(1); log.info("Value: " + resource.getSpec().getValue()); @@ -32,15 +33,15 @@ public UpdateControl reconcile( resource.getMetadata().setAnnotations(new HashMap<>()); resource.getMetadata().getAnnotations().put(TEST_ANNOTATION, TEST_ANNOTATION_VALUE); ensureStatusExists(resource); - resource.getStatus().setState(DoubleUpdateTestCustomResourceStatus.State.SUCCESS); + resource.getStatus().setState(PatchResourceAndStatusNoSSAStatus.State.SUCCESS); - return UpdateControl.updateResourceAndStatus(resource); + return UpdateControl.patchResourceAndStatus(resource); } - private void ensureStatusExists(DoubleUpdateTestCustomResource resource) { - DoubleUpdateTestCustomResourceStatus status = resource.getStatus(); + private void ensureStatusExists(PatchResourceAndStatusNoSSACustomResource resource) { + PatchResourceAndStatusNoSSAStatus status = resource.getStatus(); if (status == null) { - status = new DoubleUpdateTestCustomResourceStatus(); + status = new PatchResourceAndStatusNoSSAStatus(); resource.setStatus(status); } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourceandstatusnossa/PatchResourceAndStatusNoSSASpec.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourceandstatusnossa/PatchResourceAndStatusNoSSASpec.java new file mode 100644 index 0000000000..ebc58bc862 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourceandstatusnossa/PatchResourceAndStatusNoSSASpec.java @@ -0,0 +1,15 @@ +package io.javaoperatorsdk.operator.sample.patchresourceandstatusnossa; + +public class PatchResourceAndStatusNoSSASpec { + + private String value; + + public String getValue() { + return value; + } + + public PatchResourceAndStatusNoSSASpec setValue(String value) { + this.value = value; + return this; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourceandstatusnossa/PatchResourceAndStatusNoSSAStatus.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourceandstatusnossa/PatchResourceAndStatusNoSSAStatus.java new file mode 100644 index 0000000000..f31031cbcc --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourceandstatusnossa/PatchResourceAndStatusNoSSAStatus.java @@ -0,0 +1,19 @@ +package io.javaoperatorsdk.operator.sample.patchresourceandstatusnossa; + +public class PatchResourceAndStatusNoSSAStatus { + + private State state; + + public State getState() { + return state; + } + + public PatchResourceAndStatusNoSSAStatus setState(State state) { + this.state = state; + return this; + } + + public enum State { + SUCCESS, ERROR + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourcewithssa/PatchResourceAndStatusWithSSAReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourcewithssa/PatchResourceAndStatusWithSSAReconciler.java new file mode 100644 index 0000000000..0c9cbf0456 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourcewithssa/PatchResourceAndStatusWithSSAReconciler.java @@ -0,0 +1,37 @@ +package io.javaoperatorsdk.operator.sample.patchresourcewithssa; + +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.operator.api.reconciler.*; + +@ControllerConfiguration +public class PatchResourceAndStatusWithSSAReconciler + implements Reconciler, + Cleaner { + + public static final String ADDED_VALUE = "Added Value"; + + @Override + public UpdateControl reconcile( + PatchResourceWithSSACustomResource resource, + Context context) { + + var res = new PatchResourceWithSSACustomResource(); + res.setMetadata(new ObjectMetaBuilder() + .withName(resource.getMetadata().getName()) + .withNamespace(resource.getMetadata().getNamespace()) + .build()); + + res.setSpec(new PatchResourceWithSSASpec()); + res.getSpec().setControllerManagedValue(ADDED_VALUE); + res.setStatus(new PatchResourceWithSSAStatus()); + res.getStatus().setSuccessfullyReconciled(true); + + return UpdateControl.patchResourceAndStatus(res); + } + + @Override + public DeleteControl cleanup(PatchResourceWithSSACustomResource resource, + Context context) { + return DeleteControl.defaultDelete(); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourcewithssa/PatchResourceWithSSACustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourcewithssa/PatchResourceWithSSACustomResource.java new file mode 100644 index 0000000000..602776f3cb --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourcewithssa/PatchResourceWithSSACustomResource.java @@ -0,0 +1,16 @@ +package io.javaoperatorsdk.operator.sample.patchresourcewithssa; + +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("prs") +public class PatchResourceWithSSACustomResource + extends CustomResource + implements Namespaced { + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourcewithssa/PatchResourceWithSSAReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourcewithssa/PatchResourceWithSSAReconciler.java new file mode 100644 index 0000000000..5e3929a163 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourcewithssa/PatchResourceWithSSAReconciler.java @@ -0,0 +1,41 @@ +package io.javaoperatorsdk.operator.sample.patchresourcewithssa; + +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.operator.api.reconciler.*; + +@ControllerConfiguration +public class PatchResourceWithSSAReconciler + implements Reconciler, + Cleaner { + + public static final String ADDED_VALUE = "Added Value"; + + @Override + public UpdateControl reconcile( + PatchResourceWithSSACustomResource resource, + Context context) { + + var res = new PatchResourceWithSSACustomResource(); + res.setMetadata(new ObjectMetaBuilder() + .withName(resource.getMetadata().getName()) + .withNamespace(resource.getMetadata().getNamespace()) + .build()); + + // first update the spec with missing value, then status in next reconciliation + if (resource.getSpec().getControllerManagedValue() == null) { + res.setSpec(new PatchResourceWithSSASpec()); + res.getSpec().setControllerManagedValue(ADDED_VALUE); + return UpdateControl.patchResource(res); + } else { + res.setStatus(new PatchResourceWithSSAStatus()); + res.getStatus().setSuccessfullyReconciled(true); + return UpdateControl.patchStatus(res); + } + } + + @Override + public DeleteControl cleanup(PatchResourceWithSSACustomResource resource, + Context context) { + return DeleteControl.defaultDelete(); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourcewithssa/PatchResourceWithSSASpec.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourcewithssa/PatchResourceWithSSASpec.java new file mode 100644 index 0000000000..77d4fe0428 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourcewithssa/PatchResourceWithSSASpec.java @@ -0,0 +1,23 @@ +package io.javaoperatorsdk.operator.sample.patchresourcewithssa; + +public class PatchResourceWithSSASpec { + + private String initValue; + private String controllerManagedValue; + + public String getInitValue() { + return initValue; + } + + public void setInitValue(String initValue) { + this.initValue = initValue; + } + + public String getControllerManagedValue() { + return controllerManagedValue; + } + + public void setControllerManagedValue(String controllerManagedValue) { + this.controllerManagedValue = controllerManagedValue; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourcewithssa/PatchResourceWithSSAStatus.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourcewithssa/PatchResourceWithSSAStatus.java new file mode 100644 index 0000000000..982ef83129 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourcewithssa/PatchResourceWithSSAStatus.java @@ -0,0 +1,14 @@ +package io.javaoperatorsdk.operator.sample.patchresourcewithssa; + +public class PatchResourceWithSSAStatus { + + private boolean successfullyReconciled; + + public boolean isSuccessfullyReconciled() { + return successfullyReconciled; + } + + public void setSuccessfullyReconciled(boolean successfullyReconciled) { + this.successfullyReconciled = successfullyReconciled; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/perresourceeventsource/PerResourcePollingEventSourceTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/perresourceeventsource/PerResourcePollingEventSourceTestReconciler.java index 81d8773986..d8dde593ac 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/perresourceeventsource/PerResourcePollingEventSourceTestReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/perresourceeventsource/PerResourcePollingEventSourceTestReconciler.java @@ -1,19 +1,24 @@ package io.javaoperatorsdk.operator.sample.perresourceeventsource; import java.time.Duration; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; -import io.javaoperatorsdk.operator.api.reconciler.*; +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.polling.PerResourcePollingConfigurationBuilder; import io.javaoperatorsdk.operator.processing.event.source.polling.PerResourcePollingEventSource; @ControllerConfiguration public class PerResourcePollingEventSourceTestReconciler - implements Reconciler, - EventSourceInitializer { + implements Reconciler { public static final int POLL_PERIOD = 100; private final Map numberOfExecutions = new ConcurrentHashMap<>(); @@ -29,16 +34,18 @@ public UpdateControl reconcile( } @Override - public Map prepareEventSources( + public List prepareEventSources( EventSourceContext context) { PerResourcePollingEventSource eventSource = - new PerResourcePollingEventSource<>(resource -> { - numberOfFetchExecutions.putIfAbsent(resource.getMetadata().getName(), 0); - numberOfFetchExecutions.compute(resource.getMetadata().getName(), (s, v) -> v + 1); - return Set.of(UUID.randomUUID().toString()); - }, - context, Duration.ofMillis(POLL_PERIOD), String.class); - return EventSourceInitializer.nameEventSources(eventSource); + new PerResourcePollingEventSource<>(String.class, context, + new PerResourcePollingConfigurationBuilder<>( + (PerResourceEventSourceCustomResource resource) -> { + numberOfFetchExecutions.putIfAbsent(resource.getMetadata().getName(), 0); + numberOfFetchExecutions.compute(resource.getMetadata().getName(), + (s, v) -> v + 1); + return Set.of(UUID.randomUUID().toString()); + }, Duration.ofMillis(POLL_PERIOD)).build()); + return List.of(eventSource); } public int getNumberOfExecutions(String name) { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/primaryindexer/AbstractPrimaryIndexerTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/primaryindexer/AbstractPrimaryIndexerTestReconciler.java index 3b8fc43bfb..0fcdc8e39d 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/primaryindexer/AbstractPrimaryIndexerTestReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/primaryindexer/AbstractPrimaryIndexerTestReconciler.java @@ -15,6 +15,8 @@ public class AbstractPrimaryIndexerTestReconciler implements Reconciler { + public static final String CONFIG_MAP_NAME = "common-config-map"; + private final Map numberOfExecutions = new ConcurrentHashMap<>(); protected static final String CONFIG_MAP_RELATION_INDEXER = "cm-indexer"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/primaryindexer/DependentPrimaryIndexerTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/primaryindexer/DependentPrimaryIndexerTestReconciler.java index 89b2a43700..513cc2cb5d 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/primaryindexer/DependentPrimaryIndexerTestReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/primaryindexer/DependentPrimaryIndexerTestReconciler.java @@ -5,9 +5,13 @@ import java.util.stream.Collectors; import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ConfigMapBuilder; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +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.Workflow; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource; import io.javaoperatorsdk.operator.processing.event.ResourceID; @@ -15,8 +19,9 @@ import io.javaoperatorsdk.operator.processing.event.source.SecondaryToPrimaryMapper; import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; -@ControllerConfiguration(dependents = @Dependent( +@Workflow(dependents = @Dependent( type = DependentPrimaryIndexerTestReconciler.ReadOnlyConfigMapDependent.class)) +@ControllerConfiguration public class DependentPrimaryIndexerTestReconciler extends AbstractPrimaryIndexerTestReconciler implements Reconciler { @@ -30,6 +35,17 @@ public ReadOnlyConfigMapDependent() { super(ConfigMap.class); } + @Override + protected ConfigMap desired(PrimaryIndexerTestCustomResource primary, + Context context) { + return new ConfigMapBuilder() + .withMetadata(new ObjectMetaBuilder() + .withName(CONFIG_MAP_NAME) + .withNamespace(primary.getMetadata().getNamespace()) + .build()) + .build(); + } + @Override public Set toPrimaryResourceIDs(ConfigMap resource) { return cache.byIndex(CONFIG_MAP_RELATION_INDEXER, resource.getMetadata().getName()) diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/primaryindexer/PrimaryIndexerTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/primaryindexer/PrimaryIndexerTestReconciler.java index 6890a3c8c4..547c224e55 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/primaryindexer/PrimaryIndexerTestReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/primaryindexer/PrimaryIndexerTestReconciler.java @@ -1,30 +1,28 @@ package io.javaoperatorsdk.operator.sample.primaryindexer; -import java.util.Map; +import java.util.List; import java.util.stream.Collectors; import io.fabric8.kubernetes.api.model.ConfigMap; import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; -import io.javaoperatorsdk.operator.api.reconciler.EventSourceInitializer; import io.javaoperatorsdk.operator.processing.event.ResourceID; import io.javaoperatorsdk.operator.processing.event.source.EventSource; import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; @ControllerConfiguration public class PrimaryIndexerTestReconciler - extends AbstractPrimaryIndexerTestReconciler implements - EventSourceInitializer { + extends AbstractPrimaryIndexerTestReconciler { @Override - public Map prepareEventSources( + public List prepareEventSources( EventSourceContext context) { context.getPrimaryCache().addIndexer(CONFIG_MAP_RELATION_INDEXER, indexer); var informerConfiguration = - InformerConfiguration.from(ConfigMap.class) + InformerConfiguration.from(ConfigMap.class, context) .withSecondaryToPrimaryMapper( (ConfigMap secondaryResource) -> context .getPrimaryCache() @@ -36,7 +34,6 @@ public Map prepareEventSources( .collect(Collectors.toSet())) .build(); - return EventSourceInitializer - .nameEventSources(new InformerEventSource<>(informerConfiguration, context)); + return List.of(new InformerEventSource<>(informerConfiguration, context)); } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/primarytosecondary/JobReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/primarytosecondary/JobReconciler.java index ace158360a..eaeb86cfe2 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/primarytosecondary/JobReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/primarytosecondary/JobReconciler.java @@ -1,7 +1,6 @@ package io.javaoperatorsdk.operator.sample.primarytosecondary; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; @@ -20,7 +19,7 @@ */ @ControllerConfiguration() public class JobReconciler - implements Reconciler, EventSourceInitializer, ErrorStatusHandler { + implements Reconciler, ErrorStatusHandler { private static final String JOB_CLUSTER_INDEX = "job-cluster-index"; @@ -49,7 +48,7 @@ public UpdateControl reconcile( } else { // reading the resource from cache as alternative, works without primary to secondary mapper var informerEventSource = (InformerEventSource) context.eventSourceRetriever() - .getResourceEventSourceFor(Cluster.class); + .getEventSourceFor(Cluster.class); informerEventSource .get(new ResourceID(resource.getSpec().getClusterName(), resource.getMetadata().getNamespace())) @@ -61,7 +60,7 @@ public UpdateControl reconcile( } @Override - public Map prepareEventSources(EventSourceContext context) { + public List prepareEventSources(EventSourceContext context) { context.getPrimaryCache().addIndexer(JOB_CLUSTER_INDEX, (job -> List .of(indexKey(job.getSpec().getClusterName(), job.getMetadata().getNamespace())))); @@ -79,8 +78,7 @@ public Map prepareEventSources(EventSourceContext cont primary.getSpec().getClusterName(), primary.getMetadata().getNamespace()))); } - return EventSourceInitializer - .nameEventSources(new InformerEventSource<>(informerConfiguration.build(), context)); + return List.of(new InformerEventSource<>(informerConfiguration.build(), context)); } private String indexKey(String clusterName, String namespace) { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/primarytosecondaydependent/ConfigMapDependent.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/primarytosecondaydependent/ConfigMapDependent.java index d08bc2131f..364e9088f7 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/primarytosecondaydependent/ConfigMapDependent.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/primarytosecondaydependent/ConfigMapDependent.java @@ -1,12 +1,28 @@ package io.javaoperatorsdk.operator.sample.primarytosecondaydependent; import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ConfigMapBuilder; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource; public class ConfigMapDependent extends KubernetesDependentResource { + public static final String TEST_CONFIG_MAP_NAME = "testconfigmap"; + public ConfigMapDependent() { super(ConfigMap.class); } + + @Override + protected ConfigMap desired(PrimaryToSecondaryDependentCustomResource primary, + Context context) { + return new ConfigMapBuilder() + .withMetadata(new ObjectMetaBuilder() + .withName(TEST_CONFIG_MAP_NAME) + .withNamespace(primary.getMetadata().getNamespace()) + .build()) + .build(); + } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/primarytosecondaydependent/PrimaryToSecondaryDependentReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/primarytosecondaydependent/PrimaryToSecondaryDependentReconciler.java index c51111b206..a6cf2cef12 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/primarytosecondaydependent/PrimaryToSecondaryDependentReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/primarytosecondaydependent/PrimaryToSecondaryDependentReconciler.java @@ -1,7 +1,6 @@ package io.javaoperatorsdk.operator.sample.primarytosecondaydependent; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; @@ -24,14 +23,14 @@ * Note that this is usually just used with read only resources. So it has limited usage, one reason * to use it is to have nice condition on that resource within a workflow. */ -@ControllerConfiguration(dependents = {@Dependent(type = ConfigMapDependent.class, +@Workflow(dependents = {@Dependent(type = ConfigMapDependent.class, name = CONFIG_MAP, reconcilePrecondition = ConfigMapReconcilePrecondition.class, useEventSourceWithName = CONFIG_MAP_EVENT_SOURCE), @Dependent(type = SecretDependent.class, dependsOn = CONFIG_MAP)}) +@ControllerConfiguration() public class PrimaryToSecondaryDependentReconciler - implements Reconciler, TestExecutionInfoProvider, - EventSourceInitializer { + implements Reconciler, TestExecutionInfoProvider { public static final String DATA_KEY = "data"; public static final String CONFIG_MAP = "ConfigMap"; @@ -59,7 +58,7 @@ public int getNumberOfExecutions() { * demand for it. **/ @Override - public Map prepareEventSources( + public List prepareEventSources( EventSourceContext context) { // there is no owner reference in the config map, but we still want to trigger reconciliation if // the config map changes. So first we add an index which custom resource references the config @@ -67,7 +66,7 @@ public Map prepareEventSources( context.getPrimaryCache().addIndexer(CONFIG_MAP_INDEX, (primary -> List .of(indexKey(primary.getSpec().getConfigMapName(), primary.getMetadata().getNamespace())))); - var cmES = new InformerEventSource<>(InformerConfiguration + var es = new InformerEventSource<>(CONFIG_MAP_EVENT_SOURCE, InformerConfiguration .from(ConfigMap.class, context) // if there is a many-to-many relationship (thus no direct owner reference) // PrimaryToSecondaryMapper needs to be added @@ -83,7 +82,7 @@ public Map prepareEventSources( .build(), context); - return Map.of(CONFIG_MAP_EVENT_SOURCE, cmES); + return List.of(es); } private String indexKey(String configMapName, String namespace) { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/ratelimit/RateLimitCustomResourceStatus.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/ratelimit/RateLimitCustomResourceStatus.java index 087408fc16..975728c18c 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/ratelimit/RateLimitCustomResourceStatus.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/ratelimit/RateLimitCustomResourceStatus.java @@ -1,7 +1,5 @@ package io.javaoperatorsdk.operator.sample.ratelimit; -import io.javaoperatorsdk.operator.api.ObservedGenerationAwareStatus; - -public class RateLimitCustomResourceStatus extends ObservedGenerationAwareStatus { +public class RateLimitCustomResourceStatus { } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/restart/RestartTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/restart/RestartTestReconciler.java index decd9b597b..e7daf5b2eb 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/restart/RestartTestReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/restart/RestartTestReconciler.java @@ -2,15 +2,12 @@ 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.*; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider; -@ControllerConfiguration( - dependents = @Dependent(type = ConfigMapDependentResource.class)) +@Workflow(dependents = @Dependent(type = ConfigMapDependentResource.class)) +@ControllerConfiguration public class RestartTestReconciler implements Reconciler, TestExecutionInfoProvider { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/servicestrictmatcher/ServiceStrictMatcherTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/servicestrictmatcher/ServiceStrictMatcherTestReconciler.java index 64e81e7c31..0746de2897 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/servicestrictmatcher/ServiceStrictMatcherTestReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/servicestrictmatcher/ServiceStrictMatcherTestReconciler.java @@ -2,13 +2,11 @@ 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.*; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; -@ControllerConfiguration(dependents = {@Dependent(type = ServiceDependentResource.class)}) +@Workflow(dependents = {@Dependent(type = ServiceDependentResource.class)}) +@ControllerConfiguration public class ServiceStrictMatcherTestReconciler implements Reconciler { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/simple/TestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/simple/TestReconciler.java index fea06bba93..4ff4521d00 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/simple/TestReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/simple/TestReconciler.java @@ -27,20 +27,10 @@ public class TestReconciler private final AtomicInteger numberOfExecutions = new AtomicInteger(0); private final AtomicInteger numberOfCleanupExecutions = new AtomicInteger(0); private volatile boolean updateStatus; - private volatile boolean patchStatus; - public TestReconciler(boolean updateStatus) { - this(updateStatus, false); - } - public TestReconciler(boolean updateStatus, boolean patchStatus) { + public TestReconciler(boolean updateStatus) { this.updateStatus = updateStatus; - this.patchStatus = patchStatus; - } - - public TestReconciler setPatchStatus(boolean patchStatus) { - this.patchStatus = patchStatus; - return this; } public void setUpdateStatus(boolean updateStatus) { @@ -114,16 +104,16 @@ public UpdateControl reconcile( .createOrReplace(); } if (updateStatus) { - if (resource.getStatus() == null) { - resource.setStatus(new TestCustomResourceStatus()); - } + var statusUpdateResource = new TestCustomResource(); + statusUpdateResource.setMetadata(new ObjectMetaBuilder() + .withName(resource.getMetadata().getName()) + .withNamespace(resource.getMetadata().getNamespace()) + .build()); + resource.setStatus(new TestCustomResourceStatus()); resource.getStatus().setConfigMapStatus("ConfigMap Ready"); - } - if (patchStatus) { return UpdateControl.patchStatus(resource); - } else { - return UpdateControl.updateStatus(resource); } + return UpdateControl.noUpdate(); } private Map configMapData(TestCustomResource resource) { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/specialresourcesdependent/SpecialResourceTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/specialresourcesdependent/SpecialResourceTestReconciler.java index 5fa7d778b3..b36b6f31a2 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/specialresourcesdependent/SpecialResourceTestReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/specialresourcesdependent/SpecialResourceTestReconciler.java @@ -6,11 +6,10 @@ import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider; -@ControllerConfiguration( - namespaces = Constants.WATCH_CURRENT_NAMESPACE, - dependents = { - @Dependent(type = ServiceAccountDependentResource.class), - }) +@Workflow(dependents = { + @Dependent(type = ServiceAccountDependentResource.class), +}) +@ControllerConfiguration(namespaces = Constants.WATCH_CURRENT_NAMESPACE) public class SpecialResourceTestReconciler implements Reconciler, TestExecutionInfoProvider { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/ssalegacymatcher/SSALegacyMatcherReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/ssalegacymatcher/SSALegacyMatcherReconciler.java index a513133670..e0cdf50c96 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/ssalegacymatcher/SSALegacyMatcherReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/ssalegacymatcher/SSALegacyMatcherReconciler.java @@ -2,13 +2,11 @@ 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.*; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; -@ControllerConfiguration(dependents = {@Dependent(type = ServiceDependentResource.class)}) +@Workflow(dependents = {@Dependent(type = ServiceDependentResource.class)}) +@ControllerConfiguration public class SSALegacyMatcherReconciler implements Reconciler { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/ssalegacymatcher/ServiceDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/ssalegacymatcher/ServiceDependentResource.java index a1f5f6faf0..e0699093de 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/ssalegacymatcher/ServiceDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/ssalegacymatcher/ServiceDependentResource.java @@ -39,8 +39,10 @@ protected Service desired(SSALegacyMatcherCustomResource primary, @Override public Result match(Service actualResource, SSALegacyMatcherCustomResource primary, Context context) { + var desired = desired(primary, context); + return GenericKubernetesResourceMatcher.match(this, actualResource, primary, context, - true, false, false); + false, false); } // override just to check the exec count diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/standalonedependent/StandaloneDependentTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/standalonedependent/StandaloneDependentTestReconciler.java index 77fcf0b85d..213c699854 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/standalonedependent/StandaloneDependentTestReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/standalonedependent/StandaloneDependentTestReconciler.java @@ -1,20 +1,26 @@ package io.javaoperatorsdk.operator.sample.standalonedependent; -import java.util.Map; +import java.util.List; import java.util.Optional; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.client.KubernetesClientException; import io.javaoperatorsdk.operator.ReconcilerUtils; import io.javaoperatorsdk.operator.StandaloneDependentResourceIT; -import io.javaoperatorsdk.operator.api.reconciler.*; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; +import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusHandler; +import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusUpdateControl; +import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; +import io.javaoperatorsdk.operator.api.reconciler.EventSourceUtils; +import io.javaoperatorsdk.operator.api.reconciler.Reconciler; +import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; import io.javaoperatorsdk.operator.processing.event.source.EventSource; @ControllerConfiguration public class StandaloneDependentTestReconciler implements Reconciler, - EventSourceInitializer, ErrorStatusHandler { private volatile boolean errorOccurred = false; @@ -25,9 +31,9 @@ public StandaloneDependentTestReconciler() { } @Override - public Map prepareEventSources( + public List prepareEventSources( EventSourceContext context) { - return EventSourceInitializer.nameEventSourcesFromDependentResource(context, + return EventSourceUtils.dependentEventSources(context, deploymentDependent); } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/statefulsetdesiredsanitizer/StatefulSetDesiredSanitizerReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/statefulsetdesiredsanitizer/StatefulSetDesiredSanitizerReconciler.java index c884619227..3b30acfc5c 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/statefulsetdesiredsanitizer/StatefulSetDesiredSanitizerReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/statefulsetdesiredsanitizer/StatefulSetDesiredSanitizerReconciler.java @@ -1,13 +1,10 @@ package io.javaoperatorsdk.operator.sample.statefulsetdesiredsanitizer; -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.*; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; -@ControllerConfiguration( - dependents = {@Dependent(type = StatefulSetDesiredSanitizerDependentResource.class)}) +@Workflow(dependents = {@Dependent(type = StatefulSetDesiredSanitizerDependentResource.class)}) +@ControllerConfiguration public class StatefulSetDesiredSanitizerReconciler implements Reconciler { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/statuspatchnonlocking/StatusPatchLockingCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/statuspatchnonlocking/StatusPatchLockingCustomResource.java index 4d77259999..93ec97884d 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/statuspatchnonlocking/StatusPatchLockingCustomResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/statuspatchnonlocking/StatusPatchLockingCustomResource.java @@ -3,14 +3,12 @@ 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.Kind; import io.fabric8.kubernetes.model.annotation.ShortNames; import io.fabric8.kubernetes.model.annotation.Version; @Group("sample.javaoperatorsdk") @Version("v1") -@Kind("StatusUpdateLockingCustomResource") -@ShortNames("sul") +@ShortNames("spl") public class StatusPatchLockingCustomResource extends CustomResource diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/statusupdatelocking/StatusUpdateLockingCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/statusupdatelocking/StatusUpdateLockingCustomResource.java index df832aaed0..0f95ccd824 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/statusupdatelocking/StatusUpdateLockingCustomResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/statusupdatelocking/StatusUpdateLockingCustomResource.java @@ -15,8 +15,4 @@ public class StatusUpdateLockingCustomResource extends CustomResource implements Namespaced { - @Override - protected StatusUpdateLockingCustomResourceStatus initStatus() { - return new StatusUpdateLockingCustomResourceStatus(); - } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/statusupdatelocking/StatusUpdateLockingReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/statusupdatelocking/StatusUpdateLockingReconciler.java index e21897cf38..fc007f5dfa 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/statusupdatelocking/StatusUpdateLockingReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/statusupdatelocking/StatusUpdateLockingReconciler.java @@ -18,8 +18,9 @@ public UpdateControl reconcile( throws InterruptedException { numberOfExecutions.addAndGet(1); Thread.sleep(WAIT_TIME); + resource.setStatus(new StatusUpdateLockingCustomResourceStatus()); resource.getStatus().setValue(resource.getStatus().getValue() + 1); - return UpdateControl.updateStatus(resource); + return UpdateControl.patchStatus(resource); } public int getNumberOfExecutions() { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/subresource/SubResourceTestCustomReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/subresource/SubResourceTestCustomReconciler.java index 1e76681f25..b50b9fc4b5 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/subresource/SubResourceTestCustomReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/subresource/SubResourceTestCustomReconciler.java @@ -33,7 +33,7 @@ public UpdateControl reconcile( ensureStatusExists(resource); resource.getStatus().setState(SubResourceTestCustomResourceStatus.State.SUCCESS); waitXms(RECONCILER_MIN_EXEC_TIME); - return UpdateControl.updateStatus(resource); + return UpdateControl.patchStatus(resource); } private void ensureStatusExists(SubResourceTestCustomResource resource) { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/unmodifiabledependentpart/UnmodifiableDependentPartReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/unmodifiabledependentpart/UnmodifiableDependentPartReconciler.java index fd63a2cb12..9cc4a3e9d6 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/unmodifiabledependentpart/UnmodifiableDependentPartReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/unmodifiabledependentpart/UnmodifiableDependentPartReconciler.java @@ -2,13 +2,11 @@ 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.*; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; -@ControllerConfiguration(dependents = {@Dependent(type = UnmodifiablePartConfigMapDependent.class)}) +@Workflow(dependents = {@Dependent(type = UnmodifiablePartConfigMapDependent.class)}) +@ControllerConfiguration public class UnmodifiableDependentPartReconciler implements Reconciler { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/unmodifiabledependentpart/UnmodifiablePartConfigMapDependent.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/unmodifiabledependentpart/UnmodifiablePartConfigMapDependent.java index a103e53162..d373fe7d26 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/unmodifiabledependentpart/UnmodifiablePartConfigMapDependent.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/unmodifiabledependentpart/UnmodifiablePartConfigMapDependent.java @@ -21,7 +21,7 @@ public UnmodifiablePartConfigMapDependent() { @Override protected ConfigMap desired(UnmodifiableDependentPartCustomResource primary, Context context) { - var actual = getSecondaryResource(primary, context); + var actual = context.getSecondaryResource(ConfigMap.class); ConfigMap res = new ConfigMapBuilder() .withMetadata(new ObjectMetaBuilder() .withName(primary.getMetadata().getName()) diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/updatestatusincleanupandreschedule/UpdateStatusInCleanupAndRescheduleReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/updatestatusincleanupandreschedule/UpdateStatusInCleanupAndRescheduleReconciler.java index 9fcceca851..762948d1c1 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/updatestatusincleanupandreschedule/UpdateStatusInCleanupAndRescheduleReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/updatestatusincleanupandreschedule/UpdateStatusInCleanupAndRescheduleReconciler.java @@ -3,7 +3,12 @@ import java.time.LocalTime; import java.time.temporal.ChronoUnit; -import io.javaoperatorsdk.operator.api.reconciler.*; +import io.javaoperatorsdk.operator.api.reconciler.Cleaner; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; +import io.javaoperatorsdk.operator.api.reconciler.DeleteControl; +import io.javaoperatorsdk.operator.api.reconciler.Reconciler; +import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; @ControllerConfiguration public class UpdateStatusInCleanupAndRescheduleReconciler @@ -38,14 +43,10 @@ public DeleteControl cleanup(UpdateStatusInCleanupAndRescheduleCustomResource re resource.getStatus().setCleanupAttempt(currentAttempt + 1); if (!Boolean.FALSE.equals(rescheduleDelayWorked)) { var diff = ChronoUnit.MILLIS.between(lastCleanupExecution, LocalTime.now()); - if (diff < DELAY) { - rescheduleDelayWorked = false; - } else { - rescheduleDelayWorked = true; - } + rescheduleDelayWorked = diff >= DELAY; } } - var res = context.getClient().resource(resource).updateStatus(); + context.getClient().resource(resource).updateStatus(); if (resource.getStatus().getCleanupAttempt() > 5) { return DeleteControl.defaultDelete(); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowactivationcleanup/WorkflowActivationCleanupReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowactivationcleanup/WorkflowActivationCleanupReconciler.java index 3f2fba15c5..6a30f3d9f4 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowactivationcleanup/WorkflowActivationCleanupReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowactivationcleanup/WorkflowActivationCleanupReconciler.java @@ -3,10 +3,11 @@ import io.javaoperatorsdk.operator.api.reconciler.*; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; -@ControllerConfiguration(dependents = { +@Workflow(dependents = { @Dependent(type = ConfigMapDependentResource.class, activationCondition = TestActivcationCondition.class), }) +@ControllerConfiguration public class WorkflowActivationCleanupReconciler implements Reconciler, Cleaner { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowactivationcondition/WorkflowActivationConditionReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowactivationcondition/WorkflowActivationConditionReconciler.java index 33db3043ba..8669c24cb7 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowactivationcondition/WorkflowActivationConditionReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowactivationcondition/WorkflowActivationConditionReconciler.java @@ -3,11 +3,12 @@ import io.javaoperatorsdk.operator.api.reconciler.*; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; -@ControllerConfiguration(dependents = { +@Workflow(dependents = { @Dependent(type = ConfigMapDependentResource.class), @Dependent(type = RouteDependentResource.class, activationCondition = IsOpenShiftCondition.class) }) +@ControllerConfiguration public class WorkflowActivationConditionReconciler implements Reconciler { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowallfeature/DeploymentDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowallfeature/DeploymentDependentResource.java index 61cf18f57b..30cd555abe 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowallfeature/DeploymentDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowallfeature/DeploymentDependentResource.java @@ -25,5 +25,3 @@ protected Deployment desired(WorkflowAllFeatureCustomResource primary, return deployment; } } - - diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowallfeature/WorkflowAllFeatureReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowallfeature/WorkflowAllFeatureReconciler.java index 2c25d13924..f5c14d6f96 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowallfeature/WorkflowAllFeatureReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowallfeature/WorkflowAllFeatureReconciler.java @@ -7,7 +7,7 @@ import static io.javaoperatorsdk.operator.sample.workflowallfeature.WorkflowAllFeatureReconciler.DEPLOYMENT_NAME; -@ControllerConfiguration(dependents = { +@Workflow(dependents = { @Dependent(name = DEPLOYMENT_NAME, type = DeploymentDependentResource.class, readyPostcondition = DeploymentReadyCondition.class), @Dependent(type = ConfigMapDependentResource.class, @@ -15,6 +15,7 @@ deletePostcondition = ConfigMapDeletePostCondition.class, dependsOn = DEPLOYMENT_NAME) }) +@ControllerConfiguration public class WorkflowAllFeatureReconciler implements Reconciler, Cleaner { @@ -34,8 +35,8 @@ public UpdateControl reconcile( } resource.getStatus() .setReady( - context.managedDependentResourceContext() - .getWorkflowReconcileResult().orElseThrow() + context.managedWorkflowAndDependentResourceContext() + .getWorkflowReconcileResult() .allDependentResourcesReady()); return UpdateControl.patchStatus(resource); } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitcleanup/ConfigMapDependent.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitcleanup/ConfigMapDependent.java new file mode 100644 index 0000000000..91bf73906f --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitcleanup/ConfigMapDependent.java @@ -0,0 +1,29 @@ +package io.javaoperatorsdk.operator.sample.workflowexplicitcleanup; + +import java.util.Map; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ConfigMapBuilder; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDNoGCKubernetesDependentResource; + +public class ConfigMapDependent extends + CRUDNoGCKubernetesDependentResource { + + public ConfigMapDependent() { + super(ConfigMap.class); + } + + @Override + protected ConfigMap desired(WorkflowExplicitCleanupCustomResource primary, + Context context) { + return new ConfigMapBuilder() + .withMetadata(new ObjectMetaBuilder() + .withName(primary.getMetadata().getName()) + .withNamespace(primary.getMetadata().getNamespace()) + .build()) + .withData(Map.of("key", "val")) + .build(); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitcleanup/WorkflowExplicitCleanupCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitcleanup/WorkflowExplicitCleanupCustomResource.java new file mode 100644 index 0000000000..b2057a54dd --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitcleanup/WorkflowExplicitCleanupCustomResource.java @@ -0,0 +1,15 @@ +package io.javaoperatorsdk.operator.sample.workflowexplicitcleanup; + +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("wec") +public class WorkflowExplicitCleanupCustomResource + extends CustomResource + implements Namespaced { +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitcleanup/WorkflowExplicitCleanupReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitcleanup/WorkflowExplicitCleanupReconciler.java new file mode 100644 index 0000000000..128bb9629c --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitcleanup/WorkflowExplicitCleanupReconciler.java @@ -0,0 +1,32 @@ +package io.javaoperatorsdk.operator.sample.workflowexplicitcleanup; + +import io.javaoperatorsdk.operator.api.reconciler.*; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; + +@Workflow(explicitInvocation = true, + dependents = @Dependent(type = ConfigMapDependent.class)) +@ControllerConfiguration +public class WorkflowExplicitCleanupReconciler + implements Reconciler, + Cleaner { + + @Override + public UpdateControl reconcile( + WorkflowExplicitCleanupCustomResource resource, + Context context) { + + context.managedWorkflowAndDependentResourceContext().reconcileManagedWorkflow(); + + return UpdateControl.noUpdate(); + } + + @Override + public DeleteControl cleanup(WorkflowExplicitCleanupCustomResource resource, + Context context) { + + context.managedWorkflowAndDependentResourceContext().cleanupManageWorkflow(); + // this can be checked + // context.managedWorkflowAndDependentResourceContext().getWorkflowCleanupResult() + return DeleteControl.defaultDelete(); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitcleanup/WorkflowExplicitCleanupSpec.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitcleanup/WorkflowExplicitCleanupSpec.java new file mode 100644 index 0000000000..d8da8797f5 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitcleanup/WorkflowExplicitCleanupSpec.java @@ -0,0 +1,15 @@ +package io.javaoperatorsdk.operator.sample.workflowexplicitcleanup; + +public class WorkflowExplicitCleanupSpec { + + private String value; + + public String getValue() { + return value; + } + + public WorkflowExplicitCleanupSpec setValue(String value) { + this.value = value; + return this; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitinvocation/ConfigMapDependent.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitinvocation/ConfigMapDependent.java new file mode 100644 index 0000000000..e26fcfcf11 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitinvocation/ConfigMapDependent.java @@ -0,0 +1,29 @@ +package io.javaoperatorsdk.operator.sample.workflowexplicitinvocation; + +import java.util.Map; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ConfigMapBuilder; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDNoGCKubernetesDependentResource; + +public class ConfigMapDependent extends + CRUDNoGCKubernetesDependentResource { + + public ConfigMapDependent() { + super(ConfigMap.class); + } + + @Override + protected ConfigMap desired(WorkflowExplicitInvocationCustomResource primary, + Context context) { + return new ConfigMapBuilder() + .withMetadata(new ObjectMetaBuilder() + .withName(primary.getMetadata().getName()) + .withNamespace(primary.getMetadata().getNamespace()) + .build()) + .withData(Map.of("key", primary.getSpec().getValue())) + .build(); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitinvocation/WorkflowExplicitInvocationCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitinvocation/WorkflowExplicitInvocationCustomResource.java new file mode 100644 index 0000000000..827a17ddaf --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitinvocation/WorkflowExplicitInvocationCustomResource.java @@ -0,0 +1,15 @@ +package io.javaoperatorsdk.operator.sample.workflowexplicitinvocation; + +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("wei") +public class WorkflowExplicitInvocationCustomResource + extends CustomResource + implements Namespaced { +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitinvocation/WorkflowExplicitInvocationReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitinvocation/WorkflowExplicitInvocationReconciler.java new file mode 100644 index 0000000000..dc7bce4296 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitinvocation/WorkflowExplicitInvocationReconciler.java @@ -0,0 +1,39 @@ +package io.javaoperatorsdk.operator.sample.workflowexplicitinvocation; + +import java.util.concurrent.atomic.AtomicInteger; + +import io.javaoperatorsdk.operator.api.reconciler.*; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; + +@Workflow(explicitInvocation = true, + dependents = @Dependent(type = ConfigMapDependent.class)) +@ControllerConfiguration +public class WorkflowExplicitInvocationReconciler + implements Reconciler { + + private final AtomicInteger numberOfExecutions = new AtomicInteger(0); + + private volatile boolean invokeWorkflow = false; + + @Override + public UpdateControl reconcile( + WorkflowExplicitInvocationCustomResource resource, + Context context) { + + numberOfExecutions.addAndGet(1); + if (invokeWorkflow) { + context.managedWorkflowAndDependentResourceContext().reconcileManagedWorkflow(); + } + + + return UpdateControl.noUpdate(); + } + + public int getNumberOfExecutions() { + return numberOfExecutions.get(); + } + + public void setInvokeWorkflow(boolean invokeWorkflow) { + this.invokeWorkflow = invokeWorkflow; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitinvocation/WorkflowExplicitInvocationSpec.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitinvocation/WorkflowExplicitInvocationSpec.java new file mode 100644 index 0000000000..2112d348e2 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowexplicitinvocation/WorkflowExplicitInvocationSpec.java @@ -0,0 +1,15 @@ +package io.javaoperatorsdk.operator.sample.workflowexplicitinvocation; + +public class WorkflowExplicitInvocationSpec { + + private String value; + + public String getValue() { + return value; + } + + public WorkflowExplicitInvocationSpec setValue(String value) { + this.value = value; + return this; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowmultipleactivation/WorkflowMultipleActivationReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowmultipleactivation/WorkflowMultipleActivationReconciler.java index 8277e7f8e7..aeb4403f7c 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowmultipleactivation/WorkflowMultipleActivationReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowmultipleactivation/WorkflowMultipleActivationReconciler.java @@ -2,17 +2,15 @@ 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.*; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; -@ControllerConfiguration(dependents = { +@Workflow(dependents = { @Dependent(type = ConfigMapDependentResource.class, activationCondition = ActivationCondition.class), @Dependent(type = SecretDependentResource.class) }) +@ControllerConfiguration public class WorkflowMultipleActivationReconciler implements Reconciler { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/ConfigMapDependent.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/ConfigMapDependent.java new file mode 100644 index 0000000000..a418e8787e --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/ConfigMapDependent.java @@ -0,0 +1,28 @@ +package io.javaoperatorsdk.operator.sample.workflowsilentexceptionhandling; + + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDNoGCKubernetesDependentResource; + +public class ConfigMapDependent extends + CRUDNoGCKubernetesDependentResource { + + public ConfigMapDependent() { + super(ConfigMap.class); + } + + @Override + public ReconcileResult reconcile( + HandleWorkflowExceptionsInReconcilerCustomResource primary, + Context context) { + throw new RuntimeException("Exception thrown on purpose"); + } + + @Override + public void delete(HandleWorkflowExceptionsInReconcilerCustomResource primary, + Context context) { + throw new RuntimeException("Exception thrown on purpose"); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/HandleWorkflowExceptionsInReconcilerCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/HandleWorkflowExceptionsInReconcilerCustomResource.java new file mode 100644 index 0000000000..3d4283e182 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/HandleWorkflowExceptionsInReconcilerCustomResource.java @@ -0,0 +1,15 @@ +package io.javaoperatorsdk.operator.sample.workflowsilentexceptionhandling; + +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("hweir") +public class HandleWorkflowExceptionsInReconcilerCustomResource + extends CustomResource + implements Namespaced { +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/HandleWorkflowExceptionsInReconcilerReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/HandleWorkflowExceptionsInReconcilerReconciler.java new file mode 100644 index 0000000000..2519ccfe8d --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/workflowsilentexceptionhandling/HandleWorkflowExceptionsInReconcilerReconciler.java @@ -0,0 +1,50 @@ +package io.javaoperatorsdk.operator.sample.workflowsilentexceptionhandling; + +import io.javaoperatorsdk.operator.api.reconciler.Cleaner; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; +import io.javaoperatorsdk.operator.api.reconciler.DeleteControl; +import io.javaoperatorsdk.operator.api.reconciler.Reconciler; +import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; +import io.javaoperatorsdk.operator.api.reconciler.Workflow; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; + +@Workflow(handleExceptionsInReconciler = true, + dependents = @Dependent(type = ConfigMapDependent.class)) +@ControllerConfiguration +public class HandleWorkflowExceptionsInReconcilerReconciler + implements Reconciler, + Cleaner { + + private volatile boolean errorsFoundInReconcilerResult = false; + private volatile boolean errorsFoundInCleanupResult = false; + + @Override + public UpdateControl reconcile( + HandleWorkflowExceptionsInReconcilerCustomResource resource, + Context context) { + + errorsFoundInReconcilerResult = context.managedWorkflowAndDependentResourceContext() + .getWorkflowReconcileResult().erroredDependentsExist(); + + + return UpdateControl.noUpdate(); + } + + @Override + public DeleteControl cleanup(HandleWorkflowExceptionsInReconcilerCustomResource resource, + Context context) { + + errorsFoundInCleanupResult = context.managedWorkflowAndDependentResourceContext() + .getWorkflowCleanupResult().erroredDependentsExist(); + return DeleteControl.defaultDelete(); + } + + public boolean isErrorsFoundInReconcilerResult() { + return errorsFoundInReconcilerResult; + } + + public boolean isErrorsFoundInCleanupResult() { + return errorsFoundInCleanupResult; + } +} diff --git a/operator-framework/src/test/resources/compile-fixtures/MultilevelReconciler.java b/operator-framework/src/test/resources/compile-fixtures/MultilevelReconciler.java index acea0a0db2..254d211bd0 100644 --- a/operator-framework/src/test/resources/compile-fixtures/MultilevelReconciler.java +++ b/operator-framework/src/test/resources/compile-fixtures/MultilevelReconciler.java @@ -17,7 +17,7 @@ public static class MyCustomResource extends CustomResource { public UpdateControl reconcile( MultilevelReconciler.MyCustomResource customResource, Context context) { - return UpdateControl.updateResource(null); + return UpdateControl.patchResource(null); } public DeleteControl cleanup(MultilevelReconciler.MyCustomResource customResource, diff --git a/operator-framework/src/test/resources/compile-fixtures/ReconcilerImplemented2Interfaces.java b/operator-framework/src/test/resources/compile-fixtures/ReconcilerImplemented2Interfaces.java index 0adc9aee09..bd1ba773be 100644 --- a/operator-framework/src/test/resources/compile-fixtures/ReconcilerImplemented2Interfaces.java +++ b/operator-framework/src/test/resources/compile-fixtures/ReconcilerImplemented2Interfaces.java @@ -14,7 +14,7 @@ public static class MyCustomResource extends CustomResource { @Override public UpdateControl reconcile(MyCustomResource customResource, Context context) { - return UpdateControl.updateResource(null); + return UpdateControl.patchResource(null); } @Override diff --git a/operator-framework/src/test/resources/compile-fixtures/ReconcilerImplementedIntermediateAbstractClass.java b/operator-framework/src/test/resources/compile-fixtures/ReconcilerImplementedIntermediateAbstractClass.java index b95495a614..ee291cf9ce 100644 --- a/operator-framework/src/test/resources/compile-fixtures/ReconcilerImplementedIntermediateAbstractClass.java +++ b/operator-framework/src/test/resources/compile-fixtures/ReconcilerImplementedIntermediateAbstractClass.java @@ -13,7 +13,7 @@ public class ReconcilerImplementedIntermediateAbstractClass extends public UpdateControl reconcile( AbstractReconciler.MyCustomResource customResource, Context context) { - return UpdateControl.updateResource(null); + return UpdateControl.patchResource(null); } public DeleteControl cleanup(AbstractReconciler.MyCustomResource customResource, diff --git a/pom.xml b/pom.xml index c62b44c8cb..79cc6f954c 100644 --- a/pom.xml +++ b/pom.xml @@ -1,551 +1,512 @@ - - 4.0.0 + + 4.0.0 - io.javaoperatorsdk - java-operator-sdk - 4.9.1-SNAPSHOT - Operator SDK for Java - Java SDK for implementing Kubernetes operators - pom - https://github.com/operator-framework/java-operator-sdk + io.javaoperatorsdk + java-operator-sdk + 5.0.0-SNAPSHOT + pom + Operator SDK for Java + Java SDK for implementing Kubernetes operators + https://github.com/operator-framework/java-operator-sdk - - - Apache 2 License - https://www.apache.org/licenses/LICENSE-2.0.html - - - - - Adam Sandor - adam.sandor@container-solutions.com - - - Attila Meszaros - csviri@gmail.com - - + + + Apache 2 License + https://www.apache.org/licenses/LICENSE-2.0.html + + + + + Adam Sandor + adam.sandor@container-solutions.com + + + Attila Meszaros + csviri@gmail.com + + - - scm:git:git://github.com/java-operator-sdk/java-operator-sdk.git - scm:git:git@github.com/java-operator-sdk/java-operator-sdk.git - https://github.com/operator-framework/java-operator-sdk/tree/main - + + operator-framework-bom + operator-framework-core + operator-framework-junit5 + operator-framework + micrometer-support + sample-operators + caffeine-bounded-cache-support + bootstrapper-maven-plugin + - - UTF-8 - 11 - ${java.version} - ${java.version} - java-operator-sdk - https://sonarcloud.io - okhttp + + scm:git:git://github.com/operator-framework/java-operator-sdk.git + scm:git:git@github.com/operator-framework/java-operator-sdk.git + https://github.com/operator-framework/java-operator-sdk/tree/main + - 5.10.1 - 6.12.1 - 1.7.36 - 2.23.1 - 5.12.0 - 3.14.0 - 0.21.0 - 1.13.0 - 3.25.3 - 4.2.0 - 2.7.3 - 1.13.0 - 4.12.0 - 3.1.8 - 0.9.6 - 0.9.11 - 2.16.1 + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + - 2.11 - 3.12.1 - 3.2.5 - 3.6.3 - 3.3.1 - 3.3.1 - 3.4.1 - 3.3.2 - 3.2.4 - 1.6.13 - 3.0.0 - 3.1.2 - 8.0.2 - 2.23.0 - 1.0 - 1.9.0 - + + UTF-8 + 17 + ${java.version} + ${java.version} + java-operator-sdk + https://sonarcloud.io + jdk - - operator-framework-bom - operator-framework-core - operator-framework-junit5 - operator-framework - micrometer-support - sample-operators - caffeine-bounded-cache-support - bootstrapper-maven-plugin - + 5.10.1 + 6.12.1 + 1.7.36 + 2.23.1 + 5.12.0 + 3.14.0 + 0.21.0 + 1.13.0 + 3.25.3 + 4.2.0 + 2.7.3 + 1.13.0 + 4.12.0 + 3.1.8 + 0.9.6 + 0.9.11 + 2.16.1 + 2.11 + 3.12.1 + 3.2.5 + 3.6.3 + 3.3.1 + 3.3.1 + 3.4.1 + 3.3.2 + 3.2.4 + 1.6.13 + 3.0.0 + 3.1.2 + 8.0.2 + 3.4.1 + 2.43.0 + - - - - org.junit - junit-bom - ${junit.version} - pom - import - - - io.fabric8 - kubernetes-client-bom - ${fabric8-client.version} - pom - import - - - io.fabric8 - kubernetes-server-mock - ${fabric8-client.version} - test - - - io.fabric8 - kubernetes-client-api - ${fabric8-client.version} - - - org.apache.commons - commons-lang3 - ${commons-lang3.version} - - - com.google.testing.compile - compile-testing - ${compile-testing.version} - - - io.micrometer - micrometer-core - ${micrometer-core.version} - - - com.squareup - javapoet - ${javapoet.version} - - - org.awaitility - awaitility - ${awaitility.version} - - - commons-io - commons-io - ${commons.io.version} - - - org.assertj - assertj-core - ${assertj.version} - - - org.mockito - mockito-core - ${mokito.version} - + + + + org.junit + junit-bom + ${junit.version} + pom + import + + + io.fabric8 + kubernetes-client-bom + ${fabric8-client.version} + pom + import + + + io.fabric8 + kubernetes-server-mock + ${fabric8-client.version} + test + + + io.fabric8 + kubernetes-client-api + ${fabric8-client.version} + + + org.apache.commons + commons-lang3 + ${commons-lang3.version} + + + com.google.testing.compile + compile-testing + ${compile-testing.version} + + + io.micrometer + micrometer-core + ${micrometer-core.version} + + + com.squareup + javapoet + ${javapoet.version} + + + org.awaitility + awaitility + ${awaitility.version} + + + commons-io + commons-io + ${commons.io.version} + + + org.assertj + assertj-core + ${assertj.version} + + + org.mockito + mockito-core + ${mokito.version} + - - org.slf4j - slf4j-api - ${slf4j.version} - - - org.apache.logging.log4j - log4j-slf4j-impl - ${log4j.version} - - - org.apache.logging.log4j - log4j-core - ${log4j.version} - test - - - org.apache.logging.log4j - log4j2-core - ${log4j.version} - - - com.github.spullara.mustache.java - compiler - ${mustache.version} - - - io.javaoperatorsdk - operator-framework-core - ${project.version} - - - io.javaoperatorsdk - operator-framework - ${project.version} - - - - com.squareup.okhttp3 - okhttp - ${okhttp.version} - - - com.squareup.okhttp3 - logging-interceptor - ${okhttp.version} - - - com.squareup.okhttp3 - mockwebserver - ${okhttp.version} - - - com.github.ben-manes.caffeine - caffeine - ${caffeine.version} - - - io.javaoperatorsdk - jenvtest - ${jenvtest.version} - test - - - - io.fabric8 - kubernetes-httpclient-okhttp - ${fabric8-client.version} - - - io.fabric8 - kubernetes-httpclient-vertx - ${fabric8-client.version} - - - - io.fabric8 - kubernetes-httpclient-jdk - ${fabric8-client.version} - - - io.fabric8 - kubernetes-httpclient-jetty - ${fabric8-client.version} - - - + + org.slf4j + slf4j-api + ${slf4j.version} + + + org.apache.logging.log4j + log4j-slf4j-impl + ${log4j.version} + + + org.apache.logging.log4j + log4j-core + ${log4j.version} + test + + + org.apache.logging.log4j + log4j2-core + ${log4j.version} + + + com.github.spullara.mustache.java + compiler + ${mustache.version} + + + io.javaoperatorsdk + operator-framework-core + ${project.version} + + + io.javaoperatorsdk + operator-framework + ${project.version} + + + com.squareup.okhttp3 + logging-interceptor + ${okhttp.version} + + + com.squareup.okhttp3 + mockwebserver + ${okhttp.version} + + + com.github.ben-manes.caffeine + caffeine + ${caffeine.version} + + + io.javaoperatorsdk + jenvtest + ${jenvtest.version} + test + + + + + io.fabric8 + kubernetes-httpclient-okhttp + ${fabric8-client.version} + + + io.fabric8 + kubernetes-httpclient-vertx + ${fabric8-client.version} + + + io.fabric8 + kubernetes-httpclient-jdk + ${fabric8-client.version} + + + io.fabric8 + kubernetes-httpclient-jetty + ${fabric8-client.version} + + + - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots/ - - true - always - - - - - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - ${maven-compiler-plugin.version} - - - org.apache.maven.plugins - maven-resources-plugin - ${maven-resources-plugin.version} - - - org.apache.maven.plugins - maven-jar-plugin - ${maven-jar-plugin.version} - - - org.apache.maven.plugins - maven-clean-plugin - ${maven-clean-plugin.version} - - - org.apache.maven.plugins - maven-surefire-plugin - ${maven-surefire-plugin.version} - - - org.apache.maven.plugins - maven-source-plugin - ${maven-source-plugin.version} - - - org.apache.maven.plugins - maven-gpg-plugin - ${maven-gpg-plugin.version} - - - org.apache.maven.plugins - maven-install-plugin - ${maven-install-plugin.version} - - - net.revelc.code.formatter - formatter-maven-plugin - ${formatter-maven-plugin.version} - - .cache - - - - net.revelc.code - impsort-maven-plugin - ${impsort-maven-plugin.version} - - .cache - java.,javax.,org.,io.,com. - * - true - true - - - - + + + + true + always + + ossrh + https://oss.sonatype.org/content/repositories/snapshots/ + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + + org.apache.maven.plugins + maven-resources-plugin + ${maven-resources-plugin.version} + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + org.apache.maven.plugins + maven-clean-plugin + ${maven-clean-plugin.version} + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + org.apache.maven.plugins + maven-source-plugin + ${maven-source-plugin.version} + + + org.apache.maven.plugins + maven-gpg-plugin + ${maven-gpg-plugin.version} + + + org.apache.maven.plugins + maven-install-plugin + ${maven-install-plugin.version} + + + com.diffplug.spotless + spotless-maven-plugin + ${spotless.version} + + + + + + com.diffplug.spotless + spotless-maven-plugin + + + + pom.xml + ./**/pom.xml + + + false + + + + + contributing/eclipse-google-style.xml + + + contributing/eclipse.importorder + + + + + + + + apply + + compile + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/*Test.java + + + **/*IT.java + **/*E2E.java + + WatchPermissionAwareTest + + + + + + + all-tests + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/*Test.java + **/*IT.java + **/*E2E.java + + + + + + + + no-unit-tests + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/*IT.java + + + **/*Test.java + **/*E2E.java + + + + + + + + + minimal-watch-timeout-dependent-it + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/*ITS.java + + + **/*Test.java + **/*E2E.java + **/*IT.java + + + + + + + + end-to-end-tests + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/*E2E.java + + + **/*Test.java + **/*IT.java + + + + + + + + release + - - org.commonjava.maven.plugins - directory-maven-plugin - ${directory-maven-plugin.version} - - - directories - - highest-basedir - - initialize - - josdk.project.root - - - - - - org.apache.maven.plugins - maven-surefire-plugin + + org.apache.maven.plugins + maven-surefire-plugin + + + **/*IT.java + **/*E2E.java + **/InformerRelatedBehaviorTest.java + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + + sign + + verify - - **/*Test.java - - - **/*IT.java - **/*E2E.java - - WatchPermissionAwareTest + + --pinentry-mode + loopback + - - - net.revelc.code.formatter - formatter-maven-plugin - - - - format - - - - ${josdk.project.root}/contributing/eclipse-google-style.xml - - - - - - net.revelc.code - impsort-maven-plugin - - - sort - - sort - - - - + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + ${nexus-staging-maven-plugin.version} + true + + ossrh + https://oss.sonatype.org/ + true + + - - - - all-tests - - - - org.apache.maven.plugins - maven-surefire-plugin - - - **/*Test.java - **/*IT.java - **/*E2E.java - - - - - - - - no-unit-tests - - - - org.apache.maven.plugins - maven-surefire-plugin - - - **/*IT.java - - - **/*Test.java - **/*E2E.java - - - - - - - - - minimal-watch-timeout-dependent-it - - - - org.apache.maven.plugins - maven-surefire-plugin - - - **/*ITS.java - - - **/*Test.java - **/*E2E.java - **/*IT.java - - - - - - - - end-to-end-tests - - - - org.apache.maven.plugins - maven-surefire-plugin - - - **/*E2E.java - - - **/*Test.java - **/*IT.java - - - - - - - - release - - - - org.apache.maven.plugins - maven-surefire-plugin - - - **/*IT.java - **/*E2E.java - **/InformerRelatedBehaviorTest.java - - - - - org.apache.maven.plugins - maven-javadoc-plugin - ${maven-javadoc-plugin.version} - - - attach-javadocs - - jar - - - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-gpg-plugin - - - sign-artifacts - verify - - sign - - - - --pinentry-mode - loopback - - - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - ${nexus-staging-maven-plugin.version} - true - - ossrh - https://oss.sonatype.org/ - true - - - - - - + + + diff --git a/sample-operators/leader-election/k8s/namespace-inferred-operator.yaml b/sample-operators/leader-election/k8s/namespace-inferred-operator.yaml index 13724a911a..cf8e743bd2 100644 --- a/sample-operators/leader-election/k8s/namespace-inferred-operator.yaml +++ b/sample-operators/leader-election/k8s/namespace-inferred-operator.yaml @@ -46,7 +46,7 @@ rules: verbs: - '*' - apiGroups: - - "sample.javaoperatorsdk" + - "sample.operator.javaoperatorsdk.io" resources: - leaderelections - leaderelections/status diff --git a/sample-operators/leader-election/k8s/operator.yaml b/sample-operators/leader-election/k8s/operator.yaml index 9d289a2d6c..eea9348072 100644 --- a/sample-operators/leader-election/k8s/operator.yaml +++ b/sample-operators/leader-election/k8s/operator.yaml @@ -50,7 +50,7 @@ rules: verbs: - '*' - apiGroups: - - "sample.javaoperatorsdk" + - "sample.operator.javaoperatorsdk.io" resources: - leaderelections - leaderelections/status diff --git a/sample-operators/leader-election/pom.xml b/sample-operators/leader-election/pom.xml index c7bbcb0cd2..bcbc4fbcff 100644 --- a/sample-operators/leader-election/pom.xml +++ b/sample-operators/leader-election/pom.xml @@ -1,105 +1,103 @@ - - 4.0.0 + + 4.0.0 - - io.javaoperatorsdk - sample-operators - 4.9.1-SNAPSHOT - - - sample-leader-election - Operator SDK - Samples - Leader Election - An E2E test for leader election - jar - - - 11 - 11 - 3.4.2 - + + io.javaoperatorsdk + sample-operators + 5.0.0-SNAPSHOT + - - - - io.javaoperatorsdk - operator-framework-bom - ${project.version} - pom - import - - - + sample-leader-election + jar + Operator SDK - Samples - Leader Election + An E2E test for leader election + - - io.javaoperatorsdk - operator-framework - - - org.apache.logging.log4j - log4j-slf4j-impl - - - org.takes - takes - 1.24.4 - - - org.awaitility - awaitility - compile - - - io.javaoperatorsdk - operator-framework-junit-5 - test - - - org.junit.jupiter - junit-jupiter-params - test - + + io.javaoperatorsdk + operator-framework-bom + ${project.version} + pom + import + - - - - com.google.cloud.tools - jib-maven-plugin - ${jib-maven-plugin.version} - - - gcr.io/distroless/java:11 - - - leader-election-operator - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.12.1 - - - - io.fabric8 - java-generator-maven-plugin - ${fabric8-client.version} - - - - generate - - - - - src/main/resources/kubernetes - - - - + + + + + io.javaoperatorsdk + operator-framework + + + org.apache.logging.log4j + log4j-slf4j-impl + compile + + + org.apache.logging.log4j + log4j-core + compile + + + org.takes + takes + 1.24.4 + + + org.awaitility + awaitility + compile + + + io.javaoperatorsdk + operator-framework-junit-5 + test + + + org.junit.jupiter + junit-jupiter-params + test + + + + + + com.google.cloud.tools + jib-maven-plugin + ${jib-maven-plugin.version} + + + gcr.io/distroless/java17-debian11 + + + leader-election-operator + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.12.1 + + + + io.fabric8 + java-generator-maven-plugin + ${fabric8-client.version} + + src/main/resources/kubernetes + + + + + generate + + + + + + diff --git a/sample-operators/leader-election/src/main/java/io/javaoperatorsdk/operator/sample/LeaderElectionTestOperator.java b/sample-operators/leader-election/src/main/java/io/javaoperatorsdk/operator/sample/LeaderElectionTestOperator.java index 262c0a7c70..359272e0ef 100644 --- a/sample-operators/leader-election/src/main/java/io/javaoperatorsdk/operator/sample/LeaderElectionTestOperator.java +++ b/sample-operators/leader-election/src/main/java/io/javaoperatorsdk/operator/sample/LeaderElectionTestOperator.java @@ -3,7 +3,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.fabric8.kubernetes.client.KubernetesClientBuilder; import io.javaoperatorsdk.operator.Operator; import io.javaoperatorsdk.operator.api.config.LeaderElectionConfiguration; @@ -22,9 +21,8 @@ public static void main(String[] args) { ? new LeaderElectionConfiguration("leader-election-test") : new LeaderElectionConfiguration("leader-election-test", namespace, identity); - var client = new KubernetesClientBuilder().build(); Operator operator = - new Operator(client, c -> c.withLeaderElectionConfiguration(leaderElectionConfiguration)); + new Operator(c -> c.withLeaderElectionConfiguration(leaderElectionConfiguration)); operator.register(new LeaderElectionTestReconciler(identity)); operator.start(); diff --git a/sample-operators/leader-election/src/main/java/io/javaoperatorsdk/operator/sample/LeaderElectionTestReconciler.java b/sample-operators/leader-election/src/main/java/io/javaoperatorsdk/operator/sample/LeaderElectionTestReconciler.java index 4cd8627328..1e54ddd915 100644 --- a/sample-operators/leader-election/src/main/java/io/javaoperatorsdk/operator/sample/LeaderElectionTestReconciler.java +++ b/sample-operators/leader-election/src/main/java/io/javaoperatorsdk/operator/sample/LeaderElectionTestReconciler.java @@ -7,9 +7,8 @@ import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; import io.javaoperatorsdk.operator.api.reconciler.Reconciler; import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; - -import javaoperatorsdk.sample.v1.LeaderElection; -import javaoperatorsdk.sample.v1.LeaderElectionStatus; +import io.javaoperatorsdk.operator.sample.v1.LeaderElection; +import io.javaoperatorsdk.operator.sample.v1.LeaderElectionStatus; @ControllerConfiguration() public class LeaderElectionTestReconciler @@ -35,7 +34,7 @@ public UpdateControl reconcile( resource.getStatus().getReconciledBy().add(reconcilerName); // update status is with optimistic locking - return UpdateControl.updateStatus(resource).rescheduleAfter(Duration.ofSeconds(1)); + return UpdateControl.patchStatus(resource).rescheduleAfter(Duration.ofSeconds(1)); } } diff --git a/sample-operators/leader-election/src/main/resources/kubernetes/leaderelections.sample.javaoperatorsdk-v1.yml b/sample-operators/leader-election/src/main/resources/kubernetes/leaderelections.sample.javaoperatorsdk-v1.yml index cc9d8c3fc6..e0580f8901 100644 --- a/sample-operators/leader-election/src/main/resources/kubernetes/leaderelections.sample.javaoperatorsdk-v1.yml +++ b/sample-operators/leader-election/src/main/resources/kubernetes/leaderelections.sample.javaoperatorsdk-v1.yml @@ -4,9 +4,9 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: - name: leaderelections.sample.javaoperatorsdk + name: leaderelections.sample.operator.javaoperatorsdk.io spec: - group: sample.javaoperatorsdk + group: sample.operator.javaoperatorsdk.io names: kind: LeaderElection singular: leaderelection diff --git a/sample-operators/leader-election/src/test/java/io/javaoperatorsdk/operator/sample/LeaderElectionE2E.java b/sample-operators/leader-election/src/test/java/io/javaoperatorsdk/operator/sample/LeaderElectionE2E.java index 7932472aab..863407999d 100644 --- a/sample-operators/leader-election/src/test/java/io/javaoperatorsdk/operator/sample/LeaderElectionE2E.java +++ b/sample-operators/leader-election/src/test/java/io/javaoperatorsdk/operator/sample/LeaderElectionE2E.java @@ -26,8 +26,7 @@ import io.fabric8.kubernetes.client.ConfigBuilder; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.KubernetesClientBuilder; - -import javaoperatorsdk.sample.v1.LeaderElection; +import io.javaoperatorsdk.operator.sample.v1.LeaderElection; import static io.javaoperatorsdk.operator.junit.AbstractOperatorExtension.CRD_READY_WAIT; import static org.assertj.core.api.Assertions.assertThat; diff --git a/sample-operators/mysql-schema/pom.xml b/sample-operators/mysql-schema/pom.xml index 3c3a9107f9..dedb681116 100644 --- a/sample-operators/mysql-schema/pom.xml +++ b/sample-operators/mysql-schema/pom.xml @@ -1,116 +1,113 @@ - - 4.0.0 + + 4.0.0 - - io.javaoperatorsdk - sample-operators - 4.9.1-SNAPSHOT - - - sample-mysql-schema-operator - Operator SDK - Samples - MySQL Schema - Provisions Schemas in a MySQL database - jar - - - 11 - 11 - 3.4.2 - + + io.javaoperatorsdk + sample-operators + 5.0.0-SNAPSHOT + - - - - io.javaoperatorsdk - operator-framework-bom - ${project.version} - pom - import - - - + sample-mysql-schema-operator + jar + Operator SDK - Samples - MySQL Schema + Provisions Schemas in a MySQL database + - - io.javaoperatorsdk - operator-framework - - - io.javaoperatorsdk - micrometer-support - - - org.takes - takes - 1.24.4 - - - mysql - mysql-connector-java - 8.0.30 - - - io.fabric8 - crd-generator-apt - provided - - - org.apache.logging.log4j - log4j-slf4j-impl - - - org.junit.jupiter - junit-jupiter-api - test - - - org.junit.jupiter - junit-jupiter-engine - test - - - org.awaitility - awaitility - test - - - io.javaoperatorsdk - operator-framework-junit-5 - test - + + io.javaoperatorsdk + operator-framework-bom + ${project.version} + pom + import + + + + + + io.javaoperatorsdk + operator-framework + + + io.javaoperatorsdk + micrometer-support + + + org.takes + takes + 1.24.4 + + + mysql + mysql-connector-java + 8.0.30 + + + io.fabric8 + crd-generator-apt + provided + + + org.apache.logging.log4j + log4j-slf4j-impl + + + org.apache.logging.log4j + log4j-core + compile + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.awaitility + awaitility + test + + + io.javaoperatorsdk + operator-framework-junit-5 + test + + - - - - org.apache.maven.plugins - maven-surefire-plugin - - 0 - - - - com.google.cloud.tools - jib-maven-plugin - ${jib-maven-plugin.version} - - - gcr.io/distroless/java:11 - - - mysql-schema-operator - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.12.1 - - - + + + + org.apache.maven.plugins + maven-surefire-plugin + + 0 + + + + com.google.cloud.tools + jib-maven-plugin + ${jib-maven-plugin.version} + + + gcr.io/distroless/java17-debian11 + + + mysql-schema-operator + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.12.1 + + + - \ No newline at end of file + diff --git a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/MySQLDbConfig.java b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/MySQLDbConfig.java index 7cc06dd373..0f63cc846a 100644 --- a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/MySQLDbConfig.java +++ b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/MySQLDbConfig.java @@ -43,4 +43,3 @@ public String getPassword() { return password; } } - diff --git a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/MySQLSchemaOperator.java b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/MySQLSchemaOperator.java index e9e1ac1d49..ce3595f0c3 100644 --- a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/MySQLSchemaOperator.java +++ b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/MySQLSchemaOperator.java @@ -1,6 +1,7 @@ package io.javaoperatorsdk.operator.sample; import java.io.IOException; +import java.time.Duration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,7 +32,8 @@ public static void main(String[] args) throws IOException { operator.register(schemaReconciler, configOverrider -> configOverrider.replacingNamedDependentResourceConfig( SchemaDependentResource.NAME, - new ResourcePollerConfig(300, MySQLDbConfig.loadFromEnvironmentVars()))); + new ResourcePollerConfig(Duration.ofMillis(300), + MySQLDbConfig.loadFromEnvironmentVars()))); operator.start(); new FtBasic(new TkFork(new FkRegex("/health", "ALL GOOD!")), 8080).start(Exit.NEVER); diff --git a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/MySQLSchemaReconciler.java b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/MySQLSchemaReconciler.java index 95db43b228..4a6f4f4d45 100644 --- a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/MySQLSchemaReconciler.java +++ b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/MySQLSchemaReconciler.java @@ -3,13 +3,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.Secret; -import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; -import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusHandler; -import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusUpdateControl; -import io.javaoperatorsdk.operator.api.reconciler.Reconciler; -import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; +import io.javaoperatorsdk.operator.api.reconciler.*; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; import io.javaoperatorsdk.operator.sample.dependent.SchemaDependentResource; import io.javaoperatorsdk.operator.sample.dependent.SecretDependentResource; @@ -19,12 +15,12 @@ import static io.javaoperatorsdk.operator.sample.dependent.SecretDependentResource.MYSQL_SECRET_USERNAME; import static java.lang.String.format; -@ControllerConfiguration( - dependents = { - @Dependent(type = SecretDependentResource.class, name = SecretDependentResource.NAME), - @Dependent(type = SchemaDependentResource.class, name = SchemaDependentResource.NAME, - dependsOn = SecretDependentResource.NAME) - }) +@Workflow(dependents = { + @Dependent(type = SecretDependentResource.class, name = SecretDependentResource.NAME), + @Dependent(type = SchemaDependentResource.class, name = SchemaDependentResource.NAME, + dependsOn = SecretDependentResource.NAME) +}) +@ControllerConfiguration public class MySQLSchemaReconciler implements Reconciler, ErrorStatusHandler { @@ -38,10 +34,10 @@ public UpdateControl reconcile(MySQLSchema schema, Context { - updateStatusPojo(schema, s, secret.getMetadata().getName(), + var statusUpdateResource = createForStatusUpdate(schema, s, secret.getMetadata().getName(), decode(secret.getData().get(MYSQL_SECRET_USERNAME))); log.info("Schema {} created - updating CR status", s.getName()); - return UpdateControl.patchStatus(schema); + return UpdateControl.patchStatus(statusUpdateResource); }).orElseGet(UpdateControl::noUpdate); } @@ -55,12 +51,18 @@ public ErrorStatusUpdateControl updateErrorStatus(MySQLSchema schem status.setSecretName(null); status.setStatus("ERROR: " + e.getMessage()); schema.setStatus(status); - return ErrorStatusUpdateControl.updateStatus(schema); + return ErrorStatusUpdateControl.patchStatus(schema); } - private void updateStatusPojo(MySQLSchema mySQLSchema, Schema schema, String secretName, + private MySQLSchema createForStatusUpdate(MySQLSchema mySQLSchema, Schema schema, + String secretName, String userName) { + MySQLSchema res = new MySQLSchema(); + res.setMetadata(new ObjectMetaBuilder() + .withName(mySQLSchema.getMetadata().getName()) + .withNamespace(mySQLSchema.getMetadata().getNamespace()) + .build()); SchemaStatus status = new SchemaStatus(); status.setUrl( format( @@ -69,6 +71,7 @@ private void updateStatusPojo(MySQLSchema mySQLSchema, Schema schema, String sec status.setUserName(userName); status.setSecretName(secretName); status.setStatus("CREATED"); - mySQLSchema.setStatus(status); + res.setStatus(status); + return res; } } diff --git a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/SchemaStatus.java b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/SchemaStatus.java index 92ddb67a63..168cd8db15 100644 --- a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/SchemaStatus.java +++ b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/SchemaStatus.java @@ -1,8 +1,6 @@ package io.javaoperatorsdk.operator.sample; -import io.javaoperatorsdk.operator.api.ObservedGenerationAwareStatus; - -public class SchemaStatus extends ObservedGenerationAwareStatus { +public class SchemaStatus { private String url; diff --git a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/dependent/ResourcePollerConfig.java b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/dependent/ResourcePollerConfig.java index 5e9cc3f964..44de818f88 100644 --- a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/dependent/ResourcePollerConfig.java +++ b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/dependent/ResourcePollerConfig.java @@ -1,19 +1,21 @@ package io.javaoperatorsdk.operator.sample.dependent; +import java.time.Duration; + import io.javaoperatorsdk.operator.sample.MySQLDbConfig; public class ResourcePollerConfig { - private final int pollPeriod; + private final Duration pollPeriod; private final MySQLDbConfig mySQLDbConfig; - public ResourcePollerConfig(int pollPeriod, MySQLDbConfig mySQLDbConfig) { + public ResourcePollerConfig(Duration pollPeriod, MySQLDbConfig mySQLDbConfig) { this.pollPeriod = pollPeriod; this.mySQLDbConfig = mySQLDbConfig; } - public int getPollPeriod() { + public Duration getPollPeriod() { return pollPeriod; } 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 77d8932e58..2098b531b3 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 @@ -3,6 +3,7 @@ import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; +import java.time.Duration; import java.util.Base64; import java.util.Collections; import java.util.Optional; @@ -30,7 +31,7 @@ import static io.javaoperatorsdk.operator.sample.dependent.SecretDependentResource.MYSQL_SECRET_USERNAME; import static java.lang.String.format; -@SchemaConfig(pollPeriod = 700, host = "127.0.0.1", +@SchemaConfig(pollPeriod = 400, host = "127.0.0.1", port = SchemaDependentResource.LOCAL_PORT, user = "root", password = "password") // NOSONAR: password is only used locally, example only @Configured(by = SchemaConfig.class, with = ResourcePollerConfig.class, @@ -52,7 +53,7 @@ public SchemaDependentResource() { @Override public Optional configuration() { - return Optional.of(new ResourcePollerConfig((int) getPollingPeriod(), dbConfig)); + return Optional.of(new ResourcePollerConfig(getPollingPeriod(), dbConfig)); } @Override @@ -63,7 +64,9 @@ public void configureWith(ResourcePollerConfig config) { @Override public Schema desired(MySQLSchema primary, Context context) { - return new Schema(primary.getMetadata().getName(), primary.getSpec().getEncoding()); + var desired = new Schema(primary.getMetadata().getName(), primary.getSpec().getEncoding()); + log.debug("Desired schema: {}", desired); + return desired; } @Override @@ -72,6 +75,7 @@ public Schema create(Schema target, MySQLSchema mySQLSchema, Context fetchResources(MySQLSchema primaryResource) { try (Connection connection = getConnection()) { - return SchemaService.getSchema(connection, primaryResource.getMetadata().getName()) + var schema = SchemaService.getSchema(connection, primaryResource.getMetadata().getName()) .map(Set::of).orElseGet(Collections::emptySet); + log.debug("Fetched schema: {}", schema); + return schema; } catch (SQLException e) { throw new RuntimeException("Error while trying read Schema", e); } @@ -122,11 +128,11 @@ public ResourcePollerConfig configFrom(SchemaConfig configAnnotation, ControllerConfiguration parentConfiguration, Class originatingClass) { if (configAnnotation != null) { - return new ResourcePollerConfig(configAnnotation.pollPeriod(), + return new ResourcePollerConfig(Duration.ofMillis(configAnnotation.pollPeriod()), new MySQLDbConfig(configAnnotation.host(), String.valueOf(configAnnotation.port()), configAnnotation.user(), configAnnotation.password())); } - return new ResourcePollerConfig(SchemaConfig.DEFAULT_POLL_PERIOD, + return new ResourcePollerConfig(Duration.ofMillis(SchemaConfig.DEFAULT_POLL_PERIOD), MySQLDbConfig.loadFromEnvironmentVars()); } } diff --git a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/schema/Schema.java b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/schema/Schema.java index 34c1e54255..87fb88e9a4 100644 --- a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/schema/Schema.java +++ b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/schema/Schema.java @@ -28,11 +28,19 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; Schema schema = (Schema) o; - return Objects.equals(name, schema.name) && Objects.equals(characterSet, schema.characterSet); + return Objects.equals(name, schema.name); } @Override public int hashCode() { return Objects.hash(name, characterSet); } + + @Override + public String toString() { + return "Schema{" + + "name='" + name + '\'' + + ", characterSet='" + characterSet + '\'' + + '}'; + } } diff --git a/sample-operators/pom.xml b/sample-operators/pom.xml index 959257cec5..6d09f9a3ad 100644 --- a/sample-operators/pom.xml +++ b/sample-operators/pom.xml @@ -1,27 +1,21 @@ - - 4.0.0 + + 4.0.0 - - io.javaoperatorsdk - java-operator-sdk - 4.9.1-SNAPSHOT - + + io.javaoperatorsdk + java-operator-sdk + 5.0.0-SNAPSHOT + - sample-operators - Operator SDK - Samples - pom + sample-operators + pom + Operator SDK - Samples - - 3.1.4 - - - - tomcat-operator - webpage - mysql-schema - leader-election - + + tomcat-operator + webpage + mysql-schema + leader-election + diff --git a/sample-operators/tomcat-operator/pom.xml b/sample-operators/tomcat-operator/pom.xml index be62027eb1..158133160d 100644 --- a/sample-operators/tomcat-operator/pom.xml +++ b/sample-operators/tomcat-operator/pom.xml @@ -1,118 +1,115 @@ - - 4.0.0 + + 4.0.0 - - io.javaoperatorsdk - sample-operators - 4.9.1-SNAPSHOT - - - sample-tomcat-operator - Operator SDK - Samples - Tomcat - Provisions Tomcat Pods and deploys Webapplications in them - jar - - - 11 - 11 - 3.4.2 - + + io.javaoperatorsdk + sample-operators + 5.0.0-SNAPSHOT + - - - - io.javaoperatorsdk - operator-framework-bom - ${project.version} - pom - import - - - + sample-tomcat-operator + jar + Operator SDK - Samples - Tomcat + Provisions Tomcat Pods and deploys Webapplications in them + - - io.javaoperatorsdk - operator-framework - - - io.fabric8 - kubernetes-httpclient-okhttp - - - - - io.fabric8 - kubernetes-httpclient-vertx - - - io.fabric8 - crd-generator-apt - provided - - - org.apache.logging.log4j - log4j-slf4j-impl - - - org.takes - takes - 1.24.4 - - - org.junit.jupiter - junit-jupiter-api - test - - - org.junit.jupiter - junit-jupiter-engine - test - - - org.awaitility - awaitility - 4.2.0 - test - - - io.javaoperatorsdk - operator-framework-junit-5 - test - + + io.javaoperatorsdk + operator-framework-bom + ${project.version} + pom + import + + + + + + io.javaoperatorsdk + operator-framework + + + io.fabric8 + kubernetes-httpclient-okhttp + + + + + io.fabric8 + kubernetes-httpclient-vertx + + + io.fabric8 + crd-generator-apt + provided + + + org.apache.logging.log4j + log4j-slf4j-impl + + + org.apache.logging.log4j + log4j-core + compile + + + org.takes + takes + 1.24.4 + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.awaitility + awaitility + 4.2.0 + test + + + io.javaoperatorsdk + operator-framework-junit-5 + test + + - - - - org.apache.maven.plugins - maven-surefire-plugin - - 0 - - - - com.google.cloud.tools - jib-maven-plugin - ${jib-maven-plugin.version} - - - gcr.io/distroless/java:11 - - - tomcat-operator - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.12.1 - - - + + + + org.apache.maven.plugins + maven-surefire-plugin + + 0 + + + + com.google.cloud.tools + jib-maven-plugin + ${jib-maven-plugin.version} + + + gcr.io/distroless/java17-debian11 + + + tomcat-operator + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.12.1 + + + - \ No newline at end of file + diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatReconciler.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatReconciler.java index de4a63431b..f89a5f22e0 100644 --- a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatReconciler.java +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatReconciler.java @@ -5,23 +5,21 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.apps.DeploymentStatus; -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.*; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; /** * Runs a specified number of Tomcat app server Pods. It uses a Deployment to create the Pods. Also * creates a Service over which the Pods can be accessed. */ -@ControllerConfiguration( - dependents = { - @Dependent(type = DeploymentDependentResource.class), - @Dependent(type = ServiceDependentResource.class) - }) +@Workflow(dependents = { + @Dependent(type = DeploymentDependentResource.class), + @Dependent(type = ServiceDependentResource.class) +}) +@ControllerConfiguration public class TomcatReconciler implements Reconciler { private final Logger log = LoggerFactory.getLogger(getClass()); @@ -29,23 +27,28 @@ public class TomcatReconciler implements Reconciler { @Override public UpdateControl reconcile(Tomcat tomcat, Context context) { return context.getSecondaryResource(Deployment.class).map(deployment -> { - Tomcat updatedTomcat = updateTomcatStatus(tomcat, deployment); + Tomcat updatedTomcat = createTomcatForStatusUpdate(tomcat, deployment); log.info( "Updating status of Tomcat {} in namespace {} to {} ready replicas", tomcat.getMetadata().getName(), tomcat.getMetadata().getNamespace(), - tomcat.getStatus().getReadyReplicas()); + tomcat.getStatus() == null ? 0 : tomcat.getStatus().getReadyReplicas()); return UpdateControl.patchStatus(updatedTomcat); }).orElseGet(UpdateControl::noUpdate); } - private Tomcat updateTomcatStatus(Tomcat tomcat, Deployment deployment) { + private Tomcat createTomcatForStatusUpdate(Tomcat tomcat, Deployment deployment) { + Tomcat res = new Tomcat(); + res.setMetadata(new ObjectMetaBuilder() + .withName(tomcat.getMetadata().getName()) + .withNamespace(tomcat.getMetadata().getNamespace()) + .build()); DeploymentStatus deploymentStatus = Objects.requireNonNullElse(deployment.getStatus(), new DeploymentStatus()); int readyReplicas = Objects.requireNonNullElse(deploymentStatus.getReadyReplicas(), 0); TomcatStatus status = new TomcatStatus(); status.setReadyReplicas(readyReplicas); - tomcat.setStatus(status); - return tomcat; + res.setStatus(status); + return res; } } diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/WebappReconciler.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/WebappReconciler.java index 5b87d23aac..79194c7990 100644 --- a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/WebappReconciler.java +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/WebappReconciler.java @@ -2,7 +2,6 @@ import java.io.ByteArrayOutputStream; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.CompletableFuture; @@ -14,6 +13,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.client.KubernetesClient; @@ -25,7 +25,6 @@ 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.EventSourceInitializer; import io.javaoperatorsdk.operator.api.reconciler.Reconciler; import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; import io.javaoperatorsdk.operator.processing.event.ResourceID; @@ -35,7 +34,7 @@ @ControllerConfiguration public class WebappReconciler - implements Reconciler, Cleaner, EventSourceInitializer { + implements Reconciler, Cleaner { private static final Logger log = LoggerFactory.getLogger(WebappReconciler.class); @@ -46,7 +45,7 @@ public WebappReconciler(KubernetesClient kubernetesClient) { } @Override - public Map prepareEventSources(EventSourceContext context) { + public List prepareEventSources(EventSourceContext context) { /* * To create an event to a related WebApp resource and trigger the reconciliation we need to * find which WebApp this Tomcat custom resource is related to. To find the related @@ -66,8 +65,7 @@ public Map prepareEventSources(EventSourceContext c (Webapp primary) -> Set.of(new ResourceID(primary.getSpec().getTomcat(), primary.getMetadata().getNamespace()))) .build(); - return EventSourceInitializer - .nameEventSources(new InformerEventSource<>(configuration, context)); + return List.of(new InformerEventSource<>(configuration, context)); } /** @@ -101,12 +99,7 @@ public UpdateControl reconcile(Webapp webapp, Context context) { String[] commandStatusInAllPods = executeCommandInAllPods(kubernetesClient, webapp, command); - if (webapp.getStatus() == null) { - webapp.setStatus(new WebappStatus()); - } - webapp.getStatus().setDeployedArtifact(webapp.getSpec().getUrl()); - webapp.getStatus().setDeploymentStatus(commandStatusInAllPods); - return UpdateControl.patchStatus(webapp); + return UpdateControl.patchStatus(createWebAppForStatusUpdate(webapp, commandStatusInAllPods)); } else { log.info("WebappController invoked but Tomcat not ready yet ({}/{})", tomcat.getStatus() != null ? tomcat.getStatus().getReadyReplicas() : 0, @@ -115,6 +108,18 @@ public UpdateControl reconcile(Webapp webapp, Context context) { } } + private Webapp createWebAppForStatusUpdate(Webapp actual, String[] commandStatusInAllPods) { + var webapp = new Webapp(); + webapp.setMetadata(new ObjectMetaBuilder() + .withName(actual.getMetadata().getName()) + .withNamespace(actual.getMetadata().getNamespace()) + .build()); + webapp.setStatus(new WebappStatus()); + webapp.getStatus().setDeployedArtifact(actual.getSpec().getUrl()); + webapp.getStatus().setDeploymentStatus(commandStatusInAllPods); + return webapp; + } + @Override public DeleteControl cleanup(Webapp webapp, Context context) { @@ -157,14 +162,14 @@ private String[] executeCommandInAllPods( CompletableFuture data = new CompletableFuture<>(); try (ExecWatch execWatch = execCmd(pod, data, command)) { - status[i] = "" + pod.getMetadata().getName() + ":" + data.get(30, TimeUnit.SECONDS); + status[i] = pod.getMetadata().getName() + ":" + data.get(30, TimeUnit.SECONDS); } catch (ExecutionException e) { - status[i] = "" + pod.getMetadata().getName() + ": ExecutionException - " + e.getMessage(); + status[i] = pod.getMetadata().getName() + ": ExecutionException - " + e.getMessage(); } catch (InterruptedException e) { status[i] = - "" + pod.getMetadata().getName() + ": InterruptedException - " + e.getMessage(); + pod.getMetadata().getName() + ": InterruptedException - " + e.getMessage(); } catch (TimeoutException e) { - status[i] = "" + pod.getMetadata().getName() + ": TimeoutException - " + e.getMessage(); + status[i] = pod.getMetadata().getName() + ": TimeoutException - " + e.getMessage(); } } } diff --git a/sample-operators/webpage/pom.xml b/sample-operators/webpage/pom.xml index 0f43b5b2e6..5470ece6d3 100644 --- a/sample-operators/webpage/pom.xml +++ b/sample-operators/webpage/pom.xml @@ -1,89 +1,86 @@ - - 4.0.0 + + 4.0.0 - - io.javaoperatorsdk - sample-operators - 4.9.1-SNAPSHOT - - - sample-webpage-operator - Operator SDK - Samples - WebPage - Provisions an nginx Webserver based on a CRD with give html - jar - - - 11 - 11 - 3.4.2 - + + io.javaoperatorsdk + sample-operators + 5.0.0-SNAPSHOT + - - - - io.javaoperatorsdk - operator-framework-bom - ${project.version} - pom - import - - - + sample-webpage-operator + jar + Operator SDK - Samples - WebPage + Provisions an nginx Webserver based on a CRD with give html + - - io.javaoperatorsdk - operator-framework - - - org.apache.logging.log4j - log4j-slf4j-impl - - - org.takes - takes - 1.24.4 - - - io.fabric8 - crd-generator-apt - provided - - - org.awaitility - awaitility - compile - - - io.javaoperatorsdk - operator-framework-junit-5 - test - + + io.javaoperatorsdk + operator-framework-bom + ${project.version} + pom + import + - - - - com.google.cloud.tools - jib-maven-plugin - ${jib-maven-plugin.version} - - - gcr.io/distroless/java:11 - - - webpage-operator - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.12.1 - - - + + + + + io.javaoperatorsdk + operator-framework + + + org.apache.logging.log4j + log4j-slf4j-impl + + + org.apache.logging.log4j + log4j-core + compile + + + org.takes + takes + 1.24.4 + + + io.fabric8 + crd-generator-apt + provided + + + org.awaitility + awaitility + compile + + + io.javaoperatorsdk + operator-framework-junit-5 + test + + + + + + com.google.cloud.tools + jib-maven-plugin + ${jib-maven-plugin.version} + + + gcr.io/distroless/java17-debian11 + + + webpage-operator + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.12.1 + + + - \ No newline at end of file + diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/Utils.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/Utils.java index b37b98aa52..72d04b42ed 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/Utils.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/Utils.java @@ -1,5 +1,6 @@ package io.javaoperatorsdk.operator.sample; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusUpdateControl; import io.javaoperatorsdk.operator.sample.customresource.WebPage; @@ -11,6 +12,16 @@ public class Utils { private Utils() {} + public static WebPage createWebPageForStatusUpdate(WebPage webPage, String configMapName) { + WebPage res = new WebPage(); + res.setMetadata(new ObjectMetaBuilder() + .withName(webPage.getMetadata().getName()) + .withNamespace(webPage.getMetadata().getNamespace()) + .build()); + res.setStatus(createStatus(configMapName)); + return res; + } + public static WebPageStatus createStatus(String configMapName) { WebPageStatus status = new WebPageStatus(); status.setHtmlConfigMap(configMapName); @@ -33,7 +44,7 @@ public static String serviceName(WebPage webPage) { public static ErrorStatusUpdateControl handleError(WebPage resource, Exception e) { resource.getStatus().setErrorMessage("Error: " + e.getMessage()); - return ErrorStatusUpdateControl.updateStatus(resource); + return ErrorStatusUpdateControl.patchStatus(resource); } public static void simulateErrorIfRequested(WebPage webPage) throws ErrorSimulationException { diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageDependentsWorkflowReconciler.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageDependentsWorkflowReconciler.java index 8494af5402..f9664760f5 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageDependentsWorkflowReconciler.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageDependentsWorkflowReconciler.java @@ -1,7 +1,7 @@ package io.javaoperatorsdk.operator.sample; import java.util.Arrays; -import java.util.Map; +import java.util.List; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.Service; @@ -26,7 +26,7 @@ labelSelector = WebPageDependentsWorkflowReconciler.DEPENDENT_RESOURCE_LABEL_SELECTOR) @SuppressWarnings("unused") public class WebPageDependentsWorkflowReconciler - implements Reconciler, ErrorStatusHandler, EventSourceInitializer { + implements Reconciler, ErrorStatusHandler { public static final String DEPENDENT_RESOURCE_LABEL_SELECTOR = "!low-level"; @@ -48,8 +48,8 @@ public WebPageDependentsWorkflowReconciler(KubernetesClient kubernetesClient) { } @Override - public Map prepareEventSources(EventSourceContext context) { - return EventSourceInitializer.nameEventSourcesFromDependentResource(context, configMapDR, + public List prepareEventSources(EventSourceContext context) { + return EventSourceUtils.dependentEventSources(context, configMapDR, deploymentDR, serviceDR, ingressDR); } @@ -61,10 +61,10 @@ public UpdateControl reconcile(WebPage webPage, Context contex workflow.reconcile(webPage, context); - webPage.setStatus( - createStatus( - context.getSecondaryResource(ConfigMap.class).orElseThrow().getMetadata().getName())); - return UpdateControl.patchStatus(webPage); + return UpdateControl + .patchStatus( + createWebPageForStatusUpdate(webPage, context.getSecondaryResource(ConfigMap.class) + .orElseThrow().getMetadata().getName())); } @Override diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageManagedDependentsReconciler.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageManagedDependentsReconciler.java index d370cd3315..32811251d7 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageManagedDependentsReconciler.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageManagedDependentsReconciler.java @@ -6,21 +6,19 @@ import io.javaoperatorsdk.operator.sample.customresource.WebPage; import io.javaoperatorsdk.operator.sample.dependentresource.*; -import static io.javaoperatorsdk.operator.sample.Utils.createStatus; -import static io.javaoperatorsdk.operator.sample.Utils.handleError; -import static io.javaoperatorsdk.operator.sample.Utils.simulateErrorIfRequested; +import static io.javaoperatorsdk.operator.sample.Utils.*; /** * Shows how to implement a reconciler with managed dependent resources. */ -@ControllerConfiguration( - dependents = { - @Dependent(type = ConfigMapDependentResource.class), - @Dependent(type = DeploymentDependentResource.class), - @Dependent(type = ServiceDependentResource.class), - @Dependent(type = IngressDependentResource.class, - reconcilePrecondition = ExposedIngressCondition.class) - }) +@Workflow(dependents = { + @Dependent(type = ConfigMapDependentResource.class), + @Dependent(type = DeploymentDependentResource.class), + @Dependent(type = ServiceDependentResource.class), + @Dependent(type = IngressDependentResource.class, + reconcilePrecondition = ExposedIngressCondition.class) +}) +@ControllerConfiguration public class WebPageManagedDependentsReconciler implements Reconciler, ErrorStatusHandler, Cleaner { @@ -39,8 +37,7 @@ public UpdateControl reconcile(WebPage webPage, Context contex final var name = context.getSecondaryResource(ConfigMap.class).orElseThrow() .getMetadata().getName(); - webPage.setStatus(createStatus(name)); - return UpdateControl.patchStatus(webPage); + return UpdateControl.patchStatus(createWebPageForStatusUpdate(webPage, name)); } @Override diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageOperator.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageOperator.java index e3ab63dc54..ff80cc5901 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageOperator.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageOperator.java @@ -6,8 +6,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.KubernetesClientBuilder; import io.javaoperatorsdk.operator.Operator; import io.javaoperatorsdk.operator.sample.probes.LivenessHandler; import io.javaoperatorsdk.operator.sample.probes.StartupHandler; @@ -28,11 +26,10 @@ public class WebPageOperator { public static void main(String[] args) throws IOException { log.info("WebServer Operator starting!"); - KubernetesClient client = new KubernetesClientBuilder().build(); - Operator operator = new Operator(client, o -> o.withStopOnInformerErrorDuringStartup(false)); + Operator operator = new Operator(o -> o.withStopOnInformerErrorDuringStartup(false)); String reconcilerEnvVar = System.getenv(WEBPAGE_RECONCILER_ENV); if (WEBPAGE_CLASSIC_RECONCILER_ENV_VALUE.equals(reconcilerEnvVar)) { - operator.register(new WebPageReconciler(client)); + operator.register(new WebPageReconciler()); } else if (WEBPAGE_MANAGED_DEPENDENT_RESOURCE_ENV_VALUE .equals(reconcilerEnvVar)) { operator.register(new WebPageManagedDependentsReconciler()); diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java index 4be2da11c7..6669d3a1f5 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java @@ -1,6 +1,7 @@ package io.javaoperatorsdk.operator.sample; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; @@ -8,59 +9,37 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.fabric8.kubernetes.api.model.ConfigMap; -import io.fabric8.kubernetes.api.model.ConfigMapBuilder; -import io.fabric8.kubernetes.api.model.ConfigMapVolumeSourceBuilder; -import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; -import io.fabric8.kubernetes.api.model.Service; +import io.fabric8.kubernetes.api.model.*; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; -import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.dsl.Replaceable; import io.javaoperatorsdk.operator.ReconcilerUtils; import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; +import io.javaoperatorsdk.operator.api.reconciler.*; import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; -import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusHandler; -import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusUpdateControl; -import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; -import io.javaoperatorsdk.operator.api.reconciler.EventSourceInitializer; -import io.javaoperatorsdk.operator.api.reconciler.Reconciler; -import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; import io.javaoperatorsdk.operator.processing.event.rate.RateLimited; import io.javaoperatorsdk.operator.processing.event.source.EventSource; import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; import io.javaoperatorsdk.operator.sample.customresource.WebPage; -import static io.javaoperatorsdk.operator.sample.Utils.configMapName; -import static io.javaoperatorsdk.operator.sample.Utils.createStatus; -import static io.javaoperatorsdk.operator.sample.Utils.deploymentName; -import static io.javaoperatorsdk.operator.sample.Utils.handleError; -import static io.javaoperatorsdk.operator.sample.Utils.isValidHtml; -import static io.javaoperatorsdk.operator.sample.Utils.makeDesiredIngress; -import static io.javaoperatorsdk.operator.sample.Utils.serviceName; -import static io.javaoperatorsdk.operator.sample.Utils.setInvalidHtmlErrorMessage; -import static io.javaoperatorsdk.operator.sample.Utils.simulateErrorIfRequested; +import static io.javaoperatorsdk.operator.sample.Utils.*; import static io.javaoperatorsdk.operator.sample.WebPageManagedDependentsReconciler.SELECTOR; /** Shows how to implement reconciler using the low level api directly. */ @RateLimited(maxReconciliations = 2, within = 3) @ControllerConfiguration public class WebPageReconciler - implements Reconciler, ErrorStatusHandler, EventSourceInitializer { + implements Reconciler, ErrorStatusHandler { public static final String INDEX_HTML = "index.html"; private static final Logger log = LoggerFactory.getLogger(WebPageReconciler.class); - private final KubernetesClient kubernetesClient; + public WebPageReconciler() { - public WebPageReconciler(KubernetesClient kubernetesClient) { - this.kubernetesClient = kubernetesClient; } @Override - public Map prepareEventSources(EventSourceContext context) { + public List prepareEventSources(EventSourceContext context) { var configMapEventSource = new InformerEventSource<>(InformerConfiguration.from(ConfigMap.class, context) .withLabelSelector(SELECTOR) @@ -77,7 +56,7 @@ public Map prepareEventSources(EventSourceContext new InformerEventSource<>(InformerConfiguration.from(Ingress.class, context) .withLabelSelector(SELECTOR) .build(), context); - return EventSourceInitializer.nameEventSources(configMapEventSource, deploymentEventSource, + return List.of(configMapEventSource, deploymentEventSource, serviceEventSource, ingressEventSource); } @@ -107,8 +86,8 @@ public UpdateControl reconcile(WebPage webPage, Context contex "Creating or updating ConfigMap {} in {}", desiredHtmlConfigMap.getMetadata().getName(), ns); - kubernetesClient.configMaps().inNamespace(ns).resource(desiredHtmlConfigMap) - .createOr(Replaceable::update); + context.getClient().configMaps().inNamespace(ns).resource(desiredHtmlConfigMap) + .serverSideApply(); } var existingDeployment = context.getSecondaryResource(Deployment.class).orElse(null); @@ -117,8 +96,8 @@ public UpdateControl reconcile(WebPage webPage, Context contex "Creating or updating Deployment {} in {}", desiredDeployment.getMetadata().getName(), ns); - kubernetesClient.apps().deployments().inNamespace(ns).resource(desiredDeployment) - .createOr(Replaceable::update); + context.getClient().apps().deployments().inNamespace(ns).resource(desiredDeployment) + .serverSideApply(); } var existingService = context.getSecondaryResource(Service.class).orElse(null); @@ -127,19 +106,19 @@ public UpdateControl reconcile(WebPage webPage, Context contex "Creating or updating Deployment {} in {}", desiredDeployment.getMetadata().getName(), ns); - kubernetesClient.services().inNamespace(ns).resource(desiredService) - .createOr(Replaceable::update); + context.getClient().services().inNamespace(ns).resource(desiredService) + .serverSideApply(); } var existingIngress = context.getSecondaryResource(Ingress.class); if (Boolean.TRUE.equals(webPage.getSpec().getExposed())) { var desiredIngress = makeDesiredIngress(webPage); if (existingIngress.isEmpty() || !match(desiredIngress, existingIngress.get())) { - kubernetesClient.resource(desiredIngress).inNamespace(ns).createOr(Replaceable::update); + context.getClient().resource(desiredIngress).inNamespace(ns).serverSideApply(); } } else existingIngress.ifPresent( - ingress -> kubernetesClient.resource(ingress).delete()); + ingress -> context.getClient().resource(ingress).delete()); // not that this is not necessary, eventually mounted config map would be updated, just this way // is much faster; what is handy for demo purposes. @@ -148,10 +127,11 @@ public UpdateControl reconcile(WebPage webPage, Context contex previousConfigMap.getData().get(INDEX_HTML), desiredHtmlConfigMap.getData().get(INDEX_HTML))) { log.info("Restarting pods because HTML has changed in {}", ns); - kubernetesClient.pods().inNamespace(ns).withLabel("app", deploymentName(webPage)).delete(); + context.getClient().pods().inNamespace(ns).withLabel("app", deploymentName(webPage)).delete(); } - webPage.setStatus(createStatus(desiredHtmlConfigMap.getMetadata().getName())); - return UpdateControl.patchStatus(webPage); + + return UpdateControl.patchStatus( + createWebPageForStatusUpdate(webPage, desiredHtmlConfigMap.getMetadata().getName())); } private boolean match(Ingress desiredIngress, Ingress existingIngress) { diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStandaloneDependentsReconciler.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStandaloneDependentsReconciler.java index 1799d72cea..66f853e841 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStandaloneDependentsReconciler.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStandaloneDependentsReconciler.java @@ -1,55 +1,72 @@ package io.javaoperatorsdk.operator.sample; import java.util.Arrays; -import java.util.Map; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import java.util.List; import io.fabric8.kubernetes.api.model.ConfigMap; -import io.javaoperatorsdk.operator.api.reconciler.*; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; +import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusHandler; +import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusUpdateControl; +import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; +import io.javaoperatorsdk.operator.api.reconciler.EventSourceUtils; +import io.javaoperatorsdk.operator.api.reconciler.Reconciler; +import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfigBuilder; import io.javaoperatorsdk.operator.processing.dependent.workflow.Workflow; import io.javaoperatorsdk.operator.processing.dependent.workflow.WorkflowBuilder; import io.javaoperatorsdk.operator.processing.event.source.EventSource; import io.javaoperatorsdk.operator.sample.customresource.WebPage; -import io.javaoperatorsdk.operator.sample.dependentresource.*; +import io.javaoperatorsdk.operator.sample.dependentresource.ConfigMapDependentResource; +import io.javaoperatorsdk.operator.sample.dependentresource.DeploymentDependentResource; +import io.javaoperatorsdk.operator.sample.dependentresource.ExposedIngressCondition; +import io.javaoperatorsdk.operator.sample.dependentresource.IngressDependentResource; +import io.javaoperatorsdk.operator.sample.dependentresource.ServiceDependentResource; import static io.javaoperatorsdk.operator.sample.Utils.*; import static io.javaoperatorsdk.operator.sample.WebPageManagedDependentsReconciler.SELECTOR; /** - * Shows how to implement reconciler using standalone dependent resources. + * Shows how to implement reconciler using standalone dependent resources and workflows. */ @ControllerConfiguration public class WebPageStandaloneDependentsReconciler - implements Reconciler, ErrorStatusHandler, EventSourceInitializer { - - private static final Logger log = - LoggerFactory.getLogger(WebPageStandaloneDependentsReconciler.class); + implements Reconciler, ErrorStatusHandler { - private Workflow workflow; + private final Workflow workflow; public WebPageStandaloneDependentsReconciler() { + // initialize the workflow workflow = createDependentResourcesAndWorkflow(); } @Override - public Map prepareEventSources(EventSourceContext context) { - return EventSourceInitializer.eventSourcesFromWorkflow(context, workflow); + public List prepareEventSources(EventSourceContext context) { + // initializes the dependents' event sources from the given context + return EventSourceUtils.eventSourcesFromWorkflow(context, workflow); } @Override public UpdateControl reconcile(WebPage webPage, Context context) throws Exception { + // for testing purposes simulateErrorIfRequested(webPage); + // validate the html page and update the status with an error message if it isn't valid if (!isValidHtml(webPage)) { return UpdateControl.patchStatus(setInvalidHtmlErrorMessage(webPage)); } + // Explicitly reconcile the dependent resources. + // Calling the workflow reconciliation explicitly allows control over the workflow customization + // but also *when* dependents are reconciled (as opposed to before the main reconciler's + // reconcile method in the managed case). + // With the default configuration, this will throw an exception if one of the dependents + // couldn't be properly reconciled workflow.reconcile(webPage, context); + // retrieve the name of the ConfigMap secondary resource to update the status if everything went + // well webPage.setStatus( createStatus( context.getSecondaryResource(ConfigMap.class).orElseThrow().getMetadata().getName())); @@ -62,25 +79,38 @@ public ErrorStatusUpdateControl updateErrorStatus( return handleError(resource, e); } + /** + * Initializes the dependent resources and connect them in the context of a {@link Workflow} + * + * @return the {@link Workflow} that will reconcile automatically secondary resources + */ @SuppressWarnings({"unchecked", "rawtypes"}) private Workflow createDependentResourcesAndWorkflow() { + // create the dependent resources var configMapDR = new ConfigMapDependentResource(); var deploymentDR = new DeploymentDependentResource(); var serviceDR = new ServiceDependentResource(); var ingressDR = new IngressDependentResource(); + + // configure them with our label selector Arrays.asList(configMapDR, deploymentDR, serviceDR, ingressDR) .forEach(dr -> dr.configureWith(new KubernetesDependentResourceConfigBuilder() .withLabelSelector(SELECTOR + "=true").build())); + // connect the dependent resources into a workflow, configuring them as we go + // Note the method call order is significant and configuration applies to the dependent being + // configured as defined by the method call order (in this example, the reconcile pre-condition + // that is added applies to the Ingress dependent) return new WorkflowBuilder() .addDependentResource(configMapDR) .addDependentResource(deploymentDR) .addDependentResource(serviceDR) .addDependentResource(ingressDR) + // prevent the Ingress from being created based on the linked condition (here: only if the + // `exposed` flag is set in the primary resource), delete the Ingress if it already exists + // and the condition becomes false .withReconcilePrecondition(new ExposedIngressCondition()) .build(); } - - } diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/customresource/WebPageStatus.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/customresource/WebPageStatus.java index 7ab20c76be..36409ac7f9 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/customresource/WebPageStatus.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/customresource/WebPageStatus.java @@ -1,8 +1,6 @@ package io.javaoperatorsdk.operator.sample.customresource; -import io.javaoperatorsdk.operator.api.ObservedGenerationAwareStatus; - -public class WebPageStatus extends ObservedGenerationAwareStatus { +public class WebPageStatus { private String htmlConfigMap; diff --git a/sample-operators/webpage/src/test/java/io/javaoperatorsdk/operator/sample/WebPageOperatorAbstractTest.java b/sample-operators/webpage/src/test/java/io/javaoperatorsdk/operator/sample/WebPageOperatorAbstractTest.java index 040b3b2f8f..6c9a5512bb 100644 --- a/sample-operators/webpage/src/test/java/io/javaoperatorsdk/operator/sample/WebPageOperatorAbstractTest.java +++ b/sample-operators/webpage/src/test/java/io/javaoperatorsdk/operator/sample/WebPageOperatorAbstractTest.java @@ -37,7 +37,7 @@ public abstract class WebPageOperatorAbstractTest { public static final String TEST_PAGE = "test-page"; public static final String TITLE1 = "Hello Operator World"; public static final String TITLE2 = "Hello Operator World Title 2"; - public static final int WAIT_SECONDS = 20; + public static final int WAIT_SECONDS = 360; public static final int LONG_WAIT_SECONDS = 120; public static final Duration POLL_INTERVAL = Duration.ofSeconds(1); diff --git a/sample-operators/webpage/src/test/java/io/javaoperatorsdk/operator/sample/WebPageOperatorE2E.java b/sample-operators/webpage/src/test/java/io/javaoperatorsdk/operator/sample/WebPageOperatorE2E.java index 03c3d2c2e0..45fc4cd5ba 100644 --- a/sample-operators/webpage/src/test/java/io/javaoperatorsdk/operator/sample/WebPageOperatorE2E.java +++ b/sample-operators/webpage/src/test/java/io/javaoperatorsdk/operator/sample/WebPageOperatorE2E.java @@ -27,7 +27,7 @@ public WebPageOperatorE2E() throws FileNotFoundException {} isLocal() ? LocallyRunOperatorExtension.builder() .waitForNamespaceDeletion(false) - .withReconciler(new WebPageReconciler(client)) + .withReconciler(new WebPageReconciler()) .build() : ClusterDeployedOperatorExtension.builder() .waitForNamespaceDeletion(false)