ticket47462_test.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. import os
  2. import sys
  3. import time
  4. import ldap
  5. import logging
  6. import socket
  7. import time
  8. import logging
  9. import pytest
  10. import re
  11. from lib389 import DirSrv, Entry, tools
  12. from lib389.tools import DirSrvTools
  13. from lib389._constants import *
  14. from lib389.properties import *
  15. from constants import *
  16. from lib389._constants import *
  17. logging.getLogger(__name__).setLevel(logging.DEBUG)
  18. log = logging.getLogger(__name__)
  19. #
  20. # important part. We can deploy Master1 and Master2 on different versions
  21. #
  22. installation1_prefix = None
  23. installation2_prefix = None
  24. DES_PLUGIN = 'cn=DES,cn=Password Storage Schemes,cn=plugins,cn=config'
  25. AES_PLUGIN = 'cn=AES,cn=Password Storage Schemes,cn=plugins,cn=config'
  26. MMR_PLUGIN = 'cn=Multimaster Replication Plugin,cn=plugins,cn=config'
  27. AGMT_DN = ''
  28. USER_DN = 'cn=test_user,' + DEFAULT_SUFFIX
  29. USER1_DN = 'cn=test_user1,' + DEFAULT_SUFFIX
  30. TEST_REPL_DN = 'cn=test repl,' + DEFAULT_SUFFIX
  31. class TopologyMaster1Master2(object):
  32. def __init__(self, master1, master2):
  33. master1.open()
  34. self.master1 = master1
  35. master2.open()
  36. self.master2 = master2
  37. @pytest.fixture(scope="module")
  38. def topology(request):
  39. '''
  40. This fixture is used to create a replicated topology for the 'module'.
  41. The replicated topology is MASTER1 <-> Master2.
  42. At the beginning, It may exists a master2 instance and/or a master2 instance.
  43. It may also exists a backup for the master1 and/or the master2.
  44. Principle:
  45. If master1 instance exists:
  46. restart it
  47. If master2 instance exists:
  48. restart it
  49. If backup of master1 AND backup of master2 exists:
  50. create or rebind to master1
  51. create or rebind to master2
  52. restore master1 from backup
  53. restore master2 from backup
  54. else:
  55. Cleanup everything
  56. remove instances
  57. remove backups
  58. Create instances
  59. Initialize replication
  60. Create backups
  61. '''
  62. global installation1_prefix
  63. global installation2_prefix
  64. # allocate master1 on a given deployement
  65. master1 = DirSrv(verbose=False)
  66. if installation1_prefix:
  67. args_instance[SER_DEPLOYED_DIR] = installation1_prefix
  68. # Args for the master1 instance
  69. args_instance[SER_HOST] = HOST_MASTER_1
  70. args_instance[SER_PORT] = PORT_MASTER_1
  71. args_instance[SER_SERVERID_PROP] = SERVERID_MASTER_1
  72. args_master = args_instance.copy()
  73. master1.allocate(args_master)
  74. # allocate master1 on a given deployement
  75. master2 = DirSrv(verbose=False)
  76. if installation2_prefix:
  77. args_instance[SER_DEPLOYED_DIR] = installation2_prefix
  78. # Args for the consumer instance
  79. args_instance[SER_HOST] = HOST_MASTER_2
  80. args_instance[SER_PORT] = PORT_MASTER_2
  81. args_instance[SER_SERVERID_PROP] = SERVERID_MASTER_2
  82. args_master = args_instance.copy()
  83. master2.allocate(args_master)
  84. # Get the status of the backups
  85. backup_master1 = master1.checkBackupFS()
  86. backup_master2 = master2.checkBackupFS()
  87. # Get the status of the instance and restart it if it exists
  88. instance_master1 = master1.exists()
  89. if instance_master1:
  90. master1.stop(timeout=10)
  91. master1.start(timeout=10)
  92. instance_master2 = master2.exists()
  93. if instance_master2:
  94. master2.stop(timeout=10)
  95. master2.start(timeout=10)
  96. if backup_master1 and backup_master2:
  97. # The backups exist, assuming they are correct
  98. # we just re-init the instances with them
  99. if not instance_master1:
  100. master1.create()
  101. # Used to retrieve configuration information (dbdir, confdir...)
  102. master1.open()
  103. if not instance_master2:
  104. master2.create()
  105. # Used to retrieve configuration information (dbdir, confdir...)
  106. master2.open()
  107. # restore master1 from backup
  108. master1.stop(timeout=10)
  109. master1.restoreFS(backup_master1)
  110. master1.start(timeout=10)
  111. # restore master2 from backup
  112. master2.stop(timeout=10)
  113. master2.restoreFS(backup_master2)
  114. master2.start(timeout=10)
  115. else:
  116. # We should be here only in two conditions
  117. # - This is the first time a test involve master-consumer
  118. # so we need to create everything
  119. # - Something weird happened (instance/backup destroyed)
  120. # so we discard everything and recreate all
  121. # Remove all the backups. So even if we have a specific backup file
  122. # (e.g backup_master) we clear all backups that an instance my have created
  123. if backup_master1:
  124. master1.clearBackupFS()
  125. if backup_master2:
  126. master2.clearBackupFS()
  127. # Remove all the instances
  128. if instance_master1:
  129. master1.delete()
  130. if instance_master2:
  131. master2.delete()
  132. # Create the instances
  133. master1.create()
  134. master1.open()
  135. master2.create()
  136. master2.open()
  137. #
  138. # Now prepare the Master-Consumer topology
  139. #
  140. # First Enable replication
  141. master1.replica.enableReplication(suffix=SUFFIX, role=REPLICAROLE_MASTER, replicaId=REPLICAID_MASTER_1)
  142. master2.replica.enableReplication(suffix=SUFFIX, role=REPLICAROLE_MASTER, replicaId=REPLICAID_MASTER_2)
  143. # Initialize the supplier->consumer
  144. properties = {RA_NAME: r'meTo_$host:$port',
  145. RA_BINDDN: defaultProperties[REPLICATION_BIND_DN],
  146. RA_BINDPW: defaultProperties[REPLICATION_BIND_PW],
  147. RA_METHOD: defaultProperties[REPLICATION_BIND_METHOD],
  148. RA_TRANSPORT_PROT: defaultProperties[REPLICATION_TRANSPORT]}
  149. AGMT_DN = master1.agreement.create(suffix=SUFFIX, host=master2.host, port=master2.port, properties=properties)
  150. master1.agreement
  151. if not AGMT_DN:
  152. log.fatal("Fail to create a replica agreement")
  153. sys.exit(1)
  154. log.debug("%s created" % AGMT_DN)
  155. properties = {RA_NAME: r'meTo_$host:$port',
  156. RA_BINDDN: defaultProperties[REPLICATION_BIND_DN],
  157. RA_BINDPW: defaultProperties[REPLICATION_BIND_PW],
  158. RA_METHOD: defaultProperties[REPLICATION_BIND_METHOD],
  159. RA_TRANSPORT_PROT: defaultProperties[REPLICATION_TRANSPORT]}
  160. master2.agreement.create(suffix=DEFAULT_SUFFIX, host=master1.host, port=master1.port, properties=properties)
  161. master1.agreement.init(SUFFIX, HOST_MASTER_2, PORT_MASTER_2)
  162. master1.waitForReplInit(AGMT_DN)
  163. # Check replication is working fine
  164. master1.add_s(Entry((TEST_REPL_DN, {'objectclass': "top person".split(),
  165. 'sn': 'test_repl',
  166. 'cn': 'test_repl'})))
  167. loop = 0
  168. while loop <= 10:
  169. try:
  170. ent = master2.getEntry(TEST_REPL_DN, ldap.SCOPE_BASE, "(objectclass=*)")
  171. break
  172. except ldap.NO_SUCH_OBJECT:
  173. time.sleep(1)
  174. loop += 1
  175. if not ent:
  176. log.fatal('Replication is not working!')
  177. assert False
  178. # Time to create the backups
  179. master1.stop(timeout=10)
  180. master1.backupfile = master1.backupFS()
  181. master1.start(timeout=10)
  182. master2.stop(timeout=10)
  183. master2.backupfile = master2.backupFS()
  184. master2.start(timeout=10)
  185. # clear the tmp directory
  186. master1.clearTmpDir(__file__)
  187. #
  188. # Here we have two instances master and consumer
  189. # with replication working. Either coming from a backup recovery
  190. # or from a fresh (re)init
  191. # Time to return the topology
  192. return TopologyMaster1Master2(master1, master2)
  193. def test_ticket47462(topology):
  194. """
  195. Test that AES properly replaces DES during an update/restart, and that
  196. replication also works correctly.
  197. """
  198. #
  199. # First set config as if it's an older version. Set DES to use libdes-plugin,
  200. # MMR to depend on DES, delete the existing AES plugin, and set a DES password
  201. # for the replication agreement.
  202. #
  203. #
  204. # Add an extra attribute to the DES plugin args
  205. #
  206. try:
  207. topology.master1.modify_s(DES_PLUGIN,
  208. [(ldap.MOD_REPLACE, 'nsslapd-pluginEnabled', 'on')])
  209. except ldap.LDAPError, e:
  210. log.fatal('Failed to enable DES plugin, error: ' + e.message['desc'])
  211. assert False
  212. try:
  213. topology.master1.modify_s(DES_PLUGIN,
  214. [(ldap.MOD_ADD, 'nsslapd-pluginarg2', 'description')])
  215. except ldap.LDAPError, e:
  216. log.fatal('Failed to reset DES plugin, error: ' + e.message['desc'])
  217. assert False
  218. try:
  219. topology.master1.modify_s(MMR_PLUGIN,
  220. [(ldap.MOD_DELETE, 'nsslapd-plugin-depends-on-named', 'AES')])
  221. except ldap.NO_SUCH_ATTRIBUTE:
  222. pass
  223. except ldap.LDAPError, e:
  224. log.fatal('Failed to reset MMR plugin, error: ' + e.message['desc'])
  225. assert False
  226. #
  227. # Delete the AES plugin
  228. #
  229. try:
  230. topology.master1.delete_s(AES_PLUGIN)
  231. except ldap.NO_SUCH_OBJECT:
  232. pass
  233. except ldap.LDAPError, e:
  234. log.fatal('Failed to delete AES plugin, error: ' + e.message['desc'])
  235. assert False
  236. # restart the server so we must use DES plugin
  237. topology.master1.restart(timeout=10)
  238. #
  239. # Get the agmt dn, and set the password
  240. #
  241. try:
  242. entry = topology.master1.search_s('cn=config', ldap.SCOPE_SUBTREE, 'objectclass=nsDS5ReplicationAgreement')
  243. if entry:
  244. agmt_dn = entry[0].dn
  245. log.info('Found agmt dn (%s)' % agmt_dn)
  246. else:
  247. log.fatal('No replication agreements!')
  248. assert False
  249. except ldap.LDAPError, e:
  250. log.fatal('Failed to search for replica credentials: ' + e.message['desc'])
  251. assert False
  252. try:
  253. properties = {RA_BINDPW: "password"}
  254. topology.master1.agreement.setProperties(None, agmt_dn, None, properties)
  255. log.info('Successfully modified replication agreement')
  256. except ValueError:
  257. log.error('Failed to update replica agreement: ' + AGMT_DN)
  258. assert False
  259. #
  260. # Check replication works with the new DES password
  261. #
  262. try:
  263. topology.master1.add_s(Entry((USER1_DN,
  264. {'objectclass': "top person".split(),
  265. 'sn': 'sn',
  266. 'cn': 'test_user'})))
  267. loop = 0
  268. ent = None
  269. while loop <= 10:
  270. try:
  271. ent = topology.master2.getEntry(USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)")
  272. break
  273. except ldap.NO_SUCH_OBJECT:
  274. time.sleep(1)
  275. loop += 1
  276. if not ent:
  277. log.fatal('Replication test failed fo user1!')
  278. assert False
  279. else:
  280. log.info('Replication test passed')
  281. except ldap.LDAPError, e:
  282. log.fatal('Failed to add test user: ' + e.message['desc'])
  283. assert False
  284. #
  285. # Run the upgrade...
  286. #
  287. topology.master1.upgrade('online')
  288. topology.master1.restart(timeout=10)
  289. topology.master2.restart(timeout=10)
  290. #
  291. # Check that the restart converted existing DES credentials
  292. #
  293. try:
  294. entry = topology.master1.search_s('cn=config', ldap.SCOPE_SUBTREE, 'nsDS5ReplicaCredentials=*')
  295. if entry:
  296. val = entry[0].getValue('nsDS5ReplicaCredentials')
  297. if val.startswith('{AES-'):
  298. log.info('The DES credentials have been converted to AES')
  299. else:
  300. log.fatal('Failed to convert credentials from DES to AES!')
  301. assert False
  302. else:
  303. log.fatal('Failed to find any entries with nsDS5ReplicaCredentials ')
  304. assert False
  305. except ldap.LDAPError, e:
  306. log.fatal('Failed to search for replica credentials: ' + e.message['desc'])
  307. assert False
  308. #
  309. # Check that the AES plugin exists, and has all the attributes listed in DES plugin.
  310. # The attributes might not be in the expected order so check all the attributes.
  311. #
  312. try:
  313. entry = topology.master1.search_s(AES_PLUGIN, ldap.SCOPE_BASE, 'objectclass=*')
  314. if not entry[0].hasValue('nsslapd-pluginarg0', 'description') and \
  315. not entry[0].hasValue('nsslapd-pluginarg1', 'description') and \
  316. not entry[0].hasValue('nsslapd-pluginarg2', 'description'):
  317. log.fatal('The AES plugin did not have the DES attribute copied over correctly')
  318. assert False
  319. else:
  320. log.info('The AES plugin was correctly setup')
  321. except ldap.LDAPError, e:
  322. log.fatal('Failed to find AES plugin: ' + e.message['desc'])
  323. assert False
  324. #
  325. # Check that the MMR plugin was updated
  326. #
  327. try:
  328. entry = topology.master1.search_s(MMR_PLUGIN, ldap.SCOPE_BASE, 'objectclass=*')
  329. if not entry[0].hasValue('nsslapd-plugin-depends-on-named', 'AES'):
  330. log.fatal('The MMR Plugin was not correctly updated')
  331. assert False
  332. else:
  333. log.info('The MMR plugin was correctly updated')
  334. except ldap.LDAPError, e:
  335. log.fatal('Failed to find AES plugin: ' + e.message['desc'])
  336. assert False
  337. #
  338. # Check that the DES plugin was correctly updated
  339. #
  340. try:
  341. entry = topology.master1.search_s(DES_PLUGIN, ldap.SCOPE_BASE, 'objectclass=*')
  342. if not entry[0].hasValue('nsslapd-pluginPath', 'libpbe-plugin'):
  343. log.fatal('The DES Plugin was not correctly updated')
  344. assert False
  345. else:
  346. log.info('The DES plugin was correctly updated')
  347. except ldap.LDAPError, e:
  348. log.fatal('Failed to find AES plugin: ' + e.message['desc'])
  349. assert False
  350. #
  351. # Check replication one last time
  352. #
  353. try:
  354. topology.master1.add_s(Entry((USER_DN,
  355. {'objectclass': "top person".split(),
  356. 'sn': 'sn',
  357. 'cn': 'test_user'})))
  358. loop = 0
  359. ent = None
  360. while loop <= 10:
  361. try:
  362. ent = topology.master2.getEntry(USER_DN, ldap.SCOPE_BASE, "(objectclass=*)")
  363. break
  364. except ldap.NO_SUCH_OBJECT:
  365. time.sleep(1)
  366. loop += 1
  367. if not ent:
  368. log.fatal('Replication test failed!')
  369. assert False
  370. else:
  371. log.info('Replication test passed')
  372. except ldap.LDAPError, e:
  373. log.fatal('Failed to add test user: ' + e.message['desc'])
  374. assert False
  375. #
  376. # If we got here the test passed
  377. #
  378. log.info('Test PASSED')
  379. def test_ticket47462_final(topology):
  380. topology.master1.delete()
  381. topology.master2.delete()
  382. def run_isolated():
  383. '''
  384. run_isolated is used to run these test cases independently of a test scheduler (xunit, py.test..)
  385. To run isolated without py.test, you need to
  386. - edit this file and comment '@pytest.fixture' line before 'topology' function.
  387. - set the installation prefix
  388. - run this program
  389. '''
  390. global installation1_prefix
  391. global installation2_prefix
  392. installation1_prefix = None
  393. installation2_prefix = None
  394. topo = topology(True)
  395. test_ticket47462(topo)
  396. test_ticket47462_final(topo)
  397. if __name__ == '__main__':
  398. run_isolated()