[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]

Re: [libvirt] [PATCH 1/1] Document how to extend the libvirt API



Daniel P. Berrange wrote:
On Wed, May 27, 2009 at 01:08:04PM -0400, David Allan wrote:
 create mode 100644 docs/api_extension/API_implementation_guide.txt

Nice idea - how'd you actually like to turn this into HTML
and wire it into our website ? I think it'd be nice to create a new 'libvirt internals' section in the website
for things like your doc, another about adding new XML
features, and another about writing new hypervisor drivers,
and of course a expanded & HTML-ified version of our 'HACKING.txt'file. You just need to drop a .html.in file into
the docs/ directory, and then add a entry to sitemap.html.in
to make it appear in the navigation.

Regards,
Daniel

Ok, Here's a patch with an HTML version of the doc and the addition to sitemap.html.in I wasn't at all sure where the right place for it in the sitemap is, but I took a swag at it and the HTML version of the doc is much easier to read in any case. I've also added a paragraph on making sure to propose changes to the list before starting coding, per DV's suggestion.

Dave

>From be8c9e3d1dc89ade9680fa61e4392cc9b523f12b Mon Sep 17 00:00:00 2001
From: David Allan <dallan redhat com>
Date: Wed, 27 May 2009 12:50:57 -0400
Subject: [PATCH 1/1] Document how to extend the libvirt API

---
 .../0001-Step-1-of-8-Define-the-public-API.patch   |   45 +
 ...tep-2-of-8-Define-the-internal-driver-API.patch |   37 +
 ...0003-Step-3-of-8-Implement-the-public-API.patch |  120 ++
 ...ep-4-of-8-Define-the-wire-protocol-format.patch |   48 +
 ...0005-Step-5-of-8-Implement-the-RPC-client.patch |   85 ++
 ...of-8-Implement-the-server-side-dispatcher.patch |   71 ++
 ...-Step-7-of-8-Implement-the-driver-methods.patch | 1172 ++++++++++++++++++++
 .../0008-Step-8-of-8-Add-virsh-support.patch       |  133 +++
 docs/api_extension/api_extension.html              |  264 +++++
 docs/sitemap.html.in                               |    4 +
 10 files changed, 1979 insertions(+), 0 deletions(-)
 create mode 100644 docs/api_extension/0001-Step-1-of-8-Define-the-public-API.patch
 create mode 100644 docs/api_extension/0002-Step-2-of-8-Define-the-internal-driver-API.patch
 create mode 100644 docs/api_extension/0003-Step-3-of-8-Implement-the-public-API.patch
 create mode 100644 docs/api_extension/0004-Step-4-of-8-Define-the-wire-protocol-format.patch
 create mode 100644 docs/api_extension/0005-Step-5-of-8-Implement-the-RPC-client.patch
 create mode 100644 docs/api_extension/0006-Step-6-of-8-Implement-the-server-side-dispatcher.patch
 create mode 100644 docs/api_extension/0007-Step-7-of-8-Implement-the-driver-methods.patch
 create mode 100644 docs/api_extension/0008-Step-8-of-8-Add-virsh-support.patch
 create mode 100644 docs/api_extension/api_extension.html

diff --git a/docs/api_extension/0001-Step-1-of-8-Define-the-public-API.patch b/docs/api_extension/0001-Step-1-of-8-Define-the-public-API.patch
new file mode 100644
index 0000000..6d0cd68
--- /dev/null
+++ b/docs/api_extension/0001-Step-1-of-8-Define-the-public-API.patch
@@ -0,0 +1,45 @@
+From 2ae8fd62a1e5e085b7902da9bc207b806d84fd91 Mon Sep 17 00:00:00 2001
+From: David Allan <dallan redhat com>
+Date: Tue, 19 May 2009 16:16:11 -0400
+Subject: [PATCH] Step 1 of 8 Define the public API
+
+---
+ include/libvirt/libvirt.h.in |    6 ++++++
+ src/libvirt_public.syms      |    6 ++++++
+ 2 files changed, 12 insertions(+), 0 deletions(-)
+
+diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in
+index a028b21..2f7076f 100644
+--- a/include/libvirt/libvirt.h.in
++++ b/include/libvirt/libvirt.h.in
+@@ -1124,6 +1124,12 @@ int                     virNodeDeviceDettach    (virNodeDevicePtr dev);
+ int                     virNodeDeviceReAttach   (virNodeDevicePtr dev);
+ int                     virNodeDeviceReset      (virNodeDevicePtr dev);
+ 
++virNodeDevicePtr        virNodeDeviceCreateXML  (virConnectPtr conn,
++                                                 const char *xmlDesc,
++                                                 unsigned int flags);
++
++int                     virNodeDeviceDestroy    (virNodeDevicePtr dev);
++
+ /*
+  * Domain Event Notification
+  */
+diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms
+index f7ebbc3..b8f9128 100644
+--- a/src/libvirt_public.syms
++++ b/src/libvirt_public.syms
+@@ -258,4 +258,10 @@ LIBVIRT_0.6.1 {
+ 	virNodeGetSecurityModel;
+ } LIBVIRT_0.6.0;
+ 
++LIBVIRT_0.6.3 {
++    global:
++	virNodeDeviceCreateXML;
++	virNodeDeviceDestroy;
++} LIBVIRT_0.6.1;
++
+ # .... define new API here using predicted next version number ....
+-- 
+1.6.0.6
+
diff --git a/docs/api_extension/0002-Step-2-of-8-Define-the-internal-driver-API.patch b/docs/api_extension/0002-Step-2-of-8-Define-the-internal-driver-API.patch
new file mode 100644
index 0000000..231cbdf
--- /dev/null
+++ b/docs/api_extension/0002-Step-2-of-8-Define-the-internal-driver-API.patch
@@ -0,0 +1,37 @@
+From b26d7fc2d64e7e6e4d3ea2b43361015d3620d7a6 Mon Sep 17 00:00:00 2001
+From: David Allan <dallan redhat com>
+Date: Tue, 19 May 2009 16:19:14 -0400
+Subject: [PATCH] Step 2 of 8 Define the internal driver API
+
+---
+ src/driver.h |    7 +++++++
+ 1 files changed, 7 insertions(+), 0 deletions(-)
+
+diff --git a/src/driver.h b/src/driver.h
+index 39dc413..c357b76 100644
+--- a/src/driver.h
++++ b/src/driver.h
+@@ -684,6 +684,11 @@ typedef int (*virDevMonDeviceListCaps)(virNodeDevicePtr dev,
+                                        char **const names,
+                                        int maxnames);
+ 
++typedef virNodeDevicePtr (*virDrvNodeDeviceCreateXML)(virConnectPtr conn,
++                                                      const char *xmlDesc,
++                                                      unsigned int flags);
++typedef int (*virDrvNodeDeviceDestroy)(virNodeDevicePtr dev);
++
+ /**
+  * _virDeviceMonitor:
+  *
+@@ -702,6 +707,8 @@ struct _virDeviceMonitor {
+     virDevMonDeviceGetParent deviceGetParent;
+     virDevMonDeviceNumOfCaps deviceNumOfCaps;
+     virDevMonDeviceListCaps deviceListCaps;
++    virDrvNodeDeviceCreateXML deviceCreateXML;
++    virDrvNodeDeviceDestroy deviceDestroy;
+ };
+ 
+ /*
+-- 
+1.6.0.6
+
diff --git a/docs/api_extension/0003-Step-3-of-8-Implement-the-public-API.patch b/docs/api_extension/0003-Step-3-of-8-Implement-the-public-API.patch
new file mode 100644
index 0000000..079bd06
--- /dev/null
+++ b/docs/api_extension/0003-Step-3-of-8-Implement-the-public-API.patch
@@ -0,0 +1,120 @@
+From fc585594a207dfb9149e7d3d01c9eb1c79b6d52d Mon Sep 17 00:00:00 2001
+From: David Allan <dallan redhat com>
+Date: Tue, 19 May 2009 16:22:23 -0400
+Subject: [PATCH] Step 3 of 8 Implement the public API
+
+---
+ src/libvirt.c |   97 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ 1 files changed, 97 insertions(+), 0 deletions(-)
+
+diff --git a/src/libvirt.c b/src/libvirt.c
+index f3d4484..ded18a7 100644
+--- a/src/libvirt.c
++++ b/src/libvirt.c
+@@ -7509,6 +7509,103 @@ error:
+ }
+ 
+ 
++/**
++ * virNodeDeviceCreateXML:
++ * @conn: pointer to the hypervisor connection
++ * @xmlDesc: string containing an XML description of the device to be created
++ * @flags: callers should always pass 0
++ *
++ * Create a new device on the VM host machine, for example, virtual
++ * HBAs created using vport_create.
++ *
++ * Returns a node device object if successful, NULL in case of failure
++ */
++virNodeDevicePtr
++virNodeDeviceCreateXML(virConnectPtr conn,
++                       const char *xmlDesc,
++                       unsigned int flags)
++{
++    VIR_DEBUG("conn=%p, xmlDesc=%s, flags=%d", conn, xmlDesc, flags);
++
++    virResetLastError();
++
++    if (!VIR_IS_CONNECT(conn)) {
++        virLibConnError(NULL, VIR_ERR_INVALID_CONN, __FUNCTION__);
++        return NULL;
++    }
++
++    if (conn->flags & VIR_CONNECT_RO) {
++        virLibConnError(conn, VIR_ERR_OPERATION_DENIED, __FUNCTION__);
++        goto error;
++    }
++
++    if (xmlDesc == NULL) {
++        virLibConnError(conn, VIR_ERR_INVALID_ARG, __FUNCTION__);
++        goto error;
++    }
++
++    if (conn->deviceMonitor &&
++        conn->deviceMonitor->deviceCreateXML) {
++        virNodeDevicePtr dev = conn->deviceMonitor->deviceCreateXML(conn, xmlDesc, flags);
++        if (dev == NULL)
++            goto error;
++        return dev;
++    }
++
++    virLibConnError (conn, VIR_ERR_NO_SUPPORT, __FUNCTION__);
++
++error:
++    /* Copy to connection error object for back compatability */
++    virSetConnError(conn);
++    return NULL;
++}
++
++
++/**
++ * virNodeDeviceDestroy:
++ * @dev: a device object
++ *
++ * Destroy the device object. The virtual device is removed from the host operating system.
++ * This function may require privileged access
++ *
++ * Returns 0 in case of success and -1 in case of failure.
++ */
++int
++virNodeDeviceDestroy(virNodeDevicePtr dev)
++{
++    DEBUG("dev=%p", dev);
++
++    virResetLastError();
++
++    if (!VIR_IS_CONNECTED_NODE_DEVICE(dev)) {
++        virLibNodeDeviceError(NULL, VIR_ERR_INVALID_NODE_DEVICE, __FUNCTION__);
++        return (-1);
++    }
++
++    if (dev->conn->flags & VIR_CONNECT_RO) {
++        virLibConnError(dev->conn, VIR_ERR_OPERATION_DENIED, __FUNCTION__);
++        goto error;
++    }
++
++    if (dev->conn->deviceMonitor &&
++        dev->conn->deviceMonitor->deviceDestroy) {
++        int retval = dev->conn->deviceMonitor->deviceDestroy(dev);
++        if (retval < 0) {
++            goto error;
++        }
++
++        return 0;
++    }
++
++    virLibConnError (dev->conn, VIR_ERR_NO_SUPPORT, __FUNCTION__);
++
++error:
++    /* Copy to connection error object for back compatability */
++    virSetConnError(dev->conn);
++    return -1;
++}
++
++
+ /*
+  * Domain Event Notification
+  */
+-- 
+1.6.0.6
+
diff --git a/docs/api_extension/0004-Step-4-of-8-Define-the-wire-protocol-format.patch b/docs/api_extension/0004-Step-4-of-8-Define-the-wire-protocol-format.patch
new file mode 100644
index 0000000..8990263
--- /dev/null
+++ b/docs/api_extension/0004-Step-4-of-8-Define-the-wire-protocol-format.patch
@@ -0,0 +1,48 @@
+From bce8f1243b0454c0d70e3db832a039d22faab09a Mon Sep 17 00:00:00 2001
+From: David Allan <dallan redhat com>
+Date: Wed, 20 May 2009 13:58:58 -0400
+Subject: [PATCH] Step 4 of 8 Define the wire protocol format
+
+---
+ qemud/remote_protocol.x |   18 +++++++++++++++++-
+ 1 files changed, 17 insertions(+), 1 deletions(-)
+
+diff --git a/qemud/remote_protocol.x b/qemud/remote_protocol.x
+index 2d8e6a2..2c79949 100644
+--- a/qemud/remote_protocol.x
++++ b/qemud/remote_protocol.x
+@@ -1109,6 +1109,19 @@ struct remote_node_device_reset_args {
+     remote_nonnull_string name;
+ };
+ 
++struct remote_node_device_create_xml_args {
++    remote_nonnull_string xml_desc;
++    int flags;
++};
++
++struct remote_node_device_create_xml_ret {
++    remote_nonnull_node_device dev;
++};
++
++struct remote_node_device_destroy_args {
++    remote_nonnull_string name;
++};
++
+ 
+ /**
+  * Events Register/Deregister:
+@@ -1270,7 +1283,10 @@ enum remote_procedure {
+     REMOTE_PROC_NODE_DEVICE_RESET = 120,
+ 
+     REMOTE_PROC_DOMAIN_GET_SECURITY_LABEL = 121,
+-    REMOTE_PROC_NODE_GET_SECURITY_MODEL = 122
++    REMOTE_PROC_NODE_GET_SECURITY_MODEL = 122,
++
++    REMOTE_PROC_NODE_DEVICE_CREATE_XML = 123,
++    REMOTE_PROC_NODE_DEVICE_DESTROY = 124
+ };
+ 
+ /* Custom RPC structure. */
+-- 
+1.6.0.6
+
diff --git a/docs/api_extension/0005-Step-5-of-8-Implement-the-RPC-client.patch b/docs/api_extension/0005-Step-5-of-8-Implement-the-RPC-client.patch
new file mode 100644
index 0000000..6f87dea
--- /dev/null
+++ b/docs/api_extension/0005-Step-5-of-8-Implement-the-RPC-client.patch
@@ -0,0 +1,85 @@
+From ff272552c297966ace3492aefe91fc830152251a Mon Sep 17 00:00:00 2001
+From: David Allan <dallan redhat com>
+Date: Tue, 19 May 2009 16:26:12 -0400
+Subject: [PATCH] Step 5 of 8 Implement the RPC client
+
+---
+ src/remote_internal.c |   55 +++++++++++++++++++++++++++++++++++++++++++++++++
+ 1 files changed, 55 insertions(+), 0 deletions(-)
+
+diff --git a/src/remote_internal.c b/src/remote_internal.c
+index 4b3afb0..e665ef8 100644
+--- a/src/remote_internal.c
++++ b/src/remote_internal.c
+@@ -4978,6 +4978,59 @@ done:
+ }
+ 
+ 
++static virNodeDevicePtr
++remoteNodeDeviceCreateXML(virConnectPtr conn,
++                          const char *xmlDesc,
++                          unsigned int flags)
++{
++    remote_node_device_create_xml_args args;
++    remote_node_device_create_xml_ret ret;
++    virNodeDevicePtr dev = NULL;
++    struct private_data *priv = conn->privateData;
++
++    remoteDriverLock(priv);
++
++    memset(&ret, 0, sizeof ret);
++    args.xml_desc = (char *)xmlDesc;
++    args.flags = flags;
++
++    if (call(conn, priv, 0, REMOTE_PROC_NODE_DEVICE_CREATE_XML,
++             (xdrproc_t) xdr_remote_node_device_create_xml_args, (char *) &args,
++             (xdrproc_t) xdr_remote_node_device_create_xml_ret, (char *) &ret) == -1)
++        goto done;
++
++    dev = get_nonnull_node_device(conn, ret.dev);
++    xdr_free ((xdrproc_t) xdr_remote_node_device_create_xml_ret, (char *) &ret);
++
++done:
++    remoteDriverUnlock(priv);
++    return dev;
++}
++
++static int
++remoteNodeDeviceDestroy(virNodeDevicePtr dev)
++{
++    int rv = -1;
++    remote_node_device_destroy_args args;
++    struct private_data *priv = dev->conn->privateData;
++
++    remoteDriverLock(priv);
++
++    args.name = dev->name;
++
++    if (call(dev->conn, priv, 0, REMOTE_PROC_NODE_DEVICE_DESTROY,
++             (xdrproc_t) xdr_remote_node_device_destroy_args, (char *) &args,
++             (xdrproc_t) xdr_void, (char *) NULL) == -1)
++        goto done;
++
++    rv = 0;
++
++done:
++    remoteDriverUnlock(priv);
++    return rv;
++}
++
++
+ /*----------------------------------------------------------------------*/
+ 
+ static int
+@@ -6982,6 +7035,8 @@ static virDeviceMonitor dev_monitor = {
+     .deviceGetParent = remoteNodeDeviceGetParent,
+     .deviceNumOfCaps = remoteNodeDeviceNumOfCaps,
+     .deviceListCaps = remoteNodeDeviceListCaps,
++    .deviceCreateXML = remoteNodeDeviceCreateXML,
++    .deviceDestroy = remoteNodeDeviceDestroy
+ };
+ 
+ 
+-- 
+1.6.0.6
+
diff --git a/docs/api_extension/0006-Step-6-of-8-Implement-the-server-side-dispatcher.patch b/docs/api_extension/0006-Step-6-of-8-Implement-the-server-side-dispatcher.patch
new file mode 100644
index 0000000..96df453
--- /dev/null
+++ b/docs/api_extension/0006-Step-6-of-8-Implement-the-server-side-dispatcher.patch
@@ -0,0 +1,71 @@
+From 4c5166df583459574526841234d61d6ae5be19a0 Mon Sep 17 00:00:00 2001
+From: David Allan <dallan redhat com>
+Date: Tue, 19 May 2009 16:26:55 -0400
+Subject: [PATCH] Step 6 of 8 Implement the server side dispatcher
+
+---
+ qemud/remote.c |   48 ++++++++++++++++++++++++++++++++++++++++++++++++
+ 1 files changed, 48 insertions(+), 0 deletions(-)
+
+diff --git a/qemud/remote.c b/qemud/remote.c
+index e27820f..8d24a3a 100644
+--- a/qemud/remote.c
++++ b/qemud/remote.c
+@@ -4323,6 +4323,54 @@ remoteDispatchNodeDeviceReset (struct qemud_server *server ATTRIBUTE_UNUSED,
+ }
+ 
+ 
++static int
++remoteDispatchNodeDeviceCreateXml(struct qemud_server *server ATTRIBUTE_UNUSED,
++                                  struct qemud_client *client ATTRIBUTE_UNUSED,
++                                  virConnectPtr conn,
++                                  remote_error *rerr,
++                                  remote_node_device_create_xml_args *args,
++                                  remote_node_device_create_xml_ret *ret)
++{
++    virNodeDevicePtr dev;
++
++    dev = virNodeDeviceCreateXML (conn, args->xml_desc, args->flags);
++    if (dev == NULL) {
++        remoteDispatchConnError(rerr, conn);
++        return -1;
++    }
++
++    make_nonnull_node_device (&ret->dev, dev);
++    virNodeDeviceFree(dev);
++
++    return 0;
++}
++
++
++static int
++remoteDispatchNodeDeviceDestroy(struct qemud_server *server ATTRIBUTE_UNUSED,
++                                struct qemud_client *client ATTRIBUTE_UNUSED,
++                                virConnectPtr conn,
++                                remote_error *rerr,
++                                remote_node_device_destroy_args *args,
++                                void *ret ATTRIBUTE_UNUSED)
++{
++    virNodeDevicePtr dev;
++
++    dev = virNodeDeviceLookupByName(conn, args->name);
++    if (dev == NULL) {
++        remoteDispatchFormatError(rerr, "%s", _("node_device not found"));
++        return -1;
++    }
++
++    if (virNodeDeviceDestroy(dev) == -1) {
++        remoteDispatchConnError(rerr, conn);
++        return -1;
++    }
++
++    return 0;
++}
++
++
+ /**************************
+  * Async Events
+  **************************/
+-- 
+1.6.0.6
+
diff --git a/docs/api_extension/0007-Step-7-of-8-Implement-the-driver-methods.patch b/docs/api_extension/0007-Step-7-of-8-Implement-the-driver-methods.patch
new file mode 100644
index 0000000..ddd0a41
--- /dev/null
+++ b/docs/api_extension/0007-Step-7-of-8-Implement-the-driver-methods.patch
@@ -0,0 +1,1172 @@
+From 04d20a662109de6727232eb1213627877bb9662f Mon Sep 17 00:00:00 2001
+From: David Allan <dallan redhat com>
+Date: Tue, 19 May 2009 16:35:15 -0400
+Subject: [PATCH] Step 7 of 8 Implement the driver methods
+
+---
+ src/Makefile.am             |    4 +-
+ src/node_device.c           |  430 +++++++++++++++++++++++++++++++++++++++++++
+ src/node_device.h           |   13 ++
+ src/node_device_conf.c      |  136 ++++++++++++--
+ src/node_device_conf.h      |   22 ++-
+ src/node_device_hal.c       |    5 +
+ src/node_device_hal.h       |   40 ++++
+ src/node_device_hal_linux.c |  170 +++++++++++++++++
+ src/qemu_driver.c           |    2 +-
+ src/storage_backend.c       |   24 +--
+ src/xen_unified.c           |    2 +-
+ tests/nodedevxml2xmltest.c  |    2 +-
+ 12 files changed, 810 insertions(+), 40 deletions(-)
+ create mode 100644 src/node_device_hal.h
+ create mode 100644 src/node_device_hal_linux.c
+
+diff --git a/src/Makefile.am b/src/Makefile.am
+index fd692b4..39fabce 100644
+--- a/src/Makefile.am
++++ b/src/Makefile.am
+@@ -188,7 +188,9 @@ NODE_DEVICE_DRIVER_SOURCES =					\
+ 		node_device.c node_device.h
+ 
+ NODE_DEVICE_DRIVER_HAL_SOURCES =				\
+-		node_device_hal.c
++		node_device_hal.c				\
++		node_device_hal_linux.c
++
+ NODE_DEVICE_DRIVER_DEVKIT_SOURCES =				\
+ 		node_device_devkit.c
+ 
+diff --git a/src/node_device.c b/src/node_device.c
+index b84729f..4f73baf 100644
+--- a/src/node_device.c
++++ b/src/node_device.c
+@@ -25,6 +25,8 @@
+ 
+ #include <unistd.h>
+ #include <errno.h>
++#include <fcntl.h>
++#include <time.h>
+ 
+ #include "virterror_internal.h"
+ #include "datatypes.h"
+@@ -133,6 +135,53 @@ cleanup:
+     return ret;
+ }
+ 
++
++static virNodeDevicePtr
++nodeDeviceLookupByWWN(virConnectPtr conn,
++                      const char *wwnn,
++                      const char *wwpn)
++{
++    unsigned int i;
++    virDeviceMonitorStatePtr driver = conn->devMonPrivateData;
++    virNodeDeviceObjListPtr devs = &driver->devs;
++    virNodeDevCapsDefPtr cap = NULL;
++    virNodeDeviceObjPtr obj = NULL;
++    virNodeDevicePtr dev = NULL;
++
++    nodeDeviceLock(driver);
++
++    for (i = 0; i < devs->count; i++) {
++
++        obj = devs->objs[i];
++        virNodeDeviceObjLock(obj);
++        cap = obj->def->caps;
++
++        while (cap) {
++
++            if (cap->type == VIR_NODE_DEV_CAP_SCSI_HOST) {
++                if (cap->data.scsi_host.flags &
++                    VIR_NODE_DEV_CAP_FLAG_HBA_FC_HOST) {
++
++                    if (STREQ(cap->data.scsi_host.wwnn, wwnn) &&
++                        STREQ(cap->data.scsi_host.wwpn, wwpn)) {
++                        dev = virGetNodeDevice(conn, obj->def->name);
++                        virNodeDeviceObjUnlock(obj);
++                        goto out;
++                    }
++                }
++            }
++            cap = cap->next;
++        }
++
++        virNodeDeviceObjUnlock(obj);
++    }
++
++out:
++    nodeDeviceUnlock(driver);
++    return dev;
++}
++
++
+ static char *nodeDeviceDumpXML(virNodeDevicePtr dev,
+                                unsigned int flags ATTRIBUTE_UNUSED)
+ {
+@@ -258,6 +307,385 @@ cleanup:
+ }
+ 
+ 
++static int
++nodeDeviceVportCreateDelete(virConnectPtr conn,
++                            const int parent_host,
++                            const char *wwpn,
++                            const char *wwnn,
++                            int operation)
++{
++    int fd = -1;
++    int retval = 0;
++    char *operation_path = NULL, *vport_name = NULL;
++    const char *operation_file = NULL;
++    size_t towrite = 0;
++    unsigned int written = 0;
++
++    switch (operation) {
++    case VPORT_CREATE:
++        operation_file = LINUX_SYSFS_VPORT_CREATE_POSTFIX;
++        break;
++    case VPORT_DELETE:
++        operation_file = LINUX_SYSFS_VPORT_DELETE_POSTFIX;
++        break;
++    default:
++        virNodeDeviceReportError(conn, VIR_ERR_INTERNAL_ERROR,
++                                 _("Invalid vport operation (%d)"), operation);
++        retval = -1;
++        goto cleanup;
++        break;
++    }
++
++    if (virAsprintf(&operation_path,
++                    "%shost%d%s",
++                    LINUX_SYSFS_FC_HOST_PREFIX,
++                    parent_host,
++                    operation_file) < 0) {
++
++        virReportOOMError(conn);
++        retval = -1;
++        goto cleanup;
++    }
++
++    VIR_DEBUG(_("Vport operation path is '%s'"), operation_path);
++
++    fd = open(operation_path, O_WRONLY);
++
++    if (fd < 0) {
++        virReportSystemError(conn, errno,
++                             _("Could not open '%s' for vport operation"),
++                             operation_path);
++        retval = -1;
++        goto cleanup;
++    }
++
++    if (virAsprintf(&vport_name,
++                    "%s:%s",
++                    wwpn,
++                    wwnn) < 0) {
++
++        virReportOOMError(conn);
++        retval = -1;
++        goto cleanup;
++    }
++
++    towrite = strlen(vport_name);
++    written = safewrite(fd, vport_name, towrite);
++    if (written != towrite) {
++        virReportSystemError(conn, errno,
++                             _("Write of '%s' to '%s' during "
++                               "vport create/delete failed "
++                               "(towrite: %lu written: %d)"),
++                             vport_name, operation_path,
++                             towrite, written);
++        retval = -1;
++    }
++
++cleanup:
++    if (fd != -1) {
++        close(fd);
++    }
++    VIR_FREE(vport_name);
++    VIR_FREE(operation_path);
++    VIR_DEBUG("%s", _("Vport operation complete"));
++    return retval;
++}
++
++
++static int
++get_wwns(virConnectPtr conn,
++         virNodeDeviceDefPtr def,
++         char **wwnn,
++         char **wwpn)
++{
++    virNodeDevCapsDefPtr cap = NULL;
++    int ret = 0;
++
++    cap = def->caps;
++    while (cap != NULL) {
++        if (cap->type == VIR_NODE_DEV_CAP_SCSI_HOST &&
++            cap->data.scsi_host.flags & VIR_NODE_DEV_CAP_FLAG_HBA_FC_HOST) {
++            *wwnn = strdup(cap->data.scsi_host.wwnn);
++            *wwpn = strdup(cap->data.scsi_host.wwpn);
++            break;
++        }
++
++        cap = cap->next;
++    }
++
++    if (cap == NULL) {
++        virNodeDeviceReportError(conn, VIR_ERR_NO_SUPPORT,
++                                 "%s", _("Device is not a fibre channel HBA"));
++        ret = -1;
++    }
++
++    if (*wwnn == NULL || *wwpn == NULL) {
++        /* Free the other one, if allocated... */
++        VIR_FREE(wwnn);
++        VIR_FREE(wwpn);
++        ret = -1;
++        virReportOOMError(conn);
++    }
++
++    return ret;
++}
++
++
++static int
++get_parent_host(virConnectPtr conn,
++                virDeviceMonitorStatePtr driver,
++                const char *dev_name,
++                const char *parent_name,
++                int *parent_host)
++{
++    virNodeDeviceObjPtr parent = NULL;
++    virNodeDevCapsDefPtr cap = NULL;
++    int ret = 0;
++
++    parent = virNodeDeviceFindByName(&driver->devs, parent_name);
++    if (parent == NULL) {
++        virNodeDeviceReportError(conn, VIR_ERR_INVALID_NODE_DEVICE,
++                                 _("Could not find parent device for '%s'"),
++                                 dev_name);
++        ret = -1;
++        goto out;
++    }
++
++    cap = parent->def->caps;
++    while (cap != NULL) {
++        if (cap->type == VIR_NODE_DEV_CAP_SCSI_HOST &&
++            (cap->data.scsi_host.flags &
++             VIR_NODE_DEV_CAP_FLAG_HBA_VPORT_OPS)) {
++                *parent_host = cap->data.scsi_host.host;
++                break;
++        }
++
++        cap = cap->next;
++    }
++
++    if (cap == NULL) {
++        virNodeDeviceReportError(conn, VIR_ERR_INVALID_NODE_DEVICE,
++                                 _("Device %s is not capable of vport operations"),
++                                 parent->def->name);
++        ret = -1;
++    }
++
++    virNodeDeviceObjUnlock(parent);
++
++out:
++    return ret;
++}
++
++
++static int
++get_time(virConnectPtr conn, time_t *t)
++{
++    int ret = 0;
++
++    *t = time(NULL);
++    if (*t == (time_t)-1) {
++        virNodeDeviceReportError(conn, VIR_ERR_INTERNAL_ERROR,
++                                 "%s", _("Could not get current time"));
++
++        *t = 0;
++        ret = -1;
++    }
++
++    return ret;
++}
++
++
++/* When large numbers of devices are present on the host, it's
++ * possible for udev not to realize that it has work to do before we
++ * get here.  We thus keep trying to find the new device we just
++ * created for up to LINUX_NEW_DEVICE_WAIT_TIME.  Note that udev's
++ * default settle time is 180 seconds, so once udev realizes that it
++ * has work to do, it might take that long for the udev wait to
++ * return.  Thus the total maximum time for this function to return is
++ * the udev settle time plus LINUX_NEW_DEVICE_WAIT_TIME.
++ *
++ * This whole area is a race, but if we retry the udev wait for
++ * LINUX_NEW_DEVICE_WAIT_TIME seconds and there's still no device,
++ * it's probably safe to assume it's not going to appear.
++ */
++static virNodeDevicePtr
++find_new_device(virConnectPtr conn, const char *wwnn, const char *wwpn)
++{
++    virDeviceMonitorStatePtr driver = conn->devMonPrivateData;
++    virNodeDevicePtr dev = NULL;
++    time_t start = 0, now = 0;
++
++    /* The thread that creates the device takes the driver lock, so we
++     * must release it in order to allow the device to be created.
++     * We're not doing anything with the driver pointer at this point,
++     * so it's safe to release it, assuming that the pointer itself
++     * doesn't become invalid.  */
++    nodeDeviceUnlock(driver);
++
++    get_time(conn, &start);
++
++    while ((now - start) < LINUX_NEW_DEVICE_WAIT_TIME) {
++
++        virNodeDeviceWaitForDevices(conn);
++
++        dev = nodeDeviceLookupByWWN(conn, wwnn, wwpn);
++
++        if (dev != NULL) {
++            break;
++        }
++
++        sleep(5);
++        if (get_time(conn, &now) == -1) {
++            break;
++        }
++    }
++
++    nodeDeviceLock(driver);
++
++    return dev;
++}
++
++static virNodeDevicePtr
++nodeDeviceCreateXML(virConnectPtr conn,
++                    const char *xmlDesc,
++                    unsigned int flags ATTRIBUTE_UNUSED)
++{
++    virDeviceMonitorStatePtr driver = conn->devMonPrivateData;
++    virNodeDeviceDefPtr def = NULL;
++    char *wwnn = NULL, *wwpn = NULL;
++    int parent_host = -1;
++    virNodeDevicePtr dev = NULL;
++
++    nodeDeviceLock(driver);
++
++    def = virNodeDeviceDefParseString(conn, xmlDesc, CREATE_DEVICE);
++    if (def == NULL) {
++        goto cleanup;
++    }
++
++    if (get_wwns(conn, def, &wwnn, &wwpn) == -1) {
++        goto cleanup;
++    }
++
++    if (get_parent_host(conn,
++                        driver,
++                        def->name,
++                        def->parent,
++                        &parent_host) == -1) {
++        goto cleanup;
++    }
++
++    if (nodeDeviceVportCreateDelete(conn,
++                                    parent_host,
++                                    wwpn,
++                                    wwnn,
++                                    VPORT_CREATE) == -1) {
++        goto cleanup;
++    }
++
++    dev = find_new_device(conn, wwnn, wwpn);
++    /* We don't check the return value, because one way or another,
++     * we're returning what we get... */
++
++    if (dev == NULL) {
++        virNodeDeviceReportError(conn, VIR_ERR_NO_NODE_DEVICE, NULL);
++    }
++
++cleanup:
++    nodeDeviceUnlock(driver);
++    virNodeDeviceDefFree(def);
++    VIR_FREE(wwnn);
++    VIR_FREE(wwpn);
++    return dev;
++}
++
++
++static int
++nodeDeviceDestroy(virNodeDevicePtr dev)
++{
++    int ret = 0;
++    virDeviceMonitorStatePtr driver = dev->conn->devMonPrivateData;
++    virNodeDeviceObjPtr obj = NULL;
++    char *parent_name = NULL, *wwnn = NULL, *wwpn = NULL;
++    int parent_host = -1;
++
++    nodeDeviceLock(driver);
++    obj = virNodeDeviceFindByName(&driver->devs, dev->name);
++    nodeDeviceUnlock(driver);
++
++    if (!obj) {
++        virNodeDeviceReportError(dev->conn, VIR_ERR_NO_NODE_DEVICE, NULL);
++        goto out;
++    }
++
++    if (get_wwns(dev->conn, obj->def, &wwnn, &wwpn) == -1) {
++        goto out;
++    }
++
++    parent_name = strdup(obj->def->parent);
++
++    /* get_parent_host will cause the device object's lock to be
++     * taken, so we have to dup the parent's name and drop the lock
++     * before calling it.  We don't need the reference to the object
++     * any more once we have the parent's name.  */
++    virNodeDeviceObjUnlock(obj);
++    obj = NULL;
++
++    if (parent_name == NULL) {
++        virReportOOMError(dev->conn);
++        goto out;
++    }
++
++    if (get_parent_host(dev->conn,
++                        driver,
++                        dev->name,
++                        parent_name,
++                        &parent_host) == -1) {
++        goto out;
++    }
++
++    if (nodeDeviceVportCreateDelete(dev->conn,
++                                    parent_host,
++                                    wwpn,
++                                    wwnn,
++                                    VPORT_DELETE) == -1) {
++        goto out;
++    }
++
++out:
++    VIR_FREE(parent_name);
++    VIR_FREE(wwnn);
++    VIR_FREE(wwpn);
++    return ret;
++}
++
++
++#if defined(UDEVADM) || defined(UDEVSETTLE)
++void virNodeDeviceWaitForDevices(virConnectPtr conn)
++{
++#ifdef UDEVADM
++    const char *const settleprog[] = { UDEVADM, "settle", NULL };
++#else
++    const char *const settleprog[] = { UDEVSETTLE, NULL };
++#endif
++    int exitstatus;
++
++    if (access(settleprog[0], X_OK) != 0)
++        return;
++
++    /*
++     * NOTE: we ignore errors here; this is just to make sure that any device
++     * nodes that are being created finish before we try to scan them.
++     * If this fails for any reason, we still have the backup of polling for
++     * 5 seconds for device nodes.
++     */
++    virRun(conn, settleprog, &exitstatus);
++}
++#else
++void virNodeDeviceWaitForDevices(virConnectPtr conn ATTRIBUTE_UNUSED) {}
++#endif
++
++
+ void registerCommonNodeFuncs(virDeviceMonitorPtr driver)
+ {
+     driver->numOfDevices = nodeNumOfDevices;
+@@ -267,6 +695,8 @@ void registerCommonNodeFuncs(virDeviceMonitorPtr driver)
+     driver->deviceGetParent = nodeDeviceGetParent;
+     driver->deviceNumOfCaps = nodeDeviceNumOfCaps;
+     driver->deviceListCaps = nodeDeviceListCaps;
++    driver->deviceCreateXML = nodeDeviceCreateXML;
++    driver->deviceDestroy = nodeDeviceDestroy;
+ }
+ 
+ 
+diff --git a/src/node_device.h b/src/node_device.h
+index 9496120..882ba0f 100644
+--- a/src/node_device.h
++++ b/src/node_device.h
+@@ -28,6 +28,17 @@
+ #include "driver.h"
+ #include "node_device_conf.h"
+ 
++#define LINUX_SYSFS_SCSI_HOST_PREFIX "/sys/class/scsi_host"
++#define LINUX_SYSFS_SCSI_HOST_POSTFIX "device"
++#define LINUX_SYSFS_FC_HOST_PREFIX "/sys/class/fc_host/"
++
++#define VPORT_CREATE 0
++#define VPORT_DELETE 1
++#define LINUX_SYSFS_VPORT_CREATE_POSTFIX "/vport_create"
++#define LINUX_SYSFS_VPORT_DELETE_POSTFIX "/vport_delete"
++
++#define LINUX_NEW_DEVICE_WAIT_TIME 60
++
+ #ifdef HAVE_HAL
+ int halNodeRegister(void);
+ #endif
+@@ -42,4 +53,6 @@ void registerCommonNodeFuncs(virDeviceMonitorPtr mon);
+ 
+ int nodedevRegister(void);
+ 
++void virNodeDeviceWaitForDevices(virConnectPtr conn);
++
+ #endif /* __VIR_NODE_DEVICE_H__ */
+diff --git a/src/node_device_conf.c b/src/node_device_conf.c
+index 6e04112..5b35b60 100644
+--- a/src/node_device_conf.c
++++ b/src/node_device_conf.c
+@@ -53,9 +53,34 @@ VIR_ENUM_IMPL(virNodeDevNetCap, VIR_NODE_DEV_CAP_NET_LAST,
+               "80203",
+               "80211")
+ 
++VIR_ENUM_IMPL(virNodeDevHBACap, VIR_NODE_DEV_CAP_HBA_LAST,
++              "fc_host",
++              "vport_ops")
+ 
+ #define virNodeDeviceLog(msg...) fprintf(stderr, msg)
+ 
++static int
++virNodeDevCapsDefParseString(virConnectPtr conn,
++                             const char *xpath,
++                             xmlXPathContextPtr ctxt,
++                             char **string,
++                             virNodeDeviceDefPtr def,
++                             const char *missing_error_fmt)
++{
++    char *s;
++
++    s = virXPathString(conn, xpath, ctxt);
++    if (s == NULL) {
++        virNodeDeviceReportError(conn, VIR_ERR_INTERNAL_ERROR,
++                                 missing_error_fmt,
++                                 def->name);
++        return -1;
++    }
++
++    *string = s;
++    return 0;
++}
++
+ virNodeDeviceObjPtr virNodeDeviceFindByName(const virNodeDeviceObjListPtr devs,
+                                             const char *name)
+ {
+@@ -302,6 +327,18 @@ char *virNodeDeviceDefFormat(virConnectPtr conn,
+         case VIR_NODE_DEV_CAP_SCSI_HOST:
+             virBufferVSprintf(&buf, "    <host>%d</host>\n",
+                               data->scsi_host.host);
++            if (data->scsi_host.flags & VIR_NODE_DEV_CAP_FLAG_HBA_FC_HOST) {
++                virBufferAddLit(&buf, "    <capability type='fc_host'>\n");
++                virBufferVSprintf(&buf,
++                                  "      <wwnn>%s</wwnn>\n", data->scsi_host.wwnn);
++                virBufferVSprintf(&buf,
++                                  "      <wwpn>%s</wwpn>\n", data->scsi_host.wwpn);
++                virBufferAddLit(&buf, "    </capability>\n");
++            }
++            if (data->scsi_host.flags & VIR_NODE_DEV_CAP_FLAG_HBA_VPORT_OPS) {
++                virBufferAddLit(&buf, "    <capability type='vport_ops' />\n");
++            }
++
+             break;
+         case VIR_NODE_DEV_CAP_SCSI:
+             virBufferVSprintf(&buf, "    <host>%d</host>\n", data->scsi.host);
+@@ -561,26 +598,91 @@ virNodeDevCapScsiHostParseXML(virConnectPtr conn,
+                               xmlXPathContextPtr ctxt,
+                               virNodeDeviceDefPtr def,
+                               xmlNodePtr node,
+-                              union _virNodeDevCapData *data)
++                              union _virNodeDevCapData *data,
++                              int create)
+ {
+-    xmlNodePtr orignode;
+-    int ret = -1;
++    xmlNodePtr orignode, *nodes = NULL;
++    int ret = -1, n = 0, i;
++    char *type = NULL;
+ 
+     orignode = ctxt->node;
+     ctxt->node = node;
+ 
+-    if (virNodeDevCapsDefParseULong(conn, "number(./host[1])", ctxt,
++    if (create == EXISTING_DEVICE &&
++        virNodeDevCapsDefParseULong(conn, "number(./host[1])", ctxt,
+                                     &data->scsi_host.host, def,
+                                     _("no SCSI host ID supplied for '%s'"),
+-                                    _("invalid SCSI host ID supplied for '%s'")) < 0)
++                                    _("invalid SCSI host ID supplied for '%s'")) < 0) {
+         goto out;
++    }
++
++    if ((n = virXPathNodeSet(conn, "./capability", ctxt, &nodes)) < 0) {
++        virNodeDeviceReportError(conn, VIR_ERR_INTERNAL_ERROR,
++                                 _("error parsing SCSI host capabilities for '%s'"),
++                                 def->name);
++        goto out;
++    }
++
++    for (i = 0 ; i < n ; i++) {
++        type = virXMLPropString(nodes[i], "type");
++
++        if (!type) {
++            virNodeDeviceReportError(conn, VIR_ERR_INTERNAL_ERROR,
++                                     _("missing SCSI host capability type for '%s'"),
++                                     def->name);
++            goto out;
++        }
++
++        if (STREQ(type, "vport_ops")) {
++
++            data->scsi_host.flags |= VIR_NODE_DEV_CAP_FLAG_HBA_VPORT_OPS;
++
++        } else if (STREQ(type, "fc_host")) {
++
++            xmlNodePtr orignode2;
++
++            data->scsi_host.flags |= VIR_NODE_DEV_CAP_FLAG_HBA_FC_HOST;
++
++            orignode2 = ctxt->node;
++            ctxt->node = nodes[i];
++
++            if (virNodeDevCapsDefParseString(conn, "string(./wwnn[1])",
++                                             ctxt,
++                                             &data->scsi_host.wwnn,
++                                             def,
++                                             _("no WWNN supplied for '%s'")) < 0) {
++                goto out;
++            }
++
++            if (virNodeDevCapsDefParseString(conn, "string(./wwpn[1])",
++                                             ctxt,
++                                             &data->scsi_host.wwpn,
++                                             def,
++                                             _("no WWPN supplied for '%s'")) < 0) {
++                goto out;
++            }
++
++            ctxt->node = orignode2;
++
++        } else {
++            virNodeDeviceReportError(conn, VIR_ERR_INTERNAL_ERROR,
++                                     _("unknown SCSI host capability type '%s' for '%s'"),
++                                     type, def->name);
++            goto out;
++        }
++
++        VIR_FREE(type);
++    }
+ 
+     ret = 0;
++
+ out:
++    VIR_FREE(type);
+     ctxt->node = orignode;
+     return ret;
+ }
+ 
++
+ static int
+ virNodeDevCapNetParseXML(virConnectPtr conn,
+                          xmlXPathContextPtr ctxt,
+@@ -848,7 +950,8 @@ static virNodeDevCapsDefPtr
+ virNodeDevCapsDefParseXML(virConnectPtr conn,
+                           xmlXPathContextPtr ctxt,
+                           virNodeDeviceDefPtr def,
+-                          xmlNodePtr node)
++                          xmlNodePtr node,
++                          int create)
+ {
+     virNodeDevCapsDefPtr caps;
+     char *tmp;
+@@ -892,7 +995,7 @@ virNodeDevCapsDefParseXML(virConnectPtr conn,
+         ret = virNodeDevCapNetParseXML(conn, ctxt, def, node, &caps->data);
+         break;
+     case VIR_NODE_DEV_CAP_SCSI_HOST:
+-        ret = virNodeDevCapScsiHostParseXML(conn, ctxt, def, node, &caps->data);
++        ret = virNodeDevCapScsiHostParseXML(conn, ctxt, def, node, &caps->data, create);
+         break;
+     case VIR_NODE_DEV_CAP_SCSI:
+         ret = virNodeDevCapScsiParseXML(conn, ctxt, def, node, &caps->data);
+@@ -918,7 +1021,7 @@ error:
+ }
+ 
+ static virNodeDeviceDefPtr
+-virNodeDeviceDefParseXML(virConnectPtr conn, xmlXPathContextPtr ctxt)
++virNodeDeviceDefParseXML(virConnectPtr conn, xmlXPathContextPtr ctxt, int create)
+ {
+     virNodeDeviceDefPtr def;
+     virNodeDevCapsDefPtr *next_cap;
+@@ -931,7 +1034,12 @@ virNodeDeviceDefParseXML(virConnectPtr conn, xmlXPathContextPtr ctxt)
+     }
+ 
+     /* Extract device name */
+-    def->name = virXPathString(conn, "string(./name[1])", ctxt);
++    if (create == EXISTING_DEVICE) {
++        def->name = virXPathString(conn, "string(./name[1])", ctxt);
++    } else {
++        def->name = strdup("new device");
++    }
++
+     if (!def->name) {
+         virNodeDeviceReportError(conn, VIR_ERR_NO_NAME, NULL);
+         goto error;
+@@ -951,7 +1059,7 @@ virNodeDeviceDefParseXML(virConnectPtr conn, xmlXPathContextPtr ctxt)
+ 
+     next_cap = &def->caps;
+     for (i = 0 ; i < n ; i++) {
+-        *next_cap = virNodeDevCapsDefParseXML(conn, ctxt, def, nodes[i]);
++        *next_cap = virNodeDevCapsDefParseXML(conn, ctxt, def, nodes[i], create);
+         if (!*next_cap) {
+             VIR_FREE(nodes);
+             goto error;
+@@ -969,7 +1077,7 @@ virNodeDeviceDefParseXML(virConnectPtr conn, xmlXPathContextPtr ctxt)
+ }
+ 
+ static virNodeDeviceDefPtr
+-virNodeDeviceDefParseNode(virConnectPtr conn, xmlDocPtr xml, xmlNodePtr root)
++virNodeDeviceDefParseNode(virConnectPtr conn, xmlDocPtr xml, xmlNodePtr root, int create)
+ {
+     xmlXPathContextPtr ctxt = NULL;
+     virNodeDeviceDefPtr def = NULL;
+@@ -987,7 +1095,7 @@ virNodeDeviceDefParseNode(virConnectPtr conn, xmlDocPtr xml, xmlNodePtr root)
+     }
+ 
+     ctxt->node = root;
+-    def = virNodeDeviceDefParseXML(conn, ctxt);
++    def = virNodeDeviceDefParseXML(conn, ctxt, create);
+ 
+ cleanup:
+     xmlXPathFreeContext(ctxt);
+@@ -1015,7 +1123,7 @@ catchXMLError(void *ctx, const char *msg ATTRIBUTE_UNUSED, ...)
+ }
+ 
+ virNodeDeviceDefPtr
+-virNodeDeviceDefParseString(virConnectPtr conn, const char *str)
++virNodeDeviceDefParseString(virConnectPtr conn, const char *str, int create)
+ {
+     xmlParserCtxtPtr pctxt;
+     xmlDocPtr xml = NULL;
+@@ -1046,7 +1154,7 @@ virNodeDeviceDefParseString(virConnectPtr conn, const char *str)
+         goto cleanup;
+     }
+ 
+-    def = virNodeDeviceDefParseNode(conn, xml, root);
++    def = virNodeDeviceDefParseNode(conn, xml, root, create);
+ 
+ cleanup:
+     xmlFreeParserCtxt(pctxt);
+diff --git a/src/node_device_conf.h b/src/node_device_conf.h
+index 26e5558..62b4e71 100644
+--- a/src/node_device_conf.h
++++ b/src/node_device_conf.h
+@@ -28,6 +28,9 @@
+ #include "util.h"
+ #include "threads.h"
+ 
++#define CREATE_DEVICE 1
++#define EXISTING_DEVICE 0
++
+ enum virNodeDevCapType {
+     /* Keep in sync with VIR_ENUM_IMPL in node_device_conf.c */
+     VIR_NODE_DEV_CAP_SYSTEM,		/* System capability */
+@@ -48,8 +51,16 @@ enum virNodeDevNetCapType {
+     VIR_NODE_DEV_CAP_NET_LAST
+ };
+ 
++enum virNodeDevHBACapType {
++    /* Keep in sync with VIR_ENUM_IMPL in node_device_conf.c */
++    VIR_NODE_DEV_CAP_HBA_FC_HOST,	/* fibre channel HBA */
++    VIR_NODE_DEV_CAP_HBA_VPORT_OPS,	/* capable of vport operations */
++    VIR_NODE_DEV_CAP_HBA_LAST
++};
++
+ VIR_ENUM_DECL(virNodeDevCap)
+ VIR_ENUM_DECL(virNodeDevNetCap)
++VIR_ENUM_DECL(virNodeDevHBACap)
+ 
+ enum virNodeDevStorageCapFlags {
+     VIR_NODE_DEV_CAP_STORAGE_REMOVABLE			= (1 << 0),
+@@ -57,6 +68,11 @@ enum virNodeDevStorageCapFlags {
+     VIR_NODE_DEV_CAP_STORAGE_HOTPLUGGABLE		= (1 << 2),
+ };
+ 
++enum virNodeDevScsiHostCapFlags {
++    VIR_NODE_DEV_CAP_FLAG_HBA_FC_HOST			= (1 << 0),
++    VIR_NODE_DEV_CAP_FLAG_HBA_VPORT_OPS			= (1 << 1),
++};
++
+ typedef struct _virNodeDevCapsDef virNodeDevCapsDef;
+ typedef virNodeDevCapsDef *virNodeDevCapsDefPtr;
+ struct _virNodeDevCapsDef {
+@@ -108,6 +124,9 @@ struct _virNodeDevCapsDef {
+         } net;
+         struct {
+             unsigned host;
++            char *wwnn;
++            char *wwpn;
++            unsigned flags;
+         } scsi_host;
+         struct {
+             unsigned host;
+@@ -185,7 +204,8 @@ char *virNodeDeviceDefFormat(virConnectPtr conn,
+                              const virNodeDeviceDefPtr def);
+ 
+ virNodeDeviceDefPtr virNodeDeviceDefParseString(virConnectPtr conn,
+-                                                const char *str);
++                                                const char *str,
++                                                int create);
+ 
+ void virNodeDeviceDefFree(virNodeDeviceDefPtr def);
+ 
+diff --git a/src/node_device_hal.c b/src/node_device_hal.c
+index b214f60..5927ba1 100644
+--- a/src/node_device_hal.c
++++ b/src/node_device_hal.c
+@@ -28,6 +28,7 @@
+ #include <libhal.h>
+ 
+ #include "node_device_conf.h"
++#include "node_device_hal.h"
+ #include "virterror_internal.h"
+ #include "driver.h"
+ #include "datatypes.h"
+@@ -37,6 +38,8 @@
+ #include "logging.h"
+ #include "node_device.h"
+ 
++#define VIR_FROM_THIS VIR_FROM_NODEDEV
++
+ /*
+  * Host device enumeration (HAL implementation)
+  */
+@@ -215,6 +218,8 @@ static int gather_scsi_host_cap(LibHalContext *ctx, const char *udi,
+                                 union _virNodeDevCapData *d)
+ {
+     (void)get_int_prop(ctx, udi, "scsi_host.host", (int *)&d->scsi_host.host);
++    (void)check_fc_host(d);
++    (void)check_vport_capable(d);
+     return 0;
+ }
+ 
+diff --git a/src/node_device_hal.h b/src/node_device_hal.h
+new file mode 100644
+index 0000000..0b4a2ef
+--- /dev/null
++++ b/src/node_device_hal.h
+@@ -0,0 +1,40 @@
++/*
++ * node_device_hal.h: node device enumeration - HAL-based implementation
++ *
++ * Copyright (C) 2009 Red Hat, Inc.
++ *
++ * This library is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2.1 of the License, or (at your option) any later version.
++ *
++ * This library 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
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General Public
++ * License along with this library; if not, write to the Free Software
++ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
++ *
++ */
++
++#ifndef __VIR_NODE_DEVICE_HAL_H__
++#define __VIR_NODE_DEVICE_HAL_H__
++
++#ifdef __linux__
++
++#define check_fc_host(d) check_fc_host_linux(d)
++int check_fc_host_linux(union _virNodeDevCapData *d);
++
++#define check_vport_capable(d) check_vport_capable_linux(d)
++int check_vport_capable_linux(union _virNodeDevCapData *d);
++
++#else  /* __linux__ */
++
++#define check_fc_host(d)
++#define check_vport_capable(d)
++
++#endif /* __linux__ */
++
++#endif /* __VIR_NODE_DEVICE_HAL_H__ */
+diff --git a/src/node_device_hal_linux.c b/src/node_device_hal_linux.c
+new file mode 100644
+index 0000000..1deb6d2
+--- /dev/null
++++ b/src/node_device_hal_linux.c
+@@ -0,0 +1,170 @@
++/*
++ * node_device_hal_linuc.c: Linux specific code to gather device data
++ * not available through HAL.
++ *
++ * Copyright (C) 2009 Red Hat, Inc.
++ *
++ * This library is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2.1 of the License, or (at your option) any later version.
++ *
++ * This library 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
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General Public
++ * License along with this library; if not, write to the Free Software
++ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
++ *
++ */
++
++#include <config.h>
++
++#include <fcntl.h>
++
++#include "node_device.h"
++#include "node_device_hal.h"
++#include "virterror_internal.h"
++#include "memory.h"
++#include "logging.h"
++
++#define VIR_FROM_THIS VIR_FROM_NODEDEV
++
++#ifdef __linux__
++
++int check_fc_host_linux(union _virNodeDevCapData *d)
++{
++    char *sysfs_path = NULL;
++    char *wwnn_path = NULL;
++    char *wwpn_path = NULL;
++    char *p = NULL;
++    int fd = -1;
++    char buf[64];
++    struct stat st;
++
++    VIR_DEBUG(_("Checking if host%d is an FC HBA"), d->scsi_host.host);
++
++    if (virAsprintf(&sysfs_path, "%s/host%d",
++                    LINUX_SYSFS_FC_HOST_PREFIX,
++                    d->scsi_host.host) < 0) {
++        virReportOOMError(NULL);
++        goto out;
++    }
++
++    if (stat(sysfs_path, &st) != 0) {
++        /* Not an FC HBA */
++        goto out;
++    }
++
++    d->scsi_host.flags |= VIR_NODE_DEV_CAP_FLAG_HBA_FC_HOST;
++
++    if (virAsprintf(&wwnn_path, "%s/node_name",
++                    sysfs_path) < 0) {
++        virReportOOMError(NULL);
++        goto out;
++    }
++
++    if ((fd = open(wwnn_path, O_RDONLY)) < 0) {
++        goto out;
++    }
++
++    memset(buf, 0, sizeof(buf));
++    if (saferead(fd, buf, sizeof(buf)) < 0) {
++        goto out;
++    }
++
++    close(fd);
++    fd = -1;
++
++    p = strstr(buf, "0x");
++    if (p != NULL) {
++        p += strlen("0x");
++    } else {
++        p = buf;
++    }
++
++    d->scsi_host.wwnn = strndup(p, sizeof(buf));
++    if (d->scsi_host.wwnn == NULL) {
++        virReportOOMError(NULL);
++        goto out;
++    }
++
++    p = strchr(d->scsi_host.wwnn, '\n');
++    if (p != NULL) {
++        *p = '\0';
++    }
++
++    if (virAsprintf(&wwpn_path, "%s/port_name",
++                    sysfs_path) < 0) {
++        virReportOOMError(NULL);
++        goto out;
++    }
++
++    if ((fd = open(wwpn_path, O_RDONLY)) < 0) {
++        goto out;
++    }
++
++    memset(buf, 0, sizeof(buf));
++    if (saferead(fd, buf, sizeof(buf)) < 0) {
++        goto out;
++    }
++
++    close(fd);
++    fd = -1;
++
++    p = strstr(buf, "0x");
++    if (p != NULL) {
++        p += strlen("0x");
++    } else {
++        p = buf;
++    }
++
++    d->scsi_host.wwpn = strndup(p, sizeof(buf));
++    if (d->scsi_host.wwpn == NULL) {
++        virReportOOMError(NULL);
++        goto out;
++    }
++
++    p = strchr(d->scsi_host.wwpn, '\n');
++    if (p != NULL) {
++        *p = '\0';
++    }
++
++out:
++    if (fd != -1) {
++        close(fd);
++    }
++    VIR_FREE(sysfs_path);
++    VIR_FREE(wwnn_path);
++    VIR_FREE(wwpn_path);
++    return 0;
++}
++
++
++int check_vport_capable_linux(union _virNodeDevCapData *d)
++{
++    char *sysfs_path = NULL;
++    struct stat st;
++
++    if (virAsprintf(&sysfs_path, "%s/host%d/vport_create",
++                    LINUX_SYSFS_FC_HOST_PREFIX,
++                    d->scsi_host.host) < 0) {
++        virReportOOMError(NULL);
++        goto out;
++    }
++
++    if (stat(sysfs_path, &st) != 0) {
++        /* Not a vport capable HBA */
++        goto out;
++    }
++
++    d->scsi_host.flags |= VIR_NODE_DEV_CAP_FLAG_HBA_VPORT_OPS;
++
++out:
++    VIR_FREE(sysfs_path);
++    return 0;
++}
++
++#endif /* __linux__ */
+diff --git a/src/qemu_driver.c b/src/qemu_driver.c
+index bd60b29..057e97b 100644
+--- a/src/qemu_driver.c
++++ b/src/qemu_driver.c
+@@ -5089,7 +5089,7 @@ qemudNodeDeviceGetPciInfo (virNodeDevicePtr dev,
+     if (!xml)
+         goto out;
+ 
+-    def = virNodeDeviceDefParseString(dev->conn, xml);
++    def = virNodeDeviceDefParseString(dev->conn, xml, EXISTING_DEVICE);
+     if (!def)
+         goto out;
+ 
+diff --git a/src/storage_backend.c b/src/storage_backend.c
+index b154140..74759cf 100644
+--- a/src/storage_backend.c
++++ b/src/storage_backend.c
+@@ -46,6 +46,7 @@
+ #include "virterror_internal.h"
+ #include "util.h"
+ #include "memory.h"
++#include "node_device.h"
+ 
+ #include "storage_backend.h"
+ 
+@@ -245,30 +246,11 @@ virStorageBackendUpdateVolTargetInfoFD(virConnectPtr conn,
+     return 0;
+ }
+ 
+-#if defined(UDEVADM) || defined(UDEVSETTLE)
+ void virStorageBackendWaitForDevices(virConnectPtr conn)
+ {
+-#ifdef UDEVADM
+-    const char *const settleprog[] = { UDEVADM, "settle", NULL };
+-#else
+-    const char *const settleprog[] = { UDEVSETTLE, NULL };
+-#endif
+-    int exitstatus;
+-
+-    if (access(settleprog[0], X_OK) != 0)
+-        return;
+-
+-    /*
+-     * NOTE: we ignore errors here; this is just to make sure that any device
+-     * nodes that are being created finish before we try to scan them.
+-     * If this fails for any reason, we still have the backup of polling for
+-     * 5 seconds for device nodes.
+-     */
+-    virRun(conn, settleprog, &exitstatus);
++    virNodeDeviceWaitForDevices(conn);
++    return;
+ }
+-#else
+-void virStorageBackendWaitForDevices(virConnectPtr conn ATTRIBUTE_UNUSED) {}
+-#endif
+ 
+ /*
+  * Given a volume path directly in /dev/XXX, iterate over the
+diff --git a/src/xen_unified.c b/src/xen_unified.c
+index e708980..8da4e23 100644
+--- a/src/xen_unified.c
++++ b/src/xen_unified.c
+@@ -1439,7 +1439,7 @@ xenUnifiedNodeDeviceGetPciInfo (virNodeDevicePtr dev,
+     if (!xml)
+         goto out;
+ 
+-    def = virNodeDeviceDefParseString(dev->conn, xml);
++    def = virNodeDeviceDefParseString(dev->conn, xml, EXISTING_DEVICE);
+     if (!def)
+         goto out;
+ 
+diff --git a/tests/nodedevxml2xmltest.c b/tests/nodedevxml2xmltest.c
+index 29cdb9e..7621212 100644
+--- a/tests/nodedevxml2xmltest.c
++++ b/tests/nodedevxml2xmltest.c
+@@ -29,7 +29,7 @@ static int testCompareXMLToXMLFiles(const char *xml) {
+     if (virtTestLoadFile(xml, &xmlPtr, MAX_FILE) < 0)
+         goto fail;
+ 
+-    if (!(dev = virNodeDeviceDefParseString(NULL, xmlData)))
++    if (!(dev = virNodeDeviceDefParseString(NULL, xmlData, EXISTING_DEVICE)))
+         goto fail;
+ 
+     if (!(actual = virNodeDeviceDefFormat(NULL, dev)))
+-- 
+1.6.0.6
+
diff --git a/docs/api_extension/0008-Step-8-of-8-Add-virsh-support.patch b/docs/api_extension/0008-Step-8-of-8-Add-virsh-support.patch
new file mode 100644
index 0000000..55b758b
--- /dev/null
+++ b/docs/api_extension/0008-Step-8-of-8-Add-virsh-support.patch
@@ -0,0 +1,133 @@
+From 193cc4abbb6c2fc5557d3699f86ff0103d5a21ef Mon Sep 17 00:00:00 2001
+From: David Allan <dallan redhat com>
+Date: Tue, 19 May 2009 16:47:31 -0400
+Subject: [PATCH 8/8] Step 8 of 8 Add virsh support
+
+---
+ src/virsh.c |  103 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ 1 files changed, 103 insertions(+), 0 deletions(-)
+
+diff --git a/src/virsh.c b/src/virsh.c
+index cb32ede..ab2a2b7 100644
+--- a/src/virsh.c
++++ b/src/virsh.c
+@@ -2962,6 +2962,107 @@ cmdPoolCreate(vshControl *ctl, const vshCmd *cmd)
+ 
+ 
+ /*
++ * "nodedev-create" command
++ */
++static const vshCmdInfo info_node_device_create[] = {
++    {"help", gettext_noop("create a device defined "
++                          "by an XML file on the node")},
++    {"desc", gettext_noop("Create a device on the node.  Note that this "
++                          "command creates devices on the physical host "
++                          "that can then be assigned to a virtual machine.")},
++    {NULL, NULL}
++};
++
++static const vshCmdOptDef opts_node_device_create[] = {
++    {"file", VSH_OT_DATA, VSH_OFLAG_REQ,
++     gettext_noop("file containing an XML description of the device")},
++    {NULL, 0, 0, NULL}
++};
++
++static int
++cmdNodeDeviceCreate(vshControl *ctl, const vshCmd *cmd)
++{
++    virNodeDevicePtr dev = NULL;
++    char *from;
++    int found = 0;
++    int ret = TRUE;
++    char *buffer;
++
++    if (!vshConnectionUsability(ctl, ctl->conn, TRUE))
++        return FALSE;
++
++    from = vshCommandOptString(cmd, "file", &found);
++    if (!found) {
++        return FALSE;
++    }
++
++    if (virFileReadAll(from, VIRSH_MAX_XML_FILE, &buffer) < 0) {
++        return FALSE;
++    }
++
++    dev = virNodeDeviceCreateXML(ctl->conn, buffer, 0);
++    free (buffer);
++
++    if (dev != NULL) {
++        vshPrint(ctl, _("Node device %s created from %s\n"),
++                 virNodeDeviceGetName(dev), from);
++    } else {
++        vshError(ctl, FALSE, _("Failed to create node device from %s"), from);
++        ret = FALSE;
++    }
++
++    return ret;
++}
++
++
++/*
++ * "nodedev-destroy" command
++ */
++static const vshCmdInfo info_node_device_destroy[] = {
++    {"help", gettext_noop("destroy a device on the node")},
++    {"desc", gettext_noop("Destroy a device on the node.  Note that this "
++                          "command destroys devices on the physical host ")},
++    {NULL, NULL}
++};
++
++static const vshCmdOptDef opts_node_device_destroy[] = {
++    {"name", VSH_OT_DATA, VSH_OFLAG_REQ,
++     gettext_noop("name of the device to be destroyed")},
++    {NULL, 0, 0, NULL}
++};
++
++static int
++cmdNodeDeviceDestroy(vshControl *ctl, const vshCmd *cmd)
++{
++    virNodeDevicePtr dev = NULL;
++    int ret = TRUE;
++    int found = 0;
++    char *name;
++
++    if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) {
++        return FALSE;
++    }
++
++    name = vshCommandOptString(cmd, "name", &found);
++    if (!found) {
++        return FALSE;
++    }
++
++    dev = virNodeDeviceLookupByName(ctl->conn, name);
++
++    if (virNodeDeviceDestroy(dev) == 0) {
++        vshPrint(ctl, _("Destroyed node device '%s'\n"), name);
++    } else {
++        vshError(ctl, FALSE, _("Failed to destroy node device '%s'"), name);
++        ret = FALSE;
++    }
++
++    virNodeDeviceFree(dev);
++    return ret;
++}
++
++
++/*
+  * XML Building helper for pool-define-as and pool-create-as
+  */
+ static const vshCmdOptDef opts_pool_X_as[] = {
+@@ -5895,6 +5996,8 @@ static const vshCmdDef commands[] = {
+     {"nodedev-dettach", cmdNodeDeviceDettach, opts_node_device_dettach, info_node_device_dettach},
+     {"nodedev-reattach", cmdNodeDeviceReAttach, opts_node_device_reattach, info_node_device_reattach},
+     {"nodedev-reset", cmdNodeDeviceReset, opts_node_device_reset, info_node_device_reset},
++    {"nodedev-create", cmdNodeDeviceCreate, opts_node_device_create, info_node_device_create},
++    {"nodedev-destroy", cmdNodeDeviceDestroy, opts_node_device_destroy, info_node_device_destroy},
+ 
+     {"pool-autostart", cmdPoolAutostart, opts_pool_autostart, info_pool_autostart},
+     {"pool-build", cmdPoolBuild, opts_pool_build, info_pool_build},
+-- 
+1.6.0.6
+
diff --git a/docs/api_extension/api_extension.html b/docs/api_extension/api_extension.html
new file mode 100644
index 0000000..28250fb
--- /dev/null
+++ b/docs/api_extension/api_extension.html
@@ -0,0 +1,264 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+"http://www.w3.org/TR/html4/strict.dtd";>
+<html>
+<head>
+<style type="text/css">
+h1 {text-align:center}
+p {margin-left:10px} 
+p.filename {font-family:courier}
+ol.steps li {font-size:larger;font-weight:bold}
+ol.steps p {margin-left:-30px;font-size:smaller;font-weight:normal}
+ol.steps p.stepname {margin-left:10px;font-weight:bold}
+ol.ordinarylist li {margin-left:10px;font-size:smaller;font-weight:normal}
+</style>
+<title>Implementing a new API in Libvirt</title>
+</head>
+
+<body>
+<h1>Implementing a new API in Libvirt</h1>
+
+<p>This document walks you through the process of implementing a new
+API in libvirt.  It uses as an example the addition of the node device
+create and destroy APIs.</p>
+
+<p>Before you begin coding, it is critical that you propose your
+changes on the libvirt mailing list and get feedback on your ideas to
+make sure what you're proposing fits with the general direction of the
+project.  Even before doing a proof of concept implementation, send an
+email giving an overview of the functionality you think should be
+added to libvirt.  Someone may already be working on the feature you
+want.  Also, recognize that everything you write is likely to undergo
+significant rework as you discuss it with the other developers, so
+don't wait too long before getting feedback.</p>
+
+<p>Adding a new API to libvirt is not difficult, but there are quite a
+few steps.  This document assumes that you are familiar with C
+programming and have checked out the libvirt code from the source code
+repository and successfully built the existing tree.  Instructions on
+how to check out and build the code can be found at:</p>
+
+<p><a href="http://libvirt.org/downloads.html";>http://libvirt.org/downloads.html</a></p>
+
+<p>Once you have a working development environment, the steps to create a
+new API are:</p>
+<ol>
+    <li>define the public API</li>
+    <li>define the internal driver API</li>
+    <li>implement the public API</li>
+    <li>define the wire protocol format</li>
+    <li>implement the RPC client</li>
+    <li>implement the server side dispatcher</li>
+    <li>implement the driver methods</li>
+    <li>add virsh support</li>
+</ol>
+<p>It is, of course, possible to implement the pieces in any order, but
+if the development tasks are completed in the order listed, the code
+will compile after each step.  Given the number of changes required,
+verification after each step is highly recommended.</p>
+
+<p>Submit new code in the form shown in the example code: one patch
+per step.  That's not to say submit patches before you have working
+functionality--get the whole thing working and make sure you're happy
+with it.  Then use git or some other version control system that lets
+you rewrite your commit history and break patches into pieces so you
+don't drop a big blob of code on the mailing list at one go.  For
+example, I didn't follow my own advice when I originally submitted the
+example code to the libvirt list but rather submitted it in several
+large chunks.  I've used git's ability to rewrite my commit history to
+break the code apart into the example patches shown.</p>
+
+<p>Don't mix anything else into the patches you submit.  The patches
+should be the minimal changes required to implement the functionality
+you're adding.  If you notice a bug in unrelated code (i.e., code you
+don't have to touch to implement your API change) during development,
+create a patch that just addresses that bug and submit it
+separately.</p>
+
+<p>With that said, let's begin.</p>
+
+<ol class="steps">
+<li>
+<p class="stepname">DEFINING THE PUBLIC API</p>
+
+<p>The first task is to define the public API and add it to:</p>
+
+<p class="filename">include/libvirt/libvirt.h.in</p>
+
+<p>This task is in many ways the most important to get right, since once
+the API has been committed to the repository, it's libvirt's policy
+never to change it.  Mistakes in the implementation are bugs that you
+can fix.  Make a mistake in the API definition and you're stuck with
+it, so think carefully about the interface and don't be afraid to
+rework it as you go through the process of implementing it.</p>
+
+<p>Once you have defined the API, you have to add the symbol names to:</p>
+
+<p class="filename">src/libvirt_public.syms</p>
+
+<p class="example">See <a href="0001-Step-1-of-8-Define-the-public-API.patch">0001-Step-1-of-8-Define-the-public-API.patch</a> for example code.</p>
+</li>
+<li>
+<p class="stepname">DEFINING THE INTERNAL API</p>
+
+<p>Each public API call is associated with a driver, such as a host
+virtualization driver, a network virtualization driver, a storage
+virtualization driver, a state driver, or a device monitor.  Adding
+the internal API is ordinarily a matter of adding a new member to the
+struct representing one of these drivers.</p>
+
+<p>Of course, it's possible that the new API will involve the creation of
+an entire new driver type, in which case the changes will include the
+creation of a new struct type to represent the new driver type.</p>
+
+<p>The driver structs are defined in:</p>
+
+<p class="filename">src/driver.h</p>
+
+<p>To define the internal API, first typedef the driver function
+prototype and then add a new field for it to the relevant driver
+struct.</p>
+
+<p class="example">See <a href="0002-Step-2-of-8-Define-the-internal-driver-API.patch">0002-Step-2-of-8-Define-the-internal-driver-API.patch</a></p>
+</li>
+<li>
+<p class="stepname">IMPLEMENTING THE PUBLIC API</p>
+
+<p>Implementing the public API is largely a formality in which we wire up
+public API to the internal driver API.  The public API implementation
+takes care of some basic validity checks before passing control to the
+driver implementation.  In RFC 2119 vocabulary, this function:</p>
+<ol class="ordinarylist">
+  <li>SHOULD log a message with VIR_DEBUG() indicating that it is
+    being called and its parameters;</li>
+  <li>MUST call virResetLastError();</li>
+  <li>SHOULD confirm that the connection is valid with
+    VIR_IS_CONNECT(conn);</li>
+  <li>If the API requires a connection with write privileges, SHOULD
+    confirm that the connection flags do not indicate that the
+    connection is read-only;</li>
+  <li>SHOULD do basic validation of the parameters that are being
+    passed in;</li>
+  <li>MUST confirm that the driver for this connection exists and that
+     it implements this function;</li>
+  <li>MUST call the internal API;</li>
+  <li>SHOULD log a message with VIR_DEBUG() indicating that it is
+    returning, its return value, and status.</li>
+  <li>MUST return status to the caller.</li>
+</ol>
+
+<p>The public API calls are implemented in:</p>
+
+<p class="filename">src/libvirt.c</p>
+
+<p class="example">See <a href="0003-Step-3-of-8-Implement-the-public-API.patch">0003-Step-3-of-8-Implement-the-public-API.patch</a></p>
+</li>
+<li>
+<p class="stepname">DEFINING THE WIRE PROTOCOL FORMAT</p>
+
+<p>Defining the wire protocol is essentially a straightforward exercise
+which is probably most easily understood by referring to the existing
+remote protocol wire format definitions and the example patch.  It
+involves making two additions to:</p>
+
+<p class="filename">qemud/remote_protocol.x</p>
+
+<p>First, create two new structs for each new function that you're adding
+to the API.  One struct describes the parameters to be passed to the
+remote function, and a second struct describes the value returned by
+the remote function.  The one exception to this rule is that functions
+that return only integer status do not require a struct for returned
+data.</p>
+
+<p>Second, add values to the remote_procedure enum for each new function
+added to the API.</p>
+
+<p class="example">See <a href="0004-Step-4-of-8-Define-the-wire-protocol-format.patch">0004-Step-4-of-8-Define-the-wire-protocol-format.patch</a></p>
+
+<p>Once these changes are in place, it's necessary to run 'make rpcgen'
+in the qemud directory to create the .c and .h files required by the
+remote protocol code.</p>
+</li>
+<li>
+<p class="stepname">IMPLEMENT THE RPC CLIENT</p>
+
+<p>Implementing the RPC client is also relatively mechanical, so refer to
+the exising code and example patch for guidance.  The RPC client uses
+the rpcgen generated .h files.  The remote method calls go in:</p>
+
+<p class="filename">src/remote_internal.c</p>
+
+<p>Each remote method invocation does the following:</p>
+
+<ol class="ordinarylist">
+     <li>locks the remote driver;</li>
+     <li>sets up the method arguments;</li>
+     <li>invokes the remote function;</li>
+     <li>checks the return value, if necessary;</li>
+     <li>extracts any returned data;</li>
+     <li>frees any returned data;</li>
+     <li>unlocks the remote driver.</li>
+</ol>
+
+<p>Once you have created the remote method calls, you have to add fields
+for them to the driver structs for the appropriate remote driver.</p>
+
+<p class="example">See <a href="0005-Step-5-of-8-Implement-the-RPC-client.patch">0005-Step-5-of-8-Implement-the-RPC-client.patch</a></p>
+</li>
+<li>
+<p class=stepname>IMPLEMENTING THE SERVER SIDE DISPATCHER</p>
+
+<p>Implementing the server side of the remote function calls is simply a
+matter of deserializing the parameters passed in from the remote
+caller and passing them to the corresponding internal API function.
+The server side dispatchers are implemented in:</p>
+
+<p class="filename">qemud/remote.c</p>
+
+<p>Again, this step uses the .h files generated by make rpcgen.</p>
+
+<p class="example">See <a href="0006-Step-6-of-8-Implement-the-server-side-dispatcher.patch">0006-Step-6-of-8-Implement-the-server-side-dispatcher.patch</a></p>
+</li>
+<li>
+<p class=stepname>IMPLEMENT THE DRIVER METHODS</p>
+
+<p>So, after all that, we get to the fun part.  All functionality in
+libvirt is implemented inside a driver.  Thus, here is where you
+implement whatever functionality you're adding to libvirt.  You'll
+either need to add additional files to the src directory or extend
+files that are already there, depending on what functionality you're
+adding.</p>
+
+<p>In the example code, the extension is only an additional two function
+calls in the node device API, so most of the new code is additions to
+existing files.  The only new files are there for multi-platform
+implementation convenience, as some of the new code is Linux specific.</p>
+
+<p>The example code is probably uninteresting unless you're concerned
+with libvirt storage, but I've included it here to show how new files
+are added to the build environment.</p>
+
+<p class="example">See <a href="0007-Step-7-of-8-Implement-the-driver-methods.patch">0007-Step-7-of-8-Implement-the-driver-methods.patch</a></p>
+</li>
+<li>
+<p class=stepname>IMPLEMENT VIRSH SUPPORT</p>
+
+<p>Once you have the new functionality in place, the easiest way to test
+it and also to provide it to end users is to implement support for it
+in virsh.  </p>
+
+<p>A virsh command is composed of a few pieces of code.  You need to
+define an array of vshCmdInfo structs for each new command that
+contain the help text and the command description text.  You also need
+an array of vshCmdOptDef structs to describe the command options.
+Once you have those pieces of data in place you can write the function
+implementing the virsh command.  Finally, you need to add the new
+command to the commands[] array.</p>
+
+<p class="example">See <a href="0008-Step-8-of-8-Add-virsh-support.patch">0008-Step-8-of-8-Add-virsh-support.patch</a></p>
+</li>
+</ol>
+
+<p>Once you have working functionality, run make check and make
+syntax-check before generating patches.</p>
+</body>
+</html>
diff --git a/docs/sitemap.html.in b/docs/sitemap.html.in
index 00328e1..b850a0b 100644
--- a/docs/sitemap.html.in
+++ b/docs/sitemap.html.in
@@ -65,6 +65,10 @@
                 <span>The libvirt API concepts</span>
               </li>
               <li>
+                <a href="api_extension/api_extension.html">Internals</a>
+                <span>Extending the libvirt APIs</span>
+              </li>
+              <li>
                 <a href="archdomain.html">Domains</a>
                 <span>Managing virtual machines</span>
               </li>
-- 
1.6.0.6


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]