Creating the Next Generation of Network Engineers with NTC University

Blog Detail

The IT industry is at a crossroads. While the demand for IT professionals, including network engineers, is skyrocketing, the number of candidates suitable for these positions is low.

The limited candidate pool makes it more crucial than ever for organizations to provide early-career professionals with opportunities to develop their skills and grow their knowledge of the network automation industry.

Recognizing the need to find and keep the right talent, Network to Code launched its own talent development program, NTC University, designed to propel the network engineers of today into the network automation engineers of the future.

So, what makes NTC University the go-to development program for aspiring network engineers and Network to Code the employer of choice?

Let’s find out!

Growing Demand for Network Engineers

The job market for network engineers is growing rapidly, with a projected 6% increase in employment from 2016 to 2026. With the increasing reliance on technology, the need for companies to maintain and secure their networks is more important than ever.

Network engineers are one of the most wanted professionals in the US. Despite the growing demand for network engineers, the industry is experiencing a need for more qualified professionals in the field. This shortage is due to the rapidly changing nature of technology, which requires constant learning and adaptation. Network engineers have not been offered career growth opportunities in the industry.

What Is NTC University?

NTC University is a trailblazing new corporate development program from Network to Code for early-career network engineers. The ten-week immersive program will provide the necessary technical education and consulting skill set for a select group of network engineering professionals. With acceptance into this program, individuals will join Network to Code as full-time Associate Engineers before the start of the program in the fall of 2023.

Participating in this talent development program means you will join our team as a full-time employee from day one and represent our core values in all you do. We are looking for results-driven individuals and creative problem solvers who love to do it differently and want to make an impact on the future of Network Automation in real time.

After completing the ten-week program, participants will join a Network Automation team at Network to Code, starting to work on customer projects delivering solutions for clients, with job shadow and mentorship support for an entire year.

And the benefits are invaluable.

Technical Education Provided

NTC University offers a comprehensive technical education that covers a wide range of topics. From the fundamentals of networking to advanced automation techniques, network engineers have access to a wealth of knowledge that can help them excel in their careers at Network to Code and beyond. The curriculum is designed to be hands-on, allowing participants to gain practical experience to apply in real-world scenarios at Network to Code after completing the program.

Consulting Skill Set Development

In addition to technical education, NTC University also offers training in consulting skills, including communication, project management, and problem-solving skills. With NTC University, network engineers can develop a well-rounded skill set to help them succeed in their new role at Network to Code.

Real-World Experience from Network Automation Leader

NTC University uniquely provides network engineers with real-world experience from a network automation leader named to the Inc. Best Workplaces list along with special recognition in the Prosperous and Thriving category. Participants will have access to dedicated instructors, shadow engineers, and mentors to set them up for success in their new position at Network to Code. The instructors are experts in network automation and have years of experience working with some of the world’s largest and most complex networks.

Propelling Engineers into the Future with NTC University

Through NTCU, Network to Code aims to create a clear career path to propel today’s network engineers into the future as network automation engineers.

In the ten-week program, eligible candidates who are located in the United States and have at least two years of network engineering experience, a basic understanding of Python and data structures, and a passion for network automation will learn everything from Python & Django Fundamentals and Network Automation with Python & Ansible to Network Source of Truth with Nautobot and Nautobot App Development.

Network to Code will host an informational session on NTC University for interested network engineers to learn more about the program. The next session will be on May 16th at 1 p.m. ET. So save your seat and learn more about this trailblazing new corporate development program from Network to Code.


Conclusion

To learn more and apply for NTC University, visit: https://go.networktocode.com/NTCU.

-Ken


Tags :

ntc img
ntc img

Contact Us to Learn More

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

A Journey in Golden Config

Blog Detail


On the surface, all network engineers have a good understanding about what configuration compliance, or golden config, is. However, I’ve found the devil is in the details, and there are many different ideas as to what it should be. In this article, we will explore what some of those views are and some of the pros/cons for them.

Bulk Matching

This works best for “global configurations” such as ntp, dns, snmp, etc. The idea is that you have a standard of what the configurations should be, and you should ensure the actual configurations are the same.

This works well if the configurations are truly the same with a pattern, as described in this pseudo code:

expected_config = "ntp server 10.1.1.1\nntp server 10.1.1.2\nntp server 10.1.1.3 prefer"
actual_config = run_ios_command("show run | in ^ntp server")

if expected_config == actual_config:
    return True
else:
    return False

This tends to not scale well when you have exceptions, a lot of regionality (e.g., differences for region-based standards such as eu, am, ep, etc.), exceptions to the rule (e.g., if a site has a local ntp server, use it), and does not really consider the configurations that will always be different like IPs on interfaces, BGP ASNs, VLANs, etc.

Regex Pattern Matching

Sometimes, you are not as concerned about the actual data, such as whether or not the NTP servers are specific IPs, but instead are concerned that there are NTP servers configured. In such situations you can set up regex to match against your configurations.

Let’s take a look at this as described in this microfocus documentation.

Block Start: interface (.*)
Block End: !

Condition A: Config Block
must not contain
ip address (10\..*)\s(.*)

Condition B: Config Text
must contain only:
Must contain these lines:
ntp server 169\.243\.103\.34
ntp server 170\.242\.62\.16
ntp server 170\.242\.62\.17
ntp server 169\.243\.226\.94
But must not have any additional lines containing:
ntp server(.*)

Logic: A AND B

As you can see, you can use regex to perform “greedy” matches, such as ip address (10\..*)\s(.*), as well as specific matches, such as ntp server 169\.243\.103\.34.

There are also some scaling concerns with this approach, such as which devices does this template apply to, which templates are applied to a device, complex regex matching that quickly gets out of control, and not providing a path to “fix” the configurations.

Profiling Configurations

As you will note, the previous options made it difficult to scale beyond the global configurations. Developing compliance on configurations like interface can be rather difficult, if not impossible, in them.

With profiling the configurations, we can build strategies to pull out the relevant data and ensure a configuration can be rebuilt to the actual configurations. Well, that was a confusing mouthful, so let’s break this down a bit.

  • Grab a piece of configuration, such as all configuration under “interface GigabitEthernet0/1”.
    • We will call this actual_configuration.
  • Grab the detail from that configuration that you would use to profile it, such as the description and VLAN.
  • Use that data with predefined templates and process through a templating engine.
    • We call this expected_configuration.
  • Compare actual_configuration and expected_configuration and see whether they are the same.
    • If there are configs in actual_configuration and not in expected_configuration, there are unexpected configurations.
    • If there are configs in expected_configuration and not in actual_configuration, there are missing configurations.

Well, this is still a bit much, how about a diagram?

All make sense? If not, one more effort with actual code:

Basic Functions

>>>
>>> import jinja2
>>> import difflib
>>>
>>> def parse_cfg(actual_configuration):
...     parsed_data = []
...     interface = ""
...     description = ""
...     vlan = 0
...     for line in actual_configuration.splitlines():
...         if line.startswith("interface "):
...             if interface:
...                 parsed_data.append({"interface": interface, "description": description, "vlan": vlan})
...             interface = line.split()[1]
...             description = ""
...             vlan = 0
...         if line.startswith(" description "):
...             description = line[13:]
...         if line.startswith(" switchport access vlan "):
...             vlan = line[24:]
...     parsed_data.append({"interface": interface, "description": description, "vlan": vlan})
...     return parsed_data
...
>>> def regen_cfg(vars):
...     template_str = """"""
...     environment = jinja2.Environment()
...     template = environment.from_string(template_str)
...     return template.render(**vars)
...
>>> def compare_cfg(actual_configuration, expected_configuration):
...     if actual_configuration == expected_configuration:
...         return True
...     else:
...         for text in difflib.unified_diff(actual_configuration.split("\n"), expected_configuration.split("\n")):
...             print(text)
...         return False
...
>>> 

Example of Compliant Configuration

>>> compliance_actual_configuration = """
... interface GigabitEthernet0/1
...  description USER PORT
...  switchport mode access
...  switchport access vlan 205
...  snmp trap mac-notification change added
...  snmp trap mac-notification change removed
...  auto qos trust dscp
...  no mdix auto
...  spanning-tree portfast
...  spanning-tree guard root"""
>>>
>>> compliant_vars = {}
>>> compliant_vars['interface_vars'] = parse_cfg(compliance_actual_configuration)
>>> compliant_vars['interface_vars']
[{'interface': 'GigabitEthernet0/1', 'description': 'USER PORT', 'vlan': '205'}]
>>> compliant_expected_configuration = regen_cfg(compliant_vars)
>>>
>>> compare_cfg(compliance_actual_configuration, compliant_expected_configuration)
True
>>> 

Example of Non-Compliant Configurations

>>> non_compliant_actual_configuration = """
... interface GigabitEthernet0/1
...  description USER PORT
...  switchport access vlan 205
...  spanning-tree portfast
...  spanning-tree guard root
... """
>>>
>>> non_compliant_vars = {}
>>> non_compliant_vars['interface_vars'] = parse_cfg(non_compliant_actual_configuration)
>>> non_compliant_expected_configuration = regen_cfg(non_compliant_vars)
>>>
>>> compare_cfg(non_compliant_actual_configuration, non_compliant_expected_configuration)
---

+++

@@ -1,7 +1,11 @@


 interface GigabitEthernet0/1
  description USER PORT
+ switchport mode access
  switchport access vlan 205
+ snmp trap mac-notification change added
+ snmp trap mac-notification change removed
+ auto qos trust dscp
+ no mdix auto
  spanning-tree portfast
  spanning-tree guard root
-
False
>>>

With this approach you solve many of the challenges of comparing different types of configurations. That being said, numerous challenges remain.

  • Each configuration stanza requires some custom code
  • Exception management is difficult
    • For example, you want to add broadcast suppression on twenty interfaces within your entire org, how do you handle that?
  • In some cases you do not care about the current configuration, you simply want the configuration to match the expected; so you must support this solution and another solution as well.

Intended State vs Actual State

Having built many such solutions, the idea of building a comparison of the actual state vs intended seemed the most logical. In such a design, the lion’s share of the work is how to generate configurations, in a “traditional” Infrastructure as Code (IaC) approach. With IaC, you generate your configurations (within networking) generally by combining the data with Jinja templates.

Let’s break down this process a bit.

  • Obtain the actual configuration from the backup
    • Parse out the relevant configuration, often by breaking up into features (think stanza levels of configurations)
    • We will call this actual_configuration.
  • Generate the intended configuration
    • We will call this intended_configuration.
  • Compare the two configuration parts

To help bring this to life, here is a diagram of how this works:

In pursuing this approach, you get the direct benefits for configuration compliance of:

  • Having a single solution for any CLI-based configurations, regardless of vendor
  • Limiting the amount of code for any given configuration (to nearly zero)
  • Providing a platform for exception management
  • Providing a path to fix the configurations

Note: Though outside the scope of this blog, the ability to remedy configurations is generally predicated on having both an actual and an intended state.

Additionally, there are the collateral benefits of:

  • Providing a reason to develop an IaC solution
  • Providing a reason to build out configurations
  • Providing a reason to populate a Source of Truth

To finally drive home how this works, let’s review some code.

Basic Setup

>>> import jinja2
>>> from netutils.config import compliance
>>>
>>> def regen_cfg(vars):
...     template_str = """"""
...     environment = jinja2.Environment()
...     template = environment.from_string(template_str)
...     return template.render(**vars)
...
>>> # This is our pseudo SoT
>>> vars = {}
>>> vars['interface_vars'] = [{'interface': 'GigabitEthernet0/1', 'description': 'USER PORT', 'vlan': '205'}]
>>>
>>> network_os = "cisco_ios"
>>> features = [
...     {"name": "interface", "ordered": True, "section": ["interface "]},
... ]
>>>

Example of Compliant Configuration

<span role="button" tabindex="0" data-code=">>> compliant_backup_cfg = """ … interface GigabitEthernet0/1 … description USER PORT … switchport mode access … switchport access vlan 205 … snmp trap mac-notification change added … snmp trap mac-notification change removed … auto qos trust dscp … no mdix auto … spanning-tree portfast … spanning-tree guard root""" >>> >>> compliant_intended_cfg = regen_cfg(vars) >>> >>> compliance.compliance(features, compliant_backup_cfg, compliant_intended_cfg, network_os, "string") {'interface': {'compliant': True, 'missing': '', 'extra': '', 'cannot_parse': True, 'unordered_compliant': True, 'ordered_compliant': True, 'actual': 'interface GigabitEthernet0/1\n description USER PORT\n switchport mode access\n switchport access vlan 205\n snmp trap mac-notification change added\n snmp trap mac-notification change removed\n auto qos trust dscp\n no mdix auto\n spanning-tree portfast\n spanning-tree guard root', 'intended': 'interface GigabitEthernet0/1\n description USER PORT\n switchport mode access\n switchport access vlan 205\n snmp trap mac-notification change added\n snmp trap mac-notification change removed\n auto qos trust dscp\n no mdix auto\n spanning-tree portfast\n spanning-tree guard root'}} >>> # simplified yaml view of same data # interface: # compliant: true # missing: '' # extra: '' # cannot_parse: true # unordered_compliant: true # ordered_compliant: true # actual: <omitted> # intended:
>>> compliant_backup_cfg = """
... interface GigabitEthernet0/1
...  description USER PORT
...  switchport mode access
...  switchport access vlan 205
...  snmp trap mac-notification change added
...  snmp trap mac-notification change removed
...  auto qos trust dscp
...  no mdix auto
...  spanning-tree portfast
...  spanning-tree guard root"""
>>>
>>> compliant_intended_cfg = regen_cfg(vars)
>>>
>>> compliance.compliance(features, compliant_backup_cfg, compliant_intended_cfg, network_os, "string")
{'interface': {'compliant': True, 'missing': '', 'extra': '', 'cannot_parse': True, 'unordered_compliant': True, 'ordered_compliant': True, 'actual': 'interface GigabitEthernet0/1\n description USER PORT\n switchport mode access\n switchport access vlan 205\n snmp trap mac-notification change added\n snmp trap mac-notification change removed\n auto qos trust dscp\n no mdix auto\n spanning-tree portfast\n spanning-tree guard root', 'intended': 'interface GigabitEthernet0/1\n description USER PORT\n switchport mode access\n switchport access vlan 205\n snmp trap mac-notification change added\n snmp trap mac-notification change removed\n auto qos trust dscp\n no mdix auto\n spanning-tree portfast\n spanning-tree guard root'}}
>>>
# simplified yaml view of same data
# interface:
#   compliant: true
#   missing: ''
#   extra: ''
#   cannot_parse: true
#   unordered_compliant: true
#   ordered_compliant: true
#   actual: <omitted>
#   intended: <omitted>
<span role="button" tabindex="0" data-code=">>> non_compliant_backup_cfg = """ … interface GigabitEthernet0/1 … description USER PORT … switchport access vlan 205 … spanning-tree portfast … spanning-tree guard root … """ >>> >>> non_compliant_intended_cfg = regen_cfg(vars) >>> >>> compliance.compliance(features, non_compliant_backup_cfg, non_compliant_intended_cfg, network_os, "string") {'interface': {'compliant': False, 'missing': 'interface GigabitEthernet0/1\n switchport mode access\n snmp trap mac-notification change added\n snmp trap mac-notification change removed\n auto qos trust dscp\n no mdix auto', 'extra': '', 'cannot_parse': True, 'unordered_compliant': False, 'ordered_compliant': False, 'actual': 'interface GigabitEthernet0/1\n description USER PORT\n switchport access vlan 205\n spanning-tree portfast\n spanning-tree guard root', 'intended': 'interface GigabitEthernet0/1\n description USER PORT\n switchport mode access\n switchport access vlan 205\n snmp trap mac-notification change added\n snmp trap mac-notification change removed\n auto qos trust dscp\n no mdix auto\n spanning-tree portfast\n spanning-tree guard root'}} >>> # simplified yaml view of same data # interface: # compliant: false # missing: <omitted> # extra: '' # cannot_parse: true # unordered_compliant: false # ordered_compliant: false # actual: <omitted> # intended:
>>> non_compliant_backup_cfg = """
... interface GigabitEthernet0/1
...  description USER PORT
...  switchport access vlan 205
...  spanning-tree portfast
...  spanning-tree guard root
... """
>>>
>>> non_compliant_intended_cfg = regen_cfg(vars)
>>>
>>> compliance.compliance(features, non_compliant_backup_cfg, non_compliant_intended_cfg, network_os, "string")
{'interface': {'compliant': False, 'missing': 'interface GigabitEthernet0/1\n switchport mode access\n snmp trap mac-notification change added\n snmp trap mac-notification change removed\n auto qos trust dscp\n no mdix auto', 'extra': '', 'cannot_parse': True, 'unordered_compliant': False, 'ordered_compliant': False, 'actual': 'interface GigabitEthernet0/1\n description USER PORT\n switchport access vlan 205\n spanning-tree portfast\n spanning-tree guard root', 'intended': 'interface GigabitEthernet0/1\n description USER PORT\n switchport mode access\n switchport access vlan 205\n snmp trap mac-notification change added\n snmp trap mac-notification change removed\n auto qos trust dscp\n no mdix auto\n spanning-tree portfast\n spanning-tree guard root'}}
>>>
# simplified yaml view of same data
# interface:
#   compliant: false
#   missing: <omitted>
#   extra: ''
#   cannot_parse: true
#   unordered_compliant: false
#   ordered_compliant: false
#   actual: <omitted>
#   intended: <omitted>

It may not be immediately obvious, but the key is in the feature definition features = [{"name": "interface", "ordered": True, "section": ["interface "]}]. This is the only thing that needs to change when adding additional features. This truly becomes powerful once you have an SoT and have built out your IaC processes.

This process is the underlying principle on which Nautobot Golden Config app is built:

The app provides tooling and ease of use around the processes, which makes it more consumable, but the crux of what is happening is described in these last few paragraphs and code snippets.

Custom Business Logic

While not more strictly defined, it is important to cover custom business logic. There are times when you may only care about the application of certain features but not check beyond that. Let’s take an example used in Nautobot Golden Config custom compliance engine.

<span role="button" tabindex="0" data-code="# sample_config = '''router bgp 400 # no synchronization # bgp log-neighbor-changes # neighbor 70.70.70.70 remote-as 400 # neighbor 70.70.70.70 password cisco # neighbor 70.70.70.70 update-source Loopback80 # no auto-summary # ''' import re BGP_PATTERN = re.compile("\s*neighbor (?P<ip>\d+\.\d+\.\d+\.\d+) .*") BGP_SECRET = re.compile("\s*neighbor (?P
# sample_config = '''router bgp 400
#  no synchronization
#  bgp log-neighbor-changes
#  neighbor 70.70.70.70 remote-as 400
#  neighbor 70.70.70.70 password cisco
#  neighbor 70.70.70.70 update-source Loopback80
#  no auto-summary
# '''
import re
BGP_PATTERN = re.compile("\s*neighbor (?P<ip>\d+\.\d+\.\d+\.\d+) .*")
BGP_SECRET = re.compile("\s*neighbor (?P<ip>\d+\.\d+\.\d+\.\d+) password (\S+).*")
def custom_compliance_func(obj):
    if obj.rule == 'bgp' and obj.device.platform.slug == 'ios':
        actual_config = obj.actual
        neighbors = []
        secrets = []
        for line in actual_config.splitlines():
            match = BGP_PATTERN.search(line)
            if match:
                neighbors.append(match.groups("ip")[0])
            secret_match = BGP_SECRET.search(line)
            if secret_match:
                secrets.append(match.groups("ip")[0])
    neighbors = list(set(neighbors))
    secrets = list(set(secrets))
    if secrets != neighbors:
        compliance_int = 0
        compliance = False
        ordered = False
        missing = f"neighbors Found: {str(neighbors)}\nneigbors with secrets found: {str(secrets)}"
        extra = ""
    else:
        compliance_int = 1
        compliance = True
        ordered = True
        missing = ""
        extra = ""
    return {
        "compliance": compliance,
        "compliance_int": compliance_int,
        "ordered": ordered,
        "missing": missing,
        "extra": extra,
    }

In the above case you are simply enforcing that if neighbor 70.70.70.70 is found, there is a configured password on it. The obvious downside to this is every situation must be handled with custom code, and you are not reviewing all of the configuration or even a majority of the configuration.

Linting

In some cases you are truly only looking for certain conditions. This can be nice, since it applies more generically and has good utility around ensuring security configurations are applied correctly. Services such as STIG or Cisco Config Analysis Tool are largely based on the same concept, which is to confirm that a piece of configuration is on or specifically not on.

We can take a look at a netlint that was built by fellow Network to Coder Leo Kirchner.

>>> from netlint.checks.checker import Checker
>>> from netlint.checks.utils import NOS
>>>
>>> configuration = [
...   "feature ssh",
...   "feature bgp",
...   "hostname test.local"
... ]
>>>
>>> checker = Checker()
>>>
>>> checker.run_checks(configuration, NOS.CISCO_NXOS)
False
>>> checker.check_results
{'NXOS101': None, 'NXOS102': CheckResult(text='BGP enabled but never used.', lines=['feature bgp']), 'NXOS103': None, 'NXOS104': None, 'VAR101': None, 'VAR102': None, 'VAR103': None}
>>>

Conclusion

Throughout my career, I have personally deployed and built each of these types of systems. However, I truly believe that the only long-term method to scale is provided in the “Intended State vs Actual State” section and used within Nautobot Golden Config. Any other method is likely good to get quick results, but tends to not work in situations more complicated than the initial POC. The collateral benefits are also equally compelling in themselves.

That being said, would love to hear your feedback. Are there any other types of golden config that I have missed? If so, look forward to seeing you in the comments.

-Ken



ntc img
ntc img

Contact Us to Learn More

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

Automation Principles – Inheritance

Blog Detail

This is part of a series of posts intended to provide an understanding of Network Automation Principles.

The term inheritance has its origin rooted in object-oriented programming, dating back to the ’60s. As the name suggests, it describes the relationship between objects and how their attributes are handed down, using similar hierarchical relationship naming as used in a family tree (parent, child, grandparent, etc.).

Inheritance is one of the tools in the arsenal that allow programmers to simplify their code and generally keep it more DRY.

Inheritance in Computer Science

A common example used to portray this would be classifying a vehicle, specifically with vehicle being the parent object and vehicle types such as cars, vans, motorcycles being the child object.

class Vehicle():
    def __init__(self, manufacturer, model, color):
        self.manufacturer = manufacturer
        self.model = model
        self.color = color

class Car(Vehicle):
    @property
    def tires(self):
        return 4
    @property
    def doors(self):
        return 4

class MotorCycle(Vehicle):
    @property
    def tires(self):
        return 2
    @property
    def doors(self):
        return 0

Working with this simple example, we can start to see how Inheritance works in Python:

>>> honda_civic = Car('Honda', "Civic", "blue")
>>> honda_civic.color
'blue'
>>> honda_civic.tires
4
>>> honda_civic.doors
4
>>> 
>>> 
>>> 
>>> honda_cbr = MotorCycle('Honda', "CBR", "red")
>>> honda_cbr.color
'red'
>>> honda_cbr.tires
2
>>> honda_cbr.doors
0
>>>

Example in Networks

Let’s take a look at a similar construct using network-focused terms.

class NetworkDevice():
    def __init__(self, manufacturer, model, memory):
        self.manufacturer = manufacturer
        self.model = model
        self.memory = memory

class Router(NetworkDevice):
    @property
    def ports(self):
        return 8
    @property
    def tunneling_protocols(self):
        return ["dmvpn", "gre"]


class Switch(NetworkDevice):
    @property
    def ports(self):
        return 48
    @property
    def rmon(self):
        return True

One thing you may notice here is that while Router has the property of tunneling_protocols, the Switch does not. The same is true in reverse for rmon support. So with the use of inheritance, you can use the same interface as the Router for the properties that are on both or inherited from the parent, which supports the ability to be DRY.

>>> cisco_asr = Router("Cisco", "ASR1000", "1Gb")
>>> cisco_asr.tunneling_protocols
['dmvpn', 'gre']
>>> cisco_asr.ports
8
>>>
>>>
>>> cisco_nexus = Switch("Cisco", "NX9K", "1Gb")
>>> cisco_nexus.ports
48
>>> cisco_nexus.rmon
True
>>>

You can add multiple levels of inheritance in Python. Oftentimes a strategy would be to use a Mixin, which is a conceptual idea of a class that has the sole use of being mixed in with other classes. Take the following example.

class SuperNetworkDeviceMixin():
    @property
    def cpu(self):
        return "8 cores"
    @property
    def routing_protocols(self):
        return ["bgp", "ospf", "isis"]

class Router(NetworkDevice, SuperNetworkDeviceMixin):
    @property
    def ports(self):
        return 8
    @property
    def tunneling_protocols(self):
        return ["dmvpn", "gre"]

You can see that SuperNetworkDeviceMixin can be used to add the properties cpu and routing_protocols to the Router class.

This ability to have multiple levels of inheritance is based on the Method Resolution Order (MRO), if you are interested in learning more.

Inheritance in Ansible

Ansible provides the ability to have set the hash_behaviour, however the default is replace. With this, it works the same way as inheritance, in that the more specific attribute overrides the parent.

Given the files:

group_vars/all.yml


ntp:
  - 10.1.1.1
  - 10.1.1.2

dns:
  - 10.10.10.10
  - 10.10.10.11

snmp:
  - 10.20.20.20
  - 10.20.20.21

group_vars/eu.yml


ntp:
  - 10.200.200.1
  - 10.200.200.2

host_vara/lon-rt01.yml


snmp:
  - 10.150.150.1
  - 10.150.150.2

Would result in the variables being “flattened” via inheritance to:


ntp:
  - 10.200.200.1
  - 10.200.200.2

dns:
  - 10.10.10.10
  - 10.10.10.11

snmp:
  - 10.150.150.1
  - 10.150.150.2

As the ntp would be inherited from the eu.yml file and dns inherited from the all.yml file. The layout of the inheritance structure is found in Ansible Variable Precedence documentation.

Real-Life Use Case with NAPALM

NAPALM heavily relies on inheritance to provide a consistent interface to many vendors, while still having drastically different code for each vendor. In this seriously truncated code taken directly from NAPALM’s source code, you can see how the base class (NetworkDriver) works and how a child class (IOSDriver) is implemented.

class NetworkDriver(object):

    def get_ntp_peers(self) -> Dict[str, models.NTPPeerDict]:
        """
        Returns the NTP peers configuration as dictionary.
        The keys of the dictionary represent the IP Addresses of the peers.
        Inner dictionaries do not have yet any available keys.
        Example::
            {
                '192.168.0.1': {},
                '17.72.148.53': {},
                '37.187.56.220': {},
                '162.158.20.18': {}
            }
        """
        raise NotImplementedError

class IOSDriver(NetworkDriver):

    def get_ntp_peers(self):
        """Implementation of get_ntp_peers for IOS."""
        ntp_stats = self.get_ntp_stats()
        return {
            napalm.base.helpers.ip(ntp_peer.get("remote")): {}
            for ntp_peer in ntp_stats
            if ntp_peer.get("remote")
        }

This way, each class that inherits from the base class clearly indicates which methods it implements (by explicitly overwriting those methods) and which ones it doesn’t (and thus will raise a NotImplementedError if called).

Inheritance in Django

One great example of Inheritance is Django. Its usage of object-oriented programming can be amazing when you understand it and frustrating when you don’t. Knowing how the MRO works and is implemented in Django is a must to start to understand how Django works. In that pursuit, I have found the “classy” pages for Django and DRF to be helpful.


Conclusion

Inheritance is used extensively throughout object oriented-programming languages such as Python. Additionally, there are concepts are used elsewhere, such as in Ansible’s variable structure.

Understanding the concept can help you in your day-to-day automation as well as provide the ability to build more scalable solutions.

-Ken



ntc img
ntc img

Contact Us to Learn More

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