Add guest agent client and retrieve OS info.
This commit is contained in:
parent
1fc26647b6
commit
5078001f4b
14 changed files with 592 additions and 7 deletions
|
|
@ -1491,6 +1491,10 @@ spec:
|
|||
by the runner if password protection is not enabled.
|
||||
type: integer
|
||||
default: 0
|
||||
osinfo:
|
||||
description: Copy of the OS info provided by the guest agent.
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
assignment:
|
||||
description: >-
|
||||
The assignment of this VM to a a particular user.
|
||||
|
|
|
|||
|
|
@ -5,9 +5,7 @@ metadata:
|
|||
name: test-vm
|
||||
spec:
|
||||
image:
|
||||
repository: docker-registry.lan.mnl.de
|
||||
path: vmoperator/org.jdrupes.vmoperator.runner.qemu-alpine
|
||||
version: latest
|
||||
source: registry.mnl.de/org/jdrupes/vm-operator/org.jdrupes.vmoperator.runner.qemu-arch:testing
|
||||
pullPolicy: Always
|
||||
|
||||
permissions:
|
||||
|
|
@ -34,8 +32,9 @@ spec:
|
|||
currentCpus: 4
|
||||
|
||||
networks:
|
||||
- tap:
|
||||
mac: "02:16:3e:33:58:10"
|
||||
# No bridge on test cluster
|
||||
- user: {}
|
||||
|
||||
disks:
|
||||
- volumeClaimTemplate:
|
||||
metadata:
|
||||
|
|
|
|||
|
|
@ -67,6 +67,9 @@ public class Configuration implements Dto {
|
|||
/** The monitor socket. */
|
||||
public Path monitorSocket;
|
||||
|
||||
/** The guest agent socket socket. */
|
||||
public Path guestAgentSocket;
|
||||
|
||||
/** The firmware rom. */
|
||||
public Path firmwareRom;
|
||||
|
||||
|
|
@ -341,6 +344,7 @@ public class Configuration implements Dto {
|
|||
runtimeDir.toFile().mkdir();
|
||||
swtpmSocket = runtimeDir.resolve("swtpm-sock");
|
||||
monitorSocket = runtimeDir.resolve("monitor.sock");
|
||||
guestAgentSocket = runtimeDir.resolve("org.qemu.guest_agent.0");
|
||||
}
|
||||
if (!Files.isDirectory(runtimeDir) || !Files.isWritable(runtimeDir)) {
|
||||
logger.severe(() -> String.format(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,254 @@
|
|||
/*
|
||||
* VM-Operator
|
||||
* Copyright (C) 2025 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;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.lang.reflect.UndeclaredThrowableException;
|
||||
import java.net.UnixDomainSocketAddress;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
import java.util.logging.Level;
|
||||
import org.jdrupes.vmoperator.runner.qemu.commands.QmpCommand;
|
||||
import org.jdrupes.vmoperator.runner.qemu.commands.QmpGuestGetOsinfo;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.GuestAgentCommand;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.MonitorReady;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.OsinfoEvent;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.VserportChangeEvent;
|
||||
import org.jgrapes.core.Channel;
|
||||
import org.jgrapes.core.Component;
|
||||
import org.jgrapes.core.EventPipeline;
|
||||
import org.jgrapes.core.annotation.Handler;
|
||||
import org.jgrapes.core.events.Start;
|
||||
import org.jgrapes.core.events.Stop;
|
||||
import org.jgrapes.io.events.Closed;
|
||||
import org.jgrapes.io.events.ConnectError;
|
||||
import org.jgrapes.io.events.Input;
|
||||
import org.jgrapes.io.events.OpenSocketConnection;
|
||||
import org.jgrapes.io.util.ByteBufferWriter;
|
||||
import org.jgrapes.io.util.LineCollector;
|
||||
import org.jgrapes.net.SocketIOChannel;
|
||||
import org.jgrapes.net.events.ClientConnected;
|
||||
import org.jgrapes.util.events.ConfigurationUpdate;
|
||||
|
||||
/**
|
||||
* A component that handles the communication over the guest agent
|
||||
* socket.
|
||||
*
|
||||
* If the log level for this class is set to fine, the messages
|
||||
* exchanged on the monitor socket are logged.
|
||||
*/
|
||||
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
|
||||
public class GuestAgentClient extends Component {
|
||||
|
||||
private static ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
private EventPipeline rep;
|
||||
private Path socketPath;
|
||||
private SocketIOChannel gaChannel;
|
||||
private final Queue<QmpCommand> executing = new LinkedList<>();
|
||||
|
||||
/**
|
||||
* Instantiates a new guest agent client.
|
||||
*
|
||||
* @param componentChannel the component channel
|
||||
* @throws IOException Signals that an I/O exception has occurred.
|
||||
*/
|
||||
@SuppressWarnings({ "PMD.AssignmentToNonFinalStatic",
|
||||
"PMD.ConstructorCallsOverridableMethod" })
|
||||
public GuestAgentClient(Channel componentChannel) throws IOException {
|
||||
super(componentChannel);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
/* default */ void configure(Path socketPath) {
|
||||
this.socketPath = socketPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the start event.
|
||||
*
|
||||
* @param event the event
|
||||
* @throws IOException Signals that an I/O exception has occurred.
|
||||
*/
|
||||
@Handler
|
||||
public void onStart(Start event) throws IOException {
|
||||
rep = event.associated(EventPipeline.class).get();
|
||||
if (socketPath == null) {
|
||||
return;
|
||||
}
|
||||
Files.deleteIfExists(socketPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* When the virtual serial port "channel0" has been opened,
|
||||
* establish the connection by opening the socket.
|
||||
*
|
||||
* @param event the event
|
||||
*/
|
||||
@Handler
|
||||
public void onVserportChanged(VserportChangeEvent event) {
|
||||
if ("channel0".equals(event.id()) && event.isOpen()) {
|
||||
fire(new OpenSocketConnection(
|
||||
UnixDomainSocketAddress.of(socketPath))
|
||||
.setAssociated(GuestAgentClient.class, this));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is from opening the monitor socket and if true,
|
||||
* save the socket in the context and associate the channel with
|
||||
* the context. Then send the initial message to the socket.
|
||||
*
|
||||
* @param event the event
|
||||
* @param channel the channel
|
||||
*/
|
||||
@SuppressWarnings("resource")
|
||||
@Handler
|
||||
public void onClientConnected(ClientConnected event,
|
||||
SocketIOChannel channel) {
|
||||
event.openEvent().associated(GuestAgentClient.class).ifPresent(qm -> {
|
||||
gaChannel = channel;
|
||||
channel.setAssociated(GuestAgentClient.class, this);
|
||||
channel.setAssociated(Writer.class, new ByteBufferWriter(
|
||||
channel).nativeCharset());
|
||||
channel.setAssociated(LineCollector.class,
|
||||
new LineCollector()
|
||||
.consumer(line -> {
|
||||
try {
|
||||
processGuestAgentInput(line);
|
||||
} catch (IOException e) {
|
||||
throw new UndeclaredThrowableException(e);
|
||||
}
|
||||
}));
|
||||
fire(new GuestAgentCommand(new QmpGuestGetOsinfo()));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a connection attempt fails.
|
||||
*
|
||||
* @param event the event
|
||||
* @param channel the channel
|
||||
*/
|
||||
@Handler
|
||||
public void onConnectError(ConnectError event, SocketIOChannel channel) {
|
||||
event.event().associated(GuestAgentClient.class).ifPresent(qm -> {
|
||||
rep.fire(new Stop());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle data from qemu monitor connection.
|
||||
*
|
||||
* @param event the event
|
||||
* @param channel the channel
|
||||
*/
|
||||
@Handler
|
||||
public void onInput(Input<?> event, SocketIOChannel channel) {
|
||||
if (channel.associated(GuestAgentClient.class).isEmpty()) {
|
||||
return;
|
||||
}
|
||||
channel.associated(LineCollector.class).ifPresent(collector -> {
|
||||
collector.feed(event);
|
||||
});
|
||||
}
|
||||
|
||||
private void processGuestAgentInput(String line)
|
||||
throws IOException {
|
||||
logger.fine(() -> "guest agent(in): " + line);
|
||||
try {
|
||||
var response = mapper.readValue(line, ObjectNode.class);
|
||||
if (response.has("QMP")) {
|
||||
rep.fire(new MonitorReady());
|
||||
return;
|
||||
}
|
||||
if (response.has("return") || response.has("error")) {
|
||||
QmpCommand executed = executing.poll();
|
||||
logger.fine(
|
||||
() -> String.format("(Previous \"guest agent(in)\" is "
|
||||
+ "result from executing %s)", executed));
|
||||
if (executed instanceof QmpGuestGetOsinfo) {
|
||||
rep.fire(new OsinfoEvent(response.get("return")));
|
||||
}
|
||||
}
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On closed.
|
||||
*
|
||||
* @param event the event
|
||||
*/
|
||||
@Handler
|
||||
@SuppressWarnings({ "PMD.AvoidSynchronizedStatement",
|
||||
"PMD.AvoidDuplicateLiterals" })
|
||||
public void onClosed(Closed<?> event, SocketIOChannel channel) {
|
||||
channel.associated(QemuMonitor.class).ifPresent(qm -> {
|
||||
gaChannel = null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* On guest agent command.
|
||||
*
|
||||
* @param event the event
|
||||
*/
|
||||
@Handler
|
||||
@SuppressWarnings({ "PMD.AvoidLiteralsInIfCondition",
|
||||
"PMD.AvoidSynchronizedStatement" })
|
||||
public void onGuestAgentCommand(GuestAgentCommand event) {
|
||||
var command = event.command();
|
||||
logger.fine(() -> "guest agent(out): " + command.toString());
|
||||
String asText;
|
||||
try {
|
||||
asText = command.asText();
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.log(Level.SEVERE, e,
|
||||
() -> "Cannot serialize Json: " + e.getMessage());
|
||||
return;
|
||||
}
|
||||
synchronized (executing) {
|
||||
gaChannel.associated(Writer.class).ifPresent(writer -> {
|
||||
try {
|
||||
executing.add(command);
|
||||
writer.append(asText).append('\n').flush();
|
||||
} catch (IOException e) {
|
||||
// Cannot happen, but...
|
||||
logger.log(Level.WARNING, e, e::getMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -218,6 +218,7 @@ public class Runner extends Component {
|
|||
private CommandDefinition cloudInitImgDefinition;
|
||||
private CommandDefinition qemuDefinition;
|
||||
private final QemuMonitor qemuMonitor;
|
||||
private final GuestAgentClient guestAgentClient;
|
||||
private Integer resetCounter;
|
||||
private RunState state = RunState.INITIALIZING;
|
||||
|
||||
|
|
@ -275,6 +276,7 @@ public class Runner extends Component {
|
|||
attach(new ProcessManager(channel()));
|
||||
attach(new SocketConnector(channel()));
|
||||
attach(qemuMonitor = new QemuMonitor(channel(), configDir));
|
||||
attach(guestAgentClient = new GuestAgentClient(channel()));
|
||||
attach(new StatusUpdater(channel()));
|
||||
attach(new YamlConfigurationStore(channel(), configFile, false));
|
||||
fire(new WatchFile(configFile.toPath()));
|
||||
|
|
@ -349,6 +351,7 @@ public class Runner extends Component {
|
|||
// Forward some values to child components
|
||||
qemuMonitor.configure(config.monitorSocket,
|
||||
config.vm.powerdownTimeout);
|
||||
guestAgentClient.configure(config.guestAgentSocket);
|
||||
} catch (IllegalArgumentException | IOException | TemplateException e) {
|
||||
logger.log(Level.SEVERE, e, () -> "Invalid configuration: "
|
||||
+ e.getMessage());
|
||||
|
|
|
|||
|
|
@ -18,12 +18,16 @@
|
|||
|
||||
package org.jdrupes.vmoperator.runner.qemu;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import io.kubernetes.client.apimachinery.GroupVersionKind;
|
||||
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.JSON;
|
||||
import io.kubernetes.client.openapi.models.EventsV1Event;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
|
|
@ -40,6 +44,7 @@ import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu;
|
|||
import org.jdrupes.vmoperator.runner.qemu.events.DisplayPasswordChanged;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.Exit;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.HotpluggableCpuStatus;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.OsinfoEvent;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.RunState;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.ShutdownEvent;
|
||||
|
|
@ -55,6 +60,12 @@ import org.jgrapes.core.events.Start;
|
|||
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
|
||||
public class StatusUpdater extends VmDefUpdater {
|
||||
|
||||
@SuppressWarnings("PMD.FieldNamingConventions")
|
||||
private static final Gson gson = new JSON().getGson();
|
||||
@SuppressWarnings("PMD.FieldNamingConventions")
|
||||
private static final ObjectMapper objectMapper
|
||||
= new ObjectMapper().registerModule(new JavaTimeModule());
|
||||
|
||||
private static final Set<RunState> RUNNING_STATES
|
||||
= Set.of(RunState.RUNNING, RunState.TERMINATING);
|
||||
|
||||
|
|
@ -286,4 +297,26 @@ public class StatusUpdater extends VmDefUpdater {
|
|||
public void onShutdown(ShutdownEvent event) throws ApiException {
|
||||
shutdownByGuest = event.byGuest();
|
||||
}
|
||||
|
||||
/**
|
||||
* On osinfo.
|
||||
*
|
||||
* @param event the event
|
||||
* @throws ApiException
|
||||
*/
|
||||
@Handler
|
||||
public void onOsinfo(OsinfoEvent event) throws ApiException {
|
||||
if (vmStub == null) {
|
||||
return;
|
||||
}
|
||||
var asGson = gson.toJsonTree(
|
||||
objectMapper.convertValue(event.osinfo(), Object.class));
|
||||
|
||||
vmStub.updateStatus(from -> {
|
||||
JsonObject status = from.statusJson();
|
||||
status.add("osinfo", asGson);
|
||||
return status;
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* VM-Operator
|
||||
* Copyright (C) 2025 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.commands;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
/**
|
||||
* A {@link QmpCommand} that pings the guest agent.
|
||||
*/
|
||||
public class QmpGuestGetOsinfo extends QmpCommand {
|
||||
|
||||
@Override
|
||||
public JsonNode toJson() {
|
||||
ObjectNode cmd = mapper.createObjectNode();
|
||||
cmd.put("execute", "guest-get-osinfo");
|
||||
return cmd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "QmpGuestGetOsinfo()";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* VM-Operator
|
||||
* Copyright (C) 2025 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.commands;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
/**
|
||||
* A {@link QmpCommand} that requests the guest info.
|
||||
*/
|
||||
public class QmpGuestInfo extends QmpCommand {
|
||||
|
||||
@Override
|
||||
public JsonNode toJson() {
|
||||
ObjectNode cmd = mapper.createObjectNode();
|
||||
cmd.put("execute", "guest-info");
|
||||
return cmd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "QmpGuestInfo()";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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.commands;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
/**
|
||||
* A {@link QmpCommand} that pings the guest agent.
|
||||
*/
|
||||
public class QmpGuestPing extends QmpCommand {
|
||||
|
||||
@Override
|
||||
public JsonNode toJson() {
|
||||
ObjectNode cmd = mapper.createObjectNode();
|
||||
cmd.put("execute", "guest-ping");
|
||||
return cmd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "QmpGuestPing()";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* VM-Operator
|
||||
* Copyright (C) 2025 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 org.jdrupes.vmoperator.runner.qemu.commands.QmpCommand;
|
||||
import org.jgrapes.core.Channel;
|
||||
import org.jgrapes.core.Components;
|
||||
import org.jgrapes.core.Event;
|
||||
|
||||
/**
|
||||
* An {@link Event} that causes some component to send a QMP
|
||||
* command to the guest agent process.
|
||||
*/
|
||||
public class GuestAgentCommand extends Event<Void> {
|
||||
|
||||
private final QmpCommand command;
|
||||
|
||||
/**
|
||||
* Instantiates a new exec qmp command.
|
||||
*
|
||||
* @param command the command
|
||||
*/
|
||||
public GuestAgentCommand(QmpCommand command) {
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the command.
|
||||
*
|
||||
* @return the command
|
||||
*/
|
||||
public QmpCommand command() {
|
||||
return command;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append(Components.objectName(this))
|
||||
.append(" [").append(command);
|
||||
if (channels() != null) {
|
||||
builder.append(", channels=").append(Channel.toString(channels()));
|
||||
}
|
||||
builder.append(']');
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -35,7 +35,7 @@ public class MonitorEvent extends Event<Void> {
|
|||
*/
|
||||
public enum Kind {
|
||||
READY, POWERDOWN, DEVICE_TRAY_MOVED, BALLOON_CHANGE, SHUTDOWN,
|
||||
SPICE_CONNECTED, SPICE_INITIALIZED, SPICE_DISCONNECTED
|
||||
SPICE_CONNECTED, SPICE_INITIALIZED, SPICE_DISCONNECTED, VSERPORT_CHANGE
|
||||
}
|
||||
|
||||
private final Kind kind;
|
||||
|
|
@ -72,6 +72,9 @@ public class MonitorEvent extends Event<Void> {
|
|||
case SPICE_DISCONNECTED:
|
||||
return Optional.of(new SpiceDisconnectedEvent(kind,
|
||||
response.get(EVENT_DATA)));
|
||||
case VSERPORT_CHANGE:
|
||||
return Optional.of(new VserportChangeEvent(kind,
|
||||
response.get(EVENT_DATA)));
|
||||
default:
|
||||
return Optional
|
||||
.of(new MonitorEvent(kind, response.get(EVENT_DATA)));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* VM-Operator
|
||||
* Copyright (C) 2025 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.jgrapes.core.Event;
|
||||
|
||||
/**
|
||||
* Signals information about the guest OS.
|
||||
*/
|
||||
public class OsinfoEvent extends Event<Void> {
|
||||
|
||||
private final JsonNode osinfo;
|
||||
|
||||
/**
|
||||
* Instantiates a new osinfo event.
|
||||
*
|
||||
* @param data the data
|
||||
*/
|
||||
public OsinfoEvent(JsonNode data) {
|
||||
osinfo = data;
|
||||
}
|
||||
|
||||
public JsonNode osinfo() {
|
||||
return osinfo;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Signals a virtual serial port's open state change.
|
||||
*/
|
||||
public class VserportChangeEvent extends MonitorEvent {
|
||||
|
||||
/**
|
||||
* Initializes a new instance.
|
||||
*
|
||||
* @param kind the kind
|
||||
* @param data the data
|
||||
*/
|
||||
public VserportChangeEvent(Kind kind, JsonNode data) {
|
||||
super(kind, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the channel's id.
|
||||
*
|
||||
* @return the string
|
||||
*/
|
||||
@SuppressWarnings("PMD.ShortMethodName")
|
||||
public String id() {
|
||||
return data().get("id").asText();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the open state of the port.
|
||||
*
|
||||
* @return true, if is open
|
||||
*/
|
||||
public boolean isOpen() {
|
||||
return Boolean.parseBoolean(data().get("open").asText());
|
||||
}
|
||||
}
|
||||
|
|
@ -122,7 +122,7 @@
|
|||
# Best explanation found:
|
||||
# https://fedoraproject.org/wiki/Features/VirtioSerial
|
||||
- [ "-device", "virtio-serial-pci,id=virtio-serial0" ]
|
||||
# - Guest agent serial connection
|
||||
# - Guest agent serial connection. MUST have id "channel0"!
|
||||
- [ "-device", "virtserialport,id=channel0,name=org.qemu.guest_agent.0,\
|
||||
chardev=guest-agent-socket" ]
|
||||
- [ "-chardev","socket,id=guest-agent-socket,\
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue