Basically working.
This commit is contained in:
parent
d060a9334a
commit
6d5ba8829c
12 changed files with 493 additions and 148 deletions
|
|
@ -44,7 +44,7 @@ spec:
|
||||||
- reset
|
- reset
|
||||||
- accessConsole
|
- accessConsole
|
||||||
- "*"
|
- "*"
|
||||||
default: []
|
default: ["accessConsole"]
|
||||||
required:
|
required:
|
||||||
- permissions
|
- permissions
|
||||||
# either Namespaced or Cluster
|
# either Namespaced or Cluster
|
||||||
|
|
|
||||||
|
|
@ -1022,7 +1022,7 @@ spec:
|
||||||
pools:
|
pools:
|
||||||
type: array
|
type: array
|
||||||
description: >-
|
description: >-
|
||||||
List of pools to which this VM belongs.
|
List of pools this VM belongs to.
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
default: []
|
default: []
|
||||||
|
|
@ -1495,17 +1495,14 @@ spec:
|
||||||
description: >-
|
description: >-
|
||||||
The pool this VM is taken from.
|
The pool this VM is taken from.
|
||||||
type: string
|
type: string
|
||||||
default: ""
|
|
||||||
user:
|
user:
|
||||||
description: >-
|
description: >-
|
||||||
The user this VM is assigned to.
|
The user this VM is assigned to.
|
||||||
type: string
|
type: string
|
||||||
default: ""
|
|
||||||
lastUsed:
|
lastUsed:
|
||||||
description: >-
|
description: >-
|
||||||
The last time this VM was used by the user.
|
The last time this VM was used by the user.
|
||||||
type: string
|
type: string
|
||||||
default: "1970-01-01T00:00:00Z"
|
|
||||||
default: {}
|
default: {}
|
||||||
conditions:
|
conditions:
|
||||||
description: >-
|
description: >-
|
||||||
|
|
|
||||||
|
|
@ -8,3 +8,8 @@ spec:
|
||||||
- user: admin
|
- user: admin
|
||||||
may:
|
may:
|
||||||
- accessConsole
|
- accessConsole
|
||||||
|
- user: test
|
||||||
|
may:
|
||||||
|
- accessConsole
|
||||||
|
- start
|
||||||
|
- stop
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,6 @@ spec:
|
||||||
- role: admin
|
- role: admin
|
||||||
may:
|
may:
|
||||||
- "*"
|
- "*"
|
||||||
- user: test
|
|
||||||
may:
|
|
||||||
- accessConsole
|
|
||||||
|
|
||||||
guestShutdownStops: true
|
guestShutdownStops: true
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,6 @@ spec:
|
||||||
- user: admin
|
- user: admin
|
||||||
may:
|
may:
|
||||||
- "*"
|
- "*"
|
||||||
- user: test
|
|
||||||
may:
|
|
||||||
- "accessConsole"
|
|
||||||
|
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,11 @@ public class VmDefinition {
|
||||||
return Set.of(reprs.get(value));
|
return Set.of(reprs.get(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To string.
|
||||||
|
*
|
||||||
|
* @return the string
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return repr;
|
return repr;
|
||||||
|
|
@ -104,6 +109,11 @@ public class VmDefinition {
|
||||||
*/
|
*/
|
||||||
public record Grant(String user, String role, Set<Permission> may) {
|
public record Grant(String user, String role, Set<Permission> may) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To string.
|
||||||
|
*
|
||||||
|
* @return the string
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
|
|
@ -180,6 +190,16 @@ public class VmDefinition {
|
||||||
this.metadata = metadata;
|
this.metadata = metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pools that this VM belongs to.
|
||||||
|
*
|
||||||
|
* @return the list
|
||||||
|
*/
|
||||||
|
public List<String> pools() {
|
||||||
|
return this.<List<String>> fromSpec("pools")
|
||||||
|
.orElse(Collections.emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the spec.
|
* Gets the spec.
|
||||||
*
|
*
|
||||||
|
|
@ -268,6 +288,24 @@ public class VmDefinition {
|
||||||
this.status = status;
|
this.status = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pool that the VM was taken from.
|
||||||
|
*
|
||||||
|
* @return the optional
|
||||||
|
*/
|
||||||
|
public Optional<String> assignedFrom() {
|
||||||
|
return fromStatus("assignment", "pool");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The user that the VM was assigned to.
|
||||||
|
*
|
||||||
|
* @return the optional
|
||||||
|
*/
|
||||||
|
public Optional<String> assignedTo() {
|
||||||
|
return fromStatus("assignment", "user");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a condition's status.
|
* Return a condition's status.
|
||||||
*
|
*
|
||||||
|
|
@ -298,6 +336,7 @@ public class VmDefinition {
|
||||||
/**
|
/**
|
||||||
* Return extra data.
|
* Return extra data.
|
||||||
*
|
*
|
||||||
|
* @param <T> the generic type
|
||||||
* @param property the property
|
* @param property the property
|
||||||
* @return the object
|
* @return the object
|
||||||
*/
|
*/
|
||||||
|
|
@ -325,7 +364,7 @@ public class VmDefinition {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the requested VM state
|
* Return the requested VM state.
|
||||||
*
|
*
|
||||||
* @return the string
|
* @return the string
|
||||||
*/
|
*/
|
||||||
|
|
@ -367,11 +406,22 @@ public class VmDefinition {
|
||||||
.map(Number::longValue);
|
.map(Number::longValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hash code.
|
||||||
|
*
|
||||||
|
* @return the int
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(metadata.getNamespace(), metadata.getName());
|
return Objects.hash(metadata.getNamespace(), metadata.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Equals.
|
||||||
|
*
|
||||||
|
* @param obj the obj
|
||||||
|
* @return true, if successful
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
if (this == obj) {
|
if (this == obj) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* VM-Operator
|
||||||
|
* Copyright (C) 2024 Michael N. Lipp
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jdrupes.vmoperator.manager.events;
|
||||||
|
|
||||||
|
import org.jdrupes.vmoperator.manager.events.GetVms.VmData;
|
||||||
|
import org.jgrapes.core.Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign a VM from a pool to a user.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("PMD.DataClass")
|
||||||
|
public class AssignVm extends Event<VmData> {
|
||||||
|
|
||||||
|
private final String fromPool;
|
||||||
|
private final String toUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new event.
|
||||||
|
*
|
||||||
|
* @param fromPool the from pool
|
||||||
|
* @param toUser the to user
|
||||||
|
*/
|
||||||
|
public AssignVm(String fromPool, String toUser) {
|
||||||
|
this.fromPool = fromPool;
|
||||||
|
this.toUser = toUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the pool to assign from.
|
||||||
|
*
|
||||||
|
* @return the pool
|
||||||
|
*/
|
||||||
|
public String fromPool() {
|
||||||
|
return fromPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the user to assign to.
|
||||||
|
*
|
||||||
|
* @return the to user
|
||||||
|
*/
|
||||||
|
public String toUser() {
|
||||||
|
return toUser;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -33,6 +33,8 @@ public class GetVms extends Event<List<GetVms.VmData>> {
|
||||||
private String name;
|
private String name;
|
||||||
private String user;
|
private String user;
|
||||||
private List<String> roles = Collections.emptyList();
|
private List<String> roles = Collections.emptyList();
|
||||||
|
private String fromPool;
|
||||||
|
private String toUser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return only the VMs with the given name.
|
* Return only the VMs with the given name.
|
||||||
|
|
@ -59,6 +61,28 @@ public class GetVms extends Event<List<GetVms.VmData>> {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return only {@link VmDefinition}s that are assigned from the given pool.
|
||||||
|
*
|
||||||
|
* @param pool the pool
|
||||||
|
* @return the returns the vms
|
||||||
|
*/
|
||||||
|
public GetVms assignedFrom(String pool) {
|
||||||
|
this.fromPool = pool;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return only {@link VmDefinition}s that are assigned to the given user.
|
||||||
|
*
|
||||||
|
* @param user the user
|
||||||
|
* @return the returns the vms
|
||||||
|
*/
|
||||||
|
public GetVms assignedTo(String user) {
|
||||||
|
this.toUser = user;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the name filter criterion, if set.
|
* Returns the name filter criterion, if set.
|
||||||
*
|
*
|
||||||
|
|
@ -86,6 +110,24 @@ public class GetVms extends Event<List<GetVms.VmData>> {
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the pool filter criterion, if set.
|
||||||
|
*
|
||||||
|
* @return the optional
|
||||||
|
*/
|
||||||
|
public Optional<String> fromPool() {
|
||||||
|
return Optional.ofNullable(fromPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the user filter criterion, if set.
|
||||||
|
*
|
||||||
|
* @return the optional
|
||||||
|
*/
|
||||||
|
public Optional<String> toUser() {
|
||||||
|
return Optional.ofNullable(toUser);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return tuple.
|
* Return tuple.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@
|
||||||
|
|
||||||
package org.jdrupes.vmoperator.manager;
|
package org.jdrupes.vmoperator.manager;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import io.kubernetes.client.apimachinery.GroupVersionKind;
|
||||||
import io.kubernetes.client.openapi.ApiException;
|
import io.kubernetes.client.openapi.ApiException;
|
||||||
import io.kubernetes.client.openapi.models.V1ObjectMeta;
|
import io.kubernetes.client.openapi.models.V1ObjectMeta;
|
||||||
import io.kubernetes.client.util.Watch;
|
import io.kubernetes.client.util.Watch;
|
||||||
|
|
@ -29,6 +31,7 @@ import java.util.Set;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import static org.jdrupes.vmoperator.common.Constants.VM_OP_GROUP;
|
import static org.jdrupes.vmoperator.common.Constants.VM_OP_GROUP;
|
||||||
|
import static org.jdrupes.vmoperator.common.Constants.VM_OP_KIND_VM;
|
||||||
import org.jdrupes.vmoperator.common.K8s;
|
import org.jdrupes.vmoperator.common.K8s;
|
||||||
import org.jdrupes.vmoperator.common.K8sClient;
|
import org.jdrupes.vmoperator.common.K8sClient;
|
||||||
import org.jdrupes.vmoperator.common.K8sDynamicStub;
|
import org.jdrupes.vmoperator.common.K8sDynamicStub;
|
||||||
|
|
@ -41,13 +44,15 @@ import org.jdrupes.vmoperator.common.VmDefinitionModel;
|
||||||
import org.jdrupes.vmoperator.common.VmDefinitionModels;
|
import org.jdrupes.vmoperator.common.VmDefinitionModels;
|
||||||
import org.jdrupes.vmoperator.common.VmDefinitionStub;
|
import org.jdrupes.vmoperator.common.VmDefinitionStub;
|
||||||
import static org.jdrupes.vmoperator.manager.Constants.APP_NAME;
|
import static org.jdrupes.vmoperator.manager.Constants.APP_NAME;
|
||||||
import static org.jdrupes.vmoperator.manager.Constants.VM_OP_KIND_VM;
|
|
||||||
import static org.jdrupes.vmoperator.manager.Constants.VM_OP_NAME;
|
import static org.jdrupes.vmoperator.manager.Constants.VM_OP_NAME;
|
||||||
|
import org.jdrupes.vmoperator.manager.events.AssignVm;
|
||||||
import org.jdrupes.vmoperator.manager.events.ChannelManager;
|
import org.jdrupes.vmoperator.manager.events.ChannelManager;
|
||||||
import org.jdrupes.vmoperator.manager.events.GetVms;
|
import org.jdrupes.vmoperator.manager.events.GetVms;
|
||||||
import org.jdrupes.vmoperator.manager.events.GetVms.VmData;
|
import org.jdrupes.vmoperator.manager.events.GetVms.VmData;
|
||||||
|
import org.jdrupes.vmoperator.manager.events.ModifyVm;
|
||||||
import org.jdrupes.vmoperator.manager.events.VmChannel;
|
import org.jdrupes.vmoperator.manager.events.VmChannel;
|
||||||
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
|
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
|
||||||
|
import org.jdrupes.vmoperator.util.GsonPtr;
|
||||||
import org.jgrapes.core.Channel;
|
import org.jgrapes.core.Channel;
|
||||||
import org.jgrapes.core.Event;
|
import org.jgrapes.core.Event;
|
||||||
import org.jgrapes.core.annotation.Handler;
|
import org.jgrapes.core.annotation.Handler;
|
||||||
|
|
@ -225,7 +230,60 @@ public class VmMonitor extends
|
||||||
.filter(c -> event.user().isEmpty() && event.roles().isEmpty()
|
.filter(c -> event.user().isEmpty() && event.roles().isEmpty()
|
||||||
|| !c.vmDefinition().permissionsFor(event.user().orElse(null),
|
|| !c.vmDefinition().permissionsFor(event.user().orElse(null),
|
||||||
event.roles()).isEmpty())
|
event.roles()).isEmpty())
|
||||||
|
.filter(c -> event.fromPool().isEmpty()
|
||||||
|
|| c.vmDefinition().assignedFrom()
|
||||||
|
.map(p -> p.equals(event.fromPool().get())).orElse(false))
|
||||||
|
.filter(c -> event.toUser().isEmpty()
|
||||||
|
|| c.vmDefinition().assignedTo()
|
||||||
|
.map(u -> u.equals(event.toUser().get())).orElse(false))
|
||||||
.map(c -> new VmData(c.vmDefinition(), c))
|
.map(c -> new VmData(c.vmDefinition(), c))
|
||||||
.toList());
|
.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign a VM if not already assigned.
|
||||||
|
*
|
||||||
|
* @param event the event
|
||||||
|
* @throws ApiException the api exception
|
||||||
|
*/
|
||||||
|
@Handler
|
||||||
|
public void onAssignVm(AssignVm event) throws ApiException {
|
||||||
|
// Search for existing assignment.
|
||||||
|
var assignedVm = channelManager.channels().stream()
|
||||||
|
.filter(c -> c.vmDefinition().assignedFrom()
|
||||||
|
.map(p -> p.equals(event.fromPool())).orElse(false))
|
||||||
|
.filter(c -> c.vmDefinition().assignedTo()
|
||||||
|
.map(u -> u.equals(event.toUser())).orElse(false))
|
||||||
|
.findFirst();
|
||||||
|
if (assignedVm.isPresent()) {
|
||||||
|
event.setResult(new VmData(assignedVm.get().vmDefinition(),
|
||||||
|
assignedVm.get()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find available VM.
|
||||||
|
assignedVm = channelManager.channels().stream()
|
||||||
|
.filter(c -> c.vmDefinition().pools().contains(event.fromPool()))
|
||||||
|
.filter(c -> c.vmDefinition().assignedTo().isEmpty())
|
||||||
|
.findFirst();
|
||||||
|
if (assignedVm.isPresent()) {
|
||||||
|
var vmDef = assignedVm.get().vmDefinition();
|
||||||
|
var vmStub = VmDefinitionStub.get(client(),
|
||||||
|
new GroupVersionKind(VM_OP_GROUP, "", VM_OP_KIND_VM),
|
||||||
|
vmDef.namespace(), vmDef.name());
|
||||||
|
vmStub.updateStatus(from -> {
|
||||||
|
JsonObject status = from.status();
|
||||||
|
var assignment = GsonPtr.to(status).to("assignment");
|
||||||
|
assignment.set("pool", event.fromPool());
|
||||||
|
assignment.set("user", event.toUser());
|
||||||
|
return status;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Always start a newly assigned VM.
|
||||||
|
fire(new ModifyVm(vmDef.name(), "state", "Running",
|
||||||
|
assignedVm.get()));
|
||||||
|
event.setResult(new VmData(vmDef, assignedVm.get()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* VM-Operator
|
* VM-Operator
|
||||||
* Copyright (C) 2023,2024 Michael N. Lipp
|
* Copyright (C) 2023,2025 Michael N. Lipp
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Affero General Public License as
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
|
@ -51,6 +51,7 @@ import org.jdrupes.vmoperator.common.K8sObserver;
|
||||||
import org.jdrupes.vmoperator.common.VmDefinition;
|
import org.jdrupes.vmoperator.common.VmDefinition;
|
||||||
import org.jdrupes.vmoperator.common.VmDefinition.Permission;
|
import org.jdrupes.vmoperator.common.VmDefinition.Permission;
|
||||||
import org.jdrupes.vmoperator.common.VmPool;
|
import org.jdrupes.vmoperator.common.VmPool;
|
||||||
|
import org.jdrupes.vmoperator.manager.events.AssignVm;
|
||||||
import org.jdrupes.vmoperator.manager.events.GetDisplayPassword;
|
import org.jdrupes.vmoperator.manager.events.GetDisplayPassword;
|
||||||
import org.jdrupes.vmoperator.manager.events.GetPools;
|
import org.jdrupes.vmoperator.manager.events.GetPools;
|
||||||
import org.jdrupes.vmoperator.manager.events.GetVms;
|
import org.jdrupes.vmoperator.manager.events.GetVms;
|
||||||
|
|
@ -117,6 +118,7 @@ import org.jgrapes.webconsole.base.freemarker.FreeMarkerConlet;
|
||||||
public class VmAccess extends FreeMarkerConlet<VmAccess.ResourceModel> {
|
public class VmAccess extends FreeMarkerConlet<VmAccess.ResourceModel> {
|
||||||
|
|
||||||
private static final String VM_NAME_PROPERTY = "vmName";
|
private static final String VM_NAME_PROPERTY = "vmName";
|
||||||
|
private static final String POOL_NAME_PROPERTY = "poolName";
|
||||||
private static final String RENDERED
|
private static final String RENDERED
|
||||||
= VmAccess.class.getName() + ".rendered";
|
= VmAccess.class.getName() + ".rendered";
|
||||||
private static final String PENDING
|
private static final String PENDING
|
||||||
|
|
@ -281,33 +283,56 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ResourceModel> {
|
||||||
private void addMissingConlets(ConsoleConfigured event,
|
private void addMissingConlets(ConsoleConfigured event,
|
||||||
ConsoleConnection connection, final Set<ResourceModel> rendered)
|
ConsoleConnection connection, final Set<ResourceModel> rendered)
|
||||||
throws InterruptedException {
|
throws InterruptedException {
|
||||||
boolean foundMissing = false;
|
|
||||||
var session = connection.session();
|
var session = connection.session();
|
||||||
for (var vmName : appPipeline.fire(new GetVms().accessibleFor(
|
|
||||||
|
// Evaluate missing VMs
|
||||||
|
var missingVms = appPipeline.fire(new GetVms().accessibleFor(
|
||||||
WebConsoleUtils.userFromSession(session)
|
WebConsoleUtils.userFromSession(session)
|
||||||
.map(ConsoleUser::getName).orElse(null),
|
.map(ConsoleUser::getName).orElse(null),
|
||||||
WebConsoleUtils.rolesFromSession(session).stream()
|
WebConsoleUtils.rolesFromSession(session).stream()
|
||||||
.map(ConsoleRole::getName).toList()))
|
.map(ConsoleRole::getName).toList()))
|
||||||
.get().stream().map(d -> d.definition().name()).toList()) {
|
.get().stream().map(d -> d.definition().name())
|
||||||
if (rendered.stream()
|
.collect(Collectors.toCollection(HashSet::new));
|
||||||
.anyMatch(r -> r.mode() == ResourceModel.Mode.VM
|
missingVms.removeAll(rendered.stream()
|
||||||
&& r.name().equals(vmName))) {
|
.filter(r -> r.mode() == ResourceModel.Mode.VM)
|
||||||
continue;
|
.map(ResourceModel::name).toList());
|
||||||
|
|
||||||
|
// Evaluate missing pools
|
||||||
|
var missingPools = appPipeline.fire(new GetPools().accessibleFor(
|
||||||
|
WebConsoleUtils.userFromSession(session)
|
||||||
|
.map(ConsoleUser::getName).orElse(null),
|
||||||
|
WebConsoleUtils.rolesFromSession(session).stream()
|
||||||
|
.map(ConsoleRole::getName).toList()))
|
||||||
|
.get().stream().map(VmPool::name)
|
||||||
|
.collect(Collectors.toCollection(HashSet::new));
|
||||||
|
missingPools.removeAll(rendered.stream()
|
||||||
|
.filter(r -> r.mode() == ResourceModel.Mode.POOL)
|
||||||
|
.map(ResourceModel::name).toList());
|
||||||
|
|
||||||
|
// Nothing to do
|
||||||
|
if (missingVms.isEmpty() && missingPools.isEmpty()) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (!foundMissing) {
|
|
||||||
// Suspending to allow rendering of conlets to be noticed
|
// Suspending to allow rendering of conlets to be noticed
|
||||||
var failSafe = Components.schedule(t -> event.resumeHandling(),
|
var failSafe = Components.schedule(t -> event.resumeHandling(),
|
||||||
Duration.ofSeconds(1));
|
Duration.ofSeconds(1));
|
||||||
event.suspendHandling(failSafe::cancel);
|
event.suspendHandling(failSafe::cancel);
|
||||||
connection.setAssociated(PENDING, event);
|
connection.setAssociated(PENDING, event);
|
||||||
foundMissing = true;
|
|
||||||
}
|
// Create conlets for VMs and pools that haven't been rendered
|
||||||
|
for (var vmName : missingVms) {
|
||||||
fire(new AddConletRequest(event.event().event().renderSupport(),
|
fire(new AddConletRequest(event.event().event().renderSupport(),
|
||||||
VmAccess.class.getName(),
|
VmAccess.class.getName(), RenderMode.asSet(RenderMode.Preview))
|
||||||
RenderMode.asSet(RenderMode.Preview))
|
|
||||||
.addProperty(VM_NAME_PROPERTY, vmName),
|
.addProperty(VM_NAME_PROPERTY, vmName),
|
||||||
connection);
|
connection);
|
||||||
}
|
}
|
||||||
|
for (var poolName : missingPools) {
|
||||||
|
fire(new AddConletRequest(event.event().event().renderSupport(),
|
||||||
|
VmAccess.class.getName(), RenderMode.asSet(RenderMode.Preview))
|
||||||
|
.addProperty(POOL_NAME_PROPERTY, poolName),
|
||||||
|
connection);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -334,9 +359,14 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ResourceModel> {
|
||||||
protected Optional<ResourceModel> createNewState(AddConletRequest event,
|
protected Optional<ResourceModel> createNewState(AddConletRequest event,
|
||||||
ConsoleConnection connection, String conletId) throws Exception {
|
ConsoleConnection connection, String conletId) throws Exception {
|
||||||
var model = new ResourceModel(conletId);
|
var model = new ResourceModel(conletId);
|
||||||
|
var poolName = (String) event.properties().get(POOL_NAME_PROPERTY);
|
||||||
|
if (poolName != null) {
|
||||||
|
model.setMode(ResourceModel.Mode.POOL);
|
||||||
|
model.setName(poolName);
|
||||||
|
} else {
|
||||||
model.setMode(ResourceModel.Mode.VM);
|
model.setMode(ResourceModel.Mode.VM);
|
||||||
model
|
model.setName((String) event.properties().get(VM_NAME_PROPERTY));
|
||||||
.setName((String) event.properties().get(VM_NAME_PROPERTY));
|
}
|
||||||
String jsonState = objectMapper.writeValueAsString(model);
|
String jsonState = objectMapper.writeValueAsString(model);
|
||||||
connection.respond(new KeyValueStoreUpdate().update(
|
connection.respond(new KeyValueStoreUpdate().update(
|
||||||
storagePath(connection.session(), model.getConletId()), jsonState));
|
storagePath(connection.session(), model.getConletId()), jsonState));
|
||||||
|
|
@ -428,18 +458,13 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ResourceModel> {
|
||||||
channel.setAssociated(PENDING, null);
|
channel.setAssociated(PENDING, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
var session = channel.session();
|
VmDefinition vmDef = null;
|
||||||
if (model.mode() == ResourceModel.Mode.VM && model.name() != null) {
|
if (model.mode() == ResourceModel.Mode.VM && model.name() != null) {
|
||||||
// Remove conlet if VM definition has been removed
|
// Remove conlet if VM definition has been removed
|
||||||
// or user has not at least one permission
|
// or user has not at least one permission
|
||||||
Optional<VmData> vmData = appPipeline.fire(new GetVms()
|
vmDef = getVmData(model, channel).map(VmData::definition)
|
||||||
.withName(model.name()).accessibleFor(
|
.orElse(null);
|
||||||
WebConsoleUtils.userFromSession(session)
|
if (vmDef == null) {
|
||||||
.map(ConsoleUser::getName).orElse(null),
|
|
||||||
WebConsoleUtils.rolesFromSession(session).stream()
|
|
||||||
.map(ConsoleRole::getName).toList()))
|
|
||||||
.get().stream().findFirst();
|
|
||||||
if (vmData.isEmpty()) {
|
|
||||||
channel.respond(
|
channel.respond(
|
||||||
new DeleteConlet(conletId, Collections.emptySet()));
|
new DeleteConlet(conletId, Collections.emptySet()));
|
||||||
return Collections.emptySet();
|
return Collections.emptySet();
|
||||||
|
|
@ -453,11 +478,13 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ResourceModel> {
|
||||||
.fire(new GetPools().withName(model.name())).get()
|
.fire(new GetPools().withName(model.name())).get()
|
||||||
.stream().findFirst().orElse(null);
|
.stream().findFirst().orElse(null);
|
||||||
if (pool == null
|
if (pool == null
|
||||||
|| poolPermissions(pool, channel.session()).isEmpty()) {
|
|| permissions(pool, channel.session()).isEmpty()) {
|
||||||
channel.respond(
|
channel.respond(
|
||||||
new DeleteConlet(conletId, Collections.emptySet()));
|
new DeleteConlet(conletId, Collections.emptySet()));
|
||||||
return Collections.emptySet();
|
return Collections.emptySet();
|
||||||
}
|
}
|
||||||
|
vmDef = getVmData(model, channel).map(VmData::definition)
|
||||||
|
.orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render
|
// Render
|
||||||
|
|
@ -474,13 +501,32 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ResourceModel> {
|
||||||
if (!Strings.isNullOrEmpty(model.name())) {
|
if (!Strings.isNullOrEmpty(model.name())) {
|
||||||
Optional.ofNullable(channel.session().get(RENDERED))
|
Optional.ofNullable(channel.session().get(RENDERED))
|
||||||
.ifPresent(s -> ((Set<ResourceModel>) s).add(model));
|
.ifPresent(s -> ((Set<ResourceModel>) s).add(model));
|
||||||
updateConfig(channel, model);
|
updatePreview(channel, model, vmDef);
|
||||||
}
|
}
|
||||||
return EnumSet.of(RenderMode.Preview);
|
return EnumSet.of(RenderMode.Preview);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<Permission> vmPermissions(VmDefinition vmDef,
|
private Optional<VmData> getVmData(ResourceModel model,
|
||||||
Session session) {
|
ConsoleConnection channel) throws InterruptedException {
|
||||||
|
if (model.mode() == ResourceModel.Mode.VM) {
|
||||||
|
// Get the VM data by name.
|
||||||
|
var session = channel.session();
|
||||||
|
return appPipeline.fire(new GetVms().withName(model.name())
|
||||||
|
.accessibleFor(WebConsoleUtils.userFromSession(session)
|
||||||
|
.map(ConsoleUser::getName).orElse(null),
|
||||||
|
WebConsoleUtils.rolesFromSession(session).stream()
|
||||||
|
.map(ConsoleRole::getName).toList()))
|
||||||
|
.get().stream().findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for an (already) assigned VM
|
||||||
|
var user = WebConsoleUtils.userFromSession(channel.session())
|
||||||
|
.map(ConsoleUser::getName).orElse(null);
|
||||||
|
return appPipeline.fire(new GetVms().assignedFrom(model.name())
|
||||||
|
.assignedTo(user)).get().stream().findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<Permission> permissions(VmDefinition vmDef, Session session) {
|
||||||
var user = WebConsoleUtils.userFromSession(session)
|
var user = WebConsoleUtils.userFromSession(session)
|
||||||
.map(ConsoleUser::getName).orElse(null);
|
.map(ConsoleUser::getName).orElse(null);
|
||||||
var roles = WebConsoleUtils.rolesFromSession(session)
|
var roles = WebConsoleUtils.rolesFromSession(session)
|
||||||
|
|
@ -488,8 +534,7 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ResourceModel> {
|
||||||
return vmDef.permissionsFor(user, roles);
|
return vmDef.permissionsFor(user, roles);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<Permission> poolPermissions(VmPool pool,
|
private Set<Permission> permissions(VmPool pool, Session session) {
|
||||||
Session session) {
|
|
||||||
var user = WebConsoleUtils.userFromSession(session)
|
var user = WebConsoleUtils.userFromSession(session)
|
||||||
.map(ConsoleUser::getName).orElse(null);
|
.map(ConsoleUser::getName).orElse(null);
|
||||||
var roles = WebConsoleUtils.rolesFromSession(session)
|
var roles = WebConsoleUtils.rolesFromSession(session)
|
||||||
|
|
@ -497,36 +542,60 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ResourceModel> {
|
||||||
return pool.permissionsFor(user, roles);
|
return pool.permissionsFor(user, roles);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateConfig(ConsoleConnection channel, ResourceModel model)
|
private Set<Permission> permissions(ResourceModel model, Session session,
|
||||||
throws InterruptedException {
|
VmPool pool, VmDefinition vmDef) throws InterruptedException {
|
||||||
channel.respond(new NotifyConletView(type(),
|
var user = WebConsoleUtils.userFromSession(session)
|
||||||
model.getConletId(), "updateConfig", model.mode(), model.name()));
|
.map(ConsoleUser::getName).orElse(null);
|
||||||
updateVmDef(channel, model);
|
var roles = WebConsoleUtils.rolesFromSession(session)
|
||||||
|
.stream().map(ConsoleRole::getName).toList();
|
||||||
|
Set<Permission> result = new HashSet<>();
|
||||||
|
if (model.mode() == ResourceModel.Mode.POOL) {
|
||||||
|
if (pool == null) {
|
||||||
|
pool = appPipeline.fire(new GetPools()
|
||||||
|
.withName(model.name())).get().stream().findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
if (pool != null) {
|
||||||
|
result.addAll(pool.permissionsFor(user, roles));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (vmDef != null) {
|
||||||
|
result.addAll(vmDef.permissionsFor(user, roles));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateVmDef(ConsoleConnection channel, ResourceModel model)
|
private void updatePreview(ConsoleConnection channel, ResourceModel model,
|
||||||
throws InterruptedException {
|
VmDefinition vmDef) throws InterruptedException {
|
||||||
if (Strings.isNullOrEmpty(model.name())) {
|
channel.respond(new NotifyConletView(type(),
|
||||||
return;
|
model.getConletId(), "updateConfig", model.mode(), model.name()));
|
||||||
|
updateVmDef(channel, model, vmDef);
|
||||||
}
|
}
|
||||||
appPipeline.fire(new GetVms().withName(model.name())).get().stream()
|
|
||||||
.findFirst().map(d -> d.definition()).ifPresent(vmDef -> {
|
private void updateVmDef(ConsoleConnection channel, ResourceModel model,
|
||||||
|
VmDefinition vmDef) throws InterruptedException {
|
||||||
|
Map<String, Object> data = null;
|
||||||
|
if (vmDef != null) {
|
||||||
|
model.setAssignedVm(vmDef.name());
|
||||||
try {
|
try {
|
||||||
var data = Map.of("metadata",
|
data = Map.of("metadata",
|
||||||
Map.of("namespace", vmDef.namespace(),
|
Map.of("namespace", vmDef.namespace(),
|
||||||
"name", vmDef.name()),
|
"name", vmDef.name()),
|
||||||
"spec", vmDef.spec(),
|
"spec", vmDef.spec(),
|
||||||
"status", vmDef.getStatus(),
|
"status", vmDef.getStatus(),
|
||||||
"userPermissions",
|
"userPermissions",
|
||||||
vmPermissions(vmDef, channel.session()).stream()
|
permissions(model, channel.session(), null, vmDef).stream()
|
||||||
.map(VmDefinition.Permission::toString).toList());
|
.map(VmDefinition.Permission::toString).toList());
|
||||||
channel.respond(new NotifyConletView(type(),
|
|
||||||
model.getConletId(), "updateVmDefinition", data));
|
|
||||||
} catch (JsonSyntaxException e) {
|
} catch (JsonSyntaxException e) {
|
||||||
logger.log(Level.SEVERE, e,
|
logger.log(Level.SEVERE, e,
|
||||||
() -> "Failed to serialize VM definition");
|
() -> "Failed to serialize VM definition");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
});
|
} else {
|
||||||
|
model.setAssignedVm(null);
|
||||||
|
}
|
||||||
|
channel.respond(new NotifyConletView(type(),
|
||||||
|
model.getConletId(), "updateVmDefinition", data));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -541,7 +610,7 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ResourceModel> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Track the VM definitions.
|
* Track the VM definitions and update conlets.
|
||||||
*
|
*
|
||||||
* @param event the event
|
* @param event the event
|
||||||
* @param channel the channel
|
* @param channel the channel
|
||||||
|
|
@ -562,17 +631,43 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ResourceModel> {
|
||||||
for (var conletId : entry.getValue()) {
|
for (var conletId : entry.getValue()) {
|
||||||
var model = stateFromSession(connection.session(), conletId);
|
var model = stateFromSession(connection.session(), conletId);
|
||||||
if (model.isEmpty()
|
if (model.isEmpty()
|
||||||
|| model.get().mode() != ResourceModel.Mode.VM
|
|| Strings.isNullOrEmpty(model.get().name())) {
|
||||||
|| !Objects.areEqual(model.get().name(), vmDef.name())) {
|
continue;
|
||||||
|
}
|
||||||
|
if (model.get().mode() == ResourceModel.Mode.VM) {
|
||||||
|
// Check if this VM is used by conlet
|
||||||
|
if (!Objects.areEqual(model.get().name(), vmDef.name())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (event.type() == K8sObserver.ResponseType.DELETED
|
if (event.type() == K8sObserver.ResponseType.DELETED
|
||||||
|| vmPermissions(vmDef, connection.session()).isEmpty()) {
|
|| permissions(vmDef, connection.session()).isEmpty()) {
|
||||||
connection.respond(
|
connection.respond(
|
||||||
new DeleteConlet(conletId, Collections.emptySet()));
|
new DeleteConlet(conletId, Collections.emptySet()));
|
||||||
} else {
|
continue;
|
||||||
updateVmDef(connection, model.get());
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Check if VM is used by pool conlet or to be assigned to
|
||||||
|
// it
|
||||||
|
var user
|
||||||
|
= WebConsoleUtils.userFromSession(connection.session())
|
||||||
|
.map(ConsoleUser::getName).orElse(null);
|
||||||
|
var toBeUsedByConlet = vmDef.assignedFrom()
|
||||||
|
.map(p -> p.equals(model.get().name())).orElse(false)
|
||||||
|
&& vmDef.assignedTo().map(u -> u.equals(user))
|
||||||
|
.orElse(false);
|
||||||
|
if (!Objects.areEqual(model.get().assignedVm(),
|
||||||
|
vmDef.name()) && !toBeUsedByConlet) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now unassigned if VM is deleted or no longer to be used
|
||||||
|
if (event.type() == K8sObserver.ResponseType.DELETED
|
||||||
|
|| !toBeUsedByConlet) {
|
||||||
|
updateVmDef(connection, model.get(), null);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateVmDef(connection, model.get(), vmDef);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -598,7 +693,7 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ResourceModel> {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (event.deleted()
|
if (event.deleted()
|
||||||
|| poolPermissions(event.vmPool(), connection.session())
|
|| permissions(event.vmPool(), connection.session())
|
||||||
.isEmpty()) {
|
.isEmpty()) {
|
||||||
connection.respond(
|
connection.respond(
|
||||||
new DeleteConlet(conletId, Collections.emptySet()));
|
new DeleteConlet(conletId, Collections.emptySet()));
|
||||||
|
|
@ -612,24 +707,36 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ResourceModel> {
|
||||||
"PMD.ConfusingArgumentToVarargsMethod", "PMD.NcssCount",
|
"PMD.ConfusingArgumentToVarargsMethod", "PMD.NcssCount",
|
||||||
"PMD.AvoidLiteralsInIfCondition" })
|
"PMD.AvoidLiteralsInIfCondition" })
|
||||||
protected void doUpdateConletState(NotifyConletModel event,
|
protected void doUpdateConletState(NotifyConletModel event,
|
||||||
ConsoleConnection channel, ResourceModel model)
|
ConsoleConnection channel, ResourceModel model) throws Exception {
|
||||||
throws Exception {
|
|
||||||
event.stop();
|
event.stop();
|
||||||
if ("selectedResource".equals(event.method())) {
|
if ("selectedResource".equals(event.method())) {
|
||||||
selectResource(event, channel, model);
|
selectResource(event, channel, model);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle command for selected VM
|
Optional<VmData> vmData = getVmData(model, channel);
|
||||||
var vmData = appPipeline.fire(new GetVms().withName(model.name())).get()
|
|
||||||
.stream().findFirst();
|
|
||||||
if (vmData.isEmpty()) {
|
if (vmData.isEmpty()) {
|
||||||
|
if (model.mode() == ResourceModel.Mode.VM) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if ("start".equals(event.method())) {
|
||||||
|
// Assign a VM.
|
||||||
|
var user = WebConsoleUtils.userFromSession(channel.session())
|
||||||
|
.map(ConsoleUser::getName).orElse(null);
|
||||||
|
vmData = Optional.ofNullable(appPipeline
|
||||||
|
.fire(new AssignVm(model.name(), user)).get());
|
||||||
|
if (vmData.isEmpty()) {
|
||||||
|
// TODO message
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle command for selected VM
|
||||||
var vmChannel = vmData.get().channel();
|
var vmChannel = vmData.get().channel();
|
||||||
var vmDef = vmData.get().definition();
|
var vmDef = vmData.get().definition();
|
||||||
var vmName = vmDef.metadata().getName();
|
var vmName = vmDef.metadata().getName();
|
||||||
var perms = vmPermissions(vmDef, channel.session());
|
var perms = permissions(model, channel.session(), null, vmDef);
|
||||||
var resourceBundle = resourceBundle(channel.locale());
|
var resourceBundle = resourceBundle(channel.locale());
|
||||||
switch (event.method()) {
|
switch (event.method()) {
|
||||||
case "start":
|
case "start":
|
||||||
|
|
@ -658,7 +765,7 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ResourceModel> {
|
||||||
.map(ConsoleUser::getName).orElse("");
|
.map(ConsoleUser::getName).orElse("");
|
||||||
var pwQuery
|
var pwQuery
|
||||||
= Event.onCompletion(new GetDisplayPassword(vmDef, user),
|
= Event.onCompletion(new GetDisplayPassword(vmDef, user),
|
||||||
e -> openConsole(vmName, channel, model,
|
e -> openConsole(vmDef, channel, model,
|
||||||
e.password().orElse(null)));
|
e.password().orElse(null)));
|
||||||
fire(pwQuery, vmChannel);
|
fire(pwQuery, vmChannel);
|
||||||
}
|
}
|
||||||
|
|
@ -680,33 +787,29 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ResourceModel> {
|
||||||
String jsonState = objectMapper.writeValueAsString(model);
|
String jsonState = objectMapper.writeValueAsString(model);
|
||||||
channel.respond(new KeyValueStoreUpdate().update(storagePath(
|
channel.respond(new KeyValueStoreUpdate().update(storagePath(
|
||||||
channel.session(), model.getConletId()), jsonState));
|
channel.session(), model.getConletId()), jsonState));
|
||||||
updateConfig(channel, model);
|
updatePreview(channel, model,
|
||||||
|
getVmData(model, channel).map(VmData::definition).orElse(null));
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
logger.warning(() -> "Invalid resource type: " + e.getMessage());
|
logger.warning(() -> "Invalid resource type: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openConsole(String vmName, ConsoleConnection connection,
|
private void openConsole(VmDefinition vmDef, ConsoleConnection connection,
|
||||||
ResourceModel model, String password) {
|
ResourceModel model, String password) {
|
||||||
VmDefinition vmDef;
|
|
||||||
try {
|
|
||||||
vmDef = appPipeline.fire(new GetVms().withName(model.name())).get()
|
|
||||||
.stream().findFirst().map(VmData::definition).orElse(null);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (vmDef == null) {
|
if (vmDef == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var addr = displayIp(vmDef);
|
var addr = displayIp(vmDef);
|
||||||
if (addr.isEmpty()) {
|
if (addr.isEmpty()) {
|
||||||
logger.severe(() -> "Failed to find display IP for " + vmName);
|
logger
|
||||||
|
.severe(() -> "Failed to find display IP for " + vmDef.name());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var port = vmDef.<Number> fromVm("display", "spice", "port")
|
var port = vmDef.<Number> fromVm("display", "spice", "port")
|
||||||
.map(Number::longValue);
|
.map(Number::longValue);
|
||||||
if (port.isEmpty()) {
|
if (port.isEmpty()) {
|
||||||
logger.severe(() -> "No port defined for display of " + vmName);
|
logger
|
||||||
|
.severe(() -> "No port defined for display of " + vmDef.name());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
StringBuffer data = new StringBuffer(100)
|
StringBuffer data = new StringBuffer(100)
|
||||||
|
|
@ -799,6 +902,7 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ResourceModel> {
|
||||||
|
|
||||||
private Mode mode;
|
private Mode mode;
|
||||||
private String name;
|
private String name;
|
||||||
|
private String assignedVm;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiates a new resource model.
|
* Instantiates a new resource model.
|
||||||
|
|
@ -809,6 +913,25 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ResourceModel> {
|
||||||
super(conletId);
|
super(conletId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the mode.
|
||||||
|
*
|
||||||
|
* @return the resourceType
|
||||||
|
*/
|
||||||
|
@JsonGetter("mode")
|
||||||
|
public Mode mode() {
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the mode.
|
||||||
|
*
|
||||||
|
* @param mode the resource mode to set
|
||||||
|
*/
|
||||||
|
public void setMode(Mode mode) {
|
||||||
|
this.mode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the resource name.
|
* Gets the resource name.
|
||||||
*
|
*
|
||||||
|
|
@ -829,29 +952,29 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ResourceModel> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the mode.
|
* Gets the assigned vm.
|
||||||
*
|
*
|
||||||
* @return the resourceType
|
* @return the string
|
||||||
*/
|
*/
|
||||||
@JsonGetter("mode")
|
@JsonGetter("assignedVm")
|
||||||
public Mode mode() {
|
public String assignedVm() {
|
||||||
return mode;
|
return assignedVm;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the mode.
|
* Sets the assigned vm.
|
||||||
*
|
*
|
||||||
* @param mode the resource mode to set
|
* @param name the assigned vm
|
||||||
*/
|
*/
|
||||||
public void setMode(Mode mode) {
|
public void setAssignedVm(String name) {
|
||||||
this.mode = mode;
|
this.assignedVm = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
final int prime = 31;
|
final int prime = 31;
|
||||||
int result = super.hashCode();
|
int result = super.hashCode();
|
||||||
result = prime * result + java.util.Objects.hash(name, mode);
|
result = prime * result + java.util.Objects.hash(mode, name);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -867,8 +990,8 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ResourceModel> {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ResourceModel other = (ResourceModel) obj;
|
ResourceModel other = (ResourceModel) obj;
|
||||||
return java.util.Objects.equals(name, other.name)
|
return mode == other.mode
|
||||||
&& mode == other.mode;
|
&& java.util.Objects.equals(name, other.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -878,6 +1001,5 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ResourceModel> {
|
||||||
.append(", name=").append(name).append(']');
|
.append(", name=").append(name).append(']');
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ interface Api {
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
vmName: string;
|
vmName: string;
|
||||||
vmDefinition: any;
|
vmDefinition: any;
|
||||||
poolName: string;
|
poolName: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const localize = (key: string) => {
|
const localize = (key: string) => {
|
||||||
|
|
@ -64,12 +64,21 @@ window.orgJDrupesVmOperatorVmAccess.initPreview = (previewDom: HTMLElement,
|
||||||
const previewApi: Api = reactive({
|
const previewApi: Api = reactive({
|
||||||
vmName: "",
|
vmName: "",
|
||||||
vmDefinition: {},
|
vmDefinition: {},
|
||||||
poolName: ""
|
poolName: null
|
||||||
});
|
});
|
||||||
|
const poolName = computed(() => previewApi.poolName);
|
||||||
|
const vmName = computed(() => previewApi.vmDefinition.name);
|
||||||
const configured = computed(() => previewApi.vmDefinition.spec);
|
const configured = computed(() => previewApi.vmDefinition.spec);
|
||||||
const startable = computed(() => previewApi.vmDefinition.spec &&
|
const busy = computed(() => previewApi.vmDefinition.spec
|
||||||
previewApi.vmDefinition.spec.vm.state !== 'Running'
|
&& (previewApi.vmDefinition.spec.vm.state === 'Running'
|
||||||
&& !previewApi.vmDefinition.running);
|
&& !previewApi.vmDefinition.running
|
||||||
|
|| previewApi.vmDefinition.spec.vm.state === 'Stopped'
|
||||||
|
&& previewApi.vmDefinition.running));
|
||||||
|
const startable = computed(() => previewApi.vmDefinition.spec
|
||||||
|
&& previewApi.vmDefinition.spec.vm.state !== 'Running'
|
||||||
|
&& !previewApi.vmDefinition.running
|
||||||
|
&& previewApi.vmDefinition.userPermissions.includes('start')
|
||||||
|
|| previewApi.poolName !== null && !previewApi.vmDefinition.name);
|
||||||
const stoppable = computed(() => previewApi.vmDefinition.spec &&
|
const stoppable = computed(() => previewApi.vmDefinition.spec &&
|
||||||
previewApi.vmDefinition.spec.vm.state !== 'Stopped'
|
previewApi.vmDefinition.spec.vm.state !== 'Stopped'
|
||||||
&& previewApi.vmDefinition.running);
|
&& previewApi.vmDefinition.running);
|
||||||
|
|
@ -79,10 +88,8 @@ window.orgJDrupesVmOperatorVmAccess.initPreview = (previewDom: HTMLElement,
|
||||||
? previewApi.vmDefinition.userPermissions : []);
|
? previewApi.vmDefinition.userPermissions : []);
|
||||||
|
|
||||||
watch(previewApi, (api: Api) => {
|
watch(previewApi, (api: Api) => {
|
||||||
const name = api.vmName || api.poolName;
|
JGConsole.instance.updateConletTitle(conletId,
|
||||||
if (name !== "") {
|
api.poolName || api.vmDefinition.name || "");
|
||||||
JGConsole.instance.updateConletTitle(conletId, name);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
provideApi(previewDom, previewApi);
|
provideApi(previewDom, previewApi);
|
||||||
|
|
@ -91,16 +98,16 @@ window.orgJDrupesVmOperatorVmAccess.initPreview = (previewDom: HTMLElement,
|
||||||
JGConsole.notifyConletModel(conletId, action);
|
JGConsole.notifyConletModel(conletId, action);
|
||||||
};
|
};
|
||||||
|
|
||||||
return { localize, resourceBase, vmAction, configured,
|
return { localize, resourceBase, vmAction, poolName, vmName,
|
||||||
startable, stoppable, running, inUse, permissions };
|
configured, busy, startable, stoppable, running, inUse,
|
||||||
|
permissions };
|
||||||
},
|
},
|
||||||
template: `
|
template: `
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan="2" style="position: relative"><span
|
<td rowspan="2" style="position: relative"><span
|
||||||
style="position: absolute;"
|
style="position: absolute;" :class="{ busy: busy }"
|
||||||
:class="{ busy: configured && !startable && !stoppable }"
|
|
||||||
><img role=button :aria-disabled="!running
|
><img role=button :aria-disabled="!running
|
||||||
|| !permissions.includes('accessConsole')"
|
|| !permissions.includes('accessConsole')"
|
||||||
v-on:click="vmAction('openConsole')"
|
v-on:click="vmAction('openConsole')"
|
||||||
|
|
@ -110,9 +117,12 @@ window.orgJDrupesVmOperatorVmAccess.initPreview = (previewDom: HTMLElement,
|
||||||
:title="localize('Open console')"></span><span
|
:title="localize('Open console')"></span><span
|
||||||
style="visibility: hidden;"><img
|
style="visibility: hidden;"><img
|
||||||
:src="resourceBase + 'computer.svg'"></span></td>
|
:src="resourceBase + 'computer.svg'"></span></td>
|
||||||
|
<td v-if="!poolName" style="padding: 0;"></td>
|
||||||
|
<td v-else>{{ vmName }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
<td class="jdrupes-vmoperator-vmaccess-preview-action-list">
|
<td class="jdrupes-vmoperator-vmaccess-preview-action-list">
|
||||||
<span role="button"
|
<span role="button" :aria-disabled="!startable"
|
||||||
:aria-disabled="!startable || !permissions.includes('start')"
|
|
||||||
tabindex="0" class="fa fa-play" :title="localize('Start VM')"
|
tabindex="0" class="fa fa-play" :title="localize('Start VM')"
|
||||||
v-on:click="vmAction('start')"></span>
|
v-on:click="vmAction('start')"></span>
|
||||||
<span role="button"
|
<span role="button"
|
||||||
|
|
@ -130,9 +140,6 @@ window.orgJDrupesVmOperatorVmAccess.initPreview = (previewDom: HTMLElement,
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>`
|
</table>`
|
||||||
});
|
});
|
||||||
|
|
@ -160,13 +167,14 @@ JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmaccess.VmAccess",
|
||||||
});
|
});
|
||||||
|
|
||||||
JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmaccess.VmAccess",
|
JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmaccess.VmAccess",
|
||||||
"updateVmDefinition", function(conletId: string, vmDefinition: any) {
|
"updateVmDefinition", function(conletId: string, vmDefinition: any | null) {
|
||||||
const conlet = JGConsole.findConletPreview(conletId);
|
const conlet = JGConsole.findConletPreview(conletId);
|
||||||
if (!conlet) {
|
if (!conlet) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const api = getApi<Api>(conlet.element().querySelector(
|
const api = getApi<Api>(conlet.element().querySelector(
|
||||||
":scope .jdrupes-vmoperator-vmaccess-preview"))!;
|
":scope .jdrupes-vmoperator-vmaccess-preview"))!;
|
||||||
|
if (vmDefinition) {
|
||||||
// Add some short-cuts for rendering
|
// Add some short-cuts for rendering
|
||||||
vmDefinition.name = vmDefinition.metadata.name;
|
vmDefinition.name = vmDefinition.metadata.name;
|
||||||
vmDefinition.currentCpus = vmDefinition.status.cpus;
|
vmDefinition.currentCpus = vmDefinition.status.cpus;
|
||||||
|
|
@ -180,6 +188,9 @@ JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmaccess.VmAccess",
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
vmDefinition = {};
|
||||||
|
}
|
||||||
api.vmDefinition = vmDefinition;
|
api.vmDefinition = vmDefinition;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,12 @@
|
||||||
|
|
||||||
.jdrupes-vmoperator-vmaccess.jdrupes-vmoperator-vmaccess-preview {
|
.jdrupes-vmoperator-vmaccess.jdrupes-vmoperator-vmaccess-preview {
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
|
display: block;
|
||||||
height: 3em;
|
height: 3em;
|
||||||
padding: 0.25rem;
|
padding: 0.25rem;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue