Skip to content

Commit

Permalink
- update to Orleans 7.0.0-rc2
Browse files Browse the repository at this point in the history
- add GrainIdExtensions GetTenantId, GetKeyWithinTenant
  • Loading branch information
VincentH-Net committed Oct 20, 2022
1 parent 58abfe2 commit 8c541e5
Show file tree
Hide file tree
Showing 22 changed files with 84 additions and 68 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# <img src="img/CSharp-Toolkit-Icon.png" alt="Backend Toolkit" width="64px" />Orleans.Multitenant
Secure, flexible tenant separation for Microsoft Orleans 4
Secure, flexible tenant separation for Microsoft Orleans 7

> [![Nuget (with prereleases)](https://img.shields.io/nuget/vpre/Orleans.Multitenant?color=gold&label=NuGet:%20Orleans.Multitenant&style=plastic)](https://www.nuget.org/packages/Orleans.Multitenant)<br />
> (install in silo client and grain implementation projects)
## Summary
[Microsoft Orleans 4](https://github.com/dotnet/orleans/releases/tag/v4.0.0-preview2) is a great technology for building distributed, cloud-native applications. It was designed to reduce the complexity of building this type of applications for C# developers.
[Microsoft Orleans 7](https://github.com/dotnet/orleans/releases/tag/v7.0.0-rc2) is a great technology for building distributed, cloud-native applications. It was designed to reduce the complexity of building this type of applications for C# developers.

However, creating multi tenant applications with Orleans out of the box requires careful design, complex coding and significant testing to prevent unintentional leakage of communication or stored data across tenants. Orleans.Multitenant adds this capability to Orleans for free, as an uncomplicated, flexible and extensible API that lets developers:

Expand Down
13 changes: 7 additions & 6 deletions src/Example/Apis/Apis.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
Expand All @@ -11,14 +11,15 @@

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
<NoWarn>$(NoWarn);1591;CA1852</NoWarn>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Orleans.Persistence.AzureStorage" Version="4.0.0-preview2" />
<PackageReference Include="Microsoft.Orleans.Persistence.Memory" Version="4.0.0-preview2" />
<PackageReference Include="Microsoft.Orleans.Sdk" Version="4.0.0-preview2" />
<PackageReference Include="Orleans.Multitenant" Version="1.0.0-preview.2" />
<PackageReference Include="Microsoft.Orleans.Persistence.AzureStorage" Version="7.0.0-rc2" />
<PackageReference Include="Microsoft.Orleans.Persistence.Memory" Version="7.0.0-rc2" />
<PackageReference Include="Microsoft.Orleans.Sdk" Version="7.0.0-rc2" />
<PackageReference Include="Orleans.Multitenant" Version="1.0.0-rc2" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.0-rc.2.22476.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>

Expand Down
2 changes: 1 addition & 1 deletion src/Example/Apis/Foundation/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options => {
options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, $"{System.Reflection.Assembly.GetExecutingAssembly().GetName().Name}.xml"));
options.SwaggerDoc("v1", new OpenApiInfo { Title = "Example Orleans 4 Multitenant API", Version = "v1" });
options.SwaggerDoc("v1", new OpenApiInfo { Title = "Example Orleans 7 Multitenant API", Version = "v1" });
options.OperationFilter<TenantHeader.AddAsOpenApiParameter>();
});

Expand Down
6 changes: 3 additions & 3 deletions src/Example/Contracts/Contracts.csproj
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Orleans.Sdk" Version="4.0.0-preview2" />
<PackageReference Include="Microsoft.Orleans.Sdk" Version="7.0.0-rc2" />
</ItemGroup>

</Project>
6 changes: 3 additions & 3 deletions src/Example/Services.Tenant/Services.Tenant.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
Expand All @@ -13,8 +13,8 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Orleans.Sdk" Version="4.0.0-preview2" />
<PackageReference Include="Orleans.Multitenant" Version="1.0.0-preview.2" />
<PackageReference Include="Microsoft.Orleans.Sdk" Version="7.0.0-rc2" />
<PackageReference Include="Orleans.Multitenant" Version="1.0.0-rc2" />
</ItemGroup>

<ItemGroup>
Expand Down
4 changes: 2 additions & 2 deletions src/Example/Services.Tenant/Tenant.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@

namespace Orleans4Multitenant.Services.Tenant;

class Tenant : GrainBase<Tenant.State>, ITenant
sealed class Tenant : GrainBase<Tenant.State>, ITenant
{
[GenerateSerializer]
internal class State
internal sealed class State
{
[Id(0)] public TenantInfo Info { get; set; } = new(string.Empty);
[Id(1)] public Dictionary<Guid, UserInfo> Users { get; set; } = new();
Expand Down
4 changes: 2 additions & 2 deletions src/Example/Services.Tenant/User.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ interface IUser : IGrainWithStringKey
Task Clear();
}

class User : GrainBase<User.State>, IUser
sealed class User : GrainBase<User.State>, IUser
{
[GenerateSerializer]
internal class State
internal sealed class State
{
[Id(0)] public UserInfo Info { get; set; } = new(Guid.Empty, string.Empty);
}
Expand Down
17 changes: 16 additions & 1 deletion src/Orleans.Multitenant/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,21 @@ public static string GetKeyWithinTenant(this IAddressable grain)
=> grain.GetGrainId().Key.Value.Span.GetKey();
}

public static class GrainIdExtensions
{
/// <summary>Get the tenant id part of the <see cref="GrainId"/> key</summary>
/// <param name="grainId">This grain id</param>
/// <returns>The tenant id</returns>
public static string? GetTenantId(this GrainId grainId)
=> grainId.TryGetTenantId().TenantIdString();

/// <summary>Get the part of the <see cref="GrainId"/> key that identifies it within it's tenant</summary>
/// <param name="grainId">This grain id</param>
/// <returns>The key within the tenant. This corresponds to the keyWithinTenant parameter of <see cref="TenantGrainFactory.GetGrain(Type, string)"/></returns>
public static string GetKeyWithinTenant(this GrainId grainId)
=> grainId.Key.Value.Span.GetKey();
}

public static class StreamIdExtensions
{
/// <summary>Get the tenant id part of the <see cref="StreamId"/> key</summary>
Expand All @@ -264,7 +279,7 @@ public static class StreamIdExtensions

/// <summary>Get the part of the <see cref="StreamId"/> key that identifies it within it's tenant</summary>
/// <param name="streamId">This stream id</param>
/// <returns>The key within the tenant. This corresponds to the keyWithinTenant parameter of <see cref="TenantGrainFactory.GetGrain(Type, string)"/></returns>
/// <returns>The key within the tenant. This corresponds to the keyWithinTenant parameter of <see cref="TenantStreamProvider.GetStream{T}(string, string)"/></returns>
public static string GetKeyWithinTenant(this StreamId streamId)
=> streamId.Key.Span.GetKey();
}
2 changes: 1 addition & 1 deletion src/Orleans.Multitenant/Internal/SiloLifecycle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ interface IRepeatedSiloLifecycleObservable
void SubscribeStopEvents(IRepeatedSiloLifecycleObserver observer);
}

record LifecycleStartupRecording(int HighestCompletedStageOnParticipate, int LowestStoppedStageOnParticipate, ReadOnlyCollection<LifecycleStartEventRecord> Events, ISiloLifecycle Lifecycle);
sealed record LifecycleStartupRecording(int HighestCompletedStageOnParticipate, int LowestStoppedStageOnParticipate, ReadOnlyCollection<LifecycleStartEventRecord> Events, ISiloLifecycle Lifecycle);

readonly record struct LifecycleStartEventRecord(int LifecycleIndex, int HighestCompletedStage, int LowestStoppedStage);

Expand Down
22 changes: 11 additions & 11 deletions src/Orleans.Multitenant/Internal/StorageProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,22 @@ public MultitenantStorage(
ILogger<MultitenantStorage> logger)
=> (this.name, this.options, this.tenantGrainStorageFactory, this.logger) = (name, options, tenantGrainStorageFactory, logger);

public async Task ClearStateAsync<T>(string grainType, GrainReference grainReference, IGrainState<T> grainState)
public async Task ClearStateAsync<T>(string grainType, GrainId grainId, IGrainState<T> grainState)
{
var provider = await GetTenantStorageProvider(grainReference);
await provider.ClearStateAsync(grainType, grainReference, grainState);
var provider = await GetTenantStorageProvider(grainId);
await provider.ClearStateAsync(grainType, grainId, grainState);
}

public async Task ReadStateAsync<T>(string grainType, GrainReference grainReference, IGrainState<T> grainState)
public async Task ReadStateAsync<T>(string grainType, GrainId grainId, IGrainState<T> grainState)
{
var provider = await GetTenantStorageProvider(grainReference);
await provider.ReadStateAsync(grainType, grainReference, grainState);
var provider = await GetTenantStorageProvider(grainId);
await provider.ReadStateAsync(grainType, grainId, grainState);
}

public async Task WriteStateAsync<T>(string grainType, GrainReference grainReference, IGrainState<T> grainState)
public async Task WriteStateAsync<T>(string grainType, GrainId grainId, IGrainState<T> grainState)
{
var provider = await GetTenantStorageProvider(grainReference);
await provider.WriteStateAsync(grainType, grainReference, grainState);
var provider = await GetTenantStorageProvider(grainId);
await provider.WriteStateAsync(grainType, grainId, grainState);
}

public void Participate(ISiloLifecycle observer)
Expand All @@ -55,9 +55,9 @@ public void Participate(ISiloLifecycle observer)
siloLifecycleRepeater = new(observer, logger);
}

async Task<IGrainStorage> GetTenantStorageProvider(GrainReference grainReference)
async Task<IGrainStorage> GetTenantStorageProvider(GrainId grainId)
{
string tenantId = grainReference.GetTenantId() ?? options.TenantIdForNullTenant;
string tenantId = grainId.GetTenantId() ?? options.TenantIdForNullTenant;

if (!tenantStorageProviders.TryGetValue(tenantId, out var grainStorage))
{
Expand Down
16 changes: 8 additions & 8 deletions src/Orleans.Multitenant/Orleans.Multitenant.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
Expand All @@ -12,9 +12,9 @@
<PropertyGroup>
<IsPackable>true</IsPackable>
<PackageId>Orleans.Multitenant</PackageId>
<PackageVersion>1.0.0-preview.2</PackageVersion>
<PackageVersion>1.0.0-rc2</PackageVersion>
<Title>Orleans Multitenant</Title>
<Description>Secure, flexible tenant separation for Microsoft Orleans 4</Description>
<Description>Secure, flexible tenant separation for Microsoft Orleans 7</Description>
<Authors>VincentH.NET;Applicita</Authors>
<Company>Applicita</Company>
<Copyright>Copyright © Applicita</Copyright>
Expand All @@ -24,7 +24,7 @@
<PackageReadmeFile>Readme.md</PackageReadmeFile>
<PackageReleaseNotes>See source repository for release notes</PackageReleaseNotes>
<RepositoryUrl>https://github.com/Applicita/Orleans.Multitenant</RepositoryUrl>
<PackageTags>multitenant;multi-tenant;tenant;tenant separation;separation;Orleans;Orleans 4;Microsoft Orleans;Applicita</PackageTags>
<PackageTags>multitenant;multi-tenant;tenant;tenant separation;separation;Orleans;Orleans 7;Microsoft Orleans;Applicita</PackageTags>
<GenerateDocumentationFile>true</GenerateDocumentationFile>

<!-- Enable Source Link -->
Expand All @@ -40,10 +40,10 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Orleans.OrleansRuntime" Version="4.0.0-preview2" />
<PackageReference Include="Microsoft.Orleans.Sdk" Version="4.0.0-preview2" />
<PackageReference Include="Microsoft.Orleans.Streaming" Version="4.0.0-preview2" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All"/>
<PackageReference Include="Microsoft.Orleans.Runtime" Version="7.0.0-rc2" />
<PackageReference Include="Microsoft.Orleans.Sdk" Version="7.0.0-rc2" />
<PackageReference Include="Microsoft.Orleans.Streaming" Version="7.0.0-rc2" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
</ItemGroup>

</Project>
4 changes: 2 additions & 2 deletions src/Orleans.Multitenant/Readme.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Secure, flexible tenant separation for [Microsoft Orleans 4](https://github.com/dotnet/orleans/releases/tag/v4.0.0-preview2)
Secure, flexible tenant separation for [Microsoft Orleans 7](https://github.com/dotnet/orleans/releases/tag/v7.0.0-rc2)

Docs: see the [repo readme](https://github.com/Applicita/Orleans.Multitenant#readme) and the inline C# documentation. All public Orleans.Multitenant API's come with full inline documentation.

[Release Notes](https://github.com/Applicita/Orleans.Multitenant/releases/tag/1-0-0-preview-2)
[Release Notes](https://github.com/Applicita/Orleans.Multitenant/releases/tag/1-0-0-rc-2)
6 changes: 3 additions & 3 deletions src/Orleans.Multitenant/TenantStreamProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,13 @@ public TenantStream<T> GetStream<T>(string @namespace, string keyWithinTenant)

internal TenantStream(IAsyncStream<TenantEvent<T>> stream) => this.stream = stream;

/// <inheritdoc cref="IAsyncStream{T}.IsRewindable"/>
/// <inheritdoc cref="IAsyncStream.IsRewindable"/>
public bool IsRewindable => stream.IsRewindable;

/// <inheritdoc cref="IAsyncStream{T}.ProviderName"/>
/// <inheritdoc cref="IAsyncStream.ProviderName"/>
public string ProviderName => stream.ProviderName;

/// <inheritdoc cref="IAsyncStream{T}.StreamId"/>
/// <inheritdoc cref="IAsyncStream.StreamId"/>
public StreamId StreamId => stream.StreamId;

/// <inheritdoc cref="IAsyncStream{T}.GetAllSubscriptionHandles"/>
Expand Down
6 changes: 3 additions & 3 deletions src/Tests/Examples/AuthorizedStreaming.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@ internal static IAsyncStream<int> GetTenantUnawareStream(this Grain grain, Strea
=> grain.GetStreamProvider(ClusterFixture.TenantUnawareStreamProviderName).GetStream<int>(id);
}

class StreamProducerGrain : Grain, IStreamProducerGrain
sealed class StreamProducerGrain : Grain, IStreamProducerGrain
{
public Task ProduceEvent(string @namespace, string key, int value) => this.IsTenantAware()
? this.GetTenantAwareStream(StreamId.Create(@namespace, key)).OnNextAsync(value)
: this.GetTenantUnawareStream(StreamId.Create(@namespace, key)).OnNextAsync(value);
}

[ImplicitStreamSubscription(Constants.Stream1Namespace)]
class ImplicitStreamSubscriberGrain : Grain, IImplicitStreamSubscriberGrain
sealed class ImplicitStreamSubscriberGrain : Grain, IImplicitStreamSubscriberGrain
{
int? lastValue;
StreamSubscriptionHandle<TenantEvent<int>>? tenantAwaresubscription;
Expand Down Expand Up @@ -89,7 +89,7 @@ Task OnNext(int value, StreamSequenceToken token)
}
}

class ExplicitStreamSubscriberGrain : Grain, IExplicitStreamSubscriberGrain
sealed class ExplicitStreamSubscriberGrain : Grain, IExplicitStreamSubscriberGrain
{
int? lastValue;
StreamSubscriptionHandle<TenantEvent<int>>? tenantAwaresubscription;
Expand Down
8 changes: 4 additions & 4 deletions src/Tests/Examples/Extensibility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace OrleansMultitenant.Tests.Examples.Extensibility
{
class ExtendedCrossTenantAccessAuthorizer : ICrossTenantAuthorizer
sealed class ExtendedCrossTenantAccessAuthorizer : ICrossTenantAuthorizer
{
internal const string RootTenantId = "RootTenant";

Expand All @@ -17,7 +17,7 @@ public bool IsAccessAuthorized(string? sourceTenantId, string? targetTenantId)
}
}

class ExtendedIncomingGrainCallTenantSeparator : IGrainCallTenantSeparator
sealed class ExtendedIncomingGrainCallTenantSeparator : IGrainCallTenantSeparator
{
/// <remarks>static can be used to access the same object instances in silo's and tests, because <see cref="Orleans.TestingHost.TestCluster"/> uses in-process silo's</remarks>
internal static int CrossTenantCallCount;
Expand Down Expand Up @@ -45,7 +45,7 @@ interface ITenantSpecificGrain : IGrainWithStringKey
Task AMethod();
}

class TenantSpecificGrain : Grain, ITenantSpecificGrain
sealed class TenantSpecificGrain : Grain, ITenantSpecificGrain
{
public async Task CallTenantSpecificGrain(string targetGrainId)
=> await this.GetTenantGrainFactory().GetGrain<ITenantSpecificGrain>(targetGrainId).AMethod();
Expand Down Expand Up @@ -81,7 +81,7 @@ interface ICrossTenantGrain : IGrainWithStringKey
Task AMethod();
}

class CrossTenantGrain : Grain, ICrossTenantGrain
sealed class CrossTenantGrain : Grain, ICrossTenantGrain
{
public async Task CallTenantSpecificGrain(string targetTenantId, string targetGrainId)
=> await GrainFactory.ForTenant(targetTenantId).GetGrain<ITenantSpecificGrain>(targetGrainId).AMethod();
Expand Down
4 changes: 2 additions & 2 deletions src/Tests/Examples/GrainCalling.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ interface ITargetGrain : IGrainWithStringKey
Task AMethod();
}

class SourceGrain : Grain, ISourceGrain
sealed class SourceGrain : Grain, ISourceGrain
{
public async Task CallTargetGrain(string targetGrainId)
=> await this.GetTenantGrainFactory().GetGrain<ITargetGrain>(targetGrainId).AMethod();
Expand All @@ -20,7 +20,7 @@ public async Task CallTargetGrain(string? targetTenantId, string targetGrainId)
=> await GrainFactory.ForTenant(targetTenantId!).GetGrain<ITargetGrain>(targetGrainId).AMethod(); // Note that we pass in null tenantId by design - the internals must support null tenants, even though the public API does not allow it
}

class TargetGrain : Grain, ITargetGrain
sealed class TargetGrain : Grain, ITargetGrain
{
public Task AMethod() => Task.CompletedTask;
}
4 changes: 2 additions & 2 deletions src/Tests/Examples/UnauthorizedStreaming.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ internal static IAsyncStream<int> GetTenantUnawareStream(this Grain grain, strin
=> grain.GetStreamProvider(provider).GetStream<int>(id);
}

class CrossTenantStreamProducerGrain : Grain, ICrossTenantStreamProducerGrain
sealed class CrossTenantStreamProducerGrain : Grain, ICrossTenantStreamProducerGrain
{
public Task ProduceEvent(string provider, string @namespace, string? tenantId, string key, int value) => this.IsTenantAware()
? this.GetTenantAwareStream(provider, tenantId, StreamId.Create(@namespace, key)).OnNextAsync(value)
: this.GetTenantUnawareStream(provider, StreamId.Create(@namespace, key)).OnNextAsync(value);
}

class CrossTenantExplicitStreamSubscriberGrain : Grain, ICrossTenantExplicitStreamSubscriberGrain
sealed class CrossTenantExplicitStreamSubscriberGrain : Grain, ICrossTenantExplicitStreamSubscriberGrain
{
int? lastValue;
StreamSubscriptionHandle<TenantEvent<int>>? tenantAwaresubscription;
Expand Down
6 changes: 3 additions & 3 deletions src/Tests/Foundation/ClusterFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public ClusterFixture()

public TestCluster Cluster { get; }

class SiloConfigurator : ISiloConfigurator
sealed class SiloConfigurator : ISiloConfigurator
{
public void Configure(ISiloBuilder siloBuilder) => siloBuilder
.ConfigureLogging(l => l.AddProcessing())
Expand All @@ -45,15 +45,15 @@ public void Configure(ISiloBuilder siloBuilder) => siloBuilder
.AddMemoryGrainStorage(TenantUnawareStreamProviderName);
}

class ClientConfigurator : IClientBuilderConfigurator
sealed class ClientConfigurator : IClientBuilderConfigurator
{
public void Configure(IConfiguration configuration, IClientBuilder clientBuilder) => clientBuilder
.AddMemoryStreams<DefaultMemoryMessageBodySerializer>(TenantAwareStreamProviderName)
.AddMemoryStreams<DefaultMemoryMessageBodySerializer>(TenantUnawareStreamProviderName);
}
}

class CrossTenantAccessAuthorizer : ICrossTenantAuthorizer
sealed class CrossTenantAccessAuthorizer : ICrossTenantAuthorizer
{
/// <remarks>static can be used to access the same object instances in silo's and tests, because <see cref="TestCluster"/> uses in-process silo's</remarks>
internal static ConcurrentQueue<(string? sourceTenantId, string? targetTenantId)> AccessChecks { get; } = new();
Expand Down
Loading

0 comments on commit 8c541e5

Please sign in to comment.