ticket47676_test.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  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 pytest
  11. from lib389 import DirSrv, Entry, tools
  12. from lib389.tools import DirSrvTools
  13. from lib389._constants import *
  14. from lib389.properties import *
  15. logging.getLogger(__name__).setLevel(logging.DEBUG)
  16. log = logging.getLogger(__name__)
  17. #
  18. # important part. We can deploy Master1 and Master2 on different versions
  19. #
  20. installation1_prefix = None
  21. installation2_prefix = None
  22. SCHEMA_DN = "cn=schema"
  23. TEST_REPL_DN = "cn=test_repl, %s" % SUFFIX
  24. OC_NAME = 'OCticket47676'
  25. OC_OID_EXT = 2
  26. MUST = "(postalAddress $ postalCode)"
  27. MAY = "(member $ street)"
  28. OC2_NAME = 'OC2ticket47676'
  29. OC2_OID_EXT = 3
  30. MUST_2 = "(postalAddress $ postalCode)"
  31. MAY_2 = "(member $ street)"
  32. REPL_SCHEMA_POLICY_CONSUMER = "cn=consumerUpdatePolicy,cn=replSchema,cn=config"
  33. REPL_SCHEMA_POLICY_SUPPLIER = "cn=supplierUpdatePolicy,cn=replSchema,cn=config"
  34. OTHER_NAME = 'other_entry'
  35. MAX_OTHERS = 10
  36. BIND_NAME = 'bind_entry'
  37. BIND_DN = 'cn=%s, %s' % (BIND_NAME, SUFFIX)
  38. BIND_PW = 'password'
  39. ENTRY_NAME = 'test_entry'
  40. ENTRY_DN = 'cn=%s, %s' % (ENTRY_NAME, SUFFIX)
  41. ENTRY_OC = "top person %s" % OC_NAME
  42. BASE_OID = "1.2.3.4.5.6.7.8.9.10"
  43. def _oc_definition(oid_ext, name, must=None, may=None):
  44. oid = "%s.%d" % (BASE_OID, oid_ext)
  45. desc = 'To test ticket 47490'
  46. sup = 'person'
  47. if not must:
  48. must = MUST
  49. if not may:
  50. may = MAY
  51. new_oc = "( %s NAME '%s' DESC '%s' SUP %s AUXILIARY MUST %s MAY %s )" % (oid, name, desc, sup, must, may)
  52. return new_oc
  53. class TopologyMaster1Master2(object):
  54. def __init__(self, master1, master2):
  55. master1.open()
  56. self.master1 = master1
  57. master2.open()
  58. self.master2 = master2
  59. @pytest.fixture(scope="module")
  60. def topology(request):
  61. '''
  62. This fixture is used to create a replicated topology for the 'module'.
  63. The replicated topology is MASTER1 <-> Master2.
  64. '''
  65. global installation1_prefix
  66. global installation2_prefix
  67. # allocate master1 on a given deployement
  68. master1 = DirSrv(verbose=False)
  69. if installation1_prefix:
  70. args_instance[SER_DEPLOYED_DIR] = installation1_prefix
  71. # Args for the master1 instance
  72. args_instance[SER_HOST] = HOST_MASTER_1
  73. args_instance[SER_PORT] = PORT_MASTER_1
  74. args_instance[SER_SERVERID_PROP] = SERVERID_MASTER_1
  75. args_master = args_instance.copy()
  76. master1.allocate(args_master)
  77. # allocate master1 on a given deployement
  78. master2 = DirSrv(verbose=False)
  79. if installation2_prefix:
  80. args_instance[SER_DEPLOYED_DIR] = installation2_prefix
  81. # Args for the consumer instance
  82. args_instance[SER_HOST] = HOST_MASTER_2
  83. args_instance[SER_PORT] = PORT_MASTER_2
  84. args_instance[SER_SERVERID_PROP] = SERVERID_MASTER_2
  85. args_master = args_instance.copy()
  86. master2.allocate(args_master)
  87. # Get the status of the instance and restart it if it exists
  88. instance_master1 = master1.exists()
  89. instance_master2 = master2.exists()
  90. # Remove all the instances
  91. if instance_master1:
  92. master1.delete()
  93. if instance_master2:
  94. master2.delete()
  95. # Create the instances
  96. master1.create()
  97. master1.open()
  98. master2.create()
  99. master2.open()
  100. #
  101. # Now prepare the Master-Consumer topology
  102. #
  103. # First Enable replication
  104. master1.replica.enableReplication(suffix=SUFFIX, role=REPLICAROLE_MASTER, replicaId=REPLICAID_MASTER_1)
  105. master2.replica.enableReplication(suffix=SUFFIX, role=REPLICAROLE_MASTER, replicaId=REPLICAID_MASTER_2)
  106. # Initialize the supplier->consumer
  107. properties = {RA_NAME: r'meTo_$host:$port',
  108. RA_BINDDN: defaultProperties[REPLICATION_BIND_DN],
  109. RA_BINDPW: defaultProperties[REPLICATION_BIND_PW],
  110. RA_METHOD: defaultProperties[REPLICATION_BIND_METHOD],
  111. RA_TRANSPORT_PROT: defaultProperties[REPLICATION_TRANSPORT]}
  112. repl_agreement = master1.agreement.create(suffix=SUFFIX, host=master2.host, port=master2.port, properties=properties)
  113. if not repl_agreement:
  114. log.fatal("Fail to create a replica agreement")
  115. sys.exit(1)
  116. log.debug("%s created" % repl_agreement)
  117. properties = {RA_NAME: r'meTo_$host:$port',
  118. RA_BINDDN: defaultProperties[REPLICATION_BIND_DN],
  119. RA_BINDPW: defaultProperties[REPLICATION_BIND_PW],
  120. RA_METHOD: defaultProperties[REPLICATION_BIND_METHOD],
  121. RA_TRANSPORT_PROT: defaultProperties[REPLICATION_TRANSPORT]}
  122. master2.agreement.create(suffix=SUFFIX, host=master1.host, port=master1.port, properties=properties)
  123. master1.agreement.init(SUFFIX, HOST_MASTER_2, PORT_MASTER_2)
  124. master1.waitForReplInit(repl_agreement)
  125. # Check replication is working fine
  126. if master1.testReplication(DEFAULT_SUFFIX, master2):
  127. log.info('Replication is working.')
  128. else:
  129. log.fatal('Replication is not working.')
  130. assert False
  131. # clear the tmp directory
  132. master1.clearTmpDir(__file__)
  133. # Here we have two instances master and consumer
  134. # with replication working.
  135. return TopologyMaster1Master2(master1, master2)
  136. def test_ticket47676_init(topology):
  137. """
  138. It adds
  139. - Objectclass with MAY 'member'
  140. - an entry ('bind_entry') with which we bind to test the 'SELFDN' operation
  141. It deletes the anonymous aci
  142. """
  143. topology.master1.log.info("Add %s that allows 'member' attribute" % OC_NAME)
  144. new_oc = _oc_definition(OC_OID_EXT, OC_NAME, must = MUST, may = MAY)
  145. topology.master1.schema.add_schema('objectClasses', new_oc)
  146. # entry used to bind with
  147. topology.master1.log.info("Add %s" % BIND_DN)
  148. topology.master1.add_s(Entry((BIND_DN, {
  149. 'objectclass': "top person".split(),
  150. 'sn': BIND_NAME,
  151. 'cn': BIND_NAME,
  152. 'userpassword': BIND_PW})))
  153. # enable acl error logging
  154. mod = [(ldap.MOD_REPLACE, 'nsslapd-errorlog-level', str(128 + 8192))] # ACL + REPL
  155. topology.master1.modify_s(DN_CONFIG, mod)
  156. topology.master2.modify_s(DN_CONFIG, mod)
  157. # add dummy entries
  158. for cpt in range(MAX_OTHERS):
  159. name = "%s%d" % (OTHER_NAME, cpt)
  160. topology.master1.add_s(Entry(("cn=%s,%s" % (name, SUFFIX), {
  161. 'objectclass': "top person".split(),
  162. 'sn': name,
  163. 'cn': name})))
  164. def test_ticket47676_skip_oc_at(topology):
  165. '''
  166. This test ADD an entry on MASTER1 where 47676 is fixed. Then it checks that entry is replicated
  167. on MASTER2 (even if on MASTER2 47676 is NOT fixed). Then update on MASTER2.
  168. If the schema has successfully been pushed, updating Master2 should succeed
  169. '''
  170. topology.master1.log.info("\n\n######################### ADD ######################\n")
  171. # bind as 'cn=Directory manager'
  172. topology.master1.log.info("Bind as %s and add the add the entry with specific oc" % DN_DM)
  173. topology.master1.simple_bind_s(DN_DM, PASSWORD)
  174. # Prepare the entry with multivalued members
  175. entry = Entry(ENTRY_DN)
  176. entry.setValues('objectclass', 'top', 'person', 'OCticket47676')
  177. entry.setValues('sn', ENTRY_NAME)
  178. entry.setValues('cn', ENTRY_NAME)
  179. entry.setValues('postalAddress', 'here')
  180. entry.setValues('postalCode', '1234')
  181. members = []
  182. for cpt in range(MAX_OTHERS):
  183. name = "%s%d" % (OTHER_NAME, cpt)
  184. members.append("cn=%s,%s" % (name, SUFFIX))
  185. members.append(BIND_DN)
  186. entry.setValues('member', members)
  187. topology.master1.log.info("Try to add Add %s should be successful" % ENTRY_DN)
  188. topology.master1.add_s(entry)
  189. #
  190. # Now check the entry as been replicated
  191. #
  192. topology.master2.simple_bind_s(DN_DM, PASSWORD)
  193. topology.master1.log.info("Try to retrieve %s from Master2" % ENTRY_DN)
  194. loop = 0
  195. while loop <= 10:
  196. try:
  197. ent = topology.master2.getEntry(ENTRY_DN, ldap.SCOPE_BASE, "(objectclass=*)")
  198. break
  199. except ldap.NO_SUCH_OBJECT:
  200. time.sleep(2)
  201. loop += 1
  202. assert loop <= 10
  203. # Now update the entry on Master2 (as DM because 47676 is possibly not fixed on M2)
  204. topology.master1.log.info("Update %s on M2" % ENTRY_DN)
  205. mod = [(ldap.MOD_REPLACE, 'description', 'test_add')]
  206. topology.master2.modify_s(ENTRY_DN, mod)
  207. topology.master1.simple_bind_s(DN_DM, PASSWORD)
  208. loop = 0
  209. while loop <= 10:
  210. ent = topology.master1.getEntry(ENTRY_DN, ldap.SCOPE_BASE, "(objectclass=*)")
  211. if ent.hasAttr('description') and (ent.getValue('description') == 'test_add'):
  212. break
  213. time.sleep(1)
  214. loop += 1
  215. assert ent.getValue('description') == 'test_add'
  216. def test_ticket47676_reject_action(topology):
  217. topology.master1.log.info("\n\n######################### REJECT ACTION ######################\n")
  218. topology.master1.simple_bind_s(DN_DM, PASSWORD)
  219. topology.master2.simple_bind_s(DN_DM, PASSWORD)
  220. # make master1 to refuse to push the schema if OC_NAME is present in consumer schema
  221. mod = [(ldap.MOD_ADD, 'schemaUpdateObjectclassReject', '%s' % (OC_NAME))] # ACL + REPL
  222. topology.master1.modify_s(REPL_SCHEMA_POLICY_SUPPLIER, mod)
  223. # Restart is required to take into account that policy
  224. topology.master1.stop(timeout=10)
  225. topology.master1.start(timeout=10)
  226. # Add a new OC on M1 so that schema CSN will change and M1 will try to push the schema
  227. topology.master1.log.info("Add %s on M1" % OC2_NAME)
  228. new_oc = _oc_definition(OC2_OID_EXT, OC2_NAME, must=MUST, may=MAY)
  229. topology.master1.schema.add_schema('objectClasses', new_oc)
  230. # Safety checking that the schema has been updated on M1
  231. topology.master1.log.info("Check %s is in M1" % OC2_NAME)
  232. ent = topology.master1.getEntry(SCHEMA_DN, ldap.SCOPE_BASE, "(objectclass=*)", ["objectclasses"])
  233. assert ent.hasAttr('objectclasses')
  234. found = False
  235. for objectclass in ent.getValues('objectclasses'):
  236. if str(objectclass).find(OC2_NAME) >= 0:
  237. found = True
  238. break
  239. assert found
  240. # Do an update of M1 so that M1 will try to push the schema
  241. topology.master1.log.info("Update %s on M1" % ENTRY_DN)
  242. mod = [(ldap.MOD_REPLACE, 'description', 'test_reject')]
  243. topology.master1.modify_s(ENTRY_DN, mod)
  244. # Check the replication occured and so also M1 attempted to push the schema
  245. topology.master1.log.info("Check updated %s on M2" % ENTRY_DN)
  246. loop = 0
  247. while loop <= 10:
  248. ent = topology.master2.getEntry(ENTRY_DN, ldap.SCOPE_BASE, "(objectclass=*)", ['description'])
  249. if ent.hasAttr('description') and ent.getValue('description') == 'test_reject':
  250. # update was replicated
  251. break
  252. time.sleep(2)
  253. loop += 1
  254. assert loop <= 10
  255. # Check that the schema has not been pushed
  256. topology.master1.log.info("Check %s is not in M2" % OC2_NAME)
  257. ent = topology.master2.getEntry(SCHEMA_DN, ldap.SCOPE_BASE, "(objectclass=*)", ["objectclasses"])
  258. assert ent.hasAttr('objectclasses')
  259. found = False
  260. for objectclass in ent.getValues('objectclasses'):
  261. if str(objectclass).find(OC2_NAME) >= 0:
  262. found = True
  263. break
  264. assert not found
  265. topology.master1.log.info("\n\n######################### NO MORE REJECT ACTION ######################\n")
  266. # make master1 to do no specific action on OC_NAME
  267. mod = [(ldap.MOD_DELETE, 'schemaUpdateObjectclassReject', '%s' % (OC_NAME))] # ACL + REPL
  268. topology.master1.modify_s(REPL_SCHEMA_POLICY_SUPPLIER, mod)
  269. # Restart is required to take into account that policy
  270. topology.master1.stop(timeout=10)
  271. topology.master1.start(timeout=10)
  272. # Do an update of M1 so that M1 will try to push the schema
  273. topology.master1.log.info("Update %s on M1" % ENTRY_DN)
  274. mod = [(ldap.MOD_REPLACE, 'description', 'test_no_more_reject')]
  275. topology.master1.modify_s(ENTRY_DN, mod)
  276. # Check the replication occured and so also M1 attempted to push the schema
  277. topology.master1.log.info("Check updated %s on M2" % ENTRY_DN)
  278. loop = 0
  279. while loop <= 10:
  280. ent = topology.master2.getEntry(ENTRY_DN, ldap.SCOPE_BASE, "(objectclass=*)", ['description'])
  281. if ent.hasAttr('description') and ent.getValue('description') == 'test_no_more_reject':
  282. # update was replicated
  283. break
  284. time.sleep(2)
  285. loop += 1
  286. assert loop <= 10
  287. # Check that the schema has been pushed
  288. topology.master1.log.info("Check %s is in M2" % OC2_NAME)
  289. ent = topology.master2.getEntry(SCHEMA_DN, ldap.SCOPE_BASE, "(objectclass=*)", ["objectclasses"])
  290. assert ent.hasAttr('objectclasses')
  291. found = False
  292. for objectclass in ent.getValues('objectclasses'):
  293. if str(objectclass).find(OC2_NAME) >= 0:
  294. found = True
  295. break
  296. assert found
  297. def test_ticket47676_final(topology):
  298. topology.master1.delete()
  299. topology.master2.delete()
  300. log.info('Testcase PASSED')
  301. def run_isolated():
  302. '''
  303. run_isolated is used to run these test cases independently of a test scheduler (xunit, py.test..)
  304. To run isolated without py.test, you need to
  305. - edit this file and comment '@pytest.fixture' line before 'topology' function.
  306. - set the installation prefix
  307. - run this program
  308. '''
  309. global installation1_prefix
  310. global installation2_prefix
  311. installation1_prefix = None
  312. installation2_prefix = None
  313. topo = topology(True)
  314. topo.master1.log.info("\n\n######################### Ticket 47676 ######################\n")
  315. test_ticket47676_init(topo)
  316. test_ticket47676_skip_oc_at(topo)
  317. test_ticket47676_reject_action(topo)
  318. test_ticket47676_final(topo)
  319. if __name__ == '__main__':
  320. run_isolated()