16
from lib389 import DirSrv, Entry, tools
17
from lib389.tools import DirSrvTools
18
from lib389._constants import *
19
from lib389.properties import *
20
from constants import *
22
logging.getLogger(__name__).setLevel(logging.DEBUG)
23
log = logging.getLogger(__name__)
25
installation_prefix = None
27
TEST_REPL_DN = "cn=test_repl, %s" % SUFFIX
28
ENTRY_DN = "cn=test_entry, %s" % SUFFIX
29
MUST_OLD = "(postalAddress $ preferredLocale)"
30
MUST_NEW = "(postalAddress $ preferredLocale $ telexNumber)"
31
MAY_OLD = "(postalCode $ street)"
32
MAY_NEW = "(postalCode $ street $ postOfficeBox)"
35
class TopologyMasterConsumer(object):
36
def __init__(self, master, consumer):
41
self.consumer = consumer
43
def pattern_errorlog(file, log_pattern):
45
pattern_errorlog.last_pos += 1
46
except AttributeError:
47
pattern_errorlog.last_pos = 0
50
log.debug("_pattern_errorlog: start at offset %d" % pattern_errorlog.last_pos)
51
file.seek(pattern_errorlog.last_pos)
53
# Use a while true iteration because 'for line in file: hit a
54
# python bug that break file.tell()
56
line = file.readline()
57
log.debug("_pattern_errorlog: [%d] %s" % (file.tell(), line))
58
found = log_pattern.search(line)
59
if ((line == '') or (found)):
62
log.debug("_pattern_errorlog: end at offset %d" % file.tell())
63
pattern_errorlog.last_pos = file.tell()
66
def _oc_definition(oid_ext, name, must=None, may=None):
67
oid = "1.2.3.4.5.6.7.8.9.10.%d" % oid_ext
68
desc = 'To test ticket 47490'
75
new_oc = "( %s NAME '%s' DESC '%s' SUP %s AUXILIARY MUST %s MAY %s )" % (oid, name, desc, sup, must, may)
78
def add_OC(instance, oid_ext, name):
79
new_oc = _oc_definition(oid_ext, name)
80
instance.schema.add_schema('objectClasses', new_oc)
82
def mod_OC(instance, oid_ext, name, old_must=None, old_may=None, new_must=None, new_may=None):
83
old_oc = _oc_definition(oid_ext, name, old_must, old_may)
84
new_oc = _oc_definition(oid_ext, name, new_must, new_may)
85
instance.schema.del_schema('objectClasses', old_oc)
86
instance.schema.add_schema('objectClasses', new_oc)
88
def trigger_schema_push(topology):
90
It triggers an update on the supplier. This will start a replication
91
session and a schema push
94
trigger_schema_push.value += 1
95
except AttributeError:
96
trigger_schema_push.value = 1
97
replace = [(ldap.MOD_REPLACE, 'telephonenumber', str(trigger_schema_push.value))]
98
topology.master.modify_s(ENTRY_DN, replace)
100
# wait 10 seconds that the update is replicated
104
ent = topology.consumer.getEntry(ENTRY_DN, ldap.SCOPE_BASE, "(objectclass=*)", ['telephonenumber'])
105
val = ent.telephonenumber or "0"
106
if int(val) == trigger_schema_push.value:
108
# the expected value is not yet replicated. try again
111
log.debug("trigger_schema_push: receive %s (expected %d)" % (val, trigger_schema_push.value))
112
except ldap.NO_SUCH_OBJECT:
116
@pytest.fixture(scope="module")
117
def topology(request):
119
This fixture is used to create a replicated topology for the 'module'.
120
The replicated topology is MASTER -> Consumer.
121
At the beginning, It may exists a master instance and/or a consumer instance.
122
It may also exists a backup for the master and/or the consumer.
125
If master instance exists:
127
If consumer instance exists:
129
If backup of master AND backup of consumer exists:
130
create or rebind to consumer
131
create or rebind to master
133
restore master from backup
134
restore consumer from backup
140
Initialize replication
143
global installation_prefix
145
if installation_prefix:
146
args_instance[SER_DEPLOYED_DIR] = installation_prefix
148
master = DirSrv(verbose=False)
149
consumer = DirSrv(verbose=False)
151
# Args for the master instance
152
args_instance[SER_HOST] = HOST_MASTER
153
args_instance[SER_PORT] = PORT_MASTER
154
args_instance[SER_SERVERID_PROP] = SERVERID_MASTER
155
args_master = args_instance.copy()
156
master.allocate(args_master)
158
# Args for the consumer instance
159
args_instance[SER_HOST] = HOST_CONSUMER
160
args_instance[SER_PORT] = PORT_CONSUMER
161
args_instance[SER_SERVERID_PROP] = SERVERID_CONSUMER
162
args_consumer = args_instance.copy()
163
consumer.allocate(args_consumer)
166
# Get the status of the backups
167
backup_master = master.checkBackupFS()
168
backup_consumer = consumer.checkBackupFS()
170
# Get the status of the instance and restart it if it exists
171
instance_master = master.exists()
173
master.stop(timeout=10)
174
master.start(timeout=10)
176
instance_consumer = consumer.exists()
177
if instance_consumer:
178
consumer.stop(timeout=10)
179
consumer.start(timeout=10)
181
if backup_master and backup_consumer:
182
# The backups exist, assuming they are correct
183
# we just re-init the instances with them
184
if not instance_master:
186
# Used to retrieve configuration information (dbdir, confdir...)
189
if not instance_consumer:
191
# Used to retrieve configuration information (dbdir, confdir...)
194
# restore master from backup
195
master.stop(timeout=10)
196
master.restoreFS(backup_master)
197
master.start(timeout=10)
199
# restore consumer from backup
200
consumer.stop(timeout=10)
201
consumer.restoreFS(backup_consumer)
202
consumer.start(timeout=10)
204
# We should be here only in two conditions
205
# - This is the first time a test involve master-consumer
206
# so we need to create everything
207
# - Something weird happened (instance/backup destroyed)
208
# so we discard everything and recreate all
210
# Remove all the backups. So even if we have a specific backup file
211
# (e.g backup_master) we clear all backups that an instance my have created
213
master.clearBackupFS()
215
consumer.clearBackupFS()
217
# Remove all the instances
220
if instance_consumer:
223
# Create the instances
230
# Now prepare the Master-Consumer topology
232
# First Enable replication
233
master.replica.enableReplication(suffix=SUFFIX, role=REPLICAROLE_MASTER, replicaId=REPLICAID_MASTER)
234
consumer.replica.enableReplication(suffix=SUFFIX, role=REPLICAROLE_CONSUMER)
236
# Initialize the supplier->consumer
238
properties = {RA_NAME: r'meTo_$host:$port',
239
RA_BINDDN: defaultProperties[REPLICATION_BIND_DN],
240
RA_BINDPW: defaultProperties[REPLICATION_BIND_PW],
241
RA_METHOD: defaultProperties[REPLICATION_BIND_METHOD],
242
RA_TRANSPORT_PROT: defaultProperties[REPLICATION_TRANSPORT]}
243
repl_agreement = master.agreement.create(suffix=SUFFIX, host=consumer.host, port=consumer.port, properties=properties)
245
if not repl_agreement:
246
log.fatal("Fail to create a replica agreement")
249
log.debug("%s created" % repl_agreement)
250
master.agreement.init(SUFFIX, HOST_CONSUMER, PORT_CONSUMER)
251
master.waitForReplInit(repl_agreement)
253
# Check replication is working fine
254
master.add_s(Entry((TEST_REPL_DN, {
255
'objectclass': "top person".split(),
257
'cn': 'test_repl'})))
261
ent = consumer.getEntry(TEST_REPL_DN, ldap.SCOPE_BASE, "(objectclass=*)")
263
except ldap.NO_SUCH_OBJECT:
267
# Time to create the backups
268
master.stop(timeout=10)
269
master.backupfile = master.backupFS()
270
master.start(timeout=10)
272
consumer.stop(timeout=10)
273
consumer.backupfile = consumer.backupFS()
274
consumer.start(timeout=10)
277
# Here we have two instances master and consumer
278
# with replication working. Either coming from a backup recovery
279
# or from a fresh (re)init
280
# Time to return the topology
281
return TopologyMasterConsumer(master, consumer)
284
def test_ticket47490_init(topology):
286
Initialize the test environment
288
log.debug("test_ticket47490_init topology %r (master %r, consumer %r" % (topology, topology.master, topology.consumer))
289
# the test case will check if a warning message is logged in the
290
# error log of the supplier
291
topology.master.errorlog_file = open(topology.master.errlog, "r")
293
# This entry will be used to trigger attempt of schema push
294
topology.master.add_s(Entry((ENTRY_DN, {
295
'objectclass': "top person".split(),
297
'cn': 'test_entry'})))
299
def test_ticket47490_one(topology):
301
Summary: Extra OC Schema is pushed - no error
303
If supplier schema is a superset (one extra OC) of consumer schema, then
304
schema is pushed and there is no message in the error log
306
- supplier default schema
307
- consumer default schema
309
- supplier +masterNewOCA
310
- consumer +masterNewOCA
313
log.debug("test_ticket47490_one topology %r (master %r, consumer %r" % (topology, topology.master, topology.consumer))
314
# update the schema of the supplier so that it is a superset of
315
# consumer. Schema should be pushed
316
add_OC(topology.master, 2, 'masterNewOCA')
318
trigger_schema_push(topology)
319
master_schema_csn = topology.master.schema.get_schema_csn()
320
consumer_schema_csn = topology.consumer.schema.get_schema_csn()
322
# Check the schemaCSN was updated on the consumer
323
log.debug("test_ticket47490_one master_schema_csn=%s", master_schema_csn)
324
log.debug("ctest_ticket47490_one onsumer_schema_csn=%s", consumer_schema_csn)
325
assert master_schema_csn == consumer_schema_csn
327
# Check the error log of the supplier does not contain an error
328
regex = re.compile("must not be overwritten \(set replication log for additional info\)")
329
res = pattern_errorlog(topology.master.errorlog_file, regex)
332
def test_ticket47490_two(topology):
334
Summary: Extra OC Schema is pushed - (ticket 47721 allows to learn missing def)
336
If consumer schema is a superset (one extra OC) of supplier schema, then
337
schema is pushed and there is a message in the error log
339
- supplier +masterNewOCA
340
- consumer +masterNewOCA
342
- supplier +masterNewOCA +masterNewOCB
343
- consumer +masterNewOCA +consumerNewOCA
347
# add this OC on consumer. Supplier will no push the schema
348
add_OC(topology.consumer, 1, 'consumerNewOCA')
350
# add a new OC on the supplier so that its nsSchemaCSN is larger than the consumer (wait 2s)
352
add_OC(topology.master, 3, 'masterNewOCB')
354
# now push the scheam
355
trigger_schema_push(topology)
356
master_schema_csn = topology.master.schema.get_schema_csn()
357
consumer_schema_csn = topology.consumer.schema.get_schema_csn()
359
# Check the schemaCSN was NOT updated on the consumer
360
# with 47721, supplier learns the missing definition
361
log.debug("test_ticket47490_two master_schema_csn=%s", master_schema_csn)
362
log.debug("test_ticket47490_two consumer_schema_csn=%s", consumer_schema_csn)
363
assert master_schema_csn != consumer_schema_csn
365
# Check the error log of the supplier does not contain an error
366
# This message may happen during the learning phase
367
regex = re.compile("must not be overwritten \(set replication log for additional info\)")
368
res = pattern_errorlog(topology.master.errorlog_file, regex)
371
def test_ticket47490_three(topology):
373
Summary: Extra OC Schema is pushed - no error
375
If supplier schema is again a superset (one extra OC), then
376
schema is pushed and there is no message in the error log
378
- supplier +masterNewOCA +masterNewOCB
379
- consumer +masterNewOCA +consumerNewOCA
381
- supplier +masterNewOCA +masterNewOCB +consumerNewOCA
382
- consumer +masterNewOCA +masterNewOCB +consumerNewOCA
385
# Do an upate to trigger the schema push attempt
386
# add this OC on consumer. Supplier will no push the schema
387
add_OC(topology.master, 1, 'consumerNewOCA')
389
# now push the scheam
390
trigger_schema_push(topology)
391
master_schema_csn = topology.master.schema.get_schema_csn()
392
consumer_schema_csn = topology.consumer.schema.get_schema_csn()
394
# Check the schemaCSN was NOT updated on the consumer
395
log.debug("test_ticket47490_three master_schema_csn=%s", master_schema_csn)
396
log.debug("test_ticket47490_three consumer_schema_csn=%s", consumer_schema_csn)
397
assert master_schema_csn == consumer_schema_csn
399
# Check the error log of the supplier does not contain an error
400
regex = re.compile("must not be overwritten \(set replication log for additional info\)")
401
res = pattern_errorlog(topology.master.errorlog_file, regex)
404
def test_ticket47490_four(topology):
406
Summary: Same OC - extra MUST: Schema is pushed - no error
408
If supplier schema is again a superset (OC with more MUST), then
409
schema is pushed and there is no message in the error log
411
- supplier +masterNewOCA +masterNewOCB +consumerNewOCA
412
- consumer +masterNewOCA +masterNewOCB +consumerNewOCA
414
- supplier +masterNewOCA +masterNewOCB +consumerNewOCA
416
- consumer +masterNewOCA +masterNewOCB +consumerNewOCA
420
mod_OC(topology.master, 2, 'masterNewOCA', old_must=MUST_OLD, new_must=MUST_NEW, old_may=MAY_OLD, new_may=MAY_OLD)
423
trigger_schema_push(topology)
424
master_schema_csn = topology.master.schema.get_schema_csn()
425
consumer_schema_csn = topology.consumer.schema.get_schema_csn()
427
# Check the schemaCSN was updated on the consumer
428
log.debug("test_ticket47490_four master_schema_csn=%s", master_schema_csn)
429
log.debug("ctest_ticket47490_four onsumer_schema_csn=%s", consumer_schema_csn)
430
assert master_schema_csn == consumer_schema_csn
432
# Check the error log of the supplier does not contain an error
433
regex = re.compile("must not be overwritten \(set replication log for additional info\)")
434
res = pattern_errorlog(topology.master.errorlog_file, regex)
437
def test_ticket47490_five(topology):
439
Summary: Same OC - extra MUST: Schema is pushed - (fix for 47721)
441
If consumer schema is a superset (OC with more MUST), then
442
schema is pushed (fix for 47721) and there is a message in the error log
444
- supplier +masterNewOCA +masterNewOCB +consumerNewOCA
446
- consumer +masterNewOCA +masterNewOCB +consumerNewOCA
449
- supplier +masterNewOCA +masterNewOCB +consumerNewOCA +masterNewOCC
451
- consumer +masterNewOCA +masterNewOCB +consumerNewOCA
452
+must=telexnumber +must=telexnumber
454
Note: replication log is enabled to get more details
456
# get more detail why it fails
457
topology.master.enableReplLogging()
459
# add telenumber to 'consumerNewOCA' on the consumer
460
mod_OC(topology.consumer, 1, 'consumerNewOCA', old_must=MUST_OLD, new_must=MUST_NEW, old_may=MAY_OLD, new_may=MAY_OLD)
461
# add a new OC on the supplier so that its nsSchemaCSN is larger than the consumer (wait 2s)
463
add_OC(topology.master, 4, 'masterNewOCC')
465
trigger_schema_push(topology)
466
master_schema_csn = topology.master.schema.get_schema_csn()
467
consumer_schema_csn = topology.consumer.schema.get_schema_csn()
469
# Check the schemaCSN was NOT updated on the consumer
470
# with 47721, supplier learns the missing definition
471
log.debug("test_ticket47490_five master_schema_csn=%s", master_schema_csn)
472
log.debug("ctest_ticket47490_five onsumer_schema_csn=%s", consumer_schema_csn)
473
assert master_schema_csn != consumer_schema_csn
475
# Check the error log of the supplier does not contain an error
476
# This message may happen during the learning phase
477
regex = re.compile("must not be overwritten \(set replication log for additional info\)")
478
res = pattern_errorlog(topology.master.errorlog_file, regex)
480
def test_ticket47490_six(topology):
482
Summary: Same OC - extra MUST: Schema is pushed - no error
484
If supplier schema is again a superset (OC with more MUST), then
485
schema is pushed and there is no message in the error log
487
- supplier +masterNewOCA +masterNewOCB +consumerNewOCA +masterNewOCC
489
- consumer +masterNewOCA +masterNewOCB +consumerNewOCA
490
+must=telexnumber +must=telexnumber
493
- supplier +masterNewOCA +masterNewOCB +consumerNewOCA +masterNewOCC
494
+must=telexnumber +must=telexnumber
495
- consumer +masterNewOCA +masterNewOCB +consumerNewOCA +masterNewOCC
496
+must=telexnumber +must=telexnumber
498
Note: replication log is enabled to get more details
502
# add telenumber to 'consumerNewOCA' on the consumer
503
mod_OC(topology.master, 1, 'consumerNewOCA', old_must=MUST_OLD, new_must=MUST_NEW, old_may=MAY_OLD, new_may=MAY_OLD)
505
trigger_schema_push(topology)
506
master_schema_csn = topology.master.schema.get_schema_csn()
507
consumer_schema_csn = topology.consumer.schema.get_schema_csn()
509
# Check the schemaCSN was NOT updated on the consumer
510
log.debug("test_ticket47490_six master_schema_csn=%s", master_schema_csn)
511
log.debug("ctest_ticket47490_six onsumer_schema_csn=%s", consumer_schema_csn)
512
assert master_schema_csn == consumer_schema_csn
514
# Check the error log of the supplier does not contain an error
515
# This message may happen during the learning phase
516
regex = re.compile("must not be overwritten \(set replication log for additional info\)")
517
res = pattern_errorlog(topology.master.errorlog_file, regex)
523
def test_ticket47490_seven(topology):
525
Summary: Same OC - extra MAY: Schema is pushed - no error
527
If supplier schema is again a superset (OC with more MAY), then
528
schema is pushed and there is no message in the error log
530
- supplier +masterNewOCA +masterNewOCB +consumerNewOCA +masterNewOCC
531
+must=telexnumber +must=telexnumber
532
- consumer +masterNewOCA +masterNewOCB +consumerNewOCA +masterNewOCC
533
+must=telexnumber +must=telexnumber
535
- supplier +masterNewOCA +masterNewOCB +consumerNewOCA +masterNewOCC
536
+must=telexnumber +must=telexnumber
538
- consumer +masterNewOCA +masterNewOCB +consumerNewOCA +masterNewOCC
539
+must=telexnumber +must=telexnumber
542
mod_OC(topology.master, 2, 'masterNewOCA', old_must=MUST_NEW, new_must=MUST_NEW, old_may=MAY_OLD, new_may=MAY_NEW)
545
trigger_schema_push(topology)
546
master_schema_csn = topology.master.schema.get_schema_csn()
547
consumer_schema_csn = topology.consumer.schema.get_schema_csn()
549
# Check the schemaCSN was updated on the consumer
550
log.debug("test_ticket47490_seven master_schema_csn=%s", master_schema_csn)
551
log.debug("ctest_ticket47490_seven consumer_schema_csn=%s", consumer_schema_csn)
552
assert master_schema_csn == consumer_schema_csn
554
# Check the error log of the supplier does not contain an error
555
regex = re.compile("must not be overwritten \(set replication log for additional info\)")
556
res = pattern_errorlog(topology.master.errorlog_file, regex)
560
def test_ticket47490_eight(topology):
562
Summary: Same OC - extra MAY: Schema is pushed (fix for 47721)
564
If consumer schema is a superset (OC with more MAY), then
565
schema is pushed (fix for 47721) and there is message in the error log
567
- supplier +masterNewOCA +masterNewOCB +consumerNewOCA +masterNewOCC
568
+must=telexnumber +must=telexnumber
570
- consumer +masterNewOCA +masterNewOCB +consumerNewOCA +masterNewOCC
571
+must=telexnumber +must=telexnumber
574
- supplier +masterNewOCA +masterNewOCB +consumerNewOCA +masterNewOCC
575
+must=telexnumber +must=telexnumber
576
+may=postOfficeBox +may=postOfficeBox
577
- consumer +masterNewOCA +masterNewOCB +consumerNewOCA +masterNewOCC
578
+must=telexnumber +must=telexnumber
579
+may=postOfficeBox +may=postOfficeBox
581
mod_OC(topology.consumer, 1, 'consumerNewOCA', old_must=MUST_NEW, new_must=MUST_NEW, old_may=MAY_OLD, new_may=MAY_NEW)
583
# modify OC on the supplier so that its nsSchemaCSN is larger than the consumer (wait 2s)
585
mod_OC(topology.master, 4, 'masterNewOCC', old_must=MUST_OLD, new_must=MUST_OLD, old_may=MAY_OLD, new_may=MAY_NEW)
587
trigger_schema_push(topology)
588
master_schema_csn = topology.master.schema.get_schema_csn()
589
consumer_schema_csn = topology.consumer.schema.get_schema_csn()
591
# Check the schemaCSN was not updated on the consumer
592
# with 47721, supplier learns the missing definition
593
log.debug("test_ticket47490_eight master_schema_csn=%s", master_schema_csn)
594
log.debug("ctest_ticket47490_eight onsumer_schema_csn=%s", consumer_schema_csn)
595
assert master_schema_csn != consumer_schema_csn
597
# Check the error log of the supplier does not contain an error
598
# This message may happen during the learning phase
599
regex = re.compile("must not be overwritten \(set replication log for additional info\)")
600
res = pattern_errorlog(topology.master.errorlog_file, regex)
603
def test_ticket47490_nine(topology):
605
Summary: Same OC - extra MAY: Schema is pushed - no error
607
If consumer schema is a superset (OC with more MAY), then
608
schema is not pushed and there is message in the error log
610
- supplier +masterNewOCA +masterNewOCB +consumerNewOCA +masterNewOCC
611
+must=telexnumber +must=telexnumber
612
+may=postOfficeBox +may=postOfficeBox
613
- consumer +masterNewOCA +masterNewOCB +consumerNewOCA +masterNewOCC
614
+must=telexnumber +must=telexnumber
615
+may=postOfficeBox +may=postOfficeBox
619
- supplier +masterNewOCA +masterNewOCB +consumerNewOCA +masterNewOCC
620
+must=telexnumber +must=telexnumber
621
+may=postOfficeBox +may=postOfficeBox +may=postOfficeBox
622
- consumer +masterNewOCA +masterNewOCB +consumerNewOCA +masterNewOCC
623
+must=telexnumber +must=telexnumber
624
+may=postOfficeBox +may=postOfficeBox +may=postOfficeBox
626
mod_OC(topology.master, 1, 'consumerNewOCA', old_must=MUST_NEW, new_must=MUST_NEW, old_may=MAY_OLD, new_may=MAY_NEW)
628
trigger_schema_push(topology)
629
master_schema_csn = topology.master.schema.get_schema_csn()
630
consumer_schema_csn = topology.consumer.schema.get_schema_csn()
632
# Check the schemaCSN was updated on the consumer
633
log.debug("test_ticket47490_nine master_schema_csn=%s", master_schema_csn)
634
log.debug("ctest_ticket47490_nine onsumer_schema_csn=%s", consumer_schema_csn)
635
assert master_schema_csn == consumer_schema_csn
637
# Check the error log of the supplier does not contain an error
638
regex = re.compile("must not be overwritten \(set replication log for additional info\)")
639
res = pattern_errorlog(topology.master.errorlog_file, regex)
642
def test_ticket47490_final(topology):
643
topology.master.stop(timeout=10)
644
topology.consumer.stop(timeout=10)
648
run_isolated is used to run these test cases independently of a test scheduler (xunit, py.test..)
649
To run isolated without py.test, you need to
650
- edit this file and comment '@pytest.fixture' line before 'topology' function.
651
- set the installation prefix
654
global installation_prefix
655
installation_prefix = None
657
topo = topology(True)
658
test_ticket47490_init(topo)
659
test_ticket47490_one(topo)
660
test_ticket47490_two(topo)
661
test_ticket47490_three(topo)
662
test_ticket47490_four(topo)
663
test_ticket47490_five(topo)
664
test_ticket47490_six(topo)
665
test_ticket47490_seven(topo)
666
test_ticket47490_eight(topo)
667
test_ticket47490_nine(topo)
669
test_ticket47490_final(topo)
672
if __name__ == '__main__':