| OLD | NEW |
| (Empty) | |
| 1 #!/usr/bin/env python |
| 2 # Copyright 2016 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. |
| 5 |
| 6 """This script will change a master from using postgresql to sqlite. |
| 7 |
| 8 It: |
| 9 1) Creates .stop_master_lifecycle to stop master manager. |
| 10 2) Stops the master. |
| 11 3) Dumps the postgres database and imports it into sqlite. |
| 12 4) Removes the .dbconfig file indicating buildbot should now use sqlite. |
| 13 5) Starts the master. |
| 14 6) Removes .stop_master_lifecycle. |
| 15 |
| 16 If this script fails in the middle you will have to restore it to a known state |
| 17 manually. |
| 18 |
| 19 Run this script in the master's directory. Some third_party libraries need to |
| 20 be on the PYTHONPATH, so use runit.py: |
| 21 export TOOLS_DIR=~/buildbot/build/scripts/tools |
| 22 ${TOOLS_DIR}/runit.py ${TOOLS_DIR}/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 Run(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 def 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 = Run(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() |
| 145 |
| 146 |
| 147 if __name__ == '__main__': |
| 148 main() |
| OLD | NEW |