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
):
94 self
.samdb
, self
.dbfile
= samdb_from_ldif(MULTISITE_LDIF
,
97 self
.dburl
= 'tdb://' + self
.dbfile
100 self
.remove_files(self
.dbfile
)
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