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