Site icon Karneliuk

From Python to Go 008. Object Oriented Programming Or Build Your Own Network Switch.

Hello my friend,

So far we have covered almost all possible data types in Python and Go (Golang), at least the ones we are going to use ourselves for network automation. One of these data types, which we have introduced in the previous blog post, that is object/class or struct, has without overestimations enormous importance as it opens for us doors into object oriented programming. As doors are opened, let’s enter them.

Festive Time Is Here… Make Sure You Make Most Of It.

Meaning, apart of spending time your family and friends, cooking, eating and dancing, you also study network automation with our trainings!

We offer the following training programs in network automation for you:

During these trainings you will learn the following topics:

Moreover, we put all mentions technologies in the context of real use cases, which our team has solved and are solving in various projects in the service providers, enterprise and data centre networks and systems across the Europe and USA. That gives you opportunity to ask questions to understand the solutions in-depth and have discussions about your own projects. And on top of that, each technology is provided with online demos and labs to master your skills thoroughly. Such a mixture creates a unique learning environment, which all students value so much. Join us and unleash your potential.

Start your automation training today.

What Are We Going To Talk Today?

Object-oriented programming (OOP) is one of the very important approaches to software development. It is also one of the classification of programming languages. For example, Python is an object-oriented programming language (so is Java, for example). Being OOP language means that many internal its concepts are object-oriented and you generally shall write your application using object-oriented approach. In fact, you have already used OOP in Python if you conducted labs from previous blog post in this series, even if we haven’t explicitly said that.

In contrast, Go (Golang) is NOT object-oriented programming language. However, it has some concepts, which are very close and are very useful, so you shall know then and shall be able to use them.

With that in mind, in this blog post we are going to explore:

  1. What is object oriented programming?
  2. Where it is useful and what it is not?
  3. How to create and use objects (or alike) in Python and Go (Golang)?

Explanation

In our zero-to-hero network automation training we in-depth explain what is object oriented programming and all the associated fundamental concepts, so we encourage you to enrol to grasp them.

As we talk about network and IT infrastructure automation, let’s provide example from this area. Think about network switch. In a simplistic form, it is a network device, which is used to send packets from one interface to another. It has a number of interfaces, where users are connected, as well as a number different tables (e.g., MAC address tables). Both interfaces and these tables are existing for all the switches of the same type; however, for each specific switch its content will be different depending on its location in the network, which interface are active or not based on connected customers, etc.

From the object-oriented programming perspective:

The real switch is far more complicated, both in terms of methods and attributes, but this example is good enough for our explanation.

Essentially, attributes are variables contained within the object and methods are functions contained within the object. Both of these concepts are known to you already. You may think, what’s the big deal to have both variables and functions enclosed in something. That’s actually a real deal: think you may need to a hundred of network switches to represent your network programmatically:

As you can imagine, variables associated with objects can be changed during the lifecycle of the object. For example, the MAC address table can be updated with new MAC addresses learned as well as cleaned out of entries, which are outdated. That’s important capability of objects, you shall think about: They aren’t static, quite opposite, they can change.

Let’s see how you can put this knowledge in practice.

Examples

I was originally thinking to code the example above with switches, and network devices being objects, but it ended up being too long and complicated code. Hence, for the piratical code we’ll rewrite the example from the previous blog post: the rewrite will focus on moving standalone functions inside classes to become methods.

Please refer to the previous blog post for further details.

Python

Here is the modified code to embrace object oriented programming in Python:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
"""From Python to Go: Python: 008 - Object-oriented programming"""

# Import os
import os
from  typing import List


# Data models
class User:
    """Class to store user credentials"""
    def __init__(self, username: str = None, password: str = None):
        self.username: str = username
        self.password: str = password

    def get_credentials(self):
        """Function to retrieve credentials from the environment"""
        self.username, self.password = os.getenv("AUTOMATION_CREDS").split(",")


class Device:
    """Class to store device information"""
    def __init__(self, name: str, port: int, nos: str = None, ip: str = None):
        self.name: str = name
        self.port: int = port
        self.nos = nos
        self.ip = ip


class Inventory:
    """Class to store inventory information"""
    def __init__(self):
        self.devices: List[Device] = []

    def populate(self):
        """Function to retrieve inventory from the environment"""
        # Loop through the environment variables
        for key, value in os.environ.items():
            # Check if the key starts with AUTOMATION_DEVICE_
            if key.startswith('AUTOMATION_DEVICE_'):
                # Split the value by comma and create a new device object
                split_value = value.split(',')
                self.devices.append(
                    Device(
                        name=split_value[0],
                        port=int(split_value[1]),
                        nos=split_value[3],
                        ip=split_value[2],
                    )
                )


# Execution
if __name__ == "__main__":
    # Get the credentials
    user = User()
    user.get_credentials()

    # Print the credentials
    print(f"Username: {user.username}")
    print(f"Password: {user.password}")

    # Get the inventory
    inventory = Inventory()
    inventory.populate()

    # Print inventory memory address
    print(f"Memory address of inventory: {id(inventory):#x}")

    # Print the inventory
    print("Inventory:")
    for device in inventory.devices:
        print(f"Device: {device.name}")
        print(f"Port:   {device.port}")
        print(f"IP:     {device.ip}")
        print(f"NOS:    {device.nos}")
        print("\n")

What is new here:

  1. In addition to built-in constructor method “__init__()” we have now also custom user-defined method “get_credentials()” within the “User()” class and “populate()” within the “Inventory()” class.
  2. We instantiate the object using the same mechanics as in the previous blog post “object_instance = class()”.
  3. When we want to call a method on the object, we do it using “object.method()” syntax. Remember, method is a function, so it has all the same capabilities and attributes: it can have arguments, it can produce and return result. The main difference is that it MUST have the first argument pointing to the object itself signaled by “self” keyword.

We don’t have this example here, but you can also call the method from the object inside if this required by your business logic. This can be can be achieved using syntax “self.method()”: so it is identical to calling the attributes from the object using “self.attribute”.

The result of the execution is identical, which is exactly what’s expected:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
Username: karneliuk
Password: lab
Memory address of inventory: 0x7fbe96ed3880
Inventory:
Device: leaf-1
Port:   830
IP:     192.168.1.1
NOS:    arista-eos


Device: leaf-2
Port:   22
IP:     192.168.1.1
NOS:    cisco-nxos

Go (Golang)

It is a turn of Go (Golang) now:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/* From Python to Go: Go (Golang): 008 - Object-oriented programming */

package main

// Imports
import (
    "fmt"
    "os"
    "strconv"
    "strings"
)

// Data types
type User struct {
    // Class to store user credentials
    username string
    password string
}

func (u *User) getCredentials() {
    // Function to retrieve credentials from the environment
    blocks := strings.Split(os.Getenv("AUTOMATION_CREDS"), ",")
    (*u).username = blocks[0]
    (*u).password = blocks[1]
}

type Device struct {
    // Class to store device information
    hostname string
    port     uint64
    ip       string
    nos      string
}

// Class to store inventory information
type Inventory []Device

// Aux functions

func (i *Inventory) populate() {
    // Loop through the environment variables
    for _, kv := range os.Environ() {
        // Check if the key starts with AUTOMATION_DEVICE_
        if strings.Contains(kv, "AUTOMATION_DEVICE_") {
            // Split the value by comma and create a new device object
            blocks := strings.Split(strings.Split(kv, "=")[1], ",")

            devicePort, err := strconv.ParseUint(blocks[1], 10, 64)
            if err != nil {
                fmt.Printf("Got error when converting string to uint: %v\n", err)
                os.Exit(1)
            }

            *i = append(*i, Device{
                blocks[0],
                devicePort,
                blocks[3],
                blocks[2],
            })
        }
    }
}

// Main function
func main() {
    /* Main business logic */

    // Get the credentials
    user := User{}
    user.getCredentials()

    // Print credentails
    fmt.Printf("%+v\n", user)

    // Get inventory
    inventory := Inventory{}
    inventory.populate()

    // Print inventory memory address
    fmt.Printf("Memory address of inventory: %v\n", &inventory)

    // Print inventory content
    fmt.Printf("%+v\n", inventory)
}

Details:

  1. First thing first, Go (Golang) doesn’t have objects and methods. It has structs and receiver functions as a closest implementation. That is important to outline this from terminology perspective. However, structs and receiver functions from look and feel perspective are very similar to objects.
  2. The syntax of receiving object is “func (var_name receiving_data_type) func_name(args) …”, where:
    1. var_name” is how the instance of the object is called from within the receiver function. Think about it as “self” in Python.
    2. receiving_data_type” is the data type your instance shall so that you can use this receiver function.
    3. Other details are identical to function in Go (Golang) (link to blog). Comparing to Python, you don’t need to add “self” or anything else to arguments of the function, you already do it via definition of receiver.
  3. As Go (Golang) is pass by value language, in majority of cases you would need to specify pointer to receiving data type if you want to change the content of your struct. This is what you see in both examples above (e.g., “(u *User)” ).If you don’t want to change the content of your original struct, you don’t have to use pointer to a receiving data type.

In the same we mentioned it on Python, if it is needed per your business logic, you can call one receiver function from another one.

Let’s execute this small application developed in Go (Golang):


1
2
3
4
$ go run .
{username:karneliuk password:lab}
Memory address of inventory: &[{leaf-1 830 arista-eos 192.168.1.1} {leaf-2 22 cisco-nxos 192.168.1.1}]
[{hostname:leaf-1 port:830 ip:arista-eos nos:192.168.1.1} {hostname:leaf-2 port:22 ip:cisco-nxos nos:192.168.1.1}]

Lessons in GitHub

You can find the final working versions of the files from this blog at out GitHub page.

Conclusion

Majority of the existing libraries in Python and to a degree in Go (Golang) are created using object-oriented programming. The idea to held together certain variables, do operations over them or using them and continue storing the change state is very powerful and elegant Moreover, it is a working battle-proof idea. We will see it implemented in CLI/NETCONF/GNMI clients both in Python and Go (Golang), we will see it in database clients, in parsers, etc. Take care and good bye.

Support us






P.S.

If you have further questions or you need help with your networks, I’m happy to assist you, just send me message. Also don’t forget to share the article on your social media, if you like it.

BR,

Anton Karneliuk 

Exit mobile version