2015-06-10 16:42:37 +12:00
# Graph functions used by KCC intersite
#
# Copyright (C) Dave Craft 2011
# Copyright (C) Andrew Bartlett 2015
#
# Andrew Bartlett's alleged work performed by his underlings Douglas
# Bagnall and Garming Sam.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# 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 itertools
import heapq
from samba . kcc . graph_utils import write_dot_file , verify_and_dot , verify_graph
2018-10-11 13:38:27 +13:00
from samba . kcc . kcc_utils import KCCError
2015-06-10 16:42:37 +12:00
from samba . ndr import ndr_pack
from samba . dcerpc import misc
2015-05-13 13:58:15 +12:00
from samba . kcc . debug import DEBUG , DEBUG_FN , WARN
2015-06-10 16:42:37 +12:00
2015-05-05 11:12:30 +12:00
MAX_DWORD = 2 * * 32 - 1
class ReplInfo ( object ) :
2015-05-07 13:56:18 +12:00
""" Represents information about replication
NTDSConnections use one representation a replication schedule , and
graph vertices use another . This is the Vertex one .
"""
2015-05-05 11:12:30 +12:00
def __init__ ( self ) :
self . cost = 0
self . interval = 0
self . options = 0
self . schedule = None
2015-06-05 12:35:39 +12:00
self . duration = 84 * 8
def set_repltimes_from_schedule ( self , schedule ) :
""" Convert the schedule and calculate duration
: param schdule : the schedule to convert
"""
self . schedule = convert_schedule_to_repltimes ( schedule )
self . duration = total_schedule ( self . schedule )
2015-05-05 11:12:30 +12:00
def total_schedule ( schedule ) :
""" Return the total number of 15 minute windows in which the schedule
is set to replicate in a week . If the schedule is None it is
assumed that the replication will happen in every 15 minute
window .
This is essentially a bit population count .
"""
if schedule is None :
return 84 * 8 # 84 bytes = 84 * 8 bits
total = 0
for byte in schedule :
while byte != 0 :
total + = byte & 1
byte >> = 1
return total
2015-04-23 14:42:51 +12:00
def convert_schedule_to_repltimes ( schedule ) :
""" Convert NTDS Connection schedule to replTime schedule.
Schedule defined in MS - ADTS 6.1 .4 .5 .2
ReplTimes defined in MS - DRSR 5.164 .
" Schedule " has 168 bytes but only the lower nibble of each is
significant . There is one byte per hour . Bit 3 ( 0x08 ) represents
the first 15 minutes of the hour and bit 0 ( 0x01 ) represents the
last 15 minutes . The first byte presumably covers 12 am - 1 am
Sunday , though the spec doesn ' t define the start of a week.
" ReplTimes " has 84 bytes which are the 168 lower nibbles of
" Schedule " packed together . Thus each byte covers 2 hours . Bits 7
( i . e . 0x80 ) is the first 15 minutes and bit 0 is the last . The
first byte covers Sunday 12 am - 2 am ( per spec ) .
Here we pack two elements of the NTDS Connection schedule slots
into one element of the replTimes list .
If no schedule appears in NTDS Connection then a default of 0x11
is set in each replTimes slot as per behaviour noted in a Windows
DC . That default would cause replication within the last 15
minutes of each hour .
"""
2015-05-08 16:38:42 +12:00
# note, NTDSConnection schedule == None means "once an hour"
# repl_info == None means "always"
2015-04-23 14:42:51 +12:00
if schedule is None or schedule . dataArray [ 0 ] is None :
return [ 0x11 ] * 84
times = [ ]
data = schedule . dataArray [ 0 ] . slots
for i in range ( 84 ) :
times . append ( ( data [ i * 2 ] & 0xF ) << 4 | ( data [ i * 2 + 1 ] & 0xF ) )
return times
2015-06-10 16:01:52 +12:00
def combine_repl_info ( info_a , info_b ) :
""" Generate an repl_info combining two others
2015-06-10 16:00:36 +12:00
2015-06-10 16:01:52 +12:00
The schedule is set to be the intersection of the two input schedules .
The duration is set to be the duration of the new schedule .
The cost is the sum of the costs ( saturating at a huge value ) .
The options are the intersection of the input options .
The interval is the maximum of the two intervals .
2015-06-10 16:00:36 +12:00
: param info_a : An input replInfo object
: param info_b : An input replInfo object
2015-06-10 16:01:52 +12:00
: return : a new ReplInfo combining the other 2
2015-06-10 16:00:36 +12:00
"""
2015-06-10 16:01:52 +12:00
info_c = ReplInfo ( )
2015-04-23 14:42:51 +12:00
info_c . interval = max ( info_a . interval , info_b . interval )
info_c . options = info_a . options & info_b . options
2018-07-30 18:19:49 +12:00
# schedule of None defaults to "always"
2015-04-23 14:42:51 +12:00
if info_a . schedule is None :
info_a . schedule = [ 0xFF ] * 84
if info_b . schedule is None :
info_b . schedule = [ 0xFF ] * 84
2015-06-10 16:01:52 +12:00
info_c . schedule = [ a & b for a , b in zip ( info_a . schedule , info_b . schedule ) ]
info_c . duration = total_schedule ( info_c . schedule )
2015-04-23 14:42:51 +12:00
2015-06-10 16:01:52 +12:00
info_c . cost = min ( info_a . cost + info_b . cost , MAX_DWORD )
return info_c
2015-06-10 16:42:37 +12:00
2015-06-10 16:42:54 +12:00
def get_spanning_tree_edges ( graph , my_site , label = None , verify = False ,
2015-04-30 10:39:54 +12:00
dot_file_dir = None ) :
2015-05-01 15:06:22 +12:00
""" Find edges for the intersite graph
2015-05-07 13:57:10 +12:00
From MS - ADTS 6.2 .2 .3 .4 .4
2015-05-01 15:06:22 +12:00
: param graph : a kcc . kcc_utils . Graph object
: param my_site : the topology generator ' s site
: param label : a label for use in dot files and verification
: param verify : if True , try to verify that graph properties are correct
: param dot_file_dir : if not None , write Graphviz dot files here
"""
2015-06-10 16:42:54 +12:00
# 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 )
2015-04-30 10:39:54 +12:00
if verify or dot_file_dir is not None :
2015-06-10 16:42:54 +12:00
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 ]
2015-04-30 10:39:54 +12:00
if dot_file_dir is not None :
2015-06-10 16:42:54 +12:00
write_dot_file ( ' edgeset_ %s ' % ( edgeType , ) , graph_edges ,
vertices = graph_nodes , label = label )
if verify :
2018-03-15 12:01:10 +13:00
errors = verify_graph ( graph_edges , vertices = graph_nodes ,
properties = ( ' complete ' , ' connected ' ) )
if errors :
DEBUG ( ' spanning tree edge set %s FAILED ' % edgeType )
2018-05-15 14:40:36 +12:00
for p , e , doc in errors :
2018-03-15 12:01:10 +13:00
DEBUG ( " %18s : %s " % ( p , e ) )
raise KCCError ( " spanning tree failed " )
2015-06-10 16:42:54 +12:00
# 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 )
2015-04-30 10:39:54 +12:00
if verify or dot_file_dir is not None :
2015-06-10 16:42:54 +12:00
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 ,
2015-04-30 10:39:54 +12:00
verify = verify , dot_file_dir = dot_file_dir )
2015-06-10 16:42:54 +12:00
# 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
2015-04-30 10:39:54 +12:00
if verify or dot_file_dir is not None :
2015-06-10 16:42:54 +12:00
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 ,
2015-04-30 10:39:54 +12:00
dot_file_dir = dot_file_dir )
2015-06-10 16:42:54 +12:00
# 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 )
2015-04-30 10:39:54 +12:00
if verify or dot_file_dir is not None :
2015-06-10 16:42:54 +12:00
graph_edges = [ [ x . site . site_dnstr for x in e . vertices ]
for e in edge_list ]
2018-07-30 18:19:49 +12:00
# add the reverse edge if not directed.
2015-06-10 16:42:54 +12:00
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 ,
2015-04-30 10:39:54 +12:00
dot_file_dir = dot_file_dir )
2015-06-10 16:42:54 +12:00
# count the components
return edge_list , components
def create_edge ( con_type , site_link , guid_to_vertex ) :
2015-05-07 13:57:58 +12:00
""" Set up a MultiEdge for the intersite graph
A MultiEdge can have multiple vertices .
From MS - ADTS 6.2 .2 .3 .4 .4
: param con_type : a transport type GUID
: param site_link : a kcc . kcc_utils . SiteLink object
: param guid_to_vertex : a mapping between GUIDs and vertices
: return : a MultiEdge
"""
2015-06-10 16:42:54 +12:00
e = MultiEdge ( )
e . site_link = site_link
e . vertices = [ ]
2018-01-07 22:17:43 +13:00
for site_guid , site_dn in site_link . site_list :
2015-06-10 16:42:54 +12:00
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
2015-06-05 12:35:39 +12:00
e . repl_info . set_repltimes_from_schedule ( site_link . schedule )
2015-06-10 16:42:54 +12:00
e . con_type = con_type
e . directed = False
return e
2015-05-07 13:59:39 +12:00
def create_auto_edge_set ( graph , transport_guid ) :
""" Set up an automatic MultiEdgeSet for the intersite graph
From within MS - ADTS 6.2 .2 .3 .4 .4
: param graph : the intersite graph object
: param transport_guid : a transport type GUID
: return : a MultiEdgeSet
"""
2015-06-10 16:42:54 +12:00
e_set = MultiEdgeSet ( )
# use a NULL guid, not associated with a SiteLinkBridge object
e_set . guid = misc . GUID ( )
for site_link in graph . edges :
2015-05-07 13:59:39 +12:00
if site_link . con_type == transport_guid :
2015-06-10 16:42:54 +12:00
e_set . edges . append ( site_link )
return e_set
def setup_vertices ( graph ) :
2015-05-07 14:06:09 +12:00
""" Initialise vertices in the graph for the Dijkstra ' s run.
2015-06-05 12:14:12 +12:00
Part of MS - ADTS 6.2 .2 .3 .4 .4
The schedule and options are set to all - on , so that intersections
with real data defer to that data .
Refer to the convert_schedule_to_repltimes ( ) docstring for an
explanation of the repltimes schedule values .
2015-05-07 14:06:09 +12:00
: param graph : an IntersiteGraph object
: return : None
"""
2015-06-10 16:42:54 +12:00
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
2015-06-05 12:14:12 +12:00
# repl_info.schedule == None means "always".
v . repl_info . schedule = None
2015-06-05 12:35:39 +12:00
v . repl_info . duration = 84 * 8
2015-06-10 16:42:54 +12:00
v . demoted = False
def dijkstra ( graph , edge_type , include_black ) :
2015-05-07 14:06:42 +12:00
""" Perform Dijkstra ' s algorithm on an intersite graph.
: param graph : an IntersiteGraph object
: param edge_type : a transport type GUID
: param include_black : boolean , whether to include black vertices
: return : None
"""
2015-05-07 14:22:56 +12:00
queue = setup_dijkstra ( graph , edge_type , include_black )
2015-06-10 16:42:54 +12:00
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 )
2015-05-07 14:22:56 +12:00
def setup_dijkstra ( graph , edge_type , include_black ) :
""" Create a vertex queue for Dijksta ' s algorithm.
2015-05-07 14:06:42 +12:00
: param graph : an IntersiteGraph object
: param edge_type : a transport type GUID
: param include_black : boolean , whether to include black vertices
2015-05-07 14:22:56 +12:00
: return : A heap queue of vertices
2015-05-07 14:06:42 +12:00
"""
2015-05-07 14:22:56 +12:00
queue = [ ]
2015-06-10 16:42:54 +12:00
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 ) )
2015-05-07 14:22:56 +12:00
return queue
2015-06-10 16:42:54 +12:00
def try_new_path ( graph , queue , vfrom , edge , vto ) :
2015-05-07 14:07:01 +12:00
""" Helper function for Dijksta ' s algorithm.
: param graph : an IntersiteGraph object
: param queue : the empty queue to initialise .
: param vfrom : Vertex we are coming from
: param edge : an edge to try
: param vto : the other Vertex
: return : None
"""
2015-06-10 16:01:52 +12:00
new_repl_info = combine_repl_info ( vfrom . repl_info , edge . repl_info )
2015-06-10 16:42:54 +12:00
2015-06-05 12:35:39 +12:00
# Cheaper or longer schedule goes in the heap
2015-06-10 16:42:54 +12:00
2015-06-10 16:01:52 +12:00
if ( new_repl_info . cost < vto . repl_info . cost or
new_repl_info . duration > vto . repl_info . duration ) :
2015-06-10 16:42:54 +12:00
vto . root = vfrom . root
vto . component_id = vfrom . component_id
2015-06-10 16:01:52 +12:00
vto . repl_info = new_repl_info
2015-06-10 16:42:54 +12:00
heapq . heappush ( queue , ( vto . repl_info . cost , vto . guid , vto ) )
def check_demote_vertex ( vertex , edge_type ) :
2015-05-08 11:02:54 +12:00
""" Demote non-white vertices that accept only white edges
This makes them seem temporarily like white vertices .
: param vertex : a Vertex ( )
: param edge_type : a transport type GUID
: return : None
"""
2015-06-10 16:42:54 +12:00
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 ) :
2015-05-08 11:03:07 +12:00
""" Un-demote non-white vertices
Set a vertex ' s to an undemoted state.
: param vertex : a Vertex ( )
: return : None
"""
2015-06-10 16:42:54 +12:00
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 ) :
2015-05-08 11:03:26 +12:00
""" Find internal edges to pass to Kruskal ' s algorithm
: param graph : an IntersiteGraph object
: param e_set : an edge set
: param internal_edges : a set that internal edges get added to
: return : None
"""
2015-06-10 16:42:54 +12:00
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 ) :
2015-05-08 11:03:37 +12:00
""" Find the set of all vertices touching an edge to examine
: param graph : an IntersiteGraph object
: param examine : an edge
: param internal_edges : a set that internal edges get added to
: return : None
"""
2015-06-10 16:42:54 +12:00
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 )
def add_int_edge ( graph , internal_edges , examine , v1 , v2 ) :
2015-05-08 12:19:51 +12:00
""" Add edges between compatible red and black vertices
Internal edges form the core of the tree - - white and RODC
vertices attach to it as leaf nodes . An edge needs to have black
or red endpoints with compatible replication schedules to be
accepted as an internal edge .
Here we examine an edge and add it to the set of internal edges if
it looks good .
: param graph : the graph object .
: param internal_edges : a set of internal edges
: param examine : an edge to examine for suitability .
: param v1 : a Vertex
: param v2 : the other Vertex
"""
2015-06-10 16:42:54 +12:00
root1 = v1 . root
root2 = v2 . root
2015-05-08 12:26:00 +12:00
red_red = root1 . is_red ( ) and root2 . is_red ( )
2015-06-10 16:42:54 +12:00
if red_red :
2015-05-08 12:26:00 +12:00
if ( examine . con_type not in root1 . accept_red_red
or examine . con_type not in root2 . accept_red_red ) :
2015-06-10 16:42:54 +12:00
return
elif ( examine . con_type not in root1 . accept_black
or examine . con_type not in root2 . accept_black ) :
return
# Create the transitive replInfo for the two trees and this edge
2015-06-10 16:01:52 +12:00
ri = combine_repl_info ( v1 . repl_info , v2 . repl_info )
if ri . duration == 0 :
2015-06-10 16:42:54 +12:00
return
2015-06-10 16:01:52 +12:00
ri2 = combine_repl_info ( ri , examine . repl_info )
if ri2 . duration == 0 :
2015-06-10 16:42:54 +12:00
return
2015-05-08 12:26:00 +12:00
# Order by vertex guid
if root1 . ndrpacked_guid > root2 . ndrpacked_guid :
root1 , root2 = root2 , root1
2015-06-10 16:42:54 +12:00
newIntEdge = InternalEdge ( root1 , root2 , red_red , ri2 , examine . con_type ,
examine . site_link )
internal_edges . add ( newIntEdge )
def kruskal ( graph , edges ) :
2015-05-08 12:25:12 +12:00
""" Perform Kruskal ' s algorithm using the given set of edges
The input edges are " internal edges " - - between red and black
nodes . The output edges are a minimal spanning tree .
: param graph : the graph object .
: param edges : a set of edges
: return : a tuple of a list of edges , and the number of components
"""
2015-06-10 16:42:54 +12:00
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 ( )
2018-07-30 18:19:49 +12:00
# XXX expected_num_tree_edges is never used
2015-06-10 16:42:54 +12:00
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 ) :
2015-05-08 14:52:13 +12:00
""" Kruskal helper to find the component a vertex belongs to.
: param vertex : a Vertex
: return : the Vertex object representing the component
"""
2015-06-10 16:42:54 +12:00
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 ) :
2015-05-08 14:52:27 +12:00
""" Kruskal helper to add output edges
: param graph : the InterSiteGraph
: param output_edges : the list of spanning tree edges
: param e : the edge to be added
: return : None
"""
2015-06-10 16:42:54 +12:00
v1 = e . v1
v2 = e . v2
2015-05-08 14:52:27 +12:00
# This multi-edge is a 'real' undirected 2-vertex edge with no
# GUID. XXX It is not really the same thing at all as the
# multi-vertex edges relating to site-links. We shouldn't really
# be using the same class or storing them in the same list as the
# other ones. But we do. Historical reasons.
2015-06-10 16:42:54 +12:00
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 )
2015-06-10 17:12:51 +12:00
def setup_graph ( part , site_table , transport_guid , sitelink_table ,
2015-06-10 16:42:54 +12:00
bridges_required ) :
2015-05-08 14:52:41 +12:00
""" Set up an IntersiteGraph based on intersite topology
2015-06-10 16:42:54 +12:00
2015-05-08 14:52:41 +12:00
The graph will have a Vertex for each site , a MultiEdge for each
siteLink object , and a MultiEdgeSet for each siteLinkBridge object
( or implied siteLinkBridge ) .
: param part : the partition we are dealing with
: param site_table : a mapping of guids to sites ( KCC . site_table )
: param transport_guid : the GUID of the IP transport
: param sitelink_table : a mapping of dnstrs to sitelinks
2015-05-13 13:58:15 +12:00
: param bridges_required : boolean , asking in vain for something to do
with site link bridges
2015-05-08 14:52:41 +12:00
: return : a new IntersiteGraph
2015-06-10 16:42:54 +12:00
"""
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 ( )
2015-06-10 17:12:51 +12:00
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 )
2015-05-13 13:58:15 +12:00
# XXX we are ignoring the bridges_required option and indeed the
# whole concept of SiteLinkBridge objects.
2015-06-10 17:12:51 +12:00
if bridges_required :
2015-05-13 13:58:15 +12:00
WARN ( " Samba KCC ignores the bridges required option " )
2015-06-10 16:42:54 +12:00
2015-05-13 13:58:15 +12:00
g . edge_set . add ( create_auto_edge_set ( g , transport_guid ) )
2015-06-10 16:42:54 +12:00
g . connected_vertices = connected_vertices
return g
2015-06-10 16:42:37 +12:00
class VertexColor ( object ) :
2015-05-08 14:52:59 +12:00
""" Enumeration of vertex colours """
2015-06-10 16:42:37 +12:00
( red , black , white , unknown ) = range ( 0 , 4 )
class Vertex ( object ) :
2015-05-08 14:53:16 +12:00
""" intersite graph representation of a Site.
There is a separate vertex for each partition .
: param site : the site to make a vertex of .
: param part : the partition .
2015-06-10 16:42:37 +12:00
"""
def __init__ ( self , site , part ) :
self . site = site
self . part = part
self . color = VertexColor . unknown
self . edges = [ ]
self . accept_red_red = [ ]
self . accept_black = [ ]
self . repl_info = ReplInfo ( )
self . root = self
self . guid = None
self . component_id = self
self . demoted = False
self . options = 0
self . interval = 0
def color_vertex ( self ) :
2015-05-08 14:53:16 +12:00
""" Color to indicate which kind of NC replica the vertex contains
2015-06-10 16:42:37 +12:00
"""
# IF s contains one or more DCs with full replicas of the
# NC cr!nCName
# SET v.Color to COLOR.RED
# ELSEIF s contains one or more partial replicas of the NC
# SET v.Color to COLOR.BLACK
2018-07-30 18:19:49 +12:00
# ELSE
2015-06-10 16:42:37 +12:00
# SET v.Color to COLOR.WHITE
# set to minimum (no replica)
self . color = VertexColor . white
for dnstr , dsa in self . site . dsa_table . items ( ) :
rep = dsa . get_current_replica ( self . part . nc_dnstr )
if rep is None :
continue
# We have a full replica which is the largest
# value so exit
if not rep . is_partial ( ) :
self . color = VertexColor . red
break
else :
self . color = VertexColor . black
def is_red ( self ) :
assert ( self . color != VertexColor . unknown )
return ( self . color == VertexColor . red )
def is_black ( self ) :
assert ( self . color != VertexColor . unknown )
return ( self . color == VertexColor . black )
def is_white ( self ) :
assert ( self . color != VertexColor . unknown )
return ( self . color == VertexColor . white )
class IntersiteGraph ( object ) :
""" Graph for representing the intersite """
def __init__ ( self ) :
self . vertices = set ( )
self . edges = set ( )
self . edge_set = set ( )
# All vertices that are endpoints of edges
self . connected_vertices = None
class MultiEdgeSet ( object ) :
""" Defines a multi edge set """
def __init__ ( self ) :
self . guid = 0 # objectGuid siteLinkBridge
self . edges = [ ]
class MultiEdge ( object ) :
2015-05-08 14:53:27 +12:00
""" An " edge " between multiple vertices """
2015-06-10 16:42:37 +12:00
def __init__ ( self ) :
self . site_link = None # object siteLink
self . vertices = [ ]
self . con_type = None # interSiteTransport GUID
self . repl_info = ReplInfo ( )
self . directed = True
class InternalEdge ( object ) :
2015-05-08 14:53:41 +12:00
""" An edge that forms part of the minimal spanning tree
These are used in the Kruskal ' s algorithm. Their interesting
feature isa that they are sortable , with the good edges sorting
before the bad ones - - lower is better .
"""
2015-06-10 16:42:37 +12:00
def __init__ ( self , v1 , v2 , redred , repl , eType , site_link ) :
self . v1 = v1
self . v2 = v2
self . red_red = redred
self . repl_info = repl
self . e_type = eType
self . site_link = site_link
2018-04-10 15:06:51 +12:00
def __hash__ ( self ) :
return hash ( (
self . v1 , self . v2 , self . red_red , self . repl_info , self . e_type ,
self . site_link ) )
2015-06-10 16:42:37 +12:00
def __eq__ ( self , other ) :
return not self < other and not other < self
def __ne__ ( self , other ) :
return self < other or other < self
def __gt__ ( self , other ) :
return other < self
def __ge__ ( self , other ) :
return not self < other
def __le__ ( self , other ) :
return not other < self
def __lt__ ( self , other ) :
2015-05-07 14:07:27 +12:00
""" Here " less than " means " better " .
From within MS - ADTS 6.2 .2 .3 .4 .4 :
SORT internalEdges by ( descending RedRed ,
ascending ReplInfo . Cost ,
descending available time in ReplInfo . Schedule ,
ascending V1ID ,
ascending V2ID ,
ascending Type )
"""
2015-06-10 16:42:37 +12:00
if self . red_red != other . red_red :
return self . red_red
if self . repl_info . cost != other . repl_info . cost :
return self . repl_info . cost < other . repl_info . cost
2015-06-05 12:35:39 +12:00
if self . repl_info . duration != other . repl_info . duration :
return self . repl_info . duration > other . repl_info . duration
2015-06-10 16:42:37 +12:00
if self . v1 . guid != other . v1 . guid :
return self . v1 . ndrpacked_guid < other . v1 . ndrpacked_guid
if self . v2 . guid != other . v2 . guid :
return self . v2 . ndrpacked_guid < other . v2 . ndrpacked_guid
return self . e_type < other . e_type