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@xxxxxxxxx> --- 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 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list