mirror of
https://github.com/logos-storage/logos-storage-nim-cs-dist-tests.git
synced 2026-01-08 08:23:06 +00:00
Merge branch 'feature/automatic-contracts-image-version-detection'
This commit is contained in:
commit
a77bbbdaaf
@ -1,6 +1,6 @@
|
||||
namespace Core
|
||||
{
|
||||
internal class PluginManager
|
||||
internal class PluginManager : IPluginAccess
|
||||
{
|
||||
private readonly List<PluginToolsPair> pairs = new List<PluginToolsPair>();
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
|
||||
ApplyLogPrefix(plugin, tools);
|
||||
}
|
||||
AwakePlugins();
|
||||
}
|
||||
|
||||
internal void AnnouncePlugins()
|
||||
@ -43,7 +44,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
internal T GetPlugin<T>() where T : IProjectPlugin
|
||||
public T GetPlugin<T>() where T : IProjectPlugin
|
||||
{
|
||||
return (T)pairs.Single(p => p.Plugin.GetType() == typeof(T)).Plugin;
|
||||
}
|
||||
@ -55,6 +56,14 @@
|
||||
return plugin;
|
||||
}
|
||||
|
||||
private void AwakePlugins()
|
||||
{
|
||||
foreach (var p in pairs)
|
||||
{
|
||||
p.Plugin.Awake(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyLogPrefix(IProjectPlugin plugin, PluginTools tools)
|
||||
{
|
||||
if (plugin is IHasLogPrefix hasLogPrefix)
|
||||
|
||||
@ -4,6 +4,7 @@ namespace Core
|
||||
{
|
||||
public interface IProjectPlugin
|
||||
{
|
||||
void Awake(IPluginAccess access);
|
||||
void Announce();
|
||||
void Decommission();
|
||||
}
|
||||
@ -18,6 +19,11 @@ namespace Core
|
||||
void AddMetadata(IAddMetadata metadata);
|
||||
}
|
||||
|
||||
public interface IPluginAccess
|
||||
{
|
||||
T GetPlugin<T>() where T : IProjectPlugin;
|
||||
}
|
||||
|
||||
public static class ProjectPlugin
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@ -6,13 +6,17 @@ namespace CodexContractsPlugin
|
||||
{
|
||||
public class CodexContractsContainerRecipe : ContainerRecipeFactory
|
||||
{
|
||||
public static string DockerImage { get; } = "codexstorage/codex-contracts-eth:latest-dist-tests";
|
||||
|
||||
public const string MarketplaceAddressFilename = "/hardhat/deployments/codexdisttestnetwork/Marketplace.json";
|
||||
public const string MarketplaceArtifactFilename = "/hardhat/artifacts/contracts/Marketplace.sol/Marketplace.json";
|
||||
private readonly VersionRegistry versionRegistry;
|
||||
|
||||
public override string AppName => "codex-contracts";
|
||||
public override string Image => DockerImage;
|
||||
public override string Image => versionRegistry.GetContractsDockerImage();
|
||||
|
||||
public CodexContractsContainerRecipe(VersionRegistry versionRegistry)
|
||||
{
|
||||
this.versionRegistry = versionRegistry;
|
||||
}
|
||||
|
||||
protected override void Initialize(StartupConfig startupConfig)
|
||||
{
|
||||
|
||||
@ -7,15 +7,23 @@ namespace CodexContractsPlugin
|
||||
{
|
||||
private readonly IPluginTools tools;
|
||||
private readonly CodexContractsStarter starter;
|
||||
private readonly VersionRegistry versionRegistry;
|
||||
private readonly CodexContractsContainerRecipe recipe;
|
||||
|
||||
public CodexContractsPlugin(IPluginTools tools)
|
||||
{
|
||||
this.tools = tools;
|
||||
starter = new CodexContractsStarter(tools);
|
||||
versionRegistry = new VersionRegistry(tools.GetLog());
|
||||
recipe = new CodexContractsContainerRecipe(versionRegistry);
|
||||
starter = new CodexContractsStarter(tools, recipe);
|
||||
}
|
||||
|
||||
public string LogPrefix => "(CodexContracts) ";
|
||||
|
||||
public void Awake(IPluginAccess access)
|
||||
{
|
||||
}
|
||||
|
||||
public void Announce()
|
||||
{
|
||||
tools.GetLog().Log($"Loaded Codex-Marketplace SmartContracts");
|
||||
@ -23,7 +31,7 @@ namespace CodexContractsPlugin
|
||||
|
||||
public void AddMetadata(IAddMetadata metadata)
|
||||
{
|
||||
metadata.Add("codexcontractsid", CodexContractsContainerRecipe.DockerImage);
|
||||
metadata.Add("codexcontractsid", recipe.Image);
|
||||
}
|
||||
|
||||
public void Decommission()
|
||||
@ -40,5 +48,10 @@ namespace CodexContractsPlugin
|
||||
deployment = SerializeGate.Gate(deployment);
|
||||
return starter.Wrap(gethNode, deployment);
|
||||
}
|
||||
|
||||
public void SetCodexDockerImageProvider(ICodexDockerImageProvider provider)
|
||||
{
|
||||
versionRegistry.SetProvider(provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,10 +12,12 @@ namespace CodexContractsPlugin
|
||||
public class CodexContractsStarter
|
||||
{
|
||||
private readonly IPluginTools tools;
|
||||
private readonly CodexContractsContainerRecipe recipe;
|
||||
|
||||
public CodexContractsStarter(IPluginTools tools)
|
||||
public CodexContractsStarter(IPluginTools tools, CodexContractsContainerRecipe recipe)
|
||||
{
|
||||
this.tools = tools;
|
||||
this.recipe = recipe;
|
||||
}
|
||||
|
||||
public CodexContractsDeployment Deploy(CoreInterface ci, IGethNode gethNode)
|
||||
@ -26,7 +28,7 @@ namespace CodexContractsPlugin
|
||||
var startupConfig = CreateStartupConfig(gethNode);
|
||||
startupConfig.NameOverride = "codex-contracts";
|
||||
|
||||
var containers = workflow.Start(1, new CodexContractsContainerRecipe(), startupConfig).WaitForOnline();
|
||||
var containers = workflow.Start(1, recipe, startupConfig).WaitForOnline();
|
||||
if (containers.Containers.Length != 1) throw new InvalidOperationException("Expected 1 Codex contracts container to be created. Test infra failure.");
|
||||
var container = containers.Containers[0];
|
||||
|
||||
|
||||
@ -21,6 +21,11 @@ namespace CodexContractsPlugin
|
||||
return WrapCodexContractsDeployment(ci, gethNode, deployment);
|
||||
}
|
||||
|
||||
public static void SetCodexDockerImageProvider(this CoreInterface ci, ICodexDockerImageProvider provider)
|
||||
{
|
||||
Plugin(ci).SetCodexDockerImageProvider(provider);
|
||||
}
|
||||
|
||||
private static CodexContractsPlugin Plugin(CoreInterface ci)
|
||||
{
|
||||
return ci.GetPlugin<CodexContractsPlugin>();
|
||||
|
||||
File diff suppressed because one or more lines are too long
125
ProjectPlugins/CodexContractsPlugin/VersionRegistry.cs
Normal file
125
ProjectPlugins/CodexContractsPlugin/VersionRegistry.cs
Normal file
@ -0,0 +1,125 @@
|
||||
using System.Diagnostics;
|
||||
using Logging;
|
||||
|
||||
namespace CodexContractsPlugin
|
||||
{
|
||||
public interface ICodexDockerImageProvider
|
||||
{
|
||||
string GetCodexDockerImage();
|
||||
}
|
||||
|
||||
public class VersionRegistry
|
||||
{
|
||||
private ICodexDockerImageProvider provider = new ExceptionProvider();
|
||||
private static readonly Dictionary<string, string> cache = new Dictionary<string, string>();
|
||||
private static readonly object cacheLock = new object();
|
||||
private readonly ILog log;
|
||||
|
||||
public VersionRegistry(ILog log)
|
||||
{
|
||||
this.log = log;
|
||||
}
|
||||
|
||||
public void SetProvider(ICodexDockerImageProvider provider)
|
||||
{
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
public string GetContractsDockerImage()
|
||||
{
|
||||
try
|
||||
{
|
||||
var codexImage = provider.GetCodexDockerImage();
|
||||
return GetContractsDockerImage(codexImage);
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
throw new Exception("Failed to get contracts docker image.", exc);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetContractsDockerImage(string codexImage)
|
||||
{
|
||||
lock (cacheLock)
|
||||
{
|
||||
if (cache.TryGetValue(codexImage, out string? value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
var result = GetContractsImage(codexImage);
|
||||
cache.Add(codexImage, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetContractsImage(string codexImage)
|
||||
{
|
||||
var inspectResult = InspectCodexImage(codexImage);
|
||||
var image = ParseCodexContractsImageName(inspectResult);
|
||||
log.Log($"From codex docker image '{codexImage}', determined codex-contracts docker image: '{image}'");
|
||||
return image;
|
||||
}
|
||||
|
||||
private string InspectCodexImage(string img)
|
||||
{
|
||||
Execute("docker", $"pull {img}");
|
||||
return Execute("docker", $"inspect {img}");
|
||||
}
|
||||
|
||||
private string ParseCodexContractsImageName(string inspectResult)
|
||||
{
|
||||
// It is a nice json structure. But we only need this one line.
|
||||
// "storage.codex.nim-codex.blockchain-image": "codexstorage/codex-contracts-eth:sha-0bf1385-dist-tests"
|
||||
var lines = inspectResult.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
var line = lines.Single(l => l.Contains("storage.codex.nim-codex.blockchain-image"));
|
||||
var tokens = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
return tokens.Last().Replace("\"", "").Trim();
|
||||
}
|
||||
|
||||
private string Execute(string cmd, string args)
|
||||
{
|
||||
var startInfo = new ProcessStartInfo(
|
||||
fileName: cmd,
|
||||
arguments: args
|
||||
);
|
||||
startInfo.RedirectStandardOutput = true;
|
||||
startInfo.RedirectStandardError = true;
|
||||
|
||||
var process = Process.Start(startInfo);
|
||||
if (process == null)
|
||||
{
|
||||
throw new Exception("Failed to start: " + cmd + args);
|
||||
}
|
||||
KillAfterTimeout(process);
|
||||
|
||||
process.WaitForExit();
|
||||
return process.StandardOutput.ReadToEnd();
|
||||
}
|
||||
|
||||
private void KillAfterTimeout(Process process)
|
||||
{
|
||||
// There's a known issue that some docker commands on some platforms
|
||||
// will fail to stop on their own. This has been known since 2019 and it's not fixed.
|
||||
// So we will issue a kill to the process ourselves if it exceeds a timeout.
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
Thread.Sleep(TimeSpan.FromSeconds(30.0));
|
||||
|
||||
if (process != null && !process.HasExited)
|
||||
{
|
||||
process.Kill();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
internal class ExceptionProvider : ICodexDockerImageProvider
|
||||
{
|
||||
public string GetCodexDockerImage()
|
||||
{
|
||||
throw new InvalidOperationException("CodexContractsPlugin has not yet received a CodexDockerImageProvider " +
|
||||
"and so cannot select a compatible contracts docker image.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -17,6 +17,10 @@ namespace CodexDiscordBotPlugin
|
||||
|
||||
public string LogPrefix => "(DiscordBot) ";
|
||||
|
||||
public void Awake(IPluginAccess access)
|
||||
{
|
||||
}
|
||||
|
||||
public void Announce()
|
||||
{
|
||||
tools.GetLog().Log($"Codex DiscordBot (BiblioTech) loaded.");
|
||||
|
||||
@ -7,7 +7,6 @@ namespace CodexPlugin
|
||||
{
|
||||
public class CodexContainerRecipe : ContainerRecipeFactory
|
||||
{
|
||||
private const string DefaultDockerImage = "codexstorage/nim-codex:latest-dist-tests";
|
||||
public const string ApiPortTag = "codex_api_port";
|
||||
public const string ListenPortTag = "codex_listen_port";
|
||||
public const string MetricsPortTag = "codex_metrics_port";
|
||||
@ -16,11 +15,15 @@ namespace CodexPlugin
|
||||
// Used by tests for time-constraint assertions.
|
||||
public static readonly TimeSpan MaxUploadTimePerMegabyte = TimeSpan.FromSeconds(2.0);
|
||||
public static readonly TimeSpan MaxDownloadTimePerMegabyte = TimeSpan.FromSeconds(2.0);
|
||||
private readonly CodexDockerImage codexDockerImage;
|
||||
|
||||
public override string AppName => "codex";
|
||||
public override string Image => GetDockerImage();
|
||||
public override string Image => codexDockerImage.GetCodexDockerImage();
|
||||
|
||||
public static string DockerImageOverride { get; set; } = string.Empty;
|
||||
public CodexContainerRecipe(CodexDockerImage codexDockerImage)
|
||||
{
|
||||
this.codexDockerImage = codexDockerImage;
|
||||
}
|
||||
|
||||
protected override void Initialize(StartupConfig startupConfig)
|
||||
{
|
||||
@ -163,13 +166,5 @@ namespace CodexPlugin
|
||||
// Default Codex quota: 8 Gb, using +20% to be safe.
|
||||
return 8.GB().Multiply(1.2);
|
||||
}
|
||||
|
||||
private string GetDockerImage()
|
||||
{
|
||||
var image = Environment.GetEnvironmentVariable("CODEXDOCKERIMAGE");
|
||||
if (!string.IsNullOrEmpty(image)) return image;
|
||||
if (!string.IsNullOrEmpty(DockerImageOverride)) return DockerImageOverride;
|
||||
return DefaultDockerImage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
20
ProjectPlugins/CodexPlugin/CodexDockerImage.cs
Normal file
20
ProjectPlugins/CodexPlugin/CodexDockerImage.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using CodexContractsPlugin;
|
||||
|
||||
namespace CodexPlugin
|
||||
{
|
||||
public class CodexDockerImage : ICodexDockerImageProvider
|
||||
{
|
||||
private const string DefaultDockerImage = "codexstorage/nim-codex:sha-c9a5ef8-dist-tests";
|
||||
//"codexstorage/nim-codex:latest-dist-tests";
|
||||
|
||||
public static string Override { get; set; } = string.Empty;
|
||||
|
||||
public string GetCodexDockerImage()
|
||||
{
|
||||
var image = Environment.GetEnvironmentVariable("CODEXDOCKERIMAGE");
|
||||
if (!string.IsNullOrEmpty(image)) return image;
|
||||
if (!string.IsNullOrEmpty(Override)) return Override;
|
||||
return DefaultDockerImage;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -13,12 +13,15 @@ namespace CodexPlugin
|
||||
private readonly CodexLogLevel defaultLogLevel = CodexLogLevel.Trace;
|
||||
private readonly CodexHooksFactory hooksFactory = new CodexHooksFactory();
|
||||
private readonly ProcessControlMap processControlMap = new ProcessControlMap();
|
||||
private readonly CodexDockerImage codexDockerImage = new CodexDockerImage();
|
||||
private readonly CodexContainerRecipe recipe;
|
||||
private readonly CodexWrapper codexWrapper;
|
||||
|
||||
public CodexPlugin(IPluginTools tools)
|
||||
{
|
||||
this.tools = tools;
|
||||
|
||||
recipe = new CodexContainerRecipe(codexDockerImage);
|
||||
codexStarter = CreateCodexStarter();
|
||||
codexWrapper = new CodexWrapper(tools, processControlMap, hooksFactory);
|
||||
}
|
||||
@ -28,7 +31,7 @@ namespace CodexPlugin
|
||||
if (UseContainers)
|
||||
{
|
||||
Log("Using Containerized Codex instances");
|
||||
return new ContainerCodexStarter(tools, processControlMap);
|
||||
return new ContainerCodexStarter(tools, recipe, processControlMap);
|
||||
}
|
||||
|
||||
Log("Using Binary Codex instances");
|
||||
@ -37,8 +40,15 @@ namespace CodexPlugin
|
||||
|
||||
public string LogPrefix => "(Codex) ";
|
||||
|
||||
public void Awake(IPluginAccess access)
|
||||
{
|
||||
access.GetPlugin<CodexContractsPlugin.CodexContractsPlugin>().SetCodexDockerImageProvider(codexDockerImage);
|
||||
}
|
||||
|
||||
public void Announce()
|
||||
{
|
||||
// give codex docker image to contracts plugin.
|
||||
|
||||
Log($"Loaded with Codex ID: '{codexWrapper.GetCodexId()}' - Revision: {codexWrapper.GetCodexRevision()}");
|
||||
}
|
||||
|
||||
|
||||
@ -10,12 +10,13 @@ namespace CodexPlugin
|
||||
{
|
||||
private readonly IPluginTools pluginTools;
|
||||
private readonly ProcessControlMap processControlMap;
|
||||
private readonly CodexContainerRecipe recipe = new CodexContainerRecipe();
|
||||
private readonly CodexContainerRecipe recipe;
|
||||
private readonly ApiChecker apiChecker;
|
||||
|
||||
public ContainerCodexStarter(IPluginTools pluginTools, ProcessControlMap processControlMap)
|
||||
public ContainerCodexStarter(IPluginTools pluginTools, CodexContainerRecipe recipe, ProcessControlMap processControlMap)
|
||||
{
|
||||
this.pluginTools = pluginTools;
|
||||
this.recipe = recipe;
|
||||
this.processControlMap = processControlMap;
|
||||
apiChecker = new ApiChecker(pluginTools);
|
||||
}
|
||||
|
||||
@ -40,7 +40,7 @@ namespace CodexNetDeployer
|
||||
Log($"Codex docker image will be built in path '{repoPath}'.");
|
||||
Log("Please note this can take several minutes. If you're not trying to use a Codex image with local code changes,");
|
||||
Log("Consider using the default test image or consider setting the 'CODEXDOCKERIMAGE' environment variable to use an already built image.");
|
||||
CodexContainerRecipe.DockerImageOverride = $"Using docker image locally built in path '{repoPath}'.";
|
||||
CodexDockerImage.Override = $"Using docker image locally built in path '{repoPath}'.";
|
||||
}
|
||||
|
||||
public void Build()
|
||||
@ -62,7 +62,7 @@ namespace CodexNetDeployer
|
||||
|
||||
Docker("push", customImage);
|
||||
|
||||
CodexContainerRecipe.DockerImageOverride = customImage;
|
||||
CodexDockerImage.Override = customImage;
|
||||
Log("Image pushed. Good to go!");
|
||||
}
|
||||
|
||||
|
||||
@ -13,6 +13,10 @@ namespace DeployAndRunPlugin
|
||||
this.tools = tools;
|
||||
}
|
||||
|
||||
public void Awake(IPluginAccess access)
|
||||
{
|
||||
}
|
||||
|
||||
public void Announce()
|
||||
{
|
||||
tools.GetLog().Log("Deploy-and-Run plugin loaded.");
|
||||
|
||||
@ -16,6 +16,10 @@ namespace GethPlugin
|
||||
|
||||
public string LogPrefix => "(Geth) ";
|
||||
|
||||
public void Awake(IPluginAccess access)
|
||||
{
|
||||
}
|
||||
|
||||
public void Announce()
|
||||
{
|
||||
tools.GetLog().Log($"Loaded Geth plugin.");
|
||||
|
||||
@ -18,6 +18,10 @@ namespace MetricsPlugin
|
||||
|
||||
public string LogPrefix => "(Metrics) ";
|
||||
|
||||
public void Awake(IPluginAccess access)
|
||||
{
|
||||
}
|
||||
|
||||
public void Announce()
|
||||
{
|
||||
tools.GetLog().Log($"Prometheus plugin loaded with '{starter.GetPrometheusId()}'.");
|
||||
|
||||
@ -33,7 +33,7 @@ namespace ContinuousTests
|
||||
var entryPoint = CreateEntryPoint();
|
||||
// We have to be sure that the transient node we start is using the same image as whatever's already in the deployed network.
|
||||
// Therefore, we use the image of the bootstrap node.
|
||||
CodexContainerRecipe.DockerImageOverride = bootstrapNode.GetImageName();
|
||||
CodexDockerImage.Override = bootstrapNode.GetImageName();
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user