Compare commits

...
Author SHA1 Message Date
Ben 5280f0732c Sets up docker image for creating keys 2024-06-19 14:04:27 +02:00
Ben 5ca135646a Container crash detection during start-up 2024-06-19 10:39:14 +02:00
Ben e1da38b6e8 Adds test for making sure node does not crash after mysterious CID is downloaded 2024-06-18 11:04:04 +02:00
benbierens 54f053cfcc Improves crash detection 2024-06-14 09:05:56 +02:00
Ben 7b5e802efb Applies delete in scalability tests 2024-06-13 08:51:52 +02:00
Ben 390b9de54a Replaces delete with disk info log. 2024-06-12 15:28:08 +02:00
Ben 4fc9835f43 Attempt to log disk space before and after uploads and downloads 2024-06-12 10:48:52 +02:00
Ben 9a46e20b4a Fixes exception in CodexDistTests 2024-06-11 11:33:02 +02:00
Ben 74ca512f92 Sets up test for downloading content by contract CID 2024-06-10 15:15:27 +02:00
Ben 22527a5d93 Deletes block repostore folder in container when Codex node stops. 2024-06-10 10:58:50 +02:00
benbierens 25663b59a0 sets trace level for scalability tests 2024-06-08 10:50:30 +02:00
benbierens aa416d50b3 ensuring enough mounted disk space 2024-06-08 10:36:23 +02:00
benbierens 16bf5ce5ab I think I may be on to something 2024-06-07 18:16:19 +02:00
benbierens 52ae671234 focussing on the interesting numbers for now 2024-06-07 17:08:14 +02:00
benbierens 5ffff1ed07 Disables metrics in scalability test. Downloads container log when retry attempt failed too quickly to be a timeout. 2024-06-07 17:07:35 +02:00
benbierens 18a02b1717 Implements mapping of debug/info routing table nodes 2024-06-07 16:37:31 +02:00
benbierens 3a61fc89c6 Adds WaitForCleanup test attribute to allow tests to wait for resources to be cleaned up 2024-06-06 15:09:52 +02:00
Ben 38c2d1749a Links up status method for checking. 2024-06-06 10:31:31 +02:00
Ben 9900db8d25 simply scalability test failure handling 2024-06-06 10:12:11 +02:00
Ben e53a644718 Updates to new codex image 2024-06-06 10:11:51 +02:00
Ben 62b56e198b Adds logging of node status to base codexdistTest 2024-06-06 09:54:50 +02:00
Ben f4d1dae478 Disables large network scalability test for now 2024-06-05 10:00:50 +02:00
Ben bf75c25152 Cannot assume retry uploads will require as much space as file is large 2024-06-05 09:59:03 +02:00
Ben b888999b61 Adds space check for upload call 2024-06-05 09:34:43 +02:00
Ben e9555cf99e Upload/download failure now automatically investigates node 2024-06-05 09:20:00 +02:00
Ben 11b866986d wip retry upgrade 2024-06-04 11:06:19 +02:00
benbierens 67fc2183b7 Better logging for slot-filled event checking 2024-06-03 12:56:19 +02:00
benbierens 02baa72c1e DebugInfo log 2024-06-03 11:42:52 +02:00
benbierens 383102b988 Adds container name to crash messge. 2024-06-03 11:36:48 +02:00
benbierens 3da36725e3 Updates codex image 2024-06-03 10:58:04 +02:00
benbierens 684a99027b nice checks for timings of successful contract 2024-05-24 14:33:59 +02:00
Ben 6c956c1a64 wip 2024-05-23 11:37:17 +02:00
Ben a749f82ca5 Switches blocktime logs to debug 2024-05-23 11:24:32 +02:00
Ben 6e9ea47b7d Tracking contract state change moments in MarketplaceAccess 2024-05-23 11:15:14 +02:00
Ben f7bdafbdc5 Improved representation of TestToken amounts 2024-05-22 11:06:34 +02:00
Ben e082f8a31c Changes Mint-TT bot parameter to BigInt type. 2024-05-21 13:09:46 +02:00
Ben ad2181db0b Fixes invariant-culture in assigner. Adds support for ulong. 2024-05-16 14:58:42 +02:00
Ben 3525d13e69 Merge branch 'feature/bot-use-ids' 2024-05-16 14:07:29 +02:00
Ben 8847de116d cleanup rewarder bot config 2024-05-16 14:07:14 +02:00
Ben 7d9dcb263d Prints default ArgsUniform values. Applies discord IDs instead of names. 2024-05-16 14:05:58 +02:00
Ben b3771cce32 Extract assigner from argsUniform 2024-05-16 12:14:03 +02:00
Ben 9717591224 Updates bot config to use ids instead of names 2024-05-16 11:54:31 +02:00
Ben 64ae7c8efe Latest image of Codex 2024-05-15 10:34:40 +02:00
benbierens d8d7605ce5 Increases marketplace graceperiod timespan. 2024-05-10 10:08:45 +02:00
benbierens 6995cbfb23 hacky way to download container logs while retries are failing. 2024-05-09 09:38:04 +02:00
benbierens c6a757d6fb Rename AddCodex. Better retry logging 2024-05-09 09:32:48 +02:00
benbierens c01f9dbb21 Fixes crash in blocktimefinder. 2024-05-09 08:57:19 +02:00
Ben 0ec43a9325 Shuts up json-serialization codex logging 2024-05-07 11:04:32 +02:00
Ben 266c661958 Updates codex API 2024-05-07 10:21:05 +02:00
Ben 8ea4c4ee37 Clean up codex long tests 2024-05-07 10:15:02 +02:00
Ben 725dfc23a6 Cleanup of codex tests 2024-05-07 09:49:00 +02:00
Ben 39ff757b39 Update + instructions for codex contracts 2024-05-06 16:12:26 +02:00
benbierens e187bfc941 Changes time.retry to fixed timelength instead of fixed number of retries 2024-05-02 08:41:20 +02:00
benbierens dd36929a81 Sets scalability tests to current codex image 2024-05-01 13:51:46 +02:00
Ben 2902f6baab latest master image of Codex 2024-05-01 10:05:46 +02:00
benbierens f38f7861a0 Log comparing test 2024-04-27 18:08:54 +02:00
benbierens 84a68521b0 sets up parser for codex log lines 2024-04-27 14:20:31 +02:00
benbierens ce7ba47a32 trying to detect upload slowdown related to number of blocks in node 2024-04-27 11:33:13 +02:00
Ben a20e9a30cf bumps to image with proof support 2024-04-26 11:10:14 +02:00
Ben 52cec0e9f3 bump codex image 2024-04-26 10:43:36 +02:00
Ben 9a6866cf8e clean up logs in Codex 2024-04-25 15:04:02 +02:00
Ben 15d7e6483e Allows parsing longs only from a certain timestamp 2024-04-25 10:57:19 +02:00
Ben e1c710a093 Merge branch 'less-announce-tests' 2024-04-25 10:25:29 +02:00
Ben 0c700ded9d Test for detecting common log lines 2024-04-25 10:25:12 +02:00
94 changed files with 1852 additions and 1036 deletions
+27
View File
@@ -0,0 +1,27 @@
name: Docker - KeyMaker
on:
push:
branches:
- master
tags:
- 'v*.*.*'
paths:
- 'Tools/KeyMaker/**'
- 'Framework/**'
- 'ProjectPlugins/**'
- .github/workflows/docker-KeyMaker.yml
- .github/workflows/docker-reusable.yml
workflow_dispatch:
jobs:
build-and-push:
name: Build and Push
uses: ./.github/workflows/docker-reusable.yml
with:
docker_file: Tools/KeyMaker/docker/Dockerfile
docker_repo: codexstorage/codex-keymaker
secrets: inherit
+33 -158
View File
@@ -4,9 +4,8 @@ namespace ArgsUniform
{
public class ArgsUniform<T>
{
private readonly Assigner<T> assigner;
private readonly Action printAppInfo;
private readonly object? defaultsProvider;
private readonly IEnv.IEnv env;
private readonly string[] args;
private const int cliStart = 8;
private const int shortStart = 38;
@@ -31,9 +30,9 @@ namespace ArgsUniform
public ArgsUniform(Action printAppInfo, object defaultsProvider, IEnv.IEnv env, params string[] args)
{
this.printAppInfo = printAppInfo;
this.defaultsProvider = defaultsProvider;
this.env = env;
this.args = args;
assigner = new Assigner<T>(env, args, defaultsProvider);
}
public T Parse(bool printResult = false)
@@ -42,7 +41,7 @@ namespace ArgsUniform
{
printAppInfo();
PrintHelp();
throw new Exception();
Environment.Exit(0);
}
var result = Activator.CreateInstance<T>();
@@ -53,18 +52,16 @@ namespace ArgsUniform
var attr = uniformProperty.GetCustomAttribute<UniformAttribute>();
if (attr != null)
{
if (!UniformAssign(result, attr, uniformProperty) && attr.Required)
if (!assigner.UniformAssign(result, attr, uniformProperty) && attr.Required)
{
{
missingRequired.Add(uniformProperty);
}
missingRequired.Add(uniformProperty);
}
}
}
if (missingRequired.Any())
{
PrintResults(result, uniformProperties);
PrintResults(printResult,result, uniformProperties);
Print("");
foreach (var missing in missingRequired)
{
@@ -75,37 +72,39 @@ namespace ArgsUniform
}
PrintHelp();
throw new ArgumentException("Unable to assemble all required arguments");
Environment.Exit(1);
}
if (printResult)
{
PrintResults(result, uniformProperties);
}
PrintResults(printResult, result, uniformProperties);
return result;
}
private void PrintResults(T result, PropertyInfo[] uniformProperties)
{
Print("");
foreach (var p in uniformProperties)
{
Print($"\t{p.Name} = {p.GetValue(result)}");
}
Print("");
}
public void PrintHelp()
{
Print("");
PrintAligned("CLI option:", "(short)", "Environment variable:", "Description");
var attrs = typeof(T).GetProperties().Where(m => m.GetCustomAttributes(typeof(UniformAttribute), false).Length == 1).Select(p => p.GetCustomAttribute<UniformAttribute>()).Where(a => a != null).ToArray();
foreach (var attr in attrs)
PrintAligned("CLI option:", "(short)", "Environment variable:", "Description", "(default)");
var props = typeof(T).GetProperties().Where(m => m.GetCustomAttributes(typeof(UniformAttribute), false).Length == 1).ToArray();
foreach (var prop in props)
{
var a = attr!;
var optional = !a.Required ? " *" : "";
PrintAligned($"--{a.Arg}=...", $"({a.ArgShort})", a.EnvVar, a.Description + optional);
var a = prop.GetCustomAttribute<UniformAttribute>();
if (a != null)
{
var optional = !a.Required ? " (optional)" : "";
var def = assigner.DescribeDefaultFor(prop);
PrintAligned($"--{a.Arg}=...", $"({a.ArgShort})", a.EnvVar, a.Description + optional, $"({def})");
}
}
Print("");
}
private void PrintResults(bool printResult, T result, PropertyInfo[] uniformProperties)
{
if (!printResult) return;
Print("");
foreach (var p in uniformProperties)
{
Print($"\t{p.Name} = {p.GetValue(result)}");
}
Print("");
}
@@ -115,7 +114,7 @@ namespace ArgsUniform
Console.WriteLine(msg);
}
private void PrintAligned(string cli, string s, string env, string desc)
private void PrintAligned(string cli, string s, string env, string desc, string def)
{
Console.CursorLeft = cliStart;
Console.Write(cli);
@@ -124,132 +123,8 @@ namespace ArgsUniform
Console.CursorLeft = envStart;
Console.Write(env);
Console.CursorLeft = descStart;
Console.Write(desc + Environment.NewLine);
}
private object GetDefaultValue(Type t)
{
if (t.IsValueType) return Activator.CreateInstance(t)!;
return null!;
}
private bool UniformAssign(T result, UniformAttribute attr, PropertyInfo uniformProperty)
{
if (AssignFromArgsIfAble(result, attr, uniformProperty)) return true;
if (AssignFromEnvVarIfAble(result, attr, uniformProperty)) return true;
if (AssignFromDefaultsIfAble(result, uniformProperty)) return true;
return false;
}
private bool AssignFromDefaultsIfAble(T result, PropertyInfo uniformProperty)
{
var currentValue = uniformProperty.GetValue(result);
var isEmptryString = (currentValue as string) == string.Empty;
if (currentValue != GetDefaultValue(uniformProperty.PropertyType) && !isEmptryString) return true;
if (defaultsProvider == null) return false;
var defaultProperty = defaultsProvider.GetType().GetProperties().SingleOrDefault(p => p.Name == uniformProperty.Name);
if (defaultProperty == null) return false;
var value = defaultProperty.GetValue(defaultsProvider);
if (value != null)
{
return Assign(result, uniformProperty, value);
}
return false;
}
private bool AssignFromEnvVarIfAble(T result, UniformAttribute attr, PropertyInfo uniformProperty)
{
var e = env.GetEnvVarOrDefault(attr.EnvVar, string.Empty);
if (!string.IsNullOrEmpty(e))
{
return Assign(result, uniformProperty, e);
}
return false;
}
private bool AssignFromArgsIfAble(T result, UniformAttribute attr, PropertyInfo uniformProperty)
{
var fromArg = GetFromArgs(attr.Arg);
if (fromArg != null)
{
return Assign(result, uniformProperty, fromArg);
}
var fromShort = GetFromArgs(attr.ArgShort);
if (fromShort != null)
{
return Assign(result, uniformProperty, fromShort);
}
return false;
}
private bool Assign(T result, PropertyInfo uniformProperty, object value)
{
if (uniformProperty.PropertyType == value.GetType())
{
uniformProperty.SetValue(result, value);
return true;
}
else
{
if (uniformProperty.PropertyType == typeof(string) || uniformProperty.PropertyType == typeof(int))
{
uniformProperty.SetValue(result, Convert.ChangeType(value, uniformProperty.PropertyType));
return true;
}
else
{
if (uniformProperty.PropertyType == typeof(int?)) return AssignOptionalInt(result, uniformProperty, value);
if (uniformProperty.PropertyType.IsEnum) return AssignEnum(result, uniformProperty, value);
if (uniformProperty.PropertyType == typeof(bool)) return AssignBool(result, uniformProperty, value);
throw new NotSupportedException();
}
}
}
private static bool AssignEnum(T result, PropertyInfo uniformProperty, object value)
{
var s = value.ToString();
if (Enum.TryParse(uniformProperty.PropertyType, s, out var e))
{
uniformProperty.SetValue(result, e);
return true;
}
return false;
}
private static bool AssignOptionalInt(T result, PropertyInfo uniformProperty, object value)
{
if (int.TryParse(value.ToString(), out int i))
{
uniformProperty.SetValue(result, i);
return true;
}
return false;
}
private static bool AssignBool(T result, PropertyInfo uniformProperty, object value)
{
var s = value.ToString();
if (s == "1" || (s != null && s.ToLowerInvariant() == "true"))
{
uniformProperty.SetValue(result, true);
}
return true;
}
private string? GetFromArgs(string key)
{
var argKey = $"--{key}=";
var arg = args.FirstOrDefault(a => a.StartsWith(argKey));
if (arg != null)
{
return arg.Substring(argKey.Length);
}
return null;
Console.Write(desc + " ");
Console.Write(def + Environment.NewLine);
}
}
}
+186
View File
@@ -0,0 +1,186 @@
using System.Globalization;
using System.Numerics;
using System.Reflection;
namespace ArgsUniform
{
public class Assigner<T>
{
private readonly IEnv.IEnv env;
private readonly string[] args;
private readonly object? defaultsProvider;
public Assigner(IEnv.IEnv env, string[] args, object? defaultsProvider)
{
this.env = env;
this.args = args;
this.defaultsProvider = defaultsProvider;
}
public bool UniformAssign(T result, UniformAttribute attr, PropertyInfo uniformProperty)
{
if (AssignFromArgsIfAble(result, attr, uniformProperty)) return true;
if (AssignFromEnvVarIfAble(result, attr, uniformProperty)) return true;
if (AssignFromDefaultsIfAble(result, uniformProperty)) return true;
return false;
}
public string DescribeDefaultFor(PropertyInfo property)
{
var obj = Activator.CreateInstance<T>();
var defaultValue = GetDefaultValue(obj, property);
if (defaultValue == null) return "";
if (defaultValue is string str)
{
return "\"" + str + "\"";
}
return defaultValue.ToString() ?? string.Empty;
}
private object? GetDefaultValue(T result, PropertyInfo uniformProperty)
{
// Get value from object's static initializer if it's there.
var currentValue = uniformProperty.GetValue(result);
if (currentValue != null) return currentValue;
// Get value from defaults-provider object if it's there.
if (defaultsProvider == null) return null;
var defaultProperty = defaultsProvider.GetType().GetProperties().SingleOrDefault(p => p.Name == uniformProperty.Name);
if (defaultProperty == null) return null;
return defaultProperty.GetValue(defaultsProvider);
}
private bool AssignFromDefaultsIfAble(T result, PropertyInfo uniformProperty)
{
var defaultValue = GetDefaultValue(result, uniformProperty);
var isEmptryString = (defaultValue as string) == string.Empty;
if (defaultValue != null && defaultValue != GetDefaultValueForType(uniformProperty.PropertyType) && !isEmptryString)
{
return Assign(result, uniformProperty, defaultValue);
}
return false;
}
private bool AssignFromEnvVarIfAble(T result, UniformAttribute attr, PropertyInfo uniformProperty)
{
var e = env.GetEnvVarOrDefault(attr.EnvVar, string.Empty);
if (!string.IsNullOrEmpty(e))
{
return Assign(result, uniformProperty, e);
}
return false;
}
private bool AssignFromArgsIfAble(T result, UniformAttribute attr, PropertyInfo uniformProperty)
{
var fromArg = GetFromArgs(attr.Arg);
if (fromArg != null)
{
return Assign(result, uniformProperty, fromArg);
}
var fromShort = GetFromArgs(attr.ArgShort);
if (fromShort != null)
{
return Assign(result, uniformProperty, fromShort);
}
return false;
}
private bool Assign(T result, PropertyInfo uniformProperty, object value)
{
if (uniformProperty.PropertyType == value.GetType())
{
uniformProperty.SetValue(result, value);
return true;
}
else
{
if (uniformProperty.PropertyType == typeof(string) || uniformProperty.PropertyType == typeof(int))
{
uniformProperty.SetValue(result, Convert.ChangeType(value, uniformProperty.PropertyType));
return true;
}
else
{
if (uniformProperty.PropertyType == typeof(int?)) return AssignOptionalInt(result, uniformProperty, value);
if (uniformProperty.PropertyType.IsEnum) return AssignEnum(result, uniformProperty, value);
if (uniformProperty.PropertyType == typeof(bool)) return AssignBool(result, uniformProperty, value);
if (uniformProperty.PropertyType == typeof(ulong)) return AssignUlong(result, uniformProperty, value);
if (uniformProperty.PropertyType == typeof(BigInteger)) return AssignBigInt(result, uniformProperty, value);
throw new NotSupportedException(
$"Unsupported property type '${uniformProperty.PropertyType}' " +
$"for property '${uniformProperty.Name}'.");
}
}
}
private static bool AssignEnum(T result, PropertyInfo uniformProperty, object value)
{
var s = value.ToString();
if (Enum.TryParse(uniformProperty.PropertyType, s, out var e))
{
uniformProperty.SetValue(result, e);
return true;
}
return false;
}
private static bool AssignOptionalInt(T result, PropertyInfo uniformProperty, object value)
{
if (int.TryParse(value.ToString(), CultureInfo.InvariantCulture, out int i))
{
uniformProperty.SetValue(result, i);
return true;
}
return false;
}
private bool AssignUlong(T? result, PropertyInfo uniformProperty, object value)
{
if (ulong.TryParse(value.ToString(), CultureInfo.InvariantCulture, out ulong i))
{
uniformProperty.SetValue(result, i);
return true;
}
return false;
}
private bool AssignBigInt(T result, PropertyInfo uniformProperty, object value)
{
if (BigInteger.TryParse(value.ToString(), CultureInfo.InvariantCulture, out BigInteger i))
{
uniformProperty.SetValue(result, i);
return true;
}
return false;
}
private static bool AssignBool(T result, PropertyInfo uniformProperty, object value)
{
var s = value.ToString();
if (s == "1" || (s != null && s.ToLowerInvariant() == "true"))
{
uniformProperty.SetValue(result, true);
}
return true;
}
private string? GetFromArgs(string key)
{
var argKey = $"--{key}=";
var arg = args.FirstOrDefault(a => a.StartsWith(argKey));
if (arg != null)
{
return arg.Substring(argKey.Length);
}
return null;
}
private static object GetDefaultValueForType(Type t)
{
if (t.IsValueType) return Activator.CreateInstance(t)!;
return null!;
}
}
}
+4 -4
View File
@@ -30,11 +30,11 @@ namespace Core
public IDownloadedLog DownloadLog(RunningContainer container, int? tailLines = null)
{
var workflow = entryPoint.Tools.CreateWorkflow();
var file = entryPoint.Tools.GetLog().CreateSubfile();
entryPoint.Tools.GetLog().Log($"Downloading container log for '{container.Name}' to file '{file.FullFilename}'...");
var logHandler = new LogDownloadHandler(container.Name, file);
var msg = $"Downloading container log for '{container.Name}'";
entryPoint.Tools.GetLog().Log(msg);
var logHandler = new WriteToFileLogHandler(entryPoint.Tools.GetLog(), msg);
workflow.DownloadContainerLog(container, logHandler, tailLines);
return logHandler.DownloadLog();
return new DownloadedLog(logHandler);
}
public string ExecuteContainerCommand(IHasContainer containerSource, string command, params string[] args)
+4 -3
View File
@@ -1,4 +1,5 @@
using Logging;
using KubernetesWorkflow;
using Logging;
namespace Core
{
@@ -14,9 +15,9 @@ namespace Core
{
private readonly LogFile logFile;
internal DownloadedLog(LogFile logFile)
internal DownloadedLog(WriteToFileLogHandler logHandler)
{
this.logFile = logFile;
logFile = logHandler.LogFile;
}
public void IterateLines(Action<string> action)
+7 -3
View File
@@ -38,10 +38,14 @@ namespace Core
return new CoreInterface(this);
}
public void Decommission(bool deleteKubernetesResources, bool deleteTrackedFiles)
/// <summary>
/// Deletes kubernetes and tracked file resources.
/// when `waitTillDone` is true, this function will block until resources are deleted.
/// </summary>
public void Decommission(bool deleteKubernetesResources, bool deleteTrackedFiles, bool waitTillDone)
{
manager.DecommissionPlugins(deleteKubernetesResources, deleteTrackedFiles);
Tools.Decommission(deleteKubernetesResources, deleteTrackedFiles);
manager.DecommissionPlugins(deleteKubernetesResources, deleteTrackedFiles, waitTillDone);
Tools.Decommission(deleteKubernetesResources, deleteTrackedFiles, waitTillDone);
}
internal T GetPlugin<T>() where T : IProjectPlugin
+10 -3
View File
@@ -7,6 +7,7 @@ namespace Core
{
T OnClient<T>(Func<HttpClient, T> action);
T OnClient<T>(Func<HttpClient, T> action, string description);
T OnClient<T>(Func<HttpClient, T> action, Retry retry);
IEndpoint CreateEndpoint(Address address, string baseUrl, string? logAlias = null);
}
@@ -35,13 +36,19 @@ namespace Core
}
public T OnClient<T>(Func<HttpClient, T> action, string description)
{
var retry = new Retry(description, timeSet.HttpRetryTimeout(), timeSet.HttpCallRetryDelay(), f => { });
return OnClient(action, retry);
}
public T OnClient<T>(Func<HttpClient, T> action, Retry retry)
{
var client = GetClient();
return LockRetry(() =>
{
return action(client);
}, description);
}, retry);
}
public IEndpoint CreateEndpoint(Address address, string baseUrl, string? logAlias = null)
@@ -54,11 +61,11 @@ namespace Core
return DebugStack.GetCallerName(skipFrames: 2);
}
private T LockRetry<T>(Func<T> operation, string description)
private T LockRetry<T>(Func<T> operation, Retry retry)
{
lock (httpLock)
{
return Time.Retry(operation, timeSet.HttpMaxNumberOfRetries(), timeSet.HttpCallRetryDelay(), description);
return retry.Run(operation);
}
}
-28
View File
@@ -1,28 +0,0 @@
using KubernetesWorkflow;
using Logging;
namespace Core
{
internal class LogDownloadHandler : LogHandler, ILogHandler
{
private readonly LogFile log;
internal LogDownloadHandler(string description, LogFile log)
{
this.log = log;
log.Write($"{description} -->> {log.FullFilename}");
log.WriteRaw(description);
}
internal IDownloadedLog DownloadLog()
{
return new DownloadedLog(log);
}
protected override void ProcessLine(string line)
{
log.WriteRaw(line);
}
}
}
+2 -2
View File
@@ -34,12 +34,12 @@
return metadata;
}
internal void DecommissionPlugins(bool deleteKubernetesResources, bool deleteTrackedFiles)
internal void DecommissionPlugins(bool deleteKubernetesResources, bool deleteTrackedFiles, bool waitTillDone)
{
foreach (var pair in pairs)
{
pair.Plugin.Decommission();
pair.Tools.Decommission(deleteKubernetesResources, deleteTrackedFiles);
pair.Tools.Decommission(deleteKubernetesResources, deleteTrackedFiles, waitTillDone);
}
}
+14 -7
View File
@@ -6,7 +6,13 @@ namespace Core
{
public interface IPluginTools : IWorkflowTool, ILogTool, IHttpFactoryTool, IFileTool
{
void Decommission(bool deleteKubernetesResources, bool deleteTrackedFiles);
ITimeSet TimeSet { get; }
/// <summary>
/// Deletes kubernetes and tracked file resources.
/// when `waitTillDone` is true, this function will block until resources are deleted.
/// </summary>
void Decommission(bool deleteKubernetesResources, bool deleteTrackedFiles, bool waitTillDone);
}
public interface IWorkflowTool
@@ -33,7 +39,6 @@ namespace Core
internal class PluginTools : IPluginTools
{
private readonly ITimeSet timeSet;
private readonly WorkflowCreator workflowCreator;
private readonly IFileManager fileManager;
private readonly LogPrefixer log;
@@ -42,10 +47,12 @@ namespace Core
{
this.log = new LogPrefixer(log);
this.workflowCreator = workflowCreator;
this.timeSet = timeSet;
TimeSet = timeSet;
fileManager = new FileManager(log, fileManagerRootFolder);
}
public ITimeSet TimeSet { get; }
public void ApplyLogPrefix(string prefix)
{
log.Prefix = prefix;
@@ -53,7 +60,7 @@ namespace Core
public IHttp CreateHttp(Action<HttpClient> onClientCreated)
{
return CreateHttp(onClientCreated, timeSet);
return CreateHttp(onClientCreated, TimeSet);
}
public IHttp CreateHttp(Action<HttpClient> onClientCreated, ITimeSet ts)
@@ -63,7 +70,7 @@ namespace Core
public IHttp CreateHttp()
{
return new Http(log, timeSet);
return new Http(log, TimeSet);
}
public IStartupWorkflow CreateWorkflow(string? namespaceOverride = null)
@@ -71,9 +78,9 @@ namespace Core
return workflowCreator.CreateWorkflow(namespaceOverride);
}
public void Decommission(bool deleteKubernetesResources, bool deleteTrackedFiles)
public void Decommission(bool deleteKubernetesResources, bool deleteTrackedFiles, bool waitTillDone)
{
if (deleteKubernetesResources) CreateWorkflow().DeleteNamespace();
if (deleteKubernetesResources) CreateWorkflow().DeleteNamespace(waitTillDone);
if (deleteTrackedFiles) fileManager.DeleteAllFiles();
}
+28 -7
View File
@@ -2,10 +2,31 @@
{
public interface ITimeSet
{
/// <summary>
/// Timeout for a single HTTP call.
/// </summary>
TimeSpan HttpCallTimeout();
int HttpMaxNumberOfRetries();
/// <summary>
/// Maximum total time to attempt to make a successful HTTP call to a service.
/// When HTTP calls time out during this timespan, retries will be made.
/// </summary>
TimeSpan HttpRetryTimeout();
/// <summary>
/// After a failed HTTP call, wait this long before trying again.
/// </summary>
TimeSpan HttpCallRetryDelay();
/// <summary>
/// After a failed K8s operation, wait this long before trying again.
/// </summary>
TimeSpan K8sOperationRetryDelay();
/// <summary>
/// Maximum total time to attempt to perform a successful k8s operation.
/// If k8s operations fail during this timespan, retries will be made.
/// </summary>
TimeSpan K8sOperationTimeout();
}
@@ -16,9 +37,9 @@
return TimeSpan.FromMinutes(3);
}
public int HttpMaxNumberOfRetries()
public TimeSpan HttpRetryTimeout()
{
return 3;
return TimeSpan.FromMinutes(10);
}
public TimeSpan HttpCallRetryDelay()
@@ -41,17 +62,17 @@
{
public TimeSpan HttpCallTimeout()
{
return TimeSpan.FromHours(2);
return TimeSpan.FromMinutes(30);
}
public int HttpMaxNumberOfRetries()
public TimeSpan HttpRetryTimeout()
{
return 1;
return TimeSpan.FromHours(2.2);
}
public TimeSpan HttpCallRetryDelay()
{
return TimeSpan.FromSeconds(2);
return TimeSpan.FromSeconds(20);
}
public TimeSpan K8sOperationRetryDelay()
+1 -1
View File
@@ -65,7 +65,7 @@ namespace FileUtils
if (readExpected == 0 && readActual == 0)
{
log.Log($"OK: '{Describe()}' is equal to '{actual.Describe()}'.");
log.Log($"OK: {Describe()} is equal to {actual.Describe()}.");
return;
}
@@ -1,41 +0,0 @@
using Utils;
namespace KubernetesWorkflow
{
public static class ByteSizeExtensions
{
public static string ToSuffixNotation(this ByteSize b)
{
long x = 1024;
var map = new Dictionary<long, string>
{
{ Pow(x, 4), "Ti" },
{ Pow(x, 3), "Gi" },
{ Pow(x, 2), "Mi" },
{ (x), "Ki" },
};
var bytes = b.SizeInBytes;
foreach (var pair in map)
{
if (bytes > pair.Key)
{
double bytesD = bytes;
double divD = pair.Key;
double numD = Math.Ceiling(bytesD / divD);
var v = Convert.ToInt64(numD);
return $"{v}{pair.Value}";
}
}
return $"{bytes}";
}
private static long Pow(long x, int v)
{
long result = 1;
for (var i = 0; i < v; i++) result *= x;
return result;
}
}
}
+9 -7
View File
@@ -11,7 +11,6 @@ namespace KubernetesWorkflow
private readonly string podName;
private readonly string recipeName;
private readonly string k8sNamespace;
private ILogHandler? logHandler;
private CancellationTokenSource cts;
private Task? worker;
private Exception? workerException;
@@ -27,11 +26,10 @@ namespace KubernetesWorkflow
cts = new CancellationTokenSource();
}
public void Start(ILogHandler logHandler)
public void Start()
{
if (worker != null) throw new InvalidOperationException();
this.logHandler = logHandler;
cts = new CancellationTokenSource();
worker = Task.Run(Worker);
}
@@ -50,7 +48,9 @@ namespace KubernetesWorkflow
public bool HasContainerCrashed()
{
using var client = new Kubernetes(config);
return HasContainerBeenRestarted(client);
var result = HasContainerBeenRestarted(client);
if (result) DownloadCrashedContainerLogs(client);
return result;
}
private void Worker()
@@ -83,14 +83,16 @@ namespace KubernetesWorkflow
private bool HasContainerBeenRestarted(Kubernetes client)
{
var podInfo = client.ReadNamespacedPod(podName, k8sNamespace);
return podInfo.Status.ContainerStatuses.Any(c => c.RestartCount > 0);
var result = podInfo.Status.ContainerStatuses.Any(c => c.RestartCount > 0);
if (result) log.Log("Pod crash detected for " + containerName);
return result;
}
private void DownloadCrashedContainerLogs(Kubernetes client)
{
log.Log("Pod crash detected for " + containerName);
using var stream = client.ReadNamespacedPodLog(podName, k8sNamespace, recipeName, previous: true);
logHandler!.Log(stream);
var handler = new WriteToFileLogHandler(log, "Crash detected for " + containerName);
handler.Log(stream);
}
}
}
@@ -16,6 +16,7 @@ namespace KubernetesWorkflow
{
var config = GetConfig();
UpdateHostAddress(config);
config.SkipTlsVerify = true; // Required for operation on Wings cluster.
return config;
}
+43 -11
View File
@@ -45,7 +45,7 @@ namespace KubernetesWorkflow
public void WaitUntilOnline(RunningContainer container)
{
WaitUntilDeploymentOnline(container.Recipe.Name);
WaitUntilDeploymentOnline(container);
}
public PodInfo GetPodInfo(RunningDeployment deployment)
@@ -64,14 +64,14 @@ namespace KubernetesWorkflow
if (waitTillStopped) WaitUntilPodsForDeploymentAreOffline(startResult.Deployment);
}
public void DownloadPodLog(RunningContainer container, ILogHandler logHandler, int? tailLines)
public void DownloadPodLog(RunningContainer container, ILogHandler logHandler, int? tailLines, bool? previous)
{
log.Debug();
var podName = GetPodName(container);
var recipeName = container.Recipe.Name;
using var stream = client.Run(c => c.ReadNamespacedPodLog(podName, K8sNamespace, recipeName, tailLines: tailLines));
using var stream = client.Run(c => c.ReadNamespacedPodLog(podName, K8sNamespace, recipeName, tailLines: tailLines, previous: previous));
logHandler.Log(stream);
}
@@ -115,7 +115,7 @@ namespace KubernetesWorkflow
});
}
public void DeleteAllNamespacesStartingWith(string prefix)
public void DeleteAllNamespacesStartingWith(string prefix, bool wait)
{
log.Debug();
@@ -124,25 +124,28 @@ namespace KubernetesWorkflow
foreach (var ns in namespaces)
{
DeleteNamespace(ns);
DeleteNamespace(ns, wait);
}
}
public void DeleteNamespace()
public void DeleteNamespace(bool wait)
{
log.Debug();
if (IsNamespaceOnline(K8sNamespace))
{
client.Run(c => c.DeleteNamespace(K8sNamespace, null, null, gracePeriodSeconds: 0));
if (wait) WaitUntilNamespaceDeleted(K8sNamespace);
}
}
public void DeleteNamespace(string ns)
public void DeleteNamespace(string ns, bool wait)
{
log.Debug();
if (IsNamespaceOnline(ns))
{
client.Run(c => c.DeleteNamespace(ns, null, null, gracePeriodSeconds: 0));
if (wait) WaitUntilNamespaceDeleted(ns);
}
}
@@ -532,7 +535,7 @@ namespace KubernetesWorkflow
}
if (set.Memory.SizeInBytes != 0)
{
result.Add("memory", new ResourceQuantity(set.Memory.ToSuffixNotation()));
result.Add("memory", new ResourceQuantity(set.Memory.SizeInBytes.ToString()));
}
return result;
}
@@ -712,7 +715,7 @@ namespace KubernetesWorkflow
{
return Time.Retry(() => GetPodForDeplomentInternal(deployment),
// We will wait up to 1 minute, k8s might be moving pods around.
maxRetries: 6,
maxTimeout: TimeSpan.FromMinutes(1),
retryTime: TimeSpan.FromSeconds(10),
description: "Find pod by label for deployment.");
}
@@ -871,15 +874,44 @@ namespace KubernetesWorkflow
WaitUntil(() => IsNamespaceOnline(K8sNamespace), nameof(WaitUntilNamespaceCreated));
}
private void WaitUntilDeploymentOnline(string deploymentName)
private void WaitUntilNamespaceDeleted(string @namespace)
{
WaitUntil(() => !IsNamespaceOnline(@namespace), nameof(WaitUntilNamespaceDeleted));
}
private void WaitUntilDeploymentOnline(RunningContainer container)
{
WaitUntil(() =>
{
var deployment = client.Run(c => c.ReadNamespacedDeployment(deploymentName, K8sNamespace));
CheckForCrash(container);
var deployment = client.Run(c => c.ReadNamespacedDeployment(container.Recipe.Name, K8sNamespace));
return deployment?.Status.AvailableReplicas != null && deployment.Status.AvailableReplicas > 0;
}, nameof(WaitUntilDeploymentOnline));
}
private void CheckForCrash(RunningContainer container)
{
var deploymentName = container.Recipe.Name;
var podName = GetPodName(container);
var podInfo = client.Run(c => c.ReadNamespacedPod(podName, K8sNamespace));
if (podInfo == null) return;
if (podInfo.Status == null) return;
if (podInfo.Status.ContainerStatuses == null) return;
var result = podInfo.Status.ContainerStatuses.Any(c => c.RestartCount > 0);
if (result)
{
var msg = $"Pod crash detected for deployment {deploymentName} (pod:{podName})";
log.Error(msg);
DownloadPodLog(container, new WriteToFileLogHandler(log, msg), tailLines: null, previous: true);
throw new Exception(msg);
}
}
private void WaitUntilDeploymentOffline(string deploymentName)
{
WaitUntil(() =>
+24 -1
View File
@@ -1,4 +1,6 @@
namespace KubernetesWorkflow
using Logging;
namespace KubernetesWorkflow
{
public interface ILogHandler
{
@@ -20,4 +22,25 @@
protected abstract void ProcessLine(string line);
}
public class WriteToFileLogHandler : LogHandler, ILogHandler
{
public WriteToFileLogHandler(ILog sourceLog, string description)
{
LogFile = sourceLog.CreateSubfile();
var msg = $"{description} -->> {LogFile.FullFilename}";
sourceLog.Log(msg);
LogFile.Write(msg);
LogFile.WriteRaw(description);
}
public LogFile LogFile { get; }
protected override void ProcessLine(string line)
{
LogFile.WriteRaw(line);
}
}
}
@@ -105,7 +105,7 @@ namespace KubernetesWorkflow.Recipe
protected void AddVolume(string name, string mountPath, string? subPath = null, string? secret = null, string? hostPath = null)
{
var size = 10.MB().ToSuffixNotation();
var size = 10.MB().SizeInBytes.ToString();
volumeMounts.Add(new VolumeMount(name, mountPath, subPath, size, secret, hostPath));
}
@@ -114,7 +114,7 @@ namespace KubernetesWorkflow.Recipe
volumeMounts.Add(new VolumeMount(
$"autovolume-{Guid.NewGuid().ToString().ToLowerInvariant()}",
mountPath,
resourceQuantity: volumeSize.ToSuffixNotation()));
resourceQuantity: volumeSize.SizeInBytes.ToString()));
}
protected void Additional(object userData)
+1 -4
View File
@@ -1,8 +1,5 @@
using k8s;
using k8s.Models;
using KubernetesWorkflow.Recipe;
using KubernetesWorkflow.Recipe;
using KubernetesWorkflow.Types;
using Newtonsoft.Json;
namespace KubernetesWorkflow
{
@@ -15,10 +15,10 @@ namespace KubernetesWorkflow
PodInfo GetPodInfo(RunningPod pod);
CrashWatcher CreateCrashWatcher(RunningContainer container);
void Stop(RunningPod pod, bool waitTillStopped);
void DownloadContainerLog(RunningContainer container, ILogHandler logHandler, int? tailLines = null);
void DownloadContainerLog(RunningContainer container, ILogHandler logHandler, int? tailLines = null, bool? previous = null);
string ExecuteCommand(RunningContainer container, string command, params string[] args);
void DeleteNamespace();
void DeleteNamespacesStartingWith(string namespacePrefix);
void DeleteNamespace(bool wait);
void DeleteNamespacesStartingWith(string namespacePrefix, bool wait);
}
public class StartupWorkflow : IStartupWorkflow
@@ -106,11 +106,11 @@ namespace KubernetesWorkflow
});
}
public void DownloadContainerLog(RunningContainer container, ILogHandler logHandler, int? tailLines = null)
public void DownloadContainerLog(RunningContainer container, ILogHandler logHandler, int? tailLines = null, bool? previous = null)
{
K8s(controller =>
{
controller.DownloadPodLog(container, logHandler, tailLines);
controller.DownloadPodLog(container, logHandler, tailLines, previous);
});
}
@@ -122,19 +122,19 @@ namespace KubernetesWorkflow
});
}
public void DeleteNamespace()
public void DeleteNamespace(bool wait)
{
K8s(controller =>
{
controller.DeleteNamespace();
controller.DeleteNamespace(wait);
});
}
public void DeleteNamespacesStartingWith(string namespacePrefix)
public void DeleteNamespacesStartingWith(string namespacePrefix, bool wait)
{
K8s(controller =>
{
controller.DeleteAllNamespacesStartingWith(namespacePrefix);
controller.DeleteAllNamespacesStartingWith(namespacePrefix, wait);
});
}
@@ -20,7 +20,7 @@ namespace KubernetesWorkflow.Types
[JsonIgnore]
public string Name
{
get { return $"{Containers.Length}x '{Containers.First().Name}'"; }
get { return $"'{string.Join("&", Containers.Select(c => c.Name).ToArray())}'"; }
}
public string Describe()
@@ -20,6 +20,7 @@ namespace NethereumWorkflow.BlockUtils
public BlockTimeEntry Get(ulong blockNumber)
{
bounds.Initialize();
var b = cache.Get(blockNumber);
if (b != null) return b;
return GetBlock(blockNumber);
@@ -45,7 +46,7 @@ namespace NethereumWorkflow.BlockUtils
private ulong Log(Func<ulong> operation)
{
var sw = Stopwatch.Begin(log, nameof(BlockTimeFinder));
var sw = Stopwatch.Begin(log, nameof(BlockTimeFinder), true);
var result = operation();
sw.End($"(Bounds: [{bounds.Genesis.BlockNumber}-{bounds.Current.BlockNumber}] Cache: {cache.Size})");
+131
View File
@@ -0,0 +1,131 @@
namespace Utils
{
public class Retry
{
private readonly string description;
private readonly TimeSpan maxTimeout;
private readonly TimeSpan sleepAfterFail;
private readonly Action<Failure> onFail;
public Retry(string description, TimeSpan maxTimeout, TimeSpan sleepAfterFail, Action<Failure> onFail)
{
this.description = description;
this.maxTimeout = maxTimeout;
this.sleepAfterFail = sleepAfterFail;
this.onFail = onFail;
}
public void Run(Action task)
{
var run = new RetryRun(description, task, maxTimeout, sleepAfterFail, onFail);
run.Run();
}
public T Run<T>(Func<T> task)
{
T? result = default;
var run = new RetryRun(description, () =>
{
result = task();
}, maxTimeout, sleepAfterFail, onFail);
run.Run();
return result!;
}
private class RetryRun
{
private readonly string description;
private readonly Action task;
private readonly TimeSpan maxTimeout;
private readonly TimeSpan sleepAfterFail;
private readonly Action<Failure> onFail;
private readonly DateTime start = DateTime.UtcNow;
private readonly List<Failure> failures = new List<Failure>();
private int tryNumber;
private DateTime tryStart;
public RetryRun(string description, Action task, TimeSpan maxTimeout, TimeSpan sleepAfterFail, Action<Failure> onFail)
{
this.description = description;
this.task = task;
this.maxTimeout = maxTimeout;
this.sleepAfterFail = sleepAfterFail;
this.onFail = onFail;
tryNumber = 0;
tryStart = DateTime.UtcNow;
}
public void Run()
{
while (true)
{
CheckMaximums();
tryNumber++;
tryStart = DateTime.UtcNow;
try
{
task();
return;
}
catch (Exception ex)
{
var failure = CaptureFailure(ex);
onFail(failure);
Time.Sleep(sleepAfterFail);
}
}
}
private Failure CaptureFailure(Exception ex)
{
var f = new Failure(ex, DateTime.UtcNow - tryStart, tryNumber);
failures.Add(f);
return f;
}
private void CheckMaximums()
{
if (Duration() > maxTimeout) Fail();
}
private void Fail()
{
throw new TimeoutException($"Retry '{description}' timed out after {tryNumber} tries over {Time.FormatDuration(Duration())}: {GetFailureReport}",
new AggregateException(failures.Select(f => f.Exception)));
}
private string GetFailureReport()
{
return Environment.NewLine + string.Join(Environment.NewLine, failures.Select(f => f.Describe()));
}
private TimeSpan Duration()
{
return DateTime.UtcNow - start;
}
}
}
public class Failure
{
public Failure(Exception exception, TimeSpan duration, int tryNumber)
{
Exception = exception;
Duration = duration;
TryNumber = tryNumber;
}
public Exception Exception { get; }
public TimeSpan Duration { get; }
public int TryNumber { get; }
public string Describe()
{
return $"Try {TryNumber} failed after {Time.FormatDuration(Duration)} with exception '{Exception}'";
}
}
}
+26 -53
View File
@@ -18,6 +18,12 @@
task.Wait();
}
public static string FormatDuration(TimeSpan? d)
{
if (d == null) return "[NULL]";
return FormatDuration(d.Value);
}
public static string FormatDuration(TimeSpan d)
{
var result = "";
@@ -83,77 +89,44 @@
public static void Retry(Action action, string description)
{
Retry(action, 1, description);
Retry(action, TimeSpan.FromSeconds(30), description);
}
public static T Retry<T>(Func<T> action, string description)
{
return Retry(action, 1, description);
return Retry(action, TimeSpan.FromSeconds(30), description);
}
public static void Retry(Action action, int maxRetries, string description)
public static void Retry(Action action, TimeSpan maxTimeout, string description)
{
Retry(action, maxRetries, TimeSpan.FromSeconds(5), description);
Retry(action, maxTimeout, TimeSpan.FromSeconds(5), description);
}
public static T Retry<T>(Func<T> action, int maxRetries, string description)
public static T Retry<T>(Func<T> action, TimeSpan maxTimeout, string description)
{
return Retry(action, maxRetries, TimeSpan.FromSeconds(5), description);
return Retry(action, maxTimeout, TimeSpan.FromSeconds(5), description);
}
public static void Retry(Action action, int maxRetries, TimeSpan retryTime, string description)
public static void Retry(Action action, TimeSpan maxTimeout, TimeSpan retryTime, string description)
{
var start = DateTime.UtcNow;
var retries = 0;
var exceptions = new List<Exception>();
while (true)
{
if (retries > maxRetries)
{
var duration = DateTime.UtcNow - start;
throw new TimeoutException($"Retry '{description}' timed out after {maxRetries} tries over {Time.FormatDuration(duration)}.", new AggregateException(exceptions));
}
try
{
action();
return;
}
catch (Exception ex)
{
exceptions.Add(ex);
retries++;
}
Sleep(retryTime);
}
Retry(action, maxTimeout, retryTime, description, f => { });
}
public static T Retry<T>(Func<T> action, int maxRetries, TimeSpan retryTime, string description)
public static T Retry<T>(Func<T> action, TimeSpan maxTimeout, TimeSpan retryTime, string description)
{
var start = DateTime.UtcNow;
var retries = 0;
var exceptions = new List<Exception>();
while (true)
{
if (retries > maxRetries)
{
var duration = DateTime.UtcNow - start;
throw new TimeoutException($"Retry '{description}' timed out after {maxRetries} tries over {Time.FormatDuration(duration)}.", new AggregateException(exceptions));
}
return Retry(action, maxTimeout, retryTime, description, f => { });
}
try
{
return action();
}
catch (Exception ex)
{
exceptions.Add(ex);
retries++;
}
public static void Retry(Action action, TimeSpan maxTimeout, TimeSpan retryTime, string description, Action<Failure> onFail)
{
var r = new Retry(description, maxTimeout, retryTime, onFail);
r.Run(action);
}
Sleep(retryTime);
}
public static T Retry<T>(Func<T> action, TimeSpan maxTimeout, TimeSpan retryTime, string description, Action<Failure> onFail)
{
var r = new Retry(description, maxTimeout, retryTime, onFail);
return r.Run(action);
}
}
}
@@ -67,7 +67,7 @@ namespace CodexContractsPlugin
public string MintTestTokens(EthAddress ethAddress, TestToken testTokens)
{
return StartInteraction().MintTestTokens(ethAddress, testTokens.Amount, Deployment.TokenAddress);
return StartInteraction().MintTestTokens(ethAddress, testTokens.TstWei, Deployment.TokenAddress);
}
public TestToken GetTestTokenBalance(IHasEthAddress owner)
@@ -78,7 +78,7 @@ namespace CodexContractsPlugin
public TestToken GetTestTokenBalance(EthAddress ethAddress)
{
var balance = StartInteraction().GetBalance(Deployment.TokenAddress, ethAddress.Address);
return balance.TestTokens();
return balance.TstWei();
}
public Request[] GetStorageRequests(BlockInterval blockRange)
@@ -44,7 +44,7 @@ namespace CodexContractsPlugin
}
}
public string MintTestTokens(EthAddress address, decimal amount, string tokenAddress)
public string MintTestTokens(EthAddress address, BigInteger amount, string tokenAddress)
{
log.Debug($"{amount} -> {address} (token: {tokenAddress})");
return MintTokens(address.Address, amount, tokenAddress);
@@ -85,7 +85,7 @@ namespace CodexContractsPlugin
}
}
private string MintTokens(string account, decimal amount, string tokenAddress)
private string MintTokens(string account, BigInteger amount, string tokenAddress)
{
log.Debug($"({tokenAddress}) {amount} --> {account}");
if (string.IsNullOrEmpty(account)) throw new ArgumentException("Invalid arguments for MintTestTokens");
@@ -93,7 +93,7 @@ namespace CodexContractsPlugin
var function = new MintTokensFunction
{
Holder = account,
Amount = amount.ToBig()
Amount = amount
};
return gethNode.SendTransaction(tokenAddress, function);
File diff suppressed because one or more lines are too long
@@ -1 +1,14 @@
This code was generated using the Nethereum code generator, here: http://playground.nethereum.com
1. Go to site -> Abi Code Gen.
1. Contract name = "Marketplace".
1. In container, get "/hardhat/artifacts/contracts/Marketplace.sol/Marketplace.json".
1. Save only ABI section as new JSON. (top-level is a json array.)
1. From original JSON get byte code.
1. Put ABI JSON and byte code into site.
1. Generate.
1. From site generated code, copy `public partial class MarketplaceDeployment` and everything after it. (be considerate of namespace brackets!)
1. In Marketplace/Marketplace.cs, replace content of 'namespace CodexContractsPlugin.Marketplace'.
@@ -1,45 +1,102 @@
namespace CodexContractsPlugin
using System.Numerics;
namespace CodexContractsPlugin
{
public class TestToken : IComparable<TestToken>
{
public TestToken(decimal amount)
public static BigInteger WeiFactor = new BigInteger(1000000000000000000);
public TestToken(BigInteger tstWei)
{
Amount = amount;
TstWei = tstWei;
Tst = tstWei / WeiFactor;
}
public decimal Amount { get; }
public BigInteger TstWei { get; }
public BigInteger Tst { get; }
public int CompareTo(TestToken? other)
{
return Amount.CompareTo(other!.Amount);
return TstWei.CompareTo(other!.TstWei);
}
public override bool Equals(object? obj)
{
return obj is TestToken token && Amount == token.Amount;
return obj is TestToken token && TstWei == token.TstWei;
}
public override int GetHashCode()
{
return HashCode.Combine(Amount);
return HashCode.Combine(TstWei);
}
public override string ToString()
{
return $"{Amount} TestTokens";
var weiOnly = TstWei % WeiFactor;
var tokens = new List<string>();
if (Tst > 0) tokens.Add($"{Tst} TST");
if (weiOnly > 0) tokens.Add($"{weiOnly} TSTWEI");
return string.Join(" + ", tokens);
}
public static TestToken operator +(TestToken a, TestToken b)
{
return new TestToken(a.TstWei + b.TstWei);
}
public static bool operator <(TestToken a, TestToken b)
{
return a.TstWei < b.TstWei;
}
public static bool operator >(TestToken a, TestToken b)
{
return a.TstWei > b.TstWei;
}
public static bool operator ==(TestToken a, TestToken b)
{
return a.TstWei == b.TstWei;
}
public static bool operator !=(TestToken a, TestToken b)
{
return a.TstWei != b.TstWei;
}
}
public static class TokensIntExtensions
public static class TestTokensExtensions
{
public static TestToken TestTokens(this int i)
public static TestToken TstWei(this int i)
{
return TestTokens(Convert.ToDecimal(i));
return TstWei(Convert.ToDecimal(i));
}
public static TestToken TestTokens(this decimal i)
public static TestToken TstWei(this decimal i)
{
return new TestToken(new BigInteger(i));
}
public static TestToken TstWei(this BigInteger i)
{
return new TestToken(i);
}
public static TestToken Tst(this int i)
{
return Tst(Convert.ToDecimal(i));
}
public static TestToken Tst(this decimal i)
{
return new TestToken(new BigInteger(i) * TestToken.WeiFactor);
}
public static TestToken Tst(this BigInteger i)
{
return new TestToken(i * TestToken.WeiFactor);
}
}
}
+1 -1
View File
@@ -9,7 +9,7 @@ namespace CodexPlugin
public class ApiChecker
{
// <INSERT-OPENAPI-YAML-HASH>
private const string OpenApiYamlHash = "5A-B0-2A-AC-42-B1-A2-49-6F-9D-4E-D8-56-40-10-A6-67-F4-0D-2A-9F-E0-84-5C-EB-B8-2D-4F-D8-56-79-6C";
private const string OpenApiYamlHash = "67-76-AB-FC-54-4F-EB-81-F5-E4-F8-27-DF-82-92-41-63-A5-EA-1B-17-14-0C-BE-20-9C-B3-DF-CE-E4-AA-38";
private const string OpenApiFilePath = "/codex/openapi.yaml";
private const string DisableEnvironmentVariable = "CODEXPLUGIN_DISABLE_APICHECK";
+150 -48
View File
@@ -2,25 +2,26 @@
using Core;
using KubernetesWorkflow;
using KubernetesWorkflow.Types;
using Logging;
using Newtonsoft.Json;
using Utils;
namespace CodexPlugin
{
public class CodexAccess : ILogHandler
public class CodexAccess
{
private readonly ILog log;
private readonly IPluginTools tools;
private readonly Mapper mapper = new Mapper();
private bool hasContainerCrashed;
public CodexAccess(IPluginTools tools, RunningPod container, CrashWatcher crashWatcher)
{
this.tools = tools;
log = tools.GetLog();
Container = container;
CrashWatcher = crashWatcher;
hasContainerCrashed = false;
CrashWatcher.Start(this);
CrashWatcher.Start();
}
public RunningPod Container { get; }
@@ -34,20 +35,23 @@ namespace CodexPlugin
public DebugPeer GetDebugPeer(string peerId)
{
// Cannot use openAPI: debug/peer endpoint is not specified there.
var endpoint = GetEndpoint();
var str = endpoint.HttpGetString($"debug/peer/{peerId}");
if (str.ToLowerInvariant() == "unable to find peer!")
return CrashCheck(() =>
{
return new DebugPeer
{
IsPeerFound = false
};
}
var endpoint = GetEndpoint();
var str = endpoint.HttpGetString($"debug/peer/{peerId}");
var result = endpoint.Deserialize<DebugPeer>(str);
result.IsPeerFound = true;
return result;
if (str.ToLowerInvariant() == "unable to find peer!")
{
return new DebugPeer
{
IsPeerFound = false
};
}
var result = endpoint.Deserialize<DebugPeer>(str);
result.IsPeerFound = true;
return result;
});
}
public void ConnectToPeer(string peerId, string[] peerMultiAddresses)
@@ -59,14 +63,19 @@ namespace CodexPlugin
});
}
public string UploadFile(FileStream fileStream)
public string UploadFile(FileStream fileStream, Action<Failure> onFailure)
{
return OnCodex(api => api.UploadAsync(fileStream));
return OnCodex(
api => api.UploadAsync(fileStream),
CreateRetryConfig(nameof(UploadFile), onFailure));
}
public Stream DownloadFile(string contentId)
public Stream DownloadFile(string contentId, Action<Failure> onFailure)
{
var fileResponse = OnCodex(api => api.DownloadNetworkAsync(contentId));
var fileResponse = OnCodex(
api => api.DownloadNetworkAsync(contentId),
CreateRetryConfig(nameof(DownloadFile), onFailure));
if (fileResponse.StatusCode != 200) throw new Exception("Download failed with StatusCode: " + fileResponse.StatusCode);
return fileResponse.Stream;
}
@@ -89,15 +98,24 @@ namespace CodexPlugin
return OnCodex<string>(api => api.CreateStorageRequestAsync(request.ContentId.Id, body));
}
public CodexSpace Space()
{
var space = OnCodex<Space>(api => api.SpaceAsync());
return mapper.Map(space);
}
public StoragePurchase GetPurchaseStatus(string purchaseId)
{
var endpoint = GetEndpoint();
return Time.Retry(() =>
return CrashCheck(() =>
{
var str = endpoint.HttpGetString($"storage/purchases/{purchaseId}");
if (string.IsNullOrEmpty(str)) throw new Exception("Empty response.");
return JsonConvert.DeserializeObject<StoragePurchase>(str)!;
}, nameof(GetPurchaseStatus));
var endpoint = GetEndpoint();
return Time.Retry(() =>
{
var str = endpoint.HttpGetString($"storage/purchases/{purchaseId}");
if (string.IsNullOrEmpty(str)) throw new Exception("Empty response.");
return JsonConvert.DeserializeObject<StoragePurchase>(str)!;
}, nameof(GetPurchaseStatus));
});
// TODO: current getpurchase api does not line up with its openapi spec.
// return mapper.Map(OnCodex(api => api.GetPurchaseAsync(purchaseId)));
@@ -114,19 +132,67 @@ namespace CodexPlugin
return workflow.GetPodInfo(Container);
}
public void LogDiskSpace(string msg)
{
try
{
var diskInfo = tools.CreateWorkflow().ExecuteCommand(Container.Containers.Single(), "df", "--sync");
Log($"{msg} - Disk info: {diskInfo}");
}
catch (Exception e)
{
Log("Failed to get disk info: " + e);
}
}
public void DeleteRepoFolder()
{
try
{
var containerNumber = Container.Containers.First().Recipe.Number;
var dataDir = $"datadir{containerNumber}";
var workflow = tools.CreateWorkflow();
workflow.ExecuteCommand(Container.Containers.First(), "rm", "-Rfv", $"/codex/{dataDir}/repo");
Log("Deleted repo folder.");
}
catch (Exception e)
{
Log("Unable to delete repo folder: " + e);
}
}
private T OnCodex<T>(Func<CodexApi, Task<T>> action)
{
var address = GetAddress();
var result = tools.CreateHttp(CheckContainerCrashed)
.OnClient(client =>
{
var api = new CodexApi(client);
api.BaseUrl = $"{address.Host}:{address.Port}/api/codex/v1";
return Time.Wait(action(api));
});
var result = tools.CreateHttp(CheckContainerCrashed).OnClient(client => CallCodex(client, action));
return result;
}
private T OnCodex<T>(Func<CodexApi, Task<T>> action, Retry retry)
{
var result = tools.CreateHttp(CheckContainerCrashed).OnClient(client => CallCodex(client, action), retry);
return result;
}
private T CallCodex<T>(HttpClient client, Func<CodexApi, Task<T>> action)
{
var address = GetAddress();
var api = new CodexApi(client);
api.BaseUrl = $"{address.Host}:{address.Port}/api/codex/v1";
return CrashCheck(() => Time.Wait(action(api)));
}
private T CrashCheck<T>(Func<T> action)
{
try
{
return action();
}
finally
{
CrashWatcher.HasContainerCrashed();
}
}
private IEndpoint GetEndpoint()
{
return tools
@@ -136,31 +202,67 @@ namespace CodexPlugin
private Address GetAddress()
{
return Container.Containers.Single().GetAddress(tools.GetLog(), CodexContainerRecipe.ApiPortTag);
return Container.Containers.Single().GetAddress(log, CodexContainerRecipe.ApiPortTag);
}
private void CheckContainerCrashed(HttpClient client)
{
if (hasContainerCrashed) throw new Exception("Container has crashed.");
if (CrashWatcher.HasContainerCrashed()) throw new Exception($"Container {GetName()} has crashed.");
}
public void Log(Stream crashLog)
private Retry CreateRetryConfig(string description, Action<Failure> onFailure)
{
var log = tools.GetLog();
var file = log.CreateSubfile();
log.Log($"Container {Container.Name} has crashed. Downloading crash log to '{file.FullFilename}'...");
file.Write($"Container Crash Log for {Container.Name}.");
var timeSet = tools.TimeSet;
using var reader = new StreamReader(crashLog);
var line = reader.ReadLine();
while (line != null)
return new Retry(description, timeSet.HttpRetryTimeout(), timeSet.HttpCallRetryDelay(), failure =>
{
file.Write(line);
line = reader.ReadLine();
onFailure(failure);
Investigate(failure, timeSet);
});
}
private void Investigate(Failure failure, ITimeSet timeSet)
{
Log($"Retry {failure.TryNumber} took {Time.FormatDuration(failure.Duration)} and failed with '{failure.Exception}'. " +
$"(HTTP timeout = {Time.FormatDuration(timeSet.HttpCallTimeout())}) " +
$"Checking if node responds to debug/info...");
LogDiskSpace("After retry failure");
try
{
var debugInfo = GetDebugInfo();
if (string.IsNullOrEmpty(debugInfo.Spr))
{
Log("Did not get value debug/info response.");
Throw(failure);
}
else
{
Log("Got valid response from debug/info.");
}
}
catch (Exception ex)
{
Log("Got exception from debug/info call: " + ex);
Throw(failure);
}
log.Log("Crash log successfully downloaded.");
hasContainerCrashed = true;
if (failure.Duration < timeSet.HttpCallTimeout())
{
Log("Retry failed within HTTP timeout duration.");
Throw(failure);
}
}
private void Throw(Failure failure)
{
throw failure.Exception;
}
private void Log(string msg)
{
log.Log($"{GetName()} {msg}");
}
}
}
@@ -7,7 +7,8 @@ namespace CodexPlugin
{
public class CodexContainerRecipe : ContainerRecipeFactory
{
private const string DefaultDockerImage = "codexstorage/nim-codex:sha-1524803-dist-tests";
private const string DefaultDockerImage = "codexstorage/nim-codex:sha-b89493e-dist-tests";
public const string ApiPortTag = "codex_api_port";
public const string ListenPortTag = "codex_listen_port";
public const string MetricsPortTag = "codex_metrics_port";
@@ -118,7 +119,7 @@ namespace CodexPlugin
}
}
if(!string.IsNullOrEmpty(config.NameOverride))
if (!string.IsNullOrEmpty(config.NameOverride))
{
AddEnvVar("CODEX_NODENAME", config.NameOverride);
}
@@ -158,7 +159,7 @@ namespace CodexPlugin
private ByteSize GetVolumeCapacity(CodexStartupConfig config)
{
if (config.StorageQuota != null) return config.StorageQuota;
if (config.StorageQuota != null) return config.StorageQuota.Multiply(1.2);
// Default Codex quota: 8 Gb, using +20% to be safe.
return 8.GB().Multiply(1.2);
}
+105
View File
@@ -0,0 +1,105 @@
using System.Globalization;
namespace CodexPlugin
{
public class CodexLogLine
{
public static CodexLogLine? Parse(string line)
{
try
{
if (string.IsNullOrEmpty(line) ||
line.Length < 34 ||
line[3] != ' ' ||
line[33] != ' ') return null;
line = line.Replace(Environment.NewLine, string.Empty);
var level = line.Substring(0, 3);
var dtLine = line.Substring(4, 23);
var firstEqualSign = line.IndexOf('=');
var msgStart = 34;
var msgEnd = line.Substring(0, firstEqualSign).LastIndexOf(' ');
var msg = line.Substring(msgStart, msgEnd - msgStart).Trim();
var attrsLine = line.Substring(msgEnd);
var attrs = SplitAttrs(attrsLine);
var format = "yyyy-MM-dd HH:mm:ss.fff";
var dt = DateTime.ParseExact(dtLine, format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal).ToUniversalTime();
return new CodexLogLine()
{
LogLevel = level,
TimestampUtc = dt,
Message = msg,
Attributes = attrs
};
}
catch
{
return null;
}
}
public string LogLevel { get; set; } = string.Empty;
public DateTime TimestampUtc { get; set; }
public string Message { get; set; } = string.Empty;
public Dictionary<string, string> Attributes { get; private set; } = new Dictionary<string, string>();
/// <summary>
/// After too much time spent cursing at regexes, here's what I got:
/// Parses input string into 'key=value' pair, considerate of quoted (") values.
/// </summary>
private static Dictionary<string, string> SplitAttrs(string input)
{
input += " ";
var result = new Dictionary<string, string>();
var key = string.Empty;
var value = string.Empty;
var mode = 1;
var inQuote = false;
foreach (var c in input)
{
if (mode == 1)
{
if (c == '=') mode = 2;
else if (c == ' ')
{
if (string.IsNullOrEmpty(key)) continue;
else
{
result.Add(key, string.Empty);
key = string.Empty;
value = string.Empty;
}
}
else key += c;
}
else if (mode == 2)
{
if (c == ' ' && !inQuote)
{
result.Add(key, value);
key = string.Empty;
value = string.Empty;
mode = 1;
}
else if (c == '\"')
{
inQuote = !inQuote;
}
else
{
value += c;
}
}
}
return result;
}
}
}
+48 -10
View File
@@ -15,14 +15,24 @@ namespace CodexPlugin
DebugInfo GetDebugInfo();
DebugPeer GetDebugPeer(string peerId);
ContentId UploadFile(TrackedFile file);
ContentId UploadFile(TrackedFile file, Action<Failure> onFailure);
TrackedFile? DownloadContent(ContentId contentId, string fileLabel = "");
TrackedFile? DownloadContent(ContentId contentId, Action<Failure> onFailure, string fileLabel = "");
LocalDatasetList LocalFiles();
CodexSpace Space();
void ConnectToPeer(ICodexNode node);
DebugInfoVersion Version { get; }
IMarketplaceAccess Marketplace { get; }
CrashWatcher CrashWatcher { get; }
PodInfo GetPodInfo();
ITransferSpeeds TransferSpeeds { get; }
/// <summary>
/// Warning! The node is not usable after this.
/// TODO: Replace with delete-blocks debug call once available in Codex.
/// </summary>
void DeleteRepoFolder();
void Stop(bool waitTillStopped);
}
@@ -80,7 +90,7 @@ namespace CodexPlugin
{
var debugInfo = CodexAccess.GetDebugInfo();
var known = string.Join(",", debugInfo.Table.Nodes.Select(n => n.PeerId));
Log($"Got DebugInfo with id: '{debugInfo.Id}'. This node knows: {known}");
Log($"Got DebugInfo with id: {debugInfo.Id}. This node knows: [{known}]");
return debugInfo;
}
@@ -91,13 +101,20 @@ namespace CodexPlugin
public ContentId UploadFile(TrackedFile file)
{
return UploadFile(file, DoNothing);
}
public ContentId UploadFile(TrackedFile file, Action<Failure> onFailure)
{
CodexAccess.LogDiskSpace("Before upload");
using var fileStream = File.OpenRead(file.Filename);
var logMessage = $"Uploading file {file.Describe()}...";
Log(logMessage);
var measurement = Stopwatch.Measure(tools.GetLog(), logMessage, () =>
{
return CodexAccess.UploadFile(fileStream);
return CodexAccess.UploadFile(fileStream, onFailure);
});
var response = measurement.Value;
@@ -107,15 +124,22 @@ namespace CodexPlugin
if (response.StartsWith(UploadFailedMessage)) FrameworkAssert.Fail("Node failed to store block.");
Log($"Uploaded file. Received contentId: '{response}'.");
CodexAccess.LogDiskSpace("After upload");
return new ContentId(response);
}
public TrackedFile? DownloadContent(ContentId contentId, string fileLabel = "")
{
return DownloadContent(contentId, DoNothing, fileLabel);
}
public TrackedFile? DownloadContent(ContentId contentId, Action<Failure> onFailure, string fileLabel = "")
{
var logMessage = $"Downloading for contentId: '{contentId.Id}'...";
Log(logMessage);
var file = tools.GetFileManager().CreateEmptyFile(fileLabel);
var measurement = Stopwatch.Measure(tools.GetLog(), logMessage, () => DownloadToFile(contentId.Id, file));
var measurement = Stopwatch.Measure(tools.GetLog(), logMessage, () => DownloadToFile(contentId.Id, file, onFailure));
transferSpeeds.AddDownloadSample(file.GetFilesize(), measurement);
Log($"Downloaded file {file.Describe()} to '{file.Filename}'.");
return file;
@@ -126,6 +150,11 @@ namespace CodexPlugin
return CodexAccess.LocalFiles();
}
public CodexSpace Space()
{
return CodexAccess.Space();
}
public void ConnectToPeer(ICodexNode node)
{
var peer = (CodexNode)node;
@@ -142,15 +171,16 @@ namespace CodexPlugin
return CodexAccess.GetPodInfo();
}
public void DeleteRepoFolder()
{
CodexAccess.DeleteRepoFolder();
}
public void Stop(bool waitTillStopped)
{
Log("Stopping...");
CrashWatcher.Stop();
Group.Stop(this, waitTillStopped);
// if (Group.Count() > 1) throw new InvalidOperationException("Codex-nodes that are part of a group cannot be " +
// "individually shut down. Use 'BringOffline()' on the group object to stop the group. This method is only " +
// "available for codex-nodes in groups of 1.");
//
// Group.BringOffline(waitTillStopped);
}
public void EnsureOnlineGetVersionResponse()
@@ -182,12 +212,14 @@ namespace CodexPlugin
.ToArray();
}
private void DownloadToFile(string contentId, TrackedFile file)
private void DownloadToFile(string contentId, TrackedFile file, Action<Failure> onFailure)
{
CodexAccess.LogDiskSpace("Before download");
using var fileStream = File.OpenWrite(file.Filename);
try
{
using var downloadStream = CodexAccess.DownloadFile(contentId);
using var downloadStream = CodexAccess.DownloadFile(contentId, onFailure);
downloadStream.CopyTo(fileStream);
}
catch
@@ -195,11 +227,17 @@ namespace CodexPlugin
Log($"Failed to download file '{contentId}'.");
throw;
}
CodexAccess.LogDiskSpace("After download");
}
private void Log(string msg)
{
tools.GetLog().Log($"{GetName()}: {msg}");
}
private void DoNothing(Failure failure)
{
}
}
}
+3 -2
View File
@@ -52,6 +52,7 @@ namespace CodexPlugin
public CodexLogLevel Libp2p { get; set; }
public CodexLogLevel ContractClock { get; set; } = CodexLogLevel.Warn;
public CodexLogLevel? BlockExchange { get; }
public CodexLogLevel JsonSerialize { get; set; } = CodexLogLevel.Warn;
}
public class CodexSetup : CodexStartupConfig, ICodexSetup
@@ -167,7 +168,7 @@ namespace CodexPlugin
public bool IsStorageNode { get; private set; }
public bool IsValidator { get; private set; }
public Ether InitialEth { get; private set; } = 0.Eth();
public TestToken InitialTestTokens { get; private set; } = 0.TestTokens();
public TestToken InitialTestTokens { get; private set; } = 0.Tst();
public EthAccount EthAccount { get; private set; } = EthAccount.GenerateNew();
public IMarketplaceSetup AsStorageNode()
@@ -201,7 +202,7 @@ namespace CodexPlugin
result += IsStorageNode ? "(storageNode)" : "()";
result += IsValidator ? "(validator)" : "() ";
result += $"Address: '{EthAccount.EthAddress}' ";
result += $"InitialEth/TT({InitialEth.Eth}/{InitialTestTokens.Amount})";
result += $"{InitialEth.Eth} / {InitialTestTokens}";
result += "] ";
return result;
}
+1 -1
View File
@@ -33,7 +33,7 @@ namespace CodexPlugin
foreach (var rc in containers)
{
var podInfo = GetPodInfo(rc);
var podInfos = string.Join(", ", rc.Containers.Select(c => $"Container: '{c.Name}' runs at '{podInfo.K8SNodeName}'={podInfo.Ip}"));
var podInfos = string.Join(", ", rc.Containers.Select(c => $"Container: '{c.Name}' PodLabel: '{c.RunningPod.StartResult.Deployment.PodLabel}' runs at '{podInfo.K8SNodeName}'={podInfo.Ip}"));
Log($"Started {codexSetup.NumberOfNodes} nodes of image '{containers.First().Containers.First().Recipe.Image}'. ({podInfos})");
}
LogSeparator();
@@ -73,11 +73,18 @@ namespace CodexPlugin
"contracts",
"clock"
};
var jsonSerializeTopics = new[]
{
"serde",
"json",
"serialization"
};
level = $"{level};" +
$"{CustomTopics.DiscV5.ToString()!.ToLowerInvariant()}:{string.Join(",", discV5Topics)};" +
$"{CustomTopics.Libp2p.ToString()!.ToLowerInvariant()}:{string.Join(",", libp2pTopics)};" +
$"{CustomTopics.ContractClock.ToString().ToLowerInvariant()}:{string.Join(",", contractClockTopics)}";
$"{CustomTopics.ContractClock.ToString().ToLowerInvariant()}:{string.Join(",", contractClockTopics)};" +
$"{CustomTopics.JsonSerialize.ToString().ToLowerInvariant()}:{string.Join(",", jsonSerializeTopics)}";
if (CustomTopics.BlockExchange != null)
{
+14
View File
@@ -105,4 +105,18 @@ namespace CodexPlugin
return HashCode.Combine(Id);
}
}
public class CodexSpace
{
public long TotalBlocks { get; set; }
public long QuotaMaxBytes { get; set; }
public long QuotaUsedBytes { get; set; }
public long QuotaReservedBytes { get; set; }
public long FreeBytes => QuotaMaxBytes - (QuotaUsedBytes + QuotaReservedBytes);
public override string ToString()
{
return JsonConvert.SerializeObject(this);
}
}
}
+69 -15
View File
@@ -1,4 +1,5 @@
using CodexContractsPlugin;
using CodexOpenApi;
using Newtonsoft.Json.Linq;
using System.Numerics;
using Utils;
@@ -56,34 +57,78 @@ namespace CodexPlugin
ProofProbability = ToDecInt(purchase.ProofProbability),
Reward = ToDecInt(purchase.PricePerSlotPerSecond),
Collateral = ToDecInt(purchase.RequiredCollateral),
Expiry = ToDecInt(DateTimeOffset.UtcNow.ToUnixTimeSeconds() + purchase.Expiry.TotalSeconds),
Expiry = ToDecInt(purchase.Expiry.TotalSeconds),
Nodes = Convert.ToInt32(purchase.MinRequiredNumberOfNodes),
Tolerance = Convert.ToInt32(purchase.NodeFailureTolerance)
};
}
public StoragePurchase Map(CodexOpenApi.Purchase purchase)
{
return new StoragePurchase
{
State = purchase.State,
Error = purchase.Error
};
}
// TODO: Fix openapi spec for this call.
//public StoragePurchase Map(CodexOpenApi.Purchase purchase)
//{
// return new StoragePurchase(Map(purchase.Request))
// {
// State = purchase.State,
// Error = purchase.Error
// };
//}
//public StorageRequest Map(CodexOpenApi.StorageRequest request)
//{
// return new StorageRequest(Map(request.Ask), Map(request.Content))
// {
// Id = request.Id,
// Client = request.Client,
// Expiry = TimeSpan.FromSeconds(Convert.ToInt64(request.Expiry)),
// Nonce = request.Nonce
// };
//}
//public StorageAsk Map(CodexOpenApi.StorageAsk ask)
//{
// return new StorageAsk
// {
// Duration = TimeSpan.FromSeconds(Convert.ToInt64(ask.Duration)),
// MaxSlotLoss = ask.MaxSlotLoss,
// ProofProbability = ask.ProofProbability,
// Reward = Convert.ToDecimal(ask.Reward).TstWei(),
// Slots = ask.Slots,
// SlotSize = new ByteSize(Convert.ToInt64(ask.SlotSize))
// };
//}
//public StorageContent Map(CodexOpenApi.Content content)
//{
// return new StorageContent
// {
// Cid = content.Cid
// };
//}
public StorageAvailability Map(CodexOpenApi.SalesAvailabilityREAD read)
{
return new StorageAvailability(
totalSpace: new Utils.ByteSize(Convert.ToInt64(read.TotalSize)),
totalSpace: new ByteSize(Convert.ToInt64(read.TotalSize)),
maxDuration: TimeSpan.FromSeconds(Convert.ToDouble(read.Duration)),
minPriceForTotalSpace: new TestToken(Convert.ToDecimal(read.MinPrice)),
maxCollateral: new TestToken(Convert.ToDecimal(read.MaxCollateral))
minPriceForTotalSpace: new TestToken(BigInteger.Parse(read.MinPrice)),
maxCollateral: new TestToken(BigInteger.Parse(read.MaxCollateral))
)
{
Id = read.Id
};
}
public CodexSpace Map(Space space)
{
return new CodexSpace
{
QuotaMaxBytes = space.QuotaMaxBytes,
QuotaReservedBytes = space.QuotaReservedBytes,
QuotaUsedBytes = space.QuotaUsedBytes,
TotalBlocks = space.TotalBlocks
};
}
private DebugInfoVersion MapDebugInfoVersion(JObject obj)
{
return new DebugInfoVersion
@@ -98,7 +143,7 @@ namespace CodexPlugin
return new DebugInfoTable
{
LocalNode = MapDebugInfoTableNode(obj.GetValue("localNode")),
Nodes = new DebugInfoTableNode[0]
Nodes = MapDebugInfoTableNodeArray(obj.GetValue("nodes") as JArray)
};
}
@@ -117,6 +162,16 @@ namespace CodexPlugin
};
}
private DebugInfoTableNode[] MapDebugInfoTableNodeArray(JArray? nodes)
{
if (nodes == null || nodes.Count == 0)
{
return new DebugInfoTableNode[0];
}
return nodes.Select(MapDebugInfoTableNode).ToArray();
}
private Manifest MapManifest(CodexOpenApi.ManifestItem manifest)
{
return new Manifest
@@ -165,8 +220,7 @@ namespace CodexPlugin
private string ToDecInt(TestToken t)
{
var i = new BigInteger(t.Amount);
return i.ToString("D");
return t.TstWei.ToString("D");
}
}
}
+69 -11
View File
@@ -38,7 +38,9 @@ namespace CodexPlugin
Log($"Storage requested successfully. PurchaseId: '{response}'.");
return new StoragePurchaseContract(log, codexAccess, response, purchase);
var contract = new StoragePurchaseContract(log, codexAccess, response, purchase);
contract.WaitForStorageContractSubmitted();
return contract;
}
public string MakeStorageAvailable(StorageAvailability availability)
@@ -54,7 +56,7 @@ namespace CodexPlugin
private void Log(string msg)
{
log.Log($"{codexAccess.Container.Name} {msg}");
log.Log($"{codexAccess.Container.Containers.Single().Name} {msg}");
}
}
@@ -83,8 +85,11 @@ namespace CodexPlugin
{
private readonly ILog log;
private readonly CodexAccess codexAccess;
private readonly TimeSpan gracePeriod = TimeSpan.FromSeconds(10);
private DateTime? contractStartUtc;
private readonly TimeSpan gracePeriod = TimeSpan.FromSeconds(30);
private readonly DateTime contractPendingUtc = DateTime.UtcNow;
private DateTime? contractSubmittedUtc = DateTime.UtcNow;
private DateTime? contractStartedUtc;
private DateTime? contractFinishedUtc;
public StoragePurchaseContract(ILog log, CodexAccess codexAccess, string purchaseId, StoragePurchaseRequest purchase)
{
@@ -92,28 +97,48 @@ namespace CodexPlugin
this.codexAccess = codexAccess;
PurchaseId = purchaseId;
Purchase = purchase;
ContentId = new ContentId(codexAccess.GetPurchaseStatus(purchaseId).Request.Content.Cid);
}
public string PurchaseId { get; }
public StoragePurchaseRequest Purchase { get; }
public ContentId ContentId { get; }
public TimeSpan? PendingToSubmitted => contractSubmittedUtc - contractPendingUtc;
public TimeSpan? SubmittedToStarted => contractStartedUtc - contractSubmittedUtc;
public TimeSpan? SubmittedToFinished => contractFinishedUtc - contractSubmittedUtc;
public void WaitForStorageContractSubmitted()
{
WaitForStorageContractState(gracePeriod, "submitted", sleep: 200);
contractSubmittedUtc = DateTime.UtcNow;
LogSubmittedDuration();
AssertDuration(PendingToSubmitted, gracePeriod, nameof(PendingToSubmitted));
}
public void WaitForStorageContractStarted()
{
var timeout = Purchase.Expiry + gracePeriod;
WaitForStorageContractState(timeout, "started");
contractStartUtc = DateTime.UtcNow;
contractStartedUtc = DateTime.UtcNow;
LogStartedDuration();
AssertDuration(SubmittedToStarted, timeout, nameof(SubmittedToStarted));
}
public void WaitForStorageContractFinished()
{
if (!contractStartUtc.HasValue)
if (!contractStartedUtc.HasValue)
{
WaitForStorageContractStarted();
}
var currentContractTime = DateTime.UtcNow - contractStartUtc!.Value;
var currentContractTime = DateTime.UtcNow - contractSubmittedUtc!.Value;
var timeout = (Purchase.Duration - currentContractTime) + gracePeriod;
WaitForStorageContractState(timeout, "finished");
contractFinishedUtc = DateTime.UtcNow;
LogFinishedDuration();
AssertDuration(SubmittedToFinished, timeout, nameof(SubmittedToFinished));
}
public StoragePurchase GetPurchaseStatus(string purchaseId)
@@ -121,12 +146,12 @@ namespace CodexPlugin
return codexAccess.GetPurchaseStatus(purchaseId);
}
private void WaitForStorageContractState(TimeSpan timeout, string desiredState)
private void WaitForStorageContractState(TimeSpan timeout, string desiredState, int sleep = 1000)
{
var lastState = "";
var waitStart = DateTime.UtcNow;
log.Log($"Waiting for {Time.FormatDuration(timeout)} for contract '{PurchaseId}' to reach state '{desiredState}'.");
Log($"Waiting for {Time.FormatDuration(timeout)} to reach state '{desiredState}'.");
while (lastState != desiredState)
{
var purchaseStatus = codexAccess.GetPurchaseStatus(PurchaseId);
@@ -137,7 +162,7 @@ namespace CodexPlugin
log.Debug("Purchase status: " + statusJson);
}
Thread.Sleep(1000);
Thread.Sleep(sleep);
if (lastState == "errored")
{
@@ -149,7 +174,40 @@ namespace CodexPlugin
FrameworkAssert.Fail($"Contract did not reach '{desiredState}' within {Time.FormatDuration(timeout)} timeout. {statusJson}");
}
}
log.Log($"Contract '{desiredState}'.");
}
private void LogSubmittedDuration()
{
Log($"Pending to Submitted in {Time.FormatDuration(PendingToSubmitted)} " +
$"( < {Time.FormatDuration(gracePeriod)})");
}
private void LogStartedDuration()
{
Log($"Submitted to Started in {Time.FormatDuration(SubmittedToStarted)} " +
$"( < {Time.FormatDuration(Purchase.Expiry + gracePeriod)})");
}
private void LogFinishedDuration()
{
Log($"Submitted to Finished in {Time.FormatDuration(SubmittedToFinished)} " +
$"( < {Time.FormatDuration(Purchase.Duration + gracePeriod)})");
}
private void AssertDuration(TimeSpan? span, TimeSpan max, string message)
{
if (span == null) throw new ArgumentNullException(nameof(MarketplaceAccess) + ": " + message + " (IsNull)");
if (span.Value.TotalDays >= max.TotalSeconds)
{
throw new Exception(nameof(MarketplaceAccess) +
$": Duration out of range. Max: {Time.FormatDuration(max)} but was: {Time.FormatDuration(span.Value)} " +
message);
}
}
private void Log(string msg)
{
log.Log($"[{PurchaseId}] {msg}");
}
}
}
+32 -2
View File
@@ -1,5 +1,7 @@
using CodexContractsPlugin;
using CodexOpenApi;
using Logging;
using System.Data;
using Utils;
namespace CodexPlugin
@@ -12,8 +14,8 @@ namespace CodexPlugin
}
public ContentId ContentId { get; set; }
public TestToken PricePerSlotPerSecond { get; set; } = 1.TestTokens();
public TestToken RequiredCollateral { get; set; } = 1.TestTokens();
public TestToken PricePerSlotPerSecond { get; set; } = 1.TstWei();
public TestToken RequiredCollateral { get; set; } = 1.TstWei();
public uint MinRequiredNumberOfNodes { get; set; }
public uint NodeFailureTolerance { get; set; }
public int ProofProbability { get; set; }
@@ -37,6 +39,34 @@ namespace CodexPlugin
{
public string State { get; set; } = string.Empty;
public string Error { get; set; } = string.Empty;
public StorageRequest Request { get; set; } = null!;
}
public class StorageRequest
{
public string Id { get; set; } = string.Empty;
public string Client { get; set; } = string.Empty;
public StorageAsk Ask { get; set; } = null!;
public StorageContent Content { get; set; } = null!;
public string Expiry { get; set; } = string.Empty;
public string Nonce { get; set; } = string.Empty;
}
public class StorageAsk
{
public int Slots { get; set; }
public string SlotSize { get; set; } = string.Empty;
public string Duration { get; set; } = string.Empty;
public string ProofProbability { get; set; } = string.Empty;
public string Reward { get; set; } = string.Empty;
public int MaxSlotLoss { get; set; }
}
public class StorageContent
{
public string Cid { get; set; } = string.Empty;
//public ErasureParameters Erasure { get; set; }
//public PoRParameters Por { get; set; }
}
public class StorageAvailability
+6 -2
View File
@@ -213,8 +213,7 @@ components:
description: Number as decimal string that represents how much collateral is asked from hosts that wants to fill a slots
expiry:
type: string
description: Number as decimal string that represents expiry time of the request (in unix timestamp)
description: Number as decimal string that represents expiry threshold in seconds from when the Request is submitted. When the threshold is reached and the Request does not find requested amount of nodes to host the data, the Request is voided. The number of seconds can not be higher then the Request's duration itself.
StorageAsk:
type: object
required:
@@ -290,6 +289,7 @@ components:
description: "Root hash of the content"
originalBytes:
type: integer
format: int64
description: "Length of original content in bytes"
blockSize:
type: integer
@@ -304,14 +304,18 @@ components:
totalBlocks:
description: "Number of blocks stored by the node"
type: integer
format: int64
quotaMaxBytes:
type: integer
format: int64
description: "Maximum storage space used by the node"
quotaUsedBytes:
type: integer
format: int64
description: "Amount of storage space currently in use"
quotaReservedBytes:
type: integer
format: int64
description: "Amount of storage space reserved"
servers:
@@ -1,4 +1,5 @@
using Core;
using IdentityModel;
using KubernetesWorkflow.Types;
using Logging;
using System.Globalization;
@@ -177,6 +178,41 @@ namespace MetricsPlugin
{
return "[" + string.Join(',', Sets.Select(s => s.ToString())) + "]";
}
public string AsCsv()
{
var allTimestamps = Sets.SelectMany(s => s.Values.Select(v => v.Timestamp)).Distinct().OrderDescending().ToArray();
var lines = new List<string>();
MakeLine(lines, e =>
{
e.Add("Metrics");
foreach (var ts in allTimestamps) e.Add(ts.ToEpochTime().ToString());
});
foreach (var set in Sets)
{
MakeLine(lines, e =>
{
e.Add(set.Name);
foreach (var ts in allTimestamps)
{
var value = set.Values.SingleOrDefault(v => v.Timestamp == ts);
if (value == null) e.Add(" ");
else e.Add(value.Value.ToString());
}
});
}
return string.Join(Environment.NewLine, lines.ToArray());
}
private void MakeLine(List<string> lines, Action<List<string>> values)
{
var list = new List<string>();
values(list);
lines.Add(string.Join(",", list));
}
}
public class MetricsSet
@@ -148,7 +148,7 @@ namespace ContinuousTests
log.Log($"Clearing namespace '{test.CustomK8sNamespace}'...");
var entryPoint = entryPointFactory.CreateEntryPoint(config.KubeConfigFile, config.DataPath, test.CustomK8sNamespace, log);
entryPoint.Tools.CreateWorkflow().DeleteNamespacesStartingWith(test.CustomK8sNamespace);
entryPoint.Tools.CreateWorkflow().DeleteNamespacesStartingWith(test.CustomK8sNamespace, wait: true);
}
private void PerformCleanup(ILog log)
@@ -157,7 +157,7 @@ namespace ContinuousTests
log.Log("Cleaning up test namespace...");
var entryPoint = entryPointFactory.CreateEntryPoint(config.KubeConfigFile, config.DataPath, config.CodexDeployment.Metadata.KubeNamespace, log);
entryPoint.Decommission(deleteKubernetesResources: true, deleteTrackedFiles: true);
entryPoint.Decommission(deleteKubernetesResources: true, deleteTrackedFiles: true, waitTillDone: true);
log.Log("Cleanup finished.");
}
}
+1 -1
View File
@@ -64,7 +64,7 @@ namespace ContinuousTests
}
finally
{
entryPoint.Tools.CreateWorkflow().DeleteNamespace();
entryPoint.Tools.CreateWorkflow().DeleteNamespace(wait: false);
}
}
+2 -1
View File
@@ -54,7 +54,8 @@ namespace ContinuousTests
entryPoint.Decommission(
deleteKubernetesResources: false, // This would delete the continuous test net.
deleteTrackedFiles: true
deleteTrackedFiles: true,
waitTillDone: false
);
runFinishedHandle.Set();
}
@@ -1,3 +1,4 @@
using CodexPlugin;
using CodexTests;
using DistTestCore;
using FileUtils;
@@ -7,35 +8,45 @@ using Utils;
namespace CodexLongTests.BasicTests
{
[TestFixture]
public class DownloadTests : CodexDistTest
public class DownloadTests : AutoBootstrapDistTest
{
[TestCase(3, 500)]
[TestCase(5, 100)]
[TestCase(10, 256)]
[Test]
[Combinatorial]
[UseLongTimeouts]
public void ParallelDownload(int numberOfNodes, int filesizeMb)
public void ParallelDownload(
[Values(1, 3, 5)] int numberOfFiles,
[Values(10, 50, 100)] int filesizeMb)
{
var group = AddCodex(numberOfNodes);
var host = AddCodex();
var host = StartCodex();
var client = StartCodex();
foreach (var node in group)
var testfiles = new List<TrackedFile>();
var contentIds = new List<ContentId>();
var downloadedFiles = new List<TrackedFile?>();
for (int i = 0; i < numberOfFiles; i++)
{
host.ConnectToPeer(node);
testfiles.Add(GenerateTestFile(filesizeMb.MB()));
contentIds.Add(new ContentId());
downloadedFiles.Add(null);
}
var testFile = GenerateTestFile(filesizeMb.MB());
var contentId = host.UploadFile(testFile);
var list = new List<Task<TrackedFile?>>();
foreach (var node in group)
for (int i = 0; i < numberOfFiles; i++)
{
list.Add(Task.Run(() => { return node.DownloadContent(contentId); }));
contentIds[i] = host.UploadFile(testfiles[i]);
}
Task.WaitAll(list.ToArray());
foreach (var task in list)
var downloadTasks = new List<Task>();
for (int i = 0; i < numberOfFiles; i++)
{
testFile.AssertIsEqual(task.Result);
downloadTasks.Add(Task.Run(() => { downloadedFiles[i] = client.DownloadContent(contentIds[i]); }));
}
Task.WaitAll(downloadTasks.ToArray());
for (int i = 0; i < numberOfFiles; i++)
{
testfiles[i].AssertIsEqual(downloadedFiles[i]);
}
}
}
@@ -48,7 +48,7 @@ namespace CodexLongTests.BasicTests
var expectedFile = GenerateTestFile(sizeMB);
var node = AddCodex(s => s.WithStorageQuota((size + 10).MB()));
var node = StartCodex(s => s.WithStorageQuota((size + 10).MB()));
var uploadStart = DateTime.UtcNow;
var cid = node.UploadFile(expectedFile);
@@ -6,10 +6,12 @@ namespace CodexLongTests.BasicTests
{
public class TestInfraTests : CodexDistTest
{
[Test, UseLongTimeouts]
[Test]
[UseLongTimeouts]
[Ignore("Not supported atm")]
public void TestInfraShouldHave1000AddressSpacesPerPod()
{
var group = AddCodex(1000, s => s.EnableMetrics());
var group = StartCodex(1000, s => s.EnableMetrics());
var nodeIds = group.Select(n => n.GetDebugInfo().Id).ToArray();
@@ -17,12 +19,14 @@ namespace CodexLongTests.BasicTests
"Not all created nodes provided a unique id.");
}
[Test, UseLongTimeouts]
[Test]
[UseLongTimeouts]
[Ignore("Not supported atm")]
public void TestInfraSupportsManyConcurrentPods()
{
for (var i = 0; i < 20; i++)
{
var n = AddCodex();
var n = StartCodex();
Assert.That(!string.IsNullOrEmpty(n.GetDebugInfo().Id));
}
+21 -23
View File
@@ -8,41 +8,39 @@ using Utils;
namespace CodexLongTests.BasicTests
{
[TestFixture]
public class UploadTests : CodexDistTest
public class UploadTests : AutoBootstrapDistTest
{
[TestCase(3, 50)]
[TestCase(5, 75)]
[TestCase(10, 25)]
[Test]
[Combinatorial]
[UseLongTimeouts]
public void ParallelUpload(int numberOfNodes, int filesizeMb)
public void ParallelUpload(
[Values(1, 3, 5)] int numberOfFiles,
[Values(10, 50, 100)] int filesizeMb)
{
var group = AddCodex(numberOfNodes);
var host = AddCodex();
foreach (var node in group)
{
host.ConnectToPeer(node);
}
var host = StartCodex();
var client = StartCodex();
var testfiles = new List<TrackedFile>();
var contentIds = new List<Task<ContentId>>();
var contentIds = new List<ContentId>();
for (int i = 0; i < group.Count(); i++)
for (int i = 0; i < numberOfFiles; i++)
{
testfiles.Add(GenerateTestFile(filesizeMb.MB()));
var n = i;
contentIds.Add(Task.Run(() => { return host.UploadFile(testfiles[n]); }));
contentIds.Add(new ContentId());
}
var downloads = new List<Task<TrackedFile?>>();
for (int i = 0; i < group.Count(); i++)
var uploadTasks = new List<Task>();
for (int i = 0; i < numberOfFiles; i++)
{
var n = i;
downloads.Add(Task.Run(() => { return group[n].DownloadContent(contentIds[n].Result); }));
uploadTasks.Add(Task.Run(() => { contentIds[i] = host.UploadFile(testfiles[i]); }));
}
Task.WaitAll(downloads.ToArray());
for (int i = 0; i < group.Count(); i++)
Task.WaitAll(uploadTasks.ToArray());
for (int i = 0; i < numberOfFiles; i++)
{
testfiles[i].AssertIsEqual(downloads[i].Result);
var downloaded = client.DownloadContent(contentIds[i]);
testfiles[i].AssertIsEqual(downloaded);
}
}
}
@@ -15,9 +15,9 @@ namespace CodexLongTests.DownloadConnectivityTests
[Values(10, 15, 20)] int numberOfNodes,
[Values(10, 100)] int sizeMBs)
{
for (var i = 0; i < numberOfNodes; i++) AddCodex();
var nodes = StartCodex(numberOfNodes);
CreatePeerDownloadTestHelpers().AssertFullDownloadInterconnectivity(GetAllOnlineCodexNodes(), sizeMBs.MB());
CreatePeerDownloadTestHelpers().AssertFullDownloadInterconnectivity(nodes, sizeMBs.MB());
}
}
}
@@ -16,7 +16,7 @@ namespace CodexTests.ScalabilityTests
[Values(100, 1000)] int fileSize
)
{
var hosts = AddCodex(numberOfHosts, s => s.WithLogLevel(CodexPlugin.CodexLogLevel.Trace));
var hosts = StartCodex(numberOfHosts, s => s.WithLogLevel(CodexPlugin.CodexLogLevel.Trace));
var file = GenerateTestFile(fileSize.MB());
var cid = hosts[0].UploadFile(file);
var tailOfManifestCid = cid.Id.Substring(cid.Id.Length - 6);
@@ -38,7 +38,7 @@ namespace CodexTests.ScalabilityTests
foreach (var h in hosts) h.DownloadContent(cid);
var client = AddCodex(s => s.WithLogLevel(CodexPlugin.CodexLogLevel.Trace));
var client = StartCodex(s => s.WithLogLevel(CodexPlugin.CodexLogLevel.Trace));
var resultFile = client.DownloadContent(cid);
resultFile!.AssertIsEqual(file);
@@ -9,9 +9,6 @@ namespace CodexTests.ScalabilityTests;
[TestFixture]
public class ScalabilityTests : CodexDistTest
{
private const string PatchedImage = "codexstorage/nim-codex:sha-9aeac06-dist-tests";
private const string MasterImage = "codexstorage/nim-codex:sha-5380912-dist-tests";
/// <summary>
/// We upload a file to node A, then download it with B.
/// Then we stop node A, and download again with node C.
@@ -20,18 +17,16 @@ public class ScalabilityTests : CodexDistTest
[Combinatorial]
[UseLongTimeouts]
[DontDownloadLogs]
[WaitForCleanup]
public void ShouldMaintainFileInNetwork(
[Values(10, 40, 80, 100)] int numberOfNodes,
[Values(100, 1000, 5000, 10000)] int fileSizeInMb,
[Values(true, false)] bool usePatchedImage
[Values(4, 5, 6)] int numberOfNodes, // TODO: include 10, 40, 80 and 100, not 5
[Values(4000, 5000, 6000, 7000, 8000, 9000, 10000)] int fileSizeInMb
)
{
CodexContainerRecipe.DockerImageOverride = usePatchedImage ? PatchedImage : MasterImage;
var logLevel = CodexLogLevel.Trace;
var logLevel = CodexLogLevel.Info;
var bootstrap = AddCodex(s => s.WithLogLevel(logLevel));
var nodes = AddCodex(numberOfNodes - 1, s => s
var bootstrap = StartCodex(s => s.WithLogLevel(logLevel));
var nodes = StartCodex(numberOfNodes - 1, s => s
.WithBootstrapNode(bootstrap)
.WithLogLevel(logLevel)
.WithStorageQuota((fileSizeInMb + 50).MB())
@@ -41,40 +36,48 @@ public class ScalabilityTests : CodexDistTest
var downloader = nodes.PickOneRandom();
var testFile = GenerateTestFile(fileSizeInMb.MB());
var contentId = uploader.UploadFile(testFile);
var downloadedFile = downloader.DownloadContent(contentId);
LogNodeStatus(uploader);
var contentId = uploader.UploadFile(testFile, f => LogNodeStatus(uploader));
LogNodeStatus(uploader);
LogNodeStatus(downloader);
var downloadedFile = downloader.DownloadContent(contentId, f => LogNodeStatus(downloader));
LogNodeStatus(downloader);
downloadedFile!.AssertIsEqual(testFile);
uploader.DeleteRepoFolder();
uploader.Stop(true);
var otherDownloader = nodes.PickOneRandom();
downloadedFile = otherDownloader.DownloadContent(contentId);
downloadedFile!.AssertIsEqual(testFile);
downloader.DeleteRepoFolder();
otherDownloader.DeleteRepoFolder();
}
/// <summary>
/// We upload a file to each node, to put a more wide-spread load on the network.
/// Then we run the same test as ShouldMaintainFileInNetwork.
/// </summary>
[Ignore("Make ShouldMaintainFileInNetwork pass reliably first.")]
[Ignore("Fix ShouldMaintainFileInNetwork for all values first")]
[Test]
[Combinatorial]
[UseLongTimeouts]
[DontDownloadLogs]
[WaitForCleanup]
public void EveryoneGetsAFile(
[Values(10, 40, 80, 100)] int numberOfNodes,
[Values(100, 1000)] int fileSizeInMb,
[Values(true, false)] bool usePatchedImage
[Values(100, 1000, 5000, 10000)] int fileSizeInMb
)
{
CodexContainerRecipe.DockerImageOverride = usePatchedImage ? PatchedImage : MasterImage;
var logLevel = CodexLogLevel.Info;
var bootstrap = AddCodex(s => s.WithLogLevel(logLevel));
var nodes = AddCodex(numberOfNodes - 1, s => s
var bootstrap = StartCodex(s => s.WithLogLevel(logLevel));
var nodes = StartCodex(numberOfNodes - 1, s => s
.WithBootstrapNode(bootstrap)
.WithLogLevel(logLevel)
.WithStorageQuota((fileSizeInMb + 50).MB())
+1 -1
View File
@@ -8,7 +8,7 @@ namespace CodexTests
[SetUp]
public void SetUpBootstrapNode()
{
BootstrapNode = AddCodex(s => s.WithName("BOOTSTRAP"));
BootstrapNode = StartCodex(s => s.WithName("BOOTSTRAP"));
}
[TearDown]
@@ -1,298 +0,0 @@
using CodexContractsPlugin;
using CodexPlugin;
using GethPlugin;
using KubernetesWorkflow.Types;
using Logging;
using MetricsPlugin;
using NUnit.Framework;
using Utils;
namespace CodexTests.BasicTests
{
[Ignore("Used for debugging continuous tests")]
[TestFixture]
public class ContinuousSubstitute : AutoBootstrapDistTest
{
[Test]
public void ContinuousTestSubstitute()
{
var geth = Ci.StartGethNode(s => s.IsMiner().WithName("geth"));
var contract = Ci.StartCodexContracts(geth);
var group = AddCodex(5, o => o
.EnableMetrics()
.EnableMarketplace(geth, contract, s => s
.WithInitial(10.Eth(), 100000.TestTokens())
.AsStorageNode()
.AsValidator())
.WithBlockTTL(TimeSpan.FromMinutes(5))
.WithBlockMaintenanceInterval(TimeSpan.FromSeconds(10))
.WithBlockMaintenanceNumber(100)
.WithStorageQuota(1.GB()));
var nodes = group.Cast<CodexNode>().ToArray();
var rc = Ci.DeployMetricsCollector(nodes);
var availability = new StorageAvailability(
totalSpace: 500.MB(),
maxDuration: TimeSpan.FromMinutes(5),
minPriceForTotalSpace: 500.TestTokens(),
maxCollateral: 1024.TestTokens()
);
foreach (var node in nodes)
{
node.Marketplace.MakeStorageAvailable(availability);
}
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, rc);
Thread.Sleep(TimeSpan.FromSeconds(5));
}
}
private void LogBytesPerMillisecond(Action action)
{
var sw = Stopwatch.Begin(GetTestLog());
action();
var duration = sw.End();
double totalMs = duration.TotalMilliseconds;
double totalBytes = fileSize.SizeInBytes;
var bytesPerMs = totalBytes / totalMs;
Log($"Bytes per millisecond: {bytesPerMs}");
}
[Test]
public void PeerTest()
{
var group = AddCodex(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<CodexNode>().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 = nodes.ToList().PickOneRandom();
var file = GenerateTestFile(50.MB());
node.UploadFile(file);
Thread.Sleep(20000);
}
}
private void CheckRoutingTables(IEnumerable<ICodexNode> 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(ICodexNode 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 const string BytesStoredMetric = "codexRepostoreBytesUsed";
private void PerformTest(ICodexNode primary, ICodexNode secondary, RunningPod rc)
{
ScopedTestFiles(() =>
{
var testFile = GenerateTestFile(fileSize);
var metrics = Ci.WrapMetricsCollector(rc, primary);
var beforeBytesStored = metrics.GetMetric(BytesStoredMetric);
ContentId contentId = null!;
LogBytesPerMillisecond(() => contentId = primary.UploadFile(testFile));
var low = fileSize.SizeInBytes;
var high = low * 1.2;
Log("looking for: " + low + " < " + high);
Time.WaitUntil(() =>
{
var afterBytesStored = metrics.GetMetric(BytesStoredMetric);
var newBytes = Convert.ToInt64(afterBytesStored.Values.Last().Value - beforeBytesStored.Values.Last().Value);
return high > newBytes && newBytes > low;
}, TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(2), nameof(ContinuousSubstitute));
FileUtils.TrackedFile? downloadedFile = null;
LogBytesPerMillisecond(() => downloadedFile = secondary.DownloadContent(contentId));
testFile.AssertIsEqual(downloadedFile);
});
}
[Test]
public void HoldMyBeerTest()
{
var blockExpirationTime = TimeSpan.FromMinutes(3);
var group = AddCodex(3, o => o
.EnableMetrics()
.WithBlockTTL(blockExpirationTime)
.WithBlockMaintenanceInterval(TimeSpan.FromMinutes(2))
.WithBlockMaintenanceNumber(10000)
.WithStorageQuota(2000.MB()));
var nodes = group.Cast<CodexNode>().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 = Ci.DownloadLog(node, 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 = Ci.DownloadLog(node, 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);
}
}
}
+6 -3
View File
@@ -13,7 +13,7 @@ namespace CodexTests.BasicTests
[Test]
public void CodexLogExample()
{
var primary = AddCodex(s => s.WithLogLevel(CodexLogLevel.Trace, new CodexLogCustomTopics(CodexLogLevel.Warn, CodexLogLevel.Warn)));
var primary = StartCodex(s => s.WithLogLevel(CodexLogLevel.Trace, new CodexLogCustomTopics(CodexLogLevel.Warn, CodexLogLevel.Warn)));
var cid = primary.UploadFile(GenerateTestFile(5.MB()));
@@ -28,8 +28,8 @@ namespace CodexTests.BasicTests
[Test]
public void TwoMetricsExample()
{
var group = AddCodex(2, s => s.EnableMetrics());
var group2 = AddCodex(2, s => s.EnableMetrics());
var group = StartCodex(2, s => s.EnableMetrics());
var group2 = StartCodex(2, s => s.EnableMetrics());
var primary = group[0];
var secondary = group[1];
@@ -45,6 +45,9 @@ namespace CodexTests.BasicTests
metrics[0].AssertThat("libp2p_peers", Is.EqualTo(1));
metrics[1].AssertThat("libp2p_peers", Is.EqualTo(1));
LogNodeStatus(primary, metrics[0]);
LogNodeStatus(primary2, metrics[1]);
}
[Test]
+53 -13
View File
@@ -13,15 +13,15 @@ namespace CodexTests.BasicTests
[Test]
public void MarketplaceExample()
{
var hostInitialBalance = 234.TestTokens();
var clientInitialBalance = 100000.TestTokens();
var hostInitialBalance = 234.TstWei();
var clientInitialBalance = 100000.TstWei();
var fileSize = 10.MB();
var geth = Ci.StartGethNode(s => s.IsMiner().WithName("disttest-geth"));
var contracts = Ci.StartCodexContracts(geth);
var numberOfHosts = 5;
var hosts = AddCodex(numberOfHosts, s => s
var hosts = StartCodex(numberOfHosts, s => s
.WithName("Host")
.WithLogLevel(CodexLogLevel.Trace, new CodexLogCustomTopics(CodexLogLevel.Error, CodexLogLevel.Error, CodexLogLevel.Warn)
{
@@ -33,7 +33,7 @@ namespace CodexTests.BasicTests
.AsStorageNode()
.AsValidator()));
var expectedHostBalance = (numberOfHosts * hostInitialBalance.Amount).TestTokens();
var expectedHostBalance = (numberOfHosts * hostInitialBalance.TstWei).TstWei();
foreach (var host in hosts)
{
AssertBalance(contracts, host, Is.EqualTo(expectedHostBalance));
@@ -41,15 +41,15 @@ namespace CodexTests.BasicTests
var availability = new StorageAvailability(
totalSpace: 10.GB(),
maxDuration: TimeSpan.FromMinutes(30),
minPriceForTotalSpace: 1.TestTokens(),
maxCollateral: 20.TestTokens()
minPriceForTotalSpace: 1.TstWei(),
maxCollateral: 20.TstWei()
);
host.Marketplace.MakeStorageAvailable(availability);
}
var testFile = GenerateTestFile(fileSize);
var client = AddCodex(s => s
var client = StartCodex(s => s
.WithName("Client")
.EnableMarketplace(geth, contracts, m => m
.WithInitial(10.Eth(), clientInitialBalance)));
@@ -60,8 +60,8 @@ namespace CodexTests.BasicTests
var purchase = new StoragePurchaseRequest(contentId)
{
PricePerSlotPerSecond = 2.TestTokens(),
RequiredCollateral = 10.TestTokens(),
PricePerSlotPerSecond = 2.TstWei(),
RequiredCollateral = 10.TstWei(),
MinRequiredNumberOfNodes = 5,
NodeFailureTolerance = 2,
ProofProbability = 5,
@@ -85,6 +85,46 @@ namespace CodexTests.BasicTests
Assert.That(contracts.GetRequestState(request), Is.EqualTo(RequestState.Finished));
}
[Test]
public void CanDownloadContentFromContractCid()
{
var fileSize = 10.MB();
var geth = Ci.StartGethNode(s => s.IsMiner().WithName("disttest-geth"));
var contracts = Ci.StartCodexContracts(geth);
var testFile = GenerateTestFile(fileSize);
var client = StartCodex(s => s
.WithName("Client")
.EnableMarketplace(geth, contracts, m => m
.WithInitial(10.Eth(), 10.Tst())));
var uploadCid = client.UploadFile(testFile);
var purchase = new StoragePurchaseRequest(uploadCid)
{
PricePerSlotPerSecond = 2.TstWei(),
RequiredCollateral = 10.TstWei(),
MinRequiredNumberOfNodes = 5,
NodeFailureTolerance = 2,
ProofProbability = 5,
Duration = TimeSpan.FromMinutes(5),
Expiry = TimeSpan.FromMinutes(4)
};
var purchaseContract = client.Marketplace.RequestStorage(purchase);
var contractCid = purchaseContract.ContentId;
Assert.That(uploadCid.Id, Is.Not.EqualTo(contractCid.Id));
// Download both from client.
testFile.AssertIsEqual(client.DownloadContent(uploadCid));
testFile.AssertIsEqual(client.DownloadContent(contractCid));
// Download both from another node.
var downloader = StartCodex(s => s.WithName("Downloader"));
testFile.AssertIsEqual(downloader.DownloadContent(uploadCid));
testFile.AssertIsEqual(downloader.DownloadContent(contractCid));
}
private void WaitForAllSlotFilledEvents(ICodexContracts contracts, StoragePurchaseRequest purchase, IGethNode geth)
{
Time.Retry(() =>
@@ -92,10 +132,10 @@ namespace CodexTests.BasicTests
var blockRange = geth.ConvertTimeRangeToBlockRange(GetTestRunTimeRange());
var slotFilledEvents = contracts.GetSlotFilledEvents(blockRange);
Log($"SlotFilledEvents: {slotFilledEvents.Length} - NumSlots: {purchase.MinRequiredNumberOfNodes}");
if (slotFilledEvents.Length != purchase.MinRequiredNumberOfNodes) throw new Exception();
}, Convert.ToInt32(purchase.Duration.TotalSeconds / 5) + 10, TimeSpan.FromSeconds(5), "Checking SlotFilled events");
var msg = $"SlotFilledEvents: {slotFilledEvents.Length} - NumSlots: {purchase.MinRequiredNumberOfNodes}";
Debug(msg);
if (slotFilledEvents.Length != purchase.MinRequiredNumberOfNodes) throw new Exception(msg);
}, purchase.Expiry + TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(5), "Checking SlotFilled events");
}
private void AssertStorageRequest(Request request, StoragePurchaseRequest purchase, ICodexContracts contracts, ICodexNode buyer)
+2 -13
View File
@@ -1,5 +1,4 @@
using CodexPlugin;
using DistTestCore;
using NUnit.Framework;
using Utils;
@@ -11,21 +10,11 @@ namespace CodexTests.BasicTests
[Test]
public void OneClientTest()
{
var primary = Ci.StartCodexNode();
var primary = StartCodex();
PerformOneClientTest(primary);
}
[Test]
public void RestartTest()
{
var primary = Ci.StartCodexNode();
primary.Stop(waitTillStopped: true);
primary = Ci.StartCodexNode();
PerformOneClientTest(primary);
LogNodeStatus(primary);
}
private void PerformOneClientTest(ICodexNode primary)
+33 -3
View File
@@ -1,4 +1,5 @@
using NUnit.Framework;
using CodexPlugin;
using NUnit.Framework;
using Utils;
namespace CodexTests.BasicTests
@@ -9,8 +10,8 @@ namespace CodexTests.BasicTests
[Test]
public void ThreeClient()
{
var primary = AddCodex();
var secondary = AddCodex();
var primary = StartCodex();
var secondary = StartCodex();
var testFile = GenerateTestFile(10.MB());
@@ -20,5 +21,34 @@ namespace CodexTests.BasicTests
testFile.AssertIsEqual(downloadedFile);
}
[Test]
public void DownloadingUnknownCidDoesNotCauseCrash()
{
var node = StartCodex(2).First();
var unknownCid = new ContentId("zDvZRwzkzHsok3Z8yMoiXE9EDBFwgr8WygB8s4ddcLzzSwwXAxLZ");
try
{
node.DownloadContent(unknownCid);
}
catch (Exception ex)
{
if (!ex.Message.StartsWith("Retry 'DownloadFile' timed out"))
{
throw;
}
}
// Check that the node stays alive for at least another 5 minutes.
var start = DateTime.UtcNow;
while ((DateTime.UtcNow - start) < TimeSpan.FromMinutes(5))
{
Thread.Sleep(5000);
var info = node.GetDebugInfo();
Assert.That(!string.IsNullOrEmpty(info.Id));
}
}
}
}
@@ -10,8 +10,8 @@ namespace CodexTests.BasicTests
[Test]
public void TwoClientTest()
{
var uploader = AddCodex(s => s.WithName("Uploader"));
var downloader = AddCodex(s => s.WithName("Downloader").WithBootstrapNode(uploader));
var uploader = StartCodex(s => s.WithName("Uploader"));
var downloader = StartCodex(s => s.WithName("Downloader").WithBootstrapNode(uploader));
PerformTwoClientTest(uploader, downloader);
}
+31 -34
View File
@@ -6,14 +6,14 @@ using Core;
using DistTestCore;
using DistTestCore.Helpers;
using DistTestCore.Logs;
using MetricsPlugin;
using Newtonsoft.Json;
using NUnit.Framework.Constraints;
namespace CodexTests
{
public class CodexDistTest : DistTest
{
private readonly Dictionary<TestLifecycle, List<ICodexNode>> onlineCodexNodes = new Dictionary<TestLifecycle, List<ICodexNode>>();
public CodexDistTest()
{
ProjectPlugin.Load<CodexPlugin.CodexPlugin>();
@@ -29,39 +29,29 @@ namespace CodexTests
localBuilder.Build();
}
protected override void LifecycleStart(TestLifecycle lifecycle)
public ICodexNode StartCodex()
{
onlineCodexNodes.Add(lifecycle, new List<ICodexNode>());
return StartCodex(s => { });
}
protected override void LifecycleStop(TestLifecycle lifecycle)
public ICodexNode StartCodex(Action<ICodexSetup> setup)
{
onlineCodexNodes.Remove(lifecycle);
return StartCodex(1, setup)[0];
}
public ICodexNode AddCodex()
public ICodexNodeGroup StartCodex(int numberOfNodes)
{
return AddCodex(s => { });
return StartCodex(numberOfNodes, s => { });
}
public ICodexNode AddCodex(Action<ICodexSetup> setup)
{
return AddCodex(1, setup)[0];
}
public ICodexNodeGroup AddCodex(int numberOfNodes)
{
return AddCodex(numberOfNodes, s => { });
}
public ICodexNodeGroup AddCodex(int numberOfNodes, Action<ICodexSetup> setup)
public ICodexNodeGroup StartCodex(int numberOfNodes, Action<ICodexSetup> setup)
{
var group = Ci.StartCodexNodes(numberOfNodes, s =>
{
setup(s);
OnCodexSetup(s);
});
onlineCodexNodes[Get()].AddRange(group);
return group;
}
@@ -75,11 +65,6 @@ namespace CodexTests
return new PeerDownloadTestHelpers(GetTestLog(), Get().GetFileManager());
}
public IEnumerable<ICodexNode> GetAllOnlineCodexNodes()
{
return onlineCodexNodes[Get()];
}
public void AssertBalance(ICodexContracts contracts, ICodexNode codexNode, Constraint constraint, string msg = "")
{
AssertHelpers.RetryAssert(constraint, () => contracts.GetTestTokenBalance(codexNode), nameof(AssertBalance) + msg);
@@ -99,17 +84,29 @@ namespace CodexTests
log.AssertLogDoesNotContain("ERR ");
}
public void LogNodeStatus(ICodexNode node, IMetricsAccess? metrics = null)
{
Log("Status for " + node.GetName() + Environment.NewLine +
GetBasicNodeStatus(node));
}
private string GetBasicNodeStatus(ICodexNode node)
{
return JsonConvert.SerializeObject(node.GetDebugInfo(), Formatting.Indented) + Environment.NewLine +
node.Space().ToString() + Environment.NewLine;
}
// Disabled for now: Makes huge log files!
//private string GetNodeMetrics(IMetricsAccess? metrics)
//{
// if (metrics == null) return "No metrics enabled";
// var m = metrics.GetAllMetrics();
// if (m == null) return "No metrics received";
// return m.AsCsv();
//}
protected virtual void OnCodexSetup(ICodexSetup setup)
{
}
protected override void CollectStatusLogData(TestLifecycle lifecycle, Dictionary<string, string> data)
{
var nodes = onlineCodexNodes[lifecycle];
var upload = nodes.Select(n => n.TransferSpeeds.GetUploadSpeed()).ToList()!.OptionalAverage();
var download = nodes.Select(n => n.TransferSpeeds.GetDownloadSpeed()).ToList()!.OptionalAverage();
if (upload != null) data.Add("avgupload", upload.ToString());
if (download != null) data.Add("avgdownload", download.ToString());
}
}
}
@@ -1,4 +1,5 @@
using CodexContractsPlugin;
using CodexPlugin;
using GethPlugin;
using NUnit.Framework;
using Utils;
@@ -11,9 +12,9 @@ namespace CodexTests.DownloadConnectivityTests
[Test]
public void MetricsDoesNotInterfereWithPeerDownload()
{
AddCodex(2, s => s.EnableMetrics());
var nodes = StartCodex(2, s => s.EnableMetrics());
AssertAllNodesConnected();
AssertAllNodesConnected(nodes);
}
[Test]
@@ -21,10 +22,10 @@ namespace CodexTests.DownloadConnectivityTests
{
var geth = Ci.StartGethNode(s => s.IsMiner());
var contracts = Ci.StartCodexContracts(geth);
AddCodex(2, s => s.EnableMarketplace(geth, contracts, m => m
.WithInitial(10.Eth(), 1000.TestTokens())));
var nodes = StartCodex(2, s => s.EnableMarketplace(geth, contracts, m => m
.WithInitial(10.Eth(), 1000.TstWei())));
AssertAllNodesConnected();
AssertAllNodesConnected(nodes);
}
[Test]
@@ -33,14 +34,14 @@ namespace CodexTests.DownloadConnectivityTests
[Values(2, 5)] int numberOfNodes,
[Values(1, 10)] int sizeMBs)
{
AddCodex(numberOfNodes);
var nodes = StartCodex(numberOfNodes);
AssertAllNodesConnected(sizeMBs);
AssertAllNodesConnected(nodes, sizeMBs);
}
private void AssertAllNodesConnected(int sizeMBs = 10)
private void AssertAllNodesConnected(IEnumerable<ICodexNode> nodes, int sizeMBs = 10)
{
CreatePeerDownloadTestHelpers().AssertFullDownloadInterconnectivity(GetAllOnlineCodexNodes(), sizeMBs.MB());
CreatePeerDownloadTestHelpers().AssertFullDownloadInterconnectivity(nodes, sizeMBs.MB());
}
}
}
@@ -1,4 +1,5 @@
using NUnit.Framework;
using CodexPlugin;
using NUnit.Framework;
namespace CodexTests.PeerDiscoveryTests
{
@@ -8,24 +9,24 @@ namespace CodexTests.PeerDiscoveryTests
[Test]
public void TwoLayersTest()
{
var root = AddCodex();
var l1Source = AddCodex(s => s.WithBootstrapNode(root));
var l1Node = AddCodex(s => s.WithBootstrapNode(root));
var l2Target = AddCodex(s => s.WithBootstrapNode(l1Node));
var root = StartCodex();
var l1Source = StartCodex(s => s.WithBootstrapNode(root));
var l1Node = StartCodex(s => s.WithBootstrapNode(root));
var l2Target = StartCodex(s => s.WithBootstrapNode(l1Node));
AssertAllNodesConnected();
AssertAllNodesConnected(root, l1Source, l1Node, l2Target);
}
[Test]
public void ThreeLayersTest()
{
var root = AddCodex();
var l1Source = AddCodex(s => s.WithBootstrapNode(root));
var l1Node = AddCodex(s => s.WithBootstrapNode(root));
var l2Node = AddCodex(s => s.WithBootstrapNode(l1Node));
var l3Target = AddCodex(s => s.WithBootstrapNode(l2Node));
var root = StartCodex();
var l1Source = StartCodex(s => s.WithBootstrapNode(root));
var l1Node = StartCodex(s => s.WithBootstrapNode(root));
var l2Node = StartCodex(s => s.WithBootstrapNode(l1Node));
var l3Target = StartCodex(s => s.WithBootstrapNode(l2Node));
AssertAllNodesConnected();
AssertAllNodesConnected(root, l1Source, l1Node, l2Node, l3Target);
}
[TestCase(3)]
@@ -33,18 +34,22 @@ namespace CodexTests.PeerDiscoveryTests
[TestCase(10)]
public void NodeChainTest(int chainLength)
{
var node = AddCodex();
var nodes = new List<ICodexNode>();
var node = StartCodex();
nodes.Add(node);
for (var i = 1; i < chainLength; i++)
{
node = AddCodex(s => s.WithBootstrapNode(node));
node = StartCodex(s => s.WithBootstrapNode(node));
nodes.Add(node);
}
AssertAllNodesConnected();
AssertAllNodesConnected(nodes.ToArray());
}
private void AssertAllNodesConnected()
private void AssertAllNodesConnected(params ICodexNode[] nodes)
{
CreatePeerConnectionTestHelpers().AssertFullyConnected(GetAllOnlineCodexNodes());
CreatePeerConnectionTestHelpers().AssertFullyConnected(nodes);
}
}
}
@@ -12,7 +12,7 @@ namespace CodexTests.PeerDiscoveryTests
public void CanReportUnknownPeerId()
{
var unknownId = "16Uiu2HAkv2CHWpff3dj5iuVNERAp8AGKGNgpGjPexJZHSqUstfsK";
var node = AddCodex();
var node = StartCodex();
var result = node.GetDebugPeer(unknownId);
Assert.That(result.IsPeerFound, Is.False);
@@ -21,9 +21,9 @@ namespace CodexTests.PeerDiscoveryTests
[Test]
public void MetricsDoesNotInterfereWithPeerDiscovery()
{
AddCodex(2, s => s.EnableMetrics());
var nodes = StartCodex(2, s => s.EnableMetrics());
AssertAllNodesConnected();
AssertAllNodesConnected(nodes);
}
[Test]
@@ -31,10 +31,10 @@ namespace CodexTests.PeerDiscoveryTests
{
var geth = Ci.StartGethNode(s => s.IsMiner());
var contracts = Ci.StartCodexContracts(geth);
AddCodex(2, s => s.EnableMarketplace(geth, contracts, m => m
.WithInitial(10.Eth(), 1000.TestTokens())));
var nodes = StartCodex(2, s => s.EnableMarketplace(geth, contracts, m => m
.WithInitial(10.Eth(), 1000.TstWei())));
AssertAllNodesConnected();
AssertAllNodesConnected(nodes);
}
[TestCase(2)]
@@ -42,16 +42,15 @@ namespace CodexTests.PeerDiscoveryTests
[TestCase(10)]
public void VariableNodes(int number)
{
AddCodex(number);
var nodes = StartCodex(number);
AssertAllNodesConnected();
AssertAllNodesConnected(nodes);
}
private void AssertAllNodesConnected()
private void AssertAllNodesConnected(IEnumerable<ICodexNode> nodes)
{
var allNodes = GetAllOnlineCodexNodes();
CreatePeerConnectionTestHelpers().AssertFullyConnected(allNodes);
CheckRoutingTable(allNodes);
CreatePeerConnectionTestHelpers().AssertFullyConnected(nodes);
CheckRoutingTable(nodes);
}
private void CheckRoutingTable(IEnumerable<ICodexNode> allNodes)
@@ -1,39 +0,0 @@
using CodexPlugin;
using DistTestCore;
using NUnit.Framework;
using Utils;
namespace CodexTests.ScalabilityTests
{
[TestFixture]
public class OneClientLargeFileTests : CodexDistTest
{
[Test]
[Combinatorial]
[UseLongTimeouts]
public void OneClientLargeFile([Values(
256,
512,
1024, // GB
2048,
4096,
8192,
16384,
32768,
65536,
131072
)] int sizeMb)
{
var testFile = GenerateTestFile(sizeMb.MB());
var node = AddCodex(s => s
.WithLogLevel(CodexLogLevel.Warn)
.WithStorageQuota((sizeMb + 10).MB())
);
var contentId = node.UploadFile(testFile);
var downloadedFile = node.DownloadContent(contentId);
testFile.AssertIsEqual(downloadedFile);
}
}
}
@@ -3,7 +3,7 @@ using Logging;
using NUnit.Framework;
using Utils;
namespace CodexTests.ScalabilityTests
namespace CodexTests.UtilityTests
{
[TestFixture]
public class ClusterDiscSpeedTests : DistTest
@@ -12,12 +12,13 @@ namespace CodexTests.ScalabilityTests
[Test]
[Combinatorial]
[Ignore("Used to measure disc io speeds in cluster.")]
public void DiscSpeedTest(
[Values(1, 10, 100, 1024, 1024 * 10, 1024 * 100, 1024 * 1024)] int bufferSizeKb
)
{
long targetSize = (long)(1024 * 1024 * 1024) * 2;
long bufferSizeBytes = ((long)bufferSizeKb) * 1024;
long bufferSizeBytes = (long)bufferSizeKb * 1024;
var filename = nameof(DiscSpeedTest);
@@ -27,7 +28,7 @@ namespace CodexTests.ScalabilityTests
var writeSpeed = PerformWrite(targetSize, bufferSizeBytes, filename);
Thread.Sleep(2000);
var readSpeed = PerformRead(targetSize, bufferSizeBytes, filename);
Log($"Write speed: {writeSpeed} per second.");
Log($"Read speed: {readSpeed} per second.");
}
@@ -5,18 +5,19 @@ using GethPlugin;
using NUnit.Framework;
using Utils;
namespace CodexTests.BasicTests
namespace CodexTests.UtilityTests
{
[TestFixture]
public class DiscordBotTests : AutoBootstrapDistTest
{
[Test]
[Ignore("Used for debugging bots")]
public void BotRewardTest()
{
var myAccount = EthAccount.GenerateNew();
var sellerInitialBalance = 234.TestTokens();
var buyerInitialBalance = 100000.TestTokens();
var sellerInitialBalance = 234.TstWei();
var buyerInitialBalance = 100000.TstWei();
var fileSize = 11.MB();
var geth = Ci.StartGethNode(s => s.IsMiner().WithName("disttest-geth"));
@@ -57,7 +58,7 @@ namespace CodexTests.BasicTests
for (var i = 0; i < numberOfHosts; i++)
{
var seller = AddCodex(s => s
var seller = StartCodex(s => s
.WithName("Seller")
.WithLogLevel(CodexLogLevel.Trace, new CodexLogCustomTopics(CodexLogLevel.Error, CodexLogLevel.Error, CodexLogLevel.Warn)
{
@@ -73,15 +74,15 @@ namespace CodexTests.BasicTests
var availability = new StorageAvailability(
totalSpace: 10.GB(),
maxDuration: TimeSpan.FromMinutes(30),
minPriceForTotalSpace: 1.TestTokens(),
maxCollateral: 20.TestTokens()
minPriceForTotalSpace: 1.TstWei(),
maxCollateral: 20.TstWei()
);
seller.Marketplace.MakeStorageAvailable(availability);
}
var testFile = GenerateTestFile(fileSize);
var buyer = AddCodex(s => s
var buyer = StartCodex(s => s
.WithName("Buyer")
.EnableMarketplace(geth, contracts, m => m
.WithAccount(myAccount)
@@ -91,8 +92,8 @@ namespace CodexTests.BasicTests
var purchase = new StoragePurchaseRequest(contentId)
{
PricePerSlotPerSecond = 2.TestTokens(),
RequiredCollateral = 10.TestTokens(),
PricePerSlotPerSecond = 2.TstWei(),
RequiredCollateral = 10.TstWei(),
MinRequiredNumberOfNodes = 5,
NodeFailureTolerance = 2,
ProofProbability = 5,
@@ -0,0 +1,56 @@
using CodexPlugin;
using NUnit.Framework;
using Utils;
namespace CodexTests.UtilityTests
{
[TestFixture]
public class LogHelperTests : AutoBootstrapDistTest
{
[Test]
[Ignore("Used to find the most common log messages.")]
public void FindMostCommonLogMessages()
{
var uploader = StartCodex(s => s.WithName("uploader").WithLogLevel(CodexLogLevel.Trace));
var downloader = StartCodex(s => s.WithName("downloader").WithLogLevel(CodexLogLevel.Trace));
var cid = uploader.UploadFile(GenerateTestFile(100.MB()));
Thread.Sleep(1000);
var logStartUtc = DateTime.UtcNow;
Thread.Sleep(1000);
downloader.DownloadContent(cid);
var map = GetLogMap(downloader, logStartUtc).OrderByDescending(p => p.Value);
Log("Downloader - Receive");
foreach (var entry in map)
{
if (entry.Value > 9)
{
Log($"'{entry.Key}' = {entry.Value}");
}
}
}
private Dictionary<string, int> GetLogMap(ICodexNode node, DateTime? startUtc = null)
{
var log = Ci.DownloadLog(node);
var map = new Dictionary<string, int>();
log.IterateLines(line =>
{
var log = CodexLogLine.Parse(line);
if (log == null) return;
if (startUtc.HasValue)
{
if (log.TimestampUtc < startUtc) return;
}
if (map.ContainsKey(log.Message)) map[log.Message] += 1;
else map.Add(log.Message, 1);
});
return map;
}
}
}
@@ -3,7 +3,7 @@ using DistTestCore;
using NUnit.Framework;
using Utils;
namespace CodexTests.BasicTests
namespace CodexTests.UtilityTests
{
// Warning!
// This is a test to check network-isolation in the test-infrastructure.
+16 -4
View File
@@ -52,7 +52,7 @@ namespace DistTestCore
{
Stopwatch.Measure(fixtureLog, "Global setup", () =>
{
globalEntryPoint.Tools.CreateWorkflow().DeleteNamespacesStartingWith(TestNamespacePrefix);
globalEntryPoint.Tools.CreateWorkflow().DeleteNamespacesStartingWith(TestNamespacePrefix, wait: true);
});
}
catch (Exception ex)
@@ -72,7 +72,8 @@ namespace DistTestCore
globalEntryPoint.Decommission(
// There shouldn't be any of either, but clean everything up regardless.
deleteKubernetesResources: true,
deleteTrackedFiles: true
deleteTrackedFiles: true,
waitTillDone: true
);
}
@@ -185,7 +186,13 @@ namespace DistTestCore
lock (lifecycleLock)
{
var testNamespace = TestNamespacePrefix + Guid.NewGuid().ToString();
var lifecycle = new TestLifecycle(fixtureLog.CreateTestLog(), configuration, GetTimeSet(), testNamespace, deployId);
var lifecycle = new TestLifecycle(
fixtureLog.CreateTestLog(),
configuration,
GetTimeSet(),
testNamespace,
deployId,
ShouldWaitForCleanup());
lifecycles.Add(testName, lifecycle);
LifecycleStart(lifecycle);
}
@@ -208,7 +215,7 @@ namespace DistTestCore
IncludeLogsOnTestFailure(lifecycle);
LifecycleStop(lifecycle);
lifecycle.DeleteAllResources();
lifecycle = null!;
lifecycles.Remove(GetCurrentTestName());
});
}
@@ -235,6 +242,11 @@ namespace DistTestCore
return new DefaultTimeSet();
}
private bool ShouldWaitForCleanup()
{
return CurrentTestMethodHasAttribute<WaitForCleanupAttribute>();
}
private bool ShouldUseLongTimeouts()
{
return CurrentTestMethodHasAttribute<UseLongTimeoutsAttribute>();
+5 -3
View File
@@ -16,7 +16,7 @@ namespace DistTestCore
private readonly List<RunningPod> runningContainers = new();
private readonly string deployId;
public TestLifecycle(TestLog log, Configuration configuration, ITimeSet timeSet, string testNamespace, string deployId)
public TestLifecycle(TestLog log, Configuration configuration, ITimeSet timeSet, string testNamespace, string deployId, bool waitForCleanup)
{
Log = log;
Configuration = configuration;
@@ -27,7 +27,7 @@ namespace DistTestCore
metadata = entryPoint.GetPluginMetadata();
CoreInterface = entryPoint.CreateInterface();
this.deployId = deployId;
WaitForCleanup = waitForCleanup;
log.WriteLogTag();
}
@@ -35,13 +35,15 @@ namespace DistTestCore
public TestLog Log { get; }
public Configuration Configuration { get; }
public ITimeSet TimeSet { get; }
public bool WaitForCleanup { get; }
public CoreInterface CoreInterface { get; }
public void DeleteAllResources()
{
entryPoint.Decommission(
deleteKubernetesResources: true,
deleteTrackedFiles: true
deleteTrackedFiles: true,
waitTillDone: WaitForCleanup
);
}
@@ -0,0 +1,15 @@
using NUnit.Framework;
namespace DistTestCore
{
/// <summary>
/// By default, test system does not wait until all resources are destroyed before starting the
/// next test. This saves a lot of time but it's not always what you want.
/// If you want to be sure the resources of your test are destroyed before the next test starts,
/// add this attribute to your test method.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class WaitForCleanupAttribute : PropertyAttribute
{
}
}
@@ -0,0 +1,46 @@
using CodexContractsPlugin;
using NUnit.Framework;
using System.Numerics;
namespace FrameworkTests.CodexContractsPlugin
{
[TestFixture]
public class TestTokenTests
{
private const decimal factor = 1000000000000000000m;
[Test]
public void RepresentsSmallAmount()
{
var t = 10.TstWei();
Assert.That(t.TstWei, Is.EqualTo(new BigInteger(10)));
Assert.That(t.Tst, Is.EqualTo(new BigInteger(0)));
Assert.That(t.ToString(), Is.EqualTo("10 TSTWEI"));
}
[Test]
public void RepresentsLargeAmount()
{
var t = 10.Tst();
var expected = new BigInteger(10 * factor);
Assert.That(t.TstWei, Is.EqualTo(expected));
Assert.That(t.Tst, Is.EqualTo(new BigInteger(10)));
Assert.That(t.ToString(), Is.EqualTo("10 TST"));
}
[Test]
public void RepresentsLongAmount()
{
var a = 10.Tst();
var b = 20.TstWei();
var t = a + b;
var expected = new BigInteger((10 * factor) + 20);
Assert.That(t.TstWei, Is.EqualTo(expected));
Assert.That(t.Tst, Is.EqualTo(new BigInteger(10)));
Assert.That(t.ToString(), Is.EqualTo("10 TST + 20 TSTWEI"));
}
}
}
@@ -16,6 +16,7 @@
<ItemGroup>
<ProjectReference Include="..\..\Framework\NethereumWorkflow\NethereumWorkflow.csproj" />
<ProjectReference Include="..\..\Framework\Utils\Utils.csproj" />
<ProjectReference Include="..\..\ProjectPlugins\CodexContractsPlugin\CodexContractsPlugin.csproj" />
</ItemGroup>
</Project>
+2 -2
View File
@@ -24,7 +24,7 @@ namespace BiblioTech
public bool IsAdminChannel(IChannel channel)
{
return channel.Name == Program.Config.AdminChannelName;
return channel.Id == Program.Config.AdminChannelId;
}
public ISocketMessageChannel GetAdminChannel()
@@ -45,7 +45,7 @@ namespace BiblioTech
private void UpdateAdminIds()
{
lastUpdate = DateTime.UtcNow;
var adminRole = guild.Roles.Single(r => r.Name == Program.Config.AdminRoleName);
var adminRole = guild.Roles.Single(r => r.Id == Program.Config.AdminRoleId);
adminIds = adminRole.Members.Select(m => m.Id).ToArray();
}
}
+1 -1
View File
@@ -22,7 +22,7 @@ namespace BiblioTech
private async Task Client_Ready()
{
var guild = client.Guilds.Single(g => g.Name == Program.Config.ServerName);
var guild = client.Guilds.Single(g => g.Id == Program.Config.ServerId);
Program.AdminChecker.SetGuild(guild);
Program.Log.Log($"Initializing for guild: '{guild.Name}'");
@@ -32,7 +32,7 @@ namespace BiblioTech.Commands
}
var eth = 0.Eth();
var testTokens = 0.TestTokens();
var testTokens = 0.TstWei();
await Task.Run(() =>
{
+2 -2
View File
@@ -51,7 +51,7 @@ namespace BiblioTech.Commands
{
if (ShouldMintTestTokens(contracts, addr))
{
var tokens = Program.Config.MintTT.TestTokens();
var tokens = Program.Config.MintTT.TstWei();
var transaction = contracts.MintTestTokens(addr, tokens);
report.Add($"Minted {tokens} {FormatTransactionLink(transaction)}");
return new Transaction<TestToken>(tokens, transaction);
@@ -77,7 +77,7 @@ namespace BiblioTech.Commands
private bool ShouldMintTestTokens(ICodexContracts contracts, EthAddress addr)
{
var testTokens = contracts.GetTestTokenBalance(addr);
return testTokens.Amount < Program.Config.MintTT;
return testTokens < Program.Config.MintTT.TstWei();
}
private bool ShouldSendEth(IGethNode gethNode, EthAddress addr)
+16 -15
View File
@@ -1,4 +1,5 @@
using ArgsUniform;
using System.Numerics;
namespace BiblioTech
{
@@ -7,32 +8,32 @@ namespace BiblioTech
[Uniform("token", "t", "TOKEN", true, "Discord Application Token")]
public string ApplicationToken { get; set; } = string.Empty;
[Uniform("server-name", "sn", "SERVERNAME", true, "Name of the Discord server")]
public string ServerName { get; set; } = string.Empty;
[Uniform("server-id", "sn", "SERVERID", true, "ID of the Discord server")]
public ulong ServerId { get; set; }
[Uniform("datapath", "dp", "DATAPATH", false, "Root path where all data files will be saved.")]
[Uniform("datapath", "dp", "DATAPATH", true, "Root path where all data files will be saved.")]
public string DataPath { get; set; } = "datapath";
[Uniform("admin-role", "a", "ADMINROLE", true, "Name of the Discord server admin role")]
public string AdminRoleName { get; set; } = string.Empty;
[Uniform("admin-role-id", "a", "ADMINROLEID", true, "ID of the Discord server admin role")]
public ulong AdminRoleId { get; set; }
[Uniform("admin-channel-name", "ac", "ADMINCHANNELNAME", true, "Name of the Discord server channel where admin commands are allowed.")]
public string AdminChannelName { get; set; } = "admin-channel";
[Uniform("admin-channel-id", "ac", "ADMINCHANNELID", true, "ID of the Discord server channel where admin commands are allowed.")]
public ulong AdminChannelId{ get; set; }
[Uniform("rewards-channel-name", "rc", "REWARDSCHANNELNAME", false, "Name of the Discord server channel where participation rewards will be announced.")]
public string RewardsChannelName { get; set; } = "";
[Uniform("rewards-channel-id", "rc", "REWARDSCHANNELID", false, "ID of the Discord server channel where participation rewards will be announced.")]
public ulong RewardsChannelId { get; set; }
[Uniform("chain-events-channel-name", "cc", "CHAINEVENTSCHANNELNAME", false, "Name of the Discord server channel where chain events will be posted.")]
public string ChainEventsChannelName { get; set; } = "";
[Uniform("chain-events-channel-id", "cc", "CHAINEVENTSCHANNELID", false, "ID of the Discord server channel where chain events will be posted.")]
public ulong ChainEventsChannelId { get; set; }
[Uniform("reward-api-port", "rp", "REWARDAPIPORT", false, "TCP listen port for the reward API.")]
[Uniform("reward-api-port", "rp", "REWARDAPIPORT", true, "TCP listen port for the reward API.")]
public int RewardApiPort { get; set; } = 31080;
[Uniform("send-eth", "se", "SENDETH", false, "Amount of Eth send by the mint command. Default: 10.")]
[Uniform("send-eth", "se", "SENDETH", true, "Amount of Eth send by the mint command.")]
public int SendEth { get; set; } = 10;
[Uniform("mint-tt", "mt", "MINTTT", false, "Amount of TestTokens minted by the mint command. Default: 1073741824")]
public int MintTT { get; set; } = 1073741824;
[Uniform("mint-tt", "mt", "MINTTT", true, "Amount of TSTWEI minted by the mint command.")]
public BigInteger MintTT { get; set; } = 1073741824;
public string EndpointsPath
{
+2
View File
@@ -21,6 +21,8 @@ namespace BiblioTech
public static Task Main(string[] args)
{
Log = new ConsoleLog();
var uniformArgs = new ArgsUniform<Configuration>(PrintHelp, args);
Config = uniformArgs.Parse();
+8 -8
View File
@@ -16,8 +16,8 @@ namespace BiblioTech.Rewards
{
this.client = client;
rewardsChannel = GetChannel(Program.Config.RewardsChannelName);
eventsChannel = GetChannel(Program.Config.ChainEventsChannelName);
rewardsChannel = GetChannel(Program.Config.RewardsChannelId);
eventsChannel = GetChannel(Program.Config.ChainEventsChannelId);
}
public async Task GiveRewards(GiveRewardsCommand rewards)
@@ -45,10 +45,10 @@ namespace BiblioTech.Rewards
await context.ProcessGiveRewardsCommand(LookUpUsers(rewards));
}
private SocketTextChannel? GetChannel(string name)
private SocketTextChannel? GetChannel(ulong id)
{
if (string.IsNullOrEmpty(name)) return null;
return GetGuild().TextChannels.SingleOrDefault(c => c.Name == name);
if (id == 0) return null;
return GetGuild().TextChannels.SingleOrDefault(c => c.Id == id);
}
private async Task ProcessChainEvents(string[] eventsOverview)
@@ -147,11 +147,11 @@ namespace BiblioTech.Rewards
private SocketGuild GetGuild()
{
var guild = client.Guilds.SingleOrDefault(g => g.Name == Program.Config.ServerName);
var guild = client.Guilds.SingleOrDefault(g => g.Id == Program.Config.ServerId);
if (guild == null)
{
throw new Exception($"Unable to find guild by name: '{Program.Config.ServerName}'. " +
$"Known guilds: [{string.Join(",", client.Guilds.Select(g => g.Name))}]");
throw new Exception($"Unable to find guild by id: '{Program.Config.ServerId}'. " +
$"Known guilds: [{string.Join(",", client.Guilds.Select(g => g.Name + " (" + g.Id + ")"))}]");
}
return guild;
}
+3 -3
View File
@@ -43,7 +43,7 @@ namespace CodexNetDeployer
{
s.EnableMarketplace(gethNode, contracts, m =>
{
m.WithInitial(100.Eth(), config.InitialTestTokens.TestTokens());
m.WithInitial(100.Eth(), config.InitialTestTokens.TstWei());
if (validatorsLeft > 0) m.AsValidator();
if (config.ShouldMakeStorageAvailable) m.AsStorageNode();
});
@@ -71,8 +71,8 @@ namespace CodexNetDeployer
var availability = new StorageAvailability(
totalSpace: config.StorageSell!.Value.MB(),
maxDuration: TimeSpan.FromSeconds(config.MaxDuration),
minPriceForTotalSpace: config.MinPrice.TestTokens(),
maxCollateral: config.MaxCollateral.TestTokens()
minPriceForTotalSpace: config.MinPrice.TstWei(),
maxCollateral: config.MaxCollateral.TstWei()
);
var response = codexNode.Marketplace.MakeStorageAvailable(availability);
+2 -2
View File
@@ -255,9 +255,9 @@ namespace CodexNetDeployer
return TimeSpan.FromSeconds(2);
}
public int HttpMaxNumberOfRetries()
public TimeSpan HttpRetryTimeout()
{
return 2;
return TimeSpan.FromSeconds(30);
}
public TimeSpan HttpCallTimeout()
@@ -0,0 +1,23 @@
using GethPlugin;
using Microsoft.AspNetCore.Mvc;
namespace KeyMaker.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class KeyController : ControllerBase
{
[HttpGet]
public KeyResponse Get()
{
var account = EthAccount.GenerateNew();
return new KeyResponse
{
Public = account.EthAddress.Address,
Private = account.PrivateKey,
Secure = "Not Secure! For demo/development purposes only!"
};
}
}
}
+18
View File
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.15" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\ProjectPlugins\GethPlugin\GethPlugin.csproj" />
</ItemGroup>
</Project>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ActiveDebugProfile>https</ActiveDebugProfile>
</PropertyGroup>
</Project>
+9
View File
@@ -0,0 +1,9 @@
namespace KeyMaker
{
public class KeyResponse
{
public string Public { get; set; } = string.Empty;
public string Private { get; set; } = string.Empty;
public string Secure { get; set; } = string.Empty;
}
}
+31
View File
@@ -0,0 +1,31 @@
var builder = WebApplication.CreateBuilder(args);
var listenPort = Environment.GetEnvironmentVariable("APIPORT");
if (string.IsNullOrEmpty(listenPort)) listenPort = "31090";
builder.WebHost.ConfigureKestrel((context, options) =>
{
options.ListenAnyIP(Convert.ToInt32(listenPort));
});
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
Console.WriteLine("KeyMaker listening on port " + listenPort);
app.Run();
@@ -0,0 +1,41 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:17248",
"sslPort": 44396
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5069",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7056;http://localhost:5069",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
+9
View File
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
+26
View File
@@ -0,0 +1,26 @@
# Variables
ARG BUILDER=mcr.microsoft.com/dotnet/sdk:7.0
ARG IMAGE=${BUILDER}
ARG APP_HOME=/app
# Build
FROM ${IMAGE} AS builder
ARG APP_HOME
WORKDIR ${APP_HOME}
COPY ./Tools/KeyMaker ./Tools/KeyMaker
COPY ./Framework ./Framework
COPY ./ProjectPlugins ./ProjectPlugins
RUN dotnet restore Tools/KeyMaker
RUN dotnet publish Tools/KeyMaker -c Release -o out
# Create
FROM ${IMAGE}
ARG APP_HOME
ENV APP_HOME=${APP_HOME}
WORKDIR ${APP_HOME}
COPY --from=builder ${APP_HOME}/out .
CMD dotnet ${APP_HOME}/KeyMaker.dll
+4 -4
View File
@@ -4,16 +4,16 @@ namespace TestNetRewarder
{
public class Configuration
{
[Uniform("datapath", "dp", "DATAPATH", false, "Root path where all data files will be saved.")]
[Uniform("datapath", "dp", "DATAPATH", true, "Root path where all data files will be saved.")]
public string DataPath { get; set; } = "datapath";
[Uniform("discordbot-host", "dh", "DISCORDBOTHOST", true, "http address of the discord bot.")]
public string DiscordHost { get; set; } = "host";
[Uniform("discordbot-port", "dp", "DISCORDBOTPORT", true, "port number of the discord bot reward API. (31080 by default)")]
[Uniform("discordbot-port", "dp", "DISCORDBOTPORT", true, "port number of the discord bot reward API.")]
public int DiscordPort { get; set; } = 31080;
[Uniform("interval-minutes", "im", "INTERVALMINUTES", false, "time in minutes between reward updates. (default 15)")]
[Uniform("interval-minutes", "im", "INTERVALMINUTES", true, "time in minutes between reward updates.")]
public int IntervalMinutes { get; set; } = 15;
[Uniform("check-history", "ch", "CHECKHISTORY", true, "Unix epoc timestamp of a moment in history on which processing begins. Required for hosting rewards. Should be 'launch of the testnet'.")]
@@ -22,7 +22,7 @@ namespace TestNetRewarder
[Uniform("market-insights", "mi", "MARKETINSIGHTS", false, "Semi-colon separated integers. Each represents a multiple of intervals, for which a market insights average will be generated.")]
public string MarketInsights { get; set; } = "1;96";
[Uniform("events-overview", "eo", "EVENTSOVERVIEW", false, "When greater than zero, chain event summary will be generated. (default 1)")]
[Uniform("events-overview", "eo", "EVENTSOVERVIEW", false, "When greater than zero, chain event summary will be generated.")]
public int CreateChainEventsOverview { get; set; } = 1;
public string LogPath
+7
View File
@@ -66,6 +66,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.editorconfig = .editorconfig
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KeyMaker", "Tools\KeyMaker\KeyMaker.csproj", "{B57A4789-D8EF-42E0-8D20-581C4057FFD3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -172,6 +174,10 @@ Global
{88C212E9-308A-46A4-BAAD-468E8EBD8EDF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{88C212E9-308A-46A4-BAAD-468E8EBD8EDF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{88C212E9-308A-46A4-BAAD-468E8EBD8EDF}.Release|Any CPU.Build.0 = Release|Any CPU
{B57A4789-D8EF-42E0-8D20-581C4057FFD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B57A4789-D8EF-42E0-8D20-581C4057FFD3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B57A4789-D8EF-42E0-8D20-581C4057FFD3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B57A4789-D8EF-42E0-8D20-581C4057FFD3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -202,6 +208,7 @@ Global
{F730DA73-1C92-4107-BCFB-D33759DAB0C3} = {81AE04BC-CBFA-4E6F-B039-8208E9AFAAE7}
{B07820C4-309F-4454-BCC1-1D4902C9C67B} = {81AE04BC-CBFA-4E6F-B039-8208E9AFAAE7}
{88C212E9-308A-46A4-BAAD-468E8EBD8EDF} = {8F1F1C2A-E313-4E0C-BE40-58FB0BA91124}
{B57A4789-D8EF-42E0-8D20-581C4057FFD3} = {7591C5B3-D86E-4AE4-8ED2-B272D17FE7E3}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {237BF0AA-9EC4-4659-AD9A-65DEB974250C}