Getting Started with Juniper and Ansible

Author: Kirk Byers
Date: 2015-03-24

In this article, I will discuss the steps required to use Ansible on Juniper equipment. We will then use Ansible to gather facts from a Juniper SRX. Finally, we will use Ansible to transfer a small configuration change to the SRX.

For more information on Ansible, see the following:

Ansible Getting Started

Introduction to Ansible Inventory

Introduction to Playbooks

In my lab environment, I have a Juniper SRX that is configured to support NETCONF over SSH:

pyclass@pynet-jnpr-srx1# show system services netconf    
ssh;

[edit]

Now Ansible will connect to this SRX using the NETCONF API from a remote server (in my case an AWS Linux server).

Before we do this, however, there are a set of things that need to be installed on the AWS server--1)the Juniper-Ansible modules, 2)Juniper's PyEZ library, and 3)Ansible itself (Ansible installation instructions)

# Downloading/extracting Juniper-Ansible modules
$ cd TEST
$ wget https://github.com/Juniper/ansible-junos-stdlib/archive/1.1.0.tar.gz
$ gunzip 1.1.0.tar.gz
$ tar -xvpf 1.1.0.tar

After I have downloaded and extracted the Juniper-Ansible modules, I then make a temporary WORK directory and a library subdirectory. This will enable Ansible to find the Juniper modules (By default, Ansible will search for modules in the ./library subdirectory of the current working directory).

$ mkdir WORK
$ cd WORK/
$ mkdir library
$ cd library/ 
$ cp ../../ansible-junos-stdlib-1.1.0/library/* .
$ ls -al
total 56
drwxrwxr-x. 2 kbyers kbyers  4096 Mar  9 15:09 .
drwxrwxr-x. 3 kbyers kbyers    98 Mar  9 15:26 ..
-rw-rw-r--. 1 kbyers kbyers     0 Mar  9 15:09 __init__.py
-rw-rw-r--. 1 kbyers kbyers  7348 Mar  9 15:09 junos_get_facts
-rw-rw-r--. 1 kbyers kbyers 14080 Mar  9 15:09 junos_install_config
-rw-rw-r--. 1 kbyers kbyers  9378 Mar  9 15:09 junos_install_os
-rw-rw-r--. 1 kbyers kbyers  4838 Mar  9 15:09 junos_shutdown
-rw-rw-r--. 1 kbyers kbyers  7554 Mar  9 15:09 junos_zeroize

After the Juniper-Ansible modules are installed, I then install Juniper's PyEZ library (this library is required by the Juniper-Ansible modules):

# Installing PyEZ library 
$ wget https://github.com/Juniper/py-junos-eznc/archive/1.1.2.tar.gz
$ gunzip 1.1.2.tar.gz  
$ tar -xvpf 1.1.2.tar 
$ cd py-junos-eznc-1.1.2/
$ python setup.py install     # sudo might be required

Finally, I need to install Ansible itself (if not already done):

$ pip install ansible       # sudo might be required

Now I am ready to start working on Ansible. I start by defining the SRX in my Ansible inventory file (note, the following is all one line):

[juniper]
pynet-sf-srx ansible_connection=local ansible_ssh_host=10.10.10.10 ansible_python_interpreter=~/applied_python/bin/python juniper_user=username juniper_passwd=password

The above definition states the following:

'ansible_connection=local'—Ansible is going to execute the Juniper module on the local computer (i.e. on the Ansible server). By default, Ansible expects to transfer the module using SSH to the remote device. But in this case, the module runs locally and then uses the Juniper API to communicate to the device.

'ansible_ssh_host=10.10.10.10'—IP address of the Juniper SRX, Juniper will make its API connection to this device (specified in playbook).

'ansible_python_interpreter = ~/applied_python/bin/python'—Juniper-Ansible modules by default try to use /usr/bin/python. I am running in a virtualenv so I needed to override this. You probably don't need this in your environment.

'juniper_user=username'—Username that Juniper will use in its NETCONF connection (specified in playbook).

'juniper_passwd=password'—Password that Juniper will use in its NETCONF connection (specified in playbook).

Now, I am ready to start testing Ansible and Juniper. Here is a simple Ansible playbook that checks that the NETCONF port on my SRX is reachable.

$ cat simple.yml 
---
- name: Verifying NETCONF
  hosts: pynet-sf-srx
  tasks: 
    - name: Verifying NETCONF
      wait_for: host={{ ansible_ssh_host }} port=830 
$ ansible-playbook simple.yml 

PLAY [Verifying NETCONF] ****************************************************** 

GATHERING FACTS *************************************************************** 
ok: [pynet-sf-srx]

TASK: [Verifying NETCONF] ***************************************************** 
ok: [pynet-sf-srx]

PLAY RECAP ******************************************************************** 
pynet-sf-srx               : ok=2    changed=0    unreachable=0    failed=0

I can then expand on this and create a playbook that will gather facts from my SRX.

$ cat facts.yml 
---
- name: Testing Juniper and Ansible
  hosts: pynet-sf-srx
  tasks: 
    - name: Verifying NETCONF
      wait_for: host={{ ansible_ssh_host }} port=830

    - name: Retrieve Juniper Facts
      junos_get_facts: host={{ ansible_ssh_host }} user={{ juniper_user }} passwd={{ juniper_passwd }}
      register: srx_info 

    - name: Print SRX information
      debug: msg="{{ srx_info.facts }}" 
$ ansible-playbook facts.yml 

PLAY [Testing Juniper and Ansible] ******************************************** 

GATHERING FACTS *************************************************************** 
ok: [pynet-sf-srx]

TASK: [Verifying NETCONF] ***************************************************** 
ok: [pynet-sf-srx]

TASK: [Retrieve Juniper Facts] ************************************************ 
ok: [pynet-sf-srx]

TASK: [Print SRX information] ************************************************* 
ok: [pynet-sf-srx] => {
    "msg": "{u'domain': None, u'serialnumber': u'BZ4614AF0938', u'ifd_style': u'CLASSIC', u'version_info': {u'major': [12, 1], u'type': u'X', u'build': 5, u'minor': [44, u'D', 35]}, u'RE0': {u'status': u'OK', u'last_reboot_reason': u'0x1:power cycle/failure', u'model': u'RE-SRX100H2', u'up_time': u'81 days, 4 hours, 6 minutes, 57 seconds'}, u'hostname': u'pynet-jnpr-srx1', u'fqdn': u'pynet-jnpr-srx1', u'has_2RE': False, u'switch_style': u'VLAN', u'version': u'12.1X44-D35.5', u'srx_cluster': False, u'HOME': u'/cf/var/home/pyclass', u'model': u'SRX100H2', u'personality': u'SRX_BRANCH'}"
} 

PLAY RECAP ******************************************************************** 
pynet-sf-srx               : ok=4    changed=0    unreachable=0    failed=0

Now that I have verified my ability to community with the SRX, let's use Ansible to make a configuration change.

First, I define a configuration file that will add a static route:

$ cat test_config.conf 

routing-options {
    static {
        route 1.1.1.0/24 next-hop 10.220.88.1;
    }
}

Then I verify the current state of the SRX:

pyclass@pynet-jnpr-srx1# show routing-options 
static {
    route 0.0.0.0/0 next-hop 10.220.88.1;
}

[edit]

Finally, I create an Ansible playbook. This playbook will load the test_config.conf file onto the SRX. Note, the playbook will automatically perform a 'commit' as part of this process.

$ cat load_config.yml 
---
- name: Testing Juniper and Ansible
  hosts: pynet-sf-srx
  tasks: 
    - name: Verifying NETCONF
      wait_for: host={{ ansible_ssh_host }} port=830

    - name: Add a static route
      junos_install_config:
        host={{ ansible_ssh_host }}
        file=test_config.conf
        overwrite=false
        user={{ juniper_user }}
        passwd={{ juniper_passwd }} 
$ ansible-playbook load_config.yml 

PLAY [Testing Juniper and Ansible] ******************************************** 

GATHERING FACTS *************************************************************** 
ok: [pynet-sf-srx] 

TASK: [Verifying NETCONF] ***************************************************** 
ok: [pynet-sf-srx] 

TASK: [Add a static route] **************************************************** 
changed: [pynet-sf-srx] 

PLAY RECAP ******************************************************************** 
pynet-sf-srx                : ok=3    changed=1    unreachable=0    failed=0   

[kbyers WORK]$

I then verify the change on the SRX:

pyclass@pynet-jnpr-srx1# show routing-options    
static {
    route 0.0.0.0/0 next-hop 10.220.88.1;
    route 1.1.1.0/24 next-hop 10.220.88.1;
}

[edit]
pyclass@pynet-jnpr-srx1#

Kirk Byers

@kirkbyers

You might also be interested in: