Skip to content

Commit

Permalink
bridge: Keep bond active slave selection across OVS restart
Browse files Browse the repository at this point in the history
Whenever OVS restarts, it pseudo-randomly picks an interface
of a bond port to be the active slave. This can cause traffic
disruption in case the upstream switch does not support LACP, or
in case of multi-chassis switches that do not support mLACP.

This patch helps the situation by always record the last active
slave into ovsdb. When OVS restarts, the stored last active slave
has the highest priority to be selected again. In case this interface
is available, due to configuration changes or being offline, OVS then
consider other interfaces with the bond as it does today.

In a nutshell, this patch makes the active slave selection stickier
across OVS restart.

VMware-BZ:  1332235

Signed-off-by: Andy Zhou <azhou@nicira.com>
Acked-by: Alex Wang <alexw@nicira.com>
  • Loading branch information
azhou-nicira committed Oct 6, 2014
1 parent 705e926 commit 3e5aeeb
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 9 deletions.
1 change: 1 addition & 0 deletions NEWS
Expand Up @@ -35,6 +35,7 @@ Post-v2.3.0
(IEEE 802.1D-2004). More conformance and interoperability testing is
still needed, so this should not be enabled on production environments.
- Stats are no longer updated on fake bond interface.
- Keep active bond slave selection across OVS restart.


v2.3.0 - 14 Aug 2014
Expand Down
86 changes: 86 additions & 0 deletions ofproto/bond.c
Expand Up @@ -131,6 +131,15 @@ struct bond {
uint32_t recirc_id; /* Non zero if recirculation can be used.*/
struct hmap pr_rule_ops; /* Helps to maintain post recirculation rules.*/

/* Store active slave to OVSDB. */
bool active_slave_changed; /* Set to true whenever the bond changes
active slave. It will be reset to false
after it is stored into OVSDB */

/* Interface name may not be persistent across an OS reboot, use
* MAC address for identifing the active slave */
uint8_t active_slave_mac[ETH_ADDR_LEN];
/* The MAC address of the active interface. */
/* Legacy compatibility. */
bool lacp_fallback_ab; /* Fallback to active-backup on LACP failure. */

Expand Down Expand Up @@ -446,10 +455,47 @@ bond_reconfigure(struct bond *bond, const struct bond_settings *s)
bond_entry_reset(bond);
}

memcpy(bond->active_slave_mac, s->active_slave_mac,
sizeof s->active_slave_mac);

bond->active_slave_changed = false;

ovs_rwlock_unlock(&rwlock);
return revalidate;
}

static struct bond_slave *
bond_find_slave_by_mac(const struct bond *bond, const uint8_t mac[6])
{
struct bond_slave *slave;

/* Find the last active slave */
HMAP_FOR_EACH(slave, hmap_node, &bond->slaves) {
uint8_t slave_mac[6];

if (netdev_get_etheraddr(slave->netdev, slave_mac)) {
continue;
}

if (!memcmp(slave_mac, mac, sizeof(slave_mac))) {
return slave;
}
}

return NULL;
}

static void
bond_active_slave_changed(struct bond *bond)
{
uint8_t mac[6];

netdev_get_etheraddr(bond->active_slave->netdev, mac);
memcpy(bond->active_slave_mac, mac, sizeof bond->active_slave_mac);
bond->active_slave_changed = true;
seq_change(connectivity_seq_get());
}

static void
bond_slave_set_netdev__(struct bond_slave *slave, struct netdev *netdev)
OVS_REQ_WRLOCK(rwlock)
Expand Down Expand Up @@ -1270,6 +1316,11 @@ bond_print_details(struct ds *ds, const struct bond *bond)
break;
}

ds_put_cstr(ds, "active slave mac: ");
ds_put_format(ds, ETH_ADDR_FMT, ETH_ADDR_ARGS(bond->active_slave_mac));
slave = bond_find_slave_by_mac(bond, bond->active_slave_mac);
ds_put_format(ds,"(%s)\n", slave ? slave->name : "none");

HMAP_FOR_EACH (slave, hmap_node, &bond->slaves) {
shash_add(&slave_shash, slave->name, slave);
}
Expand Down Expand Up @@ -1440,6 +1491,7 @@ bond_unixctl_set_active_slave(struct unixctl_conn *conn,
bond->name, slave->name);
bond->send_learning_packets = true;
unixctl_command_reply(conn, "done");
bond_active_slave_changed(bond);
} else {
unixctl_command_reply(conn, "no change");
}
Expand Down Expand Up @@ -1747,6 +1799,12 @@ bond_choose_slave(const struct bond *bond)
{
struct bond_slave *slave, *best;

/* Find the last active slave. */
slave = bond_find_slave_by_mac(bond, bond->active_slave_mac);
if (slave && slave->enabled) {
return slave;
}

/* Find an enabled slave. */
HMAP_FOR_EACH (slave, hmap_node, &bond->slaves) {
if (slave->enabled) {
Expand Down Expand Up @@ -1787,7 +1845,35 @@ bond_choose_active_slave(struct bond *bond)
}

bond->send_learning_packets = true;

if (bond->active_slave != old_active_slave) {
bond_active_slave_changed(bond);
}
} else if (old_active_slave) {
VLOG_INFO_RL(&rl, "bond %s: all interfaces disabled", bond->name);
}
}

/*
* Return true if bond has unstored active slave change.
* If return true, 'mac' will store the bond's current active slave's
* MAC address. */
bool
bond_get_changed_active_slave(const char *name, uint8_t* mac, bool force)
{
struct bond *bond;

ovs_rwlock_wrlock(&rwlock);
bond = bond_find(name);
if (bond) {
if (bond->active_slave_changed || force) {
memcpy(mac, bond->active_slave_mac, ETH_ADDR_LEN);
bond->active_slave_changed = false;
ovs_rwlock_unlock(&rwlock);
return true;
}
}
ovs_rwlock_unlock(&rwlock);

return false;
}
6 changes: 6 additions & 0 deletions ofproto/bond.h
Expand Up @@ -53,6 +53,10 @@ struct bond_settings {
int down_delay; /* ms before disabling a down slave. */

bool lacp_fallback_ab_cfg; /* Fallback to active-backup on LACP failure. */

uint8_t active_slave_mac[6];/* The MAC address of the interface
that was active during the last
ovs run. */
};

/* Program startup. */
Expand All @@ -79,6 +83,8 @@ bool bond_should_send_learning_packets(struct bond *);
struct ofpbuf *bond_compose_learning_packet(struct bond *,
const uint8_t eth_src[ETH_ADDR_LEN],
uint16_t vlan, void **port_aux);
bool bond_get_changed_active_slave(const char *name, uint8_t mac[6],
bool force);

/* Packet processing. */
enum bond_verdict {
Expand Down
24 changes: 17 additions & 7 deletions tests/lacp.at
Expand Up @@ -5,6 +5,11 @@ m4_define([STRIP_RECIRC_ID], [[sed '
s/Recirc-ID.*$/<del>/
' ]])

# Strips out active slave mac address since it may change over time.
m4_define([STRIP_ACTIVE_SLAVE_MAC], [[sed '
s/active slave mac.*$/<active slave mac del>/
' ]])

AT_SETUP([lacp - config])
OVS_VSWITCHD_START([\
add-port br0 p1 --\
Expand Down Expand Up @@ -119,6 +124,7 @@ bond-hash-basis: 0
updelay: 0 ms
downdelay: 0 ms
lacp_status: negotiated
active slave mac: 00:00:00:00:00:00(none)

slave p1: disabled
may_enable: false
Expand Down Expand Up @@ -184,8 +190,8 @@ done
AT_CHECK(
[ovs-appctl lacp/show bond0
ovs-appctl lacp/show bond1
ovs-appctl bond/show bond0 | STRIP_RECIRC_ID
ovs-appctl bond/show bond1 | STRIP_RECIRC_ID ], [0], [stdout])
ovs-appctl bond/show bond0 | STRIP_RECIRC_ID | STRIP_ACTIVE_SLAVE_MAC
ovs-appctl bond/show bond1 | STRIP_RECIRC_ID | STRIP_ACTIVE_SLAVE_MAC ], [0], [stdout])
AT_CHECK([sed '/active slave/d' stdout], [0], [dnl
---- bond0 ----
status: active negotiated
Expand Down Expand Up @@ -304,7 +310,7 @@ slave p3: enabled
may_enable: true

])
AT_CHECK([grep 'active slave' stdout], [0], [dnl
AT_CHECK([grep 'active slave$' stdout], [0], [dnl
active slave
active slave
])
Expand All @@ -320,8 +326,8 @@ ovs-appctl time/warp 4100 100
AT_CHECK(
[ovs-appctl lacp/show bond0
ovs-appctl lacp/show bond1
ovs-appctl bond/show bond0 | STRIP_RECIRC_ID
ovs-appctl bond/show bond1 | STRIP_RECIRC_ID ], [0], [dnl
ovs-appctl bond/show bond0 | STRIP_RECIRC_ID | STRIP_ACTIVE_SLAVE_MAC
ovs-appctl bond/show bond1 | STRIP_RECIRC_ID | STRIP_ACTIVE_SLAVE_MAC ], [0], [dnl
---- bond0 ----
status: active negotiated
sys_id: aa:55:aa:55:00:00
Expand Down Expand Up @@ -417,6 +423,7 @@ bond-hash-basis: 0
updelay: 0 ms
downdelay: 0 ms
lacp_status: negotiated
<active slave mac del>

slave p0: disabled
may_enable: false
Expand All @@ -432,6 +439,7 @@ bond-hash-basis: 0
updelay: 0 ms
downdelay: 0 ms
lacp_status: negotiated
<active slave mac del>

slave p2: disabled
may_enable: false
Expand All @@ -448,8 +456,8 @@ ovs-appctl time/warp 4100 100
AT_CHECK(
[ovs-appctl lacp/show bond0
ovs-appctl lacp/show bond1
ovs-appctl bond/show bond0 | STRIP_RECIRC_ID
ovs-appctl bond/show bond1 | STRIP_RECIRC_ID ], [0], [dnl
ovs-appctl bond/show bond0 | STRIP_RECIRC_ID | STRIP_ACTIVE_SLAVE_MAC
ovs-appctl bond/show bond1 | STRIP_RECIRC_ID | STRIP_ACTIVE_SLAVE_MAC ], [0], [dnl
---- bond0 ----
status: active negotiated
sys_id: aa:55:aa:55:00:00
Expand Down Expand Up @@ -545,6 +553,7 @@ bond-hash-basis: 0
updelay: 0 ms
downdelay: 0 ms
lacp_status: negotiated
<active slave mac del>

slave p0: disabled
may_enable: false
Expand All @@ -560,6 +569,7 @@ bond-hash-basis: 0
updelay: 0 ms
downdelay: 0 ms
lacp_status: negotiated
<active slave mac del>

slave p2: disabled
may_enable: false
Expand Down
31 changes: 31 additions & 0 deletions vswitchd/bridge.c
Expand Up @@ -62,6 +62,7 @@
#include "vlog.h"
#include "sflow_api.h"
#include "vlan-bitmap.h"
#include "packets.h"

VLOG_DEFINE_THIS_MODULE(bridge);

Expand Down Expand Up @@ -389,6 +390,7 @@ bridge_init(const char *remote)
ovsdb_idl_omit_alert(idl, &ovsrec_port_col_rstp_status);
ovsdb_idl_omit_alert(idl, &ovsrec_port_col_rstp_statistics);
ovsdb_idl_omit_alert(idl, &ovsrec_port_col_statistics);
ovsdb_idl_omit_alert(idl, &ovsrec_port_col_bond_active_slave);
ovsdb_idl_omit(idl, &ovsrec_port_col_external_ids);

ovsdb_idl_omit_alert(idl, &ovsrec_interface_col_admin_state);
Expand Down Expand Up @@ -2504,6 +2506,26 @@ port_refresh_rstp_status(struct port *port)
ARRAY_SIZE(int_values));
}

static void
port_refresh_bond_status(struct port *port, bool force_update)
{
uint8_t mac[6];

/* Return if port is not a bond */
if (list_is_singleton(&port->ifaces)) {
return;
}

if (bond_get_changed_active_slave(port->name, mac, force_update)) {
struct ds mac_s;

ds_init(&mac_s);
ds_put_format(&mac_s, ETH_ADDR_FMT, ETH_ADDR_ARGS(mac));
ovsrec_port_set_bond_active_slave(port->cfg, ds_cstr(&mac_s));
ds_destroy(&mac_s);
}
}

static bool
enable_system_stats(const struct ovsrec_open_vswitch *cfg)
{
Expand Down Expand Up @@ -2677,6 +2699,7 @@ run_status_update(void)

port_refresh_stp_status(port);
port_refresh_rstp_status(port);
port_refresh_bond_status(port, status_txn_try_again);
LIST_FOR_EACH (iface, port_elem, &port->ifaces) {
iface_refresh_netdev_status(iface);
iface_refresh_ofproto_status(iface);
Expand Down Expand Up @@ -3766,6 +3789,7 @@ port_configure_bond(struct port *port, struct bond_settings *s)
{
const char *detect_s;
struct iface *iface;
const char *mac_s;
int miimon_interval;

s->name = port->name;
Expand Down Expand Up @@ -3822,6 +3846,13 @@ port_configure_bond(struct port *port, struct bond_settings *s)
LIST_FOR_EACH (iface, port_elem, &port->ifaces) {
netdev_set_miimon_interval(iface->netdev, miimon_interval);
}

mac_s = port->cfg->bond_active_slave;
if (!mac_s || !ovs_scan(mac_s, ETH_ADDR_SCAN_FMT,
ETH_ADDR_SCAN_ARGS(s->active_slave_mac))) {
/* OVSDB did not store the last active interface */
memset(s->active_slave_mac, 0, sizeof(s->active_slave_mac));
}
}

/* Returns true if 'port' is synthetic, that is, if we constructed it locally
Expand Down
8 changes: 6 additions & 2 deletions vswitchd/vswitch.ovsschema
@@ -1,6 +1,6 @@
{"name": "Open_vSwitch",
"version": "7.9.0",
"cksum": "2301439325 21345",
"version": "8.0.0",
"cksum": "867114045 21497",
"tables": {
"Open_vSwitch": {
"columns": {
Expand Down Expand Up @@ -160,6 +160,10 @@
"type": "integer"},
"bond_downdelay": {
"type": "integer"},
"bond_active_slave": {
"type": {"key": {"type": "string"},
"min": 0, "max": 1},
"ephemeral": true},
"bond_fake_iface": {
"type": "boolean"},
"fake_bridge": {
Expand Down
7 changes: 7 additions & 0 deletions vswitchd/vswitch.xml
Expand Up @@ -1438,6 +1438,13 @@
STP role of the port.
</p>
</column>

<column name="status" key="bond_active_slave">
<p>
For a bonded port, record the mac address of the current active slave.
</p>
</column>

</group>

<group title="Port Statistics">
Expand Down

0 comments on commit 3e5aeeb

Please sign in to comment.