2
0
mirror of synced 2025-02-23 05:28:17 +00:00

Moving all codex details to CodexPlugin

This commit is contained in:
benbierens 2023-09-11 11:59:33 +02:00
parent 6915e90861
commit 83d184177a
No known key found for this signature in database
GPG Key ID: FE44815D96D0A1AA
85 changed files with 2414 additions and 2979 deletions

View File

@ -1,8 +1,9 @@
using KubernetesWorkflow;
using DistTestCore;
using KubernetesWorkflow;
using Logging;
using Utils;
namespace DistTestCore.Codex
namespace CodexPlugin
{
public class CodexAccess : ILogHandler
{

View File

@ -1,6 +1,6 @@
using Newtonsoft.Json;
namespace DistTestCore.Codex
namespace CodexPlugin
{
public class CodexDebugResponse
{

View File

@ -1,8 +1,9 @@
using DistTestCore.Marketplace;
//using DistTestCore.Marketplace;
using DistTestCore;
using KubernetesWorkflow;
using Utils;
namespace DistTestCore.Codex
namespace CodexPlugin
{
public class CodexContainerRecipe : DefaultContainerRecipe
{
@ -66,36 +67,36 @@ namespace DistTestCore.Codex
{
AddEnvVar("CODEX_BLOCK_MN", config.BlockMaintenanceNumber.ToString()!);
}
if (config.MetricsMode != Metrics.MetricsMode.None)
{
var metricsPort = AddInternalPort(MetricsPortTag);
AddEnvVar("CODEX_METRICS", "true");
AddEnvVar("CODEX_METRICS_ADDRESS", "0.0.0.0");
AddEnvVar("CODEX_METRICS_PORT", metricsPort);
AddPodAnnotation("prometheus.io/scrape", "true");
AddPodAnnotation("prometheus.io/port", metricsPort.Number.ToString());
}
//if (config.MetricsMode != Metrics.MetricsMode.None)
//{
// var metricsPort = AddInternalPort(MetricsPortTag);
// AddEnvVar("CODEX_METRICS", "true");
// AddEnvVar("CODEX_METRICS_ADDRESS", "0.0.0.0");
// AddEnvVar("CODEX_METRICS_PORT", metricsPort);
// AddPodAnnotation("prometheus.io/scrape", "true");
// AddPodAnnotation("prometheus.io/port", metricsPort.Number.ToString());
//}
if (config.MarketplaceConfig != null)
{
var gethConfig = startupConfig.Get<GethStartResult>();
var companionNode = gethConfig.CompanionNode;
var companionNodeAccount = companionNode.Accounts[GetAccountIndex(config.MarketplaceConfig)];
Additional(companionNodeAccount);
//if (config.MarketplaceConfig != null)
//{
// var gethConfig = startupConfig.Get<GethStartResult>();
// var companionNode = gethConfig.CompanionNode;
// var companionNodeAccount = companionNode.Accounts[GetAccountIndex(config.MarketplaceConfig)];
// Additional(companionNodeAccount);
var ip = companionNode.RunningContainer.Pod.PodInfo.Ip;
var port = companionNode.RunningContainer.Recipe.GetPortByTag(GethContainerRecipe.HttpPortTag).Number;
// var ip = companionNode.RunningContainer.Pod.PodInfo.Ip;
// var port = companionNode.RunningContainer.Recipe.GetPortByTag(GethContainerRecipe.HttpPortTag).Number;
AddEnvVar("CODEX_ETH_PROVIDER", $"ws://{ip}:{port}");
AddEnvVar("CODEX_ETH_ACCOUNT", companionNodeAccount.Account);
AddEnvVar("CODEX_MARKETPLACE_ADDRESS", gethConfig.MarketplaceNetwork.Marketplace.Address);
AddEnvVar("CODEX_PERSISTENCE", "true");
// AddEnvVar("CODEX_ETH_PROVIDER", $"ws://{ip}:{port}");
// AddEnvVar("CODEX_ETH_ACCOUNT", companionNodeAccount.Account);
// AddEnvVar("CODEX_MARKETPLACE_ADDRESS", gethConfig.MarketplaceNetwork.Marketplace.Address);
// AddEnvVar("CODEX_PERSISTENCE", "true");
if (config.MarketplaceConfig.IsValidator)
{
AddEnvVar("CODEX_VALIDATOR", "true");
}
}
// if (config.MarketplaceConfig.IsValidator)
// {
// AddEnvVar("CODEX_VALIDATOR", "true");
// }
//}
}
private ByteSize GetVolumeCapacity(CodexStartupConfig config)
@ -105,11 +106,11 @@ namespace DistTestCore.Codex
return 8.GB().Multiply(1.2);
}
private int GetAccountIndex(MarketplaceInitialConfig marketplaceConfig)
{
if (marketplaceConfig.AccountIndexOverride != null) return marketplaceConfig.AccountIndexOverride.Value;
return Index;
}
//private int GetAccountIndex(MarketplaceInitialConfig marketplaceConfig)
//{
// if (marketplaceConfig.AccountIndexOverride != null) return marketplaceConfig.AccountIndexOverride.Value;
// return Index;
//}
private string GetDockerImage()
{

View File

@ -1,23 +1,22 @@
using DistTestCore.Marketplace;
using KubernetesWorkflow;
using KubernetesWorkflow;
namespace DistTestCore.Codex
namespace CodexPlugin
{
public class CodexDeployment
{
public CodexDeployment(GethStartResult gethStartResult, RunningContainer[] codexContainers, RunningContainer? prometheusContainer, GrafanaStartInfo? grafanaStartInfo, DeploymentMetadata metadata)
public CodexDeployment(/*GethStartResult gethStartResult,*/ RunningContainer[] codexContainers, RunningContainer? prometheusContainer, /*GrafanaStartInfo? grafanaStartInfo,*/ DeploymentMetadata metadata)
{
GethStartResult = gethStartResult;
//GethStartResult = gethStartResult;
CodexContainers = codexContainers;
PrometheusContainer = prometheusContainer;
GrafanaStartInfo = grafanaStartInfo;
//GrafanaStartInfo = grafanaStartInfo;
Metadata = metadata;
}
public GethStartResult GethStartResult { get; }
//public GethStartResult GethStartResult { get; }
public RunningContainer[] CodexContainers { get; }
public RunningContainer? PrometheusContainer { get; }
public GrafanaStartInfo? GrafanaStartInfo { get; }
//public GrafanaStartInfo? GrafanaStartInfo { get; }
public DeploymentMetadata Metadata { get; }
}

View File

@ -1,4 +1,4 @@
namespace DistTestCore.Codex
namespace CodexPlugin
{
public enum CodexLogLevel
{

View File

@ -0,0 +1,29 @@

namespace CodexPlugin
{
public interface ICodexNodeFactory
{
//OnlineCodexNode CreateOnlineCodexNode(CodexAccess access, CodexNodeGroup group);
}
public class CodexNodeFactory : ICodexNodeFactory
{
//private readonly TestLifecycle lifecycle;
//private readonly IMetricsAccessFactory metricsAccessFactory;
//private readonly IMarketplaceAccessFactory marketplaceAccessFactory;
//public CodexNodeFactory(TestLifecycle lifecycle, IMetricsAccessFactory metricsAccessFactory, IMarketplaceAccessFactory marketplaceAccessFactory)
//{
// this.lifecycle = lifecycle;
// this.metricsAccessFactory = metricsAccessFactory;
// this.marketplaceAccessFactory = marketplaceAccessFactory;
//}
//public OnlineCodexNode CreateOnlineCodexNode(CodexAccess access, CodexNodeGroup group)
//{
// var metricsAccess = metricsAccessFactory.CreateMetricsAccess(access.Container);
// var marketplaceAccess = marketplaceAccessFactory.CreateMarketplaceAccess(access);
// return new OnlineCodexNode(lifecycle, access, group, metricsAccess, marketplaceAccess);
//}
}
}

View File

@ -1,8 +1,7 @@
using DistTestCore.Codex;
using KubernetesWorkflow;
using KubernetesWorkflow;
using System.Collections;
namespace DistTestCore
namespace CodexPlugin
{
public interface ICodexNodeGroup : IEnumerable<IOnlineCodexNode>
{
@ -12,11 +11,11 @@ namespace DistTestCore
public class CodexNodeGroup : ICodexNodeGroup
{
private readonly TestLifecycle lifecycle;
//private readonly TestLifecycle lifecycle;
public CodexNodeGroup(TestLifecycle lifecycle, CodexSetup setup, RunningContainers[] containers, ICodexNodeFactory codexNodeFactory)
public CodexNodeGroup(/*TestLifecycle lifecycle, */CodexSetup setup, RunningContainers[] containers, ICodexNodeFactory codexNodeFactory)
{
this.lifecycle = lifecycle;
//this.lifecycle = lifecycle;
Setup = setup;
Containers = containers;
Nodes = containers.Containers().Select(c => CreateOnlineCodexNode(c, codexNodeFactory)).ToArray();
@ -33,7 +32,7 @@ namespace DistTestCore
public ICodexSetup BringOffline()
{
lifecycle.CodexStarter.BringOffline(this);
//lifecycle.CodexStarter.BringOffline(this);
var result = Setup;
// Clear everything. Prevent accidental use.
@ -81,8 +80,9 @@ namespace DistTestCore
private OnlineCodexNode CreateOnlineCodexNode(RunningContainer c, ICodexNodeFactory factory)
{
var access = new CodexAccess(lifecycle.Log, c, lifecycle.TimeSet, lifecycle.Configuration.GetAddress(c));
return factory.CreateOnlineCodexNode(access, this);
//var access = new CodexAccess(lifecycle.Log, c, lifecycle.TimeSet, lifecycle.Configuration.GetAddress(c));
//return factory.CreateOnlineCodexNode(access, this);
return null!;
}
}
}

View File

@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<None Remove="Metrics\dashboard.json" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Metrics\dashboard.json">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DistTestCore\DistTestCore.csproj" />
<ProjectReference Include="..\KubernetesWorkflow\KubernetesWorkflow.csproj" />
</ItemGroup>
</Project>

View File

@ -1,9 +1,7 @@
using DistTestCore.Codex;
using DistTestCore.Marketplace;
using KubernetesWorkflow;
using KubernetesWorkflow;
using Utils;
namespace DistTestCore
namespace CodexPlugin
{
public interface ICodexSetup
{
@ -14,10 +12,10 @@ namespace DistTestCore
ICodexSetup WithBlockTTL(TimeSpan duration);
ICodexSetup WithBlockMaintenanceInterval(TimeSpan duration);
ICodexSetup WithBlockMaintenanceNumber(int numberOfBlocks);
ICodexSetup EnableMetrics();
ICodexSetup EnableMarketplace(TestToken initialBalance);
ICodexSetup EnableMarketplace(TestToken initialBalance, Ether initialEther);
ICodexSetup EnableMarketplace(TestToken initialBalance, Ether initialEther, bool isValidator);
//ICodexSetup EnableMetrics();
//ICodexSetup EnableMarketplace(TestToken initialBalance);
//ICodexSetup EnableMarketplace(TestToken initialBalance, Ether initialEther);
//ICodexSetup EnableMarketplace(TestToken initialBalance, Ether initialEther, bool isValidator);
}
public class CodexSetup : CodexStartupConfig, ICodexSetup
@ -72,27 +70,27 @@ namespace DistTestCore
return this;
}
public ICodexSetup EnableMetrics()
{
MetricsMode = Metrics.MetricsMode.Record;
return this;
}
//public ICodexSetup EnableMetrics()
//{
// MetricsMode = Metrics.MetricsMode.Record;
// return this;
//}
public ICodexSetup EnableMarketplace(TestToken initialBalance)
{
return EnableMarketplace(initialBalance, 1000.Eth());
}
//public ICodexSetup EnableMarketplace(TestToken initialBalance)
//{
// return EnableMarketplace(initialBalance, 1000.Eth());
//}
public ICodexSetup EnableMarketplace(TestToken initialBalance, Ether initialEther)
{
return EnableMarketplace(initialBalance, initialEther, false);
}
//public ICodexSetup EnableMarketplace(TestToken initialBalance, Ether initialEther)
//{
// return EnableMarketplace(initialBalance, initialEther, false);
//}
public ICodexSetup EnableMarketplace(TestToken initialBalance, Ether initialEther, bool isValidator)
{
MarketplaceConfig = new MarketplaceInitialConfig(initialEther, initialBalance, isValidator);
return this;
}
//public ICodexSetup EnableMarketplace(TestToken initialBalance, Ether initialEther, bool isValidator)
//{
// MarketplaceConfig = new MarketplaceInitialConfig(initialEther, initialBalance, isValidator);
// return this;
//}
public string Describe()
{

156
CodexPlugin/CodexStarter.cs Normal file
View File

@ -0,0 +1,156 @@
using KubernetesWorkflow;
using Logging;
namespace CodexPlugin
{
public class CodexStarter //: BaseStarter
{
//public CodexStarter(TestLifecycle lifecycle)
// : base(lifecycle)
//{
//}
public List<CodexNodeGroup> RunningGroups { get; } = new List<CodexNodeGroup>();
public ICodexNodeGroup BringOnline(CodexSetup codexSetup)
{
//LogSeparator();
//LogStart($"Starting {codexSetup.Describe()}...");
//var gethStartResult = lifecycle.GethStarter.BringOnlineMarketplaceFor(codexSetup);
//var startupConfig = CreateStartupConfig(gethStartResult, codexSetup);
//var containers = StartCodexContainers(startupConfig, codexSetup.NumberOfNodes, codexSetup.Location);
//var metricAccessFactory = CollectMetrics(codexSetup, containers);
//var codexNodeFactory = new CodexNodeFactory(lifecycle, metricAccessFactory, gethStartResult.MarketplaceAccessFactory);
//var group = CreateCodexGroup(codexSetup, containers, codexNodeFactory);
//lifecycle.SetCodexVersion(group.Version);
//var nl = Environment.NewLine;
//var podInfos = string.Join(nl, containers.Containers().Select(c => $"Container: '{c.Name}' runs at '{c.Pod.PodInfo.K8SNodeName}'={c.Pod.PodInfo.Ip}"));
//LogEnd($"Started {codexSetup.NumberOfNodes} nodes " +
// $"of image '{containers.Containers().First().Recipe.Image}' " +
// $"and version '{group.Version}'{nl}" +
// podInfos);
//LogSeparator();
//return group;
return null!;
}
public void BringOffline(CodexNodeGroup group)
{
//LogStart($"Stopping {group.Describe()}...");
//var workflow = CreateWorkflow();
//foreach (var c in group.Containers)
//{
// StopCrashWatcher(c);
// workflow.Stop(c);
//}
//RunningGroups.Remove(group);
//LogEnd("Stopped.");
}
public void DeleteAllResources()
{
//var workflow = CreateWorkflow();
//workflow.DeleteTestResources();
//RunningGroups.Clear();
}
public void DownloadLog(RunningContainer container, ILogHandler logHandler, int? tailLines)
{
//var workflow = CreateWorkflow();
//workflow.DownloadContainerLog(container, logHandler, tailLines);
}
//private IMetricsAccessFactory CollectMetrics(CodexSetup codexSetup, RunningContainers[] containers)
//{
// if (codexSetup.MetricsMode == MetricsMode.None) return new MetricsUnavailableAccessFactory();
// var runningContainers = lifecycle.PrometheusStarter.CollectMetricsFor(containers);
// if (codexSetup.MetricsMode == MetricsMode.Dashboard)
// {
// lifecycle.GrafanaStarter.StartDashboard(runningContainers.Containers.First(), codexSetup);
// }
// return new CodexNodeMetricsAccessFactory(lifecycle, runningContainers);
//}
//private StartupConfig CreateStartupConfig(GethStartResult gethStartResult, CodexSetup codexSetup)
//{
// var startupConfig = new StartupConfig();
// startupConfig.NameOverride = codexSetup.NameOverride;
// startupConfig.Add(codexSetup);
// startupConfig.Add(gethStartResult);
// return startupConfig;
//}
//private RunningContainers[] StartCodexContainers(StartupConfig startupConfig, int numberOfNodes, Location location)
//{
// var result = new List<RunningContainers>();
// var recipe = new CodexContainerRecipe();
// for (var i = 0; i < numberOfNodes; i++)
// {
// var workflow = CreateWorkflow();
// var rc = workflow.Start(1, location, recipe, startupConfig);
// CreateCrashWatcher(workflow, rc);
// result.Add(rc);
// }
// return result.ToArray();
//}
//private CodexNodeGroup CreateCodexGroup(CodexSetup codexSetup, RunningContainers[] runningContainers, CodexNodeFactory codexNodeFactory)
//{
// var group = new CodexNodeGroup(lifecycle, codexSetup, runningContainers, codexNodeFactory);
// RunningGroups.Add(group);
// try
// {
// Stopwatch.Measure(lifecycle.Log, "EnsureOnline", group.EnsureOnline, debug: true);
// }
// catch
// {
// CodexNodesNotOnline(runningContainers);
// throw;
// }
// return group;
//}
//private void CodexNodesNotOnline(RunningContainers[] runningContainers)
//{
// Log("Codex nodes failed to start");
// foreach (var container in runningContainers.Containers()) lifecycle.DownloadLog(container);
//}
//private StartupWorkflow CreateWorkflow()
//{
// return lifecycle.WorkflowCreator.CreateWorkflow();
//}
//private void LogSeparator()
//{
// Log("----------------------------------------------------------------------------");
//}
//private void CreateCrashWatcher(StartupWorkflow workflow, RunningContainers rc)
//{
// var c = rc.Containers.Single();
// c.CrashWatcher = workflow.CreateCrashWatcher(c);
//}
//private void StopCrashWatcher(RunningContainers containers)
//{
// foreach (var c in containers.Containers)
// {
// c.CrashWatcher?.Stop();
// }
//}
}
}

View File

@ -1,9 +1,7 @@
using DistTestCore.Marketplace;
using DistTestCore.Metrics;
using KubernetesWorkflow;
using KubernetesWorkflow;
using Utils;
namespace DistTestCore.Codex
namespace CodexPlugin
{
public class CodexStartupConfig
{
@ -16,8 +14,8 @@ namespace DistTestCore.Codex
public Location Location { get; set; }
public CodexLogLevel LogLevel { get; }
public ByteSize? StorageQuota { get; set; }
public MetricsMode MetricsMode { get; set; }
public MarketplaceInitialConfig? MarketplaceConfig { get; set; }
//public MetricsMode MetricsMode { get; set; }
//public MarketplaceInitialConfig? MarketplaceConfig { get; set; }
public string? BootstrapSpr { get; set; }
public int? BlockTTL { get; set; }
public TimeSpan? BlockMaintenanceInterval { get; set; }

View File

@ -0,0 +1,28 @@
using DistTestCore;
using KubernetesWorkflow;
namespace CodexPlugin
{
public static class DistTestExtensions
{
public static RunningContainers StartCodexNodes(this DistTest distTest, int number, Action<ICodexSetup> setup)
{
return null!;
}
public static ICodexNodeGroup WrapCodexContainers(this DistTest distTest, RunningContainers containers)
{
return null!;
}
public static IOnlineCodexNode SetupCodexNode(this DistTest distTest, Action<ICodexSetup> setup)
{
return null!;
}
public static ICodexNodeGroup SetupCodexNodes(this DistTest distTest, int number)
{
return null!;
}
}
}

View File

@ -0,0 +1,88 @@
//using DistTestCore.Marketplace;
//namespace CodexPlugin
//{
// public class GethStarter : BaseStarter
// {
// private readonly MarketplaceNetworkCache marketplaceNetworkCache;
// private readonly GethCompanionNodeStarter companionNodeStarter;
// public GethStarter(TestLifecycle lifecycle)
// : base(lifecycle)
// {
// marketplaceNetworkCache = new MarketplaceNetworkCache(
// new GethBootstrapNodeStarter(lifecycle),
// new CodexContractsStarter(lifecycle));
// companionNodeStarter = new GethCompanionNodeStarter(lifecycle);
// }
// public GethStartResult BringOnlineMarketplaceFor(CodexSetup codexSetup)
// {
// if (codexSetup.MarketplaceConfig == null) return CreateMarketplaceUnavailableResult();
// var marketplaceNetwork = marketplaceNetworkCache.Get();
// var companionNode = StartCompanionNode(codexSetup, marketplaceNetwork);
// LogStart("Setting up initial balance...");
// TransferInitialBalance(marketplaceNetwork, codexSetup.MarketplaceConfig, companionNode);
// LogEnd($"Initial balance of {codexSetup.MarketplaceConfig.InitialTestTokens} set for {codexSetup.NumberOfNodes} nodes.");
// return CreateGethStartResult(marketplaceNetwork, companionNode);
// }
// private void TransferInitialBalance(MarketplaceNetwork marketplaceNetwork, MarketplaceInitialConfig marketplaceConfig, GethCompanionNodeInfo companionNode)
// {
// if (marketplaceConfig.InitialTestTokens.Amount == 0) return;
// var interaction = marketplaceNetwork.StartInteraction(lifecycle);
// var tokenAddress = marketplaceNetwork.Marketplace.TokenAddress;
// var accounts = companionNode.Accounts.Select(a => a.Account).ToArray();
// interaction.MintTestTokens(accounts, marketplaceConfig.InitialTestTokens.Amount, tokenAddress);
// }
// private GethStartResult CreateGethStartResult(MarketplaceNetwork marketplaceNetwork, GethCompanionNodeInfo companionNode)
// {
// return new GethStartResult(CreateMarketplaceAccessFactory(marketplaceNetwork), marketplaceNetwork, companionNode);
// }
// private GethStartResult CreateMarketplaceUnavailableResult()
// {
// return new GethStartResult(new MarketplaceUnavailableAccessFactory(), null!, null!);
// }
// private IMarketplaceAccessFactory CreateMarketplaceAccessFactory(MarketplaceNetwork marketplaceNetwork)
// {
// return new GethMarketplaceAccessFactory(lifecycle, marketplaceNetwork);
// }
// private GethCompanionNodeInfo StartCompanionNode(CodexSetup codexSetup, MarketplaceNetwork marketplaceNetwork)
// {
// return companionNodeStarter.StartCompanionNodeFor(codexSetup, marketplaceNetwork);
// }
// }
// public class MarketplaceNetworkCache
// {
// private readonly GethBootstrapNodeStarter bootstrapNodeStarter;
// private readonly CodexContractsStarter codexContractsStarter;
// private MarketplaceNetwork? network;
// public MarketplaceNetworkCache(GethBootstrapNodeStarter bootstrapNodeStarter, CodexContractsStarter codexContractsStarter)
// {
// this.bootstrapNodeStarter = bootstrapNodeStarter;
// this.codexContractsStarter = codexContractsStarter;
// }
// public MarketplaceNetwork Get()
// {
// if (network == null)
// {
// var bootstrapInfo = bootstrapNodeStarter.StartGethBootstrapNode();
// var marketplaceInfo = codexContractsStarter.Start(bootstrapInfo);
// network = new MarketplaceNetwork(bootstrapInfo, marketplaceInfo );
// }
// return network;
// }
// }
//}

View File

@ -0,0 +1,16 @@
//using KubernetesWorkflow;
//namespace DistTestCore.Marketplace
//{
// public class CodexContractsContainerConfig
// {
// public CodexContractsContainerConfig(string bootstrapNodeIp, Port jsonRpcPort)
// {
// BootstrapNodeIp = bootstrapNodeIp;
// JsonRpcPort = jsonRpcPort;
// }
// public string BootstrapNodeIp { get; }
// public Port JsonRpcPort { get; }
// }
//}

View File

@ -0,0 +1,25 @@
//using KubernetesWorkflow;
//namespace DistTestCore.Marketplace
//{
// public class CodexContractsContainerRecipe : DefaultContainerRecipe
// {
// public const string MarketplaceAddressFilename = "/hardhat/deployments/codexdisttestnetwork/Marketplace.json";
// public const string MarketplaceArtifactFilename = "/hardhat/artifacts/contracts/Marketplace.sol/Marketplace.json";
// public override string AppName => "codex-contracts";
// public override string Image => "codexstorage/codex-contracts-eth:latest-dist-tests";
// protected override void InitializeRecipe(StartupConfig startupConfig)
// {
// var config = startupConfig.Get<CodexContractsContainerConfig>();
// var ip = config.BootstrapNodeIp;
// var port = config.JsonRpcPort.Number;
// AddEnvVar("DISTTEST_NETWORK_URL", $"http://{ip}:{port}");
// AddEnvVar("HARDHAT_NETWORK", "codexdisttestnetwork");
// AddEnvVar("KEEP_ALIVE", "1");
// }
// }
//}

View File

@ -0,0 +1,103 @@
//using KubernetesWorkflow;
//using Utils;
//namespace DistTestCore.Marketplace
//{
// public class CodexContractsStarter : BaseStarter
// {
// public CodexContractsStarter(TestLifecycle lifecycle)
// : base(lifecycle)
// {
// }
// public MarketplaceInfo Start(GethBootstrapNodeInfo bootstrapNode)
// {
// LogStart("Deploying Codex Marketplace...");
// var workflow = lifecycle.WorkflowCreator.CreateWorkflow();
// var startupConfig = CreateStartupConfig(bootstrapNode.RunningContainers.Containers[0]);
// var containers = workflow.Start(1, Location.Unspecified, new CodexContractsContainerRecipe(), startupConfig);
// if (containers.Containers.Length != 1) throw new InvalidOperationException("Expected 1 Codex contracts container to be created. Test infra failure.");
// var container = containers.Containers[0];
// WaitUntil(() =>
// {
// var logHandler = new ContractsReadyLogHandler(Debug);
// workflow.DownloadContainerLog(container, logHandler, null);
// return logHandler.Found;
// });
// Log("Contracts deployed. Extracting addresses...");
// var extractor = new ContainerInfoExtractor(lifecycle.Log, workflow, container);
// var marketplaceAddress = extractor.ExtractMarketplaceAddress();
// var abi = extractor.ExtractMarketplaceAbi();
// var interaction = bootstrapNode.StartInteraction(lifecycle);
// var tokenAddress = interaction.GetTokenAddress(marketplaceAddress);
// LogEnd("Extract completed. Marketplace deployed.");
// return new MarketplaceInfo(marketplaceAddress, abi, tokenAddress);
// }
// private void WaitUntil(Func<bool> predicate)
// {
// Time.WaitUntil(predicate, TimeSpan.FromMinutes(3), TimeSpan.FromSeconds(2));
// }
// private StartupConfig CreateStartupConfig(RunningContainer bootstrapContainer)
// {
// var startupConfig = new StartupConfig();
// var contractsConfig = new CodexContractsContainerConfig(bootstrapContainer.Pod.PodInfo.Ip, bootstrapContainer.Recipe.GetPortByTag(GethContainerRecipe.HttpPortTag));
// startupConfig.Add(contractsConfig);
// return startupConfig;
// }
// }
// public class MarketplaceInfo
// {
// public MarketplaceInfo(string address, string abi, string tokenAddress)
// {
// Address = address;
// Abi = abi;
// TokenAddress = tokenAddress;
// }
// public string Address { get; }
// public string Abi { get; }
// public string TokenAddress { get; }
// }
// public class ContractsReadyLogHandler : LogHandler
// {
// // Log should contain 'Compiled 15 Solidity files successfully' at some point.
// private const string RequiredCompiledString = "Solidity files successfully";
// // When script is done, it prints the ready-string.
// private const string ReadyString = "Done! Sleeping indefinitely...";
// private readonly Action<string> debug;
// public ContractsReadyLogHandler(Action<string> debug)
// {
// this.debug = debug;
// debug($"Looking for '{RequiredCompiledString}' and '{ReadyString}' in container logs...");
// }
// public bool SeenCompileString { get; private set; }
// public bool Found { get; private set; }
// protected override void ProcessLine(string line)
// {
// debug(line);
// if (line.Contains(RequiredCompiledString)) SeenCompileString = true;
// if (line.Contains(ReadyString))
// {
// if (!SeenCompileString) throw new Exception("CodexContracts deployment failed. " +
// "Solidity files not compiled before process exited.");
// Found = true;
// }
// }
// }
//}

View File

@ -0,0 +1,149 @@
//using KubernetesWorkflow;
//using Logging;
//using Newtonsoft.Json;
//using Newtonsoft.Json.Linq;
//using Utils;
//namespace DistTestCore.Marketplace
//{
// public class ContainerInfoExtractor
// {
// private readonly BaseLog log;
// private readonly StartupWorkflow workflow;
// private readonly RunningContainer container;
// public ContainerInfoExtractor(BaseLog log, StartupWorkflow workflow, RunningContainer container)
// {
// this.log = log;
// this.workflow = workflow;
// this.container = container;
// }
// public AllGethAccounts ExtractAccounts()
// {
// log.Debug();
// var accountsCsv = Retry(() => FetchAccountsCsv());
// if (string.IsNullOrEmpty(accountsCsv)) throw new InvalidOperationException("Unable to fetch accounts.csv for geth node. Test infra failure.");
// var lines = accountsCsv.Split('\n');
// return new AllGethAccounts(lines.Select(ParseLineToAccount).ToArray());
// }
// public string ExtractPubKey()
// {
// log.Debug();
// var pubKey = Retry(FetchPubKey);
// if (string.IsNullOrEmpty(pubKey)) throw new InvalidOperationException("Unable to fetch enode from geth node. Test infra failure.");
// return pubKey;
// }
// public string ExtractMarketplaceAddress()
// {
// log.Debug();
// var marketplaceAddress = Retry(FetchMarketplaceAddress);
// if (string.IsNullOrEmpty(marketplaceAddress)) throw new InvalidOperationException("Unable to fetch marketplace account from codex-contracts node. Test infra failure.");
// return marketplaceAddress;
// }
// public string ExtractMarketplaceAbi()
// {
// log.Debug();
// var marketplaceAbi = Retry(FetchMarketplaceAbi);
// if (string.IsNullOrEmpty(marketplaceAbi)) throw new InvalidOperationException("Unable to fetch marketplace artifacts from codex-contracts node. Test infra failure.");
// return marketplaceAbi;
// }
// private string FetchAccountsCsv()
// {
// return workflow.ExecuteCommand(container, "cat", GethContainerRecipe.AccountsFilename);
// }
// private string FetchMarketplaceAddress()
// {
// var json = workflow.ExecuteCommand(container, "cat", CodexContractsContainerRecipe.MarketplaceAddressFilename);
// var marketplace = JsonConvert.DeserializeObject<MarketplaceJson>(json);
// return marketplace!.address;
// }
// private string FetchMarketplaceAbi()
// {
// var json = workflow.ExecuteCommand(container, "cat", CodexContractsContainerRecipe.MarketplaceArtifactFilename);
// var artifact = JObject.Parse(json);
// var abi = artifact["abi"];
// return abi!.ToString(Formatting.None);
// }
// private string FetchPubKey()
// {
// var enodeFinder = new PubKeyFinder(s => log.Debug(s));
// workflow.DownloadContainerLog(container, enodeFinder, null);
// return enodeFinder.GetPubKey();
// }
// private GethAccount ParseLineToAccount(string l)
// {
// var tokens = l.Replace("\r", "").Split(',');
// if (tokens.Length != 2) throw new InvalidOperationException();
// var account = tokens[0];
// var privateKey = tokens[1];
// return new GethAccount(account, privateKey);
// }
// private static string Retry(Func<string> fetch)
// {
// return Time.Retry(fetch, nameof(ContainerInfoExtractor));
// }
// }
// public class PubKeyFinder : LogHandler, ILogHandler
// {
// private const string openTag = "self=enode://";
// private const string openTagQuote = "self=\"enode://";
// private readonly Action<string> debug;
// private string pubKey = string.Empty;
// public PubKeyFinder(Action<string> debug)
// {
// this.debug = debug;
// debug($"Looking for '{openTag}' in container logs...");
// }
// public string GetPubKey()
// {
// if (string.IsNullOrEmpty(pubKey)) throw new Exception("Not found yet exception.");
// return pubKey;
// }
// protected override void ProcessLine(string line)
// {
// debug(line);
// if (line.Contains(openTag))
// {
// ExtractPubKey(openTag, line);
// }
// else if (line.Contains(openTagQuote))
// {
// ExtractPubKey(openTagQuote, line);
// }
// }
// private void ExtractPubKey(string tag, string line)
// {
// var openIndex = line.IndexOf(tag) + tag.Length;
// var closeIndex = line.IndexOf("@");
// pubKey = line.Substring(
// startIndex: openIndex,
// length: closeIndex - openIndex);
// }
// }
// public class MarketplaceJson
// {
// public string address { get; set; } = string.Empty;
// }
//}

View File

@ -0,0 +1,42 @@
//using KubernetesWorkflow;
//using NethereumWorkflow;
//namespace DistTestCore.Marketplace
//{
// public class GethBootstrapNodeInfo
// {
// public GethBootstrapNodeInfo(RunningContainers runningContainers, AllGethAccounts allAccounts, string pubKey, Port discoveryPort)
// {
// RunningContainers = runningContainers;
// AllAccounts = allAccounts;
// Account = allAccounts.Accounts[0];
// PubKey = pubKey;
// DiscoveryPort = discoveryPort;
// }
// public RunningContainers RunningContainers { get; }
// public AllGethAccounts AllAccounts { get; }
// public GethAccount Account { get; }
// public string PubKey { get; }
// public Port DiscoveryPort { get; }
// public NethereumInteraction StartInteraction(TestLifecycle lifecycle)
// {
// var address = lifecycle.Configuration.GetAddress(RunningContainers.Containers[0]);
// var account = Account;
// var creator = new NethereumInteractionCreator(lifecycle.Log, address.Host, address.Port, account.PrivateKey);
// return creator.CreateWorkflow();
// }
// }
// public class AllGethAccounts
// {
// public GethAccount[] Accounts { get; }
// public AllGethAccounts(GethAccount[] accounts)
// {
// Accounts = accounts;
// }
// }
//}

View File

@ -0,0 +1,40 @@
//using KubernetesWorkflow;
//namespace DistTestCore.Marketplace
//{
// public class GethBootstrapNodeStarter : BaseStarter
// {
// public GethBootstrapNodeStarter(TestLifecycle lifecycle)
// : base(lifecycle)
// {
// }
// public GethBootstrapNodeInfo StartGethBootstrapNode()
// {
// LogStart("Starting Geth bootstrap node...");
// var startupConfig = CreateBootstrapStartupConfig();
// var workflow = lifecycle.WorkflowCreator.CreateWorkflow();
// var containers = workflow.Start(1, Location.Unspecified, new GethContainerRecipe(), startupConfig);
// if (containers.Containers.Length != 1) throw new InvalidOperationException("Expected 1 Geth bootstrap node to be created. Test infra failure.");
// var bootstrapContainer = containers.Containers[0];
// var extractor = new ContainerInfoExtractor(lifecycle.Log, workflow, bootstrapContainer);
// var accounts = extractor.ExtractAccounts();
// var pubKey = extractor.ExtractPubKey();
// var discoveryPort = bootstrapContainer.Recipe.GetPortByTag(GethContainerRecipe.DiscoveryPortTag);
// var result = new GethBootstrapNodeInfo(containers, accounts, pubKey, discoveryPort);
// LogEnd($"Geth bootstrap node started with account '{result.Account.Account}'");
// return result;
// }
// private StartupConfig CreateBootstrapStartupConfig()
// {
// var config = new StartupConfig();
// config.Add(new GethStartupConfig(true, null!, 0, 0));
// return config;
// }
// }
//}

View File

@ -0,0 +1,38 @@
//using KubernetesWorkflow;
//using NethereumWorkflow;
//namespace DistTestCore.Marketplace
//{
// public class GethCompanionNodeInfo
// {
// public GethCompanionNodeInfo(RunningContainer runningContainer, GethAccount[] accounts)
// {
// RunningContainer = runningContainer;
// Accounts = accounts;
// }
// public RunningContainer RunningContainer { get; }
// public GethAccount[] Accounts { get; }
// public NethereumInteraction StartInteraction(TestLifecycle lifecycle, GethAccount account)
// {
// var address = lifecycle.Configuration.GetAddress(RunningContainer);
// var privateKey = account.PrivateKey;
// var creator = new NethereumInteractionCreator(lifecycle.Log, address.Host, address.Port, privateKey);
// return creator.CreateWorkflow();
// }
// }
// public class GethAccount
// {
// public GethAccount(string account, string privateKey)
// {
// Account = account;
// PrivateKey = privateKey;
// }
// public string Account { get; }
// public string PrivateKey { get; }
// }
//}

View File

@ -0,0 +1,77 @@
//using KubernetesWorkflow;
//using Utils;
//namespace DistTestCore.Marketplace
//{
// public class GethCompanionNodeStarter : BaseStarter
// {
// private int companionAccountIndex = 0;
// public GethCompanionNodeStarter(TestLifecycle lifecycle)
// : base(lifecycle)
// {
// }
// public GethCompanionNodeInfo StartCompanionNodeFor(CodexSetup codexSetup, MarketplaceNetwork marketplace)
// {
// LogStart($"Initializing companion for {codexSetup.NumberOfNodes} Codex nodes.");
// var config = CreateCompanionNodeStartupConfig(marketplace.Bootstrap, codexSetup.NumberOfNodes);
// var workflow = lifecycle.WorkflowCreator.CreateWorkflow();
// var containers = workflow.Start(1, Location.Unspecified, new GethContainerRecipe(), CreateStartupConfig(config));
// if (containers.Containers.Length != 1) throw new InvalidOperationException("Expected one Geth companion node to be created. Test infra failure.");
// var container = containers.Containers[0];
// var node = CreateCompanionInfo(container, marketplace, config);
// EnsureCompanionNodeIsSynced(node, marketplace);
// LogEnd($"Initialized one companion node for {codexSetup.NumberOfNodes} Codex nodes. Their accounts: [{string.Join(",", node.Accounts.Select(a => a.Account))}]");
// return node;
// }
// private GethCompanionNodeInfo CreateCompanionInfo(RunningContainer container, MarketplaceNetwork marketplace, GethStartupConfig config)
// {
// var accounts = ExtractAccounts(marketplace, config);
// return new GethCompanionNodeInfo(container, accounts);
// }
// private static GethAccount[] ExtractAccounts(MarketplaceNetwork marketplace, GethStartupConfig config)
// {
// return marketplace.Bootstrap.AllAccounts.Accounts
// .Skip(1 + config.CompanionAccountStartIndex)
// .Take(config.NumberOfCompanionAccounts)
// .ToArray();
// }
// private void EnsureCompanionNodeIsSynced(GethCompanionNodeInfo node, MarketplaceNetwork marketplace)
// {
// try
// {
// Time.WaitUntil(() =>
// {
// var interaction = node.StartInteraction(lifecycle, node.Accounts.First());
// return interaction.IsSynced(marketplace.Marketplace.Address, marketplace.Marketplace.Abi);
// }, TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(3));
// }
// catch (Exception e)
// {
// throw new Exception("Geth companion node did not sync within timeout. Test infra failure.", e);
// }
// }
// private GethStartupConfig CreateCompanionNodeStartupConfig(GethBootstrapNodeInfo bootstrapNode, int numberOfAccounts)
// {
// var config = new GethStartupConfig(false, bootstrapNode, companionAccountIndex, numberOfAccounts);
// companionAccountIndex += numberOfAccounts;
// return config;
// }
// private StartupConfig CreateStartupConfig(GethStartupConfig gethConfig)
// {
// var config = new StartupConfig();
// config.Add(gethConfig);
// return config;
// }
// }
//}

View File

@ -0,0 +1,73 @@
//using KubernetesWorkflow;
//namespace DistTestCore.Marketplace
//{
// public class GethContainerRecipe : DefaultContainerRecipe
// {
// private const string defaultArgs = "--ipcdisable --syncmode full";
// public const string HttpPortTag = "http_port";
// public const string DiscoveryPortTag = "disc_port";
// public const string AccountsFilename = "accounts.csv";
// public override string AppName => "geth";
// public override string Image => "codexstorage/dist-tests-geth:latest";
// protected override void InitializeRecipe(StartupConfig startupConfig)
// {
// var config = startupConfig.Get<GethStartupConfig>();
// var args = CreateArgs(config);
// AddEnvVar("GETH_ARGS", args);
// }
// private string CreateArgs(GethStartupConfig config)
// {
// var discovery = AddInternalPort(tag: DiscoveryPortTag);
// if (config.IsBootstrapNode)
// {
// return CreateBootstapArgs(discovery);
// }
// return CreateCompanionArgs(discovery, config);
// }
// private string CreateBootstapArgs(Port discovery)
// {
// AddEnvVar("ENABLE_MINER", "1");
// UnlockAccounts(0, 1);
// var exposedPort = AddExposedPort(tag: HttpPortTag);
// return $"--http.port {exposedPort.Number} --port {discovery.Number} --discovery.port {discovery.Number} {defaultArgs}";
// }
// private string CreateCompanionArgs(Port discovery, GethStartupConfig config)
// {
// UnlockAccounts(
// config.CompanionAccountStartIndex + 1,
// config.NumberOfCompanionAccounts);
// var port = AddInternalPort();
// var authRpc = AddInternalPort();
// var httpPort = AddExposedPort(tag: HttpPortTag);
// var bootPubKey = config.BootstrapNode.PubKey;
// var bootIp = config.BootstrapNode.RunningContainers.Containers[0].Pod.PodInfo.Ip;
// var bootPort = config.BootstrapNode.DiscoveryPort.Number;
// var bootstrapArg = $"--bootnodes enode://{bootPubKey}@{bootIp}:{bootPort} --nat=extip:{bootIp}";
// return $"--port {port.Number} --discovery.port {discovery.Number} --authrpc.port {authRpc.Number} --http.addr 0.0.0.0 --http.port {httpPort.Number} --ws --ws.addr 0.0.0.0 --ws.port {httpPort.Number} {bootstrapArg} {defaultArgs}";
// }
// private void UnlockAccounts(int startIndex, int numberOfAccounts)
// {
// if (startIndex < 0) throw new ArgumentException();
// if (numberOfAccounts < 1) throw new ArgumentException();
// if (startIndex + numberOfAccounts > 1000) throw new ArgumentException("Out of accounts!");
// AddEnvVar("UNLOCK_START_INDEX", startIndex.ToString());
// AddEnvVar("UNLOCK_NUMBER", numberOfAccounts.ToString());
// }
// }
//}

View File

@ -0,0 +1,19 @@
//using Newtonsoft.Json;
//namespace DistTestCore.Marketplace
//{
// public class GethStartResult
// {
// public GethStartResult(IMarketplaceAccessFactory marketplaceAccessFactory, MarketplaceNetwork marketplaceNetwork, GethCompanionNodeInfo companionNode)
// {
// MarketplaceAccessFactory = marketplaceAccessFactory;
// MarketplaceNetwork = marketplaceNetwork;
// CompanionNode = companionNode;
// }
// [JsonIgnore]
// public IMarketplaceAccessFactory MarketplaceAccessFactory { get; }
// public MarketplaceNetwork MarketplaceNetwork { get; }
// public GethCompanionNodeInfo CompanionNode { get; }
// }
//}

View File

@ -0,0 +1,18 @@
//namespace DistTestCore.Marketplace
//{
// public class GethStartupConfig
// {
// public GethStartupConfig(bool isBootstrapNode, GethBootstrapNodeInfo bootstrapNode, int companionAccountStartIndex, int numberOfCompanionAccounts)
// {
// IsBootstrapNode = isBootstrapNode;
// BootstrapNode = bootstrapNode;
// CompanionAccountStartIndex = companionAccountStartIndex;
// NumberOfCompanionAccounts = numberOfCompanionAccounts;
// }
// public bool IsBootstrapNode { get; }
// public GethBootstrapNodeInfo BootstrapNode { get; }
// public int CompanionAccountStartIndex { get; }
// public int NumberOfCompanionAccounts { get; }
// }
//}

View File

@ -0,0 +1,243 @@
//using DistTestCore.Codex;
//using DistTestCore.Helpers;
//using Logging;
//using Newtonsoft.Json;
//using NUnit.Framework;
//using NUnit.Framework.Constraints;
//using System.Numerics;
//using Utils;
//namespace DistTestCore.Marketplace
//{
// public interface IMarketplaceAccess
// {
// string MakeStorageAvailable(ByteSize size, TestToken minPricePerBytePerSecond, TestToken maxCollateral, TimeSpan maxDuration);
// StoragePurchaseContract RequestStorage(ContentId contentId, TestToken pricePerSlotPerSecond, TestToken requiredCollateral, uint minRequiredNumberOfNodes, int proofProbability, TimeSpan duration);
// void AssertThatBalance(IResolveConstraint constraint, string message = "");
// TestToken GetBalance();
// }
// public class MarketplaceAccess : IMarketplaceAccess
// {
// private readonly TestLifecycle lifecycle;
// private readonly MarketplaceNetwork marketplaceNetwork;
// private readonly GethAccount account;
// private readonly CodexAccess codexAccess;
// public MarketplaceAccess(TestLifecycle lifecycle, MarketplaceNetwork marketplaceNetwork, GethAccount account, CodexAccess codexAccess)
// {
// this.lifecycle = lifecycle;
// this.marketplaceNetwork = marketplaceNetwork;
// this.account = account;
// this.codexAccess = codexAccess;
// }
// public StoragePurchaseContract RequestStorage(ContentId contentId, TestToken pricePerSlotPerSecond, TestToken requiredCollateral, uint minRequiredNumberOfNodes, int proofProbability, TimeSpan duration)
// {
// var request = new CodexSalesRequestStorageRequest
// {
// duration = ToDecInt(duration.TotalSeconds),
// proofProbability = ToDecInt(proofProbability),
// reward = ToDecInt(pricePerSlotPerSecond),
// collateral = ToDecInt(requiredCollateral),
// expiry = null,
// nodes = minRequiredNumberOfNodes,
// tolerance = null,
// };
// Log($"Requesting storage for: {contentId.Id}... (" +
// $"pricePerSlotPerSecond: {pricePerSlotPerSecond}, " +
// $"requiredCollateral: {requiredCollateral}, " +
// $"minRequiredNumberOfNodes: {minRequiredNumberOfNodes}, " +
// $"proofProbability: {proofProbability}, " +
// $"duration: {Time.FormatDuration(duration)})");
// var response = codexAccess.RequestStorage(request, contentId.Id);
// if (response == "Purchasing not available")
// {
// throw new InvalidOperationException(response);
// }
// Log($"Storage requested successfully. PurchaseId: '{response}'.");
// return new StoragePurchaseContract(lifecycle.Log, codexAccess, response, duration);
// }
// public string MakeStorageAvailable(ByteSize totalSpace, TestToken minPriceForTotalSpace, TestToken maxCollateral, TimeSpan maxDuration)
// {
// var request = new CodexSalesAvailabilityRequest
// {
// size = ToDecInt(totalSpace.SizeInBytes),
// duration = ToDecInt(maxDuration.TotalSeconds),
// maxCollateral = ToDecInt(maxCollateral),
// minPrice = ToDecInt(minPriceForTotalSpace)
// };
// Log($"Making storage available... (" +
// $"size: {totalSpace}, " +
// $"minPriceForTotalSpace: {minPriceForTotalSpace}, " +
// $"maxCollateral: {maxCollateral}, " +
// $"maxDuration: {Time.FormatDuration(maxDuration)})");
// var response = codexAccess.SalesAvailability(request);
// Log($"Storage successfully made available. Id: {response.id}");
// return response.id;
// }
// private string ToDecInt(double d)
// {
// var i = new BigInteger(d);
// return i.ToString("D");
// }
// public string ToDecInt(TestToken t)
// {
// var i = new BigInteger(t.Amount);
// return i.ToString("D");
// }
// public void AssertThatBalance(IResolveConstraint constraint, string message = "")
// {
// AssertHelpers.RetryAssert(constraint, GetBalance, message);
// }
// public TestToken GetBalance()
// {
// var interaction = marketplaceNetwork.StartInteraction(lifecycle);
// var amount = interaction.GetBalance(marketplaceNetwork.Marketplace.TokenAddress, account.Account);
// var balance = new TestToken(amount);
// Log($"Balance of {account.Account} is {balance}.");
// return balance;
// }
// private void Log(string msg)
// {
// lifecycle.Log.Log($"{codexAccess.Container.Name} {msg}");
// }
// }
// public class MarketplaceUnavailable : IMarketplaceAccess
// {
// public StoragePurchaseContract RequestStorage(ContentId contentId, TestToken pricePerBytePerSecond, TestToken requiredCollateral, uint minRequiredNumberOfNodes, int proofProbability, TimeSpan duration)
// {
// Unavailable();
// return null!;
// }
// public string MakeStorageAvailable(ByteSize size, TestToken minPricePerBytePerSecond, TestToken maxCollateral, TimeSpan duration)
// {
// Unavailable();
// return string.Empty;
// }
// public void AssertThatBalance(IResolveConstraint constraint, string message = "")
// {
// Unavailable();
// }
// public TestToken GetBalance()
// {
// Unavailable();
// return new TestToken(0);
// }
// private void Unavailable()
// {
// Assert.Fail("Incorrect test setup: Marketplace was not enabled for this group of Codex nodes. Add 'EnableMarketplace(...)' after 'SetupCodexNodes()' to enable it.");
// throw new InvalidOperationException();
// }
// }
// public class StoragePurchaseContract
// {
// private readonly BaseLog log;
// private readonly CodexAccess codexAccess;
// private DateTime? contractStartUtc;
// public StoragePurchaseContract(BaseLog log, CodexAccess codexAccess, string purchaseId, TimeSpan contractDuration)
// {
// this.log = log;
// this.codexAccess = codexAccess;
// PurchaseId = purchaseId;
// ContractDuration = contractDuration;
// }
// public string PurchaseId { get; }
// public TimeSpan ContractDuration { get; }
// public void WaitForStorageContractStarted()
// {
// WaitForStorageContractStarted(TimeSpan.FromSeconds(30));
// }
// public void WaitForStorageContractFinished()
// {
// if (!contractStartUtc.HasValue)
// {
// WaitForStorageContractStarted();
// }
// var gracePeriod = TimeSpan.FromSeconds(10);
// var currentContractTime = DateTime.UtcNow - contractStartUtc!.Value;
// var timeout = (ContractDuration - currentContractTime) + gracePeriod;
// WaitForStorageContractState(timeout, "finished");
// }
// /// <summary>
// /// Wait for contract to start. Max timeout depends on contract filesize. Allows more time for larger files.
// /// </summary>
// public void WaitForStorageContractStarted(ByteSize contractFileSize)
// {
// var filesizeInMb = contractFileSize.SizeInBytes / (1024 * 1024);
// var maxWaitTime = TimeSpan.FromSeconds(filesizeInMb * 10.0);
// WaitForStorageContractStarted(maxWaitTime);
// }
// public void WaitForStorageContractStarted(TimeSpan timeout)
// {
// WaitForStorageContractState(timeout, "started");
// contractStartUtc = DateTime.UtcNow;
// }
// private void WaitForStorageContractState(TimeSpan timeout, string desiredState)
// {
// var lastState = "";
// var waitStart = DateTime.UtcNow;
// log.Log($"Waiting for {Time.FormatDuration(timeout)} for contract '{PurchaseId}' 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(1000);
// if (lastState == "errored")
// {
// Assert.Fail("Contract errored: " + statusJson);
// }
// if (DateTime.UtcNow - waitStart > timeout)
// {
// Assert.Fail($"Contract did not reach '{desiredState}' within timeout. {statusJson}");
// }
// }
// log.Log($"Contract '{desiredState}'.");
// }
// public CodexStoragePurchase GetPurchaseStatus(string purchaseId)
// {
// return codexAccess.GetPurchaseStatus(purchaseId);
// }
// }
//}

View File

@ -0,0 +1,41 @@
//using DistTestCore.Codex;
//namespace DistTestCore.Marketplace
//{
// public interface IMarketplaceAccessFactory
// {
// IMarketplaceAccess CreateMarketplaceAccess(CodexAccess access);
// }
// public class MarketplaceUnavailableAccessFactory : IMarketplaceAccessFactory
// {
// public IMarketplaceAccess CreateMarketplaceAccess(CodexAccess access)
// {
// return new MarketplaceUnavailable();
// }
// }
// public class GethMarketplaceAccessFactory : IMarketplaceAccessFactory
// {
// private readonly TestLifecycle lifecycle;
// private readonly MarketplaceNetwork marketplaceNetwork;
// public GethMarketplaceAccessFactory(TestLifecycle lifecycle, MarketplaceNetwork marketplaceNetwork)
// {
// this.lifecycle = lifecycle;
// this.marketplaceNetwork = marketplaceNetwork;
// }
// public IMarketplaceAccess CreateMarketplaceAccess(CodexAccess access)
// {
// var companionNode = GetGethCompanionNode(access);
// return new MarketplaceAccess(lifecycle, marketplaceNetwork, companionNode, access);
// }
// private GethAccount GetGethCompanionNode(CodexAccess access)
// {
// var account = access.Container.Recipe.Additionals.Single(a => a is GethAccount);
// return (GethAccount)account;
// }
// }
//}

View File

@ -0,0 +1,17 @@
//namespace DistTestCore.Marketplace
//{
// public class MarketplaceInitialConfig
// {
// public MarketplaceInitialConfig(Ether initialEth, TestToken initialTestTokens, bool isValidator)
// {
// InitialEth = initialEth;
// InitialTestTokens = initialTestTokens;
// IsValidator = isValidator;
// }
// public Ether InitialEth { get; }
// public TestToken InitialTestTokens { get; }
// public bool IsValidator { get; }
// public int? AccountIndexOverride { get; set; }
// }
//}

View File

@ -0,0 +1,21 @@
//using NethereumWorkflow;
//namespace DistTestCore.Marketplace
//{
// public class MarketplaceNetwork
// {
// public MarketplaceNetwork(GethBootstrapNodeInfo bootstrap, MarketplaceInfo marketplace)
// {
// Bootstrap = bootstrap;
// Marketplace = marketplace;
// }
// public GethBootstrapNodeInfo Bootstrap { get; }
// public MarketplaceInfo Marketplace { get; }
// public NethereumInteraction StartInteraction(TestLifecycle lifecycle)
// {
// return Bootstrap.StartInteraction(lifecycle);
// }
// }
//}

View File

@ -0,0 +1,25 @@
//using KubernetesWorkflow;
//namespace DistTestCore.Metrics
//{
// public class GrafanaContainerRecipe : DefaultContainerRecipe
// {
// public override string AppName => "grafana";
// public override string Image => "grafana/grafana-oss:10.0.3";
// public const string DefaultAdminUser = "adminium";
// public const string DefaultAdminPassword = "passwordium";
// protected override void InitializeRecipe(StartupConfig startupConfig)
// {
// AddExposedPort(3000);
// AddEnvVar("GF_AUTH_ANONYMOUS_ENABLED", "true");
// AddEnvVar("GF_AUTH_ANONYMOUS_ORG_NAME", "Main Org.");
// AddEnvVar("GF_AUTH_ANONYMOUS_ORG_ROLE", "Editor");
// AddEnvVar("GF_SECURITY_ADMIN_USER", DefaultAdminUser);
// AddEnvVar("GF_SECURITY_ADMIN_PASSWORD", DefaultAdminPassword);
// }
// }
//}

View File

@ -0,0 +1,81 @@
//using DistTestCore.Helpers;
//using KubernetesWorkflow;
//using Logging;
//using NUnit.Framework;
//using NUnit.Framework.Constraints;
//using Utils;
//namespace DistTestCore.Metrics
//{
// public interface IMetricsAccess
// {
// void AssertThat(string metricName, IResolveConstraint constraint, string message = "");
// }
// public class MetricsAccess : IMetricsAccess
// {
// private readonly BaseLog log;
// private readonly ITimeSet timeSet;
// private readonly MetricsQuery query;
// private readonly RunningContainer node;
// public MetricsAccess(BaseLog log, ITimeSet timeSet, MetricsQuery query, RunningContainer node)
// {
// this.log = log;
// this.timeSet = timeSet;
// this.query = query;
// this.node = node;
// }
// public void AssertThat(string metricName, IResolveConstraint constraint, string message = "")
// {
// AssertHelpers.RetryAssert(constraint, () =>
// {
// var metricSet = GetMetricWithTimeout(metricName);
// var metricValue = metricSet.Values[0].Value;
// log.Log($"{node.Name} metric '{metricName}' = {metricValue}");
// return metricValue;
// }, message);
// }
// public Metrics? GetAllMetrics()
// {
// return query.GetAllMetricsForNode(node);
// }
// private MetricsSet GetMetricWithTimeout(string metricName)
// {
// var start = DateTime.UtcNow;
// while (true)
// {
// var mostRecent = GetMostRecent(metricName);
// if (mostRecent != null) return mostRecent;
// if (DateTime.UtcNow - start > timeSet.WaitForMetricTimeout())
// {
// Assert.Fail($"Timeout: Unable to get metric '{metricName}'.");
// throw new TimeoutException();
// }
// Time.Sleep(TimeSpan.FromSeconds(2));
// }
// }
// private MetricsSet? GetMostRecent(string metricName)
// {
// var result = query.GetMostRecent(metricName, node);
// if (result == null) return null;
// return result.Sets.LastOrDefault();
// }
// }
// public class MetricsUnavailable : IMetricsAccess
// {
// public void AssertThat(string metricName, IResolveConstraint constraint, string message = "")
// {
// Assert.Fail("Incorrect test setup: Metrics were not enabled for this group of Codex nodes. Add 'EnableMetrics()' after 'SetupCodexNodes()' to enable it.");
// throw new InvalidOperationException();
// }
// }
//}

View File

@ -0,0 +1,35 @@
//using KubernetesWorkflow;
//namespace DistTestCore.Metrics
//{
// public interface IMetricsAccessFactory
// {
// IMetricsAccess CreateMetricsAccess(RunningContainer codexContainer);
// }
// public class MetricsUnavailableAccessFactory : IMetricsAccessFactory
// {
// public IMetricsAccess CreateMetricsAccess(RunningContainer codexContainer)
// {
// return new MetricsUnavailable();
// }
// }
// public class CodexNodeMetricsAccessFactory : IMetricsAccessFactory
// {
// private readonly TestLifecycle lifecycle;
// private readonly RunningContainers prometheusContainer;
// public CodexNodeMetricsAccessFactory(TestLifecycle lifecycle, RunningContainers prometheusContainer)
// {
// this.lifecycle = lifecycle;
// this.prometheusContainer = prometheusContainer;
// }
// public IMetricsAccess CreateMetricsAccess(RunningContainer codexContainer)
// {
// var query = new MetricsQuery(lifecycle, prometheusContainer);
// return new MetricsAccess(lifecycle.Log, lifecycle.TimeSet, query, codexContainer);
// }
// }
//}

View File

@ -0,0 +1,80 @@
//using Logging;
//using System.Globalization;
//namespace DistTestCore.Metrics
//{
// public class MetricsDownloader
// {
// private readonly BaseLog log;
// public MetricsDownloader(BaseLog log)
// {
// this.log = log;
// }
// public void DownloadAllMetricsForNode(string nodeName, MetricsAccess access)
// {
// var metrics = access.GetAllMetrics();
// if (metrics == null || metrics.Sets.Length == 0 || metrics.Sets.All(s => s.Values.Length == 0)) return;
// var headers = new[] { "timestamp" }.Concat(metrics.Sets.Select(s => s.Name)).ToArray();
// var map = CreateValueMap(metrics);
// WriteToFile(nodeName, headers, map);
// }
// private void WriteToFile(string nodeName, string[] headers, Dictionary<DateTime, List<string>> map)
// {
// var file = log.CreateSubfile("csv");
// log.Log($"Downloading metrics for {nodeName} to file {file.FullFilename}");
// file.WriteRaw(string.Join(",", headers));
// foreach (var pair in map)
// {
// file.WriteRaw(string.Join(",", new[] { FormatTimestamp(pair.Key) }.Concat(pair.Value)));
// }
// }
// private Dictionary<DateTime, List<string>> CreateValueMap(Metrics metrics)
// {
// var map = CreateForAllTimestamps(metrics);
// foreach (var metric in metrics.Sets)
// {
// AddToMap(map, metric);
// }
// return map;
// }
// private Dictionary<DateTime, List<string>> CreateForAllTimestamps(Metrics metrics)
// {
// var result = new Dictionary<DateTime, List<string>>();
// var timestamps = metrics.Sets.SelectMany(s => s.Values).Select(v => v.Timestamp).Distinct().ToArray();
// foreach (var timestamp in timestamps) result.Add(timestamp, new List<string>());
// return result;
// }
// private void AddToMap(Dictionary<DateTime, List<string>> map, MetricsSet metric)
// {
// foreach (var key in map.Keys)
// {
// map[key].Add(GetValueAtTimestamp(key, metric));
// }
// }
// private string GetValueAtTimestamp(DateTime key, MetricsSet metric)
// {
// var value = metric.Values.SingleOrDefault(v => v.Timestamp == key);
// if (value == null) return "";
// return value.Value.ToString(CultureInfo.InvariantCulture);
// }
// private string FormatTimestamp(DateTime key)
// {
// var origin = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
// var diff = key - origin;
// return Math.Floor(diff.TotalSeconds).ToString(CultureInfo.InvariantCulture);
// }
// }
//}

View File

@ -0,0 +1,9 @@
//namespace DistTestCore.Metrics
//{
// public enum MetricsMode
// {
// None,
// Record,
// Dashboard
// }
//}

View File

@ -0,0 +1,198 @@
//using DistTestCore.Codex;
//using KubernetesWorkflow;
//using System.Globalization;
//namespace DistTestCore.Metrics
//{
// public class MetricsQuery
// {
// private readonly Http http;
// public MetricsQuery(TestLifecycle lifecycle, RunningContainers runningContainers)
// {
// RunningContainers = runningContainers;
// var address = lifecycle.Configuration.GetAddress(runningContainers.Containers[0]);
// http = new Http(
// lifecycle.Log,
// lifecycle.TimeSet,
// address,
// "api/v1");
// }
// public RunningContainers RunningContainers { get; }
// public Metrics? GetMostRecent(string metricName, RunningContainer node)
// {
// var response = GetLastOverTime(metricName, GetInstanceStringForNode(node));
// if (response == null) return null;
// return new Metrics
// {
// Sets = response.data.result.Select(r =>
// {
// return new MetricsSet
// {
// Instance = r.metric.instance,
// Values = MapSingleValue(r.value)
// };
// }).ToArray()
// };
// }
// public Metrics? GetMetrics(string metricName)
// {
// var response = GetAll(metricName);
// if (response == null) return null;
// return MapResponseToMetrics(response);
// }
// public Metrics? GetAllMetricsForNode(RunningContainer node)
// {
// var response = http.HttpGetJson<PrometheusQueryResponse>($"query?query={GetInstanceStringForNode(node)}{GetQueryTimeRange()}");
// if (response.status != "success") return null;
// return MapResponseToMetrics(response);
// }
// private PrometheusQueryResponse? GetLastOverTime(string metricName, string instanceString)
// {
// var response = http.HttpGetJson<PrometheusQueryResponse>($"query?query=last_over_time({metricName}{instanceString}{GetQueryTimeRange()})");
// if (response.status != "success") return null;
// return response;
// }
// private PrometheusQueryResponse? GetAll(string metricName)
// {
// var response = http.HttpGetJson<PrometheusQueryResponse>($"query?query={metricName}{GetQueryTimeRange()}");
// if (response.status != "success") return null;
// return response;
// }
// private Metrics MapResponseToMetrics(PrometheusQueryResponse response)
// {
// return new Metrics
// {
// Sets = response.data.result.Select(r =>
// {
// return new MetricsSet
// {
// Name = r.metric.__name__,
// Instance = r.metric.instance,
// Values = MapMultipleValues(r.values)
// };
// }).ToArray()
// };
// }
// private MetricsSetValue[] MapSingleValue(object[] value)
// {
// if (value != null && value.Length > 0)
// {
// return new[]
// {
// MapValue(value)
// };
// }
// return Array.Empty<MetricsSetValue>();
// }
// private MetricsSetValue[] MapMultipleValues(object[][] values)
// {
// if (values != null && values.Length > 0)
// {
// return values.Select(v => MapValue(v)).ToArray();
// }
// return Array.Empty<MetricsSetValue>();
// }
// private MetricsSetValue MapValue(object[] value)
// {
// if (value.Length != 2) throw new InvalidOperationException("Expected value to be [double, string].");
// return new MetricsSetValue
// {
// Timestamp = ToTimestamp(value[0]),
// Value = ToValue(value[1])
// };
// }
// private string GetInstanceNameForNode(RunningContainer node)
// {
// var ip = node.Pod.PodInfo.Ip;
// var port = node.Recipe.GetPortByTag(CodexContainerRecipe.MetricsPortTag).Number;
// return $"{ip}:{port}";
// }
// private string GetInstanceStringForNode(RunningContainer node)
// {
// return "{instance=\"" + GetInstanceNameForNode(node) + "\"}";
// }
// private string GetQueryTimeRange()
// {
// return "[12h]";
// }
// private double ToValue(object v)
// {
// return Convert.ToDouble(v, CultureInfo.InvariantCulture);
// }
// private DateTime ToTimestamp(object v)
// {
// var unixSeconds = ToValue(v);
// return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(unixSeconds);
// }
// }
// public class Metrics
// {
// public MetricsSet[] Sets { get; set; } = Array.Empty<MetricsSet>();
// }
// public class MetricsSet
// {
// public string Name { get; set; } = string.Empty;
// public string Instance { get; set; } = string.Empty;
// public MetricsSetValue[] Values { get; set; } = Array.Empty<MetricsSetValue>();
// }
// public class MetricsSetValue
// {
// public DateTime Timestamp { get; set; }
// public double Value { get; set; }
// }
// public class PrometheusQueryResponse
// {
// public string status { get; set; } = string.Empty;
// public PrometheusQueryResponseData data { get; set; } = new();
// }
// public class PrometheusQueryResponseData
// {
// public string resultType { get; set; } = string.Empty;
// public PrometheusQueryResponseDataResultEntry[] result { get; set; } = Array.Empty<PrometheusQueryResponseDataResultEntry>();
// }
// public class PrometheusQueryResponseDataResultEntry
// {
// public ResultEntryMetric metric { get; set; } = new();
// public object[] value { get; set; } = Array.Empty<object>();
// public object[][] values { get; set; } = Array.Empty<object[]>();
// }
// public class ResultEntryMetric
// {
// public string __name__ { get; set; } = string.Empty;
// public string instance { get; set; } = string.Empty;
// public string job { get; set; } = string.Empty;
// }
// public class PrometheusAllNamesResponse
// {
// public string status { get; set; } = string.Empty;
// public string[] data { get; set; } = Array.Empty<string>();
// }
//}

View File

@ -0,0 +1,18 @@
//using KubernetesWorkflow;
//namespace DistTestCore.Metrics
//{
// public class PrometheusContainerRecipe : DefaultContainerRecipe
// {
// public override string AppName => "prometheus";
// public override string Image => "codexstorage/dist-tests-prometheus:latest";
// protected override void InitializeRecipe(StartupConfig startupConfig)
// {
// var config = startupConfig.Get<PrometheusStartupConfig>();
// AddExposedPortAndVar("PROM_PORT");
// AddEnvVar("PROM_CONFIG", config.PrometheusConfigBase64);
// }
// }
//}

View File

@ -0,0 +1,12 @@
//namespace DistTestCore.Metrics
//{
// public class PrometheusStartupConfig
// {
// public PrometheusStartupConfig(string prometheusConfigBase64)
// {
// PrometheusConfigBase64 = prometheusConfigBase64;
// }
// public string PrometheusConfigBase64 { get; }
// }
//}

View File

@ -1,13 +1,10 @@
using DistTestCore.Codex;
using DistTestCore.Logs;
using DistTestCore.Marketplace;
using DistTestCore.Metrics;
using DistTestCore.Logs;
using FileUtils;
using Logging;
using NUnit.Framework;
using Utils;
namespace DistTestCore
namespace CodexPlugin
{
public interface IOnlineCodexNode
{
@ -18,8 +15,8 @@ namespace DistTestCore
TestFile? DownloadContent(ContentId contentId, string fileLabel = "");
void ConnectToPeer(IOnlineCodexNode node);
IDownloadedLog DownloadLog(int? tailLines = null);
IMetricsAccess Metrics { get; }
IMarketplaceAccess Marketplace { get; }
//IMetricsAccess Metrics { get; }
//IMarketplaceAccess Marketplace { get; }
CodexDebugVersionResponse Version { get; }
ICodexSetup BringOffline();
}
@ -28,22 +25,22 @@ namespace DistTestCore
{
private const string SuccessfullyConnectedMessage = "Successfully connected to peer";
private const string UploadFailedMessage = "Unable to store block";
private readonly TestLifecycle lifecycle;
//private readonly TestLifecycle lifecycle;
public OnlineCodexNode(TestLifecycle lifecycle, CodexAccess codexAccess, CodexNodeGroup group, IMetricsAccess metricsAccess, IMarketplaceAccess marketplaceAccess)
public OnlineCodexNode(/*TestLifecycle lifecycle, */CodexAccess codexAccess, CodexNodeGroup group/*, IMetricsAccess metricsAccess, IMarketplaceAccess marketplaceAccess*/)
{
this.lifecycle = lifecycle;
//this.lifecycle = lifecycle;
CodexAccess = codexAccess;
Group = group;
Metrics = metricsAccess;
Marketplace = marketplaceAccess;
//Metrics = metricsAccess;
//Marketplace = marketplaceAccess;
Version = new CodexDebugVersionResponse();
}
public CodexAccess CodexAccess { get; }
public CodexNodeGroup Group { get; }
public IMetricsAccess Metrics { get; }
public IMarketplaceAccess Marketplace { get; }
//public IMetricsAccess Metrics { get; }
//public IMarketplaceAccess Marketplace { get; }
public CodexDebugVersionResponse Version { get; private set; }
public string GetName()
@ -66,30 +63,32 @@ namespace DistTestCore
public ContentId UploadFile(TestFile file)
{
using var fileStream = File.OpenRead(file.Filename);
//using var fileStream = File.OpenRead(file.Filename);
var logMessage = $"Uploading file {file.Describe()}...";
Log(logMessage);
var response = Stopwatch.Measure(lifecycle.Log, logMessage, () =>
{
return CodexAccess.UploadFile(fileStream);
});
//var logMessage = $"Uploading file {file.Describe()}...";
//Log(logMessage);
//var response = Stopwatch.Measure(lifecycle.Log, logMessage, () =>
//{
// return CodexAccess.UploadFile(fileStream);
//});
if (string.IsNullOrEmpty(response)) Assert.Fail("Received empty response.");
if (response.StartsWith(UploadFailedMessage)) Assert.Fail("Node failed to store block.");
//if (string.IsNullOrEmpty(response)) Assert.Fail("Received empty response.");
//if (response.StartsWith(UploadFailedMessage)) Assert.Fail("Node failed to store block.");
Log($"Uploaded file. Received contentId: '{response}'.");
return new ContentId(response);
//Log($"Uploaded file. Received contentId: '{response}'.");
//return new ContentId(response);
return null!;
}
public TestFile? DownloadContent(ContentId contentId, string fileLabel = "")
{
var logMessage = $"Downloading for contentId: '{contentId.Id}'...";
Log(logMessage);
var file = lifecycle.FileManager.CreateEmptyTestFile(fileLabel);
Stopwatch.Measure(lifecycle.Log, logMessage, () => DownloadToFile(contentId.Id, file));
Log($"Downloaded file {file.Describe()} to '{file.Filename}'.");
return file;
//var logMessage = $"Downloading for contentId: '{contentId.Id}'...";
//Log(logMessage);
//var file = lifecycle.FileManager.CreateEmptyTestFile(fileLabel);
//Stopwatch.Measure(lifecycle.Log, logMessage, () => DownloadToFile(contentId.Id, file));
//Log($"Downloaded file {file.Describe()} to '{file.Filename}'.");
//return file;
return null!;
}
public void ConnectToPeer(IOnlineCodexNode node)
@ -106,7 +105,7 @@ namespace DistTestCore
public IDownloadedLog DownloadLog(int? tailLines = null)
{
return lifecycle.DownloadLog(CodexAccess.Container, tailLines);
return null!; // lifecycle.DownloadLog(CodexAccess.Container, tailLines);
}
public ICodexSetup BringOffline()
@ -129,8 +128,8 @@ namespace DistTestCore
throw new Exception($"Invalid version information received from Codex node {GetName()}: {debugInfo.codex}");
}
lifecycle.Log.AddStringReplace(nodePeerId, nodeName);
lifecycle.Log.AddStringReplace(debugInfo.table.localNode.nodeId, nodeName);
//lifecycle.Log.AddStringReplace(nodePeerId, nodeName);
//lifecycle.Log.AddStringReplace(debugInfo.table.localNode.nodeId, nodeName);
Version = debugInfo.codex;
}
@ -161,7 +160,7 @@ namespace DistTestCore
private void Log(string msg)
{
lifecycle.Log.Log($"{GetName()}: {msg}");
//lifecycle.Log.Log($"{GetName()}: {msg}");
}
}

View File

@ -4,27 +4,27 @@ namespace DistTestCore
{
public class AutoBootstrapDistTest : DistTest
{
public override IOnlineCodexNode SetupCodexBootstrapNode(Action<ICodexSetup> setup)
{
throw new Exception("AutoBootstrapDistTest creates and attaches a single bootstrap node for you. " +
"If you want to control the bootstrap node from your test, please use DistTest instead.");
}
//public override IOnlineCodexNode SetupCodexBootstrapNode(Action<ICodexSetup> setup)
//{
// throw new Exception("AutoBootstrapDistTest creates and attaches a single bootstrap node for you. " +
// "If you want to control the bootstrap node from your test, please use DistTest instead.");
//}
public override ICodexNodeGroup SetupCodexNodes(int numberOfNodes, Action<ICodexSetup> setup)
{
var codexSetup = CreateCodexSetup(numberOfNodes);
setup(codexSetup);
codexSetup.WithBootstrapNode(BootstrapNode);
return BringOnline(codexSetup);
}
//public override ICodexNodeGroup SetupCodexNodes(int numberOfNodes, Action<ICodexSetup> setup)
//{
// var codexSetup = CreateCodexSetup(numberOfNodes);
// setup(codexSetup);
// codexSetup.WithBootstrapNode(BootstrapNode);
// return BringOnline(codexSetup);
//}
[SetUp]
public void SetUpBootstrapNode()
{
var setup = CreateCodexSetup(1).WithName("BOOTSTRAP");
BootstrapNode = BringOnline(setup)[0];
}
//[SetUp]
//public void SetUpBootstrapNode()
//{
// var setup = CreateCodexSetup(1).WithName("BOOTSTRAP");
// BootstrapNode = BringOnline(setup)[0];
//}
protected IOnlineCodexNode BootstrapNode { get; private set; } = null!;
//protected IOnlineCodexNode BootstrapNode { get; private set; } = null!;
}
}

View File

@ -1,32 +0,0 @@
using DistTestCore.Codex;
using DistTestCore.Marketplace;
using DistTestCore.Metrics;
namespace DistTestCore
{
public interface ICodexNodeFactory
{
OnlineCodexNode CreateOnlineCodexNode(CodexAccess access, CodexNodeGroup group);
}
public class CodexNodeFactory : ICodexNodeFactory
{
private readonly TestLifecycle lifecycle;
private readonly IMetricsAccessFactory metricsAccessFactory;
private readonly IMarketplaceAccessFactory marketplaceAccessFactory;
public CodexNodeFactory(TestLifecycle lifecycle, IMetricsAccessFactory metricsAccessFactory, IMarketplaceAccessFactory marketplaceAccessFactory)
{
this.lifecycle = lifecycle;
this.metricsAccessFactory = metricsAccessFactory;
this.marketplaceAccessFactory = marketplaceAccessFactory;
}
public OnlineCodexNode CreateOnlineCodexNode(CodexAccess access, CodexNodeGroup group)
{
var metricsAccess = metricsAccessFactory.CreateMetricsAccess(access.Container);
var marketplaceAccess = marketplaceAccessFactory.CreateMarketplaceAccess(access);
return new OnlineCodexNode(lifecycle, access, group, metricsAccess, marketplaceAccess);
}
}
}

View File

@ -1,158 +0,0 @@
using DistTestCore.Codex;
using DistTestCore.Marketplace;
using DistTestCore.Metrics;
using KubernetesWorkflow;
using Logging;
namespace DistTestCore
{
public class CodexStarter : BaseStarter
{
public CodexStarter(TestLifecycle lifecycle)
: base(lifecycle)
{
}
public List<CodexNodeGroup> RunningGroups { get; } = new List<CodexNodeGroup>();
public ICodexNodeGroup BringOnline(CodexSetup codexSetup)
{
LogSeparator();
LogStart($"Starting {codexSetup.Describe()}...");
var gethStartResult = lifecycle.GethStarter.BringOnlineMarketplaceFor(codexSetup);
var startupConfig = CreateStartupConfig(gethStartResult, codexSetup);
var containers = StartCodexContainers(startupConfig, codexSetup.NumberOfNodes, codexSetup.Location);
var metricAccessFactory = CollectMetrics(codexSetup, containers);
var codexNodeFactory = new CodexNodeFactory(lifecycle, metricAccessFactory, gethStartResult.MarketplaceAccessFactory);
var group = CreateCodexGroup(codexSetup, containers, codexNodeFactory);
lifecycle.SetCodexVersion(group.Version);
var nl = Environment.NewLine;
var podInfos = string.Join(nl, containers.Containers().Select(c => $"Container: '{c.Name}' runs at '{c.Pod.PodInfo.K8SNodeName}'={c.Pod.PodInfo.Ip}"));
LogEnd($"Started {codexSetup.NumberOfNodes} nodes " +
$"of image '{containers.Containers().First().Recipe.Image}' " +
$"and version '{group.Version}'{nl}" +
podInfos);
LogSeparator();
return group;
}
public void BringOffline(CodexNodeGroup group)
{
LogStart($"Stopping {group.Describe()}...");
var workflow = CreateWorkflow();
foreach (var c in group.Containers)
{
StopCrashWatcher(c);
workflow.Stop(c);
}
RunningGroups.Remove(group);
LogEnd("Stopped.");
}
public void DeleteAllResources()
{
var workflow = CreateWorkflow();
workflow.DeleteTestResources();
RunningGroups.Clear();
}
public void DownloadLog(RunningContainer container, ILogHandler logHandler, int? tailLines)
{
var workflow = CreateWorkflow();
workflow.DownloadContainerLog(container, logHandler, tailLines);
}
private IMetricsAccessFactory CollectMetrics(CodexSetup codexSetup, RunningContainers[] containers)
{
if (codexSetup.MetricsMode == MetricsMode.None) return new MetricsUnavailableAccessFactory();
var runningContainers = lifecycle.PrometheusStarter.CollectMetricsFor(containers);
if (codexSetup.MetricsMode == MetricsMode.Dashboard)
{
lifecycle.GrafanaStarter.StartDashboard(runningContainers.Containers.First(), codexSetup);
}
return new CodexNodeMetricsAccessFactory(lifecycle, runningContainers);
}
private StartupConfig CreateStartupConfig(GethStartResult gethStartResult, CodexSetup codexSetup)
{
var startupConfig = new StartupConfig();
startupConfig.NameOverride = codexSetup.NameOverride;
startupConfig.Add(codexSetup);
startupConfig.Add(gethStartResult);
return startupConfig;
}
private RunningContainers[] StartCodexContainers(StartupConfig startupConfig, int numberOfNodes, Location location)
{
var result = new List<RunningContainers>();
var recipe = new CodexContainerRecipe();
for (var i = 0; i < numberOfNodes; i++)
{
var workflow = CreateWorkflow();
var rc = workflow.Start(1, location, recipe, startupConfig);
CreateCrashWatcher(workflow, rc);
result.Add(rc);
}
return result.ToArray();
}
private CodexNodeGroup CreateCodexGroup(CodexSetup codexSetup, RunningContainers[] runningContainers, CodexNodeFactory codexNodeFactory)
{
var group = new CodexNodeGroup(lifecycle, codexSetup, runningContainers, codexNodeFactory);
RunningGroups.Add(group);
try
{
Stopwatch.Measure(lifecycle.Log, "EnsureOnline", group.EnsureOnline, debug: true);
}
catch
{
CodexNodesNotOnline(runningContainers);
throw;
}
return group;
}
private void CodexNodesNotOnline(RunningContainers[] runningContainers)
{
Log("Codex nodes failed to start");
foreach (var container in runningContainers.Containers()) lifecycle.DownloadLog(container);
}
private StartupWorkflow CreateWorkflow()
{
return lifecycle.WorkflowCreator.CreateWorkflow();
}
private void LogSeparator()
{
Log("----------------------------------------------------------------------------");
}
private void CreateCrashWatcher(StartupWorkflow workflow, RunningContainers rc)
{
var c = rc.Containers.Single();
c.CrashWatcher = workflow.CreateCrashWatcher(c);
}
private void StopCrashWatcher(RunningContainers containers)
{
foreach (var c in containers.Containers)
{
c.CrashWatcher?.Stop();
}
}
}
}

View File

@ -1,5 +1,4 @@
using DistTestCore.Codex;
using KubernetesWorkflow;
using KubernetesWorkflow;
using System.Net.NetworkInformation;
using Utils;
@ -11,7 +10,7 @@ namespace DistTestCore
private readonly string logPath;
private readonly bool logDebug;
private readonly string dataFilesPath;
private readonly CodexLogLevel codexLogLevel;
//private readonly CodexLogLevel codexLogLevel;
private readonly string k8sNamespacePrefix;
private static RunnerLocation? runnerLocation = null;
@ -21,17 +20,17 @@ namespace DistTestCore
logPath = GetEnvVarOrDefault("LOGPATH", "CodexTestLogs");
logDebug = GetEnvVarOrDefault("LOGDEBUG", "false").ToLowerInvariant() == "true";
dataFilesPath = GetEnvVarOrDefault("DATAFILEPATH", "TestDataFiles");
codexLogLevel = ParseEnum.Parse<CodexLogLevel>(GetEnvVarOrDefault("LOGLEVEL", nameof(CodexLogLevel.Trace)));
//codexLogLevel = ParseEnum.Parse<CodexLogLevel>(GetEnvVarOrDefault("LOGLEVEL", nameof(CodexLogLevel.Trace)));
k8sNamespacePrefix = "ct-";
}
public Configuration(string? kubeConfigFile, string logPath, bool logDebug, string dataFilesPath, CodexLogLevel codexLogLevel, string k8sNamespacePrefix)
public Configuration(string? kubeConfigFile, string logPath, bool logDebug, string dataFilesPath, /*CodexLogLevel codexLogLevel,*/ string k8sNamespacePrefix)
{
this.kubeConfigFile = kubeConfigFile;
this.logPath = logPath;
this.logDebug = logDebug;
this.dataFilesPath = dataFilesPath;
this.codexLogLevel = codexLogLevel;
//this.codexLogLevel = codexLogLevel;
this.k8sNamespacePrefix = k8sNamespacePrefix;
}
@ -55,10 +54,10 @@ namespace DistTestCore
return dataFilesPath;
}
public CodexLogLevel GetCodexLogLevel()
{
return codexLogLevel;
}
//public CodexLogLevel GetCodexLogLevel()
//{
// return codexLogLevel;
//}
public Address GetAddress(RunningContainer container)
{

View File

@ -1,8 +1,4 @@
using DistTestCore.Codex;
using DistTestCore.Helpers;
using DistTestCore.Logs;
using DistTestCore.Marketplace;
using DistTestCore.Metrics;
using DistTestCore.Logs;
using FileUtils;
using KubernetesWorkflow;
using Logging;
@ -38,10 +34,10 @@ namespace DistTestCore
public void GlobalSetup()
{
fixtureLog.Log($"Codex Distributed Tests are starting...");
fixtureLog.Log($"Codex image: '{new CodexContainerRecipe().Image}'");
fixtureLog.Log($"CodexContracts image: '{new CodexContractsContainerRecipe().Image}'");
fixtureLog.Log($"Prometheus image: '{new PrometheusContainerRecipe().Image}'");
fixtureLog.Log($"Geth image: '{new GethContainerRecipe().Image}'");
//fixtureLog.Log($"Codex image: '{new CodexContainerRecipe().Image}'");
//fixtureLog.Log($"CodexContracts image: '{new CodexContractsContainerRecipe().Image}'");
//fixtureLog.Log($"Prometheus image: '{new PrometheusContainerRecipe().Image}'");
//fixtureLog.Log($"Geth image: '{new GethContainerRecipe().Image}'");
// Previous test run may have been interrupted.
// Begin by cleaning everything up.
@ -104,53 +100,53 @@ namespace DistTestCore
Get().FileManager.ScopedFiles(action);
}
public IOnlineCodexNode SetupCodexBootstrapNode()
{
return SetupCodexBootstrapNode(s => { });
}
//public IOnlineCodexNode SetupCodexBootstrapNode()
//{
// return SetupCodexBootstrapNode(s => { });
//}
public virtual IOnlineCodexNode SetupCodexBootstrapNode(Action<ICodexSetup> setup)
{
return SetupCodexNode(s =>
{
setup(s);
s.WithName("Bootstrap");
});
}
//public virtual IOnlineCodexNode SetupCodexBootstrapNode(Action<ICodexSetup> setup)
//{
// return SetupCodexNode(s =>
// {
// setup(s);
// s.WithName("Bootstrap");
// });
//}
public IOnlineCodexNode SetupCodexNode()
{
return SetupCodexNode(s => { });
}
//public IOnlineCodexNode SetupCodexNode()
//{
// return SetupCodexNode(s => { });
//}
public IOnlineCodexNode SetupCodexNode(Action<ICodexSetup> setup)
{
return SetupCodexNodes(1, setup)[0];
}
//public IOnlineCodexNode SetupCodexNode(Action<ICodexSetup> setup)
//{
// return SetupCodexNodes(1, setup)[0];
//}
public ICodexNodeGroup SetupCodexNodes(int numberOfNodes)
{
return SetupCodexNodes(numberOfNodes, s => { });
}
//public ICodexNodeGroup SetupCodexNodes(int numberOfNodes)
//{
// return SetupCodexNodes(numberOfNodes, s => { });
//}
public virtual ICodexNodeGroup SetupCodexNodes(int numberOfNodes, Action<ICodexSetup> setup)
{
var codexSetup = CreateCodexSetup(numberOfNodes);
//public virtual ICodexNodeGroup SetupCodexNodes(int numberOfNodes, Action<ICodexSetup> setup)
//{
// var codexSetup = CreateCodexSetup(numberOfNodes);
setup(codexSetup);
// setup(codexSetup);
return BringOnline(codexSetup);
}
// return BringOnline(codexSetup);
//}
public ICodexNodeGroup BringOnline(ICodexSetup codexSetup)
{
return Get().CodexStarter.BringOnline((CodexSetup)codexSetup);
}
//public ICodexNodeGroup BringOnline(ICodexSetup codexSetup)
//{
// return Get().CodexStarter.BringOnline((CodexSetup)codexSetup);
//}
public IEnumerable<IOnlineCodexNode> GetAllOnlineCodexNodes()
{
return Get().CodexStarter.RunningGroups.SelectMany(g => g.Nodes);
}
//public IEnumerable<IOnlineCodexNode> GetAllOnlineCodexNodes()
//{
// return Get().CodexStarter.RunningGroups.SelectMany(g => g.Nodes);
//}
public BaseLog GetTestLog()
{
@ -169,25 +165,25 @@ namespace DistTestCore
GetTestLog().Debug(msg);
}
public PeerConnectionTestHelpers CreatePeerConnectionTestHelpers()
{
return new PeerConnectionTestHelpers(GetTestLog());
}
//public PeerConnectionTestHelpers CreatePeerConnectionTestHelpers()
//{
// return new PeerConnectionTestHelpers(GetTestLog());
//}
public PeerDownloadTestHelpers CreatePeerDownloadTestHelpers()
{
return new PeerDownloadTestHelpers(GetTestLog(), Get().FileManager);
}
//public PeerDownloadTestHelpers CreatePeerDownloadTestHelpers()
//{
// return new PeerDownloadTestHelpers(GetTestLog(), Get().FileManager);
//}
public void Measure(string name, Action action)
{
Stopwatch.Measure(Get().Log, name, action);
}
protected CodexSetup CreateCodexSetup(int numberOfNodes)
{
return new CodexSetup(numberOfNodes, configuration.GetCodexLogLevel());
}
//protected CodexSetup CreateCodexSetup(int numberOfNodes)
//{
// return new CodexSetup(numberOfNodes, configuration.GetCodexLogLevel());
//}
private TestLifecycle Get()
{
@ -261,8 +257,8 @@ namespace DistTestCore
if (IsDownloadingLogsAndMetricsEnabled())
{
lifecycle.Log.Log("Downloading all CodexNode logs and metrics because of test failure...");
DownloadAllLogs(lifecycle);
DownloadAllMetrics(lifecycle);
//DownloadAllLogs(lifecycle);
//DownloadAllMetrics(lifecycle);
}
else
{
@ -271,36 +267,36 @@ namespace DistTestCore
}
}
private void DownloadAllLogs(TestLifecycle lifecycle)
{
OnEachCodexNode(lifecycle, node =>
{
lifecycle.DownloadLog(node.CodexAccess.Container);
});
}
//private void DownloadAllLogs(TestLifecycle lifecycle)
//{
// OnEachCodexNode(lifecycle, node =>
// {
// lifecycle.DownloadLog(node.CodexAccess.Container);
// });
//}
private void DownloadAllMetrics(TestLifecycle lifecycle)
{
var metricsDownloader = new MetricsDownloader(lifecycle.Log);
//private void DownloadAllMetrics(TestLifecycle lifecycle)
//{
// var metricsDownloader = new MetricsDownloader(lifecycle.Log);
OnEachCodexNode(lifecycle, node =>
{
var m = node.Metrics as MetricsAccess;
if (m != null)
{
metricsDownloader.DownloadAllMetricsForNode(node.GetName(), m);
}
});
}
// OnEachCodexNode(lifecycle, node =>
// {
// var m = node.Metrics as MetricsAccess;
// if (m != null)
// {
// metricsDownloader.DownloadAllMetricsForNode(node.GetName(), m);
// }
// });
//}
private void OnEachCodexNode(TestLifecycle lifecycle, Action<OnlineCodexNode> action)
{
var allNodes = lifecycle.CodexStarter.RunningGroups.SelectMany(g => g.Nodes);
foreach (var node in allNodes)
{
action(node);
}
}
//private void OnEachCodexNode(TestLifecycle lifecycle, Action<OnlineCodexNode> action)
//{
// var allNodes = lifecycle.CodexStarter.RunningGroups.SelectMany(g => g.Nodes);
// foreach (var node in allNodes)
// {
// action(node);
// }
//}
private string GetCurrentTestName()
{

View File

@ -10,14 +10,6 @@
<PropertyGroup Condition="'$(IsArm64)'=='true'">
<DefineConstants>Arm64</DefineConstants>
</PropertyGroup>
<ItemGroup>
<None Remove="Metrics\dashboard.json" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Metrics\dashboard.json">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />

View File

@ -1,88 +0,0 @@
using DistTestCore.Marketplace;
namespace DistTestCore
{
public class GethStarter : BaseStarter
{
private readonly MarketplaceNetworkCache marketplaceNetworkCache;
private readonly GethCompanionNodeStarter companionNodeStarter;
public GethStarter(TestLifecycle lifecycle)
: base(lifecycle)
{
marketplaceNetworkCache = new MarketplaceNetworkCache(
new GethBootstrapNodeStarter(lifecycle),
new CodexContractsStarter(lifecycle));
companionNodeStarter = new GethCompanionNodeStarter(lifecycle);
}
public GethStartResult BringOnlineMarketplaceFor(CodexSetup codexSetup)
{
if (codexSetup.MarketplaceConfig == null) return CreateMarketplaceUnavailableResult();
var marketplaceNetwork = marketplaceNetworkCache.Get();
var companionNode = StartCompanionNode(codexSetup, marketplaceNetwork);
LogStart("Setting up initial balance...");
TransferInitialBalance(marketplaceNetwork, codexSetup.MarketplaceConfig, companionNode);
LogEnd($"Initial balance of {codexSetup.MarketplaceConfig.InitialTestTokens} set for {codexSetup.NumberOfNodes} nodes.");
return CreateGethStartResult(marketplaceNetwork, companionNode);
}
private void TransferInitialBalance(MarketplaceNetwork marketplaceNetwork, MarketplaceInitialConfig marketplaceConfig, GethCompanionNodeInfo companionNode)
{
if (marketplaceConfig.InitialTestTokens.Amount == 0) return;
var interaction = marketplaceNetwork.StartInteraction(lifecycle);
var tokenAddress = marketplaceNetwork.Marketplace.TokenAddress;
var accounts = companionNode.Accounts.Select(a => a.Account).ToArray();
interaction.MintTestTokens(accounts, marketplaceConfig.InitialTestTokens.Amount, tokenAddress);
}
private GethStartResult CreateGethStartResult(MarketplaceNetwork marketplaceNetwork, GethCompanionNodeInfo companionNode)
{
return new GethStartResult(CreateMarketplaceAccessFactory(marketplaceNetwork), marketplaceNetwork, companionNode);
}
private GethStartResult CreateMarketplaceUnavailableResult()
{
return new GethStartResult(new MarketplaceUnavailableAccessFactory(), null!, null!);
}
private IMarketplaceAccessFactory CreateMarketplaceAccessFactory(MarketplaceNetwork marketplaceNetwork)
{
return new GethMarketplaceAccessFactory(lifecycle, marketplaceNetwork);
}
private GethCompanionNodeInfo StartCompanionNode(CodexSetup codexSetup, MarketplaceNetwork marketplaceNetwork)
{
return companionNodeStarter.StartCompanionNodeFor(codexSetup, marketplaceNetwork);
}
}
public class MarketplaceNetworkCache
{
private readonly GethBootstrapNodeStarter bootstrapNodeStarter;
private readonly CodexContractsStarter codexContractsStarter;
private MarketplaceNetwork? network;
public MarketplaceNetworkCache(GethBootstrapNodeStarter bootstrapNodeStarter, CodexContractsStarter codexContractsStarter)
{
this.bootstrapNodeStarter = bootstrapNodeStarter;
this.codexContractsStarter = codexContractsStarter;
}
public MarketplaceNetwork Get()
{
if (network == null)
{
var bootstrapInfo = bootstrapNodeStarter.StartGethBootstrapNode();
var marketplaceInfo = codexContractsStarter.Start(bootstrapInfo);
network = new MarketplaceNetwork(bootstrapInfo, marketplaceInfo );
}
return network;
}
}
}

View File

@ -1,9 +1,4 @@
using DistTestCore.Metrics;
using IdentityModel.Client;
using KubernetesWorkflow;
using Newtonsoft.Json;
using System.Reflection;
using Utils;
using KubernetesWorkflow;
namespace DistTestCore
{
@ -17,133 +12,134 @@ namespace DistTestCore
{
}
public GrafanaStartInfo StartDashboard(RunningContainer prometheusContainer, CodexSetup codexSetup)
public GrafanaStartInfo StartDashboard(RunningContainer prometheusContainer)//, CodexSetup codexSetup)
{
LogStart($"Starting dashboard server");
return null!;
//LogStart($"Starting dashboard server");
var grafanaContainer = StartGrafanaContainer();
var grafanaAddress = lifecycle.Configuration.GetAddress(grafanaContainer);
//var grafanaContainer = StartGrafanaContainer();
//var grafanaAddress = lifecycle.Configuration.GetAddress(grafanaContainer);
var http = new Http(lifecycle.Log, new DefaultTimeSet(), grafanaAddress, "api/", AddBasicAuth);
//var http = new Http(lifecycle.Log, new DefaultTimeSet(), grafanaAddress, "api/", AddBasicAuth);
Log("Connecting datasource...");
AddDataSource(http, prometheusContainer);
//Log("Connecting datasource...");
//AddDataSource(http, prometheusContainer);
Log("Uploading dashboard configurations...");
var jsons = ReadEachDashboardJsonFile(codexSetup);
var dashboardUrls = jsons.Select(j => UploadDashboard(http, grafanaContainer, j)).ToArray();
//Log("Uploading dashboard configurations...");
//var jsons = ReadEachDashboardJsonFile(codexSetup);
//var dashboardUrls = jsons.Select(j => UploadDashboard(http, grafanaContainer, j)).ToArray();
LogEnd("Dashboard server started.");
//LogEnd("Dashboard server started.");
return new GrafanaStartInfo(dashboardUrls, grafanaContainer);
//return new GrafanaStartInfo(dashboardUrls, grafanaContainer);
}
private RunningContainer StartGrafanaContainer()
{
var startupConfig = new StartupConfig();
//private RunningContainer StartGrafanaContainer()
//{
// var startupConfig = new StartupConfig();
var workflow = lifecycle.WorkflowCreator.CreateWorkflow();
var grafanaContainers = workflow.Start(1, Location.Unspecified, new GrafanaContainerRecipe(), startupConfig);
if (grafanaContainers.Containers.Length != 1) throw new InvalidOperationException("Expected 1 dashboard container to be created.");
// var workflow = lifecycle.WorkflowCreator.CreateWorkflow();
// var grafanaContainers = workflow.Start(1, Location.Unspecified, new GrafanaContainerRecipe(), startupConfig);
// if (grafanaContainers.Containers.Length != 1) throw new InvalidOperationException("Expected 1 dashboard container to be created.");
return grafanaContainers.Containers.First();
}
// return grafanaContainers.Containers.First();
//}
private void AddBasicAuth(HttpClient client)
{
client.SetBasicAuthentication(
GrafanaContainerRecipe.DefaultAdminUser,
GrafanaContainerRecipe.DefaultAdminPassword);
}
//private void AddBasicAuth(HttpClient client)
//{
// client.SetBasicAuthentication(
// GrafanaContainerRecipe.DefaultAdminUser,
// GrafanaContainerRecipe.DefaultAdminPassword);
//}
private static void AddDataSource(Http http, RunningContainer prometheusContainer)
{
var prometheusAddress = prometheusContainer.ClusterExternalAddress;
var prometheusUrl = prometheusAddress.Host + ":" + prometheusAddress.Port;
var response = http.HttpPostJson<GrafanaDataSourceRequest, GrafanaDataSourceResponse>("datasources", new GrafanaDataSourceRequest
{
uid = "c89eaad3-9184-429f-ac94-8ba0b1824dbb",
name = "CodexPrometheus",
type = "prometheus",
url = prometheusUrl,
access = "proxy",
basicAuth = false,
jsonData = new GrafanaDataSourceJsonData
{
httpMethod = "POST"
}
});
//private static void AddDataSource(Http http, RunningContainer prometheusContainer)
//{
// var prometheusAddress = prometheusContainer.ClusterExternalAddress;
// var prometheusUrl = prometheusAddress.Host + ":" + prometheusAddress.Port;
// var response = http.HttpPostJson<GrafanaDataSourceRequest, GrafanaDataSourceResponse>("datasources", new GrafanaDataSourceRequest
// {
// uid = "c89eaad3-9184-429f-ac94-8ba0b1824dbb",
// name = "CodexPrometheus",
// type = "prometheus",
// url = prometheusUrl,
// access = "proxy",
// basicAuth = false,
// jsonData = new GrafanaDataSourceJsonData
// {
// httpMethod = "POST"
// }
// });
if (response.message != "Datasource added")
{
throw new Exception("Test infra failure: Failed to add datasource to dashboard: " + response.message);
}
}
// if (response.message != "Datasource added")
// {
// throw new Exception("Test infra failure: Failed to add datasource to dashboard: " + response.message);
// }
//}
public static string UploadDashboard(Http http, RunningContainer grafanaContainer, string dashboardJson)
{
var request = GetDashboardCreateRequest(dashboardJson);
var response = http.HttpPostString("dashboards/db", request);
var jsonResponse = JsonConvert.DeserializeObject<GrafanaPostDashboardResponse>(response);
if (jsonResponse == null || string.IsNullOrEmpty(jsonResponse.url)) throw new Exception("Failed to upload dashboard.");
//public static string UploadDashboard(Http http, RunningContainer grafanaContainer, string dashboardJson)
//{
// var request = GetDashboardCreateRequest(dashboardJson);
// var response = http.HttpPostString("dashboards/db", request);
// var jsonResponse = JsonConvert.DeserializeObject<GrafanaPostDashboardResponse>(response);
// if (jsonResponse == null || string.IsNullOrEmpty(jsonResponse.url)) throw new Exception("Failed to upload dashboard.");
var grafanaAddress = grafanaContainer.ClusterExternalAddress;
return grafanaAddress.Host + ":" + grafanaAddress.Port + jsonResponse.url;
}
// var grafanaAddress = grafanaContainer.ClusterExternalAddress;
// return grafanaAddress.Host + ":" + grafanaAddress.Port + jsonResponse.url;
//}
private static string[] ReadEachDashboardJsonFile(CodexSetup codexSetup)
{
var assembly = Assembly.GetExecutingAssembly();
var resourceNames = new[]
{
"DistTestCore.Metrics.dashboard.json"
};
//private static string[] ReadEachDashboardJsonFile(CodexSetup codexSetup)
//{
// var assembly = Assembly.GetExecutingAssembly();
// var resourceNames = new[]
// {
// "DistTestCore.Metrics.dashboard.json"
// };
return resourceNames.Select(r => GetManifestResource(assembly, r, codexSetup)).ToArray();
}
// return resourceNames.Select(r => GetManifestResource(assembly, r, codexSetup)).ToArray();
//}
private static string GetManifestResource(Assembly assembly, string resourceName, CodexSetup codexSetup)
{
using var stream = assembly.GetManifestResourceStream(resourceName);
if (stream == null) throw new Exception("Unable to find resource " + resourceName);
using var reader = new StreamReader(stream);
return ApplyReplacements(reader.ReadToEnd(), codexSetup);
}
//private static string GetManifestResource(Assembly assembly, string resourceName, CodexSetup codexSetup)
//{
// using var stream = assembly.GetManifestResourceStream(resourceName);
// if (stream == null) throw new Exception("Unable to find resource " + resourceName);
// using var reader = new StreamReader(stream);
// return ApplyReplacements(reader.ReadToEnd(), codexSetup);
//}
private static string ApplyReplacements(string input, CodexSetup codexSetup)
{
var quotaString = GetQuotaString(codexSetup);
var softMaxString = GetSoftMaxString(codexSetup);
//private static string ApplyReplacements(string input, CodexSetup codexSetup)
//{
// var quotaString = GetQuotaString(codexSetup);
// var softMaxString = GetSoftMaxString(codexSetup);
return input
.Replace(StorageQuotaThresholdReplaceToken, quotaString)
.Replace(BytesUsedGraphAxisSoftMaxReplaceToken, softMaxString);
}
// return input
// .Replace(StorageQuotaThresholdReplaceToken, quotaString)
// .Replace(BytesUsedGraphAxisSoftMaxReplaceToken, softMaxString);
//}
private static string GetQuotaString(CodexSetup codexSetup)
{
return GetCodexStorageQuotaInBytes(codexSetup).ToString();
}
//private static string GetQuotaString(CodexSetup codexSetup)
//{
// return GetCodexStorageQuotaInBytes(codexSetup).ToString();
//}
private static string GetSoftMaxString(CodexSetup codexSetup)
{
var quota = GetCodexStorageQuotaInBytes(codexSetup);
var softMax = Convert.ToInt64(quota * 1.1); // + 10%, for nice viewing.
return softMax.ToString();
}
//private static string GetSoftMaxString(CodexSetup codexSetup)
//{
// var quota = GetCodexStorageQuotaInBytes(codexSetup);
// var softMax = Convert.ToInt64(quota * 1.1); // + 10%, for nice viewing.
// return softMax.ToString();
//}
private static long GetCodexStorageQuotaInBytes(CodexSetup codexSetup)
{
if (codexSetup.StorageQuota != null) return codexSetup.StorageQuota.SizeInBytes;
//private static long GetCodexStorageQuotaInBytes(CodexSetup codexSetup)
//{
// if (codexSetup.StorageQuota != null) return codexSetup.StorageQuota.SizeInBytes;
// Codex default: 8GB
return 8.GB().SizeInBytes;
}
// // Codex default: 8GB
// return 8.GB().SizeInBytes;
//}
private static string GetDashboardCreateRequest(string dashboardJson)
{
return $"{{\"dashboard\": {dashboardJson} ,\"message\": \"Default Codex Dashboard\",\"overwrite\": false}}";
}
//private static string GetDashboardCreateRequest(string dashboardJson)
//{
// return $"{{\"dashboard\": {dashboardJson} ,\"message\": \"Default Codex Dashboard\",\"overwrite\": false}}";
//}
}
public class GrafanaStartInfo

View File

@ -1,199 +1,199 @@
using DistTestCore.Codex;
using Logging;
using NUnit.Framework;
//using DistTestCore.Codex;
//using Logging;
//using NUnit.Framework;
namespace DistTestCore.Helpers
{
public interface IFullConnectivityImplementation
{
string Description();
string ValidateEntry(FullConnectivityHelper.Entry entry, FullConnectivityHelper.Entry[] allEntries);
FullConnectivityHelper.PeerConnectionState Check(FullConnectivityHelper.Entry from, FullConnectivityHelper.Entry to);
}
//namespace DistTestCore.Helpers
//{
// public interface IFullConnectivityImplementation
// {
// string Description();
// string ValidateEntry(FullConnectivityHelper.Entry entry, FullConnectivityHelper.Entry[] allEntries);
// FullConnectivityHelper.PeerConnectionState Check(FullConnectivityHelper.Entry from, FullConnectivityHelper.Entry to);
// }
public class FullConnectivityHelper
{
private static string Nl = Environment.NewLine;
private readonly BaseLog log;
private readonly IFullConnectivityImplementation implementation;
// public class FullConnectivityHelper
// {
// private static string Nl = Environment.NewLine;
// private readonly BaseLog log;
// private readonly IFullConnectivityImplementation implementation;
public FullConnectivityHelper(BaseLog log, IFullConnectivityImplementation implementation)
{
this.log = log;
this.implementation = implementation;
}
// public FullConnectivityHelper(BaseLog log, IFullConnectivityImplementation implementation)
// {
// this.log = log;
// this.implementation = implementation;
// }
public void AssertFullyConnected(IEnumerable<CodexAccess> nodes)
{
AssertFullyConnected(nodes.ToArray());
}
// public void AssertFullyConnected(IEnumerable<CodexAccess> nodes)
// {
// AssertFullyConnected(nodes.ToArray());
// }
private void AssertFullyConnected(CodexAccess[] nodes)
{
Log($"Asserting '{implementation.Description()}' for nodes: '{string.Join(",", nodes.Select(n => n.GetName()))}'...");
var entries = CreateEntries(nodes);
var pairs = CreatePairs(entries);
// private void AssertFullyConnected(CodexAccess[] nodes)
// {
// Log($"Asserting '{implementation.Description()}' for nodes: '{string.Join(",", nodes.Select(n => n.GetName()))}'...");
// var entries = CreateEntries(nodes);
// var pairs = CreatePairs(entries);
// Each pair gets two chances.
CheckAndRemoveSuccessful(pairs);
CheckAndRemoveSuccessful(pairs);
// // Each pair gets two chances.
// CheckAndRemoveSuccessful(pairs);
// CheckAndRemoveSuccessful(pairs);
if (pairs.Any())
{
var pairDetails = string.Join(Nl, pairs.SelectMany(p => p.GetResultMessages()));
// if (pairs.Any())
// {
// var pairDetails = string.Join(Nl, pairs.SelectMany(p => p.GetResultMessages()));
Log($"Connections failed:{Nl}{pairDetails}");
// Log($"Connections failed:{Nl}{pairDetails}");
Assert.Fail(string.Join(Nl, pairs.SelectMany(p => p.GetResultMessages())));
}
else
{
Log($"'{implementation.Description()}' = Success! for nodes: {string.Join(",", nodes.Select(n => n.GetName()))}");
}
}
// Assert.Fail(string.Join(Nl, pairs.SelectMany(p => p.GetResultMessages())));
// }
// else
// {
// Log($"'{implementation.Description()}' = Success! for nodes: {string.Join(",", nodes.Select(n => n.GetName()))}");
// }
// }
private void CheckAndRemoveSuccessful(List<Pair> pairs)
{
var results = new List<string>();
foreach (var pair in pairs.ToArray())
{
pair.Check();
if (pair.Success)
{
results.AddRange(pair.GetResultMessages());
pairs.Remove(pair);
}
}
Log($"Connections successful:{Nl}{string.Join(Nl, results)}");
}
// private void CheckAndRemoveSuccessful(List<Pair> pairs)
// {
// var results = new List<string>();
// foreach (var pair in pairs.ToArray())
// {
// pair.Check();
// if (pair.Success)
// {
// results.AddRange(pair.GetResultMessages());
// pairs.Remove(pair);
// }
// }
// Log($"Connections successful:{Nl}{string.Join(Nl, results)}");
// }
private Entry[] CreateEntries(CodexAccess[] nodes)
{
var entries = nodes.Select(n => new Entry(n)).ToArray();
// private Entry[] CreateEntries(CodexAccess[] nodes)
// {
// var entries = nodes.Select(n => new Entry(n)).ToArray();
var errors = entries
.Select(e => implementation.ValidateEntry(e, entries))
.Where(s => !string.IsNullOrEmpty(s))
.ToArray();
// var errors = entries
// .Select(e => implementation.ValidateEntry(e, entries))
// .Where(s => !string.IsNullOrEmpty(s))
// .ToArray();
if (errors.Any())
{
Assert.Fail("Some node entries failed to validate: " + string.Join(Nl, errors));
}
// if (errors.Any())
// {
// Assert.Fail("Some node entries failed to validate: " + string.Join(Nl, errors));
// }
return entries;
}
// return entries;
// }
private List<Pair> CreatePairs(Entry[] entries)
{
return CreatePairsIterator(entries).ToList();
}
// private List<Pair> CreatePairs(Entry[] entries)
// {
// return CreatePairsIterator(entries).ToList();
// }
private IEnumerable<Pair> CreatePairsIterator(Entry[] entries)
{
for (var x = 0; x < entries.Length; x++)
{
for (var y = x + 1; y < entries.Length; y++)
{
yield return new Pair(implementation, entries[x], entries[y]);
}
}
}
// private IEnumerable<Pair> CreatePairsIterator(Entry[] entries)
// {
// for (var x = 0; x < entries.Length; x++)
// {
// for (var y = x + 1; y < entries.Length; y++)
// {
// yield return new Pair(implementation, entries[x], entries[y]);
// }
// }
// }
private void Log(string msg)
{
log.Log(msg);
}
// private void Log(string msg)
// {
// log.Log(msg);
// }
public class Entry
{
public Entry(CodexAccess node)
{
Node = node;
Response = node.GetDebugInfo();
}
// public class Entry
// {
// public Entry(CodexAccess node)
// {
// Node = node;
// Response = node.GetDebugInfo();
// }
public CodexAccess Node { get; }
public CodexDebugResponse Response { get; }
// public CodexAccess Node { get; }
// public CodexDebugResponse Response { get; }
public override string ToString()
{
if (Response == null || string.IsNullOrEmpty(Response.id)) return "UNKNOWN";
return Response.id;
}
}
// public override string ToString()
// {
// if (Response == null || string.IsNullOrEmpty(Response.id)) return "UNKNOWN";
// return Response.id;
// }
// }
public enum PeerConnectionState
{
Unknown,
Connection,
NoConnection,
}
// public enum PeerConnectionState
// {
// Unknown,
// Connection,
// NoConnection,
// }
public class Pair
{
private TimeSpan aToBTime = TimeSpan.FromSeconds(0);
private TimeSpan bToATime = TimeSpan.FromSeconds(0);
private readonly IFullConnectivityImplementation implementation;
// public class Pair
// {
// private TimeSpan aToBTime = TimeSpan.FromSeconds(0);
// private TimeSpan bToATime = TimeSpan.FromSeconds(0);
// private readonly IFullConnectivityImplementation implementation;
public Pair(IFullConnectivityImplementation implementation, Entry a, Entry b)
{
this.implementation = implementation;
A = a;
B = b;
}
// public Pair(IFullConnectivityImplementation implementation, Entry a, Entry b)
// {
// this.implementation = implementation;
// A = a;
// B = b;
// }
public Entry A { get; }
public Entry B { get; }
public PeerConnectionState AKnowsB { get; private set; }
public PeerConnectionState BKnowsA { get; private set; }
public bool Success { get { return AKnowsB == PeerConnectionState.Connection && BKnowsA == PeerConnectionState.Connection; } }
public bool Inconclusive { get { return AKnowsB == PeerConnectionState.Unknown || BKnowsA == PeerConnectionState.Unknown; } }
// public Entry A { get; }
// public Entry B { get; }
// public PeerConnectionState AKnowsB { get; private set; }
// public PeerConnectionState BKnowsA { get; private set; }
// public bool Success { get { return AKnowsB == PeerConnectionState.Connection && BKnowsA == PeerConnectionState.Connection; } }
// public bool Inconclusive { get { return AKnowsB == PeerConnectionState.Unknown || BKnowsA == PeerConnectionState.Unknown; } }
public void Check()
{
aToBTime = Measure(() => AKnowsB = Check(A, B));
bToATime = Measure(() => BKnowsA = Check(B, A));
}
// public void Check()
// {
// aToBTime = Measure(() => AKnowsB = Check(A, B));
// bToATime = Measure(() => BKnowsA = Check(B, A));
// }
public override string ToString()
{
return $"[{string.Join(",", GetResultMessages())}]";
}
// public override string ToString()
// {
// return $"[{string.Join(",", GetResultMessages())}]";
// }
public string[] GetResultMessages()
{
var aName = A.ToString();
var bName = B.ToString();
// public string[] GetResultMessages()
// {
// var aName = A.ToString();
// var bName = B.ToString();
return new[]
{
$"[{aName} --> {bName}] = {AKnowsB} ({aToBTime.TotalSeconds} seconds)",
$"[{aName} <-- {bName}] = {BKnowsA} ({bToATime.TotalSeconds} seconds)"
};
}
// return new[]
// {
// $"[{aName} --> {bName}] = {AKnowsB} ({aToBTime.TotalSeconds} seconds)",
// $"[{aName} <-- {bName}] = {BKnowsA} ({bToATime.TotalSeconds} seconds)"
// };
// }
private static TimeSpan Measure(Action action)
{
var start = DateTime.UtcNow;
action();
return DateTime.UtcNow - start;
}
// private static TimeSpan Measure(Action action)
// {
// var start = DateTime.UtcNow;
// action();
// return DateTime.UtcNow - start;
// }
private PeerConnectionState Check(Entry from, Entry to)
{
Thread.Sleep(10);
// private PeerConnectionState Check(Entry from, Entry to)
// {
// Thread.Sleep(10);
try
{
return implementation.Check(from, to);
}
catch
{
// Didn't get a conclusive answer. Try again later.
return PeerConnectionState.Unknown;
}
}
}
}
}
// try
// {
// return implementation.Check(from, to);
// }
// catch
// {
// // Didn't get a conclusive answer. Try again later.
// return PeerConnectionState.Unknown;
// }
// }
// }
// }
//}

View File

@ -1,71 +1,71 @@
using DistTestCore.Codex;
using Logging;
using static DistTestCore.Helpers.FullConnectivityHelper;
//using DistTestCore.Codex;
//using Logging;
//using static DistTestCore.Helpers.FullConnectivityHelper;
namespace DistTestCore.Helpers
{
public class PeerConnectionTestHelpers : IFullConnectivityImplementation
{
private readonly FullConnectivityHelper helper;
//namespace DistTestCore.Helpers
//{
// public class PeerConnectionTestHelpers : IFullConnectivityImplementation
// {
// private readonly FullConnectivityHelper helper;
public PeerConnectionTestHelpers(BaseLog log)
{
helper = new FullConnectivityHelper(log, this);
}
// public PeerConnectionTestHelpers(BaseLog log)
// {
// helper = new FullConnectivityHelper(log, this);
// }
public void AssertFullyConnected(IEnumerable<IOnlineCodexNode> nodes)
{
AssertFullyConnected(nodes.Select(n => ((OnlineCodexNode)n).CodexAccess));
}
// public void AssertFullyConnected(IEnumerable<IOnlineCodexNode> nodes)
// {
// AssertFullyConnected(nodes.Select(n => ((OnlineCodexNode)n).CodexAccess));
// }
public void AssertFullyConnected(IEnumerable<CodexAccess> nodes)
{
helper.AssertFullyConnected(nodes);
}
// public void AssertFullyConnected(IEnumerable<CodexAccess> nodes)
// {
// helper.AssertFullyConnected(nodes);
// }
public string Description()
{
return "Peer Discovery";
}
// public string Description()
// {
// return "Peer Discovery";
// }
public string ValidateEntry(Entry entry, Entry[] allEntries)
{
var result = string.Empty;
foreach (var peer in entry.Response.table.nodes)
{
var expected = GetExpectedDiscoveryEndpoint(allEntries, peer);
if (expected != peer.address)
{
result += $"Node:{entry.Node.GetName()} has incorrect peer table entry. Was: '{peer.address}', expected: '{expected}'. ";
}
}
return result;
}
// public string ValidateEntry(Entry entry, Entry[] allEntries)
// {
// var result = string.Empty;
// foreach (var peer in entry.Response.table.nodes)
// {
// var expected = GetExpectedDiscoveryEndpoint(allEntries, peer);
// if (expected != peer.address)
// {
// result += $"Node:{entry.Node.GetName()} has incorrect peer table entry. Was: '{peer.address}', expected: '{expected}'. ";
// }
// }
// return result;
// }
public PeerConnectionState Check(Entry from, Entry to)
{
var peerId = to.Response.id;
// public PeerConnectionState Check(Entry from, Entry to)
// {
// var peerId = to.Response.id;
var response = from.Node.GetDebugPeer(peerId);
if (!response.IsPeerFound)
{
return PeerConnectionState.NoConnection;
}
if (!string.IsNullOrEmpty(response.peerId) && response.addresses.Any())
{
return PeerConnectionState.Connection;
}
return PeerConnectionState.Unknown;
}
// var response = from.Node.GetDebugPeer(peerId);
// if (!response.IsPeerFound)
// {
// return PeerConnectionState.NoConnection;
// }
// if (!string.IsNullOrEmpty(response.peerId) && response.addresses.Any())
// {
// return PeerConnectionState.Connection;
// }
// return PeerConnectionState.Unknown;
// }
private static string GetExpectedDiscoveryEndpoint(Entry[] allEntries, CodexDebugTableNodeResponse node)
{
var peer = allEntries.SingleOrDefault(e => e.Response.table.localNode.peerId == node.peerId);
if (peer == null) return $"peerId: {node.peerId} is not known.";
// private static string GetExpectedDiscoveryEndpoint(Entry[] allEntries, CodexDebugTableNodeResponse node)
// {
// var peer = allEntries.SingleOrDefault(e => e.Response.table.localNode.peerId == node.peerId);
// if (peer == null) return $"peerId: {node.peerId} is not known.";
var ip = peer.Node.Container.Pod.PodInfo.Ip;
var discPort = peer.Node.Container.Recipe.GetPortByTag(CodexContainerRecipe.DiscoveryPortTag);
return $"{ip}:{discPort.Number}";
}
}
}
// var ip = peer.Node.Container.Pod.PodInfo.Ip;
// var discPort = peer.Node.Container.Recipe.GetPortByTag(CodexContainerRecipe.DiscoveryPortTag);
// return $"{ip}:{discPort.Number}";
// }
// }
//}

View File

@ -1,89 +1,89 @@
using DistTestCore.Codex;
using FileUtils;
using Logging;
using Utils;
using static DistTestCore.Helpers.FullConnectivityHelper;
//using DistTestCore.Codex;
//using FileUtils;
//using Logging;
//using Utils;
//using static DistTestCore.Helpers.FullConnectivityHelper;
namespace DistTestCore.Helpers
{
public class PeerDownloadTestHelpers : IFullConnectivityImplementation
{
private readonly FullConnectivityHelper helper;
private readonly BaseLog log;
private readonly FileManager fileManager;
private ByteSize testFileSize;
//namespace DistTestCore.Helpers
//{
// public class PeerDownloadTestHelpers : IFullConnectivityImplementation
// {
// private readonly FullConnectivityHelper helper;
// private readonly BaseLog log;
// private readonly FileManager fileManager;
// private ByteSize testFileSize;
public PeerDownloadTestHelpers(BaseLog log, FileManager fileManager)
{
helper = new FullConnectivityHelper(log, this);
testFileSize = 1.MB();
this.log = log;
this.fileManager = fileManager;
}
// public PeerDownloadTestHelpers(BaseLog log, FileManager fileManager)
// {
// helper = new FullConnectivityHelper(log, this);
// testFileSize = 1.MB();
// this.log = log;
// this.fileManager = fileManager;
// }
public void AssertFullDownloadInterconnectivity(IEnumerable<IOnlineCodexNode> nodes, ByteSize testFileSize)
{
AssertFullDownloadInterconnectivity(nodes.Select(n => ((OnlineCodexNode)n).CodexAccess), testFileSize);
}
// public void AssertFullDownloadInterconnectivity(IEnumerable<IOnlineCodexNode> nodes, ByteSize testFileSize)
// {
// AssertFullDownloadInterconnectivity(nodes.Select(n => ((OnlineCodexNode)n).CodexAccess), testFileSize);
// }
public void AssertFullDownloadInterconnectivity(IEnumerable<CodexAccess> nodes, ByteSize testFileSize)
{
this.testFileSize = testFileSize;
helper.AssertFullyConnected(nodes);
}
// public void AssertFullDownloadInterconnectivity(IEnumerable<CodexAccess> nodes, ByteSize testFileSize)
// {
// this.testFileSize = testFileSize;
// helper.AssertFullyConnected(nodes);
// }
public string Description()
{
return "Download Connectivity";
}
// public string Description()
// {
// return "Download Connectivity";
// }
public string ValidateEntry(Entry entry, Entry[] allEntries)
{
return string.Empty;
}
// public string ValidateEntry(Entry entry, Entry[] allEntries)
// {
// return string.Empty;
// }
public PeerConnectionState Check(Entry from, Entry to)
{
return fileManager.ScopedFiles(() => CheckConnectivity(from, to));
}
// public PeerConnectionState Check(Entry from, Entry to)
// {
// return fileManager.ScopedFiles(() => CheckConnectivity(from, to));
// }
private PeerConnectionState CheckConnectivity(Entry from, Entry to)
{
var expectedFile = GenerateTestFile(from.Node, to.Node);
// private PeerConnectionState CheckConnectivity(Entry from, Entry to)
// {
// var expectedFile = GenerateTestFile(from.Node, to.Node);
using var uploadStream = File.OpenRead(expectedFile.Filename);
var contentId = Stopwatch.Measure(log, "Upload", () => from.Node.UploadFile(uploadStream));
// using var uploadStream = File.OpenRead(expectedFile.Filename);
// var contentId = Stopwatch.Measure(log, "Upload", () => from.Node.UploadFile(uploadStream));
try
{
var downloadedFile = Stopwatch.Measure(log, "Download", () => DownloadFile(to.Node, contentId, expectedFile.Label + "_downloaded"));
expectedFile.AssertIsEqual(downloadedFile);
return PeerConnectionState.Connection;
}
catch
{
// Should an exception occur during the download or file-content assertion,
// We consider that as no-connection for the purpose of this test.
return PeerConnectionState.NoConnection;
}
// Should an exception occur during upload, then this try is inconclusive and we try again next loop.
}
// try
// {
// var downloadedFile = Stopwatch.Measure(log, "Download", () => DownloadFile(to.Node, contentId, expectedFile.Label + "_downloaded"));
// expectedFile.AssertIsEqual(downloadedFile);
// return PeerConnectionState.Connection;
// }
// catch
// {
// // Should an exception occur during the download or file-content assertion,
// // We consider that as no-connection for the purpose of this test.
// return PeerConnectionState.NoConnection;
// }
// // Should an exception occur during upload, then this try is inconclusive and we try again next loop.
// }
private TestFile DownloadFile(CodexAccess node, string contentId, string label)
{
var downloadedFile = fileManager.CreateEmptyTestFile(label);
using var downloadStream = File.OpenWrite(downloadedFile.Filename);
using var stream = node.DownloadFile(contentId);
stream.CopyTo(downloadStream);
return downloadedFile;
}
// private TestFile DownloadFile(CodexAccess node, string contentId, string label)
// {
// var downloadedFile = fileManager.CreateEmptyTestFile(label);
// using var downloadStream = File.OpenWrite(downloadedFile.Filename);
// using var stream = node.DownloadFile(contentId);
// stream.CopyTo(downloadStream);
// return downloadedFile;
// }
private TestFile GenerateTestFile(CodexAccess uploader, CodexAccess downloader)
{
var up = uploader.GetName().Replace("<", "").Replace(">", "");
var down = downloader.GetName().Replace("<", "").Replace(">", "");
var label = $"~from:{up}-to:{down}~";
return fileManager.GenerateTestFile(testFileSize, label);
}
}
}
// private TestFile GenerateTestFile(CodexAccess uploader, CodexAccess downloader)
// {
// var up = uploader.GetName().Replace("<", "").Replace(">", "");
// var down = downloader.GetName().Replace("<", "").Replace(">", "");
// var label = $"~from:{up}-to:{down}~";
// return fileManager.GenerateTestFile(testFileSize, label);
// }
// }
//}

View File

@ -1,16 +0,0 @@
using KubernetesWorkflow;
namespace DistTestCore.Marketplace
{
public class CodexContractsContainerConfig
{
public CodexContractsContainerConfig(string bootstrapNodeIp, Port jsonRpcPort)
{
BootstrapNodeIp = bootstrapNodeIp;
JsonRpcPort = jsonRpcPort;
}
public string BootstrapNodeIp { get; }
public Port JsonRpcPort { get; }
}
}

View File

@ -1,25 +0,0 @@
using KubernetesWorkflow;
namespace DistTestCore.Marketplace
{
public class CodexContractsContainerRecipe : DefaultContainerRecipe
{
public const string MarketplaceAddressFilename = "/hardhat/deployments/codexdisttestnetwork/Marketplace.json";
public const string MarketplaceArtifactFilename = "/hardhat/artifacts/contracts/Marketplace.sol/Marketplace.json";
public override string AppName => "codex-contracts";
public override string Image => "codexstorage/codex-contracts-eth:latest-dist-tests";
protected override void InitializeRecipe(StartupConfig startupConfig)
{
var config = startupConfig.Get<CodexContractsContainerConfig>();
var ip = config.BootstrapNodeIp;
var port = config.JsonRpcPort.Number;
AddEnvVar("DISTTEST_NETWORK_URL", $"http://{ip}:{port}");
AddEnvVar("HARDHAT_NETWORK", "codexdisttestnetwork");
AddEnvVar("KEEP_ALIVE", "1");
}
}
}

View File

@ -1,103 +0,0 @@
using KubernetesWorkflow;
using Utils;
namespace DistTestCore.Marketplace
{
public class CodexContractsStarter : BaseStarter
{
public CodexContractsStarter(TestLifecycle lifecycle)
: base(lifecycle)
{
}
public MarketplaceInfo Start(GethBootstrapNodeInfo bootstrapNode)
{
LogStart("Deploying Codex Marketplace...");
var workflow = lifecycle.WorkflowCreator.CreateWorkflow();
var startupConfig = CreateStartupConfig(bootstrapNode.RunningContainers.Containers[0]);
var containers = workflow.Start(1, Location.Unspecified, new CodexContractsContainerRecipe(), startupConfig);
if (containers.Containers.Length != 1) throw new InvalidOperationException("Expected 1 Codex contracts container to be created. Test infra failure.");
var container = containers.Containers[0];
WaitUntil(() =>
{
var logHandler = new ContractsReadyLogHandler(Debug);
workflow.DownloadContainerLog(container, logHandler, null);
return logHandler.Found;
});
Log("Contracts deployed. Extracting addresses...");
var extractor = new ContainerInfoExtractor(lifecycle.Log, workflow, container);
var marketplaceAddress = extractor.ExtractMarketplaceAddress();
var abi = extractor.ExtractMarketplaceAbi();
var interaction = bootstrapNode.StartInteraction(lifecycle);
var tokenAddress = interaction.GetTokenAddress(marketplaceAddress);
LogEnd("Extract completed. Marketplace deployed.");
return new MarketplaceInfo(marketplaceAddress, abi, tokenAddress);
}
private void WaitUntil(Func<bool> predicate)
{
Time.WaitUntil(predicate, TimeSpan.FromMinutes(3), TimeSpan.FromSeconds(2));
}
private StartupConfig CreateStartupConfig(RunningContainer bootstrapContainer)
{
var startupConfig = new StartupConfig();
var contractsConfig = new CodexContractsContainerConfig(bootstrapContainer.Pod.PodInfo.Ip, bootstrapContainer.Recipe.GetPortByTag(GethContainerRecipe.HttpPortTag));
startupConfig.Add(contractsConfig);
return startupConfig;
}
}
public class MarketplaceInfo
{
public MarketplaceInfo(string address, string abi, string tokenAddress)
{
Address = address;
Abi = abi;
TokenAddress = tokenAddress;
}
public string Address { get; }
public string Abi { get; }
public string TokenAddress { get; }
}
public class ContractsReadyLogHandler : LogHandler
{
// Log should contain 'Compiled 15 Solidity files successfully' at some point.
private const string RequiredCompiledString = "Solidity files successfully";
// When script is done, it prints the ready-string.
private const string ReadyString = "Done! Sleeping indefinitely...";
private readonly Action<string> debug;
public ContractsReadyLogHandler(Action<string> debug)
{
this.debug = debug;
debug($"Looking for '{RequiredCompiledString}' and '{ReadyString}' in container logs...");
}
public bool SeenCompileString { get; private set; }
public bool Found { get; private set; }
protected override void ProcessLine(string line)
{
debug(line);
if (line.Contains(RequiredCompiledString)) SeenCompileString = true;
if (line.Contains(ReadyString))
{
if (!SeenCompileString) throw new Exception("CodexContracts deployment failed. " +
"Solidity files not compiled before process exited.");
Found = true;
}
}
}
}

View File

@ -1,149 +0,0 @@
using KubernetesWorkflow;
using Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Utils;
namespace DistTestCore.Marketplace
{
public class ContainerInfoExtractor
{
private readonly BaseLog log;
private readonly StartupWorkflow workflow;
private readonly RunningContainer container;
public ContainerInfoExtractor(BaseLog log, StartupWorkflow workflow, RunningContainer container)
{
this.log = log;
this.workflow = workflow;
this.container = container;
}
public AllGethAccounts ExtractAccounts()
{
log.Debug();
var accountsCsv = Retry(() => FetchAccountsCsv());
if (string.IsNullOrEmpty(accountsCsv)) throw new InvalidOperationException("Unable to fetch accounts.csv for geth node. Test infra failure.");
var lines = accountsCsv.Split('\n');
return new AllGethAccounts(lines.Select(ParseLineToAccount).ToArray());
}
public string ExtractPubKey()
{
log.Debug();
var pubKey = Retry(FetchPubKey);
if (string.IsNullOrEmpty(pubKey)) throw new InvalidOperationException("Unable to fetch enode from geth node. Test infra failure.");
return pubKey;
}
public string ExtractMarketplaceAddress()
{
log.Debug();
var marketplaceAddress = Retry(FetchMarketplaceAddress);
if (string.IsNullOrEmpty(marketplaceAddress)) throw new InvalidOperationException("Unable to fetch marketplace account from codex-contracts node. Test infra failure.");
return marketplaceAddress;
}
public string ExtractMarketplaceAbi()
{
log.Debug();
var marketplaceAbi = Retry(FetchMarketplaceAbi);
if (string.IsNullOrEmpty(marketplaceAbi)) throw new InvalidOperationException("Unable to fetch marketplace artifacts from codex-contracts node. Test infra failure.");
return marketplaceAbi;
}
private string FetchAccountsCsv()
{
return workflow.ExecuteCommand(container, "cat", GethContainerRecipe.AccountsFilename);
}
private string FetchMarketplaceAddress()
{
var json = workflow.ExecuteCommand(container, "cat", CodexContractsContainerRecipe.MarketplaceAddressFilename);
var marketplace = JsonConvert.DeserializeObject<MarketplaceJson>(json);
return marketplace!.address;
}
private string FetchMarketplaceAbi()
{
var json = workflow.ExecuteCommand(container, "cat", CodexContractsContainerRecipe.MarketplaceArtifactFilename);
var artifact = JObject.Parse(json);
var abi = artifact["abi"];
return abi!.ToString(Formatting.None);
}
private string FetchPubKey()
{
var enodeFinder = new PubKeyFinder(s => log.Debug(s));
workflow.DownloadContainerLog(container, enodeFinder, null);
return enodeFinder.GetPubKey();
}
private GethAccount ParseLineToAccount(string l)
{
var tokens = l.Replace("\r", "").Split(',');
if (tokens.Length != 2) throw new InvalidOperationException();
var account = tokens[0];
var privateKey = tokens[1];
return new GethAccount(account, privateKey);
}
private static string Retry(Func<string> fetch)
{
return Time.Retry(fetch, nameof(ContainerInfoExtractor));
}
}
public class PubKeyFinder : LogHandler, ILogHandler
{
private const string openTag = "self=enode://";
private const string openTagQuote = "self=\"enode://";
private readonly Action<string> debug;
private string pubKey = string.Empty;
public PubKeyFinder(Action<string> debug)
{
this.debug = debug;
debug($"Looking for '{openTag}' in container logs...");
}
public string GetPubKey()
{
if (string.IsNullOrEmpty(pubKey)) throw new Exception("Not found yet exception.");
return pubKey;
}
protected override void ProcessLine(string line)
{
debug(line);
if (line.Contains(openTag))
{
ExtractPubKey(openTag, line);
}
else if (line.Contains(openTagQuote))
{
ExtractPubKey(openTagQuote, line);
}
}
private void ExtractPubKey(string tag, string line)
{
var openIndex = line.IndexOf(tag) + tag.Length;
var closeIndex = line.IndexOf("@");
pubKey = line.Substring(
startIndex: openIndex,
length: closeIndex - openIndex);
}
}
public class MarketplaceJson
{
public string address { get; set; } = string.Empty;
}
}

View File

@ -1,42 +0,0 @@
using KubernetesWorkflow;
using NethereumWorkflow;
namespace DistTestCore.Marketplace
{
public class GethBootstrapNodeInfo
{
public GethBootstrapNodeInfo(RunningContainers runningContainers, AllGethAccounts allAccounts, string pubKey, Port discoveryPort)
{
RunningContainers = runningContainers;
AllAccounts = allAccounts;
Account = allAccounts.Accounts[0];
PubKey = pubKey;
DiscoveryPort = discoveryPort;
}
public RunningContainers RunningContainers { get; }
public AllGethAccounts AllAccounts { get; }
public GethAccount Account { get; }
public string PubKey { get; }
public Port DiscoveryPort { get; }
public NethereumInteraction StartInteraction(TestLifecycle lifecycle)
{
var address = lifecycle.Configuration.GetAddress(RunningContainers.Containers[0]);
var account = Account;
var creator = new NethereumInteractionCreator(lifecycle.Log, address.Host, address.Port, account.PrivateKey);
return creator.CreateWorkflow();
}
}
public class AllGethAccounts
{
public GethAccount[] Accounts { get; }
public AllGethAccounts(GethAccount[] accounts)
{
Accounts = accounts;
}
}
}

View File

@ -1,40 +0,0 @@
using KubernetesWorkflow;
namespace DistTestCore.Marketplace
{
public class GethBootstrapNodeStarter : BaseStarter
{
public GethBootstrapNodeStarter(TestLifecycle lifecycle)
: base(lifecycle)
{
}
public GethBootstrapNodeInfo StartGethBootstrapNode()
{
LogStart("Starting Geth bootstrap node...");
var startupConfig = CreateBootstrapStartupConfig();
var workflow = lifecycle.WorkflowCreator.CreateWorkflow();
var containers = workflow.Start(1, Location.Unspecified, new GethContainerRecipe(), startupConfig);
if (containers.Containers.Length != 1) throw new InvalidOperationException("Expected 1 Geth bootstrap node to be created. Test infra failure.");
var bootstrapContainer = containers.Containers[0];
var extractor = new ContainerInfoExtractor(lifecycle.Log, workflow, bootstrapContainer);
var accounts = extractor.ExtractAccounts();
var pubKey = extractor.ExtractPubKey();
var discoveryPort = bootstrapContainer.Recipe.GetPortByTag(GethContainerRecipe.DiscoveryPortTag);
var result = new GethBootstrapNodeInfo(containers, accounts, pubKey, discoveryPort);
LogEnd($"Geth bootstrap node started with account '{result.Account.Account}'");
return result;
}
private StartupConfig CreateBootstrapStartupConfig()
{
var config = new StartupConfig();
config.Add(new GethStartupConfig(true, null!, 0, 0));
return config;
}
}
}

View File

@ -1,38 +0,0 @@
using KubernetesWorkflow;
using NethereumWorkflow;
namespace DistTestCore.Marketplace
{
public class GethCompanionNodeInfo
{
public GethCompanionNodeInfo(RunningContainer runningContainer, GethAccount[] accounts)
{
RunningContainer = runningContainer;
Accounts = accounts;
}
public RunningContainer RunningContainer { get; }
public GethAccount[] Accounts { get; }
public NethereumInteraction StartInteraction(TestLifecycle lifecycle, GethAccount account)
{
var address = lifecycle.Configuration.GetAddress(RunningContainer);
var privateKey = account.PrivateKey;
var creator = new NethereumInteractionCreator(lifecycle.Log, address.Host, address.Port, privateKey);
return creator.CreateWorkflow();
}
}
public class GethAccount
{
public GethAccount(string account, string privateKey)
{
Account = account;
PrivateKey = privateKey;
}
public string Account { get; }
public string PrivateKey { get; }
}
}

View File

@ -1,77 +0,0 @@
using KubernetesWorkflow;
using Utils;
namespace DistTestCore.Marketplace
{
public class GethCompanionNodeStarter : BaseStarter
{
private int companionAccountIndex = 0;
public GethCompanionNodeStarter(TestLifecycle lifecycle)
: base(lifecycle)
{
}
public GethCompanionNodeInfo StartCompanionNodeFor(CodexSetup codexSetup, MarketplaceNetwork marketplace)
{
LogStart($"Initializing companion for {codexSetup.NumberOfNodes} Codex nodes.");
var config = CreateCompanionNodeStartupConfig(marketplace.Bootstrap, codexSetup.NumberOfNodes);
var workflow = lifecycle.WorkflowCreator.CreateWorkflow();
var containers = workflow.Start(1, Location.Unspecified, new GethContainerRecipe(), CreateStartupConfig(config));
if (containers.Containers.Length != 1) throw new InvalidOperationException("Expected one Geth companion node to be created. Test infra failure.");
var container = containers.Containers[0];
var node = CreateCompanionInfo(container, marketplace, config);
EnsureCompanionNodeIsSynced(node, marketplace);
LogEnd($"Initialized one companion node for {codexSetup.NumberOfNodes} Codex nodes. Their accounts: [{string.Join(",", node.Accounts.Select(a => a.Account))}]");
return node;
}
private GethCompanionNodeInfo CreateCompanionInfo(RunningContainer container, MarketplaceNetwork marketplace, GethStartupConfig config)
{
var accounts = ExtractAccounts(marketplace, config);
return new GethCompanionNodeInfo(container, accounts);
}
private static GethAccount[] ExtractAccounts(MarketplaceNetwork marketplace, GethStartupConfig config)
{
return marketplace.Bootstrap.AllAccounts.Accounts
.Skip(1 + config.CompanionAccountStartIndex)
.Take(config.NumberOfCompanionAccounts)
.ToArray();
}
private void EnsureCompanionNodeIsSynced(GethCompanionNodeInfo node, MarketplaceNetwork marketplace)
{
try
{
Time.WaitUntil(() =>
{
var interaction = node.StartInteraction(lifecycle, node.Accounts.First());
return interaction.IsSynced(marketplace.Marketplace.Address, marketplace.Marketplace.Abi);
}, TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(3));
}
catch (Exception e)
{
throw new Exception("Geth companion node did not sync within timeout. Test infra failure.", e);
}
}
private GethStartupConfig CreateCompanionNodeStartupConfig(GethBootstrapNodeInfo bootstrapNode, int numberOfAccounts)
{
var config = new GethStartupConfig(false, bootstrapNode, companionAccountIndex, numberOfAccounts);
companionAccountIndex += numberOfAccounts;
return config;
}
private StartupConfig CreateStartupConfig(GethStartupConfig gethConfig)
{
var config = new StartupConfig();
config.Add(gethConfig);
return config;
}
}
}

View File

@ -1,73 +0,0 @@
using KubernetesWorkflow;
namespace DistTestCore.Marketplace
{
public class GethContainerRecipe : DefaultContainerRecipe
{
private const string defaultArgs = "--ipcdisable --syncmode full";
public const string HttpPortTag = "http_port";
public const string DiscoveryPortTag = "disc_port";
public const string AccountsFilename = "accounts.csv";
public override string AppName => "geth";
public override string Image => "codexstorage/dist-tests-geth:latest";
protected override void InitializeRecipe(StartupConfig startupConfig)
{
var config = startupConfig.Get<GethStartupConfig>();
var args = CreateArgs(config);
AddEnvVar("GETH_ARGS", args);
}
private string CreateArgs(GethStartupConfig config)
{
var discovery = AddInternalPort(tag: DiscoveryPortTag);
if (config.IsBootstrapNode)
{
return CreateBootstapArgs(discovery);
}
return CreateCompanionArgs(discovery, config);
}
private string CreateBootstapArgs(Port discovery)
{
AddEnvVar("ENABLE_MINER", "1");
UnlockAccounts(0, 1);
var exposedPort = AddExposedPort(tag: HttpPortTag);
return $"--http.port {exposedPort.Number} --port {discovery.Number} --discovery.port {discovery.Number} {defaultArgs}";
}
private string CreateCompanionArgs(Port discovery, GethStartupConfig config)
{
UnlockAccounts(
config.CompanionAccountStartIndex + 1,
config.NumberOfCompanionAccounts);
var port = AddInternalPort();
var authRpc = AddInternalPort();
var httpPort = AddExposedPort(tag: HttpPortTag);
var bootPubKey = config.BootstrapNode.PubKey;
var bootIp = config.BootstrapNode.RunningContainers.Containers[0].Pod.PodInfo.Ip;
var bootPort = config.BootstrapNode.DiscoveryPort.Number;
var bootstrapArg = $"--bootnodes enode://{bootPubKey}@{bootIp}:{bootPort} --nat=extip:{bootIp}";
return $"--port {port.Number} --discovery.port {discovery.Number} --authrpc.port {authRpc.Number} --http.addr 0.0.0.0 --http.port {httpPort.Number} --ws --ws.addr 0.0.0.0 --ws.port {httpPort.Number} {bootstrapArg} {defaultArgs}";
}
private void UnlockAccounts(int startIndex, int numberOfAccounts)
{
if (startIndex < 0) throw new ArgumentException();
if (numberOfAccounts < 1) throw new ArgumentException();
if (startIndex + numberOfAccounts > 1000) throw new ArgumentException("Out of accounts!");
AddEnvVar("UNLOCK_START_INDEX", startIndex.ToString());
AddEnvVar("UNLOCK_NUMBER", numberOfAccounts.ToString());
}
}
}

View File

@ -1,19 +0,0 @@
using Newtonsoft.Json;
namespace DistTestCore.Marketplace
{
public class GethStartResult
{
public GethStartResult(IMarketplaceAccessFactory marketplaceAccessFactory, MarketplaceNetwork marketplaceNetwork, GethCompanionNodeInfo companionNode)
{
MarketplaceAccessFactory = marketplaceAccessFactory;
MarketplaceNetwork = marketplaceNetwork;
CompanionNode = companionNode;
}
[JsonIgnore]
public IMarketplaceAccessFactory MarketplaceAccessFactory { get; }
public MarketplaceNetwork MarketplaceNetwork { get; }
public GethCompanionNodeInfo CompanionNode { get; }
}
}

View File

@ -1,18 +0,0 @@
namespace DistTestCore.Marketplace
{
public class GethStartupConfig
{
public GethStartupConfig(bool isBootstrapNode, GethBootstrapNodeInfo bootstrapNode, int companionAccountStartIndex, int numberOfCompanionAccounts)
{
IsBootstrapNode = isBootstrapNode;
BootstrapNode = bootstrapNode;
CompanionAccountStartIndex = companionAccountStartIndex;
NumberOfCompanionAccounts = numberOfCompanionAccounts;
}
public bool IsBootstrapNode { get; }
public GethBootstrapNodeInfo BootstrapNode { get; }
public int CompanionAccountStartIndex { get; }
public int NumberOfCompanionAccounts { get; }
}
}

View File

@ -1,243 +0,0 @@
using DistTestCore.Codex;
using DistTestCore.Helpers;
using Logging;
using Newtonsoft.Json;
using NUnit.Framework;
using NUnit.Framework.Constraints;
using System.Numerics;
using Utils;
namespace DistTestCore.Marketplace
{
public interface IMarketplaceAccess
{
string MakeStorageAvailable(ByteSize size, TestToken minPricePerBytePerSecond, TestToken maxCollateral, TimeSpan maxDuration);
StoragePurchaseContract RequestStorage(ContentId contentId, TestToken pricePerSlotPerSecond, TestToken requiredCollateral, uint minRequiredNumberOfNodes, int proofProbability, TimeSpan duration);
void AssertThatBalance(IResolveConstraint constraint, string message = "");
TestToken GetBalance();
}
public class MarketplaceAccess : IMarketplaceAccess
{
private readonly TestLifecycle lifecycle;
private readonly MarketplaceNetwork marketplaceNetwork;
private readonly GethAccount account;
private readonly CodexAccess codexAccess;
public MarketplaceAccess(TestLifecycle lifecycle, MarketplaceNetwork marketplaceNetwork, GethAccount account, CodexAccess codexAccess)
{
this.lifecycle = lifecycle;
this.marketplaceNetwork = marketplaceNetwork;
this.account = account;
this.codexAccess = codexAccess;
}
public StoragePurchaseContract RequestStorage(ContentId contentId, TestToken pricePerSlotPerSecond, TestToken requiredCollateral, uint minRequiredNumberOfNodes, int proofProbability, TimeSpan duration)
{
var request = new CodexSalesRequestStorageRequest
{
duration = ToDecInt(duration.TotalSeconds),
proofProbability = ToDecInt(proofProbability),
reward = ToDecInt(pricePerSlotPerSecond),
collateral = ToDecInt(requiredCollateral),
expiry = null,
nodes = minRequiredNumberOfNodes,
tolerance = null,
};
Log($"Requesting storage for: {contentId.Id}... (" +
$"pricePerSlotPerSecond: {pricePerSlotPerSecond}, " +
$"requiredCollateral: {requiredCollateral}, " +
$"minRequiredNumberOfNodes: {minRequiredNumberOfNodes}, " +
$"proofProbability: {proofProbability}, " +
$"duration: {Time.FormatDuration(duration)})");
var response = codexAccess.RequestStorage(request, contentId.Id);
if (response == "Purchasing not available")
{
throw new InvalidOperationException(response);
}
Log($"Storage requested successfully. PurchaseId: '{response}'.");
return new StoragePurchaseContract(lifecycle.Log, codexAccess, response, duration);
}
public string MakeStorageAvailable(ByteSize totalSpace, TestToken minPriceForTotalSpace, TestToken maxCollateral, TimeSpan maxDuration)
{
var request = new CodexSalesAvailabilityRequest
{
size = ToDecInt(totalSpace.SizeInBytes),
duration = ToDecInt(maxDuration.TotalSeconds),
maxCollateral = ToDecInt(maxCollateral),
minPrice = ToDecInt(minPriceForTotalSpace)
};
Log($"Making storage available... (" +
$"size: {totalSpace}, " +
$"minPriceForTotalSpace: {minPriceForTotalSpace}, " +
$"maxCollateral: {maxCollateral}, " +
$"maxDuration: {Time.FormatDuration(maxDuration)})");
var response = codexAccess.SalesAvailability(request);
Log($"Storage successfully made available. Id: {response.id}");
return response.id;
}
private string ToDecInt(double d)
{
var i = new BigInteger(d);
return i.ToString("D");
}
public string ToDecInt(TestToken t)
{
var i = new BigInteger(t.Amount);
return i.ToString("D");
}
public void AssertThatBalance(IResolveConstraint constraint, string message = "")
{
AssertHelpers.RetryAssert(constraint, GetBalance, message);
}
public TestToken GetBalance()
{
var interaction = marketplaceNetwork.StartInteraction(lifecycle);
var amount = interaction.GetBalance(marketplaceNetwork.Marketplace.TokenAddress, account.Account);
var balance = new TestToken(amount);
Log($"Balance of {account.Account} is {balance}.");
return balance;
}
private void Log(string msg)
{
lifecycle.Log.Log($"{codexAccess.Container.Name} {msg}");
}
}
public class MarketplaceUnavailable : IMarketplaceAccess
{
public StoragePurchaseContract RequestStorage(ContentId contentId, TestToken pricePerBytePerSecond, TestToken requiredCollateral, uint minRequiredNumberOfNodes, int proofProbability, TimeSpan duration)
{
Unavailable();
return null!;
}
public string MakeStorageAvailable(ByteSize size, TestToken minPricePerBytePerSecond, TestToken maxCollateral, TimeSpan duration)
{
Unavailable();
return string.Empty;
}
public void AssertThatBalance(IResolveConstraint constraint, string message = "")
{
Unavailable();
}
public TestToken GetBalance()
{
Unavailable();
return new TestToken(0);
}
private void Unavailable()
{
Assert.Fail("Incorrect test setup: Marketplace was not enabled for this group of Codex nodes. Add 'EnableMarketplace(...)' after 'SetupCodexNodes()' to enable it.");
throw new InvalidOperationException();
}
}
public class StoragePurchaseContract
{
private readonly BaseLog log;
private readonly CodexAccess codexAccess;
private DateTime? contractStartUtc;
public StoragePurchaseContract(BaseLog log, CodexAccess codexAccess, string purchaseId, TimeSpan contractDuration)
{
this.log = log;
this.codexAccess = codexAccess;
PurchaseId = purchaseId;
ContractDuration = contractDuration;
}
public string PurchaseId { get; }
public TimeSpan ContractDuration { get; }
public void WaitForStorageContractStarted()
{
WaitForStorageContractStarted(TimeSpan.FromSeconds(30));
}
public void WaitForStorageContractFinished()
{
if (!contractStartUtc.HasValue)
{
WaitForStorageContractStarted();
}
var gracePeriod = TimeSpan.FromSeconds(10);
var currentContractTime = DateTime.UtcNow - contractStartUtc!.Value;
var timeout = (ContractDuration - currentContractTime) + gracePeriod;
WaitForStorageContractState(timeout, "finished");
}
/// <summary>
/// Wait for contract to start. Max timeout depends on contract filesize. Allows more time for larger files.
/// </summary>
public void WaitForStorageContractStarted(ByteSize contractFileSize)
{
var filesizeInMb = contractFileSize.SizeInBytes / (1024 * 1024);
var maxWaitTime = TimeSpan.FromSeconds(filesizeInMb * 10.0);
WaitForStorageContractStarted(maxWaitTime);
}
public void WaitForStorageContractStarted(TimeSpan timeout)
{
WaitForStorageContractState(timeout, "started");
contractStartUtc = DateTime.UtcNow;
}
private void WaitForStorageContractState(TimeSpan timeout, string desiredState)
{
var lastState = "";
var waitStart = DateTime.UtcNow;
log.Log($"Waiting for {Time.FormatDuration(timeout)} for contract '{PurchaseId}' 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(1000);
if (lastState == "errored")
{
Assert.Fail("Contract errored: " + statusJson);
}
if (DateTime.UtcNow - waitStart > timeout)
{
Assert.Fail($"Contract did not reach '{desiredState}' within timeout. {statusJson}");
}
}
log.Log($"Contract '{desiredState}'.");
}
public CodexStoragePurchase GetPurchaseStatus(string purchaseId)
{
return codexAccess.GetPurchaseStatus(purchaseId);
}
}
}

View File

@ -1,41 +0,0 @@
using DistTestCore.Codex;
namespace DistTestCore.Marketplace
{
public interface IMarketplaceAccessFactory
{
IMarketplaceAccess CreateMarketplaceAccess(CodexAccess access);
}
public class MarketplaceUnavailableAccessFactory : IMarketplaceAccessFactory
{
public IMarketplaceAccess CreateMarketplaceAccess(CodexAccess access)
{
return new MarketplaceUnavailable();
}
}
public class GethMarketplaceAccessFactory : IMarketplaceAccessFactory
{
private readonly TestLifecycle lifecycle;
private readonly MarketplaceNetwork marketplaceNetwork;
public GethMarketplaceAccessFactory(TestLifecycle lifecycle, MarketplaceNetwork marketplaceNetwork)
{
this.lifecycle = lifecycle;
this.marketplaceNetwork = marketplaceNetwork;
}
public IMarketplaceAccess CreateMarketplaceAccess(CodexAccess access)
{
var companionNode = GetGethCompanionNode(access);
return new MarketplaceAccess(lifecycle, marketplaceNetwork, companionNode, access);
}
private GethAccount GetGethCompanionNode(CodexAccess access)
{
var account = access.Container.Recipe.Additionals.Single(a => a is GethAccount);
return (GethAccount)account;
}
}
}

View File

@ -1,17 +0,0 @@
namespace DistTestCore.Marketplace
{
public class MarketplaceInitialConfig
{
public MarketplaceInitialConfig(Ether initialEth, TestToken initialTestTokens, bool isValidator)
{
InitialEth = initialEth;
InitialTestTokens = initialTestTokens;
IsValidator = isValidator;
}
public Ether InitialEth { get; }
public TestToken InitialTestTokens { get; }
public bool IsValidator { get; }
public int? AccountIndexOverride { get; set; }
}
}

View File

@ -1,21 +0,0 @@
using NethereumWorkflow;
namespace DistTestCore.Marketplace
{
public class MarketplaceNetwork
{
public MarketplaceNetwork(GethBootstrapNodeInfo bootstrap, MarketplaceInfo marketplace)
{
Bootstrap = bootstrap;
Marketplace = marketplace;
}
public GethBootstrapNodeInfo Bootstrap { get; }
public MarketplaceInfo Marketplace { get; }
public NethereumInteraction StartInteraction(TestLifecycle lifecycle)
{
return Bootstrap.StartInteraction(lifecycle);
}
}
}

View File

@ -1,25 +0,0 @@
using KubernetesWorkflow;
namespace DistTestCore.Metrics
{
public class GrafanaContainerRecipe : DefaultContainerRecipe
{
public override string AppName => "grafana";
public override string Image => "grafana/grafana-oss:10.0.3";
public const string DefaultAdminUser = "adminium";
public const string DefaultAdminPassword = "passwordium";
protected override void InitializeRecipe(StartupConfig startupConfig)
{
AddExposedPort(3000);
AddEnvVar("GF_AUTH_ANONYMOUS_ENABLED", "true");
AddEnvVar("GF_AUTH_ANONYMOUS_ORG_NAME", "Main Org.");
AddEnvVar("GF_AUTH_ANONYMOUS_ORG_ROLE", "Editor");
AddEnvVar("GF_SECURITY_ADMIN_USER", DefaultAdminUser);
AddEnvVar("GF_SECURITY_ADMIN_PASSWORD", DefaultAdminPassword);
}
}
}

View File

@ -1,81 +0,0 @@
using DistTestCore.Helpers;
using KubernetesWorkflow;
using Logging;
using NUnit.Framework;
using NUnit.Framework.Constraints;
using Utils;
namespace DistTestCore.Metrics
{
public interface IMetricsAccess
{
void AssertThat(string metricName, IResolveConstraint constraint, string message = "");
}
public class MetricsAccess : IMetricsAccess
{
private readonly BaseLog log;
private readonly ITimeSet timeSet;
private readonly MetricsQuery query;
private readonly RunningContainer node;
public MetricsAccess(BaseLog log, ITimeSet timeSet, MetricsQuery query, RunningContainer node)
{
this.log = log;
this.timeSet = timeSet;
this.query = query;
this.node = node;
}
public void AssertThat(string metricName, IResolveConstraint constraint, string message = "")
{
AssertHelpers.RetryAssert(constraint, () =>
{
var metricSet = GetMetricWithTimeout(metricName);
var metricValue = metricSet.Values[0].Value;
log.Log($"{node.Name} metric '{metricName}' = {metricValue}");
return metricValue;
}, message);
}
public Metrics? GetAllMetrics()
{
return query.GetAllMetricsForNode(node);
}
private MetricsSet GetMetricWithTimeout(string metricName)
{
var start = DateTime.UtcNow;
while (true)
{
var mostRecent = GetMostRecent(metricName);
if (mostRecent != null) return mostRecent;
if (DateTime.UtcNow - start > timeSet.WaitForMetricTimeout())
{
Assert.Fail($"Timeout: Unable to get metric '{metricName}'.");
throw new TimeoutException();
}
Time.Sleep(TimeSpan.FromSeconds(2));
}
}
private MetricsSet? GetMostRecent(string metricName)
{
var result = query.GetMostRecent(metricName, node);
if (result == null) return null;
return result.Sets.LastOrDefault();
}
}
public class MetricsUnavailable : IMetricsAccess
{
public void AssertThat(string metricName, IResolveConstraint constraint, string message = "")
{
Assert.Fail("Incorrect test setup: Metrics were not enabled for this group of Codex nodes. Add 'EnableMetrics()' after 'SetupCodexNodes()' to enable it.");
throw new InvalidOperationException();
}
}
}

View File

@ -1,35 +0,0 @@
using KubernetesWorkflow;
namespace DistTestCore.Metrics
{
public interface IMetricsAccessFactory
{
IMetricsAccess CreateMetricsAccess(RunningContainer codexContainer);
}
public class MetricsUnavailableAccessFactory : IMetricsAccessFactory
{
public IMetricsAccess CreateMetricsAccess(RunningContainer codexContainer)
{
return new MetricsUnavailable();
}
}
public class CodexNodeMetricsAccessFactory : IMetricsAccessFactory
{
private readonly TestLifecycle lifecycle;
private readonly RunningContainers prometheusContainer;
public CodexNodeMetricsAccessFactory(TestLifecycle lifecycle, RunningContainers prometheusContainer)
{
this.lifecycle = lifecycle;
this.prometheusContainer = prometheusContainer;
}
public IMetricsAccess CreateMetricsAccess(RunningContainer codexContainer)
{
var query = new MetricsQuery(lifecycle, prometheusContainer);
return new MetricsAccess(lifecycle.Log, lifecycle.TimeSet, query, codexContainer);
}
}
}

View File

@ -1,80 +0,0 @@
using Logging;
using System.Globalization;
namespace DistTestCore.Metrics
{
public class MetricsDownloader
{
private readonly BaseLog log;
public MetricsDownloader(BaseLog log)
{
this.log = log;
}
public void DownloadAllMetricsForNode(string nodeName, MetricsAccess access)
{
var metrics = access.GetAllMetrics();
if (metrics == null || metrics.Sets.Length == 0 || metrics.Sets.All(s => s.Values.Length == 0)) return;
var headers = new[] { "timestamp" }.Concat(metrics.Sets.Select(s => s.Name)).ToArray();
var map = CreateValueMap(metrics);
WriteToFile(nodeName, headers, map);
}
private void WriteToFile(string nodeName, string[] headers, Dictionary<DateTime, List<string>> map)
{
var file = log.CreateSubfile("csv");
log.Log($"Downloading metrics for {nodeName} to file {file.FullFilename}");
file.WriteRaw(string.Join(",", headers));
foreach (var pair in map)
{
file.WriteRaw(string.Join(",", new[] { FormatTimestamp(pair.Key) }.Concat(pair.Value)));
}
}
private Dictionary<DateTime, List<string>> CreateValueMap(Metrics metrics)
{
var map = CreateForAllTimestamps(metrics);
foreach (var metric in metrics.Sets)
{
AddToMap(map, metric);
}
return map;
}
private Dictionary<DateTime, List<string>> CreateForAllTimestamps(Metrics metrics)
{
var result = new Dictionary<DateTime, List<string>>();
var timestamps = metrics.Sets.SelectMany(s => s.Values).Select(v => v.Timestamp).Distinct().ToArray();
foreach (var timestamp in timestamps) result.Add(timestamp, new List<string>());
return result;
}
private void AddToMap(Dictionary<DateTime, List<string>> map, MetricsSet metric)
{
foreach (var key in map.Keys)
{
map[key].Add(GetValueAtTimestamp(key, metric));
}
}
private string GetValueAtTimestamp(DateTime key, MetricsSet metric)
{
var value = metric.Values.SingleOrDefault(v => v.Timestamp == key);
if (value == null) return "";
return value.Value.ToString(CultureInfo.InvariantCulture);
}
private string FormatTimestamp(DateTime key)
{
var origin = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
var diff = key - origin;
return Math.Floor(diff.TotalSeconds).ToString(CultureInfo.InvariantCulture);
}
}
}

View File

@ -1,9 +0,0 @@
namespace DistTestCore.Metrics
{
public enum MetricsMode
{
None,
Record,
Dashboard
}
}

View File

@ -1,198 +0,0 @@
using DistTestCore.Codex;
using KubernetesWorkflow;
using System.Globalization;
namespace DistTestCore.Metrics
{
public class MetricsQuery
{
private readonly Http http;
public MetricsQuery(TestLifecycle lifecycle, RunningContainers runningContainers)
{
RunningContainers = runningContainers;
var address = lifecycle.Configuration.GetAddress(runningContainers.Containers[0]);
http = new Http(
lifecycle.Log,
lifecycle.TimeSet,
address,
"api/v1");
}
public RunningContainers RunningContainers { get; }
public Metrics? GetMostRecent(string metricName, RunningContainer node)
{
var response = GetLastOverTime(metricName, GetInstanceStringForNode(node));
if (response == null) return null;
return new Metrics
{
Sets = response.data.result.Select(r =>
{
return new MetricsSet
{
Instance = r.metric.instance,
Values = MapSingleValue(r.value)
};
}).ToArray()
};
}
public Metrics? GetMetrics(string metricName)
{
var response = GetAll(metricName);
if (response == null) return null;
return MapResponseToMetrics(response);
}
public Metrics? GetAllMetricsForNode(RunningContainer node)
{
var response = http.HttpGetJson<PrometheusQueryResponse>($"query?query={GetInstanceStringForNode(node)}{GetQueryTimeRange()}");
if (response.status != "success") return null;
return MapResponseToMetrics(response);
}
private PrometheusQueryResponse? GetLastOverTime(string metricName, string instanceString)
{
var response = http.HttpGetJson<PrometheusQueryResponse>($"query?query=last_over_time({metricName}{instanceString}{GetQueryTimeRange()})");
if (response.status != "success") return null;
return response;
}
private PrometheusQueryResponse? GetAll(string metricName)
{
var response = http.HttpGetJson<PrometheusQueryResponse>($"query?query={metricName}{GetQueryTimeRange()}");
if (response.status != "success") return null;
return response;
}
private Metrics MapResponseToMetrics(PrometheusQueryResponse response)
{
return new Metrics
{
Sets = response.data.result.Select(r =>
{
return new MetricsSet
{
Name = r.metric.__name__,
Instance = r.metric.instance,
Values = MapMultipleValues(r.values)
};
}).ToArray()
};
}
private MetricsSetValue[] MapSingleValue(object[] value)
{
if (value != null && value.Length > 0)
{
return new[]
{
MapValue(value)
};
}
return Array.Empty<MetricsSetValue>();
}
private MetricsSetValue[] MapMultipleValues(object[][] values)
{
if (values != null && values.Length > 0)
{
return values.Select(v => MapValue(v)).ToArray();
}
return Array.Empty<MetricsSetValue>();
}
private MetricsSetValue MapValue(object[] value)
{
if (value.Length != 2) throw new InvalidOperationException("Expected value to be [double, string].");
return new MetricsSetValue
{
Timestamp = ToTimestamp(value[0]),
Value = ToValue(value[1])
};
}
private string GetInstanceNameForNode(RunningContainer node)
{
var ip = node.Pod.PodInfo.Ip;
var port = node.Recipe.GetPortByTag(CodexContainerRecipe.MetricsPortTag).Number;
return $"{ip}:{port}";
}
private string GetInstanceStringForNode(RunningContainer node)
{
return "{instance=\"" + GetInstanceNameForNode(node) + "\"}";
}
private string GetQueryTimeRange()
{
return "[12h]";
}
private double ToValue(object v)
{
return Convert.ToDouble(v, CultureInfo.InvariantCulture);
}
private DateTime ToTimestamp(object v)
{
var unixSeconds = ToValue(v);
return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(unixSeconds);
}
}
public class Metrics
{
public MetricsSet[] Sets { get; set; } = Array.Empty<MetricsSet>();
}
public class MetricsSet
{
public string Name { get; set; } = string.Empty;
public string Instance { get; set; } = string.Empty;
public MetricsSetValue[] Values { get; set; } = Array.Empty<MetricsSetValue>();
}
public class MetricsSetValue
{
public DateTime Timestamp { get; set; }
public double Value { get; set; }
}
public class PrometheusQueryResponse
{
public string status { get; set; } = string.Empty;
public PrometheusQueryResponseData data { get; set; } = new();
}
public class PrometheusQueryResponseData
{
public string resultType { get; set; } = string.Empty;
public PrometheusQueryResponseDataResultEntry[] result { get; set; } = Array.Empty<PrometheusQueryResponseDataResultEntry>();
}
public class PrometheusQueryResponseDataResultEntry
{
public ResultEntryMetric metric { get; set; } = new();
public object[] value { get; set; } = Array.Empty<object>();
public object[][] values { get; set; } = Array.Empty<object[]>();
}
public class ResultEntryMetric
{
public string __name__ { get; set; } = string.Empty;
public string instance { get; set; } = string.Empty;
public string job { get; set; } = string.Empty;
}
public class PrometheusAllNamesResponse
{
public string status { get; set; } = string.Empty;
public string[] data { get; set; } = Array.Empty<string>();
}
}

View File

@ -1,18 +0,0 @@
using KubernetesWorkflow;
namespace DistTestCore.Metrics
{
public class PrometheusContainerRecipe : DefaultContainerRecipe
{
public override string AppName => "prometheus";
public override string Image => "codexstorage/dist-tests-prometheus:latest";
protected override void InitializeRecipe(StartupConfig startupConfig)
{
var config = startupConfig.Get<PrometheusStartupConfig>();
AddExposedPortAndVar("PROM_PORT");
AddEnvVar("PROM_CONFIG", config.PrometheusConfigBase64);
}
}
}

View File

@ -1,12 +0,0 @@
namespace DistTestCore.Metrics
{
public class PrometheusStartupConfig
{
public PrometheusStartupConfig(string prometheusConfigBase64)
{
PrometheusConfigBase64 = prometheusConfigBase64;
}
public string PrometheusConfigBase64 { get; }
}
}

View File

@ -1,6 +1,4 @@
using DistTestCore.Codex;
using DistTestCore.Metrics;
using KubernetesWorkflow;
using KubernetesWorkflow;
using System.Text;
namespace DistTestCore
@ -14,39 +12,40 @@ namespace DistTestCore
public RunningContainers CollectMetricsFor(RunningContainers[] containers)
{
LogStart($"Starting metrics server for {containers.Describe()}");
var startupConfig = new StartupConfig();
startupConfig.Add(new PrometheusStartupConfig(GeneratePrometheusConfig(containers.Containers())));
//LogStart($"Starting metrics server for {containers.Describe()}");
//var startupConfig = new StartupConfig();
//startupConfig.Add(new PrometheusStartupConfig(GeneratePrometheusConfig(containers.Containers())));
var workflow = lifecycle.WorkflowCreator.CreateWorkflow();
var runningContainers = workflow.Start(1, Location.Unspecified, new PrometheusContainerRecipe(), startupConfig);
if (runningContainers.Containers.Length != 1) throw new InvalidOperationException("Expected only 1 Prometheus container to be created.");
//var workflow = lifecycle.WorkflowCreator.CreateWorkflow();
//var runningContainers = workflow.Start(1, Location.Unspecified, new PrometheusContainerRecipe(), startupConfig);
//if (runningContainers.Containers.Length != 1) throw new InvalidOperationException("Expected only 1 Prometheus container to be created.");
return runningContainers;
//return runningContainers;
return null!;
}
private string GeneratePrometheusConfig(RunningContainer[] nodes)
{
var config = "";
config += "global:\n";
config += " scrape_interval: 10s\n";
config += " scrape_timeout: 10s\n";
config += "\n";
config += "scrape_configs:\n";
config += " - job_name: services\n";
config += " metrics_path: /metrics\n";
config += " static_configs:\n";
config += " - targets:\n";
//private string GeneratePrometheusConfig(RunningContainer[] nodes)
//{
// var config = "";
// config += "global:\n";
// config += " scrape_interval: 10s\n";
// config += " scrape_timeout: 10s\n";
// config += "\n";
// config += "scrape_configs:\n";
// config += " - job_name: services\n";
// config += " metrics_path: /metrics\n";
// config += " static_configs:\n";
// config += " - targets:\n";
foreach (var node in nodes)
{
var ip = node.Pod.PodInfo.Ip;
var port = node.Recipe.GetPortByTag(CodexContainerRecipe.MetricsPortTag).Number;
config += $" - '{ip}:{port}'\n";
}
// foreach (var node in nodes)
// {
// var ip = node.Pod.PodInfo.Ip;
// var port = node.Recipe.GetPortByTag(CodexContainerRecipe.MetricsPortTag).Number;
// config += $" - '{ip}:{port}'\n";
// }
var bytes = Encoding.ASCII.GetBytes(config);
return Convert.ToBase64String(bytes);
}
// var bytes = Encoding.ASCII.GetBytes(config);
// return Convert.ToBase64String(bytes);
//}
}
}

View File

@ -1,7 +1,4 @@
using DistTestCore.Codex;
using DistTestCore.Logs;
using DistTestCore.Marketplace;
using DistTestCore.Metrics;
using DistTestCore.Logs;
using FileUtils;
using KubernetesWorkflow;
using Logging;
@ -22,12 +19,12 @@ namespace DistTestCore
WorkflowCreator = new WorkflowCreator(log, configuration.GetK8sConfiguration(timeSet), testNamespace);
FileManager = new FileManager(Log, configuration.GetFileManagerFolder());
CodexStarter = new CodexStarter(this);
//CodexStarter = new CodexStarter(this);
PrometheusStarter = new PrometheusStarter(this);
GrafanaStarter = new GrafanaStarter(this);
GethStarter = new GethStarter(this);
//GethStarter = new GethStarter(this);
testStart = DateTime.UtcNow;
CodexVersion = null;
//CodexVersion = null;
Log.WriteLogTag();
}
@ -37,15 +34,15 @@ namespace DistTestCore
public ITimeSet TimeSet { get; }
public WorkflowCreator WorkflowCreator { get; }
public FileManager FileManager { get; }
public CodexStarter CodexStarter { get; }
//public CodexStarter CodexStarter { get; }
public PrometheusStarter PrometheusStarter { get; }
public GrafanaStarter GrafanaStarter { get; }
public GethStarter GethStarter { get; }
public CodexDebugVersionResponse? CodexVersion { get; private set; }
//public GethStarter GethStarter { get; }
//public CodexDebugVersionResponse? CodexVersion { get; private set; }
public void DeleteAllResources()
{
CodexStarter.DeleteAllResources();
//CodexStarter.DeleteAllResources();
FileManager.DeleteAllTestFiles();
}
@ -56,7 +53,7 @@ namespace DistTestCore
var handler = new LogDownloadHandler(container, description, subFile);
Log.Log($"Downloading logs for {description} to file '{subFile.FullFilename}'");
CodexStarter.DownloadLog(container, handler, tailLines);
//CodexStarter.DownloadLog(container, handler, tailLines);
return new DownloadedLog(subFile, description);
}
@ -67,28 +64,30 @@ namespace DistTestCore
return Time.FormatDuration(testDuration);
}
public void SetCodexVersion(CodexDebugVersionResponse version)
{
if (CodexVersion == null) CodexVersion = version;
}
//public void SetCodexVersion(CodexDebugVersionResponse version)
//{
// if (CodexVersion == null) CodexVersion = version;
//}
public ApplicationIds GetApplicationIds()
{
return new ApplicationIds(
codexId: GetCodexId(),
gethId: new GethContainerRecipe().Image,
prometheusId: new PrometheusContainerRecipe().Image,
codexContractsId: new CodexContractsContainerRecipe().Image,
grafanaId: new GrafanaContainerRecipe().Image
);
//return new ApplicationIds(
// codexId: GetCodexId(),
// gethId: new GethContainerRecipe().Image,
// prometheusId: new PrometheusContainerRecipe().Image,
// codexContractsId: new CodexContractsContainerRecipe().Image,
// grafanaId: new GrafanaContainerRecipe().Image
//);
return null!;
}
private string GetCodexId()
{
var v = CodexVersion;
if (v == null) return new CodexContainerRecipe().Image;
if (v.version != "untagged build") return v.version;
return v.revision;
return "";
//var v = CodexVersion;
//if (v == null) return new CodexContainerRecipe().Image;
//if (v.version != "untagged build") return v.version;
//return v.revision;
}
}
}

View File

@ -1,252 +0,0 @@
using DistTestCore;
using NUnit.Framework;
using Utils;
namespace Tests.BasicTests
{
[Ignore("Used for debugging continuous tests")]
[TestFixture]
public class ContinuousSubstitute : AutoBootstrapDistTest
{
[Test]
public void ContinuousTestSubstitute()
{
var group = SetupCodexNodes(5, o => o
.EnableMetrics()
.EnableMarketplace(100000.TestTokens(), 0.Eth(), isValidator: true)
.WithBlockTTL(TimeSpan.FromMinutes(2))
.WithBlockMaintenanceInterval(TimeSpan.FromMinutes(2))
.WithBlockMaintenanceNumber(10000)
.WithBlockTTL(TimeSpan.FromMinutes(2))
.WithStorageQuota(1.GB()));
var nodes = group.Cast<OnlineCodexNode>().ToArray();
foreach (var node in nodes)
{
node.Marketplace.MakeStorageAvailable(
size: 500.MB(),
minPricePerBytePerSecond: 1.TestTokens(),
maxCollateral: 1024.TestTokens(),
maxDuration: TimeSpan.FromMinutes(5));
}
var endTime = DateTime.UtcNow + TimeSpan.FromHours(10);
while (DateTime.UtcNow < endTime)
{
var allNodes = nodes.ToList();
var primary = allNodes.PickOneRandom();
var secondary = allNodes.PickOneRandom();
Log("Run Test");
PerformTest(primary, secondary);
Thread.Sleep(TimeSpan.FromSeconds(5));
}
}
[Test]
public void PeerTest()
{
var group = SetupCodexNodes(5, o => o
.EnableMetrics()
.EnableMarketplace(100000.TestTokens(), 0.Eth(), isValidator: true)
.WithBlockTTL(TimeSpan.FromMinutes(2))
.WithBlockMaintenanceInterval(TimeSpan.FromMinutes(2))
.WithBlockMaintenanceNumber(10000)
.WithBlockTTL(TimeSpan.FromMinutes(2))
.WithStorageQuota(1.GB()));
var nodes = group.Cast<OnlineCodexNode>().ToArray();
var checkTime = DateTime.UtcNow + TimeSpan.FromMinutes(1);
var endTime = DateTime.UtcNow + TimeSpan.FromHours(10);
while (DateTime.UtcNow < endTime)
{
CreatePeerConnectionTestHelpers().AssertFullyConnected(GetAllOnlineCodexNodes());
CheckRoutingTables(GetAllOnlineCodexNodes());
var node = RandomUtils.PickOneRandom(nodes.ToList());
var file = GenerateTestFile(50.MB());
node.UploadFile(file);
Thread.Sleep(20000);
}
}
private void CheckRoutingTables(IEnumerable<IOnlineCodexNode> nodes)
{
var all = nodes.ToArray();
var allIds = all.Select(n => n.GetDebugInfo().table.localNode.nodeId).ToArray();
var errors = all.Select(n => AreAllPresent(n, allIds)).Where(s => !string.IsNullOrEmpty(s)).ToArray();
if (errors.Any())
{
Assert.Fail(string.Join(Environment.NewLine, errors));
}
}
private string AreAllPresent(IOnlineCodexNode n, string[] allIds)
{
var info = n.GetDebugInfo();
var known = info.table.nodes.Select(n => n.nodeId).ToArray();
var expected = allIds.Where(i => i != info.table.localNode.nodeId).ToArray();
if (!expected.All(ex => known.Contains(ex)))
{
return $"Not all of '{string.Join(",", expected)}' were present in routing table: '{string.Join(",", known)}'";
}
return string.Empty;
}
private ByteSize fileSize = 80.MB();
private void PerformTest(IOnlineCodexNode primary, IOnlineCodexNode secondary)
{
ScopedTestFiles(() =>
{
var testFile = GenerateTestFile(fileSize);
var contentId = primary.UploadFile(testFile);
var downloadedFile = secondary.DownloadContent(contentId);
testFile.AssertIsEqual(downloadedFile);
});
}
[Test]
public void HoldMyBeerTest()
{
var blockExpirationTime = TimeSpan.FromMinutes(3);
var group = SetupCodexNodes(3, o => o
.EnableMetrics()
.WithBlockTTL(blockExpirationTime)
.WithBlockMaintenanceInterval(TimeSpan.FromMinutes(2))
.WithBlockMaintenanceNumber(10000)
.WithStorageQuota(2000.MB()));
var nodes = group.Cast<OnlineCodexNode>().ToArray();
var endTime = DateTime.UtcNow + TimeSpan.FromHours(24);
var filesize = 80.MB();
double codexDefaultBlockSize = 31 * 64 * 33;
var numberOfBlocks = Convert.ToInt64(Math.Ceiling(filesize.SizeInBytes / codexDefaultBlockSize));
var sizeInBytes = filesize.SizeInBytes;
Assert.That(numberOfBlocks, Is.EqualTo(1282));
var startTime = DateTime.UtcNow;
var successfulUploads = 0;
var successfulDownloads = 0;
while (DateTime.UtcNow < endTime)
{
foreach (var node in nodes)
{
try
{
Thread.Sleep(TimeSpan.FromSeconds(5));
ScopedTestFiles(() =>
{
var uploadStartTime = DateTime.UtcNow;
var file = GenerateTestFile(filesize);
var cid = node.UploadFile(file);
var cidTag = cid.Id.Substring(cid.Id.Length - 6);
Measure("upload-log-asserts", () =>
{
var uploadLog = node.DownloadLog(tailLines: 50000);
var storeLines = uploadLog.FindLinesThatContain("Stored data", "topics=\"codex node\"");
uploadLog.DeleteFile();
var storeLine = GetLineForCidTag(storeLines, cidTag);
AssertStoreLineContains(storeLine, numberOfBlocks, sizeInBytes);
});
successfulUploads++;
var uploadTimeTaken = DateTime.UtcNow - uploadStartTime;
if (uploadTimeTaken >= blockExpirationTime.Subtract(TimeSpan.FromSeconds(10)))
{
Assert.Fail("Upload took too long. Blocks already expired.");
}
var dl = node.DownloadContent(cid);
file.AssertIsEqual(dl);
Measure("download-log-asserts", () =>
{
var downloadLog = node.DownloadLog(tailLines: 50000);
var sentLines = downloadLog.FindLinesThatContain("Sent bytes", "topics=\"codex restapi\"");
downloadLog.DeleteFile();
var sentLine = GetLineForCidTag(sentLines, cidTag);
AssertSentLineContains(sentLine, sizeInBytes);
});
successfulDownloads++;
});
}
catch
{
var testDuration = DateTime.UtcNow - startTime;
Log("Test failed. Delaying shut-down by 30 seconds to collect metrics.");
Log($"Test failed after {Time.FormatDuration(testDuration)} and {successfulUploads} successful uploads and {successfulDownloads} successful downloads");
Thread.Sleep(TimeSpan.FromSeconds(30));
throw;
}
}
Thread.Sleep(TimeSpan.FromSeconds(5));
}
}
private void AssertSentLineContains(string sentLine, long sizeInBytes)
{
var tag = "bytes=";
var token = sentLine.Substring(sentLine.IndexOf(tag) + tag.Length);
var bytes = Convert.ToInt64(token);
Assert.AreEqual(sizeInBytes, bytes, $"Sent bytes: Number of bytes incorrect. Line: '{sentLine}'");
}
private void AssertStoreLineContains(string storeLine, long numberOfBlocks, long sizeInBytes)
{
var tokens = storeLine.Split(" ");
var blocksToken = GetToken(tokens, "blocks=");
var sizeToken = GetToken(tokens, "size=");
if (blocksToken == null) Assert.Fail("blockToken not found in " + storeLine);
if (sizeToken == null) Assert.Fail("sizeToken not found in " + storeLine);
var blocks = Convert.ToInt64(blocksToken);
var size = Convert.ToInt64(sizeToken?.Replace("'NByte", ""));
var lineLog = $" Line: '{storeLine}'";
Assert.AreEqual(numberOfBlocks, blocks, "Stored data: Number of blocks incorrect." + lineLog);
Assert.AreEqual(sizeInBytes, size, "Stored data: Number of blocks incorrect." + lineLog);
}
private string GetLineForCidTag(string[] lines, string cidTag)
{
var result = lines.SingleOrDefault(l => l.Contains(cidTag));
if (result == null)
{
Assert.Fail($"Failed to find '{cidTag}' in lines: '{string.Join(",", lines)}'");
throw new Exception();
}
return result;
}
private string? GetToken(string[] tokens, string tag)
{
var token = tokens.SingleOrDefault(t => t.StartsWith(tag));
if (token == null) return null;
return token.Substring(tag.Length);
}
}
}

View File

@ -1,86 +0,0 @@
using DistTestCore;
using NUnit.Framework;
using Utils;
namespace Tests.BasicTests
{
[TestFixture]
public class ExampleTests : DistTest
{
[Test]
public void CodexLogExample()
{
var primary = SetupCodexNodes(2)[0];
primary.UploadFile(GenerateTestFile(5.MB()));
var log = primary.DownloadLog();
log.AssertLogContains("Uploaded file");
}
[Test]
public void TwoMetricsExample()
{
var group = SetupCodexNodes(2, s => s.EnableMetrics());
var group2 = SetupCodexNodes(2, s => s.EnableMetrics());
var primary = group[0];
var secondary = group[1];
var primary2 = group2[0];
var secondary2 = group2[1];
primary.ConnectToPeer(secondary);
primary2.ConnectToPeer(secondary2);
Thread.Sleep(TimeSpan.FromMinutes(2));
primary.Metrics.AssertThat("libp2p_peers", Is.EqualTo(1));
primary2.Metrics.AssertThat("libp2p_peers", Is.EqualTo(1));
}
[Test]
public void MarketplaceExample()
{
var sellerInitialBalance = 234.TestTokens();
var buyerInitialBalance = 1000.TestTokens();
var fileSize = 10.MB();
var seller = SetupCodexNode(s => s
.WithStorageQuota(11.GB())
.EnableMarketplace(sellerInitialBalance));
seller.Marketplace.AssertThatBalance(Is.EqualTo(sellerInitialBalance));
seller.Marketplace.MakeStorageAvailable(
size: 10.GB(),
minPricePerBytePerSecond: 1.TestTokens(),
maxCollateral: 20.TestTokens(),
maxDuration: TimeSpan.FromMinutes(3));
var testFile = GenerateTestFile(fileSize);
var buyer = SetupCodexNode(s => s
.WithBootstrapNode(seller)
.EnableMarketplace(buyerInitialBalance));
buyer.Marketplace.AssertThatBalance(Is.EqualTo(buyerInitialBalance));
var contentId = buyer.UploadFile(testFile);
var purchaseContract = buyer.Marketplace.RequestStorage(contentId,
pricePerSlotPerSecond: 2.TestTokens(),
requiredCollateral: 10.TestTokens(),
minRequiredNumberOfNodes: 1,
proofProbability: 5,
duration: TimeSpan.FromMinutes(1));
purchaseContract.WaitForStorageContractStarted(fileSize);
seller.Marketplace.AssertThatBalance(Is.LessThan(sellerInitialBalance), "Collateral was not placed.");
purchaseContract.WaitForStorageContractFinished();
seller.Marketplace.AssertThatBalance(Is.GreaterThan(sellerInitialBalance), "Seller was not paid for storage.");
buyer.Marketplace.AssertThatBalance(Is.LessThan(buyerInitialBalance), "Buyer was not charged for storage.");
}
}
}

View File

@ -1,46 +0,0 @@
using DistTestCore;
using NUnit.Framework;
using Utils;
namespace Tests.BasicTests
{
// Warning!
// This is a test to check network-isolation in the test-infrastructure.
// It requires parallelism(2) or greater to run.
[TestFixture]
[Ignore("Disabled until a solution is implemented.")]
public class NetworkIsolationTest : DistTest
{
private IOnlineCodexNode? node = null;
[Test]
public void SetUpANodeAndWait()
{
node = SetupCodexNode();
Time.WaitUntil(() => node == null, TimeSpan.FromMinutes(5), TimeSpan.FromSeconds(5));
}
[Test]
public void ForeignNodeConnects()
{
var myNode = SetupCodexNode();
Time.WaitUntil(() => node != null, TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(5));
try
{
myNode.ConnectToPeer(node!);
}
catch
{
// Good! This connection should be prohibited by the network isolation policy.
node = null;
return;
}
Assert.Fail("Connection could be established between two Codex nodes running in different namespaces. " +
"This may cause cross-test interference. Network isolation policy should be applied. Test infra failure.");
}
}
}

View File

@ -1,41 +0,0 @@
using DistTestCore;
using NUnit.Framework;
using Utils;
namespace Tests.BasicTests
{
[TestFixture]
public class OneClientTests : DistTest
{
[Test]
public void OneClientTest()
{
var primary = SetupCodexNode();
PerformOneClientTest(primary);
}
[Test]
public void RestartTest()
{
var primary = SetupCodexNode();
var setup = primary.BringOffline();
primary = BringOnline(setup)[0];
PerformOneClientTest(primary);
}
private void PerformOneClientTest(IOnlineCodexNode primary)
{
var testFile = GenerateTestFile(1.MB());
var contentId = primary.UploadFile(testFile);
var downloadedFile = primary.DownloadContent(contentId);
testFile.AssertIsEqual(downloadedFile);
}
}
}

View File

@ -1,25 +0,0 @@
using DistTestCore;
using NUnit.Framework;
using Utils;
namespace Tests.BasicTests
{
[TestFixture]
public class ThreeClientTest : AutoBootstrapDistTest
{
[Test]
public void ThreeClient()
{
var primary = SetupCodexNode();
var secondary = SetupCodexNode();
var testFile = GenerateTestFile(10.MB());
var contentId = primary.UploadFile(testFile);
var downloadedFile = secondary.DownloadContent(contentId);
testFile.AssertIsEqual(downloadedFile);
}
}
}

View File

@ -2,6 +2,7 @@
using KubernetesWorkflow;
using NUnit.Framework;
using Utils;
using CodexPlugin;
namespace Tests.BasicTests
{
@ -11,7 +12,7 @@ namespace Tests.BasicTests
[Test]
public void TwoClientTest()
{
var group = SetupCodexNodes(2);
var group = this.SetupCodexNodes(2);
var primary = group[0];
var secondary = group[1];
@ -22,8 +23,8 @@ namespace Tests.BasicTests
[Test]
public void TwoClientsTwoLocationsTest()
{
var primary = SetupCodexNode(s => s.At(Location.One));
var secondary = SetupCodexNode(s => s.At(Location.Two));
var primary = this.SetupCodexNode(s => s.At(Location.One));
var secondary = this.SetupCodexNode(s => s.At(Location.Two));
PerformTwoClientTest(primary, secondary);
}

View File

@ -1,42 +0,0 @@
using DistTestCore;
using NUnit.Framework;
using Utils;
namespace Tests.DownloadConnectivityTests
{
[TestFixture]
public class FullyConnectedDownloadTests : AutoBootstrapDistTest
{
[Test]
public void MetricsDoesNotInterfereWithPeerDownload()
{
SetupCodexNodes(2, s => s.EnableMetrics());
AssertAllNodesConnected();
}
[Test]
public void MarketplaceDoesNotInterfereWithPeerDownload()
{
SetupCodexNodes(2, s => s.EnableMetrics().EnableMarketplace(1000.TestTokens()));
AssertAllNodesConnected();
}
[Test]
[Combinatorial]
public void FullyConnectedDownloadTest(
[Values(3, 5)] int numberOfNodes,
[Values(10, 80)] int sizeMBs)
{
SetupCodexNodes(numberOfNodes);
AssertAllNodesConnected(sizeMBs);
}
private void AssertAllNodesConnected(int sizeMBs = 10)
{
CreatePeerDownloadTestHelpers().AssertFullDownloadInterconnectivity(GetAllOnlineCodexNodes(), sizeMBs.MB());
}
}
}

View File

@ -1,52 +0,0 @@
using DistTestCore;
using NUnit.Framework;
namespace Tests.PeerDiscoveryTests
{
[TestFixture]
public class LayeredDiscoveryTests : DistTest
{
[Test]
public void TwoLayersTest()
{
var root = SetupCodexNode();
var l1Source = SetupCodexNode(s => s.WithBootstrapNode(root));
var l1Node = SetupCodexNode(s => s.WithBootstrapNode(root));
var l2Target = SetupCodexNode(s => s.WithBootstrapNode(l1Node));
AssertAllNodesConnected();
}
[Test]
public void ThreeLayersTest()
{
var root = SetupCodexNode();
var l1Source = SetupCodexNode(s => s.WithBootstrapNode(root));
var l1Node = SetupCodexNode(s => s.WithBootstrapNode(root));
var l2Node = SetupCodexNode(s => s.WithBootstrapNode(l1Node));
var l3Target = SetupCodexNode(s => s.WithBootstrapNode(l2Node));
AssertAllNodesConnected();
}
[TestCase(3)]
[TestCase(5)]
[TestCase(10)]
[TestCase(20)]
public void NodeChainTest(int chainLength)
{
var node = SetupCodexNode();
for (var i = 1; i < chainLength; i++)
{
node = SetupCodexNode(s => s.WithBootstrapNode(node));
}
AssertAllNodesConnected();
}
private void AssertAllNodesConnected()
{
CreatePeerConnectionTestHelpers().AssertFullyConnected(GetAllOnlineCodexNodes());
}
}
}

View File

@ -1,51 +0,0 @@
using DistTestCore;
using NUnit.Framework;
namespace Tests.PeerDiscoveryTests
{
[TestFixture]
public class PeerDiscoveryTests : AutoBootstrapDistTest
{
[Test]
public void CanReportUnknownPeerId()
{
var unknownId = "16Uiu2HAkv2CHWpff3dj5iuVNERAp8AGKGNgpGjPexJZHSqUstfsK";
var node = SetupCodexNode();
var result = node.GetDebugPeer(unknownId);
Assert.That(result.IsPeerFound, Is.False);
}
[Test]
public void MetricsDoesNotInterfereWithPeerDiscovery()
{
SetupCodexNodes(2, s => s.EnableMetrics());
AssertAllNodesConnected();
}
[Test]
public void MarketplaceDoesNotInterfereWithPeerDiscovery()
{
SetupCodexNodes(2, s => s.EnableMarketplace(1000.TestTokens()));
AssertAllNodesConnected();
}
[TestCase(2)]
[TestCase(3)]
[TestCase(10)]
[TestCase(20)]
public void VariableNodes(int number)
{
SetupCodexNodes(number);
AssertAllNodesConnected();
}
private void AssertAllNodesConnected()
{
CreatePeerConnectionTestHelpers().AssertFullyConnected(GetAllOnlineCodexNodes());
}
}
}

View File

@ -13,6 +13,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CodexPlugin\CodexPlugin.csproj" />
<ProjectReference Include="..\ContinuousTests\ContinuousTests.csproj" />
<ProjectReference Include="..\DistTestCore\DistTestCore.csproj" />
</ItemGroup>

View File

@ -5,8 +5,6 @@ VisualStudioVersion = 17.4.33213.308
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj", "{57F57B85-A537-4D3A-B7AE-B72C66B74AAB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestsLong", "LongTests\TestsLong.csproj", "{AFCE270E-F844-4A7C-9006-69AE622BB1F4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DistTestCore", "DistTestCore\DistTestCore.csproj", "{47F31305-6E68-4827-8E39-7B41DAA1CE7A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KubernetesWorkflow", "KubernetesWorkflow\KubernetesWorkflow.csproj", "{359123AA-3D9B-4442-80F4-19E32E3EC9EA}"
@ -27,6 +25,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodexNetDownloader", "Codex
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileUtils", "FileUtils\FileUtils.csproj", "{ECC954DA-8D4E-49EE-83AD-80085A43DEEB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodexPlugin", "CodexPlugin\CodexPlugin.csproj", "{DE4E802C-288C-45C4-84B6-8A5A6A96EF49}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -37,10 +37,6 @@ Global
{57F57B85-A537-4D3A-B7AE-B72C66B74AAB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{57F57B85-A537-4D3A-B7AE-B72C66B74AAB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{57F57B85-A537-4D3A-B7AE-B72C66B74AAB}.Release|Any CPU.Build.0 = Release|Any CPU
{AFCE270E-F844-4A7C-9006-69AE622BB1F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AFCE270E-F844-4A7C-9006-69AE622BB1F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AFCE270E-F844-4A7C-9006-69AE622BB1F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AFCE270E-F844-4A7C-9006-69AE622BB1F4}.Release|Any CPU.Build.0 = Release|Any CPU
{47F31305-6E68-4827-8E39-7B41DAA1CE7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{47F31305-6E68-4827-8E39-7B41DAA1CE7A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{47F31305-6E68-4827-8E39-7B41DAA1CE7A}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -81,6 +77,10 @@ Global
{ECC954DA-8D4E-49EE-83AD-80085A43DEEB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ECC954DA-8D4E-49EE-83AD-80085A43DEEB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ECC954DA-8D4E-49EE-83AD-80085A43DEEB}.Release|Any CPU.Build.0 = Release|Any CPU
{DE4E802C-288C-45C4-84B6-8A5A6A96EF49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DE4E802C-288C-45C4-84B6-8A5A6A96EF49}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DE4E802C-288C-45C4-84B6-8A5A6A96EF49}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DE4E802C-288C-45C4-84B6-8A5A6A96EF49}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE