KCC: shift samba.kcc intersite functions to samba.kcc.graph
authorDouglas Bagnall <douglas.bagnall@catalyst.net.nz>
Wed, 10 Jun 2015 04:42:54 +0000 (16:42 +1200)
committerAndrew Bartlett <abartlet@samba.org>
Fri, 12 Jun 2015 04:57:13 +0000 (06:57 +0200)
So samba.kcc.graph is the place to look for everything intersite.

Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Reviewed-by: Garming Sam <garming@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
python/samba/kcc/__init__.py
python/samba/kcc/graph.py

index 897baa6b7e78057eaa79d7543d0518b8c33025c9..d2c99c98a1318d329ba2bcaad789541f87a5bd50 100644 (file)
@@ -61,6 +61,7 @@ from samba.kcc.kcc_utils import convert_schedule_to_repltimes
 from samba.kcc.graph_utils import verify_and_dot
 from samba import ldif_utils
 
+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
@@ -1186,52 +1187,16 @@ 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))
+        # 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
 
-        g.connected_vertices = connected_vertices
+        g = setup_graph(part, self.site_table, self.transport_table,
+                        self.sitelink_table, bridges_required)
 
-        #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):
@@ -2753,394 +2718,3 @@ class KCC(object):
             print e
             return 1
         return 0
-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 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)
-
index 66fe45ae23d46c5ab91baa28cc4e8926a931f0c0..431ff48cdc71e7a085687d8fd4a18c34a9dbf227 100644 (file)
@@ -33,6 +33,451 @@ from samba.kcc.kcc_utils import ReplInfo, combine_repl_info, total_schedule
 from samba.kcc.kcc_utils import convert_schedule_to_repltimes
 
 
+def get_spanning_tree_edges(graph, my_site, label=None, verify=False,
+                            dot_files=False):
+    # 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 verify or 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 dot_files:
+                write_dot_file('edgeset_%s' % (edgeType,), graph_edges,
+                               vertices=graph_nodes, label=label)
+
+            if 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 verify or 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=verify,
+                       dot_files=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 verify or 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=verify,
+                       dot_files=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 verify or 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=verify,
+                       directed=True,
+                       dot_files=dot_files)
+
+    # count the components
+    return edge_list, components
+
+
+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 setup_graph(part, site_table, transport_table, sitelink_table,
+                bridges_required):
+    """Set up a GRAPH, populated with a VERTEX for each site
+    object, a MULTIEDGE for each siteLink object, and a
+    MUTLIEDGESET for each siteLinkBridge object (or implied
+    siteLinkBridge).
+
+    ::returns: a new graph
+    """
+    guid_to_vertex = {}
+    # Create graph
+    g = IntersiteGraph()
+    # Add vertices
+    for site_guid, site in site_table.items():
+        vertex = Vertex(site, part)
+        vertex.guid = site_guid
+        vertex.ndrpacked_guid = ndr_pack(site.site_guid)
+        g.vertices.add(vertex)
+        guid_vertices = guid_to_vertex.setdefault(site_guid, [])
+        guid_vertices.append(vertex)
+
+    connected_vertices = set()
+    for transport_guid, transport in 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 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 bridges_required:
+            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
+
+    return g
+
+
 class VertexColor(object):
     (red, black, white, unknown) = range(0, 4)