KCC: remove an unwarranted XXX comment
[Samba.git] / python / samba / kcc / __init__.py
blob9dbd0f56dcd432ff441fc99461ab80dc8efc1061
1 # define the KCC object
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 random
23 import uuid
24 import sys
26 import itertools
27 from samba import unix2nttime, nttime2unix
28 from samba import ldb, dsdb, drs_utils
29 from samba.auth import system_session
30 from samba.samdb import SamDB
31 from samba.dcerpc import drsuapi, misc
33 from samba.kcc.kcc_utils import Site, Partition, Transport, SiteLink
34 from samba.kcc.kcc_utils import NCReplica, NCType, nctype_lut, GraphNode
35 from samba.kcc.kcc_utils import RepsFromTo, KCCError, KCCFailedObject
36 from samba.kcc.graph import convert_schedule_to_repltimes
38 from samba.ndr import ndr_pack
40 from samba.kcc.graph_utils import verify_and_dot
42 from samba.kcc import ldif_import_export
43 from samba.kcc.graph import setup_graph, get_spanning_tree_edges
44 from samba.kcc.graph import Vertex
46 from samba.kcc.debug import DEBUG, DEBUG_FN, logger
47 from samba.kcc import debug
50 def sort_replica_by_dsa_guid(rep1, rep2):
51 """Helper to sort NCReplicas by their DSA guids
53 The guids need to be sorted in their NDR form.
55 :param rep1: An NC replica
56 :param rep2: Another replica
57 :return: -1, 0, or 1, indicating sort order.
58 """
59 return cmp(ndr_pack(rep1.rep_dsa_guid), ndr_pack(rep2.rep_dsa_guid))
62 def sort_dsa_by_gc_and_guid(dsa1, dsa2):
63 """Helper to sort DSAs by guid global catalog status
65 GC DSAs come before non-GC DSAs, other than that, the guids are
66 sorted in NDR form.
68 :param dsa1: A DSA object
69 :param dsa2: Another DSA
70 :return: -1, 0, or 1, indicating sort order.
71 """
72 if dsa1.is_gc() and not dsa2.is_gc():
73 return -1
74 if not dsa1.is_gc() and dsa2.is_gc():
75 return +1
76 return cmp(ndr_pack(dsa1.dsa_guid), ndr_pack(dsa2.dsa_guid))
79 def is_smtp_replication_available():
80 """Can the KCC use SMTP replication?
82 Currently always returns false because Samba doesn't implement
83 SMTP transfer for NC changes between DCs.
85 :return: Boolean (always False)
86 """
87 return False
90 class KCC(object):
91 """The Knowledge Consistency Checker class.
93 A container for objects and methods allowing a run of the KCC. Produces a
94 set of connections in the samdb for which the Distributed Replication
95 Service can then utilize to replicate naming contexts
97 :param unix_now: The putative current time in seconds since 1970.
98 :param read_only: Don't write to the database.
99 :param verify: Check topological invariants for the generated graphs
100 :param debug: Write verbosely to stderr.
101 "param dot_file_dir: write diagnostic Graphviz files in this directory
103 def __init__(self, unix_now, readonly=False, verify=False, debug=False,
104 dot_file_dir=None):
105 """Initializes the partitions class which can hold
106 our local DCs partitions or all the partitions in
107 the forest
109 self.part_table = {} # partition objects
110 self.site_table = {}
111 self.ip_transport = None
112 self.sitelink_table = {}
113 self.dsa_by_dnstr = {}
114 self.dsa_by_guid = {}
116 self.get_dsa_by_guidstr = self.dsa_by_guid.get
117 self.get_dsa = self.dsa_by_dnstr.get
119 # TODO: These should be backed by a 'permanent' store so that when
120 # calling DRSGetReplInfo with DS_REPL_INFO_KCC_DSA_CONNECT_FAILURES,
121 # the failure information can be returned
122 self.kcc_failed_links = {}
123 self.kcc_failed_connections = set()
125 # Used in inter-site topology computation. A list
126 # of connections (by NTDSConnection object) that are
127 # to be kept when pruning un-needed NTDS Connections
128 self.kept_connections = set()
130 self.my_dsa_dnstr = None # My dsa DN
131 self.my_dsa = None # My dsa object
133 self.my_site_dnstr = None
134 self.my_site = None
136 self.samdb = None
138 self.unix_now = unix_now
139 self.nt_now = unix2nttime(unix_now)
140 self.readonly = readonly
141 self.verify = verify
142 self.debug = debug
143 self.dot_file_dir = dot_file_dir
145 def load_ip_transport(self):
146 """Loads the inter-site transport objects for Sites
148 :return: None
149 :raise KCCError: if no IP transport is found
151 try:
152 res = self.samdb.search("CN=Inter-Site Transports,CN=Sites,%s" %
153 self.samdb.get_config_basedn(),
154 scope=ldb.SCOPE_SUBTREE,
155 expression="(objectClass=interSiteTransport)")
156 except ldb.LdbError, (enum, estr):
157 raise KCCError("Unable to find inter-site transports - (%s)" %
158 estr)
160 for msg in res:
161 dnstr = str(msg.dn)
163 transport = Transport(dnstr)
165 transport.load_transport(self.samdb)
166 if transport.name == 'IP':
167 self.ip_transport = transport
168 elif transport.name == 'SMTP':
169 logger.info("Samba KCC is ignoring the obsolete SMTP transport.")
171 else:
172 logger.warning("Samba KCC does not support the transport called %r."
173 % (transport.name,))
175 if self.ip_transport is None:
176 raise KCCError("there doesn't seem to be an IP transport")
178 def load_all_sitelinks(self):
179 """Loads the inter-site siteLink objects
181 :return: None
182 :raise KCCError: if site-links aren't found
184 try:
185 res = self.samdb.search("CN=Inter-Site Transports,CN=Sites,%s" %
186 self.samdb.get_config_basedn(),
187 scope=ldb.SCOPE_SUBTREE,
188 expression="(objectClass=siteLink)")
189 except ldb.LdbError, (enum, estr):
190 raise KCCError("Unable to find inter-site siteLinks - (%s)" % estr)
192 for msg in res:
193 dnstr = str(msg.dn)
195 # already loaded
196 if dnstr in self.sitelink_table:
197 continue
199 sitelink = SiteLink(dnstr)
201 sitelink.load_sitelink(self.samdb)
203 # Assign this siteLink to table
204 # and index by dn
205 self.sitelink_table[dnstr] = sitelink
207 def load_site(self, dn_str):
208 """Helper for load_my_site and load_all_sites.
210 Put all the site's DSAs into the KCC indices.
212 :param dn_str: a site dn_str
213 :return: the Site object pertaining to the dn_str
215 site = Site(dn_str, self.unix_now)
216 site.load_site(self.samdb)
218 # We avoid replacing the site with an identical copy in case
219 # somewhere else has a reference to the old one, which would
220 # lead to all manner of confusion and chaos.
221 guid = str(site.site_guid)
222 if guid not in self.site_table:
223 self.site_table[guid] = site
224 self.dsa_by_dnstr.update(site.dsa_table)
225 self.dsa_by_guid.update((str(x.dsa_guid), x)
226 for x in site.dsa_table.values())
228 return self.site_table[guid]
230 def load_my_site(self):
231 """Load the Site object for the local DSA.
233 :return: None
235 self.my_site_dnstr = ("CN=%s,CN=Sites,%s" % (
236 self.samdb.server_site_name(),
237 self.samdb.get_config_basedn()))
239 self.my_site = self.load_site(self.my_site_dnstr)
241 def load_all_sites(self):
242 """Discover all sites and create Site objects.
244 :return: None
245 :raise: KCCError if sites can't be found
247 try:
248 res = self.samdb.search("CN=Sites,%s" %
249 self.samdb.get_config_basedn(),
250 scope=ldb.SCOPE_SUBTREE,
251 expression="(objectClass=site)")
252 except ldb.LdbError, (enum, estr):
253 raise KCCError("Unable to find sites - (%s)" % estr)
255 for msg in res:
256 sitestr = str(msg.dn)
257 self.load_site(sitestr)
259 def load_my_dsa(self):
260 """Discover my nTDSDSA dn thru the rootDSE entry
262 :return: None
263 :raise: KCCError if DSA can't be found
265 dn = ldb.Dn(self.samdb, "<GUID=%s>" % self.samdb.get_ntds_GUID())
266 try:
267 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
268 attrs=["objectGUID"])
269 except ldb.LdbError, (enum, estr):
270 logger.warning("Search for %s failed: %s. This typically happens"
271 " in --importldif mode due to lack of module"
272 " support.", dn, estr)
273 try:
274 # We work around the failure above by looking at the
275 # dsServiceName that was put in the fake rootdse by
276 # the --exportldif, rather than the
277 # samdb.get_ntds_GUID(). The disadvantage is that this
278 # mode requires we modify the @ROOTDSE dnq to support
279 # --forced-local-dsa
280 service_name_res = self.samdb.search(base="",
281 scope=ldb.SCOPE_BASE,
282 attrs=["dsServiceName"])
283 dn = ldb.Dn(self.samdb,
284 service_name_res[0]["dsServiceName"][0])
286 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
287 attrs=["objectGUID"])
288 except ldb.LdbError, (enum, estr):
289 raise KCCError("Unable to find my nTDSDSA - (%s)" % estr)
291 if len(res) != 1:
292 raise KCCError("Unable to find my nTDSDSA at %s" %
293 dn.extended_str())
295 ntds_guid = misc.GUID(self.samdb.get_ntds_GUID())
296 if misc.GUID(res[0]["objectGUID"][0]) != ntds_guid:
297 raise KCCError("Did not find the GUID we expected,"
298 " perhaps due to --importldif")
300 self.my_dsa_dnstr = str(res[0].dn)
302 self.my_dsa = self.my_site.get_dsa(self.my_dsa_dnstr)
304 if self.my_dsa_dnstr not in self.dsa_by_dnstr:
305 debug.DEBUG_DARK_YELLOW("my_dsa %s isn't in self.dsas_by_dnstr:"
306 " it must be RODC.\n"
307 "Let's add it, because my_dsa is special!"
308 "\n(likewise for self.dsa_by_guid)" %
309 self.my_dsas_dnstr)
311 self.dsa_by_dnstr[self.my_dsa_dnstr] = self.my_dsa
312 self.dsa_by_guid[str(self.my_dsa.dsa_guid)] = self.my_dsa
314 def load_all_partitions(self):
315 """Discover and load all partitions.
317 Each NC is inserted into the part_table by partition
318 dn string (not the nCName dn string)
320 :return: None
321 :raise: KCCError if partitions can't be found
323 try:
324 res = self.samdb.search("CN=Partitions,%s" %
325 self.samdb.get_config_basedn(),
326 scope=ldb.SCOPE_SUBTREE,
327 expression="(objectClass=crossRef)")
328 except ldb.LdbError, (enum, estr):
329 raise KCCError("Unable to find partitions - (%s)" % estr)
331 for msg in res:
332 partstr = str(msg.dn)
334 # already loaded
335 if partstr in self.part_table:
336 continue
338 part = Partition(partstr)
340 part.load_partition(self.samdb)
341 self.part_table[partstr] = part
343 def refresh_failed_links_connections(self, ping=None):
344 """Ensure the failed links list is up to date
346 Based on MS-ADTS 6.2.2.1
348 :param ping: An oracle function of remote site availability
349 :return: None
351 # LINKS: Refresh failed links
352 self.kcc_failed_links = {}
353 current, needed = self.my_dsa.get_rep_tables()
354 for replica in current.values():
355 # For every possible connection to replicate
356 for reps_from in replica.rep_repsFrom:
357 failure_count = reps_from.consecutive_sync_failures
358 if failure_count <= 0:
359 continue
361 dsa_guid = str(reps_from.source_dsa_obj_guid)
362 time_first_failure = reps_from.last_success
363 last_result = reps_from.last_attempt
364 dns_name = reps_from.dns_name1
366 f = self.kcc_failed_links.get(dsa_guid)
367 if f is None:
368 f = KCCFailedObject(dsa_guid, failure_count,
369 time_first_failure, last_result,
370 dns_name)
371 self.kcc_failed_links[dsa_guid] = f
372 else:
373 f.failure_count = max(f.failure_count, failure_count)
374 f.time_first_failure = min(f.time_first_failure,
375 time_first_failure)
376 f.last_result = last_result
378 # CONNECTIONS: Refresh failed connections
379 restore_connections = set()
380 if ping is not None:
381 DEBUG("refresh_failed_links: checking if links are still down")
382 for connection in self.kcc_failed_connections:
383 if ping(connection.dns_name):
384 # Failed connection is no longer failing
385 restore_connections.add(connection)
386 else:
387 connection.failure_count += 1
388 else:
389 DEBUG("refresh_failed_links: not checking live links because we\n"
390 "weren't asked to --attempt-live-connections")
392 # Remove the restored connections from the failed connections
393 self.kcc_failed_connections.difference_update(restore_connections)
395 def is_stale_link_connection(self, target_dsa):
396 """Check whether a link to a remote DSA is stale
398 Used in MS-ADTS 6.2.2.2 Intrasite Connection Creation
400 Returns True if the remote seems to have been down for at
401 least two hours, otherwise False.
403 :param target_dsa: the remote DSA object
404 :return: True if link is stale, otherwise False
406 failed_link = self.kcc_failed_links.get(str(target_dsa.dsa_guid))
407 if failed_link:
408 # failure_count should be > 0, but check anyways
409 if failed_link.failure_count > 0:
410 unix_first_failure = \
411 nttime2unix(failed_link.time_first_failure)
412 # TODO guard against future
413 if unix_first_failure > self.unix_now:
414 logger.error("The last success time attribute for \
415 repsFrom is in the future!")
417 # Perform calculation in seconds
418 if (self.unix_now - unix_first_failure) > 60 * 60 * 2:
419 return True
421 # TODO connections.
422 # We have checked failed *links*, but we also need to check
423 # *connections*
425 return False
427 # TODO: This should be backed by some form of local database
428 def remove_unneeded_failed_links_connections(self):
429 # Remove all tuples in kcc_failed_links where failure count = 0
430 # In this implementation, this should never happen.
432 # Remove all connections which were not used this run or connections
433 # that became active during this run.
434 pass
436 def remove_unneeded_ntdsconn(self, all_connected):
437 """Remove unneeded NTDS Connections once topology is calculated
439 Based on MS-ADTS 6.2.2.4 Removing Unnecessary Connections
441 :param all_connected: indicates whether all sites are connected
442 :return: None
444 mydsa = self.my_dsa
446 # New connections won't have GUIDs which are needed for
447 # sorting. Add them.
448 for cn_conn in mydsa.connect_table.values():
449 if cn_conn.guid is None:
450 if self.readonly:
451 cn_conn.guid = misc.GUID(str(uuid.uuid4()))
452 cn_conn.whenCreated = self.nt_now
453 else:
454 cn_conn.load_connection(self.samdb)
456 for cn_conn in mydsa.connect_table.values():
458 s_dnstr = cn_conn.get_from_dnstr()
459 if s_dnstr is None:
460 cn_conn.to_be_deleted = True
461 continue
463 #XXX should an RODC be regarded as same site
464 same_site = s_dnstr in self.my_site.dsa_table
466 # Given an nTDSConnection object cn, if the DC with the
467 # nTDSDSA object dc that is the parent object of cn and
468 # the DC with the nTDSDA object referenced by cn!fromServer
469 # are in the same site, the KCC on dc deletes cn if all of
470 # the following are true:
472 # Bit NTDSCONN_OPT_IS_GENERATED is clear in cn!options.
474 # No site settings object s exists for the local DC's site, or
475 # bit NTDSSETTINGS_OPT_IS_TOPL_CLEANUP_DISABLED is clear in
476 # s!options.
478 # Another nTDSConnection object cn2 exists such that cn and
479 # cn2 have the same parent object, cn!fromServer = cn2!fromServer,
480 # and either
482 # cn!whenCreated < cn2!whenCreated
484 # cn!whenCreated = cn2!whenCreated and
485 # cn!objectGUID < cn2!objectGUID
487 # Bit NTDSCONN_OPT_RODC_TOPOLOGY is clear in cn!options
488 if same_site:
489 if not cn_conn.is_generated():
490 continue
492 if self.my_site.is_cleanup_ntdsconn_disabled():
493 continue
495 # Loop thru connections looking for a duplicate that
496 # fulfills the previous criteria
497 lesser = False
498 packed_guid = ndr_pack(cn_conn.guid)
499 for cn2_conn in mydsa.connect_table.values():
500 if cn2_conn is cn_conn:
501 continue
503 s2_dnstr = cn2_conn.get_from_dnstr()
505 # If the NTDS Connections has a different
506 # fromServer field then no match
507 if s2_dnstr != s_dnstr:
508 continue
510 lesser = (cn_conn.whenCreated < cn2_conn.whenCreated or
511 (cn_conn.whenCreated == cn2_conn.whenCreated and
512 packed_guid < ndr_pack(cn2_conn.guid)))
514 if lesser:
515 break
517 if lesser and not cn_conn.is_rodc_topology():
518 cn_conn.to_be_deleted = True
520 # Given an nTDSConnection object cn, if the DC with the nTDSDSA
521 # object dc that is the parent object of cn and the DC with
522 # the nTDSDSA object referenced by cn!fromServer are in
523 # different sites, a KCC acting as an ISTG in dc's site
524 # deletes cn if all of the following are true:
526 # Bit NTDSCONN_OPT_IS_GENERATED is clear in cn!options.
528 # cn!fromServer references an nTDSDSA object for a DC
529 # in a site other than the local DC's site.
531 # The keepConnections sequence returned by
532 # CreateIntersiteConnections() does not contain
533 # cn!objectGUID, or cn is "superseded by" (see below)
534 # another nTDSConnection cn2 and keepConnections
535 # contains cn2!objectGUID.
537 # The return value of CreateIntersiteConnections()
538 # was true.
540 # Bit NTDSCONN_OPT_RODC_TOPOLOGY is clear in
541 # cn!options
543 else: # different site
545 if not mydsa.is_istg():
546 continue
548 if not cn_conn.is_generated():
549 continue
551 # TODO
552 # We are directly using this connection in intersite or
553 # we are using a connection which can supersede this one.
555 # MS-ADTS 6.2.2.4 - Removing Unnecessary Connections does not
556 # appear to be correct.
558 # 1. cn!fromServer and cn!parent appear inconsistent with
559 # no cn2
560 # 2. The repsFrom do not imply each other
562 if cn_conn in self.kept_connections: # and not_superceded:
563 continue
565 # This is the result of create_intersite_connections
566 if not all_connected:
567 continue
569 if not cn_conn.is_rodc_topology():
570 cn_conn.to_be_deleted = True
572 if mydsa.is_ro() or self.readonly:
573 for connect in mydsa.connect_table.values():
574 if connect.to_be_deleted:
575 DEBUG_FN("TO BE DELETED:\n%s" % connect)
576 if connect.to_be_added:
577 DEBUG_FN("TO BE ADDED:\n%s" % connect)
579 # Peform deletion from our tables but perform
580 # no database modification
581 mydsa.commit_connections(self.samdb, ro=True)
582 else:
583 # Commit any modified connections
584 mydsa.commit_connections(self.samdb)
586 def modify_repsFrom(self, n_rep, t_repsFrom, s_rep, s_dsa, cn_conn):
587 """Update an repsFrom object if required.
589 Part of MS-ADTS 6.2.2.5.
591 Update t_repsFrom if necessary to satisfy requirements. Such
592 updates are typically required when the IDL_DRSGetNCChanges
593 server has moved from one site to another--for example, to
594 enable compression when the server is moved from the
595 client's site to another site.
597 The repsFrom.update_flags bit field may be modified
598 auto-magically if any changes are made here. See
599 kcc_utils.RepsFromTo for gory details.
602 :param n_rep: NC replica we need
603 :param t_repsFrom: repsFrom tuple to modify
604 :param s_rep: NC replica at source DSA
605 :param s_dsa: source DSA
606 :param cn_conn: Local DSA NTDSConnection child
608 :return: None
610 s_dnstr = s_dsa.dsa_dnstr
611 same_site = s_dnstr in self.my_site.dsa_table
613 # if schedule doesn't match then update and modify
614 times = convert_schedule_to_repltimes(cn_conn.schedule)
615 if times != t_repsFrom.schedule:
616 t_repsFrom.schedule = times
618 # Bit DRS_PER_SYNC is set in replicaFlags if and only
619 # if nTDSConnection schedule has a value v that specifies
620 # scheduled replication is to be performed at least once
621 # per week.
622 if cn_conn.is_schedule_minimum_once_per_week():
624 if ((t_repsFrom.replica_flags &
625 drsuapi.DRSUAPI_DRS_PER_SYNC) == 0x0):
626 t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_PER_SYNC
628 # Bit DRS_INIT_SYNC is set in t.replicaFlags if and only
629 # if the source DSA and the local DC's nTDSDSA object are
630 # in the same site or source dsa is the FSMO role owner
631 # of one or more FSMO roles in the NC replica.
632 if same_site or n_rep.is_fsmo_role_owner(s_dnstr):
634 if ((t_repsFrom.replica_flags &
635 drsuapi.DRSUAPI_DRS_INIT_SYNC) == 0x0):
636 t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_INIT_SYNC
638 # If bit NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT is set in
639 # cn!options, bit DRS_NEVER_NOTIFY is set in t.replicaFlags
640 # if and only if bit NTDSCONN_OPT_USE_NOTIFY is clear in
641 # cn!options. Otherwise, bit DRS_NEVER_NOTIFY is set in
642 # t.replicaFlags if and only if s and the local DC's
643 # nTDSDSA object are in different sites.
644 if ((cn_conn.options &
645 dsdb.NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT) != 0x0):
647 if (cn_conn.options & dsdb.NTDSCONN_OPT_USE_NOTIFY) == 0x0:
648 # WARNING
650 # it LOOKS as if this next test is a bit silly: it
651 # checks the flag then sets it if it not set; the same
652 # effect could be achieved by unconditionally setting
653 # it. But in fact the repsFrom object has special
654 # magic attached to it, and altering replica_flags has
655 # side-effects. That is bad in my opinion, but there
656 # you go.
657 if ((t_repsFrom.replica_flags &
658 drsuapi.DRSUAPI_DRS_NEVER_NOTIFY) == 0x0):
659 t_repsFrom.replica_flags |= \
660 drsuapi.DRSUAPI_DRS_NEVER_NOTIFY
662 elif not same_site:
664 if ((t_repsFrom.replica_flags &
665 drsuapi.DRSUAPI_DRS_NEVER_NOTIFY) == 0x0):
666 t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_NEVER_NOTIFY
668 # Bit DRS_USE_COMPRESSION is set in t.replicaFlags if
669 # and only if s and the local DC's nTDSDSA object are
670 # not in the same site and the
671 # NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION bit is
672 # clear in cn!options
673 if (not same_site and
674 (cn_conn.options &
675 dsdb.NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION) == 0x0):
677 if ((t_repsFrom.replica_flags &
678 drsuapi.DRSUAPI_DRS_USE_COMPRESSION) == 0x0):
679 t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_USE_COMPRESSION
681 # Bit DRS_TWOWAY_SYNC is set in t.replicaFlags if and only
682 # if bit NTDSCONN_OPT_TWOWAY_SYNC is set in cn!options.
683 if (cn_conn.options & dsdb.NTDSCONN_OPT_TWOWAY_SYNC) != 0x0:
685 if ((t_repsFrom.replica_flags &
686 drsuapi.DRSUAPI_DRS_TWOWAY_SYNC) == 0x0):
687 t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_TWOWAY_SYNC
689 # Bits DRS_DISABLE_AUTO_SYNC and DRS_DISABLE_PERIODIC_SYNC are
690 # set in t.replicaFlags if and only if cn!enabledConnection = false.
691 if not cn_conn.is_enabled():
693 if ((t_repsFrom.replica_flags &
694 drsuapi.DRSUAPI_DRS_DISABLE_AUTO_SYNC) == 0x0):
695 t_repsFrom.replica_flags |= \
696 drsuapi.DRSUAPI_DRS_DISABLE_AUTO_SYNC
698 if ((t_repsFrom.replica_flags &
699 drsuapi.DRSUAPI_DRS_DISABLE_PERIODIC_SYNC) == 0x0):
700 t_repsFrom.replica_flags |= \
701 drsuapi.DRSUAPI_DRS_DISABLE_PERIODIC_SYNC
703 # If s and the local DC's nTDSDSA object are in the same site,
704 # cn!transportType has no value, or the RDN of cn!transportType
705 # is CN=IP:
707 # Bit DRS_MAIL_REP in t.replicaFlags is clear.
709 # t.uuidTransport = NULL GUID.
711 # t.uuidDsa = The GUID-based DNS name of s.
713 # Otherwise:
715 # Bit DRS_MAIL_REP in t.replicaFlags is set.
717 # If x is the object with dsname cn!transportType,
718 # t.uuidTransport = x!objectGUID.
720 # Let a be the attribute identified by
721 # x!transportAddressAttribute. If a is
722 # the dNSHostName attribute, t.uuidDsa = the GUID-based
723 # DNS name of s. Otherwise, t.uuidDsa = (s!parent)!a.
725 # It appears that the first statement i.e.
727 # "If s and the local DC's nTDSDSA object are in the same
728 # site, cn!transportType has no value, or the RDN of
729 # cn!transportType is CN=IP:"
731 # could be a slightly tighter statement if it had an "or"
732 # between each condition. I believe this should
733 # be interpreted as:
735 # IF (same-site) OR (no-value) OR (type-ip)
737 # because IP should be the primary transport mechanism
738 # (even in inter-site) and the absense of the transportType
739 # attribute should always imply IP no matter if its multi-site
741 # NOTE MS-TECH INCORRECT:
743 # All indications point to these statements above being
744 # incorrectly stated:
746 # t.uuidDsa = The GUID-based DNS name of s.
748 # Let a be the attribute identified by
749 # x!transportAddressAttribute. If a is
750 # the dNSHostName attribute, t.uuidDsa = the GUID-based
751 # DNS name of s. Otherwise, t.uuidDsa = (s!parent)!a.
753 # because the uuidDSA is a GUID and not a GUID-base DNS
754 # name. Nor can uuidDsa hold (s!parent)!a if not
755 # dNSHostName. What should have been said is:
757 # t.naDsa = The GUID-based DNS name of s
759 # That would also be correct if transportAddressAttribute
760 # were "mailAddress" because (naDsa) can also correctly
761 # hold the SMTP ISM service address.
763 nastr = "%s._msdcs.%s" % (s_dsa.dsa_guid, self.samdb.forest_dns_name())
765 if ((t_repsFrom.replica_flags &
766 drsuapi.DRSUAPI_DRS_MAIL_REP) != 0x0):
767 t_repsFrom.replica_flags &= ~drsuapi.DRSUAPI_DRS_MAIL_REP
769 t_repsFrom.transport_guid = misc.GUID()
771 # See (NOTE MS-TECH INCORRECT) above
773 # XXX it looks like these conditionals are pointless, because
774 # the state will end up as `t_repsFrom.dns_name1 == nastr` in
775 # either case, BUT the repsFrom thing is magic and assigning
776 # to it alters some flags. So we try not to update it unless
777 # necessary.
778 if t_repsFrom.dns_name1 != nastr:
779 t_repsFrom.dns_name1 = nastr
781 if t_repsFrom.version > 0x1 and t_repsFrom.dns_name2 != nastr:
782 t_repsFrom.dns_name2 = nastr
784 if t_repsFrom.is_modified():
785 DEBUG_FN("modify_repsFrom(): %s" % t_repsFrom)
787 def get_dsa_for_implied_replica(self, n_rep, cn_conn):
788 """If a connection imply a replica, find the relevant DSA
790 Given a NC replica and NTDS Connection, determine if the
791 connection implies a repsFrom tuple should be present from the
792 source DSA listed in the connection to the naming context. If
793 it should be, return the DSA; otherwise return None.
795 Based on part of MS-ADTS 6.2.2.5
797 :param n_rep: NC replica
798 :param cn_conn: NTDS Connection
799 :return: source DSA or None
801 #XXX different conditions for "implies" than MS-ADTS 6.2.2
803 # NTDS Connection must satisfy all the following criteria
804 # to imply a repsFrom tuple is needed:
806 # cn!enabledConnection = true.
807 # cn!options does not contain NTDSCONN_OPT_RODC_TOPOLOGY.
808 # cn!fromServer references an nTDSDSA object.
810 if not cn_conn.is_enabled() or cn_conn.is_rodc_topology():
811 return None
813 s_dnstr = cn_conn.get_from_dnstr()
814 s_dsa = self.get_dsa(s_dnstr)
816 # No DSA matching this source DN string?
817 if s_dsa is None:
818 return None
820 # To imply a repsFrom tuple is needed, each of these
821 # must be True:
823 # An NC replica of the NC "is present" on the DC to
824 # which the nTDSDSA object referenced by cn!fromServer
825 # corresponds.
827 # An NC replica of the NC "should be present" on
828 # the local DC
829 s_rep = s_dsa.get_current_replica(n_rep.nc_dnstr)
831 if s_rep is None or not s_rep.is_present():
832 return None
834 # To imply a repsFrom tuple is needed, each of these
835 # must be True:
837 # The NC replica on the DC referenced by cn!fromServer is
838 # a writable replica or the NC replica that "should be
839 # present" on the local DC is a partial replica.
841 # The NC is not a domain NC, the NC replica that
842 # "should be present" on the local DC is a partial
843 # replica, cn!transportType has no value, or
844 # cn!transportType has an RDN of CN=IP.
846 implied = (not s_rep.is_ro() or n_rep.is_partial()) and \
847 (not n_rep.is_domain() or
848 n_rep.is_partial() or
849 cn_conn.transport_dnstr is None or
850 cn_conn.transport_dnstr.find("CN=IP") == 0)
852 if implied:
853 return s_dsa
854 return None
856 def translate_ntdsconn(self, current_dsa=None):
857 """Adjust repsFrom to match NTDSConnections
859 This function adjusts values of repsFrom abstract attributes of NC
860 replicas on the local DC to match those implied by
861 nTDSConnection objects.
863 Based on [MS-ADTS] 6.2.2.5
865 :param current_dsa: optional DSA on whose behalf we are acting.
866 :return: None
868 count = 0
870 if current_dsa is None:
871 current_dsa = self.my_dsa
873 if current_dsa.is_translate_ntdsconn_disabled():
874 DEBUG_FN("skipping translate_ntdsconn() "
875 "because disabling flag is set")
876 return
878 DEBUG_FN("translate_ntdsconn(): enter")
880 current_rep_table, needed_rep_table = current_dsa.get_rep_tables()
882 # Filled in with replicas we currently have that need deleting
883 delete_reps = set()
885 # We're using the MS notation names here to allow
886 # correlation back to the published algorithm.
888 # n_rep - NC replica (n)
889 # t_repsFrom - tuple (t) in n!repsFrom
890 # s_dsa - Source DSA of the replica. Defined as nTDSDSA
891 # object (s) such that (s!objectGUID = t.uuidDsa)
892 # In our IDL representation of repsFrom the (uuidDsa)
893 # attribute is called (source_dsa_obj_guid)
894 # cn_conn - (cn) is nTDSConnection object and child of the local
895 # DC's nTDSDSA object and (cn!fromServer = s)
896 # s_rep - source DSA replica of n
898 # If we have the replica and its not needed
899 # then we add it to the "to be deleted" list.
900 for dnstr in current_rep_table:
901 if dnstr not in needed_rep_table:
902 delete_reps.add(dnstr)
904 DEBUG_FN('current %d needed %d delete %d' % (len(current_rep_table),
905 len(needed_rep_table), len(delete_reps)))
907 if delete_reps:
908 DEBUG('deleting these reps: %s' % delete_reps)
909 for dnstr in delete_reps:
910 del current_rep_table[dnstr]
912 # Now perform the scan of replicas we'll need
913 # and compare any current repsFrom against the
914 # connections
915 for n_rep in needed_rep_table.values():
917 # load any repsFrom and fsmo roles as we'll
918 # need them during connection translation
919 n_rep.load_repsFrom(self.samdb)
920 n_rep.load_fsmo_roles(self.samdb)
922 # Loop thru the existing repsFrom tupples (if any)
923 # XXX This is a list and could contain duplicates
924 # (multiple load_repsFrom calls)
925 for t_repsFrom in n_rep.rep_repsFrom:
927 # for each tuple t in n!repsFrom, let s be the nTDSDSA
928 # object such that s!objectGUID = t.uuidDsa
929 guidstr = str(t_repsFrom.source_dsa_obj_guid)
930 s_dsa = self.get_dsa_by_guidstr(guidstr)
932 # Source dsa is gone from config (strange)
933 # so cleanup stale repsFrom for unlisted DSA
934 if s_dsa is None:
935 logger.warning("repsFrom source DSA guid (%s) not found" %
936 guidstr)
937 t_repsFrom.to_be_deleted = True
938 continue
940 # Find the connection that this repsFrom would use. If
941 # there isn't a good one (i.e. non-RODC_TOPOLOGY,
942 # meaning non-FRS), we delete the repsFrom.
943 s_dnstr = s_dsa.dsa_dnstr
944 connections = current_dsa.get_connection_by_from_dnstr(s_dnstr)
945 for cn_conn in connections:
946 if not cn_conn.is_rodc_topology():
947 break
948 else:
949 # no break means no non-rodc_topology connection exists
950 t_repsFrom.to_be_deleted = True
951 continue
953 # KCC removes this repsFrom tuple if any of the following
954 # is true:
955 # No NC replica of the NC "is present" on DSA that
956 # would be source of replica
958 # A writable replica of the NC "should be present" on
959 # the local DC, but a partial replica "is present" on
960 # the source DSA
961 s_rep = s_dsa.get_current_replica(n_rep.nc_dnstr)
963 if s_rep is None or not s_rep.is_present() or \
964 (not n_rep.is_ro() and s_rep.is_partial()):
966 t_repsFrom.to_be_deleted = True
967 continue
969 # If the KCC did not remove t from n!repsFrom, it updates t
970 self.modify_repsFrom(n_rep, t_repsFrom, s_rep, s_dsa, cn_conn)
972 # Loop thru connections and add implied repsFrom tuples
973 # for each NTDSConnection under our local DSA if the
974 # repsFrom is not already present
975 for cn_conn in current_dsa.connect_table.values():
977 s_dsa = self.get_dsa_for_implied_replica(n_rep, cn_conn)
978 if s_dsa is None:
979 continue
981 # Loop thru the existing repsFrom tupples (if any) and
982 # if we already have a tuple for this connection then
983 # no need to proceed to add. It will have been changed
984 # to have the correct attributes above
985 for t_repsFrom in n_rep.rep_repsFrom:
986 guidstr = str(t_repsFrom.source_dsa_obj_guid)
987 #XXX what?
988 if s_dsa is self.get_dsa_by_guidstr(guidstr):
989 s_dsa = None
990 break
992 if s_dsa is None:
993 continue
995 # Create a new RepsFromTo and proceed to modify
996 # it according to specification
997 t_repsFrom = RepsFromTo(n_rep.nc_dnstr)
999 t_repsFrom.source_dsa_obj_guid = s_dsa.dsa_guid
1001 s_rep = s_dsa.get_current_replica(n_rep.nc_dnstr)
1003 self.modify_repsFrom(n_rep, t_repsFrom, s_rep, s_dsa, cn_conn)
1005 # Add to our NC repsFrom as this is newly computed
1006 if t_repsFrom.is_modified():
1007 n_rep.rep_repsFrom.append(t_repsFrom)
1009 if self.readonly:
1010 # Display any to be deleted or modified repsFrom
1011 text = n_rep.dumpstr_to_be_deleted()
1012 if text:
1013 logger.info("TO BE DELETED:\n%s" % text)
1014 text = n_rep.dumpstr_to_be_modified()
1015 if text:
1016 logger.info("TO BE MODIFIED:\n%s" % text)
1018 # Peform deletion from our tables but perform
1019 # no database modification
1020 n_rep.commit_repsFrom(self.samdb, ro=True)
1021 else:
1022 # Commit any modified repsFrom to the NC replica
1023 n_rep.commit_repsFrom(self.samdb)
1025 def merge_failed_links(self, ping=None):
1026 """Merge of kCCFailedLinks and kCCFailedLinks from bridgeheads.
1028 The KCC on a writable DC attempts to merge the link and connection
1029 failure information from bridgehead DCs in its own site to help it
1030 identify failed bridgehead DCs.
1032 Based on MS-ADTS 6.2.2.3.2 "Merge of kCCFailedLinks and kCCFailedLinks
1033 from Bridgeheads"
1035 :param ping: An oracle of current bridgehead availability
1036 :return: None
1038 # 1. Queries every bridgehead server in your site (other than yourself)
1039 # 2. For every ntDSConnection that references a server in a different
1040 # site merge all the failure info
1042 # XXX - not implemented yet
1043 if ping is not None:
1044 debug.DEBUG_RED("merge_failed_links() is NOT IMPLEMENTED")
1045 else:
1046 DEBUG_FN("skipping merge_failed_links() because it requires "
1047 "real network connections\n"
1048 "and we weren't asked to --attempt-live-connections")
1050 def setup_graph(self, part):
1051 """Set up an intersite graph
1053 An intersite graph has a Vertex for each site object, a
1054 MultiEdge for each SiteLink object, and a MutliEdgeSet for
1055 each siteLinkBridge object (or implied siteLinkBridge). It
1056 reflects the intersite topology in a slightly more abstract
1057 graph form.
1059 Roughly corresponds to MS-ADTS 6.2.2.3.4.3
1061 :param part: a Partition object
1062 :returns: an InterSiteGraph object
1064 # If 'Bridge all site links' is enabled and Win2k3 bridges required
1065 # is not set
1066 # NTDSTRANSPORT_OPT_BRIDGES_REQUIRED 0x00000002
1067 # No documentation for this however, ntdsapi.h appears to have:
1068 # NTDSSETTINGS_OPT_W2K3_BRIDGES_REQUIRED = 0x00001000
1069 bridges_required = self.my_site.site_options & 0x00001002 == 0
1070 transport_guid = str(self.ip_transport.guid)
1072 g = setup_graph(part, self.site_table, transport_guid,
1073 self.sitelink_table, bridges_required)
1075 if self.verify or self.dot_file_dir is not None:
1076 dot_edges = []
1077 for edge in g.edges:
1078 for a, b in itertools.combinations(edge.vertices, 2):
1079 dot_edges.append((a.site.site_dnstr, b.site.site_dnstr))
1080 verify_properties = ()
1081 verify_and_dot('site_edges', dot_edges, directed=False,
1082 label=self.my_dsa_dnstr,
1083 properties=verify_properties, debug=DEBUG,
1084 verify=self.verify,
1085 dot_file_dir=self.dot_file_dir)
1087 return g
1089 def get_bridgehead(self, site, part, transport, partial_ok, detect_failed):
1090 """Get a bridghead DC for a site.
1092 Part of MS-ADTS 6.2.2.3.4.4
1094 :param site: site object representing for which a bridgehead
1095 DC is desired.
1096 :param part: crossRef for NC to replicate.
1097 :param transport: interSiteTransport object for replication
1098 traffic.
1099 :param partial_ok: True if a DC containing a partial
1100 replica or a full replica will suffice, False if only
1101 a full replica will suffice.
1102 :param detect_failed: True to detect failed DCs and route
1103 replication traffic around them, False to assume no DC
1104 has failed.
1105 :return: dsa object for the bridgehead DC or None
1108 bhs = self.get_all_bridgeheads(site, part, transport,
1109 partial_ok, detect_failed)
1110 if len(bhs) == 0:
1111 debug.DEBUG_MAGENTA("get_bridgehead:\n\tsitedn=%s\n\tbhdn=None" %
1112 site.site_dnstr)
1113 return None
1114 else:
1115 debug.DEBUG_GREEN("get_bridgehead:\n\tsitedn=%s\n\tbhdn=%s" %
1116 (site.site_dnstr, bhs[0].dsa_dnstr))
1117 return bhs[0]
1119 def get_all_bridgeheads(self, site, part, transport,
1120 partial_ok, detect_failed):
1121 """Get all bridghead DCs on a site satisfying the given criteria
1123 Part of MS-ADTS 6.2.2.3.4.4
1125 :param site: site object representing the site for which
1126 bridgehead DCs are desired.
1127 :param part: partition for NC to replicate.
1128 :param transport: interSiteTransport object for
1129 replication traffic.
1130 :param partial_ok: True if a DC containing a partial
1131 replica or a full replica will suffice, False if
1132 only a full replica will suffice.
1133 :param detect_failed: True to detect failed DCs and route
1134 replication traffic around them, FALSE to assume
1135 no DC has failed.
1136 :return: list of dsa object for available bridgehead DCs
1138 bhs = []
1140 if transport.name != "IP":
1141 raise KCCError("get_all_bridgeheads has run into a "
1142 "non-IP transport! %r"
1143 % (transport.name,))
1145 DEBUG_FN("get_all_bridgeheads")
1146 DEBUG_FN(site.rw_dsa_table)
1147 for dsa in site.rw_dsa_table.values():
1149 pdnstr = dsa.get_parent_dnstr()
1151 # IF t!bridgeheadServerListBL has one or more values and
1152 # t!bridgeheadServerListBL does not contain a reference
1153 # to the parent object of dc then skip dc
1154 if ((len(transport.bridgehead_list) != 0 and
1155 pdnstr not in transport.bridgehead_list)):
1156 continue
1158 # IF dc is in the same site as the local DC
1159 # IF a replica of cr!nCName is not in the set of NC replicas
1160 # that "should be present" on dc or a partial replica of the
1161 # NC "should be present" but partialReplicasOkay = FALSE
1162 # Skip dc
1163 if self.my_site.same_site(dsa):
1164 needed, ro, partial = part.should_be_present(dsa)
1165 if not needed or (partial and not partial_ok):
1166 continue
1167 rep = dsa.get_current_replica(part.nc_dnstr)
1169 # ELSE
1170 # IF an NC replica of cr!nCName is not in the set of NC
1171 # replicas that "are present" on dc or a partial replica of
1172 # the NC "is present" but partialReplicasOkay = FALSE
1173 # Skip dc
1174 else:
1175 rep = dsa.get_current_replica(part.nc_dnstr)
1176 if rep is None or (rep.is_partial() and not partial_ok):
1177 continue
1179 # IF AmIRODC() and cr!nCName corresponds to default NC then
1180 # Let dsaobj be the nTDSDSA object of the dc
1181 # IF dsaobj.msDS-Behavior-Version < DS_DOMAIN_FUNCTION_2008
1182 # Skip dc
1183 if self.my_dsa.is_ro() and rep is not None and rep.is_default():
1184 if not dsa.is_minimum_behavior(dsdb.DS_DOMAIN_FUNCTION_2008):
1185 continue
1187 # IF BridgeheadDCFailed(dc!objectGUID, detectFailedDCs) = TRUE
1188 # Skip dc
1189 if self.is_bridgehead_failed(dsa, detect_failed):
1190 DEBUG("bridgehead is failed")
1191 continue
1193 DEBUG_FN("get_all_bridgeheads: dsadn=%s" % dsa.dsa_dnstr)
1194 bhs.append(dsa)
1196 # IF bit NTDSSETTINGS_OPT_IS_RAND_BH_SELECTION_DISABLED is set in
1197 # s!options
1198 # SORT bhs such that all GC servers precede DCs that are not GC
1199 # servers, and otherwise by ascending objectGUID
1200 # ELSE
1201 # SORT bhs in a random order
1202 if site.is_random_bridgehead_disabled():
1203 bhs.sort(sort_dsa_by_gc_and_guid)
1204 else:
1205 random.shuffle(bhs)
1206 debug.DEBUG_YELLOW(bhs)
1207 return bhs
1209 def is_bridgehead_failed(self, dsa, detect_failed):
1210 """Determine whether a given DC is known to be in a failed state
1212 :param dsa: the bridgehead to test
1213 :param detect_failed: True to really check, False to assume no failure
1214 :return: True if and only if the DC should be considered failed
1216 Here we DEPART from the pseudo code spec which appears to be
1217 wrong. It says, in full:
1219 /***** BridgeheadDCFailed *****/
1220 /* Determine whether a given DC is known to be in a failed state.
1221 * IN: objectGUID - objectGUID of the DC's nTDSDSA object.
1222 * IN: detectFailedDCs - TRUE if and only failed DC detection is
1223 * enabled.
1224 * RETURNS: TRUE if and only if the DC should be considered to be in a
1225 * failed state.
1227 BridgeheadDCFailed(IN GUID objectGUID, IN bool detectFailedDCs) : bool
1229 IF bit NTDSSETTINGS_OPT_IS_TOPL_DETECT_STALE_DISABLED is set in
1230 the options attribute of the site settings object for the local
1231 DC's site
1232 RETURN FALSE
1233 ELSEIF a tuple z exists in the kCCFailedLinks or
1234 kCCFailedConnections variables such that z.UUIDDsa =
1235 objectGUID, z.FailureCount > 1, and the current time -
1236 z.TimeFirstFailure > 2 hours
1237 RETURN TRUE
1238 ELSE
1239 RETURN detectFailedDCs
1240 ENDIF
1243 where you will see detectFailedDCs is not behaving as
1244 advertised -- it is acting as a default return code in the
1245 event that a failure is not detected, not a switch turning
1246 detection on or off. Elsewhere the documentation seems to
1247 concur with the comment rather than the code.
1249 if not detect_failed:
1250 return False
1252 # NTDSSETTINGS_OPT_IS_TOPL_DETECT_STALE_DISABLED = 0x00000008
1253 # When DETECT_STALE_DISABLED, we can never know of if
1254 # it's in a failed state
1255 if self.my_site.site_options & 0x00000008:
1256 return False
1258 return self.is_stale_link_connection(dsa)
1260 def create_connection(self, part, rbh, rsite, transport,
1261 lbh, lsite, link_opt, link_sched,
1262 partial_ok, detect_failed):
1263 """Create an nTDSConnection object as specified if it doesn't exist.
1265 Part of MS-ADTS 6.2.2.3.4.5
1267 :param part: crossRef object for the NC to replicate.
1268 :param rbh: nTDSDSA object for DC to act as the
1269 IDL_DRSGetNCChanges server (which is in a site other
1270 than the local DC's site).
1271 :param rsite: site of the rbh
1272 :param transport: interSiteTransport object for the transport
1273 to use for replication traffic.
1274 :param lbh: nTDSDSA object for DC to act as the
1275 IDL_DRSGetNCChanges client (which is in the local DC's site).
1276 :param lsite: site of the lbh
1277 :param link_opt: Replication parameters (aggregated siteLink options,
1278 etc.)
1279 :param link_sched: Schedule specifying the times at which
1280 to begin replicating.
1281 :partial_ok: True if bridgehead DCs containing partial
1282 replicas of the NC are acceptable.
1283 :param detect_failed: True to detect failed DCs and route
1284 replication traffic around them, FALSE to assume no DC
1285 has failed.
1287 rbhs_all = self.get_all_bridgeheads(rsite, part, transport,
1288 partial_ok, False)
1289 rbh_table = {x.dsa_dnstr: x for x in rbhs_all}
1291 debug.DEBUG_GREY("rbhs_all: %s %s" % (len(rbhs_all),
1292 [x.dsa_dnstr for x in rbhs_all]))
1294 # MS-TECH says to compute rbhs_avail but then doesn't use it
1295 # rbhs_avail = self.get_all_bridgeheads(rsite, part, transport,
1296 # partial_ok, detect_failed)
1298 lbhs_all = self.get_all_bridgeheads(lsite, part, transport,
1299 partial_ok, False)
1300 if lbh.is_ro():
1301 lbhs_all.append(lbh)
1303 debug.DEBUG_GREY("lbhs_all: %s %s" % (len(lbhs_all),
1304 [x.dsa_dnstr for x in lbhs_all]))
1306 # MS-TECH says to compute lbhs_avail but then doesn't use it
1307 # lbhs_avail = self.get_all_bridgeheads(lsite, part, transport,
1308 # partial_ok, detect_failed)
1310 # FOR each nTDSConnection object cn such that the parent of cn is
1311 # a DC in lbhsAll and cn!fromServer references a DC in rbhsAll
1312 for ldsa in lbhs_all:
1313 for cn in ldsa.connect_table.values():
1315 rdsa = rbh_table.get(cn.from_dnstr)
1316 if rdsa is None:
1317 continue
1319 debug.DEBUG_DARK_YELLOW("rdsa is %s" % rdsa.dsa_dnstr)
1320 # IF bit NTDSCONN_OPT_IS_GENERATED is set in cn!options and
1321 # NTDSCONN_OPT_RODC_TOPOLOGY is clear in cn!options and
1322 # cn!transportType references t
1323 if ((cn.is_generated() and
1324 not cn.is_rodc_topology() and
1325 cn.transport_guid == transport.guid)):
1327 # IF bit NTDSCONN_OPT_USER_OWNED_SCHEDULE is clear in
1328 # cn!options and cn!schedule != sch
1329 # Perform an originating update to set cn!schedule to
1330 # sched
1331 if ((not cn.is_user_owned_schedule() and
1332 not cn.is_equivalent_schedule(link_sched))):
1333 cn.schedule = link_sched
1334 cn.set_modified(True)
1336 # IF bits NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT and
1337 # NTDSCONN_OPT_USE_NOTIFY are set in cn
1338 if cn.is_override_notify_default() and \
1339 cn.is_use_notify():
1341 # IF bit NTDSSITELINK_OPT_USE_NOTIFY is clear in
1342 # ri.Options
1343 # Perform an originating update to clear bits
1344 # NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT and
1345 # NTDSCONN_OPT_USE_NOTIFY in cn!options
1346 if (link_opt & dsdb.NTDSSITELINK_OPT_USE_NOTIFY) == 0:
1347 cn.options &= \
1348 ~(dsdb.NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT |
1349 dsdb.NTDSCONN_OPT_USE_NOTIFY)
1350 cn.set_modified(True)
1352 # ELSE
1353 else:
1355 # IF bit NTDSSITELINK_OPT_USE_NOTIFY is set in
1356 # ri.Options
1357 # Perform an originating update to set bits
1358 # NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT and
1359 # NTDSCONN_OPT_USE_NOTIFY in cn!options
1360 if (link_opt & dsdb.NTDSSITELINK_OPT_USE_NOTIFY) != 0:
1361 cn.options |= \
1362 (dsdb.NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT |
1363 dsdb.NTDSCONN_OPT_USE_NOTIFY)
1364 cn.set_modified(True)
1366 # IF bit NTDSCONN_OPT_TWOWAY_SYNC is set in cn!options
1367 if cn.is_twoway_sync():
1369 # IF bit NTDSSITELINK_OPT_TWOWAY_SYNC is clear in
1370 # ri.Options
1371 # Perform an originating update to clear bit
1372 # NTDSCONN_OPT_TWOWAY_SYNC in cn!options
1373 if (link_opt & dsdb.NTDSSITELINK_OPT_TWOWAY_SYNC) == 0:
1374 cn.options &= ~dsdb.NTDSCONN_OPT_TWOWAY_SYNC
1375 cn.set_modified(True)
1377 # ELSE
1378 else:
1380 # IF bit NTDSSITELINK_OPT_TWOWAY_SYNC is set in
1381 # ri.Options
1382 # Perform an originating update to set bit
1383 # NTDSCONN_OPT_TWOWAY_SYNC in cn!options
1384 if (link_opt & dsdb.NTDSSITELINK_OPT_TWOWAY_SYNC) != 0:
1385 cn.options |= dsdb.NTDSCONN_OPT_TWOWAY_SYNC
1386 cn.set_modified(True)
1388 # IF bit NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION is set
1389 # in cn!options
1390 if cn.is_intersite_compression_disabled():
1392 # IF bit NTDSSITELINK_OPT_DISABLE_COMPRESSION is clear
1393 # in ri.Options
1394 # Perform an originating update to clear bit
1395 # NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION in
1396 # cn!options
1397 if ((link_opt &
1398 dsdb.NTDSSITELINK_OPT_DISABLE_COMPRESSION) == 0):
1399 cn.options &= \
1400 ~dsdb.NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION
1401 cn.set_modified(True)
1403 # ELSE
1404 else:
1405 # IF bit NTDSSITELINK_OPT_DISABLE_COMPRESSION is set in
1406 # ri.Options
1407 # Perform an originating update to set bit
1408 # NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION in
1409 # cn!options
1410 if ((link_opt &
1411 dsdb.NTDSSITELINK_OPT_DISABLE_COMPRESSION) != 0):
1412 cn.options |= \
1413 dsdb.NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION
1414 cn.set_modified(True)
1416 # Display any modified connection
1417 if self.readonly:
1418 if cn.to_be_modified:
1419 logger.info("TO BE MODIFIED:\n%s" % cn)
1421 ldsa.commit_connections(self.samdb, ro=True)
1422 else:
1423 ldsa.commit_connections(self.samdb)
1424 # ENDFOR
1426 valid_connections = 0
1428 # FOR each nTDSConnection object cn such that cn!parent is
1429 # a DC in lbhsAll and cn!fromServer references a DC in rbhsAll
1430 for ldsa in lbhs_all:
1431 for cn in ldsa.connect_table.values():
1433 rdsa = rbh_table.get(cn.from_dnstr)
1434 if rdsa is None:
1435 continue
1437 debug.DEBUG_DARK_YELLOW("round 2: rdsa is %s" % rdsa.dsa_dnstr)
1439 # IF (bit NTDSCONN_OPT_IS_GENERATED is clear in cn!options or
1440 # cn!transportType references t) and
1441 # NTDSCONN_OPT_RODC_TOPOLOGY is clear in cn!options
1442 if (((not cn.is_generated() or
1443 cn.transport_guid == transport.guid) and
1444 not cn.is_rodc_topology())):
1446 # LET rguid be the objectGUID of the nTDSDSA object
1447 # referenced by cn!fromServer
1448 # LET lguid be (cn!parent)!objectGUID
1450 # IF BridgeheadDCFailed(rguid, detectFailedDCs) = FALSE and
1451 # BridgeheadDCFailed(lguid, detectFailedDCs) = FALSE
1452 # Increment cValidConnections by 1
1453 if ((not self.is_bridgehead_failed(rdsa, detect_failed) and
1454 not self.is_bridgehead_failed(ldsa, detect_failed))):
1455 valid_connections += 1
1457 # IF keepConnections does not contain cn!objectGUID
1458 # APPEND cn!objectGUID to keepConnections
1459 self.kept_connections.add(cn)
1461 # ENDFOR
1462 debug.DEBUG_RED("valid connections %d" % valid_connections)
1463 DEBUG("kept_connections:\n%s" % (self.kept_connections,))
1464 # IF cValidConnections = 0
1465 if valid_connections == 0:
1467 # LET opt be NTDSCONN_OPT_IS_GENERATED
1468 opt = dsdb.NTDSCONN_OPT_IS_GENERATED
1470 # IF bit NTDSSITELINK_OPT_USE_NOTIFY is set in ri.Options
1471 # SET bits NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT and
1472 # NTDSCONN_OPT_USE_NOTIFY in opt
1473 if (link_opt & dsdb.NTDSSITELINK_OPT_USE_NOTIFY) != 0:
1474 opt |= (dsdb.NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT |
1475 dsdb.NTDSCONN_OPT_USE_NOTIFY)
1477 # IF bit NTDSSITELINK_OPT_TWOWAY_SYNC is set in ri.Options
1478 # SET bit NTDSCONN_OPT_TWOWAY_SYNC opt
1479 if (link_opt & dsdb.NTDSSITELINK_OPT_TWOWAY_SYNC) != 0:
1480 opt |= dsdb.NTDSCONN_OPT_TWOWAY_SYNC
1482 # IF bit NTDSSITELINK_OPT_DISABLE_COMPRESSION is set in
1483 # ri.Options
1484 # SET bit NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION in opt
1485 if ((link_opt &
1486 dsdb.NTDSSITELINK_OPT_DISABLE_COMPRESSION) != 0):
1487 opt |= dsdb.NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION
1489 # Perform an originating update to create a new nTDSConnection
1490 # object cn that is a child of lbh, cn!enabledConnection = TRUE,
1491 # cn!options = opt, cn!transportType is a reference to t,
1492 # cn!fromServer is a reference to rbh, and cn!schedule = sch
1493 DEBUG_FN("new connection, KCC dsa: %s" % self.my_dsa.dsa_dnstr)
1494 cn = lbh.new_connection(opt, 0, transport,
1495 rbh.dsa_dnstr, link_sched)
1497 # Display any added connection
1498 if self.readonly:
1499 if cn.to_be_added:
1500 logger.info("TO BE ADDED:\n%s" % cn)
1502 lbh.commit_connections(self.samdb, ro=True)
1503 else:
1504 lbh.commit_connections(self.samdb)
1506 # APPEND cn!objectGUID to keepConnections
1507 self.kept_connections.add(cn)
1509 def add_transports(self, vertex, local_vertex, graph, detect_failed):
1510 """Build a Vertex's transport lists
1512 Each vertex has accept_red_red and accept_black lists that
1513 list what transports they accept under various conditions. The
1514 only transport that is ever accepted is IP, and a dummy extra
1515 transport called "EDGE_TYPE_ALL".
1517 Part of MS-ADTS 6.2.2.3.4.3 -- ColorVertices
1519 :param vertex: the remote vertex we are thinking about
1520 :param local_vertex: the vertex relating to the local site.
1521 :param graph: the intersite graph
1522 :param detect_failed: whether to detect failed links
1523 :return: True if some bridgeheads were not found
1525 # The docs ([MS-ADTS] 6.2.2.3.4.3) say to use local_vertex
1526 # here, but using vertex seems to make more sense. That is,
1527 # the docs want this:
1529 #bh = self.get_bridgehead(vertex.site, vertex.part, transport,
1530 # local_vertex.is_black(), detect_failed)
1532 # TODO WHY?????
1534 vertex.accept_red_red = []
1535 vertex.accept_black = []
1536 found_failed = False
1538 if vertex in graph.connected_vertices:
1539 t_guid = str(self.ip_transport.guid)
1541 bh = self.get_bridgehead(vertex.site, vertex.part,
1542 self.ip_transport,
1543 vertex.is_black(), detect_failed)
1544 if bh is None:
1545 if vertex.site.is_rodc_site():
1546 vertex.accept_red_red.append(t_guid)
1547 else:
1548 found_failed = True
1549 else:
1550 vertex.accept_red_red.append(t_guid)
1551 vertex.accept_black.append(t_guid)
1553 # Add additional transport to ensure another run of Dijkstra
1554 vertex.accept_red_red.append("EDGE_TYPE_ALL")
1555 vertex.accept_black.append("EDGE_TYPE_ALL")
1557 return found_failed
1559 def create_connections(self, graph, part, detect_failed):
1560 """Create intersite NTDSConnections as needed by a partition
1562 Construct an NC replica graph for the NC identified by
1563 the given crossRef, then create any additional nTDSConnection
1564 objects required.
1566 :param graph: site graph.
1567 :param part: crossRef object for NC.
1568 :param detect_failed: True to detect failed DCs and route
1569 replication traffic around them, False to assume no DC
1570 has failed.
1572 Modifies self.kept_connections by adding any connections
1573 deemed to be "in use".
1575 :return: (all_connected, found_failed_dc)
1576 (all_connected) True if the resulting NC replica graph
1577 connects all sites that need to be connected.
1578 (found_failed_dc) True if one or more failed DCs were
1579 detected.
1581 all_connected = True
1582 found_failed = False
1584 DEBUG_FN("create_connections(): enter\n"
1585 "\tpartdn=%s\n\tdetect_failed=%s" %
1586 (part.nc_dnstr, detect_failed))
1588 # XXX - This is a highly abbreviated function from the MS-TECH
1589 # ref. It creates connections between bridgeheads to all
1590 # sites that have appropriate replicas. Thus we are not
1591 # creating a minimum cost spanning tree but instead
1592 # producing a fully connected tree. This should produce
1593 # a full (albeit not optimal cost) replication topology.
1595 my_vertex = Vertex(self.my_site, part)
1596 my_vertex.color_vertex()
1598 for v in graph.vertices:
1599 v.color_vertex()
1600 if self.add_transports(v, my_vertex, graph, False):
1601 found_failed = True
1603 # No NC replicas for this NC in the site of the local DC,
1604 # so no nTDSConnection objects need be created
1605 if my_vertex.is_white():
1606 return all_connected, found_failed
1608 edge_list, n_components = get_spanning_tree_edges(graph,
1609 self.my_site,
1610 label=part.partstr)
1612 DEBUG_FN("%s Number of components: %d" %
1613 (part.nc_dnstr, n_components))
1614 if n_components > 1:
1615 all_connected = False
1617 # LET partialReplicaOkay be TRUE if and only if
1618 # localSiteVertex.Color = COLOR.BLACK
1619 partial_ok = my_vertex.is_black()
1621 # Utilize the IP transport only for now
1622 transport = self.ip_transport
1624 DEBUG("edge_list %s" % edge_list)
1625 for e in edge_list:
1626 # XXX more accurate comparison?
1627 if e.directed and e.vertices[0].site is self.my_site:
1628 continue
1630 if e.vertices[0].site is self.my_site:
1631 rsite = e.vertices[1].site
1632 else:
1633 rsite = e.vertices[0].site
1635 # We don't make connections to our own site as that
1636 # is intrasite topology generator's job
1637 if rsite is self.my_site:
1638 DEBUG("rsite is my_site")
1639 continue
1641 # Determine bridgehead server in remote site
1642 rbh = self.get_bridgehead(rsite, part, transport,
1643 partial_ok, detect_failed)
1644 if rbh is None:
1645 continue
1647 # RODC acts as an BH for itself
1648 # IF AmIRODC() then
1649 # LET lbh be the nTDSDSA object of the local DC
1650 # ELSE
1651 # LET lbh be the result of GetBridgeheadDC(localSiteVertex.ID,
1652 # cr, t, partialReplicaOkay, detectFailedDCs)
1653 if self.my_dsa.is_ro():
1654 lsite = self.my_site
1655 lbh = self.my_dsa
1656 else:
1657 lsite = self.my_site
1658 lbh = self.get_bridgehead(lsite, part, transport,
1659 partial_ok, detect_failed)
1660 # TODO
1661 if lbh is None:
1662 debug.DEBUG_RED("DISASTER! lbh is None")
1663 return False, True
1665 DEBUG_FN("lsite: %s\nrsite: %s" % (lsite, rsite))
1666 DEBUG_FN("vertices %s" % (e.vertices,))
1667 debug.DEBUG_BLUE("bridgeheads\n%s\n%s\n%s" % (lbh, rbh, "-" * 70))
1669 sitelink = e.site_link
1670 if sitelink is None:
1671 link_opt = 0x0
1672 link_sched = None
1673 else:
1674 link_opt = sitelink.options
1675 link_sched = sitelink.schedule
1677 self.create_connection(part, rbh, rsite, transport,
1678 lbh, lsite, link_opt, link_sched,
1679 partial_ok, detect_failed)
1681 return all_connected, found_failed
1683 def create_intersite_connections(self):
1684 """Create NTDSConnections as necessary for all partitions.
1686 Computes an NC replica graph for each NC replica that "should be
1687 present" on the local DC or "is present" on any DC in the same site
1688 as the local DC. For each edge directed to an NC replica on such a
1689 DC from an NC replica on a DC in another site, the KCC creates an
1690 nTDSConnection object to imply that edge if one does not already
1691 exist.
1693 Modifies self.kept_connections - A set of nTDSConnection
1694 objects for edges that are directed
1695 to the local DC's site in one or more NC replica graphs.
1697 :return: True if spanning trees were created for all NC replica
1698 graphs, otherwise False.
1700 all_connected = True
1701 self.kept_connections = set()
1703 # LET crossRefList be the set containing each object o of class
1704 # crossRef such that o is a child of the CN=Partitions child of the
1705 # config NC
1707 # FOR each crossRef object cr in crossRefList
1708 # IF cr!enabled has a value and is false, or if FLAG_CR_NTDS_NC
1709 # is clear in cr!systemFlags, skip cr.
1710 # LET g be the GRAPH return of SetupGraph()
1712 for part in self.part_table.values():
1714 if not part.is_enabled():
1715 continue
1717 if part.is_foreign():
1718 continue
1720 graph = self.setup_graph(part)
1722 # Create nTDSConnection objects, routing replication traffic
1723 # around "failed" DCs.
1724 found_failed = False
1726 connected, found_failed = self.create_connections(graph,
1727 part, True)
1729 DEBUG("with detect_failed: connected %s Found failed %s" %
1730 (connected, found_failed))
1731 if not connected:
1732 all_connected = False
1734 if found_failed:
1735 # One or more failed DCs preclude use of the ideal NC
1736 # replica graph. Add connections for the ideal graph.
1737 self.create_connections(graph, part, False)
1739 return all_connected
1741 def intersite(self, ping):
1742 """Generate the inter-site KCC replica graph and nTDSConnections
1744 As per MS-ADTS 6.2.2.3.
1746 If self.readonly is False, the connections are added to self.samdb.
1748 Produces self.kept_connections which is a set of NTDS
1749 Connections that should be kept during subsequent pruning
1750 process.
1752 After this has run, all sites should be connected in a minimum
1753 spanning tree.
1755 :param ping: An oracle function of remote site availability
1756 :return (True or False): (True) if the produced NC replica
1757 graph connects all sites that need to be connected
1760 # Retrieve my DSA
1761 mydsa = self.my_dsa
1762 mysite = self.my_site
1763 all_connected = True
1765 DEBUG_FN("intersite(): enter")
1767 # Determine who is the ISTG
1768 if self.readonly:
1769 mysite.select_istg(self.samdb, mydsa, ro=True)
1770 else:
1771 mysite.select_istg(self.samdb, mydsa, ro=False)
1773 # Test whether local site has topology disabled
1774 if mysite.is_intersite_topology_disabled():
1775 DEBUG_FN("intersite(): exit disabled all_connected=%d" %
1776 all_connected)
1777 return all_connected
1779 if not mydsa.is_istg():
1780 DEBUG_FN("intersite(): exit not istg all_connected=%d" %
1781 all_connected)
1782 return all_connected
1784 self.merge_failed_links(ping)
1786 # For each NC with an NC replica that "should be present" on the
1787 # local DC or "is present" on any DC in the same site as the
1788 # local DC, the KCC constructs a site graph--a precursor to an NC
1789 # replica graph. The site connectivity for a site graph is defined
1790 # by objects of class interSiteTransport, siteLink, and
1791 # siteLinkBridge in the config NC.
1793 all_connected = self.create_intersite_connections()
1795 DEBUG_FN("intersite(): exit all_connected=%d" % all_connected)
1796 return all_connected
1798 def update_rodc_connection(self):
1799 """Updates the RODC NTFRS connection object.
1801 If the local DSA is not an RODC, this does nothing.
1803 if not self.my_dsa.is_ro():
1804 return
1806 # Given an nTDSConnection object cn1, such that cn1.options contains
1807 # NTDSCONN_OPT_RODC_TOPOLOGY, and another nTDSConnection object cn2,
1808 # does not contain NTDSCONN_OPT_RODC_TOPOLOGY, modify cn1 to ensure
1809 # that the following is true:
1811 # cn1.fromServer = cn2.fromServer
1812 # cn1.schedule = cn2.schedule
1814 # If no such cn2 can be found, cn1 is not modified.
1815 # If no such cn1 can be found, nothing is modified by this task.
1817 all_connections = self.my_dsa.connect_table.values()
1818 ro_connections = [x for x in all_connections if x.is_rodc_topology()]
1819 rw_connections = [x for x in all_connections
1820 if x not in ro_connections]
1822 # XXX here we are dealing with multiple RODC_TOPO connections,
1823 # if they exist. It is not clear whether the spec means that
1824 # or if it ever arises.
1825 if rw_connections and ro_connections:
1826 for con in ro_connections:
1827 cn2 = rw_connections[0]
1828 con.from_dnstr = cn2.from_dnstr
1829 con.schedule = cn2.schedule
1830 con.to_be_modified = True
1832 self.my_dsa.commit_connections(self.samdb, ro=self.readonly)
1834 def intrasite_max_node_edges(self, node_count):
1835 """Find the maximum number of edges directed to an intrasite node
1837 The KCC does not create more than 50 edges directed to a
1838 single DC. To optimize replication, we compute that each node
1839 should have n+2 total edges directed to it such that (n) is
1840 the smallest non-negative integer satisfying
1841 (node_count <= 2*(n*n) + 6*n + 7)
1843 (If the number of edges is m (i.e. n + 2), that is the same as
1844 2 * m*m - 2 * m + 3). We think in terms of n because that is
1845 the number of extra connections over the double directed ring
1846 that exists by default.
1848 edges n nodecount
1849 2 0 7
1850 3 1 15
1851 4 2 27
1852 5 3 43
1854 50 48 4903
1856 :param node_count: total number of nodes in the replica graph
1858 The intention is that there should be no more than 3 hops
1859 between any two DSAs at a site. With up to 7 nodes the 2 edges
1860 of the ring are enough; any configuration of extra edges with
1861 8 nodes will be enough. It is less clear that the 3 hop
1862 guarantee holds at e.g. 15 nodes in degenerate cases, but
1863 those are quite unlikely given the extra edges are randomly
1864 arranged.
1866 :param node_count: the number of nodes in the site
1867 "return: The desired maximum number of connections
1869 n = 0
1870 while True:
1871 if node_count <= (2 * (n * n) + (6 * n) + 7):
1872 break
1873 n = n + 1
1874 n = n + 2
1875 if n < 50:
1876 return n
1877 return 50
1879 def construct_intrasite_graph(self, site_local, dc_local,
1880 nc_x, gc_only, detect_stale):
1881 """Create an intrasite graph using given parameters
1883 This might be called a number of times per site with different
1884 parameters.
1886 Based on [MS-ADTS] 6.2.2.2
1888 :param site_local: site for which we are working
1889 :param dc_local: local DC that potentially needs a replica
1890 :param nc_x: naming context (x) that we are testing if it
1891 "should be present" on the local DC
1892 :param gc_only: Boolean - only consider global catalog servers
1893 :param detect_stale: Boolean - check whether links seems down
1894 :return: None
1896 # We're using the MS notation names here to allow
1897 # correlation back to the published algorithm.
1899 # nc_x - naming context (x) that we are testing if it
1900 # "should be present" on the local DC
1901 # f_of_x - replica (f) found on a DC (s) for NC (x)
1902 # dc_s - DC where f_of_x replica was found
1903 # dc_local - local DC that potentially needs a replica
1904 # (f_of_x)
1905 # r_list - replica list R
1906 # p_of_x - replica (p) is partial and found on a DC (s)
1907 # for NC (x)
1908 # l_of_x - replica (l) is the local replica for NC (x)
1909 # that should appear on the local DC
1910 # r_len = is length of replica list |R|
1912 # If the DSA doesn't need a replica for this
1913 # partition (NC x) then continue
1914 needed, ro, partial = nc_x.should_be_present(dc_local)
1916 debug.DEBUG_YELLOW("construct_intrasite_graph(): enter" +
1917 "\n\tgc_only=%d" % gc_only +
1918 "\n\tdetect_stale=%d" % detect_stale +
1919 "\n\tneeded=%s" % needed +
1920 "\n\tro=%s" % ro +
1921 "\n\tpartial=%s" % partial +
1922 "\n%s" % nc_x)
1924 if not needed:
1925 debug.DEBUG_RED("%s lacks 'should be present' status, "
1926 "aborting construct_intersite_graph!" %
1927 nc_x.nc_dnstr)
1928 return
1930 # Create a NCReplica that matches what the local replica
1931 # should say. We'll use this below in our r_list
1932 l_of_x = NCReplica(dc_local.dsa_dnstr, dc_local.dsa_guid,
1933 nc_x.nc_dnstr)
1935 l_of_x.identify_by_basedn(self.samdb)
1937 l_of_x.rep_partial = partial
1938 l_of_x.rep_ro = ro
1940 # Add this replica that "should be present" to the
1941 # needed replica table for this DSA
1942 dc_local.add_needed_replica(l_of_x)
1944 # Replica list
1946 # Let R be a sequence containing each writable replica f of x
1947 # such that f "is present" on a DC s satisfying the following
1948 # criteria:
1950 # * s is a writable DC other than the local DC.
1952 # * s is in the same site as the local DC.
1954 # * If x is a read-only full replica and x is a domain NC,
1955 # then the DC's functional level is at least
1956 # DS_BEHAVIOR_WIN2008.
1958 # * Bit NTDSSETTINGS_OPT_IS_TOPL_DETECT_STALE_DISABLED is set
1959 # in the options attribute of the site settings object for
1960 # the local DC's site, or no tuple z exists in the
1961 # kCCFailedLinks or kCCFailedConnections variables such
1962 # that z.UUIDDsa is the objectGUID of the nTDSDSA object
1963 # for s, z.FailureCount > 0, and the current time -
1964 # z.TimeFirstFailure > 2 hours.
1966 r_list = []
1968 # We'll loop thru all the DSAs looking for
1969 # writeable NC replicas that match the naming
1970 # context dn for (nc_x)
1972 for dc_s in self.my_site.dsa_table.values():
1973 # If this partition (nc_x) doesn't appear as a
1974 # replica (f_of_x) on (dc_s) then continue
1975 if not nc_x.nc_dnstr in dc_s.current_rep_table:
1976 continue
1978 # Pull out the NCReplica (f) of (x) with the dn
1979 # that matches NC (x) we are examining.
1980 f_of_x = dc_s.current_rep_table[nc_x.nc_dnstr]
1982 # Replica (f) of NC (x) must be writable
1983 if f_of_x.is_ro():
1984 continue
1986 # Replica (f) of NC (x) must satisfy the
1987 # "is present" criteria for DC (s) that
1988 # it was found on
1989 if not f_of_x.is_present():
1990 continue
1992 # DC (s) must be a writable DSA other than
1993 # my local DC. In other words we'd only replicate
1994 # from other writable DC
1995 if dc_s.is_ro() or dc_s is dc_local:
1996 continue
1998 # Certain replica graphs are produced only
1999 # for global catalogs, so test against
2000 # method input parameter
2001 if gc_only and not dc_s.is_gc():
2002 continue
2004 # DC (s) must be in the same site as the local DC
2005 # as this is the intra-site algorithm. This is
2006 # handled by virtue of placing DSAs in per
2007 # site objects (see enclosing for() loop)
2009 # If NC (x) is intended to be read-only full replica
2010 # for a domain NC on the target DC then the source
2011 # DC should have functional level at minimum WIN2008
2013 # Effectively we're saying that in order to replicate
2014 # to a targeted RODC (which was introduced in Windows 2008)
2015 # then we have to replicate from a DC that is also minimally
2016 # at that level.
2018 # You can also see this requirement in the MS special
2019 # considerations for RODC which state that to deploy
2020 # an RODC, at least one writable domain controller in
2021 # the domain must be running Windows Server 2008
2022 if ro and not partial and nc_x.nc_type == NCType.domain:
2023 if not dc_s.is_minimum_behavior(dsdb.DS_DOMAIN_FUNCTION_2008):
2024 continue
2026 # If we haven't been told to turn off stale connection
2027 # detection and this dsa has a stale connection then
2028 # continue
2029 if detect_stale and self.is_stale_link_connection(dc_s):
2030 continue
2032 # Replica meets criteria. Add it to table indexed
2033 # by the GUID of the DC that it appears on
2034 r_list.append(f_of_x)
2036 # If a partial (not full) replica of NC (x) "should be present"
2037 # on the local DC, append to R each partial replica (p of x)
2038 # such that p "is present" on a DC satisfying the same
2039 # criteria defined above for full replica DCs.
2041 # XXX This loop and the previous one differ only in whether
2042 # the replica is partial or not. here we only accept partial
2043 # (because we're partial); before we only accepted full. Order
2044 # doen't matter (the list is sorted a few lines down) so these
2045 # loops could easily be merged. Or this could be a helper
2046 # function.
2048 if partial:
2049 # Now we loop thru all the DSAs looking for
2050 # partial NC replicas that match the naming
2051 # context dn for (NC x)
2052 for dc_s in self.my_site.dsa_table.values():
2054 # If this partition NC (x) doesn't appear as a
2055 # replica (p) of NC (x) on the dsa DC (s) then
2056 # continue
2057 if not nc_x.nc_dnstr in dc_s.current_rep_table:
2058 continue
2060 # Pull out the NCReplica with the dn that
2061 # matches NC (x) we are examining.
2062 p_of_x = dc_s.current_rep_table[nc_x.nc_dnstr]
2064 # Replica (p) of NC (x) must be partial
2065 if not p_of_x.is_partial():
2066 continue
2068 # Replica (p) of NC (x) must satisfy the
2069 # "is present" criteria for DC (s) that
2070 # it was found on
2071 if not p_of_x.is_present():
2072 continue
2074 # DC (s) must be a writable DSA other than
2075 # my DSA. In other words we'd only replicate
2076 # from other writable DSA
2077 if dc_s.is_ro() or dc_s is dc_local:
2078 continue
2080 # Certain replica graphs are produced only
2081 # for global catalogs, so test against
2082 # method input parameter
2083 if gc_only and not dc_s.is_gc():
2084 continue
2086 # If we haven't been told to turn off stale connection
2087 # detection and this dsa has a stale connection then
2088 # continue
2089 if detect_stale and self.is_stale_link_connection(dc_s):
2090 continue
2092 # Replica meets criteria. Add it to table indexed
2093 # by the GUID of the DSA that it appears on
2094 r_list.append(p_of_x)
2096 # Append to R the NC replica that "should be present"
2097 # on the local DC
2098 r_list.append(l_of_x)
2100 r_list.sort(sort_replica_by_dsa_guid)
2101 r_len = len(r_list)
2103 max_node_edges = self.intrasite_max_node_edges(r_len)
2105 # Add a node for each r_list element to the replica graph
2106 graph_list = []
2107 for rep in r_list:
2108 node = GraphNode(rep.rep_dsa_dnstr, max_node_edges)
2109 graph_list.append(node)
2111 # For each r(i) from (0 <= i < |R|-1)
2112 i = 0
2113 while i < (r_len-1):
2114 # Add an edge from r(i) to r(i+1) if r(i) is a full
2115 # replica or r(i+1) is a partial replica
2116 if not r_list[i].is_partial() or r_list[i+1].is_partial():
2117 graph_list[i+1].add_edge_from(r_list[i].rep_dsa_dnstr)
2119 # Add an edge from r(i+1) to r(i) if r(i+1) is a full
2120 # replica or ri is a partial replica.
2121 if not r_list[i+1].is_partial() or r_list[i].is_partial():
2122 graph_list[i].add_edge_from(r_list[i+1].rep_dsa_dnstr)
2123 i = i + 1
2125 # Add an edge from r|R|-1 to r0 if r|R|-1 is a full replica
2126 # or r0 is a partial replica.
2127 if not r_list[r_len-1].is_partial() or r_list[0].is_partial():
2128 graph_list[0].add_edge_from(r_list[r_len-1].rep_dsa_dnstr)
2130 # Add an edge from r0 to r|R|-1 if r0 is a full replica or
2131 # r|R|-1 is a partial replica.
2132 if not r_list[0].is_partial() or r_list[r_len-1].is_partial():
2133 graph_list[r_len-1].add_edge_from(r_list[0].rep_dsa_dnstr)
2135 DEBUG("r_list is length %s" % len(r_list))
2136 DEBUG('\n'.join(str((x.rep_dsa_guid, x.rep_dsa_dnstr))
2137 for x in r_list))
2139 do_dot_files = self.dot_file_dir is not None and self.debug
2140 if self.verify or do_dot_files:
2141 dot_edges = []
2142 dot_vertices = set()
2143 for v1 in graph_list:
2144 dot_vertices.add(v1.dsa_dnstr)
2145 for v2 in v1.edge_from:
2146 dot_edges.append((v2, v1.dsa_dnstr))
2147 dot_vertices.add(v2)
2149 verify_properties = ('connected', 'directed_double_ring_or_small')
2150 verify_and_dot('intrasite_pre_ntdscon', dot_edges, dot_vertices,
2151 label='%s__%s__%s' % (site_local.site_dnstr,
2152 nctype_lut[nc_x.nc_type],
2153 nc_x.nc_dnstr),
2154 properties=verify_properties, debug=DEBUG,
2155 verify=self.verify,
2156 dot_file_dir=self.dot_file_dir,
2157 directed=True)
2159 # For each existing nTDSConnection object implying an edge
2160 # from rj of R to ri such that j != i, an edge from rj to ri
2161 # is not already in the graph, and the total edges directed
2162 # to ri is less than n+2, the KCC adds that edge to the graph.
2163 for vertex in graph_list:
2164 dsa = self.my_site.dsa_table[vertex.dsa_dnstr]
2165 for connect in dsa.connect_table.values():
2166 remote = connect.from_dnstr
2167 if remote in self.my_site.dsa_table:
2168 vertex.add_edge_from(remote)
2170 DEBUG('reps are: %s' % ' '.join(x.rep_dsa_dnstr for x in r_list))
2171 DEBUG('dsas are: %s' % ' '.join(x.dsa_dnstr for x in graph_list))
2173 for tnode in graph_list:
2174 # To optimize replication latency in sites with many NC
2175 # replicas, the KCC adds new edges directed to ri to bring
2176 # the total edges to n+2, where the NC replica rk of R
2177 # from which the edge is directed is chosen at random such
2178 # that k != i and an edge from rk to ri is not already in
2179 # the graph.
2181 # Note that the KCC tech ref does not give a number for
2182 # the definition of "sites with many NC replicas". At a
2183 # bare minimum to satisfy n+2 edges directed at a node we
2184 # have to have at least three replicas in |R| (i.e. if n
2185 # is zero then at least replicas from two other graph
2186 # nodes may direct edges to us).
2187 if r_len >= 3 and not tnode.has_sufficient_edges():
2188 candidates = [x for x in graph_list if
2189 (x is not tnode and
2190 x.dsa_dnstr not in tnode.edge_from)]
2192 debug.DEBUG_BLUE("looking for random link for %s. r_len %d, "
2193 "graph len %d candidates %d"
2194 % (tnode.dsa_dnstr, r_len, len(graph_list),
2195 len(candidates)))
2197 DEBUG("candidates %s" % [x.dsa_dnstr for x in candidates])
2199 while candidates and not tnode.has_sufficient_edges():
2200 other = random.choice(candidates)
2201 DEBUG("trying to add candidate %s" % other.dsa_dstr)
2202 if not tnode.add_edge_from(other):
2203 debug.DEBUG_RED("could not add %s" % other.dsa_dstr)
2204 candidates.remove(other)
2205 else:
2206 DEBUG_FN("not adding links to %s: nodes %s, links is %s/%s" %
2207 (tnode.dsa_dnstr, r_len, len(tnode.edge_from),
2208 tnode.max_edges))
2210 # Print the graph node in debug mode
2211 DEBUG_FN("%s" % tnode)
2213 # For each edge directed to the local DC, ensure a nTDSConnection
2214 # points to us that satisfies the KCC criteria
2216 if tnode.dsa_dnstr == dc_local.dsa_dnstr:
2217 tnode.add_connections_from_edges(dc_local)
2219 if self.verify or do_dot_files:
2220 dot_edges = []
2221 dot_vertices = set()
2222 for v1 in graph_list:
2223 dot_vertices.add(v1.dsa_dnstr)
2224 for v2 in v1.edge_from:
2225 dot_edges.append((v2, v1.dsa_dnstr))
2226 dot_vertices.add(v2)
2228 verify_properties = ('connected', 'directed_double_ring_or_small')
2229 verify_and_dot('intrasite_post_ntdscon', dot_edges, dot_vertices,
2230 label='%s__%s__%s' % (site_local.site_dnstr,
2231 nctype_lut[nc_x.nc_type],
2232 nc_x.nc_dnstr),
2233 properties=verify_properties, debug=DEBUG,
2234 verify=self.verify,
2235 dot_file_dir=self.dot_file_dir,
2236 directed=True)
2238 def intrasite(self):
2239 """Generate the intrasite KCC connections
2241 As per MS-ADTS 6.2.2.2.
2243 If self.readonly is False, the connections are added to self.samdb.
2245 After this call, all DCs in each site with more than 3 DCs
2246 should be connected in a bidirectional ring. If a site has 2
2247 DCs, they will bidirectionally connected. Sites with many DCs
2248 may have arbitrary extra connections.
2250 :return: None
2252 mydsa = self.my_dsa
2254 DEBUG_FN("intrasite(): enter")
2256 # Test whether local site has topology disabled
2257 mysite = self.my_site
2258 if mysite.is_intrasite_topology_disabled():
2259 return
2261 detect_stale = (not mysite.is_detect_stale_disabled())
2262 for connect in mydsa.connect_table.values():
2263 if connect.to_be_added:
2264 debug.DEBUG_CYAN("TO BE ADDED:\n%s" % connect)
2266 # Loop thru all the partitions, with gc_only False
2267 for partdn, part in self.part_table.items():
2268 self.construct_intrasite_graph(mysite, mydsa, part, False,
2269 detect_stale)
2270 for connect in mydsa.connect_table.values():
2271 if connect.to_be_added:
2272 debug.DEBUG_BLUE("TO BE ADDED:\n%s" % connect)
2274 # If the DC is a GC server, the KCC constructs an additional NC
2275 # replica graph (and creates nTDSConnection objects) for the
2276 # config NC as above, except that only NC replicas that "are present"
2277 # on GC servers are added to R.
2278 for connect in mydsa.connect_table.values():
2279 if connect.to_be_added:
2280 debug.DEBUG_YELLOW("TO BE ADDED:\n%s" % connect)
2282 # Do it again, with gc_only True
2283 for partdn, part in self.part_table.items():
2284 if part.is_config():
2285 self.construct_intrasite_graph(mysite, mydsa, part, True,
2286 detect_stale)
2288 # The DC repeats the NC replica graph computation and nTDSConnection
2289 # creation for each of the NC replica graphs, this time assuming
2290 # that no DC has failed. It does so by re-executing the steps as
2291 # if the bit NTDSSETTINGS_OPT_IS_TOPL_DETECT_STALE_DISABLED were
2292 # set in the options attribute of the site settings object for
2293 # the local DC's site. (ie. we set "detec_stale" flag to False)
2294 for connect in mydsa.connect_table.values():
2295 if connect.to_be_added:
2296 debug.DEBUG_BLUE("TO BE ADDED:\n%s" % connect)
2298 # Loop thru all the partitions.
2299 for partdn, part in self.part_table.items():
2300 self.construct_intrasite_graph(mysite, mydsa, part, False,
2301 False) # don't detect stale
2303 # If the DC is a GC server, the KCC constructs an additional NC
2304 # replica graph (and creates nTDSConnection objects) for the
2305 # config NC as above, except that only NC replicas that "are present"
2306 # on GC servers are added to R.
2307 for connect in mydsa.connect_table.values():
2308 if connect.to_be_added:
2309 debug.DEBUG_RED("TO BE ADDED:\n%s" % connect)
2311 for partdn, part in self.part_table.items():
2312 if part.is_config():
2313 self.construct_intrasite_graph(mysite, mydsa, part, True,
2314 False) # don't detect stale
2316 if self.readonly:
2317 # Display any to be added or modified repsFrom
2318 for connect in mydsa.connect_table.values():
2319 if connect.to_be_deleted:
2320 logger.info("TO BE DELETED:\n%s" % connect)
2321 if connect.to_be_modified:
2322 logger.info("TO BE MODIFIED:\n%s" % connect)
2323 if connect.to_be_added:
2324 debug.DEBUG_GREEN("TO BE ADDED:\n%s" % connect)
2326 mydsa.commit_connections(self.samdb, ro=True)
2327 else:
2328 # Commit any newly created connections to the samdb
2329 mydsa.commit_connections(self.samdb)
2331 def list_dsas(self):
2332 """Compile a comprehensive list of DSA DNs
2334 These are all the DSAs on all the sites that KCC would be
2335 dealing with.
2337 This method is not idempotent and may not work correctly in
2338 sequence with KCC.run().
2340 :return: a list of DSA DN strings.
2342 self.load_my_site()
2343 self.load_my_dsa()
2345 self.load_all_sites()
2346 self.load_all_partitions()
2347 self.load_ip_transport()
2348 self.load_all_sitelinks()
2349 dsas = []
2350 for site in self.site_table.values():
2351 dsas.extend([dsa.dsa_dnstr.replace('CN=NTDS Settings,', '', 1)
2352 for dsa in site.dsa_table.values()])
2353 return dsas
2355 def load_samdb(self, dburl, lp, creds):
2356 """Load the database using an url, loadparm, and credentials
2358 :param dburl: a database url.
2359 :param lp: a loadparm object.
2360 :param creds: a Credentials object.
2362 self.samdb = SamDB(url=dburl,
2363 session_info=system_session(),
2364 credentials=creds, lp=lp)
2366 def plot_all_connections(self, basename, verify_properties=()):
2367 """Helper function to plot and verify NTDSConnections
2369 :param basename: an identifying string to use in filenames and logs.
2370 :param verify_properties: properties to verify (default empty)
2372 verify = verify_properties and self.verify
2373 if not verify and self.dot_file_dir is None:
2374 return
2376 dot_edges = []
2377 dot_vertices = []
2378 edge_colours = []
2379 vertex_colours = []
2381 for dsa in self.dsa_by_dnstr.values():
2382 dot_vertices.append(dsa.dsa_dnstr)
2383 if dsa.is_ro():
2384 vertex_colours.append('#cc0000')
2385 else:
2386 vertex_colours.append('#0000cc')
2387 for con in dsa.connect_table.values():
2388 if con.is_rodc_topology():
2389 edge_colours.append('red')
2390 else:
2391 edge_colours.append('blue')
2392 dot_edges.append((con.from_dnstr, dsa.dsa_dnstr))
2394 verify_and_dot(basename, dot_edges, vertices=dot_vertices,
2395 label=self.my_dsa_dnstr,
2396 properties=verify_properties, debug=DEBUG,
2397 verify=verify, dot_file_dir=self.dot_file_dir,
2398 directed=True, edge_colors=edge_colours,
2399 vertex_colors=vertex_colours)
2401 def run(self, dburl, lp, creds, forced_local_dsa=None,
2402 forget_local_links=False, forget_intersite_links=False,
2403 attempt_live_connections=False):
2404 """Perform a KCC run, possibly updating repsFrom topology
2406 :param dburl: url of the database to work with.
2407 :param lp: a loadparm object.
2408 :param creds: a Credentials object.
2409 :param forced_local_dsa: pretend to be on the DSA with this dn_str
2410 :param forget_local_links: calculate as if no connections existed
2411 (boolean, default False)
2412 :param forget_intersite_links: calculate with only intrasite connection
2413 (boolean, default False)
2414 :param attempt_live_connections: attempt to connect to remote DSAs to
2415 determine link availability (boolean, default False)
2416 :return: 1 on error, 0 otherwise
2418 # We may already have a samdb setup if we are
2419 # currently importing an ldif for a test run
2420 if self.samdb is None:
2421 try:
2422 self.load_samdb(dburl, lp, creds)
2423 except ldb.LdbError, (num, msg):
2424 logger.error("Unable to open sam database %s : %s" %
2425 (dburl, msg))
2426 return 1
2428 if forced_local_dsa:
2429 self.samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" %
2430 forced_local_dsa)
2432 try:
2433 # Setup
2434 self.load_my_site()
2435 self.load_my_dsa()
2437 self.load_all_sites()
2438 self.load_all_partitions()
2439 self.load_ip_transport()
2440 self.load_all_sitelinks()
2442 if self.verify or self.dot_file_dir is not None:
2443 guid_to_dnstr = {}
2444 for site in self.site_table.values():
2445 guid_to_dnstr.update((str(dsa.dsa_guid), dnstr)
2446 for dnstr, dsa
2447 in site.dsa_table.items())
2449 self.plot_all_connections('dsa_initial')
2451 dot_edges = []
2452 current_reps, needed_reps = self.my_dsa.get_rep_tables()
2453 for dnstr, c_rep in current_reps.items():
2454 DEBUG("c_rep %s" % c_rep)
2455 dot_edges.append((self.my_dsa.dsa_dnstr, dnstr))
2457 verify_and_dot('dsa_repsFrom_initial', dot_edges,
2458 directed=True, label=self.my_dsa_dnstr,
2459 properties=(), debug=DEBUG, verify=self.verify,
2460 dot_file_dir=self.dot_file_dir)
2462 dot_edges = []
2463 for site in self.site_table.values():
2464 for dsa in site.dsa_table.values():
2465 current_reps, needed_reps = dsa.get_rep_tables()
2466 for dn_str, rep in current_reps.items():
2467 for reps_from in rep.rep_repsFrom:
2468 DEBUG("rep %s" % rep)
2469 dsa_guid = str(reps_from.source_dsa_obj_guid)
2470 dsa_dn = guid_to_dnstr[dsa_guid]
2471 dot_edges.append((dsa.dsa_dnstr, dsa_dn))
2473 verify_and_dot('dsa_repsFrom_initial_all', dot_edges,
2474 directed=True, label=self.my_dsa_dnstr,
2475 properties=(), debug=DEBUG, verify=self.verify,
2476 dot_file_dir=self.dot_file_dir)
2478 dot_edges = []
2479 for link in self.sitelink_table.values():
2480 for a, b in itertools.combinations(link.site_list, 2):
2481 dot_edges.append((str(a), str(b)))
2482 properties = ('connected',)
2483 verify_and_dot('dsa_sitelink_initial', dot_edges,
2484 directed=False,
2485 label=self.my_dsa_dnstr, properties=properties,
2486 debug=DEBUG, verify=self.verify,
2487 dot_file_dir=self.dot_file_dir)
2489 if forget_local_links:
2490 for dsa in self.my_site.dsa_table.values():
2491 dsa.connect_table = {k: v for k, v in
2492 dsa.connect_table.items()
2493 if v.is_rodc_topology()}
2494 self.plot_all_connections('dsa_forgotten_local')
2496 if forget_intersite_links:
2497 for site in self.site_table.values():
2498 for dsa in site.dsa_table.values():
2499 dsa.connect_table = {k: v for k, v in
2500 dsa.connect_table.items()
2501 if site is self.my_site and
2502 v.is_rodc_topology()}
2504 self.plot_all_connections('dsa_forgotten_all')
2506 if attempt_live_connections:
2507 # Encapsulates lp and creds in a function that
2508 # attempts connections to remote DSAs.
2509 def ping(self, dnsname):
2510 try:
2511 drs_utils.drsuapi_connect(dnsname, self.lp, self.creds)
2512 except drs_utils.drsException:
2513 return False
2514 return True
2515 else:
2516 ping = None
2517 # These are the published steps (in order) for the
2518 # MS-TECH description of the KCC algorithm ([MS-ADTS] 6.2.2)
2520 # Step 1
2521 self.refresh_failed_links_connections(ping)
2523 # Step 2
2524 self.intrasite()
2526 # Step 3
2527 all_connected = self.intersite(ping)
2529 # Step 4
2530 self.remove_unneeded_ntdsconn(all_connected)
2532 # Step 5
2533 self.translate_ntdsconn()
2535 # Step 6
2536 self.remove_unneeded_failed_links_connections()
2538 # Step 7
2539 self.update_rodc_connection()
2541 if self.verify or self.dot_file_dir is not None:
2542 self.plot_all_connections('dsa_final',
2543 ('connected',))
2545 debug.DEBUG_MAGENTA("there are %d dsa guids" %
2546 len(guid_to_dnstr))
2548 dot_edges = []
2549 edge_colors = []
2550 my_dnstr = self.my_dsa.dsa_dnstr
2551 current_reps, needed_reps = self.my_dsa.get_rep_tables()
2552 for dnstr, n_rep in needed_reps.items():
2553 for reps_from in n_rep.rep_repsFrom:
2554 guid_str = str(reps_from.source_dsa_obj_guid)
2555 dot_edges.append((my_dnstr, guid_to_dnstr[guid_str]))
2556 edge_colors.append('#' + str(n_rep.nc_guid)[:6])
2558 verify_and_dot('dsa_repsFrom_final', dot_edges, directed=True,
2559 label=self.my_dsa_dnstr,
2560 properties=(), debug=DEBUG, verify=self.verify,
2561 dot_file_dir=self.dot_file_dir,
2562 edge_colors=edge_colors)
2564 dot_edges = []
2566 for site in self.site_table.values():
2567 for dsa in site.dsa_table.values():
2568 current_reps, needed_reps = dsa.get_rep_tables()
2569 for n_rep in needed_reps.values():
2570 for reps_from in n_rep.rep_repsFrom:
2571 dsa_guid = str(reps_from.source_dsa_obj_guid)
2572 dsa_dn = guid_to_dnstr[dsa_guid]
2573 dot_edges.append((dsa.dsa_dnstr, dsa_dn))
2575 verify_and_dot('dsa_repsFrom_final_all', dot_edges,
2576 directed=True, label=self.my_dsa_dnstr,
2577 properties=(), debug=DEBUG, verify=self.verify,
2578 dot_file_dir=self.dot_file_dir)
2580 except:
2581 raise
2583 return 0
2585 def import_ldif(self, dburl, lp, creds, ldif_file, forced_local_dsa=None):
2586 """Import relevant objects and attributes from an LDIF file.
2588 The point of this function is to allow a programmer/debugger to
2589 import an LDIF file with non-security relevent information that
2590 was previously extracted from a DC database. The LDIF file is used
2591 to create a temporary abbreviated database. The KCC algorithm can
2592 then run against this abbreviated database for debug or test
2593 verification that the topology generated is computationally the
2594 same between different OSes and algorithms.
2596 :param dburl: path to the temporary abbreviated db to create
2597 :param lp: a loadparm object.
2598 :param cred: a Credentials object.
2599 :param ldif_file: path to the ldif file to import
2600 :param forced_local_dsa: perform KCC from this DSA's point of view
2601 :return: zero on success, 1 on error
2603 try:
2604 self.samdb = ldif_import_export.ldif_to_samdb(dburl, lp, ldif_file,
2605 forced_local_dsa)
2606 except ldif_import_export.LdifError, e:
2607 print >> sys.stderr, e
2608 return 1
2609 return 0
2611 def export_ldif(self, dburl, lp, creds, ldif_file):
2612 """Save KCC relevant details to an ldif file
2614 The point of this function is to allow a programmer/debugger to
2615 extract an LDIF file with non-security relevent information from
2616 a DC database. The LDIF file can then be used to "import" via
2617 the import_ldif() function this file into a temporary abbreviated
2618 database. The KCC algorithm can then run against this abbreviated
2619 database for debug or test verification that the topology generated
2620 is computationally the same between different OSes and algorithms.
2622 :param dburl: LDAP database URL to extract info from
2623 :param lp: a loadparm object.
2624 :param cred: a Credentials object.
2625 :param ldif_file: output LDIF file name to create
2626 :return: zero on success, 1 on error
2628 try:
2629 ldif_import_export.samdb_to_ldif_file(self.samdb, dburl, lp, creds,
2630 ldif_file)
2631 except ldif_import_export.LdifError, e:
2632 print >> sys.stderr, e
2633 return 1
2634 return 0