/* * 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 org.junit.experimental.categories.Category; import org.junit.Test; import static org.junit.Assert.*; import org.apache.geode.distributed.*; import org.apache.geode.test.dunit.cache.internal.JUnit4CacheTestCase; import org.apache.geode.test.dunit.internal.JUnit4DistributedTestCase; import org.apache.geode.test.junit.categories.DistributedTest; import java.util.*; 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.internal.DistributionAdvisor.Profile; import org.apache.geode.distributed.internal.InternalDistributedSystem; 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.internal.cache.GemFireCacheImpl; 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.Wait; import org.apache.geode.test.dunit.WaitCriterion; /** * Test {@link RegionMembershipListener} * * @since GemFire 5.0 */ @Category(DistributedTest.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") { public void run2() throws CacheException { getCache(); } }); this.otherId = (DistributedMember) vm .invoke(() -> RegionMembershipListenerDUnitTest.getVMDistributedMember()); } protected void createRootOtherVm(final String rName) { VM vm = getOtherVm(); vm.invoke(new CacheSerializableRunnable("create root") { 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") { public void run2() throws CacheException { getRootRegion(rName).localDestroyRegion(); } }); } protected void closeRootOtherVm(final String rName) { VM vm = getOtherVm(); vm.invoke(new CacheSerializableRunnable("close root") { public void run2() throws CacheException { getRootRegion(rName).close(); } }); } private void closeCacheOtherVm() { VM vm = getOtherVm(); vm.invoke(new CacheSerializableRunnable("close cache") { public void run2() throws CacheException { getCache().close(); } }); } private void crashCacheOtherVm() { VM vm = getOtherVm(); vm.invoke(new CacheSerializableRunnable("crash cache") { 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() GemFireCacheImpl cache = (GemFireCacheImpl) getCache(); InternalDistributedSystem sys = (InternalDistributedSystem) cache.getDistributedSystem(); MembershipManagerHelper.crashDistributedSystem(sys); } }); } public static DistributedMember getVMDistributedMember() { return InternalDistributedSystem.getAnyInstance().getDistributedMember(); } 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.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.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.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() { public boolean done() { return MyRML.this.lastOp == op; } public String description() { return MyRML.this.toString() + " waiting for Op " + op + " when lastOp was " + getOpName(MyRML.this.lastOp); } }; LogWriterUtils.getLogWriter().info(this.toString() + " waiting for Op " + getOpName(op) + " when lastOp was " + getOpName(this.lastOp)); Wait.waitForCriterion(ev, this.timeOut, 200, true); 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); } 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)); } 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"); } } public void afterRemoteRegionDeparture(RegionEvent event) { this.lastOp = Op.Departure; this.lastEvent = event; LogWriterUtils.getLogWriter().info( this.toString() + " received afterRemoteRegionDeparture notification for event " + event); } public void afterRemoteRegionCrash(RegionEvent event) { this.lastOp = Op.Crash; this.lastEvent = event; LogWriterUtils.getLogWriter().info( this.toString() + " received afterRemoteRegionCrash notification for event " + event); } } }