Add booted state.

This commit is contained in:
Michael Lipp 2025-02-17 20:47:00 +01:00
parent e4bba582a0
commit ec8152bd51
4 changed files with 70 additions and 33 deletions

View file

@ -33,7 +33,6 @@ import java.util.logging.Level;
import org.jdrupes.vmoperator.runner.qemu.commands.QmpCommand; import org.jdrupes.vmoperator.runner.qemu.commands.QmpCommand;
import org.jdrupes.vmoperator.runner.qemu.commands.QmpGuestGetOsinfo; import org.jdrupes.vmoperator.runner.qemu.commands.QmpGuestGetOsinfo;
import org.jdrupes.vmoperator.runner.qemu.events.GuestAgentCommand; 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.OsinfoEvent;
import org.jdrupes.vmoperator.runner.qemu.events.VserportChangeEvent; import org.jdrupes.vmoperator.runner.qemu.events.VserportChangeEvent;
import org.jgrapes.core.Channel; import org.jgrapes.core.Channel;
@ -188,10 +187,6 @@ public class GuestAgentClient extends Component {
logger.fine(() -> "guest agent(in): " + line); logger.fine(() -> "guest agent(in): " + line);
try { try {
var response = mapper.readValue(line, ObjectNode.class); var response = mapper.readValue(line, ObjectNode.class);
if (response.has("QMP")) {
rep.fire(new MonitorReady());
return;
}
if (response.has("return") || response.has("error")) { if (response.has("return") || response.has("error")) {
QmpCommand executed = executing.poll(); QmpCommand executed = executing.poll();
logger.fine( logger.fine(

View file

@ -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.ConfigureQemu;
import org.jdrupes.vmoperator.runner.qemu.events.Exit; import org.jdrupes.vmoperator.runner.qemu.events.Exit;
import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand; 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.QmpConfigured;
import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange; import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange;
import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.RunState; 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 * @param event the event
*/ */
@Handler @Handler
@ -629,28 +630,14 @@ public class Runner extends Component {
} }
/** /**
* On configure qemu. * Whenever a new QEMU configuration is available, check if it
* * is supposed to trigger a reset.
* @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.
* *
* @param event the event * @param event the event
*/ */
@Handler @Handler
public void onConfigureQemu(ConfigureQemu event) { public void onConfigureQemu(ConfigureQemu event) {
if (state == RunState.RUNNING) { if (state.vmActive()) {
if (resetCounter != null if (resetCounter != null
&& event.configuration().resetCounter != null && event.configuration().resetCounter != null
&& event.configuration().resetCounter > resetCounter) { && 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. * On process exited.
* *
@ -675,6 +692,7 @@ public class Runner extends Component {
mayBeStartQemu(QemuPreps.CloudInit); mayBeStartQemu(QemuPreps.CloudInit);
return; return;
} }
// No other process(es) may exit during startup // No other process(es) may exit during startup
if (state == RunState.STARTING) { if (state == RunState.STARTING) {
logger.severe(() -> "Process " + procDef.name logger.severe(() -> "Process " + procDef.name
@ -683,7 +701,9 @@ public class Runner extends Component {
rep.fire(new Stop()); rep.fire(new Stop());
return; 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())); rep.fire(new Exit(event.exitValue()));
} }
logger.info(() -> "Process " + procDef.name logger.info(() -> "Process " + procDef.name

View file

@ -1,6 +1,6 @@
/* /*
* VM-Operator * 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 * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * 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 io.kubernetes.client.openapi.models.EventsV1Event;
import java.io.IOException; import java.io.IOException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Set;
import java.util.logging.Level; import java.util.logging.Level;
import static org.jdrupes.vmoperator.common.Constants.APP_NAME; import static org.jdrupes.vmoperator.common.Constants.APP_NAME;
import static org.jdrupes.vmoperator.common.Constants.VM_OP_GROUP; import static org.jdrupes.vmoperator.common.Constants.VM_OP_GROUP;
@ -66,9 +65,6 @@ public class StatusUpdater extends VmDefUpdater {
private static final ObjectMapper objectMapper private static final ObjectMapper objectMapper
= new ObjectMapper().registerModule(new JavaTimeModule()); = new ObjectMapper().registerModule(new JavaTimeModule());
private static final Set<RunState> RUNNING_STATES
= Set.of(RunState.RUNNING, RunState.TERMINATING);
private long observedGeneration; private long observedGeneration;
private boolean guestShutdownStops; private boolean guestShutdownStops;
private boolean shutdownByGuest; private boolean shutdownByGuest;
@ -186,16 +182,23 @@ public class StatusUpdater extends VmDefUpdater {
} }
vmStub.updateStatus(vmDef, from -> { vmStub.updateStatus(vmDef, from -> {
JsonObject status = from.statusJson(); JsonObject status = from.statusJson();
boolean running = RUNNING_STATES.contains(event.runState()); boolean running = event.runState().vmRunning();
updateCondition(vmDef, vmDef.statusJson(), "Running", running, updateCondition(vmDef, vmDef.statusJson(), "Running", running,
event.reason(), event.message()); event.reason(), event.message());
updateCondition(vmDef, vmDef.statusJson(), "Booted",
event.runState() == RunState.BOOTED, event.reason(),
event.message());
if (event.runState() == RunState.STARTING) { if (event.runState() == RunState.STARTING) {
status.addProperty("ram", GsonPtr.to(from.data()) status.addProperty("ram", GsonPtr.to(from.data())
.getAsString("spec", "vm", "maximumRam").orElse("0")); .getAsString("spec", "vm", "maximumRam").orElse("0"));
status.addProperty("cpus", 1); status.addProperty("cpus", 1);
// In case we had an irregular shutdown
status.remove("osinfo");
} else if (event.runState() == RunState.STOPPED) { } else if (event.runState() == RunState.STOPPED) {
status.addProperty("ram", "0"); status.addProperty("ram", "0");
status.addProperty("cpus", 0); status.addProperty("cpus", 0);
status.remove("osinfo");
} }
// In case console connection was still present // In case console connection was still present

View file

@ -18,6 +18,7 @@
package org.jdrupes.vmoperator.runner.qemu.events; package org.jdrupes.vmoperator.runner.qemu.events;
import java.util.EnumSet;
import org.jgrapes.core.Channel; import org.jgrapes.core.Channel;
import org.jgrapes.core.Components; import org.jgrapes.core.Components;
import org.jgrapes.core.Event; import org.jgrapes.core.Event;
@ -29,10 +30,28 @@ import org.jgrapes.core.Event;
public class RunnerStateChange extends Event<Void> { public class RunnerStateChange extends Event<Void> {
/** /**
* The state. * The states.
*/ */
public enum RunState { 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; private final RunState state;