This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch camel-4.8.x in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/camel-4.8.x by this push: new 351d06c6a6d Camel 21386 4.8.x (#16098) 351d06c6a6d is described below commit 351d06c6a6dafbdbcb78ccd01822e57ed425aefa Author: Thomas Diesler <tdies...@redhat.com> AuthorDate: Mon Oct 28 12:09:42 2024 +0100 Camel 21386 4.8.x (#16098) * [CAMEL-21381] camel k8s cannot run spring-boot on openshift (#16084) * [CAMEL-21386] camel k8s run --dev does not work on openshift --- dsl/camel-jbang/README.md | 4 +- .../dsl/jbang/core/commands/CamelCommand.java | 4 +- .../dsl/jbang/core/commands/CommandHelper.java | 11 ++ .../core/commands/action/CamelThreadDump.java | 2 +- .../camel/dsl/jbang/core/common/PluginHelper.java | 2 +- .../core/commands/kubernetes/KubernetesDelete.java | 7 +- .../core/commands/kubernetes/KubernetesExport.java | 1 - .../core/commands/kubernetes/KubernetesHelper.java | 47 ++++--- .../core/commands/kubernetes/KubernetesRun.java | 150 ++++++++++++--------- .../jbang/core/commands/kubernetes/PodLogs.java | 110 ++++++++------- .../commands/kubernetes/KubernetesClientTest.java | 47 ------- .../core/commands/kubernetes/PodLogsTest.java | 7 +- 12 files changed, 201 insertions(+), 191 deletions(-) diff --git a/dsl/camel-jbang/README.md b/dsl/camel-jbang/README.md index 08bb58fa0db..62f17a370e7 100644 --- a/dsl/camel-jbang/README.md +++ b/dsl/camel-jbang/README.md @@ -9,10 +9,10 @@ jbang app install camel@apache/camel If you however like to install camel-jbang from this project build you create an alias to the local entry point. ```shell -jbang alias add --name camel -Dcamel.jbang.version=4.8.1-SNAPSHOT ./dsl/camel-jbang/camel-jbang-main/dist/CamelJBang.java +jbang alias add --name camel -Dcamel.jbang.version=4.8.2-SNAPSHOT ./dsl/camel-jbang/camel-jbang-main/dist/CamelJBang.java jbang camel version -Camel JBang version: 4.8.1-SNAPSHOT +Camel JBang version: 4.8.2-SNAPSHOT ``` Alternatively, you can change the version in [`CamelJBang.java`](https://github.com/apache/camel/blob/main/dsl/camel-jbang/camel-jbang-main/src/main/jbang/main/CamelJBang.java#L22) \ No newline at end of file diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelCommand.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelCommand.java index 41ae9f33f67..eb4b256f024 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelCommand.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelCommand.java @@ -115,7 +115,9 @@ public abstract class CamelCommand implements Callable<Integer> { } protected Printer printer() { - return getMain().getOut(); + var out = getMain().getOut(); + CommandHelper.SetPrinter(out); + return out; } protected void printConfigurationValues(String header) { diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CommandHelper.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CommandHelper.java index 1ce40f1a4f8..864da314bec 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CommandHelper.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CommandHelper.java @@ -18,13 +18,24 @@ package org.apache.camel.dsl.jbang.core.commands; import java.io.File; +import org.apache.camel.dsl.jbang.core.common.Printer; import org.apache.camel.util.FileUtil; public final class CommandHelper { + private static ThreadLocal<Printer> printerAssociation = new ThreadLocal<>(); + private CommandHelper() { } + public static Printer GetPrinter() { + return printerAssociation.get(); + } + + public static void SetPrinter(Printer out) { + printerAssociation.set(out); + } + public static void cleanExportDir(String dir) { File target = new File(dir); File[] files = target.listFiles(); diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelThreadDump.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelThreadDump.java index d5666ded944..5bd19d7b36e 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelThreadDump.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelThreadDump.java @@ -148,7 +148,7 @@ public class CamelThreadDump extends ActionWatchCommand { if (!rows.isEmpty()) { int total = jo.getInteger("threadCount"); int peak = jo.getInteger("peakThreadCount"); - printer().printf("PID: %s\tThreads: %d\tPeak: %d\t\tDisplay: %d/%d\n", pid, total, peak, rows.size(), total); + printer().printf("PID: %s\tThreads: %d\tPeak: %d\t\tDisplay: %d/%d%n", pid, total, peak, rows.size(), total); if (depth == 1) { singleTable(rows); diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/PluginHelper.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/PluginHelper.java index fbe0af60b29..fe9e504aa15 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/PluginHelper.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/PluginHelper.java @@ -157,7 +157,7 @@ public final class PluginHelper { instance = Optional.of(Plugin.class.cast(ObjectHelper.newInstance(pluginClass))); } else { String gav = String.join(":", group, "camel-jbang-plugin-" + command, version); - main.getOut().printf(String.format("ERROR: Failed to read file %s in dependency %s.\n", path, gav)); + main.getOut().printf(String.format("ERROR: Failed to read file %s in dependency %s%n", path, gav)); } } catch (IOException e) { throw new RuntimeCamelException(String.format("Failed to read the file %s.", path), e); diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesDelete.java b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesDelete.java index a5c44169177..226f050adb1 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesDelete.java +++ b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesDelete.java @@ -44,6 +44,10 @@ public class KubernetesDelete extends KubernetesBaseCommand { description = "The working directory where to find exported project sources.") String workingDir; + @CommandLine.Option(names = { "--cluster-type" }, + description = "The target cluster type. Special configurations may be applied to different cluster types such as Kind or Minikube or Openshift.") + protected String clusterType; + public KubernetesDelete(CamelJBangMain main) { super(main); } @@ -71,7 +75,8 @@ public class KubernetesDelete extends KubernetesBaseCommand { return 1; } - File manifest = KubernetesHelper.resolveKubernetesManifest(new File(resolvedWorkingDir, "target/kubernetes")); + File resolvedManifestDir = new File(resolvedWorkingDir, "target/kubernetes"); + File manifest = KubernetesHelper.resolveKubernetesManifest(clusterType, resolvedManifestDir); try (FileInputStream fis = new FileInputStream(manifest)) { List<StatusDetails> status; if (!ObjectHelper.isEmpty(namespace)) { diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExport.java b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExport.java index 6fa5cb92c4c..ef7b0b91962 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExport.java +++ b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExport.java @@ -125,7 +125,6 @@ public class KubernetesExport extends Export { public KubernetesExport(CamelJBangMain main, String[] files) { super(main); - this.files = Arrays.asList(files); } diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesHelper.java b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesHelper.java index 6f5d6724ab2..976de6a4ae3 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesHelper.java +++ b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesHelper.java @@ -31,8 +31,10 @@ import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.json.JsonMapper; +import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.KubernetesClientBuilder; +import org.apache.camel.dsl.jbang.core.commands.CommandHelper; import org.apache.camel.dsl.jbang.core.common.YamlHelper; import org.apache.camel.util.FileUtil; import org.apache.camel.util.StringHelper; @@ -74,6 +76,7 @@ public final class KubernetesHelper { public static KubernetesClient getKubernetesClient() { if (kubernetesClient == null) { kubernetesClient = new KubernetesClientBuilder().build(); + printClientInfo(kubernetesClient); } return kubernetesClient; @@ -87,7 +90,18 @@ public final class KubernetesHelper { return clients.get(config); } - return clients.put(config, new KubernetesClientBuilder().withConfig(config).build()); + var client = new KubernetesClientBuilder().withConfig(config).build(); + printClientInfo(client); + return clients.put(config, client); + } + + private static void printClientInfo(KubernetesClient client) { + var printer = CommandHelper.GetPrinter(); + if (printer != null) { + var serverUrl = client.getConfiguration().getMasterUrl(); + var info = client.getKubernetesVersion(); + printer.println(String.format("Kubernetes v%s.%s %s", info.getMajor(), info.getMinor(), serverUrl)); + } } /** @@ -146,29 +160,23 @@ public final class KubernetesHelper { return json().convertValue(model, Map.class); } - public static File resolveKubernetesManifest(String workingDir) throws FileNotFoundException { - return resolveKubernetesManifest(new File(workingDir)); + public static File resolveKubernetesManifest(String clusterType, String workingDir) throws FileNotFoundException { + return resolveKubernetesManifest(clusterType, new File(workingDir)); } - public static File resolveKubernetesManifest(String workingDir, String extension) throws FileNotFoundException { - return resolveKubernetesManifest(new File(workingDir), extension); + public static File resolveKubernetesManifest(String clusterType, String workingDir, String extension) + throws FileNotFoundException { + return resolveKubernetesManifest(clusterType, new File(workingDir), extension); } - public static File resolveKubernetesManifest(File workingDir) throws FileNotFoundException { - return resolveKubernetesManifest(workingDir, "yml"); + public static File resolveKubernetesManifest(String clusterType, File workingDir) throws FileNotFoundException { + return resolveKubernetesManifest(clusterType, workingDir, "yml"); } - public static File resolveKubernetesManifest(File workingDir, String extension) throws FileNotFoundException { - - // Try explicit Kubernetes manifest first - String clusterType = ClusterType.KUBERNETES.name(); - File manifest = getKubernetesManifest(clusterType, workingDir, extension); - if (manifest.exists()) { - return manifest; - } + public static File resolveKubernetesManifest(String clusterType, File workingDir, String extension) + throws FileNotFoundException { - // Try arbitrary Kubernetes manifest first - manifest = getKubernetesManifest(clusterType, workingDir); + var manifest = getKubernetesManifest(clusterType, workingDir); if (manifest.exists()) { return manifest; } @@ -178,6 +186,10 @@ public final class KubernetesHelper { .formatted(extension, workingDir.toPath().toString())); } + public static String getPodPhase(Pod pod) { + return Optional.ofNullable(pod).map(p -> p.getStatus().getPhase()).orElse("Unknown"); + } + public static File getKubernetesManifest(String clusterType, String workingDir) { return getKubernetesManifest(clusterType, new File(workingDir)); } @@ -193,7 +205,6 @@ public final class KubernetesHelper { } else { manifestFile = Optional.ofNullable(clusterType).map(String::toLowerCase).orElse("kubernetes"); } - return new File(workingDir, "%s.%s".formatted(manifestFile, extension)); } } diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRun.java b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRun.java index 19319d2b566..a25c8f5cc9d 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRun.java +++ b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRun.java @@ -44,6 +44,8 @@ import org.apache.camel.util.StringHelper; import org.apache.camel.util.concurrent.ThreadHelper; import picocli.CommandLine; +import static org.apache.camel.dsl.jbang.core.commands.kubernetes.KubernetesHelper.getPodPhase; + @CommandLine.Command(name = "run", description = "Run Camel application on Kubernetes", sortOptions = false) public class KubernetesRun extends KubernetesBaseCommand { @@ -239,8 +241,12 @@ public class KubernetesRun extends KubernetesBaseCommand { description = "Maven/Gradle build properties, ex. --build-property=prop1=foo") List<String> buildProperties = new ArrayList<>(); - CamelContext reloadContext; - int reloadCount; + // DevMode/Reload state + private CamelContext devModeContext; + private Thread devModeShutdownTask; + private int devModeReloadCount; + + private PodLogs reusablePodLogs; public KubernetesRun(CamelJBangMain main) { super(main); @@ -273,9 +279,10 @@ public class KubernetesRun extends KubernetesBaseCommand { File manifest; switch (output) { case "yaml" -> - manifest = KubernetesHelper.resolveKubernetesManifest(workingDir + "/target/kubernetes"); + manifest = KubernetesHelper.resolveKubernetesManifest(clusterType, workingDir + "/target/kubernetes"); case "json" -> - manifest = KubernetesHelper.resolveKubernetesManifest(workingDir + "/target/kubernetes", "json"); + manifest = KubernetesHelper.resolveKubernetesManifest(clusterType, workingDir + "/target/kubernetes", + "json"); default -> { printer().printf("Unsupported output format '%s' (supported: yaml, json)%n", output); return 1; @@ -305,7 +312,6 @@ public class KubernetesRun extends KubernetesBaseCommand { if (dev || logs) { startPodLogging(projectName); - printer().println("Stopped pod logging!"); } return 0; @@ -313,8 +319,8 @@ public class KubernetesRun extends KubernetesBaseCommand { private String getIndexedWorkingDir(String projectName) { var workingDir = RUN_PLATFORM_DIR + "/" + projectName; - if (reloadCount > 0) { - workingDir += "-%03d".formatted(reloadCount); + if (devModeReloadCount > 0) { + workingDir += "-%03d".formatted(devModeReloadCount); } return workingDir; } @@ -395,51 +401,79 @@ public class KubernetesRun extends KubernetesBaseCommand { FileWatcherResourceReloadStrategy reloadStrategy = new FileWatcherResourceReloadStrategy(watchDir); reloadStrategy.setResourceReload((name, resource) -> { - reloadCount += 1; - reloadContext.close(); - printer().printf("Reloading project due to file change: %s%n", FileUtil.stripPath(name)); - String reloadWorkingDir = getIndexedWorkingDir(projectName); - KubernetesExport export = configureExport(reloadWorkingDir); - int exit = export.export(); - if (exit != 0) { - printer().printf("Project reexport failed for: %s%n", reloadWorkingDir); - return; - } - exit = deployProject(reloadWorkingDir, true); - if (exit != 0) { - printer().printf("Project redeploy failed for: %s%n", reloadWorkingDir); - return; - } - if (dev || wait || logs) { - waitForRunningPod(projectName); - } - if (dev) { + synchronized (this) { + + printer().printf("Reloading project due to file change: %s%n", FileUtil.stripPath(name)); + + String currentWorkingDir = getIndexedWorkingDir(projectName); + devModeReloadCount += 1; + + String reloadWorkingDir = getIndexedWorkingDir(projectName); + devModeContext.close(); + + // Re-export updated project + // + KubernetesExport export = configureExport(reloadWorkingDir); + int exit = export.export(); + if (exit != 0) { + printer().printf("Project reexport failed for: %s%n", reloadWorkingDir); + return; + } + + reusablePodLogs.retryForReload = true; + try { + + // Undeploy/Delete current project + // + KubernetesDelete deleteCommand = new KubernetesDelete(getMain()); + deleteCommand.workingDir = currentWorkingDir; + deleteCommand.clusterType = clusterType; + deleteCommand.name = projectName; + deleteCommand.doCall(); + + // Re-deploy updated project + // + exit = deployProject(reloadWorkingDir, true); + if (exit != 0) { + printer().printf("Project redeploy failed for: %s%n", reloadWorkingDir); + return; + } + + waitForRunningPod(projectName); + + } finally { + reusablePodLogs.retryForReload = false; + } + + // Recursively setup --dev mode for updated project + // + Runtime.getRuntime().removeShutdownHook(devModeShutdownTask); setupDevMode(projectName, reloadWorkingDir); + + printer().printf("Project reloaded: %s%n", reloadWorkingDir); } - printer().printf("Project reloaded: %s%n", reloadWorkingDir); }); if (filter != null) { reloadStrategy.setFileFilter(filter); } - reloadContext = new DefaultCamelContext(false); - reloadContext.addService(reloadStrategy); - reloadContext.start(); + devModeContext = new DefaultCamelContext(false); + devModeContext.addService(reloadStrategy); + devModeContext.start(); if (cleanup) { - installShutdownInterceptor(projectName, workingDir); + installShutdownHook(projectName, workingDir); } } private void startPodLogging(String projectName) throws Exception { try { - var podLogs = new PodLogs(getMain()); - podLogs.withClient(client()); - podLogs.label = "%s=%s".formatted(BaseTrait.KUBERNETES_NAME_LABEL, projectName); + reusablePodLogs = new PodLogs(getMain()); if (!ObjectHelper.isEmpty(namespace)) { - podLogs.namespace = namespace; + reusablePodLogs.namespace = namespace; } - podLogs.doCall(); + reusablePodLogs.name = projectName; + reusablePodLogs.doCall(); } catch (Exception e) { printer().println("Failed to read pod logs - " + e); throw e; @@ -455,24 +489,28 @@ public class KubernetesRun extends KubernetesBaseCommand { } printer().println("Run: " + kubectlCmd); } - client(Pod.class).withLabel(BaseTrait.KUBERNETES_NAME_LABEL, projectName) - .waitUntilCondition(it -> "Running".equals(it.getStatus().getPhase()), 10, TimeUnit.MINUTES); + var pod = client(Pod.class).withLabel(BaseTrait.KUBERNETES_NAME_LABEL, projectName) + .waitUntilCondition(it -> "Running".equals(getPodPhase(it)), 10, TimeUnit.MINUTES); + if (!quiet) { + printer().println(String.format("Pod '%s' in phase %s", pod.getMetadata().getName(), getPodPhase(pod))); + } } - private void installShutdownInterceptor(String projectName, String workingDir) { + private void installShutdownHook(String projectName, String workingDir) { KubernetesDelete deleteCommand = new KubernetesDelete(getMain()); - deleteCommand.name = projectName; + deleteCommand.clusterType = clusterType; deleteCommand.workingDir = workingDir; + deleteCommand.name = projectName; - Thread task = new Thread(() -> { + devModeShutdownTask = new Thread(() -> { try { deleteCommand.doCall(); } catch (Exception e) { throw new RuntimeException(e); } }); - task.setName(ThreadHelper.resolveThreadName(null, "CamelShutdownInterceptor")); - Runtime.getRuntime().addShutdownHook(task); + devModeShutdownTask.setName(ThreadHelper.resolveThreadName(null, "CamelShutdownInterceptor")); + Runtime.getRuntime().addShutdownHook(devModeShutdownTask); } private Integer buildProject(String workingDir) throws IOException, InterruptedException { @@ -493,17 +531,10 @@ public class KubernetesRun extends KubernetesBaseCommand { args.add("--file"); args.add(workingDir); - if (runtime == RuntimeType.quarkus) { - - if (ClusterType.KUBERNETES.isEqualTo(clusterType)) { - if (!ObjectHelper.isEmpty(namespace)) { - args.add("-Dquarkus.kubernetes.namespace=" + namespace); - } - } - - } else { - - if (!ObjectHelper.isEmpty(namespace)) { + if (!ObjectHelper.isEmpty(namespace)) { + if (runtime == RuntimeType.quarkus && ClusterType.KUBERNETES.isEqualTo(clusterType)) { + args.add("-Dquarkus.kubernetes.namespace=" + namespace); + } else { args.add("-Djkube.namespace=%s".formatted(namespace)); } } @@ -547,6 +578,8 @@ public class KubernetesRun extends KubernetesBaseCommand { args.add("--file"); args.add(workingDir); + boolean isOpenshift = ClusterType.OPENSHIFT.isEqualTo(clusterType); + if (runtime == RuntimeType.quarkus) { if (imagePlatforms != null) { @@ -556,7 +589,7 @@ public class KubernetesRun extends KubernetesBaseCommand { args.add("-Dquarkus.container-image.build=" + imageBuild); args.add("-Dquarkus.container-image.push=" + imagePush); - if (ClusterType.OPENSHIFT.isEqualTo(clusterType)) { + if (isOpenshift) { args.add("-Dquarkus.openshift.deploy=true"); } else { args.add("-Dquarkus.kubernetes.deploy=true"); @@ -581,11 +614,8 @@ public class KubernetesRun extends KubernetesBaseCommand { args.add("-Djkube.namespace=%s".formatted(namespace)); } - args.add("package"); - if (reload) { - args.add("k8s:undeploy"); - } - args.add("k8s:deploy"); + var prefix = isOpenshift ? "oc" : "k8s"; + args.add(prefix + ":deploy"); } if (!quiet) { diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/PodLogs.java b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/PodLogs.java index cd6f65d7bf0..42baee9cea7 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/PodLogs.java +++ b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/PodLogs.java @@ -19,19 +19,20 @@ package org.apache.camel.dsl.jbang.core.commands.kubernetes; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.Optional; -import io.fabric8.kubernetes.api.model.Pod; -import io.fabric8.kubernetes.api.model.PodList; import io.fabric8.kubernetes.client.dsl.LogWatch; import io.fabric8.kubernetes.client.dsl.PodResource; import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain; import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.BaseTrait; import org.apache.camel.dsl.jbang.core.common.SourceScheme; import org.apache.camel.util.FileUtil; +import org.apache.camel.util.ObjectHelper; import picocli.CommandLine; import picocli.CommandLine.Command; +import static org.apache.camel.dsl.jbang.core.commands.kubernetes.KubernetesHelper.getPodPhase; + @Command(name = "logs", description = "Print the logs of a Kubernetes pod", sortOptions = false) public class PodLogs extends KubernetesBaseCommand { @@ -56,10 +57,13 @@ public class PodLogs extends KubernetesBaseCommand { description = "The number of lines from the end of the logs to show. Defaults to -1 to show all the lines.") int tail = -1; - int maxWaitAttempts = 30; // total timeout of 60 seconds + // total timeout of 60s + int maxRetryAttempts = 30; + boolean retryForReload; + private int retryCount; // used for testing - int maxLogMessages = -1; + long maxMessageCount = -1; long messageCount = 0; public PodLogs(CamelJBangMain main) { @@ -67,6 +71,7 @@ public class PodLogs extends KubernetesBaseCommand { } public Integer doCall() throws Exception { + if (name == null && label == null && filePath == null) { printer().println("Name or label selector must be set"); return 1; @@ -87,76 +92,69 @@ public class PodLogs extends KubernetesBaseCommand { printer().println("--label selector must be in syntax: key=value"); } - boolean shouldResume = true; - AtomicInteger resumeCount = new AtomicInteger(); - while (shouldResume) { - shouldResume = watchLogs(parts[0], parts[1], container, resumeCount); - printer().printf("PodLogs: [resume=%b, count=%d]%n", shouldResume, resumeCount.get()); - resumeCount.incrementAndGet(); + var retry = watchLogs(); + while ((retry || retryForReload) && ++retryCount < maxRetryAttempts) { sleepWell(); + printer().printf("Retry %d/%d Pod log for label %s%n", retryCount, maxRetryAttempts, label); + retry = watchLogs(); } + printer().println("Stopped pod logging!"); return 0; } - public boolean watchLogs(String label, String labelValue, String container, AtomicInteger resumeCount) { - PodList pods = pods().withLabel(label, labelValue).list(); + // Returns true if a retry should be attempted + private boolean watchLogs() { - Pod pod = pods.getItems().stream() - .filter(p -> p.getStatus().getPhase() != null && !"Terminated".equals(p.getStatus().getPhase())) + PodResource podRes = pods().withLabel(label) + .resources() .findFirst() .orElse(null); - - if (pod == null) { - if (resumeCount.get() == 0) { - printer().printf("Pod for label %s=%s not available - Waiting ...%n".formatted(label, labelValue)); - } - - // use 2-sec delay in waiting for pod logs mode - sleepWell(); - return resumeCount.get() < maxWaitAttempts; - } - - String containerName = null; - if (pod.getSpec() != null && pod.getSpec().getContainers() != null) { - if (container != null && pod.getSpec().getContainers().stream().anyMatch(c -> container.equals(c.getName()))) { - containerName = container; - } else if (!pod.getSpec().getContainers().isEmpty()) { - containerName = pod.getSpec().getContainers().get(0).getName(); - } + if (podRes == null) { + printer().printf("Pod for label %s not available%n", label); + return true; } - PodResource podRes = pods().withName(pod.getMetadata().getName()); + var terminated = isPodTerminated(podRes); + if (!terminated) { - LogWatch logs; - if (tail < 0) { - if (containerName != null) { - logs = podRes.inContainer(containerName).watchLog(); - } else { - logs = podRes.watchLog(); - } - } else { - if (containerName != null) { - logs = podRes.inContainer(containerName).tailingLines(tail).watchLog(); + LogWatch logs; + if (tail < 0) { + if (!ObjectHelper.isEmpty(container)) { + logs = podRes.inContainer(container).watchLog(); + } else { + logs = podRes.watchLog(); + } } else { - logs = podRes.tailingLines(tail).watchLog(); + if (!ObjectHelper.isEmpty(container)) { + logs = podRes.inContainer(container).tailingLines(tail).watchLog(); + } else { + logs = podRes.tailingLines(tail).watchLog(); + } } - } - try (logs; BufferedReader reader = new BufferedReader(new InputStreamReader(logs.getOutput()))) { - String line; - while ((line = reader.readLine()) != null) { - printer().println(line); - if (messageCount++ > maxLogMessages && maxLogMessages > 0) { - return false; + try (logs; BufferedReader reader = new BufferedReader(new InputStreamReader(logs.getOutput()))) { + String line; + while ((line = reader.readLine()) != null) { + printer().println(line); + if (maxMessageCount > 0 && ++messageCount > maxMessageCount) { + return false; + } + retryCount = 0; } - resumeCount.set(0); + } catch (IOException e) { + printer().println("Failed to read pod logs - " + e.getMessage()); } - } catch (IOException e) { - printer().println("Failed to read pod logs - " + e.getMessage()); + + terminated = isPodTerminated(podRes); } - return resumeCount.get() < maxWaitAttempts; + return !terminated; + } + + private boolean isPodTerminated(PodResource podRes) { + var phase = Optional.ofNullable(podRes).map(pr -> getPodPhase(pr.get())).orElse("Unknown"); + return "Terminated".equals(phase); } private void sleepWell() { diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesClientTest.java b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesClientTest.java deleted file mode 100644 index d532b3bb99a..00000000000 --- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesClientTest.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.camel.dsl.jbang.core.commands.kubernetes; - -import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.KubernetesClientException; -import io.fabric8.openshift.client.OpenShiftClient; -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -class KubernetesClientTest { - - private static final Logger log = LoggerFactory.getLogger(KubernetesClientTest.class); - - @Test - public void shouldHaveOpenshiftClient() { - try (KubernetesClient client = KubernetesHelper.getKubernetesClient()) { - OpenShiftClient openShiftClient = client.adapt(OpenShiftClient.class); - try { - openShiftClient.projects().list().getItems().forEach(project -> { - log.debug("Project: {}", project.getMetadata().getName()); - }); - log.info("OpenShiftClient is authenticated and working properly."); - } catch (KubernetesClientException e) { - log.debug("OpenShiftClient is not authenticated: {}", e.getMessage()); - } - } catch (Exception e) { - log.debug("Cannot construct OpenShiftClient: {}", e.getMessage()); - } - } -} diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/PodLogsTest.java b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/PodLogsTest.java index d031c7d2430..ffb83fe20bc 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/PodLogsTest.java +++ b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/PodLogsTest.java @@ -32,8 +32,9 @@ class PodLogsTest extends KubernetesBaseTest { public void shouldHandlePodNotFound() throws Exception { PodLogs command = createCommand(); command.name = "mickey-mouse"; - command.maxWaitAttempts = 2; // total timeout of 4 seconds - command.doCall(); + command.maxRetryAttempts = 2; // total timeout of 4 seconds + int exit = command.doCall(); + Assertions.assertEquals(0, exit); Assertions.assertTrue( printer.getOutput().contains("Pod for label app.kubernetes.io/name=mickey-mouse not available")); @@ -54,7 +55,7 @@ class PodLogsTest extends KubernetesBaseTest { kubernetesClient.pods().resource(pod).create(); var podLog = createCommand(); - podLog.maxLogMessages = 10; + podLog.maxMessageCount = 10; podLog.name = "routes"; int exit = podLog.doCall(); Assertions.assertEquals(0, exit);