Skip to content

Commit 01efeda

Browse files
authored
improve: better namespace naming for junit extension and custom patterns (#2171)
Signed-off-by: Attila Mészáros <[email protected]>
1 parent 73c6ead commit 01efeda

File tree

9 files changed

+282
-24
lines changed

9 files changed

+282
-24
lines changed

operator-framework-junit5/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@
3939
<groupId>org.awaitility</groupId>
4040
<artifactId>awaitility</artifactId>
4141
</dependency>
42+
<dependency>
43+
<groupId>org.mockito</groupId>
44+
<artifactId>mockito-core</artifactId>
45+
<scope>test</scope>
46+
</dependency>
4247
</dependencies>
4348

4449
</project>

operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/AbstractOperatorExtension.java

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44
import java.util.ArrayList;
55
import java.util.Arrays;
66
import java.util.List;
7-
import java.util.Locale;
8-
import java.util.UUID;
97
import java.util.concurrent.TimeUnit;
108
import java.util.function.Consumer;
9+
import java.util.function.Function;
1110

1211
import org.awaitility.Awaitility;
1312
import org.junit.jupiter.api.extension.AfterAllCallback;
@@ -23,7 +22,6 @@
2322
import io.fabric8.kubernetes.client.KubernetesClientBuilder;
2423
import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation;
2524
import io.fabric8.kubernetes.client.dsl.Resource;
26-
import io.fabric8.kubernetes.client.utils.KubernetesResourceUtil;
2725
import io.fabric8.kubernetes.client.utils.Utils;
2826
import io.javaoperatorsdk.operator.api.config.ConfigurationServiceOverrider;
2927

@@ -34,6 +32,7 @@ public abstract class AbstractOperatorExtension implements HasKubernetesClient,
3432
AfterEachCallback {
3533

3634
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractOperatorExtension.class);
35+
public static final int MAX_NAMESPACE_NAME_LENGTH = 63;
3736
public static final int CRD_READY_WAIT = 2000;
3837
public static final int DEFAULT_NAMESPACE_DELETE_TIMEOUT = 90;
3938

@@ -44,6 +43,8 @@ public abstract class AbstractOperatorExtension implements HasKubernetesClient,
4443
protected final boolean preserveNamespaceOnError;
4544
protected final boolean waitForNamespaceDeletion;
4645
protected final int namespaceDeleteTimeout = DEFAULT_NAMESPACE_DELETE_TIMEOUT;
46+
protected final Function<ExtensionContext, String> namespaceNameSupplier;
47+
protected final Function<ExtensionContext, String> perClassNamespaceNameSupplier;
4748

4849
protected String namespace;
4950

@@ -53,14 +54,18 @@ protected AbstractOperatorExtension(
5354
boolean oneNamespacePerClass,
5455
boolean preserveNamespaceOnError,
5556
boolean waitForNamespaceDeletion,
56-
KubernetesClient kubernetesClient) {
57+
KubernetesClient kubernetesClient,
58+
Function<ExtensionContext, String> namespaceNameSupplier,
59+
Function<ExtensionContext, String> perClassNamespaceNameSupplier) {
5760
this.kubernetesClient = kubernetesClient != null ? kubernetesClient
5861
: new KubernetesClientBuilder().build();
5962
this.infrastructure = infrastructure;
6063
this.infrastructureTimeout = infrastructureTimeout;
6164
this.oneNamespacePerClass = oneNamespacePerClass;
6265
this.preserveNamespaceOnError = preserveNamespaceOnError;
6366
this.waitForNamespaceDeletion = waitForNamespaceDeletion;
67+
this.namespaceNameSupplier = namespaceNameSupplier;
68+
this.perClassNamespaceNameSupplier = perClassNamespaceNameSupplier;
6469
}
6570

6671

@@ -132,26 +137,14 @@ public <T extends HasMetadata> boolean delete(Class<T> type, T resource) {
132137

133138
protected void beforeAllImpl(ExtensionContext context) {
134139
if (oneNamespacePerClass) {
135-
namespace = context.getRequiredTestClass().getSimpleName();
136-
namespace += "-";
137-
namespace += UUID.randomUUID();
138-
namespace = KubernetesResourceUtil.sanitizeName(namespace).toLowerCase(Locale.US);
139-
namespace = namespace.substring(0, Math.min(namespace.length(), 63));
140-
140+
namespace = perClassNamespaceNameSupplier.apply(context);
141141
before(context);
142142
}
143143
}
144144

145145
protected void beforeEachImpl(ExtensionContext context) {
146146
if (!oneNamespacePerClass) {
147-
namespace = context.getRequiredTestClass().getSimpleName();
148-
namespace += "-";
149-
namespace += context.getRequiredTestMethod().getName();
150-
namespace += "-";
151-
namespace += UUID.randomUUID();
152-
namespace = KubernetesResourceUtil.sanitizeName(namespace).toLowerCase(Locale.US);
153-
namespace = namespace.substring(0, Math.min(namespace.length(), 63));
154-
147+
namespace = namespaceNameSupplier.apply(context);
155148
before(context);
156149
}
157150
}
@@ -219,6 +212,10 @@ public static abstract class AbstractBuilder<T extends AbstractBuilder<T>> {
219212
protected boolean oneNamespacePerClass;
220213
protected int namespaceDeleteTimeout;
221214
protected Consumer<ConfigurationServiceOverrider> configurationServiceOverrider;
215+
protected Function<ExtensionContext, String> namespaceNameSupplier =
216+
new DefaultNamespaceNameSupplier();
217+
protected Function<ExtensionContext, String> perClassNamespaceNameSupplier =
218+
new DefaultPerClassNamespaceNameSupplier();
222219

223220
protected AbstractBuilder() {
224221
this.infrastructure = new ArrayList<>();
@@ -280,5 +277,17 @@ public T withNamespaceDeleteTimeout(int timeout) {
280277
this.namespaceDeleteTimeout = timeout;
281278
return (T) this;
282279
}
280+
281+
public AbstractBuilder<T> withNamespaceNameSupplier(
282+
Function<ExtensionContext, String> namespaceNameSupplier) {
283+
this.namespaceNameSupplier = namespaceNameSupplier;
284+
return this;
285+
}
286+
287+
public AbstractBuilder<T> withPerClassNamespaceNameSupplier(
288+
Function<ExtensionContext, String> perClassNamespaceNameSupplier) {
289+
this.perClassNamespaceNameSupplier = perClassNamespaceNameSupplier;
290+
return this;
291+
}
283292
}
284293
}

operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import java.util.Objects;
1212
import java.util.concurrent.TimeUnit;
1313
import java.util.function.Consumer;
14+
import java.util.function.Function;
1415

1516
import org.junit.jupiter.api.extension.ExtensionContext;
1617
import org.slf4j.Logger;
@@ -37,11 +38,13 @@ private ClusterDeployedOperatorExtension(
3738
boolean preserveNamespaceOnError,
3839
boolean waitForNamespaceDeletion,
3940
boolean oneNamespacePerClass,
40-
KubernetesClient kubernetesClient) {
41+
KubernetesClient kubernetesClient,
42+
Function<ExtensionContext, String> namespaceNameSupplier,
43+
Function<ExtensionContext, String> perClassNamespaceNameSupplier) {
4144
super(infrastructure, infrastructureTimeout, oneNamespacePerClass,
4245
preserveNamespaceOnError,
4346
waitForNamespaceDeletion,
44-
kubernetesClient);
47+
kubernetesClient, namespaceNameSupplier, perClassNamespaceNameSupplier);
4548
this.operatorDeployment = operatorDeployment;
4649
this.operatorDeploymentTimeout = operatorDeploymentTimeout;
4750
}
@@ -152,7 +155,9 @@ public ClusterDeployedOperatorExtension build() {
152155
preserveNamespaceOnError,
153156
waitForNamespaceDeletion,
154157
oneNamespacePerClass,
155-
kubernetesClient != null ? kubernetesClient : new KubernetesClientBuilder().build());
158+
kubernetesClient != null ? kubernetesClient : new KubernetesClientBuilder().build(),
159+
namespaceNameSupplier,
160+
perClassNamespaceNameSupplier);
156161
}
157162
}
158163
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package io.javaoperatorsdk.operator.junit;
2+
3+
import java.util.Locale;
4+
import java.util.UUID;
5+
import java.util.function.Function;
6+
7+
import org.junit.jupiter.api.extension.ExtensionContext;
8+
9+
import io.fabric8.kubernetes.client.utils.KubernetesResourceUtil;
10+
11+
import static io.javaoperatorsdk.operator.junit.AbstractOperatorExtension.MAX_NAMESPACE_NAME_LENGTH;
12+
13+
public class DefaultNamespaceNameSupplier implements Function<ExtensionContext, String> {
14+
15+
public static final int RANDOM_SUFFIX_LENGTH = 5;
16+
public static final int DELIMITERS_LENGTH = 2;
17+
18+
public static final int MAX_NAME_LENGTH_TOGETHER =
19+
MAX_NAMESPACE_NAME_LENGTH - DELIMITERS_LENGTH - RANDOM_SUFFIX_LENGTH;
20+
public static final int PART_RESERVED_NAME_LENGTH = MAX_NAME_LENGTH_TOGETHER / 2;
21+
22+
public static final String DELIMITER = "-";
23+
24+
@Override
25+
public String apply(ExtensionContext context) {
26+
String classPart = context.getRequiredTestClass().getSimpleName();
27+
String methodPart = context.getRequiredTestMethod().getName();
28+
if (classPart.length() + methodPart.length() + DELIMITERS_LENGTH
29+
+ RANDOM_SUFFIX_LENGTH > MAX_NAMESPACE_NAME_LENGTH) {
30+
if (classPart.length() > PART_RESERVED_NAME_LENGTH) {
31+
int classPartMaxLength =
32+
methodPart.length() > PART_RESERVED_NAME_LENGTH ? PART_RESERVED_NAME_LENGTH
33+
: MAX_NAME_LENGTH_TOGETHER - methodPart.length();
34+
classPart = classPart.substring(0, Math.min(classPartMaxLength, classPart.length()));
35+
}
36+
if (methodPart.length() > PART_RESERVED_NAME_LENGTH) {
37+
int methodPartMaxLength =
38+
classPart.length() > PART_RESERVED_NAME_LENGTH ? PART_RESERVED_NAME_LENGTH
39+
: MAX_NAME_LENGTH_TOGETHER - classPart.length();
40+
methodPart = methodPart.substring(0, Math.min(methodPartMaxLength, methodPart.length()));
41+
}
42+
}
43+
44+
String namespace = classPart + DELIMITER + methodPart + DELIMITER + UUID.randomUUID().toString()
45+
.substring(0, RANDOM_SUFFIX_LENGTH);
46+
namespace = KubernetesResourceUtil.sanitizeName(namespace).toLowerCase(Locale.US);
47+
return namespace;
48+
}
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package io.javaoperatorsdk.operator.junit;
2+
3+
import java.util.Locale;
4+
import java.util.UUID;
5+
import java.util.function.Function;
6+
7+
import org.junit.jupiter.api.extension.ExtensionContext;
8+
9+
import io.fabric8.kubernetes.client.utils.KubernetesResourceUtil;
10+
11+
import static io.javaoperatorsdk.operator.junit.AbstractOperatorExtension.MAX_NAMESPACE_NAME_LENGTH;
12+
import static io.javaoperatorsdk.operator.junit.DefaultNamespaceNameSupplier.DELIMITER;
13+
import static io.javaoperatorsdk.operator.junit.DefaultNamespaceNameSupplier.RANDOM_SUFFIX_LENGTH;
14+
15+
public class DefaultPerClassNamespaceNameSupplier implements Function<ExtensionContext, String> {
16+
17+
public static final int MAX_CLASS_NAME_LENGTH =
18+
MAX_NAMESPACE_NAME_LENGTH - RANDOM_SUFFIX_LENGTH - 1;
19+
20+
@Override
21+
public String apply(ExtensionContext context) {
22+
String className = context.getRequiredTestClass().getSimpleName();
23+
String namespace =
24+
className.length() > MAX_CLASS_NAME_LENGTH ? className.substring(0, MAX_CLASS_NAME_LENGTH)
25+
: className;
26+
namespace += DELIMITER;
27+
namespace += UUID.randomUUID().toString().substring(0, RANDOM_SUFFIX_LENGTH);
28+
namespace = KubernetesResourceUtil.sanitizeName(namespace).toLowerCase(Locale.US);
29+
namespace = namespace.substring(0, Math.min(namespace.length(), MAX_NAMESPACE_NAME_LENGTH));
30+
return namespace;
31+
}
32+
}

operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtension.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.util.List;
1010
import java.util.Map;
1111
import java.util.function.Consumer;
12+
import java.util.function.Function;
1213
import java.util.stream.Collectors;
1314
import java.util.stream.Stream;
1415

@@ -53,14 +54,18 @@ private LocallyRunOperatorExtension(
5354
boolean waitForNamespaceDeletion,
5455
boolean oneNamespacePerClass,
5556
KubernetesClient kubernetesClient,
56-
Consumer<ConfigurationServiceOverrider> configurationServiceOverrider) {
57+
Consumer<ConfigurationServiceOverrider> configurationServiceOverrider,
58+
Function<ExtensionContext, String> namespaceNameSupplier,
59+
Function<ExtensionContext, String> perClassNamespaceNameSupplier) {
5760
super(
5861
infrastructure,
5962
infrastructureTimeout,
6063
oneNamespacePerClass,
6164
preserveNamespaceOnError,
6265
waitForNamespaceDeletion,
63-
kubernetesClient);
66+
kubernetesClient,
67+
namespaceNameSupplier,
68+
perClassNamespaceNameSupplier);
6469
this.reconcilers = reconcilers;
6570
this.portForwards = portForwards;
6671
this.localPortForwards = new ArrayList<>(portForwards.size());
@@ -285,7 +290,7 @@ public LocallyRunOperatorExtension build() {
285290
waitForNamespaceDeletion,
286291
oneNamespacePerClass,
287292
kubernetesClient,
288-
configurationServiceOverrider);
293+
configurationServiceOverrider, namespaceNameSupplier, perClassNamespaceNameSupplier);
289294
}
290295
}
291296

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package io.javaoperatorsdk.operator.junit;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import static io.javaoperatorsdk.operator.junit.AbstractOperatorExtension.MAX_NAMESPACE_NAME_LENGTH;
6+
import static io.javaoperatorsdk.operator.junit.DefaultNamespaceNameSupplier.*;
7+
import static io.javaoperatorsdk.operator.junit.NamespaceNamingTestUtils.*;
8+
import static org.assertj.core.api.Assertions.assertThat;
9+
10+
class DefaultNamespaceNameSupplierTest {
11+
12+
13+
DefaultNamespaceNameSupplier supplier = new DefaultNamespaceNameSupplier();
14+
15+
@Test
16+
void trivialCase() {
17+
String ns = supplier.apply(mockExtensionContext(SHORT_CLASS_NAME, SHORT_METHOD_NAME));
18+
19+
assertThat(ns).startsWith(SHORT_CLASS_NAME + DELIMITER + SHORT_METHOD_NAME + DELIMITER);
20+
shortEnoughAndEndsWithRandomString(ns);
21+
}
22+
23+
@Test
24+
void classPartLongerCase() {
25+
String ns = supplier.apply(mockExtensionContext(LONG_CLASS_NAME, SHORT_METHOD_NAME));
26+
27+
assertThat(ns).startsWith(LONG_CLASS_NAME + DELIMITER + SHORT_METHOD_NAME + DELIMITER);
28+
shortEnoughAndEndsWithRandomString(ns);
29+
}
30+
31+
@Test
32+
void methodPartLonger() {
33+
String ns = supplier.apply(mockExtensionContext(SHORT_CLASS_NAME, LONG_METHOD_NAME));
34+
35+
assertThat(ns).startsWith(SHORT_CLASS_NAME + DELIMITER + LONG_METHOD_NAME + DELIMITER);
36+
shortEnoughAndEndsWithRandomString(ns);
37+
}
38+
39+
@Test
40+
void methodPartAndClassPartLonger() {
41+
String ns = supplier.apply(mockExtensionContext(LONG_CLASS_NAME, LONG_METHOD_NAME));
42+
43+
assertThat(ns).startsWith(LONG_CLASS_NAME.substring(0, PART_RESERVED_NAME_LENGTH) + DELIMITER
44+
+ LONG_METHOD_NAME.substring(0, PART_RESERVED_NAME_LENGTH)
45+
+ DELIMITER);
46+
shortEnoughAndEndsWithRandomString(ns);
47+
}
48+
49+
50+
private static void shortEnoughAndEndsWithRandomString(String ns) {
51+
assertThat(ns.length()).isLessThanOrEqualTo(MAX_NAMESPACE_NAME_LENGTH);
52+
assertThat(ns.split("-")[2]).hasSize(RANDOM_SUFFIX_LENGTH);
53+
}
54+
55+
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package io.javaoperatorsdk.operator.junit;
2+
3+
import org.junit.jupiter.api.Test;
4+
import org.junit.jupiter.api.extension.ExtensionContext;
5+
6+
import static io.javaoperatorsdk.operator.junit.AbstractOperatorExtension.MAX_NAMESPACE_NAME_LENGTH;
7+
import static io.javaoperatorsdk.operator.junit.DefaultNamespaceNameSupplier.DELIMITER;
8+
import static io.javaoperatorsdk.operator.junit.DefaultNamespaceNameSupplier.RANDOM_SUFFIX_LENGTH;
9+
import static io.javaoperatorsdk.operator.junit.DefaultPerClassNamespaceNameSupplier.MAX_CLASS_NAME_LENGTH;
10+
import static io.javaoperatorsdk.operator.junit.NamespaceNamingTestUtils.SHORT_CLASS_NAME;
11+
import static io.javaoperatorsdk.operator.junit.NamespaceNamingTestUtils.VERY_LONG_CLASS_NAME;
12+
import static org.assertj.core.api.Assertions.assertThat;
13+
14+
class DefaultPerClassNamespaceNameSupplierTest {
15+
16+
DefaultPerClassNamespaceNameSupplier supplier = new DefaultPerClassNamespaceNameSupplier();
17+
18+
@Test
19+
void shortClassCase() {
20+
var ns = supplier.apply(mockExtensionContext(SHORT_CLASS_NAME));
21+
22+
assertThat(ns).startsWith(SHORT_CLASS_NAME + DELIMITER);
23+
shortEnoughAndEndsWithRandomString(ns);
24+
}
25+
26+
@Test
27+
void longClassCase() {
28+
var ns = supplier.apply(mockExtensionContext(VERY_LONG_CLASS_NAME));
29+
30+
assertThat(ns).startsWith(VERY_LONG_CLASS_NAME.substring(0, MAX_CLASS_NAME_LENGTH) + DELIMITER);
31+
shortEnoughAndEndsWithRandomString(ns);
32+
assertThat(ns).hasSize(MAX_NAMESPACE_NAME_LENGTH);
33+
}
34+
35+
public static ExtensionContext mockExtensionContext(String className) {
36+
return NamespaceNamingTestUtils.mockExtensionContext(className, null);
37+
}
38+
39+
private static void shortEnoughAndEndsWithRandomString(String ns) {
40+
assertThat(ns.length()).isLessThanOrEqualTo(MAX_NAMESPACE_NAME_LENGTH);
41+
assertThat(ns.split("-")[1]).hasSize(RANDOM_SUFFIX_LENGTH);
42+
}
43+
44+
}

0 commit comments

Comments
 (0)