First steps with Python and Junos

I’m just spending the day trying to get my head around some very basic automation, so I thought I would install Python 2.7 and work through some of the tutorials on the Techwiki to see how I get on.

The tutorial I’m following is called Python for Non-Programmers and offers an easy way in for people like me. Of course as with a lot of open-source stuff, you don’t just download Python and get started – there are various dependencies, and dependencies with dependencies that you need to install first. If at the end of all that you’ve not forgotten what your name is or why you were doing all this shenanigans in the first place you can count yourself as winning.

Installation on a Windows machine is documented nicely here, but there are a few things which are out of date.

One part where it tells you run an executable to install ‘lxml’ but there isn’t one when you click the link provided. So instead you need to install it with a program called pip, which can be found in C:\Python27\scripts by default. pip is not in the PATH envirronment variable by default either, so you need to use .\pip to invoke it at the powershell command line – see below:

Then it tells you to go off and get ncclient from the Juniper Github repository. But that doesn’t exist any more. Instead it appears to get installed as part of the ‘junos-eznc’ installation. See below:

So far so good! Now it was time to test it. I copied the sample script to do this and filled in the IP address, username and password to be used – I created this using the IDLE python development environment:

This was a bit confusing. I could SSH to the box just fine with those credentials, so it couldn’t be that surely? I did a bit of googling and ended up looking at the code in device.py. Fortunately it is well commented, and I found this:

except NcErrors.SSHError as err:
# this is a bit of a hack for now, since we want to
# know if the connection was refused or we simply could
# not open a connection due to reachability. so using
# a timestamp to differentiate the two conditions for now
# if the diff is < 3 sec, then assume the host is
# reachable, but NETCONF connection is refushed.
ts_err = datetime.datetime.now()
diff_ts = ts_err - ts_start
if diff_ts.seconds < 3:
raise EzErrors.ConnectRefusedError(self)

So it looks like it tries to connect to the device, and if a connection fails within three seconds it is assumed the connection was refused. Longer than three seconds and it is assumed there is an IP reachability problem and the connection is timing out.

This made it click for me. Connections weren’t being refused for the user, but instead the netconf port was not open on the target device. So SSH worked fine, but netconf connections were ‘refused’ at a TCP level, A bit of a shame the error reporting wasn’t more precise, but we got there in the end.

Back to the router in question, and a “set system services netconf ssh” fixed it.