~launchpad-pqm/launchpad/devel

1876 by Canonical.com Patch Queue Manager
[r=jamesh] database dump script
1
#!/usr/bin/env python
2
"""
3
Backup one or more PostgreSQL databases.
4
Suitable for use in crontab for daily backups.
5
"""
6
7
import sys
8
import os
9
import os.path
10
import stat
11
import subprocess
12
import logging
13
from datetime import datetime
14
from optparse import OptionParser
15
16
MB = float(1024*1024)
17
18
return_code = 0 # Return code of this script. Set to the most recent failed
19
                # system call's return code
20
21
def call(cmd, **kw):
22
    log.debug(' '.join(cmd))
23
    rv = subprocess.call(cmd, **kw)
24
    if rv != 0:
25
        global return_code
26
        return_code = rv
27
    return rv
28
29
def main(options, databases):
30
    #Need longer file names if this is used more than daily
31
    #today = datetime.now().strftime('%Y%m%d_%H:%M:%S')
32
    today = datetime.now().strftime('%Y%m%d')
33
34
    backup_dir = options.backup_dir
35
 
36
    for database in databases:
37
        dest =  os.path.join(backup_dir, '%s.%s.dump' % (database, today))
38
        cmd = [
39
            "/usr/bin/pg_dump",
40
            "-U", "postgres",
41
            "--format=c",
42
            "--compress=0",
43
            "--blobs",
44
            "--file=%s" % dest,
45
            database,
46
            ]
47
        # If the file already exists, it was from a dump that didn't
48
        # complete (because completed dumps are renamed during compression).
49
        # Remove it.
50
        if os.path.exists(dest):
51
            log.warn("%s already exists. Removing." % dest)
52
            os.unlink(dest)
53
        rv = call(cmd, stdin=subprocess.PIPE)
54
        if rv != 0:
55
            log.critical("Failed to backup %s (%d)" % (database, rv))
56
            continue
57
        size = os.stat(dest)[stat.ST_SIZE]
58
59
        bzdest = "%s.bz2" % dest
60
        # If the file already exists, it is from an older dump today.
61
        # We know we have a full, current dump so kill the old compressed one.
62
        if os.path.exists(bzdest):
63
            log.warn("%s already exists. Removing." % bzdest)
64
            os.unlink(bzdest)
65
        cmd = ["/usr/bin/bzip2", "-9", dest]
66
        rv = call(cmd, stdin=subprocess.PIPE)
67
        if rv != 0:
68
            log.critical("Failed to compress %s (%d)" % (database, rv))
69
            continue
70
        csize = os.stat(bzdest)[stat.ST_SIZE]
71
72
        log.info("Backed up %s (%0.2fMB/%0.2fMB)" % (
73
            database, size/MB, csize/MB,
74
            ))
75
76
if __name__ == '__main__':
77
    parser = OptionParser(
78
            usage="usage: %prog [options] database [database ..]"
79
            )
80
    parser.add_option("-v", "--verbose", dest="verbose", default=0,
81
            action="count")
82
    parser.add_option("-q", "--quiet", dest="quiet", default=0,
83
            action="count")
84
    parser.add_option("-d", "--dir", dest="backup_dir",
85
            default="/var/lib/postgres/backups")
86
    (options, databases) = parser.parse_args()
87
    if len(databases) == 0:
88
        parser.error("must specify at least one database")
89
    if not os.path.isdir(options.backup_dir):
90
        parser.error(
91
                "Incorrect --dir. %s does not exist or is not a directory" % (
92
                    options.backup_dir
93
                    )
94
                )
95
96
    # Setup our log
97
    log = logging.getLogger('pgbackup')
98
    hdlr = logging.StreamHandler(strm=sys.stderr)
99
    hdlr.setFormatter(logging.Formatter(
100
            fmt='%(asctime)s %(levelname)s %(message)s'
101
            ))
102
    log.addHandler(hdlr)
103
    verbosity = options.verbose - options.quiet
104
    if verbosity > 0:
105
        log.setLevel(logging.DEBUG)
106
    elif verbosity == 0: # Default
107
        log.setLevel(logging.INFO)
108
    elif verbosity == -1:
109
        log.setLevel(logging.WARN)
110
    elif verbosity < -1:
111
        log.setLevel(logging.ERROR)
112
113
    main(options, databases)
114
    sys.exit(return_code)