The 5th post in the ‘Automate Leaf and Spine Deployment’ series goes through the deployment of the services that run on top of the fabric. These services are grouped into 3 categories, tenant, interface and routing. Services are configured only on the leaf and border switches, the spines have no need for them as they just route the VXLAN encapsulated packets with no knowledge or care of what is within them.
- Automate Leaf and Spine Deployment Part1 - introduction and structure
- Automate Leaf and Spine Deployment Part2 - input variable validation
- Automate Leaf and Spine Deployment Part3 - fabric variables and dynamic inventory
- Automate Leaf and Spine Deployment Part4 - deploying the fabric with ansible
- Automate Leaf and Spine Deployment Part5 - fabric services: tenant, interface, route
- Automate Leaf and Spine Deployment Part6 - post validation
- github repo
All the services are very similar in their structure in that they take input from a variable file and pass it through a filter plugin to create a data model that defines the service. Dependant on the service it could either be a per-device-type or per-device data model. All three services are for want of a better word, sub-roles within the service role. They each have separate variable files, tasks and templates as well as separate methods within the one filter plugin. The following services are built by each sub-role:
- tenant: VRFs, L3VNIs, L2VNIs, SVIs and VLANs
- interface: Loopbacks, single-homed (layer3, access, trunk) and dual-homed (access, trunk) interfaces
- routing: BGP, OSPF, static routes and redistribution
Within the main playbook each sub-role task is imported from the services role with tags associated in the playbook applied to all sub-role tasks.
- name: Builds the tenant config snippets
import_role:
name: services
tasks_from: svc_tnt
tags: [tnt, bse_fbc_tnt, bse_fbc_tnt_intf, full]
- name: Builds the interface config snippets
import_role:
name: services
tasks_from: svc_intf
tags: [intf, bse_fbc_tnt_intf, full]
- name: Builds the tenant routing config snippets
import_role:
name: services
tasks_from: svc_rte
tags: [rte, full]
The following sections go through the possible setting (variables) in each sub-role before finishing with info on deployment details and tags.
Table Of Contents
Tenant (svc_tnt)
Tenants, SVIs, VLANs and VXLANs are created based on the variables stored in the service_tenant.yml file (svc_tnt.tnt).
tnt: A list of tenants that contains a list of VLANs (Layer2 and/ or Layer3)
- Tenants (VRFs) will only be created on a leaf or border if a VLAN within that tenant is to be created on that device
- Even if a tenant is not a layer3 tenant a VRF will still be created and the L3VNI and tenant VLAN number reserved
- If the tenant is a layer3 tenant the route-map for redistribution is always created and attached to the BGP peer
Key | Value | Mandatory | Information |
---|---|---|---|
tenant_name |
string | Yes | Name of the VRF |
l3_tenant |
True or False | Yes | Does it need SVIs or is routing done off the fabric (i.e external router) |
bgp_redist_tag |
integer | No | Tag used to redistributed SVIs into BGP, by default uses tenant SVI number |
vlans |
list | Yes | List of VLANs within this tenant (see the below table) |
vlans: A List of VLANs within a tenant which at a minimum need the layer2 values of name and num. VLANs and SVIs can only be created on all leafs and/ or all borders, you can’t selectively say which individual leaf or border switches to create them on
- Unless an IP address is assigned to a VLAN (ip_addr) it will only be L2 VLAN
- L3 VLANs are automatically redistributed into BGP. This can be disabled (ipv4_bgp_redist: False) on a per-vlan basis
- By default VLANs will only be created on the leaf switches (create_on_leaf). This can be changed on a per-vlan basis to create only on borders (create_on_border) or on both leafs and borders
- To add a non-VXLAN SVI (without anycast address) create the VLAN as normal but with the extra
VXLAN: False
dictionary. The SVI is defined in service_interface.yml astype: svi
- Optional settings will implicitly use the default value, they only need defining if not using the default value
Key | Value | Mandatory | Information |
---|---|---|---|
num |
integer | Yes | The VLAN number |
name |
string | Yes | The VLAN name |
ip_addr |
x.x.x.x/x | No | Adding an IP address automatically making the VLAN L3 (not set by default) |
ipv4_bgp_redist |
True or False | No | Dictates whether the SVI is redistributed into BGP VRF address family (default True) |
create_on_leaf |
True or False | No | Dictates whether this VLAN is created on the leafs (default True) |
create_on_border |
True or False | No | Dictates whether this VLAN is created on the borders (default False) |
vxlan |
True or False | No | Whether VXLAN or normal VLAN. Only need if don’t want it to be VXLAN (default True) |
The redistribution route-map name can be changed in the advanced (adv) section of services-tenant.yml or services-routing.yml. If defined in both places the setting in services-routing.yml take precedence.
L2VNI and L3VNI numbers
The L2VNI and L3VNI values are automatically derived and incremented on a per-tenant basis based on the start and increment seed values defined in the advanced section (svc_tnt.adv) of services_tenant.yml.
adv.bse_vni: Starting VNI numbers
Key | Value | Information |
---|---|---|
tnt_vlan |
3001 | Starting VLAN number for the transit L3VNI |
l3vni |
10003001 | Starting L3VNI number |
l2vni |
10000 | Starting L2VNI number, the VLAN number will be added to this |
adv.vni_incre: Number by which VNIs are incremented for each tenant
Key | Value | Information |
---|---|---|
tnt_vlan |
1 | Value by which the transit L3VNI VLAN number is increased for each tenant |
l3vni |
1 | Value by which the transit L3VNI VNI number is increased for each tenant |
l2vni |
10000 | Value by which the L2VNI range (range + vlan) is increased for each tenant |
For example a two tenant fabric each with a VLAN 20 using the above values would have L3 tenant SVIs of 3001, 3002, L3VNIs or 10003001, 10003002 and L2VNIs of 10020 and 20020.
Tasks and templates
A new data-model is created from the services_tenant.yml variables by passing them through the filter_plugin format_dm.py method create_svc_tnt_dm along with the BGP route-map name (if exists) and ASN (from fabric.yml). The result is a per-device-type (leaf and border) list of tenants, SVIs and VLANs.
- name: "Create the tenant configuration snippets"
block:
- name: "TNT >> Creating per-device type service_tenant data-models"
set_fact:
flt_svc_tnt: "{{ svc_tnt.tnt |create_svc_tnt_dm(svc_tnt.adv, fbc.adv.mlag.peer_vlan, svc_rte.adv.redist.rm_name
|default(svc_tnt.adv.redist.rm_name)) }}"
changed_when: False
check_mode: False
run_once: true
This task only needs to be run once as the data models are created purely from the variable files, not from any of the host_vars. Below is an example of the data model format for a tenant and its VLANs.
{
"bgp_redist_tag": 99,
"l3_tnt": true,
"l3vni": 100003004,
"rm_name": "RM_CONN->BGP65001_RED",
"tnt_name": "RED",
"tnt_redist": true,
"tnt_vlan": 3004,
"vlans": [
{
"create_on_border": true,
"create_on_leaf": false,
"ip_addr": "10.99.99.1/24",
"ipv4_bgp_redist": true,
"name": "red_inet_vl99",
"num": 99,
"vni": 40099
},
{
"ip_addr": "l3_vni",
"ipv4_bgp_redist": false,
"name": "RED_L3VNI",
"num": 3004,
"vni": 100003004
}
]
}
This new data model (flt_svc_tnt) is used to render the svc_tnt_tmpl.j2 template and create the config snippet which will eventually be joined with bse_fbc and deployed to the devices. The template task conditionally only runs on border and leaf switches.
- name: "TNT >> Generating service_tenant config snippets"
template:
src: "{{ ansible_network_os }}/svc_tnt_tmpl.j2"
dest: "{{ ans.dir_path }}/{{ inventory_hostname }}/config/svc_tnt.conf"
changed_when: False
check_mode: False
when: bse.device_name.spine not in inventory_hostname
The same template is used for tenant creation on both the leaf and border switches. The conditional statement at the start of the template dictates whether the leaf or border data model (element 0 or 1 in the list) is iterated through by setting the value of the variable flt_vars dependant on the device it is being run against.
{% if bse.device_name.leaf in inventory_hostname %}{% set flt_vars = flt_svc_tnt[0] %}
{% elif bse.device_name.border in inventory_hostname %}{% set flt_vars = flt_svc_tnt[1] %}
{% endif %}
Interface (svc_intf)
The service_interface.yml variables define single or dual-homed interfaces (including port-channel) either statically or dynamically.
- By default all interfaces are dual-homed LACP ‘active’. The VPC number can not be changed, is always the port-channel number
- Interfaces and port-channels can be assigned dynamically from a pre-defined pool (under svc_intf.adv) or specified manually
- If the tenant (VRF) is not defined for a layer3, SVI or loopback interface it will be created in the global routing table
- If the interface config is the same across multiple switches (like an access port) define one interface with a list of switches
- Only specify the odd numbered switch for dual-homed interfaces, the config for MLAG neighbor is automatically generated
There are 7 pre-defined interface types that can be deployed:
- access: A single VLAN layer2 access port with STP set to ‘edge’
- stp_trunk: A trunk going to a device that supports Bridge Assurance. STP is set to ‘network’
- stp_trunk_non_ba: Same as stp_trunk except that STP is set to ‘normal’ as it is for devices that don’t support BA
- non_stp_trunk: A trunk port going to a device that doesn’t support BPDU. STP is set to ‘edge’ and BPDU Guard enabled
- layer3: A layer3 interface with an IP address. Must be single-homed as MLAG not supported for L3 interfaces
- loopback: A loopback interface with an IP address (must be single-homed)
- svi: To define a SVI the VLAN must exist in service_tenant.yml and not be a VXLAN (must be single-homed)
The intf.single_homed and intf.dual-homed dictionaries hold a list of all single-homed or dual-homed interfaces using any of the attributes in the table below. If there are no single-homed or dual-homed interfaces on the fabric hash out the relevant dictionary.
Key | Value | Mandatory | Information |
---|---|---|---|
descr |
string | Yes | Interface or port-channel description |
type |
intf_type | Yes | Either access, stp_trunk, stp_trunk_non_ba, non_stp_trunk, layer3, loopback or svi |
ip_vlan |
vlan or ip | Yes | Depends on the type, either ip/prefix, vlan or multiple vlans separated by , and/or - |
switch |
list | Yes | List of switches created on. If dual-homed needs to be the odd numbered switch from MLAG pair |
tenant |
string | No | Layer3, svi and loopbacks only. If not defined the default VRF is used (global routing table) |
po_mbr_descr |
list | No | PO member interface description, [odd_switch, even_switch]. If undefined uses PO descr |
po_mode |
string | No | Set the Port-channel mode, ‘on’, ‘passive’ or ‘active’ (default is ‘active’) |
intf_num |
integer | No | Only specify the number, the name and module are got from the fbc.adv.bse_intf.intf_fmt |
po_num |
integer | No | Only specify the number, the name is got from the fbc.adv.bse_intf.mlag_fmt |
The playbook has the logic to recognize if statically defined interface numbers overlap with the dynamic interface range and exclude them from dynamic interface assignment. For simplicity it is probably best to use separate ranges for the dynamic and static assignments.
adv.single_homed: Reserved range of interfaces to be used for dynamic single-homed and loopback assignment
Key | Value | Information |
---|---|---|
first_intf |
integer | First single-homed interface to be dynamically assigned |
last_intf |
integer | Last single-homed interface to be dynamically assigned |
first_lp |
integer | First loopback number to be dynamically used |
last_lp |
integer | Last loopback number to be dynamically used |
adv.dual-homed: Reserved range of interfaces to be used for dynamic dual-homed and port-channel assignment
Key | Value | Information |
---|---|---|
first_intf |
integer | First dual-homed interface to be dynamically assigned |
last_intf |
integer | Last dual-homed interface to be dynamically assigned |
first_po |
integer | First port-channel number to be dynamically used |
last_po |
integer | Last port-channel number to be dynamically used |
Tasks and templates
The format_dm.py filter_plugin method create_svc_intf_dm is run for each inventory host to produce a list of all interfaces to be created on that device. In addition to the service_interface.yml variables it also passes in the interface naming format (fbc.adv.bse_intf) to create the full interface name and hostname to find the interfaces relevant to that device.
- name: "Create the interface configuration snippets"
block:
- name: "SYS >> Creating per-device service_interface data-models"
set_fact:
flt_svc_intf: "{{ svc_intf.intf |create_svc_intf_dm(inventory_hostname, svc_intf.adv, fbc.adv.bse_intf) }}"
changed_when: False
check_mode: False
Below is an example of the data model format for a single-homed and dual-homed interface.
{
"descr": "UPLINK > DC1-BIP-LB01 - Eth1.1",
"dual_homed": false,
"intf_num": "Ethernet1/9",
"ip_vlan": 30,
"stp": "edge",
"type": "access"
},
{
"descr": "UPLINK > DC1-SWI-BLU01 - Gi0/0",
"dual_homed": true,
"intf_num": "Ethernet1/18",
"ip_vlan": "10,20,30",
"po_mode": "on",
"po_num": 18,
"stp": "network",
"type": "stp_trunk"
},
{
"descr": "UPLINK > DC1-SWI-BLU01 - Po18",
"intf_num": "port-channel18",
"ip_vlan": "10,20,30",
"stp": "network",
"type": "stp_trunk",
"vpc_num": 18
}
As with service_tenant the fact flt_svc_intf is used to render the template and produce the configuration snippet. The template is quite simple as it is the exact same format to create interfaces for all devices types.
Route (svc_rte)
BGP peerings, non-backbone OSPF processes, static routes and redistribution (connected, static, bgp, ospf) are configured based on the variables specified in the service_route.yml file. The naming convention of the route-maps and prefix-lists used by OSPF and BGP can be changed under the advanced section (adv) of the variable file.
I am undecided about this role as it goes against the simplistic principles defined in the design goals and used by other roles.
Simple yet functional – Keep it as simple as possible without sacrificing too much functionality. Weigh up whether it is worth allowing a feature to be customizable vs the extra complexity this adds
By its very nature routing is very configurable which leads to complexity due to the number of options and inheritance. I have tried to include the things that I think would most often be used, however there are still things like tags and communities that I decided not to add as they kept taking me deeper and deeper down a rabbit hole of complexity.
In theory all these features should work but due to the number of options and combinations available I have not tested all the possible variations of configuration.
Static routes (svc_rte.static_route)
Routes are added per-tenant with the tenant being the top-level dictionary that routes are created under.
- tenant, switch and prefix are lists to make it easy to apply the same routes across multiple devices and tenants
- For routes with the same attributes (like next-hop) can group all the routes as a list within the one
prefix
dictionary value - Can optionally set next-hop interface, administrative distance and the next hop VRF (for route leaking between VRFs)
Parent dict | Key | Value | Mandatory | Information |
---|---|---|---|---|
n/a | tenant |
list | Yes | List of tenants to create the routes in. Use ‘global’ for the global routing table |
n/a | switch |
list | Yes | List of switches to create all routes on (alternatively can be set per-route) |
route | prefix |
list | Yes | List of routes that all have same settings (gateway, interface, switch, etc) |
route | gateway |
x.x.x.x | Yes | Next hop gateway address |
route | interface |
string | No | Next hop interface, use interface full name (Ethernet), Vlan or Null0 |
route | ad |
integer | No | Set the administrative distance for this group of routes (1 - 255) |
route | next_hop_vrf |
string | No | Set the VRF for next-hop if it is in a different VRF (route leaking between VRFs) |
route | switch |
list | Yes | Switches to create this group of routes on (overrides static_route.switch) |
OSPF (svc_rte.ospf)
An OSPF processes can be configured for any of the tenants or the global routing table. Each OSPF process is enabled on a per-interface basis with summarization and redistribution defined on a per-switch basis.
- The mandatory
process.switch
list defines the switches the OSPF process is enabled on. Router-IDs (RID) are optional, if configured they must match the exact number of switches (1-to-1 relationship) - By default passive interface is globally enabled and disabled on all interfaces. This can be enabled on a per-interface basis
- If authentication is enabled for any one interface it is enabled globally for that whole area
- If BFD is enabled globally it can be disabled on a per-interface basis by setting the OSPF timers for that interface
- Non-mandatory settings only need to be defined if changing the default behavior, otherwise is no need to add the dictionary
Key | Value | Mandatory | Information |
---|---|---|---|
process |
integer or string | Yes | The process can be a number or word |
switch |
list | Yes | List of switches to create the OSPF process on |
tenant |
string | No | The VRF OSPF is enabled in. If not defined uses the global routing table |
rid |
list | No | List of RIDs, must match number of switches (if undefined uses highest loopback) |
bfd |
True | No | Enable BFD globally for all interfaces (disabled by default) |
default_orig |
True, always | No | Conditionally (True) or always advertise a default route (disabled by default) |
Interface, summary and redistribution are child dictionaries of lists under the ospf parent dictionary. They inherit process.switch
unless switch
is specifically defined under that child dictionary.
ospf.interface: Each list element is a group of interfaces with the same set of attributes (area number, interface type, auth, etc)
Key | Value | Mandatory | Information |
---|---|---|---|
name |
list | Yes | List of one or more interfaces. Use interface full name (Ethernet) or Vlan |
area |
x.x.x.x | Yes | Area this group of interfaces are in, must be in dotted decimal format |
switch |
list | No | On which switches to enable OSPF on these interfaces (inherits process.switch if not set) |
cost |
integer | No | Statically set the interfaces OSPF cost, can be 1-65535 |
authentication |
string | No | Enable authentication for the area and a password (Cisco type 7) for this interface |
area_type |
string | No | By default is normal. Can be set to stub, nssa, stub/nssa no-summary, nssa default-information-originate or nssa no-redistribution |
passive |
True | No | Make the interface passive. By default all configured interfaces are non-passive |
hello |
integer | No | Interface hello interval (deadtime is x4), automatically disables BFD for this interface |
type |
point-to-point | No | By default all interfaces are broadcast, can be changed to point-to-point |
ospf.summary: All summaries with the same attributes (switch, filter, area) can be grouped in a list within the one prefix
dictionary value
Key | Value | Mandatory | Information |
---|---|---|---|
prefix |
list | Yes | List of summaries to apply on all the specified switches |
switch |
list | No | What switches to summarize on, inherits process.switch if not set |
area |
x.x.x.x | No | By default it is LSA5. For LSA3 add an area to summarize from that area |
filter |
not-advertise | No | Stops advertisement of the summary and subordinate subnets (is basically filtering) |
ospf.redist: Each list element is the redistribution type, can be ospf xx, bgp xx, static or connected
- Redistributed prefixes can be filtered (
allow
) or weighted (metric
). If the allow list is not set is allow any (empty route-map) - The switch set under the redistribution type is preferred over that set in
process.switch
, there is no merging - Can only have one of each of the redistribution types
connected
andstatic
per-switch. Only the first occurrence is used, with the redistribution type switch always preferred overprocess.switch
Key | Value | Mandatory | Information |
---|---|---|---|
type |
string | Yes | Redistribute either OSPF process, BGP AS (whitespace before process or AS), static or connected |
switch |
list | No | What switches to redistribute on, inherits process.switch if not set |
metric |
dict | No | Add metric to redistributed prefixes. Keys are metric value and values a list of prefixes or keyword (‘any’ or ‘default’). Can’t use metric with a type of connected |
allow |
list,any,default | No | List of prefixes (connected is list of interfaces) or keyword (‘any’ or ‘default’) to redistribute |
BGP (svc_rte.bgp)
Uses the concept of groups and peers with the majority of the settings configured in either. Peer settings take precedence over group
group
holds the global settings for all peers within it. Are automatically created on any switches that peers within it are createdpeer
is a list of peers within the group. If the setting is configured in the group and peer the peer setting will take precedence- The
group.name
andpeer.name
are used in the construction of route-map and prefix-list names switch
is a list of switches (even if single device) to allow for the same group and peers to be created on multiple devicestenant
is a list of VRFs (even if a single VRF) to allow for the same peers to be created in multiple tenants. If the tenant is not specified (dictionary not defined) the group or peer will be added to the default global routing table- Keywords
any
anddefault
can be used instead of a list of prefixes with filtering (inbound/outbound) and redistribution - Non-mandatory settings only need to be defined if changing the default behavior, otherwise is no need to add the dictionary
Set in | Key | Value | Mandatory | Information |
---|---|---|---|---|
group | name |
string | Yes | Name of the group, no whitespaces or duplicate names (group or peer) |
peer | name |
string | Yes | Name of the peer, no whitespaces or duplicate names (group or peer) |
peer | peer_ip |
x.x.x.x | Yes | IP address of the peer |
peer | descr |
string | Yes | Description of the peer |
both | switch |
list | Yes | List of switches (even if is only 1) to create the group and peers on |
both | tenant |
list | No | List of tenants (even if is only 1) to create the peers under |
both | remote_as |
integer | Yes | Remote AS of this peer or if group all peers within that group |
both | timers |
[kl, ht] | No | List of [keepalive, holdtime], if not defined uses [3, 9] seconds |
both | bfd |
True | No | Enable BFD for an individual peer or all peers in group (disabled by default) |
both | password |
string | No | Plain-text password to authenticate a peer or all peers in group (default none) |
both | default |
True | No | Advertise default route to a peer or all peers in the group (default False) |
both | update_source |
string | No | Set the source interface used for peerings (default not set) |
both | ebgp_multihop |
integer | No | Increase the number of hops for eBGP peerings (2 to 255) |
both | next_hop_self |
True | No | Set the next-hop to itself for any advertised prefixes (default not set) |
inbound or outbound: Optionally set under the group or peer to filter BGP advertisements and/ or BGP attribute manipulation
- The naming of the route-maps and prefix-lists are dependant on where they are applied (group or peer)
- All BGP attribute settings are dictionaries with the key being the attribute and the value the prefixes it is applied to
as_prepend
andmed
can be applied to outbound advertisements withweight
andpref
(local preference) applied to those received inbound. If everything is defined the route-map sequence order is BGP_ATTR, deny_specific, allow_specific, allow_all, deny_all
Key | Value | Direction | Information |
---|---|---|---|
weight |
dict | inbound | Keys are the weight and the value a list of prefixes or keyword (‘any’ or ‘default’) |
pref |
dict | inbound | Keys are the local preference and the value a list of prefixes or keyword |
med |
dict | outbound | Keys are the MED value and the values a list of prefixes or keyword |
as_prepend |
dict | outbound | Keys are the number of times to add the ASN and values a list of prefixes or keyword |
allow |
list, any, default | both | Can be a list of prefixes or a keyword to advertise just the default route or anything |
deny |
list, any, default | both | Can be a list of prefixes or a keyword to not advertise the default route or anything |
bgp.tnt_advertise: Optionally advertise prefixes on a per-tenant basis (list of VRFs) using network, summary and redistribution. The switch
can be set globally for all network/summary/redistribution in a VRF and be overridden on an individual per-prefix basis
- network: List of prefixes to be advertised on a per-switch basis (network cmd). If a device is covered by 2 different
network.prefix
statements it will get a combination of them both (merged), so network statements for all prefixes - summary: Group summaries (aggregate-address) with the same attributes (switch and summary_only) within the same list element
- redist: Each list element is the redistribution type (ospf process, static or connected) with the redistributed prefixes weighted (metric) and/or filtered (allow). If the allow list is not set it is allow any (empty route-map). Can only have one each of types connected and static per-switch, the first occurrence is used. The switch set under the redistribution type is preferred over that set in
process.switch
, is no merging
Set in | Key | Value | Mand | Information |
---|---|---|---|---|
tnt_advertise | name |
string | Yes | A single VRF that is being advertising into (use ‘global’ for global routing table) |
all | switch |
list | Yes | What switches to redistribute on, inherits process.switch if not set |
network/summary | prefix |
list | Yes | List of prefixes to advertise |
summary | filter |
summary-only | No | Only advertise the summary, suppress all prefixes within it (disabled by default) |
redist | type |
string | Yes | Redistribute ospf_process (whitespace before process), static or connected |
redist | metric |
dict | No | Add metric to redistributed prefixes. Keys are the MED value and values a list of prefixes or keyword (‘any’ or ‘default’). Cant use metric with connected |
redist | allow |
list, any, default | No | List of prefixes (can use ‘ge’ and/or ‘le’), interfaces (for connected) or keyword (‘any’ or ‘default’) to redistribute. Placed after the metric in the RM sequence |
Advanced (svc_rte.adv)
Advanced settings allow the changing of the default routing protocol timers and naming convention of the route-maps and prefix-lists used for advertisement and redistribution. The only naming restrictions are the keywords that are used in the filter plugins logic to swap the keyword for another values.
parent dict | Key | Value | Information |
---|---|---|---|
adv | ospf_hello |
integer | Default hello used for all OSPF interfaces (dead time x4 of this) |
adv | bgp_timers |
[keepalive, holdtime] | Default timers used by BGP groups for all peers |
bgp_naming | rm_in |
RM_name_IN | Route-map Inbound: ‘name’ is replaced by the BGP group or peer name |
bgp_naming | rm_out |
RM_name_OUT | Route-map Outbound: ‘name’ is replaced by the BGP group or peer name |
bgp_naming | pl_in |
PL_name_IN | Prefix-list Inbound: Only created if dont use ‘pl_deny’, pl_allow' or pl_default' |
bgp_naming | pl_out |
PL_name_OUT | Prefix-list Outbound: Only created if dont use ‘pl_deny’, pl_allow' or pl_default' |
bgp_naming | pl_wght_in |
PL_name_WGHTval_IN | Prefix-list for weight (Inbound): ‘val’ is swapped for the weight value |
bgp_naming | pl_pref_in |
PL_name_PREFval_IN | Prefix-list for local Pref (Inbound): ‘val’ is swapped for the local pref value |
bgp_naming | pl_med_out |
PL_name_MEDval_OUT | Prefix-list for MED (Outbound): ‘val’ is swapped for the metric value |
bgp_naming | pl_aspath_out |
PL_name_AS+val_OUT | Prefix-list for AS prepend (Outbound): ‘val’ swapped for how many AS added |
dflt_pl | pl_deny |
PL_DENY_ALL | Prefix-list used in RM when all traffic is denied (‘any’ keyword under deny) |
dflt_pl | pl_allow |
PL_ALLOW_ALL | Prefix-list used in RM when all traffic is allowed (‘any’ keyword under allow) |
dflt_pl | pl_default |
PL_DEFAULT | Prefix-list used in RM when default route is matched (‘default’ under allow) |
redist | rm_name |
RM_src->dst | ‘src’ and ‘dst’ swapped to the source and destination of the redistribution |
redist | pl_name |
PL_src->dst | ‘src’ and ‘dst’ swapped to the source and destination of the redistribution |
redist | pl_metric_name |
PL_src->dst_MEval | ‘val’ swapped for the metric or MED value used in the redistribution |
Tasks and templates
The filter_plugin method create_svc_rte_dm is run for each inventory device to produce a data model of the routing configuration for that device. Deploying any of the routing options (BGP, BGP tenant advertisement, OSPF, static routes) is optional so has to be fed into the plugin separately with the Ansible default filter so as not to fail if it were empty.
- name: "Create the routing configuration snippets"
block:
- name: "SYS >> Creating per-device service_route data-models"
set_fact:
flt_svc_rte: "{{ inventory_hostname |create_svc_rte_dm(svc_rte.bgp.group |default (), svc_rte.bgp.tnt_advertise |default (),
svc_rte.ospf |default (), svc_rte.static_route |default (), svc_rte.adv, fbc) }}"
changed_when: False
check_mode: False
The method used for creating the routing data models is a lot longer and more complex than service_tenant or service_interface methods. To try and avoid repetitive code a lot of the complexity around building prefix-lists and route-maps data models is put into separate methods.
- create_bgpattr_rm_pfx_lst: The prefix-list and route-map for BGP attribute associated prefixes (weight, local pref, med & AS-path)
- create_allowdeny_rm_pfx_lst: Prefix-list and route-map (order deny/allow_spec, allow_any/deny_any) for allowed & denied prefixes
- create_redist_rm_pfx_lst: The prefix-list and route-map for prefix redistribution and MED/metric manipulation
The outcome is a list of seven per-device data models that are used by the svc_rte_tmpl.j2 template.
- all_pfx_lst: List of all prefix-lists with each element in the format [name, seq, permission, prefix]
- all_rm: List of all route-maps with each element in the format [name, seq, permission, prefix, [attribute, value]]. If no BGP attributes are set in the RM the last entry in the list will be [null, null]
- stc_rte: Per-VRF dictionaries (VRF is the key) of lists of static routes with interface and/or gateway, optional AD and destination VRF
- group: Dictionaries of BGP groups (group is the key) that have peers on this device. The value is dictionaries of any group settings
- peer: Dictionaries of tenants (VRFs) containing the following nested dictionaries:
- peers: Dictionary of peers (key is the peer) with the value being dictionaries of the peers settings
- network: List of networks to be advertised by BGP
- summary: Dictionary of summaries with the key being the prefix and value either null (doesn’t suppress) or summary-only
- redist: Two dictionaries of the route-map name (rm_name) and redistribution type (connected, static, etc)
- ospf_proc: Dictionary of VRFs (key) and the OSPF process settings for each VRF (settings configured under the process)
- ospf_intf: Dictionary of interfaces (key) that have OSPF enabled, the values are the interface specific OSPF settings
Running the roles
Due to the declarative nature of the playbook and inheritance between roles there are only a certain number of combinations that the roles can be deployed in. It can take 3 to 4 minutes to deploy the full configuration to NXOS when including the service roles so the Napalm default timeout has been increased to 240 seconds.
tag | Description |
---|---|
pre_val |
Checks that the var_file contents are of a valid format |
bse_fbc |
Generates, joins and applies the base, fabric and inft_cleanup configuration snippets |
bse_fbc_tnt |
Generates, joins and applies the base, fabric, inft_cleanup and tenant config snippets |
bse_fbc_intf |
Generates, joins and applies the base, fabric, tenant, interface and inft_cleanup config snippets |
full |
Generates, joins and applies the base, fabric, tenant, interface, inft_cleanup and route config snippets |
rb |
Reverses the changes by applying the rollback configuration |
diff |
Prints the differences to screen (is still also saved to file) |
- diff tag can be used with bse_fbc_tnt, bse_fbc_intf, full or rb to print the configuration changes to screen
- Changes are always saved to file no matter whether diff is used or not
- -C or –check-mode will do everything except actually apply the configuration
pre-validation: Validates the contents of variable files defined under var_files. Best to use dummy host file instead of dynamic inventory
ansible-playbook PB_build_fabric.yml -i hosts --tagpost_val
Generate the complete config: Creates config snippets, assembles them in config.cfg, compares against device config and prints the diff
ansible-playbook PB_build_fabric.yml -i inv_from_vars_cfg.yml --tag 'ful, diff '-C
Apply the config: Replaces current config on the device with changes made automatically saved to ~/device_configs/diff/device_name.txt
ansible-playbook PB_build_fabric.yml -i inv_from_vars_cfg.yml --tagfull
All roles can be deployed individually to just to create the config snippet files, no connections are made to devices or changes applied. The merge
tag can be used in conjunction with any combination of these role tags to non-declaratively merge the config snippets with the current device configuration rather than replacing it. As the L3VNIs and interfaces are generated automatically at a bare minimum the variable files will still need current tenants and interfaces as well as the advanced variable sections.
tag | Description |
---|---|
bse |
Generates the base configuration snippet |
fbc |
Generates the fabric and intf_cleanup configuration snippets |
tnt |
Generates the tenant configuration snippet saved to device_name/config/svc_tnt.conf |
intf |
Generates the interface configuration snippet saved to device_name/config/svc_intf.conf |
rte |
Generates the route configuration snippet saved to device_name/config/svc_rte.conf |
merge |
Napalm merges new and current config, must be run with other role tag or tags |
- bse, fbc, tnt, intf and rte will only generate the config snippet and save it to file. No connections are made to devices or changes applied
- check-mode and diff can be used with
merge
the same as with config_replace with the diff still always saved to file
Generate the config: Creates the tenant, interface and routing config snippets can and saves them to file
ansible-playbook PB_build_fabric.yml -i inv_from_vars_cfg.yml --tag'tnt,intf,rte'
Apply tenants and interfaces non-declaratively: Add additional tenant and routing objects by merging their config snippets with the devices config. The diffs for merges are simply the lines in the merge candidate config so wont be as true as the diffs from declarative deployments
ansible-playbook PB_build_fabric.yml -i inv_from_vars_cfg.yml --tagtnt,rte,merge,diff