KCC: improve documentation for KCC.import_ldif()
[Samba.git] / python / samba / kcc / __init__.py
blobe45b6963071b6e0d04f7d4a9ba1d010f7017b2e4
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.transport_table = {}
112 self.ip_transport = None
113 self.sitelink_table = {}
114 self.dsa_by_dnstr = {}
115 self.dsa_by_guid = {}
117 self.get_dsa_by_guidstr = self.dsa_by_guid.get
118 self.get_dsa = self.dsa_by_dnstr.get
120 # TODO: These should be backed by a 'permanent' store so that when
121 # calling DRSGetReplInfo with DS_REPL_INFO_KCC_DSA_CONNECT_FAILURES,
122 # the failure information can be returned
123 self.kcc_failed_links = {}
124 self.kcc_failed_connections = set()
126 # Used in inter-site topology computation. A list
127 # of connections (by NTDSConnection object) that are
128 # to be kept when pruning un-needed NTDS Connections
129 self.kept_connections = set()
131 self.my_dsa_dnstr = None # My dsa DN
132 self.my_dsa = None # My dsa object
134 self.my_site_dnstr = None
135 self.my_site = None
137 self.samdb = None
139 self.unix_now = unix_now
140 self.nt_now = unix2nttime(unix_now)
141 self.readonly = readonly
142 self.verify = verify
143 self.debug = debug
144 self.dot_file_dir = dot_file_dir
146 def load_all_transports(self):
147 """Loads the inter-site transport objects for Sites
149 :return: None
150 :raise KCCError: if no IP transport is found
152 try:
153 res = self.samdb.search("CN=Inter-Site Transports,CN=Sites,%s" %
154 self.samdb.get_config_basedn(),
155 scope=ldb.SCOPE_SUBTREE,
156 expression="(objectClass=interSiteTransport)")
157 except ldb.LdbError, (enum, estr):
158 raise KCCError("Unable to find inter-site transports - (%s)" %
159 estr)
161 for msg in res:
162 dnstr = str(msg.dn)
164 transport = Transport(dnstr)
166 transport.load_transport(self.samdb)
167 self.transport_table.setdefault(str(transport.guid),
168 transport)
169 if transport.name == 'IP':
170 self.ip_transport = transport
172 if self.ip_transport is None:
173 raise KCCError("there doesn't seem to be an IP transport")
175 def load_all_sitelinks(self):
176 """Loads the inter-site siteLink objects
178 :return: None
179 :raise KCCError: if site-links aren't found
181 try:
182 res = self.samdb.search("CN=Inter-Site Transports,CN=Sites,%s" %
183 self.samdb.get_config_basedn(),
184 scope=ldb.SCOPE_SUBTREE,
185 expression="(objectClass=siteLink)")
186 except ldb.LdbError, (enum, estr):
187 raise KCCError("Unable to find inter-site siteLinks - (%s)" % estr)
189 for msg in res:
190 dnstr = str(msg.dn)
192 # already loaded
193 if dnstr in self.sitelink_table:
194 continue
196 sitelink = SiteLink(dnstr)
198 sitelink.load_sitelink(self.samdb)
200 # Assign this siteLink to table
201 # and index by dn
202 self.sitelink_table[dnstr] = sitelink
204 def load_site(self, dn_str):
205 """Helper for load_my_site and load_all_sites.
207 Put all the site's DSAs into the KCC indices.
209 :param dn_str: a site dn_str
210 :return: the Site object pertaining to the dn_str
212 site = Site(dn_str, self.unix_now)
213 site.load_site(self.samdb)
215 # We avoid replacing the site with an identical copy in case
216 # somewhere else has a reference to the old one, which would
217 # lead to all manner of confusion and chaos.
218 guid = str(site.site_guid)
219 if guid not in self.site_table:
220 self.site_table[guid] = site
221 self.dsa_by_dnstr.update(site.dsa_table)
222 self.dsa_by_guid.update((str(x.dsa_guid), x)
223 for x in site.dsa_table.values())
225 return self.site_table[guid]
227 def load_my_site(self):
228 """Load the Site object for the local DSA.
230 :return: None
232 self.my_site_dnstr = ("CN=%s,CN=Sites,%s" % (
233 self.samdb.server_site_name(),
234 self.samdb.get_config_basedn()))
236 self.my_site = self.load_site(self.my_site_dnstr)
238 def load_all_sites(self):
239 """Discover all sites and create Site objects.
241 :return: None
242 :raise: KCCError if sites can't be found
244 try:
245 res = self.samdb.search("CN=Sites,%s" %
246 self.samdb.get_config_basedn(),
247 scope=ldb.SCOPE_SUBTREE,
248 expression="(objectClass=site)")
249 except ldb.LdbError, (enum, estr):
250 raise KCCError("Unable to find sites - (%s)" % estr)
252 for msg in res:
253 sitestr = str(msg.dn)
254 self.load_site(sitestr)
256 def load_my_dsa(self):
257 """Discover my nTDSDSA dn thru the rootDSE entry
259 :return: None
260 :raise: KCCError if DSA can't be found
262 dn = ldb.Dn(self.samdb, "<GUID=%s>" % self.samdb.get_ntds_GUID())
263 try:
264 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
265 attrs=["objectGUID"])
266 except ldb.LdbError, (enum, estr):
267 logger.warning("Search for %s failed: %s. This typically happens"
268 " in --importldif mode due to lack of module"
269 " support.", dn, estr)
270 try:
271 # We work around the failure above by looking at the
272 # dsServiceName that was put in the fake rootdse by
273 # the --exportldif, rather than the
274 # samdb.get_ntds_GUID(). The disadvantage is that this
275 # mode requires we modify the @ROOTDSE dnq to support
276 # --forced-local-dsa
277 service_name_res = self.samdb.search(base="",
278 scope=ldb.SCOPE_BASE,
279 attrs=["dsServiceName"])
280 dn = ldb.Dn(self.samdb,
281 service_name_res[0]["dsServiceName"][0])
283 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
284 attrs=["objectGUID"])
285 except ldb.LdbError, (enum, estr):
286 raise KCCError("Unable to find my nTDSDSA - (%s)" % estr)
288 if len(res) != 1:
289 raise KCCError("Unable to find my nTDSDSA at %s" %
290 dn.extended_str())
292 ntds_guid = misc.GUID(self.samdb.get_ntds_GUID())
293 if misc.GUID(res[0]["objectGUID"][0]) != ntds_guid:
294 raise KCCError("Did not find the GUID we expected,"
295 " perhaps due to --importldif")
297 self.my_dsa_dnstr = str(res[0].dn)
299 self.my_dsa = self.my_site.get_dsa(self.my_dsa_dnstr)
301 if self.my_dsa_dnstr not in self.dsa_by_dnstr:
302 debug.DEBUG_DARK_YELLOW("my_dsa %s isn't in self.dsas_by_dnstr:"
303 " it must be RODC.\n"
304 "Let's add it, because my_dsa is special!"
305 "\n(likewise for self.dsa_by_guid)" %
306 self.my_dsas_dnstr)
308 self.dsa_by_dnstr[self.my_dsa_dnstr] = self.my_dsa
309 self.dsa_by_guid[str(self.my_dsa.dsa_guid)] = self.my_dsa
311 def load_all_partitions(self):
312 """Discover and load all partitions.
314 Each NC is inserted into the part_table by partition
315 dn string (not the nCName dn string)
317 :return: None
318 :raise: KCCError if partitions can't be found
320 try:
321 res = self.samdb.search("CN=Partitions,%s" %
322 self.samdb.get_config_basedn(),
323 scope=ldb.SCOPE_SUBTREE,
324 expression="(objectClass=crossRef)")
325 except ldb.LdbError, (enum, estr):
326 raise KCCError("Unable to find partitions - (%s)" % estr)
328 for msg in res:
329 partstr = str(msg.dn)
331 # already loaded
332 if partstr in self.part_table:
333 continue
335 part = Partition(partstr)
337 part.load_partition(self.samdb)
338 self.part_table[partstr] = part
340 def refresh_failed_links_connections(self, ping=None):
341 """Ensure the failed links list is up to date
343 Based on MS-ADTS 6.2.2.1
345 :param ping: An oracle function of remote site availability
346 :return: None
348 # LINKS: Refresh failed links
349 self.kcc_failed_links = {}
350 current, needed = self.my_dsa.get_rep_tables()
351 for replica in current.values():
352 # For every possible connection to replicate
353 for reps_from in replica.rep_repsFrom:
354 failure_count = reps_from.consecutive_sync_failures
355 if failure_count <= 0:
356 continue
358 dsa_guid = str(reps_from.source_dsa_obj_guid)
359 time_first_failure = reps_from.last_success
360 last_result = reps_from.last_attempt
361 dns_name = reps_from.dns_name1
363 f = self.kcc_failed_links.get(dsa_guid)
364 if f is None:
365 f = KCCFailedObject(dsa_guid, failure_count,
366 time_first_failure, last_result,
367 dns_name)
368 self.kcc_failed_links[dsa_guid] = f
369 else:
370 f.failure_count = max(f.failure_count, failure_count)
371 f.time_first_failure = min(f.time_first_failure,
372 time_first_failure)
373 f.last_result = last_result
375 # CONNECTIONS: Refresh failed connections
376 restore_connections = set()
377 if ping is not None:
378 DEBUG("refresh_failed_links: checking if links are still down")
379 for connection in self.kcc_failed_connections:
380 if ping(connection.dns_name):
381 # Failed connection is no longer failing
382 restore_connections.add(connection)
383 else:
384 connection.failure_count += 1
385 else:
386 DEBUG("refresh_failed_links: not checking live links because we\n"
387 "weren't asked to --attempt-live-connections")
389 # Remove the restored connections from the failed connections
390 self.kcc_failed_connections.difference_update(restore_connections)
392 def is_stale_link_connection(self, target_dsa):
393 """Check whether a link to a remote DSA is stale
395 Used in MS-ADTS 6.2.2.2 Intrasite Connection Creation
397 Returns True if the remote seems to have been down for at
398 least two hours, otherwise False.
400 :param target_dsa: the remote DSA object
401 :return: True if link is stale, otherwise False
403 failed_link = self.kcc_failed_links.get(str(target_dsa.dsa_guid))
404 if failed_link:
405 # failure_count should be > 0, but check anyways
406 if failed_link.failure_count > 0:
407 unix_first_failure = \
408 nttime2unix(failed_link.time_first_failure)
409 # TODO guard against future
410 if unix_first_failure > self.unix_now:
411 logger.error("The last success time attribute for \
412 repsFrom is in the future!")
414 # Perform calculation in seconds
415 if (self.unix_now - unix_first_failure) > 60 * 60 * 2:
416 return True
418 # TODO connections.
419 # We have checked failed *links*, but we also need to check
420 # *connections*
422 return False
424 # TODO: This should be backed by some form of local database
425 def remove_unneeded_failed_links_connections(self):
426 # Remove all tuples in kcc_failed_links where failure count = 0
427 # In this implementation, this should never happen.
429 # Remove all connections which were not used this run or connections
430 # that became active during this run.
431 pass
433 def remove_unneeded_ntdsconn(self, all_connected):
434 """Remove unneeded NTDS Connections once topology is calculated
436 Based on MS-ADTS 6.2.2.4 Removing Unnecessary Connections
438 :param all_connected: indicates whether all sites are connected
439 :return: None
441 mydsa = self.my_dsa
443 # New connections won't have GUIDs which are needed for
444 # sorting. Add them.
445 for cn_conn in mydsa.connect_table.values():
446 if cn_conn.guid is None:
447 if self.readonly:
448 cn_conn.guid = misc.GUID(str(uuid.uuid4()))
449 cn_conn.whenCreated = self.nt_now
450 else:
451 cn_conn.load_connection(self.samdb)
453 for cn_conn in mydsa.connect_table.values():
455 s_dnstr = cn_conn.get_from_dnstr()
456 if s_dnstr is None:
457 cn_conn.to_be_deleted = True
458 continue
460 #XXX should an RODC be regarded as same site
461 same_site = s_dnstr in self.my_site.dsa_table
463 # Given an nTDSConnection object cn, if the DC with the
464 # nTDSDSA object dc that is the parent object of cn and
465 # the DC with the nTDSDA object referenced by cn!fromServer
466 # are in the same site, the KCC on dc deletes cn if all of
467 # the following are true:
469 # Bit NTDSCONN_OPT_IS_GENERATED is clear in cn!options.
471 # No site settings object s exists for the local DC's site, or
472 # bit NTDSSETTINGS_OPT_IS_TOPL_CLEANUP_DISABLED is clear in
473 # s!options.
475 # Another nTDSConnection object cn2 exists such that cn and
476 # cn2 have the same parent object, cn!fromServer = cn2!fromServer,
477 # and either
479 # cn!whenCreated < cn2!whenCreated
481 # cn!whenCreated = cn2!whenCreated and
482 # cn!objectGUID < cn2!objectGUID
484 # Bit NTDSCONN_OPT_RODC_TOPOLOGY is clear in cn!options
485 if same_site:
486 if not cn_conn.is_generated():
487 continue
489 if self.my_site.is_cleanup_ntdsconn_disabled():
490 continue
492 # Loop thru connections looking for a duplicate that
493 # fulfills the previous criteria
494 lesser = False
495 packed_guid = ndr_pack(cn_conn.guid)
496 for cn2_conn in mydsa.connect_table.values():
497 if cn2_conn is cn_conn:
498 continue
500 s2_dnstr = cn2_conn.get_from_dnstr()
502 # If the NTDS Connections has a different
503 # fromServer field then no match
504 if s2_dnstr != s_dnstr:
505 continue
507 #XXX GUID comparison
508 lesser = (cn_conn.whenCreated < cn2_conn.whenCreated or
509 (cn_conn.whenCreated == cn2_conn.whenCreated and
510 packed_guid < ndr_pack(cn2_conn.guid)))
512 if lesser:
513 break
515 if lesser and not cn_conn.is_rodc_topology():
516 cn_conn.to_be_deleted = True
518 # Given an nTDSConnection object cn, if the DC with the nTDSDSA
519 # object dc that is the parent object of cn and the DC with
520 # the nTDSDSA object referenced by cn!fromServer are in
521 # different sites, a KCC acting as an ISTG in dc's site
522 # deletes cn if all of the following are true:
524 # Bit NTDSCONN_OPT_IS_GENERATED is clear in cn!options.
526 # cn!fromServer references an nTDSDSA object for a DC
527 # in a site other than the local DC's site.
529 # The keepConnections sequence returned by
530 # CreateIntersiteConnections() does not contain
531 # cn!objectGUID, or cn is "superseded by" (see below)
532 # another nTDSConnection cn2 and keepConnections
533 # contains cn2!objectGUID.
535 # The return value of CreateIntersiteConnections()
536 # was true.
538 # Bit NTDSCONN_OPT_RODC_TOPOLOGY is clear in
539 # cn!options
541 else: # different site
543 if not mydsa.is_istg():
544 continue
546 if not cn_conn.is_generated():
547 continue
549 # TODO
550 # We are directly using this connection in intersite or
551 # we are using a connection which can supersede this one.
553 # MS-ADTS 6.2.2.4 - Removing Unnecessary Connections does not
554 # appear to be correct.
556 # 1. cn!fromServer and cn!parent appear inconsistent with
557 # no cn2
558 # 2. The repsFrom do not imply each other
560 if cn_conn in self.kept_connections: # and not_superceded:
561 continue
563 # This is the result of create_intersite_connections
564 if not all_connected:
565 continue
567 if not cn_conn.is_rodc_topology():
568 cn_conn.to_be_deleted = True
570 if mydsa.is_ro() or self.readonly:
571 for connect in mydsa.connect_table.values():
572 if connect.to_be_deleted:
573 DEBUG_FN("TO BE DELETED:\n%s" % connect)
574 if connect.to_be_added:
575 DEBUG_FN("TO BE ADDED:\n%s" % connect)
577 # Peform deletion from our tables but perform
578 # no database modification
579 mydsa.commit_connections(self.samdb, ro=True)
580 else:
581 # Commit any modified connections
582 mydsa.commit_connections(self.samdb)
584 def modify_repsFrom(self, n_rep, t_repsFrom, s_rep, s_dsa, cn_conn):
585 """Update an repsFrom object if required.
587 Part of MS-ADTS 6.2.2.5.
589 Update t_repsFrom if necessary to satisfy requirements. Such
590 updates are typically required when the IDL_DRSGetNCChanges
591 server has moved from one site to another--for example, to
592 enable compression when the server is moved from the
593 client's site to another site.
595 The repsFrom.update_flags bit field may be modified
596 auto-magically if any changes are made here. See
597 kcc_utils.RepsFromTo for gory details.
600 :param n_rep: NC replica we need
601 :param t_repsFrom: repsFrom tuple to modify
602 :param s_rep: NC replica at source DSA
603 :param s_dsa: source DSA
604 :param cn_conn: Local DSA NTDSConnection child
606 :return: None
608 s_dnstr = s_dsa.dsa_dnstr
609 same_site = s_dnstr in self.my_site.dsa_table
611 # if schedule doesn't match then update and modify
612 times = convert_schedule_to_repltimes(cn_conn.schedule)
613 if times != t_repsFrom.schedule:
614 t_repsFrom.schedule = times
616 # Bit DRS_PER_SYNC is set in replicaFlags if and only
617 # if nTDSConnection schedule has a value v that specifies
618 # scheduled replication is to be performed at least once
619 # per week.
620 if cn_conn.is_schedule_minimum_once_per_week():
622 if ((t_repsFrom.replica_flags &
623 drsuapi.DRSUAPI_DRS_PER_SYNC) == 0x0):
624 t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_PER_SYNC
626 # Bit DRS_INIT_SYNC is set in t.replicaFlags if and only
627 # if the source DSA and the local DC's nTDSDSA object are
628 # in the same site or source dsa is the FSMO role owner
629 # of one or more FSMO roles in the NC replica.
630 if same_site or n_rep.is_fsmo_role_owner(s_dnstr):
632 if ((t_repsFrom.replica_flags &
633 drsuapi.DRSUAPI_DRS_INIT_SYNC) == 0x0):
634 t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_INIT_SYNC
636 # If bit NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT is set in
637 # cn!options, bit DRS_NEVER_NOTIFY is set in t.replicaFlags
638 # if and only if bit NTDSCONN_OPT_USE_NOTIFY is clear in
639 # cn!options. Otherwise, bit DRS_NEVER_NOTIFY is set in
640 # t.replicaFlags if and only if s and the local DC's
641 # nTDSDSA object are in different sites.
642 if ((cn_conn.options &
643 dsdb.NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT) != 0x0):
645 if (cn_conn.options & dsdb.NTDSCONN_OPT_USE_NOTIFY) == 0x0:
646 # XXX WARNING
648 # it LOOKS as if this next test is a bit silly: it
649 # checks the flag then sets it if it not set; the same
650 # effect could be achieved by unconditionally setting
651 # it. But in fact the repsFrom object has special
652 # magic attached to it, and altering replica_flags has
653 # side-effects. That is bad in my opinion, but there
654 # you go.
655 if ((t_repsFrom.replica_flags &
656 drsuapi.DRSUAPI_DRS_NEVER_NOTIFY) == 0x0):
657 t_repsFrom.replica_flags |= \
658 drsuapi.DRSUAPI_DRS_NEVER_NOTIFY
660 elif not same_site:
662 if ((t_repsFrom.replica_flags &
663 drsuapi.DRSUAPI_DRS_NEVER_NOTIFY) == 0x0):
664 t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_NEVER_NOTIFY
666 # Bit DRS_USE_COMPRESSION is set in t.replicaFlags if
667 # and only if s and the local DC's nTDSDSA object are
668 # not in the same site and the
669 # NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION bit is
670 # clear in cn!options
671 if (not same_site and
672 (cn_conn.options &
673 dsdb.NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION) == 0x0):
675 if ((t_repsFrom.replica_flags &
676 drsuapi.DRSUAPI_DRS_USE_COMPRESSION) == 0x0):
677 t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_USE_COMPRESSION
679 # Bit DRS_TWOWAY_SYNC is set in t.replicaFlags if and only
680 # if bit NTDSCONN_OPT_TWOWAY_SYNC is set in cn!options.
681 if (cn_conn.options & dsdb.NTDSCONN_OPT_TWOWAY_SYNC) != 0x0:
683 if ((t_repsFrom.replica_flags &
684 drsuapi.DRSUAPI_DRS_TWOWAY_SYNC) == 0x0):
685 t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_TWOWAY_SYNC
687 # Bits DRS_DISABLE_AUTO_SYNC and DRS_DISABLE_PERIODIC_SYNC are
688 # set in t.replicaFlags if and only if cn!enabledConnection = false.
689 if not cn_conn.is_enabled():
691 if ((t_repsFrom.replica_flags &
692 drsuapi.DRSUAPI_DRS_DISABLE_AUTO_SYNC) == 0x0):
693 t_repsFrom.replica_flags |= \
694 drsuapi.DRSUAPI_DRS_DISABLE_AUTO_SYNC
696 if ((t_repsFrom.replica_flags &
697 drsuapi.DRSUAPI_DRS_DISABLE_PERIODIC_SYNC) == 0x0):
698 t_repsFrom.replica_flags |= \
699 drsuapi.DRSUAPI_DRS_DISABLE_PERIODIC_SYNC
701 # If s and the local DC's nTDSDSA object are in the same site,
702 # cn!transportType has no value, or the RDN of cn!transportType
703 # is CN=IP:
705 # Bit DRS_MAIL_REP in t.replicaFlags is clear.
707 # t.uuidTransport = NULL GUID.
709 # t.uuidDsa = The GUID-based DNS name of s.
711 # Otherwise:
713 # Bit DRS_MAIL_REP in t.replicaFlags is set.
715 # If x is the object with dsname cn!transportType,
716 # t.uuidTransport = x!objectGUID.
718 # Let a be the attribute identified by
719 # x!transportAddressAttribute. If a is
720 # the dNSHostName attribute, t.uuidDsa = the GUID-based
721 # DNS name of s. Otherwise, t.uuidDsa = (s!parent)!a.
723 # It appears that the first statement i.e.
725 # "If s and the local DC's nTDSDSA object are in the same
726 # site, cn!transportType has no value, or the RDN of
727 # cn!transportType is CN=IP:"
729 # could be a slightly tighter statement if it had an "or"
730 # between each condition. I believe this should
731 # be interpreted as:
733 # IF (same-site) OR (no-value) OR (type-ip)
735 # because IP should be the primary transport mechanism
736 # (even in inter-site) and the absense of the transportType
737 # attribute should always imply IP no matter if its multi-site
739 # NOTE MS-TECH INCORRECT:
741 # All indications point to these statements above being
742 # incorrectly stated:
744 # t.uuidDsa = The GUID-based DNS name of s.
746 # Let a be the attribute identified by
747 # x!transportAddressAttribute. If a is
748 # the dNSHostName attribute, t.uuidDsa = the GUID-based
749 # DNS name of s. Otherwise, t.uuidDsa = (s!parent)!a.
751 # because the uuidDSA is a GUID and not a GUID-base DNS
752 # name. Nor can uuidDsa hold (s!parent)!a if not
753 # dNSHostName. What should have been said is:
755 # t.naDsa = The GUID-based DNS name of s
757 # That would also be correct if transportAddressAttribute
758 # were "mailAddress" because (naDsa) can also correctly
759 # hold the SMTP ISM service address.
761 nastr = "%s._msdcs.%s" % (s_dsa.dsa_guid, self.samdb.forest_dns_name())
763 if ((t_repsFrom.replica_flags &
764 drsuapi.DRSUAPI_DRS_MAIL_REP) != 0x0):
765 t_repsFrom.replica_flags &= ~drsuapi.DRSUAPI_DRS_MAIL_REP
767 t_repsFrom.transport_guid = misc.GUID()
769 # See (NOTE MS-TECH INCORRECT) above
771 # XXX it looks like these conditionals are pointless, because
772 # the state will end up as `t_repsFrom.dns_name1 == nastr` in
773 # either case, BUT the repsFrom thing is magic and assigning
774 # to it alters some flags. So we try not to update it unless
775 # necessary.
776 if t_repsFrom.dns_name1 != nastr:
777 t_repsFrom.dns_name1 = nastr
779 if t_repsFrom.version > 0x1 and t_repsFrom.dns_name2 != nastr:
780 t_repsFrom.dns_name2 = nastr
783 if t_repsFrom.is_modified():
784 DEBUG_FN("modify_repsFrom(): %s" % t_repsFrom)
786 def get_dsa_for_implied_replica(self, n_rep, cn_conn):
787 """If a connection imply a replica, find the relevant DSA
789 Given a NC replica and NTDS Connection, determine if the
790 connection implies a repsFrom tuple should be present from the
791 source DSA listed in the connection to the naming context. If
792 it should be, return the DSA; otherwise return None.
794 Based on part of MS-ADTS 6.2.2.5
796 :param n_rep: NC replica
797 :param cn_conn: NTDS Connection
798 :return: source DSA or None
800 #XXX different conditions for "implies" than MS-ADTS 6.2.2
802 # NTDS Connection must satisfy all the following criteria
803 # to imply a repsFrom tuple is needed:
805 # cn!enabledConnection = true.
806 # cn!options does not contain NTDSCONN_OPT_RODC_TOPOLOGY.
807 # cn!fromServer references an nTDSDSA object.
809 if not cn_conn.is_enabled() or cn_conn.is_rodc_topology():
810 return None
812 s_dnstr = cn_conn.get_from_dnstr()
813 s_dsa = self.get_dsa(s_dnstr)
815 # No DSA matching this source DN string?
816 if s_dsa is None:
817 return None
819 # To imply a repsFrom tuple is needed, each of these
820 # must be True:
822 # An NC replica of the NC "is present" on the DC to
823 # which the nTDSDSA object referenced by cn!fromServer
824 # corresponds.
826 # An NC replica of the NC "should be present" on
827 # the local DC
828 s_rep = s_dsa.get_current_replica(n_rep.nc_dnstr)
830 if s_rep is None or not s_rep.is_present():
831 return None
833 # To imply a repsFrom tuple is needed, each of these
834 # must be True:
836 # The NC replica on the DC referenced by cn!fromServer is
837 # a writable replica or the NC replica that "should be
838 # present" on the local DC is a partial replica.
840 # The NC is not a domain NC, the NC replica that
841 # "should be present" on the local DC is a partial
842 # replica, cn!transportType has no value, or
843 # cn!transportType has an RDN of CN=IP.
845 implied = (not s_rep.is_ro() or n_rep.is_partial()) and \
846 (not n_rep.is_domain() or
847 n_rep.is_partial() or
848 cn_conn.transport_dnstr is None or
849 cn_conn.transport_dnstr.find("CN=IP") == 0)
851 if implied:
852 return s_dsa
853 return None
855 def translate_ntdsconn(self, current_dsa=None):
856 """Adjust repsFrom to match NTDSConnections
858 This function adjusts values of repsFrom abstract attributes of NC
859 replicas on the local DC to match those implied by
860 nTDSConnection objects.
862 Based on [MS-ADTS] 6.2.2.5
864 :param current_dsa: optional DSA on whose behalf we are acting.
865 :return: None
867 count = 0
869 if current_dsa is None:
870 current_dsa = self.my_dsa
872 if current_dsa.is_translate_ntdsconn_disabled():
873 DEBUG_FN("skipping translate_ntdsconn() "
874 "because disabling flag is set")
875 return
877 DEBUG_FN("translate_ntdsconn(): enter")
879 current_rep_table, needed_rep_table = current_dsa.get_rep_tables()
881 # Filled in with replicas we currently have that need deleting
882 delete_reps = set()
884 # We're using the MS notation names here to allow
885 # correlation back to the published algorithm.
887 # n_rep - NC replica (n)
888 # t_repsFrom - tuple (t) in n!repsFrom
889 # s_dsa - Source DSA of the replica. Defined as nTDSDSA
890 # object (s) such that (s!objectGUID = t.uuidDsa)
891 # In our IDL representation of repsFrom the (uuidDsa)
892 # attribute is called (source_dsa_obj_guid)
893 # cn_conn - (cn) is nTDSConnection object and child of the local
894 # DC's nTDSDSA object and (cn!fromServer = s)
895 # s_rep - source DSA replica of n
897 # If we have the replica and its not needed
898 # then we add it to the "to be deleted" list.
899 for dnstr in current_rep_table:
900 if dnstr not in needed_rep_table:
901 delete_reps.add(dnstr)
903 DEBUG_FN('current %d needed %d delete %d' % (len(current_rep_table),
904 len(needed_rep_table), len(delete_reps)))
906 if delete_reps:
907 DEBUG('deleting these reps: %s' % delete_reps)
908 for dnstr in delete_reps:
909 del current_rep_table[dnstr]
911 # Now perform the scan of replicas we'll need
912 # and compare any current repsFrom against the
913 # connections
914 for n_rep in needed_rep_table.values():
916 # load any repsFrom and fsmo roles as we'll
917 # need them during connection translation
918 n_rep.load_repsFrom(self.samdb)
919 n_rep.load_fsmo_roles(self.samdb)
921 # Loop thru the existing repsFrom tupples (if any)
922 # XXX This is a list and could contain duplicates
923 # (multiple load_repsFrom calls)
924 for t_repsFrom in n_rep.rep_repsFrom:
926 # for each tuple t in n!repsFrom, let s be the nTDSDSA
927 # object such that s!objectGUID = t.uuidDsa
928 guidstr = str(t_repsFrom.source_dsa_obj_guid)
929 s_dsa = self.get_dsa_by_guidstr(guidstr)
931 # Source dsa is gone from config (strange)
932 # so cleanup stale repsFrom for unlisted DSA
933 if s_dsa is None:
934 logger.warning("repsFrom source DSA guid (%s) not found" %
935 guidstr)
936 t_repsFrom.to_be_deleted = True
937 continue
939 # Find the connection that this repsFrom would use. If
940 # there isn't a good one (i.e. non-RODC_TOPOLOGY,
941 # meaning non-FRS), we delete the repsFrom.
942 s_dnstr = s_dsa.dsa_dnstr
943 connections = current_dsa.get_connection_by_from_dnstr(s_dnstr)
944 for cn_conn in connections:
945 if not cn_conn.is_rodc_topology():
946 break
947 else:
948 # no break means no non-rodc_topology connection exists
949 t_repsFrom.to_be_deleted = True
950 continue
952 # KCC removes this repsFrom tuple if any of the following
953 # is true:
954 # No NC replica of the NC "is present" on DSA that
955 # would be source of replica
957 # A writable replica of the NC "should be present" on
958 # the local DC, but a partial replica "is present" on
959 # the source DSA
960 s_rep = s_dsa.get_current_replica(n_rep.nc_dnstr)
962 if s_rep is None or not s_rep.is_present() or \
963 (not n_rep.is_ro() and s_rep.is_partial()):
965 t_repsFrom.to_be_deleted = True
966 continue
968 # If the KCC did not remove t from n!repsFrom, it updates t
969 self.modify_repsFrom(n_rep, t_repsFrom, s_rep, s_dsa, cn_conn)
971 # Loop thru connections and add implied repsFrom tuples
972 # for each NTDSConnection under our local DSA if the
973 # repsFrom is not already present
974 for cn_conn in current_dsa.connect_table.values():
976 s_dsa = self.get_dsa_for_implied_replica(n_rep, cn_conn)
977 if s_dsa is None:
978 continue
980 # Loop thru the existing repsFrom tupples (if any) and
981 # if we already have a tuple for this connection then
982 # no need to proceed to add. It will have been changed
983 # to have the correct attributes above
984 for t_repsFrom in n_rep.rep_repsFrom:
985 guidstr = str(t_repsFrom.source_dsa_obj_guid)
986 #XXX what?
987 if s_dsa is self.get_dsa_by_guidstr(guidstr):
988 s_dsa = None
989 break
991 if s_dsa is None:
992 continue
994 # Create a new RepsFromTo and proceed to modify
995 # it according to specification
996 t_repsFrom = RepsFromTo(n_rep.nc_dnstr)
998 t_repsFrom.source_dsa_obj_guid = s_dsa.dsa_guid
1000 s_rep = s_dsa.get_current_replica(n_rep.nc_dnstr)
1002 self.modify_repsFrom(n_rep, t_repsFrom, s_rep, s_dsa, cn_conn)
1004 # Add to our NC repsFrom as this is newly computed
1005 if t_repsFrom.is_modified():
1006 n_rep.rep_repsFrom.append(t_repsFrom)
1008 if self.readonly:
1009 # Display any to be deleted or modified repsFrom
1010 text = n_rep.dumpstr_to_be_deleted()
1011 if text:
1012 logger.info("TO BE DELETED:\n%s" % text)
1013 text = n_rep.dumpstr_to_be_modified()
1014 if text:
1015 logger.info("TO BE MODIFIED:\n%s" % text)
1017 # Peform deletion from our tables but perform
1018 # no database modification
1019 n_rep.commit_repsFrom(self.samdb, ro=True)
1020 else:
1021 # Commit any modified repsFrom to the NC replica
1022 n_rep.commit_repsFrom(self.samdb)
1024 def merge_failed_links(self, ping=None):
1025 """Merge of kCCFailedLinks and kCCFailedLinks from bridgeheads.
1027 The KCC on a writable DC attempts to merge the link and connection
1028 failure information from bridgehead DCs in its own site to help it
1029 identify failed bridgehead DCs.
1031 Based on MS-ADTS 6.2.2.3.2 "Merge of kCCFailedLinks and kCCFailedLinks
1032 from Bridgeheads"
1034 :param ping: An oracle of current bridgehead availability
1035 :return: None
1037 # 1. Queries every bridgehead server in your site (other than yourself)
1038 # 2. For every ntDSConnection that references a server in a different
1039 # site merge all the failure info
1041 # XXX - not implemented yet
1042 if ping is not None:
1043 debug.DEBUG_RED("merge_failed_links() is NOT IMPLEMENTED")
1044 else:
1045 DEBUG_FN("skipping merge_failed_links() because it requires "
1046 "real network connections\n"
1047 "and we weren't asked to --attempt-live-connections")
1049 def setup_graph(self, part):
1050 """Set up an intersite graph
1052 An intersite graph has a Vertex for each site object, a
1053 MultiEdge for each SiteLink object, and a MutliEdgeSet for
1054 each siteLinkBridge object (or implied siteLinkBridge). It
1055 reflects the intersite topology in a slightly more abstract
1056 graph form.
1058 Roughly corresponds to MS-ADTS 6.2.2.3.4.3
1060 :param part: a Partition object
1061 :returns: an InterSiteGraph object
1063 # If 'Bridge all site links' is enabled and Win2k3 bridges required
1064 # is not set
1065 # NTDSTRANSPORT_OPT_BRIDGES_REQUIRED 0x00000002
1066 # No documentation for this however, ntdsapi.h appears to have:
1067 # NTDSSETTINGS_OPT_W2K3_BRIDGES_REQUIRED = 0x00001000
1068 bridges_required = self.my_site.site_options & 0x00001002 == 0
1070 g = setup_graph(part, self.site_table, self.transport_table,
1071 self.sitelink_table, bridges_required)
1073 if self.verify or self.dot_file_dir is not None:
1074 dot_edges = []
1075 for edge in g.edges:
1076 for a, b in itertools.combinations(edge.vertices, 2):
1077 dot_edges.append((a.site.site_dnstr, b.site.site_dnstr))
1078 verify_properties = ()
1079 verify_and_dot('site_edges', dot_edges, directed=False,
1080 label=self.my_dsa_dnstr,
1081 properties=verify_properties, debug=DEBUG,
1082 verify=self.verify,
1083 dot_file_dir=self.dot_file_dir)
1085 return g
1087 def get_bridgehead(self, site, part, transport, partial_ok, detect_failed):
1088 """Get a bridghead DC for a site.
1090 Part of MS-ADTS 6.2.2.3.4.4
1092 :param site: site object representing for which a bridgehead
1093 DC is desired.
1094 :param part: crossRef for NC to replicate.
1095 :param transport: interSiteTransport object for replication
1096 traffic.
1097 :param partial_ok: True if a DC containing a partial
1098 replica or a full replica will suffice, False if only
1099 a full replica will suffice.
1100 :param detect_failed: True to detect failed DCs and route
1101 replication traffic around them, False to assume no DC
1102 has failed.
1103 :return: dsa object for the bridgehead DC or None
1106 bhs = self.get_all_bridgeheads(site, part, transport,
1107 partial_ok, detect_failed)
1108 if len(bhs) == 0:
1109 debug.DEBUG_MAGENTA("get_bridgehead:\n\tsitedn=%s\n\tbhdn=None" %
1110 site.site_dnstr)
1111 return None
1112 else:
1113 debug.DEBUG_GREEN("get_bridgehead:\n\tsitedn=%s\n\tbhdn=%s" %
1114 (site.site_dnstr, bhs[0].dsa_dnstr))
1115 return bhs[0]
1117 def get_all_bridgeheads(self, site, part, transport,
1118 partial_ok, detect_failed):
1119 """Get all bridghead DCs on a site satisfying the given criteria
1121 Part of MS-ADTS 6.2.2.3.4.4
1123 :param site: site object representing the site for which
1124 bridgehead DCs are desired.
1125 :param part: partition for NC to replicate.
1126 :param transport: interSiteTransport object for
1127 replication traffic.
1128 :param partial_ok: True if a DC containing a partial
1129 replica or a full replica will suffice, False if
1130 only a full replica will suffice.
1131 :param detect_failed: True to detect failed DCs and route
1132 replication traffic around them, FALSE to assume
1133 no DC has failed.
1134 :return: list of dsa object for available bridgehead DCs
1136 bhs = []
1138 DEBUG_FN("get_all_bridgeheads: %s" % transport.name)
1139 DEBUG_FN(site.rw_dsa_table)
1140 for dsa in site.rw_dsa_table.values():
1142 pdnstr = dsa.get_parent_dnstr()
1144 # IF t!bridgeheadServerListBL has one or more values and
1145 # t!bridgeheadServerListBL does not contain a reference
1146 # to the parent object of dc then skip dc
1147 if ((len(transport.bridgehead_list) != 0 and
1148 pdnstr not in transport.bridgehead_list)):
1149 continue
1151 # IF dc is in the same site as the local DC
1152 # IF a replica of cr!nCName is not in the set of NC replicas
1153 # that "should be present" on dc or a partial replica of the
1154 # NC "should be present" but partialReplicasOkay = FALSE
1155 # Skip dc
1156 if self.my_site.same_site(dsa):
1157 needed, ro, partial = part.should_be_present(dsa)
1158 if not needed or (partial and not partial_ok):
1159 continue
1160 rep = dsa.get_current_replica(part.nc_dnstr)
1162 # ELSE
1163 # IF an NC replica of cr!nCName is not in the set of NC
1164 # replicas that "are present" on dc or a partial replica of
1165 # the NC "is present" but partialReplicasOkay = FALSE
1166 # Skip dc
1167 else:
1168 rep = dsa.get_current_replica(part.nc_dnstr)
1169 if rep is None or (rep.is_partial() and not partial_ok):
1170 continue
1172 # IF AmIRODC() and cr!nCName corresponds to default NC then
1173 # Let dsaobj be the nTDSDSA object of the dc
1174 # IF dsaobj.msDS-Behavior-Version < DS_DOMAIN_FUNCTION_2008
1175 # Skip dc
1176 if self.my_dsa.is_ro() and rep is not None and rep.is_default():
1177 if not dsa.is_minimum_behavior(dsdb.DS_DOMAIN_FUNCTION_2008):
1178 continue
1180 # IF t!name != "IP" and the parent object of dc has no value for
1181 # the attribute specified by t!transportAddressAttribute
1182 # Skip dc
1183 if transport.name != "IP":
1184 # MS tech specification says we retrieve the named
1185 # attribute in "transportAddressAttribute" from the parent
1186 # of the DSA object
1187 try:
1188 attrs = [transport.address_attr]
1190 res = self.samdb.search(base=pdnstr, scope=ldb.SCOPE_BASE,
1191 attrs=attrs)
1192 except ldb.LdbError, (enum, estr):
1193 continue
1195 msg = res[0]
1196 if transport.address_attr not in msg:
1197 continue
1198 #XXX nastr is NEVER USED. It will be removed.
1199 nastr = str(msg[transport.address_attr][0])
1201 # IF BridgeheadDCFailed(dc!objectGUID, detectFailedDCs) = TRUE
1202 # Skip dc
1203 if self.is_bridgehead_failed(dsa, detect_failed):
1204 DEBUG("bridgehead is failed")
1205 continue
1207 DEBUG_FN("get_all_bridgeheads: dsadn=%s" % dsa.dsa_dnstr)
1208 bhs.append(dsa)
1210 # IF bit NTDSSETTINGS_OPT_IS_RAND_BH_SELECTION_DISABLED is set in
1211 # s!options
1212 # SORT bhs such that all GC servers precede DCs that are not GC
1213 # servers, and otherwise by ascending objectGUID
1214 # ELSE
1215 # SORT bhs in a random order
1216 if site.is_random_bridgehead_disabled():
1217 bhs.sort(sort_dsa_by_gc_and_guid)
1218 else:
1219 random.shuffle(bhs)
1220 debug.DEBUG_YELLOW(bhs)
1221 return bhs
1223 def is_bridgehead_failed(self, dsa, detect_failed):
1224 """Determine whether a given DC is known to be in a failed state
1226 :param dsa: the bridgehead to test
1227 :param detect_failed: True to really check, False to assume no failure
1228 :return: True if and only if the DC should be considered failed
1230 Here we DEPART from the pseudo code spec which appears to be
1231 wrong. It says, in full:
1233 /***** BridgeheadDCFailed *****/
1234 /* Determine whether a given DC is known to be in a failed state.
1235 * IN: objectGUID - objectGUID of the DC's nTDSDSA object.
1236 * IN: detectFailedDCs - TRUE if and only failed DC detection is
1237 * enabled.
1238 * RETURNS: TRUE if and only if the DC should be considered to be in a
1239 * failed state.
1241 BridgeheadDCFailed(IN GUID objectGUID, IN bool detectFailedDCs) : bool
1243 IF bit NTDSSETTINGS_OPT_IS_TOPL_DETECT_STALE_DISABLED is set in
1244 the options attribute of the site settings object for the local
1245 DC's site
1246 RETURN FALSE
1247 ELSEIF a tuple z exists in the kCCFailedLinks or
1248 kCCFailedConnections variables such that z.UUIDDsa =
1249 objectGUID, z.FailureCount > 1, and the current time -
1250 z.TimeFirstFailure > 2 hours
1251 RETURN TRUE
1252 ELSE
1253 RETURN detectFailedDCs
1254 ENDIF
1257 where you will see detectFailedDCs is not behaving as
1258 advertised -- it is acting as a default return code in the
1259 event that a failure is not detected, not a switch turning
1260 detection on or off. Elsewhere the documentation seems to
1261 concur with the comment rather than the code.
1263 if not detect_failed:
1264 return False
1266 # NTDSSETTINGS_OPT_IS_TOPL_DETECT_STALE_DISABLED = 0x00000008
1267 # When DETECT_STALE_DISABLED, we can never know of if
1268 # it's in a failed state
1269 if self.my_site.site_options & 0x00000008:
1270 return False
1272 return self.is_stale_link_connection(dsa)
1274 def create_connection(self, part, rbh, rsite, transport,
1275 lbh, lsite, link_opt, link_sched,
1276 partial_ok, detect_failed):
1277 """Create an nTDSConnection object as specified if it doesn't exist.
1279 Part of MS-ADTS 6.2.2.3.4.5
1281 :param part: crossRef object for the NC to replicate.
1282 :param rbh: nTDSDSA object for DC to act as the
1283 IDL_DRSGetNCChanges server (which is in a site other
1284 than the local DC's site).
1285 :param rsite: site of the rbh
1286 :param transport: interSiteTransport object for the transport
1287 to use for replication traffic.
1288 :param lbh: nTDSDSA object for DC to act as the
1289 IDL_DRSGetNCChanges client (which is in the local DC's site).
1290 :param lsite: site of the lbh
1291 :param link_opt: Replication parameters (aggregated siteLink options,
1292 etc.)
1293 :param link_sched: Schedule specifying the times at which
1294 to begin replicating.
1295 :partial_ok: True if bridgehead DCs containing partial
1296 replicas of the NC are acceptable.
1297 :param detect_failed: True to detect failed DCs and route
1298 replication traffic around them, FALSE to assume no DC
1299 has failed.
1301 rbhs_all = self.get_all_bridgeheads(rsite, part, transport,
1302 partial_ok, False)
1303 rbh_table = {x.dsa_dnstr: x for x in rbhs_all}
1305 debug.DEBUG_GREY("rbhs_all: %s %s" % (len(rbhs_all),
1306 [x.dsa_dnstr for x in rbhs_all]))
1308 # MS-TECH says to compute rbhs_avail but then doesn't use it
1309 # rbhs_avail = self.get_all_bridgeheads(rsite, part, transport,
1310 # partial_ok, detect_failed)
1312 lbhs_all = self.get_all_bridgeheads(lsite, part, transport,
1313 partial_ok, False)
1314 if lbh.is_ro():
1315 lbhs_all.append(lbh)
1317 debug.DEBUG_GREY("lbhs_all: %s %s" % (len(lbhs_all),
1318 [x.dsa_dnstr for x in lbhs_all]))
1320 # MS-TECH says to compute lbhs_avail but then doesn't use it
1321 # lbhs_avail = self.get_all_bridgeheads(lsite, part, transport,
1322 # partial_ok, detect_failed)
1324 # FOR each nTDSConnection object cn such that the parent of cn is
1325 # a DC in lbhsAll and cn!fromServer references a DC in rbhsAll
1326 for ldsa in lbhs_all:
1327 for cn in ldsa.connect_table.values():
1329 rdsa = rbh_table.get(cn.from_dnstr)
1330 if rdsa is None:
1331 continue
1333 debug.DEBUG_DARK_YELLOW("rdsa is %s" % rdsa.dsa_dnstr)
1334 # IF bit NTDSCONN_OPT_IS_GENERATED is set in cn!options and
1335 # NTDSCONN_OPT_RODC_TOPOLOGY is clear in cn!options and
1336 # cn!transportType references t
1337 if ((cn.is_generated() and
1338 not cn.is_rodc_topology() and
1339 cn.transport_guid == transport.guid)):
1341 # IF bit NTDSCONN_OPT_USER_OWNED_SCHEDULE is clear in
1342 # cn!options and cn!schedule != sch
1343 # Perform an originating update to set cn!schedule to
1344 # sched
1345 if ((not cn.is_user_owned_schedule() and
1346 not cn.is_equivalent_schedule(link_sched))):
1347 cn.schedule = link_sched
1348 cn.set_modified(True)
1350 # IF bits NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT and
1351 # NTDSCONN_OPT_USE_NOTIFY are set in cn
1352 if cn.is_override_notify_default() and \
1353 cn.is_use_notify():
1355 # IF bit NTDSSITELINK_OPT_USE_NOTIFY is clear in
1356 # ri.Options
1357 # Perform an originating update to clear 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 # ELSE
1367 else:
1369 # IF bit NTDSSITELINK_OPT_USE_NOTIFY is set in
1370 # ri.Options
1371 # Perform an originating update to set bits
1372 # NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT and
1373 # NTDSCONN_OPT_USE_NOTIFY in cn!options
1374 if (link_opt & dsdb.NTDSSITELINK_OPT_USE_NOTIFY) != 0:
1375 cn.options |= \
1376 (dsdb.NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT |
1377 dsdb.NTDSCONN_OPT_USE_NOTIFY)
1378 cn.set_modified(True)
1380 # IF bit NTDSCONN_OPT_TWOWAY_SYNC is set in cn!options
1381 if cn.is_twoway_sync():
1383 # IF bit NTDSSITELINK_OPT_TWOWAY_SYNC is clear in
1384 # ri.Options
1385 # Perform an originating update to clear bit
1386 # NTDSCONN_OPT_TWOWAY_SYNC in cn!options
1387 if (link_opt & dsdb.NTDSSITELINK_OPT_TWOWAY_SYNC) == 0:
1388 cn.options &= ~dsdb.NTDSCONN_OPT_TWOWAY_SYNC
1389 cn.set_modified(True)
1391 # ELSE
1392 else:
1394 # IF bit NTDSSITELINK_OPT_TWOWAY_SYNC is set in
1395 # ri.Options
1396 # Perform an originating update to set bit
1397 # NTDSCONN_OPT_TWOWAY_SYNC in cn!options
1398 if (link_opt & dsdb.NTDSSITELINK_OPT_TWOWAY_SYNC) != 0:
1399 cn.options |= dsdb.NTDSCONN_OPT_TWOWAY_SYNC
1400 cn.set_modified(True)
1402 # IF bit NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION is set
1403 # in cn!options
1404 if cn.is_intersite_compression_disabled():
1406 # IF bit NTDSSITELINK_OPT_DISABLE_COMPRESSION is clear
1407 # in ri.Options
1408 # Perform an originating update to clear bit
1409 # NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION in
1410 # cn!options
1411 if ((link_opt &
1412 dsdb.NTDSSITELINK_OPT_DISABLE_COMPRESSION) == 0):
1413 cn.options &= \
1414 ~dsdb.NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION
1415 cn.set_modified(True)
1417 # ELSE
1418 else:
1419 # IF bit NTDSSITELINK_OPT_DISABLE_COMPRESSION is set in
1420 # ri.Options
1421 # Perform an originating update to set bit
1422 # NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION in
1423 # cn!options
1424 if ((link_opt &
1425 dsdb.NTDSSITELINK_OPT_DISABLE_COMPRESSION) != 0):
1426 cn.options |= \
1427 dsdb.NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION
1428 cn.set_modified(True)
1430 # Display any modified connection
1431 if self.readonly:
1432 if cn.to_be_modified:
1433 logger.info("TO BE MODIFIED:\n%s" % cn)
1435 ldsa.commit_connections(self.samdb, ro=True)
1436 else:
1437 ldsa.commit_connections(self.samdb)
1438 # ENDFOR
1440 valid_connections = 0
1442 # FOR each nTDSConnection object cn such that cn!parent is
1443 # a DC in lbhsAll and cn!fromServer references a DC in rbhsAll
1444 for ldsa in lbhs_all:
1445 for cn in ldsa.connect_table.values():
1447 rdsa = rbh_table.get(cn.from_dnstr)
1448 if rdsa is None:
1449 continue
1451 debug.DEBUG_DARK_YELLOW("round 2: rdsa is %s" % rdsa.dsa_dnstr)
1453 # IF (bit NTDSCONN_OPT_IS_GENERATED is clear in cn!options or
1454 # cn!transportType references t) and
1455 # NTDSCONN_OPT_RODC_TOPOLOGY is clear in cn!options
1456 if (((not cn.is_generated() or
1457 cn.transport_guid == transport.guid) and
1458 not cn.is_rodc_topology())):
1460 # LET rguid be the objectGUID of the nTDSDSA object
1461 # referenced by cn!fromServer
1462 # LET lguid be (cn!parent)!objectGUID
1464 # IF BridgeheadDCFailed(rguid, detectFailedDCs) = FALSE and
1465 # BridgeheadDCFailed(lguid, detectFailedDCs) = FALSE
1466 # Increment cValidConnections by 1
1467 if ((not self.is_bridgehead_failed(rdsa, detect_failed) and
1468 not self.is_bridgehead_failed(ldsa, detect_failed))):
1469 valid_connections += 1
1471 # IF keepConnections does not contain cn!objectGUID
1472 # APPEND cn!objectGUID to keepConnections
1473 self.kept_connections.add(cn)
1475 # ENDFOR
1476 debug.DEBUG_RED("valid connections %d" % valid_connections)
1477 DEBUG("kept_connections:\n%s" % (self.kept_connections,))
1478 # IF cValidConnections = 0
1479 if valid_connections == 0:
1481 # LET opt be NTDSCONN_OPT_IS_GENERATED
1482 opt = dsdb.NTDSCONN_OPT_IS_GENERATED
1484 # IF bit NTDSSITELINK_OPT_USE_NOTIFY is set in ri.Options
1485 # SET bits NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT and
1486 # NTDSCONN_OPT_USE_NOTIFY in opt
1487 if (link_opt & dsdb.NTDSSITELINK_OPT_USE_NOTIFY) != 0:
1488 opt |= (dsdb.NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT |
1489 dsdb.NTDSCONN_OPT_USE_NOTIFY)
1491 # IF bit NTDSSITELINK_OPT_TWOWAY_SYNC is set in ri.Options
1492 # SET bit NTDSCONN_OPT_TWOWAY_SYNC opt
1493 if (link_opt & dsdb.NTDSSITELINK_OPT_TWOWAY_SYNC) != 0:
1494 opt |= dsdb.NTDSCONN_OPT_TWOWAY_SYNC
1496 # IF bit NTDSSITELINK_OPT_DISABLE_COMPRESSION is set in
1497 # ri.Options
1498 # SET bit NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION in opt
1499 if ((link_opt &
1500 dsdb.NTDSSITELINK_OPT_DISABLE_COMPRESSION) != 0):
1501 opt |= dsdb.NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION
1503 # Perform an originating update to create a new nTDSConnection
1504 # object cn that is a child of lbh, cn!enabledConnection = TRUE,
1505 # cn!options = opt, cn!transportType is a reference to t,
1506 # cn!fromServer is a reference to rbh, and cn!schedule = sch
1507 DEBUG_FN("new connection, KCC dsa: %s" % self.my_dsa.dsa_dnstr)
1508 cn = lbh.new_connection(opt, 0, transport,
1509 rbh.dsa_dnstr, link_sched)
1511 # Display any added connection
1512 if self.readonly:
1513 if cn.to_be_added:
1514 logger.info("TO BE ADDED:\n%s" % cn)
1516 lbh.commit_connections(self.samdb, ro=True)
1517 else:
1518 lbh.commit_connections(self.samdb)
1520 # APPEND cn!objectGUID to keepConnections
1521 self.kept_connections.add(cn)
1523 def add_transports(self, vertex, local_vertex, graph, detect_failed):
1524 """Build a Vertex's transport lists
1526 Each vertex has accept_red_red and accept_black lists that
1527 list what transports they accept under various conditions. The
1528 only transport that is ever accepted is IP, and a dummy extra
1529 transport called "EDGE_TYPE_ALL".
1531 Part of MS-ADTS 6.2.2.3.4.3 -- ColorVertices
1533 :param vertex: the remote vertex we are thinking about
1534 :param local_vertex: the vertex relating to the local site.
1535 :param graph: the intersite graph
1536 :param detect_failed: whether to detect failed links
1537 :return: True if some bridgeheads were not found
1539 # The docs ([MS-ADTS] 6.2.2.3.4.3) say to use local_vertex
1540 # here, but using vertex seems to make more sense. That is,
1541 # the docs want this:
1543 #bh = self.get_bridgehead(vertex.site, vertex.part, transport,
1544 # local_vertex.is_black(), detect_failed)
1546 # TODO WHY?????
1548 vertex.accept_red_red = []
1549 vertex.accept_black = []
1550 found_failed = False
1551 for t_guid, transport in self.transport_table.items():
1552 if transport.name != 'IP':
1553 #XXX well this is cheating a bit
1554 logger.warning("WARNING: we are ignoring a transport named %r"
1555 % transport.name)
1556 continue
1558 if vertex not in graph.connected_vertices:
1559 continue
1561 partial_replica_okay = vertex.is_black()
1562 bh = self.get_bridgehead(vertex.site, vertex.part, transport,
1563 partial_replica_okay, detect_failed)
1564 if bh is None:
1565 if vertex.site.is_rodc_site():
1566 vertex.accept_red_red.append(t_guid)
1567 else:
1568 found_failed = True
1569 continue
1571 vertex.accept_red_red.append(t_guid)
1572 vertex.accept_black.append(t_guid)
1574 # Add additional transport to allow another run of Dijkstra
1575 vertex.accept_red_red.append("EDGE_TYPE_ALL")
1576 vertex.accept_black.append("EDGE_TYPE_ALL")
1578 return found_failed
1580 def create_connections(self, graph, part, detect_failed):
1581 """Create intersite NTDSConnections as needed by a partition
1583 Construct an NC replica graph for the NC identified by
1584 the given crossRef, then create any additional nTDSConnection
1585 objects required.
1587 :param graph: site graph.
1588 :param part: crossRef object for NC.
1589 :param detect_failed: True to detect failed DCs and route
1590 replication traffic around them, False to assume no DC
1591 has failed.
1593 Modifies self.kept_connections by adding any connections
1594 deemed to be "in use".
1596 :return: (all_connected, found_failed_dc)
1597 (all_connected) True if the resulting NC replica graph
1598 connects all sites that need to be connected.
1599 (found_failed_dc) True if one or more failed DCs were
1600 detected.
1602 all_connected = True
1603 found_failed = False
1605 DEBUG_FN("create_connections(): enter\n"
1606 "\tpartdn=%s\n\tdetect_failed=%s" %
1607 (part.nc_dnstr, detect_failed))
1609 # XXX - This is a highly abbreviated function from the MS-TECH
1610 # ref. It creates connections between bridgeheads to all
1611 # sites that have appropriate replicas. Thus we are not
1612 # creating a minimum cost spanning tree but instead
1613 # producing a fully connected tree. This should produce
1614 # a full (albeit not optimal cost) replication topology.
1616 my_vertex = Vertex(self.my_site, part)
1617 my_vertex.color_vertex()
1619 for v in graph.vertices:
1620 v.color_vertex()
1621 if self.add_transports(v, my_vertex, graph, False):
1622 found_failed = True
1624 # No NC replicas for this NC in the site of the local DC,
1625 # so no nTDSConnection objects need be created
1626 if my_vertex.is_white():
1627 return all_connected, found_failed
1629 edge_list, n_components = get_spanning_tree_edges(graph,
1630 self.my_site,
1631 label=part.partstr)
1633 DEBUG_FN("%s Number of components: %d" %
1634 (part.nc_dnstr, n_components))
1635 if n_components > 1:
1636 all_connected = False
1638 # LET partialReplicaOkay be TRUE if and only if
1639 # localSiteVertex.Color = COLOR.BLACK
1640 partial_ok = my_vertex.is_black()
1642 # Utilize the IP transport only for now
1643 transport = self.ip_transport
1645 DEBUG("edge_list %s" % edge_list)
1646 for e in edge_list:
1647 # XXX more accurate comparison?
1648 if e.directed and e.vertices[0].site is self.my_site:
1649 continue
1651 if e.vertices[0].site is self.my_site:
1652 rsite = e.vertices[1].site
1653 else:
1654 rsite = e.vertices[0].site
1656 # We don't make connections to our own site as that
1657 # is intrasite topology generator's job
1658 if rsite is self.my_site:
1659 DEBUG("rsite is my_site")
1660 continue
1662 # Determine bridgehead server in remote site
1663 rbh = self.get_bridgehead(rsite, part, transport,
1664 partial_ok, detect_failed)
1665 if rbh is None:
1666 continue
1668 # RODC acts as an BH for itself
1669 # IF AmIRODC() then
1670 # LET lbh be the nTDSDSA object of the local DC
1671 # ELSE
1672 # LET lbh be the result of GetBridgeheadDC(localSiteVertex.ID,
1673 # cr, t, partialReplicaOkay, detectFailedDCs)
1674 if self.my_dsa.is_ro():
1675 lsite = self.my_site
1676 lbh = self.my_dsa
1677 else:
1678 lsite = self.my_site
1679 lbh = self.get_bridgehead(lsite, part, transport,
1680 partial_ok, detect_failed)
1681 # TODO
1682 if lbh is None:
1683 debug.DEBUG_RED("DISASTER! lbh is None")
1684 return False, True
1686 debug.DEBUG_CYAN("SITES")
1687 print >> sys.stderr, lsite, rsite
1688 debug.DEBUG_BLUE("vertices")
1689 print >> sys.stderr, e.vertices
1690 debug.DEBUG_BLUE("bridgeheads")
1691 print >> sys.stderr, lbh, rbh
1692 debug.DEBUG_BLUE("-" * 70)
1694 sitelink = e.site_link
1695 if sitelink is None:
1696 link_opt = 0x0
1697 link_sched = None
1698 else:
1699 link_opt = sitelink.options
1700 link_sched = sitelink.schedule
1702 self.create_connection(part, rbh, rsite, transport,
1703 lbh, lsite, link_opt, link_sched,
1704 partial_ok, detect_failed)
1706 return all_connected, found_failed
1708 def create_intersite_connections(self):
1709 """Create NTDSConnections as necessary for all partitions.
1711 Computes an NC replica graph for each NC replica that "should be
1712 present" on the local DC or "is present" on any DC in the same site
1713 as the local DC. For each edge directed to an NC replica on such a
1714 DC from an NC replica on a DC in another site, the KCC creates an
1715 nTDSConnection object to imply that edge if one does not already
1716 exist.
1718 Modifies self.kept_connections - A set of nTDSConnection
1719 objects for edges that are directed
1720 to the local DC's site in one or more NC replica graphs.
1722 :return: True if spanning trees were created for all NC replica
1723 graphs, otherwise False.
1725 all_connected = True
1726 self.kept_connections = set()
1728 # LET crossRefList be the set containing each object o of class
1729 # crossRef such that o is a child of the CN=Partitions child of the
1730 # config NC
1732 # FOR each crossRef object cr in crossRefList
1733 # IF cr!enabled has a value and is false, or if FLAG_CR_NTDS_NC
1734 # is clear in cr!systemFlags, skip cr.
1735 # LET g be the GRAPH return of SetupGraph()
1737 for part in self.part_table.values():
1739 if not part.is_enabled():
1740 continue
1742 if part.is_foreign():
1743 continue
1745 graph = self.setup_graph(part)
1747 # Create nTDSConnection objects, routing replication traffic
1748 # around "failed" DCs.
1749 found_failed = False
1751 connected, found_failed = self.create_connections(graph,
1752 part, True)
1754 DEBUG("with detect_failed: connected %s Found failed %s" %
1755 (connected, found_failed))
1756 if not connected:
1757 all_connected = False
1759 if found_failed:
1760 # One or more failed DCs preclude use of the ideal NC
1761 # replica graph. Add connections for the ideal graph.
1762 self.create_connections(graph, part, False)
1764 return all_connected
1766 def intersite(self, ping):
1767 """Generate the inter-site KCC replica graph and nTDSConnections
1769 As per MS-ADTS 6.2.2.3.
1771 If self.readonly is False, the connections are added to self.samdb.
1773 Produces self.kept_connections which is a set of NTDS
1774 Connections that should be kept during subsequent pruning
1775 process.
1777 After this has run, all sites should be connected in a minimum
1778 spanning tree.
1780 :param ping: An oracle function of remote site availability
1781 :return (True or False): (True) if the produced NC replica
1782 graph connects all sites that need to be connected
1785 # Retrieve my DSA
1786 mydsa = self.my_dsa
1787 mysite = self.my_site
1788 all_connected = True
1790 DEBUG_FN("intersite(): enter")
1792 # Determine who is the ISTG
1793 if self.readonly:
1794 mysite.select_istg(self.samdb, mydsa, ro=True)
1795 else:
1796 mysite.select_istg(self.samdb, mydsa, ro=False)
1798 # Test whether local site has topology disabled
1799 if mysite.is_intersite_topology_disabled():
1800 DEBUG_FN("intersite(): exit disabled all_connected=%d" %
1801 all_connected)
1802 return all_connected
1804 if not mydsa.is_istg():
1805 DEBUG_FN("intersite(): exit not istg all_connected=%d" %
1806 all_connected)
1807 return all_connected
1809 self.merge_failed_links(ping)
1811 # For each NC with an NC replica that "should be present" on the
1812 # local DC or "is present" on any DC in the same site as the
1813 # local DC, the KCC constructs a site graph--a precursor to an NC
1814 # replica graph. The site connectivity for a site graph is defined
1815 # by objects of class interSiteTransport, siteLink, and
1816 # siteLinkBridge in the config NC.
1818 all_connected = self.create_intersite_connections()
1820 DEBUG_FN("intersite(): exit all_connected=%d" % all_connected)
1821 return all_connected
1823 def update_rodc_connection(self):
1824 """Updates the RODC NTFRS connection object.
1826 If the local DSA is not an RODC, this does nothing.
1828 if not self.my_dsa.is_ro():
1829 return
1831 # Given an nTDSConnection object cn1, such that cn1.options contains
1832 # NTDSCONN_OPT_RODC_TOPOLOGY, and another nTDSConnection object cn2,
1833 # does not contain NTDSCONN_OPT_RODC_TOPOLOGY, modify cn1 to ensure
1834 # that the following is true:
1836 # cn1.fromServer = cn2.fromServer
1837 # cn1.schedule = cn2.schedule
1839 # If no such cn2 can be found, cn1 is not modified.
1840 # If no such cn1 can be found, nothing is modified by this task.
1842 all_connections = self.my_dsa.connect_table.values()
1843 ro_connections = [x for x in all_connections if x.is_rodc_topology()]
1844 rw_connections = [x for x in all_connections
1845 if x not in ro_connections]
1847 # XXX here we are dealing with multiple RODC_TOPO connections,
1848 # if they exist. It is not clear whether the spec means that
1849 # or if it ever arises.
1850 if rw_connections and ro_connections:
1851 for con in ro_connections:
1852 cn2 = rw_connections[0]
1853 con.from_dnstr = cn2.from_dnstr
1854 con.schedule = cn2.schedule
1855 con.to_be_modified = True
1857 self.my_dsa.commit_connections(self.samdb, ro=self.readonly)
1859 def intrasite_max_node_edges(self, node_count):
1860 """Find the maximum number of edges directed to an intrasite node
1862 The KCC does not create more than 50 edges directed to a
1863 single DC. To optimize replication, we compute that each node
1864 should have n+2 total edges directed to it such that (n) is
1865 the smallest non-negative integer satisfying
1866 (node_count <= 2*(n*n) + 6*n + 7)
1868 (If the number of edges is m (i.e. n + 2), that is the same as
1869 2 * m*m - 2 * m + 3). We think in terms of n because that is
1870 the number of extra connections over the double directed ring
1871 that exists by default.
1873 edges n nodecount
1874 2 0 7
1875 3 1 15
1876 4 2 27
1877 5 3 43
1879 50 48 4903
1881 :param node_count: total number of nodes in the replica graph
1883 The intention is that there should be no more than 3 hops
1884 between any two DSAs at a site. With up to 7 nodes the 2 edges
1885 of the ring are enough; any configuration of extra edges with
1886 8 nodes will be enough. It is less clear that the 3 hop
1887 guarantee holds at e.g. 15 nodes in degenerate cases, but
1888 those are quite unlikely given the extra edges are randomly
1889 arranged.
1891 :param node_count: the number of nodes in the site
1892 "return: The desired maximum number of connections
1894 n = 0
1895 while True:
1896 if node_count <= (2 * (n * n) + (6 * n) + 7):
1897 break
1898 n = n + 1
1899 n = n + 2
1900 if n < 50:
1901 return n
1902 return 50
1904 def construct_intrasite_graph(self, site_local, dc_local,
1905 nc_x, gc_only, detect_stale):
1906 """Create an intrasite graph using given parameters
1908 This might be called a number of times per site with different
1909 parameters.
1911 Based on [MS-ADTS] 6.2.2.2
1913 :param site_local: site for which we are working
1914 :param dc_local: local DC that potentially needs a replica
1915 :param nc_x: naming context (x) that we are testing if it
1916 "should be present" on the local DC
1917 :param gc_only: Boolean - only consider global catalog servers
1918 :param detect_stale: Boolean - check whether links seems down
1919 :return: None
1921 # We're using the MS notation names here to allow
1922 # correlation back to the published algorithm.
1924 # nc_x - naming context (x) that we are testing if it
1925 # "should be present" on the local DC
1926 # f_of_x - replica (f) found on a DC (s) for NC (x)
1927 # dc_s - DC where f_of_x replica was found
1928 # dc_local - local DC that potentially needs a replica
1929 # (f_of_x)
1930 # r_list - replica list R
1931 # p_of_x - replica (p) is partial and found on a DC (s)
1932 # for NC (x)
1933 # l_of_x - replica (l) is the local replica for NC (x)
1934 # that should appear on the local DC
1935 # r_len = is length of replica list |R|
1937 # If the DSA doesn't need a replica for this
1938 # partition (NC x) then continue
1939 needed, ro, partial = nc_x.should_be_present(dc_local)
1941 debug.DEBUG_YELLOW("construct_intrasite_graph(): enter" +
1942 "\n\tgc_only=%d" % gc_only +
1943 "\n\tdetect_stale=%d" % detect_stale +
1944 "\n\tneeded=%s" % needed +
1945 "\n\tro=%s" % ro +
1946 "\n\tpartial=%s" % partial +
1947 "\n%s" % nc_x)
1949 if not needed:
1950 debug.DEBUG_RED("%s lacks 'should be present' status, "
1951 "aborting construct_intersite_graph!" %
1952 nc_x.nc_dnstr)
1953 return
1955 # Create a NCReplica that matches what the local replica
1956 # should say. We'll use this below in our r_list
1957 l_of_x = NCReplica(dc_local.dsa_dnstr, dc_local.dsa_guid,
1958 nc_x.nc_dnstr)
1960 l_of_x.identify_by_basedn(self.samdb)
1962 l_of_x.rep_partial = partial
1963 l_of_x.rep_ro = ro
1965 # Add this replica that "should be present" to the
1966 # needed replica table for this DSA
1967 dc_local.add_needed_replica(l_of_x)
1969 # Replica list
1971 # Let R be a sequence containing each writable replica f of x
1972 # such that f "is present" on a DC s satisfying the following
1973 # criteria:
1975 # * s is a writable DC other than the local DC.
1977 # * s is in the same site as the local DC.
1979 # * If x is a read-only full replica and x is a domain NC,
1980 # then the DC's functional level is at least
1981 # DS_BEHAVIOR_WIN2008.
1983 # * Bit NTDSSETTINGS_OPT_IS_TOPL_DETECT_STALE_DISABLED is set
1984 # in the options attribute of the site settings object for
1985 # the local DC's site, or no tuple z exists in the
1986 # kCCFailedLinks or kCCFailedConnections variables such
1987 # that z.UUIDDsa is the objectGUID of the nTDSDSA object
1988 # for s, z.FailureCount > 0, and the current time -
1989 # z.TimeFirstFailure > 2 hours.
1991 r_list = []
1993 # We'll loop thru all the DSAs looking for
1994 # writeable NC replicas that match the naming
1995 # context dn for (nc_x)
1997 for dc_s in self.my_site.dsa_table.values():
1998 # If this partition (nc_x) doesn't appear as a
1999 # replica (f_of_x) on (dc_s) then continue
2000 if not nc_x.nc_dnstr in dc_s.current_rep_table:
2001 continue
2003 # Pull out the NCReplica (f) of (x) with the dn
2004 # that matches NC (x) we are examining.
2005 f_of_x = dc_s.current_rep_table[nc_x.nc_dnstr]
2007 # Replica (f) of NC (x) must be writable
2008 if f_of_x.is_ro():
2009 continue
2011 # Replica (f) of NC (x) must satisfy the
2012 # "is present" criteria for DC (s) that
2013 # it was found on
2014 if not f_of_x.is_present():
2015 continue
2017 # DC (s) must be a writable DSA other than
2018 # my local DC. In other words we'd only replicate
2019 # from other writable DC
2020 if dc_s.is_ro() or dc_s is dc_local:
2021 continue
2023 # Certain replica graphs are produced only
2024 # for global catalogs, so test against
2025 # method input parameter
2026 if gc_only and not dc_s.is_gc():
2027 continue
2029 # DC (s) must be in the same site as the local DC
2030 # as this is the intra-site algorithm. This is
2031 # handled by virtue of placing DSAs in per
2032 # site objects (see enclosing for() loop)
2034 # If NC (x) is intended to be read-only full replica
2035 # for a domain NC on the target DC then the source
2036 # DC should have functional level at minimum WIN2008
2038 # Effectively we're saying that in order to replicate
2039 # to a targeted RODC (which was introduced in Windows 2008)
2040 # then we have to replicate from a DC that is also minimally
2041 # at that level.
2043 # You can also see this requirement in the MS special
2044 # considerations for RODC which state that to deploy
2045 # an RODC, at least one writable domain controller in
2046 # the domain must be running Windows Server 2008
2047 if ro and not partial and nc_x.nc_type == NCType.domain:
2048 if not dc_s.is_minimum_behavior(dsdb.DS_DOMAIN_FUNCTION_2008):
2049 continue
2051 # If we haven't been told to turn off stale connection
2052 # detection and this dsa has a stale connection then
2053 # continue
2054 if detect_stale and self.is_stale_link_connection(dc_s):
2055 continue
2057 # Replica meets criteria. Add it to table indexed
2058 # by the GUID of the DC that it appears on
2059 r_list.append(f_of_x)
2061 # If a partial (not full) replica of NC (x) "should be present"
2062 # on the local DC, append to R each partial replica (p of x)
2063 # such that p "is present" on a DC satisfying the same
2064 # criteria defined above for full replica DCs.
2066 # XXX This loop and the previous one differ only in whether
2067 # the replica is partial or not. here we only accept partial
2068 # (because we're partial); before we only accepted full. Order
2069 # doen't matter (the list is sorted a few lines down) so these
2070 # loops could easily be merged. Or this could be a helper
2071 # function.
2073 if partial:
2074 # Now we loop thru all the DSAs looking for
2075 # partial NC replicas that match the naming
2076 # context dn for (NC x)
2077 for dc_s in self.my_site.dsa_table.values():
2079 # If this partition NC (x) doesn't appear as a
2080 # replica (p) of NC (x) on the dsa DC (s) then
2081 # continue
2082 if not nc_x.nc_dnstr in dc_s.current_rep_table:
2083 continue
2085 # Pull out the NCReplica with the dn that
2086 # matches NC (x) we are examining.
2087 p_of_x = dc_s.current_rep_table[nc_x.nc_dnstr]
2089 # Replica (p) of NC (x) must be partial
2090 if not p_of_x.is_partial():
2091 continue
2093 # Replica (p) of NC (x) must satisfy the
2094 # "is present" criteria for DC (s) that
2095 # it was found on
2096 if not p_of_x.is_present():
2097 continue
2099 # DC (s) must be a writable DSA other than
2100 # my DSA. In other words we'd only replicate
2101 # from other writable DSA
2102 if dc_s.is_ro() or dc_s is dc_local:
2103 continue
2105 # Certain replica graphs are produced only
2106 # for global catalogs, so test against
2107 # method input parameter
2108 if gc_only and not dc_s.is_gc():
2109 continue
2111 # If we haven't been told to turn off stale connection
2112 # detection and this dsa has a stale connection then
2113 # continue
2114 if detect_stale and self.is_stale_link_connection(dc_s):
2115 continue
2117 # Replica meets criteria. Add it to table indexed
2118 # by the GUID of the DSA that it appears on
2119 r_list.append(p_of_x)
2121 # Append to R the NC replica that "should be present"
2122 # on the local DC
2123 r_list.append(l_of_x)
2125 r_list.sort(sort_replica_by_dsa_guid)
2126 r_len = len(r_list)
2128 max_node_edges = self.intrasite_max_node_edges(r_len)
2130 # Add a node for each r_list element to the replica graph
2131 graph_list = []
2132 for rep in r_list:
2133 node = GraphNode(rep.rep_dsa_dnstr, max_node_edges)
2134 graph_list.append(node)
2136 # For each r(i) from (0 <= i < |R|-1)
2137 i = 0
2138 while i < (r_len-1):
2139 # Add an edge from r(i) to r(i+1) if r(i) is a full
2140 # replica or r(i+1) is a partial replica
2141 if not r_list[i].is_partial() or r_list[i+1].is_partial():
2142 graph_list[i+1].add_edge_from(r_list[i].rep_dsa_dnstr)
2144 # Add an edge from r(i+1) to r(i) if r(i+1) is a full
2145 # replica or ri is a partial replica.
2146 if not r_list[i+1].is_partial() or r_list[i].is_partial():
2147 graph_list[i].add_edge_from(r_list[i+1].rep_dsa_dnstr)
2148 i = i + 1
2150 # Add an edge from r|R|-1 to r0 if r|R|-1 is a full replica
2151 # or r0 is a partial replica.
2152 if not r_list[r_len-1].is_partial() or r_list[0].is_partial():
2153 graph_list[0].add_edge_from(r_list[r_len-1].rep_dsa_dnstr)
2155 # Add an edge from r0 to r|R|-1 if r0 is a full replica or
2156 # r|R|-1 is a partial replica.
2157 if not r_list[0].is_partial() or r_list[r_len-1].is_partial():
2158 graph_list[r_len-1].add_edge_from(r_list[0].rep_dsa_dnstr)
2160 DEBUG("r_list is length %s" % len(r_list))
2161 DEBUG('\n'.join(str((x.rep_dsa_guid, x.rep_dsa_dnstr))
2162 for x in r_list))
2164 do_dot_files = self.dot_file_dir is not None and self.debug
2165 if self.verify or do_dot_files:
2166 dot_edges = []
2167 dot_vertices = set()
2168 for v1 in graph_list:
2169 dot_vertices.add(v1.dsa_dnstr)
2170 for v2 in v1.edge_from:
2171 dot_edges.append((v2, v1.dsa_dnstr))
2172 dot_vertices.add(v2)
2174 verify_properties = ('connected', 'directed_double_ring_or_small')
2175 verify_and_dot('intrasite_pre_ntdscon', dot_edges, dot_vertices,
2176 label='%s__%s__%s' % (site_local.site_dnstr,
2177 nctype_lut[nc_x.nc_type],
2178 nc_x.nc_dnstr),
2179 properties=verify_properties, debug=DEBUG,
2180 verify=self.verify,
2181 dot_file_dir=self.dot_file_dir,
2182 directed=True)
2184 # For each existing nTDSConnection object implying an edge
2185 # from rj of R to ri such that j != i, an edge from rj to ri
2186 # is not already in the graph, and the total edges directed
2187 # to ri is less than n+2, the KCC adds that edge to the graph.
2188 for vertex in graph_list:
2189 dsa = self.my_site.dsa_table[vertex.dsa_dnstr]
2190 for connect in dsa.connect_table.values():
2191 remote = connect.from_dnstr
2192 if remote in self.my_site.dsa_table:
2193 vertex.add_edge_from(remote)
2195 DEBUG('reps are: %s' % ' '.join(x.rep_dsa_dnstr for x in r_list))
2196 DEBUG('dsas are: %s' % ' '.join(x.dsa_dnstr for x in graph_list))
2198 for tnode in graph_list:
2199 # To optimize replication latency in sites with many NC
2200 # replicas, the KCC adds new edges directed to ri to bring
2201 # the total edges to n+2, where the NC replica rk of R
2202 # from which the edge is directed is chosen at random such
2203 # that k != i and an edge from rk to ri is not already in
2204 # the graph.
2206 # Note that the KCC tech ref does not give a number for
2207 # the definition of "sites with many NC replicas". At a
2208 # bare minimum to satisfy n+2 edges directed at a node we
2209 # have to have at least three replicas in |R| (i.e. if n
2210 # is zero then at least replicas from two other graph
2211 # nodes may direct edges to us).
2212 if r_len >= 3 and not tnode.has_sufficient_edges():
2213 candidates = [x for x in graph_list if
2214 (x is not tnode and
2215 x.dsa_dnstr not in tnode.edge_from)]
2217 debug.DEBUG_BLUE("looking for random link for %s. r_len %d, "
2218 "graph len %d candidates %d"
2219 % (tnode.dsa_dnstr, r_len, len(graph_list),
2220 len(candidates)))
2222 DEBUG("candidates %s" % [x.dsa_dnstr for x in candidates])
2224 while candidates and not tnode.has_sufficient_edges():
2225 other = random.choice(candidates)
2226 DEBUG("trying to add candidate %s" % other.dsa_dstr)
2227 if not tnode.add_edge_from(other):
2228 debug.DEBUG_RED("could not add %s" % other.dsa_dstr)
2229 candidates.remove(other)
2230 else:
2231 DEBUG_FN("not adding links to %s: nodes %s, links is %s/%s" %
2232 (tnode.dsa_dnstr, r_len, len(tnode.edge_from),
2233 tnode.max_edges))
2235 # Print the graph node in debug mode
2236 DEBUG_FN("%s" % tnode)
2238 # For each edge directed to the local DC, ensure a nTDSConnection
2239 # points to us that satisfies the KCC criteria
2241 if tnode.dsa_dnstr == dc_local.dsa_dnstr:
2242 tnode.add_connections_from_edges(dc_local)
2244 if self.verify or do_dot_files:
2245 dot_edges = []
2246 dot_vertices = set()
2247 for v1 in graph_list:
2248 dot_vertices.add(v1.dsa_dnstr)
2249 for v2 in v1.edge_from:
2250 dot_edges.append((v2, v1.dsa_dnstr))
2251 dot_vertices.add(v2)
2253 verify_properties = ('connected', 'directed_double_ring_or_small')
2254 verify_and_dot('intrasite_post_ntdscon', dot_edges, dot_vertices,
2255 label='%s__%s__%s' % (site_local.site_dnstr,
2256 nctype_lut[nc_x.nc_type],
2257 nc_x.nc_dnstr),
2258 properties=verify_properties, debug=DEBUG,
2259 verify=self.verify,
2260 dot_file_dir=self.dot_file_dir,
2261 directed=True)
2263 def intrasite(self):
2264 """Generate the intrasite KCC connections
2266 As per MS-ADTS 6.2.2.2.
2268 If self.readonly is False, the connections are added to self.samdb.
2270 After this call, all DCs in each site with more than 3 DCs
2271 should be connected in a bidirectional ring. If a site has 2
2272 DCs, they will bidirectionally connected. Sites with many DCs
2273 may have arbitrary extra connections.
2275 :return: None
2277 mydsa = self.my_dsa
2279 DEBUG_FN("intrasite(): enter")
2281 # Test whether local site has topology disabled
2282 mysite = self.my_site
2283 if mysite.is_intrasite_topology_disabled():
2284 return
2286 detect_stale = (not mysite.is_detect_stale_disabled())
2287 for connect in mydsa.connect_table.values():
2288 if connect.to_be_added:
2289 debug.DEBUG_CYAN("TO BE ADDED:\n%s" % connect)
2291 # Loop thru all the partitions, with gc_only False
2292 for partdn, part in self.part_table.items():
2293 self.construct_intrasite_graph(mysite, mydsa, part, False,
2294 detect_stale)
2295 for connect in mydsa.connect_table.values():
2296 if connect.to_be_added:
2297 debug.DEBUG_BLUE("TO BE ADDED:\n%s" % connect)
2299 # If the DC is a GC server, the KCC constructs an additional NC
2300 # replica graph (and creates nTDSConnection objects) for the
2301 # config NC as above, except that only NC replicas that "are present"
2302 # on GC servers are added to R.
2303 for connect in mydsa.connect_table.values():
2304 if connect.to_be_added:
2305 debug.DEBUG_YELLOW("TO BE ADDED:\n%s" % connect)
2307 # Do it again, with gc_only True
2308 for partdn, part in self.part_table.items():
2309 if part.is_config():
2310 self.construct_intrasite_graph(mysite, mydsa, part, True,
2311 detect_stale)
2313 # The DC repeats the NC replica graph computation and nTDSConnection
2314 # creation for each of the NC replica graphs, this time assuming
2315 # that no DC has failed. It does so by re-executing the steps as
2316 # if the bit NTDSSETTINGS_OPT_IS_TOPL_DETECT_STALE_DISABLED were
2317 # set in the options attribute of the site settings object for
2318 # the local DC's site. (ie. we set "detec_stale" flag to False)
2319 for connect in mydsa.connect_table.values():
2320 if connect.to_be_added:
2321 debug.DEBUG_BLUE("TO BE ADDED:\n%s" % connect)
2323 # Loop thru all the partitions.
2324 for partdn, part in self.part_table.items():
2325 self.construct_intrasite_graph(mysite, mydsa, part, False,
2326 False) # don't detect stale
2328 # If the DC is a GC server, the KCC constructs an additional NC
2329 # replica graph (and creates nTDSConnection objects) for the
2330 # config NC as above, except that only NC replicas that "are present"
2331 # on GC servers are added to R.
2332 for connect in mydsa.connect_table.values():
2333 if connect.to_be_added:
2334 debug.DEBUG_RED("TO BE ADDED:\n%s" % connect)
2336 for partdn, part in self.part_table.items():
2337 if part.is_config():
2338 self.construct_intrasite_graph(mysite, mydsa, part, True,
2339 False) # don't detect stale
2341 if self.readonly:
2342 # Display any to be added or modified repsFrom
2343 for connect in mydsa.connect_table.values():
2344 if connect.to_be_deleted:
2345 logger.info("TO BE DELETED:\n%s" % connect)
2346 if connect.to_be_modified:
2347 logger.info("TO BE MODIFIED:\n%s" % connect)
2348 if connect.to_be_added:
2349 debug.DEBUG_GREEN("TO BE ADDED:\n%s" % connect)
2351 mydsa.commit_connections(self.samdb, ro=True)
2352 else:
2353 # Commit any newly created connections to the samdb
2354 mydsa.commit_connections(self.samdb)
2356 def list_dsas(self):
2357 """Compile a comprehensive list of DSA DNs
2359 These are all the DSAs on all the sites that KCC would be
2360 dealing with.
2362 This method is not idempotent and may not work correctly in
2363 sequence with KCC.run().
2365 :return: a list of DSA DN strings.
2367 self.load_my_site()
2368 self.load_my_dsa()
2370 self.load_all_sites()
2371 self.load_all_partitions()
2372 self.load_all_transports()
2373 self.load_all_sitelinks()
2374 dsas = []
2375 for site in self.site_table.values():
2376 dsas.extend([dsa.dsa_dnstr.replace('CN=NTDS Settings,', '', 1)
2377 for dsa in site.dsa_table.values()])
2378 return dsas
2380 def load_samdb(self, dburl, lp, creds):
2381 """Load the database using an url, loadparm, and credentials
2383 :param dburl: a database url.
2384 :param lp: a loadparm object.
2385 :param creds: a Credentials object.
2387 self.samdb = SamDB(url=dburl,
2388 session_info=system_session(),
2389 credentials=creds, lp=lp)
2391 def plot_all_connections(self, basename, verify_properties=()):
2392 """Helper function to plot and verify NTDSConnections
2394 :param basename: an identifying string to use in filenames and logs.
2395 :param verify_properties: properties to verify (default empty)
2397 verify = verify_properties and self.verify
2398 if not verify and self.dot_file_dir is None:
2399 return
2401 dot_edges = []
2402 dot_vertices = []
2403 edge_colours = []
2404 vertex_colours = []
2406 for dsa in self.dsa_by_dnstr.values():
2407 dot_vertices.append(dsa.dsa_dnstr)
2408 if dsa.is_ro():
2409 vertex_colours.append('#cc0000')
2410 else:
2411 vertex_colours.append('#0000cc')
2412 for con in dsa.connect_table.values():
2413 if con.is_rodc_topology():
2414 edge_colours.append('red')
2415 else:
2416 edge_colours.append('blue')
2417 dot_edges.append((con.from_dnstr, dsa.dsa_dnstr))
2419 verify_and_dot(basename, dot_edges, vertices=dot_vertices,
2420 label=self.my_dsa_dnstr, properties=verify_properties,
2421 debug=DEBUG, verify=verify, dot_file_dir=self.dot_file_dir,
2422 directed=True, edge_colors=edge_colours,
2423 vertex_colors=vertex_colours)
2425 def run(self, dburl, lp, creds, forced_local_dsa=None,
2426 forget_local_links=False, forget_intersite_links=False,
2427 attempt_live_connections=False):
2428 """Perform a KCC run, possibly updating repsFrom topology
2430 :param dburl: url of the database to work with.
2431 :param lp: a loadparm object.
2432 :param creds: a Credentials object.
2433 :param forced_local_dsa: pretend to be on the DSA with this dn_str
2434 :param forget_local_links: calculate as if no connections existed
2435 (boolean, default False)
2436 :param forget_intersite_links: calculate with only intrasite connection
2437 (boolean, default False)
2438 :param attempt_live_connections: attempt to connect to remote DSAs to
2439 determine link availability (boolean, default False)
2440 :return: 1 on error, 0 otherwise
2442 # We may already have a samdb setup if we are
2443 # currently importing an ldif for a test run
2444 if self.samdb is None:
2445 try:
2446 self.load_samdb(dburl, lp, creds)
2447 except ldb.LdbError, (num, msg):
2448 logger.error("Unable to open sam database %s : %s" %
2449 (dburl, msg))
2450 return 1
2452 if forced_local_dsa:
2453 self.samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" %
2454 forced_local_dsa)
2456 try:
2457 # Setup
2458 self.load_my_site()
2459 self.load_my_dsa()
2461 self.load_all_sites()
2462 self.load_all_partitions()
2463 self.load_all_transports()
2464 self.load_all_sitelinks()
2466 if self.verify or self.dot_file_dir is not None:
2467 guid_to_dnstr = {}
2468 for site in self.site_table.values():
2469 guid_to_dnstr.update((str(dsa.dsa_guid), dnstr)
2470 for dnstr, dsa
2471 in site.dsa_table.items())
2473 self.plot_all_connections('dsa_initial')
2475 dot_edges = []
2476 current_reps, needed_reps = self.my_dsa.get_rep_tables()
2477 for dnstr, c_rep in current_reps.items():
2478 DEBUG("c_rep %s" % c_rep)
2479 dot_edges.append((self.my_dsa.dsa_dnstr, dnstr))
2481 verify_and_dot('dsa_repsFrom_initial', dot_edges,
2482 directed=True, label=self.my_dsa_dnstr,
2483 properties=(), debug=DEBUG, verify=self.verify,
2484 dot_file_dir=self.dot_file_dir)
2486 dot_edges = []
2487 for site in self.site_table.values():
2488 for dsa in site.dsa_table.values():
2489 current_reps, needed_reps = dsa.get_rep_tables()
2490 for dn_str, rep in current_reps.items():
2491 for reps_from in rep.rep_repsFrom:
2492 DEBUG("rep %s" % rep)
2493 dsa_guid = str(reps_from.source_dsa_obj_guid)
2494 dsa_dn = guid_to_dnstr[dsa_guid]
2495 dot_edges.append((dsa.dsa_dnstr, dsa_dn))
2497 verify_and_dot('dsa_repsFrom_initial_all', dot_edges,
2498 directed=True, label=self.my_dsa_dnstr,
2499 properties=(), debug=DEBUG, verify=self.verify,
2500 dot_file_dir=self.dot_file_dir)
2502 dot_edges = []
2503 for link in self.sitelink_table.values():
2504 for a, b in itertools.combinations(link.site_list, 2):
2505 dot_edges.append((str(a), str(b)))
2506 properties = ('connected',)
2507 verify_and_dot('dsa_sitelink_initial', dot_edges,
2508 directed=False,
2509 label=self.my_dsa_dnstr, properties=properties,
2510 debug=DEBUG, verify=self.verify,
2511 dot_file_dir=self.dot_file_dir)
2513 if forget_local_links:
2514 for dsa in self.my_site.dsa_table.values():
2515 dsa.connect_table = {k: v for k, v in
2516 dsa.connect_table.items()
2517 if v.is_rodc_topology()}
2518 self.plot_all_connections('dsa_forgotten_local')
2520 if forget_intersite_links:
2521 for site in self.site_table.values():
2522 for dsa in site.dsa_table.values():
2523 dsa.connect_table = {k: v for k, v in
2524 dsa.connect_table.items()
2525 if site is self.my_site and
2526 v.is_rodc_topology()}
2528 self.plot_all_connections('dsa_forgotten_all')
2530 if attempt_live_connections:
2531 # Encapsulates lp and creds in a function that
2532 # attempts connections to remote DSAs.
2533 def ping(self, dnsname):
2534 try:
2535 drs_utils.drsuapi_connect(dnsname, self.lp, self.creds)
2536 except drs_utils.drsException:
2537 return False
2538 return True
2539 else:
2540 ping = None
2541 # These are the published steps (in order) for the
2542 # MS-TECH description of the KCC algorithm ([MS-ADTS] 6.2.2)
2544 # Step 1
2545 self.refresh_failed_links_connections(ping)
2547 # Step 2
2548 self.intrasite()
2550 # Step 3
2551 all_connected = self.intersite(ping)
2553 # Step 4
2554 self.remove_unneeded_ntdsconn(all_connected)
2556 # Step 5
2557 self.translate_ntdsconn()
2559 # Step 6
2560 self.remove_unneeded_failed_links_connections()
2562 # Step 7
2563 self.update_rodc_connection()
2565 if self.verify or self.dot_file_dir is not None:
2566 self.plot_all_connections('dsa_final',
2567 ('connected', 'forest_of_rings'))
2569 debug.DEBUG_MAGENTA("there are %d dsa guids" %
2570 len(guid_to_dnstr))
2572 dot_edges = []
2573 edge_colors = []
2574 my_dnstr = self.my_dsa.dsa_dnstr
2575 current_reps, needed_reps = self.my_dsa.get_rep_tables()
2576 for dnstr, n_rep in needed_reps.items():
2577 for reps_from in n_rep.rep_repsFrom:
2578 guid_str = str(reps_from.source_dsa_obj_guid)
2579 dot_edges.append((my_dnstr, guid_to_dnstr[guid_str]))
2580 edge_colors.append('#' + str(n_rep.nc_guid)[:6])
2582 verify_and_dot('dsa_repsFrom_final', dot_edges, directed=True,
2583 label=self.my_dsa_dnstr,
2584 properties=(), debug=DEBUG, verify=self.verify,
2585 dot_file_dir=self.dot_file_dir,
2586 edge_colors=edge_colors)
2588 dot_edges = []
2590 for site in self.site_table.values():
2591 for dsa in site.dsa_table.values():
2592 current_reps, needed_reps = dsa.get_rep_tables()
2593 for n_rep in needed_reps.values():
2594 for reps_from in n_rep.rep_repsFrom:
2595 dsa_guid = str(reps_from.source_dsa_obj_guid)
2596 dsa_dn = guid_to_dnstr[dsa_guid]
2597 dot_edges.append((dsa.dsa_dnstr, dsa_dn))
2599 verify_and_dot('dsa_repsFrom_final_all', dot_edges,
2600 directed=True, label=self.my_dsa_dnstr,
2601 properties=(), debug=DEBUG, verify=self.verify,
2602 dot_file_dir=self.dot_file_dir)
2604 except:
2605 raise
2607 return 0
2609 def import_ldif(self, dburl, lp, creds, ldif_file, forced_local_dsa=None):
2610 """Import relevant objects and attributes from an LDIF file.
2612 The point of this function is to allow a programmer/debugger to
2613 import an LDIF file with non-security relevent information that
2614 was previously extracted from a DC database. The LDIF file is used
2615 to create a temporary abbreviated database. The KCC algorithm can
2616 then run against this abbreviated database for debug or test
2617 verification that the topology generated is computationally the
2618 same between different OSes and algorithms.
2620 :param dburl: path to the temporary abbreviated db to create
2621 :param lp: a loadparm object.
2622 :param cred: a Credentials object.
2623 :param ldif_file: path to the ldif file to import
2624 :param forced_local_dsa: perform KCC from this DSA's point of view
2625 :return: zero on success, 1 on error
2627 try:
2628 self.samdb = ldif_import_export.ldif_to_samdb(dburl, lp, ldif_file,
2629 forced_local_dsa)
2630 except ldif_import_export.LdifError, e:
2631 print >> sys.stderr, e
2632 return 1
2633 return 0
2635 def export_ldif(self, dburl, lp, creds, ldif_file):
2636 """Routine to extract all objects and attributes that are relevent
2637 to the KCC algorithms from a DC database.
2639 The point of this function is to allow a programmer/debugger to
2640 extract an LDIF file with non-security relevent information from
2641 a DC database. The LDIF file can then be used to "import" via
2642 the import_ldif() function this file into a temporary abbreviated
2643 database. The KCC algorithm can then run against this abbreviated
2644 database for debug or test verification that the topology generated
2645 is computationally the same between different OSes and algorithms.
2647 :param dburl: LDAP database URL to extract info from
2648 :param ldif_file: output LDIF file name to create
2650 try:
2651 ldif_import_export.samdb_to_ldif_file(self.samdb, dburl, lp, creds,
2652 ldif_file)
2653 except ldif_import_export.LdifError, e:
2654 print >> sys.stderr, e
2655 return 1
2656 return 0