From 758412673e2bdc4172cfcefe87bc8c09267c5f58 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Tue, 8 Aug 2023 12:42:44 +0200 Subject: [PATCH] Improve powerdown behavior. --- .../vmoperator/runner/qemu/QemuMonitor.java | 80 ++++++++++++++++--- .../runner/qemu/commands/QmpPowerdown.java | 2 +- .../runner/qemu/events/MonitorEvent.java | 4 +- .../runner/qemu/events/PowerdownEvent.java | 38 +++++++++ 4 files changed, 112 insertions(+), 12 deletions(-) create mode 100644 org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/PowerdownEvent.java diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/QemuMonitor.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/QemuMonitor.java index 64ee787..7d505d9 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/QemuMonitor.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/QemuMonitor.java @@ -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 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)); + } + + } + } + } + } diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/commands/QmpPowerdown.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/commands/QmpPowerdown.java index 9d3160d..108a355 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/commands/QmpPowerdown.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/commands/QmpPowerdown.java @@ -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 { diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorEvent.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorEvent.java index 26b7615..28d2e4c 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorEvent.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorEvent.java @@ -32,7 +32,7 @@ public class MonitorEvent extends Event { * 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 { 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"))); diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/PowerdownEvent.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/PowerdownEvent.java new file mode 100644 index 0000000..6c2edb5 --- /dev/null +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/PowerdownEvent.java @@ -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 . + */ + +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); + } +}