diff --git a/ChangeLog b/ChangeLog index 4bdf4e2782..970e12409f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +Tue Jun 2 17:05:52 CEST 2009 Daniel Veillard + + * src/Makefile.am src/node_device.[ch] src/node_device_conf.[ch] + src/node_device_hal.[ch] src/node_device_hal_linux.c + src/qemu_driver.c src/remote_internal.c src/storage_backend.c + src/virsh.c src/xen_unified.c tests/nodedevxml2xmltest.c + po/POTFILES.in: implementation for node device create and destroy + in NPIV support, patch by David Allan + Fri May 29 18:37:17 CEST 2009 Daniel Veillard * configure.in libvirt.spec.in NEWS docs/* po/*: release of diff --git a/po/POTFILES.in b/po/POTFILES.in index 925cd29393..973f580fd3 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -15,6 +15,7 @@ src/network_conf.c src/network_driver.c src/node_device.c src/node_device_conf.c +src/node_device_hal_linux.c src/nodeinfo.c src/opennebula/one_conf.c src/opennebula/one_driver.c diff --git a/src/Makefile.am b/src/Makefile.am index 76d178302d..03d4cf5114 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -195,7 +195,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 b84729f778..2cdf5b3adb 100644 --- a/src/node_device.c +++ b/src/node_device.c @@ -25,6 +25,8 @@ #include #include +#include +#include #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,366 @@ cleanup: } +static int +nodeDeviceVportCreateDelete(virConnectPtr conn, + const int parent_host, + const char *wwpn, + const char *wwnn, + int operation) +{ + int retval = 0; + char *operation_path = NULL, *vport_name = NULL; + const char *operation_file = NULL; + + 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); + + if (virAsprintf(&vport_name, + "%s:%s", + wwpn, + wwnn) < 0) { + + virReportOOMError(conn); + retval = -1; + goto cleanup; + } + + if (virFileWriteStr(operation_path, vport_name) == -1) { + virReportSystemError(conn, errno, + _("Write of '%s' to '%s' during " + "vport create/delete failed"), + vport_name, operation_path); + retval = -1; + } + +cleanup: + 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_INTERNAL_ERROR, + _("Could not find parent HBA 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_INTERNAL_ERROR, + _("Parent HBA %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 +676,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 9496120e6b..882ba0f5cd 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 236042743f..85fba670cb 100644 --- a/src/node_device_conf.c +++ b/src/node_device_conf.c @@ -53,6 +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") + + +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) { @@ -299,6 +327,18 @@ char *virNodeDeviceDefFormat(virConnectPtr conn, case VIR_NODE_DEV_CAP_SCSI_HOST: virBufferVSprintf(&buf, " %d\n", data->scsi_host.host); + if (data->scsi_host.flags & VIR_NODE_DEV_CAP_FLAG_HBA_FC_HOST) { + virBufferAddLit(&buf, " \n"); + virBufferVSprintf(&buf, + " %s\n", data->scsi_host.wwnn); + virBufferVSprintf(&buf, + " %s\n", data->scsi_host.wwpn); + virBufferAddLit(&buf, " \n"); + } + if (data->scsi_host.flags & VIR_NODE_DEV_CAP_FLAG_HBA_VPORT_OPS) { + virBufferAddLit(&buf, " \n"); + } + break; case VIR_NODE_DEV_CAP_SCSI: virBufferVSprintf(&buf, " %d\n", data->scsi.host); @@ -558,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, @@ -845,7 +950,8 @@ static virNodeDevCapsDefPtr virNodeDevCapsDefParseXML(virConnectPtr conn, xmlXPathContextPtr ctxt, virNodeDeviceDefPtr def, - xmlNodePtr node) + xmlNodePtr node, + int create) { virNodeDevCapsDefPtr caps; char *tmp; @@ -889,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); @@ -915,7 +1021,7 @@ error: } static virNodeDeviceDefPtr -virNodeDeviceDefParseXML(virConnectPtr conn, xmlXPathContextPtr ctxt) +virNodeDeviceDefParseXML(virConnectPtr conn, xmlXPathContextPtr ctxt, int create) { virNodeDeviceDefPtr def; virNodeDevCapsDefPtr *next_cap; @@ -928,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; @@ -948,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; @@ -966,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; @@ -984,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); @@ -1012,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; @@ -1043,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 26e555809e..62b4e71a65 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 2f7ca8db00..0fd7e886e5 100644 --- a/src/node_device_hal.c +++ b/src/node_device_hal.c @@ -28,6 +28,7 @@ #include #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) */ @@ -214,8 +217,20 @@ static int gather_net_cap(LibHalContext *ctx, const char *udi, static int gather_scsi_host_cap(LibHalContext *ctx, const char *udi, union _virNodeDevCapData *d) { + int retval = 0; + (void)get_int_prop(ctx, udi, "scsi_host.host", (int *)&d->scsi_host.host); - return 0; + + retval = check_fc_host(d); + + if (retval == -1) { + goto out; + } + + retval = check_vport_capable(d); + +out: + return retval; } diff --git a/src/node_device_hal.h b/src/node_device_hal.h new file mode 100644 index 0000000000..0b4a2efe0b --- /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 0000000000..b76235daa4 --- /dev/null +++ b/src/node_device_hal_linux.c @@ -0,0 +1,189 @@ +/* + * 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 + +#include + +#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, retval = 0; + 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); + retval = -1; + goto out; + } + + if (stat(sysfs_path, &st) != 0) { + /* Not an FC HBA; not an error, either. */ + 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); + retval = -1; + goto out; + } + + if ((fd = open(wwnn_path, O_RDONLY)) < 0) { + retval = -1; + VIR_ERROR(_("Failed to open WWNN path '%s' for reading"), + wwnn_path); + goto out; + } + + memset(buf, 0, sizeof(buf)); + if (saferead(fd, buf, sizeof(buf)) < 0) { + retval = -1; + VIR_ERROR(_("Failed to read WWNN from '%s'"), + wwnn_path); + 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); + retval = -1; + 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); + retval = -1; + goto out; + } + + if ((fd = open(wwpn_path, O_RDONLY)) < 0) { + retval = -1; + VIR_ERROR(_("Failed to open WWPN path '%s' for reading"), + wwpn_path); + goto out; + } + + memset(buf, 0, sizeof(buf)); + if (saferead(fd, buf, sizeof(buf)) < 0) { + retval = -1; + VIR_ERROR(_("Failed to read WWPN from '%s'"), + wwpn_path); + 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); + retval = -1; + 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; + int retval = 0; + + if (virAsprintf(&sysfs_path, "%s/host%d/vport_create", + LINUX_SYSFS_FC_HOST_PREFIX, + d->scsi_host.host) < 0) { + virReportOOMError(NULL); + retval = -1; + goto out; + } + + if (stat(sysfs_path, &st) != 0) { + /* Not a vport capable HBA; not an error, either. */ + goto out; + } + + d->scsi_host.flags |= VIR_NODE_DEV_CAP_FLAG_HBA_VPORT_OPS; + +out: + VIR_FREE(sysfs_path); + return retval; +} + +#endif /* __linux__ */ diff --git a/src/qemu_driver.c b/src/qemu_driver.c index 5f23bfab8b..1ac1d15f99 100644 --- a/src/qemu_driver.c +++ b/src/qemu_driver.c @@ -5240,7 +5240,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/remote_internal.c b/src/remote_internal.c index e70527a54b..0363ba4145 100644 --- a/src/remote_internal.c +++ b/src/remote_internal.c @@ -5434,7 +5434,7 @@ remoteNodeDeviceDestroy(virNodeDevicePtr dev) args.name = dev->name; - if (call(dev->conn, priv, 0, REMOTE_PROC_NODE_DEVICE_RESET, + 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; diff --git a/src/storage_backend.c b/src/storage_backend.c index 96cf37ccac..d731a23080 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" @@ -256,30 +257,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/virsh.c b/src/virsh.c index e6241e6292..4c41a53a4f 100644 --- a/src/virsh.c +++ b/src/virsh.c @@ -3053,6 +3053,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 */ @@ -6153,6 +6254,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}, diff --git a/src/xen_unified.c b/src/xen_unified.c index 7ff23b8216..57efb40a14 100644 --- a/src/xen_unified.c +++ b/src/xen_unified.c @@ -1530,7 +1530,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 29cdb9e3b2..7621212791 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)))