1876
by Canonical.com Patch Queue Manager
[r=jamesh] database dump script |
1 |
#!/usr/bin/env python
|
2 |
"""
|
|
3 |
dropdb only more so.
|
|
4 |
||
5 |
Cut off access, slaughter connections and burn the database to the ground.
|
|
6 |
"""
|
|
7 |
||
8 |
import os |
|
9 |
import sys |
|
10 |
import time |
|
11 |
import psycopg |
|
3432.1.1
by Stuart Bishop
Update pgmassacre to work as unix user other than 'postgres' |
12 |
from signal import SIGTERM, SIGQUIT, SIGKILL, SIGINT |
3432.1.2
by Stuart Bishop
pgmassacre should connect as default db user |
13 |
from optparse import OptionParser |
3432.1.1
by Stuart Bishop
Update pgmassacre to work as unix user other than 'postgres' |
14 |
|
15 |
||
16 |
def connect(): |
|
3432.1.2
by Stuart Bishop
pgmassacre should connect as default db user |
17 |
if options.user is not None: |
18 |
return psycopg.connect("dbname=template1 user=%s" % options.user) |
|
19 |
else: |
|
20 |
return psycopg.connect("dbname=template1") |
|
3432.1.1
by Stuart Bishop
Update pgmassacre to work as unix user other than 'postgres' |
21 |
|
22 |
||
23 |
def send_signal(database, signal): |
|
24 |
con = connect() |
|
25 |
con.set_isolation_level(1) |
|
26 |
cur = con.cursor() |
|
27 |
||
28 |
# Install PL/PythonU if it isn't already
|
|
29 |
cur.execute("SELECT TRUE FROM pg_language WHERE lanname = 'plpythonu'") |
|
30 |
if cur.fetchone() is None: |
|
31 |
cur.execute('CREATE LANGUAGE "plpythonu"') |
|
32 |
||
33 |
# Create a stored procedure to kill a backend process
|
|
34 |
qdatabase = str(psycopg.QuotedString(database)) |
|
35 |
cur.execute(""" |
|
36 |
CREATE OR REPLACE FUNCTION _pgmassacre_killall(integer)
|
|
37 |
RETURNS Boolean AS $$
|
|
38 |
import os
|
|
39 |
||
40 |
signal = args[0]
|
|
41 |
for row in plpy.execute(
|
|
42 |
"SELECT procpid FROM pg_stat_activity WHERE datname=%(qdatabase)s" |
|
43 |
):
|
|
44 |
try:
|
|
45 |
os.kill(row['procpid'], signal)
|
|
46 |
except OSError:
|
|
47 |
pass
|
|
48 |
else:
|
|
49 |
return False
|
|
50 |
||
51 |
return True
|
|
52 |
$$ LANGUAGE plpythonu
|
|
53 |
""" % vars()) |
|
54 |
||
55 |
cur.execute("SELECT _pgmassacre_killall(%(signal)s)", vars()) |
|
56 |
con.rollback() |
|
57 |
con.close() |
|
58 |
||
59 |
||
60 |
def still_open(database): |
|
61 |
"""Return True if there are still open connections. Waits a while
|
|
62 |
to ensure that connections shutting down have a chance to.
|
|
63 |
"""
|
|
64 |
con = connect() |
|
65 |
con.set_isolation_level(1) |
|
66 |
cur = con.cursor() |
|
67 |
# Wait for up to 10 seconds, returning True if all backends are gone.
|
|
68 |
start = time.time() |
|
69 |
while time.time() < start + 10: |
|
70 |
cur.execute(""" |
|
71 |
SELECT procpid FROM pg_stat_activity
|
|
72 |
WHERE datname=%(database)s LIMIT 1 |
|
73 |
""", vars()) |
|
74 |
if cur.fetchone() is None: |
|
75 |
return False |
|
76 |
time.sleep(0.6) # Stats only updated every 500ms |
|
77 |
con.rollback() |
|
78 |
con.close() |
|
79 |
return True |
|
80 |
||
3432.1.2
by Stuart Bishop
pgmassacre should connect as default db user |
81 |
options = None |
3432.1.1
by Stuart Bishop
Update pgmassacre to work as unix user other than 'postgres' |
82 |
|
83 |
def main(): |
|
3432.1.2
by Stuart Bishop
pgmassacre should connect as default db user |
84 |
parser = OptionParser() |
85 |
parser.add_option("-U", "--user", dest="user", default=None, |
|
86 |
help="Connect as USER", metavar="USER", |
|
87 |
)
|
|
88 |
global options |
|
89 |
(options, args) = parser.parse_args() |
|
90 |
||
91 |
if len(args) != 1: |
|
3432.1.1
by Stuart Bishop
Update pgmassacre to work as unix user other than 'postgres' |
92 |
print >> sys.stderr, \ |
93 |
'Must specify one, and only one, database to destroy'
|
|
94 |
sys.exit(1) |
|
95 |
||
3432.1.2
by Stuart Bishop
pgmassacre should connect as default db user |
96 |
database = args[0] |
3432.1.1
by Stuart Bishop
Update pgmassacre to work as unix user other than 'postgres' |
97 |
|
98 |
if database in ('template1', 'template0'): |
|
99 |
print >> sys.stderr, "Put the gun down and back away from the vehicle!" |
|
100 |
return 666 |
|
101 |
||
102 |
con = connect() |
|
103 |
||
104 |
cur = con.cursor() |
|
105 |
||
106 |
# Ensure the database exists. Note that the script returns success
|
|
107 |
# in this case to ease scripting.
|
|
108 |
cur.execute("SELECT count(*) FROM pg_database WHERE datname=%s", [database]) |
|
109 |
if cur.fetchone()[0] == 0: |
|
110 |
print >> sys.stderr, \ |
|
111 |
"%s has fled the building. Database does not exist" % database |
|
112 |
return 0 |
|
113 |
||
114 |
# Stop connetions to the doomed database
|
|
115 |
cur.execute( |
|
116 |
"UPDATE pg_database SET datallowconn=false WHERE datname=%s", [database] |
|
1876
by Canonical.com Patch Queue Manager
[r=jamesh] database dump script |
117 |
)
|
118 |
||
3432.1.1
by Stuart Bishop
Update pgmassacre to work as unix user other than 'postgres' |
119 |
con.commit() |
120 |
con.close() |
|
121 |
||
122 |
# Terminate current statements
|
|
123 |
send_signal(database, SIGINT) |
|
124 |
||
125 |
# Shutdown current connections normally
|
|
126 |
send_signal(database, SIGTERM) |
|
127 |
||
128 |
# Shutdown current connections immediately
|
|
129 |
if still_open(database): |
|
130 |
send_signal(database, SIGQUIT) |
|
131 |
||
132 |
# Shutdown current connections nastily
|
|
133 |
if still_open(database): |
|
134 |
send_signal(database, SIGKILL) |
|
135 |
||
136 |
if still_open(database): |
|
137 |
print >> sys.stderr, \ |
|
138 |
"Unable to kill all backends! Database not destroyed."
|
|
139 |
return 9 |
|
140 |
||
141 |
# Destroy the database
|
|
142 |
con = connect() |
|
143 |
con.set_isolation_level(0) # Required to execute commands like DROP DATABASE |
|
144 |
cur = con.cursor() |
|
145 |
cur.execute("DROP DATABASE %s" % database) # Not quoted |
|
146 |
return 0 |
|
147 |
||
148 |
# print "Mwahahahaha!"
|
|
149 |
||
150 |
if __name__ == '__main__': |
|
151 |
sys.exit(main()) |