You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by pt...@apache.org on 2020/11/03 15:32:43 UTC
[ignite] branch master updated: IGNITE-2890 .NET: Add
CacheConfiguration.NodeFilter
This is an automated email from the ASF dual-hosted git repository.
ptupitsyn pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git
The following commit(s) were added to refs/heads/master by this push:
new fcc76f6 IGNITE-2890 .NET: Add CacheConfiguration.NodeFilter
fcc76f6 is described below
commit fcc76f65835b41cd952b5bfe23a897977e94b6f7
Author: Pavel Tupitsyn <pt...@apache.org>
AuthorDate: Tue Nov 3 18:31:01 2020 +0300
IGNITE-2890 .NET: Add CacheConfiguration.NodeFilter
* Add CacheConfiguration.NodeFilter
* Add AttributeNodeFilter - the only allowed IClusterNodeFilter implementation for cache node filter, maps to the same class in Java
---
.../platform/utils/PlatformConfigurationUtils.java | 49 +++-
.../apache/ignite/util/AttributeNodeFilter.java | 14 +-
.../Apache.Ignite.Core.Tests.DotNetCore.csproj | 1 +
.../Apache.Ignite.Core.Tests.csproj | 4 +
.../Cache/CacheNodeFilterTest.cs | 274 +++++++++++++++++++++
.../Config/cache-attribute-node-filter.xml | 81 ++++++
.../Config/full-config.xml | 7 +
.../IgniteConfigurationSerializerTest.cs | 9 +
.../Apache.Ignite.Core/Apache.Ignite.Core.csproj | 1 +
.../Cache/Configuration/CacheConfiguration.cs | 32 ++-
.../Cluster/AttributeNodeFilter.cs | 129 ++++++++++
.../IgniteConfigurationSection.xsd | 12 +
12 files changed, 609 insertions(+), 4 deletions(-)
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/utils/PlatformConfigurationUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/utils/PlatformConfigurationUtils.java
index 9cbef12..750c351 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/utils/PlatformConfigurationUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/utils/PlatformConfigurationUtils.java
@@ -111,6 +111,7 @@ import org.apache.ignite.spi.eventstorage.memory.MemoryEventStorageSpi;
import org.apache.ignite.ssl.SslContextFactory;
import org.apache.ignite.transactions.TransactionConcurrency;
import org.apache.ignite.transactions.TransactionIsolation;
+import org.apache.ignite.util.AttributeNodeFilter;
/**
* Configuration utils.
@@ -246,6 +247,8 @@ public class PlatformConfigurationUtils {
ccfg.setAffinity(readAffinityFunction(in));
ccfg.setExpiryPolicyFactory(readExpiryPolicyFactory(in));
+ ccfg.setNodeFilter(readAttributeNodeFilter(in));
+
int keyCnt = in.readInt();
if (keyCnt > 0) {
@@ -347,6 +350,48 @@ public class PlatformConfigurationUtils {
}
/**
+ * Reads the node filter config.
+ *
+ * @param in Stream.
+ * @return AttributeNodeFilter.
+ */
+ public static AttributeNodeFilter readAttributeNodeFilter(BinaryRawReader in) {
+ if (!in.readBoolean())
+ return null;
+
+ int cnt = in.readInt();
+
+ Map<String, Object> attrs = new HashMap<>(cnt);
+ for (int i = 0; i < cnt; i++)
+ attrs.put(in.readString(), in.readObject());
+
+ return new AttributeNodeFilter(attrs);
+ }
+
+ /**
+ * Writes the node filter.
+ * @param out Stream.
+ * @param nodeFilter IgnitePredicate.
+ */
+ private static void writeAttributeNodeFilter(BinaryRawWriter out, IgnitePredicate nodeFilter) {
+ if (!(nodeFilter instanceof AttributeNodeFilter)) {
+ out.writeBoolean(false);
+ return;
+ }
+
+ out.writeBoolean(true);
+
+ Map<String, Object> attrs = ((AttributeNodeFilter) nodeFilter).getAttrs();
+
+ out.writeInt(attrs.size());
+
+ for (Map.Entry<String, Object> entry : attrs.entrySet()) {
+ out.writeString(entry.getKey());
+ out.writeObject(entry.getValue());
+ }
+ }
+
+ /**
* Reads the eviction policy.
*
* @param in Stream.
@@ -417,7 +462,7 @@ public class PlatformConfigurationUtils {
}
/**
- * Reads the near config.
+ * Writes the near config.
*
* @param out Stream.
* @param cfg NearCacheConfiguration.
@@ -1084,6 +1129,8 @@ public class PlatformConfigurationUtils {
writeAffinityFunction(writer, ccfg.getAffinity());
writeExpiryPolicyFactory(writer, ccfg.getExpiryPolicyFactory());
+ writeAttributeNodeFilter(writer, ccfg.getNodeFilter());
+
CacheKeyConfiguration[] keys = ccfg.getKeyConfiguration();
if (keys != null) {
diff --git a/modules/core/src/main/java/org/apache/ignite/util/AttributeNodeFilter.java b/modules/core/src/main/java/org/apache/ignite/util/AttributeNodeFilter.java
index fed0d43..70c5a29 100644
--- a/modules/core/src/main/java/org/apache/ignite/util/AttributeNodeFilter.java
+++ b/modules/core/src/main/java/org/apache/ignite/util/AttributeNodeFilter.java
@@ -18,6 +18,7 @@
package org.apache.ignite.util;
import java.util.Collections;
+import java.util.HashMap;
import java.util.Map;
import org.apache.ignite.cluster.ClusterGroup;
import org.apache.ignite.cluster.ClusterNode;
@@ -42,7 +43,7 @@ import org.jetbrains.annotations.Nullable;
* attribute set to value {@code data}:
* <pre name="code" class="xml">
* <property name="nodeFilter">
- * <bean class="org.apache.ignite.util.ClusterAttributeNodeFilter">
+ * <bean class="org.apache.ignite.util.AttributeNodeFilter">
* <constructor-arg value="group"/>
* <constructor-arg value="data"/>
* </bean>
@@ -51,7 +52,7 @@ import org.jetbrains.annotations.Nullable;
* You can also specify multiple attributes for the filter:
* <pre name="code" class="xml">
* <property name="nodeFilter">
- * <bean class="org.apache.ignite.util.ClusterAttributeNodeFilter">
+ * <bean class="org.apache.ignite.util.AttributeNodeFilter">
* <constructor-arg>
* <map>
* <entry key="cpu-group" value="high"/>
@@ -105,4 +106,13 @@ public class AttributeNodeFilter implements IgnitePredicate<ClusterNode> {
return true;
}
+
+ /**
+ * Gets attributes.
+ *
+ * @return Attributes collection.
+ */
+ public Map<String, Object> getAttrs() {
+ return new HashMap<>(attrs);
+ }
}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.DotNetCore.csproj b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.DotNetCore.csproj
index ef97e9a..884009a 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.DotNetCore.csproj
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.DotNetCore.csproj
@@ -89,6 +89,7 @@
<None Update="Config\Cache\Store\cache-store-session-shared-factory.xml" CopyToOutputDirectory="PreserveNewest" />
<None Update="Config\Cache\Affinity\affinity-function.xml" CopyToOutputDirectory="PreserveNewest" />
<None Update="Config\Cache\Affinity\affinity-function2.xml" CopyToOutputDirectory="PreserveNewest" />
+ <None Update="Config\cache-attribute-node-filter.xml" CopyToOutputDirectory="PreserveNewest" />
<None Update="custom_app.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj
index 0f67ed4..b28268d 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj
@@ -112,6 +112,7 @@
<Compile Include="Binary\Serializable\DynamicFieldSetTest.cs" />
<Compile Include="Cache\Affinity\AffinityAttributeTest.cs" />
<Compile Include="Cache\CacheCreateTest.cs" />
+ <Compile Include="Cache\CacheNodeFilterTest.cs" />
<Compile Include="Cache\CacheQueryMetricsTest.cs" />
<Compile Include="Cache\CacheTimeoutOnPmeTransactionalTest.cs" />
<Compile Include="Cache\CacheTransactionGridStopTest.cs" />
@@ -439,6 +440,9 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
+ <Content Include="Config\cache-attribute-node-filter.xml">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="Config\cache-binarizables.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/CacheNodeFilterTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/CacheNodeFilterTest.cs
new file mode 100644
index 0000000..2c94775
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/CacheNodeFilterTest.cs
@@ -0,0 +1,274 @@
+/*
+ * 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.
+ */
+
+namespace Apache.Ignite.Core.Tests.Cache
+{
+ using System;
+ using System.Collections.Generic;
+ using System.IO;
+ using System.Linq;
+ using Apache.Ignite.Core.Cache.Configuration;
+ using Apache.Ignite.Core.Cluster;
+ using NUnit.Framework;
+
+ /// <summary>
+ /// Cache node filter tests.
+ /// </summary>
+ [TestFixture]
+ public class CacheNodeFilterTest
+ {
+ /** */
+ private const string AttrKey2 = "attr2";
+
+ /** */
+ private const int AttrVal2 = 3;
+
+ /** */
+ private const string AttrKey3 = "my-key";
+
+ /** */
+ private const string AttrVal3 = "my-val";
+
+ /** Grid instances. */
+ private IIgnite _grid1, _grid2, _grid3;
+
+ /// <summary>
+ /// Fixture setup.
+ /// </summary>
+ [TestFixtureSetUp]
+ public void TestFixtureSetUp()
+ {
+ var springConfig = new IgniteConfiguration(TestUtils.GetTestConfiguration())
+ {
+ SpringConfigUrl = Path.Combine("Config", "cache-attribute-node-filter.xml"),
+ IgniteInstanceName = "springGrid"
+ };
+ _grid1 = Ignition.Start(springConfig);
+
+ _grid2 = Ignition.Start(GetTestConfiguration("Ignite2",
+ new Dictionary<string, object>
+ {
+ {AttrKey2, AttrVal2}
+ }));
+
+ _grid3 = Ignition.Start(GetTestConfiguration("Ignite3",
+ new Dictionary<string, object>
+ {
+ {AttrKey2, AttrVal2},
+ {AttrKey3, AttrVal3}
+ }));
+ }
+
+ /// <summary>
+ /// Fixture tear down.
+ /// </summary>
+ [TestFixtureTearDown]
+ public void TestFixtureTearDown()
+ {
+ Ignition.StopAll(true);
+ }
+
+ /// <summary>
+ /// Gets a test configuration.
+ /// </summary>
+ /// <param name="gridName">Grid name.</param>
+ /// <param name="userAttributes">User attributes.</param>
+ /// <returns></returns>
+ private IgniteConfiguration GetTestConfiguration(string gridName, Dictionary<string, object> userAttributes)
+ {
+ IgniteConfiguration cfg = TestUtils.GetTestConfiguration(name: gridName);
+ cfg.UserAttributes = userAttributes;
+ return cfg;
+ }
+
+ /// <summary>
+ /// Tests attribute node filter with a custom user attribute name
+ /// and null value always matches.
+ /// </summary>
+ [Test]
+ public void TestUserAttributeWithNullValueMatchesAllNodes()
+ {
+ const int replicatedPartitionsCount = 512;
+
+ var cacheCfg = new CacheConfiguration
+ {
+ Name = Guid.NewGuid().ToString(),
+ NodeFilter = new AttributeNodeFilter("my.custom.attr", null),
+ CacheMode = CacheMode.Replicated,
+ };
+ var cache = _grid1.CreateCache<object, object>(cacheCfg);
+
+ var affinity = _grid1.GetAffinity(cache.Name);
+
+ Assert.AreEqual(3, _grid1.GetCluster().ForDataNodes(cache.Name).GetNodes().Count);
+
+ var parts1 = affinity.GetAllPartitions(_grid1.GetCluster().GetLocalNode());
+ var parts2 = affinity.GetAllPartitions(_grid2.GetCluster().GetLocalNode());
+ var parts3 = affinity.GetAllPartitions(_grid3.GetCluster().GetLocalNode());
+
+ Assert.AreEqual(replicatedPartitionsCount, parts1.Length);
+ Assert.AreEqual(parts1, parts2);
+ Assert.AreEqual(parts2, parts3);
+ }
+
+ /// <summary>
+ /// Tests attribute node filter matches the specified attribute.
+ /// </summary>
+ [Test]
+ public void TestAttributeNodeFilterMatchesCustomNode()
+ {
+ const int itemsCount = 10;
+
+ var cacheCfg = new CacheConfiguration
+ {
+ Name = Guid.NewGuid().ToString(),
+ NodeFilter = new AttributeNodeFilter(AttrKey2, AttrVal2),
+ CacheMode = CacheMode.Replicated,
+ };
+ var cache = _grid1.CreateCache<object, object>(cacheCfg);
+
+ for (int i = 0; i < itemsCount; i++)
+ {
+ cache.Put(i, i);
+ }
+
+ Assert.AreEqual(2, _grid1.GetCluster().ForDataNodes(cache.Name).GetNodes().Count);
+
+ Assert.AreEqual(0, cache.GetLocalEntries().Count());
+
+ var cache2 = _grid2.GetCache<object, object>(cache.Name);
+ var cache3 = _grid2.GetCache<object, object>(cache.Name);
+
+ Assert.AreEqual(itemsCount, cache2.GetLocalEntries().Count());
+ Assert.AreEqual(itemsCount, cache3.GetLocalEntries().Count());
+ }
+
+ /// <summary>
+ /// Tests node filter with multiple attributes matches single node.
+ /// </summary>
+ [Test]
+ public void TestNodeFilterWithMultipleUserAttributes()
+ {
+ var cacheCfg = new CacheConfiguration
+ {
+ Name = Guid.NewGuid().ToString(),
+ NodeFilter = new AttributeNodeFilter
+ {
+ Attributes = new Dictionary<string, object>
+ {
+ {AttrKey2, AttrVal2},
+ {AttrKey3, AttrVal3}
+ }
+ },
+ CacheMode = CacheMode.Replicated,
+ };
+ var cache = _grid1.CreateCache<object, object>(cacheCfg);
+
+ ICollection<IClusterNode> dataNodes = _grid1.GetCluster().ForDataNodes(cache.Name).GetNodes();
+ Assert.AreEqual(1, dataNodes.Count);
+ Assert.AreEqual(_grid3.GetCluster().GetLocalNode(), dataNodes.Single());
+ }
+
+ /// <summary>
+ /// Tests Java and .NET nodes can utilize the same
+ /// attribute node filter configuration.
+ /// </summary>
+ [Test]
+ public void TestSpringAttributeNodeFilter()
+ {
+ var cache = _grid1.GetCache<object, object>("cache");
+ Assert.AreEqual(2, _grid1.GetCluster().ForDataNodes(cache.Name).GetNodes().Count);
+
+ var nodeFilter = cache.GetConfiguration().NodeFilter as AttributeNodeFilter;
+ Assert.IsNotNull(nodeFilter);
+
+ Assert.AreEqual(1, nodeFilter.Attributes.Count);
+
+ var expected = new KeyValuePair<string, object>(AttrKey3, AttrVal3);
+ Assert.AreEqual(expected, nodeFilter.Attributes.Single());
+ }
+
+ /// <summary>
+ /// Tests that java node filter is not being read on .NET side.
+ /// </summary>
+ [Test]
+ public void TestJavaNodeFilterIsNotAccessedByNetConfig()
+ {
+ var cache = _grid1.GetCache<object, object>("cacheWithJavaFilter");
+
+ Assert.IsNull(cache.GetConfiguration().NodeFilter);
+ }
+
+ /// <summary>
+ /// Tests that custom node filter is not supported.
+ /// </summary>
+ [Test]
+ public void TestCustomFilterIsNotSupported()
+ {
+ var cacheCfg = new CacheConfiguration
+ {
+ Name = Guid.NewGuid().ToString(),
+ CacheMode = CacheMode.Replicated,
+ NodeFilter = new CustomFilter()
+ };
+
+ TestDelegate action = () => { _grid1.CreateCache<object, object>(cacheCfg); };
+
+ var ex = Assert.Throws<NotSupportedException>(action);
+ Assert.AreEqual("Unsupported CacheConfiguration.NodeFilter: " +
+ "'CustomFilter'. " +
+ "Only predefined implementations are supported: " +
+ "'AttributeNodeFilter'", ex.Message);
+ }
+
+ /// <summary>
+ /// Tests that attribute node filter with <code>Null</code>
+ /// Attributes value is not supported.
+ /// </summary>
+ [Test]
+ public void TestAttributeFilterWithNullValues()
+ {
+ TestDelegate action = () =>
+ {
+ var _ = new CacheConfiguration
+ {
+ NodeFilter = new AttributeNodeFilter
+ {
+ Attributes = null
+ },
+ };
+ };
+
+ var ex = Assert.Throws<ArgumentNullException>(action);
+ StringAssert.Contains("value", ex.Message);
+ }
+
+ /// <summary>
+ /// Custom node filter.
+ /// </summary>
+ public class CustomFilter : IClusterNodeFilter
+ {
+ /// <summary>
+ /// <inheritdoc cref="IClusterNodeFilter.Invoke"/>
+ /// </summary>
+ public bool Invoke(IClusterNode node)
+ {
+ return true;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Config/cache-attribute-node-filter.xml b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Config/cache-attribute-node-filter.xml
new file mode 100644
index 0000000..8b1c543
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Config/cache-attribute-node-filter.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ 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.
+-->
+
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:util="http://www.springframework.org/schema/util"
+ xsi:schemaLocation="
+ http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/util
+ http://www.springframework.org/schema/util/spring-util.xsd">
+ <bean id="grid.cfg" class="org.apache.ignite.configuration.IgniteConfiguration">
+ <property name="localHost" value="127.0.0.1"/>
+ <property name="connectorConfiguration">
+ <null/>
+ </property>
+
+ <property name="userAttributes">
+ <map>
+ <entry key="my-key" value="my-val"/>
+ </map>
+ </property>
+
+
+ <property name="cacheConfiguration">
+ <list>
+ <bean class="org.apache.ignite.configuration.CacheConfiguration">
+ <property name="name" value="cache"/>
+ <property name="nodeFilter">
+ <bean class="org.apache.ignite.util.AttributeNodeFilter">
+ <constructor-arg>
+ <map>
+ <entry key="my-key" value="my-val"/>
+ </map>
+ </constructor-arg>
+ </bean>
+ </property>
+ </bean>
+ <bean class="org.apache.ignite.configuration.CacheConfiguration">
+ <property name="name" value="cacheWithJavaFilter"/>
+ <property name="nodeFilter">
+ <bean class="org.apache.ignite.platform.PlatformAttributeNodeFilter">
+ </bean>
+ </property>
+ </bean>
+ </list>
+ </property>
+
+ <property name="discoverySpi">
+ <bean class="org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi">
+ <property name="ipFinder">
+ <bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder">
+ <property name="addresses">
+ <list>
+ <!-- In distributed environment, replace with actual host IP address. -->
+ <value>127.0.0.1:47500</value>
+ </list>
+ </property>
+ </bean>
+ </property>
+ <property name="socketTimeout" value="300" />
+ </bean>
+ </property>
+ </bean>
+</beans>
\ No newline at end of file
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Config/full-config.xml b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Config/full-config.xml
index e9e5ff4..32eed22 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Config/full-config.xml
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Config/full-config.xml
@@ -91,6 +91,13 @@
<keyConfiguration>
<cacheKeyConfiguration typeName='foo' affinityKeyFieldName='bar' />
</keyConfiguration>
+ <nodeFilter type='Apache.Ignite.Core.Cluster.AttributeNodeFilter'>
+ <attributes>
+ <pair key='myNode' value='true' />
+ <pair key='foo' />
+ <pair key='baz'>null</pair>
+ </attributes>
+ </nodeFilter>
</cacheConfiguration>
<cacheConfiguration name='secondCache' />
</cacheConfiguration>
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/IgniteConfigurationSerializerTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/IgniteConfigurationSerializerTest.cs
index 7941175..1211d38 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/IgniteConfigurationSerializerTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/IgniteConfigurationSerializerTest.cs
@@ -39,6 +39,7 @@ namespace Apache.Ignite.Core.Tests
using Apache.Ignite.Core.Cache.Eviction;
using Apache.Ignite.Core.Cache.Expiry;
using Apache.Ignite.Core.Cache.Store;
+ using Apache.Ignite.Core.Cluster;
using Apache.Ignite.Core.Ssl;
using Apache.Ignite.Core.Common;
using Apache.Ignite.Core.Communication.Tcp;
@@ -159,6 +160,14 @@ namespace Apache.Ignite.Core.Tests
Assert.IsNotNull(nearCfg);
Assert.AreEqual(7, nearCfg.NearStartSize);
+ var nodeFilter = (AttributeNodeFilter)cacheCfg.NodeFilter;
+ Assert.IsNotNull(nodeFilter);
+ var attributes = nodeFilter.Attributes.ToList();
+ Assert.AreEqual(3, nodeFilter.Attributes.Count);
+ Assert.AreEqual(new KeyValuePair<string, object>("myNode", "true"), attributes[0]);
+ Assert.AreEqual(new KeyValuePair<string, object>("foo", null), attributes[1]);
+ Assert.AreEqual(new KeyValuePair<string, object>("baz", null), attributes[2]);
+
var plc = nearCfg.EvictionPolicy as FifoEvictionPolicy;
Assert.IsNotNull(plc);
Assert.AreEqual(10, plc.BatchSize);
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj b/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
index 99bf6fd..50e3db7 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
@@ -71,6 +71,7 @@
<Compile Include="Client\ISslStreamFactory.cs" />
<Compile Include="Client\Services\IServicesClient.cs" />
<Compile Include="Client\SslStreamFactory.cs" />
+ <Compile Include="Cluster\AttributeNodeFilter.cs" />
<Compile Include="Client\Transactions\ITransactionClient.cs" />
<Compile Include="Client\Transactions\ITransactionsClient.cs" />
<Compile Include="Client\Transactions\TransactionClientConfiguration.cs" />
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/CacheConfiguration.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/CacheConfiguration.cs
index 82eb16c..56681cf 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/CacheConfiguration.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/CacheConfiguration.cs
@@ -34,6 +34,7 @@ namespace Apache.Ignite.Core.Cache.Configuration
using Apache.Ignite.Core.Cache.Eviction;
using Apache.Ignite.Core.Cache.Expiry;
using Apache.Ignite.Core.Cache.Store;
+ using Apache.Ignite.Core.Cluster;
using Apache.Ignite.Core.Common;
using Apache.Ignite.Core.Configuration;
using Apache.Ignite.Core.Impl;
@@ -249,7 +250,7 @@ namespace Apache.Ignite.Core.Cache.Configuration
/// Initializes a new instance of the <see cref="CacheConfiguration"/> class,
/// performing a deep copy of specified cache configuration.
/// </summary>
- /// <param name="other">The other configuration to perfrom deep copy from.</param>
+ /// <param name="other">The other configuration to perform deep copy from.</param>
public CacheConfiguration(CacheConfiguration other)
{
if (other != null)
@@ -340,6 +341,8 @@ namespace Apache.Ignite.Core.Cache.Configuration
AffinityFunction = AffinityFunctionSerializer.Read(reader);
ExpiryPolicyFactory = ExpiryPolicySerializer.ReadPolicyFactory(reader);
+ NodeFilter = reader.ReadBoolean() ? new AttributeNodeFilter(reader) : null;
+
KeyConfiguration = reader.ReadCollectionRaw(r => new CacheKeyConfiguration(r));
if (reader.ReadBoolean())
@@ -448,6 +451,26 @@ namespace Apache.Ignite.Core.Cache.Configuration
AffinityFunctionSerializer.Write(writer, AffinityFunction);
ExpiryPolicySerializer.WritePolicyFactory(writer, ExpiryPolicyFactory);
+ if (NodeFilter != null)
+ {
+ writer.WriteBoolean(true);
+
+ var attributeNodeFilter = NodeFilter as AttributeNodeFilter;
+ if (attributeNodeFilter == null)
+ {
+ throw new NotSupportedException(string.Format(
+ "Unsupported CacheConfiguration.NodeFilter: '{0}'. " +
+ "Only predefined implementations are supported: '{1}'",
+ NodeFilter.GetType().Name, typeof(AttributeNodeFilter).Name));
+ }
+
+ attributeNodeFilter.Write(writer);
+ }
+ else
+ {
+ writer.WriteBoolean(false);
+ }
+
writer.WriteCollectionRaw(KeyConfiguration);
if (PlatformCacheConfiguration != null)
@@ -949,5 +972,12 @@ namespace Apache.Ignite.Core.Cache.Configuration
/// </summary>
[IgniteExperimental]
public PlatformCacheConfiguration PlatformCacheConfiguration { get; set; }
+
+ /// <summary>
+ /// Gets or sets the cluster node filter. Cache will be started only on nodes that match the filter.
+ /// <para />
+ /// Only predefined implementations are supported: <see cref="AttributeNodeFilter"/>.
+ /// </summary>
+ public IClusterNodeFilter NodeFilter { get; set; }
}
}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Cluster/AttributeNodeFilter.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Cluster/AttributeNodeFilter.cs
new file mode 100644
index 0000000..4c01c0e
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Cluster/AttributeNodeFilter.cs
@@ -0,0 +1,129 @@
+/*
+ * 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.
+ */
+
+namespace Apache.Ignite.Core.Cluster
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
+ using Apache.Ignite.Core.Binary;
+ using Apache.Ignite.Core.Impl.Common;
+
+ /// <summary>
+ /// Attribute node filter.
+ /// <para />
+ /// The filter will evaluate to true if a node has all specified attributes with corresponding values.
+ /// <para />
+ /// You can set node attributes using <see cref="IgniteConfiguration.UserAttributes"/> property.
+ /// </summary>
+ public sealed class AttributeNodeFilter : IClusterNodeFilter
+ {
+ /** */
+ private IDictionary<string, object> _attributes;
+
+ /// <summary>
+ /// Attributes dictionary match.
+ /// </summary>
+ [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
+ public IDictionary<string, object> Attributes
+ {
+ get { return _attributes; }
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException("value");
+ }
+
+ _attributes = value;
+ }
+ }
+
+ /// <summary>
+ /// Initializes a new instance of <see cref="AttributeNodeFilter"/>.
+ /// </summary>
+ public AttributeNodeFilter()
+ {
+ // No-op.
+ }
+
+ /// <summary>
+ /// Initializes a new instance of <see cref="AttributeNodeFilter"/>.
+ /// </summary>
+ /// <param name="attrName">Attribute name.</param>
+ /// <param name="attrValue">Attribute value.</param>
+ public AttributeNodeFilter(string attrName, object attrValue)
+ {
+ IgniteArgumentCheck.NotNullOrEmpty(attrName, "attrName");
+
+ Attributes = new Dictionary<string, object>(1)
+ {
+ {attrName, attrValue}
+ };
+ }
+
+ /** <inheritdoc /> */
+ public bool Invoke(IClusterNode node)
+ {
+ throw new NotSupportedException("Should not be called from .NET side.");
+ }
+
+ /// <summary>
+ /// Initializes a new instance of <see cref="AttributeNodeFilter"/> from a binary reader.
+ /// </summary>
+ /// <param name="reader">Reader.</param>
+ internal AttributeNodeFilter(IBinaryRawReader reader)
+ {
+ IgniteArgumentCheck.NotNull(reader, "reader");
+
+ int count = reader.ReadInt();
+
+ Debug.Assert(count > 0);
+
+ Attributes = new Dictionary<string, object>(count);
+
+ while (count > 0)
+ {
+ string attrKey = reader.ReadString();
+ object attrVal = reader.ReadObject<object>();
+
+ Debug.Assert(attrKey != null);
+
+ Attributes[attrKey] = attrVal;
+
+ count--;
+ }
+ }
+
+ /// <summary>
+ /// Writes the instance to a writer.
+ /// </summary>
+ /// <param name="writer">Writer.</param>
+ internal void Write(IBinaryRawWriter writer)
+ {
+ writer.WriteInt(Attributes.Count);
+
+ // Does not preserve ordering, it's fine.
+ foreach (KeyValuePair<string, object> attr in Attributes)
+ {
+ writer.WriteString(attr.Key);
+ writer.WriteObject(attr.Value);
+ }
+ }
+ }
+}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfigurationSection.xsd b/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfigurationSection.xsd
index 0ab59ec..314b839 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfigurationSection.xsd
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfigurationSection.xsd
@@ -623,6 +623,18 @@
</xs:attribute>
</xs:complexType>
</xs:element>
+ <xs:element name="nodeFilter" minOccurs="0" maxOccurs="1">
+ <xs:annotation>
+ <xs:documentation>Node filter to match selected nodes. Only predefined AttributeNodeFilter is supported.</xs:documentation>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:attribute name="type" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>Assembly-qualified type name.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+ </xs:element>
<xs:element name="keyConfiguration" minOccurs="0">
<xs:annotation>
<xs:documentation>Cache key configuration collection.</xs:documentation>