Setting up Geth starters

This commit is contained in:
benbierens 2023-04-14 09:54:07 +02:00
parent 07fbda3f9a
commit 4fd00607df
No known key found for this signature in database
GPG Key ID: FE44815D96D0A1AA
20 changed files with 363 additions and 50 deletions

View File

@ -343,47 +343,7 @@ namespace CodexDistTestCore
private class CommandRunner
{
private readonly Kubernetes client;
private readonly PodInfo pod;
private readonly string containerName;
private readonly string command;
private readonly string[] arguments;
private readonly List<string> lines = new List<string>();
public CommandRunner(Kubernetes client, PodInfo pod, string containerName, string command, string[] arguments)
{
this.client = client;
this.pod = pod;
this.containerName = containerName;
this.command = command;
this.arguments = arguments;
}
public void Run()
{
var input = new[] { command }.Concat(arguments).ToArray();
Utils.Wait(client.NamespacedPodExecAsync(
pod.Name, K8sCluster.K8sNamespace, containerName, input, false, Callback, new CancellationToken()));
}
public string GetStdOut()
{
return string.Join(Environment.NewLine, lines);
}
private Task Callback(Stream stdIn, Stream stdOut, Stream stdErr)
{
using var streamReader = new StreamReader(stdOut);
var line = streamReader.ReadLine();
while (line != null)
{
lines.Add(line);
line = streamReader.ReadLine();
}
return Task.CompletedTask;
}
}
}
}

View File

@ -6,15 +6,13 @@ namespace CodexDistTestCore.Marketplace
public static class GethDockerImage
{
public const string Image = "thatbenbierens/geth-confenv:latest";
public const string AccountFilename = "account_string.txt";
public const string GenesisFilename = "genesis.json";
}
public class K8sGethBoostrapSpecs
{
public const string ContainerName = "dtest-gethb";
private const string portName = "gethb";
private const string genesisJsonBase64 = "ewogICAgImNvbmZpZyI6IHsKICAgICAgImNoYWluSWQiOiA3ODk5ODgsCiAgICAgICJob21lc3RlYWRCbG9jayI6IDAsCiAgICAgICJlaXAxNTBCbG9jayI6IDAsCiAgICAgICJlaXAxNTVCbG9jayI6IDAsCiAgICAgICJlaXAxNThCbG9jayI6IDAsCiAgICAgICJieXphbnRpdW1CbG9jayI6IDAsCiAgICAgICJjb25zdGFudGlub3BsZUJsb2NrIjogMCwKICAgICAgInBldGVyc2J1cmdCbG9jayI6IDAsCiAgICAgICJpc3RhbmJ1bEJsb2NrIjogMCwKICAgICAgIm11aXJHbGFjaWVyQmxvY2siOiAwLAogICAgICAiYmVybGluQmxvY2siOiAwLAogICAgICAibG9uZG9uQmxvY2siOiAwLAogICAgICAiYXJyb3dHbGFjaWVyQmxvY2siOiAwLAogICAgICAiZ3JheUdsYWNpZXJCbG9jayI6IDAsCiAgICAgICJjbGlxdWUiOiB7CiAgICAgICAgInBlcmlvZCI6IDUsCiAgICAgICAgImVwb2NoIjogMzAwMDAKICAgICAgfQogICAgfSwKICAgICJkaWZmaWN1bHR5IjogIjEiLAogICAgImdhc0xpbWl0IjogIjgwMDAwMDAwMCIsCiAgICAiZXh0cmFkYXRhIjogIjB4MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMEFDQ09VTlRfSEVSRTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAiLAogICAgImFsbG9jIjogewogICAgICAiMHhBQ0NPVU5UX0hFUkUiOiB7ICJiYWxhbmNlIjogIjUwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAiIH0KICAgIH0KICB9";
public K8sGethBoostrapSpecs(int servicePort)
{

View File

@ -18,6 +18,8 @@ namespace DistTestCore
public ICodexNodeGroup BringOnline(CodexSetup codexSetup)
{
var something = lifecycle.GethStarter.BringOnlineMarketplaceFor(codexSetup);
var containers = StartCodexContainers(codexSetup);
var metricAccessFactory = lifecycle.PrometheusStarter.CollectMetricsFor(codexSetup, containers);

View File

@ -1,4 +1,4 @@
using DistTestCore.CodexLogsAndMetrics;
using DistTestCore.Logs;
using DistTestCore.Metrics;
using NUnit.Framework;

View File

@ -0,0 +1,44 @@
using DistTestCore.Marketplace;
using KubernetesWorkflow;
namespace DistTestCore
{
public class GethStarter
{
private readonly TestLifecycle lifecycle;
private readonly WorkflowCreator workflowCreator;
private readonly GethBootstrapNodeStarter bootstrapNodeStarter;
private GethBootstrapNodeInfo? bootstrapNode;
public GethStarter(TestLifecycle lifecycle, WorkflowCreator workflowCreator)
{
this.lifecycle = lifecycle;
this.workflowCreator = workflowCreator;
bootstrapNodeStarter = new GethBootstrapNodeStarter(lifecycle, workflowCreator);
}
public object BringOnlineMarketplaceFor(CodexSetup codexSetup)
{
EnsureBootstrapNode();
StartCompanionNodes(codexSetup);
return null!;
}
private void EnsureBootstrapNode()
{
if (bootstrapNode != null) return;
bootstrapNode = bootstrapNodeStarter.StartGethBootstrapNode();
}
private void StartCompanionNodes(CodexSetup codexSetup)
{
throw new NotImplementedException();
}
private void Log(string msg)
{
lifecycle.Log.Log(msg);
}
}
}

View File

@ -1,7 +1,7 @@
using Logging;
using NUnit.Framework;
namespace DistTestCore.CodexLogsAndMetrics
namespace DistTestCore.Logs
{
public interface ICodexNodeLog
{

View File

@ -1,6 +1,6 @@
using NUnit.Framework;
namespace DistTestCore.CodexLogsAndMetrics
namespace DistTestCore.Logs
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class DontDownloadLogsAndMetricsOnFailureAttribute : PropertyAttribute

View File

@ -1,7 +1,7 @@
using KubernetesWorkflow;
using Logging;
namespace DistTestCore.CodexLogsAndMetrics
namespace DistTestCore.Logs
{
public class LogDownloadHandler : ILogHandler
{

View File

@ -0,0 +1,18 @@
using KubernetesWorkflow;
namespace DistTestCore.Marketplace
{
public class GethBootstrapNodeInfo
{
public GethBootstrapNodeInfo(RunningContainers runningContainers, string account, string genesisJsonBase64)
{
RunningContainers = runningContainers;
Account = account;
GenesisJsonBase64 = genesisJsonBase64;
}
public RunningContainers RunningContainers { get; }
public string Account { get; }
public string GenesisJsonBase64 { get; }
}
}

View File

@ -0,0 +1,47 @@
using KubernetesWorkflow;
namespace DistTestCore.Marketplace
{
public class GethBootstrapNodeStarter
{
private const string bootstrapGenesisJsonBase64 = "ewogICAgImNvbmZpZyI6IHsKICAgICAgImNoYWluSWQiOiA3ODk5ODgsCiAgICAgICJob21lc3RlYWRCbG9jayI6IDAsCiAgICAgICJlaXAxNTBCbG9jayI6IDAsCiAgICAgICJlaXAxNTVCbG9jayI6IDAsCiAgICAgICJlaXAxNThCbG9jayI6IDAsCiAgICAgICJieXphbnRpdW1CbG9jayI6IDAsCiAgICAgICJjb25zdGFudGlub3BsZUJsb2NrIjogMCwKICAgICAgInBldGVyc2J1cmdCbG9jayI6IDAsCiAgICAgICJpc3RhbmJ1bEJsb2NrIjogMCwKICAgICAgIm11aXJHbGFjaWVyQmxvY2siOiAwLAogICAgICAiYmVybGluQmxvY2siOiAwLAogICAgICAibG9uZG9uQmxvY2siOiAwLAogICAgICAiYXJyb3dHbGFjaWVyQmxvY2siOiAwLAogICAgICAiZ3JheUdsYWNpZXJCbG9jayI6IDAsCiAgICAgICJjbGlxdWUiOiB7CiAgICAgICAgInBlcmlvZCI6IDUsCiAgICAgICAgImVwb2NoIjogMzAwMDAKICAgICAgfQogICAgfSwKICAgICJkaWZmaWN1bHR5IjogIjEiLAogICAgImdhc0xpbWl0IjogIjgwMDAwMDAwMCIsCiAgICAiZXh0cmFkYXRhIjogIjB4MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMEFDQ09VTlRfSEVSRTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAiLAogICAgImFsbG9jIjogewogICAgICAiMHhBQ0NPVU5UX0hFUkUiOiB7ICJiYWxhbmNlIjogIjUwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAiIH0KICAgIH0KICB9";
private readonly TestLifecycle lifecycle;
private readonly WorkflowCreator workflowCreator;
public GethBootstrapNodeStarter(TestLifecycle lifecycle, WorkflowCreator workflowCreator)
{
this.lifecycle = lifecycle;
this.workflowCreator = workflowCreator;
}
public GethBootstrapNodeInfo StartGethBootstrapNode()
{
Log("Starting Geth bootstrap node...");
var startupConfig = CreateBootstrapStartupConfig();
var workflow = workflowCreator.CreateWorkflow();
var containers = workflow.Start(1, Location.Unspecified, new GethContainerRecipe(), startupConfig);
if (containers.Containers.Length != 1) throw new InvalidOperationException("Expected 1 Geth bootstrap node to be created. Test infra failure.");
var extractor = new GethInfoExtractor(workflow, containers.Containers[0]);
var account = extractor.ExtractAccount();
var genesisJsonBase64 = extractor.ExtractGenesisJsonBase64();
Log($"Geth bootstrap node started with account '{account}'");
return new GethBootstrapNodeInfo(containers, account, genesisJsonBase64);
}
private StartupConfig CreateBootstrapStartupConfig()
{
var config = new StartupConfig();
config.Add(new GethStartupConfig(true, bootstrapGenesisJsonBase64));
return config;
}
private void Log(string msg)
{
lifecycle.Log.Log(msg);
}
}
}

View File

@ -0,0 +1,16 @@
using KubernetesWorkflow;
namespace DistTestCore.Marketplace
{
public class GethCompanionNodeInfo
{
public GethCompanionNodeInfo(RunningContainer runningContainer, string account)
{
RunningContainer = runningContainer;
Account = account;
}
public RunningContainer RunningContainer { get; }
public string Account { get; }
}
}

View File

@ -0,0 +1,50 @@
using KubernetesWorkflow;
namespace DistTestCore.Marketplace
{
public class GethCompanionNodeStarter
{
private readonly TestLifecycle lifecycle;
private readonly WorkflowCreator workflowCreator;
public GethCompanionNodeStarter(TestLifecycle lifecycle, WorkflowCreator workflowCreator)
{
this.lifecycle = lifecycle;
this.workflowCreator = workflowCreator;
}
public GethCompanionNodeInfo[] StartCompanionNodesFor(CodexSetup codexSetup, GethBootstrapNodeInfo bootstrapNode)
{
Log($"Initializing companions for {codexSetup.NumberOfNodes} Codex nodes.");
var startupConfig = CreateCompanionNodeStartupConfig(bootstrapNode);
var workflow = workflowCreator.CreateWorkflow();
var containers = workflow.Start(codexSetup.NumberOfNodes, Location.Unspecified, new GethContainerRecipe(), startupConfig);
if (containers.Containers.Length != codexSetup.NumberOfNodes) throw new InvalidOperationException("Expected a Geth companion node to be created for each Codex node. Test infra failure.");
Log("Initialized companion nodes.");
return containers.Containers.Select(c => CreateCompanionInfo(workflow, c)).ToArray();
}
private GethCompanionNodeInfo CreateCompanionInfo(StartupWorkflow workflow, RunningContainer container)
{
var extractor = new GethInfoExtractor(workflow, container);
var account = extractor.ExtractAccount();
return new GethCompanionNodeInfo(container, account);
}
private StartupConfig CreateCompanionNodeStartupConfig(GethBootstrapNodeInfo bootstrapNode)
{
var config = new StartupConfig();
config.Add(new GethStartupConfig(false, bootstrapNode.GenesisJsonBase64));
return config;
}
private void Log(string msg)
{
lifecycle.Log.Log(msg);
}
}
}

View File

@ -0,0 +1,37 @@
using KubernetesWorkflow;
namespace DistTestCore.Marketplace
{
public class GethContainerRecipe : ContainerRecipeFactory
{
protected override string Image => "thatbenbierens/geth-confenv:latest";
public const string AccountFilename = "account_string.txt";
public const string GenesisFilename = "genesis.json";
protected override void Initialize(StartupConfig startupConfig)
{
var config = startupConfig.Get<GethStartupConfig>();
var args = CreateArgs(config);
AddEnvVar("GETH_ARGS", args);
AddEnvVar("GENESIS_JSON", config.GenesisJsonBase64);
}
private string CreateArgs(GethStartupConfig config)
{
if (config.IsBootstrapNode)
{
AddEnvVar("IS_BOOTSTRAP", "1");
var exposedPort = AddExposedPort();
return $"--http.port {exposedPort.Number}";
}
var port = AddInternalPort();
var discovery = AddInternalPort();
var authRpc = AddInternalPort();
var httpPort = AddInternalPort();
return $"--port {port.Number} --discovery.port {discovery.Number} --authrpc.port {authRpc.Number} --http.port {httpPort.Number}";
}
}
}

View File

@ -0,0 +1,58 @@
using KubernetesWorkflow;
using System.Text;
namespace DistTestCore.Marketplace
{
public class GethInfoExtractor
{
private readonly StartupWorkflow workflow;
private readonly RunningContainer container;
public GethInfoExtractor(StartupWorkflow workflow, RunningContainer container)
{
this.workflow = workflow;
this.container = container;
}
public string ExtractAccount()
{
var account = Retry(FetchAccount);
if (string.IsNullOrEmpty(account)) throw new InvalidOperationException("Unable to fetch account for geth node. Test infra failure.");
return account;
}
public string ExtractGenesisJsonBase64()
{
var genesisJson = Retry(FetchGenesisJson);
if (string.IsNullOrEmpty(genesisJson)) throw new InvalidOperationException("Unable to fetch genesis-json for geth node. Test infra failure.");
var encoded = Convert.ToBase64String(Encoding.ASCII.GetBytes(genesisJson));
return encoded;
}
private string Retry(Func<string> fetch)
{
var result = fetch();
if (string.IsNullOrEmpty(result))
{
Thread.Sleep(TimeSpan.FromSeconds(5));
result = fetch();
}
return result;
}
private string FetchGenesisJson()
{
return workflow.ExecuteCommand(container, "cat", GethContainerRecipe.GenesisFilename);
}
private string FetchAccount()
{
return workflow.ExecuteCommand(container, "cat", GethContainerRecipe.AccountFilename);
}
}
}

View File

@ -0,0 +1,14 @@
namespace DistTestCore.Marketplace
{
public class GethStartupConfig
{
public GethStartupConfig(bool isBootstrapNode, string genesisJsonBase64)
{
IsBootstrapNode = isBootstrapNode;
GenesisJsonBase64 = genesisJsonBase64;
}
public bool IsBootstrapNode { get; }
public string GenesisJsonBase64 { get; }
}
}

View File

@ -1,5 +1,5 @@
using DistTestCore.Codex;
using DistTestCore.CodexLogsAndMetrics;
using DistTestCore.Logs;
using DistTestCore.Metrics;
using NUnit.Framework;

View File

@ -1,4 +1,4 @@
using DistTestCore.CodexLogsAndMetrics;
using DistTestCore.Logs;
using KubernetesWorkflow;
using Logging;
@ -16,12 +16,14 @@ namespace DistTestCore
FileManager = new FileManager(Log, configuration);
CodexStarter = new CodexStarter(this, workflowCreator);
PrometheusStarter = new PrometheusStarter(this, workflowCreator);
GethStarter = new GethStarter(this, workflowCreator);
}
public TestLog Log { get; }
public FileManager FileManager { get; }
public CodexStarter CodexStarter { get; }
public PrometheusStarter PrometheusStarter { get; }
public GethStarter GethStarter { get; }
public void DeleteAllResources()
{

View File

@ -0,0 +1,52 @@
using k8s;
using Utils;
namespace KubernetesWorkflow
{
public class CommandRunner
{
private readonly Kubernetes client;
private readonly string k8sNamespace;
private readonly RunningPod pod;
private readonly string containerName;
private readonly string command;
private readonly string[] arguments;
private readonly List<string> lines = new List<string>();
public CommandRunner(Kubernetes client, string k8sNamespace, RunningPod pod, string containerName, string command, string[] arguments)
{
this.client = client;
this.k8sNamespace = k8sNamespace;
this.pod = pod;
this.containerName = containerName;
this.command = command;
this.arguments = arguments;
}
public void Run()
{
var input = new[] { command }.Concat(arguments).ToArray();
Time.Wait(client.NamespacedPodExecAsync(
pod.Name, k8sNamespace, containerName, input, false, Callback, new CancellationToken()));
}
public string GetStdOut()
{
return string.Join(Environment.NewLine, lines);
}
private Task Callback(Stream stdIn, Stream stdOut, Stream stdErr)
{
using var streamReader = new StreamReader(stdOut);
var line = streamReader.ReadLine();
while (line != null)
{
lines.Add(line);
line = streamReader.ReadLine();
}
return Task.CompletedTask;
}
}
}

View File

@ -49,6 +49,13 @@ namespace KubernetesWorkflow
logHandler.Log(stream);
}
public string ExecuteCommand(RunningPod pod, string containerName, string command, params string[] args)
{
var runner = new CommandRunner(client, K8sNamespace, pod, containerName, command, args);
runner.Run();
return runner.GetStdOut();
}
public void DeleteAllResources()
{
DeleteNamespace();

View File

@ -42,6 +42,14 @@
});
}
public string ExecuteCommand(RunningContainer container, string command, params string[] args)
{
return K8s(controller =>
{
return controller.ExecuteCommand(container.Pod, container.Recipe.Name, command, args);
});
}
public void DeleteAllResources()
{
K8s(controller =>