← Back to team overview

yellow team mailing list archive

[Merge] lp:~frankban/charms/oneiric/buildbot-slave/upgrade-charm into lp:~yellow/charms/oneiric/buildbot-slave/trunk

 

Francesco Banconi has proposed merging lp:~frankban/charms/oneiric/buildbot-slave/upgrade-charm into lp:~yellow/charms/oneiric/buildbot-slave/trunk.

Requested reviews:
  Launchpad Yellow Squad (yellow)

For more details, see:
https://code.launchpad.net/~frankban/charms/oneiric/buildbot-slave/upgrade-charm/+merge/95534

== Changes ==

- Added upgrade-charm symlink
- Updated helpers (with some refactoring to functions running "juju status")

The file helper.py is in sync with the one present in 
lp:~frankban/charms/oneiric/buildbot-master/upgrade-charm

Currently the upgrade-charm test does not work due to a bug of juju:
see https://bugs.launchpad.net/juju/+bug/941873
-- 
https://code.launchpad.net/~frankban/charms/oneiric/buildbot-slave/upgrade-charm/+merge/95534
Your team Launchpad Yellow Squad is requested to review the proposed merge of lp:~frankban/charms/oneiric/buildbot-slave/upgrade-charm into lp:~yellow/charms/oneiric/buildbot-slave/trunk.
=== modified file 'hooks/helpers.py'
--- hooks/helpers.py	2012-03-01 13:55:37 +0000
+++ hooks/helpers.py	2012-03-02 11:26:19 +0000
@@ -6,15 +6,21 @@
 __metaclass__ = type
 __all__ = [
     'get_config',
+    'juju_status',
     'log',
     'log_entry',
     'log_exit',
+    'make_charm_config_file',
     'relation_get',
     'relation_set',
     'unit_info',
+    'wait_for_machine',
+    'wait_for_page_contents',
+    'wait_for_relation',
+    'wait_for_unit',
     ]
 
-from collections import namedtuple
+from contextlib import contextmanager
 import json
 import operator
 from shelltoolbox import (
@@ -22,14 +28,13 @@
     run,
     script_name,
     )
+import os
 import tempfile
 import time
 import urllib2
 import yaml
 
 
-Env = namedtuple('Env', 'uid gid home')
-
 log = command('juju-log')
 
 
@@ -67,10 +72,18 @@
     return charm_config_file
 
 
+def juju_status(key):
+    return yaml.safe_load(run('juju', 'status'))[key]
+
+
+def get_charm_revision(service_name):
+    service = juju_status('services')[service_name]
+    return int(service['charm'].split('-')[-1])
+
+
 def unit_info(service_name, item_name, data=None):
-    if data is None:
-        data = yaml.safe_load(run('juju', 'status'))
-    service = data['services'].get(service_name)
+    services = juju_status('services') if data is None else data['services']
+    service = services.get(service_name)
     if service is None:
         # XXX 2012-02-08 gmb:
         #     This allows us to cope with the race condition that we
@@ -83,8 +96,27 @@
     return item
 
 
-def get_machine_data():
-    return yaml.safe_load(run('juju', 'status'))['machines']
+@contextmanager
+def maintain_charm_revision(path=None):
+    if path is None:
+        path = os.path.join(os.path.dirname(__file__), '..', 'revision')
+    revision = open(path).read()
+    try:
+        yield revision
+    finally:
+        with open(path, 'w') as f:
+            f.write(revision)
+
+
+def upgrade_charm(service_name, timeout=120):
+    next_revision = get_charm_revision(service_name) + 1
+    start_time = time.time()
+    run('juju', 'upgrade-charm', service_name)
+    while get_charm_revision(service_name) != next_revision:
+        if time.time() - start_time >= timeout:
+            raise RuntimeError('timeout waiting for charm to be upgraded')
+        time.sleep(0.1)
+    return next_revision
 
 
 def wait_for_machine(num_machines=1, timeout=300):
@@ -98,7 +130,7 @@
     # to tell what environment we're working in (LXC vs EC2) is to check
     # the dns-name of the first machine. If it's localhost we're in LXC
     # and we can just return here.
-    if get_machine_data()[0]['dns-name'] == 'localhost':
+    if juju_status('machines')[0]['dns-name'] == 'localhost':
         return
     start_time = time.time()
     while True:
@@ -106,7 +138,7 @@
         # not a machine that we need to wait for. This will only work
         # for EC2 environments, which is why we return early above if
         # we're in LXC.
-        machine_data = get_machine_data()
+        machine_data = juju_status('machines')
         non_zookeeper_machines = [
             machine_data[key] for key in machine_data.keys()[1:]]
         if len(non_zookeeper_machines) >= num_machines:

=== added symlink 'hooks/upgrade-charm'
=== target is u'install'
=== modified file 'revision'
--- revision	2012-02-29 09:59:39 +0000
+++ revision	2012-03-02 11:26:19 +0000
@@ -1,2 +1,1 @@
 1
-

=== modified file 'tests/buildbot-slave.test'
--- tests/buildbot-slave.test	2012-02-14 15:11:47 +0000
+++ tests/buildbot-slave.test	2012-03-02 11:26:19 +0000
@@ -4,15 +4,18 @@
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 import os
+import time
 import unittest
 
 from helpers import (
     command,
     make_charm_config_file,
+    maintain_charm_revision,
     unit_info,
     wait_for_page_contents,
     wait_for_relation,
     wait_for_unit,
+    upgrade_charm,
     )
 from create_file import (
     CONTENT,
@@ -155,6 +158,17 @@
         self.assertEqual(installdir, ssh('cat {}'.format(PATH)))
         ssh('rm {}'.format(PATH))
 
+    def test_upgrade_charm(self):
+        # Ensure the charm can be upgraded without errors.
+        self.deploy(self.charm_name)
+        wait_for_unit(self.charm_name)
+        with maintain_charm_revision():
+            upgrade_charm(self.charm_name)
+        # Wait for the charm to upgrade using sleep, since there is no
+        # other confirmation at the moment but the state to remain 'started'.
+        time.sleep(10)
+        self.assertEqual('started', unit_info(self.charm_name, 'state'))
+
 
 if __name__ == '__main__':
     unittest.main()

=== modified symlink 'tests/test.cfg'
=== target was u'../../buildbot-master/tests/test.cfg'
--- tests/test.cfg	1970-01-01 00:00:00 +0000
+++ tests/test.cfg	2012-03-02 11:26:19 +0000
@@ -0,0 +1,118 @@
+# -*- python -*-
+# ex: set syntax=python:
+
+# This is a sample buildmaster config file. It must be installed as
+# 'master.cfg' in your buildmaster's base directory.
+
+# This is the dictionary that the buildmaster pays attention to. We also use
+# a shorter alias to save typing.
+c = BuildmasterConfig = {}
+
+####### BUILDSLAVES
+
+# The 'slaves' list defines the set of recognized buildslaves. Each element is
+# a BuildSlave object, specifying a username and password.  The same username and
+# password must be configured on the slave.
+from buildbot.buildslave import BuildSlave
+c['slaves'] = []
+
+# 'slavePortnum' defines the TCP port to listen on for connections from slaves.
+# This must match the value configured into the buildslaves (with their
+# --master option)
+c['slavePortnum'] = 9989
+
+####### CHANGESOURCES
+
+# the 'change_source' setting tells the buildmaster how it should find out
+# about source code changes.  Here we point to the buildbot clone of pyflakes.
+
+from buildbot.changes.gitpoller import GitPoller
+c['change_source'] = GitPoller(
+        'git://github.com/buildbot/pyflakes.git',
+        branch='master', pollinterval=1200)
+
+####### SCHEDULERS
+
+# Configure the Schedulers, which decide how to react to incoming changes.  In this
+# case, just kick off a 'runtests' build
+
+from buildbot.scheduler import Scheduler
+c['schedulers'] = []
+c['schedulers'].append(Scheduler(name="all", branch=None,
+                                 treeStableTimer=None,
+                                 builderNames=["runtests"]))
+
+####### BUILDERS
+
+# The 'builders' list defines the Builders, which tell Buildbot how to perform a build:
+# what steps, and which slaves can execute them.  Note that any particular build will
+# only take place on one slave.
+
+from buildbot.process.factory import BuildFactory
+from buildbot.steps.source import Git
+from buildbot.steps.shell import ShellCommand
+
+factory = BuildFactory()
+# check out the source
+factory.addStep(Git(repourl='git://github.com/buildbot/pyflakes.git', mode='copy'))
+# run the tests (note that this will require that 'trial' is installed)
+factory.addStep(ShellCommand(command=["trial", "pyflakes"]))
+
+from buildbot.config import BuilderConfig
+
+c['builders'] = [
+    BuilderConfig(name="runtests",
+      # Buildbot enforces that the slavenames list must not be empty. Our
+      # wrapper will remove the empty string and replace it with proper values.
+      slavenames=[''],
+      factory=factory),
+    ]
+
+####### STATUS TARGETS
+
+# 'status' is a list of Status Targets. The results of each build will be
+# pushed to these targets. buildbot/status/*.py has a variety to choose from,
+# including web pages, email senders, and IRC bots.
+
+c['status'] = []
+
+from buildbot.status import html
+from buildbot.status.web import auth, authz
+authz_cfg=authz.Authz(
+    # change any of these to True to enable; see the manual for more
+    # options
+    gracefulShutdown = False,
+    forceBuild = True, # use this to test your slave once it is set up
+    forceAllBuilds = False,
+    pingBuilder = False,
+    stopBuild = False,
+    stopAllBuilds = False,
+    cancelPendingBuild = False,
+)
+c['status'].append(html.WebStatus(http_port=8010, authz=authz_cfg))
+
+####### PROJECT IDENTITY
+
+# the 'projectName' string will be used to describe the project that this
+# buildbot is working on. For example, it is used as the title of the
+# waterfall HTML page. The 'projectURL' string will be used to provide a link
+# from buildbot HTML pages to your project's home page.
+
+c['projectName'] = "Pyflakes"
+c['projectURL'] = "http://divmod.org/trac/wiki/DivmodPyflakes";
+
+# the 'buildbotURL' string should point to the location where the buildbot's
+# internal web server (usually the html.WebStatus page) is visible. This
+# typically uses the port number set in the Waterfall 'status' entry, but
+# with an externally-visible host name which the buildbot cannot figure out
+# without some help.
+
+c['buildbotURL'] = "http://localhost:8010/";
+
+####### DB URL
+
+# This specifies what database buildbot uses to store change and scheduler
+# state.  You can leave this at its default for all but the largest
+# installations.
+c['db_url'] = "sqlite:///state.sqlite"
+


Follow ups