PY3: change shebang to python3 in source4/scripting/bin dir
[vlendec/samba-autobuild/.git] / source4 / scripting / bin / samba_kcc
index 3a2c48871c49af804203f61d8b307b749750790d..aded135907b316c4ba5bab4de74885850303b6ad 100755 (executable)
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 #
 # Compute our KCC topology
 #
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+from __future__ import print_function
 
 import os
 import sys
 import random
-import uuid
 
 # ensure we get messages out immediately, so they get in the samba logs,
 # and don't get swallowed by a timeout
@@ -40,2979 +40,39 @@ os.environ["TZ"] = "GMT"
 sys.path.insert(0, "bin/python")
 
 import optparse
-import logging
-import itertools
-import heapq
 import time
-from functools import partial
-
-from samba import (
-    getopt as options,
-    Ldb,
-    ldb,
-    dsdb,
-    read_and_sub_file,
-    drs_utils,
-    nttime2unix)
-from samba.auth import system_session
-from samba.samdb import SamDB
-from samba.dcerpc import drsuapi
-from samba.kcc_utils import *
-from samba.graph_utils import *
-from samba import ldif_utils
-
-
-class KCC(object):
-    """The Knowledge Consistency Checker class.
-
-    A container for objects and methods allowing a run of the KCC.  Produces a
-    set of connections in the samdb for which the Distributed Replication
-    Service can then utilize to replicate naming contexts
-    """
-    def __init__(self):
-        """Initializes the partitions class which can hold
-        our local DCs partitions or all the partitions in
-        the forest
-        """
-        self.part_table = {}    # partition objects
-        self.site_table = {}
-        self.transport_table = {}
-        self.ip_transport = None
-        self.sitelink_table = {}
-        self.dsa_by_dnstr = {}
-        self.dsa_by_guid = {}
-
-        self.get_dsa_by_guidstr = self.dsa_by_guid.get
-        self.get_dsa = self.dsa_by_dnstr.get
-
-        # TODO: These should be backed by a 'permanent' store so that when
-        # calling DRSGetReplInfo with DS_REPL_INFO_KCC_DSA_CONNECT_FAILURES,
-        # the failure information can be returned
-        self.kcc_failed_links = {}
-        self.kcc_failed_connections = set()
-
-        # Used in inter-site topology computation.  A list
-        # of connections (by NTDSConnection object) that are
-        # to be kept when pruning un-needed NTDS Connections
-        self.kept_connections = set()
-
-        self.my_dsa_dnstr = None  # My dsa DN
-        self.my_dsa = None  # My dsa object
-
-        self.my_site_dnstr = None
-        self.my_site = None
-
-        self.samdb = None
-
-    def load_all_transports(self):
-        """Loads the inter-site transport objects for Sites
-
-        ::returns: Raises KCCError on error
-        """
-        try:
-            res = self.samdb.search("CN=Inter-Site Transports,CN=Sites,%s" %
-                                    self.samdb.get_config_basedn(),
-                                    scope=ldb.SCOPE_SUBTREE,
-                                    expression="(objectClass=interSiteTransport)")
-        except ldb.LdbError, (enum, estr):
-            raise KCCError("Unable to find inter-site transports - (%s)" %
-                           estr)
-
-        for msg in res:
-            dnstr = str(msg.dn)
-
-            transport = Transport(dnstr)
-
-            transport.load_transport(self.samdb)
-            self.transport_table.setdefault(str(transport.guid),
-                                            transport)
-            if transport.name == 'IP':
-                self.ip_transport = transport
-
-        if self.ip_transport is None:
-            raise KCCError("there doesn't seem to be an IP transport")
-
-    def load_all_sitelinks(self):
-        """Loads the inter-site siteLink objects
-
-        ::returns: Raises KCCError on error
-        """
-        try:
-            res = self.samdb.search("CN=Inter-Site Transports,CN=Sites,%s" %
-                                    self.samdb.get_config_basedn(),
-                                    scope=ldb.SCOPE_SUBTREE,
-                                    expression="(objectClass=siteLink)")
-        except ldb.LdbError, (enum, estr):
-            raise KCCError("Unable to find inter-site siteLinks - (%s)" % estr)
-
-        for msg in res:
-            dnstr = str(msg.dn)
-
-            # already loaded
-            if dnstr in self.sitelink_table:
-                continue
-
-            sitelink = SiteLink(dnstr)
-
-            sitelink.load_sitelink(self.samdb)
-
-            # Assign this siteLink to table
-            # and index by dn
-            self.sitelink_table[dnstr] = sitelink
-
-    def load_site(self, dn_str):
-        """Helper for load_my_site and load_all_sites. It puts all the site's
-        DSAs into the KCC indices.
-        """
-        site = Site(dn_str, unix_now)
-        site.load_site(self.samdb)
-
-        # I am not sure why, but we avoid replacing the site with an
-        # identical copy.
-        guid = str(site.site_guid)
-        if guid not in self.site_table:
-            self.site_table[guid] = site
-
-        self.dsa_by_dnstr.update(site.dsa_table)
-        self.dsa_by_guid.update((str(x.dsa_guid), x)
-                                for x in site.dsa_table.values())
-
-        return site
-
-    def load_my_site(self):
-        """Loads the Site class for the local DSA
-
-        ::returns: Raises an Exception on error
-        """
-        self.my_site_dnstr = ("CN=%s,CN=Sites,%s" % (
-            self.samdb.server_site_name(),
-            self.samdb.get_config_basedn()))
-
-        self.my_site = self.load_site(self.my_site_dnstr)
-
-    def load_all_sites(self):
-        """Discover all sites and instantiate and load each
-        NTDS Site settings.
-
-        ::returns: Raises KCCError on error
-        """
-        try:
-            res = self.samdb.search("CN=Sites,%s" %
-                                    self.samdb.get_config_basedn(),
-                                    scope=ldb.SCOPE_SUBTREE,
-                                    expression="(objectClass=site)")
-        except ldb.LdbError, (enum, estr):
-            raise KCCError("Unable to find sites - (%s)" % estr)
-
-        for msg in res:
-            sitestr = str(msg.dn)
-            self.load_site(sitestr)
-
-    def load_my_dsa(self):
-        """Discover my nTDSDSA dn thru the rootDSE entry
-
-        ::returns: Raises KCCError on error.
-        """
-        dn = ldb.Dn(self.samdb, "<GUID=%s>" % self.samdb.get_ntds_GUID())
-        try:
-            res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
-                                    attrs=["objectGUID"])
-        except ldb.LdbError, (enum, estr):
-            logger.warning("Search for %s failed: %s.  This typically happens"
-                           " in --importldif mode due to lack of module"
-                           " support.", dn, estr)
-            try:
-                # We work around the failure above by looking at the
-                # dsServiceName that was put in the fake rootdse by
-                # the --exportldif, rather than the
-                # samdb.get_ntds_GUID(). The disadvantage is that this
-                # mode requires we modify the @ROOTDSE dnq to support
-                # --forced-local-dsa
-                service_name_res = self.samdb.search(base="",
-                                                     scope=ldb.SCOPE_BASE,
-                                                     attrs=["dsServiceName"])
-                dn = ldb.Dn(self.samdb,
-                            service_name_res[0]["dsServiceName"][0])
-
-                res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
-                                        attrs=["objectGUID"])
-            except ldb.LdbError, (enum, estr):
-                raise KCCError("Unable to find my nTDSDSA - (%s)" % estr)
-
-        if len(res) != 1:
-            raise KCCError("Unable to find my nTDSDSA at %s" %
-                           dn.extended_str())
-
-        ntds_guid = misc.GUID(self.samdb.get_ntds_GUID())
-        if misc.GUID(res[0]["objectGUID"][0]) != ntds_guid:
-            raise KCCError("Did not find the GUID we expected,"
-                           " perhaps due to --importldif")
-
-        self.my_dsa_dnstr = str(res[0].dn)
-
-        self.my_dsa = self.my_site.get_dsa(self.my_dsa_dnstr)
-
-        if self.my_dsa_dnstr not in self.dsa_by_dnstr:
-            DEBUG_DARK_YELLOW("my_dsa %s isn't in self.dsas_by_dnstr:"
-                              " it must be RODC.\n"
-                              "Let's add it, because my_dsa is special!\n"
-                              "(likewise for self.dsa_by_guid of course)" %
-                              self.my_dsas_dnstr)
-
-            self.dsa_by_dnstr[self.my_dsa_dnstr] = self.my_dsa
-            self.dsa_by_guid[str(self.my_dsa.dsa_guid)] = self.my_dsa
-
-    def load_all_partitions(self):
-        """Discover all NCs thru the Partitions dn and
-        instantiate and load the NCs.
-
-        Each NC is inserted into the part_table by partition
-        dn string (not the nCName dn string)
-
-        ::returns: Raises KCCError on error
-        """
-        try:
-            res = self.samdb.search("CN=Partitions,%s" %
-                                    self.samdb.get_config_basedn(),
-                                    scope=ldb.SCOPE_SUBTREE,
-                                    expression="(objectClass=crossRef)")
-        except ldb.LdbError, (enum, estr):
-            raise KCCError("Unable to find partitions - (%s)" % estr)
-
-        for msg in res:
-            partstr = str(msg.dn)
-
-            # already loaded
-            if partstr in self.part_table:
-                continue
-
-            part = Partition(partstr)
-
-            part.load_partition(self.samdb)
-            self.part_table[partstr] = part
-
-    def should_be_present_test(self):
-        """Enumerate all loaded partitions and DSAs in local
-        site and test if NC should be present as replica
-        """
-        for partdn, part in self.part_table.items():
-            for dsadn, dsa in self.my_site.dsa_table.items():
-                needed, ro, partial = part.should_be_present(dsa)
-                logger.info("dsadn:%s\nncdn:%s\nneeded=%s:ro=%s:partial=%s\n" %
-                            (dsadn, part.nc_dnstr, needed, ro, partial))
-
-    def refresh_failed_links_connections(self):
-        """Based on MS-ADTS 6.2.2.1"""
-
-        # Instead of NULL link with failure_count = 0, the tuple is
-        # simply removed
-
-        # LINKS: Refresh failed links
-        self.kcc_failed_links = {}
-        current, needed = self.my_dsa.get_rep_tables()
-        for replica in current.values():
-            # For every possible connection to replicate
-            for reps_from in replica.rep_repsFrom:
-                failure_count = reps_from.consecutive_sync_failures
-                if failure_count <= 0:
-                    continue
-
-                dsa_guid = str(reps_from.source_dsa_obj_guid)
-                time_first_failure = reps_from.last_success
-                last_result = reps_from.last_attempt
-                dns_name = reps_from.dns_name1
-
-                f = self.kcc_failed_links.get(dsa_guid)
-                if not f:
-                    f = KCCFailedObject(dsa_guid, failure_count,
-                                        time_first_failure, last_result,
-                                        dns_name)
-                    self.kcc_failed_links[dsa_guid] = f
-                #elif f.failure_count == 0:
-                #    f.failure_count = failure_count
-                #    f.time_first_failure = time_first_failure
-                #    f.last_result = last_result
-                else:
-                    f.failure_count = max(f.failure_count, failure_count)
-                    f.time_first_failure = min(f.time_first_failure,
-                                               time_first_failure)
-                    f.last_result = last_result
-
-        # CONNECTIONS: Refresh failed connections
-        restore_connections = set()
-        if opts.attempt_live_connections:
-            DEBUG("refresh_failed_links: checking if links are still down")
-            for connection in self.kcc_failed_connections:
-                try:
-                    drs_utils.drsuapi_connect(connection.dns_name, lp, creds)
-                    # Failed connection is no longer failing
-                    restore_connections.add(connection)
-                except drs_utils.drsException:
-                    # Failed connection still failing
-                    connection.failure_count += 1
-        else:
-            DEBUG("refresh_failed_links: not checking live links because we\n"
-                  "weren't asked to --attempt-live-connections")
-
-        # Remove the restored connections from the failed connections
-        self.kcc_failed_connections.difference_update(restore_connections)
-
-    def is_stale_link_connection(self, target_dsa):
-        """Returns False if no tuple z exists in the kCCFailedLinks or
-        kCCFailedConnections variables such that z.UUIDDsa is the
-        objectGUID of the target dsa, z.FailureCount > 0, and
-        the current time - z.TimeFirstFailure > 2 hours.
-        """
-        # Returns True if tuple z exists...
-        failed_link = self.kcc_failed_links.get(str(target_dsa.dsa_guid))
-        if failed_link:
-            # failure_count should be > 0, but check anyways
-            if failed_link.failure_count > 0:
-                unix_first_failure = \
-                    nttime2unix(failed_link.time_first_failure)
-                # TODO guard against future
-                if unix_first_failure > unix_now:
-                    logger.error("The last success time attribute for \
-                                 repsFrom is in the future!")
-
-                # Perform calculation in seconds
-                if (unix_now - unix_first_failure) > 60 * 60 * 2:
-                    return True
-
-        # TODO connections
-
-        return False
-
-    # TODO: This should be backed by some form of local database
-    def remove_unneeded_failed_links_connections(self):
-        # Remove all tuples in kcc_failed_links where failure count = 0
-        # In this implementation, this should never happen.
-
-        # Remove all connections which were not used this run or connections
-        # that became active during this run.
-        pass
-
-    def remove_unneeded_ntdsconn(self, all_connected):
-        """Removes unneeded NTDS Connections after computation
-        of KCC intra and inter-site topology has finished.
-        """
-        mydsa = self.my_dsa
-
-        # Loop thru connections
-        for cn_conn in mydsa.connect_table.values():
-            if cn_conn.guid is None:
-                if opts.readonly:
-                    cn_conn.guid = misc.GUID(str(uuid.uuid4()))
-                    cn_conn.whenCreated = nt_now
-                else:
-                    cn_conn.load_connection(self.samdb)
-
-        for cn_conn in mydsa.connect_table.values():
-
-            s_dnstr = cn_conn.get_from_dnstr()
-            if s_dnstr is None:
-                cn_conn.to_be_deleted = True
-                continue
-
-            # Get the source DSA no matter what site
-            s_dsa = self.get_dsa(s_dnstr)
-
-            #XXX should an RODC be regarded as same site
-            same_site = s_dnstr in self.my_site.dsa_table
-
-            # Given an nTDSConnection object cn, if the DC with the
-            # nTDSDSA object dc that is the parent object of cn and
-            # the DC with the nTDSDA object referenced by cn!fromServer
-            # are in the same site, the KCC on dc deletes cn if all of
-            # the following are true:
-            #
-            # Bit NTDSCONN_OPT_IS_GENERATED is clear in cn!options.
-            #
-            # No site settings object s exists for the local DC's site, or
-            # bit NTDSSETTINGS_OPT_IS_TOPL_CLEANUP_DISABLED is clear in
-            # s!options.
-            #
-            # Another nTDSConnection object cn2 exists such that cn and
-            # cn2 have the same parent object, cn!fromServer = cn2!fromServer,
-            # and either
-            #
-            #     cn!whenCreated < cn2!whenCreated
-            #
-            #     cn!whenCreated = cn2!whenCreated and
-            #     cn!objectGUID < cn2!objectGUID
-            #
-            # Bit NTDSCONN_OPT_RODC_TOPOLOGY is clear in cn!options
-            if same_site:
-                if not cn_conn.is_generated():
-                    continue
-
-                if self.my_site.is_cleanup_ntdsconn_disabled():
-                    continue
-
-                # Loop thru connections looking for a duplicate that
-                # fulfills the previous criteria
-                lesser = False
-                packed_guid = ndr_pack(cn_conn.guid)
-                for cn2_conn in mydsa.connect_table.values():
-                    if cn2_conn is cn_conn:
-                        continue
-
-                    s2_dnstr = cn2_conn.get_from_dnstr()
-
-                    # If the NTDS Connections has a different
-                    # fromServer field then no match
-                    if s2_dnstr != s_dnstr:
-                        continue
-
-                    #XXX GUID comparison
-                    lesser = (cn_conn.whenCreated < cn2_conn.whenCreated or
-                              (cn_conn.whenCreated == cn2_conn.whenCreated and
-                               packed_guid < ndr_pack(cn2_conn.guid)))
-
-                    if lesser:
-                        break
-
-                if lesser and not cn_conn.is_rodc_topology():
-                    cn_conn.to_be_deleted = True
-
-            # Given an nTDSConnection object cn, if the DC with the nTDSDSA
-            # object dc that is the parent object of cn and the DC with
-            # the nTDSDSA object referenced by cn!fromServer are in
-            # different sites, a KCC acting as an ISTG in dc's site
-            # deletes cn if all of the following are true:
-            #
-            #     Bit NTDSCONN_OPT_IS_GENERATED is clear in cn!options.
-            #
-            #     cn!fromServer references an nTDSDSA object for a DC
-            #     in a site other than the local DC's site.
-            #
-            #     The keepConnections sequence returned by
-            #     CreateIntersiteConnections() does not contain
-            #     cn!objectGUID, or cn is "superseded by" (see below)
-            #     another nTDSConnection cn2 and keepConnections
-            #     contains cn2!objectGUID.
-            #
-            #     The return value of CreateIntersiteConnections()
-            #     was true.
-            #
-            #     Bit NTDSCONN_OPT_RODC_TOPOLOGY is clear in
-            #     cn!options
-            #
-            else:  # different site
-
-                if not mydsa.is_istg():
-                    continue
-
-                if not cn_conn.is_generated():
-                    continue
-
-                # TODO
-                # We are directly using this connection in intersite or
-                # we are using a connection which can supersede this one.
-                #
-                # MS-ADTS 6.2.2.4 - Removing Unnecessary Connections does not
-                # appear to be correct.
-                #
-                # 1. cn!fromServer and cn!parent appear inconsistent with
-                #    no cn2
-                # 2. The repsFrom do not imply each other
-                #
-                if cn_conn in self.kept_connections:  # and not_superceded:
-                    continue
-
-                # This is the result of create_intersite_connections
-                if not all_connected:
-                    continue
-
-                if not cn_conn.is_rodc_topology():
-                    cn_conn.to_be_deleted = True
-
-        if mydsa.is_ro() or opts.readonly:
-            for connect in mydsa.connect_table.values():
-                if connect.to_be_deleted:
-                    DEBUG_FN("TO BE DELETED:\n%s" % connect)
-                if connect.to_be_added:
-                    DEBUG_FN("TO BE ADDED:\n%s" % connect)
-
-            # Peform deletion from our tables but perform
-            # no database modification
-            mydsa.commit_connections(self.samdb, ro=True)
-        else:
-            # Commit any modified connections
-            mydsa.commit_connections(self.samdb)
-
-    def modify_repsFrom(self, n_rep, t_repsFrom, s_rep, s_dsa, cn_conn):
-        """Part of MS-ADTS 6.2.2.5.
-
-        Update t_repsFrom if necessary to satisfy requirements. Such
-        updates are typically required when the IDL_DRSGetNCChanges
-        server has moved from one site to another--for example, to
-        enable compression when the server is moved from the
-        client's site to another site.
-
-        :param n_rep: NC replica we need
-        :param t_repsFrom: repsFrom tuple to modify
-        :param s_rep: NC replica at source DSA
-        :param s_dsa: source DSA
-        :param cn_conn: Local DSA NTDSConnection child
-
-        ::returns: (update) bit field containing which portion of the
-           repsFrom was modified.  This bit field is suitable as input
-           to IDL_DRSReplicaModify ulModifyFields element, as it consists
-           of these bits:
-               drsuapi.DRSUAPI_DRS_UPDATE_SCHEDULE
-               drsuapi.DRSUAPI_DRS_UPDATE_FLAGS
-               drsuapi.DRSUAPI_DRS_UPDATE_ADDRESS
-        """
-        s_dnstr = s_dsa.dsa_dnstr
-        update = 0x0
-
-        same_site = s_dnstr in self.my_site.dsa_table
-
-        # if schedule doesn't match then update and modify
-        times = convert_schedule_to_repltimes(cn_conn.schedule)
-        if times != t_repsFrom.schedule:
-            t_repsFrom.schedule = times
-            update |= drsuapi.DRSUAPI_DRS_UPDATE_SCHEDULE
-
-        # Bit DRS_PER_SYNC is set in replicaFlags if and only
-        # if nTDSConnection schedule has a value v that specifies
-        # scheduled replication is to be performed at least once
-        # per week.
-        if cn_conn.is_schedule_minimum_once_per_week():
-
-            if ((t_repsFrom.replica_flags &
-                 drsuapi.DRSUAPI_DRS_PER_SYNC) == 0x0):
-                t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_PER_SYNC
-
-        # Bit DRS_INIT_SYNC is set in t.replicaFlags if and only
-        # if the source DSA and the local DC's nTDSDSA object are
-        # in the same site or source dsa is the FSMO role owner
-        # of one or more FSMO roles in the NC replica.
-        if same_site or n_rep.is_fsmo_role_owner(s_dnstr):
-
-            if ((t_repsFrom.replica_flags &
-                 drsuapi.DRSUAPI_DRS_INIT_SYNC) == 0x0):
-                t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_INIT_SYNC
-
-        # If bit NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT is set in
-        # cn!options, bit DRS_NEVER_NOTIFY is set in t.replicaFlags
-        # if and only if bit NTDSCONN_OPT_USE_NOTIFY is clear in
-        # cn!options. Otherwise, bit DRS_NEVER_NOTIFY is set in
-        # t.replicaFlags if and only if s and the local DC's
-        # nTDSDSA object are in different sites.
-        if ((cn_conn.options &
-             dsdb.NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT) != 0x0):
-
-            if (cn_conn.options & dsdb.NTDSCONN_OPT_USE_NOTIFY) == 0x0:
-                # XXX WARNING
-                #
-                # it LOOKS as if this next test is a bit silly: it
-                # checks the flag then sets it if it not set; the same
-                # effect could be achieved by unconditionally setting
-                # it. But in fact the repsFrom object has special
-                # magic attached to it, and altering replica_flags has
-                # side-effects. That is bad in my opinion, but there
-                # you go.
-                if ((t_repsFrom.replica_flags &
-                     drsuapi.DRSUAPI_DRS_NEVER_NOTIFY) == 0x0):
-                    t_repsFrom.replica_flags |= \
-                        drsuapi.DRSUAPI_DRS_NEVER_NOTIFY
-
-        elif not same_site:
-
-            if ((t_repsFrom.replica_flags &
-                 drsuapi.DRSUAPI_DRS_NEVER_NOTIFY) == 0x0):
-                t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_NEVER_NOTIFY
-
-        # Bit DRS_USE_COMPRESSION is set in t.replicaFlags if
-        # and only if s and the local DC's nTDSDSA object are
-        # not in the same site and the
-        # NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION bit is
-        # clear in cn!options
-        if (not same_site and
-            (cn_conn.options &
-             dsdb.NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION) == 0x0):
-
-            if ((t_repsFrom.replica_flags &
-                 drsuapi.DRSUAPI_DRS_USE_COMPRESSION) == 0x0):
-                t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_USE_COMPRESSION
-
-        # Bit DRS_TWOWAY_SYNC is set in t.replicaFlags if and only
-        # if bit NTDSCONN_OPT_TWOWAY_SYNC is set in cn!options.
-        if (cn_conn.options & dsdb.NTDSCONN_OPT_TWOWAY_SYNC) != 0x0:
-
-            if ((t_repsFrom.replica_flags &
-                 drsuapi.DRSUAPI_DRS_TWOWAY_SYNC) == 0x0):
-                t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_TWOWAY_SYNC
-
-        # Bits DRS_DISABLE_AUTO_SYNC and DRS_DISABLE_PERIODIC_SYNC are
-        # set in t.replicaFlags if and only if cn!enabledConnection = false.
-        if not cn_conn.is_enabled():
-
-            if ((t_repsFrom.replica_flags &
-                 drsuapi.DRSUAPI_DRS_DISABLE_AUTO_SYNC) == 0x0):
-                t_repsFrom.replica_flags |= \
-                    drsuapi.DRSUAPI_DRS_DISABLE_AUTO_SYNC
-
-            if ((t_repsFrom.replica_flags &
-                 drsuapi.DRSUAPI_DRS_DISABLE_PERIODIC_SYNC) == 0x0):
-                t_repsFrom.replica_flags |= \
-                    drsuapi.DRSUAPI_DRS_DISABLE_PERIODIC_SYNC
-
-        # If s and the local DC's nTDSDSA object are in the same site,
-        # cn!transportType has no value, or the RDN of cn!transportType
-        # is CN=IP:
-        #
-        #     Bit DRS_MAIL_REP in t.replicaFlags is clear.
-        #
-        #     t.uuidTransport = NULL GUID.
-        #
-        #     t.uuidDsa = The GUID-based DNS name of s.
-        #
-        # Otherwise:
-        #
-        #     Bit DRS_MAIL_REP in t.replicaFlags is set.
-        #
-        #     If x is the object with dsname cn!transportType,
-        #     t.uuidTransport = x!objectGUID.
-        #
-        #     Let a be the attribute identified by
-        #     x!transportAddressAttribute. If a is
-        #     the dNSHostName attribute, t.uuidDsa = the GUID-based
-        #      DNS name of s. Otherwise, t.uuidDsa = (s!parent)!a.
-        #
-        # It appears that the first statement i.e.
-        #
-        #     "If s and the local DC's nTDSDSA object are in the same
-        #      site, cn!transportType has no value, or the RDN of
-        #      cn!transportType is CN=IP:"
-        #
-        # could be a slightly tighter statement if it had an "or"
-        # between each condition.  I believe this should
-        # be interpreted as:
-        #
-        #     IF (same-site) OR (no-value) OR (type-ip)
-        #
-        # because IP should be the primary transport mechanism
-        # (even in inter-site) and the absense of the transportType
-        # attribute should always imply IP no matter if its multi-site
-        #
-        # NOTE MS-TECH INCORRECT:
-        #
-        #     All indications point to these statements above being
-        #     incorrectly stated:
-        #
-        #         t.uuidDsa = The GUID-based DNS name of s.
-        #
-        #         Let a be the attribute identified by
-        #         x!transportAddressAttribute. If a is
-        #         the dNSHostName attribute, t.uuidDsa = the GUID-based
-        #         DNS name of s. Otherwise, t.uuidDsa = (s!parent)!a.
-        #
-        #     because the uuidDSA is a GUID and not a GUID-base DNS
-        #     name.  Nor can uuidDsa hold (s!parent)!a if not
-        #     dNSHostName.  What should have been said is:
-        #
-        #         t.naDsa = The GUID-based DNS name of s
-        #
-        #     That would also be correct if transportAddressAttribute
-        #     were "mailAddress" because (naDsa) can also correctly
-        #     hold the SMTP ISM service address.
-        #
-        nastr = "%s._msdcs.%s" % (s_dsa.dsa_guid, self.samdb.forest_dns_name())
-
-        # We're not currently supporting SMTP replication
-        # so is_smtp_replication_available() is currently
-        # always returning False
-        if ((same_site or
-             cn_conn.transport_dnstr is None or
-             cn_conn.transport_dnstr.find("CN=IP") == 0 or
-             not is_smtp_replication_available())):
-
-            if ((t_repsFrom.replica_flags &
-                 drsuapi.DRSUAPI_DRS_MAIL_REP) != 0x0):
-                t_repsFrom.replica_flags &= ~drsuapi.DRSUAPI_DRS_MAIL_REP
-
-            t_repsFrom.transport_guid = misc.GUID()
-
-            # See (NOTE MS-TECH INCORRECT) above
-            if t_repsFrom.version == 0x1:
-                if t_repsFrom.dns_name1 is None or \
-                   t_repsFrom.dns_name1 != nastr:
-                    t_repsFrom.dns_name1 = nastr
-            else:
-                if t_repsFrom.dns_name1 is None or \
-                   t_repsFrom.dns_name2 is None or \
-                   t_repsFrom.dns_name1 != nastr or \
-                   t_repsFrom.dns_name2 != nastr:
-                    t_repsFrom.dns_name1 = nastr
-                    t_repsFrom.dns_name2 = nastr
-
-        else:
-            if ((t_repsFrom.replica_flags &
-                 drsuapi.DRSUAPI_DRS_MAIL_REP) == 0x0):
-                t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_MAIL_REP
-
-            # We have a transport type but its not an
-            # object in the database
-            if cn_conn.transport_guid not in self.transport_table:
-                raise KCCError("Missing inter-site transport - (%s)" %
-                               cn_conn.transport_dnstr)
-
-            x_transport = self.transport_table[str(cn_conn.transport_guid)]
-
-            if t_repsFrom.transport_guid != x_transport.guid:
-                t_repsFrom.transport_guid = x_transport.guid
-
-            # See (NOTE MS-TECH INCORRECT) above
-            if x_transport.address_attr == "dNSHostName":
-
-                if t_repsFrom.version == 0x1:
-                    if t_repsFrom.dns_name1 is None or \
-                       t_repsFrom.dns_name1 != nastr:
-                        t_repsFrom.dns_name1 = nastr
-                else:
-                    if t_repsFrom.dns_name1 is None or \
-                       t_repsFrom.dns_name2 is None or \
-                       t_repsFrom.dns_name1 != nastr or \
-                       t_repsFrom.dns_name2 != nastr:
-                        t_repsFrom.dns_name1 = nastr
-                        t_repsFrom.dns_name2 = nastr
-
-            else:
-                # MS tech specification says we retrieve the named
-                # attribute in "transportAddressAttribute" from the parent of
-                # the DSA object
-                try:
-                    pdnstr = s_dsa.get_parent_dnstr()
-                    attrs = [x_transport.address_attr]
-
-                    res = self.samdb.search(base=pdnstr, scope=ldb.SCOPE_BASE,
-                                            attrs=attrs)
-                except ldb.LdbError, (enum, estr):
-                    raise KCCError(
-                        "Unable to find attr (%s) for (%s) - (%s)" %
-                        (x_transport.address_attr, pdnstr, estr))
-
-                msg = res[0]
-                nastr = str(msg[x_transport.address_attr][0])
-
-                # See (NOTE MS-TECH INCORRECT) above
-                if t_repsFrom.version == 0x1:
-                    if t_repsFrom.dns_name1 is None or \
-                       t_repsFrom.dns_name1 != nastr:
-                        t_repsFrom.dns_name1 = nastr
-                else:
-                    if t_repsFrom.dns_name1 is None or \
-                       t_repsFrom.dns_name2 is None or \
-                       t_repsFrom.dns_name1 != nastr or \
-                       t_repsFrom.dns_name2 != nastr:
-
-                        t_repsFrom.dns_name1 = nastr
-                        t_repsFrom.dns_name2 = nastr
-
-        if t_repsFrom.is_modified():
-            logger.debug("modify_repsFrom(): %s" % t_repsFrom)
-
-    def is_repsFrom_implied(self, n_rep, cn_conn):
-        """Given a NC replica and NTDS Connection, determine if the connection
-        implies a repsFrom tuple should be present from the source DSA listed
-        in the connection to the naming context
-
-        :param n_rep: NC replica
-        :param conn: NTDS Connection
-        ::returns (True || False), source DSA:
-        """
-        #XXX different conditions for "implies" than MS-ADTS 6.2.2
-
-        # NTDS Connection must satisfy all the following criteria
-        # to imply a repsFrom tuple is needed:
-        #
-        #    cn!enabledConnection = true.
-        #    cn!options does not contain NTDSCONN_OPT_RODC_TOPOLOGY.
-        #    cn!fromServer references an nTDSDSA object.
-
-        s_dsa = None
-
-        if cn_conn.is_enabled() and not cn_conn.is_rodc_topology():
-
-            s_dnstr = cn_conn.get_from_dnstr()
-            if s_dnstr is not None:
-                s_dsa = self.get_dsa(s_dnstr)
-
-        # No DSA matching this source DN string?
-        if s_dsa is None:
-            return False, None
-
-        # To imply a repsFrom tuple is needed, each of these
-        # must be True:
-        #
-        #     An NC replica of the NC "is present" on the DC to
-        #     which the nTDSDSA object referenced by cn!fromServer
-        #     corresponds.
-        #
-        #     An NC replica of the NC "should be present" on
-        #     the local DC
-        s_rep = s_dsa.get_current_replica(n_rep.nc_dnstr)
-
-        if s_rep is None or not s_rep.is_present():
-            return False, None
-
-        # To imply a repsFrom tuple is needed, each of these
-        # must be True:
-        #
-        #     The NC replica on the DC referenced by cn!fromServer is
-        #     a writable replica or the NC replica that "should be
-        #     present" on the local DC is a partial replica.
-        #
-        #     The NC is not a domain NC, the NC replica that
-        #     "should be present" on the local DC is a partial
-        #     replica, cn!transportType has no value, or
-        #     cn!transportType has an RDN of CN=IP.
-        #
-        implied = (not s_rep.is_ro() or n_rep.is_partial()) and \
-                  (not n_rep.is_domain() or
-                   n_rep.is_partial() or
-                   cn_conn.transport_dnstr is None or
-                   cn_conn.transport_dnstr.find("CN=IP") == 0)
-
-        if implied:
-            return True, s_dsa
-        else:
-            return False, None
-
-    def translate_ntdsconn(self):
-        """This function adjusts values of repsFrom abstract attributes of NC
-        replicas on the local DC to match those implied by
-        nTDSConnection objects.
-        [MS-ADTS] 6.2.2.5
-        """
-        if self.my_dsa.is_translate_ntdsconn_disabled():
-            logger.debug("skipping translate_ntdsconn() "
-                         "because disabling flag is set")
-            return
-
-        logger.debug("translate_ntdsconn(): enter")
-
-        current_rep_table, needed_rep_table = self.my_dsa.get_rep_tables()
-
-        # Filled in with replicas we currently have that need deleting
-        delete_reps = set()
-
-        # We're using the MS notation names here to allow
-        # correlation back to the published algorithm.
-        #
-        # n_rep      - NC replica (n)
-        # t_repsFrom - tuple (t) in n!repsFrom
-        # s_dsa      - Source DSA of the replica. Defined as nTDSDSA
-        #              object (s) such that (s!objectGUID = t.uuidDsa)
-        #              In our IDL representation of repsFrom the (uuidDsa)
-        #              attribute is called (source_dsa_obj_guid)
-        # cn_conn    - (cn) is nTDSConnection object and child of the local
-        #               DC's nTDSDSA object and (cn!fromServer = s)
-        # s_rep      - source DSA replica of n
-        #
-        # If we have the replica and its not needed
-        # then we add it to the "to be deleted" list.
-        for dnstr in current_rep_table:
-            if dnstr not in needed_rep_table:
-                delete_reps.add(dnstr)
-
-        DEBUG_FN('current %d needed %d delete %d' % (len(current_rep_table),
-                 len(needed_rep_table), len(delete_reps)))
-
-        if delete_reps:
-            DEBUG('deleting these reps: %s' % delete_reps)
-            for dnstr in delete_reps:
-                del current_rep_table[dnstr]
-
-        # Now perform the scan of replicas we'll need
-        # and compare any current repsFrom against the
-        # connections
-        for n_rep in needed_rep_table.values():
-
-            # load any repsFrom and fsmo roles as we'll
-            # need them during connection translation
-            n_rep.load_repsFrom(self.samdb)
-            n_rep.load_fsmo_roles(self.samdb)
-
-            # Loop thru the existing repsFrom tupples (if any)
-            # XXX This is a list and could contain duplicates
-            #     (multiple load_repsFrom calls)
-            for t_repsFrom in n_rep.rep_repsFrom:
-
-                # for each tuple t in n!repsFrom, let s be the nTDSDSA
-                # object such that s!objectGUID = t.uuidDsa
-                guidstr = str(t_repsFrom.source_dsa_obj_guid)
-                s_dsa = self.get_dsa_by_guidstr(guidstr)
-
-                # Source dsa is gone from config (strange)
-                # so cleanup stale repsFrom for unlisted DSA
-                if s_dsa is None:
-                    logger.warning("repsFrom source DSA guid (%s) not found" %
-                                   guidstr)
-                    t_repsFrom.to_be_deleted = True
-                    continue
-
-                s_dnstr = s_dsa.dsa_dnstr
-
-                # Retrieve my DSAs connection object (if it exists)
-                # that specifies the fromServer equivalent to
-                # the DSA that is specified in the repsFrom source
-                cn_conn = self.my_dsa.get_connection_by_from_dnstr(s_dnstr)
-
-                # Let (cn) be the nTDSConnection object such that (cn)
-                # is a child of the local DC's nTDSDSA object and
-                # (cn!fromServer = s) and (cn!options) does not contain
-                # NTDSCONN_OPT_RODC_TOPOLOGY or NULL if no such (cn) exists.
-
-                # KCC removes this repsFrom tuple if any of the following
-                # is true:
-                #     cn = NULL.
-                #     [...]
-
-                #XXX varying possible interpretations of rodc_topology
-                if cn_conn is None or cn_conn.is_rodc_topology():
-                    t_repsFrom.to_be_deleted = True
-                    continue
-
-                #     [...] KCC removes this repsFrom tuple if:
-                #
-                #     No NC replica of the NC "is present" on DSA that
-                #     would be source of replica
-                #
-                #     A writable replica of the NC "should be present" on
-                #     the local DC, but a partial replica "is present" on
-                #     the source DSA
-                s_rep = s_dsa.get_current_replica(n_rep.nc_dnstr)
-
-                if s_rep is None or not s_rep.is_present() or \
-                   (not n_rep.is_ro() and s_rep.is_partial()):
-
-                    t_repsFrom.to_be_deleted = True
-                    continue
-
-                # If the KCC did not remove t from n!repsFrom, it updates t
-                self.modify_repsFrom(n_rep, t_repsFrom, s_rep, s_dsa, cn_conn)
-
-            # Loop thru connections and add implied repsFrom tuples
-            # for each NTDSConnection under our local DSA if the
-            # repsFrom is not already present
-            for cn_conn in self.my_dsa.connect_table.values():
-
-                implied, s_dsa = self.is_repsFrom_implied(n_rep, cn_conn)
-                if not implied:
-                    continue
-
-                # Loop thru the existing repsFrom tupples (if any) and
-                # if we already have a tuple for this connection then
-                # no need to proceed to add.  It will have been changed
-                # to have the correct attributes above
-                for t_repsFrom in n_rep.rep_repsFrom:
-                    guidstr = str(t_repsFrom.source_dsa_obj_guid)
-                    #XXXX what?
-                    if s_dsa is self.get_dsa_by_guidstr(guidstr):
-                        s_dsa = None
-                        break
-
-                if s_dsa is None:
-                    continue
-
-                # Create a new RepsFromTo and proceed to modify
-                # it according to specification
-                t_repsFrom = RepsFromTo(n_rep.nc_dnstr)
-
-                t_repsFrom.source_dsa_obj_guid = s_dsa.dsa_guid
-
-                s_rep = s_dsa.get_current_replica(n_rep.nc_dnstr)
-
-                self.modify_repsFrom(n_rep, t_repsFrom, s_rep, s_dsa, cn_conn)
-
-                # Add to our NC repsFrom as this is newly computed
-                if t_repsFrom.is_modified():
-                    n_rep.rep_repsFrom.append(t_repsFrom)
-
-            if opts.readonly:
-                # Display any to be deleted or modified repsFrom
-                text = n_rep.dumpstr_to_be_deleted()
-                if text:
-                    logger.info("TO BE DELETED:\n%s" % text)
-                text = n_rep.dumpstr_to_be_modified()
-                if text:
-                    logger.info("TO BE MODIFIED:\n%s" % text)
-
-                # Peform deletion from our tables but perform
-                # no database modification
-                n_rep.commit_repsFrom(self.samdb, ro=True)
-            else:
-                # Commit any modified repsFrom to the NC replica
-                n_rep.commit_repsFrom(self.samdb)
-
-    def merge_failed_links(self):
-        """Merge of kCCFailedLinks and kCCFailedLinks from bridgeheads.
-        The KCC on a writable DC attempts to merge the link and connection
-        failure information from bridgehead DCs in its own site to help it
-        identify failed bridgehead DCs.
-        """
-        # MS-TECH Ref 6.2.2.3.2 Merge of kCCFailedLinks and kCCFailedLinks
-        #     from Bridgeheads
-
-        # 1. Queries every bridgehead server in your site (other than yourself)
-        # 2. For every ntDSConnection that references a server in a different
-        #    site merge all the failure info
-        #
-        # XXX - not implemented yet
-        if opts.attempt_live_connections:
-            DEBUG_RED("merge_failed_links() is NOT IMPLEMENTED")
-        else:
-            DEBUG_FN("skipping merge_failed_links() because it requires "
-                     "real network connections\n"
-                     "and we weren't asked to --attempt-live-connections")
-
-    def setup_graph(self, part):
-        """Set up a GRAPH, populated with a VERTEX for each site
-        object, a MULTIEDGE for each siteLink object, and a
-        MUTLIEDGESET for each siteLinkBridge object (or implied
-        siteLinkBridge).
-
-        ::returns: a new graph
-        """
-        guid_to_vertex = {}
-        # Create graph
-        g = IntersiteGraph()
-        # Add vertices
-        for site_guid, site in self.site_table.items():
-            vertex = Vertex(site, part)
-            vertex.guid = site_guid
-            vertex.ndrpacked_guid = ndr_pack(site.site_guid)
-            g.vertices.add(vertex)
-
-            if not guid_to_vertex.get(site_guid):
-                guid_to_vertex[site_guid] = []
-
-            guid_to_vertex[site_guid].append(vertex)
-
-        connected_vertices = set()
-        for transport_guid, transport in self.transport_table.items():
-            # Currently only ever "IP"
-            for site_link_dn, site_link in self.sitelink_table.items():
-                new_edge = create_edge(transport_guid, site_link,
-                                       guid_to_vertex)
-                connected_vertices.update(new_edge.vertices)
-                g.edges.add(new_edge)
-
-            # If 'Bridge all site links' is enabled and Win2k3 bridges required
-            # is not set
-            # NTDSTRANSPORT_OPT_BRIDGES_REQUIRED 0x00000002
-            # No documentation for this however, ntdsapi.h appears to have:
-            # NTDSSETTINGS_OPT_W2K3_BRIDGES_REQUIRED = 0x00001000
-            if (((self.my_site.site_options & 0x00000002) == 0
-                 and (self.my_site.site_options & 0x00001000) == 0)):
-                g.edge_set.add(create_auto_edge_set(g, transport_guid))
-            else:
-                # TODO get all site link bridges
-                for site_link_bridge in []:
-                    g.edge_set.add(create_edge_set(g, transport_guid,
-                                                   site_link_bridge))
-
-        g.connected_vertices = connected_vertices
-
-        #be less verbose in dot file output unless --debug
-        do_dot_files = opts.dot_files and opts.debug
-        dot_edges = []
-        for edge in g.edges:
-            for a, b in itertools.combinations(edge.vertices, 2):
-                dot_edges.append((a.site.site_dnstr, b.site.site_dnstr))
-        verify_properties = ()
-        verify_and_dot('site_edges', dot_edges, directed=False,
-                       label=self.my_dsa_dnstr,
-                       properties=verify_properties, debug=DEBUG,
-                       verify=opts.verify,
-                       dot_files=do_dot_files)
-
-        return g
-
-    def get_bridgehead(self, site, part, transport, partial_ok, detect_failed):
-        """Get a bridghead DC.
-
-        :param site: site object representing for which a bridgehead
-            DC is desired.
-        :param part: crossRef for NC to replicate.
-        :param transport: interSiteTransport object for replication
-            traffic.
-        :param partial_ok: True if a DC containing a partial
-            replica or a full replica will suffice, False if only
-            a full replica will suffice.
-        :param detect_failed: True to detect failed DCs and route
-            replication traffic around them, False to assume no DC
-            has failed.
-        ::returns: dsa object for the bridgehead DC or None
-        """
-
-        bhs = self.get_all_bridgeheads(site, part, transport,
-                                       partial_ok, detect_failed)
-        if len(bhs) == 0:
-            DEBUG_MAGENTA("get_bridgehead:\n\tsitedn=%s\n\tbhdn=None" %
-                          site.site_dnstr)
-            return None
-        else:
-            DEBUG_GREEN("get_bridgehead:\n\tsitedn=%s\n\tbhdn=%s" %
-                        (site.site_dnstr, bhs[0].dsa_dnstr))
-            return bhs[0]
-
-    def get_all_bridgeheads(self, site, part, transport,
-                            partial_ok, detect_failed):
-        """Get all bridghead DCs satisfying the given criteria
-
-        :param site: site object representing the site for which
-            bridgehead DCs are desired.
-        :param part: partition for NC to replicate.
-        :param transport: interSiteTransport object for
-            replication traffic.
-        :param partial_ok: True if a DC containing a partial
-            replica or a full replica will suffice, False if
-            only a full replica will suffice.
-        :param detect_failed: True to detect failed DCs and route
-            replication traffic around them, FALSE to assume
-            no DC has failed.
-        ::returns: list of dsa object for available bridgehead
-            DCs or None
-        """
-
-        bhs = []
-
-        logger.debug("get_all_bridgeheads: %s" % transport.name)
-        if 'Site-5' in site.site_dnstr:
-            DEBUG_RED("get_all_bridgeheads with %s, part%s, partial_ok %s"
-                      " detect_failed %s" % (site.site_dnstr, part.partstr,
-                                             partial_ok, detect_failed))
-        logger.debug(site.rw_dsa_table)
-        for dsa in site.rw_dsa_table.values():
-
-            pdnstr = dsa.get_parent_dnstr()
-
-            # IF t!bridgeheadServerListBL has one or more values and
-            # t!bridgeheadServerListBL does not contain a reference
-            # to the parent object of dc then skip dc
-            if ((len(transport.bridgehead_list) != 0 and
-                 pdnstr not in transport.bridgehead_list)):
-                continue
-
-            # IF dc is in the same site as the local DC
-            #    IF a replica of cr!nCName is not in the set of NC replicas
-            #    that "should be present" on dc or a partial replica of the
-            #    NC "should be present" but partialReplicasOkay = FALSE
-            #        Skip dc
-            if self.my_site.same_site(dsa):
-                needed, ro, partial = part.should_be_present(dsa)
-                if not needed or (partial and not partial_ok):
-                    continue
-                rep = dsa.get_current_replica(part.nc_dnstr)
-
-            # ELSE
-            #     IF an NC replica of cr!nCName is not in the set of NC
-            #     replicas that "are present" on dc or a partial replica of
-            #     the NC "is present" but partialReplicasOkay = FALSE
-            #          Skip dc
-            else:
-                rep = dsa.get_current_replica(part.nc_dnstr)
-                if rep is None or (rep.is_partial() and not partial_ok):
-                    continue
-
-            # IF AmIRODC() and cr!nCName corresponds to default NC then
-            #     Let dsaobj be the nTDSDSA object of the dc
-            #     IF  dsaobj.msDS-Behavior-Version < DS_DOMAIN_FUNCTION_2008
-            #         Skip dc
-            if self.my_dsa.is_ro() and rep is not None and rep.is_default():
-                if not dsa.is_minimum_behavior(dsdb.DS_DOMAIN_FUNCTION_2008):
-                    continue
-
-            # IF t!name != "IP" and the parent object of dc has no value for
-            # the attribute specified by t!transportAddressAttribute
-            #     Skip dc
-            if transport.name != "IP":
-                # MS tech specification says we retrieve the named
-                # attribute in "transportAddressAttribute" from the parent
-                # of the DSA object
-                try:
-                    attrs = [transport.address_attr]
-
-                    res = self.samdb.search(base=pdnstr, scope=ldb.SCOPE_BASE,
-                                            attrs=attrs)
-                except ldb.LdbError, (enum, estr):
-                    continue
-
-                msg = res[0]
-                if transport.address_attr not in msg:
-                    continue
-
-                nastr = str(msg[transport.address_attr][0])
-
-            # IF BridgeheadDCFailed(dc!objectGUID, detectFailedDCs) = TRUE
-            #     Skip dc
-            if self.is_bridgehead_failed(dsa, detect_failed):
-                DEBUG("bridgehead is failed")
-                continue
-
-            logger.debug("get_all_bridgeheads: dsadn=%s" % dsa.dsa_dnstr)
-            bhs.append(dsa)
-
-        # IF bit NTDSSETTINGS_OPT_IS_RAND_BH_SELECTION_DISABLED is set in
-        # s!options
-        #    SORT bhs such that all GC servers precede DCs that are not GC
-        #    servers, and otherwise by ascending objectGUID
-        # ELSE
-        #    SORT bhs in a random order
-        if site.is_random_bridgehead_disabled():
-            bhs.sort(sort_dsa_by_gc_and_guid)
-        else:
-            random.shuffle(bhs)
-        DEBUG_YELLOW(bhs)
-        return bhs
-
-    def is_bridgehead_failed(self, dsa, detect_failed):
-        """Determine whether a given DC is known to be in a failed state
-        ::returns: True if and only if the DC should be considered failed
-
-        Here we DEPART from the pseudo code spec which appears to be
-        wrong. It says, in full:
-
-    /***** BridgeheadDCFailed *****/
-    /* Determine whether a given DC is known to be in a failed state.
-     * IN: objectGUID - objectGUID of the DC's nTDSDSA object.
-     * IN: detectFailedDCs - TRUE if and only failed DC detection is
-     *     enabled.
-     * RETURNS: TRUE if and only if the DC should be considered to be in a
-     *          failed state.
-     */
-    BridgeheadDCFailed(IN GUID objectGUID, IN bool detectFailedDCs) : bool
-    {
-        IF bit NTDSSETTINGS_OPT_IS_TOPL_DETECT_STALE_DISABLED is set in
-        the options attribute of the site settings object for the local
-        DC's site
-            RETURN FALSE
-        ELSEIF a tuple z exists in the kCCFailedLinks or
-        kCCFailedConnections variables such that z.UUIDDsa =
-        objectGUID, z.FailureCount > 1, and the current time -
-        z.TimeFirstFailure > 2 hours
-            RETURN TRUE
-        ELSE
-            RETURN detectFailedDCs
-        ENDIF
-    }
-
-        where you will see detectFailedDCs is not behaving as
-        advertised -- it is acting as a default return code in the
-        event that a failure is not detected, not a switch turning
-        detection on or off. Elsewhere the documentation seems to
-        concur with the comment rather than the code.
-        """
-        if not detect_failed:
-            return False
-
-        # NTDSSETTINGS_OPT_IS_TOPL_DETECT_STALE_DISABLED = 0x00000008
-        # When DETECT_STALE_DISABLED, we can never know of if
-        # it's in a failed state
-        if self.my_site.site_options & 0x00000008:
-            return False
-
-        return self.is_stale_link_connection(dsa)
-
-    def create_connection(self, part, rbh, rsite, transport,
-                          lbh, lsite, link_opt, link_sched,
-                          partial_ok, detect_failed):
-        """Create an nTDSConnection object with the given parameters
-        if one does not already exist.
-
-        :param part: crossRef object for the NC to replicate.
-        :param rbh: nTDSDSA object for DC to act as the
-            IDL_DRSGetNCChanges server (which is in a site other
-            than the local DC's site).
-        :param rsite: site of the rbh
-        :param transport: interSiteTransport object for the transport
-            to use for replication traffic.
-        :param lbh: nTDSDSA object for DC to act as the
-            IDL_DRSGetNCChanges client (which is in the local DC's site).
-        :param lsite: site of the lbh
-        :param link_opt: Replication parameters (aggregated siteLink options,
-                                                 etc.)
-        :param link_sched: Schedule specifying the times at which
-            to begin replicating.
-        :partial_ok: True if bridgehead DCs containing partial
-            replicas of the NC are acceptable.
-        :param detect_failed: True to detect failed DCs and route
-            replication traffic around them, FALSE to assume no DC
-            has failed.
-        """
-        rbhs_all = self.get_all_bridgeheads(rsite, part, transport,
-                                            partial_ok, False)
-        rbh_table = {x.dsa_dnstr: x for x in rbhs_all}
-
-        DEBUG_GREY("rbhs_all: %s %s" % (len(rbhs_all),
-                                        [x.dsa_dnstr for x in rbhs_all]))
-
-        # MS-TECH says to compute rbhs_avail but then doesn't use it
-        # rbhs_avail = self.get_all_bridgeheads(rsite, part, transport,
-        #                                        partial_ok, detect_failed)
-
-        lbhs_all = self.get_all_bridgeheads(lsite, part, transport,
-                                            partial_ok, False)
-
-        DEBUG_GREY("lbhs_all: %s %s" % (len(lbhs_all),
-                                        [x.dsa_dnstr for x in lbhs_all]))
-
-        # MS-TECH says to compute lbhs_avail but then doesn't use it
-        # lbhs_avail = self.get_all_bridgeheads(lsite, part, transport,
-        #                                       partial_ok, detect_failed)
-
-        # FOR each nTDSConnection object cn such that the parent of cn is
-        # a DC in lbhsAll and cn!fromServer references a DC in rbhsAll
-        for ldsa in lbhs_all:
-            for cn in ldsa.connect_table.values():
-
-                rdsa = rbh_table.get(cn.from_dnstr)
-                if rdsa is None:
-                    continue
-
-                DEBUG_DARK_YELLOW("rdsa is %s" % rdsa.dsa_dnstr)
-                # IF bit NTDSCONN_OPT_IS_GENERATED is set in cn!options and
-                # NTDSCONN_OPT_RODC_TOPOLOGY is clear in cn!options and
-                # cn!transportType references t
-                if ((cn.is_generated() and
-                     not cn.is_rodc_topology() and
-                     cn.transport_guid == transport.guid)):
-
-                    # IF bit NTDSCONN_OPT_USER_OWNED_SCHEDULE is clear in
-                    # cn!options and cn!schedule != sch
-                    #     Perform an originating update to set cn!schedule to
-                    #     sched
-                    if ((not cn.is_user_owned_schedule() and
-                         not cn.is_equivalent_schedule(link_sched))):
-                        cn.schedule = link_sched
-                        cn.set_modified(True)
-
-                    # IF bits NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT and
-                    # NTDSCONN_OPT_USE_NOTIFY are set in cn
-                    if cn.is_override_notify_default() and \
-                       cn.is_use_notify():
-
-                        # IF bit NTDSSITELINK_OPT_USE_NOTIFY is clear in
-                        # ri.Options
-                        #    Perform an originating update to clear bits
-                        #    NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT and
-                        #    NTDSCONN_OPT_USE_NOTIFY in cn!options
-                        if (link_opt & dsdb.NTDSSITELINK_OPT_USE_NOTIFY) == 0:
-                            cn.options &= \
-                                ~(dsdb.NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT |
-                                  dsdb.NTDSCONN_OPT_USE_NOTIFY)
-                            cn.set_modified(True)
-
-                    # ELSE
-                    else:
-
-                        # IF bit NTDSSITELINK_OPT_USE_NOTIFY is set in
-                        # ri.Options
-                        #     Perform an originating update to set bits
-                        #     NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT and
-                        #     NTDSCONN_OPT_USE_NOTIFY in cn!options
-                        if (link_opt & dsdb.NTDSSITELINK_OPT_USE_NOTIFY) != 0:
-                            cn.options |= \
-                                (dsdb.NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT |
-                                 dsdb.NTDSCONN_OPT_USE_NOTIFY)
-                            cn.set_modified(True)
-
-                    # IF bit NTDSCONN_OPT_TWOWAY_SYNC is set in cn!options
-                    if cn.is_twoway_sync():
-
-                        # IF bit NTDSSITELINK_OPT_TWOWAY_SYNC is clear in
-                        # ri.Options
-                        #     Perform an originating update to clear bit
-                        #     NTDSCONN_OPT_TWOWAY_SYNC in cn!options
-                        if (link_opt & dsdb.NTDSSITELINK_OPT_TWOWAY_SYNC) == 0:
-                            cn.options &= ~dsdb.NTDSCONN_OPT_TWOWAY_SYNC
-                            cn.set_modified(True)
-
-                    # ELSE
-                    else:
-
-                        # IF bit NTDSSITELINK_OPT_TWOWAY_SYNC is set in
-                        # ri.Options
-                        #     Perform an originating update to set bit
-                        #     NTDSCONN_OPT_TWOWAY_SYNC in cn!options
-                        if (link_opt & dsdb.NTDSSITELINK_OPT_TWOWAY_SYNC) != 0:
-                            cn.options |= dsdb.NTDSCONN_OPT_TWOWAY_SYNC
-                            cn.set_modified(True)
-
-                    # IF bit NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION is set
-                    # in cn!options
-                    if cn.is_intersite_compression_disabled():
-
-                        # IF bit NTDSSITELINK_OPT_DISABLE_COMPRESSION is clear
-                        # in ri.Options
-                        #     Perform an originating update to clear bit
-                        #     NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION in
-                        #     cn!options
-                        if ((link_opt &
-                             dsdb.NTDSSITELINK_OPT_DISABLE_COMPRESSION) == 0):
-                            cn.options &= \
-                                ~dsdb.NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION
-                            cn.set_modified(True)
-
-                    # ELSE
-                    else:
-                        # IF bit NTDSSITELINK_OPT_DISABLE_COMPRESSION is set in
-                        # ri.Options
-                        #     Perform an originating update to set bit
-                        #     NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION in
-                        #     cn!options
-                        if ((link_opt &
-                             dsdb.NTDSSITELINK_OPT_DISABLE_COMPRESSION) != 0):
-                            cn.options |= \
-                                dsdb.NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION
-                            cn.set_modified(True)
-
-                    # Display any modified connection
-                    if opts.readonly:
-                        if cn.to_be_modified:
-                            logger.info("TO BE MODIFIED:\n%s" % cn)
-
-                        ldsa.commit_connections(self.samdb, ro=True)
-                    else:
-                        ldsa.commit_connections(self.samdb)
-        # ENDFOR
-
-        valid_connections = 0
-
-        # FOR each nTDSConnection object cn such that cn!parent is
-        # a DC in lbhsAll and cn!fromServer references a DC in rbhsAll
-        for ldsa in lbhs_all:
-            for cn in ldsa.connect_table.values():
-
-                rdsa = rbh_table.get(cn.from_dnstr)
-                if rdsa is None:
-                    continue
-
-                DEBUG_DARK_YELLOW("round 2: rdsa is %s" % rdsa.dsa_dnstr)
-
-                # IF (bit NTDSCONN_OPT_IS_GENERATED is clear in cn!options or
-                # cn!transportType references t) and
-                # NTDSCONN_OPT_RODC_TOPOLOGY is clear in cn!options
-                if (((not cn.is_generated() or
-                      cn.transport_guid == transport.guid) and
-                     not cn.is_rodc_topology())):
-
-                    # LET rguid be the objectGUID of the nTDSDSA object
-                    # referenced by cn!fromServer
-                    # LET lguid be (cn!parent)!objectGUID
-
-                    # IF BridgeheadDCFailed(rguid, detectFailedDCs) = FALSE and
-                    # BridgeheadDCFailed(lguid, detectFailedDCs) = FALSE
-                    #     Increment cValidConnections by 1
-                    if ((not self.is_bridgehead_failed(rdsa, detect_failed) and
-                         not self.is_bridgehead_failed(ldsa, detect_failed))):
-                        valid_connections += 1
-
-                    # IF keepConnections does not contain cn!objectGUID
-                    #     APPEND cn!objectGUID to keepConnections
-                    self.kept_connections.add(cn)
-
-        # ENDFOR
-        DEBUG_RED("valid connections %d" % valid_connections)
-        DEBUG("kept_connections:\n%s" % (self.kept_connections,))
-        # IF cValidConnections = 0
-        if valid_connections == 0:
-
-            # LET opt be NTDSCONN_OPT_IS_GENERATED
-            opt = dsdb.NTDSCONN_OPT_IS_GENERATED
-
-            # IF bit NTDSSITELINK_OPT_USE_NOTIFY is set in ri.Options
-            #     SET bits NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT and
-            #     NTDSCONN_OPT_USE_NOTIFY in opt
-            if (link_opt & dsdb.NTDSSITELINK_OPT_USE_NOTIFY) != 0:
-                opt |= (dsdb.NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT |
-                        dsdb.NTDSCONN_OPT_USE_NOTIFY)
-
-            # IF bit NTDSSITELINK_OPT_TWOWAY_SYNC is set in ri.Options
-            #     SET bit NTDSCONN_OPT_TWOWAY_SYNC opt
-            if (link_opt & dsdb.NTDSSITELINK_OPT_TWOWAY_SYNC) != 0:
-                opt |= dsdb.NTDSCONN_OPT_TWOWAY_SYNC
-
-            # IF bit NTDSSITELINK_OPT_DISABLE_COMPRESSION is set in
-            # ri.Options
-            #     SET bit NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION in opt
-            if ((link_opt &
-                 dsdb.NTDSSITELINK_OPT_DISABLE_COMPRESSION) != 0):
-                opt |= dsdb.NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION
-
-            # Perform an originating update to create a new nTDSConnection
-            # object cn that is a child of lbh, cn!enabledConnection = TRUE,
-            # cn!options = opt, cn!transportType is a reference to t,
-            # cn!fromServer is a reference to rbh, and cn!schedule = sch
-            cn = lbh.new_connection(opt, 0, transport,
-                                    rbh.dsa_dnstr, link_sched)
-
-            # Display any added connection
-            if opts.readonly:
-                if cn.to_be_added:
-                    logger.info("TO BE ADDED:\n%s" % cn)
-
-                    lbh.commit_connections(self.samdb, ro=True)
-            else:
-                lbh.commit_connections(self.samdb)
-
-            # APPEND cn!objectGUID to keepConnections
-            self.kept_connections.add(cn)
-
-    def add_transports(self, vertex, local_vertex, graph, detect_failed):
-
-        # The docs ([MS-ADTS] 6.2.2.3.4.3) say to use local_vertex
-        # here and in the, but using vertex seems to make more
-        # sense. That is, it wants this:
-        #
-        #bh = self.get_bridgehead(vertex.site, vertex.part, transport,
-        #                         local_vertex.is_black(), detect_failed)
-        #
-        # TODO WHY?????
-
-        vertex.accept_red_red = []
-        vertex.accept_black = []
-        found_failed = False
-        for t_guid, transport in self.transport_table.items():
-            if transport.name != 'IP':
-                #XXX well this is cheating a bit
-                logging.warning("WARNING: we are ignoring a transport named %r"
-                                % transport.name)
-                continue
-
-            # FLAG_CR_NTDS_DOMAIN 0x00000002
-            if ((vertex.is_red() and transport.name != "IP" and
-                 vertex.part.system_flags & 0x00000002)):
-                continue
-
-            if vertex not in graph.connected_vertices:
-                continue
-
-            partial_replica_okay = vertex.is_black()
-            bh = self.get_bridgehead(vertex.site, vertex.part, transport,
-                                     partial_replica_okay, detect_failed)
-            if bh is None:
-                found_failed = True
-                continue
-
-            vertex.accept_red_red.append(t_guid)
-            vertex.accept_black.append(t_guid)
-
-            # Add additional transport to allow another run of Dijkstra
-            vertex.accept_red_red.append("EDGE_TYPE_ALL")
-            vertex.accept_black.append("EDGE_TYPE_ALL")
-
-        return found_failed
-
-    def create_connections(self, graph, part, detect_failed):
-        """Construct an NC replica graph for the NC identified by
-        the given crossRef, then create any additional nTDSConnection
-        objects required.
-
-        :param graph: site graph.
-        :param part: crossRef object for NC.
-        :param detect_failed:  True to detect failed DCs and route
-            replication traffic around them, False to assume no DC
-            has failed.
-
-        Modifies self.kept_connections by adding any connections
-        deemed to be "in use".
-
-        ::returns: (all_connected, found_failed_dc)
-        (all_connected) True if the resulting NC replica graph
-            connects all sites that need to be connected.
-        (found_failed_dc) True if one or more failed DCs were
-            detected.
-        """
-        all_connected = True
-        found_failed = False
-
-        logger.debug("create_connections(): enter\n"
-                     "\tpartdn=%s\n\tdetect_failed=%s" %
-                     (part.nc_dnstr, detect_failed))
-
-        # XXX - This is a highly abbreviated function from the MS-TECH
-        #       ref.  It creates connections between bridgeheads to all
-        #       sites that have appropriate replicas.  Thus we are not
-        #       creating a minimum cost spanning tree but instead
-        #       producing a fully connected tree.  This should produce
-        #       a full (albeit not optimal cost) replication topology.
-
-        my_vertex = Vertex(self.my_site, part)
-        my_vertex.color_vertex()
-
-        for v in graph.vertices:
-            v.color_vertex()
-            if self.add_transports(v, my_vertex, graph, False):
-                found_failed = True
-
-        # No NC replicas for this NC in the site of the local DC,
-        # so no nTDSConnection objects need be created
-        if my_vertex.is_white():
-            return all_connected, found_failed
-
-        edge_list, n_components = get_spanning_tree_edges(graph,
-                                                          self.my_site,
-                                                          label=part.partstr)
-
-        logger.debug("%s Number of components: %d" %
-                     (part.nc_dnstr, n_components))
-        if n_components > 1:
-            all_connected = False
-
-        # LET partialReplicaOkay be TRUE if and only if
-        # localSiteVertex.Color = COLOR.BLACK
-        if my_vertex.is_black():
-            partial_ok = True
-        else:
-            partial_ok = False
-
-        # Utilize the IP transport only for now
-        transport = self.ip_transport
-
-        DEBUG("edge_list %s" % edge_list)
-        for e in edge_list:
-            # XXX more accurate comparison?
-            if e.directed and e.vertices[0].site is self.my_site:
-                continue
-
-            if e.vertices[0].site is self.my_site:
-                rsite = e.vertices[1].site
-            else:
-                rsite = e.vertices[0].site
-
-            # We don't make connections to our own site as that
-            # is intrasite topology generator's job
-            if rsite is self.my_site:
-                DEBUG("rsite is my_site")
-                continue
-
-            # Determine bridgehead server in remote site
-            rbh = self.get_bridgehead(rsite, part, transport,
-                                      partial_ok, detect_failed)
-            if rbh is None:
-                continue
-
-            # RODC acts as an BH for itself
-            # IF AmIRODC() then
-            #     LET lbh be the nTDSDSA object of the local DC
-            # ELSE
-            #     LET lbh be the result of GetBridgeheadDC(localSiteVertex.ID,
-            #     cr, t, partialReplicaOkay, detectFailedDCs)
-            if self.my_dsa.is_ro():
-                lsite = self.my_site
-                lbh = self.my_dsa
-            else:
-                lsite = self.my_site
-                lbh = self.get_bridgehead(lsite, part, transport,
-                                          partial_ok, detect_failed)
-            # TODO
-            if lbh is None:
-                DEBUG_RED("DISASTER! lbh is None")
-                return False, True
-
-            DEBUG_CYAN("SITES")
-            print lsite, rsite
-            DEBUG_BLUE("vertices")
-            print e.vertices
-            DEBUG_BLUE("bridgeheads")
-            print lbh, rbh
-            DEBUG_BLUE("-" * 70)
-
-            sitelink = e.site_link
-            if sitelink is None:
-                link_opt = 0x0
-                link_sched = None
-            else:
-                link_opt = sitelink.options
-                link_sched = sitelink.schedule
-
-            self.create_connection(part, rbh, rsite, transport,
-                                   lbh, lsite, link_opt, link_sched,
-                                   partial_ok, detect_failed)
-
-        return all_connected, found_failed
-
-    def create_intersite_connections(self):
-        """Computes an NC replica graph for each NC replica that "should be
-        present" on the local DC or "is present" on any DC in the same site
-        as the local DC. For each edge directed to an NC replica on such a
-        DC from an NC replica on a DC in another site, the KCC creates an
-        nTDSConnection object to imply that edge if one does not already
-        exist.
-
-        Modifies self.kept_connections - A set of nTDSConnection
-        objects for edges that are directed
-        to the local DC's site in one or more NC replica graphs.
-
-        returns: True if spanning trees were created for all NC replica
-            graphs, otherwise False.
-        """
-        all_connected = True
-        self.kept_connections = set()
-
-        # LET crossRefList be the set containing each object o of class
-        # crossRef such that o is a child of the CN=Partitions child of the
-        # config NC
-
-        # FOR each crossRef object cr in crossRefList
-        #    IF cr!enabled has a value and is false, or if FLAG_CR_NTDS_NC
-        #        is clear in cr!systemFlags, skip cr.
-        #    LET g be the GRAPH return of SetupGraph()
-
-        for part in self.part_table.values():
-
-            if not part.is_enabled():
-                continue
-
-            if part.is_foreign():
-                continue
-
-            graph = self.setup_graph(part)
 
-            # Create nTDSConnection objects, routing replication traffic
-            # around "failed" DCs.
-            found_failed = False
+from samba import getopt as options
 
-            connected, found_failed = self.create_connections(graph,
-                                                              part, True)
+from samba.kcc.graph_utils import verify_and_dot, list_verify_tests
+from samba.kcc.graph_utils import GraphError
 
-            DEBUG("with detect_failed: connected %s Found failed %s" %
-                  (connected, found_failed))
-            if not connected:
-                all_connected = False
-
-                if found_failed:
-                    # One or more failed DCs preclude use of the ideal NC
-                    # replica graph. Add connections for the ideal graph.
-                    self.create_connections(graph, part, False)
-
-        return all_connected
-
-
-    def intersite(self):
-        """The head method for generating the inter-site KCC replica
-        connection graph and attendant nTDSConnection objects
-        in the samdb.
-
-        Produces self.kept_connections set of NTDS Connections
-        that should be kept during subsequent pruning process.
-
-        ::return (True or False):  (True) if the produced NC replica
-            graph connects all sites that need to be connected
-        """
-
-        # Retrieve my DSA
-        mydsa = self.my_dsa
-        mysite = self.my_site
-        all_connected = True
-
-        logger.debug("intersite(): enter")
-
-        # Determine who is the ISTG
-        if opts.readonly:
-            mysite.select_istg(self.samdb, mydsa, ro=True)
-        else:
-            mysite.select_istg(self.samdb, mydsa, ro=False)
-
-        # Test whether local site has topology disabled
-        if mysite.is_intersite_topology_disabled():
-            logger.debug("intersite(): exit disabled all_connected=%d" %
-                         all_connected)
-            return all_connected
-
-        if not mydsa.is_istg():
-            logger.debug("intersite(): exit not istg all_connected=%d" %
-                         all_connected)
-            return all_connected
-
-        self.merge_failed_links()
-
-        # For each NC with an NC replica that "should be present" on the
-        # local DC or "is present" on any DC in the same site as the
-        # local DC, the KCC constructs a site graph--a precursor to an NC
-        # replica graph. The site connectivity for a site graph is defined
-        # by objects of class interSiteTransport, siteLink, and
-        # siteLinkBridge in the config NC.
-
-        all_connected = self.create_intersite_connections()
-
-        logger.debug("intersite(): exit all_connected=%d" % all_connected)
-        return all_connected
-
-    def update_rodc_connection(self):
-        """Runs when the local DC is an RODC and updates the RODC NTFRS
-        connection object.
-        """
-        # Given an nTDSConnection object cn1, such that cn1.options contains
-        # NTDSCONN_OPT_RODC_TOPOLOGY, and another nTDSConnection object cn2,
-        # does not contain NTDSCONN_OPT_RODC_TOPOLOGY, modify cn1 to ensure
-        # that the following is true:
-        #
-        #     cn1.fromServer = cn2.fromServer
-        #     cn1.schedule = cn2.schedule
-        #
-        # If no such cn2 can be found, cn1 is not modified.
-        # If no such cn1 can be found, nothing is modified by this task.
-
-        if not self.my_dsa.is_ro():
-            return
-
-        all_connections = self.my_dsa.connect_table.values()
-        ro_connections = [x for x in all_connections if x.is_rodc_topology()]
-        rw_connections = [x for x in all_connections
-                          if x not in ro_connections]
-
-        # XXX here we are dealing with multiple RODC_TOPO connections,
-        # if they exist. It is not clear whether the spec means that
-        # or if it ever arises.
-        if rw_connections and ro_connections:
-            for con in ro_connections:
-                cn2 = rw_connections[0]
-                con.from_dnstr = cn2.from_dnstr
-                con.schedule = cn2.schedule
-                con.to_be_modified = True
-
-            self.my_dsa.commit_connections(self.samdb, ro=opts.readonly)
-
-    def intrasite_max_node_edges(self, node_count):
-        """Returns the maximum number of edges directed to a node in
-        the intrasite replica graph.
-
-        The KCC does not create more
-        than 50 edges directed to a single DC. To optimize replication,
-        we compute that each node should have n+2 total edges directed
-        to it such that (n) is the smallest non-negative integer
-        satisfying (node_count <= 2*(n*n) + 6*n + 7)
-
-        (If the number of edges is m (i.e. n + 2), that is the same as
-        2 * m*m - 2 * m + 3).
-
-        edges  n   nodecount
-          2    0    7
-          3    1   15
-          4    2   27
-          5    3   43
-                  ...
-         50   48 4903
-
-        :param node_count: total number of nodes in the replica graph
-        """
-        n = 0
-        while True:
-            if node_count <= (2 * (n * n) + (6 * n) + 7):
-                break
-            n = n + 1
-        n = n + 2
-        if n < 50:
-            return n
-        return 50
-
-    def construct_intrasite_graph(self, site_local, dc_local,
-                                  nc_x, gc_only, detect_stale):
-        # [MS-ADTS] 6.2.2.2
-        # We're using the MS notation names here to allow
-        # correlation back to the published algorithm.
-        #
-        # nc_x     - naming context (x) that we are testing if it
-        #            "should be present" on the local DC
-        # f_of_x   - replica (f) found on a DC (s) for NC (x)
-        # dc_s     - DC where f_of_x replica was found
-        # dc_local - local DC that potentially needs a replica
-        #            (f_of_x)
-        # r_list   - replica list R
-        # p_of_x   - replica (p) is partial and found on a DC (s)
-        #            for NC (x)
-        # l_of_x   - replica (l) is the local replica for NC (x)
-        #            that should appear on the local DC
-        # r_len = is length of replica list |R|
-        #
-        # If the DSA doesn't need a replica for this
-        # partition (NC x) then continue
-        needed, ro, partial = nc_x.should_be_present(dc_local)
-
-        DEBUG_YELLOW("construct_intrasite_graph(): enter" +
-                     "\n\tgc_only=%d" % gc_only +
-                     "\n\tdetect_stale=%d" % detect_stale +
-                     "\n\tneeded=%s" % needed +
-                     "\n\tro=%s" % ro +
-                     "\n\tpartial=%s" % partial +
-                     "\n%s" % nc_x)
-
-        if not needed:
-            DEBUG_RED("%s lacks 'should be present' status, "
-                      "aborting construct_intersite_graph!" %
-                      nc_x.nc_dnstr)
-            return
-
-        # Create a NCReplica that matches what the local replica
-        # should say.  We'll use this below in our r_list
-        l_of_x = NCReplica(dc_local.dsa_dnstr, dc_local.dsa_guid,
-                           nc_x.nc_dnstr)
-
-        l_of_x.identify_by_basedn(self.samdb)
-
-        l_of_x.rep_partial = partial
-        l_of_x.rep_ro = ro
-
-        # Add this replica that "should be present" to the
-        # needed replica table for this DSA
-        dc_local.add_needed_replica(l_of_x)
-
-        # Replica list
-        #
-        # Let R be a sequence containing each writable replica f of x
-        # such that f "is present" on a DC s satisfying the following
-        # criteria:
-        #
-        #  * s is a writable DC other than the local DC.
-        #
-        #  * s is in the same site as the local DC.
-        #
-        #  * If x is a read-only full replica and x is a domain NC,
-        #    then the DC's functional level is at least
-        #    DS_BEHAVIOR_WIN2008.
-        #
-        #  * Bit NTDSSETTINGS_OPT_IS_TOPL_DETECT_STALE_DISABLED is set
-        #    in the options attribute of the site settings object for
-        #    the local DC's site, or no tuple z exists in the
-        #    kCCFailedLinks or kCCFailedConnections variables such
-        #    that z.UUIDDsa is the objectGUID of the nTDSDSA object
-        #    for s, z.FailureCount > 0, and the current time -
-        #    z.TimeFirstFailure > 2 hours.
-
-        r_list = []
-
-        # We'll loop thru all the DSAs looking for
-        # writeable NC replicas that match the naming
-        # context dn for (nc_x)
-        #
-        for dc_s in self.my_site.dsa_table.values():
-            # If this partition (nc_x) doesn't appear as a
-            # replica (f_of_x) on (dc_s) then continue
-            if not nc_x.nc_dnstr in dc_s.current_rep_table:
-                continue
-
-            # Pull out the NCReplica (f) of (x) with the dn
-            # that matches NC (x) we are examining.
-            f_of_x = dc_s.current_rep_table[nc_x.nc_dnstr]
-
-            # Replica (f) of NC (x) must be writable
-            if f_of_x.is_ro():
-                continue
-
-            # Replica (f) of NC (x) must satisfy the
-            # "is present" criteria for DC (s) that
-            # it was found on
-            if not f_of_x.is_present():
-                continue
-
-            # DC (s) must be a writable DSA other than
-            # my local DC.  In other words we'd only replicate
-            # from other writable DC
-            if dc_s.is_ro() or dc_s is dc_local:
-                continue
-
-            # Certain replica graphs are produced only
-            # for global catalogs, so test against
-            # method input parameter
-            if gc_only and not dc_s.is_gc():
-                continue
-
-            # DC (s) must be in the same site as the local DC
-            # as this is the intra-site algorithm. This is
-            # handled by virtue of placing DSAs in per
-            # site objects (see enclosing for() loop)
-
-            # If NC (x) is intended to be read-only full replica
-            # for a domain NC on the target DC then the source
-            # DC should have functional level at minimum WIN2008
-            #
-            # Effectively we're saying that in order to replicate
-            # to a targeted RODC (which was introduced in Windows 2008)
-            # then we have to replicate from a DC that is also minimally
-            # at that level.
-            #
-            # You can also see this requirement in the MS special
-            # considerations for RODC which state that to deploy
-            # an RODC, at least one writable domain controller in
-            # the domain must be running Windows Server 2008
-            if ro and not partial and nc_x.nc_type == NCType.domain:
-                if not dc_s.is_minimum_behavior(dsdb.DS_DOMAIN_FUNCTION_2008):
-                    continue
-
-            # If we haven't been told to turn off stale connection
-            # detection and this dsa has a stale connection then
-            # continue
-            if detect_stale and self.is_stale_link_connection(dc_s):
-                continue
-
-            # Replica meets criteria.  Add it to table indexed
-            # by the GUID of the DC that it appears on
-            r_list.append(f_of_x)
-
-        # If a partial (not full) replica of NC (x) "should be present"
-        # on the local DC, append to R each partial replica (p of x)
-        # such that p "is present" on a DC satisfying the same
-        # criteria defined above for full replica DCs.
-        #
-        # XXX This loop and the previous one differ only in whether
-        # the replica is partial or not. here we only accept partial
-        # (because we're partial); before we only accepted full. Order
-        # doen't matter (the list is sorted a few lines down) so these
-        # loops could easily be merged. Or this could be a helper
-        # function.
-
-        if partial:
-            # Now we loop thru all the DSAs looking for
-            # partial NC replicas that match the naming
-            # context dn for (NC x)
-            for dc_s in self.my_site.dsa_table.values():
-
-                # If this partition NC (x) doesn't appear as a
-                # replica (p) of NC (x) on the dsa DC (s) then
-                # continue
-                if not nc_x.nc_dnstr in dc_s.current_rep_table:
-                    continue
-
-                # Pull out the NCReplica with the dn that
-                # matches NC (x) we are examining.
-                p_of_x = dc_s.current_rep_table[nc_x.nc_dnstr]
-
-                # Replica (p) of NC (x) must be partial
-                if not p_of_x.is_partial():
-                    continue
-
-                # Replica (p) of NC (x) must satisfy the
-                # "is present" criteria for DC (s) that
-                # it was found on
-                if not p_of_x.is_present():
-                    continue
-
-                # DC (s) must be a writable DSA other than
-                # my DSA.  In other words we'd only replicate
-                # from other writable DSA
-                if dc_s.is_ro() or dc_s is dc_local:
-                    continue
-
-                # Certain replica graphs are produced only
-                # for global catalogs, so test against
-                # method input parameter
-                if gc_only and not dc_s.is_gc():
-                    continue
-
-                # If we haven't been told to turn off stale connection
-                # detection and this dsa has a stale connection then
-                # continue
-                if detect_stale and self.is_stale_link_connection(dc_s):
-                    continue
-
-                # Replica meets criteria.  Add it to table indexed
-                # by the GUID of the DSA that it appears on
-                r_list.append(p_of_x)
-
-        # Append to R the NC replica that "should be present"
-        # on the local DC
-        r_list.append(l_of_x)
-
-        r_list.sort(sort_replica_by_dsa_guid)
-        r_len = len(r_list)
-
-        max_node_edges = self.intrasite_max_node_edges(r_len)
-
-        # Add a node for each r_list element to the replica graph
-        graph_list = []
-        for rep in r_list:
-            node = GraphNode(rep.rep_dsa_dnstr, max_node_edges)
-            graph_list.append(node)
-
-        # For each r(i) from (0 <= i < |R|-1)
-        i = 0
-        while i < (r_len-1):
-            # Add an edge from r(i) to r(i+1) if r(i) is a full
-            # replica or r(i+1) is a partial replica
-            if not r_list[i].is_partial() or r_list[i+1].is_partial():
-                graph_list[i+1].add_edge_from(r_list[i].rep_dsa_dnstr)
-
-            # Add an edge from r(i+1) to r(i) if r(i+1) is a full
-            # replica or ri is a partial replica.
-            if not r_list[i+1].is_partial() or r_list[i].is_partial():
-                graph_list[i].add_edge_from(r_list[i+1].rep_dsa_dnstr)
-            i = i + 1
-
-        # Add an edge from r|R|-1 to r0 if r|R|-1 is a full replica
-        # or r0 is a partial replica.
-        if not r_list[r_len-1].is_partial() or r_list[0].is_partial():
-            graph_list[0].add_edge_from(r_list[r_len-1].rep_dsa_dnstr)
-
-        # Add an edge from r0 to r|R|-1 if r0 is a full replica or
-        # r|R|-1 is a partial replica.
-        if not r_list[0].is_partial() or r_list[r_len-1].is_partial():
-            graph_list[r_len-1].add_edge_from(r_list[0].rep_dsa_dnstr)
-
-        DEBUG("r_list is length %s" % len(r_list))
-        DEBUG('\n'.join(str((x.rep_dsa_guid, x.rep_dsa_dnstr))
-                        for x in r_list))
-
-        do_dot_files = opts.dot_files and opts.debug
-        if opts.verify or do_dot_files:
-            dot_edges = []
-            dot_vertices = set()
-            for v1 in graph_list:
-                dot_vertices.add(v1.dsa_dnstr)
-                for v2 in v1.edge_from:
-                    dot_edges.append((v2, v1.dsa_dnstr))
-                    dot_vertices.add(v2)
-
-            verify_properties = ('connected', 'directed_double_ring')
-            verify_and_dot('intrasite_pre_ntdscon', dot_edges, dot_vertices,
-                           label='%s__%s__%s' % (site_local.site_dnstr,
-                                                 nctype_lut[nc_x.nc_type],
-                                                 nc_x.nc_dnstr),
-                           properties=verify_properties, debug=DEBUG,
-                           verify=opts.verify,
-                           dot_files=do_dot_files, directed=True)
-
-        # For each existing nTDSConnection object implying an edge
-        # from rj of R to ri such that j != i, an edge from rj to ri
-        # is not already in the graph, and the total edges directed
-        # to ri is less than n+2, the KCC adds that edge to the graph.
-        for vertex in graph_list:
-            dsa = self.my_site.dsa_table[vertex.dsa_dnstr]
-            for connect in dsa.connect_table.values():
-                remote = connect.from_dnstr
-                if remote in self.my_site.dsa_table:
-                    vertex.add_edge_from(remote)
-
-        DEBUG('reps are:  %s' % '   '.join(x.rep_dsa_dnstr for x in r_list))
-        DEBUG('dsas are:  %s' % '   '.join(x.dsa_dnstr for x in graph_list))
-
-        for tnode in graph_list:
-            # To optimize replication latency in sites with many NC
-            # replicas, the KCC adds new edges directed to ri to bring
-            # the total edges to n+2, where the NC replica rk of R
-            # from which the edge is directed is chosen at random such
-            # that k != i and an edge from rk to ri is not already in
-            # the graph.
-            #
-            # Note that the KCC tech ref does not give a number for
-            # the definition of "sites with many NC replicas". At a
-            # bare minimum to satisfy n+2 edges directed at a node we
-            # have to have at least three replicas in |R| (i.e. if n
-            # is zero then at least replicas from two other graph
-            # nodes may direct edges to us).
-            if r_len >= 3 and not tnode.has_sufficient_edges():
-                candidates = [x for x in graph_list if
-                              (x is not tnode and
-                               x.dsa_dnstr not in tnode.edge_from)]
-
-                DEBUG_BLUE("looking for random link for %s. r_len %d, "
-                           "graph len %d candidates %d"
-                           % (tnode.dsa_dnstr, r_len, len(graph_list),
-                              len(candidates)))
-
-                DEBUG("candidates %s" % [x.dsa_dnstr for x in candidates])
-
-                while candidates and not tnode.has_sufficient_edges():
-                    other = random.choice(candidates)
-                    DEBUG("trying to add candidate %s" % other.dsa_dstr)
-                    if not tnode.add_edge_from(other):
-                        DEBUG_RED("could not add %s" % other.dsa_dstr)
-                    candidates.remove(other)
-            else:
-                DEBUG_CYAN("not adding links to %s: nodes %s, links is %s/%s" %
-                           (tnode.dsa_dnstr, r_len, len(tnode.edge_from),
-                            tnode.max_edges))
-
-            # Print the graph node in debug mode
-            logger.debug("%s" % tnode)
-
-            # For each edge directed to the local DC, ensure a nTDSConnection
-            # points to us that satisfies the KCC criteria
-
-            if tnode.dsa_dnstr == dc_local.dsa_dnstr:
-                tnode.add_connections_from_edges(dc_local)
-
-        if opts.verify or do_dot_files:
-            dot_edges = []
-            dot_vertices = set()
-            for v1 in graph_list:
-                dot_vertices.add(v1.dsa_dnstr)
-                for v2 in v1.edge_from:
-                    dot_edges.append((v2, v1.dsa_dnstr))
-                    dot_vertices.add(v2)
-
-            verify_properties = ('connected', 'directed_double_ring_or_small')
-            verify_and_dot('intrasite_post_ntdscon', dot_edges, dot_vertices,
-                           label='%s__%s__%s' % (site_local.site_dnstr,
-                                                 nctype_lut[nc_x.nc_type],
-                                                 nc_x.nc_dnstr),
-                           properties=verify_properties, debug=DEBUG,
-                           verify=opts.verify,
-                           dot_files=do_dot_files, directed=True)
-
-    def intrasite(self):
-        """The head method for generating the intra-site KCC replica
-        connection graph and attendant nTDSConnection objects
-        in the samdb
-        """
-        # Retrieve my DSA
-        mydsa = self.my_dsa
-
-        logger.debug("intrasite(): enter")
-
-        # Test whether local site has topology disabled
-        mysite = self.my_site
-        if mysite.is_intrasite_topology_disabled():
-            return
-
-        detect_stale = (not mysite.is_detect_stale_disabled())
-        for connect in mydsa.connect_table.values():
-            if connect.to_be_added:
-                DEBUG_CYAN("TO BE ADDED:\n%s" % connect)
-
-        # Loop thru all the partitions, with gc_only False
-        for partdn, part in self.part_table.items():
-            self.construct_intrasite_graph(mysite, mydsa, part, False,
-                                           detect_stale)
-            for connect in mydsa.connect_table.values():
-                if connect.to_be_added:
-                    DEBUG_BLUE("TO BE ADDED:\n%s" % connect)
-
-        # If the DC is a GC server, the KCC constructs an additional NC
-        # replica graph (and creates nTDSConnection objects) for the
-        # config NC as above, except that only NC replicas that "are present"
-        # on GC servers are added to R.
-        for connect in mydsa.connect_table.values():
-            if connect.to_be_added:
-                DEBUG_YELLOW("TO BE ADDED:\n%s" % connect)
-
-        # Do it again, with gc_only True
-        for partdn, part in self.part_table.items():
-            if part.is_config():
-                self.construct_intrasite_graph(mysite, mydsa, part, True,
-                                               detect_stale)
-
-        # The DC repeats the NC replica graph computation and nTDSConnection
-        # creation for each of the NC replica graphs, this time assuming
-        # that no DC has failed. It does so by re-executing the steps as
-        # if the bit NTDSSETTINGS_OPT_IS_TOPL_DETECT_STALE_DISABLED were
-        # set in the options attribute of the site settings object for
-        # the local DC's site.  (ie. we set "detec_stale" flag to False)
-        for connect in mydsa.connect_table.values():
-            if connect.to_be_added:
-                DEBUG_BLUE("TO BE ADDED:\n%s" % connect)
-
-        # Loop thru all the partitions.
-        for partdn, part in self.part_table.items():
-            self.construct_intrasite_graph(mysite, mydsa, part, False,
-                                           False)  # don't detect stale
-
-        # If the DC is a GC server, the KCC constructs an additional NC
-        # replica graph (and creates nTDSConnection objects) for the
-        # config NC as above, except that only NC replicas that "are present"
-        # on GC servers are added to R.
-        for connect in mydsa.connect_table.values():
-            if connect.to_be_added:
-                DEBUG_RED("TO BE ADDED:\n%s" % connect)
-
-        for partdn, part in self.part_table.items():
-            if part.is_config():
-                self.construct_intrasite_graph(mysite, mydsa, part, True,
-                                               False)  # don't detect stale
-
-        if opts.readonly:
-            # Display any to be added or modified repsFrom
-            for connect in mydsa.connect_table.values():
-                if connect.to_be_deleted:
-                    logger.info("TO BE DELETED:\n%s" % connect)
-                if connect.to_be_modified:
-                    logger.info("TO BE MODIFIED:\n%s" % connect)
-                if connect.to_be_added:
-                    DEBUG_GREEN("TO BE ADDED:\n%s" % connect)
-
-            mydsa.commit_connections(self.samdb, ro=True)
-        else:
-            # Commit any newly created connections to the samdb
-            mydsa.commit_connections(self.samdb)
-
-    def list_dsas(self):
-        self.load_my_site()
-        self.load_my_dsa()
-
-        self.load_all_sites()
-        self.load_all_partitions()
-        self.load_all_transports()
-        self.load_all_sitelinks()
-        dsas = []
-        for site in self.site_table.values():
-            dsas.extend([dsa.dsa_dnstr.replace('CN=NTDS Settings,', '', 1)
-                         for dsa in site.dsa_table.values()])
-        return dsas
-
-    def load_samdb(self, dburl, lp, creds):
-        self.samdb = SamDB(url=dburl,
-                           session_info=system_session(),
-                           credentials=creds, lp=lp)
-
-    def plot_all_connections(self, basename, verify_properties=()):
-        verify = verify_properties and opts.verify
-        plot = opts.dot_files
-        if not (verify or plot):
-            return
-
-        dot_edges = []
-        dot_vertices = []
-        edge_colours = []
-        vertex_colours = []
-
-        for dsa in self.dsa_by_dnstr.values():
-            dot_vertices.append(dsa.dsa_dnstr)
-            if dsa.is_ro():
-                vertex_colours.append('#cc0000')
-            else:
-                vertex_colours.append('#0000cc')
-            for con in dsa.connect_table.values():
-                if con.is_rodc_topology():
-                    edge_colours.append('red')
-                else:
-                    edge_colours.append('blue')
-                dot_edges.append((con.from_dnstr, dsa.dsa_dnstr))
-
-        verify_and_dot(basename, dot_edges, vertices=dot_vertices,
-                       label=self.my_dsa_dnstr, properties=verify_properties,
-                       debug=DEBUG, verify=verify, dot_files=plot,
-                       directed=True, edge_colors=edge_colours,
-                       vertex_colors=vertex_colours)
-
-    def run(self, dburl, lp, creds, forced_local_dsa=None,
-            forget_local_links=False, forget_intersite_links=False):
-        """Method to perform a complete run of the KCC and
-        produce an updated topology for subsequent NC replica
-        syncronization between domain controllers
-        """
-        # We may already have a samdb setup if we are
-        # currently importing an ldif for a test run
-        if self.samdb is None:
-            try:
-                self.load_samdb(dburl, lp, creds)
-            except ldb.LdbError, (num, msg):
-                logger.error("Unable to open sam database %s : %s" %
-                             (dburl, msg))
-                return 1
+import logging
+from samba.kcc.debug import logger, DEBUG, DEBUG_FN
+from samba.kcc import KCC
 
-        if forced_local_dsa:
-            self.samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" %
-                                            forced_local_dsa)
+# If DEFAULT_RNG_SEED is None, /dev/urandom or system time is used.
+DEFAULT_RNG_SEED = None
 
-        try:
-            # Setup
-            self.load_my_site()
-            self.load_my_dsa()
-
-            self.load_all_sites()
-            self.load_all_partitions()
-            self.load_all_transports()
-            self.load_all_sitelinks()
-
-            if opts.verify or opts.dot_files:
-                guid_to_dnstr = {}
-                for site in self.site_table.values():
-                    guid_to_dnstr.update((str(dsa.dsa_guid), dnstr)
-                                         for dnstr, dsa
-                                         in site.dsa_table.items())
-
-                self.plot_all_connections('dsa_initial')
-
-                dot_edges = []
-                current_reps, needed_reps = self.my_dsa.get_rep_tables()
-                for dnstr, c_rep in current_reps.items():
-                    DEBUG("c_rep %s" % c_rep)
-                    dot_edges.append((self.my_dsa.dsa_dnstr, dnstr))
-
-                verify_and_dot('dsa_repsFrom_initial', dot_edges,
-                               directed=True, label=self.my_dsa_dnstr,
-                               properties=(), debug=DEBUG, verify=opts.verify,
-                               dot_files=opts.dot_files)
-
-                dot_edges = []
-                for site in self.site_table.values():
-                    for dsa in site.dsa_table.values():
-                        current_reps, needed_reps = dsa.get_rep_tables()
-                        for dn_str, rep in current_reps.items():
-                            for reps_from in rep.rep_repsFrom:
-                                DEBUG("rep %s" % rep)
-                                dsa_guid = str(reps_from.source_dsa_obj_guid)
-                                dsa_dn = guid_to_dnstr[dsa_guid]
-                                dot_edges.append((dsa.dsa_dnstr, dsa_dn))
-
-                verify_and_dot('dsa_repsFrom_initial_all', dot_edges,
-                               directed=True, label=self.my_dsa_dnstr,
-                               properties=(), debug=DEBUG, verify=opts.verify,
-                               dot_files=opts.dot_files)
-
-                dot_edges = []
-                for link in self.sitelink_table.values():
-                    for a, b in itertools.combinations(link.site_list, 2):
-                        dot_edges.append((str(a), str(b)))
-                properties = ('connected',)
-                verify_and_dot('dsa_sitelink_initial', dot_edges,
-                               directed=False,
-                               label=self.my_dsa_dnstr, properties=properties,
-                               debug=DEBUG, verify=opts.verify,
-                               dot_files=opts.dot_files)
-
-            if forget_local_links:
-                for dsa in self.my_site.dsa_table.values():
-                    dsa.connect_table = {k:v for k, v in dsa.connect_table.items()
-                                         if v.is_rodc_topology()}
-                self.plot_all_connections('dsa_forgotten_local')
-
-            if forget_intersite_links:
-                for site in self.site_table.values():
-                    for dsa in site.dsa_table.values():
-                        dsa.connect_table = {k:v for k, v in dsa.connect_table.items()
-                                             if site is self.my_site and v.is_rodc_topology()}
-
-                self.plot_all_connections('dsa_forgotten_all')
-            # These are the published steps (in order) for the
-            # MS-TECH description of the KCC algorithm ([MS-ADTS] 6.2.2)
-
-            # Step 1
-            self.refresh_failed_links_connections()
-
-            # Step 2
-            self.intrasite()
-
-            # Step 3
-            all_connected = self.intersite()
-
-            # Step 4
-            self.remove_unneeded_ntdsconn(all_connected)
-
-            # Step 5
-            self.translate_ntdsconn()
-
-            # Step 6
-            self.remove_unneeded_failed_links_connections()
-
-            # Step 7
-            self.update_rodc_connection()
-
-            if opts.verify or opts.dot_files:
-                self.plot_all_connections('dsa_final',
-                                          ('connected', 'forest_of_rings'))
-
-                DEBUG_MAGENTA("there are %d dsa guids" % len(guid_to_dnstr))
-
-                dot_edges = []
-                edge_colors = []
-                my_dnstr = self.my_dsa.dsa_dnstr
-                current_reps, needed_reps = self.my_dsa.get_rep_tables()
-                for dnstr, n_rep in needed_reps.items():
-                    for reps_from in n_rep.rep_repsFrom:
-                        guid_str = str(reps_from.source_dsa_obj_guid)
-                        dot_edges.append((my_dnstr, guid_to_dnstr[guid_str]))
-                        edge_colors.append('#' + str(n_rep.nc_guid)[:6])
-
-                verify_and_dot('dsa_repsFrom_final', dot_edges, directed=True,
-                               label=self.my_dsa_dnstr,
-                               properties=(), debug=DEBUG, verify=opts.verify,
-                               dot_files=opts.dot_files,
-                               edge_colors=edge_colors)
-
-                dot_edges = []
-
-                for site in self.site_table.values():
-                    for dsa in site.dsa_table.values():
-                        current_reps, needed_reps = dsa.get_rep_tables()
-                        for n_rep in needed_reps.values():
-                            for reps_from in n_rep.rep_repsFrom:
-                                dsa_guid = str(reps_from.source_dsa_obj_guid)
-                                dsa_dn = guid_to_dnstr[dsa_guid]
-                                dot_edges.append((dsa.dsa_dnstr, dsa_dn))
-
-                verify_and_dot('dsa_repsFrom_final_all', dot_edges,
-                               directed=True, label=self.my_dsa_dnstr,
-                               properties=(), debug=DEBUG, verify=opts.verify,
-                               dot_files=opts.dot_files)
-
-        except:
-            raise
-
-        return 0
-
-    def import_ldif(self, dburl, lp, creds, ldif_file):
-        """Import all objects and attributes that are relevent
-        to the KCC algorithms from a previously exported LDIF file.
-
-        The point of this function is to allow a programmer/debugger to
-        import an LDIF file with non-security relevent information that
-        was previously extracted from a DC database.  The LDIF file is used
-        to create a temporary abbreviated database.  The KCC algorithm can
-        then run against this abbreviated database for debug or test
-        verification that the topology generated is computationally the
-        same between different OSes and algorithms.
-
-        :param dburl: path to the temporary abbreviated db to create
-        :param ldif_file: path to the ldif file to import
-        """
-        try:
-            self.samdb = ldif_utils.ldif_to_samdb(dburl, lp, creds, ldif_file,
-                                                  opts.forced_local_dsa)
-        except ldif_utils.LdifError, e:
-            print e
-            return 1
-        return 0
-
-    def export_ldif(self, dburl, lp, creds, ldif_file):
-        """Routine to extract all objects and attributes that are relevent
-        to the KCC algorithms from a DC database.
-
-        The point of this function is to allow a programmer/debugger to
-        extract an LDIF file with non-security relevent information from
-        a DC database.  The LDIF file can then be used to "import" via
-        the import_ldif() function this file into a temporary abbreviated
-        database.  The KCC algorithm can then run against this abbreviated
-        database for debug or test verification that the topology generated
-        is computationally the same between different OSes and algorithms.
-
-        :param dburl: LDAP database URL to extract info from
-        :param ldif_file: output LDIF file name to create
-        """
-        try:
-            ldif_utils.samdb_to_ldif_file(self.samdb, dburl, lp, creds,
-                                          ldif_file)
-        except ldif_utils.LdifError, e:
-            print e
-            return 1
-        return 0
 
-##################################################
-# Global Functions
-##################################################
+def test_all_reps_from(kcc, dburl, lp, creds, unix_now, rng_seed=None,
+                       ldif_file=None):
+    """Run the KCC from all DSAs in read-only mode
 
+    The behaviour depends on the global opts variable which contains
+    command line variables. Usually you will want to run it with
+    opt.dot_file_dir set (via --dot-file-dir) to see the graphs that
+    would be created from each DC.
 
-def get_spanning_tree_edges(graph, my_site, label=None):
-    # Phase 1: Run Dijkstra's to get a list of internal edges, which are
-    # just the shortest-paths connecting colored vertices
-
-    internal_edges = set()
-
-    for e_set in graph.edge_set:
-        edgeType = None
-        for v in graph.vertices:
-            v.edges = []
-
-        # All con_type in an edge set is the same
-        for e in e_set.edges:
-            edgeType = e.con_type
-            for v in e.vertices:
-                v.edges.append(e)
-
-        if opts.verify or opts.dot_files:
-            graph_edges = [(a.site.site_dnstr, b.site.site_dnstr)
-                           for a, b in
-                           itertools.chain(
-                               *(itertools.combinations(edge.vertices, 2)
-                                 for edge in e_set.edges))]
-            graph_nodes = [v.site.site_dnstr for v in graph.vertices]
-
-            if opts.dot_files and opts.debug:
-                write_dot_file('edgeset_%s' % (edgeType,), graph_edges,
-                               vertices=graph_nodes, label=label)
-
-            if opts.verify:
-                verify_graph('spanning tree edge set %s' % edgeType,
-                             graph_edges, vertices=graph_nodes,
-                             properties=('complete', 'connected'),
-                             debug=DEBUG)
-
-        # Run dijkstra's algorithm with just the red vertices as seeds
-        # Seed from the full replicas
-        dijkstra(graph, edgeType, False)
-
-        # Process edge set
-        process_edge_set(graph, e_set, internal_edges)
-
-        # Run dijkstra's algorithm with red and black vertices as the seeds
-        # Seed from both full and partial replicas
-        dijkstra(graph, edgeType, True)
-
-        # Process edge set
-        process_edge_set(graph, e_set, internal_edges)
-
-    # All vertices have root/component as itself
-    setup_vertices(graph)
-    process_edge_set(graph, None, internal_edges)
-
-    if opts.verify or opts.dot_files:
-        graph_edges = [(e.v1.site.site_dnstr, e.v2.site.site_dnstr)
-                       for e in internal_edges]
-        graph_nodes = [v.site.site_dnstr for v in graph.vertices]
-        verify_properties = ('multi_edge_forest',)
-        verify_and_dot('prekruskal', graph_edges, graph_nodes, label=label,
-                       properties=verify_properties, debug=DEBUG,
-                       verify=opts.verify,
-                       dot_files=opts.dot_files)
-
-    # Phase 2: Run Kruskal's on the internal edges
-    output_edges, components = kruskal(graph, internal_edges)
-
-    # This recalculates the cost for the path connecting the
-    # closest red vertex. Ignoring types is fine because NO
-    # suboptimal edge should exist in the graph
-    dijkstra(graph, "EDGE_TYPE_ALL", False)  # TODO rename
-    # Phase 3: Process the output
-    for v in graph.vertices:
-        if v.is_red():
-            v.dist_to_red = 0
-        else:
-            v.dist_to_red = v.repl_info.cost
-
-    if opts.verify or opts.dot_files:
-        graph_edges = [(e.v1.site.site_dnstr, e.v2.site.site_dnstr)
-                       for e in internal_edges]
-        graph_nodes = [v.site.site_dnstr for v in graph.vertices]
-        verify_properties = ('multi_edge_forest',)
-        verify_and_dot('postkruskal', graph_edges, graph_nodes,
-                       label=label, properties=verify_properties,
-                       debug=DEBUG, verify=opts.verify,
-                       dot_files=opts.dot_files)
-
-    # Ensure only one-way connections for partial-replicas,
-    # and make sure they point the right way.
-    edge_list = []
-    for edge in output_edges:
-        # We know these edges only have two endpoints because we made
-        # them.
-        v, w = edge.vertices
-        if v.site is my_site or w.site is my_site:
-            if (((v.is_black() or w.is_black()) and
-                 v.dist_to_red != MAX_DWORD)):
-                edge.directed = True
-
-                if w.dist_to_red < v.dist_to_red:
-                    edge.vertices[:] = w, v
-            edge_list.append(edge)
-
-    if opts.verify or opts.dot_files:
-        graph_edges = [[x.site.site_dnstr for x in e.vertices]
-                       for e in edge_list]
-        #add the reverse edge if not directed.
-        graph_edges.extend([x.site.site_dnstr
-                            for x in reversed(e.vertices)]
-                           for e in edge_list if not e.directed)
-        graph_nodes = [v.site.site_dnstr for v in graph.vertices]
-        verify_properties = ()
-        verify_and_dot('post-one-way-partial', graph_edges, graph_nodes,
-                       label=label, properties=verify_properties,
-                       debug=DEBUG, verify=opts.verify,
-                       directed=True,
-                       dot_files=opts.dot_files)
-
-    # count the components
-    return edge_list, components
-
-
-def sort_replica_by_dsa_guid(rep1, rep2):
-    return cmp(ndr_pack(rep1.rep_dsa_guid), ndr_pack(rep2.rep_dsa_guid))
-
-
-def sort_dsa_by_gc_and_guid(dsa1, dsa2):
-    if dsa1.is_gc() and not dsa2.is_gc():
-        return -1
-    if not dsa1.is_gc() and dsa2.is_gc():
-        return +1
-    return cmp(ndr_pack(dsa1.dsa_guid), ndr_pack(dsa2.dsa_guid))
-
-
-def is_smtp_replication_available():
-    """Currently always returns false because Samba
-    doesn't implement SMTP transfer for NC changes
-    between DCs
+    :param lp: a loadparm object.
+    :param creds: a Credentials object.
+    :param unix_now: the unix epoch time as an integer
+    :param rng_seed: a seed for the random number generator
+    :return None:
     """
-    return False
-
-
-def create_edge(con_type, site_link, guid_to_vertex):
-    e = MultiEdge()
-    e.site_link = site_link
-    e.vertices = []
-    for site_guid in site_link.site_list:
-        if str(site_guid) in guid_to_vertex:
-            e.vertices.extend(guid_to_vertex.get(str(site_guid)))
-    e.repl_info.cost = site_link.cost
-    e.repl_info.options = site_link.options
-    e.repl_info.interval = site_link.interval
-    e.repl_info.schedule = convert_schedule_to_repltimes(site_link.schedule)
-    e.con_type = con_type
-    e.directed = False
-    return e
-
-
-def create_auto_edge_set(graph, transport):
-    e_set = MultiEdgeSet()
-    # use a NULL guid, not associated with a SiteLinkBridge object
-    e_set.guid = misc.GUID()
-    for site_link in graph.edges:
-        if site_link.con_type == transport:
-            e_set.edges.append(site_link)
-
-    return e_set
-
-
-def create_edge_set(graph, transport, site_link_bridge):
-    # TODO not implemented - need to store all site link bridges
-    e_set = MultiEdgeSet()
-    # e_set.guid = site_link_bridge
-    return e_set
-
-
-def setup_vertices(graph):
-    for v in graph.vertices:
-        if v.is_white():
-            v.repl_info.cost = MAX_DWORD
-            v.root = None
-            v.component_id = None
-        else:
-            v.repl_info.cost = 0
-            v.root = v
-            v.component_id = v
-
-        v.repl_info.interval = 0
-        v.repl_info.options = 0xFFFFFFFF
-        v.repl_info.schedule = None  # TODO highly suspicious
-        v.demoted = False
-
-
-def dijkstra(graph, edge_type, include_black):
-    queue = []
-    setup_dijkstra(graph, edge_type, include_black, queue)
-    while len(queue) > 0:
-        cost, guid, vertex = heapq.heappop(queue)
-        for edge in vertex.edges:
-            for v in edge.vertices:
-                if v is not vertex:
-                    # add new path from vertex to v
-                    try_new_path(graph, queue, vertex, edge, v)
-
-
-def setup_dijkstra(graph, edge_type, include_black, queue):
-    setup_vertices(graph)
-    for vertex in graph.vertices:
-        if vertex.is_white():
-            continue
-
-        if (((vertex.is_black() and not include_black)
-             or edge_type not in vertex.accept_black
-             or edge_type not in vertex.accept_red_red)):
-            vertex.repl_info.cost = MAX_DWORD
-            vertex.root = None  # NULL GUID
-            vertex.demoted = True  # Demoted appears not to be used
-        else:
-            heapq.heappush(queue, (vertex.repl_info.cost, vertex.guid, vertex))
-
-
-def try_new_path(graph, queue, vfrom, edge, vto):
-    newRI = ReplInfo()
-    # What this function checks is that there is a valid time frame for
-    # which replication can actually occur, despite being adequately
-    # connected
-    intersect = combine_repl_info(vfrom.repl_info, edge.repl_info, newRI)
-
-    # If the new path costs more than the current, then ignore the edge
-    if newRI.cost > vto.repl_info.cost:
-        return
-
-    if newRI.cost < vto.repl_info.cost and not intersect:
-        return
-
-    new_duration = total_schedule(newRI.schedule)
-    old_duration = total_schedule(vto.repl_info.schedule)
-
-    # Cheaper or longer schedule
-    if newRI.cost < vto.repl_info.cost or new_duration > old_duration:
-        vto.root = vfrom.root
-        vto.component_id = vfrom.component_id
-        vto.repl_info = newRI
-        heapq.heappush(queue, (vto.repl_info.cost, vto.guid, vto))
-
-
-def check_demote_vertex(vertex, edge_type):
-    if vertex.is_white():
-        return
-
-    # Accepts neither red-red nor black edges, demote
-    if ((edge_type not in vertex.accept_black and
-         edge_type not in vertex.accept_red_red)):
-        vertex.repl_info.cost = MAX_DWORD
-        vertex.root = None
-        vertex.demoted = True  # Demoted appears not to be used
-
-
-def undemote_vertex(vertex):
-    if vertex.is_white():
-        return
-
-    vertex.repl_info.cost = 0
-    vertex.root = vertex
-    vertex.demoted = False
-
-
-def process_edge_set(graph, e_set, internal_edges):
-    if e_set is None:
-        for edge in graph.edges:
-            for vertex in edge.vertices:
-                check_demote_vertex(vertex, edge.con_type)
-            process_edge(graph, edge, internal_edges)
-            for vertex in edge.vertices:
-                undemote_vertex(vertex)
-    else:
-        for edge in e_set.edges:
-            process_edge(graph, edge, internal_edges)
-
-
-def process_edge(graph, examine, internal_edges):
-    # Find the set of all vertices touches the edge to examine
-    vertices = []
-    for v in examine.vertices:
-        # Append a 4-tuple of color, repl cost, guid and vertex
-        vertices.append((v.color, v.repl_info.cost, v.ndrpacked_guid, v))
-    # Sort by color, lower
-    DEBUG("vertices is %s" % vertices)
-    vertices.sort()
-
-    color, cost, guid, bestv = vertices[0]
-    # Add to internal edges an edge from every colored vertex to bestV
-    for v in examine.vertices:
-        if v.component_id is None or v.root is None:
-            continue
-
-        # Only add edge if valid inter-tree edge - needs a root and
-        # different components
-        if ((bestv.component_id is not None and
-             bestv.root is not None and
-             v.component_id is not None and
-             v.root is not None and
-             bestv.component_id != v.component_id)):
-            add_int_edge(graph, internal_edges, examine, bestv, v)
-
-
-# Add internal edge, endpoints are roots of the vertices to pass in
-# and are always colored
-def add_int_edge(graph, internal_edges, examine, v1, v2):
-    root1 = v1.root
-    root2 = v2.root
-
-    red_red = False
-    if root1.is_red() and root2.is_red():
-        red_red = True
-
-    if red_red:
-        if ((examine.con_type not in root1.accept_red_red
-             or examine.con_type not in root2.accept_red_red)):
-            return
-    elif (examine.con_type not in root1.accept_black
-          or examine.con_type not in root2.accept_black):
-        return
-
-    ri = ReplInfo()
-    ri2 = ReplInfo()
-
-    # Create the transitive replInfo for the two trees and this edge
-    if not combine_repl_info(v1.repl_info, v2.repl_info, ri):
-        return
-    # ri is now initialized
-    if not combine_repl_info(ri, examine.repl_info, ri2):
-        return
-
-    newIntEdge = InternalEdge(root1, root2, red_red, ri2, examine.con_type,
-                              examine.site_link)
-    # Order by vertex guid
-    #XXX guid comparison using ndr_pack
-    if newIntEdge.v1.ndrpacked_guid > newIntEdge.v2.ndrpacked_guid:
-        newIntEdge.v1 = root2
-        newIntEdge.v2 = root1
-
-    internal_edges.add(newIntEdge)
-
-
-def kruskal(graph, edges):
-    for v in graph.vertices:
-        v.edges = []
-
-    components = set([x for x in graph.vertices if not x.is_white()])
-    edges = list(edges)
-
-    # Sorted based on internal comparison function of internal edge
-    edges.sort()
-
-    expected_num_tree_edges = 0  # TODO this value makes little sense
-
-    count_edges = 0
-    output_edges = []
-    index = 0
-    while index < len(edges):  # TODO and num_components > 1
-        e = edges[index]
-        parent1 = find_component(e.v1)
-        parent2 = find_component(e.v2)
-        if parent1 is not parent2:
-            count_edges += 1
-            add_out_edge(graph, output_edges, e)
-            parent1.component_id = parent2
-            components.discard(parent1)
-
-        index += 1
-
-    return output_edges, len(components)
-
-
-def find_component(vertex):
-    if vertex.component_id is vertex:
-        return vertex
-
-    current = vertex
-    while current.component_id is not current:
-        current = current.component_id
-
-    root = current
-    current = vertex
-    while current.component_id is not root:
-        n = current.component_id
-        current.component_id = root
-        current = n
-
-    return root
-
-
-def add_out_edge(graph, output_edges, e):
-    v1 = e.v1
-    v2 = e.v2
-
-    # This multi-edge is a 'real' edge with no GUID
-    ee = MultiEdge()
-    ee.directed = False
-    ee.site_link = e.site_link
-    ee.vertices.append(v1)
-    ee.vertices.append(v2)
-    ee.con_type = e.e_type
-    ee.repl_info = e.repl_info
-    output_edges.append(ee)
-
-    v1.edges.append(ee)
-    v2.edges.append(ee)
-
-
-def test_all_reps_from(lp, creds):
-    kcc = KCC()
-    kcc.load_samdb(opts.dburl, lp, creds)
+    # This implies readonly and attempt_live_connections
     dsas = kcc.list_dsas()
+    samdb = kcc.samdb
     needed_parts = {}
     current_parts = {}
 
@@ -3027,12 +87,42 @@ def test_all_reps_from(lp, creds):
     vertex_colours = []
 
     for dsa_dn in dsas:
-        kcc = KCC()
-        kcc.run(opts.dburl, lp, creds, forced_local_dsa=dsa_dn,
+        if rng_seed is not None:
+            random.seed(rng_seed)
+        kcc = KCC(unix_now, readonly=True,
+                  verify=opts.verify, debug=opts.debug,
+                  dot_file_dir=opts.dot_file_dir)
+        if ldif_file is not None:
+            try:
+                # The dburl in this case is a temporary database.
+                # Its non-existence is ensured at the script startup.
+                # If it exists, it is from a previous iteration of
+                # this loop -- unless we're in an unfortunate race.
+                # Because this database is temporary, it lacks some
+                # detail and needs to be re-created anew to set the
+                # local dsa.
+                os.unlink(dburl)
+            except OSError:
+                pass
+
+            kcc.import_ldif(dburl, lp, ldif_file, dsa_dn)
+        else:
+            kcc.samdb = samdb
+        kcc.run(dburl, lp, creds, forced_local_dsa=dsa_dn,
                 forget_local_links=opts.forget_local_links,
-                forget_intersite_links=opts.forget_intersite_links)
+                forget_intersite_links=opts.forget_intersite_links,
+                attempt_live_connections=opts.attempt_live_connections)
+
         current, needed = kcc.my_dsa.get_rep_tables()
 
+        for dsa in kcc.my_site.dsa_table.values():
+            if dsa is kcc.my_dsa:
+                continue
+            kcc.translate_ntdsconn(dsa)
+            c, n = dsa.get_rep_tables()
+            current.update(c)
+            needed.update(n)
+
         for name, rep_table, rep_parts in (
                 ('needed', needed, needed_parts),
                 ('current', current, current_parts)):
@@ -3050,6 +140,11 @@ def test_all_reps_from(lp, creds):
                 else:
                     vertex_colours.append('#0000cc')
                 dot_vertices.append(dsa.dsa_dnstr)
+                if dsa.connect_table:
+                    DEBUG_FN("DSA %s %s connections:\n%s" %
+                             (dsa.dsa_dnstr, len(dsa.connect_table),
+                              [x.from_dnstr for x in
+                               dsa.connect_table.values()]))
                 for con in dsa.connect_table.values():
                     if con.is_rodc_topology():
                         colours.append('red')
@@ -3059,45 +154,24 @@ def test_all_reps_from(lp, creds):
 
     verify_and_dot('all-dsa-connections', dot_edges, vertices=dot_vertices,
                    label="all dsa NTDSConnections", properties=(),
-                   debug=DEBUG, verify=opts.verify, dot_files=opts.dot_files,
+                   debug=DEBUG, verify=opts.verify,
+                   dot_file_dir=opts.dot_file_dir,
                    directed=True, edge_colors=colours,
                    vertex_colors=vertex_colours)
 
     for name, rep_parts in (('needed', needed_parts),
                             ('current', current_parts)):
         for part, edges in rep_parts.items():
-            verify_and_dot('repsFrom_%s_all_%s' % (name, part), edges,
+            verify_and_dot('all-repsFrom_%s__%s' % (name, part), edges,
                            directed=True, label=part,
                            properties=(), debug=DEBUG, verify=opts.verify,
-                           dot_files=opts.dot_files)
-
-
-logger = logging.getLogger("samba_kcc")
-logger.addHandler(logging.StreamHandler(sys.stdout))
-DEBUG = logger.debug
-
-
-def _color_debug(*args, **kwargs):
-    DEBUG('%s%s%s' % (kwargs['color'], args[0], C_NORMAL), *args[1:])
-
-_globals = globals()
-for _color in ('DARK_RED', 'RED', 'DARK_GREEN', 'GREEN', 'YELLOW',
-               'DARK_YELLOW', 'DARK_BLUE', 'BLUE', 'PURPLE', 'MAGENTA',
-               'DARK_CYAN', 'CYAN', 'GREY', 'WHITE', 'REV_RED'):
-    _globals['DEBUG_' + _color] = partial(_color_debug, color=_globals[_color])
-
-
-def DEBUG_FN(msg=''):
-    import traceback
-    filename, lineno, function, text = traceback.extract_stack(None, 2)[0]
-    DEBUG("%s%s:%s%s %s%s()%s '%s'" % (CYAN, filename, BLUE, lineno,
-                                       CYAN, function, C_NORMAL, msg))
-
+                           dot_file_dir=opts.dot_file_dir)
 
 ##################################################
 # samba_kcc entry point
 ##################################################
 
+
 parser = optparse.OptionParser("samba_kcc [options]")
 sambaopts = options.SambaOptions(parser)
 credopts = options.CredentialsOptions(parser)
@@ -3123,13 +197,12 @@ parser.add_option("--list-verify-tests",
                         "and do nothing else"),
                   action="store_true")
 
-parser.add_option("--no-dot-files", dest='dot_files',
-                  help="Don't write dot graph files in /tmp",
-                  default=True, action="store_false")
+parser.add_option("--dot-file-dir", default=None,
+                  help="Write Graphviz .dot files to this directory")
 
 parser.add_option("--seed",
                   help="random number seed",
-                  type=int)
+                  type=int, default=DEFAULT_RNG_SEED)
 
 parser.add_option("--importldif",
                   help="import topology ldif file",
@@ -3185,6 +258,9 @@ if opts.list_verify_tests:
     list_verify_tests()
     sys.exit(0)
 
+if opts.test_all_reps_from:
+    opts.readonly = True
+
 if opts.debug:
     logger.setLevel(logging.DEBUG)
 elif opts.readonly:
@@ -3192,11 +268,7 @@ elif opts.readonly:
 else:
     logger.setLevel(logging.WARNING)
 
-# initialize seed from optional input parameter
-if opts.seed:
-    random.seed(opts.seed)
-else:
-    random.seed(0xACE5CA11)
+random.seed(opts.seed)
 
 if opts.now:
     for timeformat in ("%Y%m%d%H%M%S%Z", "%Y%m%d%H%M%S"):
@@ -3207,28 +279,27 @@ if opts.now:
             pass
     else:
         # else happens if break doesn't --> no match
-        print >> sys.stderr, "could not parse time '%s'" % opts.now
+        print("could not parse time '%s'" % (opts.now), file = sys.stderr)
         sys.exit(1)
-
     unix_now = int(time.mktime(now_tuple))
 else:
     unix_now = int(time.time())
 
-nt_now = unix2nttime(unix_now)
-
 lp = sambaopts.get_loadparm()
 creds = credopts.get_credentials(lp, fallback_machine=True)
 
 if opts.dburl is None:
-    opts.dburl = lp.samdb_url()
-
-if opts.test_all_reps_from:
-    opts.readonly = True
-    test_all_reps_from(lp, creds)
-    sys.exit()
+    if opts.importldif:
+        opts.dburl = opts.tmpdb
+    else:
+        opts.dburl = lp.samdb_url()
+elif opts.importldif:
+    logger.error("Don't use -H/--URL with --importldif, use --tmpdb instead")
+    sys.exit(1)
 
 # Instantiate Knowledge Consistency Checker and perform run
-kcc = KCC()
+kcc = KCC(unix_now, readonly=opts.readonly, verify=opts.verify,
+          debug=opts.debug, dot_file_dir=opts.dot_file_dir)
 
 if opts.exportldif:
     rc = kcc.export_ldif(opts.dburl, lp, creds, opts.exportldif)
@@ -3238,21 +309,35 @@ if opts.importldif:
     if opts.tmpdb is None or opts.tmpdb.startswith('ldap'):
         logger.error("Specify a target temp database file with --tmpdb option")
         sys.exit(1)
+    if os.path.exists(opts.tmpdb):
+        logger.error("The temp database file (%s) specified with --tmpdb "
+                     "already exists. We refuse to clobber it." % opts.tmpdb)
+        sys.exit(1)
 
-    rc = kcc.import_ldif(opts.tmpdb, lp, creds, opts.importldif)
+    rc = kcc.import_ldif(opts.tmpdb, lp, opts.importldif,
+                         forced_local_dsa=opts.forced_local_dsa)
     if rc != 0:
         sys.exit(rc)
 
+
+kcc.load_samdb(opts.dburl, lp, creds, force=False)
+
+if opts.test_all_reps_from:
+    test_all_reps_from(kcc, opts.dburl, lp, creds, unix_now,
+                       rng_seed=opts.seed,
+                       ldif_file=opts.importldif)
+    sys.exit()
+
 if opts.list_valid_dsas:
-    kcc.load_samdb(opts.dburl, lp, creds)
-    print '\n'.join(kcc.list_dsas())
+    print('\n'.join(kcc.list_dsas()))
     sys.exit()
 
 try:
     rc = kcc.run(opts.dburl, lp, creds, opts.forced_local_dsa,
-                 opts.forget_local_links, opts.forget_intersite_links)
+                 opts.forget_local_links, opts.forget_intersite_links,
+                 attempt_live_connections=opts.attempt_live_connections)
     sys.exit(rc)
 
-except GraphError, e:
-    print e
+except GraphError as e:
+    print( e)
     sys.exit(1)