KCC: remove an unused variable in KCC.remove_unneeded_ntdsconn()
[Samba.git] / python / samba / kcc / __init__.py
blobc37b1b831d3f929bf78606bc27b38c191b256393
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
25 import itertools
26 from samba import unix2nttime, nttime2unix
27 from samba import ldb, dsdb, drs_utils
28 from samba.auth import system_session
29 from samba.samdb import SamDB
30 from samba.dcerpc import drsuapi, misc
32 from samba.kcc.kcc_utils import Site, Partition, Transport, SiteLink
33 from samba.kcc.kcc_utils import NCReplica, NCType, nctype_lut, GraphNode
34 from samba.kcc.kcc_utils import RepsFromTo, KCCError, KCCFailedObject
35 from samba.kcc.graph import convert_schedule_to_repltimes
37 from samba.ndr import ndr_pack
39 from samba.kcc.graph_utils import verify_and_dot
41 from samba.kcc import ldif_import_export
42 from samba.kcc.graph import setup_graph, get_spanning_tree_edges
43 from samba.kcc.graph import Vertex
45 from samba.kcc.debug import DEBUG, DEBUG_FN, logger
46 from samba.kcc import debug
49 def sort_replica_by_dsa_guid(rep1, rep2):
50 """Helper to sort NCReplicas by their DSA guids
52 The guids need to be sorted in their NDR form.
54 :param rep1: An NC replica
55 :param rep2: Another replica
56 :return: -1, 0, or 1, indicating sort order.
57 """
58 return cmp(ndr_pack(rep1.rep_dsa_guid), ndr_pack(rep2.rep_dsa_guid))
61 def sort_dsa_by_gc_and_guid(dsa1, dsa2):
62 """Helper to sort DSAs by guid global catalog status
64 GC DSAs come before non-GC DSAs, other than that, the guids are
65 sorted in NDR form.
67 :param dsa1: A DSA object
68 :param dsa2: Another DSA
69 :return: -1, 0, or 1, indicating sort order.
70 """
71 if dsa1.is_gc() and not dsa2.is_gc():
72 return -1
73 if not dsa1.is_gc() and dsa2.is_gc():
74 return +1
75 return cmp(ndr_pack(dsa1.dsa_guid), ndr_pack(dsa2.dsa_guid))
78 def is_smtp_replication_available():
79 """Can the KCC use SMTP replication?
81 Currently always returns false because Samba doesn't implement
82 SMTP transfer for NC changes between DCs.
84 :return: Boolean (always False)
85 """
86 return False
89 class KCC(object):
90 """The Knowledge Consistency Checker class.
92 A container for objects and methods allowing a run of the KCC. Produces a
93 set of connections in the samdb for which the Distributed Replication
94 Service can then utilize to replicate naming contexts
96 :param unix_now: The putative current time in seconds since 1970.
97 :param read_only: Don't write to the database.
98 :param verify: Check topological invariants for the generated graphs
99 :param debug: Write verbosely to stderr.
100 "param dot_files: write Graphviz files in /tmp showing topology
102 def __init__(self, unix_now, readonly=False,verify=False, debug=False,
103 dot_files=False):
104 """Initializes the partitions class which can hold
105 our local DCs partitions or all the partitions in
106 the forest
108 self.part_table = {} # partition objects
109 self.site_table = {}
110 self.transport_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_files = dot_files
145 def load_all_transports(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 self.transport_table.setdefault(str(transport.guid),
167 transport)
168 if transport.name == 'IP':
169 self.ip_transport = transport
171 if self.ip_transport is None:
172 raise KCCError("there doesn't seem to be an IP transport")
174 def load_all_sitelinks(self):
175 """Loads the inter-site siteLink objects
177 :return: None
178 :raise KCCError: if site-links aren't found
180 try:
181 res = self.samdb.search("CN=Inter-Site Transports,CN=Sites,%s" %
182 self.samdb.get_config_basedn(),
183 scope=ldb.SCOPE_SUBTREE,
184 expression="(objectClass=siteLink)")
185 except ldb.LdbError, (enum, estr):
186 raise KCCError("Unable to find inter-site siteLinks - (%s)" % estr)
188 for msg in res:
189 dnstr = str(msg.dn)
191 # already loaded
192 if dnstr in self.sitelink_table:
193 continue
195 sitelink = SiteLink(dnstr)
197 sitelink.load_sitelink(self.samdb)
199 # Assign this siteLink to table
200 # and index by dn
201 self.sitelink_table[dnstr] = sitelink
203 def load_site(self, dn_str):
204 """Helper for load_my_site and load_all_sites.
206 Put all the site's DSAs into the KCC indices.
208 :param dn_str: a site dn_str
209 :return: the Site object pertaining to the dn_str
211 site = Site(dn_str, self.unix_now)
212 site.load_site(self.samdb)
214 # We avoid replacing the site with an identical copy in case
215 # somewhere else has a reference to the old one, which would
216 # lead to all manner of confusion and chaos.
217 guid = str(site.site_guid)
218 if guid not in self.site_table:
219 self.site_table[guid] = site
220 self.dsa_by_dnstr.update(site.dsa_table)
221 self.dsa_by_guid.update((str(x.dsa_guid), x)
222 for x in site.dsa_table.values())
224 return self.site_table[guid]
226 def load_my_site(self):
227 """Load the Site object for the local DSA.
229 :return: None
231 self.my_site_dnstr = ("CN=%s,CN=Sites,%s" % (
232 self.samdb.server_site_name(),
233 self.samdb.get_config_basedn()))
235 self.my_site = self.load_site(self.my_site_dnstr)
237 def load_all_sites(self):
238 """Discover all sites and create Site objects.
240 :return: None
241 :raise: KCCError if sites can't be found
243 try:
244 res = self.samdb.search("CN=Sites,%s" %
245 self.samdb.get_config_basedn(),
246 scope=ldb.SCOPE_SUBTREE,
247 expression="(objectClass=site)")
248 except ldb.LdbError, (enum, estr):
249 raise KCCError("Unable to find sites - (%s)" % estr)
251 for msg in res:
252 sitestr = str(msg.dn)
253 self.load_site(sitestr)
255 def load_my_dsa(self):
256 """Discover my nTDSDSA dn thru the rootDSE entry
258 :return: None
259 :raise: KCCError if DSA can't be found
261 dn = ldb.Dn(self.samdb, "<GUID=%s>" % self.samdb.get_ntds_GUID())
262 try:
263 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
264 attrs=["objectGUID"])
265 except ldb.LdbError, (enum, estr):
266 logger.warning("Search for %s failed: %s. This typically happens"
267 " in --importldif mode due to lack of module"
268 " support.", dn, estr)
269 try:
270 # We work around the failure above by looking at the
271 # dsServiceName that was put in the fake rootdse by
272 # the --exportldif, rather than the
273 # samdb.get_ntds_GUID(). The disadvantage is that this
274 # mode requires we modify the @ROOTDSE dnq to support
275 # --forced-local-dsa
276 service_name_res = self.samdb.search(base="",
277 scope=ldb.SCOPE_BASE,
278 attrs=["dsServiceName"])
279 dn = ldb.Dn(self.samdb,
280 service_name_res[0]["dsServiceName"][0])
282 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
283 attrs=["objectGUID"])
284 except ldb.LdbError, (enum, estr):
285 raise KCCError("Unable to find my nTDSDSA - (%s)" % estr)
287 if len(res) != 1:
288 raise KCCError("Unable to find my nTDSDSA at %s" %
289 dn.extended_str())
291 ntds_guid = misc.GUID(self.samdb.get_ntds_GUID())
292 if misc.GUID(res[0]["objectGUID"][0]) != ntds_guid:
293 raise KCCError("Did not find the GUID we expected,"
294 " perhaps due to --importldif")
296 self.my_dsa_dnstr = str(res[0].dn)
298 self.my_dsa = self.my_site.get_dsa(self.my_dsa_dnstr)
300 if self.my_dsa_dnstr not in self.dsa_by_dnstr:
301 debug.DEBUG_DARK_YELLOW("my_dsa %s isn't in self.dsas_by_dnstr:"
302 " it must be RODC.\n"
303 "Let's add it, because my_dsa is special!"
304 "\n(likewise for self.dsa_by_guid)" %
305 self.my_dsas_dnstr)
307 self.dsa_by_dnstr[self.my_dsa_dnstr] = self.my_dsa
308 self.dsa_by_guid[str(self.my_dsa.dsa_guid)] = self.my_dsa
310 def load_all_partitions(self):
311 """Discover and load all partitions.
313 Each NC is inserted into the part_table by partition
314 dn string (not the nCName dn string)
316 :return: None
317 :raise: KCCError if partitions can't be found
319 try:
320 res = self.samdb.search("CN=Partitions,%s" %
321 self.samdb.get_config_basedn(),
322 scope=ldb.SCOPE_SUBTREE,
323 expression="(objectClass=crossRef)")
324 except ldb.LdbError, (enum, estr):
325 raise KCCError("Unable to find partitions - (%s)" % estr)
327 for msg in res:
328 partstr = str(msg.dn)
330 # already loaded
331 if partstr in self.part_table:
332 continue
334 part = Partition(partstr)
336 part.load_partition(self.samdb)
337 self.part_table[partstr] = part
339 def should_be_present_test(self):
340 """Enumerate all loaded partitions and DSAs in local
341 site and test if NC should be present as replica
343 for partdn, part in self.part_table.items():
344 for dsadn, dsa in self.my_site.dsa_table.items():
345 needed, ro, partial = part.should_be_present(dsa)
346 logger.info("dsadn:%s\nncdn:%s\nneeded=%s:ro=%s:partial=%s\n" %
347 (dsadn, part.nc_dnstr, needed, ro, partial))
349 def refresh_failed_links_connections(self, ping=None):
350 """Ensure the failed links list is up to date
352 Based on MS-ADTS 6.2.2.1
354 :param ping: An oracle function of remote site availability
355 :return: None
357 # LINKS: Refresh failed links
358 self.kcc_failed_links = {}
359 current, needed = self.my_dsa.get_rep_tables()
360 for replica in current.values():
361 # For every possible connection to replicate
362 for reps_from in replica.rep_repsFrom:
363 failure_count = reps_from.consecutive_sync_failures
364 if failure_count <= 0:
365 continue
367 dsa_guid = str(reps_from.source_dsa_obj_guid)
368 time_first_failure = reps_from.last_success
369 last_result = reps_from.last_attempt
370 dns_name = reps_from.dns_name1
372 f = self.kcc_failed_links.get(dsa_guid)
373 if not f:
374 f = KCCFailedObject(dsa_guid, failure_count,
375 time_first_failure, last_result,
376 dns_name)
377 self.kcc_failed_links[dsa_guid] = f
378 #elif f.failure_count == 0:
379 # f.failure_count = failure_count
380 # f.time_first_failure = time_first_failure
381 # f.last_result = last_result
382 else:
383 f.failure_count = max(f.failure_count, failure_count)
384 f.time_first_failure = min(f.time_first_failure,
385 time_first_failure)
386 f.last_result = last_result
388 # CONNECTIONS: Refresh failed connections
389 restore_connections = set()
390 if ping is not None:
391 DEBUG("refresh_failed_links: checking if links are still down")
392 for connection in self.kcc_failed_connections:
393 if ping(connection.dns_name):
394 # Failed connection is no longer failing
395 restore_connections.add(connection)
396 else:
397 connection.failure_count += 1
398 else:
399 DEBUG("refresh_failed_links: not checking live links because we\n"
400 "weren't asked to --attempt-live-connections")
402 # Remove the restored connections from the failed connections
403 self.kcc_failed_connections.difference_update(restore_connections)
405 def is_stale_link_connection(self, target_dsa):
406 """Check whether a link to a remote DSA is stale
408 Used in MS-ADTS 6.2.2.2 Intrasite Connection Creation
410 Returns True if the remote seems to have been down for at
411 least two hours, otherwise False.
413 :param target_dsa: the remote DSA object
414 :return: True if link is stale, otherwise False
416 failed_link = self.kcc_failed_links.get(str(target_dsa.dsa_guid))
417 if failed_link:
418 # failure_count should be > 0, but check anyways
419 if failed_link.failure_count > 0:
420 unix_first_failure = \
421 nttime2unix(failed_link.time_first_failure)
422 # TODO guard against future
423 if unix_first_failure > self.unix_now:
424 logger.error("The last success time attribute for \
425 repsFrom is in the future!")
427 # Perform calculation in seconds
428 if (self.unix_now - unix_first_failure) > 60 * 60 * 2:
429 return True
431 # TODO connections
433 return False
435 # TODO: This should be backed by some form of local database
436 def remove_unneeded_failed_links_connections(self):
437 # Remove all tuples in kcc_failed_links where failure count = 0
438 # In this implementation, this should never happen.
440 # Remove all connections which were not used this run or connections
441 # that became active during this run.
442 pass
444 def remove_unneeded_ntdsconn(self, all_connected):
445 """Remove unneeded NTDS Connections once topology is calculated
447 Based on MS-ADTS 6.2.2.4 Removing Unnecessary Connections
449 :param all_connected: indicates whether all sites are connected
450 :return: None
452 mydsa = self.my_dsa
454 # New connections won't have GUIDs which are needed for
455 # sorting. Add them.
456 for cn_conn in mydsa.connect_table.values():
457 if cn_conn.guid is None:
458 if self.readonly:
459 cn_conn.guid = misc.GUID(str(uuid.uuid4()))
460 cn_conn.whenCreated = self.nt_now
461 else:
462 cn_conn.load_connection(self.samdb)
464 for cn_conn in mydsa.connect_table.values():
466 s_dnstr = cn_conn.get_from_dnstr()
467 if s_dnstr is None:
468 cn_conn.to_be_deleted = True
469 continue
471 #XXX should an RODC be regarded as same site
472 same_site = s_dnstr in self.my_site.dsa_table
474 # Given an nTDSConnection object cn, if the DC with the
475 # nTDSDSA object dc that is the parent object of cn and
476 # the DC with the nTDSDA object referenced by cn!fromServer
477 # are in the same site, the KCC on dc deletes cn if all of
478 # the following are true:
480 # Bit NTDSCONN_OPT_IS_GENERATED is clear in cn!options.
482 # No site settings object s exists for the local DC's site, or
483 # bit NTDSSETTINGS_OPT_IS_TOPL_CLEANUP_DISABLED is clear in
484 # s!options.
486 # Another nTDSConnection object cn2 exists such that cn and
487 # cn2 have the same parent object, cn!fromServer = cn2!fromServer,
488 # and either
490 # cn!whenCreated < cn2!whenCreated
492 # cn!whenCreated = cn2!whenCreated and
493 # cn!objectGUID < cn2!objectGUID
495 # Bit NTDSCONN_OPT_RODC_TOPOLOGY is clear in cn!options
496 if same_site:
497 if not cn_conn.is_generated():
498 continue
500 if self.my_site.is_cleanup_ntdsconn_disabled():
501 continue
503 # Loop thru connections looking for a duplicate that
504 # fulfills the previous criteria
505 lesser = False
506 packed_guid = ndr_pack(cn_conn.guid)
507 for cn2_conn in mydsa.connect_table.values():
508 if cn2_conn is cn_conn:
509 continue
511 s2_dnstr = cn2_conn.get_from_dnstr()
513 # If the NTDS Connections has a different
514 # fromServer field then no match
515 if s2_dnstr != s_dnstr:
516 continue
518 #XXX GUID comparison
519 lesser = (cn_conn.whenCreated < cn2_conn.whenCreated or
520 (cn_conn.whenCreated == cn2_conn.whenCreated and
521 packed_guid < ndr_pack(cn2_conn.guid)))
523 if lesser:
524 break
526 if lesser and not cn_conn.is_rodc_topology():
527 cn_conn.to_be_deleted = True
529 # Given an nTDSConnection object cn, if the DC with the nTDSDSA
530 # object dc that is the parent object of cn and the DC with
531 # the nTDSDSA object referenced by cn!fromServer are in
532 # different sites, a KCC acting as an ISTG in dc's site
533 # deletes cn if all of the following are true:
535 # Bit NTDSCONN_OPT_IS_GENERATED is clear in cn!options.
537 # cn!fromServer references an nTDSDSA object for a DC
538 # in a site other than the local DC's site.
540 # The keepConnections sequence returned by
541 # CreateIntersiteConnections() does not contain
542 # cn!objectGUID, or cn is "superseded by" (see below)
543 # another nTDSConnection cn2 and keepConnections
544 # contains cn2!objectGUID.
546 # The return value of CreateIntersiteConnections()
547 # was true.
549 # Bit NTDSCONN_OPT_RODC_TOPOLOGY is clear in
550 # cn!options
552 else: # different site
554 if not mydsa.is_istg():
555 continue
557 if not cn_conn.is_generated():
558 continue
560 # TODO
561 # We are directly using this connection in intersite or
562 # we are using a connection which can supersede this one.
564 # MS-ADTS 6.2.2.4 - Removing Unnecessary Connections does not
565 # appear to be correct.
567 # 1. cn!fromServer and cn!parent appear inconsistent with
568 # no cn2
569 # 2. The repsFrom do not imply each other
571 if cn_conn in self.kept_connections: # and not_superceded:
572 continue
574 # This is the result of create_intersite_connections
575 if not all_connected:
576 continue
578 if not cn_conn.is_rodc_topology():
579 cn_conn.to_be_deleted = True
581 if mydsa.is_ro() or self.readonly:
582 for connect in mydsa.connect_table.values():
583 if connect.to_be_deleted:
584 DEBUG_FN("TO BE DELETED:\n%s" % connect)
585 if connect.to_be_added:
586 DEBUG_FN("TO BE ADDED:\n%s" % connect)
588 # Peform deletion from our tables but perform
589 # no database modification
590 mydsa.commit_connections(self.samdb, ro=True)
591 else:
592 # Commit any modified connections
593 mydsa.commit_connections(self.samdb)
595 def modify_repsFrom(self, n_rep, t_repsFrom, s_rep, s_dsa, cn_conn):
596 """Update an repsFrom object if required.
598 Part of MS-ADTS 6.2.2.5.
600 Update t_repsFrom if necessary to satisfy requirements. Such
601 updates are typically required when the IDL_DRSGetNCChanges
602 server has moved from one site to another--for example, to
603 enable compression when the server is moved from the
604 client's site to another site.
606 The repsFrom.update_flags bit field may be modified
607 auto-magically if any changes are made here. See
608 kcc_utils.RepsFromTo for gory details.
611 :param n_rep: NC replica we need
612 :param t_repsFrom: repsFrom tuple to modify
613 :param s_rep: NC replica at source DSA
614 :param s_dsa: source DSA
615 :param cn_conn: Local DSA NTDSConnection child
617 :return: None
619 s_dnstr = s_dsa.dsa_dnstr
620 same_site = s_dnstr in self.my_site.dsa_table
622 # if schedule doesn't match then update and modify
623 times = convert_schedule_to_repltimes(cn_conn.schedule)
624 if times != t_repsFrom.schedule:
625 t_repsFrom.schedule = times
627 # Bit DRS_PER_SYNC is set in replicaFlags if and only
628 # if nTDSConnection schedule has a value v that specifies
629 # scheduled replication is to be performed at least once
630 # per week.
631 if cn_conn.is_schedule_minimum_once_per_week():
633 if ((t_repsFrom.replica_flags &
634 drsuapi.DRSUAPI_DRS_PER_SYNC) == 0x0):
635 t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_PER_SYNC
637 # Bit DRS_INIT_SYNC is set in t.replicaFlags if and only
638 # if the source DSA and the local DC's nTDSDSA object are
639 # in the same site or source dsa is the FSMO role owner
640 # of one or more FSMO roles in the NC replica.
641 if same_site or n_rep.is_fsmo_role_owner(s_dnstr):
643 if ((t_repsFrom.replica_flags &
644 drsuapi.DRSUAPI_DRS_INIT_SYNC) == 0x0):
645 t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_INIT_SYNC
647 # If bit NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT is set in
648 # cn!options, bit DRS_NEVER_NOTIFY is set in t.replicaFlags
649 # if and only if bit NTDSCONN_OPT_USE_NOTIFY is clear in
650 # cn!options. Otherwise, bit DRS_NEVER_NOTIFY is set in
651 # t.replicaFlags if and only if s and the local DC's
652 # nTDSDSA object are in different sites.
653 if ((cn_conn.options &
654 dsdb.NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT) != 0x0):
656 if (cn_conn.options & dsdb.NTDSCONN_OPT_USE_NOTIFY) == 0x0:
657 # XXX WARNING
659 # it LOOKS as if this next test is a bit silly: it
660 # checks the flag then sets it if it not set; the same
661 # effect could be achieved by unconditionally setting
662 # it. But in fact the repsFrom object has special
663 # magic attached to it, and altering replica_flags has
664 # side-effects. That is bad in my opinion, but there
665 # you go.
666 if ((t_repsFrom.replica_flags &
667 drsuapi.DRSUAPI_DRS_NEVER_NOTIFY) == 0x0):
668 t_repsFrom.replica_flags |= \
669 drsuapi.DRSUAPI_DRS_NEVER_NOTIFY
671 elif not same_site:
673 if ((t_repsFrom.replica_flags &
674 drsuapi.DRSUAPI_DRS_NEVER_NOTIFY) == 0x0):
675 t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_NEVER_NOTIFY
677 # Bit DRS_USE_COMPRESSION is set in t.replicaFlags if
678 # and only if s and the local DC's nTDSDSA object are
679 # not in the same site and the
680 # NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION bit is
681 # clear in cn!options
682 if (not same_site and
683 (cn_conn.options &
684 dsdb.NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION) == 0x0):
686 if ((t_repsFrom.replica_flags &
687 drsuapi.DRSUAPI_DRS_USE_COMPRESSION) == 0x0):
688 t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_USE_COMPRESSION
690 # Bit DRS_TWOWAY_SYNC is set in t.replicaFlags if and only
691 # if bit NTDSCONN_OPT_TWOWAY_SYNC is set in cn!options.
692 if (cn_conn.options & dsdb.NTDSCONN_OPT_TWOWAY_SYNC) != 0x0:
694 if ((t_repsFrom.replica_flags &
695 drsuapi.DRSUAPI_DRS_TWOWAY_SYNC) == 0x0):
696 t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_TWOWAY_SYNC
698 # Bits DRS_DISABLE_AUTO_SYNC and DRS_DISABLE_PERIODIC_SYNC are
699 # set in t.replicaFlags if and only if cn!enabledConnection = false.
700 if not cn_conn.is_enabled():
702 if ((t_repsFrom.replica_flags &
703 drsuapi.DRSUAPI_DRS_DISABLE_AUTO_SYNC) == 0x0):
704 t_repsFrom.replica_flags |= \
705 drsuapi.DRSUAPI_DRS_DISABLE_AUTO_SYNC
707 if ((t_repsFrom.replica_flags &
708 drsuapi.DRSUAPI_DRS_DISABLE_PERIODIC_SYNC) == 0x0):
709 t_repsFrom.replica_flags |= \
710 drsuapi.DRSUAPI_DRS_DISABLE_PERIODIC_SYNC
712 # If s and the local DC's nTDSDSA object are in the same site,
713 # cn!transportType has no value, or the RDN of cn!transportType
714 # is CN=IP:
716 # Bit DRS_MAIL_REP in t.replicaFlags is clear.
718 # t.uuidTransport = NULL GUID.
720 # t.uuidDsa = The GUID-based DNS name of s.
722 # Otherwise:
724 # Bit DRS_MAIL_REP in t.replicaFlags is set.
726 # If x is the object with dsname cn!transportType,
727 # t.uuidTransport = x!objectGUID.
729 # Let a be the attribute identified by
730 # x!transportAddressAttribute. If a is
731 # the dNSHostName attribute, t.uuidDsa = the GUID-based
732 # DNS name of s. Otherwise, t.uuidDsa = (s!parent)!a.
734 # It appears that the first statement i.e.
736 # "If s and the local DC's nTDSDSA object are in the same
737 # site, cn!transportType has no value, or the RDN of
738 # cn!transportType is CN=IP:"
740 # could be a slightly tighter statement if it had an "or"
741 # between each condition. I believe this should
742 # be interpreted as:
744 # IF (same-site) OR (no-value) OR (type-ip)
746 # because IP should be the primary transport mechanism
747 # (even in inter-site) and the absense of the transportType
748 # attribute should always imply IP no matter if its multi-site
750 # NOTE MS-TECH INCORRECT:
752 # All indications point to these statements above being
753 # incorrectly stated:
755 # t.uuidDsa = The GUID-based DNS name of s.
757 # Let a be the attribute identified by
758 # x!transportAddressAttribute. If a is
759 # the dNSHostName attribute, t.uuidDsa = the GUID-based
760 # DNS name of s. Otherwise, t.uuidDsa = (s!parent)!a.
762 # because the uuidDSA is a GUID and not a GUID-base DNS
763 # name. Nor can uuidDsa hold (s!parent)!a if not
764 # dNSHostName. What should have been said is:
766 # t.naDsa = The GUID-based DNS name of s
768 # That would also be correct if transportAddressAttribute
769 # were "mailAddress" because (naDsa) can also correctly
770 # hold the SMTP ISM service address.
772 nastr = "%s._msdcs.%s" % (s_dsa.dsa_guid, self.samdb.forest_dns_name())
774 # We're not currently supporting SMTP replication
775 # so is_smtp_replication_available() is currently
776 # always returning False
777 if ((same_site or
778 cn_conn.transport_dnstr is None or
779 cn_conn.transport_dnstr.find("CN=IP") == 0 or
780 not is_smtp_replication_available())):
782 if ((t_repsFrom.replica_flags &
783 drsuapi.DRSUAPI_DRS_MAIL_REP) != 0x0):
784 t_repsFrom.replica_flags &= ~drsuapi.DRSUAPI_DRS_MAIL_REP
786 t_repsFrom.transport_guid = misc.GUID()
788 # See (NOTE MS-TECH INCORRECT) above
789 if t_repsFrom.version == 0x1:
790 if t_repsFrom.dns_name1 is None or \
791 t_repsFrom.dns_name1 != nastr:
792 t_repsFrom.dns_name1 = nastr
793 else:
794 if t_repsFrom.dns_name1 is None or \
795 t_repsFrom.dns_name2 is None or \
796 t_repsFrom.dns_name1 != nastr or \
797 t_repsFrom.dns_name2 != nastr:
798 t_repsFrom.dns_name1 = nastr
799 t_repsFrom.dns_name2 = nastr
801 else:
802 # XXX This entire branch is NEVER used! Because we don't do SMTP!
803 # (see the if condition above). Just close your eyes here.
804 if ((t_repsFrom.replica_flags &
805 drsuapi.DRSUAPI_DRS_MAIL_REP) == 0x0):
806 t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_MAIL_REP
808 # We have a transport type but its not an
809 # object in the database
810 if cn_conn.transport_guid not in self.transport_table:
811 raise KCCError("Missing inter-site transport - (%s)" %
812 cn_conn.transport_dnstr)
814 x_transport = self.transport_table[str(cn_conn.transport_guid)]
816 if t_repsFrom.transport_guid != x_transport.guid:
817 t_repsFrom.transport_guid = x_transport.guid
819 # See (NOTE MS-TECH INCORRECT) above
820 if x_transport.address_attr == "dNSHostName":
822 if t_repsFrom.version == 0x1:
823 if t_repsFrom.dns_name1 is None or \
824 t_repsFrom.dns_name1 != nastr:
825 t_repsFrom.dns_name1 = nastr
826 else:
827 if t_repsFrom.dns_name1 is None or \
828 t_repsFrom.dns_name2 is None or \
829 t_repsFrom.dns_name1 != nastr or \
830 t_repsFrom.dns_name2 != nastr:
831 t_repsFrom.dns_name1 = nastr
832 t_repsFrom.dns_name2 = nastr
834 else:
835 # MS tech specification says we retrieve the named
836 # attribute in "transportAddressAttribute" from the parent of
837 # the DSA object
838 try:
839 pdnstr = s_dsa.get_parent_dnstr()
840 attrs = [x_transport.address_attr]
842 res = self.samdb.search(base=pdnstr, scope=ldb.SCOPE_BASE,
843 attrs=attrs)
844 except ldb.LdbError, (enum, estr):
845 raise KCCError(
846 "Unable to find attr (%s) for (%s) - (%s)" %
847 (x_transport.address_attr, pdnstr, estr))
849 msg = res[0]
850 nastr = str(msg[x_transport.address_attr][0])
852 # See (NOTE MS-TECH INCORRECT) above
853 if t_repsFrom.version == 0x1:
854 if t_repsFrom.dns_name1 is None or \
855 t_repsFrom.dns_name1 != nastr:
856 t_repsFrom.dns_name1 = nastr
857 else:
858 if t_repsFrom.dns_name1 is None or \
859 t_repsFrom.dns_name2 is None or \
860 t_repsFrom.dns_name1 != nastr or \
861 t_repsFrom.dns_name2 != nastr:
863 t_repsFrom.dns_name1 = nastr
864 t_repsFrom.dns_name2 = nastr
866 if t_repsFrom.is_modified():
867 DEBUG_FN("modify_repsFrom(): %s" % t_repsFrom)
869 def get_dsa_for_implied_replica(self, n_rep, cn_conn):
870 """If a connection imply a replica, find the relevant DSA
872 Given a NC replica and NTDS Connection, determine if the
873 connection implies a repsFrom tuple should be present from the
874 source DSA listed in the connection to the naming context. If
875 it should be, return the DSA; otherwise return None.
877 Based on part of MS-ADTS 6.2.2.5
879 :param n_rep: NC replica
880 :param cn_conn: NTDS Connection
881 :return: source DSA or None
883 #XXX different conditions for "implies" than MS-ADTS 6.2.2
885 # NTDS Connection must satisfy all the following criteria
886 # to imply a repsFrom tuple is needed:
888 # cn!enabledConnection = true.
889 # cn!options does not contain NTDSCONN_OPT_RODC_TOPOLOGY.
890 # cn!fromServer references an nTDSDSA object.
892 if not cn_conn.is_enabled() or cn_conn.is_rodc_topology():
893 return None
895 s_dnstr = cn_conn.get_from_dnstr()
896 s_dsa = self.get_dsa(s_dnstr)
898 # No DSA matching this source DN string?
899 if s_dsa is None:
900 return None
902 # To imply a repsFrom tuple is needed, each of these
903 # must be True:
905 # An NC replica of the NC "is present" on the DC to
906 # which the nTDSDSA object referenced by cn!fromServer
907 # corresponds.
909 # An NC replica of the NC "should be present" on
910 # the local DC
911 s_rep = s_dsa.get_current_replica(n_rep.nc_dnstr)
913 if s_rep is None or not s_rep.is_present():
914 return None
916 # To imply a repsFrom tuple is needed, each of these
917 # must be True:
919 # The NC replica on the DC referenced by cn!fromServer is
920 # a writable replica or the NC replica that "should be
921 # present" on the local DC is a partial replica.
923 # The NC is not a domain NC, the NC replica that
924 # "should be present" on the local DC is a partial
925 # replica, cn!transportType has no value, or
926 # cn!transportType has an RDN of CN=IP.
928 implied = (not s_rep.is_ro() or n_rep.is_partial()) and \
929 (not n_rep.is_domain() or
930 n_rep.is_partial() or
931 cn_conn.transport_dnstr is None or
932 cn_conn.transport_dnstr.find("CN=IP") == 0)
934 if implied:
935 return s_dsa
936 return None
938 def translate_ntdsconn(self, current_dsa=None):
939 """Adjust repsFrom to match NTDSConnections
941 This function adjusts values of repsFrom abstract attributes of NC
942 replicas on the local DC to match those implied by
943 nTDSConnection objects.
945 Based on [MS-ADTS] 6.2.2.5
947 :param current_dsa: optional DSA on whose behalf we are acting.
948 :return: None
950 count = 0
952 if current_dsa is None:
953 current_dsa = self.my_dsa
955 if current_dsa.is_translate_ntdsconn_disabled():
956 DEBUG_FN("skipping translate_ntdsconn() "
957 "because disabling flag is set")
958 return
960 DEBUG_FN("translate_ntdsconn(): enter")
962 current_rep_table, needed_rep_table = current_dsa.get_rep_tables()
964 # Filled in with replicas we currently have that need deleting
965 delete_reps = set()
967 # We're using the MS notation names here to allow
968 # correlation back to the published algorithm.
970 # n_rep - NC replica (n)
971 # t_repsFrom - tuple (t) in n!repsFrom
972 # s_dsa - Source DSA of the replica. Defined as nTDSDSA
973 # object (s) such that (s!objectGUID = t.uuidDsa)
974 # In our IDL representation of repsFrom the (uuidDsa)
975 # attribute is called (source_dsa_obj_guid)
976 # cn_conn - (cn) is nTDSConnection object and child of the local
977 # DC's nTDSDSA object and (cn!fromServer = s)
978 # s_rep - source DSA replica of n
980 # If we have the replica and its not needed
981 # then we add it to the "to be deleted" list.
982 for dnstr in current_rep_table:
983 if dnstr not in needed_rep_table:
984 delete_reps.add(dnstr)
986 DEBUG_FN('current %d needed %d delete %d' % (len(current_rep_table),
987 len(needed_rep_table), len(delete_reps)))
989 if delete_reps:
990 DEBUG('deleting these reps: %s' % delete_reps)
991 for dnstr in delete_reps:
992 del current_rep_table[dnstr]
994 # Now perform the scan of replicas we'll need
995 # and compare any current repsFrom against the
996 # connections
997 for n_rep in needed_rep_table.values():
999 # load any repsFrom and fsmo roles as we'll
1000 # need them during connection translation
1001 n_rep.load_repsFrom(self.samdb)
1002 n_rep.load_fsmo_roles(self.samdb)
1004 # Loop thru the existing repsFrom tupples (if any)
1005 # XXX This is a list and could contain duplicates
1006 # (multiple load_repsFrom calls)
1007 for t_repsFrom in n_rep.rep_repsFrom:
1009 # for each tuple t in n!repsFrom, let s be the nTDSDSA
1010 # object such that s!objectGUID = t.uuidDsa
1011 guidstr = str(t_repsFrom.source_dsa_obj_guid)
1012 s_dsa = self.get_dsa_by_guidstr(guidstr)
1014 # Source dsa is gone from config (strange)
1015 # so cleanup stale repsFrom for unlisted DSA
1016 if s_dsa is None:
1017 logger.warning("repsFrom source DSA guid (%s) not found" %
1018 guidstr)
1019 t_repsFrom.to_be_deleted = True
1020 continue
1022 s_dnstr = s_dsa.dsa_dnstr
1024 # Retrieve my DSAs connection object (if it exists)
1025 # that specifies the fromServer equivalent to
1026 # the DSA that is specified in the repsFrom source
1027 connections = current_dsa.get_connection_by_from_dnstr(s_dnstr)
1029 count = 0
1030 cn_conn = None
1032 for con in connections:
1033 if con.is_rodc_topology():
1034 continue
1035 cn_conn = con
1037 # Let (cn) be the nTDSConnection object such that (cn)
1038 # is a child of the local DC's nTDSDSA object and
1039 # (cn!fromServer = s) and (cn!options) does not contain
1040 # NTDSCONN_OPT_RODC_TOPOLOGY or NULL if no such (cn) exists.
1042 # KCC removes this repsFrom tuple if any of the following
1043 # is true:
1044 # cn = NULL.
1045 # [...]
1047 #XXX varying possible interpretations of rodc_topology
1048 if cn_conn is None:
1049 t_repsFrom.to_be_deleted = True
1050 continue
1052 # [...] KCC removes this repsFrom tuple if:
1054 # No NC replica of the NC "is present" on DSA that
1055 # would be source of replica
1057 # A writable replica of the NC "should be present" on
1058 # the local DC, but a partial replica "is present" on
1059 # the source DSA
1060 s_rep = s_dsa.get_current_replica(n_rep.nc_dnstr)
1062 if s_rep is None or not s_rep.is_present() or \
1063 (not n_rep.is_ro() and s_rep.is_partial()):
1065 t_repsFrom.to_be_deleted = True
1066 continue
1068 # If the KCC did not remove t from n!repsFrom, it updates t
1069 self.modify_repsFrom(n_rep, t_repsFrom, s_rep, s_dsa, cn_conn)
1071 # Loop thru connections and add implied repsFrom tuples
1072 # for each NTDSConnection under our local DSA if the
1073 # repsFrom is not already present
1074 for cn_conn in current_dsa.connect_table.values():
1076 s_dsa = self.get_dsa_for_implied_replica(n_rep, cn_conn)
1077 if s_dsa is None:
1078 continue
1080 # Loop thru the existing repsFrom tupples (if any) and
1081 # if we already have a tuple for this connection then
1082 # no need to proceed to add. It will have been changed
1083 # to have the correct attributes above
1084 for t_repsFrom in n_rep.rep_repsFrom:
1085 guidstr = str(t_repsFrom.source_dsa_obj_guid)
1086 #XXX what?
1087 if s_dsa is self.get_dsa_by_guidstr(guidstr):
1088 s_dsa = None
1089 break
1091 if s_dsa is None:
1092 continue
1094 # Create a new RepsFromTo and proceed to modify
1095 # it according to specification
1096 t_repsFrom = RepsFromTo(n_rep.nc_dnstr)
1098 t_repsFrom.source_dsa_obj_guid = s_dsa.dsa_guid
1100 s_rep = s_dsa.get_current_replica(n_rep.nc_dnstr)
1102 self.modify_repsFrom(n_rep, t_repsFrom, s_rep, s_dsa, cn_conn)
1104 # Add to our NC repsFrom as this is newly computed
1105 if t_repsFrom.is_modified():
1106 n_rep.rep_repsFrom.append(t_repsFrom)
1108 if self.readonly:
1109 # Display any to be deleted or modified repsFrom
1110 text = n_rep.dumpstr_to_be_deleted()
1111 if text:
1112 logger.info("TO BE DELETED:\n%s" % text)
1113 text = n_rep.dumpstr_to_be_modified()
1114 if text:
1115 logger.info("TO BE MODIFIED:\n%s" % text)
1117 # Peform deletion from our tables but perform
1118 # no database modification
1119 n_rep.commit_repsFrom(self.samdb, ro=True)
1120 else:
1121 # Commit any modified repsFrom to the NC replica
1122 n_rep.commit_repsFrom(self.samdb)
1124 def merge_failed_links(self, ping=None):
1125 """Merge of kCCFailedLinks and kCCFailedLinks from bridgeheads.
1127 The KCC on a writable DC attempts to merge the link and connection
1128 failure information from bridgehead DCs in its own site to help it
1129 identify failed bridgehead DCs.
1131 Based on MS-ADTS 6.2.2.3.2 "Merge of kCCFailedLinks and kCCFailedLinks
1132 from Bridgeheads"
1134 :param ping: An oracle of current bridgehead availability
1135 :return: None
1137 # 1. Queries every bridgehead server in your site (other than yourself)
1138 # 2. For every ntDSConnection that references a server in a different
1139 # site merge all the failure info
1141 # XXX - not implemented yet
1142 if ping is not None:
1143 debug.DEBUG_RED("merge_failed_links() is NOT IMPLEMENTED")
1144 else:
1145 DEBUG_FN("skipping merge_failed_links() because it requires "
1146 "real network connections\n"
1147 "and we weren't asked to --attempt-live-connections")
1149 def setup_graph(self, part):
1150 """Set up an intersite graph
1152 An intersite graph has a Vertex for each site object, a
1153 MultiEdge for each SiteLink object, and a MutliEdgeSet for
1154 each siteLinkBridge object (or implied siteLinkBridge). It
1155 reflects the intersite topology in a slightly more abstract
1156 graph form.
1158 Roughly corresponds to MS-ADTS 6.2.2.3.4.3
1160 :param part: a Partition object
1161 :returns: an InterSiteGraph object
1163 # If 'Bridge all site links' is enabled and Win2k3 bridges required
1164 # is not set
1165 # NTDSTRANSPORT_OPT_BRIDGES_REQUIRED 0x00000002
1166 # No documentation for this however, ntdsapi.h appears to have:
1167 # NTDSSETTINGS_OPT_W2K3_BRIDGES_REQUIRED = 0x00001000
1168 bridges_required = self.my_site.site_options & 0x00001002 == 0
1170 g = setup_graph(part, self.site_table, self.transport_table,
1171 self.sitelink_table, bridges_required)
1173 dot_edges = []
1174 for edge in g.edges:
1175 for a, b in itertools.combinations(edge.vertices, 2):
1176 dot_edges.append((a.site.site_dnstr, b.site.site_dnstr))
1177 verify_properties = ()
1178 verify_and_dot('site_edges', dot_edges, directed=False,
1179 label=self.my_dsa_dnstr,
1180 properties=verify_properties, debug=DEBUG,
1181 verify=self.verify,
1182 dot_files=self.dot_files)
1184 return g
1186 def get_bridgehead(self, site, part, transport, partial_ok, detect_failed):
1187 """Get a bridghead DC for a site.
1189 Part of MS-ADTS 6.2.2.3.4.4
1191 :param site: site object representing for which a bridgehead
1192 DC is desired.
1193 :param part: crossRef for NC to replicate.
1194 :param transport: interSiteTransport object for replication
1195 traffic.
1196 :param partial_ok: True if a DC containing a partial
1197 replica or a full replica will suffice, False if only
1198 a full replica will suffice.
1199 :param detect_failed: True to detect failed DCs and route
1200 replication traffic around them, False to assume no DC
1201 has failed.
1202 :return: dsa object for the bridgehead DC or None
1205 bhs = self.get_all_bridgeheads(site, part, transport,
1206 partial_ok, detect_failed)
1207 if len(bhs) == 0:
1208 debug.DEBUG_MAGENTA("get_bridgehead:\n\tsitedn=%s\n\tbhdn=None" %
1209 site.site_dnstr)
1210 return None
1211 else:
1212 debug.DEBUG_GREEN("get_bridgehead:\n\tsitedn=%s\n\tbhdn=%s" %
1213 (site.site_dnstr, bhs[0].dsa_dnstr))
1214 return bhs[0]
1216 def get_all_bridgeheads(self, site, part, transport,
1217 partial_ok, detect_failed):
1218 """Get all bridghead DCs on a site satisfying the given criteria
1220 Part of MS-ADTS 6.2.2.3.4.4
1222 :param site: site object representing the site for which
1223 bridgehead DCs are desired.
1224 :param part: partition for NC to replicate.
1225 :param transport: interSiteTransport object for
1226 replication traffic.
1227 :param partial_ok: True if a DC containing a partial
1228 replica or a full replica will suffice, False if
1229 only a full replica will suffice.
1230 :param detect_failed: True to detect failed DCs and route
1231 replication traffic around them, FALSE to assume
1232 no DC has failed.
1233 :return: list of dsa object for available bridgehead DCs
1236 bhs = []
1238 DEBUG_FN("get_all_bridgeheads: %s" % transport.name)
1239 if 'Site-5' in site.site_dnstr:
1240 debug.DEBUG_RED("get_all_bridgeheads with %s, part%s, "
1241 "partial_ok %s detect_failed %s" %
1242 (site.site_dnstr, part.partstr, partial_ok,
1243 detect_failed))
1244 DEBUG_FN(site.rw_dsa_table)
1245 for dsa in site.rw_dsa_table.values():
1247 pdnstr = dsa.get_parent_dnstr()
1249 # IF t!bridgeheadServerListBL has one or more values and
1250 # t!bridgeheadServerListBL does not contain a reference
1251 # to the parent object of dc then skip dc
1252 if ((len(transport.bridgehead_list) != 0 and
1253 pdnstr not in transport.bridgehead_list)):
1254 continue
1256 # IF dc is in the same site as the local DC
1257 # IF a replica of cr!nCName is not in the set of NC replicas
1258 # that "should be present" on dc or a partial replica of the
1259 # NC "should be present" but partialReplicasOkay = FALSE
1260 # Skip dc
1261 if self.my_site.same_site(dsa):
1262 needed, ro, partial = part.should_be_present(dsa)
1263 if not needed or (partial and not partial_ok):
1264 continue
1265 rep = dsa.get_current_replica(part.nc_dnstr)
1267 # ELSE
1268 # IF an NC replica of cr!nCName is not in the set of NC
1269 # replicas that "are present" on dc or a partial replica of
1270 # the NC "is present" but partialReplicasOkay = FALSE
1271 # Skip dc
1272 else:
1273 rep = dsa.get_current_replica(part.nc_dnstr)
1274 if rep is None or (rep.is_partial() and not partial_ok):
1275 continue
1277 # IF AmIRODC() and cr!nCName corresponds to default NC then
1278 # Let dsaobj be the nTDSDSA object of the dc
1279 # IF dsaobj.msDS-Behavior-Version < DS_DOMAIN_FUNCTION_2008
1280 # Skip dc
1281 if self.my_dsa.is_ro() and rep is not None and rep.is_default():
1282 if not dsa.is_minimum_behavior(dsdb.DS_DOMAIN_FUNCTION_2008):
1283 continue
1285 # IF t!name != "IP" and the parent object of dc has no value for
1286 # the attribute specified by t!transportAddressAttribute
1287 # Skip dc
1288 if transport.name != "IP":
1289 # MS tech specification says we retrieve the named
1290 # attribute in "transportAddressAttribute" from the parent
1291 # of the DSA object
1292 try:
1293 attrs = [transport.address_attr]
1295 res = self.samdb.search(base=pdnstr, scope=ldb.SCOPE_BASE,
1296 attrs=attrs)
1297 except ldb.LdbError, (enum, estr):
1298 continue
1300 msg = res[0]
1301 if transport.address_attr not in msg:
1302 continue
1303 #XXX nastr is NEVER USED. It will be removed.
1304 nastr = str(msg[transport.address_attr][0])
1306 # IF BridgeheadDCFailed(dc!objectGUID, detectFailedDCs) = TRUE
1307 # Skip dc
1308 if self.is_bridgehead_failed(dsa, detect_failed):
1309 DEBUG("bridgehead is failed")
1310 continue
1312 DEBUG_FN("get_all_bridgeheads: dsadn=%s" % dsa.dsa_dnstr)
1313 bhs.append(dsa)
1315 # IF bit NTDSSETTINGS_OPT_IS_RAND_BH_SELECTION_DISABLED is set in
1316 # s!options
1317 # SORT bhs such that all GC servers precede DCs that are not GC
1318 # servers, and otherwise by ascending objectGUID
1319 # ELSE
1320 # SORT bhs in a random order
1321 if site.is_random_bridgehead_disabled():
1322 bhs.sort(sort_dsa_by_gc_and_guid)
1323 else:
1324 random.shuffle(bhs)
1325 debug.DEBUG_YELLOW(bhs)
1326 return bhs
1328 def is_bridgehead_failed(self, dsa, detect_failed):
1329 """Determine whether a given DC is known to be in a failed state
1331 :param dsa: the bridgehead to test
1332 :param detect_failed: True to really check, False to assume no failure
1333 :return: True if and only if the DC should be considered failed
1335 Here we DEPART from the pseudo code spec which appears to be
1336 wrong. It says, in full:
1338 /***** BridgeheadDCFailed *****/
1339 /* Determine whether a given DC is known to be in a failed state.
1340 * IN: objectGUID - objectGUID of the DC's nTDSDSA object.
1341 * IN: detectFailedDCs - TRUE if and only failed DC detection is
1342 * enabled.
1343 * RETURNS: TRUE if and only if the DC should be considered to be in a
1344 * failed state.
1346 BridgeheadDCFailed(IN GUID objectGUID, IN bool detectFailedDCs) : bool
1348 IF bit NTDSSETTINGS_OPT_IS_TOPL_DETECT_STALE_DISABLED is set in
1349 the options attribute of the site settings object for the local
1350 DC's site
1351 RETURN FALSE
1352 ELSEIF a tuple z exists in the kCCFailedLinks or
1353 kCCFailedConnections variables such that z.UUIDDsa =
1354 objectGUID, z.FailureCount > 1, and the current time -
1355 z.TimeFirstFailure > 2 hours
1356 RETURN TRUE
1357 ELSE
1358 RETURN detectFailedDCs
1359 ENDIF
1362 where you will see detectFailedDCs is not behaving as
1363 advertised -- it is acting as a default return code in the
1364 event that a failure is not detected, not a switch turning
1365 detection on or off. Elsewhere the documentation seems to
1366 concur with the comment rather than the code.
1368 if not detect_failed:
1369 return False
1371 # NTDSSETTINGS_OPT_IS_TOPL_DETECT_STALE_DISABLED = 0x00000008
1372 # When DETECT_STALE_DISABLED, we can never know of if
1373 # it's in a failed state
1374 if self.my_site.site_options & 0x00000008:
1375 return False
1377 return self.is_stale_link_connection(dsa)
1379 def create_connection(self, part, rbh, rsite, transport,
1380 lbh, lsite, link_opt, link_sched,
1381 partial_ok, detect_failed):
1382 """Create an nTDSConnection object as specified if it doesn't exist.
1384 Part of MS-ADTS 6.2.2.3.4.5
1386 :param part: crossRef object for the NC to replicate.
1387 :param rbh: nTDSDSA object for DC to act as the
1388 IDL_DRSGetNCChanges server (which is in a site other
1389 than the local DC's site).
1390 :param rsite: site of the rbh
1391 :param transport: interSiteTransport object for the transport
1392 to use for replication traffic.
1393 :param lbh: nTDSDSA object for DC to act as the
1394 IDL_DRSGetNCChanges client (which is in the local DC's site).
1395 :param lsite: site of the lbh
1396 :param link_opt: Replication parameters (aggregated siteLink options,
1397 etc.)
1398 :param link_sched: Schedule specifying the times at which
1399 to begin replicating.
1400 :partial_ok: True if bridgehead DCs containing partial
1401 replicas of the NC are acceptable.
1402 :param detect_failed: True to detect failed DCs and route
1403 replication traffic around them, FALSE to assume no DC
1404 has failed.
1406 rbhs_all = self.get_all_bridgeheads(rsite, part, transport,
1407 partial_ok, False)
1408 rbh_table = {x.dsa_dnstr: x for x in rbhs_all}
1410 debug.DEBUG_GREY("rbhs_all: %s %s" % (len(rbhs_all),
1411 [x.dsa_dnstr for x in rbhs_all]))
1413 # MS-TECH says to compute rbhs_avail but then doesn't use it
1414 # rbhs_avail = self.get_all_bridgeheads(rsite, part, transport,
1415 # partial_ok, detect_failed)
1417 lbhs_all = self.get_all_bridgeheads(lsite, part, transport,
1418 partial_ok, False)
1419 if lbh.is_ro():
1420 lbhs_all.append(lbh)
1422 debug.DEBUG_GREY("lbhs_all: %s %s" % (len(lbhs_all),
1423 [x.dsa_dnstr for x in lbhs_all]))
1425 # MS-TECH says to compute lbhs_avail but then doesn't use it
1426 # lbhs_avail = self.get_all_bridgeheads(lsite, part, transport,
1427 # partial_ok, detect_failed)
1429 # FOR each nTDSConnection object cn such that the parent of cn is
1430 # a DC in lbhsAll and cn!fromServer references a DC in rbhsAll
1431 for ldsa in lbhs_all:
1432 for cn in ldsa.connect_table.values():
1434 rdsa = rbh_table.get(cn.from_dnstr)
1435 if rdsa is None:
1436 continue
1438 debug.DEBUG_DARK_YELLOW("rdsa is %s" % rdsa.dsa_dnstr)
1439 # IF bit NTDSCONN_OPT_IS_GENERATED is set in cn!options and
1440 # NTDSCONN_OPT_RODC_TOPOLOGY is clear in cn!options and
1441 # cn!transportType references t
1442 if ((cn.is_generated() and
1443 not cn.is_rodc_topology() and
1444 cn.transport_guid == transport.guid)):
1446 # IF bit NTDSCONN_OPT_USER_OWNED_SCHEDULE is clear in
1447 # cn!options and cn!schedule != sch
1448 # Perform an originating update to set cn!schedule to
1449 # sched
1450 if ((not cn.is_user_owned_schedule() and
1451 not cn.is_equivalent_schedule(link_sched))):
1452 cn.schedule = link_sched
1453 cn.set_modified(True)
1455 # IF bits NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT and
1456 # NTDSCONN_OPT_USE_NOTIFY are set in cn
1457 if cn.is_override_notify_default() and \
1458 cn.is_use_notify():
1460 # IF bit NTDSSITELINK_OPT_USE_NOTIFY is clear in
1461 # ri.Options
1462 # Perform an originating update to clear bits
1463 # NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT and
1464 # NTDSCONN_OPT_USE_NOTIFY in cn!options
1465 if (link_opt & dsdb.NTDSSITELINK_OPT_USE_NOTIFY) == 0:
1466 cn.options &= \
1467 ~(dsdb.NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT |
1468 dsdb.NTDSCONN_OPT_USE_NOTIFY)
1469 cn.set_modified(True)
1471 # ELSE
1472 else:
1474 # IF bit NTDSSITELINK_OPT_USE_NOTIFY is set in
1475 # ri.Options
1476 # Perform an originating update to set bits
1477 # NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT and
1478 # NTDSCONN_OPT_USE_NOTIFY in cn!options
1479 if (link_opt & dsdb.NTDSSITELINK_OPT_USE_NOTIFY) != 0:
1480 cn.options |= \
1481 (dsdb.NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT |
1482 dsdb.NTDSCONN_OPT_USE_NOTIFY)
1483 cn.set_modified(True)
1485 # IF bit NTDSCONN_OPT_TWOWAY_SYNC is set in cn!options
1486 if cn.is_twoway_sync():
1488 # IF bit NTDSSITELINK_OPT_TWOWAY_SYNC is clear in
1489 # ri.Options
1490 # Perform an originating update to clear bit
1491 # NTDSCONN_OPT_TWOWAY_SYNC in cn!options
1492 if (link_opt & dsdb.NTDSSITELINK_OPT_TWOWAY_SYNC) == 0:
1493 cn.options &= ~dsdb.NTDSCONN_OPT_TWOWAY_SYNC
1494 cn.set_modified(True)
1496 # ELSE
1497 else:
1499 # IF bit NTDSSITELINK_OPT_TWOWAY_SYNC is set in
1500 # ri.Options
1501 # Perform an originating update to set bit
1502 # NTDSCONN_OPT_TWOWAY_SYNC in cn!options
1503 if (link_opt & dsdb.NTDSSITELINK_OPT_TWOWAY_SYNC) != 0:
1504 cn.options |= dsdb.NTDSCONN_OPT_TWOWAY_SYNC
1505 cn.set_modified(True)
1507 # IF bit NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION is set
1508 # in cn!options
1509 if cn.is_intersite_compression_disabled():
1511 # IF bit NTDSSITELINK_OPT_DISABLE_COMPRESSION is clear
1512 # in ri.Options
1513 # Perform an originating update to clear bit
1514 # NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION in
1515 # cn!options
1516 if ((link_opt &
1517 dsdb.NTDSSITELINK_OPT_DISABLE_COMPRESSION) == 0):
1518 cn.options &= \
1519 ~dsdb.NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION
1520 cn.set_modified(True)
1522 # ELSE
1523 else:
1524 # IF bit NTDSSITELINK_OPT_DISABLE_COMPRESSION is set in
1525 # ri.Options
1526 # Perform an originating update to set bit
1527 # NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION in
1528 # cn!options
1529 if ((link_opt &
1530 dsdb.NTDSSITELINK_OPT_DISABLE_COMPRESSION) != 0):
1531 cn.options |= \
1532 dsdb.NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION
1533 cn.set_modified(True)
1535 # Display any modified connection
1536 if self.readonly:
1537 if cn.to_be_modified:
1538 logger.info("TO BE MODIFIED:\n%s" % cn)
1540 ldsa.commit_connections(self.samdb, ro=True)
1541 else:
1542 ldsa.commit_connections(self.samdb)
1543 # ENDFOR
1545 valid_connections = 0
1547 # FOR each nTDSConnection object cn such that cn!parent is
1548 # a DC in lbhsAll and cn!fromServer references a DC in rbhsAll
1549 for ldsa in lbhs_all:
1550 for cn in ldsa.connect_table.values():
1552 rdsa = rbh_table.get(cn.from_dnstr)
1553 if rdsa is None:
1554 continue
1556 debug.DEBUG_DARK_YELLOW("round 2: rdsa is %s" % rdsa.dsa_dnstr)
1558 # IF (bit NTDSCONN_OPT_IS_GENERATED is clear in cn!options or
1559 # cn!transportType references t) and
1560 # NTDSCONN_OPT_RODC_TOPOLOGY is clear in cn!options
1561 if (((not cn.is_generated() or
1562 cn.transport_guid == transport.guid) and
1563 not cn.is_rodc_topology())):
1565 # LET rguid be the objectGUID of the nTDSDSA object
1566 # referenced by cn!fromServer
1567 # LET lguid be (cn!parent)!objectGUID
1569 # IF BridgeheadDCFailed(rguid, detectFailedDCs) = FALSE and
1570 # BridgeheadDCFailed(lguid, detectFailedDCs) = FALSE
1571 # Increment cValidConnections by 1
1572 if ((not self.is_bridgehead_failed(rdsa, detect_failed) and
1573 not self.is_bridgehead_failed(ldsa, detect_failed))):
1574 valid_connections += 1
1576 # IF keepConnections does not contain cn!objectGUID
1577 # APPEND cn!objectGUID to keepConnections
1578 self.kept_connections.add(cn)
1580 # ENDFOR
1581 debug.DEBUG_RED("valid connections %d" % valid_connections)
1582 DEBUG("kept_connections:\n%s" % (self.kept_connections,))
1583 # IF cValidConnections = 0
1584 if valid_connections == 0:
1586 # LET opt be NTDSCONN_OPT_IS_GENERATED
1587 opt = dsdb.NTDSCONN_OPT_IS_GENERATED
1589 # IF bit NTDSSITELINK_OPT_USE_NOTIFY is set in ri.Options
1590 # SET bits NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT and
1591 # NTDSCONN_OPT_USE_NOTIFY in opt
1592 if (link_opt & dsdb.NTDSSITELINK_OPT_USE_NOTIFY) != 0:
1593 opt |= (dsdb.NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT |
1594 dsdb.NTDSCONN_OPT_USE_NOTIFY)
1596 # IF bit NTDSSITELINK_OPT_TWOWAY_SYNC is set in ri.Options
1597 # SET bit NTDSCONN_OPT_TWOWAY_SYNC opt
1598 if (link_opt & dsdb.NTDSSITELINK_OPT_TWOWAY_SYNC) != 0:
1599 opt |= dsdb.NTDSCONN_OPT_TWOWAY_SYNC
1601 # IF bit NTDSSITELINK_OPT_DISABLE_COMPRESSION is set in
1602 # ri.Options
1603 # SET bit NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION in opt
1604 if ((link_opt &
1605 dsdb.NTDSSITELINK_OPT_DISABLE_COMPRESSION) != 0):
1606 opt |= dsdb.NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION
1608 # Perform an originating update to create a new nTDSConnection
1609 # object cn that is a child of lbh, cn!enabledConnection = TRUE,
1610 # cn!options = opt, cn!transportType is a reference to t,
1611 # cn!fromServer is a reference to rbh, and cn!schedule = sch
1612 DEBUG_FN("new connection, KCC dsa: %s" % self.my_dsa.dsa_dnstr)
1613 cn = lbh.new_connection(opt, 0, transport,
1614 rbh.dsa_dnstr, link_sched)
1616 # Display any added connection
1617 if self.readonly:
1618 if cn.to_be_added:
1619 logger.info("TO BE ADDED:\n%s" % cn)
1621 lbh.commit_connections(self.samdb, ro=True)
1622 else:
1623 lbh.commit_connections(self.samdb)
1625 # APPEND cn!objectGUID to keepConnections
1626 self.kept_connections.add(cn)
1628 def add_transports(self, vertex, local_vertex, graph, detect_failed):
1629 """Build a Vertex's transport lists
1631 Each vertex has accept_red_red and accept_black lists that
1632 list what transports they accept under various conditions. The
1633 only transport that is ever accepted is IP, and a dummy extra
1634 transport called "EDGE_TYPE_ALL".
1636 Part of MS-ADTS 6.2.2.3.4.3 -- ColorVertices
1638 :param vertex: the remote vertex we are thinking about
1639 :param local_vertex: the vertex relating to the local site.
1640 :param graph: the intersite graph
1641 :param detect_failed: whether to detect failed links
1642 :return: True if some bridgeheads were not found
1644 # The docs ([MS-ADTS] 6.2.2.3.4.3) say to use local_vertex
1645 # here, but using vertex seems to make more sense. That is,
1646 # the docs want this:
1648 #bh = self.get_bridgehead(vertex.site, vertex.part, transport,
1649 # local_vertex.is_black(), detect_failed)
1651 # TODO WHY?????
1653 vertex.accept_red_red = []
1654 vertex.accept_black = []
1655 found_failed = False
1656 for t_guid, transport in self.transport_table.items():
1657 if transport.name != 'IP':
1658 #XXX well this is cheating a bit
1659 logger.warning("WARNING: we are ignoring a transport named %r"
1660 % transport.name)
1661 continue
1663 # FLAG_CR_NTDS_DOMAIN 0x00000002
1664 if ((vertex.is_red() and transport.name != "IP" and
1665 vertex.part.system_flags & 0x00000002)):
1666 continue
1668 if vertex not in graph.connected_vertices:
1669 continue
1671 partial_replica_okay = vertex.is_black()
1672 bh = self.get_bridgehead(vertex.site, vertex.part, transport,
1673 partial_replica_okay, detect_failed)
1674 if bh is None:
1675 found_failed = True
1676 continue
1678 vertex.accept_red_red.append(t_guid)
1679 vertex.accept_black.append(t_guid)
1681 # Add additional transport to allow another run of Dijkstra
1682 vertex.accept_red_red.append("EDGE_TYPE_ALL")
1683 vertex.accept_black.append("EDGE_TYPE_ALL")
1685 return found_failed
1687 def create_connections(self, graph, part, detect_failed):
1688 """Construct an NC replica graph for the NC identified by
1689 the given crossRef, then create any additional nTDSConnection
1690 objects required.
1692 :param graph: site graph.
1693 :param part: crossRef object for NC.
1694 :param detect_failed: True to detect failed DCs and route
1695 replication traffic around them, False to assume no DC
1696 has failed.
1698 Modifies self.kept_connections by adding any connections
1699 deemed to be "in use".
1701 ::returns: (all_connected, found_failed_dc)
1702 (all_connected) True if the resulting NC replica graph
1703 connects all sites that need to be connected.
1704 (found_failed_dc) True if one or more failed DCs were
1705 detected.
1707 all_connected = True
1708 found_failed = False
1710 DEBUG_FN("create_connections(): enter\n"
1711 "\tpartdn=%s\n\tdetect_failed=%s" %
1712 (part.nc_dnstr, detect_failed))
1714 # XXX - This is a highly abbreviated function from the MS-TECH
1715 # ref. It creates connections between bridgeheads to all
1716 # sites that have appropriate replicas. Thus we are not
1717 # creating a minimum cost spanning tree but instead
1718 # producing a fully connected tree. This should produce
1719 # a full (albeit not optimal cost) replication topology.
1721 my_vertex = Vertex(self.my_site, part)
1722 my_vertex.color_vertex()
1724 for v in graph.vertices:
1725 v.color_vertex()
1726 if self.add_transports(v, my_vertex, graph, False):
1727 found_failed = True
1729 # No NC replicas for this NC in the site of the local DC,
1730 # so no nTDSConnection objects need be created
1731 if my_vertex.is_white():
1732 return all_connected, found_failed
1734 edge_list, n_components = get_spanning_tree_edges(graph,
1735 self.my_site,
1736 label=part.partstr)
1738 DEBUG_FN("%s Number of components: %d" %
1739 (part.nc_dnstr, n_components))
1740 if n_components > 1:
1741 all_connected = False
1743 # LET partialReplicaOkay be TRUE if and only if
1744 # localSiteVertex.Color = COLOR.BLACK
1745 partial_ok = my_vertex.is_black()
1747 # Utilize the IP transport only for now
1748 transport = self.ip_transport
1750 DEBUG("edge_list %s" % edge_list)
1751 for e in edge_list:
1752 # XXX more accurate comparison?
1753 if e.directed and e.vertices[0].site is self.my_site:
1754 continue
1756 if e.vertices[0].site is self.my_site:
1757 rsite = e.vertices[1].site
1758 else:
1759 rsite = e.vertices[0].site
1761 # We don't make connections to our own site as that
1762 # is intrasite topology generator's job
1763 if rsite is self.my_site:
1764 DEBUG("rsite is my_site")
1765 continue
1767 # Determine bridgehead server in remote site
1768 rbh = self.get_bridgehead(rsite, part, transport,
1769 partial_ok, detect_failed)
1770 if rbh is None:
1771 continue
1773 # RODC acts as an BH for itself
1774 # IF AmIRODC() then
1775 # LET lbh be the nTDSDSA object of the local DC
1776 # ELSE
1777 # LET lbh be the result of GetBridgeheadDC(localSiteVertex.ID,
1778 # cr, t, partialReplicaOkay, detectFailedDCs)
1779 if self.my_dsa.is_ro():
1780 lsite = self.my_site
1781 lbh = self.my_dsa
1782 else:
1783 lsite = self.my_site
1784 lbh = self.get_bridgehead(lsite, part, transport,
1785 partial_ok, detect_failed)
1786 # TODO
1787 if lbh is None:
1788 debug.DEBUG_RED("DISASTER! lbh is None")
1789 return False, True
1791 debug.DEBUG_CYAN("SITES")
1792 print lsite, rsite
1793 debug.DEBUG_BLUE("vertices")
1794 print e.vertices
1795 debug.DEBUG_BLUE("bridgeheads")
1796 print lbh, rbh
1797 debug.DEBUG_BLUE("-" * 70)
1799 sitelink = e.site_link
1800 if sitelink is None:
1801 link_opt = 0x0
1802 link_sched = None
1803 else:
1804 link_opt = sitelink.options
1805 link_sched = sitelink.schedule
1807 self.create_connection(part, rbh, rsite, transport,
1808 lbh, lsite, link_opt, link_sched,
1809 partial_ok, detect_failed)
1811 return all_connected, found_failed
1813 def create_intersite_connections(self):
1814 """Create NTDSConnections as necessary for all partitions.
1816 Computes an NC replica graph for each NC replica that "should be
1817 present" on the local DC or "is present" on any DC in the same site
1818 as the local DC. For each edge directed to an NC replica on such a
1819 DC from an NC replica on a DC in another site, the KCC creates an
1820 nTDSConnection object to imply that edge if one does not already
1821 exist.
1823 Modifies self.kept_connections - A set of nTDSConnection
1824 objects for edges that are directed
1825 to the local DC's site in one or more NC replica graphs.
1827 :return: True if spanning trees were created for all NC replica
1828 graphs, otherwise False.
1830 all_connected = True
1831 self.kept_connections = set()
1833 # LET crossRefList be the set containing each object o of class
1834 # crossRef such that o is a child of the CN=Partitions child of the
1835 # config NC
1837 # FOR each crossRef object cr in crossRefList
1838 # IF cr!enabled has a value and is false, or if FLAG_CR_NTDS_NC
1839 # is clear in cr!systemFlags, skip cr.
1840 # LET g be the GRAPH return of SetupGraph()
1842 for part in self.part_table.values():
1844 if not part.is_enabled():
1845 continue
1847 if part.is_foreign():
1848 continue
1850 graph = self.setup_graph(part)
1852 # Create nTDSConnection objects, routing replication traffic
1853 # around "failed" DCs.
1854 found_failed = False
1856 connected, found_failed = self.create_connections(graph,
1857 part, True)
1859 DEBUG("with detect_failed: connected %s Found failed %s" %
1860 (connected, found_failed))
1861 if not connected:
1862 all_connected = False
1864 if found_failed:
1865 # One or more failed DCs preclude use of the ideal NC
1866 # replica graph. Add connections for the ideal graph.
1867 self.create_connections(graph, part, False)
1869 return all_connected
1871 def intersite(self, ping):
1872 """The head method for generating the inter-site KCC replica
1873 connection graph and attendant nTDSConnection objects
1874 in the samdb.
1876 Produces self.kept_connections set of NTDS Connections
1877 that should be kept during subsequent pruning process.
1879 ::return (True or False): (True) if the produced NC replica
1880 graph connects all sites that need to be connected
1883 # Retrieve my DSA
1884 mydsa = self.my_dsa
1885 mysite = self.my_site
1886 all_connected = True
1888 DEBUG_FN("intersite(): enter")
1890 # Determine who is the ISTG
1891 if self.readonly:
1892 mysite.select_istg(self.samdb, mydsa, ro=True)
1893 else:
1894 mysite.select_istg(self.samdb, mydsa, ro=False)
1896 # Test whether local site has topology disabled
1897 if mysite.is_intersite_topology_disabled():
1898 DEBUG_FN("intersite(): exit disabled all_connected=%d" %
1899 all_connected)
1900 return all_connected
1902 if not mydsa.is_istg():
1903 DEBUG_FN("intersite(): exit not istg all_connected=%d" %
1904 all_connected)
1905 return all_connected
1907 self.merge_failed_links(ping)
1909 # For each NC with an NC replica that "should be present" on the
1910 # local DC or "is present" on any DC in the same site as the
1911 # local DC, the KCC constructs a site graph--a precursor to an NC
1912 # replica graph. The site connectivity for a site graph is defined
1913 # by objects of class interSiteTransport, siteLink, and
1914 # siteLinkBridge in the config NC.
1916 all_connected = self.create_intersite_connections()
1918 DEBUG_FN("intersite(): exit all_connected=%d" % all_connected)
1919 return all_connected
1921 def update_rodc_connection(self):
1922 """Updates the RODC NTFRS connection object.
1924 If the local DSA is not an RODC, this does nothing.
1926 if not self.my_dsa.is_ro():
1927 return
1929 # Given an nTDSConnection object cn1, such that cn1.options contains
1930 # NTDSCONN_OPT_RODC_TOPOLOGY, and another nTDSConnection object cn2,
1931 # does not contain NTDSCONN_OPT_RODC_TOPOLOGY, modify cn1 to ensure
1932 # that the following is true:
1934 # cn1.fromServer = cn2.fromServer
1935 # cn1.schedule = cn2.schedule
1937 # If no such cn2 can be found, cn1 is not modified.
1938 # If no such cn1 can be found, nothing is modified by this task.
1940 all_connections = self.my_dsa.connect_table.values()
1941 ro_connections = [x for x in all_connections if x.is_rodc_topology()]
1942 rw_connections = [x for x in all_connections
1943 if x not in ro_connections]
1945 # XXX here we are dealing with multiple RODC_TOPO connections,
1946 # if they exist. It is not clear whether the spec means that
1947 # or if it ever arises.
1948 if rw_connections and ro_connections:
1949 for con in ro_connections:
1950 cn2 = rw_connections[0]
1951 con.from_dnstr = cn2.from_dnstr
1952 con.schedule = cn2.schedule
1953 con.to_be_modified = True
1955 self.my_dsa.commit_connections(self.samdb, ro=self.readonly)
1957 def intrasite_max_node_edges(self, node_count):
1958 """Returns the maximum number of edges directed to a node in
1959 the intrasite replica graph.
1961 The KCC does not create more
1962 than 50 edges directed to a single DC. To optimize replication,
1963 we compute that each node should have n+2 total edges directed
1964 to it such that (n) is the smallest non-negative integer
1965 satisfying (node_count <= 2*(n*n) + 6*n + 7)
1967 (If the number of edges is m (i.e. n + 2), that is the same as
1968 2 * m*m - 2 * m + 3).
1970 edges n nodecount
1971 2 0 7
1972 3 1 15
1973 4 2 27
1974 5 3 43
1976 50 48 4903
1978 :param node_count: total number of nodes in the replica graph
1980 The intention is that there should be no more than 3 hops
1981 between any two DSAs at a site. With up to 7 nodes the 2 edges
1982 of the ring are enough; any configuration of extra edges with
1983 8 nodes will be enough. It is less clear that the 3 hop
1984 guarantee holds at e.g. 15 nodes in degenerate cases, but
1985 those are quite unlikely given the extra edges are randomly
1986 arranged.
1988 n = 0
1989 while True:
1990 if node_count <= (2 * (n * n) + (6 * n) + 7):
1991 break
1992 n = n + 1
1993 n = n + 2
1994 if n < 50:
1995 return n
1996 return 50
1998 def construct_intrasite_graph(self, site_local, dc_local,
1999 nc_x, gc_only, detect_stale):
2000 """Create an intrasite graph using given parameters
2002 This might be called a number of times per site with different
2003 parameters.
2005 Based on [MS-ADTS] 6.2.2.2
2007 :param site_local: site for which we are working
2008 :param dc_local: local DC that potentially needs a replica
2009 :param nc_x: naming context (x) that we are testing if it
2010 "should be present" on the local DC
2011 :param gc_only: Boolean - only consider global catalog servers
2012 :param detect_stale: Boolean - check whether links seems down
2013 :return: None
2015 # We're using the MS notation names here to allow
2016 # correlation back to the published algorithm.
2018 # nc_x - naming context (x) that we are testing if it
2019 # "should be present" on the local DC
2020 # f_of_x - replica (f) found on a DC (s) for NC (x)
2021 # dc_s - DC where f_of_x replica was found
2022 # dc_local - local DC that potentially needs a replica
2023 # (f_of_x)
2024 # r_list - replica list R
2025 # p_of_x - replica (p) is partial and found on a DC (s)
2026 # for NC (x)
2027 # l_of_x - replica (l) is the local replica for NC (x)
2028 # that should appear on the local DC
2029 # r_len = is length of replica list |R|
2031 # If the DSA doesn't need a replica for this
2032 # partition (NC x) then continue
2033 needed, ro, partial = nc_x.should_be_present(dc_local)
2035 debug.DEBUG_YELLOW("construct_intrasite_graph(): enter" +
2036 "\n\tgc_only=%d" % gc_only +
2037 "\n\tdetect_stale=%d" % detect_stale +
2038 "\n\tneeded=%s" % needed +
2039 "\n\tro=%s" % ro +
2040 "\n\tpartial=%s" % partial +
2041 "\n%s" % nc_x)
2043 if not needed:
2044 debug.DEBUG_RED("%s lacks 'should be present' status, "
2045 "aborting construct_intersite_graph!" %
2046 nc_x.nc_dnstr)
2047 return
2049 # Create a NCReplica that matches what the local replica
2050 # should say. We'll use this below in our r_list
2051 l_of_x = NCReplica(dc_local.dsa_dnstr, dc_local.dsa_guid,
2052 nc_x.nc_dnstr)
2054 l_of_x.identify_by_basedn(self.samdb)
2056 l_of_x.rep_partial = partial
2057 l_of_x.rep_ro = ro
2059 # Add this replica that "should be present" to the
2060 # needed replica table for this DSA
2061 dc_local.add_needed_replica(l_of_x)
2063 # Replica list
2065 # Let R be a sequence containing each writable replica f of x
2066 # such that f "is present" on a DC s satisfying the following
2067 # criteria:
2069 # * s is a writable DC other than the local DC.
2071 # * s is in the same site as the local DC.
2073 # * If x is a read-only full replica and x is a domain NC,
2074 # then the DC's functional level is at least
2075 # DS_BEHAVIOR_WIN2008.
2077 # * Bit NTDSSETTINGS_OPT_IS_TOPL_DETECT_STALE_DISABLED is set
2078 # in the options attribute of the site settings object for
2079 # the local DC's site, or no tuple z exists in the
2080 # kCCFailedLinks or kCCFailedConnections variables such
2081 # that z.UUIDDsa is the objectGUID of the nTDSDSA object
2082 # for s, z.FailureCount > 0, and the current time -
2083 # z.TimeFirstFailure > 2 hours.
2085 r_list = []
2087 # We'll loop thru all the DSAs looking for
2088 # writeable NC replicas that match the naming
2089 # context dn for (nc_x)
2091 for dc_s in self.my_site.dsa_table.values():
2092 # If this partition (nc_x) doesn't appear as a
2093 # replica (f_of_x) on (dc_s) then continue
2094 if not nc_x.nc_dnstr in dc_s.current_rep_table:
2095 continue
2097 # Pull out the NCReplica (f) of (x) with the dn
2098 # that matches NC (x) we are examining.
2099 f_of_x = dc_s.current_rep_table[nc_x.nc_dnstr]
2101 # Replica (f) of NC (x) must be writable
2102 if f_of_x.is_ro():
2103 continue
2105 # Replica (f) of NC (x) must satisfy the
2106 # "is present" criteria for DC (s) that
2107 # it was found on
2108 if not f_of_x.is_present():
2109 continue
2111 # DC (s) must be a writable DSA other than
2112 # my local DC. In other words we'd only replicate
2113 # from other writable DC
2114 if dc_s.is_ro() or dc_s is dc_local:
2115 continue
2117 # Certain replica graphs are produced only
2118 # for global catalogs, so test against
2119 # method input parameter
2120 if gc_only and not dc_s.is_gc():
2121 continue
2123 # DC (s) must be in the same site as the local DC
2124 # as this is the intra-site algorithm. This is
2125 # handled by virtue of placing DSAs in per
2126 # site objects (see enclosing for() loop)
2128 # If NC (x) is intended to be read-only full replica
2129 # for a domain NC on the target DC then the source
2130 # DC should have functional level at minimum WIN2008
2132 # Effectively we're saying that in order to replicate
2133 # to a targeted RODC (which was introduced in Windows 2008)
2134 # then we have to replicate from a DC that is also minimally
2135 # at that level.
2137 # You can also see this requirement in the MS special
2138 # considerations for RODC which state that to deploy
2139 # an RODC, at least one writable domain controller in
2140 # the domain must be running Windows Server 2008
2141 if ro and not partial and nc_x.nc_type == NCType.domain:
2142 if not dc_s.is_minimum_behavior(dsdb.DS_DOMAIN_FUNCTION_2008):
2143 continue
2145 # If we haven't been told to turn off stale connection
2146 # detection and this dsa has a stale connection then
2147 # continue
2148 if detect_stale and self.is_stale_link_connection(dc_s):
2149 continue
2151 # Replica meets criteria. Add it to table indexed
2152 # by the GUID of the DC that it appears on
2153 r_list.append(f_of_x)
2155 # If a partial (not full) replica of NC (x) "should be present"
2156 # on the local DC, append to R each partial replica (p of x)
2157 # such that p "is present" on a DC satisfying the same
2158 # criteria defined above for full replica DCs.
2160 # XXX This loop and the previous one differ only in whether
2161 # the replica is partial or not. here we only accept partial
2162 # (because we're partial); before we only accepted full. Order
2163 # doen't matter (the list is sorted a few lines down) so these
2164 # loops could easily be merged. Or this could be a helper
2165 # function.
2167 if partial:
2168 # Now we loop thru all the DSAs looking for
2169 # partial NC replicas that match the naming
2170 # context dn for (NC x)
2171 for dc_s in self.my_site.dsa_table.values():
2173 # If this partition NC (x) doesn't appear as a
2174 # replica (p) of NC (x) on the dsa DC (s) then
2175 # continue
2176 if not nc_x.nc_dnstr in dc_s.current_rep_table:
2177 continue
2179 # Pull out the NCReplica with the dn that
2180 # matches NC (x) we are examining.
2181 p_of_x = dc_s.current_rep_table[nc_x.nc_dnstr]
2183 # Replica (p) of NC (x) must be partial
2184 if not p_of_x.is_partial():
2185 continue
2187 # Replica (p) of NC (x) must satisfy the
2188 # "is present" criteria for DC (s) that
2189 # it was found on
2190 if not p_of_x.is_present():
2191 continue
2193 # DC (s) must be a writable DSA other than
2194 # my DSA. In other words we'd only replicate
2195 # from other writable DSA
2196 if dc_s.is_ro() or dc_s is dc_local:
2197 continue
2199 # Certain replica graphs are produced only
2200 # for global catalogs, so test against
2201 # method input parameter
2202 if gc_only and not dc_s.is_gc():
2203 continue
2205 # If we haven't been told to turn off stale connection
2206 # detection and this dsa has a stale connection then
2207 # continue
2208 if detect_stale and self.is_stale_link_connection(dc_s):
2209 continue
2211 # Replica meets criteria. Add it to table indexed
2212 # by the GUID of the DSA that it appears on
2213 r_list.append(p_of_x)
2215 # Append to R the NC replica that "should be present"
2216 # on the local DC
2217 r_list.append(l_of_x)
2219 r_list.sort(sort_replica_by_dsa_guid)
2220 r_len = len(r_list)
2222 max_node_edges = self.intrasite_max_node_edges(r_len)
2224 # Add a node for each r_list element to the replica graph
2225 graph_list = []
2226 for rep in r_list:
2227 node = GraphNode(rep.rep_dsa_dnstr, max_node_edges)
2228 graph_list.append(node)
2230 # For each r(i) from (0 <= i < |R|-1)
2231 i = 0
2232 while i < (r_len-1):
2233 # Add an edge from r(i) to r(i+1) if r(i) is a full
2234 # replica or r(i+1) is a partial replica
2235 if not r_list[i].is_partial() or r_list[i+1].is_partial():
2236 graph_list[i+1].add_edge_from(r_list[i].rep_dsa_dnstr)
2238 # Add an edge from r(i+1) to r(i) if r(i+1) is a full
2239 # replica or ri is a partial replica.
2240 if not r_list[i+1].is_partial() or r_list[i].is_partial():
2241 graph_list[i].add_edge_from(r_list[i+1].rep_dsa_dnstr)
2242 i = i + 1
2244 # Add an edge from r|R|-1 to r0 if r|R|-1 is a full replica
2245 # or r0 is a partial replica.
2246 if not r_list[r_len-1].is_partial() or r_list[0].is_partial():
2247 graph_list[0].add_edge_from(r_list[r_len-1].rep_dsa_dnstr)
2249 # Add an edge from r0 to r|R|-1 if r0 is a full replica or
2250 # r|R|-1 is a partial replica.
2251 if not r_list[0].is_partial() or r_list[r_len-1].is_partial():
2252 graph_list[r_len-1].add_edge_from(r_list[0].rep_dsa_dnstr)
2254 DEBUG("r_list is length %s" % len(r_list))
2255 DEBUG('\n'.join(str((x.rep_dsa_guid, x.rep_dsa_dnstr))
2256 for x in r_list))
2258 do_dot_files = self.dot_files and self.debug
2259 if self.verify or do_dot_files:
2260 dot_edges = []
2261 dot_vertices = set()
2262 for v1 in graph_list:
2263 dot_vertices.add(v1.dsa_dnstr)
2264 for v2 in v1.edge_from:
2265 dot_edges.append((v2, v1.dsa_dnstr))
2266 dot_vertices.add(v2)
2268 verify_properties = ('connected', 'directed_double_ring_or_small')
2269 verify_and_dot('intrasite_pre_ntdscon', dot_edges, dot_vertices,
2270 label='%s__%s__%s' % (site_local.site_dnstr,
2271 nctype_lut[nc_x.nc_type],
2272 nc_x.nc_dnstr),
2273 properties=verify_properties, debug=DEBUG,
2274 verify=self.verify,
2275 dot_files=do_dot_files, directed=True)
2277 # For each existing nTDSConnection object implying an edge
2278 # from rj of R to ri such that j != i, an edge from rj to ri
2279 # is not already in the graph, and the total edges directed
2280 # to ri is less than n+2, the KCC adds that edge to the graph.
2281 for vertex in graph_list:
2282 dsa = self.my_site.dsa_table[vertex.dsa_dnstr]
2283 for connect in dsa.connect_table.values():
2284 remote = connect.from_dnstr
2285 if remote in self.my_site.dsa_table:
2286 vertex.add_edge_from(remote)
2288 DEBUG('reps are: %s' % ' '.join(x.rep_dsa_dnstr for x in r_list))
2289 DEBUG('dsas are: %s' % ' '.join(x.dsa_dnstr for x in graph_list))
2291 for tnode in graph_list:
2292 # To optimize replication latency in sites with many NC
2293 # replicas, the KCC adds new edges directed to ri to bring
2294 # the total edges to n+2, where the NC replica rk of R
2295 # from which the edge is directed is chosen at random such
2296 # that k != i and an edge from rk to ri is not already in
2297 # the graph.
2299 # Note that the KCC tech ref does not give a number for
2300 # the definition of "sites with many NC replicas". At a
2301 # bare minimum to satisfy n+2 edges directed at a node we
2302 # have to have at least three replicas in |R| (i.e. if n
2303 # is zero then at least replicas from two other graph
2304 # nodes may direct edges to us).
2305 if r_len >= 3 and not tnode.has_sufficient_edges():
2306 candidates = [x for x in graph_list if
2307 (x is not tnode and
2308 x.dsa_dnstr not in tnode.edge_from)]
2310 debug.DEBUG_BLUE("looking for random link for %s. r_len %d, "
2311 "graph len %d candidates %d"
2312 % (tnode.dsa_dnstr, r_len, len(graph_list),
2313 len(candidates)))
2315 DEBUG("candidates %s" % [x.dsa_dnstr for x in candidates])
2317 while candidates and not tnode.has_sufficient_edges():
2318 other = random.choice(candidates)
2319 DEBUG("trying to add candidate %s" % other.dsa_dstr)
2320 if not tnode.add_edge_from(other):
2321 debug.DEBUG_RED("could not add %s" % other.dsa_dstr)
2322 candidates.remove(other)
2323 else:
2324 DEBUG_FN("not adding links to %s: nodes %s, links is %s/%s" %
2325 (tnode.dsa_dnstr, r_len, len(tnode.edge_from),
2326 tnode.max_edges))
2328 # Print the graph node in debug mode
2329 DEBUG_FN("%s" % tnode)
2331 # For each edge directed to the local DC, ensure a nTDSConnection
2332 # points to us that satisfies the KCC criteria
2334 if tnode.dsa_dnstr == dc_local.dsa_dnstr:
2335 tnode.add_connections_from_edges(dc_local)
2337 if self.verify or do_dot_files:
2338 dot_edges = []
2339 dot_vertices = set()
2340 for v1 in graph_list:
2341 dot_vertices.add(v1.dsa_dnstr)
2342 for v2 in v1.edge_from:
2343 dot_edges.append((v2, v1.dsa_dnstr))
2344 dot_vertices.add(v2)
2346 verify_properties = ('connected', 'directed_double_ring_or_small')
2347 verify_and_dot('intrasite_post_ntdscon', dot_edges, dot_vertices,
2348 label='%s__%s__%s' % (site_local.site_dnstr,
2349 nctype_lut[nc_x.nc_type],
2350 nc_x.nc_dnstr),
2351 properties=verify_properties, debug=DEBUG,
2352 verify=self.verify,
2353 dot_files=do_dot_files, directed=True)
2355 def intrasite(self):
2356 """The head method for generating the intra-site KCC replica
2357 connection graph and attendant nTDSConnection objects
2358 in the samdb
2360 # Retrieve my DSA
2361 mydsa = self.my_dsa
2363 DEBUG_FN("intrasite(): enter")
2365 # Test whether local site has topology disabled
2366 mysite = self.my_site
2367 if mysite.is_intrasite_topology_disabled():
2368 return
2370 detect_stale = (not mysite.is_detect_stale_disabled())
2371 for connect in mydsa.connect_table.values():
2372 if connect.to_be_added:
2373 debug.DEBUG_CYAN("TO BE ADDED:\n%s" % connect)
2375 # Loop thru all the partitions, with gc_only False
2376 for partdn, part in self.part_table.items():
2377 self.construct_intrasite_graph(mysite, mydsa, part, False,
2378 detect_stale)
2379 for connect in mydsa.connect_table.values():
2380 if connect.to_be_added:
2381 debug.DEBUG_BLUE("TO BE ADDED:\n%s" % connect)
2383 # If the DC is a GC server, the KCC constructs an additional NC
2384 # replica graph (and creates nTDSConnection objects) for the
2385 # config NC as above, except that only NC replicas that "are present"
2386 # on GC servers are added to R.
2387 for connect in mydsa.connect_table.values():
2388 if connect.to_be_added:
2389 debug.DEBUG_YELLOW("TO BE ADDED:\n%s" % connect)
2391 # Do it again, with gc_only True
2392 for partdn, part in self.part_table.items():
2393 if part.is_config():
2394 self.construct_intrasite_graph(mysite, mydsa, part, True,
2395 detect_stale)
2397 # The DC repeats the NC replica graph computation and nTDSConnection
2398 # creation for each of the NC replica graphs, this time assuming
2399 # that no DC has failed. It does so by re-executing the steps as
2400 # if the bit NTDSSETTINGS_OPT_IS_TOPL_DETECT_STALE_DISABLED were
2401 # set in the options attribute of the site settings object for
2402 # the local DC's site. (ie. we set "detec_stale" flag to False)
2403 for connect in mydsa.connect_table.values():
2404 if connect.to_be_added:
2405 debug.DEBUG_BLUE("TO BE ADDED:\n%s" % connect)
2407 # Loop thru all the partitions.
2408 for partdn, part in self.part_table.items():
2409 self.construct_intrasite_graph(mysite, mydsa, part, False,
2410 False) # don't detect stale
2412 # If the DC is a GC server, the KCC constructs an additional NC
2413 # replica graph (and creates nTDSConnection objects) for the
2414 # config NC as above, except that only NC replicas that "are present"
2415 # on GC servers are added to R.
2416 for connect in mydsa.connect_table.values():
2417 if connect.to_be_added:
2418 debug.DEBUG_RED("TO BE ADDED:\n%s" % connect)
2420 for partdn, part in self.part_table.items():
2421 if part.is_config():
2422 self.construct_intrasite_graph(mysite, mydsa, part, True,
2423 False) # don't detect stale
2425 if self.readonly:
2426 # Display any to be added or modified repsFrom
2427 for connect in mydsa.connect_table.values():
2428 if connect.to_be_deleted:
2429 logger.info("TO BE DELETED:\n%s" % connect)
2430 if connect.to_be_modified:
2431 logger.info("TO BE MODIFIED:\n%s" % connect)
2432 if connect.to_be_added:
2433 debug.DEBUG_GREEN("TO BE ADDED:\n%s" % connect)
2435 mydsa.commit_connections(self.samdb, ro=True)
2436 else:
2437 # Commit any newly created connections to the samdb
2438 mydsa.commit_connections(self.samdb)
2440 def list_dsas(self):
2441 """Compile a comprehensive list of DSA DNs
2443 These are all the DSAs on all the sites that KCC would be
2444 dealing with.
2446 This method is not idempotent and may not work correctly in
2447 sequence with KCC.run().
2449 :return: a list of DSA DN strings.
2451 self.load_my_site()
2452 self.load_my_dsa()
2454 self.load_all_sites()
2455 self.load_all_partitions()
2456 self.load_all_transports()
2457 self.load_all_sitelinks()
2458 dsas = []
2459 for site in self.site_table.values():
2460 dsas.extend([dsa.dsa_dnstr.replace('CN=NTDS Settings,', '', 1)
2461 for dsa in site.dsa_table.values()])
2462 return dsas
2464 def load_samdb(self, dburl, lp, creds):
2465 """Load the database using an url, loadparm, and credentials
2467 :param dburl: a database url.
2468 :param lp: a loadparm object.
2469 :param creds: a Credentials object.
2471 self.samdb = SamDB(url=dburl,
2472 session_info=system_session(),
2473 credentials=creds, lp=lp)
2475 def plot_all_connections(self, basename, verify_properties=()):
2476 verify = verify_properties and self.verify
2477 plot = self.dot_files
2478 if not (verify or plot):
2479 return
2481 dot_edges = []
2482 dot_vertices = []
2483 edge_colours = []
2484 vertex_colours = []
2486 for dsa in self.dsa_by_dnstr.values():
2487 dot_vertices.append(dsa.dsa_dnstr)
2488 if dsa.is_ro():
2489 vertex_colours.append('#cc0000')
2490 else:
2491 vertex_colours.append('#0000cc')
2492 for con in dsa.connect_table.values():
2493 if con.is_rodc_topology():
2494 edge_colours.append('red')
2495 else:
2496 edge_colours.append('blue')
2497 dot_edges.append((con.from_dnstr, dsa.dsa_dnstr))
2499 verify_and_dot(basename, dot_edges, vertices=dot_vertices,
2500 label=self.my_dsa_dnstr, properties=verify_properties,
2501 debug=DEBUG, verify=verify, dot_files=plot,
2502 directed=True, edge_colors=edge_colours,
2503 vertex_colors=vertex_colours)
2505 def run(self, dburl, lp, creds, forced_local_dsa=None,
2506 forget_local_links=False, forget_intersite_links=False,
2507 attempt_live_connections=False):
2508 """Method to perform a complete run of the KCC and
2509 produce an updated topology for subsequent NC replica
2510 syncronization between domain controllers
2512 # We may already have a samdb setup if we are
2513 # currently importing an ldif for a test run
2514 if self.samdb is None:
2515 try:
2516 self.load_samdb(dburl, lp, creds)
2517 except ldb.LdbError, (num, msg):
2518 logger.error("Unable to open sam database %s : %s" %
2519 (dburl, msg))
2520 return 1
2522 if forced_local_dsa:
2523 self.samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" %
2524 forced_local_dsa)
2526 try:
2527 # Setup
2528 self.load_my_site()
2529 self.load_my_dsa()
2531 self.load_all_sites()
2532 self.load_all_partitions()
2533 self.load_all_transports()
2534 self.load_all_sitelinks()
2536 if self.verify or self.dot_files:
2537 guid_to_dnstr = {}
2538 for site in self.site_table.values():
2539 guid_to_dnstr.update((str(dsa.dsa_guid), dnstr)
2540 for dnstr, dsa
2541 in site.dsa_table.items())
2543 self.plot_all_connections('dsa_initial')
2545 dot_edges = []
2546 current_reps, needed_reps = self.my_dsa.get_rep_tables()
2547 for dnstr, c_rep in current_reps.items():
2548 DEBUG("c_rep %s" % c_rep)
2549 dot_edges.append((self.my_dsa.dsa_dnstr, dnstr))
2551 verify_and_dot('dsa_repsFrom_initial', dot_edges,
2552 directed=True, label=self.my_dsa_dnstr,
2553 properties=(), debug=DEBUG, verify=self.verify,
2554 dot_files=self.dot_files)
2556 dot_edges = []
2557 for site in self.site_table.values():
2558 for dsa in site.dsa_table.values():
2559 current_reps, needed_reps = dsa.get_rep_tables()
2560 for dn_str, rep in current_reps.items():
2561 for reps_from in rep.rep_repsFrom:
2562 DEBUG("rep %s" % rep)
2563 dsa_guid = str(reps_from.source_dsa_obj_guid)
2564 dsa_dn = guid_to_dnstr[dsa_guid]
2565 dot_edges.append((dsa.dsa_dnstr, dsa_dn))
2567 verify_and_dot('dsa_repsFrom_initial_all', dot_edges,
2568 directed=True, label=self.my_dsa_dnstr,
2569 properties=(), debug=DEBUG, verify=self.verify,
2570 dot_files=self.dot_files)
2572 dot_edges = []
2573 for link in self.sitelink_table.values():
2574 for a, b in itertools.combinations(link.site_list, 2):
2575 dot_edges.append((str(a), str(b)))
2576 properties = ('connected',)
2577 verify_and_dot('dsa_sitelink_initial', dot_edges,
2578 directed=False,
2579 label=self.my_dsa_dnstr, properties=properties,
2580 debug=DEBUG, verify=self.verify,
2581 dot_files=self.dot_files)
2583 if forget_local_links:
2584 for dsa in self.my_site.dsa_table.values():
2585 dsa.connect_table = {k: v for k, v in
2586 dsa.connect_table.items()
2587 if v.is_rodc_topology()}
2588 self.plot_all_connections('dsa_forgotten_local')
2590 if forget_intersite_links:
2591 for site in self.site_table.values():
2592 for dsa in site.dsa_table.values():
2593 dsa.connect_table = {k: v for k, v in
2594 dsa.connect_table.items()
2595 if site is self.my_site and
2596 v.is_rodc_topology()}
2598 self.plot_all_connections('dsa_forgotten_all')
2600 if attempt_live_connections:
2601 # Encapsulates lp and creds in a function that
2602 # attempts connections to remote DSAs.
2603 def ping(self, dnsname):
2604 try:
2605 drs_utils.drsuapi_connect(dnsname, self.lp, self.creds)
2606 except drs_utils.drsException:
2607 return False
2608 return True
2609 else:
2610 ping = None
2611 # These are the published steps (in order) for the
2612 # MS-TECH description of the KCC algorithm ([MS-ADTS] 6.2.2)
2614 # Step 1
2615 self.refresh_failed_links_connections(ping)
2617 # Step 2
2618 self.intrasite()
2620 # Step 3
2621 all_connected = self.intersite(ping)
2623 # Step 4
2624 self.remove_unneeded_ntdsconn(all_connected)
2626 # Step 5
2627 self.translate_ntdsconn()
2629 # Step 6
2630 self.remove_unneeded_failed_links_connections()
2632 # Step 7
2633 self.update_rodc_connection()
2635 if self.verify or self.dot_files:
2636 self.plot_all_connections('dsa_final',
2637 ('connected', 'forest_of_rings'))
2639 debug.DEBUG_MAGENTA("there are %d dsa guids" %
2640 len(guid_to_dnstr))
2642 dot_edges = []
2643 edge_colors = []
2644 my_dnstr = self.my_dsa.dsa_dnstr
2645 current_reps, needed_reps = self.my_dsa.get_rep_tables()
2646 for dnstr, n_rep in needed_reps.items():
2647 for reps_from in n_rep.rep_repsFrom:
2648 guid_str = str(reps_from.source_dsa_obj_guid)
2649 dot_edges.append((my_dnstr, guid_to_dnstr[guid_str]))
2650 edge_colors.append('#' + str(n_rep.nc_guid)[:6])
2652 verify_and_dot('dsa_repsFrom_final', dot_edges, directed=True,
2653 label=self.my_dsa_dnstr,
2654 properties=(), debug=DEBUG, verify=self.verify,
2655 dot_files=self.dot_files,
2656 edge_colors=edge_colors)
2658 dot_edges = []
2660 for site in self.site_table.values():
2661 for dsa in site.dsa_table.values():
2662 current_reps, needed_reps = dsa.get_rep_tables()
2663 for n_rep in needed_reps.values():
2664 for reps_from in n_rep.rep_repsFrom:
2665 dsa_guid = str(reps_from.source_dsa_obj_guid)
2666 dsa_dn = guid_to_dnstr[dsa_guid]
2667 dot_edges.append((dsa.dsa_dnstr, dsa_dn))
2669 verify_and_dot('dsa_repsFrom_final_all', dot_edges,
2670 directed=True, label=self.my_dsa_dnstr,
2671 properties=(), debug=DEBUG, verify=self.verify,
2672 dot_files=self.dot_files)
2674 except:
2675 raise
2677 return 0
2679 def import_ldif(self, dburl, lp, creds, ldif_file):
2680 """Import all objects and attributes that are relevent
2681 to the KCC algorithms from a previously exported LDIF file.
2683 The point of this function is to allow a programmer/debugger to
2684 import an LDIF file with non-security relevent information that
2685 was previously extracted from a DC database. The LDIF file is used
2686 to create a temporary abbreviated database. The KCC algorithm can
2687 then run against this abbreviated database for debug or test
2688 verification that the topology generated is computationally the
2689 same between different OSes and algorithms.
2691 :param dburl: path to the temporary abbreviated db to create
2692 :param ldif_file: path to the ldif file to import
2694 try:
2695 self.samdb = ldif_import_export.ldif_to_samdb(dburl, lp, ldif_file,
2696 self.forced_local_dsa)
2697 except ldif_import_export.LdifError, e:
2698 print e
2699 return 1
2700 return 0
2702 def export_ldif(self, dburl, lp, creds, ldif_file):
2703 """Routine to extract all objects and attributes that are relevent
2704 to the KCC algorithms from a DC database.
2706 The point of this function is to allow a programmer/debugger to
2707 extract an LDIF file with non-security relevent information from
2708 a DC database. The LDIF file can then be used to "import" via
2709 the import_ldif() function this file into a temporary abbreviated
2710 database. The KCC algorithm can then run against this abbreviated
2711 database for debug or test verification that the topology generated
2712 is computationally the same between different OSes and algorithms.
2714 :param dburl: LDAP database URL to extract info from
2715 :param ldif_file: output LDIF file name to create
2717 try:
2718 ldif_import_export.samdb_to_ldif_file(self.samdb, dburl, lp, creds,
2719 ldif_file)
2720 except ldif_import_export.LdifError, e:
2721 print e
2722 return 1
2723 return 0