Config ACI
Requests

Inserting Static paths to an EPG

One common task in ACI is the creation of static path bindings for EPGs. Some of these static paths might point to many compute ports, especially when VMM integration is not used. In all the automation tools like Ansible, Terraform there even is the capability to perform "bulk" operations to accelerate this process.

For this lab we are going to utilize the CSV file data to "fill in the blanks" of the ACI object that defines a static path binding. This object looks like the following:


{
    "fvRsPathAtt": {
        "attributes": {
            "dn": "uni/tn-aciproglab04/ap-POD04_APP/epg-java_app/rspathAtt-[topology/pod-1/paths-206/pathep-[eth1/1]]",
            "encap": "vlan-1000",
            "status": "created"
        }
    }
}

To accomplish the task at hand you will need to create a function that builds the object structure that we are going to push to the ACI fabric utilizing the data from the CSV file. The simplest approach would be to utilize the Jinja2 templating engine to fill in the blanks of the JSON object with the data from the CSV file. This can be done by first creating a Jinja2 template and then rendering the template with the data from the CSV file.

Step 1 - Create Jinja2 template file

To accelerate this process we have created a single command to copy from this lab guide that will create the file for you in the correct location.


cat << EOF > ~/ltrdcn-3225/requests/epg_static_path.j2
{
    "fvRsPathAtt": {
        "attributes": {
            "dn": "uni/tn-{{tenant}}/ap-{{app}}/epg-{{epg}}/rspathAtt-[topology/pod-1/paths-{{leaf}}/pathep-[{{port}}]]",
            "encap": "vlan-{{vlan}}",
            "tDn": "topology/pod-1/paths-{{leaf}}/pathep-[{{port}}]",
            "rn": "rspathAtt-[topology/pod-1/paths-{{leaf}}/pathep-[{{port}}]]",
            "status": "{{status}}"
        },
        "children": []
    }
}
EOF

This file contains the object for fvRsPathAtt that is the object required to configure a static path binding. The curly brackets inside of the template are where the variables will be placed from the CSV file.

Step 2 - Create python file for ACI CSV configuration

In the IDE you will create a new file in:


And name the file req_aci_csv.py

Step 3 - Base structure of ACI CSV script

For this script we are also going to introduce the concept of argument parsing. This is a very useful feature of Python that allows you to pass arguments to the script from the command line. You have already seen this in many scripts and tools in the past. Run the script with some parameters ( usually identified via a dash and a letter or word ) and the script will perform different actions based on the parameters that are passed. For this script we are going to pass the name of the CSV file that we are going to read. This will allow us to use the same script to read different CSV files.

First step let's setup the import statements that you will need for this script.


import requests, json, os, re, csv
import argparse

from aci_auth import get_aci_token
from csv_read import read_csv_file

from rich.progress import track
from rich.console import Console

from jinja2 import Template

Now you are going to add the main clause and initialize the parser object.


import requests, json, os, re, csv
import argparse

from aci_auth import get_aci_token
from csv_read import read_csv_file

from rich.progress import track
from rich.console import Console

from jinja2 import Template

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='ACI CSV to EPG Static Path')
    parser.add_argument('-c', '--csvfile',  required=True, help='Specify CSV filename')
    parser.add_argument('-t', '--template',  required=True, help='Specify J2 template filename')
    parser.add_argument('-s', '--state',  required=False, help='Specify State', 
            default='created', choices=['created','deleted'] )
    args = vars(parser.parse_args())

For this lab you will define three separate parameters that we will need to pass to the script. These will be:

  • -f --file that we specify the name of the CSV filename.
  • -t --template that will be the jinja2 template file that is the ACI object.
  • -s --state that will be the object state allowing us to create or delete. To facilitate we are establishing that the user can only input two options: created or deleted. This makes sure that what we pass to ACI is correct.

One advantage of the argparse library is that we can set conditionals on inputs directly. In this case we have specified that the file and template argument is required and if it is not passed the script will not run. This simplifies our code as we don't have to validate if the data exists. This then means we can go directly to verify if we can read the file.

You can run the script with the following command: python3 req_aci_csv.py -h to see the output of the help message that is generated by the argparse library.


python req_aci_csv.py -h
usage: req_aci_csv.py [-h] -c CSVFILE -t TEMPLATE [-s {created,deleted}]

ACI CSV to EPG Static Path

options:
  -h, --help            show this help message and exit
  -c CSVFILE, --csvfile CSVFILE
                        Specify CSV filename
  -t TEMPLATE, --template TEMPLATE
                        Specify J2 template filename
  -s {created,deleted}, --state {created,deleted}
                        Specify State

Step 4 - Verify file location and start rich library

In this step we are going to initiate Console() that will allow us to utilize the rich library to print status. Then verify that the files we are using exist. If they do not exist we will print an error message. Then invoke the CSV read function that you created in the previous step.


import requests, json, os, re, csv
import argparse

from aci_auth import get_aci_token
from csv_read import read_csv_file

from rich.progress import track
from rich.console import Console

from jinja2 import Template

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='ACI CSV to EPG Static Path')
    parser.add_argument('-c', '--csvfile',  required=True, help='Specify CSV filename')
    parser.add_argument('-t', '--template',  required=True, help='Specify J2 template filename')
    parser.add_argument('-s', '--state',  required=False, help='Specify CSV filename', default='created')
    args = vars(parser.parse_args())

    console = Console()

    if os.path.isfile(args['csvfile']) and os.path.isfile(args['template']):
        full_path = os.path.abspath(args['csvfile'])
        console.print("\n[bold blue]CSV file has been found with path:{}".format(full_path))
        csv_data = read_csv_file(args['csvfile'])
    else:
        console.print("[bold red]File not found")

Step 5 - Define function for ACI fabric configuration push

First execution of this function is going to extract the APIC token from the ACI fabric. This is done by calling the get_aci_token function that you have already created in the previous section.


import requests, json, os, re, csv
import argparse

from aci_auth import get_aci_token
from csv_read import read_csv_file

from rich.progress import track
from rich.console import Console

from jinja2 import Template

def aci_epg_static_path_config(csv_data):
    aci_token = get_aci_token()

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='ACI CSV to EPG Static Path')
    parser.add_argument('-f', '--file',  required=True, help='Specify CSV filename')
    parser.add_argument('-s', '--state',  required=False, help='Specify CSV filename', default='created')
    args = vars(parser.parse_args())

    console = Console()

    if os.path.isfile(args['file']):
        full_path = os.path.abspath(args['file'])
        console.print("\n[bold blue]CSV file has been found with path:{}".format(full_path))
        csv_data = read_csv_file(args['file'])
    else:
        console.print("[bold red]File not found")

Now we need to defined the header_object that will be sent to the ACI fabric via the POST method.


import requests, json, os, re, csv
import argparse

from aci_auth import get_aci_token
from csv_read import read_csv_file

from rich.progress import track
from rich.console import Console

from jinja2 import Template

def aci_epg_static_path_config(csv_data, template_file, aci_state):
    aci_token = get_aci_token()
    header_object = {
            'Content-Type': 'application/json',
            'APIC-Cookie': aci_token
        }


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='ACI CSV to EPG Static Path')
    parser.add_argument('-f', '--file',  required=True, help='Specify CSV filename')
    args = vars(parser.parse_args())

    console = Console()

    if os.path.isfile(args['file']):
        full_path = os.path.abspath(args['file'])
        console.print("\n[bold blue]CSV file has been found with path:{}".format(full_path))
        csv_data = read_csv_file(args['file'])
    else:
        console.print("[bold red]File not found")

This next insertion of code is going to add the ability to read the Jinja2 template file that you created in a previous step. This is done by opening the file and reading the contents into the Jinja2 template object.


import requests, json, os, re, csv
import argparse

from aci_auth import get_aci_token
from csv_read import read_csv_file

from rich.progress import track
from rich.console import Console

from jinja2 import Template

def aci_epg_static_path_config(csv_data, template_file, aci_state):
    aci_token = get_aci_token()
    header_object = {
            'Content-Type': 'application/json',
            'APIC-Cookie': aci_token
        }

    with open(template_file) as j2template:
        json_template = Template(j2template.read())

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='ACI CSV to EPG Static Path')
    parser.add_argument('-f', '--file',  required=True, help='Specify CSV filename')
    args = vars(parser.parse_args())

    console = Console()

    if os.path.isfile(args['file']):
        full_path = os.path.abspath(args['file'])
        console.print("\n[bold blue]CSV file has been found with path:{}".format(full_path))
        csv_data = read_csv_file(args['file'])
    else:
        console.print("[bold red]File not found")

With the template object constructed, you can now iterate over the CSV data and render the template with the data from the CSV file. This will create the JSON object that you will send to the ACI fabric via the POST request. This is done by iterating over the CSV data and rendering the template with the data from the CSV file.

The variable json_body is the result of the rendering of the Jinja2 template with the data row from the CSV file read. We have added the track statement to show the progress of the loop. This is useful when you are working with a large number of rows in the CSV file.


import requests, json, os, re, csv
import argparse

from aci_auth import get_aci_token
from csv_read import read_csv_file

from rich.progress import track
from rich.console import Console

from jinja2 import Template

def aci_epg_static_path_config(csv_data, template_file, aci_state):
    aci_token = get_aci_token()
    header_object = {
            'Content-Type': 'application/json',
            'APIC-Cookie': aci_token
        }

    with open(template_file) as j2template:
        json_template = Template(j2template.read())

    for row in track(csv_data):
        json_body = json_template.render(tenant = row["tenant"], app=row["app_profile"], 
            epg=row['epg'], leaf=row["switch"], port=row["port"], 
            vlan=row["vlan"], state=aci_state)
        print(json_body)
        url = "https://10.0.226.41/api/mo/uni.json"
        response = requests.post(url, cookies=aci_token, data=json_body,  verify = False)

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='ACI CSV to EPG Static Path')
    parser.add_argument('-f', '--file',  required=True, help='Specify CSV filename')
    args = vars(parser.parse_args())

    console = Console()

    if os.path.isfile(args['file']):
        full_path = os.path.abspath(args['file'])
        console.print("\n[bold blue]CSV file has been found with path:{}".format(full_path))
        csv_data = read_csv_file(args['file'])
    else:
        console.print("[bold red]File not found")

Now the code is ready to be tested. You can run the script with the following command:


python req_aci_csv.py -c POD04_aci.csv -t epg_static_path.j2 -s created

This will read the CSV file and the Jinja2 template file and then iterate over the CSV data and render the Jinja2 template with the data from the CSV file. This will create the JSON object that you will send to the ACI fabric via the POST request.

The RICH library then will show the progress of the loop. This is useful when you are working with a large number of rows in the CSV file.