| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505 |
- '''
- Created on Nov 7, 2013
- @author: tbordaz
- '''
- import os
- import sys
- import time
- import ldap
- import logging
- import pytest
- import tarfile
- import stat
- import shutil
- from random import randint
- from lib389 import DirSrv, Entry, tools
- from lib389.tools import DirSrvTools
- from lib389._constants import *
- from lib389.properties import *
- logging.getLogger(__name__).setLevel(logging.DEBUG)
- log = logging.getLogger(__name__)
- #
- # important part. We can deploy Master1 and Master2 on different versions
- #
- installation1_prefix = None
- installation2_prefix = None
- TEST_REPL_DN = "cn=test_repl, %s" % SUFFIX
- OC_NAME = 'OCticket47988'
- MUST = "(postalAddress $ postalCode)"
- MAY = "(member $ street)"
- OTHER_NAME = 'other_entry'
- MAX_OTHERS = 10
- BIND_NAME = 'bind_entry'
- BIND_DN = 'cn=%s, %s' % (BIND_NAME, SUFFIX)
- BIND_PW = 'password'
- ENTRY_NAME = 'test_entry'
- ENTRY_DN = 'cn=%s, %s' % (ENTRY_NAME, SUFFIX)
- ENTRY_OC = "top person %s" % OC_NAME
- def _oc_definition(oid_ext, name, must=None, may=None):
- oid = "1.2.3.4.5.6.7.8.9.10.%d" % oid_ext
- desc = 'To test ticket 47490'
- sup = 'person'
- if not must:
- must = MUST
- if not may:
- may = MAY
- new_oc = "( %s NAME '%s' DESC '%s' SUP %s AUXILIARY MUST %s MAY %s )" % (oid, name, desc, sup, must, may)
- return new_oc
- class TopologyMaster1Master2(object):
- def __init__(self, master1, master2):
- master1.open()
- self.master1 = master1
- master2.open()
- self.master2 = master2
- @pytest.fixture(scope="module")
- def topology(request):
- '''
- This fixture is used to create a replicated topology for the 'module'.
- The replicated topology is MASTER1 <-> Master2.
- '''
- global installation1_prefix
- global installation2_prefix
- #os.environ['USE_VALGRIND'] = '1'
- # allocate master1 on a given deployement
- master1 = DirSrv(verbose=False)
- if installation1_prefix:
- args_instance[SER_DEPLOYED_DIR] = installation1_prefix
- # Args for the master1 instance
- args_instance[SER_HOST] = HOST_MASTER_1
- args_instance[SER_PORT] = PORT_MASTER_1
- args_instance[SER_SERVERID_PROP] = SERVERID_MASTER_1
- args_master = args_instance.copy()
- master1.allocate(args_master)
- # allocate master1 on a given deployement
- master2 = DirSrv(verbose=False)
- if installation2_prefix:
- args_instance[SER_DEPLOYED_DIR] = installation2_prefix
- # Args for the consumer instance
- args_instance[SER_HOST] = HOST_MASTER_2
- args_instance[SER_PORT] = PORT_MASTER_2
- args_instance[SER_SERVERID_PROP] = SERVERID_MASTER_2
- args_master = args_instance.copy()
- master2.allocate(args_master)
- # Get the status of the instance and restart it if it exists
- instance_master1 = master1.exists()
- instance_master2 = master2.exists()
- # Remove all the instances
- if instance_master1:
- master1.delete()
- if instance_master2:
- master2.delete()
- # Create the instances
- master1.create()
- master1.open()
- master2.create()
- master2.open()
- #
- # Now prepare the Master-Consumer topology
- #
- # First Enable replication
- master1.replica.enableReplication(suffix=SUFFIX, role=REPLICAROLE_MASTER, replicaId=REPLICAID_MASTER_1)
- master2.replica.enableReplication(suffix=SUFFIX, role=REPLICAROLE_MASTER, replicaId=REPLICAID_MASTER_2)
- # Initialize the supplier->consumer
- 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 = master1.agreement.create(suffix=SUFFIX, host=master2.host, port=master2.port, properties=properties)
- if not repl_agreement:
- log.fatal("Fail to create a replica agreement")
- sys.exit(1)
- log.debug("%s created" % repl_agreement)
- 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]}
- master2.agreement.create(suffix=SUFFIX, host=master1.host, port=master1.port, properties=properties)
- master1.agreement.init(SUFFIX, HOST_MASTER_2, PORT_MASTER_2)
- master1.waitForReplInit(repl_agreement)
- # Check replication is working fine
- master1.add_s(Entry((TEST_REPL_DN, {
- 'objectclass': "top person".split(),
- 'sn': 'test_repl',
- 'cn': 'test_repl'})))
- loop = 0
- ent = None
- while loop <= 10:
- try:
- ent = master2.getEntry(TEST_REPL_DN, ldap.SCOPE_BASE, "(objectclass=*)")
- break
- except ldap.NO_SUCH_OBJECT:
- time.sleep(1)
- loop += 1
- if ent is None:
- assert False
- # Here we have two instances master and consumer
- return TopologyMaster1Master2(master1, master2)
- def _header(topology, label):
- topology.master1.log.info("\n\n###############################################")
- topology.master1.log.info("#######")
- topology.master1.log.info("####### %s" % label)
- topology.master1.log.info("#######")
- topology.master1.log.info("###################################################")
- def _install_schema(server, tarFile):
- server.stop(timeout=10)
- tmpSchema = '/tmp/schema_47988'
- if not os.path.isdir(tmpSchema):
- os.mkdir(tmpSchema)
- for the_file in os.listdir(tmpSchema):
- file_path = os.path.join(tmpSchema, the_file)
- if os.path.isfile(file_path):
- os.unlink(file_path)
- os.chdir(tmpSchema)
- tar = tarfile.open(tarFile, 'r:gz')
- for member in tar.getmembers():
- tar.extract(member.name)
- tar.close()
- st = os.stat(server.schemadir)
- os.chmod(server.schemadir, st.st_mode | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRUSR)
- for the_file in os.listdir(tmpSchema):
- schemaFile = os.path.join(server.schemadir, the_file)
- if os.path.isfile(schemaFile):
- if the_file.startswith('99user.ldif'):
- # only replace 99user.ldif, the other standard definition are kept
- os.chmod(schemaFile, stat.S_IWUSR | stat.S_IRUSR)
- server.log.info("replace %s" % schemaFile)
- shutil.copy(the_file, schemaFile)
- else:
- server.log.info("add %s" % schemaFile)
- shutil.copy(the_file, schemaFile)
- os.chmod(schemaFile, stat.S_IRUSR | stat.S_IRGRP)
- os.chmod(server.schemadir, st.st_mode | stat.S_IRUSR | stat.S_IRGRP)
- def test_ticket47988_init(topology):
- """
- It adds
- - Objectclass with MAY 'member'
- - an entry ('bind_entry') with which we bind to test the 'SELFDN' operation
- It deletes the anonymous aci
- """
- _header(topology, 'test_ticket47988_init')
- # enable acl error logging
- mod = [(ldap.MOD_REPLACE, 'nsslapd-errorlog-level', str(8192))] # REPL
- topology.master1.modify_s(DN_CONFIG, mod)
- topology.master2.modify_s(DN_CONFIG, mod)
- mod = [(ldap.MOD_REPLACE, 'nsslapd-accesslog-level', str(260))] # Internal op
- topology.master1.modify_s(DN_CONFIG, mod)
- topology.master2.modify_s(DN_CONFIG, mod)
- # add dummy entries
- for cpt in range(MAX_OTHERS):
- name = "%s%d" % (OTHER_NAME, cpt)
- topology.master1.add_s(Entry(("cn=%s,%s" % (name, SUFFIX), {
- 'objectclass': "top person".split(),
- 'sn': name,
- 'cn': name})))
- # check that entry 0 is replicated before
- loop = 0
- entryDN = "cn=%s0,%s" % (OTHER_NAME, SUFFIX)
- while loop <= 10:
- try:
- ent = topology.master2.getEntry(entryDN, ldap.SCOPE_BASE, "(objectclass=*)", ['telephonenumber'])
- break
- except ldap.NO_SUCH_OBJECT:
- time.sleep(1)
- loop += 1
- assert (loop <= 10)
- topology.master1.stop(timeout=10)
- topology.master2.stop(timeout=10)
- #install the specific schema M1: ipa3.3, M2: ipa4.1
- schema_file = os.path.join(topology.master1.getDir(__file__, DATA_DIR), "ticket47988/schema_ipa3.3.tar.gz")
- _install_schema(topology.master1, schema_file)
- schema_file = os.path.join(topology.master1.getDir(__file__, DATA_DIR), "ticket47988/schema_ipa4.1.tar.gz")
- _install_schema(topology.master2, schema_file)
- topology.master1.start(timeout=10)
- topology.master2.start(timeout=10)
- def _do_update_schema(server, range=3999):
- '''
- Update the schema of the M2 (IPA4.1). to generate a nsSchemaCSN
- '''
- postfix = str(randint(range, range + 1000))
- OID = '2.16.840.1.113730.3.8.12.%s' % postfix
- NAME = 'thierry%s' % postfix
- value = '( %s NAME \'%s\' DESC \'Override for Group Attributes\' STRUCTURAL MUST ( cn ) MAY sn X-ORIGIN ( \'IPA v4.1.2\' \'user defined\' ) )' % (OID, NAME)
- mod = [(ldap.MOD_ADD, 'objectclasses', value)]
- server.modify_s('cn=schema', mod)
- def _do_update_entry(supplier=None, consumer=None, attempts=10):
- '''
- This is doing an update on M2 (IPA4.1) and checks the update has been
- propagated to M1 (IPA3.3)
- '''
- assert(supplier)
- assert(consumer)
- entryDN = "cn=%s0,%s" % (OTHER_NAME, SUFFIX)
- value = str(randint(100, 200))
- mod = [(ldap.MOD_REPLACE, 'telephonenumber', value)]
- supplier.modify_s(entryDN, mod)
- loop = 0
- while loop <= attempts:
- ent = consumer.getEntry(entryDN, ldap.SCOPE_BASE, "(objectclass=*)", ['telephonenumber'])
- read_val = ent.telephonenumber or "0"
- if read_val == value:
- break
- # the expected value is not yet replicated. try again
- time.sleep(5)
- loop += 1
- supplier.log.debug("test_do_update: receive %s (expected %s)" % (read_val, value))
- assert (loop <= attempts)
- def _pause_M2_to_M1(topology):
- topology.master1.log.info("\n\n######################### Pause RA M2->M1 ######################\n")
- ents = topology.master2.agreement.list(suffix=SUFFIX)
- assert len(ents) == 1
- topology.master2.agreement.pause(ents[0].dn)
- def _resume_M1_to_M2(topology):
- topology.master1.log.info("\n\n######################### resume RA M1->M2 ######################\n")
- ents = topology.master1.agreement.list(suffix=SUFFIX)
- assert len(ents) == 1
- topology.master1.agreement.resume(ents[0].dn)
- def _pause_M1_to_M2(topology):
- topology.master1.log.info("\n\n######################### Pause RA M1->M2 ######################\n")
- ents = topology.master1.agreement.list(suffix=SUFFIX)
- assert len(ents) == 1
- topology.master1.agreement.pause(ents[0].dn)
- def _resume_M2_to_M1(topology):
- topology.master1.log.info("\n\n######################### resume RA M2->M1 ######################\n")
- ents = topology.master2.agreement.list(suffix=SUFFIX)
- assert len(ents) == 1
- topology.master2.agreement.resume(ents[0].dn)
- def test_ticket47988_1(topology):
- '''
- Check that replication is working and pause replication M2->M1
- '''
- _header(topology, 'test_ticket47988_1')
- topology.master1.log.debug("\n\nCheck that replication is working and pause replication M2->M1\n")
- _do_update_entry(supplier=topology.master2, consumer=topology.master1, attempts=5)
- _pause_M2_to_M1(topology)
- def test_ticket47988_2(topology):
- '''
- Update M1 schema and trigger update M1->M2
- So M1 should learn new/extended definitions that are in M2 schema
- '''
- _header(topology, 'test_ticket47988_2')
- topology.master1.log.debug("\n\nUpdate M1 schema and an entry on M1\n")
- master1_schema_csn = topology.master1.schema.get_schema_csn()
- master2_schema_csn = topology.master2.schema.get_schema_csn()
- topology.master1.log.debug("\nBefore updating the schema on M1\n")
- topology.master1.log.debug("Master1 nsschemaCSN: %s" % master1_schema_csn)
- topology.master1.log.debug("Master2 nsschemaCSN: %s" % master2_schema_csn)
- # Here M1 should no, should check M2 schema and learn
- _do_update_schema(topology.master1)
- master1_schema_csn = topology.master1.schema.get_schema_csn()
- master2_schema_csn = topology.master2.schema.get_schema_csn()
- topology.master1.log.debug("\nAfter updating the schema on M1\n")
- topology.master1.log.debug("Master1 nsschemaCSN: %s" % master1_schema_csn)
- topology.master1.log.debug("Master2 nsschemaCSN: %s" % master2_schema_csn)
- assert (master1_schema_csn)
- # to avoid linger effect where a replication session is reused without checking the schema
- _pause_M1_to_M2(topology)
- _resume_M1_to_M2(topology)
- #topo.master1.log.debug("\n\nSleep.... attach the debugger dse_modify")
- #time.sleep(60)
- _do_update_entry(supplier=topology.master1, consumer=topology.master2, attempts=15)
- master1_schema_csn = topology.master1.schema.get_schema_csn()
- master2_schema_csn = topology.master2.schema.get_schema_csn()
- topology.master1.log.debug("\nAfter a full replication session\n")
- topology.master1.log.debug("Master1 nsschemaCSN: %s" % master1_schema_csn)
- topology.master1.log.debug("Master2 nsschemaCSN: %s" % master2_schema_csn)
- assert (master1_schema_csn)
- assert (master2_schema_csn)
- def test_ticket47988_3(topology):
- '''
- Resume replication M2->M1 and check replication is still working
- '''
- _header(topology, 'test_ticket47988_3')
- _resume_M2_to_M1(topology)
- _do_update_entry(supplier=topology.master1, consumer=topology.master2, attempts=5)
- _do_update_entry(supplier=topology.master2, consumer=topology.master1, attempts=5)
- def test_ticket47988_4(topology):
- '''
- Check schemaCSN is identical on both server
- And save the nsschemaCSN to later check they do not change unexpectedly
- '''
- _header(topology, 'test_ticket47988_4')
- master1_schema_csn = topology.master1.schema.get_schema_csn()
- master2_schema_csn = topology.master2.schema.get_schema_csn()
- topology.master1.log.debug("\n\nMaster1 nsschemaCSN: %s" % master1_schema_csn)
- topology.master1.log.debug("\n\nMaster2 nsschemaCSN: %s" % master2_schema_csn)
- assert (master1_schema_csn)
- assert (master2_schema_csn)
- assert (master1_schema_csn == master2_schema_csn)
- topology.master1.saved_schema_csn = master1_schema_csn
- topology.master2.saved_schema_csn = master2_schema_csn
- def test_ticket47988_5(topology):
- '''
- Check schemaCSN do not change unexpectedly
- '''
- _header(topology, 'test_ticket47988_5')
- _do_update_entry(supplier=topology.master1, consumer=topology.master2, attempts=5)
- _do_update_entry(supplier=topology.master2, consumer=topology.master1, attempts=5)
- master1_schema_csn = topology.master1.schema.get_schema_csn()
- master2_schema_csn = topology.master2.schema.get_schema_csn()
- topology.master1.log.debug("\n\nMaster1 nsschemaCSN: %s" % master1_schema_csn)
- topology.master1.log.debug("\n\nMaster2 nsschemaCSN: %s" % master2_schema_csn)
- assert (master1_schema_csn)
- assert (master2_schema_csn)
- assert (master1_schema_csn == master2_schema_csn)
- assert (topology.master1.saved_schema_csn == master1_schema_csn)
- assert (topology.master2.saved_schema_csn == master2_schema_csn)
- def test_ticket47988_6(topology):
- '''
- Update M1 schema and trigger update M2->M1
- So M2 should learn new/extended definitions that are in M1 schema
- '''
- _header(topology, 'test_ticket47988_6')
- topology.master1.log.debug("\n\nUpdate M1 schema and an entry on M1\n")
- master1_schema_csn = topology.master1.schema.get_schema_csn()
- master2_schema_csn = topology.master2.schema.get_schema_csn()
- topology.master1.log.debug("\nBefore updating the schema on M1\n")
- topology.master1.log.debug("Master1 nsschemaCSN: %s" % master1_schema_csn)
- topology.master1.log.debug("Master2 nsschemaCSN: %s" % master2_schema_csn)
- # Here M1 should no, should check M2 schema and learn
- _do_update_schema(topology.master1, range=5999)
- master1_schema_csn = topology.master1.schema.get_schema_csn()
- master2_schema_csn = topology.master2.schema.get_schema_csn()
- topology.master1.log.debug("\nAfter updating the schema on M1\n")
- topology.master1.log.debug("Master1 nsschemaCSN: %s" % master1_schema_csn)
- topology.master1.log.debug("Master2 nsschemaCSN: %s" % master2_schema_csn)
- assert (master1_schema_csn)
- # to avoid linger effect where a replication session is reused without checking the schema
- _pause_M1_to_M2(topology)
- _resume_M1_to_M2(topology)
- #topo.master1.log.debug("\n\nSleep.... attach the debugger dse_modify")
- #time.sleep(60)
- _do_update_entry(supplier=topology.master2, consumer=topology.master1, attempts=15)
- master1_schema_csn = topology.master1.schema.get_schema_csn()
- master2_schema_csn = topology.master2.schema.get_schema_csn()
- topology.master1.log.debug("\nAfter a full replication session\n")
- topology.master1.log.debug("Master1 nsschemaCSN: %s" % master1_schema_csn)
- topology.master1.log.debug("Master2 nsschemaCSN: %s" % master2_schema_csn)
- assert (master1_schema_csn)
- assert (master2_schema_csn)
- def test_ticket47988_final(topology):
- topology.master1.delete()
- topology.master2.delete()
- log.info('Testcase PASSED')
- def run_isolated():
- '''
- run_isolated is used to run these test cases independently of a test scheduler (xunit, py.test..)
- To run isolated without py.test, you need to
- - edit this file and comment '@pytest.fixture' line before 'topology' function.
- - set the installation prefix
- - run this program
- '''
- global installation1_prefix
- global installation2_prefix
- installation1_prefix = None
- installation2_prefix = None
- topo = topology(True)
- test_ticket47988_init(topo)
- test_ticket47988_1(topo)
- test_ticket47988_2(topo)
- test_ticket47988_3(topo)
- test_ticket47988_4(topo)
- test_ticket47988_5(topo)
- test_ticket47988_6(topo)
- test_ticket47988_final(topo)
- if __name__ == '__main__':
- run_isolated()
|