diff --git a/ProjectPlugins/CodexDiscordBotPlugin/RewarderBotContainerRecipe.cs b/ProjectPlugins/CodexDiscordBotPlugin/RewarderBotContainerRecipe.cs index 8bf4344..55b60a2 100644 --- a/ProjectPlugins/CodexDiscordBotPlugin/RewarderBotContainerRecipe.cs +++ b/ProjectPlugins/CodexDiscordBotPlugin/RewarderBotContainerRecipe.cs @@ -7,7 +7,7 @@ namespace CodexDiscordBotPlugin public class RewarderBotContainerRecipe : ContainerRecipeFactory { public override string AppName => "discordbot-rewarder"; - public override string Image => "codexstorage/codex-rewarderbot:sha-ccc6c81"; + public override string Image => "codexstorage/codex-rewarderbot:sha-12dc7ef"; protected override void Initialize(StartupConfig startupConfig) { diff --git a/ProjectPlugins/CodexPlugin/MarketplaceAccess.cs b/ProjectPlugins/CodexPlugin/MarketplaceAccess.cs index 8944d95..11f3b60 100644 --- a/ProjectPlugins/CodexPlugin/MarketplaceAccess.cs +++ b/ProjectPlugins/CodexPlugin/MarketplaceAccess.cs @@ -1,5 +1,4 @@ using Logging; -using Newtonsoft.Json; using Utils; namespace CodexPlugin @@ -7,7 +6,7 @@ namespace CodexPlugin public interface IMarketplaceAccess { string MakeStorageAvailable(StorageAvailability availability); - StoragePurchaseContract RequestStorage(StoragePurchaseRequest purchase); + IStoragePurchaseContract RequestStorage(StoragePurchaseRequest purchase); } public class MarketplaceAccess : IMarketplaceAccess @@ -21,7 +20,7 @@ namespace CodexPlugin this.codexAccess = codexAccess; } - public StoragePurchaseContract RequestStorage(StoragePurchaseRequest purchase) + public IStoragePurchaseContract RequestStorage(StoragePurchaseRequest purchase) { purchase.Log(log); @@ -68,7 +67,7 @@ namespace CodexPlugin throw new NotImplementedException(); } - public StoragePurchaseContract RequestStorage(StoragePurchaseRequest purchase) + public IStoragePurchaseContract RequestStorage(StoragePurchaseRequest purchase) { Unavailable(); throw new NotImplementedException(); @@ -80,131 +79,4 @@ namespace CodexPlugin throw new InvalidOperationException(); } } - - public class StoragePurchaseContract - { - private readonly ILog log; - private readonly CodexAccess codexAccess; - private readonly TimeSpan gracePeriod = TimeSpan.FromSeconds(30); - private readonly DateTime contractPendingUtc = DateTime.UtcNow; - private DateTime? contractSubmittedUtc = DateTime.UtcNow; - private DateTime? contractStartedUtc; - private DateTime? contractFinishedUtc; - - public StoragePurchaseContract(ILog log, CodexAccess codexAccess, string purchaseId, StoragePurchaseRequest purchase) - { - this.log = log; - this.codexAccess = codexAccess; - PurchaseId = purchaseId; - Purchase = purchase; - } - - public string PurchaseId { get; } - public StoragePurchaseRequest Purchase { get; } - - 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(); - AssertDuration(PendingToSubmitted, gracePeriod, nameof(PendingToSubmitted)); - } - - public void WaitForStorageContractStarted() - { - var timeout = Purchase.Expiry + gracePeriod; - - WaitForStorageContractState(timeout, "started"); - contractStartedUtc = DateTime.UtcNow; - LogStartedDuration(); - AssertDuration(SubmittedToStarted, timeout, nameof(SubmittedToStarted)); - } - - public void WaitForStorageContractFinished() - { - if (!contractStartedUtc.HasValue) - { - WaitForStorageContractStarted(); - } - var currentContractTime = DateTime.UtcNow - contractSubmittedUtc!.Value; - var timeout = (Purchase.Duration - currentContractTime) + gracePeriod; - WaitForStorageContractState(timeout, "finished"); - contractFinishedUtc = DateTime.UtcNow; - LogFinishedDuration(); - AssertDuration(SubmittedToFinished, timeout, nameof(SubmittedToFinished)); - } - - public StoragePurchase GetPurchaseStatus(string purchaseId) - { - return codexAccess.GetPurchaseStatus(purchaseId); - } - - private void WaitForStorageContractState(TimeSpan timeout, string desiredState, int sleep = 1000) - { - var lastState = ""; - var waitStart = DateTime.UtcNow; - - Log($"Waiting for {Time.FormatDuration(timeout)} to reach state '{desiredState}'."); - while (lastState != desiredState) - { - var purchaseStatus = codexAccess.GetPurchaseStatus(PurchaseId); - var statusJson = JsonConvert.SerializeObject(purchaseStatus); - if (purchaseStatus != null && purchaseStatus.State != lastState) - { - lastState = purchaseStatus.State; - log.Debug("Purchase status: " + statusJson); - } - - Thread.Sleep(sleep); - - if (lastState == "errored") - { - FrameworkAssert.Fail("Contract errored: " + statusJson); - } - - if (DateTime.UtcNow - waitStart > timeout) - { - FrameworkAssert.Fail($"Contract did not reach '{desiredState}' within {Time.FormatDuration(timeout)} timeout. {statusJson}"); - } - } - } - - private void LogSubmittedDuration() - { - Log($"Pending to Submitted in {Time.FormatDuration(PendingToSubmitted)} " + - $"( < {Time.FormatDuration(gracePeriod)})"); - } - - private void LogStartedDuration() - { - Log($"Submitted to Started in {Time.FormatDuration(SubmittedToStarted)} " + - $"( < {Time.FormatDuration(Purchase.Expiry + gracePeriod)})"); - } - - private void LogFinishedDuration() - { - 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); - } - } - - private void Log(string msg) - { - log.Log($"[{PurchaseId}] {msg}"); - } - } } diff --git a/ProjectPlugins/CodexPlugin/StoragePurchaseContract.cs b/ProjectPlugins/CodexPlugin/StoragePurchaseContract.cs new file mode 100644 index 0000000..3a9e0b9 --- /dev/null +++ b/ProjectPlugins/CodexPlugin/StoragePurchaseContract.cs @@ -0,0 +1,140 @@ +using Logging; +using Newtonsoft.Json; +using Utils; + +namespace CodexPlugin +{ + public interface IStoragePurchaseContract + { + void WaitForStorageContractSubmitted(); + void WaitForStorageContractStarted(); + void WaitForStorageContractFinished(); + } + + public class StoragePurchaseContract : IStoragePurchaseContract + { + private readonly ILog log; + private readonly CodexAccess codexAccess; + private readonly TimeSpan gracePeriod = TimeSpan.FromSeconds(30); + private readonly DateTime contractPendingUtc = DateTime.UtcNow; + private DateTime? contractSubmittedUtc = DateTime.UtcNow; + private DateTime? contractStartedUtc; + private DateTime? contractFinishedUtc; + + public StoragePurchaseContract(ILog log, CodexAccess codexAccess, string purchaseId, StoragePurchaseRequest purchase) + { + this.log = log; + this.codexAccess = codexAccess; + PurchaseId = purchaseId; + Purchase = purchase; + } + + public string PurchaseId { get; } + public StoragePurchaseRequest Purchase { get; } + + 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(); + AssertDuration(PendingToSubmitted, gracePeriod, nameof(PendingToSubmitted)); + } + + public void WaitForStorageContractStarted() + { + var timeout = Purchase.Expiry + gracePeriod; + + WaitForStorageContractState(timeout, "started"); + contractStartedUtc = DateTime.UtcNow; + LogStartedDuration(); + AssertDuration(SubmittedToStarted, timeout, nameof(SubmittedToStarted)); + } + + public void WaitForStorageContractFinished() + { + if (!contractStartedUtc.HasValue) + { + WaitForStorageContractStarted(); + } + var currentContractTime = DateTime.UtcNow - contractSubmittedUtc!.Value; + var timeout = (Purchase.Duration - currentContractTime) + gracePeriod; + WaitForStorageContractState(timeout, "finished"); + contractFinishedUtc = DateTime.UtcNow; + LogFinishedDuration(); + AssertDuration(SubmittedToFinished, timeout, nameof(SubmittedToFinished)); + } + + public StoragePurchase GetPurchaseStatus(string purchaseId) + { + return codexAccess.GetPurchaseStatus(purchaseId); + } + + private void WaitForStorageContractState(TimeSpan timeout, string desiredState, int sleep = 1000) + { + var lastState = ""; + var waitStart = DateTime.UtcNow; + + Log($"Waiting for {Time.FormatDuration(timeout)} to reach state '{desiredState}'."); + while (lastState != desiredState) + { + var purchaseStatus = codexAccess.GetPurchaseStatus(PurchaseId); + var statusJson = JsonConvert.SerializeObject(purchaseStatus); + if (purchaseStatus != null && purchaseStatus.State != lastState) + { + lastState = purchaseStatus.State; + log.Debug("Purchase status: " + statusJson); + } + + Thread.Sleep(sleep); + + if (lastState == "errored") + { + FrameworkAssert.Fail("Contract errored: " + statusJson); + } + + if (DateTime.UtcNow - waitStart > timeout) + { + FrameworkAssert.Fail($"Contract did not reach '{desiredState}' within {Time.FormatDuration(timeout)} timeout. {statusJson}"); + } + } + } + + private void LogSubmittedDuration() + { + Log($"Pending to Submitted in {Time.FormatDuration(PendingToSubmitted)} " + + $"( < {Time.FormatDuration(gracePeriod)})"); + } + + private void LogStartedDuration() + { + Log($"Submitted to Started in {Time.FormatDuration(SubmittedToStarted)} " + + $"( < {Time.FormatDuration(Purchase.Expiry + gracePeriod)})"); + } + + private void LogFinishedDuration() + { + 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); + } + } + + private void Log(string msg) + { + log.Log($"[{PurchaseId}] {msg}"); + } + } +} diff --git a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs index e9dba30..4a56778 100644 --- a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs +++ b/Tests/CodexTests/UtilityTests/DiscordBotTests.cs @@ -92,7 +92,7 @@ namespace CodexTests.UtilityTests } } - private StoragePurchaseContract ClientPurchasesStorage(ICodexNode client) + private IStoragePurchaseContract ClientPurchasesStorage(ICodexNode client) { var testFile = GenerateTestFile(GetMinFileSize()); var contentId = client.UploadFile(testFile);