From 31eac001834e80d367f2e4853921ad65629b8354 Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: Fri, 21 Jul 2017 17:32:46 +0200 Subject: [PATCH] Issue 79 - Fix replica.py and add tests Description: Move tests for ReplicaLegacy to replicaLegacy_test.py module. Add a new test suite for Replicas(DSLdapObject). Fix or change: - Agreement.create() - if property is None, define the dict object; - Changelog.list() - make it consistent and return empry list if nothing is found; - Replica: - Replace hard coded values with variables if possibe; - Fix get_role() functions so it will return right result; - Fix deleteAgreements. It now doesn't have suffix param; - Fix typos on variables; - Make promote() and demote() methods more explicit; - Rename Replicas.delete() to Replicas.disable because it coveres changelog and agreements deletion too; - Make docstring format consistent; https://pagure.io/lib389/issue/79 Reviewed by: ? --- lib389/agreement.py | 3 + lib389/changelog.py | 12 +- lib389/replica.py | 404 +++++++++++---------- lib389/tests/replicaLegacy_test.py | 459 ++++++++++++++++++++++++ lib389/tests/replica_test.py | 704 ++++++++++++++++--------------------- 5 files changed, 980 insertions(+), 602 deletions(-) create mode 100644 lib389/tests/replicaLegacy_test.py diff --git a/lib389/agreement.py b/lib389/agreement.py index 666f5b2..0970c3f 100644 --- a/lib389/agreement.py +++ b/lib389/agreement.py @@ -447,6 +447,9 @@ class Agreement(object): self.log.warning("create: suffix is missing") raise InvalidArgumentError('suffix is mandatory') + if properties is None: + properties = {} + # Compute the normalized suffix to be set in RA entry properties[RA_SUFFIX] = normalizeDN(suffix) diff --git a/lib389/changelog.py b/lib389/changelog.py index 66ced8c..5affcee 100644 --- a/lib389/changelog.py +++ b/lib389/changelog.py @@ -26,15 +26,17 @@ class Changelog(object): if name in Changelog.proxied_methods: return DirSrv.__getattr__(self.conn, name) - def list(self, suffix=None, changelogdn=None): - if not changelogdn: - raise InvalidArgumentError("changelog DN is missing") - + def list(self, suffix=None, changelogdn=DN_CHANGELOG): base = changelogdn filtr = "(objectclass=extensibleobject)" # now do the effective search - ents = self.conn.search_s(base, ldap.SCOPE_BASE, filtr) + try: + ents = self.conn.search_s(base, ldap.SCOPE_BASE, filtr) + except ldap.NO_SUCH_OBJECT: + # There are no objects to select from, se we return an empty array + # as we do in DSLdapObjects + ents = [] return ents def create(self, dbname=DEFAULT_CHANGELOG_DB): diff --git a/lib389/replica.py b/lib389/replica.py index 4de88be..5329819 100644 --- a/lib389/replica.py +++ b/lib389/replica.py @@ -18,9 +18,12 @@ from lib389.repltools import ReplTools from lib389 import DirSrv, Entry, NoSuchEntryError, InvalidArgumentError from lib389._mapped_object import DSLdapObjects, DSLdapObject -ROLE_ORDER = {'master': 3, 'hub': 2, 'consumer': 1} -ROLE_TO_NAME = {3: 'master', 2: 'hub', 1: 'consumer'} - +ROLE_ORDER = {REPLICAROLE_MASTER: 3, + REPLICAROLE_HUB: 2, + REPLICAROLE_CONSUMER: 1} +ROLE_TO_NAME = {3: REPLICAROLE_MASTER, + 2: REPLICAROLE_HUB, + 1: REPLICAROLE_CONSUMER} class ReplicaLegacy(object): proxied_methods = 'search_s getEntry'.split() @@ -257,6 +260,34 @@ class ReplicaLegacy(object): properties=None): raise NotImplementedError + def get_role(self, suffix): + """Return the replica role: + + @return: "master", "hub", or "consumer" + """ + + filter_str = ('(&(objectclass=nsDS5Replica)(nsDS5ReplicaRoot=%s))'.format(suffix)) + + try: + replica_entry = self.conn.search_s(DN_CONFIG, ldap.SCOPE_SUBTREE, + filter_str) + if replica_entry: + repltype = replica_entry[0].getValue(REPL_TYPE) + replflags = replica_entry[0].getValue(REPL_FLAGS) + + if repltype == REPLICA_RDWR_TYPE and replflags == REPLICA_FLAGS_WRITE: + replicarole = REPLICAROLE_MASTER + elif repltype == REPLICA_RDONLY_TYPE and replflags == REPLICA_FLAGS_WRITE: + replicarole = REPLICAROLE_HUB + elif repltype == REPLICA_RDONLY_TYPE and replflags == REPLICA_FLAGS_RDONLY: + replicarole = REPLICAROLE_CONSUMER + else: + raise ValueError("Failed to determine a replica role") + + return replicarole + except ldap.LDAPError as e: + raise ValueError('Failed to get replica entry: %s' % str(e)) + def create(self, suffix=None, role=None, rid=None, args=None): """ Create a replica entry on an existing suffix. @@ -340,8 +371,11 @@ class ReplicaLegacy(object): ReplicaLegacy._set_or_default(REPLICA_BINDDN, properties, [defaultProperties[REPLICATION_BIND_DN]]) - if role != REPLICAROLE_CONSUMER: - properties[REPLICA_FLAGS] = "1" + # Set flags explicitly, so it will be more readable + if role == REPLICAROLE_CONSUMER: + properties[REPLICA_FLAGS] = REPLICA_FLAGS_RDONLY + else: + properties[REPLICA_FLAGS] = REPLICA_FLAGS_WRITE # # Check if replica entry is already in the mapping-tree @@ -397,7 +431,7 @@ class ReplicaLegacy(object): except ldap.LDAPError as e: self.log.fatal('Failed to delete replica agreement (%s),' + ' error: %s' % - (admt.dn, str(e))) + (agmt.dn, str(e))) raise except ldap.LDAPError as e: self.log.fatal('Failed to search for replication agreements ' + @@ -661,37 +695,21 @@ class ReplicaLegacy(object): # Must be a hub - set the rid rid = CONSUMER_REPLICAID - # - # Get the replica entry - # + # Get replica entry filter_str = ('(&(objectclass=nsDS5Replica)(nsDS5ReplicaRoot=%s))' % suffix) try: - replica_entry = self.conn.search_s('cn=config', ldap.SCOPE_SUBTREE, + replica_entry = self.conn.search_s(DN_CONFIG, ldap.SCOPE_SUBTREE, filter_str) - if replica_entry: - repltype = replica_entry[0].getValue(REPL_TYPE) - replflags = replica_entry[0].getValue(REPL_FLAGS) - - if repltype == REPLICA_TYPE_MASTER and \ - replflags == REPLICA_FLAGS_WRITE: - replicarole = 3 - elif (repltype == REPLICA_TYPE_HUBCON and - replflags == REPLICA_TYPE_MASTER): - replicarole = 2 - else: - replicarole = 1 - - if ROLE_ORDER[newrole] < replicarole: - raise ValueError('Can not promote replica to lower role:' + - ' %s -> %s' % (ROLE_TO_NAME[replicarole], - newrole)) - else: - raise ValueError('Failed to find replica') - except ldap.LDAPError as e: raise ValueError('Failed to get replica entry: %s' % str(e)) + # Check the role type + replicarole = self.get_role(suffix) + + if ROLE_ORDER[newrole] < ROLE_ORDER[replicarole]: + raise ValueError('Can not promote replica to lower role: {} -> {}'.format(replicarole, newrole)) + # # Create the changelog # @@ -751,37 +769,21 @@ class ReplicaLegacy(object): if newrole != REPLICAROLE_CONSUMER and newrole != REPLICAROLE_HUB: raise ValueError('Can only demote replica to "hub" or "consumer"') - # - # Get the replica entry, and check the role type - # + # Get replica entry filter_str = ('(&(objectclass=nsDS5Replica)(nsDS5ReplicaRoot=%s))' % suffix) try: - replica_entry = self.conn.search_s('cn=config', ldap.SCOPE_SUBTREE, + replica_entry = self.conn.search_s(DN_CONFIG, ldap.SCOPE_SUBTREE, filter_str) - if replica_entry: - repltype = replica_entry[0].getValue(REPL_TYPE) - replflags = replica_entry[0].getValue(REPL_FLAGS) - - if repltype == REPLICA_TYPE_MASTER and \ - replflags == REPLICA_FLAGS_WRITE: - replicarole = 3 - elif (repltype == REPLICA_TYPE_HUBCON and - replflags == REPLICA_FLAGS_WRITE): - replicarole = 2 - else: - replicarole = 1 - - if ROLE_ORDER[newrole] > replicarole: - raise ValueError('Can not demote replica to lower role:' + - ' %s -> %s' % (ROLE_TO_NAME[replicarole], - newrole)) - else: - raise ValueError('Failed to find replica entry') - except ldap.LDAPError as e: raise ValueError('Failed to get replica entry: %s' % str(e)) + # Check the role type + replicarole = self.get_role(suffix) + + if ROLE_ORDER[newrole] > ROLE_ORDER[replicarole]: + raise ValueError('Can not demote replica to higher role: {} -> {}'.format(replicarole, newrole)) + # # Demote it - set the replica type and flags # @@ -800,14 +802,16 @@ class ReplicaLegacy(object): class Replica(DSLdapObject): - """Replica object. There is one "replica" per backend - """ + """Replica object. There is one "replica" per backend""" + def __init__(self, instance, dn=None, batch=False): """Init the Replica object + @param instance - a DirSrv object @param dn - A DN of the replica entry @param batch - NOT IMPLELMENTED """ + super(Replica, self).__init__(instance, dn, batch) self._rdn_attribute = 'cn' self._must_attributes = ['cn', REPL_LEGACY_CONS, REPL_TYPE, @@ -825,6 +829,7 @@ class Replica(DSLdapObject): @param role - A string containing the "role" name @return - True if the role is a valid role name, otherwise return False """ + if role != REPLICAROLE_MASTER and \ role != REPLICAROLE_HUB and \ role != REPLICAROLE_CONSUMER: @@ -834,11 +839,15 @@ class Replica(DSLdapObject): @staticmethod def _valid_rid(role, rid=None): - """ Return True if rid is valid for the replica role + """Return True if rid is valid for the replica role + @param role - A string containing the role name @param rid - Only needed if the role is a "master" @return - True is rid is valid, otherwise return False """ + + if rid is None: + return False if role == REPLICAROLE_MASTER: if not decimal.Decimal(rid) or \ (rid <= 0) or \ @@ -850,18 +859,17 @@ class Replica(DSLdapObject): return True def delete(self): - ''' - Delete a replica related to the provided suffix. - If this replica role was REPLICAROLE_HUB or REPLICAROLE_MASTER, it - also deletes the changelog associated to that replica. If it - exists some replication agreement below that replica, they are - deleted. + """Delete a replica related to the provided suffix. - @return None - @raise InvalidArgumentError - if suffix is missing - ldap.LDAPError - for all other update failures + If this replica role was REPLICAROLE_HUB or REPLICAROLE_MASTER, it + also deletes the changelog associated to that replica. If it + exists some replication agreement below that replica, they are + deleted. - ''' + @return None + @raise InvalidArgumentError - if suffix is missing + ldap.LDAPError - for all other update failures + """ # Get the suffix suffix = self.get_attr_val(REPL_ROOT) @@ -871,7 +879,7 @@ class Replica(DSLdapObject): # Delete the agreements try: - self.deleteAgreements(suffix) + self.deleteAgreements() except ldap.LDAPError as e: self.log.fatal('Failed to delete replica agreements!') raise e @@ -881,14 +889,14 @@ class Replica(DSLdapObject): super(Replica, self).delete() except ldap.LDAPError as e: self.log.fatal('Failed to delete replica configuration ' + - '(%s), error: %s' % (dn_replica, str(e))) + '(%s), error: %s' % (self._dn, str(e))) raise e def deleteAgreements(self): - ''' - Delete all the agreements for the suffix + """Delete all the agreements for the suffix + @raise LDAPError - If failing to delete or search for agreements - ''' + """ # Delete the agreements try: @@ -899,8 +907,7 @@ class Replica(DSLdapObject): self._instance.delete_s(agmt.dn) except ldap.LDAPError as e: self.log.fatal('Failed to delete replica agreement (%s),' + - ' error: %s' % - (admt.dn, str(e))) + ' error: %s' % (agmt.dn, str(e))) raise e except ldap.LDAPError as e: self.log.fatal('Failed to search for replication agreements ' + @@ -908,13 +915,11 @@ class Replica(DSLdapObject): raise e def promote(self, newrole, binddn=None, rid=None): - """ - Promote the replica + """Promote the replica to hub or master @param newrole - The new replication role for the replica: REPLICAROLE_MASTER REPLICAROLE_HUB - REPLICAROLE_CONSUMER @param binddn - The replication bind dn - only applied to master @param rid - The replication ID, applies only to promotions to "master" @@ -923,11 +928,13 @@ class Replica(DSLdapObject): @raise ValueError """ - if newrole != REPLICAROLE_MASTER and newrole != REPLICAROLE_HUB: - raise ValueError('Can only prompt replica to "master" or "hub"') - if not binddn: - raise ValueError('"binddn" required for promotion') + binddn = defaultProperties[REPLICATION_BIND_DN] + + # Check the role type + replicarole = self.get_role() + if ROLE_ORDER[newrole] <= ROLE_ORDER[replicarole]: + raise ValueError('Can not promote replica to lower or the same role: {} -> {}'.format(replicarole, newrole)) if newrole == REPLICAROLE_MASTER: if not rid: @@ -936,37 +943,13 @@ class Replica(DSLdapObject): # Must be a hub - set the rid rid = CONSUMER_REPLICAID - # - # Check the replica role and flags - # - repltype = self.get_attr_val(REPL_TYPE) - replflags = self.get_attr_val(REPL_FLAGS) - - if repltype == REPLICA_TYPE_MASTER and \ - replflags == REPLICA_FLAGS_WRITE: - replicarole = 3 - elif (repltype == REPLICA_TYPE_HUBCON and - replflags == REPLICA_TYPE_MASTER): - replicarole = 2 - else: - replicarole = 1 - - if ROLE_ORDER[newrole] < replicarole: - raise ValueError('Can not promote replica to lower role:' + - ' %s -> %s' % (ROLE_TO_NAME[replicarole], - newrole)) - - # # Create the changelog - # try: self._instance.changelog.create() except ldap.LDAPError as e: raise ValueError('Failed to create changelog: %s' % str(e)) - # # Check that a RID was provided, and its a valid number - # if newrole == REPLICAROLE_MASTER: try: rid = int(rid) @@ -978,101 +961,97 @@ class Replica(DSLdapObject): raise ValueError('"rid" value (%d) is not in range ' + ' 1 - 65534' % rid) - # # Set bind dn - # try: self.set(REPL_BINDDN, binddn) except ldap.LDAPError as e: raise ValueError('Failed to update replica: ' + str(e)) - # - # Set the replica type and flags - # - if newrole == REPLICAROLE_HUB: + # Promote it - set the replica type, flags and rid + if replicarole == REPLICAROLE_CONSUMER and newrole == REPLICAROLE_HUB: try: - self.apply_mods([(REPL_TYPE, '2'), (REPL_FLAGS, '1')]) + self.set(REPL_FLAGS, REPLICA_FLAGS_WRITE) except ldap.LDAPError as e: raise ValueError('Failed to update replica: ' + str(e)) - else: # master + elif replicarole == REPLICAROLE_CONSUMER and newrole == REPLICAROLE_MASTER: try: - self.apply_mods([(REPL_TYPE, '3'), (REPL_FLAGS, '1'), - (REPL_ID, str(rid))]) + self.apply_mods([(REPL_TYPE, str(REPLICA_RDWR_TYPE)), + (REPL_FLAGS, REPLICA_FLAGS_WRITE), + (REPL_ID, str(rid))]) + except ldap.LDAPError as e: + raise ValueError('Failed to update replica: ' + str(e)) + elif replicarole == REPLICAROLE_HUB and newrole == REPLICAROLE_MASTER: + try: + self.apply_mods([(REPL_TYPE, str(REPLICA_RDWR_TYPE)), + (REPL_ID, str(rid))]) except ldap.LDAPError as e: raise ValueError('Failed to update replica: ' + str(e)) def demote(self, newrole): - """ - Demote a replica to a hub or consumer + """Demote a replica to a hub or consumer + @param suffix - The replication suffix @param newrole - The new replication role of this replica REPLICAROLE_HUB REPLICAROLE_CONSUMER @raise ValueError """ - if newrole != REPLICAROLE_CONSUMER and newrole != REPLICAROLE_HUB: - raise ValueError('Can only demote replica to "hub" or "consumer"') - # # Check the role type - # - repltype = self.get_attr_val(REPL_TYPE) - replflags = self.get_attr_val(REPL_FLAGS) - - if repltype == REPLICA_TYPE_MASTER and \ - replflags == REPLICA_FLAGS_WRITE: - replicarole = 3 - elif (repltype == REPLICA_TYPE_HUBCON and - replflags == REPLICA_FLAGS_WRITE): - replicarole = 2 - else: - replicarole = 1 - - if ROLE_ORDER[newrole] > replicarole: - raise ValueError('Can not demote replica to lower role:' + - ' %s -> %s' % (ROLE_TO_NAME[replicarole], - newrole)) + replicarole = self.get_role() + if ROLE_ORDER[newrole] >= ROLE_ORDER[replicarole]: + raise ValueError('Can not demote replica to higher or the same role: {} -> {}'.format(replicarole, newrole)) - # - # Demote it - set the replica type and flags - # - if newrole == 'hub': - flag = '1' - else: - flag = '0' - try: - self.apply_mods([(REPL_TYPE, '2'), (REPL_FLAGS, flag), - (REPL_ID, str(CONSUMER_REPLICAID))]) - except ldap.LDAPError as e: - raise ValueError('Failed to update replica: ' + str(e)) + # Demote it - set the replica type, flags and rid + if replicarole == REPLICAROLE_MASTER and newrole == REPLICAROLE_HUB: + try: + self.apply_mods([(REPL_TYPE, str(REPLICA_RDONLY_TYPE)), + (REPL_ID, str(CONSUMER_REPLICAID))]) + except ldap.LDAPError as e: + raise ValueError('Failed to update replica: ' + str(e)) + elif replicarole == REPLICAROLE_MASTER and newrole == REPLICAROLE_CONSUMER: + try: + self.apply_mods([(REPL_TYPE, str(REPLICA_RDONLY_TYPE)), + (REPL_FLAGS, REPLICA_FLAGS_RDONLY), + (REPL_ID, str(CONSUMER_REPLICAID))]) + except ldap.LDAPError as e: + raise ValueError('Failed to update replica: ' + str(e)) + elif replicarole == REPLICAROLE_HUB and newrole == REPLICAROLE_CONSUMER: + try: + self.set(REPL_FLAGS, REPLICA_FLAGS_RDONLY) + except ldap.LDAPError as e: + raise ValueError('Failed to update replica: ' + str(e)) def get_role(self): """Return the replica role: @return: "master", "hub", or "consumer" """ - repltype = self.get_attr_val(REPL_TYPE) - replflags = self.get_attr_val(REPL_FLAGS) - - if repltype == REPLICA_TYPE_MASTER and \ - replflags == REPLICA_FLAGS_WRITE: - replicarole = 3 - elif (repltype == REPLICA_TYPE_HUBCON and - replflags == REPLICA_TYPE_MASTER): - replicarole = 2 + + repltype = self.get_attr_val_int(REPL_TYPE) + replflags = self.get_attr_val_utf8(REPL_FLAGS) + + if repltype == REPLICA_RDWR_TYPE and replflags == REPLICA_FLAGS_WRITE: + replicarole = REPLICAROLE_MASTER + elif repltype == REPLICA_RDONLY_TYPE and replflags == REPLICA_FLAGS_WRITE: + replicarole = REPLICAROLE_HUB + elif repltype == REPLICA_RDONLY_TYPE and replflags == REPLICA_FLAGS_RDONLY: + replicarole = REPLICAROLE_CONSUMER else: - replicarole = 1 + raise ValueError("Failed to determine a replica role") - return ROLE_TO_NAME[replicarole] + return replicarole def check_init(self, agmtdn): """Check that a total update has completed + @returns tuple - first element is done/not done, 2nd is no error/has error @param agmtdn - the agreement dn THIS SHOULD BE IN THE NEW AGREEMENT CLASS """ + done, hasError = False, 0 attrlist = ['cn', 'nsds5BeginReplicaRefresh', @@ -1115,11 +1094,13 @@ class Replica(DSLdapObject): def wait_init(self, agmtdn): """Initialize replication and wait for completion. + @oaram agmtdn - agreement dn @return - 0 if the initialization is complete THIS SHOULD BE IN THE NEW AGREEMENT CLASS """ + done = False haserror = 0 while not done and not haserror: @@ -1134,6 +1115,7 @@ class Replica(DSLdapObject): @return - 0 if successful THIS SHOULD BE IN THE NEW AGREEMENT CLASS """ + rc = self.start_async(agmtdn) if not rc: rc = self.wait_init(agmtdn) @@ -1143,10 +1125,11 @@ class Replica(DSLdapObject): def start_async(self, agmtdn): """Initialize replication without waiting. - @param agmtdn - agreement dn + @param agmtdn - agreement dn - THIS SHOULD BE IN THE NEW AGREEMENT CLASS + THIS SHOULD BE IN THE NEW AGREEMENT CLASS """ + self.log.info("Starting async replication %s" % agmtdn) mod = [(ldap.MOD_ADD, 'nsds5BeginReplicaRefresh', 'start')] self._instance.modify_s(agmtdn, mod) @@ -1157,6 +1140,7 @@ class Replica(DSLdapObject): @raise ValeuError - If suffix is not setup for replication LDAPError - If there is a problem trying to search for the RUV """ + try: entry = self._instance.search_s(self._suffix, ldap.SCOPE_SUBTREE, @@ -1164,20 +1148,19 @@ class Replica(DSLdapObject): if entry: return entry[0] else: - raise ValueError('Suffix (%s) is not setup for replication' % - suffix) + raise ValueError('Suffix (%s) is not setup for replication' % self._suffix) except ldap.LDAPError as e: raise e def test(self, *replica_dirsrvs): - '''Make a "dummy" update on the the replicated suffix, and check - all the provided replicas to see if they received the update. + """Make a "dummy" update on the the replicated suffix, and check + all the provided replicas to see if they received the update. - @param *replica_dirsrvs - DirSrv instance, DirSrv instance, ... - @return True - if all servers have recevioed the update by this - replica, otherwise return False - @raise LDAPError - when failing to update/search database - ''' + @param *replica_dirsrvs - DirSrv instance, DirSrv instance, ... + @return True - if all servers have recevioed the update by this + replica, otherwise return False + @raise LDAPError - when failing to update/search database + """ # Generate a unique test value test_value = ('test replication from ' + self._instance.serverid + @@ -1217,20 +1200,23 @@ class Replica(DSLdapObject): class Replicas(DSLdapObjects): """Class of all the Replicas""" + def __init__(self, instance, batch=False): """Init Replicas + @param instance - a DirSrv objectc @param batch - NOT IMPLELMENTED """ + super(Replicas, self).__init__(instance=instance, batch=False) self._objectclasses = [REPLICA_OBJECTCLASS_VALUE] self._filterattrs = [REPL_ROOT] self._childobject = Replica - self._basedn = 'cn=mapping tree,cn=config' + self._basedn = DN_MAPPING_TREE def get(self, selector=[], dn=None): - """Wrap Replicas' "get", and set the suffix after we get the replica - """ + """Wrap Replicas' "get", and set the suffix after we get the replica """ + replica = super(Replicas, self).get(selector, dn) if replica: # Get and set the replica's suffix @@ -1240,24 +1226,24 @@ class Replicas(DSLdapObjects): def enable(self, suffix, role, replicaID=None, args=None): """Enable replication for this suffix - @param suffix - The suffix to enable replication for - @param role - REPLICAROLE_MASTER, REPLICAROLE_HUB or - REPLICAROLE_CONSUMER - @param rid - number that identify the supplier replica - (role=REPLICAROLE_MASTER) in the topology. For - hub/consumer (role=REPLICAROLE_HUB or - REPLICAROLE_CONSUMER), rid value is not used. This - parameter is mandatory for supplier. + @param suffix - The suffix to enable replication for + @param role - REPLICAROLE_MASTER, REPLICAROLE_HUB or + REPLICAROLE_CONSUMER + @param rid - number that identify the supplier replica + (role=REPLICAROLE_MASTER) in the topology. For + hub/consumer (role=REPLICAROLE_HUB or + REPLICAROLE_CONSUMER), rid value is not used. This + parameter is mandatory for supplier. - @param args - dictionary of additional replica properties + @param args - dictionary of additional replica properties - @return replica DN - - @raise InvalidArgumentError - if missing mandatory arguments - ValueError - argument with invalid value - LDAPError - failed to add replica entry + @return replica DN + @raise InvalidArgumentError - if missing mandatory arguments + ValueError - argument with invalid value + LDAPError - failed to add replica entry """ + # Normalize the suffix suffix = normalizeDN(suffix) @@ -1299,23 +1285,23 @@ class Replicas(DSLdapObjects): # Now set default values of unset properties if REPLICA_LEGACY_CONS not in properties: properties[REPL_LEGACY_CONS] = 'off' - if role != REPLICAROLE_CONSUMER: - properties[REPL_FLAGS] = "1" - # + # Set flags explicitly, so it will be more readable + if role == REPLICAROLE_CONSUMER: + properties[REPL_FLAGS] = REPLICA_FLAGS_RDONLY + else: + properties[REPL_FLAGS] = REPLICA_FLAGS_WRITE + # Check if replica entry is already in the mapping-tree - # try: replica = self.get(suffix) # Should we return an error, or just return the existing relica? self._log.warn("Already setup replica for suffix %s" % suffix) return replica - except: + except ldap.NO_SUCH_OBJECT: pass - # # Create changelog - # if (role == REPLICAROLE_MASTER) or (role == REPLICAROLE_HUB): self._instance.changelog.create() @@ -1333,9 +1319,7 @@ class Replicas(DSLdapObjects): repl_manager_dn=properties[REPL_BINDDN], repl_manager_pw=repl_pw) - # # Now create the replica entry - # mtents = self._instance.mappingtree.list(suffix=suffix) suffix_dn = mtents[0].dn replica = self.create(RDN_REPLICA, properties) @@ -1343,17 +1327,23 @@ class Replicas(DSLdapObjects): return replica - def delete(self, suffix): + def disable(self, suffix): """Disable replication on the suffix specified @param suffix - Replicated suffix to disable @raise ValueError is suffix is not being replicated """ + try: replica = self.get(suffix) except ldap.NO_SUCH_OBJECT: raise ValueError('Suffix (%s) is not setup for replication' % suffix) + + role = replica.get_role() + if role in ('master', 'hub'): + self._instance.changelog.delete() + try: replica.delete() except ldap.LDAPError as e: @@ -1373,6 +1363,7 @@ class Replicas(DSLdapObjects): @raise ldap.NO_SUCH_OBJECT - If suffix is not replicated """ + replica = self.get(suffix) try: replica = self.get(suffix) @@ -1383,12 +1374,14 @@ class Replicas(DSLdapObjects): def demote(self, suffix, newrole): """Promote the replica speficied by the suffix to the new role + @param suffix - The replication suffix @param newrole - The new replication role of this replica REPLICAROLE_HUB REPLICAROLE_CONSUMER @raise ldap.NO_SUCH_OBJECT - If suffix is not replicated """ + replica = self.get(suffix) try: replica = self.get(suffix) @@ -1413,10 +1406,12 @@ class Replicas(DSLdapObjects): def get_ruv_entry(self, suffix): """Return the database RUV entry for the provided suffix + @return - The database RUV entry @raise ValeuError - If suffix is not setup for replication LDAPError - If there is a problem trying to search for the RUV """ + try: replica = self.get(suffix) except ldap.NO_SUCH_OBJECT: @@ -1426,14 +1421,15 @@ class Replicas(DSLdapObjects): def test(self, suffix, *replica_dirsrvs): """Make a "dummy" update on the the replicated suffix, and check - all the provided replicas to see if they received the update. + all the provided replicas to see if they received the update. - @param suffix - the replicated suffix we want to check - @param *replica_dirsrvs - DirSrv instance, DirSrv instance, ... - @return True - if all servers have recevioed the update by this - replica, otherwise return False - @raise LDAPError - when failing to update/search database + @param suffix - the replicated suffix we want to check + @param *replica_dirsrvs - DirSrv instance, DirSrv instance, ... + @return True - if all servers have recevioed the update by this + replica, otherwise return False + @raise LDAPError - when failing to update/search database """ + try: replica = self.get(suffix) except ldap.NO_SUCH_OBJECT: diff --git a/lib389/tests/replicaLegacy_test.py b/lib389/tests/replicaLegacy_test.py new file mode 100644 index 0000000..9eb0078 --- /dev/null +++ b/lib389/tests/replicaLegacy_test.py @@ -0,0 +1,459 @@ +# --- BEGIN COPYRIGHT BLOCK --- +# Copyright (C) 2015 Red Hat, Inc. +# All rights reserved. +# +# License: GPL (version 3 or any later version). +# See LICENSE for details. +# --- END COPYRIGHT BLOCK --- +# +import ldap +import os +import pytest +import logging + +from lib389 import InvalidArgumentError +from lib389._constants import * +from lib389.properties import * +from lib389 import DirSrv, Entry + +logging.getLogger(__name__).setLevel(logging.DEBUG) +log = logging.getLogger(__name__) + +# 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_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 TopologyReplication(object): + def __init__(self, master, consumer): + master.open() + consumer.open() + self.master = master + self.consumer = consumer + + +@pytest.fixture(scope="module") +def topology(request): + # Create the master instance + master = DirSrv(verbose=False) + master.log.debug("Master allocated") + args = {SER_HOST: HOST_MASTER, + SER_PORT: PORT_MASTER, + SER_SERVERID_PROP: SERVERID_MASTER} + master.allocate(args) + if master.exists(): + master.delete() + master.create() + master.open() + + # Create the consumer instance + consumer = DirSrv(verbose=False) + consumer.log.debug("Consumer allocated") + args = {SER_HOST: HOST_CONSUMER, + SER_PORT: PORT_CONSUMER, + SER_SERVERID_PROP: SERVERID_CONSUMER} + consumer.allocate(args) + if consumer.exists(): + consumer.delete() + consumer.create() + consumer.open() + + # Delete each instance in the end + def fin(): + master.delete() + consumer.delete() + request.addfinalizer(fin) + + return TopologyReplication(master, consumer) + + +def test_create(topology): + """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 + """ + + log.info("\n\n##########\n### CREATE\n############") + # + # MASTER (suffix/backend) + # + backendEntry = topology.master.backend.create( + suffix=NEW_SUFFIX_1, properties={BACKEND_NAME: NEW_BACKEND_1}) + backendEntry = topology.master.backend.create( + suffix=NEW_SUFFIX_2, properties={BACKEND_NAME: NEW_BACKEND_2}) + + ents = topology.master.mappingtree.list() + master_nb_mappingtree = len(ents) + + # create a first additional mapping tree + topology.master.mappingtree.create(NEW_SUFFIX_1, bename=NEW_BACKEND_1) + ents = topology.master.mappingtree.list() + assert len(ents) == (master_nb_mappingtree + 1) + topology.master.add_s(Entry((NEW_SUFFIX_1, + {'objectclass': "top organizationalunit".split(), + 'ou': NEW_SUFFIX_1.split('=', 1)[1]}))) + + # create a second additional mapping tree + topology.master.mappingtree.create(NEW_SUFFIX_2, bename=NEW_BACKEND_2) + ents = topology.master.mappingtree.list() + assert len(ents) == (master_nb_mappingtree + 2) + topology.master.add_s(Entry((NEW_SUFFIX_2, + {'objectclass': "top organizationalunit".split(), + 'ou': NEW_SUFFIX_2.split('=', 1)[1]}))) + log.info('Master it exists now %d suffix(es)' % len(ents)) + + # + # CONSUMER (suffix/backend) + # + backendEntry = topology.consumer.backend.create( + suffix=NEW_SUFFIX_1, properties={BACKEND_NAME: NEW_BACKEND_1}) + backendEntry = topology.consumer.backend.create( + suffix=NEW_SUFFIX_2, properties={BACKEND_NAME: NEW_BACKEND_2}) + + ents = topology.consumer.mappingtree.list() + consumer_nb_mappingtree = len(ents) + + # create a first additional mapping tree + topology.consumer.mappingtree.create(NEW_SUFFIX_1, bename=NEW_BACKEND_1) + ents = topology.consumer.mappingtree.list() + assert len(ents) == (consumer_nb_mappingtree + 1) + topology.consumer.add_s(Entry((NEW_SUFFIX_1, + {'objectclass': "top organizationalunit".split(), + 'ou': NEW_SUFFIX_1.split('=', 1)[1]}))) + + # create a second additional mapping tree + topology.consumer.mappingtree.create(NEW_SUFFIX_2, bename=NEW_BACKEND_2) + ents = topology.consumer.mappingtree.list() + assert len(ents) == (consumer_nb_mappingtree + 2) + topology.consumer.add_s(Entry((NEW_SUFFIX_2, + {'objectclass': "top organizationalunit".split(), + 'ou': NEW_SUFFIX_2.split('=', 1)[1]}))) + log.info('Consumer it exists now %d suffix(es)' % len(ents)) + + # + # Now create REPLICAS on master + # + # check it exists this entry to stores the changelogs + topology.master.changelog.create() + + # create a master + topology.master.replica.create(suffix=NEW_SUFFIX_1, + role=REPLICAROLE_MASTER, + rid=1) + ents = topology.master.replica.list() + assert len(ents) == 1 + log.info('Master replica %s' % ents[0].dn) + + # create a consumer + topology.master.replica.create(suffix=NEW_SUFFIX_2, + role=REPLICAROLE_CONSUMER) + ents = topology.master.replica.list() + assert len(ents) == 2 + ents = topology.master.replica.list(suffix=NEW_SUFFIX_2) + log.info('Consumer replica %s' % ents[0].dn) + + # + # Now create REPLICAS on consumer + # + # create a master + topology.consumer.replica.create(suffix=NEW_SUFFIX_1, + role=REPLICAROLE_CONSUMER) + ents = topology.consumer.replica.list() + assert len(ents) == 1 + log.info('Consumer replica %s' % ents[0].dn) + + # create a consumer + topology.consumer.replica.create(suffix=NEW_SUFFIX_2, + role=REPLICAROLE_CONSUMER) + ents = topology.consumer.replica.list() + assert len(ents) == 2 + ents = topology.consumer.replica.list(suffix=NEW_SUFFIX_2) + log.info('Consumer replica %s' % ents[0].dn) + + +def test_list(topology): + """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() + """ + + log.info("\n\n############\n### LIST\n############") + ents = topology.master.replica.list() + assert len(ents) == 2 + + # Check we can retrieve a replica with its suffix + ents = topology.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 = topology.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 = topology.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 = topology.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 = topology.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 = topology.master.replica.list(suffix="X") + for ent in ents: + log.critical("Unexpected replica: %s" % ent.dn) + assert len(ents) == 0 + + +def test_create_repl_manager(topology): + """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 + """ + + log.info("\n\n###########\n### CREATE_REPL_MANAGER\n###########") + # First create the default replication manager + topology.consumer.replica.create_repl_manager() + ents = topology.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 + topology.consumer.replica.create_repl_manager(repl_manager_dn=rm_dn) + ents = topology.consumer.search_s(rm_dn, ldap.SCOPE_BASE, "objectclass=*") + assert len(ents) == 1 + assert ents[0].dn == rm_dn + + # Check we can bind + topology.consumer.simple_bind_s(rm_dn, + defaultProperties[REPLICATION_BIND_PW]) + + # Check we fail to bind + with pytest.raises(ldap.INVALID_CREDENTIALS) as excinfo: + topology.consumer.simple_bind_s(rm_dn, "dummy") + log.info("Exception: %s" % str(excinfo.value)) + + # now rebind + topology.consumer.simple_bind_s(topology.consumer.binddn, + topology.consumer.bindpw) + + # Create a custom replication manager under NEW_SUFFIX_1 + # with a specified password + rm_dn = NEW_RM_1 + topology.consumer.replica.create_repl_manager(repl_manager_dn=rm_dn, + repl_manager_pw="Secret123") + ents = topology.consumer.search_s(rm_dn, ldap.SCOPE_BASE, "objectclass=*") + assert len(ents) == 1 + assert ents[0].dn == rm_dn + + # Check we can bind + topology.consumer.simple_bind_s(rm_dn, "Secret123") + + # Check we fail to bind + with pytest.raises(ldap.INVALID_CREDENTIALS) as excinfo: + topology.consumer.simple_bind_s(rm_dn, "dummy") + log.info("Exception: %s" % str(excinfo.value)) + topology.consumer.simple_bind_s(topology.consumer.binddn, + topology.consumer.bindpw) + + +def test_enableReplication(topology): + """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 + """ + + log.info("\n\n############\n### ENABLEREPLICATION\n##########") + # + # MASTER (suffix/backend) + # + backendEntry = topology.master.backend.create(suffix=NEW_SUFFIX_3, + properties={BACKEND_NAME: + NEW_BACKEND_3}) + + ents = topology.master.mappingtree.list() + master_nb_mappingtree = len(ents) + + # create a first additional mapping tree + topology.master.mappingtree.create(NEW_SUFFIX_3, bename=NEW_BACKEND_3) + ents = topology.master.mappingtree.list() + assert len(ents) == (master_nb_mappingtree + 1) + topology.master.add_s(Entry((NEW_SUFFIX_3, + {'objectclass': "top organizationalunit".split(), + 'ou': NEW_SUFFIX_3.split('=', 1)[1]}))) + + # a supplier should have replicaId in [1..CONSUMER_REPLICAID[ + with pytest.raises(ValueError) as excinfo: + topology.master.replica.enableReplication(suffix=NEW_SUFFIX_3, + role=REPLICAROLE_MASTER, + replicaId=CONSUMER_REPLICAID) + log.info("Exception (expected): %s" % str(excinfo.value)) + topology.master.replica.enableReplication(suffix=NEW_SUFFIX_3, + role=REPLICAROLE_MASTER, + replicaId=1) + + # + # MASTER (suffix/backend) + # + backendEntry = topology.master.backend.create(suffix=NEW_SUFFIX_4, + properties={BACKEND_NAME: + NEW_BACKEND_4}) + + ents = topology.master.mappingtree.list() + master_nb_mappingtree = len(ents) + + # create a first additional mapping tree + topology.master.mappingtree.create(NEW_SUFFIX_4, bename=NEW_BACKEND_4) + ents = topology.master.mappingtree.list() + assert len(ents) == (master_nb_mappingtree + 1) + topology.master.add_s(Entry((NEW_SUFFIX_4, + {'objectclass': "top organizationalunit".split(), + 'ou': NEW_SUFFIX_4.split('=', 1)[1]}))) + + # A consumer should have CONSUMER_REPLICAID not '1' + with pytest.raises(ValueError) as excinfo: + topology.master.replica.enableReplication(suffix=NEW_SUFFIX_4, + role=REPLICAROLE_CONSUMER, + replicaId=1) + log.info("Exception (expected): %s" % str(excinfo.value)) + topology.master.replica.enableReplication(suffix=NEW_SUFFIX_4, + role=REPLICAROLE_CONSUMER) + + +def test_disableReplication(topology): + """It checks + - Ability to disable replication on a supplier + - Ability to disable replication on a consumer + - Failure to disable replication with wrong suffix on supplier + - Failure to disable replication with wrong suffix on consumer + """ + + log.info("\n\n############\n### DISABLEREPLICATION\n##########") + topology.master.replica.disableReplication(suffix=NEW_SUFFIX_3) + with pytest.raises(ldap.LDAPError) as excinfo: + topology.master.replica.disableReplication(suffix=NEW_SUFFIX_3) + log.info("Exception (expected): %s" % str(excinfo.value)) + + topology.master.replica.disableReplication(suffix=NEW_SUFFIX_4) + with pytest.raises(ldap.LDAPError) as excinfo: + topology.master.replica.disableReplication(suffix=NEW_SUFFIX_4) + log.info("Exception (expected): %s" % str(excinfo.value)) + + +def test_setProperties(topology): + """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 + """ + + log.info("\n\n##########\n### SETPROPERTIES\n############") + # 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} + topology.master.replica.setProperties(suffix=NEW_SUFFIX_1, + properties=properties) + + # Check the values have been written + replicas = topology.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) + log.info("Replica[%s] -> %s: %s" % (prop, attr, val)) + assert val == properties[prop] + + # Check invalid properties raise exception + with pytest.raises(ValueError) as excinfo: + properties = {"dummy": 'dummy'} + topology.master.replica.setProperties(suffix=NEW_SUFFIX_1, + properties=properties) + log.info("Exception (expected): %s" % str(excinfo.value)) + + # check call without suffix/dn/entry raise InvalidArgumentError + with pytest.raises(InvalidArgumentError) as excinfo: + properties = {REPLICA_LEGACY_CONS: 'off'} + topology.master.replica.setProperties(properties=properties) + log.info("Exception (expected): %s" % str(excinfo.value)) + + # check that if we do not provide a valid entry it raises ValueError + with pytest.raises(ValueError) as excinfo: + properties = {REPLICA_LEGACY_CONS: 'off'} + topology.master.replica.setProperties(replica_entry="dummy", + properties=properties) + log.info("Exception (expected): %s" % str(excinfo.value)) + + # check that with an invalid suffix or replica_dn it raise ValueError + with pytest.raises(ValueError) as excinfo: + properties = {REPLICA_LEGACY_CONS: 'off'} + topology.master.replica.setProperties(suffix="dummy", + properties=properties) + log.info("Exception (expected): %s" % str(excinfo.value)) + + +def test_getProperties(topology): + """Currently not implemented""" + + log.info("\n\n############\n### GETPROPERTIES\n###########") + with pytest.raises(NotImplementedError) as excinfo: + properties = {REPLICA_LEGACY_CONS: 'off'} + topology.master.replica.getProperties(suffix=NEW_SUFFIX_1) + log.info("Exception (expected): %s" % str(excinfo.value)) + + +if __name__ == "__main__": + CURRENT_FILE = os.path.realpath(__file__) + pytest.main("-s -v %s" % CURRENT_FILE) diff --git a/lib389/tests/replica_test.py b/lib389/tests/replica_test.py index 9eb0078..f65d886 100644 --- a/lib389/tests/replica_test.py +++ b/lib389/tests/replica_test.py @@ -1,457 +1,375 @@ # --- BEGIN COPYRIGHT BLOCK --- -# Copyright (C) 2015 Red Hat, Inc. +# Copyright (C) 2017 Red Hat, Inc. # All rights reserved. # # License: GPL (version 3 or any later version). # See LICENSE for details. # --- END COPYRIGHT BLOCK --- # -import ldap import os +import ldap import pytest import logging -from lib389 import InvalidArgumentError -from lib389._constants import * -from lib389.properties import * -from lib389 import DirSrv, Entry +from lib389 import NoSuchEntryError +from lib389.replica import Replicas, ROLE_TO_NAME, ROLE_ORDER +from lib389.backend import Backends +from lib389.idm.domain import Domain +from lib389._constants import (REPLICAROLE_MASTER, REPLICAID_MASTER_1, REPLICAID_MASTER_2, + REPLICAROLE_HUB, REPLICAID_HUB_1, REPLICAROLE_CONSUMER, + BACKEND_SUFFIX, BACKEND_NAME, REPLICA_RUV_FILTER) +from lib389.properties import (REPL_FLAGS, REPL_TYPE) +from lib389.topologies import topology_i3 as topo logging.getLogger(__name__).setLevel(logging.DEBUG) log = logging.getLogger(__name__) -# Used for One master / One consumer topology -HOST_MASTER = LOCALHOST -PORT_MASTER = 40389 -SERVERID_MASTER = 'master' -REPLICAID_MASTER = 1 +DEBUGGING = os.getenv('DEBUGGING', default=False) +NEW_SUFFIX = 'dc=test,dc=com' +NEW_BACKEND = 'test_backend' -HOST_CONSUMER = LOCALHOST -PORT_CONSUMER = 50389 -SERVERID_CONSUMER = 'consumer' -TEST_REPL_DN = "uid=test,%s" % DEFAULT_SUFFIX -INSTANCE_PORT = 54321 -INSTANCE_SERVERID = 'dirsrv' -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 +@pytest.fixture(scope="module") +def new_suffixes(topo): + """Create new suffix, backend and mapping tree""" + + for num in range(1, 4): + backends = Backends(topo.ins["standalone{}".format(num)]) + backends.create(properties={BACKEND_SUFFIX: NEW_SUFFIX, + BACKEND_NAME: NEW_BACKEND}) + domain = Domain(topo.ins["standalone{}".format(num)], NEW_SUFFIX) + domain.create(properties={'dc': 'test', 'description': NEW_SUFFIX}) + + +@pytest.fixture(scope="function") +def simple_replica(topo, new_suffixes, request): + """Enable simple multi-master replication""" + + master1 = topo.ins["standalone1"] + master2 = topo.ins["standalone2"] + + log.info("Enable two master replicas") + replicas_m1 = Replicas(master1) + replica_m1 = replicas_m1.enable(suffix=NEW_SUFFIX, + role=REPLICAROLE_MASTER, + replicaID=REPLICAID_MASTER_1) + replicas_m2 = Replicas(master2) + replica_m2 = replicas_m2.enable(suffix=NEW_SUFFIX, + role=REPLICAROLE_MASTER, + replicaID=REPLICAID_MASTER_2) + + log.info("Create agreements between the instances") + master1.agreement.create(suffix=NEW_SUFFIX, + host=master2.host, + port=master2.port) + master2.agreement.create(suffix=NEW_SUFFIX, + host=master1.host, + port=master1.port) + + log.info("Test replication") + replicas_m1.test(NEW_SUFFIX, master2) -NEW_SUFFIX_2 = 'ou=test_consumer' -NEW_BACKEND_2 = 'test_consumerdb' + def fin(): + replicas_m1.disable(NEW_SUFFIX) + replicas_m2.disable(NEW_SUFFIX) + request.addfinalizer(fin) -NEW_SUFFIX_3 = 'ou=test_enablereplication_1' -NEW_BACKEND_3 = 'test_enablereplicationdb_1' + return [replica_m1, replica_m2] -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' +@pytest.fixture(scope="function") +def clean_up(topo, new_suffixes, request): + """Check that all replicas were disabled and disable if not""" + def fin(): + for num in range(1, 4): + try: + replicas = Replicas(topo.ins["standalone{}".format(num)]) + replicas.disable(NEW_SUFFIX) + log.info("standalone{} is disabled now".format(num)) + except: + pass + request.addfinalizer(fin) -class TopologyReplication(object): - def __init__(self, master, consumer): - master.open() - consumer.open() - self.master = master - self.consumer = consumer +def test_delete_agreements(topo, simple_replica): + """Check deleteAgreements method -@pytest.fixture(scope="module") -def topology(request): - # Create the master instance - master = DirSrv(verbose=False) - master.log.debug("Master allocated") - args = {SER_HOST: HOST_MASTER, - SER_PORT: PORT_MASTER, - SER_SERVERID_PROP: SERVERID_MASTER} - master.allocate(args) - if master.exists(): - master.delete() - master.create() - master.open() - - # Create the consumer instance - consumer = DirSrv(verbose=False) - consumer.log.debug("Consumer allocated") - args = {SER_HOST: HOST_CONSUMER, - SER_PORT: PORT_CONSUMER, - SER_SERVERID_PROP: SERVERID_CONSUMER} - consumer.allocate(args) - if consumer.exists(): - consumer.delete() - consumer.create() - consumer.open() - - # Delete each instance in the end - def fin(): - master.delete() - consumer.delete() - request.addfinalizer(fin) + :feature: Replication + :steps: 1. Enable replication with agreements + 2. Delete the agreements + 3. Check that agreements were deleted + 4. Disable replication + :expectedresults: No errors happen, agreements successfully deleted + """ + + master1 = topo.ins["standalone1"] + master2 = topo.ins["standalone2"] + + log.info("Check that agreements in place") + ents = master1.agreement.list(suffix=NEW_SUFFIX) + assert(len(ents) == 1) + ents = master2.agreement.list(suffix=NEW_SUFFIX) + assert(len(ents) == 1) + + log.info("Delete the agreements") + simple_replica[0].deleteAgreements() + simple_replica[1].deleteAgreements() + + log.info("Check that agreements were deleted") + ents = master1.agreement.list(suffix=NEW_SUFFIX) + assert(len(ents) == 0) + ents = master2.agreement.list(suffix=NEW_SUFFIX) + assert(len(ents) == 0) - return TopologyReplication(master, consumer) +def test_get_ruv_entry(topo, simple_replica): + """Check get_ruv_entry method -def test_create(topology): - """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 + :feature: Replication + :steps: 1. Enable replication with agreements + 2. Get ruv entry with get_ruv_entry() method + 3. Get ruv entry with ldap.search + 4. Disable replication + :expectedresults: Entries should be equal """ - log.info("\n\n##########\n### CREATE\n############") - # - # MASTER (suffix/backend) - # - backendEntry = topology.master.backend.create( - suffix=NEW_SUFFIX_1, properties={BACKEND_NAME: NEW_BACKEND_1}) - backendEntry = topology.master.backend.create( - suffix=NEW_SUFFIX_2, properties={BACKEND_NAME: NEW_BACKEND_2}) - - ents = topology.master.mappingtree.list() - master_nb_mappingtree = len(ents) - - # create a first additional mapping tree - topology.master.mappingtree.create(NEW_SUFFIX_1, bename=NEW_BACKEND_1) - ents = topology.master.mappingtree.list() - assert len(ents) == (master_nb_mappingtree + 1) - topology.master.add_s(Entry((NEW_SUFFIX_1, - {'objectclass': "top organizationalunit".split(), - 'ou': NEW_SUFFIX_1.split('=', 1)[1]}))) - - # create a second additional mapping tree - topology.master.mappingtree.create(NEW_SUFFIX_2, bename=NEW_BACKEND_2) - ents = topology.master.mappingtree.list() - assert len(ents) == (master_nb_mappingtree + 2) - topology.master.add_s(Entry((NEW_SUFFIX_2, - {'objectclass': "top organizationalunit".split(), - 'ou': NEW_SUFFIX_2.split('=', 1)[1]}))) - log.info('Master it exists now %d suffix(es)' % len(ents)) - - # - # CONSUMER (suffix/backend) - # - backendEntry = topology.consumer.backend.create( - suffix=NEW_SUFFIX_1, properties={BACKEND_NAME: NEW_BACKEND_1}) - backendEntry = topology.consumer.backend.create( - suffix=NEW_SUFFIX_2, properties={BACKEND_NAME: NEW_BACKEND_2}) - - ents = topology.consumer.mappingtree.list() - consumer_nb_mappingtree = len(ents) - - # create a first additional mapping tree - topology.consumer.mappingtree.create(NEW_SUFFIX_1, bename=NEW_BACKEND_1) - ents = topology.consumer.mappingtree.list() - assert len(ents) == (consumer_nb_mappingtree + 1) - topology.consumer.add_s(Entry((NEW_SUFFIX_1, - {'objectclass': "top organizationalunit".split(), - 'ou': NEW_SUFFIX_1.split('=', 1)[1]}))) - - # create a second additional mapping tree - topology.consumer.mappingtree.create(NEW_SUFFIX_2, bename=NEW_BACKEND_2) - ents = topology.consumer.mappingtree.list() - assert len(ents) == (consumer_nb_mappingtree + 2) - topology.consumer.add_s(Entry((NEW_SUFFIX_2, - {'objectclass': "top organizationalunit".split(), - 'ou': NEW_SUFFIX_2.split('=', 1)[1]}))) - log.info('Consumer it exists now %d suffix(es)' % len(ents)) - - # - # Now create REPLICAS on master - # - # check it exists this entry to stores the changelogs - topology.master.changelog.create() - - # create a master - topology.master.replica.create(suffix=NEW_SUFFIX_1, - role=REPLICAROLE_MASTER, - rid=1) - ents = topology.master.replica.list() - assert len(ents) == 1 - log.info('Master replica %s' % ents[0].dn) - - # create a consumer - topology.master.replica.create(suffix=NEW_SUFFIX_2, - role=REPLICAROLE_CONSUMER) - ents = topology.master.replica.list() - assert len(ents) == 2 - ents = topology.master.replica.list(suffix=NEW_SUFFIX_2) - log.info('Consumer replica %s' % ents[0].dn) - - # - # Now create REPLICAS on consumer - # - # create a master - topology.consumer.replica.create(suffix=NEW_SUFFIX_1, - role=REPLICAROLE_CONSUMER) - ents = topology.consumer.replica.list() - assert len(ents) == 1 - log.info('Consumer replica %s' % ents[0].dn) + ruv_entry = simple_replica[0].get_ruv_entry() + entry = topo.ins["standalone1"].search_s(NEW_SUFFIX, ldap.SCOPE_SUBTREE, REPLICA_RUV_FILTER)[0] - # create a consumer - topology.consumer.replica.create(suffix=NEW_SUFFIX_2, - role=REPLICAROLE_CONSUMER) - ents = topology.consumer.replica.list() - assert len(ents) == 2 - ents = topology.consumer.replica.list(suffix=NEW_SUFFIX_2) - log.info('Consumer replica %s' % ents[0].dn) + assert ruv_entry == entry -def test_list(topology): - """This test checks: - - existing replicas can be retrieved - - access to unknown replica does not fail +def test_get_role(topo, simple_replica): + """Check get_role method - PRE-CONDITION: - It exists on MASTER two replicas NEW_SUFFIX_1 and NEW_SUFFIX_2 - created by test_create() + :feature: Replication + :steps: 1. Enable replication with agreements + 2. Get role with get_role() method + 3. Get repl_flags, repl_type from the replica entry with ldap.search + 4. Compare the values + 5. Disable replication + :expectedresults: The role 'master' should have flags=1 and type=3 """ - log.info("\n\n############\n### LIST\n############") - ents = topology.master.replica.list() - assert len(ents) == 2 + master1 = topo.ins["standalone1"] - # Check we can retrieve a replica with its suffix - ents = topology.master.replica.list(suffix=NEW_SUFFIX_1) - assert len(ents) == 1 - replica_dn_1 = ents[0].dn + replica_role = simple_replica[0].get_role() + entry = master1.search_s(simple_replica[0].dn, ldap.SCOPE_BASE, attrlist=[REPL_FLAGS])[0] + replica_flags = entry.getValue(REPL_FLAGS) + entry = master1.search_s(simple_replica[0].dn, ldap.SCOPE_BASE, attrlist=[REPL_TYPE])[0] + replica_type = entry.getValue(REPL_TYPE) - # Check we can retrieve a replica with its suffix - ents = topology.master.replica.list(suffix=NEW_SUFFIX_2) - assert len(ents) == 1 - replica_dn_2 = ents[0].dn + log.info("Check that we've got role 'master', while {}=1 and {}=3".format(REPL_FLAGS, REPL_TYPE)) + assert replica_role == "master" and replica_flags == "1" and replica_type == "3",\ + "Failure, get_role() gave {}, while {} has {} and {} has {}".format(replica_role, REPL_FLAGS, replica_flags, + REPL_TYPE, replica_type) - # Check we can retrieve a replica with its DN - ents = topology.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 = topology.master.replica.list(suffix=NEW_SUFFIX_2, - replica_dn=replica_dn_2) +def test_basic(topo, new_suffixes, clean_up): + """Check basic replica functionality + + :feature: Replication + :steps: 1. Enable replication on master. hub and consumer + 2. Create agreements: master-hub, hub-consumer + 3. Test master-consumer replication + 4. Disable replication + 5. Check that replica, agreements and changelog were deleted + :expectedresults: No errors happen, replication is successfully enabled and disabled + """ + + master = topo.ins["standalone1"] + hub = topo.ins["standalone2"] + consumer = topo.ins["standalone3"] + + log.info("Enable replicas (create replica and changelog entries)") + master_replicas = Replicas(master) + master_replicas.enable(suffix=NEW_SUFFIX, + role=REPLICAROLE_MASTER, + replicaID=REPLICAID_MASTER_1) + ents = master_replicas.list() + assert len(ents) == 1 + ents = master.changelog.list() assert len(ents) == 1 - assert replica_dn_2 == ents[0].dn - # Check DN is used before suffix name - ents = topology.master.replica.list(suffix=NEW_SUFFIX_2, - replica_dn=replica_dn_1) + hub_replicas = Replicas(hub) + hub_replicas.enable(suffix=NEW_SUFFIX, + role=REPLICAROLE_HUB, + replicaID=REPLICAID_HUB_1) + ents = hub_replicas.list() + assert len(ents) == 1 + ents = hub.changelog.list() assert len(ents) == 1 - assert replica_dn_1 == ents[0].dn - - # Check that invalid value does not break - ents = topology.master.replica.list(suffix="X") - for ent in ents: - log.critical("Unexpected replica: %s" % ent.dn) - assert len(ents) == 0 - - -def test_create_repl_manager(topology): - """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 - """ - log.info("\n\n###########\n### CREATE_REPL_MANAGER\n###########") - # First create the default replication manager - topology.consumer.replica.create_repl_manager() - ents = topology.consumer.search_s(defaultProperties[REPLICATION_BIND_DN], - ldap.SCOPE_BASE, "objectclass=*") + consumer_replicas = Replicas(consumer) + consumer_replicas.enable(suffix=NEW_SUFFIX, + role=REPLICAROLE_CONSUMER) + ents = consumer_replicas.list() 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 - topology.consumer.replica.create_repl_manager(repl_manager_dn=rm_dn) - ents = topology.consumer.search_s(rm_dn, ldap.SCOPE_BASE, "objectclass=*") + log.info("Create agreements between the instances") + master.agreement.create(suffix=NEW_SUFFIX, + host=hub.host, + port=hub.port) + ents = master.agreement.list(suffix=NEW_SUFFIX) assert len(ents) == 1 - assert ents[0].dn == rm_dn - - # Check we can bind - topology.consumer.simple_bind_s(rm_dn, - defaultProperties[REPLICATION_BIND_PW]) - - # Check we fail to bind - with pytest.raises(ldap.INVALID_CREDENTIALS) as excinfo: - topology.consumer.simple_bind_s(rm_dn, "dummy") - log.info("Exception: %s" % str(excinfo.value)) - - # now rebind - topology.consumer.simple_bind_s(topology.consumer.binddn, - topology.consumer.bindpw) - - # Create a custom replication manager under NEW_SUFFIX_1 - # with a specified password - rm_dn = NEW_RM_1 - topology.consumer.replica.create_repl_manager(repl_manager_dn=rm_dn, - repl_manager_pw="Secret123") - ents = topology.consumer.search_s(rm_dn, ldap.SCOPE_BASE, "objectclass=*") + hub.agreement.create(suffix=NEW_SUFFIX, + host=consumer.host, + port=consumer.port) + ents = hub.agreement.list(suffix=NEW_SUFFIX) assert len(ents) == 1 - assert ents[0].dn == rm_dn - # Check we can bind - topology.consumer.simple_bind_s(rm_dn, "Secret123") + log.info("Test replication") + master_replicas.test(NEW_SUFFIX, consumer) + + log.info("Disable replication") + master_replicas.disable(suffix=NEW_SUFFIX) + hub_replicas.disable(suffix=NEW_SUFFIX) + consumer_replicas.disable(suffix=NEW_SUFFIX) + + log.info("Check that replica, agreements and changelog were deleted") + for num in range(1, 4): + log.info("Checking standalone{} instance".format(num)) + inst = topo.ins["standalone{}".format(num)] + + log.info("Checking that replica entries don't exist") + replicas = Replicas(inst) + ents = replicas.list() + assert len(ents) == 0 + + log.info("Checking that changelog doesn't exist") + ents = inst.changelog.list() + assert len(ents) == 0 + + log.info("Checking that agreements can't be acquired because the replica entry doesn't exist") + with pytest.raises(NoSuchEntryError) as e: + inst.agreement.list(suffix=NEW_SUFFIX) + assert "no replica set up" in e.msg + + +@pytest.mark.parametrize('role_from,role_to', + ((REPLICAROLE_CONSUMER, REPLICAROLE_HUB), + (REPLICAROLE_CONSUMER, REPLICAROLE_MASTER), + (REPLICAROLE_HUB, REPLICAROLE_MASTER))) +def test_promote(topo, new_suffixes, clean_up, role_from, role_to): + """Check that replica promote method works properly + + :feature: Replication + :steps: 1. Enable replication on the instance + 2. Promote it to another role + (check consumer-hub, consumer-master, hub-master + 3. Check that role was successfully changed + 4. Disable replication + :expectedresults: No errors happen, replica successfully promoted + """ - # Check we fail to bind - with pytest.raises(ldap.INVALID_CREDENTIALS) as excinfo: - topology.consumer.simple_bind_s(rm_dn, "dummy") - log.info("Exception: %s" % str(excinfo.value)) - topology.consumer.simple_bind_s(topology.consumer.binddn, - topology.consumer.bindpw) + inst = topo.ins["standalone1"] + log.info("Enable replication on instance with a role - {}".format(role_from)) + replicas = Replicas(inst) + replica = replicas.enable(suffix=NEW_SUFFIX, + role=role_from) -def test_enableReplication(topology): - """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 - """ + log.info("Promote replica to {}".format(role_to)) + replica.promote(newrole=role_to, + rid=REPLICAID_MASTER_1) - log.info("\n\n############\n### ENABLEREPLICATION\n##########") - # - # MASTER (suffix/backend) - # - backendEntry = topology.master.backend.create(suffix=NEW_SUFFIX_3, - properties={BACKEND_NAME: - NEW_BACKEND_3}) - - ents = topology.master.mappingtree.list() - master_nb_mappingtree = len(ents) - - # create a first additional mapping tree - topology.master.mappingtree.create(NEW_SUFFIX_3, bename=NEW_BACKEND_3) - ents = topology.master.mappingtree.list() - assert len(ents) == (master_nb_mappingtree + 1) - topology.master.add_s(Entry((NEW_SUFFIX_3, - {'objectclass': "top organizationalunit".split(), - 'ou': NEW_SUFFIX_3.split('=', 1)[1]}))) - - # a supplier should have replicaId in [1..CONSUMER_REPLICAID[ - with pytest.raises(ValueError) as excinfo: - topology.master.replica.enableReplication(suffix=NEW_SUFFIX_3, - role=REPLICAROLE_MASTER, - replicaId=CONSUMER_REPLICAID) - log.info("Exception (expected): %s" % str(excinfo.value)) - topology.master.replica.enableReplication(suffix=NEW_SUFFIX_3, - role=REPLICAROLE_MASTER, - replicaId=1) - - # - # MASTER (suffix/backend) - # - backendEntry = topology.master.backend.create(suffix=NEW_SUFFIX_4, - properties={BACKEND_NAME: - NEW_BACKEND_4}) - - ents = topology.master.mappingtree.list() - master_nb_mappingtree = len(ents) - - # create a first additional mapping tree - topology.master.mappingtree.create(NEW_SUFFIX_4, bename=NEW_BACKEND_4) - ents = topology.master.mappingtree.list() - assert len(ents) == (master_nb_mappingtree + 1) - topology.master.add_s(Entry((NEW_SUFFIX_4, - {'objectclass': "top organizationalunit".split(), - 'ou': NEW_SUFFIX_4.split('=', 1)[1]}))) - - # A consumer should have CONSUMER_REPLICAID not '1' - with pytest.raises(ValueError) as excinfo: - topology.master.replica.enableReplication(suffix=NEW_SUFFIX_4, - role=REPLICAROLE_CONSUMER, - replicaId=1) - log.info("Exception (expected): %s" % str(excinfo.value)) - topology.master.replica.enableReplication(suffix=NEW_SUFFIX_4, - role=REPLICAROLE_CONSUMER) - - -def test_disableReplication(topology): - """It checks - - Ability to disable replication on a supplier - - Ability to disable replication on a consumer - - Failure to disable replication with wrong suffix on supplier - - Failure to disable replication with wrong suffix on consumer + log.info("Check that replica was successfully promoted") + replica_role = replica.get_role() + assert replica_role == role_to + + +@pytest.mark.parametrize('role_from,role_to', + ((REPLICAROLE_MASTER, REPLICAROLE_HUB), + (REPLICAROLE_MASTER, REPLICAROLE_CONSUMER), + (REPLICAROLE_HUB, REPLICAROLE_CONSUMER))) +def test_demote(topo, new_suffixes, clean_up, role_from, role_to): + """Check that replica demote method works properly + + :feature: Replication + :steps: 1. Enable replication on the instance + 2. Demote it to another role + (check master-hub, master-consumer, hub-consumer) + 3. Check that role was successfully changed + 4. Disable replication + :expectedresults: No errors happen, replica successfully demoted """ - log.info("\n\n############\n### DISABLEREPLICATION\n##########") - topology.master.replica.disableReplication(suffix=NEW_SUFFIX_3) - with pytest.raises(ldap.LDAPError) as excinfo: - topology.master.replica.disableReplication(suffix=NEW_SUFFIX_3) - log.info("Exception (expected): %s" % str(excinfo.value)) + inst = topo.ins["standalone1"] + + log.info("Enable replication on instance with a role - {}".format(role_from)) + replicas = Replicas(inst) + replica = replicas.enable(suffix=NEW_SUFFIX, + role=role_from, + replicaID=REPLICAID_MASTER_1) + + log.info("Promote replica to {}".format(role_to)) + replica.demote(newrole=role_to) - topology.master.replica.disableReplication(suffix=NEW_SUFFIX_4) - with pytest.raises(ldap.LDAPError) as excinfo: - topology.master.replica.disableReplication(suffix=NEW_SUFFIX_4) - log.info("Exception (expected): %s" % str(excinfo.value)) + log.info("Check that replica was successfully promoted") + replica_role = replica.get_role() + assert replica_role == role_to -def test_setProperties(topology): - """Set some properties - Verified that valid properties are set - Verified that invalid properties raise an Exception +@pytest.mark.parametrize('role_from', (REPLICAROLE_MASTER, + REPLICAROLE_HUB, + REPLICAROLE_CONSUMER)) +def test_promote_fail(topo, new_suffixes, clean_up, role_from): + """Check that replica promote method fails + when promoted to wrong direction - PRE-REQUISITE: it exists a replica for NEW_SUFFIX_1 + :feature: Replication + :steps: 1. Enable replication on the instance + 2. Try to promote it to wrong role + (for example, master-hub, hub-consumer) + 3. Disable replication + :expectedresults: Replica shouldn't be promoted """ - log.info("\n\n##########\n### SETPROPERTIES\n############") - # 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} - topology.master.replica.setProperties(suffix=NEW_SUFFIX_1, - properties=properties) - - # Check the values have been written - replicas = topology.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) - log.info("Replica[%s] -> %s: %s" % (prop, attr, val)) - assert val == properties[prop] - - # Check invalid properties raise exception - with pytest.raises(ValueError) as excinfo: - properties = {"dummy": 'dummy'} - topology.master.replica.setProperties(suffix=NEW_SUFFIX_1, - properties=properties) - log.info("Exception (expected): %s" % str(excinfo.value)) - - # check call without suffix/dn/entry raise InvalidArgumentError - with pytest.raises(InvalidArgumentError) as excinfo: - properties = {REPLICA_LEGACY_CONS: 'off'} - topology.master.replica.setProperties(properties=properties) - log.info("Exception (expected): %s" % str(excinfo.value)) - - # check that if we do not provide a valid entry it raises ValueError - with pytest.raises(ValueError) as excinfo: - properties = {REPLICA_LEGACY_CONS: 'off'} - topology.master.replica.setProperties(replica_entry="dummy", - properties=properties) - log.info("Exception (expected): %s" % str(excinfo.value)) - - # check that with an invalid suffix or replica_dn it raise ValueError - with pytest.raises(ValueError) as excinfo: - properties = {REPLICA_LEGACY_CONS: 'off'} - topology.master.replica.setProperties(suffix="dummy", - properties=properties) - log.info("Exception (expected): %s" % str(excinfo.value)) - - -def test_getProperties(topology): - """Currently not implemented""" - - log.info("\n\n############\n### GETPROPERTIES\n###########") - with pytest.raises(NotImplementedError) as excinfo: - properties = {REPLICA_LEGACY_CONS: 'off'} - topology.master.replica.getProperties(suffix=NEW_SUFFIX_1) - log.info("Exception (expected): %s" % str(excinfo.value)) + inst = topo.ins["standalone1"] + + log.info("Enable replication on instance with a role - {}".format(role_from)) + replicas = Replicas(inst) + replica = replicas.enable(suffix=NEW_SUFFIX, + role=role_from, + replicaID=REPLICAID_MASTER_1) + + for role_to in [x for x in range(1, 4) if x <= ROLE_ORDER[role_from]]: + role_to = ROLE_TO_NAME[role_to] + log.info("Try to promote replica to {}".format(role_to)) + with pytest.raises(ValueError): + replica.promote(newrole=role_to, + rid=REPLICAID_MASTER_1) + + +@pytest.mark.parametrize('role_from', (REPLICAROLE_MASTER, + REPLICAROLE_HUB, + REPLICAROLE_CONSUMER)) +def test_demote_fail(topo, new_suffixes, clean_up, role_from): + """Check that replica demote method fails + when demoted to wrong direction + + :feature: Replication + :steps: 1. Enable replication on the instance + 2. Try to demote it to wrong role + (for example, consumer-master, hub-master) + 3. Disable replication + :expectedresults: Replica shouldn't be demoted + """ + + inst = topo.ins["standalone1"] + + log.info("Enable replication on instance with a role - {}".format(role_from)) + replicas = Replicas(inst) + replica = replicas.enable(suffix=NEW_SUFFIX, + role=role_from, + replicaID=REPLICAID_MASTER_1) + + for role_to in [x for x in range(1, 4) if x >= ROLE_ORDER[role_from]]: + role_to = ROLE_TO_NAME[role_to] + log.info("Try to demote replica to {}".format(role_to)) + with pytest.raises(ValueError): + replica.demote(newrole=role_to) if __name__ == "__main__": -- 2.13.3