Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 # Copyright 2016 The Chromium Authors. All rights reserved. | |
| 2 # Use of this source code is governed by a BSD-style license that can be | |
| 3 # found in the LICENSE file. | |
| 4 | |
| 5 """This script will change a master from using postgresql to sqlite. | |
| 6 | |
| 7 It: | |
| 8 1) Creates .stop_master_lifecycle to stop master manager. | |
| 9 2) Stops the master. | |
| 10 3) Dumps the postgres database and imports it into sqlite. | |
| 11 4) Removes the .dbconfig file indicating buildbot should now use sqlite. | |
| 12 5) Starts the master. | |
| 13 6) Removes .stop_master_lifecycle. | |
| 14 | |
| 15 If this script fails in the middle you will have to restore it to a known state | |
| 16 manually. | |
| 17 | |
| 18 Run this script in the master's directory. Some third_party libraries need to | |
| 19 be on the PYTHONPATH: | |
|
Vadim Sh.
2016/06/28 18:37:27
I think you can just run it via runit.py to setup
dsansome
2016/06/29 01:35:24
Neat, thanks!
| |
| 20 export BUILD_DIR=~/buildbot/build | |
| 21 export PYTHONPATH=${BUILD_DIR}/third_party/buildbot_8_4p1:${BUILD_DIR}/third_p arty/sqlalchemy_0_7_1:${BUILD_DIR}/third_party/sqlalchemy_migrate_0_7_1:${BUILD_ DIR}/third_party/tempita_0_5 | |
| 22 python ${BUILD_DIR}/scripts/tools/migrate_psql_to_sqlite.py | |
| 23 | |
| 24 Without any arguments this will run in a dry-run mode and create a | |
| 25 'dry-run-psql-conversion.sqlite' file for you to inspect manually. Use | |
| 26 --no-dry-run to actually stop and restart the master and update the config. | |
| 27 """ | |
| 28 | |
| 29 import argparse | |
| 30 import logging | |
| 31 import os | |
| 32 import sqlite3 | |
| 33 import subprocess | |
| 34 import sys | |
| 35 | |
| 36 from buildbot import cache | |
| 37 from buildbot.db import connector | |
| 38 from twisted.internet import defer, reactor | |
| 39 | |
| 40 | |
| 41 class FakeBuildMaster(object): | |
| 42 def __init__(self): | |
| 43 self.caches = cache.CacheManager() | |
| 44 | |
| 45 | |
| 46 @defer.inlineCallbacks | |
| 47 def Main(args): | |
| 48 if args.no_dry_run: | |
| 49 sqlite_filename = 'state.sqlite' | |
| 50 else: | |
| 51 sqlite_filename = 'dry-run-psql-conversion.sqlite' | |
| 52 | |
| 53 # Read the dbconfig. This will fail if the config doesn't exist and the | |
| 54 # master doesn't use postgresql. | |
| 55 dbconfig = {} | |
| 56 execfile('.dbconfig', dbconfig) | |
| 57 | |
| 58 if args.no_dry_run: | |
| 59 # Stop master manager from touching this master while we play with it. | |
| 60 if os.path.exists('.stop_master_lifecycle'): | |
| 61 raise Exception('A .stop_master_lifecycle file already exists') | |
| 62 logging.info('Creating .stop_master_lifecycle file') | |
| 63 with open('.stop_master_lifecycle', 'w') as fh: | |
| 64 fh.write('migrate_psql_to_sqlite.py') | |
| 65 | |
| 66 # Stop the master. | |
| 67 logging.info('Stopping master') | |
| 68 subprocess.check_call(['make', 'stop']) | |
| 69 subprocess.check_call(['make', 'wait']) | |
| 70 | |
| 71 # Dump the postgres database. | |
| 72 logging.info('Dumping postgres database %s', dbconfig['dbname']) | |
| 73 env = os.environ.copy() | |
| 74 env['PGPASSWORD'] = dbconfig['password'] | |
| 75 sql = subprocess.check_output(['pg_dump', | |
| 76 '-d', dbconfig['dbname'], | |
| 77 '-U', dbconfig['username'], | |
| 78 '-h', 'localhost', | |
| 79 '--data-only', | |
| 80 '--inserts'], env=env) | |
| 81 | |
| 82 # Strip out postgres-specific things. | |
| 83 sql = '\n'.join( | |
| 84 line for line in sql.splitlines() | |
| 85 if not line.startswith('SET') and | |
| 86 not line.startswith('INSERT INTO migrate_version') and | |
| 87 not 'pg_catalog.setval' in line) | |
| 88 | |
| 89 # Delete any existing sqlite database. | |
| 90 if os.path.exists(sqlite_filename): | |
| 91 os.unlink(sqlite_filename) | |
| 92 | |
| 93 # Create the new sqlite database. | |
| 94 logging.info('Creating empty sqlite database in %s', sqlite_filename) | |
| 95 db = connector.DBConnector( | |
| 96 FakeBuildMaster(), 'sqlite:///%s' % sqlite_filename, '.') | |
| 97 yield db.model.upgrade() | |
| 98 | |
| 99 # Import the data into the sqlite database. | |
| 100 logging.info('Filling sqlite database %s', sqlite_filename) | |
| 101 conn = sqlite3.connect(sqlite_filename) | |
| 102 cursor = conn.cursor() | |
| 103 cursor.execute('pragma synchronous = off') | |
| 104 cursor.execute('pragma journal_mode = memory') | |
| 105 cursor.executescript(sql) | |
| 106 conn.commit() | |
| 107 conn.close() | |
| 108 | |
| 109 if args.no_dry_run: | |
| 110 # Remove the .dbconfig to make it use the sqlite database. | |
| 111 logging.info('Moving .dbconfig file to dbconfig.bak') | |
| 112 os.rename('.dbconfig', 'dbconfig.bak') | |
| 113 | |
| 114 # Start the master. | |
| 115 logging.info('Starting master') | |
| 116 subprocess.check_call(['make', 'start']) | |
| 117 | |
| 118 # Let master manager take over again. | |
| 119 logging.info('Removing .stop_master_lifecycle file') | |
| 120 os.unlink('.stop_master_lifecycle') | |
| 121 | |
| 122 logging.info('Done!') | |
| 123 else: | |
| 124 logging.info('Dry-run done!') | |
| 125 | |
| 126 | |
| 127 if __name__ == '__main__': | |
| 128 parser = argparse.ArgumentParser() | |
| 129 parser.add_argument('--no-dry-run', action='store_true') | |
| 130 args = parser.parse_args() | |
| 131 | |
| 132 def HandleError(err): | |
| 133 err.printTraceback() | |
| 134 reactor.stop() | |
| 135 | |
| 136 def Start(): | |
| 137 d = Main(args) | |
| 138 d.addCallback(lambda _: reactor.stop()) | |
| 139 d.addErrback(HandleError) | |
| 140 | |
| 141 logging.basicConfig(level=logging.INFO, | |
| 142 format='\033[94m%(asctime)s %(message)s\033[0m') | |
| 143 reactor.callWhenRunning(Start) | |
| 144 reactor.run() | |
| OLD | NEW |