~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
#
8687.15.2 by Karl Fogel
In files modified by r8688, change "<YEARS>" to "2009", as per
3
# Copyright 2009 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
1876 by Canonical.com Patch Queue Manager
[r=jamesh] database dump script
6
"""
7
dropdb only more so.
8
9
Cut off access, slaughter connections and burn the database to the ground.
10
"""
11
8137.13.1 by Stuart Bishop
Option for pgmassacre.py to rebuild the database it just destroyed from a template
12
# Nothing but system installed libraries - this script sometimes
13
# gets installed standalone with no Launchpad tree available.
7675.357.8 by Stuart Bishop
Make database creation compatible with both PG 8.3 and PG 8.4
14
from distutils.version import LooseVersion
1876 by Canonical.com Patch Queue Manager
[r=jamesh] database dump script
15
import sys
16
import time
5821.2.85 by James Henstridge
Add "make check_launchpad_storm_on_merge" target that runs the tests
17
import psycopg2
18
import psycopg2.extensions
3432.1.1 by Stuart Bishop
Update pgmassacre to work as unix user other than 'postgres'
19
from signal import SIGTERM, SIGQUIT, SIGKILL, SIGINT
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
31
def send_signal(database, signal):
32
    con = connect()
5282.4.2 by Stuart Bishop
Make pgpassacre.py rollback outstanding prepared transactions to avoid 'still being accessed by other users' error.
33
    con.set_isolation_level(1) # READ COMMITTED. We rollback changes we make.
3432.1.1 by Stuart Bishop
Update pgmassacre to work as unix user other than 'postgres'
34
    cur = con.cursor()
35
5282.4.3 by Stuart Bishop
Review feedback updates
36
    # Install PL/PythonU if it isn't already.
3432.1.1 by Stuart Bishop
Update pgmassacre to work as unix user other than 'postgres'
37
    cur.execute("SELECT TRUE FROM pg_language WHERE lanname = 'plpythonu'")
38
    if cur.fetchone() is None:
39
        cur.execute('CREATE LANGUAGE "plpythonu"')
40
5282.4.3 by Stuart Bishop
Review feedback updates
41
    # Create a stored procedure to kill a backend process.
5821.2.85 by James Henstridge
Add "make check_launchpad_storm_on_merge" target that runs the tests
42
    qdatabase = str(psycopg2.extensions.QuotedString(database))
3432.1.1 by Stuart Bishop
Update pgmassacre to work as unix user other than 'postgres'
43
    cur.execute("""
44
        CREATE OR REPLACE FUNCTION _pgmassacre_killall(integer)
45
        RETURNS Boolean AS $$
46
        import os
47
48
        signal = args[0]
3691.222.6 by Stuart Bishop
pgmassacre should not shoot itself in the foot occasionally
49
        for row in plpy.execute('''
50
            SELECT procpid FROM pg_stat_activity WHERE datname=%(qdatabase)s
51
                AND procpid != pg_backend_pid()
52
            '''):
3432.1.1 by Stuart Bishop
Update pgmassacre to work as unix user other than 'postgres'
53
            try:
54
                os.kill(row['procpid'], signal)
55
            except OSError:
56
                pass
57
        else:
58
            return False
59
60
        return True
61
        $$ LANGUAGE plpythonu
62
        """ % vars())
63
64
    cur.execute("SELECT _pgmassacre_killall(%(signal)s)", vars())
65
    con.rollback()
66
    con.close()
67
68
5282.4.2 by Stuart Bishop
Make pgpassacre.py rollback outstanding prepared transactions to avoid 'still being accessed by other users' error.
69
def rollback_prepared_transactions(database):
5282.4.3 by Stuart Bishop
Review feedback updates
70
    """Rollback any prepared transactions.
71
8137.13.1 by Stuart Bishop
Option for pgmassacre.py to rebuild the database it just destroyed from a template
72
    PostgreSQL will refuse to drop a database with outstanding prepared
73
    transactions.
5282.4.3 by Stuart Bishop
Review feedback updates
74
    """
5282.4.2 by Stuart Bishop
Make pgpassacre.py rollback outstanding prepared transactions to avoid 'still being accessed by other users' error.
75
    con = connect(database)
76
    con.set_isolation_level(0) # Autocommit so we can ROLLBACK PREPARED.
77
    cur = con.cursor()
78
79
    # Get a list of outstanding prepared transactions.
80
    cur.execute(
81
            "SELECT gid FROM pg_prepared_xacts WHERE database=%(database)s",
5282.4.4 by Stuart Bishop
Update to standard 'icky parenthesis style
82
            vars())
5282.4.2 by Stuart Bishop
Make pgpassacre.py rollback outstanding prepared transactions to avoid 'still being accessed by other users' error.
83
    xids = [row[0] for row in cur.fetchall()]
84
    for xid in xids:
85
        cur.execute("ROLLBACK PREPARED %(xid)s", vars())
86
    con.close()
87
88
8137.13.1 by Stuart Bishop
Option for pgmassacre.py to rebuild the database it just destroyed from a template
89
def still_open(database, max_wait=10):
90
    """Return True if there are still open connections, apart from our own.
91
5282.4.3 by Stuart Bishop
Review feedback updates
92
    Waits a while to ensure that connections shutting down have a chance to.
3432.1.1 by Stuart Bishop
Update pgmassacre to work as unix user other than 'postgres'
93
    """
94
    con = connect()
8137.13.3 by Stuart Bishop
Add some instrumentation to pgmassacre.py creation
95
    con.set_isolation_level(0) # Autocommit.
3432.1.1 by Stuart Bishop
Update pgmassacre to work as unix user other than 'postgres'
96
    cur = con.cursor()
97
    # Wait for up to 10 seconds, returning True if all backends are gone.
98
    start = time.time()
8137.13.1 by Stuart Bishop
Option for pgmassacre.py to rebuild the database it just destroyed from a template
99
    while time.time() < start + max_wait:
3432.1.1 by Stuart Bishop
Update pgmassacre to work as unix user other than 'postgres'
100
        cur.execute("""
8137.13.3 by Stuart Bishop
Add some instrumentation to pgmassacre.py creation
101
            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
102
            WHERE
103
                datname=%(database)s
104
                AND procpid != pg_backend_pid()
105
            LIMIT 1
3432.1.1 by Stuart Bishop
Update pgmassacre to work as unix user other than 'postgres'
106
            """, vars())
107
        if cur.fetchone() is None:
108
            return False
5282.4.3 by Stuart Bishop
Review feedback updates
109
        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'
110
    con.close()
111
    return True
112
5282.4.3 by Stuart Bishop
Review feedback updates
113
8137.13.1 by Stuart Bishop
Option for pgmassacre.py to rebuild the database it just destroyed from a template
114
def massacre(database):
3432.1.1 by Stuart Bishop
Update pgmassacre to work as unix user other than 'postgres'
115
    con = connect()
5529.1.5 by Stuart Bishop
Make pgmassacre.py a little more bulletproof
116
    con.set_isolation_level(0) # Autocommit
3432.1.1 by Stuart Bishop
Update pgmassacre to work as unix user other than 'postgres'
117
    cur = con.cursor()
118
5529.1.5 by Stuart Bishop
Make pgmassacre.py a little more bulletproof
119
    # Allow connections to the doomed database if something turned this off,
120
    # such as an aborted run of this script.
121
    cur.execute(
122
        "UPDATE pg_database SET datallowconn=TRUE WHERE datname=%s",
123
        [database])
124
5282.4.2 by Stuart Bishop
Make pgpassacre.py rollback outstanding prepared transactions to avoid 'still being accessed by other users' error.
125
    # Rollback prepared transactions.
126
    rollback_prepared_transactions(database)
127
128
    try:
5282.4.3 by Stuart Bishop
Review feedback updates
129
        # 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.
130
        cur.execute(
5529.1.5 by Stuart Bishop
Make pgmassacre.py a little more bulletproof
131
            "UPDATE pg_database SET datallowconn=FALSE WHERE datname=%s",
5282.4.4 by Stuart Bishop
Update to standard 'icky parenthesis style
132
            [database])
5282.4.2 by Stuart Bishop
Make pgpassacre.py rollback outstanding prepared transactions to avoid 'still being accessed by other users' error.
133
134
        con.close()
135
5282.4.3 by Stuart Bishop
Review feedback updates
136
        # Terminate current statements.
5282.4.2 by Stuart Bishop
Make pgpassacre.py rollback outstanding prepared transactions to avoid 'still being accessed by other users' error.
137
        send_signal(database, SIGINT)
138
5282.4.3 by Stuart Bishop
Review feedback updates
139
        # Shutdown current connections normally.
8137.13.1 by Stuart Bishop
Option for pgmassacre.py to rebuild the database it just destroyed from a template
140
        if still_open(database, 1):
141
            send_signal(database, SIGTERM)
5282.4.2 by Stuart Bishop
Make pgpassacre.py rollback outstanding prepared transactions to avoid 'still being accessed by other users' error.
142
5282.4.3 by Stuart Bishop
Review feedback updates
143
        # Shutdown current connections immediately.
5282.4.2 by Stuart Bishop
Make pgpassacre.py rollback outstanding prepared transactions to avoid 'still being accessed by other users' error.
144
        if still_open(database):
145
            send_signal(database, SIGQUIT)
146
5282.4.3 by Stuart Bishop
Review feedback updates
147
        # Shutdown current connections nastily.
5282.4.2 by Stuart Bishop
Make pgpassacre.py rollback outstanding prepared transactions to avoid 'still being accessed by other users' error.
148
        if still_open(database):
149
            send_signal(database, SIGKILL)
150
151
        if still_open(database):
5282.4.3 by Stuart Bishop
Review feedback updates
152
            print >> sys.stderr, (
5282.4.4 by Stuart Bishop
Update to standard 'icky parenthesis style
153
                    "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.
154
            return 9
155
5282.4.3 by Stuart Bishop
Review feedback updates
156
        # 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.
157
        con = connect()
5282.4.3 by Stuart Bishop
Review feedback updates
158
        # AUTOCOMMIT required to execute commands like DROP DATABASE.
159
        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.
160
        cur = con.cursor()
5282.4.3 by Stuart Bishop
Review feedback updates
161
        cur.execute("DROP DATABASE %s" % database) # Not quoted.
8137.13.3 by Stuart Bishop
Add some instrumentation to pgmassacre.py creation
162
        con.close()
163
        return 0
5282.4.2 by Stuart Bishop
Make pgpassacre.py rollback outstanding prepared transactions to avoid 'still being accessed by other users' error.
164
    finally:
165
        # In case something messed up, allow connections again so we can
166
        # inspect the damage.
167
        con = connect()
168
        con.set_isolation_level(0)
169
        cur = con.cursor()
170
        cur.execute(
171
                "UPDATE pg_database SET datallowconn=TRUE WHERE datname=%s",
5282.4.4 by Stuart Bishop
Update to standard 'icky parenthesis style
172
                [database])
8137.13.1 by Stuart Bishop
Option for pgmassacre.py to rebuild the database it just destroyed from a template
173
        con.close()
174
175
176
def rebuild(database, template):
177
    if still_open(template, 20):
178
        print >> sys.stderr, (
179
            "Giving up waiting for connections to %s to drop." % template)
8137.13.3 by Stuart Bishop
Add some instrumentation to pgmassacre.py creation
180
        report_open_connections(template)
8137.13.1 by Stuart Bishop
Option for pgmassacre.py to rebuild the database it just destroyed from a template
181
        return 10
182
183
    start = time.time()
8137.13.3 by Stuart Bishop
Add some instrumentation to pgmassacre.py creation
184
    now = start
8137.13.1 by Stuart Bishop
Option for pgmassacre.py to rebuild the database it just destroyed from a template
185
    error_msg = None
8137.13.3 by Stuart Bishop
Add some instrumentation to pgmassacre.py creation
186
    con = connect()
187
    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
188
    create_db_cmd = """
189
        CREATE DATABASE %s WITH ENCODING='UTF8' TEMPLATE=%s
190
        """ % (database, template)
191
    # 8.4 allows us to create empty databases with a different locale
192
    # to template1 by using the template0 database as a template.
193
    # We make use of this feature so we don't have to care what locale
194
    # was used to create the database cluster rather than requiring it
195
    # to be rebuilt in the C locale.
196
    if pg_version >= LooseVersion("8.4.0") and template == "template0":
197
        create_db_cmd += "LC_COLLATE='C' LC_CTYPE='C'"
8137.13.3 by Stuart Bishop
Add some instrumentation to pgmassacre.py creation
198
    while now < start + 20:
8137.13.1 by Stuart Bishop
Option for pgmassacre.py to rebuild the database it just destroyed from a template
199
        cur = con.cursor()
200
        try:
7675.357.8 by Stuart Bishop
Make database creation compatible with both PG 8.3 and PG 8.4
201
            cur.execute(create_db_cmd)
8137.13.3 by Stuart Bishop
Add some instrumentation to pgmassacre.py creation
202
            con.close()
8137.13.1 by Stuart Bishop
Option for pgmassacre.py to rebuild the database it just destroyed from a template
203
            return 0
204
        except psycopg2.Error, exception:
205
            error_msg = str(exception)
206
        time.sleep(0.6) # Stats only updated every 500ms.
8137.13.3 by Stuart Bishop
Add some instrumentation to pgmassacre.py creation
207
        now = time.time()
208
    con.close()
8137.13.1 by Stuart Bishop
Option for pgmassacre.py to rebuild the database it just destroyed from a template
209
210
    print >> sys.stderr, "Unable to recreate database: %s" % error_msg
211
    return 11
212
213
8137.13.3 by Stuart Bishop
Add some instrumentation to pgmassacre.py creation
214
def report_open_connections(database):
215
    con = connect()
216
    cur = con.cursor()
217
    cur.execute("""
218
        SELECT usename, datname, count(*)
219
        FROM pg_stat_activity
220
        WHERE procpid != pg_backend_pid()
221
        GROUP BY usename, datname
222
        ORDER BY datname, usename
223
        """, [database])
224
    for usename, datname, num_connections in cur.fetchall():
225
        print >> sys.stderr, "%d connections by %s to %s" % (
226
            num_connections, usename, datname)
227
    con.close()
228
229
8137.13.1 by Stuart Bishop
Option for pgmassacre.py to rebuild the database it just destroyed from a template
230
options = None
7675.357.8 by Stuart Bishop
Make database creation compatible with both PG 8.3 and PG 8.4
231
pg_version = None # LooseVersion - Initialized in main()
8137.13.1 by Stuart Bishop
Option for pgmassacre.py to rebuild the database it just destroyed from a template
232
233
234
def main():
8137.13.2 by Stuart Bishop
Improve usage string
235
    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
236
    parser.add_option("-U", "--user", dest="user", default=None,
237
        help="Connect as USER", metavar="USER")
238
    parser.add_option("-t", "--template", dest="template", default=None,
239
        help="Recreate database using DBNAME as a template database.",
240
        metavar="DBNAME")
241
    global options
242
    (options, args) = parser.parse_args()
243
244
    if len(args) != 1:
245
        parser.error('Must specify one, and only one, database to destroy')
246
247
    database = args[0]
248
249
    # Don't be stupid protection.
250
    if database in ('template1', 'template0'):
8137.13.5 by Stuart Bishop
Tweak error message
251
        parser.error(
252
            "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
253
254
    con = connect()
255
    cur = con.cursor()
7675.357.8 by Stuart Bishop
Make database creation compatible with both PG 8.3 and PG 8.4
256
257
    # Store the database version for version specific code.
258
    global pg_version
259
    cur.execute("show server_version")
260
    pg_version = LooseVersion(cur.fetchone()[0])
261
8137.13.1 by Stuart Bishop
Option for pgmassacre.py to rebuild the database it just destroyed from a template
262
    # Ensure the template database exists.
263
    if options.template is not None:
264
        cur.execute(
265
            "SELECT TRUE FROM pg_database WHERE datname=%s",
266
            [options.template])
267
        if cur.fetchone() is None:
268
            parser.error(
269
                "Template database %s does not exist." % options.template)
270
    # If the database doesn't exist, no point attempting to drop it.
271
    cur.execute("SELECT TRUE FROM pg_database WHERE datname=%s", [database])
272
    db_exists = cur.fetchone() is not None
273
    con.close()
274
275
    if db_exists:
276
        rv = massacre(database)
277
        if rv != 0:
8137.13.3 by Stuart Bishop
Add some instrumentation to pgmassacre.py creation
278
            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
279
            return rv
280
281
    if options.template is not None:
282
        return rebuild(database, options.template)
283
    else:
284
        return 0
5282.4.2 by Stuart Bishop
Make pgpassacre.py rollback outstanding prepared transactions to avoid 'still being accessed by other users' error.
285
3432.1.1 by Stuart Bishop
Update pgmassacre to work as unix user other than 'postgres'
286
287
if __name__ == '__main__':
288
    sys.exit(main())