From: Philip Joseph Date: Thu, 22 Sep 2016 11:06:10 +0000 (+0530) Subject: Bug 49 : Update vpr-router charm to support workload state X-Git-Tag: v2.0.2~7^2~23 X-Git-Url: https://osm.etsi.org/gitweb/?a=commitdiff_plain;h=4ab6087333c8402aef95ece83922de5b23f1f197;p=osm%2Fdevops.git Bug 49 : Update vpr-router charm to support workload state Signed-off-by: Philip Joseph --- diff --git a/vpe-router/Makefile b/vpe-router/Makefile new file mode 100644 index 00000000..255da3d5 --- /dev/null +++ b/vpe-router/Makefile @@ -0,0 +1,25 @@ +#!/usr/bin/make + +all: lint unit_test + + +.PHONY: clean +clean: + @rm -rf .tox + +.PHONY: apt_prereqs +apt_prereqs: + @# Need tox, but don't install the apt version unless we have to (don't want to conflict with pip) + @which tox >/dev/null || (sudo apt-get install -y python-pip && sudo pip install tox) + +.PHONY: lint +lint: apt_prereqs + @tox --notest + @PATH=.tox/py34/bin:.tox/py35/bin flake8 $(wildcard hooks reactive lib unit_tests tests) + @which charm > /dev/null || (sudo apt-get install -y charm) + @charm proof + +.PHONY: unit_test +unit_test: apt_prereqs + @echo Starting tests... + tox diff --git a/vpe-router/bin/layer_option b/vpe-router/bin/layer_option new file mode 100755 index 00000000..90dc400e --- /dev/null +++ b/vpe-router/bin/layer_option @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 + +import sys +sys.path.append('lib') + +import argparse +from charms.layer import options + + +parser = argparse.ArgumentParser(description='Access layer options.') +parser.add_argument('section', + help='the section, or layer, the option is from') +parser.add_argument('option', + help='the option to access') + +args = parser.parse_args() +value = options(args.section).get(args.option, '') +if isinstance(value, bool): + sys.exit(0 if value else 1) +elif isinstance(value, list): + for val in value: + print(val) +else: + print(value) diff --git a/vpe-router/config.yaml b/vpe-router/config.yaml index 562515fa..1a0af1be 100644 --- a/vpe-router/config.yaml +++ b/vpe-router/config.yaml @@ -1,6 +1,6 @@ options: vpe-router: - default: + default: !!null "" type: string description: Hostname or IP of the vpe router to connect to user: @@ -9,9 +9,9 @@ options: description: Username for VPE Router pass: type: string - default: + default: !!null "" description: Password for VPE Router hostname: type: string - default: + default: !!null "" description: The hostname to set the vpe router to. diff --git a/vpe-router/hooks/config-changed b/vpe-router/hooks/config-changed new file mode 100755 index 00000000..d36afe17 --- /dev/null +++ b/vpe-router/hooks/config-changed @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 + +# Load modules from $CHARM_DIR/lib +import sys +sys.path.append('lib') + +from charms.layer import basic +basic.bootstrap_charm_deps() +basic.init_config_states() + + +# This will load and run the appropriate @hook and other decorated +# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive, +# and $CHARM_DIR/hooks/relations. +# +# See https://jujucharms.com/docs/stable/authors-charm-building +# for more information on this pattern. +from charms.reactive import main +main() diff --git a/vpe-router/hooks/hook.template b/vpe-router/hooks/hook.template new file mode 100644 index 00000000..d36afe17 --- /dev/null +++ b/vpe-router/hooks/hook.template @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 + +# Load modules from $CHARM_DIR/lib +import sys +sys.path.append('lib') + +from charms.layer import basic +basic.bootstrap_charm_deps() +basic.init_config_states() + + +# This will load and run the appropriate @hook and other decorated +# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive, +# and $CHARM_DIR/hooks/relations. +# +# See https://jujucharms.com/docs/stable/authors-charm-building +# for more information on this pattern. +from charms.reactive import main +main() diff --git a/vpe-router/hooks/install b/vpe-router/hooks/install new file mode 100755 index 00000000..d36afe17 --- /dev/null +++ b/vpe-router/hooks/install @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 + +# Load modules from $CHARM_DIR/lib +import sys +sys.path.append('lib') + +from charms.layer import basic +basic.bootstrap_charm_deps() +basic.init_config_states() + + +# This will load and run the appropriate @hook and other decorated +# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive, +# and $CHARM_DIR/hooks/relations. +# +# See https://jujucharms.com/docs/stable/authors-charm-building +# for more information on this pattern. +from charms.reactive import main +main() diff --git a/vpe-router/hooks/leader-elected b/vpe-router/hooks/leader-elected new file mode 100755 index 00000000..d36afe17 --- /dev/null +++ b/vpe-router/hooks/leader-elected @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 + +# Load modules from $CHARM_DIR/lib +import sys +sys.path.append('lib') + +from charms.layer import basic +basic.bootstrap_charm_deps() +basic.init_config_states() + + +# This will load and run the appropriate @hook and other decorated +# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive, +# and $CHARM_DIR/hooks/relations. +# +# See https://jujucharms.com/docs/stable/authors-charm-building +# for more information on this pattern. +from charms.reactive import main +main() diff --git a/vpe-router/hooks/leader-settings-changed b/vpe-router/hooks/leader-settings-changed new file mode 100755 index 00000000..d36afe17 --- /dev/null +++ b/vpe-router/hooks/leader-settings-changed @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 + +# Load modules from $CHARM_DIR/lib +import sys +sys.path.append('lib') + +from charms.layer import basic +basic.bootstrap_charm_deps() +basic.init_config_states() + + +# This will load and run the appropriate @hook and other decorated +# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive, +# and $CHARM_DIR/hooks/relations. +# +# See https://jujucharms.com/docs/stable/authors-charm-building +# for more information on this pattern. +from charms.reactive import main +main() diff --git a/vpe-router/hooks/start b/vpe-router/hooks/start new file mode 100755 index 00000000..d36afe17 --- /dev/null +++ b/vpe-router/hooks/start @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 + +# Load modules from $CHARM_DIR/lib +import sys +sys.path.append('lib') + +from charms.layer import basic +basic.bootstrap_charm_deps() +basic.init_config_states() + + +# This will load and run the appropriate @hook and other decorated +# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive, +# and $CHARM_DIR/hooks/relations. +# +# See https://jujucharms.com/docs/stable/authors-charm-building +# for more information on this pattern. +from charms.reactive import main +main() diff --git a/vpe-router/hooks/stop b/vpe-router/hooks/stop new file mode 100755 index 00000000..d36afe17 --- /dev/null +++ b/vpe-router/hooks/stop @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 + +# Load modules from $CHARM_DIR/lib +import sys +sys.path.append('lib') + +from charms.layer import basic +basic.bootstrap_charm_deps() +basic.init_config_states() + + +# This will load and run the appropriate @hook and other decorated +# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive, +# and $CHARM_DIR/hooks/relations. +# +# See https://jujucharms.com/docs/stable/authors-charm-building +# for more information on this pattern. +from charms.reactive import main +main() diff --git a/vpe-router/hooks/update-status b/vpe-router/hooks/update-status new file mode 100755 index 00000000..d36afe17 --- /dev/null +++ b/vpe-router/hooks/update-status @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 + +# Load modules from $CHARM_DIR/lib +import sys +sys.path.append('lib') + +from charms.layer import basic +basic.bootstrap_charm_deps() +basic.init_config_states() + + +# This will load and run the appropriate @hook and other decorated +# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive, +# and $CHARM_DIR/hooks/relations. +# +# See https://jujucharms.com/docs/stable/authors-charm-building +# for more information on this pattern. +from charms.reactive import main +main() diff --git a/vpe-router/hooks/upgrade-charm b/vpe-router/hooks/upgrade-charm new file mode 100755 index 00000000..1465e8ed --- /dev/null +++ b/vpe-router/hooks/upgrade-charm @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 + +# Load modules from $CHARM_DIR/lib +import os +import sys +sys.path.append('lib') + +# This is an upgrade-charm context, make sure we install latest deps +if not os.path.exists('wheelhouse/.upgrade'): + open('wheelhouse/.upgrade', 'w').close() + if os.path.exists('wheelhouse/.bootstrapped'): + os.unlink('wheelhouse/.bootstrapped') +else: + os.unlink('wheelhouse/.upgrade') + +from charms.layer import basic +basic.bootstrap_charm_deps() +basic.init_config_states() + + +# This will load and run the appropriate @hook and other decorated +# handlers from $CHARM_DIR/reactive, $CHARM_DIR/hooks/reactive, +# and $CHARM_DIR/hooks/relations. +# +# See https://jujucharms.com/docs/stable/authors-charm-building +# for more information on this pattern. +from charms.reactive import main +main() diff --git a/vpe-router/layer.yaml b/vpe-router/layer.yaml index 524a4f45..32e2c4f8 100644 --- a/vpe-router/layer.yaml +++ b/vpe-router/layer.yaml @@ -1 +1,12 @@ -includes: ['layer:basic'] +"options": + "basic": + "packages": + - "python-dev" + - "libffi-dev" + - "libssl-dev" + "use_venv": !!bool "false" + "include_system_packages": !!bool "false" + "vpe-router": {} +"includes": +- "layer:basic" +"is": "vpe-router" diff --git a/vpe-router/lib/charms/layer/__init__.py b/vpe-router/lib/charms/layer/__init__.py new file mode 100644 index 00000000..33d37e90 --- /dev/null +++ b/vpe-router/lib/charms/layer/__init__.py @@ -0,0 +1,21 @@ +import os + + +class LayerOptions(dict): + def __init__(self, layer_file, section=None): + import yaml # defer, might not be available until bootstrap + with open(layer_file) as f: + layer = yaml.safe_load(f.read()) + opts = layer.get('options', {}) + if section and section in opts: + super(LayerOptions, self).__init__(opts.get(section)) + else: + super(LayerOptions, self).__init__(opts) + + +def options(section=None, layer_file=None): + if not layer_file: + base_dir = os.environ.get('CHARM_DIR', os.getcwd()) + layer_file = os.path.join(base_dir, 'layer.yaml') + + return LayerOptions(layer_file, section) diff --git a/vpe-router/lib/charms/layer/basic.py b/vpe-router/lib/charms/layer/basic.py new file mode 100644 index 00000000..50bd6251 --- /dev/null +++ b/vpe-router/lib/charms/layer/basic.py @@ -0,0 +1,159 @@ +import os +import sys +import shutil +import platform +from glob import glob +from subprocess import check_call + +from charms.layer.execd import execd_preinstall + + +def bootstrap_charm_deps(): + """ + Set up the base charm dependencies so that the reactive system can run. + """ + # execd must happen first, before any attempt to install packages or + # access the network, because sites use this hook to do bespoke + # configuration and install secrets so the rest of this bootstrap + # and the charm itself can actually succeed. This call does nothing + # unless the operator has created and populated $CHARM_DIR/exec.d. + execd_preinstall() + # ensure that $CHARM_DIR/bin is on the path, for helper scripts + os.environ['PATH'] += ':%s' % os.path.join(os.environ['CHARM_DIR'], 'bin') + venv = os.path.abspath('../.venv') + vbin = os.path.join(venv, 'bin') + vpip = os.path.join(vbin, 'pip') + vpy = os.path.join(vbin, 'python') + if os.path.exists('wheelhouse/.bootstrapped'): + from charms import layer + cfg = layer.options('basic') + if cfg.get('use_venv') and '.venv' not in sys.executable: + # activate the venv + os.environ['PATH'] = ':'.join([vbin, os.environ['PATH']]) + reload_interpreter(vpy) + return + # bootstrap wheelhouse + if os.path.exists('wheelhouse'): + with open('/root/.pydistutils.cfg', 'w') as fp: + # make sure that easy_install also only uses the wheelhouse + # (see https://github.com/pypa/pip/issues/410) + charm_dir = os.environ['CHARM_DIR'] + fp.writelines([ + "[easy_install]\n", + "allow_hosts = ''\n", + "find_links = file://{}/wheelhouse/\n".format(charm_dir), + ]) + apt_install(['python3-pip', 'python3-setuptools', 'python3-yaml']) + from charms import layer + cfg = layer.options('basic') + # include packages defined in layer.yaml + apt_install(cfg.get('packages', [])) + # if we're using a venv, set it up + if cfg.get('use_venv'): + if not os.path.exists(venv): + distname, version, series = platform.linux_distribution() + if series in ('precise', 'trusty'): + apt_install(['python-virtualenv']) + else: + apt_install(['virtualenv']) + cmd = ['virtualenv', '-ppython3', '--never-download', venv] + if cfg.get('include_system_packages'): + cmd.append('--system-site-packages') + check_call(cmd) + os.environ['PATH'] = ':'.join([vbin, os.environ['PATH']]) + pip = vpip + else: + pip = 'pip3' + # save a copy of system pip to prevent `pip3 install -U pip` + # from changing it + if os.path.exists('/usr/bin/pip'): + shutil.copy2('/usr/bin/pip', '/usr/bin/pip.save') + # need newer pip, to fix spurious Double Requirement error: + # https://github.com/pypa/pip/issues/56 + check_call([pip, 'install', '-U', '--no-index', '-f', 'wheelhouse', + 'pip']) + # install the rest of the wheelhouse deps + check_call([pip, 'install', '-U', '--no-index', '-f', 'wheelhouse'] + + glob('wheelhouse/*')) + if not cfg.get('use_venv'): + # restore system pip to prevent `pip3 install -U pip` + # from changing it + if os.path.exists('/usr/bin/pip.save'): + shutil.copy2('/usr/bin/pip.save', '/usr/bin/pip') + os.remove('/usr/bin/pip.save') + os.remove('/root/.pydistutils.cfg') + # flag us as having already bootstrapped so we don't do it again + open('wheelhouse/.bootstrapped', 'w').close() + # Ensure that the newly bootstrapped libs are available. + # Note: this only seems to be an issue with namespace packages. + # Non-namespace-package libs (e.g., charmhelpers) are available + # without having to reload the interpreter. :/ + reload_interpreter(vpy if cfg.get('use_venv') else sys.argv[0]) + + +def reload_interpreter(python): + """ + Reload the python interpreter to ensure that all deps are available. + + Newly installed modules in namespace packages sometimes seemt to + not be picked up by Python 3. + """ + os.execle(python, python, sys.argv[0], os.environ) + + +def apt_install(packages): + """ + Install apt packages. + + This ensures a consistent set of options that are often missed but + should really be set. + """ + if isinstance(packages, (str, bytes)): + packages = [packages] + + env = os.environ.copy() + + if 'DEBIAN_FRONTEND' not in env: + env['DEBIAN_FRONTEND'] = 'noninteractive' + + cmd = ['apt-get', + '--option=Dpkg::Options::=--force-confold', + '--assume-yes', + 'install'] + check_call(cmd + packages, env=env) + + +def init_config_states(): + import yaml + from charmhelpers.core import hookenv + from charms.reactive import set_state + from charms.reactive import toggle_state + config = hookenv.config() + config_defaults = {} + config_defs = {} + config_yaml = os.path.join(hookenv.charm_dir(), 'config.yaml') + if os.path.exists(config_yaml): + with open(config_yaml) as fp: + config_defs = yaml.load(fp).get('options', {}) + config_defaults = {key: value.get('default') + for key, value in config_defs.items()} + for opt in config_defs.keys(): + if config.changed(opt): + set_state('config.changed') + set_state('config.changed.{}'.format(opt)) + toggle_state('config.set.{}'.format(opt), config.get(opt)) + toggle_state('config.default.{}'.format(opt), + config.get(opt) == config_defaults[opt]) + hookenv.atexit(clear_config_states) + + +def clear_config_states(): + from charmhelpers.core import hookenv, unitdata + from charms.reactive import remove_state + config = hookenv.config() + remove_state('config.changed') + for opt in config.keys(): + remove_state('config.changed.{}'.format(opt)) + remove_state('config.set.{}'.format(opt)) + remove_state('config.default.{}'.format(opt)) + unitdata.kv().flush() diff --git a/vpe-router/lib/charms/layer/execd.py b/vpe-router/lib/charms/layer/execd.py new file mode 100644 index 00000000..30574190 --- /dev/null +++ b/vpe-router/lib/charms/layer/execd.py @@ -0,0 +1,138 @@ +# Copyright 2014-2016 Canonical Limited. +# +# This file is part of layer-basic, the reactive base layer for Juju. +# +# charm-helpers is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version 3 as +# published by the Free Software Foundation. +# +# charm-helpers is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with charm-helpers. If not, see . + +# This module may only import from the Python standard library. +import os +import sys +import subprocess +import time + +''' +execd/preinstall + +It is often necessary to configure and reconfigure machines +after provisioning, but before attempting to run the charm. +Common examples are specialized network configuration, enabling +of custom hardware, non-standard disk partitioning and filesystems, +adding secrets and keys required for using a secured network. + +The reactive framework's base layer invokes this mechanism as +early as possible, before any network access is made or dependencies +unpacked or non-standard modules imported (including the charms.reactive +framework itself). + +Operators needing to use this functionality may branch a charm and +create an exec.d directory in it. The exec.d directory in turn contains +one or more subdirectories, each of which contains an executable called +charm-pre-install and any other required resources. The charm-pre-install +executables are run, and if successful, state saved so they will not be +run again. + + $CHARM_DIR/exec.d/mynamespace/charm-pre-install + +An alternative to branching a charm is to compose a new charm that contains +the exec.d directory, using the original charm as a layer, + +A charm author could also abuse this mechanism to modify the charm +environment in unusual ways, but for most purposes it is saner to use +charmhelpers.core.hookenv.atstart(). +''' + + +def default_execd_dir(): + return os.path.join(os.environ['CHARM_DIR'], 'exec.d') + + +def execd_module_paths(execd_dir=None): + """Generate a list of full paths to modules within execd_dir.""" + if not execd_dir: + execd_dir = default_execd_dir() + + if not os.path.exists(execd_dir): + return + + for subpath in os.listdir(execd_dir): + module = os.path.join(execd_dir, subpath) + if os.path.isdir(module): + yield module + + +def execd_submodule_paths(command, execd_dir=None): + """Generate a list of full paths to the specified command within exec_dir. + """ + for module_path in execd_module_paths(execd_dir): + path = os.path.join(module_path, command) + if os.access(path, os.X_OK) and os.path.isfile(path): + yield path + + +def execd_sentinel_path(submodule_path): + module_path = os.path.dirname(submodule_path) + execd_path = os.path.dirname(module_path) + module_name = os.path.basename(module_path) + submodule_name = os.path.basename(submodule_path) + return os.path.join(execd_path, + '.{}_{}.done'.format(module_name, submodule_name)) + + +def execd_run(command, execd_dir=None, stop_on_error=True, stderr=None): + """Run command for each module within execd_dir which defines it.""" + if stderr is None: + stderr = sys.stdout + for submodule_path in execd_submodule_paths(command, execd_dir): + # Only run each execd once. We cannot simply run them in the + # install hook, as potentially storage hooks are run before that. + # We cannot rely on them being idempotent. + sentinel = execd_sentinel_path(submodule_path) + if os.path.exists(sentinel): + continue + + try: + subprocess.check_call([submodule_path], stderr=stderr, + universal_newlines=True) + with open(sentinel, 'w') as f: + f.write('{} ran successfully {}\n'.format(submodule_path, + time.ctime())) + f.write('Removing this file will cause it to be run again\n') + except subprocess.CalledProcessError as e: + # Logs get the details. We can't use juju-log, as the + # output may be substantial and exceed command line + # length limits. + print("ERROR ({}) running {}".format(e.returncode, e.cmd), + file=stderr) + print("STDOUT< 0: - raise subprocess.CalledProcessError(returncode=retcode, - cmd=cmd, - output=stderr.decode("utf-8").strip()) + raise subprocess.CalledProcessError( + returncode=retcode, + cmd=cmd, + output=stderr.decode("utf-8").strip()) return (''.join(stdout), ''.join(stderr)) diff --git a/vpe-router/metadata.yaml b/vpe-router/metadata.yaml index ccc841fa..9b8b157e 100644 --- a/vpe-router/metadata.yaml +++ b/vpe-router/metadata.yaml @@ -2,6 +2,7 @@ name: vpe-router maintainers: - Marco Ceppi - Adam Israel + - Philip Joseph summary: setup a virtualized PE Router with GRE tunnels description: | this charm, when deployed and configured, will provide a secure virtualized diff --git a/vpe-router/reactive/__init__.py b/vpe-router/reactive/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/vpe-router/reactive/vpe_router.py b/vpe-router/reactive/vpe_router.py index c62983d8..de1e0247 100644 --- a/vpe-router/reactive/vpe_router.py +++ b/vpe-router/reactive/vpe_router.py @@ -8,7 +8,6 @@ from charmhelpers.core.hookenv import ( ) from charms.reactive import ( - hook, when, when_not, helpers, @@ -22,13 +21,15 @@ import subprocess cfg = config() -@hook('config-changed') +@when('config.changed') def validate_config(): try: """ If the ssh credentials are available, we'll act as a proxy charm. Otherwise, we execute against the unit we're deployed on to. """ + status_set('maintenance', 'configuring ssh connection') + remove_state('vpe.configured') if all(k in cfg for k in ['pass', 'vpe-router', 'user']): routerip = cfg['vpe-router'] user = cfg['user'] @@ -62,8 +63,8 @@ def validate_config(): (' '.join(e.cmd), str(e.output))) raise - set_state('vpe.configured') - status_set('active', 'ready!') + set_state('vpe.configured') + status_set('active', 'ready!') except Exception as e: log(repr(e)) diff --git a/vpe-router/requirements.txt b/vpe-router/requirements.txt new file mode 100644 index 00000000..28ecacab --- /dev/null +++ b/vpe-router/requirements.txt @@ -0,0 +1,2 @@ +flake8 +pytest diff --git a/vpe-router/tox.ini b/vpe-router/tox.ini new file mode 100644 index 00000000..0b8b27a9 --- /dev/null +++ b/vpe-router/tox.ini @@ -0,0 +1,12 @@ +[tox] +skipsdist=True +envlist = py34, py35 +skip_missing_interpreters = True + +[testenv] +commands = py.test -v +deps = + -r{toxinidir}/requirements.txt + +[flake8] +exclude=docs diff --git a/vpe-router/wheelhouse.txt b/vpe-router/wheelhouse.txt deleted file mode 100644 index df2de692..00000000 --- a/vpe-router/wheelhouse.txt +++ /dev/null @@ -1 +0,0 @@ -paramiko>=1.16.0,<1.17 diff --git a/vpe-router/wheelhouse/Jinja2-2.8.tar.gz b/vpe-router/wheelhouse/Jinja2-2.8.tar.gz new file mode 100644 index 00000000..9c384267 Binary files /dev/null and b/vpe-router/wheelhouse/Jinja2-2.8.tar.gz differ diff --git a/vpe-router/wheelhouse/MarkupSafe-0.23.tar.gz b/vpe-router/wheelhouse/MarkupSafe-0.23.tar.gz new file mode 100644 index 00000000..6b190061 Binary files /dev/null and b/vpe-router/wheelhouse/MarkupSafe-0.23.tar.gz differ diff --git a/vpe-router/wheelhouse/PyYAML-3.11.zip b/vpe-router/wheelhouse/PyYAML-3.11.zip new file mode 100644 index 00000000..c361e865 Binary files /dev/null and b/vpe-router/wheelhouse/PyYAML-3.11.zip differ diff --git a/vpe-router/wheelhouse/Tempita-0.5.2.tar.gz b/vpe-router/wheelhouse/Tempita-0.5.2.tar.gz new file mode 100644 index 00000000..755befcd Binary files /dev/null and b/vpe-router/wheelhouse/Tempita-0.5.2.tar.gz differ diff --git a/vpe-router/wheelhouse/cffi-1.7.0.tar.gz b/vpe-router/wheelhouse/cffi-1.7.0.tar.gz new file mode 100644 index 00000000..55da2608 Binary files /dev/null and b/vpe-router/wheelhouse/cffi-1.7.0.tar.gz differ diff --git a/vpe-router/wheelhouse/charmhelpers-0.7.0.tar.gz b/vpe-router/wheelhouse/charmhelpers-0.7.0.tar.gz new file mode 100644 index 00000000..93acb406 Binary files /dev/null and b/vpe-router/wheelhouse/charmhelpers-0.7.0.tar.gz differ diff --git a/vpe-router/wheelhouse/charms.reactive-0.4.4.tar.gz b/vpe-router/wheelhouse/charms.reactive-0.4.4.tar.gz new file mode 100644 index 00000000..0912b6ac Binary files /dev/null and b/vpe-router/wheelhouse/charms.reactive-0.4.4.tar.gz differ diff --git a/vpe-router/wheelhouse/cryptography-1.4.tar.gz b/vpe-router/wheelhouse/cryptography-1.4.tar.gz new file mode 100644 index 00000000..e7dfece7 Binary files /dev/null and b/vpe-router/wheelhouse/cryptography-1.4.tar.gz differ diff --git a/vpe-router/wheelhouse/idna-2.1.tar.gz b/vpe-router/wheelhouse/idna-2.1.tar.gz new file mode 100644 index 00000000..c028c715 Binary files /dev/null and b/vpe-router/wheelhouse/idna-2.1.tar.gz differ diff --git a/vpe-router/wheelhouse/netaddr-0.7.18.tar.gz b/vpe-router/wheelhouse/netaddr-0.7.18.tar.gz new file mode 100644 index 00000000..0df6b478 Binary files /dev/null and b/vpe-router/wheelhouse/netaddr-0.7.18.tar.gz differ diff --git a/vpe-router/wheelhouse/paramiko-2.0.1.tar.gz b/vpe-router/wheelhouse/paramiko-2.0.1.tar.gz new file mode 100644 index 00000000..6f2d3182 Binary files /dev/null and b/vpe-router/wheelhouse/paramiko-2.0.1.tar.gz differ diff --git a/vpe-router/wheelhouse/pip-8.1.2.tar.gz b/vpe-router/wheelhouse/pip-8.1.2.tar.gz new file mode 100644 index 00000000..e7a1a3cc Binary files /dev/null and b/vpe-router/wheelhouse/pip-8.1.2.tar.gz differ diff --git a/vpe-router/wheelhouse/pyaml-15.8.2.tar.gz b/vpe-router/wheelhouse/pyaml-15.8.2.tar.gz new file mode 100644 index 00000000..3c49aaf5 Binary files /dev/null and b/vpe-router/wheelhouse/pyaml-15.8.2.tar.gz differ diff --git a/vpe-router/wheelhouse/pyasn1-0.1.9.tar.gz b/vpe-router/wheelhouse/pyasn1-0.1.9.tar.gz new file mode 100644 index 00000000..1900b07f Binary files /dev/null and b/vpe-router/wheelhouse/pyasn1-0.1.9.tar.gz differ diff --git a/vpe-router/wheelhouse/pycparser-2.14.tar.gz b/vpe-router/wheelhouse/pycparser-2.14.tar.gz new file mode 100644 index 00000000..6cdaab19 Binary files /dev/null and b/vpe-router/wheelhouse/pycparser-2.14.tar.gz differ diff --git a/vpe-router/wheelhouse/setuptools-23.1.0.zip b/vpe-router/wheelhouse/setuptools-23.1.0.zip new file mode 100644 index 00000000..23e512af Binary files /dev/null and b/vpe-router/wheelhouse/setuptools-23.1.0.zip differ diff --git a/vpe-router/wheelhouse/six-1.10.0.tar.gz b/vpe-router/wheelhouse/six-1.10.0.tar.gz new file mode 100644 index 00000000..ac8eec53 Binary files /dev/null and b/vpe-router/wheelhouse/six-1.10.0.tar.gz differ