Reading the SSH Output – about 20 minutes
Our next step is conceptually pretty straightforward, but the details are a bit complicated. We’re using Paramiko to pretend programmatically like we are a human being using an SSH connection. We send commands to the host. The host executes the command and then sends back the results. That concept is sensible. The real world introduces complications for us though. The real world introduces things like latency due to packets flying all over the internet. So we need to handle the real world.
Our solution for handling the real world is to improve how we are reading data back from the host. Our current effort to retrieve the data is:
results = channel.recv(1024).decode('ascii')
For the purpose of testing this is a decent solution. We’re calling the Paramiko
recv(). That function reads whatever data the device has already sent back to our program. That does not account for the latency of the real world.
So we now introduce our new function:
results = send_command_and_get_response(channel, "?")
Note that this is a new function we’re calling. The function combines sending the command to the device plus making sure we read back from the device, even if there is latency.
interval = 0.1
maxseconds = 10
maxcount = maxseconds / interval
bufsize = 1024
# Poll until completion or timeout
# Note that we cannot directly use the stdout file descriptor
# because it stalls at 64K bytes (65536).
input_idx = 0
timeout_flag = False
start = datetime.datetime.now()
start_secs = time.mktime(start.timetuple())
output = ''
data = channel.recv(bufsize).decode('ascii')
output += data
# Timeout check
now = datetime.datetime.now()
now_secs = time.mktime(now.timetuple())
et_secs = now_secs - start_secs
if et_secs > maxseconds:
timeout_flag = True
rbuffer = output.rstrip(' ')
if len(rbuffer) > 0 and (rbuffer[-1] == '#' or rbuffer[-1] == '>'): ## got a Cisco command prompt
data = session.recv(bufsize)
output += data.decode('ascii')
def send_command_and_get_response(channel, cmd):
if not cmd.endswith("\n"):
cmd += "\n"
results = get_command_results(channel)
Our new function starts out by making sure that we always add a “line feed” (think of the enter key) to the end of our command. Then is delegates its real work to the
get_command_results() function. Now, that’s where things get a little more complicated.
You can wade into some deeper water with me here or just skip to the next section. We’re going to look at the techniques we’re using to read the response from the device.
get_command_results() is, at its heart, an infinite loop that starts with
while True:. Each time the program goes through the loop it is going to evaluate the statements in order.
- Check whether there is anything to be read back from the device (
channel.recv_ready()). Read back the data if there is anything.
- If the channel doesn’t have any further data then
exit_status_ready()returns True. In that case we’ll hit the
breakwill cause our
whileloop to end.
- Next we check whether we’ve been waiting for too long. If our function has been trying for longer than
breakout of the loop. We need to be patient enough to wait for the response, but we can’t just wait forever. So we make a judgement call on what the
- So we check next whether we have received from the device a Cisco command prompt. That would indicate that the device is finished responding to this command. We’re looking at the
outputand copying it to
rbuffer. If our results ends with either a hash sign (
#) or a greater than sign (
>) then we
breakout of the
whileloop. We’re assuming that either of those characters at the end of the results indicates a command prompt.
- Then we take a little rest before looping around and checking whether there’s any more info.
After the loop we take one last look with
recv_ready() to see if we can get any further data. And then we return everything we’ve received.