diff --git a/test-frame-log-collector/README.md b/test-frame-log-collector/README.md
index da3d56f..a637707 100644
--- a/test-frame-log-collector/README.md
+++ b/test-frame-log-collector/README.md
@@ -169,6 +169,43 @@ the logs path will then look like this:
```
The tree path will look similarly to above examples, there will be folders for Namespaces matching the specified labels.
+### Global log collector
+`CollectLogs` is an annotation which can handle collecting logs which user want automatically in case of test failure or before/after failure.
+It gets configuration passed into `GlobalLogCollector` and call collecting in proper callbacks.
+
+Register `GlobalLogCollector` handlers and configure
+
+```java
+import io.skodjob.testframe.LogCollectorBuilder;
+import io.skodjob.testframe.annotations.CollectLogs;
+import io.skodjob.testframe.listeners.GlobalLogCollector;
+import io.skodjob.testframe.resources.KubeResourceManager;
+import org.junit.jupiter.api.Test;
+
+@CollectLogs
+class TestClass() {
+ static {
+ // Setup global log collector and handlers
+ GlobalLogCollector.setupGlobalLogCollector(new LogCollectorBuilder()
+ .withNamespacedResources("sa", "deployment", "configmaps", "secret")
+ .withClusterWideResources("nodes")
+ .withKubeClient(KubeResourceManager.getKubeClient())
+ .withKubeCmdClient(KubeResourceManager.getKubeCmdClient())
+ .withRootFolderPath("/some-path/path/")
+ .build());
+ GlobalLogCollector.addLogCallback(() -> {
+ GlobalLogCollector.getGlobalLogCollector().collectFromNamespaces("test-namespace", "test-namespace-2");
+ GlobalLogCollector.getGlobalLogCollector().collectClusterWideResources();
+ });
+ }
+
+ @Test
+ void test() {
+ ...
+ }
+}
+```
+
### Specifying additional folder path
In case that you would like to collect the logs to additional sub-directories of your root folder, the `LogCollector` contains
diff --git a/test-frame-log-collector/src/main/java/io/skodjob/testframe/annotations/CollectLogs.java b/test-frame-log-collector/src/main/java/io/skodjob/testframe/annotations/CollectLogs.java
new file mode 100644
index 0000000..adcda8b
--- /dev/null
+++ b/test-frame-log-collector/src/main/java/io/skodjob/testframe/annotations/CollectLogs.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright Skodjob authors.
+ * License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
+ */
+package io.skodjob.testframe.annotations;
+
+import io.skodjob.testframe.listeners.GlobalLogCollector;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * This annotation is used to automatically call log collecting
+ * when test of prepare and post phase fails in JUnit tests.
+ * It is applied at the class level.
+ *
+ * It uses the {@link GlobalLogCollector}
+ */
+@Target(ElementType.TYPE)
+@Retention(RUNTIME)
+@Inherited
+@ExtendWith(GlobalLogCollector.class)
+public @interface CollectLogs {
+}
diff --git a/test-frame-log-collector/src/main/java/io/skodjob/testframe/listeners/GlobalLogCollector.java b/test-frame-log-collector/src/main/java/io/skodjob/testframe/listeners/GlobalLogCollector.java
new file mode 100644
index 0000000..0ada058
--- /dev/null
+++ b/test-frame-log-collector/src/main/java/io/skodjob/testframe/listeners/GlobalLogCollector.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright Skodjob authors.
+ * License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
+ */
+package io.skodjob.testframe.listeners;
+
+import io.skodjob.testframe.LogCollector;
+import io.skodjob.testframe.annotations.CollectLogs;
+import io.skodjob.testframe.interfaces.ThrowableRunner;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler;
+import org.junit.jupiter.api.extension.TestExecutionExceptionHandler;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Represents global log collector which is automatically called on text fail or error even in setup or post methods
+ */
+public class GlobalLogCollector implements TestExecutionExceptionHandler, LifecycleMethodExecutionExceptionHandler {
+ private static final Logger LOGGER = LogManager.getLogger(GlobalLogCollector.class);
+ private static LogCollector globalInstance;
+ private static final List COLLECT_CALLBACKS = new LinkedList<>();
+
+ /**
+ * Private constructor
+ */
+ private GlobalLogCollector() {
+ // empty constructor
+ }
+
+ /**
+ * Handler when test fails
+ *
+ * @param extensionContext extension context
+ * @param throwable throwable
+ * @throws Throwable original throwable
+ */
+ @Override
+ public void handleTestExecutionException(ExtensionContext extensionContext, Throwable throwable) throws Throwable {
+ saveKubeState();
+ throw throwable;
+ }
+
+ /**
+ * Handles beforeAll exception
+ *
+ * @param context extensionContext
+ * @param throwable throwable
+ * @throws Throwable original throwable
+ */
+ @Override
+ public void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable throwable)throws Throwable {
+ saveKubeState();
+ LifecycleMethodExecutionExceptionHandler.super.handleBeforeAllMethodExecutionException(context, throwable);
+ }
+
+ /**
+ * Handles beforeEach exception
+ *
+ * @param context extensionContext
+ * @param throwable throwable
+ * @throws Throwable original throwable
+ */
+ @Override
+ public void handleBeforeEachMethodExecutionException(ExtensionContext context,
+ Throwable throwable) throws Throwable {
+ saveKubeState();
+ LifecycleMethodExecutionExceptionHandler.super.handleBeforeEachMethodExecutionException(context, throwable);
+ }
+
+ /**
+ * Handles afterEach exception
+ *
+ * @param context extensionContext
+ * @param throwable throwable
+ * @throws Throwable original throwable
+ */
+ @Override
+ public void handleAfterEachMethodExecutionException(ExtensionContext context,
+ Throwable throwable) throws Throwable {
+ saveKubeState();
+ LifecycleMethodExecutionExceptionHandler.super.handleAfterEachMethodExecutionException(context, throwable);
+ }
+
+ /**
+ * Handles afterAll exception
+ *
+ * @param context extensionContext
+ * @param throwable throwable
+ * @throws Throwable original throwable
+ */
+ @Override
+ public void handleAfterAllMethodExecutionException(ExtensionContext context, Throwable throwable) throws Throwable {
+ saveKubeState();
+ LifecycleMethodExecutionExceptionHandler.super.handleAfterAllMethodExecutionException(context, throwable);
+ }
+
+ /**
+ * Setup globalLogCollector which is automatically used within {@link CollectLogs} annotation
+ *
+ * @param globalLogCollector log collector instance
+ */
+ public static void setupGlobalLogCollector(LogCollector globalLogCollector) {
+ globalInstance = globalLogCollector;
+ }
+
+ /**
+ * Returns globalLogCollector instance
+ *
+ * @return global log collector instance
+ */
+ public static LogCollector getGlobalLogCollector() {
+ if (globalInstance == null) {
+ throw new NullPointerException("Global log collector is not initialized");
+ }
+ return globalInstance;
+ }
+
+ /**
+ * Adds callback for running log collecting
+ *
+ * @param callback callback method with log collecting
+ */
+ public static void addLogCallback(ThrowableRunner callback) {
+ COLLECT_CALLBACKS.add(callback);
+ }
+
+ private void saveKubeState() {
+ try {
+ for (ThrowableRunner runner : COLLECT_CALLBACKS) {
+ runner.run();
+ }
+ } catch (Exception ex) {
+ LOGGER.error("Cannot collect all data", ex);
+ }
+ }
+}
diff --git a/test-frame-test-examples/src/test/java/io/skodjob/testframe/test/integration/AbstractIT.java b/test-frame-test-examples/src/test/java/io/skodjob/testframe/test/integration/AbstractIT.java
index a0607f8..155e94f 100644
--- a/test-frame-test-examples/src/test/java/io/skodjob/testframe/test/integration/AbstractIT.java
+++ b/test-frame-test-examples/src/test/java/io/skodjob/testframe/test/integration/AbstractIT.java
@@ -4,6 +4,10 @@
*/
package io.skodjob.testframe.test.integration;
+import io.skodjob.testframe.LogCollectorBuilder;
+import io.skodjob.testframe.annotations.CollectLogs;
+import io.skodjob.testframe.listeners.GlobalLogCollector;
+import io.skodjob.testframe.test.integration.helpers.GlobalLogCollectorTestHandler;
import io.skodjob.testframe.utils.LoggerUtils;
import io.skodjob.testframe.annotations.ResourceManager;
import io.skodjob.testframe.annotations.TestVisualSeparator;
@@ -12,33 +16,61 @@
import io.skodjob.testframe.resources.NamespaceType;
import io.skodjob.testframe.resources.ServiceAccountType;
import io.skodjob.testframe.utils.KubeUtils;
+import org.junit.jupiter.api.extension.ExtendWith;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
import java.util.concurrent.atomic.AtomicBoolean;
+@ExtendWith(GlobalLogCollectorTestHandler.class) // For testing purpose
@ResourceManager
+@CollectLogs
@TestVisualSeparator
public abstract class AbstractIT {
static AtomicBoolean isCreateHandlerCalled = new AtomicBoolean(false);
static AtomicBoolean isDeleteHandlerCalled = new AtomicBoolean(false);
+ public static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm");
+ public static final Path LOG_DIR = Paths.get(System.getProperty("user.dir"), "target", "logs")
+ .resolve("test-run-" + DATE_FORMAT.format(LocalDateTime.now()));
static {
+ // Register resources which KRM uses for handling instead of native status check
KubeResourceManager.getInstance().setResourceTypes(
new NamespaceType(),
new ServiceAccountType(),
new DeploymentType()
);
+
+ // Register callback which are called with every create resource method for every resource
KubeResourceManager.getInstance().addCreateCallback(r -> {
isCreateHandlerCalled.set(true);
if (r.getKind().equals("Namespace")) {
KubeUtils.labelNamespace(r.getMetadata().getName(), "test-label", "true");
}
});
+
+ // Register callback which are called with every delete resource method for every resource
KubeResourceManager.getInstance().addDeleteCallback(r -> {
isDeleteHandlerCalled.set(true);
if (r.getKind().equals("Namespace")) {
LoggerUtils.logResource("Deleted", r);
}
});
+
+ // Setup global log collector and handlers
+ GlobalLogCollector.setupGlobalLogCollector(new LogCollectorBuilder()
+ .withNamespacedResources("sa", "deployment", "configmaps", "secret")
+ .withClusterWideResources("nodes")
+ .withKubeClient(KubeResourceManager.getKubeClient())
+ .withKubeCmdClient(KubeResourceManager.getKubeCmdClient())
+ .withRootFolderPath(LOG_DIR.toString())
+ .build());
+ GlobalLogCollector.addLogCallback(() -> {
+ GlobalLogCollector.getGlobalLogCollector().collectFromNamespaces("default");
+ GlobalLogCollector.getGlobalLogCollector().collectClusterWideResources();
+ });
}
protected String nsName1 = "test";
diff --git a/test-frame-test-examples/src/test/java/io/skodjob/testframe/test/integration/GlobalLogCollectorIT.java b/test-frame-test-examples/src/test/java/io/skodjob/testframe/test/integration/GlobalLogCollectorIT.java
new file mode 100644
index 0000000..a26be4d
--- /dev/null
+++ b/test-frame-test-examples/src/test/java/io/skodjob/testframe/test/integration/GlobalLogCollectorIT.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright Skodjob authors.
+ * License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
+ */
+package io.skodjob.testframe.test.integration;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+
+import java.io.IOException;
+
+import static org.junit.jupiter.api.Assertions.fail;
+
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+public class GlobalLogCollectorIT extends AbstractIT {
+ @Test
+ void testGlobalLogCollector() {
+ fail("Expected issue");
+ }
+
+ @AfterEach
+ void clean() throws IOException {
+ FileUtils.deleteDirectory(LOG_DIR.toFile());
+ }
+}
diff --git a/test-frame-test-examples/src/test/java/io/skodjob/testframe/test/integration/helpers/GlobalLogCollectorTestHandler.java b/test-frame-test-examples/src/test/java/io/skodjob/testframe/test/integration/helpers/GlobalLogCollectorTestHandler.java
new file mode 100644
index 0000000..f71e396
--- /dev/null
+++ b/test-frame-test-examples/src/test/java/io/skodjob/testframe/test/integration/helpers/GlobalLogCollectorTestHandler.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright Skodjob authors.
+ * License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
+ */
+package io.skodjob.testframe.test.integration.helpers;
+
+import io.skodjob.testframe.test.integration.AbstractIT;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.TestExecutionExceptionHandler;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Override test failure for expected test issue in GlobalLogCollectorIT test
+ */
+public class GlobalLogCollectorTestHandler implements TestExecutionExceptionHandler {
+
+ /**
+ * Check cause message and if it is from GlobalLogCollectorIT check if logs
+ * are collected and do not mark test as failure
+ *
+ * @param context extension context
+ * @param cause throwable object
+ * @throws Throwable throwable object
+ */
+ @Override
+ public void handleTestExecutionException(ExtensionContext context, Throwable cause) throws Throwable {
+ if (cause.getMessage().contains("Expected issue")) {
+ assertTrue(AbstractIT.LOG_DIR.toFile().exists());
+ assertTrue(AbstractIT.LOG_DIR.resolve("default").toFile().exists());
+ assertTrue(AbstractIT.LOG_DIR.resolve("cluster-wide-resources").toFile().exists());
+ } else {
+ throw cause;
+ }
+ }
+}