Lab Guide - Resource Allocation in Apstra Freeform

Modify Configuration templates to use Resource Allocation

General changes needed

We have created all the resources required to implement the IP and ASN addressing schema for our topology and now need to modify the Jinja2 Configuration Templates to switch from using the 'data' Property Set to using Resource Allocation.
To summarize, we have created the following Resources:

  • IP link IP addresses (System-to-System)

  • Loopback IP addresses

  • System ASN numbers

Let’s now take some Jinja2 and make the modifications necessary to use these elements.

The good news is that not much work is needed to modify the system to use the Link IP addresses. Specifically, NO work is needed!!! The Link IP addresses were updated in the same locations that they were before so literally nothing needs to be done.

Jinja2 helper functions

We will be utilizing some built-in functions in our Jinja2 code to find the values of resources and use them in our Config Template. These functions are provided and documented for you in the Apstra UI. The best way to find them is to open a Config Template and go to the editor.

  • Navigate to [Staged] > [Catalog] > [Config Templates]. Click on the interfaces.jinja hyperlink, then the "edit icon" edit icon

Once it’s open, click on the link [ ? | Jinja2 function reference]
Jinja2 function reference

This page will open in a new tab in your browser: Jinja2 helper page Open the last item called get_resource_value and examine the function details. we will use this function in the next 2 sections to replace the existing Jinja2 that utilized the Property Set.

Loopback IP addresses

The loopback IP addresses (and ASNs) were previously stored in a Property Set and were referred to in the Config Template called interfaces.jinja to build Junos config. It is this file we will modify to build the same config but with Resource Allocation, and in particular, this config block:

		replace: lo0 {
        			unit 0 {
           			 family inet {
                		address {{ property_sets.data[this_router | replace('-', '_']['loopback'] }}/32;
            }
        }
    }

Before we start to modify the Jinja above, we need to determine where the loopback IP has been placed in the data set Apstra has generated for this device, and this is in the Device Context tree. We then need to link this to the Jinja2 helper function (mentioned above) function.get_resource_value(<location>). In this example, the argument sent to this function described as <location>, is the location in the Device Context where the Resource generator has placed the loopback IP for this device. There are a number of variables which are described below. Let’s open the 3 pane editor and figure this out.

  1. Open the 3 pane editor for interfaces.jinja

    • Navigate to [Staged] > [Catalog] > [Config Templates] and click on the interfaces.jinja hyperlink. Now click the "edit icon" edit icon

  2. In the 'Update Config Template' window, you can display the Junos config preview (as built by your Config Template (left pane) for the selected System, by selecting a System and clicking [▷ Preview]

  3. Display the device context by clicking on [Device Context]

  4. Open the ▶ resources [ …​ ] branch of the tree to examine the contents. examine resources tree

  5. After inspecting the resources tree we can view the following: resource_tree_device_context

  6. This is what we need to pass to our function.get_resource_value(). You can see there is a nesting of resources, starting with the top level group tube-resources and then progressing lower to the system group. Once we passed the correct nested data structure we then pass the actual resource which is called loopback_generator.

    {% set loopback_ipv4 = function.get_resource_value(resources, 'loopback_generator', 'tube-resources','systems') %}

Let’s now look at what the final Jinja2 should look like for this modification:

{% set loopback_ipv4 = function.get_resource_value(resources, 'loopback_generator', 'tube-resources','systems') %}
    replace: lo0 {
        unit 0 {
            family inet {
                address {{ loopback_ipv4 }};
            }
        }
    }

Edit the Jinja2 Config Template in the left pane directly (you’re in edit mode) and remember to hit the [▷ Preview] icon to reload and preview your changes and ensure it renders correctly, before you click the [ Update ] button.

{% set this_router=hostname %}
interfaces {
{% for interface_name, iface in interfaces.iteritems() %}
    replace: {{ interface_name }} {
        unit 0 {
            description "{{iface['description']}}";
    {% if iface['ipv4_address'] and iface['ipv4_prefixlen'] %}
            family inet {
                address {{iface['ipv4_address']}}/{{iface['ipv4_prefixlen']}};
            }
    {% endif %}
        }
    }
{% endfor %}
{% set loopback_ipv4 = function.get_resource_value(resources, 'loopback_generator', 'tube-resources','systems') %}
    replace: lo0 {
        unit 0 {
            family inet {
                address {{ loopback_ipv4 }};
            }
        }
    }
}

ASN numbers

Now we need to do the same thing for the ASN numbers.

  1. Open the 3 pane editor for protocols.jinja

    • Navigate to * [Staged] > [Catalog] > [Config Templates] and click on the protocols.jinja hyperlink. Now click the "edit icon" edit icon

  2. Display the preview for a system by click on [▷ Preview]

  3. Display the Device Context by clicking [ Device Context ]

  4. Open the resources tree to examine the resources. examine resources tree

  5. After inspecting the resources tree you can view the following: asn_resource_tree_device_context

  6. This is what we need to pass to our function.get_resource_value(). You can see there is a nesting of resources, starting with the top level group tube-resources and then progressing lower to the system group. Once we passed the correct nested data structure we then pass the actual resource which is called asn_generator as follows:

{% set my_asn = function.get_resource_value(resources, 'asn_generator', 'tube-resources','systems') %}

ASN number for the local system.

The first Jinja2 code we will develop will locate the local systems ASN. This function is basically the same function as above. We will replace the following area in the config template:

routing-options {
    autonomous-system  {{ property_sets.data[this_router | replace('-', '_')]['asn'] }};
}

with the following lines:

{% set my_asn = function.get_resource_value(resources,'asn_generator', 'tube-resources','systems') %}
routing-options {
    autonomous-system  {{ my_asn }};
}

Edit the Jinja2 Config Template in the left pane directly (you’re in edit mode) and remember to hit the [▷ Preview] icon to reload and preview your changes and ensure it renders correctly, before you click the [ Update ] button.

This is what should show up as the difference between the two (using the handy config template preview): my_asn_changes

  • Make the above changes to the Jinja2 for the config template protocols.jinja

ASN number for the neighbor systems.

Now that we have the local system ASN identified and working, we can move on to writing the Jinja2 to find the neighbor ASN interface using the built-in functions.

  1. Open the 3 pane editor for protocols.jinja

    • Navigate to * [Staged] > [Catalog] > [Config Templates] and click on the protocols.jinja hyperlink. Now click the "edit icon" edit icon

  2. Display the preview for a system by clicking on [▷ Preview]

  3. Display the device context by clicking on [ Device Context ] button

  4. The important logic to be removed and modified from the file is enclosed in lines 56-58 of the config template. remove_asn_code

  5. This logic walks the interface tree and finds the associated peer hostname and then looks in the property set for the hostnames ASN. In this case we need to create some new code, and we will need to use a variable in the for loop, to do this we will introduce a namespace. We call the namespace bgp_ns and define it in line 50. The Jinja2 code is:

    {% set bgp_ns = namespace(neighbor_asn=None) %}
  6. Now for the code needed to find the neighbor ASN:

    {% set bgp_ns.neighbor_asn = function.get_resource_value(all_resources.get(neighbor_interface.system_id), 'asn_generator', 'tube-resources', 'system') %}
                    neighbor {{ neighbor_interface.ipv4_address }} {
                    peer-as {{ bgp_ns.neighbor_asn }};
  7. As you can see from this code we pass to our function.get_resource_value() the same nesting of resources, starting with the top level group tube-resources and then progressing lower to the system group. Once we passed the correct nesting structure we then pass the actual resource which is called asn_generator. The biggest change here is that we are using all_resources.get(neighbor_interface.system_id) which is the value of the neighbor_interface’s system_id. It may be a bit tricky the first time you see this, but you can look at the all_resources tree to look into the nested structure we used to get the value.

  • Replace the Jinja2 for lines 56-58 in the original config template with the above lines to make a new version of the protocols.jinja. When you are complete the new Jinja2 should look like the following:

    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;
    {% set bgp_ns = namespace(neighbor_asn=None) %}
    {% for interface_name, iface in interfaces.iteritems() %}
        {% if not iface.get('ipv4_address') %}
            {% continue %}
        {% endif %}
        {% set neighbor_interfaces = iface.get('neighbor_interfaces', []) %}
        {% for neighbor_interface in neighbor_interfaces if neighbor_interface.get('ipv4_address') %}
    		{% set bgp_ns.neighbor_asn = function.get_resource_value(all_resources.get(neighbor_interface.system_id), 'asn_generator', 'tube-resources', 'systems') %}
                    neighbor {{ neighbor_interface.ipv4_address }} {
                    peer-as {{ bgp_ns.neighbor_asn }};
                    export add-med-{{ iface.link_tags[0] }};
                }
        {% endfor %}
    {% endfor %}
            }
        }
    }
    {% set my_asn = function.get_resource_value(resources,'asn_generator', 'tube-resources','systems') %}
    routing-options {
        autonomous-system  {{ my_asn }};
    }

Commit your changes, and review.

Now that you have finished your updates on both the loopback address and the ASN numbers. It is a simple step of reviewing the configuration changes.

  • Navigate to the main tab [Uncommitted] > [Logical Diff] and review the changes. You should see a list of all the changes and what remains to be committed. You should be able to navigate and click on interfaces.jinja and protocols.jinja hyperlinks and review the changes.

interfaces_changes

protocols_changes

Now that we have reviewed the changes lets commit them.

  • Navigate to [Uncommitted] and click in the upper right corner where the [Commit] button is. commit

Now enter the information that documents the changes made and click the final [Commit] button. This could be a ticket number in a production environment. commit dialog

Once the commit is complete you should see all green check boxes. all greens

Lab Completion

Congratulations ✌. You have accomplished a major step towards realizing the benefits of using Juniper Apstra Freeform. The steps in this lab represent a simple use case chosen to illustrate the powerful features offered in Apstra Freeform. We hope you enjoyed exploring the features of this solution, but we have only scratched the surface of what this system is capable of doing. We look forward to your continued journey in learning more about Apstra and how it automates designing and operating complex network infrastructure.