Improve powerdown behavior.
This commit is contained in:
parent
3a0da6cb70
commit
758412673e
4 changed files with 112 additions and 12 deletions
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
||||
|
|
|
|||
|
|
@ -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")));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue