mirror of
https://github.com/logos-storage/logos-storage-nim-cs-dist-tests.git
synced 2026-01-08 00:13:08 +00:00
Merge branch 'feature/containerless-codex'
# Conflicts: # Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs
This commit is contained in:
commit
dc7a034566
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,3 +3,4 @@ obj
|
||||
bin
|
||||
.vscode
|
||||
Tools/AutoClient/datapath
|
||||
.editorconfig
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\FileUtils\FileUtils.csproj" />
|
||||
<ProjectReference Include="..\KubernetesWorkflow\KubernetesWorkflow.csproj" />
|
||||
<ProjectReference Include="..\WebUtils\WebUtils.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using KubernetesWorkflow;
|
||||
using KubernetesWorkflow.Types;
|
||||
using Logging;
|
||||
|
||||
namespace Core
|
||||
{
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using KubernetesWorkflow;
|
||||
using Logging;
|
||||
using WebUtils;
|
||||
|
||||
namespace Core
|
||||
{
|
||||
@ -8,16 +9,16 @@ namespace Core
|
||||
private readonly IToolsFactory toolsFactory;
|
||||
private readonly PluginManager manager = new PluginManager();
|
||||
|
||||
public EntryPoint(ILog log, Configuration configuration, string fileManagerRootFolder, ITimeSet timeSet)
|
||||
public EntryPoint(ILog log, Configuration configuration, string fileManagerRootFolder, IWebCallTimeSet webCallTimeSet, IK8sTimeSet k8STimeSet)
|
||||
{
|
||||
toolsFactory = new ToolsFactory(log, configuration, fileManagerRootFolder, timeSet);
|
||||
toolsFactory = new ToolsFactory(log, configuration, fileManagerRootFolder, webCallTimeSet, k8STimeSet);
|
||||
|
||||
Tools = toolsFactory.CreateTools();
|
||||
manager.InstantiatePlugins(PluginFinder.GetPluginTypes(), toolsFactory);
|
||||
}
|
||||
|
||||
public EntryPoint(ILog log, Configuration configuration, string fileManagerRootFolder)
|
||||
: this(log, configuration, fileManagerRootFolder, new DefaultTimeSet())
|
||||
: this(log, configuration, fileManagerRootFolder, new DefaultWebCallTimeSet(), new DefaultK8sTimeSet())
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
using FileUtils;
|
||||
using KubernetesWorkflow;
|
||||
using Logging;
|
||||
using WebUtils;
|
||||
|
||||
namespace Core
|
||||
{
|
||||
public interface IPluginTools : IWorkflowTool, ILogTool, IHttpFactoryTool, IFileTool
|
||||
public interface IPluginTools : IWorkflowTool, ILogTool, IHttpFactory, IFileTool
|
||||
{
|
||||
ITimeSet TimeSet { get; }
|
||||
IWebCallTimeSet WebCallTimeSet { get; }
|
||||
IK8sTimeSet K8STimeSet { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Deletes kubernetes and tracked file resources.
|
||||
@ -25,13 +27,6 @@ namespace Core
|
||||
ILog GetLog();
|
||||
}
|
||||
|
||||
public interface IHttpFactoryTool
|
||||
{
|
||||
IHttp CreateHttp(string id, Action<HttpClient> onClientCreated);
|
||||
IHttp CreateHttp(string id, Action<HttpClient> onClientCreated, ITimeSet timeSet);
|
||||
IHttp CreateHttp(string id);
|
||||
}
|
||||
|
||||
public interface IFileTool
|
||||
{
|
||||
IFileManager GetFileManager();
|
||||
@ -40,18 +35,22 @@ namespace Core
|
||||
internal class PluginTools : IPluginTools
|
||||
{
|
||||
private readonly WorkflowCreator workflowCreator;
|
||||
private readonly HttpFactory httpFactory;
|
||||
private readonly IFileManager fileManager;
|
||||
private readonly LogPrefixer log;
|
||||
|
||||
internal PluginTools(ILog log, WorkflowCreator workflowCreator, string fileManagerRootFolder, ITimeSet timeSet)
|
||||
internal PluginTools(ILog log, WorkflowCreator workflowCreator, string fileManagerRootFolder, IWebCallTimeSet webCallTimeSet, IK8sTimeSet k8STimeSet)
|
||||
{
|
||||
this.log = new LogPrefixer(log);
|
||||
this.workflowCreator = workflowCreator;
|
||||
TimeSet = timeSet;
|
||||
httpFactory = new HttpFactory(log, webCallTimeSet);
|
||||
WebCallTimeSet = webCallTimeSet;
|
||||
K8STimeSet = k8STimeSet;
|
||||
fileManager = new FileManager(log, fileManagerRootFolder);
|
||||
}
|
||||
|
||||
public ITimeSet TimeSet { get; }
|
||||
public IWebCallTimeSet WebCallTimeSet { get; }
|
||||
public IK8sTimeSet K8STimeSet { get; }
|
||||
|
||||
public void ApplyLogPrefix(string prefix)
|
||||
{
|
||||
@ -60,17 +59,17 @@ namespace Core
|
||||
|
||||
public IHttp CreateHttp(string id, Action<HttpClient> onClientCreated)
|
||||
{
|
||||
return CreateHttp(id, onClientCreated, TimeSet);
|
||||
return httpFactory.CreateHttp(id, onClientCreated);
|
||||
}
|
||||
|
||||
public IHttp CreateHttp(string id, Action<HttpClient> onClientCreated, ITimeSet ts)
|
||||
public IHttp CreateHttp(string id, Action<HttpClient> onClientCreated, IWebCallTimeSet timeSet)
|
||||
{
|
||||
return new Http(id, log, ts, onClientCreated);
|
||||
return httpFactory.CreateHttp(id, onClientCreated, timeSet);
|
||||
}
|
||||
|
||||
public IHttp CreateHttp(string id)
|
||||
{
|
||||
return new Http(id, log, TimeSet);
|
||||
return httpFactory.CreateHttp(id);
|
||||
}
|
||||
|
||||
public IStartupWorkflow CreateWorkflow(string? namespaceOverride = null)
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using KubernetesWorkflow;
|
||||
using Logging;
|
||||
using WebUtils;
|
||||
|
||||
namespace Core
|
||||
{
|
||||
@ -13,19 +14,21 @@ namespace Core
|
||||
private readonly ILog log;
|
||||
private readonly WorkflowCreator workflowCreator;
|
||||
private readonly string fileManagerRootFolder;
|
||||
private readonly ITimeSet timeSet;
|
||||
private readonly IWebCallTimeSet webCallTimeSet;
|
||||
private readonly IK8sTimeSet k8STimeSet;
|
||||
|
||||
public ToolsFactory(ILog log, Configuration configuration, string fileManagerRootFolder, ITimeSet timeSet)
|
||||
public ToolsFactory(ILog log, Configuration configuration, string fileManagerRootFolder, IWebCallTimeSet webCallTimeSet, IK8sTimeSet k8STimeSet)
|
||||
{
|
||||
this.log = log;
|
||||
workflowCreator = new WorkflowCreator(log, configuration);
|
||||
this.fileManagerRootFolder = fileManagerRootFolder;
|
||||
this.timeSet = timeSet;
|
||||
this.webCallTimeSet = webCallTimeSet;
|
||||
this.k8STimeSet = k8STimeSet;
|
||||
}
|
||||
|
||||
public PluginTools CreateTools()
|
||||
{
|
||||
return new PluginTools(log, workflowCreator, fileManagerRootFolder, timeSet);
|
||||
return new PluginTools(log, workflowCreator, fileManagerRootFolder, webCallTimeSet, k8STimeSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,6 +14,12 @@ namespace FileUtils
|
||||
Label = label;
|
||||
}
|
||||
|
||||
public static TrackedFile FromPath(ILog log, string filepath)
|
||||
{
|
||||
// todo: I don't wanne have to do this to call upload.
|
||||
return new TrackedFile(log, filepath, string.Empty);
|
||||
}
|
||||
|
||||
public string Filename { get; }
|
||||
public string Label { get; }
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ using Logging;
|
||||
|
||||
namespace KubernetesWorkflow
|
||||
{
|
||||
public class CrashWatcher
|
||||
public class ContainerCrashWatcher
|
||||
{
|
||||
private readonly ILog log;
|
||||
private readonly KubernetesClientConfiguration config;
|
||||
@ -15,7 +15,7 @@ namespace KubernetesWorkflow
|
||||
private Task? worker;
|
||||
private Exception? workerException;
|
||||
|
||||
public CrashWatcher(ILog log, KubernetesClientConfiguration config, string containerName, string podName, string recipeName, string k8sNamespace)
|
||||
public ContainerCrashWatcher(ILog log, KubernetesClientConfiguration config, string containerName, string podName, string recipeName, string k8sNamespace)
|
||||
{
|
||||
this.log = log;
|
||||
this.config = config;
|
||||
@ -45,7 +45,7 @@ namespace KubernetesWorkflow
|
||||
if (workerException != null) throw new Exception("Exception occurred in CrashWatcher worker thread.", workerException);
|
||||
}
|
||||
|
||||
public bool HasContainerCrashed()
|
||||
public bool HasCrashed()
|
||||
{
|
||||
using var client = new Kubernetes(config);
|
||||
var result = HasContainerBeenRestarted(client);
|
||||
@ -946,13 +946,13 @@ namespace KubernetesWorkflow
|
||||
|
||||
#endregion
|
||||
|
||||
public CrashWatcher CreateCrashWatcher(RunningContainer container)
|
||||
public ContainerCrashWatcher CreateCrashWatcher(RunningContainer container)
|
||||
{
|
||||
var containerName = container.Name;
|
||||
var podName = GetPodName(container);
|
||||
var recipeName = container.Recipe.Name;
|
||||
|
||||
return new CrashWatcher(log, cluster.GetK8sClientConfig(), containerName, podName, recipeName, K8sNamespace);
|
||||
return new ContainerCrashWatcher(log, cluster.GetK8sClientConfig(), containerName, podName, recipeName, K8sNamespace);
|
||||
}
|
||||
|
||||
private V1Pod[] FindPodsByLabel(string podLabel)
|
||||
|
||||
42
Framework/KubernetesWorkflow/K8sTimeSet.cs
Normal file
42
Framework/KubernetesWorkflow/K8sTimeSet.cs
Normal file
@ -0,0 +1,42 @@
|
||||
namespace Core
|
||||
{
|
||||
public interface IK8sTimeSet
|
||||
{
|
||||
/// <summary>
|
||||
/// After a failed K8s operation, wait this long before trying again.
|
||||
/// </summary>
|
||||
TimeSpan K8sOperationRetryDelay();
|
||||
|
||||
/// <summary>
|
||||
/// Maximum total time to attempt to perform a successful k8s operation.
|
||||
/// If k8s operations fail during this timespan, retries will be made.
|
||||
/// </summary>
|
||||
TimeSpan K8sOperationTimeout();
|
||||
}
|
||||
|
||||
public class DefaultK8sTimeSet : IK8sTimeSet
|
||||
{
|
||||
public TimeSpan K8sOperationRetryDelay()
|
||||
{
|
||||
return TimeSpan.FromSeconds(10);
|
||||
}
|
||||
|
||||
public TimeSpan K8sOperationTimeout()
|
||||
{
|
||||
return TimeSpan.FromMinutes(30);
|
||||
}
|
||||
}
|
||||
|
||||
public class LongK8sTimeSet : IK8sTimeSet
|
||||
{
|
||||
public TimeSpan K8sOperationRetryDelay()
|
||||
{
|
||||
return TimeSpan.FromSeconds(30);
|
||||
}
|
||||
|
||||
public TimeSpan K8sOperationTimeout()
|
||||
{
|
||||
return TimeSpan.FromHours(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -29,7 +29,7 @@ namespace KubernetesWorkflow
|
||||
{
|
||||
LogFile = sourceLog.CreateSubfile(addFileName);
|
||||
|
||||
var msg = $"{description} -->> {LogFile.FullFilename}";
|
||||
var msg = $"{description} -->> {LogFile.Filename}";
|
||||
sourceLog.Log(msg);
|
||||
|
||||
LogFile.Write(msg);
|
||||
|
||||
@ -13,7 +13,7 @@ namespace KubernetesWorkflow
|
||||
FutureContainers Start(int numberOfContainers, ILocation location, ContainerRecipeFactory recipeFactory, StartupConfig startupConfig);
|
||||
PodInfo GetPodInfo(RunningContainer container);
|
||||
PodInfo GetPodInfo(RunningPod pod);
|
||||
CrashWatcher CreateCrashWatcher(RunningContainer container);
|
||||
ContainerCrashWatcher CreateCrashWatcher(RunningContainer container);
|
||||
void Stop(RunningPod pod, bool waitTillStopped);
|
||||
void DownloadContainerLog(RunningContainer container, ILogHandler logHandler, int? tailLines = null, bool? previous = null);
|
||||
IDownloadedLog DownloadContainerLog(RunningContainer container, int? tailLines = null, bool? previous = null);
|
||||
@ -93,7 +93,7 @@ namespace KubernetesWorkflow
|
||||
return K8s(c => c.GetPodInfo(pod.StartResult.Deployment));
|
||||
}
|
||||
|
||||
public CrashWatcher CreateCrashWatcher(RunningContainer container)
|
||||
public ContainerCrashWatcher CreateCrashWatcher(RunningContainer container)
|
||||
{
|
||||
return K8s(c => c.CreateCrashWatcher(container));
|
||||
}
|
||||
@ -134,7 +134,7 @@ namespace KubernetesWorkflow
|
||||
controller.DownloadPodLog(container, logHandler, tailLines, previous);
|
||||
});
|
||||
|
||||
return new DownloadedLog(logHandler, container.Name);
|
||||
return new DownloadedLog(logHandler.LogFile, container.Name);
|
||||
}
|
||||
|
||||
public string ExecuteCommand(RunningContainer container, string command, params string[] args)
|
||||
@ -209,6 +209,7 @@ namespace KubernetesWorkflow
|
||||
var port = startResult.GetExternalServicePorts(recipe, tag);
|
||||
|
||||
return new Address(
|
||||
logName: $"{recipe.Name}:{tag}",
|
||||
startResult.Cluster.HostAddress,
|
||||
port.Number);
|
||||
}
|
||||
@ -220,6 +221,7 @@ namespace KubernetesWorkflow
|
||||
var port = startResult.GetInternalServicePorts(recipe, tag);
|
||||
|
||||
return new Address(
|
||||
logName: $"{serviceName}:{tag}",
|
||||
$"http://{serviceName}.{namespaceName}.svc.cluster.local",
|
||||
port.Number);
|
||||
}
|
||||
|
||||
@ -34,7 +34,7 @@ namespace Logging
|
||||
{
|
||||
get
|
||||
{
|
||||
if (logFile == null) logFile = new LogFile(GetFullName(), "log");
|
||||
if (logFile == null) logFile = new LogFile(GetFullName() + ".log");
|
||||
return logFile;
|
||||
}
|
||||
}
|
||||
@ -69,7 +69,7 @@ namespace Logging
|
||||
|
||||
public virtual void Delete()
|
||||
{
|
||||
File.Delete(LogFile.FullFilename);
|
||||
File.Delete(LogFile.Filename);
|
||||
}
|
||||
|
||||
public LogFile CreateSubfile(string addName, string ext = "log")
|
||||
@ -78,7 +78,7 @@ namespace Logging
|
||||
.Replace("<", "")
|
||||
.Replace(">", "");
|
||||
|
||||
return new LogFile($"{GetFullName()}_{GetSubfileNumber()}_{addName}", ext);
|
||||
return new LogFile($"{GetFullName()}_{GetSubfileNumber()}_{addName}.{ext}");
|
||||
}
|
||||
|
||||
protected string ApplyReplacements(string str)
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
using Logging;
|
||||
|
||||
namespace KubernetesWorkflow
|
||||
namespace Logging
|
||||
{
|
||||
public interface IDownloadedLog
|
||||
{
|
||||
string ContainerName { get; }
|
||||
string SourceName { get; }
|
||||
|
||||
void IterateLines(Action<string> action);
|
||||
void IterateLines(Action<string> action, params string[] thatContain);
|
||||
@ -14,21 +12,27 @@ namespace KubernetesWorkflow
|
||||
void DeleteFile();
|
||||
}
|
||||
|
||||
internal class DownloadedLog : IDownloadedLog
|
||||
public class DownloadedLog : IDownloadedLog
|
||||
{
|
||||
private readonly LogFile logFile;
|
||||
|
||||
internal DownloadedLog(WriteToFileLogHandler logHandler, string containerName)
|
||||
public DownloadedLog(string filepath, string sourceName)
|
||||
{
|
||||
logFile = logHandler.LogFile;
|
||||
ContainerName = containerName;
|
||||
logFile = new LogFile(filepath);
|
||||
SourceName = sourceName;
|
||||
}
|
||||
|
||||
public string ContainerName { get; }
|
||||
public DownloadedLog(LogFile logFile, string sourceName)
|
||||
{
|
||||
this.logFile = logFile;
|
||||
SourceName = sourceName;
|
||||
}
|
||||
|
||||
public string SourceName { get; }
|
||||
|
||||
public void IterateLines(Action<string> action)
|
||||
{
|
||||
using var file = File.OpenRead(logFile.FullFilename);
|
||||
using var file = File.OpenRead(logFile.Filename);
|
||||
using var streamReader = new StreamReader(file);
|
||||
|
||||
var line = streamReader.ReadLine();
|
||||
@ -64,12 +68,12 @@ namespace KubernetesWorkflow
|
||||
|
||||
public string GetFilepath()
|
||||
{
|
||||
return logFile.FullFilename;
|
||||
return logFile.Filename;
|
||||
}
|
||||
|
||||
public void DeleteFile()
|
||||
{
|
||||
File.Delete(logFile.FullFilename);
|
||||
File.Delete(logFile.Filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,20 +4,16 @@ namespace Logging
|
||||
{
|
||||
public class LogFile
|
||||
{
|
||||
private readonly string extension;
|
||||
private readonly object fileLock = new object();
|
||||
private string filename;
|
||||
|
||||
public LogFile(string filename, string extension)
|
||||
public LogFile(string filename)
|
||||
{
|
||||
this.filename = filename;
|
||||
this.extension = extension;
|
||||
FullFilename = filename + "." + extension;
|
||||
Filename = filename;
|
||||
|
||||
EnsurePathExists(filename);
|
||||
}
|
||||
|
||||
public string FullFilename { get; private set; }
|
||||
public string Filename { get; private set; }
|
||||
|
||||
public void Write(string message)
|
||||
{
|
||||
@ -30,7 +26,7 @@ namespace Logging
|
||||
{
|
||||
lock (fileLock)
|
||||
{
|
||||
File.AppendAllLines(FullFilename, new[] { message });
|
||||
File.AppendAllLines(Filename, new[] { message });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -39,14 +35,19 @@ namespace Logging
|
||||
}
|
||||
}
|
||||
|
||||
public void ConcatToFilename(string toAdd)
|
||||
public void WriteRawMany(IEnumerable<string> lines)
|
||||
{
|
||||
var oldFullName = FullFilename;
|
||||
|
||||
filename += toAdd;
|
||||
FullFilename = filename + "." + extension;
|
||||
|
||||
File.Move(oldFullName, FullFilename);
|
||||
try
|
||||
{
|
||||
lock (fileLock)
|
||||
{
|
||||
File.AppendAllLines(Filename, lines);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("Writing to log has failed: " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetTimestamp()
|
||||
|
||||
@ -2,12 +2,14 @@
|
||||
{
|
||||
public class Address
|
||||
{
|
||||
public Address(string host, int port)
|
||||
public Address(string logName, string host, int port)
|
||||
{
|
||||
LogName = logName;
|
||||
Host = host;
|
||||
Port = port;
|
||||
}
|
||||
|
||||
public string LogName { get; }
|
||||
public string Host { get; }
|
||||
public int Port { get; }
|
||||
|
||||
@ -20,5 +22,21 @@
|
||||
{
|
||||
return !string.IsNullOrEmpty(Host) && Port > 0;
|
||||
}
|
||||
|
||||
public static Address Empty()
|
||||
{
|
||||
return new Address(string.Empty, string.Empty, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public interface IHasMetricsScrapeTarget
|
||||
{
|
||||
Address GetMetricsScrapeTarget();
|
||||
}
|
||||
|
||||
public interface IHasManyMetricScrapeTargets
|
||||
{
|
||||
Address[] GetMetricsScrapeTargets();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
9
Framework/Utils/CrashWatcher.cs
Normal file
9
Framework/Utils/CrashWatcher.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Utils
|
||||
{
|
||||
//public interface ICrashWatcher
|
||||
//{
|
||||
// void Start();
|
||||
// void Stop();
|
||||
// bool HasCrashed();
|
||||
//}
|
||||
}
|
||||
20
Framework/Utils/EthAccount.cs
Normal file
20
Framework/Utils/EthAccount.cs
Normal file
@ -0,0 +1,20 @@
|
||||
namespace Utils
|
||||
{
|
||||
[Serializable]
|
||||
public class EthAccount
|
||||
{
|
||||
public EthAccount(EthAddress ethAddress, string privateKey)
|
||||
{
|
||||
EthAddress = ethAddress;
|
||||
PrivateKey = privateKey;
|
||||
}
|
||||
|
||||
public EthAddress EthAddress { get; }
|
||||
public string PrivateKey { get; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return EthAddress.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
namespace GethPlugin
|
||||
namespace Utils
|
||||
{
|
||||
public interface IHasEthAddress
|
||||
{
|
||||
@ -1,4 +1,4 @@
|
||||
namespace GethPlugin
|
||||
namespace Utils
|
||||
{
|
||||
public class Ether : IComparable<Ether>
|
||||
{
|
||||
@ -1,6 +1,6 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace CodexContractsPlugin
|
||||
namespace Utils
|
||||
{
|
||||
public class TestToken : IComparable<TestToken>
|
||||
{
|
||||
@ -1,11 +1,10 @@
|
||||
using Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Serialization = Newtonsoft.Json.Serialization;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Http.Json;
|
||||
using Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Utils;
|
||||
|
||||
namespace Core
|
||||
namespace WebUtils
|
||||
{
|
||||
public interface IEndpoint
|
||||
{
|
||||
@ -119,7 +118,7 @@ namespace Core
|
||||
var errors = new List<string>();
|
||||
var deserialized = JsonConvert.DeserializeObject<T>(json, new JsonSerializerSettings()
|
||||
{
|
||||
Error = delegate (object? sender, Serialization.ErrorEventArgs args)
|
||||
Error = delegate (object? sender, Newtonsoft.Json.Serialization.ErrorEventArgs args)
|
||||
{
|
||||
if (args.CurrentObject == args.ErrorContext.OriginalObject)
|
||||
{
|
||||
@ -1,7 +1,7 @@
|
||||
using Logging;
|
||||
using Utils;
|
||||
|
||||
namespace Core
|
||||
namespace WebUtils
|
||||
{
|
||||
public interface IHttp
|
||||
{
|
||||
@ -16,16 +16,16 @@ namespace Core
|
||||
private static object lockLock = new object();
|
||||
private static readonly Dictionary<string, object> httpLocks = new Dictionary<string, object>();
|
||||
private readonly ILog log;
|
||||
private readonly ITimeSet timeSet;
|
||||
private readonly IWebCallTimeSet timeSet;
|
||||
private readonly Action<HttpClient> onClientCreated;
|
||||
private readonly string id;
|
||||
|
||||
internal Http(string id, ILog log, ITimeSet timeSet)
|
||||
internal Http(string id, ILog log, IWebCallTimeSet timeSet)
|
||||
: this(id, log, timeSet, DoNothing)
|
||||
{
|
||||
}
|
||||
|
||||
internal Http(string id, ILog log, ITimeSet timeSet, Action<HttpClient> onClientCreated)
|
||||
internal Http(string id, ILog log, IWebCallTimeSet timeSet, Action<HttpClient> onClientCreated)
|
||||
{
|
||||
this.id = id;
|
||||
this.log = log;
|
||||
43
Framework/WebUtils/HttpFactory.cs
Normal file
43
Framework/WebUtils/HttpFactory.cs
Normal file
@ -0,0 +1,43 @@
|
||||
using Logging;
|
||||
|
||||
namespace WebUtils
|
||||
{
|
||||
public interface IHttpFactory
|
||||
{
|
||||
IHttp CreateHttp(string id, Action<HttpClient> onClientCreated);
|
||||
IHttp CreateHttp(string id, Action<HttpClient> onClientCreated, IWebCallTimeSet timeSet);
|
||||
IHttp CreateHttp(string id);
|
||||
}
|
||||
|
||||
public class HttpFactory : IHttpFactory
|
||||
{
|
||||
private readonly ILog log;
|
||||
private readonly IWebCallTimeSet defaultTimeSet;
|
||||
|
||||
public HttpFactory(ILog log)
|
||||
: this (log, new DefaultWebCallTimeSet())
|
||||
{
|
||||
}
|
||||
|
||||
public HttpFactory(ILog log, IWebCallTimeSet defaultTimeSet)
|
||||
{
|
||||
this.log = log;
|
||||
this.defaultTimeSet = defaultTimeSet;
|
||||
}
|
||||
|
||||
public IHttp CreateHttp(string id, Action<HttpClient> onClientCreated)
|
||||
{
|
||||
return CreateHttp(id, onClientCreated, defaultTimeSet);
|
||||
}
|
||||
|
||||
public IHttp CreateHttp(string id, Action<HttpClient> onClientCreated, IWebCallTimeSet ts)
|
||||
{
|
||||
return new Http(id, log, ts, onClientCreated);
|
||||
}
|
||||
|
||||
public IHttp CreateHttp(string id)
|
||||
{
|
||||
return new Http(id, log, defaultTimeSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
namespace Core
|
||||
namespace WebUtils
|
||||
{
|
||||
public interface ITimeSet
|
||||
public interface IWebCallTimeSet
|
||||
{
|
||||
/// <summary>
|
||||
/// Timeout for a single HTTP call.
|
||||
@ -17,20 +17,9 @@
|
||||
/// After a failed HTTP call, wait this long before trying again.
|
||||
/// </summary>
|
||||
TimeSpan HttpCallRetryDelay();
|
||||
|
||||
/// <summary>
|
||||
/// After a failed K8s operation, wait this long before trying again.
|
||||
/// </summary>
|
||||
TimeSpan K8sOperationRetryDelay();
|
||||
|
||||
/// <summary>
|
||||
/// Maximum total time to attempt to perform a successful k8s operation.
|
||||
/// If k8s operations fail during this timespan, retries will be made.
|
||||
/// </summary>
|
||||
TimeSpan K8sOperationTimeout();
|
||||
}
|
||||
|
||||
public class DefaultTimeSet : ITimeSet
|
||||
public class DefaultWebCallTimeSet : IWebCallTimeSet
|
||||
{
|
||||
public TimeSpan HttpCallTimeout()
|
||||
{
|
||||
@ -46,19 +35,9 @@
|
||||
{
|
||||
return TimeSpan.FromSeconds(1);
|
||||
}
|
||||
|
||||
public TimeSpan K8sOperationRetryDelay()
|
||||
{
|
||||
return TimeSpan.FromSeconds(10);
|
||||
}
|
||||
|
||||
public TimeSpan K8sOperationTimeout()
|
||||
{
|
||||
return TimeSpan.FromMinutes(30);
|
||||
}
|
||||
}
|
||||
|
||||
public class LongTimeSet : ITimeSet
|
||||
public class LongWebCallTimeSet : IWebCallTimeSet
|
||||
{
|
||||
public TimeSpan HttpCallTimeout()
|
||||
{
|
||||
@ -74,15 +53,5 @@
|
||||
{
|
||||
return TimeSpan.FromSeconds(20);
|
||||
}
|
||||
|
||||
public TimeSpan K8sOperationRetryDelay()
|
||||
{
|
||||
return TimeSpan.FromSeconds(30);
|
||||
}
|
||||
|
||||
public TimeSpan K8sOperationTimeout()
|
||||
{
|
||||
return TimeSpan.FromHours(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Framework/WebUtils/WebUtils.csproj
Normal file
18
Framework/WebUtils/WebUtils.csproj
Normal file
@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Logging\Logging.csproj" />
|
||||
<ProjectReference Include="..\Utils\Utils.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -1,31 +1,50 @@
|
||||
using CodexOpenApi;
|
||||
using Core;
|
||||
using KubernetesWorkflow;
|
||||
using KubernetesWorkflow.Types;
|
||||
using Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Utils;
|
||||
using WebUtils;
|
||||
|
||||
namespace CodexPlugin
|
||||
namespace CodexClient
|
||||
{
|
||||
public class CodexAccess
|
||||
{
|
||||
private readonly ILog log;
|
||||
private readonly IPluginTools tools;
|
||||
private readonly IHttpFactory httpFactory;
|
||||
private readonly IProcessControl processControl;
|
||||
private ICodexInstance instance;
|
||||
private readonly Mapper mapper = new Mapper();
|
||||
|
||||
public CodexAccess(IPluginTools tools, RunningPod container, CrashWatcher crashWatcher)
|
||||
public CodexAccess(ILog log, IHttpFactory httpFactory, IProcessControl processControl, ICodexInstance instance)
|
||||
{
|
||||
this.tools = tools;
|
||||
log = tools.GetLog();
|
||||
Container = container;
|
||||
CrashWatcher = crashWatcher;
|
||||
|
||||
CrashWatcher.Start();
|
||||
this.log = log;
|
||||
this.httpFactory = httpFactory;
|
||||
this.processControl = processControl;
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
public RunningPod Container { get; }
|
||||
public CrashWatcher CrashWatcher { get; }
|
||||
public void Stop(bool waitTillStopped)
|
||||
{
|
||||
processControl.Stop(waitTillStopped);
|
||||
// Prevents accidental use after stop:
|
||||
instance = null!;
|
||||
}
|
||||
|
||||
public IDownloadedLog DownloadLog(string additionalName = "")
|
||||
{
|
||||
var file = log.CreateSubfile(GetName() + additionalName);
|
||||
Log($"Downloading logs to '{file.Filename}'");
|
||||
return processControl.DownloadLog(file);
|
||||
}
|
||||
|
||||
public string GetImageName()
|
||||
{
|
||||
return instance.ImageName;
|
||||
}
|
||||
|
||||
public DateTime GetStartUtc()
|
||||
{
|
||||
return instance.StartUtc;
|
||||
}
|
||||
|
||||
public DebugInfo GetDebugInfo()
|
||||
{
|
||||
@ -79,19 +98,14 @@ namespace CodexPlugin
|
||||
});
|
||||
}
|
||||
|
||||
public string UploadFile(UploadInput uploadInput, Action<Failure> onFailure)
|
||||
public string UploadFile(UploadInput uploadInput)
|
||||
{
|
||||
return OnCodex(
|
||||
api => api.UploadAsync(uploadInput.ContentType, uploadInput.ContentDisposition, uploadInput.FileStream),
|
||||
CreateRetryConfig(nameof(UploadFile), onFailure));
|
||||
return OnCodex(api => api.UploadAsync(uploadInput.ContentType, uploadInput.ContentDisposition, uploadInput.FileStream));
|
||||
}
|
||||
|
||||
public Stream DownloadFile(string contentId, Action<Failure> onFailure)
|
||||
public Stream DownloadFile(string contentId)
|
||||
{
|
||||
var fileResponse = OnCodex(
|
||||
api => api.DownloadNetworkStreamAsync(contentId),
|
||||
CreateRetryConfig(nameof(DownloadFile), onFailure));
|
||||
|
||||
var fileResponse = OnCodex(api => api.DownloadNetworkStreamAsync(contentId));
|
||||
if (fileResponse.StatusCode != 200) throw new Exception("Download failed with StatusCode: " + fileResponse.StatusCode);
|
||||
return fileResponse.Stream;
|
||||
}
|
||||
@ -123,7 +137,6 @@ namespace CodexPlugin
|
||||
return JsonConvert.DeserializeObject<LocalDatasetListJson>(str)!;
|
||||
}, nameof(LocalFiles));
|
||||
}));
|
||||
|
||||
}
|
||||
|
||||
public StorageAvailability SalesAvailability(StorageAvailability request)
|
||||
@ -170,47 +183,60 @@ namespace CodexPlugin
|
||||
|
||||
public string GetName()
|
||||
{
|
||||
return Container.Name;
|
||||
return instance.Name;
|
||||
}
|
||||
|
||||
public PodInfo GetPodInfo()
|
||||
public Address GetDiscoveryEndpoint()
|
||||
{
|
||||
var workflow = tools.CreateWorkflow();
|
||||
return workflow.GetPodInfo(Container);
|
||||
return instance.DiscoveryEndpoint;
|
||||
}
|
||||
|
||||
public void DeleteRepoFolder()
|
||||
public Address GetApiEndpoint()
|
||||
{
|
||||
try
|
||||
{
|
||||
var containerNumber = Container.Containers.First().Recipe.Number;
|
||||
var dataDir = $"datadir{containerNumber}";
|
||||
var workflow = tools.CreateWorkflow();
|
||||
workflow.ExecuteCommand(Container.Containers.First(), "rm", "-Rfv", $"/codex/{dataDir}/repo");
|
||||
Log("Deleted repo folder.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log("Unable to delete repo folder: " + e);
|
||||
}
|
||||
return instance.ApiEndpoint;
|
||||
}
|
||||
|
||||
private T OnCodex<T>(Func<CodexApi, Task<T>> action)
|
||||
public Address GetListenEndpoint()
|
||||
{
|
||||
var result = tools.CreateHttp(GetHttpId(), CheckContainerCrashed).OnClient(client => CallCodex(client, action));
|
||||
return instance.ListenEndpoint;
|
||||
}
|
||||
|
||||
public bool HasCrashed()
|
||||
{
|
||||
return processControl.HasCrashed();
|
||||
}
|
||||
|
||||
public Address? GetMetricsEndpoint()
|
||||
{
|
||||
return instance.MetricsEndpoint;
|
||||
}
|
||||
|
||||
public EthAccount? GetEthAccount()
|
||||
{
|
||||
return instance.EthAccount;
|
||||
}
|
||||
|
||||
public void DeleteDataDirFolder()
|
||||
{
|
||||
processControl.DeleteDataDirFolder();
|
||||
}
|
||||
|
||||
private T OnCodex<T>(Func<CodexApiClient, Task<T>> action)
|
||||
{
|
||||
var result = httpFactory.CreateHttp(GetHttpId(), h => CheckContainerCrashed()).OnClient(client => CallCodex(client, action));
|
||||
return result;
|
||||
}
|
||||
|
||||
private T OnCodex<T>(Func<CodexApi, Task<T>> action, Retry retry)
|
||||
private T OnCodex<T>(Func<CodexApiClient, Task<T>> action, Retry retry)
|
||||
{
|
||||
var result = tools.CreateHttp(GetHttpId(), CheckContainerCrashed).OnClient(client => CallCodex(client, action), retry);
|
||||
var result = httpFactory.CreateHttp(GetHttpId(), h => CheckContainerCrashed()).OnClient(client => CallCodex(client, action), retry);
|
||||
return result;
|
||||
}
|
||||
|
||||
private T CallCodex<T>(HttpClient client, Func<CodexApi, Task<T>> action)
|
||||
private T CallCodex<T>(HttpClient client, Func<CodexApiClient, Task<T>> action)
|
||||
{
|
||||
var address = GetAddress();
|
||||
var api = new CodexApi(client);
|
||||
var api = new CodexApiClient(client);
|
||||
api.BaseUrl = $"{address.Host}:{address.Port}/api/codex/v1";
|
||||
return CrashCheck(() => Time.Wait(action(api)));
|
||||
}
|
||||
@ -223,20 +249,20 @@ namespace CodexPlugin
|
||||
}
|
||||
finally
|
||||
{
|
||||
CrashWatcher.HasContainerCrashed();
|
||||
CheckContainerCrashed();
|
||||
}
|
||||
}
|
||||
|
||||
private IEndpoint GetEndpoint()
|
||||
{
|
||||
return tools
|
||||
.CreateHttp(GetHttpId(), CheckContainerCrashed)
|
||||
.CreateEndpoint(GetAddress(), "/api/codex/v1/", Container.Name);
|
||||
return httpFactory
|
||||
.CreateHttp(GetHttpId(), h => CheckContainerCrashed())
|
||||
.CreateEndpoint(GetAddress(), "/api/codex/v1/", GetName());
|
||||
}
|
||||
|
||||
private Address GetAddress()
|
||||
{
|
||||
return Container.Containers.Single().GetAddress(CodexContainerRecipe.ApiPortTag);
|
||||
return instance.ApiEndpoint;
|
||||
}
|
||||
|
||||
private string GetHttpId()
|
||||
@ -244,52 +270,9 @@ namespace CodexPlugin
|
||||
return GetAddress().ToString();
|
||||
}
|
||||
|
||||
private void CheckContainerCrashed(HttpClient client)
|
||||
private void CheckContainerCrashed()
|
||||
{
|
||||
if (CrashWatcher.HasContainerCrashed()) throw new Exception($"Container {GetName()} has crashed.");
|
||||
}
|
||||
|
||||
private Retry CreateRetryConfig(string description, Action<Failure> onFailure)
|
||||
{
|
||||
var timeSet = tools.TimeSet;
|
||||
|
||||
return new Retry(description, timeSet.HttpRetryTimeout(), timeSet.HttpCallRetryDelay(), failure =>
|
||||
{
|
||||
onFailure(failure);
|
||||
Investigate(failure, timeSet);
|
||||
});
|
||||
}
|
||||
|
||||
private void Investigate(Failure failure, ITimeSet timeSet)
|
||||
{
|
||||
Log($"Retry {failure.TryNumber} took {Time.FormatDuration(failure.Duration)} and failed with '{failure.Exception}'. " +
|
||||
$"(HTTP timeout = {Time.FormatDuration(timeSet.HttpCallTimeout())}) " +
|
||||
$"Checking if node responds to debug/info...");
|
||||
|
||||
try
|
||||
{
|
||||
var debugInfo = GetDebugInfo();
|
||||
if (string.IsNullOrEmpty(debugInfo.Spr))
|
||||
{
|
||||
Log("Did not get value debug/info response.");
|
||||
Throw(failure);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log("Got valid response from debug/info.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log("Got exception from debug/info call: " + ex);
|
||||
Throw(failure);
|
||||
}
|
||||
|
||||
if (failure.Duration < timeSet.HttpCallTimeout())
|
||||
{
|
||||
Log("Retry failed within HTTP timeout duration.");
|
||||
Throw(failure);
|
||||
}
|
||||
if (processControl.HasCrashed()) throw new Exception($"Container {GetName()} has crashed.");
|
||||
}
|
||||
|
||||
private void Throw(Failure failure)
|
||||
@ -299,7 +282,7 @@ namespace CodexPlugin
|
||||
|
||||
private void Log(string msg)
|
||||
{
|
||||
log.Log($"{GetName()} {msg}");
|
||||
log.Log($"({GetName()}) {msg}");
|
||||
}
|
||||
}
|
||||
|
||||
35
ProjectPlugins/CodexClient/CodexClient.csproj
Normal file
35
ProjectPlugins/CodexClient/CodexClient.csproj
Normal file
@ -0,0 +1,35 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="openapi.yaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<OpenApiReference Include="openapi.yaml" CodeGenerator="NSwagCSharp" Namespace="CodexOpenApi" ClassName="CodexApiClient" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.ApiDescription.Client" Version="7.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="NSwag.ApiDescription.Client" Version="13.18.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Framework\FileUtils\FileUtils.csproj" />
|
||||
<ProjectReference Include="..\..\Framework\Logging\Logging.csproj" />
|
||||
<ProjectReference Include="..\..\Framework\WebUtils\WebUtils.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
54
ProjectPlugins/CodexClient/CodexInstance.cs
Normal file
54
ProjectPlugins/CodexClient/CodexInstance.cs
Normal file
@ -0,0 +1,54 @@
|
||||
using Utils;
|
||||
|
||||
namespace CodexClient
|
||||
{
|
||||
public interface ICodexInstance
|
||||
{
|
||||
string Name { get; }
|
||||
string ImageName { get; }
|
||||
DateTime StartUtc { get; }
|
||||
Address DiscoveryEndpoint { get; }
|
||||
Address ApiEndpoint { get; }
|
||||
Address ListenEndpoint { get; }
|
||||
EthAccount? EthAccount { get; }
|
||||
Address? MetricsEndpoint { get; }
|
||||
}
|
||||
|
||||
public class CodexInstance : ICodexInstance
|
||||
{
|
||||
public CodexInstance(string name, string imageName, DateTime startUtc, Address discoveryEndpoint, Address apiEndpoint, Address listenEndpoint, EthAccount? ethAccount, Address? metricsEndpoint)
|
||||
{
|
||||
Name = name;
|
||||
ImageName = imageName;
|
||||
StartUtc = startUtc;
|
||||
DiscoveryEndpoint = discoveryEndpoint;
|
||||
ApiEndpoint = apiEndpoint;
|
||||
ListenEndpoint = listenEndpoint;
|
||||
EthAccount = ethAccount;
|
||||
MetricsEndpoint = metricsEndpoint;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
public string ImageName { get; }
|
||||
public DateTime StartUtc { get; }
|
||||
public Address DiscoveryEndpoint { get; }
|
||||
public Address ApiEndpoint { get; }
|
||||
public Address ListenEndpoint { get; }
|
||||
public EthAccount? EthAccount { get; }
|
||||
public Address? MetricsEndpoint { get; }
|
||||
|
||||
public static ICodexInstance CreateFromApiEndpoint(string name, Address apiEndpoint)
|
||||
{
|
||||
return new CodexInstance(
|
||||
name,
|
||||
imageName: "-",
|
||||
startUtc: DateTime.UtcNow,
|
||||
discoveryEndpoint: Address.Empty(),
|
||||
apiEndpoint: apiEndpoint,
|
||||
listenEndpoint: Address.Empty(),
|
||||
ethAccount: null,
|
||||
metricsEndpoint: null
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
namespace CodexPlugin
|
||||
namespace CodexClient
|
||||
{
|
||||
public enum CodexLogLevel
|
||||
{
|
||||
@ -1,6 +1,6 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace CodexPlugin
|
||||
namespace CodexClient
|
||||
{
|
||||
public class CodexLogLine
|
||||
{
|
||||
@ -1,27 +1,21 @@
|
||||
using CodexPlugin.Hooks;
|
||||
using Core;
|
||||
using CodexClient.Hooks;
|
||||
using FileUtils;
|
||||
using GethPlugin;
|
||||
using KubernetesWorkflow;
|
||||
using KubernetesWorkflow.Types;
|
||||
using Logging;
|
||||
using MetricsPlugin;
|
||||
using Utils;
|
||||
|
||||
namespace CodexPlugin
|
||||
namespace CodexClient
|
||||
{
|
||||
public interface ICodexNode : IHasContainer, IHasMetricsScrapeTarget, IHasEthAddress
|
||||
public partial interface ICodexNode : IHasEthAddress, IHasMetricsScrapeTarget
|
||||
{
|
||||
string GetName();
|
||||
string GetImageName();
|
||||
string GetPeerId();
|
||||
DebugInfo GetDebugInfo(bool log = false);
|
||||
string GetSpr();
|
||||
DebugPeer GetDebugPeer(string peerId);
|
||||
ContentId UploadFile(TrackedFile file);
|
||||
ContentId UploadFile(TrackedFile file, Action<Failure> onFailure);
|
||||
ContentId UploadFile(TrackedFile file, string contentType, string contentDisposition, Action<Failure> onFailure);
|
||||
ContentId UploadFile(TrackedFile file, string contentType, string contentDisposition);
|
||||
TrackedFile? DownloadContent(ContentId contentId, string fileLabel = "");
|
||||
TrackedFile? DownloadContent(ContentId contentId, Action<Failure> onFailure, string fileLabel = "");
|
||||
LocalDataset DownloadStreamless(ContentId cid);
|
||||
/// <summary>
|
||||
/// TODO: This will monitor the quota-used of the node until 'size' bytes are added. That's a very bad way
|
||||
@ -34,70 +28,67 @@ namespace CodexPlugin
|
||||
void ConnectToPeer(ICodexNode node);
|
||||
DebugInfoVersion Version { get; }
|
||||
IMarketplaceAccess Marketplace { get; }
|
||||
CrashWatcher CrashWatcher { get; }
|
||||
PodInfo GetPodInfo();
|
||||
ITransferSpeeds TransferSpeeds { get; }
|
||||
EthAccount EthAccount { get; }
|
||||
StoragePurchase GetPurchaseStatus(string purchaseId);
|
||||
|
||||
Address GetDiscoveryEndpoint();
|
||||
Address GetApiEndpoint();
|
||||
Address GetListenEndpoint();
|
||||
|
||||
/// <summary>
|
||||
/// Warning! The node is not usable after this.
|
||||
/// TODO: Replace with delete-blocks debug call once available in Codex.
|
||||
/// </summary>
|
||||
void DeleteRepoFolder();
|
||||
void DeleteDataDirFolder();
|
||||
void Stop(bool waitTillStopped);
|
||||
IDownloadedLog DownloadLog(string additionalName = "");
|
||||
bool HasCrashed();
|
||||
}
|
||||
|
||||
public class CodexNode : ICodexNode
|
||||
{
|
||||
private const string UploadFailedMessage = "Unable to store block";
|
||||
private readonly ILog log;
|
||||
private readonly IPluginTools tools;
|
||||
private readonly ICodexNodeHooks hooks;
|
||||
private readonly EthAccount? ethAccount;
|
||||
private readonly TransferSpeeds transferSpeeds;
|
||||
private string peerId = string.Empty;
|
||||
private string nodeId = string.Empty;
|
||||
private readonly CodexAccess codexAccess;
|
||||
private readonly IFileManager fileManager;
|
||||
|
||||
public CodexNode(IPluginTools tools, CodexAccess codexAccess, CodexNodeGroup group, IMarketplaceAccess marketplaceAccess, ICodexNodeHooks hooks, EthAccount? ethAccount)
|
||||
public CodexNode(ILog log, CodexAccess codexAccess, IFileManager fileManager, IMarketplaceAccess marketplaceAccess, ICodexNodeHooks hooks)
|
||||
{
|
||||
this.tools = tools;
|
||||
this.ethAccount = ethAccount;
|
||||
CodexAccess = codexAccess;
|
||||
Group = group;
|
||||
this.codexAccess = codexAccess;
|
||||
this.fileManager = fileManager;
|
||||
Marketplace = marketplaceAccess;
|
||||
this.hooks = hooks;
|
||||
Version = new DebugInfoVersion();
|
||||
transferSpeeds = new TransferSpeeds();
|
||||
|
||||
log = new LogPrefixer(tools.GetLog(), $"{GetName()} ");
|
||||
this.log = new LogPrefixer(log, $"{GetName()} ");
|
||||
}
|
||||
|
||||
public void Awake()
|
||||
{
|
||||
hooks.OnNodeStarting(Container.Recipe.RecipeCreatedUtc, Container.Recipe.Image, ethAccount);
|
||||
hooks.OnNodeStarting(codexAccess.GetStartUtc(), codexAccess.GetImageName(), codexAccess.GetEthAccount());
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
hooks.OnNodeStarted(peerId, nodeId);
|
||||
InitializePeerNodeId();
|
||||
InitializeLogReplacements();
|
||||
|
||||
hooks.OnNodeStarted(this, peerId, nodeId);
|
||||
}
|
||||
|
||||
public RunningPod Pod { get { return CodexAccess.Container; } }
|
||||
|
||||
public RunningContainer Container { get { return Pod.Containers.Single(); } }
|
||||
public CodexAccess CodexAccess { get; }
|
||||
public CrashWatcher CrashWatcher { get => CodexAccess.CrashWatcher; }
|
||||
public CodexNodeGroup Group { get; }
|
||||
public IMarketplaceAccess Marketplace { get; }
|
||||
public DebugInfoVersion Version { get; private set; }
|
||||
public ITransferSpeeds TransferSpeeds { get => transferSpeeds; }
|
||||
|
||||
public IMetricsScrapeTarget MetricsScrapeTarget
|
||||
public StoragePurchase GetPurchaseStatus(string purchaseId)
|
||||
{
|
||||
get
|
||||
{
|
||||
return new MetricsScrapeTarget(CodexAccess.Container.Containers.First(), CodexContainerRecipe.MetricsPortTag);
|
||||
}
|
||||
return codexAccess.GetPurchaseStatus(purchaseId);
|
||||
}
|
||||
|
||||
public EthAddress EthAddress
|
||||
@ -105,7 +96,7 @@ namespace CodexPlugin
|
||||
get
|
||||
{
|
||||
EnsureMarketplace();
|
||||
return ethAccount!.EthAddress;
|
||||
return codexAccess.GetEthAccount()!.EthAddress;
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,13 +105,18 @@ namespace CodexPlugin
|
||||
get
|
||||
{
|
||||
EnsureMarketplace();
|
||||
return ethAccount!;
|
||||
return codexAccess.GetEthAccount()!;
|
||||
}
|
||||
}
|
||||
|
||||
public string GetName()
|
||||
{
|
||||
return Container.Name;
|
||||
return codexAccess.GetName();
|
||||
}
|
||||
|
||||
public string GetImageName()
|
||||
{
|
||||
return codexAccess.GetImageName();
|
||||
}
|
||||
|
||||
public string GetPeerId()
|
||||
@ -130,7 +126,7 @@ namespace CodexPlugin
|
||||
|
||||
public DebugInfo GetDebugInfo(bool log = false)
|
||||
{
|
||||
var debugInfo = CodexAccess.GetDebugInfo();
|
||||
var debugInfo = codexAccess.GetDebugInfo();
|
||||
if (log)
|
||||
{
|
||||
var known = string.Join(",", debugInfo.Table.Nodes.Select(n => n.PeerId));
|
||||
@ -141,25 +137,20 @@ namespace CodexPlugin
|
||||
|
||||
public string GetSpr()
|
||||
{
|
||||
return CodexAccess.GetSpr();
|
||||
return codexAccess.GetSpr();
|
||||
}
|
||||
|
||||
public DebugPeer GetDebugPeer(string peerId)
|
||||
{
|
||||
return CodexAccess.GetDebugPeer(peerId);
|
||||
return codexAccess.GetDebugPeer(peerId);
|
||||
}
|
||||
|
||||
public ContentId UploadFile(TrackedFile file)
|
||||
{
|
||||
return UploadFile(file, DoNothing);
|
||||
return UploadFile(file, "application/octet-stream", $"attachment; filename=\"{Path.GetFileName(file.Filename)}\"");
|
||||
}
|
||||
|
||||
public ContentId UploadFile(TrackedFile file, Action<Failure> onFailure)
|
||||
{
|
||||
return UploadFile(file, "application/octet-stream", $"attachment; filename=\"{Path.GetFileName(file.Filename)}\"", onFailure);
|
||||
}
|
||||
|
||||
public ContentId UploadFile(TrackedFile file, string contentType, string contentDisposition, Action<Failure> onFailure)
|
||||
public ContentId UploadFile(TrackedFile file, string contentType, string contentDisposition)
|
||||
{
|
||||
using var fileStream = File.OpenRead(file.Filename);
|
||||
var uniqueId = Guid.NewGuid().ToString();
|
||||
@ -171,7 +162,7 @@ namespace CodexPlugin
|
||||
var logMessage = $"Uploading file {file.Describe()} with contentType: '{input.ContentType}' and disposition: '{input.ContentDisposition}'...";
|
||||
var measurement = Stopwatch.Measure(log, logMessage, () =>
|
||||
{
|
||||
return CodexAccess.UploadFile(input, onFailure);
|
||||
return codexAccess.UploadFile(input);
|
||||
});
|
||||
|
||||
var response = measurement.Value;
|
||||
@ -189,17 +180,12 @@ namespace CodexPlugin
|
||||
|
||||
public TrackedFile? DownloadContent(ContentId contentId, string fileLabel = "")
|
||||
{
|
||||
return DownloadContent(contentId, DoNothing, fileLabel);
|
||||
}
|
||||
|
||||
public TrackedFile? DownloadContent(ContentId contentId, Action<Failure> onFailure, string fileLabel = "")
|
||||
{
|
||||
var file = tools.GetFileManager().CreateEmptyFile(fileLabel);
|
||||
var file = fileManager.CreateEmptyFile(fileLabel);
|
||||
hooks.OnFileDownloading(contentId);
|
||||
Log($"Downloading '{contentId}'...");
|
||||
|
||||
var logMessage = $"Downloaded '{contentId}' to '{file.Filename}'";
|
||||
var measurement = Stopwatch.Measure(log, logMessage, () => DownloadToFile(contentId.Id, file, onFailure));
|
||||
var measurement = Stopwatch.Measure(log, logMessage, () => DownloadToFile(contentId.Id, file));
|
||||
|
||||
var size = file.GetFilesize();
|
||||
transferSpeeds.AddDownloadSample(size, measurement);
|
||||
@ -211,7 +197,7 @@ namespace CodexPlugin
|
||||
public LocalDataset DownloadStreamless(ContentId cid)
|
||||
{
|
||||
Log($"Downloading streamless '{cid}' (no-wait)");
|
||||
return CodexAccess.DownloadStreamless(cid);
|
||||
return codexAccess.DownloadStreamless(cid);
|
||||
}
|
||||
|
||||
public LocalDataset DownloadStreamlessWait(ContentId cid, ByteSize size)
|
||||
@ -221,7 +207,7 @@ namespace CodexPlugin
|
||||
var sw = Stopwatch.Measure(log, nameof(DownloadStreamlessWait), () =>
|
||||
{
|
||||
var startSpace = Space();
|
||||
var result = CodexAccess.DownloadStreamless(cid);
|
||||
var result = codexAccess.DownloadStreamless(cid);
|
||||
WaitUntilQuotaUsedIncreased(startSpace, size);
|
||||
return result;
|
||||
});
|
||||
@ -232,17 +218,17 @@ namespace CodexPlugin
|
||||
public LocalDataset DownloadManifestOnly(ContentId cid)
|
||||
{
|
||||
Log($"Downloading manifest-only '{cid}'");
|
||||
return CodexAccess.DownloadManifestOnly(cid);
|
||||
return codexAccess.DownloadManifestOnly(cid);
|
||||
}
|
||||
|
||||
public LocalDatasetList LocalFiles()
|
||||
{
|
||||
return CodexAccess.LocalFiles();
|
||||
return codexAccess.LocalFiles();
|
||||
}
|
||||
|
||||
public CodexSpace Space()
|
||||
{
|
||||
return CodexAccess.Space();
|
||||
return codexAccess.Space();
|
||||
}
|
||||
|
||||
public void ConnectToPeer(ICodexNode node)
|
||||
@ -251,47 +237,53 @@ namespace CodexPlugin
|
||||
|
||||
Log($"Connecting to peer {peer.GetName()}...");
|
||||
var peerInfo = node.GetDebugInfo();
|
||||
CodexAccess.ConnectToPeer(peerInfo.Id, GetPeerMultiAddresses(peer, peerInfo));
|
||||
codexAccess.ConnectToPeer(peerInfo.Id, GetPeerMultiAddresses(peer, peerInfo));
|
||||
|
||||
Log($"Successfully connected to peer {peer.GetName()}.");
|
||||
}
|
||||
|
||||
public PodInfo GetPodInfo()
|
||||
public void DeleteDataDirFolder()
|
||||
{
|
||||
return CodexAccess.GetPodInfo();
|
||||
}
|
||||
|
||||
public void DeleteRepoFolder()
|
||||
{
|
||||
CodexAccess.DeleteRepoFolder();
|
||||
codexAccess.DeleteDataDirFolder();
|
||||
}
|
||||
|
||||
public void Stop(bool waitTillStopped)
|
||||
{
|
||||
Log("Stopping...");
|
||||
hooks.OnNodeStopping();
|
||||
|
||||
CrashWatcher.Stop();
|
||||
Group.Stop(this, waitTillStopped);
|
||||
codexAccess.Stop(waitTillStopped);
|
||||
}
|
||||
|
||||
public void EnsureOnlineGetVersionResponse()
|
||||
public IDownloadedLog DownloadLog(string additionalName = "")
|
||||
{
|
||||
var debugInfo = Time.Retry(CodexAccess.GetDebugInfo, "ensure online");
|
||||
peerId = debugInfo.Id;
|
||||
nodeId = debugInfo.Table.LocalNode.NodeId;
|
||||
var nodeName = CodexAccess.Container.Name;
|
||||
return codexAccess.DownloadLog(additionalName);
|
||||
}
|
||||
|
||||
if (!debugInfo.Version.IsValid())
|
||||
{
|
||||
throw new Exception($"Invalid version information received from Codex node {GetName()}: {debugInfo.Version}");
|
||||
}
|
||||
public Address GetDiscoveryEndpoint()
|
||||
{
|
||||
return codexAccess.GetDiscoveryEndpoint();
|
||||
}
|
||||
|
||||
log.AddStringReplace(peerId, nodeName);
|
||||
log.AddStringReplace(CodexUtils.ToShortId(peerId), nodeName);
|
||||
log.AddStringReplace(debugInfo.Table.LocalNode.NodeId, nodeName);
|
||||
log.AddStringReplace(CodexUtils.ToShortId(debugInfo.Table.LocalNode.NodeId), nodeName);
|
||||
Version = debugInfo.Version;
|
||||
public Address GetApiEndpoint()
|
||||
{
|
||||
return codexAccess.GetApiEndpoint();
|
||||
}
|
||||
|
||||
public Address GetListenEndpoint()
|
||||
{
|
||||
return codexAccess.GetListenEndpoint();
|
||||
}
|
||||
|
||||
public Address GetMetricsScrapeTarget()
|
||||
{
|
||||
var address = codexAccess.GetMetricsEndpoint();
|
||||
if (address == null) throw new Exception("Metrics ScrapeTarget accessed, but node was not started with EnableMetrics()");
|
||||
return address;
|
||||
}
|
||||
|
||||
public bool HasCrashed()
|
||||
{
|
||||
return codexAccess.HasCrashed();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
@ -299,22 +291,44 @@ namespace CodexPlugin
|
||||
return $"CodexNode:{GetName()}";
|
||||
}
|
||||
|
||||
private void InitializePeerNodeId()
|
||||
{
|
||||
var debugInfo = Time.Retry(codexAccess.GetDebugInfo, "ensure online");
|
||||
if (!debugInfo.Version.IsValid())
|
||||
{
|
||||
throw new Exception($"Invalid version information received from Codex node {GetName()}: {debugInfo.Version}");
|
||||
}
|
||||
|
||||
peerId = debugInfo.Id;
|
||||
nodeId = debugInfo.Table.LocalNode.NodeId;
|
||||
Version = debugInfo.Version;
|
||||
}
|
||||
|
||||
private void InitializeLogReplacements()
|
||||
{
|
||||
var nodeName = GetName();
|
||||
|
||||
log.AddStringReplace(peerId, nodeName);
|
||||
log.AddStringReplace(CodexUtils.ToShortId(peerId), nodeName);
|
||||
log.AddStringReplace(nodeId, nodeName);
|
||||
log.AddStringReplace(CodexUtils.ToShortId(nodeId), nodeName);
|
||||
}
|
||||
|
||||
private string[] GetPeerMultiAddresses(CodexNode peer, DebugInfo peerInfo)
|
||||
{
|
||||
// The peer we want to connect is in a different pod.
|
||||
// We must replace the default IP with the pod IP in the multiAddress.
|
||||
var workflow = tools.CreateWorkflow();
|
||||
var podInfo = workflow.GetPodInfo(peer.Pod);
|
||||
var peerId = peer.GetDiscoveryEndpoint().Host
|
||||
.Replace("http://", "")
|
||||
.Replace("https://", "");
|
||||
|
||||
return peerInfo.Addrs.Select(a => a
|
||||
.Replace("0.0.0.0", podInfo.Ip))
|
||||
.Replace("0.0.0.0", peerId))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private void DownloadToFile(string contentId, TrackedFile file, Action<Failure> onFailure)
|
||||
private void DownloadToFile(string contentId, TrackedFile file)
|
||||
{
|
||||
using var fileStream = File.OpenWrite(file.Filename);
|
||||
var timeout = tools.TimeSet.HttpCallTimeout();
|
||||
var timeout = TimeSpan.FromMinutes(2.0); // todo: make this user-controllable.
|
||||
try
|
||||
{
|
||||
// Type of stream generated by openAPI client does not support timeouts.
|
||||
@ -322,7 +336,7 @@ namespace CodexPlugin
|
||||
var cts = new CancellationTokenSource();
|
||||
var downloadTask = Task.Run(() =>
|
||||
{
|
||||
using var downloadStream = CodexAccess.DownloadFile(contentId, onFailure);
|
||||
using var downloadStream = codexAccess.DownloadFile(contentId);
|
||||
downloadStream.CopyTo(fileStream);
|
||||
}, cts.Token);
|
||||
|
||||
@ -336,9 +350,9 @@ namespace CodexPlugin
|
||||
cts.Cancel();
|
||||
throw new TimeoutException($"Download of '{contentId}' timed out after {Time.FormatDuration(timeout)}");
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"Failed to download file '{contentId}'.");
|
||||
Log($"Failed to download file '{contentId}': {ex}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@ -378,16 +392,12 @@ namespace CodexPlugin
|
||||
|
||||
private void EnsureMarketplace()
|
||||
{
|
||||
if (ethAccount == null) throw new Exception("Marketplace is not enabled for this Codex node. Please start it with the option '.EnableMarketplace(...)' to enable it.");
|
||||
if (codexAccess.GetEthAccount() == null) throw new Exception("Marketplace is not enabled for this Codex node. Please start it with the option '.EnableMarketplace(...)' to enable it.");
|
||||
}
|
||||
|
||||
private void Log(string msg)
|
||||
{
|
||||
log.Log(msg);
|
||||
}
|
||||
|
||||
private void DoNothing(Failure failure)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
47
ProjectPlugins/CodexClient/CodexNodeFactory.cs
Normal file
47
ProjectPlugins/CodexClient/CodexNodeFactory.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using CodexClient.Hooks;
|
||||
using FileUtils;
|
||||
using Logging;
|
||||
using WebUtils;
|
||||
|
||||
namespace CodexClient
|
||||
{
|
||||
public class CodexNodeFactory
|
||||
{
|
||||
private readonly ILog log;
|
||||
private readonly IFileManager fileManager;
|
||||
private readonly CodexHooksFactory hooksFactory;
|
||||
private readonly IHttpFactory httpFactory;
|
||||
private readonly IProcessControlFactory processControlFactory;
|
||||
|
||||
public CodexNodeFactory(ILog log, IFileManager fileManager, CodexHooksFactory hooksFactory, IHttpFactory httpFactory, IProcessControlFactory processControlFactory)
|
||||
{
|
||||
this.log = log;
|
||||
this.fileManager = fileManager;
|
||||
this.hooksFactory = hooksFactory;
|
||||
this.httpFactory = httpFactory;
|
||||
this.processControlFactory = processControlFactory;
|
||||
}
|
||||
|
||||
public CodexNodeFactory(ILog log, string dataDir)
|
||||
: this(log, new FileManager(log, dataDir), new CodexHooksFactory(), new HttpFactory(log), new DoNothingProcessControlFactory())
|
||||
{
|
||||
}
|
||||
|
||||
public ICodexNode CreateCodexNode(ICodexInstance instance)
|
||||
{
|
||||
var processControl = processControlFactory.CreateProcessControl(instance);
|
||||
var access = new CodexAccess(log, httpFactory, processControl, instance);
|
||||
var hooks = hooksFactory.CreateHooks(access.GetName());
|
||||
var marketplaceAccess = CreateMarketplaceAccess(instance, access, hooks);
|
||||
var node = new CodexNode(log, access, fileManager, marketplaceAccess, hooks);
|
||||
node.Initialize();
|
||||
return node;
|
||||
}
|
||||
|
||||
private IMarketplaceAccess CreateMarketplaceAccess(ICodexInstance instance, CodexAccess access, ICodexNodeHooks hooks)
|
||||
{
|
||||
if (instance.EthAccount == null) return new MarketplaceUnavailable();
|
||||
return new MarketplaceAccess(log, access, hooks);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
using Newtonsoft.Json;
|
||||
using Utils;
|
||||
|
||||
namespace CodexPlugin
|
||||
namespace CodexClient
|
||||
{
|
||||
public class DebugInfo
|
||||
{
|
||||
@ -1,4 +1,4 @@
|
||||
namespace CodexPlugin
|
||||
namespace CodexClient
|
||||
{
|
||||
public static class CodexUtils
|
||||
{
|
||||
@ -1,7 +1,6 @@
|
||||
using GethPlugin;
|
||||
using Utils;
|
||||
using Utils;
|
||||
|
||||
namespace CodexPlugin.Hooks
|
||||
namespace CodexClient.Hooks
|
||||
{
|
||||
public interface ICodexHooksProvider
|
||||
{
|
||||
@ -10,11 +9,14 @@ namespace CodexPlugin.Hooks
|
||||
|
||||
public class CodexHooksFactory
|
||||
{
|
||||
public ICodexHooksProvider Provider { get; set; } = new DoNothingHooksProvider();
|
||||
public List<ICodexHooksProvider> Providers { get; } = new List<ICodexHooksProvider>();
|
||||
|
||||
public ICodexNodeHooks CreateHooks(string nodeName)
|
||||
{
|
||||
return Provider.CreateHooks(nodeName);
|
||||
if (Providers.Count == 0) return new DoNothingCodexHooks();
|
||||
|
||||
var hooks = Providers.Select(p => p.CreateHooks(nodeName)).ToArray();
|
||||
return new MuxingCodexNodeHooks(hooks);
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,7 +46,7 @@ namespace CodexPlugin.Hooks
|
||||
{
|
||||
}
|
||||
|
||||
public void OnNodeStarted(string peerId, string nodeId)
|
||||
public void OnNodeStarted(ICodexNode node, string peerId, string nodeId)
|
||||
{
|
||||
}
|
||||
|
||||
78
ProjectPlugins/CodexClient/Hooks/CodexNodeHooks.cs
Normal file
78
ProjectPlugins/CodexClient/Hooks/CodexNodeHooks.cs
Normal file
@ -0,0 +1,78 @@
|
||||
using Utils;
|
||||
|
||||
namespace CodexClient.Hooks
|
||||
{
|
||||
public interface ICodexNodeHooks
|
||||
{
|
||||
void OnNodeStarting(DateTime startUtc, string image, EthAccount? ethAccount);
|
||||
void OnNodeStarted(ICodexNode node, string peerId, string nodeId);
|
||||
void OnNodeStopping();
|
||||
void OnFileUploading(string uid, ByteSize size);
|
||||
void OnFileUploaded(string uid, ByteSize size, ContentId cid);
|
||||
void OnFileDownloading(ContentId cid);
|
||||
void OnFileDownloaded(ByteSize size, ContentId cid);
|
||||
void OnStorageContractSubmitted(StoragePurchaseContract storagePurchaseContract);
|
||||
void OnStorageContractUpdated(StoragePurchase purchaseStatus);
|
||||
void OnStorageAvailabilityCreated(StorageAvailability response);
|
||||
}
|
||||
|
||||
public class MuxingCodexNodeHooks : ICodexNodeHooks
|
||||
{
|
||||
private readonly ICodexNodeHooks[] backingHooks;
|
||||
|
||||
public MuxingCodexNodeHooks(ICodexNodeHooks[] backingHooks)
|
||||
{
|
||||
this.backingHooks = backingHooks;
|
||||
}
|
||||
|
||||
public void OnFileDownloaded(ByteSize size, ContentId cid)
|
||||
{
|
||||
foreach (var h in backingHooks) h.OnFileDownloaded(size, cid);
|
||||
}
|
||||
|
||||
public void OnFileDownloading(ContentId cid)
|
||||
{
|
||||
foreach (var h in backingHooks) h.OnFileDownloading(cid);
|
||||
}
|
||||
|
||||
public void OnFileUploaded(string uid, ByteSize size, ContentId cid)
|
||||
{
|
||||
foreach (var h in backingHooks) h.OnFileUploaded(uid, size, cid);
|
||||
}
|
||||
|
||||
public void OnFileUploading(string uid, ByteSize size)
|
||||
{
|
||||
foreach (var h in backingHooks) h.OnFileUploading(uid, size);
|
||||
}
|
||||
|
||||
public void OnNodeStarted(ICodexNode node, string peerId, string nodeId)
|
||||
{
|
||||
foreach (var h in backingHooks) h.OnNodeStarted(node, peerId, nodeId);
|
||||
}
|
||||
|
||||
public void OnNodeStarting(DateTime startUtc, string image, EthAccount? ethAccount)
|
||||
{
|
||||
foreach (var h in backingHooks) h.OnNodeStarting(startUtc, image, ethAccount);
|
||||
}
|
||||
|
||||
public void OnNodeStopping()
|
||||
{
|
||||
foreach (var h in backingHooks) h.OnNodeStopping();
|
||||
}
|
||||
|
||||
public void OnStorageAvailabilityCreated(StorageAvailability response)
|
||||
{
|
||||
foreach (var h in backingHooks) h.OnStorageAvailabilityCreated(response);
|
||||
}
|
||||
|
||||
public void OnStorageContractSubmitted(StoragePurchaseContract storagePurchaseContract)
|
||||
{
|
||||
foreach (var h in backingHooks) h.OnStorageContractSubmitted(storagePurchaseContract);
|
||||
}
|
||||
|
||||
public void OnStorageContractUpdated(StoragePurchase purchaseStatus)
|
||||
{
|
||||
foreach (var h in backingHooks) h.OnStorageContractUpdated(purchaseStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,8 @@
|
||||
using CodexContractsPlugin;
|
||||
using CodexOpenApi;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Numerics;
|
||||
using Utils;
|
||||
|
||||
namespace CodexPlugin
|
||||
namespace CodexClient
|
||||
{
|
||||
public class Mapper
|
||||
{
|
||||
@ -80,12 +78,12 @@ namespace CodexPlugin
|
||||
};
|
||||
}
|
||||
|
||||
public StorageAvailability[] Map(ICollection<SalesAvailabilityREAD> availabilities)
|
||||
public StorageAvailability[] Map(ICollection<CodexOpenApi.SalesAvailabilityREAD> availabilities)
|
||||
{
|
||||
return availabilities.Select(a => Map(a)).ToArray();
|
||||
}
|
||||
|
||||
public StorageAvailability Map(SalesAvailabilityREAD availability)
|
||||
public StorageAvailability Map(CodexOpenApi.SalesAvailabilityREAD availability)
|
||||
{
|
||||
return new StorageAvailability
|
||||
(
|
||||
@ -142,7 +140,7 @@ namespace CodexPlugin
|
||||
// };
|
||||
//}
|
||||
|
||||
public CodexSpace Map(Space space)
|
||||
public CodexSpace Map(CodexOpenApi.Space space)
|
||||
{
|
||||
return new CodexSpace
|
||||
{
|
||||
@ -153,7 +151,7 @@ namespace CodexPlugin
|
||||
};
|
||||
}
|
||||
|
||||
private DebugInfoVersion Map(CodexVersion obj)
|
||||
private DebugInfoVersion Map(CodexOpenApi.CodexVersion obj)
|
||||
{
|
||||
return new DebugInfoVersion
|
||||
{
|
||||
@ -162,7 +160,7 @@ namespace CodexPlugin
|
||||
};
|
||||
}
|
||||
|
||||
private DebugInfoTable Map(PeersTable obj)
|
||||
private DebugInfoTable Map(CodexOpenApi.PeersTable obj)
|
||||
{
|
||||
return new DebugInfoTable
|
||||
{
|
||||
@ -171,7 +169,7 @@ namespace CodexPlugin
|
||||
};
|
||||
}
|
||||
|
||||
private DebugInfoTableNode Map(Node? token)
|
||||
private DebugInfoTableNode Map(CodexOpenApi.Node? token)
|
||||
{
|
||||
if (token == null) return new DebugInfoTableNode();
|
||||
return new DebugInfoTableNode
|
||||
@ -184,7 +182,7 @@ namespace CodexPlugin
|
||||
};
|
||||
}
|
||||
|
||||
private DebugInfoTableNode[] Map(ICollection<Node> nodes)
|
||||
private DebugInfoTableNode[] Map(ICollection<CodexOpenApi.Node> nodes)
|
||||
{
|
||||
if (nodes == null || nodes.Count == 0)
|
||||
{
|
||||
@ -1,8 +1,8 @@
|
||||
using CodexPlugin.Hooks;
|
||||
using CodexClient.Hooks;
|
||||
using Logging;
|
||||
using Utils;
|
||||
|
||||
namespace CodexPlugin
|
||||
namespace CodexClient
|
||||
{
|
||||
public interface IMarketplaceAccess
|
||||
{
|
||||
@ -72,7 +72,7 @@ namespace CodexPlugin
|
||||
|
||||
private void Log(string msg)
|
||||
{
|
||||
log.Log($"{codexAccess.Container.Containers.Single().Name} {msg}");
|
||||
log.Log($"{codexAccess.GetName()} {msg}");
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
using CodexContractsPlugin;
|
||||
using Logging;
|
||||
using Logging;
|
||||
using Utils;
|
||||
|
||||
namespace CodexPlugin
|
||||
namespace CodexClient
|
||||
{
|
||||
public class StoragePurchaseRequest
|
||||
{
|
||||
46
ProjectPlugins/CodexClient/ProcessControl.cs
Normal file
46
ProjectPlugins/CodexClient/ProcessControl.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using Logging;
|
||||
|
||||
namespace CodexClient
|
||||
{
|
||||
public interface IProcessControlFactory
|
||||
{
|
||||
IProcessControl CreateProcessControl(ICodexInstance instance);
|
||||
}
|
||||
|
||||
public interface IProcessControl
|
||||
{
|
||||
void Stop(bool waitTillStopped);
|
||||
IDownloadedLog DownloadLog(LogFile file);
|
||||
void DeleteDataDirFolder();
|
||||
bool HasCrashed();
|
||||
}
|
||||
|
||||
public class DoNothingProcessControlFactory : IProcessControlFactory
|
||||
{
|
||||
public IProcessControl CreateProcessControl(ICodexInstance instance)
|
||||
{
|
||||
return new DoNothingProcessControl();
|
||||
}
|
||||
}
|
||||
|
||||
public class DoNothingProcessControl : IProcessControl
|
||||
{
|
||||
public void DeleteDataDirFolder()
|
||||
{
|
||||
}
|
||||
|
||||
public IDownloadedLog DownloadLog(LogFile file)
|
||||
{
|
||||
throw new NotImplementedException("Not supported by DoNothingProcessControl");
|
||||
}
|
||||
|
||||
public bool HasCrashed()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Stop(bool waitTillStopped)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,9 @@
|
||||
using CodexContractsPlugin;
|
||||
using CodexPlugin.Hooks;
|
||||
using GethPlugin;
|
||||
using CodexClient.Hooks;
|
||||
using Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Utils;
|
||||
|
||||
namespace CodexPlugin
|
||||
namespace CodexClient
|
||||
{
|
||||
public interface IStoragePurchaseContract
|
||||
{
|
||||
@ -15,8 +13,9 @@ namespace CodexPlugin
|
||||
StoragePurchase GetStatus();
|
||||
void WaitForStorageContractSubmitted();
|
||||
void WaitForStorageContractStarted();
|
||||
void WaitForStorageContractFinished(ICodexContracts contracts);
|
||||
void WaitForStorageContractFinished();
|
||||
void WaitForContractFailed();
|
||||
StoragePurchase GetPurchaseStatus();
|
||||
}
|
||||
|
||||
public class StoragePurchaseContract : IStoragePurchaseContract
|
||||
@ -71,7 +70,7 @@ namespace CodexPlugin
|
||||
AssertDuration(SubmittedToStarted, timeout, nameof(SubmittedToStarted));
|
||||
}
|
||||
|
||||
public void WaitForStorageContractFinished(ICodexContracts contracts)
|
||||
public void WaitForStorageContractFinished()
|
||||
{
|
||||
if (!contractStartedUtc.HasValue)
|
||||
{
|
||||
@ -83,13 +82,6 @@ namespace CodexPlugin
|
||||
contractFinishedUtc = DateTime.UtcNow;
|
||||
LogFinishedDuration();
|
||||
AssertDuration(SubmittedToFinished, timeout, nameof(SubmittedToFinished));
|
||||
|
||||
contracts.WaitUntilNextPeriod();
|
||||
contracts.WaitUntilNextPeriod();
|
||||
|
||||
var blocks = 3;
|
||||
Log($"Waiting {blocks} blocks for nodes to process payouts...");
|
||||
Thread.Sleep(GethContainerRecipe.BlockInterval * blocks);
|
||||
}
|
||||
|
||||
public void WaitForContractFailed()
|
||||
@ -103,9 +95,9 @@ namespace CodexPlugin
|
||||
WaitForStorageContractState(timeout, "failed");
|
||||
}
|
||||
|
||||
public StoragePurchase GetPurchaseStatus(string purchaseId)
|
||||
public StoragePurchase GetPurchaseStatus()
|
||||
{
|
||||
return codexAccess.GetPurchaseStatus(purchaseId);
|
||||
return codexAccess.GetPurchaseStatus(PurchaseId);
|
||||
}
|
||||
|
||||
private void WaitForStorageContractState(TimeSpan timeout, string desiredState, int sleep = 1000)
|
||||
@ -1,6 +1,6 @@
|
||||
using Utils;
|
||||
|
||||
namespace CodexPlugin
|
||||
namespace CodexClient
|
||||
{
|
||||
public interface ITransferSpeeds
|
||||
{
|
||||
@ -1,5 +1,5 @@
|
||||
using GethPlugin;
|
||||
using System.Numerics;
|
||||
using System.Numerics;
|
||||
using Utils;
|
||||
|
||||
namespace CodexContractsPlugin.ChainMonitor
|
||||
{
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
using CodexContractsPlugin.Marketplace;
|
||||
using GethPlugin;
|
||||
using Logging;
|
||||
using Utils;
|
||||
|
||||
namespace CodexContractsPlugin.ChainMonitor
|
||||
{
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
using GethPlugin;
|
||||
using System.Numerics;
|
||||
using System.Numerics;
|
||||
using Utils;
|
||||
|
||||
namespace CodexContractsPlugin.ChainMonitor
|
||||
{
|
||||
|
||||
@ -5,8 +5,8 @@ using Logging;
|
||||
using Nethereum.ABI.FunctionEncoding.Attributes;
|
||||
using Nethereum.Contracts;
|
||||
using Nethereum.Hex.HexConvertors.Extensions;
|
||||
using NethereumWorkflow;
|
||||
using System.Numerics;
|
||||
using Utils;
|
||||
|
||||
namespace CodexContractsPlugin
|
||||
{
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
using BlockchainUtils;
|
||||
using GethPlugin;
|
||||
using Newtonsoft.Json;
|
||||
using Utils;
|
||||
|
||||
namespace CodexContractsPlugin.Marketplace
|
||||
{
|
||||
|
||||
@ -82,12 +82,12 @@ namespace CodexPlugin
|
||||
private void OverwriteOpenApiYaml(string containerApi)
|
||||
{
|
||||
Log("API compatibility check failed. Updating CodexPlugin...");
|
||||
var openApiFilePath = Path.Combine(PluginPathUtils.ProjectPluginsDir, "CodexPlugin", "openapi.yaml");
|
||||
if (!File.Exists(openApiFilePath)) throw new Exception("Unable to locate CodexPlugin/openapi.yaml. Expected: " + openApiFilePath);
|
||||
var openApiFilePath = Path.Combine(PluginPathUtils.ProjectPluginsDir, "CodexClient", "openapi.yaml");
|
||||
if (!File.Exists(openApiFilePath)) throw new Exception("Unable to locate CodexClient/openapi.yaml. Expected: " + openApiFilePath);
|
||||
|
||||
File.Delete(openApiFilePath);
|
||||
File.WriteAllText(openApiFilePath, containerApi);
|
||||
Log("CodexPlugin/openapi.yaml has been updated.");
|
||||
Log("CodexClient/openapi.yaml has been updated.");
|
||||
}
|
||||
|
||||
private string Hash(string file)
|
||||
|
||||
156
ProjectPlugins/CodexPlugin/BinaryCodexStarter.cs
Normal file
156
ProjectPlugins/CodexPlugin/BinaryCodexStarter.cs
Normal file
@ -0,0 +1,156 @@
|
||||
using CodexClient;
|
||||
using Core;
|
||||
using Utils;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace CodexPlugin
|
||||
{
|
||||
public class BinaryCodexStarter : ICodexStarter
|
||||
{
|
||||
private readonly IPluginTools pluginTools;
|
||||
private readonly ProcessControlMap processControlMap;
|
||||
private readonly static NumberSource numberSource = new NumberSource(1);
|
||||
private readonly static FreePortFinder freePortFinder = new FreePortFinder();
|
||||
private readonly static object _lock = new object();
|
||||
private readonly static string dataParentDir = "codex_disttest_datadirs";
|
||||
private readonly static CodexExePath codexExePath = new CodexExePath();
|
||||
|
||||
static BinaryCodexStarter()
|
||||
{
|
||||
StopAllCodexProcesses();
|
||||
DeleteParentDataDir();
|
||||
}
|
||||
|
||||
public BinaryCodexStarter(IPluginTools pluginTools, ProcessControlMap processControlMap)
|
||||
{
|
||||
this.pluginTools = pluginTools;
|
||||
this.processControlMap = processControlMap;
|
||||
}
|
||||
|
||||
public ICodexInstance[] BringOnline(CodexSetup codexSetup)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
LogSeparator();
|
||||
Log($"Starting {codexSetup.Describe()}...");
|
||||
|
||||
return StartCodexBinaries(codexSetup, codexSetup.NumberOfNodes);
|
||||
}
|
||||
}
|
||||
|
||||
public void Decommission()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
processControlMap.StopAll();
|
||||
}
|
||||
}
|
||||
|
||||
private ICodexInstance[] StartCodexBinaries(CodexStartupConfig startupConfig, int numberOfNodes)
|
||||
{
|
||||
var result = new List<ICodexInstance>();
|
||||
for (var i = 0; i < numberOfNodes; i++)
|
||||
{
|
||||
result.Add(StartBinary(startupConfig));
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
private ICodexInstance StartBinary(CodexStartupConfig config)
|
||||
{
|
||||
var name = GetName(config);
|
||||
var dataDir = Path.Combine(dataParentDir, $"datadir_{numberSource.GetNextNumber()}");
|
||||
var pconfig = new CodexProcessConfig(name, freePortFinder, dataDir);
|
||||
Log(pconfig);
|
||||
|
||||
var factory = new CodexProcessRecipe(pconfig, codexExePath);
|
||||
var recipe = factory.Initialize(config);
|
||||
|
||||
var startInfo = new ProcessStartInfo(
|
||||
fileName: recipe.Cmd,
|
||||
arguments: recipe.Args
|
||||
);
|
||||
//startInfo.UseShellExecute = true;
|
||||
startInfo.RedirectStandardOutput = true;
|
||||
startInfo.RedirectStandardError = true;
|
||||
|
||||
var process = Process.Start(startInfo);
|
||||
if (process == null || process.HasExited)
|
||||
{
|
||||
throw new Exception("Failed to start");
|
||||
}
|
||||
|
||||
var local = "localhost";
|
||||
var instance = new CodexInstance(
|
||||
name: name,
|
||||
imageName: "binary",
|
||||
startUtc: DateTime.UtcNow,
|
||||
discoveryEndpoint: new Address("Disc", pconfig.LocalIpAddrs.ToString(), pconfig.DiscPort),
|
||||
apiEndpoint: new Address("Api", "http://" + local, pconfig.ApiPort),
|
||||
listenEndpoint: new Address("Listen", local, pconfig.ListenPort),
|
||||
ethAccount: null,
|
||||
metricsEndpoint: null
|
||||
);
|
||||
|
||||
var pc = new BinaryProcessControl(pluginTools.GetLog(), process, pconfig);
|
||||
processControlMap.Add(instance, pc);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
private string GetName(CodexStartupConfig config)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(config.NameOverride))
|
||||
{
|
||||
return config.NameOverride + "_" + numberSource.GetNextNumber();
|
||||
}
|
||||
return "codex_" + numberSource.GetNextNumber();
|
||||
}
|
||||
|
||||
private void LogSeparator()
|
||||
{
|
||||
Log("----------------------------------------------------------------------------");
|
||||
}
|
||||
|
||||
private void Log(CodexProcessConfig pconfig)
|
||||
{
|
||||
Log(
|
||||
"NodeConfig:Name=" + pconfig.Name +
|
||||
"ApiPort=" + pconfig.ApiPort +
|
||||
"DiscPort=" + pconfig.DiscPort +
|
||||
"ListenPort=" + pconfig.ListenPort +
|
||||
"DataDir=" + pconfig.DataDir
|
||||
);
|
||||
}
|
||||
|
||||
private void Log(string message)
|
||||
{
|
||||
pluginTools.GetLog().Log(message);
|
||||
}
|
||||
|
||||
private static void DeleteParentDataDir()
|
||||
{
|
||||
if (Directory.Exists(dataParentDir))
|
||||
{
|
||||
Directory.Delete(dataParentDir, true);
|
||||
}
|
||||
}
|
||||
|
||||
private static void StopAllCodexProcesses()
|
||||
{
|
||||
var processes = Process.GetProcesses();
|
||||
var codexes = processes.Where(p =>
|
||||
p.ProcessName.ToLowerInvariant() == "codex" &&
|
||||
p.MainModule != null &&
|
||||
p.MainModule.FileName == codexExePath.Get()
|
||||
).ToArray();
|
||||
|
||||
foreach (var c in codexes)
|
||||
{
|
||||
c.Kill();
|
||||
c.WaitForExit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
93
ProjectPlugins/CodexPlugin/BinaryProcessControl.cs
Normal file
93
ProjectPlugins/CodexPlugin/BinaryProcessControl.cs
Normal file
@ -0,0 +1,93 @@
|
||||
using System.Diagnostics;
|
||||
using CodexClient;
|
||||
using Logging;
|
||||
|
||||
namespace CodexPlugin
|
||||
{
|
||||
public class BinaryProcessControl : IProcessControl
|
||||
{
|
||||
private readonly LogFile logFile;
|
||||
private readonly Process process;
|
||||
private readonly CodexProcessConfig config;
|
||||
private List<string> logBuffer = new List<string>();
|
||||
private readonly object bufferLock = new object();
|
||||
private readonly List<Task> streamTasks = new List<Task>();
|
||||
private bool running;
|
||||
|
||||
public BinaryProcessControl(ILog log, Process process, CodexProcessConfig config)
|
||||
{
|
||||
logFile = log.CreateSubfile(config.Name);
|
||||
|
||||
running = true;
|
||||
this.process = process;
|
||||
this.config = config;
|
||||
streamTasks.Add(Task.Run(() => ReadProcessStream(process.StandardOutput)));
|
||||
streamTasks.Add(Task.Run(() => ReadProcessStream(process.StandardError)));
|
||||
streamTasks.Add(Task.Run(() => WriteLog()));
|
||||
}
|
||||
|
||||
private void ReadProcessStream(StreamReader reader)
|
||||
{
|
||||
while (running)
|
||||
{
|
||||
var line = reader.ReadLine();
|
||||
if (!string.IsNullOrEmpty(line))
|
||||
{
|
||||
lock (bufferLock)
|
||||
{
|
||||
logBuffer.Add(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteLog()
|
||||
{
|
||||
while (running || logBuffer.Count > 0)
|
||||
{
|
||||
if (logBuffer.Count > 0)
|
||||
{
|
||||
List<string> lines = null!;
|
||||
lock (bufferLock)
|
||||
{
|
||||
lines = logBuffer;
|
||||
logBuffer = new List<string>();
|
||||
}
|
||||
|
||||
logFile.WriteRawMany(lines);
|
||||
}
|
||||
else Thread.Sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteDataDirFolder()
|
||||
{
|
||||
if (!Directory.Exists(config.DataDir)) throw new Exception("datadir not found");
|
||||
Directory.Delete(config.DataDir, true);
|
||||
}
|
||||
|
||||
public IDownloadedLog DownloadLog(LogFile file)
|
||||
{
|
||||
return new DownloadedLog(logFile, config.Name);
|
||||
}
|
||||
|
||||
public bool HasCrashed()
|
||||
{
|
||||
return process.HasExited;
|
||||
}
|
||||
|
||||
public void Stop(bool waitTillStopped)
|
||||
{
|
||||
running = false;
|
||||
process.Kill();
|
||||
|
||||
if (waitTillStopped)
|
||||
{
|
||||
process.WaitForExit();
|
||||
foreach (var t in streamTasks) t.Wait();
|
||||
}
|
||||
|
||||
DeleteDataDirFolder();
|
||||
}
|
||||
}
|
||||
}
|
||||
70
ProjectPlugins/CodexPlugin/CodexContainerProcessControl.cs
Normal file
70
ProjectPlugins/CodexPlugin/CodexContainerProcessControl.cs
Normal file
@ -0,0 +1,70 @@
|
||||
using CodexClient;
|
||||
using Core;
|
||||
using KubernetesWorkflow;
|
||||
using KubernetesWorkflow.Types;
|
||||
using Logging;
|
||||
|
||||
namespace CodexPlugin
|
||||
{
|
||||
public class CodexContainerProcessControl : IProcessControl
|
||||
{
|
||||
private readonly IPluginTools tools;
|
||||
private readonly RunningPod pod;
|
||||
private readonly Action onStop;
|
||||
private readonly ContainerCrashWatcher crashWatcher;
|
||||
|
||||
public CodexContainerProcessControl(IPluginTools tools, RunningPod pod, Action onStop)
|
||||
{
|
||||
this.tools = tools;
|
||||
this.pod = pod;
|
||||
this.onStop = onStop;
|
||||
|
||||
crashWatcher = tools.CreateWorkflow().CreateCrashWatcher(pod.Containers.Single());
|
||||
crashWatcher.Start();
|
||||
}
|
||||
|
||||
public void Stop(bool waitTillStopped)
|
||||
{
|
||||
Log($"Stopping node...");
|
||||
var workflow = tools.CreateWorkflow();
|
||||
workflow.Stop(pod, waitTillStopped);
|
||||
crashWatcher.Stop();
|
||||
onStop();
|
||||
Log("Stopped.");
|
||||
}
|
||||
|
||||
public IDownloadedLog DownloadLog(LogFile file)
|
||||
{
|
||||
var workflow = tools.CreateWorkflow();
|
||||
return workflow.DownloadContainerLog(pod.Containers.Single());
|
||||
}
|
||||
|
||||
public void DeleteDataDirFolder()
|
||||
{
|
||||
var container = pod.Containers.Single();
|
||||
|
||||
try
|
||||
{
|
||||
var dataDirVar = container.Recipe.EnvVars.Single(e => e.Name == "CODEX_DATA_DIR");
|
||||
var dataDir = dataDirVar.Value;
|
||||
var workflow = tools.CreateWorkflow();
|
||||
workflow.ExecuteCommand(container, "rm", "-Rfv", $"/codex/{dataDir}/repo");
|
||||
Log("Deleted repo folder.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log("Unable to delete repo folder: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasCrashed()
|
||||
{
|
||||
return crashWatcher.HasCrashed();
|
||||
}
|
||||
|
||||
private void Log(string message)
|
||||
{
|
||||
tools.GetLog().Log(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
using CodexContractsPlugin;
|
||||
using CodexClient;
|
||||
using CodexContractsPlugin;
|
||||
using GethPlugin;
|
||||
using KubernetesWorkflow.Types;
|
||||
|
||||
@ -9,7 +10,7 @@ namespace CodexPlugin
|
||||
public CodexDeployment(CodexInstance[] codexInstances, GethDeployment gethDeployment,
|
||||
CodexContractsDeployment codexContractsDeployment, RunningPod? prometheusContainer,
|
||||
RunningPod? discordBotContainer, DeploymentMetadata metadata,
|
||||
String id)
|
||||
string id)
|
||||
{
|
||||
Id = id;
|
||||
CodexInstances = codexInstances;
|
||||
@ -20,7 +21,7 @@ namespace CodexPlugin
|
||||
Metadata = metadata;
|
||||
}
|
||||
|
||||
public String Id { get; }
|
||||
public string Id { get; }
|
||||
public CodexInstance[] CodexInstances { get; }
|
||||
public GethDeployment GethDeployment { get; }
|
||||
public CodexContractsDeployment CodexContractsDeployment { get; }
|
||||
@ -29,18 +30,6 @@ namespace CodexPlugin
|
||||
public DeploymentMetadata Metadata { get; }
|
||||
}
|
||||
|
||||
public class CodexInstance
|
||||
{
|
||||
public CodexInstance(RunningPod pod, DebugInfo info)
|
||||
{
|
||||
Pod = pod;
|
||||
Info = info;
|
||||
}
|
||||
|
||||
public RunningPod Pod { get; }
|
||||
public DebugInfo Info { get; }
|
||||
}
|
||||
|
||||
public class DeploymentMetadata
|
||||
{
|
||||
public DeploymentMetadata(string name, DateTime startUtc, DateTime finishedUtc, string kubeNamespace,
|
||||
|
||||
29
ProjectPlugins/CodexPlugin/CodexExePath.cs
Normal file
29
ProjectPlugins/CodexPlugin/CodexExePath.cs
Normal file
@ -0,0 +1,29 @@
|
||||
namespace CodexPlugin
|
||||
{
|
||||
public class CodexExePath
|
||||
{
|
||||
private readonly string[] paths = [
|
||||
Path.Combine("d:", "Dev", "nim-codex", "build", "codex.exe"),
|
||||
Path.Combine("c:", "Projects", "nim-codex", "build", "codex.exe")
|
||||
];
|
||||
|
||||
private string selectedPath = string.Empty;
|
||||
|
||||
public CodexExePath()
|
||||
{
|
||||
foreach (var p in paths)
|
||||
{
|
||||
if (File.Exists(p))
|
||||
{
|
||||
selectedPath = p;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string Get()
|
||||
{
|
||||
return selectedPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
using CodexClient;
|
||||
using KubernetesWorkflow.Types;
|
||||
using Utils;
|
||||
|
||||
namespace CodexPlugin
|
||||
{
|
||||
public static class CodexInstanceContainerExtension
|
||||
{
|
||||
public static ICodexInstance CreateFromPod(RunningPod pod)
|
||||
{
|
||||
var container = pod.Containers.Single();
|
||||
|
||||
return new CodexInstance(
|
||||
name: container.Name,
|
||||
imageName: container.Recipe.Image,
|
||||
startUtc: container.Recipe.RecipeCreatedUtc,
|
||||
discoveryEndpoint: container.GetInternalAddress(CodexContainerRecipe.DiscoveryPortTag),
|
||||
apiEndpoint: container.GetAddress(CodexContainerRecipe.ApiPortTag),
|
||||
listenEndpoint: container.GetInternalAddress(CodexContainerRecipe.ListenPortTag),
|
||||
ethAccount: container.Recipe.Additionals.Get<EthAccount>(),
|
||||
metricsEndpoint: GetMetricsEndpoint(container)
|
||||
);
|
||||
}
|
||||
|
||||
// todo: is this needed for the discovery address??
|
||||
//var info = codexAccess.GetPodInfo();
|
||||
//return new Address(
|
||||
// logName: $"{GetName()}:DiscoveryPort",
|
||||
// host: info.Ip,
|
||||
// port: Container.Recipe.GetPortByTag(CodexContainerRecipe.DiscoveryPortTag)!.Number
|
||||
//);
|
||||
|
||||
private static Address? GetMetricsEndpoint(RunningContainer container)
|
||||
{
|
||||
try
|
||||
{
|
||||
return container.GetInternalAddress(CodexContainerRecipe.MetricsPortTag);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,53 +0,0 @@
|
||||
using CodexPlugin.Hooks;
|
||||
using Core;
|
||||
using GethPlugin;
|
||||
using KubernetesWorkflow;
|
||||
using KubernetesWorkflow.Types;
|
||||
|
||||
namespace CodexPlugin
|
||||
{
|
||||
public interface ICodexNodeFactory
|
||||
{
|
||||
CodexNode CreateOnlineCodexNode(CodexAccess access, CodexNodeGroup group);
|
||||
CrashWatcher CreateCrashWatcher(RunningContainer c);
|
||||
}
|
||||
|
||||
public class CodexNodeFactory : ICodexNodeFactory
|
||||
{
|
||||
private readonly IPluginTools tools;
|
||||
private readonly CodexHooksFactory codexHooksFactory;
|
||||
|
||||
public CodexNodeFactory(IPluginTools tools, CodexHooksFactory codexHooksFactory)
|
||||
{
|
||||
this.tools = tools;
|
||||
this.codexHooksFactory = codexHooksFactory;
|
||||
}
|
||||
|
||||
public CodexNode CreateOnlineCodexNode(CodexAccess access, CodexNodeGroup group)
|
||||
{
|
||||
var ethAccount = GetEthAccount(access);
|
||||
var hooks = codexHooksFactory.CreateHooks(access.Container.Name);
|
||||
|
||||
var marketplaceAccess = GetMarketplaceAccess(access, ethAccount, hooks);
|
||||
return new CodexNode(tools, access, group, marketplaceAccess, hooks, ethAccount);
|
||||
}
|
||||
|
||||
private IMarketplaceAccess GetMarketplaceAccess(CodexAccess codexAccess, EthAccount? ethAccount, ICodexNodeHooks hooks)
|
||||
{
|
||||
if (ethAccount == null) return new MarketplaceUnavailable();
|
||||
return new MarketplaceAccess(tools.GetLog(), codexAccess, hooks);
|
||||
}
|
||||
|
||||
private EthAccount? GetEthAccount(CodexAccess access)
|
||||
{
|
||||
var ethAccount = access.Container.Containers.Single().Recipe.Additionals.Get<EthAccount>();
|
||||
if (ethAccount == null) return null;
|
||||
return ethAccount;
|
||||
}
|
||||
|
||||
public CrashWatcher CreateCrashWatcher(RunningContainer c)
|
||||
{
|
||||
return tools.CreateWorkflow().CreateCrashWatcher(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,25 +1,23 @@
|
||||
using Core;
|
||||
using KubernetesWorkflow.Types;
|
||||
using MetricsPlugin;
|
||||
using CodexClient;
|
||||
using Core;
|
||||
using System.Collections;
|
||||
using Utils;
|
||||
|
||||
namespace CodexPlugin
|
||||
{
|
||||
public interface ICodexNodeGroup : IEnumerable<ICodexNode>, IHasManyMetricScrapeTargets
|
||||
{
|
||||
void BringOffline(bool waitTillStopped);
|
||||
void Stop(bool waitTillStopped);
|
||||
ICodexNode this[int index] { get; }
|
||||
}
|
||||
|
||||
public class CodexNodeGroup : ICodexNodeGroup
|
||||
{
|
||||
private readonly CodexStarter starter;
|
||||
private readonly ICodexNode[] nodes;
|
||||
|
||||
public CodexNodeGroup(CodexStarter starter, IPluginTools tools, RunningPod[] containers, ICodexNodeFactory codexNodeFactory)
|
||||
public CodexNodeGroup(IPluginTools tools, ICodexNode[] nodes)
|
||||
{
|
||||
this.starter = starter;
|
||||
Containers = containers;
|
||||
Nodes = containers.Select(c => CreateOnlineCodexNode(c, tools, codexNodeFactory)).ToArray();
|
||||
this.nodes = nodes;
|
||||
Version = new DebugInfoVersion();
|
||||
}
|
||||
|
||||
@ -31,25 +29,23 @@ namespace CodexPlugin
|
||||
}
|
||||
}
|
||||
|
||||
public void BringOffline(bool waitTillStopped)
|
||||
public void Stop(bool waitTillStopped)
|
||||
{
|
||||
starter.BringOffline(this, waitTillStopped);
|
||||
// Clear everything. Prevent accidental use.
|
||||
Nodes = Array.Empty<CodexNode>();
|
||||
Containers = null!;
|
||||
foreach (var node in Nodes) node.Stop(waitTillStopped);
|
||||
}
|
||||
|
||||
public void Stop(CodexNode node, bool waitTillStopped)
|
||||
{
|
||||
starter.Stop(node.Pod, waitTillStopped);
|
||||
Nodes = Nodes.Where(n => n != node).ToArray();
|
||||
Containers = Containers.Where(c => c != node.Pod).ToArray();
|
||||
node.Stop(waitTillStopped);
|
||||
}
|
||||
|
||||
public RunningPod[] Containers { get; private set; }
|
||||
public CodexNode[] Nodes { get; private set; }
|
||||
public ICodexNode[] Nodes => nodes;
|
||||
public DebugInfoVersion Version { get; private set; }
|
||||
public IMetricsScrapeTarget[] ScrapeTargets => Nodes.Select(n => n.MetricsScrapeTarget).ToArray();
|
||||
|
||||
public Address[] GetMetricsScrapeTargets()
|
||||
{
|
||||
return Nodes.Select(n => n.GetMetricsScrapeTarget()).ToArray();
|
||||
}
|
||||
|
||||
public IEnumerator<ICodexNode> GetEnumerator()
|
||||
{
|
||||
@ -63,12 +59,11 @@ namespace CodexPlugin
|
||||
|
||||
public string Describe()
|
||||
{
|
||||
return $"group:[{Containers.Describe()}]";
|
||||
return $"group:[{string.Join(",", Nodes.Select(n => n.GetName()))}]";
|
||||
}
|
||||
|
||||
public void EnsureOnline()
|
||||
{
|
||||
foreach (var node in Nodes) node.EnsureOnlineGetVersionResponse();
|
||||
var versionResponses = Nodes.Select(n => n.Version);
|
||||
|
||||
var first = versionResponses.First();
|
||||
@ -79,16 +74,6 @@ namespace CodexPlugin
|
||||
}
|
||||
|
||||
Version = first;
|
||||
foreach (var node in Nodes) node.Initialize();
|
||||
}
|
||||
|
||||
private CodexNode CreateOnlineCodexNode(RunningPod c, IPluginTools tools, ICodexNodeFactory factory)
|
||||
{
|
||||
var watcher = factory.CreateCrashWatcher(c.Containers.Single());
|
||||
var access = new CodexAccess(tools, c, watcher);
|
||||
var node = factory.CreateOnlineCodexNode(access, this);
|
||||
node.Awake();
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,48 +1,68 @@
|
||||
using CodexPlugin.Hooks;
|
||||
using CodexClient;
|
||||
using CodexClient.Hooks;
|
||||
using Core;
|
||||
using KubernetesWorkflow.Types;
|
||||
|
||||
namespace CodexPlugin
|
||||
{
|
||||
public class CodexPlugin : IProjectPlugin, IHasLogPrefix, IHasMetadata
|
||||
{
|
||||
private readonly CodexStarter codexStarter;
|
||||
private const bool UseContainers = true;
|
||||
|
||||
private readonly ICodexStarter codexStarter;
|
||||
private readonly IPluginTools tools;
|
||||
private readonly CodexLogLevel defaultLogLevel = CodexLogLevel.Trace;
|
||||
private readonly CodexHooksFactory hooksFactory = new CodexHooksFactory();
|
||||
private readonly ProcessControlMap processControlMap = new ProcessControlMap();
|
||||
private readonly CodexWrapper codexWrapper;
|
||||
|
||||
public CodexPlugin(IPluginTools tools)
|
||||
{
|
||||
codexStarter = new CodexStarter(tools);
|
||||
this.tools = tools;
|
||||
|
||||
codexStarter = CreateCodexStarter();
|
||||
codexWrapper = new CodexWrapper(tools, processControlMap, hooksFactory);
|
||||
}
|
||||
|
||||
private ICodexStarter CreateCodexStarter()
|
||||
{
|
||||
if (UseContainers)
|
||||
{
|
||||
Log("Using Containerized Codex instances");
|
||||
return new ContainerCodexStarter(tools, processControlMap);
|
||||
}
|
||||
|
||||
Log("Using Binary Codex instances");
|
||||
return new BinaryCodexStarter(tools, processControlMap);
|
||||
}
|
||||
|
||||
public string LogPrefix => "(Codex) ";
|
||||
|
||||
public void Announce()
|
||||
{
|
||||
Log($"Loaded with Codex ID: '{codexStarter.GetCodexId()}' - Revision: {codexStarter.GetCodexRevision()}");
|
||||
Log($"Loaded with Codex ID: '{codexWrapper.GetCodexId()}' - Revision: {codexWrapper.GetCodexRevision()}");
|
||||
}
|
||||
|
||||
public void AddMetadata(IAddMetadata metadata)
|
||||
{
|
||||
metadata.Add("codexid", codexStarter.GetCodexId());
|
||||
metadata.Add("codexrevision", codexStarter.GetCodexRevision());
|
||||
metadata.Add("codexid", codexWrapper.GetCodexId());
|
||||
metadata.Add("codexrevision", codexWrapper.GetCodexRevision());
|
||||
}
|
||||
|
||||
public void Decommission()
|
||||
{
|
||||
codexStarter.Decommission();
|
||||
}
|
||||
|
||||
public RunningPod[] DeployCodexNodes(int numberOfNodes, Action<ICodexSetup> setup)
|
||||
public ICodexInstance[] DeployCodexNodes(int numberOfNodes, Action<ICodexSetup> setup)
|
||||
{
|
||||
var codexSetup = GetSetup(numberOfNodes, setup);
|
||||
return codexStarter.BringOnline(codexSetup);
|
||||
}
|
||||
|
||||
public ICodexNodeGroup WrapCodexContainers(CoreInterface coreInterface, RunningPod[] containers)
|
||||
public ICodexNodeGroup WrapCodexContainers(ICodexInstance[] instances)
|
||||
{
|
||||
containers = containers.Select(c => SerializeGate.Gate(c)).ToArray();
|
||||
return codexStarter.WrapCodexContainers(coreInterface, containers);
|
||||
instances = instances.Select(c => SerializeGate.Gate(c as CodexInstance)).ToArray();
|
||||
return codexWrapper.WrapCodexInstances(instances);
|
||||
}
|
||||
|
||||
public void WireUpMarketplace(ICodexNodeGroup result, Action<ICodexSetup> setup)
|
||||
@ -62,9 +82,10 @@ namespace CodexPlugin
|
||||
}
|
||||
}
|
||||
|
||||
public void SetCodexHooksProvider(ICodexHooksProvider hooksProvider)
|
||||
public void AddCodexHooksProvider(ICodexHooksProvider hooksProvider)
|
||||
{
|
||||
codexStarter.HooksFactory.Provider = hooksProvider;
|
||||
if (hooksFactory.Providers.Contains(hooksProvider)) return;
|
||||
hooksFactory.Providers.Add(hooksProvider);
|
||||
}
|
||||
|
||||
private CodexSetup GetSetup(int numberOfNodes, Action<ICodexSetup> setup)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
@ -6,14 +6,6 @@
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="openapi.yaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<OpenApiReference Include="openapi.yaml" CodeGenerator="NSwagCSharp" Namespace="CodexOpenApi" ClassName="CodexApi" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.ApiDescription.Client" Version="7.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
@ -30,6 +22,7 @@
|
||||
<ProjectReference Include="..\..\Framework\Core\Core.csproj" />
|
||||
<ProjectReference Include="..\..\Framework\KubernetesWorkflow\KubernetesWorkflow.csproj" />
|
||||
<ProjectReference Include="..\..\Framework\OverwatchTranscript\OverwatchTranscript.csproj" />
|
||||
<ProjectReference Include="..\CodexClient\CodexClient.csproj" />
|
||||
<ProjectReference Include="..\CodexContractsPlugin\CodexContractsPlugin.csproj" />
|
||||
<ProjectReference Include="..\GethPlugin\GethPlugin.csproj" />
|
||||
<ProjectReference Include="..\MetricsPlugin\MetricsPlugin.csproj" />
|
||||
|
||||
160
ProjectPlugins/CodexPlugin/CodexProcessRecipe.cs
Normal file
160
ProjectPlugins/CodexPlugin/CodexProcessRecipe.cs
Normal file
@ -0,0 +1,160 @@
|
||||
using System.Net.Sockets;
|
||||
using System.Net;
|
||||
using Nethereum.Util;
|
||||
|
||||
namespace CodexPlugin
|
||||
{
|
||||
public class ProcessRecipe
|
||||
{
|
||||
public ProcessRecipe(string cmd, string[] args)
|
||||
{
|
||||
Cmd = cmd;
|
||||
Args = args;
|
||||
}
|
||||
|
||||
public string Cmd { get; }
|
||||
public string[] Args { get; }
|
||||
}
|
||||
|
||||
public class CodexProcessConfig
|
||||
{
|
||||
public CodexProcessConfig(string name, FreePortFinder freePortFinder, string dataDir)
|
||||
{
|
||||
ApiPort = freePortFinder.GetNextFreePort();
|
||||
DiscPort = freePortFinder.GetNextFreePort();
|
||||
ListenPort = freePortFinder.GetNextFreePort();
|
||||
Name = name;
|
||||
DataDir = dataDir;
|
||||
var host = Dns.GetHostEntry(Dns.GetHostName());
|
||||
var addrs = host.AddressList.Where(a => a.AddressFamily == AddressFamily.InterNetwork).ToList();
|
||||
|
||||
LocalIpAddrs = addrs.First();
|
||||
}
|
||||
|
||||
public int ApiPort { get; }
|
||||
public int DiscPort { get; }
|
||||
public int ListenPort { get; }
|
||||
public string Name { get; }
|
||||
public string DataDir { get; }
|
||||
public IPAddress LocalIpAddrs { get; }
|
||||
}
|
||||
|
||||
public class CodexProcessRecipe
|
||||
{
|
||||
private readonly CodexProcessConfig pc;
|
||||
private readonly CodexExePath codexExePath;
|
||||
|
||||
public CodexProcessRecipe(CodexProcessConfig pc, CodexExePath codexExePath)
|
||||
{
|
||||
this.pc = pc;
|
||||
this.codexExePath = codexExePath;
|
||||
}
|
||||
|
||||
public ProcessRecipe Initialize(CodexStartupConfig config)
|
||||
{
|
||||
args.Clear();
|
||||
|
||||
AddArg("--api-port", pc.ApiPort);
|
||||
AddArg("--api-bindaddr", "0.0.0.0");
|
||||
|
||||
AddArg("--data-dir", pc.DataDir);
|
||||
|
||||
AddArg("--disc-port", pc.DiscPort);
|
||||
AddArg("--log-level", config.LogLevelWithTopics());
|
||||
|
||||
// This makes the node announce itself to its local IP address.
|
||||
AddArg("--nat", $"extip:{pc.LocalIpAddrs.ToStringInvariant()}");
|
||||
|
||||
AddArg("--listen-addrs", $"/ip4/0.0.0.0/tcp/{pc.ListenPort}");
|
||||
|
||||
if (!string.IsNullOrEmpty(config.BootstrapSpr))
|
||||
{
|
||||
AddArg("--bootstrap-node", config.BootstrapSpr);
|
||||
}
|
||||
if (config.StorageQuota != null)
|
||||
{
|
||||
AddArg("--storage-quota", config.StorageQuota.SizeInBytes.ToString()!);
|
||||
}
|
||||
if (config.BlockTTL != null)
|
||||
{
|
||||
AddArg("--block-ttl", config.BlockTTL.ToString()!);
|
||||
}
|
||||
if (config.BlockMaintenanceInterval != null)
|
||||
{
|
||||
AddArg("--block-mi", Convert.ToInt32(config.BlockMaintenanceInterval.Value.TotalSeconds).ToString());
|
||||
}
|
||||
if (config.BlockMaintenanceNumber != null)
|
||||
{
|
||||
AddArg("--block-mn", config.BlockMaintenanceNumber.ToString()!);
|
||||
}
|
||||
if (config.MetricsEnabled)
|
||||
{
|
||||
throw new Exception("Not supported");
|
||||
//var metricsPort = CreateApiPort(config, MetricsPortTag);
|
||||
//AddEnvVar("CODEX_METRICS", "true");
|
||||
//AddEnvVar("CODEX_METRICS_ADDRESS", "0.0.0.0");
|
||||
//AddEnvVar("CODEX_METRICS_PORT", metricsPort);
|
||||
//AddPodAnnotation("prometheus.io/scrape", "true");
|
||||
//AddPodAnnotation("prometheus.io/port", metricsPort.Number.ToString());
|
||||
}
|
||||
|
||||
if (config.SimulateProofFailures != null)
|
||||
{
|
||||
throw new Exception("Not supported");
|
||||
//AddEnvVar("CODEX_SIMULATE_PROOF_FAILURES", config.SimulateProofFailures.ToString()!);
|
||||
}
|
||||
|
||||
if (config.MarketplaceConfig != null)
|
||||
{
|
||||
throw new Exception("Not supported");
|
||||
//var mconfig = config.MarketplaceConfig;
|
||||
//var gethStart = mconfig.GethNode.StartResult;
|
||||
//var wsAddress = gethStart.Container.GetInternalAddress(GethContainerRecipe.WsPortTag);
|
||||
//var marketplaceAddress = mconfig.CodexContracts.Deployment.MarketplaceAddress;
|
||||
|
||||
//AddEnvVar("CODEX_ETH_PROVIDER", $"{wsAddress.Host.Replace("http://", "ws://")}:{wsAddress.Port}");
|
||||
//AddEnvVar("CODEX_MARKETPLACE_ADDRESS", marketplaceAddress);
|
||||
|
||||
//var marketplaceSetup = config.MarketplaceConfig.MarketplaceSetup;
|
||||
|
||||
//// Custom scripting in the Codex test image will write this variable to a private-key file,
|
||||
//// and pass the correct filename to Codex.
|
||||
//var account = marketplaceSetup.EthAccountSetup.GetNew();
|
||||
//AddEnvVar("ETH_PRIVATE_KEY", account.PrivateKey);
|
||||
//Additional(account);
|
||||
|
||||
//SetCommandOverride(marketplaceSetup);
|
||||
//if (marketplaceSetup.IsValidator)
|
||||
//{
|
||||
// AddEnvVar("CODEX_VALIDATOR", "true");
|
||||
//}
|
||||
}
|
||||
|
||||
//if (!string.IsNullOrEmpty(config.NameOverride))
|
||||
//{
|
||||
// AddEnvVar("CODEX_NODENAME", config.NameOverride);
|
||||
//}
|
||||
|
||||
return Create();
|
||||
}
|
||||
|
||||
private ProcessRecipe Create()
|
||||
{
|
||||
return new ProcessRecipe(
|
||||
cmd: codexExePath.Get(),
|
||||
args: args.ToArray());
|
||||
}
|
||||
|
||||
private readonly List<string> args = new List<string>();
|
||||
|
||||
private void AddArg(string arg, string val)
|
||||
{
|
||||
args.Add($"{arg}={val}");
|
||||
}
|
||||
|
||||
private void AddArg(string arg, int val)
|
||||
{
|
||||
args.Add($"{arg}={val}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
using CodexContractsPlugin;
|
||||
using CodexClient;
|
||||
using CodexContractsPlugin;
|
||||
using GethPlugin;
|
||||
using KubernetesWorkflow;
|
||||
using Utils;
|
||||
@ -223,7 +224,7 @@ namespace CodexPlugin
|
||||
{
|
||||
if (pinned) return accounts.Last();
|
||||
|
||||
var a = EthAccount.GenerateNew();
|
||||
var a = EthAccountGenerator.GenerateNew();
|
||||
accounts.Add(a);
|
||||
return a;
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using KubernetesWorkflow;
|
||||
using CodexClient;
|
||||
using KubernetesWorkflow;
|
||||
using Utils;
|
||||
|
||||
namespace CodexPlugin
|
||||
|
||||
80
ProjectPlugins/CodexPlugin/CodexWrapper.cs
Normal file
80
ProjectPlugins/CodexPlugin/CodexWrapper.cs
Normal file
@ -0,0 +1,80 @@
|
||||
using CodexClient;
|
||||
using CodexClient.Hooks;
|
||||
using Core;
|
||||
using Logging;
|
||||
|
||||
namespace CodexPlugin
|
||||
{
|
||||
public class CodexWrapper
|
||||
{
|
||||
private readonly IPluginTools pluginTools;
|
||||
private readonly ProcessControlMap processControlMap;
|
||||
private readonly CodexHooksFactory hooksFactory;
|
||||
private DebugInfoVersion? versionResponse;
|
||||
|
||||
public CodexWrapper(IPluginTools pluginTools, ProcessControlMap processControlMap, CodexHooksFactory hooksFactory)
|
||||
{
|
||||
this.pluginTools = pluginTools;
|
||||
this.processControlMap = processControlMap;
|
||||
this.hooksFactory = hooksFactory;
|
||||
}
|
||||
|
||||
public string GetCodexId()
|
||||
{
|
||||
if (versionResponse != null) return versionResponse.Version;
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
public string GetCodexRevision()
|
||||
{
|
||||
if (versionResponse != null) return versionResponse.Revision;
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
public ICodexNodeGroup WrapCodexInstances(ICodexInstance[] instances)
|
||||
{
|
||||
var codexNodeFactory = new CodexNodeFactory(
|
||||
log: pluginTools.GetLog(),
|
||||
fileManager: pluginTools.GetFileManager(),
|
||||
hooksFactory: hooksFactory,
|
||||
httpFactory: pluginTools,
|
||||
processControlFactory: processControlMap);
|
||||
|
||||
var group = CreateCodexGroup(instances, codexNodeFactory);
|
||||
|
||||
pluginTools.GetLog().Log($"Codex version: {group.Version}");
|
||||
versionResponse = group.Version;
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
private CodexNodeGroup CreateCodexGroup(ICodexInstance[] instances, CodexNodeFactory codexNodeFactory)
|
||||
{
|
||||
var nodes = instances.Select(codexNodeFactory.CreateCodexNode).ToArray();
|
||||
var group = new CodexNodeGroup(pluginTools, nodes);
|
||||
|
||||
try
|
||||
{
|
||||
Stopwatch.Measure(pluginTools.GetLog(), "EnsureOnline", group.EnsureOnline);
|
||||
}
|
||||
catch
|
||||
{
|
||||
CodexNodesNotOnline(instances);
|
||||
throw;
|
||||
}
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
private void CodexNodesNotOnline(ICodexInstance[] instances)
|
||||
{
|
||||
pluginTools.GetLog().Log("Codex nodes failed to start");
|
||||
var log = pluginTools.GetLog();
|
||||
foreach (var i in instances)
|
||||
{
|
||||
var pc = processControlMap.Get(i);
|
||||
pc.DownloadLog(log.CreateSubfile(i.Name + "_failed_to_start"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,29 +1,26 @@
|
||||
using CodexPlugin.Hooks;
|
||||
using CodexClient;
|
||||
using Core;
|
||||
using GethPlugin;
|
||||
using KubernetesWorkflow;
|
||||
using KubernetesWorkflow.Types;
|
||||
using Logging;
|
||||
using Utils;
|
||||
|
||||
namespace CodexPlugin
|
||||
{
|
||||
public class CodexStarter
|
||||
public class ContainerCodexStarter : ICodexStarter
|
||||
{
|
||||
private readonly IPluginTools pluginTools;
|
||||
private readonly ProcessControlMap processControlMap;
|
||||
private readonly CodexContainerRecipe recipe = new CodexContainerRecipe();
|
||||
private readonly ApiChecker apiChecker;
|
||||
private DebugInfoVersion? versionResponse;
|
||||
|
||||
public CodexStarter(IPluginTools pluginTools)
|
||||
public ContainerCodexStarter(IPluginTools pluginTools, ProcessControlMap processControlMap)
|
||||
{
|
||||
this.pluginTools = pluginTools;
|
||||
|
||||
this.processControlMap = processControlMap;
|
||||
apiChecker = new ApiChecker(pluginTools);
|
||||
}
|
||||
|
||||
public CodexHooksFactory HooksFactory { get; } = new CodexHooksFactory();
|
||||
|
||||
public RunningPod[] BringOnline(CodexSetup codexSetup)
|
||||
public ICodexInstance[] BringOnline(CodexSetup codexSetup)
|
||||
{
|
||||
LogSeparator();
|
||||
Log($"Starting {codexSetup.Describe()}...");
|
||||
@ -43,51 +40,11 @@ namespace CodexPlugin
|
||||
}
|
||||
LogSeparator();
|
||||
|
||||
return containers;
|
||||
return containers.Select(CreateInstance).ToArray();
|
||||
}
|
||||
|
||||
public ICodexNodeGroup WrapCodexContainers(CoreInterface coreInterface, RunningPod[] containers)
|
||||
public void Decommission()
|
||||
{
|
||||
var codexNodeFactory = new CodexNodeFactory(pluginTools, HooksFactory);
|
||||
|
||||
var group = CreateCodexGroup(coreInterface, containers, codexNodeFactory);
|
||||
|
||||
Log($"Codex version: {group.Version}");
|
||||
versionResponse = group.Version;
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
public void BringOffline(CodexNodeGroup group, bool waitTillStopped)
|
||||
{
|
||||
Log($"Stopping {group.Describe()}...");
|
||||
StopCrashWatcher(group);
|
||||
var workflow = pluginTools.CreateWorkflow();
|
||||
foreach (var c in group.Containers)
|
||||
{
|
||||
workflow.Stop(c, waitTillStopped);
|
||||
}
|
||||
Log("Stopped.");
|
||||
}
|
||||
|
||||
public void Stop(RunningPod pod, bool waitTillStopped)
|
||||
{
|
||||
Log($"Stopping node...");
|
||||
var workflow = pluginTools.CreateWorkflow();
|
||||
workflow.Stop(pod, waitTillStopped);
|
||||
Log("Stopped.");
|
||||
}
|
||||
|
||||
public string GetCodexId()
|
||||
{
|
||||
if (versionResponse != null) return versionResponse.Version;
|
||||
return recipe.Image;
|
||||
}
|
||||
|
||||
public string GetCodexRevision()
|
||||
{
|
||||
if (versionResponse != null) return versionResponse.Revision;
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
private StartupConfig CreateStartupConfig(CodexSetup codexSetup)
|
||||
@ -118,27 +75,15 @@ namespace CodexPlugin
|
||||
return workflow.GetPodInfo(rc);
|
||||
}
|
||||
|
||||
private CodexNodeGroup CreateCodexGroup(CoreInterface coreInterface, RunningPod[] runningContainers, CodexNodeFactory codexNodeFactory)
|
||||
private ICodexInstance CreateInstance(RunningPod pod)
|
||||
{
|
||||
var group = new CodexNodeGroup(this, pluginTools, runningContainers, codexNodeFactory);
|
||||
|
||||
try
|
||||
var instance = CodexInstanceContainerExtension.CreateFromPod(pod);
|
||||
var processControl = new CodexContainerProcessControl(pluginTools, pod, onStop: () =>
|
||||
{
|
||||
Stopwatch.Measure(pluginTools.GetLog(), "EnsureOnline", group.EnsureOnline);
|
||||
}
|
||||
catch
|
||||
{
|
||||
CodexNodesNotOnline(coreInterface, runningContainers);
|
||||
throw;
|
||||
}
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
private void CodexNodesNotOnline(CoreInterface coreInterface, RunningPod[] runningContainers)
|
||||
{
|
||||
Log("Codex nodes failed to start");
|
||||
foreach (var container in runningContainers.First().Containers) coreInterface.DownloadLog(container);
|
||||
processControlMap.Remove(instance);
|
||||
});
|
||||
processControlMap.Add(instance, processControl);
|
||||
return instance;
|
||||
}
|
||||
|
||||
private void LogSeparator()
|
||||
@ -157,13 +102,5 @@ namespace CodexPlugin
|
||||
{
|
||||
pluginTools.GetLog().Log(message);
|
||||
}
|
||||
|
||||
private void StopCrashWatcher(CodexNodeGroup group)
|
||||
{
|
||||
foreach (var node in group)
|
||||
{
|
||||
node.CrashWatcher.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,19 +1,19 @@
|
||||
using CodexPlugin.Hooks;
|
||||
using CodexClient;
|
||||
using CodexClient.Hooks;
|
||||
using Core;
|
||||
using KubernetesWorkflow.Types;
|
||||
|
||||
namespace CodexPlugin
|
||||
{
|
||||
public static class CoreInterfaceExtensions
|
||||
{
|
||||
public static RunningPod[] DeployCodexNodes(this CoreInterface ci, int number, Action<ICodexSetup> setup)
|
||||
public static ICodexInstance[] DeployCodexNodes(this CoreInterface ci, int number, Action<ICodexSetup> setup)
|
||||
{
|
||||
return Plugin(ci).DeployCodexNodes(number, setup);
|
||||
}
|
||||
|
||||
public static ICodexNodeGroup WrapCodexContainers(this CoreInterface ci, RunningPod[] containers)
|
||||
public static ICodexNodeGroup WrapCodexContainers(this CoreInterface ci, ICodexInstance[] instances)
|
||||
{
|
||||
return Plugin(ci).WrapCodexContainers(ci, containers);
|
||||
return Plugin(ci).WrapCodexContainers(instances);
|
||||
}
|
||||
|
||||
public static ICodexNode StartCodexNode(this CoreInterface ci)
|
||||
@ -39,9 +39,9 @@ namespace CodexPlugin
|
||||
return ci.StartCodexNodes(number, s => { });
|
||||
}
|
||||
|
||||
public static void SetCodexHooksProvider(this CoreInterface ci, ICodexHooksProvider hooksProvider)
|
||||
public static void AddCodexHooksProvider(this CoreInterface ci, ICodexHooksProvider hooksProvider)
|
||||
{
|
||||
Plugin(ci).SetCodexHooksProvider(hooksProvider);
|
||||
Plugin(ci).AddCodexHooksProvider(hooksProvider);
|
||||
}
|
||||
|
||||
private static CodexPlugin Plugin(CoreInterface ci)
|
||||
|
||||
43
ProjectPlugins/CodexPlugin/FreePortFinder.cs
Normal file
43
ProjectPlugins/CodexPlugin/FreePortFinder.cs
Normal file
@ -0,0 +1,43 @@
|
||||
using System.Net.NetworkInformation;
|
||||
|
||||
namespace CodexPlugin
|
||||
{
|
||||
public class FreePortFinder
|
||||
{
|
||||
private readonly object _lock = new object();
|
||||
private int nextPort = 8080;
|
||||
|
||||
public int GetNextFreePort()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return Next();
|
||||
}
|
||||
}
|
||||
|
||||
private int Next()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var p = nextPort;
|
||||
nextPort++;
|
||||
|
||||
if (!IsInUse(p))
|
||||
{
|
||||
return p;
|
||||
}
|
||||
|
||||
if (nextPort > 30000) throw new Exception("Running out of ports.");
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsInUse(int port)
|
||||
{
|
||||
var ipProps = IPGlobalProperties.GetIPGlobalProperties();
|
||||
if (ipProps.GetActiveTcpConnections().Any(t => t.LocalEndPoint.Port == port)) return true;
|
||||
if (ipProps.GetActiveTcpListeners().Any(t => t.Port == port)) return true;
|
||||
if (ipProps.GetActiveUdpListeners().Any(u => u.Port == port)) return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
using GethPlugin;
|
||||
using Utils;
|
||||
|
||||
namespace CodexPlugin.Hooks
|
||||
{
|
||||
public interface ICodexNodeHooks
|
||||
{
|
||||
void OnNodeStarting(DateTime startUtc, string image, EthAccount? ethAccount);
|
||||
void OnNodeStarted(string peerId, string nodeId);
|
||||
void OnNodeStopping();
|
||||
void OnFileUploading(string uid, ByteSize size);
|
||||
void OnFileUploaded(string uid, ByteSize size, ContentId cid);
|
||||
void OnFileDownloading(ContentId cid);
|
||||
void OnFileDownloaded(ByteSize size, ContentId cid);
|
||||
void OnStorageContractSubmitted(StoragePurchaseContract storagePurchaseContract);
|
||||
void OnStorageContractUpdated(StoragePurchase purchaseStatus);
|
||||
void OnStorageAvailabilityCreated(StorageAvailability response);
|
||||
}
|
||||
}
|
||||
10
ProjectPlugins/CodexPlugin/ICodexStarter.cs
Normal file
10
ProjectPlugins/CodexPlugin/ICodexStarter.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using CodexClient;
|
||||
|
||||
namespace CodexPlugin
|
||||
{
|
||||
public interface ICodexStarter
|
||||
{
|
||||
ICodexInstance[] BringOnline(CodexSetup codexSetup);
|
||||
void Decommission();
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
using CodexPlugin.OverwatchSupport.LineConverters;
|
||||
using KubernetesWorkflow;
|
||||
using CodexClient;
|
||||
using CodexPlugin.OverwatchSupport.LineConverters;
|
||||
using Logging;
|
||||
using OverwatchTranscript;
|
||||
using Utils;
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
using CodexPlugin.Hooks;
|
||||
using GethPlugin;
|
||||
using CodexClient;
|
||||
using CodexClient.Hooks;
|
||||
using OverwatchTranscript;
|
||||
using Utils;
|
||||
|
||||
@ -32,7 +32,7 @@ namespace CodexPlugin.OverwatchSupport
|
||||
});
|
||||
}
|
||||
|
||||
public void OnNodeStarted(string peerId, string nodeId)
|
||||
public void OnNodeStarted(ICodexNode node, string peerId, string nodeId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(peerId) || string.IsNullOrEmpty(nodeId))
|
||||
{
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
using CodexPlugin.Hooks;
|
||||
using KubernetesWorkflow;
|
||||
using CodexClient.Hooks;
|
||||
using Logging;
|
||||
using OverwatchTranscript;
|
||||
using Utils;
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
namespace CodexPlugin.OverwatchSupport
|
||||
using CodexClient;
|
||||
|
||||
namespace CodexPlugin.OverwatchSupport
|
||||
{
|
||||
public class IdentityMap
|
||||
{
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
namespace CodexPlugin.OverwatchSupport.LineConverters
|
||||
using CodexClient;
|
||||
|
||||
namespace CodexPlugin.OverwatchSupport.LineConverters
|
||||
{
|
||||
public class BlockReceivedLineConverter : ILineConverter
|
||||
{
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
namespace CodexPlugin.OverwatchSupport.LineConverters
|
||||
using CodexClient;
|
||||
|
||||
namespace CodexPlugin.OverwatchSupport.LineConverters
|
||||
{
|
||||
public class BootstrapLineConverter : ILineConverter
|
||||
{
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
namespace CodexPlugin.OverwatchSupport.LineConverters
|
||||
using CodexClient;
|
||||
|
||||
namespace CodexPlugin.OverwatchSupport.LineConverters
|
||||
{
|
||||
public class DialSuccessfulLineConverter : ILineConverter
|
||||
{
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
namespace CodexPlugin.OverwatchSupport.LineConverters
|
||||
using CodexClient;
|
||||
|
||||
namespace CodexPlugin.OverwatchSupport.LineConverters
|
||||
{
|
||||
public class PeerDroppedLineConverter : ILineConverter
|
||||
{
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using OverwatchTranscript;
|
||||
using CodexClient;
|
||||
using OverwatchTranscript;
|
||||
|
||||
namespace CodexPlugin.OverwatchSupport
|
||||
{
|
||||
|
||||
37
ProjectPlugins/CodexPlugin/ProcessControlMap.cs
Normal file
37
ProjectPlugins/CodexPlugin/ProcessControlMap.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using CodexClient;
|
||||
|
||||
namespace CodexPlugin
|
||||
{
|
||||
public class ProcessControlMap : IProcessControlFactory
|
||||
{
|
||||
private readonly Dictionary<string, IProcessControl> processControlMap = new Dictionary<string, IProcessControl>();
|
||||
|
||||
public void Add(ICodexInstance instance, IProcessControl control)
|
||||
{
|
||||
processControlMap.Add(instance.Name, control);
|
||||
}
|
||||
|
||||
public void Remove(ICodexInstance instance)
|
||||
{
|
||||
processControlMap.Remove(instance.Name);
|
||||
}
|
||||
|
||||
public IProcessControl CreateProcessControl(ICodexInstance instance)
|
||||
{
|
||||
return Get(instance);
|
||||
}
|
||||
|
||||
public IProcessControl Get(ICodexInstance instance)
|
||||
{
|
||||
return processControlMap[instance.Name];
|
||||
}
|
||||
|
||||
public void StopAll()
|
||||
{
|
||||
var pcs = processControlMap.Values.ToArray();
|
||||
processControlMap.Clear();
|
||||
|
||||
foreach (var c in pcs) c.Stop(waitTillStopped: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -12,11 +12,12 @@ public static class Program
|
||||
{
|
||||
Console.WriteLine("Injecting hash of 'openapi.yaml'...");
|
||||
|
||||
var root = FindCodexPluginFolder();
|
||||
Console.WriteLine("Located CodexPlugin: " + root);
|
||||
var openApiFile = Path.Combine(root, "openapi.yaml");
|
||||
var clientFile = Path.Combine(root, "obj", "openapiClient.cs");
|
||||
var targetFile = Path.Combine(root, "ApiChecker.cs");
|
||||
var pluginRoot = FindCodexPluginFolder();
|
||||
var clientRoot = FindCodexClientFolder();
|
||||
Console.WriteLine("Located CodexPlugin: " + pluginRoot);
|
||||
var openApiFile = Path.Combine(clientRoot, "openapi.yaml");
|
||||
var clientFile = Path.Combine(clientRoot, "obj", "openapiClient.cs");
|
||||
var targetFile = Path.Combine(pluginRoot, "ApiChecker.cs");
|
||||
|
||||
// Force client rebuild by deleting previous artifact.
|
||||
File.Delete(clientFile);
|
||||
@ -46,6 +47,13 @@ public static class Program
|
||||
return folder;
|
||||
}
|
||||
|
||||
private static string FindCodexClientFolder()
|
||||
{
|
||||
var folder = Path.Combine(PluginPathUtils.ProjectPluginsDir, "CodexClient");
|
||||
if (!Directory.Exists(folder)) throw new Exception("CodexClient folder not found. Expected: " + folder);
|
||||
return folder;
|
||||
}
|
||||
|
||||
private static string CreateHash(string openApiFile)
|
||||
{
|
||||
var file = File.ReadAllText(openApiFile);
|
||||
|
||||
@ -1,20 +1,11 @@
|
||||
using Nethereum.Hex.HexConvertors.Extensions;
|
||||
using Nethereum.Web3.Accounts;
|
||||
using Utils;
|
||||
|
||||
namespace GethPlugin
|
||||
{
|
||||
[Serializable]
|
||||
public class EthAccount
|
||||
public static class EthAccountGenerator
|
||||
{
|
||||
public EthAccount(EthAddress ethAddress, string privateKey)
|
||||
{
|
||||
EthAddress = ethAddress;
|
||||
PrivateKey = privateKey;
|
||||
}
|
||||
|
||||
public EthAddress EthAddress { get; }
|
||||
public string PrivateKey { get; }
|
||||
|
||||
public static EthAccount GenerateNew()
|
||||
{
|
||||
var ecKey = Nethereum.Signer.EthECKey.GenerateKey();
|
||||
@ -24,10 +15,5 @@ namespace GethPlugin
|
||||
|
||||
return new EthAccount(ethAddress, account.PrivateKey);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return EthAddress.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
using Core;
|
||||
using KubernetesWorkflow.Types;
|
||||
using Logging;
|
||||
using Utils;
|
||||
|
||||
namespace MetricsPlugin
|
||||
{
|
||||
@ -8,35 +9,35 @@ namespace MetricsPlugin
|
||||
{
|
||||
public static RunningPod DeployMetricsCollector(this CoreInterface ci, TimeSpan scrapeInterval, params IHasMetricsScrapeTarget[] scrapeTargets)
|
||||
{
|
||||
return Plugin(ci).DeployMetricsCollector(scrapeTargets.Select(t => t.MetricsScrapeTarget).ToArray(), scrapeInterval);
|
||||
return Plugin(ci).DeployMetricsCollector(scrapeTargets.Select(t => t.GetMetricsScrapeTarget()).ToArray(), scrapeInterval);
|
||||
}
|
||||
|
||||
public static RunningPod DeployMetricsCollector(this CoreInterface ci, TimeSpan scrapeInterval, params IMetricsScrapeTarget[] scrapeTargets)
|
||||
public static RunningPod DeployMetricsCollector(this CoreInterface ci, TimeSpan scrapeInterval, params Address[] scrapeTargets)
|
||||
{
|
||||
return Plugin(ci).DeployMetricsCollector(scrapeTargets, scrapeInterval);
|
||||
}
|
||||
|
||||
public static IMetricsAccess WrapMetricsCollector(this CoreInterface ci, RunningPod metricsPod, IHasMetricsScrapeTarget scrapeTarget)
|
||||
{
|
||||
return ci.WrapMetricsCollector(metricsPod, scrapeTarget.MetricsScrapeTarget);
|
||||
return ci.WrapMetricsCollector(metricsPod, scrapeTarget.GetMetricsScrapeTarget());
|
||||
}
|
||||
|
||||
public static IMetricsAccess WrapMetricsCollector(this CoreInterface ci, RunningPod metricsPod, IMetricsScrapeTarget scrapeTarget)
|
||||
public static IMetricsAccess WrapMetricsCollector(this CoreInterface ci, RunningPod metricsPod, Address scrapeTarget)
|
||||
{
|
||||
return Plugin(ci).WrapMetricsCollectorDeployment(metricsPod, scrapeTarget);
|
||||
}
|
||||
|
||||
public static IMetricsAccess[] GetMetricsFor(this CoreInterface ci, TimeSpan scrapeInterval, params IHasManyMetricScrapeTargets[] manyScrapeTargets)
|
||||
{
|
||||
return ci.GetMetricsFor(scrapeInterval, manyScrapeTargets.SelectMany(t => t.ScrapeTargets).ToArray());
|
||||
return ci.GetMetricsFor(scrapeInterval, manyScrapeTargets.SelectMany(t => t.GetMetricsScrapeTargets()).ToArray());
|
||||
}
|
||||
|
||||
public static IMetricsAccess[] GetMetricsFor(this CoreInterface ci, TimeSpan scrapeInterval, params IHasMetricsScrapeTarget[] scrapeTargets)
|
||||
{
|
||||
return ci.GetMetricsFor(scrapeInterval, scrapeTargets.Select(t => t.MetricsScrapeTarget).ToArray());
|
||||
return ci.GetMetricsFor(scrapeInterval, scrapeTargets.Select(t => t.GetMetricsScrapeTarget()).ToArray());
|
||||
}
|
||||
|
||||
public static IMetricsAccess[] GetMetricsFor(this CoreInterface ci, TimeSpan scrapeInterval, params IMetricsScrapeTarget[] scrapeTargets)
|
||||
public static IMetricsAccess[] GetMetricsFor(this CoreInterface ci, TimeSpan scrapeInterval, params Address[] scrapeTargets)
|
||||
{
|
||||
var rc = ci.DeployMetricsCollector(scrapeInterval, scrapeTargets);
|
||||
return scrapeTargets.Select(t => ci.WrapMetricsCollector(rc, t)).ToArray();
|
||||
|
||||
@ -15,13 +15,13 @@ namespace MetricsPlugin
|
||||
public class MetricsAccess : IMetricsAccess
|
||||
{
|
||||
private readonly MetricsQuery query;
|
||||
private readonly IMetricsScrapeTarget target;
|
||||
private readonly Address target;
|
||||
|
||||
public MetricsAccess(MetricsQuery query, IMetricsScrapeTarget target)
|
||||
public MetricsAccess(MetricsQuery query, Address target)
|
||||
{
|
||||
this.query = query;
|
||||
this.target = target;
|
||||
TargetName = target.Container.Name;
|
||||
TargetName = $"'{target.Host}'";
|
||||
}
|
||||
|
||||
public string TargetName { get; }
|
||||
|
||||
@ -26,7 +26,7 @@ namespace MetricsPlugin
|
||||
private LogFile WriteToFile(string nodeName, string[] headers, Dictionary<DateTime, List<string>> map)
|
||||
{
|
||||
var file = log.CreateSubfile("csv");
|
||||
log.Log($"Downloading metrics for {nodeName} to file {file.FullFilename}");
|
||||
log.Log($"Downloading metrics for {nodeName} to file {file.Filename}");
|
||||
|
||||
file.WriteRaw(string.Join(",", headers));
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using Core;
|
||||
using KubernetesWorkflow.Types;
|
||||
using Logging;
|
||||
using Utils;
|
||||
|
||||
namespace MetricsPlugin
|
||||
{
|
||||
@ -31,12 +32,12 @@ namespace MetricsPlugin
|
||||
{
|
||||
}
|
||||
|
||||
public RunningPod DeployMetricsCollector(IMetricsScrapeTarget[] scrapeTargets, TimeSpan scrapeInterval)
|
||||
public RunningPod DeployMetricsCollector(Address[] scrapeTargets, TimeSpan scrapeInterval)
|
||||
{
|
||||
return starter.CollectMetricsFor(scrapeTargets, scrapeInterval);
|
||||
}
|
||||
|
||||
public IMetricsAccess WrapMetricsCollectorDeployment(RunningPod runningPod, IMetricsScrapeTarget target)
|
||||
public IMetricsAccess WrapMetricsCollectorDeployment(RunningPod runningPod, Address target)
|
||||
{
|
||||
runningPod = SerializeGate.Gate(runningPod);
|
||||
return starter.CreateAccessForTarget(runningPod, target);
|
||||
|
||||
@ -3,6 +3,8 @@ using IdentityModel;
|
||||
using KubernetesWorkflow.Types;
|
||||
using Logging;
|
||||
using System.Globalization;
|
||||
using Utils;
|
||||
using WebUtils;
|
||||
|
||||
namespace MetricsPlugin
|
||||
{
|
||||
@ -23,7 +25,7 @@ namespace MetricsPlugin
|
||||
|
||||
public RunningContainer RunningContainer { get; }
|
||||
|
||||
public Metrics GetMostRecent(string metricName, IMetricsScrapeTarget target)
|
||||
public Metrics GetMostRecent(string metricName, Address target)
|
||||
{
|
||||
var response = GetLastOverTime(metricName, GetInstanceStringForNode(target));
|
||||
if (response == null) throw new Exception($"Failed to get most recent metric: {metricName}");
|
||||
@ -53,7 +55,7 @@ namespace MetricsPlugin
|
||||
return result;
|
||||
}
|
||||
|
||||
public Metrics GetAllMetricsForNode(IMetricsScrapeTarget target)
|
||||
public Metrics GetAllMetricsForNode(Address target)
|
||||
{
|
||||
var instanceString = GetInstanceStringForNode(target);
|
||||
var response = endpoint.HttpGetJson<PrometheusQueryResponse>($"query?query={instanceString}{GetQueryTimeRange()}");
|
||||
@ -139,12 +141,12 @@ namespace MetricsPlugin
|
||||
};
|
||||
}
|
||||
|
||||
private string GetInstanceNameForNode(IMetricsScrapeTarget target)
|
||||
private string GetInstanceNameForNode(Address target)
|
||||
{
|
||||
return ScrapeTargetHelper.FormatTarget(log, target);
|
||||
return ScrapeTargetHelper.FormatTarget(target);
|
||||
}
|
||||
|
||||
private string GetInstanceStringForNode(IMetricsScrapeTarget target)
|
||||
private string GetInstanceStringForNode(Address target)
|
||||
{
|
||||
return "{instance=\"" + GetInstanceNameForNode(target) + "\"}";
|
||||
}
|
||||
@ -172,9 +174,9 @@ namespace MetricsPlugin
|
||||
return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(unixSeconds);
|
||||
}
|
||||
|
||||
private void Log(IMetricsScrapeTarget target, string metricName, Metrics result)
|
||||
private void Log(Address target, string metricName, Metrics result)
|
||||
{
|
||||
Log($"{target.Container.Name} '{metricName}' = {result}");
|
||||
Log($"{target.LogName} '{metricName}' = {result}");
|
||||
}
|
||||
|
||||
private void Log(string metricName, Metrics result)
|
||||
@ -182,9 +184,9 @@ namespace MetricsPlugin
|
||||
Log($"'{metricName}' = {result}");
|
||||
}
|
||||
|
||||
private void Log(IMetricsScrapeTarget target, Metrics result)
|
||||
private void Log(Address target, Metrics result)
|
||||
{
|
||||
Log($"{target.Container.Name} => {result}");
|
||||
Log($"{target.LogName} => {result}");
|
||||
}
|
||||
|
||||
private void Log(string msg)
|
||||
|
||||
@ -1,32 +0,0 @@
|
||||
using KubernetesWorkflow.Types;
|
||||
|
||||
namespace MetricsPlugin
|
||||
{
|
||||
public interface IMetricsScrapeTarget
|
||||
{
|
||||
RunningContainer Container { get; }
|
||||
string MetricsPortTag { get; }
|
||||
}
|
||||
|
||||
public interface IHasMetricsScrapeTarget
|
||||
{
|
||||
IMetricsScrapeTarget MetricsScrapeTarget { get; }
|
||||
}
|
||||
|
||||
public interface IHasManyMetricScrapeTargets
|
||||
{
|
||||
IMetricsScrapeTarget[] ScrapeTargets { get; }
|
||||
}
|
||||
|
||||
public class MetricsScrapeTarget : IMetricsScrapeTarget
|
||||
{
|
||||
public MetricsScrapeTarget(RunningContainer container, string metricsPortTag)
|
||||
{
|
||||
Container = container;
|
||||
MetricsPortTag = metricsPortTag;
|
||||
}
|
||||
|
||||
public RunningContainer Container { get; }
|
||||
public string MetricsPortTag { get; }
|
||||
}
|
||||
}
|
||||
@ -3,6 +3,7 @@ using KubernetesWorkflow;
|
||||
using KubernetesWorkflow.Types;
|
||||
using Logging;
|
||||
using System.Text;
|
||||
using Utils;
|
||||
|
||||
namespace MetricsPlugin
|
||||
{
|
||||
@ -16,7 +17,7 @@ namespace MetricsPlugin
|
||||
this.tools = tools;
|
||||
}
|
||||
|
||||
public RunningPod CollectMetricsFor(IMetricsScrapeTarget[] targets, TimeSpan scrapeInterval)
|
||||
public RunningPod CollectMetricsFor(Address[] targets, TimeSpan scrapeInterval)
|
||||
{
|
||||
if (!targets.Any()) throw new ArgumentException(nameof(targets) + " must not be empty.");
|
||||
|
||||
@ -32,7 +33,7 @@ namespace MetricsPlugin
|
||||
return runningContainers;
|
||||
}
|
||||
|
||||
public MetricsAccess CreateAccessForTarget(RunningPod metricsPod, IMetricsScrapeTarget target)
|
||||
public MetricsAccess CreateAccessForTarget(RunningPod metricsPod, Address target)
|
||||
{
|
||||
var metricsQuery = new MetricsQuery(tools, metricsPod.Containers.Single());
|
||||
return new MetricsAccess(metricsQuery, target);
|
||||
@ -48,7 +49,7 @@ namespace MetricsPlugin
|
||||
tools.GetLog().Log(msg);
|
||||
}
|
||||
|
||||
private string GeneratePrometheusConfig(IMetricsScrapeTarget[] targets, TimeSpan scrapeInterval)
|
||||
private string GeneratePrometheusConfig(Address[] targets, TimeSpan scrapeInterval)
|
||||
{
|
||||
var secs = Convert.ToInt32(scrapeInterval.TotalSeconds);
|
||||
if (secs < 1) throw new Exception("ScrapeInterval can't be < 1s");
|
||||
@ -74,19 +75,18 @@ namespace MetricsPlugin
|
||||
return Convert.ToBase64String(bytes);
|
||||
}
|
||||
|
||||
private string FormatTarget(IMetricsScrapeTarget target)
|
||||
private string FormatTarget(Address target)
|
||||
{
|
||||
return ScrapeTargetHelper.FormatTarget(tools.GetLog(), target);
|
||||
return ScrapeTargetHelper.FormatTarget(target);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ScrapeTargetHelper
|
||||
{
|
||||
public static string FormatTarget(ILog log, IMetricsScrapeTarget target)
|
||||
public static string FormatTarget(Address target)
|
||||
{
|
||||
var a = target.Container.GetAddress(target.MetricsPortTag);
|
||||
var host = a.Host.Replace("http://", "").Replace("https://", "");
|
||||
return $"{host}:{a.Port}";
|
||||
var host = target.Host.Replace("http://", "").Replace("https://", "");
|
||||
return $"{host}:{target.Port}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,15 +1,13 @@
|
||||
using CodexPlugin;
|
||||
using Core;
|
||||
using DistTestCore;
|
||||
using CodexClient;
|
||||
using FileUtils;
|
||||
using Logging;
|
||||
using MetricsPlugin;
|
||||
using Utils;
|
||||
|
||||
namespace ContinuousTests
|
||||
{
|
||||
public abstract class ContinuousTestLongTimeouts : ContinuousTest
|
||||
{
|
||||
public override ITimeSet TimeSet => new LongTimeSet();
|
||||
}
|
||||
|
||||
public abstract class ContinuousTest
|
||||
@ -44,16 +42,15 @@ namespace ContinuousTests
|
||||
public ILog 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 CancellationToken CancelToken { get; private set; } = new CancellationToken();
|
||||
public NodeRunner NodeRunner { get; private set; } = null!;
|
||||
|
||||
public IMetricsAccess CreateMetricsAccess(IHasMetricsScrapeTarget target)
|
||||
{
|
||||
return CreateMetricsAccess(target.MetricsScrapeTarget);
|
||||
return CreateMetricsAccess(target.GetMetricsScrapeTarget());
|
||||
}
|
||||
|
||||
public IMetricsAccess CreateMetricsAccess(IMetricsScrapeTarget target)
|
||||
public IMetricsAccess CreateMetricsAccess(Address target)
|
||||
{
|
||||
if (Configuration.CodexDeployment.PrometheusContainer == null) throw new Exception("Expected prometheus to be part of Codex deployment.");
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
using KubernetesWorkflow.Types;
|
||||
using Logging;
|
||||
using Utils;
|
||||
using WebUtils;
|
||||
|
||||
namespace ContinuousTests
|
||||
{
|
||||
@ -37,7 +38,7 @@ namespace ContinuousTests
|
||||
var endpoint = CreateElasticSearchEndpoint();
|
||||
var queryTemplate = CreateQueryTemplate(container, startUtc, endUtc);
|
||||
|
||||
targetFile.Write($"Downloading '{container.Name}' to '{targetFile.FullFilename}'.");
|
||||
targetFile.Write($"Downloading '{container.Name}' to '{targetFile.Filename}'.");
|
||||
var reconstructor = new LogReconstructor(targetFile, endpoint, queryTemplate);
|
||||
reconstructor.DownloadFullLog();
|
||||
|
||||
@ -68,7 +69,7 @@ namespace ContinuousTests
|
||||
{
|
||||
var serviceName = "elasticsearch";
|
||||
var k8sNamespace = "monitoring";
|
||||
var address = new Address($"http://{serviceName}.{k8sNamespace}.svc.cluster.local", 9200);
|
||||
var address = new Address("ElasticSearchEndpoint", $"http://{serviceName}.{k8sNamespace}.svc.cluster.local", 9200);
|
||||
var baseUrl = "";
|
||||
|
||||
var http = tools.CreateHttp(address.ToString(), client =>
|
||||
|
||||
@ -3,8 +3,7 @@ using Logging;
|
||||
using Utils;
|
||||
using Core;
|
||||
using CodexPlugin;
|
||||
using KubernetesWorkflow.Types;
|
||||
using KubernetesWorkflow;
|
||||
using CodexClient;
|
||||
|
||||
namespace ContinuousTests
|
||||
{
|
||||
@ -24,12 +23,6 @@ namespace ContinuousTests
|
||||
this.customNamespace = customNamespace;
|
||||
}
|
||||
|
||||
public IDownloadedLog DownloadLog(RunningContainer container, int? tailLines = null)
|
||||
{
|
||||
var entryPoint = CreateEntryPoint();
|
||||
return entryPoint.CreateInterface().DownloadLog(container, tailLines);
|
||||
}
|
||||
|
||||
public void RunNode(Action<ICodexSetup> setup, Action<ICodexNode> operation)
|
||||
{
|
||||
RunNode(nodes.ToList().PickOneRandom(), setup, operation);
|
||||
@ -40,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.Container.Recipe.Image;
|
||||
CodexContainerRecipe.DockerImageOverride = bootstrapNode.GetImageName();
|
||||
|
||||
try
|
||||
{
|
||||
@ -59,7 +52,7 @@ namespace ContinuousTests
|
||||
}
|
||||
catch
|
||||
{
|
||||
DownloadLog(node.Container);
|
||||
node.DownloadLog();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ using DistTestCore.Logs;
|
||||
using Core;
|
||||
using KubernetesWorkflow.Types;
|
||||
using TaskFactory = Utils.TaskFactory;
|
||||
using CodexClient;
|
||||
|
||||
namespace ContinuousTests
|
||||
{
|
||||
@ -125,13 +126,14 @@ namespace ContinuousTests
|
||||
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
var container = node.Container;
|
||||
var deploymentName = container.RunningPod.StartResult.Deployment.Name;
|
||||
var namespaceName = container.RunningPod.StartResult.Cluster.Configuration.KubernetesNamespace;
|
||||
var openingLine =
|
||||
$"{namespaceName} - {deploymentName} = {node.Container.Name} = {node.GetDebugInfo().Id}";
|
||||
elasticSearchLogDownloader.Download(fixtureLog.CreateSubfile(node.GetName()), node.Container, effectiveStart,
|
||||
effectiveEnd, openingLine);
|
||||
//var container = node.Container;
|
||||
//var deploymentName = container.RunningPod.StartResult.Deployment.Name;
|
||||
//var namespaceName = container.RunningPod.StartResult.Cluster.Configuration.KubernetesNamespace;
|
||||
//var openingLine =
|
||||
// $"{namespaceName} - {deploymentName} = {node.Container.Name} = {node.GetDebugInfo().Id}";
|
||||
//elasticSearchLogDownloader.Download(fixtureLog.CreateSubfile(node.GetName()), node.Container, effectiveStart,
|
||||
// effectiveEnd, openingLine);
|
||||
throw new NotImplementedException("access to container info is unavilable.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -204,7 +206,6 @@ namespace ContinuousTests
|
||||
result.Add("testname", testName);
|
||||
result.Add("message", message);
|
||||
result.Add("involvedpods", string.Join(",", nodes.Select(n => n.GetName())));
|
||||
result.Add("involvedpodnames", string.Join(",", nodes.Select(n => n.GetPodInfo().Name)));
|
||||
|
||||
var error = message.Split(Environment.NewLine).First();
|
||||
if (error.Contains(":")) error = error.Substring(1 + error.LastIndexOf(":"));
|
||||
@ -286,26 +287,26 @@ namespace ContinuousTests
|
||||
private string GetContainerNames()
|
||||
{
|
||||
if (handle.Test.RequiredNumberOfNodes == -1) return "(All Nodes)";
|
||||
return $"({string.Join(",", nodes.Select(n => n.Container.Name))})";
|
||||
return $"({string.Join(",", nodes.Select(n => n.GetName()))})";
|
||||
}
|
||||
|
||||
private ICodexNode[] CreateRandomNodes()
|
||||
{
|
||||
var containers = SelectRandomContainers();
|
||||
fixtureLog.Log("Selected nodes: " + string.Join(",", containers.Select(c => c.Name)));
|
||||
return entryPoint.CreateInterface().WrapCodexContainers(containers).ToArray();
|
||||
var instances = SelectRandomInstance();
|
||||
fixtureLog.Log("Selected nodes: " + string.Join(",", instances.Select(c => c.Name)));
|
||||
return entryPoint.CreateInterface().WrapCodexContainers(instances).ToArray();
|
||||
}
|
||||
|
||||
private RunningPod[] SelectRandomContainers()
|
||||
private ICodexInstance[] SelectRandomInstance()
|
||||
{
|
||||
var number = handle.Test.RequiredNumberOfNodes;
|
||||
var containers = config.CodexDeployment.CodexInstances.Select(i => i.Pod).ToList();
|
||||
if (number == -1) return containers.ToArray();
|
||||
var instances = config.CodexDeployment.CodexInstances.ToList();
|
||||
if (number == -1) return instances.ToArray();
|
||||
|
||||
var result = new RunningPod[number];
|
||||
var result = new ICodexInstance[number];
|
||||
for (var i = 0; i < number; i++)
|
||||
{
|
||||
result[i] = containers.PickOneRandom();
|
||||
result[i] = instances.PickOneRandom();
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
using CodexPlugin;
|
||||
using CodexClient;
|
||||
using Core;
|
||||
using DistTestCore.Logs;
|
||||
using Logging;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ContinuousTests
|
||||
{
|
||||
@ -39,25 +38,27 @@ namespace ContinuousTests
|
||||
private void IncludeDeploymentConfiguration(ILog log)
|
||||
{
|
||||
log.Log("");
|
||||
var deployment = config.CodexDeployment;
|
||||
var workflow = entryPoint.Tools.CreateWorkflow();
|
||||
foreach (var instance in deployment.CodexInstances)
|
||||
{
|
||||
foreach (var container in instance.Pod.Containers)
|
||||
{
|
||||
var podInfo = workflow.GetPodInfo(container);
|
||||
log.Log($"Codex environment variables for '{container.Name}':");
|
||||
log.Log(
|
||||
$"Namespace: {container.RunningPod.StartResult.Cluster.Configuration.KubernetesNamespace} - " +
|
||||
$"Pod name: {podInfo.Name} - Deployment name: {instance.Pod.StartResult.Deployment.Name}");
|
||||
var codexVars = container.Recipe.EnvVars;
|
||||
foreach (var vars in codexVars) log.Log(vars.ToString());
|
||||
log.Log("");
|
||||
}
|
||||
}
|
||||
|
||||
log.Log($"Deployment metadata: {JsonConvert.SerializeObject(deployment.Metadata)}");
|
||||
log.Log("");
|
||||
throw new NotImplementedException();
|
||||
//var deployment = config.CodexDeployment;
|
||||
//var workflow = entryPoint.Tools.CreateWorkflow();
|
||||
//foreach (var instance in deployment.CodexInstances)
|
||||
//{
|
||||
// foreach (var container in instance.Pod.Containers)
|
||||
// {
|
||||
// var podInfo = workflow.GetPodInfo(container);
|
||||
// log.Log($"Codex environment variables for '{container.Name}':");
|
||||
// log.Log(
|
||||
// $"Namespace: {container.RunningPod.StartResult.Cluster.Configuration.KubernetesNamespace} - " +
|
||||
// $"Pod name: {podInfo.Name} - Deployment name: {instance.Pod.StartResult.Deployment.Name}");
|
||||
// var codexVars = container.Recipe.EnvVars;
|
||||
// foreach (var vars in codexVars) log.Log(vars.ToString());
|
||||
// log.Log("");
|
||||
// }
|
||||
//}
|
||||
|
||||
//log.Log($"Deployment metadata: {JsonConvert.SerializeObject(deployment.Metadata)}");
|
||||
//log.Log("");
|
||||
}
|
||||
|
||||
private void PreflightCheck(Configuration config)
|
||||
@ -91,31 +92,33 @@ namespace ContinuousTests
|
||||
|
||||
private void CheckCodexNodes(BaseLog log, Configuration config)
|
||||
{
|
||||
var nodes = entryPoint.CreateInterface()
|
||||
.WrapCodexContainers(config.CodexDeployment.CodexInstances.Select(i => i.Pod).ToArray());
|
||||
var pass = true;
|
||||
foreach (var n in nodes)
|
||||
{
|
||||
cancelToken.ThrowIfCancellationRequested();
|
||||
throw new NotImplementedException();
|
||||
|
||||
var address = n.Container.GetAddress(CodexContainerRecipe.ApiPortTag);
|
||||
log.Log($"Checking {n.Container.Name} @ '{address}'...");
|
||||
//var nodes = entryPoint.CreateInterface()
|
||||
// .WrapCodexContainers(config.CodexDeployment.CodexInstances.Select(i => i.Pod).ToArray());
|
||||
//var pass = true;
|
||||
//foreach (var n in nodes)
|
||||
//{
|
||||
// cancelToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (EnsureOnline(log, n))
|
||||
{
|
||||
log.Log("OK");
|
||||
}
|
||||
else
|
||||
{
|
||||
log.Error($"No response from '{address}'.");
|
||||
pass = false;
|
||||
}
|
||||
}
|
||||
// var address = n.GetApiEndpoint();
|
||||
// log.Log($"Checking {n.GetName()} @ '{address}'...");
|
||||
|
||||
if (!pass)
|
||||
{
|
||||
throw new Exception("Not all codex nodes responded.");
|
||||
}
|
||||
// if (EnsureOnline(log, n))
|
||||
// {
|
||||
// log.Log("OK");
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// log.Error($"No response from '{address}'.");
|
||||
// pass = false;
|
||||
// }
|
||||
//}
|
||||
|
||||
//if (!pass)
|
||||
//{
|
||||
// throw new Exception("Not all codex nodes responded.");
|
||||
//}
|
||||
}
|
||||
|
||||
private bool EnsureOnline(BaseLog log, ICodexNode n)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using CodexPlugin;
|
||||
using CodexClient;
|
||||
using FileUtils;
|
||||
using NUnit.Framework;
|
||||
using Utils;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using CodexPlugin;
|
||||
using CodexClient;
|
||||
using CodexTests.Helpers;
|
||||
using ContinuousTests;
|
||||
using NUnit.Framework;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using CodexPlugin;
|
||||
using CodexClient;
|
||||
using FileUtils;
|
||||
using Logging;
|
||||
using NUnit.Framework;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using CodexPlugin;
|
||||
using CodexClient;
|
||||
using CodexTests;
|
||||
using DistTestCore;
|
||||
using FileUtils;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using CodexPlugin;
|
||||
using CodexClient;
|
||||
using CodexTests;
|
||||
using DistTestCore;
|
||||
using FileUtils;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using CodexPlugin;
|
||||
using CodexClient;
|
||||
using DistTestCore;
|
||||
using NUnit.Framework;
|
||||
using Utils;
|
||||
@ -31,7 +31,7 @@ namespace CodexTests.ScalabilityTests
|
||||
Task.WaitAll(uploadTasks);
|
||||
var cid = new ContentId(uploadTasks.Select(t => t.Result.Id).Distinct().Single());
|
||||
|
||||
var uploadLog = Ci.DownloadLog(hosts[0]);
|
||||
var uploadLog = hosts[0].DownloadLog();
|
||||
var expectedNumberOfBlocks = RoundUp(fileSize.MB().SizeInBytes, 64.KB().SizeInBytes) + 1; // +1 for manifest block.
|
||||
var blockCids = uploadLog
|
||||
.FindLinesThatContain("Block Stored")
|
||||
@ -50,7 +50,7 @@ namespace CodexTests.ScalabilityTests
|
||||
var resultFile = client.DownloadContent(cid);
|
||||
resultFile!.AssertIsEqual(file);
|
||||
|
||||
var downloadLog = Ci.DownloadLog(client);
|
||||
var downloadLog = client.DownloadLog();
|
||||
var host = string.Empty;
|
||||
var blockAddressHostMap = new Dictionary<string, List<string>>();
|
||||
downloadLog
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using CodexPlugin;
|
||||
using CodexClient;
|
||||
using DistTestCore;
|
||||
using FileUtils;
|
||||
using NUnit.Framework;
|
||||
@ -38,16 +38,16 @@ public class ScalabilityTests : CodexDistTest
|
||||
var testFile = GenerateTestFile(fileSizeInMb.MB());
|
||||
|
||||
LogNodeStatus(uploader);
|
||||
var contentId = uploader.UploadFile(testFile, f => LogNodeStatus(uploader));
|
||||
var contentId = uploader.UploadFile(testFile);
|
||||
LogNodeStatus(uploader);
|
||||
|
||||
LogNodeStatus(downloader);
|
||||
var downloadedFile = downloader.DownloadContent(contentId, f => LogNodeStatus(downloader));
|
||||
var downloadedFile = downloader.DownloadContent(contentId);
|
||||
LogNodeStatus(downloader);
|
||||
|
||||
downloadedFile!.AssertIsEqual(testFile);
|
||||
|
||||
uploader.DeleteRepoFolder();
|
||||
uploader.DeleteDataDirFolder();
|
||||
uploader.Stop(true);
|
||||
|
||||
var otherDownloader = nodes.PickOneRandom();
|
||||
@ -55,8 +55,8 @@ public class ScalabilityTests : CodexDistTest
|
||||
|
||||
downloadedFile!.AssertIsEqual(testFile);
|
||||
|
||||
downloader.DeleteRepoFolder();
|
||||
otherDownloader.DeleteRepoFolder();
|
||||
downloader.DeleteDataDirFolder();
|
||||
otherDownloader.DeleteDataDirFolder();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user