"""Manage LLDP."""
# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation.
# Copyright (C) 2015 Brad Cowie, Christopher Lorier and Joe Stringer.
# Copyright (C) 2015 Research and Education Advanced Network New Zealand Ltd.
# Copyright (C) 2015--2019 The Contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from collections import defaultdict
from faucet import valve_util
from faucet import valve_of
from faucet import valve_packet
from faucet.valve_manager_base import ValveManagerBase
[docs]
class ValveLLDPManager(ValveManagerBase):
"""Manage LLDP."""
def __init__(
self,
vlan_table,
highest_priority,
logger,
notify,
inc_var,
set_var,
set_port_var,
stack_manager,
):
self.vlan_table = vlan_table
self.highest_priority = highest_priority
self.logger = logger
self.notify = notify
self._set_var = set_var
self._inc_var = inc_var
self._set_port_var = set_port_var
self.stack_manager = stack_manager
def _lldp_match(self, port):
return self.vlan_table.match(
in_port=port.number,
eth_dst=valve_packet.LLDP_MAC_NEAREST_BRIDGE,
eth_dst_mask=valve_packet.BRIDGE_GROUP_MASK,
eth_type=valve_of.ether.ETH_TYPE_LLDP,
)
[docs]
def add_port(self, port):
ofmsgs = []
if port.receive_lldp:
ofmsgs.append(
self.vlan_table.flowcontroller(
match=self._lldp_match(port),
priority=self.highest_priority,
max_len=128,
)
)
return ofmsgs
[docs]
def del_port(self, port):
ofmsgs = []
if port.receive_lldp:
ofmsgs.append(
self.vlan_table.flowdel(
match=self._lldp_match(port), priority=self.highest_priority
)
)
return ofmsgs
[docs]
def verify_lldp(
self,
port,
now,
valve,
other_valves,
remote_dp_id,
remote_dp_name,
remote_port_id,
remote_port_state,
):
"""
Verify correct LLDP cabling, then update port to next state
Args:
port (Port): Port that received the LLDP
now (float): Current time
other_valves (list): Other valves in the topology
remote_dp_id (int): Received LLDP remote DP ID
remote_dp_name (str): Received LLDP remote DP name
remote_port_id (int): Recevied LLDP port ID
remote_port_state (int): Received LLDP port state
Returns:
dict: Ofmsgs by valve
"""
if not port.stack:
return {}
remote_dp = port.stack["dp"]
remote_port = port.stack["port"]
stack_correct = True
self._inc_var("stack_probes_received")
if (
remote_dp_id != remote_dp.dp_id
or remote_dp_name != remote_dp.name
or remote_port_id != remote_port.number
):
self.logger.error(
"Stack %s cabling incorrect, expected %s:%s:%u, actual %s:%s:%u"
% (
port,
valve_util.dpid_log(remote_dp.dp_id),
remote_dp.name,
remote_port.number,
valve_util.dpid_log(remote_dp_id),
remote_dp_name,
remote_port_id,
)
)
stack_correct = False
self._inc_var("stack_cabling_errors")
port.dyn_stack_probe_info = {
"last_seen_lldp_time": now,
"stack_correct": stack_correct,
"remote_dp_id": remote_dp_id,
"remote_dp_name": remote_dp_name,
"remote_port_id": remote_port_id,
"remote_port_state": remote_port_state,
}
return self.update_stack_link_state([port], now, valve, other_valves)
[docs]
def update_stack_link_state(self, ports, now, valve, other_valves):
"""
Update the stack link states of the set of provided stack ports
Args:
ports (list): List of stack ports to update the state of
now (float): Current time
valve (Valve): Valve that owns this LLDPManager instance
other_valves (list): List of other valves
Returns:
dict: ofmsgs by valve
"""
stack_changes = 0
ofmsgs_by_valve = defaultdict(list)
stacked_valves = set()
if self.stack_manager:
stacked_valves = {valve}.union(
self.stack_manager.stacked_valves(other_valves)
)
for port in ports:
before_state = port.stack_state()
after_state, reason = port.stack_port_update(now)
if before_state != after_state:
self._set_port_var("port_stack_state", after_state, port)
self._inc_var(
"port_stack_state_change_count",
labels=valve.dp.port_labels(port.number),
)
self.notify(
{"STACK_STATE": {"port": port.number, "state": after_state}}
)
self.logger.info(
"Stack %s state %s (previous state %s): %s"
% (
port,
port.stack_state_name(after_state),
port.stack_state_name(before_state),
reason,
)
)
stack_changes += 1
port_up = False
if port.is_stack_up():
port_up = True
elif port.is_stack_init() and port.stack["port"].is_stack_up():
port_up = True
for stack_valve in stacked_valves:
stack_valve.stack_manager.update_stack_topo(port_up, valve.dp, port)
if stack_changes or valve.stale_root:
self.logger.info(
"%u stack ports changed state, stale root %s"
% (stack_changes, valve.stale_root)
)
valve.stale_root = False
notify_dps = {}
for stack_valve in stacked_valves:
if not stack_valve.dp.dyn_running:
continue
ofmsgs_by_valve[stack_valve].extend(
stack_valve.add_vlans(stack_valve.dp.vlans.values())
)
for port in stack_valve.dp.stack_ports():
ofmsgs_by_valve[stack_valve].extend(
stack_valve.switch_manager.del_port(port)
)
ofmsgs_by_valve[stack_valve].extend(
stack_valve.stack_manager.add_tunnel_acls()
)
path_port = stack_valve.stack_manager.chosen_towards_port
path_port_number = path_port.number if path_port else 0.0
self._set_var(
"dp_root_hop_port",
path_port_number,
labels=stack_valve.dp.base_prom_labels(),
)
notify_dps.setdefault(stack_valve.dp.name, {})[
"root_hop_port"
] = path_port_number
# Find the first valve with a valid stack and trigger notification.
for stack_valve in stacked_valves:
if stack_valve.dp.stack.graph:
self.notify(
{
"STACK_TOPO_CHANGE": {
"stack_root": stack_valve.dp.stack.root_name,
"graph": stack_valve.dp.stack.get_node_link_data(),
"dps": notify_dps,
}
}
)
break
return ofmsgs_by_valve