diff --git a/CodexNetDeployer/ArgOrVar.cs b/CodexNetDeployer/ArgOrVar.cs new file mode 100644 index 00000000..5844b68a --- /dev/null +++ b/CodexNetDeployer/ArgOrVar.cs @@ -0,0 +1,93 @@ +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); + } + } +} diff --git a/CodexNetDeployer/CodexNetDeployer.csproj b/CodexNetDeployer/CodexNetDeployer.csproj new file mode 100644 index 00000000..80f6f4c2 --- /dev/null +++ b/CodexNetDeployer/CodexNetDeployer.csproj @@ -0,0 +1,14 @@ + + + + Exe + net7.0 + enable + enable + + + + + + + diff --git a/CodexNetDeployer/CodexNodeStarter.cs b/CodexNetDeployer/CodexNodeStarter.cs new file mode 100644 index 00000000..ee071dba --- /dev/null +++ b/CodexNetDeployer/CodexNodeStarter.cs @@ -0,0 +1,75 @@ +using DistTestCore; +using DistTestCore.Codex; +using DistTestCore.Marketplace; +using KubernetesWorkflow; +using Logging; + +namespace CodexNetDeployer +{ + public class CodexNodeStarter + { + private readonly Configuration config; + private readonly WorkflowCreator workflowCreator; + private readonly TestLifecycle lifecycle; + private readonly BaseLog log; + private readonly ITimeSet timeSet; + private readonly GethStartResult gethResult; + private string bootstrapSpr = ""; + private int validatorsLeft; + + public CodexNodeStarter(Configuration config, WorkflowCreator workflowCreator, TestLifecycle lifecycle, BaseLog log, ITimeSet timeSet, GethStartResult gethResult, int numberOfValidators) + { + this.config = config; + this.workflowCreator = workflowCreator; + this.lifecycle = lifecycle; + this.log = log; + this.timeSet = timeSet; + this.gethResult = gethResult; + this.validatorsLeft = numberOfValidators; + } + + public RunningContainer? Start(int i) + { + Console.Write($" - {i} = "); + var workflow = workflowCreator.CreateWorkflow(); + var workflowStartup = new StartupConfig(); + workflowStartup.Add(gethResult); + workflowStartup.Add(CreateCodexStartupConfig(bootstrapSpr, i, validatorsLeft)); + + var containers = workflow.Start(1, Location.Unspecified, new CodexContainerRecipe(), workflowStartup); + + var container = containers.Containers.First(); + var address = lifecycle.Configuration.GetAddress(container); + var codexNode = new CodexNode(log, timeSet, address); + var debugInfo = codexNode.GetDebugInfo(); + + if (!string.IsNullOrWhiteSpace(debugInfo.spr)) + { + var pod = container.Pod.PodInfo; + Console.Write($"Online ({pod.Name} at {pod.Ip} on '{pod.K8SNodeName}')" + Environment.NewLine); + + if (string.IsNullOrEmpty(bootstrapSpr)) bootstrapSpr = debugInfo.spr; + validatorsLeft--; + return container; + } + else + { + Console.Write("Unknown failure." + Environment.NewLine); + return null; + } + } + + private CodexStartupConfig CreateCodexStartupConfig(string bootstrapSpr, int i, int validatorsLeft) + { + var codexStart = new CodexStartupConfig(config.CodexLogLevel); + + if (!string.IsNullOrEmpty(bootstrapSpr)) codexStart.BootstrapSpr = bootstrapSpr; + codexStart.StorageQuota = config.StorageQuota!.Value.MB(); + var marketplaceConfig = new MarketplaceInitialConfig(100000.Eth(), 0.TestTokens(), validatorsLeft > 0); + marketplaceConfig.AccountIndexOverride = i; + codexStart.MarketplaceConfig = marketplaceConfig; + + return codexStart; + } + } +} diff --git a/CodexNetDeployer/Configuration.cs b/CodexNetDeployer/Configuration.cs new file mode 100644 index 00000000..23830474 --- /dev/null +++ b/CodexNetDeployer/Configuration.cs @@ -0,0 +1,101 @@ +using DistTestCore; +using DistTestCore.Codex; + +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; + } + + 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; } + + public void PrintConfig() + { + ForEachProperty(onString: Print, onInt: Print); + } + + public List Validate() + { + var errors = new List(); + + ForEachProperty( + onString: (n, v) => StringIsSet(n, v, errors), + onInt: (n, v) => IntIsOverZero(n, v, errors)); + + if (NumberOfValidators > NumberOfCodexNodes) + { + errors.Add($"{nameof(NumberOfValidators)} ({NumberOfValidators}) may not be greater than {nameof(NumberOfCodexNodes)} ({NumberOfCodexNodes})."); + } + + return errors; + } + + private void ForEachProperty(Action onString, Action onInt) + { + var properties = GetType().GetProperties(); + foreach (var p in properties) + { + if (p.PropertyType == typeof(string)) onString(p.Name, (string)p.GetValue(this)!); + if (p.PropertyType == typeof(int?)) onInt(p.Name, (int?)p.GetValue(this)!); + } + } + + private static void IntIsOverZero(string variable, int? value, List errors) + { + if (value == null || value.Value < 1) + { + errors.Add($"{variable} is must be set and must be greater than 0."); + } + } + + private static void StringIsSet(string variable, string value, List errors) + { + if (string.IsNullOrWhiteSpace(value)) + { + 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, ""); + } + } +} diff --git a/CodexNetDeployer/Deployer.cs b/CodexNetDeployer/Deployer.cs new file mode 100644 index 00000000..a5aa56b1 --- /dev/null +++ b/CodexNetDeployer/Deployer.cs @@ -0,0 +1,80 @@ +using DistTestCore; +using DistTestCore.Codex; +using KubernetesWorkflow; + +namespace CodexNetDeployer +{ + public class Deployer + { + private readonly Configuration config; + private readonly NullLog log; + private readonly DefaultTimeSet timeset; + + public Deployer(Configuration config) + { + this.config = config; + log = new NullLog(); + timeset = new DefaultTimeSet(); + } + + public CodexDeployment Deploy() + { + Log("Initializing..."); + var (workflowCreator, lifecycle) = CreateFacilities(); + + Log("Preparing configuration..."); + // We trick the Geth companion node into unlocking all of its accounts, by saying we want to start 999 codex nodes. + var setup = new CodexSetup(999, config.CodexLogLevel); + setup.WithStorageQuota(config.StorageQuota!.Value.MB()).EnableMarketplace(0.TestTokens()); + + Log("Creating Geth instance and deploying contracts..."); + var gethStarter = new GethStarter(lifecycle, workflowCreator); + var gethResults = gethStarter.BringOnlineMarketplaceFor(setup); + + Log("Geth started. Codex contracts deployed."); + Log("Warning: It can take up to 45 minutes for the Geth node to finish unlocking all if its 1000 preconfigured accounts."); + + Log("Starting Codex nodes..."); + + // Each node must have its own IP, so it needs it own pod. Start them 1 at a time. + var codexStarter = new CodexNodeStarter(config, workflowCreator, lifecycle, log, timeset, gethResults, config.NumberOfValidators!.Value); + var codexContainers = new List(); + for (var i = 0; i < config.NumberOfCodexNodes; i++) + { + var container = codexStarter.Start(i); + if (container != null) codexContainers.Add(container); + } + + return new CodexDeployment(gethResults, codexContainers.ToArray()); + } + + private (WorkflowCreator, TestLifecycle) CreateFacilities() + { + var lifecycleConfig = new DistTestCore.Configuration + ( + kubeConfigFile: config.KubeConfigFile, + logPath: "null", + logDebug: false, + dataFilesPath: "notUsed", + codexLogLevel: config.CodexLogLevel, + runnerLocation: config.RunnerLocation + ); + + var kubeConfig = new KubernetesWorkflow.Configuration( + k8sNamespacePrefix: config.KubeNamespace, + kubeConfigFile: config.KubeConfigFile, + operationTimeout: timeset.K8sOperationTimeout(), + retryDelay: timeset.WaitForK8sServiceDelay()); + + var workflowCreator = new WorkflowCreator(log, kubeConfig); + var lifecycle = new TestLifecycle(log, lifecycleConfig, timeset, workflowCreator); + + return (workflowCreator, lifecycle); + } + + private void Log(string msg) + { + Console.WriteLine(msg); + } + } +} diff --git a/CodexNetDeployer/NullLog.cs b/CodexNetDeployer/NullLog.cs new file mode 100644 index 00000000..8417d392 --- /dev/null +++ b/CodexNetDeployer/NullLog.cs @@ -0,0 +1,43 @@ +using Logging; + +namespace CodexNetDeployer +{ + public class NullLog : TestLog + { + public NullLog() : base("NULL", false, "NULL") + { + } + + protected override LogFile CreateLogFile() + { + return null!; + } + + 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) + { + Console.WriteLine("Error: " + message); + } + + public override void MarkAsFailed() + { + } + + public override void AddStringReplace(string from, string to) + { + } + + public override void Delete() + { + } + } +} diff --git a/CodexNetDeployer/Program.cs b/CodexNetDeployer/Program.cs new file mode 100644 index 00000000..462667c6 --- /dev/null +++ b/CodexNetDeployer/Program.cs @@ -0,0 +1,66 @@ +using CodexNetDeployer; +using DistTestCore; +using DistTestCore.Codex; +using DistTestCore.Marketplace; +using Newtonsoft.Json; +using Utils; +using Configuration = CodexNetDeployer.Configuration; + +public class Program +{ + public static void Main(string[] args) + { + var nl = Environment.NewLine; + Console.WriteLine("CodexNetDeployer" + nl + nl); + + var argOrVar = new ArgOrVar(args); + + if (args.Any(a => a == "-h" || a == "--help" || a == "-?")) + { + argOrVar.PrintHelp(); + return; + } + + var location = TestRunnerLocation.InternalToCluster; + if (args.Any(a => a == "--external")) + { + location = 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(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(); + return; + } + + var deployer = new Deployer(config); + var deployment = deployer.Deploy(); + + Console.WriteLine("Writing codex-deployment.json..."); + + File.WriteAllText("codex-deployment.json", JsonConvert.SerializeObject(deployment, Formatting.Indented)); + + Console.WriteLine("Done!"); + } +} diff --git a/DistTestCore/Codex/CodexContainerRecipe.cs b/DistTestCore/Codex/CodexContainerRecipe.cs index f3279274..b7c3b946 100644 --- a/DistTestCore/Codex/CodexContainerRecipe.cs +++ b/DistTestCore/Codex/CodexContainerRecipe.cs @@ -50,7 +50,7 @@ namespace DistTestCore.Codex { var gethConfig = startupConfig.Get(); var companionNode = gethConfig.CompanionNode; - var companionNodeAccount = companionNode.Accounts[Index]; + var companionNodeAccount = companionNode.Accounts[GetAccountIndex(config.MarketplaceConfig)]; Additional(companionNodeAccount); var ip = companionNode.RunningContainer.Pod.PodInfo.Ip; @@ -60,7 +60,18 @@ namespace DistTestCore.Codex AddEnvVar("ETH_ACCOUNT", companionNodeAccount.Account); AddEnvVar("ETH_MARKETPLACE_ADDRESS", gethConfig.MarketplaceNetwork.Marketplace.Address); AddEnvVar("PERSISTENCE", "1"); + + if (config.MarketplaceConfig.IsValidator) + { + AddEnvVar("VALIDATOR", "1"); + } } } + + private int GetAccountIndex(MarketplaceInitialConfig marketplaceConfig) + { + if (marketplaceConfig.AccountIndexOverride != null) return marketplaceConfig.AccountIndexOverride.Value; + return Index; + } } } diff --git a/DistTestCore/Codex/CodexDeployment.cs b/DistTestCore/Codex/CodexDeployment.cs new file mode 100644 index 00000000..f86ad318 --- /dev/null +++ b/DistTestCore/Codex/CodexDeployment.cs @@ -0,0 +1,17 @@ +using DistTestCore.Marketplace; +using KubernetesWorkflow; + +namespace DistTestCore.Codex +{ + public class CodexDeployment + { + public CodexDeployment(GethStartResult gethStartResult, RunningContainer[] codexContainers) + { + GethStartResult = gethStartResult; + CodexContainers = codexContainers; + } + + public GethStartResult GethStartResult { get; } + public RunningContainer[] CodexContainers { get; } + } +} diff --git a/DistTestCore/CodexSetup.cs b/DistTestCore/CodexSetup.cs index 83c5b9b9..8c1a73cc 100644 --- a/DistTestCore/CodexSetup.cs +++ b/DistTestCore/CodexSetup.cs @@ -13,6 +13,7 @@ namespace DistTestCore ICodexSetup EnableMetrics(); ICodexSetup EnableMarketplace(TestToken initialBalance); ICodexSetup EnableMarketplace(TestToken initialBalance, Ether initialEther); + ICodexSetup EnableMarketplace(TestToken initialBalance, Ether initialEther, bool isValidator); } public class CodexSetup : CodexStartupConfig, ICodexSetup @@ -62,7 +63,12 @@ namespace DistTestCore public ICodexSetup EnableMarketplace(TestToken initialBalance, Ether initialEther) { - MarketplaceConfig = new MarketplaceInitialConfig(initialEther, initialBalance); + return EnableMarketplace(initialBalance, initialEther, false); + } + + public ICodexSetup EnableMarketplace(TestToken initialBalance, Ether initialEther, bool isValidator) + { + MarketplaceConfig = new MarketplaceInitialConfig(initialEther, initialBalance, isValidator); return this; } diff --git a/DistTestCore/Configuration.cs b/DistTestCore/Configuration.cs index af76127e..fceb4e09 100644 --- a/DistTestCore/Configuration.cs +++ b/DistTestCore/Configuration.cs @@ -17,10 +17,20 @@ namespace DistTestCore { kubeConfigFile = GetNullableEnvVarOrDefault("KUBECONFIG", null); logPath = GetEnvVarOrDefault("LOGPATH", "CodexTestLogs"); - logDebug = GetEnvVarOrDefault("LOGDEBUG", "false").ToLowerInvariant() == "true"; + logDebug = GetEnvVarOrDefault("LOGDEBUG", "true").ToLowerInvariant() == "true"; dataFilesPath = GetEnvVarOrDefault("DATAFILEPATH", "TestDataFiles"); - codexLogLevel = ParseEnum(GetEnvVarOrDefault("LOGLEVEL", nameof(CodexLogLevel.Trace))); - runnerLocation = ParseEnum(GetEnvVarOrDefault("RUNNERLOCATION", nameof(TestRunnerLocation.ExternalToCluster))); + codexLogLevel = ParseEnum.Parse(GetEnvVarOrDefault("LOGLEVEL", nameof(CodexLogLevel.Trace))); + runnerLocation = ParseEnum.Parse(GetEnvVarOrDefault("RUNNERLOCATION", nameof(TestRunnerLocation.ExternalToCluster))); + } + + public Configuration(string? kubeConfigFile, string logPath, bool logDebug, string dataFilesPath, CodexLogLevel codexLogLevel, TestRunnerLocation runnerLocation) + { + this.kubeConfigFile = kubeConfigFile; + this.logPath = logPath; + this.logDebug = logDebug; + this.dataFilesPath = dataFilesPath; + this.codexLogLevel = codexLogLevel; + this.runnerLocation = runnerLocation; } public KubernetesWorkflow.Configuration GetK8sConfiguration(ITimeSet timeSet) @@ -75,11 +85,6 @@ namespace DistTestCore if (v == null) return defaultValue; return v; } - - private static T ParseEnum(string value) - { - return (T)Enum.Parse(typeof(T), value, true); - } } public enum TestRunnerLocation diff --git a/DistTestCore/GethStarter.cs b/DistTestCore/GethStarter.cs index 92af53f0..3b9a9b13 100644 --- a/DistTestCore/GethStarter.cs +++ b/DistTestCore/GethStarter.cs @@ -33,6 +33,8 @@ namespace DistTestCore private void TransferInitialBalance(MarketplaceNetwork marketplaceNetwork, MarketplaceInitialConfig marketplaceConfig, GethCompanionNodeInfo companionNode) { + if (marketplaceConfig.InitialTestTokens.Amount == 0) return; + var interaction = marketplaceNetwork.StartInteraction(lifecycle); var tokenAddress = marketplaceNetwork.Marketplace.TokenAddress; diff --git a/DistTestCore/Marketplace/MarketplaceInitialConfig.cs b/DistTestCore/Marketplace/MarketplaceInitialConfig.cs index 1b66199c..c51d79f8 100644 --- a/DistTestCore/Marketplace/MarketplaceInitialConfig.cs +++ b/DistTestCore/Marketplace/MarketplaceInitialConfig.cs @@ -2,13 +2,16 @@ { public class MarketplaceInitialConfig { - public MarketplaceInitialConfig(Ether initialEth, TestToken initialTestTokens) + public MarketplaceInitialConfig(Ether initialEth, TestToken initialTestTokens, bool isValidator) { InitialEth = initialEth; InitialTestTokens = initialTestTokens; + IsValidator = isValidator; } public Ether InitialEth { get; } public TestToken InitialTestTokens { get; } + public bool IsValidator { get; } + public int? AccountIndexOverride { get; set; } } } diff --git a/DistTestCore/TestLifecycle.cs b/DistTestCore/TestLifecycle.cs index ffd34b18..667b96cf 100644 --- a/DistTestCore/TestLifecycle.cs +++ b/DistTestCore/TestLifecycle.cs @@ -7,15 +7,18 @@ namespace DistTestCore { public class TestLifecycle { - private readonly WorkflowCreator workflowCreator; private DateTime testStart = DateTime.MinValue; public TestLifecycle(TestLog log, Configuration configuration, ITimeSet timeSet) + : this(log, configuration, timeSet, new WorkflowCreator(log, configuration.GetK8sConfiguration(timeSet))) + { + } + + public TestLifecycle(TestLog log, Configuration configuration, ITimeSet timeSet, WorkflowCreator workflowCreator) { Log = log; Configuration = configuration; TimeSet = timeSet; - workflowCreator = new WorkflowCreator(log, configuration.GetK8sConfiguration(timeSet)); FileManager = new FileManager(Log, configuration); CodexStarter = new CodexStarter(this, workflowCreator); diff --git a/Logging/BaseLog.cs b/Logging/BaseLog.cs index 2c69f9c8..c3122569 100644 --- a/Logging/BaseLog.cs +++ b/Logging/BaseLog.cs @@ -25,12 +25,12 @@ namespace Logging } } - public void Log(string message) + public virtual void Log(string message) { LogFile.Write(ApplyReplacements(message)); } - public void Debug(string message = "", int skipFrames = 0) + public virtual void Debug(string message = "", int skipFrames = 0) { if (debug) { @@ -40,25 +40,25 @@ namespace Logging } } - public void Error(string message) + public virtual void Error(string message) { Log($"[ERROR] {message}"); } - public void MarkAsFailed() + public virtual void MarkAsFailed() { if (hasFailed) return; hasFailed = true; LogFile.ConcatToFilename("_FAILED"); } - public void AddStringReplace(string from, string to) + public virtual void AddStringReplace(string from, string to) { if (string.IsNullOrWhiteSpace(from)) return; replacements.Add(new BaseLogStringReplacement(from, to)); } - public void Delete() + public virtual void Delete() { File.Delete(LogFile.FullFilename); } diff --git a/Utils/ParseEnum.cs b/Utils/ParseEnum.cs new file mode 100644 index 00000000..3b3d0cb4 --- /dev/null +++ b/Utils/ParseEnum.cs @@ -0,0 +1,10 @@ +namespace Utils +{ + public static class ParseEnum + { + public static T Parse(string value) + { + return (T)Enum.Parse(typeof(T), value, true); + } + } +} diff --git a/cs-codex-dist-testing.sln b/cs-codex-dist-testing.sln index 7946ff45..cec7e4cd 100644 --- a/cs-codex-dist-testing.sln +++ b/cs-codex-dist-testing.sln @@ -17,7 +17,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Logging", "Logging\Logging. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NethereumWorkflow", "Nethereum\NethereumWorkflow.csproj", "{D6C3555E-D52D-4993-A87B-71AB650398FD}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ContinuousTests", "ContinuousTests\ContinuousTests.csproj", "{025B7074-0A09-4FCC-9BB9-03AE2A961EA1}" +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}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -57,6 +59,10 @@ Global {025B7074-0A09-4FCC-9BB9-03AE2A961EA1}.Debug|Any CPU.Build.0 = Debug|Any CPU {025B7074-0A09-4FCC-9BB9-03AE2A961EA1}.Release|Any CPU.ActiveCfg = Release|Any CPU {025B7074-0A09-4FCC-9BB9-03AE2A961EA1}.Release|Any CPU.Build.0 = Release|Any CPU + {871CAF12-14BE-4509-BC6E-20FDF0B1083A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE