[libvirt] [PATCH 0/2] virsh: allow to specify pci address with attach-interface

Now, at using attach-interface, pci address of the device is determined automatically. This is nice. But in some situation, users may want to specify pci address by hand. For example, when users want to use multifunction pci device, he need to specify pci slot. This patch allows to specify pci address with attach-interface. Then, this kind of script can run to assign multiple nics in a slot. == #!/bin/bash -x DOM=$1 SLOT=$2 NUM=$3 for i in `seq 0 $NUM`; do virsh attach-interface --domain $DOM --type='network' --source=default\ --persistent --model=virtio --address=pci:0000:00:$SLOT:0$i --multifunction; done == Because I moved placement of some functions in virsh.c, diffstat seems big... virsh.c | 948 ++++++++++++++++++++++++++++++++------------------------------ virsh.pod | 4 2 files changed, 495 insertions(+), 457 deletions(-) Thanks, -Kame

From: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com> This is for reusing PCIAddress parser etc..in AttachInterface. This patch just moves defs. No functional/logical changes at all. --- tools/virsh.c | 916 ++++++++++++++++++++++++++++---------------------------- 1 files changed, 458 insertions(+), 458 deletions(-) diff --git a/tools/virsh.c b/tools/virsh.c index f6571f7..2cb3a19 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -12310,75 +12310,193 @@ cmdUpdateDevice(vshControl *ctl, const vshCmd *cmd) /* - * "attach-interface" command + * "attach-disk" command */ -static const vshCmdInfo info_attach_interface[] = { - {"help", N_("attach network interface")}, - {"desc", N_("Attach new network interface.")}, +static const vshCmdInfo info_attach_disk[] = { + {"help", N_("attach disk device")}, + {"desc", N_("Attach new disk device.")}, {NULL, NULL} }; -static const vshCmdOptDef opts_attach_interface[] = { - {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, - {"type", VSH_OT_DATA, VSH_OFLAG_REQ, N_("network interface type")}, - {"source", VSH_OT_DATA, VSH_OFLAG_REQ, N_("source of network interface")}, - {"target", VSH_OT_DATA, 0, N_("target network name")}, - {"mac", VSH_OT_DATA, 0, N_("MAC address")}, - {"script", VSH_OT_DATA, 0, N_("script used to bridge network interface")}, - {"model", VSH_OT_DATA, 0, N_("model type")}, - {"persistent", VSH_OT_BOOL, 0, N_("persist interface attachment")}, - {"inbound", VSH_OT_DATA, VSH_OFLAG_NONE, N_("control domain's incoming traffics")}, - {"outbound", VSH_OT_DATA, VSH_OFLAG_NONE, N_("control domain's outgoing traffics")}, +static const vshCmdOptDef opts_attach_disk[] = { + {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, + {"source", VSH_OT_DATA, VSH_OFLAG_REQ | VSH_OFLAG_EMPTY_OK, + N_("source of disk device")}, + {"target", VSH_OT_DATA, VSH_OFLAG_REQ, N_("target of disk device")}, + {"driver", VSH_OT_STRING, 0, N_("driver of disk device")}, + {"subdriver", VSH_OT_STRING, 0, N_("subdriver of disk device")}, + {"cache", VSH_OT_STRING, 0, N_("cache mode of disk device")}, + {"type", VSH_OT_STRING, 0, N_("target device type")}, + {"mode", VSH_OT_STRING, 0, N_("mode of device reading and writing")}, + {"persistent", VSH_OT_BOOL, 0, N_("persist disk attachment")}, + {"sourcetype", VSH_OT_STRING, 0, N_("type of source (block|file)")}, + {"serial", VSH_OT_STRING, 0, N_("serial of disk device")}, + {"shareable", VSH_OT_BOOL, 0, N_("shareable between domains")}, + {"address", VSH_OT_STRING, 0, N_("address of disk device")}, + {"multifunction", VSH_OT_BOOL, 0, + N_("use multifunction pci under specified address")}, {NULL, 0, 0, NULL} }; -/* parse inbound and outbound which are in the format of - * 'average,peak,burst', in which peak and burst are optional, - * thus 'average,,burst' and 'average,peak' are also legal. */ -static int parseRateStr(const char *rateStr, virNetDevBandwidthRatePtr rate) +enum { + DISK_ADDR_TYPE_INVALID, + DISK_ADDR_TYPE_PCI, + DISK_ADDR_TYPE_SCSI, + DISK_ADDR_TYPE_IDE, +}; + +struct PCIAddress { + unsigned int domain; + unsigned int bus; + unsigned int slot; + unsigned int function; +}; + +struct SCSIAddress { + unsigned int controller; + unsigned int bus; + unsigned int unit; +}; + +struct IDEAddress { + unsigned int controller; + unsigned int bus; + unsigned int unit; +}; + +struct DiskAddress { + int type; + union { + struct PCIAddress pci; + struct SCSIAddress scsi; + struct IDEAddress ide; + } addr; +}; + +static int str2PCIAddress(const char *str, struct PCIAddress *pciAddr) { - const char *average = NULL; - char *peak = NULL, *burst = NULL; + char *domain, *bus, *slot, *function; - average = rateStr; - if (!average) + if (!pciAddr) return -1; - if (virStrToLong_ull(average, &peak, 10, &rate->average) < 0) + if (!str) return -1; - /* peak will be updated to point to the end of rateStr in case - * of 'average' */ - if (peak && *peak != '\0') { - burst = strchr(peak + 1, ','); - if (!(burst && (burst - peak == 1))) { - if (virStrToLong_ull(peak + 1, &burst, 10, &rate->peak) < 0) - return -1; - } + domain = (char *)str; - /* burst will be updated to point to the end of rateStr in case - * of 'average,peak' */ - if (burst && *burst != '\0') { - if (virStrToLong_ull(burst + 1, NULL, 10, &rate->burst) < 0) - return -1; - } - } + if (virStrToLong_ui(domain, &bus, 0, &pciAddr->domain) != 0) + return -1; + bus++; + if (virStrToLong_ui(bus, &slot, 0, &pciAddr->bus) != 0) + return -1; + + slot++; + if (virStrToLong_ui(slot, &function, 0, &pciAddr->slot) != 0) + return -1; + + function++; + if (virStrToLong_ui(function, NULL, 0, &pciAddr->function) != 0) + return -1; return 0; } +static int str2SCSIAddress(const char *str, struct SCSIAddress *scsiAddr) +{ + char *controller, *bus, *unit; + + if (!scsiAddr) + return -1; + if (!str) + return -1; + + controller = (char *)str; + + if (virStrToLong_ui(controller, &bus, 0, &scsiAddr->controller) != 0) + return -1; + + bus++; + if (virStrToLong_ui(bus, &unit, 0, &scsiAddr->bus) != 0) + return -1; + + unit++; + if (virStrToLong_ui(unit, NULL, 0, &scsiAddr->unit) != 0) + return -1; + + return 0; +} + +static int str2IDEAddress(const char *str, struct IDEAddress *ideAddr) +{ + char *controller, *bus, *unit; + + if (!ideAddr) + return -1; + if (!str) + return -1; + + controller = (char *)str; + + if (virStrToLong_ui(controller, &bus, 0, &ideAddr->controller) != 0) + return -1; + + bus++; + if (virStrToLong_ui(bus, &unit, 0, &ideAddr->bus) != 0) + return -1; + + unit++; + if (virStrToLong_ui(unit, NULL, 0, &ideAddr->unit) != 0) + return -1; + + return 0; +} + +/* pci address pci:0000.00.0x0a.0 (domain:bus:slot:function) + * ide disk address: ide:00.00.0 (controller:bus:unit) + * scsi disk address: scsi:00.00.0 (controller:bus:unit) + */ + +static int str2DiskAddress(const char *str, struct DiskAddress *diskAddr) +{ + char *type, *addr; + + if (!diskAddr) + return -1; + if (!str) + return -1; + + type = (char *)str; + addr = strchr(type, ':'); + if (!addr) + return -1; + + if (STREQLEN(type, "pci", addr - type)) { + diskAddr->type = DISK_ADDR_TYPE_PCI; + return str2PCIAddress(addr + 1, &diskAddr->addr.pci); + } else if (STREQLEN(type, "scsi", addr - type)) { + diskAddr->type = DISK_ADDR_TYPE_SCSI; + return str2SCSIAddress(addr + 1, &diskAddr->addr.scsi); + } else if (STREQLEN(type, "ide", addr - type)) { + diskAddr->type = DISK_ADDR_TYPE_IDE; + return str2IDEAddress(addr + 1, &diskAddr->addr.ide); + } + + return -1; +} + static bool -cmdAttachInterface(vshControl *ctl, const vshCmd *cmd) +cmdAttachDisk(vshControl *ctl, const vshCmd *cmd) { virDomainPtr dom = NULL; - const char *mac = NULL, *target = NULL, *script = NULL, - *type = NULL, *source = NULL, *model = NULL, - *inboundStr = NULL, *outboundStr = NULL; - virNetDevBandwidthRate inbound, outbound; - int typ; + const char *source = NULL, *target = NULL, *driver = NULL, + *subdriver = NULL, *type = NULL, *mode = NULL, + *cache = NULL, *serial = NULL, *straddr = NULL; + struct DiskAddress diskAddr; + bool isFile = false, functionReturn = false; int ret; - bool functionReturn = false; unsigned int flags; + const char *stype = NULL; virBuffer buf = VIR_BUFFER_INITIALIZER; char *xml; @@ -12388,97 +12506,130 @@ cmdAttachInterface(vshControl *ctl, const vshCmd *cmd) if (!(dom = vshCommandOptDomain(ctl, cmd, NULL))) goto cleanup; - if (vshCommandOptString(cmd, "type", &type) <= 0) + if (vshCommandOptString(cmd, "source", &source) <= 0) goto cleanup; + /* Allow empty string as a placeholder that implies no source, for + * use in adding a cdrom drive with no disk. */ + if (!*source) + source = NULL; - if (vshCommandOptString(cmd, "source", &source) < 0 || - vshCommandOptString(cmd, "target", &target) < 0 || - vshCommandOptString(cmd, "mac", &mac) < 0 || - vshCommandOptString(cmd, "script", &script) < 0 || - vshCommandOptString(cmd, "model", &model) < 0 || - vshCommandOptString(cmd, "inbound", &inboundStr) < 0 || - vshCommandOptString(cmd, "outbound", &outboundStr) < 0) { - vshError(ctl, "missing argument"); + if (vshCommandOptString(cmd, "target", &target) <= 0) goto cleanup; - } - /* check interface type */ - if (STREQ(type, "network")) { - typ = 1; - } else if (STREQ(type, "bridge")) { - typ = 2; - } else { - vshError(ctl, _("No support for %s in command 'attach-interface'"), - type); + if (vshCommandOptString(cmd, "driver", &driver) < 0 || + vshCommandOptString(cmd, "subdriver", &subdriver) < 0 || + vshCommandOptString(cmd, "type", &type) < 0 || + vshCommandOptString(cmd, "mode", &mode) < 0 || + vshCommandOptString(cmd, "cache", &cache) < 0 || + vshCommandOptString(cmd, "serial", &serial) < 0 || + vshCommandOptString(cmd, "address", &straddr) < 0 || + vshCommandOptString(cmd, "sourcetype", &stype) < 0) { + vshError(ctl, "%s", _("missing option")); goto cleanup; } - if (inboundStr) { - memset(&inbound, 0, sizeof(inbound)); - if (parseRateStr(inboundStr, &inbound) < 0) { - vshError(ctl, _("inbound format is incorrect")); - goto cleanup; - } - if (inbound.average == 0) { - vshError(ctl, _("inbound average is mandatory")); - goto cleanup; - } + if (!stype) { + if (driver && (STREQ(driver, "file") || STREQ(driver, "tap"))) + isFile = true; + } else if (STREQ(stype, "file")) { + isFile = true; + } else if (STRNEQ(stype, "block")) { + vshError(ctl, _("Unknown source type: '%s'"), stype); + goto cleanup; } - if (outboundStr) { - memset(&outbound, 0, sizeof(outbound)); - if (parseRateStr(outboundStr, &outbound) < 0) { - vshError(ctl, _("outbound format is incorrect")); - goto cleanup; - } - if (outbound.average == 0) { - vshError(ctl, _("outbound average is mandatory")); + + if (mode) { + if (STRNEQ(mode, "readonly") && STRNEQ(mode, "shareable")) { + vshError(ctl, _("No support for %s in command 'attach-disk'"), + mode); goto cleanup; } } - /* Make XML of interface */ - virBufferAsprintf(&buf, "<interface type='%s'>\n", type); + /* Make XML of disk */ + virBufferAsprintf(&buf, "<disk type='%s'", + (isFile) ? "file" : "block"); + if (type) + virBufferAsprintf(&buf, " device='%s'", type); + virBufferAddLit(&buf, ">\n"); - if (typ == 1) - virBufferAsprintf(&buf, " <source network='%s'/>\n", source); - else if (typ == 2) - virBufferAsprintf(&buf, " <source bridge='%s'/>\n", source); + if (driver || subdriver) + virBufferAsprintf(&buf, " <driver"); - if (target != NULL) - virBufferAsprintf(&buf, " <target dev='%s'/>\n", target); - if (mac != NULL) - virBufferAsprintf(&buf, " <mac address='%s'/>\n", mac); - if (script != NULL) - virBufferAsprintf(&buf, " <script path='%s'/>\n", script); - if (model != NULL) - virBufferAsprintf(&buf, " <model type='%s'/>\n", model); + if (driver) + virBufferAsprintf(&buf, " name='%s'", driver); + if (subdriver) + virBufferAsprintf(&buf, " type='%s'", subdriver); + if (cache) + virBufferAsprintf(&buf, " cache='%s'", cache); - if (inboundStr || outboundStr) { - virBufferAsprintf(&buf, " <bandwidth>\n"); - if (inboundStr && inbound.average > 0) { - virBufferAsprintf(&buf, " <inbound average='%llu'", inbound.average); - if (inbound.peak > 0) - virBufferAsprintf(&buf, " peak='%llu'", inbound.peak); - if (inbound.burst > 0) - virBufferAsprintf(&buf, " burst='%llu'", inbound.burst); - virBufferAsprintf(&buf, "/>\n"); + if (driver || subdriver || cache) + virBufferAddLit(&buf, "/>\n"); + + if (source) + virBufferAsprintf(&buf, " <source %s='%s'/>\n", + (isFile) ? "file" : "dev", + source); + virBufferAsprintf(&buf, " <target dev='%s'/>\n", target); + if (mode) + virBufferAsprintf(&buf, " <%s/>\n", mode); + + if (serial) + virBufferAsprintf(&buf, " <serial>%s</serial>\n", serial); + + if (vshCommandOptBool(cmd, "shareable")) + virBufferAsprintf(&buf, " <shareable/>\n"); + + if (straddr) { + if (str2DiskAddress(straddr, &diskAddr) != 0) { + vshError(ctl, _("Invalid address.")); + goto cleanup; } - if (outboundStr && outbound.average > 0) { - virBufferAsprintf(&buf, " <outbound average='%llu'", outbound.average); - if (outbound.peak > 0) - virBufferAsprintf(&buf, " peak='%llu'", outbound.peak); - if (outbound.burst > 0) - virBufferAsprintf(&buf, " burst='%llu'", outbound.burst); - virBufferAsprintf(&buf, "/>\n"); + + if (STRPREFIX((const char *)target, "vd")) { + if (diskAddr.type == DISK_ADDR_TYPE_PCI) { + virBufferAsprintf(&buf, + " <address type='pci' domain='0x%04x'" + " bus ='0x%02x' slot='0x%02x' function='0x%0x'", + diskAddr.addr.pci.domain, diskAddr.addr.pci.bus, + diskAddr.addr.pci.slot, diskAddr.addr.pci.function); + if (vshCommandOptBool(cmd, "multifunction")) + virBufferAddLit(&buf, " multifunction='on'"); + virBufferAddLit(&buf, "/>\n"); + } else { + vshError(ctl, "%s", _("expecting a pci:0000.00.00.00 address.")); + goto cleanup; + } + } else if (STRPREFIX((const char *)target, "sd")) { + if (diskAddr.type == DISK_ADDR_TYPE_SCSI) { + virBufferAsprintf(&buf, + " <address type='drive' controller='%d'" + " bus='%d' unit='%d' />\n", + diskAddr.addr.scsi.controller, diskAddr.addr.scsi.bus, + diskAddr.addr.scsi.unit); + } else { + vshError(ctl, "%s", _("expecting a scsi:00.00.00 address.")); + goto cleanup; + } + } else if (STRPREFIX((const char *)target, "hd")) { + if (diskAddr.type == DISK_ADDR_TYPE_IDE) { + virBufferAsprintf(&buf, + " <address type='drive' controller='%d'" + " bus='%d' unit='%d' />\n", + diskAddr.addr.ide.controller, diskAddr.addr.ide.bus, + diskAddr.addr.ide.unit); + } else { + vshError(ctl, "%s", _("expecting an ide:00.00.00 address.")); + goto cleanup; + } } - virBufferAsprintf(&buf, " </bandwidth>\n"); } - virBufferAddLit(&buf, "</interface>\n"); + virBufferAddLit(&buf, "</disk>\n"); if (virBufferError(&buf)) { vshPrint(ctl, "%s", _("Failed to allocate XML buffer")); - goto cleanup; + return false; } xml = virBufferContentAndReset(&buf); @@ -12495,9 +12646,9 @@ cmdAttachInterface(vshControl *ctl, const vshCmd *cmd) VIR_FREE(xml); if (ret != 0) { - vshError(ctl, "%s", _("Failed to attach interface")); + vshError(ctl, "%s", _("Failed to attach disk")); } else { - vshPrint(ctl, "%s", _("Interface attached successfully\n")); + vshPrint(ctl, "%s", _("Disk attached successfully\n")); functionReturn = true; } @@ -12509,37 +12660,35 @@ cmdAttachInterface(vshControl *ctl, const vshCmd *cmd) } /* - * "detach-interface" command + * "detach-disk" command */ -static const vshCmdInfo info_detach_interface[] = { - {"help", N_("detach network interface")}, - {"desc", N_("Detach network interface.")}, +static const vshCmdInfo info_detach_disk[] = { + {"help", N_("detach disk device")}, + {"desc", N_("Detach disk device.")}, {NULL, NULL} }; -static const vshCmdOptDef opts_detach_interface[] = { +static const vshCmdOptDef opts_detach_disk[] = { {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, - {"type", VSH_OT_DATA, VSH_OFLAG_REQ, N_("network interface type")}, - {"mac", VSH_OT_STRING, 0, N_("MAC address")}, - {"persistent", VSH_OT_BOOL, 0, N_("persist interface detachment")}, + {"target", VSH_OT_DATA, VSH_OFLAG_REQ, N_("target of disk device")}, + {"persistent", VSH_OT_BOOL, 0, N_("persist disk detachment")}, {NULL, 0, 0, NULL} }; static bool -cmdDetachInterface(vshControl *ctl, const vshCmd *cmd) +cmdDetachDisk(vshControl *ctl, const vshCmd *cmd) { - virDomainPtr dom = NULL; xmlDocPtr xml = NULL; xmlXPathObjectPtr obj=NULL; xmlXPathContextPtr ctxt = NULL; xmlNodePtr cur = NULL; xmlBufferPtr xml_buf = NULL; - const char *mac =NULL, *type = NULL; + virDomainPtr dom = NULL; + const char *target = NULL; char *doc; - char buf[64]; - int i = 0, diff_mac; + int i = 0, diff_tgt; int ret; - int functionReturn = false; + bool functionReturn = false; unsigned int flags; if (!vshConnectionUsability(ctl, ctl->conn)) @@ -12548,13 +12697,8 @@ cmdDetachInterface(vshControl *ctl, const vshCmd *cmd) if (!(dom = vshCommandOptDomain(ctl, cmd, NULL))) goto cleanup; - if (vshCommandOptString(cmd, "type", &type) <= 0) - goto cleanup; - - if (vshCommandOptString(cmd, "mac", &mac) < 0) { - vshError(ctl, "%s", _("missing option")); + if (vshCommandOptString(cmd, "target", &target) <= 0) goto cleanup; - } doc = virDomainGetXMLDesc(dom, 0); if (!doc) @@ -12563,44 +12707,34 @@ cmdDetachInterface(vshControl *ctl, const vshCmd *cmd) xml = virXMLParseStringCtxt(doc, _("(domain_definition)"), &ctxt); VIR_FREE(doc); if (!xml) { - vshError(ctl, "%s", _("Failed to get interface information")); + vshError(ctl, "%s", _("Failed to get disk information")); goto cleanup; } - snprintf(buf, sizeof(buf), "/domain/devices/interface[@type='%s']", type); - obj = xmlXPathEval(BAD_CAST buf, ctxt); + obj = xmlXPathEval(BAD_CAST "/domain/devices/disk", ctxt); if ((obj == NULL) || (obj->type != XPATH_NODESET) || (obj->nodesetval == NULL) || (obj->nodesetval->nodeNr == 0)) { - vshError(ctl, _("No found interface whose type is %s"), type); - goto cleanup; - } - - if ((!mac) && (obj->nodesetval->nodeNr > 1)) { - vshError(ctl, _("Domain has %d interfaces. Please specify which one " - "to detach using --mac"), obj->nodesetval->nodeNr); + vshError(ctl, "%s", _("Failed to get disk information")); goto cleanup; } - if (!mac) - goto hit; - - /* search mac */ + /* search target */ for (; i < obj->nodesetval->nodeNr; i++) { cur = obj->nodesetval->nodeTab[i]->children; while (cur != NULL) { if (cur->type == XML_ELEMENT_NODE && - xmlStrEqual(cur->name, BAD_CAST "mac")) { - char *tmp_mac = virXMLPropString(cur, "address"); - diff_mac = virMacAddrCompare (tmp_mac, mac); - VIR_FREE(tmp_mac); - if (!diff_mac) { + xmlStrEqual(cur->name, BAD_CAST "target")) { + char *tmp_tgt = virXMLPropString(cur, "dev"); + diff_tgt = STREQ(tmp_tgt, target); + VIR_FREE(tmp_tgt); + if (diff_tgt) { goto hit; } } cur = cur->next; } } - vshError(ctl, _("No found interface whose MAC address is %s"), mac); + vshError(ctl, _("No found disk whose target is %s"), target); goto cleanup; hit: @@ -12627,210 +12761,92 @@ cmdDetachInterface(vshControl *ctl, const vshCmd *cmd) } if (ret != 0) { - vshError(ctl, "%s", _("Failed to detach interface")); + vshError(ctl, "%s", _("Failed to detach disk")); } else { - vshPrint(ctl, "%s", _("Interface detached successfully\n")); + vshPrint(ctl, "%s", _("Disk detached successfully\n")); functionReturn = true; } cleanup: - if (dom) - virDomainFree(dom); xmlXPathFreeObject(obj); xmlXPathFreeContext(ctxt); xmlFreeDoc(xml); xmlBufferFree(xml_buf); + if (dom) + virDomainFree(dom); return functionReturn; } /* - * "attach-disk" command + * "attach-interface" command */ -static const vshCmdInfo info_attach_disk[] = { - {"help", N_("attach disk device")}, - {"desc", N_("Attach new disk device.")}, +static const vshCmdInfo info_attach_interface[] = { + {"help", N_("attach network interface")}, + {"desc", N_("Attach new network interface.")}, {NULL, NULL} }; -static const vshCmdOptDef opts_attach_disk[] = { - {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, - {"source", VSH_OT_DATA, VSH_OFLAG_REQ | VSH_OFLAG_EMPTY_OK, - N_("source of disk device")}, - {"target", VSH_OT_DATA, VSH_OFLAG_REQ, N_("target of disk device")}, - {"driver", VSH_OT_STRING, 0, N_("driver of disk device")}, - {"subdriver", VSH_OT_STRING, 0, N_("subdriver of disk device")}, - {"cache", VSH_OT_STRING, 0, N_("cache mode of disk device")}, - {"type", VSH_OT_STRING, 0, N_("target device type")}, - {"mode", VSH_OT_STRING, 0, N_("mode of device reading and writing")}, - {"persistent", VSH_OT_BOOL, 0, N_("persist disk attachment")}, - {"sourcetype", VSH_OT_STRING, 0, N_("type of source (block|file)")}, - {"serial", VSH_OT_STRING, 0, N_("serial of disk device")}, - {"shareable", VSH_OT_BOOL, 0, N_("shareable between domains")}, - {"address", VSH_OT_STRING, 0, N_("address of disk device")}, - {"multifunction", VSH_OT_BOOL, 0, - N_("use multifunction pci under specified address")}, +static const vshCmdOptDef opts_attach_interface[] = { + {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, + {"type", VSH_OT_DATA, VSH_OFLAG_REQ, N_("network interface type")}, + {"source", VSH_OT_DATA, VSH_OFLAG_REQ, N_("source of network interface")}, + {"target", VSH_OT_DATA, 0, N_("target network name")}, + {"mac", VSH_OT_DATA, 0, N_("MAC address")}, + {"script", VSH_OT_DATA, 0, N_("script used to bridge network interface")}, + {"model", VSH_OT_DATA, 0, N_("model type")}, + {"persistent", VSH_OT_BOOL, 0, N_("persist interface attachment")}, + {"inbound", VSH_OT_DATA, VSH_OFLAG_NONE, N_("control domain's incoming traffics")}, + {"outbound", VSH_OT_DATA, VSH_OFLAG_NONE, N_("control domain's outgoing traffics")}, {NULL, 0, 0, NULL} }; -enum { - DISK_ADDR_TYPE_INVALID, - DISK_ADDR_TYPE_PCI, - DISK_ADDR_TYPE_SCSI, - DISK_ADDR_TYPE_IDE, -}; - -struct PCIAddress { - unsigned int domain; - unsigned int bus; - unsigned int slot; - unsigned int function; -}; - -struct SCSIAddress { - unsigned int controller; - unsigned int bus; - unsigned int unit; -}; - -struct IDEAddress { - unsigned int controller; - unsigned int bus; - unsigned int unit; -}; - -struct DiskAddress { - int type; - union { - struct PCIAddress pci; - struct SCSIAddress scsi; - struct IDEAddress ide; - } addr; -}; - -static int str2PCIAddress(const char *str, struct PCIAddress *pciAddr) -{ - char *domain, *bus, *slot, *function; - - if (!pciAddr) - return -1; - if (!str) - return -1; - - domain = (char *)str; - - if (virStrToLong_ui(domain, &bus, 0, &pciAddr->domain) != 0) - return -1; - - bus++; - if (virStrToLong_ui(bus, &slot, 0, &pciAddr->bus) != 0) - return -1; - - slot++; - if (virStrToLong_ui(slot, &function, 0, &pciAddr->slot) != 0) - return -1; - - function++; - if (virStrToLong_ui(function, NULL, 0, &pciAddr->function) != 0) - return -1; - - return 0; -} - -static int str2SCSIAddress(const char *str, struct SCSIAddress *scsiAddr) -{ - char *controller, *bus, *unit; - - if (!scsiAddr) - return -1; - if (!str) - return -1; - - controller = (char *)str; - - if (virStrToLong_ui(controller, &bus, 0, &scsiAddr->controller) != 0) - return -1; - - bus++; - if (virStrToLong_ui(bus, &unit, 0, &scsiAddr->bus) != 0) - return -1; - - unit++; - if (virStrToLong_ui(unit, NULL, 0, &scsiAddr->unit) != 0) - return -1; - - return 0; -} - -static int str2IDEAddress(const char *str, struct IDEAddress *ideAddr) +/* parse inbound and outbound which are in the format of + * 'average,peak,burst', in which peak and burst are optional, + * thus 'average,,burst' and 'average,peak' are also legal. */ +static int parseRateStr(const char *rateStr, virNetDevBandwidthRatePtr rate) { - char *controller, *bus, *unit; + const char *average = NULL; + char *peak = NULL, *burst = NULL; - if (!ideAddr) + average = rateStr; + if (!average) return -1; - if (!str) + if (virStrToLong_ull(average, &peak, 10, &rate->average) < 0) return -1; - controller = (char *)str; - - if (virStrToLong_ui(controller, &bus, 0, &ideAddr->controller) != 0) - return -1; + /* peak will be updated to point to the end of rateStr in case + * of 'average' */ + if (peak && *peak != '\0') { + burst = strchr(peak + 1, ','); + if (!(burst && (burst - peak == 1))) { + if (virStrToLong_ull(peak + 1, &burst, 10, &rate->peak) < 0) + return -1; + } - bus++; - if (virStrToLong_ui(bus, &unit, 0, &ideAddr->bus) != 0) - return -1; + /* burst will be updated to point to the end of rateStr in case + * of 'average,peak' */ + if (burst && *burst != '\0') { + if (virStrToLong_ull(burst + 1, NULL, 10, &rate->burst) < 0) + return -1; + } + } - unit++; - if (virStrToLong_ui(unit, NULL, 0, &ideAddr->unit) != 0) - return -1; return 0; } -/* pci address pci:0000.00.0x0a.0 (domain:bus:slot:function) - * ide disk address: ide:00.00.0 (controller:bus:unit) - * scsi disk address: scsi:00.00.0 (controller:bus:unit) - */ - -static int str2DiskAddress(const char *str, struct DiskAddress *diskAddr) -{ - char *type, *addr; - - if (!diskAddr) - return -1; - if (!str) - return -1; - - type = (char *)str; - addr = strchr(type, ':'); - if (!addr) - return -1; - - if (STREQLEN(type, "pci", addr - type)) { - diskAddr->type = DISK_ADDR_TYPE_PCI; - return str2PCIAddress(addr + 1, &diskAddr->addr.pci); - } else if (STREQLEN(type, "scsi", addr - type)) { - diskAddr->type = DISK_ADDR_TYPE_SCSI; - return str2SCSIAddress(addr + 1, &diskAddr->addr.scsi); - } else if (STREQLEN(type, "ide", addr - type)) { - diskAddr->type = DISK_ADDR_TYPE_IDE; - return str2IDEAddress(addr + 1, &diskAddr->addr.ide); - } - - return -1; -} - static bool -cmdAttachDisk(vshControl *ctl, const vshCmd *cmd) -{ - virDomainPtr dom = NULL; - const char *source = NULL, *target = NULL, *driver = NULL, - *subdriver = NULL, *type = NULL, *mode = NULL, - *cache = NULL, *serial = NULL, *straddr = NULL; - struct DiskAddress diskAddr; - bool isFile = false, functionReturn = false; +cmdAttachInterface(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom = NULL; + const char *mac = NULL, *target = NULL, *script = NULL, + *type = NULL, *source = NULL, *model = NULL, + *inboundStr = NULL, *outboundStr = NULL; + virNetDevBandwidthRate inbound, outbound; + int typ; int ret; + bool functionReturn = false; unsigned int flags; - const char *stype = NULL; virBuffer buf = VIR_BUFFER_INITIALIZER; char *xml; @@ -12840,130 +12856,97 @@ cmdAttachDisk(vshControl *ctl, const vshCmd *cmd) if (!(dom = vshCommandOptDomain(ctl, cmd, NULL))) goto cleanup; - if (vshCommandOptString(cmd, "source", &source) <= 0) - goto cleanup; - /* Allow empty string as a placeholder that implies no source, for - * use in adding a cdrom drive with no disk. */ - if (!*source) - source = NULL; - - if (vshCommandOptString(cmd, "target", &target) <= 0) + if (vshCommandOptString(cmd, "type", &type) <= 0) goto cleanup; - if (vshCommandOptString(cmd, "driver", &driver) < 0 || - vshCommandOptString(cmd, "subdriver", &subdriver) < 0 || - vshCommandOptString(cmd, "type", &type) < 0 || - vshCommandOptString(cmd, "mode", &mode) < 0 || - vshCommandOptString(cmd, "cache", &cache) < 0 || - vshCommandOptString(cmd, "serial", &serial) < 0 || - vshCommandOptString(cmd, "address", &straddr) < 0 || - vshCommandOptString(cmd, "sourcetype", &stype) < 0) { - vshError(ctl, "%s", _("missing option")); + if (vshCommandOptString(cmd, "source", &source) < 0 || + vshCommandOptString(cmd, "target", &target) < 0 || + vshCommandOptString(cmd, "mac", &mac) < 0 || + vshCommandOptString(cmd, "script", &script) < 0 || + vshCommandOptString(cmd, "model", &model) < 0 || + vshCommandOptString(cmd, "inbound", &inboundStr) < 0 || + vshCommandOptString(cmd, "outbound", &outboundStr) < 0) { + vshError(ctl, "missing argument"); goto cleanup; } - if (!stype) { - if (driver && (STREQ(driver, "file") || STREQ(driver, "tap"))) - isFile = true; - } else if (STREQ(stype, "file")) { - isFile = true; - } else if (STRNEQ(stype, "block")) { - vshError(ctl, _("Unknown source type: '%s'"), stype); + /* check interface type */ + if (STREQ(type, "network")) { + typ = 1; + } else if (STREQ(type, "bridge")) { + typ = 2; + } else { + vshError(ctl, _("No support for %s in command 'attach-interface'"), + type); goto cleanup; } - if (mode) { - if (STRNEQ(mode, "readonly") && STRNEQ(mode, "shareable")) { - vshError(ctl, _("No support for %s in command 'attach-disk'"), - mode); + if (inboundStr) { + memset(&inbound, 0, sizeof(inbound)); + if (parseRateStr(inboundStr, &inbound) < 0) { + vshError(ctl, _("inbound format is incorrect")); + goto cleanup; + } + if (inbound.average == 0) { + vshError(ctl, _("inbound average is mandatory")); + goto cleanup; + } + } + if (outboundStr) { + memset(&outbound, 0, sizeof(outbound)); + if (parseRateStr(outboundStr, &outbound) < 0) { + vshError(ctl, _("outbound format is incorrect")); + goto cleanup; + } + if (outbound.average == 0) { + vshError(ctl, _("outbound average is mandatory")); goto cleanup; } } - /* Make XML of disk */ - virBufferAsprintf(&buf, "<disk type='%s'", - (isFile) ? "file" : "block"); - if (type) - virBufferAsprintf(&buf, " device='%s'", type); - virBufferAddLit(&buf, ">\n"); - - if (driver || subdriver) - virBufferAsprintf(&buf, " <driver"); - - if (driver) - virBufferAsprintf(&buf, " name='%s'", driver); - if (subdriver) - virBufferAsprintf(&buf, " type='%s'", subdriver); - if (cache) - virBufferAsprintf(&buf, " cache='%s'", cache); - - if (driver || subdriver || cache) - virBufferAddLit(&buf, "/>\n"); - - if (source) - virBufferAsprintf(&buf, " <source %s='%s'/>\n", - (isFile) ? "file" : "dev", - source); - virBufferAsprintf(&buf, " <target dev='%s'/>\n", target); - if (mode) - virBufferAsprintf(&buf, " <%s/>\n", mode); + /* Make XML of interface */ + virBufferAsprintf(&buf, "<interface type='%s'>\n", type); - if (serial) - virBufferAsprintf(&buf, " <serial>%s</serial>\n", serial); + if (typ == 1) + virBufferAsprintf(&buf, " <source network='%s'/>\n", source); + else if (typ == 2) + virBufferAsprintf(&buf, " <source bridge='%s'/>\n", source); - if (vshCommandOptBool(cmd, "shareable")) - virBufferAsprintf(&buf, " <shareable/>\n"); + if (target != NULL) + virBufferAsprintf(&buf, " <target dev='%s'/>\n", target); + if (mac != NULL) + virBufferAsprintf(&buf, " <mac address='%s'/>\n", mac); + if (script != NULL) + virBufferAsprintf(&buf, " <script path='%s'/>\n", script); + if (model != NULL) + virBufferAsprintf(&buf, " <model type='%s'/>\n", model); - if (straddr) { - if (str2DiskAddress(straddr, &diskAddr) != 0) { - vshError(ctl, _("Invalid address.")); - goto cleanup; + if (inboundStr || outboundStr) { + virBufferAsprintf(&buf, " <bandwidth>\n"); + if (inboundStr && inbound.average > 0) { + virBufferAsprintf(&buf, " <inbound average='%llu'", inbound.average); + if (inbound.peak > 0) + virBufferAsprintf(&buf, " peak='%llu'", inbound.peak); + if (inbound.burst > 0) + virBufferAsprintf(&buf, " burst='%llu'", inbound.burst); + virBufferAsprintf(&buf, "/>\n"); } - - if (STRPREFIX((const char *)target, "vd")) { - if (diskAddr.type == DISK_ADDR_TYPE_PCI) { - virBufferAsprintf(&buf, - " <address type='pci' domain='0x%04x'" - " bus ='0x%02x' slot='0x%02x' function='0x%0x'", - diskAddr.addr.pci.domain, diskAddr.addr.pci.bus, - diskAddr.addr.pci.slot, diskAddr.addr.pci.function); - if (vshCommandOptBool(cmd, "multifunction")) - virBufferAddLit(&buf, " multifunction='on'"); - virBufferAddLit(&buf, "/>\n"); - } else { - vshError(ctl, "%s", _("expecting a pci:0000.00.00.00 address.")); - goto cleanup; - } - } else if (STRPREFIX((const char *)target, "sd")) { - if (diskAddr.type == DISK_ADDR_TYPE_SCSI) { - virBufferAsprintf(&buf, - " <address type='drive' controller='%d'" - " bus='%d' unit='%d' />\n", - diskAddr.addr.scsi.controller, diskAddr.addr.scsi.bus, - diskAddr.addr.scsi.unit); - } else { - vshError(ctl, "%s", _("expecting a scsi:00.00.00 address.")); - goto cleanup; - } - } else if (STRPREFIX((const char *)target, "hd")) { - if (diskAddr.type == DISK_ADDR_TYPE_IDE) { - virBufferAsprintf(&buf, - " <address type='drive' controller='%d'" - " bus='%d' unit='%d' />\n", - diskAddr.addr.ide.controller, diskAddr.addr.ide.bus, - diskAddr.addr.ide.unit); - } else { - vshError(ctl, "%s", _("expecting an ide:00.00.00 address.")); - goto cleanup; - } + if (outboundStr && outbound.average > 0) { + virBufferAsprintf(&buf, " <outbound average='%llu'", outbound.average); + if (outbound.peak > 0) + virBufferAsprintf(&buf, " peak='%llu'", outbound.peak); + if (outbound.burst > 0) + virBufferAsprintf(&buf, " burst='%llu'", outbound.burst); + virBufferAsprintf(&buf, "/>\n"); } + virBufferAsprintf(&buf, " </bandwidth>\n"); } - virBufferAddLit(&buf, "</disk>\n"); + virBufferAddLit(&buf, "</interface>\n"); if (virBufferError(&buf)) { vshPrint(ctl, "%s", _("Failed to allocate XML buffer")); - return false; + goto cleanup; } xml = virBufferContentAndReset(&buf); @@ -12980,9 +12963,9 @@ cmdAttachDisk(vshControl *ctl, const vshCmd *cmd) VIR_FREE(xml); if (ret != 0) { - vshError(ctl, "%s", _("Failed to attach disk")); + vshError(ctl, "%s", _("Failed to attach interface")); } else { - vshPrint(ctl, "%s", _("Disk attached successfully\n")); + vshPrint(ctl, "%s", _("Interface attached successfully\n")); functionReturn = true; } @@ -12994,35 +12977,37 @@ cmdAttachDisk(vshControl *ctl, const vshCmd *cmd) } /* - * "detach-disk" command + * "detach-interface" command */ -static const vshCmdInfo info_detach_disk[] = { - {"help", N_("detach disk device")}, - {"desc", N_("Detach disk device.")}, +static const vshCmdInfo info_detach_interface[] = { + {"help", N_("detach network interface")}, + {"desc", N_("Detach network interface.")}, {NULL, NULL} }; -static const vshCmdOptDef opts_detach_disk[] = { +static const vshCmdOptDef opts_detach_interface[] = { {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, - {"target", VSH_OT_DATA, VSH_OFLAG_REQ, N_("target of disk device")}, - {"persistent", VSH_OT_BOOL, 0, N_("persist disk detachment")}, + {"type", VSH_OT_DATA, VSH_OFLAG_REQ, N_("network interface type")}, + {"mac", VSH_OT_STRING, 0, N_("MAC address")}, + {"persistent", VSH_OT_BOOL, 0, N_("persist interface detachment")}, {NULL, 0, 0, NULL} }; static bool -cmdDetachDisk(vshControl *ctl, const vshCmd *cmd) +cmdDetachInterface(vshControl *ctl, const vshCmd *cmd) { + virDomainPtr dom = NULL; xmlDocPtr xml = NULL; xmlXPathObjectPtr obj=NULL; xmlXPathContextPtr ctxt = NULL; xmlNodePtr cur = NULL; xmlBufferPtr xml_buf = NULL; - virDomainPtr dom = NULL; - const char *target = NULL; + const char *mac =NULL, *type = NULL; char *doc; - int i = 0, diff_tgt; + char buf[64]; + int i = 0, diff_mac; int ret; - bool functionReturn = false; + int functionReturn = false; unsigned int flags; if (!vshConnectionUsability(ctl, ctl->conn)) @@ -13031,8 +13016,13 @@ cmdDetachDisk(vshControl *ctl, const vshCmd *cmd) if (!(dom = vshCommandOptDomain(ctl, cmd, NULL))) goto cleanup; - if (vshCommandOptString(cmd, "target", &target) <= 0) + if (vshCommandOptString(cmd, "type", &type) <= 0) + goto cleanup; + + if (vshCommandOptString(cmd, "mac", &mac) < 0) { + vshError(ctl, "%s", _("missing option")); goto cleanup; + } doc = virDomainGetXMLDesc(dom, 0); if (!doc) @@ -13041,34 +13031,44 @@ cmdDetachDisk(vshControl *ctl, const vshCmd *cmd) xml = virXMLParseStringCtxt(doc, _("(domain_definition)"), &ctxt); VIR_FREE(doc); if (!xml) { - vshError(ctl, "%s", _("Failed to get disk information")); + vshError(ctl, "%s", _("Failed to get interface information")); goto cleanup; } - obj = xmlXPathEval(BAD_CAST "/domain/devices/disk", ctxt); + snprintf(buf, sizeof(buf), "/domain/devices/interface[@type='%s']", type); + obj = xmlXPathEval(BAD_CAST buf, ctxt); if ((obj == NULL) || (obj->type != XPATH_NODESET) || (obj->nodesetval == NULL) || (obj->nodesetval->nodeNr == 0)) { - vshError(ctl, "%s", _("Failed to get disk information")); + vshError(ctl, _("No found interface whose type is %s"), type); goto cleanup; } - /* search target */ + if ((!mac) && (obj->nodesetval->nodeNr > 1)) { + vshError(ctl, _("Domain has %d interfaces. Please specify which one " + "to detach using --mac"), obj->nodesetval->nodeNr); + goto cleanup; + } + + if (!mac) + goto hit; + + /* search mac */ for (; i < obj->nodesetval->nodeNr; i++) { cur = obj->nodesetval->nodeTab[i]->children; while (cur != NULL) { if (cur->type == XML_ELEMENT_NODE && - xmlStrEqual(cur->name, BAD_CAST "target")) { - char *tmp_tgt = virXMLPropString(cur, "dev"); - diff_tgt = STREQ(tmp_tgt, target); - VIR_FREE(tmp_tgt); - if (diff_tgt) { + xmlStrEqual(cur->name, BAD_CAST "mac")) { + char *tmp_mac = virXMLPropString(cur, "address"); + diff_mac = virMacAddrCompare (tmp_mac, mac); + VIR_FREE(tmp_mac); + if (!diff_mac) { goto hit; } } cur = cur->next; } } - vshError(ctl, _("No found disk whose target is %s"), target); + vshError(ctl, _("No found interface whose MAC address is %s"), mac); goto cleanup; hit: @@ -13095,19 +13095,19 @@ cmdDetachDisk(vshControl *ctl, const vshCmd *cmd) } if (ret != 0) { - vshError(ctl, "%s", _("Failed to detach disk")); + vshError(ctl, "%s", _("Failed to detach interface")); } else { - vshPrint(ctl, "%s", _("Disk detached successfully\n")); + vshPrint(ctl, "%s", _("Interface detached successfully\n")); functionReturn = true; } cleanup: + if (dom) + virDomainFree(dom); xmlXPathFreeObject(obj); xmlXPathFreeContext(ctxt); xmlFreeDoc(xml); xmlBufferFree(xml_buf); - if (dom) - virDomainFree(dom); return functionReturn; } -- 1.7.4.1

From: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com> This patch adds 2 options to attach-interface as --address and --multifunction. I used --address rather than --pci_address becasue attach-disk has --address for the same purpose, already. --- tools/virsh.c | 38 ++++++++++++++++++++++++++++++++++++-- tools/virsh.pod | 4 ++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/tools/virsh.c b/tools/virsh.c index 2cb3a19..2d0bbd8 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -12485,6 +12485,22 @@ static int str2DiskAddress(const char *str, struct DiskAddress *diskAddr) return -1; } +static int parsePCIAddress(const char *str, struct PCIAddress *address) +{ + char *type, *addr; + + if (!address || !str) + return -1; + + type = (char *)str; + addr = strchr(type, ':'); + if (!addr) + return -1; + if (STREQLEN(type, "pci", addr - type)) + return str2PCIAddress(addr + 1, address); + return -1; +} + static bool cmdAttachDisk(vshControl *ctl, const vshCmd *cmd) { @@ -12797,6 +12813,9 @@ static const vshCmdOptDef opts_attach_interface[] = { {"persistent", VSH_OT_BOOL, 0, N_("persist interface attachment")}, {"inbound", VSH_OT_DATA, VSH_OFLAG_NONE, N_("control domain's incoming traffics")}, {"outbound", VSH_OT_DATA, VSH_OFLAG_NONE, N_("control domain's outgoing traffics")}, + {"address", VSH_OT_DATA, VSH_OFLAG_NONE, N_("pci address of interface")}, + {"multifunction", VSH_OT_BOOL, VSH_OFLAG_NONE, + N_("use multifunction pci under specified address")}, {NULL, 0, 0, NULL} }; @@ -12841,7 +12860,7 @@ cmdAttachInterface(vshControl *ctl, const vshCmd *cmd) virDomainPtr dom = NULL; const char *mac = NULL, *target = NULL, *script = NULL, *type = NULL, *source = NULL, *model = NULL, - *inboundStr = NULL, *outboundStr = NULL; + *inboundStr = NULL, *outboundStr = NULL, *pci_address = NULL; virNetDevBandwidthRate inbound, outbound; int typ; int ret; @@ -12849,6 +12868,7 @@ cmdAttachInterface(vshControl *ctl, const vshCmd *cmd) unsigned int flags; virBuffer buf = VIR_BUFFER_INITIALIZER; char *xml; + struct PCIAddress pci; if (!vshConnectionUsability(ctl, ctl->conn)) goto cleanup; @@ -12865,7 +12885,8 @@ cmdAttachInterface(vshControl *ctl, const vshCmd *cmd) vshCommandOptString(cmd, "script", &script) < 0 || vshCommandOptString(cmd, "model", &model) < 0 || vshCommandOptString(cmd, "inbound", &inboundStr) < 0 || - vshCommandOptString(cmd, "outbound", &outboundStr) < 0) { + vshCommandOptString(cmd, "outbound", &outboundStr) < 0 || + vshCommandOptString(cmd, "address", &pci_address) < 0) { vshError(ctl, "missing argument"); goto cleanup; } @@ -12921,6 +12942,19 @@ cmdAttachInterface(vshControl *ctl, const vshCmd *cmd) if (model != NULL) virBufferAsprintf(&buf, " <model type='%s'/>\n", model); + if (pci_address != NULL) { + if (parsePCIAddress(pci_address, &pci) < 0) { + vshError(ctl, "%s", _("expecting a pci:0000.00.00.00 address.")); + goto cleanup; + } + virBufferAsprintf(&buf, " <address type='pci' domain='0x%04x'" + " bus ='0x%02x' slot='0x%02x' function='0x%0x'", + pci.domain, pci.bus, pci.slot, pci.function); + if (vshCommandOptBool(cmd, "multifunction")) + virBufferAddLit(&buf, " multifunction='on'"); + virBufferAddLit(&buf, "/>\n"); + } + if (inboundStr || outboundStr) { virBufferAsprintf(&buf, " <bandwidth>\n"); if (inboundStr && inbound.average > 0) { diff --git a/tools/virsh.pod b/tools/virsh.pod index c468f13..2682de5 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -1300,6 +1300,7 @@ address. =item B<attach-interface> I<domain-id> I<type> I<source> [I<--target target>] [I<--mac mac>] [I<--script script>] [I<--model model>] [I<--persistent>] [I<--inbound average,peak,burst>] [I<--outbound average,peak,burst>] +[I<--address pci_address>][I<--multifunction>] Attach a new network interface to the domain. I<type> can be either I<network> to indicate a physical network device or I<bridge> to indicate a bridge to a device. @@ -1313,6 +1314,9 @@ I<persistent> indicates the changes will affect the next boot of the domain. I<inbound> and I<outbound> control the bandwidth of the interface. I<peak> and I<burst> are optional, so "average,peak", "average,,burst" and "average" are also legal. +I<address> indicates the pci address where the interface is attached in the +form of pci:domain.bus.slot.function. I<multifunction> indicates specified pci +address is a multifunction pci device address. B<Note>: the optional target value is the name of a device to be created as the back-end on the node. If not provided a device named "vnetN" or "vifN" -- 1.7.4.1
participants (1)
-
KAMEZAWA Hiroyuki