Browse Source

Ticket 47988: test case

Thierry bordaz (tbordaz) 11 years ago
parent
commit
868ac8b009

BIN
dirsrvtests/data/ticket47988/schema_ipa3.3.tar.gz


BIN
dirsrvtests/data/ticket47988/schema_ipa4.1.tar.gz


+ 576 - 0
dirsrvtests/tickets/ticket47988_test.py

@@ -0,0 +1,576 @@
+'''
+Created on Nov 7, 2013
+
+@author: tbordaz
+'''
+import os
+import sys
+import time
+import ldap
+import logging
+import socket
+import time
+import logging
+import pytest
+import re
+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 *
+from lib389._constants import *
+from constants 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
+
+
[email protected](scope="module")
+def topology(request):
+    '''
+        This fixture is used to create a replicated topology for the 'module'.
+        The replicated topology is MASTER1 <-> Master2.
+        At the beginning, It may exists a master2 instance and/or a master2 instance.
+        It may also exists a backup for the master1 and/or the master2.
+    
+        Principle:
+            If master1 instance exists:
+                restart it
+            If master2 instance exists:
+                restart it
+            If backup of master1 AND backup of master2 exists:
+                create or rebind to master1
+                create or rebind to master2
+
+                restore master1 from backup
+                restore master2 from backup
+            else:
+                Cleanup everything
+                    remove instances
+                    remove backups
+                Create instances
+                Initialize replication
+                Create backups
+    '''
+    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 backups
+    backup_master1 = master1.checkBackupFS()
+    backup_master2 = master2.checkBackupFS()
+    
+    # Get the status of the instance and restart it if it exists
+    instance_master1   = master1.exists()
+    if instance_master1:
+        master1.stop(timeout=10)
+        master1.start(timeout=10)
+        
+    instance_master2 = master2.exists()
+    if instance_master2:
+        master2.stop(timeout=10)
+        master2.start(timeout=10)
+    
+    if backup_master1 and backup_master2:
+        # The backups exist, assuming they are correct 
+        # we just re-init the instances with them
+        if not instance_master1:
+            master1.create()
+            # Used to retrieve configuration information (dbdir, confdir...)
+            master1.open()
+        
+        if not instance_master2:
+            master2.create()
+            # Used to retrieve configuration information (dbdir, confdir...)
+            master2.open()
+        
+        # restore master1 from backup
+        master1.stop(timeout=10)
+        master1.restoreFS(backup_master1)
+        master1.start(timeout=10)
+        
+        # restore master2 from backup
+        master2.stop(timeout=10)
+        master2.restoreFS(backup_master2)
+        master2.start(timeout=10)
+    else:
+        # We should be here only in two conditions
+        #      - This is the first time a test involve master-consumer
+        #        so we need to create everything
+        #      - Something weird happened (instance/backup destroyed)
+        #        so we discard everything and recreate all
+        
+        # Remove all the backups. So even if we have a specific backup file
+        # (e.g backup_master) we clear all backups that an instance my have created
+        if backup_master1:
+            master1.clearBackupFS()
+        if backup_master2:
+            master2.clearBackupFS()
+        
+        # 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
+        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
+                
+        # Time to create the backups
+        master1.stop(timeout=10)
+        master1.backupfile = master1.backupFS()
+        master1.start(timeout=10)
+        
+        master2.stop(timeout=10)
+        master2.backupfile = master2.backupFS()
+        master2.start(timeout=10)
+    
+    # 
+    # Here we have two instances master and consumer
+    # with replication working. Either coming from a backup recovery
+    # or from a fresh (re)init
+    # Time to return the topology
+    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)
+    
+    here = os.getcwd()
+    
+    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()   
+
+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()
+