blob: ff0a3fdd6f3c7425e271f28abf41bccbb1fa31df [file] [log] [blame] [edit]
#!/usr/bin/env python
# Copyright 2016 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.
"""This script will change a master from using postgresql to sqlite.
It:
1) Creates .stop_master_lifecycle to stop master manager.
2) Stops the master.
3) Dumps the postgres database and imports it into sqlite.
4) Removes the .dbconfig file indicating buildbot should now use sqlite.
5) Starts the master.
6) Removes .stop_master_lifecycle.
If this script fails in the middle you will have to restore it to a known state
manually.
Run this script in the master's directory. Some third_party libraries need to
be on the PYTHONPATH, so use runit.py:
export TOOLS_DIR=~/buildbot/build/scripts/tools
${TOOLS_DIR}/runit.py ${TOOLS_DIR}/migrate_psql_to_sqlite.py
Without any arguments this will run in a dry-run mode and create a
'dry-run-psql-conversion.sqlite' file for you to inspect manually. Use
--no-dry-run to actually stop and restart the master and update the config.
"""
import argparse
import logging
import os
import sqlite3
import subprocess
import sys
from buildbot import cache
from buildbot.db import connector
from twisted.internet import defer, reactor
class FakeBuildMaster(object):
def __init__(self):
self.caches = cache.CacheManager()
@defer.inlineCallbacks
def Run(args):
if args.no_dry_run:
sqlite_filename = 'state.sqlite'
else:
sqlite_filename = 'dry-run-psql-conversion.sqlite'
# Read the dbconfig. This will fail if the config doesn't exist and the
# master doesn't use postgresql.
dbconfig = {}
execfile('.dbconfig', dbconfig)
if args.no_dry_run:
# Stop master manager from touching this master while we play with it.
if os.path.exists('.stop_master_lifecycle'):
raise Exception('A .stop_master_lifecycle file already exists')
logging.info('Creating .stop_master_lifecycle file')
with open('.stop_master_lifecycle', 'w') as fh:
fh.write('migrate_psql_to_sqlite.py')
# Stop the master.
logging.info('Stopping master')
subprocess.check_call(['make', 'stop'])
subprocess.check_call(['make', 'wait'])
# Dump the postgres database.
logging.info('Dumping postgres database %s', dbconfig['dbname'])
env = os.environ.copy()
env['PGPASSWORD'] = dbconfig['password']
sql = subprocess.check_output(['pg_dump',
'-d', dbconfig['dbname'],
'-U', dbconfig['username'],
'-h', 'localhost',
'--data-only',
'--inserts'], env=env)
# Strip out postgres-specific things.
sql = '\n'.join(
line for line in sql.splitlines()
if not line.startswith('SET') and
not line.startswith('INSERT INTO migrate_version') and
not 'pg_catalog.setval' in line)
# Delete any existing sqlite database.
if os.path.exists(sqlite_filename):
os.unlink(sqlite_filename)
# Create the new sqlite database.
logging.info('Creating empty sqlite database in %s', sqlite_filename)
db = connector.DBConnector(
FakeBuildMaster(), 'sqlite:///%s' % sqlite_filename, '.')
yield db.model.upgrade()
# Import the data into the sqlite database.
logging.info('Filling sqlite database %s', sqlite_filename)
conn = sqlite3.connect(sqlite_filename)
cursor = conn.cursor()
cursor.execute('pragma synchronous = off')
cursor.execute('pragma journal_mode = memory')
cursor.executescript(sql)
conn.commit()
conn.close()
if args.no_dry_run:
# Remove the .dbconfig to make it use the sqlite database.
logging.info('Moving .dbconfig file to dbconfig.bak')
os.rename('.dbconfig', 'dbconfig.bak')
# Start the master.
logging.info('Starting master')
subprocess.check_call(['make', 'start'])
# Let master manager take over again.
logging.info('Removing .stop_master_lifecycle file')
os.unlink('.stop_master_lifecycle')
logging.info('Done!')
else:
logging.info('Dry-run done!')
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--no-dry-run', action='store_true')
args = parser.parse_args()
def HandleError(err):
err.printTraceback()
reactor.stop()
def Start():
d = Run(args)
d.addCallback(lambda _: reactor.stop())
d.addErrback(HandleError)
logging.basicConfig(level=logging.INFO,
format='\033[94m%(asctime)s %(message)s\033[0m')
reactor.callWhenRunning(Start)
reactor.run()
if __name__ == '__main__':
main()