cancel
Showing results for 
Search instead for 
Did you mean: 
cancel
2075
Views
1
Helpful
10
Replies

DNAC, dynamic templates, is it possible?

2nhansen
Level 1
Level 1

Apologies for a slightly long post:


I have for some time tried to find a way to configure network equipment controlled by DNAC with device specific interface commands.

An example could be like this:

interface {{ int.portName }}
description DNAC: Standard dot1x configuration
switchport mode access
switchport voice vlan 10
no logging event link-status
no cdp enable
access-session host-mode multi-domain
access-session port-control auto
mab
no snmp trap link-status
dot1x pae authenticator
dot1x timeout tx-period 3
storm-control broadcast level 5.00
storm-control multicast level 30.00
spanning-tree portfast
device-tracking
service-policy type control subscriber DOT1X_POLICY

But some interfaces on some switches needs to have an interface configuration that is totally different, for example like this one (the main point here is that the interface configuration is non-standard and will be different for each device and template):


interface {{ int.portName }}
description Connection to Customer XYZ
switchport mode access
switchport voice vlan 200
no logging event link-status
no cdp enable
no snmp trap link-status
storm-control broadcast level 5.00
storm-control multicast level 30.00
spanning-tree portfast

What I would like to achieve is a DNAC template setup that is able to control this so that I am able to provision the device form DNAC and get all the interfaces configured correctly based on the data stored in DNAC.


I have found that setting up devices in a generic way, where interfaces are configured using the same policy across all devices of a specific HW type is easy. Setting up one interface with a tailored configuration “here and there” is on the other side not so easy, especially if you have a very large number of switches (hundreds) that all needs to be under DNACs control.


I have tried the following approaches:


1) Using TAGs in DNAC:
Using this method, I create a device specific template that contains the device-specific configuration statements for one device (referring to the above examples, this template would contain the interface configuration that is different from the standard dot1x setup applied to all the other interfaces). I create one device specific template for each device. I then assign a TAG (I have used the hostname of the device that should receive this configuration as the tag value) to the template and the same TAG to the device, thus linking that template to its intended target. Finally, I add the template to the Network Profile that is assigned to the relevant sites and provision the device.

The need for adding the template to the Network Profile is a major obstacle as I see it, as having hundreds of templates assigned to the same Network Profile simply does not scale well.

2) Using the Jinja2 include statement
Another approach that I have explored is using the Jinja2 include statement. Using this statement, I am able to include / insert one template into another template by just referring to its name.

The general syntax of the include command looks like this:

{% include "template" %}

…and may even refer to a “Project” in the template hierarchy, like this:

{% include "Device_Specific_Templates/template" %}

It even allows the include command to point to a variable, such as this, thus permitting me to set this variable to the name of the template to insert:

{% include VARIABLE %}

Initially this looked very promising, as this method would allow me to insert another template (in my case, my device specific template) into the standard template I use. Also, I avoid having to deal with TAGs and adding the device specific templates to the relevant Network Profile.

So why is this not working? My tests seem to indicate that the name of the template that the include statement points to (irrespective of whether this is a direct static pointer as shown in the first two examples above, or a template name pointed to via a variable), this name is resolved at the time the template containing the include command is deployed AND NOT WHEN THE TEMPLATE IS ACTUALLY USED DURING PROVISIONING. If I either statically or via a variable points to a template that does not exists I am unable to deploy the template containing the include command.

This even happens if I include the “ignore missing” parameter to the include command, like this:

{% include "Device_Specific_Templates/template" ignore missing %}

So I feel that I am running out of ideas. My first approach listed above works but is not scalable. The second approach is an attempt to hack this into place and does not work as I intend it to work.

My question to the community is whether one of you have been faced with the same challenge and found a way to solve it?

1 Accepted Solution

Accepted Solutions

Preston Chilcote
Cisco Employee
Cisco Employee

There isn't a way to generate template variables at the time of provision, but you maybe could construct your variable to be a list of tuples that the template can parse.  In pseudocode, that could be [(Gig0, "uplink #1"), (Gig1, "uplink #2), ....(<interface N>, <description N>)], then Jinja would just be parsing a list.  Makes the input a little ugly of course.

View solution in original post

10 Replies 10

Preston Chilcote
Cisco Employee
Cisco Employee

I have seen this solved a couple of different ways.  First, just define a range of interfaces on every switch that will look different.  Then you can basically hardcode the template to say "Ok, the last 3 ports will look different than the others".   It helps operationally to have this consistency, instead of having the "different" port be completely unpredictable from switch to switch.

When more customization is required, you can use the system variables to have DNA populate a multi-select list of all interfaces, and the provisioner can just choose the ones you want to configure differently.  Inside the template, the list of chosen interfaces will be treated as a list.  Here's an example:

#foreach($trunk_port in ${trunk_port_select})

int $trunk_port

description Uplink Port

switchport trunk encapsulation dot1q

switchport mode trunk

switchport trunk allowed vlan 1,2,3,4,10

#end 

2nhansen
Level 1
Level 1

Thank you for helping Preston!

These are good ideas that helps, but (unless I misunderstand you) does not give me the flexibility that I need. The ideal solution would be to be able to programmatically generate the custom interface configurations that I need and somehow insert these CLI fragments into the template that DNAC pushes.

It is also unfortunate to have to reserve a number of physical interfaces in order to be able to accomplish tailored interface configurations. In our network some switches only have a need for one tailored interface while others need as much as five. If I understand you correctly, I will then have to reserve five physical interfaces on all switches that uses the template used for this?

Thank you again for helping!

Preston Chilcote
Cisco Employee
Cisco Employee

How would you like DNA to know which interfaces to configure differently if a human admin isn't involved?  I think you'll want to consider both brownfield and greenfield scenarios.  Doing something programmatically when there is no existing config might be impossible.

You can make the number of "special" interfaces per switch dynamic with a variable if your only concern there is wasting ports.  That would just make the last N ports as special.  But, if all those ports don't look exactly the same (different vlans for example) then more complexity would be needed with template variables.

I think running dot1x everywhere is a solution too.  Just use MAB where needed, and you should be able to have radius assign vlans based on the device that gets connected.  That makes the access port configs very easy to templatize.

You can read up on the autoconf feature where the interface templates get configured in the global config of a switch, and then depending on the type of device detected on the port, the correct config can be applied.

Hi Preston

Thanks for these additional tips and tricks. You are spot on when you suggested that “if all those ports don't look exactly the same (different vlans for example) then more complexity would be needed with template variables”.

This scenario is unfortunately the case for our network as the existing setup is a product of the last 10-20 years of manual configuration, with different VLANs used on various ports, and some ports needing additional tuning due to non-Cisco optics and equipment being connected for example.

In a wider perspective we have the following types of interface setup on our edge-switches:

  • Ports with standard dot1x authentication
  • Ports with custom configuration adapted to each customer (we have about 50 different customers)

Our current edge switches are under replacement, and whenever we install a new edge-switch as a replacement for an old one, we add the new switch to DNAC, and consequently need to be able to copy any custom configuration present in the old switch into the new one.

We could try restructure our interface configuration setup facing our customers into a manageable number of variants, thus making it possible to use a standard set of macros in the templates that we use in DNAC. The motivation for my initial question was if there was a way to include the non-standard interface setups in the templates pushed by DNAC so that we could avoid having to go via that route (it will be very time consuming since many of our customers have critical services and therefore require all network changes to be processed via the Change Management System).

None the less, I will read up on the autoconf feature and see if I can make that work in combination with the rest of the tips you have given me. I will post my findings once I am done testing that.

Thanks again for all help!

Torbjørn
Spotlight
Spotlight

I understood Prestons first response as following:

Option 1 - Reserve X number of interfaces for _manual_ custom configurations

Option 2 - Add a variable to your template that contains a list of interfaces(selected with a multi-select of interfaces upon provisioning) that you can use in your template to apply specific configurations to the selected interfaces. As these template applies last during device provisioning this will overwrite the "regular" SDA port config and does not require reservation of interfaces on all switches.

Please correct me if I misunderstood @Preston Chilcote

Happy to help! Please mark as helpful/solution if applicable.
Get in touch: https://torbjorn.dev

Thank you Torbjørn

I forgot to mention initially that our setup is a “non-fabric” setup, i.e. we do not use LAN Automation or any other SDA-functionality. Your point is still valid, and as I said in my reply to Preston above, I will try this out in the coming days and post my findings here.

Thank you again for you help!

sherstep
Cisco Employee
Cisco Employee

Try variations of these. They are Velocity templates using DNAC implicit variables. The examples use if/then/else statements using logic for interfaceType and portName, but you can run logic on other variables including  $interface.pid,  $interface.macAddress,  $interface.portMode,  $interface.description,  $interface.name. Just be aware that the logic will be applied from the top down. 




#foreach ($interface in ${__interface})

#if ($interface.interfaceType == "Physical" && !$interface.portName.contains("App") && !$interface.portName.contains("GigabitEthernet0"))

    interface $interface.portName

    description These are regular physical ports

 

#elseif ($interface.portName.contains("App"))

    interface $interface.portName

    description This is the App GigabitEthernet port

 

#elseif ($interface.portName.contains("GigabitEthernet0"))

    interface $interface.portName

    description This is the management GigabitEthernet port

   

#end

#end

 

 

#foreach ($interface in ${__interface})

#if ($interface.interfaceType == "Physical" && $interface.portMode == "access")

  interface $interface.portName

  description user port

  switchport access vlan 100

  switchport mode access

  switchport block multicast

  switchport voice vlan 10

  no snmp trap link-status

  spanning-tree portfast

  spanning-tree bpduguard enable

  spanning-tree guard root

 

#else

#end

#end

2nhansen
Level 1
Level 1

Thank you all that have helped so far! After playing around a little with the suggestions I have come a bit closer.

The following shows my current template Jinja code:

{% macro Setup_1(VLAN_number) %}
interface {{ int }}
description Custom_Setup_1
switchport access vlan {{ VLAN_number }}
{% endmacro %}

{% macro Setup_2(Int_Desc) %}
interface {{ int }}
description {{ Int_Desc }}
switchport access vlan 200
{% endmacro %}

{% for int in Interfaces_Selected_Setup_1 %}
{% if int %}
{{ Setup_1(VLAN_number) }}
{% endif %}
{% endfor %}

{% for int in Interfaces_Selected_Setup_2 %}
{% if int %}
{{ Setup_2(Int_Desc) }}
{% endif %}
{% endfor %}

{% for int in __interface %}
{% if int.portName in Interfaces_Selected_Setup_1 %}
! Ignored 1: {{ int.portName }}
!
{% elif int.portName in Interfaces_Selected_Setup_2 %}
! Ignored 2: {{ int.portName }}
!
{% else %}
interface {{ int.portName }}
description Standard Setup
switchport access vlan 300
!
{% endif %}
{% endfor %}

The logic in this code is based on defining a macro for each custom interface setup that I need.

I then use the variables Interfaces_Selected_Setup_1 and Interfaces_Selected_Setup_2 to control which interfaces each macro is applied to (the mentioned two variables are “bound to source” and defined as “Multi Select” variables referencing the system variable where “Source” = Inventory, “Entity” = Interface and “Attribute” = portName).

I am still investigating if it is possible to add logic that requests user-input for the variables “VLAN_number” and “Int_Desc” on a per-interface basis?

This would for example make it possible to deploy macro “Setup_1” with an interface specific Access VLAN for each interface to which this macro is deployed. As it is now, the value for “VLAN_number” is input only once, and the input value given (the VLAN number) will consequently be the same for all interfaces where that macro is assigned.

 

 

Preston Chilcote
Cisco Employee
Cisco Employee

There isn't a way to generate template variables at the time of provision, but you maybe could construct your variable to be a list of tuples that the template can parse.  In pseudocode, that could be [(Gig0, "uplink #1"), (Gig1, "uplink #2), ....(<interface N>, <description N>)], then Jinja would just be parsing a list.  Makes the input a little ugly of course.

2nhansen
Level 1
Level 1

Thank you again Preston, I was afraid this would be the conclusion. Perhaps this type of functionality can be added to DNAC sometime in the future? I hope it can!

The exercise have been very educational for me, thanks to all that have participated with answers and ideas!