ticket47787_test.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. # --- BEGIN COPYRIGHT BLOCK ---
  2. # Copyright (C) 2016 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 April 14, 2014
  11. @author: tbordaz
  12. '''
  13. import logging
  14. import re
  15. import time
  16. import ldap
  17. import pytest
  18. from lib389 import Entry
  19. from lib389._constants import *
  20. from lib389.topologies import topology_m2
  21. logging.getLogger(__name__).setLevel(logging.DEBUG)
  22. log = logging.getLogger(__name__)
  23. # set this flag to False so that it will assert on failure _status_entry_both_server
  24. DEBUG_FLAG = False
  25. TEST_REPL_DN = "cn=test_repl, %s" % SUFFIX
  26. STAGING_CN = "staged user"
  27. PRODUCTION_CN = "accounts"
  28. EXCEPT_CN = "excepts"
  29. STAGING_DN = "cn=%s,%s" % (STAGING_CN, SUFFIX)
  30. PRODUCTION_DN = "cn=%s,%s" % (PRODUCTION_CN, SUFFIX)
  31. PROD_EXCEPT_DN = "cn=%s,%s" % (EXCEPT_CN, PRODUCTION_DN)
  32. STAGING_PATTERN = "cn=%s*,%s" % (STAGING_CN[:2], SUFFIX)
  33. PRODUCTION_PATTERN = "cn=%s*,%s" % (PRODUCTION_CN[:2], SUFFIX)
  34. BAD_STAGING_PATTERN = "cn=bad*,%s" % (SUFFIX)
  35. BAD_PRODUCTION_PATTERN = "cn=bad*,%s" % (SUFFIX)
  36. BIND_CN = "bind_entry"
  37. BIND_DN = "cn=%s,%s" % (BIND_CN, SUFFIX)
  38. BIND_PW = "password"
  39. NEW_ACCOUNT = "new_account"
  40. MAX_ACCOUNTS = 20
  41. CONFIG_MODDN_ACI_ATTR = "nsslapd-moddn-aci"
  42. def _bind_manager(server):
  43. server.log.info("Bind as %s " % DN_DM)
  44. server.simple_bind_s(DN_DM, PASSWORD)
  45. def _bind_normal(server):
  46. server.log.info("Bind as %s " % BIND_DN)
  47. server.simple_bind_s(BIND_DN, BIND_PW)
  48. def _header(topology_m2, label):
  49. topology_m2.ms["master1"].log.info("\n\n###############################################")
  50. topology_m2.ms["master1"].log.info("#######")
  51. topology_m2.ms["master1"].log.info("####### %s" % label)
  52. topology_m2.ms["master1"].log.info("#######")
  53. topology_m2.ms["master1"].log.info("###############################################")
  54. def _status_entry_both_server(topology_m2, name=None, desc=None, debug=True):
  55. if not name:
  56. return
  57. topology_m2.ms["master1"].log.info("\n\n######################### Tombstone on M1 ######################\n")
  58. attr = 'description'
  59. found = False
  60. attempt = 0
  61. while not found and attempt < 10:
  62. ent_m1 = _find_tombstone(topology_m2.ms["master1"], SUFFIX, 'sn', name)
  63. if attr in ent_m1.getAttrs():
  64. found = True
  65. else:
  66. time.sleep(1)
  67. attempt = attempt + 1
  68. assert ent_m1
  69. topology_m2.ms["master1"].log.info("\n\n######################### Tombstone on M2 ######################\n")
  70. ent_m2 = _find_tombstone(topology_m2.ms["master2"], SUFFIX, 'sn', name)
  71. assert ent_m2
  72. topology_m2.ms["master1"].log.info("\n\n######################### Description ######################\n%s\n" % desc)
  73. topology_m2.ms["master1"].log.info("M1 only\n")
  74. for attr in ent_m1.getAttrs():
  75. if not debug:
  76. assert attr in ent_m2.getAttrs()
  77. if not attr in ent_m2.getAttrs():
  78. topology_m2.ms["master1"].log.info(" %s" % attr)
  79. for val in ent_m1.getValues(attr):
  80. topology_m2.ms["master1"].log.info(" %s" % val)
  81. topology_m2.ms["master1"].log.info("M2 only\n")
  82. for attr in ent_m2.getAttrs():
  83. if not debug:
  84. assert attr in ent_m1.getAttrs()
  85. if not attr in ent_m1.getAttrs():
  86. topology_m2.ms["master1"].log.info(" %s" % attr)
  87. for val in ent_m2.getValues(attr):
  88. topology_m2.ms["master1"].log.info(" %s" % val)
  89. topology_m2.ms["master1"].log.info("M1 differs M2\n")
  90. if not debug:
  91. assert ent_m1.dn == ent_m2.dn
  92. if ent_m1.dn != ent_m2.dn:
  93. topology_m2.ms["master1"].log.info(" M1[dn] = %s\n M2[dn] = %s" % (ent_m1.dn, ent_m2.dn))
  94. for attr1 in ent_m1.getAttrs():
  95. if attr1 in ent_m2.getAttrs():
  96. for val1 in ent_m1.getValues(attr1):
  97. found = False
  98. for val2 in ent_m2.getValues(attr1):
  99. if val1 == val2:
  100. found = True
  101. break
  102. if not debug:
  103. assert found
  104. if not found:
  105. topology_m2.ms["master1"].log.info(" M1[%s] = %s" % (attr1, val1))
  106. for attr2 in ent_m2.getAttrs():
  107. if attr2 in ent_m1.getAttrs():
  108. for val2 in ent_m2.getValues(attr2):
  109. found = False
  110. for val1 in ent_m1.getValues(attr2):
  111. if val2 == val1:
  112. found = True
  113. break
  114. if not debug:
  115. assert found
  116. if not found:
  117. topology_m2.ms["master1"].log.info(" M2[%s] = %s" % (attr2, val2))
  118. def _pause_RAs(topology_m2):
  119. topology_m2.ms["master1"].log.info("\n\n######################### Pause RA M1<->M2 ######################\n")
  120. ents = topology_m2.ms["master1"].agreement.list(suffix=SUFFIX)
  121. assert len(ents) == 1
  122. topology_m2.ms["master1"].agreement.pause(ents[0].dn)
  123. ents = topology_m2.ms["master2"].agreement.list(suffix=SUFFIX)
  124. assert len(ents) == 1
  125. topology_m2.ms["master2"].agreement.pause(ents[0].dn)
  126. def _resume_RAs(topology_m2):
  127. topology_m2.ms["master1"].log.info("\n\n######################### resume RA M1<->M2 ######################\n")
  128. ents = topology_m2.ms["master1"].agreement.list(suffix=SUFFIX)
  129. assert len(ents) == 1
  130. topology_m2.ms["master1"].agreement.resume(ents[0].dn)
  131. ents = topology_m2.ms["master2"].agreement.list(suffix=SUFFIX)
  132. assert len(ents) == 1
  133. topology_m2.ms["master2"].agreement.resume(ents[0].dn)
  134. def _find_tombstone(instance, base, attr, value):
  135. #
  136. # we can not use a filter with a (&(objeclass=nsTombstone)(sn=name)) because
  137. # tombstone are not index in 'sn' so 'sn=name' will return NULL
  138. # and even if tombstone are indexed for objectclass the '&' will set
  139. # the candidate list to NULL
  140. #
  141. filt = '(objectclass=%s)' % REPLICA_OC_TOMBSTONE
  142. ents = instance.search_s(base, ldap.SCOPE_SUBTREE, filt)
  143. # found = False
  144. for ent in ents:
  145. if ent.hasAttr(attr):
  146. for val in ent.getValues(attr):
  147. if val == value:
  148. instance.log.debug("tombstone found: %r" % ent)
  149. return ent
  150. return None
  151. def _delete_entry(instance, entry_dn, name):
  152. instance.log.info("\n\n######################### DELETE %s (M1) ######################\n" % name)
  153. # delete the entry
  154. instance.delete_s(entry_dn)
  155. assert _find_tombstone(instance, SUFFIX, 'sn', name) is not None
  156. def _mod_entry(instance, entry_dn, attr, value):
  157. instance.log.info("\n\n######################### MOD %s (M2) ######################\n" % entry_dn)
  158. mod = [(ldap.MOD_REPLACE, attr, value)]
  159. instance.modify_s(entry_dn, mod)
  160. def _modrdn_entry(instance=None, entry_dn=None, new_rdn=None, del_old=0, new_superior=None):
  161. assert instance is not None
  162. assert entry_dn is not None
  163. if not new_rdn:
  164. pattern = 'cn=(.*),(.*)'
  165. rdnre = re.compile(pattern)
  166. match = rdnre.match(entry_dn)
  167. old_value = match.group(1)
  168. new_rdn_val = "%s_modrdn" % old_value
  169. new_rdn = "cn=%s" % new_rdn_val
  170. instance.log.info("\n\n######################### MODRDN %s (M2) ######################\n" % new_rdn)
  171. if new_superior:
  172. instance.rename_s(entry_dn, new_rdn, newsuperior=new_superior, delold=del_old)
  173. else:
  174. instance.rename_s(entry_dn, new_rdn, delold=del_old)
  175. def _check_entry_exists(instance, entry_dn):
  176. loop = 0
  177. ent = None
  178. while loop <= 10:
  179. try:
  180. ent = instance.getEntry(entry_dn, ldap.SCOPE_BASE, "(objectclass=*)")
  181. break
  182. except ldap.NO_SUCH_OBJECT:
  183. time.sleep(1)
  184. loop += 1
  185. if ent is None:
  186. assert False
  187. def _check_mod_received(instance, base, filt, attr, value):
  188. instance.log.info(
  189. "\n\n######################### Check MOD replicated on %s ######################\n" % instance.serverid)
  190. loop = 0
  191. while loop <= 10:
  192. ent = instance.getEntry(base, ldap.SCOPE_SUBTREE, filt)
  193. if ent.hasAttr(attr) and ent.getValue(attr) == value:
  194. break
  195. time.sleep(1)
  196. loop += 1
  197. assert loop <= 10
  198. def _check_replication(topology_m2, entry_dn):
  199. # prepare the filter to retrieve the entry
  200. filt = entry_dn.split(',')[0]
  201. topology_m2.ms["master1"].log.info("\n######################### Check replicat M1->M2 ######################\n")
  202. loop = 0
  203. while loop <= 10:
  204. attr = 'description'
  205. value = 'test_value_%d' % loop
  206. mod = [(ldap.MOD_REPLACE, attr, value)]
  207. topology_m2.ms["master1"].modify_s(entry_dn, mod)
  208. _check_mod_received(topology_m2.ms["master2"], SUFFIX, filt, attr, value)
  209. loop += 1
  210. topology_m2.ms["master1"].log.info("\n######################### Check replicat M2->M1 ######################\n")
  211. loop = 0
  212. while loop <= 10:
  213. attr = 'description'
  214. value = 'test_value_%d' % loop
  215. mod = [(ldap.MOD_REPLACE, attr, value)]
  216. topology_m2.ms["master2"].modify_s(entry_dn, mod)
  217. _check_mod_received(topology_m2.ms["master1"], SUFFIX, filt, attr, value)
  218. loop += 1
  219. def test_ticket47787_init(topology_m2):
  220. """
  221. Creates
  222. - a staging DIT
  223. - a production DIT
  224. - add accounts in staging DIT
  225. """
  226. topology_m2.ms["master1"].log.info("\n\n######################### INITIALIZATION ######################\n")
  227. # entry used to bind with
  228. topology_m2.ms["master1"].log.info("Add %s" % BIND_DN)
  229. topology_m2.ms["master1"].add_s(Entry((BIND_DN, {
  230. 'objectclass': "top person".split(),
  231. 'sn': BIND_CN,
  232. 'cn': BIND_CN,
  233. 'userpassword': BIND_PW})))
  234. # DIT for staging
  235. topology_m2.ms["master1"].log.info("Add %s" % STAGING_DN)
  236. topology_m2.ms["master1"].add_s(Entry((STAGING_DN, {
  237. 'objectclass': "top organizationalRole".split(),
  238. 'cn': STAGING_CN,
  239. 'description': "staging DIT"})))
  240. # DIT for production
  241. topology_m2.ms["master1"].log.info("Add %s" % PRODUCTION_DN)
  242. topology_m2.ms["master1"].add_s(Entry((PRODUCTION_DN, {
  243. 'objectclass': "top organizationalRole".split(),
  244. 'cn': PRODUCTION_CN,
  245. 'description': "production DIT"})))
  246. # enable replication error logging
  247. mod = [(ldap.MOD_REPLACE, 'nsslapd-errorlog-level', '8192')]
  248. topology_m2.ms["master1"].modify_s(DN_CONFIG, mod)
  249. topology_m2.ms["master2"].modify_s(DN_CONFIG, mod)
  250. # add dummy entries in the staging DIT
  251. for cpt in range(MAX_ACCOUNTS):
  252. name = "%s%d" % (NEW_ACCOUNT, cpt)
  253. topology_m2.ms["master1"].add_s(Entry(("cn=%s,%s" % (name, STAGING_DN), {
  254. 'objectclass': "top person".split(),
  255. 'sn': name,
  256. 'cn': name})))
  257. def test_ticket47787_2(topology_m2):
  258. '''
  259. Disable replication so that updates are not replicated
  260. Delete an entry on M1. Modrdn it on M2 (chg rdn + delold=0 + same superior).
  261. update a test entry on M2
  262. Reenable the RA.
  263. checks that entry was deleted on M2 (with the modified RDN)
  264. checks that test entry was replicated on M1 (replication M2->M1 not broken by modrdn)
  265. '''
  266. _header(topology_m2, "test_ticket47787_2")
  267. _bind_manager(topology_m2.ms["master1"])
  268. _bind_manager(topology_m2.ms["master2"])
  269. # entry to test the replication is still working
  270. name = "%s%d" % (NEW_ACCOUNT, MAX_ACCOUNTS - 1)
  271. test_rdn = "cn=%s" % (name)
  272. testentry_dn = "%s,%s" % (test_rdn, STAGING_DN)
  273. name = "%s%d" % (NEW_ACCOUNT, MAX_ACCOUNTS - 2)
  274. test2_rdn = "cn=%s" % (name)
  275. testentry2_dn = "%s,%s" % (test2_rdn, STAGING_DN)
  276. # value of updates to test the replication both ways
  277. attr = 'description'
  278. value = 'test_ticket47787_2'
  279. # entry for the modrdn
  280. name = "%s%d" % (NEW_ACCOUNT, 1)
  281. rdn = "cn=%s" % (name)
  282. entry_dn = "%s,%s" % (rdn, STAGING_DN)
  283. # created on M1, wait the entry exists on M2
  284. _check_entry_exists(topology_m2.ms["master2"], entry_dn)
  285. _check_entry_exists(topology_m2.ms["master2"], testentry_dn)
  286. _pause_RAs(topology_m2)
  287. # Delete 'entry_dn' on M1.
  288. # dummy update is only have a first CSN before the DEL
  289. # else the DEL will be in min_csn RUV and make diagnostic a bit more complex
  290. _mod_entry(topology_m2.ms["master1"], testentry2_dn, attr, 'dummy')
  291. _delete_entry(topology_m2.ms["master1"], entry_dn, name)
  292. _mod_entry(topology_m2.ms["master1"], testentry2_dn, attr, value)
  293. time.sleep(1) # important to have MOD.csn != DEL.csn
  294. # MOD 'entry_dn' on M1.
  295. # dummy update is only have a first CSN before the MOD entry_dn
  296. # else the DEL will be in min_csn RUV and make diagnostic a bit more complex
  297. _mod_entry(topology_m2.ms["master2"], testentry_dn, attr, 'dummy')
  298. _mod_entry(topology_m2.ms["master2"], entry_dn, attr, value)
  299. _mod_entry(topology_m2.ms["master2"], testentry_dn, attr, value)
  300. _resume_RAs(topology_m2)
  301. topology_m2.ms["master1"].log.info(
  302. "\n\n######################### Check DEL replicated on M2 ######################\n")
  303. loop = 0
  304. while loop <= 10:
  305. ent = _find_tombstone(topology_m2.ms["master2"], SUFFIX, 'sn', name)
  306. if ent:
  307. break
  308. time.sleep(1)
  309. loop += 1
  310. assert loop <= 10
  311. assert ent
  312. # the following checks are not necessary
  313. # as this bug is only for failing replicated MOD (entry_dn) on M1
  314. # _check_mod_received(topology_m2.ms["master1"], SUFFIX, "(%s)" % (test_rdn), attr, value)
  315. # _check_mod_received(topology_m2.ms["master2"], SUFFIX, "(%s)" % (test2_rdn), attr, value)
  316. #
  317. # _check_replication(topology_m2, testentry_dn)
  318. _status_entry_both_server(topology_m2, name=name, desc="DEL M1 - MOD M2", debug=DEBUG_FLAG)
  319. topology_m2.ms["master1"].log.info(
  320. "\n\n######################### Check MOD replicated on M1 ######################\n")
  321. loop = 0
  322. while loop <= 10:
  323. ent = _find_tombstone(topology_m2.ms["master1"], SUFFIX, 'sn', name)
  324. if ent:
  325. break
  326. time.sleep(1)
  327. loop += 1
  328. assert loop <= 10
  329. assert ent
  330. assert ent.hasAttr(attr)
  331. assert ent.getValue(attr) == value
  332. if __name__ == '__main__':
  333. # Run isolated
  334. # -s for DEBUG mode
  335. CURRENT_FILE = os.path.realpath(__file__)
  336. pytest.main("-s %s" % CURRENT_FILE)