| /* |
| |
| Derby - Class org.apache.derbyTesting.functionTests.tests.replicationTests.Derby5937SlaveShutdownTest |
| |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| */ |
| |
| package org.apache.derbyTesting.functionTests.tests.replicationTests; |
| |
| import java.io.File; |
| import java.sql.Connection; |
| import java.sql.SQLException; |
| import javax.sql.DataSource; |
| import junit.framework.Test; |
| import org.apache.derbyTesting.functionTests.util.PrivilegedFileOpsForTests; |
| import org.apache.derbyTesting.junit.BaseJDBCTestCase; |
| import org.apache.derbyTesting.junit.BaseTestCase; |
| import org.apache.derbyTesting.junit.BaseTestSuite; |
| import org.apache.derbyTesting.junit.JDBCDataSource; |
| import org.apache.derbyTesting.junit.NetworkServerTestSetup; |
| import org.apache.derbyTesting.junit.SecurityManagerSetup; |
| import org.apache.derbyTesting.junit.TestConfiguration; |
| |
| /** |
| * <p> |
| * Regression test case for DERBY-5937. After fail-over, the slave database |
| * will leak a file handle for the active log file. |
| * </p> |
| * |
| * <p> |
| * The test case will set up replication between a master database and a |
| * slave database, perform fail-over to the slave, and then shut down the |
| * slave database. Finally, it attempts to delete the slave database. On |
| * Windows, this fails if DERBY-5937 is not fixed, because one of the log |
| * files is still held open, and Windows doesn't allow deletion of open |
| * files. |
| * </p> |
| */ |
| public class Derby5937SlaveShutdownTest extends BaseJDBCTestCase { |
| |
| private static final String MASTER_DB = "d5937-master-db"; |
| private static final String SLAVE_DB = "d5937-slave-db"; |
| |
| private static final String FAILOVER_SUCCESS = "XRE20"; |
| private static final String DB_SHUTDOWN_SUCCESS = "08006"; |
| |
| private static final long RETRY_INTERVAL = 50L; |
| |
| public Derby5937SlaveShutdownTest(String name) { |
| super(name); |
| } |
| |
| public static Test suite() { |
| //DERBY-5975 test fails intermittently with weme causing a hang. |
| // Likely a jvm issue, so don't run on that OS... |
| if (BaseTestCase.isJ9Platform()) |
| { |
| Test test = new BaseTestSuite("Derby5937SlaveShutdownTest"); |
| return test; |
| } |
| Class klass = Derby5937SlaveShutdownTest.class; |
| // The default security policy doesn't allow derby.jar to do |
| // networking, which is needed for replication, so install a custom |
| // policy for this test. |
| return new SecurityManagerSetup( |
| TestConfiguration.singleUseDatabaseDecorator( |
| TestConfiguration.embeddedSuite(klass), MASTER_DB), |
| klass.getName().replace('.', '/') + ".policy", true); |
| } |
| |
| public void testSlaveFailoverLeak() throws Exception { |
| // First establish a connection so that the database is created. |
| getConnection().close(); |
| |
| // Then shut down the database cleanly so that it can be used |
| // to seed the replication slave. |
| final TestConfiguration config = TestConfiguration.getCurrent(); |
| config.shutdownDatabase(); |
| |
| // Copy the database to the slave. |
| final String masterDb = config.getDatabasePath(MASTER_DB); |
| final String slaveDb = config.getDatabasePath(SLAVE_DB); |
| PrivilegedFileOpsForTests.copy(new File(masterDb), new File(slaveDb)); |
| |
| // And start the slave. |
| DataSource startSlaveDS = JDBCDataSource.getDataSource(SLAVE_DB); |
| JDBCDataSource.setBeanProperty(startSlaveDS, "connectionAttributes", |
| "startSlave=true;slaveHost=" + config.getHostName() + |
| ";slavePort=" + config.getPort()); |
| SlaveThread slave = new SlaveThread(startSlaveDS); |
| slave.start(); |
| |
| // Start the master. This will fail until the slave is up, so do |
| // it in a loop until successful or time runs out. |
| DataSource startMasterDS = JDBCDataSource.getDataSource(); |
| JDBCDataSource.setBeanProperty(startMasterDS, "connectionAttributes", |
| "startMaster=true;slaveHost=" + config.getHostName() + |
| ";slavePort=" + config.getPort()); |
| long giveUp = |
| System.currentTimeMillis() + NetworkServerTestSetup.getWaitTime(); |
| Connection c = null; |
| while (c == null) { |
| try { |
| c = startMasterDS.getConnection(); |
| } catch (SQLException sqle) { |
| slave.checkError(); // Exit early if the slave has failed |
| if (System.currentTimeMillis() > giveUp) { |
| fail("Master won't start", sqle); |
| } else { |
| println("Retrying after startMaster failed with: " + sqle); |
| Thread.sleep(RETRY_INTERVAL); |
| } |
| } |
| } |
| c.close(); |
| |
| // Wait for the slave thread to complete, which it will do once |
| // it's connected to the master. |
| slave.join(); |
| slave.checkError(); |
| |
| // Perform fail-over. |
| DataSource failoverDS = JDBCDataSource.getDataSource(); |
| JDBCDataSource.setBeanProperty( |
| failoverDS, "connectionAttributes", "failover=true"); |
| try { |
| failoverDS.getConnection(); |
| fail("failover should receive exception"); |
| } catch (SQLException sqle) { |
| assertSQLState(FAILOVER_SUCCESS, sqle); |
| } |
| |
| // Shut down the slave database. This will fail until failover is |
| // complete, so do it in a loop until successful or time runs out. |
| giveUp = |
| System.currentTimeMillis() + NetworkServerTestSetup.getWaitTime(); |
| DataSource slaveShutdownDS = JDBCDataSource.getDataSource(SLAVE_DB); |
| JDBCDataSource.setBeanProperty( |
| slaveShutdownDS, "shutdownDatabase", "shutdown"); |
| while (true) { |
| try { |
| slaveShutdownDS.getConnection(); |
| fail("Shutdown of slave database didn't throw an exception"); |
| } catch (SQLException sqle) { |
| if (DB_SHUTDOWN_SUCCESS.equals(sqle.getSQLState())) { |
| // The expected shutdown exception was thrown. Break out |
| // of the loop. |
| break; |
| } else if (System.currentTimeMillis() > giveUp) { |
| fail("Could not shut down slave database", sqle); |
| } else { |
| println("Retrying after failover failed with: " + sqle); |
| Thread.sleep(RETRY_INTERVAL); |
| } |
| } |
| } |
| |
| // This call used to fail on Windows because one of the log files |
| // was still open. |
| assertDirectoryDeleted(new File(slaveDb)); |
| } |
| |
| /** |
| * Helper thread which starts a replication slave and blocks until the |
| * slave is connected to a master database. |
| */ |
| private class SlaveThread extends Thread { |
| |
| private final DataSource ds; |
| private volatile Throwable error; |
| |
| SlaveThread(DataSource ds) { |
| this.ds = ds; |
| } |
| |
| public void run() { |
| try { |
| run_(); |
| } catch (Throwable t) { |
| error = t; |
| } |
| } |
| |
| private void run_() throws Exception { |
| println("Slave thread started."); |
| |
| try { |
| ds.getConnection(); |
| fail("startSlave should throw exception"); |
| } catch (SQLException sqle) { |
| assertSQLState("XRE08", sqle); |
| } |
| } |
| |
| void checkError() { |
| if (error != null) { |
| fail("Slave thread failed", error); |
| } |
| } |
| } |
| } |