ticket47573_test.py 11 KB

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