Packaging the application

  • by Patrick Ogenstad
  • April 25, 2018

While we have been setting up the ZTP application we have been running the TFTP server and RQ worker in separate windows just by running the commands. This works fine when testing things and developing the service, but if we are to use this in production we need something more robust.

Configuration

Before we set up the services for the application we have a few configuration parameters that are a bit spread out throughout the application and we are going to move them to a common location. For this, we create a new file.

/opt/ztp/app/configuration.py

import os
import ruamel.yaml


with open("configuration.yaml", "r") as f:
    yml = ruamel.yaml.YAML(typ="rt", pure=True)
    _cfg = yml.load(f)


STAGING_USER = _cfg.get('staging_user') or os.environ['ZTP_STAGING_USER']
STAGING_PASS = _cfg.get('staging_pass') or os.environ['ZTP_STAGING_PASS']
TFTP_ROOT = _cfg.get('tftp_root') or os.environ['TFTP_ROOT']
REDIS_URL = _cfg.get('redis_url') or os.environ['REDIS_URL']
INVENTORY = _cfg.get('inventory') or os.environ['ZTP_INVENTORY']
SLACK_TOKEN = _cfg.get('slack_token') or os.environ['SLACK_TOKEN']
TEMPLATE_DIR = _cfg.get('template_dir') or os.environ['ZTP_TEMPLATES']

Here we setup the option to load configuration parameters from a configuration file, if a specific parameter doesn’t exist in the configuration file it must be defined as an environment variable.

Next we create the default configuration file.

/opt/ztp/configuration.yaml

---
tftp_root: "/opt/ztp/tftproot"
redis_url: "redis://"
inventory: "/opt/ztp/devices.yaml"
template_dir: "/opt/ztp/templates"

Using the configuration

In order to use the configuration we need to make a few small changes.

/opt/ztp/app/broker.py

from app import configuration as C

def trigger_job(host, filename):
    redis_conn = redis.Redis.from_url(C.REDIS_URL)

/opt/ztp/app/dispatcher.py

from app import configuration as C


class TftpData:

    def __init__(self, filename):
        path = os.path.join(C.TFTP_ROOT, filename)


def request_dispatcher(file_path):

    if "__" in file_path:
        neighbor_host, neighbor_if = file_path.split('__')
        neighbor_if = neighbor_if.split('.')[0]
        for interface in devices[neighbor_host]['links']:
            if neighbor_if == interface['interface_brief']:
                ztp_host = interface['neighbor']

        ztp_device = {}
        ztp_device['hostname'] = ztp_host
        ztp_device['staging_user'] = C.STAGING_USER
        ztp_device['staging_password'] = C.STAGING_PASS

/opt/ztp/app/inventory.py

from app import configuration as C

with open(C.INVENTORY, "r") as f:

/opt/ztp/app/network.py

from napalm import get_network_driver
from app import configuration as C


def get_napalm_connection(host, device_type, attempts=360, timeout=1):
    driver = get_network_driver(device_type)
    device = driver(hostname=host, username=C.STAGING_USER,
                    password=C.STAGING_PASS)

/opt/ztp/app/notifications.py

from app import configuration as C


def notify_slack(msg):
    url = 'https://hooks.slack.com/services/' + C.SLACK_TOKEN

/opt/ztp/app/templating.py

from app import configuration as C


def render_file(template, **kwargs):

    env = Environment(
        loader=FileSystemLoader(C.TEMPLATE_DIR),

Services

To setup the services we are going to use Supervisor which needs to be installed as a Linux package.

sudo apt-get update
sudo apt-get install supervisor

Create the file /etc/supervisor/conf.d/ztp-tftp.conf

[program:ztp-tftp]
command=/opt/ztp/venv/bin/python ztp_tftp.py
numprocs=1
directory=/opt/ztp/
user=root
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true

Then the file for the rq worker /etc/supervisor/conf.d/ztp-tasks.conf

[program:ztp-tasks]
command=/opt/ztp/venv/bin/rq worker ztp_tasks
numprocs=1
directory=/opt/ztp/
user=deploy
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true

In the above file I’m using a user called “deploy”, which to a suitable one in your environment.

Finally you can start the services.

sudo supervisorctl reload