libvirt/src/util/virnetdevopenvswitch.c

902 lines
30 KiB
C
Raw Normal View History

/*
maint: avoid 'const fooPtr' in virnet files 'const fooPtr' is the same as 'foo * const' (the pointer won't change, but it's contents can). But in general, if an interface is trying to be const-correct, it should be using 'const foo *' (the pointer is to data that can't be changed). Fix up remaining offenders in src/util. * src/util/virnetdev.h (virNetDevSetMAC) (virNetDevReplaceMacAddress, virNetDevValidateConfig) (virNetDevReplaceNetConfig): Use intended type. * src/util/virnetdevbandwidth.h (virNetDevBandwidthCopy) (virNetDevBandwidthPlug): Likewise. * src/util/virnetdevmacvlan.h (virNetDevMacVLanCreate) (virNetDevMacVLanCreateWithVPortProfile) (virNetDevMacVLanDeleteWithVPortProfile) (virNetDevMacVLanRestartWithVPortProfile) (virNetDevMacVLanVPortProfileRegisterCallback): Likewise. * src/util/virnetdevopenvswitch.h (virNetDevOpenvswitchAddPort): Likewise. * src/util/virnetdevtap.h (virNetDevTapCreateInBridgePort): Likewise. * src/util/virnetdevvlan.h (virNetDevVlanEqual) (virNetDevVlanCopy): Likewise. * src/util/virnetdevvportprofile.h (virNetDevVPortProfileAssociate) (virNetDevVPortProfileDisassociate): Likewise. * src/util/virnetlink.h (virNetlinkEventRemoveCallback) (virNetlinkEventAddClient, virNetlinkEventRemoveClient): Likewise. * src/util/virnetdev.c (virNetDevSetMAC) (virNetDevReplaceMacAddress, virNetDevValidateConfig) (virNetDevReplaceNetConfig): Fix fallout. * src/util/virnetdevbandwidth.c (virNetDevBandwidthCopy) (virNetDevBandwidthPlug): Likewise. * src/util/virnetdevmacvlan.c (virNetDevMacVLanCreate) (virNetDevMacVLanCreateWithVPortProfile) (virNetDevMacVLanDeleteWithVPortProfile) (virNetDevMacVLanRestartWithVPortProfile) (virNetDevMacVLanVPortProfileRegisterCallback): Likewise. * src/util/virnetdevopenvswitch.c (virNetDevOpenvswitchAddPort): Likewise. * src/util/virnetdevtap.c (virNetDevTapCreateInBridgePort): Likewise. * src/util/virnetdevvlan.c (virNetDevVlanEqual) (virNetDevVlanCopy): Likewise. * src/util/virnetdevvportprofile.c (virNetDevVPortProfileAssociate) (virNetDevVPortProfileDisassociate) (virNetDevVPortProfileOpSetLink, virNetDevVPortProfileOpCommon) (virNetDevVPortProfileOp8021Qbg, virNetDevVPortProfileOp8021Qbh): Likewise. * src/util/virnetlink.c (virNetlinkEventRemoveCallback) (virNetlinkEventAddClient, virNetlinkEventRemoveClient): Likewise. Signed-off-by: Eric Blake <eblake@redhat.com>
2013-10-05 19:41:44 +00:00
* Copyright (C) 2013 Red Hat, Inc.
* Copyright (C) 2012 Nicira, Inc.
* Copyright (C) 2017 IBM Corporation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include "virnetdevopenvswitch.h"
#include "vircommand.h"
2012-12-12 18:06:53 +00:00
#include "viralloc.h"
#include "virerror.h"
#include "virmacaddr.h"
#include "virstring.h"
util: don't fail if no PortData is found while getting migrateData Introduced by f6a2f97e Problem Description: After multiple times of migrating a domain, which has an ovs interface with no portData set, with non-shared disk, nbd ports got overflowed. The steps to reproduce the problem: 1 define and start a domain with its network configured as: <interface type='bridge'> <source bridge='br0'/> <virtualport type='openvswitch'> </virtualport> <model type='virtio'/> <driver name='vhost' queues='4'/> </interface> 2 do not set the network's portData. 3 migrate(ToURI2) it with flag 91(1011011), which means: VIR_MIGRATE_LIVE VIR_MIGRATE_PEER2PEER VIR_MIGRATE_PERSIST_DEST VIR_MIGRATE_UNDEFINE_SOURCE VIR_MIGRATE_NON_SHARED_DISK 4 migrate success, but we got an error log in libvirtd.log: error : virCommandWait:2423 : internal error: Child process (ovs-vsctl --timeout=5 get Interface vnet1 external_ids:PortData) unexpected exit status 1: ovs-vsctl: no key "PortData" in Interface record "vnet1" column external_ids 5 migrate it back, migrate it , migrate it back, ....... 6 nbd port got overflowed. The reasons for the problem is : 1 virNetDevOpenvswitchGetMigrateData() takes it as wrong if no portData is available for the ovs interface of a domain. (We think it's not appropriate, as portData is just OPTIONAL) 2 in func qemuMigrationBakeCookie(), it fails in qemuMigrationCookieAddNetwork(), and returns with -1. qemuMigrationCookieAddNBD() is not called thereafter, and mig->nbd is still NULL. 3 However, qemuMigrationRun() just *WARN* if qemuMigrationBakeCookie() fails, migration still successes. cookie is NULL, it's not baked on the src side. 4 On the destination side, it would alloc a port first and then free the nbd port in COOKIE. But the cookie is NULL due to qemuMigrationCookieAddNetwork() failure at src side. thus the nbd port is not freed. In this patch, we add "--if-exists" option to make ovs-vsctl not raise error if there's no portData available. Further more, because portData may be NULL in the cookie at the dest side, check it before setting portData. Signed-off-by: Zhou Yimin <zhouyimin@huawei.com> Signed-off-by: Zhang Bo <oscar.zhangbo@huawei.com>
2015-03-05 02:01:50 +00:00
#include "virlog.h"
#include "virjson.h"
#include "virfile.h"
#include "virutil.h"
#define VIR_FROM_THIS VIR_FROM_NONE
util: don't fail if no PortData is found while getting migrateData Introduced by f6a2f97e Problem Description: After multiple times of migrating a domain, which has an ovs interface with no portData set, with non-shared disk, nbd ports got overflowed. The steps to reproduce the problem: 1 define and start a domain with its network configured as: <interface type='bridge'> <source bridge='br0'/> <virtualport type='openvswitch'> </virtualport> <model type='virtio'/> <driver name='vhost' queues='4'/> </interface> 2 do not set the network's portData. 3 migrate(ToURI2) it with flag 91(1011011), which means: VIR_MIGRATE_LIVE VIR_MIGRATE_PEER2PEER VIR_MIGRATE_PERSIST_DEST VIR_MIGRATE_UNDEFINE_SOURCE VIR_MIGRATE_NON_SHARED_DISK 4 migrate success, but we got an error log in libvirtd.log: error : virCommandWait:2423 : internal error: Child process (ovs-vsctl --timeout=5 get Interface vnet1 external_ids:PortData) unexpected exit status 1: ovs-vsctl: no key "PortData" in Interface record "vnet1" column external_ids 5 migrate it back, migrate it , migrate it back, ....... 6 nbd port got overflowed. The reasons for the problem is : 1 virNetDevOpenvswitchGetMigrateData() takes it as wrong if no portData is available for the ovs interface of a domain. (We think it's not appropriate, as portData is just OPTIONAL) 2 in func qemuMigrationBakeCookie(), it fails in qemuMigrationCookieAddNetwork(), and returns with -1. qemuMigrationCookieAddNBD() is not called thereafter, and mig->nbd is still NULL. 3 However, qemuMigrationRun() just *WARN* if qemuMigrationBakeCookie() fails, migration still successes. cookie is NULL, it's not baked on the src side. 4 On the destination side, it would alloc a port first and then free the nbd port in COOKIE. But the cookie is NULL due to qemuMigrationCookieAddNetwork() failure at src side. thus the nbd port is not freed. In this patch, we add "--if-exists" option to make ovs-vsctl not raise error if there's no portData available. Further more, because portData may be NULL in the cookie at the dest side, check it before setting portData. Signed-off-by: Zhou Yimin <zhouyimin@huawei.com> Signed-off-by: Zhang Bo <oscar.zhangbo@huawei.com>
2015-03-05 02:01:50 +00:00
VIR_LOG_INIT("util.netdevopenvswitch");
/*
* Set openvswitch default timeout
*/
static unsigned int virNetDevOpenvswitchTimeout = VIR_NETDEV_OVS_DEFAULT_TIMEOUT;
/**
* virNetDevOpenvswitchSetTimeout:
* @timeout: the timeout in seconds
*
* Set the openvswitch timeout
*/
void
virNetDevOpenvswitchSetTimeout(unsigned int timeout)
{
virNetDevOpenvswitchTimeout = timeout;
}
static virCommand *
virNetDevOpenvswitchCreateCmd(void)
{
virCommand *cmd = virCommandNew(OVS_VSCTL);
virCommandAddArgFormat(cmd, "--timeout=%u", virNetDevOpenvswitchTimeout);
return cmd;
}
/**
* virNetDevOpenvswitchConstructVlans:
* @cmd: command to construct
* @virtVlan: VLAN configuration to be applied
*
* Construct the VLAN configuration parameters to be passed to
* ovs-vsctl command.
*/
static void
virNetDevOpenvswitchConstructVlans(virCommand *cmd, const virNetDevVlan *virtVlan)
{
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
if (!virtVlan || !virtVlan->nTags)
return;
switch (virtVlan->nativeMode) {
case VIR_NATIVE_VLAN_MODE_TAGGED:
virCommandAddArg(cmd, "vlan_mode=native-tagged");
virCommandAddArgFormat(cmd, "tag=%d", virtVlan->nativeTag);
break;
case VIR_NATIVE_VLAN_MODE_UNTAGGED:
virCommandAddArg(cmd, "vlan_mode=native-untagged");
virCommandAddArgFormat(cmd, "tag=%d", virtVlan->nativeTag);
break;
case VIR_NATIVE_VLAN_MODE_DEFAULT:
default:
break;
}
if (virtVlan->trunk) {
size_t i;
virBufferAddLit(&buf, "trunk=");
/*
* Trunk ports have at least one VLAN. Do the first one
* outside the "for" loop so we can put a "," at the
* start of the for loop if there are more than one VLANs
* on this trunk port.
*/
virBufferAsprintf(&buf, "%d", virtVlan->tag[0]);
for (i = 1; i < virtVlan->nTags; i++) {
virBufferAddLit(&buf, ",");
virBufferAsprintf(&buf, "%d", virtVlan->tag[i]);
}
virCommandAddArg(cmd, virBufferCurrentContent(&buf));
} else if (virtVlan->nTags) {
virCommandAddArgFormat(cmd, "tag=%d", virtVlan->tag[0]);
}
}
/**
* virNetDevOpenvswitchAddPort:
* @brname: the bridge name
* @ifname: the network interface name
* @macaddr: the mac address of the virtual interface
* @vmuuid: the Domain UUID that has this interface
* @ovsport: the ovs specific fields
*
* Add an interface to the OVS bridge
*
* Returns 0 in case of success or -1 in case of failure.
*/
int virNetDevOpenvswitchAddPort(const char *brname, const char *ifname,
const virMacAddr *macaddr,
const unsigned char *vmuuid,
const virNetDevVPortProfile *ovsport,
const virNetDevVlan *virtVlan)
{
char macaddrstr[VIR_MAC_STRING_BUFLEN];
char ifuuidstr[VIR_UUID_STRING_BUFLEN];
char vmuuidstr[VIR_UUID_STRING_BUFLEN];
g_autoptr(virCommand) cmd = NULL;
g_autofree char *attachedmac_ex_id = NULL;
g_autofree char *ifaceid_ex_id = NULL;
g_autofree char *profile_ex_id = NULL;
g_autofree char *vmid_ex_id = NULL;
g_autofree char *ifname_ex_id = NULL;
virMacAddrFormat(macaddr, macaddrstr);
virUUIDFormat(ovsport->interfaceID, ifuuidstr);
virUUIDFormat(vmuuid, vmuuidstr);
attachedmac_ex_id = g_strdup_printf("external-ids:attached-mac=\"%s\"",
macaddrstr);
ifaceid_ex_id = g_strdup_printf("external-ids:iface-id=\"%s\"", ifuuidstr);
vmid_ex_id = g_strdup_printf("external-ids:vm-id=\"%s\"", vmuuidstr);
ifname_ex_id = g_strdup_printf("external-ids:ifname=\"%s\"", ifname);
if (ovsport->profileID[0] != '\0') {
profile_ex_id = g_strdup_printf("external-ids:port-profile=\"%s\"",
ovsport->profileID);
}
cmd = virNetDevOpenvswitchCreateCmd();
openvswitch: don't delete existing OVS port prior to recreating same port Connecting a tap device to an Open vSwitch is done by adding a "port" to the switch with the ovs-vsctl "add-port" command. The port will have the same name as the tap device, but it is a separate entity, and can survive beyond the destruction of the tap device (although under normal circumstances the port will be deleted around the same time the tap device is deleted). This makes it possible for a port of a particular name to already exist at the time libvirt calls ovs-vsctl to add that port. The original commit of Open vSwitch support (commit df81004632, libvirt 0.9.10, Feb. 2012) used the "--may-exist" option to the add-port command to indicate that a port of the desired name might already exist, and that it was okay to simply re-use this port (rather than failing with an error message). Then in commit 33445ce8446d9 (libvirt 1.2.7, April 2014) the command was changed to use "--if-exists del-port blah" instead of "--may-exist". The reason given was that there was a bug in OVS where a stale port would be unusable even though it still existed; the workaround was to forcibly delete any existing port prior to adding the new port (of the same name). This is the ovs-vsctl command still in use by libvirt today. It recently came up in the discussion of a bug concerning guest packet loss during OpenStack upgrades (https://bugzilla.redhat.com/1963164) that the bug in OVS that necessitated the del-port workaround was fixed quite a long time ago (August 2015): https://github.com/openvswitch/ovs/commit/e21c6643a02c6b446d2fbdfde366ea303b4c2730 thus rendering the workaround in libvirt unnecessary. The assertion in that discussion is that this workaround is now the cause of the packet loss being experienced during OpenStack upgrades. I'm not convinced this is the case, but it does appear that there is no reason to carry this workaround in libvirt any longer, so this patch reverts the code back to the original behavior (using "--may-exist" instead of "--if-exists del-port"). Signed-off-by: Laine Stump <laine@redhat.com> Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
2021-06-07 20:27:49 +00:00
virCommandAddArgList(cmd, "--", "--may-exist",
"add-port", brname, ifname, NULL);
virNetDevOpenvswitchConstructVlans(cmd, virtVlan);
if (ovsport->profileID[0] == '\0') {
virCommandAddArgList(cmd,
"--", "set", "Interface", ifname, attachedmac_ex_id,
"--", "set", "Interface", ifname, ifaceid_ex_id,
"--", "set", "Interface", ifname, vmid_ex_id,
"--", "set", "Interface", ifname,
"external-ids:iface-status=active",
NULL);
} else {
virCommandAddArgList(cmd,
"--", "set", "Interface", ifname, attachedmac_ex_id,
"--", "set", "Interface", ifname, ifaceid_ex_id,
"--", "set", "Interface", ifname, vmid_ex_id,
"--", "set", "Interface", ifname, profile_ex_id,
"--", "set", "Interface", ifname, ifname_ex_id,
"--", "set", "Interface", ifname,
"external-ids:iface-status=active",
NULL);
}
if (virCommandRun(cmd, NULL) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unable to add port %s to OVS bridge %s"),
ifname, brname);
return -1;
}
return 0;
}
/**
* virNetDevOpenvswitchRemovePort:
* @ifname: the network interface name
*
* Deletes an interface from a OVS bridge
*
* Returns 0 in case of success or -1 in case of failure.
*/
int virNetDevOpenvswitchRemovePort(const char *brname G_GNUC_UNUSED, const char *ifname)
{
g_autoptr(virCommand) cmd = virNetDevOpenvswitchCreateCmd();
virCommandAddArgList(cmd, "--", "--if-exists", "del-port", ifname, NULL);
if (virCommandRun(cmd, NULL) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unable to delete port %s from OVS"), ifname);
return -1;
}
return 0;
}
/**
* virNetDevOpenvswitchGetMigrateData:
* @migrate: a pointer to store the data into, allocated by this function
* @ifname: name of the interface for which data is being migrated
*
* Allocates data to be migrated specific to Open vSwitch
*
* Returns 0 in case of success or -1 in case of failure
*/
int virNetDevOpenvswitchGetMigrateData(char **migrate, const char *ifname)
{
size_t len;
g_autoptr(virCommand) cmd = virNetDevOpenvswitchCreateCmd();
virCommandAddArgList(cmd, "--if-exists", "get", "Interface",
ifname, "external_ids:PortData", NULL);
virCommandSetOutputBuffer(cmd, migrate);
/* Run the command */
if (virCommandRun(cmd, NULL) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unable to run command to get OVS port data for "
"interface %s"), ifname);
return -1;
}
/* Wipeout the newline, if it exists */
len = strlen(*migrate);
if (len > 0)
(*migrate)[len - 1] = '\0';
return 0;
}
/**
* virNetDevOpenvswitchSetMigrateData:
* @migrate: the data which was transferred during migration
* @ifname: the name of the interface the data is associated with
*
* Repopulates OVS per-port data on destination host
*
* Returns 0 in case of success or -1 in case of failure
*/
int virNetDevOpenvswitchSetMigrateData(char *migrate, const char *ifname)
{
g_autoptr(virCommand) cmd = NULL;
util: don't fail if no PortData is found while getting migrateData Introduced by f6a2f97e Problem Description: After multiple times of migrating a domain, which has an ovs interface with no portData set, with non-shared disk, nbd ports got overflowed. The steps to reproduce the problem: 1 define and start a domain with its network configured as: <interface type='bridge'> <source bridge='br0'/> <virtualport type='openvswitch'> </virtualport> <model type='virtio'/> <driver name='vhost' queues='4'/> </interface> 2 do not set the network's portData. 3 migrate(ToURI2) it with flag 91(1011011), which means: VIR_MIGRATE_LIVE VIR_MIGRATE_PEER2PEER VIR_MIGRATE_PERSIST_DEST VIR_MIGRATE_UNDEFINE_SOURCE VIR_MIGRATE_NON_SHARED_DISK 4 migrate success, but we got an error log in libvirtd.log: error : virCommandWait:2423 : internal error: Child process (ovs-vsctl --timeout=5 get Interface vnet1 external_ids:PortData) unexpected exit status 1: ovs-vsctl: no key "PortData" in Interface record "vnet1" column external_ids 5 migrate it back, migrate it , migrate it back, ....... 6 nbd port got overflowed. The reasons for the problem is : 1 virNetDevOpenvswitchGetMigrateData() takes it as wrong if no portData is available for the ovs interface of a domain. (We think it's not appropriate, as portData is just OPTIONAL) 2 in func qemuMigrationBakeCookie(), it fails in qemuMigrationCookieAddNetwork(), and returns with -1. qemuMigrationCookieAddNBD() is not called thereafter, and mig->nbd is still NULL. 3 However, qemuMigrationRun() just *WARN* if qemuMigrationBakeCookie() fails, migration still successes. cookie is NULL, it's not baked on the src side. 4 On the destination side, it would alloc a port first and then free the nbd port in COOKIE. But the cookie is NULL due to qemuMigrationCookieAddNetwork() failure at src side. thus the nbd port is not freed. In this patch, we add "--if-exists" option to make ovs-vsctl not raise error if there's no portData available. Further more, because portData may be NULL in the cookie at the dest side, check it before setting portData. Signed-off-by: Zhou Yimin <zhouyimin@huawei.com> Signed-off-by: Zhang Bo <oscar.zhangbo@huawei.com>
2015-03-05 02:01:50 +00:00
if (!migrate) {
VIR_DEBUG("No OVS port data for interface %s", ifname);
return 0;
}
cmd = virNetDevOpenvswitchCreateCmd();
virCommandAddArgList(cmd, "set", "Interface", ifname, NULL);
virCommandAddArgFormat(cmd, "external_ids:PortData=%s", migrate);
/* Run the command */
if (virCommandRun(cmd, NULL) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unable to run command to set OVS port data for "
"interface %s"), ifname);
return -1;
}
return 0;
}
/**
* virNetDevOpenvswitchInterfaceParseStats:
* @json: Input string in JSON format
* @stats: parsed stats
*
* For given input string @json parse interface statistics and store them into
* @stats.
*
* Returns: 0 on success,
* -1 otherwise (with error reported).
*/
int
virNetDevOpenvswitchInterfaceParseStats(const char *json,
virDomainInterfaceStatsPtr stats)
{
g_autoptr(virJSONValue) jsonStats = NULL;
virJSONValue *jsonMap = NULL;
size_t i;
stats->rx_bytes = stats->rx_packets = stats->rx_errs = stats->rx_drop = -1;
stats->tx_bytes = stats->tx_packets = stats->tx_errs = stats->tx_drop = -1;
if (!(jsonStats = virJSONValueFromString(json)) ||
!virJSONValueIsArray(jsonStats) ||
!(jsonMap = virJSONValueArrayGet(jsonStats, 1))) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Unable to parse ovs-vsctl output"));
return -1;
}
for (i = 0; i < virJSONValueArraySize(jsonMap); i++) {
virJSONValue *item = virJSONValueArrayGet(jsonMap, i);
virJSONValue *jsonKey;
virJSONValue *jsonVal;
const char *key;
long long val;
if (!item ||
(!(jsonKey = virJSONValueArrayGet(item, 0))) ||
(!(jsonVal = virJSONValueArrayGet(item, 1))) ||
(!(key = virJSONValueGetString(jsonKey))) ||
(virJSONValueGetNumberLong(jsonVal, &val) < 0)) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Malformed ovs-vsctl output"));
return -1;
}
/* The TX/RX fields appear to be swapped here
* because this is the host view. */
if (STREQ(key, "rx_bytes")) {
stats->tx_bytes = val;
} else if (STREQ(key, "rx_packets")) {
stats->tx_packets = val;
} else if (STREQ(key, "rx_errors")) {
stats->tx_errs = val;
} else if (STREQ(key, "rx_dropped")) {
stats->tx_drop = val;
} else if (STREQ(key, "tx_bytes")) {
stats->rx_bytes = val;
} else if (STREQ(key, "tx_packets")) {
stats->rx_packets = val;
} else if (STREQ(key, "tx_errors")) {
stats->rx_errs = val;
} else if (STREQ(key, "tx_dropped")) {
stats->rx_drop = val;
} else {
VIR_DEBUG("Unused ovs-vsctl stat key=%s val=%lld", key, val);
}
}
return 0;
}
/**
* virNetDevOpenvswitchInterfaceStats:
* @ifname: the name of the interface
* @stats: the retrieved domain interface stat
*
* Retrieves the OVS interfaces stats
*
* Returns 0 in case of success or -1 in case of failure
*/
int
virNetDevOpenvswitchInterfaceStats(const char *ifname,
virDomainInterfaceStatsPtr stats)
{
g_autoptr(virCommand) cmd = virNetDevOpenvswitchCreateCmd();
g_autofree char *output = NULL;
virCommandAddArgList(cmd, "--if-exists", "--format=list", "--data=json",
"--no-headings", "--columns=statistics", "list",
"Interface", ifname, NULL);
virCommandSetOutputBuffer(cmd, &output);
/* The above command returns either:
* 1) empty string if @ifname doesn't exist, or
* 2) a JSON array, for instance:
* ["map",[["collisions",0],["rx_bytes",0],["rx_crc_err",0],["rx_dropped",0],
* ["rx_errors",0],["rx_frame_err",0],["rx_over_err",0],["rx_packets",0],
* ["tx_bytes",12406],["tx_dropped",0],["tx_errors",0],["tx_packets",173]]]
*/
if (virCommandRun(cmd, NULL) < 0 ||
STREQ_NULLABLE(output, "")) {
/* no ovs-vsctl or interface 'ifname' doesn't exists in ovs */
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Interface not found"));
return -1;
}
if (virNetDevOpenvswitchInterfaceParseStats(output, stats) < 0)
return -1;
if (stats->rx_bytes == -1 &&
stats->rx_packets == -1 &&
stats->rx_errs == -1 &&
stats->rx_drop == -1 &&
stats->tx_bytes == -1 &&
stats->tx_packets == -1 &&
stats->tx_errs == -1 &&
stats->tx_drop == -1) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Interface doesn't have any statistics"));
return -1;
}
return 0;
}
/**
* virNetDeOpenvswitchGetMaster:
* @ifname: name of interface we're interested in
* @master: used to return a string containing the name of @ifname's "master"
* (this is the bridge or bond device that this device is attached to)
*
* Returns 0 on success, -1 on failure (if @ifname has no master
* @master will be NULL, but return value will still be 0 (success)).
*
* NB: This function is needed because the IFLA_MASTER attribute of an
* interface in a netlink dump (see virNetDevGetMaster()) will always
* return "ovs-system" for any interface that is attached to an OVS
* switch. When that happens, virNetDevOpenvswitchInterfaceGetMaster()
* must be called to get the "real" master of the interface.
*/
int
virNetDevOpenvswitchInterfaceGetMaster(const char *ifname, char **master)
{
g_autoptr(virCommand) cmd = virNetDevOpenvswitchCreateCmd();
int exitstatus;
*master = NULL;
virCommandAddArgList(cmd, "iface-to-br", ifname, NULL);
virCommandSetOutputBuffer(cmd, master);
if (virCommandRun(cmd, &exitstatus) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unable to run command to get OVS master for "
"interface %s"), ifname);
return -1;
}
/* non-0 exit code just means that the interface has no master in OVS */
if (exitstatus != 0)
VIR_FREE(*master);
if (*master) {
/* truncate at the first newline */
char *nl = strchr(*master, '\n');
if (nl)
*nl = '\0';
}
VIR_DEBUG("OVS master for %s is %s", ifname, *master ? *master : "(none)");
return 0;
}
2020-12-16 16:52:14 +00:00
/**
* virNetDevOpenvswitchMaybeUnescapeReply:
* @reply: a string to unescape
*
* Depending on ovs-vsctl version a string might be escaped. For instance:
* -version 2.11.4 allows only is_alpha(), an underscore, a dash or a dot,
* -version 2.14.0 allows only is_alnum(), an underscore, a dash or a dot,
* any other character causes the string to be escaped.
*
* What this function does, is it checks whether @reply string consists solely
* from safe, not escaped characters (as defined by version 2.14.0) and if not
* an error is reported. If @reply is a string enclosed in double quotes, but
* otherwise safe those double quotes are removed.
*
* Returns: 0 on success,
* -1 otherwise (with error reported).
*/
int
virNetDevOpenvswitchMaybeUnescapeReply(char *reply)
{
g_autoptr(virJSONValue) json = NULL;
g_autofree char *jsonStr = NULL;
const char *tmp = NULL;
size_t replyLen = strlen(reply);
if (*reply != '"')
return 0;
jsonStr = g_strdup_printf("{\"name\": %s}", reply);
if (!(json = virJSONValueFromString(jsonStr)))
return -1;
if (!(tmp = virJSONValueObjectGetString(json, "name"))) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Malformed ovs-vsctl output"));
return -1;
}
return virStrcpy(reply, tmp, replyLen);
}
/**
* virNetDevOpenvswitchGetVhostuserIfname:
* @path: the path of the unix socket
* @server: true if OVS creates the @path
* @ifname: the retrieved name of the interface
*
* Retrieves the OVS ifname from vhostuser UNIX socket path.
* There are two types of vhostuser ports which differ in client/server
* role:
*
* dpdkvhostuser - OVS creates the socket and QEMU connects to it
* (@server = true)
* dpdkvhostuserclient - QEMU creates the socket and OVS connects to it
* (@server = false)
*
* Since the way of retrieving ifname is different in these two cases,
* caller must set @server according to the interface definition.
*
* Returns: 1 if interface is an openvswitch interface,
* 0 if it is not, but no other error occurred,
* -1 otherwise.
*/
int
virNetDevOpenvswitchGetVhostuserIfname(const char *path,
bool server,
char **ifname)
{
g_autoptr(virCommand) cmd = NULL;
g_autofree char *absoluteOvsVsctlPath = NULL;
int status;
if (!(absoluteOvsVsctlPath = virFindFileInPath(OVS_VSCTL))) {
/* If there is no 'ovs-vsctl' then the interface is
* probably not an OpenVSwitch interface and the @path to
* socket was created by some DPDK testing script (e.g.
* dpdk-testpmd). */
return 0;
}
cmd = virNetDevOpenvswitchCreateCmd();
if (server) {
virCommandAddArgList(cmd, "--no-headings", "--columns=name", "find",
"Interface", NULL);
virCommandAddArgPair(cmd, "options:vhost-server-path", path);
} else {
const char *tmpIfname = NULL;
/* Openvswitch vhostuser path is hardcoded to
* /<runstatedir>/openvswitch/<ifname>
* for example: /var/run/openvswitch/dpdkvhostuser0
*
* so we pick the filename and check it's an openvswitch interface
*/
if (!path ||
!(tmpIfname = strrchr(path, '/'))) {
return 0;
}
tmpIfname++;
virCommandAddArgList(cmd, "get", "Interface", tmpIfname, "name", NULL);
}
virCommandSetOutputBuffer(cmd, ifname);
if (virCommandRun(cmd, &status) < 0)
return -1;
if (status != 0) {
/* it's not a openvswitch vhostuser interface. */
return 0;
}
virStringTrimOptionalNewline(*ifname);
2020-12-16 16:52:14 +00:00
if (virNetDevOpenvswitchMaybeUnescapeReply(*ifname) < 0) {
VIR_FREE(*ifname);
return -1;
}
return 1;
}
/**
* virNetDevOpenvswitchUpdateVlan:
* @ifname: the network interface name
* @virtVlan: VLAN configuration to be applied
*
* Update VLAN configuration of an OVS port.
*
* Returns 0 in case of success or -1 in case of failure.
*/
int virNetDevOpenvswitchUpdateVlan(const char *ifname,
const virNetDevVlan *virtVlan)
{
g_autoptr(virCommand) cmd = virNetDevOpenvswitchCreateCmd();
virCommandAddArgList(cmd,
"--", "--if-exists", "clear", "Port", ifname, "tag",
"--", "--if-exists", "clear", "Port", ifname, "trunk",
"--", "--if-exists", "clear", "Port", ifname, "vlan_mode",
"--", "--if-exists", "set", "Port", ifname, NULL);
virNetDevOpenvswitchConstructVlans(cmd, virtVlan);
if (virCommandRun(cmd, NULL) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unable to set vlan configuration on port %s"), ifname);
return -1;
}
return 0;
}
/*
* Average, peak, floor and burst in virNetDevBandwidth are in kbytes.
* However other_config in ovs qos is in bit.
* ingress_policing_rate in ovs interface is in kbit.
*/
#define VIR_NETDEV_TX_TO_OVS 8192
#define VIR_NETDEV_RX_TO_OVS 8
/**
* virNetDevOpenvswitchInterfaceSetQos:
* @ifname: on which interface
* @bandwidth: rates to set (may be NULL)
* @vmuuid: the Domain UUID that has this interface
* @swapped: true if IN/OUT should be set contrariwise
*
* Update qos configuration of an OVS port.
*
* If @swapped is set, the IN part of @bandwidth is set on
* @ifname's TX, and vice versa. If it is not set, IN is set on
* RX and OUT on TX. This is because for some types of interfaces
* domain and the host live on the same side of the interface (so
* domain's RX/TX is host's RX/TX), and for some it's swapped
* (domain's RX/TX is hosts's TX/RX).
*
* Return 0 on success, -1 otherwise.
*/
int
virNetDevOpenvswitchInterfaceSetQos(const char *ifname,
const virNetDevBandwidth *bandwidth,
const unsigned char *vmuuid,
bool swapped)
{
virNetDevBandwidthRate *rx = NULL; /* From domain POV */
virNetDevBandwidthRate *tx = NULL; /* From domain POV */
if (!bandwidth) {
/* nothing to be enabled */
return 0;
}
if (geteuid() != 0) {
virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
_("Network bandwidth tuning is not available"
" in session mode"));
return -1;
}
if (!ifname) {
virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
_("Unable to set bandwidth for interface because "
"device name is unknown"));
return -1;
}
if (swapped) {
rx = bandwidth->out;
tx = bandwidth->in;
} else {
rx = bandwidth->in;
tx = bandwidth->out;
}
if (!bandwidth->out && !bandwidth->in) {
if (virNetDevOpenvswitchInterfaceClearQos(ifname, vmuuid) < 0) {
VIR_WARN("Clean qos for interface %s failed", ifname);
}
return 0;
}
if (tx && tx->average) {
char vmuuidstr[VIR_UUID_STRING_BUFLEN];
g_autoptr(virCommand) cmd = NULL;
g_autofree char *vmid_ex_id = NULL;
g_autofree char *ifname_ex_id = NULL;
g_autofree char *average = NULL;
g_autofree char *peak = NULL;
g_autofree char *burst = NULL;
g_autofree char *qos_uuid = NULL;
g_autofree char *queue_uuid = NULL;
average = g_strdup_printf("%llu", tx->average * VIR_NETDEV_TX_TO_OVS);
if (tx->burst)
burst = g_strdup_printf("%llu", tx->burst * VIR_NETDEV_TX_TO_OVS);
if (tx->peak)
peak = g_strdup_printf("%llu", tx->peak * VIR_NETDEV_TX_TO_OVS);
/* find queue */
cmd = virNetDevOpenvswitchCreateCmd();
virUUIDFormat(vmuuid, vmuuidstr);
vmid_ex_id = g_strdup_printf("external-ids:vm-id=\"%s\"", vmuuidstr);
ifname_ex_id = g_strdup_printf("external-ids:ifname=\"%s\"", ifname);
virCommandAddArgList(cmd, "--no-heading", "--columns=_uuid", "find", "queue",
vmid_ex_id, ifname_ex_id, NULL);
virCommandSetOutputBuffer(cmd, &queue_uuid);
if (virCommandRun(cmd, NULL) < 0) {
VIR_WARN("Unable to find queue on port %s", ifname);
}
/* find qos */
virCommandFree(cmd);
cmd = virNetDevOpenvswitchCreateCmd();
virCommandAddArgList(cmd, "--no-heading", "--columns=_uuid", "find", "qos",
vmid_ex_id, ifname_ex_id, NULL);
virCommandSetOutputBuffer(cmd, &qos_uuid);
if (virCommandRun(cmd, NULL) < 0) {
VIR_WARN("Unable to find qos on port %s", ifname);
}
/* create qos and set */
virCommandFree(cmd);
cmd = virNetDevOpenvswitchCreateCmd();
if (queue_uuid && *queue_uuid) {
g_auto(GStrv) lines = g_strsplit(queue_uuid, "\n", 0);
virCommandAddArgList(cmd, "set", "queue", lines[0], NULL);
} else {
virCommandAddArgList(cmd, "set", "port", ifname, "qos=@qos1",
vmid_ex_id, ifname_ex_id,
"--", "--id=@qos1", "create", "qos", "type=linux-htb", NULL);
virCommandAddArgFormat(cmd, "other_config:min-rate=%s", average);
if (burst) {
virCommandAddArgFormat(cmd, "other_config:burst=%s", burst);
}
if (peak) {
virCommandAddArgFormat(cmd, "other_config:max-rate=%s", peak);
}
virCommandAddArgList(cmd, "queues:0=@queue0", vmid_ex_id, ifname_ex_id,
"--", "--id=@queue0", "create", "queue", NULL);
}
virCommandAddArgFormat(cmd, "other_config:min-rate=%s", average);
if (burst) {
virCommandAddArgFormat(cmd, "other_config:burst=%s", burst);
}
if (peak) {
virCommandAddArgFormat(cmd, "other_config:max-rate=%s", peak);
}
virCommandAddArgList(cmd, vmid_ex_id, ifname_ex_id, NULL);
if (virCommandRun(cmd, NULL) < 0) {
if (*queue_uuid) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unable to set queue configuration on port %s"), ifname);
} else {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unable to create and set qos configuration on port %s"), ifname);
}
return -1;
}
if (qos_uuid && *qos_uuid) {
g_auto(GStrv) lines = g_strsplit(qos_uuid, "\n", 0);
virCommandFree(cmd);
cmd = virNetDevOpenvswitchCreateCmd();
virCommandAddArgList(cmd, "set", "qos", lines[0], NULL);
virCommandAddArgFormat(cmd, "other_config:min-rate=%s", average);
if (burst) {
virCommandAddArgFormat(cmd, "other_config:burst=%s", burst);
}
if (peak) {
virCommandAddArgFormat(cmd, "other_config:max-rate=%s", peak);
}
virCommandAddArgList(cmd, vmid_ex_id, ifname_ex_id, NULL);
if (virCommandRun(cmd, NULL) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unable to set qos configuration on port %s"), ifname);
return -1;
}
}
}
if (rx) {
g_autoptr(virCommand) cmd = NULL;
cmd = virNetDevOpenvswitchCreateCmd();
virCommandAddArgList(cmd, "set", "Interface", ifname, NULL);
virCommandAddArgFormat(cmd, "ingress_policing_rate=%llu",
rx->average * VIR_NETDEV_RX_TO_OVS);
if (rx->burst)
virCommandAddArgFormat(cmd, "ingress_policing_burst=%llu",
rx->burst * VIR_NETDEV_RX_TO_OVS);
if (virCommandRun(cmd, NULL) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unable to set vlan configuration on port %s"), ifname);
return -1;
}
}
return 0;
}
int
virNetDevOpenvswitchInterfaceClearQos(const char *ifname,
const unsigned char *vmuuid)
{
char vmuuidstr[VIR_UUID_STRING_BUFLEN];
g_autoptr(virCommand) cmd = NULL;
g_autofree char *vmid_ex_id = NULL;
g_autofree char *qos_uuid = NULL;
g_autofree char *queue_uuid = NULL;
g_autofree char *port_qos = NULL;
size_t i;
/* find qos */
cmd = virNetDevOpenvswitchCreateCmd();
virUUIDFormat(vmuuid, vmuuidstr);
vmid_ex_id = g_strdup_printf("external-ids:vm-id=\"%s\"", vmuuidstr);
virCommandAddArgList(cmd, "--no-heading", "--columns=_uuid", "find", "qos", vmid_ex_id, NULL);
virCommandSetOutputBuffer(cmd, &qos_uuid);
if (virCommandRun(cmd, NULL) < 0) {
VIR_WARN("Unable to find qos on port %s", ifname);
}
/* find queue */
virCommandFree(cmd);
cmd = virNetDevOpenvswitchCreateCmd();
vmid_ex_id = g_strdup_printf("external-ids:vm-id=\"%s\"", vmuuidstr);
virCommandAddArgList(cmd, "--no-heading", "--columns=_uuid", "find", "queue", vmid_ex_id, NULL);
virCommandSetOutputBuffer(cmd, &queue_uuid);
if (virCommandRun(cmd, NULL) < 0) {
VIR_WARN("Unable to find queue on port %s", ifname);
}
if (qos_uuid && *qos_uuid) {
g_auto(GStrv) lines = g_strsplit(qos_uuid, "\n", 0);
/* destroy qos */
for (i = 0; lines[i] != NULL; i++) {
const char *line = lines[i];
if (!*line) {
continue;
}
virCommandFree(cmd);
cmd = virNetDevOpenvswitchCreateCmd();
virCommandAddArgList(cmd, "--no-heading", "--columns=_uuid", "--if-exists",
"list", "port", ifname, "qos", NULL);
virCommandSetOutputBuffer(cmd, &port_qos);
if (virCommandRun(cmd, NULL) < 0) {
VIR_WARN("Unable to remove port qos on port %s", ifname);
}
if (port_qos && *port_qos) {
virCommandFree(cmd);
cmd = virNetDevOpenvswitchCreateCmd();
virCommandAddArgList(cmd, "remove", "port", ifname, "qos", line, NULL);
if (virCommandRun(cmd, NULL) < 0) {
VIR_WARN("Unable to remove port qos on port %s", ifname);
}
}
virCommandFree(cmd);
cmd = virNetDevOpenvswitchCreateCmd();
virCommandAddArgList(cmd, "destroy", "qos", line, NULL);
if (virCommandRun(cmd, NULL) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unable to destroy qos on port %s"), ifname);
return -1;
}
}
}
/* destroy queue */
if (queue_uuid && *queue_uuid) {
g_auto(GStrv) lines = g_strsplit(queue_uuid, "\n", 0);
for (i = 0; lines[i] != NULL; i++) {
const char *line = lines[i];
if (!*line) {
continue;
}
virCommandFree(cmd);
cmd = virNetDevOpenvswitchCreateCmd();
virCommandAddArgList(cmd, "destroy", "queue", line, NULL);
if (virCommandRun(cmd, NULL) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unable to destroy queue on port %s"), ifname);
return -1;
}
}
}
return 0;
}