diff --git a/operator-framework-junit5/pom.xml b/operator-framework-junit5/pom.xml
index 9366485f25..4bbd35e145 100644
--- a/operator-framework-junit5/pom.xml
+++ b/operator-framework-junit5/pom.xml
@@ -39,6 +39,11 @@
org.awaitility
awaitility
+
+ org.mockito
+ mockito-core
+ test
+
\ No newline at end of file
diff --git a/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/AbstractOperatorExtension.java b/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/AbstractOperatorExtension.java
index 9a86bdfbbf..1ce14b73ed 100644
--- a/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/AbstractOperatorExtension.java
+++ b/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/AbstractOperatorExtension.java
@@ -4,10 +4,9 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import java.util.Locale;
-import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
+import java.util.function.Function;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.extension.AfterAllCallback;
@@ -23,7 +22,6 @@
import io.fabric8.kubernetes.client.KubernetesClientBuilder;
import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation;
import io.fabric8.kubernetes.client.dsl.Resource;
-import io.fabric8.kubernetes.client.utils.KubernetesResourceUtil;
import io.fabric8.kubernetes.client.utils.Utils;
import io.javaoperatorsdk.operator.api.config.ConfigurationServiceOverrider;
@@ -34,6 +32,7 @@ public abstract class AbstractOperatorExtension implements HasKubernetesClient,
AfterEachCallback {
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractOperatorExtension.class);
+ public static final int MAX_NAMESPACE_NAME_LENGTH = 63;
public static final int CRD_READY_WAIT = 2000;
public static final int DEFAULT_NAMESPACE_DELETE_TIMEOUT = 90;
@@ -44,6 +43,8 @@ public abstract class AbstractOperatorExtension implements HasKubernetesClient,
protected final boolean preserveNamespaceOnError;
protected final boolean waitForNamespaceDeletion;
protected final int namespaceDeleteTimeout = DEFAULT_NAMESPACE_DELETE_TIMEOUT;
+ protected final Function namespaceNameSupplier;
+ protected final Function perClassNamespaceNameSupplier;
protected String namespace;
@@ -53,7 +54,9 @@ protected AbstractOperatorExtension(
boolean oneNamespacePerClass,
boolean preserveNamespaceOnError,
boolean waitForNamespaceDeletion,
- KubernetesClient kubernetesClient) {
+ KubernetesClient kubernetesClient,
+ Function namespaceNameSupplier,
+ Function perClassNamespaceNameSupplier) {
this.kubernetesClient = kubernetesClient != null ? kubernetesClient
: new KubernetesClientBuilder().build();
this.infrastructure = infrastructure;
@@ -61,6 +64,8 @@ protected AbstractOperatorExtension(
this.oneNamespacePerClass = oneNamespacePerClass;
this.preserveNamespaceOnError = preserveNamespaceOnError;
this.waitForNamespaceDeletion = waitForNamespaceDeletion;
+ this.namespaceNameSupplier = namespaceNameSupplier;
+ this.perClassNamespaceNameSupplier = perClassNamespaceNameSupplier;
}
@@ -132,26 +137,14 @@ public boolean delete(Class type, T resource) {
protected void beforeAllImpl(ExtensionContext context) {
if (oneNamespacePerClass) {
- namespace = context.getRequiredTestClass().getSimpleName();
- namespace += "-";
- namespace += UUID.randomUUID();
- namespace = KubernetesResourceUtil.sanitizeName(namespace).toLowerCase(Locale.US);
- namespace = namespace.substring(0, Math.min(namespace.length(), 63));
-
+ namespace = perClassNamespaceNameSupplier.apply(context);
before(context);
}
}
protected void beforeEachImpl(ExtensionContext context) {
if (!oneNamespacePerClass) {
- namespace = context.getRequiredTestClass().getSimpleName();
- namespace += "-";
- namespace += context.getRequiredTestMethod().getName();
- namespace += "-";
- namespace += UUID.randomUUID();
- namespace = KubernetesResourceUtil.sanitizeName(namespace).toLowerCase(Locale.US);
- namespace = namespace.substring(0, Math.min(namespace.length(), 63));
-
+ namespace = namespaceNameSupplier.apply(context);
before(context);
}
}
@@ -219,6 +212,10 @@ public static abstract class AbstractBuilder> {
protected boolean oneNamespacePerClass;
protected int namespaceDeleteTimeout;
protected Consumer configurationServiceOverrider;
+ protected Function namespaceNameSupplier =
+ new DefaultNamespaceNameSupplier();
+ protected Function perClassNamespaceNameSupplier =
+ new DefaultPerClassNamespaceNameSupplier();
protected AbstractBuilder() {
this.infrastructure = new ArrayList<>();
@@ -280,5 +277,17 @@ public T withNamespaceDeleteTimeout(int timeout) {
this.namespaceDeleteTimeout = timeout;
return (T) this;
}
+
+ public AbstractBuilder withNamespaceNameSupplier(
+ Function namespaceNameSupplier) {
+ this.namespaceNameSupplier = namespaceNameSupplier;
+ return this;
+ }
+
+ public AbstractBuilder withPerClassNamespaceNameSupplier(
+ Function perClassNamespaceNameSupplier) {
+ this.perClassNamespaceNameSupplier = perClassNamespaceNameSupplier;
+ return this;
+ }
}
}
diff --git a/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java b/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java
index ec1bff4064..b2ec773fcc 100644
--- a/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java
+++ b/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java
@@ -11,6 +11,7 @@
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
+import java.util.function.Function;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.slf4j.Logger;
@@ -37,11 +38,13 @@ private ClusterDeployedOperatorExtension(
boolean preserveNamespaceOnError,
boolean waitForNamespaceDeletion,
boolean oneNamespacePerClass,
- KubernetesClient kubernetesClient) {
+ KubernetesClient kubernetesClient,
+ Function namespaceNameSupplier,
+ Function perClassNamespaceNameSupplier) {
super(infrastructure, infrastructureTimeout, oneNamespacePerClass,
preserveNamespaceOnError,
waitForNamespaceDeletion,
- kubernetesClient);
+ kubernetesClient, namespaceNameSupplier, perClassNamespaceNameSupplier);
this.operatorDeployment = operatorDeployment;
this.operatorDeploymentTimeout = operatorDeploymentTimeout;
}
@@ -152,7 +155,9 @@ public ClusterDeployedOperatorExtension build() {
preserveNamespaceOnError,
waitForNamespaceDeletion,
oneNamespacePerClass,
- kubernetesClient != null ? kubernetesClient : new KubernetesClientBuilder().build());
+ kubernetesClient != null ? kubernetesClient : new KubernetesClientBuilder().build(),
+ namespaceNameSupplier,
+ perClassNamespaceNameSupplier);
}
}
}
diff --git a/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/DefaultNamespaceNameSupplier.java b/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/DefaultNamespaceNameSupplier.java
new file mode 100644
index 0000000000..3041ed0f31
--- /dev/null
+++ b/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/DefaultNamespaceNameSupplier.java
@@ -0,0 +1,49 @@
+package io.javaoperatorsdk.operator.junit;
+
+import java.util.Locale;
+import java.util.UUID;
+import java.util.function.Function;
+
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+import io.fabric8.kubernetes.client.utils.KubernetesResourceUtil;
+
+import static io.javaoperatorsdk.operator.junit.AbstractOperatorExtension.MAX_NAMESPACE_NAME_LENGTH;
+
+public class DefaultNamespaceNameSupplier implements Function {
+
+ public static final int RANDOM_SUFFIX_LENGTH = 5;
+ public static final int DELIMITERS_LENGTH = 2;
+
+ public static final int MAX_NAME_LENGTH_TOGETHER =
+ MAX_NAMESPACE_NAME_LENGTH - DELIMITERS_LENGTH - RANDOM_SUFFIX_LENGTH;
+ public static final int PART_RESERVED_NAME_LENGTH = MAX_NAME_LENGTH_TOGETHER / 2;
+
+ public static final String DELIMITER = "-";
+
+ @Override
+ public String apply(ExtensionContext context) {
+ String classPart = context.getRequiredTestClass().getSimpleName();
+ String methodPart = context.getRequiredTestMethod().getName();
+ if (classPart.length() + methodPart.length() + DELIMITERS_LENGTH
+ + RANDOM_SUFFIX_LENGTH > MAX_NAMESPACE_NAME_LENGTH) {
+ if (classPart.length() > PART_RESERVED_NAME_LENGTH) {
+ int classPartMaxLength =
+ methodPart.length() > PART_RESERVED_NAME_LENGTH ? PART_RESERVED_NAME_LENGTH
+ : MAX_NAME_LENGTH_TOGETHER - methodPart.length();
+ classPart = classPart.substring(0, Math.min(classPartMaxLength, classPart.length()));
+ }
+ if (methodPart.length() > PART_RESERVED_NAME_LENGTH) {
+ int methodPartMaxLength =
+ classPart.length() > PART_RESERVED_NAME_LENGTH ? PART_RESERVED_NAME_LENGTH
+ : MAX_NAME_LENGTH_TOGETHER - classPart.length();
+ methodPart = methodPart.substring(0, Math.min(methodPartMaxLength, methodPart.length()));
+ }
+ }
+
+ String namespace = classPart + DELIMITER + methodPart + DELIMITER + UUID.randomUUID().toString()
+ .substring(0, RANDOM_SUFFIX_LENGTH);
+ namespace = KubernetesResourceUtil.sanitizeName(namespace).toLowerCase(Locale.US);
+ return namespace;
+ }
+}
diff --git a/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/DefaultPerClassNamespaceNameSupplier.java b/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/DefaultPerClassNamespaceNameSupplier.java
new file mode 100644
index 0000000000..b184a3fc3d
--- /dev/null
+++ b/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/DefaultPerClassNamespaceNameSupplier.java
@@ -0,0 +1,32 @@
+package io.javaoperatorsdk.operator.junit;
+
+import java.util.Locale;
+import java.util.UUID;
+import java.util.function.Function;
+
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+import io.fabric8.kubernetes.client.utils.KubernetesResourceUtil;
+
+import static io.javaoperatorsdk.operator.junit.AbstractOperatorExtension.MAX_NAMESPACE_NAME_LENGTH;
+import static io.javaoperatorsdk.operator.junit.DefaultNamespaceNameSupplier.DELIMITER;
+import static io.javaoperatorsdk.operator.junit.DefaultNamespaceNameSupplier.RANDOM_SUFFIX_LENGTH;
+
+public class DefaultPerClassNamespaceNameSupplier implements Function {
+
+ public static final int MAX_CLASS_NAME_LENGTH =
+ MAX_NAMESPACE_NAME_LENGTH - RANDOM_SUFFIX_LENGTH - 1;
+
+ @Override
+ public String apply(ExtensionContext context) {
+ String className = context.getRequiredTestClass().getSimpleName();
+ String namespace =
+ className.length() > MAX_CLASS_NAME_LENGTH ? className.substring(0, MAX_CLASS_NAME_LENGTH)
+ : className;
+ namespace += DELIMITER;
+ namespace += UUID.randomUUID().toString().substring(0, RANDOM_SUFFIX_LENGTH);
+ namespace = KubernetesResourceUtil.sanitizeName(namespace).toLowerCase(Locale.US);
+ namespace = namespace.substring(0, Math.min(namespace.length(), MAX_NAMESPACE_NAME_LENGTH));
+ return namespace;
+ }
+}
diff --git a/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtension.java b/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtension.java
index 7f7cb64b28..61916f14bc 100644
--- a/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtension.java
+++ b/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtension.java
@@ -9,6 +9,7 @@
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -53,14 +54,18 @@ private LocallyRunOperatorExtension(
boolean waitForNamespaceDeletion,
boolean oneNamespacePerClass,
KubernetesClient kubernetesClient,
- Consumer configurationServiceOverrider) {
+ Consumer configurationServiceOverrider,
+ Function namespaceNameSupplier,
+ Function perClassNamespaceNameSupplier) {
super(
infrastructure,
infrastructureTimeout,
oneNamespacePerClass,
preserveNamespaceOnError,
waitForNamespaceDeletion,
- kubernetesClient);
+ kubernetesClient,
+ namespaceNameSupplier,
+ perClassNamespaceNameSupplier);
this.reconcilers = reconcilers;
this.portForwards = portForwards;
this.localPortForwards = new ArrayList<>(portForwards.size());
@@ -285,7 +290,7 @@ public LocallyRunOperatorExtension build() {
waitForNamespaceDeletion,
oneNamespacePerClass,
kubernetesClient,
- configurationServiceOverrider);
+ configurationServiceOverrider, namespaceNameSupplier, perClassNamespaceNameSupplier);
}
}
diff --git a/operator-framework-junit5/src/test/java/io/javaoperatorsdk/operator/junit/DefaultNamespaceNameSupplierTest.java b/operator-framework-junit5/src/test/java/io/javaoperatorsdk/operator/junit/DefaultNamespaceNameSupplierTest.java
new file mode 100644
index 0000000000..cd1dce1a51
--- /dev/null
+++ b/operator-framework-junit5/src/test/java/io/javaoperatorsdk/operator/junit/DefaultNamespaceNameSupplierTest.java
@@ -0,0 +1,56 @@
+package io.javaoperatorsdk.operator.junit;
+
+import org.junit.jupiter.api.Test;
+
+import static io.javaoperatorsdk.operator.junit.AbstractOperatorExtension.MAX_NAMESPACE_NAME_LENGTH;
+import static io.javaoperatorsdk.operator.junit.DefaultNamespaceNameSupplier.*;
+import static io.javaoperatorsdk.operator.junit.NamespaceNamingTestUtils.*;
+import static org.assertj.core.api.Assertions.assertThat;
+
+class DefaultNamespaceNameSupplierTest {
+
+
+ DefaultNamespaceNameSupplier supplier = new DefaultNamespaceNameSupplier();
+
+ @Test
+ void trivialCase() {
+ String ns = supplier.apply(mockExtensionContext(SHORT_CLASS_NAME, SHORT_METHOD_NAME));
+
+ assertThat(ns).startsWith(SHORT_CLASS_NAME + DELIMITER + SHORT_METHOD_NAME + DELIMITER);
+ shortEnoughAndEndsWithRandomString(ns);
+ }
+
+ @Test
+ void classPartLongerCase() {
+ String ns = supplier.apply(mockExtensionContext(LONG_CLASS_NAME, SHORT_METHOD_NAME));
+
+ assertThat(ns).startsWith(LONG_CLASS_NAME + DELIMITER + SHORT_METHOD_NAME + DELIMITER);
+ shortEnoughAndEndsWithRandomString(ns);
+ }
+
+ @Test
+ void methodPartLonger() {
+ String ns = supplier.apply(mockExtensionContext(SHORT_CLASS_NAME, LONG_METHOD_NAME));
+
+ assertThat(ns).startsWith(SHORT_CLASS_NAME + DELIMITER + LONG_METHOD_NAME + DELIMITER);
+ shortEnoughAndEndsWithRandomString(ns);
+ }
+
+ @Test
+ void methodPartAndClassPartLonger() {
+ String ns = supplier.apply(mockExtensionContext(LONG_CLASS_NAME, LONG_METHOD_NAME));
+
+ assertThat(ns).startsWith(LONG_CLASS_NAME.substring(0, PART_RESERVED_NAME_LENGTH) + DELIMITER
+ + LONG_METHOD_NAME.substring(0, PART_RESERVED_NAME_LENGTH)
+ + DELIMITER);
+ shortEnoughAndEndsWithRandomString(ns);
+ }
+
+
+ private static void shortEnoughAndEndsWithRandomString(String ns) {
+ assertThat(ns.length()).isLessThanOrEqualTo(MAX_NAMESPACE_NAME_LENGTH);
+ assertThat(ns.split("-")[2]).hasSize(RANDOM_SUFFIX_LENGTH);
+ }
+
+
+}
diff --git a/operator-framework-junit5/src/test/java/io/javaoperatorsdk/operator/junit/DefaultPerClassNamespaceNameSupplierTest.java b/operator-framework-junit5/src/test/java/io/javaoperatorsdk/operator/junit/DefaultPerClassNamespaceNameSupplierTest.java
new file mode 100644
index 0000000000..40e240cbd1
--- /dev/null
+++ b/operator-framework-junit5/src/test/java/io/javaoperatorsdk/operator/junit/DefaultPerClassNamespaceNameSupplierTest.java
@@ -0,0 +1,44 @@
+package io.javaoperatorsdk.operator.junit;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+import static io.javaoperatorsdk.operator.junit.AbstractOperatorExtension.MAX_NAMESPACE_NAME_LENGTH;
+import static io.javaoperatorsdk.operator.junit.DefaultNamespaceNameSupplier.DELIMITER;
+import static io.javaoperatorsdk.operator.junit.DefaultNamespaceNameSupplier.RANDOM_SUFFIX_LENGTH;
+import static io.javaoperatorsdk.operator.junit.DefaultPerClassNamespaceNameSupplier.MAX_CLASS_NAME_LENGTH;
+import static io.javaoperatorsdk.operator.junit.NamespaceNamingTestUtils.SHORT_CLASS_NAME;
+import static io.javaoperatorsdk.operator.junit.NamespaceNamingTestUtils.VERY_LONG_CLASS_NAME;
+import static org.assertj.core.api.Assertions.assertThat;
+
+class DefaultPerClassNamespaceNameSupplierTest {
+
+ DefaultPerClassNamespaceNameSupplier supplier = new DefaultPerClassNamespaceNameSupplier();
+
+ @Test
+ void shortClassCase() {
+ var ns = supplier.apply(mockExtensionContext(SHORT_CLASS_NAME));
+
+ assertThat(ns).startsWith(SHORT_CLASS_NAME + DELIMITER);
+ shortEnoughAndEndsWithRandomString(ns);
+ }
+
+ @Test
+ void longClassCase() {
+ var ns = supplier.apply(mockExtensionContext(VERY_LONG_CLASS_NAME));
+
+ assertThat(ns).startsWith(VERY_LONG_CLASS_NAME.substring(0, MAX_CLASS_NAME_LENGTH) + DELIMITER);
+ shortEnoughAndEndsWithRandomString(ns);
+ assertThat(ns).hasSize(MAX_NAMESPACE_NAME_LENGTH);
+ }
+
+ public static ExtensionContext mockExtensionContext(String className) {
+ return NamespaceNamingTestUtils.mockExtensionContext(className, null);
+ }
+
+ private static void shortEnoughAndEndsWithRandomString(String ns) {
+ assertThat(ns.length()).isLessThanOrEqualTo(MAX_NAMESPACE_NAME_LENGTH);
+ assertThat(ns.split("-")[1]).hasSize(RANDOM_SUFFIX_LENGTH);
+ }
+
+}
diff --git a/operator-framework-junit5/src/test/java/io/javaoperatorsdk/operator/junit/NamespaceNamingTestUtils.java b/operator-framework-junit5/src/test/java/io/javaoperatorsdk/operator/junit/NamespaceNamingTestUtils.java
new file mode 100644
index 0000000000..0443d0e983
--- /dev/null
+++ b/operator-framework-junit5/src/test/java/io/javaoperatorsdk/operator/junit/NamespaceNamingTestUtils.java
@@ -0,0 +1,53 @@
+package io.javaoperatorsdk.operator.junit;
+
+import java.lang.reflect.Method;
+
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class NamespaceNamingTestUtils {
+
+ public static final String SHORT_CLASS_NAME = Method.class.getSimpleName().toLowerCase();
+ public static final String SHORT_METHOD_NAME = "short";
+ public static final String LONG_METHOD_NAME = "longmethodnametotestifistruncatedcorrectly";
+ public static final String LONG_CLASS_NAME = VeryLongClassNameForSakeOfThisTestIfItWorks.class
+ .getSimpleName().toLowerCase();
+ // longer then 63
+ public static final String VERY_LONG_CLASS_NAME =
+ VeryVeryVeryVeryVeryVeryLongClassNameForSakeOfThisTestIfItWorks.class
+ .getSimpleName().toLowerCase();
+
+ public static ExtensionContext mockExtensionContext(String className, String methodName) {
+ ExtensionContext extensionContext = mock(ExtensionContext.class);
+ Method method = mock(Method.class);
+
+ Class clazz;
+ if (className.equals(SHORT_CLASS_NAME)) {
+ clazz = Method.class;
+ } else if (className.equals(LONG_CLASS_NAME)) {
+ clazz = VeryLongClassNameForSakeOfThisTestIfItWorks.class;
+ } else if (className.equals(VERY_LONG_CLASS_NAME)) {
+ clazz = VeryVeryVeryVeryVeryVeryLongClassNameForSakeOfThisTestIfItWorks.class;
+ } else {
+ throw new IllegalArgumentException();
+ }
+
+ when(method.getName()).thenReturn(methodName);
+ when(extensionContext.getRequiredTestMethod()).thenReturn(method);
+ when(extensionContext.getRequiredTestClass()).thenReturn(clazz);
+
+ return extensionContext;
+ }
+
+
+ public static class VeryVeryVeryVeryVeryVeryLongClassNameForSakeOfThisTestIfItWorks {
+
+ }
+
+ public static class VeryLongClassNameForSakeOfThisTestIfItWorks {
+
+ }
+
+}