diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/AgentConnector.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/AgentConnector.java index 3320a04..11b840c 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/AgentConnector.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/AgentConnector.java @@ -24,7 +24,6 @@ import java.util.List; import org.jdrupes.vmoperator.runner.qemu.events.VserportChangeEvent; import org.jgrapes.core.Channel; import org.jgrapes.core.annotation.Handler; -import org.jgrapes.util.events.ConfigurationUpdate; /** * A component that handles the communication with an agent @@ -48,16 +47,14 @@ public abstract class AgentConnector extends QemuConnector { } /** - * As the initial 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. + * Extracts the channel id and the socket path from the QEMU + * command line. * * @param command the command * @param chardev the chardev */ @SuppressWarnings("PMD.CognitiveComplexity") - protected void configure(List command, String chardev) { + protected void configureConnection(List command, String chardev) { Path socketPath = null; for (var arg : command) { if (arg.startsWith("virtserialport,") @@ -82,9 +79,9 @@ public abstract class AgentConnector extends QemuConnector { + " missing in runner template."); return; } - super.configure(socketPath); logger.fine(() -> getClass().getSimpleName() + " configured with" + " channelId=" + channelId); + super.configure(socketPath); } /** diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/DisplayController.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/DisplayController.java index f1b2c53..e0d0a89 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/DisplayController.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/DisplayController.java @@ -29,7 +29,6 @@ import org.jdrupes.vmoperator.runner.qemu.commands.QmpSetDisplayPassword; import org.jdrupes.vmoperator.runner.qemu.commands.QmpSetPasswordExpiry; import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu; import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand; -import org.jdrupes.vmoperator.runner.qemu.events.QmpConfigured; import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.RunState; import org.jdrupes.vmoperator.runner.qemu.events.VmopAgentConnected; import org.jdrupes.vmoperator.runner.qemu.events.VmopAgentLogIn; @@ -50,6 +49,7 @@ public class DisplayController extends Component { private String currentPassword; private String protocol; private final Path configDir; + private boolean canBeUpdated; private boolean vmopAgentConnected; private String loggedInUser; @@ -84,19 +84,7 @@ public class DisplayController extends Component { if (event.runState() == RunState.STARTING) { configurePassword(); } - } - - /** - * When the monitor is ready, send QEMU its initial configuration. - * - * @param event the event - */ - @Handler - public void onQmpConfigured(QmpConfigured event) { - if (pendingConfig != null) { - rep.fire(new ConfigureQemu(pendingConfig, state)); - pendingConfig = null; - } + canBeUpdated = true; } /** @@ -128,7 +116,8 @@ public class DisplayController extends Component { @Handler @SuppressWarnings("PMD.EmptyCatchBlock") public void onFileChanged(FileChanged event) { - if (event.path().equals(configDir.resolve(DisplaySecret.PASSWORD))) { + if (event.path().equals(configDir.resolve(DisplaySecret.PASSWORD)) + && canBeUpdated) { configurePassword(); } } diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/QemuConnector.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/QemuConnector.java index d3ff86e..2e1dbfa 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/QemuConnector.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/QemuConnector.java @@ -202,10 +202,9 @@ public abstract class QemuConnector extends Component { * Called when a connection attempt fails. * * @param event the event - * @param channel the channel */ @Handler - public void onConnectError(ConnectError event, SocketIOChannel channel) { + public void onConnectError(ConnectError event) { event.event().associated(this, getClass()).ifPresent(qc -> { rep.fire(new Stop()); }); 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 4be0aff..21c3a0f 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 @@ -164,12 +164,15 @@ public class QemuMonitor extends QemuConnector { @Handler @SuppressWarnings("PMD.AvoidSynchronizedStatement") public void onMonitorCommand(MonitorCommand event) throws IOException { - if (!monitorReady) { + // Check prerequisites + if (!monitorReady && !(event.command() instanceof QmpCapabilities)) { logger.severe(() -> "Premature monitor command (not ready): " + event.command()); rep().fire(new Stop()); return; } + + // Send the command var command = event.command(); logger.fine(() -> "monitor(out): " + command.toString()); String asText; 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 8aa2b7c..6c69191 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 @@ -303,7 +303,10 @@ public class Runner extends Component { } /** - * On configuration update. + * Process the initial configuration. The initial configuration + * and any subsequent updates will be forwarded to other components + * only when the QMP connection is ready + * (see @link #onQmpConfigured(QmpConfigured)). * * @param event the event */ @@ -362,9 +365,9 @@ public class Runner extends Component { // Forward some values to child components qemuMonitor.configure(initialConfig.monitorSocket, initialConfig.vm.powerdownTimeout); - guestAgentClient.configure(qemuDefinition.command, + guestAgentClient.configureConnection(qemuDefinition.command, "guest-agent-socket"); - vmopAgentClient.configure(qemuDefinition.command, + vmopAgentClient.configureConnection(qemuDefinition.command, "vmop-agent-socket"); } catch (IllegalArgumentException | IOException | TemplateException e) { logger.log(Level.SEVERE, e, () -> "Invalid configuration: " @@ -443,6 +446,21 @@ public class Runner extends Component { return yamlMapper.readValue(out.toString(), JsonNode.class); } + /** + * Note ready state and send a {@link ConfigureQemu} event for + * any pending configuration (initial or change). + * + * @param event the event + */ + @Handler + public void onQmpConfigured(QmpConfigured event) { + qmpConfigured = true; + if (pendingConfig != null) { + rep.fire(new ConfigureQemu(pendingConfig, state)); + pendingConfig = null; + } + } + /** * Handle the start event. * @@ -630,19 +648,6 @@ public class Runner extends Component { .ifPresent(lc -> lc.feed(event))); } - /** - * When the monitor is ready, send QEMU its initial configuration. - * - * @param event the event - */ - @Handler - public void onQmpConfigured(QmpConfigured event) { - if (pendingConfig != null) { - rep.fire(new ConfigureQemu(pendingConfig, state)); - pendingConfig = null; - } - } - /** * Whenever a new QEMU configuration is available, check if it * is supposed to trigger a reset.