ticket47573_test.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. # --- BEGIN COPYRIGHT BLOCK ---
  2. # Copyright (C) 2015 Red Hat, Inc.
  3. # All rights reserved.
  4. #
  5. # License: GPL (version 3 or any later version).
  6. # See LICENSE for details.
  7. # --- END COPYRIGHT BLOCK ---
  8. #
  9. '''
  10. Created on Nov 7, 2013
  11. @author: tbordaz
  12. '''
  13. import os
  14. import sys
  15. import time
  16. import ldap
  17. import logging
  18. import pytest
  19. import re
  20. from lib389 import DirSrv, Entry, tools
  21. from lib389.tools import DirSrvTools
  22. from lib389._constants import *
  23. from lib389.properties import *
  24. logging.getLogger(__name__).setLevel(logging.DEBUG)
  25. log = logging.getLogger(__name__)
  26. TEST_REPL_DN = "cn=test_repl, %s" % SUFFIX
  27. ENTRY_DN = "cn=test_entry, %s" % SUFFIX
  28. MUST_OLD = "(postalAddress $ preferredLocale $ telexNumber)"
  29. MAY_OLD = "(postalCode $ street)"
  30. MUST_NEW = "(postalAddress $ preferredLocale)"
  31. MAY_NEW = "(telexNumber $ postalCode $ street)"
  32. class TopologyMasterConsumer(object):
  33. def __init__(self, master, consumer):
  34. master.open()
  35. self.master = master
  36. consumer.open()
  37. self.consumer = consumer
  38. def pattern_errorlog(file, log_pattern):
  39. try:
  40. pattern_errorlog.last_pos += 1
  41. except AttributeError:
  42. pattern_errorlog.last_pos = 0
  43. found = None
  44. log.debug("_pattern_errorlog: start at offset %d" % pattern_errorlog.last_pos)
  45. file.seek(pattern_errorlog.last_pos)
  46. # Use a while true iteration because 'for line in file: hit a
  47. # python bug that break file.tell()
  48. while True:
  49. line = file.readline()
  50. log.debug("_pattern_errorlog: [%d] %s" % (file.tell(), line))
  51. found = log_pattern.search(line)
  52. if ((line == '') or (found)):
  53. break
  54. log.debug("_pattern_errorlog: end at offset %d" % file.tell())
  55. pattern_errorlog.last_pos = file.tell()
  56. return found
  57. def _oc_definition(oid_ext, name, must=None, may=None):
  58. oid = "1.2.3.4.5.6.7.8.9.10.%d" % oid_ext
  59. desc = 'To test ticket 47573'
  60. sup = 'person'
  61. if not must:
  62. must = MUST_OLD
  63. if not may:
  64. may = MAY_OLD
  65. new_oc = "( %s NAME '%s' DESC '%s' SUP %s AUXILIARY MUST %s MAY %s )" % (oid, name, desc, sup, must, may)
  66. return new_oc
  67. def add_OC(instance, oid_ext, name):
  68. new_oc = _oc_definition(oid_ext, name)
  69. instance.schema.add_schema('objectClasses', new_oc)
  70. def mod_OC(instance, oid_ext, name, old_must=None, old_may=None, new_must=None, new_may=None):
  71. old_oc = _oc_definition(oid_ext, name, old_must, old_may)
  72. new_oc = _oc_definition(oid_ext, name, new_must, new_may)
  73. instance.schema.del_schema('objectClasses', old_oc)
  74. instance.schema.add_schema('objectClasses', new_oc)
  75. def trigger_schema_push(topology):
  76. """
  77. It triggers an update on the supplier. This will start a replication
  78. session and a schema push
  79. """
  80. try:
  81. trigger_schema_push.value += 1
  82. except AttributeError:
  83. trigger_schema_push.value = 1
  84. replace = [(ldap.MOD_REPLACE, 'telephonenumber', str(trigger_schema_push.value))]
  85. topology.master.modify_s(ENTRY_DN, replace)
  86. # wait 10 seconds that the update is replicated
  87. loop = 0
  88. while loop <= 10:
  89. try:
  90. ent = topology.consumer.getEntry(ENTRY_DN, ldap.SCOPE_BASE, "(objectclass=*)", ['telephonenumber'])
  91. val = ent.telephonenumber or "0"
  92. if int(val) == trigger_schema_push.value:
  93. return
  94. # the expected value is not yet replicated. try again
  95. time.sleep(1)
  96. loop += 1
  97. log.debug("trigger_schema_push: receive %s (expected %d)" % (val, trigger_schema_push.value))
  98. except ldap.NO_SUCH_OBJECT:
  99. time.sleep(1)
  100. loop += 1
  101. @pytest.fixture(scope="module")
  102. def topology(request):
  103. '''
  104. This fixture is used to create a replicated topology for the 'module'.
  105. The replicated topology is MASTER -> Consumer.
  106. '''
  107. master = DirSrv(verbose=False)
  108. consumer = DirSrv(verbose=False)
  109. # Args for the master instance
  110. args_instance[SER_HOST] = HOST_MASTER_1
  111. args_instance[SER_PORT] = PORT_MASTER_1
  112. args_instance[SER_SERVERID_PROP] = SERVERID_MASTER_1
  113. args_master = args_instance.copy()
  114. master.allocate(args_master)
  115. # Args for the consumer instance
  116. args_instance[SER_HOST] = HOST_CONSUMER_1
  117. args_instance[SER_PORT] = PORT_CONSUMER_1
  118. args_instance[SER_SERVERID_PROP] = SERVERID_CONSUMER_1
  119. args_consumer = args_instance.copy()
  120. consumer.allocate(args_consumer)
  121. # Get the status of the instance
  122. instance_master = master.exists()
  123. instance_consumer = consumer.exists()
  124. # Remove all the instances
  125. if instance_master:
  126. master.delete()
  127. if instance_consumer:
  128. consumer.delete()
  129. # Create the instances
  130. master.create()
  131. master.open()
  132. consumer.create()
  133. consumer.open()
  134. #
  135. # Now prepare the Master-Consumer topology
  136. #
  137. # First Enable replication
  138. master.replica.enableReplication(suffix=SUFFIX, role=REPLICAROLE_MASTER, replicaId=REPLICAID_MASTER_1)
  139. consumer.replica.enableReplication(suffix=SUFFIX, role=REPLICAROLE_CONSUMER)
  140. # Initialize the supplier->consumer
  141. properties = {RA_NAME: r'meTo_$host:$port',
  142. RA_BINDDN: defaultProperties[REPLICATION_BIND_DN],
  143. RA_BINDPW: defaultProperties[REPLICATION_BIND_PW],
  144. RA_METHOD: defaultProperties[REPLICATION_BIND_METHOD],
  145. RA_TRANSPORT_PROT: defaultProperties[REPLICATION_TRANSPORT]}
  146. repl_agreement = master.agreement.create(suffix=SUFFIX, host=consumer.host, port=consumer.port, properties=properties)
  147. if not repl_agreement:
  148. log.fatal("Fail to create a replica agreement")
  149. sys.exit(1)
  150. log.debug("%s created" % repl_agreement)
  151. master.agreement.init(SUFFIX, HOST_CONSUMER_1, PORT_CONSUMER_1)
  152. master.waitForReplInit(repl_agreement)
  153. # Check replication is working fine
  154. if master.testReplication(DEFAULT_SUFFIX, consumer):
  155. log.info('Replication is working.')
  156. else:
  157. log.fatal('Replication is not working.')
  158. assert False
  159. def fin():
  160. master.delete()
  161. consumer.delete()
  162. request.addfinalizer(fin)
  163. # Here we have two instances master and consumer
  164. # with replication working.
  165. return TopologyMasterConsumer(master, consumer)
  166. def test_ticket47573_init(topology):
  167. """
  168. Initialize the test environment
  169. """
  170. log.debug("test_ticket47573_init topology %r (master %r, consumer %r" %
  171. (topology, topology.master, topology.consumer))
  172. # the test case will check if a warning message is logged in the
  173. # error log of the supplier
  174. topology.master.errorlog_file = open(topology.master.errlog, "r")
  175. # This entry will be used to trigger attempt of schema push
  176. topology.master.add_s(Entry((ENTRY_DN, {
  177. 'objectclass': "top person".split(),
  178. 'sn': 'test_entry',
  179. 'cn': 'test_entry'})))
  180. def test_ticket47573_one(topology):
  181. """
  182. Summary: Add a custom OC with MUST and MAY
  183. MUST = postalAddress $ preferredLocale
  184. MAY = telexNumber $ postalCode $ street
  185. Final state
  186. - supplier +OCwithMayAttr
  187. - consumer +OCwithMayAttr
  188. """
  189. log.debug("test_ticket47573_one topology %r (master %r, consumer %r" % (topology, topology.master, topology.consumer))
  190. # update the schema of the supplier so that it is a superset of
  191. # consumer. Schema should be pushed
  192. new_oc = _oc_definition(2, 'OCwithMayAttr',
  193. must = MUST_OLD,
  194. may = MAY_OLD)
  195. topology.master.schema.add_schema('objectClasses', new_oc)
  196. trigger_schema_push(topology)
  197. master_schema_csn = topology.master.schema.get_schema_csn()
  198. consumer_schema_csn = topology.consumer.schema.get_schema_csn()
  199. # Check the schemaCSN was updated on the consumer
  200. log.debug("test_ticket47573_one master_schema_csn=%s", master_schema_csn)
  201. log.debug("ctest_ticket47573_one onsumer_schema_csn=%s", consumer_schema_csn)
  202. assert master_schema_csn == consumer_schema_csn
  203. # Check the error log of the supplier does not contain an error
  204. regex = re.compile("must not be overwritten \(set replication log for additional info\)")
  205. res = pattern_errorlog(topology.master.errorlog_file, regex)
  206. assert res is None
  207. def test_ticket47573_two(topology):
  208. """
  209. Summary: Change OCwithMayAttr to move a MAY attribute to a MUST attribute
  210. Final state
  211. - supplier OCwithMayAttr updated
  212. - consumer OCwithMayAttr updated
  213. """
  214. # Update the objectclass so that a MAY attribute is moved to MUST attribute
  215. mod_OC(topology.master, 2, 'OCwithMayAttr', old_must=MUST_OLD, new_must=MUST_NEW, old_may=MAY_OLD, new_may=MAY_NEW)
  216. # now push the scheam
  217. trigger_schema_push(topology)
  218. master_schema_csn = topology.master.schema.get_schema_csn()
  219. consumer_schema_csn = topology.consumer.schema.get_schema_csn()
  220. # Check the schemaCSN was NOT updated on the consumer
  221. log.debug("test_ticket47573_two master_schema_csn=%s", master_schema_csn)
  222. log.debug("test_ticket47573_two consumer_schema_csn=%s", consumer_schema_csn)
  223. assert master_schema_csn == consumer_schema_csn
  224. # Check the error log of the supplier does not contain an error
  225. regex = re.compile("must not be overwritten \(set replication log for additional info\)")
  226. res = pattern_errorlog(topology.master.errorlog_file, regex)
  227. assert res is None
  228. def test_ticket47573_three(topology):
  229. '''
  230. Create a entry with OCwithMayAttr OC
  231. '''
  232. # Check replication is working fine
  233. dn = "cn=ticket47573, %s" % SUFFIX
  234. topology.master.add_s(Entry((dn,
  235. {'objectclass': "top person OCwithMayAttr".split(),
  236. 'sn': 'test_repl',
  237. 'cn': 'test_repl',
  238. 'postalAddress': 'here',
  239. 'preferredLocale': 'en',
  240. 'telexNumber': '12$us$21',
  241. 'postalCode': '54321'})))
  242. loop = 0
  243. ent = None
  244. while loop <= 10:
  245. try:
  246. ent = topology.consumer.getEntry(dn, ldap.SCOPE_BASE, "(objectclass=*)")
  247. break
  248. except ldap.NO_SUCH_OBJECT:
  249. time.sleep(1)
  250. loop += 1
  251. if ent is None:
  252. assert False
  253. log.info('Testcase PASSED')
  254. if __name__ == '__main__':
  255. # Run isolated
  256. # -s for DEBUG mode
  257. CURRENT_FILE = os.path.realpath(__file__)
  258. pytest.main("-s %s" % CURRENT_FILE)