Automation Principles – Inheritance

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!

Author