The branch, master has been updated via 08651a0 samba_kcc: do not commit new nTDSConnection, if we are rodc via a00312d samba_kcc: simplify NCReplica.set_instantiated_flags() via 81484f3 samba_kcc: simplify NCReplica constructor via 315f445 samba_kcc: clarify readonly logging, removing now unused function via d3f4429 samba_kcc: remove unused functions via d3c5420 samba_kcc: fix dot_file_dir documentation via a090d7e samba_kcc: remove an unused function via c6294c3 samba-tool visualize for understanding AD DC behaviour via ba2306f samba_kcc: use new graph module for writing dot files via cebad22 python/graph: module for generating ASCII and graphviz visualisations via b4a90a6 samba_kcc: respect kcc.read_only flag on RODC via e579d5b samba_kcc: kcc.debug module defers to samba.colour via a46c4a3 python: module containing ANSI colour sequences via f2762d0 python tests: assert string equality, with diff via 3f2762d samba_kcc: documentation fix via 6678f33 s4:torture/samba_tool_drs: demote the test dc at the end of test_samba_tool_replicate_local() from 4b17d36 WHATSNEW: document some more new options
https://git.samba.org/?p=samba.git;a=shortlog;h=master - Log ----------------------------------------------------------------- commit 08651a08ac10d472a8b170c2f33496192d7faa66 Author: Andrej Gessel <andrej.ges...@janztec.com> Date: Mon Nov 13 11:07:43 2017 +0100 samba_kcc: do not commit new nTDSConnection, if we are rodc Traceback (most recent call last): /usr/local/samba/sbin/samba_kcc: File "/usr/local/samba/sbin/samba_kcc", line 337, in <module> /usr/local/samba/sbin/samba_kcc: attempt_live_connections=opts.attempt_live_connections) /usr/local/samba/sbin/samba_kcc: File "/usr/local/samba/lib/python2.7/site-packages/samba/kcc/__init__.py", line 2644, in run /usr/local/samba/sbin/samba_kcc: all_connected = self.intersite(ping) /usr/local/samba/sbin/samba_kcc: File "/usr/local/samba/lib/python2.7/site-packages/samba/kcc/__init__.py", line 1883, in intersite /usr/local/samba/sbin/samba_kcc: all_connected = self.create_intersite_connections() /usr/local/samba/sbin/samba_kcc: File "/usr/local/samba/lib/python2.7/site-packages/samba/kcc/__init__.py", line 1817, in create_intersite_connections /usr/local/samba/sbin/samba_kcc: part, True) /usr/local/samba/sbin/samba_kcc: File "/usr/local/samba/lib/python2.7/site-packages/samba/kcc/__init__.py", line 1769, in create_connections /usr/local/samba/sbin/samba_kcc: partial_ok, detect_failed) /usr/local/samba/sbin/samba_kcc: File "/usr/local/samba/lib/python2.7/site-packages/samba/kcc/__init__.py", line 1594, in create_connection /usr/local/samba/sbin/samba_kcc: lbh.commit_connections(self.samdb) /usr/local/samba/sbin/samba_kcc: File "/usr/local/samba/lib/python2.7/site-packages/samba/kcc/kcc_utils.py", line 827, in commit_connections /usr/local/samba/sbin/samba_kcc: connect.commit_added(samdb, ro) /usr/local/samba/sbin/samba_kcc: File "/usr/local/samba/lib/python2.7/site-packages/samba/kcc/kcc_utils.py", line 1123, in commit_added /usr/local/samba/sbin/samba_kcc: (self.dnstr, estr)) /usr/local/samba/sbin/samba_kcc: samba.kcc.kcc_utils.KCCError: Could not add nTDSConnection for (CN=862f0429-c72c-4a81-ae9a-96820bb2f96d,CN=NTDS Settings, CN=BUILDHOST,CN=Servers,CN=Testsite,CN=Sites,CN=Configuration,DC=samdom,DC=com) - (Invalid LDB reply type 1) ../source4/dsdb/kcc/kcc_periodic.c:693: Failed samba_kcc - NT_STATUS_ACCESS_DENIED Signed-off-by: Andrej Gessel <andrej.ges...@janztec.com> Reviewed-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> Autobuild-User(master): Karolin Seeger <ksee...@samba.org> Autobuild-Date(master): Sat Jan 13 22:01:49 CET 2018 on sn-devel-144 commit a00312df7d5a9a2394b41111608c4d988ff4e3f2 Author: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Date: Fri Dec 15 15:58:46 2017 +1300 samba_kcc: simplify NCReplica.set_instantiated_flags() Signed-off-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit 81484f32f4dfe4aeb5624430575fe791a9063246 Author: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Date: Wed Dec 13 17:50:56 2017 +1300 samba_kcc: simplify NCReplica constructor There is nothing to be gained from setting the dn and guid separately except subtle bugs. Signed-off-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit 315f445a0256b0b63a344286debb6a27053c4d69 Author: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Date: Wed Dec 13 17:35:29 2017 +1300 samba_kcc: clarify readonly logging, removing now unused function The unused function was somewhat misnamed. Signed-off-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit d3f4429cd6e8a58926753651c015e683b92995ae Author: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Date: Wed Dec 13 16:04:19 2017 +1300 samba_kcc: remove unused functions Signed-off-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit d3c542051fb19559c5699001da8d9da6c7e66712 Author: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Date: Thu Nov 30 09:24:05 2017 +1300 samba_kcc: fix dot_file_dir documentation Signed-off-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit a090d7ef52cfd2bbc8bdf7028db0e2237def1f3e Author: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Date: Thu Nov 16 16:47:32 2017 +1300 samba_kcc: remove an unused function Signed-off-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit c6294c3c7b6c97f15daad7d463bda267726245c7 Author: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Date: Thu Aug 10 11:57:24 2017 +1200 samba-tool visualize for understanding AD DC behaviour To work out what is happening in a replication graph, it is sometimes helpful to use visualisations. We introduce a samba-tool subcommand to write Graphviz dot output and generate text-based heatmaps of the distance in hops between DCs. There are two subcommands, two graphical modes, and (roughly) two modes of operation with respect to the location of authority. `samba-tool visualize ntdsconn` looks at NTDS Connections. `samba-tool visualize reps` looks at repsTo and repsFrom objects. In '--distance' mode (default), the distances between DCs are shown in a matrix in the terminal. With '--color=yes', this is depicted as a heatmap. With '--utf8' it is a lttle prettier. In '--dot' mode, Graphviz dot output is generated. When viewed using dot or xdot, this shows the network as a graph with DCs as vertices and connections edges. Certain types of degenerate edges are shown in different colours or line-styles. Normally samba-tool talks to one database; with the '-r' (a.k.a. '--talk-to-remote') option attempts are made to contact all the DCs known to the first database. This is necessary to get sensible results from `samba-tool visualize reps` because the repsFrom/To objects are not replicated, and it can reveal replication issues in other modes. Signed-off-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit ba2306f00d32d2fc55685b388e03e28fd7d97fd7 Author: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Date: Thu Aug 10 15:29:43 2017 +1200 samba_kcc: use new graph module for writing dot files We avoid changing the (annoying) signature of write_dot_file(). Using samba_kcc to write dot files may be deprecated. Signed-off-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit cebad22ce021ce9051fbe664bc699677796e0fb3 Author: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Date: Wed Jan 10 15:25:22 2018 +1300 python/graph: module for generating ASCII and graphviz visualisations Signed-off-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit b4a90a650e969cd65b5104d37e9c57275909b336 Author: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Date: Thu Jan 11 21:56:40 2018 +1300 samba_kcc: respect kcc.read_only flag on RODC Signed-off-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit e579d5bd48dfc7bc93ecc126d42fd4389ded0e28 Author: Douglas Bagnall <doug...@halo.gen.nz> Date: Wed Jan 3 09:20:09 2018 +1300 samba_kcc: kcc.debug module defers to samba.colour Signed-off-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit a46c4a39c4d3be88f76c295b0719c025a1c39c3b Author: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Date: Sun Jan 7 23:17:38 2018 +1300 python: module containing ANSI colour sequences This is going to be used by `samba-tool visualize` and samba_kcc. Signed-off-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit f2762d088001408a706e88e0fe6f46181c01fc3f Author: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Date: Fri Jan 5 16:45:37 2018 +1300 python tests: assert string equality, with diff In the success case this works just like self.assertEqual(), but when things fail you get a better representation of where it went wrong (a unified diff). Signed-off-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit 3f2762d0b716e8a440cefeb1867caa303e21af40 Author: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Date: Fri Jan 12 07:32:59 2018 +1300 samba_kcc: documentation fix Signed-off-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit 6678f33274d4f1784635cd11fc63d9d32a9f9b16 Author: Stefan Metzmacher <me...@samba.org> Date: Fri Jan 12 14:52:45 2018 +0100 s4:torture/samba_tool_drs: demote the test dc at the end of test_samba_tool_replicate_local() Otherwise this taints other tests which might follow. Signed-off-by: Stefan Metzmacher <me...@samba.org> Reviewed-by: Ralph Boehme <s...@samba.org> ----------------------------------------------------------------------- Summary of changes: python/samba/colour.py | 50 ++ python/samba/graph.py | 621 +++++++++++++++++++++++++ python/samba/kcc/__init__.py | 21 +- python/samba/kcc/debug.py | 24 +- python/samba/kcc/graph_utils.py | 37 +- python/samba/kcc/kcc_utils.py | 39 +- python/samba/netcmd/main.py | 1 + python/samba/netcmd/visualize.py | 574 +++++++++++++++++++++++ python/samba/tests/__init__.py | 23 + python/samba/tests/graph.py | 152 ++++++ python/samba/tests/samba_tool/visualize.py | 466 +++++++++++++++++++ python/samba/tests/samba_tool/visualize_drs.py | 110 +++++ selftest/tests.py | 1 + source4/selftest/tests.py | 6 +- source4/torture/drs/python/samba_tool_drs.py | 3 + 15 files changed, 2037 insertions(+), 91 deletions(-) create mode 100644 python/samba/colour.py create mode 100644 python/samba/graph.py create mode 100644 python/samba/netcmd/visualize.py create mode 100644 python/samba/tests/graph.py create mode 100644 python/samba/tests/samba_tool/visualize.py create mode 100644 python/samba/tests/samba_tool/visualize_drs.py Changeset truncated at 500 lines: diff --git a/python/samba/colour.py b/python/samba/colour.py new file mode 100644 index 0000000..b3d9a71 --- /dev/null +++ b/python/samba/colour.py @@ -0,0 +1,50 @@ +# ANSI codes for 4 bit and xterm-256color +# +# Copyright (C) Andrew Bartlett 2018 +# +# Originally written by Douglas Bagnall +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# The 4 bit colours are available as global variables with names like +# RED, DARK_RED, REV_RED (for red background), and REV_DARK_RED. +# +# The 256-colour codes are obtained using xterm_256_color(n), where n +# is the number of the desired colour. + +# C_NORMAL resets to normal, whatever that is +C_NORMAL = "\033[0m" + +UNDERLINE = "\033[4m" + +def _gen_ansi_colours(): + g = globals() + for i, name in enumerate(('BLACK', 'RED', 'GREEN', 'YELLOW', 'BLUE', + 'MAGENTA', 'CYAN', 'WHITE')): + g[name] = "\033[1;3%dm" % i + g['DARK_' + name] = "\033[3%dm" % i + g['REV_' + name] = "\033[1;4%dm" % i + g['REV_DARK_' + name] = "\033[4%dm" % i + +_gen_ansi_colours() + +# kcc.debug uses these aliases (which make visual sense) +PURPLE = DARK_MAGENTA +GREY = DARK_WHITE + +def xterm_256_colour(n, bg=False, bold=False): + weight = '01;' if bold else '' + target = '48' if bg else '38' + + return "\033[%s%s;5;%dm" % (weight, target, int(n)) diff --git a/python/samba/graph.py b/python/samba/graph.py new file mode 100644 index 0000000..f626287 --- /dev/null +++ b/python/samba/graph.py @@ -0,0 +1,621 @@ +# -*- coding: utf-8 -*- +# Graph topology utilities and dot file generation +# +# Copyright (C) Andrew Bartlett 2018. +# +# Written by Douglas Bagnall <douglas.bagn...@catalyst.net.nz> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from __future__ import print_function +from samba import colour +import sys + +FONT_SIZE = 10 + + +def reformat_graph_label(s): + """Break DNs over multiple lines, for better shaped and arguably more + readable nodes. We try to split after commas, and if necessary + after hyphens or failing that in arbitrary places.""" + if len(s) < 12: + return s + + s = s.replace(',', ',\n') + pieces = [] + for p in s.split('\n'): + while len(p) > 20: + if '-' in p[2:20]: + q, p = p.split('-', 1) + else: + n = len(p) / 12 + b = len(p) / n + q, p = p[:b], p[b:] + pieces.append(q + '-') + if p: + pieces.append(p) + + return '\\n'.join(pieces) + + +def quote_graph_label(s, reformat=False): + """Escape a string as graphvis requires.""" + # escaping inside quotes is simple in dot, because only " is escaped. + # there is no need to count backslashes in sequences like \\\\" + s = s.replace('"', '\"') + if reformat: + s = reformat_graph_label(s) + return "%s" % s + + +def shorten_vertex_names(edges, vertices, suffix=',...', aggressive=False): + """Replace the common suffix (in practice, the base DN) of a number of + vertices with a short string (default ",..."). If this seems + pointless because the replaced string is very short or the results + seem strange, the original vertices are retained. + + :param edges: a sequence of vertex pairs to shorten + :param vertices: a sequence of vertices to shorten + :param suffix: the replacement string [",..."] + + :return: tuple of (edges, vertices, replacement) + + If no change is made, the returned edges and vertices will be the + original lists and replacement will be None. + + If a change is made, replacement will be a tuple (new, original) + indicating the new suffix that replaces the old. + """ + vlist = list(set(x[0] for x in edges) | + set(x[1] for x in edges) | + set(vertices)) + + if len(vlist) < 2: + return edges, vertices, None + + # walk backwards along all the strings until we meet a character + # that is not shared by all. + i = -1 + try: + while True: + c = set(x[i] for x in vlist) + if len(c) > 1: + break + i -= 1 + except IndexError: + # We have indexed beyond the start of a string, which should + # only happen if one node is a strict suffix of all others. + return edges, vertices, None + + # add one to get to the last unanimous character. + i += 1 + + # now, we actually really want to split on a comma. So we walk + # back to a comma. + x = vlist[0] + while i < len(x) and x[i] != ',': + i += 1 + + if i >= -len(suffix): + # there is nothing to gain here + return edges, vertices, None + + edges2 = [] + vertices2 = [] + + for a, b in edges: + edges2.append((a[:i] + suffix, b[:i] + suffix)) + for a in vertices: + vertices2.append(a[:i] + suffix) + + replacements = [(suffix, a[i:])] + + if aggressive: + # Remove known common annoying strings + map = dict((v, v) for v in vertices2) + for v in vertices2: + if ',CN=Servers,' not in v: + break + else: + map = dict((k, v.replace(',CN=Servers,', ',**,')) + for k, v in map.iteritems()) + replacements.append(('**', 'CN=Servers')) + + for v in vertices2: + if not v.startswith('CN=NTDS Settings,'): + break + else: + map = dict((k, v.replace('CN=NTDS Settings,', '*,')) + for k, v in map.iteritems()) + replacements.append(('*', 'CN=NTDS Settings')) + + edges2 = [(map.get(a, a), map.get(b, b)) for a, b in edges2] + vertices2 = [map.get(a, a) for a in vertices2] + + return edges2, vertices2, replacements + + +def compile_graph_key(key_items, nodes_above=[], elisions=None, + prefix='key_', width=2): + """Generate a dot file snippet that acts as a legend for a graph. + + :param key_items: sequence of items (is_vertex, style, label) + :param nodes_above: list of vertices (pushes key into right position) + :param elision: tuple (short, full) indicating suffix replacement + :param prefix: string used to generate key node names ["key_"] + :param width: default width of node lines + + Each item in key_items is a tuple of (is_vertex, style, label). + is_vertex is a boolean indicating whether the item is a vertex + (True) or edge (False). Style is a dot style string for the edge + or vertex. label is the text associated with the key item. + """ + edge_lines = [] + edge_names = [] + vertex_lines = [] + vertex_names = [] + order_lines = [] + for i, item in enumerate(key_items): + is_vertex, style, label = item + tag = '%s%d_' % (prefix, i) + label = quote_graph_label(label) + name = '%s_label' % tag + + if is_vertex: + order_lines.append(name) + vertex_names.append(name) + vertex_lines.append('%s[label="%s"; %s]' % + (name, label, style)) + else: + edge_names.append(name) + e1 = '%se1' % tag + e2 = '%se2' % tag + order_lines.append(name) + edge_lines.append('subgraph cluster_%s {' % tag) + edge_lines.append('%s[label=src; color="#000000"; group="%s_g"]' % + (e1, tag)) + edge_lines.append('%s[label=dest; color="#000000"; group="%s_g"]' % + (e2, tag)) + edge_lines.append('%s -> %s [constraint = false; %s]' % (e1, e2, + style)) + edge_lines.append(('%s[shape=plaintext; style=solid; width=%f; ' + 'label="%s\\r"]') % + (name, width, label)) + edge_lines.append('}') + + elision_str = '' + if elisions: + for i, elision in enumerate(reversed(elisions)): + order_lines.append('elision%d' % i) + short, long = elision + if short[0] == ',' and long[0] == ',': + short = short[1:] + long = long[1:] + elision_str += ('\nelision%d[shape=plaintext; style=solid; ' + 'label="\“%s” means “%s”\\r"]\n' + % ((i, short, long))) + + above_lines = [] + if order_lines: + for n in nodes_above: + above_lines.append('"%s" -> %s [style=invis]' % + (n, order_lines[0])) + + s = ('subgraph cluster_key {\n' + 'label="Key";\n' + 'subgraph cluster_key_nodes {\n' + 'label="";\n' + 'color = "invis";\n' + '%s\n' + '}\n' + 'subgraph cluster_key_edges {\n' + 'label="";\n' + 'color = "invis";\n' + '%s\n' + '{%s}\n' + '}\n' + '%s\n' + '}\n' + '%s\n' + '%s [style=invis; weight=9]' + '\n' + % (';\n'.join(vertex_lines), + '\n'.join(edge_lines), + ' '.join(edge_names), + elision_str, + ';\n'.join(above_lines), + ' -> '.join(order_lines), + )) + + return s + + +def dot_graph(vertices, edges, + directed=False, + title=None, + reformat_labels=True, + vertex_colors=None, + edge_colors=None, + edge_labels=None, + vertex_styles=None, + edge_styles=None, + graph_name=None, + shorten_names=False, + key_items=None, + vertex_clusters=None): + """Generate a Graphviz representation of a list of vertices and edges. + + :param vertices: list of vertex names (optional). + :param edges: list of (vertex, vertex) pairs + :param directed: bool: whether the graph is directed + :param title: optional title for the graph + :param reformat_labels: whether to wrap long vertex labels + :param vertex_colors: if not None, a sequence of colours for the vertices + :param edge_colors: if not None, colours for the edges + :param edge_labels: if not None, labels for the edges + :param vertex_styles: if not None, DOT style strings for vertices + :param edge_styles: if not None, DOT style strings for edges + :param graph_name: if not None, name of graph + :param shorten_names: if True, remove common DN suffixes + :param key: (is_vertex, style, description) tuples + :param vertex_clusters: list of subgraph cluster names + + Colour, style, and label lists must be the same length as the + corresponding list of edges or vertices (or None). + + Colours can be HTML RGB strings ("#FF0000") or common names + ("red"), or some other formats you don't want to think about. + + If `vertices` is None, only the vertices mentioned in the edges + are shown, and their appearance can be modified using the + vertex_colors and vertex_styles arguments. Vertices appearing in + the edges but not in the `vertices` list will be shown but their + styles can not be modified. + """ + out = [] + write = out.append + + if vertices is None: + vertices = set(x[0] for x in edges) | set(x[1] for x in edges) + + if shorten_names: + edges, vertices, elisions = shorten_vertex_names(edges, vertices) + else: + elisions = None + + if graph_name is None: + graph_name = 'A_samba_tool_production' + + if directed: + graph_type = 'digraph' + connector = '->' + else: + graph_type = 'graph' + connector = '--' + + write('/* generated by samba */') + write('%s %s {' % (graph_type, graph_name)) + if title is not None: + write('label="%s";' % (title,)) + write('fontsize=%s;\n' % (FONT_SIZE)) + write('node[fontname=Helvetica; fontsize=%s];\n' % (FONT_SIZE)) + + prev_cluster = None + cluster_n = 0 + quoted_vertices = [] + for i, v in enumerate(vertices): + v = quote_graph_label(v, reformat_labels) + quoted_vertices.append(v) + attrs = [] + if vertex_clusters and vertex_clusters[i]: + cluster = vertex_clusters[i] + if cluster != prev_cluster: + if prev_cluster is not None: + write("}") + prev_cluster = cluster + n = quote_graph_label(cluster) + if cluster: + write('subgraph cluster_%d {' % cluster_n) + cluster_n += 1 + write('style = "rounded,dotted";') + write('node [style="filled"; fillcolor=white];') + write('label = "%s";' % n) + + if vertex_styles and vertex_styles[i]: + attrs.append(vertex_styles[i]) + if vertex_colors and vertex_colors[i]: + attrs.append('color="%s"' % quote_graph_label(vertex_colors[i])) + if attrs: + write('"%s" [%s];' % (v, ', '.join(attrs))) + else: + write('"%s";' % (v,)) + + if prev_cluster: + write("}") + + for i, edge in enumerate(edges): + a, b = edge + if a is None: + a = "Missing source value" + if b is None: + b = "Missing destination value" + + a = quote_graph_label(a, reformat_labels) + b = quote_graph_label(b, reformat_labels) + + attrs = [] + if edge_labels: + label = quote_graph_label(edge_labels[i]) + attrs.append('label="%s"' % label) + if edge_colors: + attrs.append('color="%s"' % quote_graph_label(edge_colors[i])) + if edge_styles: + attrs.append(edge_styles[i]) # no quoting + if attrs: + write('"%s" %s "%s" [%s];' % (a, connector, b, ', '.join(attrs))) + else: + write('"%s" %s "%s";' % (a, connector, b)) + + if key_items: + key = compile_graph_key(key_items, nodes_above=quoted_vertices, + elisions=elisions) + write(key) + + write('}\n') + return '\n'.join(out) + + +COLOUR_SETS = { + 'ansi': { + 'alternate rows': (colour.DARK_WHITE, colour.BLACK), + 'disconnected': colour.RED, + 'connected': colour.GREEN, + 'transitive': colour.DARK_YELLOW, + 'header': colour.UNDERLINE, + 'reset': colour.C_NORMAL, + }, + 'ansi-heatmap': { + 'alternate rows': (colour.DARK_WHITE, colour.BLACK), + 'disconnected': colour.REV_RED, + 'connected': colour.REV_GREEN, + 'transitive': colour.REV_DARK_YELLOW, + 'header': colour.UNDERLINE, + 'reset': colour.C_NORMAL, + }, + 'xterm-256color': { + 'alternate rows': (colour.xterm_256_colour(39), + colour.xterm_256_colour(45)), + #'alternate rows': (colour.xterm_256_colour(246), + # colour.xterm_256_colour(247)), + 'disconnected': colour.xterm_256_colour(124, bg=True), + 'connected': colour.xterm_256_colour(112), + 'transitive': colour.xterm_256_colour(214), + 'transitive scale': (colour.xterm_256_colour(190), + colour.xterm_256_colour(226), + colour.xterm_256_colour(220), + colour.xterm_256_colour(214), + colour.xterm_256_colour(208), + ), + 'header': colour.UNDERLINE, + 'reset': colour.C_NORMAL, + }, + 'xterm-256color-heatmap': { + 'alternate rows': (colour.xterm_256_colour(171), + colour.xterm_256_colour(207)), + #'alternate rows': (colour.xterm_256_colour(246), + # colour.xterm_256_colour(247)), + 'disconnected': colour.xterm_256_colour(124, bg=True), + 'connected': colour.xterm_256_colour(112, bg=True), + 'transitive': colour.xterm_256_colour(214, bg=True), + 'transitive scale': (colour.xterm_256_colour(190, bg=True), + colour.xterm_256_colour(226, bg=True), + colour.xterm_256_colour(220, bg=True), + colour.xterm_256_colour(214, bg=True), + colour.xterm_256_colour(208, bg=True), + ), + 'header': colour.UNDERLINE, + 'reset': colour.C_NORMAL, + }, + None: { + 'alternate rows': ('',), + 'disconnected': '', + 'connected': '', + 'transitive': '', + 'header': '', + 'reset': '', + } +} -- Samba Shared Repository