Automation Principles – Declarative/Imperative

This is part of series of posts to understand Network Automation Principles.

Declarative and Imperative are contrasting programming paradigms that describes whether or not you describe the “how”. In Imperative programming you describe the “how”, by describing the individual steps in declarative programming you describe the end state, without worrying about the “how”. Likely, that doesn’t really mean to much without additional context.

A great real world example can be found below, taken from here:

Declarative Programming is like asking your friend to draw a landscape. You don’t care how they draw it, that’s up to them.

Imperative Programming is like your friend listening to Bob Ross tell them how to paint a landscape. While good ole Bob Ross isn’t exactly commanding, he is giving them step by step directions to get the desired result.

A classic network example in the network space is switchport trunk allowed vlan 10,20,30 vs switchport trunk allowed vlan add 30 and switchport trunk allowed vlan remove 15 has been well documented for creating havoc.

Network Automation Example

If you were tasked to manage a set of VLANs, you may use an idempotent design to ensure that the VLAN existed. That would helpful in deploying new VLANs, but how would you ensure that only VLANs [10, 20, 30] existed and not any other? Using a traditional Cisco IOS cli, you would have to first understand which VLANs are on the configuration, e.g. [10, 15, 20] and VLANs that should not be present, and understand ones that need to be added.

In the imperative approach, you must define how you add and remove VLANs, and that will be up for each developer to determine how to do so. This could result in various different mechanisms deployed, with various results expected.

>>> def add_vlan(add_vlans):
...     for vlan in add_vlans:
...         print("vlan {}".format(vlan))
...         print(" name VLAN{}".format(vlan))
...
>>> def remove_vlan(removed_vlans):
...     for vlan in remove_vlans:
...         print("no vlan {}".format(vlan))
...
>>> expected_vlans = [30]
>>> remove_vlans = [15]
>>>
>>> add_vlan(expected_vlans)
vlan 30
 name VLAN30
>>> remove_vlan(remove_vlans)
no vlan 15
>>>

However, in the declarative approach, you simply need to provide the end state:

>>> def declarative_vlan(existing_vlans, expected_vlans):
...     persisted_vlans = list(set(expected_vlans).intersection(set(existing_vlans)))
...     # shown for completeness, no functional value here
...     add_vlans = list(set(expected_vlans).difference(set(existing_vlans)))
...     remove_vlans = list(set(existing_vlans).difference(set(expected_vlans)))
...     for vlan in add_vlans:
...         print("vlan {}".format(vlan))
...         print(" name VLAN{}".format(vlan))
...     for vlan in remove_vlans:
...         print("no vlan {}".format(vlan))
...
>>> existing_vlans = [10, 15, 20]
>>> expected_vlans = [10, 20, 30]
>>> declarative_vlan(existing_vlans, expected_vlans)
vlan 30
 name VLAN30
no vlan 15
>>>

Note: The expected VLANs are given in this example for simplistic purposes only, in a real life example, you would expect to give a device or configuration, and the function to parse that out for you.

As you can see, the “steps” were still needed, e.g. the function had to determine which VLANs to add, and which VLANs to remove and build out that configuration. However, it is done one time for all occurrences afterwards, and each developer does not have to come up with their own solution.

Network Landscape

Currently having fully declarative configurations in Cisco IOS can be managed using the config replace method, which was detailed way back in 2007 by Ivan Pepelnjak. This really helps to get to a final state, and ensure all configurations are just as you intended them to be, as shown in the VLANs example. However, it takes a rather mature network automation life cycle to expect to be able to accomplish this. More realistically, you would have to take each “feature” one at a time.

Junos provides a mechanism to replace at stanza, which is similar to the usage of “feature” as described previously. This is an invaluable feature that provides native integrations from the vendor’s OS. Similar functionality now exists over various *CONF protocols, vendor documentation will provide insight into the current support.

Briefly to discuss how one may go about a feature level declarative approach in the Cisco IOS world, as a proper analysis would require one to many blogs to cover.

  • Cisco IOS NETCONF/RESTCONF – These *CONF based protocols support replace functionality
  • Hierarchical Configuration – Provides the primitives to understand how to manage configurations, without being opinionated on how you should implement, and is better described here.
  • Yangify/Rosetta – A new library intended to help with parsing and translation of configuration into data models, and it embodies the declarative approach.
  • Ansible Resource – Newly created resource modules that offer the “replace functionality”.
  • Set Theory – Using set theory, as shown in VLANs example, basic configurations are fairly easy to manage, using the same pattern.

Conclusion

Providing a declarative method provides the abstraction to get to the device to the intended end state. Declarative functions are context agnostic, as the only care about that end state which is often–but not exclusively–advantageous.

This is not to say that the declarative approach is the only method that should ever be deployed, for example, you may want to have strict controls on who could delete VLANs, and providing the declarative approach would not separate those responsibilities cleanly. Additionally, it may not always be clear to the user of said function, that it will remove something, as is the case with switchport trunk allowed vlan command has caused.

-Ken



ntc img
ntc img

Contact Us to Learn More

Share details about yourself & someone from our team will reach out to you ASAP!

Author