1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 2002, 2014 Oracle and/or its affiliates.  All rights reserved.
5  *
6  */
7 
8 package com.sleepycat.je.rep;
9 
10 import static org.junit.Assert.assertFalse;
11 import static org.junit.Assert.assertTrue;
12 
13 import java.io.File;
14 
15 import org.junit.Before;
16 import org.junit.Test;
17 
18 import com.sleepycat.je.Database;
19 import com.sleepycat.je.DatabaseConfig;
20 import com.sleepycat.je.DatabaseExistsException;
21 import com.sleepycat.je.EnvironmentConfig;
22 import com.sleepycat.je.rep.utilint.RepTestUtils;
23 import com.sleepycat.je.rep.utilint.RepTestUtils.RepEnvInfo;
24 import com.sleepycat.util.test.SharedTestUtils;
25 import com.sleepycat.util.test.TestBase;
26 
27 /**
28  * Check that both master and replica nodes catch invalid environment and
29  * database configurations.
30  */
31 public class CheckConfigTest extends TestBase {
32 
33     private File envRoot;
34     private File[] envHomes;
35 
36     @Before
setUp()37     public void setUp()
38         throws Exception {
39 
40         envRoot = SharedTestUtils.getTestDir();
41         envHomes = RepTestUtils.makeRepEnvDirs(envRoot, 2);
42         super.setUp();
43     }
44 
45     /**
46      * Replicated environments do not support non transactional mode.
47      */
48     @Test
testEnvNonTransactionalConfig()49     public void testEnvNonTransactionalConfig()
50         throws Exception {
51 
52         EnvironmentConfig config = createConfig();
53         config.setTransactional(false);
54         expectRejection(config);
55     }
56 
57     /**
58      * A configuration of transactional + noLocking is invalid.
59      */
60     @Test
testEnvNoLockingConfig()61     public void testEnvNoLockingConfig()
62         throws Exception {
63 
64         EnvironmentConfig config = createConfig();
65         config.setLocking(false);
66         expectRejection(config);
67     }
68 
69     /**
70      * ReadOnly = true should be accepted.
71      *
72      * Since setting environment read only is only possible when an Environment
73      * exists, this test first creates a normal Environment and then reopens it
74      * with read only configuration.
75      */
76     @Test
testEnvReadOnlyConfig()77     public void testEnvReadOnlyConfig()
78         throws Exception {
79 
80         EnvironmentConfig config = createConfig();
81         expectAcceptance(config);
82         config.setReadOnly(true);
83         expectRejection(config);
84     }
85 
86     /**
87      * AllowCreate = false should be accepted.
88      *
89      * Since setting environment allowCreate to false is only possible when an
90      * Environment exists, this test creates a normal Environment and then
91      * reopens it with allowCreate=false configuration.
92      */
93     @Test
testEnvAllowCreateFalseConfig()94     public void testEnvAllowCreateFalseConfig()
95         throws Exception {
96 
97         EnvironmentConfig config = createConfig();
98         expectAcceptance(config);
99         config.setAllowCreate(false);
100         expectAcceptance(config);
101     }
102 
103     /**
104      * SharedCache = true should be accepted.
105      */
106     @Test
testEnvSharedCacheConfig()107     public void testEnvSharedCacheConfig()
108         throws Exception {
109 
110         EnvironmentConfig config = createConfig();
111         config.setSharedCache(true);
112         expectAcceptance(config);
113     }
114 
115     /**
116      * Serializable isolation = true should be accepted.
117      */
118     @Test
testEnvSerializableConfig()119     public void testEnvSerializableConfig()
120         throws Exception {
121 
122         EnvironmentConfig config = createConfig();
123         config.setTxnSerializableIsolation(true);
124         expectAcceptance(config);
125     }
126 
127     /**
128      * Return a new transactional EnvironmentConfig for test use.
129      */
createConfig()130     private EnvironmentConfig createConfig() {
131         EnvironmentConfig config = new EnvironmentConfig();
132         config.setAllowCreate(true);
133         config.setTransactional(true);
134 
135         return config;
136     }
137 
138     /**
139      * Return a new transactional DatabaseConfig for test use.
140      */
createDbConfig()141     private DatabaseConfig createDbConfig() {
142         DatabaseConfig config = new DatabaseConfig();
143         config.setAllowCreate(true);
144         config.setTransactional(true);
145 
146         return config;
147     }
148 
149     /**
150      * Wrap checkEnvConfig in this method to make the intent of the test
151      * obvious.
152      */
expectAcceptance(EnvironmentConfig envConfig)153     private void expectAcceptance(EnvironmentConfig envConfig)
154         throws Exception {
155 
156         checkEnvConfig(envConfig, false /* isInvalid */);
157     }
158 
159     /**
160      * Wrap checkEnvConfig in this method to make the intent of the test
161      * obvious.
162      */
expectRejection(EnvironmentConfig envConfig)163     private void expectRejection(EnvironmentConfig envConfig)
164             throws Exception {
165 
166         checkEnvConfig(envConfig, true /* isInvalid */);
167     }
168 
169     /**
170      * Check whether an EnvironmentConfig is valid.
171      *
172      * @param envConfig The EnvironmentConfig we'd like to check.
173      * @param isInvalid if true, envConfig represents an invalid configuration
174      * and we expect ReplicatedEnvironment creation to fail.
175      */
checkEnvConfig(EnvironmentConfig envConfig, boolean isInvalid)176     private void checkEnvConfig(EnvironmentConfig envConfig,
177                                 boolean isInvalid)
178         throws Exception {
179 
180         /*
181          * masterFail and replicaFail are true if the master or replica
182          * environment creation failed.
183          */
184         boolean masterFail = false;
185         boolean replicaFail = false;
186 
187         ReplicatedEnvironment master = null;
188         ReplicatedEnvironment replica = null;
189 
190         /* Create the ReplicationConfig for master and replica. */
191         ReplicationConfig masterConfig = RepTestUtils.createRepConfig(1);
192         masterConfig.setDesignatedPrimary(true);
193 
194         masterConfig.setHelperHosts(masterConfig.getNodeHostPort());
195         ReplicationConfig replicaConfig = RepTestUtils.createRepConfig(2);
196         replicaConfig.setHelperHosts(masterConfig.getNodeHostPort());
197 
198         /*
199          * Attempt to create the master with the specified EnvironmentConfig.
200          */
201         try {
202             master = new ReplicatedEnvironment(envHomes[0],
203                                                masterConfig,
204                                                envConfig);
205         } catch (IllegalArgumentException e) {
206             masterFail = true;
207         }
208 
209         /*
210          * If the specified EnvironmentConfig is expected to fail, the above
211          * master creation fails, so when the test tries to create a replica
212          * in the following steps, it actually tries to create a master.
213          *
214          * Since the test needs to test on both master and replica, so create
215          * a real master here.
216          */
217         if (isInvalid) {
218             EnvironmentConfig okConfig =
219                 RepTestUtils.createEnvConfig(RepTestUtils.DEFAULT_DURABILITY);
220             master = new ReplicatedEnvironment(envHomes[0], masterConfig,
221                                                okConfig);
222         }
223 
224         /* Check the specified EnvironmentConfig on the replica. */
225         try {
226             replica = new ReplicatedEnvironment(envHomes[1],
227                                                 replicaConfig,
228                                                 envConfig);
229         } catch (IllegalArgumentException e) {
230             replicaFail = true;
231         }
232 
233         /* Check whether the master and replica creations are as expected. */
234         if (isInvalid) {
235             assertTrue(replicaFail && masterFail);
236         } else {
237             assertFalse(replicaFail || masterFail);
238         }
239 
240         /*
241          * If the specified EnvironmentConfig is expected to fail, close
242          * the master and return.
243          */
244         if (isInvalid) {
245             if (master != null) {
246                 assertTrue(master.getState().isMaster());
247                 master.close();
248             }
249 
250             return;
251         }
252 
253         if (master != null && replica != null) {
254             /*
255              * If the specified EnvironmentConfig is correct, wait for
256              * replication initialization to finish.
257              */
258             while (replica.getState() != ReplicatedEnvironment.State.REPLICA) {
259                 Thread.sleep(1000);
260             }
261 
262             /* Make sure the test runs on both master and replica. */
263             assertTrue(master.getState().isMaster());
264             assertTrue(!replica.getState().isMaster());
265 
266             /* Close the replica and master. */
267             replica.close();
268             master.close();
269         }
270     }
271 
272     /**
273      * AllowCreate = false should be accepted.
274      *
275      * Setting allowCreate to false is only possible when the database already
276      * exists. Because of that, this test first creates a database and then
277      * reopens it with allowCreate = false configuration.
278      */
279     @Test
testDbAllowCreateFalseConfig()280     public void testDbAllowCreateFalseConfig()
281         throws Exception {
282 
283         DatabaseConfig dbConfig = createDbConfig();
284         expectDbAcceptance(dbConfig, true);
285         dbConfig.setAllowCreate(false);
286         expectDbAcceptance(dbConfig, false);
287     }
288 
289     /**
290      * Replicated datatabases do not support non transactional mode.
291      */
292     @Test
testDbNonTransactionalConfig()293     public void testDbNonTransactionalConfig()
294         throws Exception {
295 
296         DatabaseConfig dbConfig = createDbConfig();
297         dbConfig.setTransactional(false);
298         expectDbRejection(dbConfig, false);
299     }
300 
301     /**
302      * A database configuration of transactional + deferredWrite is invalid.
303      */
304     @Test
testDbDeferredWriteConfig()305     public void testDbDeferredWriteConfig()
306         throws Exception {
307 
308         DatabaseConfig dbConfig = createDbConfig();
309         dbConfig.setDeferredWrite(true);
310         expectDbRejection(dbConfig, false);
311     }
312 
313     /**
314      * A database configuration of transactional + temporary is invalid.
315      */
316     @Test
testDbTemporaryConfig()317     public void testDbTemporaryConfig()
318         throws Exception {
319 
320         DatabaseConfig dbConfig = createDbConfig();
321         dbConfig.setTemporary(true);
322         expectDbRejection(dbConfig, false);
323     }
324 
325     /**
326      * ExclusiveCreate = true should be accepted on the master.
327      *
328      * Setting exclusiveCreate is expected to fail on the replica. It's because
329      * when a database is created on master, replication will create the same
330      * database on the replica. When the replica tries to create the database,
331      * it will find the database already exists. When we set exclusiveCreate =
332      * true, the replica will throw out a DatabaseExistException. The check
333      * for this is done within the logic for expectDbAcceptance.
334      */
335     @Test
testDbExclusiveCreateConfig()336     public void testDbExclusiveCreateConfig()
337         throws Exception {
338 
339         DatabaseConfig dbConfig = createDbConfig();
340         dbConfig.setExclusiveCreate(true);
341         expectDbAcceptance(dbConfig, true);
342     }
343 
344     /**
345      * KeyPrefixing = true should be accpted.
346      */
347     @Test
testDbKeyPrefixingConfig()348     public void testDbKeyPrefixingConfig()
349         throws Exception {
350 
351         DatabaseConfig dbConfig = createDbConfig();
352         dbConfig.setKeyPrefixing(true);
353         expectDbAcceptance(dbConfig, false);
354     }
355 
356     /**
357      * ReadOnly = true should be accpted.
358      *
359      * Database read only is only possible when the database exists, so this
360      * test first creates a database and then reopens it with read only
361      * configuration.
362      */
363     @Test
testDbReadOnlyConfig()364     public void testDbReadOnlyConfig()
365         throws Exception {
366 
367         DatabaseConfig dbConfig = createDbConfig();
368         expectDbAcceptance(dbConfig, true);
369         dbConfig.setReadOnly(true);
370         expectDbAcceptance(dbConfig, false);
371     }
372 
373     /**
374      * SortedDuplicates = true should be accpted.
375      */
376     @Test
testDbSortedDuplicatesConfig()377     public void testDbSortedDuplicatesConfig()
378         throws Exception {
379 
380         DatabaseConfig dbConfig = createDbConfig();
381         dbConfig.setSortedDuplicates(true);
382         expectDbAcceptance(dbConfig, false);
383     }
384 
385     /**
386      * OverrideBtreeComparator = true should be accepted.
387      */
388     @Test
testDbOverideBtreeComparatorConfig()389     public void testDbOverideBtreeComparatorConfig()
390         throws Exception {
391 
392         DatabaseConfig dbConfig = createDbConfig();
393         dbConfig.setOverrideBtreeComparator(true);
394         expectDbAcceptance(dbConfig, false);
395     }
396 
397     /**
398      * OverrideDuplicatComparator = true should be accepted.
399      */
400     @Test
testDbOverrideDuplicateComparatorConfig()401     public void testDbOverrideDuplicateComparatorConfig()
402         throws Exception {
403 
404         DatabaseConfig dbConfig = createDbConfig();
405         dbConfig.setOverrideDuplicateComparator(true);
406         expectDbAcceptance(dbConfig, false);
407     }
408 
409     /**
410      * UseExistingConfig = true should be accepted.
411      *
412      * UseExistingConfig is only possible when the database exists, so this
413      * test first creates a database and then reopens it with UseExistingConfig
414      * configuration.
415      */
416     @Test
testDbUseExistingConfig()417     public void testDbUseExistingConfig()
418         throws Exception {
419 
420         DatabaseConfig dbConfig = createDbConfig();
421         expectDbAcceptance(dbConfig, true);
422         dbConfig = new DatabaseConfig();
423         dbConfig.setTransactional(true);
424         dbConfig.setUseExistingConfig(true);
425         dbConfig.setReadOnly(true);
426         expectDbAcceptance(dbConfig, false);
427     }
428 
429     /**
430      * Wrap checkDbConfig in this method to make the intent of the test
431      * obvious.
432      */
expectDbAcceptance(DatabaseConfig dbConfig, boolean doSync)433     private void expectDbAcceptance(DatabaseConfig dbConfig, boolean doSync)
434         throws Exception {
435 
436         checkDbConfig(dbConfig, false /* isInvalid */, doSync);
437     }
438 
439     /**
440      * Wrap checkEnvConfig in this method to make the intent of the test
441      * obvious.
442      */
expectDbRejection(DatabaseConfig dbConfig, boolean doSync)443     private void expectDbRejection(DatabaseConfig dbConfig, boolean doSync)
444             throws Exception {
445 
446         checkDbConfig(dbConfig, true /* isInvalid */, doSync);
447     }
448 
449     /**
450      * The main function checks whether a database configuration is valid.
451      *
452      * @param dbConfig The DatabaseConfig to check.
453      * @param isInvalid if true, dbConfig represents an invalid configuration
454      * and we expect database creation to fail.
455      * @param doSync If true, the test should do a group sync after creating
456      * the database on the master
457      */
checkDbConfig(DatabaseConfig dbConfig, boolean isInvalid, boolean doSync)458     public void checkDbConfig(DatabaseConfig dbConfig,
459                               boolean isInvalid,
460                               boolean doSync)
461         throws Exception {
462 
463         /*
464          * masterFail and replicaFail are true if the master or replica
465          * database creation failed.
466          */
467         boolean masterFail = false;
468         boolean replicaFail =false;
469 
470         /* Create an array of replicators successfully and join the group. */
471         RepEnvInfo[] repEnvInfo = RepTestUtils.setupEnvInfos(envRoot, 2);
472         repEnvInfo[0].getRepConfig().setDesignatedPrimary(true);
473         RepTestUtils.joinGroup(repEnvInfo);
474 
475         /* Create the database with the specified configuration on master. */
476         Database masterDb = null;
477         try {
478             masterDb = repEnvInfo[0].getEnv().openDatabase(null, "test",
479                                                            dbConfig);
480         } catch (IllegalArgumentException e) {
481             masterFail = true;
482         }
483 
484         /*
485          * The test does a group sync when the tested configuration needs to
486          * create a real database first.
487          *
488          * If a group sync isn't done, the replica would incorrectly try to
489          * create the database since it hasn't seen it yet. Since write
490          * operations on the replica are forbidden, the test would fail, which
491          * is not expected.
492          */
493         if (doSync) {
494             RepTestUtils.syncGroupToLastCommit(repEnvInfo, repEnvInfo.length);
495         }
496 
497         /* Open the database with the specified configuration on replica. */
498         Database replicaDb = null;
499         try {
500             replicaDb = repEnvInfo[1].getEnv().openDatabase(null, "test",
501                                                             dbConfig);
502         } catch (IllegalArgumentException e) {
503             replicaFail = true;
504         } catch (ReplicaWriteException e) {
505             /*
506              * If the test throws a ReplicaStateException, it's because it
507              * tries to create a new database on replica, but replica doesn't
508              * allow create operation, it's thought to be valid.
509              */
510         } catch (DatabaseExistsException e) {
511             replicaFail = true;
512         }
513 
514         /* Check the validity here. */
515         if (isInvalid) {
516             assertTrue(masterFail && replicaFail);
517         } else {
518 
519             /*
520              * The exclusiveCreate config is checked explicitly here, because
521              * it has different master/replica behavior.
522              */
523             if (dbConfig.getExclusiveCreate()) {
524                 assertFalse(masterFail);
525                 assertTrue(replicaFail);
526             } else {
527                 assertFalse(masterFail || replicaFail);
528             }
529         }
530 
531         /* Shutdown the databases and environments. */
532         if (masterDb != null) {
533             masterDb.close();
534         }
535 
536         if (replicaDb != null) {
537             replicaDb.close();
538         }
539 
540         RepTestUtils.shutdownRepEnvs(repEnvInfo);
541     }
542 }
543