Skip to content

improve: better namespace naming for junit extension and custom patterns #2171

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

Merged
merged 1 commit into from
Dec 21, 2023
Merged
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
5 changes: 5 additions & 0 deletions operator-framework-junit5/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -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;

Expand All @@ -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<ExtensionContext, String> namespaceNameSupplier;
protected final Function<ExtensionContext, String> perClassNamespaceNameSupplier;

protected String namespace;

Expand All @@ -53,14 +54,18 @@ protected AbstractOperatorExtension(
boolean oneNamespacePerClass,
boolean preserveNamespaceOnError,
boolean waitForNamespaceDeletion,
KubernetesClient kubernetesClient) {
KubernetesClient kubernetesClient,
Function<ExtensionContext, String> namespaceNameSupplier,
Function<ExtensionContext, String> perClassNamespaceNameSupplier) {
this.kubernetesClient = kubernetesClient != null ? kubernetesClient
: new KubernetesClientBuilder().build();
this.infrastructure = infrastructure;
this.infrastructureTimeout = infrastructureTimeout;
this.oneNamespacePerClass = oneNamespacePerClass;
this.preserveNamespaceOnError = preserveNamespaceOnError;
this.waitForNamespaceDeletion = waitForNamespaceDeletion;
this.namespaceNameSupplier = namespaceNameSupplier;
this.perClassNamespaceNameSupplier = perClassNamespaceNameSupplier;
}


Expand Down Expand Up @@ -132,26 +137,14 @@ public <T extends HasMetadata> boolean delete(Class<T> 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);
}
}
Expand Down Expand Up @@ -219,6 +212,10 @@ public static abstract class AbstractBuilder<T extends AbstractBuilder<T>> {
protected boolean oneNamespacePerClass;
protected int namespaceDeleteTimeout;
protected Consumer<ConfigurationServiceOverrider> configurationServiceOverrider;
protected Function<ExtensionContext, String> namespaceNameSupplier =
new DefaultNamespaceNameSupplier();
protected Function<ExtensionContext, String> perClassNamespaceNameSupplier =
new DefaultPerClassNamespaceNameSupplier();

protected AbstractBuilder() {
this.infrastructure = new ArrayList<>();
Expand Down Expand Up @@ -280,5 +277,17 @@ public T withNamespaceDeleteTimeout(int timeout) {
this.namespaceDeleteTimeout = timeout;
return (T) this;
}

public AbstractBuilder<T> withNamespaceNameSupplier(
Function<ExtensionContext, String> namespaceNameSupplier) {
this.namespaceNameSupplier = namespaceNameSupplier;
return this;
}

public AbstractBuilder<T> withPerClassNamespaceNameSupplier(
Function<ExtensionContext, String> perClassNamespaceNameSupplier) {
this.perClassNamespaceNameSupplier = perClassNamespaceNameSupplier;
return this;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -37,11 +38,13 @@ private ClusterDeployedOperatorExtension(
boolean preserveNamespaceOnError,
boolean waitForNamespaceDeletion,
boolean oneNamespacePerClass,
KubernetesClient kubernetesClient) {
KubernetesClient kubernetesClient,
Function<ExtensionContext, String> namespaceNameSupplier,
Function<ExtensionContext, String> perClassNamespaceNameSupplier) {
super(infrastructure, infrastructureTimeout, oneNamespacePerClass,
preserveNamespaceOnError,
waitForNamespaceDeletion,
kubernetesClient);
kubernetesClient, namespaceNameSupplier, perClassNamespaceNameSupplier);
this.operatorDeployment = operatorDeployment;
this.operatorDeploymentTimeout = operatorDeploymentTimeout;
}
Expand Down Expand Up @@ -152,7 +155,9 @@ public ClusterDeployedOperatorExtension build() {
preserveNamespaceOnError,
waitForNamespaceDeletion,
oneNamespacePerClass,
kubernetesClient != null ? kubernetesClient : new KubernetesClientBuilder().build());
kubernetesClient != null ? kubernetesClient : new KubernetesClientBuilder().build(),
namespaceNameSupplier,
perClassNamespaceNameSupplier);
}
}
}
Original file line number Diff line number Diff line change
@@ -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<ExtensionContext, String> {

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;
}
}
Original file line number Diff line number Diff line change
@@ -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<ExtensionContext, String> {

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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -53,14 +54,18 @@ private LocallyRunOperatorExtension(
boolean waitForNamespaceDeletion,
boolean oneNamespacePerClass,
KubernetesClient kubernetesClient,
Consumer<ConfigurationServiceOverrider> configurationServiceOverrider) {
Consumer<ConfigurationServiceOverrider> configurationServiceOverrider,
Function<ExtensionContext, String> namespaceNameSupplier,
Function<ExtensionContext, String> perClassNamespaceNameSupplier) {
super(
infrastructure,
infrastructureTimeout,
oneNamespacePerClass,
preserveNamespaceOnError,
waitForNamespaceDeletion,
kubernetesClient);
kubernetesClient,
namespaceNameSupplier,
perClassNamespaceNameSupplier);
this.reconcilers = reconcilers;
this.portForwards = portForwards;
this.localPortForwards = new ArrayList<>(portForwards.size());
Expand Down Expand Up @@ -285,7 +290,7 @@ public LocallyRunOperatorExtension build() {
waitForNamespaceDeletion,
oneNamespacePerClass,
kubernetesClient,
configurationServiceOverrider);
configurationServiceOverrider, namespaceNameSupplier, perClassNamespaceNameSupplier);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}


}
Original file line number Diff line number Diff line change
@@ -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);
}

}
Loading