blob: 40e18bae208f2e8bf05a59c6374de01ae111ca2d [file] [log] [blame]
#!/usr/bin/env python
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Downgrades buildsteps in a directory of pickled buildbot builds.
Buildbot archives builds by pickling them and storing basic versioning
information. When the master is upgraded to a new persistenceVersion (see
third_party/buildbot_8_4p1/buildbot/status/buildstep.py), the master will
automatically update all pickled builds to the latest version. Buildbot
provides no way to downgrade back to a previous persistenceVersion, which is
what this script attempts to do.
It's important to note that this file only modifies the buildsteps inside of a
pickled build, and that builds themselves have their own persistence version
which is unrelated.
For a list of command-line options, call this script with '--help'.
"""
import cPickle
import optparse
import os
import re
import shutil
import sys
from common import master_cfg_utils
if True:
print 'This script is disabled. It should not be run by anyone.'
sys.exit(1)
def process_options():
"""Process options from the command line."""
prog_desc = 'Downgrade buildsteps in a directory of pickled builds.'
usage = '%prog [options] [master name or filename]'
parser = optparse.OptionParser(usage=(usage + '\n\n' + prog_desc))
parser.add_option('--list-masters', action='store_true',
help='list masters in search path')
parser.add_option('--master-dir', help='specify a master directory '
'instead of a mastername')
parser.add_option('-t', '--target-version', default=None, type='int',
help='downgrade to specified version')
parser.add_option('--commit', action='store_true',
help='save downgraded results on top of old files. leaving '
'out this option is equivalent to a dry-run')
parser.add_option('--builder-name', default='builder',
help='filename for builder')
options, args = parser.parse_args()
if len(args) > 1:
parser.error('too many arguments specified!')
options.filename = None
options.mastername = None
if args:
if os.path.exists(args[0]):
options.filename = args[0]
options.builderpath = os.path.join(os.path.dirname(args[0]),
options.builder_name)
else:
options.mastername = args[0]
return options
def loadBuilder(path):
"""Load a pickled builder from a filename."""
with open(path, 'rb') as pkl_file:
builder = cPickle.load(pkl_file)
return builder
def loadBuild(path, builder):
"""Load a pickled build from a filename."""
# adapted from loadBuildFromFile() in buildbot/status/builder.py
with open(path, 'rb') as pkl_file:
build = cPickle.load(pkl_file)
build.builder = builder
for step in build.getSteps():
step.builder = builder
for loog in step.getLogs():
loog.builder = builder
return build
def getBuildStepVersion(step):
"""Return the unpickled builder's persistenceVersion.
The version reported in buildstep.persistenceVersion is what the currently
loaded version of buildbot expects a step to be (instead of what is pickled).
This returns what has been pickled.
"""
return getattr(step,
'buildbot.status.builder.BuildStepStatus.persistenceVersion')
def downgrade(build, toVersion):
"""Perform a downgrade option in-memory."""
maxVersion = 4 # the maximum version we understand
minVersion = 3 # the minimum version we understand
if toVersion > maxVersion or toVersion < minVersion:
raise ValueError('Requested downgrade to version %d, only %d - %d '
' supported.' % (toVersion, minVersion, maxVersion))
for step in build.getSteps():
pV = getBuildStepVersion(step)
if pV > maxVersion or pV < minVersion:
raise ValueError('Buildsteps in build %d are persistence version %d, only'
' %d - %d supported.' % (step.getNumber(), pV,
minVersion, maxVersion))
if toVersion < 4 and step.persistenceVersion == 4:
del step.hidden
step.persistenceVersion = 3
setattr(step,
'buildbot.status.builder.BuildStepStatus.persistenceVersion',
3)
def processBuild(filename, builder, options):
"""Processes a build file, returning if the operation was successful."""
try:
build = loadBuild(filename, builder)
except IOError as e:
print ' Error %d loading %s: %s' % (e.errno, filename, e.strerror)
return False
if not options.target_version:
steps = build.getSteps()
if not steps:
print ' Build %d at version <none> -- %s' % (build.getNumber(),
filename)
else:
print ' Build %d at version %d -- %s' % (build.getNumber(),
getBuildStepVersion(steps[0]),
filename)
else:
try:
downgrade(build, options.target_version)
except ValueError as e:
print ' Error downgrading build %d: %s -- %s' % (build.getNumber(), e,
filename)
return False
if options.commit:
shutil.copy(filename, filename + '.bak')
with open(filename, 'wb') as output:
cPickle.dump(build, output, -1)
os.remove(filename + '.bak')
print (' Buildsteps for build %d downgraded to version %d'
' -- %s' % (build.getNumber(), options.target_version, filename))
return True
def locateBuilderDirs(path, options):
"""Find all candidate builder dirs under a master."""
dirs = [os.path.join(path, f) for f in os.listdir(path) if
os.path.isdir(os.path.join(path, f))]
builders = [os.path.relpath(f) for f in dirs
if options.builder_name in os.listdir(f)]
return builders
def processDirectory(path, options):
"""Process all builds found in a directory."""
print 'in dir %s:' % path
builder = loadBuilder(os.path.join(path, options.builder_name))
builds = [os.path.join(path, f) for f in os.listdir(path)
if re.match(r'^\d+$', f)]
if not builds:
print ' no builds'
for build in builds:
if not processBuild(build, builder, options):
return False
return True
def main(options):
if options.list_masters:
masterpairs = master_cfg_utils.GetMasters()
master_cfg_utils.PrettyPrintMasters(masterpairs)
return 0
if options.master_dir:
path = options.master_dir
elif options.mastername:
path = master_cfg_utils.ChooseMaster(options.mastername)
if not path:
return 2
else:
path = options.filename
if os.path.isdir(path):
print path
for directory in locateBuilderDirs(path, options):
if not processDirectory(directory, options):
if not options.commit:
print
print '*** errors encountered but commit was not set '
return 2
if not options.commit:
print
print '*** re-run with --commit to overwrite changes ***'
else:
builder = loadBuilder(options.builderpath)
if not processBuild(path, builder, options):
if not options.commit:
print
print '*** errors encountered but commit was not set '
return 2
if not options.commit:
print
print '*** re-run with --commit to overwrite changes ***'
return 0
if __name__ == '__main__':
sys.exit(main(process_options()))