-
Notifications
You must be signed in to change notification settings - Fork 58
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Transformation module supporting VLAN-to-VXLAN bridging * Simple VXLAN test case * Arista EOS Jinja2 template * Documentation * Integration test
- Loading branch information
Showing
8 changed files
with
884 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
# VXLAN Configuration Module | ||
|
||
This configuration module configures VXLAN data plane, VLAN-to-VXLAN mapping, and static head-end replication. | ||
|
||
The module supports the following features: | ||
|
||
* VLAN-to-VXLAN bridging | ||
* Single VXLAN interface per device | ||
* Default loopback address used as the VTEP IP address | ||
* Static per-VLAN or per-node ingress replication | ||
* Mapping a subset of VLANs into VXLAN VNIs | ||
|
||
The module requires VLAN module (to set up **vlans** dictionary) and should be used with a routing protocol module to establish VTEP-to-VTEP connectivity. | ||
|
||
```eval_rst | ||
.. contents:: Table of Contents | ||
:depth: 2 | ||
:local: | ||
:backlinks: none | ||
``` | ||
|
||
## Platform Support | ||
|
||
The following table describes per-platform support of individual VXLAN features: | ||
|
||
| Operating system | VXLAN<br>bridging | | ||
| ------------------ | :-: | | ||
| Arista EOS | ✅ | | ||
|
||
## Global Parameters | ||
|
||
* **vxlan.domain** -- Ingress replication domain. Optional, default: **global**. Use this parameter when you want to build several isolated bridging domains within your lab. | ||
* **vxlan.flooding** -- A mechanism used to implement VXLAN flooding. Optional, default: **static**. | ||
* **vxlan.vlans** -- list of VLANs to be mapped into VXLAN VNIs. Optional, defaults to all VLANs with **vni** attribute. All VLANs listed in **vxlan.vlans** list must have a **vni** attribute. | ||
|
||
The only supported value for **vxlan.flooding** parameter is **static** -- statically configured ingress replication | ||
|
||
All global parameters can also be used as node parameters. | ||
|
||
## Default Behavior | ||
|
||
* All VLANs are mapped into VXLAN VNIs and bridged between VXLAN-enabled nodes. | ||
* Without specifying **vxlan.domain** for individual nodes or groups of nodes, all VXLAN-enabled nodes belong to a single **global** bridging domain. | ||
* VXLAN flooding is implemented with ingress replication. The VXLAN module builds per-VLAN VTEP replication lists for each node. Whether the device configuration uses VLAN-level or global replication lists is an implementation decision. | ||
|
||
## Selecting VXLAN-enabled VLANs | ||
|
||
You can select VLANs that should be extended with VXLAN transport in two ways: | ||
|
||
* Specify a list of VLAN names in **vxlan.vlans** global- or node-level parameters. VLANs specified in that list must be valid VLAN names but do not have to be present on every node. | ||
* Select VLANs based on the presence of **vni** attribute. | ||
|
||
You can set the **vni** attribute for individual VLANs, or have it assigned automatically. By default, all global VLANs get a **vni** attribute, and are thus extended over VXLAN transport. This behavior is controlled with **defaults.vlan.auto_vni** global default. | ||
|
||
## Building Ingress Replication Lists | ||
|
||
The VXLAN module builds ingress replication lists for all nodes with **vxlan.flooding** set to **static**. Each VLAN-specific ingress replication list includes the VTEP IP addresses of all other nodes in the same **vxlan.domain** that have a VLAN with the same **vni** attribute. | ||
|
||
All VLAN-specific ingress replication lists are merged into a node-level ingress replication list. Some devices support per-VLAN replication lists while others might use node-level replication list; the only difference is the amount of irrelevant traffic replicated across the VXLAN transport network. | ||
|
||
## Example | ||
|
||
We want to create a simple two-switch network transporting two VLANs across VXLAN backbone. We have to define the VLANs first: | ||
|
||
``` | ||
vlans: | ||
red: | ||
mode: bridge | ||
blue: | ||
mode: bridge | ||
``` | ||
|
||
Next, we'll define the *switches* and *hosts* groups to simplify node configuration. All switches will run Arista EOS and use VLAN, VXLAN, and OSPF modules. | ||
|
||
``` | ||
groups: | ||
hosts: | ||
members: [ h1, h2, h3, h4 ] | ||
device: linux | ||
switches: | ||
members: [ s1,s2 ] | ||
device: eos | ||
module: [ vlan,vxlan,ospf ] | ||
``` | ||
|
||
We also have to define individual nodes. Please note that we set node parameters (modules and device type) within *switches* and *hosts* groups. | ||
|
||
``` | ||
nodes: [ h1, h2, h3, h4, s1, s2 ] | ||
``` | ||
|
||
Finally, we have to define the links in our lab: | ||
|
||
``` | ||
links: | ||
- h1: | ||
s1: | ||
vlan.access: red | ||
- h2: | ||
s2: | ||
vlan.access: red | ||
- h3: | ||
s1: | ||
vlan.access: blue | ||
- h4: | ||
s2: | ||
vlan.access: blue | ||
- s1: | ||
s2: | ||
``` | ||
|
||
Please note we did not have to define: | ||
|
||
* VLAN tags or VXLAN VNIs | ||
* VLAN-to-VXLAN mappings | ||
* Any other VLAN or VXLAN parameters apart from VLAN names | ||
* IP addresses or routing protocol parameters |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
interface vxlan 1 | ||
vxlan source-interface loopback 0 | ||
{% if vlans is defined %} | ||
{% for vlan in vlans.values() if vlan.vni is defined %} | ||
vxlan vlan {{ vlan.id }} vni {{ vlan.vni }} | ||
{% if vlan.vtep_list is defined %} | ||
vxlan vlan {{ vlan.id }} flood vtep {{ vlan.vtep_list|join(' ') }} | ||
{% endif %} | ||
{% endfor %} | ||
{% endif %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
# | ||
# VXLAN module | ||
# | ||
import typing | ||
from box import Box | ||
import netaddr | ||
|
||
from . import _Module,get_effective_module_attribute | ||
from .. import common | ||
from .. import data | ||
from ..data import get_from_box | ||
from ..augment import devices | ||
from .. import addressing | ||
|
||
# | ||
# Check VXLAN-enabled VLANs | ||
# | ||
def node_vlan_check(node: Box, topology: Box) -> bool: | ||
if not node.vxlan.vlans: # Create a default list of VLANs if needed | ||
node.vxlan.vlans = [ name for name,value in node.vlans.items() if 'vni' in value ] | ||
|
||
OK = True | ||
vlan_list = [] | ||
for vname in node.vxlan.vlans: | ||
# | ||
# Report error for unknown VLAN names in VLAN list (they are not defined globally or on the node) | ||
if not vname in node.vlans and not vname in topology.get('vlans',{}): | ||
common.error( | ||
f'Unknown VLAN {vname} is specified in the list of VXLAN-enabled VLANs in node {node.name}', | ||
common.IncorrectValue, | ||
'vxlan') | ||
OK = False | ||
continue | ||
# | ||
# Skip VLAN names that are valid but not used on this node | ||
if not vname in node.vlans: | ||
continue | ||
if not 'vni' in node.vlans[vname]: | ||
common.error( | ||
f'VXLAN-enabled VLAN {vname} in node {node.name} does not have a VNI', | ||
common.IncorrectValue, | ||
'vxlan') | ||
OK = False | ||
else: | ||
vlan_list.append(vname) | ||
|
||
node.vxlan.vlans = vlan_list | ||
return OK | ||
|
||
# | ||
# Set VTEP IPv4 address | ||
# | ||
def node_set_vtep(node: Box, topology: Box) -> bool: | ||
if not 'ipv4' in node.loopback: | ||
common.error( | ||
f'VXLAN module needs an IPv4 address on loopback interface of {node.name}', | ||
common.IncorrectValue, | ||
'vxlan') | ||
return False | ||
|
||
vtep_ip = node.loopback.ipv4 # Assume we're using primarly loopback as VTEP | ||
node.vxlan.vtep = str(netaddr.IPNetwork(vtep_ip).ip) # ... and convert IPv4 prefix into an IPv4 address | ||
return True | ||
|
||
# | ||
# Build VLAN-specific VTEP list | ||
# | ||
def build_vtep_list(vlan: Box, node: str, nodes: typing.List[str], topology: Box) -> list: | ||
vlan.vtep_list = [] # Start with an emtpy VTEP list | ||
for n in nodes: | ||
if n == node: # Skip own node | ||
continue | ||
ndata = topology.nodes[n] | ||
if not 'vlans' in ndata: # No VLANs on remote node, skip it | ||
continue | ||
vni_match = filter(lambda x: x.vni == vlan.vni,ndata.vlans.values()) | ||
if list(vni_match): # Is there a VLAN with matching VNI on remote node? | ||
vlan.vtep_list.append(ndata.vxlan.vtep) # ... if so, add remote VTEP to VLAN flood list | ||
|
||
return_value = vlan.vtep_list # We'll return whatever we built | ||
if not vlan.vtep_list: # ... but will remove empty VTEP list from VLAN data | ||
vlan.pop('vtep_list') | ||
|
||
return return_value | ||
|
||
class VXLAN(_Module): | ||
|
||
def node_pre_transform(self, node: Box, topology: Box) -> None: | ||
data.must_be_string( | ||
parent = node.vxlan, | ||
key = 'flooding', | ||
path = 'nodes.{node.name}.vxlan', | ||
valid_values = [ 'static' ]) | ||
|
||
# We need multi-step post-transform node handling, so we have to do that in | ||
# module_post_transform | ||
# | ||
def module_post_transform(self, topology: Box) -> None: | ||
vxlan_domain_list: typing.Dict[str,list] = {} | ||
|
||
for name,ndata in topology.nodes.items(): | ||
if not 'vxlan' in ndata.get('module',[]): # Skip nodes without VXLAN module | ||
continue | ||
if not 'vlans' in ndata: # Skip VXLAN-enabled nodes without VLANs | ||
continue | ||
|
||
if not node_vlan_check(ndata,topology): # Check VLANs | ||
continue | ||
if not node_set_vtep(ndata,topology): # Set VTEP IP address | ||
continue | ||
|
||
vxlan_domain = ndata.vxlan.domain # Find VXLAN flooding domain name | ||
if not vxlan_domain in vxlan_domain_list: # Unknown VXLAN domain, prepare an empty list | ||
vxlan_domain_list[vxlan_domain] = [] | ||
vxlan_domain_list[vxlan_domain].append(name) # Add current node to VXLAN domain list | ||
|
||
for domain,nodes in vxlan_domain_list.items(): # Iterate over VXLAN flooding domains | ||
for node in nodes: # ... and over all nodes in each domain | ||
ndata = topology.nodes[node] | ||
if ndata.vxlan.get('flooding') != 'static': # Skip nodes that are not using static replication lists | ||
continue | ||
|
||
vtep_set = set() | ||
for vlan in ndata.vlans.values(): # Iterate over all VLANs defined in current node | ||
if 'vni' in vlan: # Are we dealing with VXLAN-enabled VLAN? | ||
vtep_list = build_vtep_list(vlan,node,nodes,topology) # Build VLAN-specific VTEP list | ||
vtep_set.update(vtep_list) # ... and add it to node-level VTEP set | ||
|
||
|
||
ndata.vxlan.vtep_list = sorted(list(vtep_set)) # Convert node-level VTEP set into a list |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
# | ||
# The devices under test are VLAN-to-VXLAN bridges | ||
# | ||
# * h1 and h2 should be able to ping each other | ||
# * h3 and h4 should be able to ping each other | ||
# * h1 should not be able to reach h3 | ||
# | ||
# Please note it might take a while for the lab to work due to | ||
# STP learning phase | ||
# | ||
groups: | ||
hosts: | ||
members: [ h1, h2, h3, h4 ] | ||
device: linux | ||
switches: | ||
members: [ s1,s2 ] | ||
module: [ vlan,vxlan,ospf ] | ||
|
||
vlans: | ||
red: | ||
mode: bridge | ||
blue: | ||
mode: bridge | ||
|
||
nodes: | ||
h1: | ||
h2: | ||
h3: | ||
h4: | ||
s1: | ||
s2: | ||
|
||
links: | ||
- h1: | ||
s1: | ||
vlan.access: red | ||
- h2: | ||
s2: | ||
vlan.access: red | ||
- h3: | ||
s1: | ||
vlan.access: blue | ||
- h4: | ||
s2: | ||
vlan.access: blue | ||
- s1: | ||
s2: |
Oops, something went wrong.