Automating vJunOS and vEOS using OOP in Python (Pydantic)

In my last post I scripted automating a standup of a KVM lab w/ vJUNOS, vEOS and some ubuntu containers.

I don’t quite like the code and it’s difficult to maintain so many ‘if’ and ‘for’ statements to reparse an inventory files key/values. There is an easy solution to this and that is using an object.

Using objects shortcuts a lot of the re-parsing of the inventory file and just parses the file once to create an object for a given KVM or container type. The drawbacks of using objects in python is that I must use Pydantic which seems to add a few more seconds of delay and Object Oriented Programming (OOP) in Python may not be beginner friendly. From my viewpoint it’s best to write code based on who is going to maintain it. Such that I wouldn’t write Object-Oriented Python and hand it off to a beginner in Python and expect them to maintain it.

What’s nice about objects however is that I can define a list of Object attributes and make them optional. Such that if I begin to code this project for a Nokia, Palo Alto, etc. VM they may have specific parameters that vJunOS or vEOS don’t have. And for those absent parameters I can simply make them optional during object instantiation.

Observe in the following output the ‘Optional’ word which makes that attribute optional during object instantiation:

class KVM(BaseModel):
    hostname: str
    loopbacks: Optional[list] = None
    type: str
    management_mac: str
    mgmtip: Optional[str] = None
    interfaces: Optional[list] = None
    image_name: Union[list, str, dict]
    role: Optional[str] = None
    console_port: Optional[int] = None
    vlans: Optional[list] = None
    L3_VRF: Optional[list] = None
    mlag: Optional[str] = None
    bridge_domain: Optional[list] = None
    linecards: Optional[list] = None

The functions that then use these objects appear much cleaner:

    def init_tap(self):
        create tap interfaces

        for i in self.interfaces:
            print(f"creating tap interface {self.hostname}-{i['interface']}")
            os.system(f"ip tuntap add {self.hostname}-{i['interface']} mode tap")

As opposed to the previous function I had written without OOP:

def init_tap(kvm_name = ()):
  for vm in kvm_name:
    for host in kvm_inventory['kvm_nodes']:
      if vm == host['hostname']:  
        for i in host['interfaces']:
            print(f"creating tap interface {host['hostname']}-{i['interface']}")
            os.system(f"ip tuntap add {host['hostname']}-{i['interface']} mode tap")

Which function is more friendly to maintain over time? Or what if I change the structure of the inventory.yml and suddenly what I’m parsing for in many different functions has changed I must now update them all? That’s why I am really liking OOP when it comes to this project.

I’ve posted in my git-hub the code that has all of the previous functionality of the last automated scripts but now using OOP.

I’m not going to re-write how to call the CLI tools as that is all covered in the previous post. The only new additions are creating sub-folders for each project and also separate inventory files. This keeps file organization much cleaner if I’m running multiple different projects at the same time.

Leave a Reply