ticket47819_test.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. import os
  2. import sys
  3. import time
  4. import ldap
  5. import logging
  6. import socket
  7. import pytest
  8. from lib389 import DirSrv, Entry, tools, tasks
  9. from lib389.tools import DirSrvTools
  10. from lib389._constants import *
  11. from lib389.properties import *
  12. from lib389.tasks import *
  13. from constants import *
  14. log = logging.getLogger(__name__)
  15. installation_prefix = None
  16. class TopologyStandalone(object):
  17. def __init__(self, standalone):
  18. standalone.open()
  19. self.standalone = standalone
  20. @pytest.fixture(scope="module")
  21. def topology(request):
  22. '''
  23. This fixture is used to standalone topology for the 'module'.
  24. At the beginning, It may exists a standalone instance.
  25. It may also exists a backup for the standalone instance.
  26. Principle:
  27. If standalone instance exists:
  28. restart it
  29. If backup of standalone exists:
  30. create/rebind to standalone
  31. restore standalone instance from backup
  32. else:
  33. Cleanup everything
  34. remove instance
  35. remove backup
  36. Create instance
  37. Create backup
  38. '''
  39. global installation_prefix
  40. if installation_prefix:
  41. args_instance[SER_DEPLOYED_DIR] = installation_prefix
  42. standalone = DirSrv(verbose=False)
  43. # Args for the standalone instance
  44. args_instance[SER_HOST] = HOST_STANDALONE
  45. args_instance[SER_PORT] = PORT_STANDALONE
  46. args_instance[SER_SERVERID_PROP] = SERVERID_STANDALONE
  47. args_standalone = args_instance.copy()
  48. standalone.allocate(args_standalone)
  49. # Get the status of the backups
  50. backup_standalone = standalone.checkBackupFS()
  51. # Get the status of the instance and restart it if it exists
  52. instance_standalone = standalone.exists()
  53. if instance_standalone:
  54. # assuming the instance is already stopped, just wait 5 sec max
  55. standalone.stop(timeout=5)
  56. standalone.start(timeout=60)
  57. if backup_standalone:
  58. # The backup exist, assuming it is correct
  59. # we just re-init the instance with it
  60. if not instance_standalone:
  61. standalone.create()
  62. # Used to retrieve configuration information (dbdir, confdir...)
  63. standalone.open()
  64. # restore standalone instance from backup
  65. standalone.stop(timeout=10)
  66. standalone.restoreFS(backup_standalone)
  67. standalone.start(timeout=60)
  68. else:
  69. # We should be here only in two conditions
  70. # - This is the first time a test involve standalone instance
  71. # - Something weird happened (instance/backup destroyed)
  72. # so we discard everything and recreate all
  73. # Remove the backup. So even if we have a specific backup file
  74. # (e.g backup_standalone) we clear backup that an instance may have created
  75. if backup_standalone:
  76. standalone.clearBackupFS()
  77. # Remove the instance
  78. if instance_standalone:
  79. standalone.delete()
  80. # Create the instance
  81. standalone.create()
  82. # Used to retrieve configuration information (dbdir, confdir...)
  83. standalone.open()
  84. # Time to create the backups
  85. standalone.stop(timeout=10)
  86. standalone.backupfile = standalone.backupFS()
  87. standalone.start(timeout=60)
  88. # clear the tmp directory
  89. standalone.clearTmpDir(__file__)
  90. # Here we have standalone instance up and running
  91. # Either coming from a backup recovery
  92. # or from a fresh (re)init
  93. # Time to return the topology
  94. return TopologyStandalone(standalone)
  95. def test_ticket47819(topology):
  96. """
  97. Testing precise tombstone purging:
  98. [1] Make sure "nsTombstoneCSN" is added to new tombstones
  99. [2] Make sure an import of a replication ldif adds "nsTombstoneCSN"
  100. to old tombstones
  101. [4] Test fixup task
  102. [3] Make sure tombstone purging works
  103. """
  104. log.info('Testing Ticket 47819 - Test precise tombstone purging')
  105. #
  106. # Setup Replication
  107. #
  108. log.info('Setting up replication...')
  109. topology.standalone.replica.enableReplication(suffix=DEFAULT_SUFFIX, role=REPLICAROLE_MASTER,
  110. replicaId=REPLICAID_MASTER_1)
  111. #
  112. # Part 1 create a tombstone entry and make sure nsTombstoneCSN is added
  113. #
  114. log.info('Part 1: Add and then delete an entry to create a tombstone...')
  115. try:
  116. topology.standalone.add_s(Entry(('cn=entry1,dc=example,dc=com', {
  117. 'objectclass': 'top person'.split(),
  118. 'sn': 'user',
  119. 'cn': 'entry1'})))
  120. except ldap.LDAPError, e:
  121. log.error('Failed to add entry: ' + e.message['desc'])
  122. assert False
  123. try:
  124. topology.standalone.delete_s('cn=entry1,dc=example,dc=com')
  125. except ldap.LDAPError, e:
  126. log.error('Failed to delete entry: ' + e.message['desc'])
  127. assert False
  128. log.info('Search for tombstone entries...')
  129. try:
  130. entries = topology.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE,
  131. '(&(nsTombstoneCSN=*)(objectclass=nsTombstone))')
  132. if not entries:
  133. log.fatal('Search failed to the new tombstone(nsTombstoneCSN is probably missing).')
  134. assert False
  135. except ldap.LDAPError, e:
  136. log.fatal('Search failed: ' + e.message['desc'])
  137. assert False
  138. log.info('Part 1 - passed')
  139. #
  140. # Part 2 - import ldif with tombstones missing 'nsTombstoneCSN'
  141. #
  142. # First, export the replication ldif, edit the file(remove nstombstonecsn),
  143. # and reimport it.
  144. #
  145. log.info('Part 2: Exporting replication ldif...')
  146. # Get the the full path and name for our LDIF we will be exporting
  147. ldif_file = topology.standalone.getDir(__file__, TMP_DIR) + "export.ldif"
  148. args = {EXPORT_REPL_INFO: True,
  149. TASK_WAIT: True}
  150. exportTask = Tasks(topology.standalone)
  151. try:
  152. exportTask.exportLDIF(DEFAULT_SUFFIX, None, ldif_file, args)
  153. except ValueError:
  154. assert False
  155. # open the ldif file, get the lines, then rewrite the file
  156. ldif = open(ldif_file, "r")
  157. lines = ldif.readlines()
  158. ldif.close()
  159. ldif = open(ldif_file, "w")
  160. for line in lines:
  161. if not line.lower().startswith('nstombstonecsn'):
  162. ldif.write(line)
  163. ldif.close()
  164. # import the new ldif file
  165. log.info('Import replication LDIF file...')
  166. importTask = Tasks(topology.standalone)
  167. args = {TASK_WAIT: True}
  168. try:
  169. importTask.importLDIF(DEFAULT_SUFFIX, None, ldif_file, args)
  170. os.remove(ldif_file)
  171. except ValueError:
  172. os.remove(ldif_file)
  173. assert False
  174. # Search for the tombstone again
  175. log.info('Search for tombstone entries...')
  176. try:
  177. entries = topology.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE,
  178. '(&(nsTombstoneCSN=*)(objectclass=nsTombstone))')
  179. if not entries:
  180. log.fatal('Search failed to fine the new tombstone(nsTombstoneCSN is probably missing).')
  181. assert False
  182. except ldap.LDAPError, e:
  183. log.fatal('Search failed: ' + e.message['desc'])
  184. assert False
  185. log.info('Part 2 - passed')
  186. #
  187. # Part 3 - test fixup task
  188. #
  189. log.info('Part 4: test the fixup task')
  190. # Run fixup task using the strip option. This removes nsTombstoneCSN
  191. # so we can test if the fixup task works.
  192. args = {TASK_WAIT: True,
  193. TASK_TOMB_STRIP: True}
  194. fixupTombTask = Tasks(topology.standalone)
  195. try:
  196. fixupTombTask.fixupTombstones(DEFAULT_BENAME, args)
  197. except:
  198. assert False
  199. # Search for tombstones with nsTombstoneCSN - better not find any
  200. log.info('Search for tombstone entries...')
  201. try:
  202. entries = topology.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE,
  203. '(&(nsTombstoneCSN=*)(objectclass=nsTombstone))')
  204. if entries:
  205. log.fatal('Search found tombstones with nsTombstoneCSN')
  206. assert False
  207. except ldap.LDAPError, e:
  208. log.fatal('Search failed: ' + e.message['desc'])
  209. assert False
  210. # Now run the fixup task
  211. args = {TASK_WAIT: True}
  212. fixupTombTask = Tasks(topology.standalone)
  213. try:
  214. fixupTombTask.fixupTombstones(DEFAULT_BENAME, args)
  215. except:
  216. assert False
  217. # Search for tombstones with nsTombstoneCSN - better find some
  218. log.info('Search for tombstone entries...')
  219. try:
  220. entries = topology.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE,
  221. '(&(nsTombstoneCSN=*)(objectclass=nsTombstone))')
  222. if not entries:
  223. log.fatal('Search did not find any fixed-up tombstones')
  224. assert False
  225. except ldap.LDAPError, e:
  226. log.fatal('Search failed: ' + e.message['desc'])
  227. assert False
  228. log.info('Part 3 - passed')
  229. #
  230. # Part 4 - Test tombstone purging
  231. #
  232. log.info('Part 4: test tombstone purging...')
  233. args = {REPLICA_PRECISE_PURGING: 'on',
  234. REPLICA_PURGE_DELAY: '5',
  235. REPLICA_PURGE_INTERVAL: '5'}
  236. try:
  237. topology.standalone.replica.setProperties(DEFAULT_SUFFIX, None, None, args)
  238. except:
  239. log.fatal('Failed to configure replica')
  240. assert False
  241. # Wait for the interval to pass
  242. log.info('Wait for tombstone purge interval to pass...')
  243. time.sleep(6)
  244. # Add an entry to trigger replication
  245. log.info('Perform an update to help trigger tombstone purging...')
  246. try:
  247. topology.standalone.add_s(Entry(('cn=test_entry,dc=example,dc=com', {
  248. 'objectclass': 'top person'.split(),
  249. 'sn': 'user',
  250. 'cn': 'entry1'})))
  251. except ldap.LDAPError, e:
  252. log.error('Failed to add entry: ' + e.message['desc'])
  253. assert False
  254. # Wait for the interval to pass again
  255. log.info('Wait for tombstone purge interval to pass again...')
  256. time.sleep(10)
  257. # search for tombstones, there should be none
  258. log.info('Search for tombstone entries...')
  259. try:
  260. entries = topology.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE,
  261. '(&(nsTombstoneCSN=*)(objectclass=nsTombstone))')
  262. if entries:
  263. log.fatal('Search unexpectedly found tombstones')
  264. assert False
  265. except ldap.LDAPError, e:
  266. log.fatal('Search failed: ' + e.message['desc'])
  267. assert False
  268. log.info('Part 4 - passed')
  269. #
  270. # If we got here we passed!
  271. #
  272. log.info('Ticket47819 Test - Passed')
  273. def test_ticket47819_final(topology):
  274. topology.standalone.delete()
  275. def run_isolated():
  276. '''
  277. run_isolated is used to run these test cases independently of a test scheduler (xunit, py.test..)
  278. To run isolated without py.test, you need to
  279. - edit this file and comment '@pytest.fixture' line before 'topology' function.
  280. - set the installation prefix
  281. - run this program
  282. '''
  283. global installation_prefix
  284. installation_prefix = None
  285. topo = topology(True)
  286. test_ticket47819(topo)
  287. test_ticket47819_final(topo)
  288. if __name__ == '__main__':
  289. run_isolated()