8687.15.9
by Karl Fogel
Add the copyright header block to more files (everything under database/). |
1 |
# Copyright 2009 Canonical Ltd. This software is licensed under the
|
2 |
# GNU Affero General Public License version 3 (see the file LICENSE).
|
|
5799.1.12
by Stuart Bishop
Replication maintenance scripts, work in progress |
3 |
|
4 |
"""Common helpers for replication scripts."""
|
|
5 |
||
5799.1.42
by Stuart Bishop
Review feedback, round 1 |
6 |
__metaclass__ = type |
7 |
__all__ = [] |
|
8 |
||
5799.1.12
by Stuart Bishop
Replication maintenance scripts, work in progress |
9 |
import subprocess |
5799.1.30
by Stuart Bishop
Work in progress |
10 |
from tempfile import NamedTemporaryFile |
5799.1.55
by Stuart Bishop
Improve initialize, less magic dev setup |
11 |
from textwrap import dedent |
5799.1.12
by Stuart Bishop
Replication maintenance scripts, work in progress |
12 |
|
5799.1.55
by Stuart Bishop
Improve initialize, less magic dev setup |
13 |
from canonical.config import config |
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
14 |
from canonical.database.sqlbase import connect, sqlvalues |
5799.1.26
by Stuart Bishop
Work in progress |
15 |
from canonical.database.postgresql import ( |
5799.1.58
by Stuart Bishop
Hardcode slony user for slony daemon connections |
16 |
fqn, all_tables_in_schema, all_sequences_in_schema, ConnectionString |
5799.1.26
by Stuart Bishop
Work in progress |
17 |
)
|
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
18 |
from canonical.launchpad.scripts.logger import log, DEBUG2 |
5799.1.20
by Stuart Bishop
Work in progress |
19 |
|
5799.1.26
by Stuart Bishop
Work in progress |
20 |
|
5799.1.55
by Stuart Bishop
Improve initialize, less magic dev setup |
21 |
# The Slony-I clustername we use with Launchpad. Hardcoded because there
|
22 |
# is no point changing this, ever.
|
|
23 |
CLUSTERNAME = 'sl' |
|
5799.1.26
by Stuart Bishop
Work in progress |
24 |
|
25 |
# The namespace in the database used to contain all the Slony-I tables.
|
|
26 |
CLUSTER_NAMESPACE = '_%s' % CLUSTERNAME |
|
27 |
||
9795.4.2
by Stuart Bishop
Review feedback |
28 |
# Replication set id constants. Don't change these without DBA help.
|
29 |
LPMAIN_SET_ID = 1 |
|
30 |
HOLDING_SET_ID = 666 |
|
7675.395.72
by Stuart Bishop
Remove remaining authdb awareness from replication scripts |
31 |
LPMIRROR_SET_ID = 4 |
9795.4.2
by Stuart Bishop
Review feedback |
32 |
|
7675.395.72
by Stuart Bishop
Remove remaining authdb awareness from replication scripts |
33 |
# Seed tables for the lpmain replication set to be passed to
|
5799.1.26
by Stuart Bishop
Work in progress |
34 |
# calculate_replication_set().
|
7675.395.72
by Stuart Bishop
Remove remaining authdb awareness from replication scripts |
35 |
LPMAIN_SEED = frozenset([ |
5799.1.26
by Stuart Bishop
Work in progress |
36 |
('public', 'account'), |
7675.395.72
by Stuart Bishop
Remove remaining authdb awareness from replication scripts |
37 |
('public', 'openidnonce'), |
7675.88.8
by Stuart Bishop
Stop ShipIt OpenID consumer sharing tables with the SSO server, dev replication setup fixes and test fixes |
38 |
('public', 'openidassociation'), |
5799.1.26
by Stuart Bishop
Work in progress |
39 |
('public', 'person'), |
40 |
('public', 'launchpaddatabaserevision'), |
|
9795.4.27
by Stuart Bishop
Cache database replication lag information as querying the live Slony tables can be slow |
41 |
('public', 'databasereplicationlag'), |
5799.1.26
by Stuart Bishop
Work in progress |
42 |
('public', 'fticache'), |
43 |
('public', 'nameblacklist'), |
|
7675.88.8
by Stuart Bishop
Stop ShipIt OpenID consumer sharing tables with the SSO server, dev replication setup fixes and test fixes |
44 |
('public', 'openidconsumerassociation'), |
45 |
('public', 'openidconsumernonce'), |
|
5799.1.26
by Stuart Bishop
Work in progress |
46 |
('public', 'codeimportmachine'), |
47 |
('public', 'scriptactivity'), |
|
48 |
('public', 'standardshipitrequest'), |
|
49 |
('public', 'bugtag'), |
|
50 |
('public', 'launchpadstatistic'), |
|
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
51 |
('public', 'parsedapachelog'), |
52 |
('public', 'shipitsurvey'), |
|
7675.395.48
by Stuart Bishop
Add DatabaseReplicationLag as a seed table to pass staging restore sanity checks |
53 |
('public', 'databasereplicationlag'), |
7675.395.122
by Stuart Bishop
Fix SuggestivePOTemplate and mark FeatureFlag as part of the lpmain replication set |
54 |
('public', 'featureflag'), |
7675.395.128
by Stuart Bishop
Fix staging rebuild |
55 |
# suggestivepotemplate can be removed when the
|
56 |
# suggestivepotemplate.potemplate foreign key constraint exists on
|
|
57 |
# production.
|
|
58 |
('public', 'suggestivepotemplate'), |
|
5799.1.26
by Stuart Bishop
Work in progress |
59 |
])
|
60 |
||
61 |
# Explicitly list tables that should not be replicated. This includes the
|
|
62 |
# session tables, as these might exist in developer databases but will not
|
|
63 |
# exist in the production launchpad database.
|
|
64 |
IGNORED_TABLES = set([ |
|
7675.414.5
by Stuart Bishop
Make replication ignore lp_* tables, as we are going to replicate them manually |
65 |
# Session tables that in some situations will exist in the main lp
|
66 |
# database.
|
|
67 |
'public.secret', 'public.sessiondata', 'public.sessionpkgdata', |
|
68 |
# Mirror tables, per Bug #489078. These tables have their own private
|
|
69 |
# replication set that is setup manually.
|
|
7675.395.63
by Stuart Bishop
lp_account needs to be ignored by our replication scripts as it is handled manually |
70 |
'public.lp_account', |
7675.395.143
by Stuart Bishop
Ship OpenIdIdentifier to ISD via the lp_OpenIdIdentifier mirror table |
71 |
'public.lp_openididentifier', |
7675.414.5
by Stuart Bishop
Make replication ignore lp_* tables, as we are going to replicate them manually |
72 |
'public.lp_person', |
73 |
'public.lp_personlocation', |
|
74 |
'public.lp_teamparticipation', |
|
7675.686.9
by Stuart Bishop
Basic database utilization report |
75 |
# Database statistics
|
76 |
'public.databasetablestats', |
|
77 |
'public.databasecpustats', |
|
7675.395.176
by Stuart Bishop
Capture table disk utilization metrics from pgstattuple |
78 |
'public.databasediskutilization', |
7675.395.97
by Stuart Bishop
Don't replicate the OAuthNonce table - busy table and little point |
79 |
# Don't replicate OAuthNonce - too busy and no real gain.
|
80 |
'public.oauthnonce', |
|
9795.4.18
by Stuart Bishop
Review feedback |
81 |
# Ubuntu SSO database. These tables where created manually by ISD
|
82 |
# and the Launchpad scripts should not mess with them. Eventually
|
|
9795.4.19
by Stuart Bishop
Speling |
83 |
# these tables will be in a totally separate database.
|
9795.4.18
by Stuart Bishop
Review feedback |
84 |
'public.auth_permission', |
85 |
'public.auth_group', |
|
86 |
'public.auth_user', |
|
87 |
'public.auth_message', |
|
88 |
'public.django_content_type', |
|
89 |
'public.auth_permission', |
|
90 |
'public.django_session', |
|
91 |
'public.django_site', |
|
92 |
'public.django_admin_log', |
|
93 |
'public.ssoopenidrpconfig', |
|
94 |
'public.auth_group_permissions', |
|
95 |
'public.auth_user_groups', |
|
9795.4.14
by Stuart Bishop
Handle database dumps containing login service tables, a side effect from standalone authservice progress |
96 |
'public.auth_user_user_permissions', |
7675.395.51
by Stuart Bishop
Add more tables created on production db for ISD |
97 |
'public.oauth_nonce', |
98 |
'public.oauth_consumer', |
|
99 |
'public.oauth_token', |
|
100 |
'public.api_user', |
|
101 |
'public.oauth_consumer_id_seq', |
|
102 |
'public.api_user_id_seq', |
|
103 |
'public.oauth_nonce_id_seq', |
|
7675.414.5
by Stuart Bishop
Make replication ignore lp_* tables, as we are going to replicate them manually |
104 |
])
|
9795.4.14
by Stuart Bishop
Handle database dumps containing login service tables, a side effect from standalone authservice progress |
105 |
|
106 |
# Calculate IGNORED_SEQUENCES
|
|
107 |
IGNORED_SEQUENCES = set('%s_id_seq' % table for table in IGNORED_TABLES) |
|
5799.1.26
by Stuart Bishop
Work in progress |
108 |
|
109 |
||
5799.1.30
by Stuart Bishop
Work in progress |
110 |
def slony_installed(con): |
111 |
"""Return True if the connected database is part of a Launchpad Slony-I
|
|
112 |
cluster.
|
|
113 |
"""
|
|
114 |
cur = con.cursor() |
|
115 |
cur.execute(""" |
|
116 |
SELECT TRUE FROM pg_class,pg_namespace
|
|
117 |
WHERE
|
|
118 |
nspname = %s |
|
119 |
AND relname = 'sl_table'
|
|
120 |
AND pg_class.relnamespace = pg_namespace.oid
|
|
121 |
""" % sqlvalues(CLUSTER_NAMESPACE)) |
|
122 |
return cur.fetchone() is not None |
|
123 |
||
124 |
||
5799.1.55
by Stuart Bishop
Improve initialize, less magic dev setup |
125 |
class TableReplicationInfo: |
126 |
"""Internal table replication details."""
|
|
127 |
table_id = None |
|
128 |
replication_set_id = None |
|
129 |
master_node_id = None |
|
130 |
||
131 |
def __init__(self, con, namespace, table_name): |
|
132 |
cur = con.cursor() |
|
133 |
cur.execute(""" |
|
134 |
SELECT tab_id, tab_set, set_origin
|
|
135 |
FROM %s.sl_table, %s.sl_set |
|
136 |
WHERE tab_set = set_id
|
|
137 |
AND tab_nspname = %s |
|
138 |
AND tab_relname = %s |
|
139 |
""" % ( |
|
140 |
(CLUSTER_NAMESPACE, CLUSTER_NAMESPACE) |
|
141 |
+ sqlvalues(namespace, table_name))) |
|
142 |
row = cur.fetchone() |
|
143 |
if row is None: |
|
144 |
raise LookupError(fqn(namespace, table_name)) |
|
145 |
self.table_id, self.replication_set_id, self.master_node_id = row |
|
146 |
||
147 |
||
5799.1.42
by Stuart Bishop
Review feedback, round 1 |
148 |
def sync(timeout): |
5799.1.20
by Stuart Bishop
Work in progress |
149 |
"""Generate a sync event and wait for it to complete on all nodes.
|
7675.414.6
by Stuart Bishop
Split the DB patch so tables can be created and replicated before rollout |
150 |
|
5799.1.20
by Stuart Bishop
Work in progress |
151 |
This means that all pending events have propagated and are in sync
|
152 |
to the point in time this method was called. This might take several
|
|
153 |
hours if there is a large backlog of work to replicate.
|
|
5799.1.30
by Stuart Bishop
Work in progress |
154 |
|
5799.1.42
by Stuart Bishop
Review feedback, round 1 |
155 |
:param timeout: Number of seconds to wait for the sync. 0 to block
|
156 |
indefinitely.
|
|
5799.1.20
by Stuart Bishop
Work in progress |
157 |
"""
|
5799.1.42
by Stuart Bishop
Review feedback, round 1 |
158 |
return execute_slonik("", sync=timeout) |
5799.1.20
by Stuart Bishop
Work in progress |
159 |
|
160 |
||
7178.4.1
by Stuart Bishop
Script to repair a db built from a slony node dump |
161 |
def execute_slonik(script, sync=None, exit_on_fail=True, auto_preamble=True): |
5799.1.12
by Stuart Bishop
Replication maintenance scripts, work in progress |
162 |
"""Use the slonik command line tool to run a slonik script.
|
163 |
||
164 |
:param script: The script as a string. Preamble should not be included.
|
|
5799.1.20
by Stuart Bishop
Work in progress |
165 |
|
5799.1.30
by Stuart Bishop
Work in progress |
166 |
:param sync: Number of seconds to wait for sync before failing. 0 to
|
167 |
block indefinitely.
|
|
5799.1.42
by Stuart Bishop
Review feedback, round 1 |
168 |
|
169 |
:param exit_on_fail: If True, on failure of the slonik script
|
|
13465.2.16
by Stuart Bishop
Run upgrade.py in-process to save startup overhead |
170 |
SystemExit is raised using the slonik return code.
|
5799.1.42
by Stuart Bishop
Review feedback, round 1 |
171 |
|
7178.4.1
by Stuart Bishop
Script to repair a db built from a slony node dump |
172 |
:param auto_preamble: If True, the generated preamble will be
|
173 |
automatically included.
|
|
174 |
||
5799.1.46
by Stuart Bishop
Review tweaks |
175 |
:returns: True if the script completed successfully. False if
|
5799.1.42
by Stuart Bishop
Review feedback, round 1 |
176 |
exit_on_fail is False and the script failed for any reason.
|
5799.1.12
by Stuart Bishop
Replication maintenance scripts, work in progress |
177 |
"""
|
178 |
||
5799.1.30
by Stuart Bishop
Work in progress |
179 |
# Add the preamble and optional sync to the script.
|
7178.4.1
by Stuart Bishop
Script to repair a db built from a slony node dump |
180 |
if auto_preamble: |
181 |
script = preamble() + script |
|
182 |
||
5799.1.20
by Stuart Bishop
Work in progress |
183 |
if sync is not None: |
7675.395.72
by Stuart Bishop
Remove remaining authdb awareness from replication scripts |
184 |
sync_script = dedent("""\ |
5799.1.55
by Stuart Bishop
Improve initialize, less magic dev setup |
185 |
sync (id = @master_node);
|
5799.1.20
by Stuart Bishop
Work in progress |
186 |
wait for event (
|
7675.395.72
by Stuart Bishop
Remove remaining authdb awareness from replication scripts |
187 |
origin = @master_node, confirmed = ALL,
|
5799.1.55
by Stuart Bishop
Improve initialize, less magic dev setup |
188 |
wait on = @master_node, timeout = %d); |
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
189 |
""" % sync) |
7675.395.72
by Stuart Bishop
Remove remaining authdb awareness from replication scripts |
190 |
script = script + sync_script |
5799.1.20
by Stuart Bishop
Work in progress |
191 |
|
5799.1.30
by Stuart Bishop
Work in progress |
192 |
# Copy the script to a NamedTemporaryFile rather than just pumping it
|
193 |
# to slonik via stdin. This way it can be examined if slonik appears
|
|
194 |
# to hang.
|
|
195 |
script_on_disk = NamedTemporaryFile(prefix="slonik", suffix=".sk") |
|
196 |
print >> script_on_disk, script |
|
197 |
script_on_disk.flush() |
|
198 |
||
199 |
# Run slonik
|
|
200 |
log.debug("Executing slonik script %s" % script_on_disk.name) |
|
7675.395.72
by Stuart Bishop
Remove remaining authdb awareness from replication scripts |
201 |
log.log(DEBUG2, 'Running script:\n%s' % script) |
5799.1.30
by Stuart Bishop
Work in progress |
202 |
returncode = subprocess.call(['slonik', script_on_disk.name]) |
203 |
||
204 |
if returncode != 0: |
|
5799.1.20
by Stuart Bishop
Work in progress |
205 |
log.error("slonik script failed") |
206 |
if exit_on_fail: |
|
13465.2.16
by Stuart Bishop
Run upgrade.py in-process to save startup overhead |
207 |
raise SystemExit(1) |
5799.1.30
by Stuart Bishop
Work in progress |
208 |
|
209 |
return returncode == 0 |
|
5799.1.12
by Stuart Bishop
Replication maintenance scripts, work in progress |
210 |
|
211 |
||
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
212 |
class Node: |
213 |
"""Simple data structure for holding information about a Slony node."""
|
|
214 |
def __init__(self, node_id, nickname, connection_string, is_master): |
|
215 |
self.node_id = node_id |
|
216 |
self.nickname = nickname |
|
217 |
self.connection_string = connection_string |
|
218 |
self.is_master = is_master |
|
219 |
||
220 |
||
221 |
def _get_nodes(con, query): |
|
222 |
"""Return a list of Nodes."""
|
|
223 |
if not slony_installed(con): |
|
224 |
return [] |
|
225 |
cur = con.cursor() |
|
226 |
cur.execute(query) |
|
227 |
nodes = [] |
|
228 |
for node_id, nickname, connection_string, is_master in cur.fetchall(): |
|
229 |
nodes.append(Node(node_id, nickname, connection_string, is_master)) |
|
230 |
return nodes |
|
231 |
||
232 |
||
233 |
def get_master_node(con, set_id=1): |
|
234 |
"""Return the master Node, or None if the cluster is still being setup."""
|
|
235 |
nodes = _get_nodes(con, """ |
|
236 |
SELECT DISTINCT
|
|
7675.88.8
by Stuart Bishop
Stop ShipIt OpenID consumer sharing tables with the SSO server, dev replication setup fixes and test fixes |
237 |
set_origin AS node_id,
|
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
238 |
'master',
|
239 |
pa_conninfo AS connection_string,
|
|
240 |
True
|
|
241 |
FROM _sl.sl_set
|
|
7675.88.8
by Stuart Bishop
Stop ShipIt OpenID consumer sharing tables with the SSO server, dev replication setup fixes and test fixes |
242 |
LEFT OUTER JOIN _sl.sl_path ON set_origin = pa_server
|
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
243 |
WHERE set_id = %d |
244 |
""" % set_id) |
|
245 |
if not nodes: |
|
246 |
return None |
|
247 |
assert len(nodes) == 1, "More than one master found for set %s" % set_id |
|
248 |
return nodes[0] |
|
249 |
||
250 |
||
251 |
def get_slave_nodes(con, set_id=1): |
|
252 |
"""Return the list of slave Nodes."""
|
|
253 |
return _get_nodes(con, """ |
|
254 |
SELECT DISTINCT
|
|
255 |
pa_server AS node_id,
|
|
256 |
'slave' || pa_server,
|
|
257 |
pa_conninfo AS connection_string,
|
|
258 |
False
|
|
259 |
FROM _sl.sl_set
|
|
260 |
JOIN _sl.sl_subscribe ON set_id = sub_set
|
|
261 |
JOIN _sl.sl_path ON sub_receiver = pa_server
|
|
262 |
WHERE
|
|
263 |
set_id = %d |
|
264 |
ORDER BY node_id
|
|
265 |
""" % set_id) |
|
266 |
||
267 |
||
268 |
def get_nodes(con, set_id=1): |
|
269 |
"""Return a list of all Nodes."""
|
|
270 |
master_node = get_master_node(con, set_id) |
|
271 |
if master_node is None: |
|
272 |
return [] |
|
273 |
else: |
|
274 |
return [master_node] + get_slave_nodes(con, set_id) |
|
275 |
||
276 |
||
277 |
def get_all_cluster_nodes(con): |
|
278 |
"""Return a list of all Nodes in the cluster.
|
|
279 |
||
280 |
node.is_master will be None, as this boolean doesn't make sense
|
|
281 |
in the context of a cluster rather than a single replication set.
|
|
282 |
"""
|
|
283 |
if not slony_installed(con): |
|
284 |
return [] |
|
285 |
nodes = _get_nodes(con, """ |
|
286 |
SELECT DISTINCT
|
|
287 |
pa_server AS node_id,
|
|
288 |
'node' || pa_server || '_node',
|
|
289 |
pa_conninfo AS connection_string,
|
|
290 |
NULL
|
|
291 |
FROM _sl.sl_path
|
|
292 |
ORDER BY node_id
|
|
293 |
""") |
|
294 |
if not nodes: |
|
295 |
# There are no subscriptions yet, so no paths. Generate the
|
|
296 |
# master Node.
|
|
297 |
cur = con.cursor() |
|
298 |
cur.execute("SELECT no_id from _sl.sl_node") |
|
299 |
node_ids = [row[0] for row in cur.fetchall()] |
|
300 |
if len(node_ids) == 0: |
|
301 |
return [] |
|
302 |
assert len(node_ids) == 1, "Multiple nodes but no paths." |
|
303 |
master_node_id = node_ids[0] |
|
304 |
master_connection_string = ConnectionString( |
|
7675.395.43
by Stuart Bishop
Update replication scripts to cope with recent ro mode changes |
305 |
config.database.rw_main_master) |
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
306 |
master_connection_string.user = 'slony' |
307 |
return [Node( |
|
308 |
master_node_id, 'node%d_node' % master_node_id, |
|
309 |
master_connection_string, True)] |
|
310 |
return nodes |
|
311 |
||
312 |
||
313 |
def preamble(con=None): |
|
5799.1.12
by Stuart Bishop
Replication maintenance scripts, work in progress |
314 |
"""Return the preable needed at the start of all slonik scripts."""
|
5799.1.55
by Stuart Bishop
Improve initialize, less magic dev setup |
315 |
|
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
316 |
if con is None: |
317 |
con = connect('slony') |
|
318 |
||
319 |
master_node = get_master_node(con) |
|
320 |
nodes = get_all_cluster_nodes(con) |
|
321 |
if master_node is None and len(nodes) == 1: |
|
322 |
master_node = nodes[0] |
|
323 |
||
324 |
preamble = [dedent("""\ |
|
325 |
#
|
|
5799.1.55
by Stuart Bishop
Improve initialize, less magic dev setup |
326 |
# Every slonik script must start with a clustername, which cannot
|
327 |
# be changed once the cluster is initialized.
|
|
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
328 |
#
|
5799.1.55
by Stuart Bishop
Improve initialize, less magic dev setup |
329 |
cluster name = sl;
|
330 |
||
331 |
# Symbolic ids for replication sets.
|
|
7675.395.72
by Stuart Bishop
Remove remaining authdb awareness from replication scripts |
332 |
define lpmain_set %d; |
333 |
define holding_set %d; |
|
334 |
define lpmirror_set %d; |
|
335 |
""" % (LPMAIN_SET_ID, HOLDING_SET_ID, LPMIRROR_SET_ID))] |
|
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
336 |
|
337 |
if master_node is not None: |
|
338 |
preamble.append(dedent("""\ |
|
339 |
# Symbolic id for the main replication set master node.
|
|
340 |
define master_node %d; |
|
341 |
define master_node_conninfo '%s'; |
|
342 |
""" % (master_node.node_id, master_node.connection_string))) |
|
343 |
||
344 |
for node in nodes: |
|
345 |
preamble.append(dedent("""\ |
|
346 |
define %s %d; |
|
347 |
define %s_conninfo '%s'; |
|
348 |
node @%s admin conninfo = @%s_conninfo; |
|
349 |
""" % ( |
|
350 |
node.nickname, node.node_id, |
|
351 |
node.nickname, node.connection_string, |
|
352 |
node.nickname, node.nickname))) |
|
9795.4.2
by Stuart Bishop
Review feedback |
353 |
|
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
354 |
return '\n\n'.join(preamble) |
9795.4.2
by Stuart Bishop
Review feedback |
355 |
|
5799.1.26
by Stuart Bishop
Work in progress |
356 |
|
357 |
def calculate_replication_set(cur, seeds): |
|
358 |
"""Return the minimal set of tables and sequences needed in a
|
|
359 |
replication set containing the seed table.
|
|
360 |
||
361 |
A replication set must contain all tables linked by foreign key
|
|
362 |
reference to the given table, and sequences used to generate keys.
|
|
7675.395.101
by Stuart Bishop
Ignore OAuthNonce better. Make foreign key constraint ON DELETE CASCADE so replication doesn't break when moving the lpmain origin to a new node |
363 |
Tables and sequences can be added to the IGNORED_TABLES and
|
364 |
IGNORED_SEQUENCES lists for cases where we known can safely ignore
|
|
365 |
this restriction.
|
|
5799.1.26
by Stuart Bishop
Work in progress |
366 |
|
367 |
:param seeds: [(namespace, tablename), ...]
|
|
368 |
||
369 |
:returns: (tables, sequences)
|
|
370 |
"""
|
|
371 |
# Results
|
|
372 |
tables = set() |
|
373 |
sequences = set() |
|
374 |
||
375 |
# Our pending set to check
|
|
376 |
pending_tables = set(seeds) |
|
377 |
||
378 |
# Generate the set of tables that reference the seed directly
|
|
379 |
# or indirectly via foreign key constraints, including the seed itself.
|
|
380 |
while pending_tables: |
|
381 |
namespace, tablename = pending_tables.pop() |
|
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
382 |
|
383 |
# Skip if the table doesn't exist - we might have seeds listed that
|
|
384 |
# have been removed or are yet to be created.
|
|
385 |
cur.execute(""" |
|
386 |
SELECT TRUE
|
|
387 |
FROM pg_class, pg_namespace
|
|
388 |
WHERE pg_class.relnamespace = pg_namespace.oid
|
|
389 |
AND pg_namespace.nspname = %s |
|
390 |
AND pg_class.relname = %s |
|
391 |
""" % sqlvalues(namespace, tablename)) |
|
392 |
if cur.fetchone() is None: |
|
393 |
log.debug("Table %s.%s doesn't exist" % (namespace, tablename)) |
|
394 |
continue
|
|
395 |
||
5799.1.26
by Stuart Bishop
Work in progress |
396 |
tables.add((namespace, tablename)) |
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
397 |
|
5799.1.26
by Stuart Bishop
Work in progress |
398 |
# Find all tables that reference the current (seed) table
|
399 |
# and all tables that the seed table references.
|
|
400 |
cur.execute(""" |
|
401 |
SELECT ref_namespace.nspname, ref_class.relname
|
|
402 |
FROM
|
|
5799.1.42
by Stuart Bishop
Review feedback, round 1 |
403 |
-- One of the seed tables
|
5799.1.26
by Stuart Bishop
Work in progress |
404 |
pg_class AS seed_class,
|
405 |
pg_namespace AS seed_namespace,
|
|
5799.1.42
by Stuart Bishop
Review feedback, round 1 |
406 |
|
407 |
-- A table referencing the seed, or being referenced by
|
|
408 |
-- the seed.
|
|
5799.1.26
by Stuart Bishop
Work in progress |
409 |
pg_class AS ref_class,
|
410 |
pg_namespace AS ref_namespace,
|
|
5799.1.42
by Stuart Bishop
Review feedback, round 1 |
411 |
|
5799.1.26
by Stuart Bishop
Work in progress |
412 |
pg_constraint
|
413 |
WHERE
|
|
414 |
seed_class.relnamespace = seed_namespace.oid
|
|
415 |
AND ref_class.relnamespace = ref_namespace.oid
|
|
5799.1.42
by Stuart Bishop
Review feedback, round 1 |
416 |
|
5799.1.26
by Stuart Bishop
Work in progress |
417 |
AND seed_namespace.nspname = %s |
418 |
AND seed_class.relname = %s |
|
5799.1.42
by Stuart Bishop
Review feedback, round 1 |
419 |
|
5799.1.46
by Stuart Bishop
Review tweaks |
420 |
-- Foreign key constraints are all we care about.
|
5799.1.42
by Stuart Bishop
Review feedback, round 1 |
421 |
AND pg_constraint.contype = 'f'
|
422 |
||
423 |
-- We want tables referenced by, or referred to, the
|
|
424 |
-- seed table.
|
|
5799.1.26
by Stuart Bishop
Work in progress |
425 |
AND ((pg_constraint.conrelid = ref_class.oid
|
426 |
AND pg_constraint.confrelid = seed_class.oid)
|
|
427 |
OR (pg_constraint.conrelid = seed_class.oid
|
|
428 |
AND pg_constraint.confrelid = ref_class.oid)
|
|
429 |
)
|
|
430 |
""" % sqlvalues(namespace, tablename)) |
|
431 |
for namespace, tablename in cur.fetchall(): |
|
432 |
key = (namespace, tablename) |
|
7675.395.101
by Stuart Bishop
Ignore OAuthNonce better. Make foreign key constraint ON DELETE CASCADE so replication doesn't break when moving the lpmain origin to a new node |
433 |
if (key not in tables and key not in pending_tables |
434 |
and '%s.%s' % (namespace, tablename) not in IGNORED_TABLES): |
|
5799.1.26
by Stuart Bishop
Work in progress |
435 |
pending_tables.add(key) |
436 |
||
437 |
# Generate the set of sequences that are linked to any of our set of
|
|
438 |
# tables. We assume these are all sequences created by creation of
|
|
439 |
# serial or bigserial columns, or other sequences OWNED BY a particular
|
|
440 |
# column.
|
|
441 |
for namespace, tablename in tables: |
|
442 |
cur.execute(""" |
|
443 |
SELECT seq
|
|
444 |
FROM (
|
|
445 |
SELECT pg_get_serial_sequence(%s, attname) AS seq |
|
446 |
FROM pg_namespace, pg_class, pg_attribute
|
|
447 |
WHERE pg_namespace.nspname = %s |
|
448 |
AND pg_class.relnamespace = pg_namespace.oid
|
|
449 |
AND pg_class.relname = %s |
|
450 |
AND pg_attribute.attrelid = pg_class.oid
|
|
451 |
AND pg_attribute.attisdropped IS FALSE
|
|
452 |
) AS whatever
|
|
453 |
WHERE seq IS NOT NULL;
|
|
454 |
""" % sqlvalues(fqn(namespace, tablename), namespace, tablename)) |
|
7675.395.101
by Stuart Bishop
Ignore OAuthNonce better. Make foreign key constraint ON DELETE CASCADE so replication doesn't break when moving the lpmain origin to a new node |
455 |
for sequence, in cur.fetchall(): |
456 |
if sequence not in IGNORED_SEQUENCES: |
|
457 |
sequences.add(sequence) |
|
5799.1.26
by Stuart Bishop
Work in progress |
458 |
|
459 |
# We can't easily convert the sequence name to (namespace, name) tuples,
|
|
460 |
# so we might as well convert the tables to dot notation for consistancy.
|
|
7178.4.10
by Stuart Bishop
Lint cleanup |
461 |
tables = set(fqn(namespace, tablename) for namespace, tablename in tables) |
5799.1.26
by Stuart Bishop
Work in progress |
462 |
|
463 |
return tables, sequences |
|
464 |
||
465 |
||
5799.1.29
by Stuart Bishop
Tweak |
466 |
def discover_unreplicated(cur): |
5799.1.26
by Stuart Bishop
Work in progress |
467 |
"""Inspect the database for tables and sequences in the public schema
|
468 |
that are not in a replication set.
|
|
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
469 |
|
5799.1.26
by Stuart Bishop
Work in progress |
470 |
:returns: (unreplicated_tables_set, unreplicated_sequences_set)
|
471 |
"""
|
|
472 |
all_tables = all_tables_in_schema(cur, 'public') |
|
473 |
all_sequences = all_sequences_in_schema(cur, 'public') |
|
474 |
||
13011.4.5
by Stuart Bishop
Replication should ignore tables and sequences with names starting with 'temp_' |
475 |
# Ignore any tables and sequences starting with temp_. These are
|
476 |
# transient and not to be replicated per Bug #778338.
|
|
477 |
all_tables = set( |
|
478 |
table for table in all_tables |
|
479 |
if not table.startswith('public.temp_')) |
|
480 |
all_sequences = set( |
|
481 |
sequence for sequence in all_sequences |
|
482 |
if not sequence.startswith('public.temp_')) |
|
483 |
||
5799.1.26
by Stuart Bishop
Work in progress |
484 |
cur.execute(""" |
485 |
SELECT tab_nspname, tab_relname FROM %s |
|
486 |
WHERE tab_nspname = 'public'
|
|
487 |
""" % fqn(CLUSTER_NAMESPACE, "sl_table")) |
|
488 |
replicated_tables = set(fqn(*row) for row in cur.fetchall()) |
|
489 |
||
490 |
cur.execute(""" |
|
491 |
SELECT seq_nspname, seq_relname FROM %s |
|
492 |
WHERE seq_nspname = 'public'
|
|
493 |
""" % fqn(CLUSTER_NAMESPACE, "sl_sequence")) |
|
494 |
replicated_sequences = set(fqn(*row) for row in cur.fetchall()) |
|
495 |
||
496 |
return ( |
|
497 |
all_tables - replicated_tables - IGNORED_TABLES, |
|
9795.4.14
by Stuart Bishop
Handle database dumps containing login service tables, a side effect from standalone authservice progress |
498 |
all_sequences - replicated_sequences - IGNORED_SEQUENCES) |
5799.1.26
by Stuart Bishop
Work in progress |
499 |
|
500 |
||
501 |
class ReplicationConfigError(Exception): |
|
502 |
"""Exception raised by validate_replication_sets() when our replication
|
|
503 |
setup is misconfigured.
|
|
504 |
"""
|
|
505 |
||
506 |
||
507 |
def validate_replication(cur): |
|
508 |
"""Raise a ReplicationSetupError if there is something wrong with
|
|
509 |
our replication sets.
|
|
510 |
||
511 |
This might include tables exist that are not in a replication set,
|
|
512 |
or tables that exist in multiple replication sets for example.
|
|
513 |
||
514 |
These is not necessarily limits with what Slony-I allows, but might
|
|
515 |
be due to policies we have made (eg. a table allowed in just one
|
|
516 |
replication set).
|
|
517 |
"""
|
|
5799.1.29
by Stuart Bishop
Tweak |
518 |
unrepl_tables, unrepl_sequences = discover_unreplicated(cur) |
5799.1.26
by Stuart Bishop
Work in progress |
519 |
if unrepl_tables: |
520 |
raise ReplicationConfigError( |
|
521 |
"Unreplicated tables: %s" % repr(unrepl_tables)) |
|
522 |
if unrepl_sequences: |
|
523 |
raise ReplicationConfigError( |
|
524 |
"Unreplicated sequences: %s" % repr(unrepl_sequences)) |
|
525 |
||
526 |
lpmain_tables, lpmain_sequences = calculate_replication_set( |
|
527 |
cur, LPMAIN_SEED) |