/* * 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.cache.util; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Properties; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.jmock.Expectations; import org.jmock.Mockery; import org.jmock.Sequence; import org.jmock.api.Invocation; import org.jmock.lib.action.CustomAction; import org.jmock.lib.concurrent.Synchroniser; import org.jmock.lib.legacy.ClassImposteriser; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.experimental.categories.Category; import org.apache.geode.GemFireConfigException; import org.apache.geode.cache.control.RebalanceFactory; import org.apache.geode.cache.control.RebalanceOperation; import org.apache.geode.cache.control.RebalanceResults; import org.apache.geode.cache.partition.PartitionMemberInfo; import org.apache.geode.cache.util.AutoBalancer.AuditScheduler; import org.apache.geode.cache.util.AutoBalancer.CacheOperationFacade; import org.apache.geode.cache.util.AutoBalancer.GeodeCacheFacade; import org.apache.geode.cache.util.AutoBalancer.OOBAuditor; import org.apache.geode.cache.util.AutoBalancer.SizeBasedOOBAuditor; import org.apache.geode.cache.util.AutoBalancer.TimeProvider; import org.apache.geode.internal.cache.GemFireCacheImpl; import org.apache.geode.internal.cache.PRHARedundancyProvider; import org.apache.geode.internal.cache.PartitionedRegion; import org.apache.geode.internal.cache.control.InternalResourceManager; import org.apache.geode.internal.cache.partitioned.InternalPRInfo; import org.apache.geode.internal.cache.partitioned.LoadProbe; import org.apache.geode.test.junit.categories.IntegrationTest; /** * UnitTests for AutoBalancer. All collaborators should be mocked. */ @Category(IntegrationTest.class) public class AutoBalancerJUnitTest { Mockery mockContext; CacheOperationFacade mockCacheFacade; OOBAuditor mockAuditor; AuditScheduler mockScheduler; TimeProvider mockClock; @Before public void setupMock() { mockContext = new Mockery() { { setImposteriser(ClassImposteriser.INSTANCE); setThreadingPolicy(new Synchroniser()); } }; mockCacheFacade = mockContext.mock(CacheOperationFacade.class); mockAuditor = mockContext.mock(OOBAuditor.class); mockScheduler = mockContext.mock(AuditScheduler.class); mockClock = mockContext.mock(TimeProvider.class); } @After public void validateMock() { mockContext.assertIsSatisfied(); mockContext = null; } @Test public void testLockStatExecuteInSequence() throws InterruptedException { final Sequence sequence = mockContext.sequence("sequence"); mockContext.checking(new Expectations() { { oneOf(mockCacheFacade).acquireAutoBalanceLock(); inSequence(sequence); will(returnValue(true)); oneOf(mockCacheFacade).incrementAttemptCounter(); inSequence(sequence); oneOf(mockCacheFacade).getTotalTransferSize(); inSequence(sequence); will(returnValue(0L)); } }); AutoBalancer balancer = new AutoBalancer(null, null, null, mockCacheFacade); balancer.getOOBAuditor().execute(); } @Test public void testAcquireLockAfterReleasedRemotely() throws InterruptedException { final Sequence sequence = mockContext.sequence("sequence"); mockContext.checking(new Expectations() { { oneOf(mockCacheFacade).acquireAutoBalanceLock(); inSequence(sequence); will(returnValue(false)); oneOf(mockCacheFacade).acquireAutoBalanceLock(); inSequence(sequence); will(returnValue(true)); oneOf(mockCacheFacade).incrementAttemptCounter(); oneOf(mockCacheFacade).getTotalTransferSize(); will(returnValue(0L)); } }); AutoBalancer balancer = new AutoBalancer(null, null, null, mockCacheFacade); balancer.getOOBAuditor().execute(); balancer.getOOBAuditor().execute(); } @Test public void testFailExecuteIfLockedElsewhere() throws InterruptedException { mockContext.checking(new Expectations() { { oneOf(mockCacheFacade).acquireAutoBalanceLock(); will(returnValue(false)); // no other methods, rebalance, will be called } }); AutoBalancer balancer = new AutoBalancer(null, null, null, mockCacheFacade); balancer.getOOBAuditor().execute(); } @Test(expected = IllegalStateException.class) public void testNoCacheError() { AutoBalancer balancer = new AutoBalancer(); OOBAuditor auditor = balancer.getOOBAuditor(); auditor.execute(); } @Test public void testOOBWhenBelowSizeThreshold() { final long totalSize = 1000L; final Map<PartitionedRegion, InternalPRInfo> details = new HashMap<>(); mockContext.checking(new Expectations() { { allowing(mockCacheFacade).getRegionMemberDetails(); will(returnValue(details)); // first run oneOf(mockCacheFacade).getTotalDataSize(details); will(returnValue(totalSize)); oneOf(mockCacheFacade).getTotalTransferSize(); // half of threshold limit will(returnValue((AutoBalancer.DEFAULT_SIZE_THRESHOLD_PERCENT * totalSize / 100) / 2)); // second run oneOf(mockCacheFacade).getTotalTransferSize(); // nothing to transfer will(returnValue(0L)); } }); AutoBalancer balancer = new AutoBalancer(null, null, null, mockCacheFacade); Properties config = getBasicConfig(); config.put(AutoBalancer.MINIMUM_SIZE, "10"); balancer.init(config); SizeBasedOOBAuditor auditor = (SizeBasedOOBAuditor) balancer.getOOBAuditor(); // first run assertFalse(auditor.needsRebalancing()); // second run assertFalse(auditor.needsRebalancing()); } @Test public void testOOBWhenAboveThresholdButBelowMin() { final long totalSize = 1000L; mockContext.checking(new Expectations() { { // first run oneOf(mockCacheFacade).getTotalTransferSize(); // twice threshold will(returnValue((AutoBalancer.DEFAULT_SIZE_THRESHOLD_PERCENT * totalSize / 100) * 2)); // second run oneOf(mockCacheFacade).getTotalTransferSize(); // more than total size will(returnValue(2 * totalSize)); } }); AutoBalancer balancer = new AutoBalancer(null, null, null, mockCacheFacade); Properties config = getBasicConfig(); config.put(AutoBalancer.MINIMUM_SIZE, "" + (totalSize * 5)); balancer.init(config); SizeBasedOOBAuditor auditor = (SizeBasedOOBAuditor) balancer.getOOBAuditor(); // first run assertFalse(auditor.needsRebalancing()); // second run assertFalse(auditor.needsRebalancing()); } @Test public void testOOBWhenAboveThresholdAndMin() { final long totalSize = 1000L; final Map<PartitionedRegion, InternalPRInfo> details = new HashMap<>(); mockContext.checking(new Expectations() { { allowing(mockCacheFacade).getRegionMemberDetails(); will(returnValue(details)); // first run oneOf(mockCacheFacade).getTotalDataSize(details); will(returnValue(totalSize)); oneOf(mockCacheFacade).getTotalTransferSize(); // twice threshold will(returnValue((AutoBalancer.DEFAULT_SIZE_THRESHOLD_PERCENT * totalSize / 100) * 2)); // second run oneOf(mockCacheFacade).getTotalDataSize(details); will(returnValue(totalSize)); oneOf(mockCacheFacade).getTotalTransferSize(); // more than total size will(returnValue(2 * totalSize)); } }); AutoBalancer balancer = new AutoBalancer(null, null, null, mockCacheFacade); Properties config = getBasicConfig(); config.put(AutoBalancer.MINIMUM_SIZE, "10"); balancer.init(config); SizeBasedOOBAuditor auditor = (SizeBasedOOBAuditor) balancer.getOOBAuditor(); // first run assertTrue(auditor.needsRebalancing()); // second run assertTrue(auditor.needsRebalancing()); } @Test(expected = GemFireConfigException.class) public void testInvalidSchedule() { String someSchedule = "X Y * * * *"; Properties props = new Properties(); props.put(AutoBalancer.SCHEDULE, someSchedule); AutoBalancer autoR = new AutoBalancer(); autoR.init(props); } @Test public void testOOBAuditorInit() { AutoBalancer balancer = new AutoBalancer(); balancer.init(getBasicConfig()); SizeBasedOOBAuditor auditor = (SizeBasedOOBAuditor) balancer.getOOBAuditor(); assertEquals(AutoBalancer.DEFAULT_SIZE_THRESHOLD_PERCENT, auditor.getSizeThreshold()); assertEquals(AutoBalancer.DEFAULT_MINIMUM_SIZE, auditor.getSizeMinimum()); Properties props = getBasicConfig(); props.put(AutoBalancer.SIZE_THRESHOLD_PERCENT, "17"); props.put(AutoBalancer.MINIMUM_SIZE, "10"); balancer = new AutoBalancer(); balancer.init(props); auditor = (SizeBasedOOBAuditor) balancer.getOOBAuditor(); assertEquals(17, auditor.getSizeThreshold()); assertEquals(10, auditor.getSizeMinimum()); } @Test(expected = GemFireConfigException.class) public void testConfigTransferThresholdNegative() { AutoBalancer balancer = new AutoBalancer(); Properties props = getBasicConfig(); props.put(AutoBalancer.SIZE_THRESHOLD_PERCENT, "-1"); balancer.init(props); } @Test(expected = GemFireConfigException.class) public void testConfigSizeMinNegative() { AutoBalancer balancer = new AutoBalancer(); Properties props = getBasicConfig(); props.put(AutoBalancer.MINIMUM_SIZE, "-1"); balancer.init(props); } @Test(expected = GemFireConfigException.class) public void testConfigTransferThresholdZero() { AutoBalancer balancer = new AutoBalancer(); Properties props = getBasicConfig(); props.put(AutoBalancer.SIZE_THRESHOLD_PERCENT, "0"); balancer.init(props); } @Test(expected = GemFireConfigException.class) public void testConfigTransferThresholdTooHigh() { AutoBalancer balancer = new AutoBalancer(); Properties props = getBasicConfig(); props.put(AutoBalancer.SIZE_THRESHOLD_PERCENT, "100"); balancer.init(props); } @Test public void testAutoBalancerInit() { final String someSchedule = "1 * * * 1 *"; final Properties props = new Properties(); props.put(AutoBalancer.SCHEDULE, someSchedule); props.put(AutoBalancer.SIZE_THRESHOLD_PERCENT, 17); mockContext.checking(new Expectations() { { oneOf(mockScheduler).init(someSchedule); oneOf(mockAuditor).init(props); } }); AutoBalancer autoR = new AutoBalancer(mockScheduler, mockAuditor, null, null); autoR.init(props); } @Test public void testMinimalConfiguration() { AutoBalancer autoR = new AutoBalancer(); try { autoR.init(null); fail(); } catch (GemFireConfigException e) { // expected } Properties props = getBasicConfig(); autoR.init(props); } @Test public void testFacadeTotalTransferSize() throws Exception { assertEquals(12345, getFacadeForResourceManagerOps(true).getTotalTransferSize()); } @Test public void testFacadeRebalance() throws Exception { getFacadeForResourceManagerOps(false).rebalance(); } private GeodeCacheFacade getFacadeForResourceManagerOps(final boolean simulate) throws Exception { final GemFireCacheImpl mockCache = mockContext.mock(GemFireCacheImpl.class); final InternalResourceManager mockRM = mockContext.mock(InternalResourceManager.class); final RebalanceFactory mockRebalanceFactory = mockContext.mock(RebalanceFactory.class); final RebalanceOperation mockRebalanceOperation = mockContext.mock(RebalanceOperation.class); final RebalanceResults mockRebalanceResults = mockContext.mock(RebalanceResults.class); mockContext.checking(new Expectations() { { oneOf(mockCache).isClosed(); will(returnValue(false)); oneOf(mockCache).getResourceManager(); will(returnValue(mockRM)); oneOf(mockRM).createRebalanceFactory(); will(returnValue(mockRebalanceFactory)); if (simulate) { oneOf(mockRebalanceFactory).simulate(); } else { oneOf(mockRebalanceFactory).start(); } will(returnValue(mockRebalanceOperation)); oneOf(mockRebalanceOperation).getResults(); will(returnValue(mockRebalanceResults)); if (simulate) { atLeast(1).of(mockRebalanceResults).getTotalBucketTransferBytes(); will(returnValue(12345L)); } allowing(mockRebalanceResults); } }); GeodeCacheFacade facade = new GeodeCacheFacade(mockCache); return facade; } @Test public void testFacadeTotalBytesNoRegion() { CacheOperationFacade facade = new AutoBalancer().getCacheOperationFacade(); assertEquals(0, facade.getTotalDataSize(new HashMap<PartitionedRegion, InternalPRInfo>())); } @Test public void testFacadeCollectMemberDetailsNoRegion() { final GemFireCacheImpl mockCache = mockContext.mock(GemFireCacheImpl.class); mockContext.checking(new Expectations() { { oneOf(mockCache).isClosed(); will(returnValue(false)); oneOf(mockCache).getPartitionedRegions(); will(returnValue(new HashSet<PartitionedRegion>())); } }); GeodeCacheFacade facade = new GeodeCacheFacade(mockCache); assertEquals(0, facade.getRegionMemberDetails().size()); } @Test public void testFacadeCollectMemberDetails2Regions() { final GemFireCacheImpl mockCache = mockContext.mock(GemFireCacheImpl.class); final InternalResourceManager mockRM = mockContext.mock(InternalResourceManager.class); final LoadProbe mockProbe = mockContext.mock(LoadProbe.class); final PartitionedRegion mockR1 = mockContext.mock(PartitionedRegion.class, "r1"); final PartitionedRegion mockR2 = mockContext.mock(PartitionedRegion.class, "r2"); final HashSet<PartitionedRegion> regions = new HashSet<>(); regions.add(mockR1); regions.add(mockR2); final PRHARedundancyProvider mockRedundancyProviderR1 = mockContext.mock(PRHARedundancyProvider.class, "prhaR1"); final InternalPRInfo mockR1PRInfo = mockContext.mock(InternalPRInfo.class, "prInforR1"); final PRHARedundancyProvider mockRedundancyProviderR2 = mockContext.mock(PRHARedundancyProvider.class, "prhaR2"); final InternalPRInfo mockR2PRInfo = mockContext.mock(InternalPRInfo.class, "prInforR2"); mockContext.checking(new Expectations() { { oneOf(mockCache).isClosed(); will(returnValue(false)); oneOf(mockCache).getPartitionedRegions(); will(returnValue(regions)); exactly(2).of(mockCache).getResourceManager(); will(returnValue(mockRM)); exactly(2).of(mockRM).getLoadProbe(); will(returnValue(mockProbe)); allowing(mockR1).getFullPath(); oneOf(mockR1).getRedundancyProvider(); will(returnValue(mockRedundancyProviderR1)); allowing(mockR2).getFullPath(); oneOf(mockR2).getRedundancyProvider(); will(returnValue(mockRedundancyProviderR2)); oneOf(mockRedundancyProviderR1).buildPartitionedRegionInfo(with(true), with(any(LoadProbe.class))); will(returnValue(mockR1PRInfo)); oneOf(mockRedundancyProviderR2).buildPartitionedRegionInfo(with(true), with(any(LoadProbe.class))); will(returnValue(mockR2PRInfo)); } }); GeodeCacheFacade facade = new GeodeCacheFacade(mockCache); Map<PartitionedRegion, InternalPRInfo> map = facade.getRegionMemberDetails(); assertNotNull(map); assertEquals(2, map.size()); assertEquals(map.get(mockR1), mockR1PRInfo); assertEquals(map.get(mockR2), mockR2PRInfo); } @Test public void testFacadeTotalBytes2Regions() { final PartitionedRegion mockR1 = mockContext.mock(PartitionedRegion.class, "r1"); final PartitionedRegion mockR2 = mockContext.mock(PartitionedRegion.class, "r2"); final HashSet<PartitionedRegion> regions = new HashSet<>(); regions.add(mockR1); regions.add(mockR2); final InternalPRInfo mockR1PRInfo = mockContext.mock(InternalPRInfo.class, "prInforR1"); final PartitionMemberInfo mockR1M1Info = mockContext.mock(PartitionMemberInfo.class, "r1M1"); final PartitionMemberInfo mockR1M2Info = mockContext.mock(PartitionMemberInfo.class, "r1M2"); final HashSet<PartitionMemberInfo> r1Members = new HashSet<>(); r1Members.add(mockR1M1Info); r1Members.add(mockR1M2Info); final InternalPRInfo mockR2PRInfo = mockContext.mock(InternalPRInfo.class, "prInforR2"); final PartitionMemberInfo mockR2M1Info = mockContext.mock(PartitionMemberInfo.class, "r2M1"); final HashSet<PartitionMemberInfo> r2Members = new HashSet<>(); r2Members.add(mockR2M1Info); final Map<PartitionedRegion, InternalPRInfo> details = new HashMap<>(); details.put(mockR1, mockR1PRInfo); details.put(mockR2, mockR2PRInfo); mockContext.checking(new Expectations() { { allowing(mockR1).getFullPath(); allowing(mockR2).getFullPath(); oneOf(mockR1PRInfo).getPartitionMemberInfo(); will(returnValue(r1Members)); atLeast(1).of(mockR1M1Info).getSize(); will(returnValue(123L)); atLeast(1).of(mockR1M2Info).getSize(); will(returnValue(74L)); oneOf(mockR2PRInfo).getPartitionMemberInfo(); will(returnValue(r2Members)); atLeast(1).of(mockR2M1Info).getSize(); will(returnValue(3475L)); } }); GeodeCacheFacade facade = new GeodeCacheFacade() { @Override public Map<PartitionedRegion, InternalPRInfo> getRegionMemberDetails() { return details; } }; assertEquals(123 + 74 + 3475, facade.getTotalDataSize(details)); } @Test public void testAuditorInvocation() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(3); mockContext.checking(new Expectations() { { oneOf(mockAuditor).init(with(any(Properties.class))); exactly(2).of(mockAuditor).execute(); allowing(mockClock).currentTimeMillis(); will(new CustomAction("returnTime") { @Override public Object invoke(Invocation invocation) throws Throwable { latch.countDown(); return 990L; } }); } }); Properties props = AutoBalancerJUnitTest.getBasicConfig(); assertEquals(3, latch.getCount()); AutoBalancer autoR = new AutoBalancer(null, mockAuditor, mockClock, null); autoR.init(props); assertTrue(latch.await(1, TimeUnit.SECONDS)); } @Test public void destroyAutoBalancer() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(2); final CountDownLatch timerLatch = new CountDownLatch(1); final int timer = 20; // simulate 20 milliseconds mockContext.checking(new Expectations() { { oneOf(mockAuditor).init(with(any(Properties.class))); allowing(mockAuditor).execute(); allowing(mockClock).currentTimeMillis(); will(new CustomAction("returnTime") { @Override public Object invoke(Invocation invocation) throws Throwable { latch.countDown(); if (latch.getCount() == 0) { assertTrue(timerLatch.await(1, TimeUnit.SECONDS)); // scheduler is destroyed before wait is over fail(); } return 1000L - timer; } }); } }); Properties props = AutoBalancerJUnitTest.getBasicConfig(); assertEquals(2, latch.getCount()); AutoBalancer autoR = new AutoBalancer(null, mockAuditor, mockClock, null); autoR.init(props); assertTrue(latch.await(1, TimeUnit.SECONDS)); // after destroy no more execute will be called. autoR.destroy(); timerLatch.countDown(); TimeUnit.MILLISECONDS.sleep(2 * timer); } static Properties getBasicConfig() { Properties props = new Properties(); // every second schedule props.put(AutoBalancer.SCHEDULE, "* 0/30 * * * ?"); return props; } }