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 <ptupit...@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>