From efae6c1aa6406d3c60f2d48076de2e43db1f4e8a Mon Sep 17 00:00:00 2001 From: "Thierry bordaz (tbordaz)" Date: Fri, 10 Jan 2014 13:38:40 +0100 Subject: [PATCH] Ticket 47600(follow up) : Replica/Agreement/Changelog not conform to the design Bug Description: Ticket 47600 implements Replica/replica Agreement and Changelog This second commit is to finalize this ticket Fix Description: Add unit tests for Replica and RA Fix some issues found with unit tests https://fedorahosted.org/389/ticket/47600 Reviewed by: Rich Megginson (thanks Rich !!) Platforms tested: F17 Flag Day: no Doc impact: no --- lib389/_constants.py | 4 +- lib389/agreement.py | 171 +++++---- lib389/replica.py | 21 +- tests/agreement_test.py | 321 +++++++++++++++++ tests/replica_test.py | 935 ++++++++++++++++++++++++++++++++---------------- 5 files changed, 1048 insertions(+), 404 deletions(-) create mode 100644 tests/agreement_test.py diff --git a/lib389/_constants.py b/lib389/_constants.py index 3557ec3..128e30f 100644 --- a/lib389/_constants.py +++ b/lib389/_constants.py @@ -22,6 +22,7 @@ REPLICATION_BIND_DN = 'replication_bind_dn' REPLICATION_BIND_PW = 'replication_bind_pw' REPLICATION_BIND_METHOD = 'replication_bind_method' REPLICATION_TRANSPORT = 'replication_transport' +REPLICATION_TIMEOUT = 'replication_timeout' TRANS_STARTTLS = "starttls" TRANS_SECURE = "secure" @@ -34,7 +35,8 @@ defaultProperties = { REPLICATION_BIND_DN: "cn=replrepl,cn=config", REPLICATION_BIND_PW: "password", REPLICATION_BIND_METHOD: "simple", - REPLICATION_TRANSPORT: REPL_TRANS_VALUE[TRANS_NORMAL] + REPLICATION_TRANSPORT: REPL_TRANS_VALUE[TRANS_NORMAL], + REPLICATION_TIMEOUT: str(120) } diff --git a/lib389/agreement.py b/lib389/agreement.py index 37f720c..561e499 100644 --- a/lib389/agreement.py +++ b/lib389/agreement.py @@ -98,27 +98,30 @@ class Agreement(object): c = re.compile(re.compile('^([0-9][0-9])([0-9][0-9])-([0-9][0-9])([0-9][0-9]) ([0-6]{1,7})$')) if not c.match(interval): raise ValueError("Bad schedule format %r" % interval) + schedule = c.split(interval, c.groups) # check the hours - hour = int(c.group(1)) + hour = int(schedule[1]) if ((hour < 0) or (hour > 23)): raise ValueError("Bad schedule format %r: illegal hour %d" % (interval, hour)) - hour = int(c.group(3)) + hour = int(schedule[3]) if ((hour < 0) or (hour > 23)): raise ValueError("Bad schedule format %r: illegal hour %d" % (interval, hour)) + if int(schedule[1]) > int(schedule[3]): + raise ValueError("Bad schedule (start HOUR larger than end HOUR) %r: illegal hour %d" % (interval, int(schedule[1]))) # check the minutes - minute = int(c.group(2)) - if ((hour < 0) or (hour > 59)): + minute = int(schedule[2]) + if ((minute < 0) or (minute > 59)): raise ValueError("Bad schedule format %r: illegal minute %d" % (interval, minute)) - minute = int(c.group(4)) - if ((hour < 0) or (hour > 59)): + minute = int(schedule[4]) + if ((minute < 0) or (minute > 59)): raise ValueError("Bad schedule format %r: illegal minute %d" % (interval, minute)) - def schedule(self, agmtdn, interval=ALWAYS): + def schedule(self, agmtdn=None, interval=ALWAYS): """Schedule the replication agreement @param agmtdn - DN of the replica agreement @param interval - in the form @@ -131,6 +134,8 @@ class Agreement(object): @raise ValueError - if interval is not valid ldap.NO_SUCH_OBJECT - if agmtdn does not exist """ + if not agmtdn: + raise InvalidArgumentError("agreement DN is missing") # check the validity of the interval if interval != Agreement.ALWAYS and interval != Agreement.NEVER: @@ -148,26 +153,31 @@ class Agreement(object): ldap.MOD_REPLACE, 'nsds5replicaupdateschedule', [interval])] self.conn.modify_s(agmtdn, mod) - def getProperties(self, agmtdn, properties=None): + def getProperties(self, agmnt_dn=None, properties=None): ''' returns a dictionary of the requested properties. If properties is missing, it returns all the properties. @param agmtdn - is the replica agreement DN @param properties - is the list of properties name + Supported properties are + RA_NAME + RA_SUFFIX + RA_BINDDN + RA_BINDPW + RA_METHOD + RA_DESCRIPTION + RA_SCHEDULE + RA_TRANSPORT_PROT + RA_FRAC_EXCLUDE + RA_FRAC_EXCLUDE_TOTAL_UPDATE + RA_FRAC_STRIP + RA_CONSUMER_PORT + RA_CONSUMER_HOST + RA_CONSUMER_TOTAL_INIT + RA_TIMEOUT + RA_CHANGES @return - returns a dictionary of the properties - - @raise - None - - supported properties are - property name Replication Agreement attribute name - 'schedule' -> 'nsds5replicaupdateschedule' - 'fractional-exclude-attrs-inc' -> 'nsDS5ReplicatedAttributeList' - 'fractional-exclude-attrs-total' -> 'nsDS5ReplicatedAttributeListTotal' - 'fractional-strip-attrs' -> 'nsds5ReplicaStripAttrs' - 'transport-prot' -> 'nsds5replicatransportinfo' [ 'LDAP' ] - 'consumer-port' -> 'nsds5replicaport' [ 389 ] - 'consumer-total-init' -> 'nsds5BeginReplicaRefresh' @raise ValueError - if invalid property name ldap.NO_SUCH_OBJECT - if agmtdn does not exist @@ -176,7 +186,7 @@ class Agreement(object): ''' - if not agmtdn: + if not agmnt_dn: raise InvalidArgumentError("agmtdn is a mandatory argument") # @@ -184,16 +194,17 @@ class Agreement(object): # if properties is None, all RA attributes are retrieved # attrs = [] - for prop_name in properties: - prop_attr = RA_PROPNAME_TO_ATTRNAME[prop_name] - if not prop_attr: - raise ValueError("Improper property name: %s ", prop_name) - attrs.append(prop_attr) + if properties: + for prop_name in properties: + prop_attr = RA_PROPNAME_TO_ATTRNAME[prop_name] + if not prop_attr: + raise ValueError("Improper property name: %s ", prop_name) + attrs.append(prop_attr) filt = "(objectclass=*)" result = {} try: - entry = self.getEntry(agmtdn, ldap.SCOPE_BASE, filt, attrs) + entry = self.conn.getEntry(agmnt_dn, ldap.SCOPE_BASE, filt, attrs) # Build the result from the returned attributes for attr in entry.getAttrs(): @@ -201,7 +212,7 @@ class Agreement(object): props = [ k for k, v in RA_PROPNAME_TO_ATTRNAME.iteritems() if v.lower() == attr.lower() ] # If this attribute is present in the RA properties, adds it to result - if props.len() > 0: + if len(props) > 0: result[props[0]] = entry.getValues(attr) except ldap.NO_SUCH_OBJECT: @@ -283,7 +294,7 @@ class Agreement(object): # for each provided property build the mod mod = [] - for prop in properties.keys(): + for prop in properties: # retrieve/check the property name # and if the value needs to be added/deleted/replaced @@ -305,7 +316,7 @@ class Agreement(object): if prop_name == RA_SCHEDULE: self._check_interval(properties[prop]) - mod.append([mod_type, attr, properties[prop]]) + mod.append((mod_type, attr, properties[prop])) # Now time to run the effective modify self.conn.modify_s(agmnt_dn, mod) @@ -344,7 +355,7 @@ class Agreement(object): if agmtdn: # easy case, just return the RA filt = "objectclass=*" - return self.getEntry(agmtdn, ldap.SCOPE_BASE, filt) + return self.conn.search_s(agmtdn, ldap.SCOPE_BASE, filt) else: # Retrieve the replica replica_entries = self.conn.replica.list(suffix) @@ -354,11 +365,16 @@ class Agreement(object): # Now returns the replica agreement for that suffix that replicates to # consumer host/port - filt = "(&(%s=%s)(%s=%d))" % (RA_CONSUMER_HOST, consumer_host, RA_CONSUMER_PORT, consumer_port) - return self.getEntry(replica_entry.dn, ldap.SCOPE_ONELEVEL, filt) + if consumer_host and consumer_port: + filt = "(&(objectclass=%s)(%s=%s)(%s=%d))" % (RA_OBJECTCLASS_VALUE, + RA_PROPNAME_TO_ATTRNAME[RA_CONSUMER_HOST], consumer_host, + RA_PROPNAME_TO_ATTRNAME[RA_CONSUMER_PORT], consumer_port) + else: + filt = "(objectclass=%s)" % RA_OBJECTCLASS_VALUE + return self.conn.search_s(replica_entry.dn, ldap.SCOPE_ONELEVEL, filt) - def create(self, suffix=None, host=None, port=None, cn_format=r'meTo_$host:$port', description_format=r'me to $host:$port', timeout=120, auto_init=False, bindmethod='simple', starttls=False, args=None): + def create(self, suffix=None, host=None, port=None, properties=None): """Create (and return) a replication agreement from self to consumer. - self is the supplier, @@ -366,19 +382,25 @@ class Agreement(object): * a DirSrv object if chaining * an object with attributes: host, port, sslport, __str__ @param suffix - eg. 'dc=babel,dc=it' - @param binddn - - @param bindpw - - @param cn_format - string.Template to format the agreement name - @param timeout - replica timeout in seconds - @param auto_init - start replication immediately - @param bindmethod- 'simple' - @param starttls - True or False - @param args - further args dict. Allowed keys: - 'schedule', - 'fractional-exclude-attrs-inc', - 'fractional-exclude-attrs-total', - 'fractional-strip-attrs' - 'winsync' + @param properties - further properties dict. + Support properties + RA_NAME + RA_SUFFIX + RA_BINDDN + RA_BINDPW + RA_METHOD + RA_DESCRIPTION + RA_SCHEDULE + RA_TRANSPORT_PROT + RA_FRAC_EXCLUDE + RA_FRAC_EXCLUDE_TOTAL_UPDATE + RA_FRAC_STRIP + RA_CONSUMER_PORT + RA_CONSUMER_HOST + RA_CONSUMER_TOTAL_INIT + RA_TIMEOUT + RA_CHANGES + @return dn_agreement - DN of the created agreement @@ -388,11 +410,6 @@ class Agreement(object): in read-only state. To create new agreements you need to *restart* the directory server - NOTE: this method doesn't cache connection entries - - TODO: test winsync - TODO: test chain - """ import string @@ -401,20 +418,22 @@ class Agreement(object): self.log.warning("create: suffix is missing") raise InvalidArgumentError('suffix is mandatory') - if args: - binddn = args[RA_BINDDN] or defaultProperties[REPLICATION_BIND_DN] - bindpw = args[RA_BINDPW] or defaultProperties[REPLICATION_BIND_PW] - bindmethod = args[RA_METHOD] or defaultProperties[REPLICATION_BIND_METHOD] - format = args[RA_NAME] or r'meTo_$host:$port' - description = args[RA_DESCRIPTION] or format - transport = args[RA_TRANSPORT_PROT] or defaultProperties[REPLICATION_TRANSPORT] + if properties: + binddn = properties.get(RA_BINDDN) or defaultProperties[REPLICATION_BIND_DN] + bindpw = properties.get(RA_BINDPW) or defaultProperties[REPLICATION_BIND_PW] + bindmethod = properties.get(RA_METHOD) or defaultProperties[REPLICATION_BIND_METHOD] + format = properties.get(RA_NAME) or r'meTo_$host:$port' + description = properties.get(RA_DESCRIPTION) or format + transport = properties.get(RA_TRANSPORT_PROT) or defaultProperties[REPLICATION_TRANSPORT] + timeout = properties.get(RA_TIMEOUT) or defaultProperties[REPLICATION_TIMEOUT] else: binddn = defaultProperties[REPLICATION_BIND_DN] bindpw = defaultProperties[REPLICATION_BIND_PW] bindmethod = defaultProperties[REPLICATION_BIND_METHOD] format = r'meTo_$host:$port' - description = args[RA_DESCRIPTION] or format - transport = args[RA_TRANSPORT_PROT] or defaultProperties[REPLICATION_TRANSPORT] + description = format + transport = defaultProperties[REPLICATION_TRANSPORT] + timeout = defaultProperties[REPLICATION_TIMEOUT] # Compute the normalized suffix to be set in RA entry nsuffix = normalizeDN(suffix) @@ -427,7 +446,7 @@ class Agreement(object): replica = replica_entries[0] # define agreement entry - cn = string.Template(cn_format).substitute({'host': host, 'port': port}) + cn = string.Template(format).substitute({'host': host, 'port': port}) dn_agreement = ','.join(["cn=%s" % cn, replica.dn]) # This is probably unnecessary because @@ -447,7 +466,7 @@ class Agreement(object): RA_PROPNAME_TO_ATTRNAME[RA_NAME]: cn, RA_PROPNAME_TO_ATTRNAME[RA_SUFFIX]: nsuffix, RA_PROPNAME_TO_ATTRNAME[RA_CONSUMER_HOST]: host, - RA_PROPNAME_TO_ATTRNAME[RA_CONSUMER_PORT]: port, + RA_PROPNAME_TO_ATTRNAME[RA_CONSUMER_PORT]: str(port), RA_PROPNAME_TO_ATTRNAME[RA_TRANSPORT_PROT]: transport, RA_PROPNAME_TO_ATTRNAME[RA_TIMEOUT]: str(timeout), RA_PROPNAME_TO_ATTRNAME[RA_BINDDN]: binddn, @@ -457,15 +476,15 @@ class Agreement(object): }) # we make a copy here because we cannot change - # the passed in args dict - argscopy = {} - if args: + # the passed in properties dict + propertiescopy = {} + if properties: import copy - argscopy = copy.deepcopy(args) + propertiescopy = copy.deepcopy(properties) # further arguments - if 'winsync' in argscopy: # state it clearly! - self.conn.setupWinSyncAgmt(argscopy, entry) + if 'winsync' in propertiescopy: # state it clearly! + self.conn.setupWinSyncAgmt(propertiescopy, entry) try: self.log.debug("Adding replica agreement: [%r]" % entry) @@ -477,8 +496,8 @@ class Agreement(object): entry = self.conn.waitForEntry(dn_agreement) if entry: # More verbose but shows what's going on - if 'chain' in argscopy: - raise NotImplemented + if 'chain' in propertiescopy: + raise NotImplementedError chain_args = { 'suffix': suffix, 'binddn': binddn, @@ -493,14 +512,14 @@ class Agreement(object): chain_args.update({ 'isIntermediate': 0, 'urls': self.conn.toLDAPURL(), - 'args': argscopy['chainargs'] + 'args': propertiescopy['chainargs'] }) consumer.setupConsumerChainOnUpdate(**chain_args) elif replica.nsds5replicatype == HUB_TYPE: chain_args.update({ 'isIntermediate': 1, 'urls': self.conn.toLDAPURL(), - 'args': argscopy['chainargs'] + 'args': propertiescopy['chainargs'] }) consumer.setupConsumerChainOnUpdate(**chain_args) @@ -602,15 +621,15 @@ class Agreement(object): # use schedule hack self.schedule(interval) - def changes(self, agmtdn): + def changes(self, agmnt_dn): """Return a list of changes sent by this agreement.""" retval = 0 try: ent = self.conn.getEntry( - agmtdn, ldap.SCOPE_BASE, "(objectclass=*)", [ RA_PROPNAME_TO_ATTRNAME[RA_CHANGES] ]) + agmnt_dn, ldap.SCOPE_BASE, "(objectclass=*)", [ RA_PROPNAME_TO_ATTRNAME[RA_CHANGES] ]) except: raise NoSuchEntryError( - "Error reading status from agreement", agmtdn) + "Error reading status from agreement", agmnt_dn) if ent.nsds5replicaChangesSentSinceStartup: val = ent.nsds5replicaChangesSentSinceStartup diff --git a/lib389/replica.py b/lib389/replica.py index 40cb24c..318ba6a 100644 --- a/lib389/replica.py +++ b/lib389/replica.py @@ -72,6 +72,8 @@ class Replica(object): @param repl_manager_dn - DN of the bind entry. If not provided use the default one @param repl_manager_pw - Password of the entry. If not provide use the default one + @return None + @raise - KeyError if can not find valid values of Bind DN and Pwd ''' @@ -203,7 +205,7 @@ class Replica(object): # If it provides the suffix or the replicaDN, replica.list will # return the appropriate entry - ents = self.replica.list(suffix=suffix, replica_dn=replica_dn) + ents = self.conn.replica.list(suffix=suffix, replica_dn=replica_dn) if len(ents) != 1: if replica_dn: raise ValueError("invalid replica DN: %s" % replica_dn) @@ -230,7 +232,7 @@ class Replica(object): def getProperties(self, suffix=None, replica_dn=None, replica_entry=None, properties=None): - raise NotImplemented + raise NotImplementedError def create(self, suffix=None, role=None, rid=None, args=None): """ @@ -296,10 +298,11 @@ class Replica(object): # If the properties in args are valid # add them to the 'properties' dictionary # The attribute name may be prefixed '+'/'-' => keep MOD type as provided in args - for prop in args: - if not inProperties(prop, REPLICA_PROPNAME_TO_ATTRNAME): - raise ValueError("unknown property: %s" % prop) - properties[prop] = args[prop] + if args: + for prop in args: + if not inProperties(prop, REPLICA_PROPNAME_TO_ATTRNAME): + raise ValueError("unknown property: %s" % prop) + properties[prop] = args[prop] # Now set default values of unset properties Replica._set_or_default(REPLICA_LEGACY_CONS, properties, 'off') @@ -366,7 +369,7 @@ class Replica(object): except ldap.NO_SUCH_OBJECT: raise - raise NotImplemented() + raise NotImplementedError() def enableReplication(self, suffix=None, role=None, replicaId=CONSUMER_REPLICAID, binddn=None): if not suffix: @@ -438,12 +441,12 @@ class Replica(object): return ret - def disableReplication(self, suffix): + def disableReplication(self, suffix=None): if not suffix: self.log.fatal("enableReplication: suffix not specified") raise ValueError("suffix missing") - raise NotImplemented + raise NotImplementedError def check_init(self, agmtdn): """returns tuple - first element is done/not done, 2nd is no error/has error diff --git a/tests/agreement_test.py b/tests/agreement_test.py new file mode 100644 index 0000000..ef878b5 --- /dev/null +++ b/tests/agreement_test.py @@ -0,0 +1,321 @@ +''' +Created on Jan 9, 2014 + +@author: tbordaz +''' + +import ldap +import time +import sys +import os + +from lib389 import InvalidArgumentError, NoSuchEntryError +from lib389.agreement import Agreement +from lib389._constants import * +from lib389.properties import * +from lib389 import DirSrv, Entry +from _constants import REPLICAROLE_CONSUMER + +# Used for One master / One consumer topology +HOST_MASTER = LOCALHOST +PORT_MASTER = 40389 +SERVERID_MASTER = 'master' +REPLICAID_MASTER = 1 + +HOST_CONSUMER = LOCALHOST +PORT_CONSUMER = 50389 +SERVERID_CONSUMER = 'consumer' + +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_BACKUP = os.environ.get('BACKUPDIR', DEFAULT_BACKUPDIR) + +SUFFIX = DEFAULT_SUFFIX +ENTRY_DN = "cn=test_entry, %s" % SUFFIX + + + +class Test_Agreement(): + + + def setUp(self): + # + # Master + # + # Create the master instance + master = DirSrv(verbose=False) + master.log.debug("Master allocated") + args = {SER_HOST: HOST_MASTER, + SER_PORT: PORT_MASTER, + SER_DEPLOYED_DIR: INSTANCE_PREFIX, + SER_SERVERID_PROP: SERVERID_MASTER + } + master.allocate(args) + if master.exists(): + master.delete() + master.create() + master.open() + + # enable replication + master.replica.enableReplication(suffix=SUFFIX, role=REPLICAROLE_MASTER, replicaId=REPLICAID_MASTER) + self.master = master + + + # + # Consumer + # + # Create the consumer instance + consumer = DirSrv(verbose=False) + consumer.log.debug("Consumer allocated") + args = {SER_HOST: HOST_CONSUMER, + SER_PORT: PORT_CONSUMER, + SER_DEPLOYED_DIR: INSTANCE_PREFIX, + SER_SERVERID_PROP: SERVERID_CONSUMER + } + consumer.allocate(args) + if consumer.exists(): + consumer.delete() + consumer.create() + consumer.open() + + # enable replication + consumer.replica.enableReplication(suffix=SUFFIX, role=REPLICAROLE_CONSUMER) + self.consumer = consumer + + + def tearDown(self): + self.master.log.info("\n\n#########################\n### TEARDOWN\n#########################\n") + for instance in (self.master, self.consumer): + if instance.exists(): + instance.delete() + + def test_create(self): + ''' + Test to create a replica agreement and initialize the consumer. + Test on a unknown suffix + ''' + self.master.log.info("\n\n#########################\n### CREATE\n#########################\n") + properties = {RA_NAME: r'meTo_$host:$port', + RA_BINDDN: defaultProperties[REPLICATION_BIND_DN], + RA_BINDPW: defaultProperties[REPLICATION_BIND_PW], + RA_METHOD: defaultProperties[REPLICATION_BIND_METHOD], + RA_TRANSPORT_PROT: defaultProperties[REPLICATION_TRANSPORT]} + repl_agreement = self.master.agreement.create(suffix=SUFFIX, host=self.consumer.host, port=self.consumer.port, properties=properties) + self.master.log.debug("%s created" % repl_agreement) + self.master.agreement.init(SUFFIX, HOST_CONSUMER, PORT_CONSUMER) + self.master.waitForReplInit(repl_agreement) + + # Add a test entry + self.master.add_s(Entry((ENTRY_DN, {'objectclass': "top person".split(), + 'sn': 'test_entry', + 'cn': 'test_entry'}))) + + # check replication is working + loop = 0 + while loop <= 10: + try: + ent = self.consumer.getEntry(ENTRY_DN, ldap.SCOPE_BASE, "(objectclass=*)") + break + except ldap.NO_SUCH_OBJECT: + time.sleep(1) + loop += 1 + assert loop <= 10 + + # check that with an invalid suffix it raises NoSuchEntryError + try: + properties = {RA_NAME: r'meAgainTo_$host:$port'} + self.master.agreement.create(suffix="ou=dummy", host=self.consumer.host, port=self.consumer.port, properties=properties) + except Exception as e: + self.master.log.info("Exception (expected): %s" % type(e).__name__) + assert isinstance(e, NoSuchEntryError) + + def test_list(self): + ''' + List the replica agreement on a suffix => 1 + Add a RA + List the replica agreements on that suffix again => 2 + List a specific RA + + PREREQUISITE: it exists a replica for SUFFIX and a replica agreement + ''' + self.master.log.info("\n\n#########################\n### LIST\n#########################\n") + ents = self.master.agreement.list(suffix=SUFFIX) + assert len(ents) == 1 + assert ents[0].getValue(RA_PROPNAME_TO_ATTRNAME[RA_CONSUMER_HOST]) == self.consumer.host + assert ents[0].getValue(RA_PROPNAME_TO_ATTRNAME[RA_CONSUMER_PORT]) == str(self.consumer.port) + + # Create a second RA to check .list returns 2 RA + properties = {RA_NAME: r'meTo_$host:$port', + RA_BINDDN: defaultProperties[REPLICATION_BIND_DN], + RA_BINDPW: defaultProperties[REPLICATION_BIND_PW], + RA_METHOD: defaultProperties[REPLICATION_BIND_METHOD], + RA_TRANSPORT_PROT: defaultProperties[REPLICATION_TRANSPORT]} + self.master.agreement.create(suffix=SUFFIX, host=self.consumer.host, port=12345, properties=properties) + ents = self.master.agreement.list(suffix=SUFFIX) + assert len(ents) == 2 + + # Check we can .list a specific RA + ents = self.master.agreement.list(suffix=SUFFIX, consumer_host=self.consumer.host, consumer_port=self.consumer.port) + assert len(ents) == 1 + assert ents[0].getValue(RA_PROPNAME_TO_ATTRNAME[RA_CONSUMER_HOST]) == self.consumer.host + assert ents[0].getValue(RA_PROPNAME_TO_ATTRNAME[RA_CONSUMER_PORT]) == str(self.consumer.port) + + + def test_status(self): + self.master.log.info("\n\n#########################\n### STATUS\n#########################") + ents = self.master.agreement.list(suffix=SUFFIX) + for ent in ents: + self.master.log.info("Status of %s: %s" % (ent.dn, self.master.agreement.status(ent.dn))) + + def test_schedule(self): + + self.master.log.info("\n\n#########################\n### SCHEDULE\n#########################") + ents = self.master.agreement.list(suffix=SUFFIX, consumer_host=self.consumer.host, consumer_port=self.consumer.port) + assert len(ents) == 1 + + self.master.agreement.schedule(ents[0].dn, Agreement.ALWAYS) + ents = self.master.agreement.list(suffix=SUFFIX, consumer_host=self.consumer.host, consumer_port=self.consumer.port) + assert len(ents) == 1 + assert ents[0].getValue(RA_PROPNAME_TO_ATTRNAME[RA_SCHEDULE]) == Agreement.ALWAYS + + self.master.agreement.schedule(ents[0].dn, Agreement.NEVER) + ents = self.master.agreement.list(suffix=SUFFIX, consumer_host=self.consumer.host, consumer_port=self.consumer.port) + assert len(ents) == 1 + assert ents[0].getValue(RA_PROPNAME_TO_ATTRNAME[RA_SCHEDULE]) == Agreement.NEVER + + CUSTOM_SCHEDULE="0000-1234 6420" + self.master.agreement.schedule(ents[0].dn, CUSTOM_SCHEDULE) + ents = self.master.agreement.list(suffix=SUFFIX, consumer_host=self.consumer.host, consumer_port=self.consumer.port) + assert len(ents) == 1 + assert ents[0].getValue(RA_PROPNAME_TO_ATTRNAME[RA_SCHEDULE]) == CUSTOM_SCHEDULE + + # check that with an invalid HOUR schedule raise ValueError + try: + CUSTOM_SCHEDULE="2500-1234 6420" + self.master.agreement.schedule(ents[0].dn, CUSTOM_SCHEDULE) + except Exception as e: + self.master.log.info("Exception (expected) HOUR: %s" % type(e).__name__) + assert isinstance(e, ValueError) + + try: + CUSTOM_SCHEDULE="0000-2534 6420" + self.master.agreement.schedule(ents[0].dn, CUSTOM_SCHEDULE) + except Exception as e: + self.master.log.info("Exception (expected) HOUR: %s" % type(e).__name__) + assert isinstance(e, ValueError) + + # check that with an starting HOUR after ending HOUR raise ValueError + try: + CUSTOM_SCHEDULE="1300-1234 6420" + self.master.agreement.schedule(ents[0].dn, CUSTOM_SCHEDULE) + except Exception as e: + self.master.log.info("Exception (expected) HOUR: %s" % type(e).__name__) + assert isinstance(e, ValueError) + + # check that with an invalid MIN schedule raise ValueError + try: + CUSTOM_SCHEDULE="0062-1234 6420" + self.master.agreement.schedule(ents[0].dn, CUSTOM_SCHEDULE) + except Exception as e: + self.master.log.info("Exception (expected) MIN: %s" % type(e).__name__) + assert isinstance(e, ValueError) + + try: + CUSTOM_SCHEDULE="0000-1362 6420" + self.master.agreement.schedule(ents[0].dn, CUSTOM_SCHEDULE) + except Exception as e: + self.master.log.info("Exception (expected) MIN: %s" % type(e).__name__) + assert isinstance(e, ValueError) + + # check that with an invalid DAYS schedule raise ValueError + try: + CUSTOM_SCHEDULE="0000-1234 6-420" + self.master.agreement.schedule(ents[0].dn, CUSTOM_SCHEDULE) + except Exception as e: + self.master.log.info("Exception (expected) MIN: %s" % type(e).__name__) + assert isinstance(e, ValueError) + + try: + CUSTOM_SCHEDULE="0000-1362 64209" + self.master.agreement.schedule(ents[0].dn, CUSTOM_SCHEDULE) + except Exception as e: + self.master.log.info("Exception (expected) MIN: %s" % type(e).__name__) + assert isinstance(e, ValueError) + + try: + CUSTOM_SCHEDULE="0000-1362 01234560" + self.master.agreement.schedule(ents[0].dn, CUSTOM_SCHEDULE) + except Exception as e: + self.master.log.info("Exception (expected) MIN: %s" % type(e).__name__) + assert isinstance(e, ValueError) + + def test_getProperties(self): + self.master.log.info("\n\n#########################\n### GETPROPERTIES\n#########################") + ents = self.master.agreement.list(suffix=SUFFIX, consumer_host=self.consumer.host, consumer_port=self.consumer.port) + assert len(ents) == 1 + properties = self.master.agreement.getProperties(agmnt_dn=ents[0].dn) + for prop in properties: + self.master.log.info("RA %s : %s -> %s" % (prop, RA_PROPNAME_TO_ATTRNAME[prop], properties[prop])) + + properties = self.master.agreement.getProperties(agmnt_dn=ents[0].dn, properties=[RA_BINDDN]) + assert len(properties) == 1 + for prop in properties: + self.master.log.info("RA %s : %s -> %s" % (prop, RA_PROPNAME_TO_ATTRNAME[prop], properties[prop])) + + def test_setProperties(self): + self.master.log.info("\n\n#########################\n### SETPROPERTIES\n#########################") + ents = self.master.agreement.list(suffix=SUFFIX, consumer_host=self.consumer.host, consumer_port=self.consumer.port) + assert len(ents) == 1 + test_schedule = "1234-2345 12345" + test_desc = "hello world !" + self.master.agreement.setProperties(agmnt_dn=ents[0].dn, properties={RA_SCHEDULE: test_schedule, RA_DESCRIPTION: test_desc}) + properties = self.master.agreement.getProperties(agmnt_dn=ents[0].dn, properties=[RA_SCHEDULE, RA_DESCRIPTION]) + assert len(properties) == 2 + assert properties[RA_SCHEDULE][0] == test_schedule + assert properties[RA_DESCRIPTION][0] == test_desc + + def test_changes(self): + self.master.log.info("\n\n#########################\n### CHANGES\n#########################") + ents = self.master.agreement.list(suffix=SUFFIX, consumer_host=self.consumer.host, consumer_port=self.consumer.port) + assert len(ents) == 1 + value = self.master.agreement.changes(agmnt_dn=ents[0].dn) + self.master.log.info("\ntest_changes: %d changes\n" % value) + assert value > 0 + + # do an update + TEST_STRING = 'hello you' + mod = [(ldap.MOD_REPLACE, 'description', [TEST_STRING])] + self.master.modify_s(ENTRY_DN, mod) + + # the update has been replicated + loop = 0 + while loop <= 10: + ent = self.consumer.getEntry(ENTRY_DN, ldap.SCOPE_BASE, "(objectclass=*)") + if ent and ent.hasValue('description'): + if ent.getValue('description') == TEST_STRING: + break + time.sleep(1) + loop += 1 + assert loop <= 10 + + # check change number + newvalue = self.master.agreement.changes(agmnt_dn=ents[0].dn) + self.master.log.info("\ntest_changes: %d changes\n" % newvalue) + assert (value + 1) == newvalue + +if __name__ == "__main__": + test = Test_Agreement() + test.setUp() + + test.test_create() + test.test_list() + test.test_status() + test.test_schedule() + test.test_getProperties() + test.test_setProperties() + test.test_changes() + + test.tearDown() \ No newline at end of file diff --git a/tests/replica_test.py b/tests/replica_test.py index c358296..a006a21 100644 --- a/tests/replica_test.py +++ b/tests/replica_test.py @@ -1,326 +1,625 @@ -"""Brooker classes to organize ldap methods. - Stuff is split in classes, like: - * Replica - * Backend - * Suffix +''' +Created on Jan 08, 2014 - You will access this from: - DirSrv.backend.methodName() -""" +@author: tbordaz +''' -from nose import * -from nose.tools import * - -import config -from config import log -from config import * 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 -# 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' - - -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) - - # replication needs changelog - conn.replica.changelog() - # add rmanager entry - try: - conn.add_s(Entry((DN_RMANAGER, { - 'objectclass': "top person inetOrgPerson".split(), - 'sn': ["bind dn pseudo user"], - 'cn': 'replication manager', - 'uid': 'rmanager' - })) - ) - conn.added_entries.append(DN_RMANAGER) - except ldap.ALREADY_EXISTS: - pass +import os - # add a master replica entry - # to test ruv and agreements - replica_dn = ','.join( - ['cn=replica', 'cn="o=testReplica"', DN_MAPPING_TREE]) - replica_e = Entry(replica_dn) - replica_e.update({ - 'objectclass': ["top", "nsds5replica", "extensibleobject"], - 'cn': "replica", - 'nsds5replicaroot': 'o=testReplica', - 'nsds5replicaid': MOCK_REPLICA_ID, - 'nsds5replicatype': '3', - 'nsds5flags': '1', - 'nsds5replicabinddn': DN_RMANAGER - }) - try: - conn.add_s(replica_e) - except ldap.ALREADY_EXISTS: - pass - conn.added_entries.append(replica_dn) - - agreement_dn = ','.join(('cn=testAgreement', replica_dn)) - agreement_e = Entry(agreement_dn) - agreement_e.update({ - 'objectclass': ["top", "nsds5replicationagreement"], - 'cn': 'testAgreement', - 'nsds5replicahost': 'localhost', - 'nsds5replicaport': '22389', - 'nsds5replicatimeout': '120', - 'nsds5replicabinddn': DN_RMANAGER, - 'nsds5replicacredentials': 'password', - 'nsds5replicabindmethod': 'simple', - 'nsds5replicaroot': 'o=testReplica', - 'nsds5replicaupdateschedule': '0000-2359 0123456', - 'description': 'testAgreement' - }) - try: - conn.add_s(agreement_e) - except ldap.ALREADY_EXISTS: - pass - conn.added_entries.append(agreement_dn) - conn.agreement_dn = agreement_dn - - -def teardown(): - global conn - drop_added_entries(conn) - conn.delete_s(','.join(['cn="o=testreplica"', DN_MAPPING_TREE])) - conn.backend.delete('testReplicadb') +from lib389 import InvalidArgumentError +from lib389._constants import * +from lib389.properties import * +from lib389 import DirSrv, Entry +from _constants import REPLICAROLE_CONSUMER + +# Used for One master / One consumer topology +HOST_MASTER = LOCALHOST +PORT_MASTER = 40389 +SERVERID_MASTER = 'master' +REPLICAID_MASTER = 1 + +HOST_CONSUMER = LOCALHOST +PORT_CONSUMER = 50389 +SERVERID_CONSUMER = 'consumer' + +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_BACKUP = os.environ.get('BACKUPDIR', DEFAULT_BACKUPDIR) +NEW_SUFFIX_1 = 'ou=test_master' +NEW_BACKEND_1 = 'test_masterdb' +NEW_RM_1 = "cn=replrepl,%s" % NEW_SUFFIX_1 + +NEW_SUFFIX_2 = 'ou=test_consumer' +NEW_BACKEND_2 = 'test_consumerdb' + +NEW_SUFFIX_3 = 'ou=test_enablereplication_1' +NEW_BACKEND_3 = 'test_enablereplicationdb_1' + +NEW_SUFFIX_4 = 'ou=test_enablereplication_2' +NEW_BACKEND_4 = 'test_enablereplicationdb_2' + +NEW_SUFFIX_5 = 'ou=test_enablereplication_3' +NEW_BACKEND_5 = 'test_enablereplicationdb_3' + + +class Test_replica(): + + + def setUp(self): + # Create the master instance + master = DirSrv(verbose=False) + master.log.debug("Master allocated") + args = {SER_HOST: HOST_MASTER, + SER_PORT: PORT_MASTER, + SER_DEPLOYED_DIR: INSTANCE_PREFIX, + SER_SERVERID_PROP: SERVERID_MASTER + } + master.allocate(args) + if master.exists(): + master.delete() + master.create() + master.open() + self.master = master + + # Create the consumer instance + consumer = DirSrv(verbose=False) + consumer.log.debug("Consumer allocated") + args = {SER_HOST: HOST_CONSUMER, + SER_PORT: PORT_CONSUMER, + SER_DEPLOYED_DIR: INSTANCE_PREFIX, + SER_SERVERID_PROP: SERVERID_CONSUMER + } + consumer.allocate(args) + if consumer.exists(): + consumer.delete() + consumer.create() + consumer.open() + self.consumer = consumer + + + def tearDown(self): + self.master.log.info("\n\n#########################\n### TEARDOWN\n#########################") + for instance in (self.master, self.consumer): + if instance.exists(): + instance.delete() + + def test_create(self): + self.master.log.info("\n\n#########################\n### CREATE\n#########################") + ''' + This test creates + - suffix/backend (NEW_SUFFIX_[12], NEW_BACKEND_[12]) : Master + - suffix/backend (NEW_SUFFIX_[12], NEW_BACKEND_[12]) : Consumer + - replica NEW_SUFFIX_1 as MASTER : Master + - replica NEW_SUFFIX_2 as CONSUMER : Master + ''' + # + # MASTER (suffix/backend) + # + backendEntry = self.master.backend.create(suffix=NEW_SUFFIX_1, properties={BACKEND_NAME: NEW_BACKEND_1}) + backendEntry = self.master.backend.create(suffix=NEW_SUFFIX_2, properties={BACKEND_NAME: NEW_BACKEND_2}) + + ents = self.master.mappingtree.list() + master_nb_mappingtree = len(ents) + + # create a first additional mapping tree + self.master.mappingtree.create(NEW_SUFFIX_1, bename=NEW_BACKEND_1) + ents = self.master.mappingtree.list() + assert len(ents) == (master_nb_mappingtree + 1) + self.master.add_s(Entry((NEW_SUFFIX_1, {'objectclass': "top organizationalunit".split(), + 'ou': NEW_SUFFIX_1.split('=',1)[1]}))) + + + # create a second additional mapping tree + self.master.mappingtree.create(NEW_SUFFIX_2, bename=NEW_BACKEND_2) + ents = self.master.mappingtree.list() + assert len(ents) == (master_nb_mappingtree + 2) + self.master.add_s(Entry((NEW_SUFFIX_2, {'objectclass': "top organizationalunit".split(), + 'ou': NEW_SUFFIX_2.split('=',1)[1]}))) + self.master.log.info('test_create): Master it exists now %d suffix(es)' % len(ents)) + + # + # CONSUMER (suffix/backend) + # + backendEntry = self.consumer.backend.create(suffix=NEW_SUFFIX_1, properties={BACKEND_NAME: NEW_BACKEND_1}) + backendEntry = self.consumer.backend.create(suffix=NEW_SUFFIX_2, properties={BACKEND_NAME: NEW_BACKEND_2}) + + ents = self.consumer.mappingtree.list() + consumer_nb_mappingtree = len(ents) + + # create a first additional mapping tree + self.consumer.mappingtree.create(NEW_SUFFIX_1, bename=NEW_BACKEND_1) + ents = self.consumer.mappingtree.list() + assert len(ents) == (consumer_nb_mappingtree + 1) + self.consumer.add_s(Entry((NEW_SUFFIX_1, {'objectclass': "top organizationalunit".split(), + 'ou': NEW_SUFFIX_1.split('=',1)[1]}))) + + # create a second additional mapping tree + self.consumer.mappingtree.create(NEW_SUFFIX_2, bename=NEW_BACKEND_2) + ents = self.consumer.mappingtree.list() + assert len(ents) == (consumer_nb_mappingtree + 2) + self.consumer.add_s(Entry((NEW_SUFFIX_2, {'objectclass': "top organizationalunit".split(), + 'ou': NEW_SUFFIX_2.split('=',1)[1]}))) + self.consumer.log.info('test_create): Consumer it exists now %d suffix(es)' % len(ents)) + + # + # Now create REPLICAS on master + # + # check it exists this entry to stores the changelogs + self.master.changelog.create() + + # create a master + self.master.replica.create(suffix=NEW_SUFFIX_1, role=REPLICAROLE_MASTER, rid=1) + ents = self.master.replica.list() + assert len(ents) == 1 + self.master.log.info('test_create): Master replica %s' % ents[0].dn) + + # create a consumer + self.master.replica.create(suffix=NEW_SUFFIX_2, role=REPLICAROLE_CONSUMER) + ents = self.master.replica.list() + assert len(ents) == 2 + ents = self.master.replica.list(suffix=NEW_SUFFIX_2) + self.master.log.info('test_create): Consumer replica %s' % ents[0].dn) + + # + # Now create REPLICAS on consumer + # + # create a master + self.consumer.replica.create(suffix=NEW_SUFFIX_1, role=REPLICAROLE_CONSUMER) + ents = self.consumer.replica.list() + assert len(ents) == 1 + self.consumer.log.info('test_create): Consumer replica %s' % ents[0].dn) + + # create a consumer + self.consumer.replica.create(suffix=NEW_SUFFIX_2, role=REPLICAROLE_CONSUMER) + ents = self.consumer.replica.list() + assert len(ents) == 2 + ents = self.consumer.replica.list(suffix=NEW_SUFFIX_2) + self.consumer.log.info('test_create): Consumer replica %s' % ents[0].dn) + + def test_list(self): + self.master.log.info("\n\n#########################\n### LIST\n#########################") + ''' + This test checks: + - existing replicas can be retrieved + - access to unknown replica does not fail + + PRE-CONDITION: + It exists on MASTER two replicas NEW_SUFFIX_1 and NEW_SUFFIX_2 + created by test_create() + ''' + ents = self.master.replica.list() + assert len(ents) == 2 + + # Check we can retrieve a replica with its suffix + ents = self.master.replica.list(suffix=NEW_SUFFIX_1) + assert len(ents) == 1 + replica_dn_1 = ents[0].dn + + # Check we can retrieve a replica with its suffix + ents = self.master.replica.list(suffix=NEW_SUFFIX_2) + assert len(ents) == 1 + replica_dn_2 = ents[0].dn + + # Check we can retrieve a replica with its DN + ents = self.master.replica.list(replica_dn=replica_dn_1) + assert len(ents) == 1 + assert replica_dn_1 == ents[0].dn + + + # Check we can retrieve a replica if we provide DN and suffix + ents = self.master.replica.list(suffix=NEW_SUFFIX_2, replica_dn=replica_dn_2) + assert len(ents) == 1 + assert replica_dn_2 == ents[0].dn + + # Check DN is used before suffix name + ents = self.master.replica.list(suffix=NEW_SUFFIX_2, replica_dn=replica_dn_1) + assert len(ents) == 1 + assert replica_dn_1 == ents[0].dn + + # Check that invalid value does not break + ents = self.master.replica.list(suffix="X") + for ent in ents: + self.master.log.critical("Unexpected replica: %s" % ent.dn) + assert len(ents) == 0 + + def test_create_repl_manager(self): + self.master.log.info("\n\n#########################\n### CREATE_REPL_MANAGER\n#########################") + ''' + The tests are + - create the default Replication manager/Password + - create a specific Replication manager/ default Password + - Check we can bind successfully + - create a specific Replication manager / specific Password + - Check we can bind successfully + ''' + # First create the default replication manager + self.consumer.replica.create_repl_manager() + ents = self.consumer.search_s(defaultProperties[REPLICATION_BIND_DN], ldap.SCOPE_BASE, "objectclass=*") + assert len(ents) == 1 + assert ents[0].dn == defaultProperties[REPLICATION_BIND_DN] + + # Second create a custom replication manager under NEW_SUFFIX_2 + rm_dn = "cn=replrepl,%s" % NEW_SUFFIX_2 + self.consumer.replica.create_repl_manager(repl_manager_dn=rm_dn) + ents = self.consumer.search_s(rm_dn, ldap.SCOPE_BASE, "objectclass=*") + assert len(ents) == 1 + assert ents[0].dn == rm_dn + + # Check we can bind + self.consumer.simple_bind_s(rm_dn, defaultProperties[REPLICATION_BIND_PW]) + + # Check we fail to bind + try: + self.consumer.simple_bind_s(rm_dn, "dummy") + except Exception as e: + self.consumer.log.info("Exception: %s" % type(e).__name__) + assert isinstance(e, ldap.INVALID_CREDENTIALS) + + #now rebind + self.consumer.simple_bind_s(self.consumer.binddn, self.consumer.bindpw) + + + # Create a custom replication manager under NEW_SUFFIX_1 with a specified password + rm_dn = NEW_RM_1 + self.consumer.replica.create_repl_manager(repl_manager_dn=rm_dn, repl_manager_pw="Secret123") + ents = self.consumer.search_s(rm_dn, ldap.SCOPE_BASE, "objectclass=*") + assert len(ents) == 1 + assert ents[0].dn == rm_dn + + # Check we can bind + self.consumer.simple_bind_s(rm_dn, "Secret123") + + # Check we fail to bind + try: + self.consumer.simple_bind_s(rm_dn, "dummy") + except Exception as e: + self.consumer.log.info("Exception: %s" % type(e).__name__) + assert isinstance(e, ldap.INVALID_CREDENTIALS) + #now rebind + self.consumer.simple_bind_s(self.consumer.binddn, self.consumer.bindpw) + + def test_enableReplication(self): + self.master.log.info("\n\n#########################\n### ENABLEREPLICATION\n#########################") + ''' + It checks + - Ability to enable replication on a supplier + - Ability to enable replication on a consumer + - Failure to enable replication with wrong replicaID on supplier + - Failure to enable replication with wrong replicaID on consumer + ''' + # + # MASTER (suffix/backend) + # + backendEntry = self.master.backend.create(suffix=NEW_SUFFIX_3, properties={BACKEND_NAME: NEW_BACKEND_3}) + + ents = self.master.mappingtree.list() + master_nb_mappingtree = len(ents) + + # create a first additional mapping tree + self.master.mappingtree.create(NEW_SUFFIX_3, bename=NEW_BACKEND_3) + ents = self.master.mappingtree.list() + assert len(ents) == (master_nb_mappingtree + 1) + self.master.add_s(Entry((NEW_SUFFIX_3, {'objectclass': "top organizationalunit".split(), + 'ou': NEW_SUFFIX_3.split('=',1)[1]}))) + + try: + # a supplier should have replicaId in [1..CONSUMER_REPLICAID[ + self.master.replica.enableReplication(suffix=NEW_SUFFIX_3, role=REPLICAROLE_MASTER, replicaId=CONSUMER_REPLICAID) + except Exception as e: + self.master.log.info("Exception (expected): %s" % type(e).__name__) + assert isinstance(e, ValueError) + self.master.replica.enableReplication(suffix=NEW_SUFFIX_3, role=REPLICAROLE_MASTER, replicaId=1) + + # + # MASTER (suffix/backend) + # + backendEntry = self.master.backend.create(suffix=NEW_SUFFIX_4, properties={BACKEND_NAME: NEW_BACKEND_4}) + + ents = self.master.mappingtree.list() + master_nb_mappingtree = len(ents) + + # create a first additional mapping tree + self.master.mappingtree.create(NEW_SUFFIX_4, bename=NEW_BACKEND_4) + ents = self.master.mappingtree.list() + assert len(ents) == (master_nb_mappingtree + 1) + self.master.add_s(Entry((NEW_SUFFIX_4, {'objectclass': "top organizationalunit".split(), + 'ou': NEW_SUFFIX_4.split('=',1)[1]}))) + try: + # A consumer should have CONSUMER_REPLICAID not '1' + self.master.replica.enableReplication(suffix=NEW_SUFFIX_4, role=REPLICAROLE_CONSUMER, replicaId=1) + except Exception as e: + self.master.log.info("Exception (expected): %s" % type(e).__name__) + assert isinstance(e, ValueError) + self.master.replica.enableReplication(suffix=NEW_SUFFIX_4, role=REPLICAROLE_CONSUMER) + + def test_disableReplication(self): + self.master.log.info("\n\n#########################\n### DISABLEREPLICATION\n#########################") + ''' + Currently not implemented + ''' + try: + self.master.replica.disableReplication(suffix=NEW_SUFFIX_4) + except Exception as e: + self.master.log.info("Exception (expected): %s" % type(e).__name__) + assert isinstance(e, NotImplementedError) + + def test_setProperties(self): + self.master.log.info("\n\n#########################\n### SETPROPERTIES\n#########################") + ''' + Set some properties + Verified that valid properties are set + Verified that invalid properties raise an Exception + + PRE-REQUISITE: it exists a replica for NEW_SUFFIX_1 + ''' + + # set valid values to SUFFIX_1 + properties = {REPLICA_LEGACY_CONS: 'off', + REPLICA_BINDDN: NEW_RM_1, + REPLICA_PURGE_INTERVAL: str(3600), + REPLICA_PURGE_DELAY: str(5*24*3600), + REPLICA_REFERRAL: "ldap://%s:1234/" % LOCALHOST} + self.master.replica.setProperties(suffix=NEW_SUFFIX_1, properties=properties) + + # Check the values have been written + replicas = self.master.replica.list(suffix=NEW_SUFFIX_1) + assert len(replicas) == 1 + for prop in properties: + attr = REPLICA_PROPNAME_TO_ATTRNAME[prop] + val = replicas[0].getValue(attr) + self.master.log.info("Replica[%s] -> %s: %s" % (prop, attr, val)) + assert val == properties[prop] + + # Check invalid properties raise exception + try: + properties = {"dummy": 'dummy'} + self.master.replica.setProperties(suffix=NEW_SUFFIX_1, properties=properties) + except Exception as e: + self.master.log.info("Exception (expected): %s" % type(e).__name__) + assert isinstance(e, ValueError) + + # check call without suffix/dn/entry raise InvalidArgumentError + try: + properties = {REPLICA_LEGACY_CONS: 'off'} + self.master.replica.setProperties(properties=properties) + except Exception as e: + self.master.log.info("Exception (expected): %s" % type(e).__name__) + assert isinstance(e, InvalidArgumentError) + + # check that if we do not provide a valid entry it raises ValueError + try: + properties = {REPLICA_LEGACY_CONS: 'off'} + self.master.replica.setProperties(replica_entry="dummy", properties=properties) + except Exception as e: + self.master.log.info("Exception (expected): %s" % type(e).__name__) + assert isinstance(e, ValueError) + + # check that with an invalid suffix or replica_dn it raise ValueError + try: + properties = {REPLICA_LEGACY_CONS: 'off'} + self.master.replica.setProperties(suffix="dummy", properties=properties) + except Exception as e: + self.master.log.info("Exception (expected): %s" % type(e).__name__) + assert isinstance(e, ValueError) + + def test_getProperties(self): + self.master.log.info("\n\n#########################\n### GETPROPERTIES\n#########################") + ''' + Currently not implemented + ''' + try: + properties = {REPLICA_LEGACY_CONS: 'off'} + self.master.replica.setProperties(suffix=NEW_SUFFIX_1, properties=properties) + except Exception as e: + self.master.log.info("Exception (expected): %s" % type(e).__name__) + assert isinstance(e, NotImplementedError) + - conn.delete_s(','.join(['cn="o=testReplicaCreation"', DN_MAPPING_TREE])) - conn.backend.delete('testReplicaCreationdb') - - -def changelog(): - changelog_e = conn.replica.changelog(dbname='foo') - assert changelog_e.data['nsslapd-changelogdir'].endswith('foo') - - -def changelog_default_test(): - e = conn.replica.changelog() - conn.added_entries.append(e.dn) - assert e.dn, "Bad changelog entry: %r " % e - assert e.getValue('nsslapd-changelogdir').endswith("changelogdb"), "Mismatching entry %r " % e.data.get('nsslapd-changelogdir') - conn.delete_s("cn=changelog5,cn=config") - - -def changelog_customdb_test(): - e = conn.replica.changelog(dbname="mockChangelogDb") - conn.added_entries.append(e.dn) - assert e.dn, "Bad changelog entry: %r " % e - assert e.getValue('nsslapd-changelogdir').endswith("mockChangelogDb"), "Mismatching entry %r " % e.data.get('nsslapd-changelogdir') - conn.delete_s("cn=changelog5,cn=config") - - -def changelog_full_path_test(): - e = conn.replica.changelog(dbname="/tmp/mockChangelogDb") - conn.added_entries.append(e.dn) - - assert e.dn, "Bad changelog entry: %r " % e - expect(e, 'nsslapd-changelogdir', "/tmp/mockChangelogDb") - conn.delete_s("cn=changelog5,cn=config") - - -def check_init_test(): - raise NotImplementedError() - - -def disable_logging_test(): - raise NotImplementedError() - - -def enable_logging_test(): - raise NotImplementedError() - - -def status_test(): - status = conn.agreement.status(conn.agreement_dn) - log.info(status) - assert status - - -def list_test(): - # was get_entries_test(): - replicas = conn.replica.list() - assert any(['testreplica' in x.dn.lower() for x in replicas]) - - -def ruv_test(): - ruv = conn.replica.ruv(suffix='o=testReplica') - assert ruv, "Missing RUV" - assert len(ruv.rid), "Missing RID" - assert int(MOCK_REPLICA_ID) in ruv.rid.keys() - - -@raises(ldap.NO_SUCH_OBJECT) -def ruv_missing_test(): - ruv = conn.replica.ruv(suffix='o=MISSING') - assert ruv, "Missing RUV" - assert len(ruv.rid), "Missing RID" - assert int(MOCK_REPLICA_ID) in ruv.rid.keys() - - -def start_test(): - raise NotImplementedError() - - -def stop_test(): - raise NotImplementedError() - - -def restart_test(): - raise NotImplementedError() - - -def start_async_test(): - raise NotImplementedError() - - -def wait_for_init_test(): - raise NotImplementedError() - - -def setup_agreement_default_test(): - user = { - 'binddn': DN_RMANAGER, - 'bindpw': "password" - } - params = {'consumer': MockDirSrv(), 'suffix': "o=testReplica"} - params.update(user) - - agreement_dn = conn.replica.agreement_add(**params) - conn.added_entries.append(agreement_dn) - -@raises(ldap.ALREADY_EXISTS) -def setup_agreement_duplicate_test(): - user = { - 'binddn': DN_RMANAGER, - 'bindpw': "password" - } - params = { - 'consumer': MockDirSrv(), - 'suffix': "o=testReplica", - 'cn_format': 'testAgreement', - 'description_format': 'testAgreement' - } - params.update(user) - conn.replica.agreement_add(**params) - - -def setup_agreement_test(): - user = { - 'binddn': DN_RMANAGER, - 'bindpw': "password" - } - params = {'consumer': MockDirSrv(), 'suffix': "o=testReplica"} - params.update(user) - - conn.replica.agreement_add(**params) - # timeout=120, auto_init=False, bindmethod='simple', starttls=False, args=None): - raise NotImplementedError() - -def setup_agreement_fractional_test(): - # TODO: fractiona replicates only a subset of attributes - # - user = { - 'binddn': DN_RMANAGER, - 'bindpw': "password" - } - params = {'consumer': MockDirSrv(), 'suffix': "o=testReplica"} - params.update(user) - - #conn.replica.agreement_add(**params) - #cn_format=r'meTo_%s:%s', description_format=r'me to %s:%s', timeout=120, auto_init=False, bindmethod='simple', starttls=False, args=None): - raise NotImplementedError() - - -def find_agreements_test(): - agreements = conn.replica.agreements(dn=False) - assert any(['testagreement' in x.dn.lower( - ) for x in agreements]), "Missing agreement" - - -def find_agreements_dn_test(): - agreements_dn = conn.replica.agreements() - assert any(['testagreement' in x.lower( - ) for x in agreements_dn]), "Missing agreement" - - -def setup_replica_test(): - args = { - 'suffix': "o=testReplicaCreation", - 'binddn': DN_RMANAGER, - 'bindpw': "password", - 'rtype': lib389.MASTER_TYPE, - 'rid': MOCK_REPLICA_ID - } - # create a replica entry - replica_e = conn.replica.add(**args) - assert 'dn' in replica_e, "Missing dn in replica" - conn.added_entries.append(replica_e['dn']) - - -def setup_replica_hub_test(): - args = { - 'suffix': "o=testReplicaCreation", - 'binddn': DN_RMANAGER, - 'bindpw': "password", - 'rtype': lib389.HUB_TYPE, - 'rid': MOCK_REPLICA_ID - } - # create a replica entry - replica_e = conn.replica.add(**args) - assert 'dn' in replica_e, "Missing dn in replica" - conn.added_entries.append(replica_e['dn']) - - -def setup_replica_referrals_test(): - #tombstone_purgedelay=None, purgedelay=None, referrals=None, legacy=False - raise NotImplementedError() - - -def setup_all_replica_test(): - raise NotImplementedError() - -def replica_keep_in_sync_test(): - raise NotImplementedError() + def test_delete(self): + self.master.log.info("\n\n#########################\n### DELETE\n#########################") + ''' + Currently not implemented + ''' + try: + self.master.replica.delete(suffix=NEW_SUFFIX_4) + except Exception as e: + self.master.log.info("Exception (expected): %s" % type(e).__name__) + assert isinstance(e, NotImplementedError) + + +if __name__ == "__main__": + test = Test_replica() + test.setUp() + + #time.sleep(30) + test.test_create() + test.test_list() + test.test_create_repl_manager() + test.test_enableReplication() + test.test_disableReplication() + test.test_setProperties() + test.test_getProperties() + test.test_delete() + + test.tearDown() + + + +# +# +# conn = DirSrv() +# def changelog(): +# changelog_e = conn.replica.changelog(dbname='foo') +# assert changelog_e.data['nsslapd-changelogdir'].endswith('foo') +# +# +# def changelog_default_test(): +# e = conn.replica.changelog() +# conn.added_entries.append(e.dn) +# assert e.dn, "Bad changelog entry: %r " % e +# assert e.getValue('nsslapd-changelogdir').endswith("changelogdb"), "Mismatching entry %r " % e.data.get('nsslapd-changelogdir') +# conn.delete_s("cn=changelog5,cn=config") +# +# +# def changelog_customdb_test(): +# e = conn.replica.changelog(dbname="mockChangelogDb") +# conn.added_entries.append(e.dn) +# assert e.dn, "Bad changelog entry: %r " % e +# assert e.getValue('nsslapd-changelogdir').endswith("mockChangelogDb"), "Mismatching entry %r " % e.data.get('nsslapd-changelogdir') +# conn.delete_s("cn=changelog5,cn=config") +# +# +# def changelog_full_path_test(): +# e = conn.replica.changelog(dbname="/tmp/mockChangelogDb") +# conn.added_entries.append(e.dn) +# +# assert e.dn, "Bad changelog entry: %r " % e +# expect(e, 'nsslapd-changelogdir', "/tmp/mockChangelogDb") +# conn.delete_s("cn=changelog5,cn=config") +# +# def ruv_test(): +# ruv = conn.replica.ruv(suffix='o=testReplica') +# assert ruv, "Missing RUV" +# assert len(ruv.rid), "Missing RID" +# assert int(MOCK_REPLICA_ID) in ruv.rid.keys() +# +# +# @raises(ldap.NO_SUCH_OBJECT) +# def ruv_missing_test(): +# ruv = conn.replica.ruv(suffix='o=MISSING') +# assert ruv, "Missing RUV" +# assert len(ruv.rid), "Missing RID" +# assert int(MOCK_REPLICA_ID) in ruv.rid.keys() +# +# +# def start_test(): +# raise NotImplementedError() +# +# +# def stop_test(): +# raise NotImplementedError() +# +# +# def restart_test(): +# raise NotImplementedError() +# +# +# def start_async_test(): +# raise NotImplementedError() +# +# +# def wait_for_init_test(): +# raise NotImplementedError() +# +# +# def setup_agreement_default_test(): +# user = { +# 'binddn': DN_RMANAGER, +# 'bindpw': "password" +# } +# params = {'consumer': MockDirSrv(), 'suffix': "o=testReplica"} +# params.update(user) +# +# agreement_dn = conn.replica.agreement_add(**params) +# conn.added_entries.append(agreement_dn) +# +# @raises(ldap.ALREADY_EXISTS) +# def setup_agreement_duplicate_test(): +# user = { +# 'binddn': DN_RMANAGER, +# 'bindpw': "password" +# } +# params = { +# 'consumer': MockDirSrv(), +# 'suffix': "o=testReplica", +# 'cn_format': 'testAgreement', +# 'description_format': 'testAgreement' +# } +# params.update(user) +# conn.replica.agreement_add(**params) +# +# +# def setup_agreement_test(): +# user = { +# 'binddn': DN_RMANAGER, +# 'bindpw': "password" +# } +# params = {'consumer': MockDirSrv(), 'suffix': "o=testReplica"} +# params.update(user) +# +# conn.replica.agreement_add(**params) +# # timeout=120, auto_init=False, bindmethod='simple', starttls=False, args=None): +# raise NotImplementedError() +# +# def setup_agreement_fractional_test(): +# # TODO: fractiona replicates only a subset of attributes +# # +# user = { +# 'binddn': DN_RMANAGER, +# 'bindpw': "password" +# } +# params = {'consumer': MockDirSrv(), 'suffix': "o=testReplica"} +# params.update(user) +# +# #conn.replica.agreement_add(**params) +# #cn_format=r'meTo_%s:%s', description_format=r'me to %s:%s', timeout=120, auto_init=False, bindmethod='simple', starttls=False, args=None): +# raise NotImplementedError() +# +# +# def find_agreements_test(): +# agreements = conn.replica.agreements(dn=False) +# assert any(['testagreement' in x.dn.lower( +# ) for x in agreements]), "Missing agreement" +# +# +# def find_agreements_dn_test(): +# agreements_dn = conn.replica.agreements() +# assert any(['testagreement' in x.lower( +# ) for x in agreements_dn]), "Missing agreement" +# +# +# def setup_replica_test(): +# args = { +# 'suffix': "o=testReplicaCreation", +# 'binddn': DN_RMANAGER, +# 'bindpw': "password", +# 'rtype': lib389.MASTER_TYPE, +# 'rid': MOCK_REPLICA_ID +# } +# # create a replica entry +# replica_e = conn.replica.add(**args) +# assert 'dn' in replica_e, "Missing dn in replica" +# conn.added_entries.append(replica_e['dn']) +# +# +# def setup_replica_hub_test(): +# args = { +# 'suffix': "o=testReplicaCreation", +# 'binddn': DN_RMANAGER, +# 'bindpw': "password", +# 'rtype': lib389.HUB_TYPE, +# 'rid': MOCK_REPLICA_ID +# } +# # create a replica entry +# replica_e = conn.replica.add(**args) +# assert 'dn' in replica_e, "Missing dn in replica" +# conn.added_entries.append(replica_e['dn']) +# +# +# def setup_replica_referrals_test(): +# #tombstone_purgedelay=None, purgedelay=None, referrals=None, legacy=False +# raise NotImplementedError() +# +# +# def setup_all_replica_test(): +# raise NotImplementedError() +# +# def replica_keep_in_sync_test(): +# raise NotImplementedError() -- 1.7.11.7