This is the third post in this series. If you missed Part 2, you can find it here.

Dissecting platforms

Though it appears platforms is only 2 lines:

1
2
platforms = list(CLASS_MAPPER.keys())
platforms.sort()

There’s actually a lot more to it. Let’s take a look at what I mean.

  1. First, we see it refers to CLASS_MAPPER
1
platforms = list(CLASS_MAPPER.keys())
  1. We then see CLASS_MAPPER refers to new_mapper
1
CLASS_MAPPER = new_mapper
  1. new_mapper then refers to CLASS_MAPPER_BASE

To understand what’s happening, we need to look at the above list in reverse order. Putting it visually, the flow looks like this:

1
CLASS_MAPPER_BASE => new_mapper => CLASS_MAPPER => platforms

Let’s get started…

Dissecting CLASS_MAPPER_BASE

CLASS_MAPPER_BASE is actually very straight forward. It’s simply a dictionary which maps device_type strings to their classes. These classes were imported at the top of the file.

Here’s what it looks like when the code is run:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pprint import pprint

pprint(CLASS_MAPPER_BASE)
{'a10': <class 'netmiko.a10.a10_ssh.A10SSH'>,
 'accedian': <class 'netmiko.accedian.accedian_ssh.AccedianSSH'>,
 'alcatel_aos': <class 'netmiko.alcatel.alcatel_aos_ssh.AlcatelAosSSH'>,
 'alcatel_sros': <class 'netmiko.nokia.nokia_sros_ssh.NokiaSrosSSH'>,
 'apresia_aeos': <class 'netmiko.apresia.apresia_aeos.ApresiaAeosSSH'>,
 'arista_eos': <class 'netmiko.arista.arista.AristaSSH'>,
 'aruba_os': <class 'netmiko.aruba.aruba_ssh.ArubaSSH'>,
 'avaya_ers': <class 'netmiko.extreme.extreme_ers_ssh.ExtremeErsSSH'>,
 'avaya_vsp': <class 'netmiko.extreme.extreme_vsp_ssh.ExtremeVspSSH'>,
 'brocade_fastiron': <class 'netmiko.ruckus.ruckus_fastiron.RuckusFastironSSH'>,
 'brocade_netiron': <class 'netmiko.extreme.extreme_netiron.ExtremeNetironSSH'>,
 'brocade_nos': <class 'netmiko.extreme.extreme_nos_ssh.ExtremeNosSSH'>,
 'brocade_vdx': <class 'netmiko.extreme.extreme_nos_ssh.ExtremeNosSSH'>,
 'brocade_vyos': <class 'netmiko.vyos.vyos_ssh.VyOSSSH'>,
 'calix_b6': <class 'netmiko.calix.calix_b6.CalixB6SSH'>,
 
 # and so on...
}

Now that we’ve got CLASS_MAPPER_BASE covered off, let’s see what new_mapper does with it.

Dissecting new_mapper

new_mapper is defined twice. The first time is for ssh mappings. And the second time is for file transfer mappings. We’re going to be investigating the SSH mapping code.

This piece of code runs a for loop against CLASS_MAPPER_BASE. In the loop we:

  1. Extract the key (k) and value (v) of each dictionary entry. For example:
    • k = a10
    • v = <class 'netmiko.a10.a10_ssh.A10SSH'>
  2. We then add the key and value to our new_mapper dictionary
  3. Next we append _ssh to the key’s name. For example:
    • alt_key = a10_ssh
  4. Finally, we add the new key and existing value to the new_mapper dictionary.

The result? We have two ways names which can be used to instrument an SSH connection to a platform. e.g a10 and a10_ssh both call the netmiko.a10.a10_ssh.A10SSH class:

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
pprint(new_mapper)
{'a10': <class 'netmiko.a10.a10_ssh.A10SSH'>,
 'a10_ssh': <class 'netmiko.a10.a10_ssh.A10SSH'>,
 'accedian': <class 'netmiko.accedian.accedian_ssh.AccedianSSH'>,
 'accedian_ssh': <class 'netmiko.accedian.accedian_ssh.AccedianSSH'>,
 'alcatel_aos': <class 'netmiko.alcatel.alcatel_aos_ssh.AlcatelAosSSH'>,
 'alcatel_aos_ssh': <class 'netmiko.alcatel.alcatel_aos_ssh.AlcatelAosSSH'>,
 'alcatel_sros': <class 'netmiko.nokia.nokia_sros_ssh.NokiaSrosSSH'>,
 'alcatel_sros_ssh': <class 'netmiko.nokia.nokia_sros_ssh.NokiaSrosSSH'>,
 'apresia_aeos': <class 'netmiko.apresia.apresia_aeos.ApresiaAeosSSH'>,
 'apresia_aeos_ssh': <class 'netmiko.apresia.apresia_aeos.ApresiaAeosSSH'>,
 'arista_eos': <class 'netmiko.arista.arista.AristaSSH'>,
 'arista_eos_ssh': <class 'netmiko.arista.arista.AristaSSH'>,
 'aruba_os': <class 'netmiko.aruba.aruba_ssh.ArubaSSH'>,
 'aruba_os_ssh': <class 'netmiko.aruba.aruba_ssh.ArubaSSH'>,
 'avaya_ers': <class 'netmiko.extreme.extreme_ers_ssh.ExtremeErsSSH'>,
 'avaya_ers_ssh': <class 'netmiko.extreme.extreme_ers_ssh.ExtremeErsSSH'>,
 'avaya_vsp': <class 'netmiko.extreme.extreme_vsp_ssh.ExtremeVspSSH'>,
 'avaya_vsp_ssh': <class 'netmiko.extreme.extreme_vsp_ssh.ExtremeVspSSH'>,
 'brocade_fastiron': <class 'netmiko.ruckus.ruckus_fastiron.RuckusFastironSSH'>,
 'brocade_fastiron_ssh': <class 'netmiko.ruckus.ruckus_fastiron.RuckusFastironSSH'>,
 'brocade_netiron': <class 'netmiko.extreme.extreme_netiron.ExtremeNetironSSH'>,
 'brocade_netiron_ssh': <class 'netmiko.extreme.extreme_netiron.ExtremeNetironSSH'>,
 'brocade_nos': <class 'netmiko.extreme.extreme_nos_ssh.ExtremeNosSSH'>,
 'brocade_nos_ssh': <class 'netmiko.extreme.extreme_nos_ssh.ExtremeNosSSH'>,
 'brocade_vdx': <class 'netmiko.extreme.extreme_nos_ssh.ExtremeNosSSH'>,
 'brocade_vdx_ssh': <class 'netmiko.extreme.extreme_nos_ssh.ExtremeNosSSH'>,
 'brocade_vyos': <class 'netmiko.vyos.vyos_ssh.VyOSSSH'>,
 'brocade_vyos_ssh': <class 'netmiko.vyos.vyos_ssh.VyOSSSH'>,
 'calix_b6': <class 'netmiko.calix.calix_b6.CalixB6SSH'>,
 
 # and so on...

You’re probably wondering why we need two ways to reference the same class. The reason becomes apparent when we dissect CLASS_MAPPER.

Dissecting CLASS_MAPPER

We can see that CLASS_MAPPER starts life as pointer to new_mapper.

It’s very important to understand the difference between a pointer and a copy of an object. If you don’t know the difference, it’s worth having a read of this article.

Let’s see what CLASS_MAPPER looks like 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
pprint(CLASS_MAPPER)
{'a10': <class 'netmiko.a10.a10_ssh.A10SSH'>,
 'a10_ssh': <class 'netmiko.a10.a10_ssh.A10SSH'>,
 'accedian': <class 'netmiko.accedian.accedian_ssh.AccedianSSH'>,
 'accedian_ssh': <class 'netmiko.accedian.accedian_ssh.AccedianSSH'>,
 'alcatel_aos': <class 'netmiko.alcatel.alcatel_aos_ssh.AlcatelAosSSH'>,
 'alcatel_aos_ssh': <class 'netmiko.alcatel.alcatel_aos_ssh.AlcatelAosSSH'>,
 'alcatel_sros': <class 'netmiko.nokia.nokia_sros_ssh.NokiaSrosSSH'>,
 'alcatel_sros_ssh': <class 'netmiko.nokia.nokia_sros_ssh.NokiaSrosSSH'>,
 'apresia_aeos': <class 'netmiko.apresia.apresia_aeos.ApresiaAeosSSH'>,
 'apresia_aeos_ssh': <class 'netmiko.apresia.apresia_aeos.ApresiaAeosSSH'>,
 'apresia_aeos_telnet': <class 'netmiko.apresia.apresia_aeos.ApresiaAeosTelnet'>,
 'arista_eos': <class 'netmiko.arista.arista.AristaSSH'>,
 'arista_eos_ssh': <class 'netmiko.arista.arista.AristaSSH'>,
 'arista_eos_telnet': <class 'netmiko.arista.arista.AristaTelnet'>,
 'aruba_os': <class 'netmiko.aruba.aruba_ssh.ArubaSSH'>,
 'aruba_os_ssh': <class 'netmiko.aruba.aruba_ssh.ArubaSSH'>,
 'autodetect': <class 'netmiko.terminal_server.terminal_server.TerminalServerSSH'>,
 'avaya_ers': <class 'netmiko.extreme.extreme_ers_ssh.ExtremeErsSSH'>,
 'avaya_ers_ssh': <class 'netmiko.extreme.extreme_ers_ssh.ExtremeErsSSH'>,
 'avaya_vsp': <class 'netmiko.extreme.extreme_vsp_ssh.ExtremeVspSSH'>,
 'avaya_vsp_ssh': <class 'netmiko.extreme.extreme_vsp_ssh.ExtremeVspSSH'>,
 'brocade_fastiron': <class 'netmiko.ruckus.ruckus_fastiron.RuckusFastironSSH'>,
 'brocade_fastiron_ssh': <class 'netmiko.ruckus.ruckus_fastiron.RuckusFastironSSH'>,
 'brocade_fastiron_telnet': <class 'netmiko.ruckus.ruckus_fastiron.RuckusFastironTelnet'>,
 'brocade_netiron': <class 'netmiko.extreme.extreme_netiron.ExtremeNetironSSH'>,
 'brocade_netiron_ssh': <class 'netmiko.extreme.extreme_netiron.ExtremeNetironSSH'>,
 'brocade_netiron_telnet': <class 'netmiko.extreme.extreme_netiron.ExtremeNetironTelnet'>,
 'brocade_nos': <class 'netmiko.extreme.extreme_nos_ssh.ExtremeNosSSH'>,
 'brocade_nos_ssh': <class 'netmiko.extreme.extreme_nos_ssh.ExtremeNosSSH'>,
 'brocade_vdx': <class 'netmiko.extreme.extreme_nos_ssh.ExtremeNosSSH'>,
 'brocade_vdx_ssh': <class 'netmiko.extreme.extreme_nos_ssh.ExtremeNosSSH'>,
 'brocade_vyos': <class 'netmiko.vyos.vyos_ssh.VyOSSSH'>,
 'brocade_vyos_ssh': <class 'netmiko.vyos.vyos_ssh.VyOSSSH'>,
 'calix_b6': <class 'netmiko.calix.calix_b6.CalixB6SSH'>,
 'calix_b6_ssh': <class 'netmiko.calix.calix_b6.CalixB6SSH'>,
 'calix_b6_telnet': <class 'netmiko.calix.calix_b6.CalixB6Telnet'>,
 
 # and so on...

Here we see that CLASS_MAPPER is expanded to include _telnet and _serial classes. After having seen this, we can deduce that the _ssh suffix was added for consistency.

Finishing platforms dissection

Remember how I said that platforms is only 2 lines? Well, we still haven’t finished dissecting the first line yet! But we’re very close.

Now that we know what CLASS_MAPPER looks like, dissecting the first line of platforms is actually very easy. All we’re doing is extracting the keys from the dictionary. Let’s see how this works.

Running CLASS_MAPPER.keys() gives the the dictionary keys. However, the type of output is dict_keys:

1
2
3
4
>>> CLASS_MAPPER.keys()
dict_keys(['a10', 'a10_ssh', 'accedian', 'accedian_ssh', 'alcatel_aos', 'alcatel_aos_ssh'])
>>> type(CLASS_MAPPER.keys())
<class 'dict_keys'>

To get the output as a standard list, we simply put it in the list() function:

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
platforms = list(CLASS_MAPPER.keys())
pprint(platforms)
['a10',
 'a10_ssh',
 'accedian',
 'accedian_ssh',
 'alcatel_aos',
 'alcatel_aos_ssh',
 'alcatel_sros',
 'alcatel_sros_ssh',
 'apresia_aeos',
 'apresia_aeos_ssh',
 'arista_eos',
 'arista_eos_ssh',
 'aruba_os',
 'aruba_os_ssh',
 'avaya_ers',
 'avaya_ers_ssh',
 'avaya_vsp',
 'avaya_vsp_ssh',
 'brocade_fastiron',
 'brocade_fastiron_ssh',
 'brocade_netiron',
 'brocade_netiron_ssh',
 'brocade_nos',
 'brocade_nos_ssh',
 'brocade_vdx',
 'brocade_vdx_ssh',
 'brocade_vyos',
 'brocade_vyos_ssh',
 'checkpoint_gaia',
 'checkpoint_gaia_ssh',
 'calix_b6',

# and so on...

 ]
>>> type(platforms)
<class 'list'>

Circling back to this line ConnectHandler, we can now see Netmiko knows if we’ve passed in a supported device_type.

Dissecting ssh_dispatcher

The last two lines of ConnectHandler involves ssh_dispatcher. Thanks to the work we’ve already done, this function is actually very easy to understand. All we’re doing is taking the device_type which was passed in by the user, and we’re returning the corresponding class.

For example, let’s see what happens if we pass in a device type of cisco_ios:

1
2
CLASS_MAPPER['cisco_ios']
<class 'netmiko.cisco.cisco_ios.CiscoIosSSH'>

This class is then saved as ConnectionClass. Finally, we call this class and pass it the *args and **kwargs parameters.

This leads to the question, what are valid *args and **kwargs which we can pass to Netmiko? Looking at the “Getting Started” example, we know a few of them:

  • device_type
  • host
  • username
  • password
  • port
  • secret

Are there others though? For example, can we use keys for authentication instead usernames and password?

Challenge

In this post we examined how platforms works. The good news is that platforms_str, scp_platforms, and scp_platforms_str work in much the same way. My challenge to you is to dissect them to the same level as was done in this post. Doing so is a great way to improve your Python skills.


As always, if you have any questions or have a topic that you would like me to discuss, please feel free to post a comment at the bottom of this blog entry, e-mail at will@oznetnerd.com, or drop me a message on Reddit (OzNetNerd).

Note: The opinions expressed in this blog are my own and not those of my employer.

Leave a comment