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()) |