ctdb-scripts: Improve update and listing code
[samba4-gss.git] / python / samba / tests / samba_tool / visualize.py
blobf736129db8caa6333edb63d2c73dabc73facbb6e
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
21 topologies.
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
25 query.
26 """
27 import os
28 import tempfile
29 import re
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"),
49 ("WIN08", "Site-4"),
50 ("WIN07", "Site-4"),
51 ("WIN06", "Site-3"),
52 ("WIN09", "Site-5"),
53 ("WIN10", "Site-5"),
54 ("WIN02", "Site-2"),
55 ("WIN04", "Site-2"),
56 ("WIN03", "Site-2"),
57 ("WIN05", "Site-2"),
61 class StringIOThinksItIsATTY(StringIO):
62 """A StringIO that claims to be a TTY for testing --color=auto,
63 by switching the stringIO class attribute."""
64 def isatty(self):
65 return True
68 def samdb_from_ldif(ldif, tempdir, lp, dsa=None, tag=''):
69 if dsa is None:
70 dsa_name = 'default-DSA'
71 else:
72 dsa_name = dsa[:5]
73 dburl = os.path.join(tempdir,
74 ("ldif-to-sambdb-%s-%s" %
75 (tag, dsa_name)))
76 samdb = ldif_import_export.ldif_to_samdb(dburl, lp, ldif,
77 forced_local_dsa=dsa)
78 return (samdb, dburl)
81 def collapse_space(s, keep_empty_lines=False):
82 lines = []
83 for line in s.splitlines():
84 line = ' '.join(line.strip().split())
85 if line or keep_empty_lines:
86 lines.append(line)
87 return '\n'.join(lines)
90 class SambaToolVisualizeLdif(SambaToolCmdTest):
91 def setUp(self):
92 super().setUp()
93 self.lp = LoadParm()
94 self.samdb, self.dbfile = samdb_from_ldif(MULTISITE_LDIF,
95 self.tempdir,
96 self.lp)
97 self.dburl = 'tdb://' + self.dbfile
99 def tearDown(self):
100 self.remove_files(self.dbfile)
101 super().tearDown()
103 def remove_files(self, *files):
104 for f in files:
105 self.assertTrue(f.startswith(self.tempdir))
106 os.unlink(f)
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
111 know nothing."""
112 colour_re = re.compile('\033' r'\[[\d;]+m')
113 result, monochrome, err = self.runsubcmd("visualize", "ntdsconn",
114 '-H', self.dburl,
115 '--color=no', '-S')
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
121 if x is not None]
123 for args in colour_args:
124 result, out, err = self.runsubcmd("visualize", "ntdsconn",
125 '-H', self.dburl,
126 '-S', *args)
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)
137 if has_colour:
138 self.assertTrue(found, text)
139 else:
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",
149 '-H', self.dburl,
150 '--color=no', '-S')
151 self.assertCmdSuccess(result, monochrome, err)
152 self.assert_colour(monochrome, False)
153 cls = self.__class__
155 try:
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",
160 '-H', self.dburl,
161 '-S',
162 '--color=auto')
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
186 try:
187 result, out, err = self.runsubcmd("visualize", "ntdsconn",
188 '-H', self.dburl,
189 '-S',
190 opt)
191 except SystemExit:
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",
200 '-H', self.dburl,
201 '-S',
202 opt,
203 '-o', '-')
204 self.assertCmdSuccess(result, out, err)
205 self.assert_colour(out, is_colour, monochrome)
207 finally:
208 cls.stringIO = StringIO
209 if old_no_color is None:
210 os.environ.pop('NO_COLOR', None)
211 else:
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",
220 '-H', self.dburl,
221 '--color=no', '-S',
222 '--dot')
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)
234 f.close()
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,
240 '--color=no', '-S',
241 '--xdot')
243 f = open(content)
244 xdot = f.read()
245 f.close()
246 os.remove(fake_xdot)
247 os.remove(content)
248 os.rmdir(tmpdir)
250 if old_xdot_path is not None:
251 os.environ['SAMBA_TOOL_XDOT_PATH'] = old_xdot_path
252 else:
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
261 LDIF."""
262 result, s1, err = self.runsubcmd("visualize", "ntdsconn",
263 '-H', self.dburl,
264 '--color=no', '-S')
265 self.assertCmdSuccess(result, s1, err)
267 result, s2, err = self.runsubcmd("visualize", "ntdsconn",
268 '--importldif', MULTISITE_LDIF,
269 '--color=no', '-S')
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
276 --color=auto."""
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",
280 '-H', self.dburl,
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",
290 '-H', self.dburl,
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()
298 f.close()
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",
304 '-H', self.dburl,
305 '--color=no', '-S',
306 '-o', color_no_file)
307 self.assertCmdSuccess(result, out, err)
308 self.assertEqual(out, '')
309 f = open(color_no_file)
310 color_no = f.read()
311 f.close()
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",
318 '-H', self.dburl,
319 '--color=yes', '-S',
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()
325 f.close()
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",
334 '-H', self.dburl,
335 '--color', c,
336 '-S', '-o', '-')
337 self.assertCmdSuccess(result, out, err)
338 self.assertEqual((out.strip() == expected), equal)
340 def test_utf8(self):
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",
344 '-H', self.dburl,
345 '--color=no', '-S', '--utf8')
346 self.assertCmdSuccess(result, utf8, err)
348 result, ascii, err = self.runsubcmd("visualize", "ntdsconn",
349 '-H', self.dburl,
350 '--color=no', '-S')
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",
360 '-H', self.dburl,
361 '--color=no', '-S')
362 self.assertCmdSuccess(result, target, err)
363 files = []
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,
368 self.tempdir,
369 self.lp, dsa,
370 tag=cn)
372 result, out, err = self.runsubcmd("visualize", "ntdsconn",
373 '-H', 'tdb://' + dbfile,
374 '--color=no', '-S')
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)
380 files.append(dbfile)
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",
387 '-H', self.dburl,
388 '--color=no', '-S', '--no-key')
389 self.assertCmdSuccess(result, short, err)
390 result, long, err = self.runsubcmd("visualize", "ntdsconn",
391 '-H', self.dburl,
392 '--color=no', '--no-key')
393 self.assertCmdSuccess(result, long, err)
395 lines = short.split('\n')
396 replacements = []
397 key_lines = ['']
398 short_without_key = []
399 for line in lines:
400 m = re.match(r"'(.{1,2})' stands for '(.+)'", line)
401 if m:
402 a, b = m.groups()
403 replacements.append((len(a), a, b))
404 key_lines.append(line)
405 else:
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)
411 short2long = short
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
431 expected output."""
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
435 # to them.
437 samdb, dbfile = samdb_from_ldif(UNCONNECTED_LDIF,
438 self.tempdir,
439 self.lp, tag='disconnected')
440 dburl = 'tdb://' + dbfile
441 result, output, err = self.runsubcmd("visualize", "ntdsconn",
442 '-H', dburl,
443 '--color=no', '-S')
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",
452 '-H', self.dburl,
453 '--color=no', '-S', '--dot',
454 '--no-key')
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,
461 self.tempdir,
462 self.lp, tag='disconnected')
464 result, dot, err = self.runsubcmd("visualize", "ntdsconn",
465 '-H', 'tdb://' + dbfile,
466 '--color=no', '-S', '--dot',
467 '-o', '-')
468 self.assertCmdSuccess(result, dot, err)
469 self.remove_files(dbfile)
470 self.assertStringsEqual(EXPECTED_DOT_NTDSCONN_DISCONNECTED, dot,
471 strip=True)
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,
476 self.tempdir,
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',
484 '-o', dot_file)
485 self.assertCmdSuccess(result, dot, err)
486 f = open(dot_file)
487 dot = f.read()
488 f.close()
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";
497 fontsize=10;
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";
537 fontsize=10;
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 {
554 label="Key";
555 subgraph cluster_key_nodes {
556 label="";
557 color = "invis";
560 subgraph cluster_key_edges {
561 label="";
562 color = "invis";
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"]
569 {key_0__label}
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
591 destination
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