Skip to content

Commit 9e9ec34

Browse files
authored
Merge pull request #3474 from mab68/lacpstate
Modify LACP state machine
2 parents a096fb6 + 625d2fa commit 9e9ec34

File tree

8 files changed

+239
-71
lines changed

8 files changed

+239
-71
lines changed

faucet/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class InvalidConfigError(Exception):
2828

2929

3030
def test_config_condition(cond, msg):
31-
"""Evaluate condition and raise InvalidConfigError if condition not True."""
31+
"""Evaluate condition and raise InvalidConfigError if condition True."""
3232
if cond:
3333
raise InvalidConfigError(msg)
3434

faucet/port.py

Lines changed: 98 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -28,29 +28,33 @@
2828
STACK_STATE_NONE = -1
2929

3030
# LACP not configured
31-
LACP_STATE_NONE = -1
32-
33-
# Initial state, no packets received yet
31+
LACP_ACTOR_NOTCONFIGURED = -1
32+
# Not receiving packets from the actor & port is down
33+
LACP_ACTOR_NONE = 0
34+
# Not receiving packets from the actor & port is up
3435
LACP_ACTOR_INIT = 1
35-
# LACP connection is up and receiving packets
36+
# Receiving LACP packets with sync bit set
3637
LACP_ACTOR_UP = 3
37-
# LACP is down
38-
LACP_ACTOR_NOACT = 5
38+
# LACP actor is not sending LACP with sync bit set
39+
LACP_ACTOR_NOSYNC = 5
3940
LACP_ACTOR_DISPLAY_DICT = {
40-
LACP_STATE_NONE: 'NONE',
41+
LACP_ACTOR_NOTCONFIGURED: 'NOT_CONFIGURED',
42+
LACP_ACTOR_NONE: 'NONE',
4143
LACP_ACTOR_INIT: 'INITIALIZING',
4244
LACP_ACTOR_UP: 'UP',
43-
LACP_ACTOR_NOACT: 'NO_ACTOR'
45+
LACP_ACTOR_NOSYNC: 'NO_SYNC'
4446
}
4547

48+
# LACP is not configured
49+
LACP_PORT_NOTCONFIGURED = -1
4650
# Port is not a LACP port on the nominated DP
4751
LACP_PORT_UNSELECTED = 1
4852
# Port is a LACP port on the nominated DP, will send/receive
4953
LACP_PORT_SELECTED = 2
50-
# Other cases: receive-only, etc.
54+
# Port is a LACP port that is in standby
5155
LACP_PORT_STANDBY = 3
5256
LACP_PORT_DISPLAY_DICT = {
53-
LACP_STATE_NONE: 'NONE',
57+
LACP_PORT_NOTCONFIGURED: 'NOT_CONFIGURED',
5458
LACP_PORT_UNSELECTED: 'UNSELECTED',
5559
LACP_PORT_SELECTED: 'SELECTED',
5660
LACP_PORT_STANDBY: 'STANDBY'
@@ -91,6 +95,12 @@ class Port(Conf):
9195
# experimental active LACP
9296
'lacp_collect_and_distribute': False,
9397
# if true, forces LACP port to collect and distribute when syncing with the peer.
98+
'lacp_unselected': False,
99+
# if true, forces LACP port to be in unselected state
100+
'lacp_selected': False,
101+
# if true, forces LACP port to be in the selected state
102+
'lacp_standby': False,
103+
# if true, forces LACP port to be in the standby state
94104
'lacp_passthrough': None,
95105
# If set, fail the lacp on this port if any of the peer ports are down.
96106
'lacp_resp_interval': 1,
@@ -146,6 +156,9 @@ class Port(Conf):
146156
'lacp': int,
147157
'lacp_active': bool,
148158
'lacp_collect_and_distribute': bool,
159+
'lacp_unselected': bool,
160+
'lacp_selected': bool,
161+
'lacp_standby': bool,
149162
'lacp_passthrough': list,
150163
'lacp_resp_interval': int,
151164
'loop_protect': bool,
@@ -203,6 +216,9 @@ def __init__(self, _id, dp_id, conf=None):
203216
self.lacp = None
204217
self.lacp_active = None
205218
self.lacp_collect_and_distribute = None
219+
self.lacp_unselected = None
220+
self.lacp_selected = None
221+
self.lacp_standby = None
206222
self.lacp_passthrough = None
207223
self.lacp_resp_interval = None
208224
self.loop_protect = None
@@ -237,8 +253,8 @@ def __init__(self, _id, dp_id, conf=None):
237253
self.dyn_phys_up = False
238254
self.dyn_update_time = None
239255
self.dyn_stack_current_state = STACK_STATE_NONE
240-
self.dyn_lacp_port_selected = LACP_STATE_NONE
241-
self.dyn_lacp_actor_state = LACP_STATE_NONE
256+
self.dyn_lacp_port_selected = LACP_PORT_NOTCONFIGURED
257+
self.dyn_lacp_actor_state = LACP_ACTOR_NOTCONFIGURED
242258
self.dyn_stack_probe_info = {}
243259

244260
self.tagged_vlans = []
@@ -299,9 +315,9 @@ def check_config(self):
299315
if key.startswith('acl') and self.stack:
300316
continue
301317
val = getattr(self, key)
302-
if val != default_val and val:
303-
raise InvalidConfigError(
304-
'Cannot have VLAN option %s: %s on non-VLAN port %s' % (key, val, self))
318+
test_config_condition(
319+
val != default_val and val,
320+
'Cannot have VLAN option %s: %s on non-VLAN port %s' % (key, val, self))
305321
test_config_condition(
306322
self.hairpin and self.hairpin_unicast,
307323
'Cannot have both hairpin and hairpin_unicast enabled')
@@ -369,15 +385,20 @@ def check_config(self):
369385
'%6.6x' % org_tlv['oui']) # pytype: disable=missing-parameter
370386
org_tlvs.append(org_tlv)
371387
self.lldp_beacon['org_tlvs'] = org_tlvs
372-
if self.acl_in and self.acls_in:
373-
raise InvalidConfigError('found both acl_in and acls_in, use only acls_in')
388+
test_config_condition(
389+
self.acl_in and self.acls_in,
390+
'Found both acl_in and acls_in, use only acls_in')
374391
if self.acl_in and not isinstance(self.acl_in, list):
375392
self.acls_in = [self.acl_in,]
376393
self.acl_in = None
377394
if self.acls_in:
378395
for acl in self.acls_in:
379396
test_config_condition(not isinstance(acl, (int, str)),
380397
'ACL names must be int or str')
398+
lacp_options = [self.lacp_selected, self.lacp_unselected, self.lacp_standby]
399+
test_config_condition(
400+
lacp_options.count(True) > 1,
401+
'Cannot force multiple LACP port selection states')
381402

382403
def finalize(self):
383404
if self.native_vlan:
@@ -439,20 +460,24 @@ def non_stack_forwarding(self):
439460
return True
440461

441462
# LACP functions
442-
def lacp_update(self, lacp_up, now=None, lacp_pkt=None):
463+
def lacp_actor_update(self, lacp_up, now=None, lacp_pkt=None, cold_start=False):
443464
"""
444-
Update the LACP state
465+
Update the LACP actor state
445466
Args:
446467
lacp_up (bool): The intended LACP/port state
447-
now: Current time
448-
lacp_pkt: Received LACP packet
468+
now (float): Current time
469+
lacp_pkt (PacketMeta): Received LACP packet
470+
cold_start (bool): Whether the port is being cold started
449471
Returns:
450-
dyn_lacp_actor_state, dyn_lacp_current_state
472+
current LACP actor state
451473
"""
452474
self.dyn_lacp_up = 1 if lacp_up else 0
453475
self.dyn_lacp_updated_time = now
454476
self.dyn_last_lacp_pkt = lacp_pkt
455-
if not self.dyn_phys_up:
477+
if cold_start:
478+
# Cold starting, so revert to unconfigured LACP state
479+
self.actor_notconfigured()
480+
elif not self.running():
456481
# Phys not up so we do not initialize actor states
457482
self.actor_none()
458483
elif not lacp_pkt:
@@ -464,10 +489,40 @@ def lacp_update(self, lacp_up, now=None, lacp_pkt=None):
464489
# Receiving packets & LACP is UP
465490
self.actor_up()
466491
else:
467-
# Receiving packets but LACP is DOWN
468-
self.actor_noact()
492+
# Receiving packets but LACP sync bit is not set
493+
self.actor_nosync()
469494
return self.actor_state()
470495

496+
def lacp_port_update(self, selected, cold_start=False):
497+
"""
498+
Updates the LACP port selection state
499+
Args:
500+
selected (bool): Whether the port's DPID is the selected one
501+
cold_start (bool): Whether the port is being cold started
502+
Returns
503+
current lacp port state
504+
"""
505+
if cold_start:
506+
# Cold starting, so revert to unconfigured state
507+
self.deconfigure_port()
508+
elif self.lacp_selected:
509+
# Can configure LACP port to be forced SELECTED
510+
self.select_port()
511+
elif self.lacp_standby:
512+
# Can configure LACP port to be forced STANDBY
513+
self.standby_port()
514+
elif self.lacp_unselected:
515+
# Can configure LACP port to be force UNSELECTED
516+
self.deselect_port()
517+
else:
518+
if selected:
519+
# Belongs on chosen DP for LAG, so SELECT port
520+
self.select_port()
521+
else:
522+
# Doesn't belong on chosen DP for LAG, DESELECT port
523+
self.deselect_port()
524+
return self.lacp_port_state()
525+
471526
def get_lacp_flags(self):
472527
"""
473528
Get the LACP flags for the state the port is in
@@ -486,25 +541,29 @@ def is_actor_up(self):
486541
"""Return true if the LACP actor state is UP"""
487542
return self.dyn_lacp_actor_state == LACP_ACTOR_UP
488543

489-
def is_actor_noact(self):
490-
"""Return true if the LACP actor state is NOACT"""
491-
return self.dyn_lacp_actor_state == LACP_ACTOR_NOACT
544+
def is_actor_nosync(self):
545+
"""Return true if the LACP actor state is NOSYNC"""
546+
return self.dyn_lacp_actor_state == LACP_ACTOR_NOSYNC
492547

493548
def is_actor_init(self):
494549
"""Return true if the LACP actor state is INIT"""
495550
return self.dyn_lacp_actor_state == LACP_ACTOR_INIT
496551

497552
def is_actor_none(self):
498553
"""Return true if the LACP actor state is NONE"""
499-
return self.dyn_lacp_actor_state == LACP_STATE_NONE
554+
return self.dyn_lacp_actor_state == LACP_ACTOR_NONE
500555

501556
def actor_state(self):
502557
"""Return the current LACP actor state"""
503558
return self.dyn_lacp_actor_state
504559

560+
def actor_notconfigured(self):
561+
"""Set the LACP actor state to NOTCONFIGURED"""
562+
self.dyn_lacp_actor_state = LACP_ACTOR_NOTCONFIGURED
563+
505564
def actor_none(self):
506565
"""Set the LACP actor state to NONE"""
507-
self.dyn_lacp_actor_state = LACP_STATE_NONE
566+
self.dyn_lacp_actor_state = LACP_ACTOR_NONE
508567

509568
def actor_init(self):
510569
"""Set the LACP actor state to INIT"""
@@ -514,9 +573,9 @@ def actor_up(self):
514573
"""Set the LACP actor state to UP"""
515574
self.dyn_lacp_actor_state = LACP_ACTOR_UP
516575

517-
def actor_noact(self):
518-
"""Set the LACP actor state to NOACT"""
519-
self.dyn_lacp_actor_state = LACP_ACTOR_NOACT
576+
def actor_nosync(self):
577+
"""Set the LACP actor state to NOSYNC"""
578+
self.dyn_lacp_actor_state = LACP_ACTOR_NOSYNC
520579

521580
def actor_state_name(self, state):
522581
"""Return the string of the actor state"""
@@ -532,7 +591,7 @@ def is_port_unselected(self):
532591
return self.dyn_lacp_port_selected == LACP_PORT_UNSELECTED
533592

534593
def is_port_standby(self):
535-
"""Return true if the lacp is a STANDBY port"""
594+
"""Return true if the lacp is a port in STANDBY"""
536595
return self.dyn_lacp_port_selected == LACP_PORT_STANDBY
537596

538597
def lacp_port_state(self):
@@ -548,9 +607,13 @@ def deselect_port(self):
548607
self.dyn_lacp_port_selected = LACP_PORT_UNSELECTED
549608

550609
def standby_port(self):
551-
"""Set the LACP port to STANDBY"""
610+
"""Set LACP port state to STANDBY"""
552611
self.dyn_lacp_port_selected = LACP_PORT_STANDBY
553612

613+
def deconfigure_port(self):
614+
"""Set LACP port state to NOTCONFIGURED"""
615+
self.dyn_lacp_port_selected = LACP_PORT_NOTCONFIGURED
616+
554617
def port_role_name(self, state):
555618
"""Return the LACP port role state name"""
556619
return LACP_PORT_DISPLAY_DICT[state]

faucet/valve.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -886,6 +886,9 @@ def ports_add(self, port_nums, cold_start=False, log_msg='up'):
886886
max_len=128))
887887

888888
if port.lacp:
889+
if cold_start:
890+
self.lacp_update_actor_state(port, False, cold_start=cold_start)
891+
self.lacp_update_port_selection_state(port, cold_start=cold_start)
889892
ofmsgs.extend(self.lacp_update(port, False, cold_start=cold_start))
890893
if port.lacp_active:
891894
ofmsgs.extend(self._lacp_actions(port.dyn_last_lacp_pkt, port))
@@ -1016,43 +1019,43 @@ def get_lacp_dpid_nomination(self, lacp_id, other_valves):
10161019
# Most_ports_dpid is the chosen DPID
10171020
return most_ports_dpid, 'most LAG ports'
10181021

1019-
def lacp_update_port_selection_state(self, port, other_valves=None):
1022+
def lacp_update_port_selection_state(self, port, other_valves=None, cold_start=False):
10201023
"""
10211024
Update the LACP port selection state
10221025
Args:
10231026
port (Port): LACP port
10241027
other_valves (list): List of other valves
1028+
cold_start (bool): Whether the port is being cold started
10251029
Returns:
10261030
bool: True if port state changed
10271031
"""
10281032
nominated_dpid = self.dp.dp_id
10291033
if self.dp.stack:
10301034
nominated_dpid, _ = self.get_lacp_dpid_nomination(port.lacp, other_valves)
10311035
prev_state = port.lacp_port_state()
1032-
if self.dp.dp_id == nominated_dpid:
1033-
port.select_port()
1034-
else:
1035-
port.deselect_port()
1036-
new_state = port.lacp_port_state()
1036+
new_state = port.lacp_port_update(self.dp.dp_id == nominated_dpid, cold_start=cold_start)
10371037
if new_state != prev_state:
10381038
self.logger.info('LAG %u %s %s (previous state %s)' % (
10391039
port.lacp, port, port.port_role_name(new_state),
10401040
port.port_role_name(prev_state)))
10411041
return new_state != prev_state
10421042

1043-
def lacp_update_actor_state(self, port, lacp_up, now=None, lacp_pkt=None):
1043+
def lacp_update_actor_state(self, port, lacp_up, now=None, lacp_pkt=None, cold_start=False):
10441044
"""
10451045
Updates a LAG actor state
10461046
Args:
10471047
port: LACP port
10481048
lacp_up (bool): Whether LACP is going UP or DOWN
10491049
now (float): Current epoch time
10501050
lacp_pkt (PacketMeta): LACP packet
1051+
cold_start (bool): Whether the port is being cold started
10511052
Returns:
10521053
bool: True if LACP state changed
10531054
"""
10541055
prev_actor_state = port.actor_state()
1055-
new_actor_state = port.lacp_update(lacp_up, now=now, lacp_pkt=lacp_pkt)
1056+
new_actor_state = port.lacp_actor_update(
1057+
lacp_up, now=now, lacp_pkt=lacp_pkt,
1058+
cold_start=cold_start)
10561059
if prev_actor_state != new_actor_state:
10571060
self.logger.info('LAG %u %s actor state %s (previous state %s)' % (
10581061
port.lacp, port, port.actor_state_name(new_actor_state),
@@ -1071,14 +1074,14 @@ def lacp_update(self, port, lacp_up, now=None, lacp_pkt=None,
10711074
now (float): The current time
10721075
lacp_pkt (PacketMeta): The received LACP packet
10731076
other_valves (list): List of other valves (in the stack)
1074-
cold_state (bool): Whether Faucet is being cold started or not
1077+
cold_start (bool): Whether the port is being cold started
10751078
Returns:
10761079
ofmsgs
10771080
"""
10781081
ofmsgs = []
10791082
updated = self.lacp_update_actor_state(port, lacp_up, now, lacp_pkt)
10801083
select_updated = self.lacp_update_port_selection_state(port, other_valves)
1081-
if updated or select_updated or cold_start:
1084+
if updated or select_updated:
10821085
if updated:
10831086
self._reset_lacp_status(port)
10841087
if port.is_port_selected() and port.is_actor_up():

tests/integration/mininet_multidp_tests.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ def wait_for_lacp_state(self, port_no, wanted_state, dpid, dp_name, timeout=30):
143143

144144
def wait_for_lacp_port_none(self, port_no, dpid, dp_name):
145145
"""Wait for LACP state NONE"""
146-
self.wait_for_lacp_state(port_no, -1, dpid, dp_name)
146+
self.wait_for_lacp_state(port_no, 0, dpid, dp_name)
147147

148148
def wait_for_lacp_port_init(self, port_no, dpid, dp_name):
149149
"""Wait for LACP state INIT"""
@@ -153,8 +153,8 @@ def wait_for_lacp_port_up(self, port_no, dpid, dp_name):
153153
"""Wait for LACP state UP"""
154154
self.wait_for_lacp_state(port_no, 3, dpid, dp_name)
155155

156-
def wait_for_lacp_port_noact(self, port_no, dpid, dp_name):
157-
"""Wait for LACP state NOACT"""
156+
def wait_for_lacp_port_nosync(self, port_no, dpid, dp_name):
157+
"""Wait for LACP state NOSYNC"""
158158
self.wait_for_lacp_state(port_no, 5, dpid, dp_name)
159159

160160
# We sort non_host_links by port because FAUCET sorts its ports

0 commit comments

Comments
 (0)