Add viewer conlet (#25)
Some checks failed
Java CI with Gradle / build (push) Has been cancelled

This commit is contained in:
Michael N. Lipp 2024-05-27 12:57:01 +02:00 committed by GitHub
parent b6f0299932
commit a6525a2289
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
77 changed files with 2642 additions and 250 deletions

View file

@ -0,0 +1 @@
+30

View file

@ -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. */

View file

@ -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;
}

View file

@ -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)));
}
}

View file

@ -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);
}
});
}

View file

@ -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());

View file

@ -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;

View file

@ -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;

View file

@ -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)";
}
}
}

View file

@ -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<>();
/**

View file

@ -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();

View file

@ -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();

View file

@ -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();