From 948ef97fb415d36409b84df956294157a8e621d0 Mon Sep 17 00:00:00 2001 From: Reuben Bond <203839+ReubenBond@users.noreply.github.com> Date: Wed, 16 Jun 2021 11:46:14 -0700 Subject: [PATCH] Fix for UpdateIAmAlive in CosmosDBMembershipTable.cs (#51) * UpdateIAmAlive was throwing an exception as the MembershipEntry being passed in from Orleans only contained SiloAddress and IAmAliveTime. The code now reads the existing state from Cosmos Db, updates IAmAliveTime and writes it back. From what I can see, this is what UpdateIAmAlive.js used to do. * Fix IAmAlive Co-authored-by: Nick Barrett --- .github/workflows/ci.yml | 2 +- .github/workflows/publish.yml | 2 +- Orleans.CosmosDB.sln | 10 +++-- global.json | 2 +- .../CosmosDBMembershipTable.cs | 37 +++++++++++++++++-- .../CosmosDBGrainStorage.cs | 5 +++ .../Orleans.Persistence.CosmosDB.csproj | 8 ++-- .../Orleans.Reminders.CosmosDB.csproj | 4 +- .../Orleans.Streaming.CosmosDB.csproj | 4 +- test/Orleans.CosmosDB.Tests/MBTTests.cs | 22 +---------- .../MembershipTableTestsBase.cs | 24 +----------- .../Orleans.CosmosDB.Tests.csproj | 19 ++++++---- .../PersistenceTests.cs | 5 --- 13 files changed, 70 insertions(+), 74 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bc9254b..7a07155 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v1 with: - dotnet-version: 3.1.100 + dotnet-version: 5.0.301 # - name: Azure Cosmos Emulator # uses: galvesribeiro/AzureCosmosAction@v1.0.0 # - name: Start CosmosDB Emulator diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 5fa87b1..2304499 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -15,7 +15,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v1 with: - dotnet-version: 3.1.100 + dotnet-version: 5.0.301 - name: Pack working-directory: src/Orleans.Clustering.CosmosDB run: dotnet pack --configuration Release -p:Version=${GITHUB_REF##*/v} diff --git a/Orleans.CosmosDB.sln b/Orleans.CosmosDB.sln index 245b8f1..846ed17 100644 --- a/Orleans.CosmosDB.sln +++ b/Orleans.CosmosDB.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27130.2036 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31320.298 MinimumVisualStudioVersion = 15.0.26124.0 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{12BE367B-569F-4C2E-AC15-876709C119D1}" EndProject @@ -12,7 +12,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig .gitignore = .gitignore src\AssemblyInfo.cs = src\AssemblyInfo.cs - .circleci\config.yml = .circleci\config.yml + .github\workflows\ci.yml = .github\workflows\ci.yml + global.json = global.json + .github\workflows\publish.yml = .github\workflows\publish.yml README.md = README.md README.Nuget.md = README.Nuget.md EndProjectSection @@ -25,7 +27,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Orleans.Persistence.CosmosD EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Orleans.Reminders.CosmosDB", "src\Orleans.Reminders.CosmosDB\Orleans.Reminders.CosmosDB.csproj", "{E82D600B-2C44-4458-AB68-BCC25DE16631}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orleans.Streaming.CosmosDB", "src\Orleans.Streaming.CosmosDB\Orleans.Streaming.CosmosDB.csproj", "{97AC434A-A072-44E4-B8F3-CCFD87A94F08}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Orleans.Streaming.CosmosDB", "src\Orleans.Streaming.CosmosDB\Orleans.Streaming.CosmosDB.csproj", "{97AC434A-A072-44E4-B8F3-CCFD87A94F08}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/global.json b/global.json index 0f27938..aa100ed 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "3.1.100" + "version": "5.0.301" } } \ No newline at end of file diff --git a/src/Orleans.Clustering.CosmosDB/CosmosDBMembershipTable.cs b/src/Orleans.Clustering.CosmosDB/CosmosDBMembershipTable.cs index feb7cbb..1b1b320 100755 --- a/src/Orleans.Clustering.CosmosDB/CosmosDBMembershipTable.cs +++ b/src/Orleans.Clustering.CosmosDB/CosmosDBMembershipTable.cs @@ -26,6 +26,7 @@ internal class CosmosDBMembershipTable : IMembershipTable private CosmosClient _cosmos; private Container _container; + private ItemResponse _selfRow; public CosmosDBMembershipTable(ILoggerFactory loggerFactory, IOptions clusterOptions, IOptions clusteringOptions) { @@ -228,11 +229,41 @@ public async Task ReadRow(SiloAddress key) } - public Task UpdateIAmAlive(MembershipEntry entry) + public async Task UpdateIAmAlive(MembershipEntry entry) { - var siloEntity = ConvertToEntity(entry, this._clusterOptions.ClusterId); + var siloEntityId = ConstructSiloEntityId(entry.SiloAddress); - return this._container.ReplaceItemAsync(siloEntity, siloEntity.Id, new PartitionKey(this._clusterOptions.ClusterId)); + if (this._selfRow is not { } selfRow) + { + var response = await this._container.ReadItemAsync(siloEntityId, new PartitionKey(this._clusterOptions.ClusterId)); + + if (response.StatusCode != HttpStatusCode.OK) + { + var message = $"Unable to query for SiloEntity {entry.ToFullString()}"; + this._logger.LogWarning((int)ErrorCode.MembershipBase, message); + throw new OrleansException(message); + } + + this._selfRow = selfRow = response; + } + + var siloEntity = selfRow.Resource; + siloEntity.IAmAliveTime = entry.IAmAliveTime; + + try + { + var replaceResponse = await this._container.ReplaceItemAsync( + siloEntity, + siloEntityId, + new PartitionKey(this._clusterOptions.ClusterId), + new ItemRequestOptions { IfMatchEtag = selfRow.ETag }); + this._selfRow = replaceResponse; + } + catch + { + this._selfRow = null; + throw; + } } public async Task UpdateRow(MembershipEntry entry, string etag, TableVersion tableVersion) diff --git a/src/Orleans.Persistence.CosmosDB/CosmosDBGrainStorage.cs b/src/Orleans.Persistence.CosmosDB/CosmosDBGrainStorage.cs index 4f24515..d4074a8 100755 --- a/src/Orleans.Persistence.CosmosDB/CosmosDBGrainStorage.cs +++ b/src/Orleans.Persistence.CosmosDB/CosmosDBGrainStorage.cs @@ -153,10 +153,12 @@ public async Task ReadStateAsync(string grainType, GrainReference grainReference if (doc.Resource.State != null) { grainState.State = JsonConvert.DeserializeObject(doc.Resource.State.ToString(), grainState.State.GetType(), this._options.JsonSerializerSettings); + grainState.RecordExists = true; } else { grainState.State = Activator.CreateInstance(grainState.State.GetType()); + grainState.RecordExists = true; } grainState.ETag = doc.Resource.ETag; @@ -224,6 +226,7 @@ public async Task WriteStateAsync(string grainType, GrainReference grainReferenc grainState.ETag = response.Resource.ETag; } + grainState.RecordExists = true; } catch (CosmosException dce) when (dce.StatusCode == HttpStatusCode.PreconditionFailed) { @@ -259,6 +262,7 @@ await ExecuteWithRetries(() => this._container.DeleteItemAsync id, pk, requestOptions)); grainState.ETag = null; + grainState.RecordExists = false; } else { @@ -278,6 +282,7 @@ await ExecuteWithRetries(() => this._container.DeleteItemAsync .ConfigureAwait(false); grainState.ETag = response.Resource.ETag; + grainState.RecordExists = true; } } catch (Exception exc) diff --git a/src/Orleans.Persistence.CosmosDB/Orleans.Persistence.CosmosDB.csproj b/src/Orleans.Persistence.CosmosDB/Orleans.Persistence.CosmosDB.csproj index 40dee65..ed11a7d 100755 --- a/src/Orleans.Persistence.CosmosDB/Orleans.Persistence.CosmosDB.csproj +++ b/src/Orleans.Persistence.CosmosDB/Orleans.Persistence.CosmosDB.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 @@ -27,9 +27,9 @@ - - - + + + \ No newline at end of file diff --git a/src/Orleans.Reminders.CosmosDB/Orleans.Reminders.CosmosDB.csproj b/src/Orleans.Reminders.CosmosDB/Orleans.Reminders.CosmosDB.csproj index 6b4509b..29e5b9e 100755 --- a/src/Orleans.Reminders.CosmosDB/Orleans.Reminders.CosmosDB.csproj +++ b/src/Orleans.Reminders.CosmosDB/Orleans.Reminders.CosmosDB.csproj @@ -27,8 +27,8 @@ - - + + diff --git a/src/Orleans.Streaming.CosmosDB/Orleans.Streaming.CosmosDB.csproj b/src/Orleans.Streaming.CosmosDB/Orleans.Streaming.CosmosDB.csproj index fb1c1b5..96f5295 100644 --- a/src/Orleans.Streaming.CosmosDB/Orleans.Streaming.CosmosDB.csproj +++ b/src/Orleans.Streaming.CosmosDB/Orleans.Streaming.CosmosDB.csproj @@ -25,9 +25,9 @@ - + - + diff --git a/test/Orleans.CosmosDB.Tests/MBTTests.cs b/test/Orleans.CosmosDB.Tests/MBTTests.cs index e04531b..cae9cfe 100755 --- a/test/Orleans.CosmosDB.Tests/MBTTests.cs +++ b/test/Orleans.CosmosDB.Tests/MBTTests.cs @@ -16,21 +16,8 @@ namespace Orleans.CosmosDB.Tests /// /// Tests for operation of Orleans Membership Table using Azure Cosmos DB /// - public class MBTTests : MembershipTableTestsBase/*, IClassFixture*/ + public class MBTTests : MembershipTableTestsBase { - public MBTTests() : base(CreateFilters()) - { - } - - private static LoggerFilterOptions CreateFilters() - { - var filters = new LoggerFilterOptions(); - //filters.AddFilter(typeof(Orleans.Clustering.CosmosDB.AzureTableDataManager<>).FullName, LogLevel.Trace); - //filters.AddFilter(typeof(OrleansSiloInstanceManager).FullName, LogLevel.Trace); - //filters.AddFilter("Orleans.Storage", LogLevel.Trace); - return filters; - } - protected override IMembershipTable CreateMembershipTable(ILogger logger, string accountEndpoint, string accountKey) { var httpHandler = new HttpClientHandler() @@ -44,7 +31,6 @@ protected override IMembershipTable CreateMembershipTable(ILogger logger, string new CosmosClientOptions { ConnectionMode = ConnectionMode.Gateway } ); - //TestUtils.CheckForAzureStorage(); var options = new CosmosDBClusteringOptions() { Client = dbClient, @@ -80,12 +66,6 @@ protected override IGatewayListProvider CreateGatewayListProvider(ILogger logger Options.Create(new GatewayOptions())); } - protected override Task GetConnectionString() - { - //TestUtils.CheckForAzureStorage(); - return Task.FromResult(""); - } - [Fact] public async Task GetGateways() { diff --git a/test/Orleans.CosmosDB.Tests/MembershipTableTestsBase.cs b/test/Orleans.CosmosDB.Tests/MembershipTableTestsBase.cs index 46dea17..44d0150 100755 --- a/test/Orleans.CosmosDB.Tests/MembershipTableTestsBase.cs +++ b/test/Orleans.CosmosDB.Tests/MembershipTableTestsBase.cs @@ -24,7 +24,7 @@ internal static class SiloInstanceTableTestConstants } [Collection("Default")] - public abstract class MembershipTableTestsBase : IDisposable //, IClassFixture + public abstract class MembershipTableTestsBase : IDisposable { private static readonly string hostName = Dns.GetHostName(); private readonly ILogger logger = null; @@ -32,7 +32,6 @@ public abstract class MembershipTableTestsBase : IDisposable //, IClassFixture b.AddConsole()).BuildServiceProvider(); loggerFactory = sp.GetRequiredService(); logger = loggerFactory.CreateLogger(this.GetType().FullName); @@ -54,10 +51,6 @@ protected MembershipTableTestsBase(/*ConnectionStringFixture fixture, TestEnviro logger?.Info("ClusterId={0}", this.clusterId); - //fixture.InitializeConnectionStringAccessor(GetConnectionString); - //this.connectionString = fixture.ConnectionString; - var adoVariant = GetAdoInvariant(); - membershipTable = CreateMembershipTable(logger, accountEndpoint, accountKey); membershipTable.InitializeMembershipTable(true).WithTimeout(TimeSpan.FromMinutes(3)).Wait(); @@ -65,29 +58,16 @@ protected MembershipTableTestsBase(/*ConnectionStringFixture fixture, TestEnviro gatewayListProvider.InitializeGatewayListProvider().WithTimeout(TimeSpan.FromMinutes(3)).Wait(); } - //public IGrainFactory GrainFactory => this.environment.GrainFactory; - - //public IGrainReferenceConverter GrainReferenceConverter => this.environment.Services.GetRequiredService(); - - //public IServiceProvider Services => this.environment.Services; - public void Dispose() { if (membershipTable != null && SiloInstanceTableTestConstants.DeleteEntriesAfterTest) { membershipTable.DeleteMembershipTableEntries(this.clusterId).Wait(); } - //this.loggerFactory.Dispose(); } protected abstract IGatewayListProvider CreateGatewayListProvider(ILogger logger, string accountEndpoint, string accountKey); protected abstract IMembershipTable CreateMembershipTable(ILogger logger, string accountEndpoint, string accountKey); - protected abstract Task GetConnectionString(); - - protected virtual string GetAdoInvariant() - { - return null; - } protected async Task MembershipTable_GetGateways() { diff --git a/test/Orleans.CosmosDB.Tests/Orleans.CosmosDB.Tests.csproj b/test/Orleans.CosmosDB.Tests/Orleans.CosmosDB.Tests.csproj index 9241606..4cf79e1 100644 --- a/test/Orleans.CosmosDB.Tests/Orleans.CosmosDB.Tests.csproj +++ b/test/Orleans.CosmosDB.Tests/Orleans.CosmosDB.Tests.csproj @@ -1,20 +1,23 @@ - + - netcoreapp3.1 + net5.0 latest false - - - - - + + + + + - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/test/Orleans.CosmosDB.Tests/PersistenceTests.cs b/test/Orleans.CosmosDB.Tests/PersistenceTests.cs index 2808172..07379fe 100755 --- a/test/Orleans.CosmosDB.Tests/PersistenceTests.cs +++ b/test/Orleans.CosmosDB.Tests/PersistenceTests.cs @@ -188,9 +188,7 @@ public async Task ClearState() var list = await grain.Read(); - Assert.Empty(list); - } [Fact] @@ -220,9 +218,6 @@ public async Task ClearState_BeforeWrite() var grain = this._fixture.Client.GetGrain(guid); await grain.ClearState(); - - } } - }