ticket48266_test.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. import pytest
  2. from lib389.tasks import *
  3. from lib389.utils import *
  4. from lib389.topologies import topology_m2
  5. logging.getLogger(__name__).setLevel(logging.DEBUG)
  6. log = logging.getLogger(__name__)
  7. NEW_ACCOUNT = "new_account"
  8. MAX_ACCOUNTS = 20
  9. def pattern_accesslog(file, log_pattern):
  10. try:
  11. pattern_accesslog.last_pos += 1
  12. except AttributeError:
  13. pattern_accesslog.last_pos = 0
  14. found = None
  15. file.seek(pattern_accesslog.last_pos)
  16. # Use a while true iteration because 'for line in file: hit a
  17. # python bug that break file.tell()
  18. while True:
  19. line = file.readline()
  20. found = log_pattern.search(line)
  21. if ((line == '') or (found)):
  22. break
  23. pattern_accesslog.last_pos = file.tell()
  24. if found:
  25. return line
  26. else:
  27. return None
  28. @pytest.fixture(scope="module")
  29. def entries(topology_m2):
  30. # add dummy entries in the staging DIT
  31. for cpt in range(MAX_ACCOUNTS):
  32. name = "%s%d" % (NEW_ACCOUNT, cpt)
  33. topology_m2.ms["master1"].add_s(Entry(("cn=%s,%s" % (name, SUFFIX), {
  34. 'objectclass': "top person".split(),
  35. 'sn': name,
  36. 'cn': name})))
  37. mod = [(ldap.MOD_REPLACE, 'nsslapd-errorlog-level', '8192'),
  38. (ldap.MOD_REPLACE, 'nsslapd-accesslog-level', str(256 + 4))]
  39. topology_m2.ms["master1"].modify_s(DN_CONFIG, mod)
  40. topology_m2.ms["master2"].modify_s(DN_CONFIG, mod)
  41. def test_ticket48266_fractional(topology_m2, entries):
  42. ents = topology_m2.ms["master1"].agreement.list(suffix=SUFFIX)
  43. assert len(ents) == 1
  44. mod = [(ldap.MOD_REPLACE, 'nsDS5ReplicatedAttributeList', ['(objectclass=*) $ EXCLUDE telephonenumber']),
  45. (ldap.MOD_REPLACE, 'nsds5ReplicaStripAttrs', ['modifiersname modifytimestamp'])]
  46. ents = topology_m2.ms["master1"].agreement.list(suffix=SUFFIX)
  47. assert len(ents) == 1
  48. m1_m2_agmt = ents[0].dn
  49. topology_m2.ms["master1"].modify_s(ents[0].dn, mod)
  50. ents = topology_m2.ms["master2"].agreement.list(suffix=SUFFIX)
  51. assert len(ents) == 1
  52. topology_m2.ms["master2"].modify_s(ents[0].dn, mod)
  53. topology_m2.ms["master1"].restart(timeout=10)
  54. topology_m2.ms["master2"].restart(timeout=10)
  55. topology_m2.ms["master1"].agreement.init(SUFFIX, HOST_MASTER_2, PORT_MASTER_2)
  56. topology_m2.ms["master1"].waitForReplInit(m1_m2_agmt)
  57. def test_ticket48266_check_repl_desc(topology_m2, entries):
  58. name = "cn=%s1,%s" % (NEW_ACCOUNT, SUFFIX)
  59. value = 'check repl. description'
  60. mod = [(ldap.MOD_REPLACE, 'description', value)]
  61. topology_m2.ms["master1"].modify_s(name, mod)
  62. loop = 0
  63. while loop <= 10:
  64. ent = topology_m2.ms["master2"].getEntry(name, ldap.SCOPE_BASE, "(objectclass=*)")
  65. if ent.hasAttr('description') and ent.getValue('description') == value:
  66. break
  67. time.sleep(1)
  68. loop += 1
  69. assert loop <= 10
  70. # will use this CSN as a starting point on error log
  71. # after this is one 'Skipped' then the first csn _get_first_not_replicated_csn
  72. # should no longer be Skipped in the error log
  73. def _get_last_not_replicated_csn(topology_m2):
  74. name = "cn=%s5,%s" % (NEW_ACCOUNT, SUFFIX)
  75. # read the first CSN that will not be replicated
  76. mod = [(ldap.MOD_REPLACE, 'telephonenumber', str(123456))]
  77. topology_m2.ms["master1"].modify_s(name, mod)
  78. msgid = topology_m2.ms["master1"].search_ext(name, ldap.SCOPE_SUBTREE, 'objectclass=*', ['nscpentrywsi'])
  79. rtype, rdata, rmsgid = topology_m2.ms["master1"].result2(msgid)
  80. attrs = None
  81. for dn, raw_attrs in rdata:
  82. topology_m2.ms["master1"].log.info("dn: %s" % dn)
  83. if 'nscpentrywsi' in raw_attrs:
  84. attrs = raw_attrs['nscpentrywsi']
  85. assert attrs
  86. for attr in attrs:
  87. if attr.lower().startswith('telephonenumber'):
  88. break
  89. assert attr
  90. # now retrieve the CSN of the operation we are looking for
  91. csn = None
  92. topology_m2.ms["master1"].stop(timeout=10)
  93. file_obj = open(topology_m2.ms["master1"].accesslog, "r")
  94. # First the conn/op of the operation
  95. regex = re.compile("MOD dn=\"%s\"" % name)
  96. found_op = pattern_accesslog(file_obj, regex)
  97. assert found_op
  98. if found_op:
  99. conn_op_pattern = '.* (conn=[0-9]* op=[0-9]*) .*'
  100. conn_op_re = re.compile(conn_op_pattern)
  101. conn_op_match = conn_op_re.match(found_op)
  102. conn_op = conn_op_match.group(1)
  103. # now the related CSN
  104. regex = re.compile("%s RESULT" % conn_op)
  105. found_result = pattern_accesslog(file_obj, regex)
  106. csn_pattern = '.* csn=(.*)'
  107. csn_re = re.compile(csn_pattern)
  108. csn_match = csn_re.match(found_result)
  109. csn = csn_match.group(1)
  110. topology_m2.ms["master1"].start(timeout=10)
  111. return csn
  112. def _get_first_not_replicated_csn(topology_m2):
  113. name = "cn=%s2,%s" % (NEW_ACCOUNT, SUFFIX)
  114. # read the first CSN that will not be replicated
  115. mod = [(ldap.MOD_REPLACE, 'telephonenumber', str(123456))]
  116. topology_m2.ms["master1"].modify_s(name, mod)
  117. msgid = topology_m2.ms["master1"].search_ext(name, ldap.SCOPE_SUBTREE, 'objectclass=*', ['nscpentrywsi'])
  118. rtype, rdata, rmsgid = topology_m2.ms["master1"].result2(msgid)
  119. attrs = None
  120. for dn, raw_attrs in rdata:
  121. topology_m2.ms["master1"].log.info("dn: %s" % dn)
  122. if 'nscpentrywsi' in raw_attrs:
  123. attrs = raw_attrs['nscpentrywsi']
  124. assert attrs
  125. for attr in attrs:
  126. if attr.lower().startswith('telephonenumber'):
  127. break
  128. assert attr
  129. # now retrieve the CSN of the operation we are looking for
  130. csn = None
  131. topology_m2.ms["master1"].stop(timeout=10)
  132. file_obj = open(topology_m2.ms["master1"].accesslog, "r")
  133. # First the conn/op of the operation
  134. regex = re.compile("MOD dn=\"%s\"" % name)
  135. found_op = pattern_accesslog(file_obj, regex)
  136. assert found_op
  137. if found_op:
  138. conn_op_pattern = '.* (conn=[0-9]* op=[0-9]*) .*'
  139. conn_op_re = re.compile(conn_op_pattern)
  140. conn_op_match = conn_op_re.match(found_op)
  141. conn_op = conn_op_match.group(1)
  142. # now the related CSN
  143. regex = re.compile("%s RESULT" % conn_op)
  144. found_result = pattern_accesslog(file_obj, regex)
  145. csn_pattern = '.* csn=(.*)'
  146. csn_re = re.compile(csn_pattern)
  147. csn_match = csn_re.match(found_result)
  148. csn = csn_match.group(1)
  149. topology_m2.ms["master1"].start(timeout=10)
  150. return csn
  151. def _count_full_session(topology_m2):
  152. #
  153. # compute the number of 'No more updates'
  154. #
  155. file_obj = open(topology_m2.ms["master1"].errlog, "r")
  156. # pattern to find
  157. pattern = ".*No more updates to send.*"
  158. regex = re.compile(pattern)
  159. no_more_updates = 0
  160. # check initiation number of 'No more updates
  161. while True:
  162. line = file_obj.readline()
  163. found = regex.search(line)
  164. if (found):
  165. no_more_updates = no_more_updates + 1
  166. if (line == ''):
  167. break
  168. file_obj.close()
  169. return no_more_updates
  170. def test_ticket48266_count_csn_evaluation(topology_m2, entries):
  171. ents = topology_m2.ms["master1"].agreement.list(suffix=SUFFIX)
  172. assert len(ents) == 1
  173. first_csn = _get_first_not_replicated_csn(topology_m2)
  174. name = "cn=%s3,%s" % (NEW_ACCOUNT, SUFFIX)
  175. NB_SESSION = 102
  176. no_more_update_cnt = _count_full_session(topology_m2)
  177. topology_m2.ms["master1"].agreement.pause(ents[0].dn)
  178. # now do a set of updates that will NOT be replicated
  179. for telNumber in range(NB_SESSION):
  180. mod = [(ldap.MOD_REPLACE, 'telephonenumber', str(telNumber))]
  181. topology_m2.ms["master1"].modify_s(name, mod)
  182. topology_m2.ms["master1"].agreement.resume(ents[0].dn)
  183. # let's wait all replication session complete
  184. MAX_LOOP = 10
  185. cnt = 0
  186. current_no_more_update = _count_full_session(topology_m2)
  187. while (current_no_more_update == no_more_update_cnt):
  188. cnt = cnt + 1
  189. if (cnt > MAX_LOOP):
  190. break
  191. time.sleep(5)
  192. current_no_more_update = _count_full_session(topology_m2)
  193. log.info('after %d MODs we have completed %d replication sessions' % (
  194. NB_SESSION, (current_no_more_update - no_more_update_cnt)))
  195. no_more_update_cnt = current_no_more_update
  196. # At this point, with the fix a dummy update was made BUT may be not sent it
  197. # make sure it was sent so that the consumer CSN will be updated
  198. last_csn = _get_last_not_replicated_csn(topology_m2)
  199. # let's wait all replication session complete
  200. MAX_LOOP = 10
  201. cnt = 0
  202. current_no_more_update = _count_full_session(topology_m2)
  203. while (current_no_more_update == no_more_update_cnt):
  204. cnt = cnt + 1
  205. if (cnt > MAX_LOOP):
  206. break
  207. time.sleep(5)
  208. current_no_more_update = _count_full_session(topology_m2)
  209. log.info('This MODs %s triggered the send of the dummy update completed %d replication sessions' % (
  210. last_csn, (current_no_more_update - no_more_update_cnt)))
  211. no_more_update_cnt = current_no_more_update
  212. # so we should no longer see the first_csn in the log
  213. # Let's create a new csn (last_csn) and check there is no longer first_csn
  214. topology_m2.ms["master1"].agreement.pause(ents[0].dn)
  215. last_csn = _get_last_not_replicated_csn(topology_m2)
  216. topology_m2.ms["master1"].agreement.resume(ents[0].dn)
  217. # let's wait for the session to complete
  218. MAX_LOOP = 10
  219. cnt = 0
  220. while (current_no_more_update == no_more_update_cnt):
  221. cnt = cnt + 1
  222. if (cnt > MAX_LOOP):
  223. break
  224. time.sleep(5)
  225. current_no_more_update = _count_full_session(topology_m2)
  226. log.info('This MODs %s completed in %d replication sessions, should be sent without evaluating %s' % (
  227. last_csn, (current_no_more_update - no_more_update_cnt), first_csn))
  228. no_more_update_cnt = current_no_more_update
  229. # Now determine how many times we have skipped 'csn'
  230. # no need to stop the server to check the error log
  231. file_obj = open(topology_m2.ms["master1"].errlog, "r")
  232. # find where the last_csn operation was processed
  233. pattern = ".*ruv_add_csn_inprogress: successfully inserted csn %s.*" % last_csn
  234. regex = re.compile(pattern)
  235. cnt = 0
  236. while True:
  237. line = file_obj.readline()
  238. found = regex.search(line)
  239. if ((line == '') or (found)):
  240. break
  241. if (found):
  242. log.info('last operation was found at %d' % file_obj.tell())
  243. log.info(line)
  244. log.info('Now check the we can not find the first csn %s in the log' % first_csn)
  245. pattern = ".*Skipping update operation.*CSN %s.*" % first_csn
  246. regex = re.compile(pattern)
  247. found = False
  248. while True:
  249. line = file_obj.readline()
  250. found = regex.search(line)
  251. if ((line == '') or (found)):
  252. break
  253. if (found):
  254. log.info('Unexpected found %s' % line)
  255. assert not found
  256. if __name__ == '__main__':
  257. # Run isolated
  258. # -s for DEBUG mode
  259. CURRENT_FILE = os.path.realpath(__file__)
  260. pytest.main("-s %s" % CURRENT_FILE)