10637.3.7
by Guilherme Salgado
merge devel |
1 |
#!/usr/bin/python -S
|
8687.15.9
by Karl Fogel
Add the copyright header block to more files (everything under database/). |
2 |
#
|
13980.2.1
by Jeroen Vermeulen
Lint. Lots of lint. |
3 |
# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
|
8687.15.9
by Karl Fogel
Add the copyright header block to more files (everything under database/). |
4 |
# GNU Affero General Public License version 3 (see the file LICENSE).
|
5799.1.12
by Stuart Bishop
Replication maintenance scripts, work in progress |
5 |
|
5799.1.46
by Stuart Bishop
Review tweaks |
6 |
"""Generate a report on the replication setup.
|
7 |
||
8 |
This report spits out whatever we consider useful for checking up on and
|
|
9 |
diagnosing replication. This report will grow over time, and maybe some
|
|
10 |
bits of this will move to seperate monitoring systems or reports.
|
|
11 |
||
12 |
See the Slony-I documentation for more discussion on the data presented
|
|
13 |
by this report.
|
|
14 |
"""
|
|
5799.1.12
by Stuart Bishop
Replication maintenance scripts, work in progress |
15 |
|
5799.1.42
by Stuart Bishop
Review feedback, round 1 |
16 |
__metaclass__ = type |
17 |
__all__ = [] |
|
18 |
||
14606.3.5
by William Grant
Reformat |
19 |
import _pythonpath |
20 |
||
5799.1.12
by Stuart Bishop
Replication maintenance scripts, work in progress |
21 |
from cgi import escape as html_escape |
22 |
from cStringIO import StringIO |
|
23 |
from optparse import OptionParser |
|
24 |
import sys |
|
25 |
||
14606.3.1
by William Grant
Merge canonical.database into lp.services.database. |
26 |
from lp.services.database.sqlbase import ( |
13980.2.1
by Jeroen Vermeulen
Lint. Lots of lint. |
27 |
connect, |
28 |
quote_identifier, |
|
29 |
sqlvalues, |
|
30 |
)
|
|
14565.2.15
by Curtis Hovey
Moved canonical.launchpad.scripts __init__ to lp.services.scripts. |
31 |
from lp.services.scripts import db_options |
14606.3.5
by William Grant
Reformat |
32 |
import replication.helpers |
5799.1.12
by Stuart Bishop
Replication maintenance scripts, work in progress |
33 |
|
34 |
||
35 |
class Table: |
|
13980.2.1
by Jeroen Vermeulen
Lint. Lots of lint. |
36 |
"""Representation of a table.
|
5799.1.12
by Stuart Bishop
Replication maintenance scripts, work in progress |
37 |
|
13980.2.1
by Jeroen Vermeulen
Lint. Lots of lint. |
38 |
:ivar labels: List of labels to render as the table's first row.
|
39 |
:ivar rows: List of rows, each being a list of strings.
|
|
40 |
"""
|
|
5799.1.12
by Stuart Bishop
Replication maintenance scripts, work in progress |
41 |
def __init__(self, labels=None): |
42 |
if labels is None: |
|
43 |
self.labels = [] |
|
44 |
else: |
|
45 |
self.labels = labels[:] |
|
46 |
self.rows = [] |
|
47 |
||
7675.251.2
by Stuart Bishop
Fix listen report which never worked |
48 |
|
5799.1.12
by Stuart Bishop
Replication maintenance scripts, work in progress |
49 |
class HtmlReport: |
50 |
||
51 |
def alert(self, text): |
|
52 |
"""Return text marked up to be noticed."""
|
|
53 |
return '<span style="alert">%s</span>' % html_escape(text) |
|
54 |
||
55 |
def table(self, table): |
|
56 |
"""Return the rendered table."""
|
|
57 |
out = StringIO() |
|
58 |
print >> out, "<table>" |
|
59 |
if table.labels: |
|
60 |
print >> out, "<tr>" |
|
61 |
for label in table.labels: |
|
62 |
print >> out, "<th>%s</th>" % html_escape(unicode(label)) |
|
63 |
print >> out, "</tr>" |
|
64 |
||
65 |
for row in table.rows: |
|
66 |
print >> out, "<tr>" |
|
67 |
for cell in row: |
|
68 |
print >> out, "<td>%s</td>" % html_escape(unicode(cell)) |
|
69 |
print >> out, "</tr>" |
|
70 |
||
71 |
print >> out, "</table>" |
|
72 |
||
73 |
return out.getvalue() |
|
74 |
||
75 |
||
5799.1.27
by Stuart Bishop
Text mode report |
76 |
class TextReport: |
77 |
def alert(self, text): |
|
78 |
return text |
|
79 |
||
80 |
def table(self, table): |
|
81 |
max_col_widths = [] |
|
82 |
for label in table.labels: |
|
83 |
max_col_widths.append(len(label)) |
|
84 |
for row in table.rows: |
|
13980.2.1
by Jeroen Vermeulen
Lint. Lots of lint. |
85 |
for col_idx, col in enumerate(row): |
5799.1.27
by Stuart Bishop
Text mode report |
86 |
max_col_widths[col_idx] = max( |
13980.2.1
by Jeroen Vermeulen
Lint. Lots of lint. |
87 |
len(str(col)), max_col_widths[col_idx]) |
5799.1.27
by Stuart Bishop
Text mode report |
88 |
|
89 |
out = StringIO() |
|
90 |
for label_idx in range(0, len(table.labels)): |
|
91 |
print >> out, table.labels[label_idx].ljust( |
|
92 |
max_col_widths[label_idx]), |
|
93 |
print >> out |
|
94 |
for width in max_col_widths: |
|
13980.2.1
by Jeroen Vermeulen
Lint. Lots of lint. |
95 |
print >> out, '=' * width, |
5799.1.27
by Stuart Bishop
Text mode report |
96 |
print >> out |
97 |
for row in table.rows: |
|
98 |
row = list(row) |
|
99 |
for col_idx in range(0, len(row)): |
|
13980.2.1
by Jeroen Vermeulen
Lint. Lots of lint. |
100 |
print >> out, str( |
101 |
row[col_idx]).ljust(max_col_widths[col_idx]), |
|
5799.1.27
by Stuart Bishop
Text mode report |
102 |
print >> out |
103 |
print >> out |
|
104 |
||
105 |
return out.getvalue() |
|
106 |
||
107 |
||
5799.1.12
by Stuart Bishop
Replication maintenance scripts, work in progress |
108 |
def node_overview_report(cur, options): |
5799.1.42
by Stuart Bishop
Review feedback, round 1 |
109 |
"""Dumps the sl_node table in a human readable format.
|
5799.1.12
by Stuart Bishop
Replication maintenance scripts, work in progress |
110 |
|
5799.1.42
by Stuart Bishop
Review feedback, round 1 |
111 |
This report tells us which nodes are active and which are inactive.
|
112 |
"""
|
|
5799.1.27
by Stuart Bishop
Text mode report |
113 |
report = options.mode() |
5799.1.12
by Stuart Bishop
Replication maintenance scripts, work in progress |
114 |
table = Table(["Node", "Comment", "Active"]) |
115 |
||
116 |
cur.execute(""" |
|
117 |
SELECT no_id, no_comment, no_active
|
|
118 |
FROM sl_node
|
|
119 |
ORDER BY no_comment
|
|
120 |
""") |
|
121 |
for node_id, node_comment, node_active in cur.fetchall(): |
|
122 |
if node_active: |
|
123 |
node_active_text = 'Active' |
|
124 |
else: |
|
125 |
node_active_text = report.alert('Inactive') |
|
126 |
table.rows.append([ |
|
127 |
'Node %d' % node_id, node_comment, node_active_text]) |
|
128 |
||
129 |
return report.table(table) |
|
130 |
||
131 |
||
132 |
def paths_report(cur, options): |
|
5799.1.42
by Stuart Bishop
Review feedback, round 1 |
133 |
"""Dumps the sl_paths table in a human readable format.
|
134 |
||
135 |
This report describes how nodes will attempt to connect to each other
|
|
136 |
if they need to, allowing you to sanity check the settings and pick up
|
|
137 |
obvious misconfigurations that would stop Slony daemons from being able
|
|
138 |
to connect to one or more nodes, blocking replication.
|
|
139 |
"""
|
|
5799.1.27
by Stuart Bishop
Text mode report |
140 |
report = options.mode() |
5799.1.12
by Stuart Bishop
Replication maintenance scripts, work in progress |
141 |
table = Table(["From client node", "To server node", "Via connection"]) |
142 |
||
143 |
cur.execute(""" |
|
144 |
SELECT pa_client, pa_server, pa_conninfo
|
|
145 |
FROM sl_path
|
|
146 |
ORDER BY pa_client, pa_server
|
|
147 |
""") |
|
148 |
for row in cur.fetchall(): |
|
149 |
table.rows.append(row) |
|
150 |
||
151 |
return report.table(table) |
|
152 |
||
153 |
||
5799.1.22
by Stuart Bishop
Finish initial status reporting tool |
154 |
def listen_report(cur, options): |
5799.1.42
by Stuart Bishop
Review feedback, round 1 |
155 |
"""Dumps the sl_listen table in a human readable format.
|
156 |
||
157 |
This report shows you the tree of which nodes a node needs to check
|
|
158 |
for events on.
|
|
159 |
"""
|
|
5799.1.27
by Stuart Bishop
Text mode report |
160 |
report = options.mode() |
5799.1.22
by Stuart Bishop
Finish initial status reporting tool |
161 |
table = Table(["Node", "Listens To", "Via"]) |
162 |
||
163 |
cur.execute(""" |
|
164 |
SELECT li_receiver, li_origin, li_provider
|
|
7675.251.2
by Stuart Bishop
Fix listen report which never worked |
165 |
FROM sl_listen
|
166 |
ORDER BY li_receiver, li_origin, li_provider
|
|
5799.1.22
by Stuart Bishop
Finish initial status reporting tool |
167 |
""") |
168 |
for row in cur.fetchall(): |
|
7675.251.2
by Stuart Bishop
Fix listen report which never worked |
169 |
table.rows.append(['Node %s' % node for node in row]) |
5799.1.22
by Stuart Bishop
Finish initial status reporting tool |
170 |
return report.table(table) |
171 |
||
172 |
||
173 |
def subscribe_report(cur, options): |
|
5799.1.42
by Stuart Bishop
Review feedback, round 1 |
174 |
"""Dumps the sl_subscribe table in a human readable format.
|
175 |
||
176 |
This report shows the subscription tree - which nodes provide
|
|
177 |
a replication set to which subscriber.
|
|
178 |
"""
|
|
179 |
||
5799.1.27
by Stuart Bishop
Text mode report |
180 |
report = options.mode() |
5799.1.22
by Stuart Bishop
Finish initial status reporting tool |
181 |
table = Table([ |
5799.1.42
by Stuart Bishop
Review feedback, round 1 |
182 |
"Set", "Is Provided By", "Is Received By", |
5799.1.22
by Stuart Bishop
Finish initial status reporting tool |
183 |
"Is Forwardable", "Is Active"]) |
184 |
cur.execute(""" |
|
185 |
SELECT sub_set, sub_provider, sub_receiver, sub_forward, sub_active
|
|
186 |
FROM sl_subscribe ORDER BY sub_set, sub_provider, sub_receiver
|
|
187 |
""") |
|
188 |
for set_, provider, receiver, forward, active in cur.fetchall(): |
|
189 |
if active: |
|
190 |
active_text = 'Active' |
|
191 |
else: |
|
192 |
active_text = report.alert('Inactive') |
|
193 |
table.rows.append([ |
|
194 |
"Set %d" % set_, "Node %d" % provider, "Node %d" % receiver, |
|
195 |
str(forward), active_text]) |
|
196 |
return report.table(table) |
|
197 |
||
198 |
||
199 |
def tables_report(cur, options): |
|
5799.1.42
by Stuart Bishop
Review feedback, round 1 |
200 |
"""Dumps the sl_table table in a human readable format.
|
201 |
||
202 |
This report shows which tables are being replicated and in which
|
|
203 |
replication set. It also importantly shows the internal Slony id used
|
|
204 |
for a table, which is needed for slonik scripts as slonik is incapable
|
|
205 |
of doing the tablename -> Slony id mapping itself.
|
|
206 |
"""
|
|
5799.1.27
by Stuart Bishop
Text mode report |
207 |
report = options.mode() |
5799.1.31
by Stuart Bishop
Add newly created tables to lpmain replication set |
208 |
table = Table(["Set", "Schema", "Table", "Table Id"]) |
5799.1.22
by Stuart Bishop
Finish initial status reporting tool |
209 |
cur.execute(""" |
210 |
SELECT tab_set, nspname, relname, tab_id, tab_idxname, tab_comment
|
|
211 |
FROM sl_table, pg_class, pg_namespace
|
|
212 |
WHERE tab_reloid = pg_class.oid AND relnamespace = pg_namespace.oid
|
|
213 |
ORDER BY tab_set, nspname, relname
|
|
214 |
""") |
|
215 |
for set_, namespace, tablename, table_id, key, comment in cur.fetchall(): |
|
216 |
table.rows.append([ |
|
5799.1.31
by Stuart Bishop
Add newly created tables to lpmain replication set |
217 |
"Set %d" % set_, namespace, tablename, str(table_id)]) |
5799.1.22
by Stuart Bishop
Finish initial status reporting tool |
218 |
return report.table(table) |
219 |
||
220 |
||
221 |
def sequences_report(cur, options): |
|
5799.1.42
by Stuart Bishop
Review feedback, round 1 |
222 |
"""Dumps the sl_sequences table in a human readable format.
|
223 |
||
224 |
This report shows which sequences are being replicated and in which
|
|
225 |
replication set. It also importantly shows the internal Slony id used
|
|
226 |
for a sequence, which is needed for slonik scripts as slonik is incapable
|
|
227 |
of doing the tablename -> Slony id mapping itself.
|
|
228 |
"""
|
|
5799.1.27
by Stuart Bishop
Text mode report |
229 |
report = options.mode() |
5799.1.31
by Stuart Bishop
Add newly created tables to lpmain replication set |
230 |
table = Table(["Set", "Schema", "Sequence", "Sequence Id"]) |
5799.1.22
by Stuart Bishop
Finish initial status reporting tool |
231 |
cur.execute(""" |
232 |
SELECT seq_set, nspname, relname, seq_id, seq_comment
|
|
233 |
FROM sl_sequence, pg_class, pg_namespace
|
|
234 |
WHERE seq_reloid = pg_class.oid AND relnamespace = pg_namespace.oid
|
|
235 |
ORDER BY seq_set, nspname, relname
|
|
236 |
""") |
|
237 |
for set_, namespace, tablename, table_id, comment in cur.fetchall(): |
|
238 |
table.rows.append([ |
|
5799.1.31
by Stuart Bishop
Add newly created tables to lpmain replication set |
239 |
"Set %d" % set_, namespace, tablename, str(table_id)]) |
5799.1.22
by Stuart Bishop
Finish initial status reporting tool |
240 |
return report.table(table) |
241 |
||
242 |
||
5799.1.12
by Stuart Bishop
Replication maintenance scripts, work in progress |
243 |
def main(): |
244 |
parser = OptionParser() |
|
245 |
||
5799.1.27
by Stuart Bishop
Text mode report |
246 |
parser.add_option( |
247 |
"-f", "--format", dest="mode", default="text", |
|
248 |
choices=['text', 'html'], |
|
249 |
help="Output format MODE", metavar="MODE") |
|
5799.1.12
by Stuart Bishop
Replication maintenance scripts, work in progress |
250 |
db_options(parser) |
251 |
||
252 |
options, args = parser.parse_args() |
|
253 |
||
5799.1.27
by Stuart Bishop
Text mode report |
254 |
if options.mode == "text": |
255 |
options.mode = TextReport |
|
256 |
elif options.mode == "html": |
|
257 |
options.mode = HtmlReport |
|
258 |
else: |
|
259 |
assert False, "Unknown mode %s" % options.mode |
|
260 |
||
13879.1.3
by William Grant
Drop now-obsolete connect(user) args. |
261 |
con = connect() |
5799.1.12
by Stuart Bishop
Replication maintenance scripts, work in progress |
262 |
cur = con.cursor() |
263 |
||
264 |
cur.execute( |
|
265 |
"SELECT TRUE FROM pg_namespace WHERE nspname=%s" |
|
5799.1.55
by Stuart Bishop
Improve initialize, less magic dev setup |
266 |
% sqlvalues(replication.helpers.CLUSTER_NAMESPACE)) |
5799.1.12
by Stuart Bishop
Replication maintenance scripts, work in progress |
267 |
|
268 |
if cur.fetchone() is None: |
|
269 |
parser.error( |
|
270 |
"No Slony-I cluster called %s in that database" |
|
5799.1.55
by Stuart Bishop
Improve initialize, less magic dev setup |
271 |
% replication.helpers.CLUSTERNAME) |
5799.1.12
by Stuart Bishop
Replication maintenance scripts, work in progress |
272 |
return 1 |
273 |
||
274 |
# Set our search path to the schema of the cluster we care about.
|
|
275 |
cur.execute( |
|
5799.1.55
by Stuart Bishop
Improve initialize, less magic dev setup |
276 |
"SET search_path TO %s, public" |
7140.4.4
by Stuart Bishop
Delint replication scripts and fix trivial bug in report.py |
277 |
% quote_identifier(replication.helpers.CLUSTER_NAMESPACE)) |
5799.1.12
by Stuart Bishop
Replication maintenance scripts, work in progress |
278 |
|
279 |
print node_overview_report(cur, options) |
|
280 |
print paths_report(cur, options) |
|
5799.1.22
by Stuart Bishop
Finish initial status reporting tool |
281 |
print listen_report(cur, options) |
282 |
print subscribe_report(cur, options) |
|
283 |
print tables_report(cur, options) |
|
284 |
print sequences_report(cur, options) |
|
5799.1.12
by Stuart Bishop
Replication maintenance scripts, work in progress |
285 |
|
286 |
||
287 |
if __name__ == '__main__': |
|
288 |
sys.exit(main()) |