Service can then utilize to replicate naming contexts
:param unix_now: The putative current time in seconds since 1970.
- :param read_only: Don't write to the database.
+ :param readonly: Don't write to the database.
:param verify: Check topological invariants for the generated graphs
:param debug: Write verbosely to stderr.
"param dot_file_dir: write diagnostic Graphviz files in this directory
if transport.name == 'IP':
self.ip_transport = transport
elif transport.name == 'SMTP':
- logger.info("Samba KCC is ignoring the obsolete SMTP transport.")
+ logger.debug("Samba KCC is ignoring the obsolete "
+ "SMTP transport.")
else:
- logger.warning("Samba KCC does not support the transport called %r."
- % (transport.name,))
+ logger.warning("Samba KCC does not support the transport "
+ "called %r." % (transport.name,))
if self.ip_transport is None:
raise KCCError("there doesn't seem to be an IP transport")
:return: None
:raise: KCCError if DSA can't be found
"""
- dn = ldb.Dn(self.samdb, "<GUID=%s>" % self.samdb.get_ntds_GUID())
+ dn_query = "<GUID=%s>" % self.samdb.get_ntds_GUID()
+ dn = ldb.Dn(self.samdb, dn_query)
try:
res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
attrs=["objectGUID"])
except ldb.LdbError, (enum, estr):
- logger.warning("Search for %s failed: %s. This typically happens"
- " in --importldif mode due to lack of module"
- " support.", dn, estr)
+ DEBUG_FN("Search for dn '%s' [from %s] failed: %s. "
+ "This typically happens in --importldif mode due "
+ "to lack of module support." % (dn, dn_query, estr))
try:
# We work around the failure above by looking at the
# dsServiceName that was put in the fake rootdse by
" it must be RODC.\n"
"Let's add it, because my_dsa is special!"
"\n(likewise for self.dsa_by_guid)" %
- self.my_dsas_dnstr)
+ self.my_dsa_dnstr)
self.dsa_by_dnstr[self.my_dsa_dnstr] = self.my_dsa
self.dsa_by_guid[str(self.my_dsa.dsa_guid)] = self.my_dsa
mydsa = self.my_dsa
- self._ensure_connections_are_loaded(mydsa.connect_table.values())
+ try:
+ self._ensure_connections_are_loaded(mydsa.connect_table.values())
+ except KCCError:
+ # RODC never actually added any connections to begin with
+ if mydsa.is_ro():
+ return
local_connections = []
:return: None
"""
+ # TODO Figure out how best to handle the RODC case
+ # The RODC is ISTG, but shouldn't act on anyone's behalf.
+ if self.my_dsa.is_ro():
+ return
+
# Find the intersite connections
local_dsas = self.my_site.dsa_table
connections_and_dsas = []
for dsa in local_dsas.values():
for cn in dsa.connect_table.values():
+ if cn.to_be_deleted:
+ continue
s_dnstr = cn.get_from_dnstr()
+ if s_dnstr is None:
+ continue
if s_dnstr not in local_dsas:
from_dsa = self.get_dsa(s_dnstr)
+ # Samba ONLY: ISTG removes connections to dead DCs
+ if from_dsa is None or '\\0ADEL' in s_dnstr:
+ logger.info("DSA appears deleted, removing connection %s"
+ % s_dnstr)
+ cn.to_be_deleted = True
+ continue
connections_and_dsas.append((cn, dsa, from_dsa))
self._ensure_connections_are_loaded(x[0] for x in connections_and_dsas)
for dsa in self.my_site.dsa_table.values():
self._commit_changes(dsa)
-
def modify_repsFrom(self, n_rep, t_repsFrom, s_rep, s_dsa, cn_conn):
"""Update an repsFrom object if required.
if times != t_repsFrom.schedule:
t_repsFrom.schedule = times
+ # Bit DRS_ADD_REF is set in replicaFlags unconditionally
+ # Samba ONLY:
+ if ((t_repsFrom.replica_flags &
+ drsuapi.DRSUAPI_DRS_ADD_REF) == 0x0):
+ t_repsFrom.replica_flags |= drsuapi.DRSUAPI_DRS_ADD_REF
+
# 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
return s_dsa
return None
-
def translate_ntdsconn(self, current_dsa=None):
"""Adjust repsFrom to match NTDSConnections
"""
count = 0
+ ro = False
if current_dsa is None:
current_dsa = self.my_dsa
+ if current_dsa.is_ro():
+ ro = True
+
if current_dsa.is_translate_ntdsconn_disabled():
DEBUG_FN("skipping translate_ntdsconn() "
"because disabling flag is set")
# If we have the replica and its not needed
# then we add it to the "to be deleted" list.
for dnstr in current_rep_table:
- if dnstr not in needed_rep_table:
- delete_reps.add(dnstr)
+ # If we're on the RODC, hardcode the update flags
+ if ro:
+ c_rep = current_rep_table[dnstr]
+ c_rep.load_repsFrom(self.samdb)
+ for t_repsFrom in c_rep.rep_repsFrom:
+ replica_flags = (drsuapi.DRSUAPI_DRS_INIT_SYNC |
+ drsuapi.DRSUAPI_DRS_PER_SYNC |
+ drsuapi.DRSUAPI_DRS_ADD_REF |
+ drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING |
+ drsuapi.DRSUAPI_DRS_NONGC_RO_REP)
+ if t_repsFrom.replica_flags != replica_flags:
+ t_repsFrom.replica_flags = replica_flags
+ c_rep.commit_repsFrom(self.samdb, ro=self.readonly)
+ else:
+ if dnstr not in needed_rep_table:
+ delete_reps.add(dnstr)
DEBUG_FN('current %d needed %d delete %d' % (len(current_rep_table),
len(needed_rep_table), len(delete_reps)))
if delete_reps:
+ # TODO Must delete repsFrom/repsTo for these replicas
DEBUG('deleting these reps: %s' % delete_reps)
for dnstr in delete_reps:
del current_rep_table[dnstr]
+ # HANDLE REPS-FROM
+ #
# Now perform the scan of replicas we'll need
# and compare any current repsFrom against the
# connections
n_rep.load_repsFrom(self.samdb)
n_rep.load_fsmo_roles(self.samdb)
- # Loop thru the existing repsFrom tupples (if any)
+ # Loop thru the existing repsFrom tuples (if any)
# XXX This is a list and could contain duplicates
# (multiple load_repsFrom calls)
for t_repsFrom in n_rep.rep_repsFrom:
if s_dsa is None:
continue
- # Loop thru the existing repsFrom tupples (if any) and
+ # Loop thru the existing repsFrom tuples (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
if t_repsFrom.is_modified():
n_rep.rep_repsFrom.append(t_repsFrom)
- if self.readonly:
+ if self.readonly or ro:
# Display any to be deleted or modified repsFrom
text = n_rep.dumpstr_to_be_deleted()
if text:
# Commit any modified repsFrom to the NC replica
n_rep.commit_repsFrom(self.samdb)
+ # HANDLE REPS-TO:
+ #
+ # Now perform the scan of replicas we'll need
+ # and compare any current repsTo against the
+ # connections
+
+ # RODC should never push to anybody (should we check this?)
+ if ro:
+ return
+
+ for n_rep in needed_rep_table.values():
+
+ # load any repsTo and fsmo roles as we'll
+ # need them during connection translation
+ n_rep.load_repsTo(self.samdb)
+
+ # Loop thru the existing repsTo tuples (if any)
+ # XXX This is a list and could contain duplicates
+ # (multiple load_repsTo calls)
+ for t_repsTo in n_rep.rep_repsTo:
+
+ # for each tuple t in n!repsTo, let s be the nTDSDSA
+ # object such that s!objectGUID = t.uuidDsa
+ guidstr = str(t_repsTo.source_dsa_obj_guid)
+ s_dsa = self.get_dsa_by_guidstr(guidstr)
+
+ # Source dsa is gone from config (strange)
+ # so cleanup stale repsTo for unlisted DSA
+ if s_dsa is None:
+ logger.warning("repsTo source DSA guid (%s) not found" %
+ guidstr)
+ t_repsTo.to_be_deleted = True
+ continue
+
+ # Find the connection that this repsTo would use. If
+ # there isn't a good one (i.e. non-RODC_TOPOLOGY,
+ # meaning non-FRS), we delete the repsTo.
+ s_dnstr = s_dsa.dsa_dnstr
+ if '\\0ADEL' in s_dnstr:
+ logger.warning("repsTo source DSA guid (%s) appears deleted" %
+ guidstr)
+ t_repsTo.to_be_deleted = True
+ continue
+
+ connections = s_dsa.get_connection_by_from_dnstr(self.my_dsa_dnstr)
+ if len(connections) > 0:
+ # Then this repsTo is tentatively valid
+ continue
+ else:
+ # There is no plausible connection for this repsTo
+ t_repsTo.to_be_deleted = True
+
+ if self.readonly:
+ # Display any to be deleted or modified repsTo
+ text = n_rep.dumpstr_reps_to()
+ if text:
+ logger.info("REMOVING REPS-TO:\n%s" % text)
+
+ # Peform deletion from our tables but perform
+ # no database modification
+ n_rep.commit_repsTo(self.samdb, ro=True)
+ else:
+ # Commit any modified repsTo to the NC replica
+ n_rep.commit_repsTo(self.samdb)
+
+ # TODO Remove any duplicate repsTo values. This should never happen in
+ # any normal situations.
+
def merge_failed_links(self, ping=None):
"""Merge of kCCFailedLinks and kCCFailedLinks from bridgeheads.
DEBUG_FN("intersite(): exit all_connected=%d" % all_connected)
return all_connected
- def update_rodc_connection(self):
+ # This function currently does no actions. The reason being that we cannot
+ # perform modifies in this way on the RODC.
+ def update_rodc_connection(self, ro=True):
"""Updates the RODC NTFRS connection object.
If the local DSA is not an RODC, this does nothing.
con.schedule = cn2.schedule
con.to_be_modified = True
- self.my_dsa.commit_connections(self.samdb, ro=self.readonly)
+ self.my_dsa.commit_connections(self.samdb, ro=ro)
def intrasite_max_node_edges(self, node_count):
"""Find the maximum number of edges directed to an intrasite node
if not needed:
debug.DEBUG_RED("%s lacks 'should be present' status, "
- "aborting construct_intersite_graph!" %
+ "aborting construct_intrasite_graph!" %
nc_x.nc_dnstr)
return
if not self.get_dsa(x).is_ro())
rw_dot_edges = [(a, b) for a, b in dot_edges if
a in rw_dot_vertices and b in rw_dot_vertices]
- print rw_dot_edges, rw_dot_vertices
rw_verify_properties = ('connected',
'directed_double_ring_or_small')
verify_and_dot('intrasite_rw_pre_ntdscon', rw_dot_edges,
while candidates and not tnode.has_sufficient_edges():
other = random.choice(candidates)
- DEBUG("trying to add candidate %s" % other.dsa_dstr)
- if not tnode.add_edge_from(other):
- debug.DEBUG_RED("could not add %s" % other.dsa_dstr)
+ DEBUG("trying to add candidate %s" % other.dsa_dnstr)
+ if not tnode.add_edge_from(other.dsa_dnstr):
+ debug.DEBUG_RED("could not add %s" % other.dsa_dnstr)
candidates.remove(other)
else:
DEBUG_FN("not adding links to %s: nodes %s, links is %s/%s" %
if not self.get_dsa(x).is_ro())
rw_dot_edges = [(a, b) for a, b in dot_edges if
a in rw_dot_vertices and b in rw_dot_vertices]
- print rw_dot_edges, rw_dot_vertices
rw_verify_properties = ('connected',
'directed_double_ring_or_small')
verify_and_dot('intrasite_rw_post_ntdscon', rw_dot_edges,
for dsa in site.dsa_table.values()])
return dsas
- def load_samdb(self, dburl, lp, creds):
+ def load_samdb(self, dburl, lp, creds, force=False):
"""Load the database using an url, loadparm, and credentials
+ If force is False, the samdb won't be reloaded if it already
+ exists.
+
:param dburl: a database url.
:param lp: a loadparm object.
:param creds: a Credentials object.
+ :param force: a boolean indicating whether to overwrite.
+
"""
- self.samdb = SamDB(url=dburl,
- session_info=system_session(),
- credentials=creds, lp=lp)
+ if force or self.samdb is None:
+ try:
+ self.samdb = SamDB(url=dburl,
+ session_info=system_session(),
+ credentials=creds, lp=lp)
+ except ldb.LdbError, (num, msg):
+ raise KCCError("Unable to open sam database %s : %s" %
+ (dburl, msg))
def plot_all_connections(self, basename, verify_properties=()):
"""Helper function to plot and verify NTDSConnections
determine link availability (boolean, default False)
:return: 1 on error, 0 otherwise
"""
- # We may already have a samdb setup if we are
- # currently importing an ldif for a test run
if self.samdb is None:
- try:
- self.load_samdb(dburl, lp, creds)
- except ldb.LdbError, (num, msg):
- logger.error("Unable to open sam database %s : %s" %
- (dburl, msg))
- return 1
+ DEBUG_FN("samdb is None; let's load it from %s" % (dburl,))
+ self.load_samdb(dburl, lp, creds, force=False)
if forced_local_dsa:
self.samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" %
return 0
- def import_ldif(self, dburl, lp, creds, ldif_file, forced_local_dsa=None):
+ def import_ldif(self, dburl, lp, ldif_file, forced_local_dsa=None):
"""Import relevant objects and attributes from an LDIF file.
The point of this function is to allow a programmer/debugger to
:param dburl: path to the temporary abbreviated db to create
:param lp: a loadparm object.
- :param cred: a Credentials object.
:param ldif_file: path to the ldif file to import
:param forced_local_dsa: perform KCC from this DSA's point of view
:return: zero on success, 1 on error