summaryrefslogtreecommitdiff
path: root/python/samba/netcmd/visualize.py
diff options
context:
space:
mode:
authorDouglas Bagnall <douglas.bagnall@catalyst.net.nz>2018-06-01 17:20:56 +1200
committerAndrew Bartlett <abartlet@samba.org>2018-06-10 19:02:20 +0200
commit2d8cc50d392c9434993e2084d4390ce7337cb1b8 (patch)
tree08fd1d4a1b4fcc4f4fa9bea7e0ff8fd9e11af152 /python/samba/netcmd/visualize.py
parent04a773f30fdb7d03c0526ca1f73353ce5f0d29d5 (diff)
downloadsamba-2d8cc50d392c9434993e2084d4390ce7337cb1b8.tar.gz
samba-2d8cc50d392c9434993e2084d4390ce7337cb1b8.tar.bz2
samba-2d8cc50d392c9434993e2084d4390ce7337cb1b8.zip
sambatool visualize: add up-to-dateness visualization
Or more accurately, out-of-dateness visualization, which shows how far each DCs is from every other using the difference in the up-to-dateness vectors. An example usage is samba-tool visualize uptodateness -r -S -H ldap://somewhere \ -UAdministrator --color=auto --partition=DOMAIN Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abartlet@samba.org>
Diffstat (limited to 'python/samba/netcmd/visualize.py')
-rw-r--r--python/samba/netcmd/visualize.py138
1 files changed, 137 insertions, 1 deletions
diff --git a/python/samba/netcmd/visualize.py b/python/samba/netcmd/visualize.py
index bfd7d3bf341..a24962ea58a 100644
--- a/python/samba/netcmd/visualize.py
+++ b/python/samba/netcmd/visualize.py
@@ -25,12 +25,14 @@ from collections import defaultdict
import subprocess
import tempfile
-import samba
import samba.getopt as options
+from samba import dsdb
+from samba import nttime2unix
from samba.netcmd import Command, SuperCommand, CommandError, Option
from samba.samdb import SamDB
from samba.graph import dot_graph
from samba.graph import distance_matrix, COLOUR_SETS
+from samba.graph import full_matrix
from ldb import SCOPE_BASE, SCOPE_SUBTREE, LdbError
import time
import re
@@ -672,6 +674,140 @@ class cmd_ntdsconn(GraphCommand):
self.write(s, output)
+class cmd_uptodateness(GraphCommand):
+ """visualize uptodateness vectors"""
+
+ takes_options = COMMON_OPTIONS + [
+ Option("-p", "--partition", help="restrict to this partition",
+ default=None),
+ Option("--max-digits", default=3, type=int,
+ help="display this many digits of out-of-date-ness"),
+ ]
+
+ def get_utdv(self, samdb, dn):
+ """This finds the uptodateness vector in the database."""
+ cursors = []
+ config_dn = samdb.get_config_basedn()
+ for c in dsdb._dsdb_load_udv_v2(samdb, dn):
+ inv_id = str(c.source_dsa_invocation_id)
+ res = samdb.search(base=config_dn,
+ expression=("(&(invocationId=%s)"
+ "(objectClass=nTDSDSA))" % inv_id),
+ attrs=["distinguishedName", "invocationId"])
+ settings_dn = res[0]["distinguishedName"][0]
+ prefix, dsa_dn = settings_dn.split(',', 1)
+ if prefix != 'CN=NTDS Settings':
+ raise CommandError("Expected NTDS Settings DN, got %s" %
+ settings_dn)
+
+ cursors.append((dsa_dn,
+ inv_id,
+ int(c.highest_usn),
+ nttime2unix(c.last_sync_success)))
+ return cursors
+
+ def get_own_cursor(self, samdb):
+ res = samdb.search(base="",
+ scope=SCOPE_BASE,
+ attrs=["highestCommittedUSN"])
+ usn = int(res[0]["highestCommittedUSN"][0])
+ now = int(time.time())
+ return (usn, now)
+
+ def run(self, H=None, output=None, shorten_names=False,
+ key=True, talk_to_remote=False,
+ sambaopts=None, credopts=None, versionopts=None,
+ color=None, color_scheme=None,
+ utf8=False, format=None, importldif=None,
+ xdot=False, partition=None, max_digits=3):
+ if not talk_to_remote:
+ print("this won't work without talking to the remote servers "
+ "(use -r)", file=self.outf)
+ return
+
+ # We use the KCC libraries in readonly mode to get the
+ # replication graph.
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp, fallback_machine=True)
+ local_kcc, dsas = self.get_kcc_and_dsas(H, lp, creds)
+ self.samdb = local_kcc.samdb
+ partition = get_partition(self.samdb, partition)
+
+ short_partitions, long_partitions = get_partition_maps(self.samdb)
+ color_scheme = self.calc_distance_color_scheme(color,
+ color_scheme,
+ output)
+
+ for part_name, part_dn in short_partitions.items():
+ if partition not in (part_dn, None):
+ continue # we aren't doing this partition
+
+ cursors = self.get_utdv(self.samdb, part_dn)
+
+ # we talk to each remote and make a matrix of the vectors
+ # -- for each partition
+ # normalise by oldest
+ utdv_edges = {}
+ for dsa_dn in dsas:
+ res = local_kcc.samdb.search(dsa_dn,
+ scope=SCOPE_BASE,
+ attrs=["dNSHostName"])
+ ldap_url = "ldap://%s" % res[0]["dNSHostName"][0]
+ try:
+ samdb = self.get_db(ldap_url, sambaopts, credopts)
+ cursors = self.get_utdv(samdb, part_dn)
+ own_usn, own_time = self.get_own_cursor(samdb)
+ remotes = {dsa_dn: own_usn}
+ for dn, guid, usn, t in cursors:
+ remotes[dn] = usn
+ except LdbError as e:
+ print("Could not contact %s (%s)" % (ldap_url, e),
+ file=sys.stderr)
+ continue
+ utdv_edges[dsa_dn] = remotes
+
+ distances = {}
+ max_distance = 0
+ for dn1 in dsas:
+ try:
+ peak = utdv_edges[dn1][dn1]
+ except KeyError as e:
+ peak = 0
+ d = {}
+ distances[dn1] = d
+ for dn2 in dsas:
+ if dn2 in utdv_edges:
+ if dn1 in utdv_edges[dn2]:
+ dist = peak - utdv_edges[dn2][dn1]
+ d[dn2] = dist
+ if dist > max_distance:
+ max_distance = dist
+ else:
+ print("Missing dn %s from UTD vector" % dn1,
+ file=sys.stderr)
+ else:
+ print("missing dn %s from UTD vector list" % dn2,
+ file=sys.stderr)
+
+ digits = min(max_digits, len(str(max_distance)))
+ if digits < 1:
+ digits = 1
+ c_scale = 10 ** digits
+
+ s = full_matrix(distances,
+ utf8=utf8,
+ colour=color_scheme,
+ shorten_names=shorten_names,
+ generate_key=key,
+ grouping_function=get_dnstr_site,
+ colour_scale=c_scale,
+ digits=digits,
+ ylabel='DC',
+ xlabel='out-of-date-ness')
+
+ self.write('\n%s\n\n%s' % (part_name, s), output)
+
+
class cmd_visualize(SuperCommand):
"""Produces graphical representations of Samba network state"""
subcommands = {}