← Back to team overview

yellow team mailing list archive

[Merge] lp:~frankban/charms/precise/juju-gui/juju-gui into lp:~juju-gui/charms/precise/juju-gui/trunk

 

Francesco Banconi has proposed merging lp:~frankban/charms/precise/juju-gui/juju-gui into lp:~juju-gui/charms/precise/juju-gui/trunk with lp:~benji/charms/precise/juju-gui/second as a prerequisite.

Requested reviews:
  Juju GUI Hackers (juju-gui)
Related bugs:
  Bug #1074410 in juju-gui: "Need juju GUI charm that connects to improv"
  https://bugs.launchpad.net/juju-gui/+bug/1074410

For more details, see:
https://code.launchpad.net/~frankban/charms/precise/juju-gui/juju-gui/+merge/135213

First deploy charm, with tests.

First pass of the Juju GUI deploy charm, translated to Python from a
shell script prototype written by Kapil. Currently it only uses the
"improv" version of Juju, not the standard one.

It includes both unit and functional tests, the latter using the "test"
Jitsu command recently developed by Jim Baker. The README.txt file
shows instructions for running both kinds of tests.

https://codereview.appspot.com/6842074/

-- 
https://code.launchpad.net/~frankban/charms/precise/juju-gui/juju-gui/+merge/135213
Your team Juju GUI Hackers is requested to review the proposed merge of lp:~frankban/charms/precise/juju-gui/juju-gui into lp:~juju-gui/charms/precise/juju-gui/trunk.
=== modified file 'README.txt'
--- README.txt	2012-11-20 18:48:21 +0000
+++ README.txt	2012-11-20 18:48:21 +0000
@@ -37,6 +37,7 @@
 At the time of this writing the Jitsu test command is not yet released.  To
 run it you must first install it locally (replace USER with your user name)::
 
+    sudo apt-get install autoconf libtool python-shelltoolbox
     bzr branch lp:~jimbaker/juju-jitsu/unit-test jitsu-unit-test
     cd jitsu-unit-test
     autoreconf
@@ -58,7 +59,7 @@
 
 Run the functional tests thusly::
 
-    ~/bin/jitsu test juju-gui --logdir /tmp
+    JUJU_REPOSITORY=/path/to/local/repo ~/bin/jitsu test juju-gui --logdir /tmp
 
 If Jitsu generates errors about not being able bootstrap::
 

=== added directory 'config'
=== added file 'config.yaml'
--- config.yaml	1970-01-01 00:00:00 +0000
+++ config.yaml	2012-11-20 18:48:21 +0000
@@ -0,0 +1,11 @@
+options:
+  juju-gui-branch:
+    description: |
+      The Juju GUI Bazaar branch containing the source code to be deployed.
+    type: string
+    default: lp:juju-gui
+  juju-improv-branch:
+    description: |
+      The Juju Bazaar branch containing the improv script.
+    type: string
+    default: lp:~hazmat/juju/rapi-rollup

=== added file 'config/config.js.template'
--- config/config.js.template	1970-01-01 00:00:00 +0000
+++ config/config.js.template	2012-11-20 18:48:21 +0000
@@ -0,0 +1,10 @@
+var juju_config = {
+  consoleEnabled: true,
+  serverRouting: false,
+  html5: true,
+  container: '#main',
+  viewContainer: '#main',
+  transitions: false,
+  charm_store_url: 'http://jujucharms.com/',
+  socket_url: '%(ws_url)s'
+};

=== added file 'config/juju-api-improv.conf.template'
--- config/juju-api-improv.conf.template	1970-01-01 00:00:00 +0000
+++ config/juju-api-improv.conf.template	2012-11-20 18:48:21 +0000
@@ -0,0 +1,9 @@
+description "Juju GUI"
+author "Juju GUI Peeps"
+
+start on runlevel [2345]
+stop on runlevel [!2345]
+
+setuid ubuntu
+env PYTHONPATH=%(juju_dir)s:$PYTHONPATH
+exec /usr/bin/python %(juju_dir)s/improv.py -f %(juju_dir)s/sample.json &> /tmp/improv-agent.output

=== added file 'config/juju-gui.conf.template'
--- config/juju-gui.conf.template	1970-01-01 00:00:00 +0000
+++ config/juju-gui.conf.template	2012-11-20 18:48:21 +0000
@@ -0,0 +1,10 @@
+description "Juju GUI"
+author "Juju GUI Peeps"
+
+start on runlevel [2345]
+stop on runlevel [!2345]
+
+respawn
+
+setuid ubuntu
+exec nodejs %(juju_gui_dir)s/server.js

=== added directory 'hooks'
=== added file 'hooks/install'
--- hooks/install	1970-01-01 00:00:00 +0000
+++ hooks/install	2012-11-20 18:48:21 +0000
@@ -0,0 +1,52 @@
+#!/usr/bin/env python2
+
+from subprocess import check_call
+
+
+# python-shelltoolbox is installed as a dependency of python-charmhelpers.
+check_call(['apt-get', 'install', '-y', 'python-charmhelpers'])
+
+
+# These modules depend on charmhelpers and shelltoolbox being installed so they
+# must not be imported until those packages are available.
+from charmhelpers import (
+    get_config,
+    log,
+    log_entry,
+    log_exit,
+)
+from shelltoolbox import (
+    apt_get_install,
+    cd,
+    command,
+    install_extra_repositories,
+    run,
+)
+
+
+def fetch():
+    """Install required dependencies and retrieve Juju/Juju-GUI branches."""
+    log('Installing dependencies.')
+    install_extra_repositories('ppa:chris-lea/node.js')
+    apt_get_install('bzr', 'imagemagick', 'make', 'nodejs', 'npm', 'zookeeper')
+    log('Retrieving source checkouts.')
+    config = get_config()
+    bzr_checkout = command('bzr', 'co', '--lightweight')
+    bzr_checkout(config['juju-gui-branch'], 'juju-gui')
+    bzr_checkout(config['juju-improv-branch'], 'juju')
+
+
+def build():
+    """Set up Juju-GUI."""
+    log('Building Juju-GUI.')
+    with cd('juju-gui'):
+        run('make')
+
+
+if __name__ == '__main__':
+    log_entry()
+    try:
+        fetch()
+        build()
+    finally:
+        log_exit()

=== added file 'hooks/start'
--- hooks/start	1970-01-01 00:00:00 +0000
+++ hooks/start	2012-11-20 18:48:21 +0000
@@ -0,0 +1,60 @@
+#!/usr/bin/env python2
+
+import os
+import sys
+
+from charmhelpers import (
+    log,
+    log_entry,
+    log_exit,
+    open_port,
+    service_control,
+    START,
+    unit_get,
+)
+from shelltoolbox import (
+    run,
+    su,
+)
+
+from utils import render_to_file
+
+
+CURRENT_DIR = os.getcwd()
+JUJU_DIR = os.path.join(CURRENT_DIR, 'juju')
+JUJU_GUI_DIR = os.path.join(CURRENT_DIR, 'juju-gui')
+
+
+def start_improv():
+    """Start a simulated juju environment using ``improv.py``."""
+    sys.path.insert(0, JUJU_DIR)
+    log('Retrieving websocket URL.')
+    ws_url = 'ws://{0}:8081/ws'.format(unit_get('public-address'))
+    with su('root'):
+        run('chown', '-R', 'ubuntu:', JUJU_GUI_DIR)
+    log('Setting up start up scripts.')
+    render_to_file(
+        'juju-api-improv.conf.template', {'juju_dir': JUJU_DIR},
+        '/etc/init/juju-api-improv.conf')
+    render_to_file(
+        'juju-gui.conf.template', {'juju_gui_dir': JUJU_GUI_DIR},
+        '/etc/init/juju-gui.conf')
+    log('Generating the Juju-GUI configuration file.')
+    render_to_file(
+        'config.js.template', {'ws_url': ws_url},
+        os.path.join(JUJU_GUI_DIR, 'app', 'config.js'))
+    log('Starting Juju-GUI.')
+    with su('root'):
+        service_control('juju-gui', START)
+        service_control('juju-api-improv', START)
+    log('Exposing services.')
+    open_port(8888)
+    open_port(8081)
+
+
+if __name__ == '__main__':
+    log_entry()
+    try:
+        start_improv()
+    finally:
+        log_exit()

=== added file 'hooks/stop'
--- hooks/stop	1970-01-01 00:00:00 +0000
+++ hooks/stop	2012-11-20 18:48:21 +0000
@@ -0,0 +1,26 @@
+#!/usr/bin/env python2
+
+from charmhelpers import (
+    log,
+    log_entry,
+    log_exit,
+    service_control,
+    STOP,
+)
+from shelltoolbox import su
+
+
+def stop():
+    """Stop the Juju API agent."""
+    log('Stopping Juju API agent.')
+    with su('root'):
+        service_control('juju-gui', STOP)
+        service_control('juju-api-improv', STOP)
+
+
+if __name__ == '__main__':
+    log_entry()
+    try:
+        stop()
+    finally:
+        log_exit()

=== added file 'hooks/utils.py'
--- hooks/utils.py	1970-01-01 00:00:00 +0000
+++ hooks/utils.py	2012-11-20 18:48:21 +0000
@@ -0,0 +1,18 @@
+"""Juju GUI charm utilities."""
+
+import os
+
+
+def render_to_file(template, context, destination):
+    """Render the given *template* into *destination* using *context*.
+
+    The arguments *template* is the name or path of the template file: it may
+    be either a path relative to ``../config`` or an absolute path.
+    The argument *destination* is a file path.
+    The argument *context* is a dict-like object.
+    """
+    template_path = os.path.join(
+        os.path.dirname(__file__), '..', 'config', template)
+    contents = open(template_path).read()
+    with open(destination, 'w') as stream:
+        stream.write(contents % context)

=== added file 'metadata.yaml'
--- metadata.yaml	1970-01-01 00:00:00 +0000
+++ metadata.yaml	2012-11-20 18:48:21 +0000
@@ -0,0 +1,7 @@
+juju: charm
+name: juju-gui
+summary: User interface for Juju (development)
+description: Web GUI for Juju
+provides:
+  web:
+    interface: http

=== added file 'revision'
--- revision	1970-01-01 00:00:00 +0000
+++ revision	2012-11-20 18:48:21 +0000
@@ -0,0 +1,1 @@
+10

=== added file 'tests/deploy.test'
--- tests/deploy.test	1970-01-01 00:00:00 +0000
+++ tests/deploy.test	2012-11-20 18:48:21 +0000
@@ -0,0 +1,41 @@
+#!/usr/bin/env python2
+
+import unittest
+import urllib2
+
+from shelltoolbox import command
+
+
+jitsu = command('jitsu')
+juju = command('juju')
+
+
+class DeployTest(unittest.TestCase):
+
+    def setUp(self):
+        self.charm = 'juju-gui'
+        self.port = '8888'
+        juju('deploy', 'local:{0}'.format(self.charm))
+        juju('expose', self.charm)
+        jitsu(
+            'watch', '--failfast', self.charm,
+            '--state', 'started', '--open-port', self.port)
+
+    def test_improv(self):
+        # Ensure the Juju GUI and improv services are correctly set up.
+        address = jitsu('get-service-info', self.charm, 'public-address')
+        hostname = address.strip().split(':')[1]
+        url = 'http://{0}:{1}'.format(hostname, self.port)
+        response = urllib2.urlopen(url)
+        self.assertEqual(200, response.getcode())
+        ws_url = 'http://{0}:8081/ws'.format(hostname)
+        # A bad request status code here means the websocket resource is found.
+        # It would take an actual websocket client to properly interact with
+        # the websocket server.
+        with self.assertRaises(urllib2.HTTPError) as context_manager:
+            urllib2.urlopen(ws_url)
+        self.assertEqual(400, context_manager.exception.getcode())
+
+
+if __name__ == '__main__':
+    unittest.main(verbosity=2)

=== removed file 'tests/functional-noop.test'
--- tests/functional-noop.test	2012-11-20 18:48:21 +0000
+++ tests/functional-noop.test	1970-01-01 00:00:00 +0000
@@ -1,8 +0,0 @@
-#!/usr/bin/env python2
-
-import sys
-
-# The only requirement for functional tests is that they have a non-zero exit
-# code on error.  This noop is successful, therefore we exit with zero.
-
-sys.exit(0)

=== removed file 'tests/test_addition.py'
--- tests/test_addition.py	2012-11-20 18:48:21 +0000
+++ tests/test_addition.py	1970-01-01 00:00:00 +0000
@@ -1,13 +0,0 @@
-#!/usr/bin/env python2
-
-import sys
-import unittest
-
-
-class PassingTest(unittest.TestCase):
-    def test_addition(self):
-        self.assertEqual(1+1, 3)
-
-
-if __name__ == '__main__':
-    sys.exit(unittest.main())

=== added file 'tests/test_utils.py'
--- tests/test_utils.py	1970-01-01 00:00:00 +0000
+++ tests/test_utils.py	2012-11-20 18:48:21 +0000
@@ -0,0 +1,31 @@
+#!/usr/bin/env python2
+
+import os
+import sys
+import tempfile
+import unittest
+
+from utils import render_to_file
+
+
+class RenderToFileTest(unittest.TestCase):
+
+    def setUp(self):
+        self.destination_file = tempfile.NamedTemporaryFile()
+        self.addCleanup(self.destination_file.close)
+        self.template_contents = '%(foo)s, %(bar)s'
+        with tempfile.NamedTemporaryFile(delete=False) as template_file:
+            template_file.write(self.template_contents)
+            self.template_path = template_file.name
+        self.addCleanup(os.remove, self.template_path)
+
+    def test_render_to_file(self):
+        # Ensure the template is correctly rendered using the given context.
+        context = {'foo': 'spam', 'bar': 'eggs'}
+        render_to_file(self.template_path, context, self.destination_file.name)
+        expected = self.template_contents % context
+        self.assertEqual(expected, self.destination_file.read())
+
+
+if __name__ == '__main__':
+    sys.exit(unittest.main())

=== modified file 'tests/unit.test'
--- tests/unit.test	2012-11-20 18:48:21 +0000
+++ tests/unit.test	2012-11-20 18:48:21 +0000
@@ -1,6 +1,10 @@
 #!/usr/bin/env python2
 
+import os
+import sys
 import unittest
-import sys
-
-sys.exit(unittest.TextTestRunner().run(unittest.TestLoader().discover('.')))
+
+
+path = os.path.dirname(__file__)
+runner = unittest.TextTestRunner(verbosity=2)
+sys.exit(runner.run(unittest.TestLoader().discover(path)))

=== added symlink 'tests/utils.py'
=== target is u'../hooks/utils.py'

Follow ups