Skip to content

tests: add managed multiple dependent test #1275

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public class AnnotationControllerConfiguration<R extends HasMetadata>
protected final Reconciler<R> reconciler;
private final ControllerConfiguration annotation;
private List<DependentResourceSpec> specs;
private Class<R> resourceClass;

public AnnotationControllerConfiguration(Reconciler<R> reconciler) {
this.reconciler = reconciler;
Expand Down Expand Up @@ -81,7 +82,12 @@ public Set<String> getNamespaces() {
@Override
@SuppressWarnings("unchecked")
public Class<R> getResourceClass() {
return (Class<R>) Utils.getFirstTypeArgumentFromInterface(reconciler.getClass());
if (resourceClass == null) {
resourceClass =
(Class<R>) Utils.getFirstTypeArgumentFromSuperClassOrInterface(reconciler.getClass(),
Reconciler.class);
}
return resourceClass;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,15 @@ default Optional<Duration> reconciliationMaxInterval() {
return Optional.of(Duration.ofHours(10L));
}

@SuppressWarnings("unused")
default ConfigurationService getConfigurationService() {
return ConfigurationServiceProvider.instance();
}

@SuppressWarnings("unchecked")
@Override
default Class<R> getResourceClass() {
return (Class<R>) Utils.getFirstTypeArgumentFromSuperClassOrInterface(getClass(),
ControllerConfiguration.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ default String getLabelSelector() {

@SuppressWarnings("unchecked")
default Class<R> getResourceClass() {
return (Class<R>) Utils.getFirstTypeArgumentFromInterface(getClass());
return (Class<R>) Utils.getFirstTypeArgumentFromSuperClassOrInterface(getClass(),
ResourceConfiguration.class);
}

default Set<String> getNamespaces() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.lang.reflect.Type;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.Arrays;
import java.util.Date;
import java.util.Properties;
import java.util.function.Function;
Expand Down Expand Up @@ -89,13 +90,44 @@ static boolean getBooleanFromSystemPropsOrDefault(String propertyName, boolean d
}

public static Class<?> getFirstTypeArgumentFromExtendedClass(Class<?> clazz) {
Type type = clazz.getGenericSuperclass();
return (Class<?>) ((ParameterizedType) type).getActualTypeArguments()[0];
try {
Type type = clazz.getGenericSuperclass();
return (Class<?>) ((ParameterizedType) type).getActualTypeArguments()[0];
} catch (Exception e) {
throw new RuntimeException("Couldn't retrieve generic parameter type from "
+ clazz.getSimpleName()
+ " because it doesn't extend a class that is parameterized with the type we want to retrieve",
e);
}
}

public static Class<?> getFirstTypeArgumentFromInterface(Class<?> clazz,
Class<?> expectedImplementedInterface) {
return Arrays.stream(clazz.getGenericInterfaces())
.filter(type -> type.getTypeName().startsWith(expectedImplementedInterface.getName())
&& type instanceof ParameterizedType)
.map(ParameterizedType.class::cast)
.findFirst()
.map(t -> (Class<?>) t.getActualTypeArguments()[0])
.orElseThrow(() -> new RuntimeException(
"Couldn't retrieve generic parameter type from " + clazz.getSimpleName()
+ " because it doesn't implement "
+ expectedImplementedInterface.getSimpleName()
+ " directly"));
}

public static Class<?> getFirstTypeArgumentFromInterface(Class<?> clazz) {
ParameterizedType type = (ParameterizedType) clazz.getGenericInterfaces()[0];
return (Class<?>) type.getActualTypeArguments()[0];
public static Class<?> getFirstTypeArgumentFromSuperClassOrInterface(Class<?> clazz,
Class<?> expectedImplementedInterface) {
// first check super class if it exists
if (!clazz.getSuperclass().equals(Object.class)) {
try {
return getFirstTypeArgumentFromExtendedClass(clazz);
} catch (Exception e) {
// try interfaces
return getFirstTypeArgumentFromInterface(clazz, expectedImplementedInterface);
}
}
return getFirstTypeArgumentFromInterface(clazz, expectedImplementedInterface);
}

public static <C, T> T valueOrDefault(C annotation, Function<C, T> mapper, T defaultValue) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.api.config.DefaultResourceConfiguration;
import io.javaoperatorsdk.operator.api.config.ResourceConfiguration;
import io.javaoperatorsdk.operator.api.config.Utils;
import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext;
import io.javaoperatorsdk.operator.processing.event.source.SecondaryToPrimaryMapper;
import io.javaoperatorsdk.operator.processing.event.source.informer.Mappers;
Expand Down Expand Up @@ -160,4 +161,10 @@ static <R extends HasMetadata> InformerConfigurationBuilder<R> from(
.withNamespacesInheritedFromController(eventSourceContext);
}

@SuppressWarnings("unchecked")
@Override
default Class<R> getResourceClass() {
return (Class<R>) Utils.getFirstTypeArgumentFromSuperClassOrInterface(getClass(),
InformerConfiguration.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@

import io.javaoperatorsdk.operator.sample.simple.TestCustomResource;

import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

class ControllerConfigurationTest {

@Test
void getCustomResourceClass() {
final ControllerConfiguration<TestCustomResource> lambdasCannotBeUsedToExtractGenericParam =
() -> null;
assertThrows(RuntimeException.class,
lambdasCannotBeUsedToExtractGenericParam::getResourceClass);

final ControllerConfiguration<TestCustomResource> conf = new ControllerConfiguration<>() {
@Override
public String getAssociatedReconcilerClassName() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@

import org.junit.jupiter.api.Test;

import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource;
Expand Down Expand Up @@ -84,10 +88,31 @@ void getsFirstTypeArgumentFromExtendedClass() {

@Test
void getsFirstTypeArgumentFromInterface() {
assertThat(Utils.getFirstTypeArgumentFromInterface(TestDependentResource.class))
assertThat(Utils.getFirstTypeArgumentFromInterface(TestDependentResource.class,
DependentResource.class))
.isEqualTo(Deployment.class);
}

@Test
void getsFirstTypeArgumentFromInterfaceFromParent() {
assertThat(Utils.getFirstTypeArgumentFromSuperClassOrInterface(ConcreteReconciler.class,
Reconciler.class)).isEqualTo(ConfigMap.class);
}

public abstract static class AbstractReconciler<P extends HasMetadata> implements Reconciler<P> {


}

public static class ConcreteReconciler extends AbstractReconciler<ConfigMap> {

@Override
public UpdateControl<ConfigMap> reconcile(ConfigMap resource, Context<ConfigMap> context)
throws Exception {
return null;
}
}

public static class TestDependentResource
implements DependentResource<Deployment, TestCustomResource> {

Expand Down
6 changes: 6 additions & 0 deletions operator-framework/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.70</version>
<scope>test</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ public class RuntimeControllerMetadata {
RECONCILERS_RESOURCE_PATH, Reconciler.class, HasMetadata.class);
}

static <R extends HasMetadata> Class<R> getResourceClass(
Reconciler<R> reconciler) {
@SuppressWarnings("unchecked")
static <R extends HasMetadata> Class<R> getResourceClass(Reconciler<R> reconciler) {
final Class<? extends HasMetadata> resourceClass =
controllerToCustomResourceMappings.get(reconciler.getClass());
if (resourceClass == null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package io.javaoperatorsdk.operator;

import java.time.Duration;
import java.util.stream.IntStream;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
import io.javaoperatorsdk.operator.junit.LocalOperatorExtension;
import io.javaoperatorsdk.operator.sample.AbstractExecutionNumberRecordingReconciler;
import io.javaoperatorsdk.operator.sample.multipledependentresource.MultipleDependentResourceConfigMap;
import io.javaoperatorsdk.operator.sample.multipledependentresource.MultipleDependentResourceCustomResource;
import io.javaoperatorsdk.operator.sample.multipledependentresource.MultipleDependentResourceReconciler;

import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;

public abstract class AbstractMultipleDependentResourceIT<ReconcilerClass extends AbstractExecutionNumberRecordingReconciler<MultipleDependentResourceCustomResource>> {

public static final String TEST_RESOURCE_NAME = "multipledependentresource-testresource";
@RegisterExtension
LocalOperatorExtension operator =
LocalOperatorExtension.builder().withReconciler(getReconcilerClass())
.waitForNamespaceDeletion(true)
.build();

protected abstract Class<ReconcilerClass> getReconcilerClass();

@Test
void twoConfigMapsHaveBeenCreated() {
var customResource = createTestCustomResource();
operator.create(MultipleDependentResourceCustomResource.class, customResource);

var reconciler = operator.getReconcilerOfType(getReconcilerClass());

await().pollDelay(Duration.ofMillis(300))
.until(() -> reconciler.getNumberOfExecutions() <= 1);

IntStream.of(MultipleDependentResourceReconciler.FIRST_CONFIG_MAP_ID,
MultipleDependentResourceReconciler.SECOND_CONFIG_MAP_ID).forEach(configMapId -> {
ConfigMap configMap =
operator.get(ConfigMap.class, customResource.getConfigMapName(configMapId));
assertThat(configMap).isNotNull();
assertThat(configMap.getMetadata().getName())
.isEqualTo(customResource.getConfigMapName(configMapId));
assertThat(configMap.getData().get(MultipleDependentResourceConfigMap.DATA_KEY))
.isEqualTo(String.valueOf(configMapId));
});
}

public MultipleDependentResourceCustomResource createTestCustomResource() {
var resource = new MultipleDependentResourceCustomResource();
resource.setMetadata(
new ObjectMetaBuilder()
.withName(TEST_RESOURCE_NAME)
.withNamespace(operator.getNamespace())
.build());
return resource;
}
}
Original file line number Diff line number Diff line change
@@ -1,61 +1,12 @@
package io.javaoperatorsdk.operator;

import java.time.Duration;
import java.util.stream.IntStream;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
import io.javaoperatorsdk.operator.junit.LocalOperatorExtension;
import io.javaoperatorsdk.operator.sample.multipledependentresource.MultipleDependentResourceConfigMap;
import io.javaoperatorsdk.operator.sample.multipledependentresource.MultipleDependentResourceCustomResource;
import io.javaoperatorsdk.operator.sample.multipledependentresource.MultipleDependentResourceReconciler;

import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;

class MultipleDependentResourceIT {

public static final String TEST_RESOURCE_NAME = "multipledependentresource-testresource";
@RegisterExtension
LocalOperatorExtension operator =
LocalOperatorExtension.builder().withReconciler(MultipleDependentResourceReconciler.class)
.waitForNamespaceDeletion(true)
.build();

@Test
void twoConfigMapsHaveBeenCreated() {
MultipleDependentResourceCustomResource customResource = createTestCustomResource();
operator.create(MultipleDependentResourceCustomResource.class, customResource);
class MultipleDependentResourceIT
extends AbstractMultipleDependentResourceIT<MultipleDependentResourceReconciler> {

var reconciler = operator.getReconcilerOfType(MultipleDependentResourceReconciler.class);

await().pollDelay(Duration.ofMillis(300))
.until(() -> reconciler.getNumberOfExecutions() <= 1);

IntStream.of(MultipleDependentResourceReconciler.FIRST_CONFIG_MAP_ID,
MultipleDependentResourceReconciler.SECOND_CONFIG_MAP_ID).forEach(configMapId -> {
ConfigMap configMap =
operator.get(ConfigMap.class, customResource.getConfigMapName(configMapId));
assertThat(configMap).isNotNull();
assertThat(configMap.getMetadata().getName())
.isEqualTo(customResource.getConfigMapName(configMapId));
assertThat(configMap.getData().get(MultipleDependentResourceConfigMap.DATA_KEY))
.isEqualTo(String.valueOf(configMapId));
});
@Override
protected Class<MultipleDependentResourceReconciler> getReconcilerClass() {
return MultipleDependentResourceReconciler.class;
}

public MultipleDependentResourceCustomResource createTestCustomResource() {
MultipleDependentResourceCustomResource resource =
new MultipleDependentResourceCustomResource();
resource.setMetadata(
new ObjectMetaBuilder()
.withName(TEST_RESOURCE_NAME)
.withNamespace(operator.getNamespace())
.build());
return resource;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -81,16 +81,15 @@ void getDependentResources() {

@Test
void missingAnnotationThrowsException() {
Assertions.assertThrows(OperatorException.class, () -> {
new AnnotationControllerConfiguration<>(new MissingAnnotationReconciler());
});
Assertions.assertThrows(OperatorException.class,
() -> new AnnotationControllerConfiguration<>(new MissingAnnotationReconciler()));
}

@SuppressWarnings("rawtypes")
private DependentResourceSpec findByName(
List<DependentResourceSpec> dependentResourceSpecList, String name) {
return dependentResourceSpecList.stream().filter(d -> d.getName().equals(name)).findFirst()
.get();
.orElseThrow();
}

@SuppressWarnings("rawtypes")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.javaoperatorsdk.operator.sample;

import java.util.concurrent.atomic.AtomicInteger;

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider;

public abstract class AbstractExecutionNumberRecordingReconciler<P extends HasMetadata> implements
Reconciler<P>, TestExecutionInfoProvider {

private final AtomicInteger numberOfReconcileExecutions = new AtomicInteger(0);

protected int recordReconcileExecution() {
return numberOfReconcileExecutions.incrementAndGet();
}

public int getNumberOfExecutions() {
return numberOfReconcileExecutions.get();
}

@Override
public UpdateControl<P> reconcile(P resource, Context<P> context) throws Exception {
recordReconcileExecution();
return UpdateControl.noUpdate();
}
}
Loading