Extracts codexClient assembly

This commit is contained in:
ThatBen 2025-01-16 11:31:50 +01:00
parent 48f4614e5d
commit 4a151880d4
No known key found for this signature in database
GPG Key ID: 62C543548433D43E
45 changed files with 383 additions and 320 deletions

View File

@ -9,6 +9,7 @@
<ItemGroup>
<ProjectReference Include="..\FileUtils\FileUtils.csproj" />
<ProjectReference Include="..\KubernetesWorkflow\KubernetesWorkflow.csproj" />
<ProjectReference Include="..\WebUtils\WebUtils.csproj" />
</ItemGroup>
</Project>

View File

@ -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())
{
}

View File

@ -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)

View File

@ -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);
}
}
}

View 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);
}
}
}

View 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();
}
}
}

View File

@ -1,4 +1,4 @@
namespace GethPlugin
namespace Utils
{
public interface IHasEthAddress
{

View File

@ -1,4 +1,4 @@
namespace GethPlugin
namespace Utils
{
public class Ether : IComparable<Ether>
{

View File

@ -1,6 +1,6 @@
using System.Numerics;
namespace CodexContractsPlugin
namespace Utils
{
public class TestToken : IComparable<TestToken>
{

View File

@ -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)
{

View File

@ -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;

View 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);
}
}
}

View File

@ -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);
}
}
}

View 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>

View File

@ -1,26 +1,25 @@
using CodexOpenApi;
using Core;
using GethPlugin;
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, IProcessControl processControl, ICodexInstance instance, ICrashWatcher crashWatcher)
public CodexAccess(ILog log, IHttpFactory httpFactory, IProcessControl processControl, ICodexInstance instance, ICrashWatcher crashWatcher)
{
this.tools = tools;
this.log = log;
this.httpFactory = httpFactory;
this.processControl = processControl;
this.instance = instance;
log = tools.GetLog();
CrashWatcher = crashWatcher;
CrashWatcher.Start();
@ -103,19 +102,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;
}
@ -147,7 +141,6 @@ namespace CodexPlugin
return JsonConvert.DeserializeObject<LocalDatasetListJson>(str)!;
}, nameof(LocalFiles));
}));
}
public StorageAvailability SalesAvailability(StorageAvailability request)
@ -227,22 +220,22 @@ namespace CodexPlugin
processControl.DeleteDataDirFolder(instance);
}
private T OnCodex<T>(Func<CodexApi, Task<T>> action)
private T OnCodex<T>(Func<openapiClient, Task<T>> action)
{
var result = tools.CreateHttp(GetHttpId(), CheckContainerCrashed).OnClient(client => CallCodex(client, action));
var result = httpFactory.CreateHttp(GetHttpId(), CheckContainerCrashed).OnClient(client => CallCodex(client, action));
return result;
}
private T OnCodex<T>(Func<CodexApi, Task<T>> action, Retry retry)
private T OnCodex<T>(Func<openapiClient, Task<T>> action, Retry retry)
{
var result = tools.CreateHttp(GetHttpId(), CheckContainerCrashed).OnClient(client => CallCodex(client, action), retry);
var result = httpFactory.CreateHttp(GetHttpId(), 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<openapiClient, Task<T>> action)
{
var address = GetAddress();
var api = new CodexApi(client);
var api = new openapiClient(client);
api.BaseUrl = $"{address.Host}:{address.Port}/api/codex/v1";
return CrashCheck(() => Time.Wait(action(api)));
}
@ -261,7 +254,7 @@ namespace CodexPlugin
private IEndpoint GetEndpoint()
{
return tools
return httpFactory
.CreateHttp(GetHttpId(), CheckContainerCrashed)
.CreateEndpoint(GetAddress(), "/api/codex/v1/", GetName());
}
@ -281,49 +274,6 @@ namespace CodexPlugin
if (CrashWatcher.HasCrashed()) 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);
}
}
private void Throw(Failure failure)
{
throw failure.Exception;

View File

@ -0,0 +1,39 @@
<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</CodeGenerator>
<Namespace>CodexOpenApi</Namespace>
<ClassName></ClassName>
</OpenApiReference>
</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>

View File

@ -0,0 +1,40 @@
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; }
}
}

View File

@ -1,4 +1,4 @@
namespace CodexPlugin
namespace CodexClient
{
public enum CodexLogLevel
{

View File

@ -1,6 +1,6 @@
using System.Globalization;
namespace CodexPlugin
namespace CodexClient
{
public class CodexLogLine
{

View File

@ -1,14 +1,11 @@
using CodexPlugin.Hooks;
using Core;
using CodexClient.Hooks;
using FileUtils;
using GethPlugin;
using Logging;
using MetricsPlugin;
using Utils;
namespace CodexPlugin
namespace CodexClient
{
public interface ICodexNode : IHasMetricsScrapeTarget, IHasEthAddress
public partial interface ICodexNode : IHasEthAddress
{
string GetName();
string GetImageName();
@ -17,10 +14,8 @@ namespace CodexPlugin
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
@ -54,23 +49,23 @@ namespace CodexPlugin
{
private const string UploadFailedMessage = "Unable to store block";
private readonly ILog log;
private readonly IPluginTools tools;
private readonly ICodexNodeHooks hooks;
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, IMarketplaceAccess marketplaceAccess, ICodexNodeHooks hooks)
public CodexNode(ILog log, CodexAccess codexAccess, IFileManager fileManager, IMarketplaceAccess marketplaceAccess, ICodexNodeHooks hooks)
{
this.tools = tools;
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()
@ -156,15 +151,10 @@ namespace CodexPlugin
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();
@ -176,7 +166,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;
@ -194,17 +184,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);
@ -337,20 +322,20 @@ namespace CodexPlugin
.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.
var start = DateTime.UtcNow;
var cts = new CancellationTokenSource();
var downloadTask = Task.Run((Action)(() =>
var downloadTask = Task.Run(() =>
{
using var downloadStream = this.codexAccess.DownloadFile(contentId, onFailure);
downloadStream.CopyTo((Stream)fileStream);
}), cts.Token);
using var downloadStream = codexAccess.DownloadFile(contentId);
downloadStream.CopyTo(fileStream);
}, cts.Token);
while (DateTime.UtcNow - start < timeout)
{
@ -411,9 +396,5 @@ namespace CodexPlugin
{
log.Log(msg);
}
private void DoNothing(Failure failure)
{
}
}
}

View File

@ -1,7 +1,7 @@
using Newtonsoft.Json;
using Utils;
namespace CodexPlugin
namespace CodexClient
{
public class DebugInfo
{

View File

@ -1,4 +1,4 @@
namespace CodexPlugin
namespace CodexClient
{
public static class CodexUtils
{

View File

@ -1,7 +1,6 @@
using GethPlugin;
using Utils;
using Utils;
namespace CodexPlugin.Hooks
namespace CodexClient.Hooks
{
public interface ICodexHooksProvider
{

View File

@ -1,7 +1,6 @@
using GethPlugin;
using Utils;
using Utils;
namespace CodexPlugin.Hooks
namespace CodexClient.Hooks
{
public interface ICodexNodeHooks
{

View File

@ -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)
{

View File

@ -1,8 +1,8 @@
using CodexPlugin.Hooks;
using CodexClient.Hooks;
using Logging;
using Utils;
namespace CodexPlugin
namespace CodexClient
{
public interface IMarketplaceAccess
{

View File

@ -1,8 +1,7 @@
using CodexContractsPlugin;
using Logging;
using Logging;
using Utils;
namespace CodexPlugin
namespace CodexClient
{
public class StoragePurchaseRequest
{

View File

@ -0,0 +1,11 @@
using Logging;
namespace CodexClient
{
public interface IProcessControl
{
void Stop(ICodexInstance instance, bool waitTillStopped);
IDownloadedLog DownloadLog(ICodexInstance instance, LogFile file);
void DeleteDataDirFolder(ICodexInstance instance);
}
}

View File

@ -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
{
@ -14,7 +12,7 @@ namespace CodexPlugin
ContentId ContentId { get; }
void WaitForStorageContractSubmitted();
void WaitForStorageContractStarted();
void WaitForStorageContractFinished(ICodexContracts contracts);
void WaitForStorageContractFinished();
void WaitForContractFailed();
}
@ -65,7 +63,7 @@ namespace CodexPlugin
AssertDuration(SubmittedToStarted, timeout, nameof(SubmittedToStarted));
}
public void WaitForStorageContractFinished(ICodexContracts contracts)
public void WaitForStorageContractFinished()
{
if (!contractStartedUtc.HasValue)
{
@ -77,13 +75,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()

View File

@ -1,6 +1,6 @@
using Utils;
namespace CodexPlugin
namespace CodexClient
{
public interface ITransferSpeeds
{

View File

@ -1,47 +1,10 @@
using Core;
using GethPlugin;
using KubernetesWorkflow.Types;
using Logging;
using Utils;
namespace CodexPlugin
{
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 class CodexInstanceContainerExtension
{
public static ICodexInstance CreateFromPod(RunningPod pod)

View File

@ -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" />

View File

@ -1,6 +1,5 @@
using CodexPlugin.Hooks;
using Core;
using GethPlugin;
using KubernetesWorkflow;
using KubernetesWorkflow.Types;
using Logging;

View File

@ -1,5 +1,4 @@
using CodexPlugin.Hooks;
using GethPlugin;
using OverwatchTranscript;
using Utils;

View File

@ -1,29 +0,0 @@
using Logging;
namespace CodexPlugin
{
public interface IProcessControl
{
void Stop(ICodexInstance instance, bool waitTillStopped);
IDownloadedLog DownloadLog(ICodexInstance instance, LogFile file);
void DeleteDataDirFolder(ICodexInstance instance);
}
//public void DeleteDataDirFolder()
//{
// 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.Log("Deleted repo folder.");
// }
// catch (Exception e)
// {
// log.Log("Unable to delete repo folder: " + e);
// }
//}
}

View File

@ -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(pluginRoot, "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);

View File

@ -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();
}
}
}

View File

@ -1,4 +1,5 @@
using CodexContractsPlugin;
using CodexClient;
using CodexContractsPlugin;
using CodexPlugin;
using GethPlugin;
using NUnit.Framework;

View File

@ -1,4 +1,5 @@
using CodexContractsPlugin;
using CodexClient;
using CodexContractsPlugin;
using CodexPlugin;
using GethPlugin;
using NUnit.Framework;
@ -37,6 +38,14 @@ namespace CodexReleaseTests.MarketTests
All(requests, r => r.WaitForStorageContractFinished(GetContracts()));
// todo: removed from codexclient:
//contracts.WaitUntilNextPeriod();
//contracts.WaitUntilNextPeriod();
//var blocks = 3;
//Log($"Waiting {blocks} blocks for nodes to process payouts...");
//Thread.Sleep(GethContainerRecipe.BlockInterval * blocks);
// todo:
//AssertClientHasPaidForContract(pricePerSlotPerSecond, client, request, hosts);
//AssertHostsWerePaidForContract(pricePerSlotPerSecond, request, hosts);

View File

@ -1,6 +1,5 @@
using CodexPlugin;
using DistTestCore;
using GethPlugin;
using MetricsPlugin;
using NUnit.Framework;
using Utils;

View File

@ -1,4 +1,5 @@
using CodexContractsPlugin;
using CodexClient;
using CodexContractsPlugin;
using CodexContractsPlugin.Marketplace;
using CodexPlugin;
using FileUtils;
@ -109,6 +110,14 @@ namespace CodexTests.BasicTests
purchaseContract.WaitForStorageContractFinished(contracts);
// todo: removed from codexclient:
//contracts.WaitUntilNextPeriod();
//contracts.WaitUntilNextPeriod();
//var blocks = 3;
//Log($"Waiting {blocks} blocks for nodes to process payouts...");
//Thread.Sleep(GethContainerRecipe.BlockInterval * blocks);
AssertBalance(contracts, client, Is.LessThan(clientInitialBalance), "Buyer was not charged for storage.");
Assert.That(contracts.GetRequestState(request), Is.EqualTo(RequestState.Finished));
}

View File

@ -1,4 +1,5 @@
using CodexContractsPlugin;
using CodexClient;
using CodexContractsPlugin;
using CodexDiscordBotPlugin;
using CodexPlugin;
using Core;
@ -46,6 +47,15 @@ namespace CodexTests.UtilityTests
var purchaseContract = ClientPurchasesStorage(client);
purchaseContract.WaitForStorageContractStarted();
purchaseContract.WaitForStorageContractFinished(contracts);
// todo: removed from codexclient:
//contracts.WaitUntilNextPeriod();
//contracts.WaitUntilNextPeriod();
//var blocks = 3;
//Log($"Waiting {blocks} blocks for nodes to process payouts...");
//Thread.Sleep(GethContainerRecipe.BlockInterval * blocks);
Thread.Sleep(rewarderInterval * 3);
apiCalls.Stop();

View File

@ -1,5 +1,5 @@
using GethPlugin;
using Microsoft.AspNetCore.Mvc;
using Utils;
namespace KeyMaker.Controllers
{

View File

@ -82,6 +82,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExperimentalTests", "Tests\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlockchainUtils", "Framework\BlockchainUtils\BlockchainUtils.csproj", "{4648B5AA-A0A7-44BA-89BC-2FD57370943C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodexClient", "ProjectPlugins\CodexClient\CodexClient.csproj", "{9AF12703-29AF-416D-9781-204223D6D0E5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebUtils", "Framework\WebUtils\WebUtils.csproj", "{372C9E5D-5453-4D45-9948-E9324E21AD65}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -220,6 +224,14 @@ Global
{4648B5AA-A0A7-44BA-89BC-2FD57370943C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4648B5AA-A0A7-44BA-89BC-2FD57370943C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4648B5AA-A0A7-44BA-89BC-2FD57370943C}.Release|Any CPU.Build.0 = Release|Any CPU
{9AF12703-29AF-416D-9781-204223D6D0E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9AF12703-29AF-416D-9781-204223D6D0E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9AF12703-29AF-416D-9781-204223D6D0E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9AF12703-29AF-416D-9781-204223D6D0E5}.Release|Any CPU.Build.0 = Release|Any CPU
{372C9E5D-5453-4D45-9948-E9324E21AD65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{372C9E5D-5453-4D45-9948-E9324E21AD65}.Debug|Any CPU.Build.0 = Debug|Any CPU
{372C9E5D-5453-4D45-9948-E9324E21AD65}.Release|Any CPU.ActiveCfg = Release|Any CPU
{372C9E5D-5453-4D45-9948-E9324E21AD65}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -258,6 +270,8 @@ Global
{639A0603-4E80-465B-BB59-AB02F1DEEF5A} = {88C2A621-8A98-4D07-8625-7900FC8EF89E}
{BA7369CD-7C2F-4075-8E35-98BCC19EE203} = {88C2A621-8A98-4D07-8625-7900FC8EF89E}
{4648B5AA-A0A7-44BA-89BC-2FD57370943C} = {81AE04BC-CBFA-4E6F-B039-8208E9AFAAE7}
{9AF12703-29AF-416D-9781-204223D6D0E5} = {8F1F1C2A-E313-4E0C-BE40-58FB0BA91124}
{372C9E5D-5453-4D45-9948-E9324E21AD65} = {81AE04BC-CBFA-4E6F-B039-8208E9AFAAE7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {237BF0AA-9EC4-4659-AD9A-65DEB974250C}