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.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import org.jdrupes.vmoperator.runner.qemu.commands.QmpCapabilities;
|
import org.jdrupes.vmoperator.runner.qemu.commands.QmpCapabilities;
|
||||||
import org.jdrupes.vmoperator.runner.qemu.commands.QmpCommand;
|
import org.jdrupes.vmoperator.runner.qemu.commands.QmpCommand;
|
||||||
import org.jdrupes.vmoperator.runner.qemu.commands.QmpPowerdown;
|
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.MonitorCommand;
|
||||||
import org.jdrupes.vmoperator.runner.qemu.events.MonitorEvent;
|
import org.jdrupes.vmoperator.runner.qemu.events.MonitorEvent;
|
||||||
import org.jdrupes.vmoperator.runner.qemu.events.MonitorReady;
|
import org.jdrupes.vmoperator.runner.qemu.events.MonitorReady;
|
||||||
import org.jdrupes.vmoperator.runner.qemu.events.MonitorResult;
|
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.Channel;
|
||||||
import org.jgrapes.core.Component;
|
import org.jgrapes.core.Component;
|
||||||
import org.jgrapes.core.Components;
|
import org.jgrapes.core.Components;
|
||||||
|
|
@ -75,8 +78,10 @@ public class QemuMonitor extends Component {
|
||||||
private int powerdownTimeout;
|
private int powerdownTimeout;
|
||||||
private SocketIOChannel monitorChannel;
|
private SocketIOChannel monitorChannel;
|
||||||
private final Queue<QmpCommand> executing = new LinkedList<>();
|
private final Queue<QmpCommand> executing = new LinkedList<>();
|
||||||
|
private Instant powerdownStartedAt;
|
||||||
private Stop suspendedStop;
|
private Stop suspendedStop;
|
||||||
private Timer powerdownTimer;
|
private Timer powerdownTimer;
|
||||||
|
private boolean powerdownConfirmed;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiates a new qemu monitor.
|
* Instantiates a new qemu monitor.
|
||||||
|
|
@ -93,10 +98,10 @@ public class QemuMonitor extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* As the configuration of this component depends on the configuration
|
* As the initial configuration of this component depends on the
|
||||||
* of the {@link Runner}, it doesn't have a handler for the
|
* configuration of the {@link Runner}, it doesn't have a handler
|
||||||
* {@link ConfigurationUpdate} event. The values are forwarded from the
|
* for the {@link ConfigurationUpdate} event. The values are
|
||||||
* {@link Runner} instead.
|
* forwarded from the {@link Runner} instead.
|
||||||
*
|
*
|
||||||
* @param socketPath the socket path
|
* @param socketPath the socket path
|
||||||
* @param powerdownTimeout
|
* @param powerdownTimeout
|
||||||
|
|
@ -232,10 +237,10 @@ public class QemuMonitor extends Component {
|
||||||
channel.associated(QemuMonitor.class).ifPresent(qm -> {
|
channel.associated(QemuMonitor.class).ifPresent(qm -> {
|
||||||
monitorChannel = null;
|
monitorChannel = null;
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
|
if (powerdownTimer != null) {
|
||||||
|
powerdownTimer.cancel();
|
||||||
|
}
|
||||||
if (suspendedStop != null) {
|
if (suspendedStop != null) {
|
||||||
if (powerdownTimer != null) {
|
|
||||||
powerdownTimer.cancel();
|
|
||||||
}
|
|
||||||
suspendedStop.resumeHandling();
|
suspendedStop.resumeHandling();
|
||||||
suspendedStop = null;
|
suspendedStop = null;
|
||||||
}
|
}
|
||||||
|
|
@ -284,17 +289,72 @@ public class QemuMonitor extends Component {
|
||||||
// We have a connection to Qemu, attempt ACPI shutdown.
|
// We have a connection to Qemu, attempt ACPI shutdown.
|
||||||
event.suspendHandling();
|
event.suspendHandling();
|
||||||
suspendedStop = event;
|
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 -> {
|
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) {
|
synchronized (this) {
|
||||||
if (suspendedStop != null) {
|
if (suspendedStop != null) {
|
||||||
suspendedStop.resumeHandling();
|
suspendedStop.resumeHandling();
|
||||||
suspendedStop = null;
|
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;
|
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 {
|
public class QmpPowerdown extends QmpCommand {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ public class MonitorEvent extends Event<Void> {
|
||||||
* The kind of monitor event.
|
* The kind of monitor event.
|
||||||
*/
|
*/
|
||||||
public enum Kind {
|
public enum Kind {
|
||||||
READY, DEVICE_TRAY_MOVED
|
READY, POWERDOWN, DEVICE_TRAY_MOVED
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Kind kind;
|
private final Kind kind;
|
||||||
|
|
@ -50,6 +50,8 @@ public class MonitorEvent extends Event<Void> {
|
||||||
var kind
|
var kind
|
||||||
= MonitorEvent.Kind.valueOf(response.get("event").asText());
|
= MonitorEvent.Kind.valueOf(response.get("event").asText());
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
|
case POWERDOWN:
|
||||||
|
return Optional.of(new PowerdownEvent(kind, null));
|
||||||
case DEVICE_TRAY_MOVED:
|
case DEVICE_TRAY_MOVED:
|
||||||
return Optional
|
return Optional
|
||||||
.of(new TrayMovedEvent(kind, response.get("data")));
|
.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