Codementor Events

The Python API for Juniper Networks

Published Nov 29, 2018

Learn about Juniper networks and PyEZ in this guest post by Eric Chou, the author of Mastering Python Networking – Second Edition.

Juniper networks have always been a favorite among the service provider crowd. If you take a look at the service provider vertical, it would make sense that automating network equipment is on the top of their list of requirements. Before the dawn of cloud-scale data centers, service providers were the ones with the most network equipment.

A typical enterprise network might have a few redundant internet connections at the corporate headquarter with a few hub-and-spoke remote sites connected back to the HQ using the service provider's private MPLS network. To a service provider, they are the ones who need to build, provision, manage, and troubleshoot the connections and the underlying networks.

They make their money by selling the bandwidth along with value-added managed services. It would make sense for the service providers to invest in automation to use the least amount of engineering hours to keep the network humming along. In their use case, network automation is the key to their competitive advantage.

The difference between a service provider's network needs compared to a cloud data center is that, traditionally, service providers aggregate more services into a single device. A good example would be Multiprotocol Label Switching (MPLS) that almost all major service providers provide but rarely adapt in the enterprise or data center networks. Juniper has identified this need and excelled at fulfilling the service provider requirements of automating.

This article will take you through some of Juniper's automation APIs.

Juniper and NETCONF

The Network Configuration Protocol (NETCONF) is an IETF standard, which was first published in 2006 as RFC 4741and later revised in RFC 6241. Juniper networks contributed heavily to both of the RFC standards. In fact, Juniper was the sole author for RFC 4741. It makes sense that Juniper devices fully support NETCONF, and it serves as the underlying layer for most of its automation tools and frameworks. Some of the main characteristics of NETCONF include the following:

  1. It uses Extensible Markup Language (XML) for data encoding.
  2. It uses Remote Procedure Calls (RPC).Therefore, in the case of HTTP(s) as the transport, the URL endpoint is identical while the operation intended is specified in the body of the request.
  3. It is conceptually based on layers from top to bottom. The layers include the content, operations, messages, and transport:
    1.PNG

Juniper networks provide an extensive NETCONF XML management protocol developer guide in its technical library. It’s time to take a look at its usage.

Device preparation

In order to start using NETCONF, create a separate user as well as turn on the required services:

set system login user netconf uid 2001
    set system login user netconf class super-user
    set system login user netconf authentication encrypted-password
     "$1$0EkA.XVf$cm80A0GC2dgSWJIYWv7Pt1"
    set system services ssh
    set system services telnet
    set system services netconf ssh port 830

On the Juniper device, you can always take a look at the configuration either in a flat file or in XML format. The flat file comes in handy when you need to specify a one-liner command to make configuration changes:

netconf@foo> show configuration | display set
    set version 12.1R1.9
    set system host-name foo
    set system domain-name bar
<omitted>

The XML format comes in handy at times when you need to see the XML structure of the configuration:

netconf@foo> show configuration | display xml
<rpc-reply xmlns:junos="http://xml.juniper.net/junos/12.1R1/junos">
<configuration junos:commit-seconds="1485561328"junos:commit-
localtime="2017-01-27 23:55:28 UTC"junos:commit-user="netconf">
<version>12.1R1.9</version>
<system>
<host-name>foo</host-name>
<domain-name>bar</domain-name>

You’re now ready to look at your first Juniper NETCONF example.

Juniper NETCONF examples

Here’s a pretty straightforward example to execute show version. Name this file junos_netconf_1.py:

#!/usr/bin/env python3

  from ncclient import manager

  conn = manager.connect(
      host='192.168.24.252',
      port='830',
      username='netconf',
      password='juniper!',
      timeout=10,
      device_params={'name':'junos'},
      hostkey_verify=False)

  result = conn.command('show version', format='text')
  print(result)
  conn.close_session()

All the fields in the script should be pretty self-explanatory, with the exception of device_params. Starting with ncclient 0.4.1, the device handler was added to specify different vendors or platforms. For example, the name can be juniper, CSR, Nexus, or Huawei. You’ve also added hostkey_verify=False because you’re using a self-signed certificate from the Juniper device.

The returned output is rpc-reply encoded in XML with an output element:

<rpc-reply message-id="urn:uuid:7d9280eb-1384-45fe-be48-
    b7cd14ccf2b7">
<output>
Hostname: foo
    Model: olive
    JUNOS Base OS boot [12.1R1.9]
    JUNOS Base OS Software Suite [12.1R1.9]
<omitted>
    JUNOS Runtime Software Suite [12.1R1.9]
    JUNOS Routing Software Suite [12.1R1.9]
</output>
</rpc-reply>

You can parse the XML output to just include the output text:

print(result.xpath('output')[0].text)

In junos_netconf_2.py, you can make configuration changes to the device. Start with some new imports for constructing new XML elements and the connection manager object:

      #!/usr/bin/env python3

      from ncclient import manager
      from ncclient.xml_ import new_ele, sub_ele

      conn = manager.connect(host='192.168.24.252', port='830', 
    username='netconf' , password='juniper!', timeout=10, 
    device_params={'name':'junos'}, hostkey_v erify=False)

Lock the configuration and make configuration changes:

      # lock configuration and make configuration changes
      conn.lock()

      # build configuration
      config = new_ele('system')
      sub_ele(config, 'host-name').text = 'master'
      sub_ele(config, 'domain-name').text = 'python'

Under the build configuration section, create a new element of system with subelements of host-namre and domain-name. If you were wondering about the hierarchy structure, you can see from the XML display that the node structure with system is the parent of host-name and domain-name:

<system>
<host-name>foo</host-name>
<domain-name>bar</domain-name>
    ...
</system>

After the configuration is built, the script will push the configuration and commit the configuration changes. These are the normal best practice steps (lock, configure, unlock, commit) for Juniper configuration changes:

      # send, validate, and commit config
      conn.load_configuration(config=config)
      conn.validate()
      commit_config = conn.commit()
      print(commit_config.tostring)

      # unlock config
      conn.unlock()

      # close session
      conn.close_session()

The following example combines the code with a few Python functions:

# make a connection object
def connect(host, port, user, password):
    connection = manager.connect(host=host, port=port, username=user,
            password=password, timeout=10, device_params={'name':'junos'},
            hostkey_verify=False)
    return connection

# execute show commands
def show_cmds(conn, cmd):
    result = conn.command(cmd, format='text')
    return result

# push out configuration
def config_cmds(conn, config):
    conn.lock()
    conn.load_configuration(config=config)
    commit_config = conn.commit()
    return commit_config.tostring

This file can be executed by itself, or it can be imported to be used by other Python scripts.
Juniper also provides a Python library to be used with their devices called PyEZ. You’ll now take a look at a few examples of using the library.

Juniper PyEZ for developers

PyEZ is a high-level Python implementation that integrates better with your existing Python code. By utilizing the Python API, you can perform common operation and configuration tasks without the extensive knowledge of the Junos CLI.
Juniper maintains a comprehensive Junos PyEZ developer guide on their technical library. If you are interested in using PyEZ, it ishighly recommended that you at least take a glance through the various topics in the guide.

Installation and preparation

The installation instructions for each of the operating systems can be found on the Installing Junos PyEZ page. Here are the installation instructions for Ubuntu 16.04.

The following are some dependency packages:

$ sudo apt-get install -y python3-pip python3-dev libxml2-dev libxslt1-dev libssl-dev libffi-dev

PyEZ packages can be installed via pip:

$ sudo pip3 install junos-eznc
$ sudo pip install junos-eznc

On the Juniper device, NETCONF needs to be configured as the underlying XML API for PyEZ:

set system services netconf ssh port 830

For user authentication, you can either use password authentication or an SSH key pair. Creating the local user is straightforward:

set system login user netconf uid 2001
set system login user netconf class super-user
set system login user netconf authentication encrypted-password "$1$0EkA.XVf$cm80A0GC2dgSWJIYWv7Pt1"

For the ssh key authentication, generate the key pair on your hostfirst:

$ ssh-keygen -t rsa

By default, the public key will be called id_rsa.pub under ~/.ssh/, while the private key will be named id_rsa under the same directory. Treat the private key like a password that you never share. The public key can be freely distributed.

In this use case, move the public key to the /tmp directory and enable the Python 3 HTTP server module to create a reachable URL:

$ mv ~/.ssh/id_rsa.pub /tmp
$ cd /tmp
$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 ...

Note: For Python 2, use python -m SimpleHTTPServer instead.

From the Juniper device, you can create the user and associate the public key by downloading the public key from the Python 3 web server:

netconf@foo# set system login user echou class super-user authentication load-key-file http://192.168.24.164:8000/id_rsa.pub
/var/home/netconf/...transferring.file........100% of 394 B 2482 kBps

Now, if you try to ssh with the private key from the management station, the user will be automatically authenticated:

$ ssh -i ~/.ssh/id_rsa 192.168.24.252
--- JUNOS 12.1R1.9 built 2012-03-24 12:52:33 UTC
echou@foo>

To ensure that both of the authentication methods work with PyEZ, try the username and password combination:

Python 3.5.2 (default, Nov 17 2016, 17:05:23)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from jnpr.junos import Device
>>> dev = Device(host='192.168.24.252', user='netconf', password='juniper!')
>>> dev.open()
Device(192.168.24.252)
>>> dev.facts
{'serialnumber': '', 'personality': 'UNKNOWN', 'model': 'olive', 'ifd_style': 'CLASSIC', '2RE': False, 'HOME': '/var/home/juniper', 'version_info': junos.version_info(major=(12, 1), type=R, minor=1, build=9), 'switch_style': 'NONE', 'fqdn': 'foo.bar', 'hostname': 'foo', 'version': '12.1R1.9', 'domain': 'bar', 'vc_capable': False}
>>> dev.close()

You can also try to use the SSH key authentication:

>>> from jnpr.junos import Device
>>> dev1 = Device(host='192.168.24.252', user='echou', ssh_private_key_file='/home/echou/.ssh/id_rsa')
>>> dev1.open()
Device(192.168.24.252)
>>> dev1.facts
{'HOME': '/var/home/echou', 'model': 'olive', 'hostname': 'foo', 'switch_style': 'NONE', 'personality': 'UNKNOWN', '2RE': False, 'domain': 'bar', 'vc_capable': False, 'version': '12.1R1.9', 'serialnumber': '', 'fqdn': 'foo.bar', 'ifd_style': 'CLASSIC', 'version_info': junos.version_info(major=(12, 1), type=R, minor=1, build=9)}
>>> dev1.close()

Great! You are now ready to look at some examples for PyEZ.

PyEZ examples

In the previous interactive prompt, you already saw that when the device connects, the object automatically retrieves a few facts about the device. In the first example, junos_pyez_1.py, you were connecting to the device and executing an RPC call for show interface em1:

      #!/usr/bin/env python3
      from jnpr.junos import Device
      import xml.etree.ElementTree as ET
      import pprint

      dev = Device(host='192.168.24.252', user='juniper', passwd='juniper!')

      try:
          dev.open()
      except Exception as err:
          print(err)
          sys.exit(1)

      result = 
    dev.rpc.get_interface_information(interface_name='em1', terse=True)
      pprint.pprint(ET.tostring(result))

      dev.close()

The device class has an rpc property that includes all operational commands. This is pretty awesome because there is no slippage between what you can do in CLI versus API. The catch is that you need to find out the xml rpc element tag. In the first example, how do you know show interface em1 equates to get_interface_information? You have three ways of finding out this information:

  1. You can reference the Junos XML API Operational Developer Reference.
  2. You can use the CLI and display the XML RPC equivalent and replace the dash (-) between the words with an underscore (_).
  3. You can also do this programmatically by using the PyEZ library.
    Typically, the second option is used to get the output directly:
    netconf@foo> show interfaces em1 | display xml rpc
<rpc-reply xmlns:junos="http://xml.juniper.net/junos/12.1R1/junos">
<rpc>
<get-interface-information>
<interface-name>em1</interface-name>
</get-interface-information>
</rpc>
<cli>
<banner></banner>
</cli>
</rpc-reply>

Here is an example of using PyEZ programmatically (the third option):

>>> dev1.display_xml_rpc('show interfaces em1', format='text')
    '<get-interface-information>n <interface-name>em1</interface-
name>n</get-interface-information>n'

Of course, you’ll need to make configuration changes as well. In the junos_pyez_2.py configuration example, import an additional Config() method from PyEZ:

      #!/usr/bin/env python3
      from jnpr.junos import Device
      from jnpr.junos.utils.config import Config

Utilize the same block for connecting to a device:

      dev = Device(host='192.168.24.252', user='juniper', 
    passwd='juniper!')

      try:
          dev.open()
      except Exception as err:
          print(err)
          sys.exit(1)

The new Config() method will load the XML data and make the configuration changes:

      config_change = """
<system>
<host-name>master</host-name>
<domain-name>python</domain-name>
</system>
      """

      cu = Config(dev)
      cu.lock()
      cu.load(config_change)
      cu.commit()
      cu.unlock()

      dev.close()

The PyEZ examples are simple by design. Hope they were enough to explain you the ways you can leverage PyEZ for your Junos automation needs.

If you found this article helpful, you can explore Mastering Python Networking – Second Edition. Written by Eric Chou, a seasoned techie with over 18 years of experience, the book is packed with hands-on examples of using Python for network device automation, DevOps and SDN, and is a must-read for network engineers and programmers

Discover and read more posts from PACKT
get started
post commentsBe the first to share your opinion
Olivia Roy
9 months ago

Get Best Assignment Help for Excellent Grades. If you are struggling with an accounting assignment, you can get help from a professional accounting assignment help service. We provide you with a wide range of accounting topics, including financial statements, bookkeeping, taxes, and auditing. Visit us -https://www.rapidassignmenthelp.co.uk/accounting-assignment-help

writingonlinenet
5 years ago

Thank you for the article! Very valuable information. I liked it and it came in handy. If you need real reviews about companies that write work for study, look at https://writing-online.net. They are the best.

Nora Cantrell
5 years ago

For the networks there is the dire need of some improvements in the technology. For further details <a href=“https://eliteassignmenthelp.com/college-assignments”>elite assignment help</a> is going to show some essential pro tips for the usage.

Show more replies