-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add method for persistent state facets (#105)
Co-authored-by: Alex McAuliffe <[email protected]>
- Loading branch information
Showing
14 changed files
with
481 additions
and
142 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,29 +1,111 @@ | ||
using System; | ||
using System.ComponentModel; | ||
using System.Reflection; | ||
using System.Threading.Tasks; | ||
using Moq; | ||
using Orleans.Core; | ||
using Orleans.Runtime; | ||
using Orleans.Storage; | ||
using Orleans.TestKit.Storage; | ||
|
||
namespace Orleans.TestKit | ||
{ | ||
public static class StorageExtensions | ||
{ | ||
public static TState State<TState>(this TestKitSilo silo) where TState : class, new() | ||
public static TState State<TGrain, TState>(this TestKitSilo silo) | ||
where TGrain : Grain<TState> | ||
{ | ||
if (silo == null) | ||
{ | ||
throw new ArgumentNullException(nameof(silo)); | ||
} | ||
|
||
return silo.StorageManager.GetStorage<TState>().State; | ||
return silo.StorageManager.GetGrainStorage<TGrain, TState>().State; | ||
} | ||
|
||
public static TestStorageStats StorageStats(this TestKitSilo silo) | ||
public static TestStorageStats StorageStats<TGrain, TState>(this TestKitSilo silo) | ||
where TGrain : Grain<TState> | ||
{ | ||
if (silo == null) | ||
{ | ||
throw new ArgumentNullException(nameof(silo)); | ||
} | ||
|
||
return silo.StorageManager.StorageStats; | ||
return silo.StorageManager.GetStorageStats<TGrain, TState>(); | ||
} | ||
|
||
public static IStorage<T> AddGrainState<TGrain, T>( | ||
this TestKitSilo silo, | ||
T state = default) | ||
where TGrain : Grain<T> | ||
{ | ||
if (silo == null) | ||
{ | ||
throw new ArgumentNullException(nameof(silo)); | ||
} | ||
|
||
var storage = silo.StorageManager.GetGrainStorage<TGrain, T>(); | ||
storage.State = state ?? Activator.CreateInstance<T>(); | ||
return storage; | ||
} | ||
|
||
/// <summary> | ||
/// Add persistent state to the silo for a given type. If a state is provided that will be the loaded state otherwise a new <typeparamref name="T"/> | ||
/// </summary> | ||
/// <remarks> | ||
/// If neither StateName or StorageName are provided then we resolve just based on type, otherwise we try state name and optionally a storage name | ||
/// </remarks> | ||
/// <typeparam name="T">The type of data in the state</typeparam> | ||
/// <param name="silo">The silo to add the state to</param> | ||
/// <param name="stateName">The state name on the persistent state parameter</param> | ||
/// <param name="storageName">The storage name on the persistent state parameter</param> | ||
/// <param name="state">The state to set as default if any</param> | ||
/// <returns>The persistent state</returns> | ||
public static IPersistentState<T> AddPersistentState<T>( | ||
this TestKitSilo silo, | ||
string stateName, | ||
string storageName = default, | ||
T state = default) | ||
{ | ||
return silo.AddPersistentStateStorage( | ||
stateName, | ||
storageName, | ||
new TestStorage<T>(state ?? Activator.CreateInstance<T>())); | ||
} | ||
|
||
/// <summary> | ||
/// Add persistent state to the silo for a given type. If a storage is provided that will provide the loaded state otherwise a new <typeparamref name="T"/> | ||
/// </summary> | ||
/// <remarks> | ||
/// If neither StateName or StorageName are provided then we resolve just based on type, otherwise we try state name and optionally a storage name | ||
/// </remarks> | ||
/// <typeparam name="T">The type of data in the state</typeparam> | ||
/// <param name="silo">The silo to add the state to</param> | ||
/// <param name="stateName">The state name on the persistent state parameter</param> | ||
/// <param name="storageName">The storage name on the persistent state parameter</param> | ||
/// <param name="storage">The storage to use, if null a default implementation will be created</param> | ||
/// <returns>The persistent state</returns> | ||
public static IPersistentState<T> AddPersistentStateStorage<T>( | ||
this TestKitSilo silo, | ||
string stateName, | ||
string storageName = default, | ||
IStorage<T> storage = default) | ||
{ | ||
var normalizedStorage = storage ?? new TestStorage<T>(Activator.CreateInstance<T>()); | ||
var normalizedStorageName = storageName ?? "Default"; | ||
|
||
if (silo == null) | ||
{ | ||
throw new ArgumentNullException(nameof(silo)); | ||
} | ||
|
||
if (string.IsNullOrWhiteSpace(stateName)) | ||
{ | ||
throw new ArgumentException("A state name must be provided", nameof(stateName)); | ||
} | ||
|
||
silo.StorageManager.AddStorage(storage, stateName); | ||
return silo.StorageManager.StateAttributeFactoryMapper.AddPersistentState(normalizedStorage, stateName, normalizedStorageName); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
121 changes: 121 additions & 0 deletions
121
src/OrleansTestKit/Storage/TestPersistentStateAttributeToFactoryMapper.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Reflection; | ||
using System.Threading.Tasks; | ||
using Orleans.Core; | ||
using Orleans.Runtime; | ||
|
||
namespace Orleans.TestKit.Storage | ||
{ | ||
public sealed class TestPersistentStateAttributeToFactoryMapper : IAttributeToFactoryMapper<PersistentStateAttribute> | ||
{ | ||
private readonly StorageManager storageManager; | ||
private readonly Dictionary<Type, Dictionary<(string StateName, string StorageName), object>> | ||
registeredStorage = new(); | ||
private readonly MethodInfo AddEmptyStateMethod = typeof(TestPersistentStateAttributeToFactoryMapper) | ||
.GetMethod(nameof(TestPersistentStateAttributeToFactoryMapper.AddEmptyState), BindingFlags.Instance | BindingFlags.NonPublic); | ||
|
||
public TestPersistentStateAttributeToFactoryMapper(StorageManager storageManager) => this.storageManager = storageManager; | ||
|
||
public IPersistentState<T> AddPersistentState<T>( | ||
IStorage<T> storage, | ||
string stateName, | ||
string storageName) | ||
{ | ||
if (storage is null) | ||
{ | ||
throw new ArgumentNullException(nameof(storage)); | ||
} | ||
|
||
if (string.IsNullOrWhiteSpace(stateName)) | ||
{ | ||
throw new ArgumentException($"'{nameof(stateName)}' cannot be null or whitespace.", nameof(stateName)); | ||
} | ||
|
||
if (string.IsNullOrWhiteSpace(storageName)) | ||
{ | ||
throw new ArgumentException($"'{nameof(storageName)}' cannot be null or whitespace.", nameof(storageName)); | ||
} | ||
|
||
var dict = registeredStorage.TryGetValue(typeof(T), out var typeStateRegistry) | ||
? typeStateRegistry | ||
: registeredStorage[typeof(T)] = new Dictionary<(string StateName, string StorageName), object>(1); | ||
|
||
var fake = new PersistentStateFake<T>(storage); | ||
dict[(stateName, storageName)] = fake; | ||
|
||
return fake; | ||
} | ||
|
||
public Factory<IGrainActivationContext, object> GetFactory(ParameterInfo parameter, PersistentStateAttribute metadata) | ||
{ | ||
if (parameter is null) | ||
{ | ||
throw new ArgumentNullException(nameof(parameter)); | ||
} | ||
|
||
if (metadata is null) | ||
{ | ||
throw new ArgumentNullException(nameof(metadata)); | ||
} | ||
|
||
if (parameter.ParameterType.IsGenericType && | ||
parameter.ParameterType.GetGenericTypeDefinition() != typeof(IPersistentState<>)) | ||
{ | ||
throw new InvalidOperationException($"No persistent state for the parameter '{parameter.Name}'"); | ||
} | ||
|
||
var parameterType = parameter.ParameterType.GenericTypeArguments[0]; | ||
var stateName = metadata.StateName; | ||
var storageName = metadata.StorageName ?? "Default"; | ||
|
||
if (registeredStorage.TryGetValue(parameterType, out var typeStateRegistry)) | ||
{ | ||
// If we must have the state and the storage name so lookup by both | ||
if (typeStateRegistry.TryGetValue((metadata.StateName, metadata.StorageName), out var persistentState)) | ||
{ | ||
return _ => persistentState; | ||
} | ||
} | ||
|
||
var state = AddEmptyStateMethod.MakeGenericMethod(parameterType).Invoke( | ||
this, | ||
new[] { stateName, storageName }); | ||
|
||
return _ => state; | ||
} | ||
|
||
private IPersistentState<TState> AddEmptyState<TState>(string stateName, string storageName) | ||
{ | ||
var storage = storageManager.GetStorage<TState>(stateName); | ||
return AddPersistentState(storage, stateName, storageName); | ||
} | ||
} | ||
|
||
internal class PersistentStateFake<TState> : IPersistentState<TState>, IStorageStats | ||
{ | ||
private readonly IStorage<TState> _storage; | ||
|
||
public PersistentStateFake(IStorage<TState> storage) => _storage = storage; | ||
|
||
public TState State | ||
{ | ||
get => _storage.State; | ||
set => _storage.State = value; | ||
} | ||
|
||
public string Etag => _storage.Etag; | ||
|
||
public bool RecordExists => _storage.RecordExists; | ||
|
||
public TestStorageStats Stats => _storage is IStorageStats statsStorage | ||
? statsStorage.Stats | ||
: null; | ||
|
||
public Task ClearStateAsync() => _storage.ClearStateAsync(); | ||
|
||
public Task ReadStateAsync() => _storage.ReadStateAsync(); | ||
|
||
public Task WriteStateAsync() => _storage.WriteStateAsync(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.