Question on function organization

Hi all,

Working on my first foray into nornir. The project I’m working on is to render a j2 template based on the output of certain facts from a Juniper switch virtual-chassis (create base config for all ge-* interfaces) and then apply that template (load merge) to the VC.

I’ve gotten this far with my code, which is up to producing a list of ‘ge-*’ interfaces on the VC stack:

import re
from nornir import InitNornir
from nornir.plugins.tasks import networking, text
from nornir.plugins.functions.text import print_result

nr = InitNornir(config_file="config.yaml", core={'num_workers': 1}, dry_run=True)

target = nr.filter(hostname="192.168.1.170")


def do_stuff(task):
    resobj = task.run(task=networking.napalm_get,
                 getters=['facts'])

    # obtain list of all interfaces from the result facts
    intlist = resobj[0].result['facts']['interface_list']
    # print(intlist)

    # Create list of front ports
    front_ports = []
    for int in intlist:
        p = re.compile('ge-\d+')
        mch = p.match(int)
        if mch:
            front_ports.append(int)
    # print(front_ports)

    for curr_port in front_ports:
        print(curr_port)
        # What's my move to create a Jinja2 template here?


target.run(task=do_stuff)

So my question now is:

  • Should I continue on with the do_stuff function (i.e., generate the output of a .j2 template via another task.run) and go on to apply that in yet another task.run in the same function, or split those three things into three functions, then in target.run run multiple tasks?

Thanks,
Will

Continued on with the one function, final product (working! :slight_smile: ) is:

import re
from nornir import InitNornir
from nornir.plugins.tasks import networking, text


nr = InitNornir(config_file="config.yaml", core={'num_workers': 1})

target = nr.filter(hostname="138.15.180.123")


def config_front_ports(task):
    resobj = task.run(task=networking.napalm_get,
                      getters=['facts'])

    # obtain list of all interfaces from the result facts
    intlist = resobj[0].result['facts']['interface_list']

    # Create list of front ports
    front_ports = []
    p = re.compile('ge-\d+')
    for int in intlist:
        mch = p.match(int)
        if mch:
            front_ports.append(int)

    # Store front port list as host data
    task.host['front_ports'] = front_ports

    # Generate front port config from Jinja2 template
    rt = task.run(task=text.template_file,
                  name="Generate Front Port Config",
                  template="vc_interfaces.j2",
                  path=f"templates/{task.host.platform}")

    # Store rendered config as host data
    task.host['fp_config'] = rt.result

    # Apply the config to the host
    task.run(task=networking.napalm_configure,
             name="Loading Configuration on the device",
             replace=False,
             configuration=task.host['fp_config'])


target.run(task=config_front_ports)

Still interested if this is the “right” way, or if breaking the different stages out into separate functions (if it could even work that way) would be better. Please comment and help me learn; very pleased that I finally got it working, and see much more Nornir in my future!

1 Like

I think the task is small and concise enough that breaking it down into smaller chunks is not necessary and probably counterproductive. The only comments I’d make is:

  1. If it make sense you could have an is_front_port function.
  2. I suspect you could get away with int.startswith("ge-") without the need to use a regular expression

Thanks for taking a look, and your critique. Looking to start a new project with Nornir soon, and continuing to improve my Python skills.