diff options
| author | Dave Craft <wimberosa@gmail.com> | 2012-01-11 08:11:35 -0600 |
|---|---|---|
| committer | Andrew Tridgell <tridge@samba.org> | 2012-01-14 07:45:11 +0100 |
| commit | ab1f896c5152dfd10609ac146eaaecd1bd2d5b70 (patch) | |
| tree | 1494d3ab9afb06a692171625876c4cb0b144367f /source4/scripting | |
| parent | aff8dad076f803e6deb2c5b59fa3bc5cb7ca7bd7 (diff) | |
| download | samba-ab1f896c5152dfd10609ac146eaaecd1bd2d5b70.tar.gz samba-ab1f896c5152dfd10609ac146eaaecd1bd2d5b70.tar.bz2 samba-ab1f896c5152dfd10609ac146eaaecd1bd2d5b70.zip | |
KCC importldif/exportldif and intersite topology
Add options for extracting an LDIF file from a database
and reimporting the LDIF into a schema-less database for
subsequent topology test/debug. Add intersite topology
generation with computation of ISTG and bridgehead servers
Signed-off-by: Andrew Tridgell <tridge@samba.org>
Autobuild-User: Andrew Tridgell <tridge@samba.org>
Autobuild-Date: Sat Jan 14 07:45:11 CET 2012 on sn-devel-104
Diffstat (limited to 'source4/scripting')
| -rwxr-xr-x | source4/scripting/bin/samba_kcc | 1629 | ||||
| -rw-r--r-- | source4/scripting/python/samba/kcc_utils.py | 1176 |
2 files changed, 2429 insertions, 376 deletions
diff --git a/source4/scripting/bin/samba_kcc b/source4/scripting/bin/samba_kcc index c17439e6376..583d88f5970 100755 --- a/source4/scripting/bin/samba_kcc +++ b/source4/scripting/bin/samba_kcc @@ -18,6 +18,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import os +import tempfile import sys import random import copy @@ -35,11 +36,15 @@ os.environ["TZ"] = "GMT" # Find right directory when running from source tree sys.path.insert(0, "bin/python") -import samba, ldb import optparse import logging -from samba import getopt as options +from samba import (getopt as options, \ + Ldb, \ + ldb, \ + dsdb, \ + param, \ + read_and_sub_file) from samba.auth import system_session from samba.samdb import SamDB from samba.dcerpc import drsuapi @@ -47,19 +52,25 @@ from samba.kcc_utils import * class KCC: """The Knowledge Consistency Checker class. A container for - objects and methods allowing a run of the KCC. Produces - a set of connections in the samdb for which the Distributed - Replication Service can then utilize to replicate naming - contexts + objects and methods allowing a run of the KCC. Produces + a set of connections in the samdb for which the Distributed + Replication Service can then utilize to replicate naming + contexts """ - def __init__(self, samdb): + def __init__(self): """Initializes the partitions class which can hold - our local DCs partitions or all the partitions in - the forest + our local DCs partitions or all the partitions in + the forest """ self.part_table = {} # partition objects self.site_table = {} self.transport_table = {} + self.sitelink_table = {} + + # Used in inter-site topology computation. A list + # of connections (by NTDSConnection object) that are + # to be kept when pruning un-needed NTDS Connections + self.keep_connection_list = [] self.my_dsa_dnstr = None # My dsa DN self.my_dsa = None # My dsa object @@ -67,18 +78,19 @@ class KCC: self.my_site_dnstr = None self.my_site = None - self.samdb = samdb + self.samdb = None return def load_all_transports(self): """Loads the inter-site transport objects for Sites - Raises an Exception on error + + ::returns: Raises an Exception on error """ try: - res = samdb.search("CN=Inter-Site Transports,CN=Sites,%s" % \ - samdb.get_config_basedn(), - scope=ldb.SCOPE_SUBTREE, - expression="(objectClass=interSiteTransport)") + res = self.samdb.search("CN=Inter-Site Transports,CN=Sites,%s" % \ + self.samdb.get_config_basedn(), + scope=ldb.SCOPE_SUBTREE, + expression="(objectClass=interSiteTransport)") except ldb.LdbError, (enum, estr): raise Exception("Unable to find inter-site transports - (%s)" % estr) @@ -91,7 +103,7 @@ class KCC: transport = Transport(dnstr) - transport.load_transport(samdb) + transport.load_transport(self.samdb) # Assign this transport to table # and index by dn @@ -99,27 +111,96 @@ class KCC: return + def load_all_sitelinks(self): + """Loads the inter-site siteLink objects + + ::returns: Raises an Exception on error + """ + try: + res = self.samdb.search("CN=Inter-Site Transports,CN=Sites,%s" % \ + self.samdb.get_config_basedn(), + scope=ldb.SCOPE_SUBTREE, + expression="(objectClass=siteLink)") + except ldb.LdbError, (enum, estr): + raise Exception("Unable to find inter-site siteLinks - (%s)" % estr) + + for msg in res: + dnstr = str(msg.dn) + + # already loaded + if dnstr in self.sitelink_table.keys(): + continue + + sitelink = SiteLink(dnstr) + + sitelink.load_sitelink(self.samdb) + + # Assign this siteLink to table + # and index by dn + self.sitelink_table[dnstr] = sitelink + + return + + def get_sitelink(self, site1_dnstr, site2_dnstr): + """Return the siteLink (if it exists) that connects the + two input site DNs + """ + for sitelink in self.sitelink_table.values(): + if sitelink.is_sitelink(site1_dnstr, site2_dnstr): + return sitelink + return None + def load_my_site(self): """Loads the Site class for the local DSA - Raises an Exception on error + + ::returns: Raises an Exception on error """ - self.my_site_dnstr = "CN=%s,CN=Sites,%s" % (samdb.server_site_name(), - samdb.get_config_basedn()) + self.my_site_dnstr = "CN=%s,CN=Sites,%s" % \ + (self.samdb.server_site_name(), + self.samdb.get_config_basedn()) site = Site(self.my_site_dnstr) - site.load_site(samdb) + site.load_site(self.samdb) self.site_table[self.my_site_dnstr] = site self.my_site = site return + def load_all_sites(self): + """Discover all sites and instantiate and load each + NTDS Site settings. + + ::returns: Raises an Exception on error + """ + try: + res = self.samdb.search("CN=Sites,%s" % + self.samdb.get_config_basedn(), + scope=ldb.SCOPE_SUBTREE, + expression="(objectClass=site)") + except ldb.LdbError, (enum, estr): + raise Exception("Unable to find sites - (%s)" % estr) + + for msg in res: + sitestr = str(msg.dn) + + # already loaded + if sitestr in self.site_table.keys(): + continue + + site = Site(sitestr) + site.load_site(self.samdb) + + self.site_table[sitestr] = site + return + def load_my_dsa(self): """Discover my nTDSDSA dn thru the rootDSE entry - Raises an Exception on error. + + ::returns: Raises an Exception on error. """ dn = ldb.Dn(self.samdb, "") try: - res = samdb.search(base=dn, scope=ldb.SCOPE_BASE, - attrs=["dsServiceName"]) + res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, + attrs=["dsServiceName"]) except ldb.LdbError, (enum, estr): raise Exception("Unable to find my nTDSDSA - (%s)" % estr) @@ -130,10 +211,12 @@ class KCC: def load_all_partitions(self): """Discover all NCs thru the Partitions dn and - instantiate and load the NCs. Each NC is inserted - into the part_table by partition dn string (not - the nCName dn string) - Raises an Exception on error + instantiate and load the NCs. + + Each NC is inserted into the part_table by partition + dn string (not the nCName dn string) + + ::returns: Raises an Exception on error """ try: res = self.samdb.search("CN=Partitions,%s" % @@ -157,7 +240,7 @@ class KCC: def should_be_present_test(self): """Enumerate all loaded partitions and DSAs in local - site and test if NC should be present as replica + site and test if NC should be present as replica """ for partdn, part in self.part_table.items(): for dsadn, dsa in self.my_site.dsa_table.items(): @@ -172,9 +255,9 @@ class KCC: def is_stale_link_connection(self, target_dsa): """Returns False if no tuple z exists in the kCCFailedLinks or - kCCFailedConnections variables such that z.UUIDDsa is the - objectGUID of the target dsa, z.FailureCount > 0, and - the current time - z.TimeFirstFailure > 2 hours. + kCCFailedConnections variables such that z.UUIDDsa is the + objectGUID of the target dsa, z.FailureCount > 0, and + the current time - z.TimeFirstFailure > 2 hours. """ # XXX - not implemented yet return False @@ -183,13 +266,147 @@ class KCC: # XXX - not implemented yet return - def remove_unneeded_ntdsconn(self): - # XXX - not implemented yet + def remove_unneeded_ntdsconn(self, all_connected): + """Removes unneeded NTDS Connections after computation + of KCC intra and inter-site topology has finished. + """ + mydsa = self.my_dsa + + # Loop thru connections + for cn_dnstr, cn_conn in mydsa.connect_table.items(): + + s_dnstr = cn_conn.get_from_dnstr() + if s_dnstr is None: + cn_conn.to_be_deleted = True + continue + + # Get the source DSA no matter what site + s_dsa = self.get_dsa(s_dnstr) + + # Check if the DSA is in our site + if self.my_site.same_site(s_dsa): + same_site = True + else: + same_site = False + + # Given an nTDSConnection object cn, if the DC with the + # nTDSDSA object dc that is the parent object of cn and + # the DC with the nTDSDA object referenced by cn!fromServer + # are in the same site, the KCC on dc deletes cn if all of + # the following are true: + # + # Bit NTDSCONN_OPT_IS_GENERATED is clear in cn!options. + # + # No site settings object s exists for the local DC's site, or + # bit NTDSSETTINGS_OPT_IS_TOPL_CLEANUP_DISABLED is clear in + # s!options. + # + # Another nTDSConnection object cn2 exists such that cn and + # cn2 have the same parent object, cn!fromServer = cn2!fromServer, + # and either + # + # cn!whenCreated < cn2!whenCreated + # + # cn!whenCreated = cn2!whenCreated and + # cn!objectGUID < cn2!objectGUID + # + # Bit NTDSCONN_OPT_RODC_TOPOLOGY is clear in cn!options + if same_site: + if cn_conn.is_generated() == False: + continue + + if self.my_site.is_cleanup_ntdsconn_disabled() == True: + continue + + # Loop thru connections looking for a duplicate that + # fulfills the previous criteria + lesser = False + + for cn2_dnstr, cn2_conn in mydsa.connect_table.items(): + if cn2_conn is cn_conn: + continue + + s2_dnstr = cn2_conn.get_from_dnstr() + if s2_dnstr is None: + continue + + # If the NTDS Connections has a different + # fromServer field then no match + if s2_dnstr != s_dnstr: + continue + + lesser = (cn_conn.whenCreated < cn2_conn.whenCreated or + (cn_conn.whenCreated == cn2_conn.whenCreated and + cmp(cn_conn.guid, cn2_conn.guid) < 0)) + + if lesser == True: + break + + if lesser and cn_conn.is_rodc_topology() == False: + cn_conn.to_be_deleted = True + + # Given an nTDSConnection object cn, if the DC with the nTDSDSA + # object dc that is the parent object of cn and the DC with + # the nTDSDSA object referenced by cn!fromServer are in + # different sites, a KCC acting as an ISTG in dc's site + # deletes cn if all of the following are true: + # + # Bit NTDSCONN_OPT_IS_GENERATED is clear in cn!options. + # + # cn!fromServer references an nTDSDSA object for a DC + # in a site other than the local DC's site. + # + # The keepConnections sequence returned by + # CreateIntersiteConnections() does not contain + # cn!objectGUID, or cn is "superseded by" (see below) + # another nTDSConnection cn2 and keepConnections + # contains cn2!objectGUID. + # + # The return value of CreateIntersiteConnections() + # was true. + # + # Bit NTDSCONN_OPT_RODC_TOPOLOGY is clear in + # cn!options + # + else: # different site + + if mydsa.is_istg() == False: + continue + + if cn_conn.is_generated() == False: + continue + + if self.keep_connection(cn_conn) == True: + continue + + # XXX - To be implemented + + if all_connected == False: + continue + + if cn_conn.is_rodc_topology() == False: + cn_conn.to_be_deleted = True + + + if opts.readonly: + for dnstr, connect in mydsa.connect_table.items(): + if connect.to_be_deleted == True: + logger.info("TO BE DELETED:\n%s" % connect) + if connect.to_be_added == True: + logger.info("TO BE ADDED:\n%s" % connect) + + # Peform deletion from our tables but perform + # no database modification + mydsa.commit_connections(self.samdb, ro=True) + else: + # Commit any modified connections + mydsa.commit_connections(self.samdb) + return def get_dsa_by_guidstr(self, guidstr): """Given a DSA guid string, consule all sites looking - for the corresponding DSA and return it. + for the corresponding DSA and return it. """ for site in self.site_table.values(): dsa = site.get_dsa_by_guidstr(guidstr) @@ -199,7 +416,7 @@ class KCC: def get_dsa(self, dnstr): """Given a DSA dn string, consule all sites looking - for the corresponding DSA and return it. + for the corresponding DSA and return it. """ for site in self.site_table.values(): dsa = site.get_dsa(dnstr) @@ -209,16 +426,18 @@ class KCC: def modify_repsFrom(self, n_rep, t_repsFrom, s_rep, s_dsa, cn_conn): """Update t_repsFrom if necessary to satisfy requirements. Such - updates are typically required when the IDL_DRSGetNCChanges - server has moved from one site to another--for example, to - enable compression when the server is moved from the - client's site to another site. - :param n_rep: NC replica we need - :param t_repsFrom: repsFrom tuple to modify - :param s_rep: NC replica at source DSA - :param s_dsa: source DSA - :param cn_conn: Local DSA NTDSConnection child - Returns (update) bit field containing which portion of the + updates are typically required when the IDL_DRSGetNCChanges + server has moved from one site to another--for example, to + enable compression when the server is moved from the + client's site to another site. + + :param n_rep: NC replica we need + :param t_repsFrom: repsFrom tuple to modify + :param s_rep: NC replica at source DSA + :param s_dsa: source DSA + :param cn_conn: Local DSA NTDSConnection child + + ::returns: (update) bit field containing which portion of the repsFrom was modified. This bit field is suitable as input to IDL_DRSReplicaModify ulModifyFields element, as it consists of these bits: @@ -229,7 +448,7 @@ class KCC: s_dnstr = s_dsa.dsa_dnstr update = 0x0 - if self.my_site.get_dsa(s_dnstr) is s_dsa: + if self.my_site.same_site(s_dsa): same_site = True else: same_site = False @@ -424,7 +643,7 @@ class KCC: t_repsFrom.transport_guid = x_transport.guid # See (NOTE MS-TECH INCORRECT) above - if x_transport.addr_attr == "dNSHostName": + if x_transport.address_attr == "dNSHostName": if t_repsFrom.version == 0x1: if t_repsFrom.dns_name1 is None or \ @@ -440,21 +659,21 @@ class KCC: else: # MS tech specification says we retrieve the named - # attribute in "addr_attr" from the parent of the - # DSA object + # attribute in "transportAddressAttribute" from the parent of + # the DSA object try: pdnstr = s_dsa.get_parent_dnstr() - attrs = [ x_transport.addr_attr ] + attrs = [ x_transport.address_attr ] res = self.samdb.search(base=pdnstr, scope=ldb.SCOPE_BASE, attrs=attrs) except ldb.ldbError, (enum, estr): raise Exception \ ("Unable to find attr (%s) for (%s) - (%s)" % \ - (x_transport.addr_attr, pdnstr, estr)) + (x_transport.address_attr, pdnstr, estr)) msg = res[0] - nastr = str(msg[x_transport.addr_attr][0]) + nastr = str(msg[x_transport.address_attr][0]) # See (NOTE MS-TECH INCORRECT) above if t_repsFrom.version == 0x1: @@ -474,14 +693,79 @@ class KCC: logger.debug("modify_repsFrom(): %s" % t_repsFrom) return + def is_repsFrom_implied(self, n_rep, cn_conn): + """Given a NC replica and NTDS Connection, determine if the connection + implies a repsFrom tuple should be present from the source DSA listed + in the connection to the naming context + + :param n_rep: NC replica + :param conn: NTDS Connection + ::returns (True || False), source DSA: + """ + # NTDS Connection must satisfy all the following criteria + # to imply a repsFrom tuple is needed: + # + # cn!enabledConnection = true. + # cn!options does not contain NTDSCONN_OPT_RODC_TOPOLOGY. + # cn!fromServer references an nTDSDSA object. + s_dsa = None + + if cn_conn.is_enabled() == True and \ + cn_conn.is_rodc_topology() == False: + + s_dnstr = cn_conn.get_from_dnstr() + if s_dnstr is not None: + s_dsa = self.get_dsa(s_dnstr) + + # No DSA matching this source DN string? + if s_dsa == None: + return False, None + + # To imply a repsFrom tuple is needed, each of these + # must be True: + # + # An NC replica of the NC "is present" on the DC to + # which the nTDSDSA object referenced by cn!fromServer + # corresponds. + # + # An NC replica of the NC "should be present" on + # the local DC + s_rep = s_dsa.get_current_replica(n_rep.nc_dnstr) + + if s_rep is None or s_rep.is_present() == False: + return False, None + + # To imply a repsFrom tuple is needed, each of these + # must be True: + # + # The NC replica on the DC referenced by cn!fromServer is + # a writable replica or the NC replica that "should be + # present" on the local DC is a partial replica. + # + # The NC is not a domain NC, the NC replica that + # "should be present" on the local DC is a partial + # replica, cn!transportType has no value, or + # cn!transportType has an RDN of CN=IP. + # + implied = (s_rep.is_ro() == False or n_rep.is_partial() == True) and \ + (n_rep.is_domain() == False or \ + n_rep.is_partial() == True or \ + cn_conn.transport_dnstr == None or \ + cn_conn.transport_dnstr.find("CN=IP") == 0) + + if implied: + return True, s_dsa + else: + return False, None + def translate_ntdsconn(self): """This function adjusts values of repsFrom abstract attributes of NC - replicas on the local DC to match those implied by - nTDSConnection objects. + replicas on the local DC to match those implied by + nTDSConnection objects. """ - logger.debug("translate_ntdsconn(): enter mydsa:\n%s" % self.my_dsa) + logger.debug("translate_ntdsconn(): enter") - if self.my_dsa.should_translate_ntdsconn() == False: + if self.my_dsa.is_translate_ntdsconn_disabled(): return current_rep_table, needed_rep_table = self.my_dsa.get_rep_tables() @@ -489,12 +773,6 @@ class KCC: # Filled in with replicas we currently have that need deleting delete_rep_table = {} - # Table of replicas needed, combined with our local information - # if we already have the replica. This may be a superset list of - # replicas if we need additional NC replicas that we currently - # don't have local copies for - translate_rep_table = {} - # We're using the MS notation names here to allow # correlation back to the published algorithm. # @@ -508,26 +786,16 @@ class KCC: # nTDSDSA object and (cn!fromServer = s) # s_rep - source DSA replica of n # - # Build a list of replicas that we will run translation - # against. If we have the replica and its not needed - # then we add it to the "to be deleted" list. Otherwise - # we have it and we need it so move it to the translate list + # If we have the replica and its not needed + # then we add it to the "to be deleted" list. for dnstr, n_rep in current_rep_table.items(): if dnstr not in needed_rep_table.keys(): delete_rep_table[dnstr] = n_rep - else: - translate_rep_table[dnstr] = n_rep - - # If we need the replica yet we don't have it (not in - # translate list) then add it - for dnstr, n_rep in needed_rep_table.items(): - if dnstr not in translate_rep_table.keys(): - translate_rep_table[dnstr] = n_rep # Now perform the scan of replicas we'll need # and compare any current repsFrom against the # connections - for dnstr, n_rep in translate_rep_table.items(): + for dnstr, n_rep in needed_rep_table.items(): # load any repsFrom and fsmo roles as we'll # need them during connection translation @@ -591,22 +859,8 @@ class KCC: # repsFrom is not already present for cn_dnstr, cn_conn in self.my_dsa.connect_table.items(): - # NTDS Connection must satisfy all the following criteria - # to imply a repsFrom tuple is needed: - # - # cn!enabledConnection = true. - # cn!options does not contain NTDSCONN_OPT_RODC_TOPOLOGY. - # cn!fromServer references an nTDSDSA object. - s_dsa = None - - if cn_conn.is_enabled() == True and \ - cn_conn.is_rodc_topology() == False: - - s_dnstr = cn_conn.get_from_dnstr() - if s_dnstr is not None: - s_dsa = self.get_dsa(s_dnstr) - - if s_dsa == None: + implied, s_dsa = self.is_repsFrom_implied(n_rep, cn_conn) + if implied == False: continue # Loop thru the existing repsFrom tupples (if any) and @@ -623,44 +877,6 @@ class KCC: if s_dsa == None: continue - # Source dsa is gone from config (strange) - # To imply a repsFrom tuple is needed, each of these - # must be True: - # - # An NC replica of the NC "is present" on the DC to - # which the nTDSDSA object referenced by cn!fromServer - # corresponds. - # - # An NC replica of the NC "should be present" on - # the local DC - s_rep = s_dsa.get_current_replica(n_rep.nc_dnstr) - - if s_rep is None or s_rep.is_present() == False: - continue - - # To imply a repsFrom tuple is needed, each of these - # must be True: - # - # The NC replica on the DC referenced by cn!fromServer is - # a writable replica or the NC replica that "should be - # present" on the local DC is a partial replica. - # - # The NC is not a domain NC, the NC replica that - # "should be present" on the local DC is a partial - # replica, cn!transportType has no value, or - # cn!transportType has an RDN of CN=IP. - # - implies = (s_rep.is_ro() == False or \ - n_rep.is_partial() == True) \ - and \ - (n_rep.is_domain() == False or\ - n_rep.is_partial() == True or \ - cn_conn.transport_dnstr == None or \ - cn_conn.transport_dnstr.find("CN=IP") == 0) - - if implies == False: - continue - # Create a new RepsFromTo and proceed to modify # it according to specification t_repsFrom = RepsFromTo(n_rep.nc_dnstr) @@ -673,22 +889,648 @@ class KCC: if t_repsFrom.is_modified(): n_rep.rep_repsFrom.append(t_repsFrom) - # Commit any modified repsFrom to the NC replica - if opts.readonly is None: + if opts.readonly: + # Display any to be deleted or modified repsFrom + text = n_rep.dumpstr_to_be_deleted() + if text: + logger.info("TO BE DELETED:\n%s" % text) + text = n_rep.dumpstr_to_be_modified() + if text: + logger.info("TO BE MODIFIED:\n%s" % text) + + # Peform deletion from our tables but perform + # no database modification + n_rep.commit_repsFrom(self.samdb, ro=True) + else: + # Commit any modified repsFrom to the NC replica n_rep.commit_repsFrom(self.samdb) return + def keep_connection(self, cn_conn): + """Determines if the connection is meant to be kept during the + pruning of unneeded connections operation. + + Consults the keep_connection_list[] which was built during + intersite NC replica graph computation. + + ::returns (True or False): if (True) connection should not be pruned + """ + if cn_conn in self.keep_connection_list: + return True + return False + + def merge_failed_links(self): + """Merge of kCCFailedLinks and kCCFailedLinks from bridgeheads. + The KCC on a writable DC attempts to merge the link and connection + failure information from bridgehead DCs in its own site to help it + identify failed bridgehead DCs. + """ + # MS-TECH Ref 6.2.2.3.2 Merge of kCCFailedLinks and kCCFailedLinks + # from Bridgeheads + + # XXX - not implemented yet + return + + def setup_graph(self): + """Set up a GRAPH, populated with a VERTEX for each site + object, a MULTIEDGE for each siteLink object, and a + MUTLIEDGESET for each siteLinkBridge object (or implied + siteLinkBridge). + + ::returns: a new graph + """ + # XXX - not implemented yet + return None + + def get_bridgehead(self, site, part, transport, \ + partial_ok, detect_failed): + """Get a bridghead DC. + + :param site: site object representing for which a bridgehead + DC is desired. + :param part: crossRef for NC to replicate. + :param transport: interSiteTransport object for replication + traffic. + :param partial_ok: True if a DC containing a partial + replica or a full replica will suffice, False if only + a full replica will suffice. + :param detect_failed: True to detect failed DCs and route + replication traffic around them, False to assume no DC + has failed. + ::returns: dsa object for the bridgehead DC or None + """ + + bhs = self.get_all_bridgeheads(site, part, transport, \ + partial_ok, detect_failed) + if len(bhs) == 0: + logger.debug("get_bridgehead: exit\n\tsitedn=%s\n\tbhdn=None" % \ + site.site_dnstr) + return None + else: + logger.debug("get_bridgehead: exit\n\tsitedn=%s\n\tbhdn=%s" % \ + (site.site_dnstr, bhs[0].dsa_dnstr)) + return bhs[0] + + def get_all_bridgeheads(self, site, part, transport, \ + partial_ok, detect_failed): + """Get all bridghead DCs satisfying the given criteria + + :param site: site object representing the site for which + bridgehead DCs are desired. + :param part: partition for NC to replicate. + :param transport: interSiteTransport object for + replication traffic. + :param partial_ok: True if a DC containing a partial + replica or a full replica will suffice, False if + only a full replica will suffice. + :param detect_ok: True to detect failed DCs and route + replication traffic around them, FALSE to assume + no DC has failed. + ::returns: list of dsa object for available bridgehead + DCs or None + """ + + bhs = [] + + logger.debug("get_all_bridgeheads: %s" % transport) + + for key, dsa in site.dsa_table.items(): + + pdnstr = dsa.get_parent_dnstr() + + # IF t!bridgeheadServerListBL has one or more values and + # t!bridgeheadServerListBL does not contain a reference + # to the parent object of dc then skip dc + if len(transport.bridgehead_list) != 0 and \ + pdnstr not in transport.bridgehead_list: + continue + + # IF dc is in the same site as the local DC + # IF a replica of cr!nCName is not in the set of NC replicas + # that "should be present" on dc or a partial replica of the + # NC "should be present" but partialReplicasOkay = FALSE + # Skip dc + if self.my_site.same_site(dsa): + needed, ro, partial = part.should_be_present(dsa) + if needed == False or (partial == True and partial_ok == False): + continue + + # ELSE + # IF an NC replica of cr!nCName is not in the set of NC + # replicas that "are present" on dc or a partial replica of + # the NC "is present" but partialReplicasOkay = FALSE + # Skip dc + else: + rep = dsa.get_current_replica(part.nc_dnstr) + if rep is None or (rep.is_partial() and partial_ok == False): + continue + + # IF AmIRODC() and cr!nCName corresponds to default NC then + # Let dsaobj be the nTDSDSA object of the dc + # IF dsaobj.msDS-Behavior-Version < DS_BEHAVIOR_WIN2008 + # Skip dc + if self.my_dsa.is_ro() and part.is_default(): + if dsa.is_minimum_behavior(DS_BEHAVIOR_WIN2008) == False: + continue + + # IF t!name != "IP" and the parent object of dc has no value for + # the attribute specified by t!transportAddressAttribute + # Skip dc + if transport.name != "IP": + # MS tech specification says we retrieve the named + # attribute in "transportAddressAttribute" from the parent + # of the DSA object + try: + attrs = [ transport.address_attr ] + + res = self.samdb.search(base=pdnstr, scope=ldb.SCOPE_BASE, + attrs=attrs) + except ldb.ldbError, (enum, estr): + continue + + msg = res[0] + nastr = str(msg[transport.address_attr][0]) + + # IF BridgeheadDCFailed(dc!objectGUID, detectFailedDCs) = TRUE + # Skip dc + if self.is_bridgehead_failed(dsa, detect_failed) == True: + continue + + logger.debug("get_all_bridgeheads: dsadn=%s" % dsa.dsa_dnstr) + bhs.append(dsa) + + # IF bit NTDSSETTINGS_OPT_IS_RAND_BH_SELECTION_DISABLED is set in + # s!options + # SORT bhs such that all GC servers precede DCs that are not GC + # servers, and otherwise by ascending objectGUID + # ELSE + # SORT bhs in a random order + if site.is_random_bridgehead_disabled() == True: + bhs.sort(sort_dsa_by_gc_and_guid) + else: + random.shuffle(bhs) + + return bhs + + + def is_bridgehead_failed(self, dsa, detect_failed): + """Determine whether a given DC is known to be in a failed state + ::returns: True if and only if the DC should be considered failed + """ + # XXX - not implemented yet + return False + + def create_connection(self, part, rbh, rsite, transport, \ + lbh, lsite, link_opt, link_sched, \ + partial_ok, detect_failed): + """Create an nTDSConnection object with the given parameters + if one does not already exist. + + :param part: crossRef object for the NC to replicate. |
