1
0
mirror of https://github.com/samba-team/samba.git synced 2024-12-23 17:34:34 +03:00

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@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
This commit is contained in:
Douglas Bagnall 2018-06-07 14:15:10 +12:00 committed by Douglas Bagnall
parent 2403b7c4fd
commit b62b3da976
2 changed files with 179 additions and 0 deletions

View File

@ -101,6 +101,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),
@ -170,6 +172,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)
@ -184,6 +187,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)

View File

@ -20,8 +20,12 @@
from __future__ import print_function
import samba.tests
import drs_base
from samba.dcerpc import drsuapi
from samba import drs_utils
import re
import json
import ldb
import random
from samba.compat import PY3
if PY3:
@ -158,3 +162,144 @@ 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 _force_all_reps(self, samdb, dc, direction):
if direction == 'inbound':
info_type = drsuapi.DRSUAPI_DS_REPLICA_INFO_NEIGHBORS
elif direction == 'outbound':
info_type = drsuapi.DRSUAPI_DS_REPLICA_INFO_REPSTO
else:
raise ValueError("expected 'inbound' or 'outbound'")
self._enable_all_repl(dc)
lp = self.get_loadparm()
creds = self.get_credentials()
drsuapi_conn, drsuapi_handle, _ = drs_utils.drsuapi_connect(dc, lp, creds)
req1 = drsuapi.DsReplicaGetInfoRequest1()
req1.info_type = info_type
_, info = drsuapi_conn.DsReplicaGetInfo(drsuapi_handle, 1, req1)
for x in info.array:
# you might think x.source_dsa_address was the thing, but no.
# and we need to filter out RODCs and deleted DCs
res = []
try:
res = samdb.search(base=x.source_dsa_obj_dn,
scope=ldb.SCOPE_BASE,
attrs=['msDS-isRODC', 'isDeleted'],
controls=['show_deleted:0'])
except ldb.LdbError as e:
if e.args[0] != ldb.ERR_NO_SUCH_OBJECT:
raise
if (len(res) == 0 or
len(res[0].get('msDS-isRODC', '')) > 0 or
res[0]['isDeleted'] == 'TRUE'):
continue
dsa_dn = str(ldb.Dn(samdb, x.source_dsa_obj_dn).parent())
res = samdb.search(base=dsa_dn,
scope=ldb.SCOPE_BASE,
attrs=['dNSHostName'])
remote = res[0]['dNSHostName'][0]
self._enable_all_repl(remote)
if direction == 'inbound':
src, dest = remote, dc
else:
src, dest = dc, remote
self._net_drs_replicate(dest, src, forced=True)
def test_samba_tool_showrepl_summary_all_good(self):
"""Tests 'samba-tool drs showrepl --summary' command."""
# To be sure that all is good we need to force replication
# with everyone (because others might have it turned off), and
# turn replication on for them in case they suddenly decide to
# try again.
#
# We don't restore them to the non-auto-replication state.
samdb1 = self.getSamDB("-H", "ldap://%s" % self.dc1, "-U",
self.cmdline_creds)
self._enable_all_repl(self.dc1)
self._force_all_reps(samdb1, self.dc1, 'inbound')
self._force_all_reps(samdb1, self.dc1, 'outbound')
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")
# --verbose output is still quiet when all is good.
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")