From eac113c640340c7edff88a8114d2e0380be09304 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Mon, 31 Jul 2023 17:59:16 +0200 Subject: [PATCH] Support for updateing CPUs and RAM. --- .../.settings/net.sf.jautodoc.prefs | 8 + .../vmoperator/runner/qemu/CpuController.java | 169 ++++++++++++++++++ .../vmoperator/runner/qemu/QemuMonitor.java | 124 +++++++++---- .../vmoperator/runner/qemu/RamController.java | 82 +++++++++ .../vmoperator/runner/qemu/Runner.java | 65 +++++-- .../runner/qemu/events/MonitorCommand.java | 83 +++++++++ .../qemu/events/MonitorCommandCompleted.java | 76 ++++++++ .../runner/qemu/events/MonitorEvent.java | 55 ++++++ .../MonitorReady.java} | 13 +- .../runner/qemu/events/MonitorResult.java | 58 ++++++ .../runner/qemu/events/package-info.java | 1 + .../templates/Standard-VM-latest.ftl.yaml | 4 +- .../.settings/org.eclipse.jdt.core.prefs | 2 +- 13 files changed, 682 insertions(+), 58 deletions(-) create mode 100644 org.jdrupes.vmoperator.runner.qemu/.settings/net.sf.jautodoc.prefs create mode 100644 org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CpuController.java create mode 100644 org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/RamController.java create mode 100644 org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorCommand.java create mode 100644 org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorCommandCompleted.java create mode 100644 org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorEvent.java rename org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/{QemuMonitorAvailable.java => events/MonitorReady.java} (79%) create mode 100644 org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorResult.java create mode 100644 org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/package-info.java 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 @@ - [ "-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 vm.diesPerSocket gt 0>,cores=${ vm.diesPerSocket }\ <#if vm.coresPerDie gt 0>,cores=${ vm.coresPerDie }\ 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