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/>.
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.
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
67 :param dsa1: A DSA object
68 :param dsa2: Another DSA
69 :return: -1, 0, or 1, indicating sort order.
71 if dsa1
.is_gc() and not dsa2
.is_gc():
73 if not dsa1
.is_gc() and dsa2
.is_gc():
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)
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,
104 """Initializes the partitions class which can hold
105 our local DCs partitions or all the partitions in
108 self
.part_table
= {} # partition objects
110 self
.transport_table
= {}
111 self
.ip_transport
= None
112 self
.sitelink_table
= {}
113 self
.dsa_by_dnstr
= {}
114 self
.dsa_by_guid
= {}
116 self
.get_dsa_by_guidstr
= self
.dsa_by_guid
.get
117 self
.get_dsa
= self
.dsa_by_dnstr
.get
119 # TODO: These should be backed by a 'permanent' store so that when
120 # calling DRSGetReplInfo with DS_REPL_INFO_KCC_DSA_CONNECT_FAILURES,
121 # the failure information can be returned
122 self
.kcc_failed_links
= {}
123 self
.kcc_failed_connections
= set()
125 # Used in inter-site topology computation. A list
126 # of connections (by NTDSConnection object) that are
127 # to be kept when pruning un-needed NTDS Connections
128 self
.kept_connections
= set()
130 self
.my_dsa_dnstr
= None # My dsa DN
131 self
.my_dsa
= None # My dsa object
133 self
.my_site_dnstr
= None
138 self
.unix_now
= unix_now
139 self
.nt_now
= unix2nttime(unix_now
)
140 self
.readonly
= readonly
143 self
.dot_file_dir
= dot_file_dir
145 def load_all_transports(self
):
146 """Loads the inter-site transport objects for Sites
149 :raise KCCError: if no IP transport is found
152 res
= self
.samdb
.search("CN=Inter-Site Transports,CN=Sites,%s" %
153 self
.samdb
.get_config_basedn(),
154 scope
=ldb
.SCOPE_SUBTREE
,
155 expression
="(objectClass=interSiteTransport)")
156 except ldb
.LdbError
, (enum
, estr
):
157 raise KCCError("Unable to find inter-site transports - (%s)" %
163 transport
= Transport(dnstr
)
165 transport
.load_transport(self
.samdb
)
166 self
.transport_table
.setdefault(str(transport
.guid
),
168 if transport
.name
== 'IP':
169 self
.ip_transport
= transport
171 if self
.ip_transport
is None:
172 raise KCCError("there doesn't seem to be an IP transport")
174 def load_all_sitelinks(self
):
175 """Loads the inter-site siteLink objects
178 :raise KCCError: if site-links aren't found
181 res
= self
.samdb
.search("CN=Inter-Site Transports,CN=Sites,%s" %
182 self
.samdb
.get_config_basedn(),
183 scope
=ldb
.SCOPE_SUBTREE
,
184 expression
="(objectClass=siteLink)")
185 except ldb
.LdbError
, (enum
, estr
):
186 raise KCCError("Unable to find inter-site siteLinks - (%s)" % estr
)
192 if dnstr
in self
.sitelink_table
:
195 sitelink
= SiteLink(dnstr
)
197 sitelink
.load_sitelink(self
.samdb
)
199 # Assign this siteLink to table
201 self
.sitelink_table
[dnstr
] = sitelink
203 def load_site(self
, dn_str
):
204 """Helper for load_my_site and load_all_sites.
206 Put all the site's DSAs into the KCC indices.
208 :param dn_str: a site dn_str
209 :return: the Site object pertaining to the dn_str
211 site
= Site(dn_str
, self
.unix_now
)
212 site
.load_site(self
.samdb
)
214 # We avoid replacing the site with an identical copy in case
215 # somewhere else has a reference to the old one, which would
216 # lead to all manner of confusion and chaos.
217 guid
= str(site
.site_guid
)
218 if guid
not in self
.site_table
:
219 self
.site_table
[guid
] = site
220 self
.dsa_by_dnstr
.update(site
.dsa_table
)
221 self
.dsa_by_guid
.update((str(x
.dsa_guid
), x
)
222 for x
in site
.dsa_table
.values())
224 return self
.site_table
[guid
]
226 def load_my_site(self
):
227 """Load the Site object for the local DSA.
231 self
.my_site_dnstr
= ("CN=%s,CN=Sites,%s" % (
232 self
.samdb
.server_site_name(),
233 self
.samdb
.get_config_basedn()))
235 self
.my_site
= self
.load_site(self
.my_site_dnstr
)
237 def load_all_sites(self
):
238 """Discover all sites and create Site objects.
241 :raise: KCCError if sites can't be found
244 res
= self
.samdb
.search("CN=Sites,%s" %
245 self
.samdb
.get_config_basedn(),
246 scope
=ldb
.SCOPE_SUBTREE
,
247 expression
="(objectClass=site)")
248 except ldb
.LdbError
, (enum
, estr
):
249 raise KCCError("Unable to find sites - (%s)" % estr
)
252 sitestr
= str(msg
.dn
)
253 self
.load_site(sitestr
)
255 def load_my_dsa(self
):
256 """Discover my nTDSDSA dn thru the rootDSE entry
259 :raise: KCCError if DSA can't be found
261 dn
= ldb
.Dn(self
.samdb
, "<GUID=%s>" % self
.samdb
.get_ntds_GUID())
263 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
,
264 attrs
=["objectGUID"])
265 except ldb
.LdbError
, (enum
, estr
):
266 logger
.warning("Search for %s failed: %s. This typically happens"
267 " in --importldif mode due to lack of module"
268 " support.", dn
, estr
)
270 # We work around the failure above by looking at the
271 # dsServiceName that was put in the fake rootdse by
272 # the --exportldif, rather than the
273 # samdb.get_ntds_GUID(). The disadvantage is that this
274 # mode requires we modify the @ROOTDSE dnq to support
276 service_name_res
= self
.samdb
.search(base
="",
277 scope
=ldb
.SCOPE_BASE
,
278 attrs
=["dsServiceName"])
279 dn
= ldb
.Dn(self
.samdb
,
280 service_name_res
[0]["dsServiceName"][0])
282 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
,
283 attrs
=["objectGUID"])
284 except ldb
.LdbError
, (enum
, estr
):
285 raise KCCError("Unable to find my nTDSDSA - (%s)" % estr
)
288 raise KCCError("Unable to find my nTDSDSA at %s" %
291 ntds_guid
= misc
.GUID(self
.samdb
.get_ntds_GUID())
292 if misc
.GUID(res
[0]["objectGUID"][0]) != ntds_guid
:
293 raise KCCError("Did not find the GUID we expected,"
294 " perhaps due to --importldif")
296 self
.my_dsa_dnstr
= str(res
[0].dn
)
298 self
.my_dsa
= self
.my_site
.get_dsa(self
.my_dsa_dnstr
)
300 if self
.my_dsa_dnstr
not in self
.dsa_by_dnstr
:
301 debug
.DEBUG_DARK_YELLOW("my_dsa %s isn't in self.dsas_by_dnstr:"
302 " it must be RODC.\n"
303 "Let's add it, because my_dsa is special!"
304 "\n(likewise for self.dsa_by_guid)" %
307 self
.dsa_by_dnstr
[self
.my_dsa_dnstr
] = self
.my_dsa
308 self
.dsa_by_guid
[str(self
.my_dsa
.dsa_guid
)] = self
.my_dsa
310 def load_all_partitions(self
):
311 """Discover and load all partitions.
313 Each NC is inserted into the part_table by partition
314 dn string (not the nCName dn string)
317 :raise: KCCError if partitions can't be found
320 res
= self
.samdb
.search("CN=Partitions,%s" %
321 self
.samdb
.get_config_basedn(),
322 scope
=ldb
.SCOPE_SUBTREE
,
323 expression
="(objectClass=crossRef)")
324 except ldb
.LdbError
, (enum
, estr
):
325 raise KCCError("Unable to find partitions - (%s)" % estr
)
328 partstr
= str(msg
.dn
)
331 if partstr
in self
.part_table
:
334 part
= Partition(partstr
)
336 part
.load_partition(self
.samdb
)
337 self
.part_table
[partstr
] = part
339 def refresh_failed_links_connections(self
, ping
=None):
340 """Ensure the failed links list is up to date
342 Based on MS-ADTS 6.2.2.1
344 :param ping: An oracle function of remote site availability
347 # LINKS: Refresh failed links
348 self
.kcc_failed_links
= {}
349 current
, needed
= self
.my_dsa
.get_rep_tables()
350 for replica
in current
.values():
351 # For every possible connection to replicate
352 for reps_from
in replica
.rep_repsFrom
:
353 failure_count
= reps_from
.consecutive_sync_failures
354 if failure_count
<= 0:
357 dsa_guid
= str(reps_from
.source_dsa_obj_guid
)
358 time_first_failure
= reps_from
.last_success
359 last_result
= reps_from
.last_attempt
360 dns_name
= reps_from
.dns_name1
362 f
= self
.kcc_failed_links
.get(dsa_guid
)
364 f
= KCCFailedObject(dsa_guid
, failure_count
,
365 time_first_failure
, last_result
,
367 self
.kcc_failed_links
[dsa_guid
] = f
369 f
.failure_count
= max(f
.failure_count
, failure_count
)
370 f
.time_first_failure
= min(f
.time_first_failure
,
372 f
.last_result
= last_result
374 # CONNECTIONS: Refresh failed connections
375 restore_connections
= set()
377 DEBUG("refresh_failed_links: checking if links are still down")
378 for connection
in self
.kcc_failed_connections
:
379 if ping(connection
.dns_name
):
380 # Failed connection is no longer failing
381 restore_connections
.add(connection
)
383 connection
.failure_count
+= 1
385 DEBUG("refresh_failed_links: not checking live links because we\n"
386 "weren't asked to --attempt-live-connections")
388 # Remove the restored connections from the failed connections
389 self
.kcc_failed_connections
.difference_update(restore_connections
)
391 def is_stale_link_connection(self
, target_dsa
):
392 """Check whether a link to a remote DSA is stale
394 Used in MS-ADTS 6.2.2.2 Intrasite Connection Creation
396 Returns True if the remote seems to have been down for at
397 least two hours, otherwise False.
399 :param target_dsa: the remote DSA object
400 :return: True if link is stale, otherwise False
402 failed_link
= self
.kcc_failed_links
.get(str(target_dsa
.dsa_guid
))
404 # failure_count should be > 0, but check anyways
405 if failed_link
.failure_count
> 0:
406 unix_first_failure
= \
407 nttime2unix(failed_link
.time_first_failure
)
408 # TODO guard against future
409 if unix_first_failure
> self
.unix_now
:
410 logger
.error("The last success time attribute for \
411 repsFrom is in the future!")
413 # Perform calculation in seconds
414 if (self
.unix_now
- unix_first_failure
) > 60 * 60 * 2:
418 # We have checked failed *links*, but we also need to check
423 # TODO: This should be backed by some form of local database
424 def remove_unneeded_failed_links_connections(self
):
425 # Remove all tuples in kcc_failed_links where failure count = 0
426 # In this implementation, this should never happen.
428 # Remove all connections which were not used this run or connections
429 # that became active during this run.
432 def remove_unneeded_ntdsconn(self
, all_connected
):
433 """Remove unneeded NTDS Connections once topology is calculated
435 Based on MS-ADTS 6.2.2.4 Removing Unnecessary Connections
437 :param all_connected: indicates whether all sites are connected
442 # New connections won't have GUIDs which are needed for
444 for cn_conn
in mydsa
.connect_table
.values():
445 if cn_conn
.guid
is None:
447 cn_conn
.guid
= misc
.GUID(str(uuid
.uuid4()))
448 cn_conn
.whenCreated
= self
.nt_now
450 cn_conn
.load_connection(self
.samdb
)
452 for cn_conn
in mydsa
.connect_table
.values():
454 s_dnstr
= cn_conn
.get_from_dnstr()
456 cn_conn
.to_be_deleted
= True
459 #XXX should an RODC be regarded as same site
460 same_site
= s_dnstr
in self
.my_site
.dsa_table
462 # Given an nTDSConnection object cn, if the DC with the
463 # nTDSDSA object dc that is the parent object of cn and
464 # the DC with the nTDSDA object referenced by cn!fromServer
465 # are in the same site, the KCC on dc deletes cn if all of
466 # the following are true:
468 # Bit NTDSCONN_OPT_IS_GENERATED is clear in cn!options.
470 # No site settings object s exists for the local DC's site, or
471 # bit NTDSSETTINGS_OPT_IS_TOPL_CLEANUP_DISABLED is clear in
474 # Another nTDSConnection object cn2 exists such that cn and
475 # cn2 have the same parent object, cn!fromServer = cn2!fromServer,
478 # cn!whenCreated < cn2!whenCreated
480 # cn!whenCreated = cn2!whenCreated and
481 # cn!objectGUID < cn2!objectGUID
483 # Bit NTDSCONN_OPT_RODC_TOPOLOGY is clear in cn!options
485 if not cn_conn
.is_generated():
488 if self
.my_site
.is_cleanup_ntdsconn_disabled():
491 # Loop thru connections looking for a duplicate that
492 # fulfills the previous criteria
494 packed_guid
= ndr_pack(cn_conn
.guid
)
495 for cn2_conn
in mydsa
.connect_table
.values():
496 if cn2_conn
is cn_conn
:
499 s2_dnstr
= cn2_conn
.get_from_dnstr()
501 # If the NTDS Connections has a different
502 # fromServer field then no match
503 if s2_dnstr
!= s_dnstr
:
507 lesser
= (cn_conn
.whenCreated
< cn2_conn
.whenCreated
or
508 (cn_conn
.whenCreated
== cn2_conn
.whenCreated
and
509 packed_guid
< ndr_pack(cn2_conn
.guid
)))
514 if lesser
and not cn_conn
.is_rodc_topology():
515 cn_conn
.to_be_deleted
= True
517 # Given an nTDSConnection object cn, if the DC with the nTDSDSA
518 # object dc that is the parent object of cn and the DC with
519 # the nTDSDSA object referenced by cn!fromServer are in
520 # different sites, a KCC acting as an ISTG in dc's site
521 # deletes cn if all of the following are true:
523 # Bit NTDSCONN_OPT_IS_GENERATED is clear in cn!options.
525 # cn!fromServer references an nTDSDSA object for a DC
526 # in a site other than the local DC's site.
528 # The keepConnections sequence returned by
529 # CreateIntersiteConnections() does not contain
530 # cn!objectGUID, or cn is "superseded by" (see below)
531 # another nTDSConnection cn2 and keepConnections
532 # contains cn2!objectGUID.
534 # The return value of CreateIntersiteConnections()
537 # Bit NTDSCONN_OPT_RODC_TOPOLOGY is clear in
540 else: # different site
542 if not mydsa
.is_istg():
545 if not cn_conn
.is_generated():
549 # We are directly using this connection in intersite or
550 # we are using a connection which can supersede this one.
552 # MS-ADTS 6.2.2.4 - Removing Unnecessary Connections does not
553 # appear to be correct.
555 # 1. cn!fromServer and cn!parent appear inconsistent with
557 # 2. The repsFrom do not imply each other
559 if cn_conn
in self
.kept_connections
: # and not_superceded:
562 # This is the result of create_intersite_connections
563 if not all_connected
:
566 if not cn_conn
.is_rodc_topology():
567 cn_conn
.to_be_deleted
= True
569 if mydsa
.is_ro() or self
.readonly
:
570 for connect
in mydsa
.connect_table
.values():
571 if connect
.to_be_deleted
:
572 DEBUG_FN("TO BE DELETED:\n%s" % connect
)
573 if connect
.to_be_added
:
574 DEBUG_FN("TO BE ADDED:\n%s" % connect
)
576 # Peform deletion from our tables but perform
577 # no database modification
578 mydsa
.commit_connections(self
.samdb
, ro
=True)
580 # Commit any modified connections
581 mydsa
.commit_connections(self
.samdb
)
583 def modify_repsFrom(self
, n_rep
, t_repsFrom
, s_rep
, s_dsa
, cn_conn
):
584 """Update an repsFrom object if required.
586 Part of MS-ADTS 6.2.2.5.
588 Update t_repsFrom if necessary to satisfy requirements. Such
589 updates are typically required when the IDL_DRSGetNCChanges
590 server has moved from one site to another--for example, to
591 enable compression when the server is moved from the
592 client's site to another site.
594 The repsFrom.update_flags bit field may be modified
595 auto-magically if any changes are made here. See
596 kcc_utils.RepsFromTo for gory details.
599 :param n_rep: NC replica we need
600 :param t_repsFrom: repsFrom tuple to modify
601 :param s_rep: NC replica at source DSA
602 :param s_dsa: source DSA
603 :param cn_conn: Local DSA NTDSConnection child
607 s_dnstr
= s_dsa
.dsa_dnstr
608 same_site
= s_dnstr
in self
.my_site
.dsa_table
610 # if schedule doesn't match then update and modify
611 times
= convert_schedule_to_repltimes(cn_conn
.schedule
)
612 if times
!= t_repsFrom
.schedule
:
613 t_repsFrom
.schedule
= times
615 # Bit DRS_PER_SYNC is set in replicaFlags if and only
616 # if nTDSConnection schedule has a value v that specifies
617 # scheduled replication is to be performed at least once
619 if cn_conn
.is_schedule_minimum_once_per_week():
621 if ((t_repsFrom
.replica_flags
&
622 drsuapi
.DRSUAPI_DRS_PER_SYNC
) == 0x0):
623 t_repsFrom
.replica_flags |
= drsuapi
.DRSUAPI_DRS_PER_SYNC
625 # Bit DRS_INIT_SYNC is set in t.replicaFlags if and only
626 # if the source DSA and the local DC's nTDSDSA object are
627 # in the same site or source dsa is the FSMO role owner
628 # of one or more FSMO roles in the NC replica.
629 if same_site
or n_rep
.is_fsmo_role_owner(s_dnstr
):
631 if ((t_repsFrom
.replica_flags
&
632 drsuapi
.DRSUAPI_DRS_INIT_SYNC
) == 0x0):
633 t_repsFrom
.replica_flags |
= drsuapi
.DRSUAPI_DRS_INIT_SYNC
635 # If bit NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT is set in
636 # cn!options, bit DRS_NEVER_NOTIFY is set in t.replicaFlags
637 # if and only if bit NTDSCONN_OPT_USE_NOTIFY is clear in
638 # cn!options. Otherwise, bit DRS_NEVER_NOTIFY is set in
639 # t.replicaFlags if and only if s and the local DC's
640 # nTDSDSA object are in different sites.
641 if ((cn_conn
.options
&
642 dsdb
.NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT
) != 0x0):
644 if (cn_conn
.options
& dsdb
.NTDSCONN_OPT_USE_NOTIFY
) == 0x0:
647 # it LOOKS as if this next test is a bit silly: it
648 # checks the flag then sets it if it not set; the same
649 # effect could be achieved by unconditionally setting
650 # it. But in fact the repsFrom object has special
651 # magic attached to it, and altering replica_flags has
652 # side-effects. That is bad in my opinion, but there
654 if ((t_repsFrom
.replica_flags
&
655 drsuapi
.DRSUAPI_DRS_NEVER_NOTIFY
) == 0x0):
656 t_repsFrom
.replica_flags |
= \
657 drsuapi
.DRSUAPI_DRS_NEVER_NOTIFY
661 if ((t_repsFrom
.replica_flags
&
662 drsuapi
.DRSUAPI_DRS_NEVER_NOTIFY
) == 0x0):
663 t_repsFrom
.replica_flags |
= drsuapi
.DRSUAPI_DRS_NEVER_NOTIFY
665 # Bit DRS_USE_COMPRESSION is set in t.replicaFlags if
666 # and only if s and the local DC's nTDSDSA object are
667 # not in the same site and the
668 # NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION bit is
669 # clear in cn!options
670 if (not same_site
and
672 dsdb
.NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION
) == 0x0):
674 if ((t_repsFrom
.replica_flags
&
675 drsuapi
.DRSUAPI_DRS_USE_COMPRESSION
) == 0x0):
676 t_repsFrom
.replica_flags |
= drsuapi
.DRSUAPI_DRS_USE_COMPRESSION
678 # Bit DRS_TWOWAY_SYNC is set in t.replicaFlags if and only
679 # if bit NTDSCONN_OPT_TWOWAY_SYNC is set in cn!options.
680 if (cn_conn
.options
& dsdb
.NTDSCONN_OPT_TWOWAY_SYNC
) != 0x0:
682 if ((t_repsFrom
.replica_flags
&
683 drsuapi
.DRSUAPI_DRS_TWOWAY_SYNC
) == 0x0):
684 t_repsFrom
.replica_flags |
= drsuapi
.DRSUAPI_DRS_TWOWAY_SYNC
686 # Bits DRS_DISABLE_AUTO_SYNC and DRS_DISABLE_PERIODIC_SYNC are
687 # set in t.replicaFlags if and only if cn!enabledConnection = false.
688 if not cn_conn
.is_enabled():
690 if ((t_repsFrom
.replica_flags
&
691 drsuapi
.DRSUAPI_DRS_DISABLE_AUTO_SYNC
) == 0x0):
692 t_repsFrom
.replica_flags |
= \
693 drsuapi
.DRSUAPI_DRS_DISABLE_AUTO_SYNC
695 if ((t_repsFrom
.replica_flags
&
696 drsuapi
.DRSUAPI_DRS_DISABLE_PERIODIC_SYNC
) == 0x0):
697 t_repsFrom
.replica_flags |
= \
698 drsuapi
.DRSUAPI_DRS_DISABLE_PERIODIC_SYNC
700 # If s and the local DC's nTDSDSA object are in the same site,
701 # cn!transportType has no value, or the RDN of cn!transportType
704 # Bit DRS_MAIL_REP in t.replicaFlags is clear.
706 # t.uuidTransport = NULL GUID.
708 # t.uuidDsa = The GUID-based DNS name of s.
712 # Bit DRS_MAIL_REP in t.replicaFlags is set.
714 # If x is the object with dsname cn!transportType,
715 # t.uuidTransport = x!objectGUID.
717 # Let a be the attribute identified by
718 # x!transportAddressAttribute. If a is
719 # the dNSHostName attribute, t.uuidDsa = the GUID-based
720 # DNS name of s. Otherwise, t.uuidDsa = (s!parent)!a.
722 # It appears that the first statement i.e.
724 # "If s and the local DC's nTDSDSA object are in the same
725 # site, cn!transportType has no value, or the RDN of
726 # cn!transportType is CN=IP:"
728 # could be a slightly tighter statement if it had an "or"
729 # between each condition. I believe this should
732 # IF (same-site) OR (no-value) OR (type-ip)
734 # because IP should be the primary transport mechanism
735 # (even in inter-site) and the absense of the transportType
736 # attribute should always imply IP no matter if its multi-site
738 # NOTE MS-TECH INCORRECT:
740 # All indications point to these statements above being
741 # incorrectly stated:
743 # t.uuidDsa = The GUID-based DNS name of s.
745 # Let a be the attribute identified by
746 # x!transportAddressAttribute. If a is
747 # the dNSHostName attribute, t.uuidDsa = the GUID-based
748 # DNS name of s. Otherwise, t.uuidDsa = (s!parent)!a.
750 # because the uuidDSA is a GUID and not a GUID-base DNS
751 # name. Nor can uuidDsa hold (s!parent)!a if not
752 # dNSHostName. What should have been said is:
754 # t.naDsa = The GUID-based DNS name of s
756 # That would also be correct if transportAddressAttribute
757 # were "mailAddress" because (naDsa) can also correctly
758 # hold the SMTP ISM service address.
760 nastr
= "%s._msdcs.%s" % (s_dsa
.dsa_guid
, self
.samdb
.forest_dns_name())
762 # We're not currently supporting SMTP replication
763 # so is_smtp_replication_available() is currently
764 # always returning False
766 cn_conn
.transport_dnstr
is None or
767 cn_conn
.transport_dnstr
.find("CN=IP") == 0 or
768 not is_smtp_replication_available())):
770 if ((t_repsFrom
.replica_flags
&
771 drsuapi
.DRSUAPI_DRS_MAIL_REP
) != 0x0):
772 t_repsFrom
.replica_flags
&= ~drsuapi
.DRSUAPI_DRS_MAIL_REP
774 t_repsFrom
.transport_guid
= misc
.GUID()
776 # See (NOTE MS-TECH INCORRECT) above
777 if t_repsFrom
.version
== 0x1:
778 if t_repsFrom
.dns_name1
is None or \
779 t_repsFrom
.dns_name1
!= nastr
:
780 t_repsFrom
.dns_name1
= nastr
782 if t_repsFrom
.dns_name1
is None or \
783 t_repsFrom
.dns_name2
is None or \
784 t_repsFrom
.dns_name1
!= nastr
or \
785 t_repsFrom
.dns_name2
!= nastr
:
786 t_repsFrom
.dns_name1
= nastr
787 t_repsFrom
.dns_name2
= nastr
790 # XXX This entire branch is NEVER used! Because we don't do SMTP!
791 # (see the if condition above). Just close your eyes here.
792 if ((t_repsFrom
.replica_flags
&
793 drsuapi
.DRSUAPI_DRS_MAIL_REP
) == 0x0):
794 t_repsFrom
.replica_flags |
= drsuapi
.DRSUAPI_DRS_MAIL_REP
796 # We have a transport type but its not an
797 # object in the database
798 if cn_conn
.transport_guid
not in self
.transport_table
:
799 raise KCCError("Missing inter-site transport - (%s)" %
800 cn_conn
.transport_dnstr
)
802 x_transport
= self
.transport_table
[str(cn_conn
.transport_guid
)]
804 if t_repsFrom
.transport_guid
!= x_transport
.guid
:
805 t_repsFrom
.transport_guid
= x_transport
.guid
807 # See (NOTE MS-TECH INCORRECT) above
808 if x_transport
.address_attr
== "dNSHostName":
810 if t_repsFrom
.version
== 0x1:
811 if t_repsFrom
.dns_name1
is None or \
812 t_repsFrom
.dns_name1
!= nastr
:
813 t_repsFrom
.dns_name1
= nastr
815 if t_repsFrom
.dns_name1
is None or \
816 t_repsFrom
.dns_name2
is None or \
817 t_repsFrom
.dns_name1
!= nastr
or \
818 t_repsFrom
.dns_name2
!= nastr
:
819 t_repsFrom
.dns_name1
= nastr
820 t_repsFrom
.dns_name2
= nastr
823 # MS tech specification says we retrieve the named
824 # attribute in "transportAddressAttribute" from the parent of
827 pdnstr
= s_dsa
.get_parent_dnstr()
828 attrs
= [x_transport
.address_attr
]
830 res
= self
.samdb
.search(base
=pdnstr
, scope
=ldb
.SCOPE_BASE
,
832 except ldb
.LdbError
, (enum
, estr
):
834 "Unable to find attr (%s) for (%s) - (%s)" %
835 (x_transport
.address_attr
, pdnstr
, estr
))
838 nastr
= str(msg
[x_transport
.address_attr
][0])
840 # See (NOTE MS-TECH INCORRECT) above
841 if t_repsFrom
.version
== 0x1:
842 if t_repsFrom
.dns_name1
is None or \
843 t_repsFrom
.dns_name1
!= nastr
:
844 t_repsFrom
.dns_name1
= nastr
846 if t_repsFrom
.dns_name1
is None or \
847 t_repsFrom
.dns_name2
is None or \
848 t_repsFrom
.dns_name1
!= nastr
or \
849 t_repsFrom
.dns_name2
!= nastr
:
851 t_repsFrom
.dns_name1
= nastr
852 t_repsFrom
.dns_name2
= nastr
854 if t_repsFrom
.is_modified():
855 DEBUG_FN("modify_repsFrom(): %s" % t_repsFrom
)
857 def get_dsa_for_implied_replica(self
, n_rep
, cn_conn
):
858 """If a connection imply a replica, find the relevant DSA
860 Given a NC replica and NTDS Connection, determine if the
861 connection implies a repsFrom tuple should be present from the
862 source DSA listed in the connection to the naming context. If
863 it should be, return the DSA; otherwise return None.
865 Based on part of MS-ADTS 6.2.2.5
867 :param n_rep: NC replica
868 :param cn_conn: NTDS Connection
869 :return: source DSA or None
871 #XXX different conditions for "implies" than MS-ADTS 6.2.2
873 # NTDS Connection must satisfy all the following criteria
874 # to imply a repsFrom tuple is needed:
876 # cn!enabledConnection = true.
877 # cn!options does not contain NTDSCONN_OPT_RODC_TOPOLOGY.
878 # cn!fromServer references an nTDSDSA object.
880 if not cn_conn
.is_enabled() or cn_conn
.is_rodc_topology():
883 s_dnstr
= cn_conn
.get_from_dnstr()
884 s_dsa
= self
.get_dsa(s_dnstr
)
886 # No DSA matching this source DN string?
890 # To imply a repsFrom tuple is needed, each of these
893 # An NC replica of the NC "is present" on the DC to
894 # which the nTDSDSA object referenced by cn!fromServer
897 # An NC replica of the NC "should be present" on
899 s_rep
= s_dsa
.get_current_replica(n_rep
.nc_dnstr
)
901 if s_rep
is None or not s_rep
.is_present():
904 # To imply a repsFrom tuple is needed, each of these
907 # The NC replica on the DC referenced by cn!fromServer is
908 # a writable replica or the NC replica that "should be
909 # present" on the local DC is a partial replica.
911 # The NC is not a domain NC, the NC replica that
912 # "should be present" on the local DC is a partial
913 # replica, cn!transportType has no value, or
914 # cn!transportType has an RDN of CN=IP.
916 implied
= (not s_rep
.is_ro() or n_rep
.is_partial()) and \
917 (not n_rep
.is_domain() or
918 n_rep
.is_partial() or
919 cn_conn
.transport_dnstr
is None or
920 cn_conn
.transport_dnstr
.find("CN=IP") == 0)
926 def translate_ntdsconn(self
, current_dsa
=None):
927 """Adjust repsFrom to match NTDSConnections
929 This function adjusts values of repsFrom abstract attributes of NC
930 replicas on the local DC to match those implied by
931 nTDSConnection objects.
933 Based on [MS-ADTS] 6.2.2.5
935 :param current_dsa: optional DSA on whose behalf we are acting.
940 if current_dsa
is None:
941 current_dsa
= self
.my_dsa
943 if current_dsa
.is_translate_ntdsconn_disabled():
944 DEBUG_FN("skipping translate_ntdsconn() "
945 "because disabling flag is set")
948 DEBUG_FN("translate_ntdsconn(): enter")
950 current_rep_table
, needed_rep_table
= current_dsa
.get_rep_tables()
952 # Filled in with replicas we currently have that need deleting
955 # We're using the MS notation names here to allow
956 # correlation back to the published algorithm.
958 # n_rep - NC replica (n)
959 # t_repsFrom - tuple (t) in n!repsFrom
960 # s_dsa - Source DSA of the replica. Defined as nTDSDSA
961 # object (s) such that (s!objectGUID = t.uuidDsa)
962 # In our IDL representation of repsFrom the (uuidDsa)
963 # attribute is called (source_dsa_obj_guid)
964 # cn_conn - (cn) is nTDSConnection object and child of the local
965 # DC's nTDSDSA object and (cn!fromServer = s)
966 # s_rep - source DSA replica of n
968 # If we have the replica and its not needed
969 # then we add it to the "to be deleted" list.
970 for dnstr
in current_rep_table
:
971 if dnstr
not in needed_rep_table
:
972 delete_reps
.add(dnstr
)
974 DEBUG_FN('current %d needed %d delete %d' % (len(current_rep_table
),
975 len(needed_rep_table
), len(delete_reps
)))
978 DEBUG('deleting these reps: %s' % delete_reps
)
979 for dnstr
in delete_reps
:
980 del current_rep_table
[dnstr
]
982 # Now perform the scan of replicas we'll need
983 # and compare any current repsFrom against the
985 for n_rep
in needed_rep_table
.values():
987 # load any repsFrom and fsmo roles as we'll
988 # need them during connection translation
989 n_rep
.load_repsFrom(self
.samdb
)
990 n_rep
.load_fsmo_roles(self
.samdb
)
992 # Loop thru the existing repsFrom tupples (if any)
993 # XXX This is a list and could contain duplicates
994 # (multiple load_repsFrom calls)
995 for t_repsFrom
in n_rep
.rep_repsFrom
:
997 # for each tuple t in n!repsFrom, let s be the nTDSDSA
998 # object such that s!objectGUID = t.uuidDsa
999 guidstr
= str(t_repsFrom
.source_dsa_obj_guid
)
1000 s_dsa
= self
.get_dsa_by_guidstr(guidstr
)
1002 # Source dsa is gone from config (strange)
1003 # so cleanup stale repsFrom for unlisted DSA
1005 logger
.warning("repsFrom source DSA guid (%s) not found" %
1007 t_repsFrom
.to_be_deleted
= True
1010 # Find the connection that this repsFrom would use. If
1011 # there isn't a good one (i.e. non-RODC_TOPOLOGY,
1012 # meaning non-FRS), we delete the repsFrom.
1013 s_dnstr
= s_dsa
.dsa_dnstr
1014 connections
= current_dsa
.get_connection_by_from_dnstr(s_dnstr
)
1015 for cn_conn
in connections
:
1016 if not cn_conn
.is_rodc_topology():
1019 # no break means no non-rodc_topology connection exists
1020 t_repsFrom
.to_be_deleted
= True
1023 # KCC removes this repsFrom tuple if any of the following
1025 # No NC replica of the NC "is present" on DSA that
1026 # would be source of replica
1028 # A writable replica of the NC "should be present" on
1029 # the local DC, but a partial replica "is present" on
1031 s_rep
= s_dsa
.get_current_replica(n_rep
.nc_dnstr
)
1033 if s_rep
is None or not s_rep
.is_present() or \
1034 (not n_rep
.is_ro() and s_rep
.is_partial()):
1036 t_repsFrom
.to_be_deleted
= True
1039 # If the KCC did not remove t from n!repsFrom, it updates t
1040 self
.modify_repsFrom(n_rep
, t_repsFrom
, s_rep
, s_dsa
, cn_conn
)
1042 # Loop thru connections and add implied repsFrom tuples
1043 # for each NTDSConnection under our local DSA if the
1044 # repsFrom is not already present
1045 for cn_conn
in current_dsa
.connect_table
.values():
1047 s_dsa
= self
.get_dsa_for_implied_replica(n_rep
, cn_conn
)
1051 # Loop thru the existing repsFrom tupples (if any) and
1052 # if we already have a tuple for this connection then
1053 # no need to proceed to add. It will have been changed
1054 # to have the correct attributes above
1055 for t_repsFrom
in n_rep
.rep_repsFrom
:
1056 guidstr
= str(t_repsFrom
.source_dsa_obj_guid
)
1058 if s_dsa
is self
.get_dsa_by_guidstr(guidstr
):
1065 # Create a new RepsFromTo and proceed to modify
1066 # it according to specification
1067 t_repsFrom
= RepsFromTo(n_rep
.nc_dnstr
)
1069 t_repsFrom
.source_dsa_obj_guid
= s_dsa
.dsa_guid
1071 s_rep
= s_dsa
.get_current_replica(n_rep
.nc_dnstr
)
1073 self
.modify_repsFrom(n_rep
, t_repsFrom
, s_rep
, s_dsa
, cn_conn
)
1075 # Add to our NC repsFrom as this is newly computed
1076 if t_repsFrom
.is_modified():
1077 n_rep
.rep_repsFrom
.append(t_repsFrom
)
1080 # Display any to be deleted or modified repsFrom
1081 text
= n_rep
.dumpstr_to_be_deleted()
1083 logger
.info("TO BE DELETED:\n%s" % text
)
1084 text
= n_rep
.dumpstr_to_be_modified()
1086 logger
.info("TO BE MODIFIED:\n%s" % text
)
1088 # Peform deletion from our tables but perform
1089 # no database modification
1090 n_rep
.commit_repsFrom(self
.samdb
, ro
=True)
1092 # Commit any modified repsFrom to the NC replica
1093 n_rep
.commit_repsFrom(self
.samdb
)
1095 def merge_failed_links(self
, ping
=None):
1096 """Merge of kCCFailedLinks and kCCFailedLinks from bridgeheads.
1098 The KCC on a writable DC attempts to merge the link and connection
1099 failure information from bridgehead DCs in its own site to help it
1100 identify failed bridgehead DCs.
1102 Based on MS-ADTS 6.2.2.3.2 "Merge of kCCFailedLinks and kCCFailedLinks
1105 :param ping: An oracle of current bridgehead availability
1108 # 1. Queries every bridgehead server in your site (other than yourself)
1109 # 2. For every ntDSConnection that references a server in a different
1110 # site merge all the failure info
1112 # XXX - not implemented yet
1113 if ping
is not None:
1114 debug
.DEBUG_RED("merge_failed_links() is NOT IMPLEMENTED")
1116 DEBUG_FN("skipping merge_failed_links() because it requires "
1117 "real network connections\n"
1118 "and we weren't asked to --attempt-live-connections")
1120 def setup_graph(self
, part
):
1121 """Set up an intersite graph
1123 An intersite graph has a Vertex for each site object, a
1124 MultiEdge for each SiteLink object, and a MutliEdgeSet for
1125 each siteLinkBridge object (or implied siteLinkBridge). It
1126 reflects the intersite topology in a slightly more abstract
1129 Roughly corresponds to MS-ADTS 6.2.2.3.4.3
1131 :param part: a Partition object
1132 :returns: an InterSiteGraph object
1134 # If 'Bridge all site links' is enabled and Win2k3 bridges required
1136 # NTDSTRANSPORT_OPT_BRIDGES_REQUIRED 0x00000002
1137 # No documentation for this however, ntdsapi.h appears to have:
1138 # NTDSSETTINGS_OPT_W2K3_BRIDGES_REQUIRED = 0x00001000
1139 bridges_required
= self
.my_site
.site_options
& 0x00001002 == 0
1141 g
= setup_graph(part
, self
.site_table
, self
.transport_table
,
1142 self
.sitelink_table
, bridges_required
)
1144 if self
.verify
or self
.dot_file_dir
is not None:
1146 for edge
in g
.edges
:
1147 for a
, b
in itertools
.combinations(edge
.vertices
, 2):
1148 dot_edges
.append((a
.site
.site_dnstr
, b
.site
.site_dnstr
))
1149 verify_properties
= ()
1150 verify_and_dot('site_edges', dot_edges
, directed
=False,
1151 label
=self
.my_dsa_dnstr
,
1152 properties
=verify_properties
, debug
=DEBUG
,
1154 dot_file_dir
=self
.dot_file_dir
)
1158 def get_bridgehead(self
, site
, part
, transport
, partial_ok
, detect_failed
):
1159 """Get a bridghead DC for a site.
1161 Part of MS-ADTS 6.2.2.3.4.4
1163 :param site: site object representing for which a bridgehead
1165 :param part: crossRef for NC to replicate.
1166 :param transport: interSiteTransport object for replication
1168 :param partial_ok: True if a DC containing a partial
1169 replica or a full replica will suffice, False if only
1170 a full replica will suffice.
1171 :param detect_failed: True to detect failed DCs and route
1172 replication traffic around them, False to assume no DC
1174 :return: dsa object for the bridgehead DC or None
1177 bhs
= self
.get_all_bridgeheads(site
, part
, transport
,
1178 partial_ok
, detect_failed
)
1180 debug
.DEBUG_MAGENTA("get_bridgehead:\n\tsitedn=%s\n\tbhdn=None" %
1184 debug
.DEBUG_GREEN("get_bridgehead:\n\tsitedn=%s\n\tbhdn=%s" %
1185 (site
.site_dnstr
, bhs
[0].dsa_dnstr
))
1188 def get_all_bridgeheads(self
, site
, part
, transport
,
1189 partial_ok
, detect_failed
):
1190 """Get all bridghead DCs on a site satisfying the given criteria
1192 Part of MS-ADTS 6.2.2.3.4.4
1194 :param site: site object representing the site for which
1195 bridgehead DCs are desired.
1196 :param part: partition for NC to replicate.
1197 :param transport: interSiteTransport object for
1198 replication traffic.
1199 :param partial_ok: True if a DC containing a partial
1200 replica or a full replica will suffice, False if
1201 only a full replica will suffice.
1202 :param detect_failed: True to detect failed DCs and route
1203 replication traffic around them, FALSE to assume
1205 :return: list of dsa object for available bridgehead DCs
1209 DEBUG_FN("get_all_bridgeheads: %s" % transport
.name
)
1210 DEBUG_FN(site
.rw_dsa_table
)
1211 for dsa
in site
.rw_dsa_table
.values():
1213 pdnstr
= dsa
.get_parent_dnstr()
1215 # IF t!bridgeheadServerListBL has one or more values and
1216 # t!bridgeheadServerListBL does not contain a reference
1217 # to the parent object of dc then skip dc
1218 if ((len(transport
.bridgehead_list
) != 0 and
1219 pdnstr
not in transport
.bridgehead_list
)):
1222 # IF dc is in the same site as the local DC
1223 # IF a replica of cr!nCName is not in the set of NC replicas
1224 # that "should be present" on dc or a partial replica of the
1225 # NC "should be present" but partialReplicasOkay = FALSE
1227 if self
.my_site
.same_site(dsa
):
1228 needed
, ro
, partial
= part
.should_be_present(dsa
)
1229 if not needed
or (partial
and not partial_ok
):
1231 rep
= dsa
.get_current_replica(part
.nc_dnstr
)
1234 # IF an NC replica of cr!nCName is not in the set of NC
1235 # replicas that "are present" on dc or a partial replica of
1236 # the NC "is present" but partialReplicasOkay = FALSE
1239 rep
= dsa
.get_current_replica(part
.nc_dnstr
)
1240 if rep
is None or (rep
.is_partial() and not partial_ok
):
1243 # IF AmIRODC() and cr!nCName corresponds to default NC then
1244 # Let dsaobj be the nTDSDSA object of the dc
1245 # IF dsaobj.msDS-Behavior-Version < DS_DOMAIN_FUNCTION_2008
1247 if self
.my_dsa
.is_ro() and rep
is not None and rep
.is_default():
1248 if not dsa
.is_minimum_behavior(dsdb
.DS_DOMAIN_FUNCTION_2008
):
1251 # IF t!name != "IP" and the parent object of dc has no value for
1252 # the attribute specified by t!transportAddressAttribute
1254 if transport
.name
!= "IP":
1255 # MS tech specification says we retrieve the named
1256 # attribute in "transportAddressAttribute" from the parent
1259 attrs
= [transport
.address_attr
]
1261 res
= self
.samdb
.search(base
=pdnstr
, scope
=ldb
.SCOPE_BASE
,
1263 except ldb
.LdbError
, (enum
, estr
):
1267 if transport
.address_attr
not in msg
:
1269 #XXX nastr is NEVER USED. It will be removed.
1270 nastr
= str(msg
[transport
.address_attr
][0])
1272 # IF BridgeheadDCFailed(dc!objectGUID, detectFailedDCs) = TRUE
1274 if self
.is_bridgehead_failed(dsa
, detect_failed
):
1275 DEBUG("bridgehead is failed")
1278 DEBUG_FN("get_all_bridgeheads: dsadn=%s" % dsa
.dsa_dnstr
)
1281 # IF bit NTDSSETTINGS_OPT_IS_RAND_BH_SELECTION_DISABLED is set in
1283 # SORT bhs such that all GC servers precede DCs that are not GC
1284 # servers, and otherwise by ascending objectGUID
1286 # SORT bhs in a random order
1287 if site
.is_random_bridgehead_disabled():
1288 bhs
.sort(sort_dsa_by_gc_and_guid
)
1291 debug
.DEBUG_YELLOW(bhs
)
1294 def is_bridgehead_failed(self
, dsa
, detect_failed
):
1295 """Determine whether a given DC is known to be in a failed state
1297 :param dsa: the bridgehead to test
1298 :param detect_failed: True to really check, False to assume no failure
1299 :return: True if and only if the DC should be considered failed
1301 Here we DEPART from the pseudo code spec which appears to be
1302 wrong. It says, in full:
1304 /***** BridgeheadDCFailed *****/
1305 /* Determine whether a given DC is known to be in a failed state.
1306 * IN: objectGUID - objectGUID of the DC's nTDSDSA object.
1307 * IN: detectFailedDCs - TRUE if and only failed DC detection is
1309 * RETURNS: TRUE if and only if the DC should be considered to be in a
1312 BridgeheadDCFailed(IN GUID objectGUID, IN bool detectFailedDCs) : bool
1314 IF bit NTDSSETTINGS_OPT_IS_TOPL_DETECT_STALE_DISABLED is set in
1315 the options attribute of the site settings object for the local
1318 ELSEIF a tuple z exists in the kCCFailedLinks or
1319 kCCFailedConnections variables such that z.UUIDDsa =
1320 objectGUID, z.FailureCount > 1, and the current time -
1321 z.TimeFirstFailure > 2 hours
1324 RETURN detectFailedDCs
1328 where you will see detectFailedDCs is not behaving as
1329 advertised -- it is acting as a default return code in the
1330 event that a failure is not detected, not a switch turning
1331 detection on or off. Elsewhere the documentation seems to
1332 concur with the comment rather than the code.
1334 if not detect_failed
:
1337 # NTDSSETTINGS_OPT_IS_TOPL_DETECT_STALE_DISABLED = 0x00000008
1338 # When DETECT_STALE_DISABLED, we can never know of if
1339 # it's in a failed state
1340 if self
.my_site
.site_options
& 0x00000008:
1343 return self
.is_stale_link_connection(dsa
)
1345 def create_connection(self
, part
, rbh
, rsite
, transport
,
1346 lbh
, lsite
, link_opt
, link_sched
,
1347 partial_ok
, detect_failed
):
1348 """Create an nTDSConnection object as specified if it doesn't exist.
1350 Part of MS-ADTS 6.2.2.3.4.5
1352 :param part: crossRef object for the NC to replicate.
1353 :param rbh: nTDSDSA object for DC to act as the
1354 IDL_DRSGetNCChanges server (which is in a site other
1355 than the local DC's site).
1356 :param rsite: site of the rbh
1357 :param transport: interSiteTransport object for the transport
1358 to use for replication traffic.
1359 :param lbh: nTDSDSA object for DC to act as the
1360 IDL_DRSGetNCChanges client (which is in the local DC's site).
1361 :param lsite: site of the lbh
1362 :param link_opt: Replication parameters (aggregated siteLink options,
1364 :param link_sched: Schedule specifying the times at which
1365 to begin replicating.
1366 :partial_ok: True if bridgehead DCs containing partial
1367 replicas of the NC are acceptable.
1368 :param detect_failed: True to detect failed DCs and route
1369 replication traffic around them, FALSE to assume no DC
1372 rbhs_all
= self
.get_all_bridgeheads(rsite
, part
, transport
,
1374 rbh_table
= {x
.dsa_dnstr
: x
for x
in rbhs_all
}
1376 debug
.DEBUG_GREY("rbhs_all: %s %s" % (len(rbhs_all
),
1377 [x
.dsa_dnstr
for x
in rbhs_all
]))
1379 # MS-TECH says to compute rbhs_avail but then doesn't use it
1380 # rbhs_avail = self.get_all_bridgeheads(rsite, part, transport,
1381 # partial_ok, detect_failed)
1383 lbhs_all
= self
.get_all_bridgeheads(lsite
, part
, transport
,
1386 lbhs_all
.append(lbh
)
1388 debug
.DEBUG_GREY("lbhs_all: %s %s" % (len(lbhs_all
),
1389 [x
.dsa_dnstr
for x
in lbhs_all
]))
1391 # MS-TECH says to compute lbhs_avail but then doesn't use it
1392 # lbhs_avail = self.get_all_bridgeheads(lsite, part, transport,
1393 # partial_ok, detect_failed)
1395 # FOR each nTDSConnection object cn such that the parent of cn is
1396 # a DC in lbhsAll and cn!fromServer references a DC in rbhsAll
1397 for ldsa
in lbhs_all
:
1398 for cn
in ldsa
.connect_table
.values():
1400 rdsa
= rbh_table
.get(cn
.from_dnstr
)
1404 debug
.DEBUG_DARK_YELLOW("rdsa is %s" % rdsa
.dsa_dnstr
)
1405 # IF bit NTDSCONN_OPT_IS_GENERATED is set in cn!options and
1406 # NTDSCONN_OPT_RODC_TOPOLOGY is clear in cn!options and
1407 # cn!transportType references t
1408 if ((cn
.is_generated() and
1409 not cn
.is_rodc_topology() and
1410 cn
.transport_guid
== transport
.guid
)):
1412 # IF bit NTDSCONN_OPT_USER_OWNED_SCHEDULE is clear in
1413 # cn!options and cn!schedule != sch
1414 # Perform an originating update to set cn!schedule to
1416 if ((not cn
.is_user_owned_schedule() and
1417 not cn
.is_equivalent_schedule(link_sched
))):
1418 cn
.schedule
= link_sched
1419 cn
.set_modified(True)
1421 # IF bits NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT and
1422 # NTDSCONN_OPT_USE_NOTIFY are set in cn
1423 if cn
.is_override_notify_default() and \
1426 # IF bit NTDSSITELINK_OPT_USE_NOTIFY is clear in
1428 # Perform an originating update to clear bits
1429 # NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT and
1430 # NTDSCONN_OPT_USE_NOTIFY in cn!options
1431 if (link_opt
& dsdb
.NTDSSITELINK_OPT_USE_NOTIFY
) == 0:
1433 ~
(dsdb
.NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT |
1434 dsdb
.NTDSCONN_OPT_USE_NOTIFY
)
1435 cn
.set_modified(True)
1440 # IF bit NTDSSITELINK_OPT_USE_NOTIFY is set in
1442 # Perform an originating update to set bits
1443 # NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT and
1444 # NTDSCONN_OPT_USE_NOTIFY in cn!options
1445 if (link_opt
& dsdb
.NTDSSITELINK_OPT_USE_NOTIFY
) != 0:
1447 (dsdb
.NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT |
1448 dsdb
.NTDSCONN_OPT_USE_NOTIFY
)
1449 cn
.set_modified(True)
1451 # IF bit NTDSCONN_OPT_TWOWAY_SYNC is set in cn!options
1452 if cn
.is_twoway_sync():
1454 # IF bit NTDSSITELINK_OPT_TWOWAY_SYNC is clear in
1456 # Perform an originating update to clear bit
1457 # NTDSCONN_OPT_TWOWAY_SYNC in cn!options
1458 if (link_opt
& dsdb
.NTDSSITELINK_OPT_TWOWAY_SYNC
) == 0:
1459 cn
.options
&= ~dsdb
.NTDSCONN_OPT_TWOWAY_SYNC
1460 cn
.set_modified(True)
1465 # IF bit NTDSSITELINK_OPT_TWOWAY_SYNC is set in
1467 # Perform an originating update to set bit
1468 # NTDSCONN_OPT_TWOWAY_SYNC in cn!options
1469 if (link_opt
& dsdb
.NTDSSITELINK_OPT_TWOWAY_SYNC
) != 0:
1470 cn
.options |
= dsdb
.NTDSCONN_OPT_TWOWAY_SYNC
1471 cn
.set_modified(True)
1473 # IF bit NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION is set
1475 if cn
.is_intersite_compression_disabled():
1477 # IF bit NTDSSITELINK_OPT_DISABLE_COMPRESSION is clear
1479 # Perform an originating update to clear bit
1480 # NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION in
1483 dsdb
.NTDSSITELINK_OPT_DISABLE_COMPRESSION
) == 0):
1485 ~dsdb
.NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION
1486 cn
.set_modified(True)
1490 # IF bit NTDSSITELINK_OPT_DISABLE_COMPRESSION is set in
1492 # Perform an originating update to set bit
1493 # NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION in
1496 dsdb
.NTDSSITELINK_OPT_DISABLE_COMPRESSION
) != 0):
1498 dsdb
.NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION
1499 cn
.set_modified(True)
1501 # Display any modified connection
1503 if cn
.to_be_modified
:
1504 logger
.info("TO BE MODIFIED:\n%s" % cn
)
1506 ldsa
.commit_connections(self
.samdb
, ro
=True)
1508 ldsa
.commit_connections(self
.samdb
)
1511 valid_connections
= 0
1513 # FOR each nTDSConnection object cn such that cn!parent is
1514 # a DC in lbhsAll and cn!fromServer references a DC in rbhsAll
1515 for ldsa
in lbhs_all
:
1516 for cn
in ldsa
.connect_table
.values():
1518 rdsa
= rbh_table
.get(cn
.from_dnstr
)
1522 debug
.DEBUG_DARK_YELLOW("round 2: rdsa is %s" % rdsa
.dsa_dnstr
)
1524 # IF (bit NTDSCONN_OPT_IS_GENERATED is clear in cn!options or
1525 # cn!transportType references t) and
1526 # NTDSCONN_OPT_RODC_TOPOLOGY is clear in cn!options
1527 if (((not cn
.is_generated() or
1528 cn
.transport_guid
== transport
.guid
) and
1529 not cn
.is_rodc_topology())):
1531 # LET rguid be the objectGUID of the nTDSDSA object
1532 # referenced by cn!fromServer
1533 # LET lguid be (cn!parent)!objectGUID
1535 # IF BridgeheadDCFailed(rguid, detectFailedDCs) = FALSE and
1536 # BridgeheadDCFailed(lguid, detectFailedDCs) = FALSE
1537 # Increment cValidConnections by 1
1538 if ((not self
.is_bridgehead_failed(rdsa
, detect_failed
) and
1539 not self
.is_bridgehead_failed(ldsa
, detect_failed
))):
1540 valid_connections
+= 1
1542 # IF keepConnections does not contain cn!objectGUID
1543 # APPEND cn!objectGUID to keepConnections
1544 self
.kept_connections
.add(cn
)
1547 debug
.DEBUG_RED("valid connections %d" % valid_connections
)
1548 DEBUG("kept_connections:\n%s" % (self
.kept_connections
,))
1549 # IF cValidConnections = 0
1550 if valid_connections
== 0:
1552 # LET opt be NTDSCONN_OPT_IS_GENERATED
1553 opt
= dsdb
.NTDSCONN_OPT_IS_GENERATED
1555 # IF bit NTDSSITELINK_OPT_USE_NOTIFY is set in ri.Options
1556 # SET bits NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT and
1557 # NTDSCONN_OPT_USE_NOTIFY in opt
1558 if (link_opt
& dsdb
.NTDSSITELINK_OPT_USE_NOTIFY
) != 0:
1559 opt |
= (dsdb
.NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT |
1560 dsdb
.NTDSCONN_OPT_USE_NOTIFY
)
1562 # IF bit NTDSSITELINK_OPT_TWOWAY_SYNC is set in ri.Options
1563 # SET bit NTDSCONN_OPT_TWOWAY_SYNC opt
1564 if (link_opt
& dsdb
.NTDSSITELINK_OPT_TWOWAY_SYNC
) != 0:
1565 opt |
= dsdb
.NTDSCONN_OPT_TWOWAY_SYNC
1567 # IF bit NTDSSITELINK_OPT_DISABLE_COMPRESSION is set in
1569 # SET bit NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION in opt
1571 dsdb
.NTDSSITELINK_OPT_DISABLE_COMPRESSION
) != 0):
1572 opt |
= dsdb
.NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION
1574 # Perform an originating update to create a new nTDSConnection
1575 # object cn that is a child of lbh, cn!enabledConnection = TRUE,
1576 # cn!options = opt, cn!transportType is a reference to t,
1577 # cn!fromServer is a reference to rbh, and cn!schedule = sch
1578 DEBUG_FN("new connection, KCC dsa: %s" % self
.my_dsa
.dsa_dnstr
)
1579 cn
= lbh
.new_connection(opt
, 0, transport
,
1580 rbh
.dsa_dnstr
, link_sched
)
1582 # Display any added connection
1585 logger
.info("TO BE ADDED:\n%s" % cn
)
1587 lbh
.commit_connections(self
.samdb
, ro
=True)
1589 lbh
.commit_connections(self
.samdb
)
1591 # APPEND cn!objectGUID to keepConnections
1592 self
.kept_connections
.add(cn
)
1594 def add_transports(self
, vertex
, local_vertex
, graph
, detect_failed
):
1595 """Build a Vertex's transport lists
1597 Each vertex has accept_red_red and accept_black lists that
1598 list what transports they accept under various conditions. The
1599 only transport that is ever accepted is IP, and a dummy extra
1600 transport called "EDGE_TYPE_ALL".
1602 Part of MS-ADTS 6.2.2.3.4.3 -- ColorVertices
1604 :param vertex: the remote vertex we are thinking about
1605 :param local_vertex: the vertex relating to the local site.
1606 :param graph: the intersite graph
1607 :param detect_failed: whether to detect failed links
1608 :return: True if some bridgeheads were not found
1610 # The docs ([MS-ADTS] 6.2.2.3.4.3) say to use local_vertex
1611 # here, but using vertex seems to make more sense. That is,
1612 # the docs want this:
1614 #bh = self.get_bridgehead(vertex.site, vertex.part, transport,
1615 # local_vertex.is_black(), detect_failed)
1619 vertex
.accept_red_red
= []
1620 vertex
.accept_black
= []
1621 found_failed
= False
1622 for t_guid
, transport
in self
.transport_table
.items():
1623 if transport
.name
!= 'IP':
1624 #XXX well this is cheating a bit
1625 logger
.warning("WARNING: we are ignoring a transport named %r"
1629 # FLAG_CR_NTDS_DOMAIN 0x00000002
1630 if ((vertex
.is_red() and transport
.name
!= "IP" and
1631 vertex
.part
.system_flags
& 0x00000002)):
1634 if vertex
not in graph
.connected_vertices
:
1637 partial_replica_okay
= vertex
.is_black()
1638 bh
= self
.get_bridgehead(vertex
.site
, vertex
.part
, transport
,
1639 partial_replica_okay
, detect_failed
)
1644 vertex
.accept_red_red
.append(t_guid
)
1645 vertex
.accept_black
.append(t_guid
)
1647 # Add additional transport to allow another run of Dijkstra
1648 vertex
.accept_red_red
.append("EDGE_TYPE_ALL")
1649 vertex
.accept_black
.append("EDGE_TYPE_ALL")
1653 def create_connections(self
, graph
, part
, detect_failed
):
1654 """Construct an NC replica graph for the NC identified by
1655 the given crossRef, then create any additional nTDSConnection
1658 :param graph: site graph.
1659 :param part: crossRef object for NC.
1660 :param detect_failed: True to detect failed DCs and route
1661 replication traffic around them, False to assume no DC
1664 Modifies self.kept_connections by adding any connections
1665 deemed to be "in use".
1667 ::returns: (all_connected, found_failed_dc)
1668 (all_connected) True if the resulting NC replica graph
1669 connects all sites that need to be connected.
1670 (found_failed_dc) True if one or more failed DCs were
1673 all_connected
= True
1674 found_failed
= False
1676 DEBUG_FN("create_connections(): enter\n"
1677 "\tpartdn=%s\n\tdetect_failed=%s" %
1678 (part
.nc_dnstr
, detect_failed
))
1680 # XXX - This is a highly abbreviated function from the MS-TECH
1681 # ref. It creates connections between bridgeheads to all
1682 # sites that have appropriate replicas. Thus we are not
1683 # creating a minimum cost spanning tree but instead
1684 # producing a fully connected tree. This should produce
1685 # a full (albeit not optimal cost) replication topology.
1687 my_vertex
= Vertex(self
.my_site
, part
)
1688 my_vertex
.color_vertex()
1690 for v
in graph
.vertices
:
1692 if self
.add_transports(v
, my_vertex
, graph
, False):
1695 # No NC replicas for this NC in the site of the local DC,
1696 # so no nTDSConnection objects need be created
1697 if my_vertex
.is_white():
1698 return all_connected
, found_failed
1700 edge_list
, n_components
= get_spanning_tree_edges(graph
,
1704 DEBUG_FN("%s Number of components: %d" %
1705 (part
.nc_dnstr
, n_components
))
1706 if n_components
> 1:
1707 all_connected
= False
1709 # LET partialReplicaOkay be TRUE if and only if
1710 # localSiteVertex.Color = COLOR.BLACK
1711 partial_ok
= my_vertex
.is_black()
1713 # Utilize the IP transport only for now
1714 transport
= self
.ip_transport
1716 DEBUG("edge_list %s" % edge_list
)
1718 # XXX more accurate comparison?
1719 if e
.directed
and e
.vertices
[0].site
is self
.my_site
:
1722 if e
.vertices
[0].site
is self
.my_site
:
1723 rsite
= e
.vertices
[1].site
1725 rsite
= e
.vertices
[0].site
1727 # We don't make connections to our own site as that
1728 # is intrasite topology generator's job
1729 if rsite
is self
.my_site
:
1730 DEBUG("rsite is my_site")
1733 # Determine bridgehead server in remote site
1734 rbh
= self
.get_bridgehead(rsite
, part
, transport
,
1735 partial_ok
, detect_failed
)
1739 # RODC acts as an BH for itself
1741 # LET lbh be the nTDSDSA object of the local DC
1743 # LET lbh be the result of GetBridgeheadDC(localSiteVertex.ID,
1744 # cr, t, partialReplicaOkay, detectFailedDCs)
1745 if self
.my_dsa
.is_ro():
1746 lsite
= self
.my_site
1749 lsite
= self
.my_site
1750 lbh
= self
.get_bridgehead(lsite
, part
, transport
,
1751 partial_ok
, detect_failed
)
1754 debug
.DEBUG_RED("DISASTER! lbh is None")
1757 debug
.DEBUG_CYAN("SITES")
1759 debug
.DEBUG_BLUE("vertices")
1761 debug
.DEBUG_BLUE("bridgeheads")
1763 debug
.DEBUG_BLUE("-" * 70)
1765 sitelink
= e
.site_link
1766 if sitelink
is None:
1770 link_opt
= sitelink
.options
1771 link_sched
= sitelink
.schedule
1773 self
.create_connection(part
, rbh
, rsite
, transport
,
1774 lbh
, lsite
, link_opt
, link_sched
,
1775 partial_ok
, detect_failed
)
1777 return all_connected
, found_failed
1779 def create_intersite_connections(self
):
1780 """Create NTDSConnections as necessary for all partitions.
1782 Computes an NC replica graph for each NC replica that "should be
1783 present" on the local DC or "is present" on any DC in the same site
1784 as the local DC. For each edge directed to an NC replica on such a
1785 DC from an NC replica on a DC in another site, the KCC creates an
1786 nTDSConnection object to imply that edge if one does not already
1789 Modifies self.kept_connections - A set of nTDSConnection
1790 objects for edges that are directed
1791 to the local DC's site in one or more NC replica graphs.
1793 :return: True if spanning trees were created for all NC replica
1794 graphs, otherwise False.
1796 all_connected
= True
1797 self
.kept_connections
= set()
1799 # LET crossRefList be the set containing each object o of class
1800 # crossRef such that o is a child of the CN=Partitions child of the
1803 # FOR each crossRef object cr in crossRefList
1804 # IF cr!enabled has a value and is false, or if FLAG_CR_NTDS_NC
1805 # is clear in cr!systemFlags, skip cr.
1806 # LET g be the GRAPH return of SetupGraph()
1808 for part
in self
.part_table
.values():
1810 if not part
.is_enabled():
1813 if part
.is_foreign():
1816 graph
= self
.setup_graph(part
)
1818 # Create nTDSConnection objects, routing replication traffic
1819 # around "failed" DCs.
1820 found_failed
= False
1822 connected
, found_failed
= self
.create_connections(graph
,
1825 DEBUG("with detect_failed: connected %s Found failed %s" %
1826 (connected
, found_failed
))
1828 all_connected
= False
1831 # One or more failed DCs preclude use of the ideal NC
1832 # replica graph. Add connections for the ideal graph.
1833 self
.create_connections(graph
, part
, False)
1835 return all_connected
1837 def intersite(self
, ping
):
1838 """The head method for generating the inter-site KCC replica
1839 connection graph and attendant nTDSConnection objects
1842 Produces self.kept_connections set of NTDS Connections
1843 that should be kept during subsequent pruning process.
1845 ::return (True or False): (True) if the produced NC replica
1846 graph connects all sites that need to be connected
1851 mysite
= self
.my_site
1852 all_connected
= True
1854 DEBUG_FN("intersite(): enter")
1856 # Determine who is the ISTG
1858 mysite
.select_istg(self
.samdb
, mydsa
, ro
=True)
1860 mysite
.select_istg(self
.samdb
, mydsa
, ro
=False)
1862 # Test whether local site has topology disabled
1863 if mysite
.is_intersite_topology_disabled():
1864 DEBUG_FN("intersite(): exit disabled all_connected=%d" %
1866 return all_connected
1868 if not mydsa
.is_istg():
1869 DEBUG_FN("intersite(): exit not istg all_connected=%d" %
1871 return all_connected
1873 self
.merge_failed_links(ping
)
1875 # For each NC with an NC replica that "should be present" on the
1876 # local DC or "is present" on any DC in the same site as the
1877 # local DC, the KCC constructs a site graph--a precursor to an NC
1878 # replica graph. The site connectivity for a site graph is defined
1879 # by objects of class interSiteTransport, siteLink, and
1880 # siteLinkBridge in the config NC.
1882 all_connected
= self
.create_intersite_connections()
1884 DEBUG_FN("intersite(): exit all_connected=%d" % all_connected
)
1885 return all_connected
1887 def update_rodc_connection(self
):
1888 """Updates the RODC NTFRS connection object.
1890 If the local DSA is not an RODC, this does nothing.
1892 if not self
.my_dsa
.is_ro():
1895 # Given an nTDSConnection object cn1, such that cn1.options contains
1896 # NTDSCONN_OPT_RODC_TOPOLOGY, and another nTDSConnection object cn2,
1897 # does not contain NTDSCONN_OPT_RODC_TOPOLOGY, modify cn1 to ensure
1898 # that the following is true:
1900 # cn1.fromServer = cn2.fromServer
1901 # cn1.schedule = cn2.schedule
1903 # If no such cn2 can be found, cn1 is not modified.
1904 # If no such cn1 can be found, nothing is modified by this task.
1906 all_connections
= self
.my_dsa
.connect_table
.values()
1907 ro_connections
= [x
for x
in all_connections
if x
.is_rodc_topology()]
1908 rw_connections
= [x
for x
in all_connections
1909 if x
not in ro_connections
]
1911 # XXX here we are dealing with multiple RODC_TOPO connections,
1912 # if they exist. It is not clear whether the spec means that
1913 # or if it ever arises.
1914 if rw_connections
and ro_connections
:
1915 for con
in ro_connections
:
1916 cn2
= rw_connections
[0]
1917 con
.from_dnstr
= cn2
.from_dnstr
1918 con
.schedule
= cn2
.schedule
1919 con
.to_be_modified
= True
1921 self
.my_dsa
.commit_connections(self
.samdb
, ro
=self
.readonly
)
1923 def intrasite_max_node_edges(self
, node_count
):
1924 """Returns the maximum number of edges directed to a node in
1925 the intrasite replica graph.
1927 The KCC does not create more
1928 than 50 edges directed to a single DC. To optimize replication,
1929 we compute that each node should have n+2 total edges directed
1930 to it such that (n) is the smallest non-negative integer
1931 satisfying (node_count <= 2*(n*n) + 6*n + 7)
1933 (If the number of edges is m (i.e. n + 2), that is the same as
1934 2 * m*m - 2 * m + 3).
1944 :param node_count: total number of nodes in the replica graph
1946 The intention is that there should be no more than 3 hops
1947 between any two DSAs at a site. With up to 7 nodes the 2 edges
1948 of the ring are enough; any configuration of extra edges with
1949 8 nodes will be enough. It is less clear that the 3 hop
1950 guarantee holds at e.g. 15 nodes in degenerate cases, but
1951 those are quite unlikely given the extra edges are randomly
1956 if node_count
<= (2 * (n
* n
) + (6 * n
) + 7):
1964 def construct_intrasite_graph(self
, site_local
, dc_local
,
1965 nc_x
, gc_only
, detect_stale
):
1966 """Create an intrasite graph using given parameters
1968 This might be called a number of times per site with different
1971 Based on [MS-ADTS] 6.2.2.2
1973 :param site_local: site for which we are working
1974 :param dc_local: local DC that potentially needs a replica
1975 :param nc_x: naming context (x) that we are testing if it
1976 "should be present" on the local DC
1977 :param gc_only: Boolean - only consider global catalog servers
1978 :param detect_stale: Boolean - check whether links seems down
1981 # We're using the MS notation names here to allow
1982 # correlation back to the published algorithm.
1984 # nc_x - naming context (x) that we are testing if it
1985 # "should be present" on the local DC
1986 # f_of_x - replica (f) found on a DC (s) for NC (x)
1987 # dc_s - DC where f_of_x replica was found
1988 # dc_local - local DC that potentially needs a replica
1990 # r_list - replica list R
1991 # p_of_x - replica (p) is partial and found on a DC (s)
1993 # l_of_x - replica (l) is the local replica for NC (x)
1994 # that should appear on the local DC
1995 # r_len = is length of replica list |R|
1997 # If the DSA doesn't need a replica for this
1998 # partition (NC x) then continue
1999 needed
, ro
, partial
= nc_x
.should_be_present(dc_local
)
2001 debug
.DEBUG_YELLOW("construct_intrasite_graph(): enter" +
2002 "\n\tgc_only=%d" % gc_only
+
2003 "\n\tdetect_stale=%d" % detect_stale
+
2004 "\n\tneeded=%s" % needed
+
2006 "\n\tpartial=%s" % partial
+
2010 debug
.DEBUG_RED("%s lacks 'should be present' status, "
2011 "aborting construct_intersite_graph!" %
2015 # Create a NCReplica that matches what the local replica
2016 # should say. We'll use this below in our r_list
2017 l_of_x
= NCReplica(dc_local
.dsa_dnstr
, dc_local
.dsa_guid
,
2020 l_of_x
.identify_by_basedn(self
.samdb
)
2022 l_of_x
.rep_partial
= partial
2025 # Add this replica that "should be present" to the
2026 # needed replica table for this DSA
2027 dc_local
.add_needed_replica(l_of_x
)
2031 # Let R be a sequence containing each writable replica f of x
2032 # such that f "is present" on a DC s satisfying the following
2035 # * s is a writable DC other than the local DC.
2037 # * s is in the same site as the local DC.
2039 # * If x is a read-only full replica and x is a domain NC,
2040 # then the DC's functional level is at least
2041 # DS_BEHAVIOR_WIN2008.
2043 # * Bit NTDSSETTINGS_OPT_IS_TOPL_DETECT_STALE_DISABLED is set
2044 # in the options attribute of the site settings object for
2045 # the local DC's site, or no tuple z exists in the
2046 # kCCFailedLinks or kCCFailedConnections variables such
2047 # that z.UUIDDsa is the objectGUID of the nTDSDSA object
2048 # for s, z.FailureCount > 0, and the current time -
2049 # z.TimeFirstFailure > 2 hours.
2053 # We'll loop thru all the DSAs looking for
2054 # writeable NC replicas that match the naming
2055 # context dn for (nc_x)
2057 for dc_s
in self
.my_site
.dsa_table
.values():
2058 # If this partition (nc_x) doesn't appear as a
2059 # replica (f_of_x) on (dc_s) then continue
2060 if not nc_x
.nc_dnstr
in dc_s
.current_rep_table
:
2063 # Pull out the NCReplica (f) of (x) with the dn
2064 # that matches NC (x) we are examining.
2065 f_of_x
= dc_s
.current_rep_table
[nc_x
.nc_dnstr
]
2067 # Replica (f) of NC (x) must be writable
2071 # Replica (f) of NC (x) must satisfy the
2072 # "is present" criteria for DC (s) that
2074 if not f_of_x
.is_present():
2077 # DC (s) must be a writable DSA other than
2078 # my local DC. In other words we'd only replicate
2079 # from other writable DC
2080 if dc_s
.is_ro() or dc_s
is dc_local
:
2083 # Certain replica graphs are produced only
2084 # for global catalogs, so test against
2085 # method input parameter
2086 if gc_only
and not dc_s
.is_gc():
2089 # DC (s) must be in the same site as the local DC
2090 # as this is the intra-site algorithm. This is
2091 # handled by virtue of placing DSAs in per
2092 # site objects (see enclosing for() loop)
2094 # If NC (x) is intended to be read-only full replica
2095 # for a domain NC on the target DC then the source
2096 # DC should have functional level at minimum WIN2008
2098 # Effectively we're saying that in order to replicate
2099 # to a targeted RODC (which was introduced in Windows 2008)
2100 # then we have to replicate from a DC that is also minimally
2103 # You can also see this requirement in the MS special
2104 # considerations for RODC which state that to deploy
2105 # an RODC, at least one writable domain controller in
2106 # the domain must be running Windows Server 2008
2107 if ro
and not partial
and nc_x
.nc_type
== NCType
.domain
:
2108 if not dc_s
.is_minimum_behavior(dsdb
.DS_DOMAIN_FUNCTION_2008
):
2111 # If we haven't been told to turn off stale connection
2112 # detection and this dsa has a stale connection then
2114 if detect_stale
and self
.is_stale_link_connection(dc_s
):
2117 # Replica meets criteria. Add it to table indexed
2118 # by the GUID of the DC that it appears on
2119 r_list
.append(f_of_x
)
2121 # If a partial (not full) replica of NC (x) "should be present"
2122 # on the local DC, append to R each partial replica (p of x)
2123 # such that p "is present" on a DC satisfying the same
2124 # criteria defined above for full replica DCs.
2126 # XXX This loop and the previous one differ only in whether
2127 # the replica is partial or not. here we only accept partial
2128 # (because we're partial); before we only accepted full. Order
2129 # doen't matter (the list is sorted a few lines down) so these
2130 # loops could easily be merged. Or this could be a helper
2134 # Now we loop thru all the DSAs looking for
2135 # partial NC replicas that match the naming
2136 # context dn for (NC x)
2137 for dc_s
in self
.my_site
.dsa_table
.values():
2139 # If this partition NC (x) doesn't appear as a
2140 # replica (p) of NC (x) on the dsa DC (s) then
2142 if not nc_x
.nc_dnstr
in dc_s
.current_rep_table
:
2145 # Pull out the NCReplica with the dn that
2146 # matches NC (x) we are examining.
2147 p_of_x
= dc_s
.current_rep_table
[nc_x
.nc_dnstr
]
2149 # Replica (p) of NC (x) must be partial
2150 if not p_of_x
.is_partial():
2153 # Replica (p) of NC (x) must satisfy the
2154 # "is present" criteria for DC (s) that
2156 if not p_of_x
.is_present():
2159 # DC (s) must be a writable DSA other than
2160 # my DSA. In other words we'd only replicate
2161 # from other writable DSA
2162 if dc_s
.is_ro() or dc_s
is dc_local
:
2165 # Certain replica graphs are produced only
2166 # for global catalogs, so test against
2167 # method input parameter
2168 if gc_only
and not dc_s
.is_gc():
2171 # If we haven't been told to turn off stale connection
2172 # detection and this dsa has a stale connection then
2174 if detect_stale
and self
.is_stale_link_connection(dc_s
):
2177 # Replica meets criteria. Add it to table indexed
2178 # by the GUID of the DSA that it appears on
2179 r_list
.append(p_of_x
)
2181 # Append to R the NC replica that "should be present"
2183 r_list
.append(l_of_x
)
2185 r_list
.sort(sort_replica_by_dsa_guid
)
2188 max_node_edges
= self
.intrasite_max_node_edges(r_len
)
2190 # Add a node for each r_list element to the replica graph
2193 node
= GraphNode(rep
.rep_dsa_dnstr
, max_node_edges
)
2194 graph_list
.append(node
)
2196 # For each r(i) from (0 <= i < |R|-1)
2198 while i
< (r_len
-1):
2199 # Add an edge from r(i) to r(i+1) if r(i) is a full
2200 # replica or r(i+1) is a partial replica
2201 if not r_list
[i
].is_partial() or r_list
[i
+1].is_partial():
2202 graph_list
[i
+1].add_edge_from(r_list
[i
].rep_dsa_dnstr
)
2204 # Add an edge from r(i+1) to r(i) if r(i+1) is a full
2205 # replica or ri is a partial replica.
2206 if not r_list
[i
+1].is_partial() or r_list
[i
].is_partial():
2207 graph_list
[i
].add_edge_from(r_list
[i
+1].rep_dsa_dnstr
)
2210 # Add an edge from r|R|-1 to r0 if r|R|-1 is a full replica
2211 # or r0 is a partial replica.
2212 if not r_list
[r_len
-1].is_partial() or r_list
[0].is_partial():
2213 graph_list
[0].add_edge_from(r_list
[r_len
-1].rep_dsa_dnstr
)
2215 # Add an edge from r0 to r|R|-1 if r0 is a full replica or
2216 # r|R|-1 is a partial replica.
2217 if not r_list
[0].is_partial() or r_list
[r_len
-1].is_partial():
2218 graph_list
[r_len
-1].add_edge_from(r_list
[0].rep_dsa_dnstr
)
2220 DEBUG("r_list is length %s" % len(r_list
))
2221 DEBUG('\n'.join(str((x
.rep_dsa_guid
, x
.rep_dsa_dnstr
))
2224 do_dot_files
= self
.dot_file_dir
is not None and self
.debug
2225 if self
.verify
or do_dot_files
:
2227 dot_vertices
= set()
2228 for v1
in graph_list
:
2229 dot_vertices
.add(v1
.dsa_dnstr
)
2230 for v2
in v1
.edge_from
:
2231 dot_edges
.append((v2
, v1
.dsa_dnstr
))
2232 dot_vertices
.add(v2
)
2234 verify_properties
= ('connected', 'directed_double_ring_or_small')
2235 verify_and_dot('intrasite_pre_ntdscon', dot_edges
, dot_vertices
,
2236 label
='%s__%s__%s' % (site_local
.site_dnstr
,
2237 nctype_lut
[nc_x
.nc_type
],
2239 properties
=verify_properties
, debug
=DEBUG
,
2241 dot_file_dir
=self
.dot_file_dir
,
2244 # For each existing nTDSConnection object implying an edge
2245 # from rj of R to ri such that j != i, an edge from rj to ri
2246 # is not already in the graph, and the total edges directed
2247 # to ri is less than n+2, the KCC adds that edge to the graph.
2248 for vertex
in graph_list
:
2249 dsa
= self
.my_site
.dsa_table
[vertex
.dsa_dnstr
]
2250 for connect
in dsa
.connect_table
.values():
2251 remote
= connect
.from_dnstr
2252 if remote
in self
.my_site
.dsa_table
:
2253 vertex
.add_edge_from(remote
)
2255 DEBUG('reps are: %s' % ' '.join(x
.rep_dsa_dnstr
for x
in r_list
))
2256 DEBUG('dsas are: %s' % ' '.join(x
.dsa_dnstr
for x
in graph_list
))
2258 for tnode
in graph_list
:
2259 # To optimize replication latency in sites with many NC
2260 # replicas, the KCC adds new edges directed to ri to bring
2261 # the total edges to n+2, where the NC replica rk of R
2262 # from which the edge is directed is chosen at random such
2263 # that k != i and an edge from rk to ri is not already in
2266 # Note that the KCC tech ref does not give a number for
2267 # the definition of "sites with many NC replicas". At a
2268 # bare minimum to satisfy n+2 edges directed at a node we
2269 # have to have at least three replicas in |R| (i.e. if n
2270 # is zero then at least replicas from two other graph
2271 # nodes may direct edges to us).
2272 if r_len
>= 3 and not tnode
.has_sufficient_edges():
2273 candidates
= [x
for x
in graph_list
if
2275 x
.dsa_dnstr
not in tnode
.edge_from
)]
2277 debug
.DEBUG_BLUE("looking for random link for %s. r_len %d, "
2278 "graph len %d candidates %d"
2279 % (tnode
.dsa_dnstr
, r_len
, len(graph_list
),
2282 DEBUG("candidates %s" % [x
.dsa_dnstr
for x
in candidates
])
2284 while candidates
and not tnode
.has_sufficient_edges():
2285 other
= random
.choice(candidates
)
2286 DEBUG("trying to add candidate %s" % other
.dsa_dstr
)
2287 if not tnode
.add_edge_from(other
):
2288 debug
.DEBUG_RED("could not add %s" % other
.dsa_dstr
)
2289 candidates
.remove(other
)
2291 DEBUG_FN("not adding links to %s: nodes %s, links is %s/%s" %
2292 (tnode
.dsa_dnstr
, r_len
, len(tnode
.edge_from
),
2295 # Print the graph node in debug mode
2296 DEBUG_FN("%s" % tnode
)
2298 # For each edge directed to the local DC, ensure a nTDSConnection
2299 # points to us that satisfies the KCC criteria
2301 if tnode
.dsa_dnstr
== dc_local
.dsa_dnstr
:
2302 tnode
.add_connections_from_edges(dc_local
)
2304 if self
.verify
or do_dot_files
:
2306 dot_vertices
= set()
2307 for v1
in graph_list
:
2308 dot_vertices
.add(v1
.dsa_dnstr
)
2309 for v2
in v1
.edge_from
:
2310 dot_edges
.append((v2
, v1
.dsa_dnstr
))
2311 dot_vertices
.add(v2
)
2313 verify_properties
= ('connected', 'directed_double_ring_or_small')
2314 verify_and_dot('intrasite_post_ntdscon', dot_edges
, dot_vertices
,
2315 label
='%s__%s__%s' % (site_local
.site_dnstr
,
2316 nctype_lut
[nc_x
.nc_type
],
2318 properties
=verify_properties
, debug
=DEBUG
,
2320 dot_file_dir
=self
.dot_file_dir
,
2323 def intrasite(self
):
2324 """The head method for generating the intra-site KCC replica
2325 connection graph and attendant nTDSConnection objects
2331 DEBUG_FN("intrasite(): enter")
2333 # Test whether local site has topology disabled
2334 mysite
= self
.my_site
2335 if mysite
.is_intrasite_topology_disabled():
2338 detect_stale
= (not mysite
.is_detect_stale_disabled())
2339 for connect
in mydsa
.connect_table
.values():
2340 if connect
.to_be_added
:
2341 debug
.DEBUG_CYAN("TO BE ADDED:\n%s" % connect
)
2343 # Loop thru all the partitions, with gc_only False
2344 for partdn
, part
in self
.part_table
.items():
2345 self
.construct_intrasite_graph(mysite
, mydsa
, part
, False,
2347 for connect
in mydsa
.connect_table
.values():
2348 if connect
.to_be_added
:
2349 debug
.DEBUG_BLUE("TO BE ADDED:\n%s" % connect
)
2351 # If the DC is a GC server, the KCC constructs an additional NC
2352 # replica graph (and creates nTDSConnection objects) for the
2353 # config NC as above, except that only NC replicas that "are present"
2354 # on GC servers are added to R.
2355 for connect
in mydsa
.connect_table
.values():
2356 if connect
.to_be_added
:
2357 debug
.DEBUG_YELLOW("TO BE ADDED:\n%s" % connect
)
2359 # Do it again, with gc_only True
2360 for partdn
, part
in self
.part_table
.items():
2361 if part
.is_config():
2362 self
.construct_intrasite_graph(mysite
, mydsa
, part
, True,
2365 # The DC repeats the NC replica graph computation and nTDSConnection
2366 # creation for each of the NC replica graphs, this time assuming
2367 # that no DC has failed. It does so by re-executing the steps as
2368 # if the bit NTDSSETTINGS_OPT_IS_TOPL_DETECT_STALE_DISABLED were
2369 # set in the options attribute of the site settings object for
2370 # the local DC's site. (ie. we set "detec_stale" flag to False)
2371 for connect
in mydsa
.connect_table
.values():
2372 if connect
.to_be_added
:
2373 debug
.DEBUG_BLUE("TO BE ADDED:\n%s" % connect
)
2375 # Loop thru all the partitions.
2376 for partdn
, part
in self
.part_table
.items():
2377 self
.construct_intrasite_graph(mysite
, mydsa
, part
, False,
2378 False) # don't detect stale
2380 # If the DC is a GC server, the KCC constructs an additional NC
2381 # replica graph (and creates nTDSConnection objects) for the
2382 # config NC as above, except that only NC replicas that "are present"
2383 # on GC servers are added to R.
2384 for connect
in mydsa
.connect_table
.values():
2385 if connect
.to_be_added
:
2386 debug
.DEBUG_RED("TO BE ADDED:\n%s" % connect
)
2388 for partdn
, part
in self
.part_table
.items():
2389 if part
.is_config():
2390 self
.construct_intrasite_graph(mysite
, mydsa
, part
, True,
2391 False) # don't detect stale
2394 # Display any to be added or modified repsFrom
2395 for connect
in mydsa
.connect_table
.values():
2396 if connect
.to_be_deleted
:
2397 logger
.info("TO BE DELETED:\n%s" % connect
)
2398 if connect
.to_be_modified
:
2399 logger
.info("TO BE MODIFIED:\n%s" % connect
)
2400 if connect
.to_be_added
:
2401 debug
.DEBUG_GREEN("TO BE ADDED:\n%s" % connect
)
2403 mydsa
.commit_connections(self
.samdb
, ro
=True)
2405 # Commit any newly created connections to the samdb
2406 mydsa
.commit_connections(self
.samdb
)
2408 def list_dsas(self
):
2409 """Compile a comprehensive list of DSA DNs
2411 These are all the DSAs on all the sites that KCC would be
2414 This method is not idempotent and may not work correctly in
2415 sequence with KCC.run().
2417 :return: a list of DSA DN strings.
2422 self
.load_all_sites()
2423 self
.load_all_partitions()
2424 self
.load_all_transports()
2425 self
.load_all_sitelinks()
2427 for site
in self
.site_table
.values():
2428 dsas
.extend([dsa
.dsa_dnstr
.replace('CN=NTDS Settings,', '', 1)
2429 for dsa
in site
.dsa_table
.values()])
2432 def load_samdb(self
, dburl
, lp
, creds
):
2433 """Load the database using an url, loadparm, and credentials
2435 :param dburl: a database url.
2436 :param lp: a loadparm object.
2437 :param creds: a Credentials object.
2439 self
.samdb
= SamDB(url
=dburl
,
2440 session_info
=system_session(),
2441 credentials
=creds
, lp
=lp
)
2443 def plot_all_connections(self
, basename
, verify_properties
=()):
2444 """Helper function to plot and verify NTDSConnections
2446 :param basename: an identifying string to use in filenames and logs.
2447 :param verify_properties: properties to verify (default empty)
2449 verify
= verify_properties
and self
.verify
2450 if not verify
and self
.dot_file_dir
is None:
2458 for dsa
in self
.dsa_by_dnstr
.values():
2459 dot_vertices
.append(dsa
.dsa_dnstr
)
2461 vertex_colours
.append('#cc0000')
2463 vertex_colours
.append('#0000cc')
2464 for con
in dsa
.connect_table
.values():
2465 if con
.is_rodc_topology():
2466 edge_colours
.append('red')
2468 edge_colours
.append('blue')
2469 dot_edges
.append((con
.from_dnstr
, dsa
.dsa_dnstr
))
2471 verify_and_dot(basename
, dot_edges
, vertices
=dot_vertices
,
2472 label
=self
.my_dsa_dnstr
, properties
=verify_properties
,
2473 debug
=DEBUG
, verify
=verify
, dot_file_dir
=self
.dot_file_dir
,
2474 directed
=True, edge_colors
=edge_colours
,
2475 vertex_colors
=vertex_colours
)
2477 def run(self
, dburl
, lp
, creds
, forced_local_dsa
=None,
2478 forget_local_links
=False, forget_intersite_links
=False,
2479 attempt_live_connections
=False):
2480 """Perform a KCC run, possibly updating repsFrom topology
2482 :param dburl: url of the database to work with.
2483 :param lp: a loadparm object.
2484 :param creds: a Credentials object.
2485 :param forced_local_dsa: pretend to be on the DSA with this dn_str
2486 :param forget_local_links: calculate as if no connections existed
2487 (boolean, default False)
2488 :param forget_intersite_links: calculate with only intrasite connection
2489 (boolean, default False)
2490 :param attempt_live_connections: attempt to connect to remote DSAs to
2491 determine link availability (boolean, default False)
2492 :return: 1 on error, 0 otherwise
2494 # We may already have a samdb setup if we are
2495 # currently importing an ldif for a test run
2496 if self
.samdb
is None:
2498 self
.load_samdb(dburl
, lp
, creds
)
2499 except ldb
.LdbError
, (num
, msg
):
2500 logger
.error("Unable to open sam database %s : %s" %
2504 if forced_local_dsa
:
2505 self
.samdb
.set_ntds_settings_dn("CN=NTDS Settings,%s" %
2513 self
.load_all_sites()
2514 self
.load_all_partitions()
2515 self
.load_all_transports()
2516 self
.load_all_sitelinks()
2518 if self
.verify
or self
.dot_file_dir
is not None:
2520 for site
in self
.site_table
.values():
2521 guid_to_dnstr
.update((str(dsa
.dsa_guid
), dnstr
)
2523 in site
.dsa_table
.items())
2525 self
.plot_all_connections('dsa_initial')
2528 current_reps
, needed_reps
= self
.my_dsa
.get_rep_tables()
2529 for dnstr
, c_rep
in current_reps
.items():
2530 DEBUG("c_rep %s" % c_rep
)
2531 dot_edges
.append((self
.my_dsa
.dsa_dnstr
, dnstr
))
2533 verify_and_dot('dsa_repsFrom_initial', dot_edges
,
2534 directed
=True, label
=self
.my_dsa_dnstr
,
2535 properties
=(), debug
=DEBUG
, verify
=self
.verify
,
2536 dot_file_dir
=self
.dot_file_dir
)
2539 for site
in self
.site_table
.values():
2540 for dsa
in site
.dsa_table
.values():
2541 current_reps
, needed_reps
= dsa
.get_rep_tables()
2542 for dn_str
, rep
in current_reps
.items():
2543 for reps_from
in rep
.rep_repsFrom
:
2544 DEBUG("rep %s" % rep
)
2545 dsa_guid
= str(reps_from
.source_dsa_obj_guid
)
2546 dsa_dn
= guid_to_dnstr
[dsa_guid
]
2547 dot_edges
.append((dsa
.dsa_dnstr
, dsa_dn
))
2549 verify_and_dot('dsa_repsFrom_initial_all', dot_edges
,
2550 directed
=True, label
=self
.my_dsa_dnstr
,
2551 properties
=(), debug
=DEBUG
, verify
=self
.verify
,
2552 dot_file_dir
=self
.dot_file_dir
)
2555 for link
in self
.sitelink_table
.values():
2556 for a
, b
in itertools
.combinations(link
.site_list
, 2):
2557 dot_edges
.append((str(a
), str(b
)))
2558 properties
= ('connected',)
2559 verify_and_dot('dsa_sitelink_initial', dot_edges
,
2561 label
=self
.my_dsa_dnstr
, properties
=properties
,
2562 debug
=DEBUG
, verify
=self
.verify
,
2563 dot_file_dir
=self
.dot_file_dir
)
2565 if forget_local_links
:
2566 for dsa
in self
.my_site
.dsa_table
.values():
2567 dsa
.connect_table
= {k
: v
for k
, v
in
2568 dsa
.connect_table
.items()
2569 if v
.is_rodc_topology()}
2570 self
.plot_all_connections('dsa_forgotten_local')
2572 if forget_intersite_links
:
2573 for site
in self
.site_table
.values():
2574 for dsa
in site
.dsa_table
.values():
2575 dsa
.connect_table
= {k
: v
for k
, v
in
2576 dsa
.connect_table
.items()
2577 if site
is self
.my_site
and
2578 v
.is_rodc_topology()}
2580 self
.plot_all_connections('dsa_forgotten_all')
2582 if attempt_live_connections
:
2583 # Encapsulates lp and creds in a function that
2584 # attempts connections to remote DSAs.
2585 def ping(self
, dnsname
):
2587 drs_utils
.drsuapi_connect(dnsname
, self
.lp
, self
.creds
)
2588 except drs_utils
.drsException
:
2593 # These are the published steps (in order) for the
2594 # MS-TECH description of the KCC algorithm ([MS-ADTS] 6.2.2)
2597 self
.refresh_failed_links_connections(ping
)
2603 all_connected
= self
.intersite(ping
)
2606 self
.remove_unneeded_ntdsconn(all_connected
)
2609 self
.translate_ntdsconn()
2612 self
.remove_unneeded_failed_links_connections()
2615 self
.update_rodc_connection()
2617 if self
.verify
or self
.dot_file_dir
is not None:
2618 self
.plot_all_connections('dsa_final',
2619 ('connected', 'forest_of_rings'))
2621 debug
.DEBUG_MAGENTA("there are %d dsa guids" %
2626 my_dnstr
= self
.my_dsa
.dsa_dnstr
2627 current_reps
, needed_reps
= self
.my_dsa
.get_rep_tables()
2628 for dnstr
, n_rep
in needed_reps
.items():
2629 for reps_from
in n_rep
.rep_repsFrom
:
2630 guid_str
= str(reps_from
.source_dsa_obj_guid
)
2631 dot_edges
.append((my_dnstr
, guid_to_dnstr
[guid_str
]))
2632 edge_colors
.append('#' + str(n_rep
.nc_guid
)[:6])
2634 verify_and_dot('dsa_repsFrom_final', dot_edges
, directed
=True,
2635 label
=self
.my_dsa_dnstr
,
2636 properties
=(), debug
=DEBUG
, verify
=self
.verify
,
2637 dot_file_dir
=self
.dot_file_dir
,
2638 edge_colors
=edge_colors
)
2642 for site
in self
.site_table
.values():
2643 for dsa
in site
.dsa_table
.values():
2644 current_reps
, needed_reps
= dsa
.get_rep_tables()
2645 for n_rep
in needed_reps
.values():
2646 for reps_from
in n_rep
.rep_repsFrom
:
2647 dsa_guid
= str(reps_from
.source_dsa_obj_guid
)
2648 dsa_dn
= guid_to_dnstr
[dsa_guid
]
2649 dot_edges
.append((dsa
.dsa_dnstr
, dsa_dn
))
2651 verify_and_dot('dsa_repsFrom_final_all', dot_edges
,
2652 directed
=True, label
=self
.my_dsa_dnstr
,
2653 properties
=(), debug
=DEBUG
, verify
=self
.verify
,
2654 dot_file_dir
=self
.dot_file_dir
)
2661 def import_ldif(self
, dburl
, lp
, creds
, ldif_file
):
2662 """Import all objects and attributes that are relevent
2663 to the KCC algorithms from a previously exported LDIF file.
2665 The point of this function is to allow a programmer/debugger to
2666 import an LDIF file with non-security relevent information that
2667 was previously extracted from a DC database. The LDIF file is used
2668 to create a temporary abbreviated database. The KCC algorithm can
2669 then run against this abbreviated database for debug or test
2670 verification that the topology generated is computationally the
2671 same between different OSes and algorithms.
2673 :param dburl: path to the temporary abbreviated db to create
2674 :param ldif_file: path to the ldif file to import
2677 self
.samdb
= ldif_import_export
.ldif_to_samdb(dburl
, lp
, ldif_file
,
2678 self
.forced_local_dsa
)
2679 except ldif_import_export
.LdifError
, e
:
2684 def export_ldif(self
, dburl
, lp
, creds
, ldif_file
):
2685 """Routine to extract all objects and attributes that are relevent
2686 to the KCC algorithms from a DC database.
2688 The point of this function is to allow a programmer/debugger to
2689 extract an LDIF file with non-security relevent information from
2690 a DC database. The LDIF file can then be used to "import" via
2691 the import_ldif() function this file into a temporary abbreviated
2692 database. The KCC algorithm can then run against this abbreviated
2693 database for debug or test verification that the topology generated
2694 is computationally the same between different OSes and algorithms.
2696 :param dburl: LDAP database URL to extract info from
2697 :param ldif_file: output LDIF file name to create
2700 ldif_import_export
.samdb_to_ldif_file(self
.samdb
, dburl
, lp
, creds
,
2702 except ldif_import_export
.LdifError
, e
: