1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
|
#!/usr/bin/python2.5
#
# Copyright 2009 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Populate the auth replication set.
This script moves the the SSO tables from the main replication set to
the auth replication set.
Once it has been run on production, these tables can no longer be
maintained using the Launchpad database maintenance scripts
(upgrade.py, security.py etc.).
We do this so Launchpad database upgrades do not lock the SSO tables,
allowing the SSO service to continue to operate.
This is a single shot script.
"""
__metaclass__ = type
__all__ = []
import _pythonpath
import sys
from textwrap import dedent
from optparse import OptionParser
from canonical.database.sqlbase import (
connect, ISOLATION_LEVEL_AUTOCOMMIT, sqlvalues)
from canonical.launchpad.scripts import db_options, logger_options, logger
import replication.helpers
def create_auth_set(cur):
"""Create the auth replication set if it doesn't already exist."""
cur.execute("SELECT TRUE FROM _sl.sl_set WHERE set_id=2")
if cur.fetchone() is not None:
log.info("Auth set already exists.")
return
slonik_script = dedent("""\
create set (
id=@authdb_set, origin=@master_node,
comment='SSO service tables');
""")
log.info("Creating authdb replication set.")
replication.helpers.execute_slonik(slonik_script, sync=0)
def subscribe_auth_set(cur):
"""The authdb set subscription much match the lpmain set subscription.
This is a requirement to move stuff between replication sets. It
is also what we want (all nodes replicating everything).
"""
cur.execute("""
SELECT sub_receiver FROM _sl.sl_subscribe WHERE sub_set = 1
EXCEPT
SELECT sub_receiver FROM _sl.sl_subscribe WHERE sub_set = 2
""")
for node_id in (node_id for node_id, in cur.fetchall()):
log.info("Subscribing Node #%d to authdb replication set" % node_id)
success = replication.helpers.execute_slonik(dedent("""\
subscribe set (
id = @authdb_set, provider = @master_node,
receiver = %d, forward = yes);
""" % node_id), sync=0)
if not success:
log.error("Slonik failed. Exiting.")
sys.exit(1)
def migrate_tables_and_sequences(cur):
auth_tables, auth_sequences = (
replication.helpers.calculate_replication_set(
cur, replication.helpers.AUTHDB_SEED))
slonik_script = ["try {"]
for table_fqn in auth_tables:
namespace, table_name = table_fqn.split('.')
cur.execute("""
SELECT tab_id, tab_set
FROM _sl.sl_table
WHERE tab_nspname = %s AND tab_relname = %s
""" % sqlvalues(namespace, table_name))
try:
table_id, set_id = cur.fetchone()
except IndexError:
log.error("Table %s not found in _sl.sl_tables" % table_fqn)
sys.exit(1)
if set_id == 1:
slonik_script.append("echo 'Moving table %s';" % table_fqn)
slonik_script.append(
"set move table "
"(origin=@master_node, id=%d, new set=@authdb_set);"
% table_id)
elif set_id == 2:
log.warn(
"Table %s already in authdb replication set"
% table_fqn)
else:
log.error("Unknown replication set %s" % set_id)
sys.exit(1)
for sequence_fqn in auth_sequences:
namespace, sequence_name = sequence_fqn.split('.')
cur.execute("""
SELECT seq_id, seq_set
FROM _sl.sl_sequence
WHERE seq_nspname = %s AND seq_relname = %s
""" % sqlvalues(namespace, sequence_name))
try:
sequence_id, set_id = cur.fetchone()
except IndexError:
log.error(
"Sequence %s not found in _sl.sl_sequences" % sequence_fqn)
sys.exit(1)
if set_id == 1:
slonik_script.append("echo 'Moving sequence %s';" % sequence_fqn)
slonik_script.append(
"set move sequence "
"(origin=@master_node, id=%d, new set=@authdb_set);"
% sequence_id)
elif set_id ==2:
log.warn(
"Sequence %s already in authdb replication set."
% sequence_fqn)
else:
log.error("Unknown replication set %s" % set_id)
sys.exit(1)
if len(slonik_script) == 1:
log.warn("No tables or sequences to migrate.")
return
slonik_script.append(dedent("""\
} on error {
echo 'Failed to move one or more tables or sequences.';
exit 1;
}
"""))
slonik_script = "\n".join(slonik_script)
log.info("Running migration script...")
if not replication.helpers.execute_slonik(slonik_script, sync=0):
log.error("Slonik failed. Exiting.")
sys.exit(1)
def main():
parser = OptionParser()
db_options(parser)
logger_options(parser)
options, args = parser.parse_args()
global log
log = logger(options)
con = connect('slony', isolation=ISOLATION_LEVEL_AUTOCOMMIT)
cur = con.cursor()
# Don't start until cluster is synced.
log.info("Waiting for sync.")
replication.helpers.sync(0)
create_auth_set(cur)
subscribe_auth_set(cur)
migrate_tables_and_sequences(cur)
log = None # Global log
if __name__ == '__main__':
main()
|