diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/AggregatedOperatorException.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/AggregatedOperatorException.java new file mode 100644 index 0000000000..2e1247dd8f --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/AggregatedOperatorException.java @@ -0,0 +1,23 @@ +package io.javaoperatorsdk.operator; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class AggregatedOperatorException extends OperatorException { + private final List causes; + + public AggregatedOperatorException(String message, Exception... exceptions) { + super(message); + this.causes = exceptions != null ? Arrays.asList(exceptions) : Collections.emptyList(); + } + + public AggregatedOperatorException(String message, List exceptions) { + super(message); + this.causes = exceptions != null ? exceptions : Collections.emptyList(); + } + + public List getAggregatedExceptions() { + return causes; + } +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java index 0f2c442cef..dc9c684091 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java @@ -1,9 +1,9 @@ package io.javaoperatorsdk.operator.api.config; import java.time.Duration; -import java.util.ArrayList; import java.util.Collections; -import java.util.List; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Function; @@ -26,7 +26,7 @@ public class AnnotationControllerConfiguration protected final Reconciler reconciler; private final ControllerConfiguration annotation; - private List> specs; + private Map> specs; public AnnotationControllerConfiguration(Reconciler reconciler) { this.reconciler = reconciler; @@ -135,15 +135,15 @@ public static T valueOrDefault( @SuppressWarnings({"rawtypes", "unchecked"}) @Override - public List> getDependentResources() { + public Map> getDependentResources() { if (specs == null) { final var dependents = valueOrDefault(annotation, ControllerConfiguration::dependents, new Dependent[] {}); if (dependents.length == 0) { - return Collections.emptyList(); + return Collections.emptyMap(); } - specs = new ArrayList<>(dependents.length); + specs = new HashMap<>(dependents.length); for (Dependent dependent : dependents) { Object config = null; final Class dependentType = dependent.type(); @@ -159,9 +159,18 @@ public static T valueOrDefault( config = new KubernetesDependentResourceConfig(namespaces, labelSelector); } - specs.add(new DependentResourceSpec(dependentType, config)); + var name = dependent.name(); + if (name.isBlank()) { + name = DependentResource.defaultNameFor(dependentType); + } + final DependentResourceSpec spec = specs.get(name); + if (spec != null) { + throw new IllegalArgumentException( + "A DependentResource named: " + name + " already exists: " + spec); + } + specs.put(name, new DependentResourceSpec(dependentType, config, name)); } } - return specs; + return Collections.unmodifiableMap(specs); } } 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 aff1d04e8b..5702dee0f3 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 @@ -2,7 +2,7 @@ import java.time.Duration; import java.util.Collections; -import java.util.List; +import java.util.Map; import java.util.Optional; import io.fabric8.kubernetes.api.model.HasMetadata; @@ -46,8 +46,8 @@ default ResourceEventFilter getEventFilter() { return ResourceEventFilters.passthrough(); } - default List> getDependentResources() { - return Collections.emptyList(); + default Map> getDependentResources() { + return Collections.emptyMap(); } default Optional reconciliationMaxInterval() { 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 da0b6d0a52..400d043300 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 @@ -1,14 +1,14 @@ package io.javaoperatorsdk.operator.api.config; import java.time.Duration; +import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Optional; +import java.util.Map; import java.util.Set; import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec; -import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter; @SuppressWarnings({"rawtypes", "unchecked", "unused"}) @@ -22,7 +22,7 @@ public class ControllerConfigurationOverrider { private ResourceEventFilter customResourcePredicate; private final ControllerConfiguration original; private Duration reconciliationMaxInterval; - private final List> dependentResourceSpecs; + private final Map> dependentResourceSpecs; private ControllerConfigurationOverrider(ControllerConfiguration original) { finalizer = original.getFinalizerName(); @@ -32,7 +32,8 @@ private ControllerConfigurationOverrider(ControllerConfiguration original) { labelSelector = original.getLabelSelector(); customResourcePredicate = original.getEventFilter(); reconciliationMaxInterval = original.reconciliationMaxInterval().orElse(null); - dependentResourceSpecs = original.getDependentResources(); + // make the original specs modifiable + dependentResourceSpecs = new HashMap<>(original.getDependentResources()); this.original = original; } @@ -89,37 +90,17 @@ public ControllerConfigurationOverrider withReconciliationMaxInterval( return this; } - public void replaceDependentResourceConfig( - Class> dependentResourceClass, + public ControllerConfigurationOverrider replacingNamedDependentResourceConfig(String name, Object dependentResourceConfig) { - - var currentConfig = - findConfigForDependentResourceClass(dependentResourceClass); - if (currentConfig.isEmpty()) { - throw new IllegalStateException("Cannot find DependentResource config for class: " - + dependentResourceClass); - } - dependentResourceSpecs.remove(currentConfig.get()); - dependentResourceSpecs - .add(new DependentResourceSpec(dependentResourceClass, dependentResourceConfig)); - } - - public void addNewDependentResourceConfig(DependentResourceSpec dependentResourceSpec) { - var currentConfig = - findConfigForDependentResourceClass(dependentResourceSpec.getDependentResourceClass()); - if (currentConfig.isPresent()) { - throw new IllegalStateException( - "Config already present for class: " - + dependentResourceSpec.getDependentResourceClass()); + final var currentConfig = dependentResourceSpecs.get(name); + if (currentConfig == null) { + throw new IllegalArgumentException("Cannot find a DependentResource named: " + name); } - dependentResourceSpecs.add(dependentResourceSpec); - } - - private Optional> findConfigForDependentResourceClass( - Class dependentResourceClass) { - return dependentResourceSpecs.stream() - .filter(dc -> dc.getDependentResourceClass().equals(dependentResourceClass)) - .findFirst(); + dependentResourceSpecs.remove(name); + dependentResourceSpecs.put(name, + new DependentResourceSpec(currentConfig.getDependentResourceClass(), + dependentResourceConfig, name)); + return this; } public ControllerConfiguration build() { diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultControllerConfiguration.java index 890254d830..721838a0ee 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultControllerConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultControllerConfiguration.java @@ -2,7 +2,7 @@ import java.time.Duration; import java.util.Collections; -import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; @@ -10,7 +10,6 @@ import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec; import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter; -@SuppressWarnings("rawtypes") public class DefaultControllerConfiguration extends DefaultResourceConfiguration implements ControllerConfiguration { @@ -22,7 +21,7 @@ public class DefaultControllerConfiguration private final boolean generationAware; private final RetryConfiguration retryConfiguration; private final ResourceEventFilter resourceEventFilter; - private final List> dependents; + private final Map> dependents; private final Duration reconciliationMaxInterval; // NOSONAR constructor is meant to provide all information @@ -38,7 +37,7 @@ public DefaultControllerConfiguration( ResourceEventFilter resourceEventFilter, Class resourceClass, Duration reconciliationMaxInterval, - List> dependents) { + Map> dependents) { super(labelSelector, resourceClass, namespaces); this.associatedControllerClassName = associatedControllerClassName; this.name = name; @@ -52,7 +51,7 @@ public DefaultControllerConfiguration( : retryConfiguration; this.resourceEventFilter = resourceEventFilter; - this.dependents = dependents != null ? dependents : Collections.emptyList(); + this.dependents = dependents != null ? dependents : Collections.emptyMap(); } @Override @@ -91,7 +90,7 @@ public ResourceEventFilter getEventFilter() { } @Override - public List> getDependentResources() { + public Map> getDependentResources() { return dependents; } 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 0684c04bef..40a7db53c9 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 @@ -1,5 +1,6 @@ package io.javaoperatorsdk.operator.api.config.dependent; +import java.util.Objects; import java.util.Optional; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; @@ -10,9 +11,13 @@ public class DependentResourceSpec, C> { private final C dependentResourceConfig; - public DependentResourceSpec(Class dependentResourceClass, C dependentResourceConfig) { + private final String name; + + public DependentResourceSpec(Class dependentResourceClass, C dependentResourceConfig, + String name) { this.dependentResourceClass = dependentResourceClass; this.dependentResourceConfig = dependentResourceConfig; + this.name = name; } public Class getDependentResourceClass() { @@ -22,4 +27,32 @@ public Class getDependentResourceClass() { public Optional getDependentResourceConfiguration() { return Optional.ofNullable(dependentResourceConfig); } + + public String getName() { + return name; + } + + @Override + public String toString() { + return "DependentResourceSpec{ name='" + name + + "', type=" + dependentResourceClass.getCanonicalName() + + ", config=" + dependentResourceConfig + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DependentResourceSpec that = (DependentResourceSpec) o; + return name.equals(that.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } } 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 0a7b36339d..e16e42f83e 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 @@ -20,8 +20,7 @@ public DefaultContext(RetryInfo retryInfo, Controller

controller, P primaryRe this.controller = controller; this.primaryResource = primaryResource; this.controllerConfiguration = controller.getConfiguration(); - this.managedDependentResourceContext = new ManagedDependentResourceContext( - controller.getDependents()); + this.managedDependentResourceContext = new ManagedDependentResourceContext(); } @Override 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 index 562ddc3b8f..38f21683d8 100644 --- 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 @@ -1,6 +1,6 @@ package io.javaoperatorsdk.operator.api.reconciler; -import java.util.List; +import java.util.Map; import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.processing.event.source.EventSource; @@ -20,6 +20,6 @@ public interface EventSourceInitializer

{ * sources * @return list of event sources to register */ - List prepareEventSources(EventSourceContext

context); + Map prepareEventSources(EventSourceContext

context); } 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 21f0b4f55b..e773f57ba3 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 @@ -1,6 +1,11 @@ package io.javaoperatorsdk.operator.api.reconciler.dependent; +import static io.javaoperatorsdk.operator.api.reconciler.Constants.EMPTY_STRING; + public @interface Dependent { + @SuppressWarnings("rawtypes") Class type(); + + String name() default EMPTY_STRING; } 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 1dcfbe57d5..761e3d2a64 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,8 +5,49 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.Context; +/** + * An interface to implement and provide dependent resource support. + * + * @param the dependent resource type + * @param

the associated primary resource type + */ public interface DependentResource { + + /** + * Reconciles the dependent resource given the desired primary state + * + * @param primary the primary resource for which we want to reconcile the dependent state + * @param context {@link Context} providing useful contextual information + * @return a {@link ReconcileResult} providing information about the reconciliation result + */ ReconcileResult reconcile(P primary, Context

context); + /** + * Retrieves the dependent resource associated with the specified primary one + * + * @param primaryResource the primary resource for which we want to retrieve the secondary + * resource + * @return an {@link Optional} containing the secondary resource or {@link Optional#empty()} if it + * doesn't exist + */ Optional getResource(P primaryResource); + + /** + * Retrieves the resource type associated with this DependentResource + * + * @return the resource type associated with this DependentResource + */ + Class resourceType(); + + /** + * Computes a default name for the specified DependentResource class + * + * @param dependentResourceClass the DependentResource class for which we want to compute a + * default name + * @return the default name for the specified DependentResource class + */ + @SuppressWarnings("rawtypes") + static String defaultNameFor(Class dependentResourceClass) { + return dependentResourceClass.getCanonicalName(); + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResourceFactory.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResourceFactory.java index ffd0ba496f..11b6e1f059 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResourceFactory.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResourceFactory.java @@ -7,12 +7,16 @@ public interface DependentResourceFactory { default > T createFrom(DependentResourceSpec spec) { + return createFrom(spec.getDependentResourceClass()); + } + + default > T createFrom(Class dependentResourceClass) { try { - return spec.getDependentResourceClass().getConstructor().newInstance(); + return dependentResourceClass.getConstructor().newInstance(); } catch (InstantiationException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { throw new IllegalArgumentException("Cannot instantiate DependentResource " - + spec.getDependentResourceClass().getCanonicalName(), e); + + dependentResourceClass.getCanonicalName(), e); } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/ReconcileResult.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/ReconcileResult.java index 86a931c480..c83da1c8ea 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/ReconcileResult.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/ReconcileResult.java @@ -2,6 +2,9 @@ import java.util.Optional; +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.processing.event.ResourceID; + public class ReconcileResult { private final R resource; @@ -19,6 +22,14 @@ public static ReconcileResult noOperation(T resource) { return new ReconcileResult<>(resource, Operation.NONE); } + @Override + public String toString() { + return getResource() + .map(r -> r instanceof HasMetadata ? ResourceID.fromResource((HasMetadata) r) : r) + .orElse("no resource") + + " -> " + operation; + } + private ReconcileResult(R resource, Operation operation) { this.resource = resource; this.operation = operation; diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/ResourceTypeAware.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/ResourceTypeAware.java deleted file mode 100644 index e83d39aebd..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/ResourceTypeAware.java +++ /dev/null @@ -1,6 +0,0 @@ -package io.javaoperatorsdk.operator.api.reconciler.dependent; - -public interface ResourceTypeAware { - - Class resourceType(); -} 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/ManagedDependentResourceContext.java index 745f1e3151..0d0b4c1412 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/ManagedDependentResourceContext.java @@ -1,13 +1,11 @@ package io.javaoperatorsdk.operator.api.reconciler.dependent.managed; -import java.util.Collections; -import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; -import io.javaoperatorsdk.operator.OperatorException; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; /** * Contextual information related to {@link DependentResource} either to retrieve the actual @@ -16,7 +14,7 @@ @SuppressWarnings("rawtypes") public class ManagedDependentResourceContext { - private final List dependentResources; + private final Map reconcileResults = new ConcurrentHashMap<>(); private final ConcurrentHashMap attributes = new ConcurrentHashMap(); /** @@ -64,49 +62,36 @@ public Optional put(Object key, Object value) { * @return the contextual object value associated with the specified key * @see #get(Object, Class) */ + @SuppressWarnings("unused") public T getMandatory(Object key, Class expectedType) { return get(key, expectedType).orElseThrow(() -> new IllegalStateException( "Mandatory attribute (key: " + key + ", type: " + expectedType.getName() + ") is missing or not of the expected type")); } - public ManagedDependentResourceContext(List dependentResources) { - this.dependentResources = Collections.unmodifiableList(dependentResources); - } - /** - * Retrieve all the known {@link DependentResource} implementations + * Retrieve the {@link ReconcileResult}, if it exists, associated with the + * {@link DependentResource} associated with the specified name * - * @return a list of known {@link DependentResource} implementations + * @param name the name of the {@link DependentResource} for which we want to retrieve a + * {@link ReconcileResult} + * @return an Optional containing the reconcile result or {@link Optional#empty()} if no such + * result is available */ - public List getDependentResources() { - return dependentResources; + @SuppressWarnings({"rawtypes", "unused"}) + public Optional getReconcileResult(String name) { + return Optional.ofNullable(reconcileResults.get(name)); } /** - * Retrieve the dependent resource implementation associated with the specified resource type. + * Set the {@link ReconcileResult} for the specified {@link DependentResource} implementation. * - * @param resourceClass the dependent resource class for which we want to retrieve the associated - * dependent resource implementation - * @param the type of the resources for which we want to retrieve the associated dependent - * resource implementation - * @return the associated dependent resource implementation if it exists or an exception if it - * doesn't or several implementations are associated with the specified resource type + * @param name the name of the {@link DependentResource} for which we want to set the + * {@link ReconcileResult} + * @param reconcileResult the reconcile result associated with the specified + * {@link DependentResource} */ - @SuppressWarnings("unchecked") - public T getDependentResource(Class resourceClass) { - var resourceList = - dependentResources.stream() - .filter(dr -> dr.getClass().equals(resourceClass)) - .collect(Collectors.toList()); - if (resourceList.isEmpty()) { - throw new OperatorException( - "No dependent resource found for class: " + resourceClass.getName()); - } - if (resourceList.size() > 1) { - throw new OperatorException( - "More than one dependent resource found for class: " + resourceClass.getName()); - } - return (T) resourceList.get(0); + public void setReconcileResult(String name, ReconcileResult reconcileResult) { + reconcileResults.put(name, reconcileResult); } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedDependentResourceException.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedDependentResourceException.java new file mode 100644 index 0000000000..4fbdd65241 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedDependentResourceException.java @@ -0,0 +1,17 @@ +package io.javaoperatorsdk.operator.api.reconciler.dependent.managed; + +import io.javaoperatorsdk.operator.OperatorException; + +public class ManagedDependentResourceException extends OperatorException { + private final String associatedDependentName; + + public ManagedDependentResourceException(String associatedDependentName, String message, + Throwable cause) { + super(message, cause); + this.associatedDependentName = associatedDependentName; + } + + public String getAssociatedDependentName() { + return associatedDependentName; + } +} 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 f0aea0e1e2..2a96965000 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 @@ -1,7 +1,9 @@ package io.javaoperatorsdk.operator.processing; -import java.util.LinkedList; -import java.util.List; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; @@ -15,6 +17,7 @@ import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.dsl.MixedOperation; import io.fabric8.kubernetes.client.dsl.Resource; +import io.javaoperatorsdk.operator.AggregatedOperatorException; import io.javaoperatorsdk.operator.CustomResourceUtils; import io.javaoperatorsdk.operator.MissingCRDException; import io.javaoperatorsdk.operator.OperatorException; @@ -37,13 +40,13 @@ import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceProvider; import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DependentResourceConfigurator; import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.KubernetesClientAware; +import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.ManagedDependentResourceException; import io.javaoperatorsdk.operator.processing.event.EventSourceManager; -import io.javaoperatorsdk.operator.processing.event.source.EventSource; @SuppressWarnings({"unchecked", "rawtypes"}) @Ignore -public class Controller

implements Reconciler

, Cleaner

, - LifecycleAware, EventSourceInitializer

{ +public class Controller

+ implements Reconciler

, Cleaner

, LifecycleAware { private static final Logger log = LoggerFactory.getLogger(Controller.class); @@ -51,7 +54,7 @@ public class Controller

implements Reconciler

, Cleaner private final ControllerConfiguration

configuration; private final KubernetesClient kubernetesClient; private final EventSourceManager

eventSourceManager; - private final List dependents; + private final Map dependents; private final boolean contextInitializer; private final boolean hasDeleterDependents; private final boolean isCleaner; @@ -71,15 +74,22 @@ public Controller(Reconciler

reconciler, eventSourceManager = new EventSourceManager<>(this); final var hasDeleterHolder = new boolean[] {false}; - dependents = configuration.getDependentResources().stream() - .map(drs -> createAndConfigureFrom(drs, kubernetesClient)) - .peek(d -> { - // check if any dependent implements Deleter to record that fact - if (!hasDeleterHolder[0] && d instanceof Deleter) { - hasDeleterHolder[0] = true; - } - }) - .collect(Collectors.toList()); + final var specs = configuration.getDependentResources(); + final var size = specs.size(); + if (size == 0) { + dependents = Collections.emptyMap(); + } else { + final var dependentsHolder = new HashMap(size); + specs.forEach((name, drs) -> { + final var dependent = createAndConfigureFrom(drs, kubernetesClient); + // check if dependent implements Deleter to record that fact + if (!hasDeleterHolder[0] && dependent instanceof Deleter) { + hasDeleterHolder[0] = true; + } + dependentsHolder.put(name, dependent); + }); + dependents = Collections.unmodifiableMap(dependentsHolder); + } hasDeleterDependents = hasDeleterHolder[0]; isCleaner = reconciler instanceof Cleaner; @@ -133,7 +143,7 @@ public String successTypeName(DeleteControl deleteControl) { public DeleteControl execute() { initContextIfNeeded(resource, context); if (hasDeleterDependents) { - dependents.stream() + dependents.values().stream() .filter(d -> d instanceof Deleter) .map(Deleter.class::cast) .forEach(deleter -> deleter.delete(resource, context)); @@ -179,26 +189,47 @@ public String successTypeName(UpdateControl

result) { @Override public UpdateControl

execute() throws Exception { initContextIfNeeded(resource, context); - dependents.forEach(dependent -> dependent.reconcile(resource, context)); + final var exceptions = new ArrayList(dependents.size()); + dependents.forEach((name, dependent) -> { + try { + final var reconcileResult = dependent.reconcile(resource, context); + context.managedDependentResourceContext().setReconcileResult(name, reconcileResult); + log.info("Reconciled dependent '{}' -> {}", name, reconcileResult.getOperation()); + } catch (Exception e) { + final var message = e.getMessage(); + exceptions.add(new ManagedDependentResourceException( + name, "Error reconciling dependent '" + name + "': " + message, e)); + } + }); + + if (!exceptions.isEmpty()) { + throw new AggregatedOperatorException("One or more DependentResource(s) failed:\n" + + exceptions.stream() + .map(e -> "\t\t- " + e.getMessage()) + .collect(Collectors.joining("\n")), + exceptions); + } + return reconciler.reconcile(resource, context); } }); } - @Override - public List prepareEventSources(EventSourceContext

context) { - List sources = new LinkedList<>(); - dependents.stream() - .filter(dependentResource -> dependentResource instanceof EventSourceProvider) - .map(EventSourceProvider.class::cast) - .map(provider -> provider.initEventSource(context)) - .forEach(sources::add); + public void initAndRegisterEventSources(EventSourceContext

context) { + dependents.entrySet().stream() + .filter(drEntry -> drEntry.getValue() instanceof EventSourceProvider) + .forEach(drEntry -> { + final var provider = (EventSourceProvider) drEntry.getValue(); + final var source = provider.initEventSource(context); + eventSourceManager.registerEventSource(drEntry.getKey(), source); + }); // add manually defined event sources if (reconciler instanceof EventSourceInitializer) { - sources.addAll(((EventSourceInitializer

) reconciler).prepareEventSources(context)); + final var provider = (EventSourceInitializer

) this.reconciler; + final var ownSources = provider.prepareEventSources(context); + ownSources.forEach(eventSourceManager::registerEventSource); } - return sources; } @Override @@ -277,7 +308,7 @@ public void start() throws OperatorException { final var context = new EventSourceContext<>( eventSourceManager.getControllerResourceEventSource(), configuration, kubernetesClient); - prepareEventSources(context).forEach(eventSourceManager::registerEventSource); + initAndRegisterEventSources(context); eventSourceManager.start(); @@ -330,7 +361,7 @@ public boolean useFinalizer() { } @SuppressWarnings("rawtypes") - public List getDependents() { + public Map getDependents() { return dependents; } } 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 eac037d7c2..8b1a634c08 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 @@ -5,7 +5,12 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.dependent.*; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceProvider; +import io.javaoperatorsdk.operator.api.reconciler.dependent.RecentOperationCacheFiller; +import io.javaoperatorsdk.operator.api.reconciler.dependent.RecentOperationEventFilter; +import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult; import io.javaoperatorsdk.operator.processing.event.ResourceID; public abstract class AbstractDependentResource @@ -31,6 +36,8 @@ public ReconcileResult reconcile(P primary, Context

context) { if (maybeActual.isEmpty()) { if (creatable) { var desired = desired(primary, context); + log.info("Creating {} for primary {}", desired.getClass().getSimpleName(), + ResourceID.fromResource(primary)); log.debug("Creating dependent {} for primary {}", desired, primary); var createdResource = handleCreate(desired, primary, context); return ReconcileResult.resourceCreated(createdResource); @@ -41,6 +48,8 @@ public ReconcileResult reconcile(P primary, Context

context) { final var match = updater.match(actual, primary, context); if (!match.matched()) { final var desired = match.computedDesired().orElse(desired(primary, context)); + log.info("Updating {} for primary {}", desired.getClass().getSimpleName(), + ResourceID.fromResource(primary)); log.debug("Updating dependent {} for primary {}", desired, primary); var updatedResource = handleUpdate(actual, desired, primary, context); return ReconcileResult.resourceUpdated(updatedResource); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractCachingDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractCachingDependentResource.java index c45e50db9a..b131850b1e 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractCachingDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractCachingDependentResource.java @@ -4,14 +4,13 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceProvider; -import io.javaoperatorsdk.operator.api.reconciler.dependent.ResourceTypeAware; import io.javaoperatorsdk.operator.processing.dependent.AbstractDependentResource; import io.javaoperatorsdk.operator.processing.event.ExternalResourceCachingEventSource; import io.javaoperatorsdk.operator.processing.event.source.EventSource; public abstract class AbstractCachingDependentResource extends AbstractDependentResource - implements EventSourceProvider

, ResourceTypeAware { + implements EventSourceProvider

{ protected ExternalResourceCachingEventSource eventSource; private final Class resourceType; diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java index 03b7198bdd..f77ec0b4aa 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 @@ -15,7 +15,6 @@ import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceProvider; -import io.javaoperatorsdk.operator.api.reconciler.dependent.ResourceTypeAware; import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DependentResourceConfigurator; import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.KubernetesClientAware; import io.javaoperatorsdk.operator.processing.dependent.AbstractDependentResource; @@ -30,7 +29,7 @@ public abstract class KubernetesDependentResource extends AbstractDependentResource - implements KubernetesClientAware, EventSourceProvider

, ResourceTypeAware, + implements KubernetesClientAware, EventSourceProvider

, DependentResourceConfigurator { private static final Logger log = LoggerFactory.getLogger(KubernetesDependentResource.class); 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 fda64660b0..86fb203f97 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,15 +1,9 @@ package io.javaoperatorsdk.operator.processing.event; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; import java.util.LinkedHashSet; -import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.concurrent.ConcurrentNavigableMap; -import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; @@ -87,7 +81,7 @@ public void start() { } @SuppressWarnings("rawtypes") - private void logEventSourceEvent(EventSource eventSource, String event) { + private void logEventSourceEvent(NamedEventSource eventSource, String event) { if (log.isDebugEnabled()) { if (eventSource instanceof ResourceEventSource) { ResourceEventSource source = (ResourceEventSource) eventSource; @@ -119,17 +113,24 @@ public void stop() { eventProcessor.stop(); } - public final void registerEventSource(EventSource eventSource) + public final void registerEventSource(EventSource eventSource) throws OperatorException { + registerEventSource(null, eventSource); + } + + public final void registerEventSource(String name, EventSource eventSource) throws OperatorException { Objects.requireNonNull(eventSource, "EventSource must not be null"); lock.lock(); try { - eventSources.add(eventSource); + if (name == null || name.isBlank()) { + name = EventSource.defaultNameFor(eventSource); + } + eventSources.add(name, eventSource); eventSource.setEventHandler(eventProcessor); } catch (IllegalStateException | MissingCRDException e) { throw e; // leave untouched } catch (Exception e) { - throw new OperatorException("Couldn't register event source: " + eventSource.name() + " for " + throw new OperatorException("Couldn't register event source: " + name + " for " + controller.getConfiguration().getName() + " controller`", e); } finally { lock.unlock(); @@ -161,11 +162,13 @@ EventHandler getEventHandler() { } Set getRegisteredEventSources() { - return eventSources.all(); + return eventSources.flatMappedSources() + .map(NamedEventSource::original) + .collect(Collectors.toCollection(LinkedHashSet::new)); } public ControllerResourceEventSource getControllerResourceEventSource() { - return eventSources.controllerResourceEventSource; + return eventSources.controllerResourceEventSource(); } public Optional> getResourceEventSourceFor( @@ -184,141 +187,10 @@ public Optional> getResourceEventSourceFor( } TimerEventSource retryEventSource() { - return eventSources.retryAndRescheduleTimerEventSource; + return eventSources.retryEventSource(); } Controller getController() { return controller; } - - private static class EventSources implements Iterable { - private final ConcurrentNavigableMap> sources = - new ConcurrentSkipListMap<>(); - private final TimerEventSource retryAndRescheduleTimerEventSource = new TimerEventSource<>(); - private ControllerResourceEventSource controllerResourceEventSource; - - - ControllerResourceEventSource initControllerEventSource(Controller controller) { - controllerResourceEventSource = new ControllerResourceEventSource<>(controller); - return controllerResourceEventSource; - } - - TimerEventSource retryEventSource() { - return retryAndRescheduleTimerEventSource; - } - - @Override - public Iterator iterator() { - return sources.values().stream().flatMap(Collection::stream).iterator(); - } - - public Set all() { - return sources.values().stream().flatMap(Collection::stream) - .collect(Collectors.toCollection(LinkedHashSet::new)); - } - - public void clear() { - sources.clear(); - } - - public boolean contains(EventSource source) { - final var eventSources = sources.get(keyFor(source)); - if (eventSources == null || eventSources.isEmpty()) { - return false; - } - return findMatchingSource(name(source), eventSources).isPresent(); - } - - public void add(EventSource eventSource) { - if (contains(eventSource)) { - throw new IllegalArgumentException("An event source is already registered for the " - + keyAsString(getDependentType(eventSource), name(eventSource)) - + " class/name combination"); - } - sources.computeIfAbsent(keyFor(eventSource), k -> new ArrayList<>()).add(eventSource); - } - - @SuppressWarnings("rawtypes") - private Class getDependentType(EventSource source) { - return source instanceof ResourceEventSource - ? ((ResourceEventSource) source).getResourceClass() - : source.getClass(); - } - - private String name(EventSource source) { - return source.name(); - } - - private String keyFor(EventSource source) { - return keyFor(getDependentType(source)); - } - - private String keyFor(Class dependentType) { - var key = dependentType.getCanonicalName(); - - // make sure timer event source is started first, then controller event source - // this is needed so that these sources are set when informer sources start so that events can - // properly be processed - if (controllerResourceEventSource != null - && key.equals(controllerResourceEventSource.getResourceClass().getCanonicalName())) { - key = 1 + "-" + key; - } else if (key.equals(retryAndRescheduleTimerEventSource.getClass().getCanonicalName())) { - key = 0 + "-" + key; - } - return key; - } - - @SuppressWarnings("unchecked") - public ResourceEventSource get(Class dependentType, String name) { - final var sourcesForType = sources.get(keyFor(dependentType)); - if (sourcesForType == null || sourcesForType.isEmpty()) { - return null; - } - - final var size = sourcesForType.size(); - final EventSource source; - if (size == 1) { - source = sourcesForType.get(0); - } else { - if (name == null || name.isBlank()) { - throw new IllegalArgumentException("There are multiple EventSources registered for type " - + dependentType.getCanonicalName() - + ", you need to provide a name to specify which EventSource you want to query. Known names: " - + sourcesForType.stream().map(this::name).collect(Collectors.joining(","))); - } - source = findMatchingSource(name, sourcesForType).orElse(null); - - if (source == null) { - return null; - } - } - - if (!(source instanceof ResourceEventSource)) { - throw new IllegalArgumentException(source + " associated with " - + keyAsString(dependentType, name) + " is not a " - + ResourceEventSource.class.getSimpleName()); - } - final var res = (ResourceEventSource) source; - final var resourceClass = res.getResourceClass(); - if (!resourceClass.isAssignableFrom(dependentType)) { - throw new IllegalArgumentException(source + " associated with " - + keyAsString(dependentType, name) - + " is handling " + resourceClass.getName() + " resources but asked for " - + dependentType.getName()); - } - return res; - } - - private Optional findMatchingSource(String name, - List sourcesForType) { - return sourcesForType.stream().filter(es -> name(es).equals(name)).findAny(); - } - - @SuppressWarnings("rawtypes") - private String keyAsString(Class dependentType, String name) { - return name != null && name.length() > 0 - ? "(" + dependentType.getName() + ", " + name + ")" - : dependentType.getName(); - } - } } 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 new file mode 100644 index 0000000000..b890cd5d52 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSources.java @@ -0,0 +1,142 @@ +package io.javaoperatorsdk.operator.processing.event; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentNavigableMap; +import java.util.concurrent.ConcurrentSkipListMap; +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.timer.TimerEventSource; + +class EventSources implements Iterable { + + private final ConcurrentNavigableMap> sources = + new ConcurrentSkipListMap<>(); + private final TimerEventSource retryAndRescheduleTimerEventSource = new TimerEventSource<>(); + private ControllerResourceEventSource controllerResourceEventSource; + + + ControllerResourceEventSource initControllerEventSource(Controller controller) { + controllerResourceEventSource = new ControllerResourceEventSource<>(controller); + return controllerResourceEventSource; + } + + ControllerResourceEventSource controllerResourceEventSource() { + return controllerResourceEventSource; + } + + TimerEventSource retryEventSource() { + return retryAndRescheduleTimerEventSource; + } + + @Override + public Iterator iterator() { + return flatMappedSources().iterator(); + } + + Stream flatMappedSources() { + return sources.values().stream().flatMap(c -> c.entrySet().stream() + .map(esEntry -> new NamedEventSource(esEntry.getValue(), esEntry.getKey()))); + } + + public void clear() { + sources.clear(); + } + + public boolean contains(String name, EventSource source) { + final var eventSources = sources.get(keyFor(source)); + if (eventSources == null || eventSources.isEmpty()) { + return false; + } + return eventSources.containsKey(name); + } + + public void add(String name, EventSource eventSource) { + if (contains(name, eventSource)) { + throw new IllegalArgumentException("An event source is already registered for the " + + keyAsString(getDependentType(eventSource), name) + + " class/name combination"); + } + sources.computeIfAbsent(keyFor(eventSource), k -> new HashMap<>()).put(name, eventSource); + } + + @SuppressWarnings("rawtypes") + private Class getDependentType(EventSource source) { + return source instanceof ResourceEventSource + ? ((ResourceEventSource) source).getResourceClass() + : source.getClass(); + } + + private String keyFor(EventSource source) { + return keyFor(getDependentType(source)); + } + + private String keyFor(Class dependentType) { + var key = dependentType.getCanonicalName(); + + // make sure timer event source is started first, then controller event source + // this is needed so that these sources are set when informer sources start so that events can + // properly be processed + if (controllerResourceEventSource != null + && key.equals(controllerResourceEventSource.getResourceClass().getCanonicalName())) { + key = 1 + "-" + key; + } else if (key.equals(retryAndRescheduleTimerEventSource.getClass().getCanonicalName())) { + key = 0 + "-" + key; + } + return key; + } + + @SuppressWarnings("unchecked") + public ResourceEventSource get(Class dependentType, String name) { + final var sourcesForType = sources.get(keyFor(dependentType)); + if (sourcesForType == null || sourcesForType.isEmpty()) { + return null; + } + + final var size = sourcesForType.size(); + final EventSource source; + if (size == 1) { + source = sourcesForType.values().stream().findFirst().orElse(null); + } else { + if (name == null || name.isBlank()) { + throw new IllegalArgumentException("There are multiple EventSources registered for type " + + dependentType.getCanonicalName() + + ", you need to provide a name to specify which EventSource you want to query. Known names: " + + String.join(",", sourcesForType.keySet())); + } + source = sourcesForType.get(name); + + if (source == null) { + return null; + } + } + + if (!(source instanceof ResourceEventSource)) { + throw new IllegalArgumentException(source + " associated with " + + keyAsString(dependentType, name) + " is not a " + + ResourceEventSource.class.getSimpleName()); + } + final var res = (ResourceEventSource) source; + final var resourceClass = res.getResourceClass(); + if (!resourceClass.isAssignableFrom(dependentType)) { + throw new IllegalArgumentException(source + " associated with " + + keyAsString(dependentType, name) + + " is handling " + resourceClass.getName() + " resources but asked for " + + dependentType.getName()); + } + return res; + } + + @SuppressWarnings("rawtypes") + private String keyAsString(Class dependentType, String name) { + return name != null && name.length() > 0 + ? "(" + dependentType.getName() + ", " + name + ")" + : dependentType.getName(); + } +} 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 new file mode 100644 index 0000000000..38d262f254 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/NamedEventSource.java @@ -0,0 +1,43 @@ +package io.javaoperatorsdk.operator.processing.event; + +import io.javaoperatorsdk.operator.OperatorException; +import io.javaoperatorsdk.operator.processing.event.source.EventSource; + +class NamedEventSource implements EventSource { + + private final EventSource original; + private final String name; + + NamedEventSource(EventSource original, String name) { + this.original = original; + this.name = name; + } + + @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 String toString() { + return original + " named: '" + name + "'}"; + } + + public EventSource original() { + return original; + } +} 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 10a690c0e8..e0029c1867 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 @@ -12,16 +12,6 @@ */ public interface EventSource extends LifecycleAware { - /** - * An optional name for your EventSource. This is only required if you need to register multiple - * EventSources for the same resource type (e.g. {@code Deployment}). - * - * @return the name associated with this EventSource - */ - default String name() { - return getClass().getCanonicalName(); - } - /** * Sets the {@link EventHandler} that is linked to your reconciler when this EventSource is * registered. @@ -29,4 +19,8 @@ default String name() { * @param handler the {@link EventHandler} associated with your reconciler */ void setEventHandler(EventHandler handler); + + static String defaultNameFor(EventSource source) { + return source.getClass().getCanonicalName(); + } } 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 e1ef859549..f5163c9973 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 @@ -97,7 +97,7 @@ public void onUpdate(R oldObject, R newObject) { private synchronized void onAddOrUpdate(String operation, R newObject, Runnable superOnOp) { var resourceID = ResourceID.fromResource(newObject); if (eventRecorder.isRecordingFor(resourceID)) { - log.info("Recording event for: " + resourceID); + log.debug("Recording event for: {}", resourceID); eventRecorder.recordEvent(newObject); return; } @@ -224,7 +224,7 @@ private void handleRecentResourceOperationAndStopEventRecording(R resource) { @Override public synchronized void prepareForCreateOrUpdateEventFiltering(ResourceID resourceID, R resource) { - log.info("Starting event recording for: {}", resourceID); + log.debug("Starting event recording for: {}", resourceID); eventRecorder.startEventRecording(resourceID); } @@ -237,7 +237,7 @@ public synchronized void prepareForCreateOrUpdateEventFiltering(ResourceID resou @Override public synchronized void cleanupOnCreateOrUpdateEventFiltering(ResourceID resourceID, R resource) { - log.info("Stopping event recording for: {}", resourceID); + log.debug("Stopping event recording for: {}", resourceID); eventRecorder.stopEventRecording(resourceID); } 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 new file mode 100644 index 0000000000..20c7d4ae31 --- /dev/null +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverriderTest.java @@ -0,0 +1,77 @@ +package io.javaoperatorsdk.operator.api.config; + +import org.junit.jupiter.api.Test; + +import io.fabric8.kubernetes.api.model.ConfigMap; +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; +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfig; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ControllerConfigurationOverriderTest { + + @Test + void replaceNamedDependentResourceConfigShouldWork() { + var configuration = new AnnotationControllerConfiguration<>(new OneDepReconciler()); + var dependents = configuration.getDependentResources(); + assertFalse(dependents.isEmpty()); + assertEquals(1, dependents.size()); + final var dependentResourceName = DependentResource.defaultNameFor(ReadOnlyDependent.class); + assertTrue(dependents.containsKey(dependentResourceName)); + var dependentSpec = dependents.get(dependentResourceName); + assertEquals(ReadOnlyDependent.class, dependentSpec.getDependentResourceClass()); + var maybeConfig = dependentSpec.getDependentResourceConfiguration(); + assertTrue(maybeConfig.isPresent()); + assertTrue(maybeConfig.get() instanceof KubernetesDependentResourceConfig); + var config = (KubernetesDependentResourceConfig) maybeConfig.orElseThrow(); + // check that the DependentResource inherits the controller's configuration if applicable + assertEquals(1, config.namespaces().length); + assertNull(config.labelSelector()); + assertEquals(OneDepReconciler.CONFIGURED_NS, config.namespaces()[0]); + + // override the namespaces for the dependent resource + final var overriddenNS = "newNS"; + final var labelSelector = "foo=bar"; + final var overridden = ControllerConfigurationOverrider.override(configuration) + .replacingNamedDependentResourceConfig( + DependentResource.defaultNameFor(ReadOnlyDependent.class), + new KubernetesDependentResourceConfig(new String[] {overriddenNS}, labelSelector)) + .build(); + dependents = overridden.getDependentResources(); + dependentSpec = dependents.get(dependentResourceName); + config = (KubernetesDependentResourceConfig) dependentSpec.getDependentResourceConfiguration() + .orElseThrow(); + assertEquals(1, config.namespaces().length); + assertEquals(labelSelector, config.labelSelector()); + assertEquals(overriddenNS, config.namespaces()[0]); + } + + @ControllerConfiguration(namespaces = OneDepReconciler.CONFIGURED_NS, + dependents = @Dependent(type = ReadOnlyDependent.class)) + private static class OneDepReconciler implements Reconciler { + + private static final String CONFIGURED_NS = "foo"; + + @Override + public UpdateControl reconcile(ConfigMap resource, Context context) { + return null; + } + } + + private static class ReadOnlyDependent extends KubernetesDependentResource { + + public ReadOnlyDependent() { + super(ConfigMap.class); + } + } + +} diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/UtilsTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/UtilsTest.java index 75d263a4e1..f762b7d2c3 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/UtilsTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/UtilsTest.java @@ -101,6 +101,12 @@ public ReconcileResult reconcile(TestCustomResource primary, public Optional getResource(TestCustomResource primaryResource) { return Optional.empty(); } + + @Override + public Class resourceType() { + return Deployment.class; + } + } public static class TestKubernetesDependentResource diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractSimpleDependentResourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractSimpleDependentResourceTest.java index 0b9517eb81..53bbd05464 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractSimpleDependentResourceTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/external/AbstractSimpleDependentResourceTest.java @@ -18,7 +18,11 @@ import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @SuppressWarnings("unchecked") class AbstractSimpleDependentResourceTest { @@ -136,5 +140,10 @@ protected SampleExternalResource desired(TestCustomResource primary, Context context) { return SampleExternalResource.testResource1(); } + + @Override + public Class resourceType() { + return SampleExternalResource.class; + } } } 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 64e742d381..3ba1258d3e 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 @@ -1,6 +1,5 @@ package io.javaoperatorsdk.operator.processing.event; -import java.io.IOException; import java.util.Iterator; import java.util.Optional; import java.util.Set; @@ -30,6 +29,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +@SuppressWarnings({"rawtypes", "unchecked"}) class EventSourceManagerTest { private final EventProcessor eventHandler = mock(EventProcessor.class); @@ -48,7 +48,7 @@ public void registersEventSource() { } @Test - public void closeShouldCascadeToEventSources() throws IOException { + public void closeShouldCascadeToEventSources() { EventSource eventSource = mock(EventSource.class); EventSource eventSource2 = mock(TimerEventSource.class); @@ -97,23 +97,23 @@ void retrievingEventSourceForClassShouldWork() { @Test void shouldNotBePossibleToAddEventSourcesForSameTypeAndName() { EventSourceManager manager = initManager(); + final var name = "name1"; CachingEventSource eventSource = mock(CachingEventSource.class); when(eventSource.getResourceClass()).thenReturn(TestCustomResource.class); - when(eventSource.name()).thenReturn("name1"); - manager.registerEventSource(eventSource); + manager.registerEventSource(name, eventSource); eventSource = mock(CachingEventSource.class); when(eventSource.getResourceClass()).thenReturn(TestCustomResource.class); - when(eventSource.name()).thenReturn("name1"); final var source = eventSource; final var exception = assertThrows(OperatorException.class, - () -> manager.registerEventSource(source)); + () -> manager.registerEventSource(name, source)); final var cause = exception.getCause(); assertTrue(cause instanceof IllegalArgumentException); assertThat(cause.getMessage()).contains( - "An event source is already registered for the (io.javaoperatorsdk.operator.sample.simple.TestCustomResource, name1) class/name combination"); + "An event source is already registered for the (io.javaoperatorsdk.operator.sample.simple.TestCustomResource, " + + name + ") class/name combination"); } @Test @@ -122,13 +122,11 @@ void retrievingAnEventSourceWhenMultipleAreRegisteredForATypeShouldRequireAQuali CachingEventSource eventSource = mock(CachingEventSource.class); when(eventSource.getResourceClass()).thenReturn(TestCustomResource.class); - when(eventSource.name()).thenReturn("name1"); - manager.registerEventSource(eventSource); + manager.registerEventSource("name1", eventSource); CachingEventSource eventSource2 = mock(CachingEventSource.class); when(eventSource2.getResourceClass()).thenReturn(TestCustomResource.class); - when(eventSource2.name()).thenReturn("name2"); - manager.registerEventSource(eventSource2); + manager.registerEventSource("name2", eventSource2); final var exception = assertThrows(IllegalArgumentException.class, () -> manager.getResourceEventSourceFor(TestCustomResource.class)); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/runtime/AnnotationControllerConfigurationTest.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/runtime/AnnotationControllerConfigurationTest.java index fc44c3622e..7cc0162356 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/runtime/AnnotationControllerConfigurationTest.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/runtime/AnnotationControllerConfigurationTest.java @@ -8,10 +8,14 @@ import io.javaoperatorsdk.operator.api.reconciler.Reconciler; import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfig; import io.javaoperatorsdk.operator.sample.readonly.ReadOnlyDependent; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; class AnnotationControllerConfigurationTest { @@ -25,22 +29,88 @@ void getDependentResources() { dependents = configuration.getDependentResources(); assertFalse(dependents.isEmpty()); assertEquals(1, dependents.size()); - final var dependentSpec = dependents.get(0); + final var dependentResourceName = DependentResource.defaultNameFor(ReadOnlyDependent.class); + assertTrue(dependents.containsKey(dependentResourceName)); + var dependentSpec = dependents.get(dependentResourceName); assertEquals(ReadOnlyDependent.class, dependentSpec.getDependentResourceClass()); - final var maybeConfig = dependentSpec.getDependentResourceConfiguration(); + var maybeConfig = dependentSpec.getDependentResourceConfiguration(); assertTrue(maybeConfig.isPresent()); assertTrue(maybeConfig.get() instanceof KubernetesDependentResourceConfig); final var config = (KubernetesDependentResourceConfig) maybeConfig.orElseThrow(); + // check that the DependentResource inherits the controller's configuration if applicable assertEquals(1, config.namespaces().length); assertEquals(OneDepReconciler.CONFIGURED_NS, config.namespaces()[0]); + + configuration = new AnnotationControllerConfiguration<>(new NamedDepReconciler()); + dependents = configuration.getDependentResources(); + assertFalse(dependents.isEmpty()); + assertEquals(1, dependents.size()); + dependentSpec = dependents.get(NamedDepReconciler.NAME); + assertEquals(ReadOnlyDependent.class, dependentSpec.getDependentResourceClass()); + maybeConfig = dependentSpec.getDependentResourceConfiguration(); + assertTrue(maybeConfig.isPresent()); + assertTrue(maybeConfig.get() instanceof KubernetesDependentResourceConfig); } + @Test + void tryingToAddDuplicatedDependentsWithoutNameShouldFail() { + var configuration = new AnnotationControllerConfiguration<>(new DuplicatedDepReconciler()); + assertThrows(IllegalArgumentException.class, configuration::getDependentResources); + } + + @Test + void addingDuplicatedDependentsWithNameShouldWork() { + var config = new AnnotationControllerConfiguration<>(new NamedDuplicatedDepReconciler()); + var dependents = config.getDependentResources(); + assertEquals(2, dependents.size()); + assertTrue(dependents.containsKey(NamedDuplicatedDepReconciler.NAME) + && dependents.containsKey(DependentResource.defaultNameFor(ReadOnlyDependent.class))); + } @ControllerConfiguration(namespaces = OneDepReconciler.CONFIGURED_NS, dependents = @Dependent(type = ReadOnlyDependent.class)) private static class OneDepReconciler implements Reconciler { - public static final String CONFIGURED_NS = "foo"; + private static final String CONFIGURED_NS = "foo"; + + @Override + public UpdateControl reconcile(ConfigMap resource, Context context) { + return null; + } + } + + @ControllerConfiguration( + dependents = @Dependent(type = ReadOnlyDependent.class, name = NamedDepReconciler.NAME)) + private static class NamedDepReconciler implements Reconciler { + private static final String NAME = "foo"; + + @Override + public UpdateControl reconcile(ConfigMap resource, Context context) { + return null; + } + } + + @ControllerConfiguration( + dependents = { + @Dependent(type = ReadOnlyDependent.class), + @Dependent(type = ReadOnlyDependent.class) + }) + private static class DuplicatedDepReconciler implements Reconciler { + + @Override + public UpdateControl reconcile(ConfigMap resource, Context context) { + return null; + } + } + + @ControllerConfiguration( + dependents = { + @Dependent(type = ReadOnlyDependent.class, name = NamedDuplicatedDepReconciler.NAME), + @Dependent(type = ReadOnlyDependent.class) + }) + private static class NamedDuplicatedDepReconciler implements Reconciler { + + private static final String NAME = "duplicated"; @Override public UpdateControl reconcile(ConfigMap resource, Context context) { 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 4c1ffc4312..c8ab434c4f 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,7 +1,7 @@ package io.javaoperatorsdk.operator.sample.createupdateeventfilter; import java.util.HashMap; -import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; @@ -9,7 +9,12 @@ import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.client.KubernetesClient; 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.EventSourceInitializer; +import io.javaoperatorsdk.operator.api.reconciler.Reconciler; +import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; import io.javaoperatorsdk.operator.junit.KubernetesClientAware; import io.javaoperatorsdk.operator.processing.event.ResourceID; import io.javaoperatorsdk.operator.processing.event.source.EventSource; @@ -94,14 +99,14 @@ private ConfigMap createConfigMap(CreateUpdateEventFilterTestCustomResource reso } @Override - public List prepareEventSources( + public Map prepareEventSources( EventSourceContext context) { InformerConfiguration informerConfiguration = InformerConfiguration.from(context, ConfigMap.class) .withLabelSelector("integrationtest = " + this.getClass().getSimpleName()) .build(); informerEventSource = new InformerEventSource<>(informerConfiguration, client); - return List.of(informerEventSource); + return Map.of("test-informer", informerEventSource); } @Override 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 8e7dc1a6f2..efb3e18ac5 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.List; +import java.util.Map; import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; @@ -9,7 +9,12 @@ 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.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.processing.event.source.EventSource; import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; import io.javaoperatorsdk.operator.processing.event.source.informer.Mappers; @@ -33,7 +38,7 @@ public class InformerEventSourceTestCustomReconciler private final AtomicInteger numberOfExecutions = new AtomicInteger(0); @Override - public List prepareEventSources( + public Map prepareEventSources( EventSourceContext context) { InformerConfiguration config = @@ -41,7 +46,7 @@ public List prepareEventSources( .withPrimaryResourcesRetriever(Mappers.fromAnnotation(RELATED_RESOURCE_NAME)) .build(); - return List.of(new InformerEventSource<>(config, context)); + return Map.of("test-informer", new InformerEventSource<>(config, context)); } @Override 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 074c454560..0751c38d0a 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,13 +1,20 @@ package io.javaoperatorsdk.operator.sample.standalonedependent; -import java.util.List; +import java.util.Map; import java.util.Optional; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.KubernetesClientException; import io.javaoperatorsdk.operator.ReconcilerUtils; -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.junit.KubernetesClientAware; import io.javaoperatorsdk.operator.processing.dependent.Creator; import io.javaoperatorsdk.operator.processing.dependent.Updater; @@ -30,9 +37,9 @@ public StandaloneDependentTestReconciler() { } @Override - public List prepareEventSources( + public Map prepareEventSources( EventSourceContext context) { - return List.of(deploymentDependent.initEventSource(context)); + return Map.of("deployment", deploymentDependent.initEventSource(context)); } @Override 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 0a5703a676..f1cd015c34 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 @@ -35,8 +35,8 @@ public static void main(String[] args) throws IOException { // override the default configuration operator.register(schemaReconciler, - configOverrider -> configOverrider.replaceDependentResourceConfig( - SchemaDependentResource.class, + configOverrider -> configOverrider.replacingNamedDependentResourceConfig( + SchemaDependentResource.NAME, new ResourcePollerConfig(300, MySQLDbConfig.loadFromEnvironmentVars()))); operator.installShutdownHook(); operator.start(); 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 f8f312ba8e..4d3b7402f9 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 @@ -4,7 +4,12 @@ import org.slf4j.LoggerFactory; import io.fabric8.kubernetes.api.model.Secret; -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.Reconciler; +import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; import io.javaoperatorsdk.operator.sample.dependent.SchemaDependentResource; import io.javaoperatorsdk.operator.sample.dependent.SecretDependentResource; @@ -16,7 +21,7 @@ @ControllerConfiguration( dependents = { @Dependent(type = SecretDependentResource.class), - @Dependent(type = SchemaDependentResource.class) + @Dependent(type = SchemaDependentResource.class, name = SchemaDependentResource.NAME) }) public class MySQLSchemaReconciler implements Reconciler, ErrorStatusHandler { @@ -31,9 +36,8 @@ public UpdateControl reconcile(MySQLSchema schema, Context { + + return context.getSecondaryResource(MySQLSchema.class, SchemaDependentResource.NAME).map(s -> { updateStatusPojo(schema, secret.getMetadata().getName(), decode(secret.getData().get(MYSQL_SECRET_USERNAME))); log.info("Schema {} created - updating CR status", schema.getMetadata().getName()); 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 c2be8c86da..3f370bc7a2 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 @@ -30,7 +30,7 @@ public class SchemaDependentResource implements EventSourceProvider, DependentResourceConfigurator, Creator, Deleter { - + public static final String NAME = "schema"; private static final Logger log = LoggerFactory.getLogger(SchemaDependentResource.class); private MySQLDbConfig dbConfig; diff --git a/sample-operators/mysql-schema/src/test/java/io/javaoperatorsdk/operator/sample/MySQLSchemaOperatorE2E.java b/sample-operators/mysql-schema/src/test/java/io/javaoperatorsdk/operator/sample/MySQLSchemaOperatorE2E.java index 06d9d1d184..f532596f30 100644 --- a/sample-operators/mysql-schema/src/test/java/io/javaoperatorsdk/operator/sample/MySQLSchemaOperatorE2E.java +++ b/sample-operators/mysql-schema/src/test/java/io/javaoperatorsdk/operator/sample/MySQLSchemaOperatorE2E.java @@ -61,19 +61,16 @@ boolean isLocal() { } @RegisterExtension - @SuppressWarnings("unchecked") AbstractOperatorExtension operator = isLocal() ? OperatorExtension.builder() .withReconciler( new MySQLSchemaReconciler(), - c -> { - c.replaceDependentResourceConfig( - SchemaDependentResource.class, - new ResourcePollerConfig( - 700, new MySQLDbConfig("127.0.0.1", LOCAL_PORT.toString(), "root", - "password"))); - }) + c -> c.replacingNamedDependentResourceConfig( + SchemaDependentResource.NAME, + new ResourcePollerConfig( + 700, new MySQLDbConfig("127.0.0.1", LOCAL_PORT.toString(), "root", + "password")))) .withInfrastructure(infrastructure) .withPortForward(MY_SQL_NS, "app", "mysql", 3306, LOCAL_PORT) .build() 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 68e3fbe135..3ece3ddfd9 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,6 +2,7 @@ import java.io.ByteArrayOutputStream; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; @@ -18,7 +19,14 @@ import io.fabric8.kubernetes.client.dsl.ExecListener; import io.fabric8.kubernetes.client.dsl.ExecWatch; 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.EventSourceInitializer; +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.PrimaryToSecondaryMapper; @@ -38,7 +46,7 @@ public WebappReconciler(KubernetesClient kubernetesClient) { } @Override - public List prepareEventSources(EventSourceContext context) { + public Map 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 @@ -64,7 +72,7 @@ public List prepareEventSources(EventSourceContext context) .withPrimaryResourcesRetriever(webappsMatchingTomcatName) .withAssociatedSecondaryResourceIdentifier(tomcatFromWebAppSpec) .build(); - return List.of(new InformerEventSource<>(configuration, context)); + return Map.of("tomcat-informer", new InformerEventSource<>(configuration, context)); } /** @@ -182,8 +190,8 @@ private ExecWatch execCmd(Pod pod, CompletableFuture data, String... com static class SimpleListener implements ExecListener { - private CompletableFuture data; - private ByteArrayOutputStream baos; + private final CompletableFuture data; + private final ByteArrayOutputStream baos; private final Logger log = LoggerFactory.getLogger(getClass()); public SimpleListener(CompletableFuture data, ByteArrayOutputStream baos) { 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 3d74dcb00b..b67a645506 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,7 +1,6 @@ package io.javaoperatorsdk.operator.sample; import java.util.HashMap; -import java.util.List; import java.util.Map; import org.apache.commons.lang3.StringUtils; @@ -53,7 +52,7 @@ public WebPageReconciler(KubernetesClient kubernetesClient) { } @Override - public List prepareEventSources(EventSourceContext context) { + public Map prepareEventSources(EventSourceContext context) { var configMapEventSource = new InformerEventSource<>(InformerConfiguration.from(context, ConfigMap.class) .withLabelSelector(LOW_LEVEL_LABEL_KEY) @@ -66,7 +65,8 @@ public List prepareEventSources(EventSourceContext context new InformerEventSource<>(InformerConfiguration.from(context, Service.class) .withLabelSelector(LOW_LEVEL_LABEL_KEY) .build(), context); - return List.of(configMapEventSource, deploymentEventSource, serviceEventSource); + return Map.of("configmap", configMapEventSource, "deployment", deploymentEventSource, "service", + serviceEventSource); } @Override 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 180c6ade65..34c7686b45 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,6 +1,6 @@ package io.javaoperatorsdk.operator.sample; -import java.util.List; +import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,11 +46,11 @@ public WebPageStandaloneDependentsReconciler(KubernetesClient kubernetesClient) } @Override - public List prepareEventSources(EventSourceContext context) { - return List.of( - configMapDR.initEventSource(context), - deploymentDR.initEventSource(context), - serviceDR.initEventSource(context)); + public Map prepareEventSources(EventSourceContext context) { + return Map.of( + "configmap", configMapDR.initEventSource(context), + "deployment", deploymentDR.initEventSource(context), + "service", serviceDR.initEventSource(context)); } @Override