Skip to content

Commit 1e0e08d

Browse files
committed
Ordering support for filters
1 parent 046db6c commit 1e0e08d

9 files changed

+244
-25
lines changed

src/Orleans.Runtime/Placement/Filtering/PlacementFilterStrategy.cs

+24-1
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,35 @@ namespace Orleans.Runtime.Placement.Filtering;
66

77
public abstract class PlacementFilterStrategy
88
{
9+
public int Order { get; private set; }
10+
11+
protected PlacementFilterStrategy(int order)
12+
{
13+
Order = order;
14+
}
15+
916
/// <summary>
1017
/// Initializes an instance of this type using the provided grain properties.
1118
/// </summary>
1219
/// <param name="properties">
1320
/// The grain properties.
1421
/// </param>
15-
public virtual void Initialize(GrainProperties properties)
22+
public void Initialize(GrainProperties properties)
1623
{
24+
var orderProperty = GetPlacementFilterGrainProperty("order", properties);
25+
if (!int.TryParse(orderProperty, out var parsedOrder))
26+
{
27+
throw new ArgumentException("Invalid order property value.");
28+
}
29+
30+
Order = parsedOrder;
31+
32+
AdditionalInitialize(properties);
33+
}
34+
35+
public virtual void AdditionalInitialize(GrainProperties properties)
36+
{
37+
1738
}
1839

1940
/// <summary>
@@ -35,6 +56,8 @@ public void PopulateGrainProperties(IServiceProvider services, Type grainClass,
3556
properties[WellKnownGrainTypeProperties.PlacementFilter] = typeName;
3657
}
3758

59+
properties[$"{WellKnownGrainTypeProperties.PlacementFilter}.{typeName}.order"] = Order.ToString();
60+
3861
foreach (var additionalGrainProperty in GetAdditionalGrainProperties(services, grainClass, grainType, properties))
3962
{
4063
properties[$"{WellKnownGrainTypeProperties.PlacementFilter}.{typeName}.{additionalGrainProperty.Key}"] = additionalGrainProperty.Value;

src/Orleans.Runtime/Placement/Filtering/PlacementFilterStrategyResolver.cs

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Concurrent;
33
using System.Collections.Generic;
4+
using System.Linq;
45
using Microsoft.Extensions.DependencyInjection;
56
using Orleans.Metadata;
67

@@ -55,7 +56,14 @@ private PlacementFilterStrategy[] GetPlacementFilterStrategyInternal(GrainType g
5556
throw new KeyNotFoundException($"Could not resolve placement filter strategy {filterId} for grain type {grainType}. Ensure that dependencies for that filter have been configured in the Container. This is often through a .Use* extension method provided by the implementation.");
5657
}
5758
}
58-
return filterList.ToArray();
59+
60+
var orderedFilters = filterList.OrderBy(f => f.Order).ToArray();
61+
// check that the order is unique
62+
if (orderedFilters.Select(f => f.Order).Distinct().Count() != orderedFilters.Length)
63+
{
64+
throw new InvalidOperationException($"Placement filters for grain type {grainType} have duplicate order values. Order values must be specified if more than one filter is applied and must be unique.");
65+
}
66+
return orderedFilters;
5967
}
6068

6169
return [];

src/Orleans.Runtime/Placement/Filtering/PreferredMatchSiloMetadataPlacementFilterAttribute.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ namespace Orleans.Runtime.Placement.Filtering;
1111
/// <remarks>Example: If keys ["first","second"] are specified, then it will attempt to return only silos where both keys match the local silo's metadata values. If there are not sufficient silos matching both, then it will also include silos matching only the second key. Finally, if there are still fewer than minCandidates results then it will include all silos.</remarks>
1212
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
1313
[Experimental("ORLEANSEXP004")]
14-
public class PreferredMatchSiloMetadataPlacementFilterAttribute(string[] orderedMetadataKeys, int minCandidates = 2)
15-
: PlacementFilterAttribute(new PreferredMatchSiloMetadataPlacementFilterStrategy(orderedMetadataKeys, minCandidates));
14+
public class PreferredMatchSiloMetadataPlacementFilterAttribute(string[] orderedMetadataKeys, int minCandidates = 2, int order = 0)
15+
: PlacementFilterAttribute(new PreferredMatchSiloMetadataPlacementFilterStrategy(orderedMetadataKeys, minCandidates, order));

src/Orleans.Runtime/Placement/Filtering/PreferredMatchSiloMetadataPlacementFilterStrategy.cs

+4-5
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,18 @@
44

55
namespace Orleans.Runtime.Placement.Filtering;
66

7-
public class PreferredMatchSiloMetadataPlacementFilterStrategy(string[] orderedMetadataKeys, int minCandidates)
8-
: PlacementFilterStrategy
7+
public class PreferredMatchSiloMetadataPlacementFilterStrategy(string[] orderedMetadataKeys, int minCandidates, int order)
8+
: PlacementFilterStrategy(order)
99
{
1010
public string[] OrderedMetadataKeys { get; set; } = orderedMetadataKeys;
1111
public int MinCandidates { get; set; } = minCandidates;
1212

13-
public PreferredMatchSiloMetadataPlacementFilterStrategy() : this([], 1)
13+
public PreferredMatchSiloMetadataPlacementFilterStrategy() : this([], 1, 0)
1414
{
1515
}
1616

17-
public override void Initialize(GrainProperties properties)
17+
public override void AdditionalInitialize(GrainProperties properties)
1818
{
19-
base.Initialize(properties);
2019
OrderedMetadataKeys = GetPlacementFilterGrainProperty("ordered-metadata-keys", properties).Split(",");
2120
var minCandidatesProperty = GetPlacementFilterGrainProperty("min-candidates", properties);
2221
if (!int.TryParse(minCandidatesProperty, out var parsedMinCandidates))

src/Orleans.Runtime/Placement/Filtering/RequiredMatchSiloMetadataPlacementFilterAttribute.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ namespace Orleans.Runtime.Placement.Filtering;
99
/// <param name="metadataKeys"></param>
1010
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
1111
[Experimental("ORLEANSEXP004")]
12-
public class RequiredMatchSiloMetadataPlacementFilterAttribute(string[] metadataKeys)
13-
: PlacementFilterAttribute(new RequiredMatchSiloMetadataPlacementFilterStrategy(metadataKeys));
12+
public class RequiredMatchSiloMetadataPlacementFilterAttribute(string[] metadataKeys, int order = 0)
13+
: PlacementFilterAttribute(new RequiredMatchSiloMetadataPlacementFilterStrategy(metadataKeys, order));

src/Orleans.Runtime/Placement/Filtering/RequiredMatchSiloMetadataPlacementFilterStrategy.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@
44

55
namespace Orleans.Runtime.Placement.Filtering;
66

7-
public class RequiredMatchSiloMetadataPlacementFilterStrategy(string[] metadataKeys) : PlacementFilterStrategy
7+
public class RequiredMatchSiloMetadataPlacementFilterStrategy(string[] metadataKeys, int order)
8+
: PlacementFilterStrategy(order)
89
{
910
public string[] MetadataKeys { get; private set; } = metadataKeys;
1011

11-
public RequiredMatchSiloMetadataPlacementFilterStrategy() : this([])
12+
public RequiredMatchSiloMetadataPlacementFilterStrategy() : this([], 0)
1213
{
1314
}
1415

15-
public override void Initialize(GrainProperties properties)
16+
public override void AdditionalInitialize(GrainProperties properties)
1617
{
17-
base.Initialize(properties);
1818
MetadataKeys = GetPlacementFilterGrainProperty("metadata-keys", properties).Split(",");
1919
}
2020

test/TesterInternal/PlacementFilterTests/GrainPlacementFilterTests.cs

+192-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using Microsoft.Extensions.Configuration;
12
using Microsoft.Extensions.DependencyInjection;
23
using Orleans.Runtime.Placement;
34
using Orleans.Runtime.Placement.Filtering;
@@ -10,6 +11,9 @@ namespace UnitTests.PlacementFilterTests;
1011
[TestCategory("Placement"), TestCategory("Filters")]
1112
public class GrainPlacementFilterTests : TestClusterPerTest
1213
{
14+
public static Dictionary<string, List<string>> FilterScratchpad = new();
15+
private static Random random = new();
16+
1317
protected override void ConfigureTestCluster(TestClusterBuilder builder)
1418
{
1519
builder.AddSiloBuilderConfigurator<SiloConfigurator>();
@@ -22,10 +26,13 @@ public void Configure(ISiloBuilder hostBuilder)
2226
hostBuilder.ConfigureServices(services =>
2327
{
2428
services.AddPlacementFilter<TestPlacementFilterStrategy, TestPlacementFilterDirector>(ServiceLifetime.Singleton);
29+
services.AddPlacementFilter<OrderAPlacementFilterStrategy, OrderAPlacementFilterDirector>(ServiceLifetime.Singleton);
30+
services.AddPlacementFilter<OrderBPlacementFilterStrategy, OrderBPlacementFilterDirector>(ServiceLifetime.Singleton);
2531
});
2632
}
2733
}
2834

35+
2936
[Fact, TestCategory("Functional")]
3037
public async Task PlacementFilter_GrainWithoutFilterCanBeCalled()
3138
{
@@ -49,9 +56,79 @@ public async Task PlacementFilter_FilterIsTriggered()
4956
await task;
5057
Assert.True(triggered);
5158
}
59+
60+
[Fact, TestCategory("Functional")]
61+
public async Task PlacementFilter_OrderAB12()
62+
{
63+
await HostedCluster.WaitForLivenessToStabilizeAsync();
64+
65+
var primaryKey = random.Next();
66+
var testGrain = Client.GetGrain<ITestAB12FilteredGrain>(primaryKey);
67+
await testGrain.Ping();
68+
var list = FilterScratchpad.GetValueOrAddNew(testGrain.GetGrainId().ToString());
69+
Assert.Equal(2, list.Count);
70+
Assert.Equal("A", list[0]);
71+
Assert.Equal("B", list[1]);
72+
}
73+
74+
[Fact, TestCategory("Functional")]
75+
public async Task PlacementFilter_OrderAB21()
76+
{
77+
await HostedCluster.WaitForLivenessToStabilizeAsync();
78+
79+
var primaryKey = random.Next();
80+
var testGrain = Client.GetGrain<ITestAB21FilteredGrain>(primaryKey);
81+
await testGrain.Ping();
82+
var list = FilterScratchpad.GetValueOrAddNew(testGrain.GetGrainId().ToString());
83+
Assert.Equal(2, list.Count);
84+
Assert.Equal("B", list[0]);
85+
Assert.Equal("A", list[1]);
86+
}
87+
88+
[Fact, TestCategory("Functional")]
89+
public async Task PlacementFilter_OrderBA12()
90+
{
91+
await HostedCluster.WaitForLivenessToStabilizeAsync();
92+
93+
var primaryKey = random.Next();
94+
var testGrain = Client.GetGrain<ITestBA12FilteredGrain>(primaryKey);
95+
await testGrain.Ping();
96+
var list = FilterScratchpad.GetValueOrAddNew(testGrain.GetGrainId().ToString());
97+
Assert.Equal(2, list.Count);
98+
Assert.Equal("B", list[0]);
99+
Assert.Equal("A", list[1]);
100+
}
101+
102+
[Fact, TestCategory("Functional")]
103+
public async Task PlacementFilter_OrderBA21()
104+
{
105+
await HostedCluster.WaitForLivenessToStabilizeAsync();
106+
107+
var primaryKey = random.Next();
108+
var testGrain = Client.GetGrain<ITestBA21FilteredGrain>(primaryKey);
109+
await testGrain.Ping();
110+
111+
var list = FilterScratchpad.GetValueOrAddNew(testGrain.GetGrainId().ToString());
112+
Assert.Equal(2, list.Count);
113+
Assert.Equal("A", list[0]);
114+
Assert.Equal("B", list[1]);
115+
}
116+
117+
[Fact, TestCategory("Functional")]
118+
public async Task PlacementFilter_DuplicateOrder()
119+
{
120+
await HostedCluster.WaitForLivenessToStabilizeAsync();
121+
122+
var primaryKey = random.Next();
123+
var testGrain = Client.GetGrain<ITestDuplicateOrderFilteredGrain>(primaryKey);
124+
await Assert.ThrowsAsync<InvalidOperationException>(async () =>
125+
{
126+
await testGrain.Ping();
127+
});
128+
}
52129
}
53130

54-
[TestPlacementFilter]
131+
[TestPlacementFilter(order: 1)]
55132
public class TestFilteredGrain : Grain, ITestFilteredGrain
56133
{
57134
public Task Ping() => Task.CompletedTask;
@@ -62,9 +139,14 @@ public interface ITestFilteredGrain : IGrainWithIntegerKey
62139
Task Ping();
63140
}
64141

65-
public class TestPlacementFilterAttribute() : PlacementFilterAttribute(new TestPlacementFilterStrategy());
142+
public class TestPlacementFilterAttribute(int order) : PlacementFilterAttribute(new TestPlacementFilterStrategy(order));
66143

67-
public class TestPlacementFilterStrategy : PlacementFilterStrategy;
144+
public class TestPlacementFilterStrategy(int order) : PlacementFilterStrategy(order)
145+
{
146+
public TestPlacementFilterStrategy() : this(0)
147+
{
148+
}
149+
}
68150

69151
public class TestPlacementFilterDirector() : IPlacementFilterDirector
70152
{
@@ -76,3 +158,110 @@ public IEnumerable<SiloAddress> Filter(PlacementFilterStrategy filterStrategy, P
76158
return silos;
77159
}
78160
}
161+
162+
163+
164+
public class OrderAPlacementFilterAttribute(int order) : PlacementFilterAttribute(new OrderAPlacementFilterStrategy(order));
165+
166+
public class OrderAPlacementFilterStrategy(int order) : PlacementFilterStrategy(order)
167+
{
168+
public OrderAPlacementFilterStrategy() : this(0)
169+
{
170+
}
171+
}
172+
173+
public class OrderAPlacementFilterDirector : IPlacementFilterDirector
174+
{
175+
public IEnumerable<SiloAddress> Filter(PlacementFilterStrategy filterStrategy, PlacementTarget target, IEnumerable<SiloAddress> silos)
176+
{
177+
var dict = GrainPlacementFilterTests.FilterScratchpad;
178+
var list = dict.GetValueOrAddNew(target.GrainIdentity.ToString());
179+
list.Add("A");
180+
return silos;
181+
}
182+
}
183+
184+
185+
public class OrderBPlacementFilterAttribute(int order) : PlacementFilterAttribute(new OrderBPlacementFilterStrategy(order));
186+
187+
public class OrderBPlacementFilterStrategy(int order) : PlacementFilterStrategy(order)
188+
{
189+
190+
public OrderBPlacementFilterStrategy() : this(0)
191+
{
192+
}
193+
}
194+
195+
public class OrderBPlacementFilterDirector() : IPlacementFilterDirector
196+
{
197+
public IEnumerable<SiloAddress> Filter(PlacementFilterStrategy filterStrategy, PlacementTarget target, IEnumerable<SiloAddress> silos)
198+
{
199+
var dict = GrainPlacementFilterTests.FilterScratchpad;
200+
var list = dict.GetValueOrAddNew(target.GrainIdentity.ToString());
201+
list.Add("B");
202+
return silos;
203+
}
204+
}
205+
206+
[OrderAPlacementFilter(order: 1)]
207+
[OrderBPlacementFilter(order: 2)]
208+
public class TestAB12FilteredGrain : Grain, ITestAB12FilteredGrain
209+
{
210+
public Task Ping() => Task.CompletedTask;
211+
}
212+
213+
public interface ITestAB12FilteredGrain : IGrainWithIntegerKey
214+
{
215+
Task Ping();
216+
}
217+
218+
[OrderAPlacementFilter(order: 2)]
219+
[OrderBPlacementFilter(order: 1)]
220+
public class TestAB21FilteredGrain : Grain, ITestAB21FilteredGrain
221+
{
222+
public Task Ping() => Task.CompletedTask;
223+
}
224+
225+
public interface ITestAB21FilteredGrain : IGrainWithIntegerKey
226+
{
227+
Task Ping();
228+
}
229+
230+
[OrderBPlacementFilter(order: 1)]
231+
[OrderAPlacementFilter(order: 2)]
232+
public class TestBA12FilteredGrain : Grain, ITestBA12FilteredGrain
233+
{
234+
public Task Ping() => Task.CompletedTask;
235+
}
236+
237+
public interface ITestBA12FilteredGrain : IGrainWithIntegerKey
238+
{
239+
Task Ping();
240+
}
241+
242+
243+
[OrderBPlacementFilter(order: 2)]
244+
[OrderAPlacementFilter(order: 1)]
245+
public class TestBA121FilteredGrain : Grain, ITestBA21FilteredGrain
246+
{
247+
public Task Ping() => Task.CompletedTask;
248+
}
249+
250+
public interface ITestBA21FilteredGrain : IGrainWithIntegerKey
251+
{
252+
Task Ping();
253+
}
254+
255+
256+
257+
[OrderBPlacementFilter(order: 2)]
258+
[OrderAPlacementFilter(order: 2)]
259+
public class TestDuplicateOrderFilteredGrain : Grain, ITestDuplicateOrderFilteredGrain
260+
{
261+
public Task Ping() => Task.CompletedTask;
262+
}
263+
264+
public interface ITestDuplicateOrderFilteredGrain : IGrainWithIntegerKey
265+
{
266+
Task Ping();
267+
}

0 commit comments

Comments
 (0)