From 819f11285d12041f2a22a6c92ebabb8a559886c5 Mon Sep 17 00:00:00 2001 From: Dave Craft Date: Sun, 4 Dec 2011 11:08:56 -0600 Subject: samba_kcc NTDSConnection translation This is an advancement of samba_kcc to compute and commit the modification of a repsFrom on an NC Replica. The repsFrom is computed according to the MS tech spec for implied replicas of NTDSConnections. Proper maintenance of (DRS options, schedules, etc) from a NTDSConnection are now all present. New classes for inter-site transports, sites, and repsFrom) are now present in kcc_utils.py. Substantively this gets intra-site topology generation functional by committing the repsFrom that were computed from the DSA graph implemented in prior drops of samba_kcc Signed-off-by: Andrew Tridgell --- source4/scripting/bin/samba_kcc | 720 ++++++++++++++++++++++++++++++++++------ 1 file changed, 610 insertions(+), 110 deletions(-) (limited to 'source4/scripting/bin/samba_kcc') diff --git a/source4/scripting/bin/samba_kcc b/source4/scripting/bin/samba_kcc index c024cd41ef..c17439e637 100755 --- a/source4/scripting/bin/samba_kcc +++ b/source4/scripting/bin/samba_kcc @@ -20,6 +20,7 @@ import os import sys import random +import copy # ensure we get messages out immediately, so they get in the samba logs, # and don't get swallowed by a timeout @@ -41,6 +42,7 @@ import logging from samba import getopt as options from samba.auth import system_session from samba.samdb import SamDB +from samba.dcerpc import drsuapi from samba.kcc_utils import * class KCC: @@ -55,12 +57,47 @@ class KCC: our local DCs partitions or all the partitions in the forest """ - self.dsa_table = {} # dsa objects - self.part_table = {} # partition objects - self.site_table = {} + self.part_table = {} # partition objects + self.site_table = {} + self.transport_table = {} + self.my_dsa_dnstr = None # My dsa DN + self.my_dsa = None # My dsa object + self.my_site_dnstr = None + self.my_site = None + self.samdb = samdb + return + + def load_all_transports(self): + """Loads the inter-site transport objects for Sites + 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)") + except ldb.LdbError, (enum, estr): + raise Exception("Unable to find inter-site transports - (%s)" % estr) + + for msg in res: + dnstr = str(msg.dn) + + # already loaded + if dnstr in self.transport_table.keys(): + continue + + transport = Transport(dnstr) + + transport.load_transport(samdb) + + # Assign this transport to table + # and index by dn + self.transport_table[dnstr] = transport + + return def load_my_site(self): """Loads the Site class for the local DSA @@ -69,14 +106,14 @@ class KCC: self.my_site_dnstr = "CN=%s,CN=Sites,%s" % (samdb.server_site_name(), samdb.get_config_basedn()) site = Site(self.my_site_dnstr) - site.load_site(samdb) + self.site_table[self.my_site_dnstr] = site + self.my_site = site + return def load_my_dsa(self): - """Discover my nTDSDSA thru the rootDSE entry and - instantiate and load the DSA. The dsa is inserted - into the dsa_table by dn string + """Discover my nTDSDSA dn thru the rootDSE entry Raises an Exception on error. """ dn = ldb.Dn(self.samdb, "") @@ -86,49 +123,10 @@ class KCC: except ldb.LdbError, (enum, estr): raise Exception("Unable to find my nTDSDSA - (%s)" % estr) - dnstr = res[0]["dsServiceName"][0] - - # already loaded - if dnstr in self.dsa_table.keys(): - return - - self.my_dsa_dnstr = dnstr - dsa = DirectoryServiceAgent(dnstr) - - dsa.load_dsa(samdb) - - # Assign this dsa to my dsa table - # and index by dsa dn - self.dsa_table[dnstr] = dsa - - def load_all_dsa(self): - """Discover all nTDSDSA thru the sites entry and - instantiate and load the DSAs. Each dsa is inserted - into the dsa_table by dn string. - Raises an Exception on error. - """ - try: - res = self.samdb.search("CN=Sites,%s" % - self.samdb.get_config_basedn(), - scope=ldb.SCOPE_SUBTREE, - expression="(objectClass=nTDSDSA)") - except ldb.LdbError, (enum, estr): - raise Exception("Unable to find nTDSDSAs - (%s)" % estr) + self.my_dsa_dnstr = res[0]["dsServiceName"][0] + self.my_dsa = self.my_site.get_dsa(self.my_dsa_dnstr) - for msg in res: - dnstr = str(msg.dn) - - # already loaded - if dnstr in self.dsa_table.keys(): - continue - - dsa = DirectoryServiceAgent(dnstr) - - dsa.load_dsa(self.samdb) - - # Assign this dsa to my dsa table - # and index by dsa dn - self.dsa_table[dnstr] = dsa + return def load_all_partitions(self): """Discover all NCs thru the Partitions dn and @@ -158,16 +156,15 @@ class KCC: self.part_table[partstr] = part def should_be_present_test(self): - """Enumerate all loaded partitions and DSAs and test - if NC should be present as replica + """Enumerate all loaded partitions and DSAs in local + site and test if NC should be present as replica """ for partdn, part in self.part_table.items(): - - for dsadn, dsa in self.dsa_table.items(): + for dsadn, dsa in self.my_site.dsa_table.items(): needed, ro, partial = part.should_be_present(dsa) - logger.info("dsadn:%s\nncdn:%s\nneeded=%s:ro=%s:partial=%s\n" % \ - (dsa.dsa_dnstr, part.nc_dnstr, needed, ro, partial)) + (dsadn, part.nc_dnstr, needed, ro, partial)) + return def refresh_failed_links_connections(self): # XXX - not implemented yet @@ -186,12 +183,500 @@ class KCC: # XXX - not implemented yet return - def remove_unneeded_ntds_connections(self): + def remove_unneeded_ntdsconn(self): # XXX - not implemented yet return - def translate_connections(self): - # XXX - not implemented yet + def get_dsa_by_guidstr(self, guidstr): + """Given a DSA guid string, consule all sites looking + for the corresponding DSA and return it. + """ + for site in self.site_table.values(): + dsa = site.get_dsa_by_guidstr(guidstr) + if dsa is not None: + return dsa + return None + + def get_dsa(self, dnstr): + """Given a DSA dn string, consule all sites looking + for the corresponding DSA and return it. + """ + for site in self.site_table.values(): + dsa = site.get_dsa(dnstr) + if dsa is not None: + return dsa + return None + + 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 + repsFrom was modified. This bit field is suitable as input + to IDL_DRSReplicaModify ulModifyFields element, as it consists + of these bits: + drsuapi.DRSUAPI_DRS_UPDATE_SCHEDULE + drsuapi.DRSUAPI_DRS_UPDATE_FLAGS + drsuapi.DRSUAPI_DRS_UPDATE_ADDRESS + """ + s_dnstr = s_dsa.dsa_dnstr + update = 0x0 + + if self.my_site.get_dsa(s_dnstr) is s_dsa: + same_site = True + else: + same_site = False + + times = cn_conn.convert_schedule_to_repltimes() + + # if schedule doesn't match then update and modify + if times != t_repsFrom.schedule: + t_repsFrom.schedule = times + + # Bit DRS_PER_SYNC is set in replicaFlags if and only + # if nTDSConnection schedule has a value v that specifies + # scheduled replication is to be performed at least once + # per week. + if cn_conn.is_schedule_minimum_once_per_week(): + + if (t_repsFrom.replica_flags & \ + drsuapi.DRSUAPI_DRS_PER_SYNC) == 0x0: + t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_PER_SYNC + + # Bit DRS_INIT_SYNC is set in t.replicaFlags if and only + # if the source DSA and the local DC's nTDSDSA object are + # in the same site or source dsa is the FSMO role owner + # of one or more FSMO roles in the NC replica. + if same_site or n_rep.is_fsmo_role_owner(s_dnstr): + + if (t_repsFrom.replica_flags & \ + drsuapi.DRSUAPI_DRS_INIT_SYNC) == 0x0: + t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_INIT_SYNC + + # If bit NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT is set in + # cn!options, bit DRS_NEVER_NOTIFY is set in t.replicaFlags + # if and only if bit NTDSCONN_OPT_USE_NOTIFY is clear in + # cn!options. Otherwise, bit DRS_NEVER_NOTIFY is set in + # t.replicaFlags if and only if s and the local DC's + # nTDSDSA object are in different sites. + if (cn_conn.options & dsdb.NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT) != 0x0: + + if (cn_conn.option & dsdb.NTDSCONN_OPT_USE_NOTIFY) == 0x0: + + if (t_repsFrom.replica_flags & \ + drsuapi.DRSUAPI_DRS_NEVER_NOTIFY) == 0x0: + t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_NEVER_NOTIFY + + elif same_site == False: + + if (t_repsFrom.replica_flags & \ + drsuapi.DRSUAPI_DRS_NEVER_NOTIFY) == 0x0: + t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_NEVER_NOTIFY + + # Bit DRS_USE_COMPRESSION is set in t.replicaFlags if + # and only if s and the local DC's nTDSDSA object are + # not in the same site and the + # NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION bit is + # clear in cn!options + if same_site == False and \ + (cn_conn.options & \ + dsdb.NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION) == 0x0: + + if (t_repsFrom.replica_flags & \ + drsuapi.DRSUAPI_DRS_USE_COMPRESSION) == 0x0: + t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_USE_COMPRESSION + + # Bit DRS_TWOWAY_SYNC is set in t.replicaFlags if and only + # if bit NTDSCONN_OPT_TWOWAY_SYNC is set in cn!options. + if (cn_conn.options & dsdb.NTDSCONN_OPT_TWOWAY_SYNC) != 0x0: + + if (t_repsFrom.replica_flags & \ + drsuapi.DRSUAPI_DRS_TWOWAY_SYNC) == 0x0: + t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_TWOWAY_SYNC + + # Bits DRS_DISABLE_AUTO_SYNC and DRS_DISABLE_PERIODIC_SYNC are + # set in t.replicaFlags if and only if cn!enabledConnection = false. + if cn_conn.is_enabled() == False: + + if (t_repsFrom.replica_flags & \ + drsuapi.DRSUAPI_DRS_DISABLE_AUTO_SYNC) == 0x0: + t_repsFrom.replica_flags |= \ + drsuapi.DRSUAPI_DRS_DISABLE_AUTO_SYNC + + if (t_repsFrom.replica_flags & \ + drsuapi.DRSUAPI_DRS_DISABLE_PERIODIC_SYNC) == 0x0: + t_repsFrom.replica_flags |= \ + drsuapi.DRSUAPI_DRS_DISABLE_PERIODIC_SYNC + + # If s and the local DC's nTDSDSA object are in the same site, + # cn!transportType has no value, or the RDN of cn!transportType + # is CN=IP: + # + # Bit DRS_MAIL_REP in t.replicaFlags is clear. + # + # t.uuidTransport = NULL GUID. + # + # t.uuidDsa = The GUID-based DNS name of s. + # + # Otherwise: + # + # Bit DRS_MAIL_REP in t.replicaFlags is set. + # + # If x is the object with dsname cn!transportType, + # t.uuidTransport = x!objectGUID. + # + # Let a be the attribute identified by + # x!transportAddressAttribute. If a is + # the dNSHostName attribute, t.uuidDsa = the GUID-based + # DNS name of s. Otherwise, t.uuidDsa = (s!parent)!a. + # + # It appears that the first statement i.e. + # + # "If s and the local DC's nTDSDSA object are in the same + # site, cn!transportType has no value, or the RDN of + # cn!transportType is CN=IP:" + # + # could be a slightly tighter statement if it had an "or" + # between each condition. I believe this should + # be interpreted as: + # + # IF (same-site) OR (no-value) OR (type-ip) + # + # because IP should be the primary transport mechanism + # (even in inter-site) and the absense of the transportType + # attribute should always imply IP no matter if its multi-site + # + # NOTE MS-TECH INCORRECT: + # + # All indications point to these statements above being + # incorrectly stated: + # + # t.uuidDsa = The GUID-based DNS name of s. + # + # Let a be the attribute identified by + # x!transportAddressAttribute. If a is + # the dNSHostName attribute, t.uuidDsa = the GUID-based + # DNS name of s. Otherwise, t.uuidDsa = (s!parent)!a. + # + # because the uuidDSA is a GUID and not a GUID-base DNS + # name. Nor can uuidDsa hold (s!parent)!a if not + # dNSHostName. What should have been said is: + # + # t.naDsa = The GUID-based DNS name of s + # + # That would also be correct if transportAddressAttribute + # were "mailAddress" because (naDsa) can also correctly + # hold the SMTP ISM service address. + # + nastr = "%s._msdcs.%s" % (s_dsa.dsa_guid, self.samdb.forest_dns_name()) + + # We're not currently supporting SMTP replication + # so is_smtp_replication_available() is currently + # always returning False + if same_site == True or \ + cn_conn.transport_dnstr == None or \ + cn_conn.transport_dnstr.find("CN=IP") == 0 or \ + is_smtp_replication_available() == False: + + if (t_repsFrom.replica_flags & \ + drsuapi.DRSUAPI_DRS_MAIL_REP) != 0x0: + t_repsFrom.replica_flags &= ~drsuapi.DRSUAPI_DRS_MAIL_REP + + null_guid = misc.GUID() + if t_repsFrom.transport_guid is None or \ + t_repsFrom.transport_guid != null_guid: + t_repsFrom.transport_guid = null_guid + + # See (NOTE MS-TECH INCORRECT) above + if t_repsFrom.version == 0x1: + if t_repsFrom.dns_name1 is None or \ + t_repsFrom.dns_name1 != nastr: + t_repsFrom.dns_name1 = nastr + else: + if t_repsFrom.dns_name1 is None or \ + t_repsFrom.dns_name2 is None or \ + t_repsFrom.dns_name1 != nastr or \ + t_repsFrom.dns_name2 != nastr: + t_repsFrom.dns_name1 = nastr + t_repsFrom.dns_name2 = nastr + + else: + if (t_repsFrom.replica_flags & \ + drsuapi.DRSUAPI_DRS_MAIL_REP) == 0x0: + t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_MAIL_REP + + # We have a transport type but its not an + # object in the database + if cn_conn.transport_dnstr not in self.transport_table.keys(): + raise Exception("Missing inter-site transport - (%s)" % \ + cn_conn.transport_dnstr) + + x_transport = self.transport_table[cn_conn.transport_dnstr] + + if t_repsFrom.transport_guid != x_transport.guid: + t_repsFrom.transport_guid = x_transport.guid + + # See (NOTE MS-TECH INCORRECT) above + if x_transport.addr_attr == "dNSHostName": + + if t_repsFrom.version == 0x1: + if t_repsFrom.dns_name1 is None or \ + t_repsFrom.dns_name1 != nastr: + t_repsFrom.dns_name1 = nastr + else: + if t_repsFrom.dns_name1 is None or \ + t_repsFrom.dns_name2 is None or \ + t_repsFrom.dns_name1 != nastr or \ + t_repsFrom.dns_name2 != nastr: + t_repsFrom.dns_name1 = nastr + t_repsFrom.dns_name2 = nastr + + else: + # MS tech specification says we retrieve the named + # attribute in "addr_attr" from the parent of the + # DSA object + try: + pdnstr = s_dsa.get_parent_dnstr() + attrs = [ x_transport.addr_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)) + + msg = res[0] + nastr = str(msg[x_transport.addr_attr][0]) + + # See (NOTE MS-TECH INCORRECT) above + if t_repsFrom.version == 0x1: + if t_repsFrom.dns_name1 is None or \ + t_repsFrom.dns_name1 != nastr: + t_repsFrom.dns_name1 = nastr + else: + if t_repsFrom.dns_name1 is None or \ + t_repsFrom.dns_name2 is None or \ + t_repsFrom.dns_name1 != nastr or \ + t_repsFrom.dns_name2 != nastr: + + t_repsFrom.dns_name1 = nastr + t_repsFrom.dns_name2 = nastr + + if t_repsFrom.is_modified(): + logger.debug("modify_repsFrom(): %s" % t_repsFrom) + return + + 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. + """ + logger.debug("translate_ntdsconn(): enter mydsa:\n%s" % self.my_dsa) + + if self.my_dsa.should_translate_ntdsconn() == False: + return + + current_rep_table, needed_rep_table = self.my_dsa.get_rep_tables() + + # 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. + # + # n_rep - NC replica (n) + # t_repsFrom - tuple (t) in n!repsFrom + # s_dsa - Source DSA of the replica. Defined as nTDSDSA + # object (s) such that (s!objectGUID = t.uuidDsa) + # In our IDL representation of repsFrom the (uuidDsa) + # attribute is called (source_dsa_obj_guid) + # cn_conn - (cn) is nTDSConnection object and child of the local DC's + # 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 + 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(): + + # load any repsFrom and fsmo roles as we'll + # need them during connection translation + n_rep.load_repsFrom(self.samdb) + n_rep.load_fsmo_roles(self.samdb) + + # Loop thru the existing repsFrom tupples (if any) + for i, t_repsFrom in enumerate(n_rep.rep_repsFrom): + + # for each tuple t in n!repsFrom, let s be the nTDSDSA + # object such that s!objectGUID = t.uuidDsa + guidstr = str(t_repsFrom.source_dsa_obj_guid) + s_dsa = self.get_dsa_by_guidstr(guidstr) + + # Source dsa is gone from config (strange) + # so cleanup stale repsFrom for unlisted DSA + if s_dsa is None: + logger.debug("repsFrom source DSA guid (%s) not found" % \ + guidstr) + t_repsFrom.to_be_deleted = True + continue + + s_dnstr = s_dsa.dsa_dnstr + + # Retrieve my DSAs connection object (if it exists) + # that specifies the fromServer equivalent to + # the DSA that is specified in the repsFrom source + cn_conn = self.my_dsa.get_connection_by_from_dnstr(s_dnstr) + + # Let (cn) be the nTDSConnection object such that (cn) + # is a child of the local DC's nTDSDSA object and + # (cn!fromServer = s) and (cn!options) does not contain + # NTDSCONN_OPT_RODC_TOPOLOGY or NULL if no such (cn) exists. + if cn_conn and cn_conn.is_rodc_topology() == True: + cn_conn = None + + # KCC removes this repsFrom tuple if any of the following + # is true: + # cn = NULL. + # + # No NC replica of the NC "is present" on DSA that + # would be source of replica + # + # A writable replica of the NC "should be present" on + # the local DC, but a partial replica "is present" on + # the source DSA + s_rep = s_dsa.get_current_replica(n_rep.nc_dnstr) + + if cn_conn is None or \ + s_rep is None or s_rep.is_present() == False or \ + (n_rep.is_ro() == False and s_rep.is_partial() == True): + + t_repsFrom.to_be_deleted = True + continue + + # If the KCC did not remove t from n!repsFrom, it updates t + self.modify_repsFrom(n_rep, t_repsFrom, s_rep, s_dsa, cn_conn) + + # Loop thru connections and add implied repsFrom tuples + # for each NTDSConnection under our local DSA if the + # 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: + continue + + # Loop thru the existing repsFrom tupples (if any) and + # if we already have a tuple for this connection then + # no need to proceed to add. It will have been changed + # to have the correct attributes above + for i, t_repsFrom in enumerate(n_rep.rep_repsFrom): + + guidstr = str(t_repsFrom.source_dsa_obj_guid) + if s_dsa is self.get_dsa_by_guidstr(guidstr): + s_dsa = None + break + + 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) + + t_repsFrom.source_dsa_obj_guid = s_dsa.dsa_guid + + self.modify_repsFrom(n_rep, t_repsFrom, s_rep, s_dsa, cn_conn) + + # Add to our NC repsFrom as this is newly computed + 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: + n_rep.commit_repsFrom(self.samdb) + return def intersite(self): @@ -260,11 +745,13 @@ class KCC: # partition (NC x) then continue needed, ro, partial = nc_x.should_be_present(dc_local) - logger.debug("construct_intrasite_graph:\n" + \ - "nc_x: %s\ndc_local: %s\n" % \ - (nc_x, dc_local) + \ - "gc_only: %s\nneeded: %s\nro: %s\npartial: %s" % \ - (gc_only, needed, ro, partial)) + logger.debug("construct_intrasite_graph(): enter" + \ + "\n\tgc_only=%d" % gc_only + \ + "\n\tdetect_stale=%d" % detect_stale + \ + "\n\tneeded=%s" % needed + \ + "\n\tro=%s" % ro + \ + "\n\tpartial=%s" % partial + \ + "\n%s" % nc_x) if needed == False: return @@ -279,6 +766,10 @@ class KCC: l_of_x.rep_partial = partial l_of_x.rep_ro = ro + # Add this replica that "should be present" to the + # needed replica table for this DSA + dc_local.add_needed_replica(l_of_x) + # Empty replica sequence list r_list = [] @@ -286,16 +777,16 @@ class KCC: # writeable NC replicas that match the naming # context dn for (nc_x) # - for dc_s_dn, dc_s in self.dsa_table.items(): + for dc_s_dn, dc_s in self.my_site.dsa_table.items(): # If this partition (nc_x) doesn't appear as a # replica (f_of_x) on (dc_s) then continue - if not nc_x.nc_dnstr in dc_s.rep_table.keys(): + if not nc_x.nc_dnstr in dc_s.current_rep_table.keys(): continue # Pull out the NCReplica (f) of (x) with the dn # that matches NC (x) we are examining. - f_of_x = dc_s.rep_table[nc_x.nc_dnstr] + f_of_x = dc_s.current_rep_table[nc_x.nc_dnstr] # Replica (f) of NC (x) must be writable if f_of_x.is_ro() == True: @@ -320,10 +811,9 @@ class KCC: continue # DC (s) must be in the same site as the local DC - # This is the intra-site algorithm. We are not - # replicating across multiple sites - if site_local.is_same_site(dc_s) == False: - continue + # as this is the intra-site algorithm. This is + # handled by virtue of placing DSAs in per + # site objects (see enclosing for() loop) # If NC (x) is intended to be read-only full replica # for a domain NC on the target DC then the source @@ -361,17 +851,17 @@ class KCC: # Now we loop thru all the DSAs looking for # partial NC replicas that match the naming # context dn for (NC x) - for dc_s_dn, dc_s in self.dsa_table.items(): + for dc_s_dn, dc_s in self.my_site.dsa_table.items(): # If this partition NC (x) doesn't appear as a # replica (p) of NC (x) on the dsa DC (s) then # continue - if not nc_x.nc_dnstr in dc_s.rep_table.keys(): + if not nc_x.nc_dnstr in dc_s.current_rep_table.keys(): continue # Pull out the NCReplica with the dn that # matches NC (x) we are examining. - p_of_x = dsa.rep_table[nc_x.nc_dnstr] + p_of_x = dsa.current_rep_table[nc_x.nc_dnstr] # Replica (p) of NC (x) must be partial if p_of_x.is_partial() == False: @@ -396,10 +886,9 @@ class KCC: continue # DC (s) must be in the same site as the local DC - # This is the intra-site algorithm. We are not - # replicating across multiple sites - if site_local.is_same_site(dc_s) == False: - continue + # as this is the intra-site algorithm. This is + # handled by virtue of placing DSAs in per + # site objects (see enclosing for() loop) # This criteria is moot (a no-op) for this case # because we are scanning for (partial = True). The @@ -476,7 +965,7 @@ class KCC: # to ri is less than n+2, the KCC adds that edge to the graph. i = 0 while i < r_len: - dsa = self.dsa_table[graph_list[i].dsa_dnstr] + dsa = self.my_site.dsa_table[graph_list[i].dsa_dnstr] graph_list[i].add_edges_from_connections(dsa) i = i + 1 @@ -533,9 +1022,9 @@ class KCC: in the samdb """ # Retrieve my DSA - mydsa = self.dsa_table[self.my_dsa_dnstr] + mydsa = self.my_dsa - logger.debug("intrasite enter:\nmydsa: %s" % mydsa) + logger.debug("intrasite(): enter mydsa:\n%s" % mydsa) # Test whether local site has topology disabled mysite = self.site_table[self.my_site_dnstr] @@ -584,52 +1073,51 @@ class KCC: False) # don't detect stale # Commit any newly created connections to the samdb - mydsa.commit_connection_table(self.samdb) - - logger.debug("intrasite exit:\nmydsa: %s" % mydsa) + if opts.readonly is None: + mydsa.commit_connection_table(self.samdb) def run(self): """Method to perform a complete run of the KCC and produce an updated topology for subsequent NC replica syncronization between domain controllers """ - # Setup try: - self.load_my_dsa() - self.load_all_dsa() - self.load_all_partitions() + # Setup self.load_my_site() + self.load_my_dsa() - except Exception, estr: - logger.error("%s" % estr) - return + self.load_all_partitions() + self.load_all_transports() - # self.should_be_present_test() + # These are the published steps (in order) for the + # MS-TECH description of the KCC algorithm - # These are the published steps (in order) for the - # MS description of the KCC algorithm + # Step 1 + self.refresh_failed_links_connections() - # Step 1 - self.refresh_failed_links_connections() + # Step 2 + self.intrasite() - # Step 2 - self.intrasite() + # Step 3 + self.intersite() - # Step 3 - self.intersite() + # Step 4 + self.remove_unneeded_ntdsconn() - # Step 4 - self.remove_unneeded_ntds_connections() + # Step 5 + self.translate_ntdsconn() - # Step 5 - self.translate_connections() + # Step 6 + self.remove_unneeded_failed_links_connections() - # Step 6 - self.remove_unneeded_failed_links_connections() + # Step 7 + self.update_rodc_connection() - # Step 7 - self.update_rodc_connection() + except Exception, estr: + logger.error("%s" % estr) + return 1 + return 0 ################################################## # Global Functions @@ -637,6 +1125,13 @@ class KCC: def sort_replica_by_dsa_guid(rep1, rep2): return cmp(rep1.rep_dsa_guid, rep2.rep_dsa_guid) +def is_smtp_replication_availalbe(): + """Currently always returns false because Samba + doesn't implement SMTP transfer for NC changes + between DCs + """ + return False + ################################################## # samba_kcc entry point ################################################## @@ -649,8 +1144,11 @@ parser.add_option_group(sambaopts) parser.add_option_group(credopts) parser.add_option_group(options.VersionOptions(parser)) -parser.add_option("--debug", help="debug output", action="store_true") -parser.add_option("--seed", help="random number seed") +parser.add_option("--readonly", \ + help="compute topology but do not update database", \ + action="store_true") +parser.add_option("--debug", help="debug output", action="store_true") +parser.add_option("--seed", help="random number seed") logger = logging.getLogger("samba_kcc") logger.addHandler(logging.StreamHandler(sys.stdout)) @@ -683,4 +1181,6 @@ except ldb.LdbError, (num, msg): # Instantiate Knowledge Consistency Checker and perform run kcc = KCC(samdb) -kcc.run() +rc = kcc.run() + +sys.exit(rc) -- cgit