diff --git a/QRBee.Load.Generator/GeneratorSettings.cs b/QRBee.Load.Generator/GeneratorSettings.cs index f0c5359..4a22931 100644 --- a/QRBee.Load.Generator/GeneratorSettings.cs +++ b/QRBee.Load.Generator/GeneratorSettings.cs @@ -18,4 +18,6 @@ internal class GeneratorSettings public Anomaly LoadSpike { get; set; } = new(); public Anomaly LargeAmount { get; set; } = new(); + public Anomaly TransactionCorruption { get; set;} = new(); + public Anomaly CoherentTransactionCorruption { get; set; } = new(); } diff --git a/QRBee.Load.Generator/LoadGenerator.cs b/QRBee.Load.Generator/LoadGenerator.cs index 6c78b28..408670b 100644 --- a/QRBee.Load.Generator/LoadGenerator.cs +++ b/QRBee.Load.Generator/LoadGenerator.cs @@ -11,6 +11,7 @@ internal class LoadGenerator : IHostedService private readonly Client _client; private readonly ClientPool _clientPool; private readonly PaymentRequestGenerator _paymentRequestGenerator; + private readonly TransactionDefiler _transactionDefiler; private readonly ILogger _logger; private readonly IOptions _settings; @@ -21,7 +22,8 @@ internal class LoadGenerator : IHostedService public LoadGenerator( QRBee.Core.Client.Client client, ClientPool clientPool, - PaymentRequestGenerator paymentRequestGenerator, + PaymentRequestGenerator paymentRequestGenerator, + TransactionDefiler transactionDefiler, ILogger logger, IOptions settings ) @@ -29,6 +31,7 @@ internal class LoadGenerator : IHostedService _client = client; _clientPool = clientPool; _paymentRequestGenerator = paymentRequestGenerator; + _transactionDefiler = transactionDefiler; _logger = logger; _settings = settings; @@ -204,6 +207,8 @@ internal class LoadGenerator : IHostedService GatewayTransactionId = res.GatewayTransactionId }; + _transactionDefiler.CorruptPaymentConfirmation(paymentConfirmation); + var confirmationTask = _client.ConfirmPayAsync(paymentConfirmation); _confirmationQueue.Add(confirmationTask); } @@ -242,6 +247,8 @@ internal class LoadGenerator : IHostedService _rng.NextInRange(1, _settings.Value.NumberOfMerchants + 1) ); + _transactionDefiler.CorruptPaymentRequest(req); + var resp = _client.PayAsync(req); _responseQueue.Add(resp); diff --git a/QRBee.Load.Generator/Program.cs b/QRBee.Load.Generator/Program.cs index 6dd2df2..868bde8 100644 --- a/QRBee.Load.Generator/Program.cs +++ b/QRBee.Load.Generator/Program.cs @@ -30,6 +30,7 @@ builder.ConfigureServices((context, services) => .Configure(context.Configuration.GetSection("GeneratorSettings")) .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton(x => no => new PrivateKeyHandler(x.GetRequiredService>(), x.GetRequiredService(), no)) .AddSingleton(x => no => new AndroidSecurityService(x.GetRequiredService()(no))) .AddHostedService() diff --git a/QRBee.Load.Generator/TransactionDefiler.cs b/QRBee.Load.Generator/TransactionDefiler.cs new file mode 100644 index 0000000..37c20d2 --- /dev/null +++ b/QRBee.Load.Generator/TransactionDefiler.cs @@ -0,0 +1,84 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using QRBee.Core.Data; + +namespace QRBee.Load.Generator +{ + internal class TransactionDefiler + { + private readonly ILogger _logger; + private ThreadSafeRandom _rng = new ThreadSafeRandom(); + private double _corruptionProbability; + private double _coherentCorruptionProbability; + private bool _multipleCorruption = false; + private int _sequenceLengthMin; + private int _sequenceLengthMax; + private int _sequenceLength; + private int _corruptionCounter = 0; + + public TransactionDefiler(IOptions settings, ILogger logger) + { + _logger = logger; + + var transactionCorruption = settings.Value.TransactionCorruption; + _corruptionProbability = transactionCorruption.Probability; + + var coherentTransactionCorruption = settings.Value.CoherentTransactionCorruption; + _coherentCorruptionProbability = coherentTransactionCorruption.Probability; + if (_coherentCorruptionProbability > 0) + { + if (coherentTransactionCorruption.Parameters.TryGetValue("SequenceLengthMin", out var s)) + _sequenceLengthMin = int.Parse(s); + if (coherentTransactionCorruption.Parameters.TryGetValue("SequenceLengthMax", out s)) + _sequenceLengthMax = int.Parse(s); + } + + + _logger.LogDebug($"Transaction corruption configured: Probability={_corruptionProbability}"); + _logger.LogDebug($"Coherent transaction corruption configured: Probability={_coherentCorruptionProbability}, SequenceLengthMin={_sequenceLengthMin}, SequenceLengthMax={_sequenceLengthMax}"); + } + + public void CorruptPaymentRequest(PaymentRequest paymentRequest) + { + var dice = _rng.NextDouble(); + if(_multipleCorruption) + { + _corruptionCounter++; + _logger.LogWarning($"Anomaly: Coherent corrupted transaction Dice={dice}, Corruption counter = {_corruptionCounter}, Sequence length = {_sequenceLength}"); + paymentRequest.ClientResponse.MerchantRequest.Amount += 0.01M; + + if (_corruptionCounter >= _sequenceLength) + { + _corruptionCounter = 0; + _multipleCorruption = false; + } + return; + } + + if (dice < _corruptionProbability) + { + _logger.LogWarning($"Anomaly: Corrupted transaction Dice={dice}"); + paymentRequest.ClientResponse.MerchantRequest.Amount += 10M; + + if(dice < _coherentCorruptionProbability && _multipleCorruption==false) + { + _sequenceLength = _rng.NextInRange(_sequenceLengthMin,_sequenceLengthMax); + _multipleCorruption = true; + } + + } + + } + + public void CorruptPaymentConfirmation(PaymentConfirmation paymentConfirmation) + { + var dice = _rng.NextDouble(); + if (dice < _corruptionProbability) + { + _logger.LogWarning($"Anomaly: Corrupted transaction confirmation Dice={dice}"); + paymentConfirmation.GatewayTransactionId = "BadGatewayTransactionId"; + } + + } + } +} diff --git a/QRBee.Load.Generator/appsettings.json b/QRBee.Load.Generator/appsettings.json index 336fe4d..193d165 100644 --- a/QRBee.Load.Generator/appsettings.json +++ b/QRBee.Load.Generator/appsettings.json @@ -14,22 +14,37 @@ "GeneratorSettings": { "NumberOfClients": 100, "NumberOfMerchants": 10, - "NumberOfThreads": 10, - "DelayBetweenMessagesMSec": 300, + "NumberOfThreads": 5, + "DelayBetweenMessagesMSec": 500, "DelayJitterMSec": 50, "MinAmount": 10, "MaxAmount": 100, "LoadSpike": { - "Probability": 0.001, + "Probability": 0.004, "Parameters": { "Duration": "00:00:15", "Delay": "00:00:00.0100000" } }, + "TransactionCorruption": { + "Probability": 0.004, + "Parameters": { + + } + }, + + "CoherentTransactionCorruption": { + "Probability": 0.002, + "Parameters": { + "SequenceLengthMin": 2, + "SequenceLengthMax": 10 + } + }, + "LargeAmount": { - "Probability": 0.03, + "Probability": 0.003, "Parameters": { "Value": "1000" } diff --git a/QRBee.Load.Generator/log4net.config b/QRBee.Load.Generator/log4net.config index ac296f7..2df58d4 100644 --- a/QRBee.Load.Generator/log4net.config +++ b/QRBee.Load.Generator/log4net.config @@ -36,12 +36,20 @@ - + + + + + + + + + - + diff --git a/QRBeeApi/Services/CustomMetrics.cs b/QRBeeApi/Services/CustomMetrics.cs index 1843d63..774702b 100644 --- a/QRBeeApi/Services/CustomMetrics.cs +++ b/QRBeeApi/Services/CustomMetrics.cs @@ -8,6 +8,9 @@ namespace QRBee.Api.Services private Counter MerchantResponseCounter { get; } private Counter SucceededTransactionsCounter { get; } private Counter FailedTransactionsCounter { get; } + private Counter CorruptTransactionsCounter { get; } + private Counter SucceededPaymentConfirmationsCounter { get; } + private Counter FailedPaymentConfirmationsCounter { get; } private Counter TotalCreditCardCheckTime { get; } private Counter TotalPaymentTime { get; } @@ -20,11 +23,16 @@ namespace QRBee.Api.Services MerchantRequestCounter = meter.CreateCounter("merchant-requests", description: "Merchant has sent a request"); MerchantResponseCounter = meter.CreateCounter("merchant-responses"); + SucceededTransactionsCounter = meter.CreateCounter("transaction-succeeded", description: "Transaction succeeded"); FailedTransactionsCounter = meter.CreateCounter("transaction-failed", description: "Transaction failed"); + CorruptTransactionsCounter = meter.CreateCounter("transaction-corrupt", description: "Transaction was corrupted"); - TotalCreditCardCheckTime = meter.CreateCounter("Total-credit-card-check-time","msec"); - TotalPaymentTime = meter.CreateCounter("Total-payment-time","msec"); + SucceededPaymentConfirmationsCounter = meter.CreateCounter("payment-confirmation-succeeded", description: "Payment confirmation succeeded"); + FailedPaymentConfirmationsCounter = meter.CreateCounter("payment-confirmation-failed", description: "Payment confirmation failed"); + + TotalCreditCardCheckTime = meter.CreateCounter("total-credit-card-check-time","msec"); + TotalPaymentTime = meter.CreateCounter("total-payment-time","msec"); } @@ -32,6 +40,9 @@ namespace QRBee.Api.Services public void AddMerchantResponse() => MerchantResponseCounter.Add(1); public void AddSucceededTransaction() => SucceededTransactionsCounter.Add(1); public void AddFailedTransaction() => FailedTransactionsCounter.Add(1); + public void AddCorruptTransaction() => CorruptTransactionsCounter.Add(1); + public void AddSucceededPaymentConfirmation() => SucceededPaymentConfirmationsCounter.Add(1); + public void AddFailedPaymentConfirmation() => FailedPaymentConfirmationsCounter.Add(1); public void AddTotalCreditCardCheckTime(long milliseconds) => TotalCreditCardCheckTime.Add(milliseconds); public void AddTotalPaymentTime(long milliseconds) => TotalPaymentTime.Add(milliseconds); diff --git a/QRBeeApi/Services/QRBeeAPIService.cs b/QRBeeApi/Services/QRBeeAPIService.cs index db2f0b0..e39b9f7 100644 --- a/QRBeeApi/Services/QRBeeAPIService.cs +++ b/QRBeeApi/Services/QRBeeAPIService.cs @@ -172,8 +172,17 @@ namespace QRBee.Api.Services var t4 = CheckTransaction(tid); //Parallel task execution - await Task.WhenAll(t2, t3, t4); - _logger.LogInformation($"Transaction=\"{tid}\" Fully validated"); + try + { + await Task.WhenAll(t2, t3, t4); + _logger.LogInformation($"Transaction=\"{tid}\" Fully validated"); + } + catch(Exception) + { + _customMetrics.AddCorruptTransaction(); + throw; + } + //5. Decrypt client card data var creditCardCheckTime = Stopwatch.StartNew(); @@ -206,17 +215,17 @@ namespace QRBee.Api.Services //9. Record transaction with result if (gatewayResponse.Success) { + _customMetrics.AddSucceededTransaction(); + info.Status=TransactionInfo.TransactionStatus.Succeeded; info.GatewayTransactionId=gatewayResponse.GatewayTransactionId; - - _customMetrics.AddSucceededTransaction(); } else { + _customMetrics.AddFailedTransaction(); + info.Status = TransactionInfo.TransactionStatus.Rejected; info.RejectReason = gatewayResponse.ErrorMessage; - - _customMetrics.AddFailedTransaction(); } await _storage.UpdateTransaction(info); _logger.LogInformation($"Transaction=\"{tid}\" complete Status=\"{info.Status}\""); @@ -403,9 +412,11 @@ namespace QRBee.Api.Services trans.Status = TransactionInfo.TransactionStatus.Confirmed; await _storage.UpdateTransaction(trans); _logger.LogInformation($"Transaction with MerchantTransactionId: {trans.MerchantTransactionId} confirmed"); + _customMetrics.AddSucceededPaymentConfirmation(); } else { + _customMetrics.AddFailedPaymentConfirmation(); throw new ApplicationException($"Transaction with gatewayTransactionId:{value.GatewayTransactionId} failed."); } }