2024-03-20 11:32:16 +01:00
using Logging ;
2023-09-20 08:45:55 +02:00
using Newtonsoft.Json ;
using Utils ;
namespace CodexPlugin
{
public interface IMarketplaceAccess
{
2024-03-20 11:11:41 +01:00
string MakeStorageAvailable ( StorageAvailability availability ) ;
2024-03-26 12:23:38 +01:00
StoragePurchaseContract RequestStorage ( StoragePurchaseRequest purchase ) ;
2023-09-20 08:45:55 +02:00
}
public class MarketplaceAccess : IMarketplaceAccess
{
private readonly ILog log ;
private readonly CodexAccess codexAccess ;
public MarketplaceAccess ( ILog log , CodexAccess codexAccess )
{
this . log = log ;
this . codexAccess = codexAccess ;
}
2024-03-26 12:23:38 +01:00
public StoragePurchaseContract RequestStorage ( StoragePurchaseRequest purchase )
2023-09-20 08:45:55 +02:00
{
2024-03-20 11:11:41 +01:00
purchase . Log ( log ) ;
2023-11-23 14:10:59 +01:00
2024-03-26 12:23:38 +01:00
var response = codexAccess . RequestStorage ( purchase ) ;
2023-09-20 08:45:55 +02:00
2024-03-26 12:23:38 +01:00
if ( string . IsNullOrEmpty ( response ) | |
response = = "Purchasing not available" | |
2023-11-23 14:10:59 +01:00
response = = "Expiry required" | |
2024-02-26 09:02:46 +01:00
response = = "Expiry needs to be in future" | |
response = = "Expiry has to be before the request's end (now + duration)" )
2023-09-20 08:45:55 +02:00
{
throw new InvalidOperationException ( response ) ;
}
Log ( $"Storage requested successfully. PurchaseId: '{response}'." ) ;
2024-05-23 11:15:14 +02:00
var contract = new StoragePurchaseContract ( log , codexAccess , response , purchase ) ;
contract . WaitForStorageContractSubmitted ( ) ;
return contract ;
2023-09-20 08:45:55 +02:00
}
2024-03-20 11:11:41 +01:00
public string MakeStorageAvailable ( StorageAvailability availability )
2023-09-20 08:45:55 +02:00
{
2024-03-20 11:11:41 +01:00
availability . Log ( log ) ;
2023-09-20 08:45:55 +02:00
2024-03-26 12:23:38 +01:00
var response = codexAccess . SalesAvailability ( availability ) ;
2023-09-20 08:45:55 +02:00
2024-03-26 12:23:38 +01:00
Log ( $"Storage successfully made available. Id: {response.Id}" ) ;
2023-09-20 08:45:55 +02:00
2024-03-26 12:23:38 +01:00
return response . Id ;
2023-09-20 08:45:55 +02:00
}
private void Log ( string msg )
{
2024-05-23 11:15:14 +02:00
log . Log ( $"{codexAccess.Container.Containers.Single().Name} {msg}" ) ;
2023-09-20 08:45:55 +02:00
}
}
public class MarketplaceUnavailable : IMarketplaceAccess
{
2024-03-20 11:11:41 +01:00
public string MakeStorageAvailable ( StorageAvailability availability )
2023-11-23 14:10:59 +01:00
{
Unavailable ( ) ;
throw new NotImplementedException ( ) ;
}
2024-03-26 12:23:38 +01:00
public StoragePurchaseContract RequestStorage ( StoragePurchaseRequest purchase )
2023-09-20 08:45:55 +02:00
{
Unavailable ( ) ;
2024-03-20 11:11:41 +01:00
throw new NotImplementedException ( ) ;
2023-09-20 08:45:55 +02:00
}
private void Unavailable ( )
{
2023-09-20 10:51:47 +02:00
FrameworkAssert . Fail ( "Incorrect test setup: Marketplace was not enabled for this group of Codex nodes. Add 'EnableMarketplace(...)' after 'SetupCodexNodes()' to enable it." ) ;
2023-09-20 08:45:55 +02:00
throw new InvalidOperationException ( ) ;
}
}
public class StoragePurchaseContract
{
private readonly ILog log ;
private readonly CodexAccess codexAccess ;
2024-05-10 10:08:45 +02:00
private readonly TimeSpan gracePeriod = TimeSpan . FromSeconds ( 30 ) ;
2024-05-23 11:15:14 +02:00
private readonly DateTime contractPendingUtc = DateTime . UtcNow ;
private DateTime ? contractSubmittedUtc = DateTime . UtcNow ;
private DateTime ? contractStartedUtc ;
private DateTime ? contractFinishedUtc ;
2023-09-20 08:45:55 +02:00
2024-03-26 12:23:38 +01:00
public StoragePurchaseContract ( ILog log , CodexAccess codexAccess , string purchaseId , StoragePurchaseRequest purchase )
2023-09-20 08:45:55 +02:00
{
this . log = log ;
this . codexAccess = codexAccess ;
PurchaseId = purchaseId ;
2024-03-20 11:11:41 +01:00
Purchase = purchase ;
2023-09-20 08:45:55 +02:00
}
public string PurchaseId { get ; }
2024-03-26 12:23:38 +01:00
public StoragePurchaseRequest Purchase { get ; }
2023-09-20 08:45:55 +02:00
2024-05-23 11:15:14 +02:00
public TimeSpan ? PendingToSubmitted = > contractSubmittedUtc - contractPendingUtc ;
public TimeSpan ? SubmittedToStarted = > contractStartedUtc - contractSubmittedUtc ;
public TimeSpan ? SubmittedToFinished = > contractFinishedUtc - contractSubmittedUtc ;
public void WaitForStorageContractSubmitted ( )
{
WaitForStorageContractState ( gracePeriod , "submitted" , sleep : 200 ) ;
contractSubmittedUtc = DateTime . UtcNow ;
LogSubmittedDuration ( ) ;
2024-05-24 14:33:59 +02:00
AssertDuration ( PendingToSubmitted , gracePeriod , nameof ( PendingToSubmitted ) ) ;
2024-05-23 11:15:14 +02:00
}
2023-09-20 08:45:55 +02:00
public void WaitForStorageContractStarted ( )
{
2024-03-20 13:36:06 +01:00
var timeout = Purchase . Expiry + gracePeriod ;
WaitForStorageContractState ( timeout , "started" ) ;
2024-05-23 11:15:14 +02:00
contractStartedUtc = DateTime . UtcNow ;
LogStartedDuration ( ) ;
2024-05-24 14:33:59 +02:00
AssertDuration ( SubmittedToStarted , timeout , nameof ( SubmittedToStarted ) ) ;
2023-09-20 08:45:55 +02:00
}
public void WaitForStorageContractFinished ( )
{
2024-05-23 11:15:14 +02:00
if ( ! contractStartedUtc . HasValue )
2023-09-20 08:45:55 +02:00
{
WaitForStorageContractStarted ( ) ;
}
2024-05-24 14:33:59 +02:00
var currentContractTime = DateTime . UtcNow - contractSubmittedUtc ! . Value ;
2024-03-20 11:11:41 +01:00
var timeout = ( Purchase . Duration - currentContractTime ) + gracePeriod ;
2023-09-20 08:45:55 +02:00
WaitForStorageContractState ( timeout , "finished" ) ;
2024-05-23 11:15:14 +02:00
contractFinishedUtc = DateTime . UtcNow ;
LogFinishedDuration ( ) ;
2024-05-24 14:33:59 +02:00
AssertDuration ( SubmittedToFinished , timeout , nameof ( SubmittedToFinished ) ) ;
2023-09-20 08:45:55 +02:00
}
2024-03-26 12:23:38 +01:00
public StoragePurchase GetPurchaseStatus ( string purchaseId )
2023-09-20 08:45:55 +02:00
{
2024-03-20 13:36:06 +01:00
return codexAccess . GetPurchaseStatus ( purchaseId ) ;
2023-09-20 08:45:55 +02:00
}
2024-05-23 11:15:14 +02:00
private void WaitForStorageContractState ( TimeSpan timeout , string desiredState , int sleep = 1000 )
2023-09-20 08:45:55 +02:00
{
var lastState = "" ;
var waitStart = DateTime . UtcNow ;
2024-05-23 11:15:14 +02:00
Log ( $"Waiting for {Time.FormatDuration(timeout)} to reach state '{desiredState}'." ) ;
2023-09-20 08:45:55 +02:00
while ( lastState ! = desiredState )
{
var purchaseStatus = codexAccess . GetPurchaseStatus ( PurchaseId ) ;
var statusJson = JsonConvert . SerializeObject ( purchaseStatus ) ;
2024-03-26 12:23:38 +01:00
if ( purchaseStatus ! = null & & purchaseStatus . State ! = lastState )
2023-09-20 08:45:55 +02:00
{
2024-03-26 12:23:38 +01:00
lastState = purchaseStatus . State ;
2023-09-20 08:45:55 +02:00
log . Debug ( "Purchase status: " + statusJson ) ;
}
2024-05-23 11:15:14 +02:00
Thread . Sleep ( sleep ) ;
2023-09-20 08:45:55 +02:00
if ( lastState = = "errored" )
{
2023-09-20 10:51:47 +02:00
FrameworkAssert . Fail ( "Contract errored: " + statusJson ) ;
2023-09-20 08:45:55 +02:00
}
if ( DateTime . UtcNow - waitStart > timeout )
{
2023-10-17 13:52:04 +02:00
FrameworkAssert . Fail ( $"Contract did not reach '{desiredState}' within {Time.FormatDuration(timeout)} timeout. {statusJson}" ) ;
2023-09-20 08:45:55 +02:00
}
}
2024-05-23 11:15:14 +02:00
}
private void LogSubmittedDuration ( )
{
2024-05-24 14:33:59 +02:00
Log ( $"Pending to Submitted in {Time.FormatDuration(PendingToSubmitted)} " +
$"( < {Time.FormatDuration(gracePeriod)})" ) ;
2024-05-23 11:15:14 +02:00
}
private void LogStartedDuration ( )
{
2024-05-24 14:33:59 +02:00
Log ( $"Submitted to Started in {Time.FormatDuration(SubmittedToStarted)} " +
$"( < {Time.FormatDuration(Purchase.Expiry + gracePeriod)})" ) ;
2024-05-23 11:15:14 +02:00
}
private void LogFinishedDuration ( )
{
2024-05-24 14:33:59 +02:00
Log ( $"Submitted to Finished in {Time.FormatDuration(SubmittedToFinished)} " +
$"( < {Time.FormatDuration(Purchase.Duration + gracePeriod)})" ) ;
}
private void AssertDuration ( TimeSpan ? span , TimeSpan max , string message )
{
if ( span = = null ) throw new ArgumentNullException ( nameof ( MarketplaceAccess ) + ": " + message + " (IsNull)" ) ;
if ( span . Value . TotalDays > = max . TotalSeconds )
{
throw new Exception ( nameof ( MarketplaceAccess ) +
$": Duration out of range. Max: {Time.FormatDuration(max)} but was: {Time.FormatDuration(span.Value)} " +
message ) ;
}
2024-05-23 11:15:14 +02:00
}
private void Log ( string msg )
{
log . Log ( $"[{PurchaseId}] {msg}" ) ;
2023-09-20 08:45:55 +02:00
}
}
}