release-tools/stable_freeze.py

346 lines
10 KiB
Python
Executable File

#!/usr/bin/env python
#
# Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import json
import logging
import optparse
import os
import subprocess
import sys
GERRIT_USER = None
GERRIT_HOST = None
DRY_RUN = False
DEFAULT_REL = 'icehouse'
CODENAMES = {
'2013.1': 'grizzly',
'2013.2': 'havana',
'2014.1': 'icehouse',
'2014.2': 'juno',
'2015.1': 'kilo',
}
PROJECTS = {}
PROJECTS['grizzly'] = [
'nova', 'glance', 'keystone', 'neutron', 'cinder', 'horizon'
]
PROJECTS['havana'] = ['heat', 'ceilometer']
PROJECTS['havana'].extend(PROJECTS['grizzly'])
PROJECTS['icehouse'] = ['trove']
PROJECTS['icehouse'].extend(PROJECTS['havana'])
PROJECTS['juno'] = ['sahara']
PROJECTS['juno'].extend(PROJECTS['icehouse'])
PROJECTS['kilo'] = ['ironic']
PROJECTS['kilo'].extend(PROJECTS['juno'])
logging.basicConfig(level=logging.INFO)
def run_command(cmd):
_cmd = [
'ssh', '-p', '29418', '%s@%s' % (GERRIT_USER, GERRIT_HOST),
'gerrit'
]
_cmd += cmd
if 'query' in _cmd:
_cmd += ['--format', 'json']
res = subprocess.check_output(_cmd)
if 'query' not in cmd:
return res
out = []
for ln in res.split('\n'):
try:
out.append(json.loads(ln))
except Exception:
pass
return out
def open_reviews(projects, branch):
cmd = ['query', '--current-patch-set',
'branch:%s status:open ( %s )' % (branch, ' OR '.join(projects))]
reviews = run_command(cmd)
return [r for r in reviews if r.get('id')]
def reviews_hard_freeze(reviews):
return [r for r in reviews if r.get('id')]
def reviews_soft_freeze(reviews):
out = []
for review in reviews:
try:
cps = review['currentPatchSet']
except KeyError:
continue
approved = False
for app in cps.get('approvals', []):
if (app.get('description', '') == 'Workflow' and
app.get('value') == '1'):
logging.info('Skipping approved review %s' % review['id'])
approved = True
break
if not approved:
out.append(review)
return out
def openstack_release(rel):
for r, c in CODENAMES.iteritems():
if rel.startswith(r):
return c
logging.error('Invalid release: %s' % rel)
sys.exit(1)
def revision(review):
cps = review.get('currentPatchSet')
if not cps:
return None
return cps.get('revision')
class Revision(object):
def __init__(self, change, url):
self.change = change
self.url = url
def __str__(self):
return '%s # %s' % (self.change, self.url)
FREEZE_MSG = r"""
stable/%s freeze for %s
The stable branch is frozen to allow testing before the release. Freeze
exceptions can be proposed on openstack-dev mailing list with [stable] tag in
subject. Once exception request is sent, stable-maint-core team will try to
reach consensus.
More details at: https://wiki.openstack.org/wiki/StableBranch
"""
def freeze(reviews, rel):
logging.info('freezing %s reviews.' % len(reviews))
msg = FREEZE_MSG % (openstack_release(rel), rel)
frozen = []
for review in reviews:
_revision = revision(review)
if not _revision:
continue
change = Revision(_revision, review['url'])
cmd = ["review",
"--message '%s' --code-review '-2' %s" % (msg, _revision)]
if DRY_RUN:
print('Would run: gerrit %s' % ' '.join(cmd))
frozen.append(change)
continue
try:
if DRY_RUN:
logging.info('Would run: gerrit %s' % cmd)
continue
run_command(cmd)
frozen.append(change)
except Exception:
logging.error('Failed to -2: %s' % _revision)
continue
logging.info('-2: %s' % change)
return frozen
def thaw(revisions, rel):
msg = 'stable/%s branches open' % openstack_release(rel)
for rev in revisions:
cmd = ["review",
"--message '%s' --code-review '0' %s" % (msg, rev)]
if DRY_RUN:
print('Would run: gerrit %s' % ' '.join(cmd))
continue
try:
run_command(cmd)
logging.info('thawed: %s' % rev)
except Exception:
logging.error('Failed to thaw: %s' % rev)
logging.info('thawed: %s' % rev)
print(' '.join(cmd))
def parse_rev_file(f):
_in = open(f).readlines()
out = []
for l in _in:
if '#' in l:
change = l.split('#')[0].strip()
else:
change = l.strip()
out.append(change)
return out
def get_review(change_no):
cmd = ['query', '--current-patch-set', change_no]
review = run_command(cmd)
if len(review) == 1:
logging.warning('Change %s not found' % change_no)
return
return review[0]
def filter_skip(reviews):
skips = parse_rev_file('/tmp/skip')
out = []
for review in reviews:
if revision(review) not in skips:
out.append(review)
else:
print('skip: %s' % revision(review))
return out
if __name__ == '__main__':
usage = 'usage: %prog [options] query|freeze|thaw'
parser = optparse.OptionParser(usage=usage)
parser.add_option('-r', '--release',
default=None, action='store', dest='release',
help='Openstack release, eg 2013.1.3')
parser.add_option('-s', '--soft-freeze',
default=False, action='store_true', dest='soft_freeze')
parser.add_option('-o', '--outfile',
default='stable_freeze.txt', action='store',
dest='outfile',
help='File to save frozen reviews during freeze'
'(default: stable_freeze.txt)')
parser.add_option('-i', '--infile',
default='stable_freeze.txt', action='store',
dest='infile',
help='File to read frozen reviews during thaw '
'(default: stable_freeze.txt)')
parser.add_option('-d', '--dry', default=False, dest='dry_run',
action='store_true', help='Dry run')
parser.add_option('-u', '--user', default=os.getenv('GERRIT_USER'),
dest='gerrit_user', action='store',
help='Gerrit username for ssh access')
parser.add_option('-g', '--gerrit_host',
default=os.getenv('GERRIT_HOST', 'review.openstack.org'),
dest='gerrit_host', action='store',
help='Gerrit server hostname (default: '
'review.openstack.org)')
parser.add_option('-c', '--change', default=None, dest='changes',
action='append',
help='Freeze a specific review(s) and append info to '
'outfile. Use gerrit review #, space separated '
'for multiple')
(opts, args) = parser.parse_args()
if opts.dry_run:
DRY_RUN = True
GERRIT_HOST = opts.gerrit_host
GERRIT_USER = opts.gerrit_user
if not GERRIT_USER:
logging.error('must specify gerrit ssh username')
sys.exit(1)
if not args or len(args) > 1:
parser.print_help()
sys.exit(1)
if not opts.release:
logging.error('must specify release')
sys.exit(1)
action = args[0]
os_rel = openstack_release(opts.release)
projects = ['openstack/%s' % p for p in PROJECTS[os_rel]]
branch = 'stable/%s' % os_rel
if opts.changes:
reviews = []
for c in opts.changes:
r = get_review(c)
if r:
reviews.append(r)
else:
reviews = open_reviews(projects, branch)
logging.info('Found %s open reviews for %s' % (len(reviews), branch))
if action in ['freeze', 'query']:
if opts.soft_freeze:
logging.info(
'Soft-freeze, filtering reviews that are approved but '
'pending gate verification')
total = len(reviews)
to_freeze = reviews_soft_freeze(reviews)
logging.info(
'Skipped freezing %s reviews that are pending gate '
'verification' % (total - len(to_freeze)))
else:
to_freeze = reviews
if action == 'query':
print('\n\n')
logging.info('%s open reviews to be frozen: ' % len(to_freeze))
print('\n')
print('\n'.join([rev['id'] for rev in to_freeze]))
sys.exit(0)
elif action == 'freeze':
to_freeze = filter_skip(to_freeze)
frozen = freeze(to_freeze, opts.release)
if opts.changes:
# if individual changes are specified, we're likely adding
# a -2 to new post-freeze reviews. append these to the list
# of frozen reviews.
mode = 'a+'
else:
mode = 'wb'
if not DRY_RUN:
with open(opts.outfile, mode) as out:
for r in frozen:
out.write('%s\n' % r)
else:
logging.info('Would write to %s (%s):' % (opts.outfile, mode))
for r in frozen:
print(r)
if action in ['thaw']:
to_thaw = []
_in = open(opts.infile).readlines()
for l in _in:
if '#' in l:
change = l.split('#')[0].strip()
else:
change = l.strip()
to_thaw.append(change)
logging.info('Will thaw %s reviews for %s' % (len(to_thaw), branch))
thaw(to_thaw, opts.release)