KCC: let kcc.graph.ReplInfo know its duration
[Samba.git] / python / samba / kcc / graph.py
bloba09f241224665974be33138b2665510580949b73
1 # Graph functions used by KCC intersite
3 # Copyright (C) Dave Craft 2011
4 # Copyright (C) Andrew Bartlett 2015
6 # Andrew Bartlett's alleged work performed by his underlings Douglas
7 # Bagnall and Garming Sam.
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 3 of the License, or
12 # (at your option) any later version.
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with this program. If not, see <http://www.gnu.org/licenses/>.
22 import itertools
23 import heapq
25 from samba.kcc.graph_utils import write_dot_file, verify_and_dot, verify_graph
26 from samba.ndr import ndr_pack
27 from samba.dcerpc import misc
29 from samba.kcc.debug import DEBUG, DEBUG_FN
31 MAX_DWORD = 2 ** 32 - 1
34 class ReplInfo(object):
35 """Represents information about replication
37 NTDSConnections use one representation a replication schedule, and
38 graph vertices use another. This is the Vertex one.
40 """
41 def __init__(self):
42 self.cost = 0
43 self.interval = 0
44 self.options = 0
45 self.schedule = None
46 self.duration = 84 * 8
48 def set_repltimes_from_schedule(self, schedule):
49 """Convert the schedule and calculate duration
51 :param schdule: the schedule to convert
52 """
53 self.schedule = convert_schedule_to_repltimes(schedule)
54 self.duration = total_schedule(self.schedule)
57 def total_schedule(schedule):
58 """Return the total number of 15 minute windows in which the schedule
59 is set to replicate in a week. If the schedule is None it is
60 assumed that the replication will happen in every 15 minute
61 window.
63 This is essentially a bit population count.
64 """
66 if schedule is None:
67 return 84 * 8 # 84 bytes = 84 * 8 bits
69 total = 0
70 for byte in schedule:
71 while byte != 0:
72 total += byte & 1
73 byte >>= 1
74 return total
77 def convert_schedule_to_repltimes(schedule):
78 """Convert NTDS Connection schedule to replTime schedule.
80 Schedule defined in MS-ADTS 6.1.4.5.2
81 ReplTimes defined in MS-DRSR 5.164.
83 "Schedule" has 168 bytes but only the lower nibble of each is
84 significant. There is one byte per hour. Bit 3 (0x08) represents
85 the first 15 minutes of the hour and bit 0 (0x01) represents the
86 last 15 minutes. The first byte presumably covers 12am - 1am
87 Sunday, though the spec doesn't define the start of a week.
89 "ReplTimes" has 84 bytes which are the 168 lower nibbles of
90 "Schedule" packed together. Thus each byte covers 2 hours. Bits 7
91 (i.e. 0x80) is the first 15 minutes and bit 0 is the last. The
92 first byte covers Sunday 12am - 2am (per spec).
94 Here we pack two elements of the NTDS Connection schedule slots
95 into one element of the replTimes list.
97 If no schedule appears in NTDS Connection then a default of 0x11
98 is set in each replTimes slot as per behaviour noted in a Windows
99 DC. That default would cause replication within the last 15
100 minutes of each hour.
102 if schedule is None or schedule.dataArray[0] is None:
103 return [0x11] * 84
105 times = []
106 data = schedule.dataArray[0].slots
108 for i in range(84):
109 times.append((data[i * 2] & 0xF) << 4 | (data[i * 2 + 1] & 0xF))
111 return times
114 # Returns true if schedule intersect
115 def combine_repl_info(info_a, info_b, info_c):
116 """Set a replInfo to be the intersection of two others
118 If there is any overlap in the replication times specified by the
119 first two parameters, the third replInfo parameter is set up with
120 that overlap, and True is returned. If there is no overlap, the
121 third parameter is unchanged and False is returned.
123 :param info_a: An input replInfo object
124 :param info_b: An input replInfo object
125 :param info_c: The result replInfo, set to the intersection of A and B
126 if the intersection is non-empty.
127 :return: True if info_c allows any replication at all, otherwise False
129 info_c.interval = max(info_a.interval, info_b.interval)
130 info_c.options = info_a.options & info_b.options
132 if info_a.schedule is None:
133 info_a.schedule = [0xFF] * 84
134 if info_b.schedule is None:
135 info_b.schedule = [0xFF] * 84
137 new_info = [a & b for a, b in zip(info_a.schedule, info_b.schedule)]
139 if not any(new_info):
140 return False
142 info_c.schedule = new_info
144 # Truncate to MAX_DWORD
145 info_c.cost = info_a.cost + info_b.cost
146 if info_c.cost > MAX_DWORD:
147 info_c.cost = MAX_DWORD
149 info_c.duration = total_schedule(new_info)
150 return True
153 def get_spanning_tree_edges(graph, my_site, label=None, verify=False,
154 dot_file_dir=None):
155 """Find edges for the intersite graph
157 From MS-ADTS 6.2.2.3.4.4
159 :param graph: a kcc.kcc_utils.Graph object
160 :param my_site: the topology generator's site
161 :param label: a label for use in dot files and verification
162 :param verify: if True, try to verify that graph properties are correct
163 :param dot_file_dir: if not None, write Graphviz dot files here
165 # Phase 1: Run Dijkstra's to get a list of internal edges, which are
166 # just the shortest-paths connecting colored vertices
168 internal_edges = set()
170 for e_set in graph.edge_set:
171 edgeType = None
172 for v in graph.vertices:
173 v.edges = []
175 # All con_type in an edge set is the same
176 for e in e_set.edges:
177 edgeType = e.con_type
178 for v in e.vertices:
179 v.edges.append(e)
181 if verify or dot_file_dir is not None:
182 graph_edges = [(a.site.site_dnstr, b.site.site_dnstr)
183 for a, b in
184 itertools.chain(
185 *(itertools.combinations(edge.vertices, 2)
186 for edge in e_set.edges))]
187 graph_nodes = [v.site.site_dnstr for v in graph.vertices]
189 if dot_file_dir is not None:
190 write_dot_file('edgeset_%s' % (edgeType,), graph_edges,
191 vertices=graph_nodes, label=label)
193 if verify:
194 verify_graph('spanning tree edge set %s' % edgeType,
195 graph_edges, vertices=graph_nodes,
196 properties=('complete', 'connected'),
197 debug=DEBUG)
199 # Run dijkstra's algorithm with just the red vertices as seeds
200 # Seed from the full replicas
201 dijkstra(graph, edgeType, False)
203 # Process edge set
204 process_edge_set(graph, e_set, internal_edges)
206 # Run dijkstra's algorithm with red and black vertices as the seeds
207 # Seed from both full and partial replicas
208 dijkstra(graph, edgeType, True)
210 # Process edge set
211 process_edge_set(graph, e_set, internal_edges)
213 # All vertices have root/component as itself
214 setup_vertices(graph)
215 process_edge_set(graph, None, internal_edges)
217 if verify or dot_file_dir is not None:
218 graph_edges = [(e.v1.site.site_dnstr, e.v2.site.site_dnstr)
219 for e in internal_edges]
220 graph_nodes = [v.site.site_dnstr for v in graph.vertices]
221 verify_properties = ('multi_edge_forest',)
222 verify_and_dot('prekruskal', graph_edges, graph_nodes, label=label,
223 properties=verify_properties, debug=DEBUG,
224 verify=verify, dot_file_dir=dot_file_dir)
226 # Phase 2: Run Kruskal's on the internal edges
227 output_edges, components = kruskal(graph, internal_edges)
229 # This recalculates the cost for the path connecting the
230 # closest red vertex. Ignoring types is fine because NO
231 # suboptimal edge should exist in the graph
232 dijkstra(graph, "EDGE_TYPE_ALL", False) # TODO rename
233 # Phase 3: Process the output
234 for v in graph.vertices:
235 if v.is_red():
236 v.dist_to_red = 0
237 else:
238 v.dist_to_red = v.repl_info.cost
240 if verify or dot_file_dir is not None:
241 graph_edges = [(e.v1.site.site_dnstr, e.v2.site.site_dnstr)
242 for e in internal_edges]
243 graph_nodes = [v.site.site_dnstr for v in graph.vertices]
244 verify_properties = ('multi_edge_forest',)
245 verify_and_dot('postkruskal', graph_edges, graph_nodes,
246 label=label, properties=verify_properties,
247 debug=DEBUG, verify=verify,
248 dot_file_dir=dot_file_dir)
250 # Ensure only one-way connections for partial-replicas,
251 # and make sure they point the right way.
252 edge_list = []
253 for edge in output_edges:
254 # We know these edges only have two endpoints because we made
255 # them.
256 v, w = edge.vertices
257 if v.site is my_site or w.site is my_site:
258 if (((v.is_black() or w.is_black()) and
259 v.dist_to_red != MAX_DWORD)):
260 edge.directed = True
262 if w.dist_to_red < v.dist_to_red:
263 edge.vertices[:] = w, v
264 edge_list.append(edge)
266 if verify or dot_file_dir is not None:
267 graph_edges = [[x.site.site_dnstr for x in e.vertices]
268 for e in edge_list]
269 #add the reverse edge if not directed.
270 graph_edges.extend([x.site.site_dnstr
271 for x in reversed(e.vertices)]
272 for e in edge_list if not e.directed)
273 graph_nodes = [x.site.site_dnstr for x in graph.vertices]
274 verify_properties = ()
275 verify_and_dot('post-one-way-partial', graph_edges, graph_nodes,
276 label=label, properties=verify_properties,
277 debug=DEBUG, verify=verify,
278 directed=True,
279 dot_file_dir=dot_file_dir)
281 # count the components
282 return edge_list, components
285 def create_edge(con_type, site_link, guid_to_vertex):
286 """Set up a MultiEdge for the intersite graph
288 A MultiEdge can have multiple vertices.
290 From MS-ADTS 6.2.2.3.4.4
292 :param con_type: a transport type GUID
293 :param site_link: a kcc.kcc_utils.SiteLink object
294 :param guid_to_vertex: a mapping between GUIDs and vertices
295 :return: a MultiEdge
297 e = MultiEdge()
298 e.site_link = site_link
299 e.vertices = []
300 for site_guid in site_link.site_list:
301 if str(site_guid) in guid_to_vertex:
302 e.vertices.extend(guid_to_vertex.get(str(site_guid)))
303 e.repl_info.cost = site_link.cost
304 e.repl_info.options = site_link.options
305 e.repl_info.interval = site_link.interval
306 e.repl_info.set_repltimes_from_schedule(site_link.schedule)
307 e.con_type = con_type
308 e.directed = False
309 return e
312 def create_auto_edge_set(graph, transport_guid):
313 """Set up an automatic MultiEdgeSet for the intersite graph
315 From within MS-ADTS 6.2.2.3.4.4
317 :param graph: the intersite graph object
318 :param transport_guid: a transport type GUID
319 :return: a MultiEdgeSet
321 e_set = MultiEdgeSet()
322 # use a NULL guid, not associated with a SiteLinkBridge object
323 e_set.guid = misc.GUID()
324 for site_link in graph.edges:
325 if site_link.con_type == transport_guid:
326 e_set.edges.append(site_link)
328 return e_set
331 def create_edge_set(graph, transport, site_link_bridge):
332 # TODO not implemented - need to store all site link bridges
333 raise NotImplementedError("We don't create fancy edge sets")
336 def setup_vertices(graph):
337 """Initialise vertices in the graph for the Dijkstra's run.
339 :param graph: an IntersiteGraph object
340 :return: None
342 for v in graph.vertices:
343 if v.is_white():
344 v.repl_info.cost = MAX_DWORD
345 v.root = None
346 v.component_id = None
347 else:
348 v.repl_info.cost = 0
349 v.root = v
350 v.component_id = v
352 v.repl_info.interval = 0
353 v.repl_info.options = 0xFFFFFFFF
354 v.repl_info.schedule = None # TODO highly suspicious
355 v.repl_info.duration = 84 * 8
356 v.demoted = False
359 def dijkstra(graph, edge_type, include_black):
360 """Perform Dijkstra's algorithm on an intersite graph.
362 :param graph: an IntersiteGraph object
363 :param edge_type: a transport type GUID
364 :param include_black: boolean, whether to include black vertices
365 :return: None
367 queue = setup_dijkstra(graph, edge_type, include_black)
368 while len(queue) > 0:
369 cost, guid, vertex = heapq.heappop(queue)
370 for edge in vertex.edges:
371 for v in edge.vertices:
372 if v is not vertex:
373 # add new path from vertex to v
374 try_new_path(graph, queue, vertex, edge, v)
377 def setup_dijkstra(graph, edge_type, include_black):
378 """Create a vertex queue for Dijksta's algorithm.
380 :param graph: an IntersiteGraph object
381 :param edge_type: a transport type GUID
382 :param include_black: boolean, whether to include black vertices
383 :return: A heap queue of vertices
385 queue = []
386 setup_vertices(graph)
387 for vertex in graph.vertices:
388 if vertex.is_white():
389 continue
391 if (((vertex.is_black() and not include_black)
392 or edge_type not in vertex.accept_black
393 or edge_type not in vertex.accept_red_red)):
394 vertex.repl_info.cost = MAX_DWORD
395 vertex.root = None # NULL GUID
396 vertex.demoted = True # Demoted appears not to be used
397 else:
398 heapq.heappush(queue, (vertex.repl_info.cost, vertex.guid, vertex))
400 return queue
403 def try_new_path(graph, queue, vfrom, edge, vto):
404 """Helper function for Dijksta's algorithm.
406 :param graph: an IntersiteGraph object
407 :param queue: the empty queue to initialise.
408 :param vfrom: Vertex we are coming from
409 :param edge: an edge to try
410 :param vto: the other Vertex
411 :return: None
413 newRI = ReplInfo()
414 #This function combines the repl_info and checks is that there is
415 # a valid time frame for which replication can actually occur,
416 # despite being adequately connected
417 intersect = combine_repl_info(vfrom.repl_info, edge.repl_info, newRI)
419 # If the new path costs more than the current, then ignore the edge
420 if newRI.cost > vto.repl_info.cost:
421 return
423 # Cheaper or longer schedule goes in the heap
424 if newRI.cost < vto.repl_info.cost and not intersect:
425 return
428 # Cheaper or longer schedule
429 if (newRI.cost < vto.repl_info.cost or
430 newRI.duration > vto.repl_info.duration):
431 vto.root = vfrom.root
432 vto.component_id = vfrom.component_id
433 vto.repl_info = newRI
434 heapq.heappush(queue, (vto.repl_info.cost, vto.guid, vto))
437 def check_demote_vertex(vertex, edge_type):
438 """Demote non-white vertices that accept only white edges
440 This makes them seem temporarily like white vertices.
442 :param vertex: a Vertex()
443 :param edge_type: a transport type GUID
444 :return: None
446 if vertex.is_white():
447 return
449 # Accepts neither red-red nor black edges, demote
450 if ((edge_type not in vertex.accept_black and
451 edge_type not in vertex.accept_red_red)):
452 vertex.repl_info.cost = MAX_DWORD
453 vertex.root = None
454 vertex.demoted = True # Demoted appears not to be used
457 def undemote_vertex(vertex):
458 """Un-demote non-white vertices
460 Set a vertex's to an undemoted state.
462 :param vertex: a Vertex()
463 :return: None
465 if vertex.is_white():
466 return
468 vertex.repl_info.cost = 0
469 vertex.root = vertex
470 vertex.demoted = False
473 def process_edge_set(graph, e_set, internal_edges):
474 """Find internal edges to pass to Kruskal's algorithm
476 :param graph: an IntersiteGraph object
477 :param e_set: an edge set
478 :param internal_edges: a set that internal edges get added to
479 :return: None
481 if e_set is None:
482 for edge in graph.edges:
483 for vertex in edge.vertices:
484 check_demote_vertex(vertex, edge.con_type)
485 process_edge(graph, edge, internal_edges)
486 for vertex in edge.vertices:
487 undemote_vertex(vertex)
488 else:
489 for edge in e_set.edges:
490 process_edge(graph, edge, internal_edges)
493 def process_edge(graph, examine, internal_edges):
494 """Find the set of all vertices touching an edge to examine
496 :param graph: an IntersiteGraph object
497 :param examine: an edge
498 :param internal_edges: a set that internal edges get added to
499 :return: None
501 vertices = []
502 for v in examine.vertices:
503 # Append a 4-tuple of color, repl cost, guid and vertex
504 vertices.append((v.color, v.repl_info.cost, v.ndrpacked_guid, v))
505 # Sort by color, lower
506 DEBUG("vertices is %s" % vertices)
507 vertices.sort()
509 color, cost, guid, bestv = vertices[0]
510 # Add to internal edges an edge from every colored vertex to bestV
511 for v in examine.vertices:
512 if v.component_id is None or v.root is None:
513 continue
515 # Only add edge if valid inter-tree edge - needs a root and
516 # different components
517 if ((bestv.component_id is not None and
518 bestv.root is not None and
519 v.component_id is not None and
520 v.root is not None and
521 bestv.component_id != v.component_id)):
522 add_int_edge(graph, internal_edges, examine, bestv, v)
525 def add_int_edge(graph, internal_edges, examine, v1, v2):
526 """Add edges between compatible red and black vertices
528 Internal edges form the core of the tree -- white and RODC
529 vertices attach to it as leaf nodes. An edge needs to have black
530 or red endpoints with compatible replication schedules to be
531 accepted as an internal edge.
533 Here we examine an edge and add it to the set of internal edges if
534 it looks good.
536 :param graph: the graph object.
537 :param internal_edges: a set of internal edges
538 :param examine: an edge to examine for suitability.
539 :param v1: a Vertex
540 :param v2: the other Vertex
542 root1 = v1.root
543 root2 = v2.root
545 red_red = root1.is_red() and root2.is_red()
547 if red_red:
548 if (examine.con_type not in root1.accept_red_red
549 or examine.con_type not in root2.accept_red_red):
550 return
551 elif (examine.con_type not in root1.accept_black
552 or examine.con_type not in root2.accept_black):
553 return
555 ri = ReplInfo()
556 ri2 = ReplInfo()
558 # Create the transitive replInfo for the two trees and this edge
559 if not combine_repl_info(v1.repl_info, v2.repl_info, ri):
560 return
561 # ri is now initialized
562 if not combine_repl_info(ri, examine.repl_info, ri2):
563 return
565 # Order by vertex guid
566 if root1.ndrpacked_guid > root2.ndrpacked_guid:
567 root1, root2 = root2, root1
569 newIntEdge = InternalEdge(root1, root2, red_red, ri2, examine.con_type,
570 examine.site_link)
572 internal_edges.add(newIntEdge)
575 def kruskal(graph, edges):
576 """Perform Kruskal's algorithm using the given set of edges
578 The input edges are "internal edges" -- between red and black
579 nodes. The output edges are a minimal spanning tree.
581 :param graph: the graph object.
582 :param edges: a set of edges
583 :return: a tuple of a list of edges, and the number of components
585 for v in graph.vertices:
586 v.edges = []
588 components = set([x for x in graph.vertices if not x.is_white()])
589 edges = list(edges)
591 # Sorted based on internal comparison function of internal edge
592 edges.sort()
594 #XXX expected_num_tree_edges is never used
595 expected_num_tree_edges = 0 # TODO this value makes little sense
597 count_edges = 0
598 output_edges = []
599 index = 0
600 while index < len(edges): # TODO and num_components > 1
601 e = edges[index]
602 parent1 = find_component(e.v1)
603 parent2 = find_component(e.v2)
604 if parent1 is not parent2:
605 count_edges += 1
606 add_out_edge(graph, output_edges, e)
607 parent1.component_id = parent2
608 components.discard(parent1)
610 index += 1
612 return output_edges, len(components)
615 def find_component(vertex):
616 """Kruskal helper to find the component a vertex belongs to.
618 :param vertex: a Vertex
619 :return: the Vertex object representing the component
621 if vertex.component_id is vertex:
622 return vertex
624 current = vertex
625 while current.component_id is not current:
626 current = current.component_id
628 root = current
629 current = vertex
630 while current.component_id is not root:
631 n = current.component_id
632 current.component_id = root
633 current = n
635 return root
638 def add_out_edge(graph, output_edges, e):
639 """Kruskal helper to add output edges
641 :param graph: the InterSiteGraph
642 :param output_edges: the list of spanning tree edges
643 :param e: the edge to be added
644 :return: None
646 v1 = e.v1
647 v2 = e.v2
649 # This multi-edge is a 'real' undirected 2-vertex edge with no
650 # GUID. XXX It is not really the same thing at all as the
651 # multi-vertex edges relating to site-links. We shouldn't really
652 # be using the same class or storing them in the same list as the
653 # other ones. But we do. Historical reasons.
654 ee = MultiEdge()
655 ee.directed = False
656 ee.site_link = e.site_link
657 ee.vertices.append(v1)
658 ee.vertices.append(v2)
659 ee.con_type = e.e_type
660 ee.repl_info = e.repl_info
661 output_edges.append(ee)
663 v1.edges.append(ee)
664 v2.edges.append(ee)
667 def setup_graph(part, site_table, transport_guid, sitelink_table,
668 bridges_required):
669 """Set up an IntersiteGraph based on intersite topology
671 The graph will have a Vertex for each site, a MultiEdge for each
672 siteLink object, and a MultiEdgeSet for each siteLinkBridge object
673 (or implied siteLinkBridge).
675 :param part: the partition we are dealing with
676 :param site_table: a mapping of guids to sites (KCC.site_table)
677 :param transport_guid: the GUID of the IP transport
678 :param sitelink_table: a mapping of dnstrs to sitelinks
679 :param bridges_required: boolean
681 :return: a new IntersiteGraph
683 guid_to_vertex = {}
684 # Create graph
685 g = IntersiteGraph()
686 # Add vertices
687 for site_guid, site in site_table.items():
688 vertex = Vertex(site, part)
689 vertex.guid = site_guid
690 vertex.ndrpacked_guid = ndr_pack(site.site_guid)
691 g.vertices.add(vertex)
692 guid_vertices = guid_to_vertex.setdefault(site_guid, [])
693 guid_vertices.append(vertex)
695 connected_vertices = set()
697 for site_link_dn, site_link in sitelink_table.items():
698 new_edge = create_edge(transport_guid, site_link,
699 guid_to_vertex)
700 connected_vertices.update(new_edge.vertices)
701 g.edges.add(new_edge)
703 # If 'Bridge all site links' is enabled and Win2k3 bridges required
704 # is not set
705 # NTDSTRANSPORT_OPT_BRIDGES_REQUIRED 0x00000002
706 # No documentation for this however, ntdsapi.h appears to have:
707 # NTDSSETTINGS_OPT_W2K3_BRIDGES_REQUIRED = 0x00001000
708 if bridges_required:
709 g.edge_set.add(create_auto_edge_set(g, transport_guid))
710 else:
711 # TODO get all site link bridges
712 for site_link_bridge in []:
713 g.edge_set.add(create_edge_set(g, transport_guid,
714 site_link_bridge))
716 g.connected_vertices = connected_vertices
718 return g
721 class VertexColor(object):
722 """Enumeration of vertex colours"""
723 (red, black, white, unknown) = range(0, 4)
726 class Vertex(object):
727 """intersite graph representation of a Site.
729 There is a separate vertex for each partition.
731 :param site: the site to make a vertex of.
732 :param part: the partition.
734 def __init__(self, site, part):
735 self.site = site
736 self.part = part
737 self.color = VertexColor.unknown
738 self.edges = []
739 self.accept_red_red = []
740 self.accept_black = []
741 self.repl_info = ReplInfo()
742 self.root = self
743 self.guid = None
744 self.component_id = self
745 self.demoted = False
746 self.options = 0
747 self.interval = 0
749 def color_vertex(self):
750 """Color to indicate which kind of NC replica the vertex contains
752 # IF s contains one or more DCs with full replicas of the
753 # NC cr!nCName
754 # SET v.Color to COLOR.RED
755 # ELSEIF s contains one or more partial replicas of the NC
756 # SET v.Color to COLOR.BLACK
757 #ELSE
758 # SET v.Color to COLOR.WHITE
760 # set to minimum (no replica)
761 self.color = VertexColor.white
763 for dnstr, dsa in self.site.dsa_table.items():
764 rep = dsa.get_current_replica(self.part.nc_dnstr)
765 if rep is None:
766 continue
768 # We have a full replica which is the largest
769 # value so exit
770 if not rep.is_partial():
771 self.color = VertexColor.red
772 break
773 else:
774 self.color = VertexColor.black
776 def is_red(self):
777 assert(self.color != VertexColor.unknown)
778 return (self.color == VertexColor.red)
780 def is_black(self):
781 assert(self.color != VertexColor.unknown)
782 return (self.color == VertexColor.black)
784 def is_white(self):
785 assert(self.color != VertexColor.unknown)
786 return (self.color == VertexColor.white)
789 class IntersiteGraph(object):
790 """Graph for representing the intersite"""
791 def __init__(self):
792 self.vertices = set()
793 self.edges = set()
794 self.edge_set = set()
795 # All vertices that are endpoints of edges
796 self.connected_vertices = None
799 class MultiEdgeSet(object):
800 """Defines a multi edge set"""
801 def __init__(self):
802 self.guid = 0 # objectGuid siteLinkBridge
803 self.edges = []
806 class MultiEdge(object):
807 """An "edge" between multiple vertices"""
808 def __init__(self):
809 self.site_link = None # object siteLink
810 self.vertices = []
811 self.con_type = None # interSiteTransport GUID
812 self.repl_info = ReplInfo()
813 self.directed = True
816 class InternalEdge(object):
817 """An edge that forms part of the minimal spanning tree
819 These are used in the Kruskal's algorithm. Their interesting
820 feature isa that they are sortable, with the good edges sorting
821 before the bad ones -- lower is better.
823 def __init__(self, v1, v2, redred, repl, eType, site_link):
824 self.v1 = v1
825 self.v2 = v2
826 self.red_red = redred
827 self.repl_info = repl
828 self.e_type = eType
829 self.site_link = site_link
831 def __eq__(self, other):
832 return not self < other and not other < self
834 def __ne__(self, other):
835 return self < other or other < self
837 def __gt__(self, other):
838 return other < self
840 def __ge__(self, other):
841 return not self < other
843 def __le__(self, other):
844 return not other < self
846 # TODO compare options and interval
847 def __lt__(self, other):
848 """Here "less than" means "better".
850 From within MS-ADTS 6.2.2.3.4.4:
852 SORT internalEdges by (descending RedRed,
853 ascending ReplInfo.Cost,
854 descending available time in ReplInfo.Schedule,
855 ascending V1ID,
856 ascending V2ID,
857 ascending Type)
860 if self.red_red != other.red_red:
861 return self.red_red
863 if self.repl_info.cost != other.repl_info.cost:
864 return self.repl_info.cost < other.repl_info.cost
866 if self.repl_info.duration != other.repl_info.duration:
867 return self.repl_info.duration > other.repl_info.duration
869 if self.v1.guid != other.v1.guid:
870 return self.v1.ndrpacked_guid < other.v1.ndrpacked_guid
872 if self.v2.guid != other.v2.guid:
873 return self.v2.ndrpacked_guid < other.v2.ndrpacked_guid
875 return self.e_type < other.e_type