/* * 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.ignite.internal.processors.cache.distributed.rebalancing; import java.util.Collection; import java.util.HashMap; import java.util.Map; import org.apache.ignite.Ignite; import org.apache.ignite.cache.CacheAtomicityMode; import org.apache.ignite.cache.CacheMode; import org.apache.ignite.cache.CacheRebalanceMode; import org.apache.ignite.cache.CacheWriteSynchronizationMode; import org.apache.ignite.cache.affinity.Affinity; import org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction; import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.configuration.CacheConfiguration; import org.apache.ignite.lang.IgnitePredicate; import org.apache.ignite.testframework.assertions.Assertion; import org.apache.ignite.testframework.junits.common.GridRollingRestartAbstractTest; /** * Test the behavior of the partition rebalancing during a rolling restart. */ public class GridCacheRebalancingPartitionDistributionTest extends GridRollingRestartAbstractTest { /** The maximum allowable deviation from a perfect distribution. */ private static final double MAX_DEVIATION = 0.20; /** Test cache name. */ private static final String CACHE_NAME = "PARTITION_DISTRIBUTION_TEST"; /** {@inheritDoc} */ @Override protected CacheConfiguration<Integer, Integer> getCacheConfiguration() { return new CacheConfiguration<Integer, Integer>(CACHE_NAME) .setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL) .setCacheMode(CacheMode.PARTITIONED) .setBackups(1) .setAffinity(new RendezvousAffinityFunction(true /* machine-safe */, 1024)) .setRebalanceMode(CacheRebalanceMode.SYNC) .setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC); } /** * The test performs rolling restart and checks no server drops out and the partitions are balanced during * redistribution. */ public void testRollingRestart() throws InterruptedException { awaitPartitionMapExchange(); rollingRestartThread.join(); assertEquals(getMaxRestarts(), rollingRestartThread.getRestartTotal()); } /** {@inheritDoc} */ @Override public int serverCount() { return 5; } /** {@inheritDoc} */ @Override public int getMaxRestarts() { return 5; } /** {@inheritDoc} */ @Override public IgnitePredicate<Ignite> getRestartCheck() { return new IgnitePredicate<Ignite>() { @Override public boolean apply(final Ignite ignite) { Collection<ClusterNode> srvs = ignite.cluster().forServers().nodes(); if (srvs.size() < serverCount()) return false; for (ClusterNode node : srvs) { int[] primaries = ignite.affinity(CACHE_NAME).primaryPartitions(node); if (primaries == null || primaries.length == 0) return false; } return true; } }; } /** {@inheritDoc} */ @Override public Assertion getRestartAssertion() { return new FairDistributionAssertion(); } /** * Assertion for {@link RollingRestartThread} to perform prior to each restart to test * the Partition Distribution. */ private class FairDistributionAssertion extends CacheNodeSafeAssertion { /** Construct a new FairDistributionAssertion. */ public FairDistributionAssertion() { super(grid(0), CACHE_NAME); } /** {@inheritDoc} */ @Override public void test() throws AssertionError { super.test(); Affinity<?> affinity = ignite().affinity(CACHE_NAME); int partCnt = affinity.partitions(); Map<ClusterNode, Integer> partMap = new HashMap<>(serverCount()); for (int i = 0; i < partCnt; i++) { ClusterNode node = affinity.mapPartitionToNode(i); int cnt = partMap.containsKey(node) ? partMap.get(node) : 0; partMap.put(node, cnt + 1); } int fairCnt = partCnt / serverCount(); for (int count : partMap.values()) { double deviation = Math.abs(fairCnt - count) / (double)fairCnt; if (deviation > MAX_DEVIATION) { throw new AssertionError("partition distribution deviation exceeded max: fair count=" + fairCnt + ", actual count=" + count + ", deviation=" + deviation); } } } } }