ticket47721_test.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  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 socket
  11. import time
  12. import logging
  13. import pytest
  14. import re
  15. from lib389 import DirSrv, Entry, tools
  16. from lib389.tools import DirSrvTools
  17. from lib389._constants import *
  18. from lib389.properties import *
  19. from constants import *
  20. from lib389._constants import REPLICAROLE_MASTER
  21. logging.getLogger(__name__).setLevel(logging.DEBUG)
  22. log = logging.getLogger(__name__)
  23. #
  24. # important part. We can deploy Master1 and Master2 on different versions
  25. #
  26. installation1_prefix = None
  27. installation2_prefix = None
  28. SCHEMA_DN = "cn=schema"
  29. TEST_REPL_DN = "cn=test_repl, %s" % SUFFIX
  30. OC_NAME = 'OCticket47721'
  31. OC_OID_EXT = 2
  32. MUST = "(postalAddress $ postalCode)"
  33. MAY = "(member $ street)"
  34. OC2_NAME = 'OC2ticket47721'
  35. OC2_OID_EXT = 3
  36. MUST_2 = "(postalAddress $ postalCode)"
  37. MAY_2 = "(member $ street)"
  38. REPL_SCHEMA_POLICY_CONSUMER = "cn=consumerUpdatePolicy,cn=replSchema,cn=config"
  39. REPL_SCHEMA_POLICY_SUPPLIER = "cn=supplierUpdatePolicy,cn=replSchema,cn=config"
  40. OTHER_NAME = 'other_entry'
  41. MAX_OTHERS = 10
  42. BIND_NAME = 'bind_entry'
  43. BIND_DN = 'cn=%s, %s' % (BIND_NAME, SUFFIX)
  44. BIND_PW = 'password'
  45. ENTRY_NAME = 'test_entry'
  46. ENTRY_DN = 'cn=%s, %s' % (ENTRY_NAME, SUFFIX)
  47. ENTRY_OC = "top person %s" % OC_NAME
  48. BASE_OID = "1.2.3.4.5.6.7.8.9.10"
  49. def _add_custom_at_definition(name='ATticket47721'):
  50. new_at = "( %s-oid NAME '%s' DESC 'test AT ticket 47721' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN ( 'Test 47721' 'user defined' ) )" % (name, name)
  51. return new_at
  52. def _chg_std_at_defintion():
  53. new_at = "( 2.16.840.1.113730.3.1.569 NAME 'cosPriority' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 X-ORIGIN 'Netscape Directory Server' )"
  54. return new_at
  55. def _add_custom_oc_defintion(name='OCticket47721'):
  56. new_oc = "( %s-oid NAME '%s' DESC 'An group of related automount objects' SUP top STRUCTURAL MUST ou X-ORIGIN 'draft-howard-rfc2307bis' )" % (name, name)
  57. return new_oc
  58. def _chg_std_oc_defintion():
  59. new_oc = "( 5.3.6.1.1.1.2.0 NAME 'trustAccount' DESC 'Sets trust accounts information' SUP top AUXILIARY MUST trustModel MAY ( accessTo $ ou ) X-ORIGIN 'nss_ldap/pam_ldap' )"
  60. return new_oc
  61. class TopologyMaster1Master2(object):
  62. def __init__(self, master1, master2):
  63. master1.open()
  64. self.master1 = master1
  65. master2.open()
  66. self.master2 = master2
  67. @pytest.fixture(scope="module")
  68. def topology(request):
  69. '''
  70. This fixture is used to create a replicated topology for the 'module'.
  71. The replicated topology is MASTER1 <-> Master2.
  72. At the beginning, It may exists a master2 instance and/or a master2 instance.
  73. It may also exists a backup for the master1 and/or the master2.
  74. Principle:
  75. If master1 instance exists:
  76. restart it
  77. If master2 instance exists:
  78. restart it
  79. If backup of master1 AND backup of master2 exists:
  80. create or rebind to master1
  81. create or rebind to master2
  82. restore master1 from backup
  83. restore master2 from backup
  84. else:
  85. Cleanup everything
  86. remove instances
  87. remove backups
  88. Create instances
  89. Initialize replication
  90. Create backups
  91. '''
  92. global installation1_prefix
  93. global installation2_prefix
  94. # allocate master1 on a given deployement
  95. master1 = DirSrv(verbose=False)
  96. if installation1_prefix:
  97. args_instance[SER_DEPLOYED_DIR] = installation1_prefix
  98. # Args for the master1 instance
  99. args_instance[SER_HOST] = HOST_MASTER_1
  100. args_instance[SER_PORT] = PORT_MASTER_1
  101. args_instance[SER_SERVERID_PROP] = SERVERID_MASTER_1
  102. args_master = args_instance.copy()
  103. master1.allocate(args_master)
  104. # allocate master1 on a given deployement
  105. master2 = DirSrv(verbose=False)
  106. if installation2_prefix:
  107. args_instance[SER_DEPLOYED_DIR] = installation2_prefix
  108. # Args for the consumer instance
  109. args_instance[SER_HOST] = HOST_MASTER_2
  110. args_instance[SER_PORT] = PORT_MASTER_2
  111. args_instance[SER_SERVERID_PROP] = SERVERID_MASTER_2
  112. args_master = args_instance.copy()
  113. master2.allocate(args_master)
  114. # Get the status of the backups
  115. backup_master1 = master1.checkBackupFS()
  116. backup_master2 = master2.checkBackupFS()
  117. # Get the status of the instance and restart it if it exists
  118. instance_master1 = master1.exists()
  119. if instance_master1:
  120. master1.stop(timeout=10)
  121. master1.start(timeout=10)
  122. instance_master2 = master2.exists()
  123. if instance_master2:
  124. master2.stop(timeout=10)
  125. master2.start(timeout=10)
  126. if backup_master1 and backup_master2:
  127. # The backups exist, assuming they are correct
  128. # we just re-init the instances with them
  129. if not instance_master1:
  130. master1.create()
  131. # Used to retrieve configuration information (dbdir, confdir...)
  132. master1.open()
  133. if not instance_master2:
  134. master2.create()
  135. # Used to retrieve configuration information (dbdir, confdir...)
  136. master2.open()
  137. # restore master1 from backup
  138. master1.stop(timeout=10)
  139. master1.restoreFS(backup_master1)
  140. master1.start(timeout=10)
  141. # restore master2 from backup
  142. master2.stop(timeout=10)
  143. master2.restoreFS(backup_master2)
  144. master2.start(timeout=10)
  145. else:
  146. # We should be here only in two conditions
  147. # - This is the first time a test involve master-consumer
  148. # so we need to create everything
  149. # - Something weird happened (instance/backup destroyed)
  150. # so we discard everything and recreate all
  151. # Remove all the backups. So even if we have a specific backup file
  152. # (e.g backup_master) we clear all backups that an instance my have created
  153. if backup_master1:
  154. master1.clearBackupFS()
  155. if backup_master2:
  156. master2.clearBackupFS()
  157. # Remove all the instances
  158. if instance_master1:
  159. master1.delete()
  160. if instance_master2:
  161. master2.delete()
  162. # Create the instances
  163. master1.create()
  164. master1.open()
  165. master2.create()
  166. master2.open()
  167. #
  168. # Now prepare the Master-Consumer topology
  169. #
  170. # First Enable replication
  171. master1.replica.enableReplication(suffix=SUFFIX, role=REPLICAROLE_MASTER, replicaId=REPLICAID_MASTER_1)
  172. master2.replica.enableReplication(suffix=SUFFIX, role=REPLICAROLE_MASTER, replicaId=REPLICAID_MASTER_2)
  173. # Initialize the supplier->consumer
  174. properties = {RA_NAME: r'meTo_$host:$port',
  175. RA_BINDDN: defaultProperties[REPLICATION_BIND_DN],
  176. RA_BINDPW: defaultProperties[REPLICATION_BIND_PW],
  177. RA_METHOD: defaultProperties[REPLICATION_BIND_METHOD],
  178. RA_TRANSPORT_PROT: defaultProperties[REPLICATION_TRANSPORT]}
  179. repl_agreement = master1.agreement.create(suffix=SUFFIX, host=master2.host, port=master2.port, properties=properties)
  180. if not repl_agreement:
  181. log.fatal("Fail to create a replica agreement")
  182. sys.exit(1)
  183. log.debug("%s created" % repl_agreement)
  184. properties = {RA_NAME: r'meTo_$host:$port',
  185. RA_BINDDN: defaultProperties[REPLICATION_BIND_DN],
  186. RA_BINDPW: defaultProperties[REPLICATION_BIND_PW],
  187. RA_METHOD: defaultProperties[REPLICATION_BIND_METHOD],
  188. RA_TRANSPORT_PROT: defaultProperties[REPLICATION_TRANSPORT]}
  189. master2.agreement.create(suffix=SUFFIX, host=master1.host, port=master1.port, properties=properties)
  190. master1.agreement.init(SUFFIX, HOST_MASTER_2, PORT_MASTER_2)
  191. master1.waitForReplInit(repl_agreement)
  192. # Check replication is working fine
  193. master1.add_s(Entry((TEST_REPL_DN, {
  194. 'objectclass': "top person".split(),
  195. 'sn': 'test_repl',
  196. 'cn': 'test_repl'})))
  197. loop = 0
  198. while loop <= 10:
  199. try:
  200. ent = master2.getEntry(TEST_REPL_DN, ldap.SCOPE_BASE, "(objectclass=*)")
  201. break
  202. except ldap.NO_SUCH_OBJECT:
  203. time.sleep(1)
  204. loop += 1
  205. # Time to create the backups
  206. master1.stop(timeout=10)
  207. master1.backupfile = master1.backupFS()
  208. master1.start(timeout=10)
  209. master2.stop(timeout=10)
  210. master2.backupfile = master2.backupFS()
  211. master2.start(timeout=10)
  212. #
  213. # Here we have two instances master and consumer
  214. # with replication working. Either coming from a backup recovery
  215. # or from a fresh (re)init
  216. # Time to return the topology
  217. return TopologyMaster1Master2(master1, master2)
  218. def test_ticket47721_init(topology):
  219. """
  220. It adds
  221. - Objectclass with MAY 'member'
  222. - an entry ('bind_entry') with which we bind to test the 'SELFDN' operation
  223. It deletes the anonymous aci
  224. """
  225. # entry used to bind with
  226. topology.master1.log.info("Add %s" % BIND_DN)
  227. topology.master1.add_s(Entry((BIND_DN, {
  228. 'objectclass': "top person".split(),
  229. 'sn': BIND_NAME,
  230. 'cn': BIND_NAME,
  231. 'userpassword': BIND_PW})))
  232. # enable acl error logging
  233. mod = [(ldap.MOD_REPLACE, 'nsslapd-errorlog-level', str(8192))] # ACL + REPL
  234. topology.master1.modify_s(DN_CONFIG, mod)
  235. topology.master2.modify_s(DN_CONFIG, mod)
  236. # add dummy entries
  237. for cpt in range(MAX_OTHERS):
  238. name = "%s%d" % (OTHER_NAME, cpt)
  239. topology.master1.add_s(Entry(("cn=%s,%s" % (name, SUFFIX), {
  240. 'objectclass': "top person".split(),
  241. 'sn': name,
  242. 'cn': name})))
  243. def test_ticket47721_0(topology):
  244. dn = "cn=%s0,%s" % (OTHER_NAME, SUFFIX)
  245. loop = 0
  246. while loop <= 10:
  247. try:
  248. ent = topology.master2.getEntry(dn, ldap.SCOPE_BASE, "(objectclass=*)")
  249. break
  250. except ldap.NO_SUCH_OBJECT:
  251. time.sleep(1)
  252. loop += 1
  253. assert loop <= 10
  254. def test_ticket47721_1(topology):
  255. topology.master1.log.info("Attach debugger\n\n" )
  256. #time.sleep(30)
  257. new = _add_custom_at_definition()
  258. topology.master1.log.info("Add (M2) %s " % new)
  259. topology.master2.schema.add_schema('attributetypes', new)
  260. new = _chg_std_at_defintion()
  261. topology.master1.log.info("Chg (M2) %s " % new)
  262. topology.master2.schema.add_schema('attributetypes', new)
  263. new = _add_custom_oc_defintion()
  264. topology.master1.log.info("Add (M2) %s " % new)
  265. topology.master2.schema.add_schema('objectClasses', new)
  266. new = _chg_std_oc_defintion()
  267. topology.master1.log.info("Chg (M2) %s " % new)
  268. topology.master2.schema.add_schema('objectClasses', new)
  269. mod = [(ldap.MOD_REPLACE, 'description', 'Hello world 1')]
  270. dn = "cn=%s0,%s" % (OTHER_NAME, SUFFIX)
  271. topology.master2.modify_s(dn, mod)
  272. loop = 0
  273. while loop <= 10:
  274. try:
  275. ent = topology.master1.getEntry(dn, ldap.SCOPE_BASE, "(objectclass=*)")
  276. if ent.hasAttr('description') and (ent.getValue('description') == 'Hello world 1'):
  277. break
  278. except ldap.NO_SUCH_OBJECT:
  279. loop += 1
  280. time.sleep(1)
  281. assert loop <= 10
  282. def test_ticket47721_2(topology):
  283. mod = [(ldap.MOD_REPLACE, 'description', 'Hello world 2')]
  284. dn = "cn=%s0,%s" % (OTHER_NAME, SUFFIX)
  285. topology.master1.modify_s(dn, mod)
  286. loop = 0
  287. while loop <= 10:
  288. try:
  289. ent = topology.master2.getEntry(dn, ldap.SCOPE_BASE, "(objectclass=*)")
  290. if ent.hasAttr('description') and (ent.getValue('description') == 'Hello world 2'):
  291. break
  292. except ldap.NO_SUCH_OBJECT:
  293. loop += 1
  294. time.sleep(1)
  295. assert loop <= 10
  296. schema_csn_master1 = topology.master1.schema.get_schema_csn()
  297. schema_csn_master2 = topology.master2.schema.get_schema_csn()
  298. assert schema_csn_master1 != None
  299. assert schema_csn_master1 == schema_csn_master2
  300. def test_ticket47721_3(topology):
  301. '''
  302. Check that the supplier can update its schema from consumer schema
  303. Update M2 schema, then trigger a replication M1->M2
  304. '''
  305. # stop RA M2->M1, so that M1 can only learn being a supplier
  306. ents = topology.master2.agreement.list(suffix=SUFFIX)
  307. assert len(ents) == 1
  308. topology.master2.agreement.pause(ents[0].dn)
  309. new = _add_custom_at_definition('ATtest3')
  310. topology.master1.log.info("Update schema (M2) %s " % new)
  311. topology.master2.schema.add_schema('attributetypes', new)
  312. new = _add_custom_oc_defintion('OCtest3')
  313. topology.master1.log.info("Update schema (M2) %s " % new)
  314. topology.master2.schema.add_schema('objectClasses', new)
  315. mod = [(ldap.MOD_REPLACE, 'description', 'Hello world 3')]
  316. dn = "cn=%s0,%s" % (OTHER_NAME, SUFFIX)
  317. topology.master1.modify_s(dn, mod)
  318. loop = 0
  319. while loop <= 10:
  320. try:
  321. ent = topology.master2.getEntry(dn, ldap.SCOPE_BASE, "(objectclass=*)")
  322. if ent.hasAttr('description') and (ent.getValue('description') == 'Hello world 3'):
  323. break
  324. except ldap.NO_SUCH_OBJECT:
  325. loop += 1
  326. time.sleep(1)
  327. assert loop <= 10
  328. schema_csn_master1 = topology.master1.schema.get_schema_csn()
  329. schema_csn_master2 = topology.master2.schema.get_schema_csn()
  330. assert schema_csn_master1 != None
  331. # schema csn on M2 is larger that on M1. M1 only took the new definitions
  332. assert schema_csn_master1 != schema_csn_master2
  333. def test_ticket47721_4(topology):
  334. '''
  335. Here M2->M1 agreement is disabled.
  336. with test_ticket47721_3, M1 schema and M2 should be identical BUT
  337. the nsschemacsn is M2>M1. But as the RA M2->M1 is disabled, M1 keeps its schemacsn.
  338. Update schema on M2 (nsschemaCSN update), update M2. Check they have the same schemacsn
  339. '''
  340. new = _add_custom_at_definition('ATtest4')
  341. topology.master1.log.info("Update schema (M1) %s " % new)
  342. topology.master1.schema.add_schema('attributetypes', new)
  343. new = _add_custom_oc_defintion('OCtest4')
  344. topology.master1.log.info("Update schema (M1) %s " % new)
  345. topology.master1.schema.add_schema('objectClasses', new)
  346. topology.master1.log.info("trigger replication M1->M2: to update the schema")
  347. mod = [(ldap.MOD_REPLACE, 'description', 'Hello world 4')]
  348. dn = "cn=%s0,%s" % (OTHER_NAME, SUFFIX)
  349. topology.master1.modify_s(dn, mod)
  350. loop = 0
  351. while loop <= 10:
  352. try:
  353. ent = topology.master2.getEntry(dn, ldap.SCOPE_BASE, "(objectclass=*)")
  354. if ent.hasAttr('description') and (ent.getValue('description') == 'Hello world 4'):
  355. break
  356. except ldap.NO_SUCH_OBJECT:
  357. loop += 1
  358. time.sleep(1)
  359. assert loop <= 10
  360. topology.master1.log.info("trigger replication M1->M2: to push the schema")
  361. mod = [(ldap.MOD_REPLACE, 'description', 'Hello world 5')]
  362. dn = "cn=%s0,%s" % (OTHER_NAME, SUFFIX)
  363. topology.master1.modify_s(dn, mod)
  364. loop = 0
  365. while loop <= 10:
  366. try:
  367. ent = topology.master2.getEntry(dn, ldap.SCOPE_BASE, "(objectclass=*)")
  368. if ent.hasAttr('description') and (ent.getValue('description') == 'Hello world 5'):
  369. break
  370. except ldap.NO_SUCH_OBJECT:
  371. loop += 1
  372. time.sleep(1)
  373. assert loop <= 10
  374. schema_csn_master1 = topology.master1.schema.get_schema_csn()
  375. schema_csn_master2 = topology.master2.schema.get_schema_csn()
  376. assert schema_csn_master1 != None
  377. assert schema_csn_master1 == schema_csn_master2
  378. def test_ticket47721_final(topology):
  379. topology.master1.stop(timeout=10)
  380. topology.master2.stop(timeout=10)
  381. def run_isolated():
  382. '''
  383. run_isolated is used to run these test cases independently of a test scheduler (xunit, py.test..)
  384. To run isolated without py.test, you need to
  385. - edit this file and comment '@pytest.fixture' line before 'topology' function.
  386. - set the installation prefix
  387. - run this program
  388. '''
  389. global installation1_prefix
  390. global installation2_prefix
  391. installation1_prefix = None
  392. installation2_prefix = None
  393. topo = topology(True)
  394. topo.master1.log.info("\n\n######################### Ticket 47721 ######################\n")
  395. test_ticket47721_init(topo)
  396. test_ticket47721_0(topo)
  397. test_ticket47721_1(topo)
  398. test_ticket47721_2(topo)
  399. test_ticket47721_3(topo)
  400. test_ticket47721_4(topo)
  401. sys.exit(0)
  402. test_ticket47721_final(topo)
  403. if __name__ == '__main__':
  404. run_isolated()