1 # -*- coding: utf-8 -*-
2 # Tests for samba-tool visualize
3 # Copyright (C) Andrew Bartlett 2015, 2018
5 # by Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 """Tests for samba-tool visualize ntdsconn using the test ldif
23 We don't test samba-tool visualize reps here because repsTo and
24 repsFrom are not replicated, and there are no actual remote servers to
30 from io import StringIO
31 from samba.tests.samba_tool.base import SambaToolCmdTest
32 from samba.kcc import ldif_import_export
33 from samba.graph import COLOUR_SETS
34 from samba.param import LoadParm
36 MULTISITE_LDIF = os.path.join(os.environ['SRCDIR_ABS'],
37 "testdata/ldif-utils-test-multisite.ldif")
39 # UNCONNECTED_LDIF is a single site, unconnected 5DC database that was
40 # created using samba-tool domain join in testenv.
41 UNCONNECTED_LDIF = os.path.join(os.environ['SRCDIR_ABS'],
42 "testdata/unconnected-intrasite.ldif")
44 DOMAIN = "DC=ad,DC=samba,DC=example,DC=com"
45 DN_TEMPLATE = "CN=%s,CN=Servers,CN=%s,CN=Sites,CN=Configuration," + DOMAIN
47 MULTISITE_LDIF_DSAS = [
48 ("WIN01", "Default-First-Site-Name"),
61 class StringIOThinksItIsATTY(StringIO):
62 """A StringIO that claims to be a TTY for testing --color=auto,
63 by switching the stringIO class attribute."""
68 def samdb_from_ldif(ldif, tempdir, lp, dsa=None, tag=''):
70 dsa_name = 'default-DSA'
73 dburl = os.path.join(tempdir,
74 ("ldif-to-sambdb-%s-%s" %
76 samdb = ldif_import_export.ldif_to_samdb(dburl, lp, ldif,
81 def collapse_space(s, keep_empty_lines=False):
83 for line in s.splitlines():
84 line = ' '.join(line.strip().split())
85 if line or keep_empty_lines:
87 return '\n'.join(lines)
90 class SambaToolVisualizeLdif(SambaToolCmdTest):
92 super(SambaToolVisualizeLdif, self).setUp()
94 self.samdb, self.dbfile = samdb_from_ldif(MULTISITE_LDIF,
97 self.dburl = 'tdb://' + self.dbfile
100 self.remove_files(self.dbfile)
101 super(SambaToolVisualizeLdif, self).tearDown()
103 def remove_files(self, *files):
105 self.assertTrue(f.startswith(self.tempdir))
108 def test_colour(self):
109 """Ensure the colour output is the same as the monochrome output
110 EXCEPT for the colours, of which the monochrome one should
112 colour_re = re.compile('\033' r'\[[\d;]+m')
113 result, monochrome, err = self.runsubcmd("visualize", "ntdsconn",
116 self.assertCmdSuccess(result, monochrome, err)
117 self.assertFalse(colour_re.findall(monochrome))
119 colour_args = [['--color=yes']]
120 colour_args += [['--color-scheme', x] for x in COLOUR_SETS
123 for args in colour_args:
124 result, out, err = self.runsubcmd("visualize", "ntdsconn",
127 self.assertCmdSuccess(result, out, err)
128 self.assertTrue(colour_re.search(out),
129 f"'{' '.join(args)}' should be colour")
130 uncoloured = colour_re.sub('', out)
132 self.assertStringsEqual(monochrome, uncoloured, strip=True)
134 def assert_colour(self, text, has_colour=True, monochrome=None):
135 colour_re = re.compile('\033' r'\[[\d;]+m')
136 found = colour_re.search(text)
138 self.assertTrue(found, text)
140 self.assertFalse(found, text)
141 if monochrome is not None:
142 uncoloured = colour_re.sub('', text)
143 self.assertStringsEqual(monochrome, uncoloured, strip=True)
145 def test_colour_auto_tty(self):
146 """Assert the behaviour of --colour=auto with and without
147 NO_COLOUR on a fake tty"""
148 result, monochrome, err = self.runsubcmd("visualize", "ntdsconn",
151 self.assertCmdSuccess(result, monochrome, err)
152 self.assert_colour(monochrome, False)
156 cls.stringIO = StringIOThinksItIsATTY
157 old_no_color = os.environ.pop('NO_COLOR', None)
158 # First with no NO_COLOR env var. There should be colour.
159 result, out, err = self.runsubcmd("visualize", "ntdsconn",
163 self.assertCmdSuccess(result, out, err)
164 self.assert_colour(out, True, monochrome)
166 for env, opt, is_colour in [
167 # NO_COLOR='' should be as if no NO_COLOR
168 ['', '--color=auto', True],
169 # NO_COLOR='1': we expect no colour
170 ['1', '--color=auto', False],
171 # NO_COLOR='no': we still expect no colour
172 ['no', '--color=auto', False],
173 # NO_COLOR=' ', alias for 'auto'
174 [' ', '--color=tty', False],
175 # NO_COLOR=' ', alias for 'auto'
176 [' ', '--color=if-tty', False],
177 # NO_COLOR='', alias for 'auto'
178 ['', '--color=tty', True],
179 # NO_COLOR='', alias for 'no'
180 ['', '--color=never', False],
181 # NO_COLOR='x', alias for 'yes' (--color=yes wins)
182 ['x', '--color=force', True],
184 os.environ['NO_COLOR'] = env
187 result, out, err = self.runsubcmd("visualize", "ntdsconn",
192 # optparse makes us do this
193 self.fail(f"optparse rejects {env}, {opt}, {is_colour}")
195 self.assertCmdSuccess(result, out, err)
196 self.assert_colour(out, is_colour, monochrome)
198 # with "-o -" output filename alias for stdout.
199 result, out, err = self.runsubcmd("visualize", "ntdsconn",
204 self.assertCmdSuccess(result, out, err)
205 self.assert_colour(out, is_colour, monochrome)
208 cls.stringIO = StringIO
209 if old_no_color is None:
210 os.environ.pop('NO_COLOR', None)
212 os.environ['NO_COLOR'] = old_no_color
214 def test_import_ldif_xdot(self):
215 """We can't test actual xdot, but using the environment we can
216 persuade samba-tool that a script we write is xdot and ensure
217 it gets the right text.
219 result, expected, err = self.runsubcmd("visualize", "ntdsconn",
223 self.assertCmdSuccess(result, expected, err)
225 # not that we're expecting anything here
226 old_xdot_path = os.environ.get('SAMBA_TOOL_XDOT_PATH')
228 tmpdir = tempfile.mkdtemp()
229 fake_xdot = os.path.join(tmpdir, 'fake_xdot')
230 content = os.path.join(tmpdir, 'content')
231 f = open(fake_xdot, 'w')
232 print('#!/bin/sh', file=f)
233 print('cp $1 %s' % content, file=f)
235 os.chmod(fake_xdot, 0o700)
237 os.environ['SAMBA_TOOL_XDOT_PATH'] = fake_xdot
238 result, empty, err = self.runsubcmd("visualize", "ntdsconn",
239 '--importldif', MULTISITE_LDIF,
250 if old_xdot_path is not None:
251 os.environ['SAMBA_TOOL_XDOT_PATH'] = old_xdot_path
253 del os.environ['SAMBA_TOOL_XDOT_PATH']
255 self.assertCmdSuccess(result, xdot, err)
256 self.assertStringsEqual(expected, xdot, strip=True)
258 def test_import_ldif(self):
259 """Make sure the samba-tool visualize --importldif option gives the
260 same output as using the externally generated db from the same
262 result, s1, err = self.runsubcmd("visualize", "ntdsconn",
265 self.assertCmdSuccess(result, s1, err)
267 result, s2, err = self.runsubcmd("visualize", "ntdsconn",
268 '--importldif', MULTISITE_LDIF,
270 self.assertCmdSuccess(result, s2, err)
272 self.assertStringsEqual(s1, s2)
274 def test_output_file(self):
275 """Check that writing to a file works, with and without
277 # NOTE, we can't really test --color=auto works with a TTY.
278 colour_re = re.compile('\033' r'\[[\d;]+m')
279 result, expected, err = self.runsubcmd("visualize", "ntdsconn",
281 '--color=auto', '-S')
282 self.assertCmdSuccess(result, expected, err)
283 # Not a TTY, so stdout output should be colourless
284 self.assertFalse(colour_re.search(expected))
285 expected = expected.strip()
287 color_auto_file = os.path.join(self.tempdir, 'color-auto')
289 result, out, err = self.runsubcmd("visualize", "ntdsconn",
291 '--color=auto', '-S',
292 '-o', color_auto_file)
293 self.assertCmdSuccess(result, out, err)
294 # We wrote to file, so stdout should be empty
295 self.assertEqual(out, '')
296 f = open(color_auto_file)
297 color_auto = f.read()
299 self.assertStringsEqual(color_auto, expected, strip=True)
300 self.remove_files(color_auto_file)
302 color_no_file = os.path.join(self.tempdir, 'color-no')
303 result, out, err = self.runsubcmd("visualize", "ntdsconn",
307 self.assertCmdSuccess(result, out, err)
308 self.assertEqual(out, '')
309 f = open(color_no_file)
312 self.remove_files(color_no_file)
314 self.assertStringsEqual(color_no, expected, strip=True)
316 color_yes_file = os.path.join(self.tempdir, 'color-yes')
317 result, out, err = self.runsubcmd("visualize", "ntdsconn",
320 '-o', color_yes_file)
321 self.assertCmdSuccess(result, out, err)
322 self.assertEqual(out, '')
323 f = open(color_yes_file)
324 colour_yes = f.read()
326 self.assertNotEqual(colour_yes.strip(), expected)
328 self.remove_files(color_yes_file)
330 # Try the magic filename "-", meaning stdout.
331 # This doesn't exercise the case when stdout is a TTY
332 for c, equal in [('no', True), ('auto', True), ('yes', False)]:
333 result, out, err = self.runsubcmd("visualize", "ntdsconn",
337 self.assertCmdSuccess(result, out, err)
338 self.assertEqual((out.strip() == expected), equal)
341 """Ensure that --utf8 adds at least some expected utf-8, and that it
342 isn't there without --utf8."""
343 result, utf8, err = self.runsubcmd("visualize", "ntdsconn",
345 '--color=no', '-S', '--utf8')
346 self.assertCmdSuccess(result, utf8, err)
348 result, ascii, err = self.runsubcmd("visualize", "ntdsconn",
351 self.assertCmdSuccess(result, ascii, err)
352 for c in ('│', '─', '╭'):
353 self.assertTrue(c in utf8, 'UTF8 should contain %s' % c)
354 self.assertTrue(c not in ascii, 'ASCII should not contain %s' % c)
356 def test_forced_local_dsa(self):
357 # the forced_local_dsa shouldn't make any difference, except
358 # for the title line.
359 result, target, err = self.runsubcmd("visualize", "ntdsconn",
362 self.assertCmdSuccess(result, target, err)
364 target = target.strip().split('\n', 1)[1]
365 for cn, site in MULTISITE_LDIF_DSAS:
366 dsa = DN_TEMPLATE % (cn, site)
367 samdb, dbfile = samdb_from_ldif(MULTISITE_LDIF,
372 result, out, err = self.runsubcmd("visualize", "ntdsconn",
373 '-H', 'tdb://' + dbfile,
375 self.assertCmdSuccess(result, out, err)
376 # Separate out the title line, which will differ in the DN.
377 title, body = out.strip().split('\n', 1)
378 self.assertStringsEqual(target, body)
379 self.assertIn(cn, title)
381 self.remove_files(*files)
383 def test_short_names(self):
384 """Ensure the colour ones are the same as the monochrome ones EXCEPT
385 for the colours, of which the monochrome one should know nothing"""
386 result, short, err = self.runsubcmd("visualize", "ntdsconn",
388 '--color=no', '-S', '--no-key')
389 self.assertCmdSuccess(result, short, err)
390 result, long, err = self.runsubcmd("visualize", "ntdsconn",
392 '--color=no', '--no-key')
393 self.assertCmdSuccess(result, long, err)
395 lines = short.split('\n')
398 short_without_key = []
400 m = re.match(r"'(.{1,2})' stands for '(.+)'", line)
403 replacements.append((len(a), a, b))
404 key_lines.append(line)
406 short_without_key.append(line)
408 short = '\n'.join(short_without_key)
409 # we need to replace longest strings first
410 replacements.sort(reverse=True)
412 # we don't want to shorten the DC name in the header line.
413 long_header, long2short = long.strip().split('\n', 1)
414 for _, a, b in replacements:
415 short2long = short2long.replace(a, b)
416 long2short = long2short.replace(b, a)
418 long2short = '%s\n%s' % (long_header, long2short)
420 # The white space is going to be all wacky, so lets squish it down
421 short2long = collapse_space(short2long)
422 long2short = collapse_space(long2short)
423 short = collapse_space(short)
424 long = collapse_space(long)
426 self.assertStringsEqual(short2long, long, strip=True)
427 self.assertStringsEqual(short, long2short, strip=True)
429 def test_disconnected_ldif_with_key(self):
430 """Test that the 'unconnected' ldif shows up and exactly matches the
432 # This is not truly a disconnected graph because the
433 # vampre/local/promoted DCs are in there and they have
434 # relationships, and SERVER2 and SERVER3 for some reason refer
437 samdb, dbfile = samdb_from_ldif(UNCONNECTED_LDIF,
439 self.lp, tag='disconnected')
440 dburl = 'tdb://' + dbfile
441 result, output, err = self.runsubcmd("visualize", "ntdsconn",
444 self.remove_files(dbfile)
445 self.assertCmdSuccess(result, output, err)
446 self.assertStringsEqual(output,
447 EXPECTED_DISTANCE_GRAPH_WITH_KEY)
449 def test_dot_ntdsconn(self):
450 """Graphviz NTDS Connection output"""
451 result, dot, err = self.runsubcmd("visualize", "ntdsconn",
453 '--color=no', '-S', '--dot',
455 self.assertCmdSuccess(result, dot, err)
456 self.assertStringsEqual(EXPECTED_DOT_MULTISITE_NO_KEY, dot)
458 def test_dot_ntdsconn_disconnected(self):
459 """Graphviz NTDS Connection output from disconnected graph"""
460 samdb, dbfile = samdb_from_ldif(UNCONNECTED_LDIF,
462 self.lp, tag='disconnected')
464 result, dot, err = self.runsubcmd("visualize", "ntdsconn",
465 '-H', 'tdb://' + dbfile,
466 '--color=no', '-S', '--dot',
468 self.assertCmdSuccess(result, dot, err)
469 self.remove_files(dbfile)
470 self.assertStringsEqual(EXPECTED_DOT_NTDSCONN_DISCONNECTED, dot,
473 def test_dot_ntdsconn_disconnected_to_file(self):
474 """Graphviz NTDS Connection output into a file"""
475 samdb, dbfile = samdb_from_ldif(UNCONNECTED_LDIF,
477 self.lp, tag='disconnected')
479 dot_file = os.path.join(self.tempdir, 'dotfile')
481 result, dot, err = self.runsubcmd("visualize", "ntdsconn",
482 '-H', 'tdb://' + dbfile,
483 '--color=no', '-S', '--dot',
485 self.assertCmdSuccess(result, dot, err)
489 self.assertStringsEqual(EXPECTED_DOT_NTDSCONN_DISCONNECTED, dot)
491 self.remove_files(dbfile, dot_file)
494 EXPECTED_DOT_MULTISITE_NO_KEY = r"""/* generated by samba */
495 digraph A_samba_tool_production {
496 label="NTDS Connections known to CN=WIN01,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=ad,DC=samba,DC=example,DC=com";
499 node[fontname=Helvetica; fontsize=10];
501 "CN=NTDS Settings,\nCN=WIN01,\nCN=Servers,\nCN=Default-\nFirst-Site-Name,\n...";
502 "CN=NTDS Settings,\nCN=WIN02,\nCN=Servers,\nCN=Site-2,\n...";
503 "CN=NTDS Settings,\nCN=WIN03,\nCN=Servers,\nCN=Site-2,\n...";
504 "CN=NTDS Settings,\nCN=WIN04,\nCN=Servers,\nCN=Site-2,\n...";
505 "CN=NTDS Settings,\nCN=WIN05,\nCN=Servers,\nCN=Site-2,\n...";
506 "CN=NTDS Settings,\nCN=WIN06,\nCN=Servers,\nCN=Site-3,\n...";
507 "CN=NTDS Settings,\nCN=WIN07,\nCN=Servers,\nCN=Site-4,\n...";
508 "CN=NTDS Settings,\nCN=WIN08,\nCN=Servers,\nCN=Site-4,\n...";
509 "CN=NTDS Settings,\nCN=WIN09,\nCN=Servers,\nCN=Site-5,\n...";
510 "CN=NTDS Settings,\nCN=WIN10,\nCN=Servers,\nCN=Site-5,\n...";
511 "CN=NTDS Settings,\nCN=WIN01,\nCN=Servers,\nCN=Default-\nFirst-Site-Name,\n..." -> "CN=NTDS Settings,\nCN=WIN03,\nCN=Servers,\nCN=Site-2,\n..." [color="#000000", ];
512 "CN=NTDS Settings,\nCN=WIN01,\nCN=Servers,\nCN=Default-\nFirst-Site-Name,\n..." -> "CN=NTDS Settings,\nCN=WIN06,\nCN=Servers,\nCN=Site-3,\n..." [color="#000000", ];
513 "CN=NTDS Settings,\nCN=WIN01,\nCN=Servers,\nCN=Default-\nFirst-Site-Name,\n..." -> "CN=NTDS Settings,\nCN=WIN07,\nCN=Servers,\nCN=Site-4,\n..." [color="#000000", ];
514 "CN=NTDS Settings,\nCN=WIN01,\nCN=Servers,\nCN=Default-\nFirst-Site-Name,\n..." -> "CN=NTDS Settings,\nCN=WIN08,\nCN=Servers,\nCN=Site-4,\n..." [color="#000000", ];
515 "CN=NTDS Settings,\nCN=WIN01,\nCN=Servers,\nCN=Default-\nFirst-Site-Name,\n..." -> "CN=NTDS Settings,\nCN=WIN10,\nCN=Servers,\nCN=Site-5,\n..." [color="#000000", ];
516 "CN=NTDS Settings,\nCN=WIN02,\nCN=Servers,\nCN=Site-2,\n..." -> "CN=NTDS Settings,\nCN=WIN04,\nCN=Servers,\nCN=Site-2,\n..." [color="#000000", ];
517 "CN=NTDS Settings,\nCN=WIN02,\nCN=Servers,\nCN=Site-2,\n..." -> "CN=NTDS Settings,\nCN=WIN05,\nCN=Servers,\nCN=Site-2,\n..." [color="#000000", ];
518 "CN=NTDS Settings,\nCN=WIN03,\nCN=Servers,\nCN=Site-2,\n..." -> "CN=NTDS Settings,\nCN=WIN04,\nCN=Servers,\nCN=Site-2,\n..." [color="#000000", ];
519 "CN=NTDS Settings,\nCN=WIN03,\nCN=Servers,\nCN=Site-2,\n..." -> "CN=NTDS Settings,\nCN=WIN05,\nCN=Servers,\nCN=Site-2,\n..." [color="#000000", ];
520 "CN=NTDS Settings,\nCN=WIN04,\nCN=Servers,\nCN=Site-2,\n..." -> "CN=NTDS Settings,\nCN=WIN01,\nCN=Servers,\nCN=Default-\nFirst-Site-Name,\n..." [color="#000000", ];
521 "CN=NTDS Settings,\nCN=WIN04,\nCN=Servers,\nCN=Site-2,\n..." -> "CN=NTDS Settings,\nCN=WIN02,\nCN=Servers,\nCN=Site-2,\n..." [color="#000000", ];
522 "CN=NTDS Settings,\nCN=WIN04,\nCN=Servers,\nCN=Site-2,\n..." -> "CN=NTDS Settings,\nCN=WIN03,\nCN=Servers,\nCN=Site-2,\n..." [color="#000000", ];
523 "CN=NTDS Settings,\nCN=WIN05,\nCN=Servers,\nCN=Site-2,\n..." -> "CN=NTDS Settings,\nCN=WIN02,\nCN=Servers,\nCN=Site-2,\n..." [color="#000000", ];
524 "CN=NTDS Settings,\nCN=WIN05,\nCN=Servers,\nCN=Site-2,\n..." -> "CN=NTDS Settings,\nCN=WIN03,\nCN=Servers,\nCN=Site-2,\n..." [color="#000000", ];
525 "CN=NTDS Settings,\nCN=WIN07,\nCN=Servers,\nCN=Site-4,\n..." -> "CN=NTDS Settings,\nCN=WIN01,\nCN=Servers,\nCN=Default-\nFirst-Site-Name,\n..." [color="#000000", ];
526 "CN=NTDS Settings,\nCN=WIN09,\nCN=Servers,\nCN=Site-5,\n..." -> "CN=NTDS Settings,\nCN=WIN10,\nCN=Servers,\nCN=Site-5,\n..." [color="#000000", ];
527 "CN=NTDS Settings,\nCN=WIN10,\nCN=Servers,\nCN=Site-5,\n..." -> "CN=NTDS Settings,\nCN=WIN01,\nCN=Servers,\nCN=Default-\nFirst-Site-Name,\n..." [color="#000000", ];
528 "CN=NTDS Settings,\nCN=WIN10,\nCN=Servers,\nCN=Site-5,\n..." -> "CN=NTDS Settings,\nCN=WIN09,\nCN=Servers,\nCN=Site-5,\n..." [color="#000000", ];
534 EXPECTED_DOT_NTDSCONN_DISCONNECTED = r"""/* generated by samba */
535 digraph A_samba_tool_production {
536 label="NTDS Connections known to CN=LOCALDC,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=samba,DC=example,DC=com";
539 node[fontname=Helvetica; fontsize=10];
541 "CN=NTDS Settings,\nCN=CLIENT,\n...";
542 "CN=NTDS Settings,\nCN=LOCALDC,\n...";
543 "CN=NTDS Settings,\nCN=PROMOTEDVDC,\n...";
544 "CN=NTDS Settings,\nCN=SERVER1,\n...";
545 "CN=NTDS Settings,\nCN=SERVER2,\n...";
546 "CN=NTDS Settings,\nCN=SERVER3,\n...";
547 "CN=NTDS Settings,\nCN=SERVER4,\n...";
548 "CN=NTDS Settings,\nCN=SERVER5,\n...";
549 "CN=NTDS Settings,\nCN=LOCALDC,\n..." -> "CN=NTDS Settings,\nCN=PROMOTEDVDC,\n..." [color="#000000", ];
550 "CN=NTDS Settings,\nCN=PROMOTEDVDC,\n..." -> "CN=NTDS Settings,\nCN=LOCALDC,\n..." [color="#000000", ];
551 "CN=NTDS Settings,\nCN=SERVER2,\n..." -> "CN=NTDS Settings,\nCN=PROMOTEDVDC,\n..." [color="#000000", ];
552 "CN=NTDS Settings,\nCN=SERVER3,\n..." -> "CN=NTDS Settings,\nCN=LOCALDC,\n..." [color="#000000", ];
553 subgraph cluster_key {
555 subgraph cluster_key_nodes {
560 subgraph cluster_key_edges {
563 subgraph cluster_key_0_ {
564 key_0_e1[label=src; color="#000000"; group="key_0__g"]
565 key_0_e2[label=dest; color="#000000"; group="key_0__g"]
566 key_0_e1 -> key_0_e2 [constraint = false; color="#000000"]
567 key_0__label[shape=plaintext; style=solid; width=2.000000; label="NTDS Connection\r"]
572 elision0[shape=plaintext; style=solid; label="\“...” means “CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=samba,DC=example,DC=com”\r"]
575 "CN=NTDS Settings,\nCN=CLIENT,\n..." -> key_0__label [style=invis];
576 "CN=NTDS Settings,\nCN=LOCALDC,\n..." -> key_0__label [style=invis];
577 "CN=NTDS Settings,\nCN=PROMOTEDVDC,\n..." -> key_0__label [style=invis];
578 "CN=NTDS Settings,\nCN=SERVER1,\n..." -> key_0__label [style=invis];
579 "CN=NTDS Settings,\nCN=SERVER2,\n..." -> key_0__label [style=invis];
580 "CN=NTDS Settings,\nCN=SERVER3,\n..." -> key_0__label [style=invis];
581 "CN=NTDS Settings,\nCN=SERVER4,\n..." -> key_0__label [style=invis];
582 "CN=NTDS Settings,\nCN=SERVER5,\n..." -> key_0__label [style=invis]
583 key_0__label -> elision0 [style=invis; weight=9]
588 EXPECTED_DISTANCE_GRAPH_WITH_KEY = """
589 NTDS Connections known to CN=LOCALDC,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=samba,DC=example,DC=com
592 ,-------- *,CN=CLIENT+
593 |,------- *,CN=LOCALDC+
594 ||,------ *,CN=PROMOTEDVDC+
595 |||,----- *,CN=SERVER1+
596 ||||,---- *,CN=SERVER2+
597 |||||,--- *,CN=SERVER3+
598 ||||||,-- *,CN=SERVER4+
599 source |||||||,- *,CN=SERVER5+
600 *,CN=CLIENT+ 0-------
601 *,CN=LOCALDC+ -01-----
602 *,CN=PROMOTEDVDC+ -10-----
603 *,CN=SERVER1+ ---0----
604 *,CN=SERVER2+ -21-0---
605 *,CN=SERVER3+ -12--0--
606 *,CN=SERVER4+ ------0-
607 *,CN=SERVER5+ -------0
609 '*' stands for 'CN=NTDS Settings'
610 '+' stands for ',CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=samba,DC=example,DC=com'
612 Data can get from source to destination in the indicated number of steps.
613 0 means zero steps (it is the same DC)
614 1 means a direct link
615 2 means a transitive link involving two steps (i.e. one intermediate DC)
616 - means there is no connection, even through other DCs