From ec8152bd518572517f8f68cac63fdb2dd40dbb8f Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Mon, 17 Feb 2025 20:47:00 +0100 Subject: [PATCH] Add booted state. --- .../runner/qemu/GuestAgentClient.java | 5 -- .../vmoperator/runner/qemu/Runner.java | 60 ++++++++++++------- .../vmoperator/runner/qemu/StatusUpdater.java | 15 +++-- .../runner/qemu/events/RunnerStateChange.java | 23 ++++++- 4 files changed, 70 insertions(+), 33 deletions(-) diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/GuestAgentClient.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/GuestAgentClient.java index 4d1c764..f3928f5 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/GuestAgentClient.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/GuestAgentClient.java @@ -33,7 +33,6 @@ import java.util.logging.Level; import org.jdrupes.vmoperator.runner.qemu.commands.QmpCommand; import org.jdrupes.vmoperator.runner.qemu.commands.QmpGuestGetOsinfo; import org.jdrupes.vmoperator.runner.qemu.events.GuestAgentCommand; -import org.jdrupes.vmoperator.runner.qemu.events.MonitorReady; import org.jdrupes.vmoperator.runner.qemu.events.OsinfoEvent; import org.jdrupes.vmoperator.runner.qemu.events.VserportChangeEvent; import org.jgrapes.core.Channel; @@ -188,10 +187,6 @@ public class GuestAgentClient extends Component { logger.fine(() -> "guest agent(in): " + line); try { var response = mapper.readValue(line, ObjectNode.class); - if (response.has("QMP")) { - rep.fire(new MonitorReady()); - return; - } if (response.has("return") || response.has("error")) { QmpCommand executed = executing.poll(); logger.fine( 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 c8b9f44..b258e1a 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 @@ -61,6 +61,7 @@ import org.jdrupes.vmoperator.runner.qemu.commands.QmpReset; import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu; import org.jdrupes.vmoperator.runner.qemu.events.Exit; import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand; +import org.jdrupes.vmoperator.runner.qemu.events.OsinfoEvent; import org.jdrupes.vmoperator.runner.qemu.events.QmpConfigured; import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange; import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.RunState; @@ -619,8 +620,8 @@ public class Runner extends Component { } /** - * On monitor ready. - * + * When the monitor is ready, send QEMU its initial configuration. + * * @param event the event */ @Handler @@ -629,28 +630,14 @@ public class Runner extends Component { } /** - * On configure qemu. - * - * @param event the event - */ - @Handler(priority = -1000) - public void onConfigureQemuFinal(ConfigureQemu event) { - if (state == RunState.STARTING) { - fire(new MonitorCommand(new QmpCont())); - state = RunState.RUNNING; - rep.fire(new RunnerStateChange(state, "VmStarted", - "Qemu has been configured and is continuing")); - } - } - - /** - * On configure qemu. + * Whenever a new QEMU configuration is available, check if it + * is supposed to trigger a reset. * * @param event the event */ @Handler public void onConfigureQemu(ConfigureQemu event) { - if (state == RunState.RUNNING) { + if (state.vmActive()) { if (resetCounter != null && event.configuration().resetCounter != null && event.configuration().resetCounter > resetCounter) { @@ -660,6 +647,36 @@ public class Runner extends Component { } } + /** + * As last step when handling a new configuration, check if + * QEMU is suspended after startup and should be continued. + * + * @param event the event + */ + @Handler(priority = -1000) + public void onConfigureQemuFinal(ConfigureQemu event) { + if (state == RunState.STARTING) { + state = RunState.BOOTING; + fire(new MonitorCommand(new QmpCont())); + rep.fire(new RunnerStateChange(state, "VmStarted", + "Qemu has been configured and is continuing")); + } + } + + /** + * Receiving the OSinfo means that the OS has been booted. + * + * @param event the event + */ + @Handler + public void onOsinfo(OsinfoEvent event) { + if (state == RunState.BOOTING) { + state = RunState.BOOTED; + rep.fire(new RunnerStateChange(state, "VmBooted", + "The VM has started the guest agent.")); + } + } + /** * On process exited. * @@ -675,6 +692,7 @@ public class Runner extends Component { mayBeStartQemu(QemuPreps.CloudInit); return; } + // No other process(es) may exit during startup if (state == RunState.STARTING) { logger.severe(() -> "Process " + procDef.name @@ -683,7 +701,9 @@ public class Runner extends Component { rep.fire(new Stop()); return; } - if (procDef.equals(qemuDefinition) && state == RunState.RUNNING) { + + // No processes may exit while the VM is running normally + if (procDef.equals(qemuDefinition) && state.vmActive()) { rep.fire(new Exit(event.exitValue())); } logger.info(() -> "Process " + procDef.name diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java index d4548bf..f9644c8 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java @@ -1,6 +1,6 @@ /* * VM-Operator - * Copyright (C) 2023,2024 Michael N. Lipp + * Copyright (C) 2023,2025 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 @@ -31,7 +31,6 @@ import io.kubernetes.client.openapi.JSON; import io.kubernetes.client.openapi.models.EventsV1Event; import java.io.IOException; import java.math.BigDecimal; -import java.util.Set; import java.util.logging.Level; import static org.jdrupes.vmoperator.common.Constants.APP_NAME; import static org.jdrupes.vmoperator.common.Constants.VM_OP_GROUP; @@ -66,9 +65,6 @@ public class StatusUpdater extends VmDefUpdater { private static final ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule()); - private static final Set RUNNING_STATES - = Set.of(RunState.RUNNING, RunState.TERMINATING); - private long observedGeneration; private boolean guestShutdownStops; private boolean shutdownByGuest; @@ -186,16 +182,23 @@ public class StatusUpdater extends VmDefUpdater { } vmStub.updateStatus(vmDef, from -> { JsonObject status = from.statusJson(); - boolean running = RUNNING_STATES.contains(event.runState()); + boolean running = event.runState().vmRunning(); updateCondition(vmDef, vmDef.statusJson(), "Running", running, event.reason(), event.message()); + updateCondition(vmDef, vmDef.statusJson(), "Booted", + event.runState() == RunState.BOOTED, event.reason(), + event.message()); if (event.runState() == RunState.STARTING) { status.addProperty("ram", GsonPtr.to(from.data()) .getAsString("spec", "vm", "maximumRam").orElse("0")); status.addProperty("cpus", 1); + + // In case we had an irregular shutdown + status.remove("osinfo"); } else if (event.runState() == RunState.STOPPED) { status.addProperty("ram", "0"); status.addProperty("cpus", 0); + status.remove("osinfo"); } // In case console connection was still present diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/RunnerStateChange.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/RunnerStateChange.java index bb6ab10..829cc88 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/RunnerStateChange.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/RunnerStateChange.java @@ -18,6 +18,7 @@ package org.jdrupes.vmoperator.runner.qemu.events; +import java.util.EnumSet; import org.jgrapes.core.Channel; import org.jgrapes.core.Components; import org.jgrapes.core.Event; @@ -29,10 +30,28 @@ import org.jgrapes.core.Event; public class RunnerStateChange extends Event { /** - * The state. + * The states. */ public enum RunState { - INITIALIZING, STARTING, RUNNING, TERMINATING, STOPPED + INITIALIZING, STARTING, BOOTING, BOOTED, TERMINATING, STOPPED; + + /** + * Checks if the state is one of the states in which the VM is running. + * + * @return true, if is running + */ + public boolean vmRunning() { + return EnumSet.of(BOOTING, BOOTED, TERMINATING).contains(this); + } + + /** + * Checks if the state is one of the states in which the VM is active. + * + * @return true, if is active + */ + public boolean vmActive() { + return EnumSet.of(BOOTING, BOOTED).contains(this); + } } private final RunState state;