Skip to content

Commit

Permalink
Address comments v1
Browse files Browse the repository at this point in the history
Signed-off-by: David Kornel <[email protected]>
  • Loading branch information
kornys committed Feb 27, 2024
1 parent 085f18b commit ee7ac24
Show file tree
Hide file tree
Showing 25 changed files with 257 additions and 126 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@ private TestFrameConstants() {
public static final long GLOBAL_POLL_INTERVAL_SHORT = Duration.ofSeconds(5).toMillis();
public static final long GLOBAL_POLL_INTERVAL_1_SEC = Duration.ofSeconds(1).toMillis();
public static final long GLOBAL_TIMEOUT = Duration.ofMinutes(10).toMillis();
public static final String OPENSHIFT = "oc";
public static final String KUBERNETES = "kubectl";

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public class TestFrameEnv {
public static final String USER_PATH = System.getProperty("user.dir");

private static final String CONFIG_FILE_PATH_ENV = "ENV_FILE";
private static final String CLIENT_TYPE_ENV = "CLIENT_TYPE";
private static final String USERNAME_ENV = "KUBE_USERNAME";
private static final String PASSWORD_ENV = "KUBE_PASSWORD";
private static final String TOKEN_ENV = "KUBE_TOKEN";
Expand All @@ -37,6 +38,7 @@ public class TestFrameEnv {
/**
* Set values
*/
public static final String CLIENT_TYPE = getOrDefault(CLIENT_TYPE_ENV, TestFrameConstants.KUBERNETES);
public static final String KUBE_USERNAME = getOrDefault(USERNAME_ENV, null);
public static final String KUBE_PASSWORD = getOrDefault(PASSWORD_ENV, null);
public static final String KUBE_TOKEN = getOrDefault(TOKEN_ENV, null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ public KubeClient() {

this.client = new KubernetesClientBuilder()
.withConfig(config)
.build()
.adapt(OpenShiftClient.class);
.build();
}

public KubernetesClient getClient() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,18 @@
package io.skodjob.testframe.interfaces;

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.client.dsl.MixedOperation;

import java.util.function.Consumer;

public interface NamespacedResourceType<T extends HasMetadata> extends ResourceType<T> {

/**
* Get specific {@link T} client for resoruce
* @return specific client
*/
MixedOperation<?, ?, ?> getClient();

/**
* Creates specific {@link T} resource in Namespace specified by user
* @param namespaceName Namespace, where the resource should be created
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package io.skodjob.testframe.interfaces;

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation;

import java.util.function.Consumer;

Expand All @@ -13,6 +14,14 @@
* @param <T> resource type
*/
public interface ResourceType<T extends HasMetadata> {

/**
* Get specific client for resoruce
* @return specific client
*/
NonNamespaceOperation<?, ?, ?> getClient();


/**
* Kind of api resource
* @return kind name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.skodjob.testframe.LoggerUtils;
import io.skodjob.testframe.TestFrameConstants;
import io.skodjob.testframe.TestFrameEnv;
import io.skodjob.testframe.clients.KubeClient;
import io.skodjob.testframe.clients.cmdClient.KubeCmdClient;
import io.skodjob.testframe.clients.cmdClient.Kubectl;
import io.skodjob.testframe.clients.cmdClient.Oc;
import io.skodjob.testframe.interfaces.ResourceType;
import io.skodjob.testframe.utils.TestFrameUtils;
import io.skodjob.testframe.wait.Wait;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -39,7 +41,11 @@ public static synchronized ResourceManager getInstance() {
if (instance == null) {
instance = new ResourceManager();
client = new KubeClient();
kubeCmdClient = new Oc(client.getKubeconfigPath()); //TODO do it for kubectl also
if (TestFrameEnv.CLIENT_TYPE.equals(TestFrameConstants.KUBERNETES)) {
kubeCmdClient = new Kubectl();
} else {
kubeCmdClient = new Oc(client.getKubeconfigPath());
}
}
return instance;
}
Expand Down Expand Up @@ -185,7 +191,7 @@ public final <T extends HasMetadata> boolean waitResourceCondition(T resource, R
ResourceType<T> type = findResourceType(resource);
boolean[] resourceReady = new boolean[1];

TestFrameUtils.waitFor("resource condition: " + condition.getConditionName() + " to be fulfilled for resource " + resource.getKind() + ":" + resource.getMetadata().getName(),
Wait.until("resource condition: " + condition.getConditionName() + " to be fulfilled for resource " + resource.getKind() + ":" + resource.getMetadata().getName(),
TestFrameConstants.GLOBAL_POLL_INTERVAL_MEDIUM, TestFrameConstants.GLOBAL_TIMEOUT,
() -> {
T res = getKubeClient().getClient().resource(resource).get();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,126 +47,6 @@ private TestFrameUtils() {
// All static methods
}

/**
* Poll the given {@code ready} function every {@code pollIntervalMs} milliseconds until it returns true,
* or throw a WaitException if it doesn't return true within {@code timeoutMs} milliseconds.
*
* @return The remaining time left until timeout occurs
* (helpful if you have several calls which need to share a common timeout),
*/
public static long waitFor(String description, long pollIntervalMs, long timeoutMs, BooleanSupplier ready) {
return waitFor(description, pollIntervalMs, timeoutMs, ready, () -> { });
}

public static long waitFor(String description, long pollIntervalMs, long timeoutMs, BooleanSupplier ready, Runnable onTimeout) {
LOGGER.debug("Waiting for {}", description);
long deadline = System.currentTimeMillis() + timeoutMs;

String exceptionMessage = null;
String previousExceptionMessage = null;

// in case we are polling every 1s, we want to print exception after x tries, not on the first try
// for minutes poll interval will 2 be enough
int exceptionAppearanceCount = Duration.ofMillis(pollIntervalMs).toMinutes() > 0 ? 2 : Math.max((int) (timeoutMs / pollIntervalMs) / 4, 2);
int exceptionCount = 0;
int newExceptionAppearance = 0;

StringWriter stackTraceError = new StringWriter();

while (true) {
boolean result;
try {
result = ready.getAsBoolean();
} catch (Exception e) {
exceptionMessage = e.getMessage();

if (++exceptionCount == exceptionAppearanceCount && exceptionMessage != null && exceptionMessage.equals(previousExceptionMessage)) {
LOGGER.error("While waiting for {} exception occurred: {}", description, exceptionMessage);
// log the stacktrace
e.printStackTrace(new PrintWriter(stackTraceError));
} else if (exceptionMessage != null && !exceptionMessage.equals(previousExceptionMessage) && ++newExceptionAppearance == 2) {
previousExceptionMessage = exceptionMessage;
}

result = false;
}
long timeLeft = deadline - System.currentTimeMillis();
if (result) {
return timeLeft;
}
if (timeLeft <= 0) {
if (exceptionCount > 1) {
LOGGER.error("Exception waiting for {}, {}", description, exceptionMessage);

if (!stackTraceError.toString().isEmpty()) {
// printing handled stacktrace
LOGGER.error(stackTraceError.toString());
}
}
onTimeout.run();
WaitException waitException = new WaitException("Timeout after " + timeoutMs + " ms waiting for " + description);
waitException.printStackTrace();
throw waitException;
}
long sleepTime = Math.min(pollIntervalMs, timeLeft);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("{} not ready, will try again in {} ms ({}ms till timeout)", description, sleepTime, timeLeft);
}
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
return deadline - System.currentTimeMillis();
}
}
}

private static final ExecutorService EXECUTOR = Executors.newCachedThreadPool(new ThreadFactory() {
final ThreadFactory defaultThreadFactory = Executors.defaultThreadFactory();

@Override
public Thread newThread(Runnable r) {
Thread result = defaultThreadFactory.newThread(r);
result.setDaemon(true);
return result;
}
});

public static CompletableFuture<Void> asyncWaitFor(String description, long pollIntervalMs, long timeoutMs, BooleanSupplier ready) {
LOGGER.info("Waiting for {}", description);
long deadline = System.currentTimeMillis() + timeoutMs;
CompletableFuture<Void> future = new CompletableFuture<>();
Executor delayed = CompletableFuture.delayedExecutor(pollIntervalMs, TimeUnit.MILLISECONDS, EXECUTOR);
Runnable r = new Runnable() {
@Override
public void run() {
boolean result;
try {
result = ready.getAsBoolean();
} catch (Exception e) {
future.completeExceptionally(e);
return;
}
long timeLeft = deadline - System.currentTimeMillis();
if (!future.isDone()) {
if (!result) {
if (timeLeft >= 0) {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("{} not ready, will try again ({}ms till timeout)", description, timeLeft);
}
delayed.execute(this);
} else {
future.completeExceptionally(new TimeoutException(String.format("Waiting for %s timeout %s exceeded", description, timeoutMs)));
}
} else {
future.complete(null);
}
}
}
};
r.run();
return future;
}

public static InputStream getFileFromResourceAsStream(String fileName) {

// The class loader that loaded the class
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,24 @@
*/
package io.skodjob.testframe.wait;

import io.skodjob.testframe.LoggerUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.BooleanSupplier;

public class Wait {
private static final Logger LOGGER = LoggerFactory.getLogger(Wait.class);

/**
* For every poll (happening once each {@code pollIntervalMs}) checks if supplier {@code ready} is true.
Expand Down Expand Up @@ -99,4 +111,64 @@ public static void until(String description, long pollIntervalMs, long timeoutMs
}
}
}

private static final ExecutorService EXECUTOR = Executors.newCachedThreadPool(new ThreadFactory() {
final ThreadFactory defaultThreadFactory = Executors.defaultThreadFactory();

@Override
public Thread newThread(Runnable r) {
Thread result = defaultThreadFactory.newThread(r);
result.setDaemon(true);
return result;
}
});

/**
* For every poll (happening once each {@code pollIntervalMs}) checks if supplier {@code ready} is true.
* If yes, the wait is closed. Otherwise, waits another {@code pollIntervalMs} and tries again.
* Once the wait timeout (specified by {@code timeoutMs} is reached and supplier wasn't true until that time,
* runs the {@code onTimeout} (f.e. print of logs, showing the actual value that was checked inside {@code ready}),
* and finally throws {@link WaitException}.
*
* @param description information about on what we are waiting
* @param pollIntervalMs poll interval in milliseconds
* @param timeoutMs timeout specified in milliseconds
* @param ready {@link BooleanSupplier} containing code, which should be executed each poll, verifying readiness
* of the particular thing
*/
public static CompletableFuture<Void> untilAsync(String description, long pollIntervalMs, long timeoutMs, BooleanSupplier ready) {
LOGGER.info("Waiting for {}", description);
long deadline = System.currentTimeMillis() + timeoutMs;
CompletableFuture<Void> future = new CompletableFuture<>();
Executor delayed = CompletableFuture.delayedExecutor(pollIntervalMs, TimeUnit.MILLISECONDS, EXECUTOR);
Runnable r = new Runnable() {
@Override
public void run() {
boolean result;
try {
result = ready.getAsBoolean();
} catch (Exception e) {
future.completeExceptionally(e);
return;
}
long timeLeft = deadline - System.currentTimeMillis();
if (!future.isDone()) {
if (!result) {
if (timeLeft >= 0) {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("{} not ready, will try again ({}ms till timeout)", description, timeLeft);
}
delayed.execute(this);
} else {
future.completeExceptionally(new TimeoutException(String.format("Waiting for %s timeout %s exceeded", description, timeoutMs)));
}
} else {
future.complete(null);
}
}
}
};
r.run();
return future;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ public ClusterRoleBindingResource() {
this.client = ResourceManager.getKubeClient().getClient().rbac().clusterRoleBindings();
}

/**
* Get specific client for resoruce
* @return specific client
*/
@Override
public NonNamespaceOperation<?, ?, ?> getClient() {
return client;
}

/**
* Kind of api resource
* @return kind name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ public String getKind() {
return "ClusterRole";
}

/**
* Get specific client for resoruce
* @return specific client
*/
@Override
public NonNamespaceOperation<?, ?, ?> getClient() {
return client;
}

/**
* Creates specific {@link ClusterRole} resource
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ public String getKind() {
return "ConfigMap";
}

/**
* Get specific client for resoruce
* @return specific client
*/
@Override
public MixedOperation<?, ?, ?> getClient() {
return client;
}

/**
* Creates specific {@link ConfigMap} resource in Namespace specified by user
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ public String getKind() {
return "CustomResourceDefinition";
}

/**
* Get specific client for resoruce
* @return specific client
*/
@Override
public NonNamespaceOperation<?, ?, ?> getClient() {
return client;
}

/**
* Creates specific {@link CustomResourceDefinition} resource
*
Expand Down
Loading

0 comments on commit ee7ac24

Please sign in to comment.