[PATCH] do a partial replication with drs replicate --local
Bob Campbell
bobcampbell at catalyst.net.nz
Thu Feb 9 02:10:36 UTC 2017
Hi Metze -
I've changed the attached patches by adding python bindings for
dsdb_load_udv_v2, and using that in replicate --local.
Thanks,
Bob
On 08/02/17 23:41, Stefan Metzmacher wrote:
> Hi Bob,
>
> no the logic is not ok yet.
>
> Am 08.02.2017 um 02:01 schrieb Bob Campbell:
>>
>> -
>> source_dsa_invocation_id = misc.GUID(self.samdb.get_invocation_id())
>> dest_dsa_invocation_id = misc.GUID(self.local_samdb.get_invocation_id())
>> destination_dsa_guid = self.ntds_guid
>>
>> + # If we can't find an upToDateVector, replicate fully
>> + hwm = drsuapi.DsReplicaHighWaterMark()
>> + hwm.tmp_highest_usn = 0
>> + hwm.reserved_usn = 0
>> + hwm.highest_usn = 0
>> +
>> + udv = None
>> + if not full_sync:
>> + res = self.local_samdb.search(base=NC, scope=ldb.SCOPE_BASE,
>> + attrs=["replUpToDateVector", "repsFrom"])
>> + if "repsFrom" in res[0]:
>> + for reps_from_packed in res[0]["repsFrom"]:
>> + reps_from_obj = ndr_unpack(drsblobs.repsFromToBlob, reps_from_packed)
>> + if reps_from_obj.ctr.source_dsa_invocation_id == source_dsa_invocation_id:
>> + hwm = reps_from_obj.ctr.highwatermark
>> +
>> + if "replUpToDateVector" in res[0]:
>> + udv_packed = res[0]["replUpToDateVector"][0]
>> + udv_obj = ndr_unpack(drsblobs.replUpToDateVectorBlob, udv_packed)
>> + udv_ctr = udv_obj.ctr
>> +
>> + udv = drsuapi.DsReplicaCursorCtrEx()
>> + udv.version = 2
>> + udv.reserved1 = 0
>> + udv.reserved2 = 0
>> + cursors = []
>> +
>> + highest_usn = self.local_samdb.sequence_number(ldb.SEQ_NEXT)
>> + found = False
>> + for cursor in udv_ctr.cursors:
>> + udv_ex_cursor = drsuapi.DsReplicaCursor()
>> + udv_ex_cursor.source_dsa_invocation_id = cursor.source_dsa_invocation_id
>> + udv_ex_cursor.highest_usn = cursor.highest_usn
>> + # Update the cursor if it's ours
>> + if cursor.source_dsa_invocation_id == source_dsa_invocation_id:
>> + udv_ex_cursor.highest_usn = highest_usn
>> + found = True
>> + cursors.append(udv_ex_cursor)
>> +
>> + # If we didn't find our cursor, add a new one for us
>> + if not found:
>> + our_udv_cursor = drsuapi.DsReplicaCursor()
>> + our_udv_cursor.source_dsa_invocation_id = source_dsa_invocation_id
>> + our_udv_cursor.highest_usn = highest_usn
>> + cursors.append(our_udv_cursor)
>> +
>> + udv.cursors = cursors
>> + udv.count = len(cursors)
>> +
> It needs to be something like this:
>
>
> highest_usn = self.local_samdb.sequence_number(ldb.SEQ_NEXT)
>
> # If we can't find an upToDateVector, replicate fully
> hwm = drsuapi.DsReplicaHighWaterMark()
> hwm.tmp_highest_usn = 0
> hwm.reserved_usn = 0
> hwm.highest_usn = 0
>
> udv = None
> if not full_sync:
> res = self.local_samdb.search(base=NC, scope=ldb.SCOPE_BASE,
> attrs=["replUpToDateVector",
> "repsFrom"])
> if "repsFrom" in res[0]:
> for reps_from_packed in res[0]["repsFrom"]:
> reps_from_obj = ndr_unpack(drsblobs.repsFromToBlob,
> reps_from_packed)
> if reps_from_obj.ctr.source_dsa_invocation_id ==
> source_dsa_invocation_id:
> hwm = reps_from_obj.ctr.highwatermark
>
> # The following logic should match dsdb_load_udv_v2()!
> cursors = []
> found = False
>
> if "replUpToDateVector" in res[0]:
> udv_packed = res[0]["replUpToDateVector"][0]
> udv_obj = ndr_unpack(drsblobs.replUpToDateVectorBlob,
> udv_packed)
> udv_ctr = udv_obj.ctr
>
> for cursor in udv_ctr.cursors:
> udv_ex_cursor = drsuapi.DsReplicaCursor()
> udv_ex_cursor.source_dsa_invocation_id =
> cursor.source_dsa_invocation_id
> # Update the cursor if it's ours
> if cursor.source_dsa_invocation_id ==
> destinatoin_dsa_invocation_id:
> udv_ex_cursor.highest_usn = highest_usn
> found = True
> else:
> udv_ex_cursor.highest_usn = cursor.highest_usn
> cursors.append(udv_ex_cursor)
>
> # If we didn't find our cursor, add a new one for us
> if not found:
> our_udv_cursor = drsuapi.DsReplicaCursor()
> our_udv_cursor.source_dsa_invocation_id =
> destination_dsa_invocation_id
> our_udv_cursor.highest_usn = highest_usn
> cursors.append(our_udv_cursor)
>
> todo_sort_by_guid(cursors)
>
> udv = drsuapi.DsReplicaCursorCtrEx()
> udv.version = 2
> udv.reserved1 = 0
> udv.reserved2 = 0
> udv.cursors = cursors
> udv.count = len(cursors)
>
> The main points are that our highest_usn belongs to our
> destination_dsa_invocation_id not to the source_dsa_invocation_id variable.
> and we need to create the udv even without "replUpToDateVector" in that
> case we just send our own cursor.
>
> And you should to sort the cursors at the end see:
> TYPESAFE_QSORT(*cursors, *count, drsuapi_DsReplicaCursor2_compare);
>
> Another approach would be to add python bindings for dsdb_load_udv_v2().
>
> metze
>
-------------- next part --------------
From a8eaadbea11dfd4fe2f3959bb905ff2ad8cadb13 Mon Sep 17 00:00:00 2001
From: Bob Campbell <bobcampbell at catalyst.net.nz>
Date: Fri, 27 Jan 2017 10:18:21 +1300
Subject: [PATCH 1/6] drs_utils: return number of replicated objects and links
in replicate()
Signed-off-by: Bob Campbell <bobcampbell at catalyst.net.nz>
Pair-programmed-with: Andrew Bartlett <abartlet at samba.org>
---
python/samba/drs_utils.py | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/python/samba/drs_utils.py b/python/samba/drs_utils.py
index 07fc05a..645d146 100644
--- a/python/samba/drs_utils.py
+++ b/python/samba/drs_utils.py
@@ -1,7 +1,7 @@
# DRS utility code
#
# Copyright Andrew Tridgell 2010
-# Copyright Andrew Bartlett 2010
+# Copyright Andrew Bartlett 2017
#
# 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
@@ -212,6 +212,7 @@ class drs_Replicate(object):
req8.highwatermark.reserved_usn = 0
req8.highwatermark.highest_usn = 0
req8.uptodateness_vector = None
+
if replica_flags is not None:
req8.replica_flags = replica_flags
elif exop == drsuapi.DRSUAPI_EXOP_REPL_SECRET:
@@ -250,12 +251,25 @@ class drs_Replicate(object):
setattr(req5, a, getattr(req8, a))
req = req5
+ num_objects = 0
+ num_links = 0
while True:
(level, ctr) = self.drs.DsGetNCChanges(self.drs_handle, req_level, req)
if ctr.first_object is None and ctr.object_count != 0:
raise RuntimeError("DsGetNCChanges: NULL first_object with object_count=%u" % (ctr.object_count))
self.net.replicate_chunk(self.replication_state, level, ctr,
schema=schema, req_level=req_level, req=req)
+
+ num_objects += ctr.object_count
+
+ # Cope with servers that do not return level 6, so do not return any links
+ try:
+ num_links += ctr.linked_attributes_count
+ except AttributeError:
+ pass
+
if ctr.more_data == 0:
break
req.highwatermark = ctr.new_highwatermark
+
+ return (num_objects, num_links)
--
1.9.1
From 2c2a5910273117a7404b3b0fb8b81f316fa2a6b1 Mon Sep 17 00:00:00 2001
From: Bob Campbell <bobcampbell at catalyst.net.nz>
Date: Fri, 27 Jan 2017 10:40:19 +1300
Subject: [PATCH 2/6] drs_utils: use a given highwatermark and
uptodateness_vector in replicate()
Signed-off-by: Bob Campbell <bobcampbell at catalyst.net.nz>
Pair-programmed-with: Andrew Bartlett <abartlet at samba.org>
---
python/samba/drs_utils.py | 17 +++++++++++------
1 file changed, 11 insertions(+), 6 deletions(-)
diff --git a/python/samba/drs_utils.py b/python/samba/drs_utils.py
index 645d146..8c7a86b 100644
--- a/python/samba/drs_utils.py
+++ b/python/samba/drs_utils.py
@@ -197,7 +197,7 @@ class drs_Replicate(object):
def replicate(self, dn, source_dsa_invocation_id, destination_dsa_guid,
schema=False, exop=drsuapi.DRSUAPI_EXOP_NONE, rodc=False,
- replica_flags=None):
+ replica_flags=None, highwatermark=None, udv=None):
'''replicate a single DN'''
# setup for a GetNCChanges call
@@ -207,11 +207,16 @@ class drs_Replicate(object):
req8.source_dsa_invocation_id = source_dsa_invocation_id
req8.naming_context = drsuapi.DsReplicaObjectIdentifier()
req8.naming_context.dn = dn
- req8.highwatermark = drsuapi.DsReplicaHighWaterMark()
- req8.highwatermark.tmp_highest_usn = 0
- req8.highwatermark.reserved_usn = 0
- req8.highwatermark.highest_usn = 0
- req8.uptodateness_vector = None
+
+ if highwatermark is not None:
+ req8.highwatermark = highwatermark
+ else:
+ req8.highwatermark = drsuapi.DsReplicaHighWaterMark()
+ req8.highwatermark.tmp_highest_usn = 0
+ req8.highwatermark.reserved_usn = 0
+ req8.highwatermark.highest_usn = 0
+
+ req8.uptodateness_vector = udv
if replica_flags is not None:
req8.replica_flags = replica_flags
--
1.9.1
From d426ea9bb4736e2c3b68c94bea9a980c70838afb Mon Sep 17 00:00:00 2001
From: Bob Campbell <bobcampbell at catalyst.net.nz>
Date: Thu, 9 Feb 2017 11:22:08 +1300
Subject: [PATCH 3/6] pydsdb: Add _dsdb_load_udv_v2 to python bindings
Signed-off-by: Bob Campbell <bobcampbell at catalyst.net.nz>
---
source4/dsdb/pydsdb.c | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 73 insertions(+)
diff --git a/source4/dsdb/pydsdb.c b/source4/dsdb/pydsdb.c
index ab1d0d2..3348d64 100644
--- a/source4/dsdb/pydsdb.c
+++ b/source4/dsdb/pydsdb.c
@@ -1244,6 +1244,78 @@ static PyObject *py_dsdb_garbage_collect_tombstones(PyObject *self, PyObject *ar
num_links_removed);
}
+static PyObject *py_dsdb_load_udv_v2(PyObject *self, PyObject *args)
+{
+ PyObject *py_ldb, *py_dn, *pylist;
+ struct ldb_context *samdb;
+ struct ldb_dn *dn;
+ struct drsuapi_DsReplicaCursor2 *cursors;
+ uint32_t *count;
+ struct drsuapi_DsReplicaCursor2CtrEx *cursor_ctr;
+ int ret, i;
+ TALLOC_CTX *tmp_ctx;
+
+ if (!PyArg_ParseTuple(args, "OO", &py_ldb, &py_dn)) {
+ return NULL;
+ }
+
+ PyErr_LDB_OR_RAISE(py_ldb, samdb);
+
+ tmp_ctx = talloc_new(samdb);
+ if (tmp_ctx == NULL) {
+ return PyErr_NoMemory();
+ }
+
+ if (!pyldb_Object_AsDn(tmp_ctx, py_dn, samdb, &dn)) {
+ talloc_free(tmp_ctx);
+ return NULL;
+ }
+
+ cursor_ctr = talloc_zero(tmp_ctx,
+ struct drsuapi_DsReplicaCursor2CtrEx);
+ count = talloc_zero(tmp_ctx, uint32_t);
+ if (cursor_ctr == NULL || count == NULL) {
+ return PyErr_NoMemory();
+ }
+
+ ret = dsdb_load_udv_v2(samdb, dn, tmp_ctx, &cursors, count);
+ if (ret != LDB_SUCCESS) {
+ TALLOC_FREE(tmp_ctx);
+ PyErr_SetString(PyExc_RuntimeError,
+ "Failed to load udv from ldb");
+ return NULL;
+ }
+
+ pylist = PyList_New(*count);
+ if (pylist == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return PyErr_NoMemory();
+ }
+
+ for (i = 0; i < *count; i++) {
+ PyObject *py_cursor;
+ struct drsuapi_DsReplicaCursor2 *cursor;
+ cursor = talloc(tmp_ctx, struct drsuapi_DsReplicaCursor2);
+ if (cursor == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return PyErr_NoMemory();
+ }
+ *cursor = cursors[i];
+
+ py_cursor = py_return_ndr_struct("samba.dcerpc.drsuapi",
+ "DsReplicaCursor2",
+ cursor, cursor);
+ if (!py_cursor) {
+ TALLOC_FREE(tmp_ctx);
+ return PyErr_NoMemory();
+ }
+
+ PyList_SetItem(pylist, i, py_cursor);
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return pylist;
+}
static PyMethodDef py_dsdb_methods[] = {
{ "_samdb_server_site_name", (PyCFunction)py_samdb_server_site_name,
@@ -1319,6 +1391,7 @@ static PyMethodDef py_dsdb_methods[] = {
{ "_dsdb_allocate_rid", (PyCFunction)py_dsdb_allocate_rid, METH_VARARGS,
"_dsdb_allocate_rid(samdb)"
" -> RID" },
+ { "_dsdb_load_udv_v2", (PyCFunction)py_dsdb_load_udv_v2, METH_VARARGS, NULL },
{ NULL }
};
--
1.9.1
From 6e6cad818cd8081c0359c06c4d59a6b1c8c04375 Mon Sep 17 00:00:00 2001
From: Bob Campbell <bobcampbell at catalyst.net.nz>
Date: Fri, 27 Jan 2017 10:40:59 +1300
Subject: [PATCH 4/6] samba-tool/drs: do partial replication when --local is
given by default
The samba-tool drs replicate --local command would previously always do
a full replication. This changes it to only replicate changes it doesn't
have according to appropriate highwatermark if the appropriate repsFrom
attribute exists in the local database, or an uptodateness_vector if one
exists.
Signed-off-by: Bob Campbell <bobcampbell at catalyst.net.nz>
Pair-programmed-with: Andrew Bartlett <abartlet at samba.org>
---
python/samba/netcmd/drs.py | 61 +++++++++++++++++++++++----
python/samba/tests/blackbox/samba_tool_drs.py | 4 +-
2 files changed, 54 insertions(+), 11 deletions(-)
diff --git a/python/samba/netcmd/drs.py b/python/samba/netcmd/drs.py
index 67733ac..aadfff4 100644
--- a/python/samba/netcmd/drs.py
+++ b/python/samba/netcmd/drs.py
@@ -1,6 +1,7 @@
# implement samba_tool drs commands
#
# Copyright Andrew Tridgell 2010
+# Copyright Andrew Bartlett 2017
#
# based on C implementation by Kamen Mazdrashki <kamen.mazdrashki at postpath.com>
#
@@ -21,6 +22,7 @@
import samba.getopt as options
import ldb
import logging
+import common
from samba.auth import system_session
from samba.netcmd import (
@@ -32,8 +34,9 @@ from samba.netcmd import (
from samba.samdb import SamDB
from samba import drs_utils, nttime2string, dsdb
from samba.dcerpc import drsuapi, misc
-import common
from samba.join import join_clone
+from samba.ndr import ndr_unpack
+from samba.dcerpc import drsblobs
def drsuapi_connect(ctx):
'''make a DRSUAPI connection to the server'''
@@ -238,7 +241,7 @@ class cmd_drs_kcc(Command):
-def drs_local_replicate(self, SOURCE_DC, NC):
+def drs_local_replicate(self, SOURCE_DC, NC, full_sync=False):
'''replicate from a source DC to the local SAM'''
self.server = SOURCE_DC
@@ -252,17 +255,51 @@ def drs_local_replicate(self, SOURCE_DC, NC):
credentials=self.creds, lp=self.lp)
# work out the source and destination GUIDs
- res = self.local_samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
+ res = self.local_samdb.search(base="", scope=ldb.SCOPE_BASE,
+ attrs=["dsServiceName"])
self.ntds_dn = res[0]["dsServiceName"][0]
- res = self.local_samdb.search(base=self.ntds_dn, scope=ldb.SCOPE_BASE, attrs=["objectGUID"])
+ res = self.local_samdb.search(base=self.ntds_dn, scope=ldb.SCOPE_BASE,
+ attrs=["objectGUID"])
self.ntds_guid = misc.GUID(self.samdb.schema_format_value("objectGUID", res[0]["objectGUID"][0]))
-
source_dsa_invocation_id = misc.GUID(self.samdb.get_invocation_id())
dest_dsa_invocation_id = misc.GUID(self.local_samdb.get_invocation_id())
destination_dsa_guid = self.ntds_guid
+ # If we can't find an upToDateVector, replicate fully
+ hwm = drsuapi.DsReplicaHighWaterMark()
+ hwm.tmp_highest_usn = 0
+ hwm.reserved_usn = 0
+ hwm.highest_usn = 0
+
+ udv = None
+ if not full_sync:
+ res = self.local_samdb.search(base=NC, scope=ldb.SCOPE_BASE,
+ attrs=["repsFrom"])
+ if "repsFrom" in res[0]:
+ for reps_from_packed in res[0]["repsFrom"]:
+ reps_from_obj = ndr_unpack(drsblobs.repsFromToBlob, reps_from_packed)
+ if reps_from_obj.ctr.source_dsa_invocation_id == source_dsa_invocation_id:
+ hwm = reps_from_obj.ctr.highwatermark
+
+ udv = drsuapi.DsReplicaCursorCtrEx()
+ udv.version = 2
+ udv.reserved1 = 0
+ udv.reserved2 = 0
+
+ cursors_v1 = []
+ cursors_v2 = dsdb._dsdb_load_udv_v2(self.local_samdb,
+ self.local_samdb.get_default_basedn())
+ for cursor_v2 in cursors_v2:
+ cursor_v1 = drsuapi.DsReplicaCursor()
+ cursor_v1.source_dsa_invocation_id = cursor_v2.source_dsa_invocation_id
+ cursor_v1.highest_usn = cursor_v2.highest_usn
+ cursors_v1.append(cursor_v1)
+
+ udv.cursors = cursors_v1
+ udv.count = len(cursors_v1)
+
self.samdb.transaction_start()
repl = drs_utils.drs_Replicate("ncacn_ip_tcp:%s[seal]" % self.server, self.lp,
self.creds, self.local_samdb, dest_dsa_invocation_id)
@@ -271,13 +308,19 @@ def drs_local_replicate(self, SOURCE_DC, NC):
# with the admin pw does not sync passwords
rodc = self.local_samdb.am_rodc()
try:
- repl.replicate(NC, source_dsa_invocation_id, destination_dsa_guid, rodc=rodc)
+ (num_objects, num_links) = repl.replicate(NC,
+ source_dsa_invocation_id, destination_dsa_guid,
+ rodc=rodc, highwatermark=hwm, udv=udv)
except Exception, e:
raise CommandError("Error replicating DN %s" % NC, e)
self.samdb.transaction_commit()
- self.message("Replicate from %s to %s was successful." % (SOURCE_DC, self.local_samdb.url))
-
+ if full_sync:
+ self.message("Full Replication of all %d objects and %d links from %s to %s was successful."
+ % (num_objects, num_links, SOURCE_DC, self.local_samdb.url))
+ else:
+ self.message("Incremental replication of %d objects and %d links from %s to %s was successful."
+ % (num_objects, num_links, SOURCE_DC, self.local_samdb.url))
class cmd_drs_replicate(Command):
@@ -314,7 +357,7 @@ class cmd_drs_replicate(Command):
self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
if local:
- drs_local_replicate(self, SOURCE_DC, NC)
+ drs_local_replicate(self, SOURCE_DC, NC, full_sync=full_sync)
return
if local_online:
diff --git a/python/samba/tests/blackbox/samba_tool_drs.py b/python/samba/tests/blackbox/samba_tool_drs.py
index 90921f4..df0e3d7 100644
--- a/python/samba/tests/blackbox/samba_tool_drs.py
+++ b/python/samba/tests/blackbox/samba_tool_drs.py
@@ -145,7 +145,7 @@ class SambaToolDrsTests(samba.tests.BlackboxTestCase):
out = self.check_output("samba-tool drs replicate -P --local %s %s %s" % (self.dc1,
self.dc2,
nc_name))
- self.assertTrue("Replicate from" in out)
+ self.assertTrue("Incremental" in out)
self.assertTrue("was successful" in out)
def test_samba_tool_replicate_local(self):
@@ -157,7 +157,7 @@ class SambaToolDrsTests(samba.tests.BlackboxTestCase):
self.dc2,
nc_name,
self.cmdline_creds))
- self.assertTrue("Replicate from" in out)
+ self.assertTrue("Incremental" in out)
self.assertTrue("was successful" in out)
def test_samba_tool_replicate_machine_creds_P(self):
--
1.9.1
From e1e82880438a8cbf17e29569abdb1f850b8e45e8 Mon Sep 17 00:00:00 2001
From: Bob Campbell <bobcampbell at catalyst.net.nz>
Date: Fri, 27 Jan 2017 14:46:36 +1300
Subject: [PATCH 5/6] python/tests: move samba_tool_drs test to proper place
Signed-off-by: Bob Campbell <bobcampbell at catalyst.net.nz>
---
python/samba/tests/blackbox/samba_tool_drs.py | 344 -------------------------
source4/selftest/tests.py | 4 +-
source4/torture/drs/python/samba_tool_drs.py | 345 ++++++++++++++++++++++++++
3 files changed, 348 insertions(+), 345 deletions(-)
delete mode 100644 python/samba/tests/blackbox/samba_tool_drs.py
create mode 100644 source4/torture/drs/python/samba_tool_drs.py
diff --git a/python/samba/tests/blackbox/samba_tool_drs.py b/python/samba/tests/blackbox/samba_tool_drs.py
deleted file mode 100644
index df0e3d7..0000000
--- a/python/samba/tests/blackbox/samba_tool_drs.py
+++ /dev/null
@@ -1,344 +0,0 @@
-# Blackbox tests for "samba-tool drs" command
-# Copyright (C) Kamen Mazdrashki <kamenim at samba.org> 2011
-#
-# 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/>.
-#
-
-"""Blackbox tests for samba-tool drs."""
-
-import samba.tests
-import shutil
-import os
-import ldb
-
-class SambaToolDrsTests(samba.tests.BlackboxTestCase):
- """Blackbox test case for samba-tool drs."""
-
- def setUp(self):
- super(SambaToolDrsTests, self).setUp()
-
- self.dc1 = samba.tests.env_get_var_value("DC1")
- self.dc2 = samba.tests.env_get_var_value("DC2")
-
- creds = self.get_credentials()
- self.cmdline_creds = "-U%s/%s%%%s" % (creds.get_domain(),
- creds.get_username(), creds.get_password())
-
- def _get_rootDSE(self, dc, ldap_only=True):
- samdb = samba.tests.connect_samdb(dc, lp=self.get_loadparm(),
- credentials=self.get_credentials(),
- ldap_only=ldap_only)
- return samdb.search(base="", scope=samba.tests.ldb.SCOPE_BASE)[0]
-
- def test_samba_tool_bind(self):
- """Tests 'samba-tool drs bind' command."""
-
- # Output should be like:
- # Extensions supported:
- # <list-of-supported-extensions>
- # Site GUID: <GUID>
- # Repl epoch: 0
- out = self.check_output("samba-tool drs bind %s %s" % (self.dc1,
- self.cmdline_creds))
- self.assertTrue("Site GUID:" in out)
- self.assertTrue("Repl epoch:" in out)
-
- def test_samba_tool_kcc(self):
- """Tests 'samba-tool drs kcc' command."""
-
- # Output should be like 'Consistency check on <DC> successful.'
- out = self.check_output("samba-tool drs kcc %s %s" % (self.dc1,
- self.cmdline_creds))
- self.assertTrue("Consistency check on" in out)
- self.assertTrue("successful" in out)
-
- def test_samba_tool_showrepl(self):
- """Tests 'samba-tool drs showrepl' command.
- """
- # Output should be like:
- # <site-name>/<domain-name>
- # DSA Options: <hex-options>
- # DSA object GUID: <DSA-object-GUID>
- # DSA invocationId: <DSA-invocationId>
- # <Inbound-connections-list>
- # <Outbound-connections-list>
- # <KCC-objects>
- # ...
- # TODO: Perhaps we should check at least for
- # DSA's objectGUDI and invocationId
- out = self.check_output("samba-tool drs showrepl %s %s" % (self.dc1,
- self.cmdline_creds))
- self.assertTrue("DSA Options:" in out)
- self.assertTrue("DSA object GUID:" in out)
- self.assertTrue("DSA invocationId:" in out)
-
- def test_samba_tool_options(self):
- """Tests 'samba-tool drs options' command
- """
- # Output should be like 'Current DSA options: IS_GC <OTHER_FLAGS>'
- out = self.check_output("samba-tool drs options %s %s" % (self.dc1,
- self.cmdline_creds))
- self.assertTrue("Current DSA options:" in out)
-
- def test_samba_tool_replicate(self):
- """Tests 'samba-tool drs replicate' command."""
-
- # Output should be like 'Replicate from <DC-SRC> to <DC-DEST> was successful.'
- nc_name = self._get_rootDSE(self.dc1)["defaultNamingContext"]
- out = self.check_output("samba-tool drs replicate %s %s %s %s" % (self.dc1,
- self.dc2,
- nc_name,
- self.cmdline_creds))
- self.assertTrue("Replicate from" in out)
- self.assertTrue("was successful" in out)
-
- def test_samba_tool_replicate_async(self):
- """Tests 'samba-tool drs replicate --async-op' command."""
-
- # Output should be like 'Replicate from <DC-SRC> to <DC-DEST> was started.'
- nc_name = self._get_rootDSE(self.dc1)["defaultNamingContext"]
- out = self.check_output("samba-tool drs replicate --async-op %s %s %s %s" % (self.dc1,
- self.dc2,
- nc_name,
- self.cmdline_creds))
- self.assertTrue("Replicate from" in out)
- self.assertTrue("was started" in out)
-
- def test_samba_tool_replicate_local_online(self):
- """Tests 'samba-tool drs replicate --local-online' command."""
-
- # Output should be like 'Replicate from <DC-SRC> to <DC-DEST> was successful.'
- nc_name = self._get_rootDSE(self.dc1)["defaultNamingContext"]
- out = self.check_output("samba-tool drs replicate --local-online %s %s %s" % (self.dc1,
- self.dc2,
- nc_name))
- self.assertTrue("Replicate from" in out)
- self.assertTrue("was successful" in out)
-
- def test_samba_tool_replicate_local_online_async(self):
- """Tests 'samba-tool drs replicate --local-online --async-op' command."""
-
- # Output should be like 'Replicate from <DC-SRC> to <DC-DEST> was started.'
- nc_name = self._get_rootDSE(self.dc1)["defaultNamingContext"]
- out = self.check_output("samba-tool drs replicate --local-online --async-op %s %s %s" % (self.dc1,
- self.dc2,
- nc_name))
- self.assertTrue("Replicate from" in out)
- self.assertTrue("was started" in out)
-
- def test_samba_tool_replicate_local_machine_creds(self):
- """Tests 'samba-tool drs replicate --local -P' command (uses machine creds)."""
-
- # Output should be like 'Replicate from <DC-SRC> to <DC-DEST> was successful.'
- nc_name = self._get_rootDSE(self.dc1)["defaultNamingContext"]
- out = self.check_output("samba-tool drs replicate -P --local %s %s %s" % (self.dc1,
- self.dc2,
- nc_name))
- self.assertTrue("Incremental" in out)
- self.assertTrue("was successful" in out)
-
- def test_samba_tool_replicate_local(self):
- """Tests 'samba-tool drs replicate --local' command (uses machine creds)."""
-
- # Output should be like 'Replicate from <DC-SRC> to <DC-DEST> was successful.'
- nc_name = self._get_rootDSE(self.dc1)["defaultNamingContext"]
- out = self.check_output("samba-tool drs replicate --local %s %s %s %s" % (self.dc1,
- self.dc2,
- nc_name,
- self.cmdline_creds))
- self.assertTrue("Incremental" in out)
- self.assertTrue("was successful" in out)
-
- def test_samba_tool_replicate_machine_creds_P(self):
- """Tests 'samba-tool drs replicate -P' command with machine creds."""
-
- # Output should be like 'Replicate from <DC-SRC> to <DC-DEST> was successful.'
- nc_name = self._get_rootDSE(self.dc1)["defaultNamingContext"]
- out = self.check_output("samba-tool drs replicate -P %s %s %s" % (self.dc1,
- self.dc2,
- nc_name))
- self.assertTrue("Replicate from" in out)
- self.assertTrue("was successful" in out)
-
- def test_samba_tool_replicate_machine_creds(self):
- """Tests 'samba-tool drs replicate' command with implicit machine creds."""
-
- # Output should be like 'Replicate from <DC-SRC> to <DC-DEST> was successful.'
- nc_name = self._get_rootDSE(self.dc1)["defaultNamingContext"]
- out = self.check_output("samba-tool drs replicate %s %s %s" % (self.dc1,
- self.dc2,
- nc_name))
- self.assertTrue("Replicate from" in out)
- self.assertTrue("was successful" in out)
-
- def test_samba_tool_drs_clone_dc(self):
- """Tests 'samba-tool drs clone-dc-database' command."""
- server_rootdse = self._get_rootDSE(self.dc1)
- server_nc_name = server_rootdse["defaultNamingContext"]
- server_ds_name = server_rootdse["dsServiceName"]
- server_ldap_service_name = str(server_rootdse["ldapServiceName"][0])
- server_realm = server_ldap_service_name.split(":")[0]
- creds = self.get_credentials()
- out = self.check_output("samba-tool drs clone-dc-database %s --server=%s %s --targetdir=%s"
- % (server_realm,
- self.dc1,
- self.cmdline_creds,
- self.tempdir))
- ldb_rootdse = self._get_rootDSE("tdb://" + os.path.join(self.tempdir, "private", "sam.ldb"), ldap_only=False)
- nc_name = ldb_rootdse["defaultNamingContext"]
- ds_name = ldb_rootdse["dsServiceName"]
- ldap_service_name = str(server_rootdse["ldapServiceName"][0])
- self.assertEqual(nc_name, server_nc_name)
- # The clone should pretend to be the source server
- self.assertEqual(ds_name, server_ds_name)
- self.assertEqual(ldap_service_name, server_ldap_service_name)
-
- samdb = samba.tests.connect_samdb("tdb://" + os.path.join(self.tempdir, "private", "sam.ldb"),
- ldap_only=False, lp=self.get_loadparm())
- def get_krbtgt_pw():
- krbtgt_pw = samdb.searchone("unicodePwd", "cn=krbtgt,CN=users,%s" % nc_name)
- self.assertRaises(KeyError, get_krbtgt_pw)
-
- server_dn = samdb.searchone("serverReferenceBL", "cn=%s,ou=domain controllers,%s" % (self.dc2, server_nc_name))
- ntds_guid = samdb.searchone("objectGUID", "cn=ntds settings,%s" % server_dn)
-
- res = samdb.search(base=str(server_nc_name),
- expression="(&(objectclass=user)(cn=dns-%s))" % (self.dc2),
- attrs=[], scope=ldb.SCOPE_SUBTREE)
- if len(res) == 1:
- dns_obj = res[0]
- else:
- dns_obj = None
-
- # While we have this cloned, try demoting the other server on the clone, by GUID
- out = self.check_output("samba-tool domain demote --remove-other-dead-server=%s -H %s/private/sam.ldb"
- % (ntds_guid,
- self.tempdir))
-
- # Check some of the objects that should have been removed
- def check_machine_obj():
- samdb.searchone("CN", "cn=%s,ou=domain controllers,%s" % (self.dc2, server_nc_name))
- self.assertRaises(ldb.LdbError, check_machine_obj)
-
- def check_server_obj():
- samdb.searchone("CN", server_dn)
- self.assertRaises(ldb.LdbError, check_server_obj)
-
- def check_ntds_guid():
- samdb.searchone("CN", "<GUID=%s>" % ntds_guid)
- self.assertRaises(ldb.LdbError, check_ntds_guid)
-
- if dns_obj is not None:
- # Check some of the objects that should have been removed
- def check_dns_account_obj():
- samdb.search(base=dns_obj.dn, scope=ldb.SCOPE_BASE,
- attrs=[])
- self.assertRaises(ldb.LdbError, check_dns_account_obj)
-
- shutil.rmtree(os.path.join(self.tempdir, "private"))
- shutil.rmtree(os.path.join(self.tempdir, "etc"))
- shutil.rmtree(os.path.join(self.tempdir, "msg.lock"))
- os.remove(os.path.join(self.tempdir, "names.tdb"))
- shutil.rmtree(os.path.join(self.tempdir, "state"))
-
- def test_samba_tool_drs_clone_dc_secrets(self):
- """Tests 'samba-tool drs clone-dc-database --include-secrets' command ."""
- server_rootdse = self._get_rootDSE(self.dc1)
- server_nc_name = server_rootdse["defaultNamingContext"]
- server_ds_name = server_rootdse["dsServiceName"]
- server_ldap_service_name = str(server_rootdse["ldapServiceName"][0])
- server_realm = server_ldap_service_name.split(":")[0]
- creds = self.get_credentials()
- out = self.check_output("samba-tool drs clone-dc-database %s --server=%s %s --targetdir=%s --include-secrets"
- % (server_realm,
- self.dc1,
- self.cmdline_creds,
- self.tempdir))
- ldb_rootdse = self._get_rootDSE("tdb://" + os.path.join(self.tempdir, "private", "sam.ldb"), ldap_only=False)
- nc_name = ldb_rootdse["defaultNamingContext"]
- config_nc_name = ldb_rootdse["configurationNamingContext"]
- ds_name = ldb_rootdse["dsServiceName"]
- ldap_service_name = str(server_rootdse["ldapServiceName"][0])
-
- samdb = samba.tests.connect_samdb("tdb://" + os.path.join(self.tempdir, "private", "sam.ldb"),
- ldap_only=False, lp=self.get_loadparm())
- krbtgt_pw = samdb.searchone("unicodePwd", "cn=krbtgt,CN=users,%s" % nc_name)
- self.assertIsNotNone(krbtgt_pw)
-
- self.assertEqual(nc_name, server_nc_name)
- # The clone should pretend to be the source server
- self.assertEqual(ds_name, server_ds_name)
- self.assertEqual(ldap_service_name, server_ldap_service_name)
-
- server_dn = samdb.searchone("serverReferenceBL", "cn=%s,ou=domain controllers,%s" % (self.dc2, server_nc_name))
- ntds_guid = samdb.searchone("objectGUID", "cn=ntds settings,%s" % server_dn)
-
- res = samdb.search(base=str(server_nc_name),
- expression="(&(objectclass=user)(cn=dns-%s))" % (self.dc2),
- attrs=[], scope=ldb.SCOPE_SUBTREE)
- if len(res) == 1:
- dns_obj = res[0]
- else:
- dns_obj = None
-
- def demote_self():
- # While we have this cloned, try demoting the other server on the clone
- out = self.check_output("samba-tool domain demote --remove-other-dead-server=%s -H %s/private/sam.ldb"
- % (self.dc1,
- self.tempdir))
- self.assertRaises(samba.tests.BlackboxProcessError, demote_self)
-
- # While we have this cloned, try demoting the other server on the clone
- out = self.check_output("samba-tool domain demote --remove-other-dead-server=%s -H %s/private/sam.ldb"
- % (self.dc2,
- self.tempdir))
-
- # Check some of the objects that should have been removed
- def check_machine_obj():
- samdb.searchone("CN", "cn=%s,ou=domain controllers,%s" % (self.dc2, server_nc_name))
- self.assertRaises(ldb.LdbError, check_machine_obj)
-
- def check_server_obj():
- samdb.searchone("CN", server_dn)
- self.assertRaises(ldb.LdbError, check_server_obj)
-
- def check_ntds_guid():
- samdb.searchone("CN", "<GUID=%s>" % ntds_guid)
- self.assertRaises(ldb.LdbError, check_ntds_guid)
-
- if dns_obj is not None:
- # Check some of the objects that should have been removed
- def check_dns_account_obj():
- samdb.search(base=dns_obj.dn, scope=ldb.SCOPE_BASE,
- attrs=[])
- self.assertRaises(ldb.LdbError, check_dns_account_obj)
-
- shutil.rmtree(os.path.join(self.tempdir, "private"))
- shutil.rmtree(os.path.join(self.tempdir, "etc"))
- shutil.rmtree(os.path.join(self.tempdir, "msg.lock"))
- os.remove(os.path.join(self.tempdir, "names.tdb"))
- shutil.rmtree(os.path.join(self.tempdir, "state"))
-
- def test_samba_tool_drs_clone_dc_secrets_without_targetdir(self):
- """Tests 'samba-tool drs clone-dc-database' command without --targetdir."""
- server_rootdse = self._get_rootDSE(self.dc1)
- server_ldap_service_name = str(server_rootdse["ldapServiceName"][0])
- server_realm = server_ldap_service_name.split(":")[0]
- creds = self.get_credentials()
- def attempt_clone():
- out = self.check_output("samba-tool drs clone-dc-database %s --server=%s %s"
- % (server_realm,
- self.dc1,
- self.cmdline_creds))
- self.assertRaises(samba.tests.BlackboxProcessError, attempt_clone)
diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py
index ec07106..e38ca5f 100755
--- a/source4/selftest/tests.py
+++ b/source4/selftest/tests.py
@@ -666,7 +666,9 @@ planoldpythontestsuite(env, "ridalloc_exop",
extra_args=['-U$DOMAIN/$DC_USERNAME%$DC_PASSWORD'])
for env in ['vampire_dc', 'promoted_dc']:
- planoldpythontestsuite("%s:local" % env, "samba.tests.blackbox.samba_tool_drs",
+ planoldpythontestsuite("%s:local" % env, "samba_tool_drs",
+ extra_path=[os.path.join(samba4srcdir, 'torture/drs/python')],
+ name="samba4.drs.samba_tool_drs.python(%s)" % env,
environ={'DC1': '$DC_SERVER', 'DC2': '$%s_SERVER' % env.upper()},
extra_args=['-U$DOMAIN/$DC_USERNAME%$DC_PASSWORD'])
planoldpythontestsuite("%s:local" % env, "replica_sync",
diff --git a/source4/torture/drs/python/samba_tool_drs.py b/source4/torture/drs/python/samba_tool_drs.py
new file mode 100644
index 0000000..ccdba1d
--- /dev/null
+++ b/source4/torture/drs/python/samba_tool_drs.py
@@ -0,0 +1,345 @@
+# Blackbox tests for "samba-tool drs" command
+# Copyright (C) Kamen Mazdrashki <kamenim at samba.org> 2011
+#
+# 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/>.
+#
+
+"""Blackbox tests for samba-tool drs."""
+
+import samba.tests
+import shutil
+import os
+import ldb
+import drs_base
+
+class SambaToolDrsTests(drs_base.DrsBaseTestCase):
+ """Blackbox test case for samba-tool drs."""
+
+ def setUp(self):
+ super(SambaToolDrsTests, self).setUp()
+
+ self.dc1 = samba.tests.env_get_var_value("DC1")
+ self.dc2 = samba.tests.env_get_var_value("DC2")
+
+ creds = self.get_credentials()
+ self.cmdline_creds = "-U%s/%s%%%s" % (creds.get_domain(),
+ creds.get_username(), creds.get_password())
+
+ def _get_rootDSE(self, dc, ldap_only=True):
+ samdb = samba.tests.connect_samdb(dc, lp=self.get_loadparm(),
+ credentials=self.get_credentials(),
+ ldap_only=ldap_only)
+ return samdb.search(base="", scope=samba.tests.ldb.SCOPE_BASE)[0]
+
+ def test_samba_tool_bind(self):
+ """Tests 'samba-tool drs bind' command."""
+
+ # Output should be like:
+ # Extensions supported:
+ # <list-of-supported-extensions>
+ # Site GUID: <GUID>
+ # Repl epoch: 0
+ out = self.check_output("samba-tool drs bind %s %s" % (self.dc1,
+ self.cmdline_creds))
+ self.assertTrue("Site GUID:" in out)
+ self.assertTrue("Repl epoch:" in out)
+
+ def test_samba_tool_kcc(self):
+ """Tests 'samba-tool drs kcc' command."""
+
+ # Output should be like 'Consistency check on <DC> successful.'
+ out = self.check_output("samba-tool drs kcc %s %s" % (self.dc1,
+ self.cmdline_creds))
+ self.assertTrue("Consistency check on" in out)
+ self.assertTrue("successful" in out)
+
+ def test_samba_tool_showrepl(self):
+ """Tests 'samba-tool drs showrepl' command.
+ """
+ # Output should be like:
+ # <site-name>/<domain-name>
+ # DSA Options: <hex-options>
+ # DSA object GUID: <DSA-object-GUID>
+ # DSA invocationId: <DSA-invocationId>
+ # <Inbound-connections-list>
+ # <Outbound-connections-list>
+ # <KCC-objects>
+ # ...
+ # TODO: Perhaps we should check at least for
+ # DSA's objectGUDI and invocationId
+ out = self.check_output("samba-tool drs showrepl %s %s" % (self.dc1,
+ self.cmdline_creds))
+ self.assertTrue("DSA Options:" in out)
+ self.assertTrue("DSA object GUID:" in out)
+ self.assertTrue("DSA invocationId:" in out)
+
+ def test_samba_tool_options(self):
+ """Tests 'samba-tool drs options' command
+ """
+ # Output should be like 'Current DSA options: IS_GC <OTHER_FLAGS>'
+ out = self.check_output("samba-tool drs options %s %s" % (self.dc1,
+ self.cmdline_creds))
+ self.assertTrue("Current DSA options:" in out)
+
+ def test_samba_tool_replicate(self):
+ """Tests 'samba-tool drs replicate' command."""
+
+ # Output should be like 'Replicate from <DC-SRC> to <DC-DEST> was successful.'
+ nc_name = self._get_rootDSE(self.dc1)["defaultNamingContext"]
+ out = self.check_output("samba-tool drs replicate %s %s %s %s" % (self.dc1,
+ self.dc2,
+ nc_name,
+ self.cmdline_creds))
+ self.assertTrue("Replicate from" in out)
+ self.assertTrue("was successful" in out)
+
+ def test_samba_tool_replicate_async(self):
+ """Tests 'samba-tool drs replicate --async-op' command."""
+
+ # Output should be like 'Replicate from <DC-SRC> to <DC-DEST> was started.'
+ nc_name = self._get_rootDSE(self.dc1)["defaultNamingContext"]
+ out = self.check_output("samba-tool drs replicate --async-op %s %s %s %s" % (self.dc1,
+ self.dc2,
+ nc_name,
+ self.cmdline_creds))
+ self.assertTrue("Replicate from" in out)
+ self.assertTrue("was started" in out)
+
+ def test_samba_tool_replicate_local_online(self):
+ """Tests 'samba-tool drs replicate --local-online' command."""
+
+ # Output should be like 'Replicate from <DC-SRC> to <DC-DEST> was successful.'
+ nc_name = self._get_rootDSE(self.dc1)["defaultNamingContext"]
+ out = self.check_output("samba-tool drs replicate --local-online %s %s %s" % (self.dc1,
+ self.dc2,
+ nc_name))
+ self.assertTrue("Replicate from" in out)
+ self.assertTrue("was successful" in out)
+
+ def test_samba_tool_replicate_local_online_async(self):
+ """Tests 'samba-tool drs replicate --local-online --async-op' command."""
+
+ # Output should be like 'Replicate from <DC-SRC> to <DC-DEST> was started.'
+ nc_name = self._get_rootDSE(self.dc1)["defaultNamingContext"]
+ out = self.check_output("samba-tool drs replicate --local-online --async-op %s %s %s" % (self.dc1,
+ self.dc2,
+ nc_name))
+ self.assertTrue("Replicate from" in out)
+ self.assertTrue("was started" in out)
+
+ def test_samba_tool_replicate_local_machine_creds(self):
+ """Tests 'samba-tool drs replicate --local -P' command (uses machine creds)."""
+
+ # Output should be like 'Replicate from <DC-SRC> to <DC-DEST> was successful.'
+ nc_name = self._get_rootDSE(self.dc1)["defaultNamingContext"]
+ out = self.check_output("samba-tool drs replicate -P --local %s %s %s" % (self.dc1,
+ self.dc2,
+ nc_name))
+ self.assertTrue("Incremental" in out)
+ self.assertTrue("was successful" in out)
+
+ def test_samba_tool_replicate_local(self):
+ """Tests 'samba-tool drs replicate --local' command (uses machine creds)."""
+
+ # Output should be like 'Replicate from <DC-SRC> to <DC-DEST> was successful.'
+ nc_name = self._get_rootDSE(self.dc1)["defaultNamingContext"]
+ out = self.check_output("samba-tool drs replicate --local %s %s %s %s" % (self.dc1,
+ self.dc2,
+ nc_name,
+ self.cmdline_creds))
+ self.assertTrue("Incremental" in out)
+ self.assertTrue("was successful" in out)
+
+ def test_samba_tool_replicate_machine_creds_P(self):
+ """Tests 'samba-tool drs replicate -P' command with machine creds."""
+
+ # Output should be like 'Replicate from <DC-SRC> to <DC-DEST> was successful.'
+ nc_name = self._get_rootDSE(self.dc1)["defaultNamingContext"]
+ out = self.check_output("samba-tool drs replicate -P %s %s %s" % (self.dc1,
+ self.dc2,
+ nc_name))
+ self.assertTrue("Replicate from" in out)
+ self.assertTrue("was successful" in out)
+
+ def test_samba_tool_replicate_machine_creds(self):
+ """Tests 'samba-tool drs replicate' command with implicit machine creds."""
+
+ # Output should be like 'Replicate from <DC-SRC> to <DC-DEST> was successful.'
+ nc_name = self._get_rootDSE(self.dc1)["defaultNamingContext"]
+ out = self.check_output("samba-tool drs replicate %s %s %s" % (self.dc1,
+ self.dc2,
+ nc_name))
+ self.assertTrue("Replicate from" in out)
+ self.assertTrue("was successful" in out)
+
+ def test_samba_tool_drs_clone_dc(self):
+ """Tests 'samba-tool drs clone-dc-database' command."""
+ server_rootdse = self._get_rootDSE(self.dc1)
+ server_nc_name = server_rootdse["defaultNamingContext"]
+ server_ds_name = server_rootdse["dsServiceName"]
+ server_ldap_service_name = str(server_rootdse["ldapServiceName"][0])
+ server_realm = server_ldap_service_name.split(":")[0]
+ creds = self.get_credentials()
+ out = self.check_output("samba-tool drs clone-dc-database %s --server=%s %s --targetdir=%s"
+ % (server_realm,
+ self.dc1,
+ self.cmdline_creds,
+ self.tempdir))
+ ldb_rootdse = self._get_rootDSE("tdb://" + os.path.join(self.tempdir, "private", "sam.ldb"), ldap_only=False)
+ nc_name = ldb_rootdse["defaultNamingContext"]
+ ds_name = ldb_rootdse["dsServiceName"]
+ ldap_service_name = str(server_rootdse["ldapServiceName"][0])
+ self.assertEqual(nc_name, server_nc_name)
+ # The clone should pretend to be the source server
+ self.assertEqual(ds_name, server_ds_name)
+ self.assertEqual(ldap_service_name, server_ldap_service_name)
+
+ samdb = samba.tests.connect_samdb("tdb://" + os.path.join(self.tempdir, "private", "sam.ldb"),
+ ldap_only=False, lp=self.get_loadparm())
+ def get_krbtgt_pw():
+ krbtgt_pw = samdb.searchone("unicodePwd", "cn=krbtgt,CN=users,%s" % nc_name)
+ self.assertRaises(KeyError, get_krbtgt_pw)
+
+ server_dn = samdb.searchone("serverReferenceBL", "cn=%s,ou=domain controllers,%s" % (self.dc2, server_nc_name))
+ ntds_guid = samdb.searchone("objectGUID", "cn=ntds settings,%s" % server_dn)
+
+ res = samdb.search(base=str(server_nc_name),
+ expression="(&(objectclass=user)(cn=dns-%s))" % (self.dc2),
+ attrs=[], scope=ldb.SCOPE_SUBTREE)
+ if len(res) == 1:
+ dns_obj = res[0]
+ else:
+ dns_obj = None
+
+ # While we have this cloned, try demoting the other server on the clone, by GUID
+ out = self.check_output("samba-tool domain demote --remove-other-dead-server=%s -H %s/private/sam.ldb"
+ % (ntds_guid,
+ self.tempdir))
+
+ # Check some of the objects that should have been removed
+ def check_machine_obj():
+ samdb.searchone("CN", "cn=%s,ou=domain controllers,%s" % (self.dc2, server_nc_name))
+ self.assertRaises(ldb.LdbError, check_machine_obj)
+
+ def check_server_obj():
+ samdb.searchone("CN", server_dn)
+ self.assertRaises(ldb.LdbError, check_server_obj)
+
+ def check_ntds_guid():
+ samdb.searchone("CN", "<GUID=%s>" % ntds_guid)
+ self.assertRaises(ldb.LdbError, check_ntds_guid)
+
+ if dns_obj is not None:
+ # Check some of the objects that should have been removed
+ def check_dns_account_obj():
+ samdb.search(base=dns_obj.dn, scope=ldb.SCOPE_BASE,
+ attrs=[])
+ self.assertRaises(ldb.LdbError, check_dns_account_obj)
+
+ shutil.rmtree(os.path.join(self.tempdir, "private"))
+ shutil.rmtree(os.path.join(self.tempdir, "etc"))
+ shutil.rmtree(os.path.join(self.tempdir, "msg.lock"))
+ os.remove(os.path.join(self.tempdir, "names.tdb"))
+ shutil.rmtree(os.path.join(self.tempdir, "state"))
+
+ def test_samba_tool_drs_clone_dc_secrets(self):
+ """Tests 'samba-tool drs clone-dc-database --include-secrets' command ."""
+ server_rootdse = self._get_rootDSE(self.dc1)
+ server_nc_name = server_rootdse["defaultNamingContext"]
+ server_ds_name = server_rootdse["dsServiceName"]
+ server_ldap_service_name = str(server_rootdse["ldapServiceName"][0])
+ server_realm = server_ldap_service_name.split(":")[0]
+ creds = self.get_credentials()
+ out = self.check_output("samba-tool drs clone-dc-database %s --server=%s %s --targetdir=%s --include-secrets"
+ % (server_realm,
+ self.dc1,
+ self.cmdline_creds,
+ self.tempdir))
+ ldb_rootdse = self._get_rootDSE("tdb://" + os.path.join(self.tempdir, "private", "sam.ldb"), ldap_only=False)
+ nc_name = ldb_rootdse["defaultNamingContext"]
+ config_nc_name = ldb_rootdse["configurationNamingContext"]
+ ds_name = ldb_rootdse["dsServiceName"]
+ ldap_service_name = str(server_rootdse["ldapServiceName"][0])
+
+ samdb = samba.tests.connect_samdb("tdb://" + os.path.join(self.tempdir, "private", "sam.ldb"),
+ ldap_only=False, lp=self.get_loadparm())
+ krbtgt_pw = samdb.searchone("unicodePwd", "cn=krbtgt,CN=users,%s" % nc_name)
+ self.assertIsNotNone(krbtgt_pw)
+
+ self.assertEqual(nc_name, server_nc_name)
+ # The clone should pretend to be the source server
+ self.assertEqual(ds_name, server_ds_name)
+ self.assertEqual(ldap_service_name, server_ldap_service_name)
+
+ server_dn = samdb.searchone("serverReferenceBL", "cn=%s,ou=domain controllers,%s" % (self.dc2, server_nc_name))
+ ntds_guid = samdb.searchone("objectGUID", "cn=ntds settings,%s" % server_dn)
+
+ res = samdb.search(base=str(server_nc_name),
+ expression="(&(objectclass=user)(cn=dns-%s))" % (self.dc2),
+ attrs=[], scope=ldb.SCOPE_SUBTREE)
+ if len(res) == 1:
+ dns_obj = res[0]
+ else:
+ dns_obj = None
+
+ def demote_self():
+ # While we have this cloned, try demoting the other server on the clone
+ out = self.check_output("samba-tool domain demote --remove-other-dead-server=%s -H %s/private/sam.ldb"
+ % (self.dc1,
+ self.tempdir))
+ self.assertRaises(samba.tests.BlackboxProcessError, demote_self)
+
+ # While we have this cloned, try demoting the other server on the clone
+ out = self.check_output("samba-tool domain demote --remove-other-dead-server=%s -H %s/private/sam.ldb"
+ % (self.dc2,
+ self.tempdir))
+
+ # Check some of the objects that should have been removed
+ def check_machine_obj():
+ samdb.searchone("CN", "cn=%s,ou=domain controllers,%s" % (self.dc2, server_nc_name))
+ self.assertRaises(ldb.LdbError, check_machine_obj)
+
+ def check_server_obj():
+ samdb.searchone("CN", server_dn)
+ self.assertRaises(ldb.LdbError, check_server_obj)
+
+ def check_ntds_guid():
+ samdb.searchone("CN", "<GUID=%s>" % ntds_guid)
+ self.assertRaises(ldb.LdbError, check_ntds_guid)
+
+ if dns_obj is not None:
+ # Check some of the objects that should have been removed
+ def check_dns_account_obj():
+ samdb.search(base=dns_obj.dn, scope=ldb.SCOPE_BASE,
+ attrs=[])
+ self.assertRaises(ldb.LdbError, check_dns_account_obj)
+
+ shutil.rmtree(os.path.join(self.tempdir, "private"))
+ shutil.rmtree(os.path.join(self.tempdir, "etc"))
+ shutil.rmtree(os.path.join(self.tempdir, "msg.lock"))
+ os.remove(os.path.join(self.tempdir, "names.tdb"))
+ shutil.rmtree(os.path.join(self.tempdir, "state"))
+
+ def test_samba_tool_drs_clone_dc_secrets_without_targetdir(self):
+ """Tests 'samba-tool drs clone-dc-database' command without --targetdir."""
+ server_rootdse = self._get_rootDSE(self.dc1)
+ server_ldap_service_name = str(server_rootdse["ldapServiceName"][0])
+ server_realm = server_ldap_service_name.split(":")[0]
+ creds = self.get_credentials()
+ def attempt_clone():
+ out = self.check_output("samba-tool drs clone-dc-database %s --server=%s %s"
+ % (server_realm,
+ self.dc1,
+ self.cmdline_creds))
+ self.assertRaises(samba.tests.BlackboxProcessError, attempt_clone)
--
1.9.1
From 3cb29ffab7ed98e0c0c94f545ecfbdf0f32827a5 Mon Sep 17 00:00:00 2001
From: Bob Campbell <bobcampbell at catalyst.net.nz>
Date: Fri, 27 Jan 2017 15:22:27 +1300
Subject: [PATCH 6/6] python/tests: improve samba-tool replicate --local test
It now makes sure that we only replicate incremental changes.
Signed-off-by: Bob Campbell <bobcampbell at catalyst.net.nz>
---
source4/torture/drs/python/samba_tool_drs.py | 125 +++++++++++++++++++++++----
1 file changed, 108 insertions(+), 17 deletions(-)
diff --git a/source4/torture/drs/python/samba_tool_drs.py b/source4/torture/drs/python/samba_tool_drs.py
index ccdba1d..477529d 100644
--- a/source4/torture/drs/python/samba_tool_drs.py
+++ b/source4/torture/drs/python/samba_tool_drs.py
@@ -1,5 +1,6 @@
# Blackbox tests for "samba-tool drs" command
# Copyright (C) Kamen Mazdrashki <kamenim at samba.org> 2011
+# Copyright (C) Andrew Bartlett <abartlet at samba.org> 2017
#
# 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
@@ -36,6 +37,21 @@ class SambaToolDrsTests(drs_base.DrsBaseTestCase):
self.cmdline_creds = "-U%s/%s%%%s" % (creds.get_domain(),
creds.get_username(), creds.get_password())
+ def tearDown(self):
+ self._enable_inbound_repl(self.dnsname_dc1)
+ self._enable_inbound_repl(self.dnsname_dc2)
+
+ try:
+ shutil.rmtree(os.path.join(self.tempdir, "private"))
+ shutil.rmtree(os.path.join(self.tempdir, "etc"))
+ shutil.rmtree(os.path.join(self.tempdir, "msg.lock"))
+ os.remove(os.path.join(self.tempdir, "names.tdb"))
+ shutil.rmtree(os.path.join(self.tempdir, "state"))
+ except Exception:
+ pass
+
+ super(SambaToolDrsTests, self).tearDown()
+
def _get_rootDSE(self, dc, ldap_only=True):
samdb = samba.tests.connect_samdb(dc, lp=self.get_loadparm(),
credentials=self.get_credentials(),
@@ -154,12 +170,99 @@ class SambaToolDrsTests(drs_base.DrsBaseTestCase):
# Output should be like 'Replicate from <DC-SRC> to <DC-DEST> was successful.'
nc_name = self._get_rootDSE(self.dc1)["defaultNamingContext"]
- out = self.check_output("samba-tool drs replicate --local %s %s %s %s" % (self.dc1,
- self.dc2,
- nc_name,
- self.cmdline_creds))
- self.assertTrue("Incremental" in out)
+
+ def get_num_obj_links(output):
+ num_objs = None
+ num_links = None
+ for word in output.split(" "):
+ try:
+ int(word)
+ if num_objs is None:
+ num_objs = int(word)
+ elif num_links is None:
+ num_links = int(word)
+ except ValueError:
+ pass
+
+ return (num_objs, num_links)
+
+ out = self.check_output("samba-tool drs replicate --local --full-sync %s %s %s %s"
+ % (self.dc1, self.dc2, nc_name, self.cmdline_creds))
+ self.assertTrue("was successful" in out)
+ self.assertTrue("Full" in out)
+
+ (first_obj, _) = get_num_obj_links(out)
+
+ out = self.check_output("samba-tool drs replicate --local %s %s %s %s"
+ % (self.dc1, self.dc2, nc_name, self.cmdline_creds))
self.assertTrue("was successful" in out)
+ self.assertTrue("Incremental" in out)
+
+ (second_obj, _) = get_num_obj_links(out)
+
+ self.assertTrue(first_obj > second_obj)
+
+ server_rootdse = self._get_rootDSE(self.dc1)
+ server_nc_name = server_rootdse["defaultNamingContext"]
+ server_ds_name = server_rootdse["dsServiceName"]
+ server_ldap_service_name = str(server_rootdse["ldapServiceName"][0])
+ server_realm = server_ldap_service_name.split(":")[0]
+ creds = self.get_credentials()
+
+ # We have to give it a different netbiosname every time
+ # it runs, otherwise the collision causes strange issues
+ # to happen. This should be different on different environments.
+ netbiosname = "test" + self.dc2
+ if len(netbiosname) > 15:
+ netbiosname = netbiosname[:15]
+
+ out = self.check_output("samba-tool domain join %s dc --server=%s %s --targetdir=%s --option=netbiosname=%s"
+ % (server_realm, self.dc1, self.cmdline_creds, self.tempdir, netbiosname))
+
+ new_dc_config_file = "%s/etc/smb.conf" % self.tempdir
+
+ self.check_output("samba-tool drs replicate --local %s %s %s %s -s %s"
+ % ("invalid", self.dc1, nc_name,
+ self.cmdline_creds, new_dc_config_file))
+
+ self._disable_inbound_repl(self.dnsname_dc1)
+ self._disable_inbound_repl(self.dnsname_dc2)
+
+ self._net_drs_replicate(DC=self.dnsname_dc1, fromDC=self.dnsname_dc2)
+
+ # add an object with link on dc1
+ group_name = "group-repl-local-%s" % self.dc2
+ user_name = "user-repl-local-%s" % self.dc2
+
+ self.check_output("samba-tool group add %s %s -H ldap://%s"
+ % (group_name, self.cmdline_creds, self.dc1))
+ self.check_output("samba-tool user add %s %s --random-password -H ldap://%s"
+ % (user_name, self.cmdline_creds, self.dc1))
+ self.check_output("samba-tool group addmembers %s %s %s -H ldap://%s"
+ % (group_name, user_name, self.cmdline_creds, self.dc1))
+
+ self._net_drs_replicate(DC=self.dnsname_dc2, fromDC=self.dnsname_dc1)
+
+ # pull that change with --local into local db from dc1: should send link and some objects
+ out = self.check_output("samba-tool drs replicate --local %s %s %s %s -s %s"
+ % ("invalid", self.dc1, nc_name,
+ self.cmdline_creds, new_dc_config_file))
+
+ (obj_1, link_1) = get_num_obj_links(out)
+
+ self.assertEqual(obj_1, 2)
+ self.assertEqual(link_1, 1)
+
+ # pull that change with --local into local db from dc2: shouldn't send link or object
+ # as we sent an up-to-dateness vector showing that we had already synced with DC1
+ out = self.check_output("samba-tool drs replicate --local %s %s %s %s -s %s"
+ % ("invalid", self.dc2, nc_name,
+ self.cmdline_creds, new_dc_config_file))
+
+ (obj_2, link_2) = get_num_obj_links(out)
+
+ self.assertEqual(obj_2, 0)
+ self.assertEqual(link_2, 0)
def test_samba_tool_replicate_machine_creds_P(self):
"""Tests 'samba-tool drs replicate -P' command with machine creds."""
@@ -247,12 +350,6 @@ class SambaToolDrsTests(drs_base.DrsBaseTestCase):
attrs=[])
self.assertRaises(ldb.LdbError, check_dns_account_obj)
- shutil.rmtree(os.path.join(self.tempdir, "private"))
- shutil.rmtree(os.path.join(self.tempdir, "etc"))
- shutil.rmtree(os.path.join(self.tempdir, "msg.lock"))
- os.remove(os.path.join(self.tempdir, "names.tdb"))
- shutil.rmtree(os.path.join(self.tempdir, "state"))
-
def test_samba_tool_drs_clone_dc_secrets(self):
"""Tests 'samba-tool drs clone-dc-database --include-secrets' command ."""
server_rootdse = self._get_rootDSE(self.dc1)
@@ -325,12 +422,6 @@ class SambaToolDrsTests(drs_base.DrsBaseTestCase):
attrs=[])
self.assertRaises(ldb.LdbError, check_dns_account_obj)
- shutil.rmtree(os.path.join(self.tempdir, "private"))
- shutil.rmtree(os.path.join(self.tempdir, "etc"))
- shutil.rmtree(os.path.join(self.tempdir, "msg.lock"))
- os.remove(os.path.join(self.tempdir, "names.tdb"))
- shutil.rmtree(os.path.join(self.tempdir, "state"))
-
def test_samba_tool_drs_clone_dc_secrets_without_targetdir(self):
"""Tests 'samba-tool drs clone-dc-database' command without --targetdir."""
server_rootdse = self._get_rootDSE(self.dc1)
--
1.9.1
More information about the samba-technical
mailing list