Merge branch 'feature/continuous-testing'
This commit is contained in:
commit
4cda3ad22f
|
@ -0,0 +1,235 @@
|
|||
using System.Reflection;
|
||||
|
||||
namespace ArgsUniform
|
||||
{
|
||||
public class ArgsUniform<T>
|
||||
{
|
||||
private readonly object? defaultsProvider;
|
||||
private readonly IEnv.IEnv env;
|
||||
private readonly string[] args;
|
||||
private const int cliStart = 8;
|
||||
private const int shortStart = 38;
|
||||
private const int envStart = 48;
|
||||
private const int descStart = 80;
|
||||
|
||||
public ArgsUniform(params string[] args)
|
||||
: this(new IEnv.Env(), args)
|
||||
{
|
||||
}
|
||||
|
||||
public ArgsUniform(object defaultsProvider, params string[] args)
|
||||
: this(defaultsProvider, new IEnv.Env(), args)
|
||||
{
|
||||
}
|
||||
|
||||
public ArgsUniform(IEnv.IEnv env, params string[] args)
|
||||
: this(null!, env, args)
|
||||
{
|
||||
}
|
||||
|
||||
public ArgsUniform(object defaultsProvider, IEnv.IEnv env, params string[] args)
|
||||
{
|
||||
this.defaultsProvider = defaultsProvider;
|
||||
this.env = env;
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
public T Parse(bool printResult = false)
|
||||
{
|
||||
var result = Activator.CreateInstance<T>();
|
||||
var uniformProperties = typeof(T).GetProperties().Where(m => m.GetCustomAttributes(typeof(UniformAttribute), false).Length == 1).ToArray();
|
||||
var missingRequired = new List<PropertyInfo>();
|
||||
foreach (var uniformProperty in uniformProperties)
|
||||
{
|
||||
var attr = uniformProperty.GetCustomAttribute<UniformAttribute>();
|
||||
if (attr != null)
|
||||
{
|
||||
if (!UniformAssign(result, attr, uniformProperty) && attr.Required)
|
||||
{
|
||||
{
|
||||
missingRequired.Add(uniformProperty);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (missingRequired.Any())
|
||||
{
|
||||
PrintResults(result, uniformProperties);
|
||||
Print("");
|
||||
foreach (var missing in missingRequired)
|
||||
{
|
||||
var attr = missing.GetCustomAttribute<UniformAttribute>()!;
|
||||
var exampleArg = $"--{attr.Arg}=...";
|
||||
var exampleEnvVar = $"{attr.EnvVar}=...";
|
||||
Print($" ! Missing required input. Use argument: '{exampleArg}' or environment variable: '{exampleEnvVar}'.");
|
||||
}
|
||||
|
||||
PrintHelp();
|
||||
throw new ArgumentException("Unable to assemble all required arguments");
|
||||
}
|
||||
|
||||
if (printResult)
|
||||
{
|
||||
PrintResults(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)
|
||||
{
|
||||
var a = attr!;
|
||||
var optional = !a.Required ? " *" : "";
|
||||
PrintAligned($"--{a.Arg}=...", $"({a.ArgShort})", a.EnvVar, a.Description + optional);
|
||||
}
|
||||
Print("");
|
||||
}
|
||||
|
||||
private void Print(string msg)
|
||||
{
|
||||
Console.WriteLine(msg);
|
||||
}
|
||||
|
||||
private void PrintAligned(string cli, string s, string env, string desc)
|
||||
{
|
||||
Console.CursorLeft = cliStart;
|
||||
Console.Write(cli);
|
||||
Console.CursorLeft = shortStart;
|
||||
Console.Write(s);
|
||||
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);
|
||||
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="I-Env" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,29 @@
|
|||
namespace ArgsUniform
|
||||
{
|
||||
public class ExampleUser
|
||||
{
|
||||
public class Args
|
||||
{
|
||||
[Uniform("aaa", "a", "AAA", false, "Sets the AAA!")]
|
||||
public string Aaa { get; set; } = string.Empty;
|
||||
|
||||
[Uniform("bbb", "b", "BBB", true, "Sets that BBB")]
|
||||
public string Bbb { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class DefaultsProvider
|
||||
{
|
||||
public string Aaa { get { return "non-static operation"; } }
|
||||
}
|
||||
|
||||
public void Example()
|
||||
{
|
||||
// env var: "AAA=BBB"
|
||||
var args = "--ccc=ddd";
|
||||
|
||||
var uniform = new ArgsUniform<Args>(new DefaultsProvider(), args);
|
||||
|
||||
var aaa = uniform.Parse();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
namespace ArgsUniform
|
||||
{
|
||||
public class UniformAttribute : Attribute
|
||||
{
|
||||
public UniformAttribute(string arg, string argShort, string envVar, bool required, string description)
|
||||
{
|
||||
Arg = arg;
|
||||
ArgShort = argShort;
|
||||
EnvVar = envVar;
|
||||
Required = required;
|
||||
Description = description;
|
||||
}
|
||||
|
||||
public string Arg { get; }
|
||||
public string ArgShort { get; }
|
||||
public string EnvVar { get; }
|
||||
public bool Required { get; }
|
||||
public string Description { get; }
|
||||
}
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
namespace CodexNetDeployer
|
||||
{
|
||||
public class ArgOrVar
|
||||
{
|
||||
public static readonly ArgVar CodexImage = new ArgVar("codex-image", "CODEXIMAGE", "Docker image of Codex.");
|
||||
public static readonly ArgVar GethImage = new ArgVar("geth-image", "GETHIMAGE", "Docker image of Geth.");
|
||||
public static readonly ArgVar ContractsImage = new ArgVar("contracts-image", "CONTRACTSIMAGE", "Docker image of Codex Contracts deployer.");
|
||||
public static readonly ArgVar KubeConfigFile = new ArgVar("kube-config", "KUBECONFIG", "Path to Kubeconfig file.");
|
||||
public static readonly ArgVar KubeNamespace = new ArgVar("kube-namespace", "KUBENAMESPACE", "Kubernetes namespace to be used for deployment.");
|
||||
public static readonly ArgVar NumberOfCodexNodes = new ArgVar("nodes", "NODES", "Number of Codex nodes to be created.");
|
||||
public static readonly ArgVar NumberOfValidatorNodes = new ArgVar("validators", "VALIDATORS", "Number of Codex nodes that will be validating.");
|
||||
public static readonly ArgVar StorageQuota = new ArgVar("storage-quota", "STORAGEQUOTA", "Storage quota in megabytes used by each Codex node.");
|
||||
public static readonly ArgVar LogLevel = new ArgVar("log-level", "LOGLEVEL", "Log level used by each Codex node. [Trace, Debug*, Info, Warn, Error]");
|
||||
|
||||
private readonly string[] args;
|
||||
|
||||
public ArgOrVar(string[] args)
|
||||
{
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
public string Get(ArgVar key, string defaultValue = "")
|
||||
{
|
||||
var argKey = $"--{key.Arg}=";
|
||||
var arg = args.FirstOrDefault(a => a.StartsWith(argKey));
|
||||
if (arg != null)
|
||||
{
|
||||
return arg.Substring(argKey.Length);
|
||||
}
|
||||
|
||||
var env = Environment.GetEnvironmentVariable(key.Var);
|
||||
if (env != null)
|
||||
{
|
||||
return env;
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public int? GetInt(ArgVar key)
|
||||
{
|
||||
var str = Get(key);
|
||||
if (string.IsNullOrEmpty(str)) return null;
|
||||
if (int.TryParse(str, out int result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void PrintHelp()
|
||||
{
|
||||
var nl = Environment.NewLine;
|
||||
Console.WriteLine("CodexNetDeployer allows you to easily deploy multiple Codex nodes in a Kubernetes cluster. " +
|
||||
"The deployer will set up the required supporting services, deploy the Codex on-chain contracts, start and bootstrap the Codex instances. " +
|
||||
"All Kubernetes objects will be created in the namespace provided, allowing you to easily find, modify, and delete them afterwards." + nl);
|
||||
|
||||
Console.WriteLine("CodexNetDeployer assumes you are running this tool from *inside* the Kubernetes cluster you want to deploy to. " +
|
||||
"If you are not running this from a container inside the cluster, add the argument '--external'." + nl);
|
||||
|
||||
Console.Write("\t[ CLI argument ] or [ Environment variable ]");
|
||||
Console.CursorLeft = 70;
|
||||
Console.Write("(Description)" + nl);
|
||||
var fields = GetType().GetFields();
|
||||
foreach (var field in fields)
|
||||
{
|
||||
var value = (ArgVar)field.GetValue(null)!;
|
||||
value.PrintHelp();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ArgVar
|
||||
{
|
||||
public ArgVar(string arg, string var, string description)
|
||||
{
|
||||
Arg = arg;
|
||||
Var = var;
|
||||
Description = description;
|
||||
}
|
||||
|
||||
public string Arg { get; }
|
||||
public string Var { get; }
|
||||
public string Description { get; }
|
||||
|
||||
public void PrintHelp()
|
||||
{
|
||||
Console.Write($"\t[ --{Arg}=... ] or [ {Var}=... ]");
|
||||
Console.CursorLeft = 70;
|
||||
Console.Write(Description + Environment.NewLine);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ArgsUniform\ArgsUniform.csproj" />
|
||||
<ProjectReference Include="..\DistTestCore\DistTestCore.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -1,48 +1,47 @@
|
|||
using DistTestCore;
|
||||
using ArgsUniform;
|
||||
using DistTestCore;
|
||||
using DistTestCore.Codex;
|
||||
using DistTestCore.Marketplace;
|
||||
|
||||
namespace CodexNetDeployer
|
||||
{
|
||||
public class Configuration
|
||||
{
|
||||
public Configuration(
|
||||
string codexImage,
|
||||
string gethImage,
|
||||
string contractsImage,
|
||||
string kubeConfigFile,
|
||||
string kubeNamespace,
|
||||
int? numberOfCodexNodes,
|
||||
int? numberOfValidators,
|
||||
int? storageQuota,
|
||||
CodexLogLevel codexLogLevel,
|
||||
TestRunnerLocation runnerLocation)
|
||||
{
|
||||
CodexImage = codexImage;
|
||||
GethImage = gethImage;
|
||||
ContractsImage = contractsImage;
|
||||
KubeConfigFile = kubeConfigFile;
|
||||
KubeNamespace = kubeNamespace;
|
||||
NumberOfCodexNodes = numberOfCodexNodes;
|
||||
NumberOfValidators = numberOfValidators;
|
||||
StorageQuota = storageQuota;
|
||||
CodexLogLevel = codexLogLevel;
|
||||
RunnerLocation = runnerLocation;
|
||||
}
|
||||
[Uniform("codex-image", "ci", "CODEXIMAGE", true, "Docker image of Codex.")]
|
||||
public string CodexImage { get; set; } = string.Empty;
|
||||
|
||||
public string CodexImage { get; }
|
||||
public string GethImage { get; }
|
||||
public string ContractsImage { get; }
|
||||
public string KubeConfigFile { get; }
|
||||
public string KubeNamespace { get; }
|
||||
public int? NumberOfCodexNodes { get; }
|
||||
public int? NumberOfValidators { get; }
|
||||
public int? StorageQuota { get; }
|
||||
public CodexLogLevel CodexLogLevel { get; }
|
||||
public TestRunnerLocation RunnerLocation { get; }
|
||||
[Uniform("geth-image", "gi", "GETHIMAGE", true, "Docker image of Geth.")]
|
||||
public string GethImage { get; set; } = string.Empty;
|
||||
|
||||
public void PrintConfig()
|
||||
[Uniform("contracts-image", "oi", "CONTRACTSIMAGE", true, "Docker image of Codex Contracts.")]
|
||||
public string ContractsImage { get; set; } = string.Empty;
|
||||
|
||||
[Uniform("kube-config", "kc", "KUBECONFIG", true, "Path to Kubeconfig file.")]
|
||||
public string KubeConfigFile { get; set; } = string.Empty;
|
||||
|
||||
[Uniform("kube-namespace", "kn", "KUBENAMESPACE", true, "Kubernetes namespace to be used for deployment.")]
|
||||
public string KubeNamespace { get; set; } = string.Empty;
|
||||
|
||||
[Uniform("nodes", "n", "NODES", true, "Number of Codex nodes to be created.")]
|
||||
public int? NumberOfCodexNodes { get; set; }
|
||||
|
||||
[Uniform("validators", "v", "VALIDATORS", true, "Number of Codex nodes that will be validating.")]
|
||||
public int? NumberOfValidators { get; set; }
|
||||
|
||||
[Uniform("storage-quota", "s", "STORAGEQUOTA", true, "Storage quota in megabytes used by each Codex node.")]
|
||||
public int? StorageQuota { get; set; }
|
||||
|
||||
[Uniform("log-level", "l", "LOGLEVEL", true, "Log level used by each Codex node. [Trace, Debug*, Info, Warn, Error]")]
|
||||
public CodexLogLevel CodexLogLevel { get; set; }
|
||||
|
||||
public TestRunnerLocation RunnerLocation { get; set; } = TestRunnerLocation.InternalToCluster;
|
||||
|
||||
public class Defaults
|
||||
{
|
||||
ForEachProperty(onString: Print, onInt: Print);
|
||||
public string CodexImage { get; set; } = CodexContainerRecipe.DockerImage;
|
||||
public string GethImage { get; set; } = GethContainerRecipe.DockerImage;
|
||||
public string ContractsImage { get; set; } = CodexContractsContainerRecipe.DockerImage;
|
||||
public CodexLogLevel CodexLogLevel { get; set; } = CodexLogLevel.Debug;
|
||||
}
|
||||
|
||||
public List<string> Validate()
|
||||
|
@ -86,16 +85,5 @@ namespace CodexNetDeployer
|
|||
errors.Add($"{variable} is must be set.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void Print(string variable, string value)
|
||||
{
|
||||
Console.WriteLine($"\t{variable}: '{value}'");
|
||||
}
|
||||
|
||||
private static void Print(string variable, int? value)
|
||||
{
|
||||
if (value != null) Print(variable, value.ToString()!);
|
||||
else Print(variable, "<NONE>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using DistTestCore;
|
||||
using DistTestCore.Codex;
|
||||
using KubernetesWorkflow;
|
||||
using Logging;
|
||||
|
||||
namespace CodexNetDeployer
|
||||
{
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
using CodexNetDeployer;
|
||||
using ArgsUniform;
|
||||
using CodexNetDeployer;
|
||||
using DistTestCore;
|
||||
using DistTestCore.Codex;
|
||||
using DistTestCore.Marketplace;
|
||||
using Newtonsoft.Json;
|
||||
using Utils;
|
||||
using Configuration = CodexNetDeployer.Configuration;
|
||||
|
||||
public class Program
|
||||
|
@ -11,46 +9,29 @@ public class Program
|
|||
public static void Main(string[] args)
|
||||
{
|
||||
var nl = Environment.NewLine;
|
||||
Console.WriteLine("CodexNetDeployer" + nl + nl);
|
||||
|
||||
var argOrVar = new ArgOrVar(args);
|
||||
Console.WriteLine("CodexNetDeployer" + nl);
|
||||
|
||||
if (args.Any(a => a == "-h" || a == "--help" || a == "-?"))
|
||||
{
|
||||
argOrVar.PrintHelp();
|
||||
PrintHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
var location = TestRunnerLocation.InternalToCluster;
|
||||
var uniformArgs = new ArgsUniform<Configuration>(new Configuration.Defaults(), args);
|
||||
var config = uniformArgs.Parse(true);
|
||||
|
||||
if (args.Any(a => a == "--external"))
|
||||
{
|
||||
location = TestRunnerLocation.ExternalToCluster;
|
||||
config.RunnerLocation = TestRunnerLocation.ExternalToCluster;
|
||||
}
|
||||
|
||||
var config = new Configuration(
|
||||
codexImage: argOrVar.Get(ArgOrVar.CodexImage, CodexContainerRecipe.DockerImage),
|
||||
gethImage: argOrVar.Get(ArgOrVar.GethImage, GethContainerRecipe.DockerImage),
|
||||
contractsImage: argOrVar.Get(ArgOrVar.ContractsImage, CodexContractsContainerRecipe.DockerImage),
|
||||
kubeConfigFile: argOrVar.Get(ArgOrVar.KubeConfigFile),
|
||||
kubeNamespace: argOrVar.Get(ArgOrVar.KubeNamespace),
|
||||
numberOfCodexNodes: argOrVar.GetInt(ArgOrVar.NumberOfCodexNodes),
|
||||
numberOfValidators: argOrVar.GetInt(ArgOrVar.NumberOfValidatorNodes),
|
||||
storageQuota: argOrVar.GetInt(ArgOrVar.StorageQuota),
|
||||
codexLogLevel: ParseEnum.Parse<CodexLogLevel>(argOrVar.Get(ArgOrVar.LogLevel, nameof(CodexLogLevel.Debug))),
|
||||
runnerLocation: location
|
||||
);
|
||||
|
||||
Console.WriteLine("Using:");
|
||||
config.PrintConfig();
|
||||
Console.WriteLine(nl);
|
||||
|
||||
var errors = config.Validate();
|
||||
if (errors.Any())
|
||||
{
|
||||
Console.WriteLine($"Configuration errors: ({errors.Count})");
|
||||
foreach ( var error in errors ) Console.WriteLine("\t" + error);
|
||||
Console.WriteLine(nl);
|
||||
argOrVar.PrintHelp();
|
||||
PrintHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -63,4 +44,18 @@ public class Program
|
|||
|
||||
Console.WriteLine("Done!");
|
||||
}
|
||||
|
||||
private static void PrintHelp()
|
||||
{
|
||||
var nl = Environment.NewLine;
|
||||
Console.WriteLine("CodexNetDeployer allows you to easily deploy multiple Codex nodes in a Kubernetes cluster. " +
|
||||
"The deployer will set up the required supporting services, deploy the Codex on-chain contracts, start and bootstrap the Codex instances. " +
|
||||
"All Kubernetes objects will be created in the namespace provided, allowing you to easily find, modify, and delete them afterwards." + nl);
|
||||
|
||||
Console.WriteLine("CodexNetDeployer assumes you are running this tool from *inside* the Kubernetes cluster you want to deploy to. " +
|
||||
"If you are not running this from a container inside the cluster, add the argument '--external'." + nl);
|
||||
|
||||
var uniformArgs = new ArgsUniform<Configuration>();
|
||||
uniformArgs.PrintHelp();
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,93 +1,42 @@
|
|||
using DistTestCore.Codex;
|
||||
using ArgsUniform;
|
||||
using DistTestCore.Codex;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ContinuousTests
|
||||
{
|
||||
public class Configuration
|
||||
{
|
||||
public string LogPath { get; set; } = string.Empty;
|
||||
public string DataPath { get; set; } = string.Empty;
|
||||
[Uniform("log-path", "l", "LOGPATH", true, "Path where log files will be written.")]
|
||||
public string LogPath { get; set; } = "logs";
|
||||
|
||||
[Uniform("data-path", "d", "DATAPATH", true, "Path where temporary data files will be written.")]
|
||||
public string DataPath { get; set; } = "data";
|
||||
|
||||
[Uniform("codex-deployment", "c", "CODEXDEPLOYMENT", true, "Path to codex-deployment JSON file.")]
|
||||
public string CodexDeploymentJson { get; set; } = string.Empty;
|
||||
|
||||
[Uniform("keep", "k", "KEEP", false, "Set to '1' to retain logs of successful tests.")]
|
||||
public bool KeepPassedTestLogs { get; set; } = false;
|
||||
|
||||
[Uniform("kube-config", "kc", "KUBECONFIG", true, "Path to Kubeconfig file.")]
|
||||
public string KubeConfigFile { get; set; } = string.Empty;
|
||||
|
||||
public CodexDeployment CodexDeployment { get; set; } = null!;
|
||||
public bool KeepPassedTestLogs { get; set; }
|
||||
}
|
||||
|
||||
public class ConfigLoader
|
||||
{
|
||||
private const string filename = "config.json";
|
||||
|
||||
public Configuration Load()
|
||||
public Configuration Load(string[] args)
|
||||
{
|
||||
var config = Read();
|
||||
var uniformArgs = new ArgsUniform<Configuration>(args);
|
||||
|
||||
Validate(config);
|
||||
return config;
|
||||
var result = uniformArgs.Parse();
|
||||
|
||||
result.CodexDeployment = ParseCodexDeploymentJson(result.CodexDeploymentJson);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Configuration Read()
|
||||
{
|
||||
if (File.Exists(filename))
|
||||
{
|
||||
var lines = File.ReadAllText(filename);
|
||||
try
|
||||
{
|
||||
var result = JsonConvert.DeserializeObject<Configuration>(lines);
|
||||
if (result != null) return result;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
var logPath = Environment.GetEnvironmentVariable("LOGPATH");
|
||||
var dataPath = Environment.GetEnvironmentVariable("DATAPATH");
|
||||
var codexDeploymentJson = Environment.GetEnvironmentVariable("CODEXDEPLOYMENT");
|
||||
var keep = Environment.GetEnvironmentVariable("KEEPPASSEDTESTLOGS");
|
||||
|
||||
if (!string.IsNullOrEmpty(logPath) &&
|
||||
!string.IsNullOrEmpty(dataPath) &&
|
||||
!string.IsNullOrEmpty(codexDeploymentJson))
|
||||
{
|
||||
try
|
||||
{
|
||||
return new Configuration
|
||||
{
|
||||
LogPath = logPath,
|
||||
DataPath = dataPath,
|
||||
CodexDeployment = ParseCodexDeploymentJson(codexDeploymentJson),
|
||||
KeepPassedTestLogs = keep == "1"
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("Exception: " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
var nl = Environment.NewLine;
|
||||
throw new Exception($"Unable to load configuration from '{filename}', and " +
|
||||
"unable to load configuration from environment variables. " + nl +
|
||||
"'LOGPATH' = Path where log files will be saved." + nl +
|
||||
"'DATAPATH' = Path where temporary data files will be saved." + nl +
|
||||
"'CODEXDEPLOYMENT' = Path to codex-deployment JSON file." + nl +
|
||||
nl);
|
||||
}
|
||||
|
||||
private void Validate(Configuration configuration)
|
||||
{
|
||||
if (string.IsNullOrEmpty(configuration.LogPath))
|
||||
{
|
||||
throw new Exception($"Invalid LogPath set: '{configuration.LogPath}'");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(configuration.DataPath))
|
||||
{
|
||||
throw new Exception($"Invalid DataPath set: '{configuration.DataPath}'");
|
||||
}
|
||||
|
||||
if (configuration.CodexDeployment == null || !configuration.CodexDeployment.CodexContainers.Any())
|
||||
{
|
||||
throw new Exception("No Codex deployment found.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private CodexDeployment ParseCodexDeploymentJson(string filename)
|
||||
{
|
||||
var d = JsonConvert.DeserializeObject<CodexDeployment>(File.ReadAllText(filename))!;
|
||||
|
|
|
@ -15,21 +15,24 @@ namespace ContinuousTests
|
|||
protected const int MinuteOne = 60;
|
||||
protected const int MinuteFive = MinuteOne * 5;
|
||||
protected const int HourOne = MinuteOne * 60;
|
||||
protected const int HourThree = HourOne * 3;
|
||||
protected const int DayOne = HourOne * 24;
|
||||
protected const int DayThree = DayOne * 3;
|
||||
|
||||
private const string UploadFailedMessage = "Unable to store block";
|
||||
|
||||
public void Initialize(CodexNode[] nodes, BaseLog log, FileManager fileManager)
|
||||
public void Initialize(CodexNode[] nodes, BaseLog log, FileManager fileManager, Configuration configuration)
|
||||
{
|
||||
Nodes = nodes;
|
||||
Log = log;
|
||||
FileManager = fileManager;
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
public CodexNode[] Nodes { get; private set; } = null!;
|
||||
public BaseLog Log { get; private set; } = null!;
|
||||
public IFileManager FileManager { get; private set; } = null!;
|
||||
public Configuration Configuration { get; private set; } = null!;
|
||||
public virtual ITimeSet TimeSet { get { return new DefaultTimeSet(); } }
|
||||
|
||||
public abstract int RequiredNumberOfNodes { get; }
|
||||
|
|
|
@ -9,9 +9,9 @@ namespace ContinuousTests
|
|||
private readonly Configuration config;
|
||||
private readonly StartupChecker startupChecker;
|
||||
|
||||
public ContinuousTestRunner()
|
||||
public ContinuousTestRunner(string[] args)
|
||||
{
|
||||
config = configLoader.Load();
|
||||
config = configLoader.Load(args);
|
||||
startupChecker = new StartupChecker(config);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ArgsUniform\ArgsUniform.csproj" />
|
||||
<ProjectReference Include="..\DistTestCore\DistTestCore.csproj" />
|
||||
<ProjectReference Include="..\Logging\Logging.csproj" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -6,7 +6,7 @@ public class Program
|
|||
{
|
||||
Console.WriteLine("Codex Continous-Test-Runner.");
|
||||
Console.WriteLine("Running...");
|
||||
var runner = new ContinuousTestRunner();
|
||||
var runner = new ContinuousTestRunner(args);
|
||||
runner.Run();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -111,12 +111,12 @@ namespace ContinuousTests
|
|||
private void InitializeTest(string name)
|
||||
{
|
||||
Log($" > Running TestMoment '{name}'");
|
||||
handle.Test.Initialize(nodes, fixtureLog, fileManager);
|
||||
handle.Test.Initialize(nodes, fixtureLog, fileManager, config);
|
||||
}
|
||||
|
||||
private void DecommissionTest()
|
||||
{
|
||||
handle.Test.Initialize(null!, null!, null!);
|
||||
handle.Test.Initialize(null!, null!, null!, null!);
|
||||
}
|
||||
|
||||
private void Log(string msg)
|
||||
|
|
|
@ -1,25 +1,130 @@
|
|||
using DistTestCore.Codex;
|
||||
using DistTestCore;
|
||||
using DistTestCore.Codex;
|
||||
using DistTestCore.Marketplace;
|
||||
using KubernetesWorkflow;
|
||||
using Logging;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace ContinuousTests.Tests
|
||||
{
|
||||
//public class MarketplaceTest : ContinuousTest
|
||||
//{
|
||||
// public override int RequiredNumberOfNodes => 1;
|
||||
// public override TimeSpan RunTestEvery => TimeSpan.FromDays(1);
|
||||
// public override TestFailMode TestFailMode => TestFailMode.AlwaysRunAllMoments;
|
||||
public class MarketplaceTest : ContinuousTest
|
||||
{
|
||||
public override int RequiredNumberOfNodes => 1;
|
||||
public override TimeSpan RunTestEvery => TimeSpan.FromDays(4);
|
||||
public override TestFailMode TestFailMode => TestFailMode.AlwaysRunAllMoments;
|
||||
|
||||
// [TestMoment(t: Zero)]
|
||||
// public void NodePostsStorageRequest()
|
||||
// {
|
||||
// //var c = new KubernetesWorkflow.WorkflowCreator(Log, new KubernetesWorkflow.Configuration());
|
||||
// //var flow = c.CreateWorkflow();
|
||||
// //var rc = flow.Start(10, KubernetesWorkflow.Location.Unspecified, new CodexContainerRecipe(), new KubernetesWorkflow.StartupConfig());
|
||||
// }
|
||||
public const int EthereumAccountIndex = 200; // TODO: Check against all other account indices of all other tests.
|
||||
|
||||
// [TestMoment(t: DayThree)]
|
||||
// public void NodeDownloadsStorageRequestData()
|
||||
// {
|
||||
private const string MarketplaceTestNamespace = "codex-continuous-marketplace";
|
||||
|
||||
// }
|
||||
//}
|
||||
private readonly ByteSize fileSize = 100.MB();
|
||||
private readonly TestToken pricePerBytePerSecond = 1.TestTokens();
|
||||
|
||||
private TestFile file = null!;
|
||||
private ContentId? cid;
|
||||
private TestToken startingBalance = null!;
|
||||
private string purchaseId = string.Empty;
|
||||
|
||||
[TestMoment(t: Zero)]
|
||||
public void NodePostsStorageRequest()
|
||||
{
|
||||
var contractDuration = TimeSpan.FromDays(3) + TimeSpan.FromHours(1);
|
||||
decimal totalDurationSeconds = Convert.ToDecimal(contractDuration.TotalSeconds);
|
||||
var expectedTotalCost = pricePerBytePerSecond.Amount * totalDurationSeconds;
|
||||
|
||||
file = FileManager.GenerateTestFile(fileSize);
|
||||
|
||||
var (workflowCreator, lifecycle) = CreateFacilities();
|
||||
var flow = workflowCreator.CreateWorkflow();
|
||||
var startupConfig = new StartupConfig();
|
||||
var codexStartConfig = new CodexStartupConfig(CodexLogLevel.Debug);
|
||||
codexStartConfig.MarketplaceConfig = new MarketplaceInitialConfig(0.Eth(), 0.TestTokens(), false);
|
||||
codexStartConfig.MarketplaceConfig.AccountIndexOverride = EthereumAccountIndex;
|
||||
startupConfig.Add(codexStartConfig);
|
||||
startupConfig.Add(Configuration.CodexDeployment.GethStartResult);
|
||||
var rc = flow.Start(1, Location.Unspecified, new CodexContainerRecipe(), startupConfig);
|
||||
|
||||
try
|
||||
{
|
||||
var account = Configuration.CodexDeployment.GethStartResult.MarketplaceNetwork.Bootstrap.AllAccounts.Accounts[EthereumAccountIndex];
|
||||
var tokenAddress = Configuration.CodexDeployment.GethStartResult.MarketplaceNetwork.Marketplace.TokenAddress;
|
||||
|
||||
var interaction = Configuration.CodexDeployment.GethStartResult.MarketplaceNetwork.Bootstrap.StartInteraction(lifecycle);
|
||||
interaction.MintTestTokens(new[] { account.Account }, expectedTotalCost, tokenAddress);
|
||||
|
||||
var container = rc.Containers[0];
|
||||
var marketplaceNetwork = Configuration.CodexDeployment.GethStartResult.MarketplaceNetwork;
|
||||
var codexAccess = new CodexAccess(lifecycle, container);
|
||||
var marketAccess = new MarketplaceAccess(lifecycle, marketplaceNetwork, account, codexAccess);
|
||||
|
||||
cid = UploadFile(codexAccess.Node, file);
|
||||
Assert.That(cid, Is.Not.Null);
|
||||
|
||||
startingBalance = marketAccess.GetBalance();
|
||||
|
||||
purchaseId = marketAccess.RequestStorage(
|
||||
contentId: cid!,
|
||||
pricePerBytePerSecond: pricePerBytePerSecond,
|
||||
requiredCollateral: 100.TestTokens(),
|
||||
minRequiredNumberOfNodes: 3,
|
||||
proofProbability: 10,
|
||||
duration: contractDuration);
|
||||
|
||||
Assert.That(!string.IsNullOrEmpty(purchaseId));
|
||||
}
|
||||
finally
|
||||
{
|
||||
flow.Stop(rc);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMoment(t: DayThree)]
|
||||
public void StoredDataIsAvailableAfterThreeDays()
|
||||
{
|
||||
var (workflowCreator, lifecycle) = CreateFacilities();
|
||||
var flow = workflowCreator.CreateWorkflow();
|
||||
var startupConfig = new StartupConfig();
|
||||
var codexStartConfig = new CodexStartupConfig(CodexLogLevel.Debug);
|
||||
startupConfig.Add(codexStartConfig);
|
||||
var rc = flow.Start(1, Location.Unspecified, new CodexContainerRecipe(), startupConfig);
|
||||
|
||||
try
|
||||
{
|
||||
var container = rc.Containers[0];
|
||||
var codexAccess = new CodexAccess(lifecycle, container);
|
||||
|
||||
var result = DownloadContent(codexAccess.Node, cid!);
|
||||
|
||||
file.AssertIsEqual(result);
|
||||
}
|
||||
finally
|
||||
{
|
||||
flow.Stop(rc);
|
||||
}
|
||||
}
|
||||
|
||||
private (WorkflowCreator, TestLifecycle) CreateFacilities()
|
||||
{
|
||||
var lifecycleConfig = new DistTestCore.Configuration
|
||||
(
|
||||
kubeConfigFile: Configuration.KubeConfigFile,
|
||||
logPath: "null",
|
||||
logDebug: false,
|
||||
dataFilesPath: "notUsed",
|
||||
codexLogLevel: CodexLogLevel.Debug,
|
||||
runnerLocation: TestRunnerLocation.InternalToCluster
|
||||
);
|
||||
|
||||
var kubeConfig = new KubernetesWorkflow.Configuration(
|
||||
k8sNamespacePrefix: MarketplaceTestNamespace,
|
||||
kubeConfigFile: Configuration.KubeConfigFile,
|
||||
operationTimeout: TimeSet.K8sOperationTimeout(),
|
||||
retryDelay: TimeSet.WaitForK8sServiceDelay());
|
||||
|
||||
var workflowCreator = new WorkflowCreator(Log, kubeConfig, testNamespacePostfix: string.Empty);
|
||||
var lifecycle = new TestLifecycle(new NullLog(), lifecycleConfig, TimeSet, workflowCreator);
|
||||
|
||||
return (workflowCreator, lifecycle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
using Logging;
|
||||
|
||||
namespace CodexNetDeployer
|
||||
namespace Logging
|
||||
{
|
||||
public class NullLog : TestLog
|
||||
{
|
||||
|
@ -15,12 +13,10 @@ namespace CodexNetDeployer
|
|||
|
||||
public override void Log(string message)
|
||||
{
|
||||
//Console.WriteLine(message);
|
||||
}
|
||||
|
||||
public override void Debug(string message = "", int skipFrames = 0)
|
||||
{
|
||||
//Console.WriteLine(message);
|
||||
}
|
||||
|
||||
public override void Error(string message)
|
|
@ -19,7 +19,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NethereumWorkflow", "Nether
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ContinuousTests", "ContinuousTests\ContinuousTests.csproj", "{025B7074-0A09-4FCC-9BB9-03AE2A961EA1}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodexNetDeployer", "CodexNetDeployer\CodexNetDeployer.csproj", "{871CAF12-14BE-4509-BC6E-20FDF0B1083A}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodexNetDeployer", "CodexNetDeployer\CodexNetDeployer.csproj", "{871CAF12-14BE-4509-BC6E-20FDF0B1083A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArgsUniform", "ArgsUniform\ArgsUniform.csproj", "{634324B1-E359-42B4-A269-BDC429936B3C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
@ -63,6 +65,10 @@ Global
|
|||
{871CAF12-14BE-4509-BC6E-20FDF0B1083A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{871CAF12-14BE-4509-BC6E-20FDF0B1083A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{871CAF12-14BE-4509-BC6E-20FDF0B1083A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{634324B1-E359-42B4-A269-BDC429936B3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{634324B1-E359-42B4-A269-BDC429936B3C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{634324B1-E359-42B4-A269-BDC429936B3C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{634324B1-E359-42B4-A269-BDC429936B3C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
Loading…
Reference in New Issue