← Back to team overview

bigdata-dev team mailing list archive

[Merge] lp:~bigdata-dev/charms/trusty/apache-spark-notebook/trunk into lp:charms/trusty/apache-spark-notebook

 

Kevin W Monroe has proposed merging lp:~bigdata-dev/charms/trusty/apache-spark-notebook/trunk into lp:charms/trusty/apache-spark-notebook.

Requested reviews:
  Kevin W Monroe (kwmonroe)

For more details, see:
https://code.launchpad.net/~bigdata-dev/charms/trusty/apache-spark-notebook/trunk/+merge/268674
-- 
Your team Juju Big Data Development is subscribed to branch lp:~bigdata-dev/charms/trusty/apache-spark-notebook/trunk.
=== added file 'LICENSE'
--- LICENSE	1970-01-01 00:00:00 +0000
+++ LICENSE	2015-08-20 23:14:47 +0000
@@ -0,0 +1,177 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS

=== renamed file 'LICENSE' => 'LICENSE.moved'
=== added file 'README.md'
--- README.md	1970-01-01 00:00:00 +0000
+++ README.md	2015-08-20 23:14:47 +0000
@@ -0,0 +1,72 @@
+## Overview
+
+IPython Notebook is a web-based notebook that enables interactive data
+analytics for Spark. The developers of Apache Spark have given thoughtful
+consideration to Python as a language of choice for data analysis. They have
+developed the PySpark API for working with RDDs in Python, and further support
+using the powerful IPythonshell instead of the builtin Python REPL.
+
+The developers of IPython have invested considerable effort in building the
+IPython Notebook, a system inspired by Mathematica that allows you to create
+"executable documents."" IPython Notebooks can integrate formatted text
+(Markdown), executable code (Python), mathematical formulae (LaTeX), and
+graphics/visualizations (matplotlib) into a single document that captures the
+flow of an exploration and can be exported as a formatted report or an
+executable script.
+
+
+## Usage
+
+This is a subordinate charm that requires the `apache-spark` interface. This
+means that you will need to deploy a base Apache Spark cluster to use
+IPython Notebook. An easy way to deploy the recommended environment is to use
+the [apache-hadoop-spark-notebook](https://jujucharms.com/apache-hadoop-spark-notebook)
+bundle. This will deploy the Apache Hadoop platform with an Apache Spark +
+IPython Notebook unit that communicates with the cluster by relating to the
+`apache-hadoop-plugin` subordinate charm:
+
+    juju-quickstart apache-hadoop-spark-notebook
+
+Alternatively, you may manually deploy the recommended environment as follows:
+
+    juju deploy apache-hadoop-hdfs-master hdfs-master
+    juju deploy apache-hadoop-yarn-master yarn-master
+    juju deploy apache-hadoop-compute-slave compute-slave
+    juju deploy apache-hadoop-plugin plugin
+    juju deploy apache-spark spark
+    juju deploy apache-spark-notebook notebook
+
+    juju add-relation yarn-master hdfs-master
+    juju add-relation compute-slave yarn-master
+    juju add-relation compute-slave hdfs-master
+    juju add-relation plugin yarn-master
+    juju add-relation plugin hdfs-master
+    juju add-relation spark plugin
+    juju add-relation notebook spark
+
+Once deployment is complete, expose the notebook service:
+
+    juju expose notebook
+
+You may now access the web interface at
+http://{spark_unit_ip_address}:8880. The ip address can be found by running
+`juju status spark | grep public-address`.
+
+
+## Testing the deployment
+
+From the IPython Notebook web interface, click on the "New Notebook" button.
+In the notebook cell type "sc." followed by the "Tab" key. The Spark API
+completion menu should appear. This verifies the notebook can communicate
+with the Spark unit.
+
+
+## Contact Information
+
+- <bigdata-dev@xxxxxxxxxxxxxxxxxxx>
+
+
+## Help
+
+- [Juju mailing list](https://lists.ubuntu.com/mailman/listinfo/juju)
+- [Juju community](https://jujucharms.com/community)

=== renamed file 'README.md' => 'README.md.moved'
=== added file 'copyright'
--- copyright	1970-01-01 00:00:00 +0000
+++ copyright	2015-08-20 23:14:47 +0000
@@ -0,0 +1,16 @@
+Format: http://dep.debian.net/deps/dep5/
+
+Files: *
+Copyright: Copyright 2015, Canonical Ltd., All Rights Reserved.
+License: Apache License 2.0
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ .
+     http://www.apache.org/licenses/LICENSE-2.0
+ .
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.

=== renamed file 'copyright' => 'copyright.moved'
=== added file 'dist.yaml'
--- dist.yaml	1970-01-01 00:00:00 +0000
+++ dist.yaml	2015-08-20 23:14:47 +0000
@@ -0,0 +1,17 @@
+# This file contains values that are likely to change per distribution.
+# The aim is to make it easier to update / extend the charms with
+# minimal changes to the shared code in charmhelpers.
+vendor: 'apache'
+hadoop_version: '2.4.1'
+packages:
+    - 'ipython'
+    - 'ipython-notebook'
+dirs:
+    notebooks:
+        path: '/var/lib/spark-notebook/notebook'
+        owner: 'ubuntu'
+        group: 'hadoop'
+ports:
+    notebook:
+        port: 8880
+        exposed_on: 'notebook'

=== renamed file 'dist.yaml' => 'dist.yaml.moved'
=== added directory 'hooks'
=== renamed directory 'hooks' => 'hooks.moved'
=== added file 'hooks/callbacks.py'
--- hooks/callbacks.py	1970-01-01 00:00:00 +0000
+++ hooks/callbacks.py	2015-08-20 23:14:47 +0000
@@ -0,0 +1,124 @@
+import os
+from path import Path
+from subprocess import check_output
+
+from charmhelpers.core import hookenv
+from charmhelpers.core import unitdata
+from jujubigdata import utils
+from jujubigdata.relations import Spark
+
+
+# Extended status support
+# We call update_blocked_status from the "requires" section of our service
+# block, so be sure to return True. Otherwise, we'll block the "requires"
+# and never move on to callbacks. The other status update methods are called
+# from the "callbacks" section and therefore don't need to return True.
+def update_blocked_status():
+    if unitdata.kv().get('charm.active', False):
+        return True
+    spark = Spark()
+    if not spark.is_ready():
+        hookenv.status_set('waiting', 'Waiting for Spark to become ready')
+    return True
+
+
+def update_working_status():
+    if unitdata.kv().get('charm.active', False):
+        hookenv.status_set('maintenance', 'Updating configuration')
+        return
+    hookenv.status_set('maintenance', 'Setting up Spark Notebook')
+
+
+def update_active_status():
+    unitdata.kv().set('charm.active', True)
+    hookenv.status_set('active', 'Ready')
+
+
+# Main Notebook class for callbacks
+class Notebook(object):
+    def __init__(self, dist_config):
+        self.dist_config = dist_config
+
+    def is_installed(self):
+        return unitdata.kv().get('notebook.installed')
+
+    def install(self, force=False):
+        if not force and self.is_installed():
+            return
+        self.dist_config.add_dirs()
+        self.dist_config.add_packages()
+
+        # Copy our start/stop scripts (preserving attrs) to $HOME
+        start_source = 'scripts/start_notebook.sh'
+        Path(start_source).chmod(0o755)
+        Path(start_source).chown('ubuntu', 'hadoop')
+
+        stop_source = 'scripts/stop_notebook.sh'
+        Path(stop_source).chmod(0o755)
+        Path(stop_source).chown('ubuntu', 'hadoop')
+
+        target = os.environ.get('HOME', '/home/ubuntu')
+        Path(start_source).copy2(target)
+        Path(stop_source).copy2(target)
+
+        # Create an IPython profile
+        utils.run_as("ubuntu", 'ipython', 'profile', 'create', 'pyspark')
+
+        unitdata.kv().set('notebook.installed', True)
+
+    def configure_notebook(self):
+        # profile config created during install
+        ipython_profile = "ipython_notebook_config.py"
+        # find path to ipython_notebook_config.py
+        pPath = "/home/ubuntu/.ipython/profile_pyspark"
+        cmd = "find {} -name {}".format(pPath, ipython_profile)
+        ipython_profile_path = check_output(cmd.split()).strip()
+
+        # update profile with standard opts and configured port
+        utils.re_edit_in_place(ipython_profile_path, {
+            r'.*c.NotebookApp.ip *=.*': 'c.NotebookApp.ip = "*"',
+            r'.*c.NotebookApp.open_browser *=.*': 'c.NotebookApp.open_browser = False',
+            r'.*c.NotebookApp.port *=.*': 'c.NotebookApp.port = {}'.format(self.dist_config.port('notebook')),
+            r'.*c.NotebookManager.notebook_dir *=.*': "c.NotebookManager.notebook_dir = u'{}'".format(self.dist_config.path('notebooks')),
+        })
+
+        spark_home = os.environ.get("SPARK_HOME", '/usr/lib/spark')
+        py4j = "py4j-0.*.zip"
+        cmd = "find {} -name {}".format(spark_home, py4j)
+        # TODO: handle missing py4j
+        py4j_path = check_output(cmd.split()).strip()
+
+        setup_source = 'scripts/00-pyspark-setup.py'
+        Path(setup_source).chmod(0o755)
+        Path(setup_source).chown('ubuntu', 'hadoop')
+        utils.re_edit_in_place(setup_source, {
+            r'py4j *=.*': 'py4j="{}"'.format(py4j_path),
+        })
+        setup_target = '{}/.ipython/profile_pyspark/startup/00-pyspark-setup.py'.format(os.environ.get('HOME', '/home/ubuntu'))
+        Path(setup_source).copy2(setup_target)
+
+        # Our spark charm defaults to yarn-client, so that should
+        # be a safe default here in case MASTER isn't set. Update the env
+        # with our spark mode and py4j location.
+        spark_mode = os.environ.get("MASTER", "yarn-client")
+        with utils.environment_edit_in_place('/etc/environment') as env:
+            env['PYSPARK_DRIVER_PYTHON_OPTS'] = "notebook"
+            env['PYSPARK_SUBMIT_ARGS'] = "--master " + spark_mode
+            env['PYTHONPATH'] = "{}:{}/python".format(py4j_path, os.environ.get("SPARK_HOME", "/usr/lib/spark"))
+
+    def start(self):
+        self.stop()
+        script_path = os.environ.get('HOME', '/home/ubuntu')+'/start_notebook.sh'
+        # TODO: check for executable script; error without it
+        utils.run_as("ubuntu", script_path)
+
+    def stop(self):
+        script_path = os.environ.get('HOME', '/home/ubuntu')+'/stop_notebook.sh'
+        # TODO: check for executable script; error without it
+        utils.run_as("ubuntu", script_path)
+
+    def cleanup(self):
+        ipython_profile = Path("/home/ubuntu/.ipython")
+        ipython_profile.rmtree()
+        with utils.environment_edit_in_place('/etc/environment') as env:
+            env['PYSPARK_DRIVER_PYTHON_OPTS'] = ""

=== added file 'hooks/common.py'
--- hooks/common.py	1970-01-01 00:00:00 +0000
+++ hooks/common.py	2015-08-20 23:14:47 +0000
@@ -0,0 +1,83 @@
+#!/usr/bin/env python
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""
+Common implementation for all hooks.
+"""
+
+import jujuresources
+from charmhelpers.core import hookenv
+from charmhelpers.core import unitdata
+from charmhelpers.core import charmframework
+
+
+def bootstrap_resources():
+    """
+    Install required resources defined in resources.yaml
+    """
+    if unitdata.kv().get('charm.bootstrapped', False):
+        return True
+    hookenv.status_set('maintenance', 'Installing base resources')
+    mirror_url = jujuresources.config_get('resources_mirror')
+    if not jujuresources.fetch(mirror_url=mirror_url):
+        missing = jujuresources.invalid()
+        hookenv.status_set('blocked', 'Unable to fetch required resource%s: %s' % (
+            's' if len(missing) > 1 else '',
+            ', '.join(missing),
+        ))
+        return False
+    jujuresources.install(['pathlib', 'jujubigdata'])
+    unitdata.kv().set('charm.bootstrapped', True)
+    return True
+
+
+def manage():
+    if not bootstrap_resources():
+        # defer until resources are available, since charmhelpers, and thus
+        # the framework, are required (will require manual intervention)
+        return
+
+    import jujubigdata
+    import callbacks
+
+    notebook_reqs = ['packages', 'dirs', 'ports']
+    dist_config = jujubigdata.utils.DistConfig(filename='dist.yaml',
+                                               required_keys=notebook_reqs)
+    notebook = callbacks.Notebook(dist_config)
+    manager = charmframework.Manager([
+        {
+            'name': 'notebook',
+            'provides': [],
+            'requires': [
+                jujubigdata.relations.Spark(),
+                callbacks.update_blocked_status,  # not really a requirement, but best way to fit into framework
+            ],
+            'callbacks': [
+                callbacks.update_working_status,
+                charmframework.helpers.open_ports(dist_config.exposed_ports('notebook')),
+                notebook.install,
+                notebook.configure_notebook,
+                notebook.start,
+                callbacks.update_active_status,
+            ],
+            'cleanup': [
+                charmframework.helpers.close_ports(dist_config.exposed_ports('notebook')),
+                notebook.stop,
+                notebook.cleanup,
+            ],
+        },
+    ])
+    manager.manage()
+
+
+if __name__ == '__main__':
+    manage()

=== added file 'hooks/config-changed'
--- hooks/config-changed	1970-01-01 00:00:00 +0000
+++ hooks/config-changed	2015-08-20 23:14:47 +0000
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import common
+common.manage()

=== added file 'hooks/install'
--- hooks/install	1970-01-01 00:00:00 +0000
+++ hooks/install	2015-08-20 23:14:47 +0000
@@ -0,0 +1,17 @@
+#!/usr/bin/python
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import setup
+setup.pre_install()
+
+import common
+common.manage()

=== added file 'hooks/setup.py'
--- hooks/setup.py	1970-01-01 00:00:00 +0000
+++ hooks/setup.py	2015-08-20 23:14:47 +0000
@@ -0,0 +1,33 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import subprocess
+from glob import glob
+
+
+def pre_install():
+    """
+    Do any setup required before the install hook.
+    """
+    install_pip()
+    install_bundled_resources()
+
+
+def install_pip():
+    subprocess.check_call(['apt-get', 'install', '-yq', 'python-pip', 'bzr'])
+
+
+def install_bundled_resources():
+    """
+    Install the bundled resources libraries.
+    """
+    archives = glob('resources/python/*')
+    subprocess.check_call(['pip', 'install'] + archives)

=== added file 'hooks/spark-relation-changed'
--- hooks/spark-relation-changed	1970-01-01 00:00:00 +0000
+++ hooks/spark-relation-changed	2015-08-20 23:14:47 +0000
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import common
+common.manage()

=== added file 'hooks/start'
--- hooks/start	1970-01-01 00:00:00 +0000
+++ hooks/start	2015-08-20 23:14:47 +0000
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import common
+common.manage()

=== added file 'hooks/stop'
--- hooks/stop	1970-01-01 00:00:00 +0000
+++ hooks/stop	2015-08-20 23:14:47 +0000
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import common
+common.manage()

=== added file 'icon.svg'
--- icon.svg	1970-01-01 00:00:00 +0000
+++ icon.svg	2015-08-20 23:14:47 +0000
@@ -0,0 +1,662 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:xlink="http://www.w3.org/1999/xlink";
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape";
+   width="96"
+   height="96"
+   id="svg6517"
+   version="1.1"
+   inkscape:version="0.48.5 r10040"
+   sodipodi:docname="icon.svg">
+  <defs
+     id="defs6519">
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#Background"
+       id="linearGradient6461"
+       gradientUnits="userSpaceOnUse"
+       x1="0"
+       y1="970.29498"
+       x2="144"
+       y2="970.29498"
+       gradientTransform="matrix(0,-0.66666669,0.6660448,0,-866.25992,731.29077)" />
+    <linearGradient
+       id="Background">
+      <stop
+         id="stop4178"
+         offset="0"
+         style="stop-color:#b8b8b8;stop-opacity:1" />
+      <stop
+         id="stop4180"
+         offset="1"
+         style="stop-color:#c9c9c9;stop-opacity:1" />
+    </linearGradient>
+    <filter
+       style="color-interpolation-filters:sRGB;"
+       inkscape:label="Inner Shadow"
+       id="filter1121">
+      <feFlood
+         flood-opacity="0.59999999999999998"
+         flood-color="rgb(0,0,0)"
+         result="flood"
+         id="feFlood1123" />
+      <feComposite
+         in="flood"
+         in2="SourceGraphic"
+         operator="out"
+         result="composite1"
+         id="feComposite1125" />
+      <feGaussianBlur
+         in="composite1"
+         stdDeviation="1"
+         result="blur"
+         id="feGaussianBlur1127" />
+      <feOffset
+         dx="0"
+         dy="2"
+         result="offset"
+         id="feOffset1129" />
+      <feComposite
+         in="offset"
+         in2="SourceGraphic"
+         operator="atop"
+         result="composite2"
+         id="feComposite1131" />
+    </filter>
+    <filter
+       style="color-interpolation-filters:sRGB;"
+       inkscape:label="Drop Shadow"
+       id="filter950">
+      <feFlood
+         flood-opacity="0.25"
+         flood-color="rgb(0,0,0)"
+         result="flood"
+         id="feFlood952" />
+      <feComposite
+         in="flood"
+         in2="SourceGraphic"
+         operator="in"
+         result="composite1"
+         id="feComposite954" />
+      <feGaussianBlur
+         in="composite1"
+         stdDeviation="1"
+         result="blur"
+         id="feGaussianBlur956" />
+      <feOffset
+         dx="0"
+         dy="1"
+         result="offset"
+         id="feOffset958" />
+      <feComposite
+         in="SourceGraphic"
+         in2="offset"
+         operator="over"
+         result="composite2"
+         id="feComposite960" />
+    </filter>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath873">
+      <g
+         transform="matrix(0,-0.66666667,0.66604479,0,-258.25992,677.00001)"
+         id="g875"
+         inkscape:label="Layer 1"
+         style="fill:#ff00ff;fill-opacity:1;stroke:none;display:inline">
+        <path
+           style="fill:#ff00ff;fill-opacity:1;stroke:none;display:inline"
+           d="m 46.702703,898.22775 50.594594,0 C 138.16216,898.22775 144,904.06497 144,944.92583 l 0,50.73846 c 0,40.86071 -5.83784,46.69791 -46.702703,46.69791 l -50.594594,0 C 5.8378378,1042.3622 0,1036.525 0,995.66429 L 0,944.92583 C 0,904.06497 5.8378378,898.22775 46.702703,898.22775 Z"
+           id="path877"
+           inkscape:connector-curvature="0"
+           sodipodi:nodetypes="sssssssss" />
+      </g>
+    </clipPath>
+    <filter
+       inkscape:collect="always"
+       id="filter891"
+       inkscape:label="Badge Shadow">
+      <feGaussianBlur
+         inkscape:collect="always"
+         stdDeviation="0.71999962"
+         id="feGaussianBlur893" />
+    </filter>
+    <filter
+       inkscape:collect="always"
+       id="filter3871"
+       color-interpolation-filters="sRGB">
+      <feGaussianBlur
+         inkscape:collect="always"
+         stdDeviation="2.9338986"
+         id="feGaussianBlur3873" />
+    </filter>
+    <filter
+       inkscape:collect="always"
+       id="filter3913"
+       color-interpolation-filters="sRGB">
+      <feGaussianBlur
+         inkscape:collect="always"
+         stdDeviation="0.75310748"
+         id="feGaussianBlur3915" />
+    </filter>
+    <linearGradient
+       id="linearGradient3919">
+      <stop
+         style="stop-color:#e4e4e4;stop-opacity:1;"
+         offset="0"
+         id="stop3921" />
+      <stop
+         id="stop3927"
+         offset="1"
+         style="stop-color:#ffffff;stop-opacity:1;" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3919"
+       id="linearGradient3579"
+       gradientUnits="userSpaceOnUse"
+       x1="209.00497"
+       y1="-22.631527"
+       x2="200.9668"
+       y2="-2.0393581" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4689-6"
+       id="linearGradient3266"
+       gradientUnits="userSpaceOnUse"
+       x1="323.06018"
+       y1="147.10051"
+       x2="147.68851"
+       y2="293.00339" />
+    <linearGradient
+       id="linearGradient4689-6">
+      <stop
+         style="stop-color:#5a9fd4;stop-opacity:1"
+         offset="0"
+         id="stop4691-3" />
+      <stop
+         style="stop-color:#306998;stop-opacity:1"
+         offset="1"
+         id="stop4693-8" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4689-6"
+       id="linearGradient3256"
+       gradientUnits="userSpaceOnUse"
+       x1="486.50031"
+       y1="184.54053"
+       x2="496.16876"
+       y2="248.36336" />
+    <linearGradient
+       id="linearGradient3596">
+      <stop
+         style="stop-color:#5a9fd4;stop-opacity:1"
+         offset="0"
+         id="stop3598" />
+      <stop
+         style="stop-color:#306998;stop-opacity:1"
+         offset="1"
+         id="stop3600" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4689-6"
+       id="linearGradient3254"
+       gradientUnits="userSpaceOnUse"
+       x1="486.50031"
+       y1="184.54053"
+       x2="496.16876"
+       y2="248.36336" />
+    <linearGradient
+       id="linearGradient3603">
+      <stop
+         style="stop-color:#5a9fd4;stop-opacity:1"
+         offset="0"
+         id="stop3605" />
+      <stop
+         style="stop-color:#306998;stop-opacity:1"
+         offset="1"
+         id="stop3607" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4689-6"
+       id="linearGradient3260"
+       gradientUnits="userSpaceOnUse"
+       x1="485.7803"
+       y1="185.98055"
+       x2="496.88876"
+       y2="249.08336" />
+    <linearGradient
+       id="linearGradient3610">
+      <stop
+         style="stop-color:#5a9fd4;stop-opacity:1"
+         offset="0"
+         id="stop3612" />
+      <stop
+         style="stop-color:#306998;stop-opacity:1"
+         offset="1"
+         id="stop3614" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4689-6"
+       id="linearGradient3258"
+       gradientUnits="userSpaceOnUse"
+       x1="485.7803"
+       y1="185.98055"
+       x2="496.88876"
+       y2="249.08336" />
+    <linearGradient
+       id="linearGradient3617">
+      <stop
+         style="stop-color:#5a9fd4;stop-opacity:1"
+         offset="0"
+         id="stop3619" />
+      <stop
+         style="stop-color:#306998;stop-opacity:1"
+         offset="1"
+         id="stop3621" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4689-6"
+       id="linearGradient3264"
+       gradientUnits="userSpaceOnUse"
+       x1="484.3403"
+       y1="182.38054"
+       x2="495.44876"
+       y2="243.32335" />
+    <linearGradient
+       id="linearGradient3624">
+      <stop
+         style="stop-color:#5a9fd4;stop-opacity:1"
+         offset="0"
+         id="stop3626" />
+      <stop
+         style="stop-color:#306998;stop-opacity:1"
+         offset="1"
+         id="stop3628" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4689-6"
+       id="linearGradient3262"
+       gradientUnits="userSpaceOnUse"
+       x1="484.3403"
+       y1="182.38054"
+       x2="495.44876"
+       y2="243.32335" />
+    <linearGradient
+       id="linearGradient3631">
+      <stop
+         style="stop-color:#5a9fd4;stop-opacity:1"
+         offset="0"
+         id="stop3633" />
+      <stop
+         style="stop-color:#306998;stop-opacity:1"
+         offset="1"
+         id="stop3635" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3919-6">
+      <stop
+         style="stop-color:#e4e4e4;stop-opacity:1;"
+         offset="0"
+         id="stop3921-3" />
+      <stop
+         id="stop3927-0"
+         offset="1"
+         style="stop-color:#ffffff;stop-opacity:1;" />
+    </linearGradient>
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="4.0745362"
+     inkscape:cx="18.514671"
+     inkscape:cy="78.469374"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0"
+     inkscape:window-width="1920"
+     inkscape:window-height="1056"
+     inkscape:window-x="2160"
+     inkscape:window-y="340"
+     inkscape:window-maximized="1"
+     showborder="true"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:showpageshadow="false">
+    <inkscape:grid
+       type="xygrid"
+       id="grid821" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="16,48"
+       id="guide823" />
+    <sodipodi:guide
+       orientation="0,1"
+       position="64,80"
+       id="guide825" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="80,40"
+       id="guide827" />
+    <sodipodi:guide
+       orientation="0,1"
+       position="64,16"
+       id="guide829" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata6522">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="BACKGROUND"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(268,-635.29076)"
+     style="display:inline">
+    <path
+       style="fill:url(#linearGradient6461);fill-opacity:1;stroke:none;display:inline;filter:url(#filter1121)"
+       d="m -268,700.15563 0,-33.72973 c 0,-27.24324 3.88785,-31.13513 31.10302,-31.13513 l 33.79408,0 c 27.21507,0 31.1029,3.89189 31.1029,31.13513 l 0,33.72973 c 0,27.24325 -3.88783,31.13514 -31.1029,31.13514 l -33.79408,0 C -264.11215,731.29077 -268,727.39888 -268,700.15563 Z"
+       id="path6455"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="sssssssss" />
+    <g
+       id="g3712"
+       transform="matrix(0.17744693,0,0,0.13619629,-267.2095,648.28218)">
+      <path
+         transform="matrix(2.2092365,0,0,2.2092365,35.81527,-594.94434)"
+         sodipodi:nodetypes="cccccc"
+         inkscape:connector-curvature="0"
+         id="path3077"
+         d="m 20,279.27442 88.35063,0 c 33.9881,0 77.49135,12.81451 77.49135,29.1526 L 185.84198,492 20,492 z"
+         style="fill:#666666;fill-opacity:0.99033813;stroke:#7d7d7d;stroke-width:1.08476496;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;filter:url(#filter3871)"
+         inkscape:export-filename="/Users/matthiasbussonnier/Desktop/ipython-python.png"
+         inkscape:export-xdpi="90"
+         inkscape:export-ydpi="90" />
+      <g
+         id="g3706">
+        <path
+           sodipodi:nodetypes="cccccc"
+           inkscape:connector-curvature="0"
+           id="path4338"
+           d="m 79.999519,21.999517 194.983301,0 c 61.89823,0 171.01766,39.636924 171.01766,64.410449 l 0,405.590514 -366.000961,0 z"
+           style="fill:#ffffff;fill-opacity:0.99033813;stroke:#7d7d7d;stroke-width:1.99903858;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+           inkscape:export-filename="/Users/matthiasbussonnier/Desktop/ipython-python.png"
+           inkscape:export-xdpi="90"
+           inkscape:export-ydpi="90" />
+        <g
+           id="g3936"
+           transform="matrix(2.2092365,0,0,2.2092365,-68.579768,91.189025)">
+          <path
+             style="fill:#ffffff;stroke:#7d7d7d;stroke-width:1.08476496;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;filter:url(#filter3913)"
+             d="m 155.51352,-30.823863 c 14.89296,0 39.4548,5.804798 45.99753,30.22261295 8.5133,-4.59641195 30.96516,-11.88953595 31.36664,-1.19791745"
+             id="path3875"
+             inkscape:connector-curvature="0"
+             sodipodi:nodetypes="ccc" />
+          <path
+             sodipodi:nodetypes="cccc"
+             inkscape:connector-curvature="0"
+             id="path4384"
+             d="m 155.51351,-31.318075 c 14.89296,0 39.4548,5.804798 45.99753,30.2226129 8.5133,-4.596412 30.96516,-11.8895359 31.36664,-1.1979175 0,-18.1489184 -57.91307,-29.0246954 -77.36417,-29.0246954 z"
+             style="fill:url(#linearGradient3579);fill-opacity:1;stroke:#7d7d7d;stroke-width:1.08476496;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+        </g>
+      </g>
+    </g>
+    <g
+       transform="matrix(0.15007571,0,0,0.18552765,-259.29043,641.50634)"
+       id="g4227">
+      <g
+         style="fill:#000000"
+         id="g3992"
+         transform="matrix(0.76400311,0,0,0.76400311,36.179593,5.3479839)">
+        <g
+           style="fill:#000000;fill-opacity:1"
+           id="g3994">
+          <g
+             id="text3996"
+             style="font-size:147.90756226px;font-weight:normal;fill:#000000;fill-rule:evenodd;font-family:Droid Sans Mono">
+            <path
+               id="path4049"
+               style="font-size:147.90756226px;font-weight:normal;fill:#000000;fill-rule:evenodd;font-family:Droid Sans Mono"
+               d="m 355.96093,272.06912 -38.13242,0 0,-128.9858 38.13242,0 0,10.76086 -24.98829,0 0,107.39186 24.98829,0 0,10.83308"
+               inkscape:connector-curvature="0" />
+          </g>
+          <g
+             id="text3998"
+             style="font-size:147.90756226px;font-weight:normal;fill:#000000;fill-rule:evenodd;font-family:Droid Sans Mono">
+            <path
+               id="path4052"
+               style="font-size:147.90756226px;font-weight:normal;fill:#000000;fill-rule:evenodd;font-family:Droid Sans Mono"
+               d="m 439.12013,261.23604 24.98829,0 0,-107.39186 -24.98829,0 0,-10.76086 38.13242,0 0,128.9858 -38.13242,0 0,-10.83308"
+               inkscape:connector-curvature="0" />
+          </g>
+          <g
+             id="text4000"
+             style="font-size:147.90756226px;font-weight:normal;fill:#000000;fill-rule:evenodd;font-family:Droid Sans Mono">
+            <path
+               id="path4055"
+               style="font-size:147.90756226px;font-weight:normal;fill:#000000;fill-rule:evenodd;font-family:Droid Sans Mono"
+               d="m 514.87478,165.86889 c 6.11462,8e-5 9.17195,3.3463 9.17201,10.03865 -6e-5,6.6925 -3.05739,10.03871 -9.17201,10.03865 -6.1147,6e-5 -9.17203,-3.34615 -9.172,-10.03865 -3e-5,-6.69235 3.0573,-10.03857 9.172,-10.03865 m 0,63.26515 c 6.11462,2e-5 9.17195,3.34623 9.17201,10.03865 -6e-5,6.74058 -3.05739,10.11087 -9.17201,10.11087 -6.1147,0 -9.17203,-3.37029 -9.172,-10.11087 -3e-5,-6.69242 3.0573,-10.03863 9.172,-10.03865"
+               inkscape:connector-curvature="0" />
+          </g>
+        </g>
+        <g
+           style="fill:#000000"
+           id="g4002">
+          <g
+             id="text4004"
+             style="font-size:204.03166199px;font-weight:normal;fill:#000000;fill-rule:evenodd;font-family:Droid Sans Mono">
+            <path
+               id="path4058"
+               style="font-size:204.03166199px;font-weight:normal;fill:#000000;fill-rule:evenodd;font-family:Droid Sans Mono"
+               d="m 151.39749,272.06912 -77.308871,0 0,-12.25385 29.389331,-1.9925 0,-117.1588 -29.389331,-1.9925 0,-12.25386 77.308871,0 0,12.25386 -29.2897,1.9925 0,117.1588 29.2897,1.9925 0,12.25385"
+               inkscape:connector-curvature="0" />
+            <path
+               id="path4060"
+               style="font-size:204.03166199px;font-weight:normal;fill:#000000;fill-rule:evenodd;font-family:Droid Sans Mono"
+               d="m 210.07652,215.38259 0,56.68653 -18.53022,0 0,-145.65151 40.24843,0 c 34.13802,1.5e-4 51.20706,14.21328 51.20716,42.63943 -10e-5,14.54532 -4.61605,25.90254 -13.84785,34.0717 -9.16557,8.16929 -22.51528,12.25391 -40.04918,12.25385 l -19.02834,0 m 0,-15.74072 16.93622,0 c 13.15041,7e-5 22.54834,-2.39092 28.19383,-7.17299 5.71173,-4.78191 8.56764,-12.25376 8.56773,-22.41559 -9e-5,-18.5301 -11.22448,-27.7952 -33.67319,-27.79533 l -20.02459,0 0,57.38391"
+               inkscape:connector-curvature="0" />
+          </g>
+          <g
+             id="text4006"
+             style="font-size:131.4621582px;font-weight:normal;fill:#000000;fill-rule:evenodd;font-family:Droid Sans Mono">
+            <path
+               id="path4063"
+               style="font-size:131.4621582px;font-weight:normal;fill:#000000;fill-rule:evenodd;font-family:Droid Sans Mono"
+               d="m 367.00874,170.0062 12.06781,0 16.81792,41.9806 c 3.50904,8.77272 5.41336,14.97779 5.71295,18.61524 l 0.38515,0 c 0.98421,-4.79287 2.90992,-11.04074 5.77714,-18.74363 l 15.34153,-41.85221 12.13201,0 -30.49049,79.66042 c -2.86722,7.44609 -6.20512,13.03065 -10.01372,16.75373 -3.80867,3.76581 -9.07228,5.64873 -15.79087,5.64876 -3.68027,-3e-5 -7.27493,-0.36378 -10.784,-1.09124 l 0,-9.30762 c 2.6532,0.5135 5.56316,0.77026 8.72991,0.77028 4.10817,-2e-5 7.2963,-0.87729 9.56438,-2.63181 2.31083,-1.75455 4.36493,-4.77151 6.16229,-9.05086 l 3.72305,-9.62857 -29.33506,-71.12309"
+               inkscape:connector-curvature="0" />
+          </g>
+        </g>
+      </g>
+      <g
+         transform="matrix(0.76400311,0,0,0.76400311,34.485122,4.1927795)"
+         style="fill:url(#linearGradient3266);fill-opacity:1"
+         id="g3938">
+        <g
+           id="text111"
+           style="font-size:147.90756226px;font-weight:normal;fill:url(#linearGradient3256);fill-rule:evenodd;font-family:Droid Sans Mono">
+          <path
+             id="path4037"
+             style="font-size:147.90756226px;font-weight:normal;fill:url(#linearGradient3254);fill-rule:evenodd;font-family:Droid Sans Mono"
+             d="m 355.96093,272.06912 -38.13242,0 0,-128.9858 38.13242,0 0,10.76086 -24.98829,0 0,107.39186 24.98829,0 0,10.83308"
+             inkscape:connector-curvature="0" />
+        </g>
+        <g
+           id="text113"
+           style="font-size:147.90756226px;font-weight:normal;fill:url(#linearGradient3260);fill-rule:evenodd;font-family:Droid Sans Mono">
+          <path
+             id="path4043"
+             style="font-size:147.90756226px;font-weight:normal;fill:url(#linearGradient3258);fill-rule:evenodd;font-family:Droid Sans Mono"
+             d="m 439.12013,261.23604 24.98829,0 0,-107.39186 -24.98829,0 0,-10.76086 38.13242,0 0,128.9858 -38.13242,0 0,-10.83308"
+             inkscape:connector-curvature="0" />
+        </g>
+        <g
+           id="text115"
+           style="font-size:147.90756226px;font-weight:normal;fill:url(#linearGradient3264);fill-rule:evenodd;font-family:Droid Sans Mono">
+          <path
+             id="path4046"
+             style="font-size:147.90756226px;font-weight:normal;fill:url(#linearGradient3262);fill-rule:evenodd;font-family:Droid Sans Mono"
+             d="m 514.87478,165.86889 c 6.11462,8e-5 9.17195,3.3463 9.17201,10.03865 -6e-5,6.6925 -3.05739,10.03871 -9.17201,10.03865 -6.1147,6e-5 -9.17203,-3.34615 -9.172,-10.03865 -3e-5,-6.69235 3.0573,-10.03857 9.172,-10.03865 m 0,63.26515 c 6.11462,2e-5 9.17195,3.34623 9.17201,10.03865 -6e-5,6.74058 -3.05739,10.11087 -9.17201,10.11087 -6.1147,0 -9.17203,-3.37029 -9.172,-10.11087 -3e-5,-6.69242 3.0573,-10.03863 9.172,-10.03865"
+             inkscape:connector-curvature="0" />
+        </g>
+      </g>
+      <g
+         transform="matrix(0.76400311,0,0,0.76400311,34.485122,4.1927795)"
+         style="fill:#4d4d4d;fill-opacity:1"
+         id="g3945">
+        <g
+           id="text109"
+           style="font-size:204.03166199px;font-weight:normal;fill:#4d4d4d;fill-opacity:1;fill-rule:evenodd;font-family:Droid Sans Mono">
+          <path
+             id="path4032"
+             style="font-size:204.03166199px;font-weight:normal;fill:#4d4d4d;fill-opacity:1;fill-rule:evenodd;font-family:Droid Sans Mono"
+             d="m 151.39749,272.06912 -77.308871,0 0,-12.25385 29.389331,-1.9925 0,-117.1588 -29.389331,-1.9925 0,-12.25386 77.308871,0 0,12.25386 -29.2897,1.9925 0,117.1588 29.2897,1.9925 0,12.25385"
+             inkscape:connector-curvature="0" />
+          <path
+             id="path4034"
+             style="font-size:204.03166199px;font-weight:normal;fill:#4d4d4d;fill-opacity:1;fill-rule:evenodd;font-family:Droid Sans Mono"
+             d="m 210.07652,215.38259 0,56.68653 -18.53022,0 0,-145.65151 40.24843,0 c 34.13802,1.5e-4 51.20706,14.21328 51.20716,42.63943 -10e-5,14.54532 -4.61605,25.90254 -13.84785,34.0717 -9.16557,8.16929 -22.51528,12.25391 -40.04918,12.25385 l -19.02834,0 m 0,-15.74072 16.93622,0 c 13.15041,7e-5 22.54834,-2.39092 28.19383,-7.17299 5.71173,-4.78191 8.56764,-12.25376 8.56773,-22.41559 -9e-5,-18.5301 -11.22448,-27.7952 -33.67319,-27.79533 l -20.02459,0 0,57.38391"
+             inkscape:connector-curvature="0" />
+        </g>
+        <g
+           id="text117"
+           style="font-size:131.4621582px;font-weight:normal;fill:#4d4d4d;fill-opacity:1;fill-rule:evenodd;font-family:Droid Sans Mono">
+          <path
+             id="path4040"
+             style="font-size:131.4621582px;font-weight:normal;fill:#4d4d4d;fill-opacity:1;fill-rule:evenodd;font-family:Droid Sans Mono"
+             d="m 367.00874,170.0062 12.06781,0 16.81792,41.9806 c 3.50904,8.77272 5.41336,14.97779 5.71295,18.61524 l 0.38515,0 c 0.98421,-4.79287 2.90992,-11.04074 5.77714,-18.74363 l 15.34153,-41.85221 12.13201,0 -30.49049,79.66042 c -2.86722,7.44609 -6.20512,13.03065 -10.01372,16.75373 -3.80867,3.76581 -9.07228,5.64873 -15.79087,5.64876 -3.68027,-3e-5 -7.27493,-0.36378 -10.784,-1.09124 l 0,-9.30762 c 2.6532,0.5135 5.56316,0.77026 8.72991,0.77028 4.10817,-2e-5 7.2963,-0.87729 9.56438,-2.63181 2.31083,-1.75455 4.36493,-4.77151 6.16229,-9.05086 l 3.72305,-9.62857 -29.33506,-71.12309"
+             inkscape:connector-curvature="0" />
+        </g>
+      </g>
+    </g>
+    <g
+       style="fill:#000000;stroke:none"
+       transform="matrix(0.01117253,0,0,-0.01890653,-248.2999,738.0088)"
+       id="g3904">
+      <path
+         inkscape:connector-curvature="0"
+         d="m 746,2800 c -171,-43 -305,-206 -293,-356 6,-87 39,-141 176,-290 137,-149 151,-168 151,-212 0,-74 -67,-132 -152,-132 -60,0 -105,31 -139,96 l -26,51 -102,-54 c -55,-30 -101,-59 -101,-64 0,-27 64,-130 103,-166 54,-50 123,-73 218,-73 227,0 419,177 422,391 2,97 -31,155 -166,298 -179,188 -197,224 -136,275 69,58 145,42 207,-43 l 28,-39 92,67 c 50,36 91,71 92,78 0,23 -110,131 -156,153 -54,25 -159,34 -218,20 z"
+         id="path3906" />
+      <path
+         inkscape:connector-curvature="0"
+         d="m 3700,2700 c -66,-44 -107,-77 -112,-92 -3,-13 -18,-115 -33,-228 -14,-113 -42,-322 -61,-464 -19,-142 -34,-265 -34,-273 0,-10 24,-13 105,-13 l 104,0 6,33 c 8,47 55,407 55,421 0,6 70,-94 155,-222 l 155,-232 125,0 c 69,0 125,2 125,4 0,3 -85,123 -188,268 -103,145 -191,269 -196,276 -5,8 32,54 113,137 67,68 121,130 120,137 0,7 -8,55 -17,107 l -17,93 -170,-188 c -105,-116 -171,-182 -173,-173 -2,9 11,117 28,241 16,123 30,227 30,231 0,13 -16,5 -120,-63 z"
+         id="path3908" />
+      <path
+         inkscape:connector-curvature="0"
+         d="m 1467,2485 c -193,-53 -356,-231 -386,-423 -13,-83 -91,-675 -91,-691 0,-7 31,-11 94,-11 l 94,0 11,80 c 6,44 16,116 22,160 6,45 14,79 18,77 92,-50 131,-61 218,-65 112,-5 198,18 288,76 169,109 271,324 236,494 -31,152 -128,261 -270,302 -65,19 -167,19 -234,1 z m 141,-186 c 95,-20 171,-113 172,-210 0,-76 -27,-138 -89,-200 -43,-42 -69,-59 -115,-73 -139,-44 -263,21 -296,156 -33,129 69,284 211,322 68,18 59,18 117,5 z"
+         id="path3910" />
+      <path
+         inkscape:connector-curvature="0"
+         d="m 2439,2486 c -177,-51 -339,-216 -378,-386 -37,-162 23,-336 144,-420 72,-49 142,-70 238,-70 75,0 162,17 194,38 9,6 46,232 39,239 -1,2 -21,-11 -44,-28 -132,-97 -296,-68 -363,63 -25,50 -25,148 0,205 101,222 411,238 471,24 14,-50 12,-129 -4,-252 -8,-57 -17,-129 -21,-159 -3,-30 -8,-67 -11,-82 l -6,-28 95,0 95,0 6,28 c 22,111 58,437 53,495 -9,135 -108,269 -235,318 -70,27 -205,35 -273,15 z"
+         id="path3912" />
+      <path
+         inkscape:connector-curvature="0"
+         d="m 3243,2464 c -56,-20 -126,-84 -153,-139 -17,-35 -31,-115 -61,-343 -21,-163 -41,-309 -44,-324 l -4,-28 98,0 99,0 6,28 c 3,15 8,47 11,72 3,25 19,148 36,275 37,278 35,275 144,275 l 73,0 6,32 c 3,17 9,62 13,100 l 6,68 -94,-1 c -55,0 -111,-7 -136,-15 z"
+         id="path3914" />
+    </g>
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer3"
+     inkscape:label="PLACE YOUR PICTOGRAM HERE"
+     style="display:inline" />
+  <g
+     inkscape:groupmode="layer"
+     id="layer2"
+     inkscape:label="BADGE"
+     style="display:none"
+     sodipodi:insensitive="true">
+    <g
+       style="display:inline"
+       transform="translate(-340.00001,-581)"
+       id="g4394"
+       clip-path="none">
+      <g
+         id="g855">
+        <g
+           inkscape:groupmode="maskhelper"
+           id="g870"
+           clip-path="url(#clipPath873)"
+           style="opacity:0.6;filter:url(#filter891)">
+          <path
+             transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-237.54282)"
+             d="m 264,552.36218 a 12,12 0 1 1 -24,0 A 12,12 0 1 1 264,552.36218 Z"
+             sodipodi:ry="12"
+             sodipodi:rx="12"
+             sodipodi:cy="552.36218"
+             sodipodi:cx="252"
+             id="path844"
+             style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+             sodipodi:type="arc" />
+        </g>
+        <g
+           id="g862">
+          <path
+             sodipodi:type="arc"
+             style="color:#000000;fill:#f5f5f5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+             id="path4398"
+             sodipodi:cx="252"
+             sodipodi:cy="552.36218"
+             sodipodi:rx="12"
+             sodipodi:ry="12"
+             d="m 264,552.36218 a 12,12 0 1 1 -24,0 A 12,12 0 1 1 264,552.36218 Z"
+             transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-238.54282)" />
+          <path
+             transform="matrix(1.25,0,0,1.25,33,-100.45273)"
+             d="m 264,552.36218 a 12,12 0 1 1 -24,0 A 12,12 0 1 1 264,552.36218 Z"
+             sodipodi:ry="12"
+             sodipodi:rx="12"
+             sodipodi:cy="552.36218"
+             sodipodi:cx="252"
+             id="path4400"
+             style="color:#000000;fill:#dd4814;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+             sodipodi:type="arc" />
+          <path
+             sodipodi:type="star"
+             style="color:#000000;fill:#f5f5f5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+             id="path4459"
+             sodipodi:sides="5"
+             sodipodi:cx="666.19574"
+             sodipodi:cy="589.50385"
+             sodipodi:r1="7.2431178"
+             sodipodi:r2="4.3458705"
+             sodipodi:arg1="1.0471976"
+             sodipodi:arg2="1.6755161"
+             inkscape:flatsided="false"
+             inkscape:rounded="0.1"
+             inkscape:randomized="0"
+             d="m 669.8173,595.77657 c -0.39132,0.22593 -3.62645,-1.90343 -4.07583,-1.95066 -0.44938,-0.0472 -4.05653,1.36297 -4.39232,1.06062 -0.3358,-0.30235 0.68963,-4.03715 0.59569,-4.47913 -0.0939,-0.44198 -2.5498,-3.43681 -2.36602,-3.8496 0.18379,-0.41279 4.05267,-0.59166 4.44398,-0.81759 0.39132,-0.22593 2.48067,-3.48704 2.93005,-3.4398 0.44938,0.0472 1.81505,3.67147 2.15084,3.97382 0.3358,0.30236 4.08294,1.2817 4.17689,1.72369 0.0939,0.44198 -2.9309,2.86076 -3.11469,3.27355 C 669.9821,591.68426 670.20862,595.55064 669.8173,595.77657 Z"
+             transform="matrix(1.511423,-0.16366377,0.16366377,1.511423,-755.37346,-191.93651)" />
+        </g>
+      </g>
+    </g>
+  </g>
+</svg>

=== renamed file 'icon.svg' => 'icon.svg.moved'
=== added file 'metadata.yaml'
--- metadata.yaml	1970-01-01 00:00:00 +0000
+++ metadata.yaml	2015-08-20 23:14:47 +0000
@@ -0,0 +1,14 @@
+name: apache-spark-notebook
+summary: A web-based IPython Notebook for Apache Spark.
+maintainer: Amir Sanjar <amir.sanjar@xxxxxxxxxxxxx>
+description: |
+  The IPython Notebook is an interactive computational environment, in which
+  you can combine code execution, rich text, mathematics, plots, and rich media.
+  IPython Notebook and Spark’s Python API are a powerful combination for data
+  science.
+tags: ["bigdata", "hadoop", "apache"]
+subordinate: true
+requires:
+  spark:
+    interface: spark
+    scope: container

=== renamed file 'metadata.yaml' => 'metadata.yaml.moved'
=== added directory 'resources'
=== renamed directory 'resources' => 'resources.moved'
=== added file 'resources.yaml'
--- resources.yaml	1970-01-01 00:00:00 +0000
+++ resources.yaml	2015-08-20 23:14:47 +0000
@@ -0,0 +1,7 @@
+options:
+  output_dir: /home/ubuntu/resources
+resources:
+  pathlib:
+    pypi: path.py>=7.0
+  jujubigdata:
+    pypi: jujubigdata>=4.0.0,<5.0.0

=== renamed file 'resources.yaml' => 'resources.yaml.moved'
=== added directory 'resources/python'
=== added file 'resources/python/PyYAML-3.11.tar.gz'
Binary files resources/python/PyYAML-3.11.tar.gz	1970-01-01 00:00:00 +0000 and resources/python/PyYAML-3.11.tar.gz	2015-08-20 23:14:47 +0000 differ
=== added file 'resources/python/charmhelpers-0.3.1.tar.gz'
Binary files resources/python/charmhelpers-0.3.1.tar.gz	1970-01-01 00:00:00 +0000 and resources/python/charmhelpers-0.3.1.tar.gz	2015-08-20 23:14:47 +0000 differ
=== added file 'resources/python/jujuresources-0.2.9.tar.gz'
Binary files resources/python/jujuresources-0.2.9.tar.gz	1970-01-01 00:00:00 +0000 and resources/python/jujuresources-0.2.9.tar.gz	2015-08-20 23:14:47 +0000 differ
=== added file 'resources/python/pyaml-15.5.7.tar.gz'
Binary files resources/python/pyaml-15.5.7.tar.gz	1970-01-01 00:00:00 +0000 and resources/python/pyaml-15.5.7.tar.gz	2015-08-20 23:14:47 +0000 differ
=== added file 'resources/python/six-1.9.0-py2.py3-none-any.whl'
Binary files resources/python/six-1.9.0-py2.py3-none-any.whl	1970-01-01 00:00:00 +0000 and resources/python/six-1.9.0-py2.py3-none-any.whl	2015-08-20 23:14:47 +0000 differ
=== added directory 'scripts'
=== renamed directory 'scripts' => 'scripts.moved'
=== added file 'scripts/00-pyspark-setup.py'
--- scripts/00-pyspark-setup.py	1970-01-01 00:00:00 +0000
+++ scripts/00-pyspark-setup.py	2015-08-20 23:14:47 +0000
@@ -0,0 +1,10 @@
+import os
+import sys
+
+py4j = 'python/lib/py4j-0.8.2.1-src.zip'
+spark_home = os.environ.get('SPARK_HOME', None)
+if not spark_home:
+    raise ValueError('SPARK_HOME environment variable is not set')
+sys.path.insert(0, os.path.join(spark_home, 'python'))
+sys.path.insert(0, py4j)
+execfile(os.path.join(spark_home, 'python/pyspark/shell.py'))

=== added file 'scripts/start_notebook.sh'
--- scripts/start_notebook.sh	1970-01-01 00:00:00 +0000
+++ scripts/start_notebook.sh	2015-08-20 23:14:47 +0000
@@ -0,0 +1,2 @@
+#!/bin/bash
+ipython notebook --profile=pyspark  >/dev/null 2>&1 &

=== added file 'scripts/stop_notebook.sh'
--- scripts/stop_notebook.sh	1970-01-01 00:00:00 +0000
+++ scripts/stop_notebook.sh	2015-08-20 23:14:47 +0000
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+PID=`pgrep -f 'ipython.*notebook'`
+if [ -n "${PID}" ]; then
+  kill ${PID}
+fi

=== added directory 'tests'
=== renamed directory 'tests' => 'tests.moved'
=== added file 'tests/00-setup'
--- tests/00-setup	1970-01-01 00:00:00 +0000
+++ tests/00-setup	2015-08-20 23:14:47 +0000
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+sudo add-apt-repository ppa:juju/stable -y
+sudo apt-get update
+sudo apt-get install python3 amulet -y

=== added file 'tests/100-deploy-spark-hdfs-yarn'
--- tests/100-deploy-spark-hdfs-yarn	1970-01-01 00:00:00 +0000
+++ tests/100-deploy-spark-hdfs-yarn	2015-08-20 23:14:47 +0000
@@ -0,0 +1,62 @@
+#!/usr/bin/python3
+
+import unittest
+import amulet
+
+
+class TestDeploy(unittest.TestCase):
+    """
+    Deployment test for Apache Spark using HDFS as shared storage and YARN as
+    cluster job manager.
+    """
+
+    @classmethod
+    def setUpClass(cls):
+        cls.d = amulet.Deployment(series='trusty')
+        # Deploy a hadoop cluster
+        cls.d.add('yarn-master', charm='cs:~bigdata-dev/trusty/apache-hadoop-yarn-master')
+        cls.d.add('hdfs-master', charm='cs:~bigdata-dev/trusty/apache-hadoop-hdfs-master')
+        cls.d.add('compute-slave', charm='cs:~bigdata-dev/trusty/apache-hadoop-compute-slave', units=3)
+        cls.d.add('plugin', charm='cs:~bigdata-dev/trusty/apache-hadoop-plugin')
+        cls.d.relate('yarn-master:namenode', 'hdfs-master:namenode')
+        cls.d.relate('compute-slave:nodemanager', 'yarn-master:nodemanager')
+        cls.d.relate('compute-slave:datanode', 'hdfs-master:datanode')
+        cls.d.relate('plugin:resourcemanager', 'yarn-master:resourcemanager')
+        cls.d.relate('plugin:namenode', 'hdfs-master:namenode')
+
+        # Add Spark Service
+        cls.d.add('spark', charm='cs:~bigdata-dev/trusty/apache-spark')
+        cls.d.relate('spark:hadoop-plugin', 'plugin:hadoop-plugin')
+
+        # Add IPythonNotebook
+        cls.d.add('notebook', charm='cs:~bigdata-dev/trusty/apache-spark-notebook')
+        cls.d.relate('notebook:spark', 'spark:spark')
+
+        cls.d.setup(timeout=3600)
+        cls.d.sentry.wait()
+        cls.unit = cls.d.sentry.unit['notebook/0']
+
+###########################################################################
+# Validate that the Spark HistoryServer is running
+###########################################################################
+    def test_spark_status(self):
+        o, c = self.unit.run("pgrep -a java | grep HistoryServer")
+        assert c == 0, "Spark HistoryServer not running"
+
+###########################################################################
+# Validate that the Notebook process is running
+###########################################################################
+    def test_notebook_status(self):
+        o, c = self.unit.run("pgrep -a python | grep notebook")
+        assert c == 0, "IPython Notebook daemon not running"
+
+###########################################################################
+# Validate Spark commandline operation - run SparkPi
+###########################################################################
+    def test_spark_job(self):
+        o, c = self.unit.run("su ubuntu -c '/home/ubuntu/sparkpi.sh'")
+        assert c == 0, "SparkPi test failed: %s" % o
+
+
+if __name__ == '__main__':
+    unittest.main()

=== added directory 'tests/remote'
=== added file 'tests/remote/test_dist_config.py'
--- tests/remote/test_dist_config.py	1970-01-01 00:00:00 +0000
+++ tests/remote/test_dist_config.py	2015-08-20 23:14:47 +0000
@@ -0,0 +1,72 @@
+#!/usr/bin/env python
+
+import grp
+import os
+import pwd
+import unittest
+
+from charmhelpers.contrib import bigdata
+
+
+class TestDistConfig(unittest.TestCase):
+    """
+    Test that the ``dist.yaml`` settings were applied properly, such as users, groups, and dirs.
+
+    This is done as a remote test on the deployed unit rather than a regular
+    test under ``tests/`` because filling in the ``dist.yaml`` requires Juju
+    context (e.g., config).
+    """
+    @classmethod
+    def setUpClass(cls):
+        config = None
+        config_dir = os.environ['JUJU_CHARM_DIR']
+        config_file = 'dist.yaml'
+        if os.path.isfile(os.path.join(config_dir, config_file)):
+            config = os.path.join(config_dir, config_file)
+        if not config:
+            raise IOError('Could not find {} in {}'.format(config_file, config_dir))
+        reqs = ['vendor', 'hadoop_version', 'packages', 'groups', 'users',
+                'dirs', 'ports']
+        cls.dist_config = bigdata.utils.DistConfig(config, reqs)
+
+    def test_groups(self):
+        for name in self.dist_config.groups:
+            try:
+                grp.getgrnam(name)
+            except KeyError:
+                self.fail('Group {} is missing'.format(name))
+
+    def test_users(self):
+        for username, details in self.dist_config.users.items():
+            try:
+                user = pwd.getpwnam(username)
+            except KeyError:
+                self.fail('User {} is missing'.format(username))
+            for groupname in details['groups']:
+                try:
+                    group = grp.getgrnam(groupname)
+                except KeyError:
+                    self.fail('Group {} referenced by user {} does not exist'.format(
+                        groupname, username))
+                if group.gr_gid != user.pw_gid:
+                    self.assertIn(username, group.gr_mem, 'User {} not in group {}'.format(
+                        username, groupname))
+
+    def test_dirs(self):
+        for name, details in self.dist_config.dirs.items():
+            dirpath = self.dist_config.path(name)
+            self.assertTrue(dirpath.isdir(), 'Dir {} is missing'.format(name))
+            stat = dirpath.stat()
+            owner = pwd.getpwuid(stat.st_uid).pw_name
+            group = grp.getgrgid(stat.st_gid).gr_name
+            perms = stat.st_mode & ~0o40000
+            self.assertEqual(owner, details.get('owner', 'root'),
+                             'Dir {} ({}) has wrong owner: {}'.format(name, dirpath, owner))
+            self.assertEqual(group, details.get('group', 'root'),
+                             'Dir {} ({}) has wrong group: {}'.format(name, dirpath, group))
+            self.assertEqual(perms, details.get('perms', 0o755),
+                             'Dir {} ({}) has wrong perms: 0o{:o}'.format(name, dirpath, perms))
+
+
+if __name__ == '__main__':
+    unittest.main()

=== added file 'tests/tests.yaml'
--- tests/tests.yaml	1970-01-01 00:00:00 +0000
+++ tests/tests.yaml	2015-08-20 23:14:47 +0000
@@ -0,0 +1,10 @@
+# Driver for bundletester: https://github.com/juju-solutions/bundletester
+#
+# It may be useful to alter the defaults during manual testing. For example,
+# set 'reset: false' to reuse existing charms instead of redeploying them.
+
+# Allow bootstrap of current env, default: true
+bootstrap: true
+
+# Use juju-deployer to reset env between test, default: true
+reset: true


Follow ups