Putting It All Together
The following use cases are examples of different network designs that utilize different methods and techniques of Apstra Freeform features discussed. These examples are meant to help you better understand how the features of Apstra and Freeform could be used to address various networking use cases.
Use Case #1 - Small Version of London Tube - CloudLabs Topology
This use case uses a small version of the London underground system as a map to design a network topology from arbitrary switches. The concept here is to create a very simple network design using Jinja templates and a property set. This use case demonstrates how you can create a complete network design using uncomplicated Jinja templating and property sets.
This use case is available as a hands-on lab using Juniper Apstra Cloudlabs. There is also a GitHub repository that includes the Jinja files and JSON property sets you can reference, contribute to, or even fork.
-
Topology of the environment.
The design in the blueprint
All system links showing tags and IP addresses assigned via the topology editor/API.
-
End state rendered configurations. For the Bond-Street device here is an example of the rendered configuration.
interfaces Block policy-options Block protocols Block interfaces { replace: xe-0/0/0 { unit 0 { interfaces { replace: xe-0/0/0 { unit 0 { description "facing_oxford-circus:xe-0/0/1"; family inet { address 192.168.0.2/31; } } } replace: xe-0/0/1 { unit 0 { description "facing_green-park:xe-0/0/1"; family inet { address 192.168.0.8/31; } } } replace: lo0 { unit 0 { family inet { address 10.0.0.2/32; }
protocols { lldp { port-id-subtype interface-name; port-description-type interface-description; neighbour-port-info-display port-id; interface all; } replace: rstp { bridge-priority 0; bpdu-block-on-edge; } bgp { group external-peers { type external; export send-direct; neighbor 192.168.0.3 { peer-as 22; export add-med-110; } neighbor 192.168.0.9 { peer-as 86; export add-med-177; } } } } routing-options { autonomous-system 47; }
protocols { lldp { port-id-subtype interface-name; port-description-type interface-description; neighbour-port-info-display port-id; interface all; } replace: rstp { bridge-priority 0; bpdu-block-on-edge; } bgp { group external-peers { type external; export send-direct; neighbor 192.168.0.3 { peer-as 22; export add-med-110; } neighbor 192.168.0.9 { peer-as 86; export add-med-177; } } } } routing-options { autonomous-system 47; }
-
Show Config Templates
Config Template system.jinja Description system { host-name {{hostname}}; }
This template uses the builtin “hostname” variable from the device context to set the system host-name to this. Config Template interfaces.jinja Description {% set this_router=hostname %} interfaces { {% for interface_name, iface in interfaces.iteritems() %} replace: {{ interface_name }} { unit 0 { description "{{iface['description']}}"; family inet { address {{iface['ipv4_address']}}/{{iface['ipv4_prefixlen']}}; } } } {% endfor %} replace: lo0 { unit 0 { family inet { address {{ property_sets.data[this_router]['loopback'] }}/32; } } } }
Set the variable this_router=hostname
For loop that walks through the interfaces and inserts the proper interface stanza syntax for junos including the description of the interface and the family inet address and prefix length from the device context. You can populate these values via the links in topology editor.
This inserts the property_set data for this router’s loopback address into the loopback stanza.
Config Template protocols.jinja Description {% set this_router=hostname %} policy-options { policy-statement send-direct { term 1 { from protocol direct; then accept; } } {% for interface_name, iface in interfaces.iteritems() %} {% set link_med = iface.link_tags[0] %} {# this may create multiple identical policy-statements, but JunOS is smart enough to squash them. #} policy-statement add-med-{{ link_med }} { from { route-filter 0.0.0.0/0 longer; } then { metric add {{ link_med }}; } then { accept } } {% endfor %} } protocols { lldp { port-id-subtype interface-name; port-description-type interface-description; neighbour-port-info-display port-id; interface all; } replace: rstp { bridge-priority 0; bpdu-block-on-edge; } bgp { group external-peers { type external; export send-direct; {% for interface_name, iface in interfaces.iteritems() %} neighbor {{ iface.neighbor_interface.ipv4_address }} { {% set peer_hostname=iface.neighbor_interface.system_hostname %} peer-as {{ property_sets.data[peer_hostname]['asn'] }}; export add-med-{{ iface.link_tags[0] }}; } {% endfor %} } } } routing-options { autonomous-system {{ property_sets.data[this_router]['asn'] }}; }
Set the variable this_router = hostname from the device context.
Set a policy-options stanza to send all directly attached routes.
Walk the interface tree.
Set the variable link_med to the tag set on the interface via the topology.
Set the LLDP parameters
Set the RSTP parameters
Create the bgp stanza
Group external-peers
Type of external (ebgp)
Export policy of send-direct
Walk the interface tree and insert neighbors for any neighbor that has an ipv4 address. Set the peer_hostname variable to the neighbor interfaces hostname.
Grab the peer as from the property_sets data “asn”
Peer-as stanza is set with an export policy of add-med-tags
Set the routing-options stanza with the autonomous system from the property sets asn value.
-
Property Set
Property Set "data" Description { "bond-street": { "asn": 47, "loopback": "10.0.0.2" }, "green-park": { "asn": 86, "loopback": "10.0.0.4" }, "tottenham-court-road": { "asn": 48, "loopback": "10.0.0.3" }, "leicester-square": { "asn": 137, "loopback": "10.0.0.5" }, "piccadilly-circus": { "asn": 23, "loopback": "10.0.0.1" }, "oxford-circus": { "asn": 22, "loopback": "10.0.0.0" } }
The property set is straightforward represented as a list of dictionaries that include the following:
-
Name of the system or station is the key, and inside that key are two parameters
-
Asn which is the autonomous system number for BGP peering
-
Loopback which is the loopback address of the system/station to peer with.
-
Use Case #2 - Tags and Property Sets to Drive Day 2 Configuration
The small topology shown below has been constructed in the Freeform topology editor. It has three switches and two external systems named ESXi-1 & ESXi-2. The links facing the two hosts are tagged with esxBlueTrunk and esxRedTrunk respectively. The goal of this use case is to show how one way you can use the Freeform feature to dynamically build the switch trunk facing the ESXi hosts, determined by the entries in the Property Set and the tags assigned in the topology.
The role of the tag in this instance is to indicate where the trunk should be configured / created.
The role of the Property Set in this instance is to hold the relevant data required to configure the trunk members, the VLANs, and the IRBs.
This example describes the power of utilizing a carefully crafted Configuration Template (Jinja2), Tags and Property Sets to build configuration, without the need to re-craft the Config Template. As new tags are assigned to the topology, or new VNs are assigned to the property set, the associated config will be dynamically built for these trunks.
End-state configuration
For the interface tagged with esxBlueTrunk, the final Junos configuration will include:
Interface Block | IRB Block | VLAN Block |
---|---|---|
interfaces { ae2 { description esxBlueTrunk unit 0 { family ethernet-switching { interface-mode trunk vlan { members [ vn99 vn100 vn101 ] } } } } |
irb { unit 99 { family inet { mtu 9000; address 1.1.99.1/24; } } unit 100 { family inet { mtu 9000; address 1.1.100.1/24; } } unit 101 { family inet { mtu 9000; address 1.1.101.1/24; } } } } |
vlans { vn99 { vlan-id 99; description vMotionVN-99; l3-interface irb.99; } vn100 { vlan-id 100; description storageVN-100; l3-interface irb.100; } vn101 { vlan-id 101; description mgmtVN-101; l3-interface irb.101; } } |
esxTrunk Property Set
The Property Set includes the necessary details needed to build the Junos Trunk config
esxTrunk Property Set | Description |
---|---|
The esxTrunk Property Set (to the left) has been constructed as a dictionary of dictionaries for good reason: to enable recursive lookup. The esxBlueTrunk dictionary has been expanded to show values 99, 100, 101 which in this instance are used as both VLAN IDs, and as a key to the dictionary below it. The dictionaries below provide key : value pairs for the subnet, the gateway, and the description. In this example, the subnet is not required and exists purely for reference. With the data organized in this way, the Config Template has been designed to recurse through two data structures to search for matching tags:
When the tag in both data sets match, the Config Template produces the desired configuration. By cross-referencing the Property Set to the left with the Junos configuration output above, you can see that:
Both the esx(Red / Pink)Trunk, hold similar information as the esxBlueTrunk To enable efficient recursive walking of both the Property Set to the left and the tags assigned to the links in the topology, the tags have been purposely assigned the same values
|
Jinja2 base Config Template State Machine
The Config Template esxTrunks.jinja flow is described below.
Jinja2 base Config Template
Config Template | Description |
---|---|
{% set Rendered_VNs = {} %} {% for ps_tag in property_sets.esxTrunk %} {% for interface_name, iface in interfaces.iteritems() %} {% if ((iface.link_tags) and (ps_tag in iface.link_tags)) %} interfaces { {{interface_name}} { description {{ ps_tag }} unit 0 { family ethernet-switching { interface-mode trunk vlan { members [ {% for vlan_id in property_sets.esxTrunk[ps_tag] %} {% set _ = Rendered_VNs.update({vlan_id: ps_tag}) %} vn{{ vlan_id }} {% endfor %} ] } } } } {% endif %} {% endfor %} {% endfor %} irb { {% for vn in Rendered_VNs %} {% set tag = Rendered_VNs[vn] %} unit {{ vn }} { family inet { mtu 9000; address {{ property_sets.esxTrunk[tag][vn]['gateway'] }}; } } {% endfor %} } } vlans { {% for vn in Rendered_VNs|unique %} {% set tag = Rendered_VNs[vn] %} vn{{ vn }} { vlan-id {{ vn }}; description {{ property_sets.esxTrunk[tag][vn]['description'] }}-{{ vn }}; l3-interface irb.{{ vn }}; } {% endfor %} } |
A global dictionary to store VNs to render Start by traversing the esxTrunk Property Set shown above. The first value retrieved, and stored in the variable ps_tag (Property Set tag) is one of the strings:
Now traverse or ‘iterate’ through the interfaces in the topology If an interface link_tag exists and the ps_tag is in the list of interface link tags, the condition has been met where trunk configuration will be rendered Start outputting the interfaces block . Output the interface_name where ps_tag equals the interface link tag . Optionally assign a description, although Freeform will have already assigned this description from the topology Set the Unit number and associated config to describe the trunk Set the trunk member Traverse the esxTrunk Property Set using the ps_tag to retrieve the VLAN IDs Enter each VLAN ID in the dictionary declared at line 1for later use Output the vn VLAN ID detailed in the esxTrunk Property Set End the for loop when there are no more entries in the esxTrunk Property Set End the if statement above End the for statement above End the for statement above Start outputting the irb block Traverse the Rendered_VNs dictionary For readability, set the variable tag to the tag stored in the dictionary Set the unit number using the vlan_id Set the gateway address stored in the esxTrunk PropertySet using the tag, vn and the key ‘gateway’ to access the stored string End the for statement above Starer outputting the vlans block As per the irb block above, traverse the Rendered_VNs dictionary, For readability, set the variable tag to the tag stored in the dictionary Set the vn number Set the vlan_id ID Set the description as required Set the layer3 irb number End the for statement above |
The Jinja2 based Config Template shown below, utilizes both the assigned tag and the Property Set to build the required config.
Use Case #3 - Advanced Example Using CRB - CloudLabs Topology
This final use case is a complete CRB example that has been written by Apstra Engineering. This example is built completely with static Jinja templates and all of the data is in property sets for the network/devices. This allows the users of this CRB example to operate / expand / change the network just by editing property-sets and allowing you to not touch the underlying Jinja templates. The purpose of this use case is to give you an example of the art of the possible and to demonstrate the flexibility and power of the Freeform feature. All the configuration templates, property sets, and jinja templates and functions have embedded documentation to help you understand the use and function.
This use case is available to you as a fully deployed hands-on sandbox using Juniper Apstra Cloudlabs. There is also a GitHub repository that includes all the same files. you can use this as examples of how to perform certain functions to make your own advanced Config Templates for your use case https://www.juniper.net/us/en/forms/apstra-free-trial.html