Skip to content

Commit a35474d

Browse files
committed
Refactorings for authentication, TestType added to Bombardier options, readme file updated
1 parent a6f3d04 commit a35474d

File tree

6 files changed

+209
-20
lines changed

6 files changed

+209
-20
lines changed

README.md

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,100 @@
11
# QAToolKit.Engine.Bombardier
22
![.NET Core](https://github.com/qatoolkit/qatoolkit-engine-bombardier-net/workflows/.NET%20Core/badge.svg?branch=main)
33

4-
QAToolKit Bombardier engine for load testing.
4+
`QAToolKit.Engine.Bombardier` is a .NET standard library, which takes `IList<HttpTestRequest>` object and runs load tests with tool called [Bombardier](https://github.com/codesenberg/bombardier).
55

6-
# License
6+
Library is a thin wrapper, that generates requests and parses results so you can export them to whatever format you prefer.
7+
8+
Major features:
9+
10+
- Library takes `IList<HttpTestRequest>` object, which can be produced in your code or can be imported from other sources. One example can be QAToolKit Swagger library that can produce that object with many options. Check it out [here](https://github.com/qatoolkit/qatoolkit-source-swagger-net).
11+
- Generate a Bombardier report that can be exported to the format you want.
12+
13+
## Sample
14+
15+
```csharp
16+
17+
//Instantiate a Bombardier test generator, by specifying Bombardier options
18+
var bombardierTestsGenerator = new BombardierTestsGenerator(options =>
19+
{
20+
//if your api endpoints you are testing are protected by Oauth2 access tokens
21+
options.AddOAuth2Token("eyJhbGciOiJSUzI1N....", AuthenticationType.Customer);
22+
//if your api endpoints are protected by simple "ApiKey: <apikey>" authentication header
23+
options.AddApiKey("myAPIKey123423456");
24+
//if your api endpoints are protected by basic authentication
25+
options.AddBasicAuthentication("username", "password");
26+
options.BombardierConcurrentUsers = 1;
27+
options.BombardierDuration = 1;
28+
options.BombardierTimeout = 30;
29+
options.BombardierUseHttp2 = true;
30+
});
31+
//Generate Bombardier Tests
32+
var bombardierTests = await bombardierTestsGenerator.Generate(requests);
33+
34+
//Run Bombardier Tests
35+
var bombardierTestsRunner = new BombardierTestsRunner(bombardierTests.ToList());
36+
var bombardierResults = await bombardierTestsRunner.Run();
37+
```
38+
39+
Results sample `bombardierResults`:
40+
41+
```json
42+
[
43+
{
44+
"TestStart": "2020-10-26T14:53:41.9558275+01:00",
45+
"TestStop": "2020-10-26T14:53:45.371211+01:00",
46+
"Duration": 3.4153835,
47+
"Command": "-m GET https://api.demo.com/v1/categories?parent=4 -c 1 -H \"Authorization: Bearer eyJhbGciOiJSUzI1N....\" --http2 --timeout=30s --duration=1s",
48+
"Counter1xx": 0,
49+
"Counter2xx": 48,
50+
"Counter3xx": 0,
51+
"Counter4xx": 0,
52+
"Counter5xx": 0,
53+
"AverageLatency": 63.58,
54+
"AverageRequestsPerSecond": 15.72,
55+
"MaxLatency": 215.50,
56+
"MaxRequestsPerSecond": 53.13,
57+
"StdevLatency": 25.83,
58+
"StdevRequestsPerSecond": 18.80
59+
},
60+
...
61+
]
62+
```
63+
64+
As you can see you get a lot of metrics from the tests.
65+
66+
## Description
67+
68+
#### 1. Authentication options
69+
70+
##### 1.1 AddOAuth2Token (prefered)
71+
Use `AddOAuth2Token` if your APIs are protected by Oauth2 Authentication. Pass Bearer token in the method along with the `AuthenticationType`.
72+
73+
##### 1.2 AddApiKey
74+
Use `AddApiKey` if your APIs are protected by simple API Key. Pass Api Key in the method. An `ApiKey` HTTP header will be generated with the value you provided. This is not best practice, but it's here if you need it.
75+
76+
##### 1.3 AddBasicAuthentication
77+
Use `AddBasicAuthentication` if your APIs are protected by basic authentication. Pass username and password in the method. A basic `Authentication` HTTP header will be generated. This is not best practice, but it's here if you need it.
78+
79+
#### 2. Bombardier parameters
80+
81+
You can set 5 Bombardier properties:
82+
83+
- `BombardierConcurrentUsers`: How many concurrent users should be used in Bombardier tests.
84+
- `BombardierDuration`: How long the Bombardier tests should execute in seconds. Use this depending on the type of test you want to perform and should not be used with `BombardierRateLimit`.
85+
- `BombardierTimeout`: What is the Bombardier timeout to wait for the requests to finish.
86+
- `BombardierUseHttp2`: Use HTTP2?
87+
- `BombardierRateLimit`: Rate limit Bombardier tests per second. Use this depending on the type of test you want to perform and should not be used with `BombardierDuration`.
88+
89+
## How to use
90+
91+
TO-DO
92+
93+
## TO-DO
94+
95+
- results obfuscation (authentication headers)
96+
97+
## License
798

899
MIT License
9100

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,55 @@
11
using QAToolKit.Core.Models;
2+
using System.Collections.Generic;
23

34
namespace QAToolKit.Engine.Bombardier
45
{
56
public class BombardierOptions
67
{
7-
internal string CustomerAccessToken { get; set; }
8-
internal string AdministratorAccessToken { get; private set; }
8+
internal Dictionary<AuthenticationType, string> AccessTokens { get; private set; } = new Dictionary<AuthenticationType, string>();
99
internal string ApiKey { get; private set; }
10+
internal string UserName { get; private set; }
11+
internal string Password { get; private set; }
1012
public int BombardierConcurrentUsers { get; set; }
1113
public int BombardierTimeout { get; set; }
1214
public int BombardierDuration { get; set; }
1315
public int BombardierRateLimit { get; set; }
1416
public bool BombardierUseHttp2 { get; set; }
17+
internal TestType TestType { get; } = TestType.LoadTest;
1518

16-
public BombardierOptions AddTokensAndApiKeys(string customerToken, string adminToken, string apiKey)
19+
/// <summary>
20+
/// Add Oauth2 token to the bombardier generator
21+
/// </summary>
22+
/// <param name="token"></param>
23+
/// <param name="authenticationType"></param>
24+
/// <returns></returns>
25+
public BombardierOptions AddOAuth2Token(string token, AuthenticationType authenticationType)
26+
{
27+
AccessTokens.Add(authenticationType, token);
28+
return this;
29+
}
30+
31+
/// <summary>
32+
/// Add api key to the bombardier generator
33+
/// </summary>
34+
/// <param name="apiKey"></param>
35+
/// <returns></returns>
36+
public BombardierOptions AddApiKey(string apiKey)
1737
{
18-
CustomerAccessToken = customerToken;
19-
AdministratorAccessToken = adminToken;
2038
ApiKey = apiKey;
2139
return this;
2240
}
41+
42+
/// <summary>
43+
/// Add basic authentication to Bombardier generator
44+
/// </summary>
45+
/// <param name="userName"></param>
46+
/// <param name="password"></param>
47+
/// <returns></returns>
48+
public BombardierOptions AddBasicAuthentication(string userName, string password)
49+
{
50+
UserName = userName;
51+
Password = password;
52+
return this;
53+
}
2354
}
2455
}

src/QAToolKit.Engine.Bombardier/BombardierResult.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
using QAToolKit.Core.Interfaces;
2+
using System;
23

34
namespace QAToolKit.Engine.Bombardier
45
{
56
public class BombardierResult : ILoadTestResult
67
{
8+
public DateTime TestStart { get; set; }
9+
public DateTime TestStop { get; set; }
10+
public double Duration { get; set; }
711
public string Command { get; set; }
812
public int Counter1xx { get; set; }
913
public int Counter2xx { get; set; }

src/QAToolKit.Engine.Bombardier/BombardierTestsGenerator.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
using QAToolKit.Core.Interfaces;
1+
using QAToolKit.Core.Helpers;
2+
using QAToolKit.Core.Interfaces;
23
using QAToolKit.Core.Models;
34
using QAToolKit.Engine.Bombardier.Helpers;
45
using System;
56
using System.Collections.Generic;
67
using System.IO;
8+
using System.Linq;
79
using System.Runtime.InteropServices;
810
using System.Text;
911
using System.Threading.Tasks;
@@ -40,10 +42,9 @@ public async Task<IEnumerable<BombardierTest>> Generate(IList<HttpTestRequest> r
4042
bombardierFullPath = Path.Combine("./bombardier", "linux", "bombardier");
4143
}
4244

43-
foreach (var request in restRequests)
45+
foreach (var request in restRequests.Where(request => request.TestTypes.Contains(_bombardierOptions.TestType)))
4446
{
45-
string authHeader = GeneratorHelper.GenerateAuthHeader(request, _bombardierOptions.CustomerAccessToken,
46-
_bombardierOptions.AdministratorAccessToken, _bombardierOptions.ApiKey);
47+
string authHeader = GeneratorHelper.GenerateAuthHeader(request, _bombardierOptions);
4748

4849
scriptBuilder.AppendLine($"{bombardierFullPath} " +
4950
$"-m {request.Method.ToString().ToUpper()} {GeneratorHelper.GenerateUrlParameters(request)} " +

src/QAToolKit.Engine.Bombardier/BombardierTestsRunner.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public async Task<IList<BombardierResult>> Run()
3333

3434
private async Task<BombardierResult> Run(string testCommand)
3535
{
36+
var testStart = DateTime.Now;
3637
var indexOfDelimiter = testCommand.IndexOf("-m");
3738
var bombardierExecutable = testCommand
3839
.Substring(0, indexOfDelimiter - 1);
@@ -66,7 +67,8 @@ private async Task<BombardierResult> Run(string testCommand)
6667
//Delete temp files
6768
DeleteBodyFile(bombardierArguments);
6869

69-
var parsedBombardierOutput = ParseOutput(bombardrierOutput, bombardierArguments);
70+
var testStop = DateTime.Now;
71+
var parsedBombardierOutput = ParseOutput(bombardrierOutput, bombardierArguments, testStart, testStop);
7072

7173
return parsedBombardierOutput;
7274
}
@@ -85,11 +87,16 @@ private void DeleteBodyFile(string arguments)
8587
catch { }
8688
}
8789

88-
private BombardierResult ParseOutput(StringBuilder sb, string command)
90+
private BombardierResult ParseOutput(StringBuilder sb, string command, DateTime testStart, DateTime testStop)
8991
{
9092
try
9193
{
92-
var results = new BombardierResult();
94+
var results = new BombardierResult
95+
{
96+
TestStart = testStart,
97+
TestStop = testStop,
98+
Duration = testStop.Subtract(testStart).TotalSeconds
99+
};
93100

94101
var str = sb.ToString();
95102

src/QAToolKit.Engine.Bombardier/Helpers/GeneratorHelper.cs

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.IO;
66
using System.Linq;
77
using System.Net.Http;
8+
using System.Text;
89
using System.Text.Json;
910

1011
namespace QAToolKit.Engine.Bombardier.Helpers
@@ -100,32 +101,86 @@ internal static string GenerateJsonBody(HttpTestRequest request)
100101
/// <param name="administratorAccessToken"></param>
101102
/// <param name="apiKey"></param>
102103
/// <returns></returns>
103-
internal static string GenerateAuthHeader(HttpTestRequest request, string customerAccessToken, string administratorAccessToken, string apiKey)
104+
internal static string GenerateAuthHeader(HttpTestRequest request, BombardierOptions bombardierOptions)
104105
{
105106
//Check if Swagger operation description contains certain auth tags
106107
string authHeader;
107108
if (request.Description.Contains(AuthenticationType.Oauth2.Value()))
108109
{
109110
if (request.Description.Contains(AuthenticationType.Customer.Value()) && !request.Description.Contains(AuthenticationType.Administrator.Value()))
110111
{
111-
authHeader = $"-H \"Authorization: Bearer {customerAccessToken}\" ";
112+
authHeader = GetOauth2AuthenticationHeader(bombardierOptions.AccessTokens, AuthenticationType.Customer);
112113
}
113114
else if (!request.Description.Contains(AuthenticationType.Customer.Value()) && request.Description.Contains(AuthenticationType.Administrator.Value()))
114115
{
115-
authHeader = $"-H \"Authorization: Bearer {administratorAccessToken}\" ";
116+
authHeader = GetOauth2AuthenticationHeader(bombardierOptions.AccessTokens, AuthenticationType.Administrator);
116117
}
117118
else
118119
{
119-
authHeader = $"-H \"Authorization: Bearer {customerAccessToken}\" ";
120+
authHeader = GetOauth2AuthenticationHeader(bombardierOptions.AccessTokens, AuthenticationType.Customer);
120121
}
121122
}
122123
else if (request.Description.Contains(AuthenticationType.ApiKey.Value()))
123124
{
124-
authHeader = $"-H \"ApiKey: {apiKey}\" ";
125+
authHeader = GetApiKeyAuthenticationHeader(bombardierOptions);
126+
}
127+
else if (request.Description.Contains(AuthenticationType.Basic.Value()))
128+
{
129+
authHeader = GetBasicAuthenticationHeader(bombardierOptions);
130+
}
131+
else
132+
{
133+
authHeader = String.Empty;
134+
}
135+
136+
return authHeader;
137+
}
138+
139+
internal static string GetOauth2AuthenticationHeader(Dictionary<AuthenticationType, string> accessTokens, AuthenticationType authenticationType)
140+
{
141+
string authHeader;
142+
if (accessTokens.ContainsKey(authenticationType))
143+
{
144+
accessTokens.TryGetValue(authenticationType, out var value);
145+
authHeader = $"-H \"Authorization: Bearer {value}\" ";
146+
}
147+
else
148+
{
149+
throw new Exception("One of the access token is missing.");
150+
}
151+
152+
return authHeader;
153+
}
154+
155+
internal static string GetBasicAuthenticationHeader(BombardierOptions bombardierOptions)
156+
{
157+
string authHeader;
158+
159+
if (string.IsNullOrEmpty(bombardierOptions.UserName) && string.IsNullOrEmpty(bombardierOptions.Password))
160+
{
161+
string credentials = Convert.ToBase64String(ASCIIEncoding.ASCII.GetBytes(bombardierOptions.UserName + ":" + bombardierOptions.Password));
162+
163+
authHeader = $"-H \"Authorization: Basic {credentials}\" ";
164+
}
165+
else
166+
{
167+
authHeader = String.Empty;
168+
}
169+
170+
return authHeader;
171+
}
172+
173+
internal static string GetApiKeyAuthenticationHeader(BombardierOptions bombardierOptions)
174+
{
175+
string authHeader;
176+
177+
if (string.IsNullOrEmpty(bombardierOptions.ApiKey))
178+
{
179+
authHeader = $"-H \"ApiKey: {bombardierOptions.ApiKey}\" ";
125180
}
126181
else
127182
{
128-
authHeader = "";
183+
authHeader = String.Empty;
129184
}
130185

131186
return authHeader;

0 commit comments

Comments
 (0)