"""Configuration for a datapath."""
# Copyright (C) 2015 Brad Cowie, Christopher Lorier and Joe Stringer.
# Copyright (C) 2015 Research and Education Advanced Network New Zealand Ltd.
# Copyright (C) 2015--2017 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.
import copy
import netaddr
from typing import Set
from collections import namedtuple, defaultdict
from datadiff import diff
from netaddr.core import AddrFormatError
import networkx
from faucet import faucet_pipeline
from faucet import valve_acl
from faucet import valve_of
from faucet.conf import Conf, InvalidConfigError, test_config_condition
from faucet.valve_table import ValveTable, ValveGroupTable
from faucet.valve_util import get_setting
from faucet.valve_packet import FAUCET_MAC
# Documentation generated using documentation_generator.py
# For attributues to be included in documentation they must
# have a default value, and their descriptor must come
# immediately after being set. See below for example.
[docs]class DP(Conf):
"""Stores state related to a datapath controlled by Faucet, including
configuration.
"""
acls = None
vlans = None
interfaces = None # config
interface_ranges = None
ports = None
routers = None
running = False
name = None
dp_id = None
cookie = None
configured = False
priority_offset = None
low_priority = None
high_priority = None
stack = None
stack_ports = None
output_only_ports = None
ignore_learn_ins = None
drop_broadcast_source_address = None
drop_spoofed_faucet_mac = None
groups = None
group_table = False
group_table_routing = False
max_hosts_per_resolve_cycle = None
max_host_fib_retry_count = None
max_resolve_backoff_time = None
packetin_pps = None
learn_jitter = None
learn_ban_timeout = None
advertise_interval = None
proactive_learn = None
pipeline_config_dir = None
use_idle_timeout = None
tables = {} # type: dict
tables_by_id = {} # type: dict
meters = {} # type: dict
timeout = None
arp_neighbor_timeout = None
lldp_beacon = {} # type: dict
metrics_rate_limit_sec = None
faucet_dp_mac = None
combinatorial_port_flood = None
lacp_timeout = None
dp_acls = None
dyn_last_coldstart_time = None
dyn_up_ports = set() # type: Set[int]
# Values that are set to None will be set using set_defaults
# they are included here for testing and informational purposes
defaults = {
'dp_id': None,
# Name for this dp, used for stats reporting and configuration
'name': None,
'interfaces': {},
'interface_ranges': {},
# How much to offset default priority by
'priority_offset': 0,
# Some priority values
'lowest_priority': None,
'low_priority': None,
'high_priority': None,
'highest_priority': None,
'cookie': 1524372928,
# Identification cookie value to allow for multiple controllers to control the same datapath
'timeout': 300,
# inactive MAC timeout
'description': None,
# description, strictly informational
'hardware': 'Open vSwitch',
# The hardware maker (for chosing an openflow driver)
'arp_neighbor_timeout': 250,
# ARP and neighbor timeout (seconds)
'ofchannel_log': None,
# OF channel log
'stack': None,
# stacking config, when cross connecting multiple DPs
'ignore_learn_ins': 10,
# Ignore every approx nth packet for learning.
# 2 will ignore 1 out of 2 packets; 3 will ignore 1 out of 3 packets.
# This limits control plane activity when learning new hosts rapidly.
# Flooding will still be done by the dataplane even with a packet
# is ignored for learning purposes.
'drop_broadcast_source_address': True,
# By default drop packets with a broadcast source address
'drop_spoofed_faucet_mac': True,
# By default drop packets on datapath spoofing the FAUCET_MAC
'group_table': False,
# Use GROUP tables for VLAN flooding
'group_table_routing': False,
# Use GROUP tables for routing (nexthops)
'max_hosts_per_resolve_cycle': 5,
# Max hosts to try to resolve per gateway resolution cycle.
'max_host_fib_retry_count': 10,
# Max number of times to retry resolution of a host FIB route.
'max_resolve_backoff_time': 32,
# Max number of seconds to back off to when resolving nexthops.
'packetin_pps': None,
# Ask switch to rate limit packet pps. TODO: Not supported by OVS in 2.7.0
'learn_jitter': 10,
# Jitter learn timeouts by up to this many seconds
'learn_ban_timeout': 10,
# When banning/limiting learning, wait this many seconds before learning can be retried
'advertise_interval': 30,
# How often to advertise (eg. IPv6 RAs)
'proactive_learn': True,
# whether proactive learning is enabled for IP nexthops
'pipeline_config_dir': get_setting('FAUCET_PIPELINE_DIR', True),
# where config files for pipeline are stored (if any).
'use_idle_timeout': False,
# Turn on/off the use of idle timeout for src_table, default OFF.
'lldp_beacon': {},
# Config for LLDP beacon service.
'metrics_rate_limit_sec': 0,
# Rate limit metric updates - don't update metrics if last update was less than this many seconds ago.
'faucet_dp_mac': FAUCET_MAC,
# MAC address of packets sent by FAUCET, not associated with any VLAN.
'combinatorial_port_flood': False,
# if True, use a seperate output flow for each input port on this VLAN.
'lacp_timeout': 30,
# Number of seconds without a LACP message when we consider a LACP group down.
'dp_acls': None,
# List of dataplane ACLs (overriding per port ACLs).
}
defaults_types = {
'dp_id': int,
'name': str,
'interfaces': dict,
'interface_ranges': dict,
'priority_offset': int,
'lowest_priority': int,
'low_priority': int,
'high_priority': int,
'highest_priority': int,
'cookie': int,
'timeout': int,
'description': str,
'hardware': str,
'arp_neighbor_timeout': int,
'ofchannel_log': str,
'stack': dict,
'ignore_learn_ins': int,
'drop_broadcast_source_address': bool,
'drop_spoofed_faucet_mac': bool,
'drop_bpdu': bool,
'drop_lldp': bool,
'group_table': bool,
'group_table_routing': bool,
'max_hosts_per_resolve_cycle': int,
'max_host_fib_retry_count': int,
'max_resolve_backoff_time': int,
'packetin_pps': int,
'learn_jitter': int,
'learn_ban_timeout': int,
'advertise_interval': int,
'proactive_learn': bool,
'pipeline_config_dir': str,
'use_idle_timeout': bool,
'lldp_beacon': dict,
'metrics_rate_limit_sec': int,
'faucet_dp_mac': str,
'combinatorial_port_flood': bool,
'dp_acls': list,
}
stack_defaults_types = {
'priority': int,
}
lldp_beacon_defaults_types = {
'send_interval': int,
'max_per_interval': int,
'system_name': str,
}
wildcard_table = ValveTable(
valve_of.ofp.OFPTT_ALL, 'all', None, flow_cookie=0)
def __init__(self, _id, dp_id, conf):
"""Constructs a new DP object"""
super(DP, self).__init__(_id, dp_id, conf)
self.acls = {}
self.vlans = {}
self.ports = {}
self.routers = {}
self.stack_ports = []
self.output_only_ports = []
self.lldp_beacon_ports = []
def __str__(self):
return self.name
[docs] def check_config(self):
test_config_condition(not isinstance(self.dp_id, int), (
'dp_id must be %s not %s' % (int, type(self.dp_id))))
test_config_condition(self.dp_id < 0 or self.dp_id > 2**64-1, (
'DP ID %s not in valid range' % self.dp_id))
test_config_condition(not netaddr.valid_mac(self.faucet_dp_mac), (
'invalid MAC address %s' % self.faucet_dp_mac))
test_config_condition(self.group_table and self.group_table_routing, (
'groups for routing and other functions simultaneously not supported'))
test_config_condition(not (self.interfaces or self.interface_ranges), (
'DP %s must have at least one interface' % self))
# To prevent L2 learning from timing out before L3 can refresh
test_config_condition(self.timeout < self.arp_neighbor_timeout, (
'L2 timeout must be >= L3 timeout'))
if self.lldp_beacon:
self._check_conf_types(self.lldp_beacon, self.lldp_beacon_defaults_types)
test_config_condition('send_interval' not in self.lldp_beacon, (
'lldp_beacon send_interval not set'))
test_config_condition('max_per_interval' not in self.lldp_beacon, (
'lldp_beacon max_per_interval not set'))
self.lldp_beacon = self._set_unknown_conf(
self.lldp_beacon, self.lldp_beacon_defaults_types)
if self.lldp_beacon['system_name'] is None:
self.lldp_beacon['system_name'] = self.name
if self.stack:
self._check_conf_types(self.stack, self.stack_defaults_types)
def _configure_tables(self):
"""Configure FAUCET pipeline of tables with matches."""
self.groups = ValveGroupTable()
for table_id, table_config in enumerate(faucet_pipeline.FAUCET_PIPELINE):
table_name, restricted_match_types = table_config
self.tables[table_name] = ValveTable(
table_id, table_name, restricted_match_types,
self.cookie, notify_flow_removed=self.use_idle_timeout)
self.tables_by_id[table_id] = self.tables[table_name]
[docs] def set_defaults(self):
super(DP, self).set_defaults()
self._set_default('dp_id', self._id)
self._set_default('name', str(self._id))
self._set_default('lowest_priority', self.priority_offset)
self._set_default('low_priority', self.priority_offset + 9000)
self._set_default('high_priority', self.low_priority + 1)
self._set_default('highest_priority', self.high_priority + 98)
self._set_default('description', self.name)
self._configure_tables()
[docs] def match_tables(self, match_type):
"""Return list of tables with matches of a specific match type."""
match_tables = []
for table in list(self.tables_by_id.values()):
if table.restricted_match_types is not None:
if match_type in table.restricted_match_types:
match_tables.append(table)
else:
match_tables.append(table)
return match_tables
[docs] def in_port_tables(self):
"""Return list of tables that specify in_port as a match."""
return self.match_tables('in_port')
[docs] def vlan_match_tables(self):
"""Return list of tables that specify vlan_vid as a match."""
return self.match_tables('vlan_vid')
[docs] def all_valve_tables(self):
"""Return list of all Valve tables."""
return list(self.tables_by_id.values())
[docs] def add_acl(self, acl_ident, acl):
"""Add an ACL to this DP."""
self.acls[acl_ident] = acl
[docs] def add_router(self, router_ident, router):
"""Add a router to this DP."""
self.routers[router_ident] = router
[docs] def add_port(self, port):
"""Add a port to this DP."""
port_num = port.number
self.ports[port_num] = port
if port.output_only:
self.output_only_ports.append(port)
elif port.stack is not None:
self.stack_ports.append(port)
if port.lldp_beacon_enabled():
self.lldp_beacon_ports.append(port)
[docs] def resolve_stack_topology(self, dps):
"""Resolve inter-DP config for stacking."""
def canonical_edge(dp, port):
peer_dp = port.stack['dp']
peer_port = port.stack['port']
sort_edge_a = (
dp.name, port.name, dp, port)
sort_edge_z = (
peer_dp.name, peer_port.name, peer_dp, peer_port)
sorted_edge = sorted((sort_edge_a, sort_edge_z))
edge_a, edge_b = sorted_edge[0][2:], sorted_edge[1][2:]
return edge_a, edge_b
def make_edge_name(edge_a, edge_z):
edge_a_dp, edge_a_port = edge_a
edge_z_dp, edge_z_port = edge_z
return '%s:%s-%s:%s' % (
edge_a_dp.name, edge_a_port.name,
edge_z_dp.name, edge_z_port.name)
def make_edge_attr(edge_a, edge_z):
edge_a_dp, edge_a_port = edge_a
edge_z_dp, edge_z_port = edge_z
return {
'dp_a': edge_a_dp, 'port_a': edge_a_port,
'dp_z': edge_z_dp, 'port_z': edge_z_port}
root_dp = None
stack_dps = []
for dp in dps:
if dp.stack is not None:
stack_dps.append(dp)
if 'priority' in dp.stack:
test_config_condition(dp.stack['priority'] <= 0, (
'stack priority must be > 0'))
test_config_condition(root_dp is not None, 'cannot have multiple stack roots')
root_dp = dp
for vlan in list(dp.vlans.values()):
test_config_condition(vlan.faucet_vips != [], (
'routing + stacking not supported'))
if root_dp is None:
test_config_condition(stack_dps, 'stacking enabled but no root_dp')
return
edge_count = {}
graph = networkx.MultiGraph()
for dp in dps:
if dp.stack_ports:
graph.add_node(dp.name)
for port in dp.stack_ports:
edge = canonical_edge(dp, port)
edge_a, edge_z = edge
edge_name = make_edge_name(edge_a, edge_z)
edge_attr = make_edge_attr(edge_a, edge_z)
edge_a_dp, _ = edge_a
edge_z_dp, _ = edge_z
if edge_name not in edge_count:
edge_count[edge_name] = 0
edge_count[edge_name] += 1
graph.add_edge(
edge_a_dp.name, edge_z_dp.name,
key=edge_name, port_map=edge_attr)
if graph.size():
for edge_name, count in list(edge_count.items()):
test_config_condition(count != 2, '%s defined only in one direction' % edge_name)
if self.name in graph:
if self.stack is None:
self.stack = {}
self.stack['root_dp'] = root_dp
self.stack['graph'] = graph
[docs] def shortest_path(self, dest_dp):
"""Return shortest path to a DP, as a list of DPs."""
if self.stack is not None and 'root_dp' in self.stack:
return networkx.shortest_path(
self.stack['graph'], self.name, dest_dp)
return []
[docs] def shortest_path_to_root(self):
"""Return shortest path to root DP, as list of DPs."""
# TODO: root_dp will be None, if stacking is enabled but the root DP is down.
if self.stack is not None and 'root_dp' in self.stack:
root_dp = self.stack['root_dp']
if root_dp is not None and root_dp != self:
return self.shortest_path(root_dp.name)
return []
[docs] def peer_stack_up_ports(self, peer_dp):
"""Return list of stack ports that are up towards a peer."""
return [port for port in self.stack_ports if port.running() and (
port.stack['dp'].name == peer_dp)]
[docs] def shortest_path_port(self, dest_dp):
"""Return first port on our DP, that is the shortest path towards dest DP."""
shortest_path = self.shortest_path(dest_dp)
if shortest_path is not None:
peer_dp = shortest_path[1]
peer_dp_ports = self.peer_stack_up_ports(peer_dp)
if peer_dp_ports:
return peer_dp_ports[0]
return None
[docs] def reset_refs(self, vlans=None):
if vlans is None:
vlans = self.vlans
self.vlans = {}
for vlan in list(vlans.values()):
vlan.reset_ports(list(self.ports.values()))
if vlan.get_ports():
self.vlans[vlan.vid] = vlan
[docs] def finalize_config(self, dps):
"""Perform consistency checks after initial config parsing."""
port_by_name = {}
dp_by_name = {}
vlan_by_name = {}
def resolve_port(port_name):
"""Resolve port by name or number."""
test_config_condition(not isinstance(port_name, (str, int)), (
'Port must be type %s or %s not %s' % (str, int, type(port_name))))
if port_name in port_by_name:
return port_by_name[port_name]
elif port_name in self.ports:
return self.ports[port_name]
return None
def resolve_ports(port_names):
"""Resolve list of ports, by port by name or number."""
resolved_ports = []
for port_name in port_names:
port = resolve_port(port_name)
if port is not None:
resolved_ports.append(port)
return resolved_ports
def resolve_port_numbers(port_names):
"""Resolve list of ports to numbers, by port by name or number."""
return [port.number for port in resolve_ports(port_names)]
def resolve_vlan(vlan_name):
"""Resolve VLAN by name or VID."""
test_config_condition(not isinstance(vlan_name, (str, int)), (
'VLAN must be type %s or %s not %s' % (str, int, type(vlan_name))))
if vlan_name in vlan_by_name:
return vlan_by_name[vlan_name]
elif vlan_name in self.vlans:
return self.vlans[vlan_name]
return None
def resolve_stack_dps():
"""Resolve DP references in stacking config."""
port_stack_dp = {}
for port in self.stack_ports:
stack_dp = port.stack['dp']
test_config_condition(stack_dp not in dp_by_name, (
'stack DP %s not defined' % stack_dp))
port_stack_dp[port] = dp_by_name[stack_dp]
for port, dp in list(port_stack_dp.items()):
port.stack['dp'] = dp
stack_port_name = port.stack['port']
test_config_condition(stack_port_name not in dp.ports, (
'stack port %s not defined in DP %s' % (stack_port_name, dp.name)))
port.stack['port'] = dp.ports[stack_port_name]
def resolve_mirror_destinations():
"""Resolve mirror port references and destinations."""
mirror_from_port = defaultdict(list)
for mirror_port in list(self.ports.values()):
if mirror_port.mirror is not None:
mirrored_ports = resolve_ports(mirror_port.mirror)
test_config_condition(len(mirrored_ports) != len(mirror_port.mirror), (
'port mirror not defined in DP %s' % self.name))
for mirrored_port in mirrored_ports:
mirror_from_port[mirrored_port].append(mirror_port)
# TODO: confusingly, mirror at config time means what ports to mirror from.
# But internally we use as a list of ports to mirror to.
for mirrored_port, mirror_ports in list(mirror_from_port.items()):
mirrored_port.mirror = []
for mirror_port in mirror_ports:
mirrored_port.mirror.append(mirror_port.number)
mirror_port.output_only = True
def resolve_override_output_ports():
"""Resolve override output ports."""
for port_no, port in list(self.ports.items()):
if port.override_output_port:
port.override_output_port = resolve_port(port.override_output_port)
test_config_condition(not port.override_output_port, (
'override_output_port port not defined'))
self.ports[port_no] = port
def resolve_acl(acl_in):
"""Resolve an individual ACL."""
test_config_condition(acl_in not in self.acls, (
'missing ACL %s on %s' % (self.name, acl_in)))
acl = self.acls[acl_in]
mirror_destinations = set()
def resolve_meter(_acl, action_conf):
meter_name = action_conf
test_config_condition(meter_name not in self.meters, (
'meter %s is not configured' % meter_name))
return action_conf
def resolve_mirror(acl, action_conf):
port_name = action_conf
port = resolve_port(port_name)
# If this DP does not have this port, do nothing.
if port is not None:
action_conf = port.number
mirror_destinations.add(port.number)
return action_conf
return None
def resolve_output(_acl, action_conf):
resolved_action_conf = {}
test_config_condition(not isinstance(action_conf, dict), (
'action conf is not a dictionary'))
for output_action, output_action_values in list(action_conf.items()):
if output_action == 'port':
port_name = output_action_values
port = resolve_port(port_name)
test_config_condition(not port, (
'output port not defined in DP: %s' % self.name))
resolved_action_conf[output_action] = port.number
elif output_action == 'ports':
resolved_ports = resolve_port_numbers(output_action_values)
test_config_condition(len(resolved_ports) != len(output_action_values), (
'output port(s) not defined in DP: %s' % self.name))
resolved_action_conf[output_action] = resolved_ports
elif output_action == 'failover':
failover = output_action_values
test_config_condition(not isinstance(failover, dict), (
'failover is not a dictionary'))
resolved_action_conf[output_action] = {}
for failover_name, failover_values in list(failover.items()):
if failover_name == 'ports':
resolved_failover_values = resolve_port_numbers(failover_values)
test_config_condition(len(resolved_failover_values) != len(failover_values), (
'failover port(s) not defined in DP: %s' % self.name))
failover_values = resolved_failover_values
resolved_action_conf[output_action][failover_name] = failover_values
else:
resolved_action_conf[output_action] = output_action_values
if resolved_action_conf:
return resolved_action_conf
return None
def resolve_noop(_acl, action_conf):
return action_conf
action_resolvers = {
'meter': resolve_meter,
'mirror': resolve_mirror,
'output': resolve_output,
'allow': resolve_noop,
'force_port_vlan': resolve_noop,
}
def build_acl(acl, vid=None):
"""Check that ACL can be built from config and mark mirror destinations."""
if acl.rules:
null_dp = namedtuple('null_dp', 'ofproto')
null_dp.ofproto = valve_of.ofp
try:
ofmsgs = valve_acl.build_acl_ofmsgs(
[acl], self.wildcard_table,
valve_of.goto_table(self.wildcard_table),
valve_of.goto_table(self.wildcard_table),
2**16-1, self.meters, acl.exact_match,
vlan_vid=vid)
test_config_condition(not ofmsgs, 'of messages is empty')
for ofmsg in ofmsgs:
ofmsg.datapath = null_dp
ofmsg.set_xid(0)
ofmsg.serialize()
except (AddrFormatError, KeyError, ValueError) as err:
raise InvalidConfigError(err)
for port_no in mirror_destinations:
port = self.ports[port_no]
port.output_only = True
for rule_conf in acl.rules:
for attrib, attrib_value in list(rule_conf.items()):
if attrib == 'actions':
resolved_actions = {}
test_config_condition(not isinstance(attrib_value, dict), (
'attrib_value is not a dictionary'))
for action_name, action_conf in list(attrib_value.items()):
resolved_action_conf = action_resolvers[action_name](
acl, action_conf)
test_config_condition(resolved_action_conf is None, (
'cannot resolve ACL rule %s' % rule_conf))
resolved_actions[action_name] = resolved_action_conf
rule_conf[attrib] = resolved_actions
build_acl(acl, vid=1)
def verify_acl_exact_match(acls):
for acl in acls:
test_config_condition(acl.exact_match != acls[0].exact_match, (
'ACLs when used together must have consistent exact_match'))
def resolve_acls():
"""Resolve config references in ACLs."""
# TODO: move this config validation to ACL object.
for vlan in list(self.vlans.values()):
if vlan.acls_in:
acls = []
for acl in vlan.acls_in:
resolve_acl(acl)
acls.append(self.acls[acl])
vlan.acls_in = acls
verify_acl_exact_match(acls)
for port in list(self.ports.values()):
if port.acls_in:
test_config_condition(self.dp_acls, (
'dataplane ACLs cannot be used with port ACLs.'))
acls = []
for acl in port.acls_in:
resolve_acl(acl)
acls.append(self.acls[acl])
port.acls_in = acls
verify_acl_exact_match(acls)
if self.dp_acls:
acls = []
for acl in self.acls:
resolve_acl(acl)
acls.append(self.acls[acl])
self.dp_acls = acls
def resolve_vlan_names_in_routers():
"""Resolve VLAN references in routers."""
dp_routers = {}
for router_name, router in list(self.routers.items()):
vlans = []
for vlan_name in router.vlans:
vlan = resolve_vlan(vlan_name)
if vlan is not None:
vlans.append(vlan)
if len(vlans) > 1:
dp_router = copy.copy(router)
dp_router.vlans = vlans
dp_routers[router_name] = dp_router
self.routers = dp_routers
test_config_condition(not self.vlans, 'no VLANs referenced by interfaces in %s' % self.name)
for port in list(self.ports.values()):
port_by_name[port.name] = port
for dp in dps:
dp_by_name[dp.name] = dp
for vlan in list(self.vlans.values()):
vlan_by_name[vlan.name] = vlan
resolve_stack_dps()
resolve_mirror_destinations()
resolve_override_output_ports()
resolve_vlan_names_in_routers()
resolve_acls()
bgp_vlans = self.bgp_vlans()
if bgp_vlans:
for vlan in bgp_vlans:
vlan_dps = [dp for dp in dps if vlan.vid in dp.vlans]
test_config_condition(len(vlan_dps) != 1, (
'DPs %s sharing a BGP speaker VLAN is unsupported'))
router_ids = set([vlan.bgp_routerid for vlan in bgp_vlans])
test_config_condition(len(router_ids) != 1, 'BGP router IDs must all be the same')
bgp_ports = set([vlan.bgp_port for vlan in bgp_vlans])
test_config_condition(len(bgp_ports) != 1, 'BGP ports must all be the same')
for vlan in bgp_vlans:
test_config_condition(vlan.bgp_server_addresses != (
bgp_vlans[0].bgp_server_addresses), (
'BGP server addresses must all be the same'))
for port in list(self.ports.values()):
port.finalize()
for vlan in list(self.vlans.values()):
vlan.finalize()
for acl in list(self.acls.values()):
acl.finalize()
for router in list(self.routers.values()):
router.finalize()
self.finalize()
[docs] def get_native_vlan(self, port_num):
"""Return native VLAN for a port by number, or None."""
if port_num in self.ports:
return self.ports[port_num].native_vlan
return None
[docs] def bgp_vlans(self):
"""Return list of VLANs with BGP enabled."""
return [vlan for vlan in list(self.vlans.values()) if vlan.bgp_as]
[docs] def to_conf(self):
"""Return DP config as dict."""
result = super(DP, self).to_conf()
if result is not None:
if 'stack' in result:
if result['stack'] is not None:
result['stack'] = {
'root_dp': str(self.stack['root_dp'])
}
interface_dict = {}
for port in list(self.ports.values()):
interface_dict[port.name] = port.to_conf()
result['interfaces'] = interface_dict
return result
[docs] def get_tables(self):
"""Return tables as dict for API call."""
result = {}
for table_name, table in list(self.tables.items()):
result[table_name] = table.table_id
return result
[docs] def get_config_dict(self):
"""Return DP config as a dict for API call."""
if self.name:
vlans_dict = {}
for vlan in list(self.vlans.values()):
vlans_dict[vlan.name] = vlan.to_conf()
acls_dict = {}
for acl_id, acl in list(self.acls.items()):
acls_dict[acl_id] = acl.to_conf()
return {
'dps': {self.name: self.to_conf()},
'vlans': vlans_dict,
'acls': acls_dict}
return {}
def _get_acl_config_changes(self, logger, new_dp):
"""Detect any config changes to ACLs.
Args:
logger (ValveLogger): logger instance.
new_dp (DP): new dataplane configuration.
Returns:
changed_acls (dict): ACL ID map to new/changed ACLs.
"""
changed_acls = {}
for acl_id, new_acl in list(new_dp.acls.items()):
if acl_id not in self.acls:
changed_acls[acl_id] = new_acl
logger.info('ACL %s new' % acl_id)
else:
if new_acl != self.acls[acl_id]:
changed_acls[acl_id] = new_acl
logger.info('ACL %s changed' % acl_id)
return changed_acls
def _get_vlan_config_changes(self, logger, new_dp):
"""Detect any config changes to VLANs.
Args:
logger (ValveLogger): logger instance.
new_dp (DP): new dataplane configuration.
Returns:
changes (tuple) of:
deleted_vlans (set): deleted VLAN IDs.
changed_vlans (set): changed/added VLAN IDs.
"""
deleted_vlans = set([])
for vid in list(self.vlans.keys()):
if vid not in new_dp.vlans:
deleted_vlans.add(vid)
changed_vlans = set([])
for vid, new_vlan in list(new_dp.vlans.items()):
if vid not in self.vlans:
changed_vlans.add(vid)
logger.info('VLAN %s added' % vid)
else:
old_vlan = self.vlans[vid]
if old_vlan != new_vlan:
if not old_vlan.ignore_subconf(new_vlan):
changed_vlans.add(vid)
logger.info('VLAN %s config changed' % vid)
else:
# Preserve current VLAN including current
# dynamic state like caches, if VLAN and ports
# did not change at all.
new_dp.vlans[vid].merge_dyn(old_vlan)
if not deleted_vlans and not changed_vlans:
logger.info('no VLAN config changes')
return (deleted_vlans, changed_vlans)
def _get_port_config_changes(self, logger, new_dp, changed_vlans, changed_acls):
"""Detect any config changes to ports.
Args:
logger (ValveLogger): logger instance.
new_dp (DP): new dataplane configuration.
changed_vlans (set): changed/added VLAN IDs.
changed_acls (dict): ACL ID map to new/changed ACLs.
Returns:
changes (tuple) of:
all_ports_changed (bool): True if all ports changed.
deleted_ports (set): deleted port numbers.
changed_ports (set): changed/added port numbers.
changed_acl_ports (set): changed ACL only port numbers.
"""
all_ports_changed = False
changed_ports = set([])
changed_acl_ports = set([])
for port_no, new_port in list(new_dp.ports.items()):
if port_no not in self.ports:
# Detected a newly configured port
changed_ports.add(port_no)
logger.info('port %s added' % port_no)
else:
old_port = self.ports[port_no]
# An existing port has configs changed
if new_port != old_port:
# ACL optimization - did the ACL, and only the ACL change.
if old_port.ignore_subconf(new_port, ignore_keys=set(['acls_in'])):
if old_port.acls_in != new_port.acls_in:
changed_acl_ports.add(port_no)
old_acl_ids = old_port.acls_in
if old_acl_ids:
old_acl_ids = [acl._id for acl in old_acl_ids]
new_acl_ids = new_port.acls_in
if new_acl_ids:
new_acl_ids = [acl._id for acl in new_acl_ids]
logger.info('port %s ACL changed (ACL %s to %s)' % (
port_no, old_acl_ids, new_acl_ids))
else:
changed_ports.add(port_no)
logger.info('port %s reconfigured (%s)' % (
port_no, diff(old_port.to_conf(), new_port.to_conf(), context=1)))
elif new_port.acls_in:
port_acls_changed = [acl for acl in new_port.acls_in if acl in changed_acls]
if port_acls_changed:
changed_acl_ports.add(port_no)
logger.info('port %s ACL changed (ACL %s content changed)' % (
port_no, port_acls_changed))
# TODO: optimize case where only VLAN ACL changed.
for vid in changed_vlans:
for port in new_dp.vlans[vid].get_ports():
changed_ports.add(port.number)
deleted_ports = set(list(self.ports.keys())) - set(list(new_dp.ports.keys()))
if deleted_ports:
logger.info('deleted ports: %s' % deleted_ports)
if changed_ports == set(new_dp.ports.keys()):
all_ports_changed = True
elif (not changed_ports and
not deleted_ports and
not changed_acl_ports):
logger.info('no port config changes')
return (all_ports_changed, deleted_ports,
changed_ports, changed_acl_ports)
[docs] def get_config_changes(self, logger, new_dp):
"""Detect any config changes.
Args:
logger (ValveLogger): logger instance
new_dp (DP): new dataplane configuration.
Returns:
(tuple): changes tuple containing:
deleted_ports (set): deleted port numbers.
changed_ports (set): changed/added port numbers.
changed_acl_ports (set): changed ACL only port numbers.
deleted_vlans (set): deleted VLAN IDs.
changed_vlans (set): changed/added VLAN IDs.
all_ports_changed (bool): True if all ports changed.
"""
if self.ignore_subconf(new_dp):
logger.info('DP base level config changed - requires cold start')
elif new_dp.routers != self.routers:
logger.info('DP routers config changed - requires cold start')
else:
changed_acls = self._get_acl_config_changes(logger, new_dp)
deleted_vlans, changed_vlans = self._get_vlan_config_changes(logger, new_dp)
(all_ports_changed, deleted_ports,
changed_ports, changed_acl_ports) = self._get_port_config_changes(
logger, new_dp, changed_vlans, changed_acls)
return (deleted_ports, changed_ports, changed_acl_ports,
deleted_vlans, changed_vlans, all_ports_changed)
# default cold start
return (set(), set(), set(), set(), set(), True)