~launchpad-pqm/launchpad/devel

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())