This is an automated email from the git hooks/post-receive script.
firstyear pushed a commit to branch master
in repository 389-ds-base.
commit 13dd2b61d2d6bec8bbb111541908fb839afb57b8
Author: Thierry bordaz (tbordaz) <tbordaz(a)redhat.com>
Date: Tue Dec 17 15:32:55 2013 +0100
Ticket 47635: MT/Backend/Suffix to be conform with the design
Bug Description:
This bug is to create MappingTree/Suffix classes and to move Backend from brooker.py
to a dedicated file.
Then to implement the interface of each classes as described in
http://port389.org/wiki/Upstream_test_framework
Fix Description:
Implements:
Mapping Tree:
mappingTree.py:
MappingTree/list-create-delete-getProperties-setProperties-toSuffix
mappingTree_test.py: unit tests
Backend:
brooker.py: remove Backend from that file
backend.py: Backend/list-delete-create-getProperties-setProperties-toSuffix
make readonly local function that will be reimplemented with setProperties
backend_test.py
suppress/replace methods implemented in others classes
setupBackend/getSuffixForBackend
Suffix:
suffix.py:Suffix/list-toBackend-getParent
suffix_test.py: unit tests
suppress/replace methods implemented in others classes
getSuffixes/setupSuffix/getBackendsForSuffix/findParentSuffix/addSuffix
getSuffixes/setupBackend/setupSuffix/getBackendsForSuffix/getSuffixForBackend/findParentSuffix/addSuffix
https://fedorahosted.org/389/ticket/47635
Reviewed by: Rich Megginson
Platforms tested: F17
Flag Day: no
Doc impact: no
---
src/lib389/lib389/__init__.py | 240 ++++++++-----------------
src/lib389/lib389/_constants.py | 14 +-
src/lib389/lib389/backend.py | 328 ++++++++++++++++++++++++++++++++++
src/lib389/lib389/brooker.py | 259 +--------------------------
src/lib389/lib389/mappingTree.py | 324 +++++++++++++++++++++++++++++++++
src/lib389/lib389/properties.py | 108 +++++++++--
src/lib389/lib389/suffix.py | 84 +++++++++
src/lib389/tests/backend_test.py | 335 +++++++++++++++++++++++------------
src/lib389/tests/dirsrv_test.py | 2 +-
src/lib389/tests/mappingTree_test.py | 275 ++++++++++++++++++++++++++++
src/lib389/tests/suffix_test.py | 104 +++++++++++
11 files changed, 1520 insertions(+), 553 deletions(-)
diff --git a/src/lib389/lib389/__init__.py b/src/lib389/lib389/__init__.py
index 2069750..d55763f 100644
--- a/src/lib389/lib389/__init__.py
+++ b/src/lib389/lib389/__init__.py
@@ -91,6 +91,11 @@ class MissingEntryError(NoSuchEntryError):
"""When just added entries are missing."""
pass
+class UnwillingToPerformError(Error):
+ pass
+
+class NotImplementedError(Error):
+ pass
class DsError(Error):
"""Generic DS Error."""
@@ -293,16 +298,21 @@ class DirSrv(SimpleLDAPObject):
from lib389.brooker import (
Agreement,
Replica,
- Backend,
Config,
Index)
- self.agreement = Agreement(self)
- self.replica = Replica(self)
- self.backend = Backend(self)
- self.config = Config(self)
- self.index = Index(self)
+ from lib389.mappingTree import MappingTree
+ from lib389.backend import Backend
+ from lib389.suffix import Suffix
+
+ self.agreement = Agreement(self)
+ self.replica = Replica(self)
+ self.backend = Backend(self)
+ self.config = Config(self)
+ self.index = Index(self)
+ self.mappingtree = MappingTree(self)
+ self.suffix = Suffix(self)
- def __init__(self, verbose=False):
+ def __init__(self, verbose=False, timeout=10):
"""
This method does various initialization of DirSrv object:
parameters:
@@ -322,9 +332,10 @@ class DirSrv(SimpleLDAPObject):
- open
"""
- self.state = DIRSRV_STATE_INIT
+ self.state = DIRSRV_STATE_INIT
self.verbose = verbose
- self.log = log
+ self.log = log
+ self.timeout = timeout
self.__wrapmethods()
self.__add_brookers__()
@@ -403,17 +414,25 @@ class DirSrv(SimpleLDAPObject):
def list(self, all=False):
"""
- Returns a list of files containing the environment of the created instances
- that are on the local file system (e.g. <prefix>/etc/dirsrv/slapd-*).
- If 'all' is True, it returns all the files else it returns only the
- environment file of the current instance.
- By default it is False and only returns the current instance file.
+ Returns a list dictionary. For a created instance that is on the local file
system
+ (e.g. <prefix>/etc/dirsrv/slapd-*), it exists a file describing its
properties
+ (environment): <prefix>/etc/sysconfig/dirsrv-<serverid> or
$HOME/.dirsrv/dirsv-<serverid>
+ A dictionary is created with the following properties:
+ CONF_SERVER_DIR
+ CONF_SERVERBIN_DIR
+ CONF_CONFIG_DIR
+ CONF_INST_DIR
+ CONF_RUN_DIR
+ CONF_DS_ROOT
+ CONF_PRODUCT_NAME
+ If all=True it builds a list of dictionary for all created instances.
+ Else (default), the list will only contain the dictionary of the calling
instance
@param all - True or False . default is [False]
- @return list file(s) name(s)
+ @return list of dictionaries, each of them containing instance properities
- @raise None
+ @raise IOError - if the file containing the properties is not foundable or
readable
"""
def test_and_set(prop, propname, variable, value):
'''
@@ -654,7 +673,7 @@ class DirSrv(SimpleLDAPObject):
def delete(self):
'''
Deletes the instance with the parameters sets in dirsrv
- The state changes from DIRSRV_STATE_OFFLINE -> DIRSRV_STATE_ALLOCATED
+ The state changes -> DIRSRV_STATE_ALLOCATED
@param self
@@ -829,6 +848,12 @@ class DirSrv(SimpleLDAPObject):
def clearBackupFS(self, backup_file=None):
"""
Remove a backup_file or all backup up of a given instance
+
+ @param backup_file - optional
+
+ @return None
+
+ @raise None
"""
if backup_file:
if os.path.isfile(backup_file):
@@ -852,6 +877,12 @@ class DirSrv(SimpleLDAPObject):
"""
If it exits a backup file, it returns it
else it returns None
+
+ @param None
+
+ @return file name of the first backup. None if there is no backup
+
+ @raise None
"""
backup_dir, backup_pattern = self._infoBackupFS()
@@ -875,6 +906,12 @@ class DirSrv(SimpleLDAPObject):
self.confdir : root of the instance config (e.g. /etc/dirsrv)
self.dbdir: directory where is stored the database (e.g.
/var/lib/dirsrv/slapd-standalone/db)
self.changelogdir: directory where is stored the changelog (e.g.
/var/lib/dirsrv/slapd-master/changelogdb)
+
+ @param None
+
+ @return file name of the backup
+
+ @raise none
"""
# First check it if already exists a backup file
@@ -937,15 +974,22 @@ class DirSrv(SimpleLDAPObject):
def restoreFS(self, backup_file):
"""
+ Restore a directory from a backup file
+
+ @param backup_file - file name of the backup
+
+ @return None
+
+ @raise ValueError - if backup_file invalid file name
"""
# First check the archive exists
if backup_file is None:
log.warning("Unable to restore the instance (missing backup)")
- return 1
+ raise ValueError("Unable to restore the instance (missing
backup)")
if not os.path.isfile(backup_file):
log.warning("Unable to restore the instance (%s is not a file)" %
backup_file)
- return 1
+ raise ValueError("Unable to restore the instance (%s is not a
file)" % backup_file)
#
# Second do some clean up
@@ -1076,23 +1120,6 @@ class DirSrv(SimpleLDAPObject):
log.exception("Entry %s was added successfully, but I cannot search
it" % dn)
raise MissingEntryError("Entry %s was added successfully, but I cannot
search it" % dn)
- def getMTEntry(self, suffix, attrs=None):
- """Given a suffix, return the mapping tree entry for it. If attrs
is
- given, only fetch those attributes, otherwise, get all attributes.
- """
- attrs = attrs or []
- filtr = suffixfilt(suffix)
- try:
- entry = self.getEntry(
- DN_MAPPING_TREE, ldap.SCOPE_ONELEVEL, filtr, attrs)
- return entry
- except NoSuchEntryError:
- raise NoSuchEntryError(
- "Cannot find suffix in mapping tree: %r " % suffix)
- except ldap.FILTER_ERROR, e:
- log.error("Error searching for %r" % filtr)
- raise e
-
def __wrapmethods(self):
"""This wraps all methods of SimpleLDAPObject, so that we can
intercept
the methods that deal with entries. Instead of using a raw list of tuples
@@ -1194,7 +1221,7 @@ class DirSrv(SimpleLDAPObject):
return rc
def createIndex(self, suffix, attr, verbose=False):
- entries_backend = self.getBackendsForSuffix(suffix, ['cn'])
+ entries_backend = self.backend.list(suffix=suffix)
cn = "index%d" % time.time()
dn = "cn=%s,cn=index,cn=tasks,cn=config" % cn
entry = Entry(dn)
@@ -1263,127 +1290,11 @@ class DirSrv(SimpleLDAPObject):
adder = LDIFAdder(input_file, self, cont)
- def getSuffixes(self):
- """@return a list of cn suffixes"""
- ents = self.search_s(DN_MAPPING_TREE, ldap.SCOPE_ONELEVEL)
- sufs = []
- for ent in ents:
- unquoted = None
- quoted = None
- for val in ent.getValues('cn'):
- if val.find('"') < 0: # prefer the one that is not
quoted
- unquoted = val
- else:
- quoted = val
- if unquoted: # preferred
- sufs.append(unquoted)
- elif quoted: # strip
- sufs.append(quoted.strip('"'))
- else:
- raise Exception(
- "Error: mapping tree entry %r has no suffix" % ent.dn)
- return sufs
-
- def setupBackend(self, suffix, binddn=None, bindpw=None, urls=None, attrvals=None,
benamebase='localdb', verbose=False):
- """Setup a backend and return its dn. Blank on error
-
- NOTE This won't create a suffix nor its related entry in
- the tree!!!
-
- XXX Deprecated! @see lib389.brooker.Backend.add
-
- """
- return self.backend.add(suffix=suffix, binddn=binddn, bindpw=bindpw,
- urls=urls, attrvals=attrvals, benamebase=benamebase,
- setupmt=False, parent=None)
-
-
- def setupSuffix(self, suffix, bename, parent="", verbose=False):
- """Setup a suffix with the given backend-name.
-
- XXX Deprecated! @see lib389.brooker.Backend.setup_mt
-
- """
- return self.backend.setup_mt(suffix, bename, parent)
-
-
- def getBackendsForSuffix(self, suffix, attrs=None):
- # TESTME removed try..except and raise if NoSuchEntryError
- attrs = attrs or []
- nsuffix = normalizeDN(suffix)
- entries = self.search_s("cn=plugins,cn=config", ldap.SCOPE_SUBTREE,
-
"(&(objectclass=nsBackendInstance)(|(nsslapd-suffix=%s)(nsslapd-suffix=%s)))"
% (suffix, nsuffix),
- attrs)
- return entries
-
- def getSuffixForBackend(self, bename, attrs=None):
- """Return the mapping tree entry of `bename` or None if not
found"""
- attrs = attrs or []
- try:
- entry = self.getEntry("cn=plugins,cn=config", ldap.SCOPE_SUBTREE,
-
"(&(objectclass=nsBackendInstance)(cn=%s))" % bename,
- ['nsslapd-suffix'])
- suffix = entry.getValue('nsslapd-suffix')
- return self.getMTEntry(suffix, attrs)
- except NoSuchEntryError:
- log.warning("Could not find an entry for backend %r" % bename)
- return None
-
- def findParentSuffix(self, suffix):
- """see if the given suffix has a parent suffix"""
- rdns = ldap.explode_dn(suffix)
- del rdns[0]
-
- while len(rdns) > 0:
- suffix = ','.join(rdns)
- try:
- mapent = self.getMTEntry(suffix)
- return suffix
- except NoSuchEntryError:
- del rdns[0]
-
- return ""
-
- def addSuffix(self, suffix, binddn=None, bindpw=None, urls=None, bename=None,
beattrs=None):
- """Create and return a suffix and its backend.
-
- @param suffix
- @param urls
- @param bename - name of the backed (eventually created)
- @param beattrs - parametes to create the backend
- @param binddn
- @param bindpw
- Uses: setupBackend and SetupSuffix
- Requires: adding a matching entry in the tree
- TODO: test return values and error codes
-
- `beattrs`: see setupBacked
-
- """
- benames = []
- entries_backend = self.getBackendsForSuffix(suffix, ['cn'])
- # no backends for this suffix yet - create one
- if not entries_backend:
- # if not bename, self.setupBackend raises
- bename = self.setupBackend(
- suffix, binddn, bindpw, urls, benamebase=bename, attrvals=beattrs)
- else: # use existing backend(s)
- benames = [entry.cn for entry in entries_backend]
- bename = benames.pop(0) # do I need to modify benames
-
- try:
- parent = self.findParentSuffix(suffix)
- return self.setupSuffix(suffix, bename, parent)
- except NoSuchEntryError:
- log.exception(
- "Couldn't create suffix for %s %s" % (bename, suffix))
- raise
-
def getDBStats(self, suffix, bename=''):
if bename:
dn = ','.join(("cn=monitor,cn=%s" % bename, DN_LDBM))
else:
- entries_backend = self.getBackendsForSuffix(suffix)
+ entries_backend = self.backend.list(suffix=suffix)
dn = "cn=monitor," + entries_backend[0].dn
dbmondn = "cn=monitor," + DN_LDBM
dbdbdn = "cn=database,cn=monitor," + DN_LDBM
@@ -1569,7 +1480,7 @@ class DirSrv(SimpleLDAPObject):
the name of the attribute to index, and the types of indexes
to create e.g. "pres", "eq", "sub"
"""
- entries_backend = self.getBackendsForSuffix(suffix, ['cn'])
+ entries_backend = self.backend.list(suffix=suffix)
# assume 1 local backend
dn = "cn=%s,cn=index,%s" % (attr, entries_backend[0].dn)
entry = Entry(dn)
@@ -1588,13 +1499,13 @@ class DirSrv(SimpleLDAPObject):
def modIndex(self, suffix, attr, mod):
"""just a wrapper around a plain old ldap modify, but will
find the correct index entry based on the suffix and attribute"""
- entries_backend = self.getBackendsForSuffix(suffix, ['cn'])
+ entries_backend = self.backend.list(suffix=suffix)
# assume 1 local backend
dn = "cn=%s,cn=index,%s" % (attr, entries_backend[0].dn)
self.modify_s(dn, mod)
def requireIndex(self, suffix):
- entries_backend = self.getBackendsForSuffix(suffix, ['cn'])
+ entries_backend = self.backend.list(suffix=suffix)
# assume 1 local backend
dn = entries_backend[0].dn
replace = [(ldap.MOD_REPLACE, 'nsslapd-require-index', 'on')]
@@ -1660,7 +1571,7 @@ class DirSrv(SimpleLDAPObject):
def enableChainOnUpdate(self, suffix, bename):
# first, get the mapping tree entry to modify
- mtent = self.getMTEntry(suffix, ['cn'])
+ mtent = self.mappingtree.list(suffix=suffix)
dn = mtent.dn
# next, get the path of the replication plugin
@@ -1669,10 +1580,10 @@ class DirSrv(SimpleLDAPObject):
attrlist=['nsslapd-pluginPath'])
path = e_plugin.getValue('nsslapd-pluginPath')
- mod = [(ldap.MOD_REPLACE, 'nsslapd-state', 'backend'),
- (ldap.MOD_ADD, 'nsslapd-backend', bename),
- (ldap.MOD_ADD, 'nsslapd-distribution-plugin', path),
- (ldap.MOD_ADD, 'nsslapd-distribution-funct',
'repl_chain_on_update')]
+ mod = [(ldap.MOD_REPLACE, MT_PROPNAME_TO_ATTRNAME[MT_STATE],
MT_STATE_VAL_BACKEND),
+ (ldap.MOD_ADD, MT_PROPNAME_TO_ATTRNAME[MT_BACKEND], bename),
+ (ldap.MOD_ADD, MT_PROPNAME_TO_ATTRNAME[MT_CHAIN_PATH], path),
+ (ldap.MOD_ADD, MT_PROPNAME_TO_ATTRNAME[MT_CHAIN_FCT],
MT_CHAIN_UPDATE_VAL_ON_UPDATE)]
try:
self.modify_s(dn, mod)
@@ -1992,7 +1903,7 @@ class DirSrv(SimpleLDAPObject):
raise ValueError("invalid replicaId: %d for HUB/CONSUMER replicaId is
CONSUMER_REPLICAID" % replicaId)
# Now check we have a suffix
- entries_backend = self.getBackendsForSuffix(suffix, ['nsslapd-suffix'])
+ entries_backend = self.backend.list(suffix=suffix)
if not entries_backend:
log.fatal("enableReplication: enable to retrieve the backend for
%s" % suffix)
raise ValueError("no backend for suffix %s" % suffix)
@@ -2062,8 +1973,7 @@ class DirSrv(SimpleLDAPObject):
# TODO should I check the addSuffix output as it doesn't raise
self.addSuffix(repArgs['suffix'])
if 'bename' not in repArgs:
- entries_backend = self.getBackendsForSuffix(
- repArgs['suffix'], ['cn'])
+ entries_backend = self.backend.list(suffix=repArgs['suffix'])
# just use first one
repArgs['bename'] = entries_backend[0].cn
if repArgs.get('log', False):
diff --git a/src/lib389/lib389/_constants.py b/src/lib389/lib389/_constants.py
index 704d6ac..35fbcf3 100644
--- a/src/lib389/lib389/_constants.py
+++ b/src/lib389/lib389/_constants.py
@@ -32,8 +32,7 @@ DEFAULT_USER = "nobody"
# Some DN constants
DN_DM = "cn=Directory Manager"
-DN_CONFIG = "cn=config"
-DN_LDBM = "cn=ldbm database,cn=plugins,cn=config"
+
CMD_PATH_SETUP_DS = "/setup-ds.pl"
CMD_PATH_REMOVE_DS = "/remove-ds.pl"
@@ -48,6 +47,7 @@ LOCALHOST = "localhost.localdomain"
DEFAULT_PORT = 389
DEFAULT_SECURE_PORT = 636
DEFAULT_SUFFIX = 'dc=example,dc=com'
+DEFAULT_BENAME = 'userRoot' # warning it is case sensitive
DEFAULT_BACKUPDIR = '/tmp'
DEFAULT_INST_HEAD = 'slapd-'
DEFAULT_ENV_HEAD = 'dirsrv-'
@@ -69,10 +69,14 @@ CONF_DS_ROOT = 'DS_ROOT'
CONF_PRODUCT_NAME = 'PRODUCT_NAME'
+DN_CONFIG = "cn=config"
+DN_PLUGIN = "cn=plugins,%s" % DN_CONFIG
+DN_MAPPING_TREE = "cn=mapping tree,%s" % DN_CONFIG
+DN_CHANGELOG = "cn=changelog5,%s" % DN_CONFIG
+DN_LDBM = "cn=ldbm database,%s" % DN_PLUGIN
+DN_CHAIN = "cn=chaining database,%s" % DN_PLUGIN
+
-DN_MAPPING_TREE = "cn=mapping tree,cn=config"
-DN_CHAIN = "cn=chaining database,cn=plugins,cn=config"
-DN_CHANGELOG = "cn=changelog5,cn=config"
#
# constants
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
new file mode 100644
index 0000000..e292c04
--- /dev/null
+++ b/src/lib389/lib389/backend.py
@@ -0,0 +1,328 @@
+'''
+Created on Dec 13, 2013
+
+@author: tbordaz
+'''
+
+import ldap
+from lib389 import DirSrv, Entry, NoSuchEntryError, InvalidArgumentError
+from lib389._constants import *
+from lib389.utils import (
+ normalizeDN,
+ suffixfilt
+ )
+from compiler.ast import Not
+from lib389.properties import *
+from __init__ import UnwillingToPerformError
+
+class Backend(object):
+ proxied_methods = 'search_s getEntry'.split()
+
+ def __init__(self, conn):
+ """@param conn - a DirSrv instance"""
+ self.conn = conn
+ self.log = conn.log
+
+ def __getattr__(self, name):
+ if name in Backend.proxied_methods:
+ return DirSrv.__getattr__(self.conn, name)
+
+ def list(self, suffix=None, backend_dn=None, bename=None):
+ """
+ Returns a search result of the backend(s) entries with all their attributes
+
+ If 'suffix'/'backend_dn'/'benamebase' are specified.
It uses 'backend_dn' first, then 'suffix', then 'benamebase'.
+
+ If neither 'suffix', 'backend_dn' and 'benamebase'
are specified, it returns all the backend entries
+
+ Get backends by name or suffix
+
+ @param suffix - suffix of the backend
+ @param backend_dn - DN of the backend entry
+ @param bename - 'commonname'/'cn' of the backend (e.g.
'userRoot')
+
+ @return backend entries
+
+ @raise None
+ """
+
+ filt = "(objectclass=%s)" % BACKEND_OBJECTCLASS_VALUE
+ if backend_dn:
+ self.log.info("List backend %s" % backend_dn)
+ base = backend_dn
+ scope = ldap.SCOPE_BASE
+ elif suffix:
+ self.log.info("List backend with suffix=%s" % suffix)
+ base = DN_PLUGIN
+ scope = ldap.SCOPE_SUBTREE
+ filt = "(&%s(|(%s=%s)(%s=%s)))" % (filt,
+
BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_SUFFIX],suffix,
+
BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_SUFFIX], normalizeDN(suffix))
+ elif bename:
+ self.log.info("List backend 'cn=%s'" % bename)
+ base = "%s=%s,%s" % (BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_NAME],
bename, DN_LDBM)
+ scope = ldap.SCOPE_BASE
+ else:
+ self.log.info("List all the backends")
+ base = DN_PLUGIN
+ scope = ldap.SCOPE_SUBTREE
+
+ try:
+ ents = self.conn.search_s(base, scope, filt)
+ except ldap.NO_SUCH_OBJECT:
+ return None
+
+ return ents
+
+
+ def _readonly(self, bename=None, readonly='on', suffix=None):
+ """Put a database in readonly mode
+ @param bename - the backend name (eg. addressbook1)
+ @param readonly- 'on' or 'off'
+
+ NOTE: I can ldif2db to a read-only database. After the
+ import, the database will still be in readonly.
+
+ NOTE: When a db is read-only, it seems you need to restart
+ the directory server before creating further
+ agreements or initialize consumers
+ """
+ if bename and suffix:
+ raise ValueError("Specify either bename or suffix")
+
+ if suffix:
+ raise NotImplementedError()
+
+ self.conn.modify_s(','.join(('cn=' + bename, DN_LDBM)), [
+ (ldap.MOD_REPLACE, BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_READONLY], readonly)
+ ])
+
+
+ def delete(self, suffix=None, backend_dn=None, bename=None):
+ """
+ Deletes the backend entry with the following steps:
+
+ Delete the indexes entries under this backend
+ Delete the encrypted attributes entries under this backend
+ Delete the encrypted attributes keys entries under this backend
+
+ If a mapping tree entry uses this backend (nsslapd-backend), it raise
UnwillingToPerformError
+
+ If 'suffix'/'backend_dn'/'benamebase' are specified.
+ It uses 'backend_dn' first, then 'suffix', then
'benamebase'.
+
+ If neither 'suffix', 'backend_dn' and 'benamebase' are
specified, it raise InvalidArgumentError
+ @param suffix - suffix of the backend
+ @param backend_dn - DN of the backend entry
+ @param bename - 'commonname'/'cn' of the backend (e.g.
'userRoot')
+
+ @return None
+
+ @raise InvalidArgumentError - if missing arguments or invalid
+ UnwillingToPerformError - if several backends match the argument
+ provided suffix does not match backend suffix
+ It exists a mapping tree that use that backend
+
+
+ """
+
+ # First check the backend exists and retrieved its suffix
+ be_ents = self.conn.backend.list(suffix=suffix, backend_dn=backend_dn,
bename=bename)
+ if len(be_ents) == 0:
+ raise InvalidArgumentError("Unable to retrieve the backend (%r, %r,
%r)" % (suffix, backend_dn, bename))
+ elif len(be_ents) > 1:
+ for ent in be_ents:
+ self.log.fatal("Multiple backend match the definition: %s" %
ent.dn)
+ if (not suffix) and (not backend_dn) and (not bename):
+ raise InvalidArgumentError("suffix and backend DN and backend name
are missing")
+ raise UnwillingToPerformError("Not able to identify the backend to
delete")
+ else:
+ be_ent = be_ents[0]
+ be_suffix = be_ent.getValue(BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_SUFFIX])
+
+ # Verify the provided suffix is the one stored in the found backend
+ if suffix:
+ if normalizeDN(suffix) != normalizeDN(be_suffix):
+ raise UnwillingToPerformError("provided suffix (%s) differs from
backend suffix (%s)" % (suffix, be_suffix))
+
+ # now check there is no mapping tree for that suffix
+ mt_ents = self.conn.mappingtree.list(suffix=be_suffix)
+ if len(mt_ents) > 0:
+ raise UnwillingToPerformError("It still exists a mapping tree (%s) for
that backend (%s)" % (mt_ents[0].dn, be_ent.dn))
+
+ # Now delete the indexes
+ found_bename = be_ent.getValue(BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_NAME])
+ if not bename:
+ bename = found_bename
+ elif bename != found_bename:
+ raise UnwillingToPerformError("Backend name specified (%s) differs from
the retrieved one (%s)" % (bename, found_bename))
+
+ self.conn.index.delete_all(bename)
+
+ # finally delete the backend children and the backend itself
+ ents = self.conn.search_s(be_ent.dn, ldap.SCOPE_ONELEVEL)
+ for ent in ents:
+ self.log.debug("Delete entry children %s" % (ent.dn))
+ self.conn.delete_s(ent.dn)
+
+ self.log.debug("Delete backend entry %s" % (be_ent.dn))
+ self.conn.delete_s(be_ent.dn)
+
+ return
+
+ def create(self, suffix=None, properties=None):
+ """
+ Creates backend entry and returns its dn.
+
+ If the properties 'chain-bind-pwd' and 'chain-bind-dn' and
'chain-urls' are specified
+ the backend is a chained backend.
+ A chaining backend is created under 'cn=chaining
database,cn=plugins,cn=config'.
+
+ A local backend is created under 'cn=ldbm
database,cn=plugins,cn=config'
+
+ @param suffix - suffix stored in the backend
+ @param properties - dictionary with properties values
+ supported properties are
+ BACKEND_NAME = 'name'
+ BACKEND_READONLY = 'read-only'
+ BACKEND_REQ_INDEX = 'require-index'
+ BACKEND_CACHE_ENTRIES = 'entry-cache-number'
+ BACKEND_CACHE_SIZE = 'entry-cache-size'
+ BACKEND_DNCACHE_SIZE = 'dn-cache-size'
+ BACKEND_DIRECTORY = 'directory'
+ BACKEND_DB_DEADLOCK = 'db-deadlock'
+ BACKEND_CHAIN_BIND_DN = 'chain-bind-dn'
+ BACKEND_CHAIN_BIND_PW = 'chain-bind-pw'
+ BACKEND_CHAIN_URLS = 'chain-urls'
+
+ @return backend DN of the created backend
+
+ @raise ValueError - If missing suffix
+ InvalidArgumentError - If it already exists a backend for that suffix
or a backend with the same DN
+
+ """
+ def _getBackendName(parent):
+ '''
+ Use to build a backend name that is not already used
+ '''
+ index = 1
+ while True:
+ bename = "local%ddb" % index
+ base = "%s=%s,%s" %
(BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_NAME], bename, parent)
+ scope = ldap.SCOPE_BASE
+ filt = "(objectclass=%s)" % BACKEND_OBJECTCLASS_VALUE
+ self.log.debug("_getBackendName: baser=%s : fileter=%s" %
(base, filt))
+ try:
+ ents = self.conn.getEntry(base, ldap.SCOPE_BASE, filt)
+ except (NoSuchEntryError, ldap.NO_SUCH_OBJECT) as e:
+ self.log.info("backend name will be %s" % bename)
+ return bename
+ index += 1
+
+ # suffix is mandatory
+ if not suffix:
+ raise ValueError("suffix is mandatory")
+ else:
+ nsuffix = normalizeDN(suffix)
+
+ # Check it does not already exist a backend for that suffix
+ ents = self.conn.backend.list(suffix=suffix)
+ if len(ents) != 0:
+ raise InvalidArgumentError("It already exists backend(s) for %s:
%s" % (suffix, ents[0].dn))
+
+ # Check if we are creating a local/chained backend
+ chained_suffix = properties and (BACKEND_CHAIN_BIND_DN in properties) and
(BACKEND_CHAIN_BIND_PW in properties) and (BACKEND_CHAIN_URLS in properties)
+ if chained_suffix:
+ self.log.info("Creating a chaining backend")
+ dnbase = DN_CHAIN
+ else:
+ self.log.info("Creating a local backend")
+ dnbase = DN_LDBM
+
+ # Get the future backend name
+ if properties and BACKEND_NAME in properties:
+ cn = properties[BACKEND_NAME]
+ else:
+ cn = _getBackendName(dnbase)
+
+ # Check the future backend name does not already exists
+ # we can imagine having no backends for 'suffix' but having a backend
+ # with the same name
+ dn = "%s=%s,%s" % (BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_NAME], cn,
dnbase)
+ ents = self.conn.backend.list(backend_dn=dn)
+ if ents:
+ raise InvalidArgumentError("It already exists a backend with that DN:
%s" % ents[0].dn)
+
+ # All checks are done, Time to create the backend
+ try:
+ entry = Entry(dn)
+ entry.update({
+ 'objectclass': ['top', 'extensibleObject',
BACKEND_OBJECTCLASS_VALUE],
+ BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_NAME]: cn,
+ BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_SUFFIX]: nsuffix
+ })
+
+ if chained_suffix:
+ entry.update({
+ BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_CHAIN_URLS]:
properties[BACKEND_CHAIN_URLS],
+ BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_CHAIN_BIND_DN]:
properties[BACKEND_CHAIN_BIND_DN],
+ BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_CHAIN_BIND_PW]:
properties[BACKEND_CHAIN_BIND_PW]
+ })
+
+ self.log.debug("adding entry: %r" % entry)
+ self.conn.add_s(entry)
+ except ldap.ALREADY_EXISTS, e:
+ self.log.error("Entry already exists: %r" % dn)
+ raise ldap.ALREADY_EXISTS("%s : %r" % (e, dn))
+ except ldap.LDAPError, e:
+ self.log.error("Could not add backend entry: %r" % dn)
+
+
+ backend_entry = self.conn._test_entry(dn, ldap.SCOPE_BASE)
+
+ return backend_entry
+
+ def getProperties(self, suffix=None, backend_dn=None, bename=None, properties=None):
+ raise NotImplemented
+
+ def setProperties(self,suffix=None, backend_dn=None, bename=None, properties=None):
+ raise NotImplemented
+
+ def toSuffix(self, entry=None, name=None):
+ '''
+ Return, for a given backend entry, the suffix values.
+ Suffix values are identical from a LDAP point of views. Suffix values may
+ be surrounded by ", or containing '\' escape characters.
+
+ @param entry - LDAP entry of the backend
+ @param name - backend DN
+
+ @result list of values of suffix attribute (aka 'cn')
+
+ @raise ldap.NO_SUCH_OBJECT - in name is invalid DN
+ ValueError - entry does not contains the suffix attribute
+ InvalidArgumentError - if both entry/name are missing
+ '''
+ attr_suffix = BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_SUFFIX]
+ if entry:
+ if not entry.hasValue(attr_suffix):
+ raise ValueError("Entry has no %s attribute %r" % (attr_suffix,
entry))
+ return entry.getValues(attr_suffix)
+ elif name:
+ filt = "(objectclass=%s)" % BACKEND_OBJECTCLASS_VALUE
+
+ try:
+ attrs = [attr_suffix]
+ ent = self.conn.getEntry(name, ldap.SCOPE_BASE, filt, attrs)
+ self.log.debug("toSuffix: %s found by its DN" % ent.dn)
+ except NoSuchEntryError:
+ raise ldap.NO_SUCH_OBJECT("Backend DN not found: %s" % name)
+
+ if not ent.hasValue(attr_suffix):
+ raise ValueError("Entry has no %s attribute %r" % (attr_suffix,
ent))
+ return ent.getValues(attr_suffix)
+ else:
+ raise InvalidArgumentError("entry or name are mandatory")
+
+
+
diff --git a/src/lib389/lib389/brooker.py b/src/lib389/lib389/brooker.py
index 1ab729e..26405a5 100644
--- a/src/lib389/lib389/brooker.py
+++ b/src/lib389/lib389/brooker.py
@@ -465,7 +465,7 @@ class Replica(object):
def _get_mt_entry(self, suffix):
"""Return the replica dn of the given suffix."""
- mtent = self.conn.getMTEntry(suffix)
+ mtent = self.conn.mappingtree.list(suffix=suffix)
return ','.join(("cn=replica", mtent.dn))
def create_repl_manager(self, repl_manager_dn=None, repl_manager_pw=None):
@@ -681,7 +681,7 @@ class Replica(object):
# create replica entry in mapping-tree
nsuffix = normalizeDN(suffix)
- mtent = self.conn.getMTEntry(suffix)
+ mtent = self.conn.mappingtree.list(suffix=suffix)
dn_replica = ','.join(("cn=replica", mtent.dn))
try:
entry = self.conn.getEntry(dn_replica, ldap.SCOPE_BASE)
@@ -922,261 +922,6 @@ class Config(object):
return self.conn.getEntry(DN_CONFIG, attrlist=fields)
-
-
-class Backend(object):
- proxied_methods = 'search_s getEntry'.split()
-
- def __init__(self, conn):
- """@param conn - a DirSrv instance"""
- self.conn = conn
- self.log = conn.log
-
- def __getattr__(self, name):
- if name in Replica.proxied_methods:
- return DirSrv.__getattr__(self.conn, name)
-
- def list(self, name=None, suffix=None, attrs=None):
- """Get backends by name or suffix
- @param name - backend name
- @param suffix - get backend for suffix
- """
- attrs = attrs or []
-
- # raise errors asap
- if name and suffix:
- raise ValueError("Can't specify both name and suffix")
-
- def _list_by_suffix(self, suffix, attrs=None):
- if suffix:
- nsuffix = normalizeDN(suffix)
- else:
- suffix = nsuffix = '*'
-
- entries = self.conn.search_s("cn=plugins,cn=config",
ldap.SCOPE_SUBTREE,
-
"(&(objectclass=nsBackendInstance)(|(nsslapd-suffix=%s)(nsslapd-suffix=%s)))"
% (suffix, nsuffix),
- attrs)
- return entries
-
- def _list_by_name(self, name, attrs=None):
- backend_dn = ','.join(('cn=' + name, DN_LDBM))
- return self.conn.search_s(backend_dn, ldap.SCOPE_BASE, attrlist=attrs)
-
- if name:
- return _list_by_name(self, name, attrs)
- elif suffix:
- return _list_by_suffix(self, suffix, attrs)
-
- return self.conn.search_s("cn=plugins,cn=config",
- ldap.SCOPE_SUBTREE, "(objectclass=nsBackendInstance)", attrs)
-
-
- def readonly(self, bename=None, readonly='on', suffix=None):
- """Put a database in readonly mode
- @param bename - the backend name (eg. addressbook1)
- @param readonly- 'on' or 'off'
-
- NOTE: I can ldif2db to a read-only database. After the
- import, the database will still be in readonly.
-
- NOTE: When a db is read-only, it seems you need to restart
- the directory server before creating further
- agreements or initialize consumers
- """
- if bename and suffix:
- raise ValueError("Specify either bename or suffix")
-
- if suffix:
- raise NotImplementedError()
-
- self.conn.modify_s(','.join(('cn=' + bename, DN_LDBM)), [
- (ldap.MOD_REPLACE, 'nsslapd-readonly', readonly)
- ])
-
-
- def delete(self, benamebase='localdb'):
- """Delete a backend.
- @param benamebase - the backend common name
-
- It firsts delete all the indexes under the backend, then delete the backend
entry
- Then deletes
- - encrypted attribute keys
- - encrypted attribute
- - monitor
- Then deletes the backend entry
- """
- self.conn.index.delete_all(benamebase)
-
- dn = "cn=encrypted attribute keys," + "cn=" + benamebase +
"," + DN_LDBM
- self.conn.delete_s(dn)
- dn = "cn=encrypted attributes," + "cn=" + benamebase +
"," + DN_LDBM
- self.conn.delete_s(dn)
- dn = "cn=monitor," + "cn=" + benamebase + "," +
DN_LDBM
- self.conn.delete_s(dn)
- dn = "cn=" + benamebase + "," + DN_LDBM
- self.conn.delete_s(dn)
- return
-
- def add(self, suffix, binddn=None, bindpw=None, urls=None, attrvals=None,
benamebase='localdb', setupmt=False, parent=None):
- """Setup a backend and return its dn. Blank on error XXX should
RAISE!
- @param suffix
- @param benamebase - the backend common name
- @param setupmt - eventually setup Mapping Tree entry
- @param urls - a string of ldapurl - create a chaining backend
- @oaram binddn - chaining proxy user
- @param bindpw - chaining proxy password
- @param attrvals: a dict with further params like
- for ldbm {
- 'nsslapd-cachememsize': '1073741824',
- 'nsslapd-cachesize': '-1',
- }
- for chain {
- 'nsmaxresponsedelay': '60',
- 'nsslapd-sizelimit': '-1'
- }
-
- ex.
- benamebase="Isle0-0"
- urls=[
- "ldaps://f0-ldap-vip.example.it:636/",
- "ldaps://ldap-18.example.it:636/",
- "ldaps://ldap-19.example.it:636/"
- ]
-
- NOTE: The suffix attribute is a mere string for the backend.
- the following action will work nicely:
-
c.backend.add(suffix="foo=example,dc=com",benamebase="db1")
-
- TODO: rename benamebase with cn
- TODO: split CHAIN and LDBM ? eg. backend.add_chain
- """
- attrvals = attrvals or {}
- dnbase = ""
- backend_entry = None
- suffix_entry = None
-
- # figure out what type of be based on args
- if binddn and bindpw and urls: # its a chaining be
- dnbase = DN_CHAIN
- else: # its a ldbm be
- dnbase = DN_LDBM
-
- nsuffix = normalizeDN(suffix)
- try:
- cn = benamebase
- self.log.debug("create backend with cn: %s" % cn)
- dn = "cn=" + cn + "," + dnbase
- entry = Entry(dn)
- entry.update({
- 'objectclass': ['top', 'extensibleObject',
'nsBackendInstance'],
- 'cn': cn,
- 'nsslapd-suffix': nsuffix
- })
-
- if binddn and bindpw and urls: # its a chaining be
- entry.update({
- 'nsfarmserverurl': urls,
- 'nsmultiplexorbinddn': binddn,
- 'nsmultiplexorcredentials': bindpw
- })
-
- # set attrvals (but not cn, because it's in dn)
- # TODO do it in Entry
- if attrvals:
- entry.update(attrvals)
-
- self.log.debug("adding entry: %r" % entry)
- self.conn.add_s(entry)
- except ldap.ALREADY_EXISTS, e:
- self.log.error("Entry already exists: %r" % dn)
- raise ldap.ALREADY_EXISTS("%s : %r" % (e, dn))
- except ldap.LDAPError, e:
- self.log.error("Could not add backend entry: %r" % dn)
- raise
-
- backend_entry = self.conn._test_entry(dn, ldap.SCOPE_BASE)
-
- #
- # if setupmt creates the suffix entry
- #
- if setupmt:
- self.log.debug("Setup Mapping Tree entry")
- suffix_entry = self.setup_mt(suffix=suffix, bename=cn, parent=parent)
- self.log.info("Created Mapping Tree entry %s" % suffix_entry.dn)
- return backend_entry, suffix_entry
-
-
- def setup_mt(self, suffix, bename, parent=None):
- """Setup a suffix with the given backend-name.
-
- @param suffix
- @param bename
- @param parent - the parent suffix
- @param verbose - None
-
- This method does not create the matching entry in the tree,
- nor the given backend. Both should be created apart.
-
- Ex. setup_mt(suffix='o=addressbook1', bename='addressbook1')
- creates:
- - the mapping in "cn=mapping tree,cn=config"
- you have to create:
- - the backend
- - the ldap entry "o=addressbook1" *after*
- """
- nsuffix = normalizeDN(suffix)
- #escapedn = escapeDNValue(nsuffix)
- if parent:
- nparent = normalizeDN(parent)
- else:
- nparent = ""
-
- filt = suffixfilt(suffix)
- # if suffix exists, return
- try:
- entry = self.conn.getEntry(
- DN_MAPPING_TREE, ldap.SCOPE_SUBTREE, filt)
- return entry
- except NoSuchEntryError:
- entry = None
-
- # fix me when we can actually used escaped DNs
- #dn = "cn=%s,cn=mapping tree,cn=config" % escapedn
- dn = ','.join(('cn="%s"' % nsuffix, DN_MAPPING_TREE))
- entry = Entry(dn)
- entry.update({
- 'objectclass': ['top', 'extensibleObject',
'nsMappingTree'],
- 'nsslapd-state': 'backend',
- # the value in the dn has to be DN escaped
- # internal code will add the quoted value - unquoted value is useful for
searching
- 'cn': nsuffix,
- 'nsslapd-backend': bename
- })
- #entry.setValues('cn', [escapedn, nsuffix]) # the value in the dn has to
be DN escaped
- # the other value can be the unescaped value
- if parent:
- entry.setValues('nsslapd-parent-suffix', nparent)
- try:
- self.log.debug("Creating entry: %s" % entry.dn)
- self.log.info("Entry %r" % entry)
- self.conn.add_s(entry)
- except ldap.LDAPError, e:
- raise ldap.LDAPError("Error adding suffix entry " + dn, e)
-
- ret = self.conn._test_entry(dn, ldap.SCOPE_BASE)
- return ret
-
- def suffixes(self,unquote=False):
- """Return a list of suffixes dn
- @param unquote - remove ldap escaping
- """
- suffixes = self.search_s(DN_MAPPING_TREE, ldap.SCOPE_ONELEVEL)
- if unquote:
- raise NotImplementedError()
- return
[x.dn.replace("\3D","=").replace("\2C",",") for x
in suffixes]
- return [x.dn for x in suffixes]
-
-
class Index(object):
def __init__(self, conn):
diff --git a/src/lib389/lib389/mappingTree.py b/src/lib389/lib389/mappingTree.py
new file mode 100644
index 0000000..c4224fb
--- /dev/null
+++ b/src/lib389/lib389/mappingTree.py
@@ -0,0 +1,324 @@
+'''
+Created on Dec 13, 2013
+
+@author: tbordaz
+'''
+
+import ldap
+
+from lib389 import DirSrv, Entry, NoSuchEntryError, UnwillingToPerformError,
InvalidArgumentError
+from lib389._constants import *
+from lib389.utils import (
+ suffixfilt,
+ normalizeDN
+ )
+from lib389.properties import *
+
+class MappingTree(object):
+ '''
+ classdocs
+ '''
+
+
+ proxied_methods = 'search_s getEntry'.split()
+
+ def __init__(self, conn):
+ """@param conn - a DirSrv instance"""
+ self.conn = conn
+ self.log = conn.log
+
+ def __getattr__(self, name):
+ if name in MappingTree.proxied_methods:
+ return DirSrv.__getattr__(self.conn, name)
+
+ def list(self, suffix=None, bename=None):
+ '''
+ Returns a search result of the mapping tree entries with all their
attributes
+
+ If 'suffix'/'bename' are specified. It uses
'benamebase' first, then 'suffix'.
+
+ If neither 'suffix' and 'bename' are specified, it returns
all the mapping tree entries
+
+ @param suffix - suffix of the backend
+ @param benamebase - backend common name (e.g. 'userRoot')
+
+ @return mapping tree entries
+
+ @raise if search fails
+
+ '''
+ if bename:
+ filt = "(%s=%s)" % (MT_PROPNAME_TO_ATTRNAME[MT_BACKEND], bename)
+ elif suffix:
+ filt = "(%s=%s)" % (MT_PROPNAME_TO_ATTRNAME[MT_SUFFIX], suffix)
+ else:
+ filt = "(objectclass=%s)" % MT_OBJECTCLASS_VALUE
+
+ try:
+ ents = self.conn.search_s(DN_MAPPING_TREE, ldap.SCOPE_ONELEVEL, filt)
+ for ent in ents:
+ self.log.debug('list: %r' % ent)
+ except:
+ raise
+
+ return ents
+
+
+ def create(self, suffix=None, bename=None, parent=None):
+ '''
+ Create a mapping tree entry (under "cn=mapping tree,cn=config"),
for the 'suffix'
+ and that is stored in 'bename' backend.
+ 'bename' backend must exists before creating the mapping tree entry.
+
+ If a 'parent' is provided that means that we are creating a
sub-suffix mapping tree.
+
+ @param suffix - suffix mapped by this mapping tree entry. It will be the
common name ('cn') of the entry
+ @param benamebase - backend common name (e.g. 'userRoot')
+ @param parent - if provided is a parent suffix of 'suffix'
+
+ @return DN of the mapping tree entry
+
+ @raise ldap.NO_SUCH_OBJECT - if the backend entry or parent mapping tree does
not exist
+
+ '''
+ # Check suffix is provided
+ if not suffix:
+ raise ValueError("suffix is mandatory")
+ else:
+ nsuffix = normalizeDN(suffix)
+
+ # Check backend name is provided
+ if not bename:
+ raise ValueError("backend name is mandatory")
+
+ # Check that if the parent suffix is provided then
+ # it exists a mapping tree for it
+ if parent:
+ nparent = normalizeDN(parent)
+ filt = suffixfilt(parent)
+ try:
+ entry = self.conn.getEntry(DN_MAPPING_TREE, ldap.SCOPE_SUBTREE, filt)
+ pass
+ except NoSuchEntryError:
+ raise ValueError("parent suffix has no mapping tree")
+ else:
+ nparent = ""
+
+ # Check if suffix exists, return
+ filt = suffixfilt(suffix)
+ try:
+ entry = self.conn.getEntry(DN_MAPPING_TREE, ldap.SCOPE_SUBTREE, filt)
+ return entry
+ except NoSuchEntryError:
+ entry = None
+
+ #
+ # Now start the real work
+ #
+
+ # fix me when we can actually used escaped DNs
+ dn = ','.join(('cn="%s"' % nsuffix, DN_MAPPING_TREE))
+ entry = Entry(dn)
+ entry.update({
+ 'objectclass': ['top', 'extensibleObject',
MT_OBJECTCLASS_VALUE],
+ 'nsslapd-state': 'backend',
+ # the value in the dn has to be DN escaped
+ # internal code will add the quoted value - unquoted value is useful for
searching
+ MT_PROPNAME_TO_ATTRNAME[MT_SUFFIX]: nsuffix,
+ MT_PROPNAME_TO_ATTRNAME[MT_BACKEND]: bename
+ })
+
+ # possibly add the parent
+ if parent:
+ entry.setValues(MT_PROPNAME_TO_ATTRNAME[MT_PARENT_SUFFIX], nparent)
+
+ try:
+ self.log.debug("Creating entry: %s" % entry.dn)
+ self.log.info("Entry %r" % entry)
+ self.conn.add_s(entry)
+ except ldap.LDAPError, e:
+ raise ldap.LDAPError("Error adding suffix entry " + dn, e)
+
+ ret = self.conn._test_entry(dn, ldap.SCOPE_BASE)
+ return ret.dn
+
+ def delete(self, suffix=None, bename=None, name=None):
+ '''
+ Delete a mapping tree entry (under "cn=mapping tree,cn=config"),
for the 'suffix' and
+ that is stored in 'benamebase' backend.
+ 'benamebase' backend is not changed by the mapping tree deletion.
+
+ If 'name' is specified. It uses it to retrieve the mapping tree to
delete
+ Else if 'suffix'/'benamebase' are specified. It uses both to
retrieve the mapping tree to delete
+
+ @param suffix - suffix mapped by this mapping tree entry. It is the common
name ('cn') of the entry
+ @param benamebase - backend common name (e.g. 'userRoot')
+ @param name - DN of the mapping tree entry
+
+ @return None
+
+ @raise ldap.NO_SUCH_OBJECT - the entry is not found
+ KeyError if 'name', 'suffix' and
'benamebase' are missing
+ UnwillingToPerformError - If the mapping tree has subordinates
+ '''
+ if name:
+ filt = "(objectclass=%s)" % MT_OBJECTCLASS_VALUE
+ try:
+ ent = self.conn.getEntry(name, ldap.SCOPE_BASE, filt)
+ self.log.debug("delete: %s found by its DN" % ent.dn)
+ except NoSuchEntryError:
+ raise ldap.NO_SUCH_OBJECT("mapping tree DN not found: %s" %
name)
+ else:
+ filt=None
+
+ if suffix:
+ filt = suffixfilt(suffix)
+
+ if bename:
+ if filt:
+ filt = "(&(%s=%s)%s)" %
(MT_PROPNAME_TO_ATTRNAME[MT_BACKEND], bename, filt)
+ else:
+ filt = "(%s=%s)" % (MT_PROPNAME_TO_ATTRNAME[MT_BACKEND],
bename)
+
+ try:
+ ent = self.conn.getEntry(DN_MAPPING_TREE, ldap.SCOPE_ONELEVEL, filt)
+ self.log.debug("delete: %s found by with %s" % (ent.dn, filt))
+ except NoSuchEntryError:
+ raise ldap.NO_SUCH_OBJECT("mapping tree DN not found: %s" %
name)
+
+ #
+ # At this point 'ent' contains the mapping tree entry to delete
+ #
+
+ # First Check there is no child (replica, replica agreements)
+ try:
+ ents = self.conn.search_s(ent.dn, ldap.SCOPE_SUBTREE,
"objectclass=*")
+ except:
+ raise
+ if len(ents) != 1:
+ for entry in ents:
+ self.log.warning("Error: it exists %s under %s" % (entry.dn,
ent.dn))
+ raise UnwillingToPerformError("Unable to delete %s, it is not a
leaf" % ent.dn)
+ else:
+ for entry in ents:
+ self.log.warning("Warning: %s (%s)" % (entry.dn, ent.dn))
+ self.conn.delete_s(ent.dn)
+
+ def getProperties(self, suffix=None, bename=None, name=None, properties=None):
+ '''
+ Returns a dictionary of the requested properties.
+ If properties is missing, it returns all the properties.
+
+ The returned properties are those of the 'suffix' and that is stored
in 'benamebase' backend.
+
+ If 'name' is specified. It uses it to retrieve the mapping tree to
delete Else if 'suffix'/'benamebase' are specified. It uses both to
retrieve the mapping tree to
+
+ If 'name', 'benamebase' and 'suffix' are missing it
raise an exception
+
+ @param suffix - suffix mapped by this mapping tree entry. It is the common
name ('cn') of the entry
+ @param benamebase - backend common name (e.g. 'userRoot')
+ @param name - DN of the mapping tree entry
+ @param - properties - list of properties
+
+ @return - returns a dictionary of the properties
+
+ @raise ValueError - if some name of properties are not valid
+ KeyError - if some name of properties are not valid
+ ldap.NO_SUCH_OBJECT - if the mapping tree entry is not found
+ '''
+
+ if name:
+ filt = "(objectclass=%s)" % MT_OBJECTCLASS_VALUE
+
+ try:
+ ent = self.conn.getEntry(name, ldap.SCOPE_BASE, filt,
MT_PROPNAME_TO_ATTRNAME.values())
+ self.log.debug("delete: %s found by its DN" % ent.dn)
+ except NoSuchEntryError:
+ raise ldap.NO_SUCH_OBJECT("mapping tree DN not found: %s" %
name)
+ else:
+ filt=None
+
+ if suffix:
+ filt = suffixfilt(suffix)
+
+ if bename:
+ if filt:
+ filt = "(&(%s=%s)%s)" %
(MT_PROPNAME_TO_ATTRNAME[MT_BACKEND], bename, filt)
+ else:
+ filt = "(%s=%s)" % (MT_PROPNAME_TO_ATTRNAME[MT_BACKEND],
bename)
+
+ try:
+ ent = self.conn.getEntry(DN_MAPPING_TREE, ldap.SCOPE_ONELEVEL, filt,
MT_PROPNAME_TO_ATTRNAME.values())
+ self.log.debug("delete: %s found by with %s" % (ent.dn, filt))
+ except NoSuchEntryError:
+ raise ldap.NO_SUCH_OBJECT("mapping tree DN not found: %s" %
name)
+
+
+ result = {}
+ attrs = []
+ if properties:
+ #
+ # build the list of attributes we are looking for
+
+ for prop_name in properties:
+ prop_attr = MT_PROPNAME_TO_ATTRNAME[prop_name]
+ if not prop_attr:
+ raise ValueError("Improper property name: %s ", prop_name)
+ self.log.debug("Look for attr %s (property: %s)" % (prop_attr,
prop_name))
+ attrs.append(prop_attr)
+
+ # now look for each attribute from the MT entry
+ for attr in ent.getAttrs():
+ # given an attribute name retrieve the property name
+ props = [ k for k, v in MT_PROPNAME_TO_ATTRNAME.iteritems() if v.lower() ==
attr.lower() ]
+
+ # If this attribute is present in the MT properties and was requested, adds
it to result
+ if len(props) > 0:
+ if len(attrs) > 0:
+ if MT_PROPNAME_TO_ATTRNAME[props[0]] in attrs:
+ # if the properties was requested
+ self.log.debug("keep only attribute %s " % (props[0]))
+ result[props[0]] = ent.getValues(attr)
+ else:
+ result[props[0]] = ent.getValues(attr)
+ return result
+
+ def setProperties(self, suffix=None, bename=None, name=None, properties=None):
+ raise NotImplemented()
+
+ def toSuffix(self, entry=None, name=None):
+ '''
+ Return, for a given mapping tree entry, the suffix values.
+ Suffix values are identical from a LDAP point of views. Suffix values may
+ be surrounded by ", or containing '\' escape characters.
+
+ @param entry - LDAP entry of the mapping tree
+ @param name - mapping tree DN
+
+ @result list of values of suffix attribute (aka 'cn')
+
+ @raise ldap.NO_SUCH_OBJECT - in name is invalid DN
+ ValueError - entry does not contains the suffix attribute
+ InvalidArgumentError - if both entry/name are missing
+ '''
+ attr_suffix = MT_PROPNAME_TO_ATTRNAME[MT_SUFFIX]
+ if entry:
+ if not entry.hasValue(attr_suffix):
+ raise ValueError("Entry has no %s attribute %r" % (attr_suffix,
entry))
+ return entry.getValues(attr_suffix)
+ elif name:
+ filt = "(objectclass=%s)" % MT_OBJECTCLASS_VALUE
+
+ try:
+ attrs = [attr_suffix]
+ ent = self.conn.getEntry(name, ldap.SCOPE_BASE, filt, attrs)
+ self.log.debug("toSuffix: %s found by its DN" % ent.dn)
+ except NoSuchEntryError:
+ raise ldap.NO_SUCH_OBJECT("mapping tree DN not found: %s" %
name)
+
+ if not ent.hasValue(attr_suffix):
+ raise ValueError("Entry has no %s attribute %r" % (attr_suffix,
ent))
+ return ent.getValues(attr_suffix)
+ else:
+ raise InvalidArgumentError("entry or name are mandatory")
+
diff --git a/src/lib389/lib389/properties.py b/src/lib389/lib389/properties.py
index 32a210a..07b4d0d 100644
--- a/src/lib389/lib389/properties.py
+++ b/src/lib389/lib389/properties.py
@@ -4,15 +4,21 @@ Created on Dec 5, 2013
@author: tbordaz
'''
+####################################
+#
+# Properties supported by the server
+#
+####################################
+
#
-# Properties supported by the server WITH related attribute name
+# Those WITH related attribute name
#
-SER_HOST='hostname'
-SER_PORT='ldap-port'
+SER_HOST ='hostname'
+SER_PORT ='ldap-port'
SER_SECURE_PORT='ldap-secureport'
-SER_ROOT_DN='root-dn'
-SER_ROOT_PW='root-pw'
-SER_USER_ID='user-id'
+SER_ROOT_DN ='root-dn'
+SER_ROOT_PW ='root-pw'
+SER_USER_ID ='user-id'
SER_PROPNAME_TO_ATTRNAME= {SER_HOST:'nsslapd-localhost',
SER_PORT:'nsslapd-port',
@@ -21,17 +27,95 @@ SER_PROPNAME_TO_ATTRNAME= {SER_HOST:'nsslapd-localhost',
SER_ROOT_PW:'nsslapd-rootpw',
SER_USER_ID:'nsslapd-localuser'}
#
-# Properties supported by the server WITHOUT related attribute name
+# Those WITHOUT related attribute name
+#
+SER_SERVERID_PROP ='server-id'
+SER_GROUP_ID ='group-id'
+SER_DEPLOYED_DIR ='deployed-dir'
+SER_BACKUP_INST_DIR ='inst-backupdir'
+SER_CREATION_SUFFIX ='suffix'
+
+####################################
+#
+# Properties supported by the Mapping Tree entries
#
-SER_SERVERID_PROP='server-id'
-SER_GROUP_ID='group-id'
-SER_DEPLOYED_DIR='deployed-dir'
-SER_BACKUP_INST_DIR='inst-backupdir'
-SER_CREATION_SUFFIX='suffix'
+####################################
+
+# Properties name
+MT_STATE = 'state'
+MT_BACKEND = 'backend-name'
+MT_SUFFIX = 'suffix'
+MT_REFERRAL = 'referral'
+MT_CHAIN_PATH = 'chain-plugin-path'
+MT_CHAIN_FCT = 'chain-plugin-fct'
+MT_CHAIN_UPDATE = 'chain-update-policy'
+MT_PARENT_SUFFIX= 'parent-suffix'
+
+# Properties values
+MT_STATE_VAL_DISABLED = 'disabled'
+MT_STATE_VAL_BACKEND = 'backend'
+MT_STATE_VAL_REFERRAL = 'referral'
+MT_STATE_VAL_REFERRAL_ON_UPDATE='referral-on-update'
+MT_STATE_VAL_CONTAINER ='container'
+MT_STATE_TO_VALUES={MT_STATE_VAL_DISABLED: 0,
+ MT_STATE_VAL_BACKEND: 1,
+ MT_STATE_VAL_REFERRAL: 2,
+ MT_STATE_VAL_REFERRAL_ON_UPDATE: 3,
+ MT_STATE_VAL_CONTAINER: 4}
+
+MT_CHAIN_UPDATE_VAL_ON_UPDATE = 'repl_chain_on_update'
+MT_OBJECTCLASS_VALUE = 'nsMappingTree'
+MT_PROPNAME_TO_VALUES = {MT_STATE:MT_STATE_TO_VALUES}
+MT_PROPNAME_TO_ATTRNAME = {MT_STATE: 'nsslapd-state',
+ MT_BACKEND: 'nsslapd-backend',
+ MT_SUFFIX: 'cn',
+ MT_PARENT_SUFFIX:'nsslapd-parent-suffix',
+ MT_REFERRAL: 'nsslapd-referral',
+ MT_CHAIN_PATH: 'nsslapd-distribution-plugin',
+ MT_CHAIN_FCT : 'nsslapd-distribution-funct',
+ MT_CHAIN_UPDATE: 'nsslapd-distribution-root-update'}
+
+####################################
+#
+# Properties supported by the backend
#
+####################################
+BACKEND_SUFFIX = 'suffix'
+BACKEND_NAME = 'name'
+BACKEND_READONLY = 'read-only'
+BACKEND_REQ_INDEX = 'require-index'
+BACKEND_CACHE_ENTRIES = 'entry-cache-number'
+BACKEND_CACHE_SIZE = 'entry-cache-size'
+BACKEND_DNCACHE_SIZE = 'dn-cache-size'
+BACKEND_DIRECTORY = 'directory'
+BACKEND_DB_DEADLOCK = 'db-deadlock'
+BACKEND_CHAIN_BIND_DN = 'chain-bind-dn'
+BACKEND_CHAIN_BIND_PW = 'chain-bind-pw'
+BACKEND_CHAIN_URLS = 'chain-urls'
+BACKEND_STATS = 'stats'
+
+BACKEND_OBJECTCLASS_VALUE = 'nsBackendInstance'
+
+BACKEND_PROPNAME_TO_ATTRNAME = {BACKEND_SUFFIX: 'nsslapd-suffix',
+ BACKEND_NAME: 'cn',
+ BACKEND_READONLY: 'nsslapd-readonly',
+ BACKEND_REQ_INDEX: 'nsslapd-require-index',
+ BACKEND_CACHE_ENTRIES: 'nsslapd-cachesize',
+ BACKEND_CACHE_SIZE: 'nsslapd-cachememsize',
+ BACKEND_DNCACHE_SIZE: 'nsslapd-dncachememsize',
+ BACKEND_DIRECTORY: 'nsslapd-directory',
+ BACKEND_DB_DEADLOCK:
'nsslapd-db-deadlock-policy',
+ BACKEND_CHAIN_BIND_DN: 'nsmultiplexorbinddn',
+ BACKEND_CHAIN_BIND_PW:
'nsmultiplexorcredentials',
+ BACKEND_CHAIN_URLS: 'nsfarmserverurl'}
+
+####################################
+#
# Properties supported by the replica agreement
#
+####################################
+
RA_SCHEDULE_PROP='schedule'
RA_TRANSPORT_PROT_PROP='transport-prot'
RA_FRAC_EXCLUDE_PROP='fractional-exclude-attrs-inc'
diff --git a/src/lib389/lib389/suffix.py b/src/lib389/lib389/suffix.py
new file mode 100644
index 0000000..bd6d346
--- /dev/null
+++ b/src/lib389/lib389/suffix.py
@@ -0,0 +1,84 @@
+'''
+Created on Dec 17, 2013
+
+@author: tbordaz
+'''
+
+from lib389 import DirSrv, Entry, NotImplementedError, InvalidArgumentError
+from lib389.properties import *
+
+class Suffix(object):
+ proxied_methods = 'search_s getEntry'.split()
+
+ def __init__(self, conn):
+ """@param conn - a DirSrv instance"""
+ self.conn = conn
+ self.log = conn.log
+
+ def __getattr__(self, name):
+ if name in Suffix.proxied_methods:
+ return DirSrv.__getattr__(self.conn, name)
+
+ def list(self):
+ '''
+ Returns the list of suffixes DN for which it exists a mapping tree entry
+
+ @param None
+
+ @return list of suffix DN
+
+ @raise None
+ '''
+ suffixes = []
+ ents = self.conn.mappingtree.list()
+ for ent in ents:
+ vals = self.conn.mappingtree.toSuffix(entry=ent)
+ suffixes.append(vals[0])
+ return suffixes
+
+ def toBackend(self, suffix=None):
+ '''
+ Returns the backend entry that stores the provided suffix
+
+ @param suffix - suffix DN of the backend
+
+ @return backend - LDAP entry of the backend
+
+ @return ValueError - if suffix is not provided
+
+ '''
+ if not suffix:
+ raise ValueError("suffix is mandatory")
+
+ return self.conn.backend.list(suffix=suffix)
+
+ def getParent(self, suffix=None):
+ '''
+ Returns the DN of a suffix that is the parent of the provided
'suffix'.
+ If 'suffix' has no parent, it returns None
+
+ @param suffix - suffix DN of the backend
+
+ @return parent suffix DN
+
+ @return ValueError - if suffix is not provided
+ InvalidArgumentError - if suffix is not implemented on the server
+
+ '''
+ if not suffix:
+ raise ValueError("suffix is mandatory")
+
+ ents = self.conn.mappingtree.list(suffix=suffix)
+ if len(ents) == 0:
+ raise InvalidArgumentError("suffix %s is not implemented on that
server" % suffix)
+
+ mapping_tree = ents[0]
+ if mapping_tree.hasValue(MT_PROPNAME_TO_ATTRNAME[MT_PARENT_SUFFIX]):
+ return mapping_tree.getValue(MT_PROPNAME_TO_ATTRNAME[MT_PARENT_SUFFIX])
+ else:
+ return None
+
+
+ raise NotImplemented
+
+
diff --git a/src/lib389/tests/backend_test.py b/src/lib389/tests/backend_test.py
index fe36eb3..b4345c0 100644
--- a/src/lib389/tests/backend_test.py
+++ b/src/lib389/tests/backend_test.py
@@ -1,120 +1,229 @@
-"""Brooker classes to organize ldap methods.
- Stuff is split in classes, like:
- * Replica
- * Backend
- * Suffix
-
- You will access this from:
- DirSrv.backend.methodName()
-"""
-
-from nose import *
-from nose.tools import *
-
-import config
-from config import log
-from config import *
+'''
+Created on Dec 17, 2013
+@author: tbordaz
+'''
+import os
+import pwd
import ldap
-import time
-import sys
-import lib389
-from lib389 import DirSrv, Entry
-from lib389 import NoSuchEntryError
-from lib389 import utils
-from lib389.tools import DirSrvTools
-from subprocess import Popen
from random import randint
-from lib389.brooker import Replica
-from lib389 import MASTER_TYPE, DN_MAPPING_TREE, DN_CHANGELOG, DN_LDBM
-# Test harnesses
-from dsadmin_test import drop_backend, addbackend_harn
-from dsadmin_test import drop_added_entries
-
-conn = None
-added_entries = None
-added_backends = None
-
-MOCK_REPLICA_ID = '12'
-MOCK_TESTREPLICA_DN = "cn=testReplicadb,cn=ldbm database,cn=plugins,cn=config"
-
-def setup():
- # uses an existing 389 instance
- # add a suffix
- # add an agreement
- # This setup is quite verbose but to test DirSrv method we should
- # do things manually. A better solution would be to use an LDIF.
- global conn
- conn = DirSrv(**config.auth)
- conn.verbose = True
- conn.added_entries = []
- conn.added_backends = set(['o=mockbe1'])
- conn.added_replicas = []
-
- # add a backend for testing ruv and agreements
- suffix = 'o=testReplica'
- backend = 'testReplicadb'
- backendEntry, dummy = conn.backend.add(suffix, benamebase=backend)
- suffixEntry = conn.backend.setup_mt(suffix, backend)
-
-
- # add another backend for testing replica.add()
- suffix = 'o=testReplicaCreation'
- backend = 'testReplicaCreationdb'
- backendEntry, dummy = conn.backend.add(suffix, benamebase=backend)
- suffixEntry = conn.backend.setup_mt(suffix, backend)
-
-
-def teardown():
- global conn
- drop_added_entries(conn)
- conn.delete_s(','.join(['cn="o=testreplica"',
DN_MAPPING_TREE]))
- conn.backend.delete('testReplicadb')
+from lib389.tools import DirSrvTools
+from lib389._constants import *
+from lib389.properties import *
+from lib389 import DirSrv,Entry, InvalidArgumentError
+
+
+TEST_REPL_DN = "uid=test,%s" % DEFAULT_SUFFIX
+INSTANCE_PORT = 54321
+INSTANCE_SERVERID = 'dirsrv'
+#INSTANCE_PREFIX = os.environ.get('PREFIX', None)
+INSTANCE_PREFIX = None
+INSTANCE_BACKUP = os.environ.get('BACKUPDIR', DEFAULT_BACKUPDIR)
+NEW_SUFFIX_1 = 'o=test_create'
+NEW_BACKEND_1 = 'test_createdb'
+NEW_CHILDSUFFIX_1 = 'o=child1,o=test_create'
+NEW_CHILDBACKEND_1 = 'test_createchilddb'
+NEW_SUFFIX_2 = 'o=test_bis_create'
+NEW_BACKEND_2 = 'test_bis_createdb'
+NEW_CHILDSUFFIX_2 = 'o=child2,o=test_bis_create'
+NEW_CHILDBACKEND_2 = 'test_bis_createchilddb'
+
+class Test_Backend(object):
+
+
+ def setUp(self):
+ instance = DirSrv(verbose=False)
+ instance.log.debug("Instance allocated")
+ args = {SER_HOST: LOCALHOST,
+ SER_PORT: INSTANCE_PORT,
+ SER_DEPLOYED_DIR: INSTANCE_PREFIX,
+ SER_SERVERID_PROP: INSTANCE_SERVERID
+ }
+ instance.allocate(args)
+ if instance.exists():
+ instance.delete()
+ instance.create()
+ instance.open()
+ self.instance = instance
+
+
+ def tearDown(self):
+ if self.instance.exists():
+ self.instance.delete()
+
+
+ def test_list(self):
+ ents = self.instance.backend.list()
+ nb_backend = len(ents)
+ for ent in ents:
+ self.instance.log.info("List(%d): backend %s" % (nb_backend,
ent.dn))
+
+ # Create a first backend and check list all backends
+ backendEntry = self.instance.backend.create(suffix=NEW_SUFFIX_1,
properties={BACKEND_NAME: NEW_BACKEND_1})
+ ents = self.instance.backend.list()
+ for ent in ents:
+ self.instance.log.info("List(%d): backend %s" % (nb_backend + 1,
ent.dn))
+ assert len(ents) == (nb_backend + 1)
+
+ # Create a second backend and check list all backends
+ backendEntry = self.instance.backend.create(suffix=NEW_SUFFIX_2,
properties={BACKEND_NAME: NEW_BACKEND_2})
+ ents = self.instance.backend.list()
+ for ent in ents:
+ self.instance.log.info("List(%d): backend %s" % (nb_backend + 2,
ent.dn))
+ assert len(ents) == (nb_backend + 2)
+
+ # Check list a backend per suffix
+ ents = self.instance.backend.list(suffix=NEW_SUFFIX_1)
+ for ent in ents:
+ self.instance.log.info("List suffix (%d): backend %s" % (1,
ent.dn))
+ assert len(ents) == 1
+
+ # Check list a backend by its name
+ ents = self.instance.backend.list(bename=NEW_BACKEND_2)
+ for ent in ents:
+ self.instance.log.info("List name (%d): backend %s" % (1, ent.dn))
+ assert len(ents) == 1
+
+ # Check list backends by their DN
+ all = self.instance.backend.list()
+ for ent in all:
+ ents = self.instance.backend.list(backend_dn=ent.dn)
+ for bck in ents:
+ self.instance.log.info("List DN (%d): backend %s" % (1,
bck.dn))
+ assert len(ents) == 1
+
+ # Check list with valid backend DN but invalid suffix/bename
+ all = self.instance.backend.list()
+ for ent in all:
+ ents = self.instance.backend.list(suffix="o=dummy",
backend_dn=ent.dn, bename="dummydb")
+ for bck in ents:
+ self.instance.log.info("List invalid suffix+bename (%d): backend
%s" % (1, bck.dn))
+ assert len(ents) == 1
- conn.delete_s(','.join(['cn="o=testReplicaCreation"',
DN_MAPPING_TREE]))
- conn.backend.delete('testReplicaCreationdb')
-
+ def test_create(self):
+ # just to make it clean before starting
+ self.instance.backend.delete(suffix=NEW_SUFFIX_1)
+ self.instance.backend.delete(suffix=NEW_SUFFIX_2)
+
+ # create a backend
+ backendEntry = self.instance.backend.create(suffix=NEW_SUFFIX_1,
properties={BACKEND_NAME: NEW_BACKEND_1})
+
+ # check missing suffix
+ try:
+ backendEntry = self.instance.backend.create()
+ assert backendEntry == None
+ except Exception as e:
+ self.instance.log.info('Fail to create (expected)%s: %s' %
(type(e).__name__,e.args))
+ pass
+
+ # check already existing backend for that suffix
+ try:
+ backendEntry = self.instance.backend.create(suffix=NEW_SUFFIX_1)
+ assert backendEntry == None
+ except Exception as e:
+ self.instance.log.info('Fail to create (expected)%s: %s' %
(type(e).__name__, e.args))
+ pass
+
+ # check already existing backend DN, create for a different suffix but same
backend name
+ try:
+ backendEntry = self.instance.backend.create(suffix=NEW_SUFFIX_2,
properties={BACKEND_NAME: NEW_BACKEND_1})
+ assert backendEntry == None
+ except Exception as e:
+ self.instance.log.info('Fail to create (expected)%s: %s' %
(type(e).__name__, e.args))
+ pass
+
+ # create a backend without properties
+ backendEntry = self.instance.backend.create(suffix=NEW_SUFFIX_2)
+ ents = self.instance.backend.list()
+ for ent in ents:
+ self.instance.log.info("List: backend %s" % (ent.dn))
+
+
+ def test_delete(self):
+ # just to make it clean before starting
+ self.instance.backend.delete(suffix=NEW_SUFFIX_1)
+ self.instance.backend.delete(suffix=NEW_SUFFIX_2)
+ ents = self.instance.backend.list()
+ nb_backend = len(ents)
+
+ # Check the various possibility to delete a backend
+ # First with suffix
+ backendEntry = self.instance.backend.create(suffix=NEW_SUFFIX_1,
properties={BACKEND_NAME: NEW_BACKEND_1})
+ self.instance.backend.delete(suffix=NEW_SUFFIX_1)
+ ents = self.instance.backend.list()
+ assert len(ents) == nb_backend
+
+ # Second with backend name
+ backendEntry = self.instance.backend.create(suffix=NEW_SUFFIX_1,
properties={BACKEND_NAME: NEW_BACKEND_1})
+ self.instance.backend.delete(bename=NEW_BACKEND_1)
+ ents = self.instance.backend.list()
+ assert len(ents) == nb_backend
+
+ # Third with backend DN
+ backendEntry = self.instance.backend.create(suffix=NEW_SUFFIX_1,
properties={BACKEND_NAME: NEW_BACKEND_1})
+ ents = self.instance.backend.list(suffix=NEW_SUFFIX_1)
+ assert len(ents) == 1
+ self.instance.backend.delete(backend_dn=ents[0].dn)
+ ents = self.instance.backend.list()
+ assert len(ents) == nb_backend
+
+ # Now check the failure cases
+ # First no argument -> InvalidArgumentError
+ try:
+ backendEntry = self.instance.backend.create(suffix=NEW_SUFFIX_1,
properties={BACKEND_NAME: NEW_BACKEND_1})
+ self.instance.backend.delete()
+ except Exception as e:
+ self.instance.log.info('Fail to delete (expected)%s: %s' %
(type(e).__name__,e.args))
+ self.instance.backend.delete(suffix=NEW_SUFFIX_1)
+ pass
+
+ # second invalid suffix -> InvalidArgumentError
+ try:
+ self.instance.backend.delete(suffix=NEW_SUFFIX_2)
+ except Exception as e:
+ self.instance.log.info('Fail to delete (expected)%s: %s' %
(type(e).__name__,e.args))
+ pass
+
+ # existing a mapping tree -> UnwillingToPerformError
+ backendEntry = self.instance.backend.create(suffix=NEW_SUFFIX_1,
properties={BACKEND_NAME: NEW_BACKEND_1})
+ self.instance.mappingtree.create(NEW_SUFFIX_1, bename=NEW_BACKEND_1)
+ try:
+ self.instance.backend.delete(suffix=NEW_SUFFIX_1)
+ except Exception as e:
+ self.instance.log.info('Fail to delete (expected)%s: %s' %
(type(e).__name__,e.args))
+ self.instance.mappingtree.delete(suffix=NEW_SUFFIX_1,bename=NEW_BACKEND_1)
+ self.instance.backend.delete(suffix=NEW_SUFFIX_1)
+
+ # backend name differs -> UnwillingToPerformError
+ backendEntry = self.instance.backend.create(suffix=NEW_SUFFIX_1,
properties={BACKEND_NAME: NEW_BACKEND_1})
+ try:
+ self.instance.backend.delete(suffix=NEW_SUFFIX_1, bename='dummydb')
+ except Exception as e:
+ self.instance.log.info('Fail to delete (expected)%s: %s' %
(type(e).__name__,e.args))
+ self.instance.backend.delete(suffix=NEW_SUFFIX_1)
+
+ def test_toSuffix(self):
+ backendEntry = self.instance.backend.create(suffix=NEW_SUFFIX_1,
properties={BACKEND_NAME: NEW_BACKEND_1})
+ self.instance.mappingtree.create(NEW_SUFFIX_1, bename=NEW_BACKEND_1)
+
+ ents = self.instance.backend.list()
+ for ent in ents:
+ suffix = ent.getValues(BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_SUFFIX])
+ values = self.instance.backend.toSuffix(name=ent.dn)
+ if not suffix[0] in values:
+ self.instance.log.info("Fail we do not retrieve the suffix %s in
%s" % (suffix[0], values))
+ else:
+ self.instance.log.info("%s retrieved" % suffix)
+
+
+if __name__ == "__main__":
+ test = Test_Backend()
+ test.setUp()
-def list_test():
- ret = conn.backend.list()
- ret = [x.dn for x in ret]
- assert len(ret) >=2, "Bad result %r" % ret
-
+ test.test_list()
+ test.test_create()
+ test.test_delete()
+ test.test_toSuffix()
-def list_by_name_test():
- tests = [({'name': 'testreplicadb'}, MOCK_TESTREPLICA_DN)]
- for params, result in tests:
- ret = conn.backend.list(**params)
- ret = [x.dn for x in ret]
- assert result in ret, "Result was %r " % ret
+ test.tearDown()
-
-def list_by_suffix_test():
- tests = [
- ({'suffix': 'o=testreplica'}, MOCK_TESTREPLICA_DN)
- ]
-
- for params, result in tests:
- ret = conn.backend.list(**params)
- ret = [x.dn for x in ret]
- assert result in ret , "Result was %r" % ret
-
-def list_suffixes():
- tests = [ 'o=testreplica' ]
- suffixes = conn.backend.suffixes()
- for params in tests:
- assert params in suffixes, "Missing %r in %r" % (params, suffixes)
-
-def readonly_test():
- bename = 'testReplicadb'
- backend_dn = ','.join(('cn=' + bename, DN_LDBM))
- try:
- conn.backend.readonly(bename=bename, readonly='on')
- e = conn.getEntry(backend_dn)
- ret = e.getValue('nsslapd-readonly')
- assert ret == 'on', "Readonly value mismatch: %r " % ret
- finally:
- conn.backend.readonly(bename=bename, readonly='off')
- e = conn.getEntry(backend_dn)
- ret = e.getValue('nsslapd-readonly')
- assert ret == 'off', "Readonly value mismatch: %r " % ret
diff --git a/src/lib389/tests/dirsrv_test.py b/src/lib389/tests/dirsrv_test.py
index 599addf..4650a46 100644
--- a/src/lib389/tests/dirsrv_test.py
+++ b/src/lib389/tests/dirsrv_test.py
@@ -17,7 +17,7 @@ TEST_REPL_DN = "uid=test,%s" % DEFAULT_SUFFIX
INSTANCE_PORT = 54321
INSTANCE_SERVERID = 'dirsrv'
#INSTANCE_PREFIX = os.environ.get('PREFIX', None)
-INSTANCE_PREFIX = '/home/tbordaz/install'
+INSTANCE_PREFIX = None
INSTANCE_BACKUP = os.environ.get('BACKUPDIR', DEFAULT_BACKUPDIR)
class Test_dirsrv():
diff --git a/src/lib389/tests/mappingTree_test.py b/src/lib389/tests/mappingTree_test.py
new file mode 100644
index 0000000..6780ee5
--- /dev/null
+++ b/src/lib389/tests/mappingTree_test.py
@@ -0,0 +1,275 @@
+'''
+Created on Dec 13, 2013
+
+@author: tbordaz
+'''
+
+
+import os
+import pwd
+import ldap
+from random import randint
+from lib389.tools import DirSrvTools
+from lib389._constants import *
+from lib389.properties import *
+from lib389 import DirSrv,Entry, InvalidArgumentError
+
+TEST_REPL_DN = "uid=test,%s" % DEFAULT_SUFFIX
+INSTANCE_PORT = 54321
+INSTANCE_SERVERID = 'dirsrv'
+#INSTANCE_PREFIX = os.environ.get('PREFIX', None)
+INSTANCE_PREFIX = None
+INSTANCE_BACKUP = os.environ.get('BACKUPDIR', DEFAULT_BACKUPDIR)
+NEW_SUFFIX_1 = 'o=test_create'
+NEW_BACKEND_1 = 'test_createdb'
+NEW_CHILDSUFFIX_1 = 'o=child1,o=test_create'
+NEW_CHILDBACKEND_1 = 'test_createchilddb'
+NEW_SUFFIX_2 = 'o=test_bis_create'
+NEW_BACKEND_2 = 'test_bis_createdb'
+NEW_CHILDSUFFIX_2 = 'o=child2,o=test_bis_create'
+NEW_CHILDBACKEND_2 = 'test_bis_createchilddb'
+
+class Test_mappingTree():
+ def _add_user(self, success=True):
+ try:
+ self.instance.add_s(Entry((TEST_REPL_DN, {'objectclass': "top
person organizationalPerson inetOrgPerson".split(),
+ 'uid': 'test',
+ 'sn': 'test',
+ 'cn': 'test'})))
+ except Exception as e:
+ if success:
+ raise
+ else:
+ self.instance.log.info('Fail to add (expected): %s' % e.args)
+ return
+
+ self.instance.log.info('%s added' % TEST_REPL_DN)
+
+ def _mod_user(self, success=True):
+ try:
+ replace = [(ldap.MOD_REPLACE, 'description', str(randint(1, 100)))]
+ self.instance.modify_s(TEST_REPL_DN, replace)
+ except Exception as e:
+ if success:
+ raise
+ else:
+ self.instance.log.info('Fail to modify (expected): %s' % e.args)
+ return
+
+ self.instance.log.info('%s modified' % TEST_REPL_DN)
+
+ def setUp(self):
+ instance = DirSrv(verbose=False)
+ instance.log.debug("Instance allocated")
+ args = {SER_HOST: LOCALHOST,
+ SER_PORT: INSTANCE_PORT,
+ SER_DEPLOYED_DIR: INSTANCE_PREFIX,
+ SER_SERVERID_PROP: INSTANCE_SERVERID
+ }
+ instance.allocate(args)
+ if instance.exists():
+ instance.delete()
+ instance.create()
+ instance.open()
+ self.instance = instance
+
+
+ def tearDown(self):
+ if self.instance.exists():
+ self.instance.delete()
+
+ def test_list(self):
+ '''
+ This test list with only no param, suffix, bename, suffix+bename (also with
invalid values)
+ '''
+ ents = self.instance.mappingtree.list()
+ for ent in ents:
+ self.instance.log.info('test_list: %r' % ent)
+
+ # get with a dedicated suffix
+ ents = self.instance.mappingtree.list(suffix=DEFAULT_SUFFIX)
+ assert len(ents) == 1
+ self.instance.log.info('test_list(suffix): %r' % ent)
+
+ ents = self.instance.mappingtree.list(suffix="dc=dummy")
+ assert len(ents) == 0
+
+ # get with a dedicated backend name
+ ents = self.instance.mappingtree.list(bename=DEFAULT_BENAME)
+ assert len(ents) == 1
+ self.instance.log.info('test_list(bename): %r' % ent)
+
+ ents = self.instance.mappingtree.list(bename="dummy")
+ assert len(ents) == 0
+
+ # check backend is taken first
+ ents = self.instance.mappingtree.list(suffix="dc=dummy",
bename=DEFAULT_BENAME)
+ assert len(ents) == 1
+ self.instance.log.info('test_list(suffix, bename): %r' % ent)
+
+ def test_create(self):
+ '''
+ This test will create 2 backends/mapping trees, then 2 childs backend/mapping
tree
+ '''
+
+ # before creating mapping trees, the backends must exists
+ backendEntry = self.instance.backend.create(NEW_SUFFIX_1, {BACKEND_NAME:
NEW_BACKEND_1})
+ backendEntry = self.instance.backend.create(NEW_SUFFIX_2, {BACKEND_NAME:
NEW_BACKEND_2})
+
+ ents = self.instance.mappingtree.list()
+ nb_mappingtree = len(ents)
+
+ # create a first additional mapping tree
+ self.instance.mappingtree.create(NEW_SUFFIX_1, bename=NEW_BACKEND_1)
+ ents = self.instance.mappingtree.list()
+ assert len(ents) == (nb_mappingtree + 1)
+
+ # create a second additional mapping tree
+ self.instance.mappingtree.create(NEW_SUFFIX_2, bename=NEW_BACKEND_2)
+ ents = self.instance.mappingtree.list()
+ assert len(ents) == (nb_mappingtree + 2)
+
+ # Creating a mapping tree that already exists => it just returns the existing
MT
+ self.instance.mappingtree.create(NEW_SUFFIX_1, bename=NEW_BACKEND_1)
+ ents = self.instance.mappingtree.list()
+ assert len(ents) == (nb_mappingtree + 2)
+
+ # Creating a mapping tree that already exists => it just returns the existing
MT
+ self.instance.mappingtree.create(NEW_SUFFIX_2, bename=NEW_BACKEND_2)
+ ents = self.instance.mappingtree.list()
+ assert len(ents) == (nb_mappingtree + 2)
+
+ # before creating mapping trees, the backends must exists
+ backendEntry = self.instance.backend.create(NEW_CHILDSUFFIX_1, {BACKEND_NAME:
NEW_CHILDBACKEND_1})
+ backendEntry = self.instance.backend.create(NEW_CHILDSUFFIX_2, {BACKEND_NAME:
NEW_CHILDBACKEND_2})
+
+ # create a third additional mapping tree, that is child of NEW_SUFFIX_1
+ self.instance.mappingtree.create(NEW_CHILDSUFFIX_1, bename=NEW_CHILDBACKEND_1,
parent=NEW_SUFFIX_1)
+ ents = self.instance.mappingtree.list()
+ assert len(ents) == (nb_mappingtree + 3)
+
+ ents = self.instance.mappingtree.list(suffix=NEW_CHILDSUFFIX_1)
+ assert len(ents) == 1
+ ent = ents[0]
+ assert ent.hasAttr(MT_PROPNAME_TO_ATTRNAME[MT_PARENT_SUFFIX]) and
(ent.getValue(MT_PROPNAME_TO_ATTRNAME[MT_PARENT_SUFFIX]) == NEW_SUFFIX_1)
+
+ # create a fourth additional mapping tree, that is child of NEW_SUFFIX_2
+ self.instance.mappingtree.create(NEW_CHILDSUFFIX_2, bename=NEW_CHILDBACKEND_2,
parent=NEW_SUFFIX_2)
+ ents = self.instance.mappingtree.list()
+ assert len(ents) == (nb_mappingtree + 4)
+
+ ents = self.instance.mappingtree.list(suffix=NEW_CHILDSUFFIX_2)
+ assert len(ents) == 1
+ ent = ents[0]
+ assert ent.hasAttr(MT_PROPNAME_TO_ATTRNAME[MT_PARENT_SUFFIX]) and
(ent.getValue(MT_PROPNAME_TO_ATTRNAME[MT_PARENT_SUFFIX]) == NEW_SUFFIX_2)
+
+
+ def test_delete(self):
+ '''
+ Delete the mapping tree and check the remaining number.
+ Delete the sub-suffix first.
+ '''
+ ents = self.instance.mappingtree.list()
+ nb_mappingtree = len(ents)
+ deleted = 0
+
+ self.instance.log.debug("delete MT for suffix " + NEW_CHILDSUFFIX_1)
+ self.instance.mappingtree.delete(suffix=NEW_CHILDSUFFIX_1)
+ deleted += 1
+ ents = self.instance.mappingtree.list()
+ assert len(ents) == (nb_mappingtree - deleted)
+
+ self.instance.log.debug("delete MT with backend " +
NEW_CHILDBACKEND_2)
+ self.instance.mappingtree.delete(bename=NEW_CHILDBACKEND_2)
+ deleted += 1
+ ents = self.instance.mappingtree.list()
+ assert len(ents) == (nb_mappingtree - deleted)
+
+ self.instance.log.debug("delete MT for suffix %s and with backend %s" %
(NEW_SUFFIX_1, NEW_BACKEND_1))
+ self.instance.mappingtree.delete(suffix=NEW_SUFFIX_1,bename=NEW_BACKEND_1)
+ deleted += 1
+ ents = self.instance.mappingtree.list()
+ assert len(ents) == (nb_mappingtree - deleted)
+
+
+ ents = self.instance.mappingtree.list(suffix=NEW_SUFFIX_2)
+ assert len(ents) == 1
+ self.instance.log.debug("delete MT with DN %s (dummy suffix/backend)" %
(ents[0].dn))
+ self.instance.mappingtree.delete(suffix="o=dummy",
bename="foo", name=ents[0].dn)
+
+ def test_getProperties(self):
+ ents = self.instance.mappingtree.list()
+ nb_mappingtree = len(ents)
+
+ # create a first additional mapping tree
+ self.instance.mappingtree.create(NEW_SUFFIX_1, bename=NEW_BACKEND_1)
+ ents = self.instance.mappingtree.list()
+ assert len(ents) == (nb_mappingtree + 1)
+
+ # check we can get properties from suffix
+ prop_ref = self.instance.mappingtree.getProperties(suffix=NEW_SUFFIX_1)
+ self.instance.log.info("properties [suffix] %s: %r" % (NEW_SUFFIX_1,
prop_ref))
+
+ # check we can get properties from backend name
+ properties = self.instance.mappingtree.getProperties(bename=NEW_BACKEND_1)
+ for key in properties.keys():
+ assert prop_ref[key] == properties[key]
+ self.instance.log.info("properties [backend] %s: %r" % (NEW_SUFFIX_1,
properties))
+
+ # check we can get properties from suffix AND backend name
+ properties = self.instance.mappingtree.getProperties(suffix=NEW_SUFFIX_1,
bename=NEW_BACKEND_1)
+ for key in properties.keys():
+ assert prop_ref[key] == properties[key]
+ self.instance.log.info("properties [suffix+backend]%s: %r" %
(NEW_SUFFIX_1, properties))
+
+ # check we can get properties from MT entry
+ ents = self.instance.mappingtree.list(suffix=NEW_SUFFIX_1)
+ assert len(ents) == 1
+ ent = ents[0]
+ properties = self.instance.mappingtree.getProperties(name=ent.dn)
+ for key in properties.keys():
+ assert prop_ref[key] == properties[key]
+ self.instance.log.info("properties [MT entry DN] %s: %r" %
(NEW_SUFFIX_1, properties))
+
+ # check we can get only one properties
+ properties =
self.instance.mappingtree.getProperties(name=ent.dn,properties=[MT_STATE])
+ assert len(properties) == 1
+ assert properties[MT_STATE] == prop_ref[MT_STATE]
+
+ try:
+ properties =
self.instance.mappingtree.getProperties(name=ent.dn,properties=['dummy'])
+ assert 0 # it should return an exception
+ except ValueError:
+ pass
+ except KeyError:
+ pass
+
+ def toSuffix(self):
+ ents = self.instance.mappingtree.list()
+ suffix = self.instance.mappingtree.toSuffix(entry=ents[0])
+ assert len(suffix) > 0
+ self.instance.log.info("suffix (entry) is %s" % suffix[0])
+
+ suffix = self.instance.mappingtree.toSuffix(name=ents[0].dn)
+ assert len(suffix) > 0
+ self.instance.log.info("suffix (dn) is %s" % suffix[0])
+
+ try:
+ suffix = self.instance.mappingtree.toSuffix()
+ except InvalidArgumentError:
+ pass
+
+if __name__ == "__main__":
+ #import sys;sys.argv = ['', 'Test.testName']
+ test = Test_mappingTree()
+ test.setUp()
+
+ test.test_list()
+ test.test_create()
+ test.test_delete()
+ test.test_getProperties()
+ test.toSuffix()
+
+ test.tearDown()
+
+
diff --git a/src/lib389/tests/suffix_test.py b/src/lib389/tests/suffix_test.py
new file mode 100644
index 0000000..7ca6189
--- /dev/null
+++ b/src/lib389/tests/suffix_test.py
@@ -0,0 +1,104 @@
+'''
+Created on Dec 17, 2013
+
+@author: tbordaz
+'''
+import os
+import pwd
+import ldap
+from random import randint
+from lib389.tools import DirSrvTools
+from lib389._constants import *
+from lib389.properties import *
+from lib389 import DirSrv,Entry, InvalidArgumentError
+
+
+TEST_REPL_DN = "uid=test,%s" % DEFAULT_SUFFIX
+INSTANCE_PORT = 54321
+INSTANCE_SERVERID = 'dirsrv'
+#INSTANCE_PREFIX = os.environ.get('PREFIX', None)
+INSTANCE_PREFIX = None
+INSTANCE_BACKUP = os.environ.get('BACKUPDIR', DEFAULT_BACKUPDIR)
+NEW_SUFFIX_1 = 'o=test_create'
+NEW_BACKEND_1 = 'test_createdb'
+NEW_CHILDSUFFIX_1 = 'o=child1,o=test_create'
+NEW_CHILDBACKEND_1 = 'test_createchilddb'
+NEW_SUFFIX_2 = 'o=test_bis_create'
+NEW_BACKEND_2 = 'test_bis_createdb'
+NEW_CHILDSUFFIX_2 = 'o=child2,o=test_bis_create'
+NEW_CHILDBACKEND_2 = 'test_bis_createchilddb'
+
+
+class Test_suffix():
+
+
+ def setUp(self):
+ instance = DirSrv(verbose=False)
+ instance.log.debug("Instance allocated")
+ args = {SER_HOST: LOCALHOST,
+ SER_PORT: INSTANCE_PORT,
+ SER_DEPLOYED_DIR: INSTANCE_PREFIX,
+ SER_SERVERID_PROP: INSTANCE_SERVERID
+ }
+ instance.allocate(args)
+ if instance.exists():
+ instance.delete()
+ instance.create()
+ instance.open()
+ self.instance = instance
+
+
+ def tearDown(self):
+ if self.instance.exists():
+ self.instance.delete()
+
+
+ def test_list(self):
+ # before creating mapping trees, the backends must exists
+ backendEntry = self.instance.backend.create(NEW_SUFFIX_1, {BACKEND_NAME:
NEW_BACKEND_1})
+
+ ents = self.instance.mappingtree.list()
+ nb_mappingtree = len(ents)
+
+ # create a first additional mapping tree
+ self.instance.mappingtree.create(NEW_SUFFIX_1, bename=NEW_BACKEND_1)
+ ents = self.instance.mappingtree.list()
+ assert len(ents) == (nb_mappingtree + 1)
+
+ suffixes = self.instance.suffix.list()
+ assert len(suffixes) == 2
+ for suffix in suffixes:
+ self.instance.log.info("suffix is %s" % suffix)
+
+ def test_toBackend(self):
+ backends = self.instance.suffix.toBackend(suffix=NEW_SUFFIX_1)
+ assert len(backends) == 1
+ backend = backends[0]
+ self.instance.log.info("backend entry is %r" % backend)
+ assert backend.getValue(BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_SUFFIX]) ==
NEW_SUFFIX_1
+ assert backend.getValue(BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_NAME]) ==
NEW_BACKEND_1
+
+
+ def test_getParent(self):
+ parent = self.instance.suffix.getParent(suffix=NEW_SUFFIX_1)
+ assert parent == None
+
+ backendEntry = self.instance.backend.create(NEW_CHILDSUFFIX_1, {BACKEND_NAME:
NEW_CHILDBACKEND_1})
+ # create a third additional mapping tree, that is child of NEW_SUFFIX_1
+ self.instance.mappingtree.create(NEW_CHILDSUFFIX_1, bename=NEW_CHILDBACKEND_1,
parent=NEW_SUFFIX_1)
+ parent = self.instance.suffix.getParent(suffix=NEW_CHILDSUFFIX_1)
+ self.instance.log.info("Retrieved parent of %s: %s" %
(NEW_CHILDSUFFIX_1, parent))
+ assert parent == NEW_SUFFIX_1
+
+
+if __name__ == "__main__":
+ test = Test_suffix()
+ test.setUp()
+
+ test.test_list()
+ test.test_toBackend()
+ test.test_getParent()
+
+
+ test.tearDown()
+
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.