Bug 49 : Update vpr-router charm to support workload state
authorPhilip Joseph <philip.joseph@riftio.com>
Thu, 22 Sep 2016 11:06:10 +0000 (16:36 +0530)
committerPhilip Joseph <philip.joseph@riftio.com>
Thu, 22 Sep 2016 11:06:10 +0000 (16:36 +0530)
Signed-off-by: Philip Joseph <philip.joseph@riftio.com>
40 files changed:
vpe-router/Makefile [new file with mode: 0644]
vpe-router/bin/layer_option [new file with mode: 0755]
vpe-router/config.yaml
vpe-router/hooks/config-changed [new file with mode: 0755]
vpe-router/hooks/hook.template [new file with mode: 0644]
vpe-router/hooks/install [new file with mode: 0755]
vpe-router/hooks/leader-elected [new file with mode: 0755]
vpe-router/hooks/leader-settings-changed [new file with mode: 0755]
vpe-router/hooks/start [new file with mode: 0755]
vpe-router/hooks/stop [new file with mode: 0755]
vpe-router/hooks/update-status [new file with mode: 0755]
vpe-router/hooks/upgrade-charm [new file with mode: 0755]
vpe-router/layer.yaml
vpe-router/lib/charms/layer/__init__.py [new file with mode: 0644]
vpe-router/lib/charms/layer/basic.py [new file with mode: 0644]
vpe-router/lib/charms/layer/execd.py [new file with mode: 0644]
vpe-router/lib/charms/router.py
vpe-router/metadata.yaml
vpe-router/reactive/__init__.py [new file with mode: 0644]
vpe-router/reactive/vpe_router.py
vpe-router/requirements.txt [new file with mode: 0644]
vpe-router/tox.ini [new file with mode: 0644]
vpe-router/wheelhouse.txt [deleted file]
vpe-router/wheelhouse/Jinja2-2.8.tar.gz [new file with mode: 0644]
vpe-router/wheelhouse/MarkupSafe-0.23.tar.gz [new file with mode: 0644]
vpe-router/wheelhouse/PyYAML-3.11.zip [new file with mode: 0644]
vpe-router/wheelhouse/Tempita-0.5.2.tar.gz [new file with mode: 0644]
vpe-router/wheelhouse/cffi-1.7.0.tar.gz [new file with mode: 0644]
vpe-router/wheelhouse/charmhelpers-0.7.0.tar.gz [new file with mode: 0644]
vpe-router/wheelhouse/charms.reactive-0.4.4.tar.gz [new file with mode: 0644]
vpe-router/wheelhouse/cryptography-1.4.tar.gz [new file with mode: 0644]
vpe-router/wheelhouse/idna-2.1.tar.gz [new file with mode: 0644]
vpe-router/wheelhouse/netaddr-0.7.18.tar.gz [new file with mode: 0644]
vpe-router/wheelhouse/paramiko-2.0.1.tar.gz [new file with mode: 0644]
vpe-router/wheelhouse/pip-8.1.2.tar.gz [new file with mode: 0644]
vpe-router/wheelhouse/pyaml-15.8.2.tar.gz [new file with mode: 0644]
vpe-router/wheelhouse/pyasn1-0.1.9.tar.gz [new file with mode: 0644]
vpe-router/wheelhouse/pycparser-2.14.tar.gz [new file with mode: 0644]
vpe-router/wheelhouse/setuptools-23.1.0.zip [new file with mode: 0644]
vpe-router/wheelhouse/six-1.10.0.tar.gz [new file with mode: 0644]

diff --git a/vpe-router/Makefile b/vpe-router/Makefile
new file mode 100644 (file)
index 0000000..255da3d
--- /dev/null
@@ -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 (executable)
index 0000000..90dc400
--- /dev/null
@@ -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)
index 562515f..1a0af1b 100644 (file)
@@ -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 (executable)
index 0000000..d36afe1
--- /dev/null
@@ -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 (file)
index 0000000..d36afe1
--- /dev/null
@@ -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 (executable)
index 0000000..d36afe1
--- /dev/null
@@ -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 (executable)
index 0000000..d36afe1
--- /dev/null
@@ -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 (executable)
index 0000000..d36afe1
--- /dev/null
@@ -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 (executable)
index 0000000..d36afe1
--- /dev/null
@@ -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 (executable)
index 0000000..d36afe1
--- /dev/null
@@ -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 (executable)
index 0000000..d36afe1
--- /dev/null
@@ -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 (executable)
index 0000000..1465e8e
--- /dev/null
@@ -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()
index 524a4f4..32e2c4f 100644 (file)
@@ -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 (file)
index 0000000..33d37e9
--- /dev/null
@@ -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 (file)
index 0000000..50bd625
--- /dev/null
@@ -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 (file)
index 0000000..3057419
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+
+# 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<<EOM", file=stderr)
+            print(e.output, file=stderr)
+            print("EOM", file=stderr)
+
+            # Unit workload status gets a shorter fail message.
+            short_path = os.path.relpath(submodule_path)
+            block_msg = "Error ({}) running {}".format(e.returncode,
+                                                       short_path)
+            try:
+                subprocess.check_call(['status-set', 'blocked', block_msg],
+                                      universal_newlines=True)
+                if stop_on_error:
+                    sys.exit(0)  # Leave unit in blocked state.
+            except Exception:
+                pass  # We care about the exec.d/* failure, not status-set.
+
+            if stop_on_error:
+                sys.exit(e.returncode or 1)  # Error state for pre-1.24 Juju
+
+
+def execd_preinstall(execd_dir=None):
+    """Run charm-pre-install for each module within execd_dir."""
+    execd_run('charm-pre-install', execd_dir=execd_dir)
index 54ff7fb..32afcba 100644 (file)
@@ -54,9 +54,10 @@ def _run(cmd, env=None):
     stdout, stderr = p.communicate()
     retcode = p.poll()
     if retcode > 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))
 
 
index ccc841f..9b8b157 100644 (file)
@@ -2,6 +2,7 @@ name: vpe-router
 maintainers:
   - Marco Ceppi <marco.ceppi@canonical.com>
   - Adam Israel <adam.israel@canonical.com>
+  - Philip Joseph <philip.joseph@riftio.com>
 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 (file)
index 0000000..e69de29
index c62983d..de1e024 100644 (file)
@@ -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 (file)
index 0000000..28ecaca
--- /dev/null
@@ -0,0 +1,2 @@
+flake8
+pytest
diff --git a/vpe-router/tox.ini b/vpe-router/tox.ini
new file mode 100644 (file)
index 0000000..0b8b27a
--- /dev/null
@@ -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 (file)
index df2de69..0000000
+++ /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 (file)
index 0000000..9c38426
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 (file)
index 0000000..6b19006
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 (file)
index 0000000..c361e86
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 (file)
index 0000000..755befc
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 (file)
index 0000000..55da260
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 (file)
index 0000000..93acb40
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 (file)
index 0000000..0912b6a
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 (file)
index 0000000..e7dfece
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 (file)
index 0000000..c028c71
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 (file)
index 0000000..0df6b47
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 (file)
index 0000000..6f2d318
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 (file)
index 0000000..e7a1a3c
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 (file)
index 0000000..3c49aaf
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 (file)
index 0000000..1900b07
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 (file)
index 0000000..6cdaab1
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 (file)
index 0000000..23e512a
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 (file)
index 0000000..ac8eec5
Binary files /dev/null and b/vpe-router/wheelhouse/six-1.10.0.tar.gz differ