1
0
mirror of https://github.com/samba-team/samba.git synced 2025-08-04 08:22:08 +03:00

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 <tridge@samba.org>
This commit is contained in:
Dave Craft
2011-12-04 11:08:56 -06:00
committed by Andrew Tridgell
parent 0a4746a200
commit 819f11285d
2 changed files with 1298 additions and 224 deletions

View File

@ -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.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,50 +123,11 @@ class KCC:
except ldb.LdbError, (enum, estr):
raise Exception("Unable to find my nTDSDSA - (%s)" % estr)
dnstr = res[0]["dsServiceName"][0]
self.my_dsa_dnstr = res[0]["dsServiceName"][0]
self.my_dsa = self.my_site.get_dsa(self.my_dsa_dnstr)
# 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)
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
def load_all_partitions(self):
"""Discover all NCs thru the Partitions dn and
instantiate and load the NCs. Each NC is inserted
@ -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,30 +1073,24 @@ class KCC:
False) # don't detect stale
# Commit any newly created connections to the samdb
if opts.readonly is None:
mydsa.commit_connection_table(self.samdb)
logger.debug("intrasite exit:\nmydsa: %s" % mydsa)
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.should_be_present_test()
self.load_all_partitions()
self.load_all_transports()
# These are the published steps (in order) for the
# MS description of the KCC algorithm
# MS-TECH description of the KCC algorithm
# Step 1
self.refresh_failed_links_connections()
@ -619,10 +1102,10 @@ class KCC:
self.intersite()
# Step 4
self.remove_unneeded_ntds_connections()
self.remove_unneeded_ntdsconn()
# Step 5
self.translate_connections()
self.translate_ntdsconn()
# Step 6
self.remove_unneeded_failed_links_connections()
@ -630,6 +1113,11 @@ class KCC:
# 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,6 +1144,9 @@ parser.add_option_group(sambaopts)
parser.add_option_group(credopts)
parser.add_option_group(options.VersionOptions(parser))
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")
@ -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)

File diff suppressed because it is too large Load Diff