Skip to content

Commit

Permalink
feat: @KubernetesTest/KubernetesNamespacedTestExtension creates an ep…
Browse files Browse the repository at this point in the history
…hemeral Namespace optionally (can opt-out)

Signed-off-by: Marc Nuri <marc@marcnuri.com>
  • Loading branch information
manusa committed Jan 10, 2023
1 parent 43343f8 commit 7d03c3a
Show file tree
Hide file tree
Showing 8 changed files with 51 additions and 24 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* Fix #4694: [java-generator] Option to override the package name of the generated code.
* Fix #4720: interceptors close any response body if the response is not a 2xx response.
* Fix #4734: @KubernetesTest annotation can be used in base test classes
* Fix #4734: @KubernetesTest creates an ephemeral Namespace optionally (can opt-out)

#### Dependency Upgrade

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import org.junit.jupiter.api.extension.ExtensionContext;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
Expand All @@ -28,10 +29,12 @@

public interface BaseExtension {

ExtensionContext.Namespace getNamespace();
default ExtensionContext.Namespace getNamespace(ExtensionContext context) {
return ExtensionContext.Namespace.create(context.getRequiredTestClass());
}

default ExtensionContext.Store getStore(ExtensionContext context) {
return context.getRoot().getStore(getNamespace());
return context.getRoot().getStore(getNamespace(context));
}

default Field[] extractFields(ExtensionContext context, Class<?> clazz, Predicate<Field>... predicates) {
Expand Down Expand Up @@ -64,4 +67,15 @@ default void setFieldValue(Field field, Object entity, Object value) throws Ille
field.setAccessible(isAccessible);
}

default <T extends Annotation> T findAnnotation(Class<?> clazz, Class<T> annotation) {
if (clazz != null) {
if (clazz.isAnnotationPresent(annotation)) {
return clazz.getAnnotation(annotation);
} else if (clazz.getSuperclass() != null) {
return findAnnotation(clazz.getSuperclass(), annotation);
}
}
return null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package io.fabric8.junit.jupiter;

import io.fabric8.junit.jupiter.api.KubernetesTest;
import io.fabric8.kubernetes.api.model.Namespace;
import io.fabric8.kubernetes.api.model.NamespaceBuilder;
import io.fabric8.kubernetes.api.model.ObjectReference;
Expand All @@ -37,22 +38,20 @@
public class KubernetesNamespacedTestExtension
implements HasKubernetesClient, BeforeAllCallback, BeforeEachCallback, AfterAllCallback {

@Override
public ExtensionContext.Namespace getNamespace() {
return ExtensionContext.Namespace.create(KubernetesNamespacedTestExtension.class);
}

@Override
public void beforeAll(ExtensionContext context) throws Exception {
final KubernetesClient client = new KubernetesClientBuilder().build();
getStore(context).put(Namespace.class, initNamespace(client));
getStore(context).put(KubernetesClient.class,
client.adapt(NamespacedKubernetesClient.class).inNamespace(getNamespace(context).getMetadata().getName()));
getStore(context).put(KubernetesClient.class, client);
if (shouldCreateNamespace(context)) {
getStore(context).put(Namespace.class, initNamespace(client));
getStore(context).put(KubernetesClient.class,
client.adapt(NamespacedKubernetesClient.class).inNamespace(getKubernetesNamespace(context).getMetadata().getName()));
}
for (Field field : extractFields(context, KubernetesClient.class, f -> Modifier.isStatic(f.getModifiers()))) {
setFieldValue(field, null, getClient(context).adapt((Class<Client>) field.getType()));
}
for (Field field : extractFields(context, Namespace.class, f -> Modifier.isStatic(f.getModifiers()))) {
setFieldValue(field, null, getNamespace(context));
setFieldValue(field, null, getKubernetesNamespace(context));
}
}

Expand All @@ -62,14 +61,19 @@ public void beforeEach(ExtensionContext context) throws Exception {
setFieldValue(field, context.getRequiredTestInstance(), getClient(context).adapt((Class<Client>) field.getType()));
}
for (Field field : extractFields(context, Namespace.class, f -> !Modifier.isStatic(f.getModifiers()))) {
setFieldValue(field, context.getRequiredTestInstance(), getNamespace(context));
setFieldValue(field, context.getRequiredTestInstance(), getKubernetesNamespace(context));
}
}

@Override
public void afterAll(ExtensionContext context) {
final KubernetesClient client = getClient(context);
client.resource(getNamespace(context)).withGracePeriod(0L).delete();
if (shouldCreateNamespace(context)) {
client.resource(getKubernetesNamespace(context)).withGracePeriod(0L).delete();
}
// Note that the ThreadPoolExecutor in OkHttp's RealConnectionPool is shared amongst all the OkHttp client
// instances. This means that closing one OkHttp client instance effectively closes all the others.
// In order to be able to use this safely, we should transition to one of the other HttpClient implementations
client.close();
}

Expand Down Expand Up @@ -109,7 +113,12 @@ private static Namespace initNamespace(KubernetesClient client) {
return namespace;
}

private Namespace getNamespace(ExtensionContext context) {
private boolean shouldCreateNamespace(ExtensionContext context) {
final KubernetesTest annotation = findAnnotation(context.getRequiredTestClass(), KubernetesTest.class);
return annotation == null || annotation.createEphemeralNamespace();
}

private Namespace getKubernetesNamespace(ExtensionContext context) {
final Namespace namespace = getStore(context).get(Namespace.class, Namespace.class);
if (namespace == null) {
throw new IllegalStateException("No Kubernetes Namespace found");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,6 @@
*/
public class LoadKubernetesManifestsExtension implements HasKubernetesClient, BeforeAllCallback, AfterAllCallback {

@Override
public ExtensionContext.Namespace getNamespace() {
// Share context with KubernetesNamespacedTestExtension to be able to consume the client
return ExtensionContext.Namespace.create(KubernetesNamespacedTestExtension.class);
}

@Override
public void beforeAll(ExtensionContext context) {
final LoadKubernetesManifests annotation = context.getRequiredTestClass()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,13 @@
* Enables and configures the {@link KubernetesNamespacedTestExtension} extension.
*
* <p>
* Creates a namespace and configures a {@link KubernetesClient} instance that will
* Creates a {@link KubernetesClient} instance that will
* be automatically injected into tests.
*
* <p>
* Optionally, creates a Namespace for the tests and configures the client to use it. The Namespace
* is deleted after the test suite execution.
*
* <pre>{@code
* &#64;KubernetesTest
* class MyTest {
Expand All @@ -46,4 +50,8 @@
@Retention(RUNTIME)
@ExtendWith(KubernetesNamespacedTestExtension.class)
public @interface KubernetesTest {
/**
* Create an ephemeral Namespace for the test.
*/
boolean createEphemeralNamespace() default true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
*/
@Target({ TYPE, METHOD, ANNOTATION_TYPE })
@Retention(RUNTIME)
@ExtendWith({ KubernetesNamespacedTestExtension.class, LoadKubernetesManifestsExtension.class })
@ExtendWith(KubernetesNamespacedTestExtension.class)
@ExtendWith(LoadKubernetesManifestsExtension.class)
public @interface LoadKubernetesManifests {

String[] value();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

import static org.assertj.core.api.Assertions.assertThat;

@KubernetesTest
@KubernetesTest(createEphemeralNamespace = false)
// Due to the way the extensions interact with the test, this test class can't be parameterized
class KubernetesTestTest {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

import io.fabric8.kubernetes.client.KubernetesClient;

@KubernetesTest
@KubernetesTest(createEphemeralNamespace = false)
public class KubernetesTestWithSuperClass {

static KubernetesClient staticSuperClient;
Expand Down

0 comments on commit 7d03c3a

Please sign in to comment.