1
0
mirror of https://github.com/samba-team/samba.git synced 2024-12-22 13:34:15 +03:00
samba-mirror/python/samba/tests/samba_tool/visualize.py
Rob van der Linde 6ac4833678 python: tests: update all super calls to python 3 style in tests
Signed-off-by: Rob van der Linde <rob@catalyst.net.nz>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>

[abartlet@samba.org Some python2 style super() calls remain due
 to being an actual, even if reasonable, behaviour change]
2023-11-30 01:05:32 +00:00

619 lines
28 KiB
Python

# -*- coding: utf-8 -*-
# Tests for samba-tool visualize
# Copyright (C) Andrew Bartlett 2015, 2018
#
# by Douglas Bagnall <douglas.bagnall@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/>.
#
"""Tests for samba-tool visualize ntdsconn using the test ldif
topologies.
We don't test samba-tool visualize reps here because repsTo and
repsFrom are not replicated, and there are no actual remote servers to
query.
"""
import os
import tempfile
import re
from io import StringIO
from samba.tests.samba_tool.base import SambaToolCmdTest
from samba.kcc import ldif_import_export
from samba.graph import COLOUR_SETS
from samba.param import LoadParm
MULTISITE_LDIF = os.path.join(os.environ['SRCDIR_ABS'],
"testdata/ldif-utils-test-multisite.ldif")
# UNCONNECTED_LDIF is a single site, unconnected 5DC database that was
# created using samba-tool domain join in testenv.
UNCONNECTED_LDIF = os.path.join(os.environ['SRCDIR_ABS'],
"testdata/unconnected-intrasite.ldif")
DOMAIN = "DC=ad,DC=samba,DC=example,DC=com"
DN_TEMPLATE = "CN=%s,CN=Servers,CN=%s,CN=Sites,CN=Configuration," + DOMAIN
MULTISITE_LDIF_DSAS = [
("WIN01", "Default-First-Site-Name"),
("WIN08", "Site-4"),
("WIN07", "Site-4"),
("WIN06", "Site-3"),
("WIN09", "Site-5"),
("WIN10", "Site-5"),
("WIN02", "Site-2"),
("WIN04", "Site-2"),
("WIN03", "Site-2"),
("WIN05", "Site-2"),
]
class StringIOThinksItIsATTY(StringIO):
"""A StringIO that claims to be a TTY for testing --color=auto,
by switching the stringIO class attribute."""
def isatty(self):
return True
def samdb_from_ldif(ldif, tempdir, lp, dsa=None, tag=''):
if dsa is None:
dsa_name = 'default-DSA'
else:
dsa_name = dsa[:5]
dburl = os.path.join(tempdir,
("ldif-to-sambdb-%s-%s" %
(tag, dsa_name)))
samdb = ldif_import_export.ldif_to_samdb(dburl, lp, ldif,
forced_local_dsa=dsa)
return (samdb, dburl)
def collapse_space(s, keep_empty_lines=False):
lines = []
for line in s.splitlines():
line = ' '.join(line.strip().split())
if line or keep_empty_lines:
lines.append(line)
return '\n'.join(lines)
class SambaToolVisualizeLdif(SambaToolCmdTest):
def setUp(self):
super().setUp()
self.lp = LoadParm()
self.samdb, self.dbfile = samdb_from_ldif(MULTISITE_LDIF,
self.tempdir,
self.lp)
self.dburl = 'tdb://' + self.dbfile
def tearDown(self):
self.remove_files(self.dbfile)
super().tearDown()
def remove_files(self, *files):
for f in files:
self.assertTrue(f.startswith(self.tempdir))
os.unlink(f)
def test_colour(self):
"""Ensure the colour output is the same as the monochrome output
EXCEPT for the colours, of which the monochrome one should
know nothing."""
colour_re = re.compile('\033' r'\[[\d;]+m')
result, monochrome, err = self.runsubcmd("visualize", "ntdsconn",
'-H', self.dburl,
'--color=no', '-S')
self.assertCmdSuccess(result, monochrome, err)
self.assertFalse(colour_re.findall(monochrome))
colour_args = [['--color=yes']]
colour_args += [['--color-scheme', x] for x in COLOUR_SETS
if x is not None]
for args in colour_args:
result, out, err = self.runsubcmd("visualize", "ntdsconn",
'-H', self.dburl,
'-S', *args)
self.assertCmdSuccess(result, out, err)
self.assertTrue(colour_re.search(out),
f"'{' '.join(args)}' should be colour")
uncoloured = colour_re.sub('', out)
self.assertStringsEqual(monochrome, uncoloured, strip=True)
def assert_colour(self, text, has_colour=True, monochrome=None):
colour_re = re.compile('\033' r'\[[\d;]+m')
found = colour_re.search(text)
if has_colour:
self.assertTrue(found, text)
else:
self.assertFalse(found, text)
if monochrome is not None:
uncoloured = colour_re.sub('', text)
self.assertStringsEqual(monochrome, uncoloured, strip=True)
def test_colour_auto_tty(self):
"""Assert the behaviour of --colour=auto with and without
NO_COLOUR on a fake tty"""
result, monochrome, err = self.runsubcmd("visualize", "ntdsconn",
'-H', self.dburl,
'--color=no', '-S')
self.assertCmdSuccess(result, monochrome, err)
self.assert_colour(monochrome, False)
cls = self.__class__
try:
cls.stringIO = StringIOThinksItIsATTY
old_no_color = os.environ.pop('NO_COLOR', None)
# First with no NO_COLOR env var. There should be colour.
result, out, err = self.runsubcmd("visualize", "ntdsconn",
'-H', self.dburl,
'-S',
'--color=auto')
self.assertCmdSuccess(result, out, err)
self.assert_colour(out, True, monochrome)
for env, opt, is_colour in [
# NO_COLOR='' should be as if no NO_COLOR
['', '--color=auto', True],
# NO_COLOR='1': we expect no colour
['1', '--color=auto', False],
# NO_COLOR='no': we still expect no colour
['no', '--color=auto', False],
# NO_COLOR=' ', alias for 'auto'
[' ', '--color=tty', False],
# NO_COLOR=' ', alias for 'auto'
[' ', '--color=if-tty', False],
# NO_COLOR='', alias for 'auto'
['', '--color=tty', True],
# NO_COLOR='', alias for 'no'
['', '--color=never', False],
# NO_COLOR='x', alias for 'yes' (--color=yes wins)
['x', '--color=force', True],
]:
os.environ['NO_COLOR'] = env
try:
result, out, err = self.runsubcmd("visualize", "ntdsconn",
'-H', self.dburl,
'-S',
opt)
except SystemExit:
# optparse makes us do this
self.fail(f"optparse rejects {env}, {opt}, {is_colour}")
self.assertCmdSuccess(result, out, err)
self.assert_colour(out, is_colour, monochrome)
# with "-o -" output filename alias for stdout.
result, out, err = self.runsubcmd("visualize", "ntdsconn",
'-H', self.dburl,
'-S',
opt,
'-o', '-')
self.assertCmdSuccess(result, out, err)
self.assert_colour(out, is_colour, monochrome)
finally:
cls.stringIO = StringIO
if old_no_color is None:
os.environ.pop('NO_COLOR', None)
else:
os.environ['NO_COLOR'] = old_no_color
def test_import_ldif_xdot(self):
"""We can't test actual xdot, but using the environment we can
persuade samba-tool that a script we write is xdot and ensure
it gets the right text.
"""
result, expected, err = self.runsubcmd("visualize", "ntdsconn",
'-H', self.dburl,
'--color=no', '-S',
'--dot')
self.assertCmdSuccess(result, expected, err)
# not that we're expecting anything here
old_xdot_path = os.environ.get('SAMBA_TOOL_XDOT_PATH')
tmpdir = tempfile.mkdtemp()
fake_xdot = os.path.join(tmpdir, 'fake_xdot')
content = os.path.join(tmpdir, 'content')
f = open(fake_xdot, 'w')
print('#!/bin/sh', file=f)
print('cp $1 %s' % content, file=f)
f.close()
os.chmod(fake_xdot, 0o700)
os.environ['SAMBA_TOOL_XDOT_PATH'] = fake_xdot
result, empty, err = self.runsubcmd("visualize", "ntdsconn",
'--importldif', MULTISITE_LDIF,
'--color=no', '-S',
'--xdot')
f = open(content)
xdot = f.read()
f.close()
os.remove(fake_xdot)
os.remove(content)
os.rmdir(tmpdir)
if old_xdot_path is not None:
os.environ['SAMBA_TOOL_XDOT_PATH'] = old_xdot_path
else:
del os.environ['SAMBA_TOOL_XDOT_PATH']
self.assertCmdSuccess(result, xdot, err)
self.assertStringsEqual(expected, xdot, strip=True)
def test_import_ldif(self):
"""Make sure the samba-tool visualize --importldif option gives the
same output as using the externally generated db from the same
LDIF."""
result, s1, err = self.runsubcmd("visualize", "ntdsconn",
'-H', self.dburl,
'--color=no', '-S')
self.assertCmdSuccess(result, s1, err)
result, s2, err = self.runsubcmd("visualize", "ntdsconn",
'--importldif', MULTISITE_LDIF,
'--color=no', '-S')
self.assertCmdSuccess(result, s2, err)
self.assertStringsEqual(s1, s2)
def test_output_file(self):
"""Check that writing to a file works, with and without
--color=auto."""
# NOTE, we can't really test --color=auto works with a TTY.
colour_re = re.compile('\033' r'\[[\d;]+m')
result, expected, err = self.runsubcmd("visualize", "ntdsconn",
'-H', self.dburl,
'--color=auto', '-S')
self.assertCmdSuccess(result, expected, err)
# Not a TTY, so stdout output should be colourless
self.assertFalse(colour_re.search(expected))
expected = expected.strip()
color_auto_file = os.path.join(self.tempdir, 'color-auto')
result, out, err = self.runsubcmd("visualize", "ntdsconn",
'-H', self.dburl,
'--color=auto', '-S',
'-o', color_auto_file)
self.assertCmdSuccess(result, out, err)
# We wrote to file, so stdout should be empty
self.assertEqual(out, '')
f = open(color_auto_file)
color_auto = f.read()
f.close()
self.assertStringsEqual(color_auto, expected, strip=True)
self.remove_files(color_auto_file)
color_no_file = os.path.join(self.tempdir, 'color-no')
result, out, err = self.runsubcmd("visualize", "ntdsconn",
'-H', self.dburl,
'--color=no', '-S',
'-o', color_no_file)
self.assertCmdSuccess(result, out, err)
self.assertEqual(out, '')
f = open(color_no_file)
color_no = f.read()
f.close()
self.remove_files(color_no_file)
self.assertStringsEqual(color_no, expected, strip=True)
color_yes_file = os.path.join(self.tempdir, 'color-yes')
result, out, err = self.runsubcmd("visualize", "ntdsconn",
'-H', self.dburl,
'--color=yes', '-S',
'-o', color_yes_file)
self.assertCmdSuccess(result, out, err)
self.assertEqual(out, '')
f = open(color_yes_file)
colour_yes = f.read()
f.close()
self.assertNotEqual(colour_yes.strip(), expected)
self.remove_files(color_yes_file)
# Try the magic filename "-", meaning stdout.
# This doesn't exercise the case when stdout is a TTY
for c, equal in [('no', True), ('auto', True), ('yes', False)]:
result, out, err = self.runsubcmd("visualize", "ntdsconn",
'-H', self.dburl,
'--color', c,
'-S', '-o', '-')
self.assertCmdSuccess(result, out, err)
self.assertEqual((out.strip() == expected), equal)
def test_utf8(self):
"""Ensure that --utf8 adds at least some expected utf-8, and that it
isn't there without --utf8."""
result, utf8, err = self.runsubcmd("visualize", "ntdsconn",
'-H', self.dburl,
'--color=no', '-S', '--utf8')
self.assertCmdSuccess(result, utf8, err)
result, ascii, err = self.runsubcmd("visualize", "ntdsconn",
'-H', self.dburl,
'--color=no', '-S')
self.assertCmdSuccess(result, ascii, err)
for c in ('', '', ''):
self.assertTrue(c in utf8, 'UTF8 should contain %s' % c)
self.assertTrue(c not in ascii, 'ASCII should not contain %s' % c)
def test_forced_local_dsa(self):
# the forced_local_dsa shouldn't make any difference, except
# for the title line.
result, target, err = self.runsubcmd("visualize", "ntdsconn",
'-H', self.dburl,
'--color=no', '-S')
self.assertCmdSuccess(result, target, err)
files = []
target = target.strip().split('\n', 1)[1]
for cn, site in MULTISITE_LDIF_DSAS:
dsa = DN_TEMPLATE % (cn, site)
samdb, dbfile = samdb_from_ldif(MULTISITE_LDIF,
self.tempdir,
self.lp, dsa,
tag=cn)
result, out, err = self.runsubcmd("visualize", "ntdsconn",
'-H', 'tdb://' + dbfile,
'--color=no', '-S')
self.assertCmdSuccess(result, out, err)
# Separate out the title line, which will differ in the DN.
title, body = out.strip().split('\n', 1)
self.assertStringsEqual(target, body)
self.assertIn(cn, title)
files.append(dbfile)
self.remove_files(*files)
def test_short_names(self):
"""Ensure the colour ones are the same as the monochrome ones EXCEPT
for the colours, of which the monochrome one should know nothing"""
result, short, err = self.runsubcmd("visualize", "ntdsconn",
'-H', self.dburl,
'--color=no', '-S', '--no-key')
self.assertCmdSuccess(result, short, err)
result, long, err = self.runsubcmd("visualize", "ntdsconn",
'-H', self.dburl,
'--color=no', '--no-key')
self.assertCmdSuccess(result, long, err)
lines = short.split('\n')
replacements = []
key_lines = ['']
short_without_key = []
for line in lines:
m = re.match(r"'(.{1,2})' stands for '(.+)'", line)
if m:
a, b = m.groups()
replacements.append((len(a), a, b))
key_lines.append(line)
else:
short_without_key.append(line)
short = '\n'.join(short_without_key)
# we need to replace longest strings first
replacements.sort(reverse=True)
short2long = short
# we don't want to shorten the DC name in the header line.
long_header, long2short = long.strip().split('\n', 1)
for _, a, b in replacements:
short2long = short2long.replace(a, b)
long2short = long2short.replace(b, a)
long2short = '%s\n%s' % (long_header, long2short)
# The white space is going to be all wacky, so lets squish it down
short2long = collapse_space(short2long)
long2short = collapse_space(long2short)
short = collapse_space(short)
long = collapse_space(long)
self.assertStringsEqual(short2long, long, strip=True)
self.assertStringsEqual(short, long2short, strip=True)
def test_disconnected_ldif_with_key(self):
"""Test that the 'unconnected' ldif shows up and exactly matches the
expected output."""
# This is not truly a disconnected graph because the
# vampre/local/promoted DCs are in there and they have
# relationships, and SERVER2 and SERVER3 for some reason refer
# to them.
samdb, dbfile = samdb_from_ldif(UNCONNECTED_LDIF,
self.tempdir,
self.lp, tag='disconnected')
dburl = 'tdb://' + dbfile
result, output, err = self.runsubcmd("visualize", "ntdsconn",
'-H', dburl,
'--color=no', '-S')
self.remove_files(dbfile)
self.assertCmdSuccess(result, output, err)
self.assertStringsEqual(output,
EXPECTED_DISTANCE_GRAPH_WITH_KEY)
def test_dot_ntdsconn(self):
"""Graphviz NTDS Connection output"""
result, dot, err = self.runsubcmd("visualize", "ntdsconn",
'-H', self.dburl,
'--color=no', '-S', '--dot',
'--no-key')
self.assertCmdSuccess(result, dot, err)
self.assertStringsEqual(EXPECTED_DOT_MULTISITE_NO_KEY, dot)
def test_dot_ntdsconn_disconnected(self):
"""Graphviz NTDS Connection output from disconnected graph"""
samdb, dbfile = samdb_from_ldif(UNCONNECTED_LDIF,
self.tempdir,
self.lp, tag='disconnected')
result, dot, err = self.runsubcmd("visualize", "ntdsconn",
'-H', 'tdb://' + dbfile,
'--color=no', '-S', '--dot',
'-o', '-')
self.assertCmdSuccess(result, dot, err)
self.remove_files(dbfile)
self.assertStringsEqual(EXPECTED_DOT_NTDSCONN_DISCONNECTED, dot,
strip=True)
def test_dot_ntdsconn_disconnected_to_file(self):
"""Graphviz NTDS Connection output into a file"""
samdb, dbfile = samdb_from_ldif(UNCONNECTED_LDIF,
self.tempdir,
self.lp, tag='disconnected')
dot_file = os.path.join(self.tempdir, 'dotfile')
result, dot, err = self.runsubcmd("visualize", "ntdsconn",
'-H', 'tdb://' + dbfile,
'--color=no', '-S', '--dot',
'-o', dot_file)
self.assertCmdSuccess(result, dot, err)
f = open(dot_file)
dot = f.read()
f.close()
self.assertStringsEqual(EXPECTED_DOT_NTDSCONN_DISCONNECTED, dot)
self.remove_files(dbfile, dot_file)
EXPECTED_DOT_MULTISITE_NO_KEY = r"""/* generated by samba */
digraph A_samba_tool_production {
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";
fontsize=10;
node[fontname=Helvetica; fontsize=10];
"CN=NTDS Settings,\nCN=WIN01,\nCN=Servers,\nCN=Default-\nFirst-Site-Name,\n...";
"CN=NTDS Settings,\nCN=WIN02,\nCN=Servers,\nCN=Site-2,\n...";
"CN=NTDS Settings,\nCN=WIN03,\nCN=Servers,\nCN=Site-2,\n...";
"CN=NTDS Settings,\nCN=WIN04,\nCN=Servers,\nCN=Site-2,\n...";
"CN=NTDS Settings,\nCN=WIN05,\nCN=Servers,\nCN=Site-2,\n...";
"CN=NTDS Settings,\nCN=WIN06,\nCN=Servers,\nCN=Site-3,\n...";
"CN=NTDS Settings,\nCN=WIN07,\nCN=Servers,\nCN=Site-4,\n...";
"CN=NTDS Settings,\nCN=WIN08,\nCN=Servers,\nCN=Site-4,\n...";
"CN=NTDS Settings,\nCN=WIN09,\nCN=Servers,\nCN=Site-5,\n...";
"CN=NTDS Settings,\nCN=WIN10,\nCN=Servers,\nCN=Site-5,\n...";
"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", ];
"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", ];
"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", ];
"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", ];
"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", ];
"CN=NTDS Settings,\nCN=WIN02,\nCN=Servers,\nCN=Site-2,\n..." -> "CN=NTDS Settings,\nCN=WIN04,\nCN=Servers,\nCN=Site-2,\n..." [color="#000000", ];
"CN=NTDS Settings,\nCN=WIN02,\nCN=Servers,\nCN=Site-2,\n..." -> "CN=NTDS Settings,\nCN=WIN05,\nCN=Servers,\nCN=Site-2,\n..." [color="#000000", ];
"CN=NTDS Settings,\nCN=WIN03,\nCN=Servers,\nCN=Site-2,\n..." -> "CN=NTDS Settings,\nCN=WIN04,\nCN=Servers,\nCN=Site-2,\n..." [color="#000000", ];
"CN=NTDS Settings,\nCN=WIN03,\nCN=Servers,\nCN=Site-2,\n..." -> "CN=NTDS Settings,\nCN=WIN05,\nCN=Servers,\nCN=Site-2,\n..." [color="#000000", ];
"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", ];
"CN=NTDS Settings,\nCN=WIN04,\nCN=Servers,\nCN=Site-2,\n..." -> "CN=NTDS Settings,\nCN=WIN02,\nCN=Servers,\nCN=Site-2,\n..." [color="#000000", ];
"CN=NTDS Settings,\nCN=WIN04,\nCN=Servers,\nCN=Site-2,\n..." -> "CN=NTDS Settings,\nCN=WIN03,\nCN=Servers,\nCN=Site-2,\n..." [color="#000000", ];
"CN=NTDS Settings,\nCN=WIN05,\nCN=Servers,\nCN=Site-2,\n..." -> "CN=NTDS Settings,\nCN=WIN02,\nCN=Servers,\nCN=Site-2,\n..." [color="#000000", ];
"CN=NTDS Settings,\nCN=WIN05,\nCN=Servers,\nCN=Site-2,\n..." -> "CN=NTDS Settings,\nCN=WIN03,\nCN=Servers,\nCN=Site-2,\n..." [color="#000000", ];
"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", ];
"CN=NTDS Settings,\nCN=WIN09,\nCN=Servers,\nCN=Site-5,\n..." -> "CN=NTDS Settings,\nCN=WIN10,\nCN=Servers,\nCN=Site-5,\n..." [color="#000000", ];
"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", ];
"CN=NTDS Settings,\nCN=WIN10,\nCN=Servers,\nCN=Site-5,\n..." -> "CN=NTDS Settings,\nCN=WIN09,\nCN=Servers,\nCN=Site-5,\n..." [color="#000000", ];
}
"""
EXPECTED_DOT_NTDSCONN_DISCONNECTED = r"""/* generated by samba */
digraph A_samba_tool_production {
label="NTDS Connections known to CN=LOCALDC,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=samba,DC=example,DC=com";
fontsize=10;
node[fontname=Helvetica; fontsize=10];
"CN=NTDS Settings,\nCN=CLIENT,\n...";
"CN=NTDS Settings,\nCN=LOCALDC,\n...";
"CN=NTDS Settings,\nCN=PROMOTEDVDC,\n...";
"CN=NTDS Settings,\nCN=SERVER1,\n...";
"CN=NTDS Settings,\nCN=SERVER2,\n...";
"CN=NTDS Settings,\nCN=SERVER3,\n...";
"CN=NTDS Settings,\nCN=SERVER4,\n...";
"CN=NTDS Settings,\nCN=SERVER5,\n...";
"CN=NTDS Settings,\nCN=LOCALDC,\n..." -> "CN=NTDS Settings,\nCN=PROMOTEDVDC,\n..." [color="#000000", ];
"CN=NTDS Settings,\nCN=PROMOTEDVDC,\n..." -> "CN=NTDS Settings,\nCN=LOCALDC,\n..." [color="#000000", ];
"CN=NTDS Settings,\nCN=SERVER2,\n..." -> "CN=NTDS Settings,\nCN=PROMOTEDVDC,\n..." [color="#000000", ];
"CN=NTDS Settings,\nCN=SERVER3,\n..." -> "CN=NTDS Settings,\nCN=LOCALDC,\n..." [color="#000000", ];
subgraph cluster_key {
label="Key";
subgraph cluster_key_nodes {
label="";
color = "invis";
}
subgraph cluster_key_edges {
label="";
color = "invis";
subgraph cluster_key_0_ {
key_0_e1[label=src; color="#000000"; group="key_0__g"]
key_0_e2[label=dest; color="#000000"; group="key_0__g"]
key_0_e1 -> key_0_e2 [constraint = false; color="#000000"]
key_0__label[shape=plaintext; style=solid; width=2.000000; label="NTDS Connection\r"]
}
{key_0__label}
}
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"]
}
"CN=NTDS Settings,\nCN=CLIENT,\n..." -> key_0__label [style=invis];
"CN=NTDS Settings,\nCN=LOCALDC,\n..." -> key_0__label [style=invis];
"CN=NTDS Settings,\nCN=PROMOTEDVDC,\n..." -> key_0__label [style=invis];
"CN=NTDS Settings,\nCN=SERVER1,\n..." -> key_0__label [style=invis];
"CN=NTDS Settings,\nCN=SERVER2,\n..." -> key_0__label [style=invis];
"CN=NTDS Settings,\nCN=SERVER3,\n..." -> key_0__label [style=invis];
"CN=NTDS Settings,\nCN=SERVER4,\n..." -> key_0__label [style=invis];
"CN=NTDS Settings,\nCN=SERVER5,\n..." -> key_0__label [style=invis]
key_0__label -> elision0 [style=invis; weight=9]
}
"""
EXPECTED_DISTANCE_GRAPH_WITH_KEY = """
NTDS Connections known to CN=LOCALDC,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=samba,DC=example,DC=com
destination
,-------- *,CN=CLIENT+
|,------- *,CN=LOCALDC+
||,------ *,CN=PROMOTEDVDC+
|||,----- *,CN=SERVER1+
||||,---- *,CN=SERVER2+
|||||,--- *,CN=SERVER3+
||||||,-- *,CN=SERVER4+
source |||||||,- *,CN=SERVER5+
*,CN=CLIENT+ 0-------
*,CN=LOCALDC+ -01-----
*,CN=PROMOTEDVDC+ -10-----
*,CN=SERVER1+ ---0----
*,CN=SERVER2+ -21-0---
*,CN=SERVER3+ -12--0--
*,CN=SERVER4+ ------0-
*,CN=SERVER5+ -------0
'*' stands for 'CN=NTDS Settings'
'+' stands for ',CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=samba,DC=example,DC=com'
Data can get from source to destination in the indicated number of steps.
0 means zero steps (it is the same DC)
1 means a direct link
2 means a transitive link involving two steps (i.e. one intermediate DC)
- means there is no connection, even through other DCs
"""