Move automatic login request to CRD.
Also reorganizes constants.
This commit is contained in:
parent
3152ff842b
commit
5366e24092
22 changed files with 259 additions and 206 deletions
|
|
@ -1430,6 +1430,12 @@ spec:
|
|||
outputs:
|
||||
type: integer
|
||||
default: 1
|
||||
loggedInUser:
|
||||
description: >-
|
||||
The name of a user that should be automatically
|
||||
logged in on the display. Note that this requires
|
||||
support from an agent in the guest OS.
|
||||
type: string
|
||||
spice:
|
||||
type: object
|
||||
properties:
|
||||
|
|
@ -1485,6 +1491,11 @@ spec:
|
|||
connection.
|
||||
type: string
|
||||
default: ""
|
||||
loggedInUser:
|
||||
description: >-
|
||||
The name of a user that is currently logged in by the
|
||||
VM operator agent.
|
||||
type: string
|
||||
displayPasswordSerial:
|
||||
description: >-
|
||||
Counts changes of the display password. Set to -1
|
||||
|
|
|
|||
|
|
@ -126,8 +126,18 @@ attemptLogout() {
|
|||
|
||||
# Log out any user currently using tty1. This is invoked when executing
|
||||
# the logout command and therefore sends back a 2xx return code.
|
||||
# Also try to restart gdm, if it is not running.
|
||||
doLogout() {
|
||||
attemptLogout
|
||||
systemctl status gdm >/dev/null 2>&1
|
||||
if [ $? != 0 ]; then
|
||||
systemctl restart gdm 2>$temperr
|
||||
if [ $? -eq 0 ]; then
|
||||
echo >&${con} "102 gdm restarted"
|
||||
else
|
||||
echo >&${con} "102 Restarting gdm failed: $(tr '\n' ' ' <${temperr})"
|
||||
fi
|
||||
fi
|
||||
echo >&${con} "202 User logged out"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,31 +27,47 @@ 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";
|
||||
/**
|
||||
* Constants related to the CRD.
|
||||
*/
|
||||
@SuppressWarnings("PMD.ShortClassName")
|
||||
public static class Crd {
|
||||
|
||||
/** The Constant VM_OP_GROUP. */
|
||||
public static final String VM_OP_GROUP = "vmoperator.jdrupes.org";
|
||||
/** The Constant NAME. */
|
||||
public static final String NAME = "vm-operator";
|
||||
|
||||
/** The Constant VM_OP_KIND_VM. */
|
||||
public static final String VM_OP_KIND_VM = "VirtualMachine";
|
||||
/** The Constant GROUP. */
|
||||
public static final String GROUP = "vmoperator.jdrupes.org";
|
||||
|
||||
/** The Constant VM_OP_KIND_VM_POOL. */
|
||||
public static final String VM_OP_KIND_VM_POOL = "VmPool";
|
||||
/** The Constant KIND_VM. */
|
||||
public static final String KIND_VM = "VirtualMachine";
|
||||
|
||||
/** The Constant COMP_DISPLAY_SECRETS. */
|
||||
public static final String COMP_DISPLAY_SECRET = "display-secret";
|
||||
/** The Constant KIND_VM_POOL. */
|
||||
public static final String KIND_VM_POOL = "VmPool";
|
||||
}
|
||||
|
||||
/** The Constant DATA_DISPLAY_PASSWORD. */
|
||||
public static final String DATA_DISPLAY_PASSWORD = "display-password";
|
||||
/**
|
||||
* Constants for the display secret.
|
||||
*/
|
||||
public static class DisplaySecret {
|
||||
|
||||
/** The Constant DATA_PASSWORD_EXPIRY. */
|
||||
public static final String DATA_PASSWORD_EXPIRY = "password-expiry";
|
||||
/** The Constant NAME. */
|
||||
public static final String NAME = "display-secret";
|
||||
|
||||
/** The Constant DATA_DISPLAY_USER. */
|
||||
public static final String DATA_DISPLAY_USER = "display-user";
|
||||
/** The Constant DISPLAY_PASSWORD. */
|
||||
public static final String DISPLAY_PASSWORD = "display-password";
|
||||
|
||||
/** The Constant DATA_DISPLAY_LOGIN. */
|
||||
public static final String DATA_DISPLAY_LOGIN = "login-user";
|
||||
/** The Constant PASSWORD_EXPIRY. */
|
||||
public static final String PASSWORD_EXPIRY = "password-expiry";
|
||||
}
|
||||
|
||||
/**
|
||||
* Constants for status fields.
|
||||
*/
|
||||
public static class Status {
|
||||
|
||||
/** The Constant LOGGED_IN_USER. */
|
||||
public static final String LOGGED_IN_USER = "loggedInUser";
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -193,7 +193,7 @@ public class K8sGenericStub<O extends KubernetesObject,
|
|||
}
|
||||
|
||||
/**
|
||||
* Updates the object's status.
|
||||
* Updates the object's status. This method will not retry.
|
||||
*
|
||||
* @param object the current state of the object (passed to `status`)
|
||||
* @param status function that returns the new status
|
||||
|
|
@ -231,7 +231,7 @@ public class K8sGenericStub<O extends KubernetesObject,
|
|||
}
|
||||
|
||||
/**
|
||||
* Updates the status.
|
||||
* Updates the status. In case of conflict, retries up to 16 times.
|
||||
*
|
||||
* @param status the status
|
||||
* @return the kubernetes api response
|
||||
|
|
|
|||
|
|
@ -201,6 +201,9 @@ data:
|
|||
<#if spec.vm.display.outputs?? >
|
||||
outputs: ${ spec.vm.display.outputs?c }
|
||||
</#if>
|
||||
<#if spec.vm.display.loggedInUser?? >
|
||||
loggedInUser: "${ spec.vm.display.loggedInUser }"
|
||||
</#if>
|
||||
<#if spec.vm.display.spice??>
|
||||
spice:
|
||||
port: ${ spec.vm.display.spice.port?c }
|
||||
|
|
|
|||
|
|
@ -33,9 +33,9 @@ import java.io.IOException;
|
|||
import java.io.StringWriter;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Logger;
|
||||
import org.jdrupes.vmoperator.common.Constants.Crd;
|
||||
import org.jdrupes.vmoperator.common.K8s;
|
||||
import static org.jdrupes.vmoperator.manager.Constants.APP_NAME;
|
||||
import static org.jdrupes.vmoperator.manager.Constants.VM_OP_NAME;
|
||||
import org.jdrupes.vmoperator.manager.events.VmChannel;
|
||||
import org.jdrupes.vmoperator.util.DataPath;
|
||||
import org.jdrupes.vmoperator.util.GsonPtr;
|
||||
|
|
@ -121,7 +121,7 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
|
|||
DynamicKubernetesObject newCm) {
|
||||
ListOptions listOpts = new ListOptions();
|
||||
listOpts.setLabelSelector(
|
||||
"app.kubernetes.io/managed-by=" + VM_OP_NAME + ","
|
||||
"app.kubernetes.io/managed-by=" + Crd.NAME + ","
|
||||
+ "app.kubernetes.io/name=" + APP_NAME + ","
|
||||
+ "app.kubernetes.io/instance=" + newCm.getMetadata()
|
||||
.getLabels().get("app.kubernetes.io/instance"));
|
||||
|
|
|
|||
|
|
@ -29,8 +29,7 @@ import java.nio.file.Files;
|
|||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
import java.util.logging.Level;
|
||||
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.Constants.Crd;
|
||||
import org.jdrupes.vmoperator.common.K8sClient;
|
||||
import org.jdrupes.vmoperator.common.K8sDynamicStub;
|
||||
import org.jdrupes.vmoperator.common.VmDefinitionStub;
|
||||
|
|
@ -194,7 +193,7 @@ public class Controller extends Component {
|
|||
private void patchVmDef(K8sClient client, String name, String path,
|
||||
Object value) throws ApiException, IOException {
|
||||
var vmStub = K8sDynamicStub.get(client,
|
||||
new GroupVersionKind(VM_OP_GROUP, "", VM_OP_KIND_VM), namespace,
|
||||
new GroupVersionKind(Crd.GROUP, "", Crd.KIND_VM), namespace,
|
||||
name);
|
||||
|
||||
// Patch running
|
||||
|
|
@ -227,7 +226,7 @@ public class Controller extends Component {
|
|||
try {
|
||||
var vmDef = channel.vmDefinition();
|
||||
var vmStub = VmDefinitionStub.get(channel.client(),
|
||||
new GroupVersionKind(VM_OP_GROUP, "", VM_OP_KIND_VM),
|
||||
new GroupVersionKind(Crd.GROUP, "", Crd.KIND_VM),
|
||||
vmDef.namespace(), vmDef.name());
|
||||
if (vmStub.updateStatus(vmDef, from -> {
|
||||
JsonObject status = from.statusJson();
|
||||
|
|
|
|||
|
|
@ -28,11 +28,11 @@ import io.kubernetes.client.util.generic.options.PatchOptions;
|
|||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
import static org.jdrupes.vmoperator.common.Constants.APP_NAME;
|
||||
import static org.jdrupes.vmoperator.common.Constants.VM_OP_NAME;
|
||||
import org.jdrupes.vmoperator.common.Constants.Crd;
|
||||
import org.jdrupes.vmoperator.common.Constants.DisplaySecret;
|
||||
import org.jdrupes.vmoperator.common.K8sClient;
|
||||
import org.jdrupes.vmoperator.common.K8sV1PodStub;
|
||||
import org.jdrupes.vmoperator.common.K8sV1SecretStub;
|
||||
import static org.jdrupes.vmoperator.manager.Constants.COMP_DISPLAY_SECRET;
|
||||
import org.jdrupes.vmoperator.manager.events.ChannelDictionary;
|
||||
import org.jdrupes.vmoperator.manager.events.VmChannel;
|
||||
import org.jgrapes.core.Channel;
|
||||
|
|
@ -61,7 +61,7 @@ public class DisplaySecretMonitor
|
|||
context(K8sV1SecretStub.CONTEXT);
|
||||
ListOptions options = new ListOptions();
|
||||
options.setLabelSelector("app.kubernetes.io/name=" + APP_NAME + ","
|
||||
+ "app.kubernetes.io/component=" + COMP_DISPLAY_SECRET);
|
||||
+ "app.kubernetes.io/component=" + DisplaySecret.NAME);
|
||||
options(options);
|
||||
}
|
||||
|
||||
|
|
@ -95,7 +95,7 @@ public class DisplaySecretMonitor
|
|||
// Force update for pod
|
||||
ListOptions listOpts = new ListOptions();
|
||||
listOpts.setLabelSelector(
|
||||
"app.kubernetes.io/managed-by=" + VM_OP_NAME + ","
|
||||
"app.kubernetes.io/managed-by=" + Crd.NAME + ","
|
||||
+ "app.kubernetes.io/name=" + APP_NAME + ","
|
||||
+ "app.kubernetes.io/instance=" + change.object.getMetadata()
|
||||
.getLabels().get("app.kubernetes.io/instance"));
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ import io.kubernetes.client.openapi.models.V1ObjectMeta;
|
|||
import io.kubernetes.client.openapi.models.V1Secret;
|
||||
import io.kubernetes.client.util.generic.options.ListOptions;
|
||||
import java.io.IOException;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.time.Instant;
|
||||
|
|
@ -34,20 +33,16 @@ import java.util.Collections;
|
|||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Scanner;
|
||||
import java.util.logging.Logger;
|
||||
import static org.jdrupes.vmoperator.common.Constants.APP_NAME;
|
||||
import static org.jdrupes.vmoperator.common.Constants.COMP_DISPLAY_SECRET;
|
||||
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.Constants.Crd;
|
||||
import org.jdrupes.vmoperator.common.Constants.DisplaySecret;
|
||||
import org.jdrupes.vmoperator.common.Constants.Status;
|
||||
import org.jdrupes.vmoperator.common.K8sV1SecretStub;
|
||||
import org.jdrupes.vmoperator.common.VmDefinition;
|
||||
import org.jdrupes.vmoperator.common.VmDefinitionStub;
|
||||
import static org.jdrupes.vmoperator.manager.Constants.DATA_DISPLAY_LOGIN;
|
||||
import static org.jdrupes.vmoperator.manager.Constants.DATA_DISPLAY_PASSWORD;
|
||||
import static org.jdrupes.vmoperator.manager.Constants.DATA_DISPLAY_USER;
|
||||
import static org.jdrupes.vmoperator.manager.Constants.DATA_PASSWORD_EXPIRY;
|
||||
import org.jdrupes.vmoperator.manager.events.PrepareConsole;
|
||||
import org.jdrupes.vmoperator.manager.events.VmChannel;
|
||||
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
|
||||
|
|
@ -146,7 +141,7 @@ public class DisplaySecretReconciler extends Component {
|
|||
var vmDef = event.vmDefinition();
|
||||
ListOptions options = new ListOptions();
|
||||
options.setLabelSelector("app.kubernetes.io/name=" + APP_NAME + ","
|
||||
+ "app.kubernetes.io/component=" + COMP_DISPLAY_SECRET + ","
|
||||
+ "app.kubernetes.io/component=" + DisplaySecret.NAME + ","
|
||||
+ "app.kubernetes.io/instance=" + vmDef.name());
|
||||
var stubs = K8sV1SecretStub.list(channel.client(), vmDef.namespace(),
|
||||
options);
|
||||
|
|
@ -157,9 +152,9 @@ public class DisplaySecretReconciler extends Component {
|
|||
// Create secret
|
||||
var secret = new V1Secret();
|
||||
secret.setMetadata(new V1ObjectMeta().namespace(vmDef.namespace())
|
||||
.name(vmDef.name() + "-" + COMP_DISPLAY_SECRET)
|
||||
.name(vmDef.name() + "-" + DisplaySecret.NAME)
|
||||
.putLabelsItem("app.kubernetes.io/name", APP_NAME)
|
||||
.putLabelsItem("app.kubernetes.io/component", COMP_DISPLAY_SECRET)
|
||||
.putLabelsItem("app.kubernetes.io/component", DisplaySecret.NAME)
|
||||
.putLabelsItem("app.kubernetes.io/instance", vmDef.name()));
|
||||
secret.setType("Opaque");
|
||||
SecureRandom random = null;
|
||||
|
|
@ -172,8 +167,8 @@ public class DisplaySecretReconciler extends Component {
|
|||
byte[] bytes = new byte[16];
|
||||
random.nextBytes(bytes);
|
||||
var password = Base64.encode(bytes);
|
||||
secret.setStringData(Map.of(DATA_DISPLAY_PASSWORD, password,
|
||||
DATA_PASSWORD_EXPIRY, "now"));
|
||||
secret.setStringData(Map.of(DisplaySecret.DISPLAY_PASSWORD, password,
|
||||
DisplaySecret.PASSWORD_EXPIRY, "now"));
|
||||
K8sV1SecretStub.create(channel.client(), secret);
|
||||
}
|
||||
|
||||
|
|
@ -192,49 +187,31 @@ public class DisplaySecretReconciler extends Component {
|
|||
public void onPrepareConsole(PrepareConsole event, VmChannel channel)
|
||||
throws ApiException {
|
||||
// Update console user in status
|
||||
var vmStub = VmDefinitionStub.get(channel.client(),
|
||||
new GroupVersionKind(VM_OP_GROUP, "", VM_OP_KIND_VM),
|
||||
event.vmDefinition().namespace(), event.vmDefinition().name());
|
||||
var optVmDef = vmStub.updateStatus(from -> {
|
||||
JsonObject status = from.statusJson();
|
||||
status.addProperty("consoleUser", event.user());
|
||||
return status;
|
||||
});
|
||||
if (optVmDef.isEmpty()) {
|
||||
var vmDef = updateConsoleUser(event, channel);
|
||||
if (vmDef == null) {
|
||||
return;
|
||||
}
|
||||
var vmDef = optVmDef.get();
|
||||
|
||||
// Check if access is possible
|
||||
if (event.loginUser()
|
||||
? !vmDef.conditionStatus("Booted").orElse(false)
|
||||
? !vmDef.<String> fromStatus(Status.LOGGED_IN_USER)
|
||||
.map(u -> u.equals(event.user())).orElse(false)
|
||||
: !vmDef.conditionStatus("Running").orElse(false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Look for secret
|
||||
ListOptions options = new ListOptions();
|
||||
options.setLabelSelector("app.kubernetes.io/name=" + APP_NAME + ","
|
||||
+ "app.kubernetes.io/component=" + COMP_DISPLAY_SECRET + ","
|
||||
+ "app.kubernetes.io/instance=" + vmDef.name());
|
||||
var stubs = K8sV1SecretStub.list(channel.client(), vmDef.namespace(),
|
||||
options);
|
||||
if (stubs.isEmpty()) {
|
||||
// No secret means no password for this VM wanted
|
||||
event.setResult(null);
|
||||
// Get secret and update password in secret
|
||||
var stub = getSecretStub(event, channel, vmDef);
|
||||
if (stub == null) {
|
||||
return;
|
||||
}
|
||||
var stub = stubs.iterator().next();
|
||||
|
||||
// Get secret and update
|
||||
var secret = stub.model().get();
|
||||
var updPw = updatePassword(secret, event);
|
||||
var updUsr = updateUser(secret, event);
|
||||
if (!updPw && !updUsr) {
|
||||
if (!updatePassword(secret, event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Register wait for confirmation (by VM status change)
|
||||
// Register wait for confirmation (by VM status change,
|
||||
// after secret update)
|
||||
var pending = new PendingPrepare(event,
|
||||
event.vmDefinition().displayPasswordSerial().orElse(0L) + 1,
|
||||
new CompletionLock(event, 1500));
|
||||
|
|
@ -247,30 +224,45 @@ public class DisplaySecretReconciler extends Component {
|
|||
stub.update(secret).getObject();
|
||||
}
|
||||
|
||||
private boolean updateUser(V1Secret secret, PrepareConsole event) {
|
||||
var curUser = DataPath.<byte[]> get(secret, "data", DATA_DISPLAY_USER)
|
||||
.map(b -> new String(b, UTF_8)).orElse(null);
|
||||
var curLogin = DataPath.<byte[]> get(secret, "data", DATA_DISPLAY_LOGIN)
|
||||
.map(b -> new String(b, UTF_8)).map(Boolean::parseBoolean)
|
||||
.orElse(null);
|
||||
if (Objects.equals(curUser, event.user()) && Objects.equals(
|
||||
curLogin, event.loginUser())) {
|
||||
return false;
|
||||
private VmDefinition updateConsoleUser(PrepareConsole event,
|
||||
VmChannel channel) throws ApiException {
|
||||
var vmStub = VmDefinitionStub.get(channel.client(),
|
||||
new GroupVersionKind(Crd.GROUP, "", Crd.KIND_VM),
|
||||
event.vmDefinition().namespace(), event.vmDefinition().name());
|
||||
return vmStub.updateStatus(from -> {
|
||||
JsonObject status = from.statusJson();
|
||||
status.addProperty("consoleUser", event.user());
|
||||
return status;
|
||||
}).orElse(null);
|
||||
}
|
||||
|
||||
private K8sV1SecretStub getSecretStub(PrepareConsole event,
|
||||
VmChannel channel, VmDefinition vmDef) throws ApiException {
|
||||
// Look for 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 stubs = K8sV1SecretStub.list(channel.client(), vmDef.namespace(),
|
||||
options);
|
||||
if (stubs.isEmpty()) {
|
||||
// No secret means no password for this VM wanted
|
||||
event.setResult(null);
|
||||
return null;
|
||||
}
|
||||
secret.getData().put(DATA_DISPLAY_USER, event.user().getBytes(UTF_8));
|
||||
secret.getData().put(DATA_DISPLAY_LOGIN,
|
||||
Boolean.toString(event.loginUser()).getBytes(UTF_8));
|
||||
return true;
|
||||
return stubs.iterator().next();
|
||||
}
|
||||
|
||||
private boolean updatePassword(V1Secret secret, PrepareConsole event) {
|
||||
var expiry = Optional.ofNullable(secret.getData()
|
||||
.get(DATA_PASSWORD_EXPIRY)).map(b -> new String(b)).orElse(null);
|
||||
if (secret.getData().get(DATA_DISPLAY_PASSWORD) != null
|
||||
.get(DisplaySecret.PASSWORD_EXPIRY)).map(b -> new String(b))
|
||||
.orElse(null);
|
||||
if (secret.getData().get(DisplaySecret.DISPLAY_PASSWORD) != null
|
||||
&& stillValid(expiry)) {
|
||||
// Fixed secret, don't touch
|
||||
event.setResult(
|
||||
new String(secret.getData().get(DATA_DISPLAY_PASSWORD)));
|
||||
new String(
|
||||
secret.getData().get(DisplaySecret.DISPLAY_PASSWORD)));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -285,8 +277,8 @@ public class DisplaySecretReconciler extends Component {
|
|||
byte[] bytes = new byte[16];
|
||||
random.nextBytes(bytes);
|
||||
var password = Base64.encode(bytes);
|
||||
secret.setStringData(Map.of(DATA_DISPLAY_PASSWORD, password,
|
||||
DATA_PASSWORD_EXPIRY,
|
||||
secret.setStringData(Map.of(DisplaySecret.DISPLAY_PASSWORD, password,
|
||||
DisplaySecret.PASSWORD_EXPIRY,
|
||||
Long.toString(Instant.now().getEpochSecond() + passwordValidity)));
|
||||
event.setResult(password);
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ import org.apache.commons.cli.CommandLineParser;
|
|||
import org.apache.commons.cli.DefaultParser;
|
||||
import org.apache.commons.cli.Option;
|
||||
import org.apache.commons.cli.Options;
|
||||
import static org.jdrupes.vmoperator.manager.Constants.VM_OP_NAME;
|
||||
import org.jdrupes.vmoperator.common.Constants.Crd;
|
||||
import org.jdrupes.vmoperator.manager.events.Exit;
|
||||
import org.jdrupes.vmoperator.util.FsdUtils;
|
||||
import org.jgrapes.core.Channel;
|
||||
|
|
@ -108,7 +108,7 @@ public class Manager extends Component {
|
|||
|
||||
// Configuration store with file in /etc/opt (default)
|
||||
File cfgFile = new File(cmdLine.getOptionValue('c',
|
||||
"/etc/opt/" + VM_OP_NAME.replace("-", "") + "/config.yaml"));
|
||||
"/etc/opt/" + Crd.NAME.replace("-", "") + "/config.yaml"));
|
||||
logger.config(() -> "Using configuration from: " + cfgFile.getPath());
|
||||
// Don't rely on night config to produce a good exception
|
||||
// for this simple case
|
||||
|
|
@ -271,7 +271,7 @@ public class Manager extends Component {
|
|||
try {
|
||||
// Get logging properties from file and put them in effect
|
||||
InputStream props;
|
||||
var path = FsdUtils.findConfigFile(VM_OP_NAME.replace("-", ""),
|
||||
var path = FsdUtils.findConfigFile(Crd.NAME.replace("-", ""),
|
||||
"logging.properties");
|
||||
if (path.isPresent()) {
|
||||
props = Files.newInputStream(path.get());
|
||||
|
|
|
|||
|
|
@ -28,8 +28,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
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.Constants.Crd;
|
||||
import org.jdrupes.vmoperator.common.K8s;
|
||||
import org.jdrupes.vmoperator.common.K8sClient;
|
||||
import org.jdrupes.vmoperator.common.K8sDynamicModel;
|
||||
|
|
@ -38,7 +37,6 @@ import org.jdrupes.vmoperator.common.K8sDynamicStub;
|
|||
import org.jdrupes.vmoperator.common.K8sObserver.ResponseType;
|
||||
import org.jdrupes.vmoperator.common.VmDefinitionStub;
|
||||
import org.jdrupes.vmoperator.common.VmPool;
|
||||
import static org.jdrupes.vmoperator.manager.Constants.VM_OP_KIND_VM_POOL;
|
||||
import org.jdrupes.vmoperator.manager.events.GetPools;
|
||||
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
|
||||
import org.jdrupes.vmoperator.manager.events.VmPoolChanged;
|
||||
|
|
@ -88,7 +86,7 @@ public class PoolMonitor extends
|
|||
client(new K8sClient());
|
||||
|
||||
// Get all our API versions
|
||||
var ctx = K8s.context(client(), VM_OP_GROUP, "", VM_OP_KIND_VM_POOL);
|
||||
var ctx = K8s.context(client(), Crd.GROUP, "", Crd.KIND_VM_POOL);
|
||||
if (ctx.isEmpty()) {
|
||||
logger.severe(() -> "Cannot get CRD context.");
|
||||
return;
|
||||
|
|
@ -184,7 +182,7 @@ public class PoolMonitor extends
|
|||
return;
|
||||
}
|
||||
var vmStub = VmDefinitionStub.get(client(),
|
||||
new GroupVersionKind(VM_OP_GROUP, "", VM_OP_KIND_VM),
|
||||
new GroupVersionKind(Crd.GROUP, "", Crd.KIND_VM),
|
||||
vmDef.namespace(), vmDef.name());
|
||||
vmStub.updateStatus(from -> {
|
||||
// TODO
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ import java.util.Set;
|
|||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
import static org.jdrupes.vmoperator.common.Constants.APP_NAME;
|
||||
import static org.jdrupes.vmoperator.common.Constants.VM_OP_NAME;
|
||||
import org.jdrupes.vmoperator.common.Constants.Crd;
|
||||
import org.jdrupes.vmoperator.common.K8sV1PvcStub;
|
||||
import org.jdrupes.vmoperator.manager.events.VmChannel;
|
||||
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
|
||||
|
|
@ -83,7 +83,7 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
|
|||
// Existing disks
|
||||
ListOptions listOpts = new ListOptions();
|
||||
listOpts.setLabelSelector(
|
||||
"app.kubernetes.io/managed-by=" + VM_OP_NAME + ","
|
||||
"app.kubernetes.io/managed-by=" + Crd.NAME + ","
|
||||
+ "app.kubernetes.io/name=" + APP_NAME + ","
|
||||
+ "app.kubernetes.io/instance=" + vmDef.name());
|
||||
var knownDisks = K8sV1PvcStub.list(channel.client(),
|
||||
|
|
|
|||
|
|
@ -46,12 +46,12 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import static org.jdrupes.vmoperator.common.Constants.APP_NAME;
|
||||
import org.jdrupes.vmoperator.common.Constants.DisplaySecret;
|
||||
import org.jdrupes.vmoperator.common.Convertions;
|
||||
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 static org.jdrupes.vmoperator.manager.Constants.COMP_DISPLAY_SECRET;
|
||||
import org.jdrupes.vmoperator.manager.events.ResetVm;
|
||||
import org.jdrupes.vmoperator.manager.events.VmChannel;
|
||||
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
|
||||
|
|
@ -276,7 +276,7 @@ public class Reconciler extends Component {
|
|||
// Check if we have a display secret
|
||||
ListOptions options = new ListOptions();
|
||||
options.setLabelSelector("app.kubernetes.io/name=" + APP_NAME + ","
|
||||
+ "app.kubernetes.io/component=" + COMP_DISPLAY_SECRET + ","
|
||||
+ "app.kubernetes.io/component=" + DisplaySecret.NAME + ","
|
||||
+ "app.kubernetes.io/instance=" + vmDef.name());
|
||||
var dsStub = K8sV1SecretStub
|
||||
.list(client, vmDef.namespace(), options)
|
||||
|
|
|
|||
|
|
@ -31,8 +31,7 @@ import java.util.Set;
|
|||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
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.Constants.Crd;
|
||||
import org.jdrupes.vmoperator.common.K8s;
|
||||
import org.jdrupes.vmoperator.common.K8sClient;
|
||||
import org.jdrupes.vmoperator.common.K8sDynamicStub;
|
||||
|
|
@ -46,7 +45,6 @@ import org.jdrupes.vmoperator.common.VmDefinitions;
|
|||
import org.jdrupes.vmoperator.common.VmExtraData;
|
||||
import org.jdrupes.vmoperator.common.VmPool;
|
||||
import static org.jdrupes.vmoperator.manager.Constants.APP_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.GetPools;
|
||||
|
|
@ -87,7 +85,7 @@ public class VmMonitor extends
|
|||
client(new K8sClient());
|
||||
|
||||
// Get all our API versions
|
||||
var ctx = K8s.context(client(), VM_OP_GROUP, "", VM_OP_KIND_VM);
|
||||
var ctx = K8s.context(client(), Crd.GROUP, "", Crd.KIND_VM);
|
||||
if (ctx.isEmpty()) {
|
||||
logger.severe(() -> "Cannot get CRD context.");
|
||||
return;
|
||||
|
|
@ -105,7 +103,7 @@ public class VmMonitor extends
|
|||
.stream().map(stub -> stub.name()).collect(Collectors.toSet());
|
||||
ListOptions opts = new ListOptions();
|
||||
opts.setLabelSelector(
|
||||
"app.kubernetes.io/managed-by=" + VM_OP_NAME + ","
|
||||
"app.kubernetes.io/managed-by=" + Crd.NAME + ","
|
||||
+ "app.kubernetes.io/name=" + APP_NAME);
|
||||
for (var context : Set.of(K8sV1StatefulSetStub.CONTEXT,
|
||||
K8sV1ConfigMapStub.CONTEXT)) {
|
||||
|
|
|
|||
|
|
@ -13,10 +13,8 @@ import java.util.LinkedHashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import static org.jdrupes.vmoperator.common.Constants.APP_NAME;
|
||||
import static org.jdrupes.vmoperator.common.Constants.COMP_DISPLAY_SECRET;
|
||||
import static org.jdrupes.vmoperator.common.Constants.VM_OP_GROUP;
|
||||
import static org.jdrupes.vmoperator.common.Constants.VM_OP_KIND_VM;
|
||||
import static org.jdrupes.vmoperator.common.Constants.VM_OP_NAME;
|
||||
import org.jdrupes.vmoperator.common.Constants.Crd;
|
||||
import org.jdrupes.vmoperator.common.Constants.DisplaySecret;
|
||||
import org.jdrupes.vmoperator.common.K8s;
|
||||
import org.jdrupes.vmoperator.common.K8sClient;
|
||||
import org.jdrupes.vmoperator.common.K8sDynamicStub;
|
||||
|
|
@ -60,7 +58,7 @@ class BasicTests {
|
|||
waitForManager();
|
||||
|
||||
// Context for working with our CR
|
||||
var apiRes = K8s.context(client, VM_OP_GROUP, null, VM_OP_KIND_VM);
|
||||
var apiRes = K8s.context(client, Crd.GROUP, null, Crd.KIND_VM);
|
||||
assertTrue(apiRes.isPresent());
|
||||
vmsContext = apiRes.get();
|
||||
|
||||
|
|
@ -70,7 +68,7 @@ class BasicTests {
|
|||
ListOptions listOpts = new ListOptions();
|
||||
listOpts.setLabelSelector("app.kubernetes.io/name=" + APP_NAME + ","
|
||||
+ "app.kubernetes.io/instance=" + VM_NAME + ","
|
||||
+ "app.kubernetes.io/component=" + COMP_DISPLAY_SECRET);
|
||||
+ "app.kubernetes.io/component=" + DisplaySecret.NAME);
|
||||
var secrets = K8sV1SecretStub.list(client, "vmop-dev", listOpts);
|
||||
for (var secret : secrets) {
|
||||
secret.delete();
|
||||
|
|
@ -100,7 +98,7 @@ class BasicTests {
|
|||
private static void deletePvcs() throws ApiException {
|
||||
ListOptions listOpts = new ListOptions();
|
||||
listOpts.setLabelSelector(
|
||||
"app.kubernetes.io/managed-by=" + VM_OP_NAME + ","
|
||||
"app.kubernetes.io/managed-by=" + Crd.NAME + ","
|
||||
+ "app.kubernetes.io/name=" + APP_NAME + ","
|
||||
+ "app.kubernetes.io/instance=" + VM_NAME);
|
||||
var knownPvcs = K8sV1PvcStub.list(client, "vmop-dev", listOpts);
|
||||
|
|
@ -139,11 +137,11 @@ class BasicTests {
|
|||
List.of("labels", "app.kubernetes.io/name"), Constants.APP_NAME,
|
||||
List.of("labels", "app.kubernetes.io/instance"), VM_NAME,
|
||||
List.of("labels", "app.kubernetes.io/managed-by"),
|
||||
Constants.VM_OP_NAME,
|
||||
Crd.NAME,
|
||||
List.of("annotations", "vmoperator.jdrupes.org/version"), EXISTS,
|
||||
List.of("ownerReferences", 0, "apiVersion"),
|
||||
vmsContext.getGroup() + "/" + vmsContext.getVersions().get(0),
|
||||
List.of("ownerReferences", 0, "kind"), Constants.VM_OP_KIND_VM,
|
||||
List.of("ownerReferences", 0, "kind"), Crd.KIND_VM,
|
||||
List.of("ownerReferences", 0, "name"), VM_NAME,
|
||||
List.of("ownerReferences", 0, "uid"), EXISTS);
|
||||
checkProps(config.getMetadata(), toCheck);
|
||||
|
|
@ -189,7 +187,7 @@ class BasicTests {
|
|||
ListOptions listOpts = new ListOptions();
|
||||
listOpts.setLabelSelector("app.kubernetes.io/name=" + APP_NAME + ","
|
||||
+ "app.kubernetes.io/instance=" + VM_NAME + ","
|
||||
+ "app.kubernetes.io/component=" + COMP_DISPLAY_SECRET);
|
||||
+ "app.kubernetes.io/component=" + DisplaySecret.NAME);
|
||||
Collection<K8sV1SecretStub> secrets = null;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
secrets = K8sV1SecretStub.list(client, "vmop-dev", listOpts);
|
||||
|
|
@ -220,7 +218,7 @@ class BasicTests {
|
|||
List.of("labels", "app.kubernetes.io/name"), Constants.APP_NAME,
|
||||
List.of("labels", "app.kubernetes.io/instance"), VM_NAME,
|
||||
List.of("labels", "app.kubernetes.io/managed-by"),
|
||||
Constants.VM_OP_NAME));
|
||||
Crd.NAME));
|
||||
checkProps(pvc.getSpec(), Map.of(
|
||||
List.of("resources", "requests", "storage"),
|
||||
Quantity.fromString("1Mi")));
|
||||
|
|
@ -241,7 +239,7 @@ class BasicTests {
|
|||
List.of("labels", "app.kubernetes.io/name"), Constants.APP_NAME,
|
||||
List.of("labels", "app.kubernetes.io/instance"), VM_NAME,
|
||||
List.of("labels", "app.kubernetes.io/managed-by"),
|
||||
Constants.VM_OP_NAME,
|
||||
Crd.NAME,
|
||||
List.of("annotations", "use_as"), "system-disk"));
|
||||
checkProps(pvc.getSpec(), Map.of(
|
||||
List.of("resources", "requests", "storage"),
|
||||
|
|
@ -263,7 +261,7 @@ class BasicTests {
|
|||
List.of("labels", "app.kubernetes.io/name"), Constants.APP_NAME,
|
||||
List.of("labels", "app.kubernetes.io/instance"), VM_NAME,
|
||||
List.of("labels", "app.kubernetes.io/managed-by"),
|
||||
Constants.VM_OP_NAME));
|
||||
Crd.NAME));
|
||||
checkProps(pvc.getSpec(), Map.of(
|
||||
List.of("resources", "requests", "storage"),
|
||||
Quantity.fromString("1Gi")));
|
||||
|
|
@ -291,12 +289,12 @@ class BasicTests {
|
|||
List.of("labels", "app.kubernetes.io/instance"), VM_NAME,
|
||||
List.of("labels", "app.kubernetes.io/component"), APP_NAME,
|
||||
List.of("labels", "app.kubernetes.io/managed-by"),
|
||||
Constants.VM_OP_NAME,
|
||||
Crd.NAME,
|
||||
List.of("annotations", "vmrunner.jdrupes.org/cmVersion"), EXISTS,
|
||||
List.of("annotations", "vmoperator.jdrupes.org/version"), EXISTS,
|
||||
List.of("ownerReferences", 0, "apiVersion"),
|
||||
vmsContext.getGroup() + "/" + vmsContext.getVersions().get(0),
|
||||
List.of("ownerReferences", 0, "kind"), Constants.VM_OP_KIND_VM,
|
||||
List.of("ownerReferences", 0, "kind"), Crd.KIND_VM,
|
||||
List.of("ownerReferences", 0, "name"), VM_NAME,
|
||||
List.of("ownerReferences", 0, "uid"), EXISTS));
|
||||
checkProps(pod.getSpec(), Map.of(
|
||||
|
|
@ -319,7 +317,7 @@ class BasicTests {
|
|||
checkProps(svc.getMetadata(), Map.of(
|
||||
List.of("labels", "app.kubernetes.io/name"), APP_NAME,
|
||||
List.of("labels", "app.kubernetes.io/instance"), VM_NAME,
|
||||
List.of("labels", "app.kubernetes.io/managed-by"), VM_OP_NAME,
|
||||
List.of("labels", "app.kubernetes.io/managed-by"), Crd.NAME,
|
||||
List.of("labels", "label1"), "label1",
|
||||
List.of("labels", "label2"), "replaced",
|
||||
List.of("labels", "label3"), "added",
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@
|
|||
|
||||
handlers=java.util.logging.ConsoleHandler
|
||||
|
||||
org.jgrapes.level=FINE
|
||||
org.jgrapes.core.handlerTracking.level=FINER
|
||||
#org.jgrapes.level=FINE
|
||||
#org.jgrapes.core.handlerTracking.level=FINER
|
||||
|
||||
org.jdrupes.vmoperator.runner.qemu.level=FINE
|
||||
|
||||
|
|
|
|||
|
|
@ -248,6 +248,9 @@ public class Configuration implements Dto {
|
|||
/** The number of outputs. */
|
||||
public int outputs = 1;
|
||||
|
||||
/** The logged in user. */
|
||||
public String loggedInUser;
|
||||
|
||||
/** The spice. */
|
||||
public Spice spice;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,8 +25,7 @@ import io.kubernetes.client.openapi.models.EventsV1Event;
|
|||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
import static org.jdrupes.vmoperator.common.Constants.APP_NAME;
|
||||
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.Constants.Crd;
|
||||
import org.jdrupes.vmoperator.common.K8s;
|
||||
import org.jdrupes.vmoperator.common.K8sClient;
|
||||
import org.jdrupes.vmoperator.common.VmDefinitionStub;
|
||||
|
|
@ -74,7 +73,7 @@ public class ConsoleTracker extends VmDefUpdater {
|
|||
}
|
||||
try {
|
||||
vmStub = VmDefinitionStub.get(apiClient,
|
||||
new GroupVersionKind(VM_OP_GROUP, "", VM_OP_KIND_VM),
|
||||
new GroupVersionKind(Crd.GROUP, "", Crd.KIND_VM),
|
||||
namespace, vmName);
|
||||
} catch (ApiException e) {
|
||||
logger.log(Level.SEVERE, e,
|
||||
|
|
@ -115,7 +114,7 @@ public class ConsoleTracker extends VmDefUpdater {
|
|||
|
||||
// Log event
|
||||
var evt = new EventsV1Event()
|
||||
.reportingController(VM_OP_GROUP + "/" + APP_NAME)
|
||||
.reportingController(Crd.GROUP + "/" + APP_NAME)
|
||||
.action("ConsoleConnectionUpdate")
|
||||
.reason("Connection from " + event.clientHost());
|
||||
K8s.createEvent(apiClient, vmStub.model().get(), evt);
|
||||
|
|
@ -150,7 +149,7 @@ public class ConsoleTracker extends VmDefUpdater {
|
|||
|
||||
// Log event
|
||||
var evt = new EventsV1Event()
|
||||
.reportingController(VM_OP_GROUP + "/" + APP_NAME)
|
||||
.reportingController(Crd.GROUP + "/" + APP_NAME)
|
||||
.action("ConsoleConnectionUpdate")
|
||||
.reason("Disconnected from " + event.clientHost());
|
||||
K8s.createEvent(apiClient, vmStub.model().get(), evt);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* VM-Operator
|
||||
* Copyright (C) 2023 Michael N. Lipp
|
||||
* Copyright (C) 2023,2025 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
|
||||
|
|
@ -24,10 +24,7 @@ import java.nio.file.Path;
|
|||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
import static org.jdrupes.vmoperator.common.Constants.DATA_DISPLAY_LOGIN;
|
||||
import static org.jdrupes.vmoperator.common.Constants.DATA_DISPLAY_PASSWORD;
|
||||
import static org.jdrupes.vmoperator.common.Constants.DATA_DISPLAY_USER;
|
||||
import static org.jdrupes.vmoperator.common.Constants.DATA_PASSWORD_EXPIRY;
|
||||
import org.jdrupes.vmoperator.common.Constants.DisplaySecret;
|
||||
import org.jdrupes.vmoperator.runner.qemu.commands.QmpSetDisplayPassword;
|
||||
import org.jdrupes.vmoperator.runner.qemu.commands.QmpSetPasswordExpiry;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu;
|
||||
|
|
@ -35,9 +32,10 @@ import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand;
|
|||
import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.RunState;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.VmopAgentConnected;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.VmopAgentLogIn;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.VmopAgentLoggedIn;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.VmopAgentLogOut;
|
||||
import org.jgrapes.core.Channel;
|
||||
import org.jgrapes.core.Component;
|
||||
import org.jgrapes.core.Event;
|
||||
import org.jgrapes.core.annotation.Handler;
|
||||
import org.jgrapes.util.events.FileChanged;
|
||||
import org.jgrapes.util.events.WatchFile;
|
||||
|
|
@ -52,6 +50,7 @@ public class DisplayController extends Component {
|
|||
private String protocol;
|
||||
private final Path configDir;
|
||||
private boolean vmopAgentConnected;
|
||||
private String loggedInUser;
|
||||
|
||||
/**
|
||||
* Instantiates a new Display controller.
|
||||
|
|
@ -64,17 +63,7 @@ public class DisplayController extends Component {
|
|||
public DisplayController(Channel componentChannel, Path configDir) {
|
||||
super(componentChannel);
|
||||
this.configDir = configDir;
|
||||
fire(new WatchFile(configDir.resolve(DATA_DISPLAY_PASSWORD)));
|
||||
}
|
||||
|
||||
/**
|
||||
* On vmop agent connected.
|
||||
*
|
||||
* @param event the event
|
||||
*/
|
||||
@Handler
|
||||
public void onVmopAgentConnected(VmopAgentConnected event) {
|
||||
vmopAgentConnected = true;
|
||||
fire(new WatchFile(configDir.resolve(DisplaySecret.DISPLAY_PASSWORD)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -89,7 +78,32 @@ public class DisplayController extends Component {
|
|||
}
|
||||
protocol
|
||||
= event.configuration().vm.display.spice != null ? "spice" : null;
|
||||
configureAccess(false);
|
||||
loggedInUser = event.configuration().vm.display.loggedInUser;
|
||||
configureLogin();
|
||||
if (event.runState() == RunState.STARTING) {
|
||||
configurePassword();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On vmop agent connected.
|
||||
*
|
||||
* @param event the event
|
||||
*/
|
||||
@Handler
|
||||
public void onVmopAgentConnected(VmopAgentConnected event) {
|
||||
vmopAgentConnected = true;
|
||||
configureLogin();
|
||||
}
|
||||
|
||||
private void configureLogin() {
|
||||
if (!vmopAgentConnected) {
|
||||
return;
|
||||
}
|
||||
Event<?> evt = loggedInUser != null
|
||||
? new VmopAgentLogIn(loggedInUser)
|
||||
: new VmopAgentLogOut();
|
||||
fire(evt);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -100,46 +114,10 @@ public class DisplayController extends Component {
|
|||
@Handler
|
||||
@SuppressWarnings("PMD.EmptyCatchBlock")
|
||||
public void onFileChanged(FileChanged event) {
|
||||
if (event.path().equals(configDir.resolve(DATA_DISPLAY_PASSWORD))) {
|
||||
configureAccess(true);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
|
||||
private void configureAccess(boolean passwordChange) {
|
||||
var userLoginConfigured = readFromFile(DATA_DISPLAY_LOGIN)
|
||||
.map(Boolean::parseBoolean).orElse(false);
|
||||
if (!userLoginConfigured) {
|
||||
if (event.path()
|
||||
.equals(configDir.resolve(DisplaySecret.DISPLAY_PASSWORD))) {
|
||||
configurePassword();
|
||||
return;
|
||||
}
|
||||
|
||||
// With user login configured, we have to make sure that the
|
||||
// user is logged in before we set the password and thus allow
|
||||
// access to the display.
|
||||
if (!vmopAgentConnected) {
|
||||
if (passwordChange) {
|
||||
logger.warning(() -> "Request for user login before "
|
||||
+ "VM operator agent has connected");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var user = readFromFile(DATA_DISPLAY_USER);
|
||||
if (user.isEmpty()) {
|
||||
logger.warning(() -> "Login requested, but no user configured");
|
||||
}
|
||||
fire(new VmopAgentLogIn(user.get()).setAssociated(this, user.get()));
|
||||
}
|
||||
|
||||
/**
|
||||
* On vmop agent logged in.
|
||||
*
|
||||
* @param event the event
|
||||
*/
|
||||
@Handler
|
||||
public void onVmopAgentLoggedIn(VmopAgentLoggedIn event) {
|
||||
configurePassword();
|
||||
}
|
||||
|
||||
private void configurePassword() {
|
||||
|
|
@ -152,7 +130,7 @@ public class DisplayController extends Component {
|
|||
}
|
||||
|
||||
private boolean setDisplayPassword() {
|
||||
return readFromFile(DATA_DISPLAY_PASSWORD).map(password -> {
|
||||
return readFromFile(DisplaySecret.DISPLAY_PASSWORD).map(password -> {
|
||||
if (Objects.equals(this.currentPassword, password)) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -165,7 +143,7 @@ public class DisplayController extends Component {
|
|||
}
|
||||
|
||||
private void setPasswordExpiry() {
|
||||
readFromFile(DATA_PASSWORD_EXPIRY).ifPresent(expiry -> {
|
||||
readFromFile(DisplaySecret.PASSWORD_EXPIRY).ifPresent(expiry -> {
|
||||
logger.fine(() -> "Updating expiry time to " + expiry);
|
||||
fire(
|
||||
new MonitorCommand(new QmpSetPasswordExpiry(protocol, expiry)));
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ import org.apache.commons.cli.DefaultParser;
|
|||
import org.apache.commons.cli.Option;
|
||||
import org.apache.commons.cli.Options;
|
||||
import static org.jdrupes.vmoperator.common.Constants.APP_NAME;
|
||||
import static org.jdrupes.vmoperator.common.Constants.DATA_DISPLAY_PASSWORD;
|
||||
import org.jdrupes.vmoperator.common.Constants.DisplaySecret;
|
||||
import org.jdrupes.vmoperator.runner.qemu.commands.QmpCont;
|
||||
import org.jdrupes.vmoperator.runner.qemu.commands.QmpReset;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu;
|
||||
|
|
@ -312,7 +312,7 @@ public class Runner extends Component {
|
|||
|
||||
// Add some values from other sources to configuration
|
||||
newConf.asOf = Instant.ofEpochSecond(configFile.lastModified());
|
||||
Path dsPath = configDir.resolve(DATA_DISPLAY_PASSWORD);
|
||||
Path dsPath = configDir.resolve(DisplaySecret.DISPLAY_PASSWORD);
|
||||
newConf.hasDisplayPassword = dsPath.toFile().canRead();
|
||||
|
||||
// Special actions for initial configuration (startup)
|
||||
|
|
|
|||
|
|
@ -33,8 +33,8 @@ import java.io.IOException;
|
|||
import java.math.BigDecimal;
|
||||
import java.util.logging.Level;
|
||||
import static org.jdrupes.vmoperator.common.Constants.APP_NAME;
|
||||
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.Constants.Crd;
|
||||
import org.jdrupes.vmoperator.common.Constants.Status;
|
||||
import org.jdrupes.vmoperator.common.K8s;
|
||||
import org.jdrupes.vmoperator.common.VmDefinition;
|
||||
import org.jdrupes.vmoperator.common.VmDefinitionStub;
|
||||
|
|
@ -48,6 +48,8 @@ import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange;
|
|||
import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.RunState;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.ShutdownEvent;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.VmopAgentConnected;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.VmopAgentLoggedIn;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.VmopAgentLoggedOut;
|
||||
import org.jdrupes.vmoperator.util.GsonPtr;
|
||||
import org.jgrapes.core.Channel;
|
||||
import org.jgrapes.core.annotation.Handler;
|
||||
|
|
@ -110,11 +112,17 @@ public class StatusUpdater extends VmDefUpdater {
|
|||
}
|
||||
try {
|
||||
vmStub = VmDefinitionStub.get(apiClient,
|
||||
new GroupVersionKind(VM_OP_GROUP, "", VM_OP_KIND_VM),
|
||||
new GroupVersionKind(Crd.GROUP, "", Crd.KIND_VM),
|
||||
namespace, vmName);
|
||||
vmStub.model().ifPresent(model -> {
|
||||
observedGeneration = model.getMetadata().getGeneration();
|
||||
});
|
||||
var vmDef = vmStub.updateStatus(from -> {
|
||||
JsonObject status = from.statusJson();
|
||||
status.remove(Status.LOGGED_IN_USER);
|
||||
return status;
|
||||
}).orElse(null);
|
||||
if (vmDef == null) {
|
||||
return;
|
||||
}
|
||||
observedGeneration = vmDef.getMetadata().getGeneration();
|
||||
} catch (ApiException e) {
|
||||
logger.log(Level.SEVERE, e,
|
||||
() -> "Cannot access VM object, terminating.");
|
||||
|
|
@ -152,7 +160,7 @@ public class StatusUpdater extends VmDefUpdater {
|
|||
"displayPasswordSerial").getAsInt() == -1)) {
|
||||
return;
|
||||
}
|
||||
vmStub.updateStatus(vmDef.get(), from -> {
|
||||
vmStub.updateStatus(from -> {
|
||||
JsonObject status = from.statusJson();
|
||||
if (!event.configuration().hasDisplayPassword) {
|
||||
status.addProperty("displayPasswordSerial", -1);
|
||||
|
|
@ -173,15 +181,15 @@ public class StatusUpdater extends VmDefUpdater {
|
|||
* @throws ApiException
|
||||
*/
|
||||
@Handler
|
||||
@SuppressWarnings({ "PMD.AssignmentInOperand",
|
||||
"PMD.AvoidLiteralsInIfCondition" })
|
||||
@SuppressWarnings({ "PMD.AvoidLiteralsInIfCondition",
|
||||
"PMD.AssignmentInOperand", "PMD.AvoidDuplicateLiterals" })
|
||||
public void onRunnerStateChanged(RunnerStateChange event)
|
||||
throws ApiException {
|
||||
VmDefinition vmDef;
|
||||
if (vmStub == null || (vmDef = vmStub.model().orElse(null)) == null) {
|
||||
return;
|
||||
}
|
||||
vmStub.updateStatus(vmDef, from -> {
|
||||
vmStub.updateStatus(from -> {
|
||||
JsonObject status = from.statusJson();
|
||||
boolean running = event.runState().vmRunning();
|
||||
updateCondition(vmDef, vmDef.statusJson(), "Running", running,
|
||||
|
|
@ -196,6 +204,7 @@ public class StatusUpdater extends VmDefUpdater {
|
|||
} else if (event.runState() == RunState.STOPPED) {
|
||||
status.addProperty("ram", "0");
|
||||
status.addProperty("cpus", 0);
|
||||
status.remove(Status.LOGGED_IN_USER);
|
||||
}
|
||||
|
||||
if (!running) {
|
||||
|
|
@ -228,7 +237,7 @@ public class StatusUpdater extends VmDefUpdater {
|
|||
|
||||
// Log event
|
||||
var evt = new EventsV1Event()
|
||||
.reportingController(VM_OP_GROUP + "/" + APP_NAME)
|
||||
.reportingController(Crd.GROUP + "/" + APP_NAME)
|
||||
.action("StatusUpdate").reason(event.reason())
|
||||
.note(event.message());
|
||||
K8s.createEvent(apiClient, vmDef, evt);
|
||||
|
|
@ -344,4 +353,35 @@ public class StatusUpdater extends VmDefUpdater {
|
|||
return status;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param event the event
|
||||
* @throws ApiException
|
||||
*/
|
||||
@Handler
|
||||
@SuppressWarnings("PMD.AssignmentInOperand")
|
||||
public void onVmopAgentLoggedIn(VmopAgentLoggedIn event)
|
||||
throws ApiException {
|
||||
vmStub.updateStatus(from -> {
|
||||
JsonObject status = from.statusJson();
|
||||
status.addProperty(Status.LOGGED_IN_USER,
|
||||
event.triggering().user());
|
||||
return status;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param event the event
|
||||
* @throws ApiException
|
||||
*/
|
||||
@Handler
|
||||
@SuppressWarnings("PMD.AssignmentInOperand")
|
||||
public void onVmopAgentLoggedOut(VmopAgentLoggedOut event)
|
||||
throws ApiException {
|
||||
vmStub.updateStatus(from -> {
|
||||
JsonObject status = from.statusJson();
|
||||
status.remove(Status.LOGGED_IN_USER);
|
||||
return status;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,11 +12,12 @@ layout: vm-operator
|
|||
### Shared file system
|
||||
|
||||
Mount a shared file system as home file system on all VMs in the pool.
|
||||
If you want to use the sample script for logging in a user, the filesystem
|
||||
must support POSIX file access control lists (ACLs).
|
||||
|
||||
### Restrict access
|
||||
|
||||
The only possibility to access the VMs should be via a desktop started by
|
||||
the VM-Operator.
|
||||
The VMs should only be accessible via a desktop started by the VM-Operator.
|
||||
|
||||
* Disable the display manager.
|
||||
|
||||
|
|
@ -31,10 +32,17 @@ the VM-Operator.
|
|||
# systemctl mask getty@tty1
|
||||
# systemctl stop getty@tty1
|
||||
```
|
||||
|
||||
You can, of course, disable `getty` completely. If you do this, make sure
|
||||
that you can still access your master VM through `ssh`, else you have
|
||||
locked yourself out.
|
||||
|
||||
Strictly speaking, it is not necessary to disable these services, because
|
||||
the sample script includes a `Conflicts=` directive in the systemd service
|
||||
that starts the desktop for the user. However, this is mainly intended for
|
||||
development purposes and not for production.
|
||||
|
||||
You can, of course, disable `getty` completely. If you do this, make sure
|
||||
that you can still access your master VM through `ssh`, else you have
|
||||
locked yourself out.
|
||||
The following should actually be configured for any VM.
|
||||
|
||||
* Prevent suspend/hibernate, because it will lock the VM.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue