Sets up autoclient.
This commit is contained in:
parent
4cd22f3719
commit
d532d9505a
|
@ -0,0 +1,16 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Framework\ArgsUniform\ArgsUniform.csproj" />
|
||||
<ProjectReference Include="..\Framework\Logging\Logging.csproj" />
|
||||
<ProjectReference Include="..\ProjectPlugins\CodexPlugin\CodexPlugin.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,112 @@
|
|||
using CodexOpenApi;
|
||||
using CodexPlugin;
|
||||
using Core;
|
||||
using Utils;
|
||||
using DebugInfo = CodexPlugin.DebugInfo;
|
||||
|
||||
namespace AutoClient
|
||||
{
|
||||
public class Codex
|
||||
{
|
||||
private readonly IPluginTools tools;
|
||||
private readonly Address address;
|
||||
private readonly Mapper mapper = new Mapper();
|
||||
|
||||
/// <summary>
|
||||
/// This class was largely copied from CodexAccess in CodexPlugin.
|
||||
/// Should really be generalized so CodexPlugin supports talking to custom Codex instances.
|
||||
/// </summary>
|
||||
public Codex(IPluginTools tools, Address address)
|
||||
{
|
||||
this.tools = tools;
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public DebugInfo GetDebugInfo()
|
||||
{
|
||||
return mapper.Map(OnCodex(api => api.GetDebugInfoAsync()));
|
||||
}
|
||||
|
||||
public DebugPeer GetDebugPeer(string peerId)
|
||||
{
|
||||
// Cannot use openAPI: debug/peer endpoint is not specified there.
|
||||
var endpoint = GetEndpoint();
|
||||
var str = endpoint.HttpGetString($"debug/peer/{peerId}");
|
||||
|
||||
if (str.ToLowerInvariant() == "unable to find peer!")
|
||||
{
|
||||
return new DebugPeer
|
||||
{
|
||||
IsPeerFound = false
|
||||
};
|
||||
}
|
||||
|
||||
var result = endpoint.Deserialize<DebugPeer>(str);
|
||||
result.IsPeerFound = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
public void ConnectToPeer(string peerId, string[] peerMultiAddresses)
|
||||
{
|
||||
OnCodex(api =>
|
||||
{
|
||||
Time.Wait(api.ConnectPeerAsync(peerId, peerMultiAddresses));
|
||||
return Task.FromResult(string.Empty);
|
||||
});
|
||||
}
|
||||
|
||||
public string UploadFile(FileStream fileStream)
|
||||
{
|
||||
return OnCodex(api => api.UploadAsync(fileStream));
|
||||
}
|
||||
|
||||
public Stream DownloadFile(string contentId)
|
||||
{
|
||||
var fileResponse = OnCodex(api => api.DownloadNetworkAsync(contentId));
|
||||
if (fileResponse.StatusCode != 200) throw new Exception("Download failed with StatusCode: " + fileResponse.StatusCode);
|
||||
return fileResponse.Stream;
|
||||
}
|
||||
|
||||
public LocalDatasetList LocalFiles()
|
||||
{
|
||||
return mapper.Map(OnCodex(api => api.ListDataAsync()));
|
||||
}
|
||||
|
||||
public StorageAvailability SalesAvailability(StorageAvailability request)
|
||||
{
|
||||
var body = mapper.Map(request);
|
||||
var read = OnCodex<SalesAvailabilityREAD>(api => api.OfferStorageAsync(body));
|
||||
return mapper.Map(read);
|
||||
}
|
||||
|
||||
public string RequestStorage(StoragePurchaseRequest request)
|
||||
{
|
||||
var body = mapper.Map(request);
|
||||
return OnCodex<string>(api => api.CreateStorageRequestAsync(request.ContentId.Id, body));
|
||||
}
|
||||
|
||||
public StoragePurchase GetPurchaseStatus(string purchaseId)
|
||||
{
|
||||
return mapper.Map(OnCodex(api => api.GetPurchaseAsync(purchaseId)));
|
||||
}
|
||||
|
||||
private T OnCodex<T>(Func<CodexApi, Task<T>> action)
|
||||
{
|
||||
var result = tools.CreateHttp()
|
||||
.OnClient(client =>
|
||||
{
|
||||
var api = new CodexApi(client);
|
||||
api.BaseUrl = $"{address.Host}:{address.Port}/api/codex/v1";
|
||||
return Time.Wait(action(api));
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private IEndpoint GetEndpoint()
|
||||
{
|
||||
return tools
|
||||
.CreateHttp()
|
||||
.CreateEndpoint(address, "/api/codex/v1/");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
using ArgsUniform;
|
||||
|
||||
namespace AutoClient
|
||||
{
|
||||
public class Configuration
|
||||
{
|
||||
[Uniform("codex-host", "ch", "CODEXHOST", false, "Codex Host address. (default localhost)")]
|
||||
public string CodexHost { get; set; } = "localhost";
|
||||
|
||||
[Uniform("codex-port", "cp", "CODEXPORT", false, "port number of Codex API. (8080 by default)")]
|
||||
public int CodexPort { get; set; } = 8080;
|
||||
|
||||
[Uniform("datapath", "dp", "DATAPATH", false, "Root path where all data files will be saved.")]
|
||||
public string DataPath { get; set; } = "datapath";
|
||||
|
||||
[Uniform("contract-duration", "cd", "CONTRACTDURATION", false, "contract duration in minutes. (default 30)")]
|
||||
public int ContractDurationMinutes { get; set; } = 30;
|
||||
|
||||
[Uniform("contract-expiry", "ce", "CONTRACTEXPIRY", false, "contract expiry in minutes. (default 15)")]
|
||||
public int ContractExpiryMinutes { get; set; } = 15;
|
||||
|
||||
[Uniform("dataset-size", "ds", "DATASETSIZE", false, "Total dataset size in bytes. (default 10MB).")]
|
||||
public int DatasetSizeBytes { get; set; } = 10 * 1024 * 1024;
|
||||
|
||||
[Uniform("num-hosts", "nh", "NUMHOSTS", false, "Number of hosts for contract. (default 5)")]
|
||||
public int NumHosts { get; set; } = 5;
|
||||
|
||||
[Uniform("num-hosts-tolerance", "nt", "NUMTOL", false, "Number of host tolerance for contract. (default 2)")]
|
||||
public int HostTolerance { get; set; } = 2;
|
||||
|
||||
[Uniform("price","p", "PRICE", false, "Price of contract. (default 10)")]
|
||||
public int Price { get; set; } = 10;
|
||||
|
||||
[Uniform("collateral", "c", "COLLATERAL", false, "Required collateral. (default 1)")]
|
||||
public int RequiredCollateral { get; set; } = 1;
|
||||
|
||||
public string LogPath
|
||||
{
|
||||
get
|
||||
{
|
||||
return Path.Combine(DataPath, "logs");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
using ArgsUniform;
|
||||
using AutoClient;
|
||||
using CodexPlugin;
|
||||
using Core;
|
||||
using Logging;
|
||||
using static Org.BouncyCastle.Math.EC.ECCurve;
|
||||
|
||||
public static class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
var cancellationToken = cts.Token;
|
||||
Console.CancelKeyPress += (sender, args) => cts.Cancel();
|
||||
|
||||
var uniformArgs = new ArgsUniform<Configuration>(PrintHelp, args);
|
||||
var config = uniformArgs.Parse(true);
|
||||
|
||||
var log = new LogSplitter(
|
||||
new FileLog(Path.Combine(config.LogPath, "autoclient")),
|
||||
new ConsoleLog()
|
||||
);
|
||||
|
||||
var address = new Utils.Address(
|
||||
host: config.CodexHost,
|
||||
port: config.CodexPort
|
||||
);
|
||||
|
||||
log.Log($"Start. Address: {address}");
|
||||
|
||||
var tools = CreateTools(log, config);
|
||||
var fileManager = tools.GetFileManager();
|
||||
var codex = new Codex(tools, address);
|
||||
|
||||
var runner = new Runner(log, codex, fileManager, cancellationToken, config);
|
||||
runner.Run();
|
||||
|
||||
log.Log("Done.");
|
||||
}
|
||||
|
||||
private static void PrintHelp()
|
||||
{
|
||||
Console.WriteLine("Generates fake data and creates Codex storage contracts for it.");
|
||||
}
|
||||
|
||||
private static IPluginTools CreateTools(ILog log, Configuration config)
|
||||
{
|
||||
var configuration = new KubernetesWorkflow.Configuration(
|
||||
null,
|
||||
operationTimeout: TimeSpan.FromMinutes(10),
|
||||
retryDelay: TimeSpan.FromSeconds(10),
|
||||
kubernetesNamespace: "notUsed!#");
|
||||
|
||||
var result = new EntryPoint(log, configuration, config.DataPath, new DefaultTimeSet());
|
||||
return result.Tools;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
using CodexContractsPlugin;
|
||||
using CodexPlugin;
|
||||
using FileUtils;
|
||||
using Logging;
|
||||
using Utils;
|
||||
|
||||
namespace AutoClient
|
||||
{
|
||||
public class Runner
|
||||
{
|
||||
private readonly ILog log;
|
||||
private readonly Codex codex;
|
||||
private readonly IFileManager fileManager;
|
||||
private readonly CancellationToken ct;
|
||||
private readonly Configuration config;
|
||||
|
||||
public Runner(ILog log, Codex codex, IFileManager fileManager, CancellationToken ct, Configuration config)
|
||||
{
|
||||
this.log = log;
|
||||
this.codex = codex;
|
||||
this.fileManager = fileManager;
|
||||
this.ct = ct;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public void Run()
|
||||
{
|
||||
while (!ct.IsCancellationRequested)
|
||||
{
|
||||
log.Log("New run!");
|
||||
|
||||
try
|
||||
{
|
||||
fileManager.ScopedFiles(() =>
|
||||
{
|
||||
DoRun();
|
||||
});
|
||||
|
||||
log.Log("Run succcessful.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("Exception during run: " + ex);
|
||||
}
|
||||
|
||||
FixedShortDelay();
|
||||
}
|
||||
}
|
||||
|
||||
private void DoRun()
|
||||
{
|
||||
var file = CreateFile();
|
||||
var cid = UploadFile(file);
|
||||
var pid = RequestStorage(cid);
|
||||
WaitUntilStarted(pid);
|
||||
}
|
||||
|
||||
private TrackedFile CreateFile()
|
||||
{
|
||||
return fileManager.GenerateFile(new ByteSize(Convert.ToInt64(config.DatasetSizeBytes)));
|
||||
}
|
||||
|
||||
private ContentId UploadFile(TrackedFile file)
|
||||
{
|
||||
// Copied from CodexNode :/
|
||||
using var fileStream = File.OpenRead(file.Filename);
|
||||
|
||||
var logMessage = $"Uploading file {file.Describe()}...";
|
||||
log.Log(logMessage);
|
||||
var response = codex.UploadFile(fileStream);
|
||||
|
||||
if (string.IsNullOrEmpty(response)) FrameworkAssert.Fail("Received empty response.");
|
||||
if (response.StartsWith("Unable to store block")) FrameworkAssert.Fail("Node failed to store block.");
|
||||
|
||||
log.Log($"Uploaded file. Received contentId: '{response}'.");
|
||||
return new ContentId(response);
|
||||
}
|
||||
|
||||
private string RequestStorage(ContentId cid)
|
||||
{
|
||||
var request = new StoragePurchaseRequest(cid)
|
||||
{
|
||||
PricePerSlotPerSecond = config.Price.TestTokens(),
|
||||
RequiredCollateral = config.RequiredCollateral.TestTokens(),
|
||||
MinRequiredNumberOfNodes = Convert.ToUInt32(config.NumHosts),
|
||||
NodeFailureTolerance = Convert.ToUInt32(config.HostTolerance),
|
||||
Duration = TimeSpan.FromMinutes(config.ContractDurationMinutes),
|
||||
Expiry = TimeSpan.FromMinutes(config.ContractExpiryMinutes)
|
||||
};
|
||||
log.Log($"Requesting storage: {request}");
|
||||
return codex.RequestStorage(request);
|
||||
}
|
||||
|
||||
private void WaitUntilStarted(string pid)
|
||||
{
|
||||
log.Log("Waiting till contract is started, or expired...");
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
FixedShortDelay();
|
||||
var status = codex.GetPurchaseStatus(pid);
|
||||
if (status != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(status.Error)) log.Log("Contract errored: " + status.Error);
|
||||
var state = status.State.ToLowerInvariant();
|
||||
if (state.Contains("pending") || state.Contains("submitted"))
|
||||
{
|
||||
FixedShortDelay();
|
||||
}
|
||||
else
|
||||
{
|
||||
log.Log("Wait finished with contract status: " + state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Log($"Wait failed with exception: {ex}. Assume contract will expire: Wait expiry time.");
|
||||
ExpiryTimeDelay();
|
||||
}
|
||||
}
|
||||
|
||||
private void ExpiryTimeDelay()
|
||||
{
|
||||
Thread.Sleep(config.ContractExpiryMinutes * 60 * 1000);
|
||||
}
|
||||
|
||||
private void FixedShortDelay()
|
||||
{
|
||||
Thread.Sleep(15 * 1000);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -57,16 +57,27 @@ namespace FileUtils
|
|||
public void ScopedFiles(Action action)
|
||||
{
|
||||
PushFileSet();
|
||||
action();
|
||||
PopFileSet();
|
||||
try
|
||||
{
|
||||
action();
|
||||
}
|
||||
finally
|
||||
{
|
||||
PopFileSet();
|
||||
}
|
||||
}
|
||||
|
||||
public T ScopedFiles<T>(Func<T> action)
|
||||
{
|
||||
PushFileSet();
|
||||
var result = action();
|
||||
PopFileSet();
|
||||
return result;
|
||||
try
|
||||
{
|
||||
return action();
|
||||
}
|
||||
finally
|
||||
{
|
||||
PopFileSet();
|
||||
}
|
||||
}
|
||||
|
||||
private void PushFileSet()
|
||||
|
|
|
@ -66,6 +66,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||
.editorconfig = .editorconfig
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoClient", "AutoClient\AutoClient.csproj", "{8B8BF2B9-5855-4C92-A5DA-D13D778B7934}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -172,6 +174,10 @@ Global
|
|||
{88C212E9-308A-46A4-BAAD-468E8EBD8EDF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{88C212E9-308A-46A4-BAAD-468E8EBD8EDF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{88C212E9-308A-46A4-BAAD-468E8EBD8EDF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8B8BF2B9-5855-4C92-A5DA-D13D778B7934}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8B8BF2B9-5855-4C92-A5DA-D13D778B7934}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8B8BF2B9-5855-4C92-A5DA-D13D778B7934}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8B8BF2B9-5855-4C92-A5DA-D13D778B7934}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -202,6 +208,7 @@ Global
|
|||
{F730DA73-1C92-4107-BCFB-D33759DAB0C3} = {81AE04BC-CBFA-4E6F-B039-8208E9AFAAE7}
|
||||
{B07820C4-309F-4454-BCC1-1D4902C9C67B} = {81AE04BC-CBFA-4E6F-B039-8208E9AFAAE7}
|
||||
{88C212E9-308A-46A4-BAAD-468E8EBD8EDF} = {8F1F1C2A-E313-4E0C-BE40-58FB0BA91124}
|
||||
{8B8BF2B9-5855-4C92-A5DA-D13D778B7934} = {7591C5B3-D86E-4AE4-8ED2-B272D17FE7E3}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {237BF0AA-9EC4-4659-AD9A-65DEB974250C}
|
||||
|
|
Loading…
Reference in New Issue