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.
- First, we see it refers to
CLASS_MAPPER
1
platforms = list(CLASS_MAPPER.keys())
- We then see
CLASS_MAPPER
refers tonew_mapper
1
CLASS_MAPPER = new_mapper
new_mapper
then refers toCLASS_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:
- Extract the key (
k
) and value (v
) of each dictionary entry. For example:k
=a10
v
=<class 'netmiko.a10.a10_ssh.A10SSH'>
- We then add the key and value to our
new_mapper
dictionary - Next we append
_ssh
to the key’s name. For example:alt_key
=a10_ssh
- 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