Add viewer conlet (#25)
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
This commit is contained in:
parent
b6f0299932
commit
a6525a2289
77 changed files with 2642 additions and 250 deletions
1
org.jdrupes.vmoperator.runner.qemu/password-expiry
Normal file
1
org.jdrupes.vmoperator.runner.qemu/password-expiry
Normal file
|
|
@ -0,0 +1 @@
|
|||
+30
|
||||
|
|
@ -108,7 +108,7 @@ public class Configuration implements Dto {
|
|||
* Subsection "vm".
|
||||
*/
|
||||
@SuppressWarnings({ "PMD.ShortClassName", "PMD.TooManyFields",
|
||||
"PMD.DataClass" })
|
||||
"PMD.DataClass", "PMD.AvoidDuplicateLiterals" })
|
||||
public static class Vm implements Dto {
|
||||
|
||||
/** The name. */
|
||||
|
|
@ -196,6 +196,7 @@ public class Configuration implements Dto {
|
|||
/**
|
||||
* Subsection "network".
|
||||
*/
|
||||
@SuppressWarnings("PMD.DataClass")
|
||||
public static class Network implements Dto {
|
||||
|
||||
/** The type. */
|
||||
|
|
@ -217,6 +218,7 @@ public class Configuration implements Dto {
|
|||
/**
|
||||
* Subsection "drive".
|
||||
*/
|
||||
@SuppressWarnings("PMD.DataClass")
|
||||
public static class Drive implements Dto {
|
||||
|
||||
/** The type. */
|
||||
|
|
@ -247,6 +249,7 @@ public class Configuration implements Dto {
|
|||
/**
|
||||
* Subsection "spice".
|
||||
*/
|
||||
@SuppressWarnings("PMD.DataClass")
|
||||
public static class Spice implements Dto {
|
||||
|
||||
/** The port. */
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
|
|||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import org.jdrupes.vmoperator.runner.qemu.commands.QmpAddCpu;
|
||||
|
|
@ -170,7 +171,7 @@ public class CpuController extends Component {
|
|||
|
||||
private void checkCpus() {
|
||||
if (suspendedConfigure != null && desiredCpus != null
|
||||
&& currentCpus == desiredCpus.intValue()) {
|
||||
&& Objects.equals(currentCpus, desiredCpus)) {
|
||||
suspendedConfigure.resumeHandling();
|
||||
suspendedConfigure = null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import java.nio.file.Path;
|
|||
import java.util.Objects;
|
||||
import java.util.logging.Level;
|
||||
import org.jdrupes.vmoperator.runner.qemu.commands.QmpSetDisplayPassword;
|
||||
import org.jdrupes.vmoperator.runner.qemu.commands.QmpSetPasswordExpiry;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.State;
|
||||
|
|
@ -40,6 +41,7 @@ import org.jgrapes.util.events.WatchFile;
|
|||
public class DisplayController extends Component {
|
||||
|
||||
public static final String DISPLAY_PASSWORD_FILE = "display-password";
|
||||
public static final String PASSWORD_EXPIRY_FILE = "password-expiry";
|
||||
private String currentPassword;
|
||||
private String protocol;
|
||||
private final Path configDir;
|
||||
|
|
@ -50,7 +52,8 @@ public class DisplayController extends Component {
|
|||
* @param componentChannel the component channel
|
||||
* @param configDir
|
||||
*/
|
||||
@SuppressWarnings("PMD.AssignmentToNonFinalStatic")
|
||||
@SuppressWarnings({ "PMD.AssignmentToNonFinalStatic",
|
||||
"PMD.ConstructorCallsOverridableMethod" })
|
||||
public DisplayController(Channel componentChannel, Path configDir) {
|
||||
super(componentChannel);
|
||||
this.configDir = configDir;
|
||||
|
|
@ -90,7 +93,12 @@ public class DisplayController extends Component {
|
|||
if (protocol == null) {
|
||||
return;
|
||||
}
|
||||
if (setDisplayPassword()) {
|
||||
setPasswordExpiry();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean setDisplayPassword() {
|
||||
String password;
|
||||
Path dpPath = configDir.resolve(DISPLAY_PASSWORD_FILE);
|
||||
if (dpPath.toFile().canRead()) {
|
||||
|
|
@ -100,18 +108,37 @@ public class DisplayController extends Component {
|
|||
} catch (IOException e) {
|
||||
logger.log(Level.WARNING, e, () -> "Cannot read display"
|
||||
+ " password: " + e.getMessage());
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
logger.finer(() -> "No display password");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Objects.equals(this.currentPassword, password)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
logger.fine(() -> "Updating display password");
|
||||
fire(new MonitorCommand(new QmpSetDisplayPassword(protocol, password)));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setPasswordExpiry() {
|
||||
Path pePath = configDir.resolve(PASSWORD_EXPIRY_FILE);
|
||||
if (!pePath.toFile().canRead()) {
|
||||
return;
|
||||
}
|
||||
logger.finer(() -> "Found expiry time");
|
||||
String expiry;
|
||||
try {
|
||||
expiry = Files.readString(pePath);
|
||||
} catch (IOException e) {
|
||||
logger.log(Level.WARNING, e, () -> "Cannot read expiry"
|
||||
+ " time: " + e.getMessage());
|
||||
return;
|
||||
}
|
||||
logger.fine(() -> "Updating expiry time");
|
||||
fire(new MonitorCommand(new QmpSetPasswordExpiry(protocol, expiry)));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,7 +90,8 @@ public class QemuMonitor extends Component {
|
|||
* @param configDir the config dir
|
||||
* @throws IOException Signals that an I/O exception has occurred.
|
||||
*/
|
||||
@SuppressWarnings("PMD.AssignmentToNonFinalStatic")
|
||||
@SuppressWarnings({ "PMD.AssignmentToNonFinalStatic",
|
||||
"PMD.ConstructorCallsOverridableMethod" })
|
||||
public QemuMonitor(Channel componentChannel, Path configDir)
|
||||
throws IOException {
|
||||
super(componentChannel);
|
||||
|
|
@ -155,6 +156,7 @@ public class QemuMonitor extends Component {
|
|||
* @param event the event
|
||||
* @param channel the channel
|
||||
*/
|
||||
@SuppressWarnings("resource")
|
||||
@Handler
|
||||
public void onClientConnected(ClientConnected event,
|
||||
SocketIOChannel channel) {
|
||||
|
|
@ -276,7 +278,7 @@ public class QemuMonitor extends Component {
|
|||
writer.append(asText).append('\n').flush();
|
||||
} catch (IOException e) {
|
||||
// Cannot happen, but...
|
||||
logger.log(Level.WARNING, e, () -> e.getMessage());
|
||||
logger.log(Level.WARNING, e, e::getMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -186,7 +186,8 @@ import org.jgrapes.util.events.WatchFile;
|
|||
*
|
||||
*/
|
||||
@SuppressWarnings({ "PMD.ExcessiveImports", "PMD.AvoidPrintStackTrace",
|
||||
"PMD.DataflowAnomalyAnalysis", "PMD.TooManyMethods" })
|
||||
"PMD.DataflowAnomalyAnalysis", "PMD.TooManyMethods",
|
||||
"PMD.CouplingBetweenObjects" })
|
||||
public class Runner extends Component {
|
||||
|
||||
private static final String QEMU = "qemu";
|
||||
|
|
@ -232,7 +233,8 @@ public class Runner extends Component {
|
|||
* @param cmdLine the cmd line
|
||||
* @throws IOException Signals that an I/O exception has occurred.
|
||||
*/
|
||||
@SuppressWarnings("PMD.SystemPrintln")
|
||||
@SuppressWarnings({ "PMD.SystemPrintln",
|
||||
"PMD.ConstructorCallsOverridableMethod" })
|
||||
public Runner(CommandLine cmdLine) throws IOException {
|
||||
yamlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
|
||||
false);
|
||||
|
|
@ -495,27 +497,27 @@ public class Runner extends Component {
|
|||
try {
|
||||
var cloudInitDir = config.dataDir.resolve("cloud-init");
|
||||
cloudInitDir.toFile().mkdir();
|
||||
var metaOut
|
||||
= Files.newBufferedWriter(cloudInitDir.resolve("meta-data"));
|
||||
if (config.cloudInit.metaData != null) {
|
||||
yamlMapper.writer().writeValue(metaOut,
|
||||
config.cloudInit.metaData);
|
||||
try (var metaOut
|
||||
= Files.newBufferedWriter(cloudInitDir.resolve("meta-data"))) {
|
||||
if (config.cloudInit.metaData != null) {
|
||||
yamlMapper.writer().writeValue(metaOut,
|
||||
config.cloudInit.metaData);
|
||||
}
|
||||
}
|
||||
metaOut.close();
|
||||
var userOut
|
||||
= Files.newBufferedWriter(cloudInitDir.resolve("user-data"));
|
||||
userOut.write("#cloud-config\n");
|
||||
if (config.cloudInit.userData != null) {
|
||||
yamlMapper.writer().writeValue(userOut,
|
||||
config.cloudInit.userData);
|
||||
try (var userOut
|
||||
= Files.newBufferedWriter(cloudInitDir.resolve("user-data"))) {
|
||||
userOut.write("#cloud-config\n");
|
||||
if (config.cloudInit.userData != null) {
|
||||
yamlMapper.writer().writeValue(userOut,
|
||||
config.cloudInit.userData);
|
||||
}
|
||||
}
|
||||
userOut.close();
|
||||
if (config.cloudInit.networkConfig != null) {
|
||||
var networkConfig = Files.newBufferedWriter(
|
||||
cloudInitDir.resolve("network-config"));
|
||||
yamlMapper.writer().writeValue(networkConfig,
|
||||
config.cloudInit.networkConfig);
|
||||
networkConfig.close();
|
||||
try (var networkConfig = Files.newBufferedWriter(
|
||||
cloudInitDir.resolve("network-config"))) {
|
||||
yamlMapper.writer().writeValue(networkConfig,
|
||||
config.cloudInit.networkConfig);
|
||||
}
|
||||
}
|
||||
startProcess(cloudInitImgDefinition);
|
||||
} catch (IOException e) {
|
||||
|
|
@ -545,7 +547,6 @@ public class Runner extends Component {
|
|||
&& event.path().equals(config.swtpmSocket)) {
|
||||
// swtpm running, maybe start qemu
|
||||
mayBeStartQemu(QemuPreps.Tpm);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -690,6 +691,7 @@ public class Runner extends Component {
|
|||
"The VM has been shut down"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("PMD.ConfusingArgumentToVarargsMethod")
|
||||
private void shutdown() {
|
||||
if (!Set.of(State.TERMINATING, State.STOPPED).contains(state)) {
|
||||
fire(new Stop());
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ public class StatusUpdater extends Component {
|
|||
*
|
||||
* @param componentChannel the component channel
|
||||
*/
|
||||
@SuppressWarnings("PMD.ConstructorCallsOverridableMethod")
|
||||
public StatusUpdater(Channel componentChannel) {
|
||||
super(componentChannel);
|
||||
try {
|
||||
|
|
@ -91,7 +92,6 @@ public class StatusUpdater extends Component {
|
|||
() -> "Cannot access events API, terminating.");
|
||||
fire(new Exit(1));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -179,6 +179,7 @@ public class StatusUpdater extends Component {
|
|||
* @throws ApiException
|
||||
*/
|
||||
@Handler
|
||||
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
|
||||
public void onConfigureQemu(ConfigureQemu event)
|
||||
throws ApiException {
|
||||
guestShutdownStops = event.configuration().guestShutdownStops;
|
||||
|
|
@ -189,14 +190,22 @@ public class StatusUpdater extends Component {
|
|||
}
|
||||
|
||||
// A change of the runner configuration is typically caused
|
||||
// by a new version of the CR. So we observe the new CR.
|
||||
// by a new version of the CR. So we update only if we have
|
||||
// a new version of the CR. There's one exception: the display
|
||||
// password is configured by a file, not by the CR.
|
||||
var vmDef = vmStub.model();
|
||||
if (vmDef.isPresent()
|
||||
&& vmDef.get().metadata().getGeneration() == observedGeneration) {
|
||||
&& vmDef.get().metadata().getGeneration() == observedGeneration
|
||||
&& (event.configuration().hasDisplayPassword
|
||||
|| vmDef.get().status().getAsJsonPrimitive(
|
||||
"displayPasswordSerial").getAsInt() == -1)) {
|
||||
return;
|
||||
}
|
||||
vmStub.updateStatus(vmDef.get(), from -> {
|
||||
JsonObject status = from.status();
|
||||
if (!event.configuration().hasDisplayPassword) {
|
||||
status.addProperty("displayPasswordSerial", -1);
|
||||
}
|
||||
status.getAsJsonArray("conditions").asList().stream()
|
||||
.map(cond -> (JsonObject) cond).filter(cond -> "Running"
|
||||
.equals(cond.get("type").getAsString()))
|
||||
|
|
@ -213,7 +222,8 @@ public class StatusUpdater extends Component {
|
|||
* @throws ApiException
|
||||
*/
|
||||
@Handler
|
||||
@SuppressWarnings("PMD.AssignmentInOperand")
|
||||
@SuppressWarnings({ "PMD.AssignmentInOperand",
|
||||
"PMD.AvoidLiteralsInIfCondition" })
|
||||
public void onRunnerStateChanged(RunnerStateChange event)
|
||||
throws ApiException {
|
||||
K8sDynamicModel vmDef;
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ public class QmpAddCpu extends QmpCommand {
|
|||
cmd.put("execute", "device_add");
|
||||
ObjectNode args = mapper.createObjectNode();
|
||||
cmd.set("arguments", args);
|
||||
args.setAll((ObjectNode) (unused.get("props")));
|
||||
args.setAll((ObjectNode) unused.get("props"));
|
||||
args.set("driver", unused.get("type"));
|
||||
args.put("id", cpuId);
|
||||
return cmd;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* VM-Operator
|
||||
* Copyright (C) 2023 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.runner.qemu.commands;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.fasterxml.jackson.databind.node.TextNode;
|
||||
|
||||
/**
|
||||
* A {@link QmpCommand} that sets the password expiry.
|
||||
*/
|
||||
public class QmpSetPasswordExpiry extends QmpCommand {
|
||||
|
||||
private final String protocol;
|
||||
private final String expiry;
|
||||
|
||||
/**
|
||||
* Instantiates a new command.
|
||||
*
|
||||
* @param protocol the protocol
|
||||
* @param expiry the expiry time
|
||||
*/
|
||||
public QmpSetPasswordExpiry(String protocol, String expiry) {
|
||||
this.protocol = protocol;
|
||||
this.expiry = expiry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonNode toJson() {
|
||||
ObjectNode cmd = mapper.createObjectNode();
|
||||
cmd.put("execute", "expire_password");
|
||||
ObjectNode args = mapper.createObjectNode();
|
||||
cmd.set("arguments", args);
|
||||
args.set("protocol", new TextNode(protocol));
|
||||
args.set("time", new TextNode(expiry));
|
||||
return cmd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
try {
|
||||
var json = toJson();
|
||||
return mapper.writeValueAsString(json);
|
||||
} catch (JsonProcessingException e) {
|
||||
return "(no string representation)";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -30,7 +30,9 @@ import org.jdrupes.vmoperator.runner.qemu.commands.QmpCommand;
|
|||
*/
|
||||
public class HotpluggableCpuStatus extends MonitorResult {
|
||||
|
||||
@SuppressWarnings("PMD.ImmutableField")
|
||||
private List<ObjectNode> usedCpus = new ArrayList<>();
|
||||
@SuppressWarnings("PMD.ImmutableField")
|
||||
private List<ObjectNode> unusedCpus = new ArrayList<>();
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -55,8 +55,7 @@ public class MonitorCommand extends Event<Void> {
|
|||
builder.append(Components.objectName(this))
|
||||
.append(" [").append(command);
|
||||
if (channels() != null) {
|
||||
builder.append(", channels=");
|
||||
builder.append(Channel.toString(channels()));
|
||||
builder.append(", channels=").append(Channel.toString(channels()));
|
||||
}
|
||||
builder.append(']');
|
||||
return builder.toString();
|
||||
|
|
|
|||
|
|
@ -152,8 +152,7 @@ public class MonitorResult extends Event<Void> {
|
|||
builder.append(Components.objectName(this))
|
||||
.append(" [").append(executed).append(", ").append(successful());
|
||||
if (channels() != null) {
|
||||
builder.append(", channels=");
|
||||
builder.append(Channel.toString(channels()));
|
||||
builder.append(", channels=").append(Channel.toString(channels()));
|
||||
}
|
||||
builder.append(']');
|
||||
return builder.toString();
|
||||
|
|
|
|||
|
|
@ -109,15 +109,14 @@ public class RunnerStateChange extends Event<Void> {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
StringBuilder builder = new StringBuilder(50);
|
||||
builder.append(Components.objectName(this))
|
||||
.append(" [").append(state).append(": ").append(reason);
|
||||
if (failed) {
|
||||
builder.append(" (failed)");
|
||||
}
|
||||
if (channels() != null) {
|
||||
builder.append(", channels=");
|
||||
builder.append(Channel.toString(channels()));
|
||||
builder.append(", channels=").append(Channel.toString(channels()));
|
||||
}
|
||||
builder.append(']');
|
||||
return builder.toString();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue