~launchpad-pqm/launchpad/devel

10637.3.1 by Guilherme Salgado
Use the default python version instead of a hard-coded version
1
#!/usr/bin/python
8452.3.3 by Karl Fogel
* utilities/: Add copyright header block to source files that were
2
#
9893.10.4 by Stuart Bishop
Comment on mirrored copy of the script for production deployment
3
# Copyright 2009-2011 Canonical Ltd.  This software is licensed under the
8687.15.3 by Karl Fogel
Shorten the copyright header block to two lines.
4
# GNU Affero General Public License version 3 (see the file LICENSE).
8452.3.3 by Karl Fogel
* utilities/: Add copyright header block to source files that were
5
9893.10.4 by Stuart Bishop
Comment on mirrored copy of the script for production deployment
6
# This file is mirrored into lp:losa-db-scripts, so please keep that
7
# version in sync with the master in the Launchpad tree.
8
1876 by Canonical.com Patch Queue Manager
[r=jamesh] database dump script
9
"""
10
dropdb only more so.
11
9893.10.3 by Stuart Bishop
Ensure pgmassacre does not put the database cluster into recovery mode, and remove support for PostgreSQL versions earlier than 8.4
12
Cut off access, slaughter connections and burn the database to the ground
13
(but do nothing that could put the system into recovery mode).
1876 by Canonical.com Patch Queue Manager
[r=jamesh] database dump script
14
"""
15
16
import sys
17
import time
5821.2.85 by James Henstridge
Add "make check_launchpad_storm_on_merge" target that runs the tests
18
import psycopg2
19
import psycopg2.extensions
3432.1.2 by Stuart Bishop
pgmassacre should connect as default db user
20
from optparse import OptionParser
3432.1.1 by Stuart Bishop
Update pgmassacre to work as unix user other than 'postgres'
21
22
5282.4.2 by Stuart Bishop
Make pgpassacre.py rollback outstanding prepared transactions to avoid 'still being accessed by other users' error.
23
def connect(dbname='template1'):
5282.4.3 by Stuart Bishop
Review feedback updates
24
    """Connect to the database, returning the DB-API connection."""
3432.1.2 by Stuart Bishop
pgmassacre should connect as default db user
25
    if options.user is not None:
5821.2.85 by James Henstridge
Add "make check_launchpad_storm_on_merge" target that runs the tests
26
        return psycopg2.connect("dbname=%s user=%s" % (dbname, options.user))
3432.1.2 by Stuart Bishop
pgmassacre should connect as default db user
27
    else:
5821.2.85 by James Henstridge
Add "make check_launchpad_storm_on_merge" target that runs the tests
28
        return psycopg2.connect("dbname=%s" % dbname)
3432.1.1 by Stuart Bishop
Update pgmassacre to work as unix user other than 'postgres'
29
30
5282.4.2 by Stuart Bishop
Make pgpassacre.py rollback outstanding prepared transactions to avoid 'still being accessed by other users' error.
31
def rollback_prepared_transactions(database):
5282.4.3 by Stuart Bishop
Review feedback updates
32
    """Rollback any prepared transactions.
33
8137.13.1 by Stuart Bishop
Option for pgmassacre.py to rebuild the database it just destroyed from a template
34
    PostgreSQL will refuse to drop a database with outstanding prepared
35
    transactions.
5282.4.3 by Stuart Bishop
Review feedback updates
36
    """
5282.4.2 by Stuart Bishop
Make pgpassacre.py rollback outstanding prepared transactions to avoid 'still being accessed by other users' error.
37
    con = connect(database)
9893.10.5 by Stuart Bishop
delint
38
    con.set_isolation_level(0)  # Autocommit so we can ROLLBACK PREPARED.
5282.4.2 by Stuart Bishop
Make pgpassacre.py rollback outstanding prepared transactions to avoid 'still being accessed by other users' error.
39
    cur = con.cursor()
40
41
    # Get a list of outstanding prepared transactions.
42
    cur.execute(
43
            "SELECT gid FROM pg_prepared_xacts WHERE database=%(database)s",
5282.4.4 by Stuart Bishop
Update to standard 'icky parenthesis style
44
            vars())
5282.4.2 by Stuart Bishop
Make pgpassacre.py rollback outstanding prepared transactions to avoid 'still being accessed by other users' error.
45
    xids = [row[0] for row in cur.fetchall()]
46
    for xid in xids:
47
        cur.execute("ROLLBACK PREPARED %(xid)s", vars())
48
    con.close()
49
50
9893.10.3 by Stuart Bishop
Ensure pgmassacre does not put the database cluster into recovery mode, and remove support for PostgreSQL versions earlier than 8.4
51
def still_open(database, max_wait=120):
8137.13.1 by Stuart Bishop
Option for pgmassacre.py to rebuild the database it just destroyed from a template
52
    """Return True if there are still open connections, apart from our own.
53
9893.10.3 by Stuart Bishop
Ensure pgmassacre does not put the database cluster into recovery mode, and remove support for PostgreSQL versions earlier than 8.4
54
    Waits a while to ensure that connections shutting down have a chance
55
    to. This might take a while if there is a big transaction to
56
    rollback.
3432.1.1 by Stuart Bishop
Update pgmassacre to work as unix user other than 'postgres'
57
    """
58
    con = connect()
9893.10.5 by Stuart Bishop
delint
59
    con.set_isolation_level(0)  # Autocommit.
3432.1.1 by Stuart Bishop
Update pgmassacre to work as unix user other than 'postgres'
60
    cur = con.cursor()
9893.10.3 by Stuart Bishop
Ensure pgmassacre does not put the database cluster into recovery mode, and remove support for PostgreSQL versions earlier than 8.4
61
    # Keep checking until the timeout is reached, returning True if all
62
    # of the backends are gone.
3432.1.1 by Stuart Bishop
Update pgmassacre to work as unix user other than 'postgres'
63
    start = time.time()
8137.13.1 by Stuart Bishop
Option for pgmassacre.py to rebuild the database it just destroyed from a template
64
    while time.time() < start + max_wait:
3432.1.1 by Stuart Bishop
Update pgmassacre to work as unix user other than 'postgres'
65
        cur.execute("""
8137.13.3 by Stuart Bishop
Add some instrumentation to pgmassacre.py creation
66
            SELECT TRUE FROM pg_stat_activity
8137.13.1 by Stuart Bishop
Option for pgmassacre.py to rebuild the database it just destroyed from a template
67
            WHERE
68
                datname=%(database)s
69
                AND procpid != pg_backend_pid()
70
            LIMIT 1
3432.1.1 by Stuart Bishop
Update pgmassacre to work as unix user other than 'postgres'
71
            """, vars())
72
        if cur.fetchone() is None:
73
            return False
9893.10.5 by Stuart Bishop
delint
74
        time.sleep(0.6)  # Stats only updated every 500ms.
3432.1.1 by Stuart Bishop
Update pgmassacre to work as unix user other than 'postgres'
75
    con.close()
76
    return True
77
5282.4.3 by Stuart Bishop
Review feedback updates
78
8137.13.1 by Stuart Bishop
Option for pgmassacre.py to rebuild the database it just destroyed from a template
79
def massacre(database):
3432.1.1 by Stuart Bishop
Update pgmassacre to work as unix user other than 'postgres'
80
    con = connect()
9893.10.5 by Stuart Bishop
delint
81
    con.set_isolation_level(0)  # Autocommit
3432.1.1 by Stuart Bishop
Update pgmassacre to work as unix user other than 'postgres'
82
    cur = con.cursor()
83
5529.1.5 by Stuart Bishop
Make pgmassacre.py a little more bulletproof
84
    # Allow connections to the doomed database if something turned this off,
85
    # such as an aborted run of this script.
86
    cur.execute(
87
        "UPDATE pg_database SET datallowconn=TRUE WHERE datname=%s",
88
        [database])
89
5282.4.2 by Stuart Bishop
Make pgpassacre.py rollback outstanding prepared transactions to avoid 'still being accessed by other users' error.
90
    # Rollback prepared transactions.
91
    rollback_prepared_transactions(database)
92
93
    try:
5282.4.3 by Stuart Bishop
Review feedback updates
94
        # Stop connections to the doomed database.
5282.4.2 by Stuart Bishop
Make pgpassacre.py rollback outstanding prepared transactions to avoid 'still being accessed by other users' error.
95
        cur.execute(
5529.1.5 by Stuart Bishop
Make pgmassacre.py a little more bulletproof
96
            "UPDATE pg_database SET datallowconn=FALSE WHERE datname=%s",
5282.4.4 by Stuart Bishop
Update to standard 'icky parenthesis style
97
            [database])
5282.4.2 by Stuart Bishop
Make pgpassacre.py rollback outstanding prepared transactions to avoid 'still being accessed by other users' error.
98
9893.10.3 by Stuart Bishop
Ensure pgmassacre does not put the database cluster into recovery mode, and remove support for PostgreSQL versions earlier than 8.4
99
        # New connections are disabled, but pg_stat_activity is only
100
        # updated every 500ms. Ensure that pg_stat_activity has
101
        # been refreshed to catch any connections that opened
102
        # immediately before setting datallowconn.
103
        time.sleep(1)
104
105
        # Terminate open connections.
106
        cur.execute("""
107
            SELECT procpid, pg_terminate_backend(procpid)
108
            FROM pg_stat_activity
109
            WHERE datname=%s AND procpid <> pg_backend_pid()
110
            """, [database])
9893.10.5 by Stuart Bishop
delint
111
        for procpid, success in cur.fetchall():
9893.10.3 by Stuart Bishop
Ensure pgmassacre does not put the database cluster into recovery mode, and remove support for PostgreSQL versions earlier than 8.4
112
            if not success:
113
                print >> sys.stderr, (
114
                    "pg_terminate_backend(%s) failed" % procpid)
5282.4.2 by Stuart Bishop
Make pgpassacre.py rollback outstanding prepared transactions to avoid 'still being accessed by other users' error.
115
        con.close()
116
117
        if still_open(database):
5282.4.3 by Stuart Bishop
Review feedback updates
118
            print >> sys.stderr, (
5282.4.4 by Stuart Bishop
Update to standard 'icky parenthesis style
119
                    "Unable to kill all backends! Database not destroyed.")
5282.4.2 by Stuart Bishop
Make pgpassacre.py rollback outstanding prepared transactions to avoid 'still being accessed by other users' error.
120
            return 9
121
5282.4.3 by Stuart Bishop
Review feedback updates
122
        # Destroy the database.
5282.4.2 by Stuart Bishop
Make pgpassacre.py rollback outstanding prepared transactions to avoid 'still being accessed by other users' error.
123
        con = connect()
5282.4.3 by Stuart Bishop
Review feedback updates
124
        # AUTOCOMMIT required to execute commands like DROP DATABASE.
125
        con.set_isolation_level(0)
5282.4.2 by Stuart Bishop
Make pgpassacre.py rollback outstanding prepared transactions to avoid 'still being accessed by other users' error.
126
        cur = con.cursor()
9893.10.5 by Stuart Bishop
delint
127
        cur.execute("DROP DATABASE %s" % database)  # Not quoted.
8137.13.3 by Stuart Bishop
Add some instrumentation to pgmassacre.py creation
128
        con.close()
129
        return 0
5282.4.2 by Stuart Bishop
Make pgpassacre.py rollback outstanding prepared transactions to avoid 'still being accessed by other users' error.
130
    finally:
131
        # In case something messed up, allow connections again so we can
132
        # inspect the damage.
133
        con = connect()
134
        con.set_isolation_level(0)
135
        cur = con.cursor()
136
        cur.execute(
137
                "UPDATE pg_database SET datallowconn=TRUE WHERE datname=%s",
5282.4.4 by Stuart Bishop
Update to standard 'icky parenthesis style
138
                [database])
8137.13.1 by Stuart Bishop
Option for pgmassacre.py to rebuild the database it just destroyed from a template
139
        con.close()
140
141
142
def rebuild(database, template):
143
    if still_open(template, 20):
144
        print >> sys.stderr, (
145
            "Giving up waiting for connections to %s to drop." % template)
8137.13.3 by Stuart Bishop
Add some instrumentation to pgmassacre.py creation
146
        report_open_connections(template)
8137.13.1 by Stuart Bishop
Option for pgmassacre.py to rebuild the database it just destroyed from a template
147
        return 10
148
149
    start = time.time()
8137.13.3 by Stuart Bishop
Add some instrumentation to pgmassacre.py creation
150
    now = start
8137.13.1 by Stuart Bishop
Option for pgmassacre.py to rebuild the database it just destroyed from a template
151
    error_msg = None
8137.13.3 by Stuart Bishop
Add some instrumentation to pgmassacre.py creation
152
    con = connect()
9893.10.5 by Stuart Bishop
delint
153
    con.set_isolation_level(0)  # Autocommit required for CREATE DATABASE.
7675.357.8 by Stuart Bishop
Make database creation compatible with both PG 8.3 and PG 8.4
154
    create_db_cmd = """
155
        CREATE DATABASE %s WITH ENCODING='UTF8' TEMPLATE=%s
156
        """ % (database, template)
157
    # 8.4 allows us to create empty databases with a different locale
158
    # to template1 by using the template0 database as a template.
159
    # We make use of this feature so we don't have to care what locale
160
    # was used to create the database cluster rather than requiring it
161
    # to be rebuilt in the C locale.
9893.10.3 by Stuart Bishop
Ensure pgmassacre does not put the database cluster into recovery mode, and remove support for PostgreSQL versions earlier than 8.4
162
    if template == "template0":
7675.357.8 by Stuart Bishop
Make database creation compatible with both PG 8.3 and PG 8.4
163
        create_db_cmd += "LC_COLLATE='C' LC_CTYPE='C'"
8137.13.3 by Stuart Bishop
Add some instrumentation to pgmassacre.py creation
164
    while now < start + 20:
8137.13.1 by Stuart Bishop
Option for pgmassacre.py to rebuild the database it just destroyed from a template
165
        cur = con.cursor()
166
        try:
7675.357.8 by Stuart Bishop
Make database creation compatible with both PG 8.3 and PG 8.4
167
            cur.execute(create_db_cmd)
8137.13.3 by Stuart Bishop
Add some instrumentation to pgmassacre.py creation
168
            con.close()
8137.13.1 by Stuart Bishop
Option for pgmassacre.py to rebuild the database it just destroyed from a template
169
            return 0
170
        except psycopg2.Error, exception:
171
            error_msg = str(exception)
9893.10.5 by Stuart Bishop
delint
172
        time.sleep(0.6)  # Stats only updated every 500ms.
8137.13.3 by Stuart Bishop
Add some instrumentation to pgmassacre.py creation
173
        now = time.time()
174
    con.close()
8137.13.1 by Stuart Bishop
Option for pgmassacre.py to rebuild the database it just destroyed from a template
175
176
    print >> sys.stderr, "Unable to recreate database: %s" % error_msg
177
    return 11
178
179
8137.13.3 by Stuart Bishop
Add some instrumentation to pgmassacre.py creation
180
def report_open_connections(database):
181
    con = connect()
182
    cur = con.cursor()
183
    cur.execute("""
184
        SELECT usename, datname, count(*)
185
        FROM pg_stat_activity
186
        WHERE procpid != pg_backend_pid()
187
        GROUP BY usename, datname
188
        ORDER BY datname, usename
189
        """, [database])
190
    for usename, datname, num_connections in cur.fetchall():
191
        print >> sys.stderr, "%d connections by %s to %s" % (
192
            num_connections, usename, datname)
193
    con.close()
194
195
8137.13.1 by Stuart Bishop
Option for pgmassacre.py to rebuild the database it just destroyed from a template
196
options = None
197
9893.10.5 by Stuart Bishop
delint
198
8137.13.1 by Stuart Bishop
Option for pgmassacre.py to rebuild the database it just destroyed from a template
199
def main():
8137.13.2 by Stuart Bishop
Improve usage string
200
    parser = OptionParser("Usage: %prog [options] DBNAME")
8137.13.1 by Stuart Bishop
Option for pgmassacre.py to rebuild the database it just destroyed from a template
201
    parser.add_option("-U", "--user", dest="user", default=None,
202
        help="Connect as USER", metavar="USER")
203
    parser.add_option("-t", "--template", dest="template", default=None,
9893.10.3 by Stuart Bishop
Ensure pgmassacre does not put the database cluster into recovery mode, and remove support for PostgreSQL versions earlier than 8.4
204
        help="Recreate database using DBNAME as a template database."
205
            " If template0, database will be created in the C locale.",
8137.13.1 by Stuart Bishop
Option for pgmassacre.py to rebuild the database it just destroyed from a template
206
        metavar="DBNAME")
207
    global options
208
    (options, args) = parser.parse_args()
209
210
    if len(args) != 1:
211
        parser.error('Must specify one, and only one, database to destroy')
212
213
    database = args[0]
214
215
    # Don't be stupid protection.
216
    if database in ('template1', 'template0'):
8137.13.5 by Stuart Bishop
Tweak error message
217
        parser.error(
218
            "Running this script against template1 or template0 is nuts.")
8137.13.1 by Stuart Bishop
Option for pgmassacre.py to rebuild the database it just destroyed from a template
219
220
    con = connect()
221
    cur = con.cursor()
7675.357.8 by Stuart Bishop
Make database creation compatible with both PG 8.3 and PG 8.4
222
8137.13.1 by Stuart Bishop
Option for pgmassacre.py to rebuild the database it just destroyed from a template
223
    # Ensure the template database exists.
224
    if options.template is not None:
225
        cur.execute(
226
            "SELECT TRUE FROM pg_database WHERE datname=%s",
227
            [options.template])
228
        if cur.fetchone() is None:
229
            parser.error(
230
                "Template database %s does not exist." % options.template)
231
    # If the database doesn't exist, no point attempting to drop it.
232
    cur.execute("SELECT TRUE FROM pg_database WHERE datname=%s", [database])
233
    db_exists = cur.fetchone() is not None
234
    con.close()
235
236
    if db_exists:
237
        rv = massacre(database)
238
        if rv != 0:
8137.13.3 by Stuart Bishop
Add some instrumentation to pgmassacre.py creation
239
            print >> sys.stderr, "Fail %d" % rv
8137.13.1 by Stuart Bishop
Option for pgmassacre.py to rebuild the database it just destroyed from a template
240
            return rv
241
242
    if options.template is not None:
243
        return rebuild(database, options.template)
244
    else:
245
        return 0
5282.4.2 by Stuart Bishop
Make pgpassacre.py rollback outstanding prepared transactions to avoid 'still being accessed by other users' error.
246
3432.1.1 by Stuart Bishop
Update pgmassacre to work as unix user other than 'postgres'
247
248
if __name__ == '__main__':
249
    sys.exit(main())