Skip to content

Commit

Permalink
Merge pull request #136 from lanegoolsby/chaos
Browse files Browse the repository at this point in the history
(feat) Add Chaos Engineering
  • Loading branch information
natenho authored Aug 25, 2024
2 parents 3e7449f + 0c1a5a0 commit 99f53f4
Show file tree
Hide file tree
Showing 16 changed files with 322 additions and 1 deletion.
17 changes: 17 additions & 0 deletions src/Mockaco.AspNetCore/Chaos/Strategies/ChaosStrategyBehavior.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Net;
using System.Text;
using Microsoft.AspNetCore.Http;

namespace Mockaco.Chaos.Strategies;

internal class ChaosStrategyBehavior : IChaosStrategy
{
public Task Response(HttpResponse httpResponse)
{
httpResponse.StatusCode = (int)HttpStatusCode.ServiceUnavailable;

var bodyBytes = Encoding.UTF8.GetBytes($"Error {httpResponse.StatusCode}: {HttpStatusCode.ServiceUnavailable}");

return httpResponse.Body.WriteAsync(bodyBytes, 0, bodyBytes.Length, default);
}
}
17 changes: 17 additions & 0 deletions src/Mockaco.AspNetCore/Chaos/Strategies/ChaosStrategyException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Net;
using System.Text;
using Microsoft.AspNetCore.Http;

namespace Mockaco.Chaos.Strategies;

internal class ChaosStrategyException : IChaosStrategy
{
public Task Response(HttpResponse httpResponse)
{
httpResponse.StatusCode = (int)HttpStatusCode.InternalServerError;

var bodyBytes = Encoding.UTF8.GetBytes($"Error {httpResponse.StatusCode}: {HttpStatusCode.InternalServerError}");

return httpResponse.Body.WriteAsync(bodyBytes, 0, bodyBytes.Length);
}
}
23 changes: 23 additions & 0 deletions src/Mockaco.AspNetCore/Chaos/Strategies/ChaosStrategyLatency.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Mockaco.Chaos.Strategies;

internal class ChaosStrategyLatency : IChaosStrategy
{
private readonly ILogger<ChaosStrategyLatency> _logger;
private readonly IOptions<ChaosOptions> _options;

public ChaosStrategyLatency(ILogger<ChaosStrategyLatency> logger, IOptions<ChaosOptions> options)
{
_logger = logger;
_options = options;
}
public Task Response(HttpResponse httpResponse)
{
var responseDelay = new Random().Next(_options.Value.MinimumLatencyTime, _options.Value.MaximumLatencyTime);
_logger.LogInformation($"Chaos Latency (ms): {responseDelay}");
return Task.Delay(responseDelay);
}
}
17 changes: 17 additions & 0 deletions src/Mockaco.AspNetCore/Chaos/Strategies/ChaosStrategyResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Net;
using System.Text;
using Microsoft.AspNetCore.Http;

namespace Mockaco.Chaos.Strategies;

internal class ChaosStrategyResult : IChaosStrategy
{
public Task Response(HttpResponse httpResponse)
{
httpResponse.StatusCode = (int)HttpStatusCode.BadRequest;

var bodyBytes = Encoding.UTF8.GetBytes($"Error {httpResponse.StatusCode}: {HttpStatusCode.BadRequest}");

return httpResponse.Body.WriteAsync(bodyBytes, 0, bodyBytes.Length);
}
}
27 changes: 27 additions & 0 deletions src/Mockaco.AspNetCore/Chaos/Strategies/ChaosStrategyTimeout.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.Net;
using System.Text;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;

namespace Mockaco.Chaos.Strategies;

internal class ChaosStrategyTimeout : IChaosStrategy
{
private readonly IOptions<ChaosOptions> _options;

public ChaosStrategyTimeout(IOptions<ChaosOptions> options)
{
_options = options;
}

public async Task Response(HttpResponse httpResponse)
{
await Task.Delay(_options.Value.TimeBeforeTimeout);

httpResponse.StatusCode = (int)HttpStatusCode.RequestTimeout;

var bodyBytes = Encoding.UTF8.GetBytes($"Error {httpResponse.StatusCode}: {HttpStatusCode.RequestTimeout}");

await httpResponse.Body.WriteAsync(bodyBytes, 0, bodyBytes.Length, default);
}
}
8 changes: 8 additions & 0 deletions src/Mockaco.AspNetCore/Chaos/Strategies/IChaosStrategy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Microsoft.AspNetCore.Http;

namespace Mockaco.Chaos.Strategies;

internal interface IChaosStrategy
{
Task Response(HttpResponse httpResponse);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ public static IApplicationBuilder UseMockaco(this IApplicationBuilder app, Actio

var options = app.ApplicationServices.GetRequiredService<IOptions<MockacoOptions>>().Value;

var optionsChaos = app.ApplicationServices.GetRequiredService<IOptions<ChaosOptions>>().Value;

app.UseEndpoints(endpoints =>
{
endpoints.Map($"/{options.VerificationEndpointPrefix ?? options.MockacoEndpoint}/{options.VerificationEndpointName}", VerifyerExtensions.Verify);
Expand All @@ -36,6 +38,7 @@ public static IApplicationBuilder UseMockaco(this IApplicationBuilder app, Actio
app
.UseMiddleware<RequestMatchingMiddleware>()
.UseMiddleware<ResponseDelayMiddleware>()
.UseMiddleware<ChaosMiddleware>()
.UseMiddleware<ResponseMockingMiddleware>()
.UseMiddleware<CallbackMiddleware>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Options;
using Mockaco;
using Mockaco.Chaos.Strategies;
using Mockaco.HealthChecks;
using Mockaco.Settings;

Expand All @@ -15,6 +16,7 @@ public static IServiceCollection AddMockaco(this IServiceCollection services) =>
public static IServiceCollection AddMockaco(this IServiceCollection services, Action<MockacoOptions> config) =>
services
.AddOptions<MockacoOptions>().Configure(config).Services
.AddOptions<ChaosOptions>().Configure<IOptions<MockacoOptions>>((options, parent) => options = parent.Value.Chaos).Services
.AddOptions<TemplateFileProviderOptions>()
.Configure<IOptions<MockacoOptions>>((options, parent) => options = parent.Value.TemplateFileProvider)
.Services
Expand All @@ -30,6 +32,7 @@ private static IServiceCollection AddConfiguration(this IServiceCollection servi
services
.AddOptions()
.Configure<MockacoOptions>(config)
.Configure<ChaosOptions>(config.GetSection("Chaos"))
.Configure<TemplateFileProviderOptions>(config.GetSection("TemplateFileProvider"));

private static IServiceCollection AddCommonServices(this IServiceCollection services)
Expand All @@ -38,6 +41,7 @@ private static IServiceCollection AddCommonServices(this IServiceCollection serv
.AddMemoryCache()
.AddHttpClient()
.AddInternalServices()
.AddChaosServices()
.AddHostedService<MockProviderWarmUp>();

services
Expand Down Expand Up @@ -81,5 +85,13 @@ private static IServiceCollection AddInternalServices(this IServiceCollection se
.AddTransient<ITemplateTransformer, TemplateTransformer>()

.AddTemplatesGenerating();

private static IServiceCollection AddChaosServices(this IServiceCollection services) =>
services
.AddTransient<IChaosStrategy, ChaosStrategyBehavior>()
.AddTransient<IChaosStrategy, ChaosStrategyException>()
.AddTransient<IChaosStrategy, ChaosStrategyLatency>()
.AddTransient<IChaosStrategy, ChaosStrategyResult>()
.AddTransient<IChaosStrategy, ChaosStrategyTimeout>();
}
}
5 changes: 5 additions & 0 deletions src/Mockaco.AspNetCore/Extensions/EnumerableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,10 @@ public static async Task<bool> AllAsync<TSource>(this IEnumerable<Task<TSource>>

return true;
}

public static T Random<T>(this IEnumerable<T> enumerable) {
int index = new Random().Next(0, enumerable.Count());
return enumerable.ElementAt(index);
}
}
}
73 changes: 73 additions & 0 deletions src/Mockaco.AspNetCore/Middlewares/ChaosMiddleware.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using System.Net;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Mockaco.Chaos.Strategies;

namespace Mockaco;

internal class ChaosMiddleware
{
private readonly RequestDelegate _next;
private readonly IEnumerable<IChaosStrategy> _strategies;
private readonly ILogger<ChaosMiddleware> _logger;
private readonly IOptions<ChaosOptions> _options;

private List<int> ErrorList { get; set; }
private int Counter { get; set; }

public ChaosMiddleware(
RequestDelegate next,
IEnumerable<IChaosStrategy> strategies,
ILogger<ChaosMiddleware> logger,
IOptions<ChaosOptions> options)
{
_next = next;
_strategies = strategies;
_logger = logger;
_options = options;
}

public async Task Invoke(HttpContext httpContext)
{
if (!_options.Value.Enabled)
{
return;
}

Counter++;
if (Counter > 100)
Counter = 1;

if (Counter == 1)
ErrorList = GenerateErrorList(_options.Value.ChaosRate);

if (ErrorList.Contains(Counter))
{
var selected = _strategies.Random();
_logger.LogInformation($"Chaos: {selected?.ToString()}");
if (selected != null) await selected.Response(httpContext.Response);
}

if (httpContext.Response.StatusCode != (int)HttpStatusCode.OK)
return;


await _next(httpContext);
}

private List<int> GenerateErrorList(int rate)
{
var list = new List<int>();
while (list.Count < rate)
{
var item = new Random().Next(1, 100);
if (!list.Contains(item))
{
list.Add(item);
}
}

return list.OrderBy(x => x).ToList();
}
}
19 changes: 19 additions & 0 deletions src/Mockaco.AspNetCore/Options/ChaosOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace Mockaco;

public class ChaosOptions
{
public bool Enabled { get; set; }
public int ChaosRate { get; set; }
public int MinimumLatencyTime { get; set; }
public int MaximumLatencyTime { get; set; }
public int TimeBeforeTimeout { get; set; }

public ChaosOptions()
{
Enabled = true;
ChaosRate = 10;
MinimumLatencyTime = 500;
MaximumLatencyTime = 3000;
TimeBeforeTimeout = 10000;
}
}
3 changes: 3 additions & 0 deletions src/Mockaco.AspNetCore/Options/MockacoOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public class MockacoOptions

// Deprecated (use MockacoEndpoint instead)
public string VerificationEndpointPrefix { get; set; }

public ChaosOptions Chaos { get; set; }

public string VerificationEndpointName { get; set; }

Expand All @@ -38,6 +40,7 @@ public MockacoOptions()
MockacoEndpoint = "_mockaco";
VerificationEndpointName = "verification";
TemplateFileProvider = new();
Chaos = new ChaosOptions();
}
}
}
14 changes: 14 additions & 0 deletions src/Mockaco.AspNetCore/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
Bogus.PhoneNumberExtensions
Microsoft.AspNetCore.Builder.MockacoApplicationBuilder
Microsoft.Extensions.DependencyInjection.MockacoServiceCollection
Mockaco.ChaosOptions
Mockaco.ChaosOptions.ChaosOptions() -> void
Mockaco.ChaosOptions.ChaosRate.get -> int
Mockaco.ChaosOptions.ChaosRate.set -> void
Mockaco.ChaosOptions.Enabled.get -> bool
Mockaco.ChaosOptions.Enabled.set -> void
Mockaco.ChaosOptions.MaximumLatencyTime.get -> int
Mockaco.ChaosOptions.MaximumLatencyTime.set -> void
Mockaco.ChaosOptions.MinimumLatencyTime.get -> int
Mockaco.ChaosOptions.MinimumLatencyTime.set -> void
Mockaco.ChaosOptions.TimeBeforeTimeout.get -> int
Mockaco.ChaosOptions.TimeBeforeTimeout.set -> void
Mockaco.HealthChecks.StartupHealthCheck
Mockaco.HealthChecks.StartupHealthCheck.CheckHealthAsync(Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckContext context, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult>
Mockaco.HealthChecks.StartupHealthCheck.StartupCompleted.get -> bool
Expand Down Expand Up @@ -42,6 +54,8 @@ Mockaco.Mock.RawTemplate.set -> void
Mockaco.Mock.Route.get -> string
Mockaco.Mock.Route.set -> void
Mockaco.MockacoOptions
Mockaco.MockacoOptions.Chaos.get -> Mockaco.ChaosOptions
Mockaco.MockacoOptions.Chaos.set -> void
Mockaco.MockacoOptions.DefaultHttpContentType.get -> string
Mockaco.MockacoOptions.DefaultHttpContentType.set -> void
Mockaco.MockacoOptions.DefaultHttpStatusCode.get -> System.Net.HttpStatusCode
Expand Down
9 changes: 8 additions & 1 deletion src/Mockaco/Settings/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,14 @@
"Imports": [],
"MatchedRoutesCacheDuration": 60,
"MockacoEndpoint": "_mockaco",
"VerificationEndpointName": "verification"
"VerificationEndpointName": "verification",
"Chaos": {
"Enabled": false,
"ChaosRate": 20,
"MinimumLatencyTime": 500,
"MaximumLatencyTime": 3000,
"TimeBeforeTimeout": 10000
}
},
"AllowedHosts": "*",
"Serilog": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Mockaco.Tests.Extensions;

public class EnumerableExtensionTests
{
[Theory]
[MemberData(nameof(Data))]
public void Select_Random_IEnumerables(List<object> source)
{
IEnumerable<object> enummerables = source;

object selected = enummerables.Random();

Assert.Contains(selected, source);
}

public static IEnumerable<object[]> Data()
{
yield return new object[] { new List<object> { 1, 2, 3 } };
yield return new object[] { new List<object> { "a", "b", "c" } };
yield return new object[] { new List<object> { "a1", "c2" } };
}
}
Loading

0 comments on commit 99f53f4

Please sign in to comment.