virnetdevopenvswitch.c 14.7 KB
Newer Older
A
Ansis Atteka 已提交
1
/*
2
 * Copyright (C) 2013 Red Hat, Inc.
A
Ansis Atteka 已提交
3
 * Copyright (C) 2012 Nicira, Inc.
4
 * Copyright (C) 2017 IBM Corporation
A
Ansis Atteka 已提交
5 6 7 8 9 10 11 12 13 14 15 16
 *
 * 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
17
 * License along with this library.  If not, see
O
Osier Yang 已提交
18
 * <http://www.gnu.org/licenses/>.
A
Ansis Atteka 已提交
19 20 21 22 23
 *
 * Authors:
 *     Dan Wendlandt <dan@nicira.com>
 *     Kyle Mestery <kmestery@cisco.com>
 *     Ansis Atteka <aatteka@nicira.com>
24
 *     Boris Fiuczynski <fiuczy@linux.vnet.ibm.com>
A
Ansis Atteka 已提交
25 26 27 28
 */

#include <config.h>

29 30
#include <stdio.h>

A
Ansis Atteka 已提交
31
#include "virnetdevopenvswitch.h"
32
#include "vircommand.h"
33
#include "viralloc.h"
34
#include "virerror.h"
A
Ansis Atteka 已提交
35
#include "virmacaddr.h"
36
#include "virstring.h"
37
#include "virlog.h"
A
Ansis Atteka 已提交
38 39 40

#define VIR_FROM_THIS VIR_FROM_NONE

41 42
VIR_LOG_INIT("util.netdevopenvswitch");

43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
/*
 * Set openvswitch default timout
 */
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 void
virNetDevOpenvswitchAddTimeout(virCommandPtr cmd)
{
    virCommandAddArgFormat(cmd, "--timeout=%u", virNetDevOpenvswitchTimeout);
}

66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
/**
 * virNetDevOpenvswitchConstructVlans:
 * @cmd: command to construct
 * @virtVlan: VLAN configuration to be applied
 *
 * Construct the VLAN configuration parameters to be passed to
 * ovs-vsctl command.
 *
 * Returns 0 in case of success or -1 in case of failure.
 */
static int
virNetDevOpenvswitchConstructVlans(virCommandPtr cmd, virNetDevVlanPtr virtVlan)
{
    int ret = -1;
    size_t i = 0;
    virBuffer buf = VIR_BUFFER_INITIALIZER;

    if (!virtVlan || !virtVlan->nTags)
        return 0;

    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) {
        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[i]);

        for (i = 1; i < virtVlan->nTags; i++) {
            virBufferAddLit(&buf, ",");
            virBufferAsprintf(&buf, "%d", virtVlan->tag[i]);
        }

        if (virBufferCheckError(&buf) < 0)
            goto cleanup;
        virCommandAddArg(cmd, virBufferCurrentContent(&buf));
    } else if (virtVlan->nTags) {
        virCommandAddArgFormat(cmd, "tag=%d", virtVlan->tag[0]);
    }

    ret = 0;
 cleanup:
    virBufferFreeAndReset(&buf);
    return ret;
}

A
Ansis Atteka 已提交
129 130 131 132 133
/**
 * virNetDevOpenvswitchAddPort:
 * @brname: the bridge name
 * @ifname: the network interface name
 * @macaddr: the mac address of the virtual interface
134
 * @vmuuid: the Domain UUID that has this interface
A
Ansis Atteka 已提交
135 136 137 138 139 140 141
 * @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,
142
                                   const virMacAddr *macaddr,
143
                                   const unsigned char *vmuuid,
144 145
                                   virNetDevVPortProfilePtr ovsport,
                                   virNetDevVlanPtr virtVlan)
A
Ansis Atteka 已提交
146 147 148 149
{
    int ret = -1;
    virCommandPtr cmd = NULL;
    char macaddrstr[VIR_MAC_STRING_BUFLEN];
150 151
    char ifuuidstr[VIR_UUID_STRING_BUFLEN];
    char vmuuidstr[VIR_UUID_STRING_BUFLEN];
A
Ansis Atteka 已提交
152 153 154
    char *attachedmac_ex_id = NULL;
    char *ifaceid_ex_id = NULL;
    char *profile_ex_id = NULL;
155
    char *vmid_ex_id = NULL;
A
Ansis Atteka 已提交
156 157

    virMacAddrFormat(macaddr, macaddrstr);
158
    virUUIDFormat(ovsport->interfaceID, ifuuidstr);
159
    virUUIDFormat(vmuuid, vmuuidstr);
A
Ansis Atteka 已提交
160 161 162

    if (virAsprintf(&attachedmac_ex_id, "external-ids:attached-mac=\"%s\"",
                    macaddrstr) < 0)
163
        goto cleanup;
A
Ansis Atteka 已提交
164
    if (virAsprintf(&ifaceid_ex_id, "external-ids:iface-id=\"%s\"",
165
                    ifuuidstr) < 0)
166
        goto cleanup;
167 168
    if (virAsprintf(&vmid_ex_id, "external-ids:vm-id=\"%s\"",
                    vmuuidstr) < 0)
169
        goto cleanup;
170
    if (ovsport->profileID[0] != '\0') {
A
Ansis Atteka 已提交
171
        if (virAsprintf(&profile_ex_id, "external-ids:port-profile=\"%s\"",
172
                        ovsport->profileID) < 0)
173
            goto cleanup;
A
Ansis Atteka 已提交
174
    }
175

L
Laine Stump 已提交
176
    cmd = virCommandNew(OVSVSCTL);
177 178
    virNetDevOpenvswitchAddTimeout(cmd);
    virCommandAddArgList(cmd, "--", "--if-exists", "del-port",
179
                         ifname, "--", "add-port", brname, ifname, NULL);
L
Laine Stump 已提交
180

181 182
    if (virNetDevOpenvswitchConstructVlans(cmd, virtVlan) < 0)
        goto cleanup;
A
Ansis Atteka 已提交
183

184
    if (ovsport->profileID[0] == '\0') {
185
        virCommandAddArgList(cmd,
A
Ansis Atteka 已提交
186 187
                        "--", "set", "Interface", ifname, attachedmac_ex_id,
                        "--", "set", "Interface", ifname, ifaceid_ex_id,
188
                        "--", "set", "Interface", ifname, vmid_ex_id,
A
Ansis Atteka 已提交
189 190 191 192
                        "--", "set", "Interface", ifname,
                        "external-ids:iface-status=active",
                        NULL);
    } else {
193
        virCommandAddArgList(cmd,
A
Ansis Atteka 已提交
194 195
                        "--", "set", "Interface", ifname, attachedmac_ex_id,
                        "--", "set", "Interface", ifname, ifaceid_ex_id,
196
                        "--", "set", "Interface", ifname, vmid_ex_id,
A
Ansis Atteka 已提交
197 198 199 200 201 202 203
                        "--", "set", "Interface", ifname, profile_ex_id,
                        "--", "set", "Interface", ifname,
                        "external-ids:iface-status=active",
                        NULL);
    }

    if (virCommandRun(cmd, NULL) < 0) {
J
Jiri Denemark 已提交
204 205 206
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Unable to add port %s to OVS bridge %s"),
                       ifname, brname);
A
Ansis Atteka 已提交
207 208 209
        goto cleanup;
    }

210
    ret = 0;
211
 cleanup:
212 213
    VIR_FREE(attachedmac_ex_id);
    VIR_FREE(ifaceid_ex_id);
214
    VIR_FREE(vmid_ex_id);
215 216 217
    VIR_FREE(profile_ex_id);
    virCommandFree(cmd);
    return ret;
A
Ansis Atteka 已提交
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
}

/**
 * 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 ATTRIBUTE_UNUSED, const char *ifname)
{
    int ret = -1;
    virCommandPtr cmd = NULL;

    cmd = virCommandNew(OVSVSCTL);
234 235
    virNetDevOpenvswitchAddTimeout(cmd);
    virCommandAddArgList(cmd, "--", "--if-exists", "del-port", ifname, NULL);
A
Ansis Atteka 已提交
236 237

    if (virCommandRun(cmd, NULL) < 0) {
J
Jiri Denemark 已提交
238 239
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Unable to delete port %s from OVS"), ifname);
A
Ansis Atteka 已提交
240 241
        goto cleanup;
    }
242 243

    ret = 0;
244
 cleanup:
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
    virCommandFree(cmd);
    return ret;
}

/**
 * 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)
{
    virCommandPtr cmd = NULL;
261
    size_t len;
262 263
    int ret = -1;

264 265 266 267
    cmd = virCommandNew(OVSVSCTL);
    virNetDevOpenvswitchAddTimeout(cmd);
    virCommandAddArgList(cmd, "--if-exists", "get", "Interface",
                         ifname, "external_ids:PortData", NULL);
268 269 270 271 272

    virCommandSetOutputBuffer(cmd, migrate);

    /* Run the command */
    if (virCommandRun(cmd, NULL) < 0) {
J
Jiri Denemark 已提交
273 274 275
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Unable to run command to get OVS port data for "
                         "interface %s"), ifname);
276 277 278
        goto cleanup;
    }

279
    /* Wipeout the newline, if it exists */
280 281 282
    len = strlen(*migrate);
    if (len > 0)
        (*migrate)[len - 1] = '\0';
283

A
Ansis Atteka 已提交
284
    ret = 0;
285
 cleanup:
J
John Ferlan 已提交
286
    virCommandFree(cmd);
287 288
    return ret;
}
A
Ansis Atteka 已提交
289

290 291 292 293 294 295 296 297 298 299 300 301 302 303
/**
 * 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)
{
    virCommandPtr cmd = NULL;
    int ret = -1;

304 305 306 307 308
    if (!migrate) {
        VIR_DEBUG("No OVS port data for interface %s", ifname);
        return 0;
    }

309 310 311
    cmd = virCommandNew(OVSVSCTL);
    virNetDevOpenvswitchAddTimeout(cmd);
    virCommandAddArgList(cmd, "set", "Interface", ifname, NULL);
312 313 314 315
    virCommandAddArgFormat(cmd, "external_ids:PortData=%s", migrate);

    /* Run the command */
    if (virCommandRun(cmd, NULL) < 0) {
J
Jiri Denemark 已提交
316 317 318
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Unable to run command to set OVS port data for "
                         "interface %s"), ifname);
319 320 321 322
        goto cleanup;
    }

    ret = 0;
323
 cleanup:
J
John Ferlan 已提交
324
    virCommandFree(cmd);
325
    return ret;
A
Ansis Atteka 已提交
326
}
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342

/**
 * virNetDevOpenvswitchInterfaceStats:
 * @ifname: the name of the interface
 * @stats: the retreived 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)
{
    virCommandPtr cmd = NULL;
    char *output;
343 344
    char *tmp;
    bool gotStats = false;
345 346 347
    int ret = -1;

    /* Just ensure the interface exists in ovs */
348 349 350
    cmd = virCommandNew(OVSVSCTL);
    virNetDevOpenvswitchAddTimeout(cmd);
    virCommandAddArgList(cmd, "get", "Interface", ifname, "name", NULL);
351 352 353 354 355 356 357 358 359
    virCommandSetOutputBuffer(cmd, &output);

    if (virCommandRun(cmd, NULL) < 0) {
        /* no ovs-vsctl or interface 'ifname' doesn't exists in ovs */
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Interface not found"));
        goto cleanup;
    }

360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379
#define GET_STAT(name, member) \
    do { \
        VIR_FREE(output); \
        virCommandFree(cmd); \
        cmd = virCommandNew(OVSVSCTL); \
        virNetDevOpenvswitchAddTimeout(cmd); \
        virCommandAddArgList(cmd, "get", "Interface", ifname, \
                             "statistics:" name, NULL); \
        virCommandSetOutputBuffer(cmd, &output); \
        if (virCommandRun(cmd, NULL) < 0) { \
            stats->member = -1; \
        } else { \
            if (virStrToLong_ll(output, &tmp, 10, &stats->member) < 0 || \
                *tmp != '\n') { \
                virReportError(VIR_ERR_INTERNAL_ERROR, "%s", \
                               _("Fail to parse ovs-vsctl output")); \
                goto cleanup; \
            } \
            gotStats = true; \
        } \
380
    } while (0)
381 382 383

    /* The TX/RX fields appear to be swapped here
     * because this is the host view. */
384 385 386 387 388 389 390 391 392 393
    GET_STAT("rx_bytes", tx_bytes);
    GET_STAT("rx_packets", tx_packets);
    GET_STAT("rx_errors", tx_errs);
    GET_STAT("rx_dropped", tx_drop);
    GET_STAT("tx_bytes", rx_bytes);
    GET_STAT("tx_packets", rx_packets);
    GET_STAT("tx_errors", rx_errs);
    GET_STAT("tx_dropped", rx_drop);

    if (!gotStats) {
394
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
395
                       _("Interface doesn't have any statistics"));
396 397 398 399 400 401 402 403 404 405
        goto cleanup;
    }

    ret = 0;

 cleanup:
    VIR_FREE(output);
    virCommandFree(cmd);
    return ret;
}
406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427

/**
 * virNetDevOpenvswitchVhostuserGetIfname:
 * @path: the path of the unix socket
 * @ifname: the retrieved name of the interface
 *
 * Retreives the ovs ifname from vhostuser unix socket path.
 *
 * 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,
                                       char **ifname)
{
    virCommandPtr cmd = NULL;
    char *tmpIfname = NULL;
    char **tokens = NULL;
    size_t ntokens = 0;
    int status;
    int ret = -1;
428
    char *ovs_timeout = NULL;
429 430 431 432 433 434 435 436 437 438 439 440 441

    /* Openvswitch vhostuser path are hardcoded to
     * /<runstatedir>/openvswitch/<ifname>
     * for example: /var/run/openvswitch/dpdkvhostuser0
     *
     * so we pick the filename and check it's a openvswitch interface
     */
    if (!path ||
        !(tmpIfname = strrchr(path, '/'))) {
        ret = 0;
        goto cleanup;
    }

442
    tmpIfname++;
443 444 445
    cmd = virCommandNew(OVSVSCTL);
    virNetDevOpenvswitchAddTimeout(cmd);
    virCommandAddArgList(cmd, "get", "Interface", tmpIfname, "name", NULL);
446 447 448 449 450 451 452 453 454 455 456 457 458 459
    if (virCommandRun(cmd, &status) < 0 ||
        status) {
        /* it's not a openvswitch vhostuser interface. */
        ret = 0;
        goto cleanup;
    }

    if (VIR_STRDUP(*ifname, tmpIfname) < 0)
        goto cleanup;
    ret = 1;

 cleanup:
    virStringListFreeCount(tokens, ntokens);
    virCommandFree(cmd);
460
    VIR_FREE(ovs_timeout);
461 462
    return ret;
}
463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500

/**
 * 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,
                                   virNetDevVlanPtr virtVlan)
{
    int ret = -1;
    virCommandPtr cmd = NULL;

    cmd = virCommandNew(OVSVSCTL);
    virNetDevOpenvswitchAddTimeout(cmd);
    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);

    if (virNetDevOpenvswitchConstructVlans(cmd, virtVlan) < 0)
        goto cleanup;

    if (virCommandRun(cmd, NULL) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Unable to set vlan configuration on port %s"), ifname);
        goto cleanup;
    }

    ret = 0;
 cleanup:
    virCommandFree(cmd);
    return ret;
}