repl: Set GET_ALL_GROUP_MEMBERSHIP flag in the drepl server
[nivanova/samba-autobuild/.git] / python / samba / kcc / __init__.py
old mode 100755 (executable)
new mode 100644 (file)
index 1c1e6d9..f775a11
@@ -1,6 +1,4 @@
-#!/usr/bin/env python
-#
-# Compute our KCC topology
+# define the KCC object
 #
 # Copyright (C) Dave Craft 2011
 # Copyright (C) Andrew Bartlett 2015
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-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
-os.environ['PYTHONUNBUFFERED'] = '1'
-
-# forcing GMT avoids a problem in some timezones with kerberos. Both MIT
-# heimdal can get mutual authentication errors due to the 24 second difference
-# between UTC and GMT when using some zone files (eg. the PDT zone from
-# the US)
-os.environ["TZ"] = "GMT"
-
-# Find right directory when running from source tree
-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,
-    dsdb,
-    drs_utils,
-    nttime2unix)
+from samba import unix2nttime, nttime2unix
+from samba import ldb, dsdb, drs_utils
 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
+from samba.dcerpc import drsuapi, misc
+
+from samba.kcc.kcc_utils import Site, Partition, Transport, SiteLink
+from samba.kcc.kcc_utils import NCReplica, NCType, nctype_lut, GraphNode
+from samba.kcc.kcc_utils import RepsFromTo, KCCError, KCCFailedObject
+from samba.kcc.graph import convert_schedule_to_repltimes
+
+from samba.ndr import ndr_pack
+
+from samba.kcc.graph_utils import verify_and_dot
+
+from samba.kcc import ldif_import_export
+from samba.kcc.graph import setup_graph, get_spanning_tree_edges
+from samba.kcc.graph import Vertex
+
+from samba.kcc.debug import DEBUG, DEBUG_FN, logger
+from samba.kcc import debug
+
+
+def sort_replica_by_dsa_guid(rep1, rep2):
+    """Helper to sort NCReplicas by their DSA guids
+
+    The guids need to be sorted in their NDR form.
+
+    :param rep1: An NC replica
+    :param rep2: Another replica
+    :return: -1, 0, or 1, indicating sort order.
+    """
+    return cmp(ndr_pack(rep1.rep_dsa_guid), ndr_pack(rep2.rep_dsa_guid))
+
+
+def sort_dsa_by_gc_and_guid(dsa1, dsa2):
+    """Helper to sort DSAs by guid global catalog status
+
+    GC DSAs come before non-GC DSAs, other than that, the guids are
+    sorted in NDR form.
+
+    :param dsa1: A DSA object
+    :param dsa2: Another DSA
+    :return: -1, 0, or 1, indicating sort order.
+    """
+    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():
+    """Can the KCC use SMTP replication?
+
+    Currently always returns false because Samba doesn't implement
+    SMTP transfer for NC changes between DCs.
+
+    :return: Boolean (always False)
+    """
+    return False
 
 
 class KCC(object):
@@ -71,16 +97,16 @@ class KCC(object):
     :param read_only: Don't write to the database.
     :param verify: Check topological invariants for the generated graphs
     :param debug: Write verbosely to stderr.
-    "param dot_files: write Graphviz files in /tmp showing topology
+    "param dot_file_dir: write diagnostic Graphviz files in this directory
     """
-    def __init__(self):
+    def __init__(self, unix_now, readonly=False, verify=False, debug=False,
+                 dot_file_dir=None):
         """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 = {}
@@ -108,7 +134,14 @@ class KCC(object):
 
         self.samdb = None
 
-    def load_all_transports(self):
+        self.unix_now = unix_now
+        self.nt_now = unix2nttime(unix_now)
+        self.readonly = readonly
+        self.verify = verify
+        self.debug = debug
+        self.dot_file_dir = dot_file_dir
+
+    def load_ip_transport(self):
         """Loads the inter-site transport objects for Sites
 
         :return: None
@@ -129,10 +162,15 @@ class KCC(object):
             transport = Transport(dnstr)
 
             transport.load_transport(self.samdb)
-            self.transport_table.setdefault(str(transport.guid),
-                                            transport)
             if transport.name == 'IP':
                 self.ip_transport = transport
+            elif transport.name == 'SMTP':
+                logger.debug("Samba KCC is ignoring the obsolete "
+                             "SMTP transport.")
+
+            else:
+                logger.warning("Samba KCC does not support the transport "
+                               "called %r." % (transport.name,))
 
         if self.ip_transport is None:
             raise KCCError("there doesn't seem to be an IP transport")
@@ -174,7 +212,7 @@ class KCC(object):
         :param dn_str: a site dn_str
         :return: the Site object pertaining to the dn_str
         """
-        site = Site(dn_str, unix_now)
+        site = Site(dn_str, self.unix_now)
         site.load_site(self.samdb)
 
         # We avoid replacing the site with an identical copy in case
@@ -224,14 +262,15 @@ class KCC(object):
         :return: None
         :raise: KCCError if DSA can't be found
         """
-        dn = ldb.Dn(self.samdb, "<GUID=%s>" % self.samdb.get_ntds_GUID())
+        dn_query = "<GUID=%s>" % self.samdb.get_ntds_GUID()
+        dn = ldb.Dn(self.samdb, dn_query)
         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)
+            DEBUG_FN("Search for dn '%s' [from %s] failed: %s. "
+                     "This typically happens in --importldif mode due "
+                     "to lack of module support." % (dn, dn_query, estr))
             try:
                 # We work around the failure above by looking at the
                 # dsServiceName that was put in the fake rootdse by
@@ -264,11 +303,11 @@ class KCC(object):
         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)
+            debug.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)" %
+                                    self.my_dsa_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
@@ -302,22 +341,14 @@ class KCC(object):
             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"""
+    def refresh_failed_links_connections(self, ping=None):
+        """Ensure the failed links list is up to date
 
-        # Instead of NULL link with failure_count = 0, the tuple is
-        # simply removed
+        Based on MS-ADTS 6.2.2.1
 
+        :param ping: An oracle function of remote site availability
+        :return: None
+        """
         # LINKS: Refresh failed links
         self.kcc_failed_links = {}
         current, needed = self.my_dsa.get_rep_tables()
@@ -334,15 +365,11 @@ class KCC(object):
                 dns_name = reps_from.dns_name1
 
                 f = self.kcc_failed_links.get(dsa_guid)
-                if not f:
+                if f is None:
                     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,
@@ -351,15 +378,13 @@ class KCC(object):
 
         # CONNECTIONS: Refresh failed connections
         restore_connections = set()
-        if opts.attempt_live_connections:
+        if ping is not None:
             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)
+                if ping(connection.dns_name):
                     # Failed connection is no longer failing
                     restore_connections.add(connection)
-                except drs_utils.drsException:
-                    # Failed connection still failing
+                else:
                     connection.failure_count += 1
         else:
             DEBUG("refresh_failed_links: not checking live links because we\n"
@@ -386,15 +411,17 @@ class KCC(object):
                 unix_first_failure = \
                     nttime2unix(failed_link.time_first_failure)
                 # TODO guard against future
-                if unix_first_failure > unix_now:
+                if unix_first_failure > self.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:
+                if (self.unix_now - unix_first_failure) > 60 * 60 * 2:
                     return True
 
-        # TODO connections
+        # TODO connections.
+        # We have checked failed *links*, but we also need to check
+        # *connections*
 
         return False
 
@@ -407,160 +434,176 @@ class KCC(object):
         # that became active during this run.
         pass
 
-    def remove_unneeded_ntdsconn(self, all_connected):
-        """Remove unneeded NTDS Connections once topology is calculated
+    def _ensure_connections_are_loaded(self, connections):
+        """Load or fake-load NTDSConnections lacking GUIDs
 
-        Based on MS-ADTS 6.2.2.4 Removing Unnecessary Connections
+        New connections don't have GUIDs and created times which are
+        needed for sorting. If we're in read-only mode, we make fake
+        GUIDs, otherwise we ask SamDB to do it for us.
 
-        :param all_connected: indicates whether all sites are connected
+        :param connections: an iterable of NTDSConnection objects.
         :return: None
         """
-        mydsa = self.my_dsa
-
-        # New connections won't have GUIDs which are needed for
-        # sorting. Add them.
-        for cn_conn in mydsa.connect_table.values():
+        for cn_conn in connections:
             if cn_conn.guid is None:
-                if opts.readonly:
+                if self.readonly:
                     cn_conn.guid = misc.GUID(str(uuid.uuid4()))
-                    cn_conn.whenCreated = nt_now
+                    cn_conn.whenCreated = self.nt_now
                 else:
                     cn_conn.load_connection(self.samdb)
 
-        for cn_conn in mydsa.connect_table.values():
+    def _mark_broken_ntdsconn(self):
+        """Find NTDS Connections that lack a remote
 
+        I'm not sure how they appear. Let's be rid of them by marking
+        them with the to_be_deleted attribute.
+
+        :return: None
+        """
+        for cn_conn in self.my_dsa.connect_table.values():
             s_dnstr = cn_conn.get_from_dnstr()
             if s_dnstr is None:
+                DEBUG_FN("%s has phantom connection %s" % (self.my_dsa,
+                                                           cn_conn))
                 cn_conn.to_be_deleted = True
-                continue
-
-            # Get the source DSA no matter what site
-            # XXX s_dsa is NEVER USED. It will be removed.
-            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
+    def _mark_unneeded_local_ntdsconn(self):
+        """Find unneeded intrasite NTDS Connections for removal
 
-            # 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
+        Based on MS-ADTS 6.2.2.4 Removing Unnecessary Connections.
+        Every DC removes its own unnecessary intrasite connections.
+        This function tags them with the to_be_deleted attribute.
 
-                if self.my_site.is_cleanup_ntdsconn_disabled():
-                    continue
+        :return: None
+        """
+        # XXX should an RODC be regarded as same site? It isn't part
+        # of the intrasite ring.
 
-                # 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
+        if self.my_site.is_cleanup_ntdsconn_disabled():
+            DEBUG_FN("not doing ntdsconn cleanup for site %s, "
+                     "because it is disabled" % self.my_site)
+            return
 
-                    s2_dnstr = cn2_conn.get_from_dnstr()
+        mydsa = self.my_dsa
 
-                    # If the NTDS Connections has a different
-                    # fromServer field then no match
-                    if s2_dnstr != s_dnstr:
-                        continue
+        try:
+            self._ensure_connections_are_loaded(mydsa.connect_table.values())
+        except KCCError:
+            # RODC never actually added any connections to begin with
+            if mydsa.is_ro():
+                return
 
-                    #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)))
+        local_connections = []
 
-                    if lesser:
-                        break
+        for cn_conn in mydsa.connect_table.values():
+            s_dnstr = cn_conn.get_from_dnstr()
+            if s_dnstr in self.my_site.dsa_table:
+                removable = not (cn_conn.is_generated() or
+                                 cn_conn.is_rodc_topology())
+                packed_guid = ndr_pack(cn_conn.guid)
+                local_connections.append((cn_conn, s_dnstr,
+                                          packed_guid, removable))
+
+        for a, b in itertools.permutations(local_connections, 2):
+            cn_conn, s_dnstr, packed_guid, removable = a
+            cn_conn2, s_dnstr2, packed_guid2, removable2 = b
+            if (removable and
+                s_dnstr == s_dnstr2 and
+                cn_conn.whenCreated < cn_conn2.whenCreated or
+                (cn_conn.whenCreated == cn_conn2.whenCreated and
+                 packed_guid < packed_guid2)):
+                cn_conn.to_be_deleted = True
 
-                if lesser and not cn_conn.is_rodc_topology():
-                    cn_conn.to_be_deleted = True
+    def _mark_unneeded_intersite_ntdsconn(self):
+        """find unneeded intersite NTDS Connections for removal
 
-            # 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
+        Based on MS-ADTS 6.2.2.4 Removing Unnecessary Connections. The
+        intersite topology generator removes links for all DCs in its
+        site. Here we just tag them with the to_be_deleted attribute.
 
-                if not mydsa.is_istg():
-                    continue
+        :return: None
+        """
+        # TODO Figure out how best to handle the RODC case
+        # The RODC is ITSG, but shouldn't act on anyone's behalf.
+        if self.my_dsa.is_ro():
+            return
 
-                if not cn_conn.is_generated():
+        # Find the intersite connections
+        local_dsas = self.my_site.dsa_table
+        connections_and_dsas = []
+        for dsa in local_dsas.values():
+            for cn in dsa.connect_table.values():
+                if cn.to_be_deleted:
                     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:
+                s_dnstr = cn.get_from_dnstr()
+                if s_dnstr is None:
                     continue
+                if s_dnstr not in local_dsas:
+                    from_dsa = self.get_dsa(s_dnstr)
+                    # Samba ONLY: ISTG removes connections to dead DCs
+                    if from_dsa is None and '\\0ADEL' in s_dnstr:
+                        logger.info("DSA appears deleted, removing connection %s" % s_dnstr)
+                        cn.to_be_deleted = True
+                        continue
+                    connections_and_dsas.append((cn, dsa, from_dsa))
 
-                # This is the result of create_intersite_connections
-                if not all_connected:
-                    continue
+        self._ensure_connections_are_loaded(x[0] for x in connections_and_dsas)
+        for cn, to_dsa, from_dsa in connections_and_dsas:
+            if not cn.is_generated() or cn.is_rodc_topology():
+                continue
 
-                if not cn_conn.is_rodc_topology():
-                    cn_conn.to_be_deleted = True
+            # If the connection is in the kept_connections list, we
+            # only remove it if an endpoint seems down.
+            if (cn in self.kept_connections and
+                not (self.is_bridgehead_failed(to_dsa, True) or
+                     self.is_bridgehead_failed(from_dsa, True))):
+                continue
 
-        if mydsa.is_ro() or opts.readonly:
-            for connect in mydsa.connect_table.values():
+            # this one is broken and might be superseded by another.
+            # But which other? Let's just say another link to the same
+            # site can supersede.
+            from_dnstr = from_dsa.dsa_dnstr
+            for site in self.site_table.values():
+                if from_dnstr in site.rw_dsa_table:
+                    for cn2, to_dsa2, from_dsa2 in connections_and_dsas:
+                        if (cn is not cn2 and
+                            from_dsa2 in site.rw_dsa_table):
+                            cn.to_be_deleted = True
+
+    def _commit_changes(self, dsa):
+        if dsa.is_ro() or self.readonly:
+            for connect in dsa.connect_table.values():
                 if connect.to_be_deleted:
-                    DEBUG_FN("TO BE DELETED:\n%s" % connect)
+                    logger.info("TO BE DELETED:\n%s" % connect)
                 if connect.to_be_added:
-                    DEBUG_FN("TO BE ADDED:\n%s" % connect)
+                    logger.info("TO BE ADDED:\n%s" % connect)
+                if connect.to_be_modified:
+                    logger.info("TO BE MODIFIED:\n%s" % connect)
 
             # Peform deletion from our tables but perform
             # no database modification
-            mydsa.commit_connections(self.samdb, ro=True)
+            dsa.commit_connections(self.samdb, ro=True)
         else:
             # Commit any modified connections
-            mydsa.commit_connections(self.samdb)
+            dsa.commit_connections(self.samdb)
+
+    def remove_unneeded_ntdsconn(self, all_connected):
+        """Remove unneeded NTDS Connections once topology is calculated
+
+        Based on MS-ADTS 6.2.2.4 Removing Unnecessary Connections
+
+        :param all_connected: indicates whether all sites are connected
+        :return: None
+        """
+        self._mark_broken_ntdsconn()
+        self._mark_unneeded_local_ntdsconn()
+        # if we are not the istg, we're done!
+        # if we are the istg, but all_connected is False, we also do nothing.
+        if self.my_dsa.is_istg() and all_connected:
+            self._mark_unneeded_intersite_ntdsconn()
+
+        for dsa in self.my_site.dsa_table.values():
+            self._commit_changes(dsa)
 
     def modify_repsFrom(self, n_rep, t_repsFrom, s_rep, s_dsa, cn_conn):
         """Update an repsFrom object if required.
@@ -587,15 +630,18 @@ class KCC(object):
         :return: None
         """
         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_ADD_REF is set in replicaFlags unconditionally
+        # Samba ONLY:
+        if ((t_repsFrom.replica_flags &
+             drsuapi.DRSUAPI_DRS_ADD_REF) == 0x0):
+            t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_ADD_REF
 
         # Bit DRS_PER_SYNC is set in replicaFlags if and only
         # if nTDSConnection schedule has a value v that specifies
@@ -627,7 +673,7 @@ class KCC(object):
              dsdb.NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT) != 0x0):
 
             if (cn_conn.options & dsdb.NTDSCONN_OPT_USE_NOTIFY) == 0x0:
-                # XXX WARNING
+                # 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
@@ -744,166 +790,66 @@ class KCC(object):
         #
         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:
-            # XXX This entire branch is NEVER used! Because we don't do SMTP!
-            # (see the if condition above). Just close your eyes here.
-            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)
+        if ((t_repsFrom.replica_flags &
+             drsuapi.DRSUAPI_DRS_MAIL_REP) != 0x0):
+            t_repsFrom.replica_flags &= ~drsuapi.DRSUAPI_DRS_MAIL_REP
 
-            x_transport = self.transport_table[str(cn_conn.transport_guid)]
+        t_repsFrom.transport_guid = misc.GUID()
 
-            if t_repsFrom.transport_guid != x_transport.guid:
-                t_repsFrom.transport_guid = x_transport.guid
+        # See (NOTE MS-TECH INCORRECT) above
 
-            # See (NOTE MS-TECH INCORRECT) above
-            if x_transport.address_attr == "dNSHostName":
+        # NOTE: it looks like these conditionals are pointless,
+        # because the state will end up as `t_repsFrom.dns_name1 ==
+        # nastr` in either case, BUT the repsFrom thing is magic and
+        # assigning to it alters some flags. So we try not to update
+        # it unless necessary.
+        if t_repsFrom.dns_name1 != nastr:
+            t_repsFrom.dns_name1 = nastr
 
-                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.version > 0x1 and t_repsFrom.dns_name2 != 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:
+        if t_repsFrom.is_modified():
+            DEBUG_FN("modify_repsFrom(): %s" % t_repsFrom)
 
-                        t_repsFrom.dns_name1 = nastr
-                        t_repsFrom.dns_name2 = nastr
+    def get_dsa_for_implied_replica(self, n_rep, cn_conn):
+        """If a connection imply a replica, find the relevant DSA
 
-        if t_repsFrom.is_modified():
-            logger.debug("modify_repsFrom(): %s" % t_repsFrom)
+        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. If
+        it should be, return the DSA; otherwise return None.
 
-    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
+        Based on part of MS-ADTS 6.2.2.5
 
         :param n_rep: NC replica
-        :param conn: NTDS Connection
-        ::returns (True || False), source DSA:
+        :param cn_conn: NTDS Connection
+        :return: source DSA or None
         """
-        #XXX different conditions for "implies" than MS-ADTS 6.2.2
+        # XXX different conditions for "implies" than MS-ADTS 6.2.2
+        # preamble.
 
-        # 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.
+        # It boils down to: we want an enabled, non-FRS connections to
+        # a valid remote DSA with a non-RO replica corresponding to
+        # n_rep.
 
-        s_dsa = None
+        if not cn_conn.is_enabled() or cn_conn.is_rodc_topology():
+            return 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)
+        s_dnstr = cn_conn.get_from_dnstr()
+        s_dsa = self.get_dsa(s_dnstr)
 
         # No DSA matching this source DN string?
         if s_dsa is None:
-            return False, None
+            return 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
+        if (s_rep is not None and
+            s_rep.is_present() and
+            (not s_rep.is_ro() or n_rep.is_partial())):
+            return s_dsa
+        return None
 
     def translate_ntdsconn(self, current_dsa=None):
         """Adjust repsFrom to match NTDSConnections
@@ -919,15 +865,19 @@ class KCC(object):
         """
         count = 0
 
+        ro = False
         if current_dsa is None:
             current_dsa = self.my_dsa
 
+        if current_dsa.is_ro():
+            ro = True
+
         if current_dsa.is_translate_ntdsconn_disabled():
-            logger.debug("skipping translate_ntdsconn() "
-                         "because disabling flag is set")
+            DEBUG_FN("skipping translate_ntdsconn() "
+                     "because disabling flag is set")
             return
 
-        logger.debug("translate_ntdsconn(): enter")
+        DEBUG_FN("translate_ntdsconn(): enter")
 
         current_rep_table, needed_rep_table = current_dsa.get_rep_tables()
 
@@ -950,17 +900,34 @@ class KCC(object):
         # 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)
+            # If we're on the RODC, hardcode the update flags
+            if ro:
+                c_rep = current_rep_table[dnstr]
+                c_rep.load_repsFrom(self.samdb)
+                for t_repsFrom in c_rep.rep_repsFrom:
+                    replica_flags = (drsuapi.DRSUAPI_DRS_INIT_SYNC |
+                                     drsuapi.DRSUAPI_DRS_PER_SYNC |
+                                     drsuapi.DRSUAPI_DRS_ADD_REF |
+                                     drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING |
+                                     drsuapi.DRSUAPI_DRS_NONGC_RO_REP)
+                    if t_repsFrom.replica_flags != replica_flags:
+                        t_repsFrom.replica_flags = replica_flags
+                c_rep.commit_repsFrom(self.samdb)
+            else:
+                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:
+            # TODO Must delete repsFrom/repsTo for these replicas
             DEBUG('deleting these reps: %s' % delete_reps)
             for dnstr in delete_reps:
                 del current_rep_table[dnstr]
 
+        # HANDLE REPS-FROM
+        #
         # Now perform the scan of replicas we'll need
         # and compare any current repsFrom against the
         # connections
@@ -971,7 +938,7 @@ class KCC(object):
             n_rep.load_repsFrom(self.samdb)
             n_rep.load_fsmo_roles(self.samdb)
 
-            # Loop thru the existing repsFrom tupples (if any)
+            # Loop thru the existing repsFrom tuples (if any)
             # XXX This is a list and could contain duplicates
             #     (multiple load_repsFrom calls)
             for t_repsFrom in n_rep.rep_repsFrom:
@@ -989,38 +956,21 @@ class KCC(object):
                     t_repsFrom.to_be_deleted = True
                     continue
 
+                # Find the connection that this repsFrom would use. If
+                # there isn't a good one (i.e. non-RODC_TOPOLOGY,
+                # meaning non-FRS), we delete the repsFrom.
                 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
                 connections = current_dsa.get_connection_by_from_dnstr(s_dnstr)
-
-                count = 0
-                cn_conn = None
-
-                for con in connections:
-                    if con.is_rodc_topology():
-                        continue
-                    cn_conn = con
-
-                # 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:
+                for cn_conn in connections:
+                    if not cn_conn.is_rodc_topology():
+                        break
+                else:
+                    # no break means no non-rodc_topology connection exists
                     t_repsFrom.to_be_deleted = True
                     continue
 
-                #     [...] KCC removes this repsFrom tuple if:
-                #
+                # KCC removes this repsFrom tuple if any of the following
+                # is true:
                 #     No NC replica of the NC "is present" on DSA that
                 #     would be source of replica
                 #
@@ -1043,17 +993,16 @@ class KCC(object):
             # repsFrom is not already present
             for cn_conn in current_dsa.connect_table.values():
 
-                implied, s_dsa = self.is_repsFrom_implied(n_rep, cn_conn)
-                if not implied:
+                s_dsa = self.get_dsa_for_implied_replica(n_rep, cn_conn)
+                if s_dsa is None:
                     continue
 
-                # Loop thru the existing repsFrom tupples (if any) and
+                # Loop thru the existing repsFrom tuples (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)
-                    #XXX what?
                     if s_dsa is self.get_dsa_by_guidstr(guidstr):
                         s_dsa = None
                         break
@@ -1075,7 +1024,7 @@ class KCC(object):
                 if t_repsFrom.is_modified():
                     n_rep.rep_repsFrom.append(t_repsFrom)
 
-            if opts.readonly:
+            if self.readonly or ro:
                 # Display any to be deleted or modified repsFrom
                 text = n_rep.dumpstr_to_be_deleted()
                 if text:
@@ -1091,7 +1040,75 @@ class KCC(object):
                 # Commit any modified repsFrom to the NC replica
                 n_rep.commit_repsFrom(self.samdb)
 
-    def merge_failed_links(self):
+        # HANDLE REPS-TO:
+        #
+        # Now perform the scan of replicas we'll need
+        # and compare any current repsTo against the
+        # connections
+
+        # RODC should never push to anybody (should we check this?)
+        if ro:
+            return
+
+        for n_rep in needed_rep_table.values():
+
+            # load any repsTo and fsmo roles as we'll
+            # need them during connection translation
+            n_rep.load_repsTo(self.samdb)
+
+            # Loop thru the existing repsTo tuples (if any)
+            # XXX This is a list and could contain duplicates
+            #     (multiple load_repsTo calls)
+            for t_repsTo in n_rep.rep_repsTo:
+
+                # for each tuple t in n!repsTo, let s be the nTDSDSA
+                # object such that s!objectGUID = t.uuidDsa
+                guidstr = str(t_repsTo.source_dsa_obj_guid)
+                s_dsa = self.get_dsa_by_guidstr(guidstr)
+
+                # Source dsa is gone from config (strange)
+                # so cleanup stale repsTo for unlisted DSA
+                if s_dsa is None:
+                    logger.warning("repsTo source DSA guid (%s) not found" %
+                                   guidstr)
+                    t_repsTo.to_be_deleted = True
+                    continue
+
+                # Find the connection that this repsTo would use. If
+                # there isn't a good one (i.e. non-RODC_TOPOLOGY,
+                # meaning non-FRS), we delete the repsTo.
+                s_dnstr = s_dsa.dsa_dnstr
+                if '\\0ADEL' in s_dnstr:
+                    logger.warning("repsTo source DSA guid (%s) appears deleted" %
+                                   guidstr)
+                    t_repsTo.to_be_deleted = True
+                    continue
+
+                connections = s_dsa.get_connection_by_from_dnstr(self.my_dsa_dnstr)
+                if len(connections) > 0:
+                    # Then this repsTo is tentatively valid
+                    continue
+                else:
+                    # There is no plausible connection for this repsTo
+                    t_repsTo.to_be_deleted = True
+
+            if self.readonly:
+                # Display any to be deleted or modified repsTo
+                text = n_rep.dumpstr_reps_to()
+                if text:
+                    logger.info("REMOVING REPS-TO:\n%s" % text)
+
+                # Peform deletion from our tables but perform
+                # no database modification
+                n_rep.commit_repsTo(self.samdb, ro=True)
+            else:
+                # Commit any modified repsTo to the NC replica
+                n_rep.commit_repsTo(self.samdb)
+
+        # TODO Remove any duplicate repsTo values. This should never happen in
+        # any normal situations.
+
+    def merge_failed_links(self, ping=None):
         """Merge of kCCFailedLinks and kCCFailedLinks from bridgeheads.
 
         The KCC on a writable DC attempts to merge the link and connection
@@ -1109,8 +1126,8 @@ class KCC(object):
         #    site merge all the failure info
         #
         # XXX - not implemented yet
-        if opts.attempt_live_connections:
-            DEBUG_RED("merge_failed_links() is NOT IMPLEMENTED")
+        if ping is not None:
+            debug.DEBUG_RED("merge_failed_links() is NOT IMPLEMENTED")
         else:
             DEBUG_FN("skipping merge_failed_links() because it requires "
                      "real network connections\n"
@@ -1130,62 +1147,29 @@ class KCC(object):
         :param part: a Partition object
         :returns: an InterSiteGraph object
         """
-        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"
-            if transport.name != 'IP':
-                DEBUG_FN("setup_graph is ignoring transport %s" %
-                         transport.name)
-                continue
-            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)
+        # 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
+        bridges_required = self.my_site.site_options & 0x00001002 != 0
+        transport_guid = str(self.ip_transport.guid)
+
+        g = setup_graph(part, self.site_table, transport_guid,
+                        self.sitelink_table, bridges_required)
+
+        if self.verify or self.dot_file_dir is not None:
+            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 = ()
+            name = 'site_edges_%s' % part.partstr
+            verify_and_dot(name, dot_edges, directed=False,
+                           label=self.my_dsa_dnstr,
+                           properties=verify_properties, debug=DEBUG,
+                           verify=self.verify,
+                           dot_file_dir=self.dot_file_dir)
 
         return g
 
@@ -1210,14 +1194,14 @@ class KCC(object):
 
         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)
+        if not bhs:
+            debug.DEBUG_MAGENTA("get_bridgehead FAILED:\nsitedn = %s" %
+                                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]
+
+        debug.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):
@@ -1238,15 +1222,14 @@ class KCC(object):
             no DC has failed.
         :return: list of dsa object for available bridgehead DCs
         """
-
         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)
+        if transport.name != "IP":
+            raise KCCError("get_all_bridgeheads has run into a "
+                           "non-IP transport! %r"
+                           % (transport.name,))
+
+        DEBUG_FN(site.rw_dsa_table)
         for dsa in site.rw_dsa_table.values():
 
             pdnstr = dsa.get_parent_dnstr()
@@ -1287,34 +1270,13 @@ class KCC(object):
                 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
-                #XXX nastr is NEVER USED. It will be removed.
-                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)
+            DEBUG_FN("found a bridgehead: %s" % dsa.dsa_dnstr)
             bhs.append(dsa)
 
         # IF bit NTDSSETTINGS_OPT_IS_RAND_BH_SELECTION_DISABLED is set in
@@ -1327,7 +1289,7 @@ class KCC(object):
             bhs.sort(sort_dsa_by_gc_and_guid)
         else:
             random.shuffle(bhs)
-        DEBUG_YELLOW(bhs)
+        debug.DEBUG_YELLOW(bhs)
         return bhs
 
     def is_bridgehead_failed(self, dsa, detect_failed):
@@ -1410,10 +1372,10 @@ class KCC(object):
         """
         rbhs_all = self.get_all_bridgeheads(rsite, part, transport,
                                             partial_ok, False)
-        rbh_table = {x.dsa_dnstr: x for x in rbhs_all}
+        rbh_table = dict((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]))
+        debug.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,
@@ -1424,8 +1386,8 @@ class KCC(object):
         if lbh.is_ro():
             lbhs_all.append(lbh)
 
-        DEBUG_GREY("lbhs_all: %s %s" % (len(lbhs_all),
-                                        [x.dsa_dnstr for x in lbhs_all]))
+        debug.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,
@@ -1440,7 +1402,7 @@ class KCC(object):
                 if rdsa is None:
                     continue
 
-                DEBUG_DARK_YELLOW("rdsa is %s" % rdsa.dsa_dnstr)
+                debug.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
@@ -1538,7 +1500,7 @@ class KCC(object):
                             cn.set_modified(True)
 
                     # Display any modified connection
-                    if opts.readonly:
+                    if self.readonly:
                         if cn.to_be_modified:
                             logger.info("TO BE MODIFIED:\n%s" % cn)
 
@@ -1558,7 +1520,7 @@ class KCC(object):
                 if rdsa is None:
                     continue
 
-                DEBUG_DARK_YELLOW("round 2: rdsa is %s" % rdsa.dsa_dnstr)
+                debug.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
@@ -1583,7 +1545,7 @@ class KCC(object):
                     self.kept_connections.add(cn)
 
         # ENDFOR
-        DEBUG_RED("valid connections %d" % valid_connections)
+        debug.DEBUG_RED("valid connections %d" % valid_connections)
         DEBUG("kept_connections:\n%s" % (self.kept_connections,))
         # IF cValidConnections = 0
         if valid_connections == 0:
@@ -1615,11 +1577,14 @@ class KCC(object):
             # cn!options = opt, cn!transportType is a reference to t,
             # cn!fromServer is a reference to rbh, and cn!schedule = sch
             DEBUG_FN("new connection, KCC dsa: %s" % self.my_dsa.dsa_dnstr)
-            cn = lbh.new_connection(opt, 0, transport,
+            system_flags = (dsdb.SYSTEM_FLAG_CONFIG_ALLOW_RENAME |
+                            dsdb.SYSTEM_FLAG_CONFIG_ALLOW_MOVE)
+
+            cn = lbh.new_connection(opt, system_flags, transport,
                                     rbh.dsa_dnstr, link_sched)
 
             # Display any added connection
-            if opts.readonly:
+            if self.readonly:
                 if cn.to_be_added:
                     logger.info("TO BE ADDED:\n%s" % cn)
 
@@ -1650,7 +1615,7 @@ class KCC(object):
         # here, but using vertex seems to make more sense. That is,
         # the docs want this:
         #
-        #bh = self.get_bridgehead(vertex.site, vertex.part, transport,
+        #bh = self.get_bridgehead(local_vertex.site, vertex.part, transport,
         #                         local_vertex.is_black(), detect_failed)
         #
         # TODO WHY?????
@@ -1658,39 +1623,32 @@ class KCC(object):
         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
+        if vertex in graph.connected_vertices:
+            t_guid = str(self.ip_transport.guid)
 
-            partial_replica_okay = vertex.is_black()
-            bh = self.get_bridgehead(vertex.site, vertex.part, transport,
-                                     partial_replica_okay, detect_failed)
+            bh = self.get_bridgehead(vertex.site, vertex.part,
+                                     self.ip_transport,
+                                     vertex.is_black(), detect_failed)
             if bh is None:
-                found_failed = True
-                continue
-
-            vertex.accept_red_red.append(t_guid)
-            vertex.accept_black.append(t_guid)
+                if vertex.site.is_rodc_site():
+                    vertex.accept_red_red.append(t_guid)
+                else:
+                    found_failed = True
+            else:
+                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")
+        # Add additional transport to ensure 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
+        """Create intersite NTDSConnections as needed by a partition
+
+        Construct an NC replica graph for the NC identified by
         the given crossRef, then create any additional nTDSConnection
         objects required.
 
@@ -1703,7 +1661,7 @@ class KCC(object):
         Modifies self.kept_connections by adding any connections
         deemed to be "in use".
 
-        ::returns: (all_connected, found_failed_dc)
+        :return: (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
@@ -1712,9 +1670,9 @@ class KCC(object):
         all_connected = True
         found_failed = False
 
-        logger.debug("create_connections(): enter\n"
-                     "\tpartdn=%s\n\tdetect_failed=%s" %
-                     (part.nc_dnstr, detect_failed))
+        DEBUG_FN("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
@@ -1728,7 +1686,7 @@ class KCC(object):
 
         for v in graph.vertices:
             v.color_vertex()
-            if self.add_transports(v, my_vertex, graph, False):
+            if self.add_transports(v, my_vertex, graph, detect_failed):
                 found_failed = True
 
         # No NC replicas for this NC in the site of the local DC,
@@ -1740,8 +1698,8 @@ class KCC(object):
                                                           self.my_site,
                                                           label=part.partstr)
 
-        logger.debug("%s Number of components: %d" %
-                     (part.nc_dnstr, n_components))
+        DEBUG_FN("%s Number of components: %d" %
+                 (part.nc_dnstr, n_components))
         if n_components > 1:
             all_connected = False
 
@@ -1790,16 +1748,12 @@ class KCC(object):
                                           partial_ok, detect_failed)
             # TODO
             if lbh is None:
-                DEBUG_RED("DISASTER! lbh is None")
+                debug.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)
+            DEBUG_FN("lsite: %s\nrsite: %s" % (lsite, rsite))
+            DEBUG_FN("vertices %s" % (e.vertices,))
+            debug.DEBUG_BLUE("bridgeheads\n%s\n%s\n%s" % (lbh, rbh, "-" * 70))
 
             sitelink = e.site_link
             if sitelink is None:
@@ -1816,7 +1770,9 @@ class KCC(object):
         return all_connected, found_failed
 
     def create_intersite_connections(self):
-        """Computes an NC replica graph for each NC replica that "should be
+        """Create NTDSConnections as necessary for all partitions.
+
+        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
@@ -1827,8 +1783,8 @@ class KCC(object):
         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.
+        :return: True if spanning trees were created for all NC replica
+                 graphs, otherwise False.
         """
         all_connected = True
         self.kept_connections = set()
@@ -1871,15 +1827,22 @@ class KCC(object):
 
         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.
+    def intersite(self, ping):
+        """Generate the inter-site KCC replica graph and nTDSConnections
+
+        As per MS-ADTS 6.2.2.3.
+
+        If self.readonly is False, the connections are added to self.samdb.
 
-        Produces self.kept_connections set of NTDS Connections
-        that should be kept during subsequent pruning process.
+        Produces self.kept_connections which is a set of NTDS
+        Connections that should be kept during subsequent pruning
+        process.
 
-        ::return (True or False):  (True) if the produced NC replica
+        After this has run, all sites should be connected in a minimum
+        spanning tree.
+
+        :param ping: An oracle function of remote site availability
+        :return (True or False):  (True) if the produced NC replica
             graph connects all sites that need to be connected
         """
 
@@ -1888,26 +1851,26 @@ class KCC(object):
         mysite = self.my_site
         all_connected = True
 
-        logger.debug("intersite(): enter")
+        DEBUG_FN("intersite(): enter")
 
         # Determine who is the ISTG
-        if opts.readonly:
+        if self.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)
+            DEBUG_FN("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)
+            DEBUG_FN("intersite(): exit not istg all_connected=%d" %
+                     all_connected)
             return all_connected
 
-        self.merge_failed_links()
+        self.merge_failed_links(ping)
 
         # 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
@@ -1918,13 +1881,19 @@ class KCC(object):
 
         all_connected = self.create_intersite_connections()
 
-        logger.debug("intersite(): exit all_connected=%d" % all_connected)
+        DEBUG_FN("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.
+    # This function currently does no actions. The reason being that we cannot
+    # perform modifies in this way on the RODC.
+    def update_rodc_connection(self, ro=True):
+        """Updates the RODC NTFRS connection object.
+
+        If the local DSA is not an RODC, this does nothing.
         """
+        if not self.my_dsa.is_ro():
+            return
+
         # 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
@@ -1936,9 +1905,6 @@ class KCC(object):
         # 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
@@ -1954,20 +1920,21 @@ class KCC(object):
                 con.schedule = cn2.schedule
                 con.to_be_modified = True
 
-            self.my_dsa.commit_connections(self.samdb, ro=opts.readonly)
+            self.my_dsa.commit_connections(self.samdb, ro=ro)
 
     def intrasite_max_node_edges(self, node_count):
-        """Returns the maximum number of edges directed to a node in
-        the intrasite replica graph.
+        """Find the maximum number of edges directed to an intrasite node
 
-        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)
+        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).
+        2 * m*m - 2 * m + 3). We think in terms of n because that is
+        the number of extra connections over the double directed ring
+        that exists by default.
 
         edges  n   nodecount
           2    0    7
@@ -1986,6 +1953,9 @@ class KCC(object):
         guarantee holds at e.g. 15 nodes in degenerate cases, but
         those are quite unlikely given the extra edges are randomly
         arranged.
+
+        :param node_count: the number of nodes in the site
+        "return: The desired maximum number of connections
         """
         n = 0
         while True:
@@ -1999,7 +1969,21 @@ class KCC(object):
 
     def construct_intrasite_graph(self, site_local, dc_local,
                                   nc_x, gc_only, detect_stale):
-        # [MS-ADTS] 6.2.2.2
+        """Create an intrasite graph using given parameters
+
+        This might be called a number of times per site with different
+        parameters.
+
+        Based on [MS-ADTS] 6.2.2.2
+
+        :param site_local: site for which we are working
+        :param dc_local: local DC that potentially needs a replica
+        :param nc_x:  naming context (x) that we are testing if it
+                    "should be present" on the local DC
+        :param gc_only: Boolean - only consider global catalog servers
+        :param detect_stale: Boolean - check whether links seems down
+        :return: None
+        """
         # We're using the MS notation names here to allow
         # correlation back to the published algorithm.
         #
@@ -2020,18 +2004,18 @@ class KCC(object):
         # 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)
+        debug.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)
+            debug.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
@@ -2243,8 +2227,8 @@ class KCC(object):
         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:
+        do_dot_files = self.dot_file_dir is not None and self.debug
+        if self.verify or do_dot_files:
             dot_edges = []
             dot_vertices = set()
             for v1 in graph_list:
@@ -2253,14 +2237,31 @@ class KCC(object):
                     dot_edges.append((v2, v1.dsa_dnstr))
                     dot_vertices.add(v2)
 
-            verify_properties = ('connected', 'directed_double_ring_or_small')
+            verify_properties = ('connected',)
             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)
+                           verify=self.verify,
+                           dot_file_dir=self.dot_file_dir,
+                           directed=True)
+
+            rw_dot_vertices = set(x for x in dot_vertices
+                                  if not self.get_dsa(x).is_ro())
+            rw_dot_edges = [(a, b) for a, b in dot_edges if
+                            a in rw_dot_vertices and b in rw_dot_vertices]
+            rw_verify_properties = ('connected',
+                                    'directed_double_ring_or_small')
+            verify_and_dot('intrasite_rw_pre_ntdscon', rw_dot_edges,
+                           rw_dot_vertices,
+                           label='%s__%s__%s' % (site_local.site_dnstr,
+                                                 nctype_lut[nc_x.nc_type],
+                                                 nc_x.nc_dnstr),
+                           properties=rw_verify_properties, debug=DEBUG,
+                           verify=self.verify,
+                           dot_file_dir=self.dot_file_dir,
+                           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
@@ -2295,18 +2296,18 @@ class KCC(object):
                               (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.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)
+                    DEBUG("trying to add candidate %s" % other.dsa_dnstr)
+                    if not tnode.add_edge_from(other.dsa_dnstr):
+                        debug.DEBUG_RED("could not add %s" % other.dsa_dnstr)
                     candidates.remove(other)
             else:
                 DEBUG_FN("not adding links to %s: nodes %s, links is %s/%s" %
@@ -2314,15 +2315,15 @@ class KCC(object):
                           tnode.max_edges))
 
             # Print the graph node in debug mode
-            logger.debug("%s" % tnode)
+            DEBUG_FN("%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)
+                tnode.add_connections_from_edges(dc_local, self.ip_transport)
 
-        if opts.verify or do_dot_files:
+        if self.verify or do_dot_files:
             dot_edges = []
             dot_vertices = set()
             for v1 in graph_list:
@@ -2331,24 +2332,49 @@ class KCC(object):
                     dot_edges.append((v2, v1.dsa_dnstr))
                     dot_vertices.add(v2)
 
-            verify_properties = ('connected', 'directed_double_ring_or_small')
+            verify_properties = ('connected',)
             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)
+                           verify=self.verify,
+                           dot_file_dir=self.dot_file_dir,
+                           directed=True)
+
+            rw_dot_vertices = set(x for x in dot_vertices
+                                  if not self.get_dsa(x).is_ro())
+            rw_dot_edges = [(a, b) for a, b in dot_edges if
+                            a in rw_dot_vertices and b in rw_dot_vertices]
+            rw_verify_properties = ('connected',
+                                    'directed_double_ring_or_small')
+            verify_and_dot('intrasite_rw_post_ntdscon', rw_dot_edges,
+                           rw_dot_vertices,
+                           label='%s__%s__%s' % (site_local.site_dnstr,
+                                                 nctype_lut[nc_x.nc_type],
+                                                 nc_x.nc_dnstr),
+                           properties=rw_verify_properties, debug=DEBUG,
+                           verify=self.verify,
+                           dot_file_dir=self.dot_file_dir,
+                           directed=True)
 
     def intrasite(self):
-        """The head method for generating the intra-site KCC replica
-        connection graph and attendant nTDSConnection objects
-        in the samdb
+        """Generate the intrasite KCC connections
+
+        As per MS-ADTS 6.2.2.2.
+
+        If self.readonly is False, the connections are added to self.samdb.
+
+        After this call, all DCs in each site with more than 3 DCs
+        should be connected in a bidirectional ring. If a site has 2
+        DCs, they will bidirectionally connected. Sites with many DCs
+        may have arbitrary extra connections.
+
+        :return: None
         """
-        # Retrieve my DSA
         mydsa = self.my_dsa
 
-        logger.debug("intrasite(): enter")
+        DEBUG_FN("intrasite(): enter")
 
         # Test whether local site has topology disabled
         mysite = self.my_site
@@ -2358,7 +2384,7 @@ class KCC(object):
         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)
+                debug.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():
@@ -2366,7 +2392,7 @@ class KCC(object):
                                            detect_stale)
             for connect in mydsa.connect_table.values():
                 if connect.to_be_added:
-                    DEBUG_BLUE("TO BE ADDED:\n%s" % connect)
+                    debug.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
@@ -2374,7 +2400,7 @@ class KCC(object):
         # 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)
+                debug.DEBUG_YELLOW("TO BE ADDED:\n%s" % connect)
 
         # Do it again, with gc_only True
         for partdn, part in self.part_table.items():
@@ -2390,7 +2416,7 @@ class KCC(object):
         # 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)
+                debug.DEBUG_BLUE("TO BE ADDED:\n%s" % connect)
 
         # Loop thru all the partitions.
         for partdn, part in self.part_table.items():
@@ -2403,27 +2429,14 @@ class KCC(object):
         # 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)
+                debug.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)
+        self._commit_changes(mydsa)
 
     def list_dsas(self):
         """Compile a comprehensive list of DSA DNs
@@ -2441,7 +2454,7 @@ class KCC(object):
 
         self.load_all_sites()
         self.load_all_partitions()
-        self.load_all_transports()
+        self.load_ip_transport()
         self.load_all_sitelinks()
         dsas = []
         for site in self.site_table.values():
@@ -2449,21 +2462,35 @@ class KCC(object):
                          for dsa in site.dsa_table.values()])
         return dsas
 
-    def load_samdb(self, dburl, lp, creds):
+    def load_samdb(self, dburl, lp, creds, force=False):
         """Load the database using an url, loadparm, and credentials
 
+        If force is False, the samdb won't be reloaded if it already
+        exists.
+
         :param dburl: a database url.
         :param lp: a loadparm object.
-        :param cred: a Credentials object.
+        :param creds: a Credentials object.
+        :param force: a boolean indicating whether to overwrite.
+
         """
-        self.samdb = SamDB(url=dburl,
-                           session_info=system_session(),
-                           credentials=creds, lp=lp)
+        if force or self.samdb is None:
+            try:
+                self.samdb = SamDB(url=dburl,
+                                   session_info=system_session(),
+                                   credentials=creds, lp=lp)
+            except ldb.LdbError, (num, msg):
+                raise KCCError("Unable to open sam database %s : %s" %
+                               (dburl, msg))
 
     def plot_all_connections(self, basename, verify_properties=()):
-        verify = verify_properties and opts.verify
-        plot = opts.dot_files
-        if not (verify or plot):
+        """Helper function to plot and verify NTDSConnections
+
+        :param basename: an identifying string to use in filenames and logs.
+        :param verify_properties: properties to verify (default empty)
+        """
+        verify = verify_properties and self.verify
+        if not verify and self.dot_file_dir is None:
             return
 
         dot_edges = []
@@ -2485,26 +2512,32 @@ class KCC(object):
                 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,
+                       label=self.my_dsa_dnstr,
+                       properties=verify_properties, debug=DEBUG,
+                       verify=verify, dot_file_dir=self.dot_file_dir,
                        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
+            forget_local_links=False, forget_intersite_links=False,
+            attempt_live_connections=False):
+        """Perform a KCC run, possibly updating repsFrom topology
+
+        :param dburl: url of the database to work with.
+        :param lp: a loadparm object.
+        :param creds: a Credentials object.
+        :param forced_local_dsa: pretend to be on the DSA with this dn_str
+        :param forget_local_links: calculate as if no connections existed
+               (boolean, default False)
+        :param forget_intersite_links: calculate with only intrasite connection
+               (boolean, default False)
+        :param attempt_live_connections: attempt to connect to remote DSAs to
+               determine link availability (boolean, default False)
+        :return: 1 on error, 0 otherwise
         """
-        # 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
+            DEBUG_FN("samdb is None; let's load it from %s" % (dburl,))
+            self.load_samdb(dburl, lp, creds, force=False)
 
         if forced_local_dsa:
             self.samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" %
@@ -2517,10 +2550,10 @@ class KCC(object):
 
             self.load_all_sites()
             self.load_all_partitions()
-            self.load_all_transports()
+            self.load_ip_transport()
             self.load_all_sitelinks()
 
-            if opts.verify or opts.dot_files:
+            if self.verify or self.dot_file_dir is not None:
                 guid_to_dnstr = {}
                 for site in self.site_table.values():
                     guid_to_dnstr.update((str(dsa.dsa_guid), dnstr)
@@ -2537,8 +2570,8 @@ class KCC(object):
 
                 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)
+                               properties=(), debug=DEBUG, verify=self.verify,
+                               dot_file_dir=self.dot_file_dir)
 
                 dot_edges = []
                 for site in self.site_table.values():
@@ -2553,8 +2586,8 @@ class KCC(object):
 
                 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)
+                               properties=(), debug=DEBUG, verify=self.verify,
+                               dot_file_dir=self.dot_file_dir)
 
                 dot_edges = []
                 for link in self.sitelink_table.values():
@@ -2564,36 +2597,50 @@ class KCC(object):
                 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)
+                               debug=DEBUG, verify=self.verify,
+                               dot_file_dir=self.dot_file_dir)
 
             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()}
+                    dsa.connect_table = dict((k, v) for k, v in
+                                             dsa.connect_table.items()
+                                             if v.is_rodc_topology() or
+                                             (v.from_dnstr not in
+                                              self.my_site.dsa_table))
                 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()}
+                        dsa.connect_table = dict((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')
+
+            if attempt_live_connections:
+                # Encapsulates lp and creds in a function that
+                # attempts connections to remote DSAs.
+                def ping(self, dnsname):
+                    try:
+                        drs_utils.drsuapi_connect(dnsname, self.lp, self.creds)
+                    except drs_utils.drsException:
+                        return False
+                    return True
+            else:
+                ping = None
             # 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()
+            self.refresh_failed_links_connections(ping)
 
             # Step 2
             self.intrasite()
 
             # Step 3
-            all_connected = self.intersite()
+            all_connected = self.intersite(ping)
 
             # Step 4
             self.remove_unneeded_ntdsconn(all_connected)
@@ -2607,11 +2654,12 @@ class KCC(object):
             # Step 7
             self.update_rodc_connection()
 
-            if opts.verify or opts.dot_files:
+            if self.verify or self.dot_file_dir is not None:
                 self.plot_all_connections('dsa_final',
-                                          ('connected', 'forest_of_rings'))
+                                          ('connected',))
 
-                DEBUG_MAGENTA("there are %d dsa guids" % len(guid_to_dnstr))
+                debug.DEBUG_MAGENTA("there are %d dsa guids" %
+                                    len(guid_to_dnstr))
 
                 dot_edges = []
                 edge_colors = []
@@ -2625,8 +2673,8 @@ class KCC(object):
 
                 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,
+                               properties=(), debug=DEBUG, verify=self.verify,
+                               dot_file_dir=self.dot_file_dir,
                                edge_colors=edge_colors)
 
                 dot_edges = []
@@ -2642,17 +2690,16 @@ class KCC(object):
 
                 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)
+                               properties=(), debug=DEBUG, verify=self.verify,
+                               dot_file_dir=self.dot_file_dir)
 
         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.
+    def import_ldif(self, dburl, lp, ldif_file, forced_local_dsa=None):
+        """Import relevant objects and attributes from an LDIF file.
 
         The point of this function is to allow a programmer/debugger to
         import an LDIF file with non-security relevent information that
@@ -2663,19 +2710,21 @@ class KCC(object):
         same between different OSes and algorithms.
 
         :param dburl: path to the temporary abbreviated db to create
+        :param lp: a loadparm object.
         :param ldif_file: path to the ldif file to import
+        :param forced_local_dsa: perform KCC from this DSA's point of view
+        :return: zero on success, 1 on error
         """
         try:
-            self.samdb = ldif_utils.ldif_to_samdb(dburl, lp, ldif_file,
-                                                  opts.forced_local_dsa)
-        except ldif_utils.LdifError, e:
-            print e
+            self.samdb = ldif_import_export.ldif_to_samdb(dburl, lp, ldif_file,
+                                                          forced_local_dsa)
+        except ldif_import_export.LdifError, e:
+            logger.critical(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.
+        """Save KCC relevant details to an ldif file
 
         The point of this function is to allow a programmer/debugger to
         extract an LDIF file with non-security relevent information from
@@ -2686,714 +2735,15 @@ class KCC(object):
         is computationally the same between different OSes and algorithms.
 
         :param dburl: LDAP database URL to extract info from
+        :param lp: a loadparm object.
+        :param cred: a Credentials object.
         :param ldif_file: output LDIF file name to create
+        :return: zero on success, 1 on error
         """
         try:
-            ldif_utils.samdb_to_ldif_file(self.samdb, dburl, lp, creds,
-                                          ldif_file)
-        except ldif_utils.LdifError, e:
-            print e
+            ldif_import_export.samdb_to_ldif_file(self.samdb, dburl, lp, creds,
+                                                  ldif_file)
+        except ldif_import_export.LdifError, e:
+            logger.critical(e)
             return 1
         return 0
-
-##################################################
-# Global Functions
-##################################################
-
-
-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 = [x.site.site_dnstr for x 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):
-    """Helper to sort NCReplicas by their DSA guids
-
-    The guids need to be sorted in their NDR form.
-
-    :param rep1: An NC replica
-    :param rep2: Another replica
-    :return: -1, 0, or 1, indicating sort order.
-    """
-    return cmp(ndr_pack(rep1.rep_dsa_guid), ndr_pack(rep2.rep_dsa_guid))
-
-
-def sort_dsa_by_gc_and_guid(dsa1, dsa2):
-    """Helper to sort DSAs by guid global catalog status
-
-    GC DSAs come before non-GC DSAs, other than that, the guids are
-    sorted in NDR form.
-
-    :param dsa1: A DSA object
-    :param dsa2: Another DSA
-    :return: -1, 0, or 1, indicating sort order.
-    """
-    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():
-    """Can the KCC use SMTP replication?
-
-    Currently always returns false because Samba doesn't implement
-    SMTP transfer for NC changes between DCs.
-
-    :return: Boolean (always False)
-    """
-    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()
-
-    #XXX expected_num_tree_edges is never used
-    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, rng_seed=None):
-    kcc = KCC()
-    kcc.load_samdb(opts.dburl, lp, creds)
-    dsas = kcc.list_dsas()
-    needed_parts = {}
-    current_parts = {}
-
-    guid_to_dnstr = {}
-    for site in kcc.site_table.values():
-        guid_to_dnstr.update((str(dsa.dsa_guid), dnstr)
-                             for dnstr, dsa in site.dsa_table.items())
-
-    dot_edges = []
-    dot_vertices = []
-    colours = []
-    vertex_colours = []
-
-    for dsa_dn in dsas:
-        if rng_seed:
-            random.seed(rng_seed)
-        kcc = KCC()
-        kcc.run(opts.dburl, lp, creds, forced_local_dsa=dsa_dn,
-                forget_local_links=opts.forget_local_links,
-                forget_intersite_links=opts.forget_intersite_links)
-
-        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)):
-            for part, nc_rep in rep_table.items():
-                edges = rep_parts.setdefault(part, [])
-                for reps_from in nc_rep.rep_repsFrom:
-                    source = guid_to_dnstr[str(reps_from.source_dsa_obj_guid)]
-                    dest = guid_to_dnstr[str(nc_rep.rep_dsa_guid)]
-                    edges.append((source, dest))
-
-        for site in kcc.site_table.values():
-            for dsa in site.dsa_table.values():
-                if dsa.is_ro():
-                    vertex_colours.append('#cc0000')
-                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')
-                    else:
-                        colours.append('blue')
-                    dot_edges.append((con.from_dnstr, dsa.dsa_dnstr))
-
-    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,
-                   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('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))
-
-
-##################################################
-# samba_kcc entry point
-##################################################
-
-parser = optparse.OptionParser("samba_kcc [options]")
-sambaopts = options.SambaOptions(parser)
-credopts = options.CredentialsOptions(parser)
-
-parser.add_option_group(sambaopts)
-parser.add_option_group(credopts)
-parser.add_option_group(options.VersionOptions(parser))
-
-parser.add_option("--readonly", default=False,
-                  help="compute topology but do not update database",
-                  action="store_true")
-
-parser.add_option("--debug",
-                  help="debug output",
-                  action="store_true")
-
-parser.add_option("--verify",
-                  help="verify that assorted invariants are kept",
-                  action="store_true")
-
-parser.add_option("--list-verify-tests",
-                  help=("list what verification actions are available "
-                        "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("--seed",
-                  help="random number seed",
-                  type=int)
-
-parser.add_option("--importldif",
-                  help="import topology ldif file",
-                  type=str, metavar="<file>")
-
-parser.add_option("--exportldif",
-                  help="export topology ldif file",
-                  type=str, metavar="<file>")
-
-parser.add_option("-H", "--URL",
-                  help="LDB URL for database or target server",
-                  type=str, metavar="<URL>", dest="dburl")
-
-parser.add_option("--tmpdb",
-                  help="schemaless database file to create for ldif import",
-                  type=str, metavar="<file>")
-
-parser.add_option("--now",
-                  help=("assume current time is this ('YYYYmmddHHMMSS[tz]',"
-                        " default: system time)"),
-                  type=str, metavar="<date>")
-
-parser.add_option("--forced-local-dsa",
-                  help="run calculations assuming the DSA is this DN",
-                  type=str, metavar="<DSA>")
-
-parser.add_option("--attempt-live-connections", default=False,
-                  help="Attempt to connect to other DSAs to test links",
-                  action="store_true")
-
-parser.add_option("--list-valid-dsas", default=False,
-                  help=("Print a list of DSA dnstrs that could be"
-                        " used in --forced-local-dsa"),
-                  action="store_true")
-
-parser.add_option("--test-all-reps-from", default=False,
-                  help="Create and verify a graph of reps-from for every DSA",
-                  action="store_true")
-
-parser.add_option("--forget-local-links", default=False,
-                  help="pretend not to know the existing local topology",
-                  action="store_true")
-
-parser.add_option("--forget-intersite-links", default=False,
-                  help="pretend not to know the existing intersite topology",
-                  action="store_true")
-
-
-opts, args = parser.parse_args()
-
-
-if opts.list_verify_tests:
-    list_verify_tests()
-    sys.exit(0)
-
-if opts.debug:
-    logger.setLevel(logging.DEBUG)
-elif opts.readonly:
-    logger.setLevel(logging.INFO)
-else:
-    logger.setLevel(logging.WARNING)
-
-# initialize seed from optional input parameter
-if opts.seed:
-    random.seed(opts.seed)
-else:
-    random.seed(0xACE5CA11)
-
-if opts.now:
-    for timeformat in ("%Y%m%d%H%M%S%Z", "%Y%m%d%H%M%S"):
-        try:
-            now_tuple = time.strptime(opts.now, timeformat)
-            break
-        except ValueError:
-            pass
-    else:
-        # else happens if break doesn't --> no match
-        print >> sys.stderr, "could not parse time '%s'" % opts.now
-        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
-    rng_seed = opts.seed or 0xACE5CA11
-    test_all_reps_from(lp, creds, rng_seed=rng_seed)
-    sys.exit()
-
-# Instantiate Knowledge Consistency Checker and perform run
-kcc = KCC()
-
-if opts.exportldif:
-    rc = kcc.export_ldif(opts.dburl, lp, creds, opts.exportldif)
-    sys.exit(rc)
-
-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)
-
-    rc = kcc.import_ldif(opts.tmpdb, lp, creds, opts.importldif)
-    if rc != 0:
-        sys.exit(rc)
-
-if opts.list_valid_dsas:
-    kcc.load_samdb(opts.dburl, lp, creds)
-    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)
-    sys.exit(rc)
-
-except GraphError, e:
-    print e
-    sys.exit(1)