[PATCH] samba-tool drs showrepl --summary

Douglas Bagnall douglas.bagnall at catalyst.net.nz
Sat Jun 9 21:08:22 UTC 2018


A while ago I added a --json option to make `samba-tool drs showrepl`
produce machine readable output. This time I'm aiming for *human*
readable output with a --summary option. It differs from what is now
called --classic (still the default) in that it doesn't go on and on
describing things that are perfectly normal. When it finds a problem
it reverts to verbosity in the classic style.

A typical conversation with it should look like this:

  $ samba-tool drs showrepl --summary $DC $CREDS
  [ALL GOOD]
  $

Soon I'll have a patch for `samba-tool drs showrepl --global` which
looks around the network and tries to verify that it is in good
health.

cheers,
Douglas
-------------- next part --------------
From f37952a4861262080dc9fdf2d937e4a7862f4c61 Mon Sep 17 00:00:00 2001
From: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Date: Sat, 9 Jun 2018 20:07:37 +1200
Subject: [PATCH 1/6] python/kcc/graph_utils: short-cut edge failure test
 without edges

Otherwise we get an exception because itertools.combinations is asked
to find combinations with negative size.

Instead we assert the graph is connected as-is, which in this case is
the same as asserting there are no vertices.

Signed-off-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
---
 python/samba/kcc/graph_utils.py | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/python/samba/kcc/graph_utils.py b/python/samba/kcc/graph_utils.py
index 727b342d4bb..65f5ee67207 100644
--- a/python/samba/kcc/graph_utils.py
+++ b/python/samba/kcc/graph_utils.py
@@ -93,6 +93,9 @@ def verify_graph_connected(edges, vertices, edge_vertices):
 
 def verify_graph_connected_under_edge_failures(edges, vertices, edge_vertices):
     """The graph stays connected when any single edge is removed."""
+    if len(edges) == 0:
+        return verify_graph_connected(edges, vertices, edge_vertices)
+
     for subset in itertools.combinations(edges, len(edges) - 1):
         try:
             verify_graph_connected(subset, vertices, edge_vertices)
-- 
2.11.0


From 50d1927a5e1112ada0717f995e1337b91a08c506 Mon Sep 17 00:00:00 2001
From: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Date: Sat, 9 Jun 2018 20:35:30 +1200
Subject: [PATCH 2/6] samba-tool drs showrepl tests: don't assert existence of
 DNS partitions

Because their existence is uncertain immediately after provision,
when these tests will run under some circumstances.

Signed-off-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
---
 source4/torture/drs/python/samba_tool_drs_showrepl.py | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/source4/torture/drs/python/samba_tool_drs_showrepl.py b/source4/torture/drs/python/samba_tool_drs_showrepl.py
index 93fbc4ef414..90bb0484a27 100644
--- a/source4/torture/drs/python/samba_tool_drs_showrepl.py
+++ b/source4/torture/drs/python/samba_tool_drs_showrepl.py
@@ -90,11 +90,12 @@ class SambaToolDrsShowReplTests(drs_base.DrsBaseTestCase):
                          r"DSA invocationId: %s" %
                          (HEX8_RE, GUID_RE, GUID_RE), header)
 
-        for p in ['DC=DomainDnsZones,DC=samba,DC=example,DC=com',
-                  'CN=Configuration,DC=samba,DC=example,DC=com',
+        # We don't assert the DomainDnsZones and ForestDnsZones are
+        # there because we don't know that they have been set up yet.
+
+        for p in ['CN=Configuration,DC=samba,DC=example,DC=com',
                   'DC=samba,DC=example,DC=com',
-                  'CN=Schema,CN=Configuration,DC=samba,DC=example,DC=com',
-                  'DC=ForestDnsZones,DC=samba,DC=example,DC=com']:
+                  'CN=Schema,CN=Configuration,DC=samba,DC=example,DC=com']:
             self.assertRegex(r'%s\n'
                              r'\tDefault-First-Site-Name\\[A-Z]+ via RPC\n'
                              r'\t\tDSA object GUID: %s\n'
-- 
2.11.0


From c6fe397d9cf20df3bc42e4d271e1e0b70278c34d Mon Sep 17 00:00:00 2001
From: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Date: Fri, 9 Mar 2018 16:16:23 +1300
Subject: [PATCH 3/6] samba-tool drs showrepl: generalise the way output mode
 is chosen

We have a couple more output modes coming along, so it makes senses to
untangle .run() into a number of independent sub-methods.

Signed-off-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
---
 python/samba/netcmd/drs.py | 57 ++++++++++++++++++++++++++++++++++------------
 1 file changed, 42 insertions(+), 15 deletions(-)

diff --git a/python/samba/netcmd/drs.py b/python/samba/netcmd/drs.py
index 9b3c6050af9..f00a159e616 100644
--- a/python/samba/netcmd/drs.py
+++ b/python/samba/netcmd/drs.py
@@ -23,6 +23,7 @@ import samba.getopt as options
 import ldb
 import logging
 import common
+import json
 
 from samba.auth import system_session
 from samba.netcmd import (
@@ -81,8 +82,7 @@ def drs_parse_ntds_dn(ntds_dn):
     return (site, server)
 
 
-
-
+DEFAULT_SHOWREPL_FORMAT = 'classic'
 
 class cmd_drs_showrepl(Command):
     """Show replication status."""
@@ -96,7 +96,11 @@ class cmd_drs_showrepl(Command):
     }
 
     takes_options = [
-        Option("--json", help="output in JSON format", action='store_true'),
+        Option("--json", help="replication details in JSON format",
+               dest='format', action='store_const', const='json'),
+        Option("--classic", help="print local replication details",
+               dest='format', action='store_const', const='classic',
+               default=DEFAULT_SHOWREPL_FORMAT),
     ]
 
     takes_args = ["DC?"]
@@ -148,14 +152,30 @@ class cmd_drs_showrepl(Command):
         return (info_type, info)
 
     def run(self, DC=None, sambaopts=None,
-            credopts=None, versionopts=None, json=False):
-
+            credopts=None, versionopts=None,
+            format=DEFAULT_SHOWREPL_FORMAT):
         self.lp = sambaopts.get_loadparm()
         if DC is None:
             DC = common.netcmd_dnsname(self.lp)
         self.server = DC
         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
 
+        output_function = {
+            'json': self.json_output,
+            'classic': self.classic_output,
+        }.get(format)
+        if output_function is None:
+            raise CommandError("unknown showrepl format %s" % format)
+
+        return output_function()
+
+    def json_output(self):
+        data = self.get_local_repl_data()
+        del data['site']
+        del data['server']
+        json.dump(data, self.outf, indent=2)
+
+    def get_local_repl_data(self):
         drsuapi_connect(self)
         samdb_connect(self)
 
@@ -213,16 +233,23 @@ class cmd_drs_showrepl(Command):
                 a = str(r).split(':')
                 d['replicates NC'].append((a[3], int(a[2])))
 
-        if json:
-            import json as json_mod
-            data = {
-                'dsa': dsa_details,
-                'repsFrom': repsfrom,
-                'repsTo': repsto,
-                'NTDSConnections': conn_details
-            }
-            json_mod.dump(data, self.outf, indent=2)
-            return
+        return {
+            'dsa': dsa_details,
+            'repsFrom': repsfrom,
+            'repsTo': repsto,
+            'NTDSConnections': conn_details,
+            'site': site,
+            'server': server
+        }
+
+    def classic_output(self):
+        data = self.get_local_repl_data()
+        dsa_details = data['dsa']
+        repsfrom = data['repsFrom']
+        repsto = data['repsTo']
+        conn_details = data['NTDSConnections']
+        site = data['site']
+        server = data['server']
 
         self.message("%s\\%s" % (site, server))
         self.message("DSA Options: 0x%08x" % dsa_details["options"])
-- 
2.11.0


From e6711c23af2acad4bb42e3026c7f33b2461b7d42 Mon Sep 17 00:00:00 2001
From: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Date: Thu, 7 Jun 2018 14:35:38 +1200
Subject: [PATCH 4/6] samba-tool drs showrepl: add a --verbose flag

Signed-off-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
---
 python/samba/netcmd/drs.py | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/python/samba/netcmd/drs.py b/python/samba/netcmd/drs.py
index f00a159e616..45ec8f539a3 100644
--- a/python/samba/netcmd/drs.py
+++ b/python/samba/netcmd/drs.py
@@ -101,6 +101,7 @@ class cmd_drs_showrepl(Command):
         Option("--classic", help="print local replication details",
                dest='format', action='store_const', const='classic',
                default=DEFAULT_SHOWREPL_FORMAT),
+        Option("-v", "--verbose", help="Be verbose", action="store_true"),
     ]
 
     takes_args = ["DC?"]
@@ -153,12 +154,14 @@ class cmd_drs_showrepl(Command):
 
     def run(self, DC=None, sambaopts=None,
             credopts=None, versionopts=None,
-            format=DEFAULT_SHOWREPL_FORMAT):
+            format=DEFAULT_SHOWREPL_FORMAT,
+            verbose=False):
         self.lp = sambaopts.get_loadparm()
         if DC is None:
             DC = common.netcmd_dnsname(self.lp)
         self.server = DC
         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
+        self.verbose = verbose
 
         output_function = {
             'json': self.json_output,
-- 
2.11.0


From f2d2537bd6f7bca17a205fdd87859d24301d0d50 Mon Sep 17 00:00:00 2001
From: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Date: Thu, 7 Jun 2018 14:27:37 +1200
Subject: [PATCH 5/6] samba-tool drs showrepl: add a --color flag

Nothing is using it yet, but the next commit will

Signed-off-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
---
 python/samba/netcmd/drs.py | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/python/samba/netcmd/drs.py b/python/samba/netcmd/drs.py
index 45ec8f539a3..0cc43106bc2 100644
--- a/python/samba/netcmd/drs.py
+++ b/python/samba/netcmd/drs.py
@@ -38,6 +38,7 @@ from samba.dcerpc import drsuapi, misc
 from samba.join import join_clone
 from samba.ndr import ndr_unpack
 from samba.dcerpc import drsblobs
+from samba import colour
 
 def drsuapi_connect(ctx):
     '''make a DRSUAPI connection to the server'''
@@ -102,6 +103,8 @@ class cmd_drs_showrepl(Command):
                dest='format', action='store_const', const='classic',
                default=DEFAULT_SHOWREPL_FORMAT),
         Option("-v", "--verbose", help="Be verbose", action="store_true"),
+        Option("--color", help="Use colour output (yes|no|auto)",
+               default='no'),
     ]
 
     takes_args = ["DC?"]
@@ -155,7 +158,8 @@ class cmd_drs_showrepl(Command):
     def run(self, DC=None, sambaopts=None,
             credopts=None, versionopts=None,
             format=DEFAULT_SHOWREPL_FORMAT,
-            verbose=False):
+            verbose=False, color='no'):
+        self.apply_colour_choice(color)
         self.lp = sambaopts.get_loadparm()
         if DC is None:
             DC = common.netcmd_dnsname(self.lp)
-- 
2.11.0


From 5ed940a6fe928d07f99227eb22dc94280a42c709 Mon Sep 17 00:00:00 2001
From: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Date: Thu, 7 Jun 2018 14:15:10 +1200
Subject: [PATCH 6/6] samba-tool drs showrepl --summary for a quick local check

The default output ("classic") gives you a lot of very uninteresting
detail when everything is fine. --summary shuts up about things that
are fine but shouts a little bit when things are broken. It doesn't
provide any new information, just tries to present it in a more useful
format.

Signed-off-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
---
 python/samba/netcmd/drs.py                         | 34 +++++++++
 .../torture/drs/python/samba_tool_drs_showrepl.py  | 84 ++++++++++++++++++++++
 2 files changed, 118 insertions(+)

diff --git a/python/samba/netcmd/drs.py b/python/samba/netcmd/drs.py
index 0cc43106bc2..83cfac8694a 100644
--- a/python/samba/netcmd/drs.py
+++ b/python/samba/netcmd/drs.py
@@ -99,6 +99,8 @@ class cmd_drs_showrepl(Command):
     takes_options = [
         Option("--json", help="replication details in JSON format",
                dest='format', action='store_const', const='json'),
+        Option("--summary", help="summarize local DRS health",
+               dest='format', action='store_const', const='summary'),
         Option("--classic", help="print local replication details",
                dest='format', action='store_const', const='classic',
                default=DEFAULT_SHOWREPL_FORMAT),
@@ -168,6 +170,7 @@ class cmd_drs_showrepl(Command):
         self.verbose = verbose
 
         output_function = {
+            'summary': self.summary_output,
             'json': self.json_output,
             'classic': self.classic_output,
         }.get(format)
@@ -182,6 +185,37 @@ class cmd_drs_showrepl(Command):
         del data['server']
         json.dump(data, self.outf, indent=2)
 
+    def summary_output(self):
+        """Print a short message if every seems fine, but print details of any
+        links that seem broken."""
+        failing_repsto = []
+        failing_repsfrom = []
+
+        local_data = self.get_local_repl_data()
+        for rep in local_data['repsTo']:
+            if rep["consecutive failures"] != 0 or rep["last success"] == 0:
+                failing_repsto.append(rep)
+
+        for rep in local_data['repsFrom']:
+            if rep["consecutive failures"] != 0 or rep["last success"] == 0:
+                failing_repsto.append(rep)
+
+        if failing_repsto or failing_repsfrom:
+            self.message(colour.c_RED("There are failing connections"))
+            if failing_repsto:
+                self.message(colour.c_RED("Failing outbound connections:"))
+                for rep in failing_repsto:
+                    self.print_neighbour(rep)
+            if failing_repsfrom:
+                self.message(colour.c_RED("Failing inbound connection:"))
+                for rep in failing_repsfrom:
+                    self.print_neighbour(rep)
+
+            return 1
+
+        self.message(colour.c_GREEN("[ALL GOOD]"))
+
+
     def get_local_repl_data(self):
         drsuapi_connect(self)
         samdb_connect(self)
diff --git a/source4/torture/drs/python/samba_tool_drs_showrepl.py b/source4/torture/drs/python/samba_tool_drs_showrepl.py
index 90bb0484a27..1c24bd8d322 100644
--- a/source4/torture/drs/python/samba_tool_drs_showrepl.py
+++ b/source4/torture/drs/python/samba_tool_drs_showrepl.py
@@ -22,6 +22,7 @@ import samba.tests
 import drs_base
 import re
 import json
+import random
 from samba.compat import PY3
 
 if PY3:
@@ -158,3 +159,86 @@ class SambaToolDrsShowReplTests(drs_base.DrsBaseTestCase):
             self.assertTrue(isinstance(n['options'], int))
             self.assertTrue(isinstance(n['replicates NC'], list))
             self.assertRegex("^%s$" % DN_RE, n["remote DN"])
+
+    def test_samba_tool_showrepl_summary_all_good(self):
+        """Tests 'samba-tool drs showrepl --summary' command.
+        """
+        out = self.check_output("samba-tool drs showrepl --summary %s %s" %
+                                (self.dc1, self.cmdline_creds))
+        self.assertStringsEqual(out, "[ALL GOOD]\n")
+
+        out = self.check_output("samba-tool drs showrepl --summary "
+                                "--color=yes %s %s" %
+                                (self.dc1, self.cmdline_creds))
+        self.assertStringsEqual(out, "\033[1;32m[ALL GOOD]\033[0m\n")
+
+        # ve
+        out = self.check_output("samba-tool drs showrepl --summary -v %s %s" %
+                                (self.dc1, self.cmdline_creds))
+        self.assertStringsEqual(out, "[ALL GOOD]\n")
+        out = self.check_output("samba-tool drs showrepl --summary -v "
+                                "--color=yes %s %s" %
+                                (self.dc1, self.cmdline_creds))
+        self.assertStringsEqual(out, "\033[1;32m[ALL GOOD]\033[0m\n")
+
+    def test_samba_tool_showrepl_summary_forced_failure(self):
+        """Tests 'samba-tool drs showrepl --summary' command when we break the
+        network on purpose.
+        """
+        self.addCleanup(self._enable_all_repl, self.dc1)
+        self._disable_all_repl(self.dc1)
+
+        samdb1 = self.getSamDB("-H", "ldap://%s" % self.dc1, "-U",
+                               self.cmdline_creds)
+        samdb2 = self.getSamDB("-H", "ldap://%s" % self.dc2, "-U",
+                               self.cmdline_creds)
+        domain_dn = samdb1.domain_dn()
+
+        # Add some things to NOT replicate
+        ou1 = "OU=dc1.%x,%s" % (random.randrange(1 << 64), domain_dn)
+        ou2 = "OU=dc2.%x,%s" % (random.randrange(1 << 64), domain_dn)
+        samdb1.add({
+            "dn": ou1,
+            "objectclass": "organizationalUnit"
+        })
+        self.addCleanup(samdb1.delete, ou1, ['tree_delete:1'])
+        samdb2.add({
+            "dn": ou2,
+            "objectclass": "organizationalUnit"
+        })
+        self.addCleanup(samdb2.delete, ou2, ['tree_delete:1'])
+
+        dn1 = 'cn=u1.%%d,%s' % (ou1)
+        dn2 = 'cn=u2.%%d,%s' % (ou2)
+
+        try:
+            for i in range(100):
+                samdb1.add({
+                    "dn": dn1 % i,
+                    "objectclass": "user"
+                })
+                samdb2.add({
+                    "dn": dn2 % i,
+                    "objectclass": "user"
+                })
+                out = self.check_output("samba-tool drs showrepl --summary -v "
+                                        "%s %s" %
+                                        (self.dc1, self.cmdline_creds))
+                self.assertStringsEqual('[ALL GOOD]', out, strip=True)
+                out = self.check_output("samba-tool drs showrepl --summary -v "
+                                        "--color=yes %s %s" %
+                                        (self.dc2, self.cmdline_creds))
+                self.assertIn('[ALL GOOD]', out)
+
+        except samba.tests.BlackboxProcessError as e:
+            print("Good, failed as expected after %d rounds: %r" % (i, e.cmd))
+            self.assertIn('There are failing connections', e.stdout)
+            self.assertRegexpMatches(e.stdout,
+                                     r'result 845[67] '
+                                     r'\(WERR_DS_DRA_(SINK|SOURCE)_DISABLED\)',
+                                     msg=("The process should have failed "
+                                          "because replication was forced off, "
+                                          "but it failed for some other reason."))
+            self.assertIn('consecutive failure(s).', e.stdout)
+        else:
+            self.fail("No DRS failure noticed after 100 rounds of trying")
-- 
2.11.0



More information about the samba-technical mailing list