yellow team mailing list archive
-
yellow team
-
Mailing list archive
-
Message #00480
[Merge] lp:~bac/charms/oneiric/buildbot-master/history-s3 into lp:~yellow/charms/oneiric/buildbot-master/trunk
Brad Crittenden has proposed merging lp:~bac/charms/oneiric/buildbot-master/history-s3 into lp:~yellow/charms/oneiric/buildbot-master/trunk.
Requested reviews:
Launchpad Yellow Squad (yellow): code
For more details, see:
https://code.launchpad.net/~bac/charms/oneiric/buildbot-master/history-s3/+merge/94262
Add ability to store buildbot data files to S3 and retrieve them the next time the master is started.
The configuration settings for access-key and secret-key need to be passed, like so:
juju set buildbot-master access-key='<your AWS key>' secret-key='<your AWS secret key>'
Unfortunately the 'stop' hook is not being called (see bug 872264) so automatically saving the history when bringing down the juju service does not work. As a work-around a new config value is provided which causes the files to get pushed. Setting that config variable to a different value will cause it to save again, e.g.
juju set buildbot-master save-history-now='`date`'
--
https://code.launchpad.net/~bac/charms/oneiric/buildbot-master/history-s3/+merge/94262
Your team Launchpad Yellow Squad is requested to review the proposed merge of lp:~bac/charms/oneiric/buildbot-master/history-s3 into lp:~yellow/charms/oneiric/buildbot-master/trunk.
=== modified file 'config.yaml'
--- config.yaml 2012-02-10 21:19:58 +0000
+++ config.yaml 2012-02-22 19:51:18 +0000
@@ -52,3 +52,25 @@
install the newly specified packages while leaving the previous
ones installed.
type: string
+ access-key:
+ description: |
+ Access key for EC2.
+ type: string
+ secret-key:
+ description: |
+ Secret key for EC2.
+ type: string
+ bucket-name:
+ description: |
+ The bucket used to store buildbot history. If not provided a
+ default based on the access-key will be used.
+ type: string
+ save-history-now:
+ description: |
+ Configuration hack to fire off the saving of the buildbot master
+ history. Normally this would be done in the stop hook but due to
+ Bug 872264 that hook is not firing properly. The value of the
+ setting is not important but it must change between invocations
+ or the event will not be recogized. Monotonically increasing
+ integer values would be a good choice. Or a time string.
+ type: string
=== modified file 'examples/pyflakes.yaml'
--- examples/pyflakes.yaml 2012-02-10 21:19:58 +0000
+++ examples/pyflakes.yaml 2012-02-22 19:51:18 +0000
@@ -1,5 +1,5 @@
buildbot-master:
- extra-packages: git
+ extra-packages: git python-sqlalchemy python-migrate
installdir: /tmp/buildbot
config-file: |
# -*- python -*-
=== modified file 'hooks/config-changed'
--- hooks/config-changed 2012-02-14 15:51:49 +0000
+++ hooks/config-changed 2012-02-22 19:51:18 +0000
@@ -3,10 +3,12 @@
# Copyright 2012 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
+import base64
import json
import os
import os.path
import shutil
+import subprocess
import sys
from helpers import (
@@ -27,6 +29,9 @@
buildbot_reconfig,
config_json,
generate_string,
+ get_bucket,
+ get_key,
+ put_history,
slave_json,
)
@@ -36,7 +41,6 @@
]
SUPPORTED_TRANSPORTS = ['bzr']
-
bzr = command('bzr')
@@ -58,7 +62,6 @@
def handle_config_changes(config, diff):
log('Updating buildbot configuration.')
log('Configuration changes seen:')
- log(str(diff))
buildbot_pkg = config.get('buildbot-pkg')
extra_repo = config.get('extra-repository')
@@ -91,7 +94,6 @@
# Write the buildbot config to disk (fetching it if necessary).
added_or_changed = diff.added_or_changed
- log("CONFIG FILE: {}".format(config_file))
log("ADDED OR CHANGED: {}".format(added_or_changed))
if config_file and 'config-file' in added_or_changed:
buildbot_create(buildbot_dir)
@@ -108,7 +110,7 @@
# gpg-agent needs to send the key over and the bzr launchpad-login
# needs to be set.
with su('buildbot'):
- lp_id = config.get('config-usr')
+ lp_id = config.get('config-user')
if lp_id:
bzr('launchpad-login', lp_id)
private_key = config.get('config-private-key')
@@ -134,6 +136,44 @@
return restart_required
+def fetch_history(config, diff):
+ """Fetch the buildbot history from an external store."""
+ # Currently only S3 is supported.
+ restart_required = False
+
+ log("fetching history")
+ if 'secret-key' not in diff.added_or_changed:
+ log("skipping fetch of history")
+ return restart_required
+
+ bucket_name = config.get('bucket-name')
+ bucket = get_bucket(config, bucket_name)
+
+ if bucket:
+ key = get_key(bucket)
+ if key.exists():
+ target = '/tmp/history-fetched.tgz'
+ key.get_contents_to_filename(target)
+ cwd = os.getcwd()
+ os.chdir(config['installdir'])
+ with su('buildbot'):
+ try:
+ run('tar', 'xzf', target)
+ except subprocess.CalledProcessError as e:
+ print e
+ print e.output
+ raise
+ os.chdir(cwd)
+ os.unlink(target)
+ log("History fetched from S3.")
+ restart_required = True
+ else:
+ log("Key does not exist: " + key.key)
+ else:
+ log("Bucket not found: " + bucket_name)
+ return restart_required
+
+
def initialize_buildbot(config):
# Initialize the buildbot directory and (re)start buildbot.
log("Initializing buildbot")
@@ -153,12 +193,21 @@
if not os.path.exists(placeholder_path):
with open(placeholder_path, 'w') as f:
json.dump(generate_string("temporary-placeholder-"), f)
- buildbot_reconfig()
+ try:
+ buildbot_reconfig()
+ except subprocess.CalledProcessError as e:
+ print e
+ print e.output
+ raise
+
+
+def conditionally_save_history(config, diff):
+ if 'save-history-now' in diff.added_or_changed:
+ put_history(config)
def main():
config = get_config()
- log(str(config))
if not check_config(config):
log("Configuration not valid.")
sys.exit(1)
@@ -178,6 +227,7 @@
os.makedirs(buildbot_dir)
restart_required |= configure_buildbot(config, diff)
+ restart_required |= fetch_history(config, diff)
master_cfg_path = os.path.join(buildbot_dir, 'master.cfg')
if restart_required and os.path.exists(master_cfg_path):
@@ -185,6 +235,8 @@
else:
log("Configuration changed but didn't require restarting.")
+ conditionally_save_history(config, diff)
+
config_json.set(config)
=== modified file 'hooks/install'
--- hooks/install 2012-02-14 16:54:21 +0000
+++ hooks/install 2012-02-22 19:51:18 +0000
@@ -22,7 +22,7 @@
def cleanup(buildbot_dir):
- apt_get_install('bzr')
+ apt_get_install('bzr', 'python-boto')
# Since we may be installing into a pre-existing service, ensure the
# buildbot directory is removed.
if os.path.exists(buildbot_dir):
=== modified file 'hooks/local.py'
--- hooks/local.py 2012-02-10 01:05:09 +0000
+++ hooks/local.py 2012-02-22 19:51:18 +0000
@@ -11,11 +11,15 @@
'config_json',
'create_slave',
'generate_string',
+ 'get_bucket',
+ 'get_key',
'HTTP_PORT_PROTOCOL',
+ 'put_history',
'slave_json',
]
import os
+import re
import subprocess
import uuid
@@ -157,3 +161,84 @@
slave_json = Serializer('/tmp/slave_info.json')
config_json = Serializer('/tmp/config.json')
+
+
+def get_bucket(config, bucket_name=None):
+ """Return an S3 bucket or None."""
+ # Late import to ensure python-boto package has been installed.
+ import boto
+ access_key = config.get('access-key')
+ secret_key = config.get('secret-key')
+ bucket = None
+ if access_key and secret_key:
+ if bucket_name is None:
+ bucket_name = str(access_key + '-buildbot-history').lower()
+ conn = boto.connect_s3(access_key, secret_key)
+ bucket = conn.create_bucket(bucket_name)
+ log("Using bucket: " + bucket.name)
+ return bucket
+
+
+def get_key(bucket):
+ """Return an S3 key for the bucket."""
+ # Late import to ensure python-boto package has been installed.
+ from boto.s3.key import Key
+ key = Key(bucket)
+ key.key = os.environ['JUJU_UNIT_NAME']
+ log("Using key: " + key.key)
+ return key
+
+
+VERSION_TO_STORE = {
+ '0.7': "*/builder",
+ '0.8': "state.sqlite",
+ }
+
+
+def get_buildbot_version():
+ """Get the major version (x.y) of buildbot and return as a string.
+
+ Return None if the output from buildbot cannot be parsed.
+ """
+ version = None
+ output = run('buildbot', '--version')
+ match = re.search('Buildbot version: (\d+\.\d+)(\.\d+)*\n', output)
+ if match and len(match.groups()) > 0:
+ version = match.group(1)
+ return version
+
+
+def put_history(config):
+ """Put the buildbot history to an external store, if set up."""
+ log("put_history called")
+ bucket_name = config.get('bucket-name')
+ bucket = get_bucket(config, bucket_name)
+ success = False
+ if bucket:
+ key = get_key(bucket)
+ target = '/tmp/history-put.tgz'
+ version = get_buildbot_version()
+ store_pattern = VERSION_TO_STORE.get(version)
+ assert store_pattern is not None, (
+ "Buildbot version not supported: {}".format(version))
+ cwd = os.getcwd()
+ os.chdir(config['installdir'])
+ with su('buildbot'):
+ try:
+ run('tar', 'czf', target, store_pattern)
+ key.set_contents_from_filename(target)
+ success = True
+ # If would be natural to just log the success here, but we are
+ # su-ed to the buildbot user and that causes permission
+ # problems.
+ # log("History stored to S3.")
+ except subprocess.CalledProcessError as e:
+ print e
+ print e.output
+ raise
+ os.unlink(target)
+ os.chdir(cwd)
+ else:
+ log("Bucket not found: " + bucket_name)
+ if success:
+ log("History stored to S3.")
=== modified file 'hooks/start'
--- hooks/start 2012-02-08 14:26:58 +0000
+++ hooks/start 2012-02-22 19:51:18 +0000
@@ -4,7 +4,6 @@
# GNU Affero General Public License version 3 (see the file LICENSE).
from helpers import (
- log,
log_entry,
log_exit,
run,
=== modified file 'hooks/stop'
--- hooks/stop 2012-02-08 14:26:58 +0000
+++ hooks/stop 2012-02-22 19:51:18 +0000
@@ -10,14 +10,17 @@
log_exit,
run,
)
-from local import HTTP_PORT_PROTOCOL
+from local import (
+ HTTP_PORT_PROTOCOL,
+ put_history,
+ )
def main():
config = get_config()
log('Stopping buildbot in {}'.format(config['installdir']))
+ put_history(config)
run('close-port', HTTP_PORT_PROTOCOL)
-
log('Finished stopping buildbot')
=== modified file 'hooks/tests.py'
--- hooks/tests.py 2012-02-10 19:43:46 +0000
+++ hooks/tests.py 2012-02-22 19:51:18 +0000
@@ -10,7 +10,6 @@
unit_info,
)
-from subprocess import CalledProcessError
class TestRun(unittest.TestCase):
@@ -24,7 +23,7 @@
# produces a string.
self.assertIn('Usage:', run('/bin/ls', '--help'))
- def testSimpleCommand(self):
+ def testCalledProcessErrorRaised(self):
# If an error occurs a CalledProcessError is raised with the return
# code, command executed, and the output of the command.
with self.assertRaises(CalledProcessError) as info:
Follow ups