Allow guest to (finally) shutdown the VM.
This commit is contained in:
parent
1a608df411
commit
aa7fdbee08
6 changed files with 136 additions and 18 deletions
|
|
@ -12,6 +12,7 @@ rules:
|
|||
verbs:
|
||||
- list
|
||||
- get
|
||||
- patch
|
||||
- apiGroups:
|
||||
- vmoperator.jdrupes.org
|
||||
resources:
|
||||
|
|
|
|||
|
|
@ -640,13 +640,25 @@ public class Runner extends Component {
|
|||
return;
|
||||
}
|
||||
if (procDef.equals(qemuDefinition) && state == State.RUNNING) {
|
||||
rep.fire(new Stop());
|
||||
rep.fire(new Exit(event.exitValue()));
|
||||
}
|
||||
logger.info(() -> "Process " + procDef.name
|
||||
+ " has exited with value " + event.exitValue());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* On exit.
|
||||
*
|
||||
* @param event the event
|
||||
*/
|
||||
@Handler(priority = 10_001)
|
||||
public void onExit(Exit event) {
|
||||
if (exitStatus == 0) {
|
||||
exitStatus = event.exitStatus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On stop.
|
||||
*
|
||||
|
|
@ -656,7 +668,7 @@ public class Runner extends Component {
|
|||
public void onStopFirst(Stop event) {
|
||||
state = State.TERMINATING;
|
||||
rep.fire(new RunnerStateChange(state, "VmTerminating",
|
||||
"The VM is being shut down"));
|
||||
"The VM is being shut down", exitStatus != 0));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -671,16 +683,6 @@ public class Runner extends Component {
|
|||
"The VM has been shut down"));
|
||||
}
|
||||
|
||||
/**
|
||||
* On exit.
|
||||
*
|
||||
* @param event the event
|
||||
*/
|
||||
@Handler
|
||||
public void onExit(Exit event) {
|
||||
exitStatus = event.exitStatus();
|
||||
}
|
||||
|
||||
private void shutdown() {
|
||||
if (!Set.of(State.TERMINATING, State.STOPPED).contains(state)) {
|
||||
fire(new Stop());
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ package org.jdrupes.vmoperator.runner.qemu;
|
|||
import com.google.gson.JsonObject;
|
||||
import io.kubernetes.client.custom.Quantity;
|
||||
import io.kubernetes.client.custom.Quantity.Format;
|
||||
import io.kubernetes.client.custom.V1Patch;
|
||||
import io.kubernetes.client.openapi.ApiException;
|
||||
import io.kubernetes.client.openapi.apis.ApisApi;
|
||||
import io.kubernetes.client.openapi.apis.CustomObjectsApi;
|
||||
|
|
@ -32,6 +33,7 @@ import io.kubernetes.client.openapi.models.V1ObjectMeta;
|
|||
import io.kubernetes.client.util.Config;
|
||||
import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesApi;
|
||||
import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject;
|
||||
import io.kubernetes.client.util.generic.options.PatchOptions;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.file.Files;
|
||||
|
|
@ -52,6 +54,7 @@ import org.jdrupes.vmoperator.runner.qemu.events.HotpluggableCpuStatus;
|
|||
import org.jdrupes.vmoperator.runner.qemu.events.RunnerConfigurationUpdate;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.State;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.ShutdownEvent;
|
||||
import org.jdrupes.vmoperator.util.GsonPtr;
|
||||
import org.jgrapes.core.Channel;
|
||||
import org.jgrapes.core.Component;
|
||||
|
|
@ -75,6 +78,7 @@ public class StatusUpdater extends Component {
|
|||
private DynamicKubernetesApi vmCrApi;
|
||||
private EventsV1Api evtsApi;
|
||||
private long observedGeneration;
|
||||
private boolean shutdownByGuest;
|
||||
|
||||
/**
|
||||
* Instantiates a new status updater.
|
||||
|
|
@ -268,6 +272,22 @@ public class StatusUpdater extends Component {
|
|||
return status;
|
||||
}).throwsApiException();
|
||||
|
||||
// Maybe stop VM
|
||||
if (event.state() == State.TERMINATING && !event.failed()
|
||||
&& shutdownByGuest) {
|
||||
PatchOptions patchOpts = new PatchOptions();
|
||||
patchOpts.setFieldManager("kubernetes-java-kubectl-apply");
|
||||
var res = vmCrApi.patch(namespace, vmName,
|
||||
V1Patch.PATCH_FORMAT_JSON_PATCH,
|
||||
new V1Patch("[{\"op\": \"replace\", \"path\": \"/spec/vm/state"
|
||||
+ "\", \"value\": \"Stopped\"}]"),
|
||||
patchOpts);
|
||||
if (!res.isSuccess()) {
|
||||
logger.warning(
|
||||
() -> "Cannot patch pod annotations: " + res.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
// Log event
|
||||
var evt = new EventsV1Event().kind("Event")
|
||||
.metadata(new V1ObjectMeta().namespace(namespace)
|
||||
|
|
@ -344,4 +364,15 @@ public class StatusUpdater extends Component {
|
|||
return status;
|
||||
}).throwsApiException();
|
||||
}
|
||||
|
||||
/**
|
||||
* On shutdown.
|
||||
*
|
||||
* @param event the event
|
||||
* @throws ApiException the api exception
|
||||
*/
|
||||
@Handler
|
||||
public void onShutdown(ShutdownEvent event) throws ApiException {
|
||||
shutdownByGuest = event.byGuest();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,11 +28,13 @@ import org.jgrapes.core.Event;
|
|||
*/
|
||||
public class MonitorEvent extends Event<Void> {
|
||||
|
||||
private static final String EVENT_DATA = "data";
|
||||
|
||||
/**
|
||||
* The kind of monitor event.
|
||||
*/
|
||||
public enum Kind {
|
||||
READY, POWERDOWN, DEVICE_TRAY_MOVED, BALLOON_CHANGE
|
||||
READY, POWERDOWN, DEVICE_TRAY_MOVED, BALLOON_CHANGE, SHUTDOWN
|
||||
}
|
||||
|
||||
private final Kind kind;
|
||||
|
|
@ -47,20 +49,23 @@ public class MonitorEvent extends Event<Void> {
|
|||
@SuppressWarnings("PMD.TooFewBranchesForASwitchStatement")
|
||||
public static Optional<MonitorEvent> from(JsonNode response) {
|
||||
try {
|
||||
var kind
|
||||
= MonitorEvent.Kind.valueOf(response.get("event").asText());
|
||||
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")));
|
||||
.of(new TrayMovedEvent(kind, response.get(EVENT_DATA)));
|
||||
case BALLOON_CHANGE:
|
||||
return Optional.of(
|
||||
new BalloonChangeEvent(kind, response.get(EVENT_DATA)));
|
||||
case SHUTDOWN:
|
||||
return Optional
|
||||
.of(new BalloonChangeEvent(kind, response.get("data")));
|
||||
.of(new ShutdownEvent(kind, response.get(EVENT_DATA)));
|
||||
default:
|
||||
return Optional
|
||||
.of(new MonitorEvent(kind, response.get("data")));
|
||||
.of(new MonitorEvent(kind, response.get(EVENT_DATA)));
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Optional.empty();
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import org.jgrapes.core.Event;
|
|||
/**
|
||||
* The Class RunnerStateChange.
|
||||
*/
|
||||
@SuppressWarnings("PMD.DataClass")
|
||||
public class RunnerStateChange extends Event<Void> {
|
||||
|
||||
/**
|
||||
|
|
@ -37,17 +38,36 @@ public class RunnerStateChange extends Event<Void> {
|
|||
private final State state;
|
||||
private final String reason;
|
||||
private final String message;
|
||||
private final boolean failed;
|
||||
|
||||
/**
|
||||
* Instantiates a new runner state change.
|
||||
*
|
||||
* @param state the state
|
||||
* @param reason the reason
|
||||
* @param message the message
|
||||
* @param channels the channels
|
||||
*/
|
||||
public RunnerStateChange(State state, String reason, String message,
|
||||
Channel... channels) {
|
||||
this(state, reason, message, false, channels);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new runner state change.
|
||||
*
|
||||
* @param state the state
|
||||
* @param reason the reason
|
||||
* @param message the message
|
||||
* @param failed the failed
|
||||
* @param channels the channels
|
||||
*/
|
||||
public RunnerStateChange(State state, String reason, String message,
|
||||
boolean failed, Channel... channels) {
|
||||
super(channels);
|
||||
this.state = state;
|
||||
this.reason = reason;
|
||||
this.failed = failed;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
|
|
@ -78,11 +98,23 @@ public class RunnerStateChange extends Event<Void> {
|
|||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if is failed.
|
||||
*
|
||||
* @return the failed
|
||||
*/
|
||||
public boolean failed() {
|
||||
return failed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append(Components.objectName(this))
|
||||
.append(" [").append(state).append(": ").append(reason);
|
||||
if (failed) {
|
||||
builder.append(" (failed)");
|
||||
}
|
||||
if (channels() != null) {
|
||||
builder.append(", channels=");
|
||||
builder.append(Channel.toString(channels()));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* VM-Operator
|
||||
* Copyright (C) 2024 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;
|
||||
|
||||
/**
|
||||
* Signals the processing of the {@link QmpShutdown} event.
|
||||
*/
|
||||
public class ShutdownEvent extends MonitorEvent {
|
||||
|
||||
/**
|
||||
* Instantiates a new shutdown event.
|
||||
*
|
||||
* @param kind the kind
|
||||
* @param data the data
|
||||
*/
|
||||
public ShutdownEvent(Kind kind, JsonNode data) {
|
||||
super(kind, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns if this is initiated by the guest.
|
||||
*
|
||||
* @return the value
|
||||
*/
|
||||
public boolean byGuest() {
|
||||
return data().get("guest").asBoolean();
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue