summaryrefslogtreecommitdiff
path: root/source4/scripting
diff options
context:
space:
mode:
authorDave Craft <wimberosa@gmail.com>2012-01-11 08:11:35 -0600
committerAndrew Tridgell <tridge@samba.org>2012-01-14 07:45:11 +0100
commitab1f896c5152dfd10609ac146eaaecd1bd2d5b70 (patch)
tree1494d3ab9afb06a692171625876c4cb0b144367f /source4/scripting
parentaff8dad076f803e6deb2c5b59fa3bc5cb7ca7bd7 (diff)
downloadsamba-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-xsource4/scripting/bin/samba_kcc1629
-rw-r--r--source4/scripting/python/samba/kcc_utils.py1176
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.