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

Chore/k6 testing #349

Merged
merged 16 commits into from
Mar 7, 2024
Merged
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,17 @@ Formatting of the code base is handled by Dotnet format. [See how to configure i
## Deploy

The build and push workflow produces a docker image that is pushed to Github packages. This image is then used by the release action found in the [altinn-broker-infra repository](https://github.com/Altinn/altinn-broker-infra).


### Load testing with k6
Before running tests you should mock the following:
- AltinnAuthorization by setting the function CheckUserAccess to return true
- AltinnRegisterService to return a string
- AltinnResourceRegister to return a ResourceEntity
- Use the ConsoleLogEventBus

We run load tests using k6. To run without installing k6 you can use docker-compose(base url has to be http://host.docker.internal:5096):
```docker-compose -f docker-compose-test.yml up k6-test```

if you have k6 installed locally, you can run it by using the following command:
```"k6 run test.js"```
1 change: 1 addition & 0 deletions Test/Altinn.Broker.Tests/K6.Tests/data/testfile.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
this is a testfile
84 changes: 84 additions & 0 deletions Test/Altinn.Broker.Tests/K6.Tests/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import http from 'k6/http';
import { sleep, check, fail } from 'k6';

export const options = {
vus: 20,
duration: '10m',
iterations: 20

//remove this line before doing a real test
//httpDebug: 'full',
};

var tokens = {
DUMMY_SENDER_TOKEN: "",
DUMMY_SERVICE_OWNER_TOKEN: ""
}
var baseUrl = "http://localhost:5096"

const file = open("./data/testfile.txt", "b");
function checkResult(res, status) {
if (!status) {
console.error(status)
console.error(res)
}
}

export function setup() {
let headers = generateHeaders(tokens.DUMMY_SERVICE_OWNER_TOKEN, 'application/json')

//set fileTransfer TTL to 15 minutes. Should be longer than the test time
var fileRes = http.put(`${baseUrl}/broker/api/v1/serviceowner/fileretention`, JSON.stringify({
fileTransferTimeToLive: "PT15M"
}), { headers: headers });

if (
!check(fileRes, {
'status code MUST be 200 or 409': (fileRes) => fileRes.status === 200 || fileRes.status === 409,
})
) {
fail('Could not update file transfer TTL. Exiting');
}
}

export default async function () {
var baseFile = {
resourceId: 'altinn-broker-test-resource-1',
checksum: null,
fileName: 'testfile.txt',
recipients: ['0192:986252932'],
sender: '0192:991825827',
sendersFileTransferReference: 'test-data'
}

let headers = generateHeaders(tokens.DUMMY_SENDER_TOKEN, 'application/json')
var res = await http.asyncRequest('POST',
`${baseUrl}/broker/api/v1/filetransfer`,
JSON.stringify(baseFile), { headers: headers });
var status = check(res, { 'Initialize: status was 200': (r) => r.status == 200 });
sleep(1);
checkResult(res, status)

if (status) {
headers = generateHeaders(tokens.DUMMY_SENDER_TOKEN, 'application/octet-stream')
const data = {
field: 'this is a standard form field',
file: http.file(file, 'testfile.txt')
}
var res2 = await http.asyncRequest('POST',
`${baseUrl}/broker/api/v1/filetransfer/${res.body}/upload`, data, { timeout: "600s", headers: headers });
sleep(1);
status = check(res2, { 'Upload: status was 200': (r) => r.status == 200 });
checkResult(res, status)
}
}

function generateHeaders(token, contentType) {
return {
'Authorization': 'Bearer ' + token,
'Content-Type': contentType,
'Accept': '*/*, text/plain',
'Accept-Encoding': 'gzip, deflate, br',
'Connection': 'keep-alive'
}
}
9 changes: 9 additions & 0 deletions docker-compose-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
version: '3.4'

services:
k6-test:
image: grafana/k6:latest
command: run /test.js
volumes:
- ./Test/Altinn.Broker.Tests/K6.Tests/test.js:/test.js
- ./Test/Altinn.Broker.Tests/K6.Tests/data:/data
4 changes: 4 additions & 0 deletions src/Altinn.Broker.API/Controllers/ServiceOwnerController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ public async Task<ActionResult> UpdateFileRetention([FromBody] ServiceOwnerUpdat
}

var fileTimeToLive = XmlConvert.ToTimeSpan(serviceOwnerUpdateFileRetentionExt.FileTransferTimeToLive);
if (fileTimeToLive == serviceOwner.FileTransferTimeToLive)
{
return Problem(detail: "The file transfer already has the requested retention time", statusCode: (int)HttpStatusCode.Conflict);
}
await _serviceOwnerRepository.UpdateFileRetention(token.Consumer, fileTimeToLive);
await updateFileRetentionHandler.Process(new UpdateFileRetentionRequest
{
Expand Down
3 changes: 3 additions & 0 deletions src/Altinn.Broker.API/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ static void ConfigureServices(IServiceCollection services, IConfiguration config
services.Configure<KestrelServerOptions>(options =>
{
options.Limits.MaxRequestBodySize = null;
options.Limits.MaxRequestBufferSize = null;
options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(60);
options.Limits.MinRequestBodyDataRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
});
services.Configure<FormOptions>(options =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,15 @@ private async Task EnableMicrosoftDefender(string resourceGroupName, string stor
};
var json = JsonSerializer.Serialize(requestBody);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PutAsync(endpoint, content);
var response = await client.PutAsync(endpoint, content, cancellationToken);
if (!response.IsSuccessStatusCode)
{
var errorMessage = await response.Content.ReadAsStringAsync();
_logger.LogWarning("Failed to enable Defender Malware Scan. Error: {error}", errorMessage);
throw new HttpRequestException($"Failed to enable Defender Malware Scan. Error: {errorMessage}");
}
_logger.LogInformation($"Microsoft Defender Malware scan enabled for storage account {storageAccountName}");
client.Dispose();
}

private string GenerateStorageAccountName()
Expand Down
6 changes: 3 additions & 3 deletions src/Altinn.Broker.Persistence/Repositories/ActorRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ public ActorRepository(DatabaseConnectionProvider connectionProvider)

public async Task<ActorEntity?> GetActorAsync(string actorExternalId, CancellationToken cancellationToken)
{
using var command = await _connectionProvider.CreateCommand(
await using var command = await _connectionProvider.CreateCommand(
"SELECT actor_id_pk, actor_external_id FROM broker.actor WHERE actor_external_id = @actorExternalId");
command.Parameters.AddWithValue("@actorExternalId", actorExternalId);

using NpgsqlDataReader reader = await command.ExecuteReaderAsync(cancellationToken);
await using NpgsqlDataReader reader = await command.ExecuteReaderAsync(cancellationToken);
ActorEntity? actor = null;
while (await reader.ReadAsync(cancellationToken))
{
Expand All @@ -36,7 +36,7 @@ public ActorRepository(DatabaseConnectionProvider connectionProvider)

public async Task<long> AddActorAsync(ActorEntity actor, CancellationToken cancellationToken)
{
NpgsqlCommand command = await _connectionProvider.CreateCommand(
await using NpgsqlCommand command = await _connectionProvider.CreateCommand(
"INSERT INTO broker.actor (actor_external_id) " +
"VALUES (@actorExternalId) " +
"RETURNING actor_id_pk");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public FileTransferRepository(DatabaseConnectionProvider connectionProvider, IAc
{
var fileTransfer = new FileTransferEntity();

using var command = await _connectionProvider.CreateCommand(
await using var command = await _connectionProvider.CreateCommand(
@"
SELECT
f.file_transfer_id_pk,
Expand Down Expand Up @@ -73,7 +73,7 @@ GROUP BY
f.file_transfer_id_pk = @fileTransferId;");
{
command.Parameters.AddWithValue("@fileTransferId", fileTransferId);
using NpgsqlDataReader reader = await command.ExecuteReaderAsync(cancellationToken);
await using NpgsqlDataReader reader = await command.ExecuteReaderAsync(cancellationToken);
if (await reader.ReadAsync(cancellationToken))
{
fileTransfer = new FileTransferEntity
Expand Down Expand Up @@ -132,7 +132,7 @@ FROM broker.file_transfer
{
command.Parameters.AddWithValue("@fileTransferId", fileTransferId);
var commandText = command.CommandText;
using (var reader = await command.ExecuteReaderAsync(cancellationToken))
await using (var reader = await command.ExecuteReaderAsync(cancellationToken))
{
while (await reader.ReadAsync(cancellationToken))
{
Expand Down Expand Up @@ -174,7 +174,7 @@ public async Task<Guid> AddFileTransfer(ServiceOwnerEntity serviceOwner, Resourc
actorId = actor.ActorId;
}
var fileTransferId = Guid.NewGuid();
NpgsqlCommand command = await _connectionProvider.CreateCommand(
await using NpgsqlCommand command = await _connectionProvider.CreateCommand(
"INSERT INTO broker.file_transfer (file_transfer_id_pk, resource_id, filename, checksum, file_transfer_size, external_file_transfer_reference, sender_actor_id_fk, created, storage_provider_id_fk, expiration_time, hangfire_job_id) " +
"VALUES (@fileTransferId, @resourceId, @fileName, @checksum, @fileTransferSize, @externalFileTransferReference, @senderActorId, @created, @storageProviderId, @expirationTime, @hangfireJobId)");

Expand Down Expand Up @@ -261,7 +261,7 @@ public async Task<List<Guid>> LegacyGetFilesForRecipientsWithRecipientStatus(Leg
command.Parameters.AddWithValue("@recipientFileTransferStatus", (int)fileTransferSearch.RecipientStatus);

var fileTransfers = new List<Guid>();
using (var reader = await command.ExecuteReaderAsync(cancellationToken))
await using (var reader = await command.ExecuteReaderAsync(cancellationToken))
{
while (await reader.ReadAsync(cancellationToken))
{
Expand Down Expand Up @@ -338,7 +338,7 @@ public async Task<List<Guid>> GetFileTransfersAssociatedWithActor(FileTransferSe
command.Parameters.AddWithValue("@fileTransferStatus", (int)fileTransferSearch.Status);

var files = new List<Guid>();
using (var reader = await command.ExecuteReaderAsync(cancellationToken))
await using (var reader = await command.ExecuteReaderAsync(cancellationToken))
{
while (await reader.ReadAsync(cancellationToken))
{
Expand Down Expand Up @@ -390,7 +390,7 @@ public async Task<List<Guid>> GetFileTransfersForRecipientWithRecipientStatus(Fi
command.Parameters.AddWithValue("@recipientFileStatus", (int)fileTransferSearch.RecipientStatus);

var files = new List<Guid>();
using (var reader = await command.ExecuteReaderAsync(cancellationToken))
await using (var reader = await command.ExecuteReaderAsync(cancellationToken))
{
while (await reader.ReadAsync(cancellationToken))
{
Expand Down Expand Up @@ -431,7 +431,7 @@ private async Task<Dictionary<string, string>> GetMetadata(Guid fileTransferId,
{
command.Parameters.AddWithValue("@fileTransferId", fileTransferId);
var property = new Dictionary<string, string>();
using (var reader = await command.ExecuteReaderAsync(cancellationToken))
await using (var reader = await command.ExecuteReaderAsync(cancellationToken))
{
while (await reader.ReadAsync(cancellationToken))
{
Expand Down Expand Up @@ -536,7 +536,7 @@ LIMIT 1
{
command.Parameters.AddWithValue("@storageProviderId", storageProviderId);
command.Parameters.AddWithValue("@deletedStatusId", (int)FileTransferStatus.Deleted);
using (var reader = await command.ExecuteReaderAsync(cancellationToken))
await using (var reader = await command.ExecuteReaderAsync(cancellationToken))
{
while (await reader.ReadAsync(cancellationToken))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public FileTransferStatusRepository(DatabaseConnectionProvider connectionProvide

public async Task InsertFileTransferStatus(Guid fileTransferId, FileTransferStatus status, string? detailedFileTransferStatus = null, CancellationToken cancellationToken = default)
{
using var command = await _connectionProvider.CreateCommand(
await using var command = await _connectionProvider.CreateCommand(
"INSERT INTO broker.file_transfer_status (file_transfer_id_fk, file_transfer_status_description_id_fk, file_transfer_status_date, file_transfer_status_detailed_description) " +
"VALUES (@fileTransferId, @statusId, NOW(), @detailedFileTransferStatus) RETURNING file_transfer_status_id_pk;");
command.Parameters.AddWithValue("@fileTransferId", fileTransferId);
Expand All @@ -30,14 +30,14 @@ public async Task InsertFileTransferStatus(Guid fileTransferId, FileTransferStat

public async Task<List<FileTransferStatusEntity>> GetFileTransferStatusHistory(Guid fileTransferId, CancellationToken cancellationToken)
{
using (var command = await _connectionProvider.CreateCommand(
await using (var command = await _connectionProvider.CreateCommand(
"SELECT file_transfer_id_fk, file_transfer_status_description_id_fk, file_transfer_status_date, file_transfer_status_detailed_description " +
"FROM broker.file_transfer_status fis " +
"WHERE fis.file_transfer_id_fk = @fileTransferId"))
{
command.Parameters.AddWithValue("@fileTransferId", fileTransferId);
var fileTransferStatuses = new List<FileTransferStatusEntity>();
using (var reader = await command.ExecuteReaderAsync(cancellationToken))
await using (var reader = await command.ExecuteReaderAsync(cancellationToken))
{
while (await reader.ReadAsync(cancellationToken))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public IdempotencyEventRepository(DatabaseConnectionProvider connectionProvider)

public async Task AddIdempotencyEventAsync(string IdempotencyEventId, CancellationToken cancellationToken)
{
NpgsqlCommand command = await _connectionProvider.CreateCommand(
await using NpgsqlCommand command = await _connectionProvider.CreateCommand(
"INSERT INTO broker.idempotency_event (idempotency_event_id_pk, created)" +
"VALUES (@idempotency_event_id_pk, @created) ");
command.Parameters.AddWithValue("@idempotency_event_id_pk", IdempotencyEventId);
Expand All @@ -26,7 +26,7 @@ public async Task AddIdempotencyEventAsync(string IdempotencyEventId, Cancellati
}
public async Task DeleteIdempotencyEventAsync(string IdempotencyEventId, CancellationToken cancellationToken)
{
NpgsqlCommand command = await _connectionProvider.CreateCommand(
await using NpgsqlCommand command = await _connectionProvider.CreateCommand(
"DELETE FROM broker.idempotency_event " +
"WHERE idempotency_event_id_pk = @idempotency_event_id_pk");
command.Parameters.AddWithValue("@idempotency_event_id_pk", IdempotencyEventId);
Expand All @@ -35,7 +35,7 @@ public async Task DeleteIdempotencyEventAsync(string IdempotencyEventId, Cancell
}
public async Task DeleteOldIdempotencyEvents()
{
NpgsqlCommand command = await _connectionProvider.CreateCommand(
await using NpgsqlCommand command = await _connectionProvider.CreateCommand(
"DELETE FROM broker.idempotency_event " +
"WHERE created < @created");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public ServiceOwnerRepository(DatabaseConnectionProvider connectionProvider)

public async Task<ServiceOwnerEntity?> GetServiceOwner(string serviceOwnerId)
{
using var command = await _connectionProvider.CreateCommand(
await using var command = await _connectionProvider.CreateCommand(
"SELECT service_owner_id_pk, service_owner_name, file_transfer_time_to_live, " +
"storage_provider_id_pk, created, resource_name, storage_provider_type " +
"FROM broker.service_owner " +
Expand Down
Loading