Use the Qemu guest agent with Memory Overcommitment Manager

Qemu now has an official guest agent. Programs on a KVM host can now talk to guests using a virtual hardware channel (either virtio-serial or an emulated ISA serial port). Using this mechanism, it is now possible to reliably power down a guest, read and write files, and perform filesystem snapshotting. More information about the guest agent is here.

As of today, Memory Overcommitment Manager (MOM) can use the qemu guest agent for guest statistics collection. This has several key advantages:

  • No need for host->guest network connectivity
  • No need to map guest names to guest IP addresses
  • Fast and secure hardware channel
  • Uses a standard guest agent

Configuring MOM to use the qemu guest agent involves a few simple steps on the host and guest:

Host setup

1. Update MOM

Download the latest version of MOM from github and install it on your host.

2. Reconfigure MOM

  • Remove any existing name-to-ip setting from mom.conf
  • Remove GuestNetworkDaemon from the list of guest collectors (if present)
  • Add GuestQemuAgent to the list of guest collectors

2. Modify your guest domain.xml files to create the communication channels:

Place the following stanza into the devices section of each domain configuration file:

<serial type='unix'>
  <source mode='bind' path='/var/lib/libvirt/qemu/va-$NAME-isa.sock'/>
  <target port='1'/>
</serial>
<channel type='unix'>
  <source mode='bind' path='/var/lib/libvirt/qemu/va-$NAME-virtio.sock'/>
  <target type='virtio' name='org.qemu.guest_agent.0'/>
  <address type='virtio-serial' controller='0' bus='0' port='1'/>
</channel>

Replace $NAME with the guest’s name. Redefine/restart the domains to ensure the changes take effect. This will require a VM restart. Note: In this example we create both an ISA-serial channel and a virtio-serial channel. The MOM Collector will automatically connect to whichever of these channels is active. Always specifying both allows you to have a standardized libvirt XML configuration regardless of underlying guest support.

Guest setup

1. Build the guest agent binary on your guest

The guest agent is supported in qemu versions v0.15.0-rc0-413-g957f1f9 or later. Download and build the qemu source on your guest and find the binary ‘qemu-ga’.

2. Configure the guest agent to start automatically

The agent can be started on a guest that supports virtio-serial with no arguments. Follow your distribution’s recommendations to start the agent automatically at boot. If you were previously using the mom-guestd agent, you can use the same process for autostarting qemu-ga.

Testing it out

Assuming the above has gone ok, when you start up MOM on the host and start some guests, the mom log should show messages like “GuestMonitor-$NAME is ready” and your statistics should appear as usual.

Posted in KVM, libvirt, MOM, qemu | 37 Comments

What is the IP address of my guest?

When using libvirt to create virtual machines, I find that networking works pretty well right out of the box. My VMs are connected to one another via a virtual subnet and have outbound access to the internet. But there is one itch that needs scratching. When I am ready to SSH into a VM, what hostname/IP address should I use? That answer is attained only after connecting to the VM with VNC, logging in, and looking at ‘ifconfig’ output. Sure, you could configure your VMs with static IPs, but that’s not a very elegant solution either.

The ideal solution to this problem is to be able to do: ssh <vm name> and have it “just work”. In this article I will show you how to use a libvirt virtual network to do just that.

Libvirt Virtual Networks

Libvirt provides many useful networking functions to domains through the concept of a virtual network. Among the services that can be provided are addressing via DHCP and local DNS, LAN connectivity via Network Address Translation (NAT), and network boot services (TFTP and BOOTP). When you configure your domain with
<interface type='network'> and <source network='default'> you are using the ‘default’ virtual network which provides DHCP and NAT. By creating our own virtual network, we can customize how these services are provided and produce the results we want.

Create your own libvirt network

The first step is to define a new virtual network. To do this, we will create an XML document to describe what we want. Details on the format of this document can be found here.

Let’s begin:

<network>
  <name>mynetwork</name>
  <bridge name="virbr1" />
  <forward/>
  <ip address="192.168.123.1" netmask="255.255.255.0">
    <dhcp>
      <range start="192.168.123.2" end="192.168.123.254" />
      <host mac='de:ad:be:ef:00:01' name='vm-1' ip='192.168.123.2' />
      <host mac='de:ad:be:ef:00:02' name='vm-2' ip='192.168.123.3' />
      ...
    </dhcp>
  </ip>
</network>

On line 2, we define the name of the network. The name is how we select this network in the domain configuration. On line 3, we define the bridge device name that will be used to build the virtual network. Be sure to choose a name that is not already taken (virbr0 is reserved for the ‘default’ network). On line 4, we enable NAT so our VMs can access the internet. The <ip> tag on line 5 is where things get interesting. This is where we configure addressing. Line 5 defines the gateway (or router) for the virtual network. Here we reserve a block of 254 possible addresses all beginning with ‘192.168.123.’. This should be enough but make sure this subnet does not conflict with your existing network configuration. On the next line, the <dhcp> tag enables DHCP services and gives us the opportunity to customize address and hostname assignment. The <range> tag tells the DHCP server which addresses it is allowed to assign to virtual machines.

Immediately following that, are explicit per-host address assignments. You will want one of these lines for each of your VMs. The DHCP server will identify your VMs by mac address. Therefore, make sure that the network config and domain config files agree about these mac addresses. The name tag will create a named entry in the local DNS server for this VM which is very handy. I recommend using your VM name here. ‘.localdomain’ must be appended to produce a fully-qualified domain name. Since these IP addresses are local to the host, use localdomain. Finally, the ip allows you to reserve a specific IP address for the VM.

Once you have made your network config file, save it to /etc/libvirt/qemu/networks and create a symlink in /etc/libvirt/qemu/networks/autostart to have it started automatically. Start up the network with the following:

virsh net-define /etc/libvirt/qemu/networks/mynetwork.xml
virsh net-start mynetwork

If you typed your network xml carefully you should see a message indicating that your network was started successfully.

Modify your libvirt domains

With the custom virtual network created, the last step is to modify your domains to use it. This should be a simple process if your domains were previously using the ‘default’ network. In that case, change:

<source network='default'>
to
<source network='mynetwork'>

in your domain configuration files. Remember to make sure that the mac address in your network xml matches the domain xml. After making changes, shut down and restart your virtual machines. If you decided to change the mac address in your domain xml, you may need to reconfigure networking inside the VM.

At this point, your efforts should bear fruit. From your host, you should be able to map vm name to ip address automatically (ie. ssh vm-1 or host vm-1). Enjoy!

Posted in libvirt | 2 Comments

Enable python bindings for hello

This is the eighth and final post in the series “HOWTO: Add libvirt support for a qemu command”

One more to go. This time we will enable our command in the libvirt python bindings. These bindings are auto-generated but (due to the virHelloParams structure, we must hand code an implementation. While not that much code, this part can be tricky because the API generator doesn’t seem to be documented. Also, you will need to learn how to work with python objects in C.

diff --git a/python/generator.py b/python/generator.py
index 569f4a8..5b830bc 100755
--- a/python/generator.py
+++ b/python/generator.py
@@ -166,7 +166,6 @@ def enum(type, name, value):
 functions_failed = []
 functions_skipped = [
     "virConnectListDomains",
-    "virDomainHello",
 ]
 
 skipped_modules = {
@@ -181,7 +180,6 @@ skipped_types = {
      'virConnectDomainEventIOErrorCallback': "No function types in python",
      'virConnectDomainEventGraphicsCallback': "No function types in python",
      'virEventAddHandleFunc': "No function types in python",
-     'virHelloParamsPtr': "Not implemented yet",
 }
 
 #######################################################################

First, undo the compile fixes we made at the start of this patch series. Removing these lines tells the generator not to skip our function anymore.

@@ -257,6 +255,7 @@ py_types = {
     'const virDomainSnapshotPtr':  ('O', "virDomainSnapshot", "virDomainSnapshotPtr", "virDomainSnapshotPtr"),
     'virDomainSnapshot *':  ('O', "virDomainSnapshot", "virDomainSnapshotPtr", "virDomainSnapshotPtr"),
     'const virDomainSnapshot *':  ('O', "virDomainSnapshot", "virDomainSnapshotPtr", "virDomainSnapshotPtr"),
+    'virHelloParamsPtr': ('O', "virHelloParams", "virHelloParamsPtr", "virHelloParamsPtr"),
 }
 
 py_return_types = {

Tell the generator about the virHelloParamsPtr argument type. We will treat this C structure as a python object (actually a dictionary — read on).

@@ -343,6 +342,7 @@ skip_impl = (
     'virNodeDeviceListCaps',
     'virConnectBaselineCPU',
     'virDomainRevertToSnapshot',
+    'virDomainHello',
 )

This tells the generator that we will supply a hand-coded implementation of virDomainHello.

diff --git a/python/libvirt-override-api.xml b/python/libvirt-override-api.xml
index 54deeb5..4d1ea7d 100644
--- a/python/libvirt-override-api.xml
+++ b/python/libvirt-override-api.xml
@@ -308,5 +308,11 @@
       <arg name='flags' type='unsigned int' info='flags, curently unused'/>
       <return type='int' info="0 on success, -1 on error"/>
     </function>
+    <function name='virDomainHello' file='python'>
+      <info>Say hallo to a domain</info>
+      <arg name='domain' type='virDomainPtr' info='pointer to the domain'/>
+      <arg name='params' type='virHelloParamsPtr' info='A dictionary of parameters' />
+      <return type='str *' info='A Greeting from the domain' />
+    </function>
   </symbols>
 </api>

Manually document the API

diff --git a/python/libvirt-override.c b/python/libvirt-override.c
index 4a9b432..20df116 100644
--- a/python/libvirt-override.c
+++ b/python/libvirt-override.c
@@ -2350,6 +2350,42 @@ libvirt_virDomainGetJobInfo(PyObject *self ATTRIBUTE_UNUSED, PyObject *args) {
     return(py_retval);
 }
 
+static PyObject *
+libvirt_virDomainHello(PyObject *self ATTRIBUTE_UNUSED,
+                       PyObject *args) {
+    virDomainPtr domain;
+    struct _virHelloParams params;
+    PyObject *pyobj_domain, *pyobj_params, *pyobj;
+    char *c_retval;
+    PyObject *ret;
+
+    if (!PyArg_ParseTuple(args, (char *)"OO:virDomainHello",
+                          &pyobj_domain, &pyobj_params))
+        return(NULL);
+    domain = (virDomainPtr) PyvirDomain_Get(pyobj_domain);
+
+    pyobj = PyDict_GetItemString(pyobj_params, "exclaim");
+    if (!pyobj)
+        params.exclaim = 0;
+    else
+        params.exclaim = (int) PyInt_AS_LONG(pyobj);
+
+    pyobj = PyDict_GetItemString(pyobj_params, "lang");
+    if (!pyobj)
+        params.lang = NULL;
+    else
+        params.lang = PyString_AsString(pyobj);
+
+    c_retval = virDomainHello(domain, &params);
+    
+    if (c_retval == NULL)
+        return VIR_PY_NONE;
+
+    ret = PyString_FromString(c_retval);
+    free(c_retval);
+    return ret;
+}
+
 
 /*******************************************
  * Helper functions to avoid importing modules
@@ -3585,6 +3621,7 @@ static PyMethodDef libvirtMethods[] = {
     {(char *) "virDomainGetJobInfo", libvirt_virDomainGetJobInfo, METH_VARARGS, NULL},
     {(char *) "virDomainSnapshotListNames", libvirt_virDomainSnapshotListNames, METH_VARARGS, NULL},
     {(char *) "virDomainRevertToSnapshot", libvirt_virDomainRevertToSnapshot, METH_VARARGS, NULL},
+    {(char *) "virDomainHello", libvirt_virDomainHello, METH_VARARGS, NULL},
     {NULL, NULL, 0, NULL}
 };

Here lies the meat of today’s exercise. Above we supply a python-to-c wrapper function. According to our oft-repeated formula we: unpack arguments, make the C API call, and package the return value. The python representation of virHelloParams is a dictionary containing the keys lang and exclaim. Both keys are optional. If supplied, lang is a string and exclaim is a boolean.

Well that’s it! You’re done with “Hello World” for libvirt. Thanks for following my tutorial. I welcome your comments on anything (the tutorial itself but especially the process for adding new functionality to libvirt). If my opinion hasn’t come through in the tutorial, I think there is room for simplification 🙂

Posted in Uncategorized | Leave a comment

Create hello virsh command

This is the seventh post in the series “HOWTO: Add libvirt support for a qemu command”

Phew, we are getting near the end! In contrast to the remote support, this part will seem easy. We have only one file to change. In that file, we define the command and some metadata. Then we provide an API wrapper function that parses virsh command line arguments, makes the API call, and outputs the result to the user. Find the patch here.

diff --git a/tools/virsh.c b/tools/virsh.c
index 50ca50f..254a2bd 100644
--- a/tools/virsh.c
+++ b/tools/virsh.c
@@ -3987,6 +3987,59 @@ done:
 }
 
 /*
+ * "hello" command
+ */
+static const vshCmdInfo info_domhello[] = {
+    {"help", N_("say hello to a domain")},
+    {"desc", N_("Qemu demonstration command -- does nothing useful .")},
+    {NULL, NULL}
+};
+
+static const vshCmdOptDef opts_domhello[] = {
+    {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, gettext_noop("domain name, id or uuid")},
+    {"exclaim", VSH_OT_BOOL, 0, N_("Get a more excited greeting") },
+    {"lang", VSH_OT_DATA, VSH_OFLAG_NONE, N_("Select desired language (english or spanish)")},
+    { NULL, 0, 0, NULL },
+};
+

First create two structures that define the virsh command. info_domhello defines some help text that will get displayed by the help command. opts_domhello defines the virsh options for our command. Each entry sets: the option name, the type, is whether the parameter is optional, and the help text.

+static int
+cmdDomHello(vshControl *ctl, const vshCmd *cmd)
+{
+    virDomainPtr dom;
+    const char *name;
+    struct _virHelloParams params;
+    int exclaim = 0;
+    const char *lang;
+    char *response;
+
+    if (!vshConnectionUsability(ctl, ctl->conn))
+        return FALSE;
+
+    if (!(dom = vshCommandOptDomain(ctl, cmd, &name)))
+        return FALSE;
+
+    if (vshCommandOptBool(cmd, "exclaim"))
+        exclaim = 1;
+    if (vshCommandOptString(cmd, "lang", &lang) < 0)
+        lang = NULL;
+
+    params.lang = (char *) lang;
+    params.exclaim = exclaim;
+    response = virDomainHello(dom, &params);
+    
+    if (response) {
+        vshPrint(ctl, "%s", response);
+        VIR_FREE(response);
+    } else {
+        virDomainFree(dom);
+        return FALSE;
+    }
+
+    virDomainFree(dom);
+    return TRUE;
+}
+
+/*
  * "net-autostart" command
  */
 static const vshCmdInfo info_network_autostart[] = {
@@ -10504,6 +10557,7 @@ static const vshCmdDef domMonitoringCmds[] = {
     {"dommemstat", cmdDomMemStat, opts_dommemstat, info_dommemstat},
     {"domstate", cmdDomstate, opts_domstate, info_domstate},
     {"list", cmdList, opts_list, info_list},
+    {"domhello", cmdDomHello, opts_domhello, info_domhello},
     {NULL, NULL, NULL, NULL}
 };

By now, for structure of this function should be looking very familiar. It starts by testing the connection and domain for validity. Next, the exclaim and lang arguments are extracted if present and the params structure is built. Next, the API call is made. Either the response or error message is displayed and cleanup is done.

Up next… Enable python bindings for helloThe last one!

Posted in Uncategorized | Leave a comment

Add hello remote transport support

This is the sixth post in the series “HOWTO: Add libvirt support for a qemu command”

One great thing about libvirt is its ability to work with remote hosts just as easily as the local system on which it is installed. Of course this feature doesn’t come for free. Every new libvirt API requires an XDR implementation to transmit function calls and their results over the wire. Note: virsh always uses the remote driver to execute commands so this functionality must be in place for virsh to work even for local operations. I would regard this part to be the most difficult task when adding a new API to libvirt. Unless you are familiar with XDR, there is quite a bit going on and not much documentation to explain it.

This patch provides an XDR protocol specification which is used to generate several XDR-specific stubs. Additionally, a client and server implementation are provided. See the unadultered patch here.

    hello: Add hello remote transport support
    
    * src/remote/remote_protocol.x: provide defines for the new entry point
    * src/remote/remote_driver.c daemon/remote.c: implement the client and
      server side
    * GENERATED STUBS:
        daemon/remote_dispatch_args.h daemon/remote_dispatch_prototypes.h
        daemon/remote_dispatch_ret.h daemon/remote_dispatch_table.h
        src/remote/remote_protocol.c src/remote/remote_protocol.h

When adding remote support for an API it is only necessary to change three files. The others are generated automatically from remote_protocol.x during the build process. When submitting patches, include the changes to the generated files. I will make one additional point regarding XDR generation. I have found it helpful to manually regenerate the files whenever I modify remote_protocol.x. To do this, execute the following command from the top of the libvirt source tree:

make -C src rpcgen

Onward and upward!

diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x
index 7310689..54346cd 100644
--- a/src/remote/remote_protocol.x
+++ b/src/remote/remote_protocol.x
@@ -1926,6 +1926,20 @@ struct remote_domain_open_console_args {
     unsigned int flags;
 };
 
+struct remote_domain_hello_params {
+    remote_string lang;
+    int exclaim;
+};
+
+struct remote_domain_hello_args {
+    remote_nonnull_domain domain;
+    remote_domain_hello_params params;
+};
+
+struct remote_domain_hello_ret {
+    remote_string response;
+};
+
 /*----- Protocol. -----*/
 
 /* Define the program number, protocol version and procedure numbers here. */
@@ -2159,7 +2173,8 @@ enum remote_procedure {
     REMOTE_PROC_DOMAIN_SET_MEMORY_FLAGS = 204,
     REMOTE_PROC_DOMAIN_SET_BLKIO_PARAMETERS = 205,
     REMOTE_PROC_DOMAIN_GET_BLKIO_PARAMETERS = 206,
-    REMOTE_PROC_DOMAIN_MIGRATE_SET_MAX_SPEED = 207
+    REMOTE_PROC_DOMAIN_MIGRATE_SET_MAX_SPEED = 207,
+    REMOTE_PROC_DOMAIN_HELLO = 208
 
     /*
      * Notice how the entries are grouped in sets of 10 ?

The first step when adding remote support should be to define the wire protocol for your API. You must define the format of the arguments and return value (including explicit format definitions for any structures that are to be passed or returned). For virDomainHello, the arguments include a domain and a structure. The return is a string that can be NULL. The format of this file is C-like but not strict C. There should be enough examples in the file to figure out how to express the various C types.

After defining the types, allocate the next available RPC number for this API. The RPC number is used to figure out if both the client and server support a specific function.

diff --git a/daemon/remote.c b/daemon/remote.c
index d49e0d8..9be77ad 100644
--- a/daemon/remote.c
+++ b/daemon/remote.c
@@ -6995,6 +6995,51 @@ remoteDispatchDomainEventsDeregisterAny (struct qemud_server *server ATTRIBUTE_U
 }
 
 
+static int
+remoteDispatchDomainHello (struct qemud_server *server ATTRIBUTE_UNUSED,
+                           struct qemud_client *client ATTRIBUTE_UNUSED,
+                           virConnectPtr conn,
+                           remote_message_header *hdr ATTRIBUTE_UNUSED,
+                           remote_error *rerr,
+                           remote_domain_hello_args *args,
+                           remote_domain_hello_ret *ret)
+{
+    virDomainPtr dom;
+    struct _virHelloParams params;
+    char **response, *ret_str;
+    int rc = 0;
+
+    dom = get_nonnull_domain (conn, args->domain);
+    if (dom == NULL) {
+        remoteDispatchConnError(rerr, conn);
+        return -1;
+    }
+
+    /* XDR passes lang as a pointer to a pointer ... */
+    params.lang = args->params.lang == NULL ? NULL : *args->params.lang;
+    params.exclaim = (args->params.exclaim != 0);        
+
+    /* ... and the same thing for the return string */
+    if (VIR_ALLOC(response) < 0) {
+        remoteDispatchOOMError(rerr);
+        virDomainFree (dom);
+        return -1;
+    }
+
+    ret_str = virDomainHello(dom, &params);
+    if (!ret_str) {
+        remoteDispatchConnError(rerr, conn);
+        VIR_FREE(response);
+        rc = -1;
+    } else {
+        *response = ret_str;
+        ret->response = response;
+    }
+    
+    virDomainFree (dom);
+    return rc;
+}
+
 
 static int
 remoteDispatchNwfilterLookupByName (struct qemud_server *server ATTRIBUTE_UNUSED,

The function above implements the server side of the remote function call. This code runs within libvirtd on the “remote” host and calls down into the qemu driver on behalf of a client that may be running on another host. The pointer arguments args and ret contain encoded data representing the arguments to virDomainHello and a place to store the return value. Regardless of the API being called, this function returns an integer to indicate success or failure at the RPC level.

The first step is to convert args->dom into a valid local domain pointer. Next we build a local virHelloParams structure from the rest of the arguments. Something I found very confusing (mostly due to lack of documentation) is that XDR passes strings as char** pointers. That explains the strange way of accessing args.params->lang. Likewise to return a string we must allocate response a char** pointer. Once this is done we can make the actual API call on behalf of the client. If successful, the returned string is packed into response and then packed into the XDR return parameter. Finally, the domain pointer must be freed.

diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c
index 87a68a6..75c655a 100644
--- a/src/remote/remote_driver.c
+++ b/src/remote/remote_driver.c
@@ -9632,6 +9632,33 @@ done:
 
 }
 
+static char *
+remoteDomainHello(virDomainPtr dom, virHelloParamsPtr params)
+{
+    char *rv = NULL;
+    remote_domain_hello_args args;
+    remote_domain_hello_ret ret;
+    struct private_data *priv = dom->conn->privateData;
+    
+    remoteDriverLock(priv);
+    
+    make_nonnull_domain (&args.domain, dom);
+    args.params.lang = params->lang == NULL ? NULL : (char **) &params->lang;
+    args.params.exclaim = params->exclaim;
+    memset(&ret, 0, sizeof ret);
+    if (call (dom->conn, priv, 0, REMOTE_PROC_DOMAIN_HELLO,
+              (xdrproc_t) xdr_remote_domain_hello_args, (char *) &args,
+              (xdrproc_t) xdr_remote_domain_hello_ret, (char *) &ret) == -1)
+        goto done;
+        
+    /* Caller frees this. */
+    rv = *ret.response == NULL ? NULL : *ret.response;
+    
+done:
+    remoteDriverUnlock(priv);
+    return rv;
+}
+
 
 /*----------------------------------------------------------------------*/
 
@@ -11202,7 +11229,7 @@ static virDriver remote_driver = {
     remoteDomainSnapshotDelete, /* domainSnapshotDelete */
     remoteQemuDomainMonitorCommand, /* qemuDomainMonitorCommand */
     remoteDomainOpenConsole, /* domainOpenConsole */
-    NULL, /* domainHello */
+    remoteDomainHello, /* domainHello */
 };
 
 static virNetworkDriver network_driver = {

Here is the implementation of the client side which is organized as a driver (just like qemu, xen, etc). I followed the example set by other functions in the remote driver to handle driver locking. With that taken care of, the structures for XDR arguments and return value are prepared. The domain pointer is encoded using a helper function. As stated previously, XDR requires character pointers to be passed by reference. Next we make the actual RPC call. REMOTE_PROC_DOMAIN_HELLO refers to the RPC number that we specified in the protocol definition. The prepared args and ret structures are passed along wih their types. After the call, we unpack the returned string, clean up and then return our result string. By convention, we own the reference on the returned string so we do not need to duplicate it. The caller will free it.

diff --git a/daemon/remote_dispatch_args.h b/daemon/remote_dispatch_args.h
index 15fa1a0..cb6091a 100644
--- a/daemon/remote_dispatch_args.h
+++ b/daemon/remote_dispatch_args.h
@@ -176,3 +176,4 @@
     remote_domain_set_blkio_parameters_args val_remote_domain_set_blkio_parameters_args;
     remote_domain_get_blkio_parameters_args val_remote_domain_get_blkio_parameters_args;
     remote_domain_migrate_set_max_speed_args val_remote_domain_migrate_set_max_speed_args;
+    remote_domain_hello_args val_remote_domain_hello_args;
diff --git a/daemon/remote_dispatch_prototypes.h b/daemon/remote_dispatch_prototypes.h
index 3fcf87c..5f2264f 100644
--- a/daemon/remote_dispatch_prototypes.h
+++ b/daemon/remote_dispatch_prototypes.h
@@ -338,6 +338,14 @@ static int remoteDispatchDomainHasManagedSaveImage(
     remote_error *err,
     remote_domain_has_managed_save_image_args *args,
     remote_domain_has_managed_save_image_ret *ret);
+static int remoteDispatchDomainHello(
+    struct qemud_server *server,
+    struct qemud_client *client,
+    virConnectPtr conn,
+    remote_message_header *hdr,
+    remote_error *err,
+    remote_domain_hello_args *args,
+    remote_domain_hello_ret *ret);
 static int remoteDispatchDomainInterfaceStats(
     struct qemud_server *server,
     struct qemud_client *client,
diff --git a/daemon/remote_dispatch_ret.h b/daemon/remote_dispatch_ret.h
index 114e832..cdc9c89 100644
--- a/daemon/remote_dispatch_ret.h
+++ b/daemon/remote_dispatch_ret.h
@@ -140,3 +140,4 @@
     remote_domain_is_updated_ret val_remote_domain_is_updated_ret;
     remote_get_sysinfo_ret val_remote_get_sysinfo_ret;
     remote_domain_get_blkio_parameters_ret val_remote_domain_get_blkio_parameters_ret;
+    remote_domain_hello_ret val_remote_domain_hello_ret;
diff --git a/daemon/remote_dispatch_table.h b/daemon/remote_dispatch_table.h
index c5f6653..fb2c074 100644
--- a/daemon/remote_dispatch_table.h
+++ b/daemon/remote_dispatch_table.h
@@ -1042,3 +1042,8 @@
     .args_filter = (xdrproc_t) xdr_remote_domain_migrate_set_max_speed_args,
     .ret_filter = (xdrproc_t) xdr_void,
 },
+{   /* DomainHello => 208 */
+    .fn = (dispatch_fn) remoteDispatchDomainHello,
+    .args_filter = (xdrproc_t) xdr_remote_domain_hello_args,
+    .ret_filter = (xdrproc_t) xdr_remote_domain_hello_ret,
+},

diff --git a/src/remote/remote_protocol.c b/src/remote/remote_protocol.c
index 7ecea9d..ba31d50 100644
--- a/src/remote/remote_protocol.c
+++ b/src/remote/remote_protocol.c
@@ -3872,6 +3872,37 @@ xdr_remote_domain_open_console_args (XDR *xdrs, remote_domain_open_console_args
 }
 
 bool_t
+xdr_remote_domain_hello_params (XDR *xdrs, remote_domain_hello_params *objp)
+{
+
+         if (!xdr_remote_string (xdrs, &objp->lang))
+                 return FALSE;
+         if (!xdr_int (xdrs, &objp->exclaim))
+                 return FALSE;
+        return TRUE;
+}
+
+bool_t
+xdr_remote_domain_hello_args (XDR *xdrs, remote_domain_hello_args *objp)
+{
+
+         if (!xdr_remote_nonnull_domain (xdrs, &objp->domain))
+                 return FALSE;
+         if (!xdr_remote_domain_hello_params (xdrs, &objp->params))
+                 return FALSE;
+        return TRUE;
+}
+
+bool_t
+xdr_remote_domain_hello_ret (XDR *xdrs, remote_domain_hello_ret *objp)
+{
+
+         if (!xdr_remote_string (xdrs, &objp->response))
+                 return FALSE;
+        return TRUE;
+}
+
+bool_t
 xdr_remote_procedure (XDR *xdrs, remote_procedure *objp)
 {
 
diff --git a/src/remote/remote_protocol.h b/src/remote/remote_protocol.h
index 87de0da..318fa34 100644
--- a/src/remote/remote_protocol.h
+++ b/src/remote/remote_protocol.h
@@ -2184,6 +2184,23 @@ struct remote_domain_open_console_args {
         u_int flags;
 };
 typedef struct remote_domain_open_console_args remote_domain_open_console_args;
+
+struct remote_domain_hello_params {
+        remote_string lang;
+        int exclaim;
+};
+typedef struct remote_domain_hello_params remote_domain_hello_params;
+
+struct remote_domain_hello_args {
+        remote_nonnull_domain domain;
+        remote_domain_hello_params params;
+};
+typedef struct remote_domain_hello_args remote_domain_hello_args;
+
+struct remote_domain_hello_ret {
+        remote_string response;
+};
+typedef struct remote_domain_hello_ret remote_domain_hello_ret;
 #define REMOTE_PROGRAM 0x20008086
 #define REMOTE_PROTOCOL_VERSION 1
 
@@ -2395,6 +2412,7 @@ enum remote_procedure {
         REMOTE_PROC_DOMAIN_SET_BLKIO_PARAMETERS = 205,
         REMOTE_PROC_DOMAIN_GET_BLKIO_PARAMETERS = 206,
         REMOTE_PROC_DOMAIN_MIGRATE_SET_MAX_SPEED = 207,
+        REMOTE_PROC_DOMAIN_HELLO = 208,
 };
 typedef enum remote_procedure remote_procedure;
 
@@ -2776,6 +2794,9 @@ extern  bool_t xdr_remote_domain_snapshot_current_ret (XDR *, remote_domain_snap
 extern  bool_t xdr_remote_domain_revert_to_snapshot_args (XDR *, remote_domain_revert_to_snapshot_args*);
 extern  bool_t xdr_remote_domain_snapshot_delete_args (XDR *, remote_domain_snapshot_delete_args*);
 extern  bool_t xdr_remote_domain_open_console_args (XDR *, remote_domain_open_console_args*);
+extern  bool_t xdr_remote_domain_hello_params (XDR *, remote_domain_hello_params*);
+extern  bool_t xdr_remote_domain_hello_args (XDR *, remote_domain_hello_args*);
+extern  bool_t xdr_remote_domain_hello_ret (XDR *, remote_domain_hello_ret*);
 extern  bool_t xdr_remote_procedure (XDR *, remote_procedure*);
 extern  bool_t xdr_remote_message_type (XDR *, remote_message_type*);
 extern  bool_t xdr_remote_message_status (XDR *, remote_message_status*);
@@ -3131,6 +3152,9 @@ extern bool_t xdr_remote_domain_snapshot_current_ret ();
 extern bool_t xdr_remote_domain_revert_to_snapshot_args ();
 extern bool_t xdr_remote_domain_snapshot_delete_args ();
 extern bool_t xdr_remote_domain_open_console_args ();
+extern bool_t xdr_remote_domain_hello_params ();
+extern bool_t xdr_remote_domain_hello_args ();
+extern bool_t xdr_remote_domain_hello_ret ();
 extern bool_t xdr_remote_procedure ();
 extern bool_t xdr_remote_message_type ();
 extern bool_t xdr_remote_message_status ();

The rest of this is auto-generated by rpcgen. Therefore, I don’t see a need to discuss it any further.

Up next… Create hello virsh command

Posted in Uncategorized | Leave a comment

Implement the qemu version of hello

This is the fifth post in the series “HOWTO: Add libvirt support for a qemu command”

Now begins the fun part where we can actually interact with qemu to implement the command. In this patch we first create a virDrvDomainHello function in the qemu driver. This function locates the domain we are targetting, issues a monitor command, and returns the result. We provide support for issuing the command using the qmp and hmp monitors.

Find the patch here.

diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index 40bb4c0..de6c719 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -7079,6 +7079,50 @@ cleanup:
     return ret;
 }
 
+static char *
+qemuDomainHello(virDomainPtr dom,
+                virHelloParamsPtr params)
+{
+    struct qemud_driver *driver = dom->conn->privateData;
+    virDomainObjPtr vm = NULL;
+    char *greeting = NULL;
+    
+    qemuDriverLock(driver);
+    vm = virDomainFindByUUID(&driver->domains, dom->uuid);
+    qemuDriverUnlock(driver);
+
+    if (!vm) {
+        char uuidstr[VIR_UUID_STRING_BUFLEN];
+        virUUIDFormat(dom->uuid, uuidstr);
+        qemuReportError(VIR_ERR_NO_DOMAIN,
+                        _("no domain with matching uuid '%s'"), uuidstr);
+        goto cleanup;
+    }
+
+    if (qemuDomainObjBeginJob(vm) < 0)
+        goto cleanup;
+
+    if (virDomainObjIsActive(vm)) {
+        qemuDomainObjPrivatePtr priv = vm->privateData;
+        qemuDomainObjEnterMonitor(vm);
+        greeting = qemuMonitorHello(priv->mon, params);
+        if (!greeting)
+            qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                            _("monitor command failed"));
+        qemuDomainObjExitMonitor(vm);
+    } else {
+        qemuReportError(VIR_ERR_OPERATION_INVALID,
+                        "%s", _("domain is not running"));
+    }
+
+    if (qemuDomainObjEndJob(vm) == 0)
+        vm = NULL;
+
+cleanup:
+    if (vm)
+        virDomainObjUnlock(vm);
+    return greeting;
+}
 
 static virDriver qemuDriver = {
     VIR_DRV_QEMU,
@@ -7191,7 +7235,7 @@ static virDriver qemuDriver = {
     qemuDomainSnapshotDelete, /* domainSnapshotDelete */
     qemuDomainMonitorCommand, /* qemuDomainMonitorCommand */
     qemuDomainOpenConsole, /* domainOpenConsole */
-    NULL, /* domainHello */
+    qemuDomainHello, /* domainHello */
 };

Above is the top-level implementation for the qemu driver. The first thing we do is find the vm object corresponding to the domain passed in by performing a UUID lookup. Be careful to follow driver locking rules. Just follow the many existing examples in order to get this right. In order to issue commands to a qemu monitor, you must enter a job context. This is another form of locking to make sure that commands are executed synchronously. Again, look to other commands for examples of correct usage. Once in job context, we call into the monitor code to perform the monitor command. Let’s look at that part next.

diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c
index 800f744..46b89ae 100644
--- a/src/qemu/qemu_monitor.c
+++ b/src/qemu/qemu_monitor.c
@@ -2189,6 +2189,24 @@ int qemuMonitorDeleteSnapshot(qemuMonitorPtr mon, const char *name)
     return ret;
 }
 
+char *qemuMonitorHello(qemuMonitorPtr mon, virHelloParams *params) {
+    char *ret;
+    
+    VIR_DEBUG("mon=%p, params=%p",mon,params);
+
+    if (!mon) {
+        qemuReportError(VIR_ERR_INVALID_ARG, "%s",
+                        _("monitor must not be NULL"));
+        return NULL;
+    }
+
+    if (mon->json)
+        ret = qemuMonitorJSONHello(mon, params);
+    else
+        ret = qemuMonitorTextHello(mon, params);
+    return ret;
+}
+
 int qemuMonitorArbitraryCommand(qemuMonitorPtr mon,
                                 const char *cmd,
                                 char **reply,
diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h
index c90219b..c1ecd10 100644
--- a/src/qemu/qemu_monitor.h
+++ b/src/qemu/qemu_monitor.h
@@ -418,6 +418,8 @@ int qemuMonitorCreateSnapshot(qemuMonitorPtr mon, const char *name);
 int qemuMonitorLoadSnapshot(qemuMonitorPtr mon, const char *name);
 int qemuMonitorDeleteSnapshot(qemuMonitorPtr mon, const char *name);
 
+char *qemuMonitorHello(qemuMonitorPtr mon, virHelloParams *params);
+
 int qemuMonitorArbitraryCommand(qemuMonitorPtr mon,
                                 const char *cmd,
                                 char **reply,

The function qemuMonitorHello is (yet another) wrapper around actual functionality. Libvirt supports the qmp and hmp monitor protocols. This function selects the appropriate function depending on the protocol being used by the active monitor.

diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c
index 6bd03d6..5f18cfa 100644
--- a/src/qemu/qemu_monitor_json.c
+++ b/src/qemu/qemu_monitor_json.c
@@ -2470,6 +2470,49 @@ cleanup:
     return ret;
 }
 
+char *qemuMonitorJSONHello(qemuMonitorPtr mon, virHelloParams *params) {
+    const char *response;
+    char *ret = NULL;
+    virJSONValuePtr cmd;
+    virJSONValuePtr reply = NULL;
+    virJSONValuePtr obj;
+
+    if (params->lang == NULL)
+        cmd = qemuMonitorJSONMakeCommand("hello",
+                                         "b:exclaim", params->exclaim != 0,
+                                         NULL);
+    else
+        cmd = qemuMonitorJSONMakeCommand("hello",
+                                         "b:exclaim", params->exclaim != 0,
+                                         "s:lang", params->lang,
+                                         NULL);
+    if (!cmd)
+        return NULL;
+    
+    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0 ||
+        qemuMonitorJSONCheckError(cmd, reply) < 0)
+        goto cleanup;
+
+    if (!(obj = virJSONValueObjectGet(reply, "return")) ||
+        obj->type != VIR_JSON_TYPE_OBJECT)
+        goto cleanup;
+
+    response = virJSONValueObjectGetString(obj, "response");
+    if (response == NULL) {
+        qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                        _("malformed response to hello command"));
+        goto cleanup;
+    }
+
+    /* Return a copy because we will be freeing reply below */
+    ret = strdup(response);
+    
+cleanup:
+    virJSONValueFree(cmd);
+    virJSONValueFree(reply);
+    return ret;
+}
+
 int qemuMonitorJSONArbitraryCommand(qemuMonitorPtr mon,
                                     const char *cmd_str,
                                     char **reply_str,
diff --git a/src/qemu/qemu_monitor_json.h b/src/qemu/qemu_monitor_json.h
index 086f0e1..e0cf5bd 100644
--- a/src/qemu/qemu_monitor_json.h
+++ b/src/qemu/qemu_monitor_json.h
@@ -199,6 +199,8 @@ int qemuMonitorJSONCreateSnapshot(qemuMonitorPtr mon, const char *name);
 int qemuMonitorJSONLoadSnapshot(qemuMonitorPtr mon, const char *name);
 int qemuMonitorJSONDeleteSnapshot(qemuMonitorPtr mon, const char *name);
 
+char *qemuMonitorJSONHello(qemuMonitorPtr mon, virHelloParams *params);
+
 int qemuMonitorJSONArbitraryCommand(qemuMonitorPtr mon,
                                     const char *cmd_str,
                                     char **reply_str,

Next, implement the JSON version of the hello command (for qmp monitors). This file contains very useful functions for manipulating JSON objects. qemuMonitorJSONMakeCommand is used to build the proper JSON command string. Next, we send the command, receive the response, and check for errors. We then use the virJSONValueObjectGet family of functions to extract the returned string from the response. Be careful when returning pointers and be sure to think about how the API is supposed to work. We want to return a new string to the caller and transfer ownership of that string to the caller. In order to do this we must make a copy (because the JSON response is going to be freed next. The string we return will eventually be freed by the caller when it is no longer needed.

diff --git a/src/qemu/qemu_monitor_text.c b/src/qemu/qemu_monitor_text.c
index 75b2995..452c43f 100644
--- a/src/qemu/qemu_monitor_text.c
+++ b/src/qemu/qemu_monitor_text.c
@@ -2607,6 +2607,36 @@ cleanup:
     return ret;
 }
 
+char *qemuMonitorTextHello(qemuMonitorPtr mon, virHelloParams *params)
+{
+    char *cmd;
+    char *reply = NULL;
+    int rc;
+
+    if (params->exclaim != 0)
+        rc = virAsprintf(&cmd, "hello -e %s",
+                         params->lang ? params->lang : "");
+    else
+        rc = virAsprintf(&cmd, "hello %s",
+                         params->lang ? params->lang : "");
+    
+    if (rc == -1) {
+        virReportOOMError();
+        return NULL;
+    }
+    
+    if (qemuMonitorHMPCommand(mon, cmd, &reply)) {
+        qemuReportError(VIR_ERR_OPERATION_FAILED,
+                         _("failed to perform command '%s'"),
+                         cmd);
+        VIR_FREE(reply);
+    }
+    VIR_FREE(cmd);
+    
+    /* We own the reference to reply, return it and let the caller free it */
+    return reply;
+}
+
 int qemuMonitorTextArbitraryCommand(qemuMonitorPtr mon, const char *cmd,
                                     char **reply)
 {
diff --git a/src/qemu/qemu_monitor_text.h b/src/qemu/qemu_monitor_text.h
index 0838a2b..6c5f4f2 100644
--- a/src/qemu/qemu_monitor_text.h
+++ b/src/qemu/qemu_monitor_text.h
@@ -195,6 +195,8 @@ int qemuMonitorTextCreateSnapshot(qemuMonitorPtr mon, const char *name);
 int qemuMonitorTextLoadSnapshot(qemuMonitorPtr mon, const char *name);
 int qemuMonitorTextDeleteSnapshot(qemuMonitorPtr mon, const char *name);
 
+char *qemuMonitorTextHello(qemuMonitorPtr mon, virHelloParams *params);
+
 int qemuMonitorTextArbitraryCommand(qemuMonitorPtr mon, const char *cmd,
                                     char **reply);

Last but not least, we have the hmp implementation of the command. Hopefully some day we will only need to program one monitor protocol but for now there are two. First, build a command string based on the parameters. Then execute the command and check for errors. If successful, reply will contain the hmp reply. No further parsing of this string is required so we just return it. Why don’t we duplicate the string as we did for the JSON monitor implementation? In this case, qemuMonitorHMPCommand allocates the reply string and transfers control of the reference to its caller. Thus, it is safe to return the pointer directly and let the caller free it.

Up next… Add hello remote transport support

Posted in Uncategorized | Leave a comment

Add hello to the public libvirt API

This is the fourth post in the series “HOWTO: Add libvirt support for a qemu command”

Although we have defined a top-level libvirt API, as yet there is no entry point implemented. In this patch, we create the entry point which does some basic sanity checking and dispatches the call down to a driver to perform the real work. Make this entry point visible by updating the libvirt.so version script. Find the patch here.

diff --git a/src/libvirt.c b/src/libvirt.c
index e46c18b..774ab3c 100644
--- a/src/libvirt.c
+++ b/src/libvirt.c
@@ -13355,3 +13355,46 @@ error:
     virDispatchError(conn);
     return -1;
 }
+
+/**
+ * virDomainHello:
+ * @dom: pointer to the domain object
+ * @params: pointer to a virHelloParams object
+ *
+ * Returns: A pleasant greeting.
+ */
+char *
+virDomainHello(virDomainPtr dom,
+               virHelloParamsPtr params)
+{
+    virConnectPtr conn;
+    char *ret = NULL;
+
+    VIR_DOMAIN_DEBUG(dom, "params=%p", params);
+
+    if (params == NULL) {
+        virLibDomainError (VIR_ERR_INVALID_ARG, __FUNCTION__);
+        goto error;
+    }
+
+    virResetLastError();
+    if (!VIR_IS_CONNECTED_DOMAIN (dom)) {
+        virLibDomainError (VIR_ERR_INVALID_DOMAIN, __FUNCTION__);
+        goto error;
+    }
+
+    conn = dom->conn;
+    if (conn->driver->domainHello) {
+        ret = conn->driver->domainHello (dom, params);
+        if (ret == NULL) {
+            goto error;
+        }
+        return ret;
+    } else {
+        virLibDomainError (VIR_ERR_NO_SUPPORT, __FUNCTION__);
+    }
+
+error:
+    virDispatchError(dom->conn);
+    return NULL;
+}

Shown above is the entry point. If you look at src/libvirt.c you will notice a lot of functions that look similar to this one. This is mostly boiler-plate code that: checks for obvious problems with parameters (like NULL pointers where they are not allowed), checks for a valid connection and valid domain, and dispatches the call down to the driver associated with this connection. If the driver does not support the operation a suitable error is returned.

diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms
index 5caab4c..379ee6c 100644
--- a/src/libvirt_public.syms
+++ b/src/libvirt_public.syms
@@ -434,4 +434,9 @@ LIBVIRT_0.9.0 {
         virEventRunDefaultImpl;
 } LIBVIRT_0.8.8;
 
+LIBVIRT_0.8.7 {
+    global:
+        virDomainHello;
+} LIBVIRT_0.8.6;
+
 # .... define new API here using predicted next version number ....

All public symbols in libvirt must be declared in the version script in the section corresponding to the release in which the symbol first became available.

Up next… Implement the qemu version of hello

Posted in Uncategorized | 1 Comment

Define hello API function and data types

This is the third post in the series “HOWTO: Add libvirt support for a qemu command”

When designing an API for libvirt, the first step is to think about how your API will be used and establish its calling conventions. Our new API interacts with a domain (by issuing a monitor command) so we will follow libvirt convention and name it virDomainHello. The hello qemu monitor command returns a string so let’s do the same in libvirt. The first parameter should be a pointer to the domain (qemu instance). The exclaim and language parameters could be passed individually (as an integer and string). I chose to pass them in a structure to show how structures are handled by libvirt.

Once the API is defined, we add an entry into the driver structure so that individual hypervisors (including qemu) can register an implementation of it. The driver structure is statically initialized by each hypervisor driver so this patch updates all drivers to set the new field to NULL.

Finally, virDomainHello breaks the Python bindings generator and causes compilation to fail. We can fix this by telling the generator to skip our function for the time being.

Let’s walk through the patch.

diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in
index 1cf9273..9fe724f 100644
--- a/include/libvirt/libvirt.h.in
+++ b/include/libvirt/libvirt.h.in
@@ -2507,6 +2507,15 @@ int virDomainOpenConsole(virDomainPtr dom,
                          virStreamPtr st,
                          unsigned int flags);
 
+typedef struct _virHelloParams virHelloParams;
+struct _virHelloParams {
+    char *lang;
+    int exclaim;
+};
+typedef virHelloParams *virHelloParamsPtr;
+char *                   virDomainHello          (virDomainPtr dom,
+                                                  virHelloParamsPtr params);
+
 #ifdef __cplusplus
 }
 #endif

This first part of the patch defines the top-level data types. We create a new structure type virHelloParams to wrap the arguments. Libvirt coding style requires typedefs for the structure and structure pointers. Then we define the API function itself. It takes a domain pointer and a pointer to our new parameters structure as arguments and returns a string.

diff --git a/python/generator.py b/python/generator.py
index 4fa4f65..569f4a8 100755
--- a/python/generator.py
+++ b/python/generator.py
@@ -166,6 +166,7 @@ def enum(type, name, value):
 functions_failed = []
 functions_skipped = [
     "virConnectListDomains",
+    "virDomainHello",
 ]
 
 skipped_modules = {
@@ -180,6 +181,7 @@ skipped_types = {
      'virConnectDomainEventIOErrorCallback': "No function types in python",
      'virConnectDomainEventGraphicsCallback': "No function types in python",
      'virEventAddHandleFunc': "No function types in python",
+     'virHelloParamsPtr': "Not implemented yet",
 }
 
 #######################################################################

Here we tell the Python API generator to ignore our function and parameters structure. This is a compile fix. We’ll implement the Python bindings in a later patch.

diff --git a/src/driver.h b/src/driver.h
index 286130a..b2fc0e5 100644
--- a/src/driver.h
+++ b/src/driver.h
@@ -515,6 +515,8 @@ typedef int
                                virStreamPtr st,
                                unsigned int flags);
 
+typedef char *
+    (*virDrvDomainHello)(virDomainPtr dom, virHelloParamsPtr params);
 
 /**
  * _virDriver:
@@ -639,6 +641,7 @@ struct _virDriver {
     virDrvDomainSnapshotDelete domainSnapshotDelete;
     virDrvQemuDomainMonitorCommand qemuDomainMonitorCommand;
     virDrvDomainOpenConsole domainOpenConsole;
+    virDrvDomainHello domainHello;
 };
 
 typedef int

Add our new API to the driver structure.

diff --git a/src/esx/esx_driver.c b/src/esx/esx_driver.c
index deda372..28cc0e4 100644
--- a/src/esx/esx_driver.c
+++ b/src/esx/esx_driver.c
@@ -4675,6 +4675,7 @@ static virDriver esxDriver = {
     esxDomainSnapshotDelete,         /* domainSnapshotDelete */
     NULL,                            /* qemuDomainMonitorCommand */
     NULL,                            /* domainOpenConsole */
+    NULL,                            /* domainHello */
 };
 
 
diff --git a/src/lxc/lxc_driver.c b/src/lxc/lxc_driver.c
index 3159e1b..1b3408f 100644
--- a/src/lxc/lxc_driver.c
+++ b/src/lxc/lxc_driver.c
@@ -2904,6 +2904,7 @@ static virDriver lxcDriver = {
     NULL, /* domainSnapshotDelete */
     NULL, /* qemuDomainMonitorCommand */
     lxcDomainOpenConsole, /* domainOpenConsole */
+    NULL, /* domainHello */
 };
 
 static virStateDriver lxcStateDriver = {
diff --git a/src/opennebula/one_driver.c b/src/opennebula/one_driver.c
index 3146589..6e1867f 100644
--- a/src/opennebula/one_driver.c
+++ b/src/opennebula/one_driver.c
@@ -832,6 +832,7 @@ static virDriver oneDriver = {
     NULL, /* domainSnapshotDelete */
     NULL, /* qemuDomainMonitorCommand */
     NULL, /* domainOpenConsole */
+    NULL, /* domainHello */
 };
 
 static virStateDriver oneStateDriver = {
diff --git a/src/openvz/openvz_driver.c b/src/openvz/openvz_driver.c
index fb30c37..0b9c270 100644
--- a/src/openvz/openvz_driver.c
+++ b/src/openvz/openvz_driver.c
@@ -1654,6 +1654,7 @@ static virDriver openvzDriver = {
     NULL, /* domainSnapshotDelete */
     NULL, /* qemuDomainMonitorCommand */
     NULL, /* domainOpenConsole */
+    NULL, /* domainHello */
 };
 
 int openvzRegister(void) {
diff --git a/src/phyp/phyp_driver.c b/src/phyp/phyp_driver.c
index 51f9ff6..dcb848e 100644
--- a/src/phyp/phyp_driver.c
+++ b/src/phyp/phyp_driver.c
@@ -4055,6 +4055,7 @@ static virDriver phypDriver = {
     NULL,                       /* domainSnapshotDelete */
     NULL,                       /* qemuMonitorCommand */
     NULL, /* domainOpenConsole */
+    NULL, /* domainHello */
 };
 
 static virStorageDriver phypStorageDriver = {
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index af897ad..40bb4c0 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -7191,6 +7191,7 @@ static virDriver qemuDriver = {
     qemuDomainSnapshotDelete, /* domainSnapshotDelete */
     qemuDomainMonitorCommand, /* qemuDomainMonitorCommand */
     qemuDomainOpenConsole, /* domainOpenConsole */
+    NULL, /* domainHello */
 };
 
 
diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c
index b05bbcb..87a68a6 100644
--- a/src/remote/remote_driver.c
+++ b/src/remote/remote_driver.c
@@ -11202,6 +11202,7 @@ static virDriver remote_driver = {
     remoteDomainSnapshotDelete, /* domainSnapshotDelete */
     remoteQemuDomainMonitorCommand, /* qemuDomainMonitorCommand */
     remoteDomainOpenConsole, /* domainOpenConsole */
+    NULL, /* domainHello */
 };
 
 static virNetworkDriver network_driver = {
diff --git a/src/test/test_driver.c b/src/test/test_driver.c
index 17f5ad9..57edda0 100644
--- a/src/test/test_driver.c
+++ b/src/test/test_driver.c
@@ -5447,6 +5447,7 @@ static virDriver testDriver = {
     NULL, /* domainSnapshotDelete */
     NULL, /* qemuDomainMonitorCommand */
     NULL, /* domainOpenConsole */
+    NULL, /* domainHello */
 };
 
 static virNetworkDriver testNetworkDriver = {
diff --git a/src/uml/uml_driver.c b/src/uml/uml_driver.c
index 5514487..99d2f7a 100644
--- a/src/uml/uml_driver.c
+++ b/src/uml/uml_driver.c
@@ -2249,6 +2249,7 @@ static virDriver umlDriver = {
     NULL, /* domainSnapshotDelete */
     NULL, /* qemuDomainMonitorCommand */
     umlDomainOpenConsole, /* domainOpenConsole */
+    NULL, /* domainHello */
 };
 
 static int
diff --git a/src/vbox/vbox_tmpl.c b/src/vbox/vbox_tmpl.c
index 8bd27dd..f532923 100644
--- a/src/vbox/vbox_tmpl.c
+++ b/src/vbox/vbox_tmpl.c
@@ -8647,6 +8647,7 @@ virDriver NAME(Driver) = {
     vboxDomainSnapshotDelete, /* domainSnapshotDelete */
     NULL, /* qemuDomainMonitorCommand */
     NULL, /* domainOpenConsole */
+    NULL, /* domainHello */
 };
 
 virNetworkDriver NAME(NetworkDriver) = {
diff --git a/src/vmware/vmware_driver.c b/src/vmware/vmware_driver.c
index b5e416b..3c14fbb 100644
--- a/src/vmware/vmware_driver.c
+++ b/src/vmware/vmware_driver.c
@@ -1007,6 +1007,7 @@ static virDriver vmwareDriver = {
     NULL,                       /* domainSnapshotDelete */
     NULL,                       /* qemuDomainMonitorCommand */
     NULL,                       /* domainOpenConsole */
+    NULL,                       /* domainHello */
 };
 
 int
diff --git a/src/xen/xen_driver.c b/src/xen/xen_driver.c
index bf422e6..ff2b172 100644
--- a/src/xen/xen_driver.c
+++ b/src/xen/xen_driver.c
@@ -2141,6 +2141,7 @@ static virDriver xenUnifiedDriver = {
     NULL, /* domainSnapshotDelete */
     NULL, /* qemuDomainMonitorCommand */
     xenUnifiedDomainOpenConsole, /* domainOpenConsole */
+    NULL, /* domainHello */
 };
 
 /**

It’s a bit tedious, but due to static initialization, we must add a placeholder to every driver.

Up next… Add hello to the public libvirt API

Posted in Uncategorized | Leave a comment

Hello World in Qemu

This is the second post in the series “HOWTO: Add libvirt support for a qemu command”

This first patch shows the implementation of the ‘hello’ monitor command in qemu. The command is simple — merely printing a greeting in English or Spanish and with punctuation if requested. The command synopsis looks like this:

hello [-e] [language]

  • if -e is specified, the greeting will be punctuated with exclamation marks
  • language may be specified as either ‘english or ‘spanish’. If ommitted, english is used.

Next we will walk through the patch to explain the implementation. You can view the patch here.

commit 468da14f29552f415413864ea848310c9ad6efe9
Author: Adam Litke <agl@us.ibm.com>
Date:   Thu Apr 7 08:25:58 2011 -0500

    hello: Add a simple hello world monitor command to qemu
    
    Signed-off-by: Adam Litke <agl@us.ibm.com>

diff --git a/hmp-commands.hx b/hmp-commands.hx
index 372bef4..dd2ad7d 100644
--- a/hmp-commands.hx
+++ b/hmp-commands.hx
@@ -10,6 +10,21 @@ STEXI
 ETEXI
 
     {
+        .name       = "hello",
+        .args_type  = "exclaim:-e,lang:s?",
+        .params     = "[-e] [language]",
+        .help       = "Say hello",
+        .user_print = monitor_print_hello,
+        .mhandler.cmd_new = do_hello,
+    },
+
+STEXI
+@item hello
+@findex hello
+Say hello to QEMU.
+ETEXI
+
+    {
         .name       = "help|?",
         .args_type  = "name:s?",
         .params     = "[cmd]",

This first part of the patch declares the command for the human monitor. hmp_commands.hx contains a list of these command declarations. Here you set the command name, argument synopsis, help text, command handler, and a function that will be called to print the results.

diff --git a/monitor.c b/monitor.c
index 7fc311d..957ea01 100644
--- a/monitor.c
+++ b/monitor.c
@@ -360,6 +360,7 @@ static void monitor_json_emitter(Monitor *mon, const QObject *data)
     qstring_append_chr(json, '\n');
     monitor_puts(mon, qstring_get_str(json));
 
+    printf("-> %s\n", qstring_get_str(json));
     QDECREF(json);
 }
 

I included this bit of debugging code for convenience. This will echo all JSON responses from the qmp monitor to standard out. I found this very helpful when debugging libvirt/qemu interactions.

@@ -1004,6 +1005,47 @@ static void do_info_trace_events(Monitor *mon)
 }
 #endif
 
+static void monitor_print_hello(Monitor *mon, const QObject *data)
+{
+    QDict *qdict;
+
+    qdict = qobject_to_qdict(data);
+    if (!qdict_haskey(qdict, "response"))
+        return;
+
+    monitor_printf(mon, "%s\n", qdict_get_str(qdict, "response"));
+}
+
+/**
+ * do_hello(): Implement the sample hello command
+ */
+static int do_hello(Monitor *mon, const QDict *qdict, QObject **ret_data)
+{
+    int exclaim = qdict_get_try_bool(qdict, "exclaim", 0);
+    const char *lang = qdict_get_try_str(qdict, "lang");
+    char *resp;
+    int rc;
+
+    if (!lang || !strcmp(lang, "english"))
+	    rc = asprintf(&resp, "%s%s", "Hello world", exclaim ? "!" : "");
+    else if (!strcmp(lang, "spanish"))
+        rc = asprintf(&resp, "%s%s%s", exclaim ? "¡" : "",
+                 "Hola mundo", exclaim ? "!" : "");
+    else {
+        qerror_report(QERR_INVALID_PARAMETER, "lang");
+        return -1;
+    }
+
+    if (rc == -1) {
+        qerror_report(QERR_UNDEFINED_ERROR);
+        return -1;
+    }
+
+    *ret_data = qobject_from_jsonf("{ 'response': %s }", resp);
+    free(resp);
+    return 0;
+}
+
 /**
  * do_quit(): Quit QEMU execution
  */

Shown above are the print handler and command handler. The print handler takes the command result (as a QObject) and emits it as a human readable string to the monitor. The command handler is where the command itself is implemented. Most commands will do something interesting and virtualization-related here. Our command simply parses its arguments and builds a return object based on them.

@@ -4396,6 +4438,8 @@ static void handle_user_command(Monitor *mon, const char *cmdline)
 
     qdict = qdict_new();
 
+    printf("Got command: %s\n", cmdline);
+
     cmd = monitor_parse_command(mon, cmdline, qdict);
     if (!cmd)
         goto out;

This is more debugging code. The line above causes all commands received via the ‘hmp’ monitor to be echoed to standard out.

@@ -5024,6 +5068,7 @@ static void monitor_control_read(void *opaque, const uint8_t *buf, int size)
 
     cur_mon = opaque;
 
+    printf("%*c", size, (char)*buf);
     json_message_parser_feed(&cur_mon->mc->parser, (const char *) buf, size);
 
     cur_mon = old_mon;

Debug statememt similar to the one above, but commands received via the ‘qmp’ monitor are echoed.

diff --git a/qmp-commands.hx b/qmp-commands.hx
index df40a3d..e60aec8 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -61,6 +61,37 @@ refer to the QMP specification for more details on error responses.
 EQMP
 
     {
+        .name       = "hello",
+        .args_type  = "exclaim:-e,lang:s?",
+        .params     = "[-e] [language]",
+        .help       = "Say hello",
+        .mhandler.cmd_new = do_hello,
+    },
+
+SQMP
+hello
+-----
+
+Say hello.
+
+Arguments:
+
+- exclaim: Greet with more enthusiasm (json-bool, optional)
+- lang: Select greeting language (json-string, optional)
+
+Note: The lang argument defaults to english
+
+Returns a JSON object containing:
+- response: A greeting/response (json-string)
+
+Example:
+
+-> { "execute": "hello", "arguments": { "lang": "spanish" } }
+<- { "return": { "response": "Hola Mundo" } }
+
+EQMP
+
+    {
         .name       = "quit",
         .args_type  = "",
         .params     = "",

This block of code declares the hello command in the qmp monitor. Similar to the definition in hmp-commands.hx, this specifies the command name, arguments synopsis, documentation, and the actual command handler. Notice that both the hmp and qmp version use the same handler. This is good.

This concludes the code walkthrough for the ‘hello’ qemu monitor command. In the next installment, we will begin extending libvirt to support this command.

Up next… Define hello API function and data types

Posted in Uncategorized | Leave a comment

HOWTO: Add libvirt support for a qemu command

If you are a qemu developer, one task you’ll inevitably do is add a new command to qemu. If you want your command to be usable by tools such as virt-manager, you will also need to add support for your command to libvirt. The first time I did this, I found the process quite daunting (due to the number of abstraction layers). In this series of posts, I will guide you through the process of supporting a fictitious hello qemu command in libvirt.

Continue reading

Posted in libvirt | Leave a comment