blob: bae7b46d092b874971c89b0c335594ce24c8d732 [file] [log] [blame]
/*
* 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.geode.test.dunit.internal;
import static org.apache.geode.distributed.ConfigurationProperties.LOCATORS;
import static org.apache.geode.distributed.ConfigurationProperties.LOG_FILE;
import static org.apache.geode.distributed.ConfigurationProperties.MCAST_PORT;
import static org.apache.geode.distributed.ConfigurationProperties.STATISTIC_ARCHIVE_FILE;
import static org.apache.geode.distributed.internal.InternalConfigurationPersistenceService.CLUSTER_CONFIG_DISK_DIR_PREFIX;
import static org.apache.geode.test.dunit.DistributedTestUtils.getAllDistributedSystemProperties;
import static org.apache.geode.test.dunit.DistributedTestUtils.unregisterInstantiatorsInThisVM;
import static org.apache.geode.test.dunit.Invoke.invokeInEveryVM;
import static org.apache.geode.test.dunit.Invoke.invokeInLocator;
import static org.apache.geode.test.dunit.LogWriterUtils.getLogWriter;
import static org.junit.Assert.assertNotNull;
import java.io.File;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Map.Entry;
import java.util.Properties;
import org.apache.commons.io.FileUtils;
import org.apache.logging.log4j.Logger;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.apache.geode.cache.Cache;
import org.apache.geode.cache.Region;
import org.apache.geode.distributed.DistributedSystem;
import org.apache.geode.distributed.internal.DistributionConfig;
import org.apache.geode.distributed.internal.DistributionMessageObserver;
import org.apache.geode.distributed.internal.InternalDistributedSystem;
import org.apache.geode.internal.cache.GemFireCacheImpl;
import org.apache.geode.internal.cache.HARegion;
import org.apache.geode.internal.cache.PartitionedRegion;
import org.apache.geode.logging.internal.log4j.api.LogService;
import org.apache.geode.test.dunit.DUnitBlackboard;
import org.apache.geode.test.dunit.Disconnect;
import org.apache.geode.test.dunit.Host;
import org.apache.geode.test.dunit.cache.internal.JUnit4CacheTestCase;
import org.apache.geode.test.dunit.rules.ClusterStartupRule;
import org.apache.geode.test.dunit.rules.DistributedRule;
import org.apache.geode.test.junit.rules.serializable.SerializableTestName;
import org.apache.geode.test.version.VersionManager;
/**
* @deprecated Please use {@link DistributedRule} and Geode User APIs or {@link ClusterStartupRule}
* instead.
*/
public abstract class JUnit4DistributedTestCase implements DistributedTestFixture, Serializable {
private static final Logger logger = LogService.getLogger();
/** This VM's connection to the distributed system */
protected static InternalDistributedSystem system;
private static Class lastSystemCreatedInTest;
private static Properties lastSystemProperties;
private static volatile String testMethodName;
private static DUnitBlackboard blackboard;
private static final boolean logPerTest = Boolean.getBoolean("dunitLogPerTest");
private final DistributedTestFixture distributedTestFixture;
/**
* Constructs a new distributed test. All JUnit 4 test classes need to have a no-arg constructor.
*/
public JUnit4DistributedTestCase() {
this(null);
}
/**
* This constructor should only be used internally, not by tests.
*/
protected JUnit4DistributedTestCase(final DistributedTestFixture distributedTestFixture) {
if (distributedTestFixture == null) {
this.distributedTestFixture = this;
} else {
this.distributedTestFixture = distributedTestFixture;
}
}
@Rule
public SerializableTestName testNameForDistributedTestCase = new SerializableTestName();
@BeforeClass
public static final void initializeDistributedTestCase() {
DUnitLauncher.launchIfNeeded(true);
}
public static final void initializeBlackboard() {
blackboard = new DUnitBlackboard();
}
protected static void deleteBACKUPDiskStoreFile(final File file) {
if (file.getName().startsWith("BACKUPDiskStore-")
|| file.getName().startsWith(CLUSTER_CONFIG_DISK_DIR_PREFIX)) {
FileUtils.deleteQuietly(file);
}
}
public static final void cleanDiskDirs() {
FileUtils.deleteQuietly(JUnit4CacheTestCase.getDiskDir());
FileUtils.deleteQuietly(JUnit4CacheTestCase.getDiskDir());
Arrays.stream(new File(".").listFiles()).forEach(file -> deleteBACKUPDiskStoreFile(file));
}
@Override
public final String getName() {
if (this.distributedTestFixture != this) {
return this.distributedTestFixture.getName();
}
return this.testNameForDistributedTestCase.getMethodName();
}
public final Class<? extends DistributedTestFixture> getTestClass() {
return this.distributedTestFixture.getClass();
}
/**
* @deprecated Please override {@link #getDistributedSystemProperties()} instead.
*/
@Deprecated
public final void setSystem(final Properties props, final DistributedSystem ds) {
// TODO: override getDistributedSystemProperties and then delete
system = (InternalDistributedSystem) ds;
lastSystemProperties = props;
lastSystemCreatedInTest = getTestClass(); // used to be getDeclaringClass()
}
/**
* Returns this VM's connection to the distributed system. If necessary, the connection will be
* lazily created using the given {@code Properties}.
*
* <p>
* Do not override this method. Override {@link #getDistributedSystemProperties()} instead.
*
* <p>
* Note: "final" was removed so that WANTestBase can override this method. This was part of the xd
* offheap merge.
*
* @since GemFire 3.0
*/
public final InternalDistributedSystem getSystem(final Properties props) {
// Setting the default disk store name is now done in setUp
if (system == null) {
system = InternalDistributedSystem.getAnyInstance();
}
if (system == null || !system.isConnected()) {
// Figure out our distributed system properties
Properties p = getAllDistributedSystemProperties(props);
lastSystemCreatedInTest = getTestClass(); // used to be getDeclaringClass()
if (logPerTest) {
String testMethod = getTestMethodName();
String testName = lastSystemCreatedInTest.getName() + '-' + testMethod;
String oldLogFile = p.getProperty(LOG_FILE);
p.put(LOG_FILE, oldLogFile.replace("system.log", testName + ".log"));
String oldStatFile = p.getProperty(STATISTIC_ARCHIVE_FILE);
p.put(STATISTIC_ARCHIVE_FILE, oldStatFile.replace("statArchive.gfs", testName + ".gfs"));
}
if (VersionManager.getInstance().getCurrentVersionOrdinal() < 75) {
p.remove("validate-serializable-objects");
p.remove("serializable-object-filter");
}
system = (InternalDistributedSystem) DistributedSystem.connect(p);
lastSystemProperties = p;
} else {
boolean needNewSystem = false;
if (!getTestClass().equals(lastSystemCreatedInTest)) { // used to be getDeclaringClass()
Properties newProps = getAllDistributedSystemProperties(props);
needNewSystem = !newProps.equals(lastSystemProperties);
if (needNewSystem) {
getLogWriter()
.info("Test class has changed and the new DS properties are not an exact match. "
+ "Forcing DS disconnect. Old props = " + lastSystemProperties + "new props="
+ newProps);
}
} else {
Properties activeProps = system.getConfig().toProperties();
for (Entry<Object, Object> entry : props.entrySet()) {
String key = (String) entry.getKey();
if (key.startsWith("security-")) {
continue;
}
String value = (String) entry.getValue();
if (!value.equals(activeProps.getProperty(key))) {
needNewSystem = true;
getLogWriter().info("Forcing DS disconnect. For property " + key + " old value = "
+ activeProps.getProperty(key) + " new value = " + value);
break;
}
}
try {
activeProps = system.getConfig().toSecurityProperties();
for (Entry<Object, Object> entry : props.entrySet()) {
String key = (String) entry.getKey();
if (!key.startsWith("security-")) {
continue;
}
String value = (String) entry.getValue();
if (!value.equals(activeProps.getProperty(key))) {
needNewSystem = true;
getLogWriter().info("Forcing DS disconnect. For property " + key + " old value = "
+ activeProps.getProperty(key) + " new value = " + value);
break;
}
}
} catch (NoSuchMethodError e) {
if (VersionManager.getInstance().getCurrentVersionOrdinal() >= 85) {
throw new IllegalStateException("missing method", e);
}
}
}
if (needNewSystem) {
// the current system does not meet our needs to disconnect and
// call recursively to get a new system.
getLogWriter().info("Disconnecting from current DS in order to make a new one");
disconnectFromDS();
getSystem(props);
}
}
return system;
}
/**
* Returns this VM's connection to the distributed system. If necessary, the connection will be
* lazily created using the {@code Properties} returned by
* {@link #getDistributedSystemProperties()}.
*
* <p>
* Do not override this method. Override {@link #getDistributedSystemProperties()} instead.
*
* @see #getSystem(Properties)
*
* @since GemFire 3.0
*/
public final InternalDistributedSystem getSystem() {
return getSystem(getDistributedSystemProperties());
}
public final InternalDistributedSystem basicGetSystem() {
return system;
}
public final void nullSystem() { // TODO: delete
system = null;
}
public static final InternalDistributedSystem getSystemStatic() {
return system;
}
/**
* Returns a loner distributed system that isn't connected to other vms.
*
* @since GemFire 6.5
*/
public final InternalDistributedSystem getLonerSystem() {
Properties props = getDistributedSystemProperties();
props.put(MCAST_PORT, "0");
props.put(LOCATORS, "");
return getSystem(props);
}
/**
* Returns whether or this VM is connected to a {@link DistributedSystem}.
*/
public final boolean isConnectedToDS() {
return system != null && system.isConnected();
}
/**
* Returns a {@code Properties} object used to configure a connection to a
* {@link DistributedSystem}. Unless overridden, this method will return an empty
* {@code Properties} object.
*
* <p>
* Override this as needed. Default implementation returns empty {@code Properties}.
*
* @since GemFire 3.0
*/
@Override
public Properties getDistributedSystemProperties() {
if (this.distributedTestFixture != this) {
return this.distributedTestFixture.getDistributedSystemProperties();
}
return defaultGetDistributedSystemProperties();
}
final Properties defaultGetDistributedSystemProperties() {
return new Properties();
}
public static final void disconnectAllFromDS() {
Disconnect.disconnectAllFromDS();
}
/**
* Disconnects this VM from the distributed system
*/
public static final void disconnectFromDS() {
if (system != null) {
system.disconnect();
system = null;
}
Disconnect.disconnectFromDS();
}
/**
* Returns a DUnitBlackboard that can be used to pass data between VMs and synchronize actions.
*
* @return the blackboard
*/
public static DUnitBlackboard getBlackboard() {
return blackboard;
}
public static final String getTestMethodName() {
return testMethodName;
}
private static final void setTestMethodName(final String testMethodName) {
JUnit4DistributedTestCase.testMethodName = testMethodName;
}
/**
* Returns a unique name for this test method. It is based on the name of the class as well as the
* name of the method.
*/
public final String getUniqueName() {
assertNotNull(getName());
return getTestClass().getSimpleName() + "_" + getName();
}
/**
* Sets up the DistributedTestCase.
*
* <p>
* Do not override this method. Override {@link #preSetUp()} with work that needs to occur before
* setUp() or override {@link #postSetUp()} with work that needs to occur after setUp().
*/
@Before
public final void setUpDistributedTestCase() throws Exception {
preSetUp();
doSetUpDistributedTestCase();
postSetUp();
}
/**
* Sets up DistributedTest in controller and remote VMs. This includes the defining the test name,
* setting the default disk store name, logging the test history, and capturing a creation stack
* for detecting the source of incompatible DistributedSystem connections.
*
* <p>
* Do not override this method.
*/
private final void doSetUpDistributedTestCase() {
final String className = getTestClass().getCanonicalName();
final String methodName = getName();
TestHistoryLogger.logTestHistory(getTestClass().getSimpleName(), methodName);
setUpVM(methodName, getDefaultDiskStoreName(0, -1, className, methodName));
for (int hostIndex = 0; hostIndex < Host.getHostCount(); hostIndex++) {
Host host = Host.getHost(hostIndex);
for (int vmIndex = 0; vmIndex < host.getVMCount(); vmIndex++) {
final String vmDefaultDiskStoreName =
getDefaultDiskStoreName(hostIndex, vmIndex, className, methodName);
host.getVM(vmIndex).invoke("setupVM", () -> setUpVM(methodName, vmDefaultDiskStoreName));
}
}
logTestStart();
}
/**
* {@code preSetUp()} is invoked before {@link #doSetUpDistributedTestCase()}.
*
* <p>
* Override this as needed. Default implementation is empty.
*/
@Override
public void preSetUp() throws Exception {
if (this.distributedTestFixture != this) {
this.distributedTestFixture.preSetUp();
}
}
/**
* {@code postSetUp()} is invoked after {@link #doSetUpDistributedTestCase()}.
*
* <p>
* Override this as needed. Default implementation is empty.
*/
@Override
public void postSetUp() throws Exception {
if (this.distributedTestFixture != this) {
this.distributedTestFixture.postSetUp();
}
}
private static final String getDefaultDiskStoreName(final int hostIndex, final int vmIndex,
final String className, final String methodName) {
return "DiskStore-" + String.valueOf(hostIndex) + "-" + String.valueOf(vmIndex) + "-"
+ className + "." + methodName; // used to be getDeclaringClass()
}
private static final void setUpVM(final String methodName, final String defaultDiskStoreName) {
assertNotNull("methodName must not be null", methodName);
assertNotNull("defaultDiskStoreName must not be null", defaultDiskStoreName);
setTestMethodName(methodName);
GemFireCacheImpl.setDefaultDiskStoreName(defaultDiskStoreName);
setUpCreationStackGenerator();
}
private final void logTestStart() {
System.out.println(
"\n\n[setup] START TEST " + getTestClass().getSimpleName() + "." + testMethodName + "\n\n");
}
private static final void setUpCreationStackGenerator() {
// the following is moved from InternalDistributedSystem to fix #51058
InternalDistributedSystem.TEST_CREATION_STACK_GENERATOR
.set(new InternalDistributedSystem.CreationStackGenerator() {
@Override
public Throwable generateCreationStack(final DistributionConfig config) {
StringBuilder sb = new StringBuilder();
String[] validAttributeNames = config.getAttributeNames();
for (String attName : validAttributeNames) {
Object actualAtt = config.getAttributeObject(attName);
String actualAttStr = actualAtt.toString();
sb.append(" ");
sb.append(attName);
sb.append("=\"");
if (actualAtt.getClass().isArray()) {
actualAttStr = InternalDistributedSystem.arrayToString(actualAtt);
}
sb.append(actualAttStr);
sb.append("\"");
sb.append("\n");
}
return new Throwable(
"Creating distributed system with the following configuration:\n" + sb.toString());
}
});
}
/**
* Tears down the DistributedTestCase.
*
* <p>
* Do not override this method. Override {@link #preTearDown()} with work that needs to occur
* before tearDown() or override {@link #postTearDown()} with work that needs to occur after
* tearDown().
*/
@After
public final void tearDownDistributedTestCase() throws Exception {
try {
try {
preTearDownAssertions();
} finally {
preTearDown();
doTearDownDistributedTestCase();
}
} finally {
postTearDown();
postTearDownAssertions();
System.out.println(
"\n\n[setup] END TEST " + getTestClass().getSimpleName() + "." + testMethodName + "\n\n");
}
}
private final void doTearDownDistributedTestCase() throws Exception {
invokeInEveryVM("tearDownCreationStackGenerator", () -> tearDownCreationStackGenerator());
if (logPerTest) {
disconnectAllFromDS();
}
cleanupAllVms();
if (!getDistributedSystemProperties().isEmpty()) {
disconnectAllFromDS();
}
}
/**
* {@code preTearDown()} is invoked before {@link #doTearDownDistributedTestCase()}.
*
* <p>
* Override this as needed. Default implementation is empty.
*/
@Override
public void preTearDown() throws Exception {
if (this.distributedTestFixture != this) {
this.distributedTestFixture.preTearDown();
}
}
/**
* {@code postTearDown()} is invoked after {@link #doTearDownDistributedTestCase()}.
*
* <p>
* Override this as needed. Default implementation is empty.
*/
@Override
public void postTearDown() throws Exception {
if (this.distributedTestFixture != this) {
this.distributedTestFixture.postTearDown();
}
}
@Override
public void preTearDownAssertions() throws Exception {
if (this.distributedTestFixture != this) {
this.distributedTestFixture.preTearDownAssertions();
}
}
@Override
public void postTearDownAssertions() throws Exception {
if (this.distributedTestFixture != this) {
this.distributedTestFixture.postTearDownAssertions();
}
}
public static final void cleanupAllVms() {
tearDownVM();
invokeInEveryVM("tearDownVM", () -> tearDownVM());
invokeInLocator(() -> {
DistributionMessageObserver.setInstance(null);
unregisterInstantiatorsInThisVM();
});
DUnitLauncher.closeAndCheckForSuspects();
}
private static final void tearDownVM() {
closeCache();
DistributedRule.TearDown.tearDownInVM();
cleanDiskDirs();
}
// TODO: this should move to CacheTestCase
private static final void closeCache() {
GemFireCacheImpl cache = GemFireCacheImpl.getInstance();
if (cache != null && !cache.isClosed()) {
destroyRegions(cache);
cache.close();
}
}
// TODO: this should move to CacheTestCase
protected static final void destroyRegions(final Cache cache) {
if (cache != null && !cache.isClosed()) {
// try to destroy the root regions first so that we clean up any persistent files.
for (Region<?, ?> root : cache.rootRegions()) {
String regionFullPath = root == null ? null : root.getFullPath();
// for colocated regions you can't locally destroy a partitioned region.
if (root.isDestroyed() || root instanceof HARegion || root instanceof PartitionedRegion) {
continue;
}
try {
root.localDestroyRegion("teardown");
} catch (Throwable t) {
logger.error("Failure during tearDown destroyRegions for " + regionFullPath, t);
}
}
}
}
private static final void tearDownCreationStackGenerator() {
InternalDistributedSystem.TEST_CREATION_STACK_GENERATOR
.set(InternalDistributedSystem.DEFAULT_CREATION_STACK_GENERATOR);
}
}