Easy network automation with Ansible & Expect

These days I’ve been testing different things at work related to network automation. Most of the development that I’ve done is related to Junos platforms, netconf and ansible. It’s been really fun to learn different things such as Jinja2/Yaml/Netconf/XML(Path)/Networkx….
I see all of these things like lego blocks that are just waiting to be assembled into something bigger and actually pretty powerful.

But well, as an introduction to a series of posts that I’ll try to do soon related to network automation, let me introduce a really humble approach to a well-known tool (expect) and its integration with Ansible.

The task is basically to log into several hosts and execute a series of ‘show’ commands to retrieve some information.
(I’ve seen documentation related with an expect module integrated already on Ansible but what I’m trying to do here is to show how to run a script via Ansible and by doing so, make it multi-threading).

First, we have a simple Expect script.
We pass 3 variables via command line:
1.- Ip address of the host
2.- Command list file
3.- Host name (for log file naming purposes)

I named the expect script ‘get_commands.expect’ (it will be referenced by Ansible below).

#!/usr/bin/expect
set USER xxxx
set PASS yyyy
set HOST [lindex $argv 0]

set COMMAND_LIST [open [lindex $argv 1]]
set commands [split [read $COMMAND_LIST] "\n"]
close $COMMAND_LIST

set HOSTNAME [lindex $argv 2]
set timeout 60

set FILE_DIR [lindex $argv 3]

log_file -noappend ${FILE_DIR}/${HOSTNAME}_results.log

spawn telnet $HOST
expect {
	"ogin:" { send "$USER\r" }
	"sername" { send  "$USER\r" }
}
expect "assword:"
send "$PASS\r"

foreach cmd $commands {
	expect "$USER@"
	send "$cmd\r"
}
send "\r"
close $spawn_id

The idea for passing commands via the for loop came from here

We could have a nested ‘for’ referencing each target host pulled from another file (like in the link). But if we do this, we would be having the telnet session built sequentially for each of the hosts, by this I mean that we would have to wait for the commands to be executed at one host before being able to move to the next host and get the info.

Instead of that, we use ansible to do the multi-threading work for us, so we can log into all the target hosts at once and extract the necessary information given by the commands defined by a command list file:

---
- name: Fetching Juniper Commands
  hosts: target_hosts
  gather_facts: no
  serial: 100%

  tasks:
  - set_fact:
      script_dir: "scripts"
  - name: Executing the expect script
    script: ./{{ script_dir }}/get_commands.expect {{ ansible_ssh_host }} {{ commands }} {{ inventory_hostname }} {{ log_dir }}

For this to work we also need the hosts file to have the group named [target_hosts] and the necessary login information for each target.
We need to have a .txt including the show commands as well.
For example, the command_list.txt (referenced inside the ansible playbook and passed as an argument to the expect script as {{ commands }}) could have the next commands:

cat scripts/command_list.txt

show bgp summary
show bgp neighbor
show route summary
show bgp summary

{{ log_dir }} is defined inside the group_vars yml file. This is useful to define the same directory to keep all the log files for each router.
cat target_hosts.yml

---
log_dir: /xxxx/logs

When we pass this as an argument to the expect script and the other necessary info to make the telnet sessions, then the magic occurs.

I’ll be publishing more and more regarding these topics, especially regarding netconf and pyez (a library written in python released by Juniper to make automation easier), so keep in touch.

I hope this is useful to somebody 🙂

goliath

Anuncios