blob: d39a4421547b52505aa8a2f21fac5aa68ca6e4f3 [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.cache30;
import static org.apache.geode.test.dunit.LogWriterUtils.getLogWriter;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.apache.geode.cache.AttributesFactory;
import org.apache.geode.cache.Cache;
import org.apache.geode.cache.CacheException;
import org.apache.geode.cache.CacheListener;
import org.apache.geode.cache.Operation;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.RegionAttributes;
import org.apache.geode.cache.RegionEvent;
import org.apache.geode.cache.RegionMembershipListener;
import org.apache.geode.cache.util.RegionMembershipListenerAdapter;
import org.apache.geode.distributed.ConfigurationProperties;
import org.apache.geode.distributed.DistributedMember;
import org.apache.geode.distributed.internal.DistributionAdvisor.Profile;
import org.apache.geode.distributed.internal.membership.gms.MembershipManagerHelper;
import org.apache.geode.internal.cache.CacheDistributionAdvisor.CacheProfile;
import org.apache.geode.internal.cache.DistributedRegion;
import org.apache.geode.test.awaitility.GeodeAwaitility;
import org.apache.geode.test.dunit.Host;
import org.apache.geode.test.dunit.LogWriterUtils;
import org.apache.geode.test.dunit.VM;
import org.apache.geode.test.dunit.WaitCriterion;
import org.apache.geode.test.dunit.cache.internal.JUnit4CacheTestCase;
import org.apache.geode.test.junit.categories.MembershipTest;
/**
* Test {@link RegionMembershipListener}
*
* @since GemFire 5.0
*/
@Category({MembershipTest.class})
public class RegionMembershipListenerDUnitTest extends JUnit4CacheTestCase {
private transient MyRML myListener;
private transient MyRML mySRListener;
private transient Region r; // root region
private transient Region sr; // subregion
protected transient DistributedMember otherId;
public RegionMembershipListenerDUnitTest() {
super();
}
@Override
public final void postSetUp() throws Exception {
DistributedRegion.TEST_HOOK_ADD_PROFILE = true;
}
@Override
public final void postTearDownCacheTestCase() throws Exception {
DistributedRegion.TEST_HOOK_ADD_PROFILE = false;
}
@Override
public Properties getDistributedSystemProperties() {
Properties props = super.getDistributedSystemProperties();
props.put(ConfigurationProperties.ENABLE_NETWORK_PARTITION_DETECTION, "false");
return props;
}
protected VM getOtherVm() {
Host host = Host.getHost(0);
return host.getVM(0);
}
private void initOtherId() {
VM vm = getOtherVm();
vm.invoke(new CacheSerializableRunnable("Connect") {
@Override
public void run2() throws CacheException {
getCache();
}
});
this.otherId = vm.invoke(() -> getSystem().getDistributedMember());
}
protected void createRootOtherVm(final String rName) {
VM vm = getOtherVm();
vm.invoke(new CacheSerializableRunnable("create root") {
@Override
public void run2() throws CacheException {
Region r = createRootRegion(rName, createRootRegionAttributes(null));
r.createSubregion("mysub", createSubRegionAttributes(null));
}
});
}
protected RegionAttributes createRootRegionAttributes(CacheListener[] cacheListeners) {
AttributesFactory af = new AttributesFactory();
if (cacheListeners != null) {
af.initCacheListeners(cacheListeners);
}
return af.create();
}
protected RegionAttributes createSubRegionAttributes(CacheListener[] cacheListeners) {
return createRootRegionAttributes(cacheListeners);
}
protected void destroyRootOtherVm(final String rName) {
VM vm = getOtherVm();
vm.invoke(new CacheSerializableRunnable("local destroy root") {
@Override
public void run2() throws CacheException {
getRootRegion(rName).localDestroyRegion();
}
});
}
protected void closeRootOtherVm(final String rName) {
VM vm = getOtherVm();
vm.invoke(new CacheSerializableRunnable("close root") {
@Override
public void run2() throws CacheException {
getRootRegion(rName).close();
}
});
}
private void closeCacheOtherVm() {
VM vm = getOtherVm();
vm.invoke(new CacheSerializableRunnable("close cache") {
@Override
public void run2() throws CacheException {
getCache().close();
}
});
}
private void crashCacheOtherVm() {
VM vm = getOtherVm();
vm.invoke(new CacheSerializableRunnable("crash cache") {
@Override
public void run2() throws CacheException {
// shut down the gms before the distributed system to simulate
// a crash. In post-5.1.x, this could use SystemFailure.initFailure()
MembershipManagerHelper.crashDistributedSystem(getCache().getDistributedSystem());
}
});
}
protected void createRootRegionWithListener(String rName) throws CacheException {
int to = getOpTimeout();
this.myListener = new MyRML(to);
this.r =
createRootRegion(rName, createRootRegionAttributes(new CacheListener[] {this.myListener}));
this.mySRListener = new MyRML(to);
this.sr = this.r.createSubregion("mysub",
createSubRegionAttributes(new CacheListener[] {this.mySRListener}));
}
public int getOpTimeout() {
return getSystem().getDistributionManager().getConfig().getMemberTimeout() * 3;
}
////////////////////// Test Methods //////////////////////
/**
* tests {@link RegionMembershipListener#initialMembers}
*/
@Test
public void testInitialMembers() throws CacheException {
final String rName = getUniqueName();
initOtherId();
createRootRegionWithListener(rName);
assertInitialMembers(null);
createRootOtherVm(rName);
// now close the region in the controller
// and recreate it and see if initMembers includes otherId
closeRoots();
createRootRegionWithListener(rName);
assertInitialMembers(this.otherId);
}
protected void closeRoots() {
this.r.close();
}
protected List<DistributedMember> assertInitialMembers(final DistributedMember expectedId) {
final List<DistributedMember> l;
if (expectedId == null) {
l = Arrays.asList(new DistributedMember[] {});
} else {
l = Arrays.asList(new DistributedMember[] {expectedId});
}
assertTrue(this.myListener.lastOpWasInitialMembers());
assertEquals(l, this.myListener.getInitialMembers());
assertTrue(this.mySRListener.lastOpWasInitialMembers());
assertEquals(l, this.mySRListener.getInitialMembers());
// test new methods added for #43098
if (expectedId != null) {
Cache cache = (Cache) this.r.getRegionService();
// assertIndexDetailsEquals(l, new ArrayList(cache.getMembers()));
assertEquals(l, new ArrayList(cache.getMembers(this.r)));
assertEquals(l, new ArrayList(cache.getMembers(this.sr)));
}
return l;
}
/**
* tests {@link RegionMembershipListener#afterRemoteRegionCreate}
*/
@Test
public void testCreate() throws CacheException {
final String rName = getUniqueName();
initOtherId();
createRootRegionWithListener(rName);
createRootOtherVm(rName);
assertTrue(this.myListener.lastOpWasCreate());
{
RegionEvent e = this.myListener.getLastEvent();
assertEquals(this.otherId, e.getDistributedMember());
assertEquals(Operation.REGION_CREATE, e.getOperation());
assertEquals(true, e.isOriginRemote());
assertEquals(false, e.getOperation().isDistributed());
assertEquals(this.r, e.getRegion());
// the test now uses a hook to get the member's DistributionAdvisor profile in the callback
// argument
assertTrue(e.getCallbackArgument() instanceof Profile);
// assertIndexDetailsEquals(null, e.getCallbackArgument());
}
assertTrue(this.mySRListener.lastOpWasCreate());
{
RegionEvent e = this.mySRListener.getLastEvent();
assertEquals(this.otherId, e.getDistributedMember());
assertEquals(Operation.REGION_CREATE, e.getOperation());
assertEquals(true, e.isOriginRemote());
assertEquals(false, e.getOperation().isDistributed());
assertEquals(this.sr, e.getRegion());
// the test now uses a hook to get the member's DistributionAdvisor profile in the callback
// argument
assertTrue(e.getCallbackArgument() instanceof Profile);
// assertIndexDetailsEquals(null, e.getCallbackArgument());
}
}
/**
* tests {@link RegionMembershipListener#afterRemoteRegionDeparture}
*/
@Test
public void testDeparture() throws CacheException {
final String rName = getUniqueName();
initOtherId();
createRootRegionWithListener(rName);
createRootOtherVm(rName);
assertOpWasCreate();
destroyRootOtherVm(rName);
assertOpWasDeparture();
createRootOtherVm(rName);
assertOpWasCreate();
closeRootOtherVm(rName);
assertOpWasDeparture();
createRootOtherVm(rName);
assertOpWasCreate();
closeCacheOtherVm();
assertOpWasDeparture();
}
protected void assertOpWasDeparture() {
assertTrue(this.myListener.lastOpWasDeparture());
assertEventStuff(this.myListener.getLastEvent(), this.otherId, this.r);
assertTrue(this.mySRListener.lastOpWasDeparture());
assertEventStuff(this.mySRListener.getLastEvent(), this.otherId, this.sr);
}
public static void assertEventStuff(RegionEvent e, DistributedMember em, Region er) {
assertEquals(em, e.getDistributedMember());
assertEquals(Operation.REGION_CLOSE, e.getOperation());
assertEquals(true, e.isOriginRemote());
assertEquals(false, e.getOperation().isDistributed());
assertEquals(er, e.getRegion());
assertEquals(null, e.getCallbackArgument());
}
protected void assertOpWasCreate() {
assertTrue(this.myListener.lastOpWasCreate());
assertTrue(this.mySRListener.lastOpWasCreate());
}
/**
* tests {@link RegionMembershipListener#afterRemoteRegionCrash}
*/
@Test
public void testCrash() throws CacheException {
final String rName = getUniqueName();
initOtherId();
createRootRegionWithListener(rName);
createRootOtherVm(rName);
try {
assertTrue(this.myListener.lastOpWasCreate()); // root region
assertTrue(this.mySRListener.lastOpWasCreate()); // subregion
MembershipManagerHelper.inhibitForcedDisconnectLogging(true);
crashCacheOtherVm();
int to = getOpTimeout();
MembershipManagerHelper.waitForMemberDeparture(basicGetSystem(), this.otherId, to);
this.myListener.waitForCrashOp();
{
RegionEvent e = this.myListener.getLastEvent();
assertEquals(this.otherId, e.getDistributedMember());
assertEquals(Operation.REGION_CLOSE, e.getOperation());
assertEquals(true, e.isOriginRemote());
assertEquals(false, e.getOperation().isDistributed());
assertEquals(this.r, e.getRegion());
assertEquals(null, e.getCallbackArgument());
}
this.mySRListener.waitForCrashOp();
{
RegionEvent e = this.mySRListener.getLastEvent();
assertEquals(this.otherId, e.getDistributedMember());
assertEquals(Operation.REGION_CLOSE, e.getOperation());
assertEquals(true, e.isOriginRemote());
assertEquals(false, e.getOperation().isDistributed());
assertEquals(this.sr, e.getRegion());
assertEquals(null, e.getCallbackArgument());
}
} finally {
MembershipManagerHelper.inhibitForcedDisconnectLogging(false);
disconnectAllFromDS();
}
}
enum Op {
Initial, Create, Departure, Crash
};
public class MyRML extends RegionMembershipListenerAdapter {
private final int timeOut;
volatile Op lastOp;
private volatile RegionEvent lastEvent;
private volatile DistributedMember[] initialMembers;
private volatile boolean memberInitialized; // was the member initialized when
// afterRemoteRegionCreate was called?
public MyRML(int to) {
this.timeOut = to;
}
public boolean lastOpWasInitialMembers() {
return waitForOp(Op.Initial);
}
public boolean lastOpWasCreate() {
boolean result = waitForOp(Op.Create);
if (result) {
// bug #44684 - afterRemoteRegionCreate should not be invoked before the remote region is
// initialized
assertTrue(
"bug #44684 - expected remote member to be initialized when afterRemoteRegionCreate was invoked",
this.memberInitialized);
}
return result;
}
public boolean lastOpWasDeparture() {
return waitForOp(Op.Departure);
}
public String getOpName(Op op) {
if (op == null) {
return "null";
}
switch (op) {
case Initial:
return "Initial";
case Create:
return "Create";
case Departure:
return "Departure";
case Crash:
return "Crash";
default:
return "Unknown";
}
}
private boolean waitForOp(final Op op) {
WaitCriterion ev = new WaitCriterion() {
@Override
public boolean done() {
return MyRML.this.lastOp == op;
}
@Override
public String description() {
return MyRML.this.toString() + " waiting for Op " + op + " when lastOp was "
+ getOpName(MyRML.this.lastOp);
}
};
getLogWriter().info(this.toString() + " waiting for Op " + getOpName(op)
+ " when lastOp was " + getOpName(this.lastOp));
GeodeAwaitility.await().untilAsserted(ev);
assertEquals(op, this.lastOp);
return true;
}
public void waitForCrashOp() {
waitForOp(Op.Crash);
}
public RegionEvent getLastEvent() {
return this.lastEvent;
}
public List getInitialMembers() {
return Arrays.asList(this.initialMembers);
}
@Override
public void initialMembers(Region r, DistributedMember[] initialMembers) {
this.lastOp = Op.Initial;
this.lastEvent = null;
this.initialMembers = initialMembers;
LogWriterUtils.getLogWriter()
.info(this.toString() + " received initialMembers notification for region " + r
+ " with members " + Arrays.deepToString(initialMembers));
}
@Override
public void afterRemoteRegionCreate(RegionEvent event) {
this.lastOp = Op.Create;
this.lastEvent = event;
CacheProfile cacheProfile = (CacheProfile) event.getCallbackArgument();
if (cacheProfile != null) {
this.memberInitialized = cacheProfile.regionInitialized;
if (!this.memberInitialized) {
LogWriterUtils.getLogWriter().warning(
"afterRemoteRegionCreate invoked when member is not done initializing!",
new Exception("stack trace"));
}
LogWriterUtils.getLogWriter().info(
this.toString() + " received afterRemoteRegionCreate notification for event " + event);
} else {
LogWriterUtils.getLogWriter().warning(
"afterRemoteRegionCreate was expecting a profile in the event callback but there was none. "
+ " This indicates a problem with the test hook DistributedRegion.TEST_HOOK_ADD_PROFILE");
}
}
@Override
public void afterRemoteRegionDeparture(RegionEvent event) {
this.lastOp = Op.Departure;
this.lastEvent = event;
LogWriterUtils.getLogWriter().info(
this.toString() + " received afterRemoteRegionDeparture notification for event " + event);
}
@Override
public void afterRemoteRegionCrash(RegionEvent event) {
this.lastOp = Op.Crash;
this.lastEvent = event;
LogWriterUtils.getLogWriter().info(
this.toString() + " received afterRemoteRegionCrash notification for event " + event);
}
}
}