This is part of a series of posts focused on Network Automation Principles.
DRY in Computer Science
The phrase Do Not Repeat Yourself or DRY is a rather simple concept. If you are writing a piece of code multiple times, modularize the code in a way to ensure you are not copying code multiple times. The definition of DRY states:
Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.
Once there are multiple copies, inevitably, one of the pieces of code will not function the same as others, based on some missed step. Additionally if you want to change functionality in one place, you would have to remember to change it in all places.
To put in context, Bart Simpson writing the same phrase on the chalkboard over and over is certainly not DRY.
Example
In this first contrived example you will see the duplication of code in how the VLAN data is being validated.
>>> def add_vlan(vlan_id):...if not isinstance(vlan_id, int):... raise TypeError("VLAN ID is not an integer")... elif not (vlan_id >=1 and vlan_id <=4096):... raise ValueError("Invalid VLAN ID, which must be between 1-4096")...return"vlan {vlan_id}".format(vlan_id=vlan_id)...>>> def configure_port_vlan(interface, vlan_id):...if not isinstance(vlan_id, int):... raise TypeError("VLAN ID is not an integer")... elif not (vlan_id >=1 and vlan_id <=4096):... raise ValueError("Invalid VLAN ID, which must be between 1-4096")...return"interface {interface}\n switchport access vlan {vlan_id}".format(interface=interface, vlan_id=vlan_id)...>>>
A common method to to modularize code is building functions. A function can be thought of as a piece of code that can be called to run that single action, and functions can call other functions. Here you can see how the validation functionality is broken out.
>>> def validate_vlan(vlan_id):...if not isinstance(vlan_id, int):... raise TypeError("VLAN ID is not an integer")... elif not (vlan_id >=1 and vlan_id <=4096):... raise ValueError("Invalid VLAN ID, which must be between 1-4096")...>>>
The duplicated functionality is removed from the initial functions, and you can see it was tested to work the same.
>>> def add_vlan(vlan_id):...validate_vlan(vlan_id)...return"vlan {vlan_id}".format(vlan_id=vlan_id)...>>> def configure_port_vlan(interface, vlan_id):...validate_vlan(vlan_id)...return"interface {interface}\n switchport access vlan {vlan_id}".format(interface=interface, vlan_id=vlan_id)...>>>print(add_vlan(500))vlan 500>>>print(add_vlan(5000))Traceback (most recent call last): File "<stdin>", line 1,in<module> File "<stdin>", line 2,in add_vlan File "<stdin>", line 5,in validate_vlanValueError: Invalid VLAN ID, which must be between 1-4096>>>print(configure_port_vlan("GigabitEthernet1/0/1",50))interfaceGigabitEthernet1/0/1switchportaccessvlan 50>>> print(add_vlan("500"))Traceback (mostrecentcalllast):File "<stdin>", line 1, in<module>File "<stdin>", line 2, inadd_vlanFile "<stdin>", line 3, invalidate_vlanTypeError: VLANIDisnotaninteger>>>
Each time you need to validate a VLAN ID, you simply can call the same function. If at a later date you decided that you wanted to validate the VLAN name for length as well, you can simply add that testing to the one function, instead of having to remember all of the multiple places the code can be.
DRY Data
You will note in the official definition, there is reference to “knowledge”, which as quoted from Pragmatic Programmer
A system’s knowledge is far broader than just its code. It refers to database schemas, test plans, the build system, even documentation
Simply put, code is not the only thing DRY applies to. One example understood by network engineers is managing data for a subnet mask. I often run into solutions of data and associated Jinja files that similar to:
{%ifansible_network_os="ios"%} ip address {{subnet['network'] |ipaddr('add',1 ) }}{{subnet['subnet_mask'] }}{# exampleresult:10.1.1.1255.255.255.0 #}{%elifansible_network_os="nxos"%} ip address {{subnet['network'] |ipaddr('add',1 ) }}/{{subnet['cidr'] }}{# exampleresult:10.1.1.1/24 #}{%endif%}
This solution certainly has benefits, however, given the sheer amount of subnets being managed, it is likely at some point there will be a user that creates a cidr of 25 and subnet_mask as 255.255.255.0. However, the subnet and wildcard mask can be ascertained from the cidr, and maintaining the data multiple times is superfluous and error prone. The one-time cost of building a translation ensures that you are not repeating yourself and not falling into the issues being described here.
Instead you can manage your data a bit more DRY, such as:
While this is a trivial example, the potential issue with data duplication is compounded for every feature that needs to be configured on a given device.
Getting WET in a DRY World
If DRY is not repeating any piece of knowledge, the reverse would be “Write Every Time” (WET). Now why would you want to repeat yourself? Well, as always, it depends, and there are always design considerations that should be taken into account.
As an example, a common pattern we at NTC end up seeing is customers that build a single Jinja template for IOS, NXOS, and EOS. Since these three OS’s have a lot of commonalities, from a DRY perspective you may be inclined to write your templates in consolidated templates.
{%forvlaninvlans%}vlan {{vlan['id'] }} name {{vlan['name'] }}{%endfor%}!
This works out well for this case, however, as you move on to more complicated use cases, such as BGP, you will end with greater differences between the various OS’s.
As you can see, it very quickly get’s complicated. A few basic options, each with pros and cons.
Keep a flat structure, as originally shown, and leverage the similarities of the multiple OS’s to alleviate re-writing them, but add complexity to the use cases where there are differences.
Keep a nested structure, one for each OS, and have the code copied over from one OS to the other, where it is the same configuration.
Attempt to merge the two, and only leverage OS folder when complicated, and a default folder when not.
In this use case, my personal preference is to have a well defined OS structure as the next developer can review the tree structure (as depicted in the second option), and likely figure out where the configuration templates should go. Essentially, make it easier for the next developer to understand the layout, rather than worry about the duplication of code.
Situations like this will come up all the time, and you will simply have to use experience/intuition to understand the impact and make the best decision from there.
Conclusion
Managing your code or data should in most use cases be done in a DRY manner, as this method cuts down on errors and drives computational optimizations. That being said, there are some design considerations where it may make sense to take a WET approach.
Does this all sound amazing? Want to know more about how Network to Code can help you do this, reach out to our sales team. If you want to help make this a reality for our clients, check out our careers page.
We use cookies on our website to give you the most relevant experience by remembering your preferences and repeat visits. By clicking “Accept”, you consent to the use of ALL the cookies. In case of sale of your personal information, you may opt out by using the link Do not sell my personal information. Privacy | Cookies
This website uses cookies to improve your experience while you navigate through the website. Out of these cookies, the cookies that are categorized as necessary are stored on your browser as they are essential for the working of basic functionalities of the website. We also use third-party cookies that help us analyze and understand how you use this website. These cookies will be stored in your browser only with your consent. You also have the option to opt-out of these cookies. But opting out of some of these cookies may have an effect on your browsing experience.
Necessary cookies are absolutely essential for the website to function properly. These cookies ensure basic functionalities and security features of the website, anonymously.
Cookie
Duration
Description
__hssc
30 minutes
HubSpot sets this cookie to keep track of sessions and to determine if HubSpot should increment the session number and timestamps in the __hstc cookie.
__hssrc
session
This cookie is set by Hubspot whenever it changes the session cookie. The __hssrc cookie set to 1 indicates that the user has restarted the browser, and if the cookie does not exist, it is assumed to be a new session.
cookielawinfo-checkbox-advertisement
1 year
Set by the GDPR Cookie Consent plugin, this cookie records the user consent for the cookies in the "Advertisement" category.
cookielawinfo-checkbox-analytics
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Analytics".
cookielawinfo-checkbox-functional
11 months
The cookie is set by GDPR cookie consent to record the user consent for the cookies in the category "Functional".
cookielawinfo-checkbox-necessary
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookies is used to store the user consent for the cookies in the category "Necessary".
cookielawinfo-checkbox-others
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Other.
cookielawinfo-checkbox-performance
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Performance".
CookieLawInfoConsent
1 year
CookieYes sets this cookie to record the default button state of the corresponding category and the status of CCPA. It works only in coordination with the primary cookie.
viewed_cookie_policy
11 months
The cookie is set by the GDPR Cookie Consent plugin and is used to store whether or not user has consented to the use of cookies. It does not store any personal data.
Functional cookies help to perform certain functionalities like sharing the content of the website on social media platforms, collect feedbacks, and other third-party features.
Cookie
Duration
Description
__cf_bm
30 minutes
Cloudflare set the cookie to support Cloudflare Bot Management.
li_gc
5 months 27 days
Linkedin set this cookie for storing visitor's consent regarding using cookies for non-essential purposes.
lidc
1 day
LinkedIn sets the lidc cookie to facilitate data center selection.
UserMatchHistory
1 month
LinkedIn sets this cookie for LinkedIn Ads ID syncing.
Performance cookies are used to understand and analyze the key performance indexes of the website which helps in delivering a better user experience for the visitors.
Analytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics the number of visitors, bounce rate, traffic source, etc.
Cookie
Duration
Description
__hstc
5 months 27 days
Hubspot set this main cookie for tracking visitors. It contains the domain, initial timestamp (first visit), last timestamp (last visit), current timestamp (this visit), and session number (increments for each subsequent session).
_ga
1 year 1 month 4 days
Google Analytics sets this cookie to calculate visitor, session and campaign data and track site usage for the site's analytics report. The cookie stores information anonymously and assigns a randomly generated number to recognise unique visitors.
_gat_gtag_UA_*
1 minute
Google Analytics sets this cookie to store a unique user ID.
_gid
1 day
Google Analytics sets this cookie to store information on how visitors use a website while also creating an analytics report of the website's performance. Some of the collected data includes the number of visitors, their source, and the pages they visit anonymously.
AnalyticsSyncHistory
1 month
Linkedin set this cookie to store information about the time a sync took place with the lms_analytics cookie.
CONSENT
2 years
YouTube sets this cookie via embedded YouTube videos and registers anonymous statistical data.
hubspotutk
5 months 27 days
HubSpot sets this cookie to keep track of the visitors to the website. This cookie is passed to HubSpot on form submission and used when deduplicating contacts.
ln_or
1 day
Linkedin sets this cookie to registers statistical data on users' behaviour on the website for internal analytics.
Advertisement cookies are used to provide visitors with relevant ads and marketing campaigns. These cookies track visitors across websites and collect information to provide customized ads.
Cookie
Duration
Description
bcookie
1 year
LinkedIn sets this cookie from LinkedIn share buttons and ad tags to recognize browser IDs.
bscookie
1 year
LinkedIn sets this cookie to store performed actions on the website.
li_sugr
3 months
LinkedIn sets this cookie to collect user behaviour data to optimise the website and make advertisements on the website more relevant.
VISITOR_INFO1_LIVE
5 months 27 days
YouTube sets this cookie to measure bandwidth, determining whether the user gets the new or old player interface.
YSC
session
Youtube sets this cookie to track the views of embedded videos on Youtube pages.
yt-remote-connected-devices
never
YouTube sets this cookie to store the user's video preferences using embedded YouTube videos.
yt-remote-device-id
never
YouTube sets this cookie to store the user's video preferences using embedded YouTube videos.
yt.innertube::nextId
never
YouTube sets this cookie to register a unique ID to store data on what videos from YouTube the user has seen.
yt.innertube::requests
never
YouTube sets this cookie to register a unique ID to store data on what videos from YouTube the user has seen.