[libvirt] [PATCH v2 0/5] Virtual interface link state modification

This patchset adds the ability for libvirt users to control interface link state of the virtual network devices provided by hypervisors. Modification of the link state is accomplished by adding a new XML element <link state"up"> or "down" to the domain XML. The corresponding network interface is thereafter initialised with the desired state. Live modification of the link state is achieved by calling virDomainUpdateDeviceFlags containing a modified interface configuration. Yet, users may modify only link state of an interface. This feature allows propagation of network topology changes to guests, testing scenarios in complex virtual networks, etc ... Future upgrade: - add support for VirtualBox Modifications to v1: - use virUpdateDeviceFlags instead of dedicated API - incorporate helpful comments by Eric Blake https://bugzilla.redhat.com/show_bug.cgi?id=643373 Peter Krempa (5): link-state: util: Add equality comparision functions for structures link-state: conf: Add element to XML for controling link state link-state: qemu: Add monitor handling for link state modification link-state: qemu: Add net intf modification to virUpdateDeviceFlags link-state: virsh: Add wrapper commands for changing link state docs/formatdomain.html.in | 21 +++ docs/schemas/domain.rng | 11 ++ src/conf/domain_conf.c | 24 ++++ src/conf/domain_conf.h | 11 ++ src/qemu/qemu_driver.c | 24 ++++ src/qemu/qemu_hotplug.c | 176 ++++++++++++++++++++++++++ src/qemu/qemu_hotplug.h | 8 ++ src/qemu/qemu_monitor.c | 19 +++ src/qemu/qemu_monitor.h | 4 + src/qemu/qemu_monitor_json.c | 23 ++++ src/qemu/qemu_monitor_json.h | 4 + src/qemu/qemu_monitor_text.c | 46 +++++++ src/qemu/qemu_monitor_text.h | 4 + src/qemu/qemu_process.c | 47 +++++++- src/util/network.c | 62 +++++++++ src/util/network.h | 5 + tools/virsh.c | 286 ++++++++++++++++++++++++++++++++++++++++++ tools/virsh.pod | 11 ++ 18 files changed, 785 insertions(+), 1 deletions(-) -- 1.7.3.4

This patch adds functions to compare structures containing network device configuration for equality. They serve for the purpose of disallowing unsupported changes to live network devices. --- src/util/network.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/util/network.h | 5 ++++ 2 files changed, 67 insertions(+), 0 deletions(-) diff --git a/src/util/network.c b/src/util/network.c index 6fcdab2..ee69557 100644 --- a/src/util/network.c +++ b/src/util/network.c @@ -842,6 +842,43 @@ error: return ret; } +bool +virVirtualPortProfileEqual(virVirtualPortProfileParamsPtr a, virVirtualPortProfileParamsPtr b) +{ + /* NULL resistant */ + if (!a && !b) + return true; + + if (!a || !b) + return false; + + if (a->virtPortType != b->virtPortType) + return false; + + switch (a->virtPortType) { + case VIR_VIRTUALPORT_NONE: + break; + + case VIR_VIRTUALPORT_8021QBG: + if (a->u.virtPort8021Qbg.managerID != b->u.virtPort8021Qbg.managerID || + a->u.virtPort8021Qbg.typeID != b->u.virtPort8021Qbg.typeID || + a->u.virtPort8021Qbg.typeIDVersion != b->u.virtPort8021Qbg.typeIDVersion || + memcmp(a->u.virtPort8021Qbg.instanceID, b->u.virtPort8021Qbg.instanceID, VIR_UUID_BUFLEN) != 0) + return false; + break; + + case VIR_VIRTUALPORT_8021QBH: + if (STRNEQ(a->u.virtPort8021Qbh.profileID, b->u.virtPort8021Qbh.profileID)) + return false; + break; + + default: + break; + } + + return true; +} + void virVirtualPortProfileFormat(virBufferPtr buf, virVirtualPortProfileParamsPtr virtPort, @@ -1321,3 +1358,28 @@ cleanup: } return ret; } + +bool +virBandwidthEqual(virBandwidthPtr a, + virBandwidthPtr b) +{ + if (!a && !b) + return true; + + if (!a || !b) + return false; + + /* in */ + if (a->in->average != b->in->average || + a->in->peak != b->in->peak || + a->in->burst != b->in->burst) + return false; + + /*out*/ + if (a->out->average != b->out->average || + a->out->peak != b->out->peak || + a->out->burst != b->out->burst) + return false; + + return true; +} diff --git a/src/util/network.h b/src/util/network.h index 6ceaa6d..4d195af 100644 --- a/src/util/network.h +++ b/src/util/network.h @@ -150,6 +150,8 @@ virVirtualPortProfileFormat(virBufferPtr buf, virVirtualPortProfileParamsPtr virtPort, const char *indent); +bool virVirtualPortProfileEqual(virVirtualPortProfileParamsPtr a, virVirtualPortProfileParamsPtr b); + virBandwidthPtr virBandwidthDefParseNode(xmlNodePtr node); void virBandwidthDefFree(virBandwidthPtr def); int virBandwidthDefFormat(virBufferPtr buf, @@ -160,4 +162,7 @@ int virBandwidthEnable(virBandwidthPtr bandwidth, const char *iface); int virBandwidthDisable(const char *iface, bool may_fail); int virBandwidthCopy(virBandwidthPtr *dest, const virBandwidthPtr src); +bool virBandwidthEqual(virBandwidthPtr a, virBandwidthPtr b); + + #endif /* __VIR_NETWORK_H__ */ -- 1.7.3.4

A new element is introduced to XML that allows to control state of virtual network interfaces in hypervisors. Live modification of the link state allows networking tools propagate topology changes to guest OS or testing of scenarios in complex (virtual) networks. This patch adds elements to XML grammars and parsing and generating code. docs/formatdomain.html.in - Documentation of new element docs/schemas/domain.rng - Grammar for the new element src/conf/domain_conf.c - Parsing and generating code src/conf/domain_conf.h - Enum of available link states --- docs/formatdomain.html.in | 21 +++++++++++++++++++++ docs/schemas/domain.rng | 11 +++++++++++ src/conf/domain_conf.c | 24 ++++++++++++++++++++++++ src/conf/domain_conf.h | 11 +++++++++++ 4 files changed, 67 insertions(+), 0 deletions(-) diff --git a/docs/formatdomain.html.in b/docs/formatdomain.html.in index f46771d..47cf0e6 100644 --- a/docs/formatdomain.html.in +++ b/docs/formatdomain.html.in @@ -2024,6 +2024,27 @@ qemu-kvm -net nic,model=? /dev/null <span class="since">Since 0.9.4</span> </p> + <h5><a name="elementLink">Modyfing virtual link state</a></h5> +<pre> + ... + <devices> + <interface type='network'> + <source network='default'/> + <target dev='vnet0'/> + <b><link state='down'/></b> + </interface> + <devices> + ...</pre> + + <p> + This element provides means of setting state of the virtual network link. + Possible values for attribute <code>state</code> are <code>up</code> and + <code>down</code>. If <code>down</code> is specified as the value, the interface + behaves as if it had the network cable disconnected. Default behavior if this + element is unspecified is to have the link state <code>up</code>. + <span class="since">Since 0.9.5</span> + </p> + <h4><a name="elementsInput">Input devices</a></h4> <p> diff --git a/docs/schemas/domain.rng b/docs/schemas/domain.rng index dd8c41a..d2b73ac 100644 --- a/docs/schemas/domain.rng +++ b/docs/schemas/domain.rng @@ -1206,6 +1206,17 @@ <optional> <ref name="bandwidth"/> </optional> + <optional> + <element name="link"> + <attribute name="state"> + <choice> + <value>up</value> + <value>down</value> + </choice> + </attribute> + <empty/> + </element> + </optional> </interleave> </define> <!-- diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 44212cf..f480f37 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -235,6 +235,11 @@ VIR_ENUM_IMPL(virDomainNetVirtioTxMode, VIR_DOMAIN_NET_VIRTIO_TX_MODE_LAST, "iothread", "timer") +VIR_ENUM_IMPL(virDomainNetInterfaceLinkState, VIR_DOMAIN_NET_INTERFACE_LINK_STATE_LAST, + "default", + "up", + "down") + VIR_ENUM_IMPL(virDomainChrChannelTarget, VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_LAST, "guestfwd", @@ -2774,6 +2779,7 @@ virDomainNetDefParseXML(virCapsPtr caps, char *internal = NULL; char *devaddr = NULL; char *mode = NULL; + char *linkstate = NULL; virNWFilterHashTablePtr filterparams = NULL; virVirtualPortProfileParamsPtr virtPort = NULL; virDomainActualNetDefPtr actual = NULL; @@ -2850,6 +2856,9 @@ virDomainNetDefParseXML(virCapsPtr caps, /* An auto-generated target name, blank it out */ VIR_FREE(ifname); } + } else if ((linkstate == NULL) && + xmlStrEqual(cur->name, BAD_CAST "link")) { + linkstate = virXMLPropString(cur, "state"); } else if ((script == NULL) && (def->type == VIR_DOMAIN_NET_TYPE_ETHERNET || def->type == VIR_DOMAIN_NET_TYPE_BRIDGE) && @@ -3114,6 +3123,16 @@ virDomainNetDefParseXML(virCapsPtr caps, } } + def->linkstate = VIR_DOMAIN_NET_INTERFACE_LINK_STATE_DEFAULT; + if (linkstate != NULL) { + if ((def->linkstate = virDomainNetInterfaceLinkStateTypeFromString(linkstate)) <= 0) { + virDomainReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("unknown interface link state '%s'"), + linkstate); + goto error; + } + } + if (filter != NULL) { switch (def->type) { case VIR_DOMAIN_NET_TYPE_ETHERNET: @@ -3161,6 +3180,7 @@ cleanup: VIR_FREE(internal); VIR_FREE(devaddr); VIR_FREE(mode); + VIR_FREE(linkstate); virNWFilterHashTableFree(filterparams); return def; @@ -9057,6 +9077,10 @@ virDomainNetDefFormat(virBufferPtr buf, virBufferAddLit(buf, " </tune>\n"); } + if (def->linkstate) + virBufferAsprintf(buf, " <link state='%s'/>\n", + virDomainNetInterfaceLinkStateTypeToString(def->linkstate)); + if (virBandwidthDefFormat(buf, def->bandwidth, " ") < 0) return -1; diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 8382d28..6e0c8fe 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -352,6 +352,15 @@ enum virDomainNetVirtioTxModeType { VIR_DOMAIN_NET_VIRTIO_TX_MODE_LAST, }; +/* link interface states */ +enum virDomainNetInterfaceLinkState { + VIR_DOMAIN_NET_INTERFACE_LINK_STATE_DEFAULT = 0, /* Default link state (up) */ + VIR_DOMAIN_NET_INTERFACE_LINK_STATE_UP, /* Link is up. ("cable" connected) */ + VIR_DOMAIN_NET_INTERFACE_LINK_STATE_DOWN , /* Link is down. ("cable" disconnected) */ + + VIR_DOMAIN_NET_INTERFACE_LINK_STATE_LAST +}; + /* Config that was actually used to bring up interface, after * resolving network reference. This is private data, only used within * libvirt, but still must maintain backward compatibility, because @@ -437,6 +446,7 @@ struct _virDomainNetDef { char *filter; virNWFilterHashTablePtr filterparams; virBandwidthPtr bandwidth; + int linkstate; }; /* Used for prefix of ifname of any network name generated dynamically @@ -1681,6 +1691,7 @@ VIR_ENUM_DECL(virDomainFSAccessMode) VIR_ENUM_DECL(virDomainNet) VIR_ENUM_DECL(virDomainNetBackend) VIR_ENUM_DECL(virDomainNetVirtioTxMode) +VIR_ENUM_DECL(virDomainNetInterfaceLinkState) VIR_ENUM_DECL(virDomainChrDevice) VIR_ENUM_DECL(virDomainChrChannelTarget) VIR_ENUM_DECL(virDomainChrConsoleTarget) -- 1.7.3.4

This patch adds handlers for modification of guest's interface link state. Both HMP and QMP commands are supported, but as the link state functionality is from the beginning supported in QMP the HMP code will probably never be used. src/qemu/qemu_monitor.c - Add command dispatcher src/qemu/qemu_monitor.h - Add headar for command src/qemu/qemu_monitor_json.c - Add QMP command handling src/qemu/qemu_monitor_json.h - Add header for command src/qemu/qemu_monitor_text.c - Add HMP command handling src/qemu/qemu_monitor_text.h - Add header for command --- src/qemu/qemu_monitor.c | 19 +++++++++++++++++ src/qemu/qemu_monitor.h | 4 +++ src/qemu/qemu_monitor_json.c | 23 +++++++++++++++++++++ src/qemu/qemu_monitor_json.h | 4 +++ src/qemu/qemu_monitor_text.c | 46 ++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_monitor_text.h | 4 +++ 6 files changed, 100 insertions(+), 0 deletions(-) diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index db6107c..5738116 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -1135,6 +1135,25 @@ int qemuMonitorGetCPUInfo(qemuMonitorPtr mon, return ret; } +int qemuMonitorSetLink(qemuMonitorPtr mon, + const char *name, + enum virDomainNetInterfaceLinkState state) +{ + int ret; + VIR_DEBUG("mon=%p, name=%p:%s, state=%u", mon, name, name, state); + + if (!mon || !name) { + qemuReportError(VIR_ERR_INVALID_ARG, + _("monitor || name must not be NULL")); + return -1; + } + + if (mon->json) + ret = qemuMonitorJSONSetLink(mon, name, state); + else + ret = qemuMonitorTextSetLink(mon, name, state); + return ret; +} int qemuMonitorGetVirtType(qemuMonitorPtr mon, int *virtType) diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h index f241c9e..cd46608 100644 --- a/src/qemu/qemu_monitor.h +++ b/src/qemu/qemu_monitor.h @@ -145,6 +145,10 @@ void qemuMonitorUnlock(qemuMonitorPtr mon); int qemuMonitorRef(qemuMonitorPtr mon); int qemuMonitorUnref(qemuMonitorPtr mon) ATTRIBUTE_RETURN_CHECK; +int qemuMonitorSetLink(qemuMonitorPtr mon, + const char *name, + enum virDomainNetInterfaceLinkState state) ; + /* These APIs are for use by the internal Text/JSON monitor impl code only */ char *qemuMonitorNextCommandID(qemuMonitorPtr mon); int qemuMonitorSend(qemuMonitorPtr mon, diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index 715b26e..4736e50 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -955,6 +955,29 @@ int qemuMonitorJSONSystemPowerdown(qemuMonitorPtr mon) return ret; } +int qemuMonitorJSONSetLink(qemuMonitorPtr mon, + const char *name, + enum virDomainNetInterfaceLinkState state) +{ + + int ret; + virJSONValuePtr reply = NULL; + virJSONValuePtr cmd = qemuMonitorJSONMakeCommand("set_link", + "s:name", name, + "b:up", state != VIR_DOMAIN_NET_INTERFACE_LINK_STATE_DOWN, + NULL); + + if (!cmd) + return -1; + + if ((ret = qemuMonitorJSONCommand(mon, cmd, &reply)) == 0) + ret = qemuMonitorJSONCheckError(cmd, reply); + + virJSONValueFree(cmd); + virJSONValueFree(reply); + + return ret; +} int qemuMonitorJSONSystemReset(qemuMonitorPtr mon) { diff --git a/src/qemu/qemu_monitor_json.h b/src/qemu/qemu_monitor_json.h index 9512793..2611b07 100644 --- a/src/qemu/qemu_monitor_json.h +++ b/src/qemu/qemu_monitor_json.h @@ -231,4 +231,8 @@ int qemuMonitorJSONBlockJob(qemuMonitorPtr mon, virDomainBlockJobInfoPtr info, int mode); +int qemuMonitorJSONSetLink(qemuMonitorPtr mon, + const char *name, + enum virDomainNetInterfaceLinkState state); + #endif /* QEMU_MONITOR_JSON_H */ diff --git a/src/qemu/qemu_monitor_text.c b/src/qemu/qemu_monitor_text.c index 4bd761d..2c0b460 100644 --- a/src/qemu/qemu_monitor_text.c +++ b/src/qemu/qemu_monitor_text.c @@ -433,6 +433,52 @@ int qemuMonitorTextSystemPowerdown(qemuMonitorPtr mon) { return 0; } +int qemuMonitorTextSetLink(qemuMonitorPtr mon, const char *name, enum virDomainNetInterfaceLinkState state) { + char *info = NULL; + char *cmd = NULL; + const char *st_str = NULL; + + /* determine state */ + if (state == VIR_DOMAIN_NET_INTERFACE_LINK_STATE_DOWN) + st_str = "off"; + else + st_str = "on"; + + if (virAsprintf(&cmd, "set_link %s %s", name, st_str) < 0) { + virReportOOMError(); + goto error; + } + if (qemuMonitorHMPCommand(mon, cmd, &info) < 0) { + qemuReportError(VIR_ERR_OPERATION_FAILED, + "%s", _("set_link operation failed")); + goto error; + } + + /* check if set_link command is supported */ + if (strstr(info, "\nunknown ")) { + qemuReportError(VIR_ERR_NO_SUPPORT, + "%s", + _("\'set_link\' not supported by this qemu")); + goto error; + } + + /* check if qemu didn't reject device name */ + if (strstr(info, "\nDevice ")) { + qemuReportError(VIR_ERR_OPERATION_FAILED, + "%s", _("device name rejected")); + goto error; + } + + VIR_FREE(info); + VIR_FREE(cmd); + return 0; + +error: + VIR_FREE(info); + VIR_FREE(cmd); + + return -1; +} int qemuMonitorTextSystemReset(qemuMonitorPtr mon) { char *info; diff --git a/src/qemu/qemu_monitor_text.h b/src/qemu/qemu_monitor_text.h index b250738..ddf89fe 100644 --- a/src/qemu/qemu_monitor_text.h +++ b/src/qemu/qemu_monitor_text.h @@ -224,4 +224,8 @@ int qemuMonitorTextBlockJob(qemuMonitorPtr mon, virDomainBlockJobInfoPtr info, int mode); +int qemuMonitorTextSetLink(qemuMonitorPtr mon, + const char *name, + enum virDomainNetInterfaceLinkState state); + #endif /* QEMU_MONITOR_TEXT_H */ -- 1.7.3.4

This patch enables modifying network device configuration using the virUpdateDeviceFlags API method. Matching of devices is accomplished using MAC addresses. While updating live configuration of a running domain, the user is allowed only to change link state of the interface. Additional modifications may be added later. For now the code checks for unsupported changes and tereafer changes the link state, if applicable. When updating persistent configuration of guest's network interface the whole configuration (except for the MAC address) may be modified and is stored for the next startup. src/qemu/qemu_driver.c - Add dispatching of virUpdateDevice for network devices update (live/config) src/qemu/qemu_hotplug.c - add setting of initial link state on live device addition - add function to change network device configuration. By now it supports only changing of link state src/qemu/qemu_hotplug.h - Headers to above functions src/qemu/qemu_process.c - set link states before virtual machine start. Qemu does not support setting of this on the command line. --- src/qemu/qemu_driver.c | 24 +++++++ src/qemu/qemu_hotplug.c | 176 +++++++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_hotplug.h | 8 ++ src/qemu/qemu_process.c | 47 ++++++++++++- 4 files changed, 254 insertions(+), 1 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index f21122d..6013fe3 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -5073,6 +5073,9 @@ qemuDomainUpdateDeviceLive(virDomainObjPtr vm, case VIR_DOMAIN_DEVICE_GRAPHICS: ret = qemuDomainChangeGraphics(driver, vm, dev->data.graphics); break; + case VIR_DOMAIN_DEVICE_NET: + ret = qemuDomainChangeNet(driver, vm, dom, dev->data.net); + break; default: qemuReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("device type '%s' cannot be updated"), @@ -5207,6 +5210,7 @@ qemuDomainUpdateDeviceConfig(virDomainDefPtr vmdef, virDomainDeviceDefPtr dev) { virDomainDiskDefPtr orig, disk; + virDomainNetDefPtr net; int pos; switch (dev->type) { @@ -5245,6 +5249,26 @@ qemuDomainUpdateDeviceConfig(virDomainDefPtr vmdef, } disk->src = NULL; break; + + case VIR_DOMAIN_DEVICE_NET: + net = dev->data.net; + if ((pos = virDomainNetIndexByMac(vmdef, net->mac)) < 0) { + char macbuf[VIR_MAC_STRING_BUFLEN]; + virFormatMacAddr(net->mac, macbuf); + qemuReportError(VIR_ERR_INVALID_ARG, + _("mac %s doesn't exist"), macbuf); + return -1; + } + + VIR_FREE(vmdef->nets[pos]); + + vmdef->nets[pos] = net; + dev->data.net = NULL; + + if (qemuDomainAssignPCIAddresses(vmdef) < 0) + return -1; + break; + default: qemuReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("persistent update of device is not supported")); diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index b7fdfa0..02d1dda 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -743,6 +743,30 @@ int qemuDomainAttachNetDevice(virConnectPtr conn, } qemuDomainObjExitMonitorWithDriver(driver, vm); + /* set link state */ + if (net->linkstate == VIR_DOMAIN_NET_INTERFACE_LINK_STATE_DOWN) { + if (!net->info.alias) { + qemuReportError(VIR_ERR_OPERATION_FAILED, + _("device alias not found: cannot set link state to down")); + } else { + qemuDomainObjEnterMonitorWithDriver(driver, vm); + + if (qemuCapsGet(priv->qemuCaps, QEMU_CAPS_NETDEV)) { + if (qemuMonitorSetLink(priv->mon, net->info.alias, VIR_DOMAIN_NET_INTERFACE_LINK_STATE_DOWN) < 0) { + qemuDomainObjExitMonitorWithDriver(driver, vm); + virDomainAuditNet(vm, NULL, net, "attach", false); + goto try_remove; + } + } else { + qemuReportError(VIR_ERR_OPERATION_FAILED, + _("setting of link state not supported: Link is up")); + } + + qemuDomainObjExitMonitorWithDriver(driver, vm); + } + /* link set to down */ + } + virDomainAuditNet(vm, NULL, net, "attach", true); ret = 0; @@ -1031,6 +1055,158 @@ error: return -1; } +static virDomainNetDefPtr qemuDomainFindNet(virDomainObjPtr vm, + virDomainNetDefPtr dev) +{ + int i; + + for (i = 0; i < vm->def->nnets; i++) { + if (memcmp(vm->def->nets[i]->mac, dev->mac, VIR_MAC_BUFLEN) == 0) + return vm->def->nets[i]; + } + + return NULL; +} + +int qemuDomainChangeNetLinkState(struct qemud_driver *driver, + virDomainObjPtr vm, + virDomainNetDefPtr dev, + int linkstate) +{ + int ret = -1; + qemuDomainObjPrivatePtr priv = vm->privateData; + + VIR_DEBUG("dev: %s, state: %d", dev->info.alias, linkstate); + + if (!dev->info.alias) { + qemuReportError(VIR_ERR_OPERATION_FAILED, + _("can't change link state: device alias not found")); + return -1; + } + + qemuDomainObjEnterMonitorWithDriver(driver, vm); + + ret = qemuMonitorSetLink(priv->mon, dev->info.alias, linkstate); + if (ret < 0) + goto cleanup; + + /* modify the device configuration */ + dev->linkstate = linkstate; + +cleanup: + qemuDomainObjExitMonitorWithDriver(driver, vm); + + return ret; +} + +int qemuDomainChangeNet(struct qemud_driver *driver, + virDomainObjPtr vm, + virDomainPtr dom ATTRIBUTE_UNUSED, + virDomainNetDefPtr dev) + +{ + virDomainNetDefPtr olddev = qemuDomainFindNet(vm, dev); + int ret = 0; + + if (!olddev) { + qemuReportError(VIR_ERR_NO_SUPPORT, + _("cannot find existing network device to modify")); + return -1; + } + + if (olddev->type != dev->type) { + qemuReportError(VIR_ERR_NO_SUPPORT, + _("cannot change network interface type")); + return -1; + } + + switch (olddev->type) { + case VIR_DOMAIN_NET_TYPE_USER: + break; + + case VIR_DOMAIN_NET_TYPE_ETHERNET: + if (STRNEQ_NULLABLE(olddev->data.ethernet.dev, dev->data.ethernet.dev) || + STRNEQ_NULLABLE(olddev->data.ethernet.script, dev->data.ethernet.script) || + STRNEQ_NULLABLE(olddev->data.ethernet.ipaddr, dev->data.ethernet.ipaddr)) { + qemuReportError(VIR_ERR_NO_SUPPORT, + _("cannot modify ethernet network device configuration")); + return -1; + } + break; + + case VIR_DOMAIN_NET_TYPE_SERVER: + case VIR_DOMAIN_NET_TYPE_CLIENT: + case VIR_DOMAIN_NET_TYPE_MCAST: + if (STRNEQ_NULLABLE(olddev->data.socket.address, dev->data.socket.address) || + olddev->data.socket.port != dev->data.socket.port) { + qemuReportError(VIR_ERR_NO_SUPPORT, + _("cannot modify network socket device configuration")); + return -1; + } + break; + + case VIR_DOMAIN_NET_TYPE_NETWORK: + if (STRNEQ_NULLABLE(olddev->data.network.name, dev->data.network.name) || + STRNEQ_NULLABLE(olddev->data.network.portgroup, dev->data.network.portgroup) || + !virVirtualPortProfileEqual(olddev->data.network.virtPortProfile, dev->data.network.virtPortProfile)) { + qemuReportError(VIR_ERR_NO_SUPPORT, + _("cannot modify network device configuration")); + return -1; + } + + break; + + case VIR_DOMAIN_NET_TYPE_INTERNAL: + if (STRNEQ_NULLABLE(olddev->data.internal.name, dev->data.internal.name)) { + qemuReportError(VIR_ERR_NO_SUPPORT, + _("cannot modify internal network device configuration")); + return -1; + } + break; + + case VIR_DOMAIN_NET_TYPE_DIRECT: + if (STRNEQ_NULLABLE(olddev->data.direct.linkdev, dev->data.direct.linkdev) || + olddev->data.direct.mode != dev->data.direct.mode || + !virVirtualPortProfileEqual(olddev->data.direct.virtPortProfile, dev->data.direct.virtPortProfile)) { + qemuReportError(VIR_ERR_NO_SUPPORT, + _("cannot modify direct network device configuration")); + return -1; + } + break; + + default: + qemuReportError(VIR_ERR_INTERNAL_ERROR, + _("unable to change config on '%s' network type"), + virDomainNetTypeToString(dev->type)); + break; + + } + + /* all other unmodifiable parameters */ + if (STRNEQ_NULLABLE(olddev->model, dev->model) || + STRNEQ_NULLABLE(olddev->filter, dev->filter)) { + qemuReportError(VIR_ERR_NO_SUPPORT, + _("cannot modify network device configuration")); + return -1; + } + + /* check if device name has been set, if no, retain the autogenerated one */ + if (dev->ifname && + STRNEQ_NULLABLE(olddev->ifname, dev->ifname)) { + qemuReportError(VIR_ERR_NO_SUPPORT, + _("cannot modify network device configuration")); + return -1; + } + + if (olddev->linkstate != dev->linkstate) { + if ((ret = qemuDomainChangeNetLinkState(driver, vm, olddev, dev->linkstate)) < 0) + return ret; + } + + return ret; +} + + static virDomainGraphicsDefPtr qemuDomainFindGraphics(virDomainObjPtr vm, virDomainGraphicsDefPtr dev) diff --git a/src/qemu/qemu_hotplug.h b/src/qemu/qemu_hotplug.h index 009f1f6..f564f3c 100644 --- a/src/qemu/qemu_hotplug.h +++ b/src/qemu/qemu_hotplug.h @@ -64,6 +64,14 @@ int qemuDomainChangeGraphicsPasswords(struct qemud_driver *driver, int type, virDomainGraphicsAuthDefPtr auth, const char *defaultPasswd); +int qemuDomainChangeNet(struct qemud_driver *driver, + virDomainObjPtr vm, + virDomainPtr dom, + virDomainNetDefPtr dev); +int qemuDomainChangeNetLinkState(struct qemud_driver *driver, + virDomainObjPtr vm, + virDomainNetDefPtr dev, + int linkstate); int qemuDomainDetachPciDiskDevice(struct qemud_driver *driver, virDomainObjPtr vm, virDomainDeviceDefPtr dev); diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 58b4d36..6398c7b 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -1443,7 +1443,40 @@ qemuProcessInitCpuAffinity(virDomainObjPtr vm) return 0; } -/* Set CPU affinites for vcpus if vcpupin xml provided. */ +/* set link states to down on interfaces at qemu start */ +static int +qemuProcessSetLinkStates(virDomainObjPtr vm) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + virDomainDefPtr def = vm->def; + int i; + int ret = 0; + + for (i = 0; i < def->nnets; i++) { + if (def->nets[i]->linkstate == VIR_DOMAIN_NET_INTERFACE_LINK_STATE_DOWN) { + VIR_DEBUG("Setting link state: %s", def->nets[i]->info.alias); + + if (!qemuCapsGet(priv->qemuCaps, QEMU_CAPS_NETDEV)) { + qemuReportError(VIR_ERR_NO_SUPPORT, + _("Setting of link state is not supported by this qemu")); + return -1; + } + + ret = qemuMonitorSetLink(priv->mon, + def->nets[i]->info.alias, + VIR_DOMAIN_NET_INTERFACE_LINK_STATE_DOWN); + if (ret != 0) { + qemuReportError(VIR_ERR_OPERATION_FAILED, + _("Couldn't set link state on interface: %s"), def->nets[i]->info.alias); + break; + } + } + } + + return ret; +} + +/* Set CPU affinities for vcpus if vcpupin xml provided. */ static int qemuProcessSetVcpuAffinites(virConnectPtr conn, virDomainObjPtr vm) @@ -2976,6 +3009,18 @@ int qemuProcessStart(virConnectPtr conn, goto cleanup; } + /* set default link states */ + /* qemu doesn't support setting this on the command line, so + * enter the monitor */ + VIR_DEBUG("Setting network link states"); + qemuDomainObjEnterMonitorWithDriver(driver, vm); + if (qemuProcessSetLinkStates(vm) < 0) { + qemuDomainObjExitMonitorWithDriver(driver, vm); + goto cleanup; + } + + qemuDomainObjExitMonitorWithDriver(driver, vm); + /* Technically, qemuProcessStart can be called from inside * QEMU_ASYNC_JOB_MIGRATION_IN, but we are okay treating this like * a sync job since no other job can call into the domain until -- 1.7.3.4

Two new commands are added to virsh that wrap usage of virDomainUpdateDeviceFlags for changing link state of domain's network interfaces. These wrappers extract network devices's xml configuration and modify the link state for easy manipulation from user's perspective. * domif-setlink - set link state of a domains virtual network interface * domif-getlink - get link state tools/virsh.c - Add functionality to virsh tools/virsh.pod - Manpage documentation --- tools/virsh.c | 286 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ tools/virsh.pod | 11 ++ 2 files changed, 297 insertions(+), 0 deletions(-) diff --git a/tools/virsh.c b/tools/virsh.c index 15b9bdd..90de26d 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -1173,6 +1173,290 @@ cmdDomIfstat (vshControl *ctl, const vshCmd *cmd) return true; } +/* "domif-setlink" command + */ +static const vshCmdInfo info_domif_setlink[] = { + {"help", N_("set link state of a virtual interface")}, + {"desc", N_("Set link state of a domain's virtual interface. This command wraps usage of update-device command.")}, + {NULL,NULL} +}; + +static const vshCmdOptDef opts_domif_setlink[] = { + {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, + {"interface", VSH_OT_DATA, VSH_OFLAG_REQ, N_("interface device (MAC Address)")}, + {"state", VSH_OT_DATA, VSH_OFLAG_REQ, N_("new state of the device")}, + {"persistent", VSH_OT_BOOL, 0, N_("persist interface state")}, + {NULL, 0, 0, NULL} +}; + +static bool +cmdDomIfSetLink (vshControl *ctl, const vshCmd *cmd) +{ + + virDomainPtr dom; + const char *interface; + const char *state; + const char *mac; + const char *desc; + bool persistent; + bool ret = false; + unsigned int flags = 0; + int i; + + xmlDocPtr xml = NULL; + xmlXPathContextPtr ctxt = NULL; + xmlXPathObjectPtr obj = NULL; + xmlNodePtr cur = NULL; + xmlBufferPtr xml_buf = NULL; + + + if (!vshConnectionUsability(ctl, ctl->conn)) + return false; + + if (!(dom = vshCommandOptDomain(ctl, cmd, NULL))) + return false; + + if (vshCommandOptString(cmd, "interface", &interface) <= 0) + goto cleanup; + + if (vshCommandOptString(cmd, "state", &state) <= 0) + goto cleanup; + + persistent = vshCommandOptBool(cmd, "persistent"); + + if (STRNEQ(state, "up") && STRNEQ(state, "down")) { + vshError(ctl, _("invalid link state '%s'"), state); + goto cleanup; + } + + /* get persistent or live description of network device */ + desc = virDomainGetXMLDesc(dom, persistent?VIR_DOMAIN_XML_INACTIVE:0); + if (desc == NULL) { + vshError(ctl, _("Failed to get domain description xml")); + goto cleanup; + } + + if (persistent) + flags = VIR_DOMAIN_AFFECT_CONFIG; + else + flags = VIR_DOMAIN_AFFECT_LIVE; + + if (virDomainIsActive(dom) == 0) + flags = VIR_DOMAIN_AFFECT_CONFIG; + + /* extract current network device description */ + xml = virXMLParseStringCtxt(desc, "domain configuration", &ctxt); + VIR_FREE(desc); + if (!xml) { + vshError(ctl, _("Failed to parse domain description xml")); + goto cleanup; + } + + obj = xmlXPathEval(BAD_CAST "/domain/devices/interface", ctxt); + if (obj == NULL || obj->type != XPATH_NODESET || + obj->nodesetval == NULL || obj->nodesetval->nodeNr == 0) { + vshError(ctl, _("Failed to extract interface information or no interfaces found")); + goto cleanup; + } + + /* find interface with matching mac addr */ + for (i = 0; i < obj->nodesetval->nodeNr; i++) { + cur = obj->nodesetval->nodeTab[i]->children; + + while (cur) { + if (cur->type == XML_ELEMENT_NODE && + xmlStrEqual(cur->name, BAD_CAST "mac")) { + mac = virXMLPropString(cur, "address"); + + if (STRCASEEQ(mac, interface)) { + VIR_FREE(mac); + goto hit; + } + VIR_FREE(mac); + } + cur = cur->next; + } + } + + vshError(ctl, _("interface with address '%s' not found"), interface); + goto cleanup; + +hit: + /* find and modify/add link state node */ + /* try to find <link> element */ + cur = obj->nodesetval->nodeTab[i]->children; + + while (cur) { + if (cur->type == XML_ELEMENT_NODE && + xmlStrEqual(cur->name, BAD_CAST "link")) { + /* found, just modify the property */ + xmlSetProp(cur, BAD_CAST "state", BAD_CAST state); + + break; + } + cur = cur->next; + } + + if (!cur) { + /* element <link> not found, add one */ + cur = xmlNewChild(obj->nodesetval->nodeTab[i], + NULL, + BAD_CAST "link", + NULL); + if (!cur) + goto cleanup; + + if (xmlNewProp(cur, BAD_CAST "state", BAD_CAST state) == NULL) + goto cleanup; + } + + xml_buf = xmlBufferCreate(); + if (!xml_buf) { + vshError(ctl, _("Failed to allocate memory")); + goto cleanup; + } + + if (xmlNodeDump(xml_buf, xml, obj->nodesetval->nodeTab[i], 0, 0) < 0 ) { + vshError(ctl, _("Failed to create XML")); + goto cleanup; + } + + if (virDomainUpdateDeviceFlags(dom, (char *)xmlBufferContent(xml_buf), flags) < 0) { + vshError(ctl, _("Failed to update interface link state")); + goto cleanup; + } else { + vshPrint(ctl, "%s", _("Device updated successfully\n")); + ret = true; + } + +cleanup: + xmlXPathFreeObject(obj); + xmlXPathFreeContext(ctxt); + xmlFreeDoc(xml); + xmlBufferFree(xml_buf); + + if (dom) + virDomainFree(dom); + + return ret; +} + +/* "domif-getlink" command + */ +static const vshCmdInfo info_domif_getlink[] = { + {"help", N_("get link state of a virtual interface")}, + {"desc", N_("Get link state of a domain's virtual interface.")}, + {NULL,NULL} +}; + +static const vshCmdOptDef opts_domif_getlink[] = { + {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, + {"interface", VSH_OT_DATA, VSH_OFLAG_REQ, N_("interface device (MAC Address)")}, + {"persistent", VSH_OT_BOOL, 0, N_("Get persistent interface state")}, + {NULL, 0, 0, NULL} +}; + +static bool +cmdDomIfGetLink (vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom; + const char *interface = NULL; + int flags = 0; + char *state = NULL; + char *mac = NULL; + bool ret = false; + int i; + + char *desc; + xmlDocPtr xml = NULL; + xmlXPathContextPtr ctxt = NULL; + xmlNodePtr cur = NULL; + xmlXPathObjectPtr obj = NULL; + + if (!vshConnectionUsability (ctl, ctl->conn)) + return false; + + if (!(dom = vshCommandOptDomain (ctl, cmd, NULL))) + return false; + + if (vshCommandOptString (cmd, "interface", &interface) <= 0) { + virDomainFree(dom); + return false; + } + + if (vshCommandOptBool(cmd, "persistent")) + flags = VIR_DOMAIN_XML_INACTIVE; + + desc = virDomainGetXMLDesc(dom, flags); + if (desc == NULL) { + vshError(ctl, _("Failed to get domain description xml")); + goto cleanup; + } + + xml = virXMLParseStringCtxt(desc, "domain configuration", &ctxt); + VIR_FREE(desc); + if (!xml) { + vshError(ctl, _("Failed to parse domain description xml")); + goto cleanup; + } + + obj = xmlXPathEval(BAD_CAST "/domain/devices/interface", ctxt); + if (obj == NULL || obj->type != XPATH_NODESET || + obj->nodesetval == NULL || obj->nodesetval->nodeNr == 0) { + vshError(ctl, _("Failed to extract interface information or no interfaces found")); + goto cleanup; + } + + /* find interface with matching mac addr */ + for (i = 0; i < obj->nodesetval->nodeNr; i++) { + cur = obj->nodesetval->nodeTab[i]->children; + + while (cur) { + if (cur->type == XML_ELEMENT_NODE && + xmlStrEqual(cur->name, BAD_CAST "mac")) { + + mac = virXMLPropString(cur, "address"); + + if (STRCASEEQ(mac, interface)){ + VIR_FREE(mac); + goto hit; + } + } + cur = cur->next; + } + } + + vshError(ctl, _("Interface with address '%s' not found."), interface); + goto cleanup; + +hit: + cur = obj->nodesetval->nodeTab[i]->children; + while (cur) { + if (cur->type == XML_ELEMENT_NODE && + xmlStrEqual(cur->name, BAD_CAST "link")) { + + state = virXMLPropString(cur, "state"); + vshPrint(ctl, "%s %s", interface, state); + VIR_FREE(state); + + goto cleanup; + } + cur = cur->next; + } + + /* attribute not found */ + vshPrint(ctl, "%s default", interface); + +cleanup: + xmlXPathFreeObject(obj); + xmlXPathFreeContext(ctxt); + xmlFreeDoc(xml); + if (dom) + virDomainFree(dom); + + return ret; +} + /* * "dommemstats" command */ @@ -12548,6 +12832,7 @@ static const vshCmdDef domManagementCmds[] = { {"detach-interface", cmdDetachInterface, opts_detach_interface, info_detach_interface, 0}, {"domid", cmdDomid, opts_domid, info_domid, 0}, + {"domif-setlink", cmdDomIfSetLink, opts_domif_setlink, info_domif_setlink, 0}, {"domjobabort", cmdDomjobabort, opts_domjobabort, info_domjobabort, 0}, {"domjobinfo", cmdDomjobinfo, opts_domjobinfo, info_domjobinfo, 0}, {"domname", cmdDomname, opts_domname, info_domname, 0}, @@ -12605,6 +12890,7 @@ static const vshCmdDef domMonitoringCmds[] = { {"domblkinfo", cmdDomblkinfo, opts_domblkinfo, info_domblkinfo, 0}, {"domblkstat", cmdDomblkstat, opts_domblkstat, info_domblkstat, 0}, {"domcontrol", cmdDomControl, opts_domcontrol, info_domcontrol, 0}, + {"domif-getlink", cmdDomIfGetLink, opts_domif_getlink, info_domif_getlink, 0}, {"domifstat", cmdDomIfstat, opts_domifstat, info_domifstat, 0}, {"dominfo", cmdDominfo, opts_dominfo, info_dominfo, 0}, {"dommemstat", cmdDomMemStat, opts_dommemstat, info_dommemstat, 0}, diff --git a/tools/virsh.pod b/tools/virsh.pod index 81d7a1e..2d413c0 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -427,6 +427,17 @@ Get device block stats for a running domain. Get network interface stats for a running domain. +=item B<domif-setlink> I<domain> I<interface-MAC> I<state> I<--persistent> + +Modify link state of the domain's virtual interface. Possible values for +state are "up" and "down. If --persistent is specified, only the persistent +configuration of the domain is modified. + +=item B<domif-setlink> I<domain> I<interface-MAC> I<--persistent> + +Query link state of the domain's virtual interface. If --persistent +is specified, query the persistent configuration. + =item B<dommemstat> I<domain> Get memory stats for a running domain. -- 1.7.3.4
participants (1)
-
Peter Krempa