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