Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Chaos Engineering to inject random crashes into calls #114

Closed
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 57 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@ Mockaco is an HTTP-based API mock server with fast setup.
- Callback support - trigger another service call when a request hits your mocked API
- State support - stateful mock support allow a mock to be returned based on a global variable previously set by another mock
- Portable - runs in any [.NET Core compatible environment](https://github.com/dotnet/core/blob/main/release-notes/5.0/5.0-supported-os.md)
- Fault and Delay Injection (Chaos Engineering)

[![Mocking APIs with Mockaco | .NET 7](https://user-images.githubusercontent.com/4236481/195997781-b730959e-8d6d-432c-b35a-3adb580abc41.png)](https://www.youtube.com/watch?v=QBnXCgZFzM0 "Mocking APIs with Mockaco | .NET 7")

@@ -51,7 +52,7 @@ Mockaco is an HTTP-based API mock server with fast setup.
+ [Configure the duration of cache storing last request for verification](#configure-the-time-of-cache-storing-last-request-for-verification)
+ [Verification summary](#verification-summary)
- [Generators](#generators)

- [Chaos Engineering](#chaos-engineering)

# Get Started

@@ -553,3 +554,58 @@ Icon made by [Freepik](https://www.freepik.com/ "Freepik") from [www.flaticon.co

## License
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fnatenho%2FMockaco.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fnatenho%2FMockaco?ref=badge_large)

# Chaos Engineering

Enabling chaos engineering, behavior different from what is expected will be randomly inserted into the calls, such as errors and delays, with this it is possible to verify how the client behaves in unforeseen situations.

## How to enable Chaos Engineering

To enable chaos it is necessary to set the 'Enabled' variable to 'true' as shown in the example below:

```
"Mockaco": {
...
"Chaos": {
"Enabled": true,
},
...
},
```
in ```appsettings.json```.

## Types of answers with chaos

- Behavior: Return HTTP Error 503 (Service Unavailable)
- Exception: Return HTTP Error 500 (Internal Server Erro)
- Latency: Randomly add delay time to a call
- Result: Return HTTP Error 400 (Bad Request)
- Timeout: Waits a while and returns error 408 (Request Timeout)

## How to define paramenters Chaos Engenering

Parameters are defined inside the Chaos key

```
"Mockaco": {
...
"Chaos": {
"Enabled": true,
"ChaosRate": 20,
"MinimumLatencyTime": 500,
"MaximumLatencyTime": 3000,
"TimeBeforeTimeout": 10000
},
...
},
```
in ```appsettings.json```.


|Parameter|Description|Default|
||||
|Enabled|Option to enable and disable chaos (true / false)|true|
|ChaosRate|Percentage of calls affected by chaos (0 - 100) |20|
|MinimumLatencyTime|Minimum latency in milliseconds when the latency strategy is drawn|500|
|MaximumLatencyTime|Maximum latency in milliseconds when the latency strategy is drawn|3000|
|TimeBeforeTimeout|Time in milliseconds before timeout error|10000|
Binary file added gitversion.json
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this

Binary file not shown.
9 changes: 9 additions & 0 deletions src/Mockaco.AspNetCore/Chaos/Strategies/IStrategyChaos.cs
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think of the "IChaosStrategy" name? It sounds better to me.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Microsoft.AspNetCore.Http;

namespace Mockaco
{
internal interface IStrategyChaos
{
Task Response(HttpResponse httpResponse);
}
}
19 changes: 19 additions & 0 deletions src/Mockaco.AspNetCore/Chaos/Strategies/StrategyChaosBehavior.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.Net;
using System.Text;

namespace Mockaco
{
internal class StrategyChaosBehavior : IStrategyChaos
{
public async Task Response(HttpResponse httpResponse)
{
httpResponse.StatusCode = (int)HttpStatusCode.ServiceUnavailable;

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

await httpResponse.Body.WriteAsync(bodyBytes, 0, bodyBytes.Length, default);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The await is not necessary here, you can directly return the task

}
}
}
19 changes: 19 additions & 0 deletions src/Mockaco.AspNetCore/Chaos/Strategies/StrategyChaosException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.Net;
using System.Text;

namespace Mockaco
{
internal class StrategyChaosException : IStrategyChaos
{
public async Task Response(HttpResponse httpResponse)
{
httpResponse.StatusCode = (int)HttpStatusCode.InternalServerError;

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

await httpResponse.Body.WriteAsync(bodyBytes, 0, bodyBytes.Length, default);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The await is not necessary here, you can directly return the task

}
}
}
27 changes: 27 additions & 0 deletions src/Mockaco.AspNetCore/Chaos/Strategies/StrategyChaosLatency.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Net;
using System.Text;

namespace Mockaco
{
internal class StrategyChaosLatency : IStrategyChaos
{
private readonly ILogger<StrategyChaosLatency> _logger;
private readonly IOptions<ChaosOptions> _options;

public StrategyChaosLatency(ILogger<StrategyChaosLatency> logger,
IOptions<ChaosOptions> options)
{
_logger = logger;
_options = options;
}
public async Task Response(HttpResponse httpResponse)
{
var responseDelay = new Random().Next(_options.Value.MinimumLatencyTime, _options.Value.MaximumLatencyTime);
_logger.LogInformation($"Chaos Latency (ms): {responseDelay}");
await Task.Delay(responseDelay);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The await is not necessary here, you can directly return the task

}
}
}
19 changes: 19 additions & 0 deletions src/Mockaco.AspNetCore/Chaos/Strategies/StrategyChaosResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.Net;
using System.Text;

namespace Mockaco
{
internal class StrategyChaosResult : IStrategyChaos
{
public async Task Response(HttpResponse httpResponse)
{
httpResponse.StatusCode = (int)HttpStatusCode.BadRequest;

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

await httpResponse.Body.WriteAsync(bodyBytes, 0, bodyBytes.Length, default);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The await is not necessary here, you can directly return the task

}
}
}
27 changes: 27 additions & 0 deletions src/Mockaco.AspNetCore/Chaos/Strategies/StrategyChaosTimeout.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Net;
using System.Text;

namespace Mockaco
{
internal class StrategyChaosTimeout : IStrategyChaos
{
private readonly IOptions<ChaosOptions> _options;

public StrategyChaosTimeout(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);
}
}
}
Original file line number Diff line number Diff line change
@@ -11,13 +11,16 @@ public static IApplicationBuilder UseMockaco(this IApplicationBuilder app, Actio
{
app.UseRouting();
var options = app.ApplicationServices.GetRequiredService<IOptions<MockacoOptions>>().Value;
var optionsChaos = app.ApplicationServices.GetRequiredService<IOptions<ChaosOptions>>().Value;

app.UseEndpoints(endpoints => endpoints.Map($"/{options.VerificationEndpointPrefix}/{options.VerificationEndpointName}", VerifyerExtensions.Verify));

app.UseMiddleware<ErrorHandlingMiddleware>();
configure(app);
app
.UseMiddleware<RequestMatchingMiddleware>()
.UseMiddleware<ResponseDelayMiddleware>()
.UseMiddleware<ChaosMiddleware>()
.UseMiddleware<ResponseMockingMiddleware>()
.UseMiddleware<CallbackMiddleware>();

Original file line number Diff line number Diff line change
@@ -13,6 +13,9 @@ 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
@@ -28,13 +31,15 @@ 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) =>
services
.AddMemoryCache()
.AddHttpClient()
.AddInternalServices()
.AddChaosServices()
.AddHostedService<MockProviderWarmUp>();

private static IServiceCollection AddInternalServices(this IServiceCollection services) =>
@@ -70,5 +75,14 @@ private static IServiceCollection AddInternalServices(this IServiceCollection se
.AddTransient<ITemplateTransformer, TemplateTransformer>()

.AddTemplatesGenerating();

private static IServiceCollection AddChaosServices(this IServiceCollection services) =>
services
.AddSingleton<IStrategyChaos, StrategyChaosBehavior>()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why Singleton? all strategies seems to be perfectly transient.

.AddSingleton<IStrategyChaos, StrategyChaosException>()
.AddSingleton<IStrategyChaos, StrategyChaosLatency>()
.AddSingleton<IStrategyChaos, StrategyChaosResult>()
.AddSingleton<IStrategyChaos, StrategyChaosTimeout>();

}
}
5 changes: 5 additions & 0 deletions src/Mockaco.AspNetCore/Extensions/EnumerableExtensions.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
70 changes: 70 additions & 0 deletions src/Mockaco.AspNetCore/Middlewares/ChaosMiddleware.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Net;

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

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


Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove additional blank lines

public ChaosMiddleware(RequestDelegate next, IEnumerable<IStrategyChaos> 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)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inverting this if would reduce the nesting

{
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()}");
await selected.Response(httpContext.Response);
}

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

}

await _next(httpContext);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The await is not necessary here, you can directly return the task :)


}

private List<int> GenerateErrorList(int rate)
{
int items = rate;
var list = new List<int>();
while (list.Count() < items)
{
var item = new Random().Next(1, 100);
if (!list.Contains(item))
{
list.Add(item);
}
}
return list.OrderBy(x => x).ToList();
}
}
}
22 changes: 22 additions & 0 deletions src/Mockaco.AspNetCore/Options/ChaosOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Net;

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
@@ -22,6 +22,8 @@ public class MockacoOptions

public TemplateFileProviderOptions TemplateFileProvider { get; set; }

public ChaosOptions Chaos { get; set; }

public MockacoOptions()
{
DefaultHttpStatusCode = HttpStatusCode.OK;
@@ -33,6 +35,7 @@ public MockacoOptions()
VerificationEndpointPrefix = "_mockaco";
VerificationEndpointName = "verification";
TemplateFileProvider = new();
Chaos = new();
}
}
}
Loading