diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/VmDefinition.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/VmDefinition.java index 5699017..6a27723 100644 --- a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/VmDefinition.java +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/VmDefinition.java @@ -141,6 +141,16 @@ public class VmDefinition extends K8sDynamicModel { } } + /** + * The assignment information. + * + * @param pool the pool + * @param user the user + * @param lastUsed the last used + */ + public record Assignment(String pool, String user, Instant lastUsed) { + } + /** * Instantiates a new vm definition. * @@ -215,31 +225,15 @@ public class VmDefinition extends K8sDynamicModel { } /** - * The pool that the VM was taken from. + * The assignment information. * * @return the optional */ - public Optional assignedFrom() { - return fromStatus(Status.ASSIGNMENT, "pool"); - } - - /** - * The user that the VM was assigned to. - * - * @return the optional - */ - public Optional assignedTo() { - return fromStatus(Status.ASSIGNMENT, "user"); - } - - /** - * Last usage of assigned VM. - * - * @return the optional - */ - public Optional assignmentLastUsed() { - return this. fromStatus(Status.ASSIGNMENT, "lastUsed") - .map(Instant::parse); + public Optional assignment() { + return this.> fromStatus(Status.ASSIGNMENT) + .filter(m -> !m.isEmpty()).map(a -> new Assignment( + a.get("pool").toString(), a.get("user").toString(), + Instant.parse(a.get("lastUsed").toString()))); } /** diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/VmPool.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/VmPool.java index 155609f..7c13ddb 100644 --- a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/VmPool.java +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/VmPool.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; +import org.jdrupes.vmoperator.common.VmDefinition.Assignment; import org.jdrupes.vmoperator.common.VmDefinition.Grant; import org.jdrupes.vmoperator.common.VmDefinition.Permission; import org.jdrupes.vmoperator.util.DataPath; @@ -165,13 +166,12 @@ public class VmPool { } // If not assigned, it's usable - if (vmDef.assignedTo().isEmpty()) { + if (vmDef.assignment().isEmpty()) { return true; } // Check if it is to be retained - if (vmDef.assignmentLastUsed() - .map(this::retainUntil) + if (vmDef.assignment().map(Assignment::lastUsed).map(this::retainUntil) .map(ru -> Instant.now().isBefore(ru)).orElse(false)) { return false; } diff --git a/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/UpdateAssignment.java b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/UpdateAssignment.java index 676af3d..af9dde0 100644 --- a/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/UpdateAssignment.java +++ b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/UpdateAssignment.java @@ -18,6 +18,7 @@ package org.jdrupes.vmoperator.manager.events; +import org.jdrupes.vmoperator.common.VmPool; import org.jgrapes.core.Event; /** @@ -26,31 +27,31 @@ import org.jgrapes.core.Event; @SuppressWarnings("PMD.DataClass") public class UpdateAssignment extends Event { - private final String usedPool; + private final VmPool fromPool; private final String toUser; /** * Instantiates a new event. * - * @param usedPool the used pool + * @param fromPool the pool from which the VM was assigned * @param toUser the to user */ - public UpdateAssignment(String usedPool, String toUser) { - this.usedPool = usedPool; + public UpdateAssignment(VmPool fromPool, String toUser) { + this.fromPool = fromPool; this.toUser = toUser; } /** - * Gets the pool to assign from. + * Gets the pool from which the VM was assigned. * * @return the pool */ - public String usedPool() { - return usedPool; + public VmPool fromPool() { + return fromPool; } /** - * Gets the user to assign to. + * Gets the user to whom the VM was assigned. * * @return the to user */ diff --git a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml index 2fbeb94..2a59a2c 100644 --- a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml +++ b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml @@ -201,8 +201,8 @@ data: <#if spec.vm.display.outputs?? > outputs: ${ spec.vm.display.outputs?c } - <#if spec.vm.display.loggedInUser?? > - loggedInUser: "${ spec.vm.display.loggedInUser }" + <#if loginRequestedFor?? > + loggedInUser: "${ loginRequestedFor }" <#if spec.vm.display.spice??> spice: diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java index b61b26a..5d5c592 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java @@ -232,7 +232,7 @@ public class Controller extends Component { if (vmStub.updateStatus(vmDef, from -> { JsonObject status = from.statusJson(); var assignment = GsonPtr.to(status).to(Status.ASSIGNMENT); - assignment.set("pool", event.usedPool()); + assignment.set("pool", event.fromPool().name()); assignment.set("user", event.toUser()); assignment.set("lastUsed", Instant.now().toString()); return status; diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/PoolMonitor.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/PoolMonitor.java index 0e25803..5d85280 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/PoolMonitor.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/PoolMonitor.java @@ -36,6 +36,7 @@ import org.jdrupes.vmoperator.common.K8sDynamicModel; import org.jdrupes.vmoperator.common.K8sDynamicModels; import org.jdrupes.vmoperator.common.K8sDynamicStub; import org.jdrupes.vmoperator.common.K8sObserver.ResponseType; +import org.jdrupes.vmoperator.common.VmDefinition.Assignment; import org.jdrupes.vmoperator.common.VmDefinitionStub; import org.jdrupes.vmoperator.common.VmPool; import org.jdrupes.vmoperator.manager.events.GetPools; @@ -165,7 +166,7 @@ public class PoolMonitor extends } // Sync last usage to console state change if user matches - if (vmDef.assignedTo() + if (vmDef.assignment().map(Assignment::user) .map(at -> at.equals(vmDef.consoleUser().orElse(null))) .orElse(true)) { return; @@ -174,8 +175,8 @@ public class PoolMonitor extends var ccChange = vmDef.condition("ConsoleConnected") .map(cc -> cc.getLastTransitionTime().toInstant()); if (ccChange - .map(tt -> vmDef.assignmentLastUsed().map(alu -> alu.isAfter(tt)) - .orElse(true)) + .map(tt -> vmDef.assignment().map(Assignment::lastUsed) + .map(alu -> alu.isAfter(tt)).orElse(true)) .orElse(true)) { return; } diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java index e5bfaec..8df5c88 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java @@ -45,6 +45,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.logging.Level; import static org.jdrupes.vmoperator.common.Constants.APP_NAME; import org.jdrupes.vmoperator.common.Constants.DisplaySecret; import org.jdrupes.vmoperator.common.Convertions; @@ -52,6 +53,9 @@ import org.jdrupes.vmoperator.common.K8sClient; import org.jdrupes.vmoperator.common.K8sObserver; import org.jdrupes.vmoperator.common.K8sV1SecretStub; import org.jdrupes.vmoperator.common.VmDefinition; +import org.jdrupes.vmoperator.common.VmDefinition.Assignment; +import org.jdrupes.vmoperator.common.VmPool; +import org.jdrupes.vmoperator.manager.events.GetPools; import org.jdrupes.vmoperator.manager.events.ResetVm; import org.jdrupes.vmoperator.manager.events.VmChannel; import org.jdrupes.vmoperator.manager.events.VmDefChanged; @@ -212,11 +216,6 @@ public class Reconciler extends Component { @SuppressWarnings("PMD.ConfusingTernary") public void onVmDefChanged(VmDefChanged event, VmChannel channel) throws ApiException, TemplateException, IOException { - // We're only interested in "spec" changes. - if (!event.specChanged()) { - return; - } - // Ownership relationships takes care of deletions if (event.type() == K8sObserver.ResponseType.DELETED) { logger.fine( @@ -228,6 +227,11 @@ public class Reconciler extends Component { Map model = prepareModel(channel.client(), event.vmDefinition()); var configMap = cmReconciler.reconcile(model, channel); + + // The remaining reconcilers depend only on changes of the spec part. + if (!event.specChanged()) { + return; + } model.put("cm", configMap); dsReconciler.reconcile(event, model, channel); // Manage (eventual) removal of stateful set. @@ -266,24 +270,10 @@ public class Reconciler extends Component { Optional.ofNullable(Reconciler.class.getPackage() .getImplementationVersion()).orElse("(Unknown)")); model.put("cr", vmDef); - // Freemarker's static models don't handle nested classes. - model.put("constants", constantsMap(Constants.class)); model.put("reconciler", config); - - // Check if we have a display secret - ListOptions options = new ListOptions(); - options.setLabelSelector("app.kubernetes.io/name=" + APP_NAME + "," - + "app.kubernetes.io/component=" + DisplaySecret.NAME + "," - + "app.kubernetes.io/instance=" + vmDef.name()); - var dsStub = K8sV1SecretStub - .list(client, vmDef.namespace(), options) - .stream() - .findFirst(); - if (dsStub.isPresent()) { - dsStub.get().model().ifPresent(m -> { - model.put("displaySecret", m.getMetadata().getName()); - }); - } + model.put("constants", constantsMap(Constants.class)); + addLoginRequestedFor(model, vmDef); + addDisplaySecret(client, model, vmDef); // Methods model.put("parseQuantity", parseQuantityModel); @@ -294,6 +284,13 @@ public class Reconciler extends Component { return model; } + /** + * Creates a map with constants. Needed because freemarker doesn't support + * nested classes with its static models. + * + * @param clazz the clazz + * @return the map + */ @SuppressWarnings("PMD.EmptyCatchBlock") private Map constantsMap(Class clazz) { @SuppressWarnings("PMD.UseConcurrentHashMap") @@ -318,6 +315,38 @@ public class Reconciler extends Component { return result; } + private void addLoginRequestedFor(Map model, + VmDefinition vmDef) { + vmDef.assignment().filter(a -> { + try { + return newEventPipeline() + .fire(new GetPools().withName(a.pool())).get() + .stream().findFirst().map(VmPool::loginOnAssignment) + .orElse(false); + } catch (InterruptedException e) { + logger.log(Level.WARNING, e, e::getMessage); + } + return false; + }).map(Assignment::user) + .or(() -> vmDef.fromSpec("vm", "display", "loggedInUser")) + .ifPresent(u -> model.put("loginRequestedFor", u)); + } + + private void addDisplaySecret(K8sClient client, Map model, + VmDefinition vmDef) throws ApiException { + ListOptions options = new ListOptions(); + options.setLabelSelector("app.kubernetes.io/name=" + APP_NAME + "," + + "app.kubernetes.io/component=" + DisplaySecret.NAME + "," + + "app.kubernetes.io/instance=" + vmDef.name()); + var dsStub = K8sV1SecretStub + .list(client, vmDef.namespace(), options).stream().findFirst(); + if (dsStub.isPresent()) { + dsStub.get().model().ifPresent(m -> { + model.put("displaySecret", m.getMetadata().getName()); + }); + } + } + private final TemplateMethodModelEx parseQuantityModel = new TemplateMethodModelEx() { @Override diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmMonitor.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmMonitor.java index 9e080e6..1a559b3 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmMonitor.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmMonitor.java @@ -40,6 +40,7 @@ import org.jdrupes.vmoperator.common.K8sV1ConfigMapStub; import org.jdrupes.vmoperator.common.K8sV1PodStub; import org.jdrupes.vmoperator.common.K8sV1StatefulSetStub; import org.jdrupes.vmoperator.common.VmDefinition; +import org.jdrupes.vmoperator.common.VmDefinition.Assignment; import org.jdrupes.vmoperator.common.VmDefinitionStub; import org.jdrupes.vmoperator.common.VmDefinitions; import org.jdrupes.vmoperator.common.VmExtraData; @@ -234,10 +235,10 @@ public class VmMonitor extends || !c.vmDefinition().permissionsFor(event.user().orElse(null), event.roles()).isEmpty()) .filter(c -> event.fromPool().isEmpty() - || c.vmDefinition().assignedFrom() + || c.vmDefinition().assignment().map(Assignment::pool) .map(p -> p.equals(event.fromPool().get())).orElse(false)) .filter(c -> event.toUser().isEmpty() - || c.vmDefinition().assignedTo() + || c.vmDefinition().assignment().map(Assignment::user) .map(u -> u.equals(event.toUser().get())).orElse(false)) .map(c -> new VmData(c.vmDefinition(), c)) .toList()); @@ -257,9 +258,9 @@ public class VmMonitor extends while (true) { // Search for existing assignment. var vmQuery = channelManager.channels().stream() - .filter(c -> c.vmDefinition().assignedFrom() + .filter(c -> c.vmDefinition().assignment().map(Assignment::pool) .map(p -> p.equals(event.fromPool())).orElse(false)) - .filter(c -> c.vmDefinition().assignedTo() + .filter(c -> c.vmDefinition().assignment().map(Assignment::user) .map(u -> u.equals(event.toUser())).orElse(false)) .findFirst(); if (vmQuery.isPresent()) { @@ -280,7 +281,8 @@ public class VmMonitor extends vmQuery = channelManager.channels().stream() .filter(c -> vmPool.isAssignable(c.vmDefinition())) .sorted(Comparator.comparing((VmChannel c) -> c.vmDefinition() - .assignmentLastUsed().orElse(Instant.ofEpochSecond(0))) + .assignment().map(Assignment::lastUsed) + .orElse(Instant.ofEpochSecond(0))) .thenComparing(preferRunning)) .findFirst(); @@ -293,7 +295,7 @@ public class VmMonitor extends var chosenVm = vmQuery.get(); var vmPipeline = chosenVm.pipeline(); if (Optional.ofNullable(vmPipeline.fire(new UpdateAssignment( - vmPool.name(), event.toUser()), chosenVm).get()) + vmPool, event.toUser()), chosenVm).get()) .orElse(false)) { var vmDef = chosenVm.vmDefinition(); event.setResult(new VmData(vmDef, chosenVm)); @@ -301,10 +303,6 @@ public class VmMonitor extends // Make sure that a newly assigned VM is running. chosenVm.pipeline().fire(new ModifyVm(vmDef.name(), "state", "Running", chosenVm)); - if (vmPool.loginOnAssignment()) { - chosenVm.pipeline().fire(new ModifyVm(vmDef.name(), - "display/loggedInUser", event.toUser(), chosenVm)); - } return; } } diff --git a/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/GsonPtr.java b/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/GsonPtr.java index 36c3444..f78374c 100644 --- a/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/GsonPtr.java +++ b/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/GsonPtr.java @@ -327,6 +327,18 @@ public class GsonPtr { return set(selector, new JsonPrimitive(value)); } + /** + * Short for `set(selector, new JsonPrimitive(value))`. + * + * @param selector the selector + * @param value the value + * @return the gson ptr + * @see #set(Object, JsonElement) + */ + public GsonPtr set(Object selector, Boolean value) { + return set(selector, new JsonPrimitive(value)); + } + /** * Same as {@link #set(Object, JsonElement)}, but sets the value * only if it doesn't exist yet, else returns the existing value. diff --git a/org.jdrupes.vmoperator.vmaccess/src/org/jdrupes/vmoperator/vmaccess/VmAccess.java b/org.jdrupes.vmoperator.vmaccess/src/org/jdrupes/vmoperator/vmaccess/VmAccess.java index d6d9e64..5625a4d 100644 --- a/org.jdrupes.vmoperator.vmaccess/src/org/jdrupes/vmoperator/vmaccess/VmAccess.java +++ b/org.jdrupes.vmoperator.vmaccess/src/org/jdrupes/vmoperator/vmaccess/VmAccess.java @@ -46,6 +46,7 @@ import java.util.stream.Collectors; import org.bouncycastle.util.Objects; import org.jdrupes.vmoperator.common.K8sObserver; import org.jdrupes.vmoperator.common.VmDefinition; +import org.jdrupes.vmoperator.common.VmDefinition.Assignment; import org.jdrupes.vmoperator.common.VmDefinition.Permission; import org.jdrupes.vmoperator.common.VmPool; import org.jdrupes.vmoperator.manager.events.AssignVm; @@ -657,10 +658,11 @@ public class VmAccess extends FreeMarkerConlet { var user = WebConsoleUtils.userFromSession(connection.session()) .map(ConsoleUser::getName).orElse(null); - var toBeUsedByConlet = vmDef.assignedFrom() + var toBeUsedByConlet = vmDef.assignment() + .map(Assignment::pool) .map(p -> p.equals(model.get().name())).orElse(false) - && vmDef.assignedTo().map(u -> u.equals(user)) - .orElse(false); + && vmDef.assignment().map(Assignment::user) + .map(u -> u.equals(user)).orElse(false); if (!Objects.areEqual(model.get().assignedVm(), vmDef.name()) && !toBeUsedByConlet) { continue;