diff --git a/Microsoft.Health.Fhir.sln b/Microsoft.Health.Fhir.sln index 5b198b5284..788977da40 100644 --- a/Microsoft.Health.Fhir.sln +++ b/Microsoft.Health.Fhir.sln @@ -199,6 +199,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PerfTester", "tools\PerfTes EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SqlScriptRunner", "tools\SqlScriptRunner\SqlScriptRunner.csproj", "{76C29222-8D35-43A2-89C5-43114D113C10}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Health.Fhir.CosmosDb.Initialization", "src\Microsoft.Health.Fhir.CosmosDb.Initialization\Microsoft.Health.Fhir.CosmosDb.Initialization.csproj", "{10661BC9-01B0-4E35-9751-3B5CE97E25C0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Health.Fhir.CosmosDb.Initialization.UnitTests", "src\Microsoft.Health.Fhir.CosmosDb.Initialization.UnitTests\Microsoft.Health.Fhir.CosmosDb.Initialization.UnitTests.csproj", "{B9AAA11D-8C8C-44C3-AADE-801376EF82F0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Health.Fhir.CosmosDb.Core", "src\Microsoft.Health.Fhir.CosmosDb.Core\Microsoft.Health.Fhir.CosmosDb.Core.csproj", "{1CD46DC5-6022-4BBE-9A1C-6B13C3CEFC75}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -465,6 +471,18 @@ Global {76C29222-8D35-43A2-89C5-43114D113C10}.Debug|Any CPU.Build.0 = Debug|Any CPU {76C29222-8D35-43A2-89C5-43114D113C10}.Release|Any CPU.ActiveCfg = Release|Any CPU {76C29222-8D35-43A2-89C5-43114D113C10}.Release|Any CPU.Build.0 = Release|Any CPU + {10661BC9-01B0-4E35-9751-3B5CE97E25C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10661BC9-01B0-4E35-9751-3B5CE97E25C0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10661BC9-01B0-4E35-9751-3B5CE97E25C0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10661BC9-01B0-4E35-9751-3B5CE97E25C0}.Release|Any CPU.Build.0 = Release|Any CPU + {B9AAA11D-8C8C-44C3-AADE-801376EF82F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B9AAA11D-8C8C-44C3-AADE-801376EF82F0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B9AAA11D-8C8C-44C3-AADE-801376EF82F0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B9AAA11D-8C8C-44C3-AADE-801376EF82F0}.Release|Any CPU.Build.0 = Release|Any CPU + {1CD46DC5-6022-4BBE-9A1C-6B13C3CEFC75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1CD46DC5-6022-4BBE-9A1C-6B13C3CEFC75}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1CD46DC5-6022-4BBE-9A1C-6B13C3CEFC75}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1CD46DC5-6022-4BBE-9A1C-6B13C3CEFC75}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -557,10 +575,13 @@ Global {71A1A274-23A9-441B-BB47-1FC561FF0F35} = {B70945F4-01A6-4351-955B-C4A2943B5E3B} {5E0B69FE-40DE-42E8-9F67-4BB7410AF67C} = {B70945F4-01A6-4351-955B-C4A2943B5E3B} {76C29222-8D35-43A2-89C5-43114D113C10} = {B70945F4-01A6-4351-955B-C4A2943B5E3B} + {10661BC9-01B0-4E35-9751-3B5CE97E25C0} = {DC5A2CB1-8995-4D39-97FE-3CE80E892C69} + {B9AAA11D-8C8C-44C3-AADE-801376EF82F0} = {DC5A2CB1-8995-4D39-97FE-3CE80E892C69} + {1CD46DC5-6022-4BBE-9A1C-6B13C3CEFC75} = {DC5A2CB1-8995-4D39-97FE-3CE80E892C69} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {E370FB31-CF95-47D1-B1E1-863A77973FF8} RESX_SortFileContentOnSave = True + SolutionGuid = {E370FB31-CF95-47D1-B1E1-863A77973FF8} EndGlobalSection GlobalSection(SharedMSBuildProjectFiles) = preSolution test\Microsoft.Health.Fhir.Shared.Tests.E2E.Common\Microsoft.Health.Fhir.Shared.Tests.E2E.Common.projitems*{0478b687-7105-40f6-a2dc-81057890e944}*SharedItemsImports = 13 diff --git a/R4.slnf b/R4.slnf index 4469743c86..0e555d8325 100644 --- a/R4.slnf +++ b/R4.slnf @@ -10,6 +10,9 @@ "src\\Microsoft.Health.Fhir.Azure\\Microsoft.Health.Fhir.Azure.csproj", "src\\Microsoft.Health.Fhir.Core.UnitTests\\Microsoft.Health.Fhir.Core.UnitTests.csproj", "src\\Microsoft.Health.Fhir.Core\\Microsoft.Health.Fhir.Core.csproj", + "src\Microsoft.Health.Fhir.CosmosDb.Core\Microsoft.Health.Fhir.CosmosDb.Core.csproj", + "src\Microsoft.Health.Fhir.CosmosDb.Initialization.UnitTests\Microsoft.Health.Fhir.CosmosDb.Initialization.UnitTests.csproj", + "src\Microsoft.Health.Fhir.CosmosDb.Initialization\Microsoft.Health.Fhir.CosmosDb.Initialization.csproj", "src\\Microsoft.Health.Fhir.CosmosDb.UnitTests\\Microsoft.Health.Fhir.CosmosDb.UnitTests.csproj", "src\\Microsoft.Health.Fhir.CosmosDb\\Microsoft.Health.Fhir.CosmosDb.csproj", "src\\Microsoft.Health.Fhir.R4.Api\\Microsoft.Health.Fhir.R4.Api.csproj", diff --git a/R5.slnf b/R5.slnf index b34a610a38..61bdc352fd 100644 --- a/R5.slnf +++ b/R5.slnf @@ -10,6 +10,9 @@ "src\\Microsoft.Health.Fhir.Azure\\Microsoft.Health.Fhir.Azure.csproj", "src\\Microsoft.Health.Fhir.Core.UnitTests\\Microsoft.Health.Fhir.Core.UnitTests.csproj", "src\\Microsoft.Health.Fhir.Core\\Microsoft.Health.Fhir.Core.csproj", + "src\Microsoft.Health.Fhir.CosmosDb.Core\Microsoft.Health.Fhir.CosmosDb.Core.csproj", + "src\Microsoft.Health.Fhir.CosmosDb.Initialization.UnitTests\Microsoft.Health.Fhir.CosmosDb.Initialization.UnitTests.csproj", + "src\Microsoft.Health.Fhir.CosmosDb.Initialization\Microsoft.Health.Fhir.CosmosDb.Initialization.csproj", "src\\Microsoft.Health.Fhir.CosmosDb.UnitTests\\Microsoft.Health.Fhir.CosmosDb.UnitTests.csproj", "src\\Microsoft.Health.Fhir.CosmosDb\\Microsoft.Health.Fhir.CosmosDb.csproj", "src\\Microsoft.Health.Fhir.R5.Api\\Microsoft.Health.Fhir.R5.Api.csproj", diff --git a/build/docker/Dockerfile b/build/docker/Dockerfile index 5e16dc879c..12960bf45b 100644 --- a/build/docker/Dockerfile +++ b/build/docker/Dockerfile @@ -43,6 +43,12 @@ COPY ./src/Microsoft.Health.Fhir.Api/Microsoft.Health.Fhir.Api.csproj \ COPY ./src/Microsoft.Health.Fhir.CosmosDb/Microsoft.Health.Fhir.CosmosDb.csproj \ ./src/Microsoft.Health.Fhir.CosmosDb/Microsoft.Health.Fhir.CosmosDb.csproj +COPY ./src/Microsoft.Health.Fhir.CosmosDb.Core/Microsoft.Health.Fhir.CosmosDb.Core.csproj \ + ./src/Microsoft.Health.Fhir.CosmosDb.Core/Microsoft.Health.Fhir.CosmosDb.Core.csproj + +COPY ./src/Microsoft.Health.Fhir.CosmosDb.Initialization/Microsoft.Health.Fhir.CosmosDb.Initialization.csproj \ + ./src/Microsoft.Health.Fhir.CosmosDb.Initialization/Microsoft.Health.Fhir.CosmosDb.Initialization.csproj + COPY ./src/Microsoft.Health.Fhir.${FHIR_VERSION}.Core/Microsoft.Health.Fhir.${FHIR_VERSION}.Core.csproj \ ./src/Microsoft.Health.Fhir.${FHIR_VERSION}.Core/Microsoft.Health.Fhir.${FHIR_VERSION}.Core.csproj diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Configs/CosmosCollectionConfiguration.cs b/src/Microsoft.Health.Fhir.CosmosDb.Core/Configs/CosmosCollectionConfiguration.cs similarity index 90% rename from src/Microsoft.Health.Fhir.CosmosDb/Configs/CosmosCollectionConfiguration.cs rename to src/Microsoft.Health.Fhir.CosmosDb.Core/Configs/CosmosCollectionConfiguration.cs index 2130fe6ffe..7447ee7eea 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Configs/CosmosCollectionConfiguration.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb.Core/Configs/CosmosCollectionConfiguration.cs @@ -3,7 +3,7 @@ // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. // ------------------------------------------------------------------------------------------------- -namespace Microsoft.Health.Fhir.CosmosDb.Configs +namespace Microsoft.Health.Fhir.CosmosDb.Core.Configs { public class CosmosCollectionConfiguration { diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Configs/CosmosDataStoreConfiguration.cs b/src/Microsoft.Health.Fhir.CosmosDb.Core/Configs/CosmosDataStoreConfiguration.cs similarity index 98% rename from src/Microsoft.Health.Fhir.CosmosDb/Configs/CosmosDataStoreConfiguration.cs rename to src/Microsoft.Health.Fhir.CosmosDb.Core/Configs/CosmosDataStoreConfiguration.cs index f6d85f5852..eb21be6264 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Configs/CosmosDataStoreConfiguration.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb.Core/Configs/CosmosDataStoreConfiguration.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using Microsoft.Azure.Cosmos; -namespace Microsoft.Health.Fhir.CosmosDb.Configs +namespace Microsoft.Health.Fhir.CosmosDb.Core.Configs { public class CosmosDataStoreConfiguration { diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Configs/CosmosDataStoreParallelQueryOptions.cs b/src/Microsoft.Health.Fhir.CosmosDb.Core/Configs/CosmosDataStoreParallelQueryOptions.cs similarity index 94% rename from src/Microsoft.Health.Fhir.CosmosDb/Configs/CosmosDataStoreParallelQueryOptions.cs rename to src/Microsoft.Health.Fhir.CosmosDb.Core/Configs/CosmosDataStoreParallelQueryOptions.cs index a161523b83..78c28292e0 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Configs/CosmosDataStoreParallelQueryOptions.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb.Core/Configs/CosmosDataStoreParallelQueryOptions.cs @@ -5,7 +5,7 @@ using System; -namespace Microsoft.Health.Fhir.CosmosDb.Configs +namespace Microsoft.Health.Fhir.CosmosDb.Core.Configs { public class CosmosDataStoreParallelQueryOptions { diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Configs/CosmosDataStoreRetryOptions.cs b/src/Microsoft.Health.Fhir.CosmosDb.Core/Configs/CosmosDataStoreRetryOptions.cs similarity index 93% rename from src/Microsoft.Health.Fhir.CosmosDb/Configs/CosmosDataStoreRetryOptions.cs rename to src/Microsoft.Health.Fhir.CosmosDb.Core/Configs/CosmosDataStoreRetryOptions.cs index 9e789d508a..e99ad62f2a 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Configs/CosmosDataStoreRetryOptions.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb.Core/Configs/CosmosDataStoreRetryOptions.cs @@ -3,7 +3,7 @@ // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. // ------------------------------------------------------------------------------------------------- -namespace Microsoft.Health.Fhir.CosmosDb.Configs +namespace Microsoft.Health.Fhir.CosmosDb.Core.Configs { public class CosmosDataStoreRetryOptions { diff --git a/src/Microsoft.Health.Fhir.CosmosDb.Core/Constants.cs b/src/Microsoft.Health.Fhir.CosmosDb.Core/Constants.cs new file mode 100644 index 0000000000..e5c4b4d54c --- /dev/null +++ b/src/Microsoft.Health.Fhir.CosmosDb.Core/Constants.cs @@ -0,0 +1,13 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +namespace Microsoft.Health.Fhir.CosmosDb.Core +{ + public static class Constants + { + public const string CollectionConfigurationName = "fhirCosmosDb"; + public const string RawResource = "rawResource"; + } +} diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosClientExtensions.cs b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/CosmosClientExtensions.cs similarity index 93% rename from src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosClientExtensions.cs rename to src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/CosmosClientExtensions.cs index 6c37bf27c5..d43cfb79ee 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosClientExtensions.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/CosmosClientExtensions.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; using Microsoft.Azure.Cosmos; -namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage +namespace Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage { public static class CosmosClientExtensions { diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Versioning/ICollectionUpdater.cs b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/ICollectionDataUpdater.cs similarity index 83% rename from src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Versioning/ICollectionUpdater.cs rename to src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/ICollectionDataUpdater.cs index c2842808a0..93cc4bc43d 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Versioning/ICollectionUpdater.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/ICollectionDataUpdater.cs @@ -7,9 +7,9 @@ using System.Threading.Tasks; using Microsoft.Azure.Cosmos; -namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage.Versioning +namespace Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage { - public interface ICollectionUpdater + public interface ICollectionDataUpdater { Task ExecuteAsync(Container container, CancellationToken cancellationToken); } diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/ICollectionInitializer.cs b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/ICollectionInitializer.cs similarity index 78% rename from src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/ICollectionInitializer.cs rename to src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/ICollectionInitializer.cs index 3fb4ab2e87..db39a41123 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/ICollectionInitializer.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/ICollectionInitializer.cs @@ -6,11 +6,12 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos; +using Polly; -namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage +namespace Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage { public interface ICollectionInitializer { - Task InitializeCollectionAsync(CosmosClient client, CancellationToken cancellationToken = default); + Task InitializeCollectionAsync(CosmosClient client, AsyncPolicy retryPolicy, CancellationToken cancellationToken = default); } } diff --git a/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/ICollectionSetup.cs b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/ICollectionSetup.cs new file mode 100644 index 0000000000..03b07d0e66 --- /dev/null +++ b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/ICollectionSetup.cs @@ -0,0 +1,21 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Polly; + +namespace Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage +{ + public interface ICollectionSetup + { + public Task CreateDatabaseAsync(AsyncPolicy retryPolicy, CancellationToken cancellationToken); + + public Task CreateCollectionAsync(IEnumerable collectionInitializers, AsyncPolicy retryPolicy, CancellationToken cancellationToken = default); + + public Task UpdateFhirCollectionSettingsAsync(CancellationToken cancellationToken); + } +} diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/ICosmosClientInitializer.cs b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/ICosmosClientInitializer.cs similarity index 70% rename from src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/ICosmosClientInitializer.cs rename to src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/ICosmosClientInitializer.cs index c7ae2075d1..f5e2d179c5 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/ICosmosClientInitializer.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/ICosmosClientInitializer.cs @@ -7,9 +7,9 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos; -using Microsoft.Health.Fhir.CosmosDb.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Configs; -namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage +namespace Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage { /// /// Provides methods for creating a CosmosClient instance and initializing a collection. @@ -32,16 +32,6 @@ public interface ICosmosClientInitializer /// The collection configuration for the query to use Task OpenCosmosClient(CosmosClient client, CosmosDataStoreConfiguration configuration, CosmosCollectionConfiguration cosmosCollectionConfiguration); - /// - /// Ensures that the necessary database and collection exist with the proper indexing policy and stored procedures - /// - /// The instance to use for initialization. - /// The data store configuration. - /// The collection of collection initializers. - /// Cancellation token. - /// A task - Task InitializeDataStoreAsync(CosmosClient client, CosmosDataStoreConfiguration cosmosDataStoreConfiguration, IEnumerable collectionInitializers, CancellationToken cancellationToken = default); - /// /// Creates a new Container instance for access the Cosmos API /// diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/ICosmosClientTestProvider.cs b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/ICosmosClientTestProvider.cs similarity index 86% rename from src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/ICosmosClientTestProvider.cs rename to src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/ICosmosClientTestProvider.cs index 5b2f98d058..8f376ddd28 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/ICosmosClientTestProvider.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/ICosmosClientTestProvider.cs @@ -6,9 +6,9 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos; -using Microsoft.Health.Fhir.CosmosDb.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Configs; -namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage +namespace Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage { public interface ICosmosClientTestProvider { diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/ICosmosDbDistributedLock.cs b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/ICosmosDbDistributedLock.cs similarity index 95% rename from src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/ICosmosDbDistributedLock.cs rename to src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/ICosmosDbDistributedLock.cs index 438fb873d0..a230b97dc6 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/ICosmosDbDistributedLock.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/ICosmosDbDistributedLock.cs @@ -7,7 +7,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage +namespace Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage { public interface ICosmosDbDistributedLock : IAsyncDisposable, IDisposable { diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/ICosmosDbDistributedLockFactory.cs b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/ICosmosDbDistributedLockFactory.cs similarity index 90% rename from src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/ICosmosDbDistributedLockFactory.cs rename to src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/ICosmosDbDistributedLockFactory.cs index c315b4ef67..095997af51 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/ICosmosDbDistributedLockFactory.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/ICosmosDbDistributedLockFactory.cs @@ -5,7 +5,7 @@ using Microsoft.Azure.Cosmos; -namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage +namespace Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage { public interface ICosmosDbDistributedLockFactory { diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/KnownDocumentProperties.cs b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/KnownDocumentProperties.cs similarity index 93% rename from src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/KnownDocumentProperties.cs rename to src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/KnownDocumentProperties.cs index 44ec2d0451..a3ac60a4e0 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/KnownDocumentProperties.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/KnownDocumentProperties.cs @@ -3,7 +3,7 @@ // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. // ------------------------------------------------------------------------------------------------- -namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage +namespace Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage { public static class KnownDocumentProperties { diff --git a/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/StoreProcedures/IStoredProcedure.cs b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/StoreProcedures/IStoredProcedure.cs new file mode 100644 index 0000000000..8c731baf36 --- /dev/null +++ b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/StoreProcedures/IStoredProcedure.cs @@ -0,0 +1,15 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Azure.Cosmos.Scripts; + +namespace Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.StoredProcedures +{ + public interface IStoredProcedure + { + Task> ExecuteStoredProcAsync(Scripts client, string partitionId, CancellationToken cancellationToken, params object[] parameters); + } +} diff --git a/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/StoreProcedures/IStoredProcedureInstaller.cs b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/StoreProcedures/IStoredProcedureInstaller.cs new file mode 100644 index 0000000000..b44e5fb46f --- /dev/null +++ b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/StoreProcedures/IStoredProcedureInstaller.cs @@ -0,0 +1,16 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Azure.Cosmos; + +namespace Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.StoredProcedures +{ + public interface IStoredProcedureInstaller + { + Task ExecuteAsync(Container container, CancellationToken cancellationToken); + } +} diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/IStoredProcedure.cs b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/StoreProcedures/IStoredProcedureMetadata.cs similarity index 80% rename from src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/IStoredProcedure.cs rename to src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/StoreProcedures/IStoredProcedureMetadata.cs index 84fb9ffc41..9131d31eff 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/IStoredProcedure.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/StoreProcedures/IStoredProcedureMetadata.cs @@ -5,9 +5,9 @@ using Microsoft.Azure.Cosmos.Scripts; -namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage.StoredProcedures +namespace Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.StoredProcedures { - public interface IStoredProcedure + public interface IStoredProcedureMetadata { string FullName { get; } diff --git a/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/StoreProcedures/StoredProcedureBase.cs b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/StoreProcedures/StoredProcedureBase.cs new file mode 100644 index 0000000000..55f512634f --- /dev/null +++ b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/StoreProcedures/StoredProcedureBase.cs @@ -0,0 +1,43 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Threading; +using System.Threading.Tasks; +using EnsureThat; +using Microsoft.Azure.Cosmos; +using Microsoft.Azure.Cosmos.Scripts; +using Microsoft.Health.Core.Extensions; + +namespace Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.StoredProcedures +{ + public abstract class StoredProcedureBase : IStoredProcedure + { + private readonly IStoredProcedureMetadata _storedProcedureMetadata; + + protected StoredProcedureBase(IStoredProcedureMetadata storedProcedureMetadata) + { + EnsureArg.IsNotNull(storedProcedureMetadata, nameof(storedProcedureMetadata)); + + _storedProcedureMetadata = storedProcedureMetadata; + } + + public string FullName => _storedProcedureMetadata.FullName; + + public async Task> ExecuteStoredProcAsync(Scripts client, string partitionId, CancellationToken cancellationToken, params object[] parameters) + { + EnsureArg.IsNotNull(client, nameof(client)); + EnsureArg.IsNotNull(partitionId, nameof(partitionId)); + + StoredProcedureExecuteResponse results = await client.ExecuteStoredProcedureAsync( + FullName, + new PartitionKey(partitionId), + parameters, + cancellationToken: cancellationToken); + + return results; + } + } +} diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/StoredProcedureBase.cs b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/StoreProcedures/StoredProcedureMetadataBase.cs similarity index 70% rename from src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/StoredProcedureBase.cs rename to src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/StoreProcedures/StoredProcedureMetadataBase.cs index edb7ce70e3..e6cbc94eee 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/StoredProcedureBase.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/StoreProcedures/StoredProcedureMetadataBase.cs @@ -12,20 +12,20 @@ using Microsoft.Azure.Cosmos.Scripts; using Microsoft.Health.Core.Extensions; -namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage.StoredProcedures +namespace Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.StoredProcedures { - public abstract class StoredProcedureBase : IStoredProcedure + public abstract class StoredProcedureMetadataBase : IStoredProcedureMetadata { private readonly Lazy _body; private readonly Lazy _versionedName; - protected StoredProcedureBase() + protected StoredProcedureMetadataBase() { _body = new Lazy(GetBody); _versionedName = new Lazy(() => $"{Name}_{_body.Value.ComputeHash()}"); } - protected virtual string Name => CamelCase(GetType().Name); + public virtual string Name => CamelCase(RemoveSuffix(GetType().Name, "Metadata")); public string FullName => _versionedName.Value; @@ -38,20 +38,6 @@ public StoredProcedureProperties ToStoredProcedureProperties() }; } - protected async Task> ExecuteStoredProc(Scripts client, string partitionId, CancellationToken cancellationToken, params object[] parameters) - { - EnsureArg.IsNotNull(client, nameof(client)); - EnsureArg.IsNotNull(partitionId, nameof(partitionId)); - - StoredProcedureExecuteResponse results = await client.ExecuteStoredProcedureAsync( - FullName, - new PartitionKey(partitionId), - parameters, - cancellationToken: cancellationToken); - - return results; - } - public Uri GetUri(Uri collection) { return new Uri($"{collection}/sprocs/{Uri.EscapeDataString(FullName)}", UriKind.Relative); @@ -76,5 +62,15 @@ private static string CamelCase(string str) return string.Concat(char.ToLowerInvariant(str[0]), str.Substring(1)); } + + private static string RemoveSuffix(string str, string suffix) + { + if (str.EndsWith(suffix, StringComparison.Ordinal)) + { + return str.Substring(0, str.Length - suffix.Length); + } + + return str; + } } } diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/SystemData.cs b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/SystemData.cs similarity index 95% rename from src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/SystemData.cs rename to src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/SystemData.cs index 040d80d6a5..c7b1c06a86 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/SystemData.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/SystemData.cs @@ -5,7 +5,7 @@ using Newtonsoft.Json; -namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage +namespace Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage { /// /// Defines a base object for serializing System/Meta information to a CosmosDb collection diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Versioning/CollectionUpgradeManager.cs b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/Versioning/CollectionUpgradeManager.cs similarity index 80% rename from src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Versioning/CollectionUpgradeManager.cs rename to src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/Versioning/CollectionUpgradeManager.cs index 0ee8d49834..61b3668588 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Versioning/CollectionUpgradeManager.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/Versioning/CollectionUpgradeManager.cs @@ -4,6 +4,7 @@ // ------------------------------------------------------------------------------------------------- using System; +using System.Collections; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -11,32 +12,32 @@ using Microsoft.Azure.Cosmos; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Microsoft.Health.Fhir.CosmosDb.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.StoredProcedures; -namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage.Versioning +namespace Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.Versioning { - internal class CollectionUpgradeManager : IUpgradeManager + public class CollectionUpgradeManager : IUpgradeManager { - private readonly IEnumerable _collectionUpdater; + private readonly ICollectionDataUpdater _collectionDataUpdater; private readonly CosmosDataStoreConfiguration _configuration; private readonly CosmosCollectionConfiguration _collectionConfiguration; private readonly ICosmosDbDistributedLockFactory _lockFactory; private readonly ILogger _logger; public CollectionUpgradeManager( - IEnumerable collectionUpdater, + ICollectionDataUpdater collectionDataUpdater, CosmosDataStoreConfiguration configuration, IOptionsMonitor namedCosmosCollectionConfigurationAccessor, ICosmosDbDistributedLockFactory lockFactory, ILogger logger) { - EnsureArg.IsNotNull(collectionUpdater, nameof(collectionUpdater)); + EnsureArg.IsNotNull(collectionDataUpdater, nameof(collectionDataUpdater)); EnsureArg.IsNotNull(configuration, nameof(configuration)); EnsureArg.IsNotNull(namedCosmosCollectionConfigurationAccessor, nameof(namedCosmosCollectionConfigurationAccessor)); EnsureArg.IsNotNull(lockFactory, nameof(lockFactory)); EnsureArg.IsNotNull(logger, nameof(logger)); - - _collectionUpdater = collectionUpdater; + _collectionDataUpdater = collectionDataUpdater; _configuration = configuration; _collectionConfiguration = GetCosmosCollectionConfiguration(namedCosmosCollectionConfigurationAccessor, Constants.CollectionConfigurationName); _lockFactory = lockFactory; @@ -48,7 +49,7 @@ public CollectionUpgradeManager( /// public int CollectionSettingsVersion { get; } = 3; - public async Task SetupContainerAsync(Container container) + public async Task SetupContainerAsync(Container container, CancellationToken cancellationToken) { EnsureArg.IsNotNull(container, nameof(container)); @@ -59,14 +60,7 @@ public async Task SetupContainerAsync(Container container) _logger.LogDebug("Attempting to acquire upgrade lock"); await distributedLock.AcquireLock(cancellationTokenSource.Token); - - foreach (var updater in _collectionUpdater) - { - _logger.LogDebug("Running {CollectionUpdater} on {CollectionId}", updater.GetType().Name, _collectionConfiguration.CollectionId); - - await updater.ExecuteAsync(container, CancellationToken.None); - } - + await _collectionDataUpdater.ExecuteAsync(container, cancellationToken); await distributedLock.ReleaseLock(); } } diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Versioning/CollectionVersion.cs b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/Versioning/CollectionVersion.cs similarity index 91% rename from src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Versioning/CollectionVersion.cs rename to src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/Versioning/CollectionVersion.cs index c298ea0bb5..caa00fce17 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Versioning/CollectionVersion.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/Versioning/CollectionVersion.cs @@ -5,7 +5,7 @@ using Newtonsoft.Json; -namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage.Versioning +namespace Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.Versioning { public class CollectionVersion : SystemData { diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Versioning/IUpgradeManager.cs b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/Versioning/IUpgradeManager.cs similarity index 71% rename from src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Versioning/IUpgradeManager.cs rename to src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/Versioning/IUpgradeManager.cs index 2d169b97ed..f7cae70eb4 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Versioning/IUpgradeManager.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb.Core/Features/Storage/Versioning/IUpgradeManager.cs @@ -3,13 +3,14 @@ // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. // ------------------------------------------------------------------------------------------------- +using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos; -namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage.Versioning +namespace Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.Versioning { public interface IUpgradeManager { - Task SetupContainerAsync(Container container); + Task SetupContainerAsync(Container container, CancellationToken cancellationToken); } } diff --git a/src/Microsoft.Health.Fhir.CosmosDb.Core/Microsoft.Health.Fhir.CosmosDb.Core.csproj b/src/Microsoft.Health.Fhir.CosmosDb.Core/Microsoft.Health.Fhir.CosmosDb.Core.csproj new file mode 100644 index 0000000000..04072db7f7 --- /dev/null +++ b/src/Microsoft.Health.Fhir.CosmosDb.Core/Microsoft.Health.Fhir.CosmosDb.Core.csproj @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/Microsoft.Health.Fhir.CosmosDb.Initialization.UnitTests/CosmosDbInitializationTests.cs b/src/Microsoft.Health.Fhir.CosmosDb.Initialization.UnitTests/CosmosDbInitializationTests.cs new file mode 100644 index 0000000000..3063eb343d --- /dev/null +++ b/src/Microsoft.Health.Fhir.CosmosDb.Initialization.UnitTests/CosmosDbInitializationTests.cs @@ -0,0 +1,73 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Linq; +using Microsoft.Azure.Cosmos.Scripts; +using Microsoft.Health.Core.Extensions; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.StoredProcedures; +using Microsoft.Health.Fhir.CosmosDb.Initialization.Features.Storage.StoredProcedures; +using Microsoft.Health.Fhir.Tests.Common; +using Microsoft.Health.Test.Utilities; +using Xunit; + +namespace Microsoft.Health.Fhir.CosmosDb.Initialization.UnitTests +{ + [Trait(Traits.OwningTeam, OwningTeam.Fhir)] + [Trait(Traits.Category, Categories.DataSourceValidation)] + public class CosmosDbInitializationTests + { + [Fact] + public void GivenAListOfExpectedStoredProcedures_ThenAllStoredProceduresInTheAssemblyShouldMatch() + { + string[] storeProcs = new string[] + { + "AcquireExportJobsMetadata", + "AcquireReindexJobsMetadata", + "HardDeleteMetadata", + "ReplaceSingleResourceMetadata", + "UpdateUnsupportedSearchParametersMetadata", + }; + + Type[] fhirStoredProcsClasses = typeof(DataPlaneStoredProcedureInstaller).Assembly + .GetTypes() + .Where(x => !x.IsAbstract && typeof(StoredProcedureMetadataBase).IsAssignableFrom(x)) + .ToArray(); + + Assert.NotEmpty(fhirStoredProcsClasses); + + foreach (Type storeproc in fhirStoredProcsClasses) + { + Assert.Contains(storeproc.Name, storeProcs); + } + } + + [Fact] + public void GivenAllStoredProceduresInTheAssembly_ThenAllStructureShouldBeAsExpected() + { + Type[] fhirStoredProcsClasses = typeof(DataPlaneStoredProcedureInstaller).Assembly + .GetTypes() + .Where(x => !x.IsAbstract && typeof(StoredProcedureMetadataBase).IsAssignableFrom(x)) + .ToArray(); + + Assert.NotEmpty(fhirStoredProcsClasses); + + foreach (Type storeproc in fhirStoredProcsClasses) + { + StoredProcedureMetadataBase storedProcedureMetadata = storeproc.GetConstructors().First().Invoke(null) as StoredProcedureMetadataBase; + + Assert.NotNull(storedProcedureMetadata); + Assert.NotNull(storedProcedureMetadata.FullName); + + StoredProcedureProperties metadataProperties = storedProcedureMetadata.ToStoredProcedureProperties(); + Assert.NotNull(metadataProperties.Id); + Assert.False(string.IsNullOrEmpty(metadataProperties.Body)); + + string storedProcedureHash = metadataProperties.Body.ComputeHash(); + Assert.Equal($"{storedProcedureMetadata.Name}_{storedProcedureHash}", storedProcedureMetadata.FullName); + } + } + } +} diff --git a/src/Microsoft.Health.Fhir.CosmosDb.Initialization.UnitTests/Microsoft.Health.Fhir.CosmosDb.Initialization.UnitTests.csproj b/src/Microsoft.Health.Fhir.CosmosDb.Initialization.UnitTests/Microsoft.Health.Fhir.CosmosDb.Initialization.UnitTests.csproj new file mode 100644 index 0000000000..57eb3e5ddb --- /dev/null +++ b/src/Microsoft.Health.Fhir.CosmosDb.Initialization.UnitTests/Microsoft.Health.Fhir.CosmosDb.Initialization.UnitTests.csproj @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CollectionInitializer.cs b/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/CollectionInitializer.cs similarity index 82% rename from src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CollectionInitializer.cs rename to src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/CollectionInitializer.cs index 3791292609..8689d160c3 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CollectionInitializer.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/CollectionInitializer.cs @@ -10,18 +10,18 @@ using Microsoft.Azure.Cosmos; using Microsoft.Extensions.Logging; using Microsoft.Health.Abstractions.Exceptions; -using Microsoft.Health.Fhir.CosmosDb.Configs; -using Microsoft.Health.Fhir.CosmosDb.Features.Storage.Versioning; +using Microsoft.Health.Fhir.CosmosDb.Core.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.Versioning; using Polly; -namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage +namespace Microsoft.Health.Fhir.CosmosDb.Initialization.Features.Storage { public class CollectionInitializer : ICollectionInitializer { private readonly CosmosCollectionConfiguration _cosmosCollectionConfiguration; private readonly CosmosDataStoreConfiguration _cosmosDataStoreConfiguration; private readonly IUpgradeManager _upgradeManager; - private readonly RetryExceptionPolicyFactory _retryExceptionPolicyFactory; private readonly ICosmosClientTestProvider _clientTestProvider; private readonly ILogger _logger; @@ -29,7 +29,6 @@ public CollectionInitializer( CosmosCollectionConfiguration cosmosCollectionConfiguration, CosmosDataStoreConfiguration cosmosDataStoreConfiguration, IUpgradeManager upgradeManager, - RetryExceptionPolicyFactory retryExceptionPolicyFactory, ICosmosClientTestProvider clientTestProvider, ILogger logger) { @@ -38,35 +37,31 @@ public CollectionInitializer( EnsureArg.IsNotNull(clientTestProvider, nameof(clientTestProvider)); EnsureArg.IsNotNull(cosmosDataStoreConfiguration, nameof(cosmosDataStoreConfiguration)); EnsureArg.IsNotNull(upgradeManager, nameof(upgradeManager)); - EnsureArg.IsNotNull(retryExceptionPolicyFactory, nameof(retryExceptionPolicyFactory)); EnsureArg.IsNotNull(logger, nameof(logger)); _cosmosCollectionConfiguration = cosmosCollectionConfiguration; _cosmosDataStoreConfiguration = cosmosDataStoreConfiguration; _upgradeManager = upgradeManager; - _retryExceptionPolicyFactory = retryExceptionPolicyFactory; _clientTestProvider = clientTestProvider; _logger = logger; } - public async Task InitializeCollectionAsync(CosmosClient client, CancellationToken cancellationToken = default) + public async Task InitializeCollectionAsync(CosmosClient client, AsyncPolicy retryPolicy, CancellationToken cancellationToken = default) { Database database = client.GetDatabase(_cosmosDataStoreConfiguration.DatabaseId); Container containerClient = database.GetContainer(_cosmosCollectionConfiguration.CollectionId); _logger.LogInformation("Finding Container: {CollectionId}", _cosmosCollectionConfiguration.CollectionId); - AsyncPolicy retryPolicy = _retryExceptionPolicyFactory.RetryPolicy; - var existingContainer = await retryPolicy.ExecuteAsync(async () => await database.TryGetContainerAsync(_cosmosCollectionConfiguration.CollectionId)); - _logger.LogInformation("Creating Cosmos Container if not exits: {CollectionId}", _cosmosCollectionConfiguration.CollectionId); - ContainerResponse containerResponse = await retryPolicy.ExecuteAsync(async () => - await database.CreateContainerIfNotExistsAsync( - _cosmosCollectionConfiguration.CollectionId, - $"/{KnownDocumentProperties.PartitionKey}", - _cosmosCollectionConfiguration.InitialCollectionThroughput)); + ContainerResponse containerResponse = await retryPolicy + .ExecuteAsync(async () => + await database.CreateContainerIfNotExistsAsync( + _cosmosCollectionConfiguration.CollectionId, + $"/{KnownDocumentProperties.PartitionKey}", + _cosmosCollectionConfiguration.InitialCollectionThroughput)); if (containerResponse.StatusCode == HttpStatusCode.Created || containerResponse.Resource.DefaultTimeToLive != -1) { @@ -94,7 +89,7 @@ await retryPolicy.ExecuteAsync(async () => } }); - await _upgradeManager.SetupContainerAsync(containerClient); + await _upgradeManager.SetupContainerAsync(containerClient, cancellationToken); return existingContainer; } diff --git a/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/DataPlaneCollectionSetup.cs b/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/DataPlaneCollectionSetup.cs new file mode 100644 index 0000000000..f07aeac0ea --- /dev/null +++ b/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/DataPlaneCollectionSetup.cs @@ -0,0 +1,172 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using EnsureThat; +using Microsoft.Azure.Cosmos; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Microsoft.Health.Abstractions.Exceptions; +using Microsoft.Health.Core.Features.Context; +using Microsoft.Health.Fhir.CosmosDb.Core; +using Microsoft.Health.Fhir.CosmosDb.Core.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.StoredProcedures; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.Versioning; +using Polly; +using Polly.Retry; + +namespace Microsoft.Health.Fhir.CosmosDb.Initialization.Features.Storage +{ + public class DataPlaneCollectionSetup : ICollectionSetup + { + private const int CollectionSettingsVersion = 3; + private readonly ILogger _logger; + private readonly CosmosClient _client; + private readonly Lazy _container; + private readonly CosmosDataStoreConfiguration _cosmosDataStoreConfiguration; + private readonly IStoredProcedureInstaller _storedProcedureInstaller; + + public DataPlaneCollectionSetup( + CosmosDataStoreConfiguration cosmosDataStoreConfiguration, + IOptionsMonitor collectionConfiguration, + ICosmosClientInitializer cosmosClientInitializer, + IStoredProcedureInstaller storedProcedureInstaller, + ILogger logger) + { + EnsureArg.IsNotNull(cosmosDataStoreConfiguration, nameof(cosmosDataStoreConfiguration)); + EnsureArg.IsNotNull(cosmosClientInitializer, nameof(cosmosClientInitializer)); + EnsureArg.IsNotNull(collectionConfiguration, nameof(collectionConfiguration)); + EnsureArg.IsNotNull(storedProcedureInstaller, nameof(storedProcedureInstaller)); + EnsureArg.IsNotNull(logger, nameof(logger)); + + string collectionId = collectionConfiguration.Get(Constants.CollectionConfigurationName).CollectionId; + _cosmosDataStoreConfiguration = cosmosDataStoreConfiguration; + _client = cosmosClientInitializer.CreateCosmosClient(cosmosDataStoreConfiguration); + _container = new Lazy(() => cosmosClientInitializer.CreateFhirContainer( + _client, + cosmosDataStoreConfiguration.DatabaseId, + collectionId)); + _storedProcedureInstaller = storedProcedureInstaller; + _logger = logger; + } + + public async Task CreateDatabaseAsync(AsyncPolicy retryPolicy, CancellationToken cancellationToken = default) + { + EnsureArg.IsNotNull(retryPolicy, nameof(retryPolicy)); + + try + { + _logger.LogInformation("Creating Cosmos DB Database {DatabaseId}", _cosmosDataStoreConfiguration.DatabaseId); + + if (_cosmosDataStoreConfiguration.AllowDatabaseCreation) + { + _logger.LogInformation("CreateDatabaseIfNotExists {DatabaseId}", _cosmosDataStoreConfiguration.DatabaseId); + + await retryPolicy.ExecuteAsync( + async () => + await _client.CreateDatabaseIfNotExistsAsync( + _cosmosDataStoreConfiguration.DatabaseId, + _cosmosDataStoreConfiguration.InitialDatabaseThroughput.HasValue ? ThroughputProperties.CreateManualThroughput(_cosmosDataStoreConfiguration.InitialDatabaseThroughput.Value) : null, + cancellationToken: cancellationToken)); + } + + _logger.LogInformation("Cosmos DB Database {DatabaseId} successfully initialized", _cosmosDataStoreConfiguration.DatabaseId); + } + catch (Exception ex) + { + LogLevel logLevel = ex is RequestRateExceededException ? LogLevel.Warning : LogLevel.Critical; + _logger.Log(logLevel, ex, "Cosmos DB Database {DatabaseId} initialization failed", _cosmosDataStoreConfiguration.DatabaseId); + throw; + } + } + + public async Task CreateCollectionAsync(IEnumerable collectionInitializers, AsyncPolicy retryPolicy, CancellationToken cancellationToken = default) + { + EnsureArg.IsNotNull(_client, nameof(_client)); + EnsureArg.IsNotNull(_cosmosDataStoreConfiguration, nameof(_cosmosDataStoreConfiguration)); + EnsureArg.IsNotNull(collectionInitializers, nameof(collectionInitializers)); + EnsureArg.IsNotNull(retryPolicy, nameof(retryPolicy)); + + try + { + _logger.LogInformation("Initializing Cosmos DB Database {DatabaseId} and collections", _cosmosDataStoreConfiguration.DatabaseId); + + foreach (var collectionInitializer in collectionInitializers) + { + await collectionInitializer.InitializeCollectionAsync(_client, retryPolicy, cancellationToken); + } + + _logger.LogInformation("Collections successfully initialized"); + + _logger.LogInformation("Installing Stored Procedures"); + + await _storedProcedureInstaller.ExecuteAsync(_container.Value, cancellationToken); + + _logger.LogInformation("Stored Procedures are installed"); + } + catch (Exception ex) + { + LogLevel logLevel = ex is RequestRateExceededException ? LogLevel.Warning : LogLevel.Critical; + _logger.Log(logLevel, ex, "Collections initialization failed"); + throw; + } + } + + public async Task UpdateFhirCollectionSettingsAsync(CancellationToken cancellationToken) + { + EnsureArg.IsNotNull(_container, nameof(_container)); + + var thisVersion = await GetLatestCollectionVersionAsync(_container.Value, cancellationToken); + + if (thisVersion.Version < 2) + { + var containerResponse = await _container.Value.ReadContainerAsync(cancellationToken: cancellationToken); + + // For more information on setting indexing policies refer to: + // https://docs.microsoft.com/en-us/azure/cosmos-db/how-to-manage-indexing-policy + // It is no longer necessary to explicitly set the kind (range/hash) + containerResponse.Resource.IndexingPolicy.IncludedPaths.Clear(); + containerResponse.Resource.IndexingPolicy.IncludedPaths.Add(new IncludedPath + { + Path = "/*", + }); + + containerResponse.Resource.IndexingPolicy.ExcludedPaths.Clear(); + containerResponse.Resource.IndexingPolicy.ExcludedPaths.Add(new ExcludedPath { Path = $"/{Constants.RawResource}/*", }); + containerResponse.Resource.IndexingPolicy.ExcludedPaths.Add(new ExcludedPath { Path = $"/\"_etag\"/?", }); + + // Setting the DefaultTTL to -1 means that by default all documents in the collection will live forever + // but the Cosmos DB service should monitor this collection for documents that have overridden this default. + // See: https://docs.microsoft.com/en-us/azure/cosmos-db/time-to-live + containerResponse.Resource.DefaultTimeToLive = -1; + + await _container.Value.ReplaceContainerAsync(containerResponse, cancellationToken: cancellationToken); + + thisVersion.Version = CollectionSettingsVersion; + await _container.Value.UpsertItemAsync(thisVersion, new PartitionKey(thisVersion.PartitionKey), cancellationToken: cancellationToken); + } + } + + private static async Task GetLatestCollectionVersionAsync(Container container, CancellationToken cancellationToken) + { + FeedIterator query = container.GetItemQueryIterator( + new QueryDefinition("SELECT * FROM root r"), + requestOptions: new QueryRequestOptions + { + PartitionKey = new PartitionKey(CollectionVersion.CollectionVersionPartition), + }); + + FeedResponse result = await query.ReadNextAsync(cancellationToken); + + return result.FirstOrDefault() ?? new CollectionVersion(); + } + } +} diff --git a/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/DefaultCollectionSetup.cs b/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/DefaultCollectionSetup.cs new file mode 100644 index 0000000000..d74ae3edf9 --- /dev/null +++ b/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/DefaultCollectionSetup.cs @@ -0,0 +1,35 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Polly; + +namespace Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage +{ + /// + /// This class does not execute any real initialization. + /// It's used as a placeholder to external initialization + /// + public class DefaultCollectionSetup : ICollectionSetup + { + public Task CreateCollectionAsync(IEnumerable collectionInitializers, AsyncPolicy retryPolicy, CancellationToken cancellationToken = default) + { + return Task.CompletedTask; + } + + public Task CreateDatabaseAsync(AsyncPolicy retryPolicy, CancellationToken cancellationToken = default) + { + return Task.CompletedTask; + } + + public Task UpdateFhirCollectionSettingsAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + } +} diff --git a/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/StoredProcedures/AcquireExportJobs/AcquireExportJobsMetadata.cs b/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/StoredProcedures/AcquireExportJobs/AcquireExportJobsMetadata.cs new file mode 100644 index 0000000000..43663b9a7e --- /dev/null +++ b/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/StoredProcedures/AcquireExportJobs/AcquireExportJobsMetadata.cs @@ -0,0 +1,14 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.StoredProcedures; +using Microsoft.Health.Fhir.CosmosDb.Initialization.Features.Storage.StoredProcedures; + +namespace Microsoft.Health.Fhir.CosmosDb.Initialization.Features.Storage.StoredProcedures.AcquireExportJobs +{ + public sealed class AcquireExportJobsMetadata : StoredProcedureMetadataBase + { + } +} diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/AcquireExportJobs/acquireExportJobs.js b/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/StoredProcedures/AcquireExportJobs/acquireExportJobs.js similarity index 100% rename from src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/AcquireExportJobs/acquireExportJobs.js rename to src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/StoredProcedures/AcquireExportJobs/acquireExportJobs.js diff --git a/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/StoredProcedures/AcquireReindexJobs/AcquireReindexJobsMetadata.cs b/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/StoredProcedures/AcquireReindexJobs/AcquireReindexJobsMetadata.cs new file mode 100644 index 0000000000..3367ce8544 --- /dev/null +++ b/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/StoredProcedures/AcquireReindexJobs/AcquireReindexJobsMetadata.cs @@ -0,0 +1,14 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.StoredProcedures; +using Microsoft.Health.Fhir.CosmosDb.Initialization.Features.Storage.StoredProcedures; + +namespace Microsoft.Health.Fhir.CosmosDb.Initialization.Features.Storage.StoredProcedures.AcquireReindexJobs +{ + public sealed class AcquireReindexJobsMetadata : StoredProcedureMetadataBase + { + } +} diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/AcquireReindexJobs/acquireReindexJobs.js b/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/StoredProcedures/AcquireReindexJobs/acquireReindexJobs.js similarity index 100% rename from src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/AcquireReindexJobs/acquireReindexJobs.js rename to src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/StoredProcedures/AcquireReindexJobs/acquireReindexJobs.js diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/StoredProcedureInstaller.cs b/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/StoredProcedures/DataPlaneStoredProcedureInstaller.cs similarity index 67% rename from src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/StoredProcedureInstaller.cs rename to src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/StoredProcedures/DataPlaneStoredProcedureInstaller.cs index 1f05340a12..3f162b1e16 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/StoredProcedureInstaller.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/StoredProcedures/DataPlaneStoredProcedureInstaller.cs @@ -9,24 +9,24 @@ using System.Threading.Tasks; using EnsureThat; using Microsoft.Azure.Cosmos; -using Microsoft.Health.Fhir.CosmosDb.Features.Storage.Versioning; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.StoredProcedures; -namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage.StoredProcedures +namespace Microsoft.Health.Fhir.CosmosDb.Initialization.Features.Storage.StoredProcedures { - public class StoredProcedureInstaller : ICollectionUpdater + public sealed class DataPlaneStoredProcedureInstaller : IStoredProcedureInstaller { - private readonly IEnumerable _storedProcedures; + private readonly IEnumerable _storeProceduresMetadata; - public StoredProcedureInstaller(IEnumerable storedProcedures) + public DataPlaneStoredProcedureInstaller(IEnumerable storedProcedures) { EnsureArg.IsNotNull(storedProcedures, nameof(storedProcedures)); - _storedProcedures = storedProcedures; + _storeProceduresMetadata = storedProcedures; } public async Task ExecuteAsync(Container container, CancellationToken cancellationToken) { - foreach (IStoredProcedure storedProc in _storedProcedures) + foreach (IStoredProcedureMetadata storedProc in _storeProceduresMetadata) { try { diff --git a/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/StoredProcedures/HardDelete/HardDeleteMetadata.cs b/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/StoredProcedures/HardDelete/HardDeleteMetadata.cs new file mode 100644 index 0000000000..b886170034 --- /dev/null +++ b/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/StoredProcedures/HardDelete/HardDeleteMetadata.cs @@ -0,0 +1,14 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.StoredProcedures; +using Microsoft.Health.Fhir.CosmosDb.Initialization.Features.Storage.StoredProcedures; + +namespace Microsoft.Health.Fhir.CosmosDb.Initialization.Features.Storage.StoredProcedures.HardDelete +{ + public sealed class HardDeleteMetadata : StoredProcedureMetadataBase + { + } +} diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/HardDelete/hardDelete.js b/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/StoredProcedures/HardDelete/hardDelete.js similarity index 100% rename from src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/HardDelete/hardDelete.js rename to src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/StoredProcedures/HardDelete/hardDelete.js diff --git a/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/StoredProcedures/Replace/ReplaceSingleResourceMetadata.cs b/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/StoredProcedures/Replace/ReplaceSingleResourceMetadata.cs new file mode 100644 index 0000000000..a5616a657c --- /dev/null +++ b/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/StoredProcedures/Replace/ReplaceSingleResourceMetadata.cs @@ -0,0 +1,13 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.StoredProcedures; +using Microsoft.Health.Fhir.CosmosDb.Initialization.Features.Storage.StoredProcedures; + +namespace Microsoft.Health.Fhir.CosmosDb.Initialization.Features.Storage.StoredProcedures.Replace +{ + public sealed class ReplaceSingleResourceMetadata : StoredProcedureMetadataBase + { + } +} diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/Replace/replaceSingleResource.js b/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/StoredProcedures/Replace/replaceSingleResource.js similarity index 100% rename from src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/Replace/replaceSingleResource.js rename to src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/StoredProcedures/Replace/replaceSingleResource.js diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/UpdateUnsupportedSearchParametersToUnsupported/UpdateUnsupportedSearchParameters.cs b/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/StoredProcedures/UpdateUnsupportedSearchParametersToUnsupported/UpdateUnsupportedSearchParameters.cs similarity index 51% rename from src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/UpdateUnsupportedSearchParametersToUnsupported/UpdateUnsupportedSearchParameters.cs rename to src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/StoredProcedures/UpdateUnsupportedSearchParametersToUnsupported/UpdateUnsupportedSearchParameters.cs index e3a735d29c..b0329acfb7 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/UpdateUnsupportedSearchParametersToUnsupported/UpdateUnsupportedSearchParameters.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/StoredProcedures/UpdateUnsupportedSearchParametersToUnsupported/UpdateUnsupportedSearchParameters.cs @@ -7,17 +7,24 @@ using System.Threading.Tasks; using EnsureThat; using Microsoft.Azure.Cosmos.Scripts; -using Microsoft.Health.Fhir.CosmosDb.Features.Storage.Operations.Reindex; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.StoredProcedures; -namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage.StoredProcedures.UpdateUnsupportedSearchParametersToUnsupported +namespace Microsoft.Health.Fhir.CosmosDb.Initialization.Features.Storage.StoredProcedures.UpdateUnsupportedSearchParametersToUnsupported { - internal class UpdateUnsupportedSearchParameters : StoredProcedureBase + public class UpdateUnsupportedSearchParameters : StoredProcedureBase { + private const string _searchParameterStatusPartitionKey = "__searchparameterstatus__"; + + public UpdateUnsupportedSearchParameters() + : base(new UpdateUnsupportedSearchParametersMetadata()) + { + } + public async Task> Execute(Scripts client, CancellationToken cancellationToken) { EnsureArg.IsNotNull(client, nameof(client)); - return await ExecuteStoredProc(client, CosmosDbReindexConstants.SearchParameterStatusPartitionKey, cancellationToken); + return await ExecuteStoredProcAsync(client, _searchParameterStatusPartitionKey, cancellationToken); } } } diff --git a/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/StoredProcedures/UpdateUnsupportedSearchParametersToUnsupported/UpdateUnsupportedSearchParametersMetadata.cs b/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/StoredProcedures/UpdateUnsupportedSearchParametersToUnsupported/UpdateUnsupportedSearchParametersMetadata.cs new file mode 100644 index 0000000000..e918199802 --- /dev/null +++ b/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/StoredProcedures/UpdateUnsupportedSearchParametersToUnsupported/UpdateUnsupportedSearchParametersMetadata.cs @@ -0,0 +1,14 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.StoredProcedures; +using Microsoft.Health.Fhir.CosmosDb.Initialization.Features.Storage.StoredProcedures; + +namespace Microsoft.Health.Fhir.CosmosDb.Initialization.Features.Storage.StoredProcedures.UpdateUnsupportedSearchParametersToUnsupported +{ + public sealed class UpdateUnsupportedSearchParametersMetadata : StoredProcedureMetadataBase + { + } +} diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/UpdateUnsupportedSearchParametersToUnsupported/updateUnsupportedSearchParameters.js b/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/StoredProcedures/UpdateUnsupportedSearchParametersToUnsupported/updateUnsupportedSearchParameters.js similarity index 100% rename from src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/UpdateUnsupportedSearchParametersToUnsupported/updateUnsupportedSearchParameters.js rename to src/Microsoft.Health.Fhir.CosmosDb.Initialization/Features/Storage/StoredProcedures/UpdateUnsupportedSearchParametersToUnsupported/updateUnsupportedSearchParameters.js diff --git a/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Microsoft.Health.Fhir.CosmosDb.Initialization.csproj b/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Microsoft.Health.Fhir.CosmosDb.Initialization.csproj new file mode 100644 index 0000000000..88c7942789 --- /dev/null +++ b/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Microsoft.Health.Fhir.CosmosDb.Initialization.csproj @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Registration/DataPlaneCosmosDbRegistrationExtensions.cs b/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Registration/DataPlaneCosmosDbRegistrationExtensions.cs new file mode 100644 index 0000000000..5208b86158 --- /dev/null +++ b/src/Microsoft.Health.Fhir.CosmosDb.Initialization/Registration/DataPlaneCosmosDbRegistrationExtensions.cs @@ -0,0 +1,33 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- +using System; +using System.Linq; +using Microsoft.Azure.Cosmos; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Health.Extensions.DependencyInjection; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.StoredProcedures; + +namespace Microsoft.Health.Fhir.CosmosDb.Initialization.Registration +{ + public static class DataPlaneCosmosDbRegistrationExtensions + { + public static void AddCosmosDbInitializationDependencies(this IServiceCollection services) + { + Type[] storedProcedureMetadataTypes = typeof(DataPlaneCosmosDbRegistrationExtensions).Assembly.GetTypes() + .Where(x => !x.IsAbstract && typeof(StoredProcedureMetadataBase).IsAssignableFrom(x)) + .Where(x => x.IsClass && !x.IsAbstract && !x.ContainsGenericParameters) + .ToArray(); + + foreach (Type type in storedProcedureMetadataTypes) + { + services + .Add(type) + .Singleton() + .AsSelf() + .AsService(); + } + } + } +} diff --git a/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Health/CosmosHealthCheckTests.cs b/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Health/CosmosHealthCheckTests.cs index ddfab51622..fbb8101214 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Health/CosmosHealthCheckTests.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Health/CosmosHealthCheckTests.cs @@ -17,7 +17,8 @@ using Microsoft.Health.Abstractions.Exceptions; using Microsoft.Health.Core.Features.Health; using Microsoft.Health.Fhir.Core.Features.Health; -using Microsoft.Health.Fhir.CosmosDb.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage; using Microsoft.Health.Fhir.CosmosDb.Features.Storage; using Microsoft.Health.Fhir.Tests.Common; using Microsoft.Health.Test.Utilities; diff --git a/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Health/TestCosmosHealthCheck.cs b/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Health/TestCosmosHealthCheck.cs index 3fd980d170..5622335466 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Health/TestCosmosHealthCheck.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Health/TestCosmosHealthCheck.cs @@ -7,7 +7,8 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Health.Extensions.DependencyInjection; -using Microsoft.Health.Fhir.CosmosDb.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage; using Microsoft.Health.Fhir.CosmosDb.Features.Health; using Microsoft.Health.Fhir.CosmosDb.Features.Storage; diff --git a/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Storage/CosmosFhirDataStoreTests.cs b/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Storage/CosmosFhirDataStoreTests.cs index ecbeba93df..5958d6ee22 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Storage/CosmosFhirDataStoreTests.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Storage/CosmosFhirDataStoreTests.cs @@ -33,7 +33,7 @@ using Microsoft.Health.Fhir.Core.Features.Search.SearchValues; using Microsoft.Health.Fhir.Core.Models; using Microsoft.Health.Fhir.Core.UnitTests.Extensions; -using Microsoft.Health.Fhir.CosmosDb.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Configs; using Microsoft.Health.Fhir.CosmosDb.Features.Queries; using Microsoft.Health.Fhir.CosmosDb.Features.Storage; using Microsoft.Health.Fhir.Tests.Common; diff --git a/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Storage/FhirCosmosClientInitializerTests.cs b/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Storage/FhirCosmosClientInitializerTests.cs index d1f7fbc74d..8fc4f329e3 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Storage/FhirCosmosClientInitializerTests.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Storage/FhirCosmosClientInitializerTests.cs @@ -8,8 +8,10 @@ using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Health.Core.Features.Context; using Microsoft.Health.Fhir.Core.Features.Context; -using Microsoft.Health.Fhir.CosmosDb.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage; using Microsoft.Health.Fhir.CosmosDb.Features.Storage; +using Microsoft.Health.Fhir.CosmosDb.Initialization.Features.Storage; using Microsoft.Health.Fhir.Tests.Common; using Microsoft.Health.Test.Utilities; using NSubstitute; diff --git a/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Storage/Registry/CosmosDbSearchParameterStatusInitializerTests.cs b/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Storage/Registry/CosmosDbSearchParameterStatusInitializerTests.cs index df694110f7..73761adc29 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Storage/Registry/CosmosDbSearchParameterStatusInitializerTests.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Storage/Registry/CosmosDbSearchParameterStatusInitializerTests.cs @@ -7,11 +7,13 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Features; using Microsoft.Azure.Cosmos; using Microsoft.Health.Core; using Microsoft.Health.Fhir.Core.Extensions; using Microsoft.Health.Fhir.Core.Features.Search.Registry; -using Microsoft.Health.Fhir.CosmosDb.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.Versioning; using Microsoft.Health.Fhir.CosmosDb.Features.Queries; using Microsoft.Health.Fhir.CosmosDb.Features.Storage; using Microsoft.Health.Fhir.CosmosDb.Features.Storage.Registry; @@ -66,7 +68,19 @@ public async Task GivenARegistryInitializer_WhenDatabaseIsNew_SearchParametersSh .ExecuteNextAsync() .Returns(Substitute.ForPartsOf>()); + var feedResponse = Substitute.For>(); + + var feedIterator = Substitute.For>(); + feedIterator.ReadNextAsync(Arg.Any()) + .Returns(feedResponse); + Container container = Substitute.For(); + container + .GetItemQueryIterator( + queryDefinition: Arg.Any(), + continuationToken: Arg.Any(), + requestOptions: Arg.Any()) + .Returns(feedIterator); await _initializer.ExecuteAsync(container, CancellationToken.None); @@ -90,7 +104,19 @@ public async Task GivenARegistryInitializer_WhenDatabaseIsExisting_NothingNeedsT .ExecuteNextAsync() .Returns(info => response); + var feedResponse = Substitute.For>(); + + var feedIterator = Substitute.For>(); + feedIterator.ReadNextAsync(Arg.Any()) + .Returns(feedResponse); + Container container = Substitute.For(); + container + .GetItemQueryIterator( + queryDefinition: Arg.Any(), + continuationToken: Arg.Any(), + requestOptions: Arg.Any()) + .Returns(feedIterator); await _initializer.ExecuteAsync(container, CancellationToken.None); diff --git a/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Storage/Versioning/CollectionUpgradeManagerTests.cs b/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Storage/Versioning/CollectionUpgradeManagerTests.cs index 72036938a0..83e9392a95 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Storage/Versioning/CollectionUpgradeManagerTests.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Storage/Versioning/CollectionUpgradeManagerTests.cs @@ -3,16 +3,22 @@ // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. // ------------------------------------------------------------------------------------------------- +using System; using System.Threading; +using DotLiquid.Util; using Microsoft.Azure.Cosmos; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; -using Microsoft.Health.Fhir.CosmosDb.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.StoredProcedures; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.Versioning; using Microsoft.Health.Fhir.CosmosDb.Features.Storage; -using Microsoft.Health.Fhir.CosmosDb.Features.Storage.Versioning; +using Microsoft.Health.Fhir.CosmosDb.Initialization.Features.Storage; using Microsoft.Health.Fhir.Tests.Common; using Microsoft.Health.Test.Utilities; using NSubstitute; +using NSubstitute.ClearExtensions; using Xunit; using Task = System.Threading.Tasks.Task; @@ -38,32 +44,45 @@ public class CollectionUpgradeManagerTests }; private readonly CollectionUpgradeManager _manager; - private readonly Container _client; + private readonly ICollectionDataUpdater _collectionDataUpdater; + private readonly Container _container; private readonly ContainerResponse _containerResponse; public CollectionUpgradeManagerTests() { - var factory = Substitute.For(); + _container = Substitute.For(); + var cosmosDbDistributedLock = Substitute.For(); - var optionsMonitor = Substitute.For>(); + cosmosDbDistributedLock.TryAcquireLock().Returns(true); + var factory = Substitute.For(); + factory.Create(Arg.Any(), Arg.Any()).Returns(cosmosDbDistributedLock); + + var optionsMonitor = Substitute.For>(); optionsMonitor.Get(Constants.CollectionConfigurationName).Returns(_cosmosCollectionConfiguration); - factory.Create(Arg.Any(), Arg.Any()).Returns(cosmosDbDistributedLock); - cosmosDbDistributedLock.TryAcquireLock().Returns(true); + _collectionDataUpdater = Substitute.For(); + _collectionDataUpdater.ExecuteAsync(Arg.Any(), Arg.Any()) + .Returns(Task.FromResult(true)); - _client = Substitute.For(); + var collectionInitializer = Substitute.For(); + collectionInitializer.CreateFhirContainer(Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(_container); var collectionVersionWrappers = Substitute.ForPartsOf>(); - _client.GetItemQueryIterator(Arg.Any(), Arg.Any(), Arg.Any()) + _container.GetItemQueryIterator(Arg.Any(), Arg.Any(), Arg.Any()) .Returns(collectionVersionWrappers); collectionVersionWrappers.ReadNextAsync(Arg.Any()) .Returns(Substitute.ForPartsOf>()); - var updaters = new ICollectionUpdater[] { new FhirCollectionSettingsUpdater(_cosmosDataStoreConfiguration, optionsMonitor, NullLogger.Instance), }; - _manager = new CollectionUpgradeManager(updaters, _cosmosDataStoreConfiguration, optionsMonitor, factory, NullLogger.Instance); + _manager = new CollectionUpgradeManager( + _collectionDataUpdater, + _cosmosDataStoreConfiguration, + optionsMonitor, + factory, + NullLogger.Instance); _containerResponse = Substitute.ForPartsOf(); @@ -75,38 +94,27 @@ public CollectionUpgradeManagerTests() }; _containerResponse.Resource.Returns(containerProperties); - _client.ReadContainerAsync(Arg.Any(), Arg.Any()) + _container.ReadContainerAsync(Arg.Any(), Arg.Any()) .Returns(_containerResponse); } [Fact] - public async Task GivenACollection_WhenSettingUpCollection_ThenTheCollectionIndexIsUpdated() + public async Task GivenACollection_WhenSettingUpCollection_ThenSearchParameterIsRegistered() { await UpdateCollectionAsync(); - await _client.Received(1).ReplaceContainerAsync(Arg.Any(), null, Arg.Any()); + await _collectionDataUpdater.Received(1).ExecuteAsync(_container, Arg.Any()); } - [Fact] - public async Task GivenACollection_WhenSettingUpCollection_ThenTheCollectionVersionWrapperIsSaved() - { - await UpdateCollectionAsync(); - - await _client.Received(1) - .UpsertItemAsync(Arg.Is(x => x.Version == _manager.CollectionSettingsVersion), Arg.Any(), null, Arg.Any()); - } - - [Fact] - public async Task GivenACollection_WhenSettingUpCollection_ThenTheCollectionTTLIsSetToNeg1() + private async Task UpdateCollectionAsync() { - await UpdateCollectionAsync(); - - Assert.Equal(-1, _containerResponse.Resource.DefaultTimeToLive); + await _manager.SetupContainerAsync(_container, cancellationToken: GetCancellationToken()); } - private async Task UpdateCollectionAsync() + private static CancellationToken GetCancellationToken() { - await _manager.SetupContainerAsync(_client); + using CancellationTokenSource tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + return tokenSource.Token; } } } diff --git a/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Storage/Versioning/DataPlaneCollectionSetupTests.cs b/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Storage/Versioning/DataPlaneCollectionSetupTests.cs new file mode 100644 index 0000000000..bfaceae3b5 --- /dev/null +++ b/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Storage/Versioning/DataPlaneCollectionSetupTests.cs @@ -0,0 +1,112 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Azure.Cosmos; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Microsoft.Health.Fhir.CosmosDb.Core.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.StoredProcedures; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.Versioning; +using Microsoft.Health.Fhir.CosmosDb.Initialization.Features.Storage; +using Microsoft.Health.Fhir.Tests.Common; +using Microsoft.Health.Test.Utilities; +using NSubstitute; +using Xunit; + +namespace Microsoft.Health.Fhir.CosmosDb.UnitTests.Features.Storage.Versioning +{ + [Trait(Traits.OwningTeam, OwningTeam.Fhir)] + [Trait(Traits.Category, Categories.DataSourceValidation)] + public class DataPlaneCollectionSetupTests + { + private readonly Container _container; + private readonly DataPlaneCollectionSetup _setup; + private readonly ContainerResponse _containerResponse; + private readonly ILogger _logger = Substitute.For>(); + + private readonly CosmosDataStoreConfiguration _cosmosDataStoreConfiguration = new CosmosDataStoreConfiguration + { + AllowDatabaseCreation = false, + ConnectionMode = ConnectionMode.Direct, + DatabaseId = "testdatabaseid", + Host = "https://fakehost", + Key = "ZmFrZWtleQ==", // "fakekey" + PreferredLocations = null, + }; + + private readonly CosmosCollectionConfiguration _cosmosCollectionConfiguration = new CosmosCollectionConfiguration + { + CollectionId = "testcollectionid", + }; + + public DataPlaneCollectionSetupTests() + { + _container = Substitute.For(); + _containerResponse = Substitute.ForPartsOf(); + + var containerProperties = new ContainerProperties(); + containerProperties.IndexingPolicy = new IndexingPolicy + { + IncludedPaths = { }, + ExcludedPaths = { }, + }; + + _containerResponse.Resource.Returns(containerProperties); + _container.ReadContainerAsync(Arg.Any(), Arg.Any()) + .Returns(_containerResponse); + + var optionsMonitor = Substitute.For>(); + optionsMonitor.Get(Constants.CollectionConfigurationName).Returns(_cosmosCollectionConfiguration); + + var collectionInitializer = Substitute.For(); + collectionInitializer.CreateFhirContainer(Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(_container); + + var collectionVersionWrappers = Substitute.ForPartsOf>(); + + _container.GetItemQueryIterator(Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(collectionVersionWrappers); + + collectionVersionWrappers.ReadNextAsync(Arg.Any()) + .Returns(Substitute.ForPartsOf>()); + + var storeProcedureInstaller = Substitute.For(); + storeProcedureInstaller.ExecuteAsync(Arg.Any(), Arg.Any()) + .Returns(Task.FromResult(true)); + + _setup = new DataPlaneCollectionSetup(_cosmosDataStoreConfiguration, optionsMonitor, collectionInitializer, storeProcedureInstaller, _logger); + } + + [Fact] + public async Task GivenACollection_WhenSettingUpCollection_ThenTheCollectionIndexIsUpdated() + { + await _setup.UpdateFhirCollectionSettingsAsync(CancellationToken.None); + await _container.Received(1).ReplaceContainerAsync(Arg.Any(), null, Arg.Any()); + } + + [Fact] + public async Task GivenACollection_WhenSettingUpCollection_ThenTheCollectionVersionWrapperIsSaved() + { + await _setup.UpdateFhirCollectionSettingsAsync(CancellationToken.None); + + await _container.Received(1) + .UpsertItemAsync(Arg.Is(x => x.Version == 3), Arg.Any(), null, Arg.Any()); + } + + [Fact] + public async Task GivenACollection_WhenSettingUpCollection_ThenTheCollectionTTLIsSetToNeg1() + { + await _setup.UpdateFhirCollectionSettingsAsync(CancellationToken.None); + Assert.Equal(-1, _containerResponse.Resource.DefaultTimeToLive); + } + } +} diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Health/CosmosHealthCheck.cs b/src/Microsoft.Health.Fhir.CosmosDb/Features/Health/CosmosHealthCheck.cs index 41a3f20b54..7beeaccd71 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Health/CosmosHealthCheck.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Features/Health/CosmosHealthCheck.cs @@ -16,7 +16,8 @@ using Microsoft.Health.Extensions.DependencyInjection; using Microsoft.Health.Fhir.Core.Extensions; using Microsoft.Health.Fhir.Core.Features.Health; -using Microsoft.Health.Fhir.CosmosDb.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage; using Microsoft.Health.Fhir.CosmosDb.Features.Storage; namespace Microsoft.Health.Fhir.CosmosDb.Features.Health diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Search/CosmosDbSortingValidator.cs b/src/Microsoft.Health.Fhir.CosmosDb/Features/Search/CosmosDbSortingValidator.cs index 642be83c8b..036e968157 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Search/CosmosDbSortingValidator.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Features/Search/CosmosDbSortingValidator.cs @@ -46,10 +46,10 @@ public bool ValidateSorting(IReadOnlyList<(SearchParameterInfo searchParameter, return true; } - errorMessages = new[] { string.Format(CultureInfo.InvariantCulture, Core.Resources.SearchSortParameterNotSupported, parameter.searchParameter.Code) }; + errorMessages = new[] { string.Format(CultureInfo.InvariantCulture, Microsoft.Health.Fhir.Core.Resources.SearchSortParameterNotSupported, parameter.searchParameter.Code) }; return false; default: - errorMessages = new[] { Core.Resources.MultiSortParameterNotSupported }; + errorMessages = new[] { Microsoft.Health.Fhir.Core.Resources.MultiSortParameterNotSupported }; return false; } } diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Search/FhirCosmosSearchService.cs b/src/Microsoft.Health.Fhir.CosmosDb/Features/Search/FhirCosmosSearchService.cs index 4e9f1f940b..3717558f6a 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Search/FhirCosmosSearchService.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Features/Search/FhirCosmosSearchService.cs @@ -23,7 +23,7 @@ using Microsoft.Health.Fhir.Core.Features.Search; using Microsoft.Health.Fhir.Core.Features.Search.Expressions; using Microsoft.Health.Fhir.Core.Models; -using Microsoft.Health.Fhir.CosmosDb.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Configs; using Microsoft.Health.Fhir.CosmosDb.Features.Search.Queries; using Microsoft.Health.Fhir.CosmosDb.Features.Storage; using Microsoft.Health.Fhir.ValueSets; @@ -302,7 +302,7 @@ private async Task RecurseChainedExpression(ChainedExpression expres catch (OperationCanceledException) { _logger.LogWarning("Request Too Costly (ConditionalRequestTooCostly)"); - throw new RequestTooCostlyException(Core.Resources.ConditionalRequestTooCostly); + throw new RequestTooCostlyException(Microsoft.Health.Fhir.Core.Resources.ConditionalRequestTooCostly); } if (!chainedResults.Any()) @@ -555,10 +555,10 @@ private async Task ExecuteCountSearchAsync( new OperationOutcomeIssue( OperationOutcomeConstants.IssueSeverity.Error, OperationOutcomeConstants.IssueType.NotSupported, - string.Format(Core.Resources.SearchCountResultsExceedLimit, count, int.MaxValue))); + string.Format(Microsoft.Health.Fhir.Core.Resources.SearchCountResultsExceedLimit, count, int.MaxValue))); _logger.LogWarning("Invalid Search Operation (SearchCountResultsExceedLimit)"); - throw new InvalidSearchOperationException(string.Format(Core.Resources.SearchCountResultsExceedLimit, count, int.MaxValue)); + throw new InvalidSearchOperationException(string.Format(Microsoft.Health.Fhir.Core.Resources.SearchCountResultsExceedLimit, count, int.MaxValue)); } return (int)count; @@ -572,7 +572,7 @@ private SearchResult CreateSearchResult(SearchOptions searchOptions, IEnumerable new OperationOutcomeIssue( OperationOutcomeConstants.IssueSeverity.Warning, OperationOutcomeConstants.IssueType.Incomplete, - Core.Resources.TruncatedIncludeMessage)); + Microsoft.Health.Fhir.Core.Resources.TruncatedIncludeMessage)); } return new SearchResult( diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Search/Queries/ExpressionQueryBuilder.cs b/src/Microsoft.Health.Fhir.CosmosDb/Features/Search/Queries/ExpressionQueryBuilder.cs index beb43255fd..aa5935309d 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Search/Queries/ExpressionQueryBuilder.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Features/Search/Queries/ExpressionQueryBuilder.cs @@ -200,7 +200,7 @@ public object VisitChained(ChainedExpression expression, Context context) public object VisitSortParameter(SortExpression expression, Context context) { - throw new SearchOperationNotSupportedException(Core.Resources.SortNotSupported); + throw new SearchOperationNotSupportedException(Microsoft.Health.Fhir.Core.Resources.SortNotSupported); } public object VisitMissingField(MissingFieldExpression expression, Context context) diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Search/Queries/QueryBuilder.cs b/src/Microsoft.Health.Fhir.CosmosDb/Features/Search/Queries/QueryBuilder.cs index 08be51683a..07d7b97359 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Search/Queries/QueryBuilder.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Features/Search/Queries/QueryBuilder.cs @@ -14,6 +14,7 @@ using Microsoft.Health.Fhir.Core.Features.Search; using Microsoft.Health.Fhir.Core.Features.Search.Expressions; using Microsoft.Health.Fhir.Core.Models; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage; using Microsoft.Health.Fhir.CosmosDb.Features.Queries; using Microsoft.Health.Fhir.CosmosDb.Features.Storage; diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosClientReadWriteTestProvider.cs b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosClientReadWriteTestProvider.cs index 057501a5b9..6008d6e206 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosClientReadWriteTestProvider.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosClientReadWriteTestProvider.cs @@ -6,7 +6,8 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos; -using Microsoft.Health.Fhir.CosmosDb.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage; using Newtonsoft.Json; namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosContainerProvider.cs b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosContainerProvider.cs index 5241a149a6..def19b349e 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosContainerProvider.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosContainerProvider.cs @@ -10,6 +10,7 @@ using EnsureThat; using MediatR; using Microsoft.Azure.Cosmos; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -17,7 +18,9 @@ using Microsoft.Health.Core; using Microsoft.Health.Extensions.DependencyInjection; using Microsoft.Health.Fhir.Core.Messages.Storage; -using Microsoft.Health.Fhir.CosmosDb.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage; +using Microsoft.Health.Fhir.CosmosDb.Initialization.Features.Storage; namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage { @@ -37,6 +40,9 @@ public CosmosContainerProvider( CosmosDataStoreConfiguration cosmosDataStoreConfiguration, IOptionsMonitor collectionConfiguration, ICosmosClientInitializer cosmosClientInitializer, + ICollectionSetup collectionSetup, + ICollectionDataUpdater collectionDataUpdater, + RetryExceptionPolicyFactory retryPolicyFactory, ILogger logger, IMediator mediator, IEnumerable collectionInitializers) @@ -46,19 +52,21 @@ public CosmosContainerProvider( EnsureArg.IsNotNull(cosmosClientInitializer, nameof(cosmosClientInitializer)); EnsureArg.IsNotNull(logger, nameof(logger)); EnsureArg.IsNotNull(collectionInitializers, nameof(collectionInitializers)); + EnsureArg.IsNotNull(collectionSetup, nameof(collectionSetup)); + EnsureArg.IsNotNull(collectionDataUpdater, nameof(collectionDataUpdater)); _logger = logger; _mediator = mediator; string collectionId = collectionConfiguration.Get(Constants.CollectionConfigurationName).CollectionId; _client = cosmosClientInitializer.CreateCosmosClient(cosmosDataStoreConfiguration); - - _initializationOperation = new RetryableInitializationOperation( - () => cosmosClientInitializer.InitializeDataStoreAsync(_client, cosmosDataStoreConfiguration, collectionInitializers)); - _container = new Lazy(() => cosmosClientInitializer.CreateFhirContainer( _client, cosmosDataStoreConfiguration.DatabaseId, collectionId)); + _initializationOperation = new RetryableInitializationOperation(async () => + { + await InitializeDataStoreAsync(collectionSetup, collectionDataUpdater, cosmosDataStoreConfiguration, retryPolicyFactory, collectionInitializers); + }); } public Container Container @@ -76,6 +84,32 @@ public Container Container } } + private async Task InitializeDataStoreAsync( + ICollectionSetup collectionSetup, + ICollectionDataUpdater collectionDataUpdater, + CosmosDataStoreConfiguration cosmosDataStoreConfiguration, + RetryExceptionPolicyFactory retryPolicyFactory, + IEnumerable collectionInitializers) + { + try + { + _logger.LogInformation("Initializing Cosmos DB Database {DatabaseId} and collections", cosmosDataStoreConfiguration.DatabaseId); + using (var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromMinutes(5))) + { + await collectionSetup.CreateDatabaseAsync(retryPolicyFactory.RetryPolicy, cancellationTokenSource.Token); // We need valid cancellation token + await collectionSetup.CreateCollectionAsync(collectionInitializers, retryPolicyFactory.RetryPolicy, cancellationTokenSource.Token); + await collectionSetup.UpdateFhirCollectionSettingsAsync(cancellationTokenSource.Token); + await collectionDataUpdater.ExecuteAsync(_container.Value, cancellationTokenSource.Token); + } + } + catch (Exception ex) + { + LogLevel logLevel = LogLevel.Critical; + _logger.Log(logLevel, ex, "Cosmos DB Database {DatabaseId} Initialization has failed.", cosmosDataStoreConfiguration.DatabaseId); + throw; + } + } + /// /// Starts the initialization of the document client and cosmos data store. /// diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosDbCollectionPhysicalPartitionInfo.cs b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosDbCollectionPhysicalPartitionInfo.cs index 2c7d315501..9f2876d547 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosDbCollectionPhysicalPartitionInfo.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosDbCollectionPhysicalPartitionInfo.cs @@ -18,7 +18,7 @@ using Microsoft.Health.Abstractions.Exceptions; using Microsoft.Health.Extensions.DependencyInjection; using Microsoft.Health.Fhir.Core.Extensions; -using Microsoft.Health.Fhir.CosmosDb.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Configs; namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage { diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosDbDistributedLock.cs b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosDbDistributedLock.cs index 99433011ab..ca1327b7f4 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosDbDistributedLock.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosDbDistributedLock.cs @@ -13,6 +13,7 @@ using Microsoft.Health.Abstractions.Exceptions; using Microsoft.Health.Extensions.DependencyInjection; using Microsoft.Health.Fhir.Core.Extensions; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage; using Newtonsoft.Json; namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosDbDistributedLockFactory.cs b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosDbDistributedLockFactory.cs index 88de9ffefb..1a81230679 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosDbDistributedLockFactory.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosDbDistributedLockFactory.cs @@ -8,6 +8,7 @@ using Microsoft.Azure.Cosmos; using Microsoft.Extensions.Logging; using Microsoft.Health.Extensions.DependencyInjection; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage; namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage { diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosFhirDataStore.cs b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosFhirDataStore.cs index 5fd1096010..862dd9bf75 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosFhirDataStore.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosFhirDataStore.cs @@ -31,7 +31,7 @@ using Microsoft.Health.Fhir.Core.Features.Persistence; using Microsoft.Health.Fhir.Core.Features.Persistence.Orchestration; using Microsoft.Health.Fhir.Core.Models; -using Microsoft.Health.Fhir.CosmosDb.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Configs; using Microsoft.Health.Fhir.CosmosDb.Features.Queries; using Microsoft.Health.Fhir.CosmosDb.Features.Search; using Microsoft.Health.Fhir.CosmosDb.Features.Storage.StoredProcedures.HardDelete; @@ -268,7 +268,7 @@ private async Task InternalUpsertAsync( if (cosmosWrapper.SearchIndices == null || cosmosWrapper.SearchIndices.Count == 0) { - throw new MissingSearchIndicesException(string.Format(Core.Resources.MissingSearchIndices, resource.ResourceTypeName)); + throw new MissingSearchIndicesException(string.Format(Microsoft.Health.Fhir.Core.Resources.MissingSearchIndices, resource.ResourceTypeName)); } var partitionKey = new PartitionKey(cosmosWrapper.PartitionKey); @@ -308,11 +308,11 @@ await retryPolicy.ExecuteAsync( if (_modelInfoProvider.Version == FhirSpecification.Stu3) { _logger.LogInformation("PreconditionFailed: IfMatchHeaderRequiredForResource"); - throw new PreconditionFailedException(string.Format(Core.Resources.IfMatchHeaderRequiredForResource, resource.ResourceTypeName)); + throw new PreconditionFailedException(string.Format(Microsoft.Health.Fhir.Core.Resources.IfMatchHeaderRequiredForResource, resource.ResourceTypeName)); } _logger.LogInformation("BadRequest: IfMatchHeaderRequiredForResource"); - throw new BadRequestException(string.Format(Core.Resources.IfMatchHeaderRequiredForResource, resource.ResourceTypeName)); + throw new BadRequestException(string.Format(Microsoft.Health.Fhir.Core.Resources.IfMatchHeaderRequiredForResource, resource.ResourceTypeName)); } while (true) @@ -337,13 +337,13 @@ await retryPolicy.ExecuteAsync( if (weakETag != null) { _logger.LogInformation("ResourceNotFound: ResourceNotFoundByIdAndVersion"); - throw new ResourceNotFoundException(string.Format(Core.Resources.ResourceNotFoundByIdAndVersion, resource.ResourceTypeName, resource.ResourceId, weakETag.VersionId)); + throw new ResourceNotFoundException(string.Format(Microsoft.Health.Fhir.Core.Resources.ResourceNotFoundByIdAndVersion, resource.ResourceTypeName, resource.ResourceId, weakETag.VersionId)); } if (!allowCreate) { _logger.LogInformation("MethodNotAllowed: ResourceCreationNotAllowed"); - throw new MethodNotAllowedException(Core.Resources.ResourceCreationNotAllowed); + throw new MethodNotAllowedException(Microsoft.Health.Fhir.Core.Resources.ResourceCreationNotAllowed); } throw; @@ -359,7 +359,7 @@ await retryPolicy.ExecuteAsync( } _logger.LogInformation("PreconditionFailed: ResourceVersionConflict"); - throw new PreconditionFailedException(string.Format(Core.Resources.ResourceVersionConflict, weakETag.VersionId)); + throw new PreconditionFailedException(string.Format(Microsoft.Health.Fhir.Core.Resources.ResourceVersionConflict, weakETag.VersionId)); } if (existingItemResource.IsDeleted && cosmosWrapper.IsDeleted) @@ -562,8 +562,8 @@ public async Task UpdateSearchParameterIndicesAsync(ResourceWra switch (exception.GetSubStatusCode()) { case HttpStatusCode.PreconditionFailed: - _logger.LogError(string.Format(Core.Resources.ResourceVersionConflict, WeakETag.FromVersionId(resourceWrapper.Version))); - throw new PreconditionFailedException(string.Format(Core.Resources.ResourceVersionConflict, WeakETag.FromVersionId(resourceWrapper.Version))); + _logger.LogError(string.Format(Microsoft.Health.Fhir.Core.Resources.ResourceVersionConflict, WeakETag.FromVersionId(resourceWrapper.Version))); + throw new PreconditionFailedException(string.Format(Microsoft.Health.Fhir.Core.Resources.ResourceVersionConflict, WeakETag.FromVersionId(resourceWrapper.Version))); case HttpStatusCode.ServiceUnavailable: _logger.LogError("Failed to reindex resource because the Cosmos service was unavailable."); diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosResponseProcessor.cs b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosResponseProcessor.cs index 89561c4233..88f09e43e4 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosResponseProcessor.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosResponseProcessor.cs @@ -70,7 +70,7 @@ public async Task ProcessErrorResponseAsync(HttpStatusCode statusCode, Headers h } else if (errorMessage.Contains("Invalid Continuation Token", StringComparison.OrdinalIgnoreCase) || errorMessage.Contains("Malformed Continuation Token", StringComparison.OrdinalIgnoreCase)) { - exception = new Core.Exceptions.RequestNotValidException(Core.Resources.InvalidContinuationToken); + exception = new Microsoft.Health.Fhir.Core.Exceptions.RequestNotValidException(Microsoft.Health.Fhir.Core.Resources.InvalidContinuationToken); } else if (statusCode == HttpStatusCode.RequestEntityTooLarge || (statusCode == HttpStatusCode.BadRequest && errorMessage.Contains("Request size is too large", StringComparison.OrdinalIgnoreCase))) @@ -78,14 +78,14 @@ public async Task ProcessErrorResponseAsync(HttpStatusCode statusCode, Headers h // There are multiple known failures relating to RequestEntityTooLarge. // 1. When the document size is ~2mb (just under or at the limit) it can make it into the stored proc and fail on create // 2. Larger documents are rejected by CosmosDb with HttpStatusCode.RequestEntityTooLarge - exception = new Core.Exceptions.RequestEntityTooLargeException(); + exception = new Microsoft.Health.Fhir.Core.Exceptions.RequestEntityTooLargeException(); } else if (statusCode == HttpStatusCode.Forbidden) { int? subStatusValue = headers.GetSubStatusValue(); if (subStatusValue.HasValue && Enum.IsDefined(typeof(KnownCosmosDbCmkSubStatusValue), subStatusValue)) { - exception = new Core.Exceptions.CustomerManagedKeyException(GetCustomerManagedKeyErrorMessage(subStatusValue.Value)); + exception = new Microsoft.Health.Fhir.Core.Exceptions.CustomerManagedKeyException(GetCustomerManagedKeyErrorMessage(subStatusValue.Value)); } } diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/FhirCosmosClientInitializer.cs b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/FhirCosmosClientInitializer.cs index 778eff6bcb..42785f1d05 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/FhirCosmosClientInitializer.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/FhirCosmosClientInitializer.cs @@ -4,6 +4,7 @@ // ------------------------------------------------------------------------------------------------- using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; @@ -14,7 +15,9 @@ using Microsoft.Azure.Cosmos.Fluent; using Microsoft.Extensions.Logging; using Microsoft.Health.Abstractions.Exceptions; -using Microsoft.Health.Fhir.CosmosDb.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage; +using Microsoft.Health.Fhir.CosmosDb.Initialization.Features.Storage; using Microsoft.IO; using Newtonsoft.Json; using Newtonsoft.Json.Converters; @@ -27,6 +30,8 @@ public class FhirCosmosClientInitializer : ICosmosClientInitializer private readonly ILogger _logger; private readonly Func> _requestHandlerFactory; private readonly RetryExceptionPolicyFactory _retryExceptionPolicyFactory; + private CosmosClient _cosmosClient; + private readonly object _lockObject; public FhirCosmosClientInitializer( ICosmosClientTestProvider testProvider, @@ -43,6 +48,7 @@ public FhirCosmosClientInitializer( _requestHandlerFactory = requestHandlerFactory; _retryExceptionPolicyFactory = retryExceptionPolicyFactory; _logger = logger; + _lockObject = new object(); } /// @@ -50,38 +56,23 @@ public CosmosClient CreateCosmosClient(CosmosDataStoreConfiguration configuratio { EnsureArg.IsNotNull(configuration, nameof(configuration)); - var host = configuration.Host; - var key = configuration.Key; - - if (string.IsNullOrWhiteSpace(host) && string.IsNullOrWhiteSpace(key)) + // Thread-safe logic to ensure that a single instance of CosmosClient is created. + if (_cosmosClient != null) { - _logger.LogWarning("No connection string provided, attempting to connect to local emulator."); - - host = CosmosDbLocalEmulator.Host; - key = CosmosDbLocalEmulator.Key; + return _cosmosClient; } - - _logger.LogInformation("Creating CosmosClient instance for {DatabaseId}, Host: {Host}", configuration.DatabaseId, host); - - IEnumerable requestHandlers = _requestHandlerFactory.Invoke(); - - var builder = new CosmosClientBuilder(host, key) - .WithConnectionModeDirect(enableTcpConnectionEndpointRediscovery: true) - .WithCustomSerializer(new FhirCosmosSerializer(_logger)) - .WithThrottlingRetryOptions(TimeSpan.FromSeconds(configuration.RetryOptions.MaxWaitTimeInSeconds), configuration.RetryOptions.MaxNumberOfRetries) - .AddCustomHandlers(requestHandlers.ToArray()); - - if (configuration.PreferredLocations?.Any() == true) + else { - builder.WithApplicationPreferredRegions(configuration.PreferredLocations?.ToArray()); - } + lock (_lockObject) + { + if (_cosmosClient == null) + { + _cosmosClient = CreateCosmosClientInternal(configuration); + } - if (configuration.DefaultConsistencyLevel != null) - { - builder.WithConsistencyLevel(configuration.DefaultConsistencyLevel.Value); + return _cosmosClient; + } } - - return builder.Build(); } public Container CreateFhirContainer(CosmosClient client, string databaseId, string collectionId) @@ -111,42 +102,40 @@ await _retryExceptionPolicyFactory.RetryPolicy.ExecuteAsync(async () => } } - /// - public async Task InitializeDataStoreAsync(CosmosClient client, CosmosDataStoreConfiguration cosmosDataStoreConfiguration, IEnumerable collectionInitializers, CancellationToken cancellationToken = default) + private CosmosClient CreateCosmosClientInternal(CosmosDataStoreConfiguration configuration) { - EnsureArg.IsNotNull(client, nameof(client)); - EnsureArg.IsNotNull(cosmosDataStoreConfiguration, nameof(cosmosDataStoreConfiguration)); - EnsureArg.IsNotNull(collectionInitializers, nameof(collectionInitializers)); + var host = configuration.Host; + var key = configuration.Key; - try + if (string.IsNullOrWhiteSpace(host) && string.IsNullOrWhiteSpace(key)) { - _logger.LogInformation("Initializing Cosmos DB Database {DatabaseId} and collections", cosmosDataStoreConfiguration.DatabaseId); + _logger.LogWarning("No connection string provided, attempting to connect to local emulator."); - if (cosmosDataStoreConfiguration.AllowDatabaseCreation) - { - _logger.LogInformation("CreateDatabaseIfNotExists {DatabaseId}", cosmosDataStoreConfiguration.DatabaseId); - - await _retryExceptionPolicyFactory.RetryPolicy.ExecuteAsync( - async () => - await client.CreateDatabaseIfNotExistsAsync( - cosmosDataStoreConfiguration.DatabaseId, - cosmosDataStoreConfiguration.InitialDatabaseThroughput.HasValue ? ThroughputProperties.CreateManualThroughput(cosmosDataStoreConfiguration.InitialDatabaseThroughput.Value) : null, - cancellationToken: cancellationToken)); - } + host = CosmosDbLocalEmulator.Host; + key = CosmosDbLocalEmulator.Key; + } - foreach (var collectionInitializer in collectionInitializers) - { - await collectionInitializer.InitializeCollectionAsync(client, cancellationToken); - } + _logger.LogInformation("Creating CosmosClient instance for {DatabaseId}, Host: {Host}", configuration.DatabaseId, host); + + IEnumerable requestHandlers = _requestHandlerFactory.Invoke(); - _logger.LogInformation("Cosmos DB Database {DatabaseId} and collections successfully initialized", cosmosDataStoreConfiguration.DatabaseId); + var builder = new CosmosClientBuilder(host, key) + .WithConnectionModeDirect(enableTcpConnectionEndpointRediscovery: true) + .WithCustomSerializer(new FhirCosmosSerializer(_logger)) + .WithThrottlingRetryOptions(TimeSpan.FromSeconds(configuration.RetryOptions.MaxWaitTimeInSeconds), configuration.RetryOptions.MaxNumberOfRetries) + .AddCustomHandlers(requestHandlers.ToArray()); + + if (configuration.PreferredLocations?.Any() == true) + { + builder.WithApplicationPreferredRegions(configuration.PreferredLocations?.ToArray()); } - catch (Exception ex) + + if (configuration.DefaultConsistencyLevel != null) { - LogLevel logLevel = ex is RequestRateExceededException ? LogLevel.Warning : LogLevel.Critical; - _logger.Log(logLevel, ex, "Cosmos DB Database {DatabaseId} and collections initialization failed", cosmosDataStoreConfiguration.DatabaseId); - throw; + builder.WithConsistencyLevel(configuration.DefaultConsistencyLevel.Value); } + + return builder.Build(); } private class FhirCosmosSerializer : CosmosSerializer diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/FhirCosmosResourceWrapper.cs b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/FhirCosmosResourceWrapper.cs index 8b40d8300a..008c0628ce 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/FhirCosmosResourceWrapper.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/FhirCosmosResourceWrapper.cs @@ -9,6 +9,7 @@ using Microsoft.Health.Fhir.Core.Features.Persistence; using Microsoft.Health.Fhir.Core.Features.Search; using Microsoft.Health.Fhir.Core.Features.Search.SearchValues; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage; using Microsoft.Health.Fhir.CosmosDb.Features.Storage.Search; using Newtonsoft.Json; diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/FhirCosmosResponseHandler.cs b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/FhirCosmosResponseHandler.cs index 6dad67bbc4..8af92bc601 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/FhirCosmosResponseHandler.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/FhirCosmosResponseHandler.cs @@ -13,7 +13,7 @@ using Microsoft.Health.Extensions.DependencyInjection; using Microsoft.Health.Fhir.Core.Features.Context; using Microsoft.Health.Fhir.Core.Features.Persistence; -using Microsoft.Health.Fhir.CosmosDb.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Configs; using Microsoft.Health.Fhir.CosmosDb.Features.Queries; namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Operations/CosmosFhirOperationDataStore.cs b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Operations/CosmosFhirOperationDataStore.cs index 59de7f62b5..d047eb66e0 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Operations/CosmosFhirOperationDataStore.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Operations/CosmosFhirOperationDataStore.cs @@ -22,7 +22,8 @@ using Microsoft.Health.Fhir.Core.Features.Operations.Export.Models; using Microsoft.Health.Fhir.Core.Features.Operations.Reindex.Models; using Microsoft.Health.Fhir.Core.Features.Persistence; -using Microsoft.Health.Fhir.CosmosDb.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage; using Microsoft.Health.Fhir.CosmosDb.Features.Storage.Operations.Export; using Microsoft.Health.Fhir.CosmosDb.Features.Storage.Operations.Reindex; using Microsoft.Health.Fhir.CosmosDb.Features.Storage.StoredProcedures.AcquireExportJobs; @@ -228,7 +229,7 @@ async Task ILegacyExportOperationDataStore.GetLegacyExportJobB if (dce.StatusCode == HttpStatusCode.NotFound) { - throw new JobNotFoundException(string.Format(Core.Resources.JobNotFound, id)); + throw new JobNotFoundException(string.Format(Microsoft.Health.Fhir.Core.Resources.JobNotFound, id)); } _logger.LogError(dce, "Failed to get an export job by id."); @@ -274,7 +275,7 @@ async Task ILegacyExportOperationDataStore.UpdateLegacyExportJ } else if (dce.StatusCode == HttpStatusCode.NotFound) { - throw new JobNotFoundException(string.Format(Core.Resources.JobNotFound, jobRecord.Id)); + throw new JobNotFoundException(string.Format(Microsoft.Health.Fhir.Core.Resources.JobNotFound, jobRecord.Id)); } _logger.LogError(dce, "Failed to update an export job."); @@ -391,7 +392,7 @@ public override async Task GetReindexJobByIdAsync(string jobI } else if (dce.StatusCode == HttpStatusCode.NotFound) { - throw new JobNotFoundException(string.Format(Core.Resources.JobNotFound, jobId)); + throw new JobNotFoundException(string.Format(Microsoft.Health.Fhir.Core.Resources.JobNotFound, jobId)); } _logger.LogError(dce, "Failed to get reindex job by id: {JobId}.", jobId); @@ -437,7 +438,7 @@ public override async Task UpdateReindexJobAsync(ReindexJobRe } else if (dce.StatusCode == HttpStatusCode.NotFound) { - throw new JobNotFoundException(string.Format(Core.Resources.JobNotFound, jobRecord.Id)); + throw new JobNotFoundException(string.Format(Microsoft.Health.Fhir.Core.Resources.JobNotFound, jobRecord.Id)); } _logger.LogError(dce, "Failed to update a reindex job."); diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Operations/CosmosJobRecordWrapper.cs b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Operations/CosmosJobRecordWrapper.cs index 074642e35c..6dfb99560f 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Operations/CosmosJobRecordWrapper.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Operations/CosmosJobRecordWrapper.cs @@ -3,6 +3,7 @@ // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. // ------------------------------------------------------------------------------------------------- +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage; using Newtonsoft.Json; namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage.Operations diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Operations/Export/CosmosExportJobRecordWrapper.cs b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Operations/Export/CosmosExportJobRecordWrapper.cs index 7af53492b5..bb6faecc75 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Operations/Export/CosmosExportJobRecordWrapper.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Operations/Export/CosmosExportJobRecordWrapper.cs @@ -6,6 +6,7 @@ using EnsureThat; using Microsoft.Health.Fhir.Core.Features.Operations; using Microsoft.Health.Fhir.Core.Features.Operations.Export.Models; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage; using Newtonsoft.Json; namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage.Operations.Export diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Operations/Reindex/CosmosReindexJobRecordWrapper.cs b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Operations/Reindex/CosmosReindexJobRecordWrapper.cs index 15ec768fe8..9974d468ab 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Operations/Reindex/CosmosReindexJobRecordWrapper.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Operations/Reindex/CosmosReindexJobRecordWrapper.cs @@ -6,6 +6,7 @@ using EnsureThat; using Microsoft.Health.Fhir.Core.Features.Operations; using Microsoft.Health.Fhir.Core.Features.Operations.Reindex.Models; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage; using Newtonsoft.Json; namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage.Operations.Reindex diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Queues/CosmosQueueClient.cs b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Queues/CosmosQueueClient.cs index feefaf921f..abd42d5bf9 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Queues/CosmosQueueClient.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Queues/CosmosQueueClient.cs @@ -17,6 +17,7 @@ using Microsoft.Health.Core.Extensions; using Microsoft.Health.Extensions.DependencyInjection; using Microsoft.Health.Fhir.Core.Extensions; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage; using Microsoft.Health.Fhir.CosmosDb.Features.Queries; using Microsoft.Health.JobManagement; using Polly; diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Queues/JobGroupWrapper.cs b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Queues/JobGroupWrapper.cs index d4527b8565..c68b63172a 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Queues/JobGroupWrapper.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Queues/JobGroupWrapper.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage; using Newtonsoft.Json; namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage.Queues; diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Registry/CosmosDbSearchParameterStatusDataStore.cs b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Registry/CosmosDbSearchParameterStatusDataStore.cs index 0f6c2eeb5a..302ccf07b3 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Registry/CosmosDbSearchParameterStatusDataStore.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Registry/CosmosDbSearchParameterStatusDataStore.cs @@ -14,7 +14,7 @@ using Microsoft.Health.Extensions.DependencyInjection; using Microsoft.Health.Fhir.Core.Extensions; using Microsoft.Health.Fhir.Core.Features.Search.Registry; -using Microsoft.Health.Fhir.CosmosDb.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Configs; namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage.Registry { diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Registry/CosmosDbSearchParameterStatusInitializer.cs b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Registry/CosmosDbSearchParameterStatusInitializer.cs index c49ddcc3e7..54a36a0d0b 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Registry/CosmosDbSearchParameterStatusInitializer.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Registry/CosmosDbSearchParameterStatusInitializer.cs @@ -11,16 +11,21 @@ using Microsoft.Health.Fhir.Core.Extensions; using Microsoft.Health.Fhir.Core.Features.Search.Registry; using Microsoft.Health.Fhir.Core.Models; -using Microsoft.Health.Fhir.CosmosDb.Configs; -using Microsoft.Health.Fhir.CosmosDb.Features.Storage.Versioning; +using Microsoft.Health.Fhir.CosmosDb.Core.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.Versioning; +using Microsoft.Health.Fhir.CosmosDb.Features.Storage; +using Microsoft.Health.Fhir.CosmosDb.Initialization.Features.Storage.StoredProcedures.UpdateUnsupportedSearchParametersToUnsupported; namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage.Registry { - public class CosmosDbSearchParameterStatusInitializer : ICollectionUpdater + public class CosmosDbSearchParameterStatusInitializer : ICollectionDataUpdater { private readonly ISearchParameterStatusDataStore _filebasedSearchParameterStatusDataStore; private readonly ICosmosQueryFactory _queryFactory; private readonly CosmosDataStoreConfiguration _configuration; + private readonly UpdateUnsupportedSearchParameters _updateSP = new UpdateUnsupportedSearchParameters(); + private const int CollectionSettingsVersion = 3; public CosmosDbSearchParameterStatusInitializer( FilebasedSearchParameterStatusDataStore.Resolver filebasedSearchParameterStatusDataStoreResolver, @@ -39,6 +44,8 @@ public async Task ExecuteAsync(Container container, CancellationToken cancellati { EnsureArg.IsNotNull(container, nameof(container)); + var thisVersion = await GetLatestCollectionVersion(container, cancellationToken); + // Detect if registry has been initialized var partitionKey = new PartitionKey(SearchParameterStatusWrapper.SearchParameterStatusPartitionKey); var query = _queryFactory.Create( @@ -70,6 +77,26 @@ public async Task ExecuteAsync(Container container, CancellationToken cancellati await transaction.ExecuteAsync(cancellationToken); } } + else + { + await _updateSP.Execute(container.Scripts, cancellationToken); + thisVersion.Version = CollectionSettingsVersion; + await container.UpsertItemAsync(thisVersion, new PartitionKey(thisVersion.PartitionKey), cancellationToken: cancellationToken); + } + } + + private static async Task GetLatestCollectionVersion(Container container, CancellationToken cancellationToken) + { + FeedIterator query = container.GetItemQueryIterator( + new QueryDefinition("SELECT * FROM root r"), + requestOptions: new QueryRequestOptions + { + PartitionKey = new PartitionKey(CollectionVersion.CollectionVersionPartition), + }); + + FeedResponse result = await query.ReadNextAsync(cancellationToken); + + return result.FirstOrDefault() ?? new CollectionVersion(); } } } diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Registry/SearchParameterStatusWrapper.cs b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Registry/SearchParameterStatusWrapper.cs index 25af50dc70..615601d25f 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Registry/SearchParameterStatusWrapper.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Registry/SearchParameterStatusWrapper.cs @@ -7,6 +7,7 @@ using Microsoft.Health.Core.Extensions; using Microsoft.Health.Fhir.Core.Features.Search.Registry; using Microsoft.Health.Fhir.Core.Models; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage; using Newtonsoft.Json; using Newtonsoft.Json.Converters; diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/RetryExceptionPolicyFactory.cs b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/RetryExceptionPolicyFactory.cs index ebdbfd9044..2850384550 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/RetryExceptionPolicyFactory.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/RetryExceptionPolicyFactory.cs @@ -12,7 +12,7 @@ using Microsoft.Health.Core.Features.Context; using Microsoft.Health.Fhir.Core.Extensions; using Microsoft.Health.Fhir.Core.Features.Context; -using Microsoft.Health.Fhir.CosmosDb.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Configs; using Polly; using Polly.Retry; diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/AcquireExportJobs/AcquireExportJobs.cs b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/AcquireExportJobs/AcquireExportJobs.cs index d1fea46599..8e590bf48b 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/AcquireExportJobs/AcquireExportJobs.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/AcquireExportJobs/AcquireExportJobs.cs @@ -8,12 +8,20 @@ using System.Threading.Tasks; using EnsureThat; using Microsoft.Azure.Cosmos.Scripts; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.StoredProcedures; using Microsoft.Health.Fhir.CosmosDb.Features.Storage.Operations.Export; +using Microsoft.Health.Fhir.CosmosDb.Initialization.Features.Storage.StoredProcedures; +using Microsoft.Health.Fhir.CosmosDb.Initialization.Features.Storage.StoredProcedures.AcquireExportJobs; namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage.StoredProcedures.AcquireExportJobs { internal class AcquireExportJobs : StoredProcedureBase { + public AcquireExportJobs() + : base(new AcquireExportJobsMetadata()) + { + } + public async Task>> ExecuteAsync( Scripts client, ushort numberOfJobsToAcquire, @@ -22,7 +30,7 @@ public async Task>( + return await ExecuteStoredProcAsync>( client, CosmosDbExportConstants.ExportJobPartitionKey, cancellationToken, diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/AcquireReindexJobs/AcquireReindexJobs.cs b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/AcquireReindexJobs/AcquireReindexJobs.cs index 879250aae0..c6745ffba3 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/AcquireReindexJobs/AcquireReindexJobs.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/AcquireReindexJobs/AcquireReindexJobs.cs @@ -8,12 +8,19 @@ using System.Threading.Tasks; using EnsureThat; using Microsoft.Azure.Cosmos.Scripts; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.StoredProcedures; using Microsoft.Health.Fhir.CosmosDb.Features.Storage.Operations.Reindex; +using Microsoft.Health.Fhir.CosmosDb.Initialization.Features.Storage.StoredProcedures.AcquireReindexJobs; namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage.StoredProcedures.AcquireReindexJobs { internal class AcquireReindexJobs : StoredProcedureBase { + public AcquireReindexJobs() + : base(new AcquireReindexJobsMetadata()) + { + } + public async Task>> ExecuteAsync( Scripts client, ushort maximumNumberOfConcurrentJobsAllowed, @@ -22,7 +29,7 @@ public async Task>( + return await ExecuteStoredProcAsync>( client, CosmosDbReindexConstants.ReindexJobPartitionKey, cancellationToken, diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/HardDelete/HardDelete.cs b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/HardDelete/HardDelete.cs index 4ffeaf888a..e8c7a1a5a7 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/HardDelete/HardDelete.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/HardDelete/HardDelete.cs @@ -9,17 +9,24 @@ using EnsureThat; using Microsoft.Azure.Cosmos.Scripts; using Microsoft.Health.Fhir.Core.Features.Persistence; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.StoredProcedures; +using Microsoft.Health.Fhir.CosmosDb.Initialization.Features.Storage.StoredProcedures.HardDelete; namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage.StoredProcedures.HardDelete { internal class HardDelete : StoredProcedureBase { + public HardDelete() + : base(new HardDeleteMetadata()) + { + } + public async Task> Execute(Scripts client, ResourceKey key, bool keepCurrentVersion, bool allowPartialSuccess, CancellationToken cancellationToken) { EnsureArg.IsNotNull(client, nameof(client)); EnsureArg.IsNotNull(key, nameof(key)); - return await ExecuteStoredProc(client, key.ToPartitionKey(), cancellationToken, key.ResourceType, key.Id, keepCurrentVersion, allowPartialSuccess); + return await ExecuteStoredProcAsync(client, key.ToPartitionKey(), cancellationToken, key.ResourceType, key.Id, keepCurrentVersion, allowPartialSuccess); } } } diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/Replace/ReplaceSingleResource.cs b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/Replace/ReplaceSingleResource.cs index 92ebac081b..db80fd45f9 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/Replace/ReplaceSingleResource.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/StoredProcedures/Replace/ReplaceSingleResource.cs @@ -7,18 +7,25 @@ using System.Threading.Tasks; using EnsureThat; using Microsoft.Azure.Cosmos.Scripts; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.StoredProcedures; +using Microsoft.Health.Fhir.CosmosDb.Initialization.Features.Storage.StoredProcedures.Replace; namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage.StoredProcedures.Replace { internal class ReplaceSingleResource : StoredProcedureBase { + public ReplaceSingleResource() + : base(new ReplaceSingleResourceMetadata()) + { + } + public async Task Execute(Scripts client, FhirCosmosResourceWrapper resource, string matchVersionId, CancellationToken cancellationToken) { EnsureArg.IsNotNull(client, nameof(client)); EnsureArg.IsNotNull(resource, nameof(resource)); StoredProcedureExecuteResponse result = - await ExecuteStoredProc( + await ExecuteStoredProcAsync( client, resource.PartitionKey, cancellationToken, diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Versioning/FhirCollectionSettingsUpdater.cs b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Versioning/FhirCollectionSettingsUpdater.cs deleted file mode 100644 index 64542c8dd2..0000000000 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/Versioning/FhirCollectionSettingsUpdater.cs +++ /dev/null @@ -1,100 +0,0 @@ -// ------------------------------------------------------------------------------------------------- -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. -// ------------------------------------------------------------------------------------------------- - -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using EnsureThat; -using Microsoft.Azure.Cosmos; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Microsoft.Health.Fhir.Core.Features.Persistence; -using Microsoft.Health.Fhir.CosmosDb.Configs; -using Microsoft.Health.Fhir.CosmosDb.Features.Storage.StoredProcedures.UpdateUnsupportedSearchParametersToUnsupported; - -namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage.Versioning -{ - /// - /// Updates a document collection to the latest index - /// - public sealed class FhirCollectionSettingsUpdater : ICollectionUpdater - { - private readonly ILogger _logger; - private readonly CosmosDataStoreConfiguration _configuration; - private readonly CosmosCollectionConfiguration _collectionConfiguration; - private readonly UpdateUnsupportedSearchParameters _updateSP = new UpdateUnsupportedSearchParameters(); - - private const int CollectionSettingsVersion = 3; - - public FhirCollectionSettingsUpdater( - CosmosDataStoreConfiguration configuration, - IOptionsMonitor namedCosmosCollectionConfigurationAccessor, - ILogger logger) - { - EnsureArg.IsNotNull(configuration, nameof(configuration)); - EnsureArg.IsNotNull(namedCosmosCollectionConfigurationAccessor, nameof(namedCosmosCollectionConfigurationAccessor)); - EnsureArg.IsNotNull(logger, nameof(logger)); - - _configuration = configuration; - _collectionConfiguration = namedCosmosCollectionConfigurationAccessor.Get(Constants.CollectionConfigurationName); - _logger = logger; - } - - public async Task ExecuteAsync(Container container, CancellationToken cancellationToken) - { - EnsureArg.IsNotNull(container, nameof(container)); - - var thisVersion = await GetLatestCollectionVersion(container, cancellationToken); - - if (thisVersion.Version < 2) - { - var containerResponse = await container.ReadContainerAsync(cancellationToken: cancellationToken); - - // For more information on setting indexing policies refer to: - // https://docs.microsoft.com/en-us/azure/cosmos-db/how-to-manage-indexing-policy - // It is no longer necessary to explicitly set the kind (range/hash) - containerResponse.Resource.IndexingPolicy.IncludedPaths.Clear(); - containerResponse.Resource.IndexingPolicy.IncludedPaths.Add(new IncludedPath - { - Path = "/*", - }); - - containerResponse.Resource.IndexingPolicy.ExcludedPaths.Clear(); - containerResponse.Resource.IndexingPolicy.ExcludedPaths.Add(new ExcludedPath { Path = $"/{KnownResourceWrapperProperties.RawResource}/*", }); - containerResponse.Resource.IndexingPolicy.ExcludedPaths.Add(new ExcludedPath { Path = $"/\"_etag\"/?", }); - - // Setting the DefaultTTL to -1 means that by default all documents in the collection will live forever - // but the Cosmos DB service should monitor this collection for documents that have overridden this default. - // See: https://docs.microsoft.com/en-us/azure/cosmos-db/time-to-live - containerResponse.Resource.DefaultTimeToLive = -1; - - await container.ReplaceContainerAsync(containerResponse, cancellationToken: cancellationToken); - - thisVersion.Version = CollectionSettingsVersion; - await container.UpsertItemAsync(thisVersion, new PartitionKey(thisVersion.PartitionKey), cancellationToken: cancellationToken); - } - else if (thisVersion.Version < CollectionSettingsVersion) - { - await _updateSP.Execute(container.Scripts, cancellationToken); - thisVersion.Version = CollectionSettingsVersion; - await container.UpsertItemAsync(thisVersion, new PartitionKey(thisVersion.PartitionKey), cancellationToken: cancellationToken); - } - } - - private static async Task GetLatestCollectionVersion(Container container, CancellationToken cancellationToken) - { - FeedIterator query = container.GetItemQueryIterator( - new QueryDefinition("SELECT * FROM root r"), - requestOptions: new QueryRequestOptions - { - PartitionKey = new PartitionKey(CollectionVersion.CollectionVersionPartition), - }); - - FeedResponse result = await query.ReadNextAsync(cancellationToken); - - return result.FirstOrDefault() ?? new CollectionVersion(); - } - } -} diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Microsoft.Health.Fhir.CosmosDb.csproj b/src/Microsoft.Health.Fhir.CosmosDb/Microsoft.Health.Fhir.CosmosDb.csproj index 9a2cf11ffa..0abfc3618b 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Microsoft.Health.Fhir.CosmosDb.csproj +++ b/src/Microsoft.Health.Fhir.CosmosDb/Microsoft.Health.Fhir.CosmosDb.csproj @@ -1,14 +1,4 @@  - - - - - - - - - - @@ -28,6 +18,7 @@ + diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Registration/FhirServerBuilderCosmosDbRegistrationExtensions.cs b/src/Microsoft.Health.Fhir.CosmosDb/Registration/FhirServerBuilderCosmosDbRegistrationExtensions.cs index 548063e869..bec843d067 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Registration/FhirServerBuilderCosmosDbRegistrationExtensions.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Registration/FhirServerBuilderCosmosDbRegistrationExtensions.cs @@ -21,7 +21,10 @@ using Microsoft.Health.Fhir.Core.Messages.Storage; using Microsoft.Health.Fhir.Core.Models; using Microsoft.Health.Fhir.Core.Registration; -using Microsoft.Health.Fhir.CosmosDb.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.StoredProcedures; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.Versioning; using Microsoft.Health.Fhir.CosmosDb.Features.Health; using Microsoft.Health.Fhir.CosmosDb.Features.Operations; using Microsoft.Health.Fhir.CosmosDb.Features.Operations.Export; @@ -34,7 +37,9 @@ using Microsoft.Health.Fhir.CosmosDb.Features.Storage.Queues; using Microsoft.Health.Fhir.CosmosDb.Features.Storage.Registry; using Microsoft.Health.Fhir.CosmosDb.Features.Storage.StoredProcedures; -using Microsoft.Health.Fhir.CosmosDb.Features.Storage.Versioning; +using Microsoft.Health.Fhir.CosmosDb.Initialization.Features.Storage; +using Microsoft.Health.Fhir.CosmosDb.Initialization.Features.Storage.StoredProcedures; +using Microsoft.Health.Fhir.CosmosDb.Initialization.Registration; using Microsoft.Health.JobManagement; using Constants = Microsoft.Health.Fhir.CosmosDb.Constants; @@ -159,23 +164,23 @@ private static IFhirServerBuilder AddCosmosDbPersistence(this IFhirServerBuilder cosmosCollectionConfiguration, config, upgradeManager, - retryExceptionPolicyFactory, cosmosClientTestProvider, loggerFactory.CreateLogger()); }) .Singleton() .AsService(); - services.Add() - .Transient() - .AsService(); - services.Add() + services.Add() .Transient() - .AsService(); + .AsService(); services.Add() .Transient() - .AsService(); + .AsService(); + + services.Add() + .Singleton() + .AsService(); services.TypesInSameAssemblyAs() .AssignableTo() @@ -183,6 +188,8 @@ private static IFhirServerBuilder AddCosmosDbPersistence(this IFhirServerBuilder .AsSelf() .AsService(); + services.AddCosmosDbInitializationDependencies(); + services.Add() .Scoped() .AsSelf() diff --git a/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/CosmosDbFhirStorageTestsFixture.cs b/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/CosmosDbFhirStorageTestsFixture.cs index 127a5c42a8..5c07ab68c2 100644 --- a/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/CosmosDbFhirStorageTestsFixture.cs +++ b/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/CosmosDbFhirStorageTestsFixture.cs @@ -12,6 +12,7 @@ using Microsoft.Azure.Cosmos; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; +using Microsoft.Health.Core.Extensions; using Microsoft.Health.Core.Features.Context; using Microsoft.Health.Extensions.DependencyInjection; using Microsoft.Health.Fhir.Core.Configs; @@ -30,7 +31,10 @@ using Microsoft.Health.Fhir.Core.Features.Search.SearchValues; using Microsoft.Health.Fhir.Core.Models; using Microsoft.Health.Fhir.Core.UnitTests.Extensions; -using Microsoft.Health.Fhir.CosmosDb.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Configs; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.StoredProcedures; +using Microsoft.Health.Fhir.CosmosDb.Core.Features.Storage.Versioning; using Microsoft.Health.Fhir.CosmosDb.Features.Queries; using Microsoft.Health.Fhir.CosmosDb.Features.Search; using Microsoft.Health.Fhir.CosmosDb.Features.Search.Queries; @@ -39,7 +43,8 @@ using Microsoft.Health.Fhir.CosmosDb.Features.Storage.Queues; using Microsoft.Health.Fhir.CosmosDb.Features.Storage.Registry; using Microsoft.Health.Fhir.CosmosDb.Features.Storage.StoredProcedures; -using Microsoft.Health.Fhir.CosmosDb.Features.Storage.Versioning; +using Microsoft.Health.Fhir.CosmosDb.Initialization.Features.Storage; +using Microsoft.Health.Fhir.CosmosDb.Initialization.Features.Storage.StoredProcedures; using Microsoft.Health.JobManagement; using Microsoft.Health.JobManagement.UnitTests; using NSubstitute; @@ -93,11 +98,13 @@ public CosmosDbFhirStorageTestsFixture() public virtual async Task InitializeAsync() { - var fhirStoredProcs = typeof(IStoredProcedure).Assembly + var fhirStoredProcs = typeof(DataPlaneCollectionSetup).Assembly .GetTypes() - .Where(x => !x.IsAbstract && typeof(IStoredProcedure).IsAssignableFrom(x)) + .Where(x => !x.IsAbstract && typeof(IStoredProcedureMetadata).IsAssignableFrom(x)) .ToArray() - .Select(type => (IStoredProcedure)Activator.CreateInstance(type)); + .Select(type => (IStoredProcedureMetadata)Activator.CreateInstance(type)); + + IStoredProcedureInstaller storedProcedureInstallter = new DataPlaneStoredProcedureInstaller(fhirStoredProcs); var optionsMonitor = Substitute.For>(); @@ -115,21 +122,16 @@ public virtual async Task InitializeAsync() IMediator mediator = Substitute.For(); - var updaters = new ICollectionUpdater[] - { - new FhirCollectionSettingsUpdater(_cosmosDataStoreConfiguration, optionsMonitor, NullLogger.Instance), - new StoredProcedureInstaller(fhirStoredProcs), - new CosmosDbSearchParameterStatusInitializer( - () => _filebasedSearchParameterStatusDataStore, - new CosmosQueryFactory( - new CosmosResponseProcessor(_fhirRequestContextAccessor, mediator, Substitute.For(), NullLogger.Instance), - NullFhirCosmosQueryLogger.Instance), - _cosmosDataStoreConfiguration), - }; + ICollectionDataUpdater dataCollectionUpdater = new CosmosDbSearchParameterStatusInitializer( + () => _filebasedSearchParameterStatusDataStore, + new CosmosQueryFactory( + new CosmosResponseProcessor(_fhirRequestContextAccessor, mediator, Substitute.For(), NullLogger.Instance), + NullFhirCosmosQueryLogger.Instance), + _cosmosDataStoreConfiguration); var dbLock = new CosmosDbDistributedLockFactory(Substitute.For>>(), NullLogger.Instance); - var upgradeManager = new CollectionUpgradeManager(updaters, _cosmosDataStoreConfiguration, optionsMonitor, dbLock, NullLogger.Instance); + var upgradeManager = new CollectionUpgradeManager(dataCollectionUpdater, _cosmosDataStoreConfiguration, optionsMonitor, dbLock, NullLogger.Instance); ICosmosClientTestProvider testProvider = new CosmosClientReadWriteTestProvider(); var cosmosResponseProcessor = Substitute.For(); @@ -139,15 +141,22 @@ public virtual async Task InitializeAsync() var retryExceptionPolicyFactory = new RetryExceptionPolicyFactory(_cosmosDataStoreConfiguration, _fhirRequestContextAccessor); var documentClientInitializer = new FhirCosmosClientInitializer(testProvider, () => new[] { handler }, retryExceptionPolicyFactory, NullLogger.Instance); _cosmosClient = documentClientInitializer.CreateCosmosClient(_cosmosDataStoreConfiguration); - var fhirCollectionInitializer = new CollectionInitializer(_cosmosCollectionConfiguration, _cosmosDataStoreConfiguration, upgradeManager, retryExceptionPolicyFactory, testProvider, NullLogger.Instance); + + var fhirCollectionInitializer = new CollectionInitializer(_cosmosCollectionConfiguration, _cosmosDataStoreConfiguration, upgradeManager, testProvider, NullLogger.Instance); // Cosmos DB emulators throws errors when multiple collections are initialized concurrently. // Use the semaphore to only allow one initialization at a time. await CollectionInitializationSemaphore.WaitAsync(); - + var datacollectionSetup = new DataPlaneCollectionSetup( + _cosmosDataStoreConfiguration, + optionsMonitor, + documentClientInitializer, + storedProcedureInstallter, + NullLogger.Instance); try { - await documentClientInitializer.InitializeDataStoreAsync(_cosmosClient, _cosmosDataStoreConfiguration, new List { fhirCollectionInitializer }, CancellationToken.None); + await datacollectionSetup.CreateDatabaseAsync(retryExceptionPolicyFactory.RetryPolicy, CancellationToken.None); + await datacollectionSetup.CreateCollectionAsync(new List { fhirCollectionInitializer }, retryExceptionPolicyFactory.RetryPolicy, CancellationToken.None); _container = documentClientInitializer.CreateFhirContainer(_cosmosClient, _cosmosDataStoreConfiguration.DatabaseId, _cosmosCollectionConfiguration.CollectionId); } finally