Topic: tech juniper jaut prev next

tech juniper jaut > Module 04: Advanced Junos PyEZ

Module 04: Advanced Junos PyEZ

PyEZ is a micro framework. PyEZ is used by the Juniper Ansible and Salt modules and by JSNAPy. Aside, Puppet and Chef use RubyEZ.

PyEZ works with any Junos device running Junos version 11.4 or later. It supports NETCONF, telnet and serial connections. From Junos 16.1, PyEZ is available for on-box scripting.

Junos PyEZ Modules

Prepare Devices to be Managed

[edit system services]
set netconf ssh

A user account must be mapped to a class with the correct permissions (the same permissions as to execute instructions through the API).

Verifying Junos PyEZ Installation

>>> from jnpr.junos.version import VERSION
>>> VERSION
'2.2.0'

pip list | grep junos-eznc

Device Connection Options

NETCONF over SSH;

Device(host=..., user=..., passwd=...)

Telnet connection;

Device(host=..., user=..., passwd=..., mode='telnet', port='23')

Telnet can be used to connect via a console server. The port number will usually be non-standard.

Direct serial console connection;

Device(host=..., user=..., passwd=..., mode='serial', port='/dev/ttyUSB0')

Serial can be used to configure new devices out of the box. The console provides root login with no password.

SSH connections through a console server;

Device(host=..., user=..., passwd=..., cs_user=..., cs_passwd=...))

SSH Keys

scp .ssh/id_rsa.pub lab@...:/tmp

edit system login user lab
set class super-user
set authentication load-key-file /tmp/id_rsa.pub
commit

With an SSH Key Agent

ssh-add ssh/id_rsa

Without SSH Key Argument

The ‘passwd’ argument contains the key passphrase, not the login password. The ‘sshprivatekey_file’ parameter to ‘Device’ can be used to specify a different key.

Junos RPC Review

Display the RPC needed to execute the ‘show’ command;

show route table inet.0 | display xml rpc

Display the output of the command in XML format;

show route table inet.0 | display xml

This can also be done from Python;

>>> print(dev.display_xml_rpc('show interfaces terse', format='text'))

For example, the RPC;

<get-interface-information>
    <interface-name>lo0</interface-name>
    <terse/>
</get-interface-information>

Translates into;

dev.rpc.get_interface_information(interface_name='lo0', terse=True)

XPath

XPath can be used to process the result of an RPC query.

find() finds the first match. findall() returns a list of all matches. findtext() returns the .text content of the first match (shortcut to find()). xpath() implements full XPath functionality. Full XPath functionality is only available when using the xpath() function.

r1 = xml.find('interface')
dump(r1)

r3 = xml.findtext('interface/name')
print(r3)

Normalising the XML RPC Reply

Extraneous whitespace can make searching the RPC reply tricky. Pass ‘normalize=True’ to the RPC function call to normalise the reply.

jxmlease

Python library developed by Juniper. Converts XML to Python data structures and back.

import jxmlease
parser = jxmlease.EtreeParser()
route_lxml_element = dev.rpc.get_route_information(table='inet.0')
ezxml = parser(route_lxml_element)
print(ezxml)

root = jxmlease.parse(route_lxml_element)
root.prettyprint()

PyEZ utils Modules

Unstructured changes, consisting of ASCII text as XML or ‘set’ commands. Lock the configuration using ‘lock()’, load in the new changes using ‘load()’, then commit the changes and unlock using ‘commit()’ and ‘unlock()’.

with Config(dev, mode='private') as cu:
    cu.load('...', format='set')
    cu.commit()

Arguments to load():

Text Config Example

Lock the configuration. This is the same as running ‘configure exclusive’. Load the prepared config data in text format. Print a diff of the candidate configuration against the active configuration. Check that the config is acceptable and commit it, otherwise, rollback.

conf.lock()
conf.load(data, format='text')
conf.pdiff()
if conf.commit_check():
    conf.commit()
else:
    conf.rollback()
conf.unlock()

StartShell

Note that dev.open needn’t be called as StartShell opens its own connection. ShartShell also has ‘open()’ and ‘close()’ methods.

with StartShell(dev) as ss:
    res, text = ss.run('ls -la ~lab/important.txt')
    print(res)
    print(text)

To execute non-returning commands, use the ‘timeout’ argument and set ‘this’ equal to ‘None’. The default time is 30 seconds.

ss.run('cli -r "monitor traffic fxp0"', this=None, timeout=15)

Automated Software Upgrades

if dev.facts['junos_info']['re0']['text'] == TARGET_VERSION:
    print('no upgrade requried')
    exit(1)

fs = FS(dev)
bytes_free = fs.storage_usage()['/dev/gpt/junos']['avail_block'] * 512
file_size = os.stat(IMAGE_FILE).st_size

if bytes_free < file_size:
    print('not enough free space')
    exit(1)

with SCP(dev, progress=True) as scp:
    scp.put(IMAGE_FILE, remote_path=REMOTE_PATH)

if not sw.install(
    package=REMOTE_PATH+IMAGE_FILE,
    no_copy=True,
    validate=False
    ):
print('installation error')
exit(1)

sw.reboot()

for _ in range(20):
    print('waiting for device')
    sleep(50)
    try:
        dev.open(auto_probe=10)
        ver = dev.facts['junos_info']['re0']['text']
    except (ProbeError, ConnectError):
        continue
    dev.close()
    break
else:
    print('device did not reboot in time')

if ver == TARGET_VERSION...

if __name__ == '__main__':
    main()

Tables and Views

A view is a set of fields from a table. This table is defined in the jnpr.junos.op.arp module.

---
ArpTable:
  rpc: get-arp-table-information
  item: arp-table-entry
  key: mac-address
  view: ArpView

ArpView:
  fields:
    mac_address: mac-address
    ip_address: ip-address
    interface_name: interface-name

Use the following to retrieve data from a table;

with Device(...) as dev:
    arp = ArpTable(dev)
    arp.get()
    for mac in arp:
        print(
            "{}: {} {}".format(
                mac.mac_address,
                mac.ip_address,
                mac.interface_name
                )
                )

Modifying the Configuration Using Tables

Access can be read-write using the ‘set’ property.

To modify the user configuration, use the XPath ‘system/login/user’. The user name is the key field.

The YAML to allow setting the configuration. The ‘set’ mapping is required to modify the configuration.

UserAccountTable:
    set: system/login/user
    key-field: username
    view: UserAccountView

UserAccountView:
    fields:
        username: name
        fullname: full-name

‘userclass’ is stored in the ‘class’ tag.

        userclass:
            class:
                default: unauthorized

‘uid’ is stored in the ‘uid’ element.

        uid:
            uid:
                type: int
                default: 1001
                minValue: 100
                maxValue: 64000


        groups:
            auth: authentication

Because ‘password’ is defined under the ‘auth’ group (here, ‘fieldsauth’), its actual path will be ‘authentication/encryptedpassword’.

        fields_auth:
            password: encrypted-password

Save the YAML files in a new directory, for example, ‘myTables’. Save both the table and the view in the same file, with the extension ‘.yml’. Save the following in a file with the same name but with a ‘.py’ extension;

from jnpr.junos.factory import loadyaml
from os.path import splitext

# Generate the .yml filename matching the name of this file.
_YAML_ = splitext(__file__)[0] + '.yml'

# Build classes based on the .yml file and export them.
globals().update(loadyaml(_YAML_))

Create a blank file called ‘init.py’. This tells the Python interpreter to look for modules in this directory.

Finally, write the script that will modify the configuration.

ua = UserAccountTable(dev)
ua.username = 'bob'
ua.userclass = 'super-user'
# use 'import crypt'
ua.password = crypt.crypt('lab123')
# Generate and store the configuration data in the internal LXML object.
ua.append()
# Lock the DB, make then change then unlock the DB.
ua.set(merge=True, comment="Commit")

An alternative is to lock, load, commit and unlock in separate steps.

ua.lock()
ua.load(merge=True)
ua.commit(comment="Commit")
ua.unlock()

The result can be verified using the same table and view.

ua.get()
for account in ua:
    print(...)

Best Practices