~launchpad-pqm/launchpad/devel

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