diff --git a/.github/workflows/run-continuous-tests.yaml b/.github/workflows/run-continuous-tests.yaml index c364fa31..3b7fc060 100644 --- a/.github/workflows/run-continuous-tests.yaml +++ b/.github/workflows/run-continuous-tests.yaml @@ -80,7 +80,7 @@ env: TESTS_TARGET_DURATION: 2d TESTS_FILTER: "" TESTS_CLEANUP: true - JOB_MANIFEST: docker/continuous-tests-job.yaml + JOB_MANIFEST: docker/job-continuous-tests.yaml KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }} KUBE_VERSION: v1.28.2 @@ -146,7 +146,7 @@ jobs: if: false run: | # Variables - # We need more than 300 seconds because Auto Scaler may take 3 minutes to tun a node + # We need more than 300 seconds because Auto Scaler may take 3 minutes to run a node duration=600 namespace="${{ env.NAMESPACE }}" pod=$(kubectl get pod --selector job-name=${{ env.NAMEPREFIX }} -o jsonpath="{.items[0].metadata.name}") diff --git a/.github/workflows/run-dist-tests.yaml b/.github/workflows/run-dist-tests.yaml index 7eb45c6f..c42a6cfe 100644 --- a/.github/workflows/run-dist-tests.yaml +++ b/.github/workflows/run-dist-tests.yaml @@ -32,7 +32,7 @@ env: NAMEPREFIX: d-tests-runner NAMESPACE: default COMMAND: dotnet test Tests/CodexTests - JOB_MANIFEST: docker/dist-tests-job.yaml + JOB_MANIFEST: docker/job-dist-tests.yaml KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }} KUBE_VERSION: v1.28.2 diff --git a/.github/workflows/run-release-tests.yaml b/.github/workflows/run-release-tests.yaml new file mode 100644 index 00000000..32d4c26a --- /dev/null +++ b/.github/workflows/run-release-tests.yaml @@ -0,0 +1,105 @@ +name: Run Release Tests + + +on: + workflow_dispatch: + inputs: + codexdockerimage: + description: "Codex Docker image (example: 'codexstorage/nim-codex:0.1.8-dist-tests')" + required: true + type: string + workflow_call: + inputs: + codexdockerimage: + description: "Codex Docker image (example: 'codexstorage/nim-codex:0.1.8-dist-tests')" + required: true + type: string + + +env: + SOURCE: ${{ format('{0}/{1}', github.server_url, github.repository) }} + BRANCH: ${{ github.ref_name }} + CODEXDOCKERIMAGE: codexstorage/nim-codex:latest-dist-tests + TEST_TYPE: release-tests + NAMEPREFIX: r-tests + NAMESPACE: default + JOB_MANIFEST: docker/job-release-tests.yaml + COMMAND: dotnet test Tests/CodexReleaseTests + DURATION: 7200 + KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }} + KUBE_VERSION: v1.30.5 + + +jobs: + run_tests: + name: Run Release Tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + repository: ${{ inputs.workflow_source }} + + - name: Variables + run: | + RUNID=$(date +%Y%m%d-%H%M%S) + echo "RUNID=${RUNID}" >> $GITHUB_ENV + echo "TESTID=$(git rev-parse --short HEAD)" >> $GITHUB_ENV + [[ -n "${{ inputs.source }}" ]] && echo "SOURCE=${{ inputs.source }}" >>"$GITHUB_ENV" || echo "SOURCE=${{ env.SOURCE }}" >>"$GITHUB_ENV" + [[ -n "${{ inputs.branch }}" ]] && echo "BRANCH=${{ inputs.branch }}" >>"$GITHUB_ENV" || echo "BRANCH=${{ env.BRANCH }}" >>"$GITHUB_ENV" + [[ -n "${{ inputs.codexdockerimage }}" ]] && echo "CODEXDOCKERIMAGE=${{ inputs.codexdockerimage }}" >>"$GITHUB_ENV" || echo "CODEXDOCKERIMAGE=${{ env.CODEXDOCKERIMAGE }}" >>"$GITHUB_ENV" + [[ -n "${{ inputs.nameprefix }}" ]] && NAMEPREFIX="`awk '{ print tolower($0) }' <<< ${{ inputs.nameprefix }}`" || NAMEPREFIX="`awk '{ print tolower($0) }' <<< ${{ env.NAMEPREFIX }}`" + echo "NAMEPREFIX=${NAMEPREFIX}-${RUNID}" >>"$GITHUB_ENV" + [[ -n "${{ inputs.namespace }}" ]] && echo "NAMESPACE=${{ inputs.namespace }}" >>"$GITHUB_ENV" || echo "NAMESPACE=${{ env.NAMESPACE }}" >>"$GITHUB_ENV" + [[ -n "${{ inputs.command }}" ]] && COMMAND="${{ inputs.command }}" || COMMAND="${{ env.COMMAND }}" + echo "COMMAND=$(jq -c 'split(" ")' <<< '"'${COMMAND}'"')" >>"$GITHUB_ENV" + + - name: Kubectl - Install ${{ env.KUBE_VERSION }} + uses: azure/setup-kubectl@v4 + with: + version: ${{ env.KUBE_VERSION }} + + - name: Kubectl - Kubeconfig + run: | + mkdir -p "${HOME}"/.kube + echo "${{ env.KUBE_CONFIG }}" | base64 -d > "${HOME}"/.kube/config + + - name: Kubectl - Create Job to run tests + run: | + envsubst < ${{ env.JOB_MANIFEST }} | kubectl apply -f - + + - name: Tests Identification + run: | + echo "----" + echo "Repository: ${{ env.SOURCE }}" + echo "Branch: ${{ env.BRANCH }}" + echo "Runner job: ${{ env.NAMEPREFIX }}" + echo "Runner pod: $(kubectl get pod --selector job-name=${{ env.NAMEPREFIX }} -ojsonpath='{.items[0].metadata.name}')" + echo "Runner namespace: ${{ env.NAMESPACE }}" + echo "----" + + - name: Show Runner logs + run: | + # Variables + # We need more than 300 seconds because Auto Scaler may take 3 minutes to tun a node + duration=${{ env.DURATION }} + namespace="${{ env.NAMESPACE }}" + pod=$(kubectl get pod --selector job-name=${{ env.NAMEPREFIX }} -o jsonpath="{.items[0].metadata.name}") + + # Check Pod status + WAIT=120 + SECONDS=0 + sleep=1 + while (( SECONDS < WAIT )); do + phase=$(kubectl get pod ${pod} -n ${namespace} -o jsonpath="{.status.phase}") + [[ "${phase}" == "Running" ]] && { echo "Pod $pod is in $phase state - Get the logs"; break; } || { echo "Pod $pod is in $phase state - Retry in $sleep seconds / $((WAIT - SECONDS))"; } + sleep $sleep + done + + # Get logs + timeout $duration \ + kubectl logs $pod \ + -n $namespace \ + -f \ + --tail=-1 \ + --timestamps || true diff --git a/Framework/GethConnector/GethConnector.cs b/Framework/GethConnector/GethConnector.cs index c7c7687c..6b713faa 100644 --- a/Framework/GethConnector/GethConnector.cs +++ b/Framework/GethConnector/GethConnector.cs @@ -1,4 +1,5 @@ using CodexContractsPlugin; +using CodexContractsPlugin.Marketplace; using GethPlugin; using Logging; @@ -18,18 +19,29 @@ public class GethConnector return null; } + var gethNode = new CustomGethNode(log, GethInput.GethHost, GethInput.GethPort, GethInput.PrivateKey); + + var config = GetCodexMarketplaceConfig(gethNode, GethInput.MarketplaceAddress); + var contractsDeployment = new CodexContractsDeployment( + config: config, marketplaceAddress: GethInput.MarketplaceAddress, abi: GethInput.ABI, tokenAddress: GethInput.TokenAddress ); - var gethNode = new CustomGethNode(log, GethInput.GethHost, GethInput.GethPort, GethInput.PrivateKey); var contracts = new CodexContractsAccess(log, gethNode, contractsDeployment); return new GethConnector(gethNode, contracts); } + private static MarketplaceConfig GetCodexMarketplaceConfig(IGethNode gethNode, string marketplaceAddress) + { + var func = new ConfigurationFunctionBase(); + var response = gethNode.Call(marketplaceAddress, func); + return response.ReturnValue1; + } + private GethConnector(IGethNode gethNode, ICodexContracts codexContracts) { GethNode = gethNode; diff --git a/ProjectPlugins/CodexContractsPlugin/CodexContractsAccess.cs b/ProjectPlugins/CodexContractsPlugin/CodexContractsAccess.cs index 58fbb29b..2537d334 100644 --- a/ProjectPlugins/CodexContractsPlugin/CodexContractsAccess.cs +++ b/ProjectPlugins/CodexContractsPlugin/CodexContractsAccess.cs @@ -2,6 +2,7 @@ using GethPlugin; using Logging; using Nethereum.ABI; +using Nethereum.Hex.HexConvertors.Extensions; using Nethereum.Util; using NethereumWorkflow; using Newtonsoft.Json; @@ -24,6 +25,7 @@ public interface ICodexContracts ICodexContractsEvents GetEvents(BlockInterval blockInterval); EthAddress? GetSlotHost(Request storageRequest, decimal slotIndex); RequestState GetRequestState(Request request); + void WaitUntilNextPeriod(); } [JsonConverter(typeof(StringEnumConverter))] @@ -114,6 +116,15 @@ public RequestState GetRequestState(Request request) return gethNode.Call(Deployment.MarketplaceAddress, func); } + public void WaitUntilNextPeriod() + { + log.Log("Waiting until next proof period..."); + var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + var periodSeconds = (int)Deployment.Config.Proofs.Period; + var secondsLeft = now % periodSeconds; + Thread.Sleep(TimeSpan.FromSeconds(secondsLeft + 1)); + } + private ContractInteractions StartInteraction() { return new ContractInteractions(log, gethNode); diff --git a/ProjectPlugins/CodexContractsPlugin/CodexContractsDeployment.cs b/ProjectPlugins/CodexContractsPlugin/CodexContractsDeployment.cs index d61857c7..fe7e7d6c 100644 --- a/ProjectPlugins/CodexContractsPlugin/CodexContractsDeployment.cs +++ b/ProjectPlugins/CodexContractsPlugin/CodexContractsDeployment.cs @@ -1,14 +1,18 @@ -namespace CodexContractsPlugin +using CodexContractsPlugin.Marketplace; + +namespace CodexContractsPlugin { public class CodexContractsDeployment { - public CodexContractsDeployment(string marketplaceAddress, string abi, string tokenAddress) + public CodexContractsDeployment(MarketplaceConfig config, string marketplaceAddress, string abi, string tokenAddress) { + Config = config; MarketplaceAddress = marketplaceAddress; Abi = abi; TokenAddress = tokenAddress; } + public MarketplaceConfig Config { get; } public string MarketplaceAddress { get; } public string Abi { get; } public string TokenAddress { get; } diff --git a/ProjectPlugins/CodexContractsPlugin/CodexContractsStarter.cs b/ProjectPlugins/CodexContractsPlugin/CodexContractsStarter.cs index ed54f173..be74a3b2 100644 --- a/ProjectPlugins/CodexContractsPlugin/CodexContractsStarter.cs +++ b/ProjectPlugins/CodexContractsPlugin/CodexContractsStarter.cs @@ -4,6 +4,7 @@ using KubernetesWorkflow; using KubernetesWorkflow.Types; using Logging; +using Newtonsoft.Json; using Utils; namespace CodexContractsPlugin @@ -34,6 +35,7 @@ public CodexContractsDeployment Deploy(CoreInterface ci, IGethNode gethNode) try { var result = DeployContract(container, workflow, gethNode); + workflow.Stop(containers, waitTillStopped: false); Log("Container stopped."); return result; @@ -75,9 +77,20 @@ private CodexContractsDeployment DeployContract(RunningContainer container, ISta Time.WaitUntil(() => interaction.IsSynced(marketplaceAddress, abi), nameof(DeployContract)); - Log("Synced. Codex SmartContracts deployed."); + Log("Synced. Codex SmartContracts deployed. Getting configuration..."); + + var config = GetMarketplaceConfiguration(marketplaceAddress, gethNode); + + Log("Got config: " + JsonConvert.SerializeObject(config)); - return new CodexContractsDeployment(marketplaceAddress, abi, tokenAddress); + return new CodexContractsDeployment(config, marketplaceAddress, abi, tokenAddress); + } + + private MarketplaceConfig GetMarketplaceConfiguration(string marketplaceAddress, IGethNode gethNode) + { + var func = new ConfigurationFunctionBase(); + var response = gethNode.Call(marketplaceAddress, func); + return response.ReturnValue1; } private void EnsureCompatbility(string abi, string bytecode) diff --git a/ProjectPlugins/CodexContractsPlugin/ContractInteractions.cs b/ProjectPlugins/CodexContractsPlugin/ContractInteractions.cs index 29840f97..85d12cf7 100644 --- a/ProjectPlugins/CodexContractsPlugin/ContractInteractions.cs +++ b/ProjectPlugins/CodexContractsPlugin/ContractInteractions.cs @@ -23,9 +23,8 @@ public ContractInteractions(ILog log, IGethNode gethNode) public string GetTokenAddress(string marketplaceAddress) { log.Debug(marketplaceAddress); - var function = new GetTokenFunction(); - - return gethNode.Call(marketplaceAddress, function); + var function = new TokenFunctionBase(); + return gethNode.Call(marketplaceAddress, function); } public string GetTokenName(string tokenAddress) @@ -111,11 +110,6 @@ private bool IsContractAvailable(string marketplaceAddress, string marketplaceAb } } - [Function("token", "address")] - public class GetTokenFunction : FunctionMessage - { - } - [Function("name", "string")] public class GetTokenNameFunction : FunctionMessage { diff --git a/ProjectPlugins/CodexContractsPlugin/TestTokenExtensions.cs b/ProjectPlugins/CodexContractsPlugin/TestTokenExtensions.cs index e94032a7..ddd66759 100644 --- a/ProjectPlugins/CodexContractsPlugin/TestTokenExtensions.cs +++ b/ProjectPlugins/CodexContractsPlugin/TestTokenExtensions.cs @@ -46,6 +46,16 @@ public override string ToString() return new TestToken(a.TstWei + b.TstWei); } + public static TestToken operator -(TestToken a, TestToken b) + { + return new TestToken(a.TstWei - b.TstWei); + } + + public static TestToken operator *(TestToken a, int b) + { + return new TestToken(a.TstWei * b); + } + public static bool operator <(TestToken a, TestToken b) { return a.TstWei < b.TstWei; diff --git a/ProjectPlugins/CodexPlugin/CodexAccess.cs b/ProjectPlugins/CodexPlugin/CodexAccess.cs index e8f755cf..3e28ab32 100644 --- a/ProjectPlugins/CodexPlugin/CodexAccess.cs +++ b/ProjectPlugins/CodexPlugin/CodexAccess.cs @@ -32,6 +32,22 @@ public DebugInfo GetDebugInfo() return mapper.Map(OnCodex(api => api.GetDebugInfoAsync())); } + public string GetSpr() + { + return CrashCheck(() => + { + var endpoint = GetEndpoint(); + var json = endpoint.HttpGetString("spr"); + var response = JsonConvert.DeserializeObject(json); + return response!.Spr; + }); + } + + private class SprResponse + { + public string Spr { get; set; } = string.Empty; + } + public DebugPeer GetDebugPeer(string peerId) { // Cannot use openAPI: debug/peer endpoint is not specified there. @@ -80,6 +96,18 @@ public Stream DownloadFile(string contentId, Action onFailure) return fileResponse.Stream; } + public LocalDataset DownloadStreamless(ContentId cid) + { + var response = OnCodex(api => api.DownloadNetworkAsync(cid.Id)); + return mapper.Map(response); + } + + public LocalDataset DownloadManifestOnly(ContentId cid) + { + var response = OnCodex(api => api.DownloadNetworkManifestAsync(cid.Id)); + return mapper.Map(response); + } + public LocalDatasetList LocalFiles() { return mapper.Map(OnCodex(api => api.ListDataAsync())); diff --git a/ProjectPlugins/CodexPlugin/CodexNode.cs b/ProjectPlugins/CodexPlugin/CodexNode.cs index 004ab72d..2b6ddcb2 100644 --- a/ProjectPlugins/CodexPlugin/CodexNode.cs +++ b/ProjectPlugins/CodexPlugin/CodexNode.cs @@ -15,12 +15,15 @@ public interface ICodexNode : IHasContainer, IHasMetricsScrapeTarget, IHasEthAdd string GetName(); string GetPeerId(); DebugInfo GetDebugInfo(); + string GetSpr(); DebugPeer GetDebugPeer(string peerId); ContentId UploadFile(TrackedFile file); ContentId UploadFile(TrackedFile file, Action onFailure); ContentId UploadFile(TrackedFile file, string contentType, string contentDisposition, Action onFailure); TrackedFile? DownloadContent(ContentId contentId, string fileLabel = ""); TrackedFile? DownloadContent(ContentId contentId, Action onFailure, string fileLabel = ""); + LocalDataset DownloadStreamless(ContentId cid); + LocalDataset DownloadManifestOnly(ContentId cid); LocalDatasetList LocalFiles(); CodexSpace Space(); void ConnectToPeer(ICodexNode node); @@ -128,6 +131,11 @@ public DebugInfo GetDebugInfo() return debugInfo; } + public string GetSpr() + { + return CodexAccess.GetSpr(); + } + public DebugPeer GetDebugPeer(string peerId) { return CodexAccess.GetDebugPeer(peerId); @@ -192,6 +200,16 @@ public ContentId UploadFile(TrackedFile file, string contentType, string content return file; } + public LocalDataset DownloadStreamless(ContentId cid) + { + return CodexAccess.DownloadStreamless(cid); + } + + public LocalDataset DownloadManifestOnly(ContentId cid) + { + return CodexAccess.DownloadManifestOnly(cid); + } + public LocalDatasetList LocalFiles() { return CodexAccess.LocalFiles(); diff --git a/ProjectPlugins/CodexPlugin/CodexPlugin.cs b/ProjectPlugins/CodexPlugin/CodexPlugin.cs index 9dba163d..04c2d5f5 100644 --- a/ProjectPlugins/CodexPlugin/CodexPlugin.cs +++ b/ProjectPlugins/CodexPlugin/CodexPlugin.cs @@ -20,7 +20,7 @@ public CodexPlugin(IPluginTools tools) public void Announce() { - tools.GetLog().Log($"Loaded with Codex ID: '{codexStarter.GetCodexId()}' - Revision: {codexStarter.GetCodexRevision()}"); + Log($"Loaded with Codex ID: '{codexStarter.GetCodexId()}' - Revision: {codexStarter.GetCodexRevision()}"); } public void AddMetadata(IAddMetadata metadata) @@ -55,6 +55,10 @@ public void WireUpMarketplace(ICodexNodeGroup result, Action setup) { mconfig.GethNode.SendEth(node, mconfig.MarketplaceSetup.InitialEth); mconfig.CodexContracts.MintTestTokens(node, mconfig.MarketplaceSetup.InitialTestTokens); + + Log($"Send {mconfig.MarketplaceSetup.InitialEth} and " + + $"minted {mconfig.MarketplaceSetup.InitialTestTokens} for " + + $"{node.GetName()} (address: {node.EthAddress})"); } } @@ -70,5 +74,10 @@ private CodexSetup GetSetup(int numberOfNodes, Action setup) setup(codexSetup); return codexSetup; } + + private void Log(string msg) + { + tools.GetLog().Log(msg); + } } } diff --git a/ProjectPlugins/CodexPlugin/MarketplaceTypes.cs b/ProjectPlugins/CodexPlugin/MarketplaceTypes.cs index fcbfe63a..c92220b9 100644 --- a/ProjectPlugins/CodexPlugin/MarketplaceTypes.cs +++ b/ProjectPlugins/CodexPlugin/MarketplaceTypes.cs @@ -1,7 +1,5 @@ using CodexContractsPlugin; -using CodexOpenApi; using Logging; -using System.Data; using Utils; namespace CodexPlugin diff --git a/ProjectPlugins/CodexPlugin/StoragePurchaseContract.cs b/ProjectPlugins/CodexPlugin/StoragePurchaseContract.cs index 61f6f596..e4325cab 100644 --- a/ProjectPlugins/CodexPlugin/StoragePurchaseContract.cs +++ b/ProjectPlugins/CodexPlugin/StoragePurchaseContract.cs @@ -1,4 +1,6 @@ -using CodexPlugin.Hooks; +using CodexContractsPlugin; +using CodexPlugin.Hooks; +using GethPlugin; using Logging; using Newtonsoft.Json; using Utils; @@ -12,7 +14,7 @@ public interface IStoragePurchaseContract ContentId ContentId { get; } void WaitForStorageContractSubmitted(); void WaitForStorageContractStarted(); - void WaitForStorageContractFinished(); + void WaitForStorageContractFinished(ICodexContracts contracts); } public class StoragePurchaseContract : IStoragePurchaseContract @@ -62,7 +64,7 @@ public void WaitForStorageContractStarted() AssertDuration(SubmittedToStarted, timeout, nameof(SubmittedToStarted)); } - public void WaitForStorageContractFinished() + public void WaitForStorageContractFinished(ICodexContracts contracts) { if (!contractStartedUtc.HasValue) { @@ -74,6 +76,12 @@ public void WaitForStorageContractFinished() contractFinishedUtc = DateTime.UtcNow; LogFinishedDuration(); AssertDuration(SubmittedToFinished, timeout, nameof(SubmittedToFinished)); + + contracts.WaitUntilNextPeriod(); + + var blocks = 3; + Log($"Waiting {blocks} blocks for nodes to process payouts..."); + Thread.Sleep(GethContainerRecipe.BlockInterval * blocks); } public StoragePurchase GetPurchaseStatus(string purchaseId) diff --git a/ProjectPlugins/GethPlugin/GethContainerRecipe.cs b/ProjectPlugins/GethPlugin/GethContainerRecipe.cs index e4c2fe86..8fc5239c 100644 --- a/ProjectPlugins/GethPlugin/GethContainerRecipe.cs +++ b/ProjectPlugins/GethPlugin/GethContainerRecipe.cs @@ -6,6 +6,7 @@ namespace GethPlugin public class GethContainerRecipe : ContainerRecipeFactory { public static string DockerImage { get; } = "codexstorage/dist-tests-geth:latest"; + public static TimeSpan BlockInterval { get; } = TimeSpan.FromSeconds(1.0); private const string defaultArgs = "--ipcdisable --syncmode full"; public const string HttpPortTag = "http_port"; diff --git a/Tests/CodexContinuousTests/CodexContinuousTests.csproj b/Tests/CodexContinuousTests/CodexContinuousTests.csproj index 7b85bb0e..156df9a7 100644 --- a/Tests/CodexContinuousTests/CodexContinuousTests.csproj +++ b/Tests/CodexContinuousTests/CodexContinuousTests.csproj @@ -14,8 +14,8 @@ - + diff --git a/Tests/CodexLongTests/CodexTestsLong.csproj b/Tests/CodexLongTests/CodexTestsLong.csproj index a3a4c50c..f504cfd2 100644 --- a/Tests/CodexLongTests/CodexTestsLong.csproj +++ b/Tests/CodexLongTests/CodexTestsLong.csproj @@ -14,8 +14,8 @@ - + diff --git a/Tests/CodexReleaseTests/CodexReleaseTests.csproj b/Tests/CodexReleaseTests/CodexReleaseTests.csproj new file mode 100644 index 00000000..e7053e1d --- /dev/null +++ b/Tests/CodexReleaseTests/CodexReleaseTests.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + diff --git a/Tests/CodexTests/BasicTests/OneClientTests.cs b/Tests/CodexReleaseTests/DataTests/InterruptUploadTest.cs similarity index 66% rename from Tests/CodexTests/BasicTests/OneClientTests.cs rename to Tests/CodexReleaseTests/DataTests/InterruptUploadTest.cs index 27c855ab..ae9c9d58 100644 --- a/Tests/CodexTests/BasicTests/OneClientTests.cs +++ b/Tests/CodexReleaseTests/DataTests/InterruptUploadTest.cs @@ -1,26 +1,16 @@ using CodexPlugin; +using CodexTests; using FileUtils; using NUnit.Framework; using System.Diagnostics; using Utils; -namespace CodexTests.BasicTests +namespace CodexReleaseTests.DataTests { - [TestFixture] - public class OneClientTests : CodexDistTest + public class InterruptUploadTest : CodexDistTest { [Test] - public void OneClientTest() - { - var node = StartCodex(); - - PerformOneClientTest(node); - - LogNodeStatus(node); - } - - [Test] - public void InterruptUploadTest() + public void UploadInterruptTest() { var nodes = StartCodex(10); @@ -28,6 +18,8 @@ public void InterruptUploadTest() Task.WaitAll(tasks.ToArray()); Assert.That(tasks.Select(t => t.Result).All(r => r == true)); + + WaitAndCheckNodesStaysAlive(TimeSpan.FromMinutes(2), nodes); } private bool RunInterruptUploadTest(ICodexNode node) @@ -51,16 +43,5 @@ private Process StartCurlUploadProcess(ICodexNode node, TrackedFile file) var filePath = file.Filename; return Process.Start("curl", $"-X POST {codexUrl} -H \"Content-Type: application/octet-stream\" -T {filePath}"); } - - private void PerformOneClientTest(ICodexNode primary) - { - var testFile = GenerateTestFile(1.MB()); - - var contentId = primary.UploadFile(testFile); - - var downloadedFile = primary.DownloadContent(contentId); - - testFile.AssertIsEqual(downloadedFile); - } } } diff --git a/Tests/CodexReleaseTests/DataTests/ManifestOnlyDownloadTest.cs b/Tests/CodexReleaseTests/DataTests/ManifestOnlyDownloadTest.cs new file mode 100644 index 00000000..c55b3f8f --- /dev/null +++ b/Tests/CodexReleaseTests/DataTests/ManifestOnlyDownloadTest.cs @@ -0,0 +1,33 @@ +using CodexTests; +using NUnit.Framework; +using Utils; + +namespace CodexReleaseTests.DataTests +{ + [TestFixture] + public class ManifestOnlyDownloadTest : CodexDistTest + { + [Test] + public void ManifestOnlyTest() + { + var uploader = StartCodex(); + var downloader = StartCodex(s => s.WithBootstrapNode(uploader)); + + var file = GenerateTestFile(2.GB()); + var size = file.GetFilesize().SizeInBytes; + var cid = uploader.UploadFile(file); + + var startSpace = downloader.Space(); + var localDataset = downloader.DownloadManifestOnly(cid); + + Thread.Sleep(1000); + + var spaceDiff = startSpace.FreeBytes - downloader.Space().FreeBytes; + + Assert.That(spaceDiff, Is.LessThan(64.KB().SizeInBytes)); + + Assert.That(localDataset.Cid, Is.EqualTo(cid)); + Assert.That(localDataset.Manifest.OriginalBytes.SizeInBytes, Is.EqualTo(file.GetFilesize().SizeInBytes)); + } + } +} diff --git a/Tests/CodexReleaseTests/DataTests/OneClientTest.cs b/Tests/CodexReleaseTests/DataTests/OneClientTest.cs new file mode 100644 index 00000000..1be82b4d --- /dev/null +++ b/Tests/CodexReleaseTests/DataTests/OneClientTest.cs @@ -0,0 +1,39 @@ +using CodexPlugin; +using CodexTests; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Utils; + +namespace CodexReleaseTests.DataTests +{ + [TestFixture] + public class OneClientTest : CodexDistTest + { + [Test] + public void OneClient() + { + var node = StartCodex(); + + PerformOneClientTest(node); + + LogNodeStatus(node); + } + + private void PerformOneClientTest(ICodexNode primary) + { + var testFile = GenerateTestFile(1.MB()); + + var contentId = primary.UploadFile(testFile); + + AssertNodesContainFile(contentId, primary); + + var downloadedFile = primary.DownloadContent(contentId); + + testFile.AssertIsEqual(downloadedFile); + } + } +} diff --git a/Tests/CodexReleaseTests/DataTests/StreamlessDownloadTest.cs b/Tests/CodexReleaseTests/DataTests/StreamlessDownloadTest.cs new file mode 100644 index 00000000..b7fb5071 --- /dev/null +++ b/Tests/CodexReleaseTests/DataTests/StreamlessDownloadTest.cs @@ -0,0 +1,47 @@ +using CodexTests; +using NUnit.Framework; +using Utils; + +namespace CodexReleaseTests.DataTests +{ + [TestFixture] + public class StreamlessDownloadTest : CodexDistTest + { + [Test] + public void StreamlessTest() + { + var uploader = StartCodex(); + var downloader = StartCodex(s => s.WithBootstrapNode(uploader)); + + var file = GenerateTestFile(10.MB()); + var size = file.GetFilesize().SizeInBytes; + var cid = uploader.UploadFile(file); + + var startSpace = downloader.Space(); + var start = DateTime.UtcNow; + var localDataset = downloader.DownloadStreamless(cid); + + Assert.That(localDataset.Cid, Is.EqualTo(cid)); + Assert.That(localDataset.Manifest.OriginalBytes.SizeInBytes, Is.EqualTo(file.GetFilesize().SizeInBytes)); + + // TODO: We have no way to inspect the status or progress of the download. + // We use local space information to estimate. + var retry = new Retry("Checking local space", + maxTimeout: TimeSpan.FromMinutes(2), + sleepAfterFail: TimeSpan.FromSeconds(3), + onFail: f => { }); + + retry.Run(() => + { + var space = downloader.Space(); + var expected = startSpace.FreeBytes - size; + if (space.FreeBytes > expected) throw new Exception("Expected free space not reached."); + }); + + // Stop the uploader node and verify that the downloader has the data. + uploader.Stop(waitTillStopped: true); + var downloaded = downloader.DownloadContent(cid); + file.AssertIsEqual(downloaded); + } + } +} diff --git a/Tests/CodexReleaseTests/DataTests/SwarmTest.cs b/Tests/CodexReleaseTests/DataTests/SwarmTest.cs new file mode 100644 index 00000000..c08d2011 --- /dev/null +++ b/Tests/CodexReleaseTests/DataTests/SwarmTest.cs @@ -0,0 +1,92 @@ +using CodexPlugin; +using CodexTests; +using FileUtils; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Utils; + +namespace CodexReleaseTests.DataTests +{ + [TestFixture] + public class SwarmTests : AutoBootstrapDistTest + { + private const int NumberOfNodes = 5; + private const int FileSizeMb = 2; + + [Test] + public void SmallSwarm() + { + var nodes = StartCodex(NumberOfNodes); + var files = nodes.Select(UploadUniqueFilePerNode).ToArray(); + + var tasks = ParallelDownloadEachFile(nodes, files); + Task.WaitAll(tasks); + + AssertAllFilesDownloadedCorrectly(files); + } + + private SwarmTestNetworkFile UploadUniqueFilePerNode(ICodexNode node) + { + var file = GenerateTestFile(FileSizeMb.MB()); + var cid = node.UploadFile(file); + return new SwarmTestNetworkFile(file, cid); + } + + private Task[] ParallelDownloadEachFile(ICodexNodeGroup nodes, SwarmTestNetworkFile[] files) + { + var tasks = new List(); + + foreach (var node in nodes) + { + foreach (var file in files) + { + tasks.Add(StartDownload(node, file)); + } + } + + return tasks.ToArray(); + } + + private Task StartDownload(ICodexNode node, SwarmTestNetworkFile file) + { + return Task.Run(() => + { + try + { + file.Downloaded = node.DownloadContent(file.Cid); + } + catch (Exception ex) + { + file.Error = ex; + } + }); + } + + private void AssertAllFilesDownloadedCorrectly(SwarmTestNetworkFile[] files) + { + foreach (var file in files) + { + if (file.Error != null) throw file.Error; + file.Original.AssertIsEqual(file.Downloaded); + } + } + + private class SwarmTestNetworkFile + { + public SwarmTestNetworkFile(TrackedFile original, ContentId cid) + { + Original = original; + Cid = cid; + } + + public TrackedFile Original { get; } + public ContentId Cid { get; } + public TrackedFile? Downloaded { get; set; } + public Exception? Error { get; set; } = null; + } + } +} diff --git a/Tests/CodexReleaseTests/DataTests/ThreeClientTest.cs b/Tests/CodexReleaseTests/DataTests/ThreeClientTest.cs new file mode 100644 index 00000000..a99f0ae1 --- /dev/null +++ b/Tests/CodexReleaseTests/DataTests/ThreeClientTest.cs @@ -0,0 +1,32 @@ +using CodexTests; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Utils; + +namespace CodexReleaseTests.DataTests +{ + public class ThreeClientTest : AutoBootstrapDistTest + { + [Test] + public void ThreeClient() + { + var primary = StartCodex(); + var secondary = StartCodex(); + + var testFile = GenerateTestFile(10.MB()); + + var contentId = primary.UploadFile(testFile); + AssertNodesContainFile(contentId, primary); + + var downloadedFile = secondary.DownloadContent(contentId); + AssertNodesContainFile(contentId, primary, secondary); + + testFile.AssertIsEqual(downloadedFile); + } + } +} diff --git a/Tests/CodexTests/BasicTests/TwoClientTests.cs b/Tests/CodexReleaseTests/DataTests/TwoClientTest.cs similarity index 84% rename from Tests/CodexTests/BasicTests/TwoClientTests.cs rename to Tests/CodexReleaseTests/DataTests/TwoClientTest.cs index ab15ff25..c2bf324f 100644 --- a/Tests/CodexTests/BasicTests/TwoClientTests.cs +++ b/Tests/CodexReleaseTests/DataTests/TwoClientTest.cs @@ -1,8 +1,14 @@ using CodexPlugin; +using CodexTests; using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; using Utils; -namespace CodexTests.BasicTests +namespace CodexReleaseTests.DataTests { [TestFixture] public class TwoClientTests : CodexDistTest @@ -42,8 +48,10 @@ private void PerformTwoClientTest(ICodexNode uploader, ICodexNode downloader, By var testFile = GenerateTestFile(size); var contentId = uploader.UploadFile(testFile); + AssertNodesContainFile(contentId, uploader); var downloadedFile = downloader.DownloadContent(contentId); + AssertNodesContainFile(contentId, uploader, downloader); testFile.AssertIsEqual(downloadedFile); CheckLogForErrors(uploader, downloader); diff --git a/Tests/CodexReleaseTests/DataTests/UnknownCidTest.cs b/Tests/CodexReleaseTests/DataTests/UnknownCidTest.cs new file mode 100644 index 00000000..d0699a53 --- /dev/null +++ b/Tests/CodexReleaseTests/DataTests/UnknownCidTest.cs @@ -0,0 +1,38 @@ +using CodexPlugin; +using CodexTests; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodexReleaseTests.DataTests +{ + [TestFixture] + public class UnknownCidTest : CodexDistTest + { + [Test] + public void DownloadingUnknownCidDoesNotCauseCrash() + { + var node = StartCodex(); + + var unknownCid = new ContentId("zDvZRwzkzHsok3Z8yMoiXE9EDBFwgr8WygB8s4ddcLzzSwwXAxLZ"); + + var localFiles = node.LocalFiles().Content; + CollectionAssert.DoesNotContain(localFiles.Select(f => f.Cid), unknownCid); + + try + { + node.DownloadContent(unknownCid); + } + catch (Exception ex) + { + var expectedMessage = $"Download of '{unknownCid.Id}' timed out"; + if (!ex.Message.StartsWith(expectedMessage)) throw; + } + + WaitAndCheckNodesStaysAlive(TimeSpan.FromMinutes(2), node); + } + } +} diff --git a/Tests/CodexReleaseTests/MarketTests/ContractFailedTest.cs b/Tests/CodexReleaseTests/MarketTests/ContractFailedTest.cs new file mode 100644 index 00000000..2d30584c --- /dev/null +++ b/Tests/CodexReleaseTests/MarketTests/ContractFailedTest.cs @@ -0,0 +1,20 @@ +using CodexTests; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodexReleaseTests.MarketTests +{ + public class ContractFailedTest : CodexDistTest + { + [Test] + [Ignore("TODO - Test in which hosts are punished for failing a contract")] + public void ContractFailed() + { + + } + } +} diff --git a/Tests/CodexReleaseTests/MarketTests/ContractRepairedTest.cs b/Tests/CodexReleaseTests/MarketTests/ContractRepairedTest.cs new file mode 100644 index 00000000..752afe18 --- /dev/null +++ b/Tests/CodexReleaseTests/MarketTests/ContractRepairedTest.cs @@ -0,0 +1,18 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodexReleaseTests.MarketTests +{ + public class ContractRepairedTest + { + [Test] + [Ignore("TODO - Test in which a host fails, but the slot is repaired")] + public void ContractRepaired() + { + } + } +} diff --git a/Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs b/Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs new file mode 100644 index 00000000..989da95d --- /dev/null +++ b/Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs @@ -0,0 +1,73 @@ +using CodexContractsPlugin; +using CodexPlugin; +using GethPlugin; +using NUnit.Framework; +using Utils; + +namespace CodexReleaseTests.MarketTests +{ + [TestFixture] + public class ContractSuccessfulTest : MarketplaceAutoBootstrapDistTest + { + private const int FilesizeMb = 10; + + protected override int NumberOfHosts => 4; + protected override int NumberOfClients => 1; + protected override ByteSize HostAvailabilitySize => (5 * FilesizeMb).MB(); + protected override TimeSpan HostAvailabilityMaxDuration => Get8TimesConfiguredPeriodDuration(); + private readonly TestToken pricePerSlotPerSecond = 10.TstWei(); + + [Test] + public void ContractSuccessful() + { + var hosts = StartHosts(); + var client = StartClients().Single(); + + var request = CreateStorageRequest(client); + + request.WaitForStorageContractSubmitted(); + AssertContractIsOnChain(request); + + request.WaitForStorageContractStarted(); + AssertContractSlotsAreFilledByHosts(request, hosts); + + request.WaitForStorageContractFinished(GetContracts()); + + AssertClientHasPaidForContract(pricePerSlotPerSecond, client, request, hosts); + AssertHostsWerePaidForContract(pricePerSlotPerSecond, request, hosts); + AssertHostsCollateralsAreUnchanged(hosts); + } + + private IStoragePurchaseContract CreateStorageRequest(ICodexNode client) + { + var cid = client.UploadFile(GenerateTestFile(FilesizeMb.MB())); + var config = GetContracts().Deployment.Config; + return client.Marketplace.RequestStorage(new StoragePurchaseRequest(cid) + { + Duration = GetContractDuration(), + Expiry = GetContractExpiry(), + MinRequiredNumberOfNodes = (uint)NumberOfHosts, + NodeFailureTolerance = (uint)(NumberOfHosts / 2), + PricePerSlotPerSecond = pricePerSlotPerSecond, + ProofProbability = 20, + RequiredCollateral = 1.Tst() + }); + } + + private TimeSpan GetContractExpiry() + { + return GetContractDuration() / 2; + } + + private TimeSpan GetContractDuration() + { + return Get8TimesConfiguredPeriodDuration() / 2; + } + + private TimeSpan Get8TimesConfiguredPeriodDuration() + { + var config = GetContracts().Deployment.Config; + return TimeSpan.FromSeconds(((double)config.Proofs.Period) * 8.0); + } + } +} diff --git a/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs b/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs new file mode 100644 index 00000000..e2594edb --- /dev/null +++ b/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs @@ -0,0 +1,254 @@ +using CodexContractsPlugin; +using CodexContractsPlugin.Marketplace; +using CodexPlugin; +using CodexTests; +using DistTestCore; +using GethPlugin; +using Nethereum.Hex.HexConvertors.Extensions; +using NUnit.Framework; +using Utils; + +namespace CodexReleaseTests.MarketTests +{ + public abstract class MarketplaceAutoBootstrapDistTest : AutoBootstrapDistTest + { + private readonly Dictionary handles = new Dictionary(); + protected const int StartingBalanceTST = 1000; + protected const int StartingBalanceEth = 10; + + protected override void LifecycleStart(TestLifecycle lifecycle) + { + base.LifecycleStart(lifecycle); + var geth = Ci.StartGethNode(s => s.IsMiner()); + var contracts = Ci.StartCodexContracts(geth); + handles.Add(lifecycle, new MarketplaceHandle(geth, contracts)); + } + + protected override void LifecycleStop(TestLifecycle lifecycle, DistTestResult result) + { + base.LifecycleStop(lifecycle, result); + handles.Remove(lifecycle); + } + + protected IGethNode GetGeth() + { + return handles[Get()].Geth; + } + + protected ICodexContracts GetContracts() + { + return handles[Get()].Contracts; + } + + protected abstract int NumberOfHosts { get; } + protected abstract int NumberOfClients { get; } + protected abstract ByteSize HostAvailabilitySize { get; } + protected abstract TimeSpan HostAvailabilityMaxDuration { get; } + + public ICodexNodeGroup StartHosts() + { + var hosts = StartCodex(NumberOfHosts, s => s + .WithName("host") + .EnableMarketplace(GetGeth(), GetContracts(), m => m + .WithInitial(StartingBalanceEth.Eth(), StartingBalanceTST.Tst()) + .AsStorageNode() + ) + ); + + var config = GetContracts().Deployment.Config; + foreach (var host in hosts) + { + Assert.That(GetTstBalance(host).TstWei, Is.EqualTo(StartingBalanceTST.Tst().TstWei)); + Assert.That(GetEthBalance(host).Wei, Is.EqualTo(StartingBalanceEth.Eth().Wei)); + + host.Marketplace.MakeStorageAvailable(new CodexPlugin.StorageAvailability( + totalSpace: HostAvailabilitySize, + maxDuration: HostAvailabilityMaxDuration, + minPriceForTotalSpace: 1.TstWei(), + maxCollateral: 999999.Tst()) + ); + } + return hosts; + } + + public TestToken GetTstBalance(ICodexNode node) + { + return GetContracts().GetTestTokenBalance(node); + } + + public TestToken GetTstBalance(EthAddress address) + { + return GetContracts().GetTestTokenBalance(address); + } + + public Ether GetEthBalance(ICodexNode node) + { + return GetGeth().GetEthBalance(node); + } + + public Ether GetEthBalance(EthAddress address) + { + return GetGeth().GetEthBalance(address); + } + + public ICodexNodeGroup StartClients() + { + return StartCodex(NumberOfClients, s => s + .WithName("client") + .EnableMarketplace(GetGeth(), GetContracts(), m => m + .WithInitial(StartingBalanceEth.Eth(), StartingBalanceTST.Tst()) + ) + ); + } + + public SlotFill[] GetOnChainSlotFills(ICodexNodeGroup possibleHosts, string purchaseId) + { + var fills = GetOnChainSlotFills(possibleHosts); + return fills.Where(f => f + .SlotFilledEvent.RequestId.ToHex(false).ToLowerInvariant() == purchaseId.ToLowerInvariant()) + .ToArray(); + } + + public SlotFill[] GetOnChainSlotFills(ICodexNodeGroup possibleHosts) + { + var events = GetContracts().GetEvents(GetTestRunTimeRange()); + var fills = events.GetSlotFilledEvents(); + return fills.Select(f => + { + var host = possibleHosts.Single(h => h.EthAddress.Address == f.Host.Address); + return new SlotFill(f, host); + + }).ToArray(); + } + + protected void AssertClientHasPaidForContract(TestToken pricePerSlotPerSecond, ICodexNode client, IStoragePurchaseContract contract, ICodexNodeGroup hosts) + { + var balance = GetTstBalance(client); + var expectedBalance = StartingBalanceTST.Tst() - GetContractFinalCost(pricePerSlotPerSecond, contract, hosts); + + Assert.That(balance, Is.EqualTo(expectedBalance), "Client balance incorrect."); + } + + protected void AssertHostsWerePaidForContract(TestToken pricePerSlotPerSecond, IStoragePurchaseContract contract, ICodexNodeGroup hosts) + { + var fills = GetOnChainSlotFills(hosts); + var submitUtc = GetContractOnChainSubmittedUtc(contract); + var finishUtc = submitUtc + contract.Purchase.Duration; + var expectedBalances = new Dictionary(); + foreach (var host in hosts) expectedBalances.Add(host.EthAddress, StartingBalanceTST.Tst()); + foreach (var fill in fills) + { + var slotDuration = finishUtc - fill.SlotFilledEvent.Block.Utc; + expectedBalances[fill.Host.EthAddress] += GetContractCostPerSlot(pricePerSlotPerSecond, slotDuration); + } + + foreach (var pair in expectedBalances) + { + var balance = GetTstBalance(pair.Key); + Assert.That(balance, Is.EqualTo(pair.Value), "Host was not paid for storage."); + } + } + + protected void AssertHostsCollateralsAreUnchanged(ICodexNodeGroup hosts) + { + // There is no separate collateral location yet. + // All host balances should be equal to or greater than the starting balance. + foreach (var host in hosts) + { + Assert.That(GetTstBalance(host), Is.GreaterThanOrEqualTo(StartingBalanceTST.Tst())); + } + } + + private TestToken GetContractFinalCost(TestToken pricePerSlotPerSecond, IStoragePurchaseContract contract, ICodexNodeGroup hosts) + { + var fills = GetOnChainSlotFills(hosts); + var result = 0.Tst(); + var submitUtc = GetContractOnChainSubmittedUtc(contract); + var finishUtc = submitUtc + contract.Purchase.Duration; + + foreach (var fill in fills) + { + var slotDuration = finishUtc - fill.SlotFilledEvent.Block.Utc; + result += GetContractCostPerSlot(pricePerSlotPerSecond, slotDuration); + } + + return result; + } + + private DateTime GetContractOnChainSubmittedUtc(IStoragePurchaseContract contract) + { + var events = GetContracts().GetEvents(GetTestRunTimeRange()); + var submitEvent = events.GetStorageRequests().Single(e => e.RequestId.ToHex(false) == contract.PurchaseId); + return submitEvent.Block.Utc; + } + + private TestToken GetContractCostPerSlot(TestToken pricePerSlotPerSecond, TimeSpan slotDuration) + { + return pricePerSlotPerSecond * (int)slotDuration.TotalSeconds; + } + + protected void AssertContractSlotsAreFilledByHosts(IStoragePurchaseContract contract, ICodexNodeGroup hosts) + { + var activeHosts = new Dictionary(); + + Time.Retry(() => + { + var fills = GetOnChainSlotFills(hosts, contract.PurchaseId); + foreach (var fill in fills) + { + var index = (int)fill.SlotFilledEvent.SlotIndex; + if (!activeHosts.ContainsKey(index)) + { + activeHosts.Add(index, fill); + } + } + + if (activeHosts.Count != contract.Purchase.MinRequiredNumberOfNodes) throw new Exception("Not all slots were filled..."); + + }, nameof(AssertContractSlotsAreFilledByHosts)); + } + + protected void AssertContractIsOnChain(IStoragePurchaseContract contract) + { + AssertOnChainEvents(events => + { + var onChainRequests = events.GetStorageRequests(); + if (onChainRequests.Any(r => r.Id == contract.PurchaseId)) return; + throw new Exception($"OnChain request {contract.PurchaseId} not found..."); + }, nameof(AssertContractIsOnChain)); + } + + protected void AssertOnChainEvents(Action onEvents, string description) + { + Time.Retry(() => + { + var events = GetContracts().GetEvents(GetTestRunTimeRange()); + onEvents(events); + }, description); + } + + public class SlotFill + { + public SlotFill(SlotFilledEventDTO slotFilledEvent, ICodexNode host) + { + SlotFilledEvent = slotFilledEvent; + Host = host; + } + + public SlotFilledEventDTO SlotFilledEvent { get; } + public ICodexNode Host { get; } + } + + private class MarketplaceHandle + { + public MarketplaceHandle(IGethNode geth, ICodexContracts contracts) + { + Geth = geth; + Contracts = contracts; + } + + public IGethNode Geth { get; } + public ICodexContracts Contracts { get; } + } + } +} diff --git a/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs b/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs new file mode 100644 index 00000000..50293c1d --- /dev/null +++ b/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs @@ -0,0 +1,83 @@ +using CodexContractsPlugin; +using CodexPlugin; +using GethPlugin; +using NUnit.Framework; +using Utils; + +namespace CodexReleaseTests.MarketTests +{ + [TestFixture] + public class MultipleContractsTest : MarketplaceAutoBootstrapDistTest + { + private const int FilesizeMb = 10; + + protected override int NumberOfHosts => 8; + protected override int NumberOfClients => 3; + protected override ByteSize HostAvailabilitySize => (5 * FilesizeMb).MB(); + protected override TimeSpan HostAvailabilityMaxDuration => Get8TimesConfiguredPeriodDuration(); + private readonly TestToken pricePerSlotPerSecond = 10.TstWei(); + + [Test] + [Ignore("TODO - Test where multiple successful contracts are run simultaenously")] + public void MultipleSuccessfulContracts() + { + var hosts = StartHosts(); + var clients = StartClients(); + + var requests = clients.Select(c => CreateStorageRequest(c)).ToArray(); + + All(requests, r => + { + r.WaitForStorageContractSubmitted(); + AssertContractIsOnChain(r); + }); + + All(requests, r => r.WaitForStorageContractStarted()); + All(requests, r => AssertContractSlotsAreFilledByHosts(r, hosts)); + + All(requests, r => r.WaitForStorageContractFinished(GetContracts())); + + // todo: + //AssertClientHasPaidForContract(pricePerSlotPerSecond, client, request, hosts); + //AssertHostsWerePaidForContract(pricePerSlotPerSecond, request, hosts); + //AssertHostsCollateralsAreUnchanged(hosts); + } + + private void All(IStoragePurchaseContract[] requests, Action action) + { + foreach (var r in requests) action(r); + } + + private IStoragePurchaseContract CreateStorageRequest(ICodexNode client) + { + var cid = client.UploadFile(GenerateTestFile(FilesizeMb.MB())); + var config = GetContracts().Deployment.Config; + return client.Marketplace.RequestStorage(new StoragePurchaseRequest(cid) + { + Duration = GetContractDuration(), + Expiry = GetContractExpiry(), + MinRequiredNumberOfNodes = (uint)NumberOfHosts, + NodeFailureTolerance = (uint)(NumberOfHosts / 2), + PricePerSlotPerSecond = pricePerSlotPerSecond, + ProofProbability = 20, + RequiredCollateral = 1.Tst() + }); + } + + private TimeSpan GetContractExpiry() + { + return GetContractDuration() / 2; + } + + private TimeSpan GetContractDuration() + { + return Get8TimesConfiguredPeriodDuration() / 2; + } + + private TimeSpan Get8TimesConfiguredPeriodDuration() + { + var config = GetContracts().Deployment.Config; + return TimeSpan.FromSeconds(((double)config.Proofs.Period) * 8.0); + } + } +} diff --git a/Tests/CodexReleaseTests/NodeTests/BasicInfoTests.cs b/Tests/CodexReleaseTests/NodeTests/BasicInfoTests.cs new file mode 100644 index 00000000..914bedd7 --- /dev/null +++ b/Tests/CodexReleaseTests/NodeTests/BasicInfoTests.cs @@ -0,0 +1,62 @@ +using CodexPlugin; +using CodexTests; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Utils; + +namespace CodexReleaseTests.NodeTests +{ + [TestFixture] + public class BasicInfoTests : CodexDistTest + { + [Test] + public void QuotaTest() + { + var size = 3.GB(); + var node = StartCodex(s => s.WithStorageQuota(size)); + var space = node.Space(); + + Assert.That(space.QuotaMaxBytes, Is.EqualTo(size.SizeInBytes)); + } + + [Test] + public void Spr() + { + var node = StartCodex(); + + var info = node.GetDebugInfo(); + Assert.That(!string.IsNullOrEmpty(info.Spr)); + + var spr = node.GetSpr(); + Assert.That(!string.IsNullOrEmpty(spr)); + + Assert.That(info.Spr, Is.EqualTo(spr)); + } + + [Test] + public void VersionInfo() + { + var node = StartCodex(); + + var info = node.GetDebugInfo(); + Assert.That(!string.IsNullOrEmpty(info.Version.Version)); + Assert.That(!string.IsNullOrEmpty(info.Version.Revision)); + } + + [Test] + public void AnnounceAddress() + { + var node = StartCodex(); + var addr = node.Container.GetInternalAddress(CodexContainerRecipe.ListenPortTag); + + var info = node.GetDebugInfo(); + + Assert.That(info.AnnounceAddresses.Count, Is.GreaterThan(0)); + // Ideally we'd assert the pod IP is in the announce address, but we can't access it from here. + } + } +} diff --git a/Tests/CodexReleaseTests/NodeTests/PeerTableTests.cs b/Tests/CodexReleaseTests/NodeTests/PeerTableTests.cs new file mode 100644 index 00000000..f61a4219 --- /dev/null +++ b/Tests/CodexReleaseTests/NodeTests/PeerTableTests.cs @@ -0,0 +1,60 @@ +using CodexPlugin; +using CodexTests; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Utils; + +namespace CodexReleaseTests.NodeTests +{ + [TestFixture] + public class PeerTableTests : AutoBootstrapDistTest + { + [Test] + public void PeerTableCompleteness() + { + var nodes = StartCodex(10); + + var retry = new Retry( + description: nameof(PeerTableCompleteness), + maxTimeout: TimeSpan.FromMinutes(2), + sleepAfterFail: TimeSpan.FromSeconds(5), + onFail: f => { } + ); + + retry.Run(() => AssertAllNodesSeeEachOther(nodes)); + } + + private void AssertAllNodesSeeEachOther(ICodexNodeGroup nodes) + { + foreach (var a in nodes) + { + AssertHasSeenAllOtherNodes(a, nodes); + } + } + + private void AssertHasSeenAllOtherNodes(ICodexNode node, ICodexNodeGroup nodes) + { + var localNode = node.GetDebugInfo().Table.LocalNode; + + foreach (var other in nodes) + { + var info = other.GetDebugInfo(); + if (info.Table.LocalNode.PeerId != localNode.PeerId) + { + AssertContainsPeerId(info, localNode.PeerId); + } + } + } + + private void AssertContainsPeerId(DebugInfo info, string peerId) + { + var entry = info.Table.Nodes.SingleOrDefault(n => n.PeerId == peerId); + if (entry == null) throw new Exception("Table entry not found."); + if (!entry.Seen) throw new Exception("Peer not seen."); + } + } +} diff --git a/Tests/CodexReleaseTests/Parallelism.cs b/Tests/CodexReleaseTests/Parallelism.cs new file mode 100644 index 00000000..a1b26c73 --- /dev/null +++ b/Tests/CodexReleaseTests/Parallelism.cs @@ -0,0 +1,6 @@ +using NUnit.Framework; + +[assembly: LevelOfParallelism(1)] +namespace CodexReleaseTests.DataTests +{ +} diff --git a/Tests/CodexTests/BasicTests/ThreeClientTest.cs b/Tests/CodexTests/BasicTests/ThreeClientTest.cs deleted file mode 100644 index e6f9428e..00000000 --- a/Tests/CodexTests/BasicTests/ThreeClientTest.cs +++ /dev/null @@ -1,54 +0,0 @@ -using CodexPlugin; -using NUnit.Framework; -using Utils; - -namespace CodexTests.BasicTests -{ - [TestFixture] - public class ThreeClientTest : AutoBootstrapDistTest - { - [Test] - public void ThreeClient() - { - var primary = StartCodex(); - var secondary = StartCodex(); - - var testFile = GenerateTestFile(10.MB()); - - var contentId = primary.UploadFile(testFile); - - var downloadedFile = secondary.DownloadContent(contentId); - - testFile.AssertIsEqual(downloadedFile); - } - - [Test] - public void DownloadingUnknownCidDoesNotCauseCrash() - { - var node = StartCodex(2).First(); - - var unknownCid = new ContentId("zDvZRwzkzHsok3Z8yMoiXE9EDBFwgr8WygB8s4ddcLzzSwwXAxLZ"); - - try - { - node.DownloadContent(unknownCid); - } - catch (Exception ex) - { - if (!ex.Message.StartsWith("Retry 'DownloadFile' timed out")) - { - throw; - } - } - - // Check that the node stays alive for at least another 5 minutes. - var start = DateTime.UtcNow; - while ((DateTime.UtcNow - start) < TimeSpan.FromMinutes(5)) - { - Thread.Sleep(5000); - var info = node.GetDebugInfo(); - Assert.That(!string.IsNullOrEmpty(info.Id)); - } - } - } -} diff --git a/Tests/CodexTests/AutoBootstrapDistTest.cs b/Tests/ExperimentalTests/AutoBootstrapDistTest.cs similarity index 100% rename from Tests/CodexTests/AutoBootstrapDistTest.cs rename to Tests/ExperimentalTests/AutoBootstrapDistTest.cs diff --git a/Tests/CodexTests/BasicTests/AsyncProfiling.cs b/Tests/ExperimentalTests/BasicTests/AsyncProfiling.cs similarity index 100% rename from Tests/CodexTests/BasicTests/AsyncProfiling.cs rename to Tests/ExperimentalTests/BasicTests/AsyncProfiling.cs diff --git a/Tests/CodexTests/BasicTests/ExampleTests.cs b/Tests/ExperimentalTests/BasicTests/ExampleTests.cs similarity index 100% rename from Tests/CodexTests/BasicTests/ExampleTests.cs rename to Tests/ExperimentalTests/BasicTests/ExampleTests.cs diff --git a/Tests/CodexTests/BasicTests/MarketplaceTests.cs b/Tests/ExperimentalTests/BasicTests/MarketplaceTests.cs similarity index 98% rename from Tests/CodexTests/BasicTests/MarketplaceTests.cs rename to Tests/ExperimentalTests/BasicTests/MarketplaceTests.cs index 9bb0f6f1..d8e178f4 100644 --- a/Tests/CodexTests/BasicTests/MarketplaceTests.cs +++ b/Tests/ExperimentalTests/BasicTests/MarketplaceTests.cs @@ -107,7 +107,7 @@ public void MarketplaceExample( AssertStorageRequest(request, purchase, contracts, client); AssertContractSlot(contracts, request, 0); - purchaseContract.WaitForStorageContractFinished(); + purchaseContract.WaitForStorageContractFinished(contracts); AssertBalance(contracts, client, Is.LessThan(clientInitialBalance), "Buyer was not charged for storage."); Assert.That(contracts.GetRequestState(request), Is.EqualTo(RequestState.Finished)); diff --git a/Tests/CodexTests/BasicTests/PyramidTests.cs b/Tests/ExperimentalTests/BasicTests/PyramidTests.cs similarity index 100% rename from Tests/CodexTests/BasicTests/PyramidTests.cs rename to Tests/ExperimentalTests/BasicTests/PyramidTests.cs diff --git a/Tests/CodexTests/CodexDistTest.cs b/Tests/ExperimentalTests/CodexDistTest.cs similarity index 86% rename from Tests/CodexTests/CodexDistTest.cs rename to Tests/ExperimentalTests/CodexDistTest.cs index b17a37a1..d8f75e84 100644 --- a/Tests/CodexTests/CodexDistTest.cs +++ b/Tests/ExperimentalTests/CodexDistTest.cs @@ -108,6 +108,39 @@ public void LogNodeStatus(ICodexNode node, IMetricsAccess? metrics = null) GetBasicNodeStatus(node)); } + public void WaitAndCheckNodesStaysAlive(TimeSpan duration, ICodexNodeGroup nodes) + { + WaitAndCheckNodesStaysAlive(duration, nodes.ToArray()); + } + + public void WaitAndCheckNodesStaysAlive(TimeSpan duration, params ICodexNode[] nodes) + { + var start = DateTime.UtcNow; + while ((DateTime.UtcNow - start) < duration) + { + Thread.Sleep(5000); + foreach (var node in nodes) + { + var info = node.GetDebugInfo(); + Assert.That(!string.IsNullOrEmpty(info.Id)); + } + } + } + + public void AssertNodesContainFile(ContentId cid, ICodexNodeGroup nodes) + { + AssertNodesContainFile(cid, nodes.ToArray()); + } + + public void AssertNodesContainFile(ContentId cid, params ICodexNode[] nodes) + { + foreach (var node in nodes) + { + var localDatasets = node.LocalFiles(); + CollectionAssert.Contains(localDatasets.Content.Select(c => c.Cid), cid); + } + } + private string GetBasicNodeStatus(ICodexNode node) { return JsonConvert.SerializeObject(node.GetDebugInfo(), Formatting.Indented) + Environment.NewLine + diff --git a/Tests/CodexTests/DownloadConnectivityTests/SwarmTests.cs b/Tests/ExperimentalTests/DownloadConnectivityTests/DetectBlockRetransmitTest.cs similarity index 90% rename from Tests/CodexTests/DownloadConnectivityTests/SwarmTests.cs rename to Tests/ExperimentalTests/DownloadConnectivityTests/DetectBlockRetransmitTest.cs index ee4ad46f..6f007c4e 100644 --- a/Tests/CodexTests/DownloadConnectivityTests/SwarmTests.cs +++ b/Tests/ExperimentalTests/DownloadConnectivityTests/DetectBlockRetransmitTest.cs @@ -4,7 +4,7 @@ namespace CodexTests.DownloadConnectivityTests { [TestFixture] - public class SwarmTests : AutoBootstrapDistTest + public class DetectBlockRetransmitTest : AutoBootstrapDistTest { [Test] [Combinatorial] diff --git a/Tests/CodexTests/DownloadConnectivityTests/FullyConnectedDownloadTests.cs b/Tests/ExperimentalTests/DownloadConnectivityTests/FullyConnectedDownloadTests.cs similarity index 100% rename from Tests/CodexTests/DownloadConnectivityTests/FullyConnectedDownloadTests.cs rename to Tests/ExperimentalTests/DownloadConnectivityTests/FullyConnectedDownloadTests.cs diff --git a/Tests/CodexTests/DownloadConnectivityTests/MultiswarmTests.cs b/Tests/ExperimentalTests/DownloadConnectivityTests/MultiswarmTests.cs similarity index 100% rename from Tests/CodexTests/DownloadConnectivityTests/MultiswarmTests.cs rename to Tests/ExperimentalTests/DownloadConnectivityTests/MultiswarmTests.cs diff --git a/Tests/CodexTests/CodexTests.csproj b/Tests/ExperimentalTests/ExperimentalTests.csproj similarity index 100% rename from Tests/CodexTests/CodexTests.csproj rename to Tests/ExperimentalTests/ExperimentalTests.csproj diff --git a/Tests/CodexTests/Helpers/FullConnectivityHelper.cs b/Tests/ExperimentalTests/Helpers/FullConnectivityHelper.cs similarity index 100% rename from Tests/CodexTests/Helpers/FullConnectivityHelper.cs rename to Tests/ExperimentalTests/Helpers/FullConnectivityHelper.cs diff --git a/Tests/CodexTests/Helpers/PeerConnectionTestHelpers.cs b/Tests/ExperimentalTests/Helpers/PeerConnectionTestHelpers.cs similarity index 100% rename from Tests/CodexTests/Helpers/PeerConnectionTestHelpers.cs rename to Tests/ExperimentalTests/Helpers/PeerConnectionTestHelpers.cs diff --git a/Tests/CodexTests/Helpers/PeerDownloadTestHelpers.cs b/Tests/ExperimentalTests/Helpers/PeerDownloadTestHelpers.cs similarity index 100% rename from Tests/CodexTests/Helpers/PeerDownloadTestHelpers.cs rename to Tests/ExperimentalTests/Helpers/PeerDownloadTestHelpers.cs diff --git a/Tests/CodexTests/MetricsAccessExtensions.cs b/Tests/ExperimentalTests/MetricsAccessExtensions.cs similarity index 100% rename from Tests/CodexTests/MetricsAccessExtensions.cs rename to Tests/ExperimentalTests/MetricsAccessExtensions.cs diff --git a/Tests/CodexTests/Parallelism.cs b/Tests/ExperimentalTests/Parallelism.cs similarity index 100% rename from Tests/CodexTests/Parallelism.cs rename to Tests/ExperimentalTests/Parallelism.cs diff --git a/Tests/CodexTests/PeerDiscoveryTests/LayeredDiscoveryTests.cs b/Tests/ExperimentalTests/PeerDiscoveryTests/LayeredDiscoveryTests.cs similarity index 100% rename from Tests/CodexTests/PeerDiscoveryTests/LayeredDiscoveryTests.cs rename to Tests/ExperimentalTests/PeerDiscoveryTests/LayeredDiscoveryTests.cs diff --git a/Tests/CodexTests/PeerDiscoveryTests/PeerDiscoveryTests.cs b/Tests/ExperimentalTests/PeerDiscoveryTests/PeerDiscoveryTests.cs similarity index 100% rename from Tests/CodexTests/PeerDiscoveryTests/PeerDiscoveryTests.cs rename to Tests/ExperimentalTests/PeerDiscoveryTests/PeerDiscoveryTests.cs diff --git a/Tests/CodexTests/README.md b/Tests/ExperimentalTests/README.md similarity index 100% rename from Tests/CodexTests/README.md rename to Tests/ExperimentalTests/README.md diff --git a/Tests/CodexTests/UtilityTests/ClusterSpeedTests.cs b/Tests/ExperimentalTests/UtilityTests/ClusterSpeedTests.cs similarity index 100% rename from Tests/CodexTests/UtilityTests/ClusterSpeedTests.cs rename to Tests/ExperimentalTests/UtilityTests/ClusterSpeedTests.cs diff --git a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs b/Tests/ExperimentalTests/UtilityTests/DiscordBotTests.cs similarity index 99% rename from Tests/CodexTests/UtilityTests/DiscordBotTests.cs rename to Tests/ExperimentalTests/UtilityTests/DiscordBotTests.cs index 50094dcd..b22fcba0 100644 --- a/Tests/CodexTests/UtilityTests/DiscordBotTests.cs +++ b/Tests/ExperimentalTests/UtilityTests/DiscordBotTests.cs @@ -45,7 +45,7 @@ public void BotRewardTest() var purchaseContract = ClientPurchasesStorage(client); purchaseContract.WaitForStorageContractStarted(); - purchaseContract.WaitForStorageContractFinished(); + purchaseContract.WaitForStorageContractFinished(contracts); Thread.Sleep(rewarderInterval * 3); apiCalls.Stop(); diff --git a/Tests/CodexTests/UtilityTests/LogHelperTests.cs b/Tests/ExperimentalTests/UtilityTests/LogHelperTests.cs similarity index 100% rename from Tests/CodexTests/UtilityTests/LogHelperTests.cs rename to Tests/ExperimentalTests/UtilityTests/LogHelperTests.cs diff --git a/Tests/CodexTests/UtilityTests/NetworkIsolationTest.cs b/Tests/ExperimentalTests/UtilityTests/NetworkIsolationTest.cs similarity index 100% rename from Tests/CodexTests/UtilityTests/NetworkIsolationTest.cs rename to Tests/ExperimentalTests/UtilityTests/NetworkIsolationTest.cs diff --git a/Tools/CodexNetDeployer/CodexNetDeployer.csproj b/Tools/CodexNetDeployer/CodexNetDeployer.csproj index 8f403fd3..89a5d72d 100644 --- a/Tools/CodexNetDeployer/CodexNetDeployer.csproj +++ b/Tools/CodexNetDeployer/CodexNetDeployer.csproj @@ -12,7 +12,7 @@ - + diff --git a/cs-codex-dist-testing.sln b/cs-codex-dist-testing.sln index 67f63100..b95938f2 100644 --- a/cs-codex-dist-testing.sln +++ b/cs-codex-dist-testing.sln @@ -37,8 +37,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodexContinuousTests", "Tes EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodexTestsLong", "Tests\CodexLongTests\CodexTestsLong.csproj", "{0C2D067F-053C-45A8-AE0D-4EB388E77C89}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodexTests", "Tests\CodexTests\CodexTests.csproj", "{562EC700-6984-4C9A-83BF-3BF4E3EB1A64}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DistTestCore", "Tests\DistTestCore\DistTestCore.csproj", "{E849B7BA-FDCC-4CFF-998F-845ED2F1BF40}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodexNetDeployer", "Tools\CodexNetDeployer\CodexNetDeployer.csproj", "{3417D508-E2F4-4974-8988-BB124046D9E2}" @@ -76,7 +74,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TranscriptAnalysis", "Tools EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MarketInsights", "Tools\MarketInsights\MarketInsights.csproj", "{004614DF-1C65-45E3-882D-59AE44282573}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CsvCombiner", "Tools\CsvCombiner\CsvCombiner.csproj", "{6230347F-5045-4E25-8E7A-13D7221B7444}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CsvCombiner", "Tools\CsvCombiner\CsvCombiner.csproj", "{6230347F-5045-4E25-8E7A-13D7221B7444}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodexReleaseTests", "Tests\CodexReleaseTests\CodexReleaseTests.csproj", "{639A0603-4E80-465B-BB59-AB02F1DEEF5A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExperimentalTests", "Tests\ExperimentalTests\ExperimentalTests.csproj", "{BA7369CD-7C2F-4075-8E35-98BCC19EE203}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -136,10 +138,6 @@ Global {0C2D067F-053C-45A8-AE0D-4EB388E77C89}.Debug|Any CPU.Build.0 = Debug|Any CPU {0C2D067F-053C-45A8-AE0D-4EB388E77C89}.Release|Any CPU.ActiveCfg = Release|Any CPU {0C2D067F-053C-45A8-AE0D-4EB388E77C89}.Release|Any CPU.Build.0 = Release|Any CPU - {562EC700-6984-4C9A-83BF-3BF4E3EB1A64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {562EC700-6984-4C9A-83BF-3BF4E3EB1A64}.Debug|Any CPU.Build.0 = Debug|Any CPU - {562EC700-6984-4C9A-83BF-3BF4E3EB1A64}.Release|Any CPU.ActiveCfg = Release|Any CPU - {562EC700-6984-4C9A-83BF-3BF4E3EB1A64}.Release|Any CPU.Build.0 = Release|Any CPU {E849B7BA-FDCC-4CFF-998F-845ED2F1BF40}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E849B7BA-FDCC-4CFF-998F-845ED2F1BF40}.Debug|Any CPU.Build.0 = Debug|Any CPU {E849B7BA-FDCC-4CFF-998F-845ED2F1BF40}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -208,6 +206,14 @@ Global {6230347F-5045-4E25-8E7A-13D7221B7444}.Debug|Any CPU.Build.0 = Debug|Any CPU {6230347F-5045-4E25-8E7A-13D7221B7444}.Release|Any CPU.ActiveCfg = Release|Any CPU {6230347F-5045-4E25-8E7A-13D7221B7444}.Release|Any CPU.Build.0 = Release|Any CPU + {639A0603-4E80-465B-BB59-AB02F1DEEF5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {639A0603-4E80-465B-BB59-AB02F1DEEF5A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {639A0603-4E80-465B-BB59-AB02F1DEEF5A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {639A0603-4E80-465B-BB59-AB02F1DEEF5A}.Release|Any CPU.Build.0 = Release|Any CPU + {BA7369CD-7C2F-4075-8E35-98BCC19EE203}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BA7369CD-7C2F-4075-8E35-98BCC19EE203}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BA7369CD-7C2F-4075-8E35-98BCC19EE203}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BA7369CD-7C2F-4075-8E35-98BCC19EE203}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -226,7 +232,6 @@ Global {8DE8FF65-23CB-4FB3-8BE5-6C0BEC4BAA97} = {8F1F1C2A-E313-4E0C-BE40-58FB0BA91124} {ADEC06CF-6F3A-44C5-AA57-EAB94124AC82} = {88C2A621-8A98-4D07-8625-7900FC8EF89E} {0C2D067F-053C-45A8-AE0D-4EB388E77C89} = {88C2A621-8A98-4D07-8625-7900FC8EF89E} - {562EC700-6984-4C9A-83BF-3BF4E3EB1A64} = {88C2A621-8A98-4D07-8625-7900FC8EF89E} {E849B7BA-FDCC-4CFF-998F-845ED2F1BF40} = {88C2A621-8A98-4D07-8625-7900FC8EF89E} {3417D508-E2F4-4974-8988-BB124046D9E2} = {7591C5B3-D86E-4AE4-8ED2-B272D17FE7E3} {078ABA6D-A04E-4F62-A44C-EA66F1B66548} = {7591C5B3-D86E-4AE4-8ED2-B272D17FE7E3} @@ -244,6 +249,8 @@ Global {C0EEBD32-23CB-45EC-A863-79FB948508C8} = {7591C5B3-D86E-4AE4-8ED2-B272D17FE7E3} {004614DF-1C65-45E3-882D-59AE44282573} = {7591C5B3-D86E-4AE4-8ED2-B272D17FE7E3} {6230347F-5045-4E25-8E7A-13D7221B7444} = {7591C5B3-D86E-4AE4-8ED2-B272D17FE7E3} + {639A0603-4E80-465B-BB59-AB02F1DEEF5A} = {88C2A621-8A98-4D07-8625-7900FC8EF89E} + {BA7369CD-7C2F-4075-8E35-98BCC19EE203} = {88C2A621-8A98-4D07-8625-7900FC8EF89E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {237BF0AA-9EC4-4659-AD9A-65DEB974250C} diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh index 8e023b90..6246d70b 100644 --- a/docker/docker-entrypoint.sh +++ b/docker/docker-entrypoint.sh @@ -1,35 +1,33 @@ #!/bin/bash -# Common +# Variables +## Common SOURCE="${SOURCE:-https://github.com/codex-storage/cs-codex-dist-tests.git}" BRANCH="${BRANCH:-master}" FOLDER="${FOLDER:-/opt/cs-codex-dist-tests}" -# Continuous Tests +## Tests specific DEPLOYMENT_CODEXNETDEPLOYER_PATH="${DEPLOYMENT_CODEXNETDEPLOYER_PATH:-Tools/CodexNetDeployer}" DEPLOYMENT_CODEXNETDEPLOYER_RUNNER="${DEPLOYMENT_CODEXNETDEPLOYER_RUNNER:-deploy-continuous-testnet.sh}" CONTINUOUS_TESTS_FOLDER="${CONTINUOUS_TESTS_FOLDER:-Tests/CodexContinuousTests}" CONTINUOUS_TESTS_RUNNER="${CONTINUOUS_TESTS_RUNNER:-run.sh}" - # Get code -echo "`date` - Clone ${SOURCE}" +echo -e "Cloning ${SOURCE} to ${FOLDER}\n" git clone -b "${BRANCH}" "${SOURCE}" "${FOLDER}" -echo "`date` - Change folder to ${FOLDER}" +echo -e "\nChanging folder to ${FOLDER}\n" cd "${FOLDER}" -# Run -echo "Run tests from branch '`git branch --show-current` / `git rev-parse HEAD`'" +# Run tests +echo -e "Running tests from branch '$(git branch --show-current) ($(git rev-parse --short HEAD))'\n" if [[ "${TESTS_TYPE}" == "continuous-tests" ]]; then - echo "`date` - Running Continuous Tests" - echo - echo "`date` - Running CodexNetDeployer" + echo -e "Running CodexNetDeployer\n" bash "${DEPLOYMENT_CODEXNETDEPLOYER_PATH}"/"${DEPLOYMENT_CODEXNETDEPLOYER_RUNNER}" echo - echo "`date` - Running Tests" + echo -e "Running continuous-tests\n" bash "${CONTINUOUS_TESTS_FOLDER}"/"${CONTINUOUS_TESTS_RUNNER}" else - echo "`date` - Running Dist Tests" + echo -e "Running ${TESTS_TYPE}\n" exec "$@" fi diff --git a/docker/continuous-tests-job.yaml b/docker/job-continuous-tests.yaml similarity index 100% rename from docker/continuous-tests-job.yaml rename to docker/job-continuous-tests.yaml diff --git a/docker/dist-tests-job.yaml b/docker/job-dist-tests.yaml similarity index 100% rename from docker/dist-tests-job.yaml rename to docker/job-dist-tests.yaml diff --git a/docker/job-release-tests.yaml b/docker/job-release-tests.yaml new file mode 100644 index 00000000..21de935a --- /dev/null +++ b/docker/job-release-tests.yaml @@ -0,0 +1,63 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: ${NAMEPREFIX} + namespace: ${NAMESPACE} + labels: + name: ${NAMEPREFIX} + runid: ${RUNID} +spec: + ttlSecondsAfterFinished: 86400 + backoffLimit: 0 + template: + metadata: + name: ${NAMEPREFIX} + labels: + app: ${TEST_TYPE}-runner + name: ${NAMEPREFIX} + runid: ${RUNID} + spec: + priorityClassName: system-node-critical + nodeSelector: + workload-type: "tests-runners-ci" + containers: + - name: runner + image: codexstorage/cs-codex-dist-tests:latest + imagePullPolicy: Always + resources: + requests: + memory: "1Gi" + env: + - name: KUBECONFIG + value: "/opt/kubeconfig.yaml" + - name: LOGPATH + value: "/var/log/codex-${TEST_TYPE}" + - name: NAMESPACE + value: "${NAMESPACE}" + - name: BRANCH + value: "${BRANCH}" + - name: SOURCE + value: "${SOURCE}" + - name: RUNID + value: "${RUNID}" + - name: CODEXDOCKERIMAGE + value: "${CODEXDOCKERIMAGE}" + - name: TESTID + value: "${TESTID}" + - name: TESTS_TYPE + value: "${TEST_TYPE}" + volumeMounts: + - name: kubeconfig + mountPath: /opt/kubeconfig.yaml + subPath: kubeconfig.yaml + - name: logs + mountPath: /var/log/codex-${TEST_TYPE} + args: ${COMMAND} + restartPolicy: Never + volumes: + - name: kubeconfig + secret: + secretName: codex-dist-tests-app-kubeconfig + - name: logs + hostPath: + path: /var/log/codex-${TEST_TYPE}