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