~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to database/schema/full-update.py

  • Committer: Launchpad Patch Queue Manager
  • Date: 2011-07-26 08:43:37 UTC
  • mfrom: (13465.2.21 staging)
  • Revision ID: launchpad@pqm.canonical.com-20110726084337-l7d35ntknx8rnjko
[r=benji][bug=798120,809123] fastdowntime deployment improvements

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
    logger_options,
18
18
    )
19
19
 
 
20
from preflight import (
 
21
    KillConnectionsPreflight,
 
22
    NoConnectionCheckPreflight,
 
23
    )
 
24
import security  # security.py script
 
25
import upgrade  # upgrade.py script
 
26
 
20
27
 
21
28
PGBOUNCER_INITD = ['sudo', '/etc/init.d/pgbouncer']
22
29
 
23
30
 
24
 
def run_script(script, *extra_args):
25
 
    script_path = os.path.join(os.path.dirname(__file__), script)
26
 
    return subprocess.call([script_path] + sys.argv[1:] + list(extra_args))
 
31
def run_pgbouncer(log, cmd):
 
32
    """Invoke the pgbouncer initscript.
 
33
 
 
34
    :param cmd: One of 'start', 'stop' or 'status'.
 
35
    """
 
36
    assert cmd in ('start', 'stop', 'status'), '''
 
37
        Unrecognized command; remember any new commands need to be
 
38
        granted sudo on staging and prod.
 
39
        '''
 
40
    pgbouncer_rc = subprocess.call(PGBOUNCER_INITD + [cmd])
 
41
    sys.stdout.flush()
 
42
    if pgbouncer_rc != 0:
 
43
        log.error("pgbouncer '%s' failed [%s]", cmd, pgbouncer_rc)
 
44
    return pgbouncer_rc
 
45
 
 
46
 
 
47
def run_upgrade(options, log):
 
48
    """Invoke upgrade.py in-process.
 
49
 
 
50
    It would be easier to just invoke the script, but this way we save
 
51
    several seconds of overhead as the component architecture loads up.
 
52
    """
 
53
    # Fake expected command line arguments and global log
 
54
    options.commit = True
 
55
    options.partial = False
 
56
    upgrade.options = options
 
57
    upgrade.log = log
 
58
    # Invoke the database schema upgrade process.
 
59
    try:
 
60
        return upgrade.main()
 
61
    except Exception:
 
62
        log.exception('Unhandled exception')
 
63
        return 1
 
64
    except SystemExit, x:
 
65
        log.fatal("upgrade.py failed [%s]", x)
 
66
 
 
67
 
 
68
def run_security(options, log):
 
69
    """Invoke security.py in-process.
 
70
 
 
71
    It would be easier to just invoke the script, but this way we save
 
72
    several seconds of overhead as the component architecture loads up.
 
73
    """
 
74
    # Fake expected command line arguments and global log
 
75
    options.dryrun = False
 
76
    options.revoke = True
 
77
    options.owner = 'postgres'
 
78
    options.cluster = True
 
79
    security.options = options
 
80
    security.log = log
 
81
    # Invoke the database security reset process.
 
82
    try:
 
83
        return security.main(options)
 
84
    except Exception:
 
85
        log.exception('Unhandled exception')
 
86
        return 1
 
87
    except SystemExit, x:
 
88
        log.fatal("security.py failed [%s]", x)
27
89
 
28
90
 
29
91
def main():
45
107
 
46
108
    # We initially ignore open connections, as they will shortly be
47
109
    # killed.
48
 
    preflight_rc = run_script('preflight.py', '--skip-connection-check')
49
 
    if preflight_rc != 0:
50
 
        return preflight_rc
 
110
    if not NoConnectionCheckPreflight(log).check_all():
 
111
        return 99
51
112
 
52
113
    # Confirm we can invoke PGBOUNCER_INITD
53
 
    pgbouncer_status_cmd = PGBOUNCER_INITD + ['status']
54
 
    pgbouncer_rc = subprocess.call(pgbouncer_status_cmd)
55
 
    sys.stdout.flush()
 
114
    pgbouncer_rc = run_pgbouncer(log, 'status')
56
115
    if pgbouncer_rc != 0:
57
 
        log.fatal("Unable to invoke %s", ' '.join(pgbouncer_status_cmd))
58
116
        return pgbouncer_rc
59
117
 
60
118
    #
65
123
    # status flags
66
124
    pgbouncer_down = False
67
125
    upgrade_run = False
68
 
    fti_run = False
69
126
    security_run = False
70
127
 
71
128
    try:
72
129
        # Shutdown pgbouncer
73
 
        pgbouncer_rc = subprocess.call(PGBOUNCER_INITD + ['stop'])
74
 
        sys.stdout.flush()
 
130
        pgbouncer_rc = run_pgbouncer(log, 'stop')
75
131
        if pgbouncer_rc != 0:
76
132
            log.fatal("pgbouncer not shut down [%s]", pgbouncer_rc)
77
133
            return pgbouncer_rc
78
134
        pgbouncer_down = True
79
135
 
80
 
        preflight_rc = run_script('preflight.py', '--kill-connections')
81
 
        if preflight_rc != 0:
82
 
            return preflight_rc
 
136
        if not KillConnectionsPreflight(log).check_all():
 
137
            return 100
83
138
 
84
 
        upgrade_rc = run_script('upgrade.py')
 
139
        upgrade_rc = run_upgrade(options, log)
85
140
        if upgrade_rc != 0:
86
 
            log.warning("upgrade.py run may have been partial")
87
141
            return upgrade_rc
88
142
        upgrade_run = True
89
143
 
90
 
        fti_rc = run_script('fti.py')
91
 
        if fti_rc != 0:
92
 
            return fti_rc
93
 
        fti_run = True
94
 
 
95
 
        security_rc = run_script('security.py', '--cluster')
 
144
        security_rc = run_security(options, log)
96
145
        if security_rc != 0:
97
146
            return security_rc
98
147
        security_run = True
99
148
 
100
149
        log.info("All database upgrade steps completed")
101
150
 
102
 
        pgbouncer_rc = subprocess.call(PGBOUNCER_INITD + ['start'])
103
 
        sys.stdout.flush()
 
151
        pgbouncer_rc = run_pgbouncer(log, 'start')
104
152
        if pgbouncer_rc != 0:
105
153
            log.fatal("pgbouncer not restarted [%s]", pgbouncer_rc)
106
154
            return pgbouncer_rc
108
156
 
109
157
        # We will start seeing connections as soon as pgbouncer is
110
158
        # reenabled, so ignore them here.
111
 
        preflight_rc = run_script('preflight.py', '--skip-connection-check')
112
 
        if preflight_rc != 0:
113
 
            return preflight_rc
 
159
        if not NoConnectionCheckPreflight(log).check_all():
 
160
            return 101
114
161
 
115
162
        log.info("All good. All done.")
116
163
        return 0
117
164
 
118
165
    finally:
119
166
        if pgbouncer_down:
120
 
            log.warning("pgbouncer is down and will need to be restarted")
 
167
            # Even if upgrade.py or security.py failed, we should be in
 
168
            # a good enough state to continue operation so restart
 
169
            # pgbouncer and allow connections.
 
170
            #  - upgrade.py may have failed to update the master, and
 
171
            #    changes should have rolled back.
 
172
            #  - upgrade.py may have failed to update a slave, breaking
 
173
            #    replication. The master is still operational, but
 
174
            #    slaves may be lagging and have the old schema.
 
175
            #  - security.py may have died, rolling back its changes on
 
176
            #    one or more nodes.
 
177
            # In all cases except the first, we have recovery to do but
 
178
            # systems are probably ok, or at least providing some
 
179
            # services.
 
180
            pgbouncer_rc = run_pgbouncer(log, 'start')
 
181
            if pgbouncer_rc == 0:
 
182
                log.info("Despite failures, pgbouncer restarted.")
 
183
            else:
 
184
                log.fatal("pgbouncer is down and refuses to restart")
121
185
        if not upgrade_run:
122
186
            log.warning("upgrade.py still needs to be run")
123
 
        if not fti_run:
124
 
            log.warning("fti.py still needs to be run")
125
187
        if not security_run:
126
188
            log.warning("security.py still needs to be run")
127
189