From 8567a2f052e8756338ea47fe3017f279a28ac432 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" <1446020+mnlipp@users.noreply.github.com> Date: Tue, 24 Oct 2023 13:23:13 +0200 Subject: [PATCH] Feature/web gui (#15) Add node display. --- .../jdrupes/vmoperator/common/Constants.java | 3 + org.jdrupes.vmoperator.manager/build.gradle | 4 +- .../jdrupes/vmoperator/manager/Constants.java | 3 - .../jdrupes/vmoperator/manager/VmWatcher.java | 56 +++++++++++++++---- .../vmconlet/VmConlet-view.ftl.html | 17 ++++-- .../vmoperator/vmconlet/l10n.properties | 1 + .../vmoperator/vmconlet/l10n_de.properties | 1 + .../jdrupes/vmoperator/vmconlet/VmConlet.java | 5 +- .../vmconlet/browser/VmConlet-functions.ts | 13 +++-- .../vmconlet/browser/VmConlet-style.scss | 15 +++++ 10 files changed, 90 insertions(+), 28 deletions(-) diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/Constants.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/Constants.java index 22decd3..3ebe29d 100644 --- a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/Constants.java +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/Constants.java @@ -23,6 +23,9 @@ package org.jdrupes.vmoperator.common; */ public class Constants { + /** The Constant APP_NAME. */ + public static final String APP_NAME = "vm-runner"; + /** The Constant VM_OP_NAME. */ public static final String VM_OP_NAME = "vm-operator"; diff --git a/org.jdrupes.vmoperator.manager/build.gradle b/org.jdrupes.vmoperator.manager/build.gradle index c112737..33a6fb5 100644 --- a/org.jdrupes.vmoperator.manager/build.gradle +++ b/org.jdrupes.vmoperator.manager/build.gradle @@ -19,7 +19,7 @@ dependencies { implementation 'org.jgrapes:org.jgrapes.util:[1.31.0,2)' implementation 'org.jgrapes:org.jgrapes.webconsole.base:[1.2.0,2)' - implementation 'org.jgrapes:org.jgrapes.webconsole.vuejs:[1.3.0,2)' + implementation 'org.jgrapes:org.jgrapes.webconsole.vuejs:[1.5.0,2)' implementation 'org.jgrapes:org.jgrapes.webconsole.rbac:[1.0.0,2)' implementation 'org.jgrapes:org.jgrapes.webconlet.locallogin:[1.0.0,2)' @@ -39,7 +39,7 @@ dependencies { application { applicationName = 'vm-manager' - applicationDefaultJvmArgs = ['-Xmx50m', '-XX:+UseParallelGC', + applicationDefaultJvmArgs = ['-Xmx128m', '-XX:+UseParallelGC', '-Djava.util.logging.manager=org.jdrupes.vmoperator.util.LongLoggingManager' ] // Define the main class for the application. diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java index bf51a59..2ef4199 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java @@ -23,9 +23,6 @@ package org.jdrupes.vmoperator.manager; */ public class Constants extends org.jdrupes.vmoperator.common.Constants { - /** The Constant APP_NAME. */ - public static final String APP_NAME = "vm-runner"; - /** The Constant STATE_RUNNING. */ public static final String STATE_RUNNING = "Running"; diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java index 492ad40..a09b4b2 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java @@ -18,6 +18,8 @@ package org.jdrupes.vmoperator.manager; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; import com.google.gson.reflect.TypeToken; import io.kubernetes.client.openapi.ApiClient; import io.kubernetes.client.openapi.ApiException; @@ -31,6 +33,7 @@ import io.kubernetes.client.openapi.models.V1ObjectMeta; import io.kubernetes.client.util.Config; import io.kubernetes.client.util.Watch; import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesApi; +import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject; import io.kubernetes.client.util.generic.options.ListOptions; import java.io.IOException; import java.nio.file.Files; @@ -52,6 +55,7 @@ import static org.jdrupes.vmoperator.manager.Constants.VM_OP_NAME; import org.jdrupes.vmoperator.manager.events.VmChannel; import org.jdrupes.vmoperator.manager.events.VmDefChanged; import org.jdrupes.vmoperator.manager.events.VmDefChanged.Type; +import org.jdrupes.vmoperator.util.GsonPtr; import org.jgrapes.core.Channel; import org.jgrapes.core.Component; import org.jgrapes.core.Components; @@ -251,8 +255,8 @@ public class VmWatcher extends Component { } private void handleVmDefinitionChange(V1APIResource vmsCrd, - Watch.Response item) { - V1ObjectMeta metadata = item.object.getMetadata(); + Watch.Response vmDefStub) { + V1ObjectMeta metadata = vmDefStub.object.getMetadata(); VmChannel channel = channels.computeIfAbsent(metadata.getName(), k -> { try { @@ -268,20 +272,52 @@ public class VmWatcher extends Component { return; } - // if (event.type() == Type.DELETED) { - // Get full definition and associate with channel as backup - var apiVersion = K8s.version(item.object.getApiVersion()); + var apiVersion = K8s.version(vmDefStub.object.getApiVersion()); DynamicKubernetesApi vmCrApi = new DynamicKubernetesApi(VM_OP_GROUP, apiVersion, vmsCrd.getName(), channel.client()); - var vmDef = K8s.get(vmCrApi, metadata); - vmDef.ifPresent(def -> channel.setVmDefinition(def)); + var curVmDef = K8s.get(vmCrApi, metadata); + curVmDef.ifPresent(def -> channel.setVmDefinition(def)); + + // Get eventual definition and augment with "dynamic" data. + var vmDef = curVmDef.orElse(channel.vmDefinition()); + addDynamicData(channel, vmDef); // Create and fire event channel.pipeline().fire(new VmDefChanged(VmDefChanged.Type - .valueOf(item.type), - channel.setGeneration(item.object.getMetadata().getGeneration()), - vmsCrd, vmDef.orElse(channel.vmDefinition())), channel); + .valueOf(vmDefStub.type), + channel + .setGeneration(vmDefStub.object.getMetadata().getGeneration()), + vmsCrd, vmDef), channel); + } + + private void addDynamicData(VmChannel channel, + DynamicKubernetesObject vmDef) { + var rootNode = GsonPtr.to(vmDef.getRaw()).get(JsonObject.class); + rootNode.addProperty("nodeName", ""); + + // VM definition status changes before the pod terminates. + // This results in pod information being shown for a stopped + // VM which is irritating. So check condition first. + var isRunning = GsonPtr.to(rootNode).to("status", "conditions") + .get(JsonArray.class) + .asList().stream().filter(el -> "Running" + .equals(((JsonObject) el).get("type").getAsString())) + .findFirst().map(el -> "True" + .equals(((JsonObject) el).get("status").getAsString())) + .orElse(false); + if (!isRunning) { + return; + } + var podSearch = new ListOptions(); + podSearch.setLabelSelector("app.kubernetes.io/name=" + APP_NAME + + ",app.kubernetes.io/component=" + APP_NAME + + ",app.kubernetes.io/instance=" + vmDef.getMetadata().getName()); + var podList = K8s.podApi(channel.client()).list(namespaceToWatch, + podSearch); + podList.getObject().getItems().stream().forEach(pod -> { + rootNode.addProperty("nodeName", pod.getSpec().getNodeName()); + }); } /** diff --git a/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-view.ftl.html b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-view.ftl.html index 5e7d4b9..83492f4 100644 --- a/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-view.ftl.html +++ b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-view.ftl.html @@ -33,26 +33,33 @@ - + + + v-html="formatMemory(entry[key])"> - - + + { public void onVmDefChanged(VmDefChanged event, VmChannel channel) throws JsonDecodeException { if (event.type() == Type.DELETED) { - vmInfos.remove(event.vmDefinition().getMetadata().getName()); + var vmName = event.vmDefinition().getMetadata().getName(); + vmInfos.remove(vmName); for (var entry : conletIdsByConsoleConnection().entrySet()) { for (String conletId : entry.getValue()) { entry.getKey().respond(new NotifyConletView(type(), - conletId, "removeVm")); + conletId, "removeVm", vmName)); } } } else { diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-functions.ts b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-functions.ts index f0f8919..43ab44b 100644 --- a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-functions.ts +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-functions.ts @@ -73,7 +73,7 @@ window.orgJDrupesVmOperatorVmConlet = {}; let vmInfos = reactive(new Map()); window.orgJDrupesVmOperatorVmConlet.initPreview - = (previewDom: HTMLElement, isUpdate: boolean) => { + = (previewDom: HTMLElement, _isUpdate: boolean) => { const app = createApp({}); app.use(JgwcPlugin, []); app.config.globalProperties.window = window; @@ -81,7 +81,7 @@ window.orgJDrupesVmOperatorVmConlet.initPreview }; window.orgJDrupesVmOperatorVmConlet.initView = (viewDom: HTMLElement, - isUpdate: boolean) => { + _isUpdate: boolean) => { const app = createApp({ setup(_props: any) { const conletId: string @@ -96,7 +96,8 @@ window.orgJDrupesVmOperatorVmConlet.initView = (viewDom: HTMLElement, ["name", "vmname"], ["running", "running"], ["currentCpus", "currentCpus"], - ["currentRam", "currentRam"] + ["currentRam", "currentRam"], + ["nodeName", "nodeName"] ], { sortKey: "name", sortOrder: "up" @@ -127,11 +128,11 @@ window.orgJDrupesVmOperatorVmConlet.initView = (viewDom: HTMLElement, }; JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmconlet.VmConlet", - "updateVm", function(conletId: String, vmDefinition: any) { + "updateVm", function(_conletId: String, vmDefinition: any) { // Add some short-cuts for table controller vmDefinition.name = vmDefinition.metadata.name; vmDefinition.currentCpus = vmDefinition.status.cpus; - vmDefinition.currentRam = vmDefinition.status.ram; + vmDefinition.currentRam = BigInt(vmDefinition.status.ram); for (let condition of vmDefinition.status.conditions) { if (condition.type === "Running") { vmDefinition.running = condition.status === "True"; @@ -143,6 +144,6 @@ JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmconlet.VmConlet", }); JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmconlet.VmConlet", - "removeVm", function(conletId: String, vmName: String) { + "removeVm", function(_conletId: String, vmName: String) { vmInfos.delete(vmName); }); diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-style.scss b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-style.scss index 6d3168b..349d5be 100644 --- a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-style.scss +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-style.scss @@ -49,3 +49,18 @@ padding-left: 1em; } +.jdrupes-vmoperator-vmconlet-view-table { + td.column-running { + text-align: center; + + span { + &.fa-check { + color: var(--success); + } + + &.fa-close { + color: var(--danger); + } + } + } +}