Improve powerdown behavior.

This commit is contained in:
Michael Lipp 2023-08-08 12:42:44 +02:00
parent 3a0da6cb70
commit 758412673e
4 changed files with 112 additions and 12 deletions

View file

@ -28,16 +28,19 @@ import java.net.UnixDomainSocketAddress;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.time.Instant;
import java.util.LinkedList;
import java.util.Queue;
import java.util.logging.Level;
import org.jdrupes.vmoperator.runner.qemu.commands.QmpCapabilities;
import org.jdrupes.vmoperator.runner.qemu.commands.QmpCommand;
import org.jdrupes.vmoperator.runner.qemu.commands.QmpPowerdown;
import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu;
import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand;
import org.jdrupes.vmoperator.runner.qemu.events.MonitorEvent;
import org.jdrupes.vmoperator.runner.qemu.events.MonitorReady;
import org.jdrupes.vmoperator.runner.qemu.events.MonitorResult;
import org.jdrupes.vmoperator.runner.qemu.events.PowerdownEvent;
import org.jgrapes.core.Channel;
import org.jgrapes.core.Component;
import org.jgrapes.core.Components;
@ -75,8 +78,10 @@ public class QemuMonitor extends Component {
private int powerdownTimeout;
private SocketIOChannel monitorChannel;
private final Queue<QmpCommand> executing = new LinkedList<>();
private Instant powerdownStartedAt;
private Stop suspendedStop;
private Timer powerdownTimer;
private boolean powerdownConfirmed;
/**
* Instantiates a new qemu monitor.
@ -93,10 +98,10 @@ public class QemuMonitor extends Component {
}
/**
* As the 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.
* 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.
*
* @param socketPath the socket path
* @param powerdownTimeout
@ -232,10 +237,10 @@ public class QemuMonitor extends Component {
channel.associated(QemuMonitor.class).ifPresent(qm -> {
monitorChannel = null;
synchronized (this) {
if (powerdownTimer != null) {
powerdownTimer.cancel();
}
if (suspendedStop != null) {
if (powerdownTimer != null) {
powerdownTimer.cancel();
}
suspendedStop.resumeHandling();
suspendedStop = null;
}
@ -284,17 +289,72 @@ public class QemuMonitor extends Component {
// We have a connection to Qemu, attempt ACPI shutdown.
event.suspendHandling();
suspendedStop = event;
fire(new MonitorCommand(new QmpPowerdown()));
// Schedule timer as fallback
// Attempt powerdown command. If not confirmed, assume
// "hanging" qemu process.
powerdownTimer = Components.schedule(t -> {
// Powerdown not confirmed
logger.fine(() -> "QMP powerdown command has not effect.");
synchronized (this) {
powerdownTimer = null;
if (suspendedStop != null) {
suspendedStop.resumeHandling();
suspendedStop = null;
}
}
}, Duration.ofSeconds(1));
logger.fine(() -> "Attempting QMP powerdown.");
powerdownStartedAt = Instant.now();
fire(new MonitorCommand(new QmpPowerdown()));
}
}
/**
* On powerdown event.
*
* @param event the event
*/
@Handler
public void onPowerdownEvent(PowerdownEvent event) {
synchronized (this) {
// Cancel confirmation timeout
if (powerdownTimer != null) {
powerdownTimer.cancel();
}
// (Re-)schedule timer as fallback
logger.fine(() -> "QMP powerdown confirmed, waiting...");
powerdownTimer = Components.schedule(t -> {
logger.fine(() -> "Powerdown timeout reached.");
synchronized (this) {
if (suspendedStop != null) {
suspendedStop.resumeHandling();
suspendedStop = null;
}
}
}, Duration.ofSeconds(powerdownTimeout));
}, powerdownStartedAt.plusSeconds(powerdownTimeout));
powerdownConfirmed = true;
}
}
/**
* On configure qemu.
*
* @param event the event
*/
@Handler
public void onConfigureQemu(ConfigureQemu event) {
int newTimeout = event.configuration().vm.powerdownTimeout;
if (powerdownTimeout != newTimeout) {
powerdownTimeout = newTimeout;
synchronized (this) {
if (powerdownTimer != null && powerdownConfirmed) {
powerdownTimer
.reschedule(powerdownStartedAt.plusSeconds(newTimeout));
}
}
}
}
}

View file

@ -21,7 +21,7 @@ package org.jdrupes.vmoperator.runner.qemu.commands;
import com.fasterxml.jackson.databind.JsonNode;
/**
* A {@link QmpCommand} that send a powerdown to the VM.
* A {@link QmpCommand} that send a system_powerdown to the VM.
*/
public class QmpPowerdown extends QmpCommand {

View file

@ -32,7 +32,7 @@ public class MonitorEvent extends Event<Void> {
* The kind of monitor event.
*/
public enum Kind {
READY, DEVICE_TRAY_MOVED
READY, POWERDOWN, DEVICE_TRAY_MOVED
}
private final Kind kind;
@ -50,6 +50,8 @@ public class MonitorEvent extends Event<Void> {
var kind
= MonitorEvent.Kind.valueOf(response.get("event").asText());
switch (kind) {
case POWERDOWN:
return Optional.of(new PowerdownEvent(kind, null));
case DEVICE_TRAY_MOVED:
return Optional
.of(new TrayMovedEvent(kind, response.get("data")));

View file

@ -0,0 +1,38 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package org.jdrupes.vmoperator.runner.qemu.events;
import com.fasterxml.jackson.databind.JsonNode;
import org.jdrupes.vmoperator.runner.qemu.commands.QmpPowerdown;
/**
* Signals the processing of the {@link QmpPowerdown} event.
*/
public class PowerdownEvent extends MonitorEvent {
/**
* Instantiates a new powerdown event.
*
* @param kind the kind
* @param data the data
*/
public PowerdownEvent(Kind kind, JsonNode data) {
super(kind, data);
}
}