diff --git a/org.jdrupes.vmoperator.runner.qemu/build.gradle b/org.jdrupes.vmoperator.runner.qemu/build.gradle
index f794975..522fa04 100644
--- a/org.jdrupes.vmoperator.runner.qemu/build.gradle
+++ b/org.jdrupes.vmoperator.runner.qemu/build.gradle
@@ -10,7 +10,7 @@ plugins {
dependencies {
implementation 'org.jgrapes:org.jgrapes.core:[1.19.0,2)'
- implementation 'org.jgrapes:org.jgrapes.io:[2.5.0,3)'
+ implementation 'org.jgrapes:org.jgrapes.io:[2.7.0,3)'
implementation 'org.jgrapes:org.jgrapes.http:[3.1.0,4)'
implementation 'org.jgrapes:org.jgrapes.util:[1.28.0,2)'
diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java
index 06d4e42..31b3133 100644
--- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java
+++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java
@@ -18,7 +18,6 @@
package org.jdrupes.vmoperator.runner.qemu;
-import com.fasterxml.jackson.databind.JsonNode;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@@ -46,7 +45,6 @@ class Configuration implements Dto {
public Path monitorSocket;
public Path firmwareRom;
public Path firmwareFlash;
- public JsonNode monitorMessages;
@SuppressWarnings("PMD.ShortVariable")
public Vm vm;
@@ -126,10 +124,8 @@ class Configuration implements Dto {
}
}
runtimeDir += "/vmrunner/" + vm.name;
- swtpmSocket
- = Path.of(runtimeDir, "swtpm-sock");
- monitorSocket
- = Path.of(runtimeDir, "monitor.sock");
+ swtpmSocket = Path.of(runtimeDir, "swtpm-sock");
+ monitorSocket = Path.of(runtimeDir, "monitor.sock");
}
Path runtimePath = Path.of(runtimeDir);
if (!Files.exists(runtimePath)) {
diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/QemuMonitor.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/QemuMonitor.java
new file mode 100644
index 0000000..f187685
--- /dev/null
+++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/QemuMonitor.java
@@ -0,0 +1,251 @@
+/*
+ * 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 .
+ */
+
+package org.jdrupes.vmoperator.runner.qemu;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import java.io.IOException;
+import java.io.Writer;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.net.UnixDomainSocketAddress;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.jgrapes.core.Channel;
+import org.jgrapes.core.Component;
+import org.jgrapes.core.Components;
+import org.jgrapes.core.annotation.Handler;
+import org.jgrapes.core.events.Start;
+import org.jgrapes.core.events.Stop;
+import org.jgrapes.io.events.Closed;
+import org.jgrapes.io.events.ConnectError;
+import org.jgrapes.io.events.Input;
+import org.jgrapes.io.events.OpenSocketConnection;
+import org.jgrapes.io.util.ByteBufferWriter;
+import org.jgrapes.io.util.LineCollector;
+import org.jgrapes.net.SocketIOChannel;
+import org.jgrapes.net.events.ClientConnected;
+import org.jgrapes.util.events.ConfigurationUpdate;
+import org.jgrapes.util.events.FileChanged;
+import org.jgrapes.util.events.FileChanged.Kind;
+import org.jgrapes.util.events.WatchFile;
+
+/**
+ * A component that handles the communication over the Qemu monitor
+ * socket.
+ */
+public class QemuMonitor extends Component {
+
+ @SuppressWarnings({ "PMD.FieldNamingConventions",
+ "PMD.VariableNamingConventions" })
+ private static final Logger monitorLog
+ = Logger.getLogger(QemuMonitor.class.getName());
+
+ @SuppressWarnings("PMD.UseConcurrentHashMap")
+ private final Map monitorMessages = new HashMap<>(Map.of(
+ "connect", "{ \"execute\": \"qmp_capabilities\" }",
+ "powerdown", "{ \"execute\": \"system_powerdown\" }"));
+
+ private Path socketPath;
+
+ private SocketIOChannel monitorChannel;
+
+ private Stop suspendedStop;
+
+ /**
+ * Instantiates a new qemu monitor.
+ *
+ * @param componentChannel the component channel
+ */
+ public QemuMonitor(Channel componentChannel) {
+ super(componentChannel);
+ }
+
+ /**
+ * As the configuration of this component depends on the configuration
+ * of the {@link Runner}, it doesn't have a handler for the
+ * {@link ConfigurationUpdate} event. The values are forwarded from the
+ * {@link Runner} instead.
+ *
+ * @param socketPath the socket path
+ */
+ /* default */ void configure(Path socketPath) {
+ this.socketPath = socketPath;
+ }
+
+ /**
+ * Handle the start event.
+ *
+ * @param event the event
+ * @throws IOException Signals that an I/O exception has occurred.
+ */
+ @Handler
+ public void onStart(Start event) throws IOException {
+ Files.deleteIfExists(socketPath);
+ fire(new WatchFile(socketPath));
+ }
+
+ /**
+ * Watch for the creation of the swtpm socket and start the
+ * qemu process if it has been created.
+ *
+ * @param event the event
+ * @param context the context
+ */
+ @Handler
+ public void onFileChanged(FileChanged event) {
+ if (event.change() == Kind.CREATED && event.path().equals(socketPath)) {
+ // qemu running, open socket
+ fire(new OpenSocketConnection(
+ UnixDomainSocketAddress.of(socketPath))
+ .setAssociated(QemuMonitor.class, this));
+ }
+ }
+
+ /**
+ * Check if this is from opening the monitor socket and if true,
+ * save the socket in the context and associate the channel with
+ * the context. Then send the initial message to the socket.
+ *
+ * @param event the event
+ * @param channel the channel
+ */
+ @Handler
+ public void onClientConnected(ClientConnected event,
+ SocketIOChannel channel) {
+ event.openEvent().associated(QemuMonitor.class).ifPresent(qm -> {
+ monitorChannel = channel;
+ channel.setAssociated(QemuMonitor.class, this);
+ channel.setAssociated(Writer.class, new ByteBufferWriter(
+ channel).nativeCharset());
+ channel.setAssociated(LineCollector.class,
+ new LineCollector()
+ .consumer(line -> {
+ try {
+ processMonitorInput(line);
+ } catch (IOException e) {
+ throw new UndeclaredThrowableException(e);
+ }
+ }));
+ writeToMonitor(monitorMessages.get("connect"));
+ });
+ }
+
+ /**
+ * Called when a connection attempt fails.
+ *
+ * @param event the event
+ * @param channel the channel
+ */
+ @Handler
+ public void onConnectError(ConnectError event, SocketIOChannel channel) {
+ event.event().associated(QemuMonitor.class).ifPresent(qm -> {
+ fire(new Stop());
+ });
+ }
+
+ private void writeToMonitor(String message) {
+ monitorLog.fine(() -> "monitor(out): " + message);
+ monitorChannel.associated(Writer.class).ifPresent(writer -> {
+ try {
+ writer.append(message).append('\n').flush();
+ } catch (IOException e) {
+ // Cannot happen, but...
+ logger.log(Level.WARNING, e, () -> e.getMessage());
+ }
+ });
+ }
+
+ /**
+ * Handle data from qemu monitor connection.
+ *
+ * @param event the event
+ * @param channel the channel
+ */
+ @Handler
+ public void onInput(Input> event, SocketIOChannel channel) {
+ if (channel.associated(QemuMonitor.class).isEmpty()) {
+ return;
+ }
+ channel.associated(LineCollector.class).ifPresent(collector -> {
+ collector.feed(event);
+ });
+ }
+
+ private void processMonitorInput(String line)
+ throws IOException {
+ monitorLog.fine(() -> "monitor(in): " + line);
+ try {
+ var response
+ = ((Runner) channel()).mapper().readValue(line, JsonNode.class);
+ if (response.has("QMP")) {
+ fire(new QemuMonitorOpened());
+ }
+ } catch (JsonProcessingException e) {
+ throw new IOException(e);
+ }
+ }
+
+ /**
+ * On closed.
+ *
+ * @param event the event
+ */
+ @Handler
+ public void onClosed(Closed> event, SocketIOChannel channel) {
+ channel.associated(QemuMonitor.class).ifPresent(qm -> {
+ monitorChannel = null;
+ synchronized (this) {
+ if (suspendedStop != null) {
+ suspendedStop.resumeHandling();
+ suspendedStop = null;
+ }
+ }
+ });
+ }
+
+ /**
+ * Shutdown the VM.
+ *
+ * @param event the event
+ */
+ @Handler(priority = 100)
+ public void onStop(Stop event) {
+ if (monitorChannel != null) {
+ // We have a connection to Qemu, attempt ACPI shutdown.
+ event.suspendHandling();
+ suspendedStop = event;
+ writeToMonitor(monitorMessages.get("powerdown"));
+
+ // Schedule timer as fallback
+ Components.schedule(t -> {
+ synchronized (this) {
+ if (suspendedStop != null) {
+ suspendedStop.resumeHandling();
+ suspendedStop = null;
+ }
+ }
+ }, Duration.ofMillis(5000));
+ }
+ }
+}
diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/QemuMonitorOpened.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/QemuMonitorOpened.java
new file mode 100644
index 0000000..2eea438
--- /dev/null
+++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/QemuMonitorOpened.java
@@ -0,0 +1,29 @@
+/*
+ * 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 .
+ */
+
+package org.jdrupes.vmoperator.runner.qemu;
+
+import org.jgrapes.io.events.Opened;
+
+/**
+ * Signals that the connection to the Qemu monitor socket has been
+ * established successfully.
+ */
+public class QemuMonitorOpened extends Opened {
+
+}
diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java
index 0a2e825..21a98cd 100644
--- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java
+++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java
@@ -33,17 +33,18 @@ import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.StringWriter;
-import java.io.Writer;
-import java.net.UnixDomainSocketAddress;
+import java.lang.reflect.UndeclaredThrowableException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
+import java.util.Map;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
import static org.jdrupes.vmoperator.runner.qemu.Configuration.BOOT_MODE_SECURE;
import static org.jdrupes.vmoperator.runner.qemu.Configuration.BOOT_MODE_UEFI;
+import org.jdrupes.vmoperator.runner.qemu.StateController.State;
import org.jdrupes.vmoperator.util.ExtendedObjectWrapper;
import org.jgrapes.core.Channel;
import org.jgrapes.core.Component;
@@ -51,36 +52,34 @@ import org.jgrapes.core.Components;
import org.jgrapes.core.TypedIdKey;
import org.jgrapes.core.annotation.Handler;
import org.jgrapes.core.events.Start;
+import org.jgrapes.core.events.Started;
import org.jgrapes.core.events.Stop;
import org.jgrapes.io.NioDispatcher;
-import org.jgrapes.io.events.ConnectError;
import org.jgrapes.io.events.Input;
-import org.jgrapes.io.events.OpenSocketConnection;
import org.jgrapes.io.events.ProcessExited;
import org.jgrapes.io.events.ProcessStarted;
import org.jgrapes.io.events.StartProcess;
import org.jgrapes.io.process.ProcessManager;
import org.jgrapes.io.process.ProcessManager.ProcessChannel;
-import org.jgrapes.io.util.ByteBufferWriter;
import org.jgrapes.io.util.LineCollector;
import org.jgrapes.net.SocketConnector;
-import org.jgrapes.net.SocketIOChannel;
-import org.jgrapes.net.events.ClientConnected;
import org.jgrapes.util.FileSystemWatcher;
import org.jgrapes.util.YamlConfigurationStore;
import org.jgrapes.util.events.ConfigurationUpdate;
import org.jgrapes.util.events.FileChanged;
import org.jgrapes.util.events.FileChanged.Kind;
+import org.jgrapes.util.events.InitialConfiguration;
import org.jgrapes.util.events.WatchFile;
/**
* The Runner.
*
* @startuml
- * [*] --> Setup
- * Setup --> Setup: InitialConfiguration/configure Runner
+ * [*] --> Initializing
+ * Initializing -> Initializing: InitialConfiguration/configure Runner
+ * Initializing -> Initializing: Start/start Runner
*
- * state Startup {
+ * state "Starting (Processes)" as StartingProcess {
*
* state which <>
* state "Start swtpm" as swtpm
@@ -105,9 +104,9 @@ import org.jgrapes.util.events.WatchFile;
* monitor --> error: ConnectError[for monitor]
* }
*
- * Setup --> which: Start
+ * Initializing --> which: Started
*
- * success --> Run
+ * success --> Running
* error --> [*]
*
* @enduml
@@ -124,18 +123,15 @@ public class Runner extends Component {
private static final String SAVED_TEMPLATE = "VM.ftl.yaml";
private static final String FW_FLASH = "fw-flash.fd";
- @SuppressWarnings({ "PMD.FieldNamingConventions",
- "PMD.VariableNamingConventions" })
- private static final Logger monitorLog
- = Logger.getLogger(Runner.class.getPackageName() + ".monitor");
-
- private static Runner app;
-
private final ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
private final JsonNode defaults;
@SuppressWarnings("PMD.UseConcurrentHashMap")
private Configuration config = new Configuration();
private final freemarker.template.Configuration fmConfig;
+ private final StateController state;
+ private CommandDefinition swtpmDefinition;
+ private CommandDefinition qemuDefinition;
+ private final QemuMonitor qemuMonitor;
/**
* Instantiates a new runner.
@@ -143,7 +139,7 @@ public class Runner extends Component {
* @throws IOException Signals that an I/O exception has occurred.
*/
public Runner() throws IOException {
- super(new Context());
+ state = new StateController(this);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
false);
@@ -167,6 +163,7 @@ public class Runner extends Component {
attach(new FileSystemWatcher(channel()));
attach(new ProcessManager(channel()));
attach(new SocketConnector(channel()));
+ attach(qemuMonitor = new QemuMonitor(channel()));
// Configuration store with file in /etc (default)
File config = new File(System.getProperty(
@@ -176,6 +173,10 @@ public class Runner extends Component {
fire(new WatchFile(config.toPath()));
}
+ /* default */ ObjectMapper mapper() {
+ return mapper;
+ }
+
/**
* On configuration update.
*
@@ -184,58 +185,64 @@ public class Runner extends Component {
@Handler
public void onConfigurationUpdate(ConfigurationUpdate event) {
event.structured(componentPath()).ifPresent(c -> {
- try {
- config = mapper.convertValue(c, Configuration.class);
- } catch (IllegalArgumentException e) {
- logger.log(Level.SEVERE, e, () -> "Invalid configuration: "
- + e.getMessage());
- // Don't use default configuration
- config = null;
+ if (event instanceof InitialConfiguration) {
+ processInitialConfiguration(c);
}
});
}
+ private void processInitialConfiguration(
+ Map runnerConfiguration) {
+ try {
+ config = mapper.convertValue(runnerConfiguration,
+ Configuration.class);
+ if (!config.check()) {
+ // Invalid configuration, not used, problems already logged.
+ config = null;
+ }
+ // Forward some values to child components
+ qemuMonitor.configure(config.monitorSocket);
+ } catch (IllegalArgumentException e) {
+ logger.log(Level.SEVERE, e, () -> "Invalid configuration: "
+ + e.getMessage());
+ // Don't use default configuration
+ config = null;
+ }
+ }
+
/**
* Handle the start event.
*
* @param event the event
*/
@Handler
- @SuppressWarnings({ "PMD.SystemPrintln" })
public void onStart(Start event) {
try {
- if (config == null || !config.check()) {
- // Invalid configuration, fail
+ if (config == null) {
+ // Missing configuration, fail
fire(new Stop());
return;
}
+ // Store process id
+ try (var pidFile = Files.newBufferedWriter(
+ Path.of(config.runtimeDir, "runner.pid"))) {
+ pidFile.write(ProcessHandle.current().pid() + "\n");
+ }
+
// Prepare firmware files and add to config
setFirmwarePaths();
- // Obtain more data from template
+ // Obtain more context data from template
var tplData = dataFromTemplate();
-
- // Get process definitions etc. from processed data
- Context context = (Context) channel();
- context.swtpmDefinition = Optional.ofNullable(tplData.get("swtpm"))
+ swtpmDefinition = Optional.ofNullable(tplData.get("swtpm"))
.map(d -> new CommandDefinition("swtpm", d)).orElse(null);
- context.qemuDefinition = Optional.ofNullable(tplData.get("qemu"))
+ qemuDefinition = Optional.ofNullable(tplData.get("qemu"))
.map(d -> new CommandDefinition("qemu", d)).orElse(null);
- config.monitorMessages = tplData.get("monitorMessages");
// Files to watch for
Files.deleteIfExists(config.swtpmSocket);
fire(new WatchFile(config.swtpmSocket));
- Files.deleteIfExists(config.monitorSocket);
- fire(new WatchFile(config.monitorSocket));
-
- // Start first
- if (config.vm.useTpm && context.swtpmDefinition != null) {
- startProcess(context, context.swtpmDefinition);
- return;
- }
- startProcess(context, context.qemuDefinition);
} catch (IOException | TemplateException e) {
logger.log(Level.SEVERE, e,
() -> "Cannot configure runner: " + e.getMessage());
@@ -307,12 +314,27 @@ public class Runner extends Component {
return mapper.readValue(out.toString(), JsonNode.class);
}
- private boolean startProcess(Context context, CommandDefinition toStart) {
+ /**
+ * Handle the started event.
+ *
+ * @param event the event
+ */
+ @Handler
+ public void onStarted(Started event) {
+ state.set(State.STARTING);
+ // Start first process
+ if (config.vm.useTpm && swtpmDefinition != null) {
+ startProcess(swtpmDefinition);
+ return;
+ }
+ startProcess(qemuDefinition);
+ }
+
+ private boolean startProcess(CommandDefinition toStart) {
logger.fine(
() -> "Starting process: " + String.join(" ", toStart.command));
fire(new StartProcess(toStart.command)
- .setAssociated(Context.class, context)
- .setAssociated(CommandDefinition.class, toStart), channel());
+ .setAssociated(CommandDefinition.class, toStart));
return true;
}
@@ -324,22 +346,13 @@ public class Runner extends Component {
* @param context the context
*/
@Handler
- public void onFileChanged(FileChanged event, Context context) {
+ public void onFileChanged(FileChanged event) {
if (event.change() == Kind.CREATED
- && event.path()
- .equals(Path.of(config.runtimeDir, "swtpm-sock"))) {
+ && event.path().equals(config.swtpmSocket)) {
// swtpm running, start qemu
- startProcess(context, context.qemuDefinition);
+ startProcess(qemuDefinition);
return;
}
- var monSockPath = Path.of(config.runtimeDir, "monitor.sock");
- if (event.change() == Kind.CREATED
- && event.path().equals(monSockPath)) {
- // qemu running, open socket
- fire(new OpenSocketConnection(
- UnixDomainSocketAddress.of(monSockPath))
- .setAssociated(Context.class, context));
- }
}
/**
@@ -355,34 +368,27 @@ public class Runner extends Component {
"PMD.TooFewBranchesForASwitchStatement" })
public void onProcessStarted(ProcessStarted event, ProcessChannel channel)
throws InterruptedException {
- event.startEvent().associated(Context.class).ifPresent(context -> {
- // Associate the process channel with the general context
- // and with its process definition (both carried over by
- // the start event).
- channel.setAssociated(Context.class, context);
- CommandDefinition procDef
- = event.startEvent().associated(CommandDefinition.class).get();
- channel.setAssociated(CommandDefinition.class, procDef);
+ event.startEvent().associated(CommandDefinition.class)
+ .ifPresent(procDef -> {
+ channel.setAssociated(CommandDefinition.class, procDef);
+ try (var pidFile = Files.newBufferedWriter(
+ Path.of(config.runtimeDir, procDef.name + ".pid"))) {
+ pidFile.write(channel.process().toHandle().pid() + "\n");
+ } catch (IOException e) {
+ throw new UndeclaredThrowableException(e);
+ }
- // Associate the channel with a line collector (one for
- // each stream) for logging the process's output.
- TypedIdKey.associate(channel, 1, new LineCollector().nativeCharset()
- .consumer(line -> logger
- .info(() -> procDef.name() + "(out): " + line)));
- TypedIdKey.associate(channel, 2, new LineCollector().nativeCharset()
- .consumer(line -> logger
- .info(() -> procDef.name() + "(err): " + line)));
-
- // Register the channel in the context.
- switch (procDef.name) {
- case "swtpm":
- context.swtpmChannel = channel;
- break;
- case "qemu":
- context.qemuChannel = channel;
- break;
- }
- });
+ // Associate the channel with a line collector (one for
+ // each stream) for logging the process's output.
+ TypedIdKey.associate(channel, 1,
+ new LineCollector().nativeCharset()
+ .consumer(line -> logger
+ .info(() -> procDef.name() + "(out): " + line)));
+ TypedIdKey.associate(channel, 2,
+ new LineCollector().nativeCharset()
+ .consumer(line -> logger
+ .info(() -> procDef.name() + "(err): " + line)));
+ });
}
/**
@@ -399,16 +405,14 @@ public class Runner extends Component {
}
/**
- * Handle data from qemu monitor connection.
+ * On qemu monitor started.
*
* @param event the event
- * @param channel the channel
+ * @param context the context
*/
@Handler
- public void onInput(Input> event, SocketIOChannel channel) {
- channel.associated(LineCollector.class).ifPresent(collector -> {
- collector.feed(event);
- });
+ public void onQemuMonitorOpened(QemuMonitorOpened event) {
+ state.set(State.RUNNING);
}
/**
@@ -423,84 +427,19 @@ public class Runner extends Component {
}
/**
- * Check if this is from opening the monitor socket and if true,
- * save the socket in the context and associate the channel with
- * the context. Then send the initial message to the socket.
+ * On stop.
*
* @param event the event
- * @param channel the channel
*/
@Handler
- public void onClientConnected(ClientConnected event,
- SocketIOChannel channel) {
- if (event.openEvent().address() instanceof UnixDomainSocketAddress addr
- && addr.getPath()
- .equals(Path.of(config.runtimeDir, "monitor.sock"))) {
- event.openEvent().associated(Context.class).ifPresent(context -> {
- context.monitorChannel = channel;
- channel.setAssociated(Context.class, context);
- channel.setAssociated(LineCollector.class,
- new LineCollector().consumer(line -> {
- monitorLog.fine(() -> "monitor(in): " + line);
- }));
- channel.setAssociated(Writer.class, new ByteBufferWriter(
- channel).nativeCharset());
- writeToMonitor(context,
- config.monitorMessages.get("connect").asText());
- });
- }
- }
-
- /**
- * Called when a connection attempt fails.
- *
- * @param event the event
- * @param channel the channel
- */
- @Handler
- public void onConnectError(ConnectError event, SocketIOChannel channel) {
- if (event.event() instanceof OpenSocketConnection openEvent
- && openEvent.address() instanceof UnixDomainSocketAddress addr
- && addr.getPath()
- .equals(Path.of(config.runtimeDir, "monitor.sock"))) {
- openEvent.associated(Context.class).ifPresent(context -> {
- fire(new Stop());
- });
- }
- }
-
- private void writeToMonitor(Context context, String message) {
- monitorLog.fine(() -> "monitor(out): " + message);
- context.monitorChannel.associated(Writer.class)
- .ifPresent(writer -> {
- try {
- writer.append(message).append('\n').flush();
- } catch (IOException e) {
- // Cannot happen, but...
- logger.log(Level.WARNING, e, () -> e.getMessage());
- }
- });
- }
-
- /**
- * The context.
- */
- private static class Context implements Channel {
- public CommandDefinition swtpmDefinition;
- public CommandDefinition qemuDefinition;
- public ProcessChannel swtpmChannel;
- public ProcessChannel qemuChannel;
- public SocketIOChannel monitorChannel;
-
- @Override
- public Object defaultCriterion() {
- return "ProcMgr";
- }
-
- @Override
- public String toString() {
- return "ProcMgr";
- }
+ public void onStop(Stop event) {
+// Context context = (Context) channel();
+// if (context.qemuChannel != null) {
+// event.suspendHandling();
+// context.suspendedStop = event;
+// writeToMonitor(context,
+// config.monitorMessages.get("powerdown").asText());
+// }
}
/**
@@ -511,7 +450,7 @@ public class Runner extends Component {
public static void main(String[] args) {
// The Runner is the root component
try {
- app = new Runner();
+ var app = new Runner();
// Prepare Stop
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
@@ -529,6 +468,5 @@ public class Runner extends Component {
Logger.getLogger(Runner.class.getName()).log(Level.SEVERE, e,
() -> "Failed to start runner: " + e.getMessage());
}
-
}
}
diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StateController.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StateController.java
new file mode 100644
index 0000000..3709fcb
--- /dev/null
+++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StateController.java
@@ -0,0 +1,54 @@
+/*
+ * 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 .
+ */
+
+package org.jdrupes.vmoperator.runner.qemu;
+
+/**
+ * The context.
+ */
+/* default */ class StateController {
+
+ private Runner runner;
+
+ /**
+ * The state.
+ */
+ enum State {
+ INITIALIZING, STARTING, RUNNING, TERMINATING
+ }
+
+ private State state = State.INITIALIZING;
+
+ public StateController(Runner runner) {
+ this.runner = runner;
+ }
+
+ /**
+ * Sets the state.
+ *
+ * @param state the new state
+ */
+ public void set(State state) {
+ this.state = state;
+ }
+
+ @Override
+ public String toString() {
+ return "StateController [state=" + state + "]";
+ }
+}
diff --git a/org.jdrupes.vmoperator.runner.qemu/templates/Standard-VM-latest.ftl.yaml b/org.jdrupes.vmoperator.runner.qemu/templates/Standard-VM-latest.ftl.yaml
index 0ca6563..9adf4b8 100644
--- a/org.jdrupes.vmoperator.runner.qemu/templates/Standard-VM-latest.ftl.yaml
+++ b/org.jdrupes.vmoperator.runner.qemu/templates/Standard-VM-latest.ftl.yaml
@@ -152,8 +152,3 @@
- [ "-chardev", "spicevmc,id=charredir${ index },name=usbredir" ]
- [ "-device", "usb-redir,id=redir${ index },chardev=charredir${ index }" ]
#list>
-
-
-
-"monitorMessages":
- "connect": '{ "execute": "qmp_capabilities" }'
\ No newline at end of file
diff --git a/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs b/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs
index c8414c5..9b11b65 100644
--- a/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs
+++ b/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs
@@ -1,5 +1,5 @@
#
-#Sun May 28 16:23:45 CEST 2023
+#Tue May 30 22:54:50 CEST 2023
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
@@ -286,9 +286,9 @@ org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=1
org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement=common_lines
org.eclipse.jdt.core.formatter.lineSplit=80
org.eclipse.jdt.core.compiler.source=17
-org.eclipse.jdt.core.formatter.tabulation.size=4
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
+org.eclipse.jdt.core.formatter.tabulation.size=4
org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position=false
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
@@ -304,13 +304,13 @@ org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line
eclipse.preferences.version=1
-org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
+org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
org.eclipse.jdt.core.compiler.compliance=17
org.eclipse.jdt.core.formatter.blank_lines_after_package=1
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
diff --git a/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/LongLoggingManager.java b/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/LongLoggingManager.java
new file mode 100644
index 0000000..7b64c9f
--- /dev/null
+++ b/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/LongLoggingManager.java
@@ -0,0 +1,42 @@
+/*
+ * 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 .
+ */
+
+package org.jdrupes.vmoperator.util;
+
+import java.util.logging.LogManager;
+
+public class LongLoggingManager extends LogManager {
+ private static LongLoggingManager instance;
+
+ public LongLoggingManager() {
+ instance = this;
+ }
+
+ @Override
+ public void reset() {
+ /* don't reset yet. */
+ }
+
+ private void reset0() {
+ super.reset();
+ }
+
+ public static void resetFinally() {
+ instance.reset0();
+ }
+}
\ No newline at end of file