001: /*
002:
003: Derby - Class org.apache.derbyTesting.functionTests.store.ReEncryptCrashRecovery
004:
005: Licensed to the Apache Software Foundation (ASF) under one or more
006: contributor license agreements. See the NOTICE file distributed with
007: this work for additional information regarding copyright ownership.
008: The ASF licenses this file to You under the Apache License, Version 2.0
009: (the "License"); you may not use this file except in compliance with
010: the License. You may obtain a copy of the License at
011:
012: http://www.apache.org/licenses/LICENSE-2.0
013:
014: Unless required by applicable law or agreed to in writing, software
015: distributed under the License is distributed on an "AS IS" BASIS,
016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: See the License for the specific language governing permissions and
018: limitations under the License.
019:
020: */
021:
022: package org.apache.derbyTesting.functionTests.tests.store;
023:
024: import java.sql.Connection;
025: import java.sql.Statement;
026: import java.sql.PreparedStatement;
027: import java.sql.ResultSet;
028: import java.sql.SQLException;
029: import org.apache.derby.tools.ij;
030: import org.apache.derbyTesting.functionTests.util.TestUtil;
031: import org.apache.derby.iapi.services.sanity.SanityManager;
032:
033: /*
034: * This class tests crash/recovery scenarions during (re) encryption of
035: * database. Debug flags are used to simulate crashes during the
036: * encrytpion of an un-encrypted database and re-encryption of an encrypted
037: * database with new password/key.
038: *
039: * Unlike the other recovery tests which do a setup and recovery as different
040: * tests, Incase of re-encryption crash/recovery can be simulated in one
041: * test itself because re-encryption is done at boot time. When debug flags are
042: * set database boot itself fails. To test the recovery, it is just a matter
043: * of clearing up the debug flag and rebooting the database.
044: *
045: * In Non debug mode, this tests does not do anything.
046: *
047: * @author <a href="mailto:suresh.thalamati@gmail.com">Suresh Thalamati</a>
048: * @version 1.0
049: */
050:
051: public class ReEncryptCrashRecovery {
052:
053: // database name used to test re-encryption of an encrypted database
054: // using a new boot password.
055: private static final String TEST_REENCRYPT_PWD_DATABASE = "wombat_pwd_ren";
056: // database name used to test encryption and un-encrypted database.
057: // using a boot password.
058: private static final String TEST_ENCRYPT_PWD_DATABASE = "wombat_pwd_en";
059:
060: // database name used to test re-encryption of an encrypted database
061: // using the external encryption key.
062: private static final String TEST_REENCRYPT_KEY_DATABASE = "wombat_key_ren";
063: // database name used to test encryption of un-encrypted database.
064: // using external encryption key.
065: private static final String TEST_ENCRYPT_KEY_DATABASE = "wombat_key_en";
066:
067: // flags to indicate type of mechanism used to test the (re)encryption
068: private static final int USING_KEY = 1;
069: private static final int USING_PASSWORD = 2;
070:
071: // flags to indicate the password/key to be used during recovery
072: // on reboot after a crash.
073: private static final int NONE = 1;
074: private static final int OLD = 2;
075: private static final int NEW = 3;
076:
077: // test table name.
078: private static final String TEST_TABLE_NAME = "emp";
079:
080: private static final String OLD_PASSWORD = "xyz1234abc";
081: private static final String NEW_PASSWORD = "new1234xyz";
082:
083: private static final String OLD_KEY = "6162636465666768";
084: private static final String NEW_KEY = "5666768616263646";
085:
086: // the current database being tested.
087: private String currentTestDatabase;
088: // the current encryption type being tested.
089: private int encryptionType;
090:
091: // set the following to true, for this test
092: // spit out more status messages.
093: private boolean verbose = false;
094:
095: ReEncryptCrashRecovery() {
096:
097: }
098:
099: /*
100: * Test (re)encrytpion crash/recovery scenarios.
101: */
102: private void runTest() throws Exception {
103: logMessage("Begin ReEncryptCrashRecovery Test");
104:
105: if (SanityManager.DEBUG) {
106: if (verbose)
107: logMessage("Start testing re-encryption with Password");
108: // test crash recovery during re-encryption
109: // using the password mechanism.
110: currentTestDatabase = TEST_REENCRYPT_PWD_DATABASE;
111: encryptionType = USING_PASSWORD;
112: runCrashRecoveryTestCases(true);
113:
114: if (verbose)
115: logMessage("Start Testing encryption with Password");
116:
117: // test crash recovery during databse encryption
118: // using the password mechanism.
119: currentTestDatabase = TEST_ENCRYPT_PWD_DATABASE;
120: encryptionType = USING_PASSWORD;
121: // run crash recovery test cases.
122: runCrashRecoveryTestCases(false);
123:
124: if (verbose) {
125: logMessage("Start Testing Encryption with external Key");
126: }
127: // test crash recovery during database encryption
128: // using the encryption key.
129:
130: currentTestDatabase = TEST_ENCRYPT_KEY_DATABASE;
131: encryptionType = USING_KEY;
132: runCrashRecoveryTestCases(false);
133:
134: if (verbose)
135: logMessage("Start Testing re-encryption with external Key");
136:
137: // test crash recovery dureing re-encryption
138: // using the encryption key.
139:
140: currentTestDatabase = TEST_REENCRYPT_KEY_DATABASE;
141: encryptionType = USING_KEY;
142: runCrashRecoveryTestCases(true);
143: }
144: logMessage("End ReEncryptCrashRecovery Test");
145: }
146:
147: /**
148: * run crash recovery test scenarios using the debug flags.
149: * @param reEncrypt <code> true </code> if testing re-encryption
150: * <colde> false </code> otherwise.
151: */
152: private void runCrashRecoveryTestCases(boolean reEncrypt)
153: throws SQLException {
154: Connection conn;
155: if (reEncrypt)
156: conn = createEncryptedDatabase();
157: else
158: conn = createDatabase();
159:
160: createTable(conn, TEST_TABLE_NAME);
161: //load some rows
162: insert(conn, TEST_TABLE_NAME, 100);
163: conn.commit();
164: conn.close();
165: shutdown();
166:
167: // following cases of (re) encryption should be rolled back.
168: int passwordKey = (reEncrypt ? OLD : NONE);
169:
170: crash(reEncrypt, TEST_REENCRYPT_CRASH_BEFORE_COMMT);
171:
172: crash(reEncrypt, TEST_REENCRYPT_CRASH_AFTER_COMMT);
173: crashInRecovery(passwordKey,
174: TEST_REENCRYPT_CRASH_AFTER_RECOVERY_UNDO_LOGFILE_DELETE);
175: crashInRecovery(passwordKey,
176: TEST_REENCRYPT_CRASH_AFTER_RECOVERY_UNDO_REVERTING_KEY);
177: crashInRecovery(passwordKey,
178: TEST_REENCRYPT_CRASH_BEFORE_RECOVERY_FINAL_CLEANUP);
179:
180: crash(reEncrypt, TEST_REENCRYPT_CRASH_AFTER_COMMT);
181: crashInRecovery(passwordKey,
182: TEST_REENCRYPT_CRASH_AFTER_RECOVERY_UNDO_LOGFILE_DELETE);
183: // retry (re)encryption and crash.
184: crash(reEncrypt, TEST_REENCRYPT_CRASH_AFTER_COMMT);
185:
186: crash(reEncrypt, TEST_REENCRYPT_CRASH_AFTER_SWITCH_TO_NEWKEY);
187: crashInRecovery(passwordKey,
188: TEST_REENCRYPT_CRASH_AFTER_RECOVERY_UNDO_LOGFILE_DELETE);
189: crashInRecovery(passwordKey,
190: TEST_REENCRYPT_CRASH_AFTER_RECOVERY_UNDO_REVERTING_KEY);
191: crashInRecovery(passwordKey,
192: TEST_REENCRYPT_CRASH_BEFORE_RECOVERY_FINAL_CLEANUP);
193:
194: crash(reEncrypt, TEST_REENCRYPT_CRASH_AFTER_SWITCH_TO_NEWKEY);
195: crashInRecovery(passwordKey,
196: TEST_REENCRYPT_CRASH_AFTER_RECOVERY_UNDO_REVERTING_KEY);
197: // retry (re)encryption and crash.
198: crash(reEncrypt, TEST_REENCRYPT_CRASH_AFTER_SWITCH_TO_NEWKEY);
199: crashInRecovery(passwordKey,
200: TEST_REENCRYPT_CRASH_BEFORE_RECOVERY_FINAL_CLEANUP);
201:
202: // following cases (re) encryption should be successful, only
203: // cleanup is pending.
204:
205: // crash after database is re-encrypted, but before cleanup.
206: // (re)encryption is complete, database should be bootable
207: // with a new password.
208: passwordKey = (reEncrypt ? NEW : OLD);
209: crash(reEncrypt, TEST_REENCRYPT_CRASH_AFTER_CHECKPOINT);
210: crashInRecovery(passwordKey,
211: TEST_REENCRYPT_CRASH_BEFORE_RECOVERY_FINAL_CLEANUP);
212:
213: recover(passwordKey);
214: shutdown();
215: }
216:
217: /*
218: * Attempt to (re)encrypt the database and force it to crash
219: * at the given debug flag.
220: */
221: private void crash(boolean reEncrypt, String debugFlag) {
222: if (verbose)
223: logMessage("Testing : " + debugFlag);
224: // set the debug flag to crash.
225: setDebugFlag(debugFlag);
226:
227: SQLException sqle = null;
228: Connection conn;
229: try {
230: if (reEncrypt)
231: conn = reEncryptDatabase();
232: else
233: conn = encryptDatabase();
234:
235: } catch (SQLException se) {
236: // (re)encryption of the database should have failed,
237: // at the specified debug flag.
238: sqle = se;
239: }
240:
241: // check that database boot failed at the set debug flag.
242: verifyException(sqle, debugFlag);
243: // clear the debug flag.
244: clearDebugFlag(debugFlag);
245: }
246:
247: /*
248: * Crash in recovery of the database at the given
249: * debug flag.
250: */
251: private void crashInRecovery(int passwordKey, String debugFlag)
252: throws SQLException {
253: if (verbose)
254: logMessage("Testing : " + debugFlag);
255:
256: // set the debug flag to crash.
257: setDebugFlag(debugFlag);
258: SQLException sqle = null;
259: try {
260: Connection conn = bootDatabase(passwordKey);
261: } catch (SQLException se) {
262: // recovery of the database
263: // shold have failed at the specified
264: // debug flag.
265: sqle = se;
266: }
267: // check that database boot failed at the set debug flag.
268: verifyException(sqle, debugFlag);
269: // clear the debug flag.
270: clearDebugFlag(debugFlag);
271: }
272:
273: /*
274: * Recover the database that failied during re-encryption and
275: * perform some simple sanity check on the database.
276: */
277: private void recover(int passwordKey) throws SQLException {
278: // starting recovery of database with failed Re-encrytpion
279: // in debug mode;
280:
281: Connection conn = bootDatabase(passwordKey);
282:
283: // verify the contents of the db are ok.
284: runConsistencyChecker(conn, TEST_TABLE_NAME);
285: // insert some rows, this might fail if anyhing is
286: // wrong in the logging system setup.
287: insert(conn, TEST_TABLE_NAME, 100);
288: conn.commit();
289: conn.close();
290: }
291:
292: /** *************************************************
293: * Crash/recovery test scenarios during
294: * encryption of an un-encrypted database.
295: ****************************************************/
296:
297: // Debug flags that are to be set to simulate a crash
298: // at different points during (re)encryption of the database.
299: // these flags should match the flags in the engine code;
300: // these are redifined here to avoid pulling the engine code
301: // into the tests.
302:
303: /*
304: Set to true if we want the re-encryption to crash just
305: before the commit.
306: */
307:
308: public static final String TEST_REENCRYPT_CRASH_BEFORE_COMMT = SanityManager.DEBUG ? "TEST_REENCRYPT_CRASH_BEFORE_COMMT"
309: : null;
310: public static final String TEST_REENCRYPT_CRASH_AFTER_COMMT = SanityManager.DEBUG ? "TEST_REENCRYPT_CRASH_AFTER_COMMT"
311: : null;
312: public static final String TEST_REENCRYPT_CRASH_AFTER_SWITCH_TO_NEWKEY = SanityManager.DEBUG ? "TEST_REENCRYPT_CRASH_AFTER_SWITCH_TO_NEWKEY"
313: : null;
314: public static final String TEST_REENCRYPT_CRASH_AFTER_CHECKPOINT = SanityManager.DEBUG ? "TEST_REENCRYPT_CRASH_AFTER_CHECKPOINT"
315: : null;
316:
317: public static final String TEST_REENCRYPT_CRASH_AFTER_RECOVERY_UNDO_LOGFILE_DELETE = SanityManager.DEBUG ? "TEST_REENCRYPT_CRASH_AFTER_RECOVERY_UNDO_LOGFILE_DELETE"
318: : null;
319: public static final String TEST_REENCRYPT_CRASH_AFTER_RECOVERY_UNDO_REVERTING_KEY = SanityManager.DEBUG ? "TEST_REENCRYPT_CRASH_AFTER_RECOVERY_UNDO_REVERTING_KEY"
320: : null;
321: public static final String TEST_REENCRYPT_CRASH_BEFORE_RECOVERY_FINAL_CLEANUP = SanityManager.DEBUG ? "TEST_REENCRYPT_CRASH_BEFORE_RECOVERY_FINAL_CLEANUP"
322: : null;
323:
324: void setDebugFlag(String debugFlag) {
325: if (SanityManager.DEBUG) {
326: SanityManager.DEBUG_SET(debugFlag);
327: }
328: }
329:
330: void clearDebugFlag(String debugFlag) {
331: if (SanityManager.DEBUG) {
332: SanityManager.DEBUG_CLEAR(debugFlag);
333: }
334: }
335:
336: /*
337: * verify that database boot failed when a debug flag is set.
338: */
339: private void verifyException(SQLException sqle, String debugFlag) {
340: boolean expectedExcepion = false;
341: if (sqle != null) {
342:
343: if (sqle.getSQLState() != null
344: && sqle.getSQLState().equals("XJ040")) {
345: // boot failed as expected with the debug flag
346: // now check if it failed with specifed debug flags.
347: SQLException ne = sqle.getNextException();
348: if (ne != null) {
349: String message = ne.getMessage();
350: // check if debug flag exists in the message
351: if (message.indexOf(debugFlag) != -1) {
352: expectedExcepion = true;
353: }
354: }
355: }
356:
357: if (!expectedExcepion)
358: dumpSQLException(sqle);
359: } else {
360: if (SanityManager.DEBUG) {
361: logMessage("Did not crash at " + debugFlag);
362: }
363: }
364: }
365:
366: /*
367: * create the tables that are used by this test.
368: * @param conn connection to the database.
369: * @param tableName Name of the table to create.
370: * @exception SQLException if any database exception occurs.
371: */
372: void createTable(Connection conn, String tableName)
373: throws SQLException {
374:
375: Statement s = conn.createStatement();
376: s.executeUpdate("CREATE TABLE " + tableName + "(id INT,"
377: + "name CHAR(200))");
378: s.executeUpdate("create index " + tableName + "_id_idx on "
379: + tableName + "(id)");
380: s.close();
381: }
382:
383: /**
384: * Run some consistency checks.
385: * @param conn connection to the database.
386: * @param tableName consistency checks are performed on this table.
387: * @exception SQLException if any database exception occurs.
388: */
389: void runConsistencyChecker(Connection conn, String tableName)
390: throws SQLException {
391: Statement stmt = conn.createStatement();
392: stmt
393: .execute("values SYSCS_UTIL.SYSCS_CHECK_TABLE('APP', 'EMP')");
394: // check the data in the EMP table.
395: select(conn, tableName);
396: }
397:
398: /**
399: * Insert some rows into the specified table.
400: * @param conn connection to the database.
401: * @param tableName name of the table that rows are inserted.
402: * @param rowCount Number of rows to Insert.
403: * @exception SQLException if any database exception occurs.
404: */
405: void insert(Connection conn, String tableName, int rowCount)
406: throws SQLException {
407:
408: PreparedStatement ps = conn.prepareStatement("INSERT INTO "
409: + tableName + " VALUES(?,?)");
410: int startId = findMax(conn, tableName);
411: for (int i = startId; i < rowCount; i++) {
412:
413: ps.setInt(1, i); // ID
414: ps.setString(2, "skywalker" + i);
415: ps.executeUpdate();
416: }
417: ps.close();
418: conn.commit();
419: }
420:
421: /**
422: * find a max value on the give table.
423: * @param conn connection to the database.
424: * @param tableName name of the table.
425: * @exception SQLException if any database exception occurs.
426: */
427: private int findMax(Connection conn, String tableName)
428: throws SQLException {
429: Statement s = conn.createStatement();
430: ResultSet rs = s.executeQuery("SELECT max(ID) from "
431: + tableName);
432: rs.next();
433: int max = rs.getInt(1);
434: rs.close();
435: s.close();
436: return max;
437: }
438:
439: /*
440: * read the rows in the table.
441: * @param conn connection to the database.
442: * @param tableName select operation is perfomed on this table.
443: * @exception SQLException if any database exception occurs.
444: */
445: void select(Connection conn, String tableName) throws SQLException {
446:
447: Statement s = conn.createStatement();
448: ResultSet rs = s.executeQuery("SELECT ID, name from "
449: + tableName + " order by id");
450: int count = 0;
451: int id = 0;
452: while (rs.next()) {
453: int tid = rs.getInt(1);
454: String name = rs.getString(2);
455: if (name.equals("skywalker" + id) && tid != id) {
456: logMessage("DATA IN THE TABLE IS NOT AS EXPECTED");
457: logMessage("Got :ID=" + tid + " Name=:" + name);
458: logMessage("Expected: ID=" + id + "Name=" + "skywalker"
459: + id);
460: }
461:
462: id++;
463: count++;
464: }
465:
466: rs.close();
467: s.close();
468: conn.commit();
469: }
470:
471: /*
472: * create an encrypted database.
473: */
474: private Connection createEncryptedDatabase() throws SQLException {
475: String connAttrs = "";
476: if (encryptionType == USING_PASSWORD) {
477: // create encrypted database.
478: connAttrs = "create=true;dataEncryption=true;bootPassword="
479: + OLD_PASSWORD;
480: }
481:
482: if (encryptionType == USING_KEY) {
483: // create an encrypted database.
484: connAttrs = "create=true;dataEncryption=true;encryptionKey="
485: + OLD_KEY;
486: }
487:
488: return TestUtil.getConnection(currentTestDatabase, connAttrs);
489: }
490:
491: /*
492: * create an un-encrypted database.
493: */
494: private Connection createDatabase() throws SQLException {
495: return TestUtil.getConnection(currentTestDatabase,
496: "create=true");
497: }
498:
499: /**
500: * Re-encrypt the database.
501: * @exception SQLException if any database exception occurs.
502: */
503: private Connection reEncryptDatabase() throws SQLException {
504: String connAttrs = "";
505: if (encryptionType == USING_PASSWORD) {
506: // re-encrypt the database.
507: connAttrs = "bootPassword=" + OLD_PASSWORD
508: + ";newBootPassword=" + NEW_PASSWORD;
509: }
510:
511: if (encryptionType == USING_KEY) {
512: // re-encrypt the database.
513: connAttrs = "encryptionKey=" + OLD_KEY
514: + ";newEncryptionKey=" + NEW_KEY;
515: }
516:
517: if (verbose)
518: logMessage("re-encrypting " + currentTestDatabase
519: + " with " + connAttrs);
520:
521: return TestUtil.getConnection(currentTestDatabase, connAttrs);
522: }
523:
524: /**
525: * Encrypt an un-encrypted atabase.
526: * @param password boot password of the database.
527: * @exception SQLException if any database exception occurs.
528: */
529: private Connection encryptDatabase() throws SQLException {
530: String connAttrs = "";
531: if (encryptionType == USING_PASSWORD) {
532: //encrypt an existing database.
533: connAttrs = "dataEncryption=true;bootPassword="
534: + OLD_PASSWORD;
535: }
536: if (encryptionType == USING_KEY) {
537: //encrypt an existing database.
538: connAttrs = "dataEncryption=true;encryptionKey=" + OLD_KEY;
539: }
540:
541: if (verbose)
542: logMessage("encrypting " + currentTestDatabase + " with "
543: + connAttrs);
544: return TestUtil.getConnection(currentTestDatabase, connAttrs);
545: }
546:
547: /**
548: * Boot the database.
549: * @param passwordOrKey the password/key to use.
550: * @exception SQLException if any database exception occurs.
551: */
552: Connection bootDatabase(int passwordKey) throws SQLException {
553:
554: String connAttrs = "";
555: if (encryptionType == USING_PASSWORD) {
556: if (passwordKey == NEW)
557: connAttrs = "bootPassword=" + NEW_PASSWORD;
558: else if (passwordKey == OLD)
559: connAttrs = "bootPassword=" + OLD_PASSWORD;
560: }
561:
562: if (encryptionType == USING_KEY) {
563: if (passwordKey == NEW)
564: connAttrs = "encryptionKey=" + NEW_KEY;
565: else if (passwordKey == OLD)
566: connAttrs = "encryptionKey=" + OLD_KEY;
567: }
568:
569: if (verbose)
570: logMessage("booting " + currentTestDatabase + " with "
571: + connAttrs);
572: return TestUtil.getConnection(currentTestDatabase, connAttrs);
573: }
574:
575: /**
576: * Shutdown the datbase
577: */
578: void shutdown() {
579:
580: if (verbose)
581: logMessage("Shutdown " + currentTestDatabase);
582: try {
583: //shutdown
584: TestUtil
585: .getConnection(currentTestDatabase, "shutdown=true");
586: } catch (SQLException se) {
587: if (se.getSQLState() == null
588: || !(se.getSQLState().equals("08006"))) {
589: // database was not shutdown properly
590: dumpSQLException(se);
591: }
592: }
593:
594: }
595:
596: /**
597: * dump the SQLException to the standard output.
598: */
599: private void dumpSQLException(SQLException sqle) {
600:
601: org.apache.derby.tools.JDBCDisplayUtil.ShowSQLException(
602: System.out, sqle);
603: sqle.printStackTrace(System.out);
604: }
605:
606: void logMessage(String str) {
607: System.out.println(str);
608: }
609:
610: public static void main(String[] argv) throws Throwable {
611:
612: ReEncryptCrashRecovery test = new ReEncryptCrashRecovery();
613: try {
614: test.runTest();
615: } catch (SQLException sqle) {
616: org.apache.derby.tools.JDBCDisplayUtil.ShowSQLException(
617: System.out, sqle);
618: sqle.printStackTrace(System.out);
619: }
620: }
621: }
|