diff --git a/src/Services/Catalog/Catalog.API/Extensions/WebHostExtensions.cs b/src/Services/Catalog/Catalog.API/Extensions/WebHostExtensions.cs new file mode 100644 index 000000000..b7a982b2e --- /dev/null +++ b/src/Services/Catalog/Catalog.API/Extensions/WebHostExtensions.cs @@ -0,0 +1,80 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Polly; +using System.Data.SqlClient; +using Microsoft.AspNetCore.Hosting; + +namespace Catalog.API.Extensions +{ + public static class WebHostExtensions + { + public static bool IsInKubernetes(this IWebHost host) + { + var cfg = host.Services.GetService(); + var orchestratorType = cfg.GetValue("OrchestratorType"); + return orchestratorType?.ToUpper() == "K8S"; + } + + public static IWebHost MigrateDbContext(this IWebHost host, Action seeder) where TContext : DbContext + { + var underK8s = host.IsInKubernetes(); + + using (var scope = host.Services.CreateScope()) + { + var services = scope.ServiceProvider; + + var logger = services.GetRequiredService>(); + + var context = services.GetService(); + + try + { + logger.LogInformation("Migrating database associated with context {DbContextName}", typeof(TContext).Name); + + if (underK8s) + { + InvokeSeeder(seeder, context, services); + } + else + { + var retry = Policy.Handle() + .WaitAndRetry(new TimeSpan[] + { + TimeSpan.FromSeconds(3), + TimeSpan.FromSeconds(5), + TimeSpan.FromSeconds(8), + }); + + //if the sql server container is not created on run docker compose this + //migration can't fail for network related exception. The retry options for DbContext only + //apply to transient exceptions + // Note that this is NOT applied when running some orchestrators (let the orchestrator to recreate the failing service) + retry.Execute(() => InvokeSeeder(seeder, context, services)); + } + + logger.LogInformation("Migrated database associated with context {DbContextName}", typeof(TContext).Name); + } + catch (Exception ex) + { + logger.LogError(ex, "An error occurred while migrating the database used on context {DbContextName}", typeof(TContext).Name); + if (underK8s) + { + throw; // Rethrow under k8s because we rely on k8s to re-run the pod + } + } + } + + return host; + } + + private static void InvokeSeeder(Action seeder, TContext context, IServiceProvider services) + where TContext : DbContext + { + context.Database.Migrate(); + seeder(context, services); + } + } +} diff --git a/src/Services/Catalog/Catalog.API/Program.cs b/src/Services/Catalog/Catalog.API/Program.cs index 39f3643d8..d24f976f9 100644 --- a/src/Services/Catalog/Catalog.API/Program.cs +++ b/src/Services/Catalog/Catalog.API/Program.cs @@ -68,7 +68,6 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API private static IHostBuilder CreateHostBuilder(IConfiguration configuration, string[] args) => Host.CreateDefaultBuilder(args) .ConfigureServices(services => services.AddAutofac()) - .UseServiceProviderFactory(new AutofacServiceProviderFactory()) .ConfigureWebHostDefaults(builder => { builder.CaptureStartupErrors(false) diff --git a/src/Services/Catalog/Catalog.API/Startup.cs b/src/Services/Catalog/Catalog.API/Startup.cs index 9dcf81f01..64441f9ea 100644 --- a/src/Services/Catalog/Catalog.API/Startup.cs +++ b/src/Services/Catalog/Catalog.API/Startup.cs @@ -44,7 +44,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API public IConfiguration Configuration { get; } - public void ConfigureServices(IServiceCollection services) + public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddAppInsight(Configuration) .AddGrpc().Services @@ -59,6 +59,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API var container = new ContainerBuilder(); container.Populate(services); + return new AutofacServiceProvider(container.Build()); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) diff --git a/src/Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj b/src/Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj index a679d5d35..9db9a3c37 100644 --- a/src/Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj +++ b/src/Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj @@ -33,7 +33,7 @@ - + diff --git a/src/Services/Catalog/Catalog.FunctionalTests/CatalogScenarioBase.cs b/src/Services/Catalog/Catalog.FunctionalTests/CatalogScenarioBase.cs index 46bdb07d1..6a8cbd72f 100644 --- a/src/Services/Catalog/Catalog.FunctionalTests/CatalogScenarioBase.cs +++ b/src/Services/Catalog/Catalog.FunctionalTests/CatalogScenarioBase.cs @@ -1,3 +1,5 @@ +using Autofac.Extensions.DependencyInjection; +using Catalog.API.Extensions; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; @@ -5,6 +7,7 @@ using Microsoft.eShopOnContainers.Services.Catalog.API; using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System.IO; @@ -25,14 +28,16 @@ namespace Catalog.FunctionalTests { cb.AddJsonFile("appsettings.json", optional: false) .AddEnvironmentVariables(); - }).UseStartup(); + }) + .UseStartup(); + var testServer = new TestServer(hostBuilder); testServer.Host .MigrateDbContext((context, services) => { - var env = services.GetService(); + var env = services.GetService(); var settings = services.GetService>(); var logger = services.GetService>();