~launchpad-pqm/launchpad/devel

7675.395.197 by Stuart Bishop
Full database update script for fast deployments
1
#!/usr/bin/python2.6 -S
2
# Copyright 2011 Canonical Ltd.  This software is licensed under the
3
# GNU Affero General Public License version 3 (see the file LICENSE).
4
5
"""Full update process."""
6
7
import _pythonpath
8
13465.2.29 by Stuart Bishop
Report outage time
9
from datetime import datetime
7675.395.197 by Stuart Bishop
Full database update script for fast deployments
10
from optparse import OptionParser
11
import subprocess
12
import sys
13
14565.2.15 by Curtis Hovey
Moved canonical.launchpad.scripts __init__ to lp.services.scripts.
14
from lp.services.scripts import (
7675.395.197 by Stuart Bishop
Full database update script for fast deployments
15
    db_options,
13465.2.5 by Stuart Bishop
Integrate full-update.py with local pgbouncer
16
    logger,
7675.395.197 by Stuart Bishop
Full database update script for fast deployments
17
    logger_options,
18
    )
13465.2.10 by Stuart Bishop
Inline preflight checks
19
from preflight import (
20
    KillConnectionsPreflight,
21
    NoConnectionCheckPreflight,
22
    )
13465.2.16 by Stuart Bishop
Run upgrade.py in-process to save startup overhead
23
import security  # security.py script
24
import upgrade  # upgrade.py script
13465.2.10 by Stuart Bishop
Inline preflight checks
25
7675.395.197 by Stuart Bishop
Full database update script for fast deployments
26
13465.2.5 by Stuart Bishop
Integrate full-update.py with local pgbouncer
27
PGBOUNCER_INITD = ['sudo', '/etc/init.d/pgbouncer']
28
29
13465.2.17 by Stuart Bishop
Bring pgbouncer backup even if some database update steps failed
30
def run_pgbouncer(log, cmd):
31
    """Invoke the pgbouncer initscript.
32
33
    :param cmd: One of 'start', 'stop' or 'status'.
34
    """
13465.2.21 by Stuart Bishop
Review feedback
35
    assert cmd in ('start', 'stop', 'status'), '''
36
        Unrecognized command; remember any new commands need to be
37
        granted sudo on staging and prod.
38
        '''
13465.2.17 by Stuart Bishop
Bring pgbouncer backup even if some database update steps failed
39
    pgbouncer_rc = subprocess.call(PGBOUNCER_INITD + [cmd])
40
    sys.stdout.flush()
41
    if pgbouncer_rc != 0:
42
        log.error("pgbouncer '%s' failed [%s]", cmd, pgbouncer_rc)
43
    return pgbouncer_rc
44
45
13465.2.16 by Stuart Bishop
Run upgrade.py in-process to save startup overhead
46
def run_upgrade(options, log):
47
    """Invoke upgrade.py in-process.
48
49
    It would be easier to just invoke the script, but this way we save
50
    several seconds of overhead as the component architecture loads up.
51
    """
52
    # Fake expected command line arguments and global log
53
    options.commit = True
54
    options.partial = False
55
    upgrade.options = options
56
    upgrade.log = log
57
    # Invoke the database schema upgrade process.
58
    try:
59
        return upgrade.main()
60
    except Exception:
61
        log.exception('Unhandled exception')
62
        return 1
63
    except SystemExit, x:
64
        log.fatal("upgrade.py failed [%s]", x)
65
66
13465.2.15 by Stuart Bishop
Invoke security.py in-process to save startup overhead
67
def run_security(options, log):
68
    """Invoke security.py in-process.
69
70
    It would be easier to just invoke the script, but this way we save
71
    several seconds of overhead as the component architecture loads up.
72
    """
73
    # Fake expected command line arguments and global log
74
    options.dryrun = False
75
    options.revoke = True
76
    options.owner = 'postgres'
77
    options.cluster = True
78
    security.options = options
79
    security.log = log
80
    # Invoke the database security reset process.
81
    try:
82
        return security.main(options)
83
    except Exception:
84
        log.exception('Unhandled exception')
85
        return 1
13465.2.16 by Stuart Bishop
Run upgrade.py in-process to save startup overhead
86
    except SystemExit, x:
87
        log.fatal("security.py failed [%s]", x)
13465.2.15 by Stuart Bishop
Invoke security.py in-process to save startup overhead
88
89
7675.395.197 by Stuart Bishop
Full database update script for fast deployments
90
def main():
91
    parser = OptionParser()
92
93
    # Add all the command command line arguments.
94
    db_options(parser)
95
    logger_options(parser)
96
    (options, args) = parser.parse_args()
97
    if args:
98
        parser.error("Too many arguments")
99
13465.2.5 by Stuart Bishop
Integrate full-update.py with local pgbouncer
100
    log = logger(options)
101
102
    #
103
    # Preflight checks. Confirm as best we can that the upgrade will
104
    # work unattended.
105
    #
106
13465.2.36 by Stuart Bishop
Retry until connections in need of termination die, although this shouldn't happen as killable connections should be connecting via pgbouncer (because we can't stop them reconnecting)
107
    # Confirm we can invoke PGBOUNCER_INITD
108
    log.debug("Confirming sudo access to pgbouncer startup script")
109
    pgbouncer_rc = run_pgbouncer(log, 'status')
110
    if pgbouncer_rc != 0:
111
        return pgbouncer_rc
112
13465.2.5 by Stuart Bishop
Integrate full-update.py with local pgbouncer
113
    # We initially ignore open connections, as they will shortly be
114
    # killed.
13465.2.10 by Stuart Bishop
Inline preflight checks
115
    if not NoConnectionCheckPreflight(log).check_all():
116
        return 99
7675.395.197 by Stuart Bishop
Full database update script for fast deployments
117
13465.2.5 by Stuart Bishop
Integrate full-update.py with local pgbouncer
118
    #
119
    # Start the actual upgrade. Failures beyond this point need to
120
    # generate informative messages to help with recovery.
121
    #
122
123
    # status flags
124
    pgbouncer_down = False
125
    upgrade_run = False
126
    security_run = False
127
13465.2.29 by Stuart Bishop
Report outage time
128
    outage_start = datetime.now()
129
13465.2.5 by Stuart Bishop
Integrate full-update.py with local pgbouncer
130
    try:
131
        # Shutdown pgbouncer
13465.2.25 by Stuart Bishop
More status messages
132
        log.info("Outage starts. Shutting down pgbouncer.")
13465.2.17 by Stuart Bishop
Bring pgbouncer backup even if some database update steps failed
133
        pgbouncer_rc = run_pgbouncer(log, 'stop')
13465.2.5 by Stuart Bishop
Integrate full-update.py with local pgbouncer
134
        if pgbouncer_rc != 0:
135
            log.fatal("pgbouncer not shut down [%s]", pgbouncer_rc)
136
            return pgbouncer_rc
137
        pgbouncer_down = True
138
13465.2.10 by Stuart Bishop
Inline preflight checks
139
        if not KillConnectionsPreflight(log).check_all():
140
            return 100
13465.2.5 by Stuart Bishop
Integrate full-update.py with local pgbouncer
141
13465.2.26 by Stuart Bishop
More status updates to diagnose slow spots
142
        log.info("Preflight check succeeded. Starting upgrade.")
13465.2.16 by Stuart Bishop
Run upgrade.py in-process to save startup overhead
143
        upgrade_rc = run_upgrade(options, log)
13465.2.5 by Stuart Bishop
Integrate full-update.py with local pgbouncer
144
        if upgrade_rc != 0:
145
            return upgrade_rc
146
        upgrade_run = True
13465.2.26 by Stuart Bishop
More status updates to diagnose slow spots
147
        log.info("Database patches applied. Stored procedures updated.")
13465.2.5 by Stuart Bishop
Integrate full-update.py with local pgbouncer
148
13465.2.15 by Stuart Bishop
Invoke security.py in-process to save startup overhead
149
        security_rc = run_security(options, log)
13465.2.5 by Stuart Bishop
Integrate full-update.py with local pgbouncer
150
        if security_rc != 0:
151
            return security_rc
152
        security_run = True
153
154
        log.info("All database upgrade steps completed")
155
13465.2.25 by Stuart Bishop
More status messages
156
        log.info("Restarting pgbouncer")
13465.2.17 by Stuart Bishop
Bring pgbouncer backup even if some database update steps failed
157
        pgbouncer_rc = run_pgbouncer(log, 'start')
13465.2.5 by Stuart Bishop
Integrate full-update.py with local pgbouncer
158
        if pgbouncer_rc != 0:
159
            log.fatal("pgbouncer not restarted [%s]", pgbouncer_rc)
160
            return pgbouncer_rc
161
        pgbouncer_down = False
13465.2.29 by Stuart Bishop
Report outage time
162
        log.info("Outage complete. %s", datetime.now() - outage_start)
13465.2.5 by Stuart Bishop
Integrate full-update.py with local pgbouncer
163
13465.2.8 by Stuart Bishop
Systems reconnecting after the outage is good, so stop complaining
164
        # We will start seeing connections as soon as pgbouncer is
165
        # reenabled, so ignore them here.
13465.2.10 by Stuart Bishop
Inline preflight checks
166
        if not NoConnectionCheckPreflight(log).check_all():
167
            return 101
13465.2.5 by Stuart Bishop
Integrate full-update.py with local pgbouncer
168
169
        log.info("All good. All done.")
170
        return 0
171
172
    finally:
173
        if pgbouncer_down:
13465.2.17 by Stuart Bishop
Bring pgbouncer backup even if some database update steps failed
174
            # Even if upgrade.py or security.py failed, we should be in
175
            # a good enough state to continue operation so restart
176
            # pgbouncer and allow connections.
13465.2.18 by Stuart Bishop
delint
177
            #  - upgrade.py may have failed to update the master, and
13465.2.17 by Stuart Bishop
Bring pgbouncer backup even if some database update steps failed
178
            #    changes should have rolled back.
179
            #  - upgrade.py may have failed to update a slave, breaking
180
            #    replication. The master is still operational, but
181
            #    slaves may be lagging and have the old schema.
182
            #  - security.py may have died, rolling back its changes on
183
            #    one or more nodes.
184
            # In all cases except the first, we have recovery to do but
185
            # systems are probably ok, or at least providing some
186
            # services.
187
            pgbouncer_rc = run_pgbouncer(log, 'start')
188
            if pgbouncer_rc == 0:
189
                log.info("Despite failures, pgbouncer restarted.")
13465.2.29 by Stuart Bishop
Report outage time
190
                log.info("Outage complete. %s", datetime.now() - outage_start)
13465.2.17 by Stuart Bishop
Bring pgbouncer backup even if some database update steps failed
191
            else:
192
                log.fatal("pgbouncer is down and refuses to restart")
13465.2.5 by Stuart Bishop
Integrate full-update.py with local pgbouncer
193
        if not upgrade_run:
194
            log.warning("upgrade.py still needs to be run")
195
        if not security_run:
196
            log.warning("security.py still needs to be run")
7675.395.197 by Stuart Bishop
Full database update script for fast deployments
197
198
199
if __name__ == '__main__':
200
    sys.exit(main())