mirror of
https://github.com/NecroticBamboo/QRBee.git
synced 2025-12-21 12:11:53 +00:00
DEEP-26 Load Generator added.
This commit is contained in:
parent
5d7223e801
commit
dc67d69bd2
32
QRBee.Load.Generator/ClientPool.cs
Normal file
32
QRBee.Load.Generator/ClientPool.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
using QRBee.Core.Client;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
|
namespace QRBee.Load.Generator;
|
||||||
|
|
||||||
|
public class ClientPool
|
||||||
|
{
|
||||||
|
private readonly ConcurrentDictionary<int, ClientSettings> _clientPool = new();
|
||||||
|
private readonly ConcurrentDictionary<int, ClientSettings> _merchantPool = new();
|
||||||
|
private readonly SecurityServiceFactory _securityServiceFactory;
|
||||||
|
private readonly Client _client;
|
||||||
|
|
||||||
|
public ClientPool(SecurityServiceFactory securityServiceFactory, QRBee.Core.Client.Client client)
|
||||||
|
{
|
||||||
|
_securityServiceFactory = securityServiceFactory;
|
||||||
|
this._client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ClientSettings> GetMerchant(int no)
|
||||||
|
{
|
||||||
|
var merchant = _merchantPool.GetOrAdd(no+100_000_000, x => new ClientSettings(_securityServiceFactory, no, true));
|
||||||
|
await merchant.InitialSetup(_client);
|
||||||
|
return merchant;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ClientSettings> GetClient(int no)
|
||||||
|
{
|
||||||
|
var customer = _clientPool.GetOrAdd(no, x => new ClientSettings(_securityServiceFactory, no, false));
|
||||||
|
await customer.InitialSetup(_client);
|
||||||
|
return customer;
|
||||||
|
}
|
||||||
|
}
|
||||||
68
QRBee.Load.Generator/ClientSettings.cs
Normal file
68
QRBee.Load.Generator/ClientSettings.cs
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
using QRBee.Core.Data;
|
||||||
|
using QRBee.Core.Security;
|
||||||
|
|
||||||
|
namespace QRBee.Load.Generator;
|
||||||
|
|
||||||
|
public class ClientSettings
|
||||||
|
{
|
||||||
|
private ISecurityService _securityService;
|
||||||
|
|
||||||
|
public ClientSettings(SecurityServiceFactory securityServiceFactory, int no, bool isMerchant)
|
||||||
|
{
|
||||||
|
Id = no;
|
||||||
|
IsMerchant = isMerchant;
|
||||||
|
CardNumber = IsMerchant ? "": $"123400000000{no:0000}";
|
||||||
|
CardHolderName = IsMerchant ? $"Merchant {no}" : $"Mr {no}";
|
||||||
|
CVC = IsMerchant ? "" : $"{no:000}";
|
||||||
|
ExpirationDate = IsMerchant ? "" : (DateTime.Now.Date + TimeSpan.FromDays(364)).ToString("yyyy-MM");
|
||||||
|
ValidFrom = IsMerchant ? "" : (DateTime.Now.Date - TimeSpan.FromDays(7)).ToString("yyyy-MM");
|
||||||
|
Email = IsMerchant ? $"{no}@merchant.org" : $"{no}@client.org";
|
||||||
|
|
||||||
|
_securityService = securityServiceFactory(no);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Id { get; }
|
||||||
|
public string? ClientId { get; private set; }
|
||||||
|
public string CardNumber { get; }
|
||||||
|
public string CardHolderName { get; }
|
||||||
|
public string CVC { get; }
|
||||||
|
public int? IssueNo { get; }
|
||||||
|
public string? ExpirationDate { get; }
|
||||||
|
public string? ValidFrom { get; }
|
||||||
|
|
||||||
|
public bool IsMerchant { get; }
|
||||||
|
public string Email { get; }
|
||||||
|
public ISecurityService SecurityService { get => _securityService; }
|
||||||
|
|
||||||
|
public async Task InitialSetup(QRBee.Core.Client.Client client)
|
||||||
|
{
|
||||||
|
var idFileName = Environment.ExpandEnvironmentVariables($"%TEMP%/!QRBee/QRBee-{Id:8X}.txt");
|
||||||
|
|
||||||
|
if (File.Exists(idFileName))
|
||||||
|
{
|
||||||
|
var l = File.ReadAllLines(idFileName);
|
||||||
|
if (l != null && l.Length > 0)
|
||||||
|
ClientId = l[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(ClientId) && SecurityService.PrivateKeyHandler.Exists())
|
||||||
|
return;
|
||||||
|
|
||||||
|
var request = new RegistrationRequest
|
||||||
|
{
|
||||||
|
Name = CardHolderName,
|
||||||
|
Email = Email,
|
||||||
|
DateOfBirth = "2000-01-01",
|
||||||
|
RegisterAsMerchant = IsMerchant,
|
||||||
|
CertificateRequest = SecurityService.PrivateKeyHandler.CreateCertificateRequest(Email)
|
||||||
|
};
|
||||||
|
|
||||||
|
var resp = await client.RegisterAsync(request);
|
||||||
|
_securityService.APIServerCertificate = _securityService.Deserialize(resp.APIServerCertificate);
|
||||||
|
|
||||||
|
ClientId = resp.ClientId;
|
||||||
|
File.WriteAllText(idFileName, ClientId);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
21
QRBee.Load.Generator/GeneratorSettings.cs
Normal file
21
QRBee.Load.Generator/GeneratorSettings.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
namespace QRBee.Load.Generator;
|
||||||
|
|
||||||
|
internal class Anomaly
|
||||||
|
{
|
||||||
|
public double Probability { get; set; }
|
||||||
|
public Dictionary<string,string> Parameters { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class GeneratorSettings
|
||||||
|
{
|
||||||
|
public int NumberOfClients { get; set; } = 100;
|
||||||
|
public int NumberOfMerchants { get; set; } = 10;
|
||||||
|
public int NumberOfThreads { get; set; } = 20;
|
||||||
|
public int DelayBetweenMessagesMSec { get; set; } = 100;
|
||||||
|
public int DelayJitterMSec { get; set; } = 50;
|
||||||
|
public double MinAmount { get; set; } = 10;
|
||||||
|
public double MaxAmount { get; set; } = 100;
|
||||||
|
|
||||||
|
public Anomaly LoadSpike { get; set; } = new();
|
||||||
|
public Anomaly LargeAmount { get; set; } = new();
|
||||||
|
}
|
||||||
273
QRBee.Load.Generator/LoadGenerator.cs
Normal file
273
QRBee.Load.Generator/LoadGenerator.cs
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
// See https://aka.ms/new-console-template for more information
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using QRBee.Core.Client;
|
||||||
|
using QRBee.Core.Data;
|
||||||
|
using QRBee.Load.Generator;
|
||||||
|
|
||||||
|
internal class LoadGenerator : IHostedService
|
||||||
|
{
|
||||||
|
private readonly Client _client;
|
||||||
|
private readonly ClientPool _clientPool;
|
||||||
|
private readonly PaymentRequestGenerator _paymentRequestGenerator;
|
||||||
|
private readonly ILogger<LoadGenerator> _logger;
|
||||||
|
private readonly IOptions<GeneratorSettings> _settings;
|
||||||
|
|
||||||
|
public LoadGenerator(
|
||||||
|
QRBee.Core.Client.Client client,
|
||||||
|
ClientPool clientPool,
|
||||||
|
PaymentRequestGenerator paymentRequestGenerator,
|
||||||
|
ILogger<LoadGenerator> logger,
|
||||||
|
IOptions<GeneratorSettings> settings
|
||||||
|
)
|
||||||
|
{
|
||||||
|
_client = client;
|
||||||
|
_clientPool = clientPool;
|
||||||
|
_paymentRequestGenerator = paymentRequestGenerator;
|
||||||
|
_logger = logger;
|
||||||
|
_settings = settings;
|
||||||
|
}
|
||||||
|
public async Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await InitClients();
|
||||||
|
_ = Task.Run(ReportingThread);
|
||||||
|
_ = Task.Run(ConfirmationThread);
|
||||||
|
_ = Task.Run(ReceivingThread);
|
||||||
|
_ = Task.Run(GenerateLoad);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task InitClients()
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"Initializing {_settings.Value.NumberOfClients} clients...");
|
||||||
|
for ( var i = 1; i < _settings.Value.NumberOfClients+1; i++ )
|
||||||
|
{
|
||||||
|
await _clientPool.GetClient(i);
|
||||||
|
}
|
||||||
|
_logger.LogInformation($"Initializing {_settings.Value.NumberOfMerchants} merchants...");
|
||||||
|
for (var i = 1; i < _settings.Value.NumberOfMerchants + 1; i++)
|
||||||
|
{
|
||||||
|
await _clientPool.GetMerchant(i);
|
||||||
|
}
|
||||||
|
_logger.LogInformation($"=== Initialization complete ===");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ReportingThread()
|
||||||
|
{
|
||||||
|
DateTime nextReport = DateTime.MinValue;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (DateTime.Now > nextReport)
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"S: {_paymentsGenerated,10:N0} R: {_paymentsProcessed,10:N0} C: {_paymentsConfirmed,10:N0} F: {_paymentsFailed,10:N0}");
|
||||||
|
nextReport = DateTime.Now + TimeSpan.FromSeconds(1);
|
||||||
|
}
|
||||||
|
await Task.Delay(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task GenerateLoad()
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Generator started");
|
||||||
|
|
||||||
|
var threadTasks = Enumerable.Range(0, _settings.Value.NumberOfThreads)
|
||||||
|
.Select(_ => GenerationThread())
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
await Task.WhenAll(threadTasks);
|
||||||
|
|
||||||
|
_logger.LogInformation("Generator finished");
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Task<PaymentResponse>> _responseQueue = new();
|
||||||
|
private List<Task> _confirmationQueue = new();
|
||||||
|
private ThreadSafeRandom _rng = new();
|
||||||
|
private object _lock = new();
|
||||||
|
|
||||||
|
private long _paymentsGenerated;
|
||||||
|
private long _paymentsProcessed;
|
||||||
|
private long _paymentsConfirmed;
|
||||||
|
private long _paymentsFailed;
|
||||||
|
|
||||||
|
private async Task ConfirmationThread()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var newQueue = new List<Task>();
|
||||||
|
|
||||||
|
lock (_lock)
|
||||||
|
newQueue = Interlocked.Exchange(ref _confirmationQueue, newQueue);
|
||||||
|
|
||||||
|
if (newQueue.Count == 0)
|
||||||
|
{
|
||||||
|
await Task.Delay( _rng.NextInRange(300, 600));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tasks = newQueue.ToList();
|
||||||
|
|
||||||
|
while (tasks.Any(x => !x.IsCompleted))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var t = await Task.WhenAny(tasks);
|
||||||
|
tasks.Remove(t);
|
||||||
|
|
||||||
|
Interlocked.Increment(ref _paymentsConfirmed);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref _paymentsFailed);
|
||||||
|
_logger.LogError(ex, "Confirmation thread");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Receining thread");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private async Task ReceivingThread()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var newQueue = new List<Task<PaymentResponse>>();
|
||||||
|
|
||||||
|
lock (_lock)
|
||||||
|
newQueue = Interlocked.Exchange(ref _responseQueue, newQueue);
|
||||||
|
|
||||||
|
if (newQueue.Count == 0)
|
||||||
|
{
|
||||||
|
await Task.Delay(_rng.NextInRange(300, 600));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tasks = newQueue.ToList();
|
||||||
|
|
||||||
|
while ( tasks.Any() )
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var t = await Task.WhenAny(tasks);
|
||||||
|
tasks.Remove(t);
|
||||||
|
|
||||||
|
var res = await t;
|
||||||
|
|
||||||
|
Interlocked.Increment(ref _paymentsProcessed);
|
||||||
|
|
||||||
|
if (res?.Success ?? false)
|
||||||
|
{
|
||||||
|
var paymentConfirmation = new PaymentConfirmation
|
||||||
|
{
|
||||||
|
MerchantId = res.PaymentRequest.ClientResponse.MerchantRequest.MerchantId,
|
||||||
|
MerchantTransactionId = res.PaymentRequest.ClientResponse.MerchantRequest.MerchantTransactionId,
|
||||||
|
GatewayTransactionId = res.GatewayTransactionId
|
||||||
|
};
|
||||||
|
|
||||||
|
var confirmationTask = _client.ConfirmPayAsync(paymentConfirmation);
|
||||||
|
_confirmationQueue.Add(confirmationTask);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Interlocked.Increment(ref _paymentsFailed);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref _paymentsFailed);
|
||||||
|
_logger.LogError(ex, "Receining thread (confirmation)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch ( Exception ex )
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Receining thread");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private async Task GenerationThread()
|
||||||
|
{
|
||||||
|
|
||||||
|
// initial delay
|
||||||
|
await Task.Delay(500 + _rng.Next() % 124);
|
||||||
|
|
||||||
|
var spikeEnd = DateTime.MinValue;
|
||||||
|
var loadSpike = _settings.Value.LoadSpike;
|
||||||
|
var spikeDuration = TimeSpan.Zero;
|
||||||
|
var spikeDelay = TimeSpan.Zero;
|
||||||
|
var spikeProbability = loadSpike?.Probability ?? 0.0;
|
||||||
|
|
||||||
|
if ( loadSpike != null && loadSpike.Probability > 0.0 )
|
||||||
|
{
|
||||||
|
if ( !loadSpike.Parameters.TryGetValue("Duration", out var duration)
|
||||||
|
|| !TimeSpan.TryParse( duration, out spikeDuration ) )
|
||||||
|
{
|
||||||
|
spikeProbability = 0.0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!loadSpike.Parameters.TryGetValue("Delay", out duration)
|
||||||
|
|| !TimeSpan.TryParse(duration, out spikeDelay))
|
||||||
|
{
|
||||||
|
spikeDelay = TimeSpan.FromMilliseconds(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var req = await _paymentRequestGenerator.GeneratePaymentRequest(
|
||||||
|
_rng.NextInRange(1, _settings.Value.NumberOfClients + 1),
|
||||||
|
_rng.NextInRange(1, _settings.Value.NumberOfMerchants + 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
var resp = _client.PayAsync(req);
|
||||||
|
|
||||||
|
_responseQueue.Add(resp);
|
||||||
|
Interlocked.Increment(ref _paymentsGenerated);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref _paymentsFailed);
|
||||||
|
_logger.LogError(ex, "Generation thread");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DateTime.Now > spikeEnd)
|
||||||
|
{
|
||||||
|
if (loadSpike != null && _rng.NextDouble() < spikeProbability)
|
||||||
|
{
|
||||||
|
// start load spike
|
||||||
|
spikeEnd = DateTime.Now + spikeDuration;
|
||||||
|
_logger.LogWarning($"Anomaly: Load spike until {spikeEnd}");
|
||||||
|
await Task.Delay(spikeDuration);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Task.Delay(_rng.NextInRange(
|
||||||
|
_settings.Value.DelayBetweenMessagesMSec,
|
||||||
|
_settings.Value.DelayBetweenMessagesMSec + _settings.Value.DelayJitterMSec
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Task.Delay(spikeDuration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
111
QRBee.Load.Generator/PaymentRequestGenerator.cs
Normal file
111
QRBee.Load.Generator/PaymentRequestGenerator.cs
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using QRBee.Core.Data;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace QRBee.Load.Generator;
|
||||||
|
|
||||||
|
internal class PaymentRequestGenerator
|
||||||
|
{
|
||||||
|
private readonly ClientPool _clientPool;
|
||||||
|
private readonly ILogger<PaymentRequestGenerator> _logger;
|
||||||
|
private ThreadSafeRandom _rng = new ThreadSafeRandom();
|
||||||
|
private double _minAmount = 1;
|
||||||
|
private double _maxAmount = 100;
|
||||||
|
private double _largeAmountProbability;
|
||||||
|
private double _largeAmountValue;
|
||||||
|
|
||||||
|
public PaymentRequestGenerator(ClientPool clientPool, IOptions<GeneratorSettings> settings, ILogger<PaymentRequestGenerator> logger)
|
||||||
|
{
|
||||||
|
_clientPool = clientPool;
|
||||||
|
_logger = logger;
|
||||||
|
_minAmount = settings.Value.MinAmount;
|
||||||
|
_maxAmount = settings.Value.MaxAmount;
|
||||||
|
|
||||||
|
var largeAmount = settings.Value.LargeAmount;
|
||||||
|
_largeAmountProbability = largeAmount.Probability;
|
||||||
|
if (_largeAmountProbability > 0)
|
||||||
|
{
|
||||||
|
if ( largeAmount.Parameters.TryGetValue("Value", out var s))
|
||||||
|
_largeAmountValue = Double.Parse(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_largeAmountValue <= 0.0)
|
||||||
|
_largeAmountProbability = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PaymentRequest> GeneratePaymentRequest(int clientId, int merchantId)
|
||||||
|
{
|
||||||
|
var merchant = await GetMerchant(merchantId);
|
||||||
|
var merchantReq = new MerchantToClientRequest()
|
||||||
|
{
|
||||||
|
MerchantId = merchant.ClientId,
|
||||||
|
MerchantTransactionId = Guid.NewGuid().ToString(),
|
||||||
|
Name = merchant.CardHolderName,
|
||||||
|
Amount = GetAmount(),
|
||||||
|
TimeStampUTC = DateTime.UtcNow
|
||||||
|
};
|
||||||
|
|
||||||
|
var merchantSignature = merchant.SecurityService.Sign(Encoding.UTF8.GetBytes(merchantReq.AsDataForSignature()));
|
||||||
|
merchantReq.MerchantSignature = Convert.ToBase64String(merchantSignature);
|
||||||
|
|
||||||
|
var clientResp = await CreateClientResponse(merchantReq, clientId);
|
||||||
|
|
||||||
|
var req = new PaymentRequest()
|
||||||
|
{
|
||||||
|
ClientResponse = clientResp
|
||||||
|
};
|
||||||
|
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<ClientToMerchantResponse> CreateClientResponse(MerchantToClientRequest merchantReq, int clientId)
|
||||||
|
{
|
||||||
|
var client = await GetClient(clientId);
|
||||||
|
|
||||||
|
var response = new ClientToMerchantResponse
|
||||||
|
{
|
||||||
|
ClientId = client.ClientId,
|
||||||
|
TimeStampUTC = DateTime.UtcNow,
|
||||||
|
MerchantRequest = merchantReq,
|
||||||
|
EncryptedClientCardData = EncryptCardData(client, merchantReq.MerchantTransactionId)
|
||||||
|
};
|
||||||
|
|
||||||
|
var clientSignature = client.SecurityService.Sign(Encoding.UTF8.GetBytes(response.AsDataForSignature()));
|
||||||
|
response.ClientSignature = Convert.ToBase64String(clientSignature);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private decimal GetAmount()
|
||||||
|
{
|
||||||
|
if (_rng.NextDouble() < _largeAmountProbability)
|
||||||
|
{
|
||||||
|
_logger.LogWarning($"Anomaly: Large amount");
|
||||||
|
return Convert.ToDecimal(_rng.NextDoubleInRange(_largeAmountValue, _largeAmountValue* 1.10));
|
||||||
|
}
|
||||||
|
return Convert.ToDecimal(_rng.NextDoubleInRange(_minAmount, _maxAmount));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task<ClientSettings> GetMerchant(int id) => _clientPool.GetMerchant(id);
|
||||||
|
|
||||||
|
private Task<ClientSettings> GetClient(int id) => _clientPool.GetClient(id);
|
||||||
|
|
||||||
|
private string EncryptCardData(ClientSettings settings, string transactionId)
|
||||||
|
{
|
||||||
|
var clientCardData = new ClientCardData
|
||||||
|
{
|
||||||
|
TransactionId = transactionId,
|
||||||
|
CardNumber = settings.CardNumber,
|
||||||
|
ExpirationDateYYYYMM = string.IsNullOrWhiteSpace(settings.ExpirationDate) ? null : DateTime.Parse(settings.ExpirationDate).ToString("yyyy-MM"),
|
||||||
|
ValidFromYYYYMM = string.IsNullOrWhiteSpace(settings.ValidFrom) ? null : DateTime.Parse(settings.ValidFrom).ToString("yyyy-MM"),
|
||||||
|
CardHolderName = settings.CardHolderName,
|
||||||
|
CVC = settings.CVC,
|
||||||
|
IssueNo = settings.IssueNo
|
||||||
|
};
|
||||||
|
|
||||||
|
var bytes = settings.SecurityService.Encrypt(Encoding.UTF8.GetBytes(clientCardData.AsString()), settings.SecurityService.APIServerCertificate);
|
||||||
|
return Convert.ToBase64String(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
17
QRBee.Load.Generator/PrivateKeyHandler.cs
Normal file
17
QRBee.Load.Generator/PrivateKeyHandler.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using QRBee.Api.Services;
|
||||||
|
using QRBee.Core.Security;
|
||||||
|
|
||||||
|
namespace QRBee.Load.Generator;
|
||||||
|
|
||||||
|
public delegate IPrivateKeyHandler PrivateKeyHandlerFactory(int keyNo);
|
||||||
|
public delegate ISecurityService SecurityServiceFactory(int keyNo);
|
||||||
|
|
||||||
|
internal class PrivateKeyHandler : ServerPrivateKeyHandler
|
||||||
|
{
|
||||||
|
public PrivateKeyHandler(ILogger<ServerPrivateKeyHandler> logger, IConfiguration config, int keyNo) : base(logger, config)
|
||||||
|
{
|
||||||
|
PrivateKeyFileName = Environment.ExpandEnvironmentVariables($"%TEMP%/!QRBee/QRBee-{keyNo:8X}.key");
|
||||||
|
}
|
||||||
|
}
|
||||||
42
QRBee.Load.Generator/Program.cs
Normal file
42
QRBee.Load.Generator/Program.cs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// See https://aka.ms/new-console-template for more information
|
||||||
|
using log4net;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using QRBee.Api.Services;
|
||||||
|
using QRBee.Droid.Services;
|
||||||
|
using QRBee.Load.Generator;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
|
||||||
|
Console.WriteLine("=== QRBee artificaial load generator ===");
|
||||||
|
|
||||||
|
var builder = Host.CreateDefaultBuilder();
|
||||||
|
|
||||||
|
builder.ConfigureServices((context, services) =>
|
||||||
|
{
|
||||||
|
services.AddLogging(logging =>
|
||||||
|
{
|
||||||
|
logging.ClearProviders();
|
||||||
|
GlobalContext.Properties["LOGS_ROOT"] = Environment.GetEnvironmentVariable("LOGS_ROOT") ?? "";
|
||||||
|
System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
|
||||||
|
logging.AddLog4Net("log4net.config");
|
||||||
|
});
|
||||||
|
|
||||||
|
services
|
||||||
|
.AddHttpClient<QRBee.Core.Client.Client, QRBee.Core.Client.Client>(httpClient => new QRBee.Core.Client.Client("https://localhost:7000/", httpClient))
|
||||||
|
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler() { ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator });
|
||||||
|
;
|
||||||
|
services
|
||||||
|
.Configure<GeneratorSettings>(context.Configuration.GetSection("GeneratorSettings"))
|
||||||
|
.AddSingleton<ClientPool>()
|
||||||
|
.AddSingleton<PaymentRequestGenerator>()
|
||||||
|
.AddSingleton<PrivateKeyHandlerFactory>(x => no => new PrivateKeyHandler(x.GetRequiredService<ILogger<ServerPrivateKeyHandler>>(), x.GetRequiredService<IConfiguration>(), no))
|
||||||
|
.AddSingleton<SecurityServiceFactory>(x => no => new AndroidSecurityService(x.GetRequiredService<PrivateKeyHandlerFactory>()(no)))
|
||||||
|
.AddHostedService<LoadGenerator>()
|
||||||
|
;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var host = builder.Build();
|
||||||
|
host.Run();
|
||||||
61
QRBee.Load.Generator/QRBee.Load.Generator.csproj
Normal file
61
QRBee.Load.Generator/QRBee.Load.Generator.csproj
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="appsettings.Development.json" />
|
||||||
|
<None Remove="appsettings.json" />
|
||||||
|
<None Remove="log4net.config" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="log4net" Version="2.0.15" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.1" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="7.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Log4Net.AspNetCore" Version="6.1.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\QRBee.Core\QRBee.Core.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="..\QRBee\QRBee.Android\Services\AndroidSecurityService.cs" />
|
||||||
|
<Compile Include="..\QRBeeApi\Services\ServerPrivateKeyHandler.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="appsettings.Development.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||||
|
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||||
|
</Content>
|
||||||
|
<Content Include="appsettings.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||||
|
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||||
|
</Content>
|
||||||
|
<Content Include="log4net.config">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||||
|
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="private_key.p12">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
47
QRBee.Load.Generator/ThreadSafeRandom.cs
Normal file
47
QRBee.Load.Generator/ThreadSafeRandom.cs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
namespace QRBee.Load.Generator;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// https://stackoverflow.com/questions/3049467/is-c-sharp-random-number-generator-thread-safe
|
||||||
|
/// </summary>
|
||||||
|
public class ThreadSafeRandom
|
||||||
|
{
|
||||||
|
private static readonly Random _global = new Random();
|
||||||
|
[ThreadStatic] private static Random? _local;
|
||||||
|
|
||||||
|
public int Next()
|
||||||
|
{
|
||||||
|
Init();
|
||||||
|
return _local!.Next();
|
||||||
|
}
|
||||||
|
|
||||||
|
public double NextDouble()
|
||||||
|
{
|
||||||
|
Init();
|
||||||
|
return _local!.NextDouble();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int NextInRange(int start, int end)
|
||||||
|
{
|
||||||
|
var n = Next();
|
||||||
|
return start + n % (end - start);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double NextDoubleInRange(double min, double max)
|
||||||
|
{
|
||||||
|
var n = NextDouble();
|
||||||
|
return min + (max - min) * n;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Init()
|
||||||
|
{
|
||||||
|
if (_local == null)
|
||||||
|
{
|
||||||
|
int seed;
|
||||||
|
lock (_global)
|
||||||
|
{
|
||||||
|
seed = _global.Next();
|
||||||
|
}
|
||||||
|
_local = new Random(seed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
QRBee.Load.Generator/appsettings.Development.json
Normal file
8
QRBee.Load.Generator/appsettings.Development.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
38
QRBee.Load.Generator/appsettings.json
Normal file
38
QRBee.Load.Generator/appsettings.json
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"PrivateKey" : {
|
||||||
|
"FileName": "private_key.p12",
|
||||||
|
"Password": ""
|
||||||
|
},
|
||||||
|
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Trace",
|
||||||
|
"Microsoft.AspNetCore": "Information"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"GeneratorSettings": {
|
||||||
|
"NumberOfClients": 100,
|
||||||
|
"NumberOfMerchants": 10,
|
||||||
|
"NumberOfThreads": 20,
|
||||||
|
"DelayBetweenMessagesMSec": 100,
|
||||||
|
"DelayJitterMSec": 50,
|
||||||
|
"MinAmount": 10,
|
||||||
|
"MaxAmount": 100,
|
||||||
|
|
||||||
|
"LoadSpike": {
|
||||||
|
"Probability": 0.001,
|
||||||
|
"Parameters": {
|
||||||
|
"Duration": "00:00:05",
|
||||||
|
"Delay": "00:00:00.0100000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"LargeAmount": {
|
||||||
|
"Probability": 0.03,
|
||||||
|
"Parameters": {
|
||||||
|
"Valie": "1_000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
55
QRBee.Load.Generator/log4net.config
Normal file
55
QRBee.Load.Generator/log4net.config
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<log4net>
|
||||||
|
<appender name="DebugAppender" type="log4net.Appender.DebugAppender" >
|
||||||
|
<layout type="log4net.Layout.PatternLayout">
|
||||||
|
<conversionPattern value="%date [%-2thread] %-5level %logger - %message%newline" />
|
||||||
|
</layout>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<appender name="Console" type="log4net.Appender.ConsoleAppender">
|
||||||
|
<layout type="log4net.Layout.PatternLayout">
|
||||||
|
<conversionPattern value="%date [%-2thread] %-5level %-20logger - %message%newline" />
|
||||||
|
</layout>
|
||||||
|
</appender>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!--Console appender-->
|
||||||
|
<appender name="Console" type="log4net.Appender.ManagedColoredConsoleAppender">
|
||||||
|
<mapping>
|
||||||
|
<level value="INFO" />
|
||||||
|
<forecolor value="Green" />
|
||||||
|
</mapping>
|
||||||
|
<mapping>
|
||||||
|
<level value="WARN" />
|
||||||
|
<forecolor value="Yellow" />
|
||||||
|
</mapping>
|
||||||
|
<mapping>
|
||||||
|
<level value="ERROR" />
|
||||||
|
<forecolor value="Red" />
|
||||||
|
</mapping>
|
||||||
|
<mapping>
|
||||||
|
<level value="DEBUG" />
|
||||||
|
<forecolor value="Blue" />
|
||||||
|
</mapping>
|
||||||
|
<layout type="log4net.Layout.PatternLayout">
|
||||||
|
<conversionPattern value="%date{HH:mm:ss,fff} [%-2thread] %-5level %-15logger{1} %message%newline" />
|
||||||
|
</layout>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<appender name="BufferingForwardingAppender" type="log4net.Appender.BufferingForwardingAppender" >
|
||||||
|
<bufferSize value="1"/>
|
||||||
|
<appender-ref ref="DebugAppender" />
|
||||||
|
<appender-ref ref="Console" />
|
||||||
|
<!-- <appender-ref ref="RollingFile" /> -->
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<logger name="System.Net.Http">
|
||||||
|
<level value="ERROR"/>
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<root>
|
||||||
|
<level value="ALL"/>
|
||||||
|
<appender-ref ref="BufferingForwardingAppender" />
|
||||||
|
</root>
|
||||||
|
</log4net>
|
||||||
14
QRBee.sln
14
QRBee.sln
@ -13,6 +13,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QRBee.Core", "QRBee.Core\QR
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QRBee.Tests", "QRBee.Tests\QRBee.Tests.csproj", "{C48223E7-4AEF-4F6B-A8A0-DDE16B7111DA}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QRBee.Tests", "QRBee.Tests\QRBee.Tests.csproj", "{C48223E7-4AEF-4F6B-A8A0-DDE16B7111DA}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QRBee.Load.Generator", "QRBee.Load.Generator\QRBee.Load.Generator.csproj", "{656BE747-ADE3-4E85-A70C-13167E716260}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -95,6 +97,18 @@ Global
|
|||||||
{C48223E7-4AEF-4F6B-A8A0-DDE16B7111DA}.Release|iPhone.Build.0 = Release|Any CPU
|
{C48223E7-4AEF-4F6B-A8A0-DDE16B7111DA}.Release|iPhone.Build.0 = Release|Any CPU
|
||||||
{C48223E7-4AEF-4F6B-A8A0-DDE16B7111DA}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
{C48223E7-4AEF-4F6B-A8A0-DDE16B7111DA}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
{C48223E7-4AEF-4F6B-A8A0-DDE16B7111DA}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
{C48223E7-4AEF-4F6B-A8A0-DDE16B7111DA}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||||
|
{656BE747-ADE3-4E85-A70C-13167E716260}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{656BE747-ADE3-4E85-A70C-13167E716260}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{656BE747-ADE3-4E85-A70C-13167E716260}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
|
{656BE747-ADE3-4E85-A70C-13167E716260}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||||
|
{656BE747-ADE3-4E85-A70C-13167E716260}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
|
{656BE747-ADE3-4E85-A70C-13167E716260}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
|
{656BE747-ADE3-4E85-A70C-13167E716260}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{656BE747-ADE3-4E85-A70C-13167E716260}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{656BE747-ADE3-4E85-A70C-13167E716260}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||||
|
{656BE747-ADE3-4E85-A70C-13167E716260}.Release|iPhone.Build.0 = Release|Any CPU
|
||||||
|
{656BE747-ADE3-4E85-A70C-13167E716260}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
|
{656BE747-ADE3-4E85-A70C-13167E716260}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user