Repository: ignite
Updated Branches:
  refs/heads/master 525a0636a -> 9d9c237d0


IGNITE-9365 - Attribute based affinity backup filter implementation - Fixes 
#4719

Signed-off-by: Valentin Kulichenko <[email protected]>


Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/9d9c237d
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/9d9c237d
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/9d9c237d

Branch: refs/heads/master
Commit: 9d9c237d0bc7ef16b3405a6cef77ab7c8dee12a3
Parents: 525a063
Author: Dave Harvey <[email protected]>
Authored: Fri Sep 21 15:44:26 2018 -0700
Committer: Valentin Kulichenko <[email protected]>
Committed: Fri Sep 21 15:44:26 2018 -0700

----------------------------------------------------------------------
 ...lusterNodeAttributeAffinityBackupFilter.java | 134 +++++++++++++++++++
 .../rendezvous/RendezvousAffinityFunction.java  |   6 +-
 ...ityFunctionBackupFilterAbstractSelfTest.java |  17 ++-
 ...deAttributeAffinityBackupFilterSelfTest.java |  58 ++++++++
 ...ousAffinityFunctionBackupFilterSelfTest.java |   2 +-
 .../testsuites/IgniteCacheTestSuite2.java       |   3 +
 6 files changed, 211 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/9d9c237d/modules/core/src/main/java/org/apache/ignite/cache/affinity/rendezvous/ClusterNodeAttributeAffinityBackupFilter.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/main/java/org/apache/ignite/cache/affinity/rendezvous/ClusterNodeAttributeAffinityBackupFilter.java
 
b/modules/core/src/main/java/org/apache/ignite/cache/affinity/rendezvous/ClusterNodeAttributeAffinityBackupFilter.java
new file mode 100644
index 0000000..7b75dbe
--- /dev/null
+++ 
b/modules/core/src/main/java/org/apache/ignite/cache/affinity/rendezvous/ClusterNodeAttributeAffinityBackupFilter.java
@@ -0,0 +1,134 @@
+/*
+ * 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.cache.affinity.rendezvous;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Objects;
+
+import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.lang.IgniteBiPredicate;
+
+/**
+ * This class can be used as a {@link 
RendezvousAffinityFunction#affinityBackupFilter } to create 
+ * cache templates in Spring that force each partition's primary and backup to 
different hardware which 
+ * is not expected to fail simultaneously, e.g., in AWS, to different 
"availability zones".  This
+ * is a per-partition selection, and different partitions may choose different 
primaries.
+ * <p>
+ * This implementation will discard backups rather than place multiple on the 
same set of nodes. This avoids
+ * trying to cram more data onto remaining nodes  when some have failed.
+ * <p>
+ * A list of node attributes to compare is provided on construction.  Note: 
"All cluster nodes,
+ * on startup, automatically register all the environment and system 
properties as node attributes."
+ * <p> 
+ * This class is constructed with a array of node attribute names, and a 
candidate node will be rejected if *any* of the 
+ * previously selected nodes for a partition have the identical values for 
*all* of those attributes on the candidate node.
+ * Another way to understand this is the set of attribute values defines the 
key of a group into which a node is placed,
+ * an the primaries and backups for a partition cannot share nodes in the same 
group.   A null attribute is treated  as
+ * a distinct value, so two nodes with a null attribute will be treated as 
having the same value.
+ * <p>
+ * Warning: the misspelling of an attribute name can cause all nodes to 
believe they have a null attribute, which would
+ * the number of cache entries seen in visor with the number of expected 
entries, e.g., SELECT COUNT(*) from YOUR_TABLE
+ * times the number of backups.
+ * </pre>
+ * <h2 class="header">Spring Example</h2>
+ * Create a partitioned cache template plate with 1 backup, where the backup 
will not be placed in the same availability zone
+ * as the primary.   Note: This example requires that the environment variable 
"AVAILABILTY_ZONE" be set appropriately on 
+ * each node via some means external to Ignite.  On AWS, some nodes might have 
AVAILABILTY_ZONE=us-east-1a and others 
+ * AVAILABILTY_ZONE=us-east-1b. 
+ * <pre name="code" class="xml">
+ * 
+ * &lt;property name="cacheConfiguration"&gt; 
+ *     &lt;list&gt; 
+ *         &lt;bean id="cache-template-bean" abstract="true" 
class="org.apache.ignite.configuration.CacheConfiguration"&gt; 
+ *             &lt;property name="name" 
value="JobcaseDefaultCacheConfig*"/&gt; 
+ *             &lt;property name="cacheMode" value="PARTITIONED" /&gt; 
+ *             &lt;property name="backups" value="1" /&gt; 
+ *             &lt;property name="affinity"&gt;
+ *                 &lt;bean 
class="org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction"&gt;
+ *                     &lt;property name="affinityBackupFilter"&gt;
+ *                         &lt;bean 
class="org.apache.ignite.cache.affinity.rendezvous.ClusterNodeAttributeAffinityBackupFilter"&gt;
+ *                            &lt;constructor-arg&gt;
+ *                                &lt;array value-type="java.lang.String"&gt;
+ *                                     &lt;!-- Backups must go to different 
AZs --&gt;
+ *                                     
&lt;value&gt;AVAILABILITY_ZONE&lt;/value&gt;
+ *                                &lt;/array&gt;
+ *                            &lt;/constructor-arg&gt;                         
          
+ *                         &lt;/bean&gt;
+ *                     &lt;/property&gt;
+ *                &lt;/bean&gt;
+ *             &lt;/property&gt;
+ *        &lt;/bean&gt; 
+ *    &lt;/list&gt; 
+ *  &lt;/property&gt; 
+ * </pre>
+ * 
+ * With more backups, multiple properties, e.g., SITE, ZONE,  could be used to 
force backups to different subgroups. 
+ */
+public class ClusterNodeAttributeAffinityBackupFilter implements 
IgniteBiPredicate<ClusterNode, List<ClusterNode>>, Serializable {
+   /**
+    * 
+    */
+   private static final long serialVersionUID = 1L;
+
+   
+   final String [] attributeNames;
+
+   /*
+    * @param attributeNames - the list of attribute names for the set of 
attributes to compare. Must be at least one.
+    */
+   ClusterNodeAttributeAffinityBackupFilter(String[] attributeNames)
+   {
+      assert attributeNames.length > 0;
+      
+      this.attributeNames = attributeNames;
+   }
+
+   /**
+    * Defines a predicate which returns {@code true} if a node is acceptable 
for a backup
+    * or {@code false} otherwise. An acceptable node is one where its set of 
attribute values
+    * is not exact match with any of the previously selected nodes.  If an 
attribute does not
+    * exist on exactly one node of a pair, then the attribute does not match.  
If the attribute
+    * does not exist both nodes of a pair, then the attribute matches.
+    * <p>
+    * Warning:  if an attribute is specified that does not exist on any node, 
then no backups
+    * will be created, because all nodes will match.
+    * <p>
+    * @param candidate A node that is a candidate for becoming a backup node 
for a partition.
+    * @param previouslySelected A list of primary/backup nodes already chosen 
for a partition.  
+    * The primary is first.
+    */
+   @Override
+   public boolean apply(ClusterNode candidate, List<ClusterNode> 
previouslySelected) {
+
+      for (ClusterNode node : previouslySelected) {
+         boolean match = true;
+         
+         for (String attribute : attributeNames) {
+               if (!Objects.equals(candidate.attribute(attribute), 
node.attribute(attribute)) ) {
+                  match = false;
+                  break;
+               }         
+         }
+         if (match)
+            return false;   
+      }
+      return true;
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/9d9c237d/modules/core/src/main/java/org/apache/ignite/cache/affinity/rendezvous/RendezvousAffinityFunction.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/main/java/org/apache/ignite/cache/affinity/rendezvous/RendezvousAffinityFunction.java
 
b/modules/core/src/main/java/org/apache/ignite/cache/affinity/rendezvous/RendezvousAffinityFunction.java
index 7b1ea59..c31fc5d 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/cache/affinity/rendezvous/RendezvousAffinityFunction.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/cache/affinity/rendezvous/RendezvousAffinityFunction.java
@@ -241,7 +241,7 @@ public class RendezvousAffinityFunction implements 
AffinityFunction, Serializabl
      * from all nodes that pass this filter. First node passed to this filter 
is a node being tested,
      * and the second parameter is a list of nodes that are already assigned 
for a given partition (primary node is the first in the list).
      * <p>
-     * Note that {@code affinityBackupFilter} is ignored if {@code 
excludeNeighbors} is set to {@code true}.
+     * Note that {@code affinityBackupFilter} is ignored if {@code 
excludeNeighbors} is set to {@code true}. 
      *
      * @return Optional backup filter.
      */
@@ -254,7 +254,9 @@ public class RendezvousAffinityFunction implements 
AffinityFunction, Serializabl
      * nodes that pass this filter. First node being passed to this filter is 
a node being tested,
      * and the second parameter is a list of nodes that are already assigned 
for a given partition (primary node is the first in the list).
      * <p>
-     * Note that {@code affinityBackupFilter} is ignored if {@code 
excludeNeighbors} is set to {@code true}.
+     * Note that {@code affinityBackupFilter} is ignored if {@code 
excludeNeighbors} is set to {@code true}.     
+     * <p>
+     * For an example filter, see {@link 
ClusterNodeAttributeAffinityBackupFilter }.  
      *
      * @param affinityBackupFilter Optional backup filter.
      * @return {@code this} for chaining.

http://git-wip-us.apache.org/repos/asf/ignite/blob/9d9c237d/modules/core/src/test/java/org/apache/ignite/cache/affinity/AffinityFunctionBackupFilterAbstractSelfTest.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/test/java/org/apache/ignite/cache/affinity/AffinityFunctionBackupFilterAbstractSelfTest.java
 
b/modules/core/src/test/java/org/apache/ignite/cache/affinity/AffinityFunctionBackupFilterAbstractSelfTest.java
index 99e80ca..9e04dbb 100644
--- 
a/modules/core/src/test/java/org/apache/ignite/cache/affinity/AffinityFunctionBackupFilterAbstractSelfTest.java
+++ 
b/modules/core/src/test/java/org/apache/ignite/cache/affinity/AffinityFunctionBackupFilterAbstractSelfTest.java
@@ -87,7 +87,7 @@ public abstract class 
AffinityFunctionBackupFilterAbstractSelfTest extends GridC
                 return backupAssignedAttribute.get(nodeAttributeVal).equals(0);
             }
         };
-
+     
     /**
      * @param nodes List of cluster nodes.
      * @return Statistic.
@@ -125,7 +125,7 @@ public abstract class 
AffinityFunctionBackupFilterAbstractSelfTest extends GridC
         if (backups < 2)
             cacheCfg.setAffinity(affinityFunction());
         else
-            cacheCfg.setAffinity(affinityFunctionWithAffinityBackupFilter());
+            
cacheCfg.setAffinity(affinityFunctionWithAffinityBackupFilter(SPLIT_ATTRIBUTE_NAME));
 
         
cacheCfg.setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC);
         cacheCfg.setRebalanceMode(SYNC);
@@ -151,7 +151,7 @@ public abstract class 
AffinityFunctionBackupFilterAbstractSelfTest extends GridC
     /**
      * @return Affinity function for test.
      */
-    protected abstract AffinityFunction 
affinityFunctionWithAffinityBackupFilter();
+    protected abstract AffinityFunction 
affinityFunctionWithAffinityBackupFilter(String attributeName);
 
     /**
      * @throws Exception If failed.
@@ -233,6 +233,11 @@ public abstract class 
AffinityFunctionBackupFilterAbstractSelfTest extends GridC
             stopAllGrids();
         }
     }
+    
+    /* Different affinityBackupFilters have different goals */
+    protected int expectedNodesForEachPartition() {
+       return backups + 1;
+    }
 
     /**
      * @throws Exception If failed.
@@ -244,15 +249,15 @@ public abstract class 
AffinityFunctionBackupFilterAbstractSelfTest extends GridC
         int partCnt = aff.partitions();
 
         IgniteCache<Object, Object> cache = grid(0).cache(DEFAULT_CACHE_NAME);
-
+        
         for (int i = 0; i < partCnt; i++) {
             Collection<ClusterNode> nodes = 
affinity(cache).mapKeyToPrimaryAndBackups(i);
 
-            assertEquals(backups + 1, nodes.size());
+            assertEquals(expectedNodesForEachPartition(), nodes.size());
 
             Map<String, Integer> stat = getAttributeStatistic(nodes);
 
-            assertEquals(stat.get(FIRST_NODE_GROUP), new Integer(2));
+            assertEquals(stat.get(FIRST_NODE_GROUP), new 
Integer(expectedNodesForEachPartition() - 2 ));
 
             assertEquals(stat.get("B"), new Integer(1));
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/9d9c237d/modules/core/src/test/java/org/apache/ignite/cache/affinity/rendezvous/ClusterNodeAttributeAffinityBackupFilterSelfTest.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/test/java/org/apache/ignite/cache/affinity/rendezvous/ClusterNodeAttributeAffinityBackupFilterSelfTest.java
 
b/modules/core/src/test/java/org/apache/ignite/cache/affinity/rendezvous/ClusterNodeAttributeAffinityBackupFilterSelfTest.java
new file mode 100644
index 0000000..f0e8720
--- /dev/null
+++ 
b/modules/core/src/test/java/org/apache/ignite/cache/affinity/rendezvous/ClusterNodeAttributeAffinityBackupFilterSelfTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.cache.affinity.rendezvous;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ignite.cache.affinity.AffinityFunction;
+import 
org.apache.ignite.cache.affinity.AffinityFunctionBackupFilterAbstractSelfTest;
+
+/**
+ * Partitioned affinity test.
+ */
+public class ClusterNodeAttributeAffinityBackupFilterSelfTest extends 
AffinityFunctionBackupFilterAbstractSelfTest {
+    /** {@inheritDoc} */
+    @Override protected AffinityFunction affinityFunction() {
+        RendezvousAffinityFunction aff = new RendezvousAffinityFunction(false);
+
+        aff.setBackupFilter(backupFilter);
+
+        return aff;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected AffinityFunction 
affinityFunctionWithAffinityBackupFilter(String attributeName) {
+        RendezvousAffinityFunction aff = new RendezvousAffinityFunction(false);
+        
+        String[] stringArray = new String[1];
+       
+        stringArray[0] = attributeName;
+        
+        aff.setAffinityBackupFilter(new 
ClusterNodeAttributeAffinityBackupFilter(stringArray));
+
+        return aff;
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected int expectedNodesForEachPartition() {
+       return 3;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/9d9c237d/modules/core/src/test/java/org/apache/ignite/cache/affinity/rendezvous/RendezvousAffinityFunctionBackupFilterSelfTest.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/test/java/org/apache/ignite/cache/affinity/rendezvous/RendezvousAffinityFunctionBackupFilterSelfTest.java
 
b/modules/core/src/test/java/org/apache/ignite/cache/affinity/rendezvous/RendezvousAffinityFunctionBackupFilterSelfTest.java
index a78c383..5a2d83a 100644
--- 
a/modules/core/src/test/java/org/apache/ignite/cache/affinity/rendezvous/RendezvousAffinityFunctionBackupFilterSelfTest.java
+++ 
b/modules/core/src/test/java/org/apache/ignite/cache/affinity/rendezvous/RendezvousAffinityFunctionBackupFilterSelfTest.java
@@ -34,7 +34,7 @@ public class RendezvousAffinityFunctionBackupFilterSelfTest 
extends AffinityFunc
     }
 
     /** {@inheritDoc} */
-    @Override protected AffinityFunction 
affinityFunctionWithAffinityBackupFilter() {
+    @Override protected AffinityFunction 
affinityFunctionWithAffinityBackupFilter(String attributeName) {
         RendezvousAffinityFunction aff = new RendezvousAffinityFunction(false);
 
         aff.setAffinityBackupFilter(affinityBackupFilter);

http://git-wip-us.apache.org/repos/asf/ignite/blob/9d9c237d/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheTestSuite2.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheTestSuite2.java
 
b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheTestSuite2.java
index 79e1e4a..b8eb276 100644
--- 
a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheTestSuite2.java
+++ 
b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheTestSuite2.java
@@ -18,6 +18,8 @@
 package org.apache.ignite.testsuites;
 
 import junit.framework.TestSuite;
+
+import 
org.apache.ignite.cache.affinity.rendezvous.ClusterNodeAttributeAffinityBackupFilterSelfTest;
 import 
org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunctionBackupFilterSelfTest;
 import 
org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunctionExcludeNeighborsSelfTest;
 import 
org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunctionFastPowerOfTwoHashSelfTest;
@@ -263,6 +265,7 @@ public class IgniteCacheTestSuite2 extends TestSuite {
         suite.addTest(new 
TestSuite(GridCacheDhtPreloadStartStopSelfTest.class));
         suite.addTest(new TestSuite(GridCacheDhtPreloadUnloadSelfTest.class));
         suite.addTest(new 
TestSuite(RendezvousAffinityFunctionBackupFilterSelfTest.class));
+        suite.addTest(new 
TestSuite(ClusterNodeAttributeAffinityBackupFilterSelfTest.class));
         suite.addTest(new 
TestSuite(GridCachePartitionedPreloadLifecycleSelfTest.class));
         suite.addTest(new 
TestSuite(CacheLoadingConcurrentGridStartSelfTest.class));
         suite.addTest(new 
TestSuite(CacheLoadingConcurrentGridStartSelfTestAllowOverwrite.class));

Reply via email to