Ansible Two-Tier Template
When executing an AOS-CX Ansible playbook, Jinja2-based template files are used to create AOS-CX switch configurations.
Table of contents
Overview
Template files are called by the playbook to build a complete AOS-CX configuration file for each target switch. The template file’s Jinja2 syntax in combination with variables defined in the inventory file support the building of dynamic and complex switch configurations.
Multiple template files can be used to build configurations based on switch roles or other criteria. The playbook selects the template assigned to a switch host in the inventory file using the config_template variable’s value. The variable can be assigned directly to a host or it can be inherited from a group.
Template Syntax
Jinja2 syntax supports the use of variable replacement values, loops, and conditional statements.
This chapter describes basic formatting of the Jinja2-based template file. The sample template can be modified or used as a reference to build new templates.
The core and access template files in this example deployment can be used unmodified when deploying CX 8325-32C core switches and any 83xx-series CX model as access switches.
Standard CLI Statements
Any standard AOS-CX switch configuration statement can be added to a template file. The configuration line is copied to the switch exactly as it appears in the file. Strict adherence to AOS-CX syntax is required, and it is best practice to use a full and complete configuration statement. Configuration statements should be placed in the correct context in the file. AOS-CX indentation uses four spaces.
The following example uses standard AOS-CX CLI configuration statements in a template file:
profile l3-agg
router pim
enable
active-active
https-server vrf mgmt
Simple Variable Substitution
The template file can substitute a single variable value in place of a static value in a configuration statement. Variable values are assigned in the inventory file. To use a variable’s value in the template file, place the inventory file’s variable name inside two curly braces: {{variable_name}}.
By substituting static values with a variable, the same template file can be used to configure multiple switches that require different values in config statements. In addition to applying unique values such as IP addresses, variables enable the same template to be used across different administrative and geographic boundaries. For example, inventory variables can be defined for timezones, SNMP information, DNS servers, NTP servers, and other configuration components that may have different values based on location.
The flexibility of a template file is increased as you increase the number of variables. Each administrator must balance the additional complexity of inventory and template files when increasing the number of variables for added flexibility.
Variable values can be assigned directly to a host in the inventory file, or they can be inherited from a parent group. The following example of AOS-CX configuration statements uses simple variable substitutions in a template file.
hostname {{hostname}}
clock timezone {{timezone}}
interface lag {{vsx_isl_lagid}}
no shutdown
description VSX-ISL-LAG
no routing
vlan trunk native 1 tag
vlan trunk allowed all
lacp mode active
Note: A playbook error will occur if a template references a variable name that is not defined in the inventory file.
Iteration Over a List of Values
When an inventory variable is assigned a list of values, the template file can iterate over each member of the list using a for loop to generate multiple AOS-CX configuration lines.
Iteration over a simple list is helpful when generating multiple lines of configuration that contain a single modified value. For example, generating multiple AOS-CX configuration statements to define a set of NTP or DNS servers. Iteration over a dictionary enables more complex configuration, such as creating a set of VLAN interfaces, where each interface requires a set of additional information to build the configuration, such as VLAN ID, IP address and, active gateway assignments.
Examples of a simple list and dictionary-based iteration are provided below.
Example 1: Iteration Over a Simple List
The example inventory file defines a simple comma-delimited list for DNS servers at the DC-RSV group level as follows:
dns_servers: [10.2.120.98, 10.2.120.99]
The playbook calls upon the template file which iterates over this list and generates the proper AOS-CX config using the following syntax:
p{% for server in dns_servers %}
ip dns server-address {{server}} vrf mgmt
{% endfor %} p
In the above example, the for statement reads one list member at a time from the dns_servers variable, and assigns it to the local server variable. The server variable value is used to complete the AOS-CX configuration statement located between the beginning and end of the for loop, by replacing the variable name with its assigned value on each iteration. This process is repeated until all list members are read and each configuration line is written to the switch config file. In this example, the first list value of 10.2.120.98 is assigned to the server variable to generate the first configuration line from the loop. After the second list value of 10.2.120.99 is read and another configuration line is generated, the for loop is complete, since no additional list members are present.
The following AOS-CX configuration lines are generated from the above for loop:
ip dns server-address 10.2.120.98 vrf mgmt
ip dns server-address 10.2.120.99 vrf mgmt
Example 2: Iteration Over a Dictionary
The example inventory file defines a dictionary at the RSVDC-CORE1-1 host level to assign VLAN interface values (DC-RSV > core > RSVDC-CORE1-1). In the example below, there are two entries in the host_vlans dictionary, and each entry contains a list of associated variable values that are required to define a VLAN interface such as id, name, ip_address, etc.
host_vlans:
- id: 101
name: PROD-WEB
ip_address: 10.12.101.2
active_gateway_mac: 02:00:0a:01:65:01
active_gateway_ip: 10.12.101.1
- id: 102
name: PROD-DB
ip_address: 10.12.102.2
active_gateway_mac: 02:00:0a:01:65:01
active_gateway_ip: 10.12.102.1
The playbook calls upon the template file to iterate over the host_vlans dictionary and generate the proper AOS-CX VLAN interface config using the following syntax:
{% for vlan in host_vlans %}
interface vlan {{vlan.id}}
description {{vlan.name}}
ip mtu 9198
ip address {{vlan.ip_address}}/{{vlan.mask | default('24', true)}}
active-gateway ip mac {{vlan.active_gateway_mac}}
active-gateway ip {{vlan.active_gateway_ip}}
ip ospf 1 area {{ospf_area}}
ip igmp enable
ip pim-sparse enable
{% endfor %}
In the above example, the for statement reads one dictionary member at a time from the host_vlans array. Each array entry is read as a set of variables and assigned to the local vlan variable. The individual sub-variable values are referenced by concatenating the parent variable (vlan) and one of the sub-variable names (i.e., id, name, ip_address, etc.) with a dot separator. For example, vlan.name references the VLAN name value of the current dictionary entry read into the vlan variable.
This method supports writing complex configuration, where multiple variable values are associated with a single logical AOS-CX configuration area.
When the first dictionary entry is read, the following variable value assignments are made:
- vlan.id: 101
- vlan.name: PROD-WEB
- vlan.ip_address: 10.12.101.2
- vlan.active_gateway_mac: 02:00:0a:01:65:01
- vlan.active_gateway_ip: 10.12.101.1
Note: The for loop contains additional logic, where if no vlan.mask variable is defined, the default value of 24 is substituted. It also contains reference to the ospf_area variable that is an inherited value of 0.0.0.0 for all hosts assigned at the DC-RSV group level.
The following AOS-CX configuration is generated by the template using the example for loop:
interface vlan 101
description PROD-WEB
ip mtu 9198
ip address 10.12.101.2/24
active-gateway ip mac 02:00:0a:01:65:01
active-gateway ip 10.12.101.1
ip ospf 1 area 0.0.0.0
ip igmp enable
ip pim-sparse enable
interface vlan 102
description PROD-DB
ip mtu 9198
ip address 10.12.102.2/24
active-gateway ip mac 02:00:0a:01:65:01
active-gateway ip 10.12.102.1
ip ospf 1 area 0.0.0.0
ip igmp enable
ip pim-sparse enable
Conditional Statements
Jinja2 supports evaluating conditional if/then/else statements, which can be used when generating configuration statements.
The example below contains two conditional statements, a second nested inside the first:
{% if vsx_keepalive_int is defined %}
interface {{vsx_keepalive_int}}
no shutdown
description VSX KA
{% if vsx_role == 'primary' %}
ip address {{vsx_keepalive_ip_primary}}/31
{% else %}
ip address {{vsx_keepalive_ip_secondary}}/31
{% endif %}
{% endif %}
In the example above, the conditional statement {% if vsx_keepalive_int is defined %} checks if the vsx_keepalive_int variable is defined for the current host for which the configuration is being generated. If not, the entire stanza above is ignored and no VSX keepalive interface configuration is generated for the switch. If the variable is defined, the configuration is generated using variable substitution.
The second conditional evaluation is designed to assign the correct IP address to the VSX keepalive interface based on the role of the switch in the VSX pair. Both the primary and secondary keepalive IP addresses are defined in the inventory file. If the current switch processed by the playbook is the primary switch, then the vsx_keepalive_ip_primary variable value is assigned. If the switch is not the primary, then the vsx_keepalive_ip_secondary variable value is assigned.