ticket47573_test.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  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. master.add_s(Entry((TEST_REPL_DN, {
  151. 'objectclass': "top person".split(),
  152. 'sn': 'test_repl',
  153. 'cn': 'test_repl'})))
  154. loop = 0
  155. ent = None
  156. while loop <= 10:
  157. try:
  158. ent = consumer.getEntry(TEST_REPL_DN, ldap.SCOPE_BASE, "(objectclass=*)")
  159. break
  160. except ldap.NO_SUCH_OBJECT:
  161. time.sleep(1)
  162. loop += 1
  163. if ent is None:
  164. assert False
  165. # clear the tmp directory
  166. master.clearTmpDir(__file__)
  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" % (topology, topology.master, topology.consumer))
  175. # the test case will check if a warning message is logged in the
  176. # error log of the supplier
  177. topology.master.errorlog_file = open(topology.master.errlog, "r")
  178. # This entry will be used to trigger attempt of schema push
  179. topology.master.add_s(Entry((ENTRY_DN, {
  180. 'objectclass': "top person".split(),
  181. 'sn': 'test_entry',
  182. 'cn': 'test_entry'})))
  183. def test_ticket47573_one(topology):
  184. """
  185. Summary: Add a custom OC with MUST and MAY
  186. MUST = postalAddress $ preferredLocale
  187. MAY = telexNumber $ postalCode $ street
  188. Final state
  189. - supplier +OCwithMayAttr
  190. - consumer +OCwithMayAttr
  191. """
  192. log.debug("test_ticket47573_one topology %r (master %r, consumer %r" % (topology, topology.master, topology.consumer))
  193. # update the schema of the supplier so that it is a superset of
  194. # consumer. Schema should be pushed
  195. new_oc = _oc_definition(2, 'OCwithMayAttr',
  196. must = MUST_OLD,
  197. may = MAY_OLD)
  198. topology.master.schema.add_schema('objectClasses', new_oc)
  199. trigger_schema_push(topology)
  200. master_schema_csn = topology.master.schema.get_schema_csn()
  201. consumer_schema_csn = topology.consumer.schema.get_schema_csn()
  202. # Check the schemaCSN was updated on the consumer
  203. log.debug("test_ticket47573_one master_schema_csn=%s", master_schema_csn)
  204. log.debug("ctest_ticket47573_one onsumer_schema_csn=%s", consumer_schema_csn)
  205. assert master_schema_csn == consumer_schema_csn
  206. # Check the error log of the supplier does not contain an error
  207. regex = re.compile("must not be overwritten \(set replication log for additional info\)")
  208. res = pattern_errorlog(topology.master.errorlog_file, regex)
  209. assert res is None
  210. def test_ticket47573_two(topology):
  211. """
  212. Summary: Change OCwithMayAttr to move a MAY attribute to a MUST attribute
  213. Final state
  214. - supplier OCwithMayAttr updated
  215. - consumer OCwithMayAttr updated
  216. """
  217. # Update the objectclass so that a MAY attribute is moved to MUST attribute
  218. mod_OC(topology.master, 2, 'OCwithMayAttr', old_must=MUST_OLD, new_must=MUST_NEW, old_may=MAY_OLD, new_may=MAY_NEW)
  219. # now push the scheam
  220. trigger_schema_push(topology)
  221. master_schema_csn = topology.master.schema.get_schema_csn()
  222. consumer_schema_csn = topology.consumer.schema.get_schema_csn()
  223. # Check the schemaCSN was NOT updated on the consumer
  224. log.debug("test_ticket47573_two master_schema_csn=%s", master_schema_csn)
  225. log.debug("test_ticket47573_two consumer_schema_csn=%s", consumer_schema_csn)
  226. assert master_schema_csn == consumer_schema_csn
  227. # Check the error log of the supplier does not contain an error
  228. regex = re.compile("must not be overwritten \(set replication log for additional info\)")
  229. res = pattern_errorlog(topology.master.errorlog_file, regex)
  230. assert res is None
  231. def test_ticket47573_three(topology):
  232. '''
  233. Create a entry with OCwithMayAttr OC
  234. '''
  235. # Check replication is working fine
  236. dn = "cn=ticket47573, %s" % SUFFIX
  237. topology.master.add_s(Entry((dn,
  238. {'objectclass': "top person OCwithMayAttr".split(),
  239. 'sn': 'test_repl',
  240. 'cn': 'test_repl',
  241. 'postalAddress': 'here',
  242. 'preferredLocale': 'en',
  243. 'telexNumber': '12$us$21',
  244. 'postalCode': '54321'})))
  245. loop = 0
  246. ent = None
  247. while loop <= 10:
  248. try:
  249. ent = topology.consumer.getEntry(dn, ldap.SCOPE_BASE, "(objectclass=*)")
  250. break
  251. except ldap.NO_SUCH_OBJECT:
  252. time.sleep(1)
  253. loop += 1
  254. if ent is None:
  255. assert False
  256. def test_ticket47573_final(topology):
  257. topology.master.delete()
  258. topology.consumer.delete()
  259. log.info('Testcase PASSED')
  260. def run_isolated():
  261. '''
  262. run_isolated is used to run these test cases independently of a test scheduler (xunit, py.test..)
  263. To run isolated without py.test, you need to
  264. - edit this file and comment '@pytest.fixture' line before 'topology' function.
  265. - set the installation prefix
  266. - run this program
  267. '''
  268. global installation_prefix
  269. installation_prefix = None
  270. topo = topology(True)
  271. test_ticket47573_init(topo)
  272. test_ticket47573_one(topo)
  273. test_ticket47573_two(topo)
  274. test_ticket47573_three(topo)
  275. test_ticket47573_final(topo)
  276. if __name__ == '__main__':
  277. run_isolated()