KCC: more debug info when --import-ldif goes badly
[Samba.git] / python / samba / kcc / __init__.py
blob6f97e67dd30d9015b3bf6544245503d1a5bc4a8f
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_file_dir: write diagnostic Graphviz files in this directory
102 def __init__(self, unix_now, readonly=False, verify=False, debug=False,
103 dot_file_dir=None):
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.ip_transport = None
111 self.sitelink_table = {}
112 self.dsa_by_dnstr = {}
113 self.dsa_by_guid = {}
115 self.get_dsa_by_guidstr = self.dsa_by_guid.get
116 self.get_dsa = self.dsa_by_dnstr.get
118 # TODO: These should be backed by a 'permanent' store so that when
119 # calling DRSGetReplInfo with DS_REPL_INFO_KCC_DSA_CONNECT_FAILURES,
120 # the failure information can be returned
121 self.kcc_failed_links = {}
122 self.kcc_failed_connections = set()
124 # Used in inter-site topology computation. A list
125 # of connections (by NTDSConnection object) that are
126 # to be kept when pruning un-needed NTDS Connections
127 self.kept_connections = set()
129 self.my_dsa_dnstr = None # My dsa DN
130 self.my_dsa = None # My dsa object
132 self.my_site_dnstr = None
133 self.my_site = None
135 self.samdb = None
137 self.unix_now = unix_now
138 self.nt_now = unix2nttime(unix_now)
139 self.readonly = readonly
140 self.verify = verify
141 self.debug = debug
142 self.dot_file_dir = dot_file_dir
144 def load_ip_transport(self):
145 """Loads the inter-site transport objects for Sites
147 :return: None
148 :raise KCCError: if no IP transport is found
150 try:
151 res = self.samdb.search("CN=Inter-Site Transports,CN=Sites,%s" %
152 self.samdb.get_config_basedn(),
153 scope=ldb.SCOPE_SUBTREE,
154 expression="(objectClass=interSiteTransport)")
155 except ldb.LdbError, (enum, estr):
156 raise KCCError("Unable to find inter-site transports - (%s)" %
157 estr)
159 for msg in res:
160 dnstr = str(msg.dn)
162 transport = Transport(dnstr)
164 transport.load_transport(self.samdb)
165 if transport.name == 'IP':
166 self.ip_transport = transport
167 elif transport.name == 'SMTP':
168 logger.info("Samba KCC is ignoring the obsolete SMTP transport.")
170 else:
171 logger.warning("Samba KCC does not support the transport called %r."
172 % (transport.name,))
174 if self.ip_transport is None:
175 raise KCCError("there doesn't seem to be an IP transport")
177 def load_all_sitelinks(self):
178 """Loads the inter-site siteLink objects
180 :return: None
181 :raise KCCError: if site-links aren't found
183 try:
184 res = self.samdb.search("CN=Inter-Site Transports,CN=Sites,%s" %
185 self.samdb.get_config_basedn(),
186 scope=ldb.SCOPE_SUBTREE,
187 expression="(objectClass=siteLink)")
188 except ldb.LdbError, (enum, estr):
189 raise KCCError("Unable to find inter-site siteLinks - (%s)" % estr)
191 for msg in res:
192 dnstr = str(msg.dn)
194 # already loaded
195 if dnstr in self.sitelink_table:
196 continue
198 sitelink = SiteLink(dnstr)
200 sitelink.load_sitelink(self.samdb)
202 # Assign this siteLink to table
203 # and index by dn
204 self.sitelink_table[dnstr] = sitelink
206 def load_site(self, dn_str):
207 """Helper for load_my_site and load_all_sites.
209 Put all the site's DSAs into the KCC indices.
211 :param dn_str: a site dn_str
212 :return: the Site object pertaining to the dn_str
214 site = Site(dn_str, self.unix_now)
215 site.load_site(self.samdb)
217 # We avoid replacing the site with an identical copy in case
218 # somewhere else has a reference to the old one, which would
219 # lead to all manner of confusion and chaos.
220 guid = str(site.site_guid)
221 if guid not in self.site_table:
222 self.site_table[guid] = site
223 self.dsa_by_dnstr.update(site.dsa_table)
224 self.dsa_by_guid.update((str(x.dsa_guid), x)
225 for x in site.dsa_table.values())
227 return self.site_table[guid]
229 def load_my_site(self):
230 """Load the Site object for the local DSA.
232 :return: None
234 self.my_site_dnstr = ("CN=%s,CN=Sites,%s" % (
235 self.samdb.server_site_name(),
236 self.samdb.get_config_basedn()))
238 self.my_site = self.load_site(self.my_site_dnstr)
240 def load_all_sites(self):
241 """Discover all sites and create Site objects.
243 :return: None
244 :raise: KCCError if sites can't be found
246 try:
247 res = self.samdb.search("CN=Sites,%s" %
248 self.samdb.get_config_basedn(),
249 scope=ldb.SCOPE_SUBTREE,
250 expression="(objectClass=site)")
251 except ldb.LdbError, (enum, estr):
252 raise KCCError("Unable to find sites - (%s)" % estr)
254 for msg in res:
255 sitestr = str(msg.dn)
256 self.load_site(sitestr)
258 def load_my_dsa(self):
259 """Discover my nTDSDSA dn thru the rootDSE entry
261 :return: None
262 :raise: KCCError if DSA can't be found
264 dn_query = "<GUID=%s>" % self.samdb.get_ntds_GUID()
265 dn = ldb.Dn(self.samdb, dn_query)
266 try:
267 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
268 attrs=["objectGUID"])
269 except ldb.LdbError, (enum, estr):
270 logger.warning("Search for dn '%s' [from %s] failed: %s. "
271 "This typically happens in --importldif mode due "
272 "to lack of module support.", dn, dn_query, estr)
273 try:
274 # We work around the failure above by looking at the
275 # dsServiceName that was put in the fake rootdse by
276 # the --exportldif, rather than the
277 # samdb.get_ntds_GUID(). The disadvantage is that this
278 # mode requires we modify the @ROOTDSE dnq to support
279 # --forced-local-dsa
280 service_name_res = self.samdb.search(base="",
281 scope=ldb.SCOPE_BASE,
282 attrs=["dsServiceName"])
283 dn = ldb.Dn(self.samdb,
284 service_name_res[0]["dsServiceName"][0])
286 res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
287 attrs=["objectGUID"])
288 except ldb.LdbError, (enum, estr):
289 raise KCCError("Unable to find my nTDSDSA - (%s)" % estr)
291 if len(res) != 1:
292 raise KCCError("Unable to find my nTDSDSA at %s" %
293 dn.extended_str())
295 ntds_guid = misc.GUID(self.samdb.get_ntds_GUID())
296 if misc.GUID(res[0]["objectGUID"][0]) != ntds_guid:
297 raise KCCError("Did not find the GUID we expected,"
298 " perhaps due to --importldif")
300 self.my_dsa_dnstr = str(res[0].dn)
302 self.my_dsa = self.my_site.get_dsa(self.my_dsa_dnstr)
304 if self.my_dsa_dnstr not in self.dsa_by_dnstr:
305 debug.DEBUG_DARK_YELLOW("my_dsa %s isn't in self.dsas_by_dnstr:"
306 " it must be RODC.\n"
307 "Let's add it, because my_dsa is special!"
308 "\n(likewise for self.dsa_by_guid)" %
309 self.my_dsa_dnstr)
311 self.dsa_by_dnstr[self.my_dsa_dnstr] = self.my_dsa
312 self.dsa_by_guid[str(self.my_dsa.dsa_guid)] = self.my_dsa
314 def load_all_partitions(self):
315 """Discover and load all partitions.
317 Each NC is inserted into the part_table by partition
318 dn string (not the nCName dn string)
320 :return: None
321 :raise: KCCError if partitions can't be found
323 try:
324 res = self.samdb.search("CN=Partitions,%s" %
325 self.samdb.get_config_basedn(),
326 scope=ldb.SCOPE_SUBTREE,
327 expression="(objectClass=crossRef)")
328 except ldb.LdbError, (enum, estr):
329 raise KCCError("Unable to find partitions - (%s)" % estr)
331 for msg in res:
332 partstr = str(msg.dn)
334 # already loaded
335 if partstr in self.part_table:
336 continue
338 part = Partition(partstr)
340 part.load_partition(self.samdb)
341 self.part_table[partstr] = part
343 def refresh_failed_links_connections(self, ping=None):
344 """Ensure the failed links list is up to date
346 Based on MS-ADTS 6.2.2.1
348 :param ping: An oracle function of remote site availability
349 :return: None
351 # LINKS: Refresh failed links
352 self.kcc_failed_links = {}
353 current, needed = self.my_dsa.get_rep_tables()
354 for replica in current.values():
355 # For every possible connection to replicate
356 for reps_from in replica.rep_repsFrom:
357 failure_count = reps_from.consecutive_sync_failures
358 if failure_count <= 0:
359 continue
361 dsa_guid = str(reps_from.source_dsa_obj_guid)
362 time_first_failure = reps_from.last_success
363 last_result = reps_from.last_attempt
364 dns_name = reps_from.dns_name1
366 f = self.kcc_failed_links.get(dsa_guid)
367 if f is None:
368 f = KCCFailedObject(dsa_guid, failure_count,
369 time_first_failure, last_result,
370 dns_name)
371 self.kcc_failed_links[dsa_guid] = f
372 else:
373 f.failure_count = max(f.failure_count, failure_count)
374 f.time_first_failure = min(f.time_first_failure,
375 time_first_failure)
376 f.last_result = last_result
378 # CONNECTIONS: Refresh failed connections
379 restore_connections = set()
380 if ping is not None:
381 DEBUG("refresh_failed_links: checking if links are still down")
382 for connection in self.kcc_failed_connections:
383 if ping(connection.dns_name):
384 # Failed connection is no longer failing
385 restore_connections.add(connection)
386 else:
387 connection.failure_count += 1
388 else:
389 DEBUG("refresh_failed_links: not checking live links because we\n"
390 "weren't asked to --attempt-live-connections")
392 # Remove the restored connections from the failed connections
393 self.kcc_failed_connections.difference_update(restore_connections)
395 def is_stale_link_connection(self, target_dsa):
396 """Check whether a link to a remote DSA is stale
398 Used in MS-ADTS 6.2.2.2 Intrasite Connection Creation
400 Returns True if the remote seems to have been down for at
401 least two hours, otherwise False.
403 :param target_dsa: the remote DSA object
404 :return: True if link is stale, otherwise False
406 failed_link = self.kcc_failed_links.get(str(target_dsa.dsa_guid))
407 if failed_link:
408 # failure_count should be > 0, but check anyways
409 if failed_link.failure_count > 0:
410 unix_first_failure = \
411 nttime2unix(failed_link.time_first_failure)
412 # TODO guard against future
413 if unix_first_failure > self.unix_now:
414 logger.error("The last success time attribute for \
415 repsFrom is in the future!")
417 # Perform calculation in seconds
418 if (self.unix_now - unix_first_failure) > 60 * 60 * 2:
419 return True
421 # TODO connections.
422 # We have checked failed *links*, but we also need to check
423 # *connections*
425 return False
427 # TODO: This should be backed by some form of local database
428 def remove_unneeded_failed_links_connections(self):
429 # Remove all tuples in kcc_failed_links where failure count = 0
430 # In this implementation, this should never happen.
432 # Remove all connections which were not used this run or connections
433 # that became active during this run.
434 pass
436 def _ensure_connections_are_loaded(self, connections):
437 """Load or fake-load NTDSConnections lacking GUIDs
439 New connections don't have GUIDs and created times which are
440 needed for sorting. If we're in read-only mode, we make fake
441 GUIDs, otherwise we ask SamDB to do it for us.
443 :param connections: an iterable of NTDSConnection objects.
444 :return: None
446 for cn_conn in connections:
447 if cn_conn.guid is None:
448 if self.readonly:
449 cn_conn.guid = misc.GUID(str(uuid.uuid4()))
450 cn_conn.whenCreated = self.nt_now
451 else:
452 cn_conn.load_connection(self.samdb)
454 def _mark_broken_ntdsconn(self):
455 """Find NTDS Connections that lack a remote
457 I'm not sure how they appear. Let's be rid of them by marking
458 them with the to_be_deleted attribute.
460 :return: None
462 for cn_conn in self.my_dsa.connect_table.values():
463 s_dnstr = cn_conn.get_from_dnstr()
464 if s_dnstr is None:
465 DEBUG_FN("%s has phantom connection %s" % (self.my_dsa,
466 cn_conn))
467 cn_conn.to_be_deleted = True
469 def _mark_unneeded_local_ntdsconn(self):
470 """Find unneeded intrasite NTDS Connections for removal
472 Based on MS-ADTS 6.2.2.4 Removing Unnecessary Connections.
473 Every DC removes its own unnecessary intrasite connections.
474 This function tags them with the to_be_deleted attribute.
476 :return: None
478 # XXX should an RODC be regarded as same site? It isn't part
479 # of the intrasite ring.
481 if self.my_site.is_cleanup_ntdsconn_disabled():
482 DEBUG_FN("not doing ntdsconn cleanup for site %s, "
483 "because it is disabled" % self.my_site)
484 return
486 mydsa = self.my_dsa
488 self._ensure_connections_are_loaded(mydsa.connect_table.values())
490 local_connections = []
492 for cn_conn in mydsa.connect_table.values():
493 s_dnstr = cn_conn.get_from_dnstr()
494 if s_dnstr in self.my_site.dsa_table:
495 removable = not (cn_conn.is_generated() or
496 cn_conn.is_rodc_topology())
497 packed_guid = ndr_pack(cn_conn.guid)
498 local_connections.append((cn_conn, s_dnstr,
499 packed_guid, removable))
501 for a, b in itertools.permutations(local_connections, 2):
502 cn_conn, s_dnstr, packed_guid, removable = a
503 cn_conn2, s_dnstr2, packed_guid2, removable2 = b
504 if (removable and
505 s_dnstr == s_dnstr2 and
506 cn_conn.whenCreated < cn_conn2.whenCreated or
507 (cn_conn.whenCreated == cn_conn2.whenCreated and
508 packed_guid < packed_guid2)):
509 cn_conn.to_be_deleted = True
511 def _mark_unneeded_intersite_ntdsconn(self):
512 """find unneeded intersite NTDS Connections for removal
514 Based on MS-ADTS 6.2.2.4 Removing Unnecessary Connections. The
515 intersite topology generator removes links for all DCs in its
516 site. Here we just tag them with the to_be_deleted attribute.
518 :return: None
520 # Find the intersite connections
521 local_dsas = self.my_site.dsa_table
522 connections_and_dsas = []
523 for dsa in local_dsas.values():
524 for cn in dsa.connect_table.values():
525 s_dnstr = cn.get_from_dnstr()
526 if s_dnstr not in local_dsas:
527 from_dsa = self.get_dsa(s_dnstr)
528 connections_and_dsas.append((cn, dsa, from_dsa))
530 self._ensure_connections_are_loaded(x[0] for x in connections_and_dsas)
531 for cn, to_dsa, from_dsa in connections_and_dsas:
532 if not cn.is_generated() or cn.is_rodc_topology():
533 continue
535 # If the connection is in the kept_connections list, we
536 # only remove it if an endpoint seems down.
537 if (cn in self.kept_connections and
538 not (self.is_bridgehead_failed(to_dsa, True) or
539 self.is_bridgehead_failed(from_dsa, True))):
540 continue
542 # this one is broken and might be superseded by another.
543 # But which other? Let's just say another link to the same
544 # site can supersede.
545 from_dnstr = from_dsa.dsa_dnstr
546 for site in self.site_table.values():
547 if from_dnstr in site.rw_dsa_table:
548 for cn2, to_dsa2, from_dsa2 in connections_and_dsas:
549 if (cn is not cn2 and
550 from_dsa2 in site.rw_dsa_table):
551 cn.to_be_deleted = True
553 def _commit_changes(self, dsa):
554 if dsa.is_ro() or self.readonly:
555 for connect in dsa.connect_table.values():
556 if connect.to_be_deleted:
557 logger.info("TO BE DELETED:\n%s" % connect)
558 if connect.to_be_added:
559 logger.info("TO BE ADDED:\n%s" % connect)
560 if connect.to_be_modified:
561 logger.info("TO BE MODIFIED:\n%s" % connect)
563 # Peform deletion from our tables but perform
564 # no database modification
565 dsa.commit_connections(self.samdb, ro=True)
566 else:
567 # Commit any modified connections
568 dsa.commit_connections(self.samdb)
570 def remove_unneeded_ntdsconn(self, all_connected):
571 """Remove unneeded NTDS Connections once topology is calculated
573 Based on MS-ADTS 6.2.2.4 Removing Unnecessary Connections
575 :param all_connected: indicates whether all sites are connected
576 :return: None
578 self._mark_broken_ntdsconn()
579 self._mark_unneeded_local_ntdsconn()
580 # if we are not the istg, we're done!
581 # if we are the istg, but all_connected is False, we also do nothing.
582 if self.my_dsa.is_istg() and all_connected:
583 self._mark_unneeded_intersite_ntdsconn()
585 for dsa in self.my_site.dsa_table.values():
586 self._commit_changes(dsa)
589 def modify_repsFrom(self, n_rep, t_repsFrom, s_rep, s_dsa, cn_conn):
590 """Update an repsFrom object if required.
592 Part of MS-ADTS 6.2.2.5.
594 Update t_repsFrom if necessary to satisfy requirements. Such
595 updates are typically required when the IDL_DRSGetNCChanges
596 server has moved from one site to another--for example, to
597 enable compression when the server is moved from the
598 client's site to another site.
600 The repsFrom.update_flags bit field may be modified
601 auto-magically if any changes are made here. See
602 kcc_utils.RepsFromTo for gory details.
605 :param n_rep: NC replica we need
606 :param t_repsFrom: repsFrom tuple to modify
607 :param s_rep: NC replica at source DSA
608 :param s_dsa: source DSA
609 :param cn_conn: Local DSA NTDSConnection child
611 :return: None
613 s_dnstr = s_dsa.dsa_dnstr
614 same_site = s_dnstr in self.my_site.dsa_table
616 # if schedule doesn't match then update and modify
617 times = convert_schedule_to_repltimes(cn_conn.schedule)
618 if times != t_repsFrom.schedule:
619 t_repsFrom.schedule = times
621 # Bit DRS_PER_SYNC is set in replicaFlags if and only
622 # if nTDSConnection schedule has a value v that specifies
623 # scheduled replication is to be performed at least once
624 # per week.
625 if cn_conn.is_schedule_minimum_once_per_week():
627 if ((t_repsFrom.replica_flags &
628 drsuapi.DRSUAPI_DRS_PER_SYNC) == 0x0):
629 t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_PER_SYNC
631 # Bit DRS_INIT_SYNC is set in t.replicaFlags if and only
632 # if the source DSA and the local DC's nTDSDSA object are
633 # in the same site or source dsa is the FSMO role owner
634 # of one or more FSMO roles in the NC replica.
635 if same_site or n_rep.is_fsmo_role_owner(s_dnstr):
637 if ((t_repsFrom.replica_flags &
638 drsuapi.DRSUAPI_DRS_INIT_SYNC) == 0x0):
639 t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_INIT_SYNC
641 # If bit NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT is set in
642 # cn!options, bit DRS_NEVER_NOTIFY is set in t.replicaFlags
643 # if and only if bit NTDSCONN_OPT_USE_NOTIFY is clear in
644 # cn!options. Otherwise, bit DRS_NEVER_NOTIFY is set in
645 # t.replicaFlags if and only if s and the local DC's
646 # nTDSDSA object are in different sites.
647 if ((cn_conn.options &
648 dsdb.NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT) != 0x0):
650 if (cn_conn.options & dsdb.NTDSCONN_OPT_USE_NOTIFY) == 0x0:
651 # WARNING
653 # it LOOKS as if this next test is a bit silly: it
654 # checks the flag then sets it if it not set; the same
655 # effect could be achieved by unconditionally setting
656 # it. But in fact the repsFrom object has special
657 # magic attached to it, and altering replica_flags has
658 # side-effects. That is bad in my opinion, but there
659 # you go.
660 if ((t_repsFrom.replica_flags &
661 drsuapi.DRSUAPI_DRS_NEVER_NOTIFY) == 0x0):
662 t_repsFrom.replica_flags |= \
663 drsuapi.DRSUAPI_DRS_NEVER_NOTIFY
665 elif not same_site:
667 if ((t_repsFrom.replica_flags &
668 drsuapi.DRSUAPI_DRS_NEVER_NOTIFY) == 0x0):
669 t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_NEVER_NOTIFY
671 # Bit DRS_USE_COMPRESSION is set in t.replicaFlags if
672 # and only if s and the local DC's nTDSDSA object are
673 # not in the same site and the
674 # NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION bit is
675 # clear in cn!options
676 if (not same_site and
677 (cn_conn.options &
678 dsdb.NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION) == 0x0):
680 if ((t_repsFrom.replica_flags &
681 drsuapi.DRSUAPI_DRS_USE_COMPRESSION) == 0x0):
682 t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_USE_COMPRESSION
684 # Bit DRS_TWOWAY_SYNC is set in t.replicaFlags if and only
685 # if bit NTDSCONN_OPT_TWOWAY_SYNC is set in cn!options.
686 if (cn_conn.options & dsdb.NTDSCONN_OPT_TWOWAY_SYNC) != 0x0:
688 if ((t_repsFrom.replica_flags &
689 drsuapi.DRSUAPI_DRS_TWOWAY_SYNC) == 0x0):
690 t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_TWOWAY_SYNC
692 # Bits DRS_DISABLE_AUTO_SYNC and DRS_DISABLE_PERIODIC_SYNC are
693 # set in t.replicaFlags if and only if cn!enabledConnection = false.
694 if not cn_conn.is_enabled():
696 if ((t_repsFrom.replica_flags &
697 drsuapi.DRSUAPI_DRS_DISABLE_AUTO_SYNC) == 0x0):
698 t_repsFrom.replica_flags |= \
699 drsuapi.DRSUAPI_DRS_DISABLE_AUTO_SYNC
701 if ((t_repsFrom.replica_flags &
702 drsuapi.DRSUAPI_DRS_DISABLE_PERIODIC_SYNC) == 0x0):
703 t_repsFrom.replica_flags |= \
704 drsuapi.DRSUAPI_DRS_DISABLE_PERIODIC_SYNC
706 # If s and the local DC's nTDSDSA object are in the same site,
707 # cn!transportType has no value, or the RDN of cn!transportType
708 # is CN=IP:
710 # Bit DRS_MAIL_REP in t.replicaFlags is clear.
712 # t.uuidTransport = NULL GUID.
714 # t.uuidDsa = The GUID-based DNS name of s.
716 # Otherwise:
718 # Bit DRS_MAIL_REP in t.replicaFlags is set.
720 # If x is the object with dsname cn!transportType,
721 # t.uuidTransport = x!objectGUID.
723 # Let a be the attribute identified by
724 # x!transportAddressAttribute. If a is
725 # the dNSHostName attribute, t.uuidDsa = the GUID-based
726 # DNS name of s. Otherwise, t.uuidDsa = (s!parent)!a.
728 # It appears that the first statement i.e.
730 # "If s and the local DC's nTDSDSA object are in the same
731 # site, cn!transportType has no value, or the RDN of
732 # cn!transportType is CN=IP:"
734 # could be a slightly tighter statement if it had an "or"
735 # between each condition. I believe this should
736 # be interpreted as:
738 # IF (same-site) OR (no-value) OR (type-ip)
740 # because IP should be the primary transport mechanism
741 # (even in inter-site) and the absense of the transportType
742 # attribute should always imply IP no matter if its multi-site
744 # NOTE MS-TECH INCORRECT:
746 # All indications point to these statements above being
747 # incorrectly stated:
749 # t.uuidDsa = The GUID-based DNS name of s.
751 # Let a be the attribute identified by
752 # x!transportAddressAttribute. If a is
753 # the dNSHostName attribute, t.uuidDsa = the GUID-based
754 # DNS name of s. Otherwise, t.uuidDsa = (s!parent)!a.
756 # because the uuidDSA is a GUID and not a GUID-base DNS
757 # name. Nor can uuidDsa hold (s!parent)!a if not
758 # dNSHostName. What should have been said is:
760 # t.naDsa = The GUID-based DNS name of s
762 # That would also be correct if transportAddressAttribute
763 # were "mailAddress" because (naDsa) can also correctly
764 # hold the SMTP ISM service address.
766 nastr = "%s._msdcs.%s" % (s_dsa.dsa_guid, self.samdb.forest_dns_name())
768 if ((t_repsFrom.replica_flags &
769 drsuapi.DRSUAPI_DRS_MAIL_REP) != 0x0):
770 t_repsFrom.replica_flags &= ~drsuapi.DRSUAPI_DRS_MAIL_REP
772 t_repsFrom.transport_guid = misc.GUID()
774 # See (NOTE MS-TECH INCORRECT) above
776 # NOTE: it looks like these conditionals are pointless,
777 # because the state will end up as `t_repsFrom.dns_name1 ==
778 # nastr` in either case, BUT the repsFrom thing is magic and
779 # assigning to it alters some flags. So we try not to update
780 # it unless necessary.
781 if t_repsFrom.dns_name1 != nastr:
782 t_repsFrom.dns_name1 = nastr
784 if t_repsFrom.version > 0x1 and t_repsFrom.dns_name2 != nastr:
785 t_repsFrom.dns_name2 = nastr
787 if t_repsFrom.is_modified():
788 DEBUG_FN("modify_repsFrom(): %s" % t_repsFrom)
790 def get_dsa_for_implied_replica(self, n_rep, cn_conn):
791 """If a connection imply a replica, find the relevant DSA
793 Given a NC replica and NTDS Connection, determine if the
794 connection implies a repsFrom tuple should be present from the
795 source DSA listed in the connection to the naming context. If
796 it should be, return the DSA; otherwise return None.
798 Based on part of MS-ADTS 6.2.2.5
800 :param n_rep: NC replica
801 :param cn_conn: NTDS Connection
802 :return: source DSA or None
804 # XXX different conditions for "implies" than MS-ADTS 6.2.2
805 # preamble.
807 # It boils down to: we want an enabled, non-FRS connections to
808 # a valid remote DSA with a non-RO replica corresponding to
809 # n_rep.
811 if not cn_conn.is_enabled() or cn_conn.is_rodc_topology():
812 return None
814 s_dnstr = cn_conn.get_from_dnstr()
815 s_dsa = self.get_dsa(s_dnstr)
817 # No DSA matching this source DN string?
818 if s_dsa is None:
819 return None
821 s_rep = s_dsa.get_current_replica(n_rep.nc_dnstr)
823 if (s_rep is not None and
824 s_rep.is_present() and
825 (not s_rep.is_ro() or n_rep.is_partial())):
826 return s_dsa
827 return None
830 def translate_ntdsconn(self, current_dsa=None):
831 """Adjust repsFrom to match NTDSConnections
833 This function adjusts values of repsFrom abstract attributes of NC
834 replicas on the local DC to match those implied by
835 nTDSConnection objects.
837 Based on [MS-ADTS] 6.2.2.5
839 :param current_dsa: optional DSA on whose behalf we are acting.
840 :return: None
842 count = 0
844 if current_dsa is None:
845 current_dsa = self.my_dsa
847 if current_dsa.is_translate_ntdsconn_disabled():
848 DEBUG_FN("skipping translate_ntdsconn() "
849 "because disabling flag is set")
850 return
852 DEBUG_FN("translate_ntdsconn(): enter")
854 current_rep_table, needed_rep_table = current_dsa.get_rep_tables()
856 # Filled in with replicas we currently have that need deleting
857 delete_reps = set()
859 # We're using the MS notation names here to allow
860 # correlation back to the published algorithm.
862 # n_rep - NC replica (n)
863 # t_repsFrom - tuple (t) in n!repsFrom
864 # s_dsa - Source DSA of the replica. Defined as nTDSDSA
865 # object (s) such that (s!objectGUID = t.uuidDsa)
866 # In our IDL representation of repsFrom the (uuidDsa)
867 # attribute is called (source_dsa_obj_guid)
868 # cn_conn - (cn) is nTDSConnection object and child of the local
869 # DC's nTDSDSA object and (cn!fromServer = s)
870 # s_rep - source DSA replica of n
872 # If we have the replica and its not needed
873 # then we add it to the "to be deleted" list.
874 for dnstr in current_rep_table:
875 if dnstr not in needed_rep_table:
876 delete_reps.add(dnstr)
878 DEBUG_FN('current %d needed %d delete %d' % (len(current_rep_table),
879 len(needed_rep_table), len(delete_reps)))
881 if delete_reps:
882 DEBUG('deleting these reps: %s' % delete_reps)
883 for dnstr in delete_reps:
884 del current_rep_table[dnstr]
886 # Now perform the scan of replicas we'll need
887 # and compare any current repsFrom against the
888 # connections
889 for n_rep in needed_rep_table.values():
891 # load any repsFrom and fsmo roles as we'll
892 # need them during connection translation
893 n_rep.load_repsFrom(self.samdb)
894 n_rep.load_fsmo_roles(self.samdb)
896 # Loop thru the existing repsFrom tupples (if any)
897 # XXX This is a list and could contain duplicates
898 # (multiple load_repsFrom calls)
899 for t_repsFrom in n_rep.rep_repsFrom:
901 # for each tuple t in n!repsFrom, let s be the nTDSDSA
902 # object such that s!objectGUID = t.uuidDsa
903 guidstr = str(t_repsFrom.source_dsa_obj_guid)
904 s_dsa = self.get_dsa_by_guidstr(guidstr)
906 # Source dsa is gone from config (strange)
907 # so cleanup stale repsFrom for unlisted DSA
908 if s_dsa is None:
909 logger.warning("repsFrom source DSA guid (%s) not found" %
910 guidstr)
911 t_repsFrom.to_be_deleted = True
912 continue
914 # Find the connection that this repsFrom would use. If
915 # there isn't a good one (i.e. non-RODC_TOPOLOGY,
916 # meaning non-FRS), we delete the repsFrom.
917 s_dnstr = s_dsa.dsa_dnstr
918 connections = current_dsa.get_connection_by_from_dnstr(s_dnstr)
919 for cn_conn in connections:
920 if not cn_conn.is_rodc_topology():
921 break
922 else:
923 # no break means no non-rodc_topology connection exists
924 t_repsFrom.to_be_deleted = True
925 continue
927 # KCC removes this repsFrom tuple if any of the following
928 # is true:
929 # No NC replica of the NC "is present" on DSA that
930 # would be source of replica
932 # A writable replica of the NC "should be present" on
933 # the local DC, but a partial replica "is present" on
934 # the source DSA
935 s_rep = s_dsa.get_current_replica(n_rep.nc_dnstr)
937 if s_rep is None or not s_rep.is_present() or \
938 (not n_rep.is_ro() and s_rep.is_partial()):
940 t_repsFrom.to_be_deleted = True
941 continue
943 # If the KCC did not remove t from n!repsFrom, it updates t
944 self.modify_repsFrom(n_rep, t_repsFrom, s_rep, s_dsa, cn_conn)
946 # Loop thru connections and add implied repsFrom tuples
947 # for each NTDSConnection under our local DSA if the
948 # repsFrom is not already present
949 for cn_conn in current_dsa.connect_table.values():
951 s_dsa = self.get_dsa_for_implied_replica(n_rep, cn_conn)
952 if s_dsa is None:
953 continue
955 # Loop thru the existing repsFrom tupples (if any) and
956 # if we already have a tuple for this connection then
957 # no need to proceed to add. It will have been changed
958 # to have the correct attributes above
959 for t_repsFrom in n_rep.rep_repsFrom:
960 guidstr = str(t_repsFrom.source_dsa_obj_guid)
961 if s_dsa is self.get_dsa_by_guidstr(guidstr):
962 s_dsa = None
963 break
965 if s_dsa is None:
966 continue
968 # Create a new RepsFromTo and proceed to modify
969 # it according to specification
970 t_repsFrom = RepsFromTo(n_rep.nc_dnstr)
972 t_repsFrom.source_dsa_obj_guid = s_dsa.dsa_guid
974 s_rep = s_dsa.get_current_replica(n_rep.nc_dnstr)
976 self.modify_repsFrom(n_rep, t_repsFrom, s_rep, s_dsa, cn_conn)
978 # Add to our NC repsFrom as this is newly computed
979 if t_repsFrom.is_modified():
980 n_rep.rep_repsFrom.append(t_repsFrom)
982 if self.readonly:
983 # Display any to be deleted or modified repsFrom
984 text = n_rep.dumpstr_to_be_deleted()
985 if text:
986 logger.info("TO BE DELETED:\n%s" % text)
987 text = n_rep.dumpstr_to_be_modified()
988 if text:
989 logger.info("TO BE MODIFIED:\n%s" % text)
991 # Peform deletion from our tables but perform
992 # no database modification
993 n_rep.commit_repsFrom(self.samdb, ro=True)
994 else:
995 # Commit any modified repsFrom to the NC replica
996 n_rep.commit_repsFrom(self.samdb)
998 def merge_failed_links(self, ping=None):
999 """Merge of kCCFailedLinks and kCCFailedLinks from bridgeheads.
1001 The KCC on a writable DC attempts to merge the link and connection
1002 failure information from bridgehead DCs in its own site to help it
1003 identify failed bridgehead DCs.
1005 Based on MS-ADTS 6.2.2.3.2 "Merge of kCCFailedLinks and kCCFailedLinks
1006 from Bridgeheads"
1008 :param ping: An oracle of current bridgehead availability
1009 :return: None
1011 # 1. Queries every bridgehead server in your site (other than yourself)
1012 # 2. For every ntDSConnection that references a server in a different
1013 # site merge all the failure info
1015 # XXX - not implemented yet
1016 if ping is not None:
1017 debug.DEBUG_RED("merge_failed_links() is NOT IMPLEMENTED")
1018 else:
1019 DEBUG_FN("skipping merge_failed_links() because it requires "
1020 "real network connections\n"
1021 "and we weren't asked to --attempt-live-connections")
1023 def setup_graph(self, part):
1024 """Set up an intersite graph
1026 An intersite graph has a Vertex for each site object, a
1027 MultiEdge for each SiteLink object, and a MutliEdgeSet for
1028 each siteLinkBridge object (or implied siteLinkBridge). It
1029 reflects the intersite topology in a slightly more abstract
1030 graph form.
1032 Roughly corresponds to MS-ADTS 6.2.2.3.4.3
1034 :param part: a Partition object
1035 :returns: an InterSiteGraph object
1037 # If 'Bridge all site links' is enabled and Win2k3 bridges required
1038 # is not set
1039 # NTDSTRANSPORT_OPT_BRIDGES_REQUIRED 0x00000002
1040 # No documentation for this however, ntdsapi.h appears to have:
1041 # NTDSSETTINGS_OPT_W2K3_BRIDGES_REQUIRED = 0x00001000
1042 bridges_required = self.my_site.site_options & 0x00001002 != 0
1043 transport_guid = str(self.ip_transport.guid)
1045 g = setup_graph(part, self.site_table, transport_guid,
1046 self.sitelink_table, bridges_required)
1048 if self.verify or self.dot_file_dir is not None:
1049 dot_edges = []
1050 for edge in g.edges:
1051 for a, b in itertools.combinations(edge.vertices, 2):
1052 dot_edges.append((a.site.site_dnstr, b.site.site_dnstr))
1053 verify_properties = ()
1054 name = 'site_edges_%s' % part.partstr
1055 verify_and_dot(name, dot_edges, directed=False,
1056 label=self.my_dsa_dnstr,
1057 properties=verify_properties, debug=DEBUG,
1058 verify=self.verify,
1059 dot_file_dir=self.dot_file_dir)
1061 return g
1063 def get_bridgehead(self, site, part, transport, partial_ok, detect_failed):
1064 """Get a bridghead DC for a site.
1066 Part of MS-ADTS 6.2.2.3.4.4
1068 :param site: site object representing for which a bridgehead
1069 DC is desired.
1070 :param part: crossRef for NC to replicate.
1071 :param transport: interSiteTransport object for replication
1072 traffic.
1073 :param partial_ok: True if a DC containing a partial
1074 replica or a full replica will suffice, False if only
1075 a full replica will suffice.
1076 :param detect_failed: True to detect failed DCs and route
1077 replication traffic around them, False to assume no DC
1078 has failed.
1079 :return: dsa object for the bridgehead DC or None
1082 bhs = self.get_all_bridgeheads(site, part, transport,
1083 partial_ok, detect_failed)
1084 if not bhs:
1085 debug.DEBUG_MAGENTA("get_bridgehead FAILED:\nsitedn = %s" %
1086 site.site_dnstr)
1087 return None
1089 debug.DEBUG_GREEN("get_bridgehead:\n\tsitedn = %s\n\tbhdn = %s" %
1090 (site.site_dnstr, bhs[0].dsa_dnstr))
1091 return bhs[0]
1093 def get_all_bridgeheads(self, site, part, transport,
1094 partial_ok, detect_failed):
1095 """Get all bridghead DCs on a site satisfying the given criteria
1097 Part of MS-ADTS 6.2.2.3.4.4
1099 :param site: site object representing the site for which
1100 bridgehead DCs are desired.
1101 :param part: partition for NC to replicate.
1102 :param transport: interSiteTransport object for
1103 replication traffic.
1104 :param partial_ok: True if a DC containing a partial
1105 replica or a full replica will suffice, False if
1106 only a full replica will suffice.
1107 :param detect_failed: True to detect failed DCs and route
1108 replication traffic around them, FALSE to assume
1109 no DC has failed.
1110 :return: list of dsa object for available bridgehead DCs
1112 bhs = []
1114 if transport.name != "IP":
1115 raise KCCError("get_all_bridgeheads has run into a "
1116 "non-IP transport! %r"
1117 % (transport.name,))
1119 DEBUG_FN(site.rw_dsa_table)
1120 for dsa in site.rw_dsa_table.values():
1122 pdnstr = dsa.get_parent_dnstr()
1124 # IF t!bridgeheadServerListBL has one or more values and
1125 # t!bridgeheadServerListBL does not contain a reference
1126 # to the parent object of dc then skip dc
1127 if ((len(transport.bridgehead_list) != 0 and
1128 pdnstr not in transport.bridgehead_list)):
1129 continue
1131 # IF dc is in the same site as the local DC
1132 # IF a replica of cr!nCName is not in the set of NC replicas
1133 # that "should be present" on dc or a partial replica of the
1134 # NC "should be present" but partialReplicasOkay = FALSE
1135 # Skip dc
1136 if self.my_site.same_site(dsa):
1137 needed, ro, partial = part.should_be_present(dsa)
1138 if not needed or (partial and not partial_ok):
1139 continue
1140 rep = dsa.get_current_replica(part.nc_dnstr)
1142 # ELSE
1143 # IF an NC replica of cr!nCName is not in the set of NC
1144 # replicas that "are present" on dc or a partial replica of
1145 # the NC "is present" but partialReplicasOkay = FALSE
1146 # Skip dc
1147 else:
1148 rep = dsa.get_current_replica(part.nc_dnstr)
1149 if rep is None or (rep.is_partial() and not partial_ok):
1150 continue
1152 # IF AmIRODC() and cr!nCName corresponds to default NC then
1153 # Let dsaobj be the nTDSDSA object of the dc
1154 # IF dsaobj.msDS-Behavior-Version < DS_DOMAIN_FUNCTION_2008
1155 # Skip dc
1156 if self.my_dsa.is_ro() and rep is not None and rep.is_default():
1157 if not dsa.is_minimum_behavior(dsdb.DS_DOMAIN_FUNCTION_2008):
1158 continue
1160 # IF BridgeheadDCFailed(dc!objectGUID, detectFailedDCs) = TRUE
1161 # Skip dc
1162 if self.is_bridgehead_failed(dsa, detect_failed):
1163 DEBUG("bridgehead is failed")
1164 continue
1166 DEBUG_FN("found a bridgehead: %s" % dsa.dsa_dnstr)
1167 bhs.append(dsa)
1169 # IF bit NTDSSETTINGS_OPT_IS_RAND_BH_SELECTION_DISABLED is set in
1170 # s!options
1171 # SORT bhs such that all GC servers precede DCs that are not GC
1172 # servers, and otherwise by ascending objectGUID
1173 # ELSE
1174 # SORT bhs in a random order
1175 if site.is_random_bridgehead_disabled():
1176 bhs.sort(sort_dsa_by_gc_and_guid)
1177 else:
1178 random.shuffle(bhs)
1179 debug.DEBUG_YELLOW(bhs)
1180 return bhs
1182 def is_bridgehead_failed(self, dsa, detect_failed):
1183 """Determine whether a given DC is known to be in a failed state
1185 :param dsa: the bridgehead to test
1186 :param detect_failed: True to really check, False to assume no failure
1187 :return: True if and only if the DC should be considered failed
1189 Here we DEPART from the pseudo code spec which appears to be
1190 wrong. It says, in full:
1192 /***** BridgeheadDCFailed *****/
1193 /* Determine whether a given DC is known to be in a failed state.
1194 * IN: objectGUID - objectGUID of the DC's nTDSDSA object.
1195 * IN: detectFailedDCs - TRUE if and only failed DC detection is
1196 * enabled.
1197 * RETURNS: TRUE if and only if the DC should be considered to be in a
1198 * failed state.
1200 BridgeheadDCFailed(IN GUID objectGUID, IN bool detectFailedDCs) : bool
1202 IF bit NTDSSETTINGS_OPT_IS_TOPL_DETECT_STALE_DISABLED is set in
1203 the options attribute of the site settings object for the local
1204 DC's site
1205 RETURN FALSE
1206 ELSEIF a tuple z exists in the kCCFailedLinks or
1207 kCCFailedConnections variables such that z.UUIDDsa =
1208 objectGUID, z.FailureCount > 1, and the current time -
1209 z.TimeFirstFailure > 2 hours
1210 RETURN TRUE
1211 ELSE
1212 RETURN detectFailedDCs
1213 ENDIF
1216 where you will see detectFailedDCs is not behaving as
1217 advertised -- it is acting as a default return code in the
1218 event that a failure is not detected, not a switch turning
1219 detection on or off. Elsewhere the documentation seems to
1220 concur with the comment rather than the code.
1222 if not detect_failed:
1223 return False
1225 # NTDSSETTINGS_OPT_IS_TOPL_DETECT_STALE_DISABLED = 0x00000008
1226 # When DETECT_STALE_DISABLED, we can never know of if
1227 # it's in a failed state
1228 if self.my_site.site_options & 0x00000008:
1229 return False
1231 return self.is_stale_link_connection(dsa)
1233 def create_connection(self, part, rbh, rsite, transport,
1234 lbh, lsite, link_opt, link_sched,
1235 partial_ok, detect_failed):
1236 """Create an nTDSConnection object as specified if it doesn't exist.
1238 Part of MS-ADTS 6.2.2.3.4.5
1240 :param part: crossRef object for the NC to replicate.
1241 :param rbh: nTDSDSA object for DC to act as the
1242 IDL_DRSGetNCChanges server (which is in a site other
1243 than the local DC's site).
1244 :param rsite: site of the rbh
1245 :param transport: interSiteTransport object for the transport
1246 to use for replication traffic.
1247 :param lbh: nTDSDSA object for DC to act as the
1248 IDL_DRSGetNCChanges client (which is in the local DC's site).
1249 :param lsite: site of the lbh
1250 :param link_opt: Replication parameters (aggregated siteLink options,
1251 etc.)
1252 :param link_sched: Schedule specifying the times at which
1253 to begin replicating.
1254 :partial_ok: True if bridgehead DCs containing partial
1255 replicas of the NC are acceptable.
1256 :param detect_failed: True to detect failed DCs and route
1257 replication traffic around them, FALSE to assume no DC
1258 has failed.
1260 rbhs_all = self.get_all_bridgeheads(rsite, part, transport,
1261 partial_ok, False)
1262 rbh_table = dict((x.dsa_dnstr, x) for x in rbhs_all)
1264 debug.DEBUG_GREY("rbhs_all: %s %s" % (len(rbhs_all),
1265 [x.dsa_dnstr for x in rbhs_all]))
1267 # MS-TECH says to compute rbhs_avail but then doesn't use it
1268 # rbhs_avail = self.get_all_bridgeheads(rsite, part, transport,
1269 # partial_ok, detect_failed)
1271 lbhs_all = self.get_all_bridgeheads(lsite, part, transport,
1272 partial_ok, False)
1273 if lbh.is_ro():
1274 lbhs_all.append(lbh)
1276 debug.DEBUG_GREY("lbhs_all: %s %s" % (len(lbhs_all),
1277 [x.dsa_dnstr for x in lbhs_all]))
1279 # MS-TECH says to compute lbhs_avail but then doesn't use it
1280 # lbhs_avail = self.get_all_bridgeheads(lsite, part, transport,
1281 # partial_ok, detect_failed)
1283 # FOR each nTDSConnection object cn such that the parent of cn is
1284 # a DC in lbhsAll and cn!fromServer references a DC in rbhsAll
1285 for ldsa in lbhs_all:
1286 for cn in ldsa.connect_table.values():
1288 rdsa = rbh_table.get(cn.from_dnstr)
1289 if rdsa is None:
1290 continue
1292 debug.DEBUG_DARK_YELLOW("rdsa is %s" % rdsa.dsa_dnstr)
1293 # IF bit NTDSCONN_OPT_IS_GENERATED is set in cn!options and
1294 # NTDSCONN_OPT_RODC_TOPOLOGY is clear in cn!options and
1295 # cn!transportType references t
1296 if ((cn.is_generated() and
1297 not cn.is_rodc_topology() and
1298 cn.transport_guid == transport.guid)):
1300 # IF bit NTDSCONN_OPT_USER_OWNED_SCHEDULE is clear in
1301 # cn!options and cn!schedule != sch
1302 # Perform an originating update to set cn!schedule to
1303 # sched
1304 if ((not cn.is_user_owned_schedule() and
1305 not cn.is_equivalent_schedule(link_sched))):
1306 cn.schedule = link_sched
1307 cn.set_modified(True)
1309 # IF bits NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT and
1310 # NTDSCONN_OPT_USE_NOTIFY are set in cn
1311 if cn.is_override_notify_default() and \
1312 cn.is_use_notify():
1314 # IF bit NTDSSITELINK_OPT_USE_NOTIFY is clear in
1315 # ri.Options
1316 # Perform an originating update to clear bits
1317 # NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT and
1318 # NTDSCONN_OPT_USE_NOTIFY in cn!options
1319 if (link_opt & dsdb.NTDSSITELINK_OPT_USE_NOTIFY) == 0:
1320 cn.options &= \
1321 ~(dsdb.NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT |
1322 dsdb.NTDSCONN_OPT_USE_NOTIFY)
1323 cn.set_modified(True)
1325 # ELSE
1326 else:
1328 # IF bit NTDSSITELINK_OPT_USE_NOTIFY is set in
1329 # ri.Options
1330 # Perform an originating update to set bits
1331 # NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT and
1332 # NTDSCONN_OPT_USE_NOTIFY in cn!options
1333 if (link_opt & dsdb.NTDSSITELINK_OPT_USE_NOTIFY) != 0:
1334 cn.options |= \
1335 (dsdb.NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT |
1336 dsdb.NTDSCONN_OPT_USE_NOTIFY)
1337 cn.set_modified(True)
1339 # IF bit NTDSCONN_OPT_TWOWAY_SYNC is set in cn!options
1340 if cn.is_twoway_sync():
1342 # IF bit NTDSSITELINK_OPT_TWOWAY_SYNC is clear in
1343 # ri.Options
1344 # Perform an originating update to clear bit
1345 # NTDSCONN_OPT_TWOWAY_SYNC in cn!options
1346 if (link_opt & dsdb.NTDSSITELINK_OPT_TWOWAY_SYNC) == 0:
1347 cn.options &= ~dsdb.NTDSCONN_OPT_TWOWAY_SYNC
1348 cn.set_modified(True)
1350 # ELSE
1351 else:
1353 # IF bit NTDSSITELINK_OPT_TWOWAY_SYNC is set in
1354 # ri.Options
1355 # Perform an originating update to set bit
1356 # NTDSCONN_OPT_TWOWAY_SYNC in cn!options
1357 if (link_opt & dsdb.NTDSSITELINK_OPT_TWOWAY_SYNC) != 0:
1358 cn.options |= dsdb.NTDSCONN_OPT_TWOWAY_SYNC
1359 cn.set_modified(True)
1361 # IF bit NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION is set
1362 # in cn!options
1363 if cn.is_intersite_compression_disabled():
1365 # IF bit NTDSSITELINK_OPT_DISABLE_COMPRESSION is clear
1366 # in ri.Options
1367 # Perform an originating update to clear bit
1368 # NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION in
1369 # cn!options
1370 if ((link_opt &
1371 dsdb.NTDSSITELINK_OPT_DISABLE_COMPRESSION) == 0):
1372 cn.options &= \
1373 ~dsdb.NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION
1374 cn.set_modified(True)
1376 # ELSE
1377 else:
1378 # IF bit NTDSSITELINK_OPT_DISABLE_COMPRESSION is set in
1379 # ri.Options
1380 # Perform an originating update to set bit
1381 # NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION in
1382 # cn!options
1383 if ((link_opt &
1384 dsdb.NTDSSITELINK_OPT_DISABLE_COMPRESSION) != 0):
1385 cn.options |= \
1386 dsdb.NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION
1387 cn.set_modified(True)
1389 # Display any modified connection
1390 if self.readonly:
1391 if cn.to_be_modified:
1392 logger.info("TO BE MODIFIED:\n%s" % cn)
1394 ldsa.commit_connections(self.samdb, ro=True)
1395 else:
1396 ldsa.commit_connections(self.samdb)
1397 # ENDFOR
1399 valid_connections = 0
1401 # FOR each nTDSConnection object cn such that cn!parent is
1402 # a DC in lbhsAll and cn!fromServer references a DC in rbhsAll
1403 for ldsa in lbhs_all:
1404 for cn in ldsa.connect_table.values():
1406 rdsa = rbh_table.get(cn.from_dnstr)
1407 if rdsa is None:
1408 continue
1410 debug.DEBUG_DARK_YELLOW("round 2: rdsa is %s" % rdsa.dsa_dnstr)
1412 # IF (bit NTDSCONN_OPT_IS_GENERATED is clear in cn!options or
1413 # cn!transportType references t) and
1414 # NTDSCONN_OPT_RODC_TOPOLOGY is clear in cn!options
1415 if (((not cn.is_generated() or
1416 cn.transport_guid == transport.guid) and
1417 not cn.is_rodc_topology())):
1419 # LET rguid be the objectGUID of the nTDSDSA object
1420 # referenced by cn!fromServer
1421 # LET lguid be (cn!parent)!objectGUID
1423 # IF BridgeheadDCFailed(rguid, detectFailedDCs) = FALSE and
1424 # BridgeheadDCFailed(lguid, detectFailedDCs) = FALSE
1425 # Increment cValidConnections by 1
1426 if ((not self.is_bridgehead_failed(rdsa, detect_failed) and
1427 not self.is_bridgehead_failed(ldsa, detect_failed))):
1428 valid_connections += 1
1430 # IF keepConnections does not contain cn!objectGUID
1431 # APPEND cn!objectGUID to keepConnections
1432 self.kept_connections.add(cn)
1434 # ENDFOR
1435 debug.DEBUG_RED("valid connections %d" % valid_connections)
1436 DEBUG("kept_connections:\n%s" % (self.kept_connections,))
1437 # IF cValidConnections = 0
1438 if valid_connections == 0:
1440 # LET opt be NTDSCONN_OPT_IS_GENERATED
1441 opt = dsdb.NTDSCONN_OPT_IS_GENERATED
1443 # IF bit NTDSSITELINK_OPT_USE_NOTIFY is set in ri.Options
1444 # SET bits NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT and
1445 # NTDSCONN_OPT_USE_NOTIFY in opt
1446 if (link_opt & dsdb.NTDSSITELINK_OPT_USE_NOTIFY) != 0:
1447 opt |= (dsdb.NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT |
1448 dsdb.NTDSCONN_OPT_USE_NOTIFY)
1450 # IF bit NTDSSITELINK_OPT_TWOWAY_SYNC is set in ri.Options
1451 # SET bit NTDSCONN_OPT_TWOWAY_SYNC opt
1452 if (link_opt & dsdb.NTDSSITELINK_OPT_TWOWAY_SYNC) != 0:
1453 opt |= dsdb.NTDSCONN_OPT_TWOWAY_SYNC
1455 # IF bit NTDSSITELINK_OPT_DISABLE_COMPRESSION is set in
1456 # ri.Options
1457 # SET bit NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION in opt
1458 if ((link_opt &
1459 dsdb.NTDSSITELINK_OPT_DISABLE_COMPRESSION) != 0):
1460 opt |= dsdb.NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION
1462 # Perform an originating update to create a new nTDSConnection
1463 # object cn that is a child of lbh, cn!enabledConnection = TRUE,
1464 # cn!options = opt, cn!transportType is a reference to t,
1465 # cn!fromServer is a reference to rbh, and cn!schedule = sch
1466 DEBUG_FN("new connection, KCC dsa: %s" % self.my_dsa.dsa_dnstr)
1467 system_flags = (dsdb.SYSTEM_FLAG_CONFIG_ALLOW_RENAME |
1468 dsdb.SYSTEM_FLAG_CONFIG_ALLOW_MOVE)
1470 cn = lbh.new_connection(opt, system_flags, transport,
1471 rbh.dsa_dnstr, link_sched)
1473 # Display any added connection
1474 if self.readonly:
1475 if cn.to_be_added:
1476 logger.info("TO BE ADDED:\n%s" % cn)
1478 lbh.commit_connections(self.samdb, ro=True)
1479 else:
1480 lbh.commit_connections(self.samdb)
1482 # APPEND cn!objectGUID to keepConnections
1483 self.kept_connections.add(cn)
1485 def add_transports(self, vertex, local_vertex, graph, detect_failed):
1486 """Build a Vertex's transport lists
1488 Each vertex has accept_red_red and accept_black lists that
1489 list what transports they accept under various conditions. The
1490 only transport that is ever accepted is IP, and a dummy extra
1491 transport called "EDGE_TYPE_ALL".
1493 Part of MS-ADTS 6.2.2.3.4.3 -- ColorVertices
1495 :param vertex: the remote vertex we are thinking about
1496 :param local_vertex: the vertex relating to the local site.
1497 :param graph: the intersite graph
1498 :param detect_failed: whether to detect failed links
1499 :return: True if some bridgeheads were not found
1501 # The docs ([MS-ADTS] 6.2.2.3.4.3) say to use local_vertex
1502 # here, but using vertex seems to make more sense. That is,
1503 # the docs want this:
1505 #bh = self.get_bridgehead(local_vertex.site, vertex.part, transport,
1506 # local_vertex.is_black(), detect_failed)
1508 # TODO WHY?????
1510 vertex.accept_red_red = []
1511 vertex.accept_black = []
1512 found_failed = False
1514 if vertex in graph.connected_vertices:
1515 t_guid = str(self.ip_transport.guid)
1517 bh = self.get_bridgehead(vertex.site, vertex.part,
1518 self.ip_transport,
1519 vertex.is_black(), detect_failed)
1520 if bh is None:
1521 if vertex.site.is_rodc_site():
1522 vertex.accept_red_red.append(t_guid)
1523 else:
1524 found_failed = True
1525 else:
1526 vertex.accept_red_red.append(t_guid)
1527 vertex.accept_black.append(t_guid)
1529 # Add additional transport to ensure another run of Dijkstra
1530 vertex.accept_red_red.append("EDGE_TYPE_ALL")
1531 vertex.accept_black.append("EDGE_TYPE_ALL")
1533 return found_failed
1535 def create_connections(self, graph, part, detect_failed):
1536 """Create intersite NTDSConnections as needed by a partition
1538 Construct an NC replica graph for the NC identified by
1539 the given crossRef, then create any additional nTDSConnection
1540 objects required.
1542 :param graph: site graph.
1543 :param part: crossRef object for NC.
1544 :param detect_failed: True to detect failed DCs and route
1545 replication traffic around them, False to assume no DC
1546 has failed.
1548 Modifies self.kept_connections by adding any connections
1549 deemed to be "in use".
1551 :return: (all_connected, found_failed_dc)
1552 (all_connected) True if the resulting NC replica graph
1553 connects all sites that need to be connected.
1554 (found_failed_dc) True if one or more failed DCs were
1555 detected.
1557 all_connected = True
1558 found_failed = False
1560 DEBUG_FN("create_connections(): enter\n"
1561 "\tpartdn=%s\n\tdetect_failed=%s" %
1562 (part.nc_dnstr, detect_failed))
1564 # XXX - This is a highly abbreviated function from the MS-TECH
1565 # ref. It creates connections between bridgeheads to all
1566 # sites that have appropriate replicas. Thus we are not
1567 # creating a minimum cost spanning tree but instead
1568 # producing a fully connected tree. This should produce
1569 # a full (albeit not optimal cost) replication topology.
1571 my_vertex = Vertex(self.my_site, part)
1572 my_vertex.color_vertex()
1574 for v in graph.vertices:
1575 v.color_vertex()
1576 if self.add_transports(v, my_vertex, graph, detect_failed):
1577 found_failed = True
1579 # No NC replicas for this NC in the site of the local DC,
1580 # so no nTDSConnection objects need be created
1581 if my_vertex.is_white():
1582 return all_connected, found_failed
1584 edge_list, n_components = get_spanning_tree_edges(graph,
1585 self.my_site,
1586 label=part.partstr)
1588 DEBUG_FN("%s Number of components: %d" %
1589 (part.nc_dnstr, n_components))
1590 if n_components > 1:
1591 all_connected = False
1593 # LET partialReplicaOkay be TRUE if and only if
1594 # localSiteVertex.Color = COLOR.BLACK
1595 partial_ok = my_vertex.is_black()
1597 # Utilize the IP transport only for now
1598 transport = self.ip_transport
1600 DEBUG("edge_list %s" % edge_list)
1601 for e in edge_list:
1602 # XXX more accurate comparison?
1603 if e.directed and e.vertices[0].site is self.my_site:
1604 continue
1606 if e.vertices[0].site is self.my_site:
1607 rsite = e.vertices[1].site
1608 else:
1609 rsite = e.vertices[0].site
1611 # We don't make connections to our own site as that
1612 # is intrasite topology generator's job
1613 if rsite is self.my_site:
1614 DEBUG("rsite is my_site")
1615 continue
1617 # Determine bridgehead server in remote site
1618 rbh = self.get_bridgehead(rsite, part, transport,
1619 partial_ok, detect_failed)
1620 if rbh is None:
1621 continue
1623 # RODC acts as an BH for itself
1624 # IF AmIRODC() then
1625 # LET lbh be the nTDSDSA object of the local DC
1626 # ELSE
1627 # LET lbh be the result of GetBridgeheadDC(localSiteVertex.ID,
1628 # cr, t, partialReplicaOkay, detectFailedDCs)
1629 if self.my_dsa.is_ro():
1630 lsite = self.my_site
1631 lbh = self.my_dsa
1632 else:
1633 lsite = self.my_site
1634 lbh = self.get_bridgehead(lsite, part, transport,
1635 partial_ok, detect_failed)
1636 # TODO
1637 if lbh is None:
1638 debug.DEBUG_RED("DISASTER! lbh is None")
1639 return False, True
1641 DEBUG_FN("lsite: %s\nrsite: %s" % (lsite, rsite))
1642 DEBUG_FN("vertices %s" % (e.vertices,))
1643 debug.DEBUG_BLUE("bridgeheads\n%s\n%s\n%s" % (lbh, rbh, "-" * 70))
1645 sitelink = e.site_link
1646 if sitelink is None:
1647 link_opt = 0x0
1648 link_sched = None
1649 else:
1650 link_opt = sitelink.options
1651 link_sched = sitelink.schedule
1653 self.create_connection(part, rbh, rsite, transport,
1654 lbh, lsite, link_opt, link_sched,
1655 partial_ok, detect_failed)
1657 return all_connected, found_failed
1659 def create_intersite_connections(self):
1660 """Create NTDSConnections as necessary for all partitions.
1662 Computes an NC replica graph for each NC replica that "should be
1663 present" on the local DC or "is present" on any DC in the same site
1664 as the local DC. For each edge directed to an NC replica on such a
1665 DC from an NC replica on a DC in another site, the KCC creates an
1666 nTDSConnection object to imply that edge if one does not already
1667 exist.
1669 Modifies self.kept_connections - A set of nTDSConnection
1670 objects for edges that are directed
1671 to the local DC's site in one or more NC replica graphs.
1673 :return: True if spanning trees were created for all NC replica
1674 graphs, otherwise False.
1676 all_connected = True
1677 self.kept_connections = set()
1679 # LET crossRefList be the set containing each object o of class
1680 # crossRef such that o is a child of the CN=Partitions child of the
1681 # config NC
1683 # FOR each crossRef object cr in crossRefList
1684 # IF cr!enabled has a value and is false, or if FLAG_CR_NTDS_NC
1685 # is clear in cr!systemFlags, skip cr.
1686 # LET g be the GRAPH return of SetupGraph()
1688 for part in self.part_table.values():
1690 if not part.is_enabled():
1691 continue
1693 if part.is_foreign():
1694 continue
1696 graph = self.setup_graph(part)
1698 # Create nTDSConnection objects, routing replication traffic
1699 # around "failed" DCs.
1700 found_failed = False
1702 connected, found_failed = self.create_connections(graph,
1703 part, True)
1705 DEBUG("with detect_failed: connected %s Found failed %s" %
1706 (connected, found_failed))
1707 if not connected:
1708 all_connected = False
1710 if found_failed:
1711 # One or more failed DCs preclude use of the ideal NC
1712 # replica graph. Add connections for the ideal graph.
1713 self.create_connections(graph, part, False)
1715 return all_connected
1717 def intersite(self, ping):
1718 """Generate the inter-site KCC replica graph and nTDSConnections
1720 As per MS-ADTS 6.2.2.3.
1722 If self.readonly is False, the connections are added to self.samdb.
1724 Produces self.kept_connections which is a set of NTDS
1725 Connections that should be kept during subsequent pruning
1726 process.
1728 After this has run, all sites should be connected in a minimum
1729 spanning tree.
1731 :param ping: An oracle function of remote site availability
1732 :return (True or False): (True) if the produced NC replica
1733 graph connects all sites that need to be connected
1736 # Retrieve my DSA
1737 mydsa = self.my_dsa
1738 mysite = self.my_site
1739 all_connected = True
1741 DEBUG_FN("intersite(): enter")
1743 # Determine who is the ISTG
1744 if self.readonly:
1745 mysite.select_istg(self.samdb, mydsa, ro=True)
1746 else:
1747 mysite.select_istg(self.samdb, mydsa, ro=False)
1749 # Test whether local site has topology disabled
1750 if mysite.is_intersite_topology_disabled():
1751 DEBUG_FN("intersite(): exit disabled all_connected=%d" %
1752 all_connected)
1753 return all_connected
1755 if not mydsa.is_istg():
1756 DEBUG_FN("intersite(): exit not istg all_connected=%d" %
1757 all_connected)
1758 return all_connected
1760 self.merge_failed_links(ping)
1762 # For each NC with an NC replica that "should be present" on the
1763 # local DC or "is present" on any DC in the same site as the
1764 # local DC, the KCC constructs a site graph--a precursor to an NC
1765 # replica graph. The site connectivity for a site graph is defined
1766 # by objects of class interSiteTransport, siteLink, and
1767 # siteLinkBridge in the config NC.
1769 all_connected = self.create_intersite_connections()
1771 DEBUG_FN("intersite(): exit all_connected=%d" % all_connected)
1772 return all_connected
1774 def update_rodc_connection(self):
1775 """Updates the RODC NTFRS connection object.
1777 If the local DSA is not an RODC, this does nothing.
1779 if not self.my_dsa.is_ro():
1780 return
1782 # Given an nTDSConnection object cn1, such that cn1.options contains
1783 # NTDSCONN_OPT_RODC_TOPOLOGY, and another nTDSConnection object cn2,
1784 # does not contain NTDSCONN_OPT_RODC_TOPOLOGY, modify cn1 to ensure
1785 # that the following is true:
1787 # cn1.fromServer = cn2.fromServer
1788 # cn1.schedule = cn2.schedule
1790 # If no such cn2 can be found, cn1 is not modified.
1791 # If no such cn1 can be found, nothing is modified by this task.
1793 all_connections = self.my_dsa.connect_table.values()
1794 ro_connections = [x for x in all_connections if x.is_rodc_topology()]
1795 rw_connections = [x for x in all_connections
1796 if x not in ro_connections]
1798 # XXX here we are dealing with multiple RODC_TOPO connections,
1799 # if they exist. It is not clear whether the spec means that
1800 # or if it ever arises.
1801 if rw_connections and ro_connections:
1802 for con in ro_connections:
1803 cn2 = rw_connections[0]
1804 con.from_dnstr = cn2.from_dnstr
1805 con.schedule = cn2.schedule
1806 con.to_be_modified = True
1808 self.my_dsa.commit_connections(self.samdb, ro=self.readonly)
1810 def intrasite_max_node_edges(self, node_count):
1811 """Find the maximum number of edges directed to an intrasite node
1813 The KCC does not create more than 50 edges directed to a
1814 single DC. To optimize replication, we compute that each node
1815 should have n+2 total edges directed to it such that (n) is
1816 the smallest non-negative integer satisfying
1817 (node_count <= 2*(n*n) + 6*n + 7)
1819 (If the number of edges is m (i.e. n + 2), that is the same as
1820 2 * m*m - 2 * m + 3). We think in terms of n because that is
1821 the number of extra connections over the double directed ring
1822 that exists by default.
1824 edges n nodecount
1825 2 0 7
1826 3 1 15
1827 4 2 27
1828 5 3 43
1830 50 48 4903
1832 :param node_count: total number of nodes in the replica graph
1834 The intention is that there should be no more than 3 hops
1835 between any two DSAs at a site. With up to 7 nodes the 2 edges
1836 of the ring are enough; any configuration of extra edges with
1837 8 nodes will be enough. It is less clear that the 3 hop
1838 guarantee holds at e.g. 15 nodes in degenerate cases, but
1839 those are quite unlikely given the extra edges are randomly
1840 arranged.
1842 :param node_count: the number of nodes in the site
1843 "return: The desired maximum number of connections
1845 n = 0
1846 while True:
1847 if node_count <= (2 * (n * n) + (6 * n) + 7):
1848 break
1849 n = n + 1
1850 n = n + 2
1851 if n < 50:
1852 return n
1853 return 50
1855 def construct_intrasite_graph(self, site_local, dc_local,
1856 nc_x, gc_only, detect_stale):
1857 """Create an intrasite graph using given parameters
1859 This might be called a number of times per site with different
1860 parameters.
1862 Based on [MS-ADTS] 6.2.2.2
1864 :param site_local: site for which we are working
1865 :param dc_local: local DC that potentially needs a replica
1866 :param nc_x: naming context (x) that we are testing if it
1867 "should be present" on the local DC
1868 :param gc_only: Boolean - only consider global catalog servers
1869 :param detect_stale: Boolean - check whether links seems down
1870 :return: None
1872 # We're using the MS notation names here to allow
1873 # correlation back to the published algorithm.
1875 # nc_x - naming context (x) that we are testing if it
1876 # "should be present" on the local DC
1877 # f_of_x - replica (f) found on a DC (s) for NC (x)
1878 # dc_s - DC where f_of_x replica was found
1879 # dc_local - local DC that potentially needs a replica
1880 # (f_of_x)
1881 # r_list - replica list R
1882 # p_of_x - replica (p) is partial and found on a DC (s)
1883 # for NC (x)
1884 # l_of_x - replica (l) is the local replica for NC (x)
1885 # that should appear on the local DC
1886 # r_len = is length of replica list |R|
1888 # If the DSA doesn't need a replica for this
1889 # partition (NC x) then continue
1890 needed, ro, partial = nc_x.should_be_present(dc_local)
1892 debug.DEBUG_YELLOW("construct_intrasite_graph(): enter" +
1893 "\n\tgc_only=%d" % gc_only +
1894 "\n\tdetect_stale=%d" % detect_stale +
1895 "\n\tneeded=%s" % needed +
1896 "\n\tro=%s" % ro +
1897 "\n\tpartial=%s" % partial +
1898 "\n%s" % nc_x)
1900 if not needed:
1901 debug.DEBUG_RED("%s lacks 'should be present' status, "
1902 "aborting construct_intersite_graph!" %
1903 nc_x.nc_dnstr)
1904 return
1906 # Create a NCReplica that matches what the local replica
1907 # should say. We'll use this below in our r_list
1908 l_of_x = NCReplica(dc_local.dsa_dnstr, dc_local.dsa_guid,
1909 nc_x.nc_dnstr)
1911 l_of_x.identify_by_basedn(self.samdb)
1913 l_of_x.rep_partial = partial
1914 l_of_x.rep_ro = ro
1916 # Add this replica that "should be present" to the
1917 # needed replica table for this DSA
1918 dc_local.add_needed_replica(l_of_x)
1920 # Replica list
1922 # Let R be a sequence containing each writable replica f of x
1923 # such that f "is present" on a DC s satisfying the following
1924 # criteria:
1926 # * s is a writable DC other than the local DC.
1928 # * s is in the same site as the local DC.
1930 # * If x is a read-only full replica and x is a domain NC,
1931 # then the DC's functional level is at least
1932 # DS_BEHAVIOR_WIN2008.
1934 # * Bit NTDSSETTINGS_OPT_IS_TOPL_DETECT_STALE_DISABLED is set
1935 # in the options attribute of the site settings object for
1936 # the local DC's site, or no tuple z exists in the
1937 # kCCFailedLinks or kCCFailedConnections variables such
1938 # that z.UUIDDsa is the objectGUID of the nTDSDSA object
1939 # for s, z.FailureCount > 0, and the current time -
1940 # z.TimeFirstFailure > 2 hours.
1942 r_list = []
1944 # We'll loop thru all the DSAs looking for
1945 # writeable NC replicas that match the naming
1946 # context dn for (nc_x)
1948 for dc_s in self.my_site.dsa_table.values():
1949 # If this partition (nc_x) doesn't appear as a
1950 # replica (f_of_x) on (dc_s) then continue
1951 if not nc_x.nc_dnstr in dc_s.current_rep_table:
1952 continue
1954 # Pull out the NCReplica (f) of (x) with the dn
1955 # that matches NC (x) we are examining.
1956 f_of_x = dc_s.current_rep_table[nc_x.nc_dnstr]
1958 # Replica (f) of NC (x) must be writable
1959 if f_of_x.is_ro():
1960 continue
1962 # Replica (f) of NC (x) must satisfy the
1963 # "is present" criteria for DC (s) that
1964 # it was found on
1965 if not f_of_x.is_present():
1966 continue
1968 # DC (s) must be a writable DSA other than
1969 # my local DC. In other words we'd only replicate
1970 # from other writable DC
1971 if dc_s.is_ro() or dc_s is dc_local:
1972 continue
1974 # Certain replica graphs are produced only
1975 # for global catalogs, so test against
1976 # method input parameter
1977 if gc_only and not dc_s.is_gc():
1978 continue
1980 # DC (s) must be in the same site as the local DC
1981 # as this is the intra-site algorithm. This is
1982 # handled by virtue of placing DSAs in per
1983 # site objects (see enclosing for() loop)
1985 # If NC (x) is intended to be read-only full replica
1986 # for a domain NC on the target DC then the source
1987 # DC should have functional level at minimum WIN2008
1989 # Effectively we're saying that in order to replicate
1990 # to a targeted RODC (which was introduced in Windows 2008)
1991 # then we have to replicate from a DC that is also minimally
1992 # at that level.
1994 # You can also see this requirement in the MS special
1995 # considerations for RODC which state that to deploy
1996 # an RODC, at least one writable domain controller in
1997 # the domain must be running Windows Server 2008
1998 if ro and not partial and nc_x.nc_type == NCType.domain:
1999 if not dc_s.is_minimum_behavior(dsdb.DS_DOMAIN_FUNCTION_2008):
2000 continue
2002 # If we haven't been told to turn off stale connection
2003 # detection and this dsa has a stale connection then
2004 # continue
2005 if detect_stale and self.is_stale_link_connection(dc_s):
2006 continue
2008 # Replica meets criteria. Add it to table indexed
2009 # by the GUID of the DC that it appears on
2010 r_list.append(f_of_x)
2012 # If a partial (not full) replica of NC (x) "should be present"
2013 # on the local DC, append to R each partial replica (p of x)
2014 # such that p "is present" on a DC satisfying the same
2015 # criteria defined above for full replica DCs.
2017 # XXX This loop and the previous one differ only in whether
2018 # the replica is partial or not. here we only accept partial
2019 # (because we're partial); before we only accepted full. Order
2020 # doen't matter (the list is sorted a few lines down) so these
2021 # loops could easily be merged. Or this could be a helper
2022 # function.
2024 if partial:
2025 # Now we loop thru all the DSAs looking for
2026 # partial NC replicas that match the naming
2027 # context dn for (NC x)
2028 for dc_s in self.my_site.dsa_table.values():
2030 # If this partition NC (x) doesn't appear as a
2031 # replica (p) of NC (x) on the dsa DC (s) then
2032 # continue
2033 if not nc_x.nc_dnstr in dc_s.current_rep_table:
2034 continue
2036 # Pull out the NCReplica with the dn that
2037 # matches NC (x) we are examining.
2038 p_of_x = dc_s.current_rep_table[nc_x.nc_dnstr]
2040 # Replica (p) of NC (x) must be partial
2041 if not p_of_x.is_partial():
2042 continue
2044 # Replica (p) of NC (x) must satisfy the
2045 # "is present" criteria for DC (s) that
2046 # it was found on
2047 if not p_of_x.is_present():
2048 continue
2050 # DC (s) must be a writable DSA other than
2051 # my DSA. In other words we'd only replicate
2052 # from other writable DSA
2053 if dc_s.is_ro() or dc_s is dc_local:
2054 continue
2056 # Certain replica graphs are produced only
2057 # for global catalogs, so test against
2058 # method input parameter
2059 if gc_only and not dc_s.is_gc():
2060 continue
2062 # If we haven't been told to turn off stale connection
2063 # detection and this dsa has a stale connection then
2064 # continue
2065 if detect_stale and self.is_stale_link_connection(dc_s):
2066 continue
2068 # Replica meets criteria. Add it to table indexed
2069 # by the GUID of the DSA that it appears on
2070 r_list.append(p_of_x)
2072 # Append to R the NC replica that "should be present"
2073 # on the local DC
2074 r_list.append(l_of_x)
2076 r_list.sort(sort_replica_by_dsa_guid)
2077 r_len = len(r_list)
2079 max_node_edges = self.intrasite_max_node_edges(r_len)
2081 # Add a node for each r_list element to the replica graph
2082 graph_list = []
2083 for rep in r_list:
2084 node = GraphNode(rep.rep_dsa_dnstr, max_node_edges)
2085 graph_list.append(node)
2087 # For each r(i) from (0 <= i < |R|-1)
2088 i = 0
2089 while i < (r_len-1):
2090 # Add an edge from r(i) to r(i+1) if r(i) is a full
2091 # replica or r(i+1) is a partial replica
2092 if not r_list[i].is_partial() or r_list[i+1].is_partial():
2093 graph_list[i+1].add_edge_from(r_list[i].rep_dsa_dnstr)
2095 # Add an edge from r(i+1) to r(i) if r(i+1) is a full
2096 # replica or ri is a partial replica.
2097 if not r_list[i+1].is_partial() or r_list[i].is_partial():
2098 graph_list[i].add_edge_from(r_list[i+1].rep_dsa_dnstr)
2099 i = i + 1
2101 # Add an edge from r|R|-1 to r0 if r|R|-1 is a full replica
2102 # or r0 is a partial replica.
2103 if not r_list[r_len-1].is_partial() or r_list[0].is_partial():
2104 graph_list[0].add_edge_from(r_list[r_len-1].rep_dsa_dnstr)
2106 # Add an edge from r0 to r|R|-1 if r0 is a full replica or
2107 # r|R|-1 is a partial replica.
2108 if not r_list[0].is_partial() or r_list[r_len-1].is_partial():
2109 graph_list[r_len-1].add_edge_from(r_list[0].rep_dsa_dnstr)
2111 DEBUG("r_list is length %s" % len(r_list))
2112 DEBUG('\n'.join(str((x.rep_dsa_guid, x.rep_dsa_dnstr))
2113 for x in r_list))
2115 do_dot_files = self.dot_file_dir is not None and self.debug
2116 if self.verify or do_dot_files:
2117 dot_edges = []
2118 dot_vertices = set()
2119 for v1 in graph_list:
2120 dot_vertices.add(v1.dsa_dnstr)
2121 for v2 in v1.edge_from:
2122 dot_edges.append((v2, v1.dsa_dnstr))
2123 dot_vertices.add(v2)
2125 verify_properties = ('connected',)
2126 verify_and_dot('intrasite_pre_ntdscon', dot_edges, dot_vertices,
2127 label='%s__%s__%s' % (site_local.site_dnstr,
2128 nctype_lut[nc_x.nc_type],
2129 nc_x.nc_dnstr),
2130 properties=verify_properties, debug=DEBUG,
2131 verify=self.verify,
2132 dot_file_dir=self.dot_file_dir,
2133 directed=True)
2135 rw_dot_vertices = set(x for x in dot_vertices
2136 if not self.get_dsa(x).is_ro())
2137 rw_dot_edges = [(a, b) for a, b in dot_edges if
2138 a in rw_dot_vertices and b in rw_dot_vertices]
2139 print rw_dot_edges, rw_dot_vertices
2140 rw_verify_properties = ('connected',
2141 'directed_double_ring_or_small')
2142 verify_and_dot('intrasite_rw_pre_ntdscon', rw_dot_edges,
2143 rw_dot_vertices,
2144 label='%s__%s__%s' % (site_local.site_dnstr,
2145 nctype_lut[nc_x.nc_type],
2146 nc_x.nc_dnstr),
2147 properties=rw_verify_properties, debug=DEBUG,
2148 verify=self.verify,
2149 dot_file_dir=self.dot_file_dir,
2150 directed=True)
2152 # For each existing nTDSConnection object implying an edge
2153 # from rj of R to ri such that j != i, an edge from rj to ri
2154 # is not already in the graph, and the total edges directed
2155 # to ri is less than n+2, the KCC adds that edge to the graph.
2156 for vertex in graph_list:
2157 dsa = self.my_site.dsa_table[vertex.dsa_dnstr]
2158 for connect in dsa.connect_table.values():
2159 remote = connect.from_dnstr
2160 if remote in self.my_site.dsa_table:
2161 vertex.add_edge_from(remote)
2163 DEBUG('reps are: %s' % ' '.join(x.rep_dsa_dnstr for x in r_list))
2164 DEBUG('dsas are: %s' % ' '.join(x.dsa_dnstr for x in graph_list))
2166 for tnode in graph_list:
2167 # To optimize replication latency in sites with many NC
2168 # replicas, the KCC adds new edges directed to ri to bring
2169 # the total edges to n+2, where the NC replica rk of R
2170 # from which the edge is directed is chosen at random such
2171 # that k != i and an edge from rk to ri is not already in
2172 # the graph.
2174 # Note that the KCC tech ref does not give a number for
2175 # the definition of "sites with many NC replicas". At a
2176 # bare minimum to satisfy n+2 edges directed at a node we
2177 # have to have at least three replicas in |R| (i.e. if n
2178 # is zero then at least replicas from two other graph
2179 # nodes may direct edges to us).
2180 if r_len >= 3 and not tnode.has_sufficient_edges():
2181 candidates = [x for x in graph_list if
2182 (x is not tnode and
2183 x.dsa_dnstr not in tnode.edge_from)]
2185 debug.DEBUG_BLUE("looking for random link for %s. r_len %d, "
2186 "graph len %d candidates %d"
2187 % (tnode.dsa_dnstr, r_len, len(graph_list),
2188 len(candidates)))
2190 DEBUG("candidates %s" % [x.dsa_dnstr for x in candidates])
2192 while candidates and not tnode.has_sufficient_edges():
2193 other = random.choice(candidates)
2194 DEBUG("trying to add candidate %s" % other.dsa_dstr)
2195 if not tnode.add_edge_from(other):
2196 debug.DEBUG_RED("could not add %s" % other.dsa_dstr)
2197 candidates.remove(other)
2198 else:
2199 DEBUG_FN("not adding links to %s: nodes %s, links is %s/%s" %
2200 (tnode.dsa_dnstr, r_len, len(tnode.edge_from),
2201 tnode.max_edges))
2203 # Print the graph node in debug mode
2204 DEBUG_FN("%s" % tnode)
2206 # For each edge directed to the local DC, ensure a nTDSConnection
2207 # points to us that satisfies the KCC criteria
2209 if tnode.dsa_dnstr == dc_local.dsa_dnstr:
2210 tnode.add_connections_from_edges(dc_local, self.ip_transport)
2212 if self.verify or do_dot_files:
2213 dot_edges = []
2214 dot_vertices = set()
2215 for v1 in graph_list:
2216 dot_vertices.add(v1.dsa_dnstr)
2217 for v2 in v1.edge_from:
2218 dot_edges.append((v2, v1.dsa_dnstr))
2219 dot_vertices.add(v2)
2221 verify_properties = ('connected',)
2222 verify_and_dot('intrasite_post_ntdscon', dot_edges, dot_vertices,
2223 label='%s__%s__%s' % (site_local.site_dnstr,
2224 nctype_lut[nc_x.nc_type],
2225 nc_x.nc_dnstr),
2226 properties=verify_properties, debug=DEBUG,
2227 verify=self.verify,
2228 dot_file_dir=self.dot_file_dir,
2229 directed=True)
2231 rw_dot_vertices = set(x for x in dot_vertices
2232 if not self.get_dsa(x).is_ro())
2233 rw_dot_edges = [(a, b) for a, b in dot_edges if
2234 a in rw_dot_vertices and b in rw_dot_vertices]
2235 print rw_dot_edges, rw_dot_vertices
2236 rw_verify_properties = ('connected',
2237 'directed_double_ring_or_small')
2238 verify_and_dot('intrasite_rw_post_ntdscon', rw_dot_edges,
2239 rw_dot_vertices,
2240 label='%s__%s__%s' % (site_local.site_dnstr,
2241 nctype_lut[nc_x.nc_type],
2242 nc_x.nc_dnstr),
2243 properties=rw_verify_properties, debug=DEBUG,
2244 verify=self.verify,
2245 dot_file_dir=self.dot_file_dir,
2246 directed=True)
2248 def intrasite(self):
2249 """Generate the intrasite KCC connections
2251 As per MS-ADTS 6.2.2.2.
2253 If self.readonly is False, the connections are added to self.samdb.
2255 After this call, all DCs in each site with more than 3 DCs
2256 should be connected in a bidirectional ring. If a site has 2
2257 DCs, they will bidirectionally connected. Sites with many DCs
2258 may have arbitrary extra connections.
2260 :return: None
2262 mydsa = self.my_dsa
2264 DEBUG_FN("intrasite(): enter")
2266 # Test whether local site has topology disabled
2267 mysite = self.my_site
2268 if mysite.is_intrasite_topology_disabled():
2269 return
2271 detect_stale = (not mysite.is_detect_stale_disabled())
2272 for connect in mydsa.connect_table.values():
2273 if connect.to_be_added:
2274 debug.DEBUG_CYAN("TO BE ADDED:\n%s" % connect)
2276 # Loop thru all the partitions, with gc_only False
2277 for partdn, part in self.part_table.items():
2278 self.construct_intrasite_graph(mysite, mydsa, part, False,
2279 detect_stale)
2280 for connect in mydsa.connect_table.values():
2281 if connect.to_be_added:
2282 debug.DEBUG_BLUE("TO BE ADDED:\n%s" % connect)
2284 # If the DC is a GC server, the KCC constructs an additional NC
2285 # replica graph (and creates nTDSConnection objects) for the
2286 # config NC as above, except that only NC replicas that "are present"
2287 # on GC servers are added to R.
2288 for connect in mydsa.connect_table.values():
2289 if connect.to_be_added:
2290 debug.DEBUG_YELLOW("TO BE ADDED:\n%s" % connect)
2292 # Do it again, with gc_only True
2293 for partdn, part in self.part_table.items():
2294 if part.is_config():
2295 self.construct_intrasite_graph(mysite, mydsa, part, True,
2296 detect_stale)
2298 # The DC repeats the NC replica graph computation and nTDSConnection
2299 # creation for each of the NC replica graphs, this time assuming
2300 # that no DC has failed. It does so by re-executing the steps as
2301 # if the bit NTDSSETTINGS_OPT_IS_TOPL_DETECT_STALE_DISABLED were
2302 # set in the options attribute of the site settings object for
2303 # the local DC's site. (ie. we set "detec_stale" flag to False)
2304 for connect in mydsa.connect_table.values():
2305 if connect.to_be_added:
2306 debug.DEBUG_BLUE("TO BE ADDED:\n%s" % connect)
2308 # Loop thru all the partitions.
2309 for partdn, part in self.part_table.items():
2310 self.construct_intrasite_graph(mysite, mydsa, part, False,
2311 False) # don't detect stale
2313 # If the DC is a GC server, the KCC constructs an additional NC
2314 # replica graph (and creates nTDSConnection objects) for the
2315 # config NC as above, except that only NC replicas that "are present"
2316 # on GC servers are added to R.
2317 for connect in mydsa.connect_table.values():
2318 if connect.to_be_added:
2319 debug.DEBUG_RED("TO BE ADDED:\n%s" % connect)
2321 for partdn, part in self.part_table.items():
2322 if part.is_config():
2323 self.construct_intrasite_graph(mysite, mydsa, part, True,
2324 False) # don't detect stale
2326 self._commit_changes(mydsa)
2328 def list_dsas(self):
2329 """Compile a comprehensive list of DSA DNs
2331 These are all the DSAs on all the sites that KCC would be
2332 dealing with.
2334 This method is not idempotent and may not work correctly in
2335 sequence with KCC.run().
2337 :return: a list of DSA DN strings.
2339 self.load_my_site()
2340 self.load_my_dsa()
2342 self.load_all_sites()
2343 self.load_all_partitions()
2344 self.load_ip_transport()
2345 self.load_all_sitelinks()
2346 dsas = []
2347 for site in self.site_table.values():
2348 dsas.extend([dsa.dsa_dnstr.replace('CN=NTDS Settings,', '', 1)
2349 for dsa in site.dsa_table.values()])
2350 return dsas
2352 def load_samdb(self, dburl, lp, creds, force=False):
2353 """Load the database using an url, loadparm, and credentials
2355 If force is False, the samdb won't be reloaded if it already
2356 exists.
2358 :param dburl: a database url.
2359 :param lp: a loadparm object.
2360 :param creds: a Credentials object.
2361 :param force: a boolean indicating whether to overwrite.
2364 if self.samdb is not None and not force:
2365 return
2367 self.samdb = SamDB(url=dburl,
2368 session_info=system_session(),
2369 credentials=creds, lp=lp)
2371 def plot_all_connections(self, basename, verify_properties=()):
2372 """Helper function to plot and verify NTDSConnections
2374 :param basename: an identifying string to use in filenames and logs.
2375 :param verify_properties: properties to verify (default empty)
2377 verify = verify_properties and self.verify
2378 if not verify and self.dot_file_dir is None:
2379 return
2381 dot_edges = []
2382 dot_vertices = []
2383 edge_colours = []
2384 vertex_colours = []
2386 for dsa in self.dsa_by_dnstr.values():
2387 dot_vertices.append(dsa.dsa_dnstr)
2388 if dsa.is_ro():
2389 vertex_colours.append('#cc0000')
2390 else:
2391 vertex_colours.append('#0000cc')
2392 for con in dsa.connect_table.values():
2393 if con.is_rodc_topology():
2394 edge_colours.append('red')
2395 else:
2396 edge_colours.append('blue')
2397 dot_edges.append((con.from_dnstr, dsa.dsa_dnstr))
2399 verify_and_dot(basename, dot_edges, vertices=dot_vertices,
2400 label=self.my_dsa_dnstr,
2401 properties=verify_properties, debug=DEBUG,
2402 verify=verify, dot_file_dir=self.dot_file_dir,
2403 directed=True, edge_colors=edge_colours,
2404 vertex_colors=vertex_colours)
2406 def run(self, dburl, lp, creds, forced_local_dsa=None,
2407 forget_local_links=False, forget_intersite_links=False,
2408 attempt_live_connections=False):
2409 """Perform a KCC run, possibly updating repsFrom topology
2411 :param dburl: url of the database to work with.
2412 :param lp: a loadparm object.
2413 :param creds: a Credentials object.
2414 :param forced_local_dsa: pretend to be on the DSA with this dn_str
2415 :param forget_local_links: calculate as if no connections existed
2416 (boolean, default False)
2417 :param forget_intersite_links: calculate with only intrasite connection
2418 (boolean, default False)
2419 :param attempt_live_connections: attempt to connect to remote DSAs to
2420 determine link availability (boolean, default False)
2421 :return: 1 on error, 0 otherwise
2423 try:
2424 self.load_samdb(dburl, lp, creds, force=False)
2425 except ldb.LdbError, (num, msg):
2426 logger.error("Unable to open sam database %s : %s" %
2427 (dburl, msg))
2428 return 1
2430 if forced_local_dsa:
2431 self.samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" %
2432 forced_local_dsa)
2434 try:
2435 # Setup
2436 self.load_my_site()
2437 self.load_my_dsa()
2439 self.load_all_sites()
2440 self.load_all_partitions()
2441 self.load_ip_transport()
2442 self.load_all_sitelinks()
2444 if self.verify or self.dot_file_dir is not None:
2445 guid_to_dnstr = {}
2446 for site in self.site_table.values():
2447 guid_to_dnstr.update((str(dsa.dsa_guid), dnstr)
2448 for dnstr, dsa
2449 in site.dsa_table.items())
2451 self.plot_all_connections('dsa_initial')
2453 dot_edges = []
2454 current_reps, needed_reps = self.my_dsa.get_rep_tables()
2455 for dnstr, c_rep in current_reps.items():
2456 DEBUG("c_rep %s" % c_rep)
2457 dot_edges.append((self.my_dsa.dsa_dnstr, dnstr))
2459 verify_and_dot('dsa_repsFrom_initial', dot_edges,
2460 directed=True, label=self.my_dsa_dnstr,
2461 properties=(), debug=DEBUG, verify=self.verify,
2462 dot_file_dir=self.dot_file_dir)
2464 dot_edges = []
2465 for site in self.site_table.values():
2466 for dsa in site.dsa_table.values():
2467 current_reps, needed_reps = dsa.get_rep_tables()
2468 for dn_str, rep in current_reps.items():
2469 for reps_from in rep.rep_repsFrom:
2470 DEBUG("rep %s" % rep)
2471 dsa_guid = str(reps_from.source_dsa_obj_guid)
2472 dsa_dn = guid_to_dnstr[dsa_guid]
2473 dot_edges.append((dsa.dsa_dnstr, dsa_dn))
2475 verify_and_dot('dsa_repsFrom_initial_all', dot_edges,
2476 directed=True, label=self.my_dsa_dnstr,
2477 properties=(), debug=DEBUG, verify=self.verify,
2478 dot_file_dir=self.dot_file_dir)
2480 dot_edges = []
2481 for link in self.sitelink_table.values():
2482 for a, b in itertools.combinations(link.site_list, 2):
2483 dot_edges.append((str(a), str(b)))
2484 properties = ('connected',)
2485 verify_and_dot('dsa_sitelink_initial', dot_edges,
2486 directed=False,
2487 label=self.my_dsa_dnstr, properties=properties,
2488 debug=DEBUG, verify=self.verify,
2489 dot_file_dir=self.dot_file_dir)
2491 if forget_local_links:
2492 for dsa in self.my_site.dsa_table.values():
2493 dsa.connect_table = dict((k, v) for k, v in
2494 dsa.connect_table.items()
2495 if v.is_rodc_topology() or
2496 (v.from_dnstr not in
2497 self.my_site.dsa_table))
2498 self.plot_all_connections('dsa_forgotten_local')
2500 if forget_intersite_links:
2501 for site in self.site_table.values():
2502 for dsa in site.dsa_table.values():
2503 dsa.connect_table = dict((k, v) for k, v in
2504 dsa.connect_table.items()
2505 if site is self.my_site and
2506 v.is_rodc_topology())
2508 self.plot_all_connections('dsa_forgotten_all')
2510 if attempt_live_connections:
2511 # Encapsulates lp and creds in a function that
2512 # attempts connections to remote DSAs.
2513 def ping(self, dnsname):
2514 try:
2515 drs_utils.drsuapi_connect(dnsname, self.lp, self.creds)
2516 except drs_utils.drsException:
2517 return False
2518 return True
2519 else:
2520 ping = None
2521 # These are the published steps (in order) for the
2522 # MS-TECH description of the KCC algorithm ([MS-ADTS] 6.2.2)
2524 # Step 1
2525 self.refresh_failed_links_connections(ping)
2527 # Step 2
2528 self.intrasite()
2530 # Step 3
2531 all_connected = self.intersite(ping)
2533 # Step 4
2534 self.remove_unneeded_ntdsconn(all_connected)
2536 # Step 5
2537 self.translate_ntdsconn()
2539 # Step 6
2540 self.remove_unneeded_failed_links_connections()
2542 # Step 7
2543 self.update_rodc_connection()
2545 if self.verify or self.dot_file_dir is not None:
2546 self.plot_all_connections('dsa_final',
2547 ('connected',))
2549 debug.DEBUG_MAGENTA("there are %d dsa guids" %
2550 len(guid_to_dnstr))
2552 dot_edges = []
2553 edge_colors = []
2554 my_dnstr = self.my_dsa.dsa_dnstr
2555 current_reps, needed_reps = self.my_dsa.get_rep_tables()
2556 for dnstr, n_rep in needed_reps.items():
2557 for reps_from in n_rep.rep_repsFrom:
2558 guid_str = str(reps_from.source_dsa_obj_guid)
2559 dot_edges.append((my_dnstr, guid_to_dnstr[guid_str]))
2560 edge_colors.append('#' + str(n_rep.nc_guid)[:6])
2562 verify_and_dot('dsa_repsFrom_final', dot_edges, directed=True,
2563 label=self.my_dsa_dnstr,
2564 properties=(), debug=DEBUG, verify=self.verify,
2565 dot_file_dir=self.dot_file_dir,
2566 edge_colors=edge_colors)
2568 dot_edges = []
2570 for site in self.site_table.values():
2571 for dsa in site.dsa_table.values():
2572 current_reps, needed_reps = dsa.get_rep_tables()
2573 for n_rep in needed_reps.values():
2574 for reps_from in n_rep.rep_repsFrom:
2575 dsa_guid = str(reps_from.source_dsa_obj_guid)
2576 dsa_dn = guid_to_dnstr[dsa_guid]
2577 dot_edges.append((dsa.dsa_dnstr, dsa_dn))
2579 verify_and_dot('dsa_repsFrom_final_all', dot_edges,
2580 directed=True, label=self.my_dsa_dnstr,
2581 properties=(), debug=DEBUG, verify=self.verify,
2582 dot_file_dir=self.dot_file_dir)
2584 except:
2585 raise
2587 return 0
2589 def import_ldif(self, dburl, lp, creds, ldif_file, forced_local_dsa=None):
2590 """Import relevant objects and attributes from an LDIF file.
2592 The point of this function is to allow a programmer/debugger to
2593 import an LDIF file with non-security relevent information that
2594 was previously extracted from a DC database. The LDIF file is used
2595 to create a temporary abbreviated database. The KCC algorithm can
2596 then run against this abbreviated database for debug or test
2597 verification that the topology generated is computationally the
2598 same between different OSes and algorithms.
2600 :param dburl: path to the temporary abbreviated db to create
2601 :param lp: a loadparm object.
2602 :param cred: a Credentials object.
2603 :param ldif_file: path to the ldif file to import
2604 :param forced_local_dsa: perform KCC from this DSA's point of view
2605 :return: zero on success, 1 on error
2607 try:
2608 self.samdb = ldif_import_export.ldif_to_samdb(dburl, lp, ldif_file,
2609 forced_local_dsa)
2610 except ldif_import_export.LdifError, e:
2611 logger.critical(e)
2612 return 1
2613 return 0
2615 def export_ldif(self, dburl, lp, creds, ldif_file):
2616 """Save KCC relevant details to an ldif file
2618 The point of this function is to allow a programmer/debugger to
2619 extract an LDIF file with non-security relevent information from
2620 a DC database. The LDIF file can then be used to "import" via
2621 the import_ldif() function this file into a temporary abbreviated
2622 database. The KCC algorithm can then run against this abbreviated
2623 database for debug or test verification that the topology generated
2624 is computationally the same between different OSes and algorithms.
2626 :param dburl: LDAP database URL to extract info from
2627 :param lp: a loadparm object.
2628 :param cred: a Credentials object.
2629 :param ldif_file: output LDIF file name to create
2630 :return: zero on success, 1 on error
2632 try:
2633 ldif_import_export.samdb_to_ldif_file(self.samdb, dburl, lp, creds,
2634 ldif_file)
2635 except ldif_import_export.LdifError, e:
2636 logger.critical(e)
2637 return 1
2638 return 0