--- src/node_device.c | 355 ++++++++++++++++++++++++++++++++++++++++++++++++ src/node_device.h | 14 ++ src/node_device_conf.c | 106 ++++++++++++++- src/node_device_conf.h | 16 ++ src/node_device_hal.c | 134 ++++++++++++++++++ src/storage_backend.c | 24 +--- src/virsh.c | 53 +++++++ 7 files changed, 678 insertions(+), 24 deletions(-) diff --git a/src/node_device.c b/src/node_device.c index b84729f..25d3251 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; } + +/* Caller must hold the driver lock. */ +static virNodeDevicePtr +nodeDeviceLookupByWWN(virConnectPtr conn, + const char *wwnn, + const char *wwpn) +{ + unsigned int i, found = 0; + virDeviceMonitorStatePtr driver = conn->devMonPrivateData; + virNodeDeviceObjListPtr devs = &driver->devs; + virNodeDevCapsDefPtr cap = NULL; + virNodeDeviceObjPtr obj = NULL; + virNodeDevicePtr dev = NULL; + + 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)) { + found = 1; + goto out; + } + } + } + cap = cap->next; + } + + virNodeDeviceObjUnlock(obj); + } + +out: + if (found) { + dev = virGetNodeDevice(conn, obj->def->name); + virNodeDeviceObjUnlock(obj); + } + + return dev; +} + + static char *nodeDeviceDumpXML(virNodeDevicePtr dev, unsigned int flags ATTRIBUTE_UNUSED) { @@ -258,6 +307,310 @@ 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; + const char *operation_file; + char *vport_name; + 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 no_unwind; + break; + } + + if (virAsprintf(&operation_path, + "%shost%d%s", + LINUX_SYSFS_FC_HOST_PREFIX, + parent_host, + operation_file) < 0) { + + virReportOOMError(conn); + retval = -1; + goto no_unwind; + } + + 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 free_path; + } + + if (virAsprintf(&vport_name, + "%s:%s", + wwpn, + wwnn) < 0) { + + virReportOOMError(conn); + retval = -1; + goto close_fd; + } + + 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; + } + + VIR_FREE(vport_name); +close_fd: + close(fd); +free_path: + VIR_FREE(operation_path); +no_unwind: + 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 = cap->data.scsi_host.wwnn; + *wwpn = cap->data.scsi_host.wwnn; + break; + } + + cap = cap->next; + } + + if (cap == NULL) { + /* XXX This error code is wrong--it results in errors of the form: + "error: invalid node device pointer in Device foo is not a fibre channel HBA" + */ + virNodeDeviceReportError(conn, VIR_ERR_INVALID_NODE_DEVICE, + _("Device %s is not a fibre channel HBA"), + def->name); + ret = -1; + } + + return ret; +} + + +static int +get_parent_host(virConnectPtr conn, + virDeviceMonitorStatePtr driver, + virNodeDeviceDefPtr def, + int *parent_host) +{ + virNodeDeviceObjPtr parent = NULL; + virNodeDevCapsDefPtr cap = NULL; + int ret = 0; + + parent = virNodeDeviceFindByName(&driver->devs, def->parent); + if (parent == NULL) { + virNodeDeviceReportError(conn, VIR_ERR_INVALID_NODE_DEVICE, + _("Could not find parent device for '%s'"), + def->name); + ret = -1; + goto out; + } + + cap = parent->def->caps; + while (cap != NULL) { + if (cap->type == VIR_NODE_DEV_CAP_SCSI_HOST) { + *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 a SCSI host"), + parent->def->name); + ret = -1; + } + +out: + if (parent != NULL) { + virNodeDeviceObjUnlock(parent); + } + + return ret; +} + + +static virNodeDevicePtr +find_new_device(virConnectPtr conn, const char *wwnn, const char *wwpn) +{ + virNodeDevicePtr dev = NULL; + virDeviceMonitorStatePtr driver = conn->devMonPrivateData; + time_t start = 0, now = 0; + + /* 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 generally racy, 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. */ + start = time(NULL); + if (start == (time_t)-1) { + virNodeDeviceReportError(dev->conn, VIR_ERR_INTERNAL_ERROR, + "%s", _("Could not get current time")); + /* Set start time to the epoch so we try find the device + * once. */ + start = 0; + } + + while (dev == NULL && now - start < LINUX_NEW_DEVICE_WAIT_TIME) { + /* We can't hold the driver lock while we wait because the + wait for devices call takes it. It's safe to drop the lock + because we're done with the driver structure at this point + anyway. We take it again when we look to see what, if + anything, was created. */ + nodeDeviceUnlock(driver); + virNodeDeviceWaitForDevices(conn); + nodeDeviceLock(driver); + + dev = nodeDeviceLookupByWWN(conn, wwnn, wwpn); + + if (dev == NULL) { + sleep(5); + now = time(NULL); + if (now == (time_t)-1) { + virNodeDeviceReportError(dev->conn, VIR_ERR_INTERNAL_ERROR, + "%s", _("Could not get current time")); + break; + } + } + } + + 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); + if (def == NULL) { + goto cleanup; + } + + if (get_wwns(conn, def, &wwnn, &wwpn) == -1) { + goto cleanup; + } + + if (get_parent_host(conn, driver, def, &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); + return dev; +} + + +static int +nodeDeviceDestroy(virNodeDevicePtr dev ATTRIBUTE_UNUSED) +{ + return 0; +} + + +#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 +620,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..59cd5f5 100644 --- a/src/node_device.h +++ b/src/node_device.h @@ -28,6 +28,18 @@ #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 +54,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..6c74551 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); @@ -563,8 +600,9 @@ virNodeDevCapScsiHostParseXML(virConnectPtr conn, xmlNodePtr node, union _virNodeDevCapData *data) { - xmlNodePtr orignode; - int ret = -1; + xmlNodePtr orignode, *nodes = NULL; + int ret = -1, n = 0, i; + char *type = NULL; orignode = ctxt->node; ctxt->node = node; @@ -572,15 +610,77 @@ virNodeDevCapScsiHostParseXML(virConnectPtr conn, if (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, diff --git a/src/node_device_conf.h b/src/node_device_conf.h index 26e5558..8233a91 100644 --- a/src/node_device_conf.h +++ b/src/node_device_conf.h @@ -48,8 +48,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 +65,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 +121,9 @@ struct _virNodeDevCapsDef { } net; struct { unsigned host; + char *wwnn; + char *wwpn; + unsigned flags; } scsi_host; struct { unsigned host; diff --git a/src/node_device_hal.c b/src/node_device_hal.c index b214f60..5e54044 100644 --- a/src/node_device_hal.c +++ b/src/node_device_hal.c @@ -26,6 +26,7 @@ #include <stdio.h> #include <stdlib.h> #include <libhal.h> +#include <fcntl.h> #include "node_device_conf.h" #include "virterror_internal.h" @@ -37,6 +38,8 @@ #include "logging.h" #include "node_device.h" +#define VIR_FROM_THIS VIR_FROM_NODEDEV + /* * Host device enumeration (HAL implementation) */ @@ -211,10 +214,141 @@ static int gather_net_cap(LibHalContext *ctx, const char *udi, } +static int check_fc_host(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); + + 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); + + 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: + VIR_FREE(sysfs_path); + VIR_FREE(wwnn_path); + VIR_FREE(wwpn_path); + return 0; +} + + +static int check_vport_capable(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; +} + + 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/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/virsh.c b/src/virsh.c index 2e41c02..1a094eb 100644 --- a/src/virsh.c +++ b/src/virsh.c @@ -2962,6 +2962,58 @@ 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; + 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; +} + + +/* * XML Building helper for pool-define-as and pool-create-as */ static const vshCmdOptDef opts_pool_X_as[] = { @@ -5895,6 +5947,7 @@ 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}, {"pool-autostart", cmdPoolAutostart, opts_pool_autostart, info_pool_autostart}, {"pool-build", cmdPoolBuild, opts_pool_build, info_pool_build}, -- 1.6.0.6 -- Libvir-list mailing list Libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list