From 3fe0f8e6f13aae17179aea7cbe9580bac1712fb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Tue, 12 Sep 2023 17:00:28 +0200 Subject: [PATCH] feat: sanitization of resources for matching (#2042) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../api/config/ConfigurationService.java | 21 ++++++- .../config/ConfigurationServiceOverrider.java | 14 +++++ .../kubernetes/DesiredResourceSanitizer.java | 47 +++++++++++++++ .../KubernetesDependentResource.java | 30 +++++++--- ...ernetesDependentResourceConfigBuilder.java | 6 +- ...BasedGenericKubernetesResourceMatcher.java | 11 +--- .../operator/DependentSSAMigrationIT.java | 8 +-- .../StatefulSetDesiredSanitizerIT.java | 57 +++++++++++++++++++ .../dependentssa/DependentSSAReconciler.java | 45 ++++++++++++++- .../dependentssa/SSAConfigMapDependent.java | 4 +- .../ServiceDependentResource.java | 4 +- .../ServiceDependentResource.java | 8 +-- ...efulSetDesiredSanitizerCustomResource.java | 14 +++++ ...lSetDesiredSanitizerDependentResource.java | 43 ++++++++++++++ ...StatefulSetDesiredSanitizerReconciler.java | 20 +++++++ .../StatefulSetDesiredSanitizerSpec.java | 15 +++++ .../statefulset.yaml | 35 ++++++++++++ .../ConfigMapDependentResource.java | 4 +- 18 files changed, 348 insertions(+), 38 deletions(-) create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/DesiredResourceSanitizer.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/StatefulSetDesiredSanitizerIT.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/statefulsetdesiredsanitizer/StatefulSetDesiredSanitizerCustomResource.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/statefulsetdesiredsanitizer/StatefulSetDesiredSanitizerDependentResource.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/statefulsetdesiredsanitizer/StatefulSetDesiredSanitizerReconciler.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/statefulsetdesiredsanitizer/StatefulSetDesiredSanitizerSpec.java create mode 100644 operator-framework/src/test/resources/io/javaoperatorsdk/operator/sample/statefulsetdesiredsanitizer/statefulset.yaml 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 2422f6ef74..86efb457c5 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 @@ -9,7 +9,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.ConfigBuilder; import io.fabric8.kubernetes.client.CustomResource; @@ -19,6 +21,7 @@ import io.javaoperatorsdk.operator.api.monitoring.Metrics; 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 static io.javaoperatorsdk.operator.api.config.ExecutorServiceManager.newThreadPoolExecutor; @@ -335,7 +338,7 @@ default ExecutorServiceManager getExecutorServiceManager() { * resources are created/updated and match was change to use * Server-Side * Apply (SSA) by default. - * + *

* SSA based create/update can be still used with the legacy matching, just overriding the match * method of Kubernetes Dependent Resource. * @@ -345,4 +348,20 @@ default boolean ssaBasedCreateUpdateMatchForDependentResources() { return true; } + /** + * Returns the set of default resources for which Server-Side Apply (SSA) will not be used, even + * if it is the default behavior for dependent resources as specified by + * {@link #ssaBasedCreateUpdateMatchForDependentResources()}. The exception to this is in the case + * where the use of SSA is explicitly enabled on the dependent resource directly using + * {@link KubernetesDependent#useSSA()}. + *

+ * By default, SSA is disabled for {@link ConfigMap} and {@link Secret} resources. + * + * @return The set of resource types for which SSA will not be used + * @since 4.4.0 + */ + default Set> defaultNonSSAResource() { + return Set.of(ConfigMap.class, Secret.class); + } + } 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 ebac41f640..15418aed5e 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 @@ -9,6 +9,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.client.KubernetesClient; import io.javaoperatorsdk.operator.api.monitoring.Metrics; @@ -35,6 +36,7 @@ public class ConfigurationServiceOverrider { private Duration cacheSyncTimeout; private ResourceClassResolver resourceClassResolver; private Boolean ssaBasedCreateUpdateMatchForDependentResources; + private Set> defaultNonSSAResource; ConfigurationServiceOverrider(ConfigurationService original) { this.original = original; @@ -150,6 +152,12 @@ public ConfigurationServiceOverrider withSSABasedCreateUpdateMatchForDependentRe return this; } + public ConfigurationServiceOverrider withDefaultNonSSAResource( + Set> defaultNonSSAResource) { + this.defaultNonSSAResource = defaultNonSSAResource; + return this; + } + public ConfigurationService build() { return new BaseConfigurationService(original.getVersion(), cloner, client) { @Override @@ -256,6 +264,12 @@ public boolean ssaBasedCreateUpdateMatchForDependentResources() { ? ssaBasedCreateUpdateMatchForDependentResources : super.ssaBasedCreateUpdateMatchForDependentResources(); } + + @Override + public Set> defaultNonSSAResource() { + return defaultNonSSAResource != null ? defaultNonSSAResource + : super.defaultNonSSAResource(); + } }; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/DesiredResourceSanitizer.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/DesiredResourceSanitizer.java new file mode 100644 index 0000000000..45b781af7a --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/DesiredResourceSanitizer.java @@ -0,0 +1,47 @@ +package io.javaoperatorsdk.operator.processing.dependent.kubernetes; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.PersistentVolumeClaimStatus; +import io.fabric8.kubernetes.api.model.Secret; +import io.fabric8.kubernetes.api.model.apps.StatefulSet; +import io.javaoperatorsdk.operator.api.reconciler.Context; + +public class DesiredResourceSanitizer { + + private DesiredResourceSanitizer() {} + + public static void sanitizeDesired(R desired, R actual, P primary, + Context

context, boolean useSSA) { + if (useSSA) { + if (desired instanceof StatefulSet) { + fillDefaultsOnVolumeClaimTemplate((StatefulSet) desired); + } + if (desired instanceof Secret) { + checkIfStringDataUsed((Secret) desired); + } + } + } + + private static void checkIfStringDataUsed(Secret secret) { + if (secret.getStringData() != null && !secret.getStringData().isEmpty()) { + throw new IllegalStateException( + "There is a known issue using StringData with SSA. Use data instead."); + } + } + + private static void fillDefaultsOnVolumeClaimTemplate(StatefulSet statefulSet) { + if (!statefulSet.getSpec().getVolumeClaimTemplates().isEmpty()) { + statefulSet.getSpec().getVolumeClaimTemplates().forEach(t -> { + if (t.getSpec().getVolumeMode() == null) { + t.getSpec().setVolumeMode("Filesystem"); + } + if (t.getStatus() == null) { + t.setStatus(new PersistentVolumeClaimStatus()); + } + if (t.getStatus().getPhase() == null) { + t.getStatus().setPhase("pending"); + } + }); + } + } +} 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 7fadcc6940..22ec5a48f9 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 @@ -103,18 +103,19 @@ public void configureWith(InformerEventSource informerEventSource) { } @SuppressWarnings("unused") - public R create(R target, P primary, Context

context) { + public R create(R desired, P primary, Context

context) { if (useSSA(context)) { // setting resource version for SSA so only created if it doesn't exist already var createIfNotExisting = kubernetesDependentResourceConfig == null ? KubernetesDependentResourceConfig.DEFAULT_CREATE_RESOURCE_ONLY_IF_NOT_EXISTING_WITH_SSA : kubernetesDependentResourceConfig.createResourceOnlyIfNotExistingWithSSA(); if (createIfNotExisting) { - target.getMetadata().setResourceVersion("1"); + desired.getMetadata().setResourceVersion("1"); } } - addMetadata(false, null, target, primary); - final var resource = prepare(target, primary, "Creating"); + addMetadata(false, null, desired, primary); + sanitizeDesired(desired, null, primary, context); + final var resource = prepare(desired, primary, "Creating"); return useSSA(context) ? resource .fieldManager(context.getControllerConfiguration().fieldManager()) @@ -123,19 +124,20 @@ public R create(R target, P primary, Context

context) { : resource.create(); } - public R update(R actual, R target, P primary, Context

context) { + public R update(R actual, R desired, P primary, Context

context) { if (log.isDebugEnabled()) { log.debug("Updating actual resource: {} version: {}", ResourceID.fromResource(actual), actual.getMetadata().getResourceVersion()); } R updatedResource; - addMetadata(false, actual, target, primary); + addMetadata(false, actual, desired, primary); + sanitizeDesired(desired, actual, primary, context); if (useSSA(context)) { - updatedResource = prepare(target, primary, "Updating") + updatedResource = prepare(desired, primary, "Updating") .fieldManager(context.getControllerConfiguration().fieldManager()) .forceConflicts().serverSideApply(); } else { - var updatedActual = updaterMatcher.updateResource(actual, target, context); + var updatedActual = updaterMatcher.updateResource(actual, desired, context); updatedResource = prepare(updatedActual, primary, "Updating").update(); } log.debug("Resource version after update: {}", @@ -146,6 +148,7 @@ public R update(R actual, R target, P primary, Context

context) { @Override public Result match(R actualResource, P primary, Context

context) { final var desired = desired(primary, context); + sanitizeDesired(desired, actualResource, primary, context); return match(actualResource, desired, primary, updaterMatcher, context); } @@ -189,9 +192,18 @@ protected void addMetadata(boolean forMatch, R actualResource, final R target, P addReferenceHandlingMetadata(target, primary); } - private boolean useSSA(Context

context) { + protected void sanitizeDesired(R desired, R actual, P primary, Context

context) { + DesiredResourceSanitizer.sanitizeDesired(desired, actual, primary, context, useSSA(context)); + } + + protected boolean useSSA(Context

context) { Optional useSSAConfig = configuration().flatMap(KubernetesDependentResourceConfig::useSSA); + var configService = context.getControllerConfiguration().getConfigurationService(); + // don't use SSA for certain resources by default, only if explicitly overriden + if (useSSAConfig.isEmpty() && configService.defaultNonSSAResource().contains(resourceType())) { + return false; + } return useSSAConfig.orElse(context.getControllerConfiguration().getConfigurationService() .ssaBasedCreateUpdateMatchForDependentResources()); } 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 8bdb8a47a5..a18d8b8a41 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 @@ -2,6 +2,7 @@ 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; @@ -10,7 +11,7 @@ public final class KubernetesDependentResourceConfigBuilder { - private Set namespaces; + private Set namespaces = Constants.SAME_AS_CONTROLLER_NAMESPACES_SET; private String labelSelector; private boolean createResourceOnlyIfNotExistingWithSSA; private ResourceDiscriminator resourceDiscriminator; @@ -77,7 +78,8 @@ public KubernetesDependentResourceConfigBuilder withGenericFilter( } public KubernetesDependentResourceConfig build() { - return new KubernetesDependentResourceConfig<>(namespaces, labelSelector, false, + return new KubernetesDependentResourceConfig<>(namespaces, labelSelector, + namespaces != Constants.SAME_AS_CONTROLLER_NAMESPACES_SET, createResourceOnlyIfNotExistingWithSSA, resourceDiscriminator, 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 f4718b45c3..442119a822 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,14 +1,7 @@ package io.javaoperatorsdk.operator.processing.dependent.kubernetes; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.Optional; -import java.util.Set; -import java.util.SortedMap; -import java.util.TreeMap; import java.util.stream.Collectors; import org.slf4j.Logger; @@ -159,7 +152,7 @@ private static void fillResultsAndTraverseFurther(Map result, Object managedFieldValue) { var emptyMapValue = new HashMap(); result.put(keyInActual, emptyMapValue); - var actualMapValue = actualMap.get(keyInActual); + var actualMapValue = actualMap.getOrDefault(keyInActual, Collections.emptyMap()); log.debug("key: {} actual map value: {} managedFieldValue: {}", keyInActual, actualMapValue, managedFieldValue); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/DependentSSAMigrationIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/DependentSSAMigrationIT.java index 7e226eca38..0eabc07c97 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/DependentSSAMigrationIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/DependentSSAMigrationIT.java @@ -144,9 +144,10 @@ private DependnetSSACustomResource reconcileWithLegacyOperator(Operator legacyOp private Operator createOperator(KubernetesClient client, boolean legacyDependentHandling, String fieldManager) { Operator operator = new Operator(client, - o -> o.withSSABasedCreateUpdateMatchForDependentResources(!legacyDependentHandling) - .withCloseClientOnStop(false)); - operator.register(new DependentSSAReconciler(), o -> { + o -> o.withCloseClientOnStop(false)); + var reconciler = new DependentSSAReconciler(!legacyDependentHandling); + reconciler.setKubernetesClient(client); + operator.register(reconciler, o -> { o.settingNamespace(namespace); if (fieldManager != null) { o.withFieldManager(fieldManager); @@ -155,7 +156,6 @@ private Operator createOperator(KubernetesClient client, boolean legacyDependent return operator; } - public DependnetSSACustomResource testResource() { DependnetSSACustomResource resource = new DependnetSSACustomResource(); resource.setMetadata(new ObjectMetaBuilder() diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/StatefulSetDesiredSanitizerIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/StatefulSetDesiredSanitizerIT.java new file mode 100644 index 0000000000..5313fb7dfb --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/StatefulSetDesiredSanitizerIT.java @@ -0,0 +1,57 @@ +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.ObjectMetaBuilder; +import io.fabric8.kubernetes.api.model.apps.StatefulSet; +import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; +import io.javaoperatorsdk.operator.sample.statefulsetdesiredsanitizer.StatefulSetDesiredSanitizerCustomResource; +import io.javaoperatorsdk.operator.sample.statefulsetdesiredsanitizer.StatefulSetDesiredSanitizerDependentResource; +import io.javaoperatorsdk.operator.sample.statefulsetdesiredsanitizer.StatefulSetDesiredSanitizerReconciler; +import io.javaoperatorsdk.operator.sample.statefulsetdesiredsanitizer.StatefulSetDesiredSanitizerSpec; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +public class StatefulSetDesiredSanitizerIT { + + public static final String TEST_1 = "test1"; + + @RegisterExtension + LocallyRunOperatorExtension extension = + LocallyRunOperatorExtension.builder() + .withReconciler(new StatefulSetDesiredSanitizerReconciler()) + .build(); + + @Test + void testSSAMatcher() { + var resource = extension.create(testResource()); + + await().pollDelay(Duration.ofMillis(200)).untilAsserted(() -> { + var statefulSet = extension.get(StatefulSet.class, TEST_1); + assertThat(statefulSet).isNotNull(); + }); + // make sure reconciliation happens at least once more + resource.getSpec().setValue("changed value"); + extension.replace(resource); + + await().untilAsserted( + () -> assertThat(StatefulSetDesiredSanitizerDependentResource.nonMatchedAtLeastOnce) + .isFalse()); + } + + StatefulSetDesiredSanitizerCustomResource testResource() { + var res = new StatefulSetDesiredSanitizerCustomResource(); + res.setMetadata(new ObjectMetaBuilder() + .withName(TEST_1) + .build()); + res.setSpec(new StatefulSetDesiredSanitizerSpec()); + res.getSpec().setValue("initial value"); + + return res; + } + +} 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 d5c48256f7..f84dfe4597 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,21 +1,43 @@ package io.javaoperatorsdk.operator.sample.dependentssa; +import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.client.KubernetesClient; import io.javaoperatorsdk.operator.api.reconciler.*; -import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; +import io.javaoperatorsdk.operator.junit.KubernetesClientAware; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfigBuilder; +import io.javaoperatorsdk.operator.processing.event.source.EventSource; import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider; -@ControllerConfiguration(dependents = {@Dependent(type = SSAConfigMapDependent.class)}) +@ControllerConfiguration public class DependentSSAReconciler - implements Reconciler, TestExecutionInfoProvider { + implements Reconciler, TestExecutionInfoProvider, + KubernetesClientAware, + EventSourceInitializer { private final AtomicInteger numberOfExecutions = new AtomicInteger(0); + private SSAConfigMapDependent ssaConfigMapDependent = new SSAConfigMapDependent(); + private KubernetesClient kubernetesClient; + + public DependentSSAReconciler() { + this(true); + } + + public DependentSSAReconciler(boolean useSSA) { + ssaConfigMapDependent.configureWith(new KubernetesDependentResourceConfigBuilder() + .withUseSSA(useSSA) + .build()); + } + @Override public UpdateControl reconcile( DependnetSSACustomResource resource, Context context) { + + ssaConfigMapDependent.reconcile(resource, context); numberOfExecutions.addAndGet(1); return UpdateControl.noUpdate(); } @@ -24,4 +46,21 @@ public int getNumberOfExecutions() { return numberOfExecutions.get(); } + @Override + public KubernetesClient getKubernetesClient() { + return kubernetesClient; + } + + @Override + public void setKubernetesClient(KubernetesClient kubernetesClient) { + this.kubernetesClient = kubernetesClient; + ssaConfigMapDependent.setKubernetesClient(kubernetesClient); + } + + @Override + public Map prepareEventSources( + EventSourceContext context) { + return EventSourceInitializer.nameEventSourcesFromDependentResource(context, + ssaConfigMapDependent); + } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentssa/SSAConfigMapDependent.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentssa/SSAConfigMapDependent.java index 70f0ddcf15..806eccd717 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentssa/SSAConfigMapDependent.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentssa/SSAConfigMapDependent.java @@ -33,10 +33,10 @@ protected ConfigMap desired(DependnetSSACustomResource primary, } @Override - public ConfigMap update(ConfigMap actual, ConfigMap target, + public ConfigMap update(ConfigMap actual, ConfigMap desired, DependnetSSACustomResource primary, Context context) { NUMBER_OF_UPDATES.incrementAndGet(); - return super.update(actual, target, primary, context); + return super.update(actual, desired, primary, context); } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/servicestrictmatcher/ServiceDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/servicestrictmatcher/ServiceDependentResource.java index 1079642b33..deddf20263 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/servicestrictmatcher/ServiceDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/servicestrictmatcher/ServiceDependentResource.java @@ -53,10 +53,10 @@ public Matcher.Result match(Service actualResource, } @Override - public Service update(Service actual, Service target, + public Service update(Service actual, Service desired, ServiceStrictMatcherTestCustomResource primary, Context context) { updated.addAndGet(1); - return super.update(actual, target, primary, context); + return super.update(actual, desired, primary, context); } } 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 5230404ceb..a1f5f6faf0 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 @@ -45,17 +45,17 @@ public Result match(Service actualResource, SSALegacyMatcherCustomResou // override just to check the exec count @Override - public Service update(Service actual, Service target, SSALegacyMatcherCustomResource primary, + public Service update(Service actual, Service desired, SSALegacyMatcherCustomResource primary, Context context) { createUpdateCount.addAndGet(1); - return super.update(actual, target, primary, context); + return super.update(actual, desired, primary, context); } // override just to check the exec count @Override - public Service create(Service target, SSALegacyMatcherCustomResource primary, + public Service create(Service desired, SSALegacyMatcherCustomResource primary, Context context) { createUpdateCount.addAndGet(1); - return super.create(target, primary, context); + return super.create(desired, primary, context); } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/statefulsetdesiredsanitizer/StatefulSetDesiredSanitizerCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/statefulsetdesiredsanitizer/StatefulSetDesiredSanitizerCustomResource.java new file mode 100644 index 0000000000..bf35304da0 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/statefulsetdesiredsanitizer/StatefulSetDesiredSanitizerCustomResource.java @@ -0,0 +1,14 @@ +package io.javaoperatorsdk.operator.sample.statefulsetdesiredsanitizer; + +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.Version; + +@Group("sample.javaoperatorsdk") +@Version("v1") +public class StatefulSetDesiredSanitizerCustomResource + extends CustomResource + implements Namespaced { + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/statefulsetdesiredsanitizer/StatefulSetDesiredSanitizerDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/statefulsetdesiredsanitizer/StatefulSetDesiredSanitizerDependentResource.java new file mode 100644 index 0000000000..12525c4c8d --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/statefulsetdesiredsanitizer/StatefulSetDesiredSanitizerDependentResource.java @@ -0,0 +1,43 @@ +package io.javaoperatorsdk.operator.sample.statefulsetdesiredsanitizer; + +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.fabric8.kubernetes.api.model.apps.StatefulSet; +import io.javaoperatorsdk.operator.ReconcilerUtils; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; + +public class StatefulSetDesiredSanitizerDependentResource + extends + CRUDKubernetesDependentResource { + + public static volatile Boolean nonMatchedAtLeastOnce; + + public StatefulSetDesiredSanitizerDependentResource() { + super(StatefulSet.class); + } + + @Override + protected StatefulSet desired(StatefulSetDesiredSanitizerCustomResource primary, + Context context) { + var template = + ReconcilerUtils.loadYaml(StatefulSet.class, getClass(), "statefulset.yaml"); + template.setMetadata(new ObjectMetaBuilder() + .withName(primary.getMetadata().getName()) + .withNamespace(primary.getMetadata().getNamespace()) + .build()); + return template; + } + + @Override + public Result match(StatefulSet actualResource, + StatefulSetDesiredSanitizerCustomResource primary, + Context context) { + var res = super.match(actualResource, primary, context); + if (!res.matched()) { + nonMatchedAtLeastOnce = true; + } else if (nonMatchedAtLeastOnce == null) { + nonMatchedAtLeastOnce = false; + } + return res; + } +} 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 new file mode 100644 index 0000000000..c884619227 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/statefulsetdesiredsanitizer/StatefulSetDesiredSanitizerReconciler.java @@ -0,0 +1,20 @@ +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.dependent.Dependent; + +@ControllerConfiguration( + dependents = {@Dependent(type = StatefulSetDesiredSanitizerDependentResource.class)}) +public class StatefulSetDesiredSanitizerReconciler + implements Reconciler { + + @Override + public UpdateControl reconcile( + StatefulSetDesiredSanitizerCustomResource resource, + Context context) throws Exception { + return UpdateControl.noUpdate(); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/statefulsetdesiredsanitizer/StatefulSetDesiredSanitizerSpec.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/statefulsetdesiredsanitizer/StatefulSetDesiredSanitizerSpec.java new file mode 100644 index 0000000000..3cca134108 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/statefulsetdesiredsanitizer/StatefulSetDesiredSanitizerSpec.java @@ -0,0 +1,15 @@ +package io.javaoperatorsdk.operator.sample.statefulsetdesiredsanitizer; + +public class StatefulSetDesiredSanitizerSpec { + + private String value; + + public String getValue() { + return value; + } + + public StatefulSetDesiredSanitizerSpec setValue(String value) { + this.value = value; + return this; + } +} diff --git a/operator-framework/src/test/resources/io/javaoperatorsdk/operator/sample/statefulsetdesiredsanitizer/statefulset.yaml b/operator-framework/src/test/resources/io/javaoperatorsdk/operator/sample/statefulsetdesiredsanitizer/statefulset.yaml new file mode 100644 index 0000000000..f40fbeb607 --- /dev/null +++ b/operator-framework/src/test/resources/io/javaoperatorsdk/operator/sample/statefulsetdesiredsanitizer/statefulset.yaml @@ -0,0 +1,35 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: "" +spec: + selector: + matchLabels: + app: nginx # has to match .spec.template.metadata.labels + serviceName: "nginx" + replicas: 1 + minReadySeconds: 10 # by default is 0 + template: + metadata: + labels: + app: nginx # has to match .spec.selector.matchLabels + spec: + terminationGracePeriodSeconds: 10 + containers: + - name: nginx + image: registry.k8s.io/nginx-slim:0.8 + ports: + - containerPort: 80 + name: web + volumeMounts: + - name: www + mountPath: /usr/share/nginx/html + volumeClaimTemplates: + - metadata: + name: www + spec: + accessModes: [ "ReadWriteOnce" ] + storageClassName: "my-storage-class" + resources: + requests: + storage: 1Gi \ No newline at end of file diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/dependentresource/ConfigMapDependentResource.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/dependentresource/ConfigMapDependentResource.java index cc7b15146b..ea1731041f 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/dependentresource/ConfigMapDependentResource.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/dependentresource/ConfigMapDependentResource.java @@ -47,9 +47,9 @@ protected ConfigMap desired(WebPage webPage, Context context) { } @Override - public ConfigMap update(ConfigMap actual, ConfigMap target, WebPage primary, + public ConfigMap update(ConfigMap actual, ConfigMap desired, WebPage primary, Context context) { - var res = super.update(actual, target, primary, context); + var res = super.update(actual, desired, primary, context); var ns = actual.getMetadata().getNamespace(); log.info("Restarting pods because HTML has changed in {}", ns);