1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-30 07:36:14 -05:00

[PM-5518] Sql-backed IDistributedCache (#3791)

* Sql-backed IDistributedCache

* sqlserver cache table

* remove unused using

* setup EF entity

* cache indexes

* add back cipher

* revert SetupEntityFramework change

* ef cache

* EntityFrameworkCache

* IServiceScopeFactory for db context

* implement EntityFrameworkCache

* move to _serviceScopeFactory

* move to config file

* ef migrations

* fixes

* datetime and error codes

* revert migrations

* migrations

* format

* static and namespace fix

* use time provider

* Move SQL migration and remove EF one for the moment

* Add clean migration of just the new table

* Formatting

* Test Custom `IDistributedCache` Implementation

* Add Back Logging

* Remove Double Logging

* Skip Test When Not EntityFrameworkCache

* Format

---------

Co-authored-by: Matt Bishop <mbishop@bitwarden.com>
Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com>
This commit is contained in:
Kyle Spearrin
2024-07-03 12:48:23 -04:00
committed by GitHub
parent b8f71271eb
commit 0d3a7b3dd5
21 changed files with 8748 additions and 41 deletions

View File

@ -3,9 +3,11 @@ using Bit.Core.Enums;
using Bit.Core.Settings;
using Bit.Infrastructure.Dapper;
using Bit.Infrastructure.EntityFramework;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Time.Testing;
using Xunit.Sdk;
namespace Bit.Infrastructure.IntegrationTest;
@ -13,6 +15,7 @@ namespace Bit.Infrastructure.IntegrationTest;
public class DatabaseDataAttribute : DataAttribute
{
public bool SelfHosted { get; set; }
public bool UseFakeTimeProvider { get; set; }
public override IEnumerable<object[]> GetData(MethodInfo testMethod)
{
@ -52,7 +55,7 @@ public class DatabaseDataAttribute : DataAttribute
if (database.Type == SupportedDatabaseProviders.SqlServer && !database.UseEf)
{
var dapperSqlServerCollection = new ServiceCollection();
dapperSqlServerCollection.AddLogging(configureLogging);
AddCommonServices(dapperSqlServerCollection, configureLogging);
dapperSqlServerCollection.AddDapperRepositories(SelfHosted);
var globalSettings = new GlobalSettings
{
@ -65,19 +68,35 @@ public class DatabaseDataAttribute : DataAttribute
dapperSqlServerCollection.AddSingleton(globalSettings);
dapperSqlServerCollection.AddSingleton<IGlobalSettings>(globalSettings);
dapperSqlServerCollection.AddSingleton(database);
dapperSqlServerCollection.AddDataProtection();
dapperSqlServerCollection.AddDistributedSqlServerCache((o) =>
{
o.ConnectionString = database.ConnectionString;
o.SchemaName = "dbo";
o.TableName = "Cache";
});
yield return dapperSqlServerCollection.BuildServiceProvider();
}
else
{
var efCollection = new ServiceCollection();
efCollection.AddLogging(configureLogging);
AddCommonServices(efCollection, configureLogging);
efCollection.SetupEntityFramework(database.ConnectionString, database.Type);
efCollection.AddPasswordManagerEFRepositories(SelfHosted);
efCollection.AddSingleton(database);
efCollection.AddDataProtection();
efCollection.AddSingleton<IDistributedCache, EntityFrameworkCache>();
yield return efCollection.BuildServiceProvider();
}
}
}
private void AddCommonServices(IServiceCollection services, Action<ILoggingBuilder> configureLogging)
{
services.AddLogging(configureLogging);
services.AddDataProtection();
if (UseFakeTimeProvider)
{
services.AddSingleton<TimeProvider, FakeTimeProvider>();
}
}
}

View File

@ -0,0 +1,71 @@
using Bit.Infrastructure.EntityFramework;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Time.Testing;
using Xunit;
namespace Bit.Infrastructure.IntegrationTest;
public class DistributedCacheTests
{
[DatabaseTheory, DatabaseData(UseFakeTimeProvider = true)]
public async Task Simple_NotExpiredItem_StartsScan(IDistributedCache cache, TimeProvider timeProvider)
{
if (cache is not EntityFrameworkCache efCache)
{
// We don't write the SqlServer cache implementation so we don't need to test it
// also it doesn't use TimeProvider under the hood so we'd have to delay the test
// for 30 minutes to get it to work. So just skip it.
return;
}
var fakeTimeProvider = (FakeTimeProvider)timeProvider;
cache.Set("test-key", "some-value"u8.ToArray(), new DistributedCacheEntryOptions
{
SlidingExpiration = TimeSpan.FromMinutes(20),
});
// Should have expired and not be returned
var firstValue = cache.Get("test-key");
// Scan for expired items is supposed to run every 30 minutes
fakeTimeProvider.Advance(TimeSpan.FromMinutes(31));
var secondValue = cache.Get("test-key");
// This should have forced the EF cache to start a scan task
Assert.NotNull(efCache.scanTask);
// We don't want the scan task to throw an exception, unwrap it.
await efCache.scanTask;
Assert.NotNull(firstValue);
Assert.Null(secondValue);
}
[DatabaseTheory, DatabaseData(UseFakeTimeProvider = true)]
public async Task ParallelReadsAndWrites_Work(IDistributedCache cache, TimeProvider timeProvider)
{
var fakeTimeProvider = (FakeTimeProvider)timeProvider;
await Parallel.ForEachAsync(Enumerable.Range(1, 100), async (index, _) =>
{
await cache.SetAsync($"test-{index}", "some-value"u8.ToArray(), new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(index),
});
});
await Parallel.ForEachAsync(Enumerable.Range(1, 100), async (index, _) =>
{
var value = await cache.GetAsync($"test-{index}");
Assert.NotNull(value);
});
}
[DatabaseTheory, DatabaseData]
public async Task MultipleWritesOnSameKey_ShouldNotThrow(IDistributedCache cache)
{
await cache.SetAsync("test-duplicate", "some-value"u8.ToArray());
await cache.SetAsync("test-duplicate", "some-value"u8.ToArray());
}
}

View File

@ -11,6 +11,7 @@
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.TimeProvider.Testing" Version="8.6.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNetTestSdkVersion)" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">

Binary file not shown.