diff --git a/org.jdrupes.vmoperator.runner.qemu/.settings/net.sf.jautodoc.prefs b/org.jdrupes.vmoperator.runner.qemu/.settings/net.sf.jautodoc.prefs
new file mode 100644
index 0000000..6f3b6d4
--- /dev/null
+++ b/org.jdrupes.vmoperator.runner.qemu/.settings/net.sf.jautodoc.prefs
@@ -0,0 +1,8 @@
+add_header=true
+eclipse.preferences.version=1
+header_text=/*\n * VM-Operator\n * Copyright (C) 2023 Michael N. Lipp\n * \n * This program is free software\: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n */
+project_specific_settings=true
+replacements=\n\n\nReturns the\nSets the\nAdds the\nEdits the\nRemoves the\nInits the\nParses the\nCreates the\nBuilds the\nChecks if is\nPrints the\nChecks for\n\n\n
+visibility_package=false
+visibility_private=false
+visibility_protected=false
diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CpuController.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CpuController.java
new file mode 100644
index 0000000..7bdae2f
--- /dev/null
+++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CpuController.java
@@ -0,0 +1,169 @@
+/*
+ * 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.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Level;
+import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand;
+import static org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand.Command.SET_CURRENT_CPUS;
+import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommandCompleted;
+import org.jdrupes.vmoperator.runner.qemu.events.MonitorResult;
+import org.jgrapes.core.Channel;
+import org.jgrapes.core.Component;
+import org.jgrapes.core.annotation.Handler;
+
+/**
+ * The Class CpuController.
+ */
+@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
+public class CpuController extends Component {
+
+ private static ObjectMapper mapper;
+ private static JsonNode queryHotpluggableCpus;
+
+ private final QemuMonitor monitor;
+ private Integer desiredCpus;
+
+ /**
+ * Instantiates a new CPU controller.
+ *
+ * @param componentChannel the component channel
+ * @param qemuMonitor
+ */
+ @SuppressWarnings("PMD.AssignmentToNonFinalStatic")
+ public CpuController(Channel componentChannel, QemuMonitor monitor) {
+ super(componentChannel);
+ if (mapper == null) {
+ mapper = new ObjectMapper();
+ try {
+ queryHotpluggableCpus = mapper.readValue(
+ "{\"execute\":\"query-hotpluggable-cpus\",\"arguments\":{}}",
+ JsonNode.class);
+ } catch (IOException e) {
+ logger.log(Level.SEVERE, e,
+ () -> "Cannot initialize class: " + e.getMessage());
+ }
+ }
+ this.monitor = monitor;
+ }
+
+ /**
+ * On monitor command.
+ *
+ * @param event the event
+ */
+ @Handler
+ public void onMonitorCommand(MonitorCommand event) {
+ if (event.command() != SET_CURRENT_CPUS) {
+ return;
+ }
+ desiredCpus = (Integer) event.arguments()[0];
+ monitor.sendToMonitor(queryHotpluggableCpus);
+ }
+
+ /**
+ * On monitor result.
+ *
+ * @param result the result
+ * @param channel the channel
+ */
+ @Handler
+ public void onMonitorResult(MonitorResult result) {
+ if (!result.executed()
+ .equals(queryHotpluggableCpus.get("execute").asText())
+ || desiredCpus == null) {
+ return;
+ }
+
+ // Sort
+ List used = new ArrayList<>();
+ List unused = new ArrayList<>();
+ for (var itr = result.returned().iterator(); itr.hasNext();) {
+ ObjectNode cpu = (ObjectNode) itr.next();
+ if (cpu.has("qom-path")) {
+ used.add(cpu);
+ } else {
+ unused.add(cpu);
+ }
+ }
+
+ // Process
+ int diff = used.size() - desiredCpus;
+ diff = addCpus(used, unused, diff);
+ diff = deleteCpus(used, diff);
+ fire(new MonitorCommandCompleted(SET_CURRENT_CPUS, desiredCpus + diff));
+ desiredCpus = null;
+ }
+
+ private int addCpus(List used, List unused,
+ int diff) {
+ Set usedIds = new HashSet<>();
+ for (var cpu : used) {
+ String qomPath = cpu.get("qom-path").asText();
+ if (qomPath.startsWith("/machine/peripheral/cpu-")) {
+ usedIds
+ .add(qomPath.substring(qomPath.lastIndexOf('/') + 1));
+ }
+ }
+ int nextId = 1;
+ while (diff < 0 && !unused.isEmpty()) {
+ ObjectNode cmd = mapper.createObjectNode();
+ cmd.put("execute", "device_add");
+ ObjectNode args = mapper.createObjectNode();
+ cmd.set("arguments", args);
+ args.setAll((ObjectNode) (unused.get(0).get("props").deepCopy()));
+ args.set("driver", unused.get(0).get("type"));
+ String id;
+ do {
+ id = "cpu-" + nextId++;
+ } while (usedIds.contains(id));
+ args.put("id", id);
+ monitor.sendToMonitor(cmd);
+ unused.remove(0);
+ diff += 1;
+ }
+ return diff;
+ }
+
+ private int deleteCpus(List used, int diff) {
+ while (diff > 0 && !used.isEmpty()) {
+ ObjectNode cpu = used.remove(0);
+ String qomPath = cpu.get("qom-path").asText();
+ if (!qomPath.startsWith("/machine/peripheral/cpu-")) {
+ continue;
+ }
+ String id = qomPath.substring(qomPath.lastIndexOf('/') + 1);
+ ObjectNode cmd = mapper.createObjectNode();
+ cmd.put("execute", "device_del");
+ ObjectNode args = mapper.createObjectNode();
+ cmd.set("arguments", args);
+ args.put("id", id);
+ monitor.sendToMonitor(cmd);
+ diff -= 1;
+ }
+ return diff;
+ }
+}
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
index d9ccf8b..8b21e47 100644
--- 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
@@ -19,7 +19,10 @@
package org.jdrupes.vmoperator.runner.qemu;
import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.UndeclaredThrowableException;
@@ -27,10 +30,14 @@ 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.LinkedList;
+import java.util.Queue;
import java.util.logging.Level;
-import java.util.logging.Logger;
+import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand;
+import static org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand.Command.CONTINUE;
+import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommandCompleted;
+import org.jdrupes.vmoperator.runner.qemu.events.MonitorReady;
+import org.jdrupes.vmoperator.runner.qemu.events.MonitorResult;
import org.jgrapes.core.Channel;
import org.jgrapes.core.Component;
import org.jgrapes.core.Components;
@@ -58,34 +65,49 @@ import org.jgrapes.util.events.WatchFile;
* If the log level for this class is set to fine, the messages
* exchanged on the monitor socket are logged.
*/
+@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
public class QemuMonitor extends Component {
- @SuppressWarnings({ "PMD.FieldNamingConventions",
- "PMD.VariableNamingConventions" })
- private static final Logger logger
- = Logger.getLogger(QemuMonitor.class.getName());
+ private static ObjectMapper mapper;
+ private static JsonNode connect;
+ private static JsonNode cont;
+ private static JsonNode powerdown;
@SuppressWarnings("PMD.UseConcurrentHashMap")
- private final Map monitorMessages = new HashMap<>(Map.of(
- "connect", "{ \"execute\": \"qmp_capabilities\" }",
- "powerdown", "{ \"execute\": \"system_powerdown\" }",
- "setBalloon", "{ \"execute\": \"balloon\", \"arguments\": "
- + "{ \"value\": %d } }"));
-
private Path socketPath;
private int powerdownTimeout;
private SocketIOChannel monitorChannel;
+ private final Queue executing = new LinkedList<>();
private Stop suspendedStop;
-
private Timer powerdownTimer;
/**
* Instantiates a new qemu monitor.
*
* @param componentChannel the component channel
+ * @param mapper
+ * @throws JsonProcessingException
+ * @throws JsonMappingException
*/
- public QemuMonitor(Channel componentChannel) {
+ @SuppressWarnings("PMD.AssignmentToNonFinalStatic")
+ public QemuMonitor(Channel componentChannel) throws IOException {
super(componentChannel);
+ if (mapper == null) {
+ mapper = new ObjectMapper();
+ try {
+ connect = mapper.readValue("{ \"execute\": "
+ + "\"qmp_capabilities\" }", JsonNode.class);
+ cont = mapper.readValue("{ \"execute\": "
+ + "\"cont\" }", JsonNode.class);
+ powerdown = mapper.readValue("{\"execute\": "
+ + "\"query-cpus-fast\",\"arguments\":{}}", JsonNode.class);
+ } catch (IOException e) {
+ logger.log(Level.SEVERE, e,
+ () -> "Cannot initialize class: " + e.getMessage());
+ }
+ }
+ attach(new RamController(channel(), this));
+ attach(new CpuController(channel(), this));
}
/**
@@ -158,7 +180,7 @@ public class QemuMonitor extends Component {
throw new UndeclaredThrowableException(e);
}
}));
- writeToMonitor(monitorMessages.get("connect"));
+ sendToMonitor(connect);
});
}
@@ -175,26 +197,30 @@ public class QemuMonitor extends Component {
});
}
- private void writeToMonitor(String message) {
- logger.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());
+ /* default */ void sendToMonitor(JsonNode message) {
+ String asText;
+ try {
+ asText = mapper.writeValueAsString(message);
+ } catch (JsonProcessingException e) {
+ logger.log(Level.SEVERE, e,
+ () -> "Cannot serialize Json: " + e.getMessage());
+ return;
+ }
+ logger.fine(() -> "monitor(out): " + asText);
+ synchronized (executing) {
+ if (message.has("execute")) {
+ executing.add(message.get("execute").asText());
}
- });
- }
+ monitorChannel.associated(Writer.class).ifPresent(writer -> {
+ try {
+ writer.append(asText).append('\n').flush();
+ } catch (IOException e) {
+ // Cannot happen, but...
+ logger.log(Level.WARNING, e, () -> e.getMessage());
+ }
+ });
- /**
- * Sets the current ram.
- *
- * @param amount the new current ram
- */
- public void setCurrentRam(Number amount) {
- writeToMonitor(
- String.format(monitorMessages.get("setBalloon"), amount));
+ }
}
/**
@@ -217,10 +243,18 @@ public class QemuMonitor extends Component {
throws IOException {
logger.fine(() -> "monitor(in): " + line);
try {
- var response
- = ((Runner) channel()).mapper().readValue(line, JsonNode.class);
+ var response = mapper.readValue(line, ObjectNode.class);
if (response.has("QMP")) {
- fire(new QemuMonitorAvailable());
+ fire(new MonitorReady());
+ return;
+ }
+ if (response.has("return")) {
+ String executed = executing.poll();
+ logger.fine(
+ () -> String.format("(Previous \"monitor(in)\" is result "
+ + "from executing %s)", executed));
+ fire(new MonitorResult(executed, response));
+ return;
}
} catch (JsonProcessingException e) {
throw new IOException(e);
@@ -248,6 +282,20 @@ public class QemuMonitor extends Component {
});
}
+ /**
+ * On monitor command.
+ *
+ * @param event the event
+ */
+ @Handler
+ public void onMonitorCommand(MonitorCommand event) {
+ if (event.command() != CONTINUE) {
+ return;
+ }
+ sendToMonitor(cont);
+ fire(new MonitorCommandCompleted(event.command(), null));
+ }
+
/**
* Shutdown the VM.
*
@@ -259,7 +307,7 @@ public class QemuMonitor extends Component {
// We have a connection to Qemu, attempt ACPI shutdown.
event.suspendHandling();
suspendedStop = event;
- writeToMonitor(monitorMessages.get("powerdown"));
+ sendToMonitor(powerdown);
// Schedule timer as fallback
powerdownTimer = Components.schedule(t -> {
diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/RamController.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/RamController.java
new file mode 100644
index 0000000..993601f
--- /dev/null
+++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/RamController.java
@@ -0,0 +1,82 @@
+/*
+ * 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.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.util.logging.Level;
+import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand;
+import static org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand.Command.SET_CURRENT_RAM;
+import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommandCompleted;
+import org.jgrapes.core.Channel;
+import org.jgrapes.core.Component;
+import org.jgrapes.core.annotation.Handler;
+
+/**
+ * The Class CpuController.
+ */
+public class RamController extends Component {
+
+ private static ObjectMapper mapper;
+ private static JsonNode setBalloon;
+ private final QemuMonitor monitor;
+
+ /**
+ * Instantiates a new CPU controller.
+ *
+ * @param componentChannel the component channel
+ * @param qemuMonitor
+ */
+ @SuppressWarnings("PMD.AssignmentToNonFinalStatic")
+ public RamController(Channel componentChannel, QemuMonitor monitor) {
+ super(componentChannel);
+ if (mapper == null) {
+ mapper = new ObjectMapper();
+ try {
+ setBalloon = mapper.readValue("{ \"execute\": \"balloon\", "
+ + "\"arguments\": " + "{ \"value\": 0 } }", JsonNode.class);
+ } catch (IOException e) {
+ logger.log(Level.SEVERE, e,
+ () -> "Cannot initialize class: " + e.getMessage());
+ }
+ }
+ this.monitor = monitor;
+ }
+
+ /**
+ * On monitor command.
+ *
+ * @param event the event
+ */
+ @Handler
+ public void onMonitorCommand(MonitorCommand event) {
+ if (event.command() != SET_CURRENT_RAM) {
+ return;
+ }
+ var msg = setBalloon.deepCopy();
+ ((ObjectNode) msg.get("arguments")).put("value",
+ (BigInteger) event.arguments()[0]);
+ monitor.sendToMonitor(msg);
+ fire(new MonitorCommandCompleted(event.command(), null));
+ }
+
+}
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 7ad48a0..97a87cb 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
@@ -51,6 +51,12 @@ import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.jdrupes.vmoperator.runner.qemu.StateController.State;
+import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand;
+import static org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand.Command.CONTINUE;
+import static org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand.Command.SET_CURRENT_CPUS;
+import static org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand.Command.SET_CURRENT_RAM;
+import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommandCompleted;
+import org.jdrupes.vmoperator.runner.qemu.events.MonitorReady;
import org.jdrupes.vmoperator.util.ExtendedObjectWrapper;
import org.jdrupes.vmoperator.util.FsdUtils;
import org.jgrapes.core.Component;
@@ -95,6 +101,7 @@ import org.jgrapes.util.events.WatchFile;
* state "Start swtpm" as swtpm
* state "Start qemu" as qemu
* state "Open monitor" as monitor
+ * state "Configure" as configure
* state success <>
* state error <>
*
@@ -108,8 +115,11 @@ import org.jgrapes.util.events.WatchFile;
* qemu --> monitor : FileChanged[monitor socket created]
*
* monitor: entry/fire OpenSocketConnection
- * monitor --> success: ClientConnected[for monitor]/set balloon value
+ * monitor --> configure: ClientConnected[for monitor]
* monitor -> error: ConnectError[for monitor]
+ *
+ * configure: entry/fire configuration commands
+ * configure --> success: last completed/fire cont command
* }
*
* Initializing --> which: Started
@@ -156,7 +166,7 @@ public class Runner extends Component {
private static final String SAVED_TEMPLATE = "VM.ftl.yaml";
private static final String FW_VARS = "fw-vars.fd";
- private final ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
+ private final ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory());
private final JsonNode defaults;
@SuppressWarnings("PMD.UseConcurrentHashMap")
private Configuration config = new Configuration();
@@ -175,11 +185,11 @@ public class Runner extends Component {
@SuppressWarnings("PMD.SystemPrintln")
public Runner(CommandLine cmdLine) throws IOException {
state = new StateController(this);
- mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
+ yamlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
false);
// Get defaults
- defaults = mapper.readValue(
+ defaults = yamlMapper.readValue(
Runner.class.getResourceAsStream("defaults.yaml"), JsonNode.class);
// Configure freemarker library
@@ -212,10 +222,6 @@ public class Runner extends Component {
fire(new WatchFile(config.toPath()));
}
- /* default */ ObjectMapper mapper() {
- return mapper;
- }
-
/**
* On configuration update.
*
@@ -235,7 +241,7 @@ public class Runner extends Component {
private void processInitialConfiguration(
Map runnerConfiguration) {
try {
- config = mapper.convertValue(runnerConfiguration,
+ config = yamlMapper.convertValue(runnerConfiguration,
Configuration.class);
if (!config.check()) {
// Invalid configuration, not used, problems already logged.
@@ -322,7 +328,7 @@ public class Runner extends Component {
var fmTemplate = fmConfig.getTemplate(templatePath.toString());
StringWriter out = new StringWriter();
fmTemplate.process(model, out);
- return mapper.readValue(out.toString(), JsonNode.class);
+ return yamlMapper.readValue(out.toString(), JsonNode.class);
}
@SuppressWarnings("unchecked")
@@ -337,7 +343,21 @@ public class Runner extends Component {
synchronized (state) {
config.vm.currentRam = cr;
if (state.get() == State.RUNNING) {
- qemuMonitor.setCurrentRam(cr);
+ fire(new MonitorCommand(SET_CURRENT_RAM, cr));
+ }
+ }
+ });
+ Optional.ofNullable((Map) conf.get("vm"))
+ .map(vm -> vm.get("currentCpus"))
+ .map(v -> v instanceof Number number ? number.intValue() : null)
+ .ifPresent(cpus -> {
+ if (config.vm.currentCpus == cpus) {
+ return;
+ }
+ synchronized (state) {
+ config.vm.currentCpus = cpus;
+ if (state.get() == State.RUNNING) {
+ fire(new MonitorCommand(SET_CURRENT_CPUS, cpus));
}
}
});
@@ -476,10 +496,27 @@ public class Runner extends Component {
* @param event the event
*/
@Handler
- public void onQemuMonitorAvailable(QemuMonitorAvailable event) {
+ public void onMonitorReady(MonitorReady event) {
synchronized (state) {
- Optional.ofNullable(config.vm.currentRam)
- .ifPresent(qemuMonitor::setCurrentRam);
+ Optional.ofNullable(config.vm.currentRam).ifPresent(ram -> {
+ fire(new MonitorCommand(SET_CURRENT_RAM, ram));
+ });
+ Optional.ofNullable(config.vm.currentCpus).ifPresent(cpus -> {
+ fire(new MonitorCommand(SET_CURRENT_CPUS, cpus));
+ });
+ }
+ }
+
+ /**
+ * On monitor command completed.
+ *
+ * @param event the event
+ */
+ @Handler
+ public void onMonitorCommandCompleted(MonitorCommandCompleted event) {
+ if (state.get() != State.RUNNING
+ && event.command() == SET_CURRENT_CPUS) {
+ fire(new MonitorCommand(CONTINUE));
state.set(State.RUNNING);
}
}
diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorCommand.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorCommand.java
new file mode 100644
index 0000000..9729d88
--- /dev/null
+++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorCommand.java
@@ -0,0 +1,83 @@
+/*
+ * 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.events;
+
+import java.util.Arrays;
+import org.jgrapes.core.Channel;
+import org.jgrapes.core.Components;
+import org.jgrapes.core.Event;
+
+/**
+ * A command to be executed by the monitor.
+ */
+public class MonitorCommand extends Event {
+
+ /**
+ * The available commands.
+ */
+ public enum Command {
+ CONTINUE, SET_CURRENT_CPUS, SET_CURRENT_RAM
+ }
+
+ private final Command command;
+ private final Object[] arguments;
+
+ /**
+ * Instantiates a new monitor command.
+ *
+ * @param command the command
+ * @param arguments the arguments
+ */
+ public MonitorCommand(Command command, Object... arguments) {
+ super();
+ this.command = command;
+ this.arguments = Arrays.copyOf(arguments, arguments.length);
+ }
+
+ /**
+ * Gets the command.
+ *
+ * @return the command
+ */
+ public Command command() {
+ return command;
+ }
+
+ /**
+ * Gets the arguments.
+ *
+ * @return the arguments
+ */
+ public Object[] arguments() {
+ return Arrays.copyOf(arguments, arguments.length);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(Components.objectName(this))
+ .append(" [").append(command);
+ if (channels() != null) {
+ builder.append(", channels=");
+ builder.append(Channel.toString(channels()));
+ }
+ builder.append(']');
+ return builder.toString();
+ }
+}
diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorCommandCompleted.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorCommandCompleted.java
new file mode 100644
index 0000000..5d3c844
--- /dev/null
+++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorCommandCompleted.java
@@ -0,0 +1,76 @@
+/*
+ * 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.events;
+
+import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand.Command;
+import org.jgrapes.core.Channel;
+import org.jgrapes.core.Components;
+import org.jgrapes.core.Event;
+
+/**
+ * Signals the completion of a monitor command.
+ */
+public class MonitorCommandCompleted extends Event {
+
+ private final Command command;
+ private final Object result;
+
+ /**
+ * Instantiates a new monitor command.
+ *
+ * @param command the command
+ * @param arguments the arguments
+ */
+ public MonitorCommandCompleted(Command command, Object result) {
+ super();
+ this.command = command;
+ this.result = result;
+ }
+
+ /**
+ * Gets the command.
+ *
+ * @return the command
+ */
+ public Command command() {
+ return command;
+ }
+
+ /**
+ * Gets the result.
+ *
+ * @return the arguments
+ */
+ public Object result() {
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(Components.objectName(this))
+ .append(" [").append(command);
+ if (channels() != null) {
+ builder.append(", channels=");
+ builder.append(Channel.toString(channels()));
+ }
+ builder.append(']');
+ return builder.toString();
+ }
+}
diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorEvent.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorEvent.java
new file mode 100644
index 0000000..492df65
--- /dev/null
+++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorEvent.java
@@ -0,0 +1,55 @@
+/*
+ * 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.events;
+
+import org.jgrapes.core.Event;
+
+// TODO: Auto-generated Javadoc
+/**
+ * Signals the reception of an event from the monitor.
+ */
+public class MonitorEvent extends Event {
+
+ /**
+ * The kind of monitor event.
+ */
+ public enum Kind {
+ READY
+ }
+
+ private final Kind kind;
+
+ /**
+ * Instantiates a new monitor event.
+ *
+ * @param kind the kind
+ */
+ public MonitorEvent(Kind kind) {
+ this.kind = kind;
+ }
+
+ /**
+ * Returns the kind of event.
+ *
+ * @return the kind
+ */
+ public Kind kind() {
+ return kind;
+ }
+}
diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/QemuMonitorAvailable.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorReady.java
similarity index 79%
rename from org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/QemuMonitorAvailable.java
rename to org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorReady.java
index f1ba921..8ff1f25 100644
--- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/QemuMonitorAvailable.java
+++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorReady.java
@@ -16,14 +16,19 @@
* along with this program. If not, see .
*/
-package org.jdrupes.vmoperator.runner.qemu;
-
-import org.jgrapes.core.Event;
+package org.jdrupes.vmoperator.runner.qemu.events;
/**
* Signals that the connection to the Qemu monitor socket has been
* established successfully.
*/
-public class QemuMonitorAvailable extends Event {
+public class MonitorReady extends MonitorEvent {
+
+ /**
+ * Instantiates a new monitor ready.
+ */
+ public MonitorReady() {
+ super(Kind.READY);
+ }
}
diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorResult.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorResult.java
new file mode 100644
index 0000000..97275dd
--- /dev/null
+++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorResult.java
@@ -0,0 +1,58 @@
+/*
+ * 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.events;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.jgrapes.core.Event;
+
+/**
+ * Signals the reception of a result from the monitor.
+ */
+public class MonitorResult extends Event {
+
+ private final String executed;
+ private final JsonNode returned;
+
+ /**
+ * Instantiates a new monitor result.
+ *
+ * @param executed the command executed
+ * @param returned the values returned
+ */
+ public MonitorResult(String executed, JsonNode response) {
+ this.executed = executed;
+ this.returned = response.get("return");
+ }
+
+ /**
+ * Return the executed command.
+ *
+ * @return the string
+ */
+ public String executed() {
+ return executed;
+ }
+
+ /**
+ * Return the values returned.
+ */
+ public JsonNode returned() {
+ return returned;
+ }
+}
diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/package-info.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/package-info.java
new file mode 100644
index 0000000..d1a1894
--- /dev/null
+++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/package-info.java
@@ -0,0 +1 @@
+package org.jdrupes.vmoperator.runner.qemu.events;
\ No newline at end of file
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 741a3ec..8b49f25 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
@@ -23,6 +23,8 @@
# Useful links:
# - https://joonas.fi/2021/02/uefi-pc-boot-process-and-uefi-with-qemu/
"arguments":
+ # Mandatory
+ - "-S"
# Qemu configuration
- "-no-user-config"
# * https://www.kernel.org/doc/Documentation/virtual/kvm/api.txt
@@ -82,7 +84,7 @@
#if>
- [ "-cpu", "${ vm.cpuModel }" ]
<#if vm.maximumCpus gt 1>
- - [ "-smp", "${ vm.currentCpus },maxcpus=${ vm.maximumCpus }\
+ - [ "-smp", "1,maxcpus=${ vm.maximumCpus }\
<#if vm.cpuSockets gt 0>,sockets=${ vm.cpuSockets }#if>\
<#if vm.diesPerSocket gt 0>,cores=${ vm.diesPerSocket }#if>\
<#if vm.coresPerDie gt 0>,cores=${ vm.coresPerDie }#if>\
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 f458908..4dda198 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 @@
#
-#Thu Jul 27 18:33:49 CEST 2023
+#Sun Jul 30 18:47:26 CEST 2023
org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert