samba-tool/drs: do partial replication when --local is given by default
authorBob Campbell <bobcampbell@catalyst.net.nz>
Thu, 26 Jan 2017 21:40:59 +0000 (10:40 +1300)
committerAndrew Bartlett <abartlet@samba.org>
Tue, 14 Feb 2017 20:28:25 +0000 (21:28 +0100)
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@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
Reviewed-by: Stefan Metzmacher <metze@samba.org>
Pair-programmed-with: Andrew Bartlett <abartlet@samba.org>

python/samba/netcmd/drs.py
python/samba/tests/blackbox/samba_tool_drs.py

index 67733aca53d426a5f21690aab48c016aa1f2106b..bf6829e2ed748853ce2ac028126e8208508798a7 100644 (file)
@@ -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@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 = 1
+        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:
index 90921f4a9be6c13ac59ee197e5c9528e986cdf06..df0e3d72104891a1a31237925a8b59d0b6a03e47 100644 (file)
@@ -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):