[libvirt] [PATCH 0/1] API extension tutorial

Here is a document describing the process for extending the libvirt API. Note that the patch includes 8 sample patches that go with the document that should be included in and not applied to the tree. Dave

--- .../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 ++++++++++++++++++++ docs/api_extension/API_implementation_guide.txt | 225 ++++ 8 files changed, 1803 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/API_implementation_guide.txt 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/API_implementation_guide.txt b/docs/api_extension/API_implementation_guide.txt new file mode 100644 index 0000000..45a8986 --- /dev/null +++ b/docs/api_extension/API_implementation_guide.txt @@ -0,0 +1,225 @@ +Implementing a new API in Libvirt + +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. + +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: + +http://libvirt.org/downloads.html + +Once you have a working development environment, the steps to create a +new API are: + + 1. define the public API + 2. define the internal driver API + 3. implement the public API + 4. define the wire protocol format + 5. implement the RPC client + 6. implement the server side dispatcher + 7. implement the driver methods + 8. add virsh support + +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. + +1) DEFINING THE PUBLIC API + +The first task is to define the public API and add it to: + +include/libvirt/libvirt.h.in + +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. + +Once you have defined the API, you have to add the symbol names to: + +src/libvirt_public.syms + +See 0001-Step-1-of-8-Define-the-public-API.patch for example code. + +2) DEFINING THE INTERNAL API + +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. + +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. + +The driver structs are defined in: + +src/driver.h + +To define the internal API, first typedef the driver function +prototype and then add a new field for it to the relevant driver +struct. + +See 0002-Step-2-of-8-Define-the-internal-driver-API.patch + +3) IMPLEMENTING THE PUBLIC API + +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: + + a) SHOULD log a message with VIR_DEBUG() indicating that it is being + called and its parameters; + + b) MUST call virResetLastError(); + + c) SHOULD confirm that the connection is valid with + VIR_IS_CONNECT(conn); + + d) If the API requires a connection with write privileges, SHOULD + confirm that the connection flags do not indicate that the + connection is read-only; + + e) SHOULD do basic validation of the parameters that are being + passed in; + + f) MUST confirm that the driver for this connection exists and that + it implements this function; + + g) MUST call the internal API; + + h) SHOULD log a message with VIR_DEBUG() indicating that it is + returning, its return value, and status. + + i) MUST return status to the caller. + +The public API calls are implemented in: + +src/libvirt.c + +See 0003-Step-3-of-8-Implement-the-public-API.patch + +4. DEFINING THE WIRE PROTOCOL FORMAT + +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: + +qemud/remote_protocol.x + +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. + +Second, add values to the remote_procedure enum for each new function +added to the API. + +See 0004-Step-4-of-8-Define-the-wire-protocol-format.patch + +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. + +5. IMPLEMENT THE RPC CLIENT + +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: + +src/remote_internal.c + +Each remote method invocation does the following: + + a) locks the remote driver; + b) sets up the method arguments; + c) invokes the remote function; + d) checks the return value, if necessary; + e) extracts any returned data; + f) frees any returned data; + g) unlocks the remote driver. + +Once you have created the remote method calls, you have to add fields +for them to the driver structs for the appropriate remote driver. + +See 0005-Step-5-of-8-Implement-the-RPC-client.patch + +6. IMPLEMENTING THE SERVER SIDE DISPATCHER + +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: + +qemud/remote.c + +Again, this step uses the .h files generated by make rpcgen. + +See 0006-Step-6-of-8-Implement-the-server-side-dispatcher.patch + +7. IMPLEMENT THE DRIVER METHODS + +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. + +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. + +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. + +See 0007-Step-7-of-8-Implement-the-driver-methods.patch + +8. IMPLEMENT VIRSH SUPPORT + +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. + +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. + +See 0008-Step-8-of-8-Add-virsh-support.patch + +GENERAL NOTES ON SUBMITTING API EXTENSIONS TO LIBVIRT + +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. + +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. -- 1.6.0.6

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 -- |: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://ovirt.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|

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
Sounds good--I'll do that. Dave

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

On Wed, May 27, 2009 at 01:08:04PM -0400, David Allan wrote:
---
Looks good but IMHO it misses one of the main point, it focuses only on the technical aspects of the submission but not at all on the process and interraction with the list. Basically, before engaging in adding a new API for libvirt, the best is to discuss a first draft of the suggested changes to libvirt.h, then make sure it gets reviewed, and start developping the code only after a first on-list validation step. There is nothing worse than working a week on a patch sending it to the list and learning that the thing could not work because it breaks some preestablished rules. I think the most common example would be a patch to add raw extra qemu command line options to the API :-) But that can be added next to your description, or included within thanks a lot :-) Daniel -- Daniel Veillard | libxml Gnome XML XSLT toolkit http://xmlsoft.org/ daniel@veillard.com | Rpmfind RPM search engine http://rpmfind.net/ http://veillard.com/ | virtualization library http://libvirt.org/

Daniel Veillard wrote:
On Wed, May 27, 2009 at 01:08:04PM -0400, David Allan wrote:
---
Looks good but IMHO it misses one of the main point, it focuses only on the technical aspects of the submission but not at all on the process and interraction with the list. Basically, before engaging in adding a new API for libvirt, the best is to discuss a first draft of the suggested changes to libvirt.h, then make sure it gets reviewed, and start developping the code only after a first on-list validation step. There is nothing worse than working a week on a patch sending it to the list and learning that the thing could not work because it breaks some preestablished rules. I think the most common example would be a patch to add raw extra qemu command line options to the API :-)
That's a good point. I deliberately limited the document to the technical aspects of the work, but I do agree that people need to be told right at the start about the need to participate in the discussion before anything; I'll add that. We should (i.e., you guys who have been here since the beginning should) list the the current pre-established rules and I can put them in a separate doc/wiki page and link to it from my doc. Would that work? What are the other NO-NOs?
But that can be added next to your description, or included within thanks a lot :-)
Daniel

On Thu, May 28, 2009 at 01:02:08PM -0400, Dave Allan wrote:
Daniel Veillard wrote:
On Wed, May 27, 2009 at 01:08:04PM -0400, David Allan wrote:
---
Looks good but IMHO it misses one of the main point, it focuses only on the technical aspects of the submission but not at all on the process and interraction with the list. Basically, before engaging in adding a new API for libvirt, the best is to discuss a first draft of the suggested changes to libvirt.h, then make sure it gets reviewed, and start developping the code only after a first on-list validation step. There is nothing worse than working a week on a patch sending it to the list and learning that the thing could not work because it breaks some preestablished rules. I think the most common example would be a patch to add raw extra qemu command line options to the API :-)
That's a good point. I deliberately limited the document to the technical aspects of the work, but I do agree that people need to be told right at the start about the need to participate in the discussion before anything; I'll add that.
We should (i.e., you guys who have been here since the beginning should) list the the current pre-established rules and I can put them in a separate doc/wiki page and link to it from my doc. Would that work?
Yeah, I think the general development process guidelines should be separate from your doc, which is about technicalities of adding new APIs. If Rich dones't mind, we should probably incorporate a large chunk of this page into a 'process guidelines' page http://et.redhat.com/~rjones/how-to-supply-code-to-open-source-projects/ Daniel -- |: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://ovirt.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|
participants (4)
-
Daniel P. Berrange
-
Daniel Veillard
-
Dave Allan
-
David Allan