ticket47573_test.py 11 KB

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