yellow team mailing list archive
-
yellow team
-
Mailing list archive
-
Message #02061
lp:~frankban/charms/precise/juju-gui/bug-1088618-serve-releases into lp:~juju-gui/charms/precise/juju-gui/trunk
Francesco Banconi has proposed merging lp:~frankban/charms/precise/juju-gui/bug-1088618-serve-releases into lp:~juju-gui/charms/precise/juju-gui/trunk.
Requested reviews:
Juju GUI Hackers (juju-gui)
Related bugs:
Bug #1088618 in juju-gui: "charm should serve only releases (or make a release from a branch)"
https://bugs.launchpad.net/juju-gui/+bug/1088618
For more details, see:
https://code.launchpad.net/~frankban/charms/precise/juju-gui/bug-1088618-serve-releases/+merge/140745
Updated the charm to serve Juju GUI releases.
I had a pre-implementation call with Gary and
some really useful help from Brad.
Details:
Replaced the juju-gui-branch config option with a new juju-gui-source
one: here you can specify where to deploy the GUI from (stable, trunk, a
branch, a specific version of stable/trunk).
Added a bunch of functions in utils.py: they help parsing the
juju-gui-source option, retrieving the URL of a release, etc.
Added tests for the utility functions above.
Changed the fetch/build function in utils.py, so that now a release
tarball is always used to install the GUI. It can be downloaded from
Launchpad or created using "make distfile" from a juju-gui checkout.
Also reorganized the fetch/build code: separated each of the fetch/build
functions into two different functions (one for the GUI, one for the API).
Updated the install and the config-changed hooks to conform to the
new fetch/setup functions (mentioned above).
Updated the config-changed hook to reflect changes to the options,
install hook and utils.py.
Added one functional test specific to deploying from a branch.
The other tests now default to the latest stable release.
Created and uploaded Juju GUI trunk/stable releases.
Updated the stop hook: now the function in utils.py is called with the
argument it expects.
Overall code lint and clean up.
All those changes result in a big diff (~820 lines), sorry about that.
https://codereview.appspot.com/6977043/
--
https://code.launchpad.net/~frankban/charms/precise/juju-gui/bug-1088618-serve-releases/+merge/140745
Your team Juju GUI Hackers is requested to review the proposed merge of lp:~frankban/charms/precise/juju-gui/bug-1088618-serve-releases into lp:~juju-gui/charms/precise/juju-gui/trunk.
=== modified file 'config.yaml'
--- config.yaml 2012-12-07 18:39:00 +0000
+++ config.yaml 2012-12-19 18:05:33 +0000
@@ -1,9 +1,17 @@
options:
- juju-gui-branch:
+ juju-gui-source:
description: |
- The Juju GUI Bazaar branch containing the source code to be deployed.
+ Where to install Juju GUI from. Possible values are:
+ - 'stable' (default): the latest stable release will be deployed;
+ - 'trunk': the latest trunk release will be deployed;
+ - a stable version (e.g '0.1.0'): the specified stable version will be
+ deployed;
+ - a trunk version (e.g '0.1.0+build.1'): the specified trunk version
+ will be deployed;
+ - a Bazaar branch (e.g. 'lp:juju-gui'): a release will be created and
+ deployed from the specified Bazaar branch.
type: string
- default: lp:juju-gui
+ default: stable
juju-api-branch:
description: |
The Juju API Bazaar branch (implementing the websocket server).
=== modified file 'hooks/config-changed'
--- hooks/config-changed 2012-12-18 13:23:42 +0000
+++ hooks/config-changed 2012-12-19 18:05:33 +0000
@@ -4,8 +4,6 @@
# Copyright 2012 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
-import os.path
-from shutil import rmtree
from subprocess import CalledProcessError
import sys
@@ -24,11 +22,13 @@
from utils import (
AGENT,
- build,
config_json,
- fetch,
+ fetch_api,
+ fetch_gui,
GUI,
IMPROV,
+ setup_api,
+ setup_gui,
start_agent,
start_gui,
start_improv,
@@ -39,33 +39,31 @@
# Handle all configuration file changes.
log('Updating configuration.')
+ added_or_changed = diff.added_or_changed
+ juju_api_port = config.get('juju-api-port')
staging = config.get('staging')
- staging_environment = config.get('staging-environment')
- juju_api_port = config.get('juju-api-port')
- added_or_changed = diff.added_or_changed
-
- # Fetch new branches?
- gui_branch = None
- api_branch = None
- if 'juju-gui-branch' in added_or_changed:
- if os.path.exists('juju-gui'):
- rmtree('juju-gui')
- gui_branch = config['juju-gui-branch']
+
+ juju_gui_source_changed = False
+ juju_api_branch_changed = False
+
+ # Fetch new sources?
+ # Restarting of the gui and api services is handled below.
+ if 'juju-gui-source' in added_or_changed:
+ juju_gui_source_changed = True
+ release_tarball = fetch_gui(
+ config['juju-gui-source'], config['command-log-file'])
+ setup_gui(release_tarball)
if 'juju-api-branch' in added_or_changed:
- if os.path.exists('juju'):
- rmtree('juju')
- api_branch = config['juju-api-branch']
- if gui_branch or api_branch:
- fetch(gui_branch, api_branch)
- build(config['command-log-file'])
- # Restarting of the gui and api services is handled below.
+ juju_api_branch_changed = True
+ fetch_api(config['juju-api-branch'])
+ setup_api()
# Handle changes to the improv server configuration.
if staging:
staging_properties = set(
['staging', 'staging-environment', 'juju-api-port'])
staging_changed = added_or_changed & staging_properties
- if staging_changed or (api_branch is not None):
+ if staging_changed or juju_api_branch_changed:
if 'staging' in added_or_changed:
# 'staging' went from False to True, so the agent server is
# running and must be stopped.
@@ -76,14 +74,13 @@
current_api = IMPROV
log('Stopping %s.' % current_api)
service_control(current_api, STOP)
-
# Now the improv server can be cleanly started.
- log('Starting or restarting improv')
- start_improv(juju_api_port, staging_environment)
+ log('Starting or restarting staging.')
+ start_improv(juju_api_port, config.get('staging-environment'))
else:
agent_properties = set(['juju-api-port', 'staging'])
agent_changed = added_or_changed & agent_properties
- if agent_changed or (api_branch is not None):
+ if agent_changed or juju_api_branch_changed:
if 'staging' in added_or_changed:
# If 'staging' transitions to False we need to stop the backend
# and start the agent.
@@ -93,15 +90,15 @@
# updated -- bounce it.
current_api = AGENT
service_control(current_api, STOP)
+ log('Starting or restarting Juju API agent.')
start_agent(juju_api_port)
# Handle changes to the juju-gui configuration.
gui_properties = set(
['juju-gui-console-enabled', 'juju-api-port', 'staging'])
gui_changed = added_or_changed & gui_properties
- if gui_changed or (gui_branch is not None):
+ if gui_changed or juju_gui_source_changed:
with su('root'):
- service_control('nginx', STOP)
service_control(GUI, STOP)
console_enabled = config.get('juju-gui-console-enabled')
start_gui(juju_api_port, console_enabled, staging)
=== modified file 'hooks/install'
--- hooks/install 2012-12-18 13:23:42 +0000
+++ hooks/install 2012-12-19 18:05:33 +0000
@@ -4,7 +4,7 @@
from subprocess import (
CalledProcessError,
check_call,
- )
+)
# python-shelltoolbox is installed as a dependency of python-charmhelpers.
check_call(['apt-get', 'install', '-y', 'python-charmhelpers'])
@@ -24,15 +24,19 @@
)
from utils import (
- build,
cmd_log,
config_json,
- fetch,
- )
+ fetch_api,
+ fetch_gui,
+ setup_api,
+ setup_gui,
+)
DEB_DEPENDENCIES = (
- 'bzr', 'imagemagick', 'make', 'nginx', 'nodejs', 'npm', 'zookeeper')
+ 'bzr', 'imagemagick', 'make', 'nginx', 'nodejs', 'npm',
+ 'python-launchpadlib', 'zookeeper',
+)
def get_dependencies():
@@ -44,8 +48,11 @@
def main():
config = get_config()
get_dependencies()
- fetch(config['juju-gui-branch'], config['juju-api-branch'])
- build(config['command-log-file'])
+ release_tarball = fetch_gui(
+ config['juju-gui-source'], config['command-log-file'])
+ setup_gui(release_tarball)
+ fetch_api(config['juju-api-branch'])
+ setup_api()
config_json.set(config)
=== modified file 'hooks/stop'
--- hooks/stop 2012-12-18 13:23:42 +0000
+++ hooks/stop 2012-12-19 18:05:33 +0000
@@ -2,6 +2,7 @@
#-*- python -*-
from charmhelpers import (
+ get_config,
log_entry,
log_exit,
)
@@ -9,9 +10,14 @@
from utils import stop
+def main():
+ config = get_config()
+ stop(config.get('staging'))
+
+
if __name__ == '__main__':
log_entry()
try:
- stop()
+ main()
finally:
log_exit()
=== modified file 'hooks/utils.py'
--- hooks/utils.py 2012-12-18 13:23:42 +0000
+++ hooks/utils.py 2012-12-19 18:05:33 +0000
@@ -2,31 +2,42 @@
__all__ = [
'AGENT',
- 'build',
+ 'bzr_checkout',
'cmd_log',
+ 'CURRENT_DIR',
+ 'fetch_api',
+ 'fetch_gui',
+ 'first_path_in_dir',
+ 'get_release_file_url',
'get_zookeeper_address',
'GUI',
'IMPROV',
+ 'JUJU_DIR',
+ 'JUJU_GUI_DIR',
+ 'parse_source',
'render_to_file',
+ 'setup_api',
+ 'setup_gui',
'start_agent',
'start_gui',
'start_improv',
'stop',
- ]
+]
import json
import os
import logging
import tempfile
+from launchpadlib.launchpad import Launchpad
from shelltoolbox import (
- cd,
command,
+ environ,
run,
search_file,
Serializer,
su,
- )
+)
from charmhelpers import (
get_config,
log,
@@ -46,6 +57,50 @@
# Store the configuration from on invocation to the next.
config_json = Serializer('/tmp/config.json')
+# Bazaar checkout command.
+bzr_checkout = command('bzr', 'co', '--lightweight')
+
+
+def first_path_in_dir(directory):
+ """Return the full path of the first file/dir in *directory*."""
+ return os.path.join(directory, os.listdir(directory)[0])
+
+
+def _get_by_attr(collection, attr, value):
+ """Return the first item in collection having attr == value.
+
+ Return None if the item is not found.
+ """
+ for item in collection:
+ if getattr(item, attr) == value:
+ return item
+
+
+def get_release_file_url(project, series_name, release_version):
+ """Return the URL of the release file hosted in Launchpad.
+
+ The returned URL points to a release file for the given project, series
+ name and release version.
+ The argument *project* is a project object as returned by launchpadlib.
+ The arguments *series_name* and *release_version* are strings. If
+ *release_version* is None, the URL of the latest release will be returned.
+ """
+ series = _get_by_attr(project.series, 'name', series_name)
+ if series is None:
+ raise ValueError('%r: series not found' % series_name)
+ releases = list(series.releases)
+ if not releases:
+ raise ValueError('%r: series does not contain releases' % series_name)
+ if release_version is None:
+ release = releases[0]
+ else:
+ release = _get_by_attr(releases, 'version', release_version)
+ if not release:
+ raise ValueError('%r: release not found' % release_version)
+ files = [i for i in release.files if str(i).endswith('.tgz')]
+ if not files:
+ raise ValueError('%r: file not found' % release_version)
+ return files[0].file_link
def get_zookeeper_address(agent_file_path):
@@ -60,6 +115,26 @@
return line.split('=')[1].strip('"')
+def parse_source(source):
+ """Parse the ``juju-gui-source`` option.
+
+ Return a tuple of two elements representing info on how to deploy Juju GUI.
+ Examples:
+ - ('stable', None): latest stable release;
+ - ('stable', '0.1.0'): stable release v0.1.0;
+ - ('trunk', None): latest trunk release;
+ - ('trunk', '0.1.0+build.1'): trunk release v0.1.0 bzr revision 1;
+ - ('branch', 'lp:juju-gui'): release is made from a branch.
+ """
+ if source in ('stable', 'trunk'):
+ return source, None
+ if source.startswith('lp:') or source.startswith('http://'):
+ return 'branch', source
+ if 'build' in source:
+ return 'trunk', source
+ return 'stable', source
+
+
def render_to_file(template, context, destination):
"""Render the given *template* into *destination* using *context*.
@@ -110,9 +185,7 @@
'port': juju_api_port,
'staging_env': staging_env,
}
- render_to_file(
- 'juju-api-improv.conf.template', context,
- config_path)
+ render_to_file('juju-api-improv.conf.template', context, config_path)
log('Starting the staging backend.')
with su('root'):
service_control(IMPROV, START)
@@ -130,9 +203,7 @@
'port': juju_api_port,
'zookeeper': zookeeper,
}
- render_to_file(
- 'juju-api-agent.conf.template', context,
- config_path)
+ render_to_file('juju-api-agent.conf.template', context, config_path)
log('Starting API agent.')
with su('root'):
service_control(AGENT, START)
@@ -148,8 +219,7 @@
build_dir = JUJU_GUI_DIR + '/build-'
build_dir += 'debug' if staging else 'prod'
log('Setting up Juju GUI start up script.')
- render_to_file(
- 'juju-gui.conf.template', {}, config_path)
+ render_to_file('juju-gui.conf.template', {}, config_path)
log('Generating the Juju GUI configuration file.')
context = {
'address': unit_get('public-address'),
@@ -159,29 +229,24 @@
if config_js_path is None:
config_js_path = os.path.join(
build_dir, 'juju-ui', 'assets', 'config.js')
- render_to_file(
- 'config.js.template', context,
- config_js_path)
+ render_to_file('config.js.template', context, config_js_path)
log('Generating the nginx site configuration file.')
context = {
'server_root': build_dir
}
- render_to_file(
- 'nginx.conf.template', context, nginx_path)
+ render_to_file('nginx.conf.template', context, nginx_path)
log('Starting Juju GUI.')
with su('root'):
- # Stop nginx so it will restart cleanly with the gui.
- service_control('nginx', STOP)
+ # Start the Juju GUI.
service_control(GUI, START)
-def stop():
+def stop(staging):
"""Stop the Juju API agent."""
- config = get_config()
with su('root'):
log('Stopping Juju GUI.')
service_control(GUI, STOP)
- if config.get('staging'):
+ if staging:
log('Stopping the staging backend.')
service_control(IMPROV, STOP)
else:
@@ -189,26 +254,60 @@
service_control(AGENT, STOP)
-def fetch(juju_gui_branch, juju_api_branch):
- """Install required dependencies and retrieve Juju/Juju GUI branches."""
- log('Retrieving source checkouts.')
- bzr_checkout = command('bzr', 'co', '--lightweight')
- if juju_gui_branch is not None:
- cmd_log(run('rm', '-rf', 'juju-gui'))
- cmd_log(bzr_checkout(juju_gui_branch, 'juju-gui'))
- if juju_api_branch is not None:
- cmd_log(run('rm', '-rf', 'juju'))
- cmd_log(bzr_checkout(juju_api_branch, 'juju'))
-
-
-def build(logpath):
- """Set up Juju GUI and nginx."""
- log('Building Juju GUI.')
- with cd('juju-gui'):
+def fetch_gui(juju_gui_source, logpath):
+ """Retrieve the Juju GUI release/branch."""
+ # Retrieve a Juju GUI release.
+ origin, version_or_branch = parse_source(juju_gui_source)
+ if origin == 'branch':
+ # Create a release starting from a branch.
+ juju_gui_source_dir = os.path.join(CURRENT_DIR, 'juju-gui-source')
+ log('Retrieving Juju GUI source checkouts.')
+ cmd_log(run('rm', '-rf', juju_gui_source_dir))
+ cmd_log(bzr_checkout(version_or_branch, juju_gui_source_dir))
+ log('Preparing a Juju GUI release.')
logdir = os.path.dirname(logpath)
- fd, name = tempfile.mkstemp(prefix='make-', dir=logdir)
- log('Output from "make" sent to', name)
- run('make', stdout=fd, stderr=fd)
+ fd, name = tempfile.mkstemp(prefix='make-distfile-', dir=logdir)
+ log('Output from "make distfile" sent to', name)
+ with environ(NO_BZR='1'):
+ run('make', '-C', juju_gui_source_dir, 'distfile',
+ stdout=fd, stderr=fd)
+ release_tarball = first_path_in_dir(
+ os.path.join(juju_gui_source_dir, 'releases'))
+ else:
+ # Retrieve a release from Launchpad.
+ log('Retrieving Juju GUI release.')
+ launchpad = Launchpad.login_anonymously('Juju GUI charm', 'production')
+ project = launchpad.projects['juju-gui']
+ file_url = get_release_file_url(project, origin, version_or_branch)
+ log('Downloading release file from %s.' % file_url)
+ release_tarball = os.path.join(CURRENT_DIR, 'release.tgz')
+ cmd_log(run('curl', '-L', '-o', release_tarball, file_url))
+ return release_tarball
+
+
+def fetch_api(juju_api_branch):
+ """Retrieve the Juju branch."""
+ # Retrieve Juju API source checkout.
+ log('Retrieving Juju API source checkout.')
+ cmd_log(run('rm', '-rf', JUJU_DIR))
+ cmd_log(bzr_checkout(juju_api_branch, JUJU_DIR))
+
+
+def setup_gui(release_tarball):
+ """Set up Juju GUI."""
+ # Uncompress the release tarball.
+ log('Installing Juju GUI.')
+ release_dir = os.path.join(CURRENT_DIR, 'release')
+ cmd_log(run('rm', '-rf', release_dir))
+ os.mkdir(release_dir)
+ uncompress = command('tar', '-x', '-z', '-C', release_dir, '-f')
+ cmd_log(uncompress(release_tarball))
+ # Link the Juju GUI dir to the contents of the release tarball.
+ cmd_log(run('ln', '-sf', first_path_in_dir(release_dir), JUJU_GUI_DIR))
+
+
+def setup_api():
+ """Set up nginx."""
log('Setting up nginx.')
nginx_default_site = '/etc/nginx/sites-enabled/default'
juju_gui_site = '/etc/nginx/sites-available/juju-gui'
=== modified file 'tests/deploy.test'
--- tests/deploy.test 2012-12-12 17:29:39 +0000
+++ tests/deploy.test 2012-12-19 18:05:33 +0000
@@ -104,7 +104,6 @@
def test_staging(self):
# Ensure the Juju GUI and improv services are correctly set up.
- self.services = ('juju-api-improv', 'juju-gui')
config = {self.charm: {'staging': 'True'}}
config_file = make_charm_config_file(config)
hostname = self.deploy(config_path=config_file.name)
@@ -113,6 +112,16 @@
self.stop_services, hostname, ['juju-api-improv', 'juju-gui'])
self.check_services(hostname)
+ def test_branch_source(self):
+ # Ensure the Juju GUI is correctly deployed from a Bazaar branch.
+ config = {self.charm: {'juju-gui-source': 'lp:juju-gui'}}
+ config_file = make_charm_config_file(config)
+ hostname = self.deploy(config_path=config_file.name)
+ # XXX 2012-11-29 frankban bug=872264: see *stop_services* above.
+ self.addCleanup(
+ self.stop_services, hostname, ['juju-api-agent', 'juju-gui'])
+ self.check_services(hostname)
+
class DeployToTest(DeployTestMixin, unittest.TestCase):
=== modified file 'tests/test_utils.py'
--- tests/test_utils.py 2012-12-18 13:23:42 +0000
+++ tests/test_utils.py 2012-12-19 18:05:33 +0000
@@ -2,14 +2,19 @@
from contextlib import contextmanager
import os
+import shutil
+from simplejson import dumps
import tempfile
import unittest
+
import charmhelpers
-from simplejson import dumps
-
from utils import (
+ _get_by_attr,
cmd_log,
+ first_path_in_dir,
+ get_release_file_url,
get_zookeeper_address,
+ parse_source,
render_to_file,
start_agent,
start_gui,
@@ -20,6 +25,205 @@
import utils
+class AttrDict(dict):
+ """A dict with the ability to access keys as attributes."""
+
+ def __getattr__(self, attr):
+ if attr in self:
+ return self[attr]
+ raise AttributeError
+
+
+class AttrDictTest(unittest.TestCase):
+
+ def test_key_as_attribute(self):
+ # Ensure attributes can be used to retrieve dict values.
+ attr_dict = AttrDict(myattr='myvalue')
+ self.assertEqual('myvalue', attr_dict.myattr)
+
+ def test_attribute_not_found(self):
+ # An AttributeError is raised if the dict does not contain an attribute
+ # corresponding to an existent key.
+ with self.assertRaises(AttributeError):
+ AttrDict().myattr
+
+
+class FirstPathInDirTest(unittest.TestCase):
+
+ def setUp(self):
+ self.directory = tempfile.mkdtemp()
+ self.addCleanup(shutil.rmtree, self.directory)
+ self.path = os.path.join(self.directory, 'file_or_dir')
+
+ def test_file_path(self):
+ # Ensure the full path of a file is correctly returned.
+ open(self.path, 'w').close()
+ self.assertEqual(self.path, first_path_in_dir(self.directory))
+
+ def test_directory_path(self):
+ # Ensure the full path of a directory is correctly returned.
+ os.mkdir(self.path)
+ self.assertEqual(self.path, first_path_in_dir(self.directory))
+
+ def test_empty_directory(self):
+ # An IndexError is raised if the directory is empty.
+ self.assertRaises(IndexError, first_path_in_dir, self.directory)
+
+
+def make_collection(attr, values):
+ """Create a collection of objects having an attribute named *attr*.
+
+ The value of the *attr* attribute, for each instance, is taken from
+ the *values* sequence.
+ """
+ return [AttrDict({attr: value}) for value in values]
+
+
+class MakeCollectionTest(unittest.TestCase):
+
+ def test_factory(self):
+ # Ensure the factory return the expected object instances.
+ instances = make_collection('myattr', range(5))
+ self.assertEqual(5, len(instances))
+ for num, instance in enumerate(instances):
+ self.assertEqual(num, instance.myattr)
+
+
+class GetByAttrTest(unittest.TestCase):
+
+ attr = 'myattr'
+ collection = make_collection(attr, range(5))
+
+ def test_item_found(self):
+ # Ensure an object instance is correctly returned if found in
+ # the collection.
+ item = _get_by_attr(self.collection, self.attr, 3)
+ self.assertEqual(3, item.myattr)
+
+ def test_value_not_found(self):
+ # None is returned if the collection does not contain the requested
+ # item.
+ item = _get_by_attr(self.collection, self.attr, '__does_not_exist__')
+ self.assertIsNone(item)
+
+ def test_attr_not_found(self):
+ # An AttributeError is raised if items in collection does not have the
+ # required attribute.
+ with self.assertRaises(AttributeError):
+ _get_by_attr(self.collection, 'another_attr', 0)
+
+
+class FileStub(object):
+ """Simulates a Launchpad hosted file returned by launchpadlib."""
+
+ def __init__(self, file_link):
+ self.file_link = file_link
+
+ def __str__(self):
+ return self.file_link
+
+
+class GetReleaseFileUrlTest(unittest.TestCase):
+
+ project = AttrDict(
+ series=(
+ AttrDict(
+ name='stable',
+ releases=(
+ AttrDict(
+ version='0.1.1',
+ files=(
+ FileStub('http://example.com/0.1.1.dmg'),
+ FileStub('http://example.com/0.1.1.tgz'),
+ ),
+ ),
+ AttrDict(
+ version='0.1.0',
+ files=(
+ FileStub('http://example.com/0.1.0.dmg'),
+ FileStub('http://example.com/0.1.0.tgz'),
+ ),
+ ),
+ ),
+ ),
+ AttrDict(
+ name='trunk',
+ releases=(
+ AttrDict(
+ version='0.1.1+build.1',
+ files=(
+ FileStub('http://example.com/0.1.1+build.1.dmg'),
+ FileStub('http://example.com/0.1.1+build.1.tgz'),
+ ),
+ ),
+ AttrDict(
+ version='0.1.0+build.1',
+ files=(
+ FileStub('http://example.com/0.1.0+build.1.dmg'),
+ FileStub('http://example.com/0.1.0+build.1.tgz'),
+ ),
+ ),
+ ),
+ ),
+ ),
+ )
+
+ def test_latest_stable_release(self):
+ # Ensure the correct URL is returned for the latest stable release.
+ url = get_release_file_url(self.project, 'stable', None)
+ self.assertEqual('http://example.com/0.1.1.tgz', url)
+
+ def test_latest_trunk_release(self):
+ # Ensure the correct URL is returned for the latest trunk release.
+ url = get_release_file_url(self.project, 'trunk', None)
+ self.assertEqual('http://example.com/0.1.1+build.1.tgz', url)
+
+ def test_specific_stable_release(self):
+ # Ensure the correct URL is returned for a specific version of the
+ # stable release.
+ url = get_release_file_url(self.project, 'stable', '0.1.0')
+ self.assertEqual('http://example.com/0.1.0.tgz', url)
+
+ def test_specific_trunk_release(self):
+ # Ensure the correct URL is returned for a specific version of the
+ # trunk release.
+ url = get_release_file_url(self.project, 'trunk', '0.1.0+build.1')
+ self.assertEqual('http://example.com/0.1.0+build.1.tgz', url)
+
+ def test_series_not_found(self):
+ # A ValueError is raised if the series cannot be found.
+ with self.assertRaises(ValueError) as cm:
+ get_release_file_url(self.project, 'unstable', None)
+ self.assertIn('series not found', str(cm.exception))
+
+ def test_no_releases(self):
+ # A ValueError is raised if the series does not contain releases.
+ project = AttrDict(series=[AttrDict(name='stable', releases=[])])
+ with self.assertRaises(ValueError) as cm:
+ get_release_file_url(project, 'stable', None)
+ self.assertIn('series does not contain releases', str(cm.exception))
+
+ def test_release_not_found(self):
+ # A ValueError is raised if the release cannot be found.
+ with self.assertRaises(ValueError) as cm:
+ get_release_file_url(self.project, 'stable', '2.0')
+ self.assertIn('release not found', str(cm.exception))
+
+ def test_file_not_found(self):
+ # A ValueError is raised if the hosted file cannot be found.
+ project = AttrDict(
+ series=[
+ AttrDict(
+ name='stable',
+ releases=[AttrDict(version='0.1.0', files=[])],
+ ),
+ ],
+ )
+ with self.assertRaises(ValueError) as cm:
+ get_release_file_url(project, 'stable', None)
+ self.assertIn('file not found', str(cm.exception))
+
+
class GetZookeeperAddressTest(unittest.TestCase):
def setUp(self):
@@ -36,6 +240,35 @@
self.assertEqual(self.zookeeper_address, address)
+class ParseSourceTest(unittest.TestCase):
+
+ def test_latest_stable_release(self):
+ # Ensure the latest stable release is correctly parsed.
+ expected = ('stable', None)
+ self.assertTupleEqual(expected, parse_source('stable'))
+
+ def test_latest_trunk_release(self):
+ # Ensure the latest trunk release is correctly parsed.
+ expected = ('trunk', None)
+ self.assertTupleEqual(expected, parse_source('trunk'))
+
+ def test_stable_release(self):
+ # Ensure a specific stable release is correctly parsed.
+ expected = ('stable', '0.1.0')
+ self.assertTupleEqual(expected, parse_source('0.1.0'))
+
+ def test_trunk_release(self):
+ # Ensure a specific trunk release is correctly parsed.
+ expected = ('trunk', '0.1.0+build.1')
+ self.assertTupleEqual(expected, parse_source('0.1.0+build.1'))
+
+ def test_bzr_branch(self):
+ # Ensure a Bazaar branch is correctly parsed.
+ sources = ('lp:example', 'http://bazaar.launchpad.net/example')
+ for source in sources:
+ self.assertTupleEqual(('branch', source), parse_source(source))
+
+
class RenderToFileTest(unittest.TestCase):
def setUp(self):
@@ -155,22 +388,18 @@
self.assertTrue('/usr/sbin/nginx' in conf)
nginx_conf = nginx_file.read()
self.assertTrue('juju-gui/build-debug' in nginx_conf)
- self.assertEqual(self.svc_ctl_call_count, 2)
- self.assertEqual(self.service_names, ['nginx', 'juju-gui'])
- self.assertEqual(self.actions, [charmhelpers.STOP, charmhelpers.START])
+ self.assertEqual(self.svc_ctl_call_count, 1)
+ self.assertEqual(self.service_names, ['juju-gui'])
+ self.assertEqual(self.actions, [charmhelpers.START])
def test_stop_staging(self):
- mock_config = {'staging': True}
- charmhelpers.command = lambda *args: lambda: dumps(mock_config)
- stop()
+ stop(True)
self.assertEqual(self.svc_ctl_call_count, 2)
self.assertEqual(self.service_names, ['juju-gui', 'juju-api-improv'])
self.assertEqual(self.actions, [charmhelpers.STOP, charmhelpers.STOP])
def test_stop_production(self):
- mock_config = {'staging': False}
- charmhelpers.command = lambda *args: lambda: dumps(mock_config)
- stop()
+ stop(False)
self.assertEqual(self.svc_ctl_call_count, 2)
self.assertEqual(self.service_names, ['juju-gui', 'juju-api-agent'])
self.assertEqual(self.actions, [charmhelpers.STOP, charmhelpers.STOP])
Follow ups
-
Re: Updated the charm to serve Juju GUI releases. (issue 6977043)
From: Francesco Banconi, 2012-12-20
-
lp:~frankban/charms/precise/juju-gui/bug-1088618-serve-releases into lp:~juju-gui/charms/precise/juju-gui/trunk
From: noreply, 2012-12-20
-
Re: Updated the charm to serve Juju GUI releases. (issue 6977043)
From: Francesco Banconi, 2012-12-20
-
Re: Updated the charm to serve Juju GUI releases. (issue 6977043)
From: Francesco Banconi, 2012-12-20
-
Re: Updated the charm to serve Juju GUI releases. (issue 6977043)
From: Nicola Larosa, 2012-12-20
-
Re: Updated the charm to serve Juju GUI releases. (issue 6977043)
From: Francesco Banconi, 2012-12-20
-
Re: Updated the charm to serve Juju GUI releases. (issue 6977043)
From: Francesco Banconi, 2012-12-20
-
Re: Updated the charm to serve Juju GUI releases. (issue 6977043)
From: Gary Poster, 2012-12-20
-
Re: Updated the charm to serve Juju GUI releases. (issue 6977043)
From: Nicola Larosa, 2012-12-20
-
Re: Updated the charm to serve Juju GUI releases. (issue 6977043)
From: Francesco Banconi, 2012-12-20
-
Re: Updated the charm to serve Juju GUI releases. (issue 6977043)
From: Francesco Banconi, 2012-12-20
-
Re: Updated the charm to serve Juju GUI releases. (issue 6977043)
From: Gary Poster, 2012-12-20
-
Re: Updated the charm to serve Juju GUI releases. (issue 6977043)
From: Gary Poster, 2012-12-20
-
Re: Updated the charm to serve Juju GUI releases. (issue 6977043)
From: Gary Poster, 2012-12-19
-
lp:~frankban/charms/precise/juju-gui/bug-1088618-serve-releases into lp:~juju-gui/charms/precise/juju-gui/trunk
From: Francesco Banconi, 2012-12-19
-
Updated the charm to serve Juju GUI releases. (issue 6977043)
From: Francesco Banconi, 2012-12-19