Through Qemu we can send commands to a Qemu Guest Agent running inside
a domain.
This way we can communicate with a running Domain by asking for it's
network information, requesting a filesystem trim or even execute a
command inside a Domain.
Commands need to be send as JSON Strings, but these have been wrapped
into the QemuCommand class which has a list of pre-defined and the most
commonly used commands.
RAW commands can also be send for more flexibility.
Signed-off-by: Wido den Hollander <wido(a)widodh.nl>
---
src/main/java/org/libvirt/Domain.java | 34 ++++++
src/main/java/org/libvirt/Library.java | 3 +
.../java/org/libvirt/jna/LibvirtQemu.java | 16 +++
.../java/org/libvirt/qemu/QemuCommand.java | 106 ++++++++++++++++++
.../org/libvirt/qemu/TestQemuCommand.java | 24 ++++
5 files changed, 183 insertions(+)
create mode 100644 src/main/java/org/libvirt/jna/LibvirtQemu.java
create mode 100644 src/main/java/org/libvirt/qemu/QemuCommand.java
create mode 100644 src/test/java/org/libvirt/qemu/TestQemuCommand.java
diff --git a/src/main/java/org/libvirt/Domain.java
b/src/main/java/org/libvirt/Domain.java
index 83a500c..9138238 100644
--- a/src/main/java/org/libvirt/Domain.java
+++ b/src/main/java/org/libvirt/Domain.java
@@ -8,6 +8,7 @@ import org.libvirt.jna.CString;
import org.libvirt.jna.DomainPointer;
import org.libvirt.jna.DomainSnapshotPointer;
import org.libvirt.jna.Libvirt;
+import org.libvirt.jna.LibvirtQemu;
import org.libvirt.jna.SizeT;
import org.libvirt.jna.virDomainBlockInfo;
import org.libvirt.jna.virDomainBlockStats;
@@ -21,7 +22,9 @@ import org.libvirt.event.RebootListener;
import org.libvirt.event.LifecycleListener;
import org.libvirt.event.PMWakeupListener;
import org.libvirt.event.PMSuspendListener;
+import org.libvirt.qemu.QemuCommand;
import static org.libvirt.Library.libvirt;
+import static org.libvirt.Library.libvirtqemu;
import static org.libvirt.ErrorHandler.processError;
import static org.libvirt.ErrorHandler.processErrorIfZero;
@@ -1556,4 +1559,35 @@ public class Domain {
return processError(libvirt.virDomainUpdateDeviceFlags(VDP, xml, flags));
}
+ /**
+ * Send a Qemu Guest Agent command to a domain
+ *
+ * @param cmd
+ * The command which has to be send
+ * @param timeout
+ * The timeout for waiting on an answer. See QemuAgentFlags for more
information.
+ * @param flags
+ * Flags to be send
+ * @return String
+ * @throws LibvirtException
+ */
+ public String qemuAgentCommand(QemuCommand command) throws LibvirtException {
+ return this.qemuAgentCommand(command, 0);
+ }
+
+ /**
+ * Send a Qemu Guest Agent command to a domain
+ * @see <a
href="http://wiki.qemu.org/Features/QAPI/GuestAgent">Qemu
Documentation</a>
+ * @param command
+ * The command which has to be send
+ * @param timeout
+ * The timeout for waiting on an answer. See QemuAgentFlags for more
information
+ * @return String
+ * @throws LibvirtException
+ */
+ public String qemuAgentCommand(QemuCommand command, int timeout) throws
LibvirtException {
+ CString result = libvirtqemu.virDomainQemuAgentCommand(this.VDP,
command.toString(), timeout, 0);
+ processError(result);
+ return result.toString();
+ }
}
diff --git a/src/main/java/org/libvirt/Library.java
b/src/main/java/org/libvirt/Library.java
index 8e054c6..30f15be 100644
--- a/src/main/java/org/libvirt/Library.java
+++ b/src/main/java/org/libvirt/Library.java
@@ -2,6 +2,7 @@ package org.libvirt;
import org.libvirt.jna.Libvirt;
import org.libvirt.jna.Libvirt.VirEventTimeoutCallback;
+import org.libvirt.jna.LibvirtQemu;
import org.libvirt.jna.CString;
import static org.libvirt.ErrorHandler.processError;
@@ -34,6 +35,7 @@ public final class Library {
};
final static Libvirt libvirt;
+ final static LibvirtQemu libvirtqemu;
// an empty string array constant
// prefer this over creating empty arrays dynamically.
@@ -47,6 +49,7 @@ public final class Library {
} catch (Exception e) {
e.printStackTrace();
}
+ libvirtqemu = LibvirtQemu.INSTANCE;
}
private Library() {}
diff --git a/src/main/java/org/libvirt/jna/LibvirtQemu.java
b/src/main/java/org/libvirt/jna/LibvirtQemu.java
new file mode 100644
index 0000000..82213e9
--- /dev/null
+++ b/src/main/java/org/libvirt/jna/LibvirtQemu.java
@@ -0,0 +1,16 @@
+package org.libvirt.jna;
+
+import com.sun.jna.Library;
+import com.sun.jna.Native;
+import com.sun.jna.Platform;
+
+/**
+ * The libvirt Qemu interface which is exposed via JNA
+ */
+
+public interface LibvirtQemu extends Library {
+
+ LibvirtQemu INSTANCE = (LibvirtQemu) Native.loadLibrary(Platform.isWindows() ?
"virt-qemu-0" : "virt-qemu", LibvirtQemu.class);
+
+ CString virDomainQemuAgentCommand(DomainPointer virDomainPtr, String cmd, int
timeout, int flags);
+}
diff --git a/src/main/java/org/libvirt/qemu/QemuCommand.java
b/src/main/java/org/libvirt/qemu/QemuCommand.java
new file mode 100644
index 0000000..5b28c81
--- /dev/null
+++ b/src/main/java/org/libvirt/qemu/QemuCommand.java
@@ -0,0 +1,106 @@
+package org.libvirt.qemu;
+
+import java.lang.IllegalArgumentException;
+
+public class QemuCommand {
+
+ public enum Command {
+
+ GUEST_INFO("guest-info", false),
+ GUEST_SYNC_DELIMITED("guest-sync-delimited", true),
+ GUEST_SYNC("guest-sync", true),
+ GUEST_PING("guest-ping", false),
+ GUEST_GET_TIME("guest-get-time", false),
+ GUEST_SET_TIME("guest-set-time", true),
+ GUEST_SHUTDOWN("guest-shutdown", false),
+ GUEST_FILE_OPEN("guest-file-open", true),
+ GUEST_FILE_CLOSE("guest-file-close", true),
+ GUEST_FILE_READ("guest-file-read", true),
+ GUEST_FILE_WRITE("guest-file-write", true),
+ GUEST_FILE_SEEK("guest-file-seek", true),
+ GUEST_FILE_FLUSH("guest-file-flush", true),
+ GUEST_FSFREEZE_STATUS("guest-fsfreeze-status", false),
+ GUEST_FSFREEZE_FREEZE("guest-fsfreeze-freeze", false),
+ GUEST_FSFREEZE_LIST("guest-fsfreeze-freeze-list", true),
+ GUEST_FSFREEZE_THAW("guest-fsfreeze-thaw", false),
+ GUEST_FSTRIM("guest-fstrim", true),
+ GUEST_SUSPEND_DISK("guest-suspend-disk", false),
+ GUEST_SUSPEND_RAM("guest-suspend-ram", false),
+ GUEST_SUSPEND_HYBRID("guest-suspend-hybrid", false),
+ GUEST_GET_NETWORK_INTERFACES("guest-network-get-interfaces", false),
+ GUEST_GET_VCPUS("guest-get-vcpus", false),
+ GUEST_SET_VCPUS("guest-set-vcpus", true),
+ GUEST_GET_FSINFO("guest-get-fsinfo", false),
+ GUEST_SET_USER_PASSWORD("guest-set-user-password", true),
+ GUEST_GET_MEMORY_BLOCKS("guest-get-memory-blocks", false),
+ GUEST_SET_MEMORY_BLOCKS("guest-set-memory-blocks", true),
+ GUEST_GET_MEMORY_BLOCK_INFO("guest-get-memory-block-info", false),
+ GUEST_EXEC_STATUS("guest-exec-status", false),
+ GUEST_EXEC("guest-exec", true);
+
+ private final String command;
+ private final Boolean requiresArguments;
+
+ Command(String command, Boolean requiresArguments) {
+ this.command = command;
+ this.requiresArguments = requiresArguments;
+ }
+
+ private String getCommand() {
+ return command;
+ }
+
+ private Boolean requiresArguments() {
+ return requiresArguments;
+ }
+ }
+
+ Command command;
+ String arguments;
+ String raw;
+
+ public static QemuCommand create(Command cmd) {
+ return new QemuCommand(cmd, null);
+ }
+
+ public static QemuCommand create(Command cmd, String args) {
+ return new QemuCommand(cmd, args);
+ }
+
+ public static QemuCommand raw(String jsonObject) {
+ return new QemuCommand(jsonObject);
+ }
+
+ private QemuCommand(Command command, String arguments) {
+ if (command.requiresArguments() && arguments == null) {
+ throw new IllegalArgumentException(command.getCommand() + " requires a
argument");
+ } else if(!command.requiresArguments() && arguments != null) {
+ throw new IllegalArgumentException(command.getCommand() + " does not
take a argument");
+ }
+
+ this.command = command;
+ this.arguments = arguments;
+ }
+
+ private QemuCommand(String raw) {
+ this.raw = raw;
+ }
+
+ public String toString() {
+ if (this.raw != null) {
+ return this.raw;
+ }
+
+ String json = "";
+
+ json += "{";
+ json += "\"execute\": \"" + command.getCommand() +
"\"";
+
+ if (arguments != null && arguments.length() > 0) {
+ json += "\"arguments\": " + arguments +
"\"";
+ }
+
+ json += "}";
+ return json;
+ }
+}
diff --git a/src/test/java/org/libvirt/qemu/TestQemuCommand.java
b/src/test/java/org/libvirt/qemu/TestQemuCommand.java
new file mode 100644
index 0000000..f372310
--- /dev/null
+++ b/src/test/java/org/libvirt/qemu/TestQemuCommand.java
@@ -0,0 +1,24 @@
+package org.libvirt.qemu;
+
+import junit.framework.TestCase;
+
+public final class TestQemuCommand extends TestCase {
+
+ public void testCommandWithoutArguments() {
+ QemuCommand command = QemuCommand.create(QemuCommand.Command.GUEST_INFO);
+ assertEquals("{\"execute\": \"guest-info\"}",
command.toString());
+ }
+
+ public void testCommandWithoutNonRequiredArguments() {
+ try {
+ QemuCommand command = QemuCommand.create(QemuCommand.Command.GUEST_INFO,
"I am Not Required");
+ fail("Expected a IllegalArgumentException which was not thrown");
+ } catch(IllegalArgumentException e) {}
+ }
+
+ public void testRawCommand() {
+ String rawcommand = "this is a raw string";
+ QemuCommand command = QemuCommand.raw(rawcommand);
+ assertEquals(rawcommand, command.toString());
+ }
+}
--
2.17.1