Skip to content

Commit aee00cf

Browse files
committed
Merge branch 'features/#74830' into features/migration-dotnet3
2 parents c524848 + 44af7aa commit aee00cf

8 files changed

Lines changed: 178 additions & 44 deletions

File tree

src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHostExtensions.cs

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,15 @@ public static IWebHost MigrateDbContext<TContext>(this IWebHost webHost, Action<
3939
}
4040
else
4141
{
42+
var retries = 10;
4243
var retry = Policy.Handle<SqlException>()
43-
.WaitAndRetry(10, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
44+
.WaitAndRetry(
45+
retryCount: retries,
46+
sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
47+
onRetry: (exception, timeSpan, retry, ctx) =>
48+
{
49+
logger.LogWarning(exception, "[{prefix}] Exception {ExceptionType} with message {Message} detected on attempt {retry} of {retries}", nameof(TContext), exception.GetType().Name, exception.Message, retry, retries);
50+
});
4451

4552
//if the sql server container is not created on run docker compose this
4653
//migration can't fail for network related exception. The retry options for DbContext only
@@ -53,6 +60,7 @@ public static IWebHost MigrateDbContext<TContext>(this IWebHost webHost, Action<
5360
}
5461
catch (Exception ex)
5562
{
63+
Console.WriteLine(ex.Message.ToString() + "An error occurred while migrating the database used on context {DbContextName}" + typeof(TContext).Name);
5664
logger.LogError(ex, "An error occurred while migrating the database used on context {DbContextName}", typeof(TContext).Name);
5765
if (underK8s)
5866
{
@@ -64,11 +72,72 @@ public static IWebHost MigrateDbContext<TContext>(this IWebHost webHost, Action<
6472
return webHost;
6573
}
6674

75+
76+
public static IWebHost RemoveDbContext<TContext>(this IWebHost webHost) where TContext : DbContext
77+
{
78+
var underK8s = webHost.IsInKubernetes();
79+
80+
using (var scope = webHost.Services.CreateScope())
81+
{
82+
var services = scope.ServiceProvider;
83+
var logger = services.GetRequiredService<ILogger<TContext>>();
84+
var context = services.GetService<TContext>();
85+
86+
try
87+
{
88+
logger.LogInformation("Deleting the database associated with context {DbContextName}" + typeof(TContext).Name);
89+
90+
if (underK8s)
91+
{
92+
InvokeRemover(context);
93+
}
94+
else
95+
{
96+
var retries = 10;
97+
var retry = Policy.Handle<SqlException>()
98+
.WaitAndRetry(
99+
retryCount: retries,
100+
sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
101+
onRetry: (exception, timeSpan, retry, ctx) =>
102+
{
103+
Console.WriteLine(" --RETRYING Exception " + exception.Message.ToString());
104+
logger.LogWarning(exception, "[{prefix}] Exception {ExceptionType} with message {Message} detected on attempt {retry} of {retries}", nameof(TContext), exception.GetType().Name, exception.Message, retry, retries);
105+
});
106+
107+
//if the sql server container is not created on run docker compose this
108+
//migration can't fail for network related exception. The retry options for DbContext only
109+
//apply to transient exceptions
110+
// Note that this is NOT applied when running some orchestrators (let the orchestrator to recreate the failing service)
111+
retry.Execute(() => InvokeRemover(context));
112+
}
113+
114+
Console.WriteLine("Deleted database associated with context {DbContextName}", typeof(TContext).Name);
115+
logger.LogInformation("Deleted database associated with context {DbContextName}", typeof(TContext).Name);
116+
}
117+
catch (Exception ex)
118+
{
119+
Console.WriteLine(ex.Message.ToString() + "An error occurred while deleting the database used on context {DbContextName}" + typeof(TContext).Name);
120+
logger.LogError(ex, "An error occurred while deleting the database used on context {DbContextName}", typeof(TContext).Name);
121+
if (underK8s)
122+
{
123+
throw; // Rethrow under k8s because we rely on k8s to re-run the pod
124+
}
125+
}
126+
}
127+
128+
return webHost;
129+
}
130+
67131
private static void InvokeSeeder<TContext>(Action<TContext, IServiceProvider> seeder, TContext context, IServiceProvider services)
68132
where TContext : DbContext
69133
{
70134
context.Database.Migrate();
71135
seeder(context, services);
72136
}
137+
private static void InvokeRemover<TContext>(TContext context)
138+
where TContext : DbContext
139+
{
140+
context.Database.EnsureDeleted();
141+
}
73142
}
74143
}

src/Services/Marketing/Marketing.API/Infrastructure/MarketingContext.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure
22
{
3+
using System;
4+
using System.IO;
35
using EntityConfigurations;
46
using Microsoft.EntityFrameworkCore;
57
using Microsoft.EntityFrameworkCore.Design;
68
using Microsoft.eShopOnContainers.Services.Marketing.API.Model;
9+
using Microsoft.Extensions.Configuration;
710

811
public class MarketingContext : DbContext
912
{
@@ -27,9 +30,18 @@ public class MarketingContextDesignFactory : IDesignTimeDbContextFactory<Marketi
2730
{
2831
public MarketingContext CreateDbContext(string[] args)
2932
{
30-
var optionsBuilder = new DbContextOptionsBuilder<MarketingContext>()
31-
.UseSqlServer("Server=.;Initial Catalog=Microsoft.eShopOnContainers.Services.MarketingDb;Integrated Security=true");
33+
IConfigurationRoot configuration = new ConfigurationBuilder()
34+
.SetBasePath(Directory.GetCurrentDirectory())
35+
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
36+
.AddEnvironmentVariables()
37+
.Build();
38+
var connectionString = configuration["ConnectionString"];
39+
Console.WriteLine(" -- Connection string");
40+
Console.WriteLine(connectionString);
3241

42+
var optionsBuilder = new DbContextOptionsBuilder<MarketingContext>()
43+
.UseSqlServer(connectionString);
44+
// .UseSqlServer("Server=.;Initial Catalog=Microsoft.eShopOnContainers.Services.MarketingDb;Integrated Security=true");
3345
return new MarketingContext(optionsBuilder.Options);
3446
}
3547
}

src/Services/Marketing/Marketing.API/Startup.cs

Lines changed: 45 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323
using Marketing.API.IntegrationEvents.Handlers;
2424
using Microsoft.AspNetCore.Authentication.JwtBearer;
2525
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
26+
using Microsoft.AspNetCore.Mvc;
2627
using Microsoft.EntityFrameworkCore.Diagnostics;
28+
using Microsoft.eShopOnContainers.Services.Marketing.API.Controllers;
2729
using Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.Middlewares;
2830
using Microsoft.Extensions.Diagnostics.HealthChecks;
2931
using Microsoft.OpenApi.Models;
@@ -44,13 +46,18 @@ public Startup(IConfiguration configuration)
4446

4547

4648
// This method gets called by the runtime. Use this method to add services to the container.
47-
public IServiceProvider ConfigureServices(IServiceCollection services)
49+
public virtual IServiceProvider ConfigureServices(IServiceCollection services)
4850
{
4951
RegisterAppInsights(services);
5052

5153
// Add framework services.
5254
services.AddCustomHealthCheck(Configuration);
53-
services.AddControllers().AddNewtonsoftJson();
55+
services
56+
.AddControllers()
57+
// Added for functional tests
58+
.AddApplicationPart(typeof(LocationsController).Assembly)
59+
.AddNewtonsoftJson()
60+
.SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
5461
services.Configure<MarketingSettings>(Configuration);
5562

5663
ConfigureAuthService(services);
@@ -117,36 +124,8 @@ public IServiceProvider ConfigureServices(IServiceCollection services)
117124
}
118125

119126
// Add framework services.
120-
services.AddSwaggerGen(options =>
121-
{
122-
options.DescribeAllEnumsAsStrings();
123-
options.SwaggerDoc("v1", new OpenApiInfo
124-
{
125-
Title = "eShopOnContainers - Marketing HTTP API",
126-
Version = "v1",
127-
Description = "The Marketing Service HTTP API"
128-
});
129-
130-
options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
131-
{
132-
Type = SecuritySchemeType.OAuth2,
133-
Flows = new OpenApiOAuthFlows()
134-
{
135-
Implicit = new OpenApiOAuthFlow()
136-
{
137-
AuthorizationUrl = new Uri($"{Configuration.GetValue<string>("IdentityUrlExternal")}/connect/authorize"),
138-
TokenUrl = new Uri($"{Configuration.GetValue<string>("IdentityUrlExternal")}/connect/token"),
139-
Scopes = new Dictionary<string, string>()
140-
{
141-
{ "marketing", "Marketing API" }
142-
}
143-
}
144-
}
145-
});
146-
147-
options.OperationFilter<AuthorizeCheckOperationFilter>();
148-
});
149127

128+
AddCustomSwagger(services);
150129
services.AddCors(options =>
151130
{
152131
options.AddPolicy("CorsPolicy",
@@ -186,10 +165,10 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerF
186165
}
187166

188167
app.UseCors("CorsPolicy");
168+
app.UseRouting();
189169

190170
ConfigureAuth(app);
191171

192-
app.UseRouting();
193172
app.UseEndpoints(endpoints =>
194173
{
195174
endpoints.MapDefaultControllerRoute();
@@ -216,6 +195,39 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerF
216195
ConfigureEventBus(app);
217196
}
218197

198+
private void AddCustomSwagger(IServiceCollection services)
199+
{
200+
services.AddSwaggerGen(options =>
201+
{
202+
options.DescribeAllEnumsAsStrings();
203+
options.SwaggerDoc("v1", new OpenApiInfo
204+
{
205+
Title = "eShopOnContainers - Marketing HTTP API",
206+
Version = "v1",
207+
Description = "The Marketing Service HTTP API"
208+
});
209+
210+
options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
211+
{
212+
Type = SecuritySchemeType.OAuth2,
213+
Flows = new OpenApiOAuthFlows()
214+
{
215+
Implicit = new OpenApiOAuthFlow()
216+
{
217+
AuthorizationUrl = new Uri($"{Configuration.GetValue<string>("IdentityUrlExternal")}/connect/authorize"),
218+
TokenUrl = new Uri($"{Configuration.GetValue<string>("IdentityUrlExternal")}/connect/token"),
219+
Scopes = new Dictionary<string, string>()
220+
{
221+
{ "marketing", "Marketing API" }
222+
}
223+
}
224+
}
225+
});
226+
227+
options.OperationFilter<AuthorizeCheckOperationFilter>();
228+
});
229+
}
230+
219231
private void RegisterAppInsights(IServiceCollection services)
220232
{
221233
services.AddApplicationInsightsTelemetry(Configuration);
@@ -225,7 +237,7 @@ private void RegisterAppInsights(IServiceCollection services)
225237
private void ConfigureAuthService(IServiceCollection services)
226238
{
227239
// prevent from mapping "sub" claim to nameidentifier.
228-
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
240+
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub");
229241

230242
services.AddAuthentication(options =>
231243
{

src/Services/Marketing/Marketing.FunctionalTests/CampaignScenarios.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public async Task Delete_delete_campaign_and_response_not_content_status_code()
7575
var campaignResponse = await server.CreateClient()
7676
.PostAsync(Post.AddNewCampaign, content);
7777

78-
if (int.TryParse(campaignResponse.Headers.Location.Segments[4], out int id))
78+
if (int.TryParse(campaignResponse.Headers.Location.Segments[3], out int id))
7979
{
8080
var response = await server.CreateClient()
8181
.DeleteAsync(Delete.CampaignBy(id));
@@ -99,7 +99,7 @@ public async Task Put_update_campaign_and_response_not_content_status_code()
9999
var campaignResponse = await server.CreateClient()
100100
.PostAsync(Post.AddNewCampaign, content);
101101

102-
if (int.TryParse(campaignResponse.Headers.Location.Segments[4], out int id))
102+
if (int.TryParse(campaignResponse.Headers.Location.Segments[3], out int id))
103103
{
104104
fakeCampaignDto.Description = "FakeCampaignUpdatedDescription";
105105
content = new StringContent(JsonConvert.SerializeObject(fakeCampaignDto), Encoding.UTF8, "application/json");

src/Services/Marketing/Marketing.FunctionalTests/Marketing.FunctionalTests.csproj

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@
1717
</ItemGroup>
1818

1919
<ItemGroup>
20-
<PackageReference Include="Microsoft.AspNetCore.App" />
21-
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="2.2.0" />
20+
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="$(Microsoft_AspNetCore_Mvc_Testing)" />
2221
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(Microsoft_NET_Test_Sdk)" />
2322
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="$(Microsoft_AspNetCore_TestHost)" />
2423
<PackageReference Include="xunit" Version="$(xunit)" />

src/Services/Marketing/Marketing.FunctionalTests/MarketingScenarioBase.cs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
using Microsoft.AspNetCore.Hosting;
22
using Microsoft.AspNetCore.TestHost;
3+
using Microsoft.eShopOnContainers.Services.Marketing.API;
34
using Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure;
45
using Microsoft.Extensions.Configuration;
56
using Microsoft.Extensions.DependencyInjection;
67
using Microsoft.Extensions.Logging;
8+
using Microsoft.Extensions.Options;
9+
using System;
710
using System.IO;
811
using System.Reflection;
12+
using System.Threading;
13+
using System.Threading.Tasks;
914

1015
namespace Marketing.FunctionalTests
1116
{
@@ -15,29 +20,54 @@ public class MarketingScenarioBase
1520

1621
public TestServer CreateServer()
1722
{
23+
24+
Console.WriteLine(" Creating test server");
1825
var path = Assembly.GetAssembly(typeof(MarketingScenarioBase))
1926
.Location;
2027

28+
Console.WriteLine(" Creating builder");
29+
2130
var hostBuilder = new WebHostBuilder()
2231
.UseContentRoot(Path.GetDirectoryName(path))
2332
.ConfigureAppConfiguration(cb =>
2433
{
25-
cb.AddJsonFile("appsettings.json", optional: false)
34+
var h = cb.AddJsonFile("appsettings.json", optional: false)
2635
.AddEnvironmentVariables();
27-
}).UseStartup<MarketingTestsStartup>();
36+
})
37+
.CaptureStartupErrors(true)
38+
.UseStartup<MarketingTestsStartup>();
2839

40+
Console.WriteLine(" Created builder");
41+
2942
var testServer = new TestServer(hostBuilder);
3043

44+
using (var scope = testServer.Services.CreateScope())
45+
{
46+
var services = scope.ServiceProvider;
47+
48+
var logger = services.GetRequiredService<ILogger<MarketingScenarioBase>>();
49+
var settings = services.GetRequiredService<IOptions<MarketingSettings>>();
50+
logger.LogError("connectionString " + settings.Value.ConnectionString);
51+
Console.WriteLine("connectionString " + settings.Value.ConnectionString);
52+
}
53+
3154
testServer.Host
55+
.RemoveDbContext<MarketingContext>()
3256
.MigrateDbContext<MarketingContext>((context, services) =>
3357
{
3458
var logger = services.GetService<ILogger<MarketingContextSeed>>();
3559

60+
logger.LogError("Migrating MarketingContextSeed");
3661
new MarketingContextSeed()
3762
.SeedAsync(context, logger)
3863
.Wait();
3964

4065
});
66+
Console.WriteLine(" Thread to sleep");
67+
68+
Thread.Sleep(5000);
69+
Console.WriteLine(" Thread after");
70+
4171

4272
return testServer;
4373
}

src/Services/Marketing/Marketing.FunctionalTests/MarketingTestStartup.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
using Microsoft.AspNetCore.Builder;
1+
using System;
2+
using Microsoft.AspNetCore.Builder;
3+
using Microsoft.AspNetCore.Routing;
24
using Microsoft.eShopOnContainers.Services.Marketing.API;
35
using Microsoft.Extensions.Configuration;
6+
using Microsoft.Extensions.DependencyInjection;
47

58
namespace Marketing.FunctionalTests
69
{
@@ -10,6 +13,14 @@ public MarketingTestsStartup(IConfiguration env) : base(env)
1013
{
1114
}
1215

16+
public override IServiceProvider ConfigureServices(IServiceCollection services)
17+
{
18+
// Added to avoid the Authorize data annotation in test environment.
19+
// Property "SuppressCheckForUnhandledSecurityMetadata" in appsettings.json
20+
services.Configure<RouteOptions>(Configuration);
21+
return base.ConfigureServices(services);
22+
}
23+
1324
protected override void ConfigureAuth(IApplicationBuilder app)
1425
{
1526
if (Configuration["isTest"] == bool.TrueString.ToLowerInvariant())

src/Services/Marketing/Marketing.FunctionalTests/appsettings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@
66
"isTest": "true",
77
"EventBusConnection": "localhost",
88
"PicBaseUrl": "http://localhost:5110/api/v1/campaigns/[0]/pic/",
9-
"SubscriptionClientName": "Marketing"
9+
"SubscriptionClientName": "Marketing",
10+
"SuppressCheckForUnhandledSecurityMetadata":true
1011
}

0 commit comments

Comments
 (0)